LCOV - code coverage report
Current view: top level - frmts/netcdf - netcdfdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 4864 5786 84.1 %
Date: 2024-05-04 12:52:34 Functions: 155 162 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             :  * Permission is hereby granted, free of charge, to any person obtaining a
      15             :  * copy of this software and associated documentation files (the "Software"),
      16             :  * to deal in the Software without restriction, including without limitation
      17             :  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
      18             :  * and/or sell copies of the Software, and to permit persons to whom the
      19             :  * Software is furnished to do so, subject to the following conditions:
      20             :  *
      21             :  * The above copyright notice and this permission notice shall be included
      22             :  * in all copies or substantial portions of the Software.
      23             :  *
      24             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
      25             :  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      26             :  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
      27             :  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      28             :  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
      29             :  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
      30             :  * DEALINGS IN THE SOFTWARE.
      31             :  ****************************************************************************/
      32             : 
      33             : #include "cpl_port.h"
      34             : 
      35             : #include <array>
      36             : #include <cassert>
      37             : #include <cctype>
      38             : #include <cerrno>
      39             : #include <climits>
      40             : #include <cmath>
      41             : #include <cstdio>
      42             : #include <cstdlib>
      43             : #include <cstring>
      44             : #include <ctime>
      45             : #include <algorithm>
      46             : #include <limits>
      47             : #include <map>
      48             : #include <set>
      49             : #include <queue>
      50             : #include <string>
      51             : #include <tuple>
      52             : #include <utility>
      53             : #include <vector>
      54             : 
      55             : // Must be included after standard includes, otherwise VS2015 fails when
      56             : // including <ctime>
      57             : #include "netcdfdataset.h"
      58             : #include "netcdfdrivercore.h"
      59             : #include "netcdfsg.h"
      60             : #include "netcdfuffd.h"
      61             : 
      62             : #include "netcdf_mem.h"
      63             : 
      64             : #include "cpl_conv.h"
      65             : #include "cpl_error.h"
      66             : #include "cpl_json.h"
      67             : #include "cpl_minixml.h"
      68             : #include "cpl_multiproc.h"
      69             : #include "cpl_progress.h"
      70             : #include "cpl_time.h"
      71             : #include "gdal.h"
      72             : #include "gdal_frmts.h"
      73             : #include "ogr_core.h"
      74             : #include "ogr_srs_api.h"
      75             : 
      76             : // netCDF 4.8 switched to expecting filenames in UTF-8 on Windows
      77             : // But with netCDF 4.9 and https://github.com/Unidata/netcdf-c/pull/2277/,
      78             : // this is apparently back to expecting filenames in current codepage...
      79             : // Detect netCDF 4.8 with NC_ENCZARR
      80             : // Detect netCDF 4.9 with NC_NOATTCREORD
      81             : #if defined(NC_ENCZARR) && !defined(NC_NOATTCREORD)
      82             : #define NETCDF_USES_UTF8
      83             : #endif
      84             : 
      85             : // Internal function declarations.
      86             : 
      87             : static bool NCDFIsGDALVersionGTE(const char *pszVersion, int nTarget);
      88             : 
      89             : static void
      90             : NCDFAddGDALHistory(int fpImage, const char *pszFilename, bool bWriteGDALVersion,
      91             :                    bool bWriteGDALHistory, const char *pszOldHist,
      92             :                    const char *pszFunctionName,
      93             :                    const char *pszCFVersion = GDAL_DEFAULT_NCDF_CONVENTIONS);
      94             : 
      95             : static void NCDFAddHistory(int fpImage, const char *pszAddHist,
      96             :                            const char *pszOldHist);
      97             : 
      98             : static CPLErr NCDFSafeStrcat(char **ppszDest, const char *pszSrc,
      99             :                              size_t *nDestSize);
     100             : 
     101             : // Var / attribute helper functions.
     102             : static CPLErr NCDFPutAttr(int nCdfId, int nVarId, const char *pszAttrName,
     103             :                           const char *pszValue);
     104             : 
     105             : // Replace this where used.
     106             : static CPLErr NCDFGet1DVar(int nCdfId, int nVarId, char **pszValue);
     107             : static CPLErr NCDFPut1DVar(int nCdfId, int nVarId, const char *pszValue);
     108             : 
     109             : // Replace this where used.
     110             : static char **NCDFTokenizeArray(const char *pszValue);
     111             : static void CopyMetadata(GDALDataset *poSrcDS, GDALRasterBand *poSrcBand,
     112             :                          GDALRasterBand *poDstBand, int fpImage, int CDFVarID,
     113             :                          const char *pszMatchPrefix = nullptr);
     114             : 
     115             : // NetCDF-4 groups helper functions.
     116             : // They all work also for NetCDF-3 files which are considered as
     117             : // NetCDF-4 file with only one group.
     118             : static CPLErr NCDFOpenSubDataset(int nCdfId, const char *pszSubdatasetName,
     119             :                                  int *pnGroupId, int *pnVarId);
     120             : static CPLErr NCDFGetVisibleDims(int nGroupId, int *pnDims, int **ppanDimIds);
     121             : static CPLErr NCDFGetSubGroups(int nGroupId, int *pnSubGroups,
     122             :                                int **ppanSubGroupIds);
     123             : static CPLErr NCDFGetGroupFullName(int nGroupId, char **ppszFullName,
     124             :                                    bool bNC3Compat = true);
     125             : static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId, char **ppszFullName,
     126             :                                  bool bNC3Compat = true);
     127             : static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId);
     128             : 
     129             : static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,
     130             :                                      char **ppszFullName,
     131             :                                      bool bMandatory = false);
     132             : static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,
     133             :                                 const char *pszAtt, int *pnAtt,
     134             :                                 bool bMandatory = false);
     135             : static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId, char ***ppapszVars);
     136             : 
     137             : // Uncomment this for more debug output.
     138             : // #define NCDF_DEBUG 1
     139             : 
     140             : CPLMutex *hNCMutex = nullptr;
     141             : 
     142             : // Workaround https://github.com/OSGeo/gdal/issues/6253
     143             : // Having 2 netCDF handles on the same file doesn't work in a multi-threaded
     144             : // way. Apparently having the same handle works better (this is OK since
     145             : // we have a global mutex on the netCDF library)
     146             : static std::map<std::string, int> goMapNameToNetCDFId;
     147             : static std::map<int, std::pair<std::string, int>> goMapNetCDFIdToKeyAndCount;
     148             : 
     149         644 : int GDAL_nc_open(const char *pszFilename, int nMode, int *pID)
     150             : {
     151        1288 :     std::string osKey(pszFilename);
     152         644 :     osKey += "#####";
     153         644 :     osKey += std::to_string(nMode);
     154         644 :     auto oIter = goMapNameToNetCDFId.find(osKey);
     155         644 :     if (oIter == goMapNameToNetCDFId.end())
     156             :     {
     157         600 :         int ret = nc_open(pszFilename, nMode, pID);
     158         600 :         if (ret != NC_NOERR)
     159           3 :             return ret;
     160         597 :         goMapNameToNetCDFId[osKey] = *pID;
     161         597 :         goMapNetCDFIdToKeyAndCount[*pID] =
     162        1194 :             std::pair<std::string, int>(osKey, 1);
     163         597 :         return ret;
     164             :     }
     165             :     else
     166             :     {
     167          44 :         *pID = oIter->second;
     168          44 :         goMapNetCDFIdToKeyAndCount[oIter->second].second++;
     169          44 :         return NC_NOERR;
     170             :     }
     171             : }
     172             : 
     173         892 : int GDAL_nc_close(int cdfid)
     174             : {
     175         892 :     int ret = NC_NOERR;
     176         892 :     auto oIter = goMapNetCDFIdToKeyAndCount.find(cdfid);
     177         892 :     if (oIter != goMapNetCDFIdToKeyAndCount.end())
     178             :     {
     179         641 :         if (--oIter->second.second == 0)
     180             :         {
     181         597 :             ret = nc_close(cdfid);
     182         597 :             goMapNameToNetCDFId.erase(oIter->second.first);
     183         597 :             goMapNetCDFIdToKeyAndCount.erase(oIter);
     184             :         }
     185             :     }
     186             :     else
     187             :     {
     188             :         // we can go here if file opened with nc_open_mem() or nc_create()
     189         251 :         ret = nc_close(cdfid);
     190             :     }
     191         892 :     return ret;
     192             : }
     193             : 
     194             : /************************************************************************/
     195             : /* ==================================================================== */
     196             : /*                         netCDFRasterBand                             */
     197             : /* ==================================================================== */
     198             : /************************************************************************/
     199             : 
     200             : class netCDFRasterBand final : public GDALPamRasterBand
     201             : {
     202             :     friend class netCDFDataset;
     203             : 
     204             :     nc_type nc_datatype;
     205             :     int cdfid;
     206             :     int nZId;
     207             :     int nZDim;
     208             :     int nLevel;
     209             :     int nBandXPos;
     210             :     int nBandYPos;
     211             :     int *panBandZPos;
     212             :     int *panBandZLev;
     213             :     bool m_bNoDataSet = false;
     214             :     double m_dfNoDataValue = 0;
     215             :     bool m_bNoDataSetAsInt64 = false;
     216             :     int64_t m_nNodataValueInt64 = 0;
     217             :     bool m_bNoDataSetAsUInt64 = false;
     218             :     uint64_t m_nNodataValueUInt64 = 0;
     219             :     bool bValidRangeValid = false;
     220             :     double adfValidRange[2]{0, 0};
     221             :     bool m_bHaveScale = false;
     222             :     bool m_bHaveOffset = false;
     223             :     double m_dfScale = 1;
     224             :     double m_dfOffset = 0;
     225             :     CPLString m_osUnitType{};
     226             :     bool bSignedData;
     227             :     bool bCheckLongitude;
     228             :     bool m_bCreateMetadataFromOtherVarsDone = false;
     229             : 
     230             :     void CreateMetadataFromAttributes();
     231             :     void CreateMetadataFromOtherVars();
     232             : 
     233             :     template <class T>
     234             :     void CheckData(void *pImage, void *pImageNC, size_t nTmpBlockXSize,
     235             :                    size_t nTmpBlockYSize, bool bCheckIsNan = false);
     236             :     template <class T>
     237             :     void CheckDataCpx(void *pImage, void *pImageNC, size_t nTmpBlockXSize,
     238             :                       size_t nTmpBlockYSize, bool bCheckIsNan = false);
     239             :     void SetBlockSize();
     240             : 
     241             :     bool FetchNetcdfChunk(size_t xstart, size_t ystart, void *pImage);
     242             : 
     243             :     void SetNoDataValueNoUpdate(double dfNoData);
     244             :     void SetNoDataValueNoUpdate(int64_t nNoData);
     245             :     void SetNoDataValueNoUpdate(uint64_t nNoData);
     246             : 
     247             :     void SetOffsetNoUpdate(double dfVal);
     248             :     void SetScaleNoUpdate(double dfVal);
     249             :     void SetUnitTypeNoUpdate(const char *pszNewValue);
     250             : 
     251             :   protected:
     252             :     CPLXMLNode *SerializeToXML(const char *pszUnused) override;
     253             : 
     254             :   public:
     255             :     struct CONSTRUCTOR_OPEN
     256             :     {
     257             :     };
     258             : 
     259             :     struct CONSTRUCTOR_CREATE
     260             :     {
     261             :     };
     262             : 
     263             :     netCDFRasterBand(const CONSTRUCTOR_OPEN &, netCDFDataset *poDS,
     264             :                      int nGroupId, int nZId, int nZDim, int nLevel,
     265             :                      const int *panBandZLen, const int *panBandPos, int nBand);
     266             :     netCDFRasterBand(const CONSTRUCTOR_CREATE &, netCDFDataset *poDS,
     267             :                      GDALDataType eType, int nBand, bool bSigned = true,
     268             :                      const char *pszBandName = nullptr,
     269             :                      const char *pszLongName = nullptr, int nZId = -1,
     270             :                      int nZDim = 2, int nLevel = 0,
     271             :                      const int *panBandZLev = nullptr,
     272             :                      const int *panBandZPos = nullptr,
     273             :                      const int *paDimIds = nullptr);
     274             :     virtual ~netCDFRasterBand();
     275             : 
     276             :     virtual double GetNoDataValue(int *) override;
     277             :     virtual int64_t GetNoDataValueAsInt64(int *pbSuccess = nullptr) override;
     278             :     virtual uint64_t GetNoDataValueAsUInt64(int *pbSuccess = nullptr) override;
     279             :     virtual CPLErr SetNoDataValue(double) override;
     280             :     virtual CPLErr SetNoDataValueAsInt64(int64_t nNoData) override;
     281             :     virtual CPLErr SetNoDataValueAsUInt64(uint64_t nNoData) override;
     282             :     // virtual CPLErr DeleteNoDataValue();
     283             :     virtual double GetOffset(int *) override;
     284             :     virtual CPLErr SetOffset(double) override;
     285             :     virtual double GetScale(int *) override;
     286             :     virtual CPLErr SetScale(double) override;
     287             :     virtual const char *GetUnitType() override;
     288             :     virtual CPLErr SetUnitType(const char *) override;
     289             :     virtual CPLErr IReadBlock(int, int, void *) override;
     290             :     virtual CPLErr IWriteBlock(int, int, void *) override;
     291             : 
     292             :     char **GetMetadata(const char *pszDomain = "") override;
     293             :     const char *GetMetadataItem(const char *pszName,
     294             :                                 const char *pszDomain = "") override;
     295             : 
     296             :     virtual CPLErr SetMetadataItem(const char *pszName, const char *pszValue,
     297             :                                    const char *pszDomain = "") override;
     298             :     virtual CPLErr SetMetadata(char **papszMD,
     299             :                                const char *pszDomain = "") override;
     300             : };
     301             : 
     302             : /************************************************************************/
     303             : /*                          netCDFRasterBand()                          */
     304             : /************************************************************************/
     305             : 
     306         451 : netCDFRasterBand::netCDFRasterBand(const netCDFRasterBand::CONSTRUCTOR_OPEN &,
     307             :                                    netCDFDataset *poNCDFDS, int nGroupId,
     308             :                                    int nZIdIn, int nZDimIn, int nLevelIn,
     309             :                                    const int *panBandZLevIn,
     310         451 :                                    const int *panBandZPosIn, int nBandIn)
     311             :     : nc_datatype(NC_NAT), cdfid(nGroupId), nZId(nZIdIn), nZDim(nZDimIn),
     312         451 :       nLevel(nLevelIn), nBandXPos(panBandZPosIn[0]),
     313         451 :       nBandYPos(nZDim == 1 ? -1 : panBandZPosIn[1]), panBandZPos(nullptr),
     314             :       panBandZLev(nullptr),
     315             :       bSignedData(true),  // Default signed, except for Byte.
     316         902 :       bCheckLongitude(false)
     317             : {
     318         451 :     poDS = poNCDFDS;
     319         451 :     nBand = nBandIn;
     320             : 
     321             :     // Take care of all other dimensions.
     322         451 :     if (nZDim > 2)
     323             :     {
     324         150 :         panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
     325         150 :         panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
     326             : 
     327         425 :         for (int i = 0; i < nZDim - 2; i++)
     328             :         {
     329         275 :             panBandZPos[i] = panBandZPosIn[i + 2];
     330         275 :             panBandZLev[i] = panBandZLevIn[i];
     331             :         }
     332             :     }
     333             : 
     334         451 :     nRasterXSize = poDS->GetRasterXSize();
     335         451 :     nRasterYSize = poDS->GetRasterYSize();
     336         451 :     nBlockXSize = poDS->GetRasterXSize();
     337         451 :     nBlockYSize = 1;
     338             : 
     339             :     // Get the type of the "z" variable, our target raster array.
     340         451 :     if (nc_inq_var(cdfid, nZId, nullptr, &nc_datatype, nullptr, nullptr,
     341         451 :                    nullptr) != NC_NOERR)
     342             :     {
     343           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Error in nc_var_inq() on 'z'.");
     344           0 :         return;
     345             :     }
     346             : 
     347         451 :     if (NCDFIsUserDefinedType(cdfid, nc_datatype))
     348             :     {
     349             :         // First enquire and check that the number of fields is 2
     350             :         size_t nfields, compoundsize;
     351           5 :         if (nc_inq_compound(cdfid, nc_datatype, nullptr, &compoundsize,
     352           5 :                             &nfields) != NC_NOERR)
     353             :         {
     354           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     355             :                      "Error in nc_inq_compound() on 'z'.");
     356           0 :             return;
     357             :         }
     358             : 
     359           5 :         if (nfields != 2)
     360             :         {
     361           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     362             :                      "Unsupported data type encountered in nc_inq_compound() "
     363             :                      "on 'z'.");
     364           0 :             return;
     365             :         }
     366             : 
     367             :         // Now check that that two types are the same in the struct.
     368             :         nc_type field_type1, field_type2;
     369             :         int field_dims1, field_dims2;
     370           5 :         if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
     371             :                                   &field_type1, &field_dims1,
     372           5 :                                   nullptr) != NC_NOERR)
     373             :         {
     374           0 :             CPLError(
     375             :                 CE_Failure, CPLE_AppDefined,
     376             :                 "Error in querying Field 1 in nc_inq_compound_field() on 'z'.");
     377           0 :             return;
     378             :         }
     379             : 
     380           5 :         if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
     381             :                                   &field_type2, &field_dims2,
     382           5 :                                   nullptr) != NC_NOERR)
     383             :         {
     384           0 :             CPLError(
     385             :                 CE_Failure, CPLE_AppDefined,
     386             :                 "Error in querying Field 2 in nc_inq_compound_field() on 'z'.");
     387           0 :             return;
     388             :         }
     389             : 
     390           5 :         if ((field_type1 != field_type2) || (field_dims1 != field_dims2) ||
     391           5 :             (field_dims1 != 0))
     392             :         {
     393           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     394             :                      "Error in interpreting compound data type on 'z'.");
     395           0 :             return;
     396             :         }
     397             : 
     398           5 :         if (field_type1 == NC_SHORT)
     399           0 :             eDataType = GDT_CInt16;
     400           5 :         else if (field_type1 == NC_INT)
     401           0 :             eDataType = GDT_CInt32;
     402           5 :         else if (field_type1 == NC_FLOAT)
     403           4 :             eDataType = GDT_CFloat32;
     404           1 :         else if (field_type1 == NC_DOUBLE)
     405           1 :             eDataType = GDT_CFloat64;
     406             :         else
     407             :         {
     408           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     409             :                      "Unsupported netCDF compound data type encountered.");
     410           0 :             return;
     411             :         }
     412             :     }
     413             :     else
     414             :     {
     415         446 :         if (nc_datatype == NC_BYTE)
     416         127 :             eDataType = GDT_Byte;
     417         319 :         else if (nc_datatype == NC_CHAR)
     418           0 :             eDataType = GDT_Byte;
     419         319 :         else if (nc_datatype == NC_SHORT)
     420          42 :             eDataType = GDT_Int16;
     421         277 :         else if (nc_datatype == NC_INT)
     422          88 :             eDataType = GDT_Int32;
     423         189 :         else if (nc_datatype == NC_FLOAT)
     424         106 :             eDataType = GDT_Float32;
     425          83 :         else if (nc_datatype == NC_DOUBLE)
     426          40 :             eDataType = GDT_Float64;
     427          43 :         else if (nc_datatype == NC_UBYTE)
     428          14 :             eDataType = GDT_Byte;
     429          29 :         else if (nc_datatype == NC_USHORT)
     430           6 :             eDataType = GDT_UInt16;
     431          23 :         else if (nc_datatype == NC_UINT)
     432           4 :             eDataType = GDT_UInt32;
     433          19 :         else if (nc_datatype == NC_INT64)
     434          10 :             eDataType = GDT_Int64;
     435           9 :         else if (nc_datatype == NC_UINT64)
     436           9 :             eDataType = GDT_UInt64;
     437             :         else
     438             :         {
     439           0 :             if (nBand == 1)
     440           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     441             :                          "Unsupported netCDF datatype (%d), treat as Float32.",
     442           0 :                          static_cast<int>(nc_datatype));
     443           0 :             eDataType = GDT_Float32;
     444           0 :             nc_datatype = NC_FLOAT;
     445             :         }
     446             :     }
     447             : 
     448             :     // Find and set No Data for this variable.
     449         451 :     nc_type atttype = NC_NAT;
     450         451 :     size_t attlen = 0;
     451         451 :     const char *pszNoValueName = nullptr;
     452             : 
     453             :     // Find attribute name, either _FillValue or missing_value.
     454         451 :     int status = nc_inq_att(cdfid, nZId, _FillValue, &atttype, &attlen);
     455         451 :     if (status == NC_NOERR)
     456             :     {
     457         247 :         pszNoValueName = _FillValue;
     458             :     }
     459             :     else
     460             :     {
     461         204 :         status = nc_inq_att(cdfid, nZId, "missing_value", &atttype, &attlen);
     462         204 :         if (status == NC_NOERR)
     463             :         {
     464          12 :             pszNoValueName = "missing_value";
     465             :         }
     466             :     }
     467             : 
     468             :     // Fetch missing value.
     469         451 :     double dfNoData = 0.0;
     470         451 :     bool bGotNoData = false;
     471         451 :     int64_t nNoDataAsInt64 = 0;
     472         451 :     bool bGotNoDataAsInt64 = false;
     473         451 :     uint64_t nNoDataAsUInt64 = 0;
     474         451 :     bool bGotNoDataAsUInt64 = false;
     475         451 :     if (status == NC_NOERR)
     476             :     {
     477         259 :         nc_type nAttrType = NC_NAT;
     478         259 :         size_t nAttrLen = 0;
     479         259 :         status = nc_inq_att(cdfid, nZId, pszNoValueName, &nAttrType, &nAttrLen);
     480         259 :         if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_INT64)
     481             :         {
     482             :             long long v;
     483           9 :             nc_get_att_longlong(cdfid, nZId, pszNoValueName, &v);
     484           9 :             bGotNoData = true;
     485           9 :             bGotNoDataAsInt64 = true;
     486           9 :             nNoDataAsInt64 = static_cast<int64_t>(v);
     487             :         }
     488         250 :         else if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_UINT64)
     489             :         {
     490             :             unsigned long long v;
     491           9 :             nc_get_att_ulonglong(cdfid, nZId, pszNoValueName, &v);
     492           9 :             bGotNoData = true;
     493           9 :             bGotNoDataAsUInt64 = true;
     494           9 :             nNoDataAsUInt64 = static_cast<uint64_t>(v);
     495             :         }
     496         241 :         else if (NCDFGetAttr(cdfid, nZId, pszNoValueName, &dfNoData) == CE_None)
     497             :         {
     498         240 :             bGotNoData = true;
     499             :         }
     500             :     }
     501             : 
     502             :     // If NoData was not found, use the default value, but for non-Byte types
     503             :     // as it is not recommended:
     504             :     // https://www.unidata.ucar.edu/software/netcdf/docs/attribute_conventions.html
     505         451 :     nc_type vartype = NC_NAT;
     506         451 :     if (!bGotNoData)
     507             :     {
     508         193 :         nc_inq_vartype(cdfid, nZId, &vartype);
     509         193 :         if (vartype == NC_INT64)
     510             :         {
     511             :             nNoDataAsInt64 =
     512           1 :                 NCDFGetDefaultNoDataValueAsInt64(cdfid, nZId, bGotNoData);
     513           1 :             bGotNoDataAsInt64 = bGotNoData;
     514             :         }
     515         192 :         else if (vartype == NC_UINT64)
     516             :         {
     517             :             nNoDataAsUInt64 =
     518           0 :                 NCDFGetDefaultNoDataValueAsUInt64(cdfid, nZId, bGotNoData);
     519           0 :             bGotNoDataAsUInt64 = bGotNoData;
     520             :         }
     521         192 :         else if (vartype != NC_CHAR && vartype != NC_BYTE &&
     522          87 :                  vartype != NC_UBYTE)
     523             :         {
     524          79 :             dfNoData =
     525          79 :                 NCDFGetDefaultNoDataValue(cdfid, nZId, vartype, bGotNoData);
     526          79 :             if (bGotNoData)
     527             :             {
     528          69 :                 CPLDebug("GDAL_netCDF",
     529             :                          "did not get nodata value for variable #%d, using "
     530             :                          "default %f",
     531             :                          nZId, dfNoData);
     532             :             }
     533             :         }
     534             :     }
     535             : 
     536         451 :     bool bHasUnderscoreUnsignedAttr = false;
     537         451 :     bool bUnderscoreUnsignedAttrVal = false;
     538             :     {
     539         451 :         char *pszTemp = nullptr;
     540         451 :         if (NCDFGetAttr(cdfid, nZId, "_Unsigned", &pszTemp) == CE_None)
     541             :         {
     542         120 :             if (EQUAL(pszTemp, "true"))
     543             :             {
     544         112 :                 bHasUnderscoreUnsignedAttr = true;
     545         112 :                 bUnderscoreUnsignedAttrVal = true;
     546             :             }
     547           8 :             else if (EQUAL(pszTemp, "false"))
     548             :             {
     549           8 :                 bHasUnderscoreUnsignedAttr = true;
     550           8 :                 bUnderscoreUnsignedAttrVal = false;
     551             :             }
     552         120 :             CPLFree(pszTemp);
     553             :         }
     554             :     }
     555             : 
     556             :     // Look for valid_range or valid_min/valid_max.
     557             : 
     558             :     // First look for valid_range.
     559         451 :     if (CPLFetchBool(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE", true))
     560             :     {
     561         449 :         char *pszValidRange = nullptr;
     562         449 :         if (NCDFGetAttr(cdfid, nZId, "valid_range", &pszValidRange) ==
     563         113 :                 CE_None &&
     564         562 :             pszValidRange[0] == '{' &&
     565         113 :             pszValidRange[strlen(pszValidRange) - 1] == '}')
     566             :         {
     567             :             const std::string osValidRange =
     568         339 :                 std::string(pszValidRange).substr(1, strlen(pszValidRange) - 2);
     569             :             const CPLStringList aosValidRange(
     570         226 :                 CSLTokenizeString2(osValidRange.c_str(), ",", 0));
     571         113 :             if (aosValidRange.size() == 2 &&
     572         226 :                 CPLGetValueType(aosValidRange[0]) != CPL_VALUE_STRING &&
     573         113 :                 CPLGetValueType(aosValidRange[1]) != CPL_VALUE_STRING)
     574             :             {
     575         113 :                 bValidRangeValid = true;
     576         113 :                 adfValidRange[0] = CPLAtof(aosValidRange[0]);
     577         113 :                 adfValidRange[1] = CPLAtof(aosValidRange[1]);
     578             :             }
     579             :         }
     580         449 :         CPLFree(pszValidRange);
     581             : 
     582             :         // If not found look for valid_min and valid_max.
     583         449 :         if (!bValidRangeValid)
     584             :         {
     585         336 :             double dfMin = 0;
     586         336 :             double dfMax = 0;
     587         351 :             if (NCDFGetAttr(cdfid, nZId, "valid_min", &dfMin) == CE_None &&
     588          15 :                 NCDFGetAttr(cdfid, nZId, "valid_max", &dfMax) == CE_None)
     589             :             {
     590           8 :                 adfValidRange[0] = dfMin;
     591           8 :                 adfValidRange[1] = dfMax;
     592           8 :                 bValidRangeValid = true;
     593             :             }
     594             :         }
     595             : 
     596         449 :         if (bValidRangeValid &&
     597         121 :             (adfValidRange[0] < 0 || adfValidRange[1] < 0) &&
     598          18 :             nc_datatype == NC_SHORT && bHasUnderscoreUnsignedAttr &&
     599             :             bUnderscoreUnsignedAttrVal)
     600             :         {
     601           3 :             if (adfValidRange[0] < 0)
     602           0 :                 adfValidRange[0] += 65536;
     603           3 :             if (adfValidRange[1] < 0)
     604           3 :                 adfValidRange[1] += 65536;
     605           3 :             if (adfValidRange[0] <= adfValidRange[1])
     606             :             {
     607             :                 // Updating metadata item
     608           3 :                 GDALPamRasterBand::SetMetadataItem(
     609             :                     "valid_range",
     610           3 :                     CPLSPrintf("{%d,%d}", static_cast<int>(adfValidRange[0]),
     611           3 :                                static_cast<int>(adfValidRange[1])));
     612             :             }
     613             :         }
     614             : 
     615         449 :         if (bValidRangeValid && adfValidRange[0] > adfValidRange[1])
     616             :         {
     617           0 :             CPLError(CE_Warning, CPLE_AppDefined,
     618             :                      "netCDFDataset::valid_range: min > max:\n"
     619             :                      "  min: %lf\n  max: %lf\n",
     620             :                      adfValidRange[0], adfValidRange[1]);
     621           0 :             bValidRangeValid = false;
     622           0 :             adfValidRange[0] = 0.0;
     623           0 :             adfValidRange[1] = 0.0;
     624             :         }
     625             :     }
     626             : 
     627             :     // Special For Byte Bands: check for signed/unsigned byte.
     628         451 :     if (nc_datatype == NC_BYTE)
     629             :     {
     630             :         // netcdf uses signed byte by default, but GDAL uses unsigned by default
     631             :         // This may cause unexpected results, but is needed for back-compat.
     632         127 :         if (poNCDFDS->bIsGdalFile)
     633         106 :             bSignedData = false;
     634             :         else
     635          21 :             bSignedData = true;
     636             : 
     637             :         // For NC4 format NC_BYTE is (normally) signed, NC_UBYTE is unsigned.
     638             :         // But in case a NC3 file was converted automatically and has hints
     639             :         // that it is unsigned, take them into account
     640         127 :         if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     641             :         {
     642           3 :             bSignedData = true;
     643             :         }
     644             : 
     645             :         // If we got valid_range, test for signed/unsigned range.
     646             :         // https://docs.unidata.ucar.edu/netcdf-c/current/attribute_conventions.html
     647         127 :         if (bValidRangeValid)
     648             :         {
     649             :             // If we got valid_range={0,255}, treat as unsigned.
     650         108 :             if (adfValidRange[0] == 0 && adfValidRange[1] == 255)
     651             :             {
     652         100 :                 bSignedData = false;
     653             :                 // Reset valid_range.
     654         100 :                 bValidRangeValid = false;
     655             :             }
     656             :             // If we got valid_range={-128,127}, treat as signed.
     657           8 :             else if (adfValidRange[0] == -128 && adfValidRange[1] == 127)
     658             :             {
     659           8 :                 bSignedData = true;
     660             :                 // Reset valid_range.
     661           8 :                 bValidRangeValid = false;
     662             :             }
     663             :         }
     664             :         // Else test for _Unsigned.
     665             :         // https://docs.unidata.ucar.edu/nug/current/best_practices.html
     666             :         else
     667             :         {
     668          19 :             if (bHasUnderscoreUnsignedAttr)
     669           7 :                 bSignedData = !bUnderscoreUnsignedAttrVal;
     670             :         }
     671             : 
     672         127 :         if (bSignedData)
     673             :         {
     674          20 :             eDataType = GDT_Int8;
     675             :         }
     676         107 :         else if (dfNoData < 0)
     677             :         {
     678             :             // Fix nodata value as it was stored signed.
     679           5 :             dfNoData += 256;
     680           5 :             if (pszNoValueName)
     681             :             {
     682             :                 // Updating metadata item
     683           5 :                 GDALPamRasterBand::SetMetadataItem(
     684             :                     pszNoValueName,
     685             :                     CPLSPrintf("%d", static_cast<int>(dfNoData)));
     686             :             }
     687             :         }
     688             :     }
     689         324 :     else if (nc_datatype == NC_SHORT)
     690             :     {
     691          42 :         if (bHasUnderscoreUnsignedAttr)
     692             :         {
     693           5 :             bSignedData = !bUnderscoreUnsignedAttrVal;
     694           5 :             if (!bSignedData)
     695           5 :                 eDataType = GDT_UInt16;
     696             :         }
     697             : 
     698             :         // Fix nodata value as it was stored signed.
     699          42 :         if (!bSignedData && dfNoData < 0)
     700             :         {
     701           5 :             dfNoData += 65536;
     702           5 :             if (pszNoValueName)
     703             :             {
     704             :                 // Updating metadata item
     705           5 :                 GDALPamRasterBand::SetMetadataItem(
     706             :                     pszNoValueName,
     707             :                     CPLSPrintf("%d", static_cast<int>(dfNoData)));
     708             :             }
     709             :         }
     710             :     }
     711             : 
     712         282 :     else if (nc_datatype == NC_UBYTE || nc_datatype == NC_USHORT ||
     713         262 :              nc_datatype == NC_UINT || nc_datatype == NC_UINT64)
     714             :     {
     715          33 :         bSignedData = false;
     716             :     }
     717             : 
     718         451 :     CPLDebug("GDAL_netCDF", "netcdf type=%d gdal type=%d signedByte=%d",
     719         451 :              nc_datatype, eDataType, static_cast<int>(bSignedData));
     720             : 
     721         451 :     if (bGotNoData)
     722             :     {
     723             :         // Set nodata value.
     724         328 :         if (bGotNoDataAsInt64)
     725             :         {
     726          10 :             if (eDataType == GDT_Int64)
     727             :             {
     728          10 :                 SetNoDataValueNoUpdate(nNoDataAsInt64);
     729             :             }
     730           0 :             else if (eDataType == GDT_UInt64 && nNoDataAsInt64 >= 0)
     731             :             {
     732           0 :                 SetNoDataValueNoUpdate(static_cast<uint64_t>(nNoDataAsInt64));
     733             :             }
     734             :             else
     735             :             {
     736           0 :                 SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsInt64));
     737             :             }
     738             :         }
     739         318 :         else if (bGotNoDataAsUInt64)
     740             :         {
     741           9 :             if (eDataType == GDT_UInt64)
     742             :             {
     743           9 :                 SetNoDataValueNoUpdate(nNoDataAsUInt64);
     744             :             }
     745           0 :             else if (eDataType == GDT_Int64 &&
     746             :                      nNoDataAsUInt64 <=
     747           0 :                          static_cast<uint64_t>(
     748           0 :                              std::numeric_limits<int64_t>::max()))
     749             :             {
     750           0 :                 SetNoDataValueNoUpdate(static_cast<int64_t>(nNoDataAsUInt64));
     751             :             }
     752             :             else
     753             :             {
     754           0 :                 SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsUInt64));
     755             :             }
     756             :         }
     757             :         else
     758             :         {
     759             : #ifdef NCDF_DEBUG
     760             :             CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) read", dfNoData);
     761             : #endif
     762         618 :             if (eDataType == GDT_Int64 &&
     763           0 :                 dfNoData >=
     764           0 :                     static_cast<double>(std::numeric_limits<int64_t>::min()) &&
     765           0 :                 dfNoData <=
     766         309 :                     static_cast<double>(std::numeric_limits<int64_t>::max()) &&
     767           0 :                 dfNoData == static_cast<double>(static_cast<int64_t>(dfNoData)))
     768             :             {
     769           0 :                 SetNoDataValueNoUpdate(static_cast<int64_t>(dfNoData));
     770             :             }
     771         618 :             else if (eDataType == GDT_UInt64 &&
     772           0 :                      dfNoData >= static_cast<double>(
     773           0 :                                      std::numeric_limits<uint64_t>::min()) &&
     774           0 :                      dfNoData <= static_cast<double>(
     775         309 :                                      std::numeric_limits<uint64_t>::max()) &&
     776           0 :                      dfNoData ==
     777           0 :                          static_cast<double>(static_cast<uint64_t>(dfNoData)))
     778             :             {
     779           0 :                 SetNoDataValueNoUpdate(static_cast<uint64_t>(dfNoData));
     780             :             }
     781             :             else
     782             :             {
     783         309 :                 SetNoDataValueNoUpdate(dfNoData);
     784             :             }
     785             :         }
     786             :     }
     787             : 
     788         451 :     CreateMetadataFromAttributes();
     789             : 
     790             :     // Attempt to fetch the scale_factor and add_offset attributes for the
     791             :     // variable and set them.  If these values are not available, set
     792             :     // offset to 0 and scale to 1.
     793         451 :     if (nc_inq_attid(cdfid, nZId, CF_ADD_OFFSET, nullptr) == NC_NOERR)
     794             :     {
     795          17 :         double dfOffset = 0;
     796          17 :         status = nc_get_att_double(cdfid, nZId, CF_ADD_OFFSET, &dfOffset);
     797          17 :         CPLDebug("GDAL_netCDF", "got add_offset=%.16g, status=%d", dfOffset,
     798             :                  status);
     799          17 :         SetOffsetNoUpdate(dfOffset);
     800             :     }
     801             : 
     802         451 :     bool bHasScale = false;
     803         451 :     if (nc_inq_attid(cdfid, nZId, CF_SCALE_FACTOR, nullptr) == NC_NOERR)
     804             :     {
     805          21 :         bHasScale = true;
     806          21 :         double dfScale = 1;
     807          21 :         status = nc_get_att_double(cdfid, nZId, CF_SCALE_FACTOR, &dfScale);
     808          21 :         CPLDebug("GDAL_netCDF", "got scale_factor=%.16g, status=%d", dfScale,
     809             :                  status);
     810          21 :         SetScaleNoUpdate(dfScale);
     811             :     }
     812             : 
     813          13 :     if (bValidRangeValid && GDALDataTypeIsInteger(eDataType) &&
     814           5 :         eDataType != GDT_Int64 && eDataType != GDT_UInt64 &&
     815           5 :         (std::fabs(std::round(adfValidRange[0]) - adfValidRange[0]) > 1e-5 ||
     816         464 :          std::fabs(std::round(adfValidRange[1]) - adfValidRange[1]) > 1e-5) &&
     817           1 :         CSLFetchNameValue(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE") ==
     818             :             nullptr)
     819             :     {
     820           1 :         CPLError(CE_Warning, CPLE_AppDefined,
     821             :                  "validity range = %f, %f contains floating-point values, "
     822             :                  "whereas data type is integer. valid_range is thus likely "
     823             :                  "wrong%s. Ignoring it.",
     824             :                  adfValidRange[0], adfValidRange[1],
     825             :                  bHasScale ? " (likely scaled using scale_factor/add_factor "
     826             :                              "whereas it should be using the packed data type)"
     827             :                            : "");
     828           1 :         bValidRangeValid = false;
     829           1 :         adfValidRange[0] = 0.0;
     830           1 :         adfValidRange[1] = 0.0;
     831             :     }
     832             : 
     833             :     // Should we check for longitude values > 360?
     834         451 :     bCheckLongitude =
     835         902 :         CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")) &&
     836         451 :         NCDFIsVarLongitude(cdfid, nZId, nullptr);
     837             : 
     838             :     // Attempt to fetch the units attribute for the variable and set it.
     839         451 :     SetUnitTypeNoUpdate(netCDFRasterBand::GetMetadataItem(CF_UNITS));
     840             : 
     841         451 :     SetBlockSize();
     842             : }
     843             : 
     844         622 : void netCDFRasterBand::SetBlockSize()
     845             : {
     846             :     // Check for variable chunking (netcdf-4 only).
     847             :     // GDAL block size should be set to hdf5 chunk size.
     848         622 :     int nTmpFormat = 0;
     849         622 :     int status = nc_inq_format(cdfid, &nTmpFormat);
     850         622 :     NetCDFFormatEnum eTmpFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
     851         622 :     if ((status == NC_NOERR) &&
     852         524 :         (eTmpFormat == NCDF_FORMAT_NC4 || eTmpFormat == NCDF_FORMAT_NC4C))
     853             :     {
     854         112 :         size_t chunksize[MAX_NC_DIMS] = {};
     855             :         // Check for chunksize and set it as the blocksize (optimizes read).
     856         112 :         status = nc_inq_var_chunking(cdfid, nZId, &nTmpFormat, chunksize);
     857         112 :         if ((status == NC_NOERR) && (nTmpFormat == NC_CHUNKED))
     858             :         {
     859          11 :             nBlockXSize = (int)chunksize[nZDim - 1];
     860          11 :             if (nZDim >= 2)
     861          11 :                 nBlockYSize = (int)chunksize[nZDim - 2];
     862             :             else
     863           0 :                 nBlockYSize = 1;
     864             :         }
     865             :     }
     866             : 
     867             :     // Deal with bottom-up datasets and nBlockYSize != 1.
     868         622 :     auto poGDS = static_cast<netCDFDataset *>(poDS);
     869         622 :     if (poGDS->bBottomUp && nBlockYSize != 1 && poGDS->poChunkCache == nullptr)
     870             :     {
     871           5 :         if (poGDS->eAccess == GA_ReadOnly)
     872             :         {
     873             :             // Try to cache 1 or 2 'rows' of netCDF chunks along the whole
     874             :             // width of the raster
     875           5 :             size_t nChunks =
     876           5 :                 static_cast<size_t>(DIV_ROUND_UP(nRasterXSize, nBlockXSize));
     877           5 :             if ((nRasterYSize % nBlockYSize) != 0)
     878           1 :                 nChunks *= 2;
     879             :             const size_t nChunkSize =
     880           5 :                 static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
     881           5 :                 nBlockXSize * nBlockYSize;
     882           5 :             constexpr size_t MAX_CACHE_SIZE = 100 * 1024 * 1024;
     883           5 :             nChunks = std::min(nChunks, MAX_CACHE_SIZE / nChunkSize);
     884           5 :             if (nChunks)
     885             :             {
     886           5 :                 poGDS->poChunkCache.reset(
     887           5 :                     new netCDFDataset::ChunkCacheType(nChunks));
     888             :             }
     889             :         }
     890             :         else
     891             :         {
     892           0 :             nBlockYSize = 1;
     893             :         }
     894             :     }
     895         622 : }
     896             : 
     897             : // Constructor in create mode.
     898             : // If nZId and following variables are not passed, the band will have 2
     899             : // dimensions.
     900             : // TODO: Get metadata, missing val from band #1 if nZDim > 2.
     901         171 : netCDFRasterBand::netCDFRasterBand(
     902             :     const netCDFRasterBand::CONSTRUCTOR_CREATE &, netCDFDataset *poNCDFDS,
     903             :     const GDALDataType eTypeIn, int nBandIn, bool bSigned,
     904             :     const char *pszBandName, const char *pszLongName, int nZIdIn, int nZDimIn,
     905             :     int nLevelIn, const int *panBandZLevIn, const int *panBandZPosIn,
     906         171 :     const int *paDimIds)
     907         171 :     : nc_datatype(NC_NAT), cdfid(poNCDFDS->GetCDFID()), nZId(nZIdIn),
     908             :       nZDim(nZDimIn), nLevel(nLevelIn), nBandXPos(1), nBandYPos(0),
     909             :       panBandZPos(nullptr), panBandZLev(nullptr), bSignedData(bSigned),
     910         171 :       bCheckLongitude(false), m_bCreateMetadataFromOtherVarsDone(true)
     911             : {
     912         171 :     poDS = poNCDFDS;
     913         171 :     nBand = nBandIn;
     914             : 
     915         171 :     nRasterXSize = poDS->GetRasterXSize();
     916         171 :     nRasterYSize = poDS->GetRasterYSize();
     917         171 :     nBlockXSize = poDS->GetRasterXSize();
     918         171 :     nBlockYSize = 1;
     919             : 
     920         171 :     if (poDS->GetAccess() != GA_Update)
     921             :     {
     922           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     923             :                  "Dataset is not in update mode, "
     924             :                  "wrong netCDFRasterBand constructor");
     925           0 :         return;
     926             :     }
     927             : 
     928             :     // Take care of all other dimensions.
     929         171 :     if (nZDim > 2 && paDimIds != nullptr)
     930             :     {
     931          23 :         nBandXPos = panBandZPosIn[0];
     932          23 :         nBandYPos = panBandZPosIn[1];
     933             : 
     934          23 :         panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
     935          23 :         panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
     936             : 
     937          68 :         for (int i = 0; i < nZDim - 2; i++)
     938             :         {
     939          45 :             panBandZPos[i] = panBandZPosIn[i + 2];
     940          45 :             panBandZLev[i] = panBandZLevIn[i];
     941             :         }
     942             :     }
     943             : 
     944             :     // Get the type of the "z" variable, our target raster array.
     945         171 :     eDataType = eTypeIn;
     946             : 
     947         171 :     switch (eDataType)
     948             :     {
     949          66 :         case GDT_Byte:
     950          66 :             nc_datatype = NC_BYTE;
     951             :             // NC_UBYTE (unsigned byte) is only available for NC4.
     952          66 :             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     953           3 :                 nc_datatype = NC_UBYTE;
     954          66 :             break;
     955           7 :         case GDT_Int8:
     956           7 :             nc_datatype = NC_BYTE;
     957           7 :             break;
     958          11 :         case GDT_Int16:
     959          11 :             nc_datatype = NC_SHORT;
     960          11 :             break;
     961          24 :         case GDT_Int32:
     962          24 :             nc_datatype = NC_INT;
     963          24 :             break;
     964          13 :         case GDT_Float32:
     965          13 :             nc_datatype = NC_FLOAT;
     966          13 :             break;
     967           8 :         case GDT_Float64:
     968           8 :             nc_datatype = NC_DOUBLE;
     969           8 :             break;
     970           7 :         case GDT_Int64:
     971           7 :             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     972             :             {
     973           7 :                 nc_datatype = NC_INT64;
     974             :             }
     975             :             else
     976             :             {
     977           0 :                 if (nBand == 1)
     978           0 :                     CPLError(
     979             :                         CE_Warning, CPLE_AppDefined,
     980             :                         "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
     981             :                         "Int64");
     982           0 :                 nc_datatype = NC_DOUBLE;
     983           0 :                 eDataType = GDT_Float64;
     984             :             }
     985           7 :             break;
     986           7 :         case GDT_UInt64:
     987           7 :             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     988             :             {
     989           7 :                 nc_datatype = NC_UINT64;
     990             :             }
     991             :             else
     992             :             {
     993           0 :                 if (nBand == 1)
     994           0 :                     CPLError(
     995             :                         CE_Warning, CPLE_AppDefined,
     996             :                         "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
     997             :                         "UInt64");
     998           0 :                 nc_datatype = NC_DOUBLE;
     999           0 :                 eDataType = GDT_Float64;
    1000             :             }
    1001           7 :             break;
    1002           6 :         case GDT_UInt16:
    1003           6 :             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
    1004             :             {
    1005           6 :                 nc_datatype = NC_USHORT;
    1006           6 :                 break;
    1007             :             }
    1008             :             [[fallthrough]];
    1009             :         case GDT_UInt32:
    1010           6 :             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
    1011             :             {
    1012           6 :                 nc_datatype = NC_UINT;
    1013           6 :                 break;
    1014             :             }
    1015             :             [[fallthrough]];
    1016             :         default:
    1017          16 :             if (nBand == 1)
    1018           8 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1019             :                          "Unsupported GDAL datatype (%d), treat as NC_FLOAT.",
    1020           8 :                          static_cast<int>(eDataType));
    1021          16 :             nc_datatype = NC_FLOAT;
    1022          16 :             eDataType = GDT_Float32;
    1023          16 :             break;
    1024             :     }
    1025             : 
    1026             :     // Define the variable if necessary (if nZId == -1).
    1027         171 :     bool bDefineVar = false;
    1028             : 
    1029         171 :     if (nZId == -1)
    1030             :     {
    1031         152 :         bDefineVar = true;
    1032             : 
    1033             :         // Make sure we are in define mode.
    1034         152 :         static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1035             : 
    1036             :         char szTempPrivate[256 + 1];
    1037         152 :         const char *pszTemp = nullptr;
    1038         152 :         if (!pszBandName || EQUAL(pszBandName, ""))
    1039             :         {
    1040         132 :             snprintf(szTempPrivate, sizeof(szTempPrivate), "Band%d", nBand);
    1041         132 :             pszTemp = szTempPrivate;
    1042             :         }
    1043             :         else
    1044             :         {
    1045          20 :             pszTemp = pszBandName;
    1046             :         }
    1047             : 
    1048             :         int status;
    1049         152 :         if (nZDim > 2 && paDimIds != nullptr)
    1050             :         {
    1051           4 :             status =
    1052           4 :                 nc_def_var(cdfid, pszTemp, nc_datatype, nZDim, paDimIds, &nZId);
    1053             :         }
    1054             :         else
    1055             :         {
    1056         148 :             int anBandDims[2] = {poNCDFDS->nYDimID, poNCDFDS->nXDimID};
    1057             :             status =
    1058         148 :                 nc_def_var(cdfid, pszTemp, nc_datatype, 2, anBandDims, &nZId);
    1059             :         }
    1060         152 :         NCDF_ERR(status);
    1061         152 :         CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d) id=%d", cdfid, pszTemp,
    1062             :                  nc_datatype, nZId);
    1063             : 
    1064         152 :         if (!pszLongName || EQUAL(pszLongName, ""))
    1065             :         {
    1066         145 :             snprintf(szTempPrivate, sizeof(szTempPrivate),
    1067             :                      "GDAL Band Number %d", nBand);
    1068         145 :             pszTemp = szTempPrivate;
    1069             :         }
    1070             :         else
    1071             :         {
    1072           7 :             pszTemp = pszLongName;
    1073             :         }
    1074             :         status =
    1075         152 :             nc_put_att_text(cdfid, nZId, CF_LNG_NAME, strlen(pszTemp), pszTemp);
    1076         152 :         NCDF_ERR(status);
    1077             : 
    1078         152 :         poNCDFDS->DefVarDeflate(nZId, true);
    1079             :     }
    1080             : 
    1081             :     // For Byte data add signed/unsigned info.
    1082         171 :     if (eDataType == GDT_Byte || eDataType == GDT_Int8)
    1083             :     {
    1084          73 :         if (bDefineVar)
    1085             :         {
    1086             :             // Only add attributes if creating variable.
    1087             :             // For unsigned NC_BYTE (except NC4 format),
    1088             :             // add valid_range and _Unsigned ( defined in CF-1 and NUG ).
    1089          68 :             if (nc_datatype == NC_BYTE && poNCDFDS->eFormat != NCDF_FORMAT_NC4)
    1090             :             {
    1091          65 :                 CPLDebug("GDAL_netCDF",
    1092             :                          "adding valid_range attributes for Byte Band");
    1093          65 :                 short l_adfValidRange[2] = {0, 0};
    1094             :                 int status;
    1095          65 :                 if (bSignedData || eDataType == GDT_Int8)
    1096             :                 {
    1097           7 :                     l_adfValidRange[0] = -128;
    1098           7 :                     l_adfValidRange[1] = 127;
    1099           7 :                     status =
    1100           7 :                         nc_put_att_text(cdfid, nZId, "_Unsigned", 5, "false");
    1101             :                 }
    1102             :                 else
    1103             :                 {
    1104          58 :                     l_adfValidRange[0] = 0;
    1105          58 :                     l_adfValidRange[1] = 255;
    1106             :                     status =
    1107          58 :                         nc_put_att_text(cdfid, nZId, "_Unsigned", 4, "true");
    1108             :                 }
    1109          65 :                 NCDF_ERR(status);
    1110          65 :                 status = nc_put_att_short(cdfid, nZId, "valid_range", NC_SHORT,
    1111             :                                           2, l_adfValidRange);
    1112          65 :                 NCDF_ERR(status);
    1113             :             }
    1114             :         }
    1115             :     }
    1116             : 
    1117         171 :     if (nc_datatype != NC_BYTE && nc_datatype != NC_CHAR &&
    1118         101 :         nc_datatype != NC_UBYTE)
    1119             :     {
    1120             :         // Set default nodata.
    1121          98 :         bool bIgnored = false;
    1122             :         double dfNoData =
    1123          98 :             NCDFGetDefaultNoDataValue(cdfid, nZId, nc_datatype, bIgnored);
    1124             : #ifdef NCDF_DEBUG
    1125             :         CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) default", dfNoData);
    1126             : #endif
    1127          98 :         netCDFRasterBand::SetNoDataValue(dfNoData);
    1128             :     }
    1129             : 
    1130         171 :     SetBlockSize();
    1131             : }
    1132             : 
    1133             : /************************************************************************/
    1134             : /*                         ~netCDFRasterBand()                          */
    1135             : /************************************************************************/
    1136             : 
    1137        1244 : netCDFRasterBand::~netCDFRasterBand()
    1138             : {
    1139         622 :     netCDFRasterBand::FlushCache(true);
    1140         622 :     CPLFree(panBandZPos);
    1141         622 :     CPLFree(panBandZLev);
    1142        1244 : }
    1143             : 
    1144             : /************************************************************************/
    1145             : /*                          GetMetadata()                               */
    1146             : /************************************************************************/
    1147             : 
    1148          47 : char **netCDFRasterBand::GetMetadata(const char *pszDomain)
    1149             : {
    1150          47 :     if (!m_bCreateMetadataFromOtherVarsDone)
    1151          47 :         CreateMetadataFromOtherVars();
    1152          47 :     return GDALPamRasterBand::GetMetadata(pszDomain);
    1153             : }
    1154             : 
    1155             : /************************************************************************/
    1156             : /*                        GetMetadataItem()                             */
    1157             : /************************************************************************/
    1158             : 
    1159         624 : const char *netCDFRasterBand::GetMetadataItem(const char *pszName,
    1160             :                                               const char *pszDomain)
    1161             : {
    1162         624 :     if (!m_bCreateMetadataFromOtherVarsDone &&
    1163         512 :         STARTS_WITH(pszName, "NETCDF_DIM_") &&
    1164           1 :         (!pszDomain || pszDomain[0] == 0))
    1165           1 :         CreateMetadataFromOtherVars();
    1166         624 :     return GDALPamRasterBand::GetMetadataItem(pszName, pszDomain);
    1167             : }
    1168             : 
    1169             : /************************************************************************/
    1170             : /*                        SetMetadataItem()                             */
    1171             : /************************************************************************/
    1172             : 
    1173           6 : CPLErr netCDFRasterBand::SetMetadataItem(const char *pszName,
    1174             :                                          const char *pszValue,
    1175             :                                          const char *pszDomain)
    1176             : {
    1177           7 :     if (GetAccess() == GA_Update &&
    1178           7 :         (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)
    1179             :     {
    1180             :         // Same logic as in CopyMetadata()
    1181             : 
    1182           1 :         const char *const papszIgnoreBand[] = {
    1183             :             CF_ADD_OFFSET, CF_SCALE_FACTOR, "valid_range", "_Unsigned",
    1184             :             _FillValue,    "coordinates",   nullptr};
    1185             :         // Do not copy varname, stats, NETCDF_DIM_*, nodata
    1186             :         // and items in papszIgnoreBand.
    1187           3 :         if (STARTS_WITH(pszName, "NETCDF_VARNAME") ||
    1188           1 :             STARTS_WITH(pszName, "STATISTICS_") ||
    1189           1 :             STARTS_WITH(pszName, "NETCDF_DIM_") ||
    1190           1 :             STARTS_WITH(pszName, "missing_value") ||
    1191           3 :             STARTS_WITH(pszName, "_FillValue") ||
    1192           1 :             CSLFindString(papszIgnoreBand, pszName) != -1)
    1193             :         {
    1194             :             // do nothing
    1195             :         }
    1196             :         else
    1197             :         {
    1198           1 :             cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1199             : 
    1200           1 :             if (!NCDFPutAttr(cdfid, nZId, pszName, pszValue))
    1201           1 :                 return CE_Failure;
    1202             :         }
    1203             :     }
    1204             : 
    1205           5 :     return GDALPamRasterBand::SetMetadataItem(pszName, pszValue, pszDomain);
    1206             : }
    1207             : 
    1208             : /************************************************************************/
    1209             : /*                          SetMetadata()                               */
    1210             : /************************************************************************/
    1211             : 
    1212           1 : CPLErr netCDFRasterBand::SetMetadata(char **papszMD, const char *pszDomain)
    1213             : {
    1214           2 :     if (GetAccess() == GA_Update &&
    1215           1 :         (pszDomain == nullptr || pszDomain[0] == '\0'))
    1216             :     {
    1217             :         // We don't handle metadata item removal for now
    1218           2 :         for (const char *const *papszIter = papszMD; papszIter && *papszIter;
    1219             :              ++papszIter)
    1220             :         {
    1221           1 :             char *pszName = nullptr;
    1222           1 :             const char *pszValue = CPLParseNameValue(*papszIter, &pszName);
    1223           1 :             if (pszName && pszValue)
    1224           1 :                 SetMetadataItem(pszName, pszValue);
    1225           1 :             CPLFree(pszName);
    1226             :         }
    1227             :     }
    1228           1 :     return GDALPamRasterBand::SetMetadata(papszMD, pszDomain);
    1229             : }
    1230             : 
    1231             : /************************************************************************/
    1232             : /*                             GetOffset()                              */
    1233             : /************************************************************************/
    1234          49 : double netCDFRasterBand::GetOffset(int *pbSuccess)
    1235             : {
    1236          49 :     if (pbSuccess != nullptr)
    1237          44 :         *pbSuccess = static_cast<int>(m_bHaveOffset);
    1238             : 
    1239          49 :     return m_dfOffset;
    1240             : }
    1241             : 
    1242             : /************************************************************************/
    1243             : /*                             SetOffset()                              */
    1244             : /************************************************************************/
    1245           1 : CPLErr netCDFRasterBand::SetOffset(double dfNewOffset)
    1246             : {
    1247           2 :     CPLMutexHolderD(&hNCMutex);
    1248             : 
    1249             :     // Write value if in update mode.
    1250           1 :     if (poDS->GetAccess() == GA_Update)
    1251             :     {
    1252             :         // Make sure we are in define mode.
    1253           1 :         static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1254             : 
    1255           1 :         const int status = nc_put_att_double(cdfid, nZId, CF_ADD_OFFSET,
    1256             :                                              NC_DOUBLE, 1, &dfNewOffset);
    1257             : 
    1258           1 :         NCDF_ERR(status);
    1259           1 :         if (status == NC_NOERR)
    1260             :         {
    1261           1 :             SetOffsetNoUpdate(dfNewOffset);
    1262           1 :             return CE_None;
    1263             :         }
    1264             : 
    1265           0 :         return CE_Failure;
    1266             :     }
    1267             : 
    1268           0 :     SetOffsetNoUpdate(dfNewOffset);
    1269           0 :     return CE_None;
    1270             : }
    1271             : 
    1272             : /************************************************************************/
    1273             : /*                         SetOffsetNoUpdate()                          */
    1274             : /************************************************************************/
    1275          18 : void netCDFRasterBand::SetOffsetNoUpdate(double dfVal)
    1276             : {
    1277          18 :     m_dfOffset = dfVal;
    1278          18 :     m_bHaveOffset = true;
    1279          18 : }
    1280             : 
    1281             : /************************************************************************/
    1282             : /*                              GetScale()                              */
    1283             : /************************************************************************/
    1284          49 : double netCDFRasterBand::GetScale(int *pbSuccess)
    1285             : {
    1286          49 :     if (pbSuccess != nullptr)
    1287          44 :         *pbSuccess = static_cast<int>(m_bHaveScale);
    1288             : 
    1289          49 :     return m_dfScale;
    1290             : }
    1291             : 
    1292             : /************************************************************************/
    1293             : /*                              SetScale()                              */
    1294             : /************************************************************************/
    1295           1 : CPLErr netCDFRasterBand::SetScale(double dfNewScale)
    1296             : {
    1297           2 :     CPLMutexHolderD(&hNCMutex);
    1298             : 
    1299             :     // Write value if in update mode.
    1300           1 :     if (poDS->GetAccess() == GA_Update)
    1301             :     {
    1302             :         // Make sure we are in define mode.
    1303           1 :         static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1304             : 
    1305           1 :         const int status = nc_put_att_double(cdfid, nZId, CF_SCALE_FACTOR,
    1306             :                                              NC_DOUBLE, 1, &dfNewScale);
    1307             : 
    1308           1 :         NCDF_ERR(status);
    1309           1 :         if (status == NC_NOERR)
    1310             :         {
    1311           1 :             SetScaleNoUpdate(dfNewScale);
    1312           1 :             return CE_None;
    1313             :         }
    1314             : 
    1315           0 :         return CE_Failure;
    1316             :     }
    1317             : 
    1318           0 :     SetScaleNoUpdate(dfNewScale);
    1319           0 :     return CE_None;
    1320             : }
    1321             : 
    1322             : /************************************************************************/
    1323             : /*                         SetScaleNoUpdate()                           */
    1324             : /************************************************************************/
    1325          22 : void netCDFRasterBand::SetScaleNoUpdate(double dfVal)
    1326             : {
    1327          22 :     m_dfScale = dfVal;
    1328          22 :     m_bHaveScale = true;
    1329          22 : }
    1330             : 
    1331             : /************************************************************************/
    1332             : /*                            GetUnitType()                             */
    1333             : /************************************************************************/
    1334             : 
    1335          21 : const char *netCDFRasterBand::GetUnitType()
    1336             : 
    1337             : {
    1338          21 :     if (!m_osUnitType.empty())
    1339           6 :         return m_osUnitType;
    1340             : 
    1341          15 :     return GDALRasterBand::GetUnitType();
    1342             : }
    1343             : 
    1344             : /************************************************************************/
    1345             : /*                           SetUnitType()                              */
    1346             : /************************************************************************/
    1347             : 
    1348           1 : CPLErr netCDFRasterBand::SetUnitType(const char *pszNewValue)
    1349             : 
    1350             : {
    1351           2 :     CPLMutexHolderD(&hNCMutex);
    1352             : 
    1353           2 :     const std::string osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
    1354             : 
    1355           1 :     if (!osUnitType.empty())
    1356             :     {
    1357             :         // Write value if in update mode.
    1358           1 :         if (poDS->GetAccess() == GA_Update)
    1359             :         {
    1360             :             // Make sure we are in define mode.
    1361           1 :             static_cast<netCDFDataset *>(poDS)->SetDefineMode(TRUE);
    1362             : 
    1363           1 :             const int status = nc_put_att_text(
    1364             :                 cdfid, nZId, CF_UNITS, osUnitType.size(), osUnitType.c_str());
    1365             : 
    1366           1 :             NCDF_ERR(status);
    1367           1 :             if (status == NC_NOERR)
    1368             :             {
    1369           1 :                 SetUnitTypeNoUpdate(pszNewValue);
    1370           1 :                 return CE_None;
    1371             :             }
    1372             : 
    1373           0 :             return CE_Failure;
    1374             :         }
    1375             :     }
    1376             : 
    1377           0 :     SetUnitTypeNoUpdate(pszNewValue);
    1378             : 
    1379           0 :     return CE_None;
    1380             : }
    1381             : 
    1382             : /************************************************************************/
    1383             : /*                       SetUnitTypeNoUpdate()                          */
    1384             : /************************************************************************/
    1385             : 
    1386         452 : void netCDFRasterBand::SetUnitTypeNoUpdate(const char *pszNewValue)
    1387             : {
    1388         452 :     m_osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
    1389         452 : }
    1390             : 
    1391             : /************************************************************************/
    1392             : /*                           GetNoDataValue()                           */
    1393             : /************************************************************************/
    1394             : 
    1395         151 : double netCDFRasterBand::GetNoDataValue(int *pbSuccess)
    1396             : 
    1397             : {
    1398         151 :     if (m_bNoDataSetAsInt64)
    1399             :     {
    1400           0 :         if (pbSuccess)
    1401           0 :             *pbSuccess = TRUE;
    1402           0 :         return GDALGetNoDataValueCastToDouble(m_nNodataValueInt64);
    1403             :     }
    1404             : 
    1405         151 :     if (m_bNoDataSetAsUInt64)
    1406             :     {
    1407           0 :         if (pbSuccess)
    1408           0 :             *pbSuccess = TRUE;
    1409           0 :         return GDALGetNoDataValueCastToDouble(m_nNodataValueUInt64);
    1410             :     }
    1411             : 
    1412         151 :     if (m_bNoDataSet)
    1413             :     {
    1414         117 :         if (pbSuccess)
    1415         101 :             *pbSuccess = TRUE;
    1416         117 :         return m_dfNoDataValue;
    1417             :     }
    1418             : 
    1419          34 :     return GDALPamRasterBand::GetNoDataValue(pbSuccess);
    1420             : }
    1421             : 
    1422             : /************************************************************************/
    1423             : /*                        GetNoDataValueAsInt64()                       */
    1424             : /************************************************************************/
    1425             : 
    1426           4 : int64_t netCDFRasterBand::GetNoDataValueAsInt64(int *pbSuccess)
    1427             : 
    1428             : {
    1429           4 :     if (m_bNoDataSetAsInt64)
    1430             :     {
    1431           4 :         if (pbSuccess)
    1432           4 :             *pbSuccess = TRUE;
    1433             : 
    1434           4 :         return m_nNodataValueInt64;
    1435             :     }
    1436             : 
    1437           0 :     return GDALPamRasterBand::GetNoDataValueAsInt64(pbSuccess);
    1438             : }
    1439             : 
    1440             : /************************************************************************/
    1441             : /*                        GetNoDataValueAsUInt64()                      */
    1442             : /************************************************************************/
    1443             : 
    1444           4 : uint64_t netCDFRasterBand::GetNoDataValueAsUInt64(int *pbSuccess)
    1445             : 
    1446             : {
    1447           4 :     if (m_bNoDataSetAsUInt64)
    1448             :     {
    1449           4 :         if (pbSuccess)
    1450           4 :             *pbSuccess = TRUE;
    1451             : 
    1452           4 :         return m_nNodataValueUInt64;
    1453             :     }
    1454             : 
    1455           0 :     return GDALPamRasterBand::GetNoDataValueAsUInt64(pbSuccess);
    1456             : }
    1457             : 
    1458             : /************************************************************************/
    1459             : /*                           SetNoDataValue()                           */
    1460             : /************************************************************************/
    1461             : 
    1462         133 : CPLErr netCDFRasterBand::SetNoDataValue(double dfNoData)
    1463             : 
    1464             : {
    1465         266 :     CPLMutexHolderD(&hNCMutex);
    1466             : 
    1467             :     // If already set to new value, don't do anything.
    1468         133 :     if (m_bNoDataSet && CPLIsEqual(dfNoData, m_dfNoDataValue))
    1469          19 :         return CE_None;
    1470             : 
    1471             :     // Write value if in update mode.
    1472         114 :     if (poDS->GetAccess() == GA_Update)
    1473             :     {
    1474             :         // netcdf-4 does not allow to set _FillValue after leaving define mode,
    1475             :         // but it is ok if variable has not been written to, so only print
    1476             :         // debug. See bug #4484.
    1477         124 :         if (m_bNoDataSet &&
    1478          10 :             !reinterpret_cast<netCDFDataset *>(poDS)->GetDefineMode())
    1479             :         {
    1480           0 :             CPLDebug("GDAL_netCDF",
    1481             :                      "Setting NoDataValue to %.18g (previously set to %.18g) "
    1482             :                      "but file is no longer in define mode (id #%d, band #%d)",
    1483             :                      dfNoData, m_dfNoDataValue, cdfid, nBand);
    1484             :         }
    1485             : #ifdef NCDF_DEBUG
    1486             :         else
    1487             :         {
    1488             :             CPLDebug("GDAL_netCDF",
    1489             :                      "Setting NoDataValue to %.18g (id #%d, band #%d)",
    1490             :                      dfNoData, cdfid, nBand);
    1491             :         }
    1492             : #endif
    1493             :         // Make sure we are in define mode.
    1494         114 :         reinterpret_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1495             : 
    1496             :         int status;
    1497         114 :         if (eDataType == GDT_Byte)
    1498             :         {
    1499           5 :             if (bSignedData)
    1500             :             {
    1501           0 :                 signed char cNoDataValue = static_cast<signed char>(dfNoData);
    1502           0 :                 status = nc_put_att_schar(cdfid, nZId, _FillValue, nc_datatype,
    1503             :                                           1, &cNoDataValue);
    1504             :             }
    1505             :             else
    1506             :             {
    1507           5 :                 const unsigned char ucNoDataValue =
    1508           5 :                     static_cast<unsigned char>(dfNoData);
    1509           5 :                 status = nc_put_att_uchar(cdfid, nZId, _FillValue, nc_datatype,
    1510             :                                           1, &ucNoDataValue);
    1511             :             }
    1512             :         }
    1513         109 :         else if (eDataType == GDT_Int16)
    1514             :         {
    1515          14 :             short nsNoDataValue = static_cast<short>(dfNoData);
    1516          14 :             status = nc_put_att_short(cdfid, nZId, _FillValue, nc_datatype, 1,
    1517             :                                       &nsNoDataValue);
    1518             :         }
    1519          95 :         else if (eDataType == GDT_Int32)
    1520             :         {
    1521          27 :             int nNoDataValue = static_cast<int>(dfNoData);
    1522          27 :             status = nc_put_att_int(cdfid, nZId, _FillValue, nc_datatype, 1,
    1523             :                                     &nNoDataValue);
    1524             :         }
    1525          68 :         else if (eDataType == GDT_Float32)
    1526             :         {
    1527          31 :             float fNoDataValue = static_cast<float>(dfNoData);
    1528          31 :             status = nc_put_att_float(cdfid, nZId, _FillValue, nc_datatype, 1,
    1529             :                                       &fNoDataValue);
    1530             :         }
    1531          37 :         else if (eDataType == GDT_UInt16 &&
    1532           6 :                  reinterpret_cast<netCDFDataset *>(poDS)->eFormat ==
    1533             :                      NCDF_FORMAT_NC4)
    1534             :         {
    1535           6 :             unsigned short usNoDataValue =
    1536           6 :                 static_cast<unsigned short>(dfNoData);
    1537           6 :             status = nc_put_att_ushort(cdfid, nZId, _FillValue, nc_datatype, 1,
    1538           6 :                                        &usNoDataValue);
    1539             :         }
    1540          31 :         else if (eDataType == GDT_UInt32 &&
    1541           7 :                  reinterpret_cast<netCDFDataset *>(poDS)->eFormat ==
    1542             :                      NCDF_FORMAT_NC4)
    1543             :         {
    1544           7 :             unsigned int unNoDataValue = static_cast<unsigned int>(dfNoData);
    1545           7 :             status = nc_put_att_uint(cdfid, nZId, _FillValue, nc_datatype, 1,
    1546           7 :                                      &unNoDataValue);
    1547             :         }
    1548             :         else
    1549             :         {
    1550          24 :             status = nc_put_att_double(cdfid, nZId, _FillValue, nc_datatype, 1,
    1551             :                                        &dfNoData);
    1552             :         }
    1553             : 
    1554         114 :         NCDF_ERR(status);
    1555             : 
    1556             :         // Update status if write worked.
    1557         114 :         if (status == NC_NOERR)
    1558             :         {
    1559         114 :             SetNoDataValueNoUpdate(dfNoData);
    1560         114 :             return CE_None;
    1561             :         }
    1562             : 
    1563           0 :         return CE_Failure;
    1564             :     }
    1565             : 
    1566           0 :     SetNoDataValueNoUpdate(dfNoData);
    1567           0 :     return CE_None;
    1568             : }
    1569             : 
    1570             : /************************************************************************/
    1571             : /*                       SetNoDataValueNoUpdate()                       */
    1572             : /************************************************************************/
    1573             : 
    1574         423 : void netCDFRasterBand::SetNoDataValueNoUpdate(double dfNoData)
    1575             : {
    1576         423 :     m_dfNoDataValue = dfNoData;
    1577         423 :     m_bNoDataSet = true;
    1578         423 :     m_bNoDataSetAsInt64 = false;
    1579         423 :     m_bNoDataSetAsUInt64 = false;
    1580         423 : }
    1581             : 
    1582             : /************************************************************************/
    1583             : /*                        SetNoDataValueAsInt64()                       */
    1584             : /************************************************************************/
    1585             : 
    1586           3 : CPLErr netCDFRasterBand::SetNoDataValueAsInt64(int64_t nNoData)
    1587             : 
    1588             : {
    1589           6 :     CPLMutexHolderD(&hNCMutex);
    1590             : 
    1591             :     // If already set to new value, don't do anything.
    1592           3 :     if (m_bNoDataSetAsInt64 && nNoData == m_nNodataValueInt64)
    1593           0 :         return CE_None;
    1594             : 
    1595             :     // Write value if in update mode.
    1596           3 :     if (poDS->GetAccess() == GA_Update)
    1597             :     {
    1598             :         // netcdf-4 does not allow to set _FillValue after leaving define mode,
    1599             :         // but it is ok if variable has not been written to, so only print
    1600             :         // debug. See bug #4484.
    1601           3 :         if (m_bNoDataSetAsInt64 &&
    1602           0 :             !reinterpret_cast<netCDFDataset *>(poDS)->GetDefineMode())
    1603             :         {
    1604           0 :             CPLDebug("GDAL_netCDF",
    1605             :                      "Setting NoDataValue to " CPL_FRMT_GIB
    1606             :                      " (previously set to " CPL_FRMT_GIB ") "
    1607             :                      "but file is no longer in define mode (id #%d, band #%d)",
    1608             :                      static_cast<GIntBig>(nNoData),
    1609           0 :                      static_cast<GIntBig>(m_nNodataValueInt64), cdfid, nBand);
    1610             :         }
    1611             : #ifdef NCDF_DEBUG
    1612             :         else
    1613             :         {
    1614             :             CPLDebug("GDAL_netCDF",
    1615             :                      "Setting NoDataValue to " CPL_FRMT_GIB
    1616             :                      " (id #%d, band #%d)",
    1617             :                      static_cast<GIntBig>(nNoData), cdfid, nBand);
    1618             :         }
    1619             : #endif
    1620             :         // Make sure we are in define mode.
    1621           3 :         reinterpret_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1622             : 
    1623             :         int status;
    1624           3 :         if (eDataType == GDT_Int64 &&
    1625           3 :             reinterpret_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    1626             :         {
    1627           3 :             long long tmp = static_cast<long long>(nNoData);
    1628           3 :             status = nc_put_att_longlong(cdfid, nZId, _FillValue, nc_datatype,
    1629           3 :                                          1, &tmp);
    1630             :         }
    1631             :         else
    1632             :         {
    1633           0 :             double dfNoData = static_cast<double>(nNoData);
    1634           0 :             status = nc_put_att_double(cdfid, nZId, _FillValue, nc_datatype, 1,
    1635             :                                        &dfNoData);
    1636             :         }
    1637             : 
    1638           3 :         NCDF_ERR(status);
    1639             : 
    1640             :         // Update status if write worked.
    1641           3 :         if (status == NC_NOERR)
    1642             :         {
    1643           3 :             SetNoDataValueNoUpdate(nNoData);
    1644           3 :             return CE_None;
    1645             :         }
    1646             : 
    1647           0 :         return CE_Failure;
    1648             :     }
    1649             : 
    1650           0 :     SetNoDataValueNoUpdate(nNoData);
    1651           0 :     return CE_None;
    1652             : }
    1653             : 
    1654             : /************************************************************************/
    1655             : /*                       SetNoDataValueNoUpdate()                       */
    1656             : /************************************************************************/
    1657             : 
    1658          13 : void netCDFRasterBand::SetNoDataValueNoUpdate(int64_t nNoData)
    1659             : {
    1660          13 :     m_nNodataValueInt64 = nNoData;
    1661          13 :     m_bNoDataSet = false;
    1662          13 :     m_bNoDataSetAsInt64 = true;
    1663          13 :     m_bNoDataSetAsUInt64 = false;
    1664          13 : }
    1665             : 
    1666             : /************************************************************************/
    1667             : /*                        SetNoDataValueAsUInt64()                      */
    1668             : /************************************************************************/
    1669             : 
    1670           3 : CPLErr netCDFRasterBand::SetNoDataValueAsUInt64(uint64_t nNoData)
    1671             : 
    1672             : {
    1673           6 :     CPLMutexHolderD(&hNCMutex);
    1674             : 
    1675             :     // If already set to new value, don't do anything.
    1676           3 :     if (m_bNoDataSetAsUInt64 && nNoData == m_nNodataValueUInt64)
    1677           0 :         return CE_None;
    1678             : 
    1679             :     // Write value if in update mode.
    1680           3 :     if (poDS->GetAccess() == GA_Update)
    1681             :     {
    1682             :         // netcdf-4 does not allow to set _FillValue after leaving define mode,
    1683             :         // but it is ok if variable has not been written to, so only print
    1684             :         // debug. See bug #4484.
    1685           3 :         if (m_bNoDataSetAsUInt64 &&
    1686           0 :             !reinterpret_cast<netCDFDataset *>(poDS)->GetDefineMode())
    1687             :         {
    1688           0 :             CPLDebug("GDAL_netCDF",
    1689             :                      "Setting NoDataValue to " CPL_FRMT_GUIB
    1690             :                      " (previously set to " CPL_FRMT_GUIB ") "
    1691             :                      "but file is no longer in define mode (id #%d, band #%d)",
    1692             :                      static_cast<GUIntBig>(nNoData),
    1693           0 :                      static_cast<GUIntBig>(m_nNodataValueUInt64), cdfid, nBand);
    1694             :         }
    1695             : #ifdef NCDF_DEBUG
    1696             :         else
    1697             :         {
    1698             :             CPLDebug("GDAL_netCDF",
    1699             :                      "Setting NoDataValue to " CPL_FRMT_GUIB
    1700             :                      " (id #%d, band #%d)",
    1701             :                      static_cast<GUIntBig>(nNoData), cdfid, nBand);
    1702             :         }
    1703             : #endif
    1704             :         // Make sure we are in define mode.
    1705           3 :         reinterpret_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1706             : 
    1707             :         int status;
    1708           3 :         if (eDataType == GDT_UInt64 &&
    1709           3 :             reinterpret_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    1710             :         {
    1711           3 :             unsigned long long tmp = static_cast<long long>(nNoData);
    1712           3 :             status = nc_put_att_ulonglong(cdfid, nZId, _FillValue, nc_datatype,
    1713           3 :                                           1, &tmp);
    1714             :         }
    1715             :         else
    1716             :         {
    1717           0 :             double dfNoData = static_cast<double>(nNoData);
    1718           0 :             status = nc_put_att_double(cdfid, nZId, _FillValue, nc_datatype, 1,
    1719             :                                        &dfNoData);
    1720             :         }
    1721             : 
    1722           3 :         NCDF_ERR(status);
    1723             : 
    1724             :         // Update status if write worked.
    1725           3 :         if (status == NC_NOERR)
    1726             :         {
    1727           3 :             SetNoDataValueNoUpdate(nNoData);
    1728           3 :             return CE_None;
    1729             :         }
    1730             : 
    1731           0 :         return CE_Failure;
    1732             :     }
    1733             : 
    1734           0 :     SetNoDataValueNoUpdate(nNoData);
    1735           0 :     return CE_None;
    1736             : }
    1737             : 
    1738             : /************************************************************************/
    1739             : /*                       SetNoDataValueNoUpdate()                       */
    1740             : /************************************************************************/
    1741             : 
    1742          12 : void netCDFRasterBand::SetNoDataValueNoUpdate(uint64_t nNoData)
    1743             : {
    1744          12 :     m_nNodataValueUInt64 = nNoData;
    1745          12 :     m_bNoDataSet = false;
    1746          12 :     m_bNoDataSetAsInt64 = false;
    1747          12 :     m_bNoDataSetAsUInt64 = true;
    1748          12 : }
    1749             : 
    1750             : /************************************************************************/
    1751             : /*                        DeleteNoDataValue()                           */
    1752             : /************************************************************************/
    1753             : 
    1754             : #ifdef notdef
    1755             : CPLErr netCDFRasterBand::DeleteNoDataValue()
    1756             : 
    1757             : {
    1758             :     CPLMutexHolderD(&hNCMutex);
    1759             : 
    1760             :     if (!bNoDataSet)
    1761             :         return CE_None;
    1762             : 
    1763             :     // Write value if in update mode.
    1764             :     if (poDS->GetAccess() == GA_Update)
    1765             :     {
    1766             :         // Make sure we are in define mode.
    1767             :         static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1768             : 
    1769             :         status = nc_del_att(cdfid, nZId, _FillValue);
    1770             : 
    1771             :         NCDF_ERR(status);
    1772             : 
    1773             :         // Update status if write worked.
    1774             :         if (status == NC_NOERR)
    1775             :         {
    1776             :             dfNoDataValue = 0.0;
    1777             :             bNoDataSet = false;
    1778             :             return CE_None;
    1779             :         }
    1780             : 
    1781             :         return CE_Failure;
    1782             :     }
    1783             : 
    1784             :     dfNoDataValue = 0.0;
    1785             :     bNoDataSet = false;
    1786             :     return CE_None;
    1787             : }
    1788             : #endif
    1789             : 
    1790             : /************************************************************************/
    1791             : /*                           SerializeToXML()                           */
    1792             : /************************************************************************/
    1793             : 
    1794          31 : CPLXMLNode *netCDFRasterBand::SerializeToXML(const char * /* pszUnused */)
    1795             : {
    1796             :     // Overridden from GDALPamDataset to add only band histogram
    1797             :     // and statistics. See bug #4244.
    1798          31 :     if (psPam == nullptr)
    1799           0 :         return nullptr;
    1800             : 
    1801             :     // Setup root node and attributes.
    1802             :     CPLXMLNode *psTree =
    1803          31 :         CPLCreateXMLNode(nullptr, CXT_Element, "PAMRasterBand");
    1804             : 
    1805          31 :     if (GetBand() > 0)
    1806             :     {
    1807          62 :         CPLString oFmt;
    1808          31 :         CPLSetXMLValue(psTree, "#band", oFmt.Printf("%d", GetBand()));
    1809             :     }
    1810             : 
    1811             :     // Histograms.
    1812          31 :     if (psPam->psSavedHistograms != nullptr)
    1813           1 :         CPLAddXMLChild(psTree, CPLCloneXMLTree(psPam->psSavedHistograms));
    1814             : 
    1815             :     // Metadata (statistics only).
    1816          31 :     GDALMultiDomainMetadata oMDMDStats;
    1817          31 :     const char *papszMDStats[] = {"STATISTICS_MINIMUM", "STATISTICS_MAXIMUM",
    1818             :                                   "STATISTICS_MEAN", "STATISTICS_STDDEV",
    1819             :                                   nullptr};
    1820         155 :     for (int i = 0; i < CSLCount(papszMDStats); i++)
    1821             :     {
    1822         124 :         const char *pszMDI = GetMetadataItem(papszMDStats[i]);
    1823         124 :         if (pszMDI)
    1824           4 :             oMDMDStats.SetMetadataItem(papszMDStats[i], pszMDI);
    1825             :     }
    1826          31 :     CPLXMLNode *psMD = oMDMDStats.Serialize();
    1827             : 
    1828          31 :     if (psMD != nullptr)
    1829             :     {
    1830           1 :         if (psMD->psChild == nullptr)
    1831           0 :             CPLDestroyXMLNode(psMD);
    1832             :         else
    1833           1 :             CPLAddXMLChild(psTree, psMD);
    1834             :     }
    1835             : 
    1836             :     // We don't want to return anything if we had no metadata to attach.
    1837          31 :     if (psTree->psChild == nullptr || psTree->psChild->psNext == nullptr)
    1838             :     {
    1839          29 :         CPLDestroyXMLNode(psTree);
    1840          29 :         psTree = nullptr;
    1841             :     }
    1842             : 
    1843          31 :     return psTree;
    1844             : }
    1845             : 
    1846             : /************************************************************************/
    1847             : /*               Get1DVariableIndexedByDimension()                      */
    1848             : /************************************************************************/
    1849             : 
    1850          70 : static int Get1DVariableIndexedByDimension(int cdfid, int nDimId,
    1851             :                                            const char *pszDimName,
    1852             :                                            bool bVerboseError, int *pnGroupID)
    1853             : {
    1854          70 :     *pnGroupID = -1;
    1855          70 :     int nVarID = -1;
    1856             :     // First try to find a variable whose name is identical to the dimension
    1857             :     // name, and check that it is indeed indexed by this dimension
    1858          70 :     if (NCDFResolveVar(cdfid, pszDimName, pnGroupID, &nVarID) == CE_None)
    1859             :     {
    1860          58 :         int nDimCountOfVariable = 0;
    1861          58 :         nc_inq_varndims(*pnGroupID, nVarID, &nDimCountOfVariable);
    1862          58 :         if (nDimCountOfVariable == 1)
    1863             :         {
    1864          58 :             int nDimIdOfVariable = -1;
    1865          58 :             nc_inq_vardimid(*pnGroupID, nVarID, &nDimIdOfVariable);
    1866          58 :             if (nDimIdOfVariable == nDimId)
    1867             :             {
    1868          58 :                 return nVarID;
    1869             :             }
    1870             :         }
    1871             :     }
    1872             : 
    1873             :     // Otherwise iterate over the variables to find potential candidates
    1874             :     // TODO: should be modified to search also in other groups using the same
    1875             :     //       logic than in NCDFResolveVar(), but maybe not needed if it's a
    1876             :     //       very rare case? and I think this is not CF compliant.
    1877          12 :     int nvars = 0;
    1878          12 :     CPL_IGNORE_RET_VAL(nc_inq(cdfid, nullptr, &nvars, nullptr, nullptr));
    1879             : 
    1880          12 :     int nCountCandidateVars = 0;
    1881          12 :     int nCandidateVarID = -1;
    1882          53 :     for (int k = 0; k < nvars; k++)
    1883             :     {
    1884          41 :         int nDimCountOfVariable = 0;
    1885          41 :         nc_inq_varndims(cdfid, k, &nDimCountOfVariable);
    1886          41 :         if (nDimCountOfVariable == 1)
    1887             :         {
    1888          23 :             int nDimIdOfVariable = -1;
    1889          23 :             nc_inq_vardimid(cdfid, k, &nDimIdOfVariable);
    1890          23 :             if (nDimIdOfVariable == nDimId)
    1891             :             {
    1892           5 :                 nCountCandidateVars++;
    1893           5 :                 nCandidateVarID = k;
    1894             :             }
    1895             :         }
    1896             :     }
    1897          12 :     if (nCountCandidateVars > 1)
    1898             :     {
    1899           1 :         if (bVerboseError)
    1900             :         {
    1901           1 :             CPLError(CE_Warning, CPLE_AppDefined,
    1902             :                      "Several 1D variables are indexed by dimension %s",
    1903             :                      pszDimName);
    1904             :         }
    1905           1 :         *pnGroupID = -1;
    1906           1 :         return -1;
    1907             :     }
    1908          11 :     else if (nCandidateVarID < 0)
    1909             :     {
    1910           8 :         if (bVerboseError)
    1911             :         {
    1912           8 :             CPLError(CE_Warning, CPLE_AppDefined,
    1913             :                      "No 1D variable is indexed by dimension %s", pszDimName);
    1914             :         }
    1915             :     }
    1916          11 :     *pnGroupID = cdfid;
    1917          11 :     return nCandidateVarID;
    1918             : }
    1919             : 
    1920             : /************************************************************************/
    1921             : /*                      CreateMetadataFromAttributes()                  */
    1922             : /************************************************************************/
    1923             : 
    1924         451 : void netCDFRasterBand::CreateMetadataFromAttributes()
    1925             : {
    1926         451 :     char szVarName[NC_MAX_NAME + 1] = {};
    1927         451 :     int status = nc_inq_varname(cdfid, nZId, szVarName);
    1928         451 :     NCDF_ERR(status);
    1929             : 
    1930         451 :     GDALPamRasterBand::SetMetadataItem("NETCDF_VARNAME", szVarName);
    1931             : 
    1932             :     // Get attribute metadata.
    1933         451 :     int nAtt = 0;
    1934         451 :     nc_inq_varnatts(cdfid, nZId, &nAtt);
    1935             : 
    1936        1879 :     for (int i = 0; i < nAtt; i++)
    1937             :     {
    1938        1428 :         char szMetaName[NC_MAX_NAME + 1] = {};
    1939        1428 :         status = nc_inq_attname(cdfid, nZId, i, szMetaName);
    1940        1428 :         if (status != NC_NOERR)
    1941          13 :             continue;
    1942             : 
    1943        1428 :         if (GDALPamRasterBand::GetMetadataItem(szMetaName) != nullptr)
    1944             :         {
    1945          13 :             continue;
    1946             :         }
    1947             : 
    1948        1415 :         char *pszMetaValue = nullptr;
    1949        1415 :         if (NCDFGetAttr(cdfid, nZId, szMetaName, &pszMetaValue) == CE_None)
    1950             :         {
    1951        1415 :             GDALPamRasterBand::SetMetadataItem(szMetaName, pszMetaValue);
    1952             :         }
    1953             :         else
    1954             :         {
    1955           0 :             CPLDebug("GDAL_netCDF", "invalid Band metadata %s", szMetaName);
    1956             :         }
    1957             : 
    1958        1415 :         if (pszMetaValue)
    1959             :         {
    1960        1415 :             CPLFree(pszMetaValue);
    1961        1415 :             pszMetaValue = nullptr;
    1962             :         }
    1963             :     }
    1964         451 : }
    1965             : 
    1966             : /************************************************************************/
    1967             : /*                      CreateMetadataFromOtherVars()                   */
    1968             : /************************************************************************/
    1969             : 
    1970          48 : void netCDFRasterBand::CreateMetadataFromOtherVars()
    1971             : 
    1972             : {
    1973          48 :     CPLAssert(!m_bCreateMetadataFromOtherVarsDone);
    1974          48 :     m_bCreateMetadataFromOtherVarsDone = true;
    1975             : 
    1976          48 :     netCDFDataset *l_poDS = reinterpret_cast<netCDFDataset *>(poDS);
    1977             : 
    1978             :     // Compute all dimensions from Band number and save in Metadata.
    1979          48 :     int nd = 0;
    1980          48 :     nc_inq_varndims(cdfid, nZId, &nd);
    1981             :     // Compute multidimention band position.
    1982             :     //
    1983             :     // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
    1984             :     // if Data[2,3,4,x,y]
    1985             :     //
    1986             :     //  BandPos0 = (nBand) / (3*4)
    1987             :     //  BandPos1 = (nBand - BandPos0*(3*4)) / (4)
    1988             :     //  BandPos2 = (nBand - BandPos0*(3*4)) % (4)
    1989             : 
    1990          48 :     int Sum = 1;
    1991          48 :     if (nd == 3)
    1992             :     {
    1993           5 :         Sum *= panBandZLev[0];
    1994             :     }
    1995             : 
    1996             :     // Loop over non-spatial dimensions.
    1997          48 :     int Taken = 0;
    1998             : 
    1999          88 :     for (int i = 0; i < nd - 2; i++)
    2000             :     {
    2001             :         int result;
    2002          40 :         if (i != nd - 2 - 1)
    2003             :         {
    2004          18 :             Sum = 1;
    2005          37 :             for (int j = i + 1; j < nd - 2; j++)
    2006             :             {
    2007          19 :                 Sum *= panBandZLev[j];
    2008             :             }
    2009          18 :             result = static_cast<int>((nLevel - Taken) / Sum);
    2010             :         }
    2011             :         else
    2012             :         {
    2013          22 :             result = static_cast<int>((nLevel - Taken) % Sum);
    2014             :         }
    2015             : 
    2016          40 :         char szName[NC_MAX_NAME + 1] = {};
    2017          40 :         snprintf(szName, sizeof(szName), "%s",
    2018          40 :                  l_poDS->papszDimName[l_poDS->m_anDimIds[panBandZPos[i]]]);
    2019             : 
    2020             :         char szMetaName[NC_MAX_NAME + 1 + 32];
    2021          40 :         snprintf(szMetaName, sizeof(szMetaName), "NETCDF_DIM_%s", szName);
    2022             : 
    2023          40 :         const int nGroupID = l_poDS->m_anExtraDimGroupIds[i];
    2024          40 :         const int nVarID = l_poDS->m_anExtraDimVarIds[i];
    2025          40 :         if (nVarID < 0)
    2026             :         {
    2027           2 :             GDALPamRasterBand::SetMetadataItem(szMetaName,
    2028             :                                                CPLSPrintf("%d", result + 1));
    2029             :         }
    2030             :         else
    2031             :         {
    2032             :             // TODO: Make sure all the status checks make sense.
    2033             : 
    2034          38 :             nc_type nVarType = NC_NAT;
    2035          38 :             /* status = */ nc_inq_vartype(nGroupID, nVarID, &nVarType);
    2036             : 
    2037          38 :             int nDims = 0;
    2038          38 :             /* status = */ nc_inq_varndims(nGroupID, nVarID, &nDims);
    2039             : 
    2040          38 :             char szMetaTemp[256] = {};
    2041          38 :             if (nDims == 1)
    2042             :             {
    2043          38 :                 size_t count[1] = {1};
    2044          38 :                 size_t start[1] = {static_cast<size_t>(result)};
    2045             : 
    2046          38 :                 switch (nVarType)
    2047             :                 {
    2048           0 :                     case NC_BYTE:
    2049             :                         // TODO: Check for signed/unsigned byte.
    2050             :                         signed char cData;
    2051           0 :                         /* status = */ nc_get_vara_schar(nGroupID, nVarID,
    2052             :                                                          start, count, &cData);
    2053           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", cData);
    2054           0 :                         break;
    2055           0 :                     case NC_SHORT:
    2056             :                         short sData;
    2057           0 :                         /* status = */ nc_get_vara_short(nGroupID, nVarID,
    2058             :                                                          start, count, &sData);
    2059           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", sData);
    2060           0 :                         break;
    2061          19 :                     case NC_INT:
    2062             :                     {
    2063             :                         int nData;
    2064          19 :                         /* status = */ nc_get_vara_int(nGroupID, nVarID, start,
    2065             :                                                        count, &nData);
    2066          19 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", nData);
    2067          19 :                         break;
    2068             :                     }
    2069           0 :                     case NC_FLOAT:
    2070             :                         float fData;
    2071           0 :                         /* status = */ nc_get_vara_float(nGroupID, nVarID,
    2072             :                                                          start, count, &fData);
    2073           0 :                         CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.8g",
    2074             :                                     fData);
    2075           0 :                         break;
    2076          18 :                     case NC_DOUBLE:
    2077             :                         double dfData;
    2078          18 :                         /* status = */ nc_get_vara_double(
    2079             :                             nGroupID, nVarID, start, count, &dfData);
    2080          18 :                         CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.16g",
    2081             :                                     dfData);
    2082          18 :                         break;
    2083           0 :                     case NC_UBYTE:
    2084             :                         unsigned char ucData;
    2085           0 :                         /* status = */ nc_get_vara_uchar(nGroupID, nVarID,
    2086             :                                                          start, count, &ucData);
    2087           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", ucData);
    2088           0 :                         break;
    2089           0 :                     case NC_USHORT:
    2090             :                         unsigned short usData;
    2091           0 :                         /* status = */ nc_get_vara_ushort(
    2092             :                             nGroupID, nVarID, start, count, &usData);
    2093           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", usData);
    2094           0 :                         break;
    2095           0 :                     case NC_UINT:
    2096             :                     {
    2097             :                         unsigned int unData;
    2098           0 :                         /* status = */ nc_get_vara_uint(nGroupID, nVarID, start,
    2099             :                                                         count, &unData);
    2100           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", unData);
    2101           0 :                         break;
    2102             :                     }
    2103           1 :                     case NC_INT64:
    2104             :                     {
    2105             :                         long long nData;
    2106           1 :                         /* status = */ nc_get_vara_longlong(
    2107             :                             nGroupID, nVarID, start, count, &nData);
    2108           1 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GIB,
    2109             :                                  nData);
    2110           1 :                         break;
    2111             :                     }
    2112           0 :                     case NC_UINT64:
    2113             :                     {
    2114             :                         unsigned long long unData;
    2115           0 :                         /* status = */ nc_get_vara_ulonglong(
    2116             :                             nGroupID, nVarID, start, count, &unData);
    2117           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GUIB,
    2118             :                                  unData);
    2119           0 :                         break;
    2120             :                     }
    2121           0 :                     default:
    2122           0 :                         CPLDebug("GDAL_netCDF", "invalid dim %s, type=%d",
    2123             :                                  szMetaTemp, nVarType);
    2124           0 :                         break;
    2125             :                 }
    2126             :             }
    2127             :             else
    2128             :             {
    2129           0 :                 snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", result + 1);
    2130             :             }
    2131             : 
    2132             :             // Save dimension value.
    2133             :             // NOTE: removed #original_units as not part of CF-1.
    2134             : 
    2135          38 :             GDALPamRasterBand::SetMetadataItem(szMetaName, szMetaTemp);
    2136             :         }
    2137             : 
    2138             :         // Avoid int32 overflow. Perhaps something more sensible to do here ?
    2139          40 :         if (result > 0 && Sum > INT_MAX / result)
    2140           0 :             break;
    2141          40 :         if (Taken > INT_MAX - result * Sum)
    2142           0 :             break;
    2143             : 
    2144          40 :         Taken += result * Sum;
    2145             :     }  // End loop non-spatial dimensions.
    2146          48 : }
    2147             : 
    2148             : /************************************************************************/
    2149             : /*                             CheckData()                              */
    2150             : /************************************************************************/
    2151             : template <class T>
    2152        5130 : void netCDFRasterBand::CheckData(void *pImage, void *pImageNC,
    2153             :                                  size_t nTmpBlockXSize, size_t nTmpBlockYSize,
    2154             :                                  bool bCheckIsNan)
    2155             : {
    2156        5130 :     CPLAssert(pImage != nullptr && pImageNC != nullptr);
    2157             : 
    2158             :     // If this block is not a full block (in the x axis), we need to re-arrange
    2159             :     // the data this is because partial blocks are not arranged the same way in
    2160             :     // netcdf and gdal.
    2161        5130 :     if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
    2162             :     {
    2163           6 :         T *ptrWrite = static_cast<T *>(pImage);
    2164           6 :         T *ptrRead = static_cast<T *>(pImageNC);
    2165          29 :         for (size_t j = 0; j < nTmpBlockYSize;
    2166          23 :              j++, ptrWrite += nBlockXSize, ptrRead += nTmpBlockXSize)
    2167             :         {
    2168          23 :             memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T));
    2169             :         }
    2170             :     }
    2171             : 
    2172             :     // Is valid data checking needed or requested?
    2173        5130 :     if (bValidRangeValid || bCheckIsNan)
    2174             :     {
    2175        1265 :         T *ptrImage = static_cast<T *>(pImage);
    2176        2584 :         for (size_t j = 0; j < nTmpBlockYSize; j++)
    2177             :         {
    2178             :             // k moves along the gdal block, skipping the out-of-range pixels.
    2179        1319 :             size_t k = j * nBlockXSize;
    2180       96938 :             for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
    2181             :             {
    2182             :                 // Check for nodata and nan.
    2183       95619 :                 if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
    2184        6301 :                     continue;
    2185       89318 :                 if (bCheckIsNan && CPLIsNan((double)ptrImage[k]))
    2186             :                 {
    2187        5737 :                     ptrImage[k] = (T)m_dfNoDataValue;
    2188        5737 :                     continue;
    2189             :                 }
    2190             :                 // Check for valid_range.
    2191       83581 :                 if (bValidRangeValid)
    2192             :                 {
    2193       40986 :                     if (((adfValidRange[0] != m_dfNoDataValue) &&
    2194       40986 :                          (ptrImage[k] < (T)adfValidRange[0])) ||
    2195       40983 :                         ((adfValidRange[1] != m_dfNoDataValue) &&
    2196       40983 :                          (ptrImage[k] > (T)adfValidRange[1])))
    2197             :                     {
    2198           4 :                         ptrImage[k] = (T)m_dfNoDataValue;
    2199             :                     }
    2200             :                 }
    2201             :             }
    2202             :         }
    2203             :     }
    2204             : 
    2205             :     // If minimum longitude is > 180, subtract 360 from all.
    2206             :     // If not, disable checking for further calls (check just once).
    2207             :     // Only check first and last block elements since lon must be monotonic.
    2208        5130 :     const bool bIsSigned = std::numeric_limits<T>::is_signed;
    2209        5419 :     if (bCheckLongitude && bIsSigned &&
    2210           9 :         !CPLIsEqual((double)((T *)pImage)[0], m_dfNoDataValue) &&
    2211           8 :         !CPLIsEqual((double)((T *)pImage)[nTmpBlockXSize - 1],
    2212        2714 :                     m_dfNoDataValue) &&
    2213           8 :         std::min(((T *)pImage)[0], ((T *)pImage)[nTmpBlockXSize - 1]) > 180.0)
    2214             :     {
    2215           0 :         T *ptrImage = static_cast<T *>(pImage);
    2216           0 :         for (size_t j = 0; j < nTmpBlockYSize; j++)
    2217             :         {
    2218           0 :             size_t k = j * nBlockXSize;
    2219           0 :             for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
    2220             :             {
    2221           0 :                 if (!CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
    2222           0 :                     ptrImage[k] = static_cast<T>(ptrImage[k] - 360);
    2223             :             }
    2224             :         }
    2225             :     }
    2226             :     else
    2227             :     {
    2228        5130 :         bCheckLongitude = false;
    2229             :     }
    2230        5130 : }
    2231             : 
    2232             : /************************************************************************/
    2233             : /*                             CheckDataCpx()                              */
    2234             : /************************************************************************/
    2235             : template <class T>
    2236          25 : void netCDFRasterBand::CheckDataCpx(void *pImage, void *pImageNC,
    2237             :                                     size_t nTmpBlockXSize,
    2238             :                                     size_t nTmpBlockYSize, bool bCheckIsNan)
    2239             : {
    2240          25 :     CPLAssert(pImage != nullptr && pImageNC != nullptr);
    2241             : 
    2242             :     // If this block is not a full block (in the x axis), we need to re-arrange
    2243             :     // the data this is because partial blocks are not arranged the same way in
    2244             :     // netcdf and gdal.
    2245          25 :     if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
    2246             :     {
    2247           0 :         T *ptrWrite = static_cast<T *>(pImage);
    2248           0 :         T *ptrRead = static_cast<T *>(pImageNC);
    2249           0 :         for (size_t j = 0; j < nTmpBlockYSize; j++,
    2250           0 :                     ptrWrite += (2 * nBlockXSize),
    2251           0 :                     ptrRead += (2 * nTmpBlockXSize))
    2252             :         {
    2253           0 :             memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T) * 2);
    2254             :         }
    2255             :     }
    2256             : 
    2257             :     // Is valid data checking needed or requested?
    2258          25 :     if (bValidRangeValid || bCheckIsNan)
    2259             :     {
    2260           0 :         T *ptrImage = static_cast<T *>(pImage);
    2261           0 :         for (size_t j = 0; j < nTmpBlockYSize; j++)
    2262             :         {
    2263             :             // k moves along the gdal block, skipping the out-of-range pixels.
    2264           0 :             size_t k = 2 * j * nBlockXSize;
    2265           0 :             for (size_t i = 0; i < (2 * nTmpBlockXSize); i++, k++)
    2266             :             {
    2267             :                 // Check for nodata and nan.
    2268           0 :                 if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
    2269           0 :                     continue;
    2270           0 :                 if (bCheckIsNan && CPLIsNan((double)ptrImage[k]))
    2271             :                 {
    2272           0 :                     ptrImage[k] = (T)m_dfNoDataValue;
    2273           0 :                     continue;
    2274             :                 }
    2275             :                 // Check for valid_range.
    2276           0 :                 if (bValidRangeValid)
    2277             :                 {
    2278           0 :                     if (((adfValidRange[0] != m_dfNoDataValue) &&
    2279           0 :                          (ptrImage[k] < (T)adfValidRange[0])) ||
    2280           0 :                         ((adfValidRange[1] != m_dfNoDataValue) &&
    2281           0 :                          (ptrImage[k] > (T)adfValidRange[1])))
    2282             :                     {
    2283           0 :                         ptrImage[k] = (T)m_dfNoDataValue;
    2284             :                     }
    2285             :                 }
    2286             :             }
    2287             :         }
    2288             :     }
    2289          25 : }
    2290             : 
    2291             : /************************************************************************/
    2292             : /*                         FetchNetcdfChunk()                           */
    2293             : /************************************************************************/
    2294             : 
    2295        5155 : bool netCDFRasterBand::FetchNetcdfChunk(size_t xstart, size_t ystart,
    2296             :                                         void *pImage)
    2297             : {
    2298        5155 :     size_t start[MAX_NC_DIMS] = {};
    2299        5155 :     size_t edge[MAX_NC_DIMS] = {};
    2300             : 
    2301        5155 :     start[nBandXPos] = xstart;
    2302        5155 :     edge[nBandXPos] = nBlockXSize;
    2303        5155 :     if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
    2304           6 :         edge[nBandXPos] = nRasterXSize - start[nBandXPos];
    2305        5155 :     if (nBandYPos >= 0)
    2306             :     {
    2307        5151 :         start[nBandYPos] = ystart;
    2308        5151 :         edge[nBandYPos] = nBlockYSize;
    2309        5151 :         if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
    2310           4 :             edge[nBandYPos] = nRasterYSize - start[nBandYPos];
    2311             :     }
    2312        5155 :     const size_t nYChunkSize = nBandYPos < 0 ? 1 : edge[nBandYPos];
    2313             : 
    2314             : #ifdef NCDF_DEBUG
    2315             :     CPLDebug("GDAL_netCDF", "start={%ld,%ld} edge={%ld,%ld} bBottomUp=%d",
    2316             :              start[nBandXPos], nBandYPos < 0 ? 0 : start[nBandYPos],
    2317             :              edge[nBandXPos], nYChunkSize, ((netCDFDataset *)poDS)->bBottomUp);
    2318             : #endif
    2319             : 
    2320        5155 :     int nd = 0;
    2321        5155 :     nc_inq_varndims(cdfid, nZId, &nd);
    2322        5155 :     if (nd == 3)
    2323             :     {
    2324         478 :         start[panBandZPos[0]] = nLevel;  // z
    2325         478 :         edge[panBandZPos[0]] = 1;
    2326             :     }
    2327             : 
    2328             :     // Compute multidimention band position.
    2329             :     //
    2330             :     // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
    2331             :     // if Data[2,3,4,x,y]
    2332             :     //
    2333             :     //  BandPos0 = (nBand) / (3*4)
    2334             :     //  BandPos1 = (nBand - (3*4)) / (4)
    2335             :     //  BandPos2 = (nBand - (3*4)) % (4)
    2336        5155 :     if (nd > 3)
    2337             :     {
    2338         160 :         int Sum = -1;
    2339         160 :         int Taken = 0;
    2340         480 :         for (int i = 0; i < nd - 2; i++)
    2341             :         {
    2342         320 :             if (i != nd - 2 - 1)
    2343             :             {
    2344         160 :                 Sum = 1;
    2345         320 :                 for (int j = i + 1; j < nd - 2; j++)
    2346             :                 {
    2347         160 :                     Sum *= panBandZLev[j];
    2348             :                 }
    2349         160 :                 start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
    2350         160 :                 edge[panBandZPos[i]] = 1;
    2351             :             }
    2352             :             else
    2353             :             {
    2354         160 :                 start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
    2355         160 :                 edge[panBandZPos[i]] = 1;
    2356             :             }
    2357         320 :             Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
    2358             :         }
    2359             :     }
    2360             : 
    2361             :     // Make sure we are in data mode.
    2362        5155 :     static_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
    2363             : 
    2364             :     // If this block is not a full block in the x axis, we need to
    2365             :     // re-arrange the data because partial blocks are not arranged the
    2366             :     // same way in netcdf and gdal, so we first we read the netcdf data at
    2367             :     // the end of the gdal block buffer then re-arrange rows in CheckData().
    2368        5155 :     void *pImageNC = pImage;
    2369        5155 :     if (edge[nBandXPos] != static_cast<size_t>(nBlockXSize))
    2370             :     {
    2371           6 :         pImageNC = static_cast<GByte *>(pImage) +
    2372           6 :                    ((static_cast<size_t>(nBlockXSize) * nBlockYSize -
    2373          12 :                      edge[nBandXPos] * nYChunkSize) *
    2374           6 :                     (GDALGetDataTypeSize(eDataType) / 8));
    2375             :     }
    2376             : 
    2377             :     // Read data according to type.
    2378             :     int status;
    2379        5155 :     if (eDataType == GDT_Byte)
    2380             :     {
    2381        2403 :         if (bSignedData)
    2382             :         {
    2383           0 :             status = nc_get_vara_schar(cdfid, nZId, start, edge,
    2384             :                                        static_cast<signed char *>(pImageNC));
    2385           0 :             if (status == NC_NOERR)
    2386           0 :                 CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
    2387             :                                        nYChunkSize, false);
    2388             :         }
    2389             :         else
    2390             :         {
    2391        2403 :             status = nc_get_vara_uchar(cdfid, nZId, start, edge,
    2392             :                                        static_cast<unsigned char *>(pImageNC));
    2393        2403 :             if (status == NC_NOERR)
    2394        2403 :                 CheckData<unsigned char>(pImage, pImageNC, edge[nBandXPos],
    2395             :                                          nYChunkSize, false);
    2396             :         }
    2397             :     }
    2398        2752 :     else if (eDataType == GDT_Int8)
    2399             :     {
    2400          60 :         status = nc_get_vara_schar(cdfid, nZId, start, edge,
    2401             :                                    static_cast<signed char *>(pImageNC));
    2402          60 :         if (status == NC_NOERR)
    2403          60 :             CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
    2404             :                                    nYChunkSize, false);
    2405             :     }
    2406        2692 :     else if (nc_datatype == NC_SHORT)
    2407             :     {
    2408         465 :         status = nc_get_vara_short(cdfid, nZId, start, edge,
    2409             :                                    static_cast<short *>(pImageNC));
    2410         465 :         if (status == NC_NOERR)
    2411             :         {
    2412         465 :             if (eDataType == GDT_Int16)
    2413             :             {
    2414         462 :                 CheckData<GInt16>(pImage, pImageNC, edge[nBandXPos],
    2415             :                                   nYChunkSize, false);
    2416             :             }
    2417             :             else
    2418             :             {
    2419           3 :                 CheckData<GUInt16>(pImage, pImageNC, edge[nBandXPos],
    2420             :                                    nYChunkSize, false);
    2421             :             }
    2422             :         }
    2423             :     }
    2424        2227 :     else if (eDataType == GDT_Int32)
    2425             :     {
    2426             : #if SIZEOF_UNSIGNED_LONG == 4
    2427             :         status = nc_get_vara_long(cdfid, nZId, start, edge,
    2428             :                                   static_cast<long *>(pImageNC));
    2429             :         if (status == NC_NOERR)
    2430             :             CheckData<long>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2431             :                             false);
    2432             : #else
    2433         912 :         status = nc_get_vara_int(cdfid, nZId, start, edge,
    2434             :                                  static_cast<int *>(pImageNC));
    2435         912 :         if (status == NC_NOERR)
    2436         912 :             CheckData<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2437             :                            false);
    2438             : #endif
    2439             :     }
    2440        1315 :     else if (eDataType == GDT_Float32)
    2441             :     {
    2442        1178 :         status = nc_get_vara_float(cdfid, nZId, start, edge,
    2443             :                                    static_cast<float *>(pImageNC));
    2444        1178 :         if (status == NC_NOERR)
    2445        1178 :             CheckData<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2446             :                              true);
    2447             :     }
    2448         137 :     else if (eDataType == GDT_Float64)
    2449             :     {
    2450          86 :         status = nc_get_vara_double(cdfid, nZId, start, edge,
    2451             :                                     static_cast<double *>(pImageNC));
    2452          86 :         if (status == NC_NOERR)
    2453          86 :             CheckData<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2454             :                               true);
    2455             :     }
    2456          51 :     else if (eDataType == GDT_UInt16)
    2457             :     {
    2458           6 :         status = nc_get_vara_ushort(cdfid, nZId, start, edge,
    2459             :                                     static_cast<unsigned short *>(pImageNC));
    2460           6 :         if (status == NC_NOERR)
    2461           6 :             CheckData<unsigned short>(pImage, pImageNC, edge[nBandXPos],
    2462             :                                       nYChunkSize, false);
    2463             :     }
    2464          45 :     else if (eDataType == GDT_UInt32)
    2465             :     {
    2466           6 :         status = nc_get_vara_uint(cdfid, nZId, start, edge,
    2467             :                                   static_cast<unsigned int *>(pImageNC));
    2468           6 :         if (status == NC_NOERR)
    2469           6 :             CheckData<unsigned int>(pImage, pImageNC, edge[nBandXPos],
    2470             :                                     nYChunkSize, false);
    2471             :     }
    2472          39 :     else if (eDataType == GDT_Int64)
    2473             :     {
    2474           7 :         status = nc_get_vara_longlong(cdfid, nZId, start, edge,
    2475             :                                       static_cast<long long *>(pImageNC));
    2476           7 :         if (status == NC_NOERR)
    2477           7 :             CheckData<std::int64_t>(pImage, pImageNC, edge[nBandXPos],
    2478             :                                     nYChunkSize, false);
    2479             :     }
    2480          32 :     else if (eDataType == GDT_UInt64)
    2481             :     {
    2482             :         status =
    2483           7 :             nc_get_vara_ulonglong(cdfid, nZId, start, edge,
    2484             :                                   static_cast<unsigned long long *>(pImageNC));
    2485           7 :         if (status == NC_NOERR)
    2486           7 :             CheckData<std::uint64_t>(pImage, pImageNC, edge[nBandXPos],
    2487             :                                      nYChunkSize, false);
    2488             :     }
    2489          25 :     else if (eDataType == GDT_CInt16)
    2490             :     {
    2491           0 :         status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
    2492           0 :         if (status == NC_NOERR)
    2493           0 :             CheckDataCpx<short>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2494             :                                 false);
    2495             :     }
    2496          25 :     else if (eDataType == GDT_CInt32)
    2497             :     {
    2498           0 :         status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
    2499           0 :         if (status == NC_NOERR)
    2500           0 :             CheckDataCpx<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2501             :                               false);
    2502             :     }
    2503          25 :     else if (eDataType == GDT_CFloat32)
    2504             :     {
    2505          20 :         status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
    2506          20 :         if (status == NC_NOERR)
    2507          20 :             CheckDataCpx<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2508             :                                 false);
    2509             :     }
    2510           5 :     else if (eDataType == GDT_CFloat64)
    2511             :     {
    2512           5 :         status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
    2513           5 :         if (status == NC_NOERR)
    2514           5 :             CheckDataCpx<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2515             :                                  false);
    2516             :     }
    2517             : 
    2518             :     else
    2519           0 :         status = NC_EBADTYPE;
    2520             : 
    2521        5155 :     if (status != NC_NOERR)
    2522             :     {
    2523           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2524             :                  "netCDF chunk fetch failed: #%d (%s)", status,
    2525             :                  nc_strerror(status));
    2526           0 :         return false;
    2527             :     }
    2528        5155 :     return true;
    2529             : }
    2530             : 
    2531             : /************************************************************************/
    2532             : /*                             IReadBlock()                             */
    2533             : /************************************************************************/
    2534             : 
    2535        5155 : CPLErr netCDFRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
    2536             :                                     void *pImage)
    2537             : 
    2538             : {
    2539       10310 :     CPLMutexHolderD(&hNCMutex);
    2540             : 
    2541             :     // Locate X, Y and Z position in the array.
    2542             : 
    2543        5155 :     size_t xstart = static_cast<size_t>(nBlockXOff) * nBlockXSize;
    2544        5155 :     size_t ystart = 0;
    2545             : 
    2546             :     // Check y order.
    2547        5155 :     if (nBandYPos >= 0)
    2548             :     {
    2549        5151 :         auto poGDS = static_cast<netCDFDataset *>(poDS);
    2550        5151 :         if (poGDS->bBottomUp)
    2551             :         {
    2552        4236 :             if (nBlockYSize == 1)
    2553             :             {
    2554        4223 :                 ystart = nRasterYSize - 1 - nBlockYOff;
    2555             :             }
    2556             :             else
    2557             :             {
    2558             :                 // in GDAL space
    2559          13 :                 ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
    2560             :                 const size_t yend =
    2561          26 :                     std::min(ystart + nBlockYSize - 1,
    2562          13 :                              static_cast<size_t>(nRasterYSize - 1));
    2563             :                 // in netCDF space
    2564          13 :                 const size_t nFirstChunkLine = nRasterYSize - 1 - yend;
    2565          13 :                 const size_t nLastChunkLine = nRasterYSize - 1 - ystart;
    2566          13 :                 const size_t nFirstChunkBlock = nFirstChunkLine / nBlockYSize;
    2567          13 :                 const size_t nLastChunkBlock = nLastChunkLine / nBlockYSize;
    2568             : 
    2569             :                 const auto firstKey = netCDFDataset::ChunkKey(
    2570          13 :                     nBlockXOff, nFirstChunkBlock, nBand);
    2571             :                 const auto secondKey =
    2572          13 :                     netCDFDataset::ChunkKey(nBlockXOff, nLastChunkBlock, nBand);
    2573             : 
    2574             :                 // Retrieve data from the one or 2 needed netCDF chunks
    2575          13 :                 std::shared_ptr<std::vector<GByte>> firstChunk;
    2576          13 :                 std::shared_ptr<std::vector<GByte>> secondChunk;
    2577          13 :                 if (poGDS->poChunkCache)
    2578             :                 {
    2579          13 :                     poGDS->poChunkCache->tryGet(firstKey, firstChunk);
    2580          13 :                     if (firstKey != secondKey)
    2581           6 :                         poGDS->poChunkCache->tryGet(secondKey, secondChunk);
    2582             :                 }
    2583             :                 const size_t nChunkLineSize =
    2584          13 :                     static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
    2585          13 :                     nBlockXSize;
    2586          13 :                 const size_t nChunkSize = nChunkLineSize * nBlockYSize;
    2587          13 :                 if (!firstChunk)
    2588             :                 {
    2589          11 :                     firstChunk.reset(new std::vector<GByte>(nChunkSize));
    2590          11 :                     if (!FetchNetcdfChunk(xstart,
    2591          11 :                                           nFirstChunkBlock * nBlockYSize,
    2592          11 :                                           firstChunk.get()->data()))
    2593           0 :                         return CE_Failure;
    2594          11 :                     if (poGDS->poChunkCache)
    2595          11 :                         poGDS->poChunkCache->insert(firstKey, firstChunk);
    2596             :                 }
    2597          13 :                 if (!secondChunk && firstKey != secondKey)
    2598             :                 {
    2599           2 :                     secondChunk.reset(new std::vector<GByte>(nChunkSize));
    2600           2 :                     if (!FetchNetcdfChunk(xstart, nLastChunkBlock * nBlockYSize,
    2601           2 :                                           secondChunk.get()->data()))
    2602           0 :                         return CE_Failure;
    2603           2 :                     if (poGDS->poChunkCache)
    2604           2 :                         poGDS->poChunkCache->insert(secondKey, secondChunk);
    2605             :                 }
    2606             : 
    2607             :                 // Assemble netCDF chunks into GDAL block
    2608          13 :                 GByte *pabyImage = static_cast<GByte *>(pImage);
    2609          13 :                 const size_t nFirstChunkBlockLine =
    2610          13 :                     nFirstChunkBlock * nBlockYSize;
    2611          13 :                 const size_t nLastChunkBlockLine =
    2612          13 :                     nLastChunkBlock * nBlockYSize;
    2613         146 :                 for (size_t iLine = ystart; iLine <= yend; iLine++)
    2614             :                 {
    2615         133 :                     const size_t nLineFromBottom = nRasterYSize - 1 - iLine;
    2616         133 :                     const size_t nChunkY = nLineFromBottom / nBlockYSize;
    2617         133 :                     if (nChunkY == nFirstChunkBlock)
    2618             :                     {
    2619         121 :                         memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
    2620         121 :                                firstChunk.get()->data() +
    2621         121 :                                    (nLineFromBottom - nFirstChunkBlockLine) *
    2622             :                                        nChunkLineSize,
    2623             :                                nChunkLineSize);
    2624             :                     }
    2625             :                     else
    2626             :                     {
    2627          12 :                         CPLAssert(nChunkY == nLastChunkBlock);
    2628          12 :                         assert(secondChunk);
    2629          12 :                         memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
    2630          12 :                                secondChunk.get()->data() +
    2631          12 :                                    (nLineFromBottom - nLastChunkBlockLine) *
    2632             :                                        nChunkLineSize,
    2633             :                                nChunkLineSize);
    2634             :                     }
    2635             :                 }
    2636          13 :                 return CE_None;
    2637             :             }
    2638             :         }
    2639             :         else
    2640             :         {
    2641         915 :             ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
    2642             :         }
    2643             :     }
    2644             : 
    2645        5142 :     return FetchNetcdfChunk(xstart, ystart, pImage) ? CE_None : CE_Failure;
    2646             : }
    2647             : 
    2648             : /************************************************************************/
    2649             : /*                             IWriteBlock()                            */
    2650             : /************************************************************************/
    2651             : 
    2652        2010 : CPLErr netCDFRasterBand::IWriteBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
    2653             :                                      void *pImage)
    2654             : {
    2655        4020 :     CPLMutexHolderD(&hNCMutex);
    2656             : 
    2657             : #ifdef NCDF_DEBUG
    2658             :     if (nBlockYOff == 0 || (nBlockYOff == nRasterYSize - 1))
    2659             :         CPLDebug("GDAL_netCDF",
    2660             :                  "netCDFRasterBand::IWriteBlock( %d, %d, ...) nBand=%d",
    2661             :                  nBlockXOff, nBlockYOff, nBand);
    2662             : #endif
    2663             : 
    2664        2010 :     int nd = 0;
    2665        2010 :     nc_inq_varndims(cdfid, nZId, &nd);
    2666             : 
    2667             :     // Locate X, Y and Z position in the array.
    2668             : 
    2669             :     size_t start[MAX_NC_DIMS];
    2670        2010 :     memset(start, 0, sizeof(start));
    2671        2010 :     start[nBandXPos] = static_cast<size_t>(nBlockXOff) * nBlockXSize;
    2672             : 
    2673             :     // check y order.
    2674        2010 :     if (static_cast<netCDFDataset *>(poDS)->bBottomUp)
    2675             :     {
    2676        1986 :         if (nBlockYSize == 1)
    2677             :         {
    2678        1986 :             start[nBandYPos] = nRasterYSize - 1 - nBlockYOff;
    2679             :         }
    2680             :         else
    2681             :         {
    2682           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    2683             :                      "nBlockYSize = %d, only 1 supported when "
    2684             :                      "writing bottom-up dataset",
    2685             :                      nBlockYSize);
    2686           0 :             return CE_Failure;
    2687             :         }
    2688             :     }
    2689             :     else
    2690             :     {
    2691          24 :         start[nBandYPos] = static_cast<size_t>(nBlockYOff) * nBlockYSize;  // y
    2692             :     }
    2693             : 
    2694        2010 :     size_t edge[MAX_NC_DIMS] = {};
    2695             : 
    2696        2010 :     edge[nBandXPos] = nBlockXSize;
    2697        2010 :     if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
    2698           0 :         edge[nBandXPos] = nRasterXSize - start[nBandXPos];
    2699        2010 :     edge[nBandYPos] = nBlockYSize;
    2700        2010 :     if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
    2701           0 :         edge[nBandYPos] = nRasterYSize - start[nBandYPos];
    2702             : 
    2703        2010 :     if (nd == 3)
    2704             :     {
    2705          10 :         start[panBandZPos[0]] = nLevel;  // z
    2706          10 :         edge[panBandZPos[0]] = 1;
    2707             :     }
    2708             : 
    2709             :     // Compute multidimention band position.
    2710             :     //
    2711             :     // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
    2712             :     // if Data[2,3,4,x,y]
    2713             :     //
    2714             :     //  BandPos0 = (nBand) / (3*4)
    2715             :     //  BandPos1 = (nBand - (3*4)) / (4)
    2716             :     //  BandPos2 = (nBand - (3*4)) % (4)
    2717        2010 :     if (nd > 3)
    2718             :     {
    2719         178 :         int Sum = -1;
    2720         178 :         int Taken = 0;
    2721         534 :         for (int i = 0; i < nd - 2; i++)
    2722             :         {
    2723         356 :             if (i != nd - 2 - 1)
    2724             :             {
    2725         178 :                 Sum = 1;
    2726         356 :                 for (int j = i + 1; j < nd - 2; j++)
    2727             :                 {
    2728         178 :                     Sum *= panBandZLev[j];
    2729             :                 }
    2730         178 :                 start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
    2731         178 :                 edge[panBandZPos[i]] = 1;
    2732             :             }
    2733             :             else
    2734             :             {
    2735         178 :                 start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
    2736         178 :                 edge[panBandZPos[i]] = 1;
    2737             :             }
    2738         356 :             Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
    2739             :         }
    2740             :     }
    2741             : 
    2742             :     // Make sure we are in data mode.
    2743        2010 :     static_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
    2744             : 
    2745             :     // Copy data according to type.
    2746        2010 :     int status = 0;
    2747        2010 :     if (eDataType == GDT_Byte)
    2748             :     {
    2749        1451 :         if (bSignedData)
    2750           0 :             status = nc_put_vara_schar(cdfid, nZId, start, edge,
    2751             :                                        static_cast<signed char *>(pImage));
    2752             :         else
    2753        1451 :             status = nc_put_vara_uchar(cdfid, nZId, start, edge,
    2754             :                                        static_cast<unsigned char *>(pImage));
    2755             :     }
    2756         559 :     else if (eDataType == GDT_Int8)
    2757             :     {
    2758          40 :         status = nc_put_vara_schar(cdfid, nZId, start, edge,
    2759             :                                    static_cast<signed char *>(pImage));
    2760             :     }
    2761         519 :     else if (nc_datatype == NC_SHORT)
    2762             :     {
    2763         101 :         status = nc_put_vara_short(cdfid, nZId, start, edge,
    2764             :                                    static_cast<short *>(pImage));
    2765             :     }
    2766         418 :     else if (eDataType == GDT_Int32)
    2767             :     {
    2768         210 :         status = nc_put_vara_int(cdfid, nZId, start, edge,
    2769             :                                  static_cast<int *>(pImage));
    2770             :     }
    2771         208 :     else if (eDataType == GDT_Float32)
    2772             :     {
    2773         128 :         status = nc_put_vara_float(cdfid, nZId, start, edge,
    2774             :                                    static_cast<float *>(pImage));
    2775             :     }
    2776          80 :     else if (eDataType == GDT_Float64)
    2777             :     {
    2778          50 :         status = nc_put_vara_double(cdfid, nZId, start, edge,
    2779             :                                     static_cast<double *>(pImage));
    2780             :     }
    2781          30 :     else if (eDataType == GDT_UInt16 &&
    2782          12 :              static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    2783             :     {
    2784          12 :         status = nc_put_vara_ushort(cdfid, nZId, start, edge,
    2785             :                                     static_cast<unsigned short *>(pImage));
    2786             :     }
    2787          18 :     else if (eDataType == GDT_UInt32 &&
    2788          12 :              static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    2789             :     {
    2790          12 :         status = nc_put_vara_uint(cdfid, nZId, start, edge,
    2791             :                                   static_cast<unsigned int *>(pImage));
    2792             :     }
    2793           6 :     else if (eDataType == GDT_UInt64 &&
    2794           3 :              static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    2795             :     {
    2796           3 :         status =
    2797           3 :             nc_put_vara_ulonglong(cdfid, nZId, start, edge,
    2798             :                                   static_cast<unsigned long long *>(pImage));
    2799             :     }
    2800           3 :     else if (eDataType == GDT_Int64 &&
    2801           3 :              static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    2802             :     {
    2803           3 :         status = nc_put_vara_longlong(cdfid, nZId, start, edge,
    2804             :                                       static_cast<long long *>(pImage));
    2805             :     }
    2806             :     else
    2807             :     {
    2808           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    2809             :                  "The NetCDF driver does not support GDAL data type %d",
    2810           0 :                  eDataType);
    2811           0 :         status = NC_EBADTYPE;
    2812             :     }
    2813        2010 :     NCDF_ERR(status);
    2814             : 
    2815        2010 :     if (status != NC_NOERR)
    2816             :     {
    2817           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2818             :                  "netCDF scanline write failed: %s", nc_strerror(status));
    2819           0 :         return CE_Failure;
    2820             :     }
    2821             : 
    2822        2010 :     return CE_None;
    2823             : }
    2824             : 
    2825             : /************************************************************************/
    2826             : /* ==================================================================== */
    2827             : /*                              netCDFDataset                           */
    2828             : /* ==================================================================== */
    2829             : /************************************************************************/
    2830             : 
    2831             : /************************************************************************/
    2832             : /*                           netCDFDataset()                            */
    2833             : /************************************************************************/
    2834             : 
    2835         961 : netCDFDataset::netCDFDataset()
    2836             :     :
    2837             : // Basic dataset vars.
    2838             : #ifdef ENABLE_NCDUMP
    2839             :       bFileToDestroyAtClosing(false),
    2840             : #endif
    2841             :       cdfid(-1), nSubDatasets(0), papszSubDatasets(nullptr),
    2842             :       papszMetadata(nullptr), bBottomUp(true), eFormat(NCDF_FORMAT_NONE),
    2843             :       bIsGdalFile(false), bIsGdalCfFile(false), pszCFProjection(nullptr),
    2844             :       pszCFCoordinates(nullptr), nCFVersion(1.6), bSGSupport(false),
    2845         961 :       eMultipleLayerBehavior(SINGLE_LAYER), logCount(0), vcdf(cdfid),
    2846         961 :       GeometryScribe(vcdf, this->generateLogName()),
    2847         961 :       FieldScribe(vcdf, this->generateLogName()),
    2848        1922 :       bufManager(CPLGetUsablePhysicalRAM() / 5),
    2849             : 
    2850             :       // projection/GT.
    2851             :       nXDimID(-1), nYDimID(-1), bIsProjected(false),
    2852             :       bIsGeographic(false),  // Can be not projected, and also not geographic
    2853             :       // State vars.
    2854             :       bDefineMode(true), bAddedGridMappingRef(false),
    2855             : 
    2856             :       // Create vars.
    2857             :       papszCreationOptions(nullptr), eCompress(NCDF_COMPRESS_NONE),
    2858             :       nZLevel(NCDF_DEFLATE_LEVEL), bChunking(false), nCreateMode(NC_CLOBBER),
    2859        2883 :       bSignedData(true)
    2860             : {
    2861         961 :     m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    2862             : 
    2863             :     // Projection/GT.
    2864         961 :     m_adfGeoTransform[0] = 0.0;
    2865         961 :     m_adfGeoTransform[1] = 1.0;
    2866         961 :     m_adfGeoTransform[2] = 0.0;
    2867         961 :     m_adfGeoTransform[3] = 0.0;
    2868         961 :     m_adfGeoTransform[4] = 0.0;
    2869         961 :     m_adfGeoTransform[5] = 1.0;
    2870             : 
    2871             :     // Set buffers
    2872         961 :     bufManager.addBuffer(&(GeometryScribe.getMemBuffer()));
    2873         961 :     bufManager.addBuffer(&(FieldScribe.getMemBuffer()));
    2874         961 : }
    2875             : 
    2876             : /************************************************************************/
    2877             : /*                           ~netCDFDataset()                           */
    2878             : /************************************************************************/
    2879             : 
    2880        1861 : netCDFDataset::~netCDFDataset()
    2881             : 
    2882             : {
    2883         961 :     netCDFDataset::Close();
    2884        1861 : }
    2885             : 
    2886             : /************************************************************************/
    2887             : /*                              Close()                                 */
    2888             : /************************************************************************/
    2889             : 
    2890        1687 : CPLErr netCDFDataset::Close()
    2891             : {
    2892        1687 :     CPLErr eErr = CE_None;
    2893        1687 :     if (nOpenFlags != OPEN_FLAGS_CLOSED)
    2894             :     {
    2895        1922 :         CPLMutexHolderD(&hNCMutex);
    2896             : 
    2897             : #ifdef NCDF_DEBUG
    2898             :         CPLDebug("GDAL_netCDF",
    2899             :                  "netCDFDataset::~netCDFDataset(), cdfid=%d filename=%s", cdfid,
    2900             :                  osFilename.c_str());
    2901             : #endif
    2902             : 
    2903             :         // Write data related to geotransform
    2904        1188 :         if (GetAccess() == GA_Update && !m_bAddedProjectionVarsData &&
    2905         227 :             (m_bHasProjection || m_bHasGeoTransform))
    2906             :         {
    2907             :             // Ensure projection is written if GeoTransform OR Projection are
    2908             :             // missing.
    2909          36 :             if (!m_bAddedProjectionVarsDefs)
    2910             :             {
    2911           2 :                 AddProjectionVars(true, nullptr, nullptr);
    2912             :             }
    2913          36 :             AddProjectionVars(false, nullptr, nullptr);
    2914             :         }
    2915             : 
    2916         961 :         if (netCDFDataset::FlushCache(true) != CE_None)
    2917           0 :             eErr = CE_Failure;
    2918             : 
    2919         961 :         if (!SGCommitPendingTransaction())
    2920           0 :             eErr = CE_Failure;
    2921             : 
    2922         963 :         for (size_t i = 0; i < apoVectorDatasets.size(); i++)
    2923           2 :             delete apoVectorDatasets[i];
    2924             : 
    2925             :         // Make sure projection variable is written to band variable.
    2926         961 :         if (GetAccess() == GA_Update && !bAddedGridMappingRef)
    2927             :         {
    2928         240 :             if (!AddGridMappingRef())
    2929           0 :                 eErr = CE_Failure;
    2930             :         }
    2931             : 
    2932         961 :         CSLDestroy(papszMetadata);
    2933         961 :         CSLDestroy(papszSubDatasets);
    2934         961 :         CSLDestroy(papszCreationOptions);
    2935             : 
    2936         961 :         CPLFree(pszCFProjection);
    2937             : 
    2938         961 :         if (cdfid > 0)
    2939             :         {
    2940             : #ifdef NCDF_DEBUG
    2941             :             CPLDebug("GDAL_netCDF", "calling nc_close( %d)", cdfid);
    2942             : #endif
    2943         641 :             int status = GDAL_nc_close(cdfid);
    2944             : #ifdef ENABLE_UFFD
    2945         641 :             NETCDF_UFFD_UNMAP(pCtx);
    2946             : #endif
    2947         641 :             NCDF_ERR(status);
    2948         641 :             if (status != NC_NOERR)
    2949           0 :                 eErr = CE_Failure;
    2950             :         }
    2951             : 
    2952         961 :         if (fpVSIMEM)
    2953          15 :             VSIFCloseL(fpVSIMEM);
    2954             : 
    2955             : #ifdef ENABLE_NCDUMP
    2956         961 :         if (bFileToDestroyAtClosing)
    2957           0 :             VSIUnlink(osFilename);
    2958             : #endif
    2959             : 
    2960         961 :         if (GDALPamDataset::Close() != CE_None)
    2961           0 :             eErr = CE_Failure;
    2962             :     }
    2963        1687 :     return eErr;
    2964             : }
    2965             : 
    2966             : /************************************************************************/
    2967             : /*                            SetDefineMode()                           */
    2968             : /************************************************************************/
    2969        9107 : bool netCDFDataset::SetDefineMode(bool bNewDefineMode)
    2970             : {
    2971             :     // Do nothing if already in new define mode
    2972             :     // or if dataset is in read-only mode or if dataset is true NC4 dataset.
    2973        9571 :     if (bDefineMode == bNewDefineMode || GetAccess() == GA_ReadOnly ||
    2974         464 :         eFormat == NCDF_FORMAT_NC4)
    2975        8786 :         return true;
    2976             : 
    2977         321 :     CPLDebug("GDAL_netCDF", "SetDefineMode(%d) old=%d",
    2978         321 :              static_cast<int>(bNewDefineMode), static_cast<int>(bDefineMode));
    2979             : 
    2980         321 :     bDefineMode = bNewDefineMode;
    2981             : 
    2982             :     int status;
    2983         321 :     if (bDefineMode)
    2984         101 :         status = nc_redef(cdfid);
    2985             :     else
    2986         220 :         status = nc_enddef(cdfid);
    2987             : 
    2988         321 :     NCDF_ERR(status);
    2989         321 :     return status == NC_NOERR;
    2990             : }
    2991             : 
    2992             : /************************************************************************/
    2993             : /*                      GetMetadataDomainList()                         */
    2994             : /************************************************************************/
    2995             : 
    2996          27 : char **netCDFDataset::GetMetadataDomainList()
    2997             : {
    2998          27 :     char **papszDomains = BuildMetadataDomainList(
    2999             :         GDALDataset::GetMetadataDomainList(), TRUE, "SUBDATASETS", nullptr);
    3000          28 :     for (const auto &kv : m_oMapDomainToJSon)
    3001           1 :         papszDomains = CSLAddString(papszDomains, ("json:" + kv.first).c_str());
    3002          27 :     return papszDomains;
    3003             : }
    3004             : 
    3005             : /************************************************************************/
    3006             : /*                            GetMetadata()                             */
    3007             : /************************************************************************/
    3008         347 : char **netCDFDataset::GetMetadata(const char *pszDomain)
    3009             : {
    3010         347 :     if (pszDomain != nullptr && STARTS_WITH_CI(pszDomain, "SUBDATASETS"))
    3011          34 :         return papszSubDatasets;
    3012             : 
    3013         313 :     if (pszDomain != nullptr && STARTS_WITH(pszDomain, "json:"))
    3014             :     {
    3015           1 :         auto iter = m_oMapDomainToJSon.find(pszDomain + strlen("json:"));
    3016           1 :         if (iter != m_oMapDomainToJSon.end())
    3017           1 :             return iter->second.List();
    3018             :     }
    3019             : 
    3020         312 :     return GDALDataset::GetMetadata(pszDomain);
    3021             : }
    3022             : 
    3023             : /************************************************************************/
    3024             : /*                        SetMetadataItem()                             */
    3025             : /************************************************************************/
    3026             : 
    3027          40 : CPLErr netCDFDataset::SetMetadataItem(const char *pszName, const char *pszValue,
    3028             :                                       const char *pszDomain)
    3029             : {
    3030          80 :     if (GetAccess() == GA_Update &&
    3031          80 :         (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)
    3032             :     {
    3033          40 :         std::string osName(pszName);
    3034             : 
    3035             :         // Same logic as in CopyMetadata()
    3036          40 :         if (STARTS_WITH(osName.c_str(), "NC_GLOBAL#"))
    3037           8 :             osName = osName.substr(strlen("NC_GLOBAL#"));
    3038          32 :         else if (strchr(osName.c_str(), '#') == nullptr)
    3039           3 :             osName = "GDAL_" + osName;
    3040             : 
    3041          80 :         if (STARTS_WITH(osName.c_str(), "NETCDF_DIM_") ||
    3042          40 :             strchr(osName.c_str(), '#') != nullptr)
    3043             :         {
    3044             :             // do nothing
    3045             :         }
    3046             :         else
    3047             :         {
    3048          11 :             SetDefineMode(true);
    3049             : 
    3050          11 :             if (!NCDFPutAttr(cdfid, NC_GLOBAL, osName.c_str(), pszValue))
    3051          11 :                 return CE_Failure;
    3052             :         }
    3053             :     }
    3054             : 
    3055          29 :     return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);
    3056             : }
    3057             : 
    3058             : /************************************************************************/
    3059             : /*                          SetMetadata()                               */
    3060             : /************************************************************************/
    3061             : 
    3062           6 : CPLErr netCDFDataset::SetMetadata(char **papszMD, const char *pszDomain)
    3063             : {
    3064          10 :     if (GetAccess() == GA_Update &&
    3065           4 :         (pszDomain == nullptr || pszDomain[0] == '\0'))
    3066             :     {
    3067             :         // We don't handle metadata item removal for now
    3068          46 :         for (const char *const *papszIter = papszMD; papszIter && *papszIter;
    3069             :              ++papszIter)
    3070             :         {
    3071          40 :             char *pszName = nullptr;
    3072          40 :             const char *pszValue = CPLParseNameValue(*papszIter, &pszName);
    3073          40 :             if (pszName && pszValue)
    3074          40 :                 SetMetadataItem(pszName, pszValue);
    3075          40 :             CPLFree(pszName);
    3076             :         }
    3077             :     }
    3078           6 :     return GDALPamDataset::SetMetadata(papszMD, pszDomain);
    3079             : }
    3080             : 
    3081             : /************************************************************************/
    3082             : /*                          GetSpatialRef()                             */
    3083             : /************************************************************************/
    3084             : 
    3085         167 : const OGRSpatialReference *netCDFDataset::GetSpatialRef() const
    3086             : {
    3087         167 :     if (m_bHasProjection)
    3088          68 :         return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
    3089             : 
    3090          99 :     return GDALPamDataset::GetSpatialRef();
    3091             : }
    3092             : 
    3093             : /************************************************************************/
    3094             : /*                           SerializeToXML()                           */
    3095             : /************************************************************************/
    3096             : 
    3097          15 : CPLXMLNode *netCDFDataset::SerializeToXML(const char *pszUnused)
    3098             : 
    3099             : {
    3100             :     // Overridden from GDALPamDataset to add only band histogram
    3101             :     // and statistics. See bug #4244.
    3102             : 
    3103          15 :     if (psPam == nullptr)
    3104           0 :         return nullptr;
    3105             : 
    3106             :     // Setup root node and attributes.
    3107          15 :     CPLXMLNode *psDSTree = CPLCreateXMLNode(nullptr, CXT_Element, "PAMDataset");
    3108             : 
    3109             :     // Process bands.
    3110          46 :     for (int iBand = 0; iBand < GetRasterCount(); iBand++)
    3111             :     {
    3112             :         netCDFRasterBand *poBand =
    3113          31 :             static_cast<netCDFRasterBand *>(GetRasterBand(iBand + 1));
    3114             : 
    3115          31 :         if (poBand == nullptr || !(poBand->GetMOFlags() & GMO_PAM_CLASS))
    3116           0 :             continue;
    3117             : 
    3118          31 :         CPLXMLNode *psBandTree = poBand->SerializeToXML(pszUnused);
    3119             : 
    3120          31 :         if (psBandTree != nullptr)
    3121           2 :             CPLAddXMLChild(psDSTree, psBandTree);
    3122             :     }
    3123             : 
    3124             :     // We don't want to return anything if we had no metadata to attach.
    3125          15 :     if (psDSTree->psChild == nullptr)
    3126             :     {
    3127          13 :         CPLDestroyXMLNode(psDSTree);
    3128          13 :         psDSTree = nullptr;
    3129             :     }
    3130             : 
    3131          15 :     return psDSTree;
    3132             : }
    3133             : 
    3134             : /************************************************************************/
    3135             : /*                           FetchCopyParam()                            */
    3136             : /************************************************************************/
    3137             : 
    3138         332 : double netCDFDataset::FetchCopyParam(const char *pszGridMappingValue,
    3139             :                                      const char *pszParam, double dfDefault,
    3140             :                                      bool *pbFound)
    3141             : 
    3142             : {
    3143             :     char *pszTemp =
    3144         332 :         CPLStrdup(CPLSPrintf("%s#%s", pszGridMappingValue, pszParam));
    3145         332 :     const char *pszValue = CSLFetchNameValue(papszMetadata, pszTemp);
    3146         332 :     CPLFree(pszTemp);
    3147             : 
    3148         332 :     if (pbFound)
    3149             :     {
    3150         332 :         *pbFound = (pszValue != nullptr);
    3151             :     }
    3152             : 
    3153         332 :     if (pszValue)
    3154             :     {
    3155           0 :         return CPLAtofM(pszValue);
    3156             :     }
    3157             : 
    3158         332 :     return dfDefault;
    3159             : }
    3160             : 
    3161             : /************************************************************************/
    3162             : /*                           FetchStandardParallels()                   */
    3163             : /************************************************************************/
    3164             : 
    3165             : std::vector<std::string>
    3166           0 : netCDFDataset::FetchStandardParallels(const char *pszGridMappingValue)
    3167             : {
    3168             :     // cf-1.0 tags
    3169           0 :     const char *pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL);
    3170             : 
    3171           0 :     std::vector<std::string> ret;
    3172           0 :     if (pszValue != nullptr)
    3173             :     {
    3174           0 :         CPLStringList aosValues;
    3175           0 :         if (pszValue[0] != '{' &&
    3176           0 :             CPLString(pszValue).Trim().find(' ') != std::string::npos)
    3177             :         {
    3178             :             // Some files like
    3179             :             // ftp://data.knmi.nl/download/KNW-NetCDF-3D/1.0/noversion/2013/11/14/KNW-1.0_H37-ERA_NL_20131114.nc
    3180             :             // do not use standard formatting for arrays, but just space
    3181             :             // separated syntax
    3182           0 :             aosValues = CSLTokenizeString2(pszValue, " ", 0);
    3183             :         }
    3184             :         else
    3185             :         {
    3186           0 :             aosValues = NCDFTokenizeArray(pszValue);
    3187             :         }
    3188           0 :         for (int i = 0; i < aosValues.size(); i++)
    3189             :         {
    3190           0 :             ret.push_back(aosValues[i]);
    3191             :         }
    3192             :     }
    3193             :     // Try gdal tags.
    3194             :     else
    3195             :     {
    3196           0 :         pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_1);
    3197             : 
    3198           0 :         if (pszValue != nullptr)
    3199           0 :             ret.push_back(pszValue);
    3200             : 
    3201           0 :         pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_2);
    3202             : 
    3203           0 :         if (pszValue != nullptr)
    3204           0 :             ret.push_back(pszValue);
    3205             :     }
    3206             : 
    3207           0 :     return ret;
    3208             : }
    3209             : 
    3210             : /************************************************************************/
    3211             : /*                           FetchAttr()                                */
    3212             : /************************************************************************/
    3213             : 
    3214        3579 : const char *netCDFDataset::FetchAttr(const char *pszVarFullName,
    3215             :                                      const char *pszAttr)
    3216             : 
    3217             : {
    3218        3579 :     char *pszKey = CPLStrdup(CPLSPrintf("%s#%s", pszVarFullName, pszAttr));
    3219        3579 :     const char *pszValue = CSLFetchNameValue(papszMetadata, pszKey);
    3220        3579 :     CPLFree(pszKey);
    3221        3579 :     return pszValue;
    3222             : }
    3223             : 
    3224        2371 : const char *netCDFDataset::FetchAttr(int nGroupId, int nVarId,
    3225             :                                      const char *pszAttr)
    3226             : 
    3227             : {
    3228        2371 :     char *pszVarFullName = nullptr;
    3229        2371 :     NCDFGetVarFullName(nGroupId, nVarId, &pszVarFullName);
    3230        2371 :     const char *pszValue = FetchAttr(pszVarFullName, pszAttr);
    3231        2371 :     CPLFree(pszVarFullName);
    3232        2371 :     return pszValue;
    3233             : }
    3234             : 
    3235             : /************************************************************************/
    3236             : /*                       IsDifferenceBelow()                            */
    3237             : /************************************************************************/
    3238             : 
    3239        1015 : static bool IsDifferenceBelow(double dfA, double dfB, double dfError)
    3240             : {
    3241        1015 :     const double dfAbsDiff = fabs(dfA - dfB);
    3242        1015 :     return dfAbsDiff <= dfError;
    3243             : }
    3244             : 
    3245             : /************************************************************************/
    3246             : /*                      SetProjectionFromVar()                          */
    3247             : /************************************************************************/
    3248         495 : void netCDFDataset::SetProjectionFromVar(
    3249             :     int nGroupId, int nVarId, bool bReadSRSOnly, const char *pszGivenGM,
    3250             :     std::string *returnProjStr, nccfdriver::SGeometry_Reader *sg,
    3251             :     std::vector<std::string> *paosRemovedMDItems)
    3252             : {
    3253         495 :     bool bGotGeogCS = false;
    3254         495 :     bool bGotCfSRS = false;
    3255         495 :     bool bGotCfWktSRS = false;
    3256         495 :     bool bGotGdalSRS = false;
    3257         495 :     bool bGotCfGT = false;
    3258         495 :     bool bGotGdalGT = false;
    3259             : 
    3260             :     // These values from CF metadata.
    3261         495 :     OGRSpatialReference oSRS;
    3262         495 :     oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    3263         495 :     size_t xdim = nRasterXSize;
    3264         495 :     size_t ydim = nRasterYSize;
    3265             : 
    3266             :     // These values from GDAL metadata.
    3267         495 :     const char *pszWKT = nullptr;
    3268         495 :     const char *pszGeoTransform = nullptr;
    3269             : 
    3270         495 :     netCDFDataset *poDS = this;  // Perhaps this should be removed for clarity.
    3271             : 
    3272         495 :     CPLDebug("GDAL_netCDF", "\n=====\nSetProjectionFromVar( %d, %d)", nGroupId,
    3273             :              nVarId);
    3274             : 
    3275             :     // Get x/y range information.
    3276             : 
    3277             :     // Temp variables to use in SetGeoTransform() and SetProjection().
    3278         495 :     double adfTempGeoTransform[6] = {0.0, 1.0, 0.0, 0.0, 0.0, 1.0};
    3279             : 
    3280             :     // Look for grid_mapping metadata.
    3281         495 :     const char *pszValue = pszGivenGM;
    3282         495 :     CPLString osTmpGridMapping;  // let is in this outer scope as pszValue may
    3283             :         // point to it
    3284         495 :     if (pszValue == nullptr)
    3285             :     {
    3286         447 :         pszValue = FetchAttr(nGroupId, nVarId, CF_GRD_MAPPING);
    3287         447 :         if (pszValue && strchr(pszValue, ':') && strchr(pszValue, ' '))
    3288             :         {
    3289             :             // Expanded form of grid_mapping
    3290             :             // e.g. "crsOSGB: x y crsWGS84: lat lon"
    3291             :             // Pickup the grid_mapping whose coordinates are dimensions of the
    3292             :             // variable
    3293           6 :             CPLStringList aosTokens(CSLTokenizeString2(pszValue, " ", 0));
    3294           3 :             if ((aosTokens.size() % 3) == 0)
    3295             :             {
    3296           3 :                 for (int i = 0; i < aosTokens.size() / 3; i++)
    3297             :                 {
    3298           3 :                     if (CSLFindString(poDS->papszDimName,
    3299           9 :                                       aosTokens[3 * i + 1]) >= 0 &&
    3300           3 :                         CSLFindString(poDS->papszDimName,
    3301           3 :                                       aosTokens[3 * i + 2]) >= 0)
    3302             :                     {
    3303           3 :                         osTmpGridMapping = aosTokens[3 * i];
    3304           6 :                         if (!osTmpGridMapping.empty() &&
    3305           3 :                             osTmpGridMapping.back() == ':')
    3306             :                         {
    3307           3 :                             osTmpGridMapping.resize(osTmpGridMapping.size() -
    3308             :                                                     1);
    3309             :                         }
    3310           3 :                         pszValue = osTmpGridMapping.c_str();
    3311           3 :                         break;
    3312             :                     }
    3313             :                 }
    3314             :             }
    3315             :         }
    3316             :     }
    3317         495 :     char *pszGridMappingValue = CPLStrdup(pszValue ? pszValue : "");
    3318             : 
    3319         495 :     if (!EQUAL(pszGridMappingValue, ""))
    3320             :     {
    3321             :         // Read grid_mapping metadata.
    3322         216 :         int nProjGroupID = -1;
    3323         216 :         int nProjVarID = -1;
    3324         216 :         if (NCDFResolveVar(nGroupId, pszGridMappingValue, &nProjGroupID,
    3325         216 :                            &nProjVarID) == CE_None)
    3326             :         {
    3327         215 :             poDS->ReadAttributes(nProjGroupID, nProjVarID);
    3328             : 
    3329             :             // Look for GDAL spatial_ref and GeoTransform within grid_mapping.
    3330         215 :             CPLFree(pszGridMappingValue);
    3331         215 :             pszGridMappingValue = nullptr;
    3332         215 :             NCDFGetVarFullName(nProjGroupID, nProjVarID, &pszGridMappingValue);
    3333         215 :             if (pszGridMappingValue)
    3334             :             {
    3335         215 :                 CPLDebug("GDAL_netCDF", "got grid_mapping %s",
    3336             :                          pszGridMappingValue);
    3337         215 :                 pszWKT = FetchAttr(pszGridMappingValue, NCDF_SPATIAL_REF);
    3338         215 :                 if (!pszWKT)
    3339             :                 {
    3340          34 :                     pszWKT = FetchAttr(pszGridMappingValue, NCDF_CRS_WKT);
    3341             :                 }
    3342             :                 else
    3343             :                 {
    3344         181 :                     bGotGdalSRS = true;
    3345         181 :                     CPLDebug("GDAL_netCDF", "setting WKT from GDAL");
    3346             :                 }
    3347         215 :                 if (pszWKT)
    3348             :                 {
    3349         185 :                     if (!bGotGdalSRS)
    3350             :                     {
    3351           4 :                         bGotCfWktSRS = true;
    3352           4 :                         CPLDebug("GDAL_netCDF", "setting WKT from CF");
    3353             :                     }
    3354         185 :                     if (returnProjStr != nullptr)
    3355             :                     {
    3356          46 :                         (*returnProjStr) = std::string(pszWKT);
    3357             :                     }
    3358             :                     else
    3359             :                     {
    3360         139 :                         m_bAddedProjectionVarsDefs = true;
    3361         139 :                         m_bAddedProjectionVarsData = true;
    3362         278 :                         OGRSpatialReference oSRSTmp;
    3363         139 :                         oSRSTmp.SetAxisMappingStrategy(
    3364             :                             OAMS_TRADITIONAL_GIS_ORDER);
    3365         139 :                         oSRSTmp.importFromWkt(pszWKT);
    3366         139 :                         SetSpatialRefNoUpdate(&oSRSTmp);
    3367             :                     }
    3368             :                     pszGeoTransform =
    3369         185 :                         FetchAttr(pszGridMappingValue, NCDF_GEOTRANSFORM);
    3370             :                 }
    3371             :             }
    3372             :             else
    3373             :             {
    3374           0 :                 pszGridMappingValue = CPLStrdup("");
    3375             :             }
    3376             :         }
    3377             :     }
    3378             : 
    3379             :     // Get information about the file.
    3380             :     //
    3381             :     // Was this file created by the GDAL netcdf driver?
    3382             :     // Was this file created by the newer (CF-conformant) driver?
    3383             :     //
    3384             :     // 1) If GDAL netcdf metadata is set, and version >= 1.9,
    3385             :     //    it was created with the new driver
    3386             :     // 2) Else, if spatial_ref and GeoTransform are present in the
    3387             :     //    grid_mapping variable, it was created by the old driver
    3388         495 :     pszValue = FetchAttr("NC_GLOBAL", "GDAL");
    3389             : 
    3390         495 :     if (pszValue && NCDFIsGDALVersionGTE(pszValue, 1900))
    3391             :     {
    3392         238 :         bIsGdalFile = true;
    3393         238 :         bIsGdalCfFile = true;
    3394             :     }
    3395         257 :     else if (pszWKT != nullptr && pszGeoTransform != nullptr)
    3396             :     {
    3397          14 :         bIsGdalFile = true;
    3398          14 :         bIsGdalCfFile = false;
    3399             :     }
    3400             : 
    3401             :     // Set default bottom-up default value.
    3402             :     // Y axis dimension and absence of GT can modify this value.
    3403             :     // Override with Config option GDAL_NETCDF_BOTTOMUP.
    3404             : 
    3405             :     // New driver is bottom-up by default.
    3406         495 :     if ((bIsGdalFile && !bIsGdalCfFile) || bSwitchedXY)
    3407          16 :         poDS->bBottomUp = false;
    3408             :     else
    3409         479 :         poDS->bBottomUp = true;
    3410             : 
    3411         495 :     CPLDebug("GDAL_netCDF",
    3412             :              "bIsGdalFile=%d bIsGdalCfFile=%d bSwitchedXY=%d bBottomUp=%d",
    3413         495 :              static_cast<int>(bIsGdalFile), static_cast<int>(bIsGdalCfFile),
    3414         495 :              static_cast<int>(bSwitchedXY), static_cast<int>(bBottomUp));
    3415             : 
    3416             :     // Read projection coordinates.
    3417             : 
    3418         495 :     int nGroupDimXID = -1;
    3419         495 :     int nVarDimXID = -1;
    3420         495 :     int nGroupDimYID = -1;
    3421         495 :     int nVarDimYID = -1;
    3422         495 :     if (sg != nullptr)
    3423             :     {
    3424          48 :         nGroupDimXID = sg->get_ncID();
    3425          48 :         nGroupDimYID = sg->get_ncID();
    3426          48 :         nVarDimXID = sg->getNodeCoordVars()[0];
    3427          48 :         nVarDimYID = sg->getNodeCoordVars()[1];
    3428             :     }
    3429             : 
    3430         495 :     if (!bReadSRSOnly)
    3431             :     {
    3432         338 :         NCDFResolveVar(nGroupId, poDS->papszDimName[nXDimID], &nGroupDimXID,
    3433             :                        &nVarDimXID);
    3434         338 :         NCDFResolveVar(nGroupId, poDS->papszDimName[nYDimID], &nGroupDimYID,
    3435             :                        &nVarDimYID);
    3436             :         // TODO: if above resolving fails we should also search for coordinate
    3437             :         // variables without same name than dimension using the same resolving
    3438             :         // logic. This should handle for example NASA Ocean Color L2 products.
    3439             : 
    3440             :         const bool bIgnoreXYAxisNameChecks =
    3441         676 :             CPLTestBool(CSLFetchNameValueDef(
    3442         338 :                 papszOpenOptions, "IGNORE_XY_AXIS_NAME_CHECKS",
    3443             :                 CPLGetConfigOption("GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS",
    3444         338 :                                    "NO"))) ||
    3445             :             // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a res
    3446             :             // and transform attributes
    3447         338 :             (FetchAttr(nGroupId, nVarId, "res") != nullptr &&
    3448         676 :              FetchAttr(nGroupId, nVarId, "transform") != nullptr) ||
    3449         337 :             FetchAttr(nGroupId, NC_GLOBAL, "GMT_version") != nullptr;
    3450             : 
    3451             :         // Check that they are 1D or 2D variables
    3452         338 :         if (nVarDimXID >= 0)
    3453             :         {
    3454         243 :             int ndims = -1;
    3455         243 :             nc_inq_varndims(nGroupId, nVarDimXID, &ndims);
    3456         243 :             if (ndims == 0 || ndims > 2)
    3457           0 :                 nVarDimXID = -1;
    3458         243 :             else if (!bIgnoreXYAxisNameChecks)
    3459             :             {
    3460         241 :                 if (!NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
    3461         159 :                     !NCDFIsVarProjectionX(nGroupId, nVarDimXID, nullptr) &&
    3462             :                     // In case of inversion of X/Y
    3463         431 :                     !NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr) &&
    3464          31 :                     !NCDFIsVarProjectionY(nGroupId, nVarDimXID, nullptr))
    3465             :                 {
    3466             :                     char szVarNameX[NC_MAX_NAME + 1];
    3467          31 :                     CPL_IGNORE_RET_VAL(
    3468          31 :                         nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
    3469          31 :                     if (!(ndims == 1 &&
    3470          30 :                           (EQUAL(szVarNameX, CF_LONGITUDE_STD_NAME) ||
    3471          29 :                            EQUAL(szVarNameX, CF_LONGITUDE_VAR_NAME))))
    3472             :                     {
    3473          30 :                         CPLDebug(
    3474             :                             "netCDF",
    3475             :                             "Georeferencing ignored due to non-specific "
    3476             :                             "enough X axis name. "
    3477             :                             "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
    3478             :                             "as configuration option to bypass this check");
    3479          30 :                         nVarDimXID = -1;
    3480             :                     }
    3481             :                 }
    3482             :             }
    3483             :         }
    3484             : 
    3485         338 :         if (nVarDimYID >= 0)
    3486             :         {
    3487         245 :             int ndims = -1;
    3488         245 :             nc_inq_varndims(nGroupId, nVarDimYID, &ndims);
    3489         245 :             if (ndims == 0 || ndims > 2)
    3490           1 :                 nVarDimYID = -1;
    3491         244 :             else if (!bIgnoreXYAxisNameChecks)
    3492             :             {
    3493         242 :                 if (!NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr) &&
    3494         160 :                     !NCDFIsVarProjectionY(nGroupId, nVarDimYID, nullptr) &&
    3495             :                     // In case of inversion of X/Y
    3496         434 :                     !NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
    3497          32 :                     !NCDFIsVarProjectionX(nGroupId, nVarDimYID, nullptr))
    3498             :                 {
    3499             :                     char szVarNameY[NC_MAX_NAME + 1];
    3500          32 :                     CPL_IGNORE_RET_VAL(
    3501          32 :                         nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
    3502          32 :                     if (!(ndims == 1 &&
    3503          32 :                           (EQUAL(szVarNameY, CF_LATITUDE_STD_NAME) ||
    3504          31 :                            EQUAL(szVarNameY, CF_LATITUDE_VAR_NAME))))
    3505             :                     {
    3506          31 :                         CPLDebug(
    3507             :                             "netCDF",
    3508             :                             "Georeferencing ignored due to non-specific "
    3509             :                             "enough Y axis name. "
    3510             :                             "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
    3511             :                             "as configuration option to bypass this check");
    3512          31 :                         nVarDimYID = -1;
    3513             :                     }
    3514             :                 }
    3515             :             }
    3516             :         }
    3517             : 
    3518         338 :         if ((nVarDimXID >= 0 && xdim == 1) || (nVarDimXID >= 0 && ydim == 1))
    3519             :         {
    3520           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    3521             :                      "1-pixel width/height files not supported, "
    3522             :                      "xdim: %ld ydim: %ld",
    3523             :                      static_cast<long>(xdim), static_cast<long>(ydim));
    3524           0 :             nVarDimXID = -1;
    3525           0 :             nVarDimYID = -1;
    3526             :         }
    3527             :     }
    3528             : 
    3529         495 :     const char *pszUnits = nullptr;
    3530         495 :     if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
    3531             :     {
    3532         261 :         const char *pszUnitsX = FetchAttr(nGroupDimXID, nVarDimXID, "units");
    3533         261 :         const char *pszUnitsY = FetchAttr(nGroupDimYID, nVarDimYID, "units");
    3534             : 
    3535         261 :         if (pszUnitsX && pszUnitsY)
    3536             :         {
    3537         209 :             if (EQUAL(pszUnitsX, pszUnitsY))
    3538         135 :                 pszUnits = pszUnitsX;
    3539          74 :             else if (!pszWKT && !EQUAL(pszGridMappingValue, ""))
    3540             :             {
    3541           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    3542             :                          "X axis unit (%s) is different from Y axis "
    3543             :                          "unit (%s). SRS will ignore axis unit and be "
    3544             :                          "likely wrong.",
    3545             :                          pszUnitsX, pszUnitsY);
    3546             :             }
    3547             :         }
    3548          52 :         else if (pszUnitsX && !pszWKT && !EQUAL(pszGridMappingValue, ""))
    3549             :         {
    3550           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    3551             :                      "X axis unit is defined, but not Y one ."
    3552             :                      "SRS will ignore axis unit and be likely wrong.");
    3553             :         }
    3554          52 :         else if (pszUnitsY && !pszWKT && !EQUAL(pszGridMappingValue, ""))
    3555             :         {
    3556           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    3557             :                      "Y axis unit is defined, but not X one ."
    3558             :                      "SRS will ignore axis unit and be likely wrong.");
    3559             :         }
    3560             :     }
    3561             : 
    3562         495 :     if (!pszWKT && !EQUAL(pszGridMappingValue, ""))
    3563             :     {
    3564          31 :         CPLStringList aosGridMappingKeyValues;
    3565          31 :         const size_t nLenGridMappingValue = strlen(pszGridMappingValue);
    3566         789 :         for (const char *const *papszIter = papszMetadata;
    3567         789 :              papszIter && *papszIter; ++papszIter)
    3568             :         {
    3569         758 :             if (STARTS_WITH(*papszIter, pszGridMappingValue) &&
    3570         236 :                 (*papszIter)[nLenGridMappingValue] == '#')
    3571             :             {
    3572         236 :                 char *pszKey = nullptr;
    3573         472 :                 pszValue = CPLParseNameValue(
    3574         236 :                     *papszIter + nLenGridMappingValue + 1, &pszKey);
    3575         236 :                 if (pszKey && pszValue)
    3576         236 :                     aosGridMappingKeyValues.SetNameValue(pszKey, pszValue);
    3577         236 :                 CPLFree(pszKey);
    3578             :             }
    3579             :         }
    3580             : 
    3581          31 :         bGotGeogCS = aosGridMappingKeyValues.FetchNameValue(
    3582             :                          CF_PP_SEMI_MAJOR_AXIS) != nullptr;
    3583             : 
    3584          31 :         oSRS.importFromCF1(aosGridMappingKeyValues.List(), pszUnits);
    3585          31 :         bGotCfSRS = oSRS.IsGeographic() || oSRS.IsProjected();
    3586             :     }
    3587             :     else
    3588             :     {
    3589             :         // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a "crs"
    3590             :         // attribute hold on the variable of interest that contains a PROJ.4
    3591             :         // string
    3592         464 :         pszValue = FetchAttr(nGroupId, nVarId, "crs");
    3593         465 :         if (pszValue &&
    3594           1 :             (strstr(pszValue, "+proj=") != nullptr ||
    3595           0 :              strstr(pszValue, "GEOGCS") != nullptr ||
    3596           0 :              strstr(pszValue, "PROJCS") != nullptr ||
    3597         465 :              strstr(pszValue, "EPSG:") != nullptr) &&
    3598           1 :             oSRS.SetFromUserInput(pszValue) == OGRERR_NONE)
    3599             :         {
    3600           1 :             bGotCfSRS = true;
    3601             :         }
    3602             :     }
    3603             : 
    3604             :     // Set Projection from CF.
    3605         495 :     double dfLinearUnitsConvFactor = 1.0;
    3606         495 :     if ((bGotGeogCS || bGotCfSRS))
    3607             :     {
    3608          31 :         if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
    3609             :         {
    3610             :             // Set SRS Units.
    3611             : 
    3612             :             // Check units for x and y.
    3613          28 :             if (oSRS.IsProjected())
    3614             :             {
    3615          25 :                 dfLinearUnitsConvFactor = oSRS.GetLinearUnits(nullptr);
    3616             : 
    3617             :                 // If the user doesn't ask to preserve the axis unit,
    3618             :                 // then normalize to metre
    3619          31 :                 if (dfLinearUnitsConvFactor != 1.0 &&
    3620           6 :                     !CPLFetchBool(GetOpenOptions(), "PRESERVE_AXIS_UNIT_IN_CRS",
    3621             :                                   false))
    3622             :                 {
    3623           5 :                     oSRS.SetLinearUnits("metre", 1.0);
    3624           5 :                     oSRS.SetAuthority("PROJCS|UNIT", "EPSG", 9001);
    3625             :                 }
    3626             :                 else
    3627             :                 {
    3628          20 :                     dfLinearUnitsConvFactor = 1.0;
    3629             :                 }
    3630             :             }
    3631             :         }
    3632             : 
    3633             :         // Set projection.
    3634          31 :         char *pszTempProjection = nullptr;
    3635          31 :         oSRS.exportToWkt(&pszTempProjection);
    3636          31 :         if (pszTempProjection)
    3637             :         {
    3638          31 :             CPLDebug("GDAL_netCDF", "setting WKT from CF");
    3639          31 :             if (returnProjStr != nullptr)
    3640             :             {
    3641           2 :                 (*returnProjStr) = std::string(pszTempProjection);
    3642             :             }
    3643             :             else
    3644             :             {
    3645          29 :                 m_bAddedProjectionVarsDefs = true;
    3646          29 :                 m_bAddedProjectionVarsData = true;
    3647          29 :                 SetSpatialRefNoUpdate(&oSRS);
    3648             :             }
    3649             :         }
    3650          31 :         CPLFree(pszTempProjection);
    3651             :     }
    3652             : 
    3653         495 :     if (!bReadSRSOnly && (nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 &&
    3654             :         ydim > 0)
    3655             :     {
    3656             :         double *pdfXCoord =
    3657         213 :             static_cast<double *>(CPLCalloc(xdim, sizeof(double)));
    3658             :         double *pdfYCoord =
    3659         213 :             static_cast<double *>(CPLCalloc(ydim, sizeof(double)));
    3660             : 
    3661         213 :         size_t start[2] = {0, 0};
    3662         213 :         size_t edge[2] = {xdim, 0};
    3663         213 :         int status = nc_get_vara_double(nGroupDimXID, nVarDimXID, start, edge,
    3664             :                                         pdfXCoord);
    3665         213 :         NCDF_ERR(status);
    3666             : 
    3667         213 :         edge[0] = ydim;
    3668         213 :         status = nc_get_vara_double(nGroupDimYID, nVarDimYID, start, edge,
    3669             :                                     pdfYCoord);
    3670         213 :         NCDF_ERR(status);
    3671             : 
    3672         213 :         nc_type nc_var_dimx_datatype = NC_NAT;
    3673             :         status =
    3674         213 :             nc_inq_vartype(nGroupDimXID, nVarDimXID, &nc_var_dimx_datatype);
    3675         213 :         NCDF_ERR(status);
    3676             : 
    3677         213 :         nc_type nc_var_dimy_datatype = NC_NAT;
    3678             :         status =
    3679         213 :             nc_inq_vartype(nGroupDimYID, nVarDimYID, &nc_var_dimy_datatype);
    3680         213 :         NCDF_ERR(status);
    3681             : 
    3682         213 :         if (!poDS->bSwitchedXY)
    3683             :         {
    3684             :             // Convert ]180,540] longitude values to ]-180,0].
    3685         293 :             if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
    3686          82 :                 CPLTestBool(
    3687             :                     CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")))
    3688             :             {
    3689             :                 // If minimum longitude is > 180, subtract 360 from all.
    3690             :                 // Add a check on the maximum X value too, since
    3691             :                 // NCDFIsVarLongitude() is not very specific by default (see
    3692             :                 // https://github.com/OSGeo/gdal/issues/1440)
    3693          89 :                 if (std::min(pdfXCoord[0], pdfXCoord[xdim - 1]) > 180.0 &&
    3694           7 :                     std::max(pdfXCoord[0], pdfXCoord[xdim - 1]) <= 540)
    3695             :                 {
    3696           0 :                     CPLDebug(
    3697             :                         "GDAL_netCDF",
    3698             :                         "Offsetting longitudes from ]180,540] to ]-180,180]. "
    3699             :                         "Can be disabled with GDAL_NETCDF_CENTERLONG_180=NO");
    3700           0 :                     for (size_t i = 0; i < xdim; i++)
    3701           0 :                         pdfXCoord[i] -= 360;
    3702             :                 }
    3703             :             }
    3704             :         }
    3705             : 
    3706             :         // Is pixel spacing uniform across the map?
    3707             : 
    3708             :         // Check Longitude.
    3709             : 
    3710         213 :         bool bLonSpacingOK = false;
    3711         213 :         if (xdim == 2)
    3712             :         {
    3713          31 :             bLonSpacingOK = true;
    3714             :         }
    3715             :         else
    3716             :         {
    3717         182 :             bool bWestIsLeft = (pdfXCoord[0] < pdfXCoord[xdim - 1]);
    3718             : 
    3719             :             // fix longitudes if longitudes should increase from
    3720             :             // west to east, but west > east
    3721         254 :             if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
    3722          72 :                 !bWestIsLeft)
    3723             :             {
    3724           2 :                 size_t ndecreases = 0;
    3725             : 
    3726             :                 // there is lon wrap if longitudes increase
    3727             :                 // with one single decrease
    3728         107 :                 for (size_t i = 1; i < xdim; i++)
    3729             :                 {
    3730         105 :                     if (pdfXCoord[i] < pdfXCoord[i - 1])
    3731           1 :                         ndecreases++;
    3732             :                 }
    3733             : 
    3734           2 :                 if (ndecreases == 1)
    3735             :                 {
    3736           1 :                     CPLDebug("GDAL_netCDF", "longitude wrap detected");
    3737           4 :                     for (size_t i = 0; i < xdim; i++)
    3738             :                     {
    3739           3 :                         if (pdfXCoord[i] > pdfXCoord[xdim - 1])
    3740           1 :                             pdfXCoord[i] -= 360;
    3741             :                     }
    3742             :                 }
    3743             :             }
    3744             : 
    3745         182 :             const double dfSpacingBegin = pdfXCoord[1] - pdfXCoord[0];
    3746         182 :             const double dfSpacingMiddle =
    3747         182 :                 pdfXCoord[xdim / 2 + 1] - pdfXCoord[xdim / 2];
    3748         182 :             const double dfSpacingLast =
    3749         182 :                 pdfXCoord[xdim - 1] - pdfXCoord[xdim - 2];
    3750             : 
    3751         182 :             CPLDebug("GDAL_netCDF",
    3752             :                      "xdim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
    3753             :                      "dfSpacingLast: %f",
    3754             :                      static_cast<long>(xdim), dfSpacingBegin, dfSpacingMiddle,
    3755             :                      dfSpacingLast);
    3756             : #ifdef NCDF_DEBUG
    3757             :             CPLDebug("GDAL_netCDF", "xcoords: %f %f %f %f %f %f", pdfXCoord[0],
    3758             :                      pdfXCoord[1], pdfXCoord[xdim / 2],
    3759             :                      pdfXCoord[(xdim / 2) + 1], pdfXCoord[xdim - 2],
    3760             :                      pdfXCoord[xdim - 1]);
    3761             : #endif
    3762             : 
    3763             :             // ftp://ftp.cdc.noaa.gov/Datasets/NARR/Dailies/monolevel/vwnd.10m.2015.nc
    3764             :             // requires a 0.02% tolerance, so let's settle for 0.05%
    3765             : 
    3766             :             // For float variables, increase to 0.2% (as seen in
    3767             :             // https://github.com/OSGeo/gdal/issues/3663)
    3768         182 :             const double dfEpsRel =
    3769         182 :                 nc_var_dimx_datatype == NC_FLOAT ? 0.002 : 0.0005;
    3770             : 
    3771             :             const double dfEps =
    3772             :                 dfEpsRel *
    3773         364 :                 std::max(fabs(dfSpacingBegin),
    3774         182 :                          std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
    3775         362 :             if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
    3776         362 :                 IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
    3777         180 :                 IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
    3778             :             {
    3779         180 :                 bLonSpacingOK = true;
    3780             :             }
    3781           2 :             else if (CPLTestBool(CPLGetConfigOption(
    3782             :                          "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
    3783             :             {
    3784           0 :                 bLonSpacingOK = true;
    3785           0 :                 CPLDebug(
    3786             :                     "GDAL_netCDF",
    3787             :                     "Longitude/X is not equally spaced, but will be considered "
    3788             :                     "as such because of "
    3789             :                     "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
    3790             :             }
    3791             :         }
    3792             : 
    3793         213 :         if (bLonSpacingOK == false)
    3794             :         {
    3795           2 :             CPLDebug(
    3796             :                 "GDAL_netCDF", "%s",
    3797             :                 "Longitude/X is not equally spaced (with a 0.05% tolerance). "
    3798             :                 "You may set the "
    3799             :                 "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
    3800             :                 "option to YES to ignore this check");
    3801             :         }
    3802             : 
    3803             :         // Check Latitude.
    3804         213 :         bool bLatSpacingOK = false;
    3805             : 
    3806         213 :         if (ydim == 2)
    3807             :         {
    3808          51 :             bLatSpacingOK = true;
    3809             :         }
    3810             :         else
    3811             :         {
    3812         162 :             const double dfSpacingBegin = pdfYCoord[1] - pdfYCoord[0];
    3813         162 :             const double dfSpacingMiddle =
    3814         162 :                 pdfYCoord[ydim / 2 + 1] - pdfYCoord[ydim / 2];
    3815             : 
    3816         162 :             const double dfSpacingLast =
    3817         162 :                 pdfYCoord[ydim - 1] - pdfYCoord[ydim - 2];
    3818             : 
    3819         162 :             CPLDebug("GDAL_netCDF",
    3820             :                      "ydim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
    3821             :                      "dfSpacingLast: %f",
    3822             :                      (long)ydim, dfSpacingBegin, dfSpacingMiddle,
    3823             :                      dfSpacingLast);
    3824             : #ifdef NCDF_DEBUG
    3825             :             CPLDebug("GDAL_netCDF", "ycoords: %f %f %f %f %f %f", pdfYCoord[0],
    3826             :                      pdfYCoord[1], pdfYCoord[ydim / 2],
    3827             :                      pdfYCoord[(ydim / 2) + 1], pdfYCoord[ydim - 2],
    3828             :                      pdfYCoord[ydim - 1]);
    3829             : #endif
    3830             : 
    3831         162 :             const double dfEpsRel =
    3832         162 :                 nc_var_dimy_datatype == NC_FLOAT ? 0.002 : 0.0005;
    3833             : 
    3834             :             const double dfEps =
    3835             :                 dfEpsRel *
    3836         324 :                 std::max(fabs(dfSpacingBegin),
    3837         162 :                          std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
    3838         322 :             if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
    3839         322 :                 IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
    3840         151 :                 IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
    3841             :             {
    3842         151 :                 bLatSpacingOK = true;
    3843             :             }
    3844          11 :             else if (CPLTestBool(CPLGetConfigOption(
    3845             :                          "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
    3846             :             {
    3847           0 :                 bLatSpacingOK = true;
    3848           0 :                 CPLDebug(
    3849             :                     "GDAL_netCDF",
    3850             :                     "Latitude/Y is not equally spaced, but will be considered "
    3851             :                     "as such because of "
    3852             :                     "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
    3853             :             }
    3854          11 :             else if (!oSRS.IsProjected() &&
    3855          11 :                      fabs(dfSpacingBegin - dfSpacingLast) <= 0.1 &&
    3856          30 :                      fabs(dfSpacingBegin - dfSpacingMiddle) <= 0.1 &&
    3857           8 :                      fabs(dfSpacingMiddle - dfSpacingLast) <= 0.1)
    3858             :             {
    3859           8 :                 bLatSpacingOK = true;
    3860           8 :                 CPLError(CE_Warning, CPLE_AppDefined,
    3861             :                          "Latitude grid not spaced evenly.  "
    3862             :                          "Setting projection for grid spacing is "
    3863             :                          "within 0.1 degrees threshold.");
    3864             : 
    3865           8 :                 CPLDebug("GDAL_netCDF",
    3866             :                          "Latitude grid not spaced evenly, but within 0.1 "
    3867             :                          "degree threshold (probably a Gaussian grid).  "
    3868             :                          "Saving original latitude values in Y_VALUES "
    3869             :                          "geolocation metadata");
    3870           8 :                 Set1DGeolocation(nGroupDimYID, nVarDimYID, "Y");
    3871             :             }
    3872             : 
    3873         162 :             if (bLatSpacingOK == false)
    3874             :             {
    3875           3 :                 CPLDebug(
    3876             :                     "GDAL_netCDF", "%s",
    3877             :                     "Latitude/Y is not equally spaced (with a 0.05% "
    3878             :                     "tolerance). "
    3879             :                     "You may set the "
    3880             :                     "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
    3881             :                     "option to YES to ignore this check");
    3882             :             }
    3883             :         }
    3884             : 
    3885         213 :         if (bLonSpacingOK && bLatSpacingOK)
    3886             :         {
    3887             :             // We have gridded data so we can set the Georeferencing info.
    3888             : 
    3889             :             // Enable GeoTransform.
    3890             : 
    3891             :             // In the following "actual_range" and "node_offset"
    3892             :             // are attributes used by netCDF files created by GMT.
    3893             :             // If we find them we know how to proceed. Else, use
    3894             :             // the original algorithm.
    3895         210 :             bGotCfGT = true;
    3896             : 
    3897         210 :             int node_offset = 0;
    3898         210 :             NCDFResolveAttInt(nGroupId, NC_GLOBAL, "node_offset", &node_offset);
    3899             : 
    3900         210 :             double adfActualRange[2] = {0.0, 0.0};
    3901         210 :             double xMinMax[2] = {0.0, 0.0};
    3902         210 :             double yMinMax[2] = {0.0, 0.0};
    3903             : 
    3904         210 :             if (!nc_get_att_double(nGroupDimXID, nVarDimXID, "actual_range",
    3905             :                                    adfActualRange))
    3906             :             {
    3907           3 :                 xMinMax[0] = adfActualRange[0];
    3908           3 :                 xMinMax[1] = adfActualRange[1];
    3909             : 
    3910             :                 // Present xMinMax[] in the same order as padfXCoord
    3911           3 :                 if ((xMinMax[0] - xMinMax[1]) *
    3912           3 :                         (pdfXCoord[0] - pdfXCoord[xdim - 1]) <
    3913             :                     0)
    3914             :                 {
    3915           0 :                     std::swap(xMinMax[0], xMinMax[1]);
    3916             :                 }
    3917             :             }
    3918             :             else
    3919             :             {
    3920         207 :                 xMinMax[0] = pdfXCoord[0];
    3921         207 :                 xMinMax[1] = pdfXCoord[xdim - 1];
    3922         207 :                 node_offset = 0;
    3923             :             }
    3924             : 
    3925         210 :             if (!nc_get_att_double(nGroupDimYID, nVarDimYID, "actual_range",
    3926             :                                    adfActualRange))
    3927             :             {
    3928           3 :                 yMinMax[0] = adfActualRange[0];
    3929           3 :                 yMinMax[1] = adfActualRange[1];
    3930             : 
    3931             :                 // Present yMinMax[] in the same order as pdfYCoord
    3932           3 :                 if ((yMinMax[0] - yMinMax[1]) *
    3933           3 :                         (pdfYCoord[0] - pdfYCoord[ydim - 1]) <
    3934             :                     0)
    3935             :                 {
    3936           1 :                     std::swap(yMinMax[0], yMinMax[1]);
    3937             :                 }
    3938             :             }
    3939             :             else
    3940             :             {
    3941         207 :                 yMinMax[0] = pdfYCoord[0];
    3942         207 :                 yMinMax[1] = pdfYCoord[ydim - 1];
    3943         207 :                 node_offset = 0;
    3944             :             }
    3945             : 
    3946         210 :             double dfCoordOffset = 0.0;
    3947         210 :             double dfCoordScale = 1.0;
    3948         210 :             if (!nc_get_att_double(nGroupId, nVarDimXID, CF_ADD_OFFSET,
    3949         214 :                                    &dfCoordOffset) &&
    3950           4 :                 !nc_get_att_double(nGroupId, nVarDimXID, CF_SCALE_FACTOR,
    3951             :                                    &dfCoordScale))
    3952             :             {
    3953           4 :                 xMinMax[0] = dfCoordOffset + xMinMax[0] * dfCoordScale;
    3954           4 :                 xMinMax[1] = dfCoordOffset + xMinMax[1] * dfCoordScale;
    3955             :             }
    3956             : 
    3957         210 :             if (!nc_get_att_double(nGroupId, nVarDimYID, CF_ADD_OFFSET,
    3958         214 :                                    &dfCoordOffset) &&
    3959           4 :                 !nc_get_att_double(nGroupId, nVarDimYID, CF_SCALE_FACTOR,
    3960             :                                    &dfCoordScale))
    3961             :             {
    3962           4 :                 yMinMax[0] = dfCoordOffset + yMinMax[0] * dfCoordScale;
    3963           4 :                 yMinMax[1] = dfCoordOffset + yMinMax[1] * dfCoordScale;
    3964             :             }
    3965             : 
    3966             :             // Check for reverse order of y-coordinate.
    3967         210 :             if (!bSwitchedXY)
    3968             :             {
    3969         208 :                 poDS->bBottomUp = (yMinMax[0] <= yMinMax[1]);
    3970         208 :                 CPLDebug("GDAL_netCDF", "set bBottomUp = %d from Y axis",
    3971         208 :                          static_cast<int>(poDS->bBottomUp));
    3972         208 :                 if (!poDS->bBottomUp)
    3973             :                 {
    3974          32 :                     std::swap(yMinMax[0], yMinMax[1]);
    3975             :                 }
    3976             :             }
    3977             : 
    3978             :             // Geostationary satellites can specify units in (micro)radians
    3979             :             // So we check if they do, and if so convert to linear units
    3980             :             // (meters)
    3981         210 :             const char *pszProjName = oSRS.GetAttrValue("PROJECTION");
    3982         210 :             if (pszProjName != nullptr)
    3983             :             {
    3984          24 :                 if (EQUAL(pszProjName, SRS_PT_GEOSTATIONARY_SATELLITE))
    3985             :                 {
    3986             :                     double satelliteHeight =
    3987           3 :                         oSRS.GetProjParm(SRS_PP_SATELLITE_HEIGHT, 1.0);
    3988           3 :                     size_t nAttlen = 0;
    3989             :                     char szUnits[NC_MAX_NAME + 1];
    3990           3 :                     szUnits[0] = '\0';
    3991           3 :                     nc_type nAttype = NC_NAT;
    3992           3 :                     nc_inq_att(nGroupId, nVarDimXID, "units", &nAttype,
    3993             :                                &nAttlen);
    3994           6 :                     if (nAttlen < sizeof(szUnits) &&
    3995           3 :                         nc_get_att_text(nGroupId, nVarDimXID, "units",
    3996             :                                         szUnits) == NC_NOERR)
    3997             :                     {
    3998           3 :                         szUnits[nAttlen] = '\0';
    3999           3 :                         if (EQUAL(szUnits, "microradian"))
    4000             :                         {
    4001           1 :                             xMinMax[0] =
    4002           1 :                                 xMinMax[0] * satelliteHeight * 0.000001;
    4003           1 :                             xMinMax[1] =
    4004           1 :                                 xMinMax[1] * satelliteHeight * 0.000001;
    4005             :                         }
    4006           2 :                         else if (EQUAL(szUnits, "rad") ||
    4007           1 :                                  EQUAL(szUnits, "radian"))
    4008             :                         {
    4009           2 :                             xMinMax[0] = xMinMax[0] * satelliteHeight;
    4010           2 :                             xMinMax[1] = xMinMax[1] * satelliteHeight;
    4011             :                         }
    4012             :                     }
    4013           3 :                     szUnits[0] = '\0';
    4014           3 :                     nc_inq_att(nGroupId, nVarDimYID, "units", &nAttype,
    4015             :                                &nAttlen);
    4016           6 :                     if (nAttlen < sizeof(szUnits) &&
    4017           3 :                         nc_get_att_text(nGroupId, nVarDimYID, "units",
    4018             :                                         szUnits) == NC_NOERR)
    4019             :                     {
    4020           3 :                         szUnits[nAttlen] = '\0';
    4021           3 :                         if (EQUAL(szUnits, "microradian"))
    4022             :                         {
    4023           1 :                             yMinMax[0] =
    4024           1 :                                 yMinMax[0] * satelliteHeight * 0.000001;
    4025           1 :                             yMinMax[1] =
    4026           1 :                                 yMinMax[1] * satelliteHeight * 0.000001;
    4027             :                         }
    4028           2 :                         else if (EQUAL(szUnits, "rad") ||
    4029           1 :                                  EQUAL(szUnits, "radian"))
    4030             :                         {
    4031           2 :                             yMinMax[0] = yMinMax[0] * satelliteHeight;
    4032           2 :                             yMinMax[1] = yMinMax[1] * satelliteHeight;
    4033             :                         }
    4034             :                     }
    4035             :                 }
    4036             :             }
    4037             : 
    4038         210 :             adfTempGeoTransform[0] = xMinMax[0];
    4039         210 :             adfTempGeoTransform[1] = (xMinMax[1] - xMinMax[0]) /
    4040         210 :                                      (poDS->nRasterXSize + (node_offset - 1));
    4041         210 :             adfTempGeoTransform[2] = 0;
    4042         210 :             if (bSwitchedXY)
    4043             :             {
    4044           2 :                 adfTempGeoTransform[3] = yMinMax[0];
    4045           2 :                 adfTempGeoTransform[4] = 0;
    4046           2 :                 adfTempGeoTransform[5] =
    4047           2 :                     (yMinMax[1] - yMinMax[0]) /
    4048           2 :                     (poDS->nRasterYSize + (node_offset - 1));
    4049             :             }
    4050             :             else
    4051             :             {
    4052         208 :                 adfTempGeoTransform[3] = yMinMax[1];
    4053         208 :                 adfTempGeoTransform[4] = 0;
    4054         208 :                 adfTempGeoTransform[5] =
    4055         208 :                     (yMinMax[0] - yMinMax[1]) /
    4056         208 :                     (poDS->nRasterYSize + (node_offset - 1));
    4057             :             }
    4058             : 
    4059             :             // Compute the center of the pixel.
    4060         210 :             if (!node_offset)
    4061             :             {
    4062             :                 // Otherwise its already the pixel center.
    4063         210 :                 adfTempGeoTransform[0] -= (adfTempGeoTransform[1] / 2);
    4064         210 :                 adfTempGeoTransform[3] -= (adfTempGeoTransform[5] / 2);
    4065             :             }
    4066             :         }
    4067             : 
    4068             :         const auto AreSRSEqualThroughProj4String =
    4069           2 :             [](const OGRSpatialReference &oSRS1,
    4070             :                const OGRSpatialReference &oSRS2)
    4071             :         {
    4072           2 :             char *pszProj4Str1 = nullptr;
    4073           2 :             oSRS1.exportToProj4(&pszProj4Str1);
    4074             : 
    4075           2 :             char *pszProj4Str2 = nullptr;
    4076           2 :             oSRS2.exportToProj4(&pszProj4Str2);
    4077             : 
    4078             :             {
    4079           2 :                 char *pszTmp = strstr(pszProj4Str1, "+datum=");
    4080           2 :                 if (pszTmp)
    4081           0 :                     memcpy(pszTmp, "+ellps=", strlen("+ellps="));
    4082             :             }
    4083             : 
    4084             :             {
    4085           2 :                 char *pszTmp = strstr(pszProj4Str2, "+datum=");
    4086           2 :                 if (pszTmp)
    4087           2 :                     memcpy(pszTmp, "+ellps=", strlen("+ellps="));
    4088             :             }
    4089             : 
    4090           2 :             bool bRet = false;
    4091           2 :             if (pszProj4Str1 && pszProj4Str2 &&
    4092           2 :                 EQUAL(pszProj4Str1, pszProj4Str2))
    4093             :             {
    4094           1 :                 bRet = true;
    4095             :             }
    4096             : 
    4097           2 :             CPLFree(pszProj4Str1);
    4098           2 :             CPLFree(pszProj4Str2);
    4099           2 :             return bRet;
    4100             :         };
    4101             : 
    4102         213 :         if (dfLinearUnitsConvFactor != 1.0)
    4103             :         {
    4104          35 :             for (int i = 0; i < 6; ++i)
    4105          30 :                 adfTempGeoTransform[i] *= dfLinearUnitsConvFactor;
    4106             : 
    4107           5 :             if (paosRemovedMDItems)
    4108             :             {
    4109             :                 char szVarNameX[NC_MAX_NAME + 1];
    4110           5 :                 CPL_IGNORE_RET_VAL(
    4111           5 :                     nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
    4112             : 
    4113             :                 char szVarNameY[NC_MAX_NAME + 1];
    4114           5 :                 CPL_IGNORE_RET_VAL(
    4115           5 :                     nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
    4116             : 
    4117           5 :                 paosRemovedMDItems->push_back(
    4118             :                     CPLSPrintf("%s#units", szVarNameX));
    4119           5 :                 paosRemovedMDItems->push_back(
    4120             :                     CPLSPrintf("%s#units", szVarNameY));
    4121             :             }
    4122             :         }
    4123             : 
    4124             :         // If there is a global "geospatial_bounds_crs" attribute, check that it
    4125             :         // is consistent with the SRS, and if so, use it as the SRS
    4126             :         const char *pszGBCRS =
    4127         213 :             FetchAttr(nGroupId, NC_GLOBAL, "geospatial_bounds_crs");
    4128         213 :         if (pszGBCRS && STARTS_WITH(pszGBCRS, "EPSG:"))
    4129             :         {
    4130           4 :             OGRSpatialReference oSRSFromGBCRS;
    4131           2 :             oSRSFromGBCRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    4132           2 :             if (oSRSFromGBCRS.SetFromUserInput(
    4133             :                     pszGBCRS,
    4134             :                     OGRSpatialReference::
    4135           4 :                         SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE &&
    4136           2 :                 AreSRSEqualThroughProj4String(oSRS, oSRSFromGBCRS))
    4137             :             {
    4138           1 :                 oSRS = std::move(oSRSFromGBCRS);
    4139           1 :                 SetSpatialRefNoUpdate(&oSRS);
    4140             :             }
    4141             :         }
    4142             : 
    4143         213 :         CPLFree(pdfXCoord);
    4144         213 :         CPLFree(pdfYCoord);
    4145             :     }  // end if(has dims)
    4146             : 
    4147             :     // Process custom GeoTransform GDAL value.
    4148         495 :     if (!EQUAL(pszGridMappingValue, "") && !bGotCfGT)
    4149             :     {
    4150             :         // TODO: Read the GT values and detect for conflict with CF.
    4151             :         // This could resolve the GT precision loss issue.
    4152             : 
    4153          96 :         if (pszGeoTransform != nullptr)
    4154             :         {
    4155             :             char **papszGeoTransform =
    4156          13 :                 CSLTokenizeString2(pszGeoTransform, " ", CSLT_HONOURSTRINGS);
    4157          13 :             if (CSLCount(papszGeoTransform) == 6)
    4158             :             {
    4159          13 :                 bGotGdalGT = true;
    4160          91 :                 for (int i = 0; i < 6; i++)
    4161          78 :                     adfTempGeoTransform[i] = CPLAtof(papszGeoTransform[i]);
    4162             :             }
    4163          13 :             CSLDestroy(papszGeoTransform);
    4164             :         }
    4165             :         else
    4166             :         {
    4167             :             // Look for corner array values.
    4168             :             // CPLDebug("GDAL_netCDF",
    4169             :             //           "looking for geotransform corners");
    4170          83 :             bool bGotNN = false;
    4171          83 :             double dfNN = FetchCopyParam(pszGridMappingValue,
    4172             :                                          "Northernmost_Northing", 0, &bGotNN);
    4173             : 
    4174          83 :             bool bGotSN = false;
    4175          83 :             double dfSN = FetchCopyParam(pszGridMappingValue,
    4176             :                                          "Southernmost_Northing", 0, &bGotSN);
    4177             : 
    4178          83 :             bool bGotEE = false;
    4179          83 :             double dfEE = FetchCopyParam(pszGridMappingValue,
    4180             :                                          "Easternmost_Easting", 0, &bGotEE);
    4181             : 
    4182          83 :             bool bGotWE = false;
    4183          83 :             double dfWE = FetchCopyParam(pszGridMappingValue,
    4184             :                                          "Westernmost_Easting", 0, &bGotWE);
    4185             : 
    4186             :             // Only set the GeoTransform if we got all the values.
    4187          83 :             if (bGotNN && bGotSN && bGotEE && bGotWE)
    4188             :             {
    4189           0 :                 bGotGdalGT = true;
    4190             : 
    4191           0 :                 adfTempGeoTransform[0] = dfWE;
    4192           0 :                 adfTempGeoTransform[1] =
    4193           0 :                     (dfEE - dfWE) / (poDS->GetRasterXSize() - 1);
    4194           0 :                 adfTempGeoTransform[2] = 0.0;
    4195           0 :                 adfTempGeoTransform[3] = dfNN;
    4196           0 :                 adfTempGeoTransform[4] = 0.0;
    4197           0 :                 adfTempGeoTransform[5] =
    4198           0 :                     (dfSN - dfNN) / (poDS->GetRasterYSize() - 1);
    4199             :                 // Compute the center of the pixel.
    4200           0 :                 adfTempGeoTransform[0] = dfWE - (adfTempGeoTransform[1] / 2);
    4201           0 :                 adfTempGeoTransform[3] = dfNN - (adfTempGeoTransform[5] / 2);
    4202             :             }
    4203             :         }  // (pszGeoTransform != NULL)
    4204             : 
    4205          96 :         if (bGotGdalSRS && !bGotGdalGT)
    4206          77 :             CPLDebug("GDAL_netCDF", "Got SRS but no geotransform from GDAL!");
    4207             :     }
    4208             : 
    4209         495 :     if (!pszWKT && !bGotCfSRS)
    4210             :     {
    4211             :         // Some netCDF files have a srid attribute (#6613) like
    4212             :         // urn:ogc:def:crs:EPSG::6931
    4213         279 :         const char *pszSRID = FetchAttr(pszGridMappingValue, "srid");
    4214         279 :         if (pszSRID != nullptr)
    4215             :         {
    4216           0 :             oSRS.Clear();
    4217           0 :             if (oSRS.SetFromUserInput(
    4218             :                     pszSRID,
    4219             :                     OGRSpatialReference::
    4220           0 :                         SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE)
    4221             :             {
    4222           0 :                 char *pszWKTExport = nullptr;
    4223           0 :                 CPLDebug("GDAL_netCDF", "Got SRS from %s", pszSRID);
    4224           0 :                 oSRS.exportToWkt(&pszWKTExport);
    4225           0 :                 if (returnProjStr != nullptr)
    4226             :                 {
    4227           0 :                     (*returnProjStr) = std::string(pszWKTExport);
    4228             :                 }
    4229             :                 else
    4230             :                 {
    4231           0 :                     m_bAddedProjectionVarsDefs = true;
    4232           0 :                     m_bAddedProjectionVarsData = true;
    4233           0 :                     SetSpatialRefNoUpdate(&oSRS);
    4234             :                 }
    4235           0 :                 CPLFree(pszWKTExport);
    4236             :             }
    4237             :         }
    4238             :     }
    4239             : 
    4240         495 :     CPLFree(pszGridMappingValue);
    4241             : 
    4242         495 :     if (bReadSRSOnly)
    4243         157 :         return;
    4244             : 
    4245             :     // Determines the SRS to be used by the geolocation array, if any
    4246         676 :     std::string osGeolocWKT = SRS_WKT_WGS84_LAT_LONG;
    4247         338 :     if (!m_oSRS.IsEmpty())
    4248             :     {
    4249         252 :         OGRSpatialReference oGeogCRS;
    4250         126 :         oGeogCRS.CopyGeogCSFrom(&m_oSRS);
    4251         126 :         char *pszWKTTmp = nullptr;
    4252         126 :         const char *const apszOptions[] = {"FORMAT=WKT2_2019", nullptr};
    4253         126 :         if (oGeogCRS.exportToWkt(&pszWKTTmp, apszOptions) == OGRERR_NONE)
    4254             :         {
    4255         126 :             osGeolocWKT = pszWKTTmp;
    4256             :         }
    4257         126 :         CPLFree(pszWKTTmp);
    4258             :     }
    4259             : 
    4260             :     // Process geolocation arrays from CF "coordinates" attribute.
    4261         676 :     std::string osGeolocXName, osGeolocYName;
    4262         338 :     if (ProcessCFGeolocation(nGroupId, nVarId, osGeolocWKT, osGeolocXName,
    4263         338 :                              osGeolocYName))
    4264             :     {
    4265          51 :         bool bCanCancelGT = true;
    4266          51 :         if ((nVarDimXID != -1) && (nVarDimYID != -1))
    4267             :         {
    4268             :             char szVarNameX[NC_MAX_NAME + 1];
    4269          40 :             CPL_IGNORE_RET_VAL(
    4270          40 :                 nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
    4271             :             char szVarNameY[NC_MAX_NAME + 1];
    4272          40 :             CPL_IGNORE_RET_VAL(
    4273          40 :                 nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
    4274          40 :             bCanCancelGT =
    4275          40 :                 !(osGeolocXName == szVarNameX && osGeolocYName == szVarNameY);
    4276             :         }
    4277          88 :         if (bCanCancelGT && !m_oSRS.IsGeographic() && !m_oSRS.IsProjected() &&
    4278          37 :             !bSwitchedXY)
    4279             :         {
    4280          35 :             bGotCfGT = false;
    4281             :         }
    4282             :     }
    4283         117 :     else if (!bGotCfGT && !bReadSRSOnly && (nVarDimXID != -1) &&
    4284         407 :              (nVarDimYID != -1) && xdim > 0 && ydim > 0 &&
    4285           3 :              ((!bSwitchedXY &&
    4286           3 :                NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
    4287           1 :                NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr)) ||
    4288           2 :               (bSwitchedXY &&
    4289           0 :                NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
    4290           0 :                NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr))))
    4291             :     {
    4292             :         // Case of autotest/gdrivers/data/netcdf/GLMELT_4X5.OCN.nc
    4293             :         // which is indexed by lat, lon variables, but lat has irregular
    4294             :         // spacing.
    4295           1 :         const char *pszGeolocXFullName = poDS->papszDimName[poDS->nXDimID];
    4296           1 :         const char *pszGeolocYFullName = poDS->papszDimName[poDS->nYDimID];
    4297           1 :         if (bSwitchedXY)
    4298             :         {
    4299           0 :             std::swap(pszGeolocXFullName, pszGeolocYFullName);
    4300           0 :             GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", "GEOLOCATION");
    4301             :         }
    4302             : 
    4303           1 :         CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
    4304             :                  pszGeolocXFullName, pszGeolocYFullName);
    4305             : 
    4306           1 :         GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
    4307             :                                         "GEOLOCATION");
    4308             : 
    4309           2 :         CPLString osTMP;
    4310           1 :         osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
    4311           1 :                      pszGeolocXFullName);
    4312             : 
    4313           1 :         GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
    4314           1 :         GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
    4315           1 :         osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
    4316           1 :                      pszGeolocYFullName);
    4317             : 
    4318           1 :         GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
    4319           1 :         GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
    4320             : 
    4321           1 :         GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
    4322           1 :         GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
    4323             : 
    4324           1 :         GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
    4325           1 :         GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
    4326             : 
    4327           1 :         GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
    4328             :                                         "PIXEL_CENTER", "GEOLOCATION");
    4329             :     }
    4330             : 
    4331             :     // Set GeoTransform if we got a complete one - after projection has been set
    4332         338 :     if (bGotCfGT || bGotGdalGT)
    4333             :     {
    4334         192 :         m_bAddedProjectionVarsDefs = true;
    4335         192 :         m_bAddedProjectionVarsData = true;
    4336         192 :         SetGeoTransformNoUpdate(adfTempGeoTransform);
    4337             :     }
    4338             : 
    4339             :     // Debugging reports.
    4340         338 :     CPLDebug("GDAL_netCDF",
    4341             :              "bGotGeogCS=%d bGotCfSRS=%d bGotCfGT=%d bGotCfWktSRS=%d "
    4342             :              "bGotGdalSRS=%d bGotGdalGT=%d",
    4343             :              static_cast<int>(bGotGeogCS), static_cast<int>(bGotCfSRS),
    4344             :              static_cast<int>(bGotCfGT), static_cast<int>(bGotCfWktSRS),
    4345             :              static_cast<int>(bGotGdalSRS), static_cast<int>(bGotGdalGT));
    4346             : 
    4347         338 :     if (!bGotCfGT && !bGotGdalGT)
    4348         146 :         CPLDebug("GDAL_netCDF", "did not get geotransform from CF nor GDAL!");
    4349             : 
    4350         338 :     if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfGT && !bGotCfWktSRS)
    4351         146 :         CPLDebug("GDAL_netCDF", "did not get projection from CF nor GDAL!");
    4352             : 
    4353             :     // wish of 6195
    4354             :     // we don't have CS/SRS, but we do have GT, and we live in -180,360 -90,90
    4355         338 :     if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfWktSRS)
    4356             :     {
    4357         212 :         if (bGotCfGT || bGotGdalGT)
    4358             :         {
    4359         132 :             bool bAssumedLongLat = CPLTestBool(CSLFetchNameValueDef(
    4360          66 :                 papszOpenOptions, "ASSUME_LONGLAT",
    4361             :                 CPLGetConfigOption("GDAL_NETCDF_ASSUME_LONGLAT", "NO")));
    4362             : 
    4363           2 :             if (bAssumedLongLat && adfTempGeoTransform[0] >= -180 &&
    4364           2 :                 adfTempGeoTransform[0] < 360 &&
    4365           4 :                 (adfTempGeoTransform[0] +
    4366           2 :                  adfTempGeoTransform[1] * poDS->GetRasterXSize()) <= 360 &&
    4367          70 :                 adfTempGeoTransform[3] <= 90 && adfTempGeoTransform[3] > -90 &&
    4368           4 :                 (adfTempGeoTransform[3] +
    4369           2 :                  adfTempGeoTransform[5] * poDS->GetRasterYSize()) >= -90)
    4370             :             {
    4371             : 
    4372           2 :                 poDS->bIsGeographic = true;
    4373           2 :                 char *pszTempProjection = nullptr;
    4374             :                 // seems odd to use 4326 so OGC:CRS84
    4375           2 :                 oSRS.SetFromUserInput("OGC:CRS84");
    4376           2 :                 oSRS.exportToWkt(&pszTempProjection);
    4377           2 :                 if (returnProjStr != nullptr)
    4378             :                 {
    4379           0 :                     (*returnProjStr) = std::string(pszTempProjection);
    4380             :                 }
    4381             :                 else
    4382             :                 {
    4383           2 :                     m_bAddedProjectionVarsDefs = true;
    4384           2 :                     m_bAddedProjectionVarsData = true;
    4385           2 :                     SetSpatialRefNoUpdate(&oSRS);
    4386             :                 }
    4387           2 :                 CPLFree(pszTempProjection);
    4388             : 
    4389           2 :                 CPLDebug("netCDF",
    4390             :                          "Assumed Longitude Latitude CRS 'OGC:CRS84' because "
    4391             :                          "none otherwise available and geotransform within "
    4392             :                          "suitable bounds. "
    4393             :                          "Set GDAL_NETCDF_ASSUME_LONGLAT=NO as configuration "
    4394             :                          "option or "
    4395             :                          "    ASSUME_LONGLAT=NO as open option to bypass this "
    4396             :                          "assumption.");
    4397             :             }
    4398             :         }
    4399             :     }
    4400             : 
    4401             : // Search for Well-known GeogCS if got only CF WKT
    4402             : // Disabled for now, as a named datum also include control points
    4403             : // (see mailing list and bug#4281
    4404             : // For example, WGS84 vs. GDA94 (EPSG:3577) - AEA in netcdf_cf.py
    4405             : 
    4406             : // Disabled for now, but could be set in a config option.
    4407             : #if 0
    4408             :     bool bLookForWellKnownGCS = false;  // This could be a Config Option.
    4409             : 
    4410             :     if( bLookForWellKnownGCS && bGotCfSRS && !bGotGdalSRS )
    4411             :     {
    4412             :         // ET - Could use a more exhaustive method by scanning all EPSG codes in
    4413             :         // data/gcs.csv as proposed by Even in the gdal-dev mailing list "help
    4414             :         // for comparing two WKT".
    4415             :         // This code could be contributed to a new function.
    4416             :         // OGRSpatialReference * OGRSpatialReference::FindMatchingGeogCS(
    4417             :         //     const OGRSpatialReference *poOther) */
    4418             :         CPLDebug("GDAL_netCDF", "Searching for Well-known GeogCS");
    4419             :         const char *pszWKGCSList[] = { "WGS84", "WGS72", "NAD27", "NAD83" };
    4420             :         char *pszWKGCS = NULL;
    4421             :         oSRS.exportToPrettyWkt(&pszWKGCS);
    4422             :         for( size_t i = 0; i < sizeof(pszWKGCSList) / 8; i++ )
    4423             :         {
    4424             :             pszWKGCS = CPLStrdup(pszWKGCSList[i]);
    4425             :             OGRSpatialReference oSRSTmp;
    4426             :             oSRSTmp.SetWellKnownGeogCS(pszWKGCSList[i]);
    4427             :             // Set datum to unknown, bug #4281.
    4428             :             if( oSRSTmp.GetAttrNode("DATUM" ) )
    4429             :                 oSRSTmp.GetAttrNode("DATUM")->GetChild(0)->SetValue("unknown");
    4430             :             // Could use OGRSpatialReference::StripCTParms(), but let's keep
    4431             :             // TOWGS84.
    4432             :             oSRSTmp.GetRoot()->StripNodes("AXIS");
    4433             :             oSRSTmp.GetRoot()->StripNodes("AUTHORITY");
    4434             :             oSRSTmp.GetRoot()->StripNodes("EXTENSION");
    4435             : 
    4436             :             oSRSTmp.exportToPrettyWkt(&pszWKGCS);
    4437             :             if( oSRS.IsSameGeogCS(&oSRSTmp) )
    4438             :             {
    4439             :                 oSRS.SetWellKnownGeogCS(pszWKGCSList[i]);
    4440             :                 oSRS.exportToWkt(&(pszTempProjection));
    4441             :                 SetProjection(pszTempProjection);
    4442             :                 CPLFree(pszTempProjection);
    4443             :             }
    4444             :         }
    4445             :     }
    4446             : #endif
    4447             : }
    4448             : 
    4449         109 : void netCDFDataset::SetProjectionFromVar(int nGroupId, int nVarId,
    4450             :                                          bool bReadSRSOnly)
    4451             : {
    4452         109 :     SetProjectionFromVar(nGroupId, nVarId, bReadSRSOnly, nullptr, nullptr,
    4453             :                          nullptr, nullptr);
    4454         109 : }
    4455             : 
    4456         296 : bool netCDFDataset::ProcessNASAL2OceanGeoLocation(int nGroupId, int nVarId)
    4457             : {
    4458             :     // Cf https://oceancolor.gsfc.nasa.gov/docs/format/l2nc/
    4459             :     // and https://github.com/OSGeo/gdal/issues/7605
    4460             : 
    4461             :     // Check for a structure like:
    4462             :     /* netcdf SNPP_VIIRS.20230406T024200.L2.OC.NRT {
    4463             :         dimensions:
    4464             :             number_of_lines = 3248 ;
    4465             :             pixels_per_line = 3200 ;
    4466             :             [...]
    4467             :             pixel_control_points = 3200 ;
    4468             :         [...]
    4469             :         group: geophysical_data {
    4470             :           variables:
    4471             :             short aot_862(number_of_lines, pixels_per_line) ;  <-- nVarId
    4472             :                 [...]
    4473             :         }
    4474             :         group: navigation_data {
    4475             :           variables:
    4476             :             float longitude(number_of_lines, pixel_control_points) ;
    4477             :                 [...]
    4478             :             float latitude(number_of_lines, pixel_control_points) ;
    4479             :                 [...]
    4480             :         }
    4481             :     }
    4482             :     */
    4483             :     // Note that the longitude and latitude arrays are not indexed by the
    4484             :     // same dimensions. Handle only the case where
    4485             :     // pixel_control_points == pixels_per_line
    4486             :     // If there was a subsampling of the geolocation arrays, we'd need to
    4487             :     // add more logic.
    4488             : 
    4489         592 :     std::string osGroupName;
    4490         296 :     osGroupName.resize(NC_MAX_NAME);
    4491         296 :     NCDF_ERR(nc_inq_grpname(nGroupId, &osGroupName[0]));
    4492         296 :     osGroupName.resize(strlen(osGroupName.data()));
    4493         296 :     if (osGroupName != "geophysical_data")
    4494         295 :         return false;
    4495             : 
    4496           1 :     int nVarDims = 0;
    4497           1 :     NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
    4498           1 :     if (nVarDims != 2)
    4499           0 :         return false;
    4500             : 
    4501           1 :     int nNavigationDataGrpId = 0;
    4502           1 :     if (nc_inq_grp_ncid(cdfid, "navigation_data", &nNavigationDataGrpId) !=
    4503             :         NC_NOERR)
    4504           0 :         return false;
    4505             : 
    4506             :     std::array<int, 2> anVarDimIds;
    4507           1 :     NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
    4508             : 
    4509           1 :     int nLongitudeId = 0;
    4510           1 :     int nLatitudeId = 0;
    4511           1 :     if (nc_inq_varid(nNavigationDataGrpId, "longitude", &nLongitudeId) !=
    4512           2 :             NC_NOERR ||
    4513           1 :         nc_inq_varid(nNavigationDataGrpId, "latitude", &nLatitudeId) !=
    4514             :             NC_NOERR)
    4515             :     {
    4516           0 :         return false;
    4517             :     }
    4518             : 
    4519           1 :     int nDimsLongitude = 0;
    4520           1 :     NCDF_ERR(
    4521             :         nc_inq_varndims(nNavigationDataGrpId, nLongitudeId, &nDimsLongitude));
    4522           1 :     int nDimsLatitude = 0;
    4523           1 :     NCDF_ERR(
    4524             :         nc_inq_varndims(nNavigationDataGrpId, nLatitudeId, &nDimsLatitude));
    4525           1 :     if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
    4526             :     {
    4527           0 :         return false;
    4528             :     }
    4529             : 
    4530             :     std::array<int, 2> anDimLongitudeIds;
    4531           1 :     NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLongitudeId,
    4532             :                              anDimLongitudeIds.data()));
    4533             :     std::array<int, 2> anDimLatitudeIds;
    4534           1 :     NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLatitudeId,
    4535             :                              anDimLatitudeIds.data()));
    4536           1 :     if (anDimLongitudeIds != anDimLatitudeIds)
    4537             :     {
    4538           0 :         return false;
    4539             :     }
    4540             : 
    4541             :     std::array<size_t, 2> anSizeVarDimIds;
    4542             :     std::array<size_t, 2> anSizeLongLatIds;
    4543           2 :     if (!(nc_inq_dimlen(cdfid, anVarDimIds[0], &anSizeVarDimIds[0]) ==
    4544           1 :               NC_NOERR &&
    4545           1 :           nc_inq_dimlen(cdfid, anVarDimIds[1], &anSizeVarDimIds[1]) ==
    4546           1 :               NC_NOERR &&
    4547           1 :           nc_inq_dimlen(cdfid, anDimLongitudeIds[0], &anSizeLongLatIds[0]) ==
    4548           1 :               NC_NOERR &&
    4549           1 :           nc_inq_dimlen(cdfid, anDimLongitudeIds[1], &anSizeLongLatIds[1]) ==
    4550             :               NC_NOERR &&
    4551           1 :           anSizeVarDimIds == anSizeLongLatIds))
    4552             :     {
    4553           0 :         return false;
    4554             :     }
    4555             : 
    4556           1 :     const char *pszGeolocXFullName = "/navigation_data/longitude";
    4557           1 :     const char *pszGeolocYFullName = "/navigation_data/latitude";
    4558             : 
    4559           1 :     if (bSwitchedXY)
    4560             :     {
    4561           0 :         std::swap(pszGeolocXFullName, pszGeolocYFullName);
    4562           0 :         GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", "GEOLOCATION");
    4563             :     }
    4564             : 
    4565           1 :     CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
    4566             :              pszGeolocXFullName, pszGeolocYFullName);
    4567             : 
    4568           1 :     GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
    4569             :                                     "GEOLOCATION");
    4570             : 
    4571           1 :     CPLString osTMP;
    4572           1 :     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
    4573             : 
    4574           1 :     GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
    4575           1 :     GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
    4576           1 :     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
    4577             : 
    4578           1 :     GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
    4579           1 :     GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
    4580             : 
    4581           1 :     GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
    4582           1 :     GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
    4583             : 
    4584           1 :     GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
    4585           1 :     GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
    4586             : 
    4587           1 :     GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
    4588             :                                     "GEOLOCATION");
    4589           1 :     return true;
    4590             : }
    4591             : 
    4592         295 : bool netCDFDataset::ProcessNASAEMITGeoLocation(int nGroupId, int nVarId)
    4593             : {
    4594             :     // Cf https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/
    4595             : 
    4596             :     // Check for a structure like:
    4597             :     /* netcdf EMIT_L2A_RFL_001_20220903T163129_2224611_012 {
    4598             :         dimensions:
    4599             :             downtrack = 1280 ;
    4600             :             crosstrack = 1242 ;
    4601             :             bands = 285 ;
    4602             :             [...]
    4603             : 
    4604             :         variables:
    4605             :             float reflectance(downtrack, crosstrack, bands) ;
    4606             : 
    4607             :         group: location {
    4608             :           variables:
    4609             :                 double lon(downtrack, crosstrack) ;
    4610             :                         lon:_FillValue = -9999. ;
    4611             :                         lon:long_name = "Longitude (WGS-84)" ;
    4612             :                         lon:units = "degrees east" ;
    4613             :                 double lat(downtrack, crosstrack) ;
    4614             :                         lat:_FillValue = -9999. ;
    4615             :                         lat:long_name = "Latitude (WGS-84)" ;
    4616             :                         lat:units = "degrees north" ;
    4617             :           } // group location
    4618             : 
    4619             :     }
    4620             :     or
    4621             :     netcdf EMIT_L2B_MIN_001_20231024T055538_2329704_040 {
    4622             :         dimensions:
    4623             :                 downtrack = 1664 ;
    4624             :                 crosstrack = 1242 ;
    4625             :                 [...]
    4626             :         variables:
    4627             :                 float group_1_band_depth(downtrack, crosstrack) ;
    4628             :                         group_1_band_depth:_FillValue = -9999.f ;
    4629             :                         group_1_band_depth:long_name = "Group 1 Band Depth" ;
    4630             :                         group_1_band_depth:units = "unitless" ;
    4631             :                 [...]
    4632             :         group: location {
    4633             :           variables:
    4634             :                 double lon(downtrack, crosstrack) ;
    4635             :                         lon:_FillValue = -9999. ;
    4636             :                         lon:long_name = "Longitude (WGS-84)" ;
    4637             :                         lon:units = "degrees east" ;
    4638             :                 double lat(downtrack, crosstrack) ;
    4639             :                         lat:_FillValue = -9999. ;
    4640             :                         lat:long_name = "Latitude (WGS-84)" ;
    4641             :                         lat:units = "degrees north" ;
    4642             :         }
    4643             :     */
    4644             : 
    4645         295 :     int nVarDims = 0;
    4646         295 :     NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
    4647         295 :     if (nVarDims != 2 && nVarDims != 3)
    4648          14 :         return false;
    4649             : 
    4650         281 :     int nLocationGrpId = 0;
    4651         281 :     if (nc_inq_grp_ncid(cdfid, "location", &nLocationGrpId) != NC_NOERR)
    4652          62 :         return false;
    4653             : 
    4654             :     std::array<int, 3> anVarDimIds;
    4655         219 :     NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
    4656         219 :     if (nYDimID != anVarDimIds[0] || nXDimID != anVarDimIds[1])
    4657          18 :         return false;
    4658             : 
    4659         201 :     int nLongitudeId = 0;
    4660         201 :     int nLatitudeId = 0;
    4661         252 :     if (nc_inq_varid(nLocationGrpId, "lon", &nLongitudeId) != NC_NOERR ||
    4662          51 :         nc_inq_varid(nLocationGrpId, "lat", &nLatitudeId) != NC_NOERR)
    4663             :     {
    4664         150 :         return false;
    4665             :     }
    4666             : 
    4667          51 :     int nDimsLongitude = 0;
    4668          51 :     NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLongitudeId, &nDimsLongitude));
    4669          51 :     int nDimsLatitude = 0;
    4670          51 :     NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLatitudeId, &nDimsLatitude));
    4671          51 :     if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
    4672             :     {
    4673          31 :         return false;
    4674             :     }
    4675             : 
    4676             :     std::array<int, 2> anDimLongitudeIds;
    4677          20 :     NCDF_ERR(nc_inq_vardimid(nLocationGrpId, nLongitudeId,
    4678             :                              anDimLongitudeIds.data()));
    4679             :     std::array<int, 2> anDimLatitudeIds;
    4680          20 :     NCDF_ERR(
    4681             :         nc_inq_vardimid(nLocationGrpId, nLatitudeId, anDimLatitudeIds.data()));
    4682          20 :     if (anDimLongitudeIds != anDimLatitudeIds)
    4683             :     {
    4684           0 :         return false;
    4685             :     }
    4686             : 
    4687          40 :     if (anDimLongitudeIds[0] != anVarDimIds[0] ||
    4688          20 :         anDimLongitudeIds[1] != anVarDimIds[1])
    4689             :     {
    4690           0 :         return false;
    4691             :     }
    4692             : 
    4693          20 :     const char *pszGeolocXFullName = "/location/lon";
    4694          20 :     const char *pszGeolocYFullName = "/location/lat";
    4695             : 
    4696          20 :     CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
    4697             :              pszGeolocXFullName, pszGeolocYFullName);
    4698             : 
    4699          20 :     GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
    4700             :                                     "GEOLOCATION");
    4701             : 
    4702          20 :     CPLString osTMP;
    4703          20 :     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
    4704             : 
    4705          20 :     GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
    4706          20 :     GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
    4707          20 :     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
    4708             : 
    4709          20 :     GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
    4710          20 :     GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
    4711             : 
    4712          20 :     GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
    4713          20 :     GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
    4714             : 
    4715          20 :     GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
    4716          20 :     GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
    4717             : 
    4718          20 :     GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
    4719             :                                     "GEOLOCATION");
    4720          20 :     return true;
    4721             : }
    4722             : 
    4723         338 : int netCDFDataset::ProcessCFGeolocation(int nGroupId, int nVarId,
    4724             :                                         const std::string &osGeolocWKT,
    4725             :                                         std::string &osGeolocXNameOut,
    4726             :                                         std::string &osGeolocYNameOut)
    4727             : {
    4728         338 :     bool bAddGeoloc = false;
    4729         338 :     char *pszTemp = nullptr;
    4730             : 
    4731         338 :     if (NCDFGetAttr(nGroupId, nVarId, "coordinates", &pszTemp) == CE_None)
    4732             :     {
    4733             :         // Get X and Y geolocation names from coordinates attribute.
    4734          42 :         char **papszTokens = NCDFTokenizeCoordinatesAttribute(pszTemp);
    4735          42 :         if (CSLCount(papszTokens) >= 2)
    4736             :         {
    4737             :             char szGeolocXName[NC_MAX_NAME + 1];
    4738             :             char szGeolocYName[NC_MAX_NAME + 1];
    4739          39 :             szGeolocXName[0] = '\0';
    4740          39 :             szGeolocYName[0] = '\0';
    4741             : 
    4742             :             // Test that each variable is longitude/latitude.
    4743         126 :             for (int i = 0; i < CSLCount(papszTokens); i++)
    4744             :             {
    4745          87 :                 if (NCDFIsVarLongitude(nGroupId, -1, papszTokens[i]))
    4746             :                 {
    4747          32 :                     int nOtherGroupId = -1;
    4748          32 :                     int nOtherVarId = -1;
    4749             :                     // Check that the variable actually exists
    4750             :                     // Needed on Sentinel-3 products
    4751          32 :                     if (NCDFResolveVar(nGroupId, papszTokens[i], &nOtherGroupId,
    4752          32 :                                        &nOtherVarId) == CE_None)
    4753             :                     {
    4754          30 :                         snprintf(szGeolocXName, sizeof(szGeolocXName), "%s",
    4755          30 :                                  papszTokens[i]);
    4756             :                     }
    4757             :                 }
    4758          55 :                 else if (NCDFIsVarLatitude(nGroupId, -1, papszTokens[i]))
    4759             :                 {
    4760          32 :                     int nOtherGroupId = -1;
    4761          32 :                     int nOtherVarId = -1;
    4762             :                     // Check that the variable actually exists
    4763             :                     // Needed on Sentinel-3 products
    4764          32 :                     if (NCDFResolveVar(nGroupId, papszTokens[i], &nOtherGroupId,
    4765          32 :                                        &nOtherVarId) == CE_None)
    4766             :                     {
    4767          30 :                         snprintf(szGeolocYName, sizeof(szGeolocYName), "%s",
    4768          30 :                                  papszTokens[i]);
    4769             :                     }
    4770             :                 }
    4771             :             }
    4772             :             // Add GEOLOCATION metadata.
    4773          39 :             if (!EQUAL(szGeolocXName, "") && !EQUAL(szGeolocYName, ""))
    4774             :             {
    4775          30 :                 osGeolocXNameOut = szGeolocXName;
    4776          30 :                 osGeolocYNameOut = szGeolocYName;
    4777             : 
    4778          30 :                 char *pszGeolocXFullName = nullptr;
    4779          30 :                 char *pszGeolocYFullName = nullptr;
    4780          30 :                 if (NCDFResolveVarFullName(nGroupId, szGeolocXName,
    4781          60 :                                            &pszGeolocXFullName) == CE_None &&
    4782          30 :                     NCDFResolveVarFullName(nGroupId, szGeolocYName,
    4783             :                                            &pszGeolocYFullName) == CE_None)
    4784             :                 {
    4785          30 :                     if (bSwitchedXY)
    4786             :                     {
    4787           2 :                         std::swap(pszGeolocXFullName, pszGeolocYFullName);
    4788           2 :                         GDALPamDataset::SetMetadataItem("SWAP_XY", "YES",
    4789             :                                                         "GEOLOCATION");
    4790             :                     }
    4791             : 
    4792          30 :                     bAddGeoloc = true;
    4793          30 :                     CPLDebug("GDAL_netCDF",
    4794             :                              "using variables %s and %s for GEOLOCATION",
    4795             :                              pszGeolocXFullName, pszGeolocYFullName);
    4796             : 
    4797          30 :                     GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
    4798             :                                                     "GEOLOCATION");
    4799             : 
    4800          60 :                     CPLString osTMP;
    4801          30 :                     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
    4802          30 :                                  pszGeolocXFullName);
    4803             : 
    4804          30 :                     GDALPamDataset::SetMetadataItem("X_DATASET", osTMP,
    4805             :                                                     "GEOLOCATION");
    4806          30 :                     GDALPamDataset::SetMetadataItem("X_BAND", "1",
    4807             :                                                     "GEOLOCATION");
    4808          30 :                     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
    4809          30 :                                  pszGeolocYFullName);
    4810             : 
    4811          30 :                     GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP,
    4812             :                                                     "GEOLOCATION");
    4813          30 :                     GDALPamDataset::SetMetadataItem("Y_BAND", "1",
    4814             :                                                     "GEOLOCATION");
    4815             : 
    4816          30 :                     GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0",
    4817             :                                                     "GEOLOCATION");
    4818          30 :                     GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1",
    4819             :                                                     "GEOLOCATION");
    4820             : 
    4821          30 :                     GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0",
    4822             :                                                     "GEOLOCATION");
    4823          30 :                     GDALPamDataset::SetMetadataItem("LINE_STEP", "1",
    4824             :                                                     "GEOLOCATION");
    4825             : 
    4826          30 :                     GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
    4827             :                                                     "PIXEL_CENTER",
    4828             :                                                     "GEOLOCATION");
    4829             :                 }
    4830             :                 else
    4831             :                 {
    4832           0 :                     CPLDebug("GDAL_netCDF",
    4833             :                              "cannot resolve location of "
    4834             :                              "lat/lon variables specified by the coordinates "
    4835             :                              "attribute [%s]",
    4836             :                              pszTemp);
    4837             :                 }
    4838          30 :                 CPLFree(pszGeolocXFullName);
    4839          30 :                 CPLFree(pszGeolocYFullName);
    4840             :             }
    4841             :             else
    4842             :             {
    4843           9 :                 CPLDebug("GDAL_netCDF",
    4844             :                          "coordinates attribute [%s] is unsupported", pszTemp);
    4845             :             }
    4846             :         }
    4847             :         else
    4848             :         {
    4849           3 :             CPLDebug("GDAL_netCDF",
    4850             :                      "coordinates attribute [%s] with %d element(s) is "
    4851             :                      "unsupported",
    4852             :                      pszTemp, CSLCount(papszTokens));
    4853             :         }
    4854          42 :         if (papszTokens)
    4855          42 :             CSLDestroy(papszTokens);
    4856             :     }
    4857             : 
    4858             :     else
    4859             :     {
    4860         296 :         bAddGeoloc = ProcessNASAL2OceanGeoLocation(nGroupId, nVarId);
    4861             : 
    4862         296 :         if (!bAddGeoloc)
    4863         295 :             bAddGeoloc = ProcessNASAEMITGeoLocation(nGroupId, nVarId);
    4864             :     }
    4865             : 
    4866         338 :     CPLFree(pszTemp);
    4867             : 
    4868         338 :     return bAddGeoloc;
    4869             : }
    4870             : 
    4871           8 : CPLErr netCDFDataset::Set1DGeolocation(int nGroupId, int nVarId,
    4872             :                                        const char *szDimName)
    4873             : {
    4874             :     // Get values.
    4875           8 :     char *pszVarValues = nullptr;
    4876           8 :     CPLErr eErr = NCDFGet1DVar(nGroupId, nVarId, &pszVarValues);
    4877           8 :     if (eErr != CE_None)
    4878           0 :         return eErr;
    4879             : 
    4880             :     // Write metadata.
    4881           8 :     char szTemp[NC_MAX_NAME + 1 + 32] = {};
    4882           8 :     snprintf(szTemp, sizeof(szTemp), "%s_VALUES", szDimName);
    4883           8 :     GDALPamDataset::SetMetadataItem(szTemp, pszVarValues, "GEOLOCATION2");
    4884             : 
    4885           8 :     CPLFree(pszVarValues);
    4886             : 
    4887           8 :     return CE_None;
    4888             : }
    4889             : 
    4890           0 : double *netCDFDataset::Get1DGeolocation(CPL_UNUSED const char *szDimName,
    4891             :                                         int &nVarLen)
    4892             : {
    4893           0 :     nVarLen = 0;
    4894             : 
    4895             :     // Get Y_VALUES as tokens.
    4896             :     char **papszValues =
    4897           0 :         NCDFTokenizeArray(GetMetadataItem("Y_VALUES", "GEOLOCATION2"));
    4898           0 :     if (papszValues == nullptr)
    4899           0 :         return nullptr;
    4900             : 
    4901             :     // Initialize and fill array.
    4902           0 :     nVarLen = CSLCount(papszValues);
    4903             :     double *pdfVarValues =
    4904           0 :         static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
    4905             : 
    4906           0 :     for (int i = 0, j = 0; i < nVarLen; i++)
    4907             :     {
    4908           0 :         if (!bBottomUp)
    4909           0 :             j = nVarLen - 1 - i;
    4910             :         else
    4911           0 :             j = i;  // Invert latitude values.
    4912           0 :         char *pszTemp = nullptr;
    4913           0 :         pdfVarValues[j] = CPLStrtod(papszValues[i], &pszTemp);
    4914             :     }
    4915           0 :     CSLDestroy(papszValues);
    4916             : 
    4917           0 :     return pdfVarValues;
    4918             : }
    4919             : 
    4920             : /************************************************************************/
    4921             : /*                        SetSpatialRefNoUpdate()                       */
    4922             : /************************************************************************/
    4923             : 
    4924         242 : void netCDFDataset::SetSpatialRefNoUpdate(const OGRSpatialReference *poSRS)
    4925             : {
    4926         242 :     m_oSRS.Clear();
    4927         242 :     if (poSRS)
    4928         235 :         m_oSRS = *poSRS;
    4929         242 :     m_bHasProjection = true;
    4930         242 : }
    4931             : 
    4932             : /************************************************************************/
    4933             : /*                          SetSpatialRef()                             */
    4934             : /************************************************************************/
    4935             : 
    4936          71 : CPLErr netCDFDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
    4937             : {
    4938         142 :     CPLMutexHolderD(&hNCMutex);
    4939             : 
    4940          71 :     if (GetAccess() != GA_Update || m_bHasProjection)
    4941             :     {
    4942           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    4943             :                  "netCDFDataset::_SetProjection() should only be called once "
    4944             :                  "in update mode!");
    4945           0 :         return CE_Failure;
    4946             :     }
    4947             : 
    4948          71 :     if (m_bHasGeoTransform)
    4949             :     {
    4950          32 :         SetSpatialRefNoUpdate(poSRS);
    4951             : 
    4952             :         // For NC4/NC4C, writing both projection variables and data,
    4953             :         // followed by redefining nodata value, cancels the projection
    4954             :         // info from the Band variable, so for now only write the
    4955             :         // variable definitions, and write data at the end.
    4956             :         // See https://trac.osgeo.org/gdal/ticket/7245
    4957          32 :         return AddProjectionVars(true, nullptr, nullptr);
    4958             :     }
    4959             : 
    4960          39 :     SetSpatialRefNoUpdate(poSRS);
    4961             : 
    4962          39 :     return CE_None;
    4963             : }
    4964             : 
    4965             : /************************************************************************/
    4966             : /*                     SetGeoTransformNoUpdate()                        */
    4967             : /************************************************************************/
    4968             : 
    4969         264 : void netCDFDataset::SetGeoTransformNoUpdate(double *padfTransform)
    4970             : {
    4971         264 :     memcpy(m_adfGeoTransform, padfTransform, sizeof(double) * 6);
    4972         264 :     m_bHasGeoTransform = true;
    4973         264 : }
    4974             : 
    4975             : /************************************************************************/
    4976             : /*                          SetGeoTransform()                           */
    4977             : /************************************************************************/
    4978             : 
    4979          72 : CPLErr netCDFDataset::SetGeoTransform(double *padfTransform)
    4980             : {
    4981         144 :     CPLMutexHolderD(&hNCMutex);
    4982             : 
    4983          72 :     if (GetAccess() != GA_Update || m_bHasGeoTransform)
    4984             :     {
    4985           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    4986             :                  "netCDFDataset::SetGeoTransform() should only be called once "
    4987             :                  "in update mode!");
    4988           0 :         return CE_Failure;
    4989             :     }
    4990             : 
    4991          72 :     CPLDebug("GDAL_netCDF", "SetGeoTransform(%f,%f,%f,%f,%f,%f)",
    4992          72 :              padfTransform[0], padfTransform[1], padfTransform[2],
    4993          72 :              padfTransform[3], padfTransform[4], padfTransform[5]);
    4994             : 
    4995          72 :     if (m_bHasProjection)
    4996             :     {
    4997           2 :         SetGeoTransformNoUpdate(padfTransform);
    4998             : 
    4999             :         // For NC4/NC4C, writing both projection variables and data,
    5000             :         // followed by redefining nodata value, cancels the projection
    5001             :         // info from the Band variable, so for now only write the
    5002             :         // variable definitions, and write data at the end.
    5003             :         // See https://trac.osgeo.org/gdal/ticket/7245
    5004           2 :         return AddProjectionVars(true, nullptr, nullptr);
    5005             :     }
    5006             : 
    5007          70 :     SetGeoTransformNoUpdate(padfTransform);
    5008          70 :     return CE_None;
    5009             : }
    5010             : 
    5011             : /************************************************************************/
    5012             : /*                         NCDFWriteSRSVariable()                       */
    5013             : /************************************************************************/
    5014             : 
    5015         123 : int NCDFWriteSRSVariable(int cdfid, const OGRSpatialReference *poSRS,
    5016             :                          char **ppszCFProjection, bool bWriteGDALTags,
    5017             :                          const std::string &srsVarName)
    5018             : {
    5019         123 :     char *pszCFProjection = nullptr;
    5020         123 :     char **papszKeyValues = nullptr;
    5021         123 :     poSRS->exportToCF1(&pszCFProjection, &papszKeyValues, nullptr, nullptr);
    5022             : 
    5023         123 :     if (bWriteGDALTags)
    5024             :     {
    5025         122 :         const char *pszWKT = CSLFetchNameValue(papszKeyValues, NCDF_CRS_WKT);
    5026         122 :         if (pszWKT)
    5027             :         {
    5028             :             // SPATIAL_REF is deprecated. Will be removed in GDAL 4.
    5029         122 :             papszKeyValues =
    5030         122 :                 CSLSetNameValue(papszKeyValues, NCDF_SPATIAL_REF, pszWKT);
    5031             :         }
    5032             :     }
    5033             : 
    5034         123 :     const int nValues = CSLCount(papszKeyValues);
    5035             : 
    5036             :     int NCDFVarID;
    5037         246 :     std::string varNameRadix(pszCFProjection);
    5038         123 :     int nCounter = 2;
    5039             :     while (true)
    5040             :     {
    5041         125 :         NCDFVarID = -1;
    5042         125 :         nc_inq_varid(cdfid, pszCFProjection, &NCDFVarID);
    5043         125 :         if (NCDFVarID < 0)
    5044         120 :             break;
    5045             : 
    5046           5 :         int nbAttr = 0;
    5047           5 :         NCDF_ERR(nc_inq_varnatts(cdfid, NCDFVarID, &nbAttr));
    5048           5 :         bool bSame = nbAttr == nValues;
    5049          41 :         for (int i = 0; bSame && (i < nbAttr); i++)
    5050             :         {
    5051             :             char szAttrName[NC_MAX_NAME + 1];
    5052          38 :             szAttrName[0] = 0;
    5053          38 :             NCDF_ERR(nc_inq_attname(cdfid, NCDFVarID, i, szAttrName));
    5054             : 
    5055             :             const char *pszValue =
    5056          38 :                 CSLFetchNameValue(papszKeyValues, szAttrName);
    5057          38 :             if (!pszValue)
    5058             :             {
    5059           0 :                 bSame = false;
    5060           2 :                 break;
    5061             :             }
    5062             : 
    5063          38 :             nc_type atttype = NC_NAT;
    5064          38 :             size_t attlen = 0;
    5065          38 :             NCDF_ERR(
    5066             :                 nc_inq_att(cdfid, NCDFVarID, szAttrName, &atttype, &attlen));
    5067          38 :             if (atttype != NC_CHAR && atttype != NC_DOUBLE)
    5068             :             {
    5069           0 :                 bSame = false;
    5070           0 :                 break;
    5071             :             }
    5072          38 :             if (atttype == NC_CHAR)
    5073             :             {
    5074          15 :                 if (CPLGetValueType(pszValue) != CPL_VALUE_STRING)
    5075             :                 {
    5076           0 :                     bSame = false;
    5077           0 :                     break;
    5078             :                 }
    5079          15 :                 std::string val;
    5080          15 :                 val.resize(attlen);
    5081          15 :                 nc_get_att_text(cdfid, NCDFVarID, szAttrName, &val[0]);
    5082          15 :                 if (val != pszValue)
    5083             :                 {
    5084           0 :                     bSame = false;
    5085           0 :                     break;
    5086             :                 }
    5087             :             }
    5088             :             else
    5089             :             {
    5090             :                 const CPLStringList aosTokens(
    5091          23 :                     CSLTokenizeString2(pszValue, ",", 0));
    5092          23 :                 if (static_cast<size_t>(aosTokens.size()) != attlen)
    5093             :                 {
    5094           0 :                     bSame = false;
    5095           0 :                     break;
    5096             :                 }
    5097             :                 double vals[2];
    5098          23 :                 nc_get_att_double(cdfid, NCDFVarID, szAttrName, vals);
    5099          44 :                 if (vals[0] != CPLAtof(aosTokens[0]) ||
    5100          21 :                     (attlen == 2 && vals[1] != CPLAtof(aosTokens[1])))
    5101             :                 {
    5102           2 :                     bSame = false;
    5103           2 :                     break;
    5104             :                 }
    5105             :             }
    5106             :         }
    5107           5 :         if (bSame)
    5108             :         {
    5109           3 :             *ppszCFProjection = pszCFProjection;
    5110           3 :             CSLDestroy(papszKeyValues);
    5111           3 :             return NCDFVarID;
    5112             :         }
    5113           2 :         CPLFree(pszCFProjection);
    5114           2 :         pszCFProjection =
    5115           2 :             CPLStrdup(CPLSPrintf("%s_%d", varNameRadix.c_str(), nCounter));
    5116           2 :         nCounter++;
    5117           2 :     }
    5118             : 
    5119         120 :     *ppszCFProjection = pszCFProjection;
    5120             : 
    5121             :     const char *pszVarName;
    5122             : 
    5123         120 :     if (srsVarName != "")
    5124             :     {
    5125          38 :         pszVarName = srsVarName.c_str();
    5126             :     }
    5127             :     else
    5128             :     {
    5129          82 :         pszVarName = pszCFProjection;
    5130             :     }
    5131             : 
    5132         120 :     int status = nc_def_var(cdfid, pszVarName, NC_CHAR, 0, nullptr, &NCDFVarID);
    5133         120 :     NCDF_ERR(status);
    5134        1144 :     for (int i = 0; i < nValues; ++i)
    5135             :     {
    5136        1024 :         char *pszKey = nullptr;
    5137        1024 :         const char *pszValue = CPLParseNameValue(papszKeyValues[i], &pszKey);
    5138        1024 :         if (pszKey && pszValue)
    5139             :         {
    5140        2048 :             const CPLStringList aosTokens(CSLTokenizeString2(pszValue, ",", 0));
    5141        1024 :             double adfValues[2] = {0, 0};
    5142        1024 :             const int nDoubleCount = std::min(2, aosTokens.size());
    5143        1024 :             if (!(aosTokens.size() == 2 &&
    5144        2047 :                   CPLGetValueType(aosTokens[0]) != CPL_VALUE_STRING) &&
    5145        1023 :                 CPLGetValueType(pszValue) == CPL_VALUE_STRING)
    5146             :             {
    5147         479 :                 status = nc_put_att_text(cdfid, NCDFVarID, pszKey,
    5148             :                                          strlen(pszValue), pszValue);
    5149             :             }
    5150             :             else
    5151             :             {
    5152        1091 :                 for (int j = 0; j < nDoubleCount; ++j)
    5153         546 :                     adfValues[j] = CPLAtof(aosTokens[j]);
    5154         545 :                 status = nc_put_att_double(cdfid, NCDFVarID, pszKey, NC_DOUBLE,
    5155             :                                            nDoubleCount, adfValues);
    5156             :             }
    5157        1024 :             NCDF_ERR(status);
    5158             :         }
    5159        1024 :         CPLFree(pszKey);
    5160             :     }
    5161             : 
    5162         120 :     CSLDestroy(papszKeyValues);
    5163         120 :     return NCDFVarID;
    5164             : }
    5165             : 
    5166             : /************************************************************************/
    5167             : /*                   NCDFWriteLonLatVarsAttributes()                    */
    5168             : /************************************************************************/
    5169             : 
    5170          99 : void NCDFWriteLonLatVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarLonID,
    5171             :                                    int nVarLatID)
    5172             : {
    5173             : 
    5174             :     try
    5175             :     {
    5176          99 :         vcdf.nc_put_vatt_text(nVarLatID, CF_STD_NAME, CF_LATITUDE_STD_NAME);
    5177          99 :         vcdf.nc_put_vatt_text(nVarLatID, CF_LNG_NAME, CF_LATITUDE_LNG_NAME);
    5178          99 :         vcdf.nc_put_vatt_text(nVarLatID, CF_UNITS, CF_DEGREES_NORTH);
    5179          99 :         vcdf.nc_put_vatt_text(nVarLonID, CF_STD_NAME, CF_LONGITUDE_STD_NAME);
    5180          99 :         vcdf.nc_put_vatt_text(nVarLonID, CF_LNG_NAME, CF_LONGITUDE_LNG_NAME);
    5181          99 :         vcdf.nc_put_vatt_text(nVarLonID, CF_UNITS, CF_DEGREES_EAST);
    5182             :     }
    5183           0 :     catch (nccfdriver::SG_Exception &e)
    5184             :     {
    5185           0 :         CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
    5186             :     }
    5187          99 : }
    5188             : 
    5189             : /************************************************************************/
    5190             : /*                   NCDFWriteRLonRLatVarsAttributes()                    */
    5191             : /************************************************************************/
    5192             : 
    5193           0 : void NCDFWriteRLonRLatVarsAttributes(nccfdriver::netCDFVID &vcdf,
    5194             :                                      int nVarRLonID, int nVarRLatID)
    5195             : {
    5196             :     try
    5197             :     {
    5198           0 :         vcdf.nc_put_vatt_text(nVarRLatID, CF_STD_NAME, "grid_latitude");
    5199           0 :         vcdf.nc_put_vatt_text(nVarRLatID, CF_LNG_NAME,
    5200             :                               "latitude in rotated pole grid");
    5201           0 :         vcdf.nc_put_vatt_text(nVarRLatID, CF_UNITS, "degrees");
    5202           0 :         vcdf.nc_put_vatt_text(nVarRLatID, CF_AXIS, "Y");
    5203             : 
    5204           0 :         vcdf.nc_put_vatt_text(nVarRLonID, CF_STD_NAME, "grid_longitude");
    5205           0 :         vcdf.nc_put_vatt_text(nVarRLonID, CF_LNG_NAME,
    5206             :                               "longitude in rotated pole grid");
    5207           0 :         vcdf.nc_put_vatt_text(nVarRLonID, CF_UNITS, "degrees");
    5208           0 :         vcdf.nc_put_vatt_text(nVarRLonID, CF_AXIS, "X");
    5209             :     }
    5210           0 :     catch (nccfdriver::SG_Exception &e)
    5211             :     {
    5212           0 :         CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
    5213             :     }
    5214           0 : }
    5215             : 
    5216             : /************************************************************************/
    5217             : /*                        NCDFGetProjectedCFUnit()                      */
    5218             : /************************************************************************/
    5219             : 
    5220          37 : std::string NCDFGetProjectedCFUnit(const OGRSpatialReference *poSRS)
    5221             : {
    5222          37 :     char *pszUnitsToWrite = nullptr;
    5223          37 :     poSRS->exportToCF1(nullptr, nullptr, &pszUnitsToWrite, nullptr);
    5224          37 :     const std::string osRet = pszUnitsToWrite ? pszUnitsToWrite : std::string();
    5225          37 :     CPLFree(pszUnitsToWrite);
    5226          74 :     return osRet;
    5227             : }
    5228             : 
    5229             : /************************************************************************/
    5230             : /*                     NCDFWriteXYVarsAttributes()                      */
    5231             : /************************************************************************/
    5232             : 
    5233          26 : void NCDFWriteXYVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarXID,
    5234             :                                int nVarYID, const OGRSpatialReference *poSRS)
    5235             : {
    5236          52 :     const std::string osUnitsToWrite = NCDFGetProjectedCFUnit(poSRS);
    5237             : 
    5238             :     try
    5239             :     {
    5240          26 :         vcdf.nc_put_vatt_text(nVarXID, CF_STD_NAME, CF_PROJ_X_COORD);
    5241          26 :         vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME, CF_PROJ_X_COORD_LONG_NAME);
    5242          26 :         if (!osUnitsToWrite.empty())
    5243          26 :             vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, osUnitsToWrite.c_str());
    5244          26 :         vcdf.nc_put_vatt_text(nVarYID, CF_STD_NAME, CF_PROJ_Y_COORD);
    5245          26 :         vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME, CF_PROJ_Y_COORD_LONG_NAME);
    5246          26 :         if (!osUnitsToWrite.empty())
    5247          26 :             vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, osUnitsToWrite.c_str());
    5248             :     }
    5249           0 :     catch (nccfdriver::SG_Exception &e)
    5250             :     {
    5251           0 :         CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
    5252             :     }
    5253          26 : }
    5254             : 
    5255             : /************************************************************************/
    5256             : /*                          AddProjectionVars()                         */
    5257             : /************************************************************************/
    5258             : 
    5259         154 : CPLErr netCDFDataset::AddProjectionVars(bool bDefsOnly,
    5260             :                                         GDALProgressFunc pfnProgress,
    5261             :                                         void *pProgressData)
    5262             : {
    5263         154 :     if (nCFVersion >= 1.8)
    5264           0 :         return CE_None;  // do nothing
    5265             : 
    5266         154 :     bool bWriteGridMapping = false;
    5267         154 :     bool bWriteLonLat = false;
    5268         154 :     bool bHasGeoloc = false;
    5269         154 :     bool bWriteGDALTags = false;
    5270         154 :     bool bWriteGeoTransform = false;
    5271             : 
    5272             :     // For GEOLOCATION information.
    5273         154 :     GDALDatasetH hDS_X = nullptr;
    5274         154 :     GDALRasterBandH hBand_X = nullptr;
    5275         154 :     GDALDatasetH hDS_Y = nullptr;
    5276         154 :     GDALRasterBandH hBand_Y = nullptr;
    5277             : 
    5278         308 :     OGRSpatialReference oSRS(m_oSRS);
    5279         154 :     if (!m_oSRS.IsEmpty())
    5280             :     {
    5281         128 :         if (oSRS.IsProjected())
    5282          44 :             bIsProjected = true;
    5283          84 :         else if (oSRS.IsGeographic())
    5284          84 :             bIsGeographic = true;
    5285             :     }
    5286             : 
    5287         154 :     if (bDefsOnly)
    5288             :     {
    5289          77 :         char *pszProjection = nullptr;
    5290          77 :         m_oSRS.exportToWkt(&pszProjection);
    5291          77 :         CPLDebug("GDAL_netCDF",
    5292             :                  "SetProjection, WKT now = [%s]\nprojected: %d geographic: %d",
    5293          77 :                  pszProjection ? pszProjection : "(null)",
    5294          77 :                  static_cast<int>(bIsProjected),
    5295          77 :                  static_cast<int>(bIsGeographic));
    5296          77 :         CPLFree(pszProjection);
    5297             : 
    5298          77 :         if (!m_bHasGeoTransform)
    5299           5 :             CPLDebug("GDAL_netCDF",
    5300             :                      "netCDFDataset::AddProjectionVars() called, "
    5301             :                      "but GeoTransform has not yet been defined!");
    5302             : 
    5303          77 :         if (!m_bHasProjection)
    5304           6 :             CPLDebug("GDAL_netCDF",
    5305             :                      "netCDFDataset::AddProjectionVars() called, "
    5306             :                      "but Projection has not yet been defined!");
    5307             :     }
    5308             : 
    5309             :     // Check GEOLOCATION information.
    5310         154 :     char **papszGeolocationInfo = netCDFDataset::GetMetadata("GEOLOCATION");
    5311         154 :     if (papszGeolocationInfo != nullptr)
    5312             :     {
    5313             :         // Look for geolocation datasets.
    5314             :         const char *pszDSName =
    5315          10 :             CSLFetchNameValue(papszGeolocationInfo, "X_DATASET");
    5316          10 :         if (pszDSName != nullptr)
    5317          10 :             hDS_X = GDALOpenShared(pszDSName, GA_ReadOnly);
    5318          10 :         pszDSName = CSLFetchNameValue(papszGeolocationInfo, "Y_DATASET");
    5319          10 :         if (pszDSName != nullptr)
    5320          10 :             hDS_Y = GDALOpenShared(pszDSName, GA_ReadOnly);
    5321             : 
    5322          10 :         if (hDS_X != nullptr && hDS_Y != nullptr)
    5323             :         {
    5324          10 :             int nBand = std::max(1, atoi(CSLFetchNameValueDef(
    5325          10 :                                         papszGeolocationInfo, "X_BAND", "0")));
    5326          10 :             hBand_X = GDALGetRasterBand(hDS_X, nBand);
    5327          10 :             nBand = std::max(1, atoi(CSLFetchNameValueDef(papszGeolocationInfo,
    5328          10 :                                                           "Y_BAND", "0")));
    5329          10 :             hBand_Y = GDALGetRasterBand(hDS_Y, nBand);
    5330             : 
    5331             :             // If geoloc bands are found, do basic validation based on their
    5332             :             // dimensions.
    5333          10 :             if (hBand_X != nullptr && hBand_Y != nullptr)
    5334             :             {
    5335          10 :                 int nXSize_XBand = GDALGetRasterXSize(hDS_X);
    5336          10 :                 int nYSize_XBand = GDALGetRasterYSize(hDS_X);
    5337          10 :                 int nXSize_YBand = GDALGetRasterXSize(hDS_Y);
    5338          10 :                 int nYSize_YBand = GDALGetRasterYSize(hDS_Y);
    5339             : 
    5340             :                 // TODO 1D geolocation arrays not implemented.
    5341          10 :                 if (nYSize_XBand == 1 && nYSize_YBand == 1)
    5342             :                 {
    5343           0 :                     bHasGeoloc = false;
    5344           0 :                     CPLDebug("GDAL_netCDF",
    5345             :                              "1D GEOLOCATION arrays not supported yet");
    5346             :                 }
    5347             :                 // 2D bands must have same sizes as the raster bands.
    5348          10 :                 else if (nXSize_XBand != nRasterXSize ||
    5349          10 :                          nYSize_XBand != nRasterYSize ||
    5350          10 :                          nXSize_YBand != nRasterXSize ||
    5351          10 :                          nYSize_YBand != nRasterYSize)
    5352             :                 {
    5353           0 :                     bHasGeoloc = false;
    5354           0 :                     CPLDebug("GDAL_netCDF",
    5355             :                              "GEOLOCATION array sizes (%dx%d %dx%d) differ "
    5356             :                              "from raster (%dx%d), not supported",
    5357             :                              nXSize_XBand, nYSize_XBand, nXSize_YBand,
    5358             :                              nYSize_YBand, nRasterXSize, nRasterYSize);
    5359             :                 }
    5360             :                 else
    5361             :                 {
    5362          10 :                     bHasGeoloc = true;
    5363          10 :                     CPLDebug("GDAL_netCDF",
    5364             :                              "dataset has GEOLOCATION information, will try to "
    5365             :                              "write it");
    5366             :                 }
    5367             :             }
    5368             :         }
    5369             :     }
    5370             : 
    5371             :     // Process projection options.
    5372         154 :     if (bIsProjected)
    5373             :     {
    5374             :         bool bIsCfProjection =
    5375          44 :             oSRS.exportToCF1(nullptr, nullptr, nullptr, nullptr) == OGRERR_NONE;
    5376          44 :         bWriteGridMapping = true;
    5377          44 :         bWriteGDALTags = CPL_TO_BOOL(
    5378          44 :             CSLFetchBoolean(papszCreationOptions, "WRITE_GDAL_TAGS", TRUE));
    5379             :         // Force WRITE_GDAL_TAGS if is not a CF projection.
    5380          44 :         if (!bWriteGDALTags && !bIsCfProjection)
    5381           0 :             bWriteGDALTags = true;
    5382          44 :         if (bWriteGDALTags)
    5383          44 :             bWriteGeoTransform = true;
    5384             : 
    5385             :         // Write lon/lat: default is NO, except if has geolocation.
    5386             :         // With IF_NEEDED: write if has geoloc or is not CF projection.
    5387             :         const char *pszValue =
    5388          44 :             CSLFetchNameValue(papszCreationOptions, "WRITE_LONLAT");
    5389          44 :         if (pszValue)
    5390             :         {
    5391           2 :             if (EQUAL(pszValue, "IF_NEEDED"))
    5392             :             {
    5393           0 :                 bWriteLonLat = bHasGeoloc || !bIsCfProjection;
    5394             :             }
    5395             :             else
    5396             :             {
    5397           2 :                 bWriteLonLat = CPLTestBool(pszValue);
    5398             :             }
    5399             :         }
    5400             :         else
    5401             :         {
    5402          42 :             bWriteLonLat = bHasGeoloc;
    5403             :         }
    5404             : 
    5405             :         // Save value of pszCFCoordinates for later.
    5406          44 :         if (bWriteLonLat)
    5407             :         {
    5408           4 :             pszCFCoordinates = NCDF_LONLAT;
    5409             :         }
    5410             :     }
    5411             :     else
    5412             :     {
    5413             :         // Files without a Datum will not have a grid_mapping variable and
    5414             :         // geographic information.
    5415         110 :         bWriteGridMapping = bIsGeographic;
    5416             : 
    5417         110 :         if (bHasGeoloc)
    5418             :         {
    5419           8 :             bWriteLonLat = true;
    5420             :         }
    5421             :         else
    5422             :         {
    5423         102 :             bWriteGDALTags = CPL_TO_BOOL(CSLFetchBoolean(
    5424         102 :                 papszCreationOptions, "WRITE_GDAL_TAGS", bWriteGridMapping));
    5425         102 :             if (bWriteGDALTags)
    5426          84 :                 bWriteGeoTransform = true;
    5427             : 
    5428         102 :             const char *pszValue = CSLFetchNameValueDef(papszCreationOptions,
    5429             :                                                         "WRITE_LONLAT", "YES");
    5430         102 :             if (EQUAL(pszValue, "IF_NEEDED"))
    5431           0 :                 bWriteLonLat = true;
    5432             :             else
    5433         102 :                 bWriteLonLat = CPLTestBool(pszValue);
    5434             :             //  Don't write lon/lat if no source geotransform.
    5435         102 :             if (!m_bHasGeoTransform)
    5436           0 :                 bWriteLonLat = false;
    5437             :             // If we don't write lon/lat, set dimnames to X/Y and write gdal
    5438             :             // tags.
    5439         102 :             if (!bWriteLonLat)
    5440             :             {
    5441           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    5442             :                          "creating geographic file without lon/lat values!");
    5443           0 :                 if (m_bHasGeoTransform)
    5444             :                 {
    5445           0 :                     bWriteGDALTags = true;  // Not desirable if no geotransform.
    5446           0 :                     bWriteGeoTransform = true;
    5447             :                 }
    5448             :             }
    5449             :         }
    5450             :     }
    5451             : 
    5452             :     // Make sure we write grid_mapping if we need to write GDAL tags.
    5453         154 :     if (bWriteGDALTags)
    5454         128 :         bWriteGridMapping = true;
    5455             : 
    5456             :     // bottom-up value: new driver is bottom-up by default.
    5457             :     // Override with WRITE_BOTTOMUP.
    5458         154 :     bBottomUp = CPL_TO_BOOL(
    5459         154 :         CSLFetchBoolean(papszCreationOptions, "WRITE_BOTTOMUP", TRUE));
    5460             : 
    5461         154 :     if (bDefsOnly)
    5462             :     {
    5463          77 :         CPLDebug(
    5464             :             "GDAL_netCDF",
    5465             :             "bIsProjected=%d bIsGeographic=%d bWriteGridMapping=%d "
    5466             :             "bWriteGDALTags=%d bWriteLonLat=%d bBottomUp=%d bHasGeoloc=%d",
    5467          77 :             static_cast<int>(bIsProjected), static_cast<int>(bIsGeographic),
    5468             :             static_cast<int>(bWriteGridMapping),
    5469             :             static_cast<int>(bWriteGDALTags), static_cast<int>(bWriteLonLat),
    5470          77 :             static_cast<int>(bBottomUp), static_cast<int>(bHasGeoloc));
    5471             :     }
    5472             : 
    5473             :     // Exit if nothing to do.
    5474         154 :     if (!bIsProjected && !bWriteLonLat)
    5475           0 :         return CE_None;
    5476             : 
    5477             :     // Define dimension names.
    5478             : 
    5479         154 :     constexpr const char *ROTATED_POLE_VAR_NAME = "rotated_pole";
    5480             : 
    5481         154 :     if (bDefsOnly)
    5482             :     {
    5483          77 :         int nVarLonID = -1;
    5484          77 :         int nVarLatID = -1;
    5485          77 :         int nVarXID = -1;
    5486          77 :         int nVarYID = -1;
    5487             : 
    5488          77 :         m_bAddedProjectionVarsDefs = true;
    5489             : 
    5490             :         // Make sure we are in define mode.
    5491          77 :         SetDefineMode(true);
    5492             : 
    5493             :         // Write projection attributes.
    5494          77 :         if (bWriteGridMapping)
    5495             :         {
    5496          64 :             const int NCDFVarID = NCDFWriteSRSVariable(
    5497             :                 cdfid, &oSRS, &pszCFProjection, bWriteGDALTags);
    5498          64 :             if (NCDFVarID < 0)
    5499           0 :                 return CE_Failure;
    5500             : 
    5501             :             // Optional GDAL custom projection tags.
    5502          64 :             if (bWriteGDALTags)
    5503             :             {
    5504         128 :                 CPLString osGeoTransform;
    5505         448 :                 for (int i = 0; i < 6; i++)
    5506             :                 {
    5507             :                     osGeoTransform +=
    5508         384 :                         CPLSPrintf("%.16g ", m_adfGeoTransform[i]);
    5509             :                 }
    5510          64 :                 CPLDebug("GDAL_netCDF", "szGeoTransform = %s",
    5511             :                          osGeoTransform.c_str());
    5512             : 
    5513             :                 // if( strlen(pszProj4Defn) > 0 ) {
    5514             :                 //     nc_put_att_text(cdfid, NCDFVarID, "proj4",
    5515             :                 //                      strlen(pszProj4Defn), pszProj4Defn);
    5516             :                 // }
    5517             : 
    5518             :                 // For now, write the geotransform for back-compat or else
    5519             :                 // the old (1.8.1) driver overrides the CF geotransform with
    5520             :                 // empty values from dfNN, dfSN, dfEE, dfWE;
    5521             : 
    5522             :                 // TODO: fix this in 1.8 branch, and then remove this here.
    5523          64 :                 if (bWriteGeoTransform && m_bHasGeoTransform)
    5524             :                 {
    5525             :                     {
    5526          63 :                         const int status = nc_put_att_text(
    5527             :                             cdfid, NCDFVarID, NCDF_GEOTRANSFORM,
    5528             :                             osGeoTransform.size(), osGeoTransform.c_str());
    5529          63 :                         NCDF_ERR(status);
    5530             :                     }
    5531             :                 }
    5532             :             }
    5533             : 
    5534             :             // Write projection variable to band variable.
    5535             :             // Need to call later if there are no bands.
    5536          64 :             AddGridMappingRef();
    5537             :         }  // end if( bWriteGridMapping )
    5538             : 
    5539             :         // Write CF Projection vars.
    5540             : 
    5541          77 :         const bool bIsRotatedPole =
    5542         141 :             pszCFProjection != nullptr &&
    5543          64 :             EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
    5544          77 :         if (bIsRotatedPole)
    5545             :         {
    5546             :             // Rename dims to rlat/rlon.
    5547             :             papszDimName
    5548           0 :                 .Clear();  // If we add other dims one day, this has to change
    5549           0 :             papszDimName.AddString(NCDF_DIMNAME_RLAT);
    5550           0 :             papszDimName.AddString(NCDF_DIMNAME_RLON);
    5551             : 
    5552           0 :             int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_RLAT);
    5553           0 :             NCDF_ERR(status);
    5554           0 :             status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_RLON);
    5555           0 :             NCDF_ERR(status);
    5556             :         }
    5557             :         // Rename dimensions if lon/lat.
    5558          77 :         else if (!bIsProjected && !bHasGeoloc)
    5559             :         {
    5560             :             // Rename dims to lat/lon.
    5561             :             papszDimName
    5562          51 :                 .Clear();  // If we add other dims one day, this has to change
    5563          51 :             papszDimName.AddString(NCDF_DIMNAME_LAT);
    5564          51 :             papszDimName.AddString(NCDF_DIMNAME_LON);
    5565             : 
    5566          51 :             int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_LAT);
    5567          51 :             NCDF_ERR(status);
    5568          51 :             status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_LON);
    5569          51 :             NCDF_ERR(status);
    5570             :         }
    5571             : 
    5572             :         // Write X/Y attributes.
    5573             :         else /* if( bIsProjected || bHasGeoloc ) */
    5574             :         {
    5575             :             // X
    5576             :             int anXDims[1];
    5577          26 :             anXDims[0] = nXDimID;
    5578          26 :             CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
    5579             :                      CF_PROJ_X_VAR_NAME, NC_DOUBLE);
    5580          26 :             int status = nc_def_var(cdfid, CF_PROJ_X_VAR_NAME, NC_DOUBLE, 1,
    5581             :                                     anXDims, &nVarXID);
    5582          26 :             NCDF_ERR(status);
    5583             : 
    5584             :             // Y
    5585             :             int anYDims[1];
    5586          26 :             anYDims[0] = nYDimID;
    5587          26 :             CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
    5588             :                      CF_PROJ_Y_VAR_NAME, NC_DOUBLE);
    5589          26 :             status = nc_def_var(cdfid, CF_PROJ_Y_VAR_NAME, NC_DOUBLE, 1,
    5590             :                                 anYDims, &nVarYID);
    5591          26 :             NCDF_ERR(status);
    5592             : 
    5593          26 :             if (bIsProjected)
    5594             :             {
    5595          22 :                 NCDFWriteXYVarsAttributes(this->vcdf, nVarXID, nVarYID, &oSRS);
    5596             :             }
    5597             :             else
    5598             :             {
    5599           4 :                 CPLAssert(bHasGeoloc);
    5600             :                 try
    5601             :                 {
    5602           4 :                     vcdf.nc_put_vatt_text(nVarXID, CF_AXIS, CF_SG_X_AXIS);
    5603           4 :                     vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME,
    5604             :                                           "x-coordinate in Cartesian system");
    5605           4 :                     vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, "m");
    5606           4 :                     vcdf.nc_put_vatt_text(nVarYID, CF_AXIS, CF_SG_Y_AXIS);
    5607           4 :                     vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME,
    5608             :                                           "y-coordinate in Cartesian system");
    5609           4 :                     vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, "m");
    5610             : 
    5611           4 :                     pszCFCoordinates = NCDF_LONLAT;
    5612             :                 }
    5613           0 :                 catch (nccfdriver::SG_Exception &e)
    5614             :                 {
    5615           0 :                     CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
    5616           0 :                     return CE_Failure;
    5617             :                 }
    5618             :             }
    5619             :         }
    5620             : 
    5621             :         // Write lat/lon attributes if needed.
    5622          77 :         if (bWriteLonLat)
    5623             :         {
    5624          57 :             int *panLatDims = nullptr;
    5625          57 :             int *panLonDims = nullptr;
    5626          57 :             int nLatDims = -1;
    5627          57 :             int nLonDims = -1;
    5628             : 
    5629             :             // Get information.
    5630          57 :             if (bHasGeoloc)
    5631             :             {
    5632             :                 // Geoloc
    5633           5 :                 nLatDims = 2;
    5634             :                 panLatDims =
    5635           5 :                     static_cast<int *>(CPLCalloc(nLatDims, sizeof(int)));
    5636           5 :                 panLatDims[0] = nYDimID;
    5637           5 :                 panLatDims[1] = nXDimID;
    5638           5 :                 nLonDims = 2;
    5639             :                 panLonDims =
    5640           5 :                     static_cast<int *>(CPLCalloc(nLonDims, sizeof(int)));
    5641           5 :                 panLonDims[0] = nYDimID;
    5642           5 :                 panLonDims[1] = nXDimID;
    5643             :             }
    5644          52 :             else if (bIsProjected)
    5645             :             {
    5646             :                 // Projected
    5647           1 :                 nLatDims = 2;
    5648             :                 panLatDims =
    5649           1 :                     static_cast<int *>(CPLCalloc(nLatDims, sizeof(int)));
    5650           1 :                 panLatDims[0] = nYDimID;
    5651           1 :                 panLatDims[1] = nXDimID;
    5652           1 :                 nLonDims = 2;
    5653             :                 panLonDims =
    5654           1 :                     static_cast<int *>(CPLCalloc(nLonDims, sizeof(int)));
    5655           1 :                 panLonDims[0] = nYDimID;
    5656           1 :                 panLonDims[1] = nXDimID;
    5657             :             }
    5658             :             else
    5659             :             {
    5660             :                 // Geographic
    5661          51 :                 nLatDims = 1;
    5662             :                 panLatDims =
    5663          51 :                     static_cast<int *>(CPLCalloc(nLatDims, sizeof(int)));
    5664          51 :                 panLatDims[0] = nYDimID;
    5665          51 :                 nLonDims = 1;
    5666             :                 panLonDims =
    5667          51 :                     static_cast<int *>(CPLCalloc(nLonDims, sizeof(int)));
    5668          51 :                 panLonDims[0] = nXDimID;
    5669             :             }
    5670             : 
    5671          57 :             nc_type eLonLatType = NC_NAT;
    5672          57 :             if (bIsProjected)
    5673             :             {
    5674           2 :                 eLonLatType = NC_FLOAT;
    5675           4 :                 const char *pszValue = CSLFetchNameValueDef(
    5676           2 :                     papszCreationOptions, "TYPE_LONLAT", "FLOAT");
    5677           2 :                 if (EQUAL(pszValue, "DOUBLE"))
    5678           0 :                     eLonLatType = NC_DOUBLE;
    5679             :             }
    5680             :             else
    5681             :             {
    5682          55 :                 eLonLatType = NC_DOUBLE;
    5683         110 :                 const char *pszValue = CSLFetchNameValueDef(
    5684          55 :                     papszCreationOptions, "TYPE_LONLAT", "DOUBLE");
    5685          55 :                 if (EQUAL(pszValue, "FLOAT"))
    5686           0 :                     eLonLatType = NC_FLOAT;
    5687             :             }
    5688             : 
    5689             :             // Def vars and attributes.
    5690             :             {
    5691          57 :                 const char *pszVarName =
    5692          57 :                     bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME;
    5693          57 :                 int status = nc_def_var(cdfid, pszVarName, eLonLatType,
    5694             :                                         nLatDims, panLatDims, &nVarLatID);
    5695          57 :                 CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
    5696             :                          cdfid, pszVarName, eLonLatType, nLatDims, nVarLatID);
    5697          57 :                 NCDF_ERR(status);
    5698          57 :                 DefVarDeflate(nVarLatID, false);  // Don't set chunking.
    5699             :             }
    5700             : 
    5701             :             {
    5702          57 :                 const char *pszVarName =
    5703          57 :                     bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME;
    5704          57 :                 int status = nc_def_var(cdfid, pszVarName, eLonLatType,
    5705             :                                         nLonDims, panLonDims, &nVarLonID);
    5706          57 :                 CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
    5707             :                          cdfid, pszVarName, eLonLatType, nLatDims, nVarLonID);
    5708          57 :                 NCDF_ERR(status);
    5709          57 :                 DefVarDeflate(nVarLonID, false);  // Don't set chunking.
    5710             :             }
    5711             : 
    5712          57 :             if (bIsRotatedPole)
    5713           0 :                 NCDFWriteRLonRLatVarsAttributes(this->vcdf, nVarLonID,
    5714             :                                                 nVarLatID);
    5715             :             else
    5716          57 :                 NCDFWriteLonLatVarsAttributes(this->vcdf, nVarLonID, nVarLatID);
    5717             : 
    5718          57 :             CPLFree(panLatDims);
    5719          57 :             CPLFree(panLonDims);
    5720             :         }
    5721             :     }
    5722             : 
    5723         154 :     if (!bDefsOnly)
    5724             :     {
    5725          77 :         m_bAddedProjectionVarsData = true;
    5726             : 
    5727          77 :         int nVarXID = -1;
    5728          77 :         int nVarYID = -1;
    5729             : 
    5730          77 :         nc_inq_varid(cdfid, CF_PROJ_X_VAR_NAME, &nVarXID);
    5731          77 :         nc_inq_varid(cdfid, CF_PROJ_Y_VAR_NAME, &nVarYID);
    5732             : 
    5733          77 :         int nVarLonID = -1;
    5734          77 :         int nVarLatID = -1;
    5735             : 
    5736          77 :         const bool bIsRotatedPole =
    5737         141 :             pszCFProjection != nullptr &&
    5738          64 :             EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
    5739          77 :         nc_inq_varid(cdfid,
    5740             :                      bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME,
    5741             :                      &nVarLonID);
    5742          77 :         nc_inq_varid(cdfid,
    5743             :                      bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME,
    5744             :                      &nVarLatID);
    5745             : 
    5746             :         // Get projection values.
    5747             : 
    5748          77 :         double *padLonVal = nullptr;
    5749          77 :         double *padLatVal = nullptr;
    5750             : 
    5751          77 :         if (bIsProjected)
    5752             :         {
    5753          22 :             OGRSpatialReference *poLatLonSRS = nullptr;
    5754          22 :             OGRCoordinateTransformation *poTransform = nullptr;
    5755             : 
    5756             :             size_t startX[1];
    5757             :             size_t countX[1];
    5758             :             size_t startY[1];
    5759             :             size_t countY[1];
    5760             : 
    5761          22 :             CPLDebug("GDAL_netCDF", "Getting (X,Y) values");
    5762             : 
    5763             :             double *padXVal =
    5764          22 :                 static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
    5765             :             double *padYVal =
    5766          22 :                 static_cast<double *>(CPLMalloc(nRasterYSize * sizeof(double)));
    5767             : 
    5768             :             // Get Y values.
    5769          22 :             const double dfY0 = (!bBottomUp) ? m_adfGeoTransform[3] :
    5770             :                                              // Invert latitude values.
    5771          22 :                                     m_adfGeoTransform[3] +
    5772          22 :                                         (m_adfGeoTransform[5] * nRasterYSize);
    5773          22 :             const double dfDY = m_adfGeoTransform[5];
    5774             : 
    5775        1393 :             for (int j = 0; j < nRasterYSize; j++)
    5776             :             {
    5777             :                 // The data point is centered inside the pixel.
    5778        1371 :                 if (!bBottomUp)
    5779           0 :                     padYVal[j] = dfY0 + (j + 0.5) * dfDY;
    5780             :                 else  // Invert latitude values.
    5781        1371 :                     padYVal[j] = dfY0 - (j + 0.5) * dfDY;
    5782             :             }
    5783          22 :             startX[0] = 0;
    5784          22 :             countX[0] = nRasterXSize;
    5785             : 
    5786             :             // Get X values.
    5787          22 :             const double dfX0 = m_adfGeoTransform[0];
    5788          22 :             const double dfDX = m_adfGeoTransform[1];
    5789             : 
    5790        1414 :             for (int i = 0; i < nRasterXSize; i++)
    5791             :             {
    5792             :                 // The data point is centered inside the pixel.
    5793        1392 :                 padXVal[i] = dfX0 + (i + 0.5) * dfDX;
    5794             :             }
    5795          22 :             startY[0] = 0;
    5796          22 :             countY[0] = nRasterYSize;
    5797             : 
    5798             :             // Write X/Y values.
    5799             : 
    5800             :             // Make sure we are in data mode.
    5801          22 :             SetDefineMode(false);
    5802             : 
    5803          22 :             CPLDebug("GDAL_netCDF", "Writing X values");
    5804             :             int status =
    5805          22 :                 nc_put_vara_double(cdfid, nVarXID, startX, countX, padXVal);
    5806          22 :             NCDF_ERR(status);
    5807             : 
    5808          22 :             CPLDebug("GDAL_netCDF", "Writing Y values");
    5809             :             status =
    5810          22 :                 nc_put_vara_double(cdfid, nVarYID, startY, countY, padYVal);
    5811          22 :             NCDF_ERR(status);
    5812             : 
    5813          22 :             if (pfnProgress)
    5814          19 :                 pfnProgress(0.20, nullptr, pProgressData);
    5815             : 
    5816             :             // Write lon/lat arrays (CF coordinates) if requested.
    5817             : 
    5818             :             // Get OGR transform if GEOLOCATION is not available.
    5819          22 :             if (bWriteLonLat && !bHasGeoloc)
    5820             :             {
    5821           1 :                 poLatLonSRS = m_oSRS.CloneGeogCS();
    5822           1 :                 if (poLatLonSRS != nullptr)
    5823             :                 {
    5824           1 :                     poLatLonSRS->SetAxisMappingStrategy(
    5825             :                         OAMS_TRADITIONAL_GIS_ORDER);
    5826             :                     poTransform =
    5827           1 :                         OGRCreateCoordinateTransformation(&m_oSRS, poLatLonSRS);
    5828             :                 }
    5829             :                 // If no OGR transform, then don't write CF lon/lat.
    5830           1 :                 if (poTransform == nullptr)
    5831             :                 {
    5832           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    5833             :                              "Unable to get Coordinate Transform");
    5834           0 :                     bWriteLonLat = false;
    5835             :                 }
    5836             :             }
    5837             : 
    5838          22 :             if (bWriteLonLat)
    5839             :             {
    5840           2 :                 if (!bHasGeoloc)
    5841           1 :                     CPLDebug("GDAL_netCDF", "Transforming (X,Y)->(lon,lat)");
    5842             :                 else
    5843           1 :                     CPLDebug("GDAL_netCDF",
    5844             :                              "Writing (lon,lat) from GEOLOCATION arrays");
    5845             : 
    5846           2 :                 bool bOK = true;
    5847           2 :                 double dfProgress = 0.2;
    5848             : 
    5849           2 :                 size_t start[] = {0, 0};
    5850           2 :                 size_t count[] = {1, (size_t)nRasterXSize};
    5851             :                 padLatVal = static_cast<double *>(
    5852           2 :                     CPLMalloc(nRasterXSize * sizeof(double)));
    5853             :                 padLonVal = static_cast<double *>(
    5854           2 :                     CPLMalloc(nRasterXSize * sizeof(double)));
    5855             : 
    5856          61 :                 for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR;
    5857             :                      j++)
    5858             :                 {
    5859          59 :                     start[0] = j;
    5860             : 
    5861             :                     // Get values from geotransform.
    5862          59 :                     if (!bHasGeoloc)
    5863             :                     {
    5864             :                         // Fill values to transform.
    5865         420 :                         for (int i = 0; i < nRasterXSize; i++)
    5866             :                         {
    5867         400 :                             padLatVal[i] = padYVal[j];
    5868         400 :                             padLonVal[i] = padXVal[i];
    5869             :                         }
    5870             : 
    5871             :                         // Do the transform.
    5872          20 :                         bOK = CPL_TO_BOOL(poTransform->Transform(
    5873          20 :                             nRasterXSize, padLonVal, padLatVal, nullptr));
    5874          20 :                         if (!bOK)
    5875             :                         {
    5876           0 :                             CPLError(CE_Failure, CPLE_AppDefined,
    5877             :                                      "Unable to Transform (X,Y) to (lon,lat).");
    5878             :                         }
    5879             :                     }
    5880             :                     // Get values from geoloc arrays.
    5881             :                     else
    5882             :                     {
    5883          39 :                         CPLErr eErr = GDALRasterIO(
    5884             :                             hBand_Y, GF_Read, 0, j, nRasterXSize, 1, padLatVal,
    5885             :                             nRasterXSize, 1, GDT_Float64, 0, 0);
    5886          39 :                         if (eErr == CE_None)
    5887             :                         {
    5888          39 :                             eErr = GDALRasterIO(
    5889             :                                 hBand_X, GF_Read, 0, j, nRasterXSize, 1,
    5890             :                                 padLonVal, nRasterXSize, 1, GDT_Float64, 0, 0);
    5891             :                         }
    5892             : 
    5893          39 :                         if (eErr == CE_None)
    5894             :                         {
    5895          39 :                             bOK = true;
    5896             :                         }
    5897             :                         else
    5898             :                         {
    5899           0 :                             bOK = false;
    5900           0 :                             CPLError(CE_Failure, CPLE_AppDefined,
    5901             :                                      "Unable to get scanline %d", j);
    5902             :                         }
    5903             :                     }
    5904             : 
    5905             :                     // Write data.
    5906          59 :                     if (bOK)
    5907             :                     {
    5908          59 :                         status = nc_put_vara_double(cdfid, nVarLatID, start,
    5909             :                                                     count, padLatVal);
    5910          59 :                         NCDF_ERR(status);
    5911          59 :                         status = nc_put_vara_double(cdfid, nVarLonID, start,
    5912             :                                                     count, padLonVal);
    5913          59 :                         NCDF_ERR(status);
    5914             :                     }
    5915             : 
    5916          59 :                     if (pfnProgress && (nRasterYSize / 10) > 0 &&
    5917          59 :                         (j % (nRasterYSize / 10) == 0))
    5918             :                     {
    5919          23 :                         dfProgress += 0.08;
    5920          23 :                         pfnProgress(dfProgress, nullptr, pProgressData);
    5921             :                     }
    5922             :                 }
    5923             :             }
    5924             : 
    5925          22 :             if (poLatLonSRS != nullptr)
    5926           1 :                 delete poLatLonSRS;
    5927          22 :             if (poTransform != nullptr)
    5928           1 :                 delete poTransform;
    5929             : 
    5930          22 :             CPLFree(padXVal);
    5931          22 :             CPLFree(padYVal);
    5932             :         }  // Projected
    5933             : 
    5934             :         // If not projected/geographic and has geoloc
    5935          55 :         else if (!bIsGeographic && bHasGeoloc)
    5936             :         {
    5937             :             // Use
    5938             :             // https://cfconventions.org/Data/cf-conventions/cf-conventions-1.9/cf-conventions.html#_two_dimensional_latitude_longitude_coordinate_variables
    5939             : 
    5940           4 :             bool bOK = true;
    5941           4 :             double dfProgress = 0.2;
    5942             : 
    5943             :             // Make sure we are in data mode.
    5944           4 :             SetDefineMode(false);
    5945             : 
    5946             :             size_t startX[1];
    5947             :             size_t countX[1];
    5948             :             size_t startY[1];
    5949             :             size_t countY[1];
    5950           4 :             startX[0] = 0;
    5951           4 :             countX[0] = nRasterXSize;
    5952             : 
    5953           4 :             startY[0] = 0;
    5954           4 :             countY[0] = nRasterYSize;
    5955             : 
    5956           8 :             std::vector<double> adfXVal(nRasterXSize);
    5957          16 :             for (int i = 0; i < nRasterXSize; i++)
    5958          12 :                 adfXVal[i] = i;
    5959             : 
    5960           8 :             std::vector<double> adfYVal(nRasterYSize);
    5961          12 :             for (int i = 0; i < nRasterYSize; i++)
    5962           8 :                 adfYVal[i] = bBottomUp ? nRasterYSize - 1 - i : i;
    5963             : 
    5964           4 :             CPLDebug("GDAL_netCDF", "Writing X values");
    5965           4 :             int status = nc_put_vara_double(cdfid, nVarXID, startX, countX,
    5966           4 :                                             adfXVal.data());
    5967           4 :             NCDF_ERR(status);
    5968             : 
    5969           4 :             CPLDebug("GDAL_netCDF", "Writing Y values");
    5970           4 :             status = nc_put_vara_double(cdfid, nVarYID, startY, countY,
    5971           4 :                                         adfYVal.data());
    5972           4 :             NCDF_ERR(status);
    5973             : 
    5974           4 :             if (pfnProgress)
    5975           0 :                 pfnProgress(0.20, nullptr, pProgressData);
    5976             : 
    5977           4 :             size_t start[] = {0, 0};
    5978           4 :             size_t count[] = {1, (size_t)nRasterXSize};
    5979             :             padLatVal =
    5980           4 :                 static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
    5981             :             padLonVal =
    5982           4 :                 static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
    5983             : 
    5984          12 :             for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR; j++)
    5985             :             {
    5986           8 :                 start[0] = j;
    5987             : 
    5988           8 :                 CPLErr eErr = GDALRasterIO(hBand_Y, GF_Read, 0,
    5989           8 :                                            bBottomUp ? nRasterYSize - 1 - j : j,
    5990             :                                            nRasterXSize, 1, padLatVal,
    5991             :                                            nRasterXSize, 1, GDT_Float64, 0, 0);
    5992           8 :                 if (eErr == CE_None)
    5993             :                 {
    5994           8 :                     eErr = GDALRasterIO(hBand_X, GF_Read, 0,
    5995           8 :                                         bBottomUp ? nRasterYSize - 1 - j : j,
    5996             :                                         nRasterXSize, 1, padLonVal,
    5997             :                                         nRasterXSize, 1, GDT_Float64, 0, 0);
    5998             :                 }
    5999             : 
    6000           8 :                 if (eErr == CE_None)
    6001             :                 {
    6002           8 :                     bOK = true;
    6003             :                 }
    6004             :                 else
    6005             :                 {
    6006           0 :                     bOK = false;
    6007           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    6008             :                              "Unable to get scanline %d", j);
    6009             :                 }
    6010             : 
    6011             :                 // Write data.
    6012           8 :                 if (bOK)
    6013             :                 {
    6014           8 :                     status = nc_put_vara_double(cdfid, nVarLatID, start, count,
    6015             :                                                 padLatVal);
    6016           8 :                     NCDF_ERR(status);
    6017           8 :                     status = nc_put_vara_double(cdfid, nVarLonID, start, count,
    6018             :                                                 padLonVal);
    6019           8 :                     NCDF_ERR(status);
    6020             :                 }
    6021             : 
    6022           8 :                 if (pfnProgress && (nRasterYSize / 10) > 0 &&
    6023           0 :                     (j % (nRasterYSize / 10) == 0))
    6024             :                 {
    6025           0 :                     dfProgress += 0.08;
    6026           0 :                     pfnProgress(dfProgress, nullptr, pProgressData);
    6027             :                 }
    6028           4 :             }
    6029             :         }
    6030             : 
    6031             :         // If not projected, assume geographic to catch grids without Datum.
    6032          51 :         else if (bWriteLonLat)
    6033             :         {
    6034             :             // Get latitude values.
    6035          51 :             const double dfY0 = (!bBottomUp) ? m_adfGeoTransform[3] :
    6036             :                                              // Invert latitude values.
    6037          51 :                                     m_adfGeoTransform[3] +
    6038          51 :                                         (m_adfGeoTransform[5] * nRasterYSize);
    6039          51 :             const double dfDY = m_adfGeoTransform[5];
    6040             : 
    6041             :             // Override lat values with the ones in GEOLOCATION/Y_VALUES.
    6042          51 :             if (netCDFDataset::GetMetadataItem("Y_VALUES", "GEOLOCATION") !=
    6043             :                 nullptr)
    6044             :             {
    6045           0 :                 int nTemp = 0;
    6046           0 :                 padLatVal = Get1DGeolocation("Y_VALUES", nTemp);
    6047             :                 // Make sure we got the correct amount, if not fallback to GT */
    6048             :                 // could add test fabs(fabs(padLatVal[0]) - fabs(dfY0)) <= 0.1))
    6049           0 :                 if (nTemp == nRasterYSize)
    6050             :                 {
    6051           0 :                     CPLDebug(
    6052             :                         "GDAL_netCDF",
    6053             :                         "Using Y_VALUES geolocation metadata for lat values");
    6054             :                 }
    6055             :                 else
    6056             :                 {
    6057           0 :                     CPLDebug("GDAL_netCDF",
    6058             :                              "Got %d elements from Y_VALUES geolocation "
    6059             :                              "metadata, need %d",
    6060             :                              nTemp, nRasterYSize);
    6061           0 :                     if (padLatVal)
    6062             :                     {
    6063           0 :                         CPLFree(padLatVal);
    6064           0 :                         padLatVal = nullptr;
    6065             :                     }
    6066             :                 }
    6067             :             }
    6068             : 
    6069          51 :             if (padLatVal == nullptr)
    6070             :             {
    6071             :                 padLatVal = static_cast<double *>(
    6072          51 :                     CPLMalloc(nRasterYSize * sizeof(double)));
    6073        3375 :                 for (int i = 0; i < nRasterYSize; i++)
    6074             :                 {
    6075             :                     // The data point is centered inside the pixel.
    6076        3324 :                     if (!bBottomUp)
    6077           0 :                         padLatVal[i] = dfY0 + (i + 0.5) * dfDY;
    6078             :                     else  // Invert latitude values.
    6079        3324 :                         padLatVal[i] = dfY0 - (i + 0.5) * dfDY;
    6080             :                 }
    6081             :             }
    6082             : 
    6083          51 :             size_t startLat[1] = {0};
    6084          51 :             size_t countLat[1] = {static_cast<size_t>(nRasterYSize)};
    6085             : 
    6086             :             // Get longitude values.
    6087          51 :             const double dfX0 = m_adfGeoTransform[0];
    6088          51 :             const double dfDX = m_adfGeoTransform[1];
    6089             : 
    6090             :             padLonVal =
    6091          51 :                 static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
    6092        3427 :             for (int i = 0; i < nRasterXSize; i++)
    6093             :             {
    6094             :                 // The data point is centered inside the pixel.
    6095        3376 :                 padLonVal[i] = dfX0 + (i + 0.5) * dfDX;
    6096             :             }
    6097             : 
    6098          51 :             size_t startLon[1] = {0};
    6099          51 :             size_t countLon[1] = {static_cast<size_t>(nRasterXSize)};
    6100             : 
    6101             :             // Write latitude and longitude values.
    6102             : 
    6103             :             // Make sure we are in data mode.
    6104          51 :             SetDefineMode(false);
    6105             : 
    6106             :             // Write values.
    6107          51 :             CPLDebug("GDAL_netCDF", "Writing lat values");
    6108             : 
    6109          51 :             int status = nc_put_vara_double(cdfid, nVarLatID, startLat,
    6110             :                                             countLat, padLatVal);
    6111          51 :             NCDF_ERR(status);
    6112             : 
    6113          51 :             CPLDebug("GDAL_netCDF", "Writing lon values");
    6114          51 :             status = nc_put_vara_double(cdfid, nVarLonID, startLon, countLon,
    6115             :                                         padLonVal);
    6116          51 :             NCDF_ERR(status);
    6117             : 
    6118             :         }  // Not projected.
    6119             : 
    6120          77 :         CPLFree(padLatVal);
    6121          77 :         CPLFree(padLonVal);
    6122             : 
    6123          77 :         if (pfnProgress)
    6124          37 :             pfnProgress(1.00, nullptr, pProgressData);
    6125             :     }
    6126             : 
    6127         154 :     if (hDS_X != nullptr)
    6128             :     {
    6129          10 :         GDALClose(hDS_X);
    6130             :     }
    6131         154 :     if (hDS_Y != nullptr)
    6132             :     {
    6133          10 :         GDALClose(hDS_Y);
    6134             :     }
    6135             : 
    6136         154 :     return CE_None;
    6137             : }
    6138             : 
    6139             : // Write Projection variable to band variable.
    6140             : // Moved from AddProjectionVars() for cases when bands are added after
    6141             : // projection.
    6142         356 : bool netCDFDataset::AddGridMappingRef()
    6143             : {
    6144         356 :     bool bRet = true;
    6145         356 :     bool bOldDefineMode = bDefineMode;
    6146             : 
    6147         531 :     if ((GetAccess() == GA_Update) && (nBands >= 1) && (GetRasterBand(1)) &&
    6148         175 :         ((pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, "")) ||
    6149         169 :          (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))))
    6150             :     {
    6151          68 :         bAddedGridMappingRef = true;
    6152             : 
    6153             :         // Make sure we are in define mode.
    6154          68 :         SetDefineMode(true);
    6155             : 
    6156         182 :         for (int i = 1; i <= nBands; i++)
    6157             :         {
    6158             :             const int nVarId =
    6159         114 :                 static_cast<netCDFRasterBand *>(GetRasterBand(i))->nZId;
    6160             : 
    6161         114 :             if (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))
    6162             :             {
    6163             :                 int status =
    6164         220 :                     nc_put_att_text(cdfid, nVarId, CF_GRD_MAPPING,
    6165         110 :                                     strlen(pszCFProjection), pszCFProjection);
    6166         110 :                 NCDF_ERR(status);
    6167         110 :                 if (status != NC_NOERR)
    6168           0 :                     bRet = false;
    6169             :             }
    6170         114 :             if (pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, ""))
    6171             :             {
    6172             :                 int status =
    6173           6 :                     nc_put_att_text(cdfid, nVarId, CF_COORDINATES,
    6174             :                                     strlen(pszCFCoordinates), pszCFCoordinates);
    6175           6 :                 NCDF_ERR(status);
    6176           6 :                 if (status != NC_NOERR)
    6177           0 :                     bRet = false;
    6178             :             }
    6179             :         }
    6180             : 
    6181             :         // Go back to previous define mode.
    6182          68 :         SetDefineMode(bOldDefineMode);
    6183             :     }
    6184         356 :     return bRet;
    6185             : }
    6186             : 
    6187             : /************************************************************************/
    6188             : /*                          GetGeoTransform()                           */
    6189             : /************************************************************************/
    6190             : 
    6191         107 : CPLErr netCDFDataset::GetGeoTransform(double *padfTransform)
    6192             : 
    6193             : {
    6194         107 :     memcpy(padfTransform, m_adfGeoTransform, sizeof(double) * 6);
    6195         107 :     if (m_bHasGeoTransform)
    6196          77 :         return CE_None;
    6197             : 
    6198          30 :     return GDALPamDataset::GetGeoTransform(padfTransform);
    6199             : }
    6200             : 
    6201             : /************************************************************************/
    6202             : /*                                rint()                                */
    6203             : /************************************************************************/
    6204             : 
    6205           0 : double netCDFDataset::rint(double dfX)
    6206             : {
    6207           0 :     return std::round(dfX);
    6208             : }
    6209             : 
    6210             : /************************************************************************/
    6211             : /*                          NCDFReadIsoMetadata()                       */
    6212             : /************************************************************************/
    6213             : 
    6214          16 : static void NCDFReadMetadataAsJson(int cdfid, CPLJSONObject &obj)
    6215             : {
    6216          16 :     int nbAttr = 0;
    6217          16 :     NCDF_ERR(nc_inq_varnatts(cdfid, NC_GLOBAL, &nbAttr));
    6218             : 
    6219          32 :     std::map<std::string, CPLJSONArray> oMapNameToArray;
    6220          40 :     for (int l = 0; l < nbAttr; l++)
    6221             :     {
    6222             :         char szAttrName[NC_MAX_NAME + 1];
    6223          24 :         szAttrName[0] = 0;
    6224          24 :         NCDF_ERR(nc_inq_attname(cdfid, NC_GLOBAL, l, szAttrName));
    6225             : 
    6226          24 :         char *pszMetaValue = nullptr;
    6227          24 :         if (NCDFGetAttr(cdfid, NC_GLOBAL, szAttrName, &pszMetaValue) == CE_None)
    6228             :         {
    6229          24 :             nc_type nAttrType = NC_NAT;
    6230          24 :             size_t nAttrLen = 0;
    6231             : 
    6232          24 :             NCDF_ERR(nc_inq_att(cdfid, NC_GLOBAL, szAttrName, &nAttrType,
    6233             :                                 &nAttrLen));
    6234             : 
    6235          24 :             std::string osAttrName(szAttrName);
    6236          24 :             const auto sharpPos = osAttrName.find('#');
    6237          24 :             if (sharpPos == std::string::npos)
    6238             :             {
    6239          16 :                 if (nAttrType == NC_DOUBLE || nAttrType == NC_FLOAT)
    6240           4 :                     obj.Add(osAttrName, CPLAtof(pszMetaValue));
    6241             :                 else
    6242          12 :                     obj.Add(osAttrName, pszMetaValue);
    6243             :             }
    6244             :             else
    6245             :             {
    6246           8 :                 osAttrName.resize(sharpPos);
    6247           8 :                 auto iter = oMapNameToArray.find(osAttrName);
    6248           8 :                 if (iter == oMapNameToArray.end())
    6249             :                 {
    6250           8 :                     CPLJSONArray array;
    6251           4 :                     obj.Add(osAttrName, array);
    6252           4 :                     oMapNameToArray[osAttrName] = array;
    6253           4 :                     array.Add(pszMetaValue);
    6254             :                 }
    6255             :                 else
    6256             :                 {
    6257           4 :                     iter->second.Add(pszMetaValue);
    6258             :                 }
    6259             :             }
    6260          24 :             CPLFree(pszMetaValue);
    6261          24 :             pszMetaValue = nullptr;
    6262             :         }
    6263             :     }
    6264             : 
    6265          16 :     int nSubGroups = 0;
    6266          16 :     int *panSubGroupIds = nullptr;
    6267          16 :     NCDFGetSubGroups(cdfid, &nSubGroups, &panSubGroupIds);
    6268          16 :     oMapNameToArray.clear();
    6269          28 :     for (int i = 0; i < nSubGroups; i++)
    6270             :     {
    6271          24 :         CPLJSONObject subObj;
    6272          12 :         NCDFReadMetadataAsJson(panSubGroupIds[i], subObj);
    6273             : 
    6274          24 :         std::string osGroupName;
    6275          12 :         osGroupName.resize(NC_MAX_NAME);
    6276          12 :         NCDF_ERR(nc_inq_grpname(panSubGroupIds[i], &osGroupName[0]));
    6277          12 :         osGroupName.resize(strlen(osGroupName.data()));
    6278          12 :         const auto sharpPos = osGroupName.find('#');
    6279          12 :         if (sharpPos == std::string::npos)
    6280             :         {
    6281           4 :             obj.Add(osGroupName, subObj);
    6282             :         }
    6283             :         else
    6284             :         {
    6285           8 :             osGroupName.resize(sharpPos);
    6286           8 :             auto iter = oMapNameToArray.find(osGroupName);
    6287           8 :             if (iter == oMapNameToArray.end())
    6288             :             {
    6289           8 :                 CPLJSONArray array;
    6290           4 :                 obj.Add(osGroupName, array);
    6291           4 :                 oMapNameToArray[osGroupName] = array;
    6292           4 :                 array.Add(subObj);
    6293             :             }
    6294             :             else
    6295             :             {
    6296           4 :                 iter->second.Add(subObj);
    6297             :             }
    6298             :         }
    6299             :     }
    6300          16 :     CPLFree(panSubGroupIds);
    6301          16 : }
    6302             : 
    6303           4 : std::string NCDFReadMetadataAsJson(int cdfid)
    6304             : {
    6305           8 :     CPLJSONDocument oDoc;
    6306           8 :     CPLJSONObject oRoot = oDoc.GetRoot();
    6307           4 :     NCDFReadMetadataAsJson(cdfid, oRoot);
    6308           8 :     return oDoc.SaveAsString();
    6309             : }
    6310             : 
    6311             : /************************************************************************/
    6312             : /*                        ReadAttributes()                              */
    6313             : /************************************************************************/
    6314        1723 : CPLErr netCDFDataset::ReadAttributes(int cdfidIn, int var)
    6315             : 
    6316             : {
    6317        1723 :     char *pszVarFullName = nullptr;
    6318        1723 :     ERR_RET(NCDFGetVarFullName(cdfidIn, var, &pszVarFullName));
    6319             : 
    6320             :     // For metadata in Sentinel 5
    6321        1723 :     if (STARTS_WITH(pszVarFullName, "/METADATA/"))
    6322             :     {
    6323           6 :         for (const char *key :
    6324             :              {"ISO_METADATA", "ESA_METADATA", "EOP_METADATA", "QA_STATISTICS",
    6325           8 :               "GRANULE_DESCRIPTION", "ALGORITHM_SETTINGS"})
    6326             :         {
    6327          14 :             if (var == NC_GLOBAL &&
    6328           7 :                 strcmp(pszVarFullName,
    6329             :                        CPLSPrintf("/METADATA/%s/NC_GLOBAL", key)) == 0)
    6330             :             {
    6331           1 :                 CPLFree(pszVarFullName);
    6332           1 :                 CPLStringList aosList;
    6333           2 :                 aosList.AddString(CPLString(NCDFReadMetadataAsJson(cdfidIn))
    6334           1 :                                       .replaceAll("\\/", '/'));
    6335           1 :                 m_oMapDomainToJSon[key] = std::move(aosList);
    6336           1 :                 return CE_None;
    6337             :             }
    6338             :         }
    6339             :     }
    6340        1722 :     if (STARTS_WITH(pszVarFullName, "/PRODUCT/SUPPORT_DATA/"))
    6341             :     {
    6342           0 :         CPLFree(pszVarFullName);
    6343           0 :         CPLStringList aosList;
    6344             :         aosList.AddString(
    6345           0 :             CPLString(NCDFReadMetadataAsJson(cdfidIn)).replaceAll("\\/", '/'));
    6346           0 :         m_oMapDomainToJSon["SUPPORT_DATA"] = std::move(aosList);
    6347           0 :         return CE_None;
    6348             :     }
    6349             : 
    6350        1722 :     size_t nMetaNameSize =
    6351        1722 :         sizeof(char) * (strlen(pszVarFullName) + 1 + NC_MAX_NAME + 1);
    6352        1722 :     char *pszMetaName = static_cast<char *>(CPLMalloc(nMetaNameSize));
    6353             : 
    6354        1722 :     int nbAttr = 0;
    6355        1722 :     NCDF_ERR(nc_inq_varnatts(cdfidIn, var, &nbAttr));
    6356             : 
    6357        8648 :     for (int l = 0; l < nbAttr; l++)
    6358             :     {
    6359             :         char szAttrName[NC_MAX_NAME + 1];
    6360        6926 :         szAttrName[0] = 0;
    6361        6926 :         NCDF_ERR(nc_inq_attname(cdfidIn, var, l, szAttrName));
    6362        6926 :         snprintf(pszMetaName, nMetaNameSize, "%s#%s", pszVarFullName,
    6363             :                  szAttrName);
    6364             : 
    6365        6926 :         char *pszMetaTemp = nullptr;
    6366        6926 :         if (NCDFGetAttr(cdfidIn, var, szAttrName, &pszMetaTemp) == CE_None)
    6367             :         {
    6368        6925 :             papszMetadata =
    6369        6925 :                 CSLSetNameValue(papszMetadata, pszMetaName, pszMetaTemp);
    6370        6925 :             CPLFree(pszMetaTemp);
    6371        6925 :             pszMetaTemp = nullptr;
    6372             :         }
    6373             :         else
    6374             :         {
    6375           1 :             CPLDebug("GDAL_netCDF", "invalid metadata %s", pszMetaName);
    6376             :         }
    6377             :     }
    6378             : 
    6379        1722 :     CPLFree(pszVarFullName);
    6380        1722 :     CPLFree(pszMetaName);
    6381             : 
    6382        1722 :     if (var == NC_GLOBAL)
    6383             :     {
    6384             :         // Recurse on sub-groups.
    6385         505 :         int nSubGroups = 0;
    6386         505 :         int *panSubGroupIds = nullptr;
    6387         505 :         NCDFGetSubGroups(cdfidIn, &nSubGroups, &panSubGroupIds);
    6388         528 :         for (int i = 0; i < nSubGroups; i++)
    6389             :         {
    6390          23 :             ReadAttributes(panSubGroupIds[i], var);
    6391             :         }
    6392         505 :         CPLFree(panSubGroupIds);
    6393             :     }
    6394             : 
    6395        1722 :     return CE_None;
    6396             : }
    6397             : 
    6398             : /************************************************************************/
    6399             : /*                netCDFDataset::CreateSubDatasetList()                 */
    6400             : /************************************************************************/
    6401          45 : void netCDFDataset::CreateSubDatasetList(int nGroupId)
    6402             : {
    6403             :     char szVarStdName[NC_MAX_NAME + 1];
    6404          45 :     int *ponDimIds = nullptr;
    6405             :     nc_type nAttype;
    6406             :     size_t nAttlen;
    6407             : 
    6408          45 :     netCDFDataset *poDS = this;
    6409             : 
    6410             :     int nVarCount;
    6411          45 :     nc_inq_nvars(nGroupId, &nVarCount);
    6412             : 
    6413         315 :     for (int nVar = 0; nVar < nVarCount; nVar++)
    6414             :     {
    6415             : 
    6416             :         int nDims;
    6417         270 :         nc_inq_varndims(nGroupId, nVar, &nDims);
    6418             : 
    6419         270 :         if (nDims >= 2)
    6420             :         {
    6421         151 :             ponDimIds = static_cast<int *>(CPLCalloc(nDims, sizeof(int)));
    6422         151 :             nc_inq_vardimid(nGroupId, nVar, ponDimIds);
    6423             : 
    6424             :             // Create Sub dataset list.
    6425         151 :             CPLString osDim;
    6426         468 :             for (int i = 0; i < nDims; i++)
    6427             :             {
    6428             :                 size_t nDimLen;
    6429         317 :                 nc_inq_dimlen(nGroupId, ponDimIds[i], &nDimLen);
    6430         317 :                 osDim += CPLSPrintf("%dx", (int)nDimLen);
    6431             :             }
    6432         151 :             CPLFree(ponDimIds);
    6433             : 
    6434             :             nc_type nVarType;
    6435         151 :             nc_inq_vartype(nGroupId, nVar, &nVarType);
    6436             :             // Get rid of the last "x" character.
    6437         151 :             osDim.resize(osDim.size() - 1);
    6438         151 :             const char *pszType = "";
    6439         151 :             switch (nVarType)
    6440             :             {
    6441          35 :                 case NC_BYTE:
    6442          35 :                     pszType = "8-bit integer";
    6443          35 :                     break;
    6444           2 :                 case NC_CHAR:
    6445           2 :                     pszType = "8-bit character";
    6446           2 :                     break;
    6447           6 :                 case NC_SHORT:
    6448           6 :                     pszType = "16-bit integer";
    6449           6 :                     break;
    6450           8 :                 case NC_INT:
    6451           8 :                     pszType = "32-bit integer";
    6452           8 :                     break;
    6453          51 :                 case NC_FLOAT:
    6454          51 :                     pszType = "32-bit floating-point";
    6455          51 :                     break;
    6456          31 :                 case NC_DOUBLE:
    6457          31 :                     pszType = "64-bit floating-point";
    6458          31 :                     break;
    6459           4 :                 case NC_UBYTE:
    6460           4 :                     pszType = "8-bit unsigned integer";
    6461           4 :                     break;
    6462           1 :                 case NC_USHORT:
    6463           1 :                     pszType = "16-bit unsigned integer";
    6464           1 :                     break;
    6465           1 :                 case NC_UINT:
    6466           1 :                     pszType = "32-bit unsigned integer";
    6467           1 :                     break;
    6468           1 :                 case NC_INT64:
    6469           1 :                     pszType = "64-bit integer";
    6470           1 :                     break;
    6471           1 :                 case NC_UINT64:
    6472           1 :                     pszType = "64-bit unsigned integer";
    6473           1 :                     break;
    6474          10 :                 default:
    6475          10 :                     break;
    6476             :             }
    6477             : 
    6478         151 :             char *pszName = nullptr;
    6479         151 :             if (NCDFGetVarFullName(nGroupId, nVar, &pszName) != CE_None)
    6480           0 :                 continue;
    6481             : 
    6482         151 :             nSubDatasets++;
    6483             : 
    6484         151 :             nAttlen = 0;
    6485         151 :             nc_inq_att(nGroupId, nVar, CF_STD_NAME, &nAttype, &nAttlen);
    6486         302 :             if (nAttlen < sizeof(szVarStdName) &&
    6487         151 :                 nc_get_att_text(nGroupId, nVar, CF_STD_NAME, szVarStdName) ==
    6488             :                     NC_NOERR)
    6489             :             {
    6490          51 :                 szVarStdName[nAttlen] = '\0';
    6491             :             }
    6492             :             else
    6493             :             {
    6494         100 :                 snprintf(szVarStdName, sizeof(szVarStdName), "%s", pszName);
    6495             :             }
    6496             : 
    6497             :             char szTemp[NC_MAX_NAME + 1];
    6498         151 :             snprintf(szTemp, sizeof(szTemp), "SUBDATASET_%d_NAME",
    6499             :                      nSubDatasets);
    6500             : 
    6501         151 :             if (strchr(pszName, ' ') || strchr(pszName, ':'))
    6502             :             {
    6503           1 :                 poDS->papszSubDatasets = CSLSetNameValue(
    6504             :                     poDS->papszSubDatasets, szTemp,
    6505             :                     CPLSPrintf("NETCDF:\"%s\":\"%s\"", poDS->osFilename.c_str(),
    6506             :                                pszName));
    6507             :             }
    6508             :             else
    6509             :             {
    6510         150 :                 poDS->papszSubDatasets = CSLSetNameValue(
    6511             :                     poDS->papszSubDatasets, szTemp,
    6512             :                     CPLSPrintf("NETCDF:\"%s\":%s", poDS->osFilename.c_str(),
    6513             :                                pszName));
    6514             :             }
    6515             : 
    6516         151 :             CPLFree(pszName);
    6517             : 
    6518         151 :             snprintf(szTemp, sizeof(szTemp), "SUBDATASET_%d_DESC",
    6519             :                      nSubDatasets);
    6520             : 
    6521         151 :             poDS->papszSubDatasets =
    6522         151 :                 CSLSetNameValue(poDS->papszSubDatasets, szTemp,
    6523             :                                 CPLSPrintf("[%s] %s (%s)", osDim.c_str(),
    6524             :                                            szVarStdName, pszType));
    6525             :         }
    6526             :     }
    6527             : 
    6528             :     // Recurse on sub groups.
    6529          45 :     int nSubGroups = 0;
    6530          45 :     int *panSubGroupIds = nullptr;
    6531          45 :     NCDFGetSubGroups(nGroupId, &nSubGroups, &panSubGroupIds);
    6532          47 :     for (int i = 0; i < nSubGroups; i++)
    6533             :     {
    6534           2 :         CreateSubDatasetList(panSubGroupIds[i]);
    6535             :     }
    6536          45 :     CPLFree(panSubGroupIds);
    6537          45 : }
    6538             : 
    6539             : /************************************************************************/
    6540             : /*                            TestCapability()                          */
    6541             : /************************************************************************/
    6542             : 
    6543         248 : int netCDFDataset::TestCapability(const char *pszCap)
    6544             : {
    6545         248 :     if (EQUAL(pszCap, ODsCCreateLayer))
    6546             :     {
    6547         223 :         return eAccess == GA_Update && nBands == 0 &&
    6548         218 :                (eMultipleLayerBehavior != SINGLE_LAYER ||
    6549         229 :                 this->GetLayerCount() == 0 || bSGSupport);
    6550             :     }
    6551         136 :     else if (EQUAL(pszCap, ODsCZGeometries))
    6552           2 :         return true;
    6553             : 
    6554         134 :     return false;
    6555             : }
    6556             : 
    6557             : /************************************************************************/
    6558             : /*                            GetLayer()                                */
    6559             : /************************************************************************/
    6560             : 
    6561         389 : OGRLayer *netCDFDataset::GetLayer(int nIdx)
    6562             : {
    6563         389 :     if (nIdx < 0 || nIdx >= this->GetLayerCount())
    6564           2 :         return nullptr;
    6565         387 :     return papoLayers[nIdx].get();
    6566             : }
    6567             : 
    6568             : /************************************************************************/
    6569             : /*                            ICreateLayer()                            */
    6570             : /************************************************************************/
    6571             : 
    6572          59 : OGRLayer *netCDFDataset::ICreateLayer(const char *pszName,
    6573             :                                       const OGRGeomFieldDefn *poGeomFieldDefn,
    6574             :                                       CSLConstList papszOptions)
    6575             : {
    6576          59 :     int nLayerCDFId = cdfid;
    6577          59 :     if (!TestCapability(ODsCCreateLayer))
    6578           0 :         return nullptr;
    6579             : 
    6580          59 :     const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
    6581             :     const auto poSpatialRef =
    6582          59 :         poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
    6583             : 
    6584         118 :     CPLString osNetCDFLayerName(pszName);
    6585          59 :     const netCDFWriterConfigLayer *poLayerConfig = nullptr;
    6586          59 :     if (oWriterConfig.m_bIsValid)
    6587             :     {
    6588             :         std::map<CPLString, netCDFWriterConfigLayer>::const_iterator
    6589           2 :             oLayerIter = oWriterConfig.m_oLayers.find(pszName);
    6590           2 :         if (oLayerIter != oWriterConfig.m_oLayers.end())
    6591             :         {
    6592           1 :             poLayerConfig = &(oLayerIter->second);
    6593           1 :             osNetCDFLayerName = poLayerConfig->m_osNetCDFName;
    6594             :         }
    6595             :     }
    6596             : 
    6597          59 :     netCDFDataset *poLayerDataset = nullptr;
    6598          59 :     if (eMultipleLayerBehavior == SEPARATE_FILES)
    6599             :     {
    6600           2 :         char **papszDatasetOptions = nullptr;
    6601           2 :         papszDatasetOptions = CSLSetNameValue(
    6602             :             papszDatasetOptions, "CONFIG_FILE",
    6603           2 :             CSLFetchNameValue(papszCreationOptions, "CONFIG_FILE"));
    6604             :         papszDatasetOptions =
    6605           2 :             CSLSetNameValue(papszDatasetOptions, "FORMAT",
    6606           2 :                             CSLFetchNameValue(papszCreationOptions, "FORMAT"));
    6607           2 :         papszDatasetOptions = CSLSetNameValue(
    6608             :             papszDatasetOptions, "WRITE_GDAL_TAGS",
    6609           2 :             CSLFetchNameValue(papszCreationOptions, "WRITE_GDAL_TAGS"));
    6610             :         CPLString osLayerFilename(
    6611           2 :             CPLFormFilename(osFilename, osNetCDFLayerName, "nc"));
    6612           2 :         CPLAcquireMutex(hNCMutex, 1000.0);
    6613           2 :         poLayerDataset =
    6614           2 :             CreateLL(osLayerFilename, 0, 0, 0, papszDatasetOptions);
    6615           2 :         CPLReleaseMutex(hNCMutex);
    6616           2 :         CSLDestroy(papszDatasetOptions);
    6617           2 :         if (poLayerDataset == nullptr)
    6618           0 :             return nullptr;
    6619             : 
    6620           2 :         nLayerCDFId = poLayerDataset->cdfid;
    6621           2 :         NCDFAddGDALHistory(nLayerCDFId, osLayerFilename, bWriteGDALVersion,
    6622           2 :                            bWriteGDALHistory, "", "Create",
    6623             :                            NCDF_CONVENTIONS_CF_V1_6);
    6624             :     }
    6625          57 :     else if (eMultipleLayerBehavior == SEPARATE_GROUPS)
    6626             :     {
    6627           2 :         SetDefineMode(true);
    6628             : 
    6629           2 :         nLayerCDFId = -1;
    6630           2 :         int status = nc_def_grp(cdfid, osNetCDFLayerName, &nLayerCDFId);
    6631           2 :         NCDF_ERR(status);
    6632           2 :         if (status != NC_NOERR)
    6633           0 :             return nullptr;
    6634             : 
    6635           2 :         NCDFAddGDALHistory(nLayerCDFId, osFilename, bWriteGDALVersion,
    6636           2 :                            bWriteGDALHistory, "", "Create",
    6637             :                            NCDF_CONVENTIONS_CF_V1_6);
    6638             :     }
    6639             : 
    6640             :     // Make a clone to workaround a bug in released MapServer versions
    6641             :     // that destroys the passed SRS instead of releasing it .
    6642          59 :     OGRSpatialReference *poSRS = nullptr;
    6643          59 :     if (poSpatialRef)
    6644             :     {
    6645          43 :         poSRS = poSpatialRef->Clone();
    6646          43 :         poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    6647             :     }
    6648             :     std::shared_ptr<netCDFLayer> poLayer(
    6649          59 :         new netCDFLayer(poLayerDataset ? poLayerDataset : this, nLayerCDFId,
    6650         118 :                         osNetCDFLayerName, eGType, poSRS));
    6651          59 :     if (poSRS != nullptr)
    6652          43 :         poSRS->Release();
    6653             : 
    6654             :     // Fetch layer creation options coming from config file
    6655          59 :     char **papszNewOptions = CSLDuplicate(papszOptions);
    6656          59 :     if (oWriterConfig.m_bIsValid)
    6657             :     {
    6658           2 :         std::map<CPLString, CPLString>::const_iterator oIter;
    6659           3 :         for (oIter = oWriterConfig.m_oLayerCreationOptions.begin();
    6660           3 :              oIter != oWriterConfig.m_oLayerCreationOptions.end(); ++oIter)
    6661             :         {
    6662             :             papszNewOptions =
    6663           1 :                 CSLSetNameValue(papszNewOptions, oIter->first, oIter->second);
    6664             :         }
    6665           2 :         if (poLayerConfig != nullptr)
    6666             :         {
    6667           3 :             for (oIter = poLayerConfig->m_oLayerCreationOptions.begin();
    6668           3 :                  oIter != poLayerConfig->m_oLayerCreationOptions.end(); ++oIter)
    6669             :             {
    6670           2 :                 papszNewOptions = CSLSetNameValue(papszNewOptions, oIter->first,
    6671           2 :                                                   oIter->second);
    6672             :             }
    6673             :         }
    6674             :     }
    6675             : 
    6676          59 :     const bool bRet = poLayer->Create(papszNewOptions, poLayerConfig);
    6677          59 :     CSLDestroy(papszNewOptions);
    6678             : 
    6679          59 :     if (!bRet)
    6680             :     {
    6681           0 :         return nullptr;
    6682             :     }
    6683             : 
    6684          59 :     if (poLayerDataset != nullptr)
    6685           2 :         apoVectorDatasets.push_back(poLayerDataset);
    6686             : 
    6687          59 :     papoLayers.push_back(poLayer);
    6688          59 :     return poLayer.get();
    6689             : }
    6690             : 
    6691             : /************************************************************************/
    6692             : /*                           CloneAttributes()                          */
    6693             : /************************************************************************/
    6694             : 
    6695         137 : bool netCDFDataset::CloneAttributes(int old_cdfid, int new_cdfid, int nSrcVarId,
    6696             :                                     int nDstVarId)
    6697             : {
    6698         137 :     int nAttCount = -1;
    6699         137 :     int status = nc_inq_varnatts(old_cdfid, nSrcVarId, &nAttCount);
    6700         137 :     NCDF_ERR(status);
    6701             : 
    6702         693 :     for (int i = 0; i < nAttCount; i++)
    6703             :     {
    6704             :         char szName[NC_MAX_NAME + 1];
    6705         556 :         szName[0] = 0;
    6706         556 :         status = nc_inq_attname(old_cdfid, nSrcVarId, i, szName);
    6707         556 :         NCDF_ERR(status);
    6708             : 
    6709             :         status =
    6710         556 :             nc_copy_att(old_cdfid, nSrcVarId, szName, new_cdfid, nDstVarId);
    6711         556 :         NCDF_ERR(status);
    6712         556 :         if (status != NC_NOERR)
    6713           0 :             return false;
    6714             :     }
    6715             : 
    6716         137 :     return true;
    6717             : }
    6718             : 
    6719             : /************************************************************************/
    6720             : /*                          CloneVariableContent()                      */
    6721             : /************************************************************************/
    6722             : 
    6723         121 : bool netCDFDataset::CloneVariableContent(int old_cdfid, int new_cdfid,
    6724             :                                          int nSrcVarId, int nDstVarId)
    6725             : {
    6726         121 :     int nVarDimCount = -1;
    6727         121 :     int status = nc_inq_varndims(old_cdfid, nSrcVarId, &nVarDimCount);
    6728         121 :     NCDF_ERR(status);
    6729         121 :     int anDimIds[] = {-1, 1};
    6730         121 :     status = nc_inq_vardimid(old_cdfid, nSrcVarId, anDimIds);
    6731         121 :     NCDF_ERR(status);
    6732         121 :     nc_type nc_datatype = NC_NAT;
    6733         121 :     status = nc_inq_vartype(old_cdfid, nSrcVarId, &nc_datatype);
    6734         121 :     NCDF_ERR(status);
    6735         121 :     size_t nTypeSize = 0;
    6736         121 :     switch (nc_datatype)
    6737             :     {
    6738          35 :         case NC_BYTE:
    6739             :         case NC_CHAR:
    6740          35 :             nTypeSize = 1;
    6741          35 :             break;
    6742           4 :         case NC_SHORT:
    6743           4 :             nTypeSize = 2;
    6744           4 :             break;
    6745          24 :         case NC_INT:
    6746          24 :             nTypeSize = 4;
    6747          24 :             break;
    6748           4 :         case NC_FLOAT:
    6749           4 :             nTypeSize = 4;
    6750           4 :             break;
    6751          43 :         case NC_DOUBLE:
    6752          43 :             nTypeSize = 8;
    6753          43 :             break;
    6754           2 :         case NC_UBYTE:
    6755           2 :             nTypeSize = 1;
    6756           2 :             break;
    6757           2 :         case NC_USHORT:
    6758           2 :             nTypeSize = 2;
    6759           2 :             break;
    6760           2 :         case NC_UINT:
    6761           2 :             nTypeSize = 4;
    6762           2 :             break;
    6763           4 :         case NC_INT64:
    6764             :         case NC_UINT64:
    6765           4 :             nTypeSize = 8;
    6766           4 :             break;
    6767           1 :         case NC_STRING:
    6768           1 :             nTypeSize = sizeof(char *);
    6769           1 :             break;
    6770           0 :         default:
    6771             :         {
    6772           0 :             CPLError(CE_Failure, CPLE_NotSupported, "Unsupported data type: %d",
    6773             :                      nc_datatype);
    6774           0 :             return false;
    6775             :         }
    6776             :     }
    6777             : 
    6778         121 :     size_t nElems = 1;
    6779             :     size_t anStart[NC_MAX_DIMS];
    6780             :     size_t anCount[NC_MAX_DIMS];
    6781         121 :     size_t nRecords = 1;
    6782         261 :     for (int i = 0; i < nVarDimCount; i++)
    6783             :     {
    6784         140 :         anStart[i] = 0;
    6785         140 :         if (i == 0)
    6786             :         {
    6787         116 :             anCount[i] = 1;
    6788         116 :             status = nc_inq_dimlen(old_cdfid, anDimIds[i], &nRecords);
    6789         116 :             NCDF_ERR(status);
    6790             :         }
    6791             :         else
    6792             :         {
    6793          24 :             anCount[i] = 0;
    6794          24 :             status = nc_inq_dimlen(old_cdfid, anDimIds[i], &anCount[i]);
    6795          24 :             NCDF_ERR(status);
    6796          24 :             nElems *= anCount[i];
    6797             :         }
    6798             :     }
    6799             : 
    6800             :     /* Workaround in some cases a netCDF bug:
    6801             :      * https://github.com/Unidata/netcdf-c/pull/1442 */
    6802         121 :     if (nRecords > 0 && nRecords < 10 * 1000 * 1000 / (nElems * nTypeSize))
    6803             :     {
    6804         119 :         nElems *= nRecords;
    6805         119 :         anCount[0] = nRecords;
    6806         119 :         nRecords = 1;
    6807             :     }
    6808             : 
    6809         121 :     void *pBuffer = VSI_MALLOC2_VERBOSE(nElems, nTypeSize);
    6810         121 :     if (pBuffer == nullptr)
    6811           0 :         return false;
    6812             : 
    6813         240 :     for (size_t iRecord = 0; iRecord < nRecords; iRecord++)
    6814             :     {
    6815         119 :         anStart[0] = iRecord;
    6816             : 
    6817         119 :         switch (nc_datatype)
    6818             :         {
    6819           5 :             case NC_BYTE:
    6820             :                 status =
    6821           5 :                     nc_get_vara_schar(old_cdfid, nSrcVarId, anStart, anCount,
    6822             :                                       static_cast<signed char *>(pBuffer));
    6823           5 :                 if (!status)
    6824           5 :                     status = nc_put_vara_schar(
    6825             :                         new_cdfid, nDstVarId, anStart, anCount,
    6826             :                         static_cast<signed char *>(pBuffer));
    6827           5 :                 break;
    6828          28 :             case NC_CHAR:
    6829             :                 status =
    6830          28 :                     nc_get_vara_text(old_cdfid, nSrcVarId, anStart, anCount,
    6831             :                                      static_cast<char *>(pBuffer));
    6832          28 :                 if (!status)
    6833             :                     status =
    6834          28 :                         nc_put_vara_text(new_cdfid, nDstVarId, anStart, anCount,
    6835             :                                          static_cast<char *>(pBuffer));
    6836          28 :                 break;
    6837           4 :             case NC_SHORT:
    6838             :                 status =
    6839           4 :                     nc_get_vara_short(old_cdfid, nSrcVarId, anStart, anCount,
    6840             :                                       static_cast<short *>(pBuffer));
    6841           4 :                 if (!status)
    6842           4 :                     status = nc_put_vara_short(new_cdfid, nDstVarId, anStart,
    6843             :                                                anCount,
    6844             :                                                static_cast<short *>(pBuffer));
    6845           4 :                 break;
    6846          24 :             case NC_INT:
    6847          24 :                 status = nc_get_vara_int(old_cdfid, nSrcVarId, anStart, anCount,
    6848             :                                          static_cast<int *>(pBuffer));
    6849          24 :                 if (!status)
    6850             :                     status =
    6851          24 :                         nc_put_vara_int(new_cdfid, nDstVarId, anStart, anCount,
    6852             :                                         static_cast<int *>(pBuffer));
    6853          24 :                 break;
    6854           4 :             case NC_FLOAT:
    6855             :                 status =
    6856           4 :                     nc_get_vara_float(old_cdfid, nSrcVarId, anStart, anCount,
    6857             :                                       static_cast<float *>(pBuffer));
    6858           4 :                 if (!status)
    6859           4 :                     status = nc_put_vara_float(new_cdfid, nDstVarId, anStart,
    6860             :                                                anCount,
    6861             :                                                static_cast<float *>(pBuffer));
    6862           4 :                 break;
    6863          43 :             case NC_DOUBLE:
    6864             :                 status =
    6865          43 :                     nc_get_vara_double(old_cdfid, nSrcVarId, anStart, anCount,
    6866             :                                        static_cast<double *>(pBuffer));
    6867          43 :                 if (!status)
    6868          43 :                     status = nc_put_vara_double(new_cdfid, nDstVarId, anStart,
    6869             :                                                 anCount,
    6870             :                                                 static_cast<double *>(pBuffer));
    6871          43 :                 break;
    6872           1 :             case NC_STRING:
    6873             :                 status =
    6874           1 :                     nc_get_vara_string(old_cdfid, nSrcVarId, anStart, anCount,
    6875             :                                        static_cast<char **>(pBuffer));
    6876           1 :                 if (!status)
    6877             :                 {
    6878           1 :                     status = nc_put_vara_string(
    6879             :                         new_cdfid, nDstVarId, anStart, anCount,
    6880             :                         static_cast<const char **>(pBuffer));
    6881           1 :                     nc_free_string(nElems, static_cast<char **>(pBuffer));
    6882             :                 }
    6883           1 :                 break;
    6884             : 
    6885           2 :             case NC_UBYTE:
    6886             :                 status =
    6887           2 :                     nc_get_vara_uchar(old_cdfid, nSrcVarId, anStart, anCount,
    6888             :                                       static_cast<unsigned char *>(pBuffer));
    6889           2 :                 if (!status)
    6890           2 :                     status = nc_put_vara_uchar(
    6891             :                         new_cdfid, nDstVarId, anStart, anCount,
    6892             :                         static_cast<unsigned char *>(pBuffer));
    6893           2 :                 break;
    6894           2 :             case NC_USHORT:
    6895             :                 status =
    6896           2 :                     nc_get_vara_ushort(old_cdfid, nSrcVarId, anStart, anCount,
    6897             :                                        static_cast<unsigned short *>(pBuffer));
    6898           2 :                 if (!status)
    6899           2 :                     status = nc_put_vara_ushort(
    6900             :                         new_cdfid, nDstVarId, anStart, anCount,
    6901             :                         static_cast<unsigned short *>(pBuffer));
    6902           2 :                 break;
    6903           2 :             case NC_UINT:
    6904             :                 status =
    6905           2 :                     nc_get_vara_uint(old_cdfid, nSrcVarId, anStart, anCount,
    6906             :                                      static_cast<unsigned int *>(pBuffer));
    6907           2 :                 if (!status)
    6908             :                     status =
    6909           2 :                         nc_put_vara_uint(new_cdfid, nDstVarId, anStart, anCount,
    6910             :                                          static_cast<unsigned int *>(pBuffer));
    6911           2 :                 break;
    6912           2 :             case NC_INT64:
    6913             :                 status =
    6914           2 :                     nc_get_vara_longlong(old_cdfid, nSrcVarId, anStart, anCount,
    6915             :                                          static_cast<long long *>(pBuffer));
    6916           2 :                 if (!status)
    6917           2 :                     status = nc_put_vara_longlong(
    6918             :                         new_cdfid, nDstVarId, anStart, anCount,
    6919             :                         static_cast<long long *>(pBuffer));
    6920           2 :                 break;
    6921           2 :             case NC_UINT64:
    6922           2 :                 status = nc_get_vara_ulonglong(
    6923             :                     old_cdfid, nSrcVarId, anStart, anCount,
    6924             :                     static_cast<unsigned long long *>(pBuffer));
    6925           2 :                 if (!status)
    6926           2 :                     status = nc_put_vara_ulonglong(
    6927             :                         new_cdfid, nDstVarId, anStart, anCount,
    6928             :                         static_cast<unsigned long long *>(pBuffer));
    6929           2 :                 break;
    6930           0 :             default:
    6931           0 :                 status = NC_EBADTYPE;
    6932             :         }
    6933             : 
    6934         119 :         NCDF_ERR(status);
    6935         119 :         if (status != NC_NOERR)
    6936             :         {
    6937           0 :             VSIFree(pBuffer);
    6938           0 :             return false;
    6939             :         }
    6940             :     }
    6941             : 
    6942         121 :     VSIFree(pBuffer);
    6943         121 :     return true;
    6944             : }
    6945             : 
    6946             : /************************************************************************/
    6947             : /*                         NCDFIsUnlimitedDim()                         */
    6948             : /************************************************************************/
    6949             : 
    6950          57 : bool NCDFIsUnlimitedDim(bool bIsNC4, int cdfid, int nDimId)
    6951             : {
    6952          57 :     if (bIsNC4)
    6953             :     {
    6954          16 :         int nUnlimitedDims = 0;
    6955          16 :         nc_inq_unlimdims(cdfid, &nUnlimitedDims, nullptr);
    6956          16 :         bool bFound = false;
    6957          16 :         if (nUnlimitedDims)
    6958             :         {
    6959             :             int *panUnlimitedDimIds =
    6960          16 :                 static_cast<int *>(CPLMalloc(sizeof(int) * nUnlimitedDims));
    6961          16 :             nc_inq_unlimdims(cdfid, nullptr, panUnlimitedDimIds);
    6962          30 :             for (int i = 0; i < nUnlimitedDims; i++)
    6963             :             {
    6964          22 :                 if (panUnlimitedDimIds[i] == nDimId)
    6965             :                 {
    6966           8 :                     bFound = true;
    6967           8 :                     break;
    6968             :                 }
    6969             :             }
    6970          16 :             CPLFree(panUnlimitedDimIds);
    6971             :         }
    6972          16 :         return bFound;
    6973             :     }
    6974             :     else
    6975             :     {
    6976          41 :         int nUnlimitedDimId = -1;
    6977          41 :         nc_inq(cdfid, nullptr, nullptr, nullptr, &nUnlimitedDimId);
    6978          41 :         return nDimId == nUnlimitedDimId;
    6979             :     }
    6980             : }
    6981             : 
    6982             : /************************************************************************/
    6983             : /*                              CloneGrp()                              */
    6984             : /************************************************************************/
    6985             : 
    6986          16 : bool netCDFDataset::CloneGrp(int nOldGrpId, int nNewGrpId, bool bIsNC4,
    6987             :                              int nLayerId, int nDimIdToGrow, size_t nNewSize)
    6988             : {
    6989             :     // Clone dimensions
    6990          16 :     int nDimCount = -1;
    6991          16 :     int status = nc_inq_ndims(nOldGrpId, &nDimCount);
    6992          16 :     NCDF_ERR(status);
    6993          16 :     int *panDimIds = static_cast<int *>(CPLMalloc(sizeof(int) * nDimCount));
    6994          16 :     int nUnlimiDimID = -1;
    6995          16 :     status = nc_inq_unlimdim(nOldGrpId, &nUnlimiDimID);
    6996          16 :     NCDF_ERR(status);
    6997          16 :     if (bIsNC4)
    6998             :     {
    6999             :         // In NC4, the dimension ids of a group are not necessarily in
    7000             :         // [0,nDimCount-1] range
    7001           8 :         int nDimCount2 = -1;
    7002           8 :         status = nc_inq_dimids(nOldGrpId, &nDimCount2, panDimIds, FALSE);
    7003           8 :         NCDF_ERR(status);
    7004           8 :         CPLAssert(nDimCount == nDimCount2);
    7005             :     }
    7006             :     else
    7007             :     {
    7008          36 :         for (int i = 0; i < nDimCount; i++)
    7009          28 :             panDimIds[i] = i;
    7010             :     }
    7011          60 :     for (int i = 0; i < nDimCount; i++)
    7012             :     {
    7013             :         char szDimName[NC_MAX_NAME + 1];
    7014          44 :         szDimName[0] = 0;
    7015          44 :         size_t nLen = 0;
    7016          44 :         const int nDimId = panDimIds[i];
    7017          44 :         status = nc_inq_dim(nOldGrpId, nDimId, szDimName, &nLen);
    7018          44 :         NCDF_ERR(status);
    7019          44 :         if (NCDFIsUnlimitedDim(bIsNC4, nOldGrpId, nDimId))
    7020          16 :             nLen = NC_UNLIMITED;
    7021          28 :         else if (nDimId == nDimIdToGrow && nOldGrpId == nLayerId)
    7022          13 :             nLen = nNewSize;
    7023          44 :         int nNewDimId = -1;
    7024          44 :         status = nc_def_dim(nNewGrpId, szDimName, nLen, &nNewDimId);
    7025          44 :         NCDF_ERR(status);
    7026          44 :         CPLAssert(nDimId == nNewDimId);
    7027          44 :         if (status != NC_NOERR)
    7028             :         {
    7029           0 :             CPLFree(panDimIds);
    7030           0 :             return false;
    7031             :         }
    7032             :     }
    7033          16 :     CPLFree(panDimIds);
    7034             : 
    7035             :     // Clone main attributes
    7036          16 :     if (!CloneAttributes(nOldGrpId, nNewGrpId, NC_GLOBAL, NC_GLOBAL))
    7037             :     {
    7038           0 :         return false;
    7039             :     }
    7040             : 
    7041             :     // Clone variable definitions
    7042          16 :     int nVarCount = -1;
    7043          16 :     status = nc_inq_nvars(nOldGrpId, &nVarCount);
    7044          16 :     NCDF_ERR(status);
    7045             : 
    7046         137 :     for (int i = 0; i < nVarCount; i++)
    7047             :     {
    7048             :         char szVarName[NC_MAX_NAME + 1];
    7049         121 :         szVarName[0] = 0;
    7050         121 :         status = nc_inq_varname(nOldGrpId, i, szVarName);
    7051         121 :         NCDF_ERR(status);
    7052         121 :         nc_type nc_datatype = NC_NAT;
    7053         121 :         status = nc_inq_vartype(nOldGrpId, i, &nc_datatype);
    7054         121 :         NCDF_ERR(status);
    7055         121 :         int nVarDimCount = -1;
    7056         121 :         status = nc_inq_varndims(nOldGrpId, i, &nVarDimCount);
    7057         121 :         NCDF_ERR(status);
    7058             :         int anDimIds[NC_MAX_DIMS];
    7059         121 :         status = nc_inq_vardimid(nOldGrpId, i, anDimIds);
    7060         121 :         NCDF_ERR(status);
    7061         121 :         int nNewVarId = -1;
    7062         121 :         status = nc_def_var(nNewGrpId, szVarName, nc_datatype, nVarDimCount,
    7063             :                             anDimIds, &nNewVarId);
    7064         121 :         NCDF_ERR(status);
    7065         121 :         CPLAssert(i == nNewVarId);
    7066         121 :         if (status != NC_NOERR)
    7067             :         {
    7068           0 :             return false;
    7069             :         }
    7070             : 
    7071         121 :         if (!CloneAttributes(nOldGrpId, nNewGrpId, i, i))
    7072             :         {
    7073           0 :             return false;
    7074             :         }
    7075             :     }
    7076             : 
    7077          16 :     status = nc_enddef(nNewGrpId);
    7078          16 :     NCDF_ERR(status);
    7079          16 :     if (status != NC_NOERR)
    7080             :     {
    7081           0 :         return false;
    7082             :     }
    7083             : 
    7084             :     // Clone variable content
    7085         137 :     for (int i = 0; i < nVarCount; i++)
    7086             :     {
    7087         121 :         if (!CloneVariableContent(nOldGrpId, nNewGrpId, i, i))
    7088             :         {
    7089           0 :             return false;
    7090             :         }
    7091             :     }
    7092             : 
    7093          16 :     return true;
    7094             : }
    7095             : 
    7096             : /************************************************************************/
    7097             : /*                              GrowDim()                               */
    7098             : /************************************************************************/
    7099             : 
    7100          13 : bool netCDFDataset::GrowDim(int nLayerId, int nDimIdToGrow, size_t nNewSize)
    7101             : {
    7102             :     int nCreationMode;
    7103             :     // Set nCreationMode based on eFormat.
    7104          13 :     switch (eFormat)
    7105             :     {
    7106             : #ifdef NETCDF_HAS_NC2
    7107           0 :         case NCDF_FORMAT_NC2:
    7108           0 :             nCreationMode = NC_CLOBBER | NC_64BIT_OFFSET;
    7109           0 :             break;
    7110             : #endif
    7111           5 :         case NCDF_FORMAT_NC4:
    7112           5 :             nCreationMode = NC_CLOBBER | NC_NETCDF4;
    7113           5 :             break;
    7114           0 :         case NCDF_FORMAT_NC4C:
    7115           0 :             nCreationMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
    7116           0 :             break;
    7117           8 :         case NCDF_FORMAT_NC:
    7118             :         default:
    7119           8 :             nCreationMode = NC_CLOBBER;
    7120           8 :             break;
    7121             :     }
    7122             : 
    7123          13 :     int new_cdfid = -1;
    7124          26 :     CPLString osTmpFilename(osFilename + ".tmp");
    7125          26 :     CPLString osFilenameForNCCreate(osTmpFilename);
    7126             : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
    7127             :     if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
    7128             :     {
    7129             :         char *pszTemp =
    7130             :             CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
    7131             :         osFilenameForNCCreate = pszTemp;
    7132             :         CPLFree(pszTemp);
    7133             :     }
    7134             : #endif
    7135          13 :     int status = nc_create(osFilenameForNCCreate, nCreationMode, &new_cdfid);
    7136          13 :     NCDF_ERR(status);
    7137          13 :     if (status != NC_NOERR)
    7138           0 :         return false;
    7139             : 
    7140          13 :     if (!CloneGrp(cdfid, new_cdfid, eFormat == NCDF_FORMAT_NC4, nLayerId,
    7141             :                   nDimIdToGrow, nNewSize))
    7142             :     {
    7143           0 :         GDAL_nc_close(new_cdfid);
    7144           0 :         return false;
    7145             :     }
    7146             : 
    7147          13 :     int nGroupCount = 0;
    7148          26 :     std::vector<CPLString> oListGrpName;
    7149          31 :     if (eFormat == NCDF_FORMAT_NC4 &&
    7150          18 :         nc_inq_grps(cdfid, &nGroupCount, nullptr) == NC_NOERR &&
    7151           5 :         nGroupCount > 0)
    7152             :     {
    7153             :         int *panGroupIds =
    7154           2 :             static_cast<int *>(CPLMalloc(sizeof(int) * nGroupCount));
    7155           2 :         status = nc_inq_grps(cdfid, nullptr, panGroupIds);
    7156           2 :         NCDF_ERR(status);
    7157           5 :         for (int i = 0; i < nGroupCount; i++)
    7158             :         {
    7159             :             char szGroupName[NC_MAX_NAME + 1];
    7160           3 :             szGroupName[0] = 0;
    7161           3 :             nc_inq_grpname(panGroupIds[i], szGroupName);
    7162           3 :             int nNewGrpId = -1;
    7163           3 :             status = nc_def_grp(new_cdfid, szGroupName, &nNewGrpId);
    7164           3 :             NCDF_ERR(status);
    7165           3 :             if (status != NC_NOERR)
    7166             :             {
    7167           0 :                 CPLFree(panGroupIds);
    7168           0 :                 GDAL_nc_close(new_cdfid);
    7169           0 :                 return false;
    7170             :             }
    7171           3 :             if (!CloneGrp(panGroupIds[i], nNewGrpId, /*bIsNC4=*/true, nLayerId,
    7172             :                           nDimIdToGrow, nNewSize))
    7173             :             {
    7174           0 :                 CPLFree(panGroupIds);
    7175           0 :                 GDAL_nc_close(new_cdfid);
    7176           0 :                 return false;
    7177             :             }
    7178             :         }
    7179           2 :         CPLFree(panGroupIds);
    7180             : 
    7181           5 :         for (int i = 0; i < this->GetLayerCount(); i++)
    7182             :         {
    7183           3 :             auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
    7184           3 :             if (poLayer)
    7185             :             {
    7186             :                 char szGroupName[NC_MAX_NAME + 1];
    7187           3 :                 szGroupName[0] = 0;
    7188           3 :                 status = nc_inq_grpname(poLayer->GetCDFID(), szGroupName);
    7189           3 :                 NCDF_ERR(status);
    7190           3 :                 oListGrpName.push_back(szGroupName);
    7191             :             }
    7192             :         }
    7193             :     }
    7194             : 
    7195          13 :     GDAL_nc_close(cdfid);
    7196          13 :     cdfid = -1;
    7197          13 :     GDAL_nc_close(new_cdfid);
    7198             : 
    7199          26 :     CPLString osOriFilename(osFilename + ".ori");
    7200          26 :     if (VSIRename(osFilename, osOriFilename) != 0 ||
    7201          13 :         VSIRename(osTmpFilename, osFilename) != 0)
    7202             :     {
    7203           0 :         CPLError(CE_Failure, CPLE_FileIO, "Renaming of files failed");
    7204           0 :         return false;
    7205             :     }
    7206          13 :     VSIUnlink(osOriFilename);
    7207             : 
    7208          26 :     CPLString osFilenameForNCOpen(osFilename);
    7209             : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
    7210             :     if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
    7211             :     {
    7212             :         char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
    7213             :         osFilenameForNCOpen = pszTemp;
    7214             :         CPLFree(pszTemp);
    7215             :     }
    7216             : #endif
    7217          13 :     status = GDAL_nc_open(osFilenameForNCOpen, NC_WRITE, &cdfid);
    7218          13 :     NCDF_ERR(status);
    7219          13 :     if (status != NC_NOERR)
    7220           0 :         return false;
    7221          13 :     bDefineMode = false;
    7222             : 
    7223          13 :     if (!oListGrpName.empty())
    7224             :     {
    7225           5 :         for (int i = 0; i < this->GetLayerCount(); i++)
    7226             :         {
    7227           3 :             auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
    7228           3 :             if (poLayer)
    7229             :             {
    7230           3 :                 int nNewLayerCDFID = -1;
    7231           3 :                 status = nc_inq_ncid(cdfid, oListGrpName[i].c_str(),
    7232             :                                      &nNewLayerCDFID);
    7233           3 :                 NCDF_ERR(status);
    7234           3 :                 poLayer->SetCDFID(nNewLayerCDFID);
    7235             :             }
    7236             :         }
    7237             :     }
    7238             :     else
    7239             :     {
    7240          22 :         for (int i = 0; i < this->GetLayerCount(); i++)
    7241             :         {
    7242          11 :             auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
    7243          11 :             if (poLayer)
    7244          11 :                 poLayer->SetCDFID(cdfid);
    7245             :         }
    7246             :     }
    7247             : 
    7248          13 :     return true;
    7249             : }
    7250             : 
    7251             : #ifdef ENABLE_NCDUMP
    7252             : 
    7253             : /************************************************************************/
    7254             : /*                      netCDFDatasetCreateTempFile()                   */
    7255             : /************************************************************************/
    7256             : 
    7257             : /* Create a netCDF file from a text dump (format of ncdump) */
    7258             : /* Mostly to easy fuzzing of the driver, while still generating valid */
    7259             : /* netCDF files. */
    7260             : /* Note: not all data types are supported ! */
    7261           4 : bool netCDFDatasetCreateTempFile(NetCDFFormatEnum eFormat,
    7262             :                                  const char *pszTmpFilename, VSILFILE *fpSrc)
    7263             : {
    7264           4 :     CPL_IGNORE_RET_VAL(eFormat);
    7265           4 :     int nCreateMode = NC_CLOBBER;
    7266           4 :     if (eFormat == NCDF_FORMAT_NC4)
    7267           1 :         nCreateMode |= NC_NETCDF4;
    7268           3 :     else if (eFormat == NCDF_FORMAT_NC4C)
    7269           0 :         nCreateMode |= NC_NETCDF4 | NC_CLASSIC_MODEL;
    7270           4 :     int nCdfId = -1;
    7271           4 :     int status = nc_create(pszTmpFilename, nCreateMode, &nCdfId);
    7272           4 :     if (status != NC_NOERR)
    7273             :     {
    7274           0 :         return false;
    7275             :     }
    7276           4 :     VSIFSeekL(fpSrc, 0, SEEK_SET);
    7277             :     const char *pszLine;
    7278           4 :     constexpr int SECTION_NONE = 0;
    7279           4 :     constexpr int SECTION_DIMENSIONS = 1;
    7280           4 :     constexpr int SECTION_VARIABLES = 2;
    7281           4 :     constexpr int SECTION_DATA = 3;
    7282           4 :     int nActiveSection = SECTION_NONE;
    7283           8 :     std::map<CPLString, int> oMapDimToId;
    7284           8 :     std::map<int, int> oMapDimIdToDimLen;
    7285           8 :     std::map<CPLString, int> oMapVarToId;
    7286           8 :     std::map<int, std::vector<int>> oMapVarIdToVectorOfDimId;
    7287           8 :     std::map<int, int> oMapVarIdToType;
    7288           4 :     std::set<CPLString> oSetAttrDefined;
    7289           4 :     oMapVarToId[""] = -1;
    7290           4 :     size_t nTotalVarSize = 0;
    7291         208 :     while ((pszLine = CPLReadLineL(fpSrc)) != nullptr)
    7292             :     {
    7293         204 :         if (STARTS_WITH(pszLine, "dimensions:") &&
    7294             :             nActiveSection == SECTION_NONE)
    7295             :         {
    7296           4 :             nActiveSection = SECTION_DIMENSIONS;
    7297             :         }
    7298         200 :         else if (STARTS_WITH(pszLine, "variables:") &&
    7299             :                  nActiveSection == SECTION_DIMENSIONS)
    7300             :         {
    7301           4 :             nActiveSection = SECTION_VARIABLES;
    7302             :         }
    7303         196 :         else if (STARTS_WITH(pszLine, "data:") &&
    7304             :                  nActiveSection == SECTION_VARIABLES)
    7305             :         {
    7306           4 :             nActiveSection = SECTION_DATA;
    7307           4 :             status = nc_enddef(nCdfId);
    7308           4 :             if (status != NC_NOERR)
    7309             :             {
    7310           0 :                 CPLDebug("netCDF", "nc_enddef() failed: %s",
    7311             :                          nc_strerror(status));
    7312             :             }
    7313             :         }
    7314         192 :         else if (nActiveSection == SECTION_DIMENSIONS)
    7315             :         {
    7316           9 :             char **papszTokens = CSLTokenizeString2(pszLine, " \t=;", 0);
    7317           9 :             if (CSLCount(papszTokens) == 2)
    7318             :             {
    7319           9 :                 const char *pszDimName = papszTokens[0];
    7320           9 :                 bool bValidName = true;
    7321           9 :                 if (STARTS_WITH(pszDimName, "_nc4_non_coord_"))
    7322             :                 {
    7323             :                     // This is an internal netcdf prefix. Using it may
    7324             :                     // cause memory leaks.
    7325           0 :                     bValidName = false;
    7326             :                 }
    7327           9 :                 if (!bValidName)
    7328             :                 {
    7329           0 :                     CPLDebug("netCDF",
    7330             :                              "nc_def_dim(%s) failed: invalid name found",
    7331             :                              pszDimName);
    7332           0 :                     CSLDestroy(papszTokens);
    7333           0 :                     continue;
    7334             :                 }
    7335             : 
    7336             :                 const bool bIsASCII =
    7337           9 :                     CPLIsASCII(pszDimName, static_cast<size_t>(-1));
    7338           9 :                 if (!bIsASCII)
    7339             :                 {
    7340             :                     // Workaround https://github.com/Unidata/netcdf-c/pull/450
    7341           0 :                     CPLDebug("netCDF",
    7342             :                              "nc_def_dim(%s) failed: rejected because "
    7343             :                              "of non-ASCII characters",
    7344             :                              pszDimName);
    7345           0 :                     CSLDestroy(papszTokens);
    7346           0 :                     continue;
    7347             :                 }
    7348           9 :                 int nDimSize = EQUAL(papszTokens[1], "UNLIMITED")
    7349             :                                    ? NC_UNLIMITED
    7350           9 :                                    : atoi(papszTokens[1]);
    7351           9 :                 if (nDimSize >= 1000)
    7352           1 :                     nDimSize = 1000;  // to avoid very long processing
    7353           9 :                 if (nDimSize >= 0)
    7354             :                 {
    7355           9 :                     int nDimId = -1;
    7356           9 :                     status = nc_def_dim(nCdfId, pszDimName, nDimSize, &nDimId);
    7357           9 :                     if (status != NC_NOERR)
    7358             :                     {
    7359           0 :                         CPLDebug("netCDF", "nc_def_dim(%s, %d) failed: %s",
    7360             :                                  pszDimName, nDimSize, nc_strerror(status));
    7361             :                     }
    7362             :                     else
    7363             :                     {
    7364             : #ifdef DEBUG_VERBOSE
    7365             :                         CPLDebug("netCDF", "nc_def_dim(%s, %d) (%s) succeeded",
    7366             :                                  pszDimName, nDimSize, pszLine);
    7367             : #endif
    7368           9 :                         oMapDimToId[pszDimName] = nDimId;
    7369           9 :                         oMapDimIdToDimLen[nDimId] = nDimSize;
    7370             :                     }
    7371             :                 }
    7372             :             }
    7373           9 :             CSLDestroy(papszTokens);
    7374             :         }
    7375         183 :         else if (nActiveSection == SECTION_VARIABLES)
    7376             :         {
    7377         390 :             while (*pszLine == ' ' || *pszLine == '\t')
    7378         249 :                 pszLine++;
    7379         141 :             const char *pszColumn = strchr(pszLine, ':');
    7380         141 :             const char *pszEqual = strchr(pszLine, '=');
    7381         141 :             if (pszColumn == nullptr)
    7382             :             {
    7383          21 :                 char **papszTokens = CSLTokenizeString2(pszLine, " \t=(),;", 0);
    7384          21 :                 if (CSLCount(papszTokens) >= 2)
    7385             :                 {
    7386          17 :                     const char *pszVarName = papszTokens[1];
    7387          17 :                     bool bValidName = true;
    7388          17 :                     if (STARTS_WITH(pszVarName, "_nc4_non_coord_"))
    7389             :                     {
    7390             :                         // This is an internal netcdf prefix. Using it may
    7391             :                         // cause memory leaks.
    7392           0 :                         bValidName = false;
    7393             :                     }
    7394         138 :                     for (int i = 0; pszVarName[i]; i++)
    7395             :                     {
    7396         121 :                         if (!((pszVarName[i] >= 'a' && pszVarName[i] <= 'z') ||
    7397          28 :                               (pszVarName[i] >= 'A' && pszVarName[i] <= 'Z') ||
    7398           9 :                               (pszVarName[i] >= '0' && pszVarName[i] <= '9') ||
    7399           6 :                               pszVarName[i] == '_'))
    7400             :                         {
    7401           0 :                             bValidName = false;
    7402             :                         }
    7403             :                     }
    7404          17 :                     if (!bValidName)
    7405             :                     {
    7406           0 :                         CPLDebug(
    7407             :                             "netCDF",
    7408             :                             "nc_def_var(%s) failed: illegal character found",
    7409             :                             pszVarName);
    7410           0 :                         CSLDestroy(papszTokens);
    7411           0 :                         continue;
    7412             :                     }
    7413          17 :                     if (oMapVarToId.find(pszVarName) != oMapVarToId.end())
    7414             :                     {
    7415           0 :                         CPLDebug("netCDF",
    7416             :                                  "nc_def_var(%s) failed: already defined",
    7417             :                                  pszVarName);
    7418           0 :                         CSLDestroy(papszTokens);
    7419           0 :                         continue;
    7420             :                     }
    7421          17 :                     const char *pszVarType = papszTokens[0];
    7422          17 :                     int nc_datatype = NC_BYTE;
    7423          17 :                     size_t nDataTypeSize = 1;
    7424          17 :                     if (EQUAL(pszVarType, "char"))
    7425             :                     {
    7426           6 :                         nc_datatype = NC_CHAR;
    7427           6 :                         nDataTypeSize = 1;
    7428             :                     }
    7429          11 :                     else if (EQUAL(pszVarType, "byte"))
    7430             :                     {
    7431           3 :                         nc_datatype = NC_BYTE;
    7432           3 :                         nDataTypeSize = 1;
    7433             :                     }
    7434           8 :                     else if (EQUAL(pszVarType, "short"))
    7435             :                     {
    7436           0 :                         nc_datatype = NC_SHORT;
    7437           0 :                         nDataTypeSize = 2;
    7438             :                     }
    7439           8 :                     else if (EQUAL(pszVarType, "int"))
    7440             :                     {
    7441           0 :                         nc_datatype = NC_INT;
    7442           0 :                         nDataTypeSize = 4;
    7443             :                     }
    7444           8 :                     else if (EQUAL(pszVarType, "float"))
    7445             :                     {
    7446           0 :                         nc_datatype = NC_FLOAT;
    7447           0 :                         nDataTypeSize = 4;
    7448             :                     }
    7449           8 :                     else if (EQUAL(pszVarType, "double"))
    7450             :                     {
    7451           8 :                         nc_datatype = NC_DOUBLE;
    7452           8 :                         nDataTypeSize = 8;
    7453             :                     }
    7454           0 :                     else if (EQUAL(pszVarType, "ubyte"))
    7455             :                     {
    7456           0 :                         nc_datatype = NC_UBYTE;
    7457           0 :                         nDataTypeSize = 1;
    7458             :                     }
    7459           0 :                     else if (EQUAL(pszVarType, "ushort"))
    7460             :                     {
    7461           0 :                         nc_datatype = NC_USHORT;
    7462           0 :                         nDataTypeSize = 2;
    7463             :                     }
    7464           0 :                     else if (EQUAL(pszVarType, "uint"))
    7465             :                     {
    7466           0 :                         nc_datatype = NC_UINT;
    7467           0 :                         nDataTypeSize = 4;
    7468             :                     }
    7469           0 :                     else if (EQUAL(pszVarType, "int64"))
    7470             :                     {
    7471           0 :                         nc_datatype = NC_INT64;
    7472           0 :                         nDataTypeSize = 8;
    7473             :                     }
    7474           0 :                     else if (EQUAL(pszVarType, "uint64"))
    7475             :                     {
    7476           0 :                         nc_datatype = NC_UINT64;
    7477           0 :                         nDataTypeSize = 8;
    7478             :                     }
    7479             : 
    7480          17 :                     int nDims = CSLCount(papszTokens) - 2;
    7481          17 :                     if (nDims >= 32)
    7482             :                     {
    7483             :                         // The number of dimensions in a netCDFv4 file is
    7484             :                         // limited by #define H5S_MAX_RANK    32
    7485             :                         // but libnetcdf doesn't check that...
    7486           0 :                         CPLDebug("netCDF",
    7487             :                                  "nc_def_var(%s) failed: too many dimensions",
    7488             :                                  pszVarName);
    7489           0 :                         CSLDestroy(papszTokens);
    7490           0 :                         continue;
    7491             :                     }
    7492          17 :                     std::vector<int> aoDimIds;
    7493          17 :                     bool bFailed = false;
    7494          17 :                     size_t nSize = 1;
    7495          35 :                     for (int i = 0; i < nDims; i++)
    7496             :                     {
    7497          18 :                         const char *pszDimName = papszTokens[2 + i];
    7498          18 :                         if (oMapDimToId.find(pszDimName) == oMapDimToId.end())
    7499             :                         {
    7500           0 :                             bFailed = true;
    7501           0 :                             break;
    7502             :                         }
    7503          18 :                         const int nDimId = oMapDimToId[pszDimName];
    7504          18 :                         aoDimIds.push_back(nDimId);
    7505             : 
    7506          18 :                         const size_t nDimSize = oMapDimIdToDimLen[nDimId];
    7507          18 :                         if (nDimSize != 0)
    7508             :                         {
    7509          18 :                             if (nSize >
    7510          18 :                                 std::numeric_limits<size_t>::max() / nDimSize)
    7511             :                             {
    7512           0 :                                 bFailed = true;
    7513           0 :                                 break;
    7514             :                             }
    7515             :                             else
    7516             :                             {
    7517          18 :                                 nSize *= nDimSize;
    7518             :                             }
    7519             :                         }
    7520             :                     }
    7521          17 :                     if (bFailed)
    7522             :                     {
    7523           0 :                         CPLDebug("netCDF",
    7524             :                                  "nc_def_var(%s) failed: unknown dimension(s)",
    7525             :                                  pszVarName);
    7526           0 :                         CSLDestroy(papszTokens);
    7527           0 :                         continue;
    7528             :                     }
    7529          17 :                     if (nSize > 100U * 1024 * 1024 / nDataTypeSize)
    7530             :                     {
    7531           0 :                         CPLDebug("netCDF",
    7532             :                                  "nc_def_var(%s) failed: too large data",
    7533             :                                  pszVarName);
    7534           0 :                         CSLDestroy(papszTokens);
    7535           0 :                         continue;
    7536             :                     }
    7537          17 :                     if (nTotalVarSize >
    7538          34 :                             std::numeric_limits<size_t>::max() - nSize ||
    7539          17 :                         nTotalVarSize + nSize > 100 * 1024 * 1024)
    7540             :                     {
    7541           0 :                         CPLDebug("netCDF",
    7542             :                                  "nc_def_var(%s) failed: too large data",
    7543             :                                  pszVarName);
    7544           0 :                         CSLDestroy(papszTokens);
    7545           0 :                         continue;
    7546             :                     }
    7547          17 :                     nTotalVarSize += nSize;
    7548             : 
    7549          17 :                     int nVarId = -1;
    7550             :                     status =
    7551          30 :                         nc_def_var(nCdfId, pszVarName, nc_datatype, nDims,
    7552          13 :                                    (nDims) ? &aoDimIds[0] : nullptr, &nVarId);
    7553          17 :                     if (status != NC_NOERR)
    7554             :                     {
    7555           0 :                         CPLDebug("netCDF", "nc_def_var(%s) failed: %s",
    7556             :                                  pszVarName, nc_strerror(status));
    7557             :                     }
    7558             :                     else
    7559             :                     {
    7560             : #ifdef DEBUG_VERBOSE
    7561             :                         CPLDebug("netCDF", "nc_def_var(%s) (%s) succeeded",
    7562             :                                  pszVarName, pszLine);
    7563             : #endif
    7564          17 :                         oMapVarToId[pszVarName] = nVarId;
    7565          17 :                         oMapVarIdToType[nVarId] = nc_datatype;
    7566          17 :                         oMapVarIdToVectorOfDimId[nVarId] = std::move(aoDimIds);
    7567             :                     }
    7568             :                 }
    7569          21 :                 CSLDestroy(papszTokens);
    7570             :             }
    7571         120 :             else if (pszEqual != nullptr && pszEqual - pszColumn > 0)
    7572             :             {
    7573         116 :                 CPLString osVarName(pszLine, pszColumn - pszLine);
    7574         116 :                 CPLString osAttrName(pszColumn + 1, pszEqual - pszColumn - 1);
    7575         116 :                 osAttrName.Trim();
    7576         116 :                 if (oMapVarToId.find(osVarName) == oMapVarToId.end())
    7577             :                 {
    7578           0 :                     CPLDebug("netCDF",
    7579             :                              "nc_put_att(%s:%s) failed: "
    7580             :                              "no corresponding variable",
    7581             :                              osVarName.c_str(), osAttrName.c_str());
    7582           0 :                     continue;
    7583             :                 }
    7584         116 :                 bool bValidName = true;
    7585        1743 :                 for (size_t i = 0; i < osAttrName.size(); i++)
    7586             :                 {
    7587        1865 :                     if (!((osAttrName[i] >= 'a' && osAttrName[i] <= 'z') ||
    7588         238 :                           (osAttrName[i] >= 'A' && osAttrName[i] <= 'Z') ||
    7589         158 :                           (osAttrName[i] >= '0' && osAttrName[i] <= '9') ||
    7590         158 :                           osAttrName[i] == '_'))
    7591             :                     {
    7592           0 :                         bValidName = false;
    7593             :                     }
    7594             :                 }
    7595         116 :                 if (!bValidName)
    7596             :                 {
    7597           0 :                     CPLDebug(
    7598             :                         "netCDF",
    7599             :                         "nc_put_att(%s:%s) failed: illegal character found",
    7600             :                         osVarName.c_str(), osAttrName.c_str());
    7601           0 :                     continue;
    7602             :                 }
    7603         116 :                 if (oSetAttrDefined.find(osVarName + ":" + osAttrName) !=
    7604         232 :                     oSetAttrDefined.end())
    7605             :                 {
    7606           0 :                     CPLDebug("netCDF",
    7607             :                              "nc_put_att(%s:%s) failed: already defined",
    7608             :                              osVarName.c_str(), osAttrName.c_str());
    7609           0 :                     continue;
    7610             :                 }
    7611             : 
    7612         116 :                 const int nVarId = oMapVarToId[osVarName];
    7613         116 :                 const char *pszValue = pszEqual + 1;
    7614         232 :                 while (*pszValue == ' ')
    7615         116 :                     pszValue++;
    7616             : 
    7617         116 :                 status = NC_EBADTYPE;
    7618         116 :                 if (*pszValue == '"')
    7619             :                 {
    7620             :                     // For _FillValue, the attribute type should match
    7621             :                     // the variable type. Leaks memory with NC4 otherwise
    7622          74 :                     if (osAttrName == "_FillValue")
    7623             :                     {
    7624           0 :                         CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
    7625             :                                  osVarName.c_str(), osAttrName.c_str(),
    7626             :                                  nc_strerror(status));
    7627           0 :                         continue;
    7628             :                     }
    7629             : 
    7630             :                     // Unquote and unescape string value
    7631          74 :                     CPLString osVal(pszValue + 1);
    7632         222 :                     while (!osVal.empty())
    7633             :                     {
    7634         222 :                         if (osVal.back() == ';' || osVal.back() == ' ')
    7635             :                         {
    7636         148 :                             osVal.resize(osVal.size() - 1);
    7637             :                         }
    7638          74 :                         else if (osVal.back() == '"')
    7639             :                         {
    7640          74 :                             osVal.resize(osVal.size() - 1);
    7641          74 :                             break;
    7642             :                         }
    7643             :                         else
    7644             :                         {
    7645           0 :                             break;
    7646             :                         }
    7647             :                     }
    7648          74 :                     osVal.replaceAll("\\\"", '"');
    7649          74 :                     status = nc_put_att_text(nCdfId, nVarId, osAttrName,
    7650             :                                              osVal.size(), osVal.c_str());
    7651             :                 }
    7652             :                 else
    7653             :                 {
    7654          84 :                     CPLString osVal(pszValue);
    7655         126 :                     while (!osVal.empty())
    7656             :                     {
    7657         126 :                         if (osVal.back() == ';' || osVal.back() == ' ')
    7658             :                         {
    7659          84 :                             osVal.resize(osVal.size() - 1);
    7660             :                         }
    7661             :                         else
    7662             :                         {
    7663          42 :                             break;
    7664             :                         }
    7665             :                     }
    7666          42 :                     int nc_datatype = -1;
    7667          42 :                     if (!osVal.empty() && osVal.back() == 'b')
    7668             :                     {
    7669           3 :                         nc_datatype = NC_BYTE;
    7670           3 :                         osVal.resize(osVal.size() - 1);
    7671             :                     }
    7672          39 :                     else if (!osVal.empty() && osVal.back() == 's')
    7673             :                     {
    7674           3 :                         nc_datatype = NC_SHORT;
    7675           3 :                         osVal.resize(osVal.size() - 1);
    7676             :                     }
    7677          42 :                     if (CPLGetValueType(osVal) == CPL_VALUE_INTEGER)
    7678             :                     {
    7679           7 :                         if (nc_datatype < 0)
    7680           4 :                             nc_datatype = NC_INT;
    7681             :                     }
    7682          35 :                     else if (CPLGetValueType(osVal) == CPL_VALUE_REAL)
    7683             :                     {
    7684          32 :                         nc_datatype = NC_DOUBLE;
    7685             :                     }
    7686             :                     else
    7687             :                     {
    7688           3 :                         nc_datatype = -1;
    7689             :                     }
    7690             : 
    7691             :                     // For _FillValue, check that the attribute type matches
    7692             :                     // the variable type. Leaks memory with NC4 otherwise
    7693          42 :                     if (osAttrName == "_FillValue")
    7694             :                     {
    7695           6 :                         if (nVarId < 0 ||
    7696           3 :                             nc_datatype != oMapVarIdToType[nVarId])
    7697             :                         {
    7698           0 :                             nc_datatype = -1;
    7699             :                         }
    7700             :                     }
    7701             : 
    7702          42 :                     if (nc_datatype == NC_BYTE)
    7703             :                     {
    7704             :                         signed char chVal =
    7705           3 :                             static_cast<signed char>(atoi(osVal));
    7706           3 :                         status = nc_put_att_schar(nCdfId, nVarId, osAttrName,
    7707             :                                                   NC_BYTE, 1, &chVal);
    7708             :                     }
    7709          39 :                     else if (nc_datatype == NC_SHORT)
    7710             :                     {
    7711           0 :                         short nVal = static_cast<short>(atoi(osVal));
    7712           0 :                         status = nc_put_att_short(nCdfId, nVarId, osAttrName,
    7713             :                                                   NC_SHORT, 1, &nVal);
    7714             :                     }
    7715          39 :                     else if (nc_datatype == NC_INT)
    7716             :                     {
    7717           4 :                         int nVal = static_cast<int>(atoi(osVal));
    7718           4 :                         status = nc_put_att_int(nCdfId, nVarId, osAttrName,
    7719             :                                                 NC_INT, 1, &nVal);
    7720             :                     }
    7721          35 :                     else if (nc_datatype == NC_DOUBLE)
    7722             :                     {
    7723          32 :                         double dfVal = CPLAtof(osVal);
    7724          32 :                         status = nc_put_att_double(nCdfId, nVarId, osAttrName,
    7725             :                                                    NC_DOUBLE, 1, &dfVal);
    7726             :                     }
    7727             :                 }
    7728         116 :                 if (status != NC_NOERR)
    7729             :                 {
    7730           3 :                     CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
    7731             :                              osVarName.c_str(), osAttrName.c_str(),
    7732             :                              nc_strerror(status));
    7733             :                 }
    7734             :                 else
    7735             :                 {
    7736         113 :                     oSetAttrDefined.insert(osVarName + ":" + osAttrName);
    7737             : #ifdef DEBUG_VERBOSE
    7738             :                     CPLDebug("netCDF", "nc_put_att_(%s:%s) (%s) succeeded",
    7739             :                              osVarName.c_str(), osAttrName.c_str(), pszLine);
    7740             : #endif
    7741             :                 }
    7742             :             }
    7743             :         }
    7744          42 :         else if (nActiveSection == SECTION_DATA)
    7745             :         {
    7746          55 :             while (*pszLine == ' ' || *pszLine == '\t')
    7747          17 :                 pszLine++;
    7748          38 :             const char *pszEqual = strchr(pszLine, '=');
    7749          38 :             if (pszEqual)
    7750             :             {
    7751          17 :                 CPLString osVarName(pszLine, pszEqual - pszLine);
    7752          17 :                 osVarName.Trim();
    7753          17 :                 if (oMapVarToId.find(osVarName) == oMapVarToId.end())
    7754           0 :                     continue;
    7755          17 :                 const int nVarId = oMapVarToId[osVarName];
    7756          17 :                 CPLString osAccVal(pszEqual + 1);
    7757          17 :                 osAccVal.Trim();
    7758         153 :                 while (osAccVal.empty() || osAccVal.back() != ';')
    7759             :                 {
    7760         136 :                     pszLine = CPLReadLineL(fpSrc);
    7761         136 :                     if (pszLine == nullptr)
    7762           0 :                         break;
    7763         272 :                     CPLString osVal(pszLine);
    7764         136 :                     osVal.Trim();
    7765         136 :                     osAccVal += osVal;
    7766             :                 }
    7767          17 :                 if (pszLine == nullptr)
    7768           0 :                     break;
    7769          17 :                 osAccVal.resize(osAccVal.size() - 1);
    7770             : 
    7771             :                 const std::vector<int> aoDimIds =
    7772          34 :                     oMapVarIdToVectorOfDimId[nVarId];
    7773          17 :                 size_t nSize = 1;
    7774          34 :                 std::vector<size_t> aoStart, aoEdge;
    7775          17 :                 aoStart.resize(aoDimIds.size());
    7776          17 :                 aoEdge.resize(aoDimIds.size());
    7777          35 :                 for (size_t i = 0; i < aoDimIds.size(); ++i)
    7778             :                 {
    7779          18 :                     const size_t nDimSize = oMapDimIdToDimLen[aoDimIds[i]];
    7780          36 :                     if (nDimSize != 0 &&
    7781          18 :                         nSize > std::numeric_limits<size_t>::max() / nDimSize)
    7782             :                     {
    7783           0 :                         nSize = 0;
    7784             :                     }
    7785             :                     else
    7786             :                     {
    7787          18 :                         nSize *= nDimSize;
    7788             :                     }
    7789          18 :                     aoStart[i] = 0;
    7790          18 :                     aoEdge[i] = nDimSize;
    7791             :                 }
    7792             : 
    7793          17 :                 status = NC_EBADTYPE;
    7794          17 :                 if (nSize == 0)
    7795             :                 {
    7796             :                     // Might happen with a unlimited dimension
    7797             :                 }
    7798          17 :                 else if (oMapVarIdToType[nVarId] == NC_DOUBLE)
    7799             :                 {
    7800           8 :                     if (!aoStart.empty())
    7801             :                     {
    7802             :                         char **papszTokens =
    7803           8 :                             CSLTokenizeString2(osAccVal, " ,;", 0);
    7804           8 :                         size_t nTokens = CSLCount(papszTokens);
    7805           8 :                         if (nTokens >= nSize)
    7806             :                         {
    7807             :                             double *padfVals = static_cast<double *>(
    7808           8 :                                 VSI_CALLOC_VERBOSE(nSize, sizeof(double)));
    7809           8 :                             if (padfVals)
    7810             :                             {
    7811         132 :                                 for (size_t i = 0; i < nSize; i++)
    7812             :                                 {
    7813         124 :                                     padfVals[i] = CPLAtof(papszTokens[i]);
    7814             :                                 }
    7815           8 :                                 status = nc_put_vara_double(
    7816           8 :                                     nCdfId, nVarId, &aoStart[0], &aoEdge[0],
    7817             :                                     padfVals);
    7818           8 :                                 VSIFree(padfVals);
    7819             :                             }
    7820             :                         }
    7821           8 :                         CSLDestroy(papszTokens);
    7822             :                     }
    7823             :                 }
    7824           9 :                 else if (oMapVarIdToType[nVarId] == NC_BYTE)
    7825             :                 {
    7826           3 :                     if (!aoStart.empty())
    7827             :                     {
    7828             :                         char **papszTokens =
    7829           3 :                             CSLTokenizeString2(osAccVal, " ,;", 0);
    7830           3 :                         size_t nTokens = CSLCount(papszTokens);
    7831           3 :                         if (nTokens >= nSize)
    7832             :                         {
    7833             :                             signed char *panVals = static_cast<signed char *>(
    7834           3 :                                 VSI_CALLOC_VERBOSE(nSize, sizeof(signed char)));
    7835           3 :                             if (panVals)
    7836             :                             {
    7837        1203 :                                 for (size_t i = 0; i < nSize; i++)
    7838             :                                 {
    7839        1200 :                                     panVals[i] = static_cast<signed char>(
    7840        1200 :                                         atoi(papszTokens[i]));
    7841             :                                 }
    7842           3 :                                 status = nc_put_vara_schar(nCdfId, nVarId,
    7843           3 :                                                            &aoStart[0],
    7844           3 :                                                            &aoEdge[0], panVals);
    7845           3 :                                 VSIFree(panVals);
    7846             :                             }
    7847             :                         }
    7848           3 :                         CSLDestroy(papszTokens);
    7849             :                     }
    7850             :                 }
    7851           6 :                 else if (oMapVarIdToType[nVarId] == NC_CHAR)
    7852             :                 {
    7853           6 :                     if (aoStart.size() == 2)
    7854             :                     {
    7855           4 :                         std::vector<CPLString> aoStrings;
    7856           2 :                         bool bInString = false;
    7857           4 :                         CPLString osCurString;
    7858         935 :                         for (size_t i = 0; i < osAccVal.size();)
    7859             :                         {
    7860         933 :                             if (!bInString)
    7861             :                             {
    7862           8 :                                 if (osAccVal[i] == '"')
    7863             :                                 {
    7864           4 :                                     bInString = true;
    7865           4 :                                     osCurString.clear();
    7866             :                                 }
    7867           8 :                                 i++;
    7868             :                             }
    7869         926 :                             else if (osAccVal[i] == '\\' &&
    7870         926 :                                      i + 1 < osAccVal.size() &&
    7871           1 :                                      osAccVal[i + 1] == '"')
    7872             :                             {
    7873           1 :                                 osCurString += '"';
    7874           1 :                                 i += 2;
    7875             :                             }
    7876         924 :                             else if (osAccVal[i] == '"')
    7877             :                             {
    7878           4 :                                 aoStrings.push_back(osCurString);
    7879           4 :                                 osCurString.clear();
    7880           4 :                                 bInString = false;
    7881           4 :                                 i++;
    7882             :                             }
    7883             :                             else
    7884             :                             {
    7885         920 :                                 osCurString += osAccVal[i];
    7886         920 :                                 i++;
    7887             :                             }
    7888             :                         }
    7889           2 :                         const size_t nRecords = oMapDimIdToDimLen[aoDimIds[0]];
    7890           2 :                         const size_t nWidth = oMapDimIdToDimLen[aoDimIds[1]];
    7891           2 :                         size_t nIters = aoStrings.size();
    7892           2 :                         if (nIters > nRecords)
    7893           0 :                             nIters = nRecords;
    7894           6 :                         for (size_t i = 0; i < nIters; i++)
    7895             :                         {
    7896             :                             size_t anIndex[2];
    7897           4 :                             anIndex[0] = i;
    7898           4 :                             anIndex[1] = 0;
    7899             :                             size_t anCount[2];
    7900           4 :                             anCount[0] = 1;
    7901           4 :                             anCount[1] = aoStrings[i].size();
    7902           4 :                             if (anCount[1] > nWidth)
    7903           0 :                                 anCount[1] = nWidth;
    7904             :                             status =
    7905           4 :                                 nc_put_vara_text(nCdfId, nVarId, anIndex,
    7906           4 :                                                  anCount, aoStrings[i].c_str());
    7907           4 :                             if (status != NC_NOERR)
    7908           0 :                                 break;
    7909             :                         }
    7910             :                     }
    7911             :                 }
    7912          17 :                 if (status != NC_NOERR)
    7913             :                 {
    7914           4 :                     CPLDebug("netCDF", "nc_put_var_(%s) failed: %s",
    7915             :                              osVarName.c_str(), nc_strerror(status));
    7916             :                 }
    7917             :             }
    7918             :         }
    7919             :     }
    7920             : 
    7921           4 :     GDAL_nc_close(nCdfId);
    7922           4 :     return true;
    7923             : }
    7924             : 
    7925             : #endif  // ENABLE_NCDUMP
    7926             : 
    7927             : /************************************************************************/
    7928             : /*                                Open()                                */
    7929             : /************************************************************************/
    7930             : 
    7931         654 : GDALDataset *netCDFDataset::Open(GDALOpenInfo *poOpenInfo)
    7932             : 
    7933             : {
    7934             : #ifdef NCDF_DEBUG
    7935             :     CPLDebug("GDAL_netCDF", "\n=====\nOpen(), filename=[%s]",
    7936             :              poOpenInfo->pszFilename);
    7937             : #endif
    7938             : 
    7939             :     // Does this appear to be a netcdf file?
    7940         654 :     NetCDFFormatEnum eTmpFormat = NCDF_FORMAT_NONE;
    7941         654 :     if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
    7942             :     {
    7943         597 :         eTmpFormat = netCDFIdentifyFormat(poOpenInfo, /* bCheckExt = */ true);
    7944             : #ifdef NCDF_DEBUG
    7945             :         CPLDebug("GDAL_netCDF", "identified format %d", eTmpFormat);
    7946             : #endif
    7947             :         // Note: not calling Identify() directly, because we want the file type.
    7948             :         // Only support NCDF_FORMAT* formats.
    7949         597 :         if (!(NCDF_FORMAT_NC == eTmpFormat || NCDF_FORMAT_NC2 == eTmpFormat ||
    7950             :               NCDF_FORMAT_NC4 == eTmpFormat || NCDF_FORMAT_NC4C == eTmpFormat))
    7951           2 :             return nullptr;
    7952             :     }
    7953             :     else
    7954             :     {
    7955             : #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
    7956             :         // We don't necessarily want to catch bugs in libnetcdf ...
    7957             :         if (CPLGetConfigOption("DISABLE_OPEN_REAL_NETCDF_FILES", nullptr))
    7958             :         {
    7959             :             return nullptr;
    7960             :         }
    7961             : #endif
    7962             :     }
    7963             : 
    7964         652 :     if (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER)
    7965             :     {
    7966         169 :         return OpenMultiDim(poOpenInfo);
    7967             :     }
    7968             : 
    7969         966 :     CPLMutexHolderD(&hNCMutex);
    7970             : 
    7971         483 :     CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock with
    7972             :         // GDALDataset own mutex.
    7973         483 :     netCDFDataset *poDS = new netCDFDataset();
    7974         483 :     poDS->papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
    7975         483 :     CPLAcquireMutex(hNCMutex, 1000.0);
    7976             : 
    7977         483 :     poDS->SetDescription(poOpenInfo->pszFilename);
    7978             : 
    7979             :     // Check if filename start with NETCDF: tag.
    7980         483 :     bool bTreatAsSubdataset = false;
    7981         966 :     CPLString osSubdatasetName;
    7982             : 
    7983             : #ifdef ENABLE_NCDUMP
    7984         483 :     const char *pszHeader =
    7985             :         reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
    7986         483 :     if (poOpenInfo->fpL != nullptr && STARTS_WITH(pszHeader, "netcdf ") &&
    7987           3 :         strstr(pszHeader, "dimensions:") && strstr(pszHeader, "variables:"))
    7988             :     {
    7989             :         // By default create a temporary file that will be destroyed,
    7990             :         // unless NETCDF_TMP_FILE is defined. Can be useful to see which
    7991             :         // netCDF file has been generated from a potential fuzzed input.
    7992           3 :         poDS->osFilename = CPLGetConfigOption("NETCDF_TMP_FILE", "");
    7993           3 :         if (poDS->osFilename.empty())
    7994             :         {
    7995           3 :             poDS->bFileToDestroyAtClosing = true;
    7996           3 :             poDS->osFilename = CPLGenerateTempFilename("netcdf_tmp");
    7997             :         }
    7998           3 :         if (!netCDFDatasetCreateTempFile(eTmpFormat, poDS->osFilename,
    7999             :                                          poOpenInfo->fpL))
    8000             :         {
    8001           0 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8002             :                 // deadlock with GDALDataset own mutex.
    8003           0 :             delete poDS;
    8004           0 :             CPLAcquireMutex(hNCMutex, 1000.0);
    8005           0 :             return nullptr;
    8006             :         }
    8007           3 :         bTreatAsSubdataset = false;
    8008           3 :         poDS->eFormat = eTmpFormat;
    8009             :     }
    8010             :     else
    8011             : #endif
    8012             : 
    8013         480 :         if (STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
    8014             :     {
    8015             :         char **papszName =
    8016          57 :             CSLTokenizeString2(poOpenInfo->pszFilename, ":",
    8017             :                                CSLT_HONOURSTRINGS | CSLT_PRESERVEESCAPES);
    8018             : 
    8019         114 :         if (CSLCount(papszName) >= 3 &&
    8020          57 :             ((strlen(papszName[1]) == 1 && /* D:\\bla */
    8021           0 :               (papszName[2][0] == '/' || papszName[2][0] == '\\')) ||
    8022          57 :              EQUAL(papszName[1], "http") || EQUAL(papszName[1], "https") ||
    8023          57 :              EQUAL(papszName[1], "/vsicurl/http") ||
    8024          57 :              EQUAL(papszName[1], "/vsicurl/https") ||
    8025          57 :              EQUAL(papszName[1], "/vsicurl_streaming/http") ||
    8026          57 :              EQUAL(papszName[1], "/vsicurl_streaming/https")))
    8027             :         {
    8028           0 :             const int nCountBefore = CSLCount(papszName);
    8029           0 :             CPLString osTmp = papszName[1];
    8030           0 :             osTmp += ':';
    8031           0 :             osTmp += papszName[2];
    8032           0 :             CPLFree(papszName[1]);
    8033           0 :             CPLFree(papszName[2]);
    8034           0 :             papszName[1] = CPLStrdup(osTmp);
    8035           0 :             memmove(papszName + 2, papszName + 3,
    8036           0 :                     (nCountBefore - 2) * sizeof(char *));
    8037             :         }
    8038             : 
    8039          57 :         if (CSLCount(papszName) == 3)
    8040             :         {
    8041          57 :             poDS->osFilename = papszName[1];
    8042          57 :             osSubdatasetName = papszName[2];
    8043          57 :             bTreatAsSubdataset = true;
    8044          57 :             CSLDestroy(papszName);
    8045             :         }
    8046           0 :         else if (CSLCount(papszName) == 2)
    8047             :         {
    8048           0 :             poDS->osFilename = papszName[1];
    8049           0 :             osSubdatasetName = "";
    8050           0 :             bTreatAsSubdataset = false;
    8051           0 :             CSLDestroy(papszName);
    8052             :         }
    8053             :         else
    8054             :         {
    8055           0 :             CSLDestroy(papszName);
    8056           0 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8057             :                 // deadlock with GDALDataset own mutex.
    8058           0 :             delete poDS;
    8059           0 :             CPLAcquireMutex(hNCMutex, 1000.0);
    8060           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    8061             :                      "Failed to parse NETCDF: prefix string into expected 2, 3 "
    8062             :                      "or 4 fields.");
    8063           0 :             return nullptr;
    8064             :         }
    8065             : 
    8066         114 :         if (!STARTS_WITH(poDS->osFilename, "http://") &&
    8067          57 :             !STARTS_WITH(poDS->osFilename, "https://"))
    8068             :         {
    8069             :             // Identify Format from real file, with bCheckExt=FALSE.
    8070             :             GDALOpenInfo *poOpenInfo2 =
    8071          57 :                 new GDALOpenInfo(poDS->osFilename.c_str(), GA_ReadOnly);
    8072          57 :             poDS->eFormat =
    8073          57 :                 netCDFIdentifyFormat(poOpenInfo2, /* bCheckExt = */ false);
    8074          57 :             delete poOpenInfo2;
    8075          57 :             if (NCDF_FORMAT_NONE == poDS->eFormat ||
    8076          57 :                 NCDF_FORMAT_UNKNOWN == poDS->eFormat)
    8077             :             {
    8078           0 :                 CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8079             :                     // deadlock with GDALDataset own mutex.
    8080           0 :                 delete poDS;
    8081           0 :                 CPLAcquireMutex(hNCMutex, 1000.0);
    8082           0 :                 return nullptr;
    8083             :             }
    8084             :         }
    8085             :     }
    8086             :     else
    8087             :     {
    8088         423 :         poDS->osFilename = poOpenInfo->pszFilename;
    8089         423 :         bTreatAsSubdataset = false;
    8090         423 :         poDS->eFormat = eTmpFormat;
    8091             :     }
    8092             : 
    8093             : // Try opening the dataset.
    8094             : #if defined(NCDF_DEBUG) && defined(ENABLE_UFFD)
    8095             :     CPLDebug("GDAL_netCDF", "calling nc_open_mem(%s)",
    8096             :              poDS->osFilename.c_str());
    8097             : #elif defined(NCDF_DEBUG) && !defined(ENABLE_UFFD)
    8098             :     CPLDebug("GDAL_netCDF", "calling nc_open(%s)", poDS->osFilename.c_str());
    8099             : #endif
    8100         483 :     int cdfid = -1;
    8101         483 :     const int nMode = ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE) != 0)
    8102             :                           ? NC_WRITE
    8103             :                           : NC_NOWRITE;
    8104         966 :     CPLString osFilenameForNCOpen(poDS->osFilename);
    8105             : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
    8106             :     if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
    8107             :     {
    8108             :         char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
    8109             :         osFilenameForNCOpen = pszTemp;
    8110             :         CPLFree(pszTemp);
    8111             :     }
    8112             : #endif
    8113         483 :     int status2 = -1;
    8114             : 
    8115             : #ifdef ENABLE_UFFD
    8116         483 :     cpl_uffd_context *pCtx = nullptr;
    8117             : #endif
    8118             : 
    8119         498 :     if (STARTS_WITH(osFilenameForNCOpen, "/vsimem/") &&
    8120          15 :         poOpenInfo->eAccess == GA_ReadOnly)
    8121             :     {
    8122          15 :         vsi_l_offset nLength = 0;
    8123          15 :         poDS->fpVSIMEM = VSIFOpenL(osFilenameForNCOpen, "rb");
    8124          15 :         if (poDS->fpVSIMEM)
    8125             :         {
    8126             :             // We assume that the file will not be modified. If it is, then
    8127             :             // pabyBuffer might become invalid.
    8128             :             GByte *pabyBuffer =
    8129          15 :                 VSIGetMemFileBuffer(osFilenameForNCOpen, &nLength, false);
    8130          15 :             if (pabyBuffer)
    8131             :             {
    8132          15 :                 status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen),
    8133             :                                       nMode, static_cast<size_t>(nLength),
    8134             :                                       pabyBuffer, &cdfid);
    8135             :             }
    8136             :         }
    8137             :     }
    8138             :     else
    8139             :     {
    8140             :         const bool bVsiFile =
    8141         468 :             !strncmp(osFilenameForNCOpen, "/vsi", strlen("/vsi"));
    8142             : #ifdef ENABLE_UFFD
    8143         468 :         bool bReadOnly = (poOpenInfo->eAccess == GA_ReadOnly);
    8144         468 :         void *pVma = nullptr;
    8145         468 :         uint64_t nVmaSize = 0;
    8146             : 
    8147         468 :         if (bVsiFile)
    8148             :         {
    8149           1 :             if (bReadOnly)
    8150             :             {
    8151           1 :                 if (CPLIsUserFaultMappingSupported())
    8152             :                 {
    8153           1 :                     pCtx = CPLCreateUserFaultMapping(osFilenameForNCOpen, &pVma,
    8154             :                                                      &nVmaSize);
    8155             :                 }
    8156             :                 else
    8157             :                 {
    8158           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    8159             :                              "Opening a /vsi file with the netCDF driver "
    8160             :                              "requires Linux userfaultfd to be available. "
    8161             :                              "If running from Docker, "
    8162             :                              "--security-opt seccomp=unconfined might be "
    8163             :                              "needed.%s",
    8164           0 :                              ((poDS->eFormat == NCDF_FORMAT_NC4 ||
    8165           0 :                                poDS->eFormat == NCDF_FORMAT_HDF5) &&
    8166           0 :                               GDALGetDriverByName("HDF5"))
    8167             :                                  ? " Or you may set the GDAL_SKIP=netCDF "
    8168             :                                    "configuration option to force the use of "
    8169             :                                    "the HDF5 driver."
    8170             :                                  : "");
    8171             :                 }
    8172             :             }
    8173             :             else
    8174             :             {
    8175           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    8176             :                          "Opening a /vsi file with the netCDF driver is only "
    8177             :                          "supported in read-only mode");
    8178             :             }
    8179             :         }
    8180         468 :         if (pCtx != nullptr && pVma != nullptr && nVmaSize > 0)
    8181             :         {
    8182             :             // netCDF code, at least for netCDF 4.7.0, is confused by filenames
    8183             :             // like /vsicurl/http[s]://example.com/foo.nc, so just pass the
    8184             :             // final part
    8185           1 :             status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen), nMode,
    8186             :                                   static_cast<size_t>(nVmaSize), pVma, &cdfid);
    8187             :         }
    8188             :         else
    8189         467 :             status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
    8190             : #else
    8191             :         if (bVsiFile)
    8192             :         {
    8193             :             CPLError(
    8194             :                 CE_Failure, CPLE_AppDefined,
    8195             :                 "Opening a /vsi file with the netCDF driver requires Linux "
    8196             :                 "userfaultfd to be available.%s",
    8197             :                 ((poDS->eFormat == NCDF_FORMAT_NC4 ||
    8198             :                   poDS->eFormat == NCDF_FORMAT_HDF5) &&
    8199             :                  GDALGetDriverByName("HDF5"))
    8200             :                     ? " Or you may set the GDAL_SKIP=netCDF "
    8201             :                       "configuration option to force the use of the HDF5 "
    8202             :                       "driver."
    8203             :                     : "");
    8204             :             status2 = NC_EIO;
    8205             :         }
    8206             :         else
    8207             :         {
    8208             :             status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
    8209             :         }
    8210             : #endif
    8211             :     }
    8212         483 :     if (status2 != NC_NOERR)
    8213             :     {
    8214             : #ifdef NCDF_DEBUG
    8215             :         CPLDebug("GDAL_netCDF", "error opening");
    8216             : #endif
    8217           0 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8218             :             // with GDALDataset own mutex.
    8219           0 :         delete poDS;
    8220           0 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8221           0 :         return nullptr;
    8222             :     }
    8223             : #ifdef NCDF_DEBUG
    8224             :     CPLDebug("GDAL_netCDF", "got cdfid=%d", cdfid);
    8225             : #endif
    8226             : 
    8227             : #if defined(ENABLE_NCDUMP) && !defined(_WIN32)
    8228             :     // Try to destroy the temporary file right now on Unix
    8229         483 :     if (poDS->bFileToDestroyAtClosing)
    8230             :     {
    8231           3 :         if (VSIUnlink(poDS->osFilename) == 0)
    8232             :         {
    8233           3 :             poDS->bFileToDestroyAtClosing = false;
    8234             :         }
    8235             :     }
    8236             : #endif
    8237             : 
    8238             :     // Is this a real netCDF file?
    8239             :     int ndims;
    8240             :     int ngatts;
    8241             :     int nvars;
    8242             :     int unlimdimid;
    8243         483 :     int status = nc_inq(cdfid, &ndims, &nvars, &ngatts, &unlimdimid);
    8244         483 :     if (status != NC_NOERR)
    8245             :     {
    8246           0 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8247             :             // with GDALDataset own mutex.
    8248           0 :         delete poDS;
    8249           0 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8250           0 :         return nullptr;
    8251             :     }
    8252             : 
    8253             :     // Get file type from netcdf.
    8254         483 :     int nTmpFormat = NCDF_FORMAT_NONE;
    8255         483 :     status = nc_inq_format(cdfid, &nTmpFormat);
    8256         483 :     if (status != NC_NOERR)
    8257             :     {
    8258           0 :         NCDF_ERR(status);
    8259             :     }
    8260             :     else
    8261             :     {
    8262         483 :         CPLDebug("GDAL_netCDF",
    8263             :                  "driver detected file type=%d, libnetcdf detected type=%d",
    8264         483 :                  poDS->eFormat, nTmpFormat);
    8265         483 :         if (static_cast<NetCDFFormatEnum>(nTmpFormat) != poDS->eFormat)
    8266             :         {
    8267             :             // Warn if file detection conflicts with that from libnetcdf
    8268             :             // except for NC4C, which we have no way of detecting initially.
    8269          24 :             if (nTmpFormat != NCDF_FORMAT_NC4C &&
    8270          12 :                 !STARTS_WITH(poDS->osFilename, "http://") &&
    8271           0 :                 !STARTS_WITH(poDS->osFilename, "https://"))
    8272             :             {
    8273           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    8274             :                          "NetCDF driver detected file type=%d, but libnetcdf "
    8275             :                          "detected type=%d",
    8276           0 :                          poDS->eFormat, nTmpFormat);
    8277             :             }
    8278          12 :             CPLDebug("GDAL_netCDF", "setting file type to %d, was %d",
    8279          12 :                      nTmpFormat, poDS->eFormat);
    8280          12 :             poDS->eFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
    8281             :         }
    8282             :     }
    8283             : 
    8284             :     // Does the request variable exist?
    8285         483 :     if (bTreatAsSubdataset)
    8286             :     {
    8287             :         int dummy;
    8288          57 :         if (NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &dummy,
    8289          57 :                                &dummy) != CE_None)
    8290             :         {
    8291           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    8292             :                      "%s is a netCDF file, but %s is not a variable.",
    8293             :                      poOpenInfo->pszFilename, osSubdatasetName.c_str());
    8294             : 
    8295           0 :             GDAL_nc_close(cdfid);
    8296             : #ifdef ENABLE_UFFD
    8297           0 :             NETCDF_UFFD_UNMAP(pCtx);
    8298             : #endif
    8299           0 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8300             :                 // deadlock with GDALDataset own mutex.
    8301           0 :             delete poDS;
    8302           0 :             CPLAcquireMutex(hNCMutex, 1000.0);
    8303           0 :             return nullptr;
    8304             :         }
    8305             :     }
    8306             : 
    8307             :     // Figure out whether or not the listed dataset has support for simple
    8308             :     // geometries (CF-1.8)
    8309         483 :     poDS->nCFVersion = nccfdriver::getCFVersion(cdfid);
    8310         483 :     bool bHasSimpleGeometries = false;  // but not necessarily valid
    8311         483 :     if (poDS->nCFVersion >= 1.8)
    8312             :     {
    8313          72 :         poDS->bSGSupport = true;
    8314          72 :         bHasSimpleGeometries = poDS->DetectAndFillSGLayers(cdfid);
    8315          72 :         poDS->vcdf.enableFullVirtualMode();
    8316             :     }
    8317             :     else
    8318             :     {
    8319         411 :         poDS->bSGSupport = false;
    8320             :     }
    8321             : 
    8322             :     char szConventions[NC_MAX_NAME + 1];
    8323         483 :     szConventions[0] = '\0';
    8324         483 :     nc_type nAttype = NC_NAT;
    8325         483 :     size_t nAttlen = 0;
    8326         483 :     nc_inq_att(cdfid, NC_GLOBAL, "Conventions", &nAttype, &nAttlen);
    8327         966 :     if (nAttlen >= sizeof(szConventions) ||
    8328         483 :         nc_get_att_text(cdfid, NC_GLOBAL, "Conventions", szConventions) !=
    8329             :             NC_NOERR)
    8330             :     {
    8331          55 :         CPLDebug("GDAL_netCDF", "No UNIDATA NC_GLOBAL:Conventions attribute");
    8332             :         // Note that 'Conventions' is always capital 'C' in CF spec.
    8333             :     }
    8334             :     else
    8335             :     {
    8336         428 :         szConventions[nAttlen] = '\0';
    8337             :     }
    8338             : 
    8339             :     // Create band information objects.
    8340         483 :     CPLDebug("GDAL_netCDF", "var_count = %d", nvars);
    8341             : 
    8342             :     // Create a corresponding GDALDataset.
    8343             :     // Create Netcdf Subdataset if filename as NETCDF tag.
    8344         483 :     poDS->cdfid = cdfid;
    8345             : #ifdef ENABLE_UFFD
    8346         483 :     poDS->pCtx = pCtx;
    8347             : #endif
    8348         483 :     poDS->eAccess = poOpenInfo->eAccess;
    8349         483 :     poDS->bDefineMode = false;
    8350             : 
    8351         483 :     poDS->ReadAttributes(cdfid, NC_GLOBAL);
    8352             : 
    8353             :     // Identify coordinate and boundary variables that we should
    8354             :     // ignore as Raster Bands.
    8355         483 :     char **papszIgnoreVars = nullptr;
    8356         483 :     NCDFGetCoordAndBoundVarFullNames(cdfid, &papszIgnoreVars);
    8357             :     // Filter variables to keep only valid 2+D raster bands and vector fields.
    8358         483 :     int nRasterVars = 0;
    8359         483 :     int nIgnoredVars = 0;
    8360         483 :     int nGroupID = -1;
    8361         483 :     int nVarID = -1;
    8362             : 
    8363             :     std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>
    8364         966 :         oMap2DDimsToGroupAndVar;
    8365        1125 :     if ((poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
    8366         159 :         STARTS_WITH(CSLFetchNameValueDef(poDS->papszMetadata,
    8367             :                                          "NC_GLOBAL#mission_name", ""),
    8368           1 :                     "Sentinel 3") &&
    8369           1 :         EQUAL(CSLFetchNameValueDef(poDS->papszMetadata,
    8370             :                                    "NC_GLOBAL#altimeter_sensor_name", ""),
    8371         642 :               "SRAL") &&
    8372           1 :         EQUAL(CSLFetchNameValueDef(poDS->papszMetadata,
    8373             :                                    "NC_GLOBAL#radiometer_sensor_name", ""),
    8374             :               "MWR"))
    8375             :     {
    8376           1 :         if (poDS->eAccess == GA_Update)
    8377             :         {
    8378           0 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8379             :                 // deadlock with GDALDataset own mutex.
    8380           0 :             delete poDS;
    8381           0 :             return nullptr;
    8382             :         }
    8383           1 :         poDS->ProcessSentinel3_SRAL_MWR();
    8384             :     }
    8385             :     else
    8386             :     {
    8387         482 :         poDS->FilterVars(cdfid, (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0,
    8388         640 :                          (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
    8389         158 :                              !bHasSimpleGeometries,
    8390             :                          papszIgnoreVars, &nRasterVars, &nGroupID, &nVarID,
    8391             :                          &nIgnoredVars, oMap2DDimsToGroupAndVar);
    8392             :     }
    8393         483 :     CSLDestroy(papszIgnoreVars);
    8394             : 
    8395             :     // Case where there is no raster variable
    8396         483 :     if (nRasterVars == 0 && !bTreatAsSubdataset)
    8397             :     {
    8398         122 :         poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
    8399         122 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8400             :             // with GDALDataset own mutex.
    8401         122 :         poDS->TryLoadXML();
    8402             :         // If the dataset has been opened in raster mode only, exit
    8403         122 :         if ((poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0 &&
    8404          12 :             (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) == 0)
    8405             :         {
    8406           4 :             delete poDS;
    8407           4 :             poDS = nullptr;
    8408             :         }
    8409             :         // Otherwise if the dataset is opened in vector mode, that there is
    8410             :         // no vector layer and we are in read-only, exit too.
    8411         118 :         else if (poDS->GetLayerCount() == 0 &&
    8412         126 :                  (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
    8413           8 :                  poOpenInfo->eAccess == GA_ReadOnly)
    8414             :         {
    8415           8 :             delete poDS;
    8416           8 :             poDS = nullptr;
    8417             :         }
    8418         122 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8419         122 :         return poDS;
    8420             :     }
    8421             : 
    8422             :     // We have more than one variable with 2 dimensions in the
    8423             :     // file, then treat this as a subdataset container dataset.
    8424         361 :     bool bSeveralVariablesAsBands = false;
    8425         361 :     if ((nRasterVars > 1) && !bTreatAsSubdataset)
    8426             :     {
    8427          24 :         if (CPLFetchBool(poOpenInfo->papszOpenOptions, "VARIABLES_AS_BANDS",
    8428          29 :                          false) &&
    8429           5 :             oMap2DDimsToGroupAndVar.size() == 1)
    8430             :         {
    8431           5 :             std::tie(nGroupID, nVarID) =
    8432          10 :                 oMap2DDimsToGroupAndVar.begin()->second.front();
    8433           5 :             bSeveralVariablesAsBands = true;
    8434             :         }
    8435             :         else
    8436             :         {
    8437          19 :             poDS->CreateSubDatasetList(cdfid);
    8438          19 :             poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
    8439          19 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8440             :                 // deadlock with GDALDataset own mutex.
    8441          19 :             poDS->TryLoadXML();
    8442          19 :             CPLAcquireMutex(hNCMutex, 1000.0);
    8443          19 :             return poDS;
    8444             :         }
    8445             :     }
    8446             : 
    8447             :     // If we are not treating things as a subdataset, then capture
    8448             :     // the name of the single available variable as the subdataset.
    8449         342 :     if (!bTreatAsSubdataset)
    8450             :     {
    8451         285 :         char *pszVarName = nullptr;
    8452         285 :         NCDF_ERR(NCDFGetVarFullName(nGroupID, nVarID, &pszVarName));
    8453         285 :         osSubdatasetName = (pszVarName != nullptr ? pszVarName : "");
    8454         285 :         CPLFree(pszVarName);
    8455             :     }
    8456             : 
    8457             :     // We have ignored at least one variable, so we should report them
    8458             :     // as subdatasets for reference.
    8459         342 :     if (nIgnoredVars > 0 && !bTreatAsSubdataset)
    8460             :     {
    8461          24 :         CPLDebug("GDAL_netCDF",
    8462             :                  "As %d variables were ignored, creating subdataset list "
    8463             :                  "for reference. Variable #%d [%s] is the main variable",
    8464             :                  nIgnoredVars, nVarID, osSubdatasetName.c_str());
    8465          24 :         poDS->CreateSubDatasetList(cdfid);
    8466             :     }
    8467             : 
    8468             :     // Open the NETCDF subdataset NETCDF:"filename":subdataset.
    8469         342 :     int var = -1;
    8470         342 :     NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &nGroupID, &var);
    8471             :     // Now we can forget the root cdfid and only use the selected group.
    8472         342 :     cdfid = nGroupID;
    8473         342 :     int nd = 0;
    8474         342 :     nc_inq_varndims(cdfid, var, &nd);
    8475             : 
    8476         342 :     poDS->m_anDimIds.resize(nd);
    8477             : 
    8478             :     // X, Y, Z position in array
    8479         684 :     std::vector<int> anBandDimPos(nd);
    8480             : 
    8481         342 :     nc_inq_vardimid(cdfid, var, poDS->m_anDimIds.data());
    8482             : 
    8483             :     // Check if somebody tried to pass a variable with less than 1D.
    8484         342 :     if (nd < 1)
    8485             :     {
    8486           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    8487             :                  "Variable has %d dimension(s) - not supported.", nd);
    8488           0 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8489             :             // with GDALDataset own mutex.
    8490           0 :         delete poDS;
    8491           0 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8492           0 :         return nullptr;
    8493             :     }
    8494             : 
    8495             :     // CF-1 Convention
    8496             :     //
    8497             :     // Dimensions to appear in the relative order T, then Z, then Y,
    8498             :     // then X  to the file. All other dimensions should, whenever
    8499             :     // possible, be placed to the left of the spatiotemporal
    8500             :     // dimensions.
    8501             : 
    8502             :     // Verify that dimensions are in the {T,Z,Y,X} or {T,Z,Y,X} order
    8503             :     // Ideally we should detect for other ordering and act accordingly
    8504             :     // Only done if file has Conventions=CF-* and only prints warning
    8505             :     // To disable set GDAL_NETCDF_VERIFY_DIMS=NO and to use only
    8506             :     // attributes (not varnames) set GDAL_NETCDF_VERIFY_DIMS=STRICT
    8507             :     const bool bCheckDims =
    8508         684 :         CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES")) &&
    8509         342 :         STARTS_WITH_CI(szConventions, "CF");
    8510             : 
    8511         342 :     if (nd >= 2 && bCheckDims)
    8512             :     {
    8513         258 :         char szDimName1[NC_MAX_NAME + 1] = {};
    8514         258 :         char szDimName2[NC_MAX_NAME + 1] = {};
    8515         258 :         status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 1], szDimName1);
    8516         258 :         NCDF_ERR(status);
    8517         258 :         status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 2], szDimName2);
    8518         258 :         NCDF_ERR(status);
    8519         415 :         if (NCDFIsVarLongitude(cdfid, -1, szDimName1) == false &&
    8520         157 :             NCDFIsVarProjectionX(cdfid, -1, szDimName1) == false)
    8521             :         {
    8522           4 :             CPLError(CE_Warning, CPLE_AppDefined,
    8523             :                      "dimension #%d (%s) is not a Longitude/X dimension.",
    8524             :                      nd - 1, szDimName1);
    8525             :         }
    8526         415 :         if (NCDFIsVarLatitude(cdfid, -1, szDimName2) == false &&
    8527         157 :             NCDFIsVarProjectionY(cdfid, -1, szDimName2) == false)
    8528             :         {
    8529           4 :             CPLError(CE_Warning, CPLE_AppDefined,
    8530             :                      "dimension #%d (%s) is not a Latitude/Y dimension.",
    8531             :                      nd - 2, szDimName2);
    8532             :         }
    8533         258 :         if ((NCDFIsVarLongitude(cdfid, -1, szDimName2) ||
    8534         260 :              NCDFIsVarProjectionX(cdfid, -1, szDimName2)) &&
    8535           2 :             (NCDFIsVarLatitude(cdfid, -1, szDimName1) ||
    8536           0 :              NCDFIsVarProjectionY(cdfid, -1, szDimName1)))
    8537             :         {
    8538           2 :             poDS->bSwitchedXY = true;
    8539             :         }
    8540         258 :         if (nd >= 3)
    8541             :         {
    8542          45 :             char szDimName3[NC_MAX_NAME + 1] = {};
    8543             :             status =
    8544          45 :                 nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 3], szDimName3);
    8545          45 :             NCDF_ERR(status);
    8546          45 :             if (nd >= 4)
    8547             :             {
    8548          13 :                 char szDimName4[NC_MAX_NAME + 1] = {};
    8549             :                 status =
    8550          13 :                     nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 4], szDimName4);
    8551          13 :                 NCDF_ERR(status);
    8552          13 :                 if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false)
    8553             :                 {
    8554           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    8555             :                              "dimension #%d (%s) is not a Time dimension.",
    8556             :                              nd - 3, szDimName3);
    8557             :                 }
    8558          13 :                 if (NCDFIsVarTimeCoord(cdfid, -1, szDimName4) == false)
    8559             :                 {
    8560           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    8561             :                              "dimension #%d (%s) is not a Time dimension.",
    8562             :                              nd - 4, szDimName4);
    8563             :                 }
    8564             :             }
    8565             :             else
    8566             :             {
    8567          63 :                 if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false &&
    8568          31 :                     NCDFIsVarTimeCoord(cdfid, -1, szDimName3) == false)
    8569             :                 {
    8570           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    8571             :                              "dimension #%d (%s) is not a "
    8572             :                              "Time or Vertical dimension.",
    8573             :                              nd - 3, szDimName3);
    8574             :                 }
    8575             :             }
    8576             :         }
    8577             :     }
    8578             : 
    8579             :     // For example for EMIT data (https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/),
    8580             :     // dimension order is downtrack, crosstrack, bands
    8581         342 :     bool bYXBandOrder = false;
    8582         342 :     if (nd == 3)
    8583             :     {
    8584          37 :         char szDimName[NC_MAX_NAME + 1] = {};
    8585          37 :         status = nc_inq_dimname(cdfid, poDS->m_anDimIds[2], szDimName);
    8586          37 :         NCDF_ERR(status);
    8587          37 :         bYXBandOrder =
    8588          37 :             strcmp(szDimName, "bands") == 0 || strcmp(szDimName, "band") == 0;
    8589             :     }
    8590             : 
    8591             :     // Get X dimensions information.
    8592             :     size_t xdim;
    8593         342 :     poDS->nXDimID = poDS->m_anDimIds[bYXBandOrder ? 1 : nd - 1];
    8594         342 :     nc_inq_dimlen(cdfid, poDS->nXDimID, &xdim);
    8595             : 
    8596             :     // Get Y dimension information.
    8597             :     size_t ydim;
    8598         342 :     if (nd >= 2)
    8599             :     {
    8600         338 :         poDS->nYDimID = poDS->m_anDimIds[bYXBandOrder ? 0 : nd - 2];
    8601         338 :         nc_inq_dimlen(cdfid, poDS->nYDimID, &ydim);
    8602             :     }
    8603             :     else
    8604             :     {
    8605           4 :         poDS->nYDimID = -1;
    8606           4 :         ydim = 1;
    8607             :     }
    8608             : 
    8609         342 :     if (xdim > INT_MAX || ydim > INT_MAX)
    8610             :     {
    8611           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    8612             :                  "Invalid raster dimensions: " CPL_FRMT_GUIB "x" CPL_FRMT_GUIB,
    8613             :                  static_cast<GUIntBig>(xdim), static_cast<GUIntBig>(ydim));
    8614           0 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8615             :             // with GDALDataset own mutex.
    8616           0 :         delete poDS;
    8617           0 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8618           0 :         return nullptr;
    8619             :     }
    8620             : 
    8621         342 :     poDS->nRasterXSize = static_cast<int>(xdim);
    8622         342 :     poDS->nRasterYSize = static_cast<int>(ydim);
    8623             : 
    8624         342 :     unsigned int k = 0;
    8625        1092 :     for (int j = 0; j < nd; j++)
    8626             :     {
    8627         750 :         if (poDS->m_anDimIds[j] == poDS->nXDimID)
    8628             :         {
    8629         342 :             anBandDimPos[0] = j;  // Save Position of XDim
    8630         342 :             k++;
    8631             :         }
    8632         750 :         if (poDS->m_anDimIds[j] == poDS->nYDimID)
    8633             :         {
    8634         338 :             anBandDimPos[1] = j;  // Save Position of YDim
    8635         338 :             k++;
    8636             :         }
    8637             :     }
    8638             :     // X and Y Dimension Ids were not found!
    8639         342 :     if ((nd >= 2 && k != 2) || (nd == 1 && k != 1))
    8640             :     {
    8641           0 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8642             :             // with GDALDataset own mutex.
    8643           0 :         delete poDS;
    8644           0 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8645           0 :         return nullptr;
    8646             :     }
    8647             : 
    8648             :     // Read Metadata for this variable.
    8649             : 
    8650             :     // Should disable as is also done at band level, except driver needs the
    8651             :     // variables as metadata (e.g. projection).
    8652         342 :     poDS->ReadAttributes(cdfid, var);
    8653             : 
    8654             :     // Read Metadata for each dimension.
    8655         342 :     int *panDimIds = nullptr;
    8656         342 :     NCDFGetVisibleDims(cdfid, &ndims, &panDimIds);
    8657             :     // With NetCDF-4 groups panDimIds is not always [0..dim_count-1] like
    8658             :     // in NetCDF-3 because we see only the dimensions of the selected group
    8659             :     // and its parents.
    8660             :     // poDS->papszDimName is indexed by dim IDs, so it must contains all IDs
    8661             :     // [0..max(panDimIds)], but they are not all useful so we fill names
    8662             :     // of useless dims with empty string.
    8663         342 :     if (panDimIds)
    8664             :     {
    8665         342 :         const int nMaxDimId = *std::max_element(panDimIds, panDimIds + ndims);
    8666         342 :         std::set<int> oSetExistingDimIds;
    8667        1128 :         for (int i = 0; i < ndims; i++)
    8668             :         {
    8669         786 :             oSetExistingDimIds.insert(panDimIds[i]);
    8670             :         }
    8671         342 :         std::set<int> oSetDimIdsUsedByVar;
    8672        1092 :         for (int i = 0; i < nd; i++)
    8673             :         {
    8674         750 :             oSetDimIdsUsedByVar.insert(poDS->m_anDimIds[i]);
    8675             :         }
    8676        1128 :         for (int j = 0; j <= nMaxDimId; j++)
    8677             :         {
    8678             :             // Is j dim used?
    8679         786 :             if (oSetExistingDimIds.find(j) != oSetExistingDimIds.end())
    8680             :             {
    8681             :                 // Useful dim.
    8682         786 :                 char szTemp[NC_MAX_NAME + 1] = {};
    8683         786 :                 status = nc_inq_dimname(cdfid, j, szTemp);
    8684         786 :                 if (status != NC_NOERR)
    8685             :                 {
    8686           0 :                     CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8687             :                         // deadlock with GDALDataset own
    8688             :                         // mutex.
    8689           0 :                     delete poDS;
    8690           0 :                     CPLAcquireMutex(hNCMutex, 1000.0);
    8691           0 :                     return nullptr;
    8692             :                 }
    8693         786 :                 poDS->papszDimName.AddString(szTemp);
    8694             : 
    8695         786 :                 if (oSetDimIdsUsedByVar.find(j) != oSetDimIdsUsedByVar.end())
    8696             :                 {
    8697         750 :                     int nDimGroupId = -1;
    8698         750 :                     int nDimVarId = -1;
    8699         750 :                     if (NCDFResolveVar(cdfid, poDS->papszDimName[j],
    8700         750 :                                        &nDimGroupId, &nDimVarId) == CE_None)
    8701             :                     {
    8702         550 :                         poDS->ReadAttributes(nDimGroupId, nDimVarId);
    8703             :                     }
    8704             :                 }
    8705             :             }
    8706             :             else
    8707             :             {
    8708             :                 // Useless dim.
    8709           0 :                 poDS->papszDimName.AddString("");
    8710             :             }
    8711             :         }
    8712         342 :         CPLFree(panDimIds);
    8713             :     }
    8714             : 
    8715             :     // Set projection info.
    8716         684 :     std::vector<std::string> aosRemovedMDItems;
    8717         342 :     if (nd > 1)
    8718             :     {
    8719         338 :         poDS->SetProjectionFromVar(cdfid, var,
    8720             :                                    /*bReadSRSOnly=*/false,
    8721             :                                    /* pszGivenGM = */ nullptr,
    8722             :                                    /* returnProjStr = */ nullptr,
    8723             :                                    /* sg = */ nullptr, &aosRemovedMDItems);
    8724             :     }
    8725             : 
    8726             :     // Override bottom-up with GDAL_NETCDF_BOTTOMUP config option.
    8727         342 :     const char *pszValue = CPLGetConfigOption("GDAL_NETCDF_BOTTOMUP", nullptr);
    8728         342 :     if (pszValue)
    8729             :     {
    8730          24 :         poDS->bBottomUp = CPLTestBool(pszValue);
    8731          24 :         CPLDebug("GDAL_netCDF",
    8732             :                  "set bBottomUp=%d because GDAL_NETCDF_BOTTOMUP=%s",
    8733          24 :                  static_cast<int>(poDS->bBottomUp), pszValue);
    8734             :     }
    8735             : 
    8736             :     // Save non-spatial dimension info.
    8737             : 
    8738         342 :     int *panBandZLev = nullptr;
    8739         342 :     int nDim = (nd >= 2) ? 2 : 1;
    8740             :     size_t lev_count;
    8741         342 :     size_t nTotLevCount = 1;
    8742         342 :     nc_type nType = NC_NAT;
    8743             : 
    8744         684 :     CPLString osExtraDimNames;
    8745             : 
    8746         342 :     if (nd > 2)
    8747             :     {
    8748          52 :         nDim = 2;
    8749          52 :         panBandZLev = static_cast<int *>(CPLCalloc(nd - 2, sizeof(int)));
    8750             : 
    8751          52 :         osExtraDimNames = "{";
    8752             : 
    8753          52 :         char szDimName[NC_MAX_NAME + 1] = {};
    8754             : 
    8755         226 :         for (int j = 0; j < nd; j++)
    8756             :         {
    8757         296 :             if ((poDS->m_anDimIds[j] != poDS->nXDimID) &&
    8758         122 :                 (poDS->m_anDimIds[j] != poDS->nYDimID))
    8759             :             {
    8760          70 :                 nc_inq_dimlen(cdfid, poDS->m_anDimIds[j], &lev_count);
    8761          70 :                 nTotLevCount *= lev_count;
    8762          70 :                 panBandZLev[nDim - 2] = static_cast<int>(lev_count);
    8763          70 :                 anBandDimPos[nDim] = j;  // Save Position of ZDim
    8764             :                 // Save non-spatial dimension names.
    8765          70 :                 if (nc_inq_dimname(cdfid, poDS->m_anDimIds[j], szDimName) ==
    8766             :                     NC_NOERR)
    8767             :                 {
    8768          70 :                     osExtraDimNames += szDimName;
    8769          70 :                     if (j < nd - 3)
    8770             :                     {
    8771          18 :                         osExtraDimNames += ",";
    8772             :                     }
    8773             : 
    8774          70 :                     int nIdxGroupID = -1;
    8775          70 :                     int nIdxVarID = Get1DVariableIndexedByDimension(
    8776          70 :                         cdfid, poDS->m_anDimIds[j], szDimName, true,
    8777          70 :                         &nIdxGroupID);
    8778          70 :                     poDS->m_anExtraDimGroupIds.push_back(nIdxGroupID);
    8779          70 :                     poDS->m_anExtraDimVarIds.push_back(nIdxVarID);
    8780             : 
    8781          70 :                     if (nIdxVarID >= 0)
    8782             :                     {
    8783          61 :                         nc_inq_vartype(nIdxGroupID, nIdxVarID, &nType);
    8784             :                         char szExtraDimDef[NC_MAX_NAME + 1];
    8785          61 :                         snprintf(szExtraDimDef, sizeof(szExtraDimDef),
    8786             :                                  "{%ld,%d}", (long)lev_count, nType);
    8787             :                         char szTemp[NC_MAX_NAME + 32 + 1];
    8788          61 :                         snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
    8789             :                                  szDimName);
    8790          61 :                         poDS->papszMetadata = CSLSetNameValue(
    8791             :                             poDS->papszMetadata, szTemp, szExtraDimDef);
    8792             : 
    8793             :                         // Retrieving data for unlimited dimensions might be
    8794             :                         // costly on network storage, so don't do it.
    8795             :                         // Each band will capture the value along the extra
    8796             :                         // dimension in its NETCDF_DIM_xxxx band metadata item
    8797             :                         // Addresses use case of
    8798             :                         // https://lists.osgeo.org/pipermail/gdal-dev/2023-May/057209.html
    8799          61 :                         if (VSIIsLocal(osFilenameForNCOpen.c_str()) ||
    8800           0 :                             !NCDFIsUnlimitedDim(poDS->eFormat ==
    8801             :                                                     NCDF_FORMAT_NC4,
    8802           0 :                                                 cdfid, poDS->m_anDimIds[j]))
    8803             :                         {
    8804          61 :                             char *pszTemp = nullptr;
    8805          61 :                             if (NCDFGet1DVar(nIdxGroupID, nIdxVarID,
    8806          61 :                                              &pszTemp) == CE_None)
    8807             :                             {
    8808          61 :                                 snprintf(szTemp, sizeof(szTemp),
    8809             :                                          "NETCDF_DIM_%s_VALUES", szDimName);
    8810          61 :                                 poDS->papszMetadata = CSLSetNameValue(
    8811             :                                     poDS->papszMetadata, szTemp, pszTemp);
    8812          61 :                                 CPLFree(pszTemp);
    8813             :                             }
    8814             :                         }
    8815             :                     }
    8816             :                 }
    8817             :                 else
    8818             :                 {
    8819           0 :                     poDS->m_anExtraDimGroupIds.push_back(-1);
    8820           0 :                     poDS->m_anExtraDimVarIds.push_back(-1);
    8821             :                 }
    8822             : 
    8823          70 :                 nDim++;
    8824             :             }
    8825             :         }
    8826          52 :         osExtraDimNames += "}";
    8827          52 :         poDS->papszMetadata = CSLSetNameValue(
    8828             :             poDS->papszMetadata, "NETCDF_DIM_EXTRA", osExtraDimNames);
    8829             :     }
    8830             : 
    8831             :     // Store Metadata.
    8832         352 :     for (const auto &osStr : aosRemovedMDItems)
    8833          10 :         poDS->papszMetadata =
    8834          10 :             CSLSetNameValue(poDS->papszMetadata, osStr.c_str(), nullptr);
    8835             : 
    8836         342 :     poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
    8837             : 
    8838             :     // Create bands.
    8839             : 
    8840             :     // Arbitrary threshold.
    8841             :     int nMaxBandCount =
    8842         342 :         atoi(CPLGetConfigOption("GDAL_MAX_BAND_COUNT", "32768"));
    8843         342 :     if (nMaxBandCount <= 0)
    8844           0 :         nMaxBandCount = 32768;
    8845         342 :     if (nTotLevCount > static_cast<unsigned int>(nMaxBandCount))
    8846             :     {
    8847           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    8848             :                  "Limiting number of bands to %d instead of %u", nMaxBandCount,
    8849             :                  static_cast<unsigned int>(nTotLevCount));
    8850           0 :         nTotLevCount = static_cast<unsigned int>(nMaxBandCount);
    8851             :     }
    8852         342 :     if (poDS->nRasterXSize == 0 || poDS->nRasterYSize == 0)
    8853             :     {
    8854           0 :         poDS->nRasterXSize = 0;
    8855           0 :         poDS->nRasterYSize = 0;
    8856           0 :         nTotLevCount = 0;
    8857           0 :         if (poDS->GetLayerCount() == 0)
    8858             :         {
    8859           0 :             CPLFree(panBandZLev);
    8860           0 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8861             :                 // deadlock with GDALDataset own mutex.
    8862           0 :             delete poDS;
    8863           0 :             CPLAcquireMutex(hNCMutex, 1000.0);
    8864           0 :             return nullptr;
    8865             :         }
    8866             :     }
    8867         342 :     if (bSeveralVariablesAsBands)
    8868             :     {
    8869           5 :         const auto &listVariables = oMap2DDimsToGroupAndVar.begin()->second;
    8870          21 :         for (int iBand = 0; iBand < static_cast<int>(listVariables.size());
    8871             :              ++iBand)
    8872             :         {
    8873          16 :             int bandVarGroupId = listVariables[iBand].first;
    8874          16 :             int bandVarId = listVariables[iBand].second;
    8875             :             netCDFRasterBand *poBand = new netCDFRasterBand(
    8876           0 :                 netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, bandVarGroupId,
    8877          16 :                 bandVarId, nDim, 0, nullptr, anBandDimPos.data(), iBand + 1);
    8878          16 :             poDS->SetBand(iBand + 1, poBand);
    8879             :         }
    8880             :     }
    8881             :     else
    8882             :     {
    8883         772 :         for (unsigned int lev = 0; lev < nTotLevCount; lev++)
    8884             :         {
    8885             :             netCDFRasterBand *poBand = new netCDFRasterBand(
    8886           0 :                 netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, cdfid, var, nDim,
    8887         435 :                 lev, panBandZLev, anBandDimPos.data(), lev + 1);
    8888         435 :             poDS->SetBand(lev + 1, poBand);
    8889             :         }
    8890             :     }
    8891             : 
    8892         342 :     if (panBandZLev)
    8893          52 :         CPLFree(panBandZLev);
    8894             :     // Handle angular geographic coordinates here
    8895             : 
    8896             :     // Initialize any PAM information.
    8897         342 :     if (bTreatAsSubdataset)
    8898             :     {
    8899          57 :         poDS->SetPhysicalFilename(poDS->osFilename);
    8900          57 :         poDS->SetSubdatasetName(osSubdatasetName);
    8901             :     }
    8902             : 
    8903         342 :     CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock with
    8904             :         // GDALDataset own mutex.
    8905         342 :     poDS->TryLoadXML();
    8906             : 
    8907         342 :     if (bTreatAsSubdataset)
    8908          57 :         poDS->oOvManager.Initialize(poDS, ":::VIRTUAL:::");
    8909             :     else
    8910         285 :         poDS->oOvManager.Initialize(poDS, poDS->osFilename);
    8911             : 
    8912         342 :     CPLAcquireMutex(hNCMutex, 1000.0);
    8913             : 
    8914         342 :     return poDS;
    8915             : }
    8916             : 
    8917             : /************************************************************************/
    8918             : /*                            CopyMetadata()                            */
    8919             : /*                                                                      */
    8920             : /*      Create a copy of metadata for NC_GLOBAL or a variable           */
    8921             : /************************************************************************/
    8922             : 
    8923         140 : static void CopyMetadata(GDALDataset *poSrcDS, GDALRasterBand *poSrcBand,
    8924             :                          GDALRasterBand *poDstBand, int nCdfId, int CDFVarID,
    8925             :                          const char *pszPrefix)
    8926             : {
    8927             :     // Remove the following band meta but set them later from band data.
    8928         140 :     const char *const papszIgnoreBand[] = {
    8929             :         CF_ADD_OFFSET, CF_SCALE_FACTOR, "valid_range", "_Unsigned",
    8930             :         _FillValue,    "coordinates",   nullptr};
    8931         140 :     const char *const papszIgnoreGlobal[] = {"NETCDF_DIM_EXTRA", nullptr};
    8932             : 
    8933         140 :     CSLConstList papszMetadata = nullptr;
    8934         140 :     if (poSrcDS)
    8935             :     {
    8936          59 :         papszMetadata = poSrcDS->GetMetadata();
    8937             :     }
    8938          81 :     else if (poSrcBand)
    8939             :     {
    8940          81 :         papszMetadata = poSrcBand->GetMetadata();
    8941             :     }
    8942             : 
    8943         610 :     for (const auto &[pszKey, pszValue] : cpl::IterateNameValue(papszMetadata))
    8944             :     {
    8945             : #ifdef NCDF_DEBUG
    8946             :         CPLDebug("GDAL_netCDF", "copy metadata [%s]=[%s]", pszKey, pszValue);
    8947             : #endif
    8948             : 
    8949         470 :         CPLString osMetaName(pszKey);
    8950             : 
    8951             :         // Check for items that match pszPrefix if applicable.
    8952         470 :         if (pszPrefix && !EQUAL(pszPrefix, ""))
    8953             :         {
    8954             :             // Remove prefix.
    8955         111 :             if (STARTS_WITH(osMetaName.c_str(), pszPrefix))
    8956             :             {
    8957          16 :                 osMetaName = osMetaName.substr(strlen(pszPrefix));
    8958             :             }
    8959             :             // Only copy items that match prefix.
    8960             :             else
    8961             :             {
    8962          95 :                 continue;
    8963             :             }
    8964             :         }
    8965             : 
    8966             :         // Fix various issues with metadata translation.
    8967         375 :         if (CDFVarID == NC_GLOBAL)
    8968             :         {
    8969             :             // Do not copy items in papszIgnoreGlobal and NETCDF_DIM_*.
    8970         470 :             if ((CSLFindString(papszIgnoreGlobal, osMetaName) != -1) ||
    8971         233 :                 (STARTS_WITH(osMetaName, "NETCDF_DIM_")))
    8972          18 :                 continue;
    8973             :             // Remove NC_GLOBAL prefix for netcdf global Metadata.
    8974         219 :             else if (STARTS_WITH(osMetaName, "NC_GLOBAL#"))
    8975             :             {
    8976          33 :                 osMetaName = osMetaName.substr(strlen("NC_GLOBAL#"));
    8977             :             }
    8978             :             // GDAL Metadata renamed as GDAL-[meta].
    8979         186 :             else if (strstr(osMetaName, "#") == nullptr)
    8980             :             {
    8981          14 :                 osMetaName = "GDAL_" + osMetaName;
    8982             :             }
    8983             :             // Keep time, lev and depth information for safe-keeping.
    8984             :             // Time and vertical coordinate handling need improvements.
    8985             :             /*
    8986             :             else if( STARTS_WITH(szMetaName, "time#") )
    8987             :             {
    8988             :                 szMetaName[4] = '-';
    8989             :             }
    8990             :             else if( STARTS_WITH(szMetaName, "lev#") )
    8991             :             {
    8992             :                 szMetaName[3] = '-';
    8993             :             }
    8994             :             else if( STARTS_WITH(szMetaName, "depth#") )
    8995             :             {
    8996             :                 szMetaName[5] = '-';
    8997             :             }
    8998             :             */
    8999             :             // Only copy data without # (previously all data was copied).
    9000         219 :             if (strstr(osMetaName, "#") != nullptr)
    9001         172 :                 continue;
    9002             :             // netCDF attributes do not like the '#' character.
    9003             :             // for( unsigned int h=0; h < strlen(szMetaName) -1 ; h++ ) {
    9004             :             //     if( szMetaName[h] == '#') szMetaName[h] = '-';
    9005             :             // }
    9006             :         }
    9007             :         else
    9008             :         {
    9009             :             // Do not copy varname, stats, NETCDF_DIM_*, nodata
    9010             :             // and items in papszIgnoreBand.
    9011         138 :             if (STARTS_WITH(osMetaName, "NETCDF_VARNAME") ||
    9012         106 :                 STARTS_WITH(osMetaName, "STATISTICS_") ||
    9013         106 :                 STARTS_WITH(osMetaName, "NETCDF_DIM_") ||
    9014          73 :                 STARTS_WITH(osMetaName, "missing_value") ||
    9015         290 :                 STARTS_WITH(osMetaName, "_FillValue") ||
    9016          46 :                 CSLFindString(papszIgnoreBand, osMetaName) != -1)
    9017          97 :                 continue;
    9018             :         }
    9019             : 
    9020             : #ifdef NCDF_DEBUG
    9021             :         CPLDebug("GDAL_netCDF", "copy name=[%s] value=[%s]", osMetaName.c_str(),
    9022             :                  pszValue);
    9023             : #endif
    9024          88 :         if (NCDFPutAttr(nCdfId, CDFVarID, osMetaName, pszValue) != CE_None)
    9025             :         {
    9026           0 :             CPLDebug("GDAL_netCDF", "NCDFPutAttr(%d, %d, %s, %s) failed",
    9027             :                      nCdfId, CDFVarID, osMetaName.c_str(), pszValue);
    9028             :         }
    9029             :     }
    9030             : 
    9031             :     // Set add_offset and scale_factor here if present.
    9032         140 :     if (poSrcBand && poDstBand)
    9033             :     {
    9034             : 
    9035          81 :         int bGotAddOffset = FALSE;
    9036          81 :         const double dfAddOffset = poSrcBand->GetOffset(&bGotAddOffset);
    9037          81 :         int bGotScale = FALSE;
    9038          81 :         const double dfScale = poSrcBand->GetScale(&bGotScale);
    9039             : 
    9040          81 :         if (bGotAddOffset && dfAddOffset != 0.0)
    9041           1 :             poDstBand->SetOffset(dfAddOffset);
    9042          81 :         if (bGotScale && dfScale != 1.0)
    9043           1 :             poDstBand->SetScale(dfScale);
    9044             :     }
    9045         140 : }
    9046             : 
    9047             : /************************************************************************/
    9048             : /*                            CreateLL()                                */
    9049             : /*                                                                      */
    9050             : /*      Shared functionality between netCDFDataset::Create() and        */
    9051             : /*      netCDF::CreateCopy() for creating netcdf file based on a set of */
    9052             : /*      options and a configuration.                                    */
    9053             : /************************************************************************/
    9054             : 
    9055         192 : netCDFDataset *netCDFDataset::CreateLL(const char *pszFilename, int nXSize,
    9056             :                                        int nYSize, int nBandsIn,
    9057             :                                        char **papszOptions)
    9058             : {
    9059         192 :     if (!((nXSize == 0 && nYSize == 0 && nBandsIn == 0) ||
    9060         119 :           (nXSize > 0 && nYSize > 0 && nBandsIn > 0)))
    9061             :     {
    9062           1 :         return nullptr;
    9063             :     }
    9064             : 
    9065         191 :     CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock with
    9066             :         // GDALDataset own mutex.
    9067         191 :     netCDFDataset *poDS = new netCDFDataset();
    9068         191 :     CPLAcquireMutex(hNCMutex, 1000.0);
    9069             : 
    9070         191 :     poDS->nRasterXSize = nXSize;
    9071         191 :     poDS->nRasterYSize = nYSize;
    9072         191 :     poDS->eAccess = GA_Update;
    9073         191 :     poDS->osFilename = pszFilename;
    9074             : 
    9075             :     // From gtiff driver, is this ok?
    9076             :     /*
    9077             :     poDS->nBlockXSize = nXSize;
    9078             :     poDS->nBlockYSize = 1;
    9079             :     poDS->nBlocksPerBand =
    9080             :         ((nYSize + poDS->nBlockYSize - 1) / poDS->nBlockYSize)
    9081             :         * ((nXSize + poDS->nBlockXSize - 1) / poDS->nBlockXSize);
    9082             :         */
    9083             : 
    9084             :     // process options.
    9085         191 :     poDS->papszCreationOptions = CSLDuplicate(papszOptions);
    9086         191 :     poDS->ProcessCreationOptions();
    9087             : 
    9088         191 :     if (poDS->eMultipleLayerBehavior == SEPARATE_FILES)
    9089             :     {
    9090             :         VSIStatBuf sStat;
    9091           3 :         if (VSIStat(pszFilename, &sStat) == 0)
    9092             :         {
    9093           0 :             if (!VSI_ISDIR(sStat.st_mode))
    9094             :             {
    9095           0 :                 CPLError(CE_Failure, CPLE_FileIO,
    9096             :                          "%s is an existing file, but not a directory",
    9097             :                          pszFilename);
    9098           0 :                 CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    9099             :                     // deadlock with GDALDataset own
    9100             :                     // mutex.
    9101           0 :                 delete poDS;
    9102           0 :                 CPLAcquireMutex(hNCMutex, 1000.0);
    9103           0 :                 return nullptr;
    9104             :             }
    9105             :         }
    9106           3 :         else if (VSIMkdir(pszFilename, 0755) != 0)
    9107             :         {
    9108           2 :             CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s directory",
    9109             :                      pszFilename);
    9110           2 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    9111             :                 // deadlock with GDALDataset own mutex.
    9112           2 :             delete poDS;
    9113           2 :             CPLAcquireMutex(hNCMutex, 1000.0);
    9114           2 :             return nullptr;
    9115             :         }
    9116             : 
    9117           1 :         return poDS;
    9118             :     }
    9119             :     // Create the dataset.
    9120         376 :     CPLString osFilenameForNCCreate(pszFilename);
    9121             : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
    9122             :     if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
    9123             :     {
    9124             :         char *pszTemp =
    9125             :             CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
    9126             :         osFilenameForNCCreate = pszTemp;
    9127             :         CPLFree(pszTemp);
    9128             :     }
    9129             : #endif
    9130             : 
    9131             : #if defined(_WIN32)
    9132             :     {
    9133             :         // Works around bug of msys2 netCDF 4.9.0 package where nc_create()
    9134             :         // crashes
    9135             :         VSIStatBuf sStat;
    9136             :         const char *pszDir = CPLGetDirname(osFilenameForNCCreate.c_str());
    9137             :         if (VSIStat(pszDir, &sStat) != 0)
    9138             :         {
    9139             :             CPLError(CE_Failure, CPLE_OpenFailed,
    9140             :                      "Unable to create netCDF file %s: non existing output "
    9141             :                      "directory",
    9142             :                      pszFilename);
    9143             :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    9144             :                 // deadlock with GDALDataset own mutex.
    9145             :             delete poDS;
    9146             :             CPLAcquireMutex(hNCMutex, 1000.0);
    9147             :             return nullptr;
    9148             :         }
    9149             :     }
    9150             : #endif
    9151             : 
    9152             :     int status =
    9153         188 :         nc_create(osFilenameForNCCreate, poDS->nCreateMode, &(poDS->cdfid));
    9154             : 
    9155             :     // Put into define mode.
    9156         188 :     poDS->SetDefineMode(true);
    9157             : 
    9158         188 :     if (status != NC_NOERR)
    9159             :     {
    9160          30 :         CPLError(CE_Failure, CPLE_OpenFailed,
    9161             :                  "Unable to create netCDF file %s (Error code %d): %s .",
    9162             :                  pszFilename, status, nc_strerror(status));
    9163          30 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    9164             :             // with GDALDataset own mutex.
    9165          30 :         delete poDS;
    9166          30 :         CPLAcquireMutex(hNCMutex, 1000.0);
    9167          30 :         return nullptr;
    9168             :     }
    9169             : 
    9170             :     // Define dimensions.
    9171         158 :     if (nXSize > 0 && nYSize > 0)
    9172             :     {
    9173         105 :         poDS->papszDimName.AddString(NCDF_DIMNAME_X);
    9174             :         status =
    9175         105 :             nc_def_dim(poDS->cdfid, NCDF_DIMNAME_X, nXSize, &(poDS->nXDimID));
    9176         105 :         NCDF_ERR(status);
    9177         105 :         CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
    9178             :                  poDS->cdfid, NCDF_DIMNAME_X, nXSize, poDS->nXDimID);
    9179             : 
    9180         105 :         poDS->papszDimName.AddString(NCDF_DIMNAME_Y);
    9181             :         status =
    9182         105 :             nc_def_dim(poDS->cdfid, NCDF_DIMNAME_Y, nYSize, &(poDS->nYDimID));
    9183         105 :         NCDF_ERR(status);
    9184         105 :         CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
    9185             :                  poDS->cdfid, NCDF_DIMNAME_Y, nYSize, poDS->nYDimID);
    9186             :     }
    9187             : 
    9188         158 :     return poDS;
    9189             : }
    9190             : 
    9191             : /************************************************************************/
    9192             : /*                            Create()                                  */
    9193             : /************************************************************************/
    9194             : 
    9195         126 : GDALDataset *netCDFDataset::Create(const char *pszFilename, int nXSize,
    9196             :                                    int nYSize, int nBandsIn, GDALDataType eType,
    9197             :                                    char **papszOptions)
    9198             : {
    9199         126 :     CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::Create(%s, ...)",
    9200             :              pszFilename);
    9201             : 
    9202             :     const char *legacyCreationOp =
    9203         126 :         CSLFetchNameValueDef(papszOptions, "GEOMETRY_ENCODING", "CF_1.8");
    9204         252 :     std::string legacyCreationOp_s = std::string(legacyCreationOp);
    9205             : 
    9206             :     // Check legacy creation op FIRST
    9207             : 
    9208         126 :     bool legacyCreateMode = false;
    9209             : 
    9210         126 :     if (nXSize != 0 || nYSize != 0 || nBandsIn != 0)
    9211             :     {
    9212          55 :         legacyCreateMode = true;
    9213             :     }
    9214          71 :     else if (legacyCreationOp_s == "CF_1.8")
    9215             :     {
    9216          54 :         legacyCreateMode = false;
    9217             :     }
    9218             : 
    9219          17 :     else if (legacyCreationOp_s == "WKT")
    9220             :     {
    9221          17 :         legacyCreateMode = true;
    9222             :     }
    9223             : 
    9224             :     else
    9225             :     {
    9226           0 :         CPLError(
    9227             :             CE_Failure, CPLE_NotSupported,
    9228             :             "Dataset creation option GEOMETRY_ENCODING=%s is not supported.",
    9229             :             legacyCreationOp_s.c_str());
    9230           0 :         return nullptr;
    9231             :     }
    9232             : 
    9233         252 :     CPLStringList aosOptions(CSLDuplicate(papszOptions));
    9234         239 :     if (aosOptions.FetchNameValue("FORMAT") == nullptr &&
    9235         113 :         (eType == GDT_UInt16 || eType == GDT_UInt32 || eType == GDT_UInt64 ||
    9236             :          eType == GDT_Int64))
    9237             :     {
    9238          10 :         CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
    9239          10 :         aosOptions.SetNameValue("FORMAT", "NC4");
    9240             :     }
    9241             : 
    9242         252 :     CPLStringList aosBandNames;
    9243         126 :     if (const char *pszBandNames = aosOptions.FetchNameValue("BAND_NAMES"))
    9244             :     {
    9245             :         aosBandNames =
    9246           2 :             CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);
    9247             : 
    9248           2 :         if (aosBandNames.Count() != nBandsIn)
    9249             :         {
    9250           1 :             CPLError(CE_Failure, CPLE_OpenFailed,
    9251             :                      "Attempted to create netCDF with %d bands but %d names "
    9252             :                      "provided in BAND_NAMES.",
    9253             :                      nBandsIn, aosBandNames.Count());
    9254             : 
    9255           1 :             return nullptr;
    9256             :         }
    9257             :     }
    9258             : 
    9259         250 :     CPLMutexHolderD(&hNCMutex);
    9260             : 
    9261         125 :     auto poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize, nBandsIn,
    9262             :                                         aosOptions.List());
    9263             : 
    9264         125 :     if (!poDS)
    9265          20 :         return nullptr;
    9266             : 
    9267         105 :     if (!legacyCreateMode)
    9268             :     {
    9269          37 :         poDS->bSGSupport = true;
    9270          37 :         poDS->vcdf.enableFullVirtualMode();
    9271             :     }
    9272             : 
    9273             :     else
    9274             :     {
    9275          68 :         poDS->bSGSupport = false;
    9276             :     }
    9277             : 
    9278             :     // Should we write signed or unsigned byte?
    9279             :     // TODO should this only be done in Create()
    9280         105 :     poDS->bSignedData = true;
    9281         105 :     const char *pszValue = CSLFetchNameValueDef(papszOptions, "PIXELTYPE", "");
    9282         105 :     if (eType == GDT_Byte && !EQUAL(pszValue, "SIGNEDBYTE"))
    9283          14 :         poDS->bSignedData = false;
    9284             : 
    9285             :     // Add Conventions, GDAL info and history.
    9286         105 :     if (poDS->cdfid >= 0)
    9287             :     {
    9288         104 :         const char *CF_Vector_Conv = poDS->bSGSupport
    9289             :                                          ? NCDF_CONVENTIONS_CF_V1_8
    9290             :                                          : NCDF_CONVENTIONS_CF_V1_6;
    9291         104 :         poDS->bWriteGDALVersion = CPLTestBool(
    9292             :             CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
    9293         104 :         poDS->bWriteGDALHistory = CPLTestBool(
    9294             :             CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
    9295         104 :         NCDFAddGDALHistory(poDS->cdfid, pszFilename, poDS->bWriteGDALVersion,
    9296         104 :                            poDS->bWriteGDALHistory, "", "Create",
    9297             :                            (nBandsIn == 0) ? CF_Vector_Conv
    9298             :                                            : GDAL_DEFAULT_NCDF_CONVENTIONS);
    9299             :     }
    9300             : 
    9301             :     // Define bands.
    9302         195 :     for (int iBand = 1; iBand <= nBandsIn; iBand++)
    9303             :     {
    9304             :         const char *pszBandName =
    9305          90 :             aosBandNames.empty() ? nullptr : aosBandNames[iBand - 1];
    9306             : 
    9307          90 :         poDS->SetBand(iBand, new netCDFRasterBand(
    9308          90 :                                  netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS,
    9309          90 :                                  eType, iBand, poDS->bSignedData, pszBandName));
    9310             :     }
    9311             : 
    9312         105 :     CPLDebug("GDAL_netCDF", "netCDFDataset::Create(%s, ...) done", pszFilename);
    9313             :     // Return same dataset.
    9314         105 :     return poDS;
    9315             : }
    9316             : 
    9317             : template <class T>
    9318          81 : static CPLErr NCDFCopyBand(GDALRasterBand *poSrcBand, GDALRasterBand *poDstBand,
    9319             :                            int nXSize, int nYSize, GDALProgressFunc pfnProgress,
    9320             :                            void *pProgressData)
    9321             : {
    9322          81 :     GDALDataType eDT = poSrcBand->GetRasterDataType();
    9323          81 :     CPLErr eErr = CE_None;
    9324          81 :     T *patScanline = static_cast<T *>(CPLMalloc(nXSize * sizeof(T)));
    9325             : 
    9326        1928 :     for (int iLine = 0; iLine < nYSize && eErr == CE_None; iLine++)
    9327             :     {
    9328        1847 :         eErr = poSrcBand->RasterIO(GF_Read, 0, iLine, nXSize, 1, patScanline,
    9329             :                                    nXSize, 1, eDT, 0, 0, nullptr);
    9330        1847 :         if (eErr != CE_None)
    9331             :         {
    9332           0 :             CPLDebug(
    9333             :                 "GDAL_netCDF",
    9334             :                 "NCDFCopyBand(), poSrcBand->RasterIO() returned error code %d",
    9335             :                 eErr);
    9336             :         }
    9337             :         else
    9338             :         {
    9339        1847 :             eErr =
    9340             :                 poDstBand->RasterIO(GF_Write, 0, iLine, nXSize, 1, patScanline,
    9341             :                                     nXSize, 1, eDT, 0, 0, nullptr);
    9342        1847 :             if (eErr != CE_None)
    9343           0 :                 CPLDebug("GDAL_netCDF",
    9344             :                          "NCDFCopyBand(), poDstBand->RasterIO() returned error "
    9345             :                          "code %d",
    9346             :                          eErr);
    9347             :         }
    9348             : 
    9349        1847 :         if (nYSize > 10 && (iLine % (nYSize / 10) == 1))
    9350             :         {
    9351         196 :             if (!pfnProgress(1.0 * iLine / nYSize, nullptr, pProgressData))
    9352             :             {
    9353           0 :                 eErr = CE_Failure;
    9354           0 :                 CPLError(CE_Failure, CPLE_UserInterrupt,
    9355             :                          "User terminated CreateCopy()");
    9356             :             }
    9357             :         }
    9358             :     }
    9359             : 
    9360          81 :     CPLFree(patScanline);
    9361             : 
    9362          81 :     pfnProgress(1.0, nullptr, pProgressData);
    9363             : 
    9364          81 :     return eErr;
    9365             : }
    9366             : 
    9367             : /************************************************************************/
    9368             : /*                            CreateCopy()                              */
    9369             : /************************************************************************/
    9370             : 
    9371             : GDALDataset *
    9372          75 : netCDFDataset::CreateCopy(const char *pszFilename, GDALDataset *poSrcDS,
    9373             :                           CPL_UNUSED int bStrict, char **papszOptions,
    9374             :                           GDALProgressFunc pfnProgress, void *pProgressData)
    9375             : {
    9376         150 :     CPLMutexHolderD(&hNCMutex);
    9377             : 
    9378          75 :     CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::CreateCopy(%s, ...)",
    9379             :              pszFilename);
    9380             : 
    9381          75 :     if (poSrcDS->GetRootGroup())
    9382             :     {
    9383           5 :         auto poDrv = GDALDriver::FromHandle(GDALGetDriverByName("netCDF"));
    9384           5 :         if (poDrv)
    9385             :         {
    9386           5 :             return poDrv->DefaultCreateCopy(pszFilename, poSrcDS, bStrict,
    9387             :                                             papszOptions, pfnProgress,
    9388           5 :                                             pProgressData);
    9389             :         }
    9390             :     }
    9391             : 
    9392          70 :     const int nBands = poSrcDS->GetRasterCount();
    9393          70 :     const int nXSize = poSrcDS->GetRasterXSize();
    9394          70 :     const int nYSize = poSrcDS->GetRasterYSize();
    9395          70 :     const char *pszWKT = poSrcDS->GetProjectionRef();
    9396             : 
    9397             :     // Check input bands for errors.
    9398          70 :     if (nBands == 0)
    9399             :     {
    9400           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    9401             :                  "NetCDF driver does not support "
    9402             :                  "source dataset with zero band.");
    9403           1 :         return nullptr;
    9404             :     }
    9405             : 
    9406          69 :     GDALDataType eDT = GDT_Unknown;
    9407          69 :     GDALRasterBand *poSrcBand = nullptr;
    9408         163 :     for (int iBand = 1; iBand <= nBands; iBand++)
    9409             :     {
    9410          98 :         poSrcBand = poSrcDS->GetRasterBand(iBand);
    9411          98 :         eDT = poSrcBand->GetRasterDataType();
    9412          98 :         if (eDT == GDT_Unknown || GDALDataTypeIsComplex(eDT))
    9413             :         {
    9414           4 :             CPLError(CE_Failure, CPLE_NotSupported,
    9415             :                      "NetCDF driver does not support source dataset with band "
    9416             :                      "of complex type.");
    9417           4 :             return nullptr;
    9418             :         }
    9419             :     }
    9420             : 
    9421          65 :     if (!pfnProgress(0.0, nullptr, pProgressData))
    9422           0 :         return nullptr;
    9423             : 
    9424             :     // Same as in Create().
    9425         130 :     CPLStringList aosOptions(CSLDuplicate(papszOptions));
    9426         122 :     if (aosOptions.FetchNameValue("FORMAT") == nullptr &&
    9427          57 :         (eDT == GDT_UInt16 || eDT == GDT_UInt32 || eDT == GDT_UInt64 ||
    9428             :          eDT == GDT_Int64))
    9429             :     {
    9430           6 :         CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
    9431           6 :         aosOptions.SetNameValue("FORMAT", "NC4");
    9432             :     }
    9433          65 :     netCDFDataset *poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize,
    9434             :                                                   nBands, aosOptions.List());
    9435          65 :     if (!poDS)
    9436          13 :         return nullptr;
    9437             : 
    9438             :     // Copy global metadata.
    9439             :     // Add Conventions, GDAL info and history.
    9440          52 :     CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid, NC_GLOBAL, nullptr);
    9441          52 :     const bool bWriteGDALVersion = CPLTestBool(
    9442             :         CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
    9443          52 :     const bool bWriteGDALHistory = CPLTestBool(
    9444             :         CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
    9445          52 :     NCDFAddGDALHistory(
    9446             :         poDS->cdfid, pszFilename, bWriteGDALVersion, bWriteGDALHistory,
    9447          52 :         poSrcDS->GetMetadataItem("NC_GLOBAL#history"), "CreateCopy",
    9448          52 :         poSrcDS->GetMetadataItem("NC_GLOBAL#Conventions"));
    9449             : 
    9450          52 :     pfnProgress(0.1, nullptr, pProgressData);
    9451             : 
    9452             :     // Check for extra dimensions.
    9453          52 :     int nDim = 2;
    9454             :     char **papszExtraDimNames =
    9455          52 :         NCDFTokenizeArray(poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
    9456          52 :     char **papszExtraDimValues = nullptr;
    9457             : 
    9458          52 :     if (papszExtraDimNames != nullptr && CSLCount(papszExtraDimNames) > 0)
    9459             :     {
    9460           4 :         size_t nDimSizeTot = 1;
    9461             :         // first make sure dimensions lengths compatible with band count
    9462             :         // for( int i=0; i<CSLCount(papszExtraDimNames ); i++ ) {
    9463          11 :         for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--)
    9464             :         {
    9465             :             char szTemp[NC_MAX_NAME + 32 + 1];
    9466           7 :             snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
    9467           7 :                      papszExtraDimNames[i]);
    9468             :             papszExtraDimValues =
    9469           7 :                 NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
    9470           7 :             const size_t nDimSize = atol(papszExtraDimValues[0]);
    9471           7 :             CSLDestroy(papszExtraDimValues);
    9472           7 :             nDimSizeTot *= nDimSize;
    9473             :         }
    9474           4 :         if (nDimSizeTot == (size_t)nBands)
    9475             :         {
    9476           4 :             nDim = 2 + CSLCount(papszExtraDimNames);
    9477             :         }
    9478             :         else
    9479             :         {
    9480             :             // if nBands != #bands computed raise a warning
    9481             :             // just issue a debug message, because it was probably intentional
    9482           0 :             CPLDebug("GDAL_netCDF",
    9483             :                      "Warning: Number of bands (%d) is not compatible with "
    9484             :                      "dimensions "
    9485             :                      "(total=%ld names=%s)",
    9486             :                      nBands, (long)nDimSizeTot,
    9487           0 :                      poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
    9488           0 :             CSLDestroy(papszExtraDimNames);
    9489           0 :             papszExtraDimNames = nullptr;
    9490             :         }
    9491             :     }
    9492             : 
    9493          52 :     int *panDimIds = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
    9494          52 :     int *panBandDimPos = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
    9495             : 
    9496             :     nc_type nVarType;
    9497          52 :     int *panBandZLev = nullptr;
    9498          52 :     int *panDimVarIds = nullptr;
    9499             : 
    9500          52 :     if (nDim > 2)
    9501             :     {
    9502           4 :         panBandZLev = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
    9503           4 :         panDimVarIds = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
    9504             : 
    9505             :         // Define all dims.
    9506          11 :         for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--)
    9507             :         {
    9508           7 :             poDS->papszDimName.AddString(papszExtraDimNames[i]);
    9509             :             char szTemp[NC_MAX_NAME + 32 + 1];
    9510           7 :             snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
    9511           7 :                      papszExtraDimNames[i]);
    9512             :             papszExtraDimValues =
    9513           7 :                 NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
    9514           7 :             const int nDimSize = papszExtraDimValues && papszExtraDimValues[0]
    9515          14 :                                      ? atoi(papszExtraDimValues[0])
    9516             :                                      : 0;
    9517             :             // nc_type is an enum in netcdf-3, needs casting.
    9518           7 :             nVarType = static_cast<nc_type>(papszExtraDimValues &&
    9519           7 :                                                     papszExtraDimValues[0] &&
    9520           7 :                                                     papszExtraDimValues[1]
    9521           7 :                                                 ? atol(papszExtraDimValues[1])
    9522             :                                                 : 0);
    9523           7 :             CSLDestroy(papszExtraDimValues);
    9524           7 :             panBandZLev[i] = nDimSize;
    9525           7 :             panBandDimPos[i + 2] = i;  // Save Position of ZDim.
    9526             : 
    9527             :             // Define dim.
    9528          14 :             int status = nc_def_dim(poDS->cdfid, papszExtraDimNames[i],
    9529           7 :                                     nDimSize, &(panDimIds[i]));
    9530           7 :             NCDF_ERR(status);
    9531             : 
    9532             :             // Define dim var.
    9533           7 :             int anDim[1] = {panDimIds[i]};
    9534          14 :             status = nc_def_var(poDS->cdfid, papszExtraDimNames[i], nVarType, 1,
    9535           7 :                                 anDim, &(panDimVarIds[i]));
    9536           7 :             NCDF_ERR(status);
    9537             : 
    9538             :             // Add dim metadata, using global var# items.
    9539           7 :             snprintf(szTemp, sizeof(szTemp), "%s#", papszExtraDimNames[i]);
    9540           7 :             CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid,
    9541           7 :                          panDimVarIds[i], szTemp);
    9542             :         }
    9543             :     }
    9544             : 
    9545             :     // Copy GeoTransform and Projection.
    9546             : 
    9547             :     // Copy geolocation info.
    9548          52 :     char **papszGeolocationInfo = poSrcDS->GetMetadata("GEOLOCATION");
    9549          52 :     if (papszGeolocationInfo != nullptr)
    9550           5 :         poDS->GDALPamDataset::SetMetadata(papszGeolocationInfo, "GEOLOCATION");
    9551             : 
    9552             :     // Copy geotransform.
    9553          52 :     bool bGotGeoTransform = false;
    9554             :     double adfGeoTransform[6];
    9555          52 :     CPLErr eErr = poSrcDS->GetGeoTransform(adfGeoTransform);
    9556          52 :     if (eErr == CE_None)
    9557             :     {
    9558          36 :         poDS->SetGeoTransform(adfGeoTransform);
    9559             :         // Disable AddProjectionVars() from being called.
    9560          36 :         bGotGeoTransform = true;
    9561          36 :         poDS->m_bHasGeoTransform = false;
    9562             :     }
    9563             : 
    9564             :     // Copy projection.
    9565          52 :     void *pScaledProgress = nullptr;
    9566          52 :     if (bGotGeoTransform || (pszWKT && pszWKT[0] != 0))
    9567             :     {
    9568          37 :         poDS->SetProjection(pszWKT ? pszWKT : "");
    9569             : 
    9570             :         // Now we can call AddProjectionVars() directly.
    9571          37 :         poDS->m_bHasGeoTransform = bGotGeoTransform;
    9572          37 :         poDS->AddProjectionVars(true, nullptr, nullptr);
    9573             :         pScaledProgress =
    9574          37 :             GDALCreateScaledProgress(0.1, 0.25, pfnProgress, pProgressData);
    9575          37 :         poDS->AddProjectionVars(false, GDALScaledProgress, pScaledProgress);
    9576             :         // Save X,Y dim positions.
    9577          37 :         panDimIds[nDim - 1] = poDS->nXDimID;
    9578          37 :         panBandDimPos[0] = nDim - 1;
    9579          37 :         panDimIds[nDim - 2] = poDS->nYDimID;
    9580          37 :         panBandDimPos[1] = nDim - 2;
    9581          37 :         GDALDestroyScaledProgress(pScaledProgress);
    9582             :     }
    9583             :     else
    9584             :     {
    9585          15 :         poDS->bBottomUp =
    9586          15 :             CPL_TO_BOOL(CSLFetchBoolean(papszOptions, "WRITE_BOTTOMUP", TRUE));
    9587          15 :         if (papszGeolocationInfo)
    9588             :         {
    9589           4 :             poDS->AddProjectionVars(true, nullptr, nullptr);
    9590           4 :             poDS->AddProjectionVars(false, nullptr, nullptr);
    9591             :         }
    9592             :     }
    9593             : 
    9594             :     // Write extra dim values - after projection for optimization.
    9595          52 :     if (nDim > 2)
    9596             :     {
    9597             :         // Make sure we are in data mode.
    9598           4 :         static_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
    9599          11 :         for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--)
    9600             :         {
    9601             :             char szTemp[NC_MAX_NAME + 32 + 1];
    9602           7 :             snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_VALUES",
    9603           7 :                      papszExtraDimNames[i]);
    9604           7 :             if (poSrcDS->GetMetadataItem(szTemp) != nullptr)
    9605             :             {
    9606           7 :                 NCDFPut1DVar(poDS->cdfid, panDimVarIds[i],
    9607           7 :                              poSrcDS->GetMetadataItem(szTemp));
    9608             :             }
    9609             :         }
    9610             :     }
    9611             : 
    9612          52 :     pfnProgress(0.25, nullptr, pProgressData);
    9613             : 
    9614             :     // Define Bands.
    9615          52 :     netCDFRasterBand *poBand = nullptr;
    9616          52 :     int nBandID = -1;
    9617             : 
    9618         133 :     for (int iBand = 1; iBand <= nBands; iBand++)
    9619             :     {
    9620          81 :         CPLDebug("GDAL_netCDF", "creating band # %d/%d nDim = %d", iBand,
    9621             :                  nBands, nDim);
    9622             : 
    9623          81 :         poSrcBand = poSrcDS->GetRasterBand(iBand);
    9624          81 :         eDT = poSrcBand->GetRasterDataType();
    9625             : 
    9626             :         // Get var name from NETCDF_VARNAME.
    9627             :         const char *pszNETCDF_VARNAME =
    9628          81 :             poSrcBand->GetMetadataItem("NETCDF_VARNAME");
    9629             :         char szBandName[NC_MAX_NAME + 1];
    9630          81 :         if (pszNETCDF_VARNAME)
    9631             :         {
    9632          32 :             if (nBands > 1 && papszExtraDimNames == nullptr)
    9633           0 :                 snprintf(szBandName, sizeof(szBandName), "%s%d",
    9634             :                          pszNETCDF_VARNAME, iBand);
    9635             :             else
    9636          32 :                 snprintf(szBandName, sizeof(szBandName), "%s",
    9637             :                          pszNETCDF_VARNAME);
    9638             :         }
    9639             :         else
    9640             :         {
    9641          49 :             szBandName[0] = '\0';
    9642             :         }
    9643             : 
    9644             :         // Get long_name from <var>#long_name.
    9645          81 :         const char *pszLongName = "";
    9646          81 :         if (pszNETCDF_VARNAME)
    9647             :         {
    9648             :             pszLongName =
    9649          64 :                 poSrcDS->GetMetadataItem(std::string(pszNETCDF_VARNAME)
    9650          32 :                                              .append("#")
    9651          32 :                                              .append(CF_LNG_NAME)
    9652          32 :                                              .c_str());
    9653          32 :             if (!pszLongName)
    9654          25 :                 pszLongName = "";
    9655             :         }
    9656             : 
    9657          81 :         constexpr bool bSignedData = false;
    9658             : 
    9659          81 :         if (nDim > 2)
    9660          23 :             poBand = new netCDFRasterBand(
    9661          23 :                 netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
    9662             :                 bSignedData, szBandName, pszLongName, nBandID, nDim, iBand - 1,
    9663          23 :                 panBandZLev, panBandDimPos, panDimIds);
    9664             :         else
    9665          58 :             poBand = new netCDFRasterBand(
    9666          58 :                 netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
    9667          58 :                 bSignedData, szBandName, pszLongName);
    9668             : 
    9669          81 :         poDS->SetBand(iBand, poBand);
    9670             : 
    9671             :         // Set nodata value, if any.
    9672          81 :         GDALCopyNoDataValue(poBand, poSrcBand);
    9673             : 
    9674             :         // Copy Metadata for band.
    9675          81 :         CopyMetadata(nullptr, poSrcDS->GetRasterBand(iBand), poBand,
    9676             :                      poDS->cdfid, poBand->nZId);
    9677             : 
    9678             :         // If more than 2D pass the first band's netcdf var ID to subsequent
    9679             :         // bands.
    9680          81 :         if (nDim > 2)
    9681          23 :             nBandID = poBand->nZId;
    9682             :     }
    9683             : 
    9684             :     // Write projection variable to band variable.
    9685          52 :     poDS->AddGridMappingRef();
    9686             : 
    9687          52 :     pfnProgress(0.5, nullptr, pProgressData);
    9688             : 
    9689             :     // Write bands.
    9690             : 
    9691             :     // Make sure we are in data mode.
    9692          52 :     poDS->SetDefineMode(false);
    9693             : 
    9694          52 :     double dfTemp = 0.5;
    9695             : 
    9696          52 :     eErr = CE_None;
    9697             : 
    9698         133 :     for (int iBand = 1; iBand <= nBands && eErr == CE_None; iBand++)
    9699             :     {
    9700          81 :         const double dfTemp2 = dfTemp + 0.4 / nBands;
    9701          81 :         pScaledProgress = GDALCreateScaledProgress(dfTemp, dfTemp2, pfnProgress,
    9702             :                                                    pProgressData);
    9703          81 :         dfTemp = dfTemp2;
    9704             : 
    9705          81 :         CPLDebug("GDAL_netCDF", "copying band data # %d/%d ", iBand, nBands);
    9706             : 
    9707          81 :         poSrcBand = poSrcDS->GetRasterBand(iBand);
    9708          81 :         eDT = poSrcBand->GetRasterDataType();
    9709             : 
    9710          81 :         GDALRasterBand *poDstBand = poDS->GetRasterBand(iBand);
    9711             : 
    9712             :         // Copy band data.
    9713          81 :         if (eDT == GDT_Byte)
    9714             :         {
    9715          41 :             CPLDebug("GDAL_netCDF", "GByte Band#%d", iBand);
    9716          41 :             eErr = NCDFCopyBand<GByte>(poSrcBand, poDstBand, nXSize, nYSize,
    9717             :                                        GDALScaledProgress, pScaledProgress);
    9718             :         }
    9719          40 :         else if (eDT == GDT_Int8)
    9720             :         {
    9721           1 :             CPLDebug("GDAL_netCDF", "GInt8 Band#%d", iBand);
    9722           1 :             eErr = NCDFCopyBand<GInt8>(poSrcBand, poDstBand, nXSize, nYSize,
    9723             :                                        GDALScaledProgress, pScaledProgress);
    9724             :         }
    9725          39 :         else if (eDT == GDT_UInt16)
    9726             :         {
    9727           2 :             CPLDebug("GDAL_netCDF", "GUInt16 Band#%d", iBand);
    9728           2 :             eErr = NCDFCopyBand<GInt16>(poSrcBand, poDstBand, nXSize, nYSize,
    9729             :                                         GDALScaledProgress, pScaledProgress);
    9730             :         }
    9731          37 :         else if (eDT == GDT_Int16)
    9732             :         {
    9733           5 :             CPLDebug("GDAL_netCDF", "GInt16 Band#%d", iBand);
    9734           5 :             eErr = NCDFCopyBand<GUInt16>(poSrcBand, poDstBand, nXSize, nYSize,
    9735             :                                          GDALScaledProgress, pScaledProgress);
    9736             :         }
    9737          32 :         else if (eDT == GDT_UInt32)
    9738             :         {
    9739           2 :             CPLDebug("GDAL_netCDF", "GUInt32 Band#%d", iBand);
    9740           2 :             eErr = NCDFCopyBand<GUInt32>(poSrcBand, poDstBand, nXSize, nYSize,
    9741             :                                          GDALScaledProgress, pScaledProgress);
    9742             :         }
    9743          30 :         else if (eDT == GDT_Int32)
    9744             :         {
    9745          18 :             CPLDebug("GDAL_netCDF", "GInt32 Band#%d", iBand);
    9746          18 :             eErr = NCDFCopyBand<GInt32>(poSrcBand, poDstBand, nXSize, nYSize,
    9747             :                                         GDALScaledProgress, pScaledProgress);
    9748             :         }
    9749          12 :         else if (eDT == GDT_UInt64)
    9750             :         {
    9751           2 :             CPLDebug("GDAL_netCDF", "GUInt64 Band#%d", iBand);
    9752           2 :             eErr = NCDFCopyBand<std::uint64_t>(poSrcBand, poDstBand, nXSize,
    9753             :                                                nYSize, GDALScaledProgress,
    9754             :                                                pScaledProgress);
    9755             :         }
    9756          10 :         else if (eDT == GDT_Int64)
    9757             :         {
    9758           2 :             CPLDebug("GDAL_netCDF", "GInt64 Band#%d", iBand);
    9759             :             eErr =
    9760           2 :                 NCDFCopyBand<std::int64_t>(poSrcBand, poDstBand, nXSize, nYSize,
    9761             :                                            GDALScaledProgress, pScaledProgress);
    9762             :         }
    9763           8 :         else if (eDT == GDT_Float32)
    9764             :         {
    9765           6 :             CPLDebug("GDAL_netCDF", "float Band#%d", iBand);
    9766           6 :             eErr = NCDFCopyBand<float>(poSrcBand, poDstBand, nXSize, nYSize,
    9767             :                                        GDALScaledProgress, pScaledProgress);
    9768             :         }
    9769           2 :         else if (eDT == GDT_Float64)
    9770             :         {
    9771           2 :             CPLDebug("GDAL_netCDF", "double Band#%d", iBand);
    9772           2 :             eErr = NCDFCopyBand<double>(poSrcBand, poDstBand, nXSize, nYSize,
    9773             :                                         GDALScaledProgress, pScaledProgress);
    9774             :         }
    9775             :         else
    9776             :         {
    9777           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    9778             :                      "The NetCDF driver does not support GDAL data type %d",
    9779             :                      eDT);
    9780             :         }
    9781             : 
    9782          81 :         GDALDestroyScaledProgress(pScaledProgress);
    9783             :     }
    9784             : 
    9785          52 :     delete (poDS);
    9786             : 
    9787          52 :     CPLFree(panDimIds);
    9788          52 :     CPLFree(panBandDimPos);
    9789          52 :     CPLFree(panBandZLev);
    9790          52 :     CPLFree(panDimVarIds);
    9791          52 :     if (papszExtraDimNames)
    9792           4 :         CSLDestroy(papszExtraDimNames);
    9793             : 
    9794          52 :     if (eErr != CE_None)
    9795           0 :         return nullptr;
    9796             : 
    9797          52 :     pfnProgress(0.95, nullptr, pProgressData);
    9798             : 
    9799             :     // Re-open dataset so we can return it.
    9800         104 :     CPLStringList aosOpenOptions;
    9801          52 :     aosOpenOptions.AddString("VARIABLES_AS_BANDS=YES");
    9802          52 :     GDALOpenInfo oOpenInfo(pszFilename, GA_Update);
    9803          52 :     oOpenInfo.nOpenFlags = GDAL_OF_RASTER | GDAL_OF_UPDATE;
    9804          52 :     oOpenInfo.papszOpenOptions = aosOpenOptions.List();
    9805          52 :     auto poRetDS = Open(&oOpenInfo);
    9806             : 
    9807             :     // PAM cloning is disabled. See bug #4244.
    9808             :     // if( poDS )
    9809             :     //     poDS->CloneInfo(poSrcDS, GCIF_PAM_DEFAULT);
    9810             : 
    9811          52 :     pfnProgress(1.0, nullptr, pProgressData);
    9812             : 
    9813          52 :     return poRetDS;
    9814             : }
    9815             : 
    9816             : // Note: some logic depends on bIsProjected and bIsGeoGraphic.
    9817             : // May not be known when Create() is called, see AddProjectionVars().
    9818         248 : void netCDFDataset::ProcessCreationOptions()
    9819             : {
    9820             :     const char *pszConfig =
    9821         248 :         CSLFetchNameValue(papszCreationOptions, "CONFIG_FILE");
    9822         248 :     if (pszConfig != nullptr)
    9823             :     {
    9824           4 :         if (oWriterConfig.Parse(pszConfig))
    9825             :         {
    9826             :             // Override dataset creation options from the config file
    9827           2 :             std::map<CPLString, CPLString>::iterator oIter;
    9828           3 :             for (oIter = oWriterConfig.m_oDatasetCreationOptions.begin();
    9829           3 :                  oIter != oWriterConfig.m_oDatasetCreationOptions.end();
    9830           1 :                  ++oIter)
    9831             :             {
    9832           2 :                 papszCreationOptions = CSLSetNameValue(
    9833           2 :                     papszCreationOptions, oIter->first, oIter->second);
    9834             :             }
    9835             :         }
    9836             :     }
    9837             : 
    9838             :     // File format.
    9839         248 :     eFormat = NCDF_FORMAT_NC;
    9840         248 :     const char *pszValue = CSLFetchNameValue(papszCreationOptions, "FORMAT");
    9841         248 :     if (pszValue != nullptr)
    9842             :     {
    9843          91 :         if (EQUAL(pszValue, "NC"))
    9844             :         {
    9845           2 :             eFormat = NCDF_FORMAT_NC;
    9846             :         }
    9847             : #ifdef NETCDF_HAS_NC2
    9848          89 :         else if (EQUAL(pszValue, "NC2"))
    9849             :         {
    9850           0 :             eFormat = NCDF_FORMAT_NC2;
    9851             :         }
    9852             : #endif
    9853          89 :         else if (EQUAL(pszValue, "NC4"))
    9854             :         {
    9855          85 :             eFormat = NCDF_FORMAT_NC4;
    9856             :         }
    9857           4 :         else if (EQUAL(pszValue, "NC4C"))
    9858             :         {
    9859           4 :             eFormat = NCDF_FORMAT_NC4C;
    9860             :         }
    9861             :         else
    9862             :         {
    9863           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    9864             :                      "FORMAT=%s in not supported, using the default NC format.",
    9865             :                      pszValue);
    9866             :         }
    9867             :     }
    9868             : 
    9869             :     // COMPRESS option.
    9870         248 :     pszValue = CSLFetchNameValue(papszCreationOptions, "COMPRESS");
    9871         248 :     if (pszValue != nullptr)
    9872             :     {
    9873           2 :         if (EQUAL(pszValue, "NONE"))
    9874             :         {
    9875           1 :             eCompress = NCDF_COMPRESS_NONE;
    9876             :         }
    9877           1 :         else if (EQUAL(pszValue, "DEFLATE"))
    9878             :         {
    9879           1 :             eCompress = NCDF_COMPRESS_DEFLATE;
    9880           1 :             if (!((eFormat == NCDF_FORMAT_NC4) ||
    9881           1 :                   (eFormat == NCDF_FORMAT_NC4C)))
    9882             :             {
    9883           0 :                 CPLError(CE_Warning, CPLE_IllegalArg,
    9884             :                          "NOTICE: Format set to NC4C because compression is "
    9885             :                          "set to DEFLATE.");
    9886           0 :                 eFormat = NCDF_FORMAT_NC4C;
    9887             :             }
    9888             :         }
    9889             :         else
    9890             :         {
    9891           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    9892             :                      "COMPRESS=%s is not supported.", pszValue);
    9893             :         }
    9894             :     }
    9895             : 
    9896             :     // ZLEVEL option.
    9897         248 :     pszValue = CSLFetchNameValue(papszCreationOptions, "ZLEVEL");
    9898         248 :     if (pszValue != nullptr)
    9899             :     {
    9900           1 :         nZLevel = atoi(pszValue);
    9901           1 :         if (!(nZLevel >= 1 && nZLevel <= 9))
    9902             :         {
    9903           0 :             CPLError(CE_Warning, CPLE_IllegalArg,
    9904             :                      "ZLEVEL=%s value not recognised, ignoring.", pszValue);
    9905           0 :             nZLevel = NCDF_DEFLATE_LEVEL;
    9906             :         }
    9907             :     }
    9908             : 
    9909             :     // CHUNKING option.
    9910         248 :     bChunking =
    9911         248 :         CPL_TO_BOOL(CSLFetchBoolean(papszCreationOptions, "CHUNKING", TRUE));
    9912             : 
    9913             :     // MULTIPLE_LAYERS option.
    9914             :     const char *pszMultipleLayerBehavior =
    9915         248 :         CSLFetchNameValueDef(papszCreationOptions, "MULTIPLE_LAYERS", "NO");
    9916         496 :     const char *pszGeometryEnc = CSLFetchNameValueDef(
    9917         248 :         papszCreationOptions, "GEOMETRY_ENCODING", "CF_1.8");
    9918         248 :     if (EQUAL(pszMultipleLayerBehavior, "NO") ||
    9919           4 :         EQUAL(pszGeometryEnc, "CF_1.8"))
    9920             :     {
    9921         244 :         eMultipleLayerBehavior = SINGLE_LAYER;
    9922             :     }
    9923           4 :     else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_FILES"))
    9924             :     {
    9925           3 :         eMultipleLayerBehavior = SEPARATE_FILES;
    9926             :     }
    9927           1 :     else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_GROUPS"))
    9928             :     {
    9929           1 :         if (eFormat == NCDF_FORMAT_NC4)
    9930             :         {
    9931           1 :             eMultipleLayerBehavior = SEPARATE_GROUPS;
    9932             :         }
    9933             :         else
    9934             :         {
    9935           0 :             CPLError(CE_Warning, CPLE_IllegalArg,
    9936             :                      "MULTIPLE_LAYERS=%s is recognised only with FORMAT=NC4",
    9937             :                      pszMultipleLayerBehavior);
    9938             :         }
    9939             :     }
    9940             :     else
    9941             :     {
    9942           0 :         CPLError(CE_Warning, CPLE_IllegalArg,
    9943             :                  "MULTIPLE_LAYERS=%s not recognised", pszMultipleLayerBehavior);
    9944             :     }
    9945             : 
    9946             :     // Set nCreateMode based on eFormat.
    9947         248 :     switch (eFormat)
    9948             :     {
    9949             : #ifdef NETCDF_HAS_NC2
    9950           0 :         case NCDF_FORMAT_NC2:
    9951           0 :             nCreateMode = NC_CLOBBER | NC_64BIT_OFFSET;
    9952           0 :             break;
    9953             : #endif
    9954          85 :         case NCDF_FORMAT_NC4:
    9955          85 :             nCreateMode = NC_CLOBBER | NC_NETCDF4;
    9956          85 :             break;
    9957           4 :         case NCDF_FORMAT_NC4C:
    9958           4 :             nCreateMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
    9959           4 :             break;
    9960         159 :         case NCDF_FORMAT_NC:
    9961             :         default:
    9962         159 :             nCreateMode = NC_CLOBBER;
    9963         159 :             break;
    9964             :     }
    9965             : 
    9966         248 :     CPLDebug("GDAL_netCDF", "file options: format=%d compress=%d zlevel=%d",
    9967         248 :              eFormat, eCompress, nZLevel);
    9968         248 : }
    9969             : 
    9970         266 : int netCDFDataset::DefVarDeflate(int nVarId, bool bChunkingArg)
    9971             : {
    9972         266 :     if (eCompress == NCDF_COMPRESS_DEFLATE)
    9973             :     {
    9974             :         // Must set chunk size to avoid huge performance hit (set
    9975             :         // bChunkingArg=TRUE)
    9976             :         // perhaps another solution it to change the chunk cache?
    9977             :         // http://www.unidata.ucar.edu/software/netcdf/docs/netcdf.html#Chunk-Cache
    9978             :         // TODO: make sure this is okay.
    9979           1 :         CPLDebug("GDAL_netCDF", "DefVarDeflate(%d, %d) nZlevel=%d", nVarId,
    9980             :                  static_cast<int>(bChunkingArg), nZLevel);
    9981             : 
    9982           1 :         int status = nc_def_var_deflate(cdfid, nVarId, 1, 1, nZLevel);
    9983           1 :         NCDF_ERR(status);
    9984             : 
    9985           1 :         if (status == NC_NOERR && bChunkingArg && bChunking)
    9986             :         {
    9987             :             // set chunking to be 1 for all dims, except X dim
    9988             :             // size_t chunksize[] = { 1, (size_t)nRasterXSize };
    9989             :             size_t chunksize[MAX_NC_DIMS];
    9990             :             int nd;
    9991           1 :             nc_inq_varndims(cdfid, nVarId, &nd);
    9992           1 :             chunksize[0] = (size_t)1;
    9993           1 :             chunksize[1] = (size_t)1;
    9994           1 :             for (int i = 2; i < nd; i++)
    9995           0 :                 chunksize[i] = (size_t)1;
    9996           1 :             chunksize[nd - 1] = (size_t)nRasterXSize;
    9997             : 
    9998             :             // Config options just for testing purposes
    9999             :             const char *pszBlockXSize =
   10000           1 :                 CPLGetConfigOption("BLOCKXSIZE", nullptr);
   10001           1 :             if (pszBlockXSize)
   10002           0 :                 chunksize[nd - 1] = (size_t)atoi(pszBlockXSize);
   10003             : 
   10004             :             const char *pszBlockYSize =
   10005           1 :                 CPLGetConfigOption("BLOCKYSIZE", nullptr);
   10006           1 :             if (nd >= 2 && pszBlockYSize)
   10007           0 :                 chunksize[nd - 2] = (size_t)atoi(pszBlockYSize);
   10008             : 
   10009           1 :             CPLDebug("GDAL_netCDF",
   10010             :                      "DefVarDeflate() chunksize={%ld, %ld} chunkX=%ld nd=%d",
   10011           1 :                      (long)chunksize[0], (long)chunksize[1],
   10012           1 :                      (long)chunksize[nd - 1], nd);
   10013             : #ifdef NCDF_DEBUG
   10014             :             for (int i = 0; i < nd; i++)
   10015             :                 CPLDebug("GDAL_netCDF", "DefVarDeflate() chunk[%d]=%ld", i,
   10016             :                          chunksize[i]);
   10017             : #endif
   10018             : 
   10019           1 :             status = nc_def_var_chunking(cdfid, nVarId, NC_CHUNKED, chunksize);
   10020           1 :             NCDF_ERR(status);
   10021             :         }
   10022             :         else
   10023             :         {
   10024           0 :             CPLDebug("GDAL_netCDF", "chunksize not set");
   10025             :         }
   10026           1 :         return status;
   10027             :     }
   10028         265 :     return NC_NOERR;
   10029             : }
   10030             : 
   10031             : /************************************************************************/
   10032             : /*                           NCDFUnloadDriver()                         */
   10033             : /************************************************************************/
   10034             : 
   10035           7 : static void NCDFUnloadDriver(CPL_UNUSED GDALDriver *poDriver)
   10036             : {
   10037           7 :     if (hNCMutex != nullptr)
   10038           4 :         CPLDestroyMutex(hNCMutex);
   10039           7 :     hNCMutex = nullptr;
   10040           7 : }
   10041             : 
   10042             : /************************************************************************/
   10043             : /*                          GDALRegister_netCDF()                       */
   10044             : /************************************************************************/
   10045             : 
   10046             : class GDALnetCDFDriver final : public GDALDriver
   10047             : {
   10048             :   public:
   10049          12 :     GDALnetCDFDriver() = default;
   10050             : 
   10051         964 :     const char *GetMetadataItem(const char *pszName,
   10052             :                                 const char *pszDomain) override
   10053             :     {
   10054         964 :         if (EQUAL(pszName, GDAL_DCAP_VIRTUALIO))
   10055             :         {
   10056          13 :             InitializeDCAPVirtualIO();
   10057             :         }
   10058         964 :         return GDALDriver::GetMetadataItem(pszName, pszDomain);
   10059             :     }
   10060             : 
   10061          77 :     char **GetMetadata(const char *pszDomain) override
   10062             :     {
   10063          77 :         InitializeDCAPVirtualIO();
   10064          77 :         return GDALDriver::GetMetadata(pszDomain);
   10065             :     }
   10066             : 
   10067             :   private:
   10068             :     bool m_bInitialized = false;
   10069             : 
   10070          90 :     void InitializeDCAPVirtualIO()
   10071             :     {
   10072          90 :         if (!m_bInitialized)
   10073             :         {
   10074           9 :             m_bInitialized = true;
   10075             : 
   10076             : #ifdef ENABLE_UFFD
   10077           9 :             if (CPLIsUserFaultMappingSupported())
   10078             :             {
   10079           9 :                 SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
   10080             :             }
   10081             : #endif
   10082             :         }
   10083          90 :     }
   10084             : };
   10085             : 
   10086          12 : void GDALRegister_netCDF()
   10087             : 
   10088             : {
   10089          12 :     if (!GDAL_CHECK_VERSION("netCDF driver"))
   10090           0 :         return;
   10091             : 
   10092          12 :     if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
   10093           0 :         return;
   10094             : 
   10095          12 :     GDALDriver *poDriver = new GDALnetCDFDriver();
   10096          12 :     netCDFDriverSetCommonMetadata(poDriver);
   10097             : 
   10098          12 :     poDriver->SetMetadataItem("NETCDF_CONVENTIONS",
   10099          12 :                               GDAL_DEFAULT_NCDF_CONVENTIONS);
   10100          12 :     poDriver->SetMetadataItem("NETCDF_VERSION", nc_inq_libvers());
   10101             : 
   10102             :     // Set pfns and register driver.
   10103          12 :     poDriver->pfnOpen = netCDFDataset::Open;
   10104          12 :     poDriver->pfnCreateCopy = netCDFDataset::CreateCopy;
   10105          12 :     poDriver->pfnCreate = netCDFDataset::Create;
   10106          12 :     poDriver->pfnCreateMultiDimensional = netCDFDataset::CreateMultiDimensional;
   10107          12 :     poDriver->pfnUnloadDriver = NCDFUnloadDriver;
   10108             : 
   10109          12 :     GetGDALDriverManager()->RegisterDriver(poDriver);
   10110             : }
   10111             : 
   10112             : /************************************************************************/
   10113             : /*                          New functions                               */
   10114             : /************************************************************************/
   10115             : 
   10116             : /* Test for GDAL version string >= target */
   10117         238 : static bool NCDFIsGDALVersionGTE(const char *pszVersion, int nTarget)
   10118             : {
   10119             : 
   10120             :     // Valid strings are "GDAL 1.9dev, released 2011/01/18" and "GDAL 1.8.1 ".
   10121         238 :     if (pszVersion == nullptr || EQUAL(pszVersion, ""))
   10122           0 :         return false;
   10123         238 :     else if (!STARTS_WITH_CI(pszVersion, "GDAL "))
   10124           0 :         return false;
   10125             :     // 2.0dev of 2011/12/29 has been later renamed as 1.10dev.
   10126         238 :     else if (EQUAL("GDAL 2.0dev, released 2011/12/29", pszVersion))
   10127           0 :         return nTarget <= GDAL_COMPUTE_VERSION(1, 10, 0);
   10128         238 :     else if (STARTS_WITH_CI(pszVersion, "GDAL 1.9dev"))
   10129           2 :         return nTarget <= 1900;
   10130         236 :     else if (STARTS_WITH_CI(pszVersion, "GDAL 1.8dev"))
   10131           0 :         return nTarget <= 1800;
   10132             : 
   10133         236 :     char **papszTokens = CSLTokenizeString2(pszVersion + 5, ".", 0);
   10134             : 
   10135         236 :     int nVersions[] = {0, 0, 0, 0};
   10136         944 :     for (int iToken = 0; papszTokens && iToken < 4 && papszTokens[iToken];
   10137             :          iToken++)
   10138             :     {
   10139         708 :         nVersions[iToken] = atoi(papszTokens[iToken]);
   10140         708 :         if (nVersions[iToken] < 0)
   10141           0 :             nVersions[iToken] = 0;
   10142         708 :         else if (nVersions[iToken] > 99)
   10143           0 :             nVersions[iToken] = 99;
   10144             :     }
   10145             : 
   10146         236 :     int nVersion = 0;
   10147         236 :     if (nVersions[0] > 1 || nVersions[1] >= 10)
   10148         236 :         nVersion =
   10149         236 :             GDAL_COMPUTE_VERSION(nVersions[0], nVersions[1], nVersions[2]);
   10150             :     else
   10151           0 :         nVersion = nVersions[0] * 1000 + nVersions[1] * 100 +
   10152           0 :                    nVersions[2] * 10 + nVersions[3];
   10153             : 
   10154         236 :     CSLDestroy(papszTokens);
   10155         236 :     return nTarget <= nVersion;
   10156             : }
   10157             : 
   10158             : // Add Conventions, GDAL version and history.
   10159         160 : static void NCDFAddGDALHistory(int fpImage, const char *pszFilename,
   10160             :                                bool bWriteGDALVersion, bool bWriteGDALHistory,
   10161             :                                const char *pszOldHist,
   10162             :                                const char *pszFunctionName,
   10163             :                                const char *pszCFVersion)
   10164             : {
   10165         160 :     if (pszCFVersion == nullptr)
   10166             :     {
   10167          36 :         pszCFVersion = GDAL_DEFAULT_NCDF_CONVENTIONS;
   10168             :     }
   10169         160 :     int status = nc_put_att_text(fpImage, NC_GLOBAL, "Conventions",
   10170             :                                  strlen(pszCFVersion), pszCFVersion);
   10171         160 :     NCDF_ERR(status);
   10172             : 
   10173         160 :     if (bWriteGDALVersion)
   10174             :     {
   10175         159 :         const char *pszNCDF_GDAL = GDALVersionInfo("--version");
   10176         159 :         status = nc_put_att_text(fpImage, NC_GLOBAL, "GDAL",
   10177             :                                  strlen(pszNCDF_GDAL), pszNCDF_GDAL);
   10178         159 :         NCDF_ERR(status);
   10179             :     }
   10180             : 
   10181         160 :     if (bWriteGDALHistory)
   10182             :     {
   10183             :         // Add history.
   10184         318 :         CPLString osTmp;
   10185             : #ifdef GDAL_SET_CMD_LINE_DEFINED_TMP
   10186             :         if (!EQUAL(GDALGetCmdLine(), ""))
   10187             :             osTmp = GDALGetCmdLine();
   10188             :         else
   10189             :             osTmp =
   10190             :                 CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
   10191             : #else
   10192         159 :         osTmp = CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
   10193             : #endif
   10194             : 
   10195         159 :         NCDFAddHistory(fpImage, osTmp.c_str(), pszOldHist);
   10196             :     }
   10197           1 :     else if (pszOldHist != nullptr)
   10198             :     {
   10199           0 :         status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
   10200             :                                  strlen(pszOldHist), pszOldHist);
   10201           0 :         NCDF_ERR(status);
   10202             :     }
   10203         160 : }
   10204             : 
   10205             : // Code taken from cdo and libcdi, used for writing the history attribute.
   10206             : 
   10207             : // void cdoDefHistory(int fileID, char *histstring)
   10208         159 : static void NCDFAddHistory(int fpImage, const char *pszAddHist,
   10209             :                            const char *pszOldHist)
   10210             : {
   10211             :     // Check pszOldHist - as if there was no previous history, it will be
   10212             :     // a null pointer - if so set as empty.
   10213         159 :     if (nullptr == pszOldHist)
   10214             :     {
   10215          48 :         pszOldHist = "";
   10216             :     }
   10217             : 
   10218             :     char strtime[32];
   10219         159 :     strtime[0] = '\0';
   10220             : 
   10221         159 :     time_t tp = time(nullptr);
   10222         159 :     if (tp != -1)
   10223             :     {
   10224             :         struct tm ltime;
   10225         159 :         VSILocalTime(&tp, &ltime);
   10226         159 :         (void)strftime(strtime, sizeof(strtime),
   10227             :                        "%a %b %d %H:%M:%S %Y: ", &ltime);
   10228             :     }
   10229             : 
   10230             :     // status = nc_get_att_text(fpImage, NC_GLOBAL,
   10231             :     //                           "history", pszOldHist);
   10232             :     // printf("status: %d pszOldHist: [%s]\n",status,pszOldHist);
   10233             : 
   10234         159 :     size_t nNewHistSize =
   10235         159 :         strlen(pszOldHist) + strlen(strtime) + strlen(pszAddHist) + 1 + 1;
   10236             :     char *pszNewHist =
   10237         159 :         static_cast<char *>(CPLMalloc(nNewHistSize * sizeof(char)));
   10238             : 
   10239         159 :     strcpy(pszNewHist, strtime);
   10240         159 :     strcat(pszNewHist, pszAddHist);
   10241             : 
   10242             :     // int disableHistory = FALSE;
   10243             :     // if( !disableHistory )
   10244             :     {
   10245         159 :         if (!EQUAL(pszOldHist, ""))
   10246           3 :             strcat(pszNewHist, "\n");
   10247         159 :         strcat(pszNewHist, pszOldHist);
   10248             :     }
   10249             : 
   10250         159 :     const int status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
   10251             :                                        strlen(pszNewHist), pszNewHist);
   10252         159 :     NCDF_ERR(status);
   10253             : 
   10254         159 :     CPLFree(pszNewHist);
   10255         159 : }
   10256             : 
   10257        5814 : static CPLErr NCDFSafeStrcat(char **ppszDest, const char *pszSrc,
   10258             :                              size_t *nDestSize)
   10259             : {
   10260             :     /* Reallocate the data string until the content fits */
   10261        5814 :     while (*nDestSize < (strlen(*ppszDest) + strlen(pszSrc) + 1))
   10262             :     {
   10263         405 :         (*nDestSize) *= 2;
   10264         405 :         *ppszDest = static_cast<char *>(
   10265         405 :             CPLRealloc(reinterpret_cast<void *>(*ppszDest), *nDestSize));
   10266             : #ifdef NCDF_DEBUG
   10267             :         CPLDebug("GDAL_netCDF", "NCDFSafeStrcat() resized str from %ld to %ld",
   10268             :                  (*nDestSize) / 2, *nDestSize);
   10269             : #endif
   10270             :     }
   10271        5409 :     strcat(*ppszDest, pszSrc);
   10272             : 
   10273        5409 :     return CE_None;
   10274             : }
   10275             : 
   10276             : /* helper function for NCDFGetAttr() */
   10277             : /* if pdfValue != nullptr, sets *pdfValue to first value returned */
   10278             : /* if ppszValue != nullptr, sets *ppszValue with all attribute values */
   10279             : /* *ppszValue is the responsibility of the caller and must be freed */
   10280       61527 : static CPLErr NCDFGetAttr1(int nCdfId, int nVarId, const char *pszAttrName,
   10281             :                            double *pdfValue, char **ppszValue)
   10282             : {
   10283       61527 :     nc_type nAttrType = NC_NAT;
   10284       61527 :     size_t nAttrLen = 0;
   10285             : 
   10286       61527 :     if (ppszValue)
   10287       60389 :         *ppszValue = nullptr;
   10288             : 
   10289       61527 :     int status = nc_inq_att(nCdfId, nVarId, pszAttrName, &nAttrType, &nAttrLen);
   10290       61527 :     if (status != NC_NOERR)
   10291       33461 :         return CE_Failure;
   10292             : 
   10293             : #ifdef NCDF_DEBUG
   10294             :     CPLDebug("GDAL_netCDF", "NCDFGetAttr1(%s) len=%ld type=%d", pszAttrName,
   10295             :              nAttrLen, nAttrType);
   10296             : #endif
   10297       28066 :     if (nAttrLen == 0 && nAttrType != NC_CHAR)
   10298           1 :         return CE_Failure;
   10299             : 
   10300             :     /* Allocate guaranteed minimum size (use 10 or 20 if not a string) */
   10301       28065 :     size_t nAttrValueSize = nAttrLen + 1;
   10302       28065 :     if (nAttrType != NC_CHAR && nAttrValueSize < 10)
   10303        3081 :         nAttrValueSize = 10;
   10304       28065 :     if (nAttrType == NC_DOUBLE && nAttrValueSize < 20)
   10305        1528 :         nAttrValueSize = 20;
   10306       28065 :     if (nAttrType == NC_INT64 && nAttrValueSize < 20)
   10307          24 :         nAttrValueSize = 22;
   10308             :     char *pszAttrValue =
   10309       28065 :         static_cast<char *>(CPLCalloc(nAttrValueSize, sizeof(char)));
   10310       28065 :     *pszAttrValue = '\0';
   10311             : 
   10312       28065 :     if (nAttrLen > 1 && nAttrType != NC_CHAR)
   10313         530 :         NCDFSafeStrcat(&pszAttrValue, "{", &nAttrValueSize);
   10314             : 
   10315       28065 :     double dfValue = 0.0;
   10316             :     size_t m;
   10317             :     char szTemp[256];
   10318       28065 :     bool bSetDoubleFromStr = false;
   10319             : 
   10320       28065 :     switch (nAttrType)
   10321             :     {
   10322       24982 :         case NC_CHAR:
   10323       24982 :             CPL_IGNORE_RET_VAL(
   10324       24982 :                 nc_get_att_text(nCdfId, nVarId, pszAttrName, pszAttrValue));
   10325       24982 :             pszAttrValue[nAttrLen] = '\0';
   10326       24982 :             bSetDoubleFromStr = true;
   10327       24982 :             dfValue = 0.0;
   10328       24982 :             break;
   10329          88 :         case NC_BYTE:
   10330             :         {
   10331             :             signed char *pscTemp = static_cast<signed char *>(
   10332          88 :                 CPLCalloc(nAttrLen, sizeof(signed char)));
   10333          88 :             nc_get_att_schar(nCdfId, nVarId, pszAttrName, pscTemp);
   10334          88 :             dfValue = static_cast<double>(pscTemp[0]);
   10335          99 :             for (m = 0; m < nAttrLen - 1; m++)
   10336             :             {
   10337          11 :                 snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
   10338          11 :                 NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10339             :             }
   10340          88 :             snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
   10341          88 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10342          88 :             CPLFree(pscTemp);
   10343          88 :             break;
   10344             :         }
   10345         435 :         case NC_SHORT:
   10346             :         {
   10347             :             short *psTemp =
   10348         435 :                 static_cast<short *>(CPLCalloc(nAttrLen, sizeof(short)));
   10349         435 :             nc_get_att_short(nCdfId, nVarId, pszAttrName, psTemp);
   10350         435 :             dfValue = static_cast<double>(psTemp[0]);
   10351         767 :             for (m = 0; m < nAttrLen - 1; m++)
   10352             :             {
   10353         332 :                 snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
   10354         332 :                 NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10355             :             }
   10356         435 :             snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
   10357         435 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10358         435 :             CPLFree(psTemp);
   10359         435 :             break;
   10360             :         }
   10361         530 :         case NC_INT:
   10362             :         {
   10363         530 :             int *pnTemp = static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
   10364         530 :             nc_get_att_int(nCdfId, nVarId, pszAttrName, pnTemp);
   10365         530 :             dfValue = static_cast<double>(pnTemp[0]);
   10366         667 :             for (m = 0; m < nAttrLen - 1; m++)
   10367             :             {
   10368         137 :                 snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
   10369         137 :                 NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10370             :             }
   10371         530 :             snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
   10372         530 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10373         530 :             CPLFree(pnTemp);
   10374         530 :             break;
   10375             :         }
   10376         371 :         case NC_FLOAT:
   10377             :         {
   10378             :             float *pfTemp =
   10379         371 :                 static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
   10380         371 :             nc_get_att_float(nCdfId, nVarId, pszAttrName, pfTemp);
   10381         371 :             dfValue = static_cast<double>(pfTemp[0]);
   10382         399 :             for (m = 0; m < nAttrLen - 1; m++)
   10383             :             {
   10384          28 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
   10385          28 :                 NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10386             :             }
   10387         371 :             CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
   10388         371 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10389         371 :             CPLFree(pfTemp);
   10390         371 :             break;
   10391             :         }
   10392        1528 :         case NC_DOUBLE:
   10393             :         {
   10394             :             double *pdfTemp =
   10395        1528 :                 static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
   10396        1528 :             nc_get_att_double(nCdfId, nVarId, pszAttrName, pdfTemp);
   10397        1528 :             dfValue = pdfTemp[0];
   10398        1616 :             for (m = 0; m < nAttrLen - 1; m++)
   10399             :             {
   10400          88 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
   10401          88 :                 NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10402             :             }
   10403        1528 :             CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
   10404        1528 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10405        1528 :             CPLFree(pdfTemp);
   10406        1528 :             break;
   10407             :         }
   10408           8 :         case NC_STRING:
   10409             :         {
   10410             :             char **ppszTemp =
   10411           8 :                 static_cast<char **>(CPLCalloc(nAttrLen, sizeof(char *)));
   10412           8 :             nc_get_att_string(nCdfId, nVarId, pszAttrName, ppszTemp);
   10413           8 :             bSetDoubleFromStr = true;
   10414           8 :             dfValue = 0.0;
   10415          18 :             for (m = 0; m < nAttrLen - 1; m++)
   10416             :             {
   10417          10 :                 NCDFSafeStrcat(&pszAttrValue,
   10418          10 :                                ppszTemp[m] ? ppszTemp[m] : "{NULL}",
   10419             :                                &nAttrValueSize);
   10420          10 :                 NCDFSafeStrcat(&pszAttrValue, ",", &nAttrValueSize);
   10421             :             }
   10422           8 :             NCDFSafeStrcat(&pszAttrValue, ppszTemp[m] ? ppszTemp[m] : "{NULL}",
   10423             :                            &nAttrValueSize);
   10424           8 :             nc_free_string(nAttrLen, ppszTemp);
   10425           8 :             CPLFree(ppszTemp);
   10426           8 :             break;
   10427             :         }
   10428          26 :         case NC_UBYTE:
   10429             :         {
   10430             :             unsigned char *pucTemp = static_cast<unsigned char *>(
   10431          26 :                 CPLCalloc(nAttrLen, sizeof(unsigned char)));
   10432          26 :             nc_get_att_uchar(nCdfId, nVarId, pszAttrName, pucTemp);
   10433          26 :             dfValue = static_cast<double>(pucTemp[0]);
   10434          26 :             for (m = 0; m < nAttrLen - 1; m++)
   10435             :             {
   10436           0 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
   10437           0 :                 NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10438             :             }
   10439          26 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
   10440          26 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10441          26 :             CPLFree(pucTemp);
   10442          26 :             break;
   10443             :         }
   10444          30 :         case NC_USHORT:
   10445             :         {
   10446             :             unsigned short *pusTemp;
   10447             :             pusTemp = static_cast<unsigned short *>(
   10448          30 :                 CPLCalloc(nAttrLen, sizeof(unsigned short)));
   10449          30 :             nc_get_att_ushort(nCdfId, nVarId, pszAttrName, pusTemp);
   10450          30 :             dfValue = static_cast<double>(pusTemp[0]);
   10451          35 :             for (m = 0; m < nAttrLen - 1; m++)
   10452             :             {
   10453           5 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
   10454           5 :                 NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10455             :             }
   10456          30 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
   10457          30 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10458          30 :             CPLFree(pusTemp);
   10459          30 :             break;
   10460             :         }
   10461          19 :         case NC_UINT:
   10462             :         {
   10463             :             unsigned int *punTemp =
   10464          19 :                 static_cast<unsigned int *>(CPLCalloc(nAttrLen, sizeof(int)));
   10465          19 :             nc_get_att_uint(nCdfId, nVarId, pszAttrName, punTemp);
   10466          19 :             dfValue = static_cast<double>(punTemp[0]);
   10467          19 :             for (m = 0; m < nAttrLen - 1; m++)
   10468             :             {
   10469           0 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
   10470           0 :                 NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10471             :             }
   10472          19 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
   10473          19 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10474          19 :             CPLFree(punTemp);
   10475          19 :             break;
   10476             :         }
   10477          24 :         case NC_INT64:
   10478             :         {
   10479             :             GIntBig *panTemp =
   10480          24 :                 static_cast<GIntBig *>(CPLCalloc(nAttrLen, sizeof(GIntBig)));
   10481          24 :             nc_get_att_longlong(nCdfId, nVarId, pszAttrName, panTemp);
   10482          24 :             dfValue = static_cast<double>(panTemp[0]);
   10483          24 :             for (m = 0; m < nAttrLen - 1; m++)
   10484             :             {
   10485           0 :                 CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",",
   10486           0 :                             panTemp[m]);
   10487           0 :                 NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10488             :             }
   10489          24 :             CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, panTemp[m]);
   10490          24 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10491          24 :             CPLFree(panTemp);
   10492          24 :             break;
   10493             :         }
   10494          24 :         case NC_UINT64:
   10495             :         {
   10496             :             GUIntBig *panTemp =
   10497          24 :                 static_cast<GUIntBig *>(CPLCalloc(nAttrLen, sizeof(GUIntBig)));
   10498          24 :             nc_get_att_ulonglong(nCdfId, nVarId, pszAttrName, panTemp);
   10499          24 :             dfValue = static_cast<double>(panTemp[0]);
   10500          24 :             for (m = 0; m < nAttrLen - 1; m++)
   10501             :             {
   10502           0 :                 CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",",
   10503           0 :                             panTemp[m]);
   10504           0 :                 NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10505             :             }
   10506          24 :             CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, panTemp[m]);
   10507          24 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10508          24 :             CPLFree(panTemp);
   10509          24 :             break;
   10510             :         }
   10511           0 :         default:
   10512           0 :             CPLDebug("GDAL_netCDF",
   10513             :                      "NCDFGetAttr unsupported type %d for attribute %s",
   10514             :                      nAttrType, pszAttrName);
   10515           0 :             break;
   10516             :     }
   10517             : 
   10518       28065 :     if (nAttrLen > 1 && nAttrType != NC_CHAR)
   10519         530 :         NCDFSafeStrcat(&pszAttrValue, "}", &nAttrValueSize);
   10520             : 
   10521       28065 :     if (bSetDoubleFromStr)
   10522             :     {
   10523       24990 :         if (CPLGetValueType(pszAttrValue) == CPL_VALUE_STRING)
   10524             :         {
   10525       24808 :             if (ppszValue == nullptr && pdfValue != nullptr)
   10526             :             {
   10527           1 :                 CPLFree(pszAttrValue);
   10528           1 :                 return CE_Failure;
   10529             :             }
   10530             :         }
   10531       24989 :         dfValue = CPLAtof(pszAttrValue);
   10532             :     }
   10533             : 
   10534             :     /* set return values */
   10535       28064 :     if (ppszValue)
   10536       27757 :         *ppszValue = pszAttrValue;
   10537             :     else
   10538         307 :         CPLFree(pszAttrValue);
   10539             : 
   10540       28064 :     if (pdfValue)
   10541         307 :         *pdfValue = dfValue;
   10542             : 
   10543       28064 :     return CE_None;
   10544             : }
   10545             : 
   10546             : /* sets pdfValue to first value found */
   10547        1138 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
   10548             :                    double *pdfValue)
   10549             : {
   10550        1138 :     return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, pdfValue, nullptr);
   10551             : }
   10552             : 
   10553             : /* pszValue is the responsibility of the caller and must be freed */
   10554       60389 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
   10555             :                    char **pszValue)
   10556             : {
   10557       60389 :     return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, nullptr, pszValue);
   10558             : }
   10559             : 
   10560             : /* By default write NC_CHAR, but detect for int/float/double and */
   10561             : /* NC4 string arrays */
   10562         100 : static CPLErr NCDFPutAttr(int nCdfId, int nVarId, const char *pszAttrName,
   10563             :                           const char *pszValue)
   10564             : {
   10565         100 :     int status = 0;
   10566         100 :     char *pszTemp = nullptr;
   10567             : 
   10568             :     /* get the attribute values as tokens */
   10569         100 :     char **papszValues = NCDFTokenizeArray(pszValue);
   10570         100 :     if (papszValues == nullptr)
   10571           0 :         return CE_Failure;
   10572             : 
   10573         100 :     size_t nAttrLen = CSLCount(papszValues);
   10574             : 
   10575             :     /* first detect type */
   10576         100 :     nc_type nAttrType = NC_CHAR;
   10577         100 :     nc_type nTmpAttrType = NC_CHAR;
   10578         213 :     for (size_t i = 0; i < nAttrLen; i++)
   10579             :     {
   10580         113 :         nTmpAttrType = NC_CHAR;
   10581         113 :         bool bFoundType = false;
   10582         113 :         errno = 0;
   10583         113 :         int nValue = static_cast<int>(strtol(papszValues[i], &pszTemp, 10));
   10584             :         /* test for int */
   10585             :         /* TODO test for Byte and short - can this be done safely? */
   10586         113 :         if (errno == 0 && papszValues[i] != pszTemp && *pszTemp == 0)
   10587             :         {
   10588             :             char szTemp[256];
   10589          19 :             CPLsnprintf(szTemp, sizeof(szTemp), "%d", nValue);
   10590          19 :             if (EQUAL(szTemp, papszValues[i]))
   10591             :             {
   10592          19 :                 bFoundType = true;
   10593          19 :                 nTmpAttrType = NC_INT;
   10594             :             }
   10595             :             else
   10596             :             {
   10597             :                 unsigned int unValue = static_cast<unsigned int>(
   10598           0 :                     strtoul(papszValues[i], &pszTemp, 10));
   10599           0 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%u", unValue);
   10600           0 :                 if (EQUAL(szTemp, papszValues[i]))
   10601             :                 {
   10602           0 :                     bFoundType = true;
   10603           0 :                     nTmpAttrType = NC_UINT;
   10604             :                 }
   10605             :             }
   10606             :         }
   10607         113 :         if (!bFoundType)
   10608             :         {
   10609             :             /* test for double */
   10610          94 :             errno = 0;
   10611          94 :             double dfValue = CPLStrtod(papszValues[i], &pszTemp);
   10612          94 :             if ((errno == 0) && (papszValues[i] != pszTemp) && (*pszTemp == 0))
   10613             :             {
   10614             :                 // Test for float instead of double.
   10615             :                 // strtof() is C89, which is not available in MSVC.
   10616             :                 // See if we loose precision if we cast to float and write to
   10617             :                 // char*.
   10618          14 :                 float fValue = float(dfValue);
   10619             :                 char szTemp[256];
   10620          14 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", fValue);
   10621          14 :                 if (EQUAL(szTemp, papszValues[i]))
   10622           8 :                     nTmpAttrType = NC_FLOAT;
   10623             :                 else
   10624           6 :                     nTmpAttrType = NC_DOUBLE;
   10625             :             }
   10626             :         }
   10627         113 :         if ((nTmpAttrType <= NC_DOUBLE && nAttrType <= NC_DOUBLE &&
   10628          93 :              nTmpAttrType > nAttrType) ||
   10629          93 :             (nTmpAttrType == NC_UINT && nAttrType < NC_FLOAT) ||
   10630           5 :             (nTmpAttrType >= NC_FLOAT && nAttrType == NC_UINT))
   10631          20 :             nAttrType = nTmpAttrType;
   10632             :     }
   10633             : 
   10634             : #ifdef DEBUG
   10635         100 :     if (EQUAL(pszAttrName, "DEBUG_EMPTY_DOUBLE_ATTR"))
   10636             :     {
   10637           0 :         nAttrType = NC_DOUBLE;
   10638           0 :         nAttrLen = 0;
   10639             :     }
   10640             : #endif
   10641             : 
   10642             :     /* now write the data */
   10643         100 :     if (nAttrType == NC_CHAR)
   10644             :     {
   10645          80 :         int nTmpFormat = 0;
   10646          80 :         if (nAttrLen > 1)
   10647             :         {
   10648           0 :             status = nc_inq_format(nCdfId, &nTmpFormat);
   10649           0 :             NCDF_ERR(status);
   10650             :         }
   10651          80 :         if (nAttrLen > 1 && nTmpFormat == NCDF_FORMAT_NC4)
   10652           0 :             status = nc_put_att_string(nCdfId, nVarId, pszAttrName, nAttrLen,
   10653             :                                        const_cast<const char **>(papszValues));
   10654             :         else
   10655          80 :             status = nc_put_att_text(nCdfId, nVarId, pszAttrName,
   10656             :                                      strlen(pszValue), pszValue);
   10657          80 :         NCDF_ERR(status);
   10658             :     }
   10659             :     else
   10660             :     {
   10661          20 :         switch (nAttrType)
   10662             :         {
   10663          11 :             case NC_INT:
   10664             :             {
   10665             :                 int *pnTemp =
   10666          11 :                     static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
   10667          30 :                 for (size_t i = 0; i < nAttrLen; i++)
   10668             :                 {
   10669          19 :                     pnTemp[i] =
   10670          19 :                         static_cast<int>(strtol(papszValues[i], &pszTemp, 10));
   10671             :                 }
   10672          11 :                 status = nc_put_att_int(nCdfId, nVarId, pszAttrName, NC_INT,
   10673             :                                         nAttrLen, pnTemp);
   10674          11 :                 NCDF_ERR(status);
   10675          11 :                 CPLFree(pnTemp);
   10676          11 :                 break;
   10677             :             }
   10678           0 :             case NC_UINT:
   10679             :             {
   10680             :                 unsigned int *punTemp = static_cast<unsigned int *>(
   10681           0 :                     CPLCalloc(nAttrLen, sizeof(unsigned int)));
   10682           0 :                 for (size_t i = 0; i < nAttrLen; i++)
   10683             :                 {
   10684           0 :                     punTemp[i] = static_cast<unsigned int>(
   10685           0 :                         strtol(papszValues[i], &pszTemp, 10));
   10686             :                 }
   10687           0 :                 status = nc_put_att_uint(nCdfId, nVarId, pszAttrName, NC_UINT,
   10688             :                                          nAttrLen, punTemp);
   10689           0 :                 NCDF_ERR(status);
   10690           0 :                 CPLFree(punTemp);
   10691           0 :                 break;
   10692             :             }
   10693           6 :             case NC_FLOAT:
   10694             :             {
   10695             :                 float *pfTemp =
   10696           6 :                     static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
   10697          14 :                 for (size_t i = 0; i < nAttrLen; i++)
   10698             :                 {
   10699           8 :                     pfTemp[i] =
   10700           8 :                         static_cast<float>(CPLStrtod(papszValues[i], &pszTemp));
   10701             :                 }
   10702           6 :                 status = nc_put_att_float(nCdfId, nVarId, pszAttrName, NC_FLOAT,
   10703             :                                           nAttrLen, pfTemp);
   10704           6 :                 NCDF_ERR(status);
   10705           6 :                 CPLFree(pfTemp);
   10706           6 :                 break;
   10707             :             }
   10708           3 :             case NC_DOUBLE:
   10709             :             {
   10710             :                 double *pdfTemp =
   10711           3 :                     static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
   10712           9 :                 for (size_t i = 0; i < nAttrLen; i++)
   10713             :                 {
   10714           6 :                     pdfTemp[i] = CPLStrtod(papszValues[i], &pszTemp);
   10715             :                 }
   10716           3 :                 status = nc_put_att_double(nCdfId, nVarId, pszAttrName,
   10717             :                                            NC_DOUBLE, nAttrLen, pdfTemp);
   10718           3 :                 NCDF_ERR(status);
   10719           3 :                 CPLFree(pdfTemp);
   10720           3 :                 break;
   10721             :             }
   10722           0 :             default:
   10723           0 :                 if (papszValues)
   10724           0 :                     CSLDestroy(papszValues);
   10725           0 :                 return CE_Failure;
   10726             :                 break;
   10727             :         }
   10728             :     }
   10729             : 
   10730         100 :     if (papszValues)
   10731         100 :         CSLDestroy(papszValues);
   10732             : 
   10733         100 :     return CE_None;
   10734             : }
   10735             : 
   10736          69 : static CPLErr NCDFGet1DVar(int nCdfId, int nVarId, char **pszValue)
   10737             : {
   10738             :     /* get var information */
   10739          69 :     int nVarDimId = -1;
   10740          69 :     int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
   10741          69 :     if (status != NC_NOERR || nVarDimId != 1)
   10742           0 :         return CE_Failure;
   10743             : 
   10744          69 :     status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
   10745          69 :     if (status != NC_NOERR)
   10746           0 :         return CE_Failure;
   10747             : 
   10748          69 :     nc_type nVarType = NC_NAT;
   10749          69 :     status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
   10750          69 :     if (status != NC_NOERR)
   10751           0 :         return CE_Failure;
   10752             : 
   10753          69 :     size_t nVarLen = 0;
   10754          69 :     status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
   10755          69 :     if (status != NC_NOERR)
   10756           0 :         return CE_Failure;
   10757             : 
   10758          69 :     size_t start[1] = {0};
   10759          69 :     size_t count[1] = {nVarLen};
   10760             : 
   10761             :     /* Allocate guaranteed minimum size */
   10762          69 :     size_t nVarValueSize = NCDF_MAX_STR_LEN;
   10763             :     char *pszVarValue =
   10764          69 :         static_cast<char *>(CPLCalloc(nVarValueSize, sizeof(char)));
   10765          69 :     *pszVarValue = '\0';
   10766             : 
   10767          69 :     if (nVarLen == 0)
   10768             :     {
   10769             :         /* set return values */
   10770           1 :         *pszValue = pszVarValue;
   10771             : 
   10772           1 :         return CE_None;
   10773             :     }
   10774             : 
   10775          68 :     if (nVarLen > 1 && nVarType != NC_CHAR)
   10776          36 :         NCDFSafeStrcat(&pszVarValue, "{", &nVarValueSize);
   10777             : 
   10778          68 :     switch (nVarType)
   10779             :     {
   10780           0 :         case NC_CHAR:
   10781           0 :             nc_get_vara_text(nCdfId, nVarId, start, count, pszVarValue);
   10782           0 :             pszVarValue[nVarLen] = '\0';
   10783           0 :             break;
   10784           0 :         case NC_BYTE:
   10785             :         {
   10786             :             signed char *pscTemp = static_cast<signed char *>(
   10787           0 :                 CPLCalloc(nVarLen, sizeof(signed char)));
   10788           0 :             nc_get_vara_schar(nCdfId, nVarId, start, count, pscTemp);
   10789             :             char szTemp[256];
   10790           0 :             size_t m = 0;
   10791           0 :             for (; m < nVarLen - 1; m++)
   10792             :             {
   10793           0 :                 snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
   10794           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10795             :             }
   10796           0 :             snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
   10797           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10798           0 :             CPLFree(pscTemp);
   10799           0 :             break;
   10800             :         }
   10801           0 :         case NC_SHORT:
   10802             :         {
   10803             :             short *psTemp =
   10804           0 :                 static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
   10805           0 :             nc_get_vara_short(nCdfId, nVarId, start, count, psTemp);
   10806             :             char szTemp[256];
   10807           0 :             size_t m = 0;
   10808           0 :             for (; m < nVarLen - 1; m++)
   10809             :             {
   10810           0 :                 snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
   10811           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10812             :             }
   10813           0 :             snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
   10814           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10815           0 :             CPLFree(psTemp);
   10816           0 :             break;
   10817             :         }
   10818          17 :         case NC_INT:
   10819             :         {
   10820          17 :             int *pnTemp = static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
   10821          17 :             nc_get_vara_int(nCdfId, nVarId, start, count, pnTemp);
   10822             :             char szTemp[256];
   10823          17 :             size_t m = 0;
   10824          28 :             for (; m < nVarLen - 1; m++)
   10825             :             {
   10826          11 :                 snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
   10827          11 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10828             :             }
   10829          17 :             snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
   10830          17 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10831          17 :             CPLFree(pnTemp);
   10832          17 :             break;
   10833             :         }
   10834           7 :         case NC_FLOAT:
   10835             :         {
   10836             :             float *pfTemp =
   10837           7 :                 static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
   10838           7 :             nc_get_vara_float(nCdfId, nVarId, start, count, pfTemp);
   10839             :             char szTemp[256];
   10840           7 :             size_t m = 0;
   10841         324 :             for (; m < nVarLen - 1; m++)
   10842             :             {
   10843         317 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
   10844         317 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10845             :             }
   10846           7 :             CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
   10847           7 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10848           7 :             CPLFree(pfTemp);
   10849           7 :             break;
   10850             :         }
   10851          43 :         case NC_DOUBLE:
   10852             :         {
   10853             :             double *pdfTemp =
   10854          43 :                 static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
   10855          43 :             nc_get_vara_double(nCdfId, nVarId, start, count, pdfTemp);
   10856             :             char szTemp[256];
   10857          43 :             size_t m = 0;
   10858         219 :             for (; m < nVarLen - 1; m++)
   10859             :             {
   10860         176 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
   10861         176 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10862             :             }
   10863          43 :             CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
   10864          43 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10865          43 :             CPLFree(pdfTemp);
   10866          43 :             break;
   10867             :         }
   10868           0 :         case NC_STRING:
   10869             :         {
   10870             :             char **ppszTemp =
   10871           0 :                 static_cast<char **>(CPLCalloc(nVarLen, sizeof(char *)));
   10872           0 :             nc_get_vara_string(nCdfId, nVarId, start, count, ppszTemp);
   10873           0 :             size_t m = 0;
   10874           0 :             for (; m < nVarLen - 1; m++)
   10875             :             {
   10876           0 :                 NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
   10877           0 :                 NCDFSafeStrcat(&pszVarValue, ",", &nVarValueSize);
   10878             :             }
   10879           0 :             NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
   10880           0 :             nc_free_string(nVarLen, ppszTemp);
   10881           0 :             CPLFree(ppszTemp);
   10882           0 :             break;
   10883             :         }
   10884           0 :         case NC_UBYTE:
   10885             :         {
   10886             :             unsigned char *pucTemp;
   10887             :             pucTemp = static_cast<unsigned char *>(
   10888           0 :                 CPLCalloc(nVarLen, sizeof(unsigned char)));
   10889           0 :             nc_get_vara_uchar(nCdfId, nVarId, start, count, pucTemp);
   10890             :             char szTemp[256];
   10891           0 :             size_t m = 0;
   10892           0 :             for (; m < nVarLen - 1; m++)
   10893             :             {
   10894           0 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
   10895           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10896             :             }
   10897           0 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
   10898           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10899           0 :             CPLFree(pucTemp);
   10900           0 :             break;
   10901             :         }
   10902           0 :         case NC_USHORT:
   10903             :         {
   10904             :             unsigned short *pusTemp;
   10905             :             pusTemp = static_cast<unsigned short *>(
   10906           0 :                 CPLCalloc(nVarLen, sizeof(unsigned short)));
   10907           0 :             nc_get_vara_ushort(nCdfId, nVarId, start, count, pusTemp);
   10908             :             char szTemp[256];
   10909           0 :             size_t m = 0;
   10910           0 :             for (; m < nVarLen - 1; m++)
   10911             :             {
   10912           0 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
   10913           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10914             :             }
   10915           0 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
   10916           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10917           0 :             CPLFree(pusTemp);
   10918           0 :             break;
   10919             :         }
   10920           0 :         case NC_UINT:
   10921             :         {
   10922             :             unsigned int *punTemp;
   10923             :             punTemp = static_cast<unsigned int *>(
   10924           0 :                 CPLCalloc(nVarLen, sizeof(unsigned int)));
   10925           0 :             nc_get_vara_uint(nCdfId, nVarId, start, count, punTemp);
   10926             :             char szTemp[256];
   10927           0 :             size_t m = 0;
   10928           0 :             for (; m < nVarLen - 1; m++)
   10929             :             {
   10930           0 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
   10931           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10932             :             }
   10933           0 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
   10934           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10935           0 :             CPLFree(punTemp);
   10936           0 :             break;
   10937             :         }
   10938           1 :         case NC_INT64:
   10939             :         {
   10940             :             long long *pnTemp =
   10941           1 :                 static_cast<long long *>(CPLCalloc(nVarLen, sizeof(long long)));
   10942           1 :             nc_get_vara_longlong(nCdfId, nVarId, start, count, pnTemp);
   10943             :             char szTemp[256];
   10944           1 :             size_t m = 0;
   10945           2 :             for (; m < nVarLen - 1; m++)
   10946             :             {
   10947           1 :                 snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",", pnTemp[m]);
   10948           1 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10949             :             }
   10950           1 :             snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, pnTemp[m]);
   10951           1 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10952           1 :             CPLFree(pnTemp);
   10953           1 :             break;
   10954             :         }
   10955           0 :         case NC_UINT64:
   10956             :         {
   10957             :             unsigned long long *pnTemp = static_cast<unsigned long long *>(
   10958           0 :                 CPLCalloc(nVarLen, sizeof(unsigned long long)));
   10959           0 :             nc_get_vara_ulonglong(nCdfId, nVarId, start, count, pnTemp);
   10960             :             char szTemp[256];
   10961           0 :             size_t m = 0;
   10962           0 :             for (; m < nVarLen - 1; m++)
   10963             :             {
   10964           0 :                 snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",", pnTemp[m]);
   10965           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10966             :             }
   10967           0 :             snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, pnTemp[m]);
   10968           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10969           0 :             CPLFree(pnTemp);
   10970           0 :             break;
   10971             :         }
   10972           0 :         default:
   10973           0 :             CPLDebug("GDAL_netCDF", "NCDFGetVar1D unsupported type %d",
   10974             :                      nVarType);
   10975           0 :             CPLFree(pszVarValue);
   10976           0 :             pszVarValue = nullptr;
   10977           0 :             break;
   10978             :     }
   10979             : 
   10980          68 :     if (pszVarValue != nullptr && nVarLen > 1 && nVarType != NC_CHAR)
   10981          36 :         NCDFSafeStrcat(&pszVarValue, "}", &nVarValueSize);
   10982             : 
   10983             :     /* set return values */
   10984          68 :     *pszValue = pszVarValue;
   10985             : 
   10986          68 :     return CE_None;
   10987             : }
   10988             : 
   10989           7 : static CPLErr NCDFPut1DVar(int nCdfId, int nVarId, const char *pszValue)
   10990             : {
   10991           7 :     if (EQUAL(pszValue, ""))
   10992           0 :         return CE_Failure;
   10993             : 
   10994             :     /* get var information */
   10995           7 :     int nVarDimId = -1;
   10996           7 :     int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
   10997           7 :     if (status != NC_NOERR || nVarDimId != 1)
   10998           0 :         return CE_Failure;
   10999             : 
   11000           7 :     status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
   11001           7 :     if (status != NC_NOERR)
   11002           0 :         return CE_Failure;
   11003             : 
   11004           7 :     nc_type nVarType = NC_CHAR;
   11005           7 :     status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
   11006           7 :     if (status != NC_NOERR)
   11007           0 :         return CE_Failure;
   11008             : 
   11009           7 :     size_t nVarLen = 0;
   11010           7 :     status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
   11011           7 :     if (status != NC_NOERR)
   11012           0 :         return CE_Failure;
   11013             : 
   11014           7 :     size_t start[1] = {0};
   11015           7 :     size_t count[1] = {nVarLen};
   11016             : 
   11017             :     /* get the values as tokens */
   11018           7 :     char **papszValues = NCDFTokenizeArray(pszValue);
   11019           7 :     if (papszValues == nullptr)
   11020           0 :         return CE_Failure;
   11021             : 
   11022           7 :     nVarLen = CSLCount(papszValues);
   11023             : 
   11024             :     /* now write the data */
   11025           7 :     if (nVarType == NC_CHAR)
   11026             :     {
   11027           0 :         status = nc_put_vara_text(nCdfId, nVarId, start, count, pszValue);
   11028           0 :         NCDF_ERR(status);
   11029             :     }
   11030             :     else
   11031             :     {
   11032           7 :         switch (nVarType)
   11033             :         {
   11034           0 :             case NC_BYTE:
   11035             :             {
   11036             :                 signed char *pscTemp = static_cast<signed char *>(
   11037           0 :                     CPLCalloc(nVarLen, sizeof(signed char)));
   11038           0 :                 for (size_t i = 0; i < nVarLen; i++)
   11039             :                 {
   11040           0 :                     char *pszTemp = nullptr;
   11041           0 :                     pscTemp[i] = static_cast<signed char>(
   11042           0 :                         strtol(papszValues[i], &pszTemp, 10));
   11043             :                 }
   11044             :                 status =
   11045           0 :                     nc_put_vara_schar(nCdfId, nVarId, start, count, pscTemp);
   11046           0 :                 NCDF_ERR(status);
   11047           0 :                 CPLFree(pscTemp);
   11048           0 :                 break;
   11049             :             }
   11050           0 :             case NC_SHORT:
   11051             :             {
   11052             :                 short *psTemp =
   11053           0 :                     static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
   11054           0 :                 for (size_t i = 0; i < nVarLen; i++)
   11055             :                 {
   11056           0 :                     char *pszTemp = nullptr;
   11057           0 :                     psTemp[i] = static_cast<short>(
   11058           0 :                         strtol(papszValues[i], &pszTemp, 10));
   11059             :                 }
   11060             :                 status =
   11061           0 :                     nc_put_vara_short(nCdfId, nVarId, start, count, psTemp);
   11062           0 :                 NCDF_ERR(status);
   11063           0 :                 CPLFree(psTemp);
   11064           0 :                 break;
   11065             :             }
   11066           2 :             case NC_INT:
   11067             :             {
   11068             :                 int *pnTemp =
   11069           2 :                     static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
   11070           6 :                 for (size_t i = 0; i < nVarLen; i++)
   11071             :                 {
   11072           4 :                     char *pszTemp = nullptr;
   11073           4 :                     pnTemp[i] =
   11074           4 :                         static_cast<int>(strtol(papszValues[i], &pszTemp, 10));
   11075             :                 }
   11076           2 :                 status = nc_put_vara_int(nCdfId, nVarId, start, count, pnTemp);
   11077           2 :                 NCDF_ERR(status);
   11078           2 :                 CPLFree(pnTemp);
   11079           2 :                 break;
   11080             :             }
   11081           0 :             case NC_FLOAT:
   11082             :             {
   11083             :                 float *pfTemp =
   11084           0 :                     static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
   11085           0 :                 for (size_t i = 0; i < nVarLen; i++)
   11086             :                 {
   11087           0 :                     char *pszTemp = nullptr;
   11088           0 :                     pfTemp[i] =
   11089           0 :                         static_cast<float>(CPLStrtod(papszValues[i], &pszTemp));
   11090             :                 }
   11091             :                 status =
   11092           0 :                     nc_put_vara_float(nCdfId, nVarId, start, count, pfTemp);
   11093           0 :                 NCDF_ERR(status);
   11094           0 :                 CPLFree(pfTemp);
   11095           0 :                 break;
   11096             :             }
   11097           5 :             case NC_DOUBLE:
   11098             :             {
   11099             :                 double *pdfTemp =
   11100           5 :                     static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
   11101          19 :                 for (size_t i = 0; i < nVarLen; i++)
   11102             :                 {
   11103          14 :                     char *pszTemp = nullptr;
   11104          14 :                     pdfTemp[i] = CPLStrtod(papszValues[i], &pszTemp);
   11105             :                 }
   11106             :                 status =
   11107           5 :                     nc_put_vara_double(nCdfId, nVarId, start, count, pdfTemp);
   11108           5 :                 NCDF_ERR(status);
   11109           5 :                 CPLFree(pdfTemp);
   11110           5 :                 break;
   11111             :             }
   11112           0 :             default:
   11113             :             {
   11114           0 :                 int nTmpFormat = 0;
   11115           0 :                 status = nc_inq_format(nCdfId, &nTmpFormat);
   11116           0 :                 NCDF_ERR(status);
   11117           0 :                 if (nTmpFormat == NCDF_FORMAT_NC4)
   11118             :                 {
   11119           0 :                     switch (nVarType)
   11120             :                     {
   11121           0 :                         case NC_STRING:
   11122             :                         {
   11123             :                             status =
   11124           0 :                                 nc_put_vara_string(nCdfId, nVarId, start, count,
   11125             :                                                    (const char **)papszValues);
   11126           0 :                             NCDF_ERR(status);
   11127           0 :                             break;
   11128             :                         }
   11129           0 :                         case NC_UBYTE:
   11130             :                         {
   11131             :                             unsigned char *pucTemp =
   11132             :                                 static_cast<unsigned char *>(
   11133           0 :                                     CPLCalloc(nVarLen, sizeof(unsigned char)));
   11134           0 :                             for (size_t i = 0; i < nVarLen; i++)
   11135             :                             {
   11136           0 :                                 char *pszTemp = nullptr;
   11137           0 :                                 pucTemp[i] = static_cast<unsigned char>(
   11138           0 :                                     strtoul(papszValues[i], &pszTemp, 10));
   11139             :                             }
   11140           0 :                             status = nc_put_vara_uchar(nCdfId, nVarId, start,
   11141             :                                                        count, pucTemp);
   11142           0 :                             NCDF_ERR(status);
   11143           0 :                             CPLFree(pucTemp);
   11144           0 :                             break;
   11145             :                         }
   11146           0 :                         case NC_USHORT:
   11147             :                         {
   11148             :                             unsigned short *pusTemp =
   11149             :                                 static_cast<unsigned short *>(
   11150           0 :                                     CPLCalloc(nVarLen, sizeof(unsigned short)));
   11151           0 :                             for (size_t i = 0; i < nVarLen; i++)
   11152             :                             {
   11153           0 :                                 char *pszTemp = nullptr;
   11154           0 :                                 pusTemp[i] = static_cast<unsigned short>(
   11155           0 :                                     strtoul(papszValues[i], &pszTemp, 10));
   11156             :                             }
   11157           0 :                             status = nc_put_vara_ushort(nCdfId, nVarId, start,
   11158             :                                                         count, pusTemp);
   11159           0 :                             NCDF_ERR(status);
   11160           0 :                             CPLFree(pusTemp);
   11161           0 :                             break;
   11162             :                         }
   11163           0 :                         case NC_UINT:
   11164             :                         {
   11165             :                             unsigned int *punTemp = static_cast<unsigned int *>(
   11166           0 :                                 CPLCalloc(nVarLen, sizeof(unsigned int)));
   11167           0 :                             for (size_t i = 0; i < nVarLen; i++)
   11168             :                             {
   11169           0 :                                 char *pszTemp = nullptr;
   11170           0 :                                 punTemp[i] = static_cast<unsigned int>(
   11171           0 :                                     strtoul(papszValues[i], &pszTemp, 10));
   11172             :                             }
   11173           0 :                             status = nc_put_vara_uint(nCdfId, nVarId, start,
   11174             :                                                       count, punTemp);
   11175           0 :                             NCDF_ERR(status);
   11176           0 :                             CPLFree(punTemp);
   11177           0 :                             break;
   11178             :                         }
   11179           0 :                         default:
   11180           0 :                             if (papszValues)
   11181           0 :                                 CSLDestroy(papszValues);
   11182           0 :                             return CE_Failure;
   11183             :                             break;
   11184             :                     }
   11185             :                 }
   11186           0 :                 break;
   11187             :             }
   11188             :         }
   11189             :     }
   11190             : 
   11191           7 :     if (papszValues)
   11192           7 :         CSLDestroy(papszValues);
   11193             : 
   11194           7 :     return CE_None;
   11195             : }
   11196             : 
   11197             : /************************************************************************/
   11198             : /*                           GetDefaultNoDataValue()                    */
   11199             : /************************************************************************/
   11200             : 
   11201         186 : double NCDFGetDefaultNoDataValue(int nCdfId, int nVarId, int nVarType,
   11202             :                                  bool &bGotNoData)
   11203             : 
   11204             : {
   11205         186 :     int nNoFill = 0;
   11206         186 :     double dfNoData = 0.0;
   11207             : 
   11208         186 :     switch (nVarType)
   11209             :     {
   11210           0 :         case NC_CHAR:
   11211             :         case NC_BYTE:
   11212             :         case NC_UBYTE:
   11213             :             // Don't do default fill-values for bytes, too risky.
   11214             :             // This function should not be called in those cases.
   11215           0 :             CPLAssert(false);
   11216             :             break;
   11217          24 :         case NC_SHORT:
   11218             :         {
   11219          24 :             short nFillVal = 0;
   11220          24 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
   11221             :                 NC_NOERR)
   11222             :             {
   11223          24 :                 if (!nNoFill)
   11224             :                 {
   11225          23 :                     bGotNoData = true;
   11226          23 :                     dfNoData = nFillVal;
   11227             :                 }
   11228             :             }
   11229             :             else
   11230           0 :                 dfNoData = NC_FILL_SHORT;
   11231          24 :             break;
   11232             :         }
   11233          25 :         case NC_INT:
   11234             :         {
   11235          25 :             int nFillVal = 0;
   11236          25 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
   11237             :                 NC_NOERR)
   11238             :             {
   11239          25 :                 if (!nNoFill)
   11240             :                 {
   11241          25 :                     bGotNoData = true;
   11242          25 :                     dfNoData = nFillVal;
   11243             :                 }
   11244             :             }
   11245             :             else
   11246           0 :                 dfNoData = NC_FILL_INT;
   11247          25 :             break;
   11248             :         }
   11249          70 :         case NC_FLOAT:
   11250             :         {
   11251          70 :             float fFillVal = 0;
   11252          70 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &fFillVal) ==
   11253             :                 NC_NOERR)
   11254             :             {
   11255          70 :                 if (!nNoFill)
   11256             :                 {
   11257          66 :                     bGotNoData = true;
   11258          66 :                     dfNoData = fFillVal;
   11259             :                 }
   11260             :             }
   11261             :             else
   11262           0 :                 dfNoData = NC_FILL_FLOAT;
   11263          70 :             break;
   11264             :         }
   11265          34 :         case NC_DOUBLE:
   11266             :         {
   11267          34 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &dfNoData) ==
   11268             :                 NC_NOERR)
   11269             :             {
   11270          34 :                 if (!nNoFill)
   11271             :                 {
   11272          34 :                     bGotNoData = true;
   11273             :                 }
   11274             :             }
   11275             :             else
   11276           0 :                 dfNoData = NC_FILL_DOUBLE;
   11277          34 :             break;
   11278             :         }
   11279           7 :         case NC_USHORT:
   11280             :         {
   11281           7 :             unsigned short nFillVal = 0;
   11282           7 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
   11283             :                 NC_NOERR)
   11284             :             {
   11285           7 :                 if (!nNoFill)
   11286             :                 {
   11287           7 :                     bGotNoData = true;
   11288           7 :                     dfNoData = nFillVal;
   11289             :                 }
   11290             :             }
   11291             :             else
   11292           0 :                 dfNoData = NC_FILL_USHORT;
   11293           7 :             break;
   11294             :         }
   11295           7 :         case NC_UINT:
   11296             :         {
   11297           7 :             unsigned int nFillVal = 0;
   11298           7 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
   11299             :                 NC_NOERR)
   11300             :             {
   11301           7 :                 if (!nNoFill)
   11302             :                 {
   11303           7 :                     bGotNoData = true;
   11304           7 :                     dfNoData = nFillVal;
   11305             :                 }
   11306             :             }
   11307             :             else
   11308           0 :                 dfNoData = NC_FILL_UINT;
   11309           7 :             break;
   11310             :         }
   11311          19 :         default:
   11312          19 :             dfNoData = 0.0;
   11313          19 :             break;
   11314             :     }
   11315             : 
   11316         186 :     return dfNoData;
   11317             : }
   11318             : 
   11319             : /************************************************************************/
   11320             : /*                      NCDFGetDefaultNoDataValueAsInt64()              */
   11321             : /************************************************************************/
   11322             : 
   11323           2 : int64_t NCDFGetDefaultNoDataValueAsInt64(int nCdfId, int nVarId,
   11324             :                                          bool &bGotNoData)
   11325             : 
   11326             : {
   11327           2 :     int nNoFill = 0;
   11328           2 :     long long nFillVal = 0;
   11329           2 :     if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
   11330             :     {
   11331           2 :         if (!nNoFill)
   11332             :         {
   11333           2 :             bGotNoData = true;
   11334           2 :             return static_cast<int64_t>(nFillVal);
   11335             :         }
   11336             :     }
   11337             :     else
   11338           0 :         return static_cast<int64_t>(NC_FILL_INT64);
   11339           0 :     return 0;
   11340             : }
   11341             : 
   11342             : /************************************************************************/
   11343             : /*                     NCDFGetDefaultNoDataValueAsUInt64()              */
   11344             : /************************************************************************/
   11345             : 
   11346           1 : uint64_t NCDFGetDefaultNoDataValueAsUInt64(int nCdfId, int nVarId,
   11347             :                                            bool &bGotNoData)
   11348             : 
   11349             : {
   11350           1 :     int nNoFill = 0;
   11351           1 :     unsigned long long nFillVal = 0;
   11352           1 :     if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
   11353             :     {
   11354           1 :         if (!nNoFill)
   11355             :         {
   11356           1 :             bGotNoData = true;
   11357           1 :             return static_cast<uint64_t>(nFillVal);
   11358             :         }
   11359             :     }
   11360             :     else
   11361           0 :         return static_cast<uint64_t>(NC_FILL_UINT64);
   11362           0 :     return 0;
   11363             : }
   11364             : 
   11365       10801 : static int NCDFDoesVarContainAttribVal(int nCdfId,
   11366             :                                        const char *const *papszAttribNames,
   11367             :                                        const char *const *papszAttribValues,
   11368             :                                        int nVarId, const char *pszVarName,
   11369             :                                        bool bStrict = true)
   11370             : {
   11371       10801 :     if (nVarId == -1 && pszVarName != nullptr)
   11372        7942 :         NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
   11373             : 
   11374       10801 :     if (nVarId == -1)
   11375         898 :         return -1;
   11376             : 
   11377        9903 :     bool bFound = false;
   11378       46309 :     for (int i = 0; !bFound && papszAttribNames != nullptr &&
   11379       44170 :                     papszAttribNames[i] != nullptr;
   11380             :          i++)
   11381             :     {
   11382       36406 :         char *pszTemp = nullptr;
   11383       36406 :         if (NCDFGetAttr(nCdfId, nVarId, papszAttribNames[i], &pszTemp) ==
   11384       52057 :                 CE_None &&
   11385       15651 :             pszTemp != nullptr)
   11386             :         {
   11387       15651 :             if (bStrict)
   11388             :             {
   11389       15651 :                 if (EQUAL(pszTemp, papszAttribValues[i]))
   11390        2139 :                     bFound = true;
   11391             :             }
   11392             :             else
   11393             :             {
   11394           0 :                 if (EQUALN(pszTemp, papszAttribValues[i],
   11395             :                            strlen(papszAttribValues[i])))
   11396           0 :                     bFound = true;
   11397             :             }
   11398       15651 :             CPLFree(pszTemp);
   11399             :         }
   11400             :     }
   11401        9903 :     return bFound;
   11402             : }
   11403             : 
   11404        1926 : static int NCDFDoesVarContainAttribVal2(int nCdfId, const char *papszAttribName,
   11405             :                                         const char *const *papszAttribValues,
   11406             :                                         int nVarId, const char *pszVarName,
   11407             :                                         int bStrict = true)
   11408             : {
   11409        1926 :     if (nVarId == -1 && pszVarName != nullptr)
   11410        1552 :         NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
   11411             : 
   11412        1926 :     if (nVarId == -1)
   11413           0 :         return -1;
   11414             : 
   11415        1926 :     bool bFound = false;
   11416        1926 :     char *pszTemp = nullptr;
   11417        2283 :     if (NCDFGetAttr(nCdfId, nVarId, papszAttribName, &pszTemp) != CE_None ||
   11418         357 :         pszTemp == nullptr)
   11419        1569 :         return FALSE;
   11420             : 
   11421        7239 :     for (int i = 0; !bFound && i < CSLCount(papszAttribValues); i++)
   11422             :     {
   11423        6882 :         if (bStrict)
   11424             :         {
   11425        6854 :             if (EQUAL(pszTemp, papszAttribValues[i]))
   11426          30 :                 bFound = true;
   11427             :         }
   11428             :         else
   11429             :         {
   11430          28 :             if (EQUALN(pszTemp, papszAttribValues[i],
   11431             :                        strlen(papszAttribValues[i])))
   11432           0 :                 bFound = true;
   11433             :         }
   11434             :     }
   11435             : 
   11436         357 :     CPLFree(pszTemp);
   11437             : 
   11438         357 :     return bFound;
   11439             : }
   11440             : 
   11441         896 : static bool NCDFEqual(const char *papszName, const char *const *papszValues)
   11442             : {
   11443         896 :     if (papszName == nullptr || EQUAL(papszName, ""))
   11444           0 :         return false;
   11445             : 
   11446        2448 :     for (int i = 0; papszValues && papszValues[i]; ++i)
   11447             :     {
   11448        1674 :         if (EQUAL(papszName, papszValues[i]))
   11449         122 :             return true;
   11450             :     }
   11451             : 
   11452         774 :     return false;
   11453             : }
   11454             : 
   11455             : // Test that a variable is longitude/latitude coordinate,
   11456             : // following CF 4.1 and 4.2.
   11457        3639 : bool NCDFIsVarLongitude(int nCdfId, int nVarId, const char *pszVarName)
   11458             : {
   11459             :     // Check for matching attributes.
   11460        3639 :     int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLongitudeAttribNames,
   11461             :                                            papszCFLongitudeAttribValues, nVarId,
   11462             :                                            pszVarName);
   11463             :     // If not found using attributes then check using var name
   11464             :     // unless GDAL_NETCDF_VERIFY_DIMS=STRICT.
   11465        3639 :     if (bVal == -1)
   11466             :     {
   11467         288 :         if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
   11468             :                    "STRICT"))
   11469         288 :             bVal = NCDFEqual(pszVarName, papszCFLongitudeVarNames);
   11470             :         else
   11471           0 :             bVal = FALSE;
   11472             :     }
   11473        3351 :     else if (bVal)
   11474             :     {
   11475             :         // Check that the units is not 'm' or '1'. See #6759
   11476         737 :         char *pszTemp = nullptr;
   11477        1093 :         if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
   11478         356 :             pszTemp != nullptr)
   11479             :         {
   11480         356 :             if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
   11481          97 :                 bVal = false;
   11482         356 :             CPLFree(pszTemp);
   11483             :         }
   11484             :     }
   11485             : 
   11486        3639 :     return CPL_TO_BOOL(bVal);
   11487             : }
   11488             : 
   11489        2103 : bool NCDFIsVarLatitude(int nCdfId, int nVarId, const char *pszVarName)
   11490             : {
   11491        2103 :     int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLatitudeAttribNames,
   11492             :                                            papszCFLatitudeAttribValues, nVarId,
   11493             :                                            pszVarName);
   11494        2103 :     if (bVal == -1)
   11495             :     {
   11496         165 :         if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
   11497             :                    "STRICT"))
   11498         165 :             bVal = NCDFEqual(pszVarName, papszCFLatitudeVarNames);
   11499             :         else
   11500           0 :             bVal = FALSE;
   11501             :     }
   11502        1938 :     else if (bVal)
   11503             :     {
   11504             :         // Check that the units is not 'm' or '1'. See #6759
   11505         507 :         char *pszTemp = nullptr;
   11506         641 :         if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
   11507         134 :             pszTemp != nullptr)
   11508             :         {
   11509         134 :             if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
   11510          36 :                 bVal = false;
   11511         134 :             CPLFree(pszTemp);
   11512             :         }
   11513             :     }
   11514             : 
   11515        2103 :     return CPL_TO_BOOL(bVal);
   11516             : }
   11517             : 
   11518        2259 : bool NCDFIsVarProjectionX(int nCdfId, int nVarId, const char *pszVarName)
   11519             : {
   11520        2259 :     int bVal = NCDFDoesVarContainAttribVal(
   11521             :         nCdfId, papszCFProjectionXAttribNames, papszCFProjectionXAttribValues,
   11522             :         nVarId, pszVarName);
   11523        2259 :     if (bVal == -1)
   11524             :     {
   11525         282 :         if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
   11526             :                    "STRICT"))
   11527         282 :             bVal = NCDFEqual(pszVarName, papszCFProjectionXVarNames);
   11528             :         else
   11529           0 :             bVal = FALSE;
   11530             :     }
   11531        1977 :     else if (bVal)
   11532             :     {
   11533             :         // Check that the units is not '1'
   11534         364 :         char *pszTemp = nullptr;
   11535         518 :         if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
   11536         154 :             pszTemp != nullptr)
   11537             :         {
   11538         154 :             if (EQUAL(pszTemp, "1"))
   11539           5 :                 bVal = false;
   11540         154 :             CPLFree(pszTemp);
   11541             :         }
   11542             :     }
   11543             : 
   11544        2259 :     return CPL_TO_BOOL(bVal);
   11545             : }
   11546             : 
   11547        1607 : bool NCDFIsVarProjectionY(int nCdfId, int nVarId, const char *pszVarName)
   11548             : {
   11549        1607 :     int bVal = NCDFDoesVarContainAttribVal(
   11550             :         nCdfId, papszCFProjectionYAttribNames, papszCFProjectionYAttribValues,
   11551             :         nVarId, pszVarName);
   11552        1607 :     if (bVal == -1)
   11553             :     {
   11554         161 :         if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
   11555             :                    "STRICT"))
   11556         161 :             bVal = NCDFEqual(pszVarName, papszCFProjectionYVarNames);
   11557             :         else
   11558           0 :             bVal = FALSE;
   11559             :     }
   11560        1446 :     else if (bVal)
   11561             :     {
   11562             :         // Check that the units is not '1'
   11563         363 :         char *pszTemp = nullptr;
   11564         516 :         if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
   11565         153 :             pszTemp != nullptr)
   11566             :         {
   11567         153 :             if (EQUAL(pszTemp, "1"))
   11568           5 :                 bVal = false;
   11569         153 :             CPLFree(pszTemp);
   11570             :         }
   11571             :     }
   11572             : 
   11573        1607 :     return CPL_TO_BOOL(bVal);
   11574             : }
   11575             : 
   11576             : /* test that a variable is a vertical coordinate, following CF 4.3 */
   11577        1003 : bool NCDFIsVarVerticalCoord(int nCdfId, int nVarId, const char *pszVarName)
   11578             : {
   11579             :     /* check for matching attributes */
   11580        1003 :     if (NCDFDoesVarContainAttribVal(nCdfId, papszCFVerticalAttribNames,
   11581             :                                     papszCFVerticalAttribValues, nVarId,
   11582        1003 :                                     pszVarName))
   11583          70 :         return true;
   11584             :     /* check for matching units */
   11585         933 :     else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
   11586             :                                           papszCFVerticalUnitsValues, nVarId,
   11587         933 :                                           pszVarName))
   11588          30 :         return true;
   11589             :     /* check for matching standard name */
   11590         903 :     else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_STD_NAME,
   11591             :                                           papszCFVerticalStandardNameValues,
   11592         903 :                                           nVarId, pszVarName))
   11593           0 :         return true;
   11594             :     else
   11595         903 :         return false;
   11596             : }
   11597             : 
   11598             : /* test that a variable is a time coordinate, following CF 4.4 */
   11599         190 : bool NCDFIsVarTimeCoord(int nCdfId, int nVarId, const char *pszVarName)
   11600             : {
   11601             :     /* check for matching attributes */
   11602         190 :     if (NCDFDoesVarContainAttribVal(nCdfId, papszCFTimeAttribNames,
   11603             :                                     papszCFTimeAttribValues, nVarId,
   11604         190 :                                     pszVarName))
   11605         100 :         return true;
   11606             :     /* check for matching units */
   11607          90 :     else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
   11608             :                                           papszCFTimeUnitsValues, nVarId,
   11609          90 :                                           pszVarName, false))
   11610           0 :         return true;
   11611             :     else
   11612          90 :         return false;
   11613             : }
   11614             : 
   11615             : // Parse a string, and return as a string list.
   11616             : // If it an array of the form {a,b}, then tokenize it.
   11617             : // Otherwise, return a copy.
   11618         173 : static char **NCDFTokenizeArray(const char *pszValue)
   11619             : {
   11620         173 :     if (pszValue == nullptr || EQUAL(pszValue, ""))
   11621          48 :         return nullptr;
   11622             : 
   11623         125 :     char **papszValues = nullptr;
   11624         125 :     const int nLen = static_cast<int>(strlen(pszValue));
   11625             : 
   11626         125 :     if (pszValue[0] == '{' && nLen > 2 && pszValue[nLen - 1] == '}')
   11627             :     {
   11628          37 :         char *pszTemp = static_cast<char *>(CPLMalloc((nLen - 2) + 1));
   11629          37 :         strncpy(pszTemp, pszValue + 1, nLen - 2);
   11630          37 :         pszTemp[nLen - 2] = '\0';
   11631          37 :         papszValues = CSLTokenizeString2(pszTemp, ",", CSLT_ALLOWEMPTYTOKENS);
   11632          37 :         CPLFree(pszTemp);
   11633             :     }
   11634             :     else
   11635             :     {
   11636          88 :         papszValues = reinterpret_cast<char **>(CPLCalloc(2, sizeof(char *)));
   11637          88 :         papszValues[0] = CPLStrdup(pszValue);
   11638          88 :         papszValues[1] = nullptr;
   11639             :     }
   11640             : 
   11641         125 :     return papszValues;
   11642             : }
   11643             : 
   11644             : // Open a NetCDF subdataset from full path /group1/group2/.../groupn/var.
   11645             : // Leading slash is optional.
   11646         399 : static CPLErr NCDFOpenSubDataset(int nCdfId, const char *pszSubdatasetName,
   11647             :                                  int *pnGroupId, int *pnVarId)
   11648             : {
   11649         399 :     *pnGroupId = -1;
   11650         399 :     *pnVarId = -1;
   11651             : 
   11652             :     // Open group.
   11653         399 :     char *pszGroupFullName = CPLStrdup(CPLGetPath(pszSubdatasetName));
   11654             :     // Add a leading slash if needed.
   11655         399 :     if (pszGroupFullName[0] != '/')
   11656             :     {
   11657         385 :         char *old = pszGroupFullName;
   11658         385 :         pszGroupFullName = CPLStrdup(CPLSPrintf("/%s", pszGroupFullName));
   11659         385 :         CPLFree(old);
   11660             :     }
   11661             :     // Detect root group.
   11662         399 :     if (EQUAL(pszGroupFullName, "/"))
   11663             :     {
   11664         385 :         *pnGroupId = nCdfId;
   11665         385 :         CPLFree(pszGroupFullName);
   11666             :     }
   11667             :     else
   11668             :     {
   11669          14 :         int status = nc_inq_grp_full_ncid(nCdfId, pszGroupFullName, pnGroupId);
   11670          14 :         CPLFree(pszGroupFullName);
   11671          14 :         NCDF_ERR_RET(status);
   11672             :     }
   11673             : 
   11674             :     // Open var.
   11675         399 :     const char *pszVarName = CPLGetFilename(pszSubdatasetName);
   11676         399 :     NCDF_ERR_RET(nc_inq_varid(*pnGroupId, pszVarName, pnVarId));
   11677             : 
   11678         399 :     return CE_None;
   11679             : }
   11680             : 
   11681             : // Get all dimensions visible from a given NetCDF (or group) ID and any of
   11682             : // its parents.
   11683         342 : static CPLErr NCDFGetVisibleDims(int nGroupId, int *pnDims, int **ppanDimIds)
   11684             : {
   11685         342 :     int nDims = 0;
   11686         342 :     int *panDimIds = nullptr;
   11687         342 :     NCDF_ERR_RET(nc_inq_dimids(nGroupId, &nDims, nullptr, true));
   11688             : 
   11689         342 :     panDimIds = static_cast<int *>(CPLMalloc(nDims * sizeof(int)));
   11690             : 
   11691         342 :     int status = nc_inq_dimids(nGroupId, nullptr, panDimIds, true);
   11692         342 :     if (status != NC_NOERR)
   11693           0 :         CPLFree(panDimIds);
   11694         342 :     NCDF_ERR_RET(status);
   11695             : 
   11696         342 :     *pnDims = nDims;
   11697         342 :     *ppanDimIds = panDimIds;
   11698             : 
   11699         342 :     return CE_None;
   11700             : }
   11701             : 
   11702             : // Get direct sub-groups IDs of a given NetCDF (or group) ID.
   11703             : // Consider only direct children, does not get children of children.
   11704        2994 : static CPLErr NCDFGetSubGroups(int nGroupId, int *pnSubGroups,
   11705             :                                int **ppanSubGroupIds)
   11706             : {
   11707        2994 :     *pnSubGroups = 0;
   11708        2994 :     *ppanSubGroupIds = nullptr;
   11709             : 
   11710             :     int nSubGroups;
   11711        2994 :     NCDF_ERR_RET(nc_inq_grps(nGroupId, &nSubGroups, nullptr));
   11712             :     int *panSubGroupIds =
   11713        2994 :         static_cast<int *>(CPLMalloc(nSubGroups * sizeof(int)));
   11714        2994 :     NCDF_ERR_RET(nc_inq_grps(nGroupId, nullptr, panSubGroupIds));
   11715        2994 :     *pnSubGroups = nSubGroups;
   11716        2994 :     *ppanSubGroupIds = panSubGroupIds;
   11717             : 
   11718        2994 :     return CE_None;
   11719             : }
   11720             : 
   11721             : // Get the full name of a given NetCDF (or group) ID
   11722             : // (e.g. /group1/group2/.../groupn).
   11723             : // bNC3Compat remove the leading slash for top-level variables for
   11724             : // backward compatibility (top-level variables are the ones in the root group).
   11725       14788 : static CPLErr NCDFGetGroupFullName(int nGroupId, char **ppszFullName,
   11726             :                                    bool bNC3Compat)
   11727             : {
   11728       14788 :     *ppszFullName = nullptr;
   11729             : 
   11730             :     size_t nFullNameLen;
   11731       14788 :     NCDF_ERR_RET(nc_inq_grpname_len(nGroupId, &nFullNameLen));
   11732       14788 :     *ppszFullName =
   11733       14788 :         static_cast<char *>(CPLMalloc((nFullNameLen + 1) * sizeof(char)));
   11734       14788 :     int status = nc_inq_grpname_full(nGroupId, &nFullNameLen, *ppszFullName);
   11735       14788 :     if (status != NC_NOERR)
   11736             :     {
   11737           0 :         CPLFree(*ppszFullName);
   11738           0 :         *ppszFullName = nullptr;
   11739           0 :         NCDF_ERR_RET(status);
   11740             :     }
   11741             : 
   11742       14788 :     if (bNC3Compat && EQUAL(*ppszFullName, "/"))
   11743        7721 :         (*ppszFullName)[0] = '\0';
   11744             : 
   11745       14788 :     return CE_None;
   11746             : }
   11747             : 
   11748        6896 : CPLString NCDFGetGroupFullName(int nGroupId)
   11749             : {
   11750        6896 :     char *pszFullname = nullptr;
   11751        6896 :     NCDFGetGroupFullName(nGroupId, &pszFullname, false);
   11752        6896 :     CPLString osRet(pszFullname ? pszFullname : "");
   11753        6896 :     CPLFree(pszFullname);
   11754       13792 :     return osRet;
   11755             : }
   11756             : 
   11757             : // Get the full name of a given NetCDF variable ID
   11758             : // (e.g. /group1/group2/.../groupn/var).
   11759             : // Handle also NC_GLOBAL as nVarId.
   11760             : // bNC3Compat remove the leading slash for top-level variables for
   11761             : // backward compatibility (top-level variables are the ones in the root group).
   11762        7843 : static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId, char **ppszFullName,
   11763             :                                  bool bNC3Compat)
   11764             : {
   11765        7843 :     *ppszFullName = nullptr;
   11766        7843 :     char *pszGroupFullName = nullptr;
   11767        7843 :     ERR_RET(NCDFGetGroupFullName(nGroupId, &pszGroupFullName, bNC3Compat));
   11768             :     char szVarName[NC_MAX_NAME + 1];
   11769        7843 :     if (nVarId == NC_GLOBAL)
   11770             :     {
   11771        1056 :         strcpy(szVarName, "NC_GLOBAL");
   11772             :     }
   11773             :     else
   11774             :     {
   11775        6787 :         int status = nc_inq_varname(nGroupId, nVarId, szVarName);
   11776        6787 :         if (status != NC_NOERR)
   11777             :         {
   11778           0 :             CPLFree(pszGroupFullName);
   11779           0 :             NCDF_ERR_RET(status);
   11780             :         }
   11781             :     }
   11782        7843 :     const char *pszSep = "/";
   11783        7843 :     if (EQUAL(pszGroupFullName, "/") || EQUAL(pszGroupFullName, ""))
   11784        7674 :         pszSep = "";
   11785        7843 :     *ppszFullName =
   11786        7843 :         CPLStrdup(CPLSPrintf("%s%s%s", pszGroupFullName, pszSep, szVarName));
   11787        7843 :     CPLFree(pszGroupFullName);
   11788        7843 :     return CE_None;
   11789             : }
   11790             : 
   11791             : // Get the NetCDF root group ID of a given group ID.
   11792           0 : static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId)
   11793             : {
   11794           0 :     *pnRootGroupId = -1;
   11795             :     // Recurse on parent group.
   11796             :     int nParentGroupId;
   11797           0 :     int status = nc_inq_grp_parent(nStartGroupId, &nParentGroupId);
   11798           0 :     if (status == NC_NOERR)
   11799           0 :         return NCDFGetRootGroup(nParentGroupId, pnRootGroupId);
   11800           0 :     else if (status != NC_ENOGRP)
   11801           0 :         NCDF_ERR_RET(status);
   11802             :     else  // No more parent group.
   11803             :     {
   11804           0 :         *pnRootGroupId = nStartGroupId;
   11805             :     }
   11806             : 
   11807           0 :     return CE_None;
   11808             : }
   11809             : 
   11810             : // Implementation of NCDFResolveVar/Att.
   11811       12830 : static CPLErr NCDFResolveElem(int nStartGroupId, const char *pszVar,
   11812             :                               const char *pszAtt, int *pnGroupId, int *pnId,
   11813             :                               bool bMandatory)
   11814             : {
   11815       12830 :     if (!pszVar && !pszAtt)
   11816             :     {
   11817           0 :         CPLError(CE_Failure, CPLE_IllegalArg,
   11818             :                  "pszVar and pszAtt NCDFResolveElem() args are both null.");
   11819           0 :         return CE_Failure;
   11820             :     }
   11821             : 
   11822             :     enum
   11823             :     {
   11824             :         NCRM_PARENT,
   11825             :         NCRM_WIDTH_WISE
   11826       12830 :     } eNCResolveMode = NCRM_PARENT;
   11827             : 
   11828       25660 :     std::queue<int> aoQueueGroupIdsToVisit;
   11829       12830 :     aoQueueGroupIdsToVisit.push(nStartGroupId);
   11830             : 
   11831       14489 :     while (!aoQueueGroupIdsToVisit.empty())
   11832             :     {
   11833             :         // Get the first group of the FIFO queue.
   11834       12961 :         *pnGroupId = aoQueueGroupIdsToVisit.front();
   11835       12961 :         aoQueueGroupIdsToVisit.pop();
   11836             : 
   11837             :         // Look if this group contains the searched element.
   11838             :         int status;
   11839       12961 :         if (pszVar)
   11840       12748 :             status = nc_inq_varid(*pnGroupId, pszVar, pnId);
   11841             :         else  // pszAtt != nullptr.
   11842         213 :             status = nc_inq_attid(*pnGroupId, NC_GLOBAL, pszAtt, pnId);
   11843             : 
   11844       12961 :         if (status == NC_NOERR)
   11845             :         {
   11846       11302 :             return CE_None;
   11847             :         }
   11848        1659 :         else if ((pszVar && status != NC_ENOTVAR) ||
   11849         210 :                  (pszAtt && status != NC_ENOTATT))
   11850             :         {
   11851           0 :             NCDF_ERR(status);
   11852             :         }
   11853             :         // Element not found, in NC4 case we must search in other groups
   11854             :         // following the CF logic.
   11855             : 
   11856             :         // The first resolve mode consists to search on parent groups.
   11857        1659 :         if (eNCResolveMode == NCRM_PARENT)
   11858             :         {
   11859        1571 :             int nParentGroupId = -1;
   11860        1571 :             int status2 = nc_inq_grp_parent(*pnGroupId, &nParentGroupId);
   11861        1571 :             if (status2 == NC_NOERR)
   11862          41 :                 aoQueueGroupIdsToVisit.push(nParentGroupId);
   11863        1530 :             else if (status2 != NC_ENOGRP)
   11864           0 :                 NCDF_ERR(status2);
   11865        1530 :             else if (pszVar)
   11866             :                 // When resolving a variable, if there is no more
   11867             :                 // parent group then we switch to width-wise search mode
   11868             :                 // starting from the latest found parent group.
   11869        1323 :                 eNCResolveMode = NCRM_WIDTH_WISE;
   11870             :         }
   11871             : 
   11872             :         // The second resolve mode is a width-wise search.
   11873        1659 :         if (eNCResolveMode == NCRM_WIDTH_WISE)
   11874             :         {
   11875             :             // Enqueue all direct sub-groups.
   11876        1411 :             int nSubGroups = 0;
   11877        1411 :             int *panSubGroupIds = nullptr;
   11878        1411 :             NCDFGetSubGroups(*pnGroupId, &nSubGroups, &panSubGroupIds);
   11879        1501 :             for (int i = 0; i < nSubGroups; i++)
   11880          90 :                 aoQueueGroupIdsToVisit.push(panSubGroupIds[i]);
   11881        1411 :             CPLFree(panSubGroupIds);
   11882             :         }
   11883             :     }
   11884             : 
   11885        1528 :     if (bMandatory)
   11886             :     {
   11887           0 :         char *pszStartGroupFullName = nullptr;
   11888           0 :         NCDFGetGroupFullName(nStartGroupId, &pszStartGroupFullName);
   11889           0 :         CPLError(CE_Failure, CPLE_AppDefined,
   11890             :                  "Cannot resolve mandatory %s %s from group %s",
   11891             :                  (pszVar ? pszVar : pszAtt),
   11892             :                  (pszVar ? "variable" : "attribute"),
   11893           0 :                  (pszStartGroupFullName ? pszStartGroupFullName : ""));
   11894           0 :         CPLFree(pszStartGroupFullName);
   11895             :     }
   11896             : 
   11897        1528 :     *pnGroupId = -1;
   11898        1528 :     *pnId = -1;
   11899        1528 :     return CE_Failure;
   11900             : }
   11901             : 
   11902             : // Resolve a variable name from a given starting group following the CF logic:
   11903             : // - if var name is an absolute path then directly open it
   11904             : // - first search in the starting group and its parent groups
   11905             : // - then if there is no more parent group we switch to a width-wise search
   11906             : //   mode starting from the latest found parent group.
   11907             : // The full CF logic is described here:
   11908             : // https://github.com/diwg/cf2/blob/master/group/cf2-group.adoc#scope
   11909             : // If bMandatory then print an error if resolving fails.
   11910             : // TODO: implement support of relative paths.
   11911             : // TODO: to follow strictly the CF logic, when searching for a coordinate
   11912             : //       variable, we must stop the parent search mode once the corresponding
   11913             : //       dimension is found and start the width-wise search from this group.
   11914             : // TODO: to follow strictly the CF logic, when searching in width-wise mode
   11915             : //       we should skip every groups already visited during the parent
   11916             : //       search mode (but revisiting them should have no impact so we could
   11917             : //       let as it is if it is simpler...)
   11918             : // TODO: CF specifies that the width-wise search order is "left-to-right" so
   11919             : //       maybe we must sort sibling groups alphabetically? but maybe not
   11920             : //       necessary if nc_inq_grps() already sort them?
   11921       12620 : CPLErr NCDFResolveVar(int nStartGroupId, const char *pszVar, int *pnGroupId,
   11922             :                       int *pnVarId, bool bMandatory)
   11923             : {
   11924       12620 :     *pnGroupId = -1;
   11925       12620 :     *pnVarId = -1;
   11926       12620 :     int nGroupId = nStartGroupId, nVarId;
   11927       12620 :     if (pszVar[0] == '/')
   11928             :     {
   11929             :         // This is an absolute path: we can open the var directly.
   11930             :         int nRootGroupId;
   11931           0 :         ERR_RET(NCDFGetRootGroup(nStartGroupId, &nRootGroupId));
   11932           0 :         ERR_RET(NCDFOpenSubDataset(nRootGroupId, pszVar, &nGroupId, &nVarId));
   11933             :     }
   11934             :     else
   11935             :     {
   11936             :         // We have to search the variable following the CF logic.
   11937       12620 :         ERR_RET(NCDFResolveElem(nStartGroupId, pszVar, nullptr, &nGroupId,
   11938             :                                 &nVarId, bMandatory));
   11939             :     }
   11940       11299 :     *pnGroupId = nGroupId;
   11941       11299 :     *pnVarId = nVarId;
   11942       11299 :     return CE_None;
   11943             : }
   11944             : 
   11945             : // Like NCDFResolveVar but returns directly the var full name.
   11946        1326 : static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,
   11947             :                                      char **ppszFullName, bool bMandatory)
   11948             : {
   11949        1326 :     *ppszFullName = nullptr;
   11950             :     int nGroupId, nVarId;
   11951        1326 :     ERR_RET(
   11952             :         NCDFResolveVar(nStartGroupId, pszVar, &nGroupId, &nVarId, bMandatory));
   11953        1308 :     return NCDFGetVarFullName(nGroupId, nVarId, ppszFullName);
   11954             : }
   11955             : 
   11956             : // Like NCDFResolveVar but resolves an attribute instead a variable and
   11957             : // returns its integer value.
   11958             : // Only GLOBAL attributes are supported for the moment.
   11959         210 : static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,
   11960             :                                 const char *pszAtt, int *pnAtt, bool bMandatory)
   11961             : {
   11962         210 :     int nGroupId = nStartGroupId, nAttId = nStartVarId;
   11963         210 :     ERR_RET(NCDFResolveElem(nStartGroupId, nullptr, pszAtt, &nGroupId, &nAttId,
   11964             :                             bMandatory));
   11965           3 :     NCDF_ERR_RET(nc_get_att_int(nGroupId, NC_GLOBAL, pszAtt, pnAtt));
   11966           3 :     return CE_None;
   11967             : }
   11968             : 
   11969             : // Filter variables to keep only valid 2+D raster bands and vector fields in
   11970             : // a given a NetCDF (or group) ID and its sub-groups.
   11971             : // Coordinate or boundary variables are ignored.
   11972             : // It also creates corresponding vector layers.
   11973         508 : CPLErr netCDFDataset::FilterVars(
   11974             :     int nCdfId, bool bKeepRasters, bool bKeepVectors, char **papszIgnoreVars,
   11975             :     int *pnRasterVars, int *pnGroupId, int *pnVarId, int *pnIgnoredVars,
   11976             :     std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>
   11977             :         &oMap2DDimsToGroupAndVar)
   11978             : {
   11979         508 :     int nVars = 0;
   11980         508 :     int nRasterVars = 0;
   11981         508 :     NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
   11982             : 
   11983        1016 :     std::vector<int> anPotentialVectorVarID;
   11984             :     // oMapDimIdToCount[x] = number of times dim x is the first dimension of
   11985             :     // potential vector variables
   11986        1016 :     std::map<int, int> oMapDimIdToCount;
   11987         508 :     int nVarXId = -1;
   11988         508 :     int nVarYId = -1;
   11989         508 :     int nVarZId = -1;
   11990         508 :     int nVarTimeId = -1;
   11991         508 :     int nVarTimeDimId = -1;
   11992         508 :     bool bIsVectorOnly = true;
   11993         508 :     int nProfileDimId = -1;
   11994         508 :     int nParentIndexVarID = -1;
   11995             : 
   11996        3154 :     for (int v = 0; v < nVars; v++)
   11997             :     {
   11998             :         int nVarDims;
   11999        2646 :         NCDF_ERR_RET(nc_inq_varndims(nCdfId, v, &nVarDims));
   12000             :         // Should we ignore this variable?
   12001             :         char szTemp[NC_MAX_NAME + 1];
   12002        2646 :         szTemp[0] = '\0';
   12003        2646 :         NCDF_ERR_RET(nc_inq_varname(nCdfId, v, szTemp));
   12004             : 
   12005        2646 :         if (strstr(szTemp, "_node_coordinates") ||
   12006        2646 :             strstr(szTemp, "_node_count"))
   12007             :         {
   12008             :             // Ignore CF-1.8 Simple Geometries helper variables
   12009          75 :             continue;
   12010             :         }
   12011             : 
   12012        3849 :         if (nVarDims == 1 && (NCDFIsVarLongitude(nCdfId, -1, szTemp) ||
   12013        1278 :                               NCDFIsVarProjectionX(nCdfId, -1, szTemp)))
   12014             :         {
   12015         352 :             nVarXId = v;
   12016             :         }
   12017        3145 :         else if (nVarDims == 1 && (NCDFIsVarLatitude(nCdfId, -1, szTemp) ||
   12018         926 :                                    NCDFIsVarProjectionY(nCdfId, -1, szTemp)))
   12019             :         {
   12020         352 :             nVarYId = v;
   12021             :         }
   12022        1867 :         else if (nVarDims == 1 && NCDFIsVarVerticalCoord(nCdfId, -1, szTemp))
   12023             :         {
   12024          77 :             nVarZId = v;
   12025             :         }
   12026             :         else
   12027             :         {
   12028        1790 :             char *pszVarFullName = nullptr;
   12029        1790 :             CPLErr eErr = NCDFGetVarFullName(nCdfId, v, &pszVarFullName);
   12030        1790 :             if (eErr != CE_None)
   12031             :             {
   12032           0 :                 CPLFree(pszVarFullName);
   12033           0 :                 continue;
   12034             :             }
   12035             :             bool bIgnoreVar =
   12036        1790 :                 (CSLFindString(papszIgnoreVars, pszVarFullName) != -1);
   12037        1790 :             CPLFree(pszVarFullName);
   12038        1790 :             if (bIgnoreVar)
   12039             :             {
   12040          98 :                 if (nVarDims == 1 && NCDFIsVarTimeCoord(nCdfId, -1, szTemp))
   12041             :                 {
   12042           7 :                     nVarTimeId = v;
   12043           7 :                     nc_inq_vardimid(nCdfId, v, &nVarTimeDimId);
   12044             :                 }
   12045          91 :                 else if (nVarDims > 1)
   12046             :                 {
   12047          87 :                     (*pnIgnoredVars)++;
   12048          87 :                     CPLDebug("GDAL_netCDF", "variable #%d [%s] was ignored", v,
   12049             :                              szTemp);
   12050             :                 }
   12051             :             }
   12052             :             // Only accept 2+D vars.
   12053        1692 :             else if (nVarDims >= 2)
   12054             :             {
   12055         668 :                 bool bRasterCandidate = true;
   12056             :                 // Identify variables that might be vector variables
   12057         668 :                 if (nVarDims == 2)
   12058             :                 {
   12059         605 :                     int anDimIds[2] = {-1, -1};
   12060         605 :                     nc_inq_vardimid(nCdfId, v, anDimIds);
   12061             : 
   12062         605 :                     nc_type vartype = NC_NAT;
   12063         605 :                     nc_inq_vartype(nCdfId, v, &vartype);
   12064             : 
   12065             :                     char szDimNameFirst[NC_MAX_NAME + 1];
   12066             :                     char szDimNameSecond[NC_MAX_NAME + 1];
   12067         605 :                     szDimNameFirst[0] = '\0';
   12068         605 :                     szDimNameSecond[0] = '\0';
   12069        1372 :                     if (vartype == NC_CHAR &&
   12070         162 :                         nc_inq_dimname(nCdfId, anDimIds[0], szDimNameFirst) ==
   12071         162 :                             NC_NOERR &&
   12072         162 :                         nc_inq_dimname(nCdfId, anDimIds[1], szDimNameSecond) ==
   12073         162 :                             NC_NOERR &&
   12074         162 :                         !NCDFIsVarLongitude(nCdfId, -1, szDimNameSecond) &&
   12075         162 :                         !NCDFIsVarProjectionX(nCdfId, -1, szDimNameSecond) &&
   12076         929 :                         !NCDFIsVarLatitude(nCdfId, -1, szDimNameFirst) &&
   12077         162 :                         !NCDFIsVarProjectionY(nCdfId, -1, szDimNameFirst))
   12078             :                     {
   12079         162 :                         anPotentialVectorVarID.push_back(v);
   12080         162 :                         oMapDimIdToCount[anDimIds[0]]++;
   12081         162 :                         if (strstr(szDimNameSecond, "_max_width"))
   12082             :                         {
   12083         133 :                             bRasterCandidate = false;
   12084             :                         }
   12085             :                         else
   12086             :                         {
   12087          29 :                             std::array<int, 3> oKey{anDimIds[0], anDimIds[1],
   12088          29 :                                                     vartype};
   12089          29 :                             oMap2DDimsToGroupAndVar[oKey].emplace_back(
   12090          29 :                                 std::pair(nCdfId, v));
   12091             :                         }
   12092             :                     }
   12093             :                     else
   12094             :                     {
   12095         443 :                         std::array<int, 3> oKey{anDimIds[0], anDimIds[1],
   12096         443 :                                                 vartype};
   12097         443 :                         oMap2DDimsToGroupAndVar[oKey].emplace_back(
   12098         443 :                             std::pair(nCdfId, v));
   12099         443 :                         bIsVectorOnly = false;
   12100             :                     }
   12101             :                 }
   12102             :                 else
   12103             :                 {
   12104          63 :                     bIsVectorOnly = false;
   12105             :                 }
   12106         668 :                 if (bKeepRasters && bRasterCandidate)
   12107             :                 {
   12108         506 :                     *pnGroupId = nCdfId;
   12109         506 :                     *pnVarId = v;
   12110         506 :                     nRasterVars++;
   12111             :                 }
   12112             :             }
   12113        1024 :             else if (nVarDims == 1)
   12114             :             {
   12115         726 :                 nc_type atttype = NC_NAT;
   12116         726 :                 size_t attlen = 0;
   12117         726 :                 if (nc_inq_att(nCdfId, v, "instance_dimension", &atttype,
   12118          14 :                                &attlen) == NC_NOERR &&
   12119         726 :                     atttype == NC_CHAR && attlen < NC_MAX_NAME)
   12120             :                 {
   12121             :                     char szInstanceDimension[NC_MAX_NAME + 1];
   12122          14 :                     if (nc_get_att_text(nCdfId, v, "instance_dimension",
   12123          14 :                                         szInstanceDimension) == NC_NOERR)
   12124             :                     {
   12125          14 :                         szInstanceDimension[attlen] = 0;
   12126          14 :                         int status = nc_inq_dimid(nCdfId, szInstanceDimension,
   12127             :                                                   &nProfileDimId);
   12128          14 :                         if (status == NC_NOERR)
   12129          14 :                             nParentIndexVarID = v;
   12130             :                         else
   12131           0 :                             nProfileDimId = -1;
   12132          14 :                         if (status == NC_EBADDIM)
   12133           0 :                             CPLError(CE_Warning, CPLE_AppDefined,
   12134             :                                      "Attribute instance_dimension='%s' refers "
   12135             :                                      "to a non existing dimension",
   12136             :                                      szInstanceDimension);
   12137             :                         else
   12138          14 :                             NCDF_ERR(status);
   12139             :                     }
   12140             :                 }
   12141         726 :                 if (v != nParentIndexVarID)
   12142             :                 {
   12143         712 :                     anPotentialVectorVarID.push_back(v);
   12144         712 :                     int nDimId = -1;
   12145         712 :                     nc_inq_vardimid(nCdfId, v, &nDimId);
   12146         712 :                     oMapDimIdToCount[nDimId]++;
   12147             :                 }
   12148             :             }
   12149             :         }
   12150             :     }
   12151             : 
   12152             :     // If we are opened in raster-only mode and that there are only 1D or 2D
   12153             :     // variables and that the 2D variables have no X/Y dim, and all
   12154             :     // variables refer to the same main dimension (or 2 dimensions for
   12155             :     // featureType=profile), then it is a pure vector dataset
   12156             :     CPLString osFeatureType(
   12157         508 :         CSLFetchNameValueDef(papszMetadata, "NC_GLOBAL#featureType", ""));
   12158         397 :     if (bKeepRasters && !bKeepVectors && bIsVectorOnly && nRasterVars > 0 &&
   12159         905 :         !anPotentialVectorVarID.empty() &&
   12160           0 :         (oMapDimIdToCount.size() == 1 ||
   12161           0 :          (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2 &&
   12162           0 :           nProfileDimId >= 0)))
   12163             :     {
   12164           0 :         anPotentialVectorVarID.resize(0);
   12165             :     }
   12166             :     else
   12167             :     {
   12168         508 :         *pnRasterVars += nRasterVars;
   12169             :     }
   12170             : 
   12171         508 :     if (!anPotentialVectorVarID.empty() && bKeepVectors)
   12172             :     {
   12173             :         // Take the dimension that is referenced the most times.
   12174          64 :         if (!(oMapDimIdToCount.size() == 1 ||
   12175          27 :               (EQUAL(osFeatureType, "profile") &&
   12176          26 :                oMapDimIdToCount.size() == 2 && nProfileDimId >= 0)))
   12177             :         {
   12178           1 :             CPLError(CE_Warning, CPLE_AppDefined,
   12179             :                      "The dataset has several variables that could be "
   12180             :                      "identified as vector fields, but not all share the same "
   12181             :                      "primary dimension. Consequently they will be ignored.");
   12182             :         }
   12183             :         else
   12184             :         {
   12185          50 :             if (nVarTimeId >= 0 &&
   12186          50 :                 oMapDimIdToCount.find(nVarTimeDimId) != oMapDimIdToCount.end())
   12187             :             {
   12188           1 :                 anPotentialVectorVarID.push_back(nVarTimeId);
   12189             :             }
   12190          49 :             CreateGrpVectorLayers(nCdfId, osFeatureType, anPotentialVectorVarID,
   12191             :                                   oMapDimIdToCount, nVarXId, nVarYId, nVarZId,
   12192             :                                   nProfileDimId, nParentIndexVarID,
   12193             :                                   bKeepRasters);
   12194             :         }
   12195             :     }
   12196             : 
   12197             :     // Recurse on sub-groups.
   12198         508 :     int nSubGroups = 0;
   12199         508 :     int *panSubGroupIds = nullptr;
   12200         508 :     NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
   12201         534 :     for (int i = 0; i < nSubGroups; i++)
   12202             :     {
   12203          26 :         FilterVars(panSubGroupIds[i], bKeepRasters, bKeepVectors,
   12204             :                    papszIgnoreVars, pnRasterVars, pnGroupId, pnVarId,
   12205             :                    pnIgnoredVars, oMap2DDimsToGroupAndVar);
   12206             :     }
   12207         508 :     CPLFree(panSubGroupIds);
   12208             : 
   12209         508 :     return CE_None;
   12210             : }
   12211             : 
   12212             : // Create vector layers from given potentially identified vector variables
   12213             : // resulting from the scanning of a NetCDF (or group) ID.
   12214          49 : CPLErr netCDFDataset::CreateGrpVectorLayers(
   12215             :     int nCdfId, const CPLString &osFeatureType,
   12216             :     const std::vector<int> &anPotentialVectorVarID,
   12217             :     const std::map<int, int> &oMapDimIdToCount, int nVarXId, int nVarYId,
   12218             :     int nVarZId, int nProfileDimId, int nParentIndexVarID, bool bKeepRasters)
   12219             : {
   12220          49 :     char *pszGroupName = nullptr;
   12221          49 :     NCDFGetGroupFullName(nCdfId, &pszGroupName);
   12222          49 :     if (pszGroupName == nullptr || pszGroupName[0] == '\0')
   12223             :     {
   12224          47 :         CPLFree(pszGroupName);
   12225          47 :         pszGroupName = CPLStrdup(CPLGetBasename(osFilename));
   12226             :     }
   12227          49 :     OGRwkbGeometryType eGType = wkbUnknown;
   12228             :     CPLString osLayerName = CSLFetchNameValueDef(
   12229          98 :         papszMetadata, "NC_GLOBAL#ogr_layer_name", pszGroupName);
   12230          49 :     CPLFree(pszGroupName);
   12231          49 :     papszMetadata =
   12232          49 :         CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_name", nullptr);
   12233             : 
   12234          49 :     if (EQUAL(osFeatureType, "point") || EQUAL(osFeatureType, "profile"))
   12235             :     {
   12236          33 :         papszMetadata =
   12237          33 :             CSLSetNameValue(papszMetadata, "NC_GLOBAL#featureType", nullptr);
   12238          33 :         eGType = wkbPoint;
   12239             :     }
   12240             : 
   12241             :     const char *pszLayerType =
   12242          49 :         CSLFetchNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_type");
   12243          49 :     if (pszLayerType != nullptr)
   12244             :     {
   12245           9 :         eGType = OGRFromOGCGeomType(pszLayerType);
   12246           9 :         papszMetadata =
   12247           9 :             CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_type", nullptr);
   12248             :     }
   12249             : 
   12250             :     CPLString osGeometryField =
   12251          98 :         CSLFetchNameValueDef(papszMetadata, "NC_GLOBAL#ogr_geometry_field", "");
   12252          49 :     papszMetadata =
   12253          49 :         CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_geometry_field", nullptr);
   12254             : 
   12255          49 :     int nFirstVarId = -1;
   12256          49 :     int nVectorDim = oMapDimIdToCount.rbegin()->first;
   12257          49 :     if (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2)
   12258             :     {
   12259          13 :         if (nVectorDim == nProfileDimId)
   12260           0 :             nVectorDim = oMapDimIdToCount.begin()->first;
   12261             :     }
   12262             :     else
   12263             :     {
   12264          36 :         nProfileDimId = -1;
   12265             :     }
   12266          62 :     for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)
   12267             :     {
   12268          62 :         int anDimIds[2] = {-1, -1};
   12269          62 :         nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);
   12270          62 :         if (nVectorDim == anDimIds[0])
   12271             :         {
   12272          49 :             nFirstVarId = anPotentialVectorVarID[j];
   12273          49 :             break;
   12274             :         }
   12275             :     }
   12276             : 
   12277             :     // In case where coordinates are explicitly specified for one of the
   12278             :     // field/variable, use them in priority over the ones that might have been
   12279             :     // identified above.
   12280          49 :     char *pszCoordinates = nullptr;
   12281          49 :     if (NCDFGetAttr(nCdfId, nFirstVarId, "coordinates", &pszCoordinates) ==
   12282             :         CE_None)
   12283             :     {
   12284          34 :         char **papszTokens = NCDFTokenizeCoordinatesAttribute(pszCFCoordinates);
   12285          34 :         for (int i = 0; papszTokens != nullptr && papszTokens[i] != nullptr;
   12286             :              i++)
   12287             :         {
   12288           0 :             if (NCDFIsVarLongitude(nCdfId, -1, papszTokens[i]) ||
   12289           0 :                 NCDFIsVarProjectionX(nCdfId, -1, papszTokens[i]))
   12290             :             {
   12291           0 :                 nVarXId = -1;
   12292           0 :                 CPL_IGNORE_RET_VAL(
   12293           0 :                     nc_inq_varid(nCdfId, papszTokens[i], &nVarXId));
   12294             :             }
   12295           0 :             else if (NCDFIsVarLatitude(nCdfId, -1, papszTokens[i]) ||
   12296           0 :                      NCDFIsVarProjectionY(nCdfId, -1, papszTokens[i]))
   12297             :             {
   12298           0 :                 nVarYId = -1;
   12299           0 :                 CPL_IGNORE_RET_VAL(
   12300           0 :                     nc_inq_varid(nCdfId, papszTokens[i], &nVarYId));
   12301             :             }
   12302           0 :             else if (NCDFIsVarVerticalCoord(nCdfId, -1, papszTokens[i]))
   12303             :             {
   12304           0 :                 nVarZId = -1;
   12305           0 :                 CPL_IGNORE_RET_VAL(
   12306           0 :                     nc_inq_varid(nCdfId, papszTokens[i], &nVarZId));
   12307             :             }
   12308             :         }
   12309          34 :         CSLDestroy(papszTokens);
   12310             :     }
   12311          49 :     CPLFree(pszCoordinates);
   12312             : 
   12313             :     // Check that the X,Y,Z vars share 1D and share the same dimension as
   12314             :     // attribute variables.
   12315          49 :     if (nVarXId >= 0 && nVarYId >= 0)
   12316             :     {
   12317          38 :         int nVarDimCount = -1;
   12318          38 :         int nVarDimId = -1;
   12319          38 :         if (nc_inq_varndims(nCdfId, nVarXId, &nVarDimCount) != NC_NOERR ||
   12320          38 :             nVarDimCount != 1 ||
   12321          38 :             nc_inq_vardimid(nCdfId, nVarXId, &nVarDimId) != NC_NOERR ||
   12322          38 :             nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim) ||
   12323          35 :             nc_inq_varndims(nCdfId, nVarYId, &nVarDimCount) != NC_NOERR ||
   12324          35 :             nVarDimCount != 1 ||
   12325         111 :             nc_inq_vardimid(nCdfId, nVarYId, &nVarDimId) != NC_NOERR ||
   12326          35 :             nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim))
   12327             :         {
   12328           3 :             nVarXId = nVarYId = -1;
   12329             :         }
   12330          69 :         else if (nVarZId >= 0 &&
   12331          34 :                  (nc_inq_varndims(nCdfId, nVarZId, &nVarDimCount) != NC_NOERR ||
   12332          34 :                   nVarDimCount != 1 ||
   12333          34 :                   nc_inq_vardimid(nCdfId, nVarZId, &nVarDimId) != NC_NOERR ||
   12334          34 :                   nVarDimId != nVectorDim))
   12335             :         {
   12336           0 :             nVarZId = -1;
   12337             :         }
   12338             :     }
   12339             : 
   12340          49 :     if (eGType == wkbUnknown && nVarXId >= 0 && nVarYId >= 0)
   12341             :     {
   12342           2 :         eGType = wkbPoint;
   12343             :     }
   12344          49 :     if (eGType == wkbPoint && nVarXId >= 0 && nVarYId >= 0 && nVarZId >= 0)
   12345             :     {
   12346          34 :         eGType = wkbPoint25D;
   12347             :     }
   12348          49 :     if (eGType == wkbUnknown && osGeometryField.empty())
   12349             :     {
   12350           5 :         eGType = wkbNone;
   12351             :     }
   12352             : 
   12353             :     // Read projection info
   12354          49 :     char **papszMetadataBackup = CSLDuplicate(papszMetadata);
   12355          49 :     ReadAttributes(nCdfId, nFirstVarId);
   12356          49 :     if (!this->bSGSupport)
   12357          48 :         SetProjectionFromVar(nCdfId, nFirstVarId, true);
   12358          49 :     const char *pszValue = FetchAttr(nCdfId, nFirstVarId, CF_GRD_MAPPING);
   12359          49 :     char *pszGridMapping = (pszValue ? CPLStrdup(pszValue) : nullptr);
   12360          49 :     CSLDestroy(papszMetadata);
   12361          49 :     papszMetadata = papszMetadataBackup;
   12362             : 
   12363          49 :     OGRSpatialReference *poSRS = nullptr;
   12364          49 :     if (!m_oSRS.IsEmpty())
   12365             :     {
   12366          21 :         poSRS = m_oSRS.Clone();
   12367             :     }
   12368             :     // Reset if there's a 2D raster
   12369          49 :     m_bHasProjection = false;
   12370          49 :     m_bHasGeoTransform = false;
   12371             : 
   12372          49 :     if (!bKeepRasters)
   12373             :     {
   12374             :         // Strip out uninteresting metadata.
   12375          45 :         papszMetadata =
   12376          45 :             CSLSetNameValue(papszMetadata, "NC_GLOBAL#Conventions", nullptr);
   12377          45 :         papszMetadata =
   12378          45 :             CSLSetNameValue(papszMetadata, "NC_GLOBAL#GDAL", nullptr);
   12379          45 :         papszMetadata =
   12380          45 :             CSLSetNameValue(papszMetadata, "NC_GLOBAL#history", nullptr);
   12381             :     }
   12382             : 
   12383             :     std::shared_ptr<netCDFLayer> poLayer(
   12384          49 :         new netCDFLayer(this, nCdfId, osLayerName, eGType, poSRS));
   12385          49 :     if (poSRS != nullptr)
   12386          21 :         poSRS->Release();
   12387          49 :     poLayer->SetRecordDimID(nVectorDim);
   12388          49 :     if (wkbFlatten(eGType) == wkbPoint && nVarXId >= 0 && nVarYId >= 0)
   12389             :     {
   12390          35 :         poLayer->SetXYZVars(nVarXId, nVarYId, nVarZId);
   12391             :     }
   12392          14 :     else if (!osGeometryField.empty())
   12393             :     {
   12394           9 :         poLayer->SetWKTGeometryField(osGeometryField);
   12395             :     }
   12396          49 :     if (pszGridMapping != nullptr)
   12397             :     {
   12398          21 :         poLayer->SetGridMapping(pszGridMapping);
   12399          21 :         CPLFree(pszGridMapping);
   12400             :     }
   12401          49 :     poLayer->SetProfile(nProfileDimId, nParentIndexVarID);
   12402             : 
   12403         574 :     for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)
   12404             :     {
   12405         525 :         int anDimIds[2] = {-1, -1};
   12406         525 :         nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);
   12407         525 :         if (anDimIds[0] == nVectorDim ||
   12408          24 :             (nProfileDimId >= 0 && anDimIds[0] == nProfileDimId))
   12409             :         {
   12410             : #ifdef NCDF_DEBUG
   12411             :             char szTemp2[NC_MAX_NAME + 1] = {};
   12412             :             CPL_IGNORE_RET_VAL(
   12413             :                 nc_inq_varname(nCdfId, anPotentialVectorVarID[j], szTemp2));
   12414             :             CPLDebug("GDAL_netCDF", "Variable %s is a vector field", szTemp2);
   12415             : #endif
   12416         525 :             poLayer->AddField(anPotentialVectorVarID[j]);
   12417             :         }
   12418             :     }
   12419             : 
   12420          49 :     if (poLayer->GetLayerDefn()->GetFieldCount() != 0 ||
   12421           0 :         poLayer->GetGeomType() != wkbNone)
   12422             :     {
   12423          49 :         papoLayers.push_back(poLayer);
   12424             :     }
   12425             : 
   12426          98 :     return CE_None;
   12427             : }
   12428             : 
   12429             : // Get all coordinate and boundary variables full names referenced in
   12430             : // a given a NetCDF (or group) ID and its sub-groups.
   12431             : // These variables are identified in other variable's
   12432             : // "coordinates" and "bounds" attribute.
   12433             : // Searching coordinate and boundary variables may need to explore
   12434             : // parents groups (or other groups in case of reference given in form of an
   12435             : // absolute path).
   12436             : // See CF sections 5.2, 5.6 and 7.1
   12437         509 : static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId, char ***ppapszVars)
   12438             : {
   12439         509 :     int nVars = 0;
   12440         509 :     NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
   12441             : 
   12442        3169 :     for (int v = 0; v < nVars; v++)
   12443             :     {
   12444        2660 :         char *pszTemp = nullptr;
   12445        2660 :         char **papszTokens = nullptr;
   12446        2660 :         if (NCDFGetAttr(nCdfId, v, "coordinates", &pszTemp) == CE_None)
   12447         441 :             papszTokens = NCDFTokenizeCoordinatesAttribute(pszTemp);
   12448        2660 :         CPLFree(pszTemp);
   12449        2660 :         pszTemp = nullptr;
   12450        2660 :         if (NCDFGetAttr(nCdfId, v, "bounds", &pszTemp) == CE_None &&
   12451        2660 :             pszTemp != nullptr && !EQUAL(pszTemp, ""))
   12452           9 :             papszTokens = CSLAddString(papszTokens, pszTemp);
   12453        2660 :         CPLFree(pszTemp);
   12454        3926 :         for (int i = 0; papszTokens != nullptr && papszTokens[i] != nullptr;
   12455             :              i++)
   12456             :         {
   12457        1266 :             char *pszVarFullName = nullptr;
   12458        1266 :             if (NCDFResolveVarFullName(nCdfId, papszTokens[i],
   12459        1266 :                                        &pszVarFullName) == CE_None)
   12460        1248 :                 *ppapszVars = CSLAddString(*ppapszVars, pszVarFullName);
   12461        1266 :             CPLFree(pszVarFullName);
   12462             :         }
   12463        2660 :         CSLDestroy(papszTokens);
   12464             :     }
   12465             : 
   12466             :     // Recurse on sub-groups.
   12467             :     int nSubGroups;
   12468         509 :     int *panSubGroupIds = nullptr;
   12469         509 :     NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
   12470         535 :     for (int i = 0; i < nSubGroups; i++)
   12471             :     {
   12472          26 :         NCDFGetCoordAndBoundVarFullNames(panSubGroupIds[i], ppapszVars);
   12473             :     }
   12474         509 :     CPLFree(panSubGroupIds);
   12475             : 
   12476         509 :     return CE_None;
   12477             : }
   12478             : 
   12479             : // Check if give type is user defined
   12480         862 : bool NCDFIsUserDefinedType(int /*ncid*/, int type)
   12481             : {
   12482         862 :     return type >= NC_FIRSTUSERTYPEID;
   12483             : }
   12484             : 
   12485         533 : char **NCDFTokenizeCoordinatesAttribute(const char *pszCoordinates)
   12486             : {
   12487             :     // CF conventions use space as the separator for variable names in the
   12488             :     // coordinates attribute, but some products such as
   12489             :     // https://sentinel.esa.int/web/sentinel/technical-guides/sentinel-3-synergy/products-algorithms/level-2-aod-algorithms-and-products/level-2-aod-products-description
   12490             :     // use comma.
   12491         533 :     return CSLTokenizeString2(pszCoordinates, ", ", 0);
   12492             : }

Generated by: LCOV version 1.14