/src/gdal/frmts/netcdf/netcdfdataset.cpp
Line  | Count  | Source (jump to first uncovered line)  | 
1  |  | /******************************************************************************  | 
2  |  |  *  | 
3  |  |  * Project:  netCDF read/write Driver  | 
4  |  |  * Purpose:  GDAL bindings over netCDF library.  | 
5  |  |  * Author:   Frank Warmerdam, warmerdam@pobox.com  | 
6  |  |  *           Even Rouault <even.rouault at spatialys.com>  | 
7  |  |  *  | 
8  |  |  ******************************************************************************  | 
9  |  |  * Copyright (c) 2004, Frank Warmerdam  | 
10  |  |  * Copyright (c) 2007-2016, Even Rouault <even.rouault at spatialys.com>  | 
11  |  |  * Copyright (c) 2010, Kyle Shannon <kyle at pobox dot com>  | 
12  |  |  * Copyright (c) 2021, CLS  | 
13  |  |  *  | 
14  |  |  * SPDX-License-Identifier: MIT  | 
15  |  |  ****************************************************************************/  | 
16  |  |  | 
17  |  | #include "cpl_port.h"  | 
18  |  |  | 
19  |  | #include <array>  | 
20  |  | #include <cassert>  | 
21  |  | #include <cctype>  | 
22  |  | #include <cerrno>  | 
23  |  | #include <climits>  | 
24  |  | #include <cmath>  | 
25  |  | #include <cstdio>  | 
26  |  | #include <cstdlib>  | 
27  |  | #include <cstring>  | 
28  |  | #include <ctime>  | 
29  |  | #include <algorithm>  | 
30  |  | #include <limits>  | 
31  |  | #include <map>  | 
32  |  | #include <mutex>  | 
33  |  | #include <set>  | 
34  |  | #include <queue>  | 
35  |  | #include <string>  | 
36  |  | #include <tuple>  | 
37  |  | #include <utility>  | 
38  |  | #include <vector>  | 
39  |  |  | 
40  |  | // Must be included after standard includes, otherwise VS2015 fails when  | 
41  |  | // including <ctime>  | 
42  |  | #include "netcdfdataset.h"  | 
43  |  | #include "netcdfdrivercore.h"  | 
44  |  | #include "netcdfsg.h"  | 
45  |  | #include "netcdfuffd.h"  | 
46  |  |  | 
47  |  | #include "netcdf_mem.h"  | 
48  |  |  | 
49  |  | #include "cpl_conv.h"  | 
50  |  | #include "cpl_error.h"  | 
51  |  | #include "cpl_float.h"  | 
52  |  | #include "cpl_json.h"  | 
53  |  | #include "cpl_minixml.h"  | 
54  |  | #include "cpl_multiproc.h"  | 
55  |  | #include "cpl_progress.h"  | 
56  |  | #include "cpl_time.h"  | 
57  |  | #include "gdal.h"  | 
58  |  | #include "gdal_frmts.h"  | 
59  |  | #include "gdal_priv_templates.hpp"  | 
60  |  | #include "ogr_core.h"  | 
61  |  | #include "ogr_srs_api.h"  | 
62  |  |  | 
63  |  | // netCDF 4.8 switched to expecting filenames in UTF-8 on Windows  | 
64  |  | // But with netCDF 4.9 and https://github.com/Unidata/netcdf-c/pull/2277/,  | 
65  |  | // this is apparently back to expecting filenames in current codepage...  | 
66  |  | // Detect netCDF 4.8 with NC_ENCZARR  | 
67  |  | // Detect netCDF 4.9 with NC_NOATTCREORD  | 
68  |  | #if defined(NC_ENCZARR) && !defined(NC_NOATTCREORD)  | 
69  |  | #define NETCDF_USES_UTF8  | 
70  |  | #endif  | 
71  |  |  | 
72  |  | // Internal function declarations.  | 
73  |  |  | 
74  |  | static bool NCDFIsGDALVersionGTE(const char *pszVersion, int nTarget);  | 
75  |  |  | 
76  |  | static void  | 
77  |  | NCDFAddGDALHistory(int fpImage, const char *pszFilename, bool bWriteGDALVersion,  | 
78  |  |                    bool bWriteGDALHistory, const char *pszOldHist,  | 
79  |  |                    const char *pszFunctionName,  | 
80  |  |                    const char *pszCFVersion = GDAL_DEFAULT_NCDF_CONVENTIONS);  | 
81  |  |  | 
82  |  | static void NCDFAddHistory(int fpImage, const char *pszAddHist,  | 
83  |  |                            const char *pszOldHist);  | 
84  |  |  | 
85  |  | static CPLErr NCDFSafeStrcat(char **ppszDest, const char *pszSrc,  | 
86  |  |                              size_t *nDestSize);  | 
87  |  |  | 
88  |  | // Var / attribute helper functions.  | 
89  |  | static CPLErr NCDFPutAttr(int nCdfId, int nVarId, const char *pszAttrName,  | 
90  |  |                           const char *pszValue);  | 
91  |  |  | 
92  |  | // Replace this where used.  | 
93  |  | static CPLErr NCDFGet1DVar(int nCdfId, int nVarId, char **pszValue);  | 
94  |  | static CPLErr NCDFPut1DVar(int nCdfId, int nVarId, const char *pszValue);  | 
95  |  |  | 
96  |  | // Replace this where used.  | 
97  |  | static char **NCDFTokenizeArray(const char *pszValue);  | 
98  |  | static void CopyMetadata(GDALDataset *poSrcDS, GDALRasterBand *poSrcBand,  | 
99  |  |                          GDALRasterBand *poDstBand, int fpImage, int CDFVarID,  | 
100  |  |                          const char *pszMatchPrefix = nullptr);  | 
101  |  |  | 
102  |  | // NetCDF-4 groups helper functions.  | 
103  |  | // They all work also for NetCDF-3 files which are considered as  | 
104  |  | // NetCDF-4 file with only one group.  | 
105  |  | static CPLErr NCDFOpenSubDataset(int nCdfId, const char *pszSubdatasetName,  | 
106  |  |                                  int *pnGroupId, int *pnVarId);  | 
107  |  | static CPLErr NCDFGetVisibleDims(int nGroupId, int *pnDims, int **ppanDimIds);  | 
108  |  | static CPLErr NCDFGetSubGroups(int nGroupId, int *pnSubGroups,  | 
109  |  |                                int **ppanSubGroupIds);  | 
110  |  | static CPLErr NCDFGetGroupFullName(int nGroupId, char **ppszFullName,  | 
111  |  |                                    bool bNC3Compat = true);  | 
112  |  | static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId, char **ppszFullName,  | 
113  |  |                                  bool bNC3Compat = true);  | 
114  |  | static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId);  | 
115  |  |  | 
116  |  | static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,  | 
117  |  |                                      char **ppszFullName,  | 
118  |  |                                      bool bMandatory = false);  | 
119  |  | static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,  | 
120  |  |                                 const char *pszAtt, int *pnAtt,  | 
121  |  |                                 bool bMandatory = false);  | 
122  |  | static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId, char ***ppapszVars);  | 
123  |  |  | 
124  |  | // Uncomment this for more debug output.  | 
125  |  | // #define NCDF_DEBUG 1  | 
126  |  |  | 
127  |  | CPLMutex *hNCMutex = nullptr;  | 
128  |  |  | 
129  |  | // Workaround https://github.com/OSGeo/gdal/issues/6253  | 
130  |  | // Having 2 netCDF handles on the same file doesn't work in a multi-threaded  | 
131  |  | // way. Apparently having the same handle works better (this is OK since  | 
132  |  | // we have a global mutex on the netCDF library)  | 
133  |  | static std::map<std::string, int> goMapNameToNetCDFId;  | 
134  |  | static std::map<int, std::pair<std::string, int>> goMapNetCDFIdToKeyAndCount;  | 
135  |  |  | 
136  |  | int GDAL_nc_open(const char *pszFilename, int nMode, int *pID)  | 
137  | 399  | { | 
138  | 399  |     std::string osKey(pszFilename);  | 
139  | 399  |     osKey += "#####";  | 
140  | 399  |     osKey += std::to_string(nMode);  | 
141  | 399  |     auto oIter = goMapNameToNetCDFId.find(osKey);  | 
142  | 399  |     if (oIter == goMapNameToNetCDFId.end())  | 
143  | 399  |     { | 
144  | 399  |         int ret = nc_open(pszFilename, nMode, pID);  | 
145  | 399  |         if (ret != NC_NOERR)  | 
146  | 0  |             return ret;  | 
147  | 399  |         goMapNameToNetCDFId[osKey] = *pID;  | 
148  | 399  |         goMapNetCDFIdToKeyAndCount[*pID] =  | 
149  | 399  |             std::pair<std::string, int>(osKey, 1);  | 
150  | 399  |         return ret;  | 
151  | 399  |     }  | 
152  | 0  |     else  | 
153  | 0  |     { | 
154  | 0  |         *pID = oIter->second;  | 
155  | 0  |         goMapNetCDFIdToKeyAndCount[oIter->second].second++;  | 
156  | 0  |         return NC_NOERR;  | 
157  | 0  |     }  | 
158  | 399  | }  | 
159  |  |  | 
160  |  | int GDAL_nc_close(int cdfid)  | 
161  | 798  | { | 
162  | 798  |     int ret = NC_NOERR;  | 
163  | 798  |     auto oIter = goMapNetCDFIdToKeyAndCount.find(cdfid);  | 
164  | 798  |     if (oIter != goMapNetCDFIdToKeyAndCount.end())  | 
165  | 399  |     { | 
166  | 399  |         if (--oIter->second.second == 0)  | 
167  | 399  |         { | 
168  | 399  |             ret = nc_close(cdfid);  | 
169  | 399  |             goMapNameToNetCDFId.erase(oIter->second.first);  | 
170  | 399  |             goMapNetCDFIdToKeyAndCount.erase(oIter);  | 
171  | 399  |         }  | 
172  | 399  |     }  | 
173  | 399  |     else  | 
174  | 399  |     { | 
175  |  |         // we can go here if file opened with nc_open_mem() or nc_create()  | 
176  | 399  |         ret = nc_close(cdfid);  | 
177  | 399  |     }  | 
178  | 798  |     return ret;  | 
179  | 798  | }  | 
180  |  |  | 
181  |  | /************************************************************************/  | 
182  |  | /* ==================================================================== */  | 
183  |  | /*                         netCDFRasterBand                             */  | 
184  |  | /* ==================================================================== */  | 
185  |  | /************************************************************************/  | 
186  |  |  | 
187  |  | class netCDFRasterBand final : public GDALPamRasterBand  | 
188  |  | { | 
189  |  |     friend class netCDFDataset;  | 
190  |  |  | 
191  |  |     nc_type nc_datatype;  | 
192  |  |     int cdfid;  | 
193  |  |     int nZId;  | 
194  |  |     int nZDim;  | 
195  |  |     int nLevel;  | 
196  |  |     int nBandXPos;  | 
197  |  |     int nBandYPos;  | 
198  |  |     int *panBandZPos;  | 
199  |  |     int *panBandZLev;  | 
200  |  |     bool m_bNoDataSet = false;  | 
201  |  |     double m_dfNoDataValue = 0;  | 
202  |  |     bool m_bNoDataSetAsInt64 = false;  | 
203  |  |     int64_t m_nNodataValueInt64 = 0;  | 
204  |  |     bool m_bNoDataSetAsUInt64 = false;  | 
205  |  |     uint64_t m_nNodataValueUInt64 = 0;  | 
206  |  |     bool bValidRangeValid = false;  | 
207  |  |     double adfValidRange[2]{0, 0}; | 
208  |  |     bool m_bHaveScale = false;  | 
209  |  |     bool m_bHaveOffset = false;  | 
210  |  |     double m_dfScale = 1;  | 
211  |  |     double m_dfOffset = 0;  | 
212  |  |     CPLString m_osUnitType{}; | 
213  |  |     bool bSignedData;  | 
214  |  |     bool bCheckLongitude;  | 
215  |  |     bool m_bCreateMetadataFromOtherVarsDone = false;  | 
216  |  |  | 
217  |  |     void CreateMetadataFromAttributes();  | 
218  |  |     void CreateMetadataFromOtherVars();  | 
219  |  |  | 
220  |  |     template <class T>  | 
221  |  |     void CheckData(void *pImage, void *pImageNC, size_t nTmpBlockXSize,  | 
222  |  |                    size_t nTmpBlockYSize, bool bCheckIsNan = false);  | 
223  |  |     template <class T>  | 
224  |  |     void CheckDataCpx(void *pImage, void *pImageNC, size_t nTmpBlockXSize,  | 
225  |  |                       size_t nTmpBlockYSize, bool bCheckIsNan = false);  | 
226  |  |     void SetBlockSize();  | 
227  |  |  | 
228  |  |     bool FetchNetcdfChunk(size_t xstart, size_t ystart, void *pImage);  | 
229  |  |  | 
230  |  |     void SetNoDataValueNoUpdate(double dfNoData);  | 
231  |  |     void SetNoDataValueNoUpdate(int64_t nNoData);  | 
232  |  |     void SetNoDataValueNoUpdate(uint64_t nNoData);  | 
233  |  |  | 
234  |  |     void SetOffsetNoUpdate(double dfVal);  | 
235  |  |     void SetScaleNoUpdate(double dfVal);  | 
236  |  |     void SetUnitTypeNoUpdate(const char *pszNewValue);  | 
237  |  |  | 
238  |  |   protected:  | 
239  |  |     CPLXMLNode *SerializeToXML(const char *pszUnused) override;  | 
240  |  |  | 
241  |  |   public:  | 
242  |  |     struct CONSTRUCTOR_OPEN  | 
243  |  |     { | 
244  |  |     };  | 
245  |  |  | 
246  |  |     struct CONSTRUCTOR_CREATE  | 
247  |  |     { | 
248  |  |     };  | 
249  |  |  | 
250  |  |     netCDFRasterBand(const CONSTRUCTOR_OPEN &, netCDFDataset *poDS,  | 
251  |  |                      int nGroupId, int nZId, int nZDim, int nLevel,  | 
252  |  |                      const int *panBandZLen, const int *panBandPos, int nBand);  | 
253  |  |     netCDFRasterBand(const CONSTRUCTOR_CREATE &, netCDFDataset *poDS,  | 
254  |  |                      GDALDataType eType, int nBand, bool bSigned = true,  | 
255  |  |                      const char *pszBandName = nullptr,  | 
256  |  |                      const char *pszLongName = nullptr, int nZId = -1,  | 
257  |  |                      int nZDim = 2, int nLevel = 0,  | 
258  |  |                      const int *panBandZLev = nullptr,  | 
259  |  |                      const int *panBandZPos = nullptr,  | 
260  |  |                      const int *paDimIds = nullptr);  | 
261  |  |     virtual ~netCDFRasterBand();  | 
262  |  |  | 
263  |  |     virtual double GetNoDataValue(int *) override;  | 
264  |  |     virtual int64_t GetNoDataValueAsInt64(int *pbSuccess = nullptr) override;  | 
265  |  |     virtual uint64_t GetNoDataValueAsUInt64(int *pbSuccess = nullptr) override;  | 
266  |  |     virtual CPLErr SetNoDataValue(double) override;  | 
267  |  |     virtual CPLErr SetNoDataValueAsInt64(int64_t nNoData) override;  | 
268  |  |     virtual CPLErr SetNoDataValueAsUInt64(uint64_t nNoData) override;  | 
269  |  |     // virtual CPLErr DeleteNoDataValue();  | 
270  |  |     virtual double GetOffset(int *) override;  | 
271  |  |     virtual CPLErr SetOffset(double) override;  | 
272  |  |     virtual double GetScale(int *) override;  | 
273  |  |     virtual CPLErr SetScale(double) override;  | 
274  |  |     virtual const char *GetUnitType() override;  | 
275  |  |     virtual CPLErr SetUnitType(const char *) override;  | 
276  |  |     virtual CPLErr IReadBlock(int, int, void *) override;  | 
277  |  |     virtual CPLErr IWriteBlock(int, int, void *) override;  | 
278  |  |  | 
279  |  |     char **GetMetadata(const char *pszDomain = "") override;  | 
280  |  |     const char *GetMetadataItem(const char *pszName,  | 
281  |  |                                 const char *pszDomain = "") override;  | 
282  |  |  | 
283  |  |     virtual CPLErr SetMetadataItem(const char *pszName, const char *pszValue,  | 
284  |  |                                    const char *pszDomain = "") override;  | 
285  |  |     virtual CPLErr SetMetadata(char **papszMD,  | 
286  |  |                                const char *pszDomain = "") override;  | 
287  |  | };  | 
288  |  |  | 
289  |  | /************************************************************************/  | 
290  |  | /*                          netCDFRasterBand()                          */  | 
291  |  | /************************************************************************/  | 
292  |  |  | 
293  |  | netCDFRasterBand::netCDFRasterBand(const netCDFRasterBand::CONSTRUCTOR_OPEN &,  | 
294  |  |                                    netCDFDataset *poNCDFDS, int nGroupId,  | 
295  |  |                                    int nZIdIn, int nZDimIn, int nLevelIn,  | 
296  |  |                                    const int *panBandZLevIn,  | 
297  |  |                                    const int *panBandZPosIn, int nBandIn)  | 
298  | 0  |     : nc_datatype(NC_NAT), cdfid(nGroupId), nZId(nZIdIn), nZDim(nZDimIn),  | 
299  | 0  |       nLevel(nLevelIn), nBandXPos(panBandZPosIn[0]),  | 
300  | 0  |       nBandYPos(nZDim == 1 ? -1 : panBandZPosIn[1]), panBandZPos(nullptr),  | 
301  | 0  |       panBandZLev(nullptr),  | 
302  | 0  |       bSignedData(true),  // Default signed, except for Byte.  | 
303  | 0  |       bCheckLongitude(false)  | 
304  | 0  | { | 
305  | 0  |     poDS = poNCDFDS;  | 
306  | 0  |     nBand = nBandIn;  | 
307  |  |  | 
308  |  |     // Take care of all other dimensions.  | 
309  | 0  |     if (nZDim > 2)  | 
310  | 0  |     { | 
311  | 0  |         panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));  | 
312  | 0  |         panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));  | 
313  |  | 
  | 
314  | 0  |         for (int i = 0; i < nZDim - 2; i++)  | 
315  | 0  |         { | 
316  | 0  |             panBandZPos[i] = panBandZPosIn[i + 2];  | 
317  | 0  |             panBandZLev[i] = panBandZLevIn[i];  | 
318  | 0  |         }  | 
319  | 0  |     }  | 
320  |  | 
  | 
321  | 0  |     nRasterXSize = poDS->GetRasterXSize();  | 
322  | 0  |     nRasterYSize = poDS->GetRasterYSize();  | 
323  | 0  |     nBlockXSize = poDS->GetRasterXSize();  | 
324  | 0  |     nBlockYSize = 1;  | 
325  |  |  | 
326  |  |     // Get the type of the "z" variable, our target raster array.  | 
327  | 0  |     if (nc_inq_var(cdfid, nZId, nullptr, &nc_datatype, nullptr, nullptr,  | 
328  | 0  |                    nullptr) != NC_NOERR)  | 
329  | 0  |     { | 
330  | 0  |         CPLError(CE_Failure, CPLE_AppDefined, "Error in nc_var_inq() on 'z'.");  | 
331  | 0  |         return;  | 
332  | 0  |     }  | 
333  |  |  | 
334  | 0  |     if (NCDFIsUserDefinedType(cdfid, nc_datatype))  | 
335  | 0  |     { | 
336  |  |         // First enquire and check that the number of fields is 2  | 
337  | 0  |         size_t nfields, compoundsize;  | 
338  | 0  |         if (nc_inq_compound(cdfid, nc_datatype, nullptr, &compoundsize,  | 
339  | 0  |                             &nfields) != NC_NOERR)  | 
340  | 0  |         { | 
341  | 0  |             CPLError(CE_Failure, CPLE_AppDefined,  | 
342  | 0  |                      "Error in nc_inq_compound() on 'z'.");  | 
343  | 0  |             return;  | 
344  | 0  |         }  | 
345  |  |  | 
346  | 0  |         if (nfields != 2)  | 
347  | 0  |         { | 
348  | 0  |             CPLError(CE_Failure, CPLE_AppDefined,  | 
349  | 0  |                      "Unsupported data type encountered in nc_inq_compound() "  | 
350  | 0  |                      "on 'z'.");  | 
351  | 0  |             return;  | 
352  | 0  |         }  | 
353  |  |  | 
354  |  |         // Now check that that two types are the same in the struct.  | 
355  | 0  |         nc_type field_type1, field_type2;  | 
356  | 0  |         int field_dims1, field_dims2;  | 
357  | 0  |         if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,  | 
358  | 0  |                                   &field_type1, &field_dims1,  | 
359  | 0  |                                   nullptr) != NC_NOERR)  | 
360  | 0  |         { | 
361  | 0  |             CPLError(  | 
362  | 0  |                 CE_Failure, CPLE_AppDefined,  | 
363  | 0  |                 "Error in querying Field 1 in nc_inq_compound_field() on 'z'.");  | 
364  | 0  |             return;  | 
365  | 0  |         }  | 
366  |  |  | 
367  | 0  |         if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,  | 
368  | 0  |                                   &field_type2, &field_dims2,  | 
369  | 0  |                                   nullptr) != NC_NOERR)  | 
370  | 0  |         { | 
371  | 0  |             CPLError(  | 
372  | 0  |                 CE_Failure, CPLE_AppDefined,  | 
373  | 0  |                 "Error in querying Field 2 in nc_inq_compound_field() on 'z'.");  | 
374  | 0  |             return;  | 
375  | 0  |         }  | 
376  |  |  | 
377  | 0  |         if ((field_type1 != field_type2) || (field_dims1 != field_dims2) ||  | 
378  | 0  |             (field_dims1 != 0))  | 
379  | 0  |         { | 
380  | 0  |             CPLError(CE_Failure, CPLE_AppDefined,  | 
381  | 0  |                      "Error in interpreting compound data type on 'z'.");  | 
382  | 0  |             return;  | 
383  | 0  |         }  | 
384  |  |  | 
385  | 0  |         if (field_type1 == NC_SHORT)  | 
386  | 0  |             eDataType = GDT_CInt16;  | 
387  | 0  |         else if (field_type1 == NC_INT)  | 
388  | 0  |             eDataType = GDT_CInt32;  | 
389  | 0  |         else if (field_type1 == NC_FLOAT)  | 
390  | 0  |             eDataType = GDT_CFloat32;  | 
391  | 0  |         else if (field_type1 == NC_DOUBLE)  | 
392  | 0  |             eDataType = GDT_CFloat64;  | 
393  | 0  |         else  | 
394  | 0  |         { | 
395  | 0  |             CPLError(CE_Failure, CPLE_AppDefined,  | 
396  | 0  |                      "Unsupported netCDF compound data type encountered.");  | 
397  | 0  |             return;  | 
398  | 0  |         }  | 
399  | 0  |     }  | 
400  | 0  |     else  | 
401  | 0  |     { | 
402  | 0  |         if (nc_datatype == NC_BYTE)  | 
403  | 0  |             eDataType = GDT_Byte;  | 
404  | 0  |         else if (nc_datatype == NC_CHAR)  | 
405  | 0  |             eDataType = GDT_Byte;  | 
406  | 0  |         else if (nc_datatype == NC_SHORT)  | 
407  | 0  |             eDataType = GDT_Int16;  | 
408  | 0  |         else if (nc_datatype == NC_INT)  | 
409  | 0  |             eDataType = GDT_Int32;  | 
410  | 0  |         else if (nc_datatype == NC_FLOAT)  | 
411  | 0  |             eDataType = GDT_Float32;  | 
412  | 0  |         else if (nc_datatype == NC_DOUBLE)  | 
413  | 0  |             eDataType = GDT_Float64;  | 
414  | 0  |         else if (nc_datatype == NC_UBYTE)  | 
415  | 0  |             eDataType = GDT_Byte;  | 
416  | 0  |         else if (nc_datatype == NC_USHORT)  | 
417  | 0  |             eDataType = GDT_UInt16;  | 
418  | 0  |         else if (nc_datatype == NC_UINT)  | 
419  | 0  |             eDataType = GDT_UInt32;  | 
420  | 0  |         else if (nc_datatype == NC_INT64)  | 
421  | 0  |             eDataType = GDT_Int64;  | 
422  | 0  |         else if (nc_datatype == NC_UINT64)  | 
423  | 0  |             eDataType = GDT_UInt64;  | 
424  | 0  |         else  | 
425  | 0  |         { | 
426  | 0  |             if (nBand == 1)  | 
427  | 0  |                 CPLError(CE_Warning, CPLE_AppDefined,  | 
428  | 0  |                          "Unsupported netCDF datatype (%d), treat as Float32.",  | 
429  | 0  |                          static_cast<int>(nc_datatype));  | 
430  | 0  |             eDataType = GDT_Float32;  | 
431  | 0  |             nc_datatype = NC_FLOAT;  | 
432  | 0  |         }  | 
433  | 0  |     }  | 
434  |  |  | 
435  |  |     // Find and set No Data for this variable.  | 
436  | 0  |     nc_type atttype = NC_NAT;  | 
437  | 0  |     size_t attlen = 0;  | 
438  | 0  |     const char *pszNoValueName = nullptr;  | 
439  |  |  | 
440  |  |     // Find attribute name, either _FillValue or missing_value.  | 
441  | 0  |     int status = nc_inq_att(cdfid, nZId, NCDF_FillValue, &atttype, &attlen);  | 
442  | 0  |     if (status == NC_NOERR)  | 
443  | 0  |     { | 
444  | 0  |         pszNoValueName = NCDF_FillValue;  | 
445  | 0  |     }  | 
446  | 0  |     else  | 
447  | 0  |     { | 
448  | 0  |         status = nc_inq_att(cdfid, nZId, "missing_value", &atttype, &attlen);  | 
449  | 0  |         if (status == NC_NOERR)  | 
450  | 0  |         { | 
451  | 0  |             pszNoValueName = "missing_value";  | 
452  | 0  |         }  | 
453  | 0  |     }  | 
454  |  |  | 
455  |  |     // Fetch missing value.  | 
456  | 0  |     double dfNoData = 0.0;  | 
457  | 0  |     bool bGotNoData = false;  | 
458  | 0  |     int64_t nNoDataAsInt64 = 0;  | 
459  | 0  |     bool bGotNoDataAsInt64 = false;  | 
460  | 0  |     uint64_t nNoDataAsUInt64 = 0;  | 
461  | 0  |     bool bGotNoDataAsUInt64 = false;  | 
462  | 0  |     if (status == NC_NOERR)  | 
463  | 0  |     { | 
464  | 0  |         nc_type nAttrType = NC_NAT;  | 
465  | 0  |         size_t nAttrLen = 0;  | 
466  | 0  |         status = nc_inq_att(cdfid, nZId, pszNoValueName, &nAttrType, &nAttrLen);  | 
467  | 0  |         if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_INT64)  | 
468  | 0  |         { | 
469  | 0  |             long long v;  | 
470  | 0  |             nc_get_att_longlong(cdfid, nZId, pszNoValueName, &v);  | 
471  | 0  |             bGotNoData = true;  | 
472  | 0  |             bGotNoDataAsInt64 = true;  | 
473  | 0  |             nNoDataAsInt64 = static_cast<int64_t>(v);  | 
474  | 0  |         }  | 
475  | 0  |         else if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_UINT64)  | 
476  | 0  |         { | 
477  | 0  |             unsigned long long v;  | 
478  | 0  |             nc_get_att_ulonglong(cdfid, nZId, pszNoValueName, &v);  | 
479  | 0  |             bGotNoData = true;  | 
480  | 0  |             bGotNoDataAsUInt64 = true;  | 
481  | 0  |             nNoDataAsUInt64 = static_cast<uint64_t>(v);  | 
482  | 0  |         }  | 
483  | 0  |         else if (NCDFGetAttr(cdfid, nZId, pszNoValueName, &dfNoData) == CE_None)  | 
484  | 0  |         { | 
485  | 0  |             bGotNoData = true;  | 
486  | 0  |         }  | 
487  | 0  |     }  | 
488  |  |  | 
489  |  |     // If NoData was not found, use the default value, but for non-Byte types  | 
490  |  |     // as it is not recommended:  | 
491  |  |     // https://www.unidata.ucar.edu/software/netcdf/docs/attribute_conventions.html  | 
492  | 0  |     nc_type vartype = NC_NAT;  | 
493  | 0  |     if (!bGotNoData)  | 
494  | 0  |     { | 
495  | 0  |         nc_inq_vartype(cdfid, nZId, &vartype);  | 
496  | 0  |         if (vartype == NC_INT64)  | 
497  | 0  |         { | 
498  | 0  |             nNoDataAsInt64 =  | 
499  | 0  |                 NCDFGetDefaultNoDataValueAsInt64(cdfid, nZId, bGotNoData);  | 
500  | 0  |             bGotNoDataAsInt64 = bGotNoData;  | 
501  | 0  |         }  | 
502  | 0  |         else if (vartype == NC_UINT64)  | 
503  | 0  |         { | 
504  | 0  |             nNoDataAsUInt64 =  | 
505  | 0  |                 NCDFGetDefaultNoDataValueAsUInt64(cdfid, nZId, bGotNoData);  | 
506  | 0  |             bGotNoDataAsUInt64 = bGotNoData;  | 
507  | 0  |         }  | 
508  | 0  |         else if (vartype != NC_CHAR && vartype != NC_BYTE &&  | 
509  | 0  |                  vartype != NC_UBYTE)  | 
510  | 0  |         { | 
511  | 0  |             dfNoData =  | 
512  | 0  |                 NCDFGetDefaultNoDataValue(cdfid, nZId, vartype, bGotNoData);  | 
513  | 0  |             if (bGotNoData)  | 
514  | 0  |             { | 
515  | 0  |                 CPLDebug("GDAL_netCDF", | 
516  | 0  |                          "did not get nodata value for variable #%d, using "  | 
517  | 0  |                          "default %f",  | 
518  | 0  |                          nZId, dfNoData);  | 
519  | 0  |             }  | 
520  | 0  |         }  | 
521  | 0  |     }  | 
522  |  | 
  | 
523  | 0  |     bool bHasUnderscoreUnsignedAttr = false;  | 
524  | 0  |     bool bUnderscoreUnsignedAttrVal = false;  | 
525  | 0  |     { | 
526  | 0  |         char *pszTemp = nullptr;  | 
527  | 0  |         if (NCDFGetAttr(cdfid, nZId, "_Unsigned", &pszTemp) == CE_None)  | 
528  | 0  |         { | 
529  | 0  |             if (EQUAL(pszTemp, "true"))  | 
530  | 0  |             { | 
531  | 0  |                 bHasUnderscoreUnsignedAttr = true;  | 
532  | 0  |                 bUnderscoreUnsignedAttrVal = true;  | 
533  | 0  |             }  | 
534  | 0  |             else if (EQUAL(pszTemp, "false"))  | 
535  | 0  |             { | 
536  | 0  |                 bHasUnderscoreUnsignedAttr = true;  | 
537  | 0  |                 bUnderscoreUnsignedAttrVal = false;  | 
538  | 0  |             }  | 
539  | 0  |             CPLFree(pszTemp);  | 
540  | 0  |         }  | 
541  | 0  |     }  | 
542  |  |  | 
543  |  |     // Look for valid_range or valid_min/valid_max.  | 
544  |  |  | 
545  |  |     // First look for valid_range.  | 
546  | 0  |     if (CPLFetchBool(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE", true))  | 
547  | 0  |     { | 
548  | 0  |         char *pszValidRange = nullptr;  | 
549  | 0  |         if (NCDFGetAttr(cdfid, nZId, "valid_range", &pszValidRange) ==  | 
550  | 0  |                 CE_None &&  | 
551  | 0  |             pszValidRange[0] == '{' && | 
552  | 0  |             pszValidRange[strlen(pszValidRange) - 1] == '}')  | 
553  | 0  |         { | 
554  | 0  |             const std::string osValidRange =  | 
555  | 0  |                 std::string(pszValidRange).substr(1, strlen(pszValidRange) - 2);  | 
556  | 0  |             const CPLStringList aosValidRange(  | 
557  | 0  |                 CSLTokenizeString2(osValidRange.c_str(), ",", 0));  | 
558  | 0  |             if (aosValidRange.size() == 2 &&  | 
559  | 0  |                 CPLGetValueType(aosValidRange[0]) != CPL_VALUE_STRING &&  | 
560  | 0  |                 CPLGetValueType(aosValidRange[1]) != CPL_VALUE_STRING)  | 
561  | 0  |             { | 
562  | 0  |                 bValidRangeValid = true;  | 
563  | 0  |                 adfValidRange[0] = CPLAtof(aosValidRange[0]);  | 
564  | 0  |                 adfValidRange[1] = CPLAtof(aosValidRange[1]);  | 
565  | 0  |             }  | 
566  | 0  |         }  | 
567  | 0  |         CPLFree(pszValidRange);  | 
568  |  |  | 
569  |  |         // If not found look for valid_min and valid_max.  | 
570  | 0  |         if (!bValidRangeValid)  | 
571  | 0  |         { | 
572  | 0  |             double dfMin = 0;  | 
573  | 0  |             double dfMax = 0;  | 
574  | 0  |             if (NCDFGetAttr(cdfid, nZId, "valid_min", &dfMin) == CE_None &&  | 
575  | 0  |                 NCDFGetAttr(cdfid, nZId, "valid_max", &dfMax) == CE_None)  | 
576  | 0  |             { | 
577  | 0  |                 adfValidRange[0] = dfMin;  | 
578  | 0  |                 adfValidRange[1] = dfMax;  | 
579  | 0  |                 bValidRangeValid = true;  | 
580  | 0  |             }  | 
581  | 0  |         }  | 
582  |  | 
  | 
583  | 0  |         if (bValidRangeValid &&  | 
584  | 0  |             (adfValidRange[0] < 0 || adfValidRange[1] < 0) &&  | 
585  | 0  |             nc_datatype == NC_SHORT && bHasUnderscoreUnsignedAttr &&  | 
586  | 0  |             bUnderscoreUnsignedAttrVal)  | 
587  | 0  |         { | 
588  | 0  |             if (adfValidRange[0] < 0)  | 
589  | 0  |                 adfValidRange[0] += 65536;  | 
590  | 0  |             if (adfValidRange[1] < 0)  | 
591  | 0  |                 adfValidRange[1] += 65536;  | 
592  | 0  |             if (adfValidRange[0] <= adfValidRange[1])  | 
593  | 0  |             { | 
594  |  |                 // Updating metadata item  | 
595  | 0  |                 GDALPamRasterBand::SetMetadataItem(  | 
596  | 0  |                     "valid_range",  | 
597  | 0  |                     CPLSPrintf("{%d,%d}", static_cast<int>(adfValidRange[0]), | 
598  | 0  |                                static_cast<int>(adfValidRange[1])));  | 
599  | 0  |             }  | 
600  | 0  |         }  | 
601  |  | 
  | 
602  | 0  |         if (bValidRangeValid && adfValidRange[0] > adfValidRange[1])  | 
603  | 0  |         { | 
604  | 0  |             CPLError(CE_Warning, CPLE_AppDefined,  | 
605  | 0  |                      "netCDFDataset::valid_range: min > max:\n"  | 
606  | 0  |                      "  min: %lf\n  max: %lf\n",  | 
607  | 0  |                      adfValidRange[0], adfValidRange[1]);  | 
608  | 0  |             bValidRangeValid = false;  | 
609  | 0  |             adfValidRange[0] = 0.0;  | 
610  | 0  |             adfValidRange[1] = 0.0;  | 
611  | 0  |         }  | 
612  | 0  |     }  | 
613  |  |  | 
614  |  |     // Special For Byte Bands: check for signed/unsigned byte.  | 
615  | 0  |     if (nc_datatype == NC_BYTE)  | 
616  | 0  |     { | 
617  |  |         // netcdf uses signed byte by default, but GDAL uses unsigned by default  | 
618  |  |         // This may cause unexpected results, but is needed for back-compat.  | 
619  | 0  |         if (poNCDFDS->bIsGdalFile)  | 
620  | 0  |             bSignedData = false;  | 
621  | 0  |         else  | 
622  | 0  |             bSignedData = true;  | 
623  |  |  | 
624  |  |         // For NC4 format NC_BYTE is (normally) signed, NC_UBYTE is unsigned.  | 
625  |  |         // But in case a NC3 file was converted automatically and has hints  | 
626  |  |         // that it is unsigned, take them into account  | 
627  | 0  |         if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)  | 
628  | 0  |         { | 
629  | 0  |             bSignedData = true;  | 
630  | 0  |         }  | 
631  |  |  | 
632  |  |         // If we got valid_range, test for signed/unsigned range.  | 
633  |  |         // https://docs.unidata.ucar.edu/netcdf-c/current/attribute_conventions.html  | 
634  | 0  |         if (bValidRangeValid)  | 
635  | 0  |         { | 
636  |  |             // If we got valid_range={0,255}, treat as unsigned. | 
637  | 0  |             if (adfValidRange[0] == 0 && adfValidRange[1] == 255)  | 
638  | 0  |             { | 
639  | 0  |                 bSignedData = false;  | 
640  |  |                 // Reset valid_range.  | 
641  | 0  |                 bValidRangeValid = false;  | 
642  | 0  |             }  | 
643  |  |             // If we got valid_range={-128,127}, treat as signed. | 
644  | 0  |             else if (adfValidRange[0] == -128 && adfValidRange[1] == 127)  | 
645  | 0  |             { | 
646  | 0  |                 bSignedData = true;  | 
647  |  |                 // Reset valid_range.  | 
648  | 0  |                 bValidRangeValid = false;  | 
649  | 0  |             }  | 
650  | 0  |         }  | 
651  |  |         // Else test for _Unsigned.  | 
652  |  |         // https://docs.unidata.ucar.edu/nug/current/best_practices.html  | 
653  | 0  |         else  | 
654  | 0  |         { | 
655  | 0  |             if (bHasUnderscoreUnsignedAttr)  | 
656  | 0  |                 bSignedData = !bUnderscoreUnsignedAttrVal;  | 
657  | 0  |         }  | 
658  |  | 
  | 
659  | 0  |         if (bSignedData)  | 
660  | 0  |         { | 
661  | 0  |             eDataType = GDT_Int8;  | 
662  | 0  |         }  | 
663  | 0  |         else if (dfNoData < 0)  | 
664  | 0  |         { | 
665  |  |             // Fix nodata value as it was stored signed.  | 
666  | 0  |             dfNoData += 256;  | 
667  | 0  |             if (pszNoValueName)  | 
668  | 0  |             { | 
669  |  |                 // Updating metadata item  | 
670  | 0  |                 GDALPamRasterBand::SetMetadataItem(  | 
671  | 0  |                     pszNoValueName,  | 
672  | 0  |                     CPLSPrintf("%d", static_cast<int>(dfNoData))); | 
673  | 0  |             }  | 
674  | 0  |         }  | 
675  | 0  |     }  | 
676  | 0  |     else if (nc_datatype == NC_SHORT)  | 
677  | 0  |     { | 
678  | 0  |         if (bHasUnderscoreUnsignedAttr)  | 
679  | 0  |         { | 
680  | 0  |             bSignedData = !bUnderscoreUnsignedAttrVal;  | 
681  | 0  |             if (!bSignedData)  | 
682  | 0  |                 eDataType = GDT_UInt16;  | 
683  | 0  |         }  | 
684  |  |  | 
685  |  |         // Fix nodata value as it was stored signed.  | 
686  | 0  |         if (!bSignedData && dfNoData < 0)  | 
687  | 0  |         { | 
688  | 0  |             dfNoData += 65536;  | 
689  | 0  |             if (pszNoValueName)  | 
690  | 0  |             { | 
691  |  |                 // Updating metadata item  | 
692  | 0  |                 GDALPamRasterBand::SetMetadataItem(  | 
693  | 0  |                     pszNoValueName,  | 
694  | 0  |                     CPLSPrintf("%d", static_cast<int>(dfNoData))); | 
695  | 0  |             }  | 
696  | 0  |         }  | 
697  | 0  |     }  | 
698  |  |  | 
699  | 0  |     else if (nc_datatype == NC_UBYTE || nc_datatype == NC_USHORT ||  | 
700  | 0  |              nc_datatype == NC_UINT || nc_datatype == NC_UINT64)  | 
701  | 0  |     { | 
702  | 0  |         bSignedData = false;  | 
703  | 0  |     }  | 
704  |  | 
  | 
705  | 0  |     CPLDebug("GDAL_netCDF", "netcdf type=%d gdal type=%d signedByte=%d", | 
706  | 0  |              nc_datatype, eDataType, static_cast<int>(bSignedData));  | 
707  |  | 
  | 
708  | 0  |     if (bGotNoData)  | 
709  | 0  |     { | 
710  |  |         // Set nodata value.  | 
711  | 0  |         if (bGotNoDataAsInt64)  | 
712  | 0  |         { | 
713  | 0  |             if (eDataType == GDT_Int64)  | 
714  | 0  |             { | 
715  | 0  |                 SetNoDataValueNoUpdate(nNoDataAsInt64);  | 
716  | 0  |             }  | 
717  | 0  |             else if (eDataType == GDT_UInt64 && nNoDataAsInt64 >= 0)  | 
718  | 0  |             { | 
719  | 0  |                 SetNoDataValueNoUpdate(static_cast<uint64_t>(nNoDataAsInt64));  | 
720  | 0  |             }  | 
721  | 0  |             else  | 
722  | 0  |             { | 
723  | 0  |                 SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsInt64));  | 
724  | 0  |             }  | 
725  | 0  |         }  | 
726  | 0  |         else if (bGotNoDataAsUInt64)  | 
727  | 0  |         { | 
728  | 0  |             if (eDataType == GDT_UInt64)  | 
729  | 0  |             { | 
730  | 0  |                 SetNoDataValueNoUpdate(nNoDataAsUInt64);  | 
731  | 0  |             }  | 
732  | 0  |             else if (eDataType == GDT_Int64 &&  | 
733  | 0  |                      nNoDataAsUInt64 <=  | 
734  | 0  |                          static_cast<uint64_t>(  | 
735  | 0  |                              std::numeric_limits<int64_t>::max()))  | 
736  | 0  |             { | 
737  | 0  |                 SetNoDataValueNoUpdate(static_cast<int64_t>(nNoDataAsUInt64));  | 
738  | 0  |             }  | 
739  | 0  |             else  | 
740  | 0  |             { | 
741  | 0  |                 SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsUInt64));  | 
742  | 0  |             }  | 
743  | 0  |         }  | 
744  | 0  |         else  | 
745  | 0  |         { | 
746  |  | #ifdef NCDF_DEBUG  | 
747  |  |             CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) read", dfNoData); | 
748  |  | #endif  | 
749  | 0  |             if (eDataType == GDT_Int64 && GDALIsValueExactAs<int64_t>(dfNoData))  | 
750  | 0  |             { | 
751  | 0  |                 SetNoDataValueNoUpdate(static_cast<int64_t>(dfNoData));  | 
752  | 0  |             }  | 
753  | 0  |             else if (eDataType == GDT_UInt64 &&  | 
754  | 0  |                      GDALIsValueExactAs<uint64_t>(dfNoData))  | 
755  | 0  |             { | 
756  | 0  |                 SetNoDataValueNoUpdate(static_cast<uint64_t>(dfNoData));  | 
757  | 0  |             }  | 
758  | 0  |             else  | 
759  | 0  |             { | 
760  | 0  |                 SetNoDataValueNoUpdate(dfNoData);  | 
761  | 0  |             }  | 
762  | 0  |         }  | 
763  | 0  |     }  | 
764  |  | 
  | 
765  | 0  |     CreateMetadataFromAttributes();  | 
766  |  |  | 
767  |  |     // Attempt to fetch the scale_factor and add_offset attributes for the  | 
768  |  |     // variable and set them.  If these values are not available, set  | 
769  |  |     // offset to 0 and scale to 1.  | 
770  | 0  |     if (nc_inq_attid(cdfid, nZId, CF_ADD_OFFSET, nullptr) == NC_NOERR)  | 
771  | 0  |     { | 
772  | 0  |         double dfOffset = 0;  | 
773  | 0  |         status = nc_get_att_double(cdfid, nZId, CF_ADD_OFFSET, &dfOffset);  | 
774  | 0  |         CPLDebug("GDAL_netCDF", "got add_offset=%.16g, status=%d", dfOffset, | 
775  | 0  |                  status);  | 
776  | 0  |         SetOffsetNoUpdate(dfOffset);  | 
777  | 0  |     }  | 
778  |  | 
  | 
779  | 0  |     bool bHasScale = false;  | 
780  | 0  |     if (nc_inq_attid(cdfid, nZId, CF_SCALE_FACTOR, nullptr) == NC_NOERR)  | 
781  | 0  |     { | 
782  | 0  |         bHasScale = true;  | 
783  | 0  |         double dfScale = 1;  | 
784  | 0  |         status = nc_get_att_double(cdfid, nZId, CF_SCALE_FACTOR, &dfScale);  | 
785  | 0  |         CPLDebug("GDAL_netCDF", "got scale_factor=%.16g, status=%d", dfScale, | 
786  | 0  |                  status);  | 
787  | 0  |         SetScaleNoUpdate(dfScale);  | 
788  | 0  |     }  | 
789  |  | 
  | 
790  | 0  |     if (bValidRangeValid && GDALDataTypeIsInteger(eDataType) &&  | 
791  | 0  |         eDataType != GDT_Int64 && eDataType != GDT_UInt64 &&  | 
792  | 0  |         (std::fabs(std::round(adfValidRange[0]) - adfValidRange[0]) > 1e-5 ||  | 
793  | 0  |          std::fabs(std::round(adfValidRange[1]) - adfValidRange[1]) > 1e-5) &&  | 
794  | 0  |         CSLFetchNameValue(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE") ==  | 
795  | 0  |             nullptr)  | 
796  | 0  |     { | 
797  | 0  |         CPLError(CE_Warning, CPLE_AppDefined,  | 
798  | 0  |                  "validity range = %f, %f contains floating-point values, "  | 
799  | 0  |                  "whereas data type is integer. valid_range is thus likely "  | 
800  | 0  |                  "wrong%s. Ignoring it.",  | 
801  | 0  |                  adfValidRange[0], adfValidRange[1],  | 
802  | 0  |                  bHasScale ? " (likely scaled using scale_factor/add_factor "  | 
803  | 0  |                              "whereas it should be using the packed data type)"  | 
804  | 0  |                            : "");  | 
805  | 0  |         bValidRangeValid = false;  | 
806  | 0  |         adfValidRange[0] = 0.0;  | 
807  | 0  |         adfValidRange[1] = 0.0;  | 
808  | 0  |     }  | 
809  |  |  | 
810  |  |     // Should we check for longitude values > 360?  | 
811  | 0  |     bCheckLongitude =  | 
812  | 0  |         CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")) && | 
813  | 0  |         NCDFIsVarLongitude(cdfid, nZId, nullptr);  | 
814  |  |  | 
815  |  |     // Attempt to fetch the units attribute for the variable and set it.  | 
816  | 0  |     SetUnitTypeNoUpdate(netCDFRasterBand::GetMetadataItem(CF_UNITS));  | 
817  |  | 
  | 
818  | 0  |     SetBlockSize();  | 
819  | 0  | }  | 
820  |  |  | 
821  |  | void netCDFRasterBand::SetBlockSize()  | 
822  | 0  | { | 
823  |  |     // Check for variable chunking (netcdf-4 only).  | 
824  |  |     // GDAL block size should be set to hdf5 chunk size.  | 
825  | 0  |     int nTmpFormat = 0;  | 
826  | 0  |     int status = nc_inq_format(cdfid, &nTmpFormat);  | 
827  | 0  |     NetCDFFormatEnum eTmpFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);  | 
828  | 0  |     if ((status == NC_NOERR) &&  | 
829  | 0  |         (eTmpFormat == NCDF_FORMAT_NC4 || eTmpFormat == NCDF_FORMAT_NC4C))  | 
830  | 0  |     { | 
831  | 0  |         size_t chunksize[MAX_NC_DIMS] = {}; | 
832  |  |         // Check for chunksize and set it as the blocksize (optimizes read).  | 
833  | 0  |         status = nc_inq_var_chunking(cdfid, nZId, &nTmpFormat, chunksize);  | 
834  | 0  |         if ((status == NC_NOERR) && (nTmpFormat == NC_CHUNKED))  | 
835  | 0  |         { | 
836  | 0  |             nBlockXSize = (int)chunksize[nZDim - 1];  | 
837  | 0  |             if (nZDim >= 2)  | 
838  | 0  |                 nBlockYSize = (int)chunksize[nZDim - 2];  | 
839  | 0  |             else  | 
840  | 0  |                 nBlockYSize = 1;  | 
841  | 0  |         }  | 
842  | 0  |     }  | 
843  |  |  | 
844  |  |     // Deal with bottom-up datasets and nBlockYSize != 1.  | 
845  | 0  |     auto poGDS = static_cast<netCDFDataset *>(poDS);  | 
846  | 0  |     if (poGDS->bBottomUp && nBlockYSize != 1 && poGDS->poChunkCache == nullptr)  | 
847  | 0  |     { | 
848  | 0  |         if (poGDS->eAccess == GA_ReadOnly)  | 
849  | 0  |         { | 
850  |  |             // Try to cache 1 or 2 'rows' of netCDF chunks along the whole  | 
851  |  |             // width of the raster  | 
852  | 0  |             size_t nChunks =  | 
853  | 0  |                 static_cast<size_t>(DIV_ROUND_UP(nRasterXSize, nBlockXSize));  | 
854  | 0  |             if ((nRasterYSize % nBlockYSize) != 0)  | 
855  | 0  |                 nChunks *= 2;  | 
856  | 0  |             const size_t nChunkSize =  | 
857  | 0  |                 static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *  | 
858  | 0  |                 nBlockXSize * nBlockYSize;  | 
859  | 0  |             constexpr size_t MAX_CACHE_SIZE = 100 * 1024 * 1024;  | 
860  | 0  |             nChunks = std::min(nChunks, MAX_CACHE_SIZE / nChunkSize);  | 
861  | 0  |             if (nChunks)  | 
862  | 0  |             { | 
863  | 0  |                 poGDS->poChunkCache.reset(  | 
864  | 0  |                     new netCDFDataset::ChunkCacheType(nChunks));  | 
865  | 0  |             }  | 
866  | 0  |         }  | 
867  | 0  |         else  | 
868  | 0  |         { | 
869  | 0  |             nBlockYSize = 1;  | 
870  | 0  |         }  | 
871  | 0  |     }  | 
872  | 0  | }  | 
873  |  |  | 
874  |  | // Constructor in create mode.  | 
875  |  | // If nZId and following variables are not passed, the band will have 2  | 
876  |  | // dimensions.  | 
877  |  | // TODO: Get metadata, missing val from band #1 if nZDim > 2.  | 
878  |  | netCDFRasterBand::netCDFRasterBand(  | 
879  |  |     const netCDFRasterBand::CONSTRUCTOR_CREATE &, netCDFDataset *poNCDFDS,  | 
880  |  |     const GDALDataType eTypeIn, int nBandIn, bool bSigned,  | 
881  |  |     const char *pszBandName, const char *pszLongName, int nZIdIn, int nZDimIn,  | 
882  |  |     int nLevelIn, const int *panBandZLevIn, const int *panBandZPosIn,  | 
883  |  |     const int *paDimIds)  | 
884  | 0  |     : nc_datatype(NC_NAT), cdfid(poNCDFDS->GetCDFID()), nZId(nZIdIn),  | 
885  | 0  |       nZDim(nZDimIn), nLevel(nLevelIn), nBandXPos(1), nBandYPos(0),  | 
886  | 0  |       panBandZPos(nullptr), panBandZLev(nullptr), bSignedData(bSigned),  | 
887  | 0  |       bCheckLongitude(false), m_bCreateMetadataFromOtherVarsDone(true)  | 
888  | 0  | { | 
889  | 0  |     poDS = poNCDFDS;  | 
890  | 0  |     nBand = nBandIn;  | 
891  |  | 
  | 
892  | 0  |     nRasterXSize = poDS->GetRasterXSize();  | 
893  | 0  |     nRasterYSize = poDS->GetRasterYSize();  | 
894  | 0  |     nBlockXSize = poDS->GetRasterXSize();  | 
895  | 0  |     nBlockYSize = 1;  | 
896  |  | 
  | 
897  | 0  |     if (poDS->GetAccess() != GA_Update)  | 
898  | 0  |     { | 
899  | 0  |         CPLError(CE_Failure, CPLE_NotSupported,  | 
900  | 0  |                  "Dataset is not in update mode, "  | 
901  | 0  |                  "wrong netCDFRasterBand constructor");  | 
902  | 0  |         return;  | 
903  | 0  |     }  | 
904  |  |  | 
905  |  |     // Take care of all other dimensions.  | 
906  | 0  |     if (nZDim > 2 && paDimIds != nullptr)  | 
907  | 0  |     { | 
908  | 0  |         nBandXPos = panBandZPosIn[0];  | 
909  | 0  |         nBandYPos = panBandZPosIn[1];  | 
910  |  | 
  | 
911  | 0  |         panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));  | 
912  | 0  |         panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));  | 
913  |  | 
  | 
914  | 0  |         for (int i = 0; i < nZDim - 2; i++)  | 
915  | 0  |         { | 
916  | 0  |             panBandZPos[i] = panBandZPosIn[i + 2];  | 
917  | 0  |             panBandZLev[i] = panBandZLevIn[i];  | 
918  | 0  |         }  | 
919  | 0  |     }  | 
920  |  |  | 
921  |  |     // Get the type of the "z" variable, our target raster array.  | 
922  | 0  |     eDataType = eTypeIn;  | 
923  |  | 
  | 
924  | 0  |     switch (eDataType)  | 
925  | 0  |     { | 
926  | 0  |         case GDT_Byte:  | 
927  | 0  |             nc_datatype = NC_BYTE;  | 
928  |  |             // NC_UBYTE (unsigned byte) is only available for NC4.  | 
929  | 0  |             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)  | 
930  | 0  |                 nc_datatype = NC_UBYTE;  | 
931  | 0  |             break;  | 
932  | 0  |         case GDT_Int8:  | 
933  | 0  |             nc_datatype = NC_BYTE;  | 
934  | 0  |             break;  | 
935  | 0  |         case GDT_Int16:  | 
936  | 0  |             nc_datatype = NC_SHORT;  | 
937  | 0  |             break;  | 
938  | 0  |         case GDT_Int32:  | 
939  | 0  |             nc_datatype = NC_INT;  | 
940  | 0  |             break;  | 
941  | 0  |         case GDT_Float32:  | 
942  | 0  |             nc_datatype = NC_FLOAT;  | 
943  | 0  |             break;  | 
944  | 0  |         case GDT_Float64:  | 
945  | 0  |             nc_datatype = NC_DOUBLE;  | 
946  | 0  |             break;  | 
947  | 0  |         case GDT_Int64:  | 
948  | 0  |             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)  | 
949  | 0  |             { | 
950  | 0  |                 nc_datatype = NC_INT64;  | 
951  | 0  |             }  | 
952  | 0  |             else  | 
953  | 0  |             { | 
954  | 0  |                 if (nBand == 1)  | 
955  | 0  |                     CPLError(  | 
956  | 0  |                         CE_Warning, CPLE_AppDefined,  | 
957  | 0  |                         "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",  | 
958  | 0  |                         "Int64");  | 
959  | 0  |                 nc_datatype = NC_DOUBLE;  | 
960  | 0  |                 eDataType = GDT_Float64;  | 
961  | 0  |             }  | 
962  | 0  |             break;  | 
963  | 0  |         case GDT_UInt64:  | 
964  | 0  |             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)  | 
965  | 0  |             { | 
966  | 0  |                 nc_datatype = NC_UINT64;  | 
967  | 0  |             }  | 
968  | 0  |             else  | 
969  | 0  |             { | 
970  | 0  |                 if (nBand == 1)  | 
971  | 0  |                     CPLError(  | 
972  | 0  |                         CE_Warning, CPLE_AppDefined,  | 
973  | 0  |                         "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",  | 
974  | 0  |                         "UInt64");  | 
975  | 0  |                 nc_datatype = NC_DOUBLE;  | 
976  | 0  |                 eDataType = GDT_Float64;  | 
977  | 0  |             }  | 
978  | 0  |             break;  | 
979  | 0  |         case GDT_UInt16:  | 
980  | 0  |             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)  | 
981  | 0  |             { | 
982  | 0  |                 nc_datatype = NC_USHORT;  | 
983  | 0  |                 break;  | 
984  | 0  |             }  | 
985  | 0  |             [[fallthrough]];  | 
986  | 0  |         case GDT_UInt32:  | 
987  | 0  |             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)  | 
988  | 0  |             { | 
989  | 0  |                 nc_datatype = NC_UINT;  | 
990  | 0  |                 break;  | 
991  | 0  |             }  | 
992  | 0  |             [[fallthrough]];  | 
993  | 0  |         default:  | 
994  | 0  |             if (nBand == 1)  | 
995  | 0  |                 CPLError(CE_Warning, CPLE_AppDefined,  | 
996  | 0  |                          "Unsupported GDAL datatype (%d), treat as NC_FLOAT.",  | 
997  | 0  |                          static_cast<int>(eDataType));  | 
998  | 0  |             nc_datatype = NC_FLOAT;  | 
999  | 0  |             eDataType = GDT_Float32;  | 
1000  | 0  |             break;  | 
1001  | 0  |     }  | 
1002  |  |  | 
1003  |  |     // Define the variable if necessary (if nZId == -1).  | 
1004  | 0  |     bool bDefineVar = false;  | 
1005  |  | 
  | 
1006  | 0  |     if (nZId == -1)  | 
1007  | 0  |     { | 
1008  | 0  |         bDefineVar = true;  | 
1009  |  |  | 
1010  |  |         // Make sure we are in define mode.  | 
1011  | 0  |         static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);  | 
1012  |  | 
  | 
1013  | 0  |         char szTempPrivate[256 + 1];  | 
1014  | 0  |         const char *pszTemp = nullptr;  | 
1015  | 0  |         if (!pszBandName || EQUAL(pszBandName, ""))  | 
1016  | 0  |         { | 
1017  | 0  |             snprintf(szTempPrivate, sizeof(szTempPrivate), "Band%d", nBand);  | 
1018  | 0  |             pszTemp = szTempPrivate;  | 
1019  | 0  |         }  | 
1020  | 0  |         else  | 
1021  | 0  |         { | 
1022  | 0  |             pszTemp = pszBandName;  | 
1023  | 0  |         }  | 
1024  |  | 
  | 
1025  | 0  |         int status;  | 
1026  | 0  |         if (nZDim > 2 && paDimIds != nullptr)  | 
1027  | 0  |         { | 
1028  | 0  |             status =  | 
1029  | 0  |                 nc_def_var(cdfid, pszTemp, nc_datatype, nZDim, paDimIds, &nZId);  | 
1030  | 0  |         }  | 
1031  | 0  |         else  | 
1032  | 0  |         { | 
1033  | 0  |             int anBandDims[2] = {poNCDFDS->nYDimID, poNCDFDS->nXDimID}; | 
1034  | 0  |             status =  | 
1035  | 0  |                 nc_def_var(cdfid, pszTemp, nc_datatype, 2, anBandDims, &nZId);  | 
1036  | 0  |         }  | 
1037  | 0  |         NCDF_ERR(status);  | 
1038  | 0  |         CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d) id=%d", cdfid, pszTemp, | 
1039  | 0  |                  nc_datatype, nZId);  | 
1040  |  | 
  | 
1041  | 0  |         if (!pszLongName || EQUAL(pszLongName, ""))  | 
1042  | 0  |         { | 
1043  | 0  |             snprintf(szTempPrivate, sizeof(szTempPrivate),  | 
1044  | 0  |                      "GDAL Band Number %d", nBand);  | 
1045  | 0  |             pszTemp = szTempPrivate;  | 
1046  | 0  |         }  | 
1047  | 0  |         else  | 
1048  | 0  |         { | 
1049  | 0  |             pszTemp = pszLongName;  | 
1050  | 0  |         }  | 
1051  | 0  |         status =  | 
1052  | 0  |             nc_put_att_text(cdfid, nZId, CF_LNG_NAME, strlen(pszTemp), pszTemp);  | 
1053  | 0  |         NCDF_ERR(status);  | 
1054  |  | 
  | 
1055  | 0  |         poNCDFDS->DefVarDeflate(nZId, true);  | 
1056  | 0  |     }  | 
1057  |  |  | 
1058  |  |     // For Byte data add signed/unsigned info.  | 
1059  | 0  |     if (eDataType == GDT_Byte || eDataType == GDT_Int8)  | 
1060  | 0  |     { | 
1061  | 0  |         if (bDefineVar)  | 
1062  | 0  |         { | 
1063  |  |             // Only add attributes if creating variable.  | 
1064  |  |             // For unsigned NC_BYTE (except NC4 format),  | 
1065  |  |             // add valid_range and _Unsigned ( defined in CF-1 and NUG ).  | 
1066  | 0  |             if (nc_datatype == NC_BYTE && poNCDFDS->eFormat != NCDF_FORMAT_NC4)  | 
1067  | 0  |             { | 
1068  | 0  |                 CPLDebug("GDAL_netCDF", | 
1069  | 0  |                          "adding valid_range attributes for Byte Band");  | 
1070  | 0  |                 short l_adfValidRange[2] = {0, 0}; | 
1071  | 0  |                 int status;  | 
1072  | 0  |                 if (bSignedData || eDataType == GDT_Int8)  | 
1073  | 0  |                 { | 
1074  | 0  |                     l_adfValidRange[0] = -128;  | 
1075  | 0  |                     l_adfValidRange[1] = 127;  | 
1076  | 0  |                     status =  | 
1077  | 0  |                         nc_put_att_text(cdfid, nZId, "_Unsigned", 5, "false");  | 
1078  | 0  |                 }  | 
1079  | 0  |                 else  | 
1080  | 0  |                 { | 
1081  | 0  |                     l_adfValidRange[0] = 0;  | 
1082  | 0  |                     l_adfValidRange[1] = 255;  | 
1083  | 0  |                     status =  | 
1084  | 0  |                         nc_put_att_text(cdfid, nZId, "_Unsigned", 4, "true");  | 
1085  | 0  |                 }  | 
1086  | 0  |                 NCDF_ERR(status);  | 
1087  | 0  |                 status = nc_put_att_short(cdfid, nZId, "valid_range", NC_SHORT,  | 
1088  | 0  |                                           2, l_adfValidRange);  | 
1089  | 0  |                 NCDF_ERR(status);  | 
1090  | 0  |             }  | 
1091  | 0  |         }  | 
1092  | 0  |     }  | 
1093  |  | 
  | 
1094  | 0  |     if (nc_datatype != NC_BYTE && nc_datatype != NC_CHAR &&  | 
1095  | 0  |         nc_datatype != NC_UBYTE)  | 
1096  | 0  |     { | 
1097  |  |         // Set default nodata.  | 
1098  | 0  |         bool bIgnored = false;  | 
1099  | 0  |         double dfNoData =  | 
1100  | 0  |             NCDFGetDefaultNoDataValue(cdfid, nZId, nc_datatype, bIgnored);  | 
1101  |  | #ifdef NCDF_DEBUG  | 
1102  |  |         CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) default", dfNoData); | 
1103  |  | #endif  | 
1104  | 0  |         netCDFRasterBand::SetNoDataValue(dfNoData);  | 
1105  | 0  |     }  | 
1106  |  | 
  | 
1107  | 0  |     SetBlockSize();  | 
1108  | 0  | }  | 
1109  |  |  | 
1110  |  | /************************************************************************/  | 
1111  |  | /*                         ~netCDFRasterBand()                          */  | 
1112  |  | /************************************************************************/  | 
1113  |  |  | 
1114  |  | netCDFRasterBand::~netCDFRasterBand()  | 
1115  | 0  | { | 
1116  | 0  |     netCDFRasterBand::FlushCache(true);  | 
1117  | 0  |     CPLFree(panBandZPos);  | 
1118  | 0  |     CPLFree(panBandZLev);  | 
1119  | 0  | }  | 
1120  |  |  | 
1121  |  | /************************************************************************/  | 
1122  |  | /*                          GetMetadata()                               */  | 
1123  |  | /************************************************************************/  | 
1124  |  |  | 
1125  |  | char **netCDFRasterBand::GetMetadata(const char *pszDomain)  | 
1126  | 0  | { | 
1127  | 0  |     if (!m_bCreateMetadataFromOtherVarsDone)  | 
1128  | 0  |         CreateMetadataFromOtherVars();  | 
1129  | 0  |     return GDALPamRasterBand::GetMetadata(pszDomain);  | 
1130  | 0  | }  | 
1131  |  |  | 
1132  |  | /************************************************************************/  | 
1133  |  | /*                        GetMetadataItem()                             */  | 
1134  |  | /************************************************************************/  | 
1135  |  |  | 
1136  |  | const char *netCDFRasterBand::GetMetadataItem(const char *pszName,  | 
1137  |  |                                               const char *pszDomain)  | 
1138  | 0  | { | 
1139  | 0  |     if (!m_bCreateMetadataFromOtherVarsDone &&  | 
1140  | 0  |         STARTS_WITH(pszName, "NETCDF_DIM_") &&  | 
1141  | 0  |         (!pszDomain || pszDomain[0] == 0))  | 
1142  | 0  |         CreateMetadataFromOtherVars();  | 
1143  | 0  |     return GDALPamRasterBand::GetMetadataItem(pszName, pszDomain);  | 
1144  | 0  | }  | 
1145  |  |  | 
1146  |  | /************************************************************************/  | 
1147  |  | /*                        SetMetadataItem()                             */  | 
1148  |  | /************************************************************************/  | 
1149  |  |  | 
1150  |  | CPLErr netCDFRasterBand::SetMetadataItem(const char *pszName,  | 
1151  |  |                                          const char *pszValue,  | 
1152  |  |                                          const char *pszDomain)  | 
1153  | 0  | { | 
1154  | 0  |     if (GetAccess() == GA_Update &&  | 
1155  | 0  |         (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)  | 
1156  | 0  |     { | 
1157  |  |         // Same logic as in CopyMetadata()  | 
1158  |  | 
  | 
1159  | 0  |         const char *const papszIgnoreBand[] = { | 
1160  | 0  |             CF_ADD_OFFSET,  CF_SCALE_FACTOR, "valid_range", "_Unsigned",  | 
1161  | 0  |             NCDF_FillValue, "coordinates",   nullptr};  | 
1162  |  |         // Do not copy varname, stats, NETCDF_DIM_*, nodata  | 
1163  |  |         // and items in papszIgnoreBand.  | 
1164  | 0  |         if (STARTS_WITH(pszName, "NETCDF_VARNAME") ||  | 
1165  | 0  |             STARTS_WITH(pszName, "STATISTICS_") ||  | 
1166  | 0  |             STARTS_WITH(pszName, "NETCDF_DIM_") ||  | 
1167  | 0  |             STARTS_WITH(pszName, "missing_value") ||  | 
1168  | 0  |             STARTS_WITH(pszName, "_FillValue") ||  | 
1169  | 0  |             CSLFindString(papszIgnoreBand, pszName) != -1)  | 
1170  | 0  |         { | 
1171  |  |             // do nothing  | 
1172  | 0  |         }  | 
1173  | 0  |         else  | 
1174  | 0  |         { | 
1175  | 0  |             cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);  | 
1176  |  | 
  | 
1177  | 0  |             if (!NCDFPutAttr(cdfid, nZId, pszName, pszValue))  | 
1178  | 0  |                 return CE_Failure;  | 
1179  | 0  |         }  | 
1180  | 0  |     }  | 
1181  |  |  | 
1182  | 0  |     return GDALPamRasterBand::SetMetadataItem(pszName, pszValue, pszDomain);  | 
1183  | 0  | }  | 
1184  |  |  | 
1185  |  | /************************************************************************/  | 
1186  |  | /*                          SetMetadata()                               */  | 
1187  |  | /************************************************************************/  | 
1188  |  |  | 
1189  |  | CPLErr netCDFRasterBand::SetMetadata(char **papszMD, const char *pszDomain)  | 
1190  | 0  | { | 
1191  | 0  |     if (GetAccess() == GA_Update &&  | 
1192  | 0  |         (pszDomain == nullptr || pszDomain[0] == '\0'))  | 
1193  | 0  |     { | 
1194  |  |         // We don't handle metadata item removal for now  | 
1195  | 0  |         for (const char *const *papszIter = papszMD; papszIter && *papszIter;  | 
1196  | 0  |              ++papszIter)  | 
1197  | 0  |         { | 
1198  | 0  |             char *pszName = nullptr;  | 
1199  | 0  |             const char *pszValue = CPLParseNameValue(*papszIter, &pszName);  | 
1200  | 0  |             if (pszName && pszValue)  | 
1201  | 0  |                 SetMetadataItem(pszName, pszValue);  | 
1202  | 0  |             CPLFree(pszName);  | 
1203  | 0  |         }  | 
1204  | 0  |     }  | 
1205  | 0  |     return GDALPamRasterBand::SetMetadata(papszMD, pszDomain);  | 
1206  | 0  | }  | 
1207  |  |  | 
1208  |  | /************************************************************************/  | 
1209  |  | /*                             GetOffset()                              */  | 
1210  |  | /************************************************************************/  | 
1211  |  | double netCDFRasterBand::GetOffset(int *pbSuccess)  | 
1212  | 0  | { | 
1213  | 0  |     if (pbSuccess != nullptr)  | 
1214  | 0  |         *pbSuccess = static_cast<int>(m_bHaveOffset);  | 
1215  |  | 
  | 
1216  | 0  |     return m_dfOffset;  | 
1217  | 0  | }  | 
1218  |  |  | 
1219  |  | /************************************************************************/  | 
1220  |  | /*                             SetOffset()                              */  | 
1221  |  | /************************************************************************/  | 
1222  |  | CPLErr netCDFRasterBand::SetOffset(double dfNewOffset)  | 
1223  | 0  | { | 
1224  | 0  |     CPLMutexHolderD(&hNCMutex);  | 
1225  |  |  | 
1226  |  |     // Write value if in update mode.  | 
1227  | 0  |     if (poDS->GetAccess() == GA_Update)  | 
1228  | 0  |     { | 
1229  |  |         // Make sure we are in define mode.  | 
1230  | 0  |         static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);  | 
1231  |  | 
  | 
1232  | 0  |         const int status = nc_put_att_double(cdfid, nZId, CF_ADD_OFFSET,  | 
1233  | 0  |                                              NC_DOUBLE, 1, &dfNewOffset);  | 
1234  |  | 
  | 
1235  | 0  |         NCDF_ERR(status);  | 
1236  | 0  |         if (status == NC_NOERR)  | 
1237  | 0  |         { | 
1238  | 0  |             SetOffsetNoUpdate(dfNewOffset);  | 
1239  | 0  |             return CE_None;  | 
1240  | 0  |         }  | 
1241  |  |  | 
1242  | 0  |         return CE_Failure;  | 
1243  | 0  |     }  | 
1244  |  |  | 
1245  | 0  |     SetOffsetNoUpdate(dfNewOffset);  | 
1246  | 0  |     return CE_None;  | 
1247  | 0  | }  | 
1248  |  |  | 
1249  |  | /************************************************************************/  | 
1250  |  | /*                         SetOffsetNoUpdate()                          */  | 
1251  |  | /************************************************************************/  | 
1252  |  | void netCDFRasterBand::SetOffsetNoUpdate(double dfVal)  | 
1253  | 0  | { | 
1254  | 0  |     m_dfOffset = dfVal;  | 
1255  | 0  |     m_bHaveOffset = true;  | 
1256  | 0  | }  | 
1257  |  |  | 
1258  |  | /************************************************************************/  | 
1259  |  | /*                              GetScale()                              */  | 
1260  |  | /************************************************************************/  | 
1261  |  | double netCDFRasterBand::GetScale(int *pbSuccess)  | 
1262  | 0  | { | 
1263  | 0  |     if (pbSuccess != nullptr)  | 
1264  | 0  |         *pbSuccess = static_cast<int>(m_bHaveScale);  | 
1265  |  | 
  | 
1266  | 0  |     return m_dfScale;  | 
1267  | 0  | }  | 
1268  |  |  | 
1269  |  | /************************************************************************/  | 
1270  |  | /*                              SetScale()                              */  | 
1271  |  | /************************************************************************/  | 
1272  |  | CPLErr netCDFRasterBand::SetScale(double dfNewScale)  | 
1273  | 0  | { | 
1274  | 0  |     CPLMutexHolderD(&hNCMutex);  | 
1275  |  |  | 
1276  |  |     // Write value if in update mode.  | 
1277  | 0  |     if (poDS->GetAccess() == GA_Update)  | 
1278  | 0  |     { | 
1279  |  |         // Make sure we are in define mode.  | 
1280  | 0  |         static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);  | 
1281  |  | 
  | 
1282  | 0  |         const int status = nc_put_att_double(cdfid, nZId, CF_SCALE_FACTOR,  | 
1283  | 0  |                                              NC_DOUBLE, 1, &dfNewScale);  | 
1284  |  | 
  | 
1285  | 0  |         NCDF_ERR(status);  | 
1286  | 0  |         if (status == NC_NOERR)  | 
1287  | 0  |         { | 
1288  | 0  |             SetScaleNoUpdate(dfNewScale);  | 
1289  | 0  |             return CE_None;  | 
1290  | 0  |         }  | 
1291  |  |  | 
1292  | 0  |         return CE_Failure;  | 
1293  | 0  |     }  | 
1294  |  |  | 
1295  | 0  |     SetScaleNoUpdate(dfNewScale);  | 
1296  | 0  |     return CE_None;  | 
1297  | 0  | }  | 
1298  |  |  | 
1299  |  | /************************************************************************/  | 
1300  |  | /*                         SetScaleNoUpdate()                           */  | 
1301  |  | /************************************************************************/  | 
1302  |  | void netCDFRasterBand::SetScaleNoUpdate(double dfVal)  | 
1303  | 0  | { | 
1304  | 0  |     m_dfScale = dfVal;  | 
1305  | 0  |     m_bHaveScale = true;  | 
1306  | 0  | }  | 
1307  |  |  | 
1308  |  | /************************************************************************/  | 
1309  |  | /*                            GetUnitType()                             */  | 
1310  |  | /************************************************************************/  | 
1311  |  |  | 
1312  |  | const char *netCDFRasterBand::GetUnitType()  | 
1313  |  |  | 
1314  | 0  | { | 
1315  | 0  |     if (!m_osUnitType.empty())  | 
1316  | 0  |         return m_osUnitType;  | 
1317  |  |  | 
1318  | 0  |     return GDALRasterBand::GetUnitType();  | 
1319  | 0  | }  | 
1320  |  |  | 
1321  |  | /************************************************************************/  | 
1322  |  | /*                           SetUnitType()                              */  | 
1323  |  | /************************************************************************/  | 
1324  |  |  | 
1325  |  | CPLErr netCDFRasterBand::SetUnitType(const char *pszNewValue)  | 
1326  |  |  | 
1327  | 0  | { | 
1328  | 0  |     CPLMutexHolderD(&hNCMutex);  | 
1329  |  | 
  | 
1330  | 0  |     const std::string osUnitType = (pszNewValue != nullptr ? pszNewValue : "");  | 
1331  |  | 
  | 
1332  | 0  |     if (!osUnitType.empty())  | 
1333  | 0  |     { | 
1334  |  |         // Write value if in update mode.  | 
1335  | 0  |         if (poDS->GetAccess() == GA_Update)  | 
1336  | 0  |         { | 
1337  |  |             // Make sure we are in define mode.  | 
1338  | 0  |             static_cast<netCDFDataset *>(poDS)->SetDefineMode(TRUE);  | 
1339  |  | 
  | 
1340  | 0  |             const int status = nc_put_att_text(  | 
1341  | 0  |                 cdfid, nZId, CF_UNITS, osUnitType.size(), osUnitType.c_str());  | 
1342  |  | 
  | 
1343  | 0  |             NCDF_ERR(status);  | 
1344  | 0  |             if (status == NC_NOERR)  | 
1345  | 0  |             { | 
1346  | 0  |                 SetUnitTypeNoUpdate(pszNewValue);  | 
1347  | 0  |                 return CE_None;  | 
1348  | 0  |             }  | 
1349  |  |  | 
1350  | 0  |             return CE_Failure;  | 
1351  | 0  |         }  | 
1352  | 0  |     }  | 
1353  |  |  | 
1354  | 0  |     SetUnitTypeNoUpdate(pszNewValue);  | 
1355  |  | 
  | 
1356  | 0  |     return CE_None;  | 
1357  | 0  | }  | 
1358  |  |  | 
1359  |  | /************************************************************************/  | 
1360  |  | /*                       SetUnitTypeNoUpdate()                          */  | 
1361  |  | /************************************************************************/  | 
1362  |  |  | 
1363  |  | void netCDFRasterBand::SetUnitTypeNoUpdate(const char *pszNewValue)  | 
1364  | 0  | { | 
1365  | 0  |     m_osUnitType = (pszNewValue != nullptr ? pszNewValue : "");  | 
1366  | 0  | }  | 
1367  |  |  | 
1368  |  | /************************************************************************/  | 
1369  |  | /*                           GetNoDataValue()                           */  | 
1370  |  | /************************************************************************/  | 
1371  |  |  | 
1372  |  | double netCDFRasterBand::GetNoDataValue(int *pbSuccess)  | 
1373  |  |  | 
1374  | 0  | { | 
1375  | 0  |     if (m_bNoDataSetAsInt64)  | 
1376  | 0  |     { | 
1377  | 0  |         if (pbSuccess)  | 
1378  | 0  |             *pbSuccess = TRUE;  | 
1379  | 0  |         return GDALGetNoDataValueCastToDouble(m_nNodataValueInt64);  | 
1380  | 0  |     }  | 
1381  |  |  | 
1382  | 0  |     if (m_bNoDataSetAsUInt64)  | 
1383  | 0  |     { | 
1384  | 0  |         if (pbSuccess)  | 
1385  | 0  |             *pbSuccess = TRUE;  | 
1386  | 0  |         return GDALGetNoDataValueCastToDouble(m_nNodataValueUInt64);  | 
1387  | 0  |     }  | 
1388  |  |  | 
1389  | 0  |     if (m_bNoDataSet)  | 
1390  | 0  |     { | 
1391  | 0  |         if (pbSuccess)  | 
1392  | 0  |             *pbSuccess = TRUE;  | 
1393  | 0  |         return m_dfNoDataValue;  | 
1394  | 0  |     }  | 
1395  |  |  | 
1396  | 0  |     return GDALPamRasterBand::GetNoDataValue(pbSuccess);  | 
1397  | 0  | }  | 
1398  |  |  | 
1399  |  | /************************************************************************/  | 
1400  |  | /*                        GetNoDataValueAsInt64()                       */  | 
1401  |  | /************************************************************************/  | 
1402  |  |  | 
1403  |  | int64_t netCDFRasterBand::GetNoDataValueAsInt64(int *pbSuccess)  | 
1404  |  |  | 
1405  | 0  | { | 
1406  | 0  |     if (m_bNoDataSetAsInt64)  | 
1407  | 0  |     { | 
1408  | 0  |         if (pbSuccess)  | 
1409  | 0  |             *pbSuccess = TRUE;  | 
1410  |  | 
  | 
1411  | 0  |         return m_nNodataValueInt64;  | 
1412  | 0  |     }  | 
1413  |  |  | 
1414  | 0  |     return GDALPamRasterBand::GetNoDataValueAsInt64(pbSuccess);  | 
1415  | 0  | }  | 
1416  |  |  | 
1417  |  | /************************************************************************/  | 
1418  |  | /*                        GetNoDataValueAsUInt64()                      */  | 
1419  |  | /************************************************************************/  | 
1420  |  |  | 
1421  |  | uint64_t netCDFRasterBand::GetNoDataValueAsUInt64(int *pbSuccess)  | 
1422  |  |  | 
1423  | 0  | { | 
1424  | 0  |     if (m_bNoDataSetAsUInt64)  | 
1425  | 0  |     { | 
1426  | 0  |         if (pbSuccess)  | 
1427  | 0  |             *pbSuccess = TRUE;  | 
1428  |  | 
  | 
1429  | 0  |         return m_nNodataValueUInt64;  | 
1430  | 0  |     }  | 
1431  |  |  | 
1432  | 0  |     return GDALPamRasterBand::GetNoDataValueAsUInt64(pbSuccess);  | 
1433  | 0  | }  | 
1434  |  |  | 
1435  |  | /************************************************************************/  | 
1436  |  | /*                           SetNoDataValue()                           */  | 
1437  |  | /************************************************************************/  | 
1438  |  |  | 
1439  |  | CPLErr netCDFRasterBand::SetNoDataValue(double dfNoData)  | 
1440  |  |  | 
1441  | 0  | { | 
1442  | 0  |     CPLMutexHolderD(&hNCMutex);  | 
1443  |  |  | 
1444  |  |     // If already set to new value, don't do anything.  | 
1445  | 0  |     if (m_bNoDataSet && CPLIsEqual(dfNoData, m_dfNoDataValue))  | 
1446  | 0  |         return CE_None;  | 
1447  |  |  | 
1448  |  |     // Write value if in update mode.  | 
1449  | 0  |     if (poDS->GetAccess() == GA_Update)  | 
1450  | 0  |     { | 
1451  |  |         // netcdf-4 does not allow to set _FillValue after leaving define mode,  | 
1452  |  |         // but it is ok if variable has not been written to, so only print  | 
1453  |  |         // debug. See bug #4484.  | 
1454  | 0  |         if (m_bNoDataSet &&  | 
1455  | 0  |             !reinterpret_cast<netCDFDataset *>(poDS)->GetDefineMode())  | 
1456  | 0  |         { | 
1457  | 0  |             CPLDebug("GDAL_netCDF", | 
1458  | 0  |                      "Setting NoDataValue to %.17g (previously set to %.17g) "  | 
1459  | 0  |                      "but file is no longer in define mode (id #%d, band #%d)",  | 
1460  | 0  |                      dfNoData, m_dfNoDataValue, cdfid, nBand);  | 
1461  | 0  |         }  | 
1462  |  | #ifdef NCDF_DEBUG  | 
1463  |  |         else  | 
1464  |  |         { | 
1465  |  |             CPLDebug("GDAL_netCDF", | 
1466  |  |                      "Setting NoDataValue to %.17g (id #%d, band #%d)",  | 
1467  |  |                      dfNoData, cdfid, nBand);  | 
1468  |  |         }  | 
1469  |  | #endif  | 
1470  |  |         // Make sure we are in define mode.  | 
1471  | 0  |         reinterpret_cast<netCDFDataset *>(poDS)->SetDefineMode(true);  | 
1472  |  | 
  | 
1473  | 0  |         int status;  | 
1474  | 0  |         if (eDataType == GDT_Byte)  | 
1475  | 0  |         { | 
1476  | 0  |             if (bSignedData)  | 
1477  | 0  |             { | 
1478  | 0  |                 signed char cNoDataValue = static_cast<signed char>(dfNoData);  | 
1479  | 0  |                 status = nc_put_att_schar(cdfid, nZId, NCDF_FillValue,  | 
1480  | 0  |                                           nc_datatype, 1, &cNoDataValue);  | 
1481  | 0  |             }  | 
1482  | 0  |             else  | 
1483  | 0  |             { | 
1484  | 0  |                 const unsigned char ucNoDataValue =  | 
1485  | 0  |                     static_cast<unsigned char>(dfNoData);  | 
1486  | 0  |                 status = nc_put_att_uchar(cdfid, nZId, NCDF_FillValue,  | 
1487  | 0  |                                           nc_datatype, 1, &ucNoDataValue);  | 
1488  | 0  |             }  | 
1489  | 0  |         }  | 
1490  | 0  |         else if (eDataType == GDT_Int16)  | 
1491  | 0  |         { | 
1492  | 0  |             short nsNoDataValue = static_cast<short>(dfNoData);  | 
1493  | 0  |             status = nc_put_att_short(cdfid, nZId, NCDF_FillValue, nc_datatype,  | 
1494  | 0  |                                       1, &nsNoDataValue);  | 
1495  | 0  |         }  | 
1496  | 0  |         else if (eDataType == GDT_Int32)  | 
1497  | 0  |         { | 
1498  | 0  |             int nNoDataValue = static_cast<int>(dfNoData);  | 
1499  | 0  |             status = nc_put_att_int(cdfid, nZId, NCDF_FillValue, nc_datatype, 1,  | 
1500  | 0  |                                     &nNoDataValue);  | 
1501  | 0  |         }  | 
1502  | 0  |         else if (eDataType == GDT_Float32)  | 
1503  | 0  |         { | 
1504  | 0  |             float fNoDataValue = static_cast<float>(dfNoData);  | 
1505  | 0  |             status = nc_put_att_float(cdfid, nZId, NCDF_FillValue, nc_datatype,  | 
1506  | 0  |                                       1, &fNoDataValue);  | 
1507  | 0  |         }  | 
1508  | 0  |         else if (eDataType == GDT_UInt16 &&  | 
1509  | 0  |                  reinterpret_cast<netCDFDataset *>(poDS)->eFormat ==  | 
1510  | 0  |                      NCDF_FORMAT_NC4)  | 
1511  | 0  |         { | 
1512  | 0  |             unsigned short usNoDataValue =  | 
1513  | 0  |                 static_cast<unsigned short>(dfNoData);  | 
1514  | 0  |             status = nc_put_att_ushort(cdfid, nZId, NCDF_FillValue, nc_datatype,  | 
1515  | 0  |                                        1, &usNoDataValue);  | 
1516  | 0  |         }  | 
1517  | 0  |         else if (eDataType == GDT_UInt32 &&  | 
1518  | 0  |                  reinterpret_cast<netCDFDataset *>(poDS)->eFormat ==  | 
1519  | 0  |                      NCDF_FORMAT_NC4)  | 
1520  | 0  |         { | 
1521  | 0  |             unsigned int unNoDataValue = static_cast<unsigned int>(dfNoData);  | 
1522  | 0  |             status = nc_put_att_uint(cdfid, nZId, NCDF_FillValue, nc_datatype,  | 
1523  | 0  |                                      1, &unNoDataValue);  | 
1524  | 0  |         }  | 
1525  | 0  |         else  | 
1526  | 0  |         { | 
1527  | 0  |             status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,  | 
1528  | 0  |                                        1, &dfNoData);  | 
1529  | 0  |         }  | 
1530  |  | 
  | 
1531  | 0  |         NCDF_ERR(status);  | 
1532  |  |  | 
1533  |  |         // Update status if write worked.  | 
1534  | 0  |         if (status == NC_NOERR)  | 
1535  | 0  |         { | 
1536  | 0  |             SetNoDataValueNoUpdate(dfNoData);  | 
1537  | 0  |             return CE_None;  | 
1538  | 0  |         }  | 
1539  |  |  | 
1540  | 0  |         return CE_Failure;  | 
1541  | 0  |     }  | 
1542  |  |  | 
1543  | 0  |     SetNoDataValueNoUpdate(dfNoData);  | 
1544  | 0  |     return CE_None;  | 
1545  | 0  | }  | 
1546  |  |  | 
1547  |  | /************************************************************************/  | 
1548  |  | /*                       SetNoDataValueNoUpdate()                       */  | 
1549  |  | /************************************************************************/  | 
1550  |  |  | 
1551  |  | void netCDFRasterBand::SetNoDataValueNoUpdate(double dfNoData)  | 
1552  | 0  | { | 
1553  | 0  |     m_dfNoDataValue = dfNoData;  | 
1554  | 0  |     m_bNoDataSet = true;  | 
1555  | 0  |     m_bNoDataSetAsInt64 = false;  | 
1556  | 0  |     m_bNoDataSetAsUInt64 = false;  | 
1557  | 0  | }  | 
1558  |  |  | 
1559  |  | /************************************************************************/  | 
1560  |  | /*                        SetNoDataValueAsInt64()                       */  | 
1561  |  | /************************************************************************/  | 
1562  |  |  | 
1563  |  | CPLErr netCDFRasterBand::SetNoDataValueAsInt64(int64_t nNoData)  | 
1564  |  |  | 
1565  | 0  | { | 
1566  | 0  |     CPLMutexHolderD(&hNCMutex);  | 
1567  |  |  | 
1568  |  |     // If already set to new value, don't do anything.  | 
1569  | 0  |     if (m_bNoDataSetAsInt64 && nNoData == m_nNodataValueInt64)  | 
1570  | 0  |         return CE_None;  | 
1571  |  |  | 
1572  |  |     // Write value if in update mode.  | 
1573  | 0  |     if (poDS->GetAccess() == GA_Update)  | 
1574  | 0  |     { | 
1575  |  |         // netcdf-4 does not allow to set NCDF_FillValue after leaving define mode,  | 
1576  |  |         // but it is ok if variable has not been written to, so only print  | 
1577  |  |         // debug. See bug #4484.  | 
1578  | 0  |         if (m_bNoDataSetAsInt64 &&  | 
1579  | 0  |             !reinterpret_cast<netCDFDataset *>(poDS)->GetDefineMode())  | 
1580  | 0  |         { | 
1581  | 0  |             CPLDebug("GDAL_netCDF", | 
1582  | 0  |                      "Setting NoDataValue to " CPL_FRMT_GIB  | 
1583  | 0  |                      " (previously set to " CPL_FRMT_GIB ") "  | 
1584  | 0  |                      "but file is no longer in define mode (id #%d, band #%d)",  | 
1585  | 0  |                      static_cast<GIntBig>(nNoData),  | 
1586  | 0  |                      static_cast<GIntBig>(m_nNodataValueInt64), cdfid, nBand);  | 
1587  | 0  |         }  | 
1588  |  | #ifdef NCDF_DEBUG  | 
1589  |  |         else  | 
1590  |  |         { | 
1591  |  |             CPLDebug("GDAL_netCDF", | 
1592  |  |                      "Setting NoDataValue to " CPL_FRMT_GIB  | 
1593  |  |                      " (id #%d, band #%d)",  | 
1594  |  |                      static_cast<GIntBig>(nNoData), cdfid, nBand);  | 
1595  |  |         }  | 
1596  |  | #endif  | 
1597  |  |         // Make sure we are in define mode.  | 
1598  | 0  |         reinterpret_cast<netCDFDataset *>(poDS)->SetDefineMode(true);  | 
1599  |  | 
  | 
1600  | 0  |         int status;  | 
1601  | 0  |         if (eDataType == GDT_Int64 &&  | 
1602  | 0  |             reinterpret_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)  | 
1603  | 0  |         { | 
1604  | 0  |             long long tmp = static_cast<long long>(nNoData);  | 
1605  | 0  |             status = nc_put_att_longlong(cdfid, nZId, NCDF_FillValue,  | 
1606  | 0  |                                          nc_datatype, 1, &tmp);  | 
1607  | 0  |         }  | 
1608  | 0  |         else  | 
1609  | 0  |         { | 
1610  | 0  |             double dfNoData = static_cast<double>(nNoData);  | 
1611  | 0  |             status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,  | 
1612  | 0  |                                        1, &dfNoData);  | 
1613  | 0  |         }  | 
1614  |  | 
  | 
1615  | 0  |         NCDF_ERR(status);  | 
1616  |  |  | 
1617  |  |         // Update status if write worked.  | 
1618  | 0  |         if (status == NC_NOERR)  | 
1619  | 0  |         { | 
1620  | 0  |             SetNoDataValueNoUpdate(nNoData);  | 
1621  | 0  |             return CE_None;  | 
1622  | 0  |         }  | 
1623  |  |  | 
1624  | 0  |         return CE_Failure;  | 
1625  | 0  |     }  | 
1626  |  |  | 
1627  | 0  |     SetNoDataValueNoUpdate(nNoData);  | 
1628  | 0  |     return CE_None;  | 
1629  | 0  | }  | 
1630  |  |  | 
1631  |  | /************************************************************************/  | 
1632  |  | /*                       SetNoDataValueNoUpdate()                       */  | 
1633  |  | /************************************************************************/  | 
1634  |  |  | 
1635  |  | void netCDFRasterBand::SetNoDataValueNoUpdate(int64_t nNoData)  | 
1636  | 0  | { | 
1637  | 0  |     m_nNodataValueInt64 = nNoData;  | 
1638  | 0  |     m_bNoDataSet = false;  | 
1639  | 0  |     m_bNoDataSetAsInt64 = true;  | 
1640  | 0  |     m_bNoDataSetAsUInt64 = false;  | 
1641  | 0  | }  | 
1642  |  |  | 
1643  |  | /************************************************************************/  | 
1644  |  | /*                        SetNoDataValueAsUInt64()                      */  | 
1645  |  | /************************************************************************/  | 
1646  |  |  | 
1647  |  | CPLErr netCDFRasterBand::SetNoDataValueAsUInt64(uint64_t nNoData)  | 
1648  |  |  | 
1649  | 0  | { | 
1650  | 0  |     CPLMutexHolderD(&hNCMutex);  | 
1651  |  |  | 
1652  |  |     // If already set to new value, don't do anything.  | 
1653  | 0  |     if (m_bNoDataSetAsUInt64 && nNoData == m_nNodataValueUInt64)  | 
1654  | 0  |         return CE_None;  | 
1655  |  |  | 
1656  |  |     // Write value if in update mode.  | 
1657  | 0  |     if (poDS->GetAccess() == GA_Update)  | 
1658  | 0  |     { | 
1659  |  |         // netcdf-4 does not allow to set _FillValue after leaving define mode,  | 
1660  |  |         // but it is ok if variable has not been written to, so only print  | 
1661  |  |         // debug. See bug #4484.  | 
1662  | 0  |         if (m_bNoDataSetAsUInt64 &&  | 
1663  | 0  |             !reinterpret_cast<netCDFDataset *>(poDS)->GetDefineMode())  | 
1664  | 0  |         { | 
1665  | 0  |             CPLDebug("GDAL_netCDF", | 
1666  | 0  |                      "Setting NoDataValue to " CPL_FRMT_GUIB  | 
1667  | 0  |                      " (previously set to " CPL_FRMT_GUIB ") "  | 
1668  | 0  |                      "but file is no longer in define mode (id #%d, band #%d)",  | 
1669  | 0  |                      static_cast<GUIntBig>(nNoData),  | 
1670  | 0  |                      static_cast<GUIntBig>(m_nNodataValueUInt64), cdfid, nBand);  | 
1671  | 0  |         }  | 
1672  |  | #ifdef NCDF_DEBUG  | 
1673  |  |         else  | 
1674  |  |         { | 
1675  |  |             CPLDebug("GDAL_netCDF", | 
1676  |  |                      "Setting NoDataValue to " CPL_FRMT_GUIB  | 
1677  |  |                      " (id #%d, band #%d)",  | 
1678  |  |                      static_cast<GUIntBig>(nNoData), cdfid, nBand);  | 
1679  |  |         }  | 
1680  |  | #endif  | 
1681  |  |         // Make sure we are in define mode.  | 
1682  | 0  |         reinterpret_cast<netCDFDataset *>(poDS)->SetDefineMode(true);  | 
1683  |  | 
  | 
1684  | 0  |         int status;  | 
1685  | 0  |         if (eDataType == GDT_UInt64 &&  | 
1686  | 0  |             reinterpret_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)  | 
1687  | 0  |         { | 
1688  | 0  |             unsigned long long tmp = static_cast<long long>(nNoData);  | 
1689  | 0  |             status = nc_put_att_ulonglong(cdfid, nZId, NCDF_FillValue,  | 
1690  | 0  |                                           nc_datatype, 1, &tmp);  | 
1691  | 0  |         }  | 
1692  | 0  |         else  | 
1693  | 0  |         { | 
1694  | 0  |             double dfNoData = static_cast<double>(nNoData);  | 
1695  | 0  |             status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,  | 
1696  | 0  |                                        1, &dfNoData);  | 
1697  | 0  |         }  | 
1698  |  | 
  | 
1699  | 0  |         NCDF_ERR(status);  | 
1700  |  |  | 
1701  |  |         // Update status if write worked.  | 
1702  | 0  |         if (status == NC_NOERR)  | 
1703  | 0  |         { | 
1704  | 0  |             SetNoDataValueNoUpdate(nNoData);  | 
1705  | 0  |             return CE_None;  | 
1706  | 0  |         }  | 
1707  |  |  | 
1708  | 0  |         return CE_Failure;  | 
1709  | 0  |     }  | 
1710  |  |  | 
1711  | 0  |     SetNoDataValueNoUpdate(nNoData);  | 
1712  | 0  |     return CE_None;  | 
1713  | 0  | }  | 
1714  |  |  | 
1715  |  | /************************************************************************/  | 
1716  |  | /*                       SetNoDataValueNoUpdate()                       */  | 
1717  |  | /************************************************************************/  | 
1718  |  |  | 
1719  |  | void netCDFRasterBand::SetNoDataValueNoUpdate(uint64_t nNoData)  | 
1720  | 0  | { | 
1721  | 0  |     m_nNodataValueUInt64 = nNoData;  | 
1722  | 0  |     m_bNoDataSet = false;  | 
1723  | 0  |     m_bNoDataSetAsInt64 = false;  | 
1724  | 0  |     m_bNoDataSetAsUInt64 = true;  | 
1725  | 0  | }  | 
1726  |  |  | 
1727  |  | /************************************************************************/  | 
1728  |  | /*                        DeleteNoDataValue()                           */  | 
1729  |  | /************************************************************************/  | 
1730  |  |  | 
1731  |  | #ifdef notdef  | 
1732  |  | CPLErr netCDFRasterBand::DeleteNoDataValue()  | 
1733  |  |  | 
1734  |  | { | 
1735  |  |     CPLMutexHolderD(&hNCMutex);  | 
1736  |  |  | 
1737  |  |     if (!bNoDataSet)  | 
1738  |  |         return CE_None;  | 
1739  |  |  | 
1740  |  |     // Write value if in update mode.  | 
1741  |  |     if (poDS->GetAccess() == GA_Update)  | 
1742  |  |     { | 
1743  |  |         // Make sure we are in define mode.  | 
1744  |  |         static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);  | 
1745  |  |  | 
1746  |  |         status = nc_del_att(cdfid, nZId, NCDF_FillValue);  | 
1747  |  |  | 
1748  |  |         NCDF_ERR(status);  | 
1749  |  |  | 
1750  |  |         // Update status if write worked.  | 
1751  |  |         if (status == NC_NOERR)  | 
1752  |  |         { | 
1753  |  |             dfNoDataValue = 0.0;  | 
1754  |  |             bNoDataSet = false;  | 
1755  |  |             return CE_None;  | 
1756  |  |         }  | 
1757  |  |  | 
1758  |  |         return CE_Failure;  | 
1759  |  |     }  | 
1760  |  |  | 
1761  |  |     dfNoDataValue = 0.0;  | 
1762  |  |     bNoDataSet = false;  | 
1763  |  |     return CE_None;  | 
1764  |  | }  | 
1765  |  | #endif  | 
1766  |  |  | 
1767  |  | /************************************************************************/  | 
1768  |  | /*                           SerializeToXML()                           */  | 
1769  |  | /************************************************************************/  | 
1770  |  |  | 
1771  |  | CPLXMLNode *netCDFRasterBand::SerializeToXML(const char * /* pszUnused */)  | 
1772  | 0  | { | 
1773  |  |     // Overridden from GDALPamDataset to add only band histogram  | 
1774  |  |     // and statistics. See bug #4244.  | 
1775  | 0  |     if (psPam == nullptr)  | 
1776  | 0  |         return nullptr;  | 
1777  |  |  | 
1778  |  |     // Setup root node and attributes.  | 
1779  | 0  |     CPLXMLNode *psTree =  | 
1780  | 0  |         CPLCreateXMLNode(nullptr, CXT_Element, "PAMRasterBand");  | 
1781  |  | 
  | 
1782  | 0  |     if (GetBand() > 0)  | 
1783  | 0  |     { | 
1784  | 0  |         CPLString oFmt;  | 
1785  | 0  |         CPLSetXMLValue(psTree, "#band", oFmt.Printf("%d", GetBand())); | 
1786  | 0  |     }  | 
1787  |  |  | 
1788  |  |     // Histograms.  | 
1789  | 0  |     if (psPam->psSavedHistograms != nullptr)  | 
1790  | 0  |         CPLAddXMLChild(psTree, CPLCloneXMLTree(psPam->psSavedHistograms));  | 
1791  |  |  | 
1792  |  |     // Metadata (statistics only).  | 
1793  | 0  |     GDALMultiDomainMetadata oMDMDStats;  | 
1794  | 0  |     const char *papszMDStats[] = {"STATISTICS_MINIMUM", "STATISTICS_MAXIMUM", | 
1795  | 0  |                                   "STATISTICS_MEAN", "STATISTICS_STDDEV",  | 
1796  | 0  |                                   nullptr};  | 
1797  | 0  |     for (int i = 0; i < CSLCount(papszMDStats); i++)  | 
1798  | 0  |     { | 
1799  | 0  |         const char *pszMDI = GetMetadataItem(papszMDStats[i]);  | 
1800  | 0  |         if (pszMDI)  | 
1801  | 0  |             oMDMDStats.SetMetadataItem(papszMDStats[i], pszMDI);  | 
1802  | 0  |     }  | 
1803  | 0  |     CPLXMLNode *psMD = oMDMDStats.Serialize();  | 
1804  |  | 
  | 
1805  | 0  |     if (psMD != nullptr)  | 
1806  | 0  |     { | 
1807  | 0  |         if (psMD->psChild == nullptr)  | 
1808  | 0  |             CPLDestroyXMLNode(psMD);  | 
1809  | 0  |         else  | 
1810  | 0  |             CPLAddXMLChild(psTree, psMD);  | 
1811  | 0  |     }  | 
1812  |  |  | 
1813  |  |     // We don't want to return anything if we had no metadata to attach.  | 
1814  | 0  |     if (psTree->psChild == nullptr || psTree->psChild->psNext == nullptr)  | 
1815  | 0  |     { | 
1816  | 0  |         CPLDestroyXMLNode(psTree);  | 
1817  | 0  |         psTree = nullptr;  | 
1818  | 0  |     }  | 
1819  |  | 
  | 
1820  | 0  |     return psTree;  | 
1821  | 0  | }  | 
1822  |  |  | 
1823  |  | /************************************************************************/  | 
1824  |  | /*               Get1DVariableIndexedByDimension()                      */  | 
1825  |  | /************************************************************************/  | 
1826  |  |  | 
1827  |  | static int Get1DVariableIndexedByDimension(int cdfid, int nDimId,  | 
1828  |  |                                            const char *pszDimName,  | 
1829  |  |                                            bool bVerboseError, int *pnGroupID)  | 
1830  | 0  | { | 
1831  | 0  |     *pnGroupID = -1;  | 
1832  | 0  |     int nVarID = -1;  | 
1833  |  |     // First try to find a variable whose name is identical to the dimension  | 
1834  |  |     // name, and check that it is indeed indexed by this dimension  | 
1835  | 0  |     if (NCDFResolveVar(cdfid, pszDimName, pnGroupID, &nVarID) == CE_None)  | 
1836  | 0  |     { | 
1837  | 0  |         int nDimCountOfVariable = 0;  | 
1838  | 0  |         nc_inq_varndims(*pnGroupID, nVarID, &nDimCountOfVariable);  | 
1839  | 0  |         if (nDimCountOfVariable == 1)  | 
1840  | 0  |         { | 
1841  | 0  |             int nDimIdOfVariable = -1;  | 
1842  | 0  |             nc_inq_vardimid(*pnGroupID, nVarID, &nDimIdOfVariable);  | 
1843  | 0  |             if (nDimIdOfVariable == nDimId)  | 
1844  | 0  |             { | 
1845  | 0  |                 return nVarID;  | 
1846  | 0  |             }  | 
1847  | 0  |         }  | 
1848  | 0  |     }  | 
1849  |  |  | 
1850  |  |     // Otherwise iterate over the variables to find potential candidates  | 
1851  |  |     // TODO: should be modified to search also in other groups using the same  | 
1852  |  |     //       logic than in NCDFResolveVar(), but maybe not needed if it's a  | 
1853  |  |     //       very rare case? and I think this is not CF compliant.  | 
1854  | 0  |     int nvars = 0;  | 
1855  | 0  |     CPL_IGNORE_RET_VAL(nc_inq(cdfid, nullptr, &nvars, nullptr, nullptr));  | 
1856  |  | 
  | 
1857  | 0  |     int nCountCandidateVars = 0;  | 
1858  | 0  |     int nCandidateVarID = -1;  | 
1859  | 0  |     for (int k = 0; k < nvars; k++)  | 
1860  | 0  |     { | 
1861  | 0  |         int nDimCountOfVariable = 0;  | 
1862  | 0  |         nc_inq_varndims(cdfid, k, &nDimCountOfVariable);  | 
1863  | 0  |         if (nDimCountOfVariable == 1)  | 
1864  | 0  |         { | 
1865  | 0  |             int nDimIdOfVariable = -1;  | 
1866  | 0  |             nc_inq_vardimid(cdfid, k, &nDimIdOfVariable);  | 
1867  | 0  |             if (nDimIdOfVariable == nDimId)  | 
1868  | 0  |             { | 
1869  | 0  |                 nCountCandidateVars++;  | 
1870  | 0  |                 nCandidateVarID = k;  | 
1871  | 0  |             }  | 
1872  | 0  |         }  | 
1873  | 0  |     }  | 
1874  | 0  |     if (nCountCandidateVars > 1)  | 
1875  | 0  |     { | 
1876  | 0  |         if (bVerboseError)  | 
1877  | 0  |         { | 
1878  | 0  |             CPLError(CE_Warning, CPLE_AppDefined,  | 
1879  | 0  |                      "Several 1D variables are indexed by dimension %s",  | 
1880  | 0  |                      pszDimName);  | 
1881  | 0  |         }  | 
1882  | 0  |         *pnGroupID = -1;  | 
1883  | 0  |         return -1;  | 
1884  | 0  |     }  | 
1885  | 0  |     else if (nCandidateVarID < 0)  | 
1886  | 0  |     { | 
1887  | 0  |         if (bVerboseError)  | 
1888  | 0  |         { | 
1889  | 0  |             CPLError(CE_Warning, CPLE_AppDefined,  | 
1890  | 0  |                      "No 1D variable is indexed by dimension %s", pszDimName);  | 
1891  | 0  |         }  | 
1892  | 0  |     }  | 
1893  | 0  |     *pnGroupID = cdfid;  | 
1894  | 0  |     return nCandidateVarID;  | 
1895  | 0  | }  | 
1896  |  |  | 
1897  |  | /************************************************************************/  | 
1898  |  | /*                      CreateMetadataFromAttributes()                  */  | 
1899  |  | /************************************************************************/  | 
1900  |  |  | 
1901  |  | void netCDFRasterBand::CreateMetadataFromAttributes()  | 
1902  | 0  | { | 
1903  | 0  |     char szVarName[NC_MAX_NAME + 1] = {}; | 
1904  | 0  |     int status = nc_inq_varname(cdfid, nZId, szVarName);  | 
1905  | 0  |     NCDF_ERR(status);  | 
1906  |  | 
  | 
1907  | 0  |     GDALPamRasterBand::SetMetadataItem("NETCDF_VARNAME", szVarName); | 
1908  |  |  | 
1909  |  |     // Get attribute metadata.  | 
1910  | 0  |     int nAtt = 0;  | 
1911  | 0  |     NCDF_ERR(nc_inq_varnatts(cdfid, nZId, &nAtt));  | 
1912  |  | 
  | 
1913  | 0  |     for (int i = 0; i < nAtt; i++)  | 
1914  | 0  |     { | 
1915  | 0  |         char szMetaName[NC_MAX_NAME + 1] = {}; | 
1916  | 0  |         status = nc_inq_attname(cdfid, nZId, i, szMetaName);  | 
1917  | 0  |         if (status != NC_NOERR)  | 
1918  | 0  |             continue;  | 
1919  |  |  | 
1920  | 0  |         if (GDALPamRasterBand::GetMetadataItem(szMetaName) != nullptr)  | 
1921  | 0  |         { | 
1922  | 0  |             continue;  | 
1923  | 0  |         }  | 
1924  |  |  | 
1925  | 0  |         char *pszMetaValue = nullptr;  | 
1926  | 0  |         if (NCDFGetAttr(cdfid, nZId, szMetaName, &pszMetaValue) == CE_None)  | 
1927  | 0  |         { | 
1928  | 0  |             GDALPamRasterBand::SetMetadataItem(szMetaName, pszMetaValue);  | 
1929  | 0  |         }  | 
1930  | 0  |         else  | 
1931  | 0  |         { | 
1932  | 0  |             CPLDebug("GDAL_netCDF", "invalid Band metadata %s", szMetaName); | 
1933  | 0  |         }  | 
1934  |  | 
  | 
1935  | 0  |         if (pszMetaValue)  | 
1936  | 0  |         { | 
1937  | 0  |             CPLFree(pszMetaValue);  | 
1938  | 0  |             pszMetaValue = nullptr;  | 
1939  | 0  |         }  | 
1940  | 0  |     }  | 
1941  | 0  | }  | 
1942  |  |  | 
1943  |  | /************************************************************************/  | 
1944  |  | /*                      CreateMetadataFromOtherVars()                   */  | 
1945  |  | /************************************************************************/  | 
1946  |  |  | 
1947  |  | void netCDFRasterBand::CreateMetadataFromOtherVars()  | 
1948  |  |  | 
1949  | 0  | { | 
1950  | 0  |     CPLAssert(!m_bCreateMetadataFromOtherVarsDone);  | 
1951  | 0  |     m_bCreateMetadataFromOtherVarsDone = true;  | 
1952  |  | 
  | 
1953  | 0  |     netCDFDataset *l_poDS = reinterpret_cast<netCDFDataset *>(poDS);  | 
1954  | 0  |     const int nPamFlagsBackup = l_poDS->nPamFlags;  | 
1955  |  |  | 
1956  |  |     // Compute all dimensions from Band number and save in Metadata.  | 
1957  | 0  |     int nd = 0;  | 
1958  | 0  |     nc_inq_varndims(cdfid, nZId, &nd);  | 
1959  |  |     // Compute multidimention band position.  | 
1960  |  |     //  | 
1961  |  |     // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)  | 
1962  |  |     // if Data[2,3,4,x,y]  | 
1963  |  |     //  | 
1964  |  |     //  BandPos0 = (nBand) / (3*4)  | 
1965  |  |     //  BandPos1 = (nBand - BandPos0*(3*4)) / (4)  | 
1966  |  |     //  BandPos2 = (nBand - BandPos0*(3*4)) % (4)  | 
1967  |  | 
  | 
1968  | 0  |     int Sum = 1;  | 
1969  | 0  |     if (nd == 3)  | 
1970  | 0  |     { | 
1971  | 0  |         Sum *= panBandZLev[0];  | 
1972  | 0  |     }  | 
1973  |  |  | 
1974  |  |     // Loop over non-spatial dimensions.  | 
1975  | 0  |     int Taken = 0;  | 
1976  |  | 
  | 
1977  | 0  |     for (int i = 0; i < nd - 2; i++)  | 
1978  | 0  |     { | 
1979  | 0  |         int result;  | 
1980  | 0  |         if (i != nd - 2 - 1)  | 
1981  | 0  |         { | 
1982  | 0  |             Sum = 1;  | 
1983  | 0  |             for (int j = i + 1; j < nd - 2; j++)  | 
1984  | 0  |             { | 
1985  | 0  |                 Sum *= panBandZLev[j];  | 
1986  | 0  |             }  | 
1987  | 0  |             result = static_cast<int>((nLevel - Taken) / Sum);  | 
1988  | 0  |         }  | 
1989  | 0  |         else  | 
1990  | 0  |         { | 
1991  | 0  |             result = static_cast<int>((nLevel - Taken) % Sum);  | 
1992  | 0  |         }  | 
1993  |  | 
  | 
1994  | 0  |         char szName[NC_MAX_NAME + 1] = {}; | 
1995  | 0  |         snprintf(szName, sizeof(szName), "%s",  | 
1996  | 0  |                  l_poDS->papszDimName[l_poDS->m_anDimIds[panBandZPos[i]]]);  | 
1997  |  | 
  | 
1998  | 0  |         char szMetaName[NC_MAX_NAME + 1 + 32];  | 
1999  | 0  |         snprintf(szMetaName, sizeof(szMetaName), "NETCDF_DIM_%s", szName);  | 
2000  |  | 
  | 
2001  | 0  |         const int nGroupID = l_poDS->m_anExtraDimGroupIds[i];  | 
2002  | 0  |         const int nVarID = l_poDS->m_anExtraDimVarIds[i];  | 
2003  | 0  |         if (nVarID < 0)  | 
2004  | 0  |         { | 
2005  | 0  |             GDALPamRasterBand::SetMetadataItem(szMetaName,  | 
2006  | 0  |                                                CPLSPrintf("%d", result + 1)); | 
2007  | 0  |         }  | 
2008  | 0  |         else  | 
2009  | 0  |         { | 
2010  |  |             // TODO: Make sure all the status checks make sense.  | 
2011  |  | 
  | 
2012  | 0  |             nc_type nVarType = NC_NAT;  | 
2013  | 0  |             /* status = */ nc_inq_vartype(nGroupID, nVarID, &nVarType);  | 
2014  |  | 
  | 
2015  | 0  |             int nDims = 0;  | 
2016  | 0  |             /* status = */ nc_inq_varndims(nGroupID, nVarID, &nDims);  | 
2017  |  | 
  | 
2018  | 0  |             char szMetaTemp[256] = {}; | 
2019  | 0  |             if (nDims == 1)  | 
2020  | 0  |             { | 
2021  | 0  |                 size_t count[1] = {1}; | 
2022  | 0  |                 size_t start[1] = {static_cast<size_t>(result)}; | 
2023  |  | 
  | 
2024  | 0  |                 switch (nVarType)  | 
2025  | 0  |                 { | 
2026  | 0  |                     case NC_BYTE:  | 
2027  |  |                         // TODO: Check for signed/unsigned byte.  | 
2028  | 0  |                         signed char cData;  | 
2029  | 0  |                         /* status = */ nc_get_vara_schar(nGroupID, nVarID,  | 
2030  | 0  |                                                          start, count, &cData);  | 
2031  | 0  |                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", cData);  | 
2032  | 0  |                         break;  | 
2033  | 0  |                     case NC_SHORT:  | 
2034  | 0  |                         short sData;  | 
2035  | 0  |                         /* status = */ nc_get_vara_short(nGroupID, nVarID,  | 
2036  | 0  |                                                          start, count, &sData);  | 
2037  | 0  |                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", sData);  | 
2038  | 0  |                         break;  | 
2039  | 0  |                     case NC_INT:  | 
2040  | 0  |                     { | 
2041  | 0  |                         int nData;  | 
2042  | 0  |                         /* status = */ nc_get_vara_int(nGroupID, nVarID, start,  | 
2043  | 0  |                                                        count, &nData);  | 
2044  | 0  |                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", nData);  | 
2045  | 0  |                         break;  | 
2046  | 0  |                     }  | 
2047  | 0  |                     case NC_FLOAT:  | 
2048  | 0  |                         float fData;  | 
2049  | 0  |                         /* status = */ nc_get_vara_float(nGroupID, nVarID,  | 
2050  | 0  |                                                          start, count, &fData);  | 
2051  | 0  |                         CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.8g",  | 
2052  | 0  |                                     fData);  | 
2053  | 0  |                         break;  | 
2054  | 0  |                     case NC_DOUBLE:  | 
2055  | 0  |                         double dfData;  | 
2056  | 0  |                         /* status = */ nc_get_vara_double(  | 
2057  | 0  |                             nGroupID, nVarID, start, count, &dfData);  | 
2058  | 0  |                         CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.16g",  | 
2059  | 0  |                                     dfData);  | 
2060  | 0  |                         break;  | 
2061  | 0  |                     case NC_UBYTE:  | 
2062  | 0  |                         unsigned char ucData;  | 
2063  | 0  |                         /* status = */ nc_get_vara_uchar(nGroupID, nVarID,  | 
2064  | 0  |                                                          start, count, &ucData);  | 
2065  | 0  |                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", ucData);  | 
2066  | 0  |                         break;  | 
2067  | 0  |                     case NC_USHORT:  | 
2068  | 0  |                         unsigned short usData;  | 
2069  | 0  |                         /* status = */ nc_get_vara_ushort(  | 
2070  | 0  |                             nGroupID, nVarID, start, count, &usData);  | 
2071  | 0  |                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", usData);  | 
2072  | 0  |                         break;  | 
2073  | 0  |                     case NC_UINT:  | 
2074  | 0  |                     { | 
2075  | 0  |                         unsigned int unData;  | 
2076  | 0  |                         /* status = */ nc_get_vara_uint(nGroupID, nVarID, start,  | 
2077  | 0  |                                                         count, &unData);  | 
2078  | 0  |                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", unData);  | 
2079  | 0  |                         break;  | 
2080  | 0  |                     }  | 
2081  | 0  |                     case NC_INT64:  | 
2082  | 0  |                     { | 
2083  | 0  |                         long long nData;  | 
2084  | 0  |                         /* status = */ nc_get_vara_longlong(  | 
2085  | 0  |                             nGroupID, nVarID, start, count, &nData);  | 
2086  | 0  |                         snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GIB,  | 
2087  | 0  |                                  nData);  | 
2088  | 0  |                         break;  | 
2089  | 0  |                     }  | 
2090  | 0  |                     case NC_UINT64:  | 
2091  | 0  |                     { | 
2092  | 0  |                         unsigned long long unData;  | 
2093  | 0  |                         /* status = */ nc_get_vara_ulonglong(  | 
2094  | 0  |                             nGroupID, nVarID, start, count, &unData);  | 
2095  | 0  |                         snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GUIB,  | 
2096  | 0  |                                  unData);  | 
2097  | 0  |                         break;  | 
2098  | 0  |                     }  | 
2099  | 0  |                     default:  | 
2100  | 0  |                         CPLDebug("GDAL_netCDF", "invalid dim %s, type=%d", | 
2101  | 0  |                                  szMetaTemp, nVarType);  | 
2102  | 0  |                         break;  | 
2103  | 0  |                 }  | 
2104  | 0  |             }  | 
2105  | 0  |             else  | 
2106  | 0  |             { | 
2107  | 0  |                 snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", result + 1);  | 
2108  | 0  |             }  | 
2109  |  |  | 
2110  |  |             // Save dimension value.  | 
2111  |  |             // NOTE: removed #original_units as not part of CF-1.  | 
2112  |  |  | 
2113  | 0  |             GDALPamRasterBand::SetMetadataItem(szMetaName, szMetaTemp);  | 
2114  | 0  |         }  | 
2115  |  |  | 
2116  |  |         // Avoid int32 overflow. Perhaps something more sensible to do here ?  | 
2117  | 0  |         if (result > 0 && Sum > INT_MAX / result)  | 
2118  | 0  |             break;  | 
2119  | 0  |         if (Taken > INT_MAX - result * Sum)  | 
2120  | 0  |             break;  | 
2121  |  |  | 
2122  | 0  |         Taken += result * Sum;  | 
2123  | 0  |     }  // End loop non-spatial dimensions.  | 
2124  |  |  | 
2125  | 0  |     l_poDS->nPamFlags = nPamFlagsBackup;  | 
2126  | 0  | }  | 
2127  |  |  | 
2128  |  | /************************************************************************/  | 
2129  |  | /*                             CheckData()                              */  | 
2130  |  | /************************************************************************/  | 
2131  |  | template <class T>  | 
2132  |  | void netCDFRasterBand::CheckData(void *pImage, void *pImageNC,  | 
2133  |  |                                  size_t nTmpBlockXSize, size_t nTmpBlockYSize,  | 
2134  |  |                                  bool bCheckIsNan)  | 
2135  | 0  | { | 
2136  | 0  |     CPLAssert(pImage != nullptr && pImageNC != nullptr);  | 
2137  |  |  | 
2138  |  |     // If this block is not a full block (in the x axis), we need to re-arrange  | 
2139  |  |     // the data this is because partial blocks are not arranged the same way in  | 
2140  |  |     // netcdf and gdal.  | 
2141  | 0  |     if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))  | 
2142  | 0  |     { | 
2143  | 0  |         T *ptrWrite = static_cast<T *>(pImage);  | 
2144  | 0  |         T *ptrRead = static_cast<T *>(pImageNC);  | 
2145  | 0  |         for (size_t j = 0; j < nTmpBlockYSize;  | 
2146  | 0  |              j++, ptrWrite += nBlockXSize, ptrRead += nTmpBlockXSize)  | 
2147  | 0  |         { | 
2148  | 0  |             memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T));  | 
2149  | 0  |         }  | 
2150  | 0  |     }  | 
2151  |  |  | 
2152  |  |     // Is valid data checking needed or requested?  | 
2153  | 0  |     if (bValidRangeValid || bCheckIsNan)  | 
2154  | 0  |     { | 
2155  | 0  |         T *ptrImage = static_cast<T *>(pImage);  | 
2156  | 0  |         for (size_t j = 0; j < nTmpBlockYSize; j++)  | 
2157  | 0  |         { | 
2158  |  |             // k moves along the gdal block, skipping the out-of-range pixels.  | 
2159  | 0  |             size_t k = j * nBlockXSize;  | 
2160  | 0  |             for (size_t i = 0; i < nTmpBlockXSize; i++, k++)  | 
2161  | 0  |             { | 
2162  |  |                 // Check for nodata and nan.  | 
2163  | 0  |                 if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))  | 
2164  | 0  |                     continue;  | 
2165  | 0  |                 if (bCheckIsNan && std::isnan((double)ptrImage[k]))  | 
2166  | 0  |                 { | 
2167  | 0  |                     ptrImage[k] = (T)m_dfNoDataValue;  | 
2168  | 0  |                     continue;  | 
2169  | 0  |                 }  | 
2170  |  |                 // Check for valid_range.  | 
2171  | 0  |                 if (bValidRangeValid)  | 
2172  | 0  |                 { | 
2173  | 0  |                     if (((adfValidRange[0] != m_dfNoDataValue) &&  | 
2174  | 0  |                          (ptrImage[k] < (T)adfValidRange[0])) ||  | 
2175  | 0  |                         ((adfValidRange[1] != m_dfNoDataValue) &&  | 
2176  | 0  |                          (ptrImage[k] > (T)adfValidRange[1])))  | 
2177  | 0  |                     { | 
2178  | 0  |                         ptrImage[k] = (T)m_dfNoDataValue;  | 
2179  | 0  |                     }  | 
2180  | 0  |                 }  | 
2181  | 0  |             }  | 
2182  | 0  |         }  | 
2183  | 0  |     }  | 
2184  |  |  | 
2185  |  |     // If minimum longitude is > 180, subtract 360 from all.  | 
2186  |  |     // If not, disable checking for further calls (check just once).  | 
2187  |  |     // Only check first and last block elements since lon must be monotonic.  | 
2188  | 0  |     const bool bIsSigned = std::numeric_limits<T>::is_signed;  | 
2189  | 0  |     if (bCheckLongitude && bIsSigned &&  | 
2190  | 0  |         !CPLIsEqual((double)((T *)pImage)[0], m_dfNoDataValue) &&  | 
2191  | 0  |         !CPLIsEqual((double)((T *)pImage)[nTmpBlockXSize - 1],  | 
2192  | 0  |                     m_dfNoDataValue) &&  | 
2193  | 0  |         std::min(((T *)pImage)[0], ((T *)pImage)[nTmpBlockXSize - 1]) > 180.0)  | 
2194  | 0  |     { | 
2195  | 0  |         T *ptrImage = static_cast<T *>(pImage);  | 
2196  | 0  |         for (size_t j = 0; j < nTmpBlockYSize; j++)  | 
2197  | 0  |         { | 
2198  | 0  |             size_t k = j * nBlockXSize;  | 
2199  | 0  |             for (size_t i = 0; i < nTmpBlockXSize; i++, k++)  | 
2200  | 0  |             { | 
2201  | 0  |                 if (!CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))  | 
2202  | 0  |                     ptrImage[k] = static_cast<T>(ptrImage[k] - 360);  | 
2203  | 0  |             }  | 
2204  | 0  |         }  | 
2205  | 0  |     }  | 
2206  | 0  |     else  | 
2207  | 0  |     { | 
2208  | 0  |         bCheckLongitude = false;  | 
2209  | 0  |     }  | 
2210  | 0  | } Unexecuted instantiation: void netCDFRasterBand::CheckData<signed char>(void*, void*, unsigned long, unsigned long, bool) Unexecuted instantiation: void netCDFRasterBand::CheckData<unsigned char>(void*, void*, unsigned long, unsigned long, bool) Unexecuted instantiation: void netCDFRasterBand::CheckData<short>(void*, void*, unsigned long, unsigned long, bool) Unexecuted instantiation: void netCDFRasterBand::CheckData<unsigned short>(void*, void*, unsigned long, unsigned long, bool) Unexecuted instantiation: void netCDFRasterBand::CheckData<int>(void*, void*, unsigned long, unsigned long, bool) Unexecuted instantiation: void netCDFRasterBand::CheckData<float>(void*, void*, unsigned long, unsigned long, bool) Unexecuted instantiation: void netCDFRasterBand::CheckData<double>(void*, void*, unsigned long, unsigned long, bool) Unexecuted instantiation: void netCDFRasterBand::CheckData<unsigned int>(void*, void*, unsigned long, unsigned long, bool) Unexecuted instantiation: void netCDFRasterBand::CheckData<long>(void*, void*, unsigned long, unsigned long, bool) Unexecuted instantiation: void netCDFRasterBand::CheckData<unsigned long>(void*, void*, unsigned long, unsigned long, bool)  | 
2211  |  |  | 
2212  |  | /************************************************************************/  | 
2213  |  | /*                             CheckDataCpx()                              */  | 
2214  |  | /************************************************************************/  | 
2215  |  | template <class T>  | 
2216  |  | void netCDFRasterBand::CheckDataCpx(void *pImage, void *pImageNC,  | 
2217  |  |                                     size_t nTmpBlockXSize,  | 
2218  |  |                                     size_t nTmpBlockYSize, bool bCheckIsNan)  | 
2219  | 0  | { | 
2220  | 0  |     CPLAssert(pImage != nullptr && pImageNC != nullptr);  | 
2221  |  |  | 
2222  |  |     // If this block is not a full block (in the x axis), we need to re-arrange  | 
2223  |  |     // the data this is because partial blocks are not arranged the same way in  | 
2224  |  |     // netcdf and gdal.  | 
2225  | 0  |     if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))  | 
2226  | 0  |     { | 
2227  | 0  |         T *ptrWrite = static_cast<T *>(pImage);  | 
2228  | 0  |         T *ptrRead = static_cast<T *>(pImageNC);  | 
2229  | 0  |         for (size_t j = 0; j < nTmpBlockYSize; j++,  | 
2230  | 0  |                     ptrWrite += (2 * nBlockXSize),  | 
2231  | 0  |                     ptrRead += (2 * nTmpBlockXSize))  | 
2232  | 0  |         { | 
2233  | 0  |             memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T) * 2);  | 
2234  | 0  |         }  | 
2235  | 0  |     }  | 
2236  |  |  | 
2237  |  |     // Is valid data checking needed or requested?  | 
2238  | 0  |     if (bValidRangeValid || bCheckIsNan)  | 
2239  | 0  |     { | 
2240  | 0  |         T *ptrImage = static_cast<T *>(pImage);  | 
2241  | 0  |         for (size_t j = 0; j < nTmpBlockYSize; j++)  | 
2242  | 0  |         { | 
2243  |  |             // k moves along the gdal block, skipping the out-of-range pixels.  | 
2244  | 0  |             size_t k = 2 * j * nBlockXSize;  | 
2245  | 0  |             for (size_t i = 0; i < (2 * nTmpBlockXSize); i++, k++)  | 
2246  | 0  |             { | 
2247  |  |                 // Check for nodata and nan.  | 
2248  | 0  |                 if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))  | 
2249  | 0  |                     continue;  | 
2250  | 0  |                 if (bCheckIsNan && std::isnan((double)ptrImage[k]))  | 
2251  | 0  |                 { | 
2252  | 0  |                     ptrImage[k] = (T)m_dfNoDataValue;  | 
2253  | 0  |                     continue;  | 
2254  | 0  |                 }  | 
2255  |  |                 // Check for valid_range.  | 
2256  | 0  |                 if (bValidRangeValid)  | 
2257  | 0  |                 { | 
2258  | 0  |                     if (((adfValidRange[0] != m_dfNoDataValue) &&  | 
2259  | 0  |                          (ptrImage[k] < (T)adfValidRange[0])) ||  | 
2260  | 0  |                         ((adfValidRange[1] != m_dfNoDataValue) &&  | 
2261  | 0  |                          (ptrImage[k] > (T)adfValidRange[1])))  | 
2262  | 0  |                     { | 
2263  | 0  |                         ptrImage[k] = (T)m_dfNoDataValue;  | 
2264  | 0  |                     }  | 
2265  | 0  |                 }  | 
2266  | 0  |             }  | 
2267  | 0  |         }  | 
2268  | 0  |     }  | 
2269  | 0  | } Unexecuted instantiation: void netCDFRasterBand::CheckDataCpx<short>(void*, void*, unsigned long, unsigned long, bool) Unexecuted instantiation: void netCDFRasterBand::CheckDataCpx<int>(void*, void*, unsigned long, unsigned long, bool) Unexecuted instantiation: void netCDFRasterBand::CheckDataCpx<float>(void*, void*, unsigned long, unsigned long, bool) Unexecuted instantiation: void netCDFRasterBand::CheckDataCpx<double>(void*, void*, unsigned long, unsigned long, bool)  | 
2270  |  |  | 
2271  |  | /************************************************************************/  | 
2272  |  | /*                         FetchNetcdfChunk()                           */  | 
2273  |  | /************************************************************************/  | 
2274  |  |  | 
2275  |  | bool netCDFRasterBand::FetchNetcdfChunk(size_t xstart, size_t ystart,  | 
2276  |  |                                         void *pImage)  | 
2277  | 0  | { | 
2278  | 0  |     size_t start[MAX_NC_DIMS] = {}; | 
2279  | 0  |     size_t edge[MAX_NC_DIMS] = {}; | 
2280  |  | 
  | 
2281  | 0  |     start[nBandXPos] = xstart;  | 
2282  | 0  |     edge[nBandXPos] = nBlockXSize;  | 
2283  | 0  |     if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)  | 
2284  | 0  |         edge[nBandXPos] = nRasterXSize - start[nBandXPos];  | 
2285  | 0  |     if (nBandYPos >= 0)  | 
2286  | 0  |     { | 
2287  | 0  |         start[nBandYPos] = ystart;  | 
2288  | 0  |         edge[nBandYPos] = nBlockYSize;  | 
2289  | 0  |         if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)  | 
2290  | 0  |             edge[nBandYPos] = nRasterYSize - start[nBandYPos];  | 
2291  | 0  |     }  | 
2292  | 0  |     const size_t nYChunkSize = nBandYPos < 0 ? 1 : edge[nBandYPos];  | 
2293  |  | 
  | 
2294  |  | #ifdef NCDF_DEBUG  | 
2295  |  |     CPLDebug("GDAL_netCDF", "start={%ld,%ld} edge={%ld,%ld} bBottomUp=%d", | 
2296  |  |              start[nBandXPos], nBandYPos < 0 ? 0 : start[nBandYPos],  | 
2297  |  |              edge[nBandXPos], nYChunkSize, ((netCDFDataset *)poDS)->bBottomUp);  | 
2298  |  | #endif  | 
2299  |  | 
  | 
2300  | 0  |     int nd = 0;  | 
2301  | 0  |     nc_inq_varndims(cdfid, nZId, &nd);  | 
2302  | 0  |     if (nd == 3)  | 
2303  | 0  |     { | 
2304  | 0  |         start[panBandZPos[0]] = nLevel;  // z  | 
2305  | 0  |         edge[panBandZPos[0]] = 1;  | 
2306  | 0  |     }  | 
2307  |  |  | 
2308  |  |     // Compute multidimention band position.  | 
2309  |  |     //  | 
2310  |  |     // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)  | 
2311  |  |     // if Data[2,3,4,x,y]  | 
2312  |  |     //  | 
2313  |  |     //  BandPos0 = (nBand) / (3*4)  | 
2314  |  |     //  BandPos1 = (nBand - (3*4)) / (4)  | 
2315  |  |     //  BandPos2 = (nBand - (3*4)) % (4)  | 
2316  | 0  |     if (nd > 3)  | 
2317  | 0  |     { | 
2318  | 0  |         int Sum = -1;  | 
2319  | 0  |         int Taken = 0;  | 
2320  | 0  |         for (int i = 0; i < nd - 2; i++)  | 
2321  | 0  |         { | 
2322  | 0  |             if (i != nd - 2 - 1)  | 
2323  | 0  |             { | 
2324  | 0  |                 Sum = 1;  | 
2325  | 0  |                 for (int j = i + 1; j < nd - 2; j++)  | 
2326  | 0  |                 { | 
2327  | 0  |                     Sum *= panBandZLev[j];  | 
2328  | 0  |                 }  | 
2329  | 0  |                 start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);  | 
2330  | 0  |                 edge[panBandZPos[i]] = 1;  | 
2331  | 0  |             }  | 
2332  | 0  |             else  | 
2333  | 0  |             { | 
2334  | 0  |                 start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);  | 
2335  | 0  |                 edge[panBandZPos[i]] = 1;  | 
2336  | 0  |             }  | 
2337  | 0  |             Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;  | 
2338  | 0  |         }  | 
2339  | 0  |     }  | 
2340  |  |  | 
2341  |  |     // Make sure we are in data mode.  | 
2342  | 0  |     static_cast<netCDFDataset *>(poDS)->SetDefineMode(false);  | 
2343  |  |  | 
2344  |  |     // If this block is not a full block in the x axis, we need to  | 
2345  |  |     // re-arrange the data because partial blocks are not arranged the  | 
2346  |  |     // same way in netcdf and gdal, so we first we read the netcdf data at  | 
2347  |  |     // the end of the gdal block buffer then re-arrange rows in CheckData().  | 
2348  | 0  |     void *pImageNC = pImage;  | 
2349  | 0  |     if (edge[nBandXPos] != static_cast<size_t>(nBlockXSize))  | 
2350  | 0  |     { | 
2351  | 0  |         pImageNC = static_cast<GByte *>(pImage) +  | 
2352  | 0  |                    ((static_cast<size_t>(nBlockXSize) * nBlockYSize -  | 
2353  | 0  |                      edge[nBandXPos] * nYChunkSize) *  | 
2354  | 0  |                     (GDALGetDataTypeSize(eDataType) / 8));  | 
2355  | 0  |     }  | 
2356  |  |  | 
2357  |  |     // Read data according to type.  | 
2358  | 0  |     int status;  | 
2359  | 0  |     if (eDataType == GDT_Byte)  | 
2360  | 0  |     { | 
2361  | 0  |         if (bSignedData)  | 
2362  | 0  |         { | 
2363  | 0  |             status = nc_get_vara_schar(cdfid, nZId, start, edge,  | 
2364  | 0  |                                        static_cast<signed char *>(pImageNC));  | 
2365  | 0  |             if (status == NC_NOERR)  | 
2366  | 0  |                 CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],  | 
2367  | 0  |                                        nYChunkSize, false);  | 
2368  | 0  |         }  | 
2369  | 0  |         else  | 
2370  | 0  |         { | 
2371  | 0  |             status = nc_get_vara_uchar(cdfid, nZId, start, edge,  | 
2372  | 0  |                                        static_cast<unsigned char *>(pImageNC));  | 
2373  | 0  |             if (status == NC_NOERR)  | 
2374  | 0  |                 CheckData<unsigned char>(pImage, pImageNC, edge[nBandXPos],  | 
2375  | 0  |                                          nYChunkSize, false);  | 
2376  | 0  |         }  | 
2377  | 0  |     }  | 
2378  | 0  |     else if (eDataType == GDT_Int8)  | 
2379  | 0  |     { | 
2380  | 0  |         status = nc_get_vara_schar(cdfid, nZId, start, edge,  | 
2381  | 0  |                                    static_cast<signed char *>(pImageNC));  | 
2382  | 0  |         if (status == NC_NOERR)  | 
2383  | 0  |             CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],  | 
2384  | 0  |                                    nYChunkSize, false);  | 
2385  | 0  |     }  | 
2386  | 0  |     else if (nc_datatype == NC_SHORT)  | 
2387  | 0  |     { | 
2388  | 0  |         status = nc_get_vara_short(cdfid, nZId, start, edge,  | 
2389  | 0  |                                    static_cast<short *>(pImageNC));  | 
2390  | 0  |         if (status == NC_NOERR)  | 
2391  | 0  |         { | 
2392  | 0  |             if (eDataType == GDT_Int16)  | 
2393  | 0  |             { | 
2394  | 0  |                 CheckData<GInt16>(pImage, pImageNC, edge[nBandXPos],  | 
2395  | 0  |                                   nYChunkSize, false);  | 
2396  | 0  |             }  | 
2397  | 0  |             else  | 
2398  | 0  |             { | 
2399  | 0  |                 CheckData<GUInt16>(pImage, pImageNC, edge[nBandXPos],  | 
2400  | 0  |                                    nYChunkSize, false);  | 
2401  | 0  |             }  | 
2402  | 0  |         }  | 
2403  | 0  |     }  | 
2404  | 0  |     else if (eDataType == GDT_Int32)  | 
2405  | 0  |     { | 
2406  |  | #if SIZEOF_UNSIGNED_LONG == 4  | 
2407  |  |         status = nc_get_vara_long(cdfid, nZId, start, edge,  | 
2408  |  |                                   static_cast<long *>(pImageNC));  | 
2409  |  |         if (status == NC_NOERR)  | 
2410  |  |             CheckData<long>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,  | 
2411  |  |                             false);  | 
2412  |  | #else  | 
2413  | 0  |         status = nc_get_vara_int(cdfid, nZId, start, edge,  | 
2414  | 0  |                                  static_cast<int *>(pImageNC));  | 
2415  | 0  |         if (status == NC_NOERR)  | 
2416  | 0  |             CheckData<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,  | 
2417  | 0  |                            false);  | 
2418  | 0  | #endif  | 
2419  | 0  |     }  | 
2420  | 0  |     else if (eDataType == GDT_Float32)  | 
2421  | 0  |     { | 
2422  | 0  |         status = nc_get_vara_float(cdfid, nZId, start, edge,  | 
2423  | 0  |                                    static_cast<float *>(pImageNC));  | 
2424  | 0  |         if (status == NC_NOERR)  | 
2425  | 0  |             CheckData<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,  | 
2426  | 0  |                              true);  | 
2427  | 0  |     }  | 
2428  | 0  |     else if (eDataType == GDT_Float64)  | 
2429  | 0  |     { | 
2430  | 0  |         status = nc_get_vara_double(cdfid, nZId, start, edge,  | 
2431  | 0  |                                     static_cast<double *>(pImageNC));  | 
2432  | 0  |         if (status == NC_NOERR)  | 
2433  | 0  |             CheckData<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,  | 
2434  | 0  |                               true);  | 
2435  | 0  |     }  | 
2436  | 0  |     else if (eDataType == GDT_UInt16)  | 
2437  | 0  |     { | 
2438  | 0  |         status = nc_get_vara_ushort(cdfid, nZId, start, edge,  | 
2439  | 0  |                                     static_cast<unsigned short *>(pImageNC));  | 
2440  | 0  |         if (status == NC_NOERR)  | 
2441  | 0  |             CheckData<unsigned short>(pImage, pImageNC, edge[nBandXPos],  | 
2442  | 0  |                                       nYChunkSize, false);  | 
2443  | 0  |     }  | 
2444  | 0  |     else if (eDataType == GDT_UInt32)  | 
2445  | 0  |     { | 
2446  | 0  |         status = nc_get_vara_uint(cdfid, nZId, start, edge,  | 
2447  | 0  |                                   static_cast<unsigned int *>(pImageNC));  | 
2448  | 0  |         if (status == NC_NOERR)  | 
2449  | 0  |             CheckData<unsigned int>(pImage, pImageNC, edge[nBandXPos],  | 
2450  | 0  |                                     nYChunkSize, false);  | 
2451  | 0  |     }  | 
2452  | 0  |     else if (eDataType == GDT_Int64)  | 
2453  | 0  |     { | 
2454  | 0  |         status = nc_get_vara_longlong(cdfid, nZId, start, edge,  | 
2455  | 0  |                                       static_cast<long long *>(pImageNC));  | 
2456  | 0  |         if (status == NC_NOERR)  | 
2457  | 0  |             CheckData<std::int64_t>(pImage, pImageNC, edge[nBandXPos],  | 
2458  | 0  |                                     nYChunkSize, false);  | 
2459  | 0  |     }  | 
2460  | 0  |     else if (eDataType == GDT_UInt64)  | 
2461  | 0  |     { | 
2462  | 0  |         status =  | 
2463  | 0  |             nc_get_vara_ulonglong(cdfid, nZId, start, edge,  | 
2464  | 0  |                                   static_cast<unsigned long long *>(pImageNC));  | 
2465  | 0  |         if (status == NC_NOERR)  | 
2466  | 0  |             CheckData<std::uint64_t>(pImage, pImageNC, edge[nBandXPos],  | 
2467  | 0  |                                      nYChunkSize, false);  | 
2468  | 0  |     }  | 
2469  | 0  |     else if (eDataType == GDT_CInt16)  | 
2470  | 0  |     { | 
2471  | 0  |         status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);  | 
2472  | 0  |         if (status == NC_NOERR)  | 
2473  | 0  |             CheckDataCpx<short>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,  | 
2474  | 0  |                                 false);  | 
2475  | 0  |     }  | 
2476  | 0  |     else if (eDataType == GDT_CInt32)  | 
2477  | 0  |     { | 
2478  | 0  |         status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);  | 
2479  | 0  |         if (status == NC_NOERR)  | 
2480  | 0  |             CheckDataCpx<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,  | 
2481  | 0  |                               false);  | 
2482  | 0  |     }  | 
2483  | 0  |     else if (eDataType == GDT_CFloat32)  | 
2484  | 0  |     { | 
2485  | 0  |         status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);  | 
2486  | 0  |         if (status == NC_NOERR)  | 
2487  | 0  |             CheckDataCpx<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,  | 
2488  | 0  |                                 false);  | 
2489  | 0  |     }  | 
2490  | 0  |     else if (eDataType == GDT_CFloat64)  | 
2491  | 0  |     { | 
2492  | 0  |         status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);  | 
2493  | 0  |         if (status == NC_NOERR)  | 
2494  | 0  |             CheckDataCpx<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,  | 
2495  | 0  |                                  false);  | 
2496  | 0  |     }  | 
2497  |  |  | 
2498  | 0  |     else  | 
2499  | 0  |         status = NC_EBADTYPE;  | 
2500  |  | 
  | 
2501  | 0  |     if (status != NC_NOERR)  | 
2502  | 0  |     { | 
2503  | 0  |         CPLError(CE_Failure, CPLE_AppDefined,  | 
2504  | 0  |                  "netCDF chunk fetch failed: #%d (%s)", status,  | 
2505  | 0  |                  nc_strerror(status));  | 
2506  | 0  |         return false;  | 
2507  | 0  |     }  | 
2508  | 0  |     return true;  | 
2509  | 0  | }  | 
2510  |  |  | 
2511  |  | /************************************************************************/  | 
2512  |  | /*                             IReadBlock()                             */  | 
2513  |  | /************************************************************************/  | 
2514  |  |  | 
2515  |  | CPLErr netCDFRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,  | 
2516  |  |                                     void *pImage)  | 
2517  |  |  | 
2518  | 0  | { | 
2519  | 0  |     CPLMutexHolderD(&hNCMutex);  | 
2520  |  |  | 
2521  |  |     // Locate X, Y and Z position in the array.  | 
2522  |  | 
  | 
2523  | 0  |     size_t xstart = static_cast<size_t>(nBlockXOff) * nBlockXSize;  | 
2524  | 0  |     size_t ystart = 0;  | 
2525  |  |  | 
2526  |  |     // Check y order.  | 
2527  | 0  |     if (nBandYPos >= 0)  | 
2528  | 0  |     { | 
2529  | 0  |         auto poGDS = static_cast<netCDFDataset *>(poDS);  | 
2530  | 0  |         if (poGDS->bBottomUp)  | 
2531  | 0  |         { | 
2532  | 0  |             if (nBlockYSize == 1)  | 
2533  | 0  |             { | 
2534  | 0  |                 ystart = nRasterYSize - 1 - nBlockYOff;  | 
2535  | 0  |             }  | 
2536  | 0  |             else  | 
2537  | 0  |             { | 
2538  |  |                 // in GDAL space  | 
2539  | 0  |                 ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;  | 
2540  | 0  |                 const size_t yend =  | 
2541  | 0  |                     std::min(ystart + nBlockYSize - 1,  | 
2542  | 0  |                              static_cast<size_t>(nRasterYSize - 1));  | 
2543  |  |                 // in netCDF space  | 
2544  | 0  |                 const size_t nFirstChunkLine = nRasterYSize - 1 - yend;  | 
2545  | 0  |                 const size_t nLastChunkLine = nRasterYSize - 1 - ystart;  | 
2546  | 0  |                 const size_t nFirstChunkBlock = nFirstChunkLine / nBlockYSize;  | 
2547  | 0  |                 const size_t nLastChunkBlock = nLastChunkLine / nBlockYSize;  | 
2548  |  | 
  | 
2549  | 0  |                 const auto firstKey = netCDFDataset::ChunkKey(  | 
2550  | 0  |                     nBlockXOff, nFirstChunkBlock, nBand);  | 
2551  | 0  |                 const auto secondKey =  | 
2552  | 0  |                     netCDFDataset::ChunkKey(nBlockXOff, nLastChunkBlock, nBand);  | 
2553  |  |  | 
2554  |  |                 // Retrieve data from the one or 2 needed netCDF chunks  | 
2555  | 0  |                 std::shared_ptr<std::vector<GByte>> firstChunk;  | 
2556  | 0  |                 std::shared_ptr<std::vector<GByte>> secondChunk;  | 
2557  | 0  |                 if (poGDS->poChunkCache)  | 
2558  | 0  |                 { | 
2559  | 0  |                     poGDS->poChunkCache->tryGet(firstKey, firstChunk);  | 
2560  | 0  |                     if (firstKey != secondKey)  | 
2561  | 0  |                         poGDS->poChunkCache->tryGet(secondKey, secondChunk);  | 
2562  | 0  |                 }  | 
2563  | 0  |                 const size_t nChunkLineSize =  | 
2564  | 0  |                     static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *  | 
2565  | 0  |                     nBlockXSize;  | 
2566  | 0  |                 const size_t nChunkSize = nChunkLineSize * nBlockYSize;  | 
2567  | 0  |                 if (!firstChunk)  | 
2568  | 0  |                 { | 
2569  | 0  |                     firstChunk.reset(new std::vector<GByte>(nChunkSize));  | 
2570  | 0  |                     if (!FetchNetcdfChunk(xstart,  | 
2571  | 0  |                                           nFirstChunkBlock * nBlockYSize,  | 
2572  | 0  |                                           firstChunk.get()->data()))  | 
2573  | 0  |                         return CE_Failure;  | 
2574  | 0  |                     if (poGDS->poChunkCache)  | 
2575  | 0  |                         poGDS->poChunkCache->insert(firstKey, firstChunk);  | 
2576  | 0  |                 }  | 
2577  | 0  |                 if (!secondChunk && firstKey != secondKey)  | 
2578  | 0  |                 { | 
2579  | 0  |                     secondChunk.reset(new std::vector<GByte>(nChunkSize));  | 
2580  | 0  |                     if (!FetchNetcdfChunk(xstart, nLastChunkBlock * nBlockYSize,  | 
2581  | 0  |                                           secondChunk.get()->data()))  | 
2582  | 0  |                         return CE_Failure;  | 
2583  | 0  |                     if (poGDS->poChunkCache)  | 
2584  | 0  |                         poGDS->poChunkCache->insert(secondKey, secondChunk);  | 
2585  | 0  |                 }  | 
2586  |  |  | 
2587  |  |                 // Assemble netCDF chunks into GDAL block  | 
2588  | 0  |                 GByte *pabyImage = static_cast<GByte *>(pImage);  | 
2589  | 0  |                 const size_t nFirstChunkBlockLine =  | 
2590  | 0  |                     nFirstChunkBlock * nBlockYSize;  | 
2591  | 0  |                 const size_t nLastChunkBlockLine =  | 
2592  | 0  |                     nLastChunkBlock * nBlockYSize;  | 
2593  | 0  |                 for (size_t iLine = ystart; iLine <= yend; iLine++)  | 
2594  | 0  |                 { | 
2595  | 0  |                     const size_t nLineFromBottom = nRasterYSize - 1 - iLine;  | 
2596  | 0  |                     const size_t nChunkY = nLineFromBottom / nBlockYSize;  | 
2597  | 0  |                     if (nChunkY == nFirstChunkBlock)  | 
2598  | 0  |                     { | 
2599  | 0  |                         memcpy(pabyImage + nChunkLineSize * (iLine - ystart),  | 
2600  | 0  |                                firstChunk.get()->data() +  | 
2601  | 0  |                                    (nLineFromBottom - nFirstChunkBlockLine) *  | 
2602  | 0  |                                        nChunkLineSize,  | 
2603  | 0  |                                nChunkLineSize);  | 
2604  | 0  |                     }  | 
2605  | 0  |                     else  | 
2606  | 0  |                     { | 
2607  | 0  |                         CPLAssert(nChunkY == nLastChunkBlock);  | 
2608  | 0  |                         assert(secondChunk);  | 
2609  | 0  |                         memcpy(pabyImage + nChunkLineSize * (iLine - ystart),  | 
2610  | 0  |                                secondChunk.get()->data() +  | 
2611  | 0  |                                    (nLineFromBottom - nLastChunkBlockLine) *  | 
2612  | 0  |                                        nChunkLineSize,  | 
2613  | 0  |                                nChunkLineSize);  | 
2614  | 0  |                     }  | 
2615  | 0  |                 }  | 
2616  | 0  |                 return CE_None;  | 
2617  | 0  |             }  | 
2618  | 0  |         }  | 
2619  | 0  |         else  | 
2620  | 0  |         { | 
2621  | 0  |             ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;  | 
2622  | 0  |         }  | 
2623  | 0  |     }  | 
2624  |  |  | 
2625  | 0  |     return FetchNetcdfChunk(xstart, ystart, pImage) ? CE_None : CE_Failure;  | 
2626  | 0  | }  | 
2627  |  |  | 
2628  |  | /************************************************************************/  | 
2629  |  | /*                             IWriteBlock()                            */  | 
2630  |  | /************************************************************************/  | 
2631  |  |  | 
2632  |  | CPLErr netCDFRasterBand::IWriteBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,  | 
2633  |  |                                      void *pImage)  | 
2634  | 0  | { | 
2635  | 0  |     CPLMutexHolderD(&hNCMutex);  | 
2636  |  | 
  | 
2637  |  | #ifdef NCDF_DEBUG  | 
2638  |  |     if (nBlockYOff == 0 || (nBlockYOff == nRasterYSize - 1))  | 
2639  |  |         CPLDebug("GDAL_netCDF", | 
2640  |  |                  "netCDFRasterBand::IWriteBlock( %d, %d, ...) nBand=%d",  | 
2641  |  |                  nBlockXOff, nBlockYOff, nBand);  | 
2642  |  | #endif  | 
2643  |  | 
  | 
2644  | 0  |     int nd = 0;  | 
2645  | 0  |     nc_inq_varndims(cdfid, nZId, &nd);  | 
2646  |  |  | 
2647  |  |     // Locate X, Y and Z position in the array.  | 
2648  |  | 
  | 
2649  | 0  |     size_t start[MAX_NC_DIMS];  | 
2650  | 0  |     memset(start, 0, sizeof(start));  | 
2651  | 0  |     start[nBandXPos] = static_cast<size_t>(nBlockXOff) * nBlockXSize;  | 
2652  |  |  | 
2653  |  |     // check y order.  | 
2654  | 0  |     if (static_cast<netCDFDataset *>(poDS)->bBottomUp)  | 
2655  | 0  |     { | 
2656  | 0  |         if (nBlockYSize == 1)  | 
2657  | 0  |         { | 
2658  | 0  |             start[nBandYPos] = nRasterYSize - 1 - nBlockYOff;  | 
2659  | 0  |         }  | 
2660  | 0  |         else  | 
2661  | 0  |         { | 
2662  | 0  |             CPLError(CE_Failure, CPLE_AppDefined,  | 
2663  | 0  |                      "nBlockYSize = %d, only 1 supported when "  | 
2664  | 0  |                      "writing bottom-up dataset",  | 
2665  | 0  |                      nBlockYSize);  | 
2666  | 0  |             return CE_Failure;  | 
2667  | 0  |         }  | 
2668  | 0  |     }  | 
2669  | 0  |     else  | 
2670  | 0  |     { | 
2671  | 0  |         start[nBandYPos] = static_cast<size_t>(nBlockYOff) * nBlockYSize;  // y  | 
2672  | 0  |     }  | 
2673  |  |  | 
2674  | 0  |     size_t edge[MAX_NC_DIMS] = {}; | 
2675  |  | 
  | 
2676  | 0  |     edge[nBandXPos] = nBlockXSize;  | 
2677  | 0  |     if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)  | 
2678  | 0  |         edge[nBandXPos] = nRasterXSize - start[nBandXPos];  | 
2679  | 0  |     edge[nBandYPos] = nBlockYSize;  | 
2680  | 0  |     if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)  | 
2681  | 0  |         edge[nBandYPos] = nRasterYSize - start[nBandYPos];  | 
2682  |  | 
  | 
2683  | 0  |     if (nd == 3)  | 
2684  | 0  |     { | 
2685  | 0  |         start[panBandZPos[0]] = nLevel;  // z  | 
2686  | 0  |         edge[panBandZPos[0]] = 1;  | 
2687  | 0  |     }  | 
2688  |  |  | 
2689  |  |     // Compute multidimention band position.  | 
2690  |  |     //  | 
2691  |  |     // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)  | 
2692  |  |     // if Data[2,3,4,x,y]  | 
2693  |  |     //  | 
2694  |  |     //  BandPos0 = (nBand) / (3*4)  | 
2695  |  |     //  BandPos1 = (nBand - (3*4)) / (4)  | 
2696  |  |     //  BandPos2 = (nBand - (3*4)) % (4)  | 
2697  | 0  |     if (nd > 3)  | 
2698  | 0  |     { | 
2699  | 0  |         int Sum = -1;  | 
2700  | 0  |         int Taken = 0;  | 
2701  | 0  |         for (int i = 0; i < nd - 2; i++)  | 
2702  | 0  |         { | 
2703  | 0  |             if (i != nd - 2 - 1)  | 
2704  | 0  |             { | 
2705  | 0  |                 Sum = 1;  | 
2706  | 0  |                 for (int j = i + 1; j < nd - 2; j++)  | 
2707  | 0  |                 { | 
2708  | 0  |                     Sum *= panBandZLev[j];  | 
2709  | 0  |                 }  | 
2710  | 0  |                 start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);  | 
2711  | 0  |                 edge[panBandZPos[i]] = 1;  | 
2712  | 0  |             }  | 
2713  | 0  |             else  | 
2714  | 0  |             { | 
2715  | 0  |                 start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);  | 
2716  | 0  |                 edge[panBandZPos[i]] = 1;  | 
2717  | 0  |             }  | 
2718  | 0  |             Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;  | 
2719  | 0  |         }  | 
2720  | 0  |     }  | 
2721  |  |  | 
2722  |  |     // Make sure we are in data mode.  | 
2723  | 0  |     static_cast<netCDFDataset *>(poDS)->SetDefineMode(false);  | 
2724  |  |  | 
2725  |  |     // Copy data according to type.  | 
2726  | 0  |     int status = 0;  | 
2727  | 0  |     if (eDataType == GDT_Byte)  | 
2728  | 0  |     { | 
2729  | 0  |         if (bSignedData)  | 
2730  | 0  |             status = nc_put_vara_schar(cdfid, nZId, start, edge,  | 
2731  | 0  |                                        static_cast<signed char *>(pImage));  | 
2732  | 0  |         else  | 
2733  | 0  |             status = nc_put_vara_uchar(cdfid, nZId, start, edge,  | 
2734  | 0  |                                        static_cast<unsigned char *>(pImage));  | 
2735  | 0  |     }  | 
2736  | 0  |     else if (eDataType == GDT_Int8)  | 
2737  | 0  |     { | 
2738  | 0  |         status = nc_put_vara_schar(cdfid, nZId, start, edge,  | 
2739  | 0  |                                    static_cast<signed char *>(pImage));  | 
2740  | 0  |     }  | 
2741  | 0  |     else if (nc_datatype == NC_SHORT)  | 
2742  | 0  |     { | 
2743  | 0  |         status = nc_put_vara_short(cdfid, nZId, start, edge,  | 
2744  | 0  |                                    static_cast<short *>(pImage));  | 
2745  | 0  |     }  | 
2746  | 0  |     else if (eDataType == GDT_Int32)  | 
2747  | 0  |     { | 
2748  | 0  |         status = nc_put_vara_int(cdfid, nZId, start, edge,  | 
2749  | 0  |                                  static_cast<int *>(pImage));  | 
2750  | 0  |     }  | 
2751  | 0  |     else if (eDataType == GDT_Float32)  | 
2752  | 0  |     { | 
2753  | 0  |         status = nc_put_vara_float(cdfid, nZId, start, edge,  | 
2754  | 0  |                                    static_cast<float *>(pImage));  | 
2755  | 0  |     }  | 
2756  | 0  |     else if (eDataType == GDT_Float64)  | 
2757  | 0  |     { | 
2758  | 0  |         status = nc_put_vara_double(cdfid, nZId, start, edge,  | 
2759  | 0  |                                     static_cast<double *>(pImage));  | 
2760  | 0  |     }  | 
2761  | 0  |     else if (eDataType == GDT_UInt16 &&  | 
2762  | 0  |              static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)  | 
2763  | 0  |     { | 
2764  | 0  |         status = nc_put_vara_ushort(cdfid, nZId, start, edge,  | 
2765  | 0  |                                     static_cast<unsigned short *>(pImage));  | 
2766  | 0  |     }  | 
2767  | 0  |     else if (eDataType == GDT_UInt32 &&  | 
2768  | 0  |              static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)  | 
2769  | 0  |     { | 
2770  | 0  |         status = nc_put_vara_uint(cdfid, nZId, start, edge,  | 
2771  | 0  |                                   static_cast<unsigned int *>(pImage));  | 
2772  | 0  |     }  | 
2773  | 0  |     else if (eDataType == GDT_UInt64 &&  | 
2774  | 0  |              static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)  | 
2775  | 0  |     { | 
2776  | 0  |         status =  | 
2777  | 0  |             nc_put_vara_ulonglong(cdfid, nZId, start, edge,  | 
2778  | 0  |                                   static_cast<unsigned long long *>(pImage));  | 
2779  | 0  |     }  | 
2780  | 0  |     else if (eDataType == GDT_Int64 &&  | 
2781  | 0  |              static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)  | 
2782  | 0  |     { | 
2783  | 0  |         status = nc_put_vara_longlong(cdfid, nZId, start, edge,  | 
2784  | 0  |                                       static_cast<long long *>(pImage));  | 
2785  | 0  |     }  | 
2786  | 0  |     else  | 
2787  | 0  |     { | 
2788  | 0  |         CPLError(CE_Failure, CPLE_NotSupported,  | 
2789  | 0  |                  "The NetCDF driver does not support GDAL data type %d",  | 
2790  | 0  |                  eDataType);  | 
2791  | 0  |         status = NC_EBADTYPE;  | 
2792  | 0  |     }  | 
2793  | 0  |     NCDF_ERR(status);  | 
2794  |  | 
  | 
2795  | 0  |     if (status != NC_NOERR)  | 
2796  | 0  |     { | 
2797  | 0  |         CPLError(CE_Failure, CPLE_AppDefined,  | 
2798  | 0  |                  "netCDF scanline write failed: %s", nc_strerror(status));  | 
2799  | 0  |         return CE_Failure;  | 
2800  | 0  |     }  | 
2801  |  |  | 
2802  | 0  |     return CE_None;  | 
2803  | 0  | }  | 
2804  |  |  | 
2805  |  | /************************************************************************/  | 
2806  |  | /* ==================================================================== */  | 
2807  |  | /*                              netCDFDataset                           */  | 
2808  |  | /* ==================================================================== */  | 
2809  |  | /************************************************************************/  | 
2810  |  |  | 
2811  |  | /************************************************************************/  | 
2812  |  | /*                           netCDFDataset()                            */  | 
2813  |  | /************************************************************************/  | 
2814  |  |  | 
2815  |  | netCDFDataset::netCDFDataset()  | 
2816  |  |     :  | 
2817  |  | // Basic dataset vars.  | 
2818  |  | #ifdef ENABLE_NCDUMP  | 
2819  | 399  |       bFileToDestroyAtClosing(false),  | 
2820  |  | #endif  | 
2821  | 399  |       cdfid(-1), nSubDatasets(0), papszSubDatasets(nullptr),  | 
2822  | 399  |       papszMetadata(nullptr), bBottomUp(true), eFormat(NCDF_FORMAT_NONE),  | 
2823  | 399  |       bIsGdalFile(false), bIsGdalCfFile(false), pszCFProjection(nullptr),  | 
2824  | 399  |       pszCFCoordinates(nullptr), nCFVersion(1.6), bSGSupport(false),  | 
2825  | 399  |       eMultipleLayerBehavior(SINGLE_LAYER), logCount(0), vcdf(this, cdfid),  | 
2826  | 399  |       GeometryScribe(vcdf, this->generateLogName()),  | 
2827  | 399  |       FieldScribe(vcdf, this->generateLogName()),  | 
2828  | 399  |       bufManager(CPLGetUsablePhysicalRAM() / 5),  | 
2829  |  |  | 
2830  |  |       // projection/GT.  | 
2831  | 399  |       nXDimID(-1), nYDimID(-1), bIsProjected(false),  | 
2832  | 399  |       bIsGeographic(false),  // Can be not projected, and also not geographic  | 
2833  |  |       // State vars.  | 
2834  | 399  |       bDefineMode(true), bAddedGridMappingRef(false),  | 
2835  |  |  | 
2836  |  |       // Create vars.  | 
2837  | 399  |       papszCreationOptions(nullptr), eCompress(NCDF_COMPRESS_NONE),  | 
2838  | 399  |       nZLevel(NCDF_DEFLATE_LEVEL), bChunking(false), nCreateMode(NC_CLOBBER),  | 
2839  | 399  |       bSignedData(true)  | 
2840  | 399  | { | 
2841  | 399  |     m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);  | 
2842  |  |  | 
2843  |  |     // Projection/GT.  | 
2844  | 399  |     m_adfGeoTransform[0] = 0.0;  | 
2845  | 399  |     m_adfGeoTransform[1] = 1.0;  | 
2846  | 399  |     m_adfGeoTransform[2] = 0.0;  | 
2847  | 399  |     m_adfGeoTransform[3] = 0.0;  | 
2848  | 399  |     m_adfGeoTransform[4] = 0.0;  | 
2849  | 399  |     m_adfGeoTransform[5] = 1.0;  | 
2850  |  |  | 
2851  |  |     // Set buffers  | 
2852  | 399  |     bufManager.addBuffer(&(GeometryScribe.getMemBuffer()));  | 
2853  | 399  |     bufManager.addBuffer(&(FieldScribe.getMemBuffer()));  | 
2854  | 399  | }  | 
2855  |  |  | 
2856  |  | /************************************************************************/  | 
2857  |  | /*                           ~netCDFDataset()                           */  | 
2858  |  | /************************************************************************/  | 
2859  |  |  | 
2860  |  | netCDFDataset::~netCDFDataset()  | 
2861  |  |  | 
2862  | 399  | { | 
2863  | 399  |     netCDFDataset::Close();  | 
2864  | 399  | }  | 
2865  |  |  | 
2866  |  | /************************************************************************/  | 
2867  |  | /*                              Close()                                 */  | 
2868  |  | /************************************************************************/  | 
2869  |  |  | 
2870  |  | CPLErr netCDFDataset::Close()  | 
2871  | 422  | { | 
2872  | 422  |     CPLErr eErr = CE_None;  | 
2873  | 422  |     if (nOpenFlags != OPEN_FLAGS_CLOSED)  | 
2874  | 399  |     { | 
2875  | 399  |         CPLMutexHolderD(&hNCMutex);  | 
2876  |  |  | 
2877  |  | #ifdef NCDF_DEBUG  | 
2878  |  |         CPLDebug("GDAL_netCDF", | 
2879  |  |                  "netCDFDataset::~netCDFDataset(), cdfid=%d filename=%s", cdfid,  | 
2880  |  |                  osFilename.c_str());  | 
2881  |  | #endif  | 
2882  |  |  | 
2883  |  |         // Write data related to geotransform  | 
2884  | 399  |         if (GetAccess() == GA_Update && !m_bAddedProjectionVarsData &&  | 
2885  | 399  |             (m_bHasProjection || m_bHasGeoTransform))  | 
2886  | 0  |         { | 
2887  |  |             // Ensure projection is written if GeoTransform OR Projection are  | 
2888  |  |             // missing.  | 
2889  | 0  |             if (!m_bAddedProjectionVarsDefs)  | 
2890  | 0  |             { | 
2891  | 0  |                 AddProjectionVars(true, nullptr, nullptr);  | 
2892  | 0  |             }  | 
2893  | 0  |             AddProjectionVars(false, nullptr, nullptr);  | 
2894  | 0  |         }  | 
2895  |  |  | 
2896  | 399  |         if (netCDFDataset::FlushCache(true) != CE_None)  | 
2897  | 0  |             eErr = CE_Failure;  | 
2898  |  |  | 
2899  | 399  |         if (GetAccess() == GA_Update && !SGCommitPendingTransaction())  | 
2900  | 0  |             eErr = CE_Failure;  | 
2901  |  |  | 
2902  | 399  |         for (size_t i = 0; i < apoVectorDatasets.size(); i++)  | 
2903  | 0  |             delete apoVectorDatasets[i];  | 
2904  |  |  | 
2905  |  |         // Make sure projection variable is written to band variable.  | 
2906  | 399  |         if (GetAccess() == GA_Update && !bAddedGridMappingRef)  | 
2907  | 0  |         { | 
2908  | 0  |             if (!AddGridMappingRef())  | 
2909  | 0  |                 eErr = CE_Failure;  | 
2910  | 0  |         }  | 
2911  |  |  | 
2912  | 399  |         CSLDestroy(papszMetadata);  | 
2913  | 399  |         CSLDestroy(papszSubDatasets);  | 
2914  | 399  |         CSLDestroy(papszCreationOptions);  | 
2915  |  |  | 
2916  | 399  |         CPLFree(pszCFProjection);  | 
2917  |  |  | 
2918  | 399  |         if (cdfid > 0)  | 
2919  | 399  |         { | 
2920  |  | #ifdef NCDF_DEBUG  | 
2921  |  |             CPLDebug("GDAL_netCDF", "calling nc_close( %d)", cdfid); | 
2922  |  | #endif  | 
2923  | 399  |             int status = GDAL_nc_close(cdfid);  | 
2924  | 399  | #ifdef ENABLE_UFFD  | 
2925  | 399  |             NETCDF_UFFD_UNMAP(pCtx);  | 
2926  | 399  | #endif  | 
2927  | 399  |             NCDF_ERR(status);  | 
2928  | 399  |             if (status != NC_NOERR)  | 
2929  | 0  |                 eErr = CE_Failure;  | 
2930  | 399  |         }  | 
2931  |  |  | 
2932  | 399  |         if (fpVSIMEM)  | 
2933  | 0  |             VSIFCloseL(fpVSIMEM);  | 
2934  |  |  | 
2935  | 399  | #ifdef ENABLE_NCDUMP  | 
2936  | 399  |         if (bFileToDestroyAtClosing)  | 
2937  | 0  |             VSIUnlink(osFilename);  | 
2938  | 399  | #endif  | 
2939  |  |  | 
2940  | 399  |         if (GDALPamDataset::Close() != CE_None)  | 
2941  | 0  |             eErr = CE_Failure;  | 
2942  | 399  |     }  | 
2943  | 422  |     return eErr;  | 
2944  | 422  | }  | 
2945  |  |  | 
2946  |  | /************************************************************************/  | 
2947  |  | /*                            SetDefineMode()                           */  | 
2948  |  | /************************************************************************/  | 
2949  |  | bool netCDFDataset::SetDefineMode(bool bNewDefineMode)  | 
2950  | 5.16k  | { | 
2951  |  |     // Do nothing if already in new define mode  | 
2952  |  |     // or if dataset is in read-only mode or if dataset is true NC4 dataset.  | 
2953  | 5.16k  |     if (bDefineMode == bNewDefineMode || GetAccess() == GA_ReadOnly ||  | 
2954  | 5.16k  |         eFormat == NCDF_FORMAT_NC4)  | 
2955  | 5.16k  |         return true;  | 
2956  |  |  | 
2957  | 0  |     CPLDebug("GDAL_netCDF", "SetDefineMode(%d) old=%d", | 
2958  | 0  |              static_cast<int>(bNewDefineMode), static_cast<int>(bDefineMode));  | 
2959  |  | 
  | 
2960  | 0  |     bDefineMode = bNewDefineMode;  | 
2961  |  | 
  | 
2962  | 0  |     int status;  | 
2963  | 0  |     if (bDefineMode)  | 
2964  | 0  |         status = nc_redef(cdfid);  | 
2965  | 0  |     else  | 
2966  | 0  |         status = nc_enddef(cdfid);  | 
2967  |  | 
  | 
2968  | 0  |     NCDF_ERR(status);  | 
2969  | 0  |     return status == NC_NOERR;  | 
2970  | 5.16k  | }  | 
2971  |  |  | 
2972  |  | /************************************************************************/  | 
2973  |  | /*                      GetMetadataDomainList()                         */  | 
2974  |  | /************************************************************************/  | 
2975  |  |  | 
2976  |  | char **netCDFDataset::GetMetadataDomainList()  | 
2977  | 0  | { | 
2978  | 0  |     char **papszDomains = BuildMetadataDomainList(  | 
2979  | 0  |         GDALDataset::GetMetadataDomainList(), TRUE, "SUBDATASETS", nullptr);  | 
2980  | 0  |     for (const auto &kv : m_oMapDomainToJSon)  | 
2981  | 0  |         papszDomains = CSLAddString(papszDomains, ("json:" + kv.first).c_str()); | 
2982  | 0  |     return papszDomains;  | 
2983  | 0  | }  | 
2984  |  |  | 
2985  |  | /************************************************************************/  | 
2986  |  | /*                            GetMetadata()                             */  | 
2987  |  | /************************************************************************/  | 
2988  |  | char **netCDFDataset::GetMetadata(const char *pszDomain)  | 
2989  | 0  | { | 
2990  | 0  |     if (pszDomain != nullptr && STARTS_WITH_CI(pszDomain, "SUBDATASETS"))  | 
2991  | 0  |         return papszSubDatasets;  | 
2992  |  |  | 
2993  | 0  |     if (pszDomain != nullptr && STARTS_WITH(pszDomain, "json:"))  | 
2994  | 0  |     { | 
2995  | 0  |         auto iter = m_oMapDomainToJSon.find(pszDomain + strlen("json:")); | 
2996  | 0  |         if (iter != m_oMapDomainToJSon.end())  | 
2997  | 0  |             return iter->second.List();  | 
2998  | 0  |     }  | 
2999  |  |  | 
3000  | 0  |     return GDALDataset::GetMetadata(pszDomain);  | 
3001  | 0  | }  | 
3002  |  |  | 
3003  |  | /************************************************************************/  | 
3004  |  | /*                        SetMetadataItem()                             */  | 
3005  |  | /************************************************************************/  | 
3006  |  |  | 
3007  |  | CPLErr netCDFDataset::SetMetadataItem(const char *pszName, const char *pszValue,  | 
3008  |  |                                       const char *pszDomain)  | 
3009  | 0  | { | 
3010  | 0  |     if (GetAccess() == GA_Update &&  | 
3011  | 0  |         (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)  | 
3012  | 0  |     { | 
3013  | 0  |         std::string osName(pszName);  | 
3014  |  |  | 
3015  |  |         // Same logic as in CopyMetadata()  | 
3016  | 0  |         if (STARTS_WITH(osName.c_str(), "NC_GLOBAL#"))  | 
3017  | 0  |             osName = osName.substr(strlen("NC_GLOBAL#")); | 
3018  | 0  |         else if (strchr(osName.c_str(), '#') == nullptr)  | 
3019  | 0  |             osName = "GDAL_" + osName;  | 
3020  |  | 
  | 
3021  | 0  |         if (STARTS_WITH(osName.c_str(), "NETCDF_DIM_") ||  | 
3022  | 0  |             strchr(osName.c_str(), '#') != nullptr)  | 
3023  | 0  |         { | 
3024  |  |             // do nothing  | 
3025  | 0  |             return CE_None;  | 
3026  | 0  |         }  | 
3027  | 0  |         else  | 
3028  | 0  |         { | 
3029  | 0  |             SetDefineMode(true);  | 
3030  |  | 
  | 
3031  | 0  |             if (!NCDFPutAttr(cdfid, NC_GLOBAL, osName.c_str(), pszValue))  | 
3032  | 0  |                 return CE_Failure;  | 
3033  | 0  |         }  | 
3034  | 0  |     }  | 
3035  |  |  | 
3036  | 0  |     return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);  | 
3037  | 0  | }  | 
3038  |  |  | 
3039  |  | /************************************************************************/  | 
3040  |  | /*                          SetMetadata()                               */  | 
3041  |  | /************************************************************************/  | 
3042  |  |  | 
3043  |  | CPLErr netCDFDataset::SetMetadata(char **papszMD, const char *pszDomain)  | 
3044  | 0  | { | 
3045  | 0  |     if (GetAccess() == GA_Update &&  | 
3046  | 0  |         (pszDomain == nullptr || pszDomain[0] == '\0'))  | 
3047  | 0  |     { | 
3048  |  |         // We don't handle metadata item removal for now  | 
3049  | 0  |         for (const char *const *papszIter = papszMD; papszIter && *papszIter;  | 
3050  | 0  |              ++papszIter)  | 
3051  | 0  |         { | 
3052  | 0  |             char *pszName = nullptr;  | 
3053  | 0  |             const char *pszValue = CPLParseNameValue(*papszIter, &pszName);  | 
3054  | 0  |             if (pszName && pszValue)  | 
3055  | 0  |                 SetMetadataItem(pszName, pszValue);  | 
3056  | 0  |             CPLFree(pszName);  | 
3057  | 0  |         }  | 
3058  | 0  |         return CE_None;  | 
3059  | 0  |     }  | 
3060  | 0  |     return GDALPamDataset::SetMetadata(papszMD, pszDomain);  | 
3061  | 0  | }  | 
3062  |  |  | 
3063  |  | /************************************************************************/  | 
3064  |  | /*                          GetSpatialRef()                             */  | 
3065  |  | /************************************************************************/  | 
3066  |  |  | 
3067  |  | const OGRSpatialReference *netCDFDataset::GetSpatialRef() const  | 
3068  | 0  | { | 
3069  | 0  |     if (m_bHasProjection)  | 
3070  | 0  |         return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;  | 
3071  |  |  | 
3072  | 0  |     return GDALPamDataset::GetSpatialRef();  | 
3073  | 0  | }  | 
3074  |  |  | 
3075  |  | /************************************************************************/  | 
3076  |  | /*                           FetchCopyParam()                            */  | 
3077  |  | /************************************************************************/  | 
3078  |  |  | 
3079  |  | double netCDFDataset::FetchCopyParam(const char *pszGridMappingValue,  | 
3080  |  |                                      const char *pszParam, double dfDefault,  | 
3081  |  |                                      bool *pbFound)  | 
3082  |  |  | 
3083  | 0  | { | 
3084  | 0  |     char *pszTemp =  | 
3085  | 0  |         CPLStrdup(CPLSPrintf("%s#%s", pszGridMappingValue, pszParam)); | 
3086  | 0  |     const char *pszValue = CSLFetchNameValue(papszMetadata, pszTemp);  | 
3087  | 0  |     CPLFree(pszTemp);  | 
3088  |  | 
  | 
3089  | 0  |     if (pbFound)  | 
3090  | 0  |     { | 
3091  | 0  |         *pbFound = (pszValue != nullptr);  | 
3092  | 0  |     }  | 
3093  |  | 
  | 
3094  | 0  |     if (pszValue)  | 
3095  | 0  |     { | 
3096  | 0  |         return CPLAtofM(pszValue);  | 
3097  | 0  |     }  | 
3098  |  |  | 
3099  | 0  |     return dfDefault;  | 
3100  | 0  | }  | 
3101  |  |  | 
3102  |  | /************************************************************************/  | 
3103  |  | /*                           FetchStandardParallels()                   */  | 
3104  |  | /************************************************************************/  | 
3105  |  |  | 
3106  |  | std::vector<std::string>  | 
3107  |  | netCDFDataset::FetchStandardParallels(const char *pszGridMappingValue)  | 
3108  | 0  | { | 
3109  |  |     // cf-1.0 tags  | 
3110  | 0  |     const char *pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL);  | 
3111  |  | 
  | 
3112  | 0  |     std::vector<std::string> ret;  | 
3113  | 0  |     if (pszValue != nullptr)  | 
3114  | 0  |     { | 
3115  | 0  |         CPLStringList aosValues;  | 
3116  | 0  |         if (pszValue[0] != '{' && | 
3117  | 0  |             CPLString(pszValue).Trim().find(' ') != std::string::npos) | 
3118  | 0  |         { | 
3119  |  |             // Some files like  | 
3120  |  |             // ftp://data.knmi.nl/download/KNW-NetCDF-3D/1.0/noversion/2013/11/14/KNW-1.0_H37-ERA_NL_20131114.nc  | 
3121  |  |             // do not use standard formatting for arrays, but just space  | 
3122  |  |             // separated syntax  | 
3123  | 0  |             aosValues = CSLTokenizeString2(pszValue, " ", 0);  | 
3124  | 0  |         }  | 
3125  | 0  |         else  | 
3126  | 0  |         { | 
3127  | 0  |             aosValues = NCDFTokenizeArray(pszValue);  | 
3128  | 0  |         }  | 
3129  | 0  |         for (int i = 0; i < aosValues.size(); i++)  | 
3130  | 0  |         { | 
3131  | 0  |             ret.push_back(aosValues[i]);  | 
3132  | 0  |         }  | 
3133  | 0  |     }  | 
3134  |  |     // Try gdal tags.  | 
3135  | 0  |     else  | 
3136  | 0  |     { | 
3137  | 0  |         pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_1);  | 
3138  |  | 
  | 
3139  | 0  |         if (pszValue != nullptr)  | 
3140  | 0  |             ret.push_back(pszValue);  | 
3141  |  | 
  | 
3142  | 0  |         pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_2);  | 
3143  |  | 
  | 
3144  | 0  |         if (pszValue != nullptr)  | 
3145  | 0  |             ret.push_back(pszValue);  | 
3146  | 0  |     }  | 
3147  |  | 
  | 
3148  | 0  |     return ret;  | 
3149  | 0  | }  | 
3150  |  |  | 
3151  |  | /************************************************************************/  | 
3152  |  | /*                           FetchAttr()                                */  | 
3153  |  | /************************************************************************/  | 
3154  |  |  | 
3155  |  | const char *netCDFDataset::FetchAttr(const char *pszVarFullName,  | 
3156  |  |                                      const char *pszAttr)  | 
3157  |  |  | 
3158  | 115  | { | 
3159  | 115  |     char *pszKey = CPLStrdup(CPLSPrintf("%s#%s", pszVarFullName, pszAttr)); | 
3160  | 115  |     const char *pszValue = CSLFetchNameValue(papszMetadata, pszKey);  | 
3161  | 115  |     CPLFree(pszKey);  | 
3162  | 115  |     return pszValue;  | 
3163  | 115  | }  | 
3164  |  |  | 
3165  |  | const char *netCDFDataset::FetchAttr(int nGroupId, int nVarId,  | 
3166  |  |                                      const char *pszAttr)  | 
3167  |  |  | 
3168  | 69  | { | 
3169  | 69  |     char *pszVarFullName = nullptr;  | 
3170  | 69  |     NCDFGetVarFullName(nGroupId, nVarId, &pszVarFullName);  | 
3171  | 69  |     const char *pszValue = FetchAttr(pszVarFullName, pszAttr);  | 
3172  | 69  |     CPLFree(pszVarFullName);  | 
3173  | 69  |     return pszValue;  | 
3174  | 69  | }  | 
3175  |  |  | 
3176  |  | /************************************************************************/  | 
3177  |  | /*                       IsDifferenceBelow()                            */  | 
3178  |  | /************************************************************************/  | 
3179  |  |  | 
3180  |  | static bool IsDifferenceBelow(double dfA, double dfB, double dfError)  | 
3181  | 0  | { | 
3182  | 0  |     const double dfAbsDiff = fabs(dfA - dfB);  | 
3183  | 0  |     return dfAbsDiff <= dfError;  | 
3184  | 0  | }  | 
3185  |  |  | 
3186  |  | /************************************************************************/  | 
3187  |  | /*                      SetProjectionFromVar()                          */  | 
3188  |  | /************************************************************************/  | 
3189  |  | void netCDFDataset::SetProjectionFromVar(  | 
3190  |  |     int nGroupId, int nVarId, bool bReadSRSOnly, const char *pszGivenGM,  | 
3191  |  |     std::string *returnProjStr, nccfdriver::SGeometry_Reader *sg,  | 
3192  |  |     std::vector<std::string> *paosRemovedMDItems)  | 
3193  | 23  | { | 
3194  | 23  |     bool bGotGeogCS = false;  | 
3195  | 23  |     bool bGotCfSRS = false;  | 
3196  | 23  |     bool bGotCfWktSRS = false;  | 
3197  | 23  |     bool bGotGdalSRS = false;  | 
3198  | 23  |     bool bGotCfGT = false;  | 
3199  | 23  |     bool bGotGdalGT = false;  | 
3200  |  |  | 
3201  |  |     // These values from CF metadata.  | 
3202  | 23  |     OGRSpatialReference oSRS;  | 
3203  | 23  |     oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);  | 
3204  | 23  |     size_t xdim = nRasterXSize;  | 
3205  | 23  |     size_t ydim = nRasterYSize;  | 
3206  |  |  | 
3207  |  |     // These values from GDAL metadata.  | 
3208  | 23  |     const char *pszWKT = nullptr;  | 
3209  | 23  |     const char *pszGeoTransform = nullptr;  | 
3210  |  |  | 
3211  | 23  |     netCDFDataset *poDS = this;  // Perhaps this should be removed for clarity.  | 
3212  |  |  | 
3213  | 23  |     CPLDebug("GDAL_netCDF", "\n=====\nSetProjectionFromVar( %d, %d)", nGroupId, | 
3214  | 23  |              nVarId);  | 
3215  |  |  | 
3216  |  |     // Get x/y range information.  | 
3217  |  |  | 
3218  |  |     // Temp variables to use in SetGeoTransform() and SetProjection().  | 
3219  | 23  |     double adfTempGeoTransform[6] = {0.0, 1.0, 0.0, 0.0, 0.0, 1.0}; | 
3220  |  |  | 
3221  |  |     // Look for grid_mapping metadata.  | 
3222  | 23  |     const char *pszValue = pszGivenGM;  | 
3223  | 23  |     CPLString osTmpGridMapping;  // let is in this outer scope as pszValue may  | 
3224  |  |         // point to it  | 
3225  | 23  |     if (pszValue == nullptr)  | 
3226  | 23  |     { | 
3227  | 23  |         pszValue = FetchAttr(nGroupId, nVarId, CF_GRD_MAPPING);  | 
3228  | 23  |         if (pszValue && strchr(pszValue, ':') && strchr(pszValue, ' '))  | 
3229  | 0  |         { | 
3230  |  |             // Expanded form of grid_mapping  | 
3231  |  |             // e.g. "crsOSGB: x y crsWGS84: lat lon"  | 
3232  |  |             // Pickup the grid_mapping whose coordinates are dimensions of the  | 
3233  |  |             // variable  | 
3234  | 0  |             CPLStringList aosTokens(CSLTokenizeString2(pszValue, " ", 0));  | 
3235  | 0  |             if ((aosTokens.size() % 3) == 0)  | 
3236  | 0  |             { | 
3237  | 0  |                 for (int i = 0; i < aosTokens.size() / 3; i++)  | 
3238  | 0  |                 { | 
3239  | 0  |                     if (CSLFindString(poDS->papszDimName,  | 
3240  | 0  |                                       aosTokens[3 * i + 1]) >= 0 &&  | 
3241  | 0  |                         CSLFindString(poDS->papszDimName,  | 
3242  | 0  |                                       aosTokens[3 * i + 2]) >= 0)  | 
3243  | 0  |                     { | 
3244  | 0  |                         osTmpGridMapping = aosTokens[3 * i];  | 
3245  | 0  |                         if (!osTmpGridMapping.empty() &&  | 
3246  | 0  |                             osTmpGridMapping.back() == ':')  | 
3247  | 0  |                         { | 
3248  | 0  |                             osTmpGridMapping.resize(osTmpGridMapping.size() -  | 
3249  | 0  |                                                     1);  | 
3250  | 0  |                         }  | 
3251  | 0  |                         pszValue = osTmpGridMapping.c_str();  | 
3252  | 0  |                         break;  | 
3253  | 0  |                     }  | 
3254  | 0  |                 }  | 
3255  | 0  |             }  | 
3256  | 0  |         }  | 
3257  | 23  |     }  | 
3258  | 23  |     char *pszGridMappingValue = CPLStrdup(pszValue ? pszValue : "");  | 
3259  |  |  | 
3260  | 23  |     if (!EQUAL(pszGridMappingValue, ""))  | 
3261  | 0  |     { | 
3262  |  |         // Read grid_mapping metadata.  | 
3263  | 0  |         int nProjGroupID = -1;  | 
3264  | 0  |         int nProjVarID = -1;  | 
3265  | 0  |         if (NCDFResolveVar(nGroupId, pszGridMappingValue, &nProjGroupID,  | 
3266  | 0  |                            &nProjVarID) == CE_None)  | 
3267  | 0  |         { | 
3268  | 0  |             poDS->ReadAttributes(nProjGroupID, nProjVarID);  | 
3269  |  |  | 
3270  |  |             // Look for GDAL spatial_ref and GeoTransform within grid_mapping.  | 
3271  | 0  |             CPLFree(pszGridMappingValue);  | 
3272  | 0  |             pszGridMappingValue = nullptr;  | 
3273  | 0  |             NCDFGetVarFullName(nProjGroupID, nProjVarID, &pszGridMappingValue);  | 
3274  | 0  |             if (pszGridMappingValue)  | 
3275  | 0  |             { | 
3276  | 0  |                 CPLDebug("GDAL_netCDF", "got grid_mapping %s", | 
3277  | 0  |                          pszGridMappingValue);  | 
3278  | 0  |                 pszWKT = FetchAttr(pszGridMappingValue, NCDF_SPATIAL_REF);  | 
3279  | 0  |                 if (!pszWKT)  | 
3280  | 0  |                 { | 
3281  | 0  |                     pszWKT = FetchAttr(pszGridMappingValue, NCDF_CRS_WKT);  | 
3282  | 0  |                 }  | 
3283  | 0  |                 else  | 
3284  | 0  |                 { | 
3285  | 0  |                     bGotGdalSRS = true;  | 
3286  | 0  |                     CPLDebug("GDAL_netCDF", "setting WKT from GDAL"); | 
3287  | 0  |                 }  | 
3288  | 0  |                 if (pszWKT)  | 
3289  | 0  |                 { | 
3290  | 0  |                     if (!bGotGdalSRS)  | 
3291  | 0  |                     { | 
3292  | 0  |                         bGotCfWktSRS = true;  | 
3293  | 0  |                         CPLDebug("GDAL_netCDF", "setting WKT from CF"); | 
3294  | 0  |                     }  | 
3295  | 0  |                     if (returnProjStr != nullptr)  | 
3296  | 0  |                     { | 
3297  | 0  |                         (*returnProjStr) = std::string(pszWKT);  | 
3298  | 0  |                     }  | 
3299  | 0  |                     else  | 
3300  | 0  |                     { | 
3301  | 0  |                         m_bAddedProjectionVarsDefs = true;  | 
3302  | 0  |                         m_bAddedProjectionVarsData = true;  | 
3303  | 0  |                         OGRSpatialReference oSRSTmp;  | 
3304  | 0  |                         oSRSTmp.SetAxisMappingStrategy(  | 
3305  | 0  |                             OAMS_TRADITIONAL_GIS_ORDER);  | 
3306  | 0  |                         oSRSTmp.importFromWkt(pszWKT);  | 
3307  | 0  |                         SetSpatialRefNoUpdate(&oSRSTmp);  | 
3308  | 0  |                     }  | 
3309  | 0  |                     pszGeoTransform =  | 
3310  | 0  |                         FetchAttr(pszGridMappingValue, NCDF_GEOTRANSFORM);  | 
3311  | 0  |                 }  | 
3312  | 0  |             }  | 
3313  | 0  |             else  | 
3314  | 0  |             { | 
3315  | 0  |                 pszGridMappingValue = CPLStrdup(""); | 
3316  | 0  |             }  | 
3317  | 0  |         }  | 
3318  | 0  |     }  | 
3319  |  |  | 
3320  |  |     // Get information about the file.  | 
3321  |  |     //  | 
3322  |  |     // Was this file created by the GDAL netcdf driver?  | 
3323  |  |     // Was this file created by the newer (CF-conformant) driver?  | 
3324  |  |     //  | 
3325  |  |     // 1) If GDAL netcdf metadata is set, and version >= 1.9,  | 
3326  |  |     //    it was created with the new driver  | 
3327  |  |     // 2) Else, if spatial_ref and GeoTransform are present in the  | 
3328  |  |     //    grid_mapping variable, it was created by the old driver  | 
3329  | 23  |     pszValue = FetchAttr("NC_GLOBAL", "GDAL"); | 
3330  |  |  | 
3331  | 23  |     if (pszValue && NCDFIsGDALVersionGTE(pszValue, 1900))  | 
3332  | 0  |     { | 
3333  | 0  |         bIsGdalFile = true;  | 
3334  | 0  |         bIsGdalCfFile = true;  | 
3335  | 0  |     }  | 
3336  | 23  |     else if (pszWKT != nullptr && pszGeoTransform != nullptr)  | 
3337  | 0  |     { | 
3338  | 0  |         bIsGdalFile = true;  | 
3339  | 0  |         bIsGdalCfFile = false;  | 
3340  | 0  |     }  | 
3341  |  |  | 
3342  |  |     // Set default bottom-up default value.  | 
3343  |  |     // Y axis dimension and absence of GT can modify this value.  | 
3344  |  |     // Override with Config option GDAL_NETCDF_BOTTOMUP.  | 
3345  |  |  | 
3346  |  |     // New driver is bottom-up by default.  | 
3347  | 23  |     if ((bIsGdalFile && !bIsGdalCfFile) || bSwitchedXY)  | 
3348  | 0  |         poDS->bBottomUp = false;  | 
3349  | 23  |     else  | 
3350  | 23  |         poDS->bBottomUp = true;  | 
3351  |  |  | 
3352  | 23  |     CPLDebug("GDAL_netCDF", | 
3353  | 23  |              "bIsGdalFile=%d bIsGdalCfFile=%d bSwitchedXY=%d bBottomUp=%d",  | 
3354  | 23  |              static_cast<int>(bIsGdalFile), static_cast<int>(bIsGdalCfFile),  | 
3355  | 23  |              static_cast<int>(bSwitchedXY), static_cast<int>(bBottomUp));  | 
3356  |  |  | 
3357  |  |     // Read projection coordinates.  | 
3358  |  |  | 
3359  | 23  |     int nGroupDimXID = -1;  | 
3360  | 23  |     int nVarDimXID = -1;  | 
3361  | 23  |     int nGroupDimYID = -1;  | 
3362  | 23  |     int nVarDimYID = -1;  | 
3363  | 23  |     if (sg != nullptr)  | 
3364  | 0  |     { | 
3365  | 0  |         nGroupDimXID = sg->get_ncID();  | 
3366  | 0  |         nGroupDimYID = sg->get_ncID();  | 
3367  | 0  |         nVarDimXID = sg->getNodeCoordVars()[0];  | 
3368  | 0  |         nVarDimYID = sg->getNodeCoordVars()[1];  | 
3369  | 0  |     }  | 
3370  |  |  | 
3371  | 23  |     if (!bReadSRSOnly)  | 
3372  | 0  |     { | 
3373  | 0  |         NCDFResolveVar(nGroupId, poDS->papszDimName[nXDimID], &nGroupDimXID,  | 
3374  | 0  |                        &nVarDimXID);  | 
3375  | 0  |         NCDFResolveVar(nGroupId, poDS->papszDimName[nYDimID], &nGroupDimYID,  | 
3376  | 0  |                        &nVarDimYID);  | 
3377  |  |         // TODO: if above resolving fails we should also search for coordinate  | 
3378  |  |         // variables without same name than dimension using the same resolving  | 
3379  |  |         // logic. This should handle for example NASA Ocean Color L2 products.  | 
3380  |  | 
  | 
3381  | 0  |         const bool bIgnoreXYAxisNameChecks =  | 
3382  | 0  |             CPLTestBool(CSLFetchNameValueDef(  | 
3383  | 0  |                 papszOpenOptions, "IGNORE_XY_AXIS_NAME_CHECKS",  | 
3384  | 0  |                 CPLGetConfigOption("GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS", | 
3385  | 0  |                                    "NO"))) ||  | 
3386  |  |             // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a res  | 
3387  |  |             // and transform attributes  | 
3388  | 0  |             (FetchAttr(nGroupId, nVarId, "res") != nullptr &&  | 
3389  | 0  |              FetchAttr(nGroupId, nVarId, "transform") != nullptr) ||  | 
3390  | 0  |             FetchAttr(nGroupId, NC_GLOBAL, "GMT_version") != nullptr;  | 
3391  |  |  | 
3392  |  |         // Check that they are 1D or 2D variables  | 
3393  | 0  |         if (nVarDimXID >= 0)  | 
3394  | 0  |         { | 
3395  | 0  |             int ndims = -1;  | 
3396  | 0  |             nc_inq_varndims(nGroupId, nVarDimXID, &ndims);  | 
3397  | 0  |             if (ndims == 0 || ndims > 2)  | 
3398  | 0  |                 nVarDimXID = -1;  | 
3399  | 0  |             else if (!bIgnoreXYAxisNameChecks)  | 
3400  | 0  |             { | 
3401  | 0  |                 if (!NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&  | 
3402  | 0  |                     !NCDFIsVarProjectionX(nGroupId, nVarDimXID, nullptr) &&  | 
3403  |  |                     // In case of inversion of X/Y  | 
3404  | 0  |                     !NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr) &&  | 
3405  | 0  |                     !NCDFIsVarProjectionY(nGroupId, nVarDimXID, nullptr))  | 
3406  | 0  |                 { | 
3407  | 0  |                     char szVarNameX[NC_MAX_NAME + 1];  | 
3408  | 0  |                     CPL_IGNORE_RET_VAL(  | 
3409  | 0  |                         nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));  | 
3410  | 0  |                     if (!(ndims == 1 &&  | 
3411  | 0  |                           (EQUAL(szVarNameX, CF_LONGITUDE_STD_NAME) ||  | 
3412  | 0  |                            EQUAL(szVarNameX, CF_LONGITUDE_VAR_NAME))))  | 
3413  | 0  |                     { | 
3414  | 0  |                         CPLDebug(  | 
3415  | 0  |                             "netCDF",  | 
3416  | 0  |                             "Georeferencing ignored due to non-specific "  | 
3417  | 0  |                             "enough X axis name. "  | 
3418  | 0  |                             "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "  | 
3419  | 0  |                             "as configuration option to bypass this check");  | 
3420  | 0  |                         nVarDimXID = -1;  | 
3421  | 0  |                     }  | 
3422  | 0  |                 }  | 
3423  | 0  |             }  | 
3424  | 0  |         }  | 
3425  |  | 
  | 
3426  | 0  |         if (nVarDimYID >= 0)  | 
3427  | 0  |         { | 
3428  | 0  |             int ndims = -1;  | 
3429  | 0  |             nc_inq_varndims(nGroupId, nVarDimYID, &ndims);  | 
3430  | 0  |             if (ndims == 0 || ndims > 2)  | 
3431  | 0  |                 nVarDimYID = -1;  | 
3432  | 0  |             else if (!bIgnoreXYAxisNameChecks)  | 
3433  | 0  |             { | 
3434  | 0  |                 if (!NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr) &&  | 
3435  | 0  |                     !NCDFIsVarProjectionY(nGroupId, nVarDimYID, nullptr) &&  | 
3436  |  |                     // In case of inversion of X/Y  | 
3437  | 0  |                     !NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&  | 
3438  | 0  |                     !NCDFIsVarProjectionX(nGroupId, nVarDimYID, nullptr))  | 
3439  | 0  |                 { | 
3440  | 0  |                     char szVarNameY[NC_MAX_NAME + 1];  | 
3441  | 0  |                     CPL_IGNORE_RET_VAL(  | 
3442  | 0  |                         nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));  | 
3443  | 0  |                     if (!(ndims == 1 &&  | 
3444  | 0  |                           (EQUAL(szVarNameY, CF_LATITUDE_STD_NAME) ||  | 
3445  | 0  |                            EQUAL(szVarNameY, CF_LATITUDE_VAR_NAME))))  | 
3446  | 0  |                     { | 
3447  | 0  |                         CPLDebug(  | 
3448  | 0  |                             "netCDF",  | 
3449  | 0  |                             "Georeferencing ignored due to non-specific "  | 
3450  | 0  |                             "enough Y axis name. "  | 
3451  | 0  |                             "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "  | 
3452  | 0  |                             "as configuration option to bypass this check");  | 
3453  | 0  |                         nVarDimYID = -1;  | 
3454  | 0  |                     }  | 
3455  | 0  |                 }  | 
3456  | 0  |             }  | 
3457  | 0  |         }  | 
3458  |  | 
  | 
3459  | 0  |         if ((nVarDimXID >= 0 && xdim == 1) || (nVarDimXID >= 0 && ydim == 1))  | 
3460  | 0  |         { | 
3461  | 0  |             CPLError(CE_Warning, CPLE_AppDefined,  | 
3462  | 0  |                      "1-pixel width/height files not supported, "  | 
3463  | 0  |                      "xdim: %ld ydim: %ld",  | 
3464  | 0  |                      static_cast<long>(xdim), static_cast<long>(ydim));  | 
3465  | 0  |             nVarDimXID = -1;  | 
3466  | 0  |             nVarDimYID = -1;  | 
3467  | 0  |         }  | 
3468  | 0  |     }  | 
3469  |  |  | 
3470  | 23  |     const char *pszUnits = nullptr;  | 
3471  | 23  |     if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)  | 
3472  | 0  |     { | 
3473  | 0  |         const char *pszUnitsX = FetchAttr(nGroupDimXID, nVarDimXID, "units");  | 
3474  | 0  |         const char *pszUnitsY = FetchAttr(nGroupDimYID, nVarDimYID, "units");  | 
3475  |  |         // Normalize degrees_east/degrees_north to degrees  | 
3476  |  |         // Cf https://github.com/OSGeo/gdal/issues/11009  | 
3477  | 0  |         if (pszUnitsX && EQUAL(pszUnitsX, "degrees_east"))  | 
3478  | 0  |             pszUnitsX = "degrees";  | 
3479  | 0  |         if (pszUnitsY && EQUAL(pszUnitsY, "degrees_north"))  | 
3480  | 0  |             pszUnitsY = "degrees";  | 
3481  |  | 
  | 
3482  | 0  |         if (pszUnitsX && pszUnitsY)  | 
3483  | 0  |         { | 
3484  | 0  |             if (EQUAL(pszUnitsX, pszUnitsY))  | 
3485  | 0  |                 pszUnits = pszUnitsX;  | 
3486  | 0  |             else if (!pszWKT && !EQUAL(pszGridMappingValue, ""))  | 
3487  | 0  |             { | 
3488  | 0  |                 CPLError(CE_Failure, CPLE_AppDefined,  | 
3489  | 0  |                          "X axis unit (%s) is different from Y axis "  | 
3490  | 0  |                          "unit (%s). SRS will ignore axis unit and be "  | 
3491  | 0  |                          "likely wrong.",  | 
3492  | 0  |                          pszUnitsX, pszUnitsY);  | 
3493  | 0  |             }  | 
3494  | 0  |         }  | 
3495  | 0  |         else if (pszUnitsX && !pszWKT && !EQUAL(pszGridMappingValue, ""))  | 
3496  | 0  |         { | 
3497  | 0  |             CPLError(CE_Failure, CPLE_AppDefined,  | 
3498  | 0  |                      "X axis unit is defined, but not Y one ."  | 
3499  | 0  |                      "SRS will ignore axis unit and be likely wrong.");  | 
3500  | 0  |         }  | 
3501  | 0  |         else if (pszUnitsY && !pszWKT && !EQUAL(pszGridMappingValue, ""))  | 
3502  | 0  |         { | 
3503  | 0  |             CPLError(CE_Failure, CPLE_AppDefined,  | 
3504  | 0  |                      "Y axis unit is defined, but not X one ."  | 
3505  | 0  |                      "SRS will ignore axis unit and be likely wrong.");  | 
3506  | 0  |         }  | 
3507  | 0  |     }  | 
3508  |  |  | 
3509  | 23  |     if (!pszWKT && !EQUAL(pszGridMappingValue, ""))  | 
3510  | 0  |     { | 
3511  | 0  |         CPLStringList aosGridMappingKeyValues;  | 
3512  | 0  |         const size_t nLenGridMappingValue = strlen(pszGridMappingValue);  | 
3513  | 0  |         for (const char *const *papszIter = papszMetadata;  | 
3514  | 0  |              papszIter && *papszIter; ++papszIter)  | 
3515  | 0  |         { | 
3516  | 0  |             if (STARTS_WITH(*papszIter, pszGridMappingValue) &&  | 
3517  | 0  |                 (*papszIter)[nLenGridMappingValue] == '#')  | 
3518  | 0  |             { | 
3519  | 0  |                 char *pszKey = nullptr;  | 
3520  | 0  |                 pszValue = CPLParseNameValue(  | 
3521  | 0  |                     *papszIter + nLenGridMappingValue + 1, &pszKey);  | 
3522  | 0  |                 if (pszKey && pszValue)  | 
3523  | 0  |                     aosGridMappingKeyValues.SetNameValue(pszKey, pszValue);  | 
3524  | 0  |                 CPLFree(pszKey);  | 
3525  | 0  |             }  | 
3526  | 0  |         }  | 
3527  |  | 
  | 
3528  | 0  |         bGotGeogCS = aosGridMappingKeyValues.FetchNameValue(  | 
3529  | 0  |                          CF_PP_SEMI_MAJOR_AXIS) != nullptr;  | 
3530  |  | 
  | 
3531  | 0  |         oSRS.importFromCF1(aosGridMappingKeyValues.List(), pszUnits);  | 
3532  | 0  |         bGotCfSRS = oSRS.IsGeographic() || oSRS.IsProjected();  | 
3533  | 0  |     }  | 
3534  | 23  |     else  | 
3535  | 23  |     { | 
3536  |  |         // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a "crs"  | 
3537  |  |         // attribute hold on the variable of interest that contains a PROJ.4  | 
3538  |  |         // string  | 
3539  | 23  |         pszValue = FetchAttr(nGroupId, nVarId, "crs");  | 
3540  | 23  |         if (pszValue &&  | 
3541  | 23  |             (strstr(pszValue, "+proj=") != nullptr ||  | 
3542  | 0  |              strstr(pszValue, "GEOGCS") != nullptr ||  | 
3543  | 0  |              strstr(pszValue, "PROJCS") != nullptr ||  | 
3544  | 0  |              strstr(pszValue, "EPSG:") != nullptr) &&  | 
3545  | 23  |             oSRS.SetFromUserInput(pszValue) == OGRERR_NONE)  | 
3546  | 0  |         { | 
3547  | 0  |             bGotCfSRS = true;  | 
3548  | 0  |         }  | 
3549  | 23  |     }  | 
3550  |  |  | 
3551  |  |     // Set Projection from CF.  | 
3552  | 23  |     double dfLinearUnitsConvFactor = 1.0;  | 
3553  | 23  |     if ((bGotGeogCS || bGotCfSRS))  | 
3554  | 0  |     { | 
3555  | 0  |         if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)  | 
3556  | 0  |         { | 
3557  |  |             // Set SRS Units.  | 
3558  |  |  | 
3559  |  |             // Check units for x and y.  | 
3560  | 0  |             if (oSRS.IsProjected())  | 
3561  | 0  |             { | 
3562  | 0  |                 dfLinearUnitsConvFactor = oSRS.GetLinearUnits(nullptr);  | 
3563  |  |  | 
3564  |  |                 // If the user doesn't ask to preserve the axis unit,  | 
3565  |  |                 // then normalize to metre  | 
3566  | 0  |                 if (dfLinearUnitsConvFactor != 1.0 &&  | 
3567  | 0  |                     !CPLFetchBool(GetOpenOptions(), "PRESERVE_AXIS_UNIT_IN_CRS",  | 
3568  | 0  |                                   false))  | 
3569  | 0  |                 { | 
3570  | 0  |                     oSRS.SetLinearUnits("metre", 1.0); | 
3571  | 0  |                     oSRS.SetAuthority("PROJCS|UNIT", "EPSG", 9001); | 
3572  | 0  |                 }  | 
3573  | 0  |                 else  | 
3574  | 0  |                 { | 
3575  | 0  |                     dfLinearUnitsConvFactor = 1.0;  | 
3576  | 0  |                 }  | 
3577  | 0  |             }  | 
3578  | 0  |         }  | 
3579  |  |  | 
3580  |  |         // Set projection.  | 
3581  | 0  |         char *pszTempProjection = nullptr;  | 
3582  | 0  |         oSRS.exportToWkt(&pszTempProjection);  | 
3583  | 0  |         if (pszTempProjection)  | 
3584  | 0  |         { | 
3585  | 0  |             CPLDebug("GDAL_netCDF", "setting WKT from CF"); | 
3586  | 0  |             if (returnProjStr != nullptr)  | 
3587  | 0  |             { | 
3588  | 0  |                 (*returnProjStr) = std::string(pszTempProjection);  | 
3589  | 0  |             }  | 
3590  | 0  |             else  | 
3591  | 0  |             { | 
3592  | 0  |                 m_bAddedProjectionVarsDefs = true;  | 
3593  | 0  |                 m_bAddedProjectionVarsData = true;  | 
3594  | 0  |                 SetSpatialRefNoUpdate(&oSRS);  | 
3595  | 0  |             }  | 
3596  | 0  |         }  | 
3597  | 0  |         CPLFree(pszTempProjection);  | 
3598  | 0  |     }  | 
3599  |  |  | 
3600  | 23  |     if (!bReadSRSOnly && (nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 &&  | 
3601  | 23  |         ydim > 0)  | 
3602  | 0  |     { | 
3603  | 0  |         double *pdfXCoord =  | 
3604  | 0  |             static_cast<double *>(CPLCalloc(xdim, sizeof(double)));  | 
3605  | 0  |         double *pdfYCoord =  | 
3606  | 0  |             static_cast<double *>(CPLCalloc(ydim, sizeof(double)));  | 
3607  |  | 
  | 
3608  | 0  |         size_t start[2] = {0, 0}; | 
3609  | 0  |         size_t edge[2] = {xdim, 0}; | 
3610  | 0  |         int status = nc_get_vara_double(nGroupDimXID, nVarDimXID, start, edge,  | 
3611  | 0  |                                         pdfXCoord);  | 
3612  | 0  |         NCDF_ERR(status);  | 
3613  |  | 
  | 
3614  | 0  |         edge[0] = ydim;  | 
3615  | 0  |         status = nc_get_vara_double(nGroupDimYID, nVarDimYID, start, edge,  | 
3616  | 0  |                                     pdfYCoord);  | 
3617  | 0  |         NCDF_ERR(status);  | 
3618  |  | 
  | 
3619  | 0  |         nc_type nc_var_dimx_datatype = NC_NAT;  | 
3620  | 0  |         status =  | 
3621  | 0  |             nc_inq_vartype(nGroupDimXID, nVarDimXID, &nc_var_dimx_datatype);  | 
3622  | 0  |         NCDF_ERR(status);  | 
3623  |  | 
  | 
3624  | 0  |         nc_type nc_var_dimy_datatype = NC_NAT;  | 
3625  | 0  |         status =  | 
3626  | 0  |             nc_inq_vartype(nGroupDimYID, nVarDimYID, &nc_var_dimy_datatype);  | 
3627  | 0  |         NCDF_ERR(status);  | 
3628  |  | 
  | 
3629  | 0  |         if (!poDS->bSwitchedXY)  | 
3630  | 0  |         { | 
3631  |  |             // Convert ]180,540] longitude values to ]-180,0].  | 
3632  | 0  |             if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&  | 
3633  | 0  |                 CPLTestBool(  | 
3634  | 0  |                     CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES"))) | 
3635  | 0  |             { | 
3636  |  |                 // If minimum longitude is > 180, subtract 360 from all.  | 
3637  |  |                 // Add a check on the maximum X value too, since  | 
3638  |  |                 // NCDFIsVarLongitude() is not very specific by default (see  | 
3639  |  |                 // https://github.com/OSGeo/gdal/issues/1440)  | 
3640  | 0  |                 if (std::min(pdfXCoord[0], pdfXCoord[xdim - 1]) > 180.0 &&  | 
3641  | 0  |                     std::max(pdfXCoord[0], pdfXCoord[xdim - 1]) <= 540)  | 
3642  | 0  |                 { | 
3643  | 0  |                     CPLDebug(  | 
3644  | 0  |                         "GDAL_netCDF",  | 
3645  | 0  |                         "Offsetting longitudes from ]180,540] to ]-180,180]. "  | 
3646  | 0  |                         "Can be disabled with GDAL_NETCDF_CENTERLONG_180=NO");  | 
3647  | 0  |                     for (size_t i = 0; i < xdim; i++)  | 
3648  | 0  |                         pdfXCoord[i] -= 360;  | 
3649  | 0  |                 }  | 
3650  | 0  |             }  | 
3651  | 0  |         }  | 
3652  |  |  | 
3653  |  |         // Is pixel spacing uniform across the map?  | 
3654  |  |  | 
3655  |  |         // Check Longitude.  | 
3656  |  | 
  | 
3657  | 0  |         bool bLonSpacingOK = false;  | 
3658  | 0  |         if (xdim == 2)  | 
3659  | 0  |         { | 
3660  | 0  |             bLonSpacingOK = true;  | 
3661  | 0  |         }  | 
3662  | 0  |         else  | 
3663  | 0  |         { | 
3664  | 0  |             bool bWestIsLeft = (pdfXCoord[0] < pdfXCoord[xdim - 1]);  | 
3665  |  |  | 
3666  |  |             // fix longitudes if longitudes should increase from  | 
3667  |  |             // west to east, but west > east  | 
3668  | 0  |             if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&  | 
3669  | 0  |                 !bWestIsLeft)  | 
3670  | 0  |             { | 
3671  | 0  |                 size_t ndecreases = 0;  | 
3672  |  |  | 
3673  |  |                 // there is lon wrap if longitudes increase  | 
3674  |  |                 // with one single decrease  | 
3675  | 0  |                 for (size_t i = 1; i < xdim; i++)  | 
3676  | 0  |                 { | 
3677  | 0  |                     if (pdfXCoord[i] < pdfXCoord[i - 1])  | 
3678  | 0  |                         ndecreases++;  | 
3679  | 0  |                 }  | 
3680  |  | 
  | 
3681  | 0  |                 if (ndecreases == 1)  | 
3682  | 0  |                 { | 
3683  | 0  |                     CPLDebug("GDAL_netCDF", "longitude wrap detected"); | 
3684  | 0  |                     for (size_t i = 0; i < xdim; i++)  | 
3685  | 0  |                     { | 
3686  | 0  |                         if (pdfXCoord[i] > pdfXCoord[xdim - 1])  | 
3687  | 0  |                             pdfXCoord[i] -= 360;  | 
3688  | 0  |                     }  | 
3689  | 0  |                 }  | 
3690  | 0  |             }  | 
3691  |  | 
  | 
3692  | 0  |             const double dfSpacingBegin = pdfXCoord[1] - pdfXCoord[0];  | 
3693  | 0  |             const double dfSpacingMiddle =  | 
3694  | 0  |                 pdfXCoord[xdim / 2 + 1] - pdfXCoord[xdim / 2];  | 
3695  | 0  |             const double dfSpacingLast =  | 
3696  | 0  |                 pdfXCoord[xdim - 1] - pdfXCoord[xdim - 2];  | 
3697  |  | 
  | 
3698  | 0  |             CPLDebug("GDAL_netCDF", | 
3699  | 0  |                      "xdim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "  | 
3700  | 0  |                      "dfSpacingLast: %f",  | 
3701  | 0  |                      static_cast<long>(xdim), dfSpacingBegin, dfSpacingMiddle,  | 
3702  | 0  |                      dfSpacingLast);  | 
3703  |  | #ifdef NCDF_DEBUG  | 
3704  |  |             CPLDebug("GDAL_netCDF", "xcoords: %f %f %f %f %f %f", pdfXCoord[0], | 
3705  |  |                      pdfXCoord[1], pdfXCoord[xdim / 2],  | 
3706  |  |                      pdfXCoord[(xdim / 2) + 1], pdfXCoord[xdim - 2],  | 
3707  |  |                      pdfXCoord[xdim - 1]);  | 
3708  |  | #endif  | 
3709  |  |  | 
3710  |  |             // ftp://ftp.cdc.noaa.gov/Datasets/NARR/Dailies/monolevel/vwnd.10m.2015.nc  | 
3711  |  |             // requires a 0.02% tolerance, so let's settle for 0.05%  | 
3712  |  |  | 
3713  |  |             // For float variables, increase to 0.2% (as seen in  | 
3714  |  |             // https://github.com/OSGeo/gdal/issues/3663)  | 
3715  | 0  |             const double dfEpsRel =  | 
3716  | 0  |                 nc_var_dimx_datatype == NC_FLOAT ? 0.002 : 0.0005;  | 
3717  |  | 
  | 
3718  | 0  |             const double dfEps =  | 
3719  | 0  |                 dfEpsRel *  | 
3720  | 0  |                 std::max(fabs(dfSpacingBegin),  | 
3721  | 0  |                          std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));  | 
3722  | 0  |             if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&  | 
3723  | 0  |                 IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&  | 
3724  | 0  |                 IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))  | 
3725  | 0  |             { | 
3726  | 0  |                 bLonSpacingOK = true;  | 
3727  | 0  |             }  | 
3728  | 0  |             else if (CPLTestBool(CPLGetConfigOption(  | 
3729  | 0  |                          "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))  | 
3730  | 0  |             { | 
3731  | 0  |                 bLonSpacingOK = true;  | 
3732  | 0  |                 CPLDebug(  | 
3733  | 0  |                     "GDAL_netCDF",  | 
3734  | 0  |                     "Longitude/X is not equally spaced, but will be considered "  | 
3735  | 0  |                     "as such because of "  | 
3736  | 0  |                     "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");  | 
3737  | 0  |             }  | 
3738  | 0  |         }  | 
3739  |  | 
  | 
3740  | 0  |         if (bLonSpacingOK == false)  | 
3741  | 0  |         { | 
3742  | 0  |             CPLDebug(  | 
3743  | 0  |                 "GDAL_netCDF", "%s",  | 
3744  | 0  |                 "Longitude/X is not equally spaced (with a 0.05% tolerance). "  | 
3745  | 0  |                 "You may set the "  | 
3746  | 0  |                 "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "  | 
3747  | 0  |                 "option to YES to ignore this check");  | 
3748  | 0  |         }  | 
3749  |  |  | 
3750  |  |         // Check Latitude.  | 
3751  | 0  |         bool bLatSpacingOK = false;  | 
3752  |  | 
  | 
3753  | 0  |         if (ydim == 2)  | 
3754  | 0  |         { | 
3755  | 0  |             bLatSpacingOK = true;  | 
3756  | 0  |         }  | 
3757  | 0  |         else  | 
3758  | 0  |         { | 
3759  | 0  |             const double dfSpacingBegin = pdfYCoord[1] - pdfYCoord[0];  | 
3760  | 0  |             const double dfSpacingMiddle =  | 
3761  | 0  |                 pdfYCoord[ydim / 2 + 1] - pdfYCoord[ydim / 2];  | 
3762  |  | 
  | 
3763  | 0  |             const double dfSpacingLast =  | 
3764  | 0  |                 pdfYCoord[ydim - 1] - pdfYCoord[ydim - 2];  | 
3765  |  | 
  | 
3766  | 0  |             CPLDebug("GDAL_netCDF", | 
3767  | 0  |                      "ydim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "  | 
3768  | 0  |                      "dfSpacingLast: %f",  | 
3769  | 0  |                      (long)ydim, dfSpacingBegin, dfSpacingMiddle,  | 
3770  | 0  |                      dfSpacingLast);  | 
3771  |  | #ifdef NCDF_DEBUG  | 
3772  |  |             CPLDebug("GDAL_netCDF", "ycoords: %f %f %f %f %f %f", pdfYCoord[0], | 
3773  |  |                      pdfYCoord[1], pdfYCoord[ydim / 2],  | 
3774  |  |                      pdfYCoord[(ydim / 2) + 1], pdfYCoord[ydim - 2],  | 
3775  |  |                      pdfYCoord[ydim - 1]);  | 
3776  |  | #endif  | 
3777  |  | 
  | 
3778  | 0  |             const double dfEpsRel =  | 
3779  | 0  |                 nc_var_dimy_datatype == NC_FLOAT ? 0.002 : 0.0005;  | 
3780  |  | 
  | 
3781  | 0  |             const double dfEps =  | 
3782  | 0  |                 dfEpsRel *  | 
3783  | 0  |                 std::max(fabs(dfSpacingBegin),  | 
3784  | 0  |                          std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));  | 
3785  | 0  |             if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&  | 
3786  | 0  |                 IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&  | 
3787  | 0  |                 IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))  | 
3788  | 0  |             { | 
3789  | 0  |                 bLatSpacingOK = true;  | 
3790  | 0  |             }  | 
3791  | 0  |             else if (CPLTestBool(CPLGetConfigOption(  | 
3792  | 0  |                          "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))  | 
3793  | 0  |             { | 
3794  | 0  |                 bLatSpacingOK = true;  | 
3795  | 0  |                 CPLDebug(  | 
3796  | 0  |                     "GDAL_netCDF",  | 
3797  | 0  |                     "Latitude/Y is not equally spaced, but will be considered "  | 
3798  | 0  |                     "as such because of "  | 
3799  | 0  |                     "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");  | 
3800  | 0  |             }  | 
3801  | 0  |             else if (!oSRS.IsProjected() &&  | 
3802  | 0  |                      fabs(dfSpacingBegin - dfSpacingLast) <= 0.1 &&  | 
3803  | 0  |                      fabs(dfSpacingBegin - dfSpacingMiddle) <= 0.1 &&  | 
3804  | 0  |                      fabs(dfSpacingMiddle - dfSpacingLast) <= 0.1)  | 
3805  | 0  |             { | 
3806  | 0  |                 bLatSpacingOK = true;  | 
3807  | 0  |                 CPLError(CE_Warning, CPLE_AppDefined,  | 
3808  | 0  |                          "Latitude grid not spaced evenly.  "  | 
3809  | 0  |                          "Setting projection for grid spacing is "  | 
3810  | 0  |                          "within 0.1 degrees threshold.");  | 
3811  |  | 
  | 
3812  | 0  |                 CPLDebug("GDAL_netCDF", | 
3813  | 0  |                          "Latitude grid not spaced evenly, but within 0.1 "  | 
3814  | 0  |                          "degree threshold (probably a Gaussian grid).  "  | 
3815  | 0  |                          "Saving original latitude values in Y_VALUES "  | 
3816  | 0  |                          "geolocation metadata");  | 
3817  | 0  |                 Set1DGeolocation(nGroupDimYID, nVarDimYID, "Y");  | 
3818  | 0  |             }  | 
3819  |  | 
  | 
3820  | 0  |             if (bLatSpacingOK == false)  | 
3821  | 0  |             { | 
3822  | 0  |                 CPLDebug(  | 
3823  | 0  |                     "GDAL_netCDF", "%s",  | 
3824  | 0  |                     "Latitude/Y is not equally spaced (with a 0.05% "  | 
3825  | 0  |                     "tolerance). "  | 
3826  | 0  |                     "You may set the "  | 
3827  | 0  |                     "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "  | 
3828  | 0  |                     "option to YES to ignore this check");  | 
3829  | 0  |             }  | 
3830  | 0  |         }  | 
3831  |  | 
  | 
3832  | 0  |         if (bLonSpacingOK && bLatSpacingOK)  | 
3833  | 0  |         { | 
3834  |  |             // We have gridded data so we can set the Georeferencing info.  | 
3835  |  |  | 
3836  |  |             // Enable GeoTransform.  | 
3837  |  |  | 
3838  |  |             // In the following "actual_range" and "node_offset"  | 
3839  |  |             // are attributes used by netCDF files created by GMT.  | 
3840  |  |             // If we find them we know how to proceed. Else, use  | 
3841  |  |             // the original algorithm.  | 
3842  | 0  |             bGotCfGT = true;  | 
3843  |  | 
  | 
3844  | 0  |             int node_offset = 0;  | 
3845  | 0  |             NCDFResolveAttInt(nGroupId, NC_GLOBAL, "node_offset", &node_offset);  | 
3846  |  | 
  | 
3847  | 0  |             double adfActualRange[2] = {0.0, 0.0}; | 
3848  | 0  |             double xMinMax[2] = {0.0, 0.0}; | 
3849  | 0  |             double yMinMax[2] = {0.0, 0.0}; | 
3850  |  | 
  | 
3851  | 0  |             const auto RoundMinMaxForFloatVals =  | 
3852  | 0  |                 [](double &dfMin, double &dfMax, int nIntervals)  | 
3853  | 0  |             { | 
3854  |  |                 // Helps for a case where longitudes range from  | 
3855  |  |                 // -179.99 to 180.0 with a 0.01 degree spacing.  | 
3856  |  |                 // However as this is encoded in a float array,  | 
3857  |  |                 // -179.99 is actually read as -179.99000549316406 as  | 
3858  |  |                 // a double. Try to detect that and correct the rounding  | 
3859  |  | 
  | 
3860  | 0  |                 const auto IsAlmostInteger = [](double dfVal)  | 
3861  | 0  |                 { | 
3862  | 0  |                     constexpr double THRESHOLD_INTEGER = 1e-3;  | 
3863  | 0  |                     return std::fabs(dfVal - std::round(dfVal)) <=  | 
3864  | 0  |                            THRESHOLD_INTEGER;  | 
3865  | 0  |                 };  | 
3866  |  | 
  | 
3867  | 0  |                 const double dfSpacing = (dfMax - dfMin) / nIntervals;  | 
3868  | 0  |                 if (dfSpacing > 0)  | 
3869  | 0  |                 { | 
3870  | 0  |                     const double dfInvSpacing = 1.0 / dfSpacing;  | 
3871  | 0  |                     if (IsAlmostInteger(dfInvSpacing))  | 
3872  | 0  |                     { | 
3873  | 0  |                         const double dfRoundedSpacing =  | 
3874  | 0  |                             1.0 / std::round(dfInvSpacing);  | 
3875  | 0  |                         const double dfMinDivRoundedSpacing =  | 
3876  | 0  |                             dfMin / dfRoundedSpacing;  | 
3877  | 0  |                         const double dfMaxDivRoundedSpacing =  | 
3878  | 0  |                             dfMax / dfRoundedSpacing;  | 
3879  | 0  |                         if (IsAlmostInteger(dfMinDivRoundedSpacing) &&  | 
3880  | 0  |                             IsAlmostInteger(dfMaxDivRoundedSpacing))  | 
3881  | 0  |                         { | 
3882  | 0  |                             const double dfRoundedMin =  | 
3883  | 0  |                                 std::round(dfMinDivRoundedSpacing) *  | 
3884  | 0  |                                 dfRoundedSpacing;  | 
3885  | 0  |                             const double dfRoundedMax =  | 
3886  | 0  |                                 std::round(dfMaxDivRoundedSpacing) *  | 
3887  | 0  |                                 dfRoundedSpacing;  | 
3888  | 0  |                             if (static_cast<float>(dfMin) ==  | 
3889  | 0  |                                     static_cast<float>(dfRoundedMin) &&  | 
3890  | 0  |                                 static_cast<float>(dfMax) ==  | 
3891  | 0  |                                     static_cast<float>(dfRoundedMax))  | 
3892  | 0  |                             { | 
3893  | 0  |                                 dfMin = dfRoundedMin;  | 
3894  | 0  |                                 dfMax = dfRoundedMax;  | 
3895  | 0  |                             }  | 
3896  | 0  |                         }  | 
3897  | 0  |                     }  | 
3898  | 0  |                 }  | 
3899  | 0  |             };  | 
3900  |  | 
  | 
3901  | 0  |             if (!nc_get_att_double(nGroupDimXID, nVarDimXID, "actual_range",  | 
3902  | 0  |                                    adfActualRange))  | 
3903  | 0  |             { | 
3904  | 0  |                 xMinMax[0] = adfActualRange[0];  | 
3905  | 0  |                 xMinMax[1] = adfActualRange[1];  | 
3906  |  |  | 
3907  |  |                 // Present xMinMax[] in the same order as padfXCoord  | 
3908  | 0  |                 if ((xMinMax[0] - xMinMax[1]) *  | 
3909  | 0  |                         (pdfXCoord[0] - pdfXCoord[xdim - 1]) <  | 
3910  | 0  |                     0)  | 
3911  | 0  |                 { | 
3912  | 0  |                     std::swap(xMinMax[0], xMinMax[1]);  | 
3913  | 0  |                 }  | 
3914  | 0  |             }  | 
3915  | 0  |             else  | 
3916  | 0  |             { | 
3917  | 0  |                 xMinMax[0] = pdfXCoord[0];  | 
3918  | 0  |                 xMinMax[1] = pdfXCoord[xdim - 1];  | 
3919  | 0  |                 node_offset = 0;  | 
3920  |  | 
  | 
3921  | 0  |                 if (nc_var_dimx_datatype == NC_FLOAT)  | 
3922  | 0  |                 { | 
3923  | 0  |                     RoundMinMaxForFloatVals(xMinMax[0], xMinMax[1],  | 
3924  | 0  |                                             poDS->nRasterXSize - 1);  | 
3925  | 0  |                 }  | 
3926  | 0  |             }  | 
3927  |  | 
  | 
3928  | 0  |             if (!nc_get_att_double(nGroupDimYID, nVarDimYID, "actual_range",  | 
3929  | 0  |                                    adfActualRange))  | 
3930  | 0  |             { | 
3931  | 0  |                 yMinMax[0] = adfActualRange[0];  | 
3932  | 0  |                 yMinMax[1] = adfActualRange[1];  | 
3933  |  |  | 
3934  |  |                 // Present yMinMax[] in the same order as pdfYCoord  | 
3935  | 0  |                 if ((yMinMax[0] - yMinMax[1]) *  | 
3936  | 0  |                         (pdfYCoord[0] - pdfYCoord[ydim - 1]) <  | 
3937  | 0  |                     0)  | 
3938  | 0  |                 { | 
3939  | 0  |                     std::swap(yMinMax[0], yMinMax[1]);  | 
3940  | 0  |                 }  | 
3941  | 0  |             }  | 
3942  | 0  |             else  | 
3943  | 0  |             { | 
3944  | 0  |                 yMinMax[0] = pdfYCoord[0];  | 
3945  | 0  |                 yMinMax[1] = pdfYCoord[ydim - 1];  | 
3946  | 0  |                 node_offset = 0;  | 
3947  |  | 
  | 
3948  | 0  |                 if (nc_var_dimy_datatype == NC_FLOAT)  | 
3949  | 0  |                 { | 
3950  | 0  |                     RoundMinMaxForFloatVals(yMinMax[0], yMinMax[1],  | 
3951  | 0  |                                             poDS->nRasterYSize - 1);  | 
3952  | 0  |                 }  | 
3953  | 0  |             }  | 
3954  |  | 
  | 
3955  | 0  |             double dfCoordOffset = 0.0;  | 
3956  | 0  |             double dfCoordScale = 1.0;  | 
3957  | 0  |             if (!nc_get_att_double(nGroupId, nVarDimXID, CF_ADD_OFFSET,  | 
3958  | 0  |                                    &dfCoordOffset) &&  | 
3959  | 0  |                 !nc_get_att_double(nGroupId, nVarDimXID, CF_SCALE_FACTOR,  | 
3960  | 0  |                                    &dfCoordScale))  | 
3961  | 0  |             { | 
3962  | 0  |                 xMinMax[0] = dfCoordOffset + xMinMax[0] * dfCoordScale;  | 
3963  | 0  |                 xMinMax[1] = dfCoordOffset + xMinMax[1] * dfCoordScale;  | 
3964  | 0  |             }  | 
3965  |  | 
  | 
3966  | 0  |             if (!nc_get_att_double(nGroupId, nVarDimYID, CF_ADD_OFFSET,  | 
3967  | 0  |                                    &dfCoordOffset) &&  | 
3968  | 0  |                 !nc_get_att_double(nGroupId, nVarDimYID, CF_SCALE_FACTOR,  | 
3969  | 0  |                                    &dfCoordScale))  | 
3970  | 0  |             { | 
3971  | 0  |                 yMinMax[0] = dfCoordOffset + yMinMax[0] * dfCoordScale;  | 
3972  | 0  |                 yMinMax[1] = dfCoordOffset + yMinMax[1] * dfCoordScale;  | 
3973  | 0  |             }  | 
3974  |  |  | 
3975  |  |             // Check for reverse order of y-coordinate.  | 
3976  | 0  |             if (!bSwitchedXY)  | 
3977  | 0  |             { | 
3978  | 0  |                 poDS->bBottomUp = (yMinMax[0] <= yMinMax[1]);  | 
3979  | 0  |                 CPLDebug("GDAL_netCDF", "set bBottomUp = %d from Y axis", | 
3980  | 0  |                          static_cast<int>(poDS->bBottomUp));  | 
3981  | 0  |                 if (!poDS->bBottomUp)  | 
3982  | 0  |                 { | 
3983  | 0  |                     std::swap(yMinMax[0], yMinMax[1]);  | 
3984  | 0  |                 }  | 
3985  | 0  |             }  | 
3986  |  |  | 
3987  |  |             // Geostationary satellites can specify units in (micro)radians  | 
3988  |  |             // So we check if they do, and if so convert to linear units  | 
3989  |  |             // (meters)  | 
3990  | 0  |             const char *pszProjName = oSRS.GetAttrValue("PROJECTION"); | 
3991  | 0  |             if (pszProjName != nullptr)  | 
3992  | 0  |             { | 
3993  | 0  |                 if (EQUAL(pszProjName, SRS_PT_GEOSTATIONARY_SATELLITE))  | 
3994  | 0  |                 { | 
3995  | 0  |                     double satelliteHeight =  | 
3996  | 0  |                         oSRS.GetProjParm(SRS_PP_SATELLITE_HEIGHT, 1.0);  | 
3997  | 0  |                     size_t nAttlen = 0;  | 
3998  | 0  |                     char szUnits[NC_MAX_NAME + 1];  | 
3999  | 0  |                     szUnits[0] = '\0';  | 
4000  | 0  |                     nc_type nAttype = NC_NAT;  | 
4001  | 0  |                     nc_inq_att(nGroupId, nVarDimXID, "units", &nAttype,  | 
4002  | 0  |                                &nAttlen);  | 
4003  | 0  |                     if (nAttlen < sizeof(szUnits) &&  | 
4004  | 0  |                         nc_get_att_text(nGroupId, nVarDimXID, "units",  | 
4005  | 0  |                                         szUnits) == NC_NOERR)  | 
4006  | 0  |                     { | 
4007  | 0  |                         szUnits[nAttlen] = '\0';  | 
4008  | 0  |                         if (EQUAL(szUnits, "microradian"))  | 
4009  | 0  |                         { | 
4010  | 0  |                             xMinMax[0] =  | 
4011  | 0  |                                 xMinMax[0] * satelliteHeight * 0.000001;  | 
4012  | 0  |                             xMinMax[1] =  | 
4013  | 0  |                                 xMinMax[1] * satelliteHeight * 0.000001;  | 
4014  | 0  |                         }  | 
4015  | 0  |                         else if (EQUAL(szUnits, "rad") ||  | 
4016  | 0  |                                  EQUAL(szUnits, "radian"))  | 
4017  | 0  |                         { | 
4018  | 0  |                             xMinMax[0] = xMinMax[0] * satelliteHeight;  | 
4019  | 0  |                             xMinMax[1] = xMinMax[1] * satelliteHeight;  | 
4020  | 0  |                         }  | 
4021  | 0  |                     }  | 
4022  | 0  |                     szUnits[0] = '\0';  | 
4023  | 0  |                     nc_inq_att(nGroupId, nVarDimYID, "units", &nAttype,  | 
4024  | 0  |                                &nAttlen);  | 
4025  | 0  |                     if (nAttlen < sizeof(szUnits) &&  | 
4026  | 0  |                         nc_get_att_text(nGroupId, nVarDimYID, "units",  | 
4027  | 0  |                                         szUnits) == NC_NOERR)  | 
4028  | 0  |                     { | 
4029  | 0  |                         szUnits[nAttlen] = '\0';  | 
4030  | 0  |                         if (EQUAL(szUnits, "microradian"))  | 
4031  | 0  |                         { | 
4032  | 0  |                             yMinMax[0] =  | 
4033  | 0  |                                 yMinMax[0] * satelliteHeight * 0.000001;  | 
4034  | 0  |                             yMinMax[1] =  | 
4035  | 0  |                                 yMinMax[1] * satelliteHeight * 0.000001;  | 
4036  | 0  |                         }  | 
4037  | 0  |                         else if (EQUAL(szUnits, "rad") ||  | 
4038  | 0  |                                  EQUAL(szUnits, "radian"))  | 
4039  | 0  |                         { | 
4040  | 0  |                             yMinMax[0] = yMinMax[0] * satelliteHeight;  | 
4041  | 0  |                             yMinMax[1] = yMinMax[1] * satelliteHeight;  | 
4042  | 0  |                         }  | 
4043  | 0  |                     }  | 
4044  | 0  |                 }  | 
4045  | 0  |             }  | 
4046  |  | 
  | 
4047  | 0  |             adfTempGeoTransform[0] = xMinMax[0];  | 
4048  | 0  |             adfTempGeoTransform[1] = (xMinMax[1] - xMinMax[0]) /  | 
4049  | 0  |                                      (poDS->nRasterXSize + (node_offset - 1));  | 
4050  | 0  |             adfTempGeoTransform[2] = 0;  | 
4051  | 0  |             if (bSwitchedXY)  | 
4052  | 0  |             { | 
4053  | 0  |                 adfTempGeoTransform[3] = yMinMax[0];  | 
4054  | 0  |                 adfTempGeoTransform[4] = 0;  | 
4055  | 0  |                 adfTempGeoTransform[5] =  | 
4056  | 0  |                     (yMinMax[1] - yMinMax[0]) /  | 
4057  | 0  |                     (poDS->nRasterYSize + (node_offset - 1));  | 
4058  | 0  |             }  | 
4059  | 0  |             else  | 
4060  | 0  |             { | 
4061  | 0  |                 adfTempGeoTransform[3] = yMinMax[1];  | 
4062  | 0  |                 adfTempGeoTransform[4] = 0;  | 
4063  | 0  |                 adfTempGeoTransform[5] =  | 
4064  | 0  |                     (yMinMax[0] - yMinMax[1]) /  | 
4065  | 0  |                     (poDS->nRasterYSize + (node_offset - 1));  | 
4066  | 0  |             }  | 
4067  |  |  | 
4068  |  |             // Compute the center of the pixel.  | 
4069  | 0  |             if (!node_offset)  | 
4070  | 0  |             { | 
4071  |  |                 // Otherwise its already the pixel center.  | 
4072  | 0  |                 adfTempGeoTransform[0] -= (adfTempGeoTransform[1] / 2);  | 
4073  | 0  |                 adfTempGeoTransform[3] -= (adfTempGeoTransform[5] / 2);  | 
4074  | 0  |             }  | 
4075  | 0  |         }  | 
4076  |  | 
  | 
4077  | 0  |         const auto AreSRSEqualThroughProj4String =  | 
4078  | 0  |             [](const OGRSpatialReference &oSRS1,  | 
4079  | 0  |                const OGRSpatialReference &oSRS2)  | 
4080  | 0  |         { | 
4081  | 0  |             char *pszProj4Str1 = nullptr;  | 
4082  | 0  |             oSRS1.exportToProj4(&pszProj4Str1);  | 
4083  |  | 
  | 
4084  | 0  |             char *pszProj4Str2 = nullptr;  | 
4085  | 0  |             oSRS2.exportToProj4(&pszProj4Str2);  | 
4086  |  | 
  | 
4087  | 0  |             { | 
4088  | 0  |                 char *pszTmp = strstr(pszProj4Str1, "+datum=");  | 
4089  | 0  |                 if (pszTmp)  | 
4090  | 0  |                     memcpy(pszTmp, "+ellps=", strlen("+ellps=")); | 
4091  | 0  |             }  | 
4092  |  | 
  | 
4093  | 0  |             { | 
4094  | 0  |                 char *pszTmp = strstr(pszProj4Str2, "+datum=");  | 
4095  | 0  |                 if (pszTmp)  | 
4096  | 0  |                     memcpy(pszTmp, "+ellps=", strlen("+ellps=")); | 
4097  | 0  |             }  | 
4098  |  | 
  | 
4099  | 0  |             bool bRet = false;  | 
4100  | 0  |             if (pszProj4Str1 && pszProj4Str2 &&  | 
4101  | 0  |                 EQUAL(pszProj4Str1, pszProj4Str2))  | 
4102  | 0  |             { | 
4103  | 0  |                 bRet = true;  | 
4104  | 0  |             }  | 
4105  |  | 
  | 
4106  | 0  |             CPLFree(pszProj4Str1);  | 
4107  | 0  |             CPLFree(pszProj4Str2);  | 
4108  | 0  |             return bRet;  | 
4109  | 0  |         };  | 
4110  |  | 
  | 
4111  | 0  |         if (dfLinearUnitsConvFactor != 1.0)  | 
4112  | 0  |         { | 
4113  | 0  |             for (int i = 0; i < 6; ++i)  | 
4114  | 0  |                 adfTempGeoTransform[i] *= dfLinearUnitsConvFactor;  | 
4115  |  | 
  | 
4116  | 0  |             if (paosRemovedMDItems)  | 
4117  | 0  |             { | 
4118  | 0  |                 char szVarNameX[NC_MAX_NAME + 1];  | 
4119  | 0  |                 CPL_IGNORE_RET_VAL(  | 
4120  | 0  |                     nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));  | 
4121  |  | 
  | 
4122  | 0  |                 char szVarNameY[NC_MAX_NAME + 1];  | 
4123  | 0  |                 CPL_IGNORE_RET_VAL(  | 
4124  | 0  |                     nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));  | 
4125  |  | 
  | 
4126  | 0  |                 paosRemovedMDItems->push_back(  | 
4127  | 0  |                     CPLSPrintf("%s#units", szVarNameX)); | 
4128  | 0  |                 paosRemovedMDItems->push_back(  | 
4129  | 0  |                     CPLSPrintf("%s#units", szVarNameY)); | 
4130  | 0  |             }  | 
4131  | 0  |         }  | 
4132  |  |  | 
4133  |  |         // If there is a global "geospatial_bounds_crs" attribute, check that it  | 
4134  |  |         // is consistent with the SRS, and if so, use it as the SRS  | 
4135  | 0  |         const char *pszGBCRS =  | 
4136  | 0  |             FetchAttr(nGroupId, NC_GLOBAL, "geospatial_bounds_crs");  | 
4137  | 0  |         if (pszGBCRS && STARTS_WITH(pszGBCRS, "EPSG:"))  | 
4138  | 0  |         { | 
4139  | 0  |             OGRSpatialReference oSRSFromGBCRS;  | 
4140  | 0  |             oSRSFromGBCRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);  | 
4141  | 0  |             if (oSRSFromGBCRS.SetFromUserInput(  | 
4142  | 0  |                     pszGBCRS,  | 
4143  | 0  |                     OGRSpatialReference::  | 
4144  | 0  |                         SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE &&  | 
4145  | 0  |                 AreSRSEqualThroughProj4String(oSRS, oSRSFromGBCRS))  | 
4146  | 0  |             { | 
4147  | 0  |                 oSRS = std::move(oSRSFromGBCRS);  | 
4148  | 0  |                 SetSpatialRefNoUpdate(&oSRS);  | 
4149  | 0  |             }  | 
4150  | 0  |         }  | 
4151  |  | 
  | 
4152  | 0  |         CPLFree(pdfXCoord);  | 
4153  | 0  |         CPLFree(pdfYCoord);  | 
4154  | 0  |     }  // end if(has dims)  | 
4155  |  |  | 
4156  |  |     // Process custom GeoTransform GDAL value.  | 
4157  | 23  |     if (!EQUAL(pszGridMappingValue, ""))  | 
4158  | 0  |     { | 
4159  | 0  |         if (pszGeoTransform != nullptr)  | 
4160  | 0  |         { | 
4161  | 0  |             CPLStringList aosGeoTransform(  | 
4162  | 0  |                 CSLTokenizeString2(pszGeoTransform, " ", CSLT_HONOURSTRINGS));  | 
4163  | 0  |             if (aosGeoTransform.size() == 6)  | 
4164  | 0  |             { | 
4165  | 0  |                 std::array<double, 6> adfGeoTransformFromAttribute;  | 
4166  | 0  |                 for (int i = 0; i < 6; i++)  | 
4167  | 0  |                 { | 
4168  | 0  |                     adfGeoTransformFromAttribute[i] =  | 
4169  | 0  |                         CPLAtof(aosGeoTransform[i]);  | 
4170  | 0  |                 }  | 
4171  |  | 
  | 
4172  | 0  |                 if (bGotCfGT)  | 
4173  | 0  |                 { | 
4174  | 0  |                     constexpr double GT_RELERROR_WARN_THRESHOLD = 1e-6;  | 
4175  | 0  |                     double dfMaxAbsoluteError = 0.0;  | 
4176  | 0  |                     for (int i = 0; i < 6; i++)  | 
4177  | 0  |                     { | 
4178  | 0  |                         double dfAbsoluteError =  | 
4179  | 0  |                             std::abs(adfTempGeoTransform[i] -  | 
4180  | 0  |                                      adfGeoTransformFromAttribute[i]);  | 
4181  | 0  |                         if (dfAbsoluteError >  | 
4182  | 0  |                             std::abs(adfGeoTransformFromAttribute[i] *  | 
4183  | 0  |                                      GT_RELERROR_WARN_THRESHOLD))  | 
4184  | 0  |                         { | 
4185  | 0  |                             dfMaxAbsoluteError =  | 
4186  | 0  |                                 std::max(dfMaxAbsoluteError, dfAbsoluteError);  | 
4187  | 0  |                         }  | 
4188  | 0  |                     }  | 
4189  |  | 
  | 
4190  | 0  |                     if (dfMaxAbsoluteError > 0)  | 
4191  | 0  |                     { | 
4192  | 0  |                         CPLError(CE_Warning, CPLE_AppDefined,  | 
4193  | 0  |                                  "GeoTransform read from attribute of %s "  | 
4194  | 0  |                                  "variable differs from value calculated from "  | 
4195  | 0  |                                  "dimension variables (max diff = %g). Using "  | 
4196  | 0  |                                  "value from attribute.",  | 
4197  | 0  |                                  pszGridMappingValue, dfMaxAbsoluteError);  | 
4198  | 0  |                     }  | 
4199  | 0  |                 }  | 
4200  |  | 
  | 
4201  | 0  |                 std::copy(adfGeoTransformFromAttribute.begin(),  | 
4202  | 0  |                           adfGeoTransformFromAttribute.end(),  | 
4203  | 0  |                           adfTempGeoTransform);  | 
4204  | 0  |                 bGotGdalGT = true;  | 
4205  | 0  |             }  | 
4206  | 0  |         }  | 
4207  | 0  |         else  | 
4208  | 0  |         { | 
4209  |  |             // Look for corner array values.  | 
4210  |  |             // CPLDebug("GDAL_netCDF", | 
4211  |  |             //           "looking for geotransform corners");  | 
4212  | 0  |             bool bGotNN = false;  | 
4213  | 0  |             double dfNN = FetchCopyParam(pszGridMappingValue,  | 
4214  | 0  |                                          "Northernmost_Northing", 0, &bGotNN);  | 
4215  |  | 
  | 
4216  | 0  |             bool bGotSN = false;  | 
4217  | 0  |             double dfSN = FetchCopyParam(pszGridMappingValue,  | 
4218  | 0  |                                          "Southernmost_Northing", 0, &bGotSN);  | 
4219  |  | 
  | 
4220  | 0  |             bool bGotEE = false;  | 
4221  | 0  |             double dfEE = FetchCopyParam(pszGridMappingValue,  | 
4222  | 0  |                                          "Easternmost_Easting", 0, &bGotEE);  | 
4223  |  | 
  | 
4224  | 0  |             bool bGotWE = false;  | 
4225  | 0  |             double dfWE = FetchCopyParam(pszGridMappingValue,  | 
4226  | 0  |                                          "Westernmost_Easting", 0, &bGotWE);  | 
4227  |  |  | 
4228  |  |             // Only set the GeoTransform if we got all the values.  | 
4229  | 0  |             if (bGotNN && bGotSN && bGotEE && bGotWE)  | 
4230  | 0  |             { | 
4231  | 0  |                 bGotGdalGT = true;  | 
4232  |  | 
  | 
4233  | 0  |                 adfTempGeoTransform[0] = dfWE;  | 
4234  | 0  |                 adfTempGeoTransform[1] =  | 
4235  | 0  |                     (dfEE - dfWE) / (poDS->GetRasterXSize() - 1);  | 
4236  | 0  |                 adfTempGeoTransform[2] = 0.0;  | 
4237  | 0  |                 adfTempGeoTransform[3] = dfNN;  | 
4238  | 0  |                 adfTempGeoTransform[4] = 0.0;  | 
4239  | 0  |                 adfTempGeoTransform[5] =  | 
4240  | 0  |                     (dfSN - dfNN) / (poDS->GetRasterYSize() - 1);  | 
4241  |  |                 // Compute the center of the pixel.  | 
4242  | 0  |                 adfTempGeoTransform[0] = dfWE - (adfTempGeoTransform[1] / 2);  | 
4243  | 0  |                 adfTempGeoTransform[3] = dfNN - (adfTempGeoTransform[5] / 2);  | 
4244  | 0  |             }  | 
4245  | 0  |         }  // (pszGeoTransform != NULL)  | 
4246  |  | 
  | 
4247  | 0  |         if (bGotGdalSRS && !bGotGdalGT)  | 
4248  | 0  |             CPLDebug("GDAL_netCDF", "Got SRS but no geotransform from GDAL!"); | 
4249  | 0  |     }  | 
4250  |  |  | 
4251  | 23  |     if (!pszWKT && !bGotCfSRS)  | 
4252  | 23  |     { | 
4253  |  |         // Some netCDF files have a srid attribute (#6613) like  | 
4254  |  |         // urn:ogc:def:crs:EPSG::6931  | 
4255  | 23  |         const char *pszSRID = FetchAttr(pszGridMappingValue, "srid");  | 
4256  | 23  |         if (pszSRID != nullptr)  | 
4257  | 0  |         { | 
4258  | 0  |             oSRS.Clear();  | 
4259  | 0  |             if (oSRS.SetFromUserInput(  | 
4260  | 0  |                     pszSRID,  | 
4261  | 0  |                     OGRSpatialReference::  | 
4262  | 0  |                         SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE)  | 
4263  | 0  |             { | 
4264  | 0  |                 char *pszWKTExport = nullptr;  | 
4265  | 0  |                 CPLDebug("GDAL_netCDF", "Got SRS from %s", pszSRID); | 
4266  | 0  |                 oSRS.exportToWkt(&pszWKTExport);  | 
4267  | 0  |                 if (returnProjStr != nullptr)  | 
4268  | 0  |                 { | 
4269  | 0  |                     (*returnProjStr) = std::string(pszWKTExport);  | 
4270  | 0  |                 }  | 
4271  | 0  |                 else  | 
4272  | 0  |                 { | 
4273  | 0  |                     m_bAddedProjectionVarsDefs = true;  | 
4274  | 0  |                     m_bAddedProjectionVarsData = true;  | 
4275  | 0  |                     SetSpatialRefNoUpdate(&oSRS);  | 
4276  | 0  |                 }  | 
4277  | 0  |                 CPLFree(pszWKTExport);  | 
4278  | 0  |             }  | 
4279  | 0  |         }  | 
4280  | 23  |     }  | 
4281  |  |  | 
4282  | 23  |     CPLFree(pszGridMappingValue);  | 
4283  |  |  | 
4284  | 23  |     if (bReadSRSOnly)  | 
4285  | 23  |         return;  | 
4286  |  |  | 
4287  |  |     // Determines the SRS to be used by the geolocation array, if any  | 
4288  | 0  |     std::string osGeolocWKT = SRS_WKT_WGS84_LAT_LONG;  | 
4289  | 0  |     if (!m_oSRS.IsEmpty())  | 
4290  | 0  |     { | 
4291  | 0  |         OGRSpatialReference oGeogCRS;  | 
4292  | 0  |         oGeogCRS.CopyGeogCSFrom(&m_oSRS);  | 
4293  | 0  |         char *pszWKTTmp = nullptr;  | 
4294  | 0  |         const char *const apszOptions[] = {"FORMAT=WKT2_2019", nullptr}; | 
4295  | 0  |         if (oGeogCRS.exportToWkt(&pszWKTTmp, apszOptions) == OGRERR_NONE)  | 
4296  | 0  |         { | 
4297  | 0  |             osGeolocWKT = pszWKTTmp;  | 
4298  | 0  |         }  | 
4299  | 0  |         CPLFree(pszWKTTmp);  | 
4300  | 0  |     }  | 
4301  |  |  | 
4302  |  |     // Process geolocation arrays from CF "coordinates" attribute.  | 
4303  | 0  |     std::string osGeolocXName, osGeolocYName;  | 
4304  | 0  |     if (ProcessCFGeolocation(nGroupId, nVarId, osGeolocWKT, osGeolocXName,  | 
4305  | 0  |                              osGeolocYName))  | 
4306  | 0  |     { | 
4307  | 0  |         bool bCanCancelGT = true;  | 
4308  | 0  |         if ((nVarDimXID != -1) && (nVarDimYID != -1))  | 
4309  | 0  |         { | 
4310  | 0  |             char szVarNameX[NC_MAX_NAME + 1];  | 
4311  | 0  |             CPL_IGNORE_RET_VAL(  | 
4312  | 0  |                 nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));  | 
4313  | 0  |             char szVarNameY[NC_MAX_NAME + 1];  | 
4314  | 0  |             CPL_IGNORE_RET_VAL(  | 
4315  | 0  |                 nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));  | 
4316  | 0  |             bCanCancelGT =  | 
4317  | 0  |                 !(osGeolocXName == szVarNameX && osGeolocYName == szVarNameY);  | 
4318  | 0  |         }  | 
4319  | 0  |         if (bCanCancelGT && !m_oSRS.IsGeographic() && !m_oSRS.IsProjected() &&  | 
4320  | 0  |             !bSwitchedXY)  | 
4321  | 0  |         { | 
4322  | 0  |             bGotCfGT = false;  | 
4323  | 0  |         }  | 
4324  | 0  |     }  | 
4325  | 0  |     else if (!bGotCfGT && !bReadSRSOnly && (nVarDimXID != -1) &&  | 
4326  | 0  |              (nVarDimYID != -1) && xdim > 0 && ydim > 0 &&  | 
4327  | 0  |              ((!bSwitchedXY &&  | 
4328  | 0  |                NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&  | 
4329  | 0  |                NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr)) ||  | 
4330  | 0  |               (bSwitchedXY &&  | 
4331  | 0  |                NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&  | 
4332  | 0  |                NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr))))  | 
4333  | 0  |     { | 
4334  |  |         // Case of autotest/gdrivers/data/netcdf/GLMELT_4X5.OCN.nc  | 
4335  |  |         // which is indexed by lat, lon variables, but lat has irregular  | 
4336  |  |         // spacing.  | 
4337  | 0  |         const char *pszGeolocXFullName = poDS->papszDimName[poDS->nXDimID];  | 
4338  | 0  |         const char *pszGeolocYFullName = poDS->papszDimName[poDS->nYDimID];  | 
4339  | 0  |         if (bSwitchedXY)  | 
4340  | 0  |         { | 
4341  | 0  |             std::swap(pszGeolocXFullName, pszGeolocYFullName);  | 
4342  | 0  |             GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", "GEOLOCATION"); | 
4343  | 0  |         }  | 
4344  |  | 
  | 
4345  | 0  |         CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION", | 
4346  | 0  |                  pszGeolocXFullName, pszGeolocYFullName);  | 
4347  |  | 
  | 
4348  | 0  |         GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(), | 
4349  | 0  |                                         "GEOLOCATION");  | 
4350  |  | 
  | 
4351  | 0  |         CPLString osTMP;  | 
4352  | 0  |         osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), | 
4353  | 0  |                      pszGeolocXFullName);  | 
4354  |  | 
  | 
4355  | 0  |         GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION"); | 
4356  | 0  |         GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION"); | 
4357  | 0  |         osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), | 
4358  | 0  |                      pszGeolocYFullName);  | 
4359  |  | 
  | 
4360  | 0  |         GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION"); | 
4361  | 0  |         GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION"); | 
4362  |  | 
  | 
4363  | 0  |         GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION"); | 
4364  | 0  |         GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION"); | 
4365  |  | 
  | 
4366  | 0  |         GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION"); | 
4367  | 0  |         GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION"); | 
4368  |  | 
  | 
4369  | 0  |         GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", | 
4370  | 0  |                                         "PIXEL_CENTER", "GEOLOCATION");  | 
4371  | 0  |     }  | 
4372  |  |  | 
4373  |  |     // Set GeoTransform if we got a complete one - after projection has been set  | 
4374  | 0  |     if (bGotCfGT || bGotGdalGT)  | 
4375  | 0  |     { | 
4376  | 0  |         m_bAddedProjectionVarsDefs = true;  | 
4377  | 0  |         m_bAddedProjectionVarsData = true;  | 
4378  | 0  |         SetGeoTransformNoUpdate(adfTempGeoTransform);  | 
4379  | 0  |     }  | 
4380  |  |  | 
4381  |  |     // Debugging reports.  | 
4382  | 0  |     CPLDebug("GDAL_netCDF", | 
4383  | 0  |              "bGotGeogCS=%d bGotCfSRS=%d bGotCfGT=%d bGotCfWktSRS=%d "  | 
4384  | 0  |              "bGotGdalSRS=%d bGotGdalGT=%d",  | 
4385  | 0  |              static_cast<int>(bGotGeogCS), static_cast<int>(bGotCfSRS),  | 
4386  | 0  |              static_cast<int>(bGotCfGT), static_cast<int>(bGotCfWktSRS),  | 
4387  | 0  |              static_cast<int>(bGotGdalSRS), static_cast<int>(bGotGdalGT));  | 
4388  |  | 
  | 
4389  | 0  |     if (!bGotCfGT && !bGotGdalGT)  | 
4390  | 0  |         CPLDebug("GDAL_netCDF", "did not get geotransform from CF nor GDAL!"); | 
4391  |  | 
  | 
4392  | 0  |     if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfGT && !bGotCfWktSRS)  | 
4393  | 0  |         CPLDebug("GDAL_netCDF", "did not get projection from CF nor GDAL!"); | 
4394  |  |  | 
4395  |  |     // wish of 6195  | 
4396  |  |     // we don't have CS/SRS, but we do have GT, and we live in -180,360 -90,90  | 
4397  | 0  |     if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfWktSRS)  | 
4398  | 0  |     { | 
4399  | 0  |         if (bGotCfGT || bGotGdalGT)  | 
4400  | 0  |         { | 
4401  | 0  |             bool bAssumedLongLat = CPLTestBool(CSLFetchNameValueDef(  | 
4402  | 0  |                 papszOpenOptions, "ASSUME_LONGLAT",  | 
4403  | 0  |                 CPLGetConfigOption("GDAL_NETCDF_ASSUME_LONGLAT", "NO"))); | 
4404  |  | 
  | 
4405  | 0  |             if (bAssumedLongLat && adfTempGeoTransform[0] >= -180 &&  | 
4406  | 0  |                 adfTempGeoTransform[0] < 360 &&  | 
4407  | 0  |                 (adfTempGeoTransform[0] +  | 
4408  | 0  |                  adfTempGeoTransform[1] * poDS->GetRasterXSize()) <= 360 &&  | 
4409  | 0  |                 adfTempGeoTransform[3] <= 90 && adfTempGeoTransform[3] > -90 &&  | 
4410  | 0  |                 (adfTempGeoTransform[3] +  | 
4411  | 0  |                  adfTempGeoTransform[5] * poDS->GetRasterYSize()) >= -90)  | 
4412  | 0  |             { | 
4413  |  | 
  | 
4414  | 0  |                 poDS->bIsGeographic = true;  | 
4415  | 0  |                 char *pszTempProjection = nullptr;  | 
4416  |  |                 // seems odd to use 4326 so OGC:CRS84  | 
4417  | 0  |                 oSRS.SetFromUserInput("OGC:CRS84"); | 
4418  | 0  |                 oSRS.exportToWkt(&pszTempProjection);  | 
4419  | 0  |                 if (returnProjStr != nullptr)  | 
4420  | 0  |                 { | 
4421  | 0  |                     (*returnProjStr) = std::string(pszTempProjection);  | 
4422  | 0  |                 }  | 
4423  | 0  |                 else  | 
4424  | 0  |                 { | 
4425  | 0  |                     m_bAddedProjectionVarsDefs = true;  | 
4426  | 0  |                     m_bAddedProjectionVarsData = true;  | 
4427  | 0  |                     SetSpatialRefNoUpdate(&oSRS);  | 
4428  | 0  |                 }  | 
4429  | 0  |                 CPLFree(pszTempProjection);  | 
4430  |  | 
  | 
4431  | 0  |                 CPLDebug("netCDF", | 
4432  | 0  |                          "Assumed Longitude Latitude CRS 'OGC:CRS84' because "  | 
4433  | 0  |                          "none otherwise available and geotransform within "  | 
4434  | 0  |                          "suitable bounds. "  | 
4435  | 0  |                          "Set GDAL_NETCDF_ASSUME_LONGLAT=NO as configuration "  | 
4436  | 0  |                          "option or "  | 
4437  | 0  |                          "    ASSUME_LONGLAT=NO as open option to bypass this "  | 
4438  | 0  |                          "assumption.");  | 
4439  | 0  |             }  | 
4440  | 0  |         }  | 
4441  | 0  |     }  | 
4442  |  |  | 
4443  |  | // Search for Well-known GeogCS if got only CF WKT  | 
4444  |  | // Disabled for now, as a named datum also include control points  | 
4445  |  | // (see mailing list and bug#4281  | 
4446  |  | // For example, WGS84 vs. GDA94 (EPSG:3577) - AEA in netcdf_cf.py  | 
4447  |  |  | 
4448  |  | // Disabled for now, but could be set in a config option.  | 
4449  |  | #if 0  | 
4450  |  |     bool bLookForWellKnownGCS = false;  // This could be a Config Option.  | 
4451  |  |  | 
4452  |  |     if( bLookForWellKnownGCS && bGotCfSRS && !bGotGdalSRS )  | 
4453  |  |     { | 
4454  |  |         // ET - Could use a more exhaustive method by scanning all EPSG codes in  | 
4455  |  |         // data/gcs.csv as proposed by Even in the gdal-dev mailing list "help  | 
4456  |  |         // for comparing two WKT".  | 
4457  |  |         // This code could be contributed to a new function.  | 
4458  |  |         // OGRSpatialReference * OGRSpatialReference::FindMatchingGeogCS(  | 
4459  |  |         //     const OGRSpatialReference *poOther) */  | 
4460  |  |         CPLDebug("GDAL_netCDF", "Searching for Well-known GeogCS"); | 
4461  |  |         const char *pszWKGCSList[] = { "WGS84", "WGS72", "NAD27", "NAD83" }; | 
4462  |  |         char *pszWKGCS = NULL;  | 
4463  |  |         oSRS.exportToPrettyWkt(&pszWKGCS);  | 
4464  |  |         for( size_t i = 0; i < sizeof(pszWKGCSList) / 8; i++ )  | 
4465  |  |         { | 
4466  |  |             pszWKGCS = CPLStrdup(pszWKGCSList[i]);  | 
4467  |  |             OGRSpatialReference oSRSTmp;  | 
4468  |  |             oSRSTmp.SetWellKnownGeogCS(pszWKGCSList[i]);  | 
4469  |  |             // Set datum to unknown, bug #4281.  | 
4470  |  |             if( oSRSTmp.GetAttrNode("DATUM" ) ) | 
4471  |  |                 oSRSTmp.GetAttrNode("DATUM")->GetChild(0)->SetValue("unknown"); | 
4472  |  |             // Could use OGRSpatialReference::StripCTParms(), but let's keep  | 
4473  |  |             // TOWGS84.  | 
4474  |  |             oSRSTmp.GetRoot()->StripNodes("AXIS"); | 
4475  |  |             oSRSTmp.GetRoot()->StripNodes("AUTHORITY"); | 
4476  |  |             oSRSTmp.GetRoot()->StripNodes("EXTENSION"); | 
4477  |  |  | 
4478  |  |             oSRSTmp.exportToPrettyWkt(&pszWKGCS);  | 
4479  |  |             if( oSRS.IsSameGeogCS(&oSRSTmp) )  | 
4480  |  |             { | 
4481  |  |                 oSRS.SetWellKnownGeogCS(pszWKGCSList[i]);  | 
4482  |  |                 oSRS.exportToWkt(&(pszTempProjection));  | 
4483  |  |                 SetProjection(pszTempProjection);  | 
4484  |  |                 CPLFree(pszTempProjection);  | 
4485  |  |             }  | 
4486  |  |         }  | 
4487  |  |     }  | 
4488  |  | #endif  | 
4489  | 0  | }  | 
4490  |  |  | 
4491  |  | void netCDFDataset::SetProjectionFromVar(int nGroupId, int nVarId,  | 
4492  |  |                                          bool bReadSRSOnly)  | 
4493  | 23  | { | 
4494  | 23  |     SetProjectionFromVar(nGroupId, nVarId, bReadSRSOnly, nullptr, nullptr,  | 
4495  | 23  |                          nullptr, nullptr);  | 
4496  | 23  | }  | 
4497  |  |  | 
4498  |  | bool netCDFDataset::ProcessNASAL2OceanGeoLocation(int nGroupId, int nVarId)  | 
4499  | 0  | { | 
4500  |  |     // Cf https://oceancolor.gsfc.nasa.gov/docs/format/l2nc/  | 
4501  |  |     // and https://github.com/OSGeo/gdal/issues/7605  | 
4502  |  |  | 
4503  |  |     // Check for a structure like:  | 
4504  |  |     /* netcdf SNPP_VIIRS.20230406T024200.L2.OC.NRT { | 
4505  |  |         dimensions:  | 
4506  |  |             number_of_lines = 3248 ;  | 
4507  |  |             pixels_per_line = 3200 ;  | 
4508  |  |             [...]  | 
4509  |  |             pixel_control_points = 3200 ;  | 
4510  |  |         [...]  | 
4511  |  |         group: geophysical_data { | 
4512  |  |           variables:  | 
4513  |  |             short aot_862(number_of_lines, pixels_per_line) ;  <-- nVarId  | 
4514  |  |                 [...]  | 
4515  |  |         }  | 
4516  |  |         group: navigation_data { | 
4517  |  |           variables:  | 
4518  |  |             float longitude(number_of_lines, pixel_control_points) ;  | 
4519  |  |                 [...]  | 
4520  |  |             float latitude(number_of_lines, pixel_control_points) ;  | 
4521  |  |                 [...]  | 
4522  |  |         }  | 
4523  |  |     }  | 
4524  |  |     */  | 
4525  |  |     // Note that the longitude and latitude arrays are not indexed by the  | 
4526  |  |     // same dimensions. Handle only the case where  | 
4527  |  |     // pixel_control_points == pixels_per_line  | 
4528  |  |     // If there was a subsampling of the geolocation arrays, we'd need to  | 
4529  |  |     // add more logic.  | 
4530  |  | 
  | 
4531  | 0  |     std::string osGroupName;  | 
4532  | 0  |     osGroupName.resize(NC_MAX_NAME);  | 
4533  | 0  |     NCDF_ERR(nc_inq_grpname(nGroupId, &osGroupName[0]));  | 
4534  | 0  |     osGroupName.resize(strlen(osGroupName.data()));  | 
4535  | 0  |     if (osGroupName != "geophysical_data")  | 
4536  | 0  |         return false;  | 
4537  |  |  | 
4538  | 0  |     int nVarDims = 0;  | 
4539  | 0  |     NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));  | 
4540  | 0  |     if (nVarDims != 2)  | 
4541  | 0  |         return false;  | 
4542  |  |  | 
4543  | 0  |     int nNavigationDataGrpId = 0;  | 
4544  | 0  |     if (nc_inq_grp_ncid(cdfid, "navigation_data", &nNavigationDataGrpId) !=  | 
4545  | 0  |         NC_NOERR)  | 
4546  | 0  |         return false;  | 
4547  |  |  | 
4548  | 0  |     std::array<int, 2> anVarDimIds;  | 
4549  | 0  |     NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));  | 
4550  |  | 
  | 
4551  | 0  |     int nLongitudeId = 0;  | 
4552  | 0  |     int nLatitudeId = 0;  | 
4553  | 0  |     if (nc_inq_varid(nNavigationDataGrpId, "longitude", &nLongitudeId) !=  | 
4554  | 0  |             NC_NOERR ||  | 
4555  | 0  |         nc_inq_varid(nNavigationDataGrpId, "latitude", &nLatitudeId) !=  | 
4556  | 0  |             NC_NOERR)  | 
4557  | 0  |     { | 
4558  | 0  |         return false;  | 
4559  | 0  |     }  | 
4560  |  |  | 
4561  | 0  |     int nDimsLongitude = 0;  | 
4562  | 0  |     NCDF_ERR(  | 
4563  | 0  |         nc_inq_varndims(nNavigationDataGrpId, nLongitudeId, &nDimsLongitude));  | 
4564  | 0  |     int nDimsLatitude = 0;  | 
4565  | 0  |     NCDF_ERR(  | 
4566  | 0  |         nc_inq_varndims(nNavigationDataGrpId, nLatitudeId, &nDimsLatitude));  | 
4567  | 0  |     if (!(nDimsLongitude == 2 && nDimsLatitude == 2))  | 
4568  | 0  |     { | 
4569  | 0  |         return false;  | 
4570  | 0  |     }  | 
4571  |  |  | 
4572  | 0  |     std::array<int, 2> anDimLongitudeIds;  | 
4573  | 0  |     NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLongitudeId,  | 
4574  | 0  |                              anDimLongitudeIds.data()));  | 
4575  | 0  |     std::array<int, 2> anDimLatitudeIds;  | 
4576  | 0  |     NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLatitudeId,  | 
4577  | 0  |                              anDimLatitudeIds.data()));  | 
4578  | 0  |     if (anDimLongitudeIds != anDimLatitudeIds)  | 
4579  | 0  |     { | 
4580  | 0  |         return false;  | 
4581  | 0  |     }  | 
4582  |  |  | 
4583  | 0  |     std::array<size_t, 2> anSizeVarDimIds;  | 
4584  | 0  |     std::array<size_t, 2> anSizeLongLatIds;  | 
4585  | 0  |     if (!(nc_inq_dimlen(cdfid, anVarDimIds[0], &anSizeVarDimIds[0]) ==  | 
4586  | 0  |               NC_NOERR &&  | 
4587  | 0  |           nc_inq_dimlen(cdfid, anVarDimIds[1], &anSizeVarDimIds[1]) ==  | 
4588  | 0  |               NC_NOERR &&  | 
4589  | 0  |           nc_inq_dimlen(cdfid, anDimLongitudeIds[0], &anSizeLongLatIds[0]) ==  | 
4590  | 0  |               NC_NOERR &&  | 
4591  | 0  |           nc_inq_dimlen(cdfid, anDimLongitudeIds[1], &anSizeLongLatIds[1]) ==  | 
4592  | 0  |               NC_NOERR &&  | 
4593  | 0  |           anSizeVarDimIds == anSizeLongLatIds))  | 
4594  | 0  |     { | 
4595  | 0  |         return false;  | 
4596  | 0  |     }  | 
4597  |  |  | 
4598  | 0  |     const char *pszGeolocXFullName = "/navigation_data/longitude";  | 
4599  | 0  |     const char *pszGeolocYFullName = "/navigation_data/latitude";  | 
4600  |  | 
  | 
4601  | 0  |     if (bSwitchedXY)  | 
4602  | 0  |     { | 
4603  | 0  |         std::swap(pszGeolocXFullName, pszGeolocYFullName);  | 
4604  | 0  |         GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", "GEOLOCATION"); | 
4605  | 0  |     }  | 
4606  |  | 
  | 
4607  | 0  |     CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION", | 
4608  | 0  |              pszGeolocXFullName, pszGeolocYFullName);  | 
4609  |  | 
  | 
4610  | 0  |     GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG, | 
4611  | 0  |                                     "GEOLOCATION");  | 
4612  |  | 
  | 
4613  | 0  |     CPLString osTMP;  | 
4614  | 0  |     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName); | 
4615  |  | 
  | 
4616  | 0  |     GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION"); | 
4617  | 0  |     GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION"); | 
4618  | 0  |     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName); | 
4619  |  | 
  | 
4620  | 0  |     GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION"); | 
4621  | 0  |     GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION"); | 
4622  |  | 
  | 
4623  | 0  |     GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION"); | 
4624  | 0  |     GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION"); | 
4625  |  | 
  | 
4626  | 0  |     GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION"); | 
4627  | 0  |     GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION"); | 
4628  |  | 
  | 
4629  | 0  |     GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER", | 
4630  | 0  |                                     "GEOLOCATION");  | 
4631  | 0  |     return true;  | 
4632  | 0  | }  | 
4633  |  |  | 
4634  |  | bool netCDFDataset::ProcessNASAEMITGeoLocation(int nGroupId, int nVarId)  | 
4635  | 0  | { | 
4636  |  |     // Cf https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/  | 
4637  |  |  | 
4638  |  |     // Check for a structure like:  | 
4639  |  |     /* netcdf EMIT_L2A_RFL_001_20220903T163129_2224611_012 { | 
4640  |  |         dimensions:  | 
4641  |  |             downtrack = 1280 ;  | 
4642  |  |             crosstrack = 1242 ;  | 
4643  |  |             bands = 285 ;  | 
4644  |  |             [...]  | 
4645  |  |  | 
4646  |  |         variables:  | 
4647  |  |             float reflectance(downtrack, crosstrack, bands) ;  | 
4648  |  |  | 
4649  |  |         group: location { | 
4650  |  |           variables:  | 
4651  |  |                 double lon(downtrack, crosstrack) ;  | 
4652  |  |                         lon:_FillValue = -9999. ;  | 
4653  |  |                         lon:long_name = "Longitude (WGS-84)" ;  | 
4654  |  |                         lon:units = "degrees east" ;  | 
4655  |  |                 double lat(downtrack, crosstrack) ;  | 
4656  |  |                         lat:_FillValue = -9999. ;  | 
4657  |  |                         lat:long_name = "Latitude (WGS-84)" ;  | 
4658  |  |                         lat:units = "degrees north" ;  | 
4659  |  |           } // group location  | 
4660  |  |  | 
4661  |  |     }  | 
4662  |  |     or  | 
4663  |  |     netcdf EMIT_L2B_MIN_001_20231024T055538_2329704_040 { | 
4664  |  |         dimensions:  | 
4665  |  |                 downtrack = 1664 ;  | 
4666  |  |                 crosstrack = 1242 ;  | 
4667  |  |                 [...]  | 
4668  |  |         variables:  | 
4669  |  |                 float group_1_band_depth(downtrack, crosstrack) ;  | 
4670  |  |                         group_1_band_depth:_FillValue = -9999.f ;  | 
4671  |  |                         group_1_band_depth:long_name = "Group 1 Band Depth" ;  | 
4672  |  |                         group_1_band_depth:units = "unitless" ;  | 
4673  |  |                 [...]  | 
4674  |  |         group: location { | 
4675  |  |           variables:  | 
4676  |  |                 double lon(downtrack, crosstrack) ;  | 
4677  |  |                         lon:_FillValue = -9999. ;  | 
4678  |  |                         lon:long_name = "Longitude (WGS-84)" ;  | 
4679  |  |                         lon:units = "degrees east" ;  | 
4680  |  |                 double lat(downtrack, crosstrack) ;  | 
4681  |  |                         lat:_FillValue = -9999. ;  | 
4682  |  |                         lat:long_name = "Latitude (WGS-84)" ;  | 
4683  |  |                         lat:units = "degrees north" ;  | 
4684  |  |         }  | 
4685  |  |     */  | 
4686  |  | 
  | 
4687  | 0  |     int nVarDims = 0;  | 
4688  | 0  |     NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));  | 
4689  | 0  |     if (nVarDims != 2 && nVarDims != 3)  | 
4690  | 0  |         return false;  | 
4691  |  |  | 
4692  | 0  |     int nLocationGrpId = 0;  | 
4693  | 0  |     if (nc_inq_grp_ncid(cdfid, "location", &nLocationGrpId) != NC_NOERR)  | 
4694  | 0  |         return false;  | 
4695  |  |  | 
4696  | 0  |     std::array<int, 3> anVarDimIds;  | 
4697  | 0  |     NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));  | 
4698  | 0  |     if (nYDimID != anVarDimIds[0] || nXDimID != anVarDimIds[1])  | 
4699  | 0  |         return false;  | 
4700  |  |  | 
4701  | 0  |     int nLongitudeId = 0;  | 
4702  | 0  |     int nLatitudeId = 0;  | 
4703  | 0  |     if (nc_inq_varid(nLocationGrpId, "lon", &nLongitudeId) != NC_NOERR ||  | 
4704  | 0  |         nc_inq_varid(nLocationGrpId, "lat", &nLatitudeId) != NC_NOERR)  | 
4705  | 0  |     { | 
4706  | 0  |         return false;  | 
4707  | 0  |     }  | 
4708  |  |  | 
4709  | 0  |     int nDimsLongitude = 0;  | 
4710  | 0  |     NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLongitudeId, &nDimsLongitude));  | 
4711  | 0  |     int nDimsLatitude = 0;  | 
4712  | 0  |     NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLatitudeId, &nDimsLatitude));  | 
4713  | 0  |     if (!(nDimsLongitude == 2 && nDimsLatitude == 2))  | 
4714  | 0  |     { | 
4715  | 0  |         return false;  | 
4716  | 0  |     }  | 
4717  |  |  | 
4718  | 0  |     std::array<int, 2> anDimLongitudeIds;  | 
4719  | 0  |     NCDF_ERR(nc_inq_vardimid(nLocationGrpId, nLongitudeId,  | 
4720  | 0  |                              anDimLongitudeIds.data()));  | 
4721  | 0  |     std::array<int, 2> anDimLatitudeIds;  | 
4722  | 0  |     NCDF_ERR(  | 
4723  | 0  |         nc_inq_vardimid(nLocationGrpId, nLatitudeId, anDimLatitudeIds.data()));  | 
4724  | 0  |     if (anDimLongitudeIds != anDimLatitudeIds)  | 
4725  | 0  |     { | 
4726  | 0  |         return false;  | 
4727  | 0  |     }  | 
4728  |  |  | 
4729  | 0  |     if (anDimLongitudeIds[0] != anVarDimIds[0] ||  | 
4730  | 0  |         anDimLongitudeIds[1] != anVarDimIds[1])  | 
4731  | 0  |     { | 
4732  | 0  |         return false;  | 
4733  | 0  |     }  | 
4734  |  |  | 
4735  | 0  |     const char *pszGeolocXFullName = "/location/lon";  | 
4736  | 0  |     const char *pszGeolocYFullName = "/location/lat";  | 
4737  |  | 
  | 
4738  | 0  |     CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION", | 
4739  | 0  |              pszGeolocXFullName, pszGeolocYFullName);  | 
4740  |  | 
  | 
4741  | 0  |     GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG, | 
4742  | 0  |                                     "GEOLOCATION");  | 
4743  |  | 
  | 
4744  | 0  |     CPLString osTMP;  | 
4745  | 0  |     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName); | 
4746  |  | 
  | 
4747  | 0  |     GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION"); | 
4748  | 0  |     GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION"); | 
4749  | 0  |     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName); | 
4750  |  | 
  | 
4751  | 0  |     GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION"); | 
4752  | 0  |     GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION"); | 
4753  |  | 
  | 
4754  | 0  |     GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION"); | 
4755  | 0  |     GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION"); | 
4756  |  | 
  | 
4757  | 0  |     GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION"); | 
4758  | 0  |     GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION"); | 
4759  |  | 
  | 
4760  | 0  |     GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER", | 
4761  | 0  |                                     "GEOLOCATION");  | 
4762  | 0  |     return true;  | 
4763  | 0  | }  | 
4764  |  |  | 
4765  |  | int netCDFDataset::ProcessCFGeolocation(int nGroupId, int nVarId,  | 
4766  |  |                                         const std::string &osGeolocWKT,  | 
4767  |  |                                         std::string &osGeolocXNameOut,  | 
4768  |  |                                         std::string &osGeolocYNameOut)  | 
4769  | 0  | { | 
4770  | 0  |     bool bAddGeoloc = false;  | 
4771  | 0  |     char *pszCoordinates = nullptr;  | 
4772  |  |  | 
4773  |  |     // If there is no explicit "coordinates" attribute, check if there are  | 
4774  |  |     // "lon" and "lat" 2D variables whose dimensions are the last  | 
4775  |  |     // 2 ones of the variable of interest.  | 
4776  | 0  |     if (NCDFGetAttr(nGroupId, nVarId, "coordinates", &pszCoordinates) !=  | 
4777  | 0  |         CE_None)  | 
4778  | 0  |     { | 
4779  | 0  |         CPLFree(pszCoordinates);  | 
4780  | 0  |         pszCoordinates = nullptr;  | 
4781  |  | 
  | 
4782  | 0  |         int nVarDims = 0;  | 
4783  | 0  |         NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));  | 
4784  | 0  |         if (nVarDims >= 2)  | 
4785  | 0  |         { | 
4786  | 0  |             std::vector<int> anVarDimIds(nVarDims);  | 
4787  | 0  |             NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));  | 
4788  |  | 
  | 
4789  | 0  |             int nLongitudeId = 0;  | 
4790  | 0  |             int nLatitudeId = 0;  | 
4791  | 0  |             if (nc_inq_varid(nGroupId, "lon", &nLongitudeId) == NC_NOERR &&  | 
4792  | 0  |                 nc_inq_varid(nGroupId, "lat", &nLatitudeId) == NC_NOERR)  | 
4793  | 0  |             { | 
4794  | 0  |                 int nDimsLongitude = 0;  | 
4795  | 0  |                 NCDF_ERR(  | 
4796  | 0  |                     nc_inq_varndims(nGroupId, nLongitudeId, &nDimsLongitude));  | 
4797  | 0  |                 int nDimsLatitude = 0;  | 
4798  | 0  |                 NCDF_ERR(  | 
4799  | 0  |                     nc_inq_varndims(nGroupId, nLatitudeId, &nDimsLatitude));  | 
4800  | 0  |                 if (nDimsLongitude == 2 && nDimsLatitude == 2)  | 
4801  | 0  |                 { | 
4802  | 0  |                     std::vector<int> anDimLongitudeIds(2);  | 
4803  | 0  |                     NCDF_ERR(nc_inq_vardimid(nGroupId, nLongitudeId,  | 
4804  | 0  |                                              anDimLongitudeIds.data()));  | 
4805  | 0  |                     std::vector<int> anDimLatitudeIds(2);  | 
4806  | 0  |                     NCDF_ERR(nc_inq_vardimid(nGroupId, nLatitudeId,  | 
4807  | 0  |                                              anDimLatitudeIds.data()));  | 
4808  | 0  |                     if (anDimLongitudeIds == anDimLatitudeIds &&  | 
4809  | 0  |                         anVarDimIds[anVarDimIds.size() - 2] ==  | 
4810  | 0  |                             anDimLongitudeIds[0] &&  | 
4811  | 0  |                         anVarDimIds[anVarDimIds.size() - 1] ==  | 
4812  | 0  |                             anDimLongitudeIds[1])  | 
4813  | 0  |                     { | 
4814  | 0  |                         pszCoordinates = CPLStrdup("lon lat"); | 
4815  | 0  |                     }  | 
4816  | 0  |                 }  | 
4817  | 0  |             }  | 
4818  | 0  |         }  | 
4819  | 0  |     }  | 
4820  |  | 
  | 
4821  | 0  |     if (pszCoordinates)  | 
4822  | 0  |     { | 
4823  |  |         // Get X and Y geolocation names from coordinates attribute.  | 
4824  | 0  |         const CPLStringList aosCoordinates(  | 
4825  | 0  |             NCDFTokenizeCoordinatesAttribute(pszCoordinates));  | 
4826  | 0  |         if (aosCoordinates.size() >= 2)  | 
4827  | 0  |         { | 
4828  | 0  |             char szGeolocXName[NC_MAX_NAME + 1];  | 
4829  | 0  |             char szGeolocYName[NC_MAX_NAME + 1];  | 
4830  | 0  |             szGeolocXName[0] = '\0';  | 
4831  | 0  |             szGeolocYName[0] = '\0';  | 
4832  |  |  | 
4833  |  |             // Test that each variable is longitude/latitude.  | 
4834  | 0  |             for (int i = 0; i < aosCoordinates.size(); i++)  | 
4835  | 0  |             { | 
4836  | 0  |                 if (NCDFIsVarLongitude(nGroupId, -1, aosCoordinates[i]))  | 
4837  | 0  |                 { | 
4838  | 0  |                     int nOtherGroupId = -1;  | 
4839  | 0  |                     int nOtherVarId = -1;  | 
4840  |  |                     // Check that the variable actually exists  | 
4841  |  |                     // Needed on Sentinel-3 products  | 
4842  | 0  |                     if (NCDFResolveVar(nGroupId, aosCoordinates[i],  | 
4843  | 0  |                                        &nOtherGroupId, &nOtherVarId) == CE_None)  | 
4844  | 0  |                     { | 
4845  | 0  |                         snprintf(szGeolocXName, sizeof(szGeolocXName), "%s",  | 
4846  | 0  |                                  aosCoordinates[i]);  | 
4847  | 0  |                     }  | 
4848  | 0  |                 }  | 
4849  | 0  |                 else if (NCDFIsVarLatitude(nGroupId, -1, aosCoordinates[i]))  | 
4850  | 0  |                 { | 
4851  | 0  |                     int nOtherGroupId = -1;  | 
4852  | 0  |                     int nOtherVarId = -1;  | 
4853  |  |                     // Check that the variable actually exists  | 
4854  |  |                     // Needed on Sentinel-3 products  | 
4855  | 0  |                     if (NCDFResolveVar(nGroupId, aosCoordinates[i],  | 
4856  | 0  |                                        &nOtherGroupId, &nOtherVarId) == CE_None)  | 
4857  | 0  |                     { | 
4858  | 0  |                         snprintf(szGeolocYName, sizeof(szGeolocYName), "%s",  | 
4859  | 0  |                                  aosCoordinates[i]);  | 
4860  | 0  |                     }  | 
4861  | 0  |                 }  | 
4862  | 0  |             }  | 
4863  |  |             // Add GEOLOCATION metadata.  | 
4864  | 0  |             if (!EQUAL(szGeolocXName, "") && !EQUAL(szGeolocYName, ""))  | 
4865  | 0  |             { | 
4866  | 0  |                 osGeolocXNameOut = szGeolocXName;  | 
4867  | 0  |                 osGeolocYNameOut = szGeolocYName;  | 
4868  |  | 
  | 
4869  | 0  |                 char *pszGeolocXFullName = nullptr;  | 
4870  | 0  |                 char *pszGeolocYFullName = nullptr;  | 
4871  | 0  |                 if (NCDFResolveVarFullName(nGroupId, szGeolocXName,  | 
4872  | 0  |                                            &pszGeolocXFullName) == CE_None &&  | 
4873  | 0  |                     NCDFResolveVarFullName(nGroupId, szGeolocYName,  | 
4874  | 0  |                                            &pszGeolocYFullName) == CE_None)  | 
4875  | 0  |                 { | 
4876  | 0  |                     if (bSwitchedXY)  | 
4877  | 0  |                     { | 
4878  | 0  |                         std::swap(pszGeolocXFullName, pszGeolocYFullName);  | 
4879  | 0  |                         GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", | 
4880  | 0  |                                                         "GEOLOCATION");  | 
4881  | 0  |                     }  | 
4882  |  | 
  | 
4883  | 0  |                     bAddGeoloc = true;  | 
4884  | 0  |                     CPLDebug("GDAL_netCDF", | 
4885  | 0  |                              "using variables %s and %s for GEOLOCATION",  | 
4886  | 0  |                              pszGeolocXFullName, pszGeolocYFullName);  | 
4887  |  | 
  | 
4888  | 0  |                     GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(), | 
4889  | 0  |                                                     "GEOLOCATION");  | 
4890  |  | 
  | 
4891  | 0  |                     CPLString osTMP;  | 
4892  | 0  |                     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), | 
4893  | 0  |                                  pszGeolocXFullName);  | 
4894  |  | 
  | 
4895  | 0  |                     GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, | 
4896  | 0  |                                                     "GEOLOCATION");  | 
4897  | 0  |                     GDALPamDataset::SetMetadataItem("X_BAND", "1", | 
4898  | 0  |                                                     "GEOLOCATION");  | 
4899  | 0  |                     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), | 
4900  | 0  |                                  pszGeolocYFullName);  | 
4901  |  | 
  | 
4902  | 0  |                     GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, | 
4903  | 0  |                                                     "GEOLOCATION");  | 
4904  | 0  |                     GDALPamDataset::SetMetadataItem("Y_BAND", "1", | 
4905  | 0  |                                                     "GEOLOCATION");  | 
4906  |  | 
  | 
4907  | 0  |                     GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", | 
4908  | 0  |                                                     "GEOLOCATION");  | 
4909  | 0  |                     GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", | 
4910  | 0  |                                                     "GEOLOCATION");  | 
4911  |  | 
  | 
4912  | 0  |                     GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", | 
4913  | 0  |                                                     "GEOLOCATION");  | 
4914  | 0  |                     GDALPamDataset::SetMetadataItem("LINE_STEP", "1", | 
4915  | 0  |                                                     "GEOLOCATION");  | 
4916  |  | 
  | 
4917  | 0  |                     GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", | 
4918  | 0  |                                                     "PIXEL_CENTER",  | 
4919  | 0  |                                                     "GEOLOCATION");  | 
4920  | 0  |                 }  | 
4921  | 0  |                 else  | 
4922  | 0  |                 { | 
4923  | 0  |                     CPLDebug("GDAL_netCDF", | 
4924  | 0  |                              "cannot resolve location of "  | 
4925  | 0  |                              "lat/lon variables specified by the coordinates "  | 
4926  | 0  |                              "attribute [%s]",  | 
4927  | 0  |                              pszCoordinates);  | 
4928  | 0  |                 }  | 
4929  | 0  |                 CPLFree(pszGeolocXFullName);  | 
4930  | 0  |                 CPLFree(pszGeolocYFullName);  | 
4931  | 0  |             }  | 
4932  | 0  |             else  | 
4933  | 0  |             { | 
4934  | 0  |                 CPLDebug("GDAL_netCDF", | 
4935  | 0  |                          "coordinates attribute [%s] is unsupported",  | 
4936  | 0  |                          pszCoordinates);  | 
4937  | 0  |             }  | 
4938  | 0  |         }  | 
4939  | 0  |         else  | 
4940  | 0  |         { | 
4941  | 0  |             CPLDebug("GDAL_netCDF", | 
4942  | 0  |                      "coordinates attribute [%s] with %d element(s) is "  | 
4943  | 0  |                      "unsupported",  | 
4944  | 0  |                      pszCoordinates, aosCoordinates.size());  | 
4945  | 0  |         }  | 
4946  | 0  |     }  | 
4947  |  |  | 
4948  | 0  |     else  | 
4949  | 0  |     { | 
4950  | 0  |         bAddGeoloc = ProcessNASAL2OceanGeoLocation(nGroupId, nVarId);  | 
4951  |  | 
  | 
4952  | 0  |         if (!bAddGeoloc)  | 
4953  | 0  |             bAddGeoloc = ProcessNASAEMITGeoLocation(nGroupId, nVarId);  | 
4954  | 0  |     }  | 
4955  |  | 
  | 
4956  | 0  |     CPLFree(pszCoordinates);  | 
4957  |  | 
  | 
4958  | 0  |     return bAddGeoloc;  | 
4959  | 0  | }  | 
4960  |  |  | 
4961  |  | CPLErr netCDFDataset::Set1DGeolocation(int nGroupId, int nVarId,  | 
4962  |  |                                        const char *szDimName)  | 
4963  | 0  | { | 
4964  |  |     // Get values.  | 
4965  | 0  |     char *pszVarValues = nullptr;  | 
4966  | 0  |     CPLErr eErr = NCDFGet1DVar(nGroupId, nVarId, &pszVarValues);  | 
4967  | 0  |     if (eErr != CE_None)  | 
4968  | 0  |         return eErr;  | 
4969  |  |  | 
4970  |  |     // Write metadata.  | 
4971  | 0  |     char szTemp[NC_MAX_NAME + 1 + 32] = {}; | 
4972  | 0  |     snprintf(szTemp, sizeof(szTemp), "%s_VALUES", szDimName);  | 
4973  | 0  |     GDALPamDataset::SetMetadataItem(szTemp, pszVarValues, "GEOLOCATION2");  | 
4974  |  | 
  | 
4975  | 0  |     CPLFree(pszVarValues);  | 
4976  |  | 
  | 
4977  | 0  |     return CE_None;  | 
4978  | 0  | }  | 
4979  |  |  | 
4980  |  | double *netCDFDataset::Get1DGeolocation(CPL_UNUSED const char *szDimName,  | 
4981  |  |                                         int &nVarLen)  | 
4982  | 0  | { | 
4983  | 0  |     nVarLen = 0;  | 
4984  |  |  | 
4985  |  |     // Get Y_VALUES as tokens.  | 
4986  | 0  |     char **papszValues =  | 
4987  | 0  |         NCDFTokenizeArray(GetMetadataItem("Y_VALUES", "GEOLOCATION2")); | 
4988  | 0  |     if (papszValues == nullptr)  | 
4989  | 0  |         return nullptr;  | 
4990  |  |  | 
4991  |  |     // Initialize and fill array.  | 
4992  | 0  |     nVarLen = CSLCount(papszValues);  | 
4993  | 0  |     double *pdfVarValues =  | 
4994  | 0  |         static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));  | 
4995  |  | 
  | 
4996  | 0  |     for (int i = 0, j = 0; i < nVarLen; i++)  | 
4997  | 0  |     { | 
4998  | 0  |         if (!bBottomUp)  | 
4999  | 0  |             j = nVarLen - 1 - i;  | 
5000  | 0  |         else  | 
5001  | 0  |             j = i;  // Invert latitude values.  | 
5002  | 0  |         char *pszTemp = nullptr;  | 
5003  | 0  |         pdfVarValues[j] = CPLStrtod(papszValues[i], &pszTemp);  | 
5004  | 0  |     }  | 
5005  | 0  |     CSLDestroy(papszValues);  | 
5006  |  | 
  | 
5007  | 0  |     return pdfVarValues;  | 
5008  | 0  | }  | 
5009  |  |  | 
5010  |  | /************************************************************************/  | 
5011  |  | /*                        SetSpatialRefNoUpdate()                       */  | 
5012  |  | /************************************************************************/  | 
5013  |  |  | 
5014  |  | void netCDFDataset::SetSpatialRefNoUpdate(const OGRSpatialReference *poSRS)  | 
5015  | 0  | { | 
5016  | 0  |     m_oSRS.Clear();  | 
5017  | 0  |     if (poSRS)  | 
5018  | 0  |         m_oSRS = *poSRS;  | 
5019  | 0  |     m_bHasProjection = true;  | 
5020  | 0  | }  | 
5021  |  |  | 
5022  |  | /************************************************************************/  | 
5023  |  | /*                          SetSpatialRef()                             */  | 
5024  |  | /************************************************************************/  | 
5025  |  |  | 
5026  |  | CPLErr netCDFDataset::SetSpatialRef(const OGRSpatialReference *poSRS)  | 
5027  | 0  | { | 
5028  | 0  |     CPLMutexHolderD(&hNCMutex);  | 
5029  |  | 
  | 
5030  | 0  |     if (GetAccess() != GA_Update || m_bHasProjection)  | 
5031  | 0  |     { | 
5032  | 0  |         CPLError(CE_Failure, CPLE_AppDefined,  | 
5033  | 0  |                  "netCDFDataset::_SetProjection() should only be called once "  | 
5034  | 0  |                  "in update mode!");  | 
5035  | 0  |         return CE_Failure;  | 
5036  | 0  |     }  | 
5037  |  |  | 
5038  | 0  |     if (m_bHasGeoTransform)  | 
5039  | 0  |     { | 
5040  | 0  |         SetSpatialRefNoUpdate(poSRS);  | 
5041  |  |  | 
5042  |  |         // For NC4/NC4C, writing both projection variables and data,  | 
5043  |  |         // followed by redefining nodata value, cancels the projection  | 
5044  |  |         // info from the Band variable, so for now only write the  | 
5045  |  |         // variable definitions, and write data at the end.  | 
5046  |  |         // See https://trac.osgeo.org/gdal/ticket/7245  | 
5047  | 0  |         return AddProjectionVars(true, nullptr, nullptr);  | 
5048  | 0  |     }  | 
5049  |  |  | 
5050  | 0  |     SetSpatialRefNoUpdate(poSRS);  | 
5051  |  | 
  | 
5052  | 0  |     return CE_None;  | 
5053  | 0  | }  | 
5054  |  |  | 
5055  |  | /************************************************************************/  | 
5056  |  | /*                     SetGeoTransformNoUpdate()                        */  | 
5057  |  | /************************************************************************/  | 
5058  |  |  | 
5059  |  | void netCDFDataset::SetGeoTransformNoUpdate(double *padfTransform)  | 
5060  | 0  | { | 
5061  | 0  |     memcpy(m_adfGeoTransform, padfTransform, sizeof(double) * 6);  | 
5062  | 0  |     m_bHasGeoTransform = true;  | 
5063  | 0  | }  | 
5064  |  |  | 
5065  |  | /************************************************************************/  | 
5066  |  | /*                          SetGeoTransform()                           */  | 
5067  |  | /************************************************************************/  | 
5068  |  |  | 
5069  |  | CPLErr netCDFDataset::SetGeoTransform(double *padfTransform)  | 
5070  | 0  | { | 
5071  | 0  |     CPLMutexHolderD(&hNCMutex);  | 
5072  |  | 
  | 
5073  | 0  |     if (GetAccess() != GA_Update || m_bHasGeoTransform)  | 
5074  | 0  |     { | 
5075  | 0  |         CPLError(CE_Failure, CPLE_AppDefined,  | 
5076  | 0  |                  "netCDFDataset::SetGeoTransform() should only be called once "  | 
5077  | 0  |                  "in update mode!");  | 
5078  | 0  |         return CE_Failure;  | 
5079  | 0  |     }  | 
5080  |  |  | 
5081  | 0  |     CPLDebug("GDAL_netCDF", "SetGeoTransform(%f,%f,%f,%f,%f,%f)", | 
5082  | 0  |              padfTransform[0], padfTransform[1], padfTransform[2],  | 
5083  | 0  |              padfTransform[3], padfTransform[4], padfTransform[5]);  | 
5084  |  | 
  | 
5085  | 0  |     if (m_bHasProjection)  | 
5086  | 0  |     { | 
5087  | 0  |         SetGeoTransformNoUpdate(padfTransform);  | 
5088  |  |  | 
5089  |  |         // For NC4/NC4C, writing both projection variables and data,  | 
5090  |  |         // followed by redefining nodata value, cancels the projection  | 
5091  |  |         // info from the Band variable, so for now only write the  | 
5092  |  |         // variable definitions, and write data at the end.  | 
5093  |  |         // See https://trac.osgeo.org/gdal/ticket/7245  | 
5094  | 0  |         return AddProjectionVars(true, nullptr, nullptr);  | 
5095  | 0  |     }  | 
5096  |  |  | 
5097  | 0  |     SetGeoTransformNoUpdate(padfTransform);  | 
5098  | 0  |     return CE_None;  | 
5099  | 0  | }  | 
5100  |  |  | 
5101  |  | /************************************************************************/  | 
5102  |  | /*                         NCDFWriteSRSVariable()                       */  | 
5103  |  | /************************************************************************/  | 
5104  |  |  | 
5105  |  | int NCDFWriteSRSVariable(int cdfid, const OGRSpatialReference *poSRS,  | 
5106  |  |                          char **ppszCFProjection, bool bWriteGDALTags,  | 
5107  |  |                          const std::string &srsVarName)  | 
5108  | 0  | { | 
5109  | 0  |     char *pszCFProjection = nullptr;  | 
5110  | 0  |     char **papszKeyValues = nullptr;  | 
5111  | 0  |     poSRS->exportToCF1(&pszCFProjection, &papszKeyValues, nullptr, nullptr);  | 
5112  |  | 
  | 
5113  | 0  |     if (bWriteGDALTags)  | 
5114  | 0  |     { | 
5115  | 0  |         const char *pszWKT = CSLFetchNameValue(papszKeyValues, NCDF_CRS_WKT);  | 
5116  | 0  |         if (pszWKT)  | 
5117  | 0  |         { | 
5118  |  |             // SPATIAL_REF is deprecated. Will be removed in GDAL 4.  | 
5119  | 0  |             papszKeyValues =  | 
5120  | 0  |                 CSLSetNameValue(papszKeyValues, NCDF_SPATIAL_REF, pszWKT);  | 
5121  | 0  |         }  | 
5122  | 0  |     }  | 
5123  |  | 
  | 
5124  | 0  |     const int nValues = CSLCount(papszKeyValues);  | 
5125  |  | 
  | 
5126  | 0  |     int NCDFVarID;  | 
5127  | 0  |     std::string varNameRadix(pszCFProjection);  | 
5128  | 0  |     int nCounter = 2;  | 
5129  | 0  |     while (true)  | 
5130  | 0  |     { | 
5131  | 0  |         NCDFVarID = -1;  | 
5132  | 0  |         nc_inq_varid(cdfid, pszCFProjection, &NCDFVarID);  | 
5133  | 0  |         if (NCDFVarID < 0)  | 
5134  | 0  |             break;  | 
5135  |  |  | 
5136  | 0  |         int nbAttr = 0;  | 
5137  | 0  |         NCDF_ERR(nc_inq_varnatts(cdfid, NCDFVarID, &nbAttr));  | 
5138  | 0  |         bool bSame = nbAttr == nValues;  | 
5139  | 0  |         for (int i = 0; bSame && (i < nbAttr); i++)  | 
5140  | 0  |         { | 
5141  | 0  |             char szAttrName[NC_MAX_NAME + 1];  | 
5142  | 0  |             szAttrName[0] = 0;  | 
5143  | 0  |             NCDF_ERR(nc_inq_attname(cdfid, NCDFVarID, i, szAttrName));  | 
5144  |  | 
  | 
5145  | 0  |             const char *pszValue =  | 
5146  | 0  |                 CSLFetchNameValue(papszKeyValues, szAttrName);  | 
5147  | 0  |             if (!pszValue)  | 
5148  | 0  |             { | 
5149  | 0  |                 bSame = false;  | 
5150  | 0  |                 break;  | 
5151  | 0  |             }  | 
5152  |  |  | 
5153  | 0  |             nc_type atttype = NC_NAT;  | 
5154  | 0  |             size_t attlen = 0;  | 
5155  | 0  |             NCDF_ERR(  | 
5156  | 0  |                 nc_inq_att(cdfid, NCDFVarID, szAttrName, &atttype, &attlen));  | 
5157  | 0  |             if (atttype != NC_CHAR && atttype != NC_DOUBLE)  | 
5158  | 0  |             { | 
5159  | 0  |                 bSame = false;  | 
5160  | 0  |                 break;  | 
5161  | 0  |             }  | 
5162  | 0  |             if (atttype == NC_CHAR)  | 
5163  | 0  |             { | 
5164  | 0  |                 if (CPLGetValueType(pszValue) != CPL_VALUE_STRING)  | 
5165  | 0  |                 { | 
5166  | 0  |                     bSame = false;  | 
5167  | 0  |                     break;  | 
5168  | 0  |                 }  | 
5169  | 0  |                 std::string val;  | 
5170  | 0  |                 val.resize(attlen);  | 
5171  | 0  |                 nc_get_att_text(cdfid, NCDFVarID, szAttrName, &val[0]);  | 
5172  | 0  |                 if (val != pszValue)  | 
5173  | 0  |                 { | 
5174  | 0  |                     bSame = false;  | 
5175  | 0  |                     break;  | 
5176  | 0  |                 }  | 
5177  | 0  |             }  | 
5178  | 0  |             else  | 
5179  | 0  |             { | 
5180  | 0  |                 const CPLStringList aosTokens(  | 
5181  | 0  |                     CSLTokenizeString2(pszValue, ",", 0));  | 
5182  | 0  |                 if (static_cast<size_t>(aosTokens.size()) != attlen)  | 
5183  | 0  |                 { | 
5184  | 0  |                     bSame = false;  | 
5185  | 0  |                     break;  | 
5186  | 0  |                 }  | 
5187  | 0  |                 double vals[2];  | 
5188  | 0  |                 nc_get_att_double(cdfid, NCDFVarID, szAttrName, vals);  | 
5189  | 0  |                 if (vals[0] != CPLAtof(aosTokens[0]) ||  | 
5190  | 0  |                     (attlen == 2 && vals[1] != CPLAtof(aosTokens[1])))  | 
5191  | 0  |                 { | 
5192  | 0  |                     bSame = false;  | 
5193  | 0  |                     break;  | 
5194  | 0  |                 }  | 
5195  | 0  |             }  | 
5196  | 0  |         }  | 
5197  | 0  |         if (bSame)  | 
5198  | 0  |         { | 
5199  | 0  |             *ppszCFProjection = pszCFProjection;  | 
5200  | 0  |             CSLDestroy(papszKeyValues);  | 
5201  | 0  |             return NCDFVarID;  | 
5202  | 0  |         }  | 
5203  | 0  |         CPLFree(pszCFProjection);  | 
5204  | 0  |         pszCFProjection =  | 
5205  | 0  |             CPLStrdup(CPLSPrintf("%s_%d", varNameRadix.c_str(), nCounter)); | 
5206  | 0  |         nCounter++;  | 
5207  | 0  |     }  | 
5208  |  |  | 
5209  | 0  |     *ppszCFProjection = pszCFProjection;  | 
5210  |  | 
  | 
5211  | 0  |     const char *pszVarName;  | 
5212  |  | 
  | 
5213  | 0  |     if (srsVarName != "")  | 
5214  | 0  |     { | 
5215  | 0  |         pszVarName = srsVarName.c_str();  | 
5216  | 0  |     }  | 
5217  | 0  |     else  | 
5218  | 0  |     { | 
5219  | 0  |         pszVarName = pszCFProjection;  | 
5220  | 0  |     }  | 
5221  |  | 
  | 
5222  | 0  |     int status = nc_def_var(cdfid, pszVarName, NC_CHAR, 0, nullptr, &NCDFVarID);  | 
5223  | 0  |     NCDF_ERR(status);  | 
5224  | 0  |     for (int i = 0; i < nValues; ++i)  | 
5225  | 0  |     { | 
5226  | 0  |         char *pszKey = nullptr;  | 
5227  | 0  |         const char *pszValue = CPLParseNameValue(papszKeyValues[i], &pszKey);  | 
5228  | 0  |         if (pszKey && pszValue)  | 
5229  | 0  |         { | 
5230  | 0  |             const CPLStringList aosTokens(CSLTokenizeString2(pszValue, ",", 0));  | 
5231  | 0  |             double adfValues[2] = {0, 0}; | 
5232  | 0  |             const int nDoubleCount = std::min(2, aosTokens.size());  | 
5233  | 0  |             if (!(aosTokens.size() == 2 &&  | 
5234  | 0  |                   CPLGetValueType(aosTokens[0]) != CPL_VALUE_STRING) &&  | 
5235  | 0  |                 CPLGetValueType(pszValue) == CPL_VALUE_STRING)  | 
5236  | 0  |             { | 
5237  | 0  |                 status = nc_put_att_text(cdfid, NCDFVarID, pszKey,  | 
5238  | 0  |                                          strlen(pszValue), pszValue);  | 
5239  | 0  |             }  | 
5240  | 0  |             else  | 
5241  | 0  |             { | 
5242  | 0  |                 for (int j = 0; j < nDoubleCount; ++j)  | 
5243  | 0  |                     adfValues[j] = CPLAtof(aosTokens[j]);  | 
5244  | 0  |                 status = nc_put_att_double(cdfid, NCDFVarID, pszKey, NC_DOUBLE,  | 
5245  | 0  |                                            nDoubleCount, adfValues);  | 
5246  | 0  |             }  | 
5247  | 0  |             NCDF_ERR(status);  | 
5248  | 0  |         }  | 
5249  | 0  |         CPLFree(pszKey);  | 
5250  | 0  |     }  | 
5251  |  | 
  | 
5252  | 0  |     CSLDestroy(papszKeyValues);  | 
5253  | 0  |     return NCDFVarID;  | 
5254  | 0  | }  | 
5255  |  |  | 
5256  |  | /************************************************************************/  | 
5257  |  | /*                   NCDFWriteLonLatVarsAttributes()                    */  | 
5258  |  | /************************************************************************/  | 
5259  |  |  | 
5260  |  | void NCDFWriteLonLatVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarLonID,  | 
5261  |  |                                    int nVarLatID)  | 
5262  | 0  | { | 
5263  |  | 
  | 
5264  | 0  |     try  | 
5265  | 0  |     { | 
5266  | 0  |         vcdf.nc_put_vatt_text(nVarLatID, CF_STD_NAME, CF_LATITUDE_STD_NAME);  | 
5267  | 0  |         vcdf.nc_put_vatt_text(nVarLatID, CF_LNG_NAME, CF_LATITUDE_LNG_NAME);  | 
5268  | 0  |         vcdf.nc_put_vatt_text(nVarLatID, CF_UNITS, CF_DEGREES_NORTH);  | 
5269  | 0  |         vcdf.nc_put_vatt_text(nVarLonID, CF_STD_NAME, CF_LONGITUDE_STD_NAME);  | 
5270  | 0  |         vcdf.nc_put_vatt_text(nVarLonID, CF_LNG_NAME, CF_LONGITUDE_LNG_NAME);  | 
5271  | 0  |         vcdf.nc_put_vatt_text(nVarLonID, CF_UNITS, CF_DEGREES_EAST);  | 
5272  | 0  |     }  | 
5273  | 0  |     catch (nccfdriver::SG_Exception &e)  | 
5274  | 0  |     { | 
5275  | 0  |         CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());  | 
5276  | 0  |     }  | 
5277  | 0  | }  | 
5278  |  |  | 
5279  |  | /************************************************************************/  | 
5280  |  | /*                   NCDFWriteRLonRLatVarsAttributes()                    */  | 
5281  |  | /************************************************************************/  | 
5282  |  |  | 
5283  |  | void NCDFWriteRLonRLatVarsAttributes(nccfdriver::netCDFVID &vcdf,  | 
5284  |  |                                      int nVarRLonID, int nVarRLatID)  | 
5285  | 0  | { | 
5286  | 0  |     try  | 
5287  | 0  |     { | 
5288  | 0  |         vcdf.nc_put_vatt_text(nVarRLatID, CF_STD_NAME, "grid_latitude");  | 
5289  | 0  |         vcdf.nc_put_vatt_text(nVarRLatID, CF_LNG_NAME,  | 
5290  | 0  |                               "latitude in rotated pole grid");  | 
5291  | 0  |         vcdf.nc_put_vatt_text(nVarRLatID, CF_UNITS, "degrees");  | 
5292  | 0  |         vcdf.nc_put_vatt_text(nVarRLatID, CF_AXIS, "Y");  | 
5293  |  | 
  | 
5294  | 0  |         vcdf.nc_put_vatt_text(nVarRLonID, CF_STD_NAME, "grid_longitude");  | 
5295  | 0  |         vcdf.nc_put_vatt_text(nVarRLonID, CF_LNG_NAME,  | 
5296  | 0  |                               "longitude in rotated pole grid");  | 
5297  | 0  |         vcdf.nc_put_vatt_text(nVarRLonID, CF_UNITS, "degrees");  | 
5298  | 0  |         vcdf.nc_put_vatt_text(nVarRLonID, CF_AXIS, "X");  | 
5299  | 0  |     }  | 
5300  | 0  |     catch (nccfdriver::SG_Exception &e)  | 
5301  | 0  |     { | 
5302  | 0  |         CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());  | 
5303  | 0  |     }  | 
5304  | 0  | }  | 
5305  |  |  | 
5306  |  | /************************************************************************/  | 
5307  |  | /*                        NCDFGetProjectedCFUnit()                      */  | 
5308  |  | /************************************************************************/  | 
5309  |  |  | 
5310  |  | std::string NCDFGetProjectedCFUnit(const OGRSpatialReference *poSRS)  | 
5311  | 0  | { | 
5312  | 0  |     char *pszUnitsToWrite = nullptr;  | 
5313  | 0  |     poSRS->exportToCF1(nullptr, nullptr, &pszUnitsToWrite, nullptr);  | 
5314  | 0  |     std::string osRet = pszUnitsToWrite ? pszUnitsToWrite : std::string();  | 
5315  | 0  |     CPLFree(pszUnitsToWrite);  | 
5316  | 0  |     return osRet;  | 
5317  | 0  | }  | 
5318  |  |  | 
5319  |  | /************************************************************************/  | 
5320  |  | /*                     NCDFWriteXYVarsAttributes()                      */  | 
5321  |  | /************************************************************************/  | 
5322  |  |  | 
5323  |  | void NCDFWriteXYVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarXID,  | 
5324  |  |                                int nVarYID, const OGRSpatialReference *poSRS)  | 
5325  | 0  | { | 
5326  | 0  |     const std::string osUnitsToWrite = NCDFGetProjectedCFUnit(poSRS);  | 
5327  |  | 
  | 
5328  | 0  |     try  | 
5329  | 0  |     { | 
5330  | 0  |         vcdf.nc_put_vatt_text(nVarXID, CF_STD_NAME, CF_PROJ_X_COORD);  | 
5331  | 0  |         vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME, CF_PROJ_X_COORD_LONG_NAME);  | 
5332  | 0  |         if (!osUnitsToWrite.empty())  | 
5333  | 0  |             vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, osUnitsToWrite.c_str());  | 
5334  | 0  |         vcdf.nc_put_vatt_text(nVarYID, CF_STD_NAME, CF_PROJ_Y_COORD);  | 
5335  | 0  |         vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME, CF_PROJ_Y_COORD_LONG_NAME);  | 
5336  | 0  |         if (!osUnitsToWrite.empty())  | 
5337  | 0  |             vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, osUnitsToWrite.c_str());  | 
5338  | 0  |     }  | 
5339  | 0  |     catch (nccfdriver::SG_Exception &e)  | 
5340  | 0  |     { | 
5341  | 0  |         CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());  | 
5342  | 0  |     }  | 
5343  | 0  | }  | 
5344  |  |  | 
5345  |  | /************************************************************************/  | 
5346  |  | /*                          AddProjectionVars()                         */  | 
5347  |  | /************************************************************************/  | 
5348  |  |  | 
5349  |  | CPLErr netCDFDataset::AddProjectionVars(bool bDefsOnly,  | 
5350  |  |                                         GDALProgressFunc pfnProgress,  | 
5351  |  |                                         void *pProgressData)  | 
5352  | 0  | { | 
5353  | 0  |     if (nCFVersion >= 1.8)  | 
5354  | 0  |         return CE_None;  // do nothing  | 
5355  |  |  | 
5356  | 0  |     bool bWriteGridMapping = false;  | 
5357  | 0  |     bool bWriteLonLat = false;  | 
5358  | 0  |     bool bHasGeoloc = false;  | 
5359  | 0  |     bool bWriteGDALTags = false;  | 
5360  | 0  |     bool bWriteGeoTransform = false;  | 
5361  |  |  | 
5362  |  |     // For GEOLOCATION information.  | 
5363  | 0  |     GDALDatasetH hDS_X = nullptr;  | 
5364  | 0  |     GDALRasterBandH hBand_X = nullptr;  | 
5365  | 0  |     GDALDatasetH hDS_Y = nullptr;  | 
5366  | 0  |     GDALRasterBandH hBand_Y = nullptr;  | 
5367  |  | 
  | 
5368  | 0  |     OGRSpatialReference oSRS(m_oSRS);  | 
5369  | 0  |     if (!m_oSRS.IsEmpty())  | 
5370  | 0  |     { | 
5371  | 0  |         if (oSRS.IsProjected())  | 
5372  | 0  |             bIsProjected = true;  | 
5373  | 0  |         else if (oSRS.IsGeographic())  | 
5374  | 0  |             bIsGeographic = true;  | 
5375  | 0  |     }  | 
5376  |  | 
  | 
5377  | 0  |     if (bDefsOnly)  | 
5378  | 0  |     { | 
5379  | 0  |         char *pszProjection = nullptr;  | 
5380  | 0  |         m_oSRS.exportToWkt(&pszProjection);  | 
5381  | 0  |         CPLDebug("GDAL_netCDF", | 
5382  | 0  |                  "SetProjection, WKT now = [%s]\nprojected: %d geographic: %d",  | 
5383  | 0  |                  pszProjection ? pszProjection : "(null)",  | 
5384  | 0  |                  static_cast<int>(bIsProjected),  | 
5385  | 0  |                  static_cast<int>(bIsGeographic));  | 
5386  | 0  |         CPLFree(pszProjection);  | 
5387  |  | 
  | 
5388  | 0  |         if (!m_bHasGeoTransform)  | 
5389  | 0  |             CPLDebug("GDAL_netCDF", | 
5390  | 0  |                      "netCDFDataset::AddProjectionVars() called, "  | 
5391  | 0  |                      "but GeoTransform has not yet been defined!");  | 
5392  |  | 
  | 
5393  | 0  |         if (!m_bHasProjection)  | 
5394  | 0  |             CPLDebug("GDAL_netCDF", | 
5395  | 0  |                      "netCDFDataset::AddProjectionVars() called, "  | 
5396  | 0  |                      "but Projection has not yet been defined!");  | 
5397  | 0  |     }  | 
5398  |  |  | 
5399  |  |     // Check GEOLOCATION information.  | 
5400  | 0  |     char **papszGeolocationInfo = netCDFDataset::GetMetadata("GEOLOCATION"); | 
5401  | 0  |     if (papszGeolocationInfo != nullptr)  | 
5402  | 0  |     { | 
5403  |  |         // Look for geolocation datasets.  | 
5404  | 0  |         const char *pszDSName =  | 
5405  | 0  |             CSLFetchNameValue(papszGeolocationInfo, "X_DATASET");  | 
5406  | 0  |         if (pszDSName != nullptr)  | 
5407  | 0  |             hDS_X = GDALOpenShared(pszDSName, GA_ReadOnly);  | 
5408  | 0  |         pszDSName = CSLFetchNameValue(papszGeolocationInfo, "Y_DATASET");  | 
5409  | 0  |         if (pszDSName != nullptr)  | 
5410  | 0  |             hDS_Y = GDALOpenShared(pszDSName, GA_ReadOnly);  | 
5411  |  | 
  | 
5412  | 0  |         if (hDS_X != nullptr && hDS_Y != nullptr)  | 
5413  | 0  |         { | 
5414  | 0  |             int nBand = std::max(1, atoi(CSLFetchNameValueDef(  | 
5415  | 0  |                                         papszGeolocationInfo, "X_BAND", "0")));  | 
5416  | 0  |             hBand_X = GDALGetRasterBand(hDS_X, nBand);  | 
5417  | 0  |             nBand = std::max(1, atoi(CSLFetchNameValueDef(papszGeolocationInfo,  | 
5418  | 0  |                                                           "Y_BAND", "0")));  | 
5419  | 0  |             hBand_Y = GDALGetRasterBand(hDS_Y, nBand);  | 
5420  |  |  | 
5421  |  |             // If geoloc bands are found, do basic validation based on their  | 
5422  |  |             // dimensions.  | 
5423  | 0  |             if (hBand_X != nullptr && hBand_Y != nullptr)  | 
5424  | 0  |             { | 
5425  | 0  |                 int nXSize_XBand = GDALGetRasterXSize(hDS_X);  | 
5426  | 0  |                 int nYSize_XBand = GDALGetRasterYSize(hDS_X);  | 
5427  | 0  |                 int nXSize_YBand = GDALGetRasterXSize(hDS_Y);  | 
5428  | 0  |                 int nYSize_YBand = GDALGetRasterYSize(hDS_Y);  | 
5429  |  |  | 
5430  |  |                 // TODO 1D geolocation arrays not implemented.  | 
5431  | 0  |                 if (nYSize_XBand == 1 && nYSize_YBand == 1)  | 
5432  | 0  |                 { | 
5433  | 0  |                     bHasGeoloc = false;  | 
5434  | 0  |                     CPLDebug("GDAL_netCDF", | 
5435  | 0  |                              "1D GEOLOCATION arrays not supported yet");  | 
5436  | 0  |                 }  | 
5437  |  |                 // 2D bands must have same sizes as the raster bands.  | 
5438  | 0  |                 else if (nXSize_XBand != nRasterXSize ||  | 
5439  | 0  |                          nYSize_XBand != nRasterYSize ||  | 
5440  | 0  |                          nXSize_YBand != nRasterXSize ||  | 
5441  | 0  |                          nYSize_YBand != nRasterYSize)  | 
5442  | 0  |                 { | 
5443  | 0  |                     bHasGeoloc = false;  | 
5444  | 0  |                     CPLDebug("GDAL_netCDF", | 
5445  | 0  |                              "GEOLOCATION array sizes (%dx%d %dx%d) differ "  | 
5446  | 0  |                              "from raster (%dx%d), not supported",  | 
5447  | 0  |                              nXSize_XBand, nYSize_XBand, nXSize_YBand,  | 
5448  | 0  |                              nYSize_YBand, nRasterXSize, nRasterYSize);  | 
5449  | 0  |                 }  | 
5450  | 0  |                 else  | 
5451  | 0  |                 { | 
5452  | 0  |                     bHasGeoloc = true;  | 
5453  | 0  |                     CPLDebug("GDAL_netCDF", | 
5454  | 0  |                              "dataset has GEOLOCATION information, will try to "  | 
5455  | 0  |                              "write it");  | 
5456  | 0  |                 }  | 
5457  | 0  |             }  | 
5458  | 0  |         }  | 
5459  | 0  |     }  | 
5460  |  |  | 
5461  |  |     // Process projection options.  | 
5462  | 0  |     if (bIsProjected)  | 
5463  | 0  |     { | 
5464  | 0  |         bool bIsCfProjection =  | 
5465  | 0  |             oSRS.exportToCF1(nullptr, nullptr, nullptr, nullptr) == OGRERR_NONE;  | 
5466  | 0  |         bWriteGridMapping = true;  | 
5467  | 0  |         bWriteGDALTags = CPL_TO_BOOL(  | 
5468  | 0  |             CSLFetchBoolean(papszCreationOptions, "WRITE_GDAL_TAGS", TRUE));  | 
5469  |  |         // Force WRITE_GDAL_TAGS if is not a CF projection.  | 
5470  | 0  |         if (!bWriteGDALTags && !bIsCfProjection)  | 
5471  | 0  |             bWriteGDALTags = true;  | 
5472  | 0  |         if (bWriteGDALTags)  | 
5473  | 0  |             bWriteGeoTransform = true;  | 
5474  |  |  | 
5475  |  |         // Write lon/lat: default is NO, except if has geolocation.  | 
5476  |  |         // With IF_NEEDED: write if has geoloc or is not CF projection.  | 
5477  | 0  |         const char *pszValue =  | 
5478  | 0  |             CSLFetchNameValue(papszCreationOptions, "WRITE_LONLAT");  | 
5479  | 0  |         if (pszValue)  | 
5480  | 0  |         { | 
5481  | 0  |             if (EQUAL(pszValue, "IF_NEEDED"))  | 
5482  | 0  |             { | 
5483  | 0  |                 bWriteLonLat = bHasGeoloc || !bIsCfProjection;  | 
5484  | 0  |             }  | 
5485  | 0  |             else  | 
5486  | 0  |             { | 
5487  | 0  |                 bWriteLonLat = CPLTestBool(pszValue);  | 
5488  | 0  |             }  | 
5489  | 0  |         }  | 
5490  | 0  |         else  | 
5491  | 0  |         { | 
5492  | 0  |             bWriteLonLat = bHasGeoloc;  | 
5493  | 0  |         }  | 
5494  |  |  | 
5495  |  |         // Save value of pszCFCoordinates for later.  | 
5496  | 0  |         if (bWriteLonLat)  | 
5497  | 0  |         { | 
5498  | 0  |             pszCFCoordinates = NCDF_LONLAT;  | 
5499  | 0  |         }  | 
5500  | 0  |     }  | 
5501  | 0  |     else  | 
5502  | 0  |     { | 
5503  |  |         // Files without a Datum will not have a grid_mapping variable and  | 
5504  |  |         // geographic information.  | 
5505  | 0  |         bWriteGridMapping = bIsGeographic;  | 
5506  |  | 
  | 
5507  | 0  |         if (bHasGeoloc)  | 
5508  | 0  |         { | 
5509  | 0  |             bWriteLonLat = true;  | 
5510  | 0  |         }  | 
5511  | 0  |         else  | 
5512  | 0  |         { | 
5513  | 0  |             bWriteGDALTags = CPL_TO_BOOL(CSLFetchBoolean(  | 
5514  | 0  |                 papszCreationOptions, "WRITE_GDAL_TAGS", bWriteGridMapping));  | 
5515  | 0  |             if (bWriteGDALTags)  | 
5516  | 0  |                 bWriteGeoTransform = true;  | 
5517  |  | 
  | 
5518  | 0  |             const char *pszValue = CSLFetchNameValueDef(papszCreationOptions,  | 
5519  | 0  |                                                         "WRITE_LONLAT", "YES");  | 
5520  | 0  |             if (EQUAL(pszValue, "IF_NEEDED"))  | 
5521  | 0  |                 bWriteLonLat = true;  | 
5522  | 0  |             else  | 
5523  | 0  |                 bWriteLonLat = CPLTestBool(pszValue);  | 
5524  |  |             //  Don't write lon/lat if no source geotransform.  | 
5525  | 0  |             if (!m_bHasGeoTransform)  | 
5526  | 0  |                 bWriteLonLat = false;  | 
5527  |  |             // If we don't write lon/lat, set dimnames to X/Y and write gdal  | 
5528  |  |             // tags.  | 
5529  | 0  |             if (!bWriteLonLat)  | 
5530  | 0  |             { | 
5531  | 0  |                 CPLError(CE_Warning, CPLE_AppDefined,  | 
5532  | 0  |                          "creating geographic file without lon/lat values!");  | 
5533  | 0  |                 if (m_bHasGeoTransform)  | 
5534  | 0  |                 { | 
5535  | 0  |                     bWriteGDALTags = true;  // Not desirable if no geotransform.  | 
5536  | 0  |                     bWriteGeoTransform = true;  | 
5537  | 0  |                 }  | 
5538  | 0  |             }  | 
5539  | 0  |         }  | 
5540  | 0  |     }  | 
5541  |  |  | 
5542  |  |     // Make sure we write grid_mapping if we need to write GDAL tags.  | 
5543  | 0  |     if (bWriteGDALTags)  | 
5544  | 0  |         bWriteGridMapping = true;  | 
5545  |  |  | 
5546  |  |     // bottom-up value: new driver is bottom-up by default.  | 
5547  |  |     // Override with WRITE_BOTTOMUP.  | 
5548  | 0  |     bBottomUp = CPL_TO_BOOL(  | 
5549  | 0  |         CSLFetchBoolean(papszCreationOptions, "WRITE_BOTTOMUP", TRUE));  | 
5550  |  | 
  | 
5551  | 0  |     if (bDefsOnly)  | 
5552  | 0  |     { | 
5553  | 0  |         CPLDebug(  | 
5554  | 0  |             "GDAL_netCDF",  | 
5555  | 0  |             "bIsProjected=%d bIsGeographic=%d bWriteGridMapping=%d "  | 
5556  | 0  |             "bWriteGDALTags=%d bWriteLonLat=%d bBottomUp=%d bHasGeoloc=%d",  | 
5557  | 0  |             static_cast<int>(bIsProjected), static_cast<int>(bIsGeographic),  | 
5558  | 0  |             static_cast<int>(bWriteGridMapping),  | 
5559  | 0  |             static_cast<int>(bWriteGDALTags), static_cast<int>(bWriteLonLat),  | 
5560  | 0  |             static_cast<int>(bBottomUp), static_cast<int>(bHasGeoloc));  | 
5561  | 0  |     }  | 
5562  |  |  | 
5563  |  |     // Exit if nothing to do.  | 
5564  | 0  |     if (!bIsProjected && !bWriteLonLat)  | 
5565  | 0  |         return CE_None;  | 
5566  |  |  | 
5567  |  |     // Define dimension names.  | 
5568  |  |  | 
5569  | 0  |     constexpr const char *ROTATED_POLE_VAR_NAME = "rotated_pole";  | 
5570  |  | 
  | 
5571  | 0  |     if (bDefsOnly)  | 
5572  | 0  |     { | 
5573  | 0  |         int nVarLonID = -1;  | 
5574  | 0  |         int nVarLatID = -1;  | 
5575  | 0  |         int nVarXID = -1;  | 
5576  | 0  |         int nVarYID = -1;  | 
5577  |  | 
  | 
5578  | 0  |         m_bAddedProjectionVarsDefs = true;  | 
5579  |  |  | 
5580  |  |         // Make sure we are in define mode.  | 
5581  | 0  |         SetDefineMode(true);  | 
5582  |  |  | 
5583  |  |         // Write projection attributes.  | 
5584  | 0  |         if (bWriteGridMapping)  | 
5585  | 0  |         { | 
5586  | 0  |             const int NCDFVarID = NCDFWriteSRSVariable(  | 
5587  | 0  |                 cdfid, &oSRS, &pszCFProjection, bWriteGDALTags);  | 
5588  | 0  |             if (NCDFVarID < 0)  | 
5589  | 0  |                 return CE_Failure;  | 
5590  |  |  | 
5591  |  |             // Optional GDAL custom projection tags.  | 
5592  | 0  |             if (bWriteGDALTags)  | 
5593  | 0  |             { | 
5594  | 0  |                 CPLString osGeoTransform;  | 
5595  | 0  |                 for (int i = 0; i < 6; i++)  | 
5596  | 0  |                 { | 
5597  | 0  |                     osGeoTransform +=  | 
5598  | 0  |                         CPLSPrintf("%.17g ", m_adfGeoTransform[i]); | 
5599  | 0  |                 }  | 
5600  | 0  |                 CPLDebug("GDAL_netCDF", "szGeoTransform = %s", | 
5601  | 0  |                          osGeoTransform.c_str());  | 
5602  |  |  | 
5603  |  |                 // if( strlen(pszProj4Defn) > 0 ) { | 
5604  |  |                 //     nc_put_att_text(cdfid, NCDFVarID, "proj4",  | 
5605  |  |                 //                      strlen(pszProj4Defn), pszProj4Defn);  | 
5606  |  |                 // }  | 
5607  |  |  | 
5608  |  |                 // For now, write the geotransform for back-compat or else  | 
5609  |  |                 // the old (1.8.1) driver overrides the CF geotransform with  | 
5610  |  |                 // empty values from dfNN, dfSN, dfEE, dfWE;  | 
5611  |  |  | 
5612  |  |                 // TODO: fix this in 1.8 branch, and then remove this here.  | 
5613  | 0  |                 if (bWriteGeoTransform && m_bHasGeoTransform)  | 
5614  | 0  |                 { | 
5615  | 0  |                     { | 
5616  | 0  |                         const int status = nc_put_att_text(  | 
5617  | 0  |                             cdfid, NCDFVarID, NCDF_GEOTRANSFORM,  | 
5618  | 0  |                             osGeoTransform.size(), osGeoTransform.c_str());  | 
5619  | 0  |                         NCDF_ERR(status);  | 
5620  | 0  |                     }  | 
5621  | 0  |                 }  | 
5622  | 0  |             }  | 
5623  |  |  | 
5624  |  |             // Write projection variable to band variable.  | 
5625  |  |             // Need to call later if there are no bands.  | 
5626  | 0  |             AddGridMappingRef();  | 
5627  | 0  |         }  // end if( bWriteGridMapping )  | 
5628  |  |  | 
5629  |  |         // Write CF Projection vars.  | 
5630  |  |  | 
5631  | 0  |         const bool bIsRotatedPole =  | 
5632  | 0  |             pszCFProjection != nullptr &&  | 
5633  | 0  |             EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);  | 
5634  | 0  |         if (bIsRotatedPole)  | 
5635  | 0  |         { | 
5636  |  |             // Rename dims to rlat/rlon.  | 
5637  | 0  |             papszDimName  | 
5638  | 0  |                 .Clear();  // If we add other dims one day, this has to change  | 
5639  | 0  |             papszDimName.AddString(NCDF_DIMNAME_RLAT);  | 
5640  | 0  |             papszDimName.AddString(NCDF_DIMNAME_RLON);  | 
5641  |  | 
  | 
5642  | 0  |             int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_RLAT);  | 
5643  | 0  |             NCDF_ERR(status);  | 
5644  | 0  |             status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_RLON);  | 
5645  | 0  |             NCDF_ERR(status);  | 
5646  | 0  |         }  | 
5647  |  |         // Rename dimensions if lon/lat.  | 
5648  | 0  |         else if (!bIsProjected && !bHasGeoloc)  | 
5649  | 0  |         { | 
5650  |  |             // Rename dims to lat/lon.  | 
5651  | 0  |             papszDimName  | 
5652  | 0  |                 .Clear();  // If we add other dims one day, this has to change  | 
5653  | 0  |             papszDimName.AddString(NCDF_DIMNAME_LAT);  | 
5654  | 0  |             papszDimName.AddString(NCDF_DIMNAME_LON);  | 
5655  |  | 
  | 
5656  | 0  |             int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_LAT);  | 
5657  | 0  |             NCDF_ERR(status);  | 
5658  | 0  |             status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_LON);  | 
5659  | 0  |             NCDF_ERR(status);  | 
5660  | 0  |         }  | 
5661  |  |  | 
5662  |  |         // Write X/Y attributes.  | 
5663  | 0  |         else /* if( bIsProjected || bHasGeoloc ) */  | 
5664  | 0  |         { | 
5665  |  |             // X  | 
5666  | 0  |             int anXDims[1];  | 
5667  | 0  |             anXDims[0] = nXDimID;  | 
5668  | 0  |             CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid, | 
5669  | 0  |                      CF_PROJ_X_VAR_NAME, NC_DOUBLE);  | 
5670  | 0  |             int status = nc_def_var(cdfid, CF_PROJ_X_VAR_NAME, NC_DOUBLE, 1,  | 
5671  | 0  |                                     anXDims, &nVarXID);  | 
5672  | 0  |             NCDF_ERR(status);  | 
5673  |  |  | 
5674  |  |             // Y  | 
5675  | 0  |             int anYDims[1];  | 
5676  | 0  |             anYDims[0] = nYDimID;  | 
5677  | 0  |             CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid, | 
5678  | 0  |                      CF_PROJ_Y_VAR_NAME, NC_DOUBLE);  | 
5679  | 0  |             status = nc_def_var(cdfid, CF_PROJ_Y_VAR_NAME, NC_DOUBLE, 1,  | 
5680  | 0  |                                 anYDims, &nVarYID);  | 
5681  | 0  |             NCDF_ERR(status);  | 
5682  |  | 
  | 
5683  | 0  |             if (bIsProjected)  | 
5684  | 0  |             { | 
5685  | 0  |                 NCDFWriteXYVarsAttributes(this->vcdf, nVarXID, nVarYID, &oSRS);  | 
5686  | 0  |             }  | 
5687  | 0  |             else  | 
5688  | 0  |             { | 
5689  | 0  |                 CPLAssert(bHasGeoloc);  | 
5690  | 0  |                 try  | 
5691  | 0  |                 { | 
5692  | 0  |                     vcdf.nc_put_vatt_text(nVarXID, CF_AXIS, CF_SG_X_AXIS);  | 
5693  | 0  |                     vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME,  | 
5694  | 0  |                                           "x-coordinate in Cartesian system");  | 
5695  | 0  |                     vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, "m");  | 
5696  | 0  |                     vcdf.nc_put_vatt_text(nVarYID, CF_AXIS, CF_SG_Y_AXIS);  | 
5697  | 0  |                     vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME,  | 
5698  | 0  |                                           "y-coordinate in Cartesian system");  | 
5699  | 0  |                     vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, "m");  | 
5700  |  | 
  | 
5701  | 0  |                     pszCFCoordinates = NCDF_LONLAT;  | 
5702  | 0  |                 }  | 
5703  | 0  |                 catch (nccfdriver::SG_Exception &e)  | 
5704  | 0  |                 { | 
5705  | 0  |                     CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());  | 
5706  | 0  |                     return CE_Failure;  | 
5707  | 0  |                 }  | 
5708  | 0  |             }  | 
5709  | 0  |         }  | 
5710  |  |  | 
5711  |  |         // Write lat/lon attributes if needed.  | 
5712  | 0  |         if (bWriteLonLat)  | 
5713  | 0  |         { | 
5714  | 0  |             int *panLatDims = nullptr;  | 
5715  | 0  |             int *panLonDims = nullptr;  | 
5716  | 0  |             int nLatDims = -1;  | 
5717  | 0  |             int nLonDims = -1;  | 
5718  |  |  | 
5719  |  |             // Get information.  | 
5720  | 0  |             if (bHasGeoloc)  | 
5721  | 0  |             { | 
5722  |  |                 // Geoloc  | 
5723  | 0  |                 nLatDims = 2;  | 
5724  | 0  |                 panLatDims =  | 
5725  | 0  |                     static_cast<int *>(CPLCalloc(nLatDims, sizeof(int)));  | 
5726  | 0  |                 panLatDims[0] = nYDimID;  | 
5727  | 0  |                 panLatDims[1] = nXDimID;  | 
5728  | 0  |                 nLonDims = 2;  | 
5729  | 0  |                 panLonDims =  | 
5730  | 0  |                     static_cast<int *>(CPLCalloc(nLonDims, sizeof(int)));  | 
5731  | 0  |                 panLonDims[0] = nYDimID;  | 
5732  | 0  |                 panLonDims[1] = nXDimID;  | 
5733  | 0  |             }  | 
5734  | 0  |             else if (bIsProjected)  | 
5735  | 0  |             { | 
5736  |  |                 // Projected  | 
5737  | 0  |                 nLatDims = 2;  | 
5738  | 0  |                 panLatDims =  | 
5739  | 0  |                     static_cast<int *>(CPLCalloc(nLatDims, sizeof(int)));  | 
5740  | 0  |                 panLatDims[0] = nYDimID;  | 
5741  | 0  |                 panLatDims[1] = nXDimID;  | 
5742  | 0  |                 nLonDims = 2;  | 
5743  | 0  |                 panLonDims =  | 
5744  | 0  |                     static_cast<int *>(CPLCalloc(nLonDims, sizeof(int)));  | 
5745  | 0  |                 panLonDims[0] = nYDimID;  | 
5746  | 0  |                 panLonDims[1] = nXDimID;  | 
5747  | 0  |             }  | 
5748  | 0  |             else  | 
5749  | 0  |             { | 
5750  |  |                 // Geographic  | 
5751  | 0  |                 nLatDims = 1;  | 
5752  | 0  |                 panLatDims =  | 
5753  | 0  |                     static_cast<int *>(CPLCalloc(nLatDims, sizeof(int)));  | 
5754  | 0  |                 panLatDims[0] = nYDimID;  | 
5755  | 0  |                 nLonDims = 1;  | 
5756  | 0  |                 panLonDims =  | 
5757  | 0  |                     static_cast<int *>(CPLCalloc(nLonDims, sizeof(int)));  | 
5758  | 0  |                 panLonDims[0] = nXDimID;  | 
5759  | 0  |             }  | 
5760  |  | 
  | 
5761  | 0  |             nc_type eLonLatType = NC_NAT;  | 
5762  | 0  |             if (bIsProjected)  | 
5763  | 0  |             { | 
5764  | 0  |                 eLonLatType = NC_FLOAT;  | 
5765  | 0  |                 const char *pszValue = CSLFetchNameValueDef(  | 
5766  | 0  |                     papszCreationOptions, "TYPE_LONLAT", "FLOAT");  | 
5767  | 0  |                 if (EQUAL(pszValue, "DOUBLE"))  | 
5768  | 0  |                     eLonLatType = NC_DOUBLE;  | 
5769  | 0  |             }  | 
5770  | 0  |             else  | 
5771  | 0  |             { | 
5772  | 0  |                 eLonLatType = NC_DOUBLE;  | 
5773  | 0  |                 const char *pszValue = CSLFetchNameValueDef(  | 
5774  | 0  |                     papszCreationOptions, "TYPE_LONLAT", "DOUBLE");  | 
5775  | 0  |                 if (EQUAL(pszValue, "FLOAT"))  | 
5776  | 0  |                     eLonLatType = NC_FLOAT;  | 
5777  | 0  |             }  | 
5778  |  |  | 
5779  |  |             // Def vars and attributes.  | 
5780  | 0  |             { | 
5781  | 0  |                 const char *pszVarName =  | 
5782  | 0  |                     bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME;  | 
5783  | 0  |                 int status = nc_def_var(cdfid, pszVarName, eLonLatType,  | 
5784  | 0  |                                         nLatDims, panLatDims, &nVarLatID);  | 
5785  | 0  |                 CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d", | 
5786  | 0  |                          cdfid, pszVarName, eLonLatType, nLatDims, nVarLatID);  | 
5787  | 0  |                 NCDF_ERR(status);  | 
5788  | 0  |                 DefVarDeflate(nVarLatID, false);  // Don't set chunking.  | 
5789  | 0  |             }  | 
5790  |  | 
  | 
5791  | 0  |             { | 
5792  | 0  |                 const char *pszVarName =  | 
5793  | 0  |                     bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME;  | 
5794  | 0  |                 int status = nc_def_var(cdfid, pszVarName, eLonLatType,  | 
5795  | 0  |                                         nLonDims, panLonDims, &nVarLonID);  | 
5796  | 0  |                 CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d", | 
5797  | 0  |                          cdfid, pszVarName, eLonLatType, nLatDims, nVarLonID);  | 
5798  | 0  |                 NCDF_ERR(status);  | 
5799  | 0  |                 DefVarDeflate(nVarLonID, false);  // Don't set chunking.  | 
5800  | 0  |             }  | 
5801  |  | 
  | 
5802  | 0  |             if (bIsRotatedPole)  | 
5803  | 0  |                 NCDFWriteRLonRLatVarsAttributes(this->vcdf, nVarLonID,  | 
5804  | 0  |                                                 nVarLatID);  | 
5805  | 0  |             else  | 
5806  | 0  |                 NCDFWriteLonLatVarsAttributes(this->vcdf, nVarLonID, nVarLatID);  | 
5807  |  | 
  | 
5808  | 0  |             CPLFree(panLatDims);  | 
5809  | 0  |             CPLFree(panLonDims);  | 
5810  | 0  |         }  | 
5811  | 0  |     }  | 
5812  |  |  | 
5813  | 0  |     if (!bDefsOnly)  | 
5814  | 0  |     { | 
5815  | 0  |         m_bAddedProjectionVarsData = true;  | 
5816  |  | 
  | 
5817  | 0  |         int nVarXID = -1;  | 
5818  | 0  |         int nVarYID = -1;  | 
5819  |  | 
  | 
5820  | 0  |         nc_inq_varid(cdfid, CF_PROJ_X_VAR_NAME, &nVarXID);  | 
5821  | 0  |         nc_inq_varid(cdfid, CF_PROJ_Y_VAR_NAME, &nVarYID);  | 
5822  |  | 
  | 
5823  | 0  |         int nVarLonID = -1;  | 
5824  | 0  |         int nVarLatID = -1;  | 
5825  |  | 
  | 
5826  | 0  |         const bool bIsRotatedPole =  | 
5827  | 0  |             pszCFProjection != nullptr &&  | 
5828  | 0  |             EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);  | 
5829  | 0  |         nc_inq_varid(cdfid,  | 
5830  | 0  |                      bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME,  | 
5831  | 0  |                      &nVarLonID);  | 
5832  | 0  |         nc_inq_varid(cdfid,  | 
5833  | 0  |                      bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME,  | 
5834  | 0  |                      &nVarLatID);  | 
5835  |  |  | 
5836  |  |         // Get projection values.  | 
5837  |  | 
  | 
5838  | 0  |         double *padLonVal = nullptr;  | 
5839  | 0  |         double *padLatVal = nullptr;  | 
5840  |  | 
  | 
5841  | 0  |         if (bIsProjected)  | 
5842  | 0  |         { | 
5843  | 0  |             OGRSpatialReference *poLatLonSRS = nullptr;  | 
5844  | 0  |             OGRCoordinateTransformation *poTransform = nullptr;  | 
5845  |  | 
  | 
5846  | 0  |             size_t startX[1];  | 
5847  | 0  |             size_t countX[1];  | 
5848  | 0  |             size_t startY[1];  | 
5849  | 0  |             size_t countY[1];  | 
5850  |  | 
  | 
5851  | 0  |             CPLDebug("GDAL_netCDF", "Getting (X,Y) values"); | 
5852  |  | 
  | 
5853  | 0  |             double *padXVal =  | 
5854  | 0  |                 static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));  | 
5855  | 0  |             double *padYVal =  | 
5856  | 0  |                 static_cast<double *>(CPLMalloc(nRasterYSize * sizeof(double)));  | 
5857  |  |  | 
5858  |  |             // Get Y values.  | 
5859  | 0  |             const double dfY0 = (!bBottomUp) ? m_adfGeoTransform[3] :  | 
5860  |  |                                              // Invert latitude values.  | 
5861  | 0  |                                     m_adfGeoTransform[3] +  | 
5862  | 0  |                                         (m_adfGeoTransform[5] * nRasterYSize);  | 
5863  | 0  |             const double dfDY = m_adfGeoTransform[5];  | 
5864  |  | 
  | 
5865  | 0  |             for (int j = 0; j < nRasterYSize; j++)  | 
5866  | 0  |             { | 
5867  |  |                 // The data point is centered inside the pixel.  | 
5868  | 0  |                 if (!bBottomUp)  | 
5869  | 0  |                     padYVal[j] = dfY0 + (j + 0.5) * dfDY;  | 
5870  | 0  |                 else  // Invert latitude values.  | 
5871  | 0  |                     padYVal[j] = dfY0 - (j + 0.5) * dfDY;  | 
5872  | 0  |             }  | 
5873  | 0  |             startX[0] = 0;  | 
5874  | 0  |             countX[0] = nRasterXSize;  | 
5875  |  |  | 
5876  |  |             // Get X values.  | 
5877  | 0  |             const double dfX0 = m_adfGeoTransform[0];  | 
5878  | 0  |             const double dfDX = m_adfGeoTransform[1];  | 
5879  |  | 
  | 
5880  | 0  |             for (int i = 0; i < nRasterXSize; i++)  | 
5881  | 0  |             { | 
5882  |  |                 // The data point is centered inside the pixel.  | 
5883  | 0  |                 padXVal[i] = dfX0 + (i + 0.5) * dfDX;  | 
5884  | 0  |             }  | 
5885  | 0  |             startY[0] = 0;  | 
5886  | 0  |             countY[0] = nRasterYSize;  | 
5887  |  |  | 
5888  |  |             // Write X/Y values.  | 
5889  |  |  | 
5890  |  |             // Make sure we are in data mode.  | 
5891  | 0  |             SetDefineMode(false);  | 
5892  |  | 
  | 
5893  | 0  |             CPLDebug("GDAL_netCDF", "Writing X values"); | 
5894  | 0  |             int status =  | 
5895  | 0  |                 nc_put_vara_double(cdfid, nVarXID, startX, countX, padXVal);  | 
5896  | 0  |             NCDF_ERR(status);  | 
5897  |  | 
  | 
5898  | 0  |             CPLDebug("GDAL_netCDF", "Writing Y values"); | 
5899  | 0  |             status =  | 
5900  | 0  |                 nc_put_vara_double(cdfid, nVarYID, startY, countY, padYVal);  | 
5901  | 0  |             NCDF_ERR(status);  | 
5902  |  | 
  | 
5903  | 0  |             if (pfnProgress)  | 
5904  | 0  |                 pfnProgress(0.20, nullptr, pProgressData);  | 
5905  |  |  | 
5906  |  |             // Write lon/lat arrays (CF coordinates) if requested.  | 
5907  |  |  | 
5908  |  |             // Get OGR transform if GEOLOCATION is not available.  | 
5909  | 0  |             if (bWriteLonLat && !bHasGeoloc)  | 
5910  | 0  |             { | 
5911  | 0  |                 poLatLonSRS = m_oSRS.CloneGeogCS();  | 
5912  | 0  |                 if (poLatLonSRS != nullptr)  | 
5913  | 0  |                 { | 
5914  | 0  |                     poLatLonSRS->SetAxisMappingStrategy(  | 
5915  | 0  |                         OAMS_TRADITIONAL_GIS_ORDER);  | 
5916  | 0  |                     poTransform =  | 
5917  | 0  |                         OGRCreateCoordinateTransformation(&m_oSRS, poLatLonSRS);  | 
5918  | 0  |                 }  | 
5919  |  |                 // If no OGR transform, then don't write CF lon/lat.  | 
5920  | 0  |                 if (poTransform == nullptr)  | 
5921  | 0  |                 { | 
5922  | 0  |                     CPLError(CE_Failure, CPLE_AppDefined,  | 
5923  | 0  |                              "Unable to get Coordinate Transform");  | 
5924  | 0  |                     bWriteLonLat = false;  | 
5925  | 0  |                 }  | 
5926  | 0  |             }  | 
5927  |  | 
  | 
5928  | 0  |             if (bWriteLonLat)  | 
5929  | 0  |             { | 
5930  | 0  |                 if (!bHasGeoloc)  | 
5931  | 0  |                     CPLDebug("GDAL_netCDF", "Transforming (X,Y)->(lon,lat)"); | 
5932  | 0  |                 else  | 
5933  | 0  |                     CPLDebug("GDAL_netCDF", | 
5934  | 0  |                              "Writing (lon,lat) from GEOLOCATION arrays");  | 
5935  |  | 
  | 
5936  | 0  |                 bool bOK = true;  | 
5937  | 0  |                 double dfProgress = 0.2;  | 
5938  |  | 
  | 
5939  | 0  |                 size_t start[] = {0, 0}; | 
5940  | 0  |                 size_t count[] = {1, (size_t)nRasterXSize}; | 
5941  | 0  |                 padLatVal = static_cast<double *>(  | 
5942  | 0  |                     CPLMalloc(nRasterXSize * sizeof(double)));  | 
5943  | 0  |                 padLonVal = static_cast<double *>(  | 
5944  | 0  |                     CPLMalloc(nRasterXSize * sizeof(double)));  | 
5945  |  | 
  | 
5946  | 0  |                 for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR;  | 
5947  | 0  |                      j++)  | 
5948  | 0  |                 { | 
5949  | 0  |                     start[0] = j;  | 
5950  |  |  | 
5951  |  |                     // Get values from geotransform.  | 
5952  | 0  |                     if (!bHasGeoloc)  | 
5953  | 0  |                     { | 
5954  |  |                         // Fill values to transform.  | 
5955  | 0  |                         for (int i = 0; i < nRasterXSize; i++)  | 
5956  | 0  |                         { | 
5957  | 0  |                             padLatVal[i] = padYVal[j];  | 
5958  | 0  |                             padLonVal[i] = padXVal[i];  | 
5959  | 0  |                         }  | 
5960  |  |  | 
5961  |  |                         // Do the transform.  | 
5962  | 0  |                         bOK = CPL_TO_BOOL(poTransform->Transform(  | 
5963  | 0  |                             nRasterXSize, padLonVal, padLatVal, nullptr));  | 
5964  | 0  |                         if (!bOK)  | 
5965  | 0  |                         { | 
5966  | 0  |                             CPLError(CE_Failure, CPLE_AppDefined,  | 
5967  | 0  |                                      "Unable to Transform (X,Y) to (lon,lat).");  | 
5968  | 0  |                         }  | 
5969  | 0  |                     }  | 
5970  |  |                     // Get values from geoloc arrays.  | 
5971  | 0  |                     else  | 
5972  | 0  |                     { | 
5973  | 0  |                         CPLErr eErr = GDALRasterIO(  | 
5974  | 0  |                             hBand_Y, GF_Read, 0, j, nRasterXSize, 1, padLatVal,  | 
5975  | 0  |                             nRasterXSize, 1, GDT_Float64, 0, 0);  | 
5976  | 0  |                         if (eErr == CE_None)  | 
5977  | 0  |                         { | 
5978  | 0  |                             eErr = GDALRasterIO(  | 
5979  | 0  |                                 hBand_X, GF_Read, 0, j, nRasterXSize, 1,  | 
5980  | 0  |                                 padLonVal, nRasterXSize, 1, GDT_Float64, 0, 0);  | 
5981  | 0  |                         }  | 
5982  |  | 
  | 
5983  | 0  |                         if (eErr == CE_None)  | 
5984  | 0  |                         { | 
5985  | 0  |                             bOK = true;  | 
5986  | 0  |                         }  | 
5987  | 0  |                         else  | 
5988  | 0  |                         { | 
5989  | 0  |                             bOK = false;  | 
5990  | 0  |                             CPLError(CE_Failure, CPLE_AppDefined,  | 
5991  | 0  |                                      "Unable to get scanline %d", j);  | 
5992  | 0  |                         }  | 
5993  | 0  |                     }  | 
5994  |  |  | 
5995  |  |                     // Write data.  | 
5996  | 0  |                     if (bOK)  | 
5997  | 0  |                     { | 
5998  | 0  |                         status = nc_put_vara_double(cdfid, nVarLatID, start,  | 
5999  | 0  |                                                     count, padLatVal);  | 
6000  | 0  |                         NCDF_ERR(status);  | 
6001  | 0  |                         status = nc_put_vara_double(cdfid, nVarLonID, start,  | 
6002  | 0  |                                                     count, padLonVal);  | 
6003  | 0  |                         NCDF_ERR(status);  | 
6004  | 0  |                     }  | 
6005  |  | 
  | 
6006  | 0  |                     if (pfnProgress && (nRasterYSize / 10) > 0 &&  | 
6007  | 0  |                         (j % (nRasterYSize / 10) == 0))  | 
6008  | 0  |                     { | 
6009  | 0  |                         dfProgress += 0.08;  | 
6010  | 0  |                         pfnProgress(dfProgress, nullptr, pProgressData);  | 
6011  | 0  |                     }  | 
6012  | 0  |                 }  | 
6013  | 0  |             }  | 
6014  |  | 
  | 
6015  | 0  |             if (poLatLonSRS != nullptr)  | 
6016  | 0  |                 delete poLatLonSRS;  | 
6017  | 0  |             if (poTransform != nullptr)  | 
6018  | 0  |                 delete poTransform;  | 
6019  |  | 
  | 
6020  | 0  |             CPLFree(padXVal);  | 
6021  | 0  |             CPLFree(padYVal);  | 
6022  | 0  |         }  // Projected  | 
6023  |  |  | 
6024  |  |         // If not projected/geographic and has geoloc  | 
6025  | 0  |         else if (!bIsGeographic && bHasGeoloc)  | 
6026  | 0  |         { | 
6027  |  |             // Use  | 
6028  |  |             // https://cfconventions.org/Data/cf-conventions/cf-conventions-1.9/cf-conventions.html#_two_dimensional_latitude_longitude_coordinate_variables  | 
6029  |  | 
  | 
6030  | 0  |             bool bOK = true;  | 
6031  | 0  |             double dfProgress = 0.2;  | 
6032  |  |  | 
6033  |  |             // Make sure we are in data mode.  | 
6034  | 0  |             SetDefineMode(false);  | 
6035  |  | 
  | 
6036  | 0  |             size_t startX[1];  | 
6037  | 0  |             size_t countX[1];  | 
6038  | 0  |             size_t startY[1];  | 
6039  | 0  |             size_t countY[1];  | 
6040  | 0  |             startX[0] = 0;  | 
6041  | 0  |             countX[0] = nRasterXSize;  | 
6042  |  | 
  | 
6043  | 0  |             startY[0] = 0;  | 
6044  | 0  |             countY[0] = nRasterYSize;  | 
6045  |  | 
  | 
6046  | 0  |             std::vector<double> adfXVal(nRasterXSize);  | 
6047  | 0  |             for (int i = 0; i < nRasterXSize; i++)  | 
6048  | 0  |                 adfXVal[i] = i;  | 
6049  |  | 
  | 
6050  | 0  |             std::vector<double> adfYVal(nRasterYSize);  | 
6051  | 0  |             for (int i = 0; i < nRasterYSize; i++)  | 
6052  | 0  |                 adfYVal[i] = bBottomUp ? nRasterYSize - 1 - i : i;  | 
6053  |  | 
  | 
6054  | 0  |             CPLDebug("GDAL_netCDF", "Writing X values"); | 
6055  | 0  |             int status = nc_put_vara_double(cdfid, nVarXID, startX, countX,  | 
6056  | 0  |                                             adfXVal.data());  | 
6057  | 0  |             NCDF_ERR(status);  | 
6058  |  | 
  | 
6059  | 0  |             CPLDebug("GDAL_netCDF", "Writing Y values"); | 
6060  | 0  |             status = nc_put_vara_double(cdfid, nVarYID, startY, countY,  | 
6061  | 0  |                                         adfYVal.data());  | 
6062  | 0  |             NCDF_ERR(status);  | 
6063  |  | 
  | 
6064  | 0  |             if (pfnProgress)  | 
6065  | 0  |                 pfnProgress(0.20, nullptr, pProgressData);  | 
6066  |  | 
  | 
6067  | 0  |             size_t start[] = {0, 0}; | 
6068  | 0  |             size_t count[] = {1, (size_t)nRasterXSize}; | 
6069  | 0  |             padLatVal =  | 
6070  | 0  |                 static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));  | 
6071  | 0  |             padLonVal =  | 
6072  | 0  |                 static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));  | 
6073  |  | 
  | 
6074  | 0  |             for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR; j++)  | 
6075  | 0  |             { | 
6076  | 0  |                 start[0] = j;  | 
6077  |  | 
  | 
6078  | 0  |                 CPLErr eErr = GDALRasterIO(hBand_Y, GF_Read, 0,  | 
6079  | 0  |                                            bBottomUp ? nRasterYSize - 1 - j : j,  | 
6080  | 0  |                                            nRasterXSize, 1, padLatVal,  | 
6081  | 0  |                                            nRasterXSize, 1, GDT_Float64, 0, 0);  | 
6082  | 0  |                 if (eErr == CE_None)  | 
6083  | 0  |                 { | 
6084  | 0  |                     eErr = GDALRasterIO(hBand_X, GF_Read, 0,  | 
6085  | 0  |                                         bBottomUp ? nRasterYSize - 1 - j : j,  | 
6086  | 0  |                                         nRasterXSize, 1, padLonVal,  | 
6087  | 0  |                                         nRasterXSize, 1, GDT_Float64, 0, 0);  | 
6088  | 0  |                 }  | 
6089  |  | 
  | 
6090  | 0  |                 if (eErr == CE_None)  | 
6091  | 0  |                 { | 
6092  | 0  |                     bOK = true;  | 
6093  | 0  |                 }  | 
6094  | 0  |                 else  | 
6095  | 0  |                 { | 
6096  | 0  |                     bOK = false;  | 
6097  | 0  |                     CPLError(CE_Failure, CPLE_AppDefined,  | 
6098  | 0  |                              "Unable to get scanline %d", j);  | 
6099  | 0  |                 }  | 
6100  |  |  | 
6101  |  |                 // Write data.  | 
6102  | 0  |                 if (bOK)  | 
6103  | 0  |                 { | 
6104  | 0  |                     status = nc_put_vara_double(cdfid, nVarLatID, start, count,  | 
6105  | 0  |                                                 padLatVal);  | 
6106  | 0  |                     NCDF_ERR(status);  | 
6107  | 0  |                     status = nc_put_vara_double(cdfid, nVarLonID, start, count,  | 
6108  | 0  |                                                 padLonVal);  | 
6109  | 0  |                     NCDF_ERR(status);  | 
6110  | 0  |                 }  | 
6111  |  | 
  | 
6112  | 0  |                 if (pfnProgress && (nRasterYSize / 10) > 0 &&  | 
6113  | 0  |                     (j % (nRasterYSize / 10) == 0))  | 
6114  | 0  |                 { | 
6115  | 0  |                     dfProgress += 0.08;  | 
6116  | 0  |                     pfnProgress(dfProgress, nullptr, pProgressData);  | 
6117  | 0  |                 }  | 
6118  | 0  |             }  | 
6119  | 0  |         }  | 
6120  |  |  | 
6121  |  |         // If not projected, assume geographic to catch grids without Datum.  | 
6122  | 0  |         else if (bWriteLonLat)  | 
6123  | 0  |         { | 
6124  |  |             // Get latitude values.  | 
6125  | 0  |             const double dfY0 = (!bBottomUp) ? m_adfGeoTransform[3] :  | 
6126  |  |                                              // Invert latitude values.  | 
6127  | 0  |                                     m_adfGeoTransform[3] +  | 
6128  | 0  |                                         (m_adfGeoTransform[5] * nRasterYSize);  | 
6129  | 0  |             const double dfDY = m_adfGeoTransform[5];  | 
6130  |  |  | 
6131  |  |             // Override lat values with the ones in GEOLOCATION/Y_VALUES.  | 
6132  | 0  |             if (netCDFDataset::GetMetadataItem("Y_VALUES", "GEOLOCATION") != | 
6133  | 0  |                 nullptr)  | 
6134  | 0  |             { | 
6135  | 0  |                 int nTemp = 0;  | 
6136  | 0  |                 padLatVal = Get1DGeolocation("Y_VALUES", nTemp); | 
6137  |  |                 // Make sure we got the correct amount, if not fallback to GT */  | 
6138  |  |                 // could add test fabs(fabs(padLatVal[0]) - fabs(dfY0)) <= 0.1))  | 
6139  | 0  |                 if (nTemp == nRasterYSize)  | 
6140  | 0  |                 { | 
6141  | 0  |                     CPLDebug(  | 
6142  | 0  |                         "GDAL_netCDF",  | 
6143  | 0  |                         "Using Y_VALUES geolocation metadata for lat values");  | 
6144  | 0  |                 }  | 
6145  | 0  |                 else  | 
6146  | 0  |                 { | 
6147  | 0  |                     CPLDebug("GDAL_netCDF", | 
6148  | 0  |                              "Got %d elements from Y_VALUES geolocation "  | 
6149  | 0  |                              "metadata, need %d",  | 
6150  | 0  |                              nTemp, nRasterYSize);  | 
6151  | 0  |                     if (padLatVal)  | 
6152  | 0  |                     { | 
6153  | 0  |                         CPLFree(padLatVal);  | 
6154  | 0  |                         padLatVal = nullptr;  | 
6155  | 0  |                     }  | 
6156  | 0  |                 }  | 
6157  | 0  |             }  | 
6158  |  | 
  | 
6159  | 0  |             if (padLatVal == nullptr)  | 
6160  | 0  |             { | 
6161  | 0  |                 padLatVal = static_cast<double *>(  | 
6162  | 0  |                     CPLMalloc(nRasterYSize * sizeof(double)));  | 
6163  | 0  |                 for (int i = 0; i < nRasterYSize; i++)  | 
6164  | 0  |                 { | 
6165  |  |                     // The data point is centered inside the pixel.  | 
6166  | 0  |                     if (!bBottomUp)  | 
6167  | 0  |                         padLatVal[i] = dfY0 + (i + 0.5) * dfDY;  | 
6168  | 0  |                     else  // Invert latitude values.  | 
6169  | 0  |                         padLatVal[i] = dfY0 - (i + 0.5) * dfDY;  | 
6170  | 0  |                 }  | 
6171  | 0  |             }  | 
6172  |  | 
  | 
6173  | 0  |             size_t startLat[1] = {0}; | 
6174  | 0  |             size_t countLat[1] = {static_cast<size_t>(nRasterYSize)}; | 
6175  |  |  | 
6176  |  |             // Get longitude values.  | 
6177  | 0  |             const double dfX0 = m_adfGeoTransform[0];  | 
6178  | 0  |             const double dfDX = m_adfGeoTransform[1];  | 
6179  |  | 
  | 
6180  | 0  |             padLonVal =  | 
6181  | 0  |                 static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));  | 
6182  | 0  |             for (int i = 0; i < nRasterXSize; i++)  | 
6183  | 0  |             { | 
6184  |  |                 // The data point is centered inside the pixel.  | 
6185  | 0  |                 padLonVal[i] = dfX0 + (i + 0.5) * dfDX;  | 
6186  | 0  |             }  | 
6187  |  | 
  | 
6188  | 0  |             size_t startLon[1] = {0}; | 
6189  | 0  |             size_t countLon[1] = {static_cast<size_t>(nRasterXSize)}; | 
6190  |  |  | 
6191  |  |             // Write latitude and longitude values.  | 
6192  |  |  | 
6193  |  |             // Make sure we are in data mode.  | 
6194  | 0  |             SetDefineMode(false);  | 
6195  |  |  | 
6196  |  |             // Write values.  | 
6197  | 0  |             CPLDebug("GDAL_netCDF", "Writing lat values"); | 
6198  |  | 
  | 
6199  | 0  |             int status = nc_put_vara_double(cdfid, nVarLatID, startLat,  | 
6200  | 0  |                                             countLat, padLatVal);  | 
6201  | 0  |             NCDF_ERR(status);  | 
6202  |  | 
  | 
6203  | 0  |             CPLDebug("GDAL_netCDF", "Writing lon values"); | 
6204  | 0  |             status = nc_put_vara_double(cdfid, nVarLonID, startLon, countLon,  | 
6205  | 0  |                                         padLonVal);  | 
6206  | 0  |             NCDF_ERR(status);  | 
6207  |  | 
  | 
6208  | 0  |         }  // Not projected.  | 
6209  |  | 
  | 
6210  | 0  |         CPLFree(padLatVal);  | 
6211  | 0  |         CPLFree(padLonVal);  | 
6212  |  | 
  | 
6213  | 0  |         if (pfnProgress)  | 
6214  | 0  |             pfnProgress(1.00, nullptr, pProgressData);  | 
6215  | 0  |     }  | 
6216  |  | 
  | 
6217  | 0  |     if (hDS_X != nullptr)  | 
6218  | 0  |     { | 
6219  | 0  |         GDALClose(hDS_X);  | 
6220  | 0  |     }  | 
6221  | 0  |     if (hDS_Y != nullptr)  | 
6222  | 0  |     { | 
6223  | 0  |         GDALClose(hDS_Y);  | 
6224  | 0  |     }  | 
6225  |  | 
  | 
6226  | 0  |     return CE_None;  | 
6227  | 0  | }  | 
6228  |  |  | 
6229  |  | // Write Projection variable to band variable.  | 
6230  |  | // Moved from AddProjectionVars() for cases when bands are added after  | 
6231  |  | // projection.  | 
6232  |  | bool netCDFDataset::AddGridMappingRef()  | 
6233  | 0  | { | 
6234  | 0  |     bool bRet = true;  | 
6235  | 0  |     bool bOldDefineMode = bDefineMode;  | 
6236  |  | 
  | 
6237  | 0  |     if ((GetAccess() == GA_Update) && (nBands >= 1) && (GetRasterBand(1)) &&  | 
6238  | 0  |         ((pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, "")) ||  | 
6239  | 0  |          (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))))  | 
6240  | 0  |     { | 
6241  | 0  |         bAddedGridMappingRef = true;  | 
6242  |  |  | 
6243  |  |         // Make sure we are in define mode.  | 
6244  | 0  |         SetDefineMode(true);  | 
6245  |  | 
  | 
6246  | 0  |         for (int i = 1; i <= nBands; i++)  | 
6247  | 0  |         { | 
6248  | 0  |             const int nVarId =  | 
6249  | 0  |                 static_cast<netCDFRasterBand *>(GetRasterBand(i))->nZId;  | 
6250  |  | 
  | 
6251  | 0  |             if (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))  | 
6252  | 0  |             { | 
6253  | 0  |                 int status =  | 
6254  | 0  |                     nc_put_att_text(cdfid, nVarId, CF_GRD_MAPPING,  | 
6255  | 0  |                                     strlen(pszCFProjection), pszCFProjection);  | 
6256  | 0  |                 NCDF_ERR(status);  | 
6257  | 0  |                 if (status != NC_NOERR)  | 
6258  | 0  |                     bRet = false;  | 
6259  | 0  |             }  | 
6260  | 0  |             if (pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, ""))  | 
6261  | 0  |             { | 
6262  | 0  |                 int status =  | 
6263  | 0  |                     nc_put_att_text(cdfid, nVarId, CF_COORDINATES,  | 
6264  | 0  |                                     strlen(pszCFCoordinates), pszCFCoordinates);  | 
6265  | 0  |                 NCDF_ERR(status);  | 
6266  | 0  |                 if (status != NC_NOERR)  | 
6267  | 0  |                     bRet = false;  | 
6268  | 0  |             }  | 
6269  | 0  |         }  | 
6270  |  |  | 
6271  |  |         // Go back to previous define mode.  | 
6272  | 0  |         SetDefineMode(bOldDefineMode);  | 
6273  | 0  |     }  | 
6274  | 0  |     return bRet;  | 
6275  | 0  | }  | 
6276  |  |  | 
6277  |  | /************************************************************************/  | 
6278  |  | /*                          GetGeoTransform()                           */  | 
6279  |  | /************************************************************************/  | 
6280  |  |  | 
6281  |  | CPLErr netCDFDataset::GetGeoTransform(double *padfTransform)  | 
6282  |  |  | 
6283  | 0  | { | 
6284  | 0  |     memcpy(padfTransform, m_adfGeoTransform, sizeof(double) * 6);  | 
6285  | 0  |     if (m_bHasGeoTransform)  | 
6286  | 0  |         return CE_None;  | 
6287  |  |  | 
6288  | 0  |     return GDALPamDataset::GetGeoTransform(padfTransform);  | 
6289  | 0  | }  | 
6290  |  |  | 
6291  |  | /************************************************************************/  | 
6292  |  | /*                                rint()                                */  | 
6293  |  | /************************************************************************/  | 
6294  |  |  | 
6295  |  | double netCDFDataset::rint(double dfX)  | 
6296  | 0  | { | 
6297  | 0  |     return std::round(dfX);  | 
6298  | 0  | }  | 
6299  |  |  | 
6300  |  | /************************************************************************/  | 
6301  |  | /*                          NCDFReadIsoMetadata()                       */  | 
6302  |  | /************************************************************************/  | 
6303  |  |  | 
6304  |  | static void NCDFReadMetadataAsJson(int cdfid, CPLJSONObject &obj)  | 
6305  | 0  | { | 
6306  | 0  |     int nbAttr = 0;  | 
6307  | 0  |     NCDF_ERR(nc_inq_varnatts(cdfid, NC_GLOBAL, &nbAttr));  | 
6308  |  | 
  | 
6309  | 0  |     std::map<std::string, CPLJSONArray> oMapNameToArray;  | 
6310  | 0  |     for (int l = 0; l < nbAttr; l++)  | 
6311  | 0  |     { | 
6312  | 0  |         char szAttrName[NC_MAX_NAME + 1];  | 
6313  | 0  |         szAttrName[0] = 0;  | 
6314  | 0  |         NCDF_ERR(nc_inq_attname(cdfid, NC_GLOBAL, l, szAttrName));  | 
6315  |  | 
  | 
6316  | 0  |         char *pszMetaValue = nullptr;  | 
6317  | 0  |         if (NCDFGetAttr(cdfid, NC_GLOBAL, szAttrName, &pszMetaValue) == CE_None)  | 
6318  | 0  |         { | 
6319  | 0  |             nc_type nAttrType = NC_NAT;  | 
6320  | 0  |             size_t nAttrLen = 0;  | 
6321  |  | 
  | 
6322  | 0  |             NCDF_ERR(nc_inq_att(cdfid, NC_GLOBAL, szAttrName, &nAttrType,  | 
6323  | 0  |                                 &nAttrLen));  | 
6324  |  | 
  | 
6325  | 0  |             std::string osAttrName(szAttrName);  | 
6326  | 0  |             const auto sharpPos = osAttrName.find('#'); | 
6327  | 0  |             if (sharpPos == std::string::npos)  | 
6328  | 0  |             { | 
6329  | 0  |                 if (nAttrType == NC_DOUBLE || nAttrType == NC_FLOAT)  | 
6330  | 0  |                     obj.Add(osAttrName, CPLAtof(pszMetaValue));  | 
6331  | 0  |                 else  | 
6332  | 0  |                     obj.Add(osAttrName, pszMetaValue);  | 
6333  | 0  |             }  | 
6334  | 0  |             else  | 
6335  | 0  |             { | 
6336  | 0  |                 osAttrName.resize(sharpPos);  | 
6337  | 0  |                 auto iter = oMapNameToArray.find(osAttrName);  | 
6338  | 0  |                 if (iter == oMapNameToArray.end())  | 
6339  | 0  |                 { | 
6340  | 0  |                     CPLJSONArray array;  | 
6341  | 0  |                     obj.Add(osAttrName, array);  | 
6342  | 0  |                     oMapNameToArray[osAttrName] = array;  | 
6343  | 0  |                     array.Add(pszMetaValue);  | 
6344  | 0  |                 }  | 
6345  | 0  |                 else  | 
6346  | 0  |                 { | 
6347  | 0  |                     iter->second.Add(pszMetaValue);  | 
6348  | 0  |                 }  | 
6349  | 0  |             }  | 
6350  | 0  |             CPLFree(pszMetaValue);  | 
6351  | 0  |             pszMetaValue = nullptr;  | 
6352  | 0  |         }  | 
6353  | 0  |     }  | 
6354  |  | 
  | 
6355  | 0  |     int nSubGroups = 0;  | 
6356  | 0  |     int *panSubGroupIds = nullptr;  | 
6357  | 0  |     NCDFGetSubGroups(cdfid, &nSubGroups, &panSubGroupIds);  | 
6358  | 0  |     oMapNameToArray.clear();  | 
6359  | 0  |     for (int i = 0; i < nSubGroups; i++)  | 
6360  | 0  |     { | 
6361  | 0  |         CPLJSONObject subObj;  | 
6362  | 0  |         NCDFReadMetadataAsJson(panSubGroupIds[i], subObj);  | 
6363  |  | 
  | 
6364  | 0  |         std::string osGroupName;  | 
6365  | 0  |         osGroupName.resize(NC_MAX_NAME);  | 
6366  | 0  |         NCDF_ERR(nc_inq_grpname(panSubGroupIds[i], &osGroupName[0]));  | 
6367  | 0  |         osGroupName.resize(strlen(osGroupName.data()));  | 
6368  | 0  |         const auto sharpPos = osGroupName.find('#'); | 
6369  | 0  |         if (sharpPos == std::string::npos)  | 
6370  | 0  |         { | 
6371  | 0  |             obj.Add(osGroupName, subObj);  | 
6372  | 0  |         }  | 
6373  | 0  |         else  | 
6374  | 0  |         { | 
6375  | 0  |             osGroupName.resize(sharpPos);  | 
6376  | 0  |             auto iter = oMapNameToArray.find(osGroupName);  | 
6377  | 0  |             if (iter == oMapNameToArray.end())  | 
6378  | 0  |             { | 
6379  | 0  |                 CPLJSONArray array;  | 
6380  | 0  |                 obj.Add(osGroupName, array);  | 
6381  | 0  |                 oMapNameToArray[osGroupName] = array;  | 
6382  | 0  |                 array.Add(subObj);  | 
6383  | 0  |             }  | 
6384  | 0  |             else  | 
6385  | 0  |             { | 
6386  | 0  |                 iter->second.Add(subObj);  | 
6387  | 0  |             }  | 
6388  | 0  |         }  | 
6389  | 0  |     }  | 
6390  | 0  |     CPLFree(panSubGroupIds);  | 
6391  | 0  | }  | 
6392  |  |  | 
6393  |  | std::string NCDFReadMetadataAsJson(int cdfid)  | 
6394  | 0  | { | 
6395  | 0  |     CPLJSONDocument oDoc;  | 
6396  | 0  |     CPLJSONObject oRoot = oDoc.GetRoot();  | 
6397  | 0  |     NCDFReadMetadataAsJson(cdfid, oRoot);  | 
6398  | 0  |     return oDoc.SaveAsString();  | 
6399  | 0  | }  | 
6400  |  |  | 
6401  |  | /************************************************************************/  | 
6402  |  | /*                        ReadAttributes()                              */  | 
6403  |  | /************************************************************************/  | 
6404  |  | CPLErr netCDFDataset::ReadAttributes(int cdfidIn, int var)  | 
6405  |  |  | 
6406  | 422  | { | 
6407  | 422  |     char *pszVarFullName = nullptr;  | 
6408  | 422  |     ERR_RET(NCDFGetVarFullName(cdfidIn, var, &pszVarFullName));  | 
6409  |  |  | 
6410  |  |     // For metadata in Sentinel 5  | 
6411  | 422  |     if (STARTS_WITH(pszVarFullName, "/METADATA/"))  | 
6412  | 0  |     { | 
6413  | 0  |         for (const char *key :  | 
6414  | 0  |              {"ISO_METADATA", "ESA_METADATA", "EOP_METADATA", "QA_STATISTICS", | 
6415  | 0  |               "GRANULE_DESCRIPTION", "ALGORITHM_SETTINGS"})  | 
6416  | 0  |         { | 
6417  | 0  |             if (var == NC_GLOBAL &&  | 
6418  | 0  |                 strcmp(pszVarFullName,  | 
6419  | 0  |                        CPLSPrintf("/METADATA/%s/NC_GLOBAL", key)) == 0) | 
6420  | 0  |             { | 
6421  | 0  |                 CPLFree(pszVarFullName);  | 
6422  | 0  |                 CPLStringList aosList;  | 
6423  | 0  |                 aosList.AddString(CPLString(NCDFReadMetadataAsJson(cdfidIn))  | 
6424  | 0  |                                       .replaceAll("\\/", '/')); | 
6425  | 0  |                 m_oMapDomainToJSon[key] = std::move(aosList);  | 
6426  | 0  |                 return CE_None;  | 
6427  | 0  |             }  | 
6428  | 0  |         }  | 
6429  | 0  |     }  | 
6430  | 422  |     if (STARTS_WITH(pszVarFullName, "/PRODUCT/SUPPORT_DATA/"))  | 
6431  | 0  |     { | 
6432  | 0  |         CPLFree(pszVarFullName);  | 
6433  | 0  |         CPLStringList aosList;  | 
6434  | 0  |         aosList.AddString(  | 
6435  | 0  |             CPLString(NCDFReadMetadataAsJson(cdfidIn)).replaceAll("\\/", '/')); | 
6436  | 0  |         m_oMapDomainToJSon["SUPPORT_DATA"] = std::move(aosList);  | 
6437  | 0  |         return CE_None;  | 
6438  | 0  |     }  | 
6439  |  |  | 
6440  | 422  |     size_t nMetaNameSize =  | 
6441  | 422  |         sizeof(char) * (strlen(pszVarFullName) + 1 + NC_MAX_NAME + 1);  | 
6442  | 422  |     char *pszMetaName = static_cast<char *>(CPLMalloc(nMetaNameSize));  | 
6443  |  |  | 
6444  | 422  |     int nbAttr = 0;  | 
6445  | 422  |     NCDF_ERR(nc_inq_varnatts(cdfidIn, var, &nbAttr));  | 
6446  |  |  | 
6447  | 2.61k  |     for (int l = 0; l < nbAttr; l++)  | 
6448  | 2.19k  |     { | 
6449  | 2.19k  |         char szAttrName[NC_MAX_NAME + 1];  | 
6450  | 2.19k  |         szAttrName[0] = 0;  | 
6451  | 2.19k  |         NCDF_ERR(nc_inq_attname(cdfidIn, var, l, szAttrName));  | 
6452  | 2.19k  |         snprintf(pszMetaName, nMetaNameSize, "%s#%s", pszVarFullName,  | 
6453  | 2.19k  |                  szAttrName);  | 
6454  |  |  | 
6455  | 2.19k  |         char *pszMetaTemp = nullptr;  | 
6456  | 2.19k  |         if (NCDFGetAttr(cdfidIn, var, szAttrName, &pszMetaTemp) == CE_None)  | 
6457  | 2.19k  |         { | 
6458  | 2.19k  |             papszMetadata =  | 
6459  | 2.19k  |                 CSLSetNameValue(papszMetadata, pszMetaName, pszMetaTemp);  | 
6460  | 2.19k  |             CPLFree(pszMetaTemp);  | 
6461  | 2.19k  |             pszMetaTemp = nullptr;  | 
6462  | 2.19k  |         }  | 
6463  | 0  |         else  | 
6464  | 0  |         { | 
6465  | 0  |             CPLDebug("GDAL_netCDF", "invalid metadata %s", pszMetaName); | 
6466  | 0  |         }  | 
6467  | 2.19k  |     }  | 
6468  |  |  | 
6469  | 422  |     CPLFree(pszVarFullName);  | 
6470  | 422  |     CPLFree(pszMetaName);  | 
6471  |  |  | 
6472  | 422  |     if (var == NC_GLOBAL)  | 
6473  | 399  |     { | 
6474  |  |         // Recurse on sub-groups.  | 
6475  | 399  |         int nSubGroups = 0;  | 
6476  | 399  |         int *panSubGroupIds = nullptr;  | 
6477  | 399  |         NCDFGetSubGroups(cdfidIn, &nSubGroups, &panSubGroupIds);  | 
6478  | 399  |         for (int i = 0; i < nSubGroups; i++)  | 
6479  | 0  |         { | 
6480  | 0  |             ReadAttributes(panSubGroupIds[i], var);  | 
6481  | 0  |         }  | 
6482  | 399  |         CPLFree(panSubGroupIds);  | 
6483  | 399  |     }  | 
6484  |  |  | 
6485  | 422  |     return CE_None;  | 
6486  | 422  | }  | 
6487  |  |  | 
6488  |  | /************************************************************************/  | 
6489  |  | /*                netCDFDataset::CreateSubDatasetList()                 */  | 
6490  |  | /************************************************************************/  | 
6491  |  | void netCDFDataset::CreateSubDatasetList(int nGroupId)  | 
6492  | 0  | { | 
6493  | 0  |     char szVarStdName[NC_MAX_NAME + 1];  | 
6494  | 0  |     int *ponDimIds = nullptr;  | 
6495  | 0  |     nc_type nAttype;  | 
6496  | 0  |     size_t nAttlen;  | 
6497  |  | 
  | 
6498  | 0  |     netCDFDataset *poDS = this;  | 
6499  |  | 
  | 
6500  | 0  |     int nVarCount;  | 
6501  | 0  |     nc_inq_nvars(nGroupId, &nVarCount);  | 
6502  |  | 
  | 
6503  | 0  |     const bool bListAllArrays = CPLTestBool(  | 
6504  | 0  |         CSLFetchNameValueDef(papszOpenOptions, "LIST_ALL_ARRAYS", "NO"));  | 
6505  |  | 
  | 
6506  | 0  |     for (int nVar = 0; nVar < nVarCount; nVar++)  | 
6507  | 0  |     { | 
6508  |  | 
  | 
6509  | 0  |         int nDims;  | 
6510  | 0  |         nc_inq_varndims(nGroupId, nVar, &nDims);  | 
6511  |  | 
  | 
6512  | 0  |         if ((bListAllArrays && nDims > 0) || nDims >= 2)  | 
6513  | 0  |         { | 
6514  | 0  |             ponDimIds = static_cast<int *>(CPLCalloc(nDims, sizeof(int)));  | 
6515  | 0  |             nc_inq_vardimid(nGroupId, nVar, ponDimIds);  | 
6516  |  |  | 
6517  |  |             // Create Sub dataset list.  | 
6518  | 0  |             CPLString osDim;  | 
6519  | 0  |             for (int i = 0; i < nDims; i++)  | 
6520  | 0  |             { | 
6521  | 0  |                 size_t nDimLen;  | 
6522  | 0  |                 nc_inq_dimlen(nGroupId, ponDimIds[i], &nDimLen);  | 
6523  | 0  |                 if (!osDim.empty())  | 
6524  | 0  |                     osDim += 'x';  | 
6525  | 0  |                 osDim += CPLSPrintf("%d", (int)nDimLen); | 
6526  | 0  |             }  | 
6527  | 0  |             CPLFree(ponDimIds);  | 
6528  |  | 
  | 
6529  | 0  |             nc_type nVarType;  | 
6530  | 0  |             nc_inq_vartype(nGroupId, nVar, &nVarType);  | 
6531  | 0  |             const char *pszType = "";  | 
6532  | 0  |             switch (nVarType)  | 
6533  | 0  |             { | 
6534  | 0  |                 case NC_BYTE:  | 
6535  | 0  |                     pszType = "8-bit integer";  | 
6536  | 0  |                     break;  | 
6537  | 0  |                 case NC_CHAR:  | 
6538  | 0  |                     pszType = "8-bit character";  | 
6539  | 0  |                     break;  | 
6540  | 0  |                 case NC_SHORT:  | 
6541  | 0  |                     pszType = "16-bit integer";  | 
6542  | 0  |                     break;  | 
6543  | 0  |                 case NC_INT:  | 
6544  | 0  |                     pszType = "32-bit integer";  | 
6545  | 0  |                     break;  | 
6546  | 0  |                 case NC_FLOAT:  | 
6547  | 0  |                     pszType = "32-bit floating-point";  | 
6548  | 0  |                     break;  | 
6549  | 0  |                 case NC_DOUBLE:  | 
6550  | 0  |                     pszType = "64-bit floating-point";  | 
6551  | 0  |                     break;  | 
6552  | 0  |                 case NC_UBYTE:  | 
6553  | 0  |                     pszType = "8-bit unsigned integer";  | 
6554  | 0  |                     break;  | 
6555  | 0  |                 case NC_USHORT:  | 
6556  | 0  |                     pszType = "16-bit unsigned integer";  | 
6557  | 0  |                     break;  | 
6558  | 0  |                 case NC_UINT:  | 
6559  | 0  |                     pszType = "32-bit unsigned integer";  | 
6560  | 0  |                     break;  | 
6561  | 0  |                 case NC_INT64:  | 
6562  | 0  |                     pszType = "64-bit integer";  | 
6563  | 0  |                     break;  | 
6564  | 0  |                 case NC_UINT64:  | 
6565  | 0  |                     pszType = "64-bit unsigned integer";  | 
6566  | 0  |                     break;  | 
6567  | 0  |                 default:  | 
6568  | 0  |                     break;  | 
6569  | 0  |             }  | 
6570  |  |  | 
6571  | 0  |             char *pszName = nullptr;  | 
6572  | 0  |             if (NCDFGetVarFullName(nGroupId, nVar, &pszName) != CE_None)  | 
6573  | 0  |                 continue;  | 
6574  |  |  | 
6575  | 0  |             nSubDatasets++;  | 
6576  |  | 
  | 
6577  | 0  |             nAttlen = 0;  | 
6578  | 0  |             nc_inq_att(nGroupId, nVar, CF_STD_NAME, &nAttype, &nAttlen);  | 
6579  | 0  |             if (nAttlen < sizeof(szVarStdName) &&  | 
6580  | 0  |                 nc_get_att_text(nGroupId, nVar, CF_STD_NAME, szVarStdName) ==  | 
6581  | 0  |                     NC_NOERR)  | 
6582  | 0  |             { | 
6583  | 0  |                 szVarStdName[nAttlen] = '\0';  | 
6584  | 0  |             }  | 
6585  | 0  |             else  | 
6586  | 0  |             { | 
6587  | 0  |                 snprintf(szVarStdName, sizeof(szVarStdName), "%s", pszName);  | 
6588  | 0  |             }  | 
6589  |  | 
  | 
6590  | 0  |             char szTemp[NC_MAX_NAME + 1];  | 
6591  | 0  |             snprintf(szTemp, sizeof(szTemp), "SUBDATASET_%d_NAME",  | 
6592  | 0  |                      nSubDatasets);  | 
6593  |  | 
  | 
6594  | 0  |             if (strchr(pszName, ' ') || strchr(pszName, ':'))  | 
6595  | 0  |             { | 
6596  | 0  |                 poDS->papszSubDatasets = CSLSetNameValue(  | 
6597  | 0  |                     poDS->papszSubDatasets, szTemp,  | 
6598  | 0  |                     CPLSPrintf("NETCDF:\"%s\":\"%s\"", poDS->osFilename.c_str(), | 
6599  | 0  |                                pszName));  | 
6600  | 0  |             }  | 
6601  | 0  |             else  | 
6602  | 0  |             { | 
6603  | 0  |                 poDS->papszSubDatasets = CSLSetNameValue(  | 
6604  | 0  |                     poDS->papszSubDatasets, szTemp,  | 
6605  | 0  |                     CPLSPrintf("NETCDF:\"%s\":%s", poDS->osFilename.c_str(), | 
6606  | 0  |                                pszName));  | 
6607  | 0  |             }  | 
6608  |  | 
  | 
6609  | 0  |             CPLFree(pszName);  | 
6610  |  | 
  | 
6611  | 0  |             snprintf(szTemp, sizeof(szTemp), "SUBDATASET_%d_DESC",  | 
6612  | 0  |                      nSubDatasets);  | 
6613  |  | 
  | 
6614  | 0  |             poDS->papszSubDatasets =  | 
6615  | 0  |                 CSLSetNameValue(poDS->papszSubDatasets, szTemp,  | 
6616  | 0  |                                 CPLSPrintf("[%s] %s (%s)", osDim.c_str(), | 
6617  | 0  |                                            szVarStdName, pszType));  | 
6618  | 0  |         }  | 
6619  | 0  |     }  | 
6620  |  |  | 
6621  |  |     // Recurse on sub groups.  | 
6622  | 0  |     int nSubGroups = 0;  | 
6623  | 0  |     int *panSubGroupIds = nullptr;  | 
6624  | 0  |     NCDFGetSubGroups(nGroupId, &nSubGroups, &panSubGroupIds);  | 
6625  | 0  |     for (int i = 0; i < nSubGroups; i++)  | 
6626  | 0  |     { | 
6627  | 0  |         CreateSubDatasetList(panSubGroupIds[i]);  | 
6628  | 0  |     }  | 
6629  | 0  |     CPLFree(panSubGroupIds);  | 
6630  | 0  | }  | 
6631  |  |  | 
6632  |  | /************************************************************************/  | 
6633  |  | /*                            TestCapability()                          */  | 
6634  |  | /************************************************************************/  | 
6635  |  |  | 
6636  |  | int netCDFDataset::TestCapability(const char *pszCap)  | 
6637  | 0  | { | 
6638  | 0  |     if (EQUAL(pszCap, ODsCCreateLayer))  | 
6639  | 0  |     { | 
6640  | 0  |         return eAccess == GA_Update && nBands == 0 &&  | 
6641  | 0  |                (eMultipleLayerBehavior != SINGLE_LAYER ||  | 
6642  | 0  |                 this->GetLayerCount() == 0 || bSGSupport);  | 
6643  | 0  |     }  | 
6644  | 0  |     else if (EQUAL(pszCap, ODsCZGeometries))  | 
6645  | 0  |         return true;  | 
6646  |  |  | 
6647  | 0  |     return false;  | 
6648  | 0  | }  | 
6649  |  |  | 
6650  |  | /************************************************************************/  | 
6651  |  | /*                            GetLayer()                                */  | 
6652  |  | /************************************************************************/  | 
6653  |  |  | 
6654  |  | OGRLayer *netCDFDataset::GetLayer(int nIdx)  | 
6655  | 46  | { | 
6656  | 46  |     if (nIdx < 0 || nIdx >= this->GetLayerCount())  | 
6657  | 0  |         return nullptr;  | 
6658  | 46  |     return papoLayers[nIdx].get();  | 
6659  | 46  | }  | 
6660  |  |  | 
6661  |  | /************************************************************************/  | 
6662  |  | /*                            ICreateLayer()                            */  | 
6663  |  | /************************************************************************/  | 
6664  |  |  | 
6665  |  | OGRLayer *netCDFDataset::ICreateLayer(const char *pszName,  | 
6666  |  |                                       const OGRGeomFieldDefn *poGeomFieldDefn,  | 
6667  |  |                                       CSLConstList papszOptions)  | 
6668  | 0  | { | 
6669  | 0  |     int nLayerCDFId = cdfid;  | 
6670  | 0  |     if (!TestCapability(ODsCCreateLayer))  | 
6671  | 0  |         return nullptr;  | 
6672  |  |  | 
6673  | 0  |     const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;  | 
6674  | 0  |     const auto poSpatialRef =  | 
6675  | 0  |         poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;  | 
6676  |  | 
  | 
6677  | 0  |     CPLString osNetCDFLayerName(pszName);  | 
6678  | 0  |     const netCDFWriterConfigLayer *poLayerConfig = nullptr;  | 
6679  | 0  |     if (oWriterConfig.m_bIsValid)  | 
6680  | 0  |     { | 
6681  | 0  |         std::map<CPLString, netCDFWriterConfigLayer>::const_iterator  | 
6682  | 0  |             oLayerIter = oWriterConfig.m_oLayers.find(pszName);  | 
6683  | 0  |         if (oLayerIter != oWriterConfig.m_oLayers.end())  | 
6684  | 0  |         { | 
6685  | 0  |             poLayerConfig = &(oLayerIter->second);  | 
6686  | 0  |             osNetCDFLayerName = poLayerConfig->m_osNetCDFName;  | 
6687  | 0  |         }  | 
6688  | 0  |     }  | 
6689  |  | 
  | 
6690  | 0  |     netCDFDataset *poLayerDataset = nullptr;  | 
6691  | 0  |     if (eMultipleLayerBehavior == SEPARATE_FILES)  | 
6692  | 0  |     { | 
6693  | 0  |         char **papszDatasetOptions = nullptr;  | 
6694  | 0  |         papszDatasetOptions = CSLSetNameValue(  | 
6695  | 0  |             papszDatasetOptions, "CONFIG_FILE",  | 
6696  | 0  |             CSLFetchNameValue(papszCreationOptions, "CONFIG_FILE"));  | 
6697  | 0  |         papszDatasetOptions =  | 
6698  | 0  |             CSLSetNameValue(papszDatasetOptions, "FORMAT",  | 
6699  | 0  |                             CSLFetchNameValue(papszCreationOptions, "FORMAT"));  | 
6700  | 0  |         papszDatasetOptions = CSLSetNameValue(  | 
6701  | 0  |             papszDatasetOptions, "WRITE_GDAL_TAGS",  | 
6702  | 0  |             CSLFetchNameValue(papszCreationOptions, "WRITE_GDAL_TAGS"));  | 
6703  | 0  |         const CPLString osLayerFilename(  | 
6704  | 0  |             CPLFormFilenameSafe(osFilename, osNetCDFLayerName, "nc"));  | 
6705  | 0  |         CPLAcquireMutex(hNCMutex, 1000.0);  | 
6706  | 0  |         poLayerDataset =  | 
6707  | 0  |             CreateLL(osLayerFilename, 0, 0, 0, papszDatasetOptions);  | 
6708  | 0  |         CPLReleaseMutex(hNCMutex);  | 
6709  | 0  |         CSLDestroy(papszDatasetOptions);  | 
6710  | 0  |         if (poLayerDataset == nullptr)  | 
6711  | 0  |             return nullptr;  | 
6712  |  |  | 
6713  | 0  |         nLayerCDFId = poLayerDataset->cdfid;  | 
6714  | 0  |         NCDFAddGDALHistory(nLayerCDFId, osLayerFilename, bWriteGDALVersion,  | 
6715  | 0  |                            bWriteGDALHistory, "", "Create",  | 
6716  | 0  |                            NCDF_CONVENTIONS_CF_V1_6);  | 
6717  | 0  |     }  | 
6718  | 0  |     else if (eMultipleLayerBehavior == SEPARATE_GROUPS)  | 
6719  | 0  |     { | 
6720  | 0  |         SetDefineMode(true);  | 
6721  |  | 
  | 
6722  | 0  |         nLayerCDFId = -1;  | 
6723  | 0  |         int status = nc_def_grp(cdfid, osNetCDFLayerName, &nLayerCDFId);  | 
6724  | 0  |         NCDF_ERR(status);  | 
6725  | 0  |         if (status != NC_NOERR)  | 
6726  | 0  |             return nullptr;  | 
6727  |  |  | 
6728  | 0  |         NCDFAddGDALHistory(nLayerCDFId, osFilename, bWriteGDALVersion,  | 
6729  | 0  |                            bWriteGDALHistory, "", "Create",  | 
6730  | 0  |                            NCDF_CONVENTIONS_CF_V1_6);  | 
6731  | 0  |     }  | 
6732  |  |  | 
6733  |  |     // Make a clone to workaround a bug in released MapServer versions  | 
6734  |  |     // that destroys the passed SRS instead of releasing it .  | 
6735  | 0  |     OGRSpatialReference *poSRS = nullptr;  | 
6736  | 0  |     if (poSpatialRef)  | 
6737  | 0  |     { | 
6738  | 0  |         poSRS = poSpatialRef->Clone();  | 
6739  | 0  |         poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);  | 
6740  | 0  |     }  | 
6741  | 0  |     std::shared_ptr<netCDFLayer> poLayer(  | 
6742  | 0  |         new netCDFLayer(poLayerDataset ? poLayerDataset : this, nLayerCDFId,  | 
6743  | 0  |                         osNetCDFLayerName, eGType, poSRS));  | 
6744  | 0  |     if (poSRS != nullptr)  | 
6745  | 0  |         poSRS->Release();  | 
6746  |  |  | 
6747  |  |     // Fetch layer creation options coming from config file  | 
6748  | 0  |     char **papszNewOptions = CSLDuplicate(papszOptions);  | 
6749  | 0  |     if (oWriterConfig.m_bIsValid)  | 
6750  | 0  |     { | 
6751  | 0  |         std::map<CPLString, CPLString>::const_iterator oIter;  | 
6752  | 0  |         for (oIter = oWriterConfig.m_oLayerCreationOptions.begin();  | 
6753  | 0  |              oIter != oWriterConfig.m_oLayerCreationOptions.end(); ++oIter)  | 
6754  | 0  |         { | 
6755  | 0  |             papszNewOptions =  | 
6756  | 0  |                 CSLSetNameValue(papszNewOptions, oIter->first, oIter->second);  | 
6757  | 0  |         }  | 
6758  | 0  |         if (poLayerConfig != nullptr)  | 
6759  | 0  |         { | 
6760  | 0  |             for (oIter = poLayerConfig->m_oLayerCreationOptions.begin();  | 
6761  | 0  |                  oIter != poLayerConfig->m_oLayerCreationOptions.end(); ++oIter)  | 
6762  | 0  |             { | 
6763  | 0  |                 papszNewOptions = CSLSetNameValue(papszNewOptions, oIter->first,  | 
6764  | 0  |                                                   oIter->second);  | 
6765  | 0  |             }  | 
6766  | 0  |         }  | 
6767  | 0  |     }  | 
6768  |  | 
  | 
6769  | 0  |     const bool bRet = poLayer->Create(papszNewOptions, poLayerConfig);  | 
6770  | 0  |     CSLDestroy(papszNewOptions);  | 
6771  |  | 
  | 
6772  | 0  |     if (!bRet)  | 
6773  | 0  |     { | 
6774  | 0  |         return nullptr;  | 
6775  | 0  |     }  | 
6776  |  |  | 
6777  | 0  |     if (poLayerDataset != nullptr)  | 
6778  | 0  |         apoVectorDatasets.push_back(poLayerDataset);  | 
6779  |  | 
  | 
6780  | 0  |     papoLayers.push_back(poLayer);  | 
6781  | 0  |     return poLayer.get();  | 
6782  | 0  | }  | 
6783  |  |  | 
6784  |  | /************************************************************************/  | 
6785  |  | /*                           CloneAttributes()                          */  | 
6786  |  | /************************************************************************/  | 
6787  |  |  | 
6788  |  | bool netCDFDataset::CloneAttributes(int old_cdfid, int new_cdfid, int nSrcVarId,  | 
6789  |  |                                     int nDstVarId)  | 
6790  | 0  | { | 
6791  | 0  |     int nAttCount = -1;  | 
6792  | 0  |     int status = nc_inq_varnatts(old_cdfid, nSrcVarId, &nAttCount);  | 
6793  | 0  |     NCDF_ERR(status);  | 
6794  |  | 
  | 
6795  | 0  |     for (int i = 0; i < nAttCount; i++)  | 
6796  | 0  |     { | 
6797  | 0  |         char szName[NC_MAX_NAME + 1];  | 
6798  | 0  |         szName[0] = 0;  | 
6799  | 0  |         status = nc_inq_attname(old_cdfid, nSrcVarId, i, szName);  | 
6800  | 0  |         NCDF_ERR(status);  | 
6801  |  | 
  | 
6802  | 0  |         status =  | 
6803  | 0  |             nc_copy_att(old_cdfid, nSrcVarId, szName, new_cdfid, nDstVarId);  | 
6804  | 0  |         NCDF_ERR(status);  | 
6805  | 0  |         if (status != NC_NOERR)  | 
6806  | 0  |             return false;  | 
6807  | 0  |     }  | 
6808  |  |  | 
6809  | 0  |     return true;  | 
6810  | 0  | }  | 
6811  |  |  | 
6812  |  | /************************************************************************/  | 
6813  |  | /*                          CloneVariableContent()                      */  | 
6814  |  | /************************************************************************/  | 
6815  |  |  | 
6816  |  | bool netCDFDataset::CloneVariableContent(int old_cdfid, int new_cdfid,  | 
6817  |  |                                          int nSrcVarId, int nDstVarId)  | 
6818  | 0  | { | 
6819  | 0  |     int nVarDimCount = -1;  | 
6820  | 0  |     int status = nc_inq_varndims(old_cdfid, nSrcVarId, &nVarDimCount);  | 
6821  | 0  |     NCDF_ERR(status);  | 
6822  | 0  |     int anDimIds[] = {-1, 1}; | 
6823  | 0  |     status = nc_inq_vardimid(old_cdfid, nSrcVarId, anDimIds);  | 
6824  | 0  |     NCDF_ERR(status);  | 
6825  | 0  |     nc_type nc_datatype = NC_NAT;  | 
6826  | 0  |     status = nc_inq_vartype(old_cdfid, nSrcVarId, &nc_datatype);  | 
6827  | 0  |     NCDF_ERR(status);  | 
6828  | 0  |     size_t nTypeSize = 0;  | 
6829  | 0  |     switch (nc_datatype)  | 
6830  | 0  |     { | 
6831  | 0  |         case NC_BYTE:  | 
6832  | 0  |         case NC_CHAR:  | 
6833  | 0  |             nTypeSize = 1;  | 
6834  | 0  |             break;  | 
6835  | 0  |         case NC_SHORT:  | 
6836  | 0  |             nTypeSize = 2;  | 
6837  | 0  |             break;  | 
6838  | 0  |         case NC_INT:  | 
6839  | 0  |             nTypeSize = 4;  | 
6840  | 0  |             break;  | 
6841  | 0  |         case NC_FLOAT:  | 
6842  | 0  |             nTypeSize = 4;  | 
6843  | 0  |             break;  | 
6844  | 0  |         case NC_DOUBLE:  | 
6845  | 0  |             nTypeSize = 8;  | 
6846  | 0  |             break;  | 
6847  | 0  |         case NC_UBYTE:  | 
6848  | 0  |             nTypeSize = 1;  | 
6849  | 0  |             break;  | 
6850  | 0  |         case NC_USHORT:  | 
6851  | 0  |             nTypeSize = 2;  | 
6852  | 0  |             break;  | 
6853  | 0  |         case NC_UINT:  | 
6854  | 0  |             nTypeSize = 4;  | 
6855  | 0  |             break;  | 
6856  | 0  |         case NC_INT64:  | 
6857  | 0  |         case NC_UINT64:  | 
6858  | 0  |             nTypeSize = 8;  | 
6859  | 0  |             break;  | 
6860  | 0  |         case NC_STRING:  | 
6861  | 0  |             nTypeSize = sizeof(char *);  | 
6862  | 0  |             break;  | 
6863  | 0  |         default:  | 
6864  | 0  |         { | 
6865  | 0  |             CPLError(CE_Failure, CPLE_NotSupported, "Unsupported data type: %d",  | 
6866  | 0  |                      nc_datatype);  | 
6867  | 0  |             return false;  | 
6868  | 0  |         }  | 
6869  | 0  |     }  | 
6870  |  |  | 
6871  | 0  |     size_t nElems = 1;  | 
6872  | 0  |     size_t anStart[NC_MAX_DIMS];  | 
6873  | 0  |     size_t anCount[NC_MAX_DIMS];  | 
6874  | 0  |     size_t nRecords = 1;  | 
6875  | 0  |     for (int i = 0; i < nVarDimCount; i++)  | 
6876  | 0  |     { | 
6877  | 0  |         anStart[i] = 0;  | 
6878  | 0  |         if (i == 0)  | 
6879  | 0  |         { | 
6880  | 0  |             anCount[i] = 1;  | 
6881  | 0  |             status = nc_inq_dimlen(old_cdfid, anDimIds[i], &nRecords);  | 
6882  | 0  |             NCDF_ERR(status);  | 
6883  | 0  |         }  | 
6884  | 0  |         else  | 
6885  | 0  |         { | 
6886  | 0  |             anCount[i] = 0;  | 
6887  | 0  |             status = nc_inq_dimlen(old_cdfid, anDimIds[i], &anCount[i]);  | 
6888  | 0  |             NCDF_ERR(status);  | 
6889  | 0  |             nElems *= anCount[i];  | 
6890  | 0  |         }  | 
6891  | 0  |     }  | 
6892  |  |  | 
6893  |  |     /* Workaround in some cases a netCDF bug:  | 
6894  |  |      * https://github.com/Unidata/netcdf-c/pull/1442 */  | 
6895  | 0  |     if (nRecords > 0 && nRecords < 10 * 1000 * 1000 / (nElems * nTypeSize))  | 
6896  | 0  |     { | 
6897  | 0  |         nElems *= nRecords;  | 
6898  | 0  |         anCount[0] = nRecords;  | 
6899  | 0  |         nRecords = 1;  | 
6900  | 0  |     }  | 
6901  |  | 
  | 
6902  | 0  |     void *pBuffer = VSI_MALLOC2_VERBOSE(nElems, nTypeSize);  | 
6903  | 0  |     if (pBuffer == nullptr)  | 
6904  | 0  |         return false;  | 
6905  |  |  | 
6906  | 0  |     for (size_t iRecord = 0; iRecord < nRecords; iRecord++)  | 
6907  | 0  |     { | 
6908  | 0  |         anStart[0] = iRecord;  | 
6909  |  | 
  | 
6910  | 0  |         switch (nc_datatype)  | 
6911  | 0  |         { | 
6912  | 0  |             case NC_BYTE:  | 
6913  | 0  |                 status =  | 
6914  | 0  |                     nc_get_vara_schar(old_cdfid, nSrcVarId, anStart, anCount,  | 
6915  | 0  |                                       static_cast<signed char *>(pBuffer));  | 
6916  | 0  |                 if (!status)  | 
6917  | 0  |                     status = nc_put_vara_schar(  | 
6918  | 0  |                         new_cdfid, nDstVarId, anStart, anCount,  | 
6919  | 0  |                         static_cast<signed char *>(pBuffer));  | 
6920  | 0  |                 break;  | 
6921  | 0  |             case NC_CHAR:  | 
6922  | 0  |                 status =  | 
6923  | 0  |                     nc_get_vara_text(old_cdfid, nSrcVarId, anStart, anCount,  | 
6924  | 0  |                                      static_cast<char *>(pBuffer));  | 
6925  | 0  |                 if (!status)  | 
6926  | 0  |                     status =  | 
6927  | 0  |                         nc_put_vara_text(new_cdfid, nDstVarId, anStart, anCount,  | 
6928  | 0  |                                          static_cast<char *>(pBuffer));  | 
6929  | 0  |                 break;  | 
6930  | 0  |             case NC_SHORT:  | 
6931  | 0  |                 status =  | 
6932  | 0  |                     nc_get_vara_short(old_cdfid, nSrcVarId, anStart, anCount,  | 
6933  | 0  |                                       static_cast<short *>(pBuffer));  | 
6934  | 0  |                 if (!status)  | 
6935  | 0  |                     status = nc_put_vara_short(new_cdfid, nDstVarId, anStart,  | 
6936  | 0  |                                                anCount,  | 
6937  | 0  |                                                static_cast<short *>(pBuffer));  | 
6938  | 0  |                 break;  | 
6939  | 0  |             case NC_INT:  | 
6940  | 0  |                 status = nc_get_vara_int(old_cdfid, nSrcVarId, anStart, anCount,  | 
6941  | 0  |                                          static_cast<int *>(pBuffer));  | 
6942  | 0  |                 if (!status)  | 
6943  | 0  |                     status =  | 
6944  | 0  |                         nc_put_vara_int(new_cdfid, nDstVarId, anStart, anCount,  | 
6945  | 0  |                                         static_cast<int *>(pBuffer));  | 
6946  | 0  |                 break;  | 
6947  | 0  |             case NC_FLOAT:  | 
6948  | 0  |                 status =  | 
6949  | 0  |                     nc_get_vara_float(old_cdfid, nSrcVarId, anStart, anCount,  | 
6950  | 0  |                                       static_cast<float *>(pBuffer));  | 
6951  | 0  |                 if (!status)  | 
6952  | 0  |                     status = nc_put_vara_float(new_cdfid, nDstVarId, anStart,  | 
6953  | 0  |                                                anCount,  | 
6954  | 0  |                                                static_cast<float *>(pBuffer));  | 
6955  | 0  |                 break;  | 
6956  | 0  |             case NC_DOUBLE:  | 
6957  | 0  |                 status =  | 
6958  | 0  |                     nc_get_vara_double(old_cdfid, nSrcVarId, anStart, anCount,  | 
6959  | 0  |                                        static_cast<double *>(pBuffer));  | 
6960  | 0  |                 if (!status)  | 
6961  | 0  |                     status = nc_put_vara_double(new_cdfid, nDstVarId, anStart,  | 
6962  | 0  |                                                 anCount,  | 
6963  | 0  |                                                 static_cast<double *>(pBuffer));  | 
6964  | 0  |                 break;  | 
6965  | 0  |             case NC_STRING:  | 
6966  | 0  |                 status =  | 
6967  | 0  |                     nc_get_vara_string(old_cdfid, nSrcVarId, anStart, anCount,  | 
6968  | 0  |                                        static_cast<char **>(pBuffer));  | 
6969  | 0  |                 if (!status)  | 
6970  | 0  |                 { | 
6971  | 0  |                     status = nc_put_vara_string(  | 
6972  | 0  |                         new_cdfid, nDstVarId, anStart, anCount,  | 
6973  | 0  |                         static_cast<const char **>(pBuffer));  | 
6974  | 0  |                     nc_free_string(nElems, static_cast<char **>(pBuffer));  | 
6975  | 0  |                 }  | 
6976  | 0  |                 break;  | 
6977  |  |  | 
6978  | 0  |             case NC_UBYTE:  | 
6979  | 0  |                 status =  | 
6980  | 0  |                     nc_get_vara_uchar(old_cdfid, nSrcVarId, anStart, anCount,  | 
6981  | 0  |                                       static_cast<unsigned char *>(pBuffer));  | 
6982  | 0  |                 if (!status)  | 
6983  | 0  |                     status = nc_put_vara_uchar(  | 
6984  | 0  |                         new_cdfid, nDstVarId, anStart, anCount,  | 
6985  | 0  |                         static_cast<unsigned char *>(pBuffer));  | 
6986  | 0  |                 break;  | 
6987  | 0  |             case NC_USHORT:  | 
6988  | 0  |                 status =  | 
6989  | 0  |                     nc_get_vara_ushort(old_cdfid, nSrcVarId, anStart, anCount,  | 
6990  | 0  |                                        static_cast<unsigned short *>(pBuffer));  | 
6991  | 0  |                 if (!status)  | 
6992  | 0  |                     status = nc_put_vara_ushort(  | 
6993  | 0  |                         new_cdfid, nDstVarId, anStart, anCount,  | 
6994  | 0  |                         static_cast<unsigned short *>(pBuffer));  | 
6995  | 0  |                 break;  | 
6996  | 0  |             case NC_UINT:  | 
6997  | 0  |                 status =  | 
6998  | 0  |                     nc_get_vara_uint(old_cdfid, nSrcVarId, anStart, anCount,  | 
6999  | 0  |                                      static_cast<unsigned int *>(pBuffer));  | 
7000  | 0  |                 if (!status)  | 
7001  | 0  |                     status =  | 
7002  | 0  |                         nc_put_vara_uint(new_cdfid, nDstVarId, anStart, anCount,  | 
7003  | 0  |                                          static_cast<unsigned int *>(pBuffer));  | 
7004  | 0  |                 break;  | 
7005  | 0  |             case NC_INT64:  | 
7006  | 0  |                 status =  | 
7007  | 0  |                     nc_get_vara_longlong(old_cdfid, nSrcVarId, anStart, anCount,  | 
7008  | 0  |                                          static_cast<long long *>(pBuffer));  | 
7009  | 0  |                 if (!status)  | 
7010  | 0  |                     status = nc_put_vara_longlong(  | 
7011  | 0  |                         new_cdfid, nDstVarId, anStart, anCount,  | 
7012  | 0  |                         static_cast<long long *>(pBuffer));  | 
7013  | 0  |                 break;  | 
7014  | 0  |             case NC_UINT64:  | 
7015  | 0  |                 status = nc_get_vara_ulonglong(  | 
7016  | 0  |                     old_cdfid, nSrcVarId, anStart, anCount,  | 
7017  | 0  |                     static_cast<unsigned long long *>(pBuffer));  | 
7018  | 0  |                 if (!status)  | 
7019  | 0  |                     status = nc_put_vara_ulonglong(  | 
7020  | 0  |                         new_cdfid, nDstVarId, anStart, anCount,  | 
7021  | 0  |                         static_cast<unsigned long long *>(pBuffer));  | 
7022  | 0  |                 break;  | 
7023  | 0  |             default:  | 
7024  | 0  |                 status = NC_EBADTYPE;  | 
7025  | 0  |         }  | 
7026  |  |  | 
7027  | 0  |         NCDF_ERR(status);  | 
7028  | 0  |         if (status != NC_NOERR)  | 
7029  | 0  |         { | 
7030  | 0  |             VSIFree(pBuffer);  | 
7031  | 0  |             return false;  | 
7032  | 0  |         }  | 
7033  | 0  |     }  | 
7034  |  |  | 
7035  | 0  |     VSIFree(pBuffer);  | 
7036  | 0  |     return true;  | 
7037  | 0  | }  | 
7038  |  |  | 
7039  |  | /************************************************************************/  | 
7040  |  | /*                         NCDFIsUnlimitedDim()                         */  | 
7041  |  | /************************************************************************/  | 
7042  |  |  | 
7043  |  | bool NCDFIsUnlimitedDim(bool bIsNC4, int cdfid, int nDimId)  | 
7044  | 0  | { | 
7045  | 0  |     if (bIsNC4)  | 
7046  | 0  |     { | 
7047  | 0  |         int nUnlimitedDims = 0;  | 
7048  | 0  |         nc_inq_unlimdims(cdfid, &nUnlimitedDims, nullptr);  | 
7049  | 0  |         bool bFound = false;  | 
7050  | 0  |         if (nUnlimitedDims)  | 
7051  | 0  |         { | 
7052  | 0  |             int *panUnlimitedDimIds =  | 
7053  | 0  |                 static_cast<int *>(CPLMalloc(sizeof(int) * nUnlimitedDims));  | 
7054  | 0  |             nc_inq_unlimdims(cdfid, nullptr, panUnlimitedDimIds);  | 
7055  | 0  |             for (int i = 0; i < nUnlimitedDims; i++)  | 
7056  | 0  |             { | 
7057  | 0  |                 if (panUnlimitedDimIds[i] == nDimId)  | 
7058  | 0  |                 { | 
7059  | 0  |                     bFound = true;  | 
7060  | 0  |                     break;  | 
7061  | 0  |                 }  | 
7062  | 0  |             }  | 
7063  | 0  |             CPLFree(panUnlimitedDimIds);  | 
7064  | 0  |         }  | 
7065  | 0  |         return bFound;  | 
7066  | 0  |     }  | 
7067  | 0  |     else  | 
7068  | 0  |     { | 
7069  | 0  |         int nUnlimitedDimId = -1;  | 
7070  | 0  |         nc_inq(cdfid, nullptr, nullptr, nullptr, &nUnlimitedDimId);  | 
7071  | 0  |         return nDimId == nUnlimitedDimId;  | 
7072  | 0  |     }  | 
7073  | 0  | }  | 
7074  |  |  | 
7075  |  | /************************************************************************/  | 
7076  |  | /*                              CloneGrp()                              */  | 
7077  |  | /************************************************************************/  | 
7078  |  |  | 
7079  |  | bool netCDFDataset::CloneGrp(int nOldGrpId, int nNewGrpId, bool bIsNC4,  | 
7080  |  |                              int nLayerId, int nDimIdToGrow, size_t nNewSize)  | 
7081  | 0  | { | 
7082  |  |     // Clone dimensions  | 
7083  | 0  |     int nDimCount = -1;  | 
7084  | 0  |     int status = nc_inq_ndims(nOldGrpId, &nDimCount);  | 
7085  | 0  |     NCDF_ERR(status);  | 
7086  | 0  |     if (nDimCount < 0 || nDimCount > NC_MAX_DIMS)  | 
7087  | 0  |         return false;  | 
7088  | 0  |     int anDimIds[NC_MAX_DIMS];  | 
7089  | 0  |     int nUnlimiDimID = -1;  | 
7090  | 0  |     status = nc_inq_unlimdim(nOldGrpId, &nUnlimiDimID);  | 
7091  | 0  |     NCDF_ERR(status);  | 
7092  | 0  |     if (bIsNC4)  | 
7093  | 0  |     { | 
7094  |  |         // In NC4, the dimension ids of a group are not necessarily in  | 
7095  |  |         // [0,nDimCount-1] range  | 
7096  | 0  |         int nDimCount2 = -1;  | 
7097  | 0  |         status = nc_inq_dimids(nOldGrpId, &nDimCount2, anDimIds, FALSE);  | 
7098  | 0  |         NCDF_ERR(status);  | 
7099  | 0  |         CPLAssert(nDimCount == nDimCount2);  | 
7100  | 0  |     }  | 
7101  | 0  |     else  | 
7102  | 0  |     { | 
7103  | 0  |         for (int i = 0; i < nDimCount; i++)  | 
7104  | 0  |             anDimIds[i] = i;  | 
7105  | 0  |     }  | 
7106  | 0  |     for (int i = 0; i < nDimCount; i++)  | 
7107  | 0  |     { | 
7108  | 0  |         char szDimName[NC_MAX_NAME + 1];  | 
7109  | 0  |         szDimName[0] = 0;  | 
7110  | 0  |         size_t nLen = 0;  | 
7111  | 0  |         const int nDimId = anDimIds[i];  | 
7112  | 0  |         status = nc_inq_dim(nOldGrpId, nDimId, szDimName, &nLen);  | 
7113  | 0  |         NCDF_ERR(status);  | 
7114  | 0  |         if (NCDFIsUnlimitedDim(bIsNC4, nOldGrpId, nDimId))  | 
7115  | 0  |             nLen = NC_UNLIMITED;  | 
7116  | 0  |         else if (nDimId == nDimIdToGrow && nOldGrpId == nLayerId)  | 
7117  | 0  |             nLen = nNewSize;  | 
7118  | 0  |         int nNewDimId = -1;  | 
7119  | 0  |         status = nc_def_dim(nNewGrpId, szDimName, nLen, &nNewDimId);  | 
7120  | 0  |         NCDF_ERR(status);  | 
7121  | 0  |         CPLAssert(nDimId == nNewDimId);  | 
7122  | 0  |         if (status != NC_NOERR)  | 
7123  | 0  |         { | 
7124  | 0  |             return false;  | 
7125  | 0  |         }  | 
7126  | 0  |     }  | 
7127  |  |  | 
7128  |  |     // Clone main attributes  | 
7129  | 0  |     if (!CloneAttributes(nOldGrpId, nNewGrpId, NC_GLOBAL, NC_GLOBAL))  | 
7130  | 0  |     { | 
7131  | 0  |         return false;  | 
7132  | 0  |     }  | 
7133  |  |  | 
7134  |  |     // Clone variable definitions  | 
7135  | 0  |     int nVarCount = -1;  | 
7136  | 0  |     status = nc_inq_nvars(nOldGrpId, &nVarCount);  | 
7137  | 0  |     NCDF_ERR(status);  | 
7138  |  | 
  | 
7139  | 0  |     for (int i = 0; i < nVarCount; i++)  | 
7140  | 0  |     { | 
7141  | 0  |         char szVarName[NC_MAX_NAME + 1];  | 
7142  | 0  |         szVarName[0] = 0;  | 
7143  | 0  |         status = nc_inq_varname(nOldGrpId, i, szVarName);  | 
7144  | 0  |         NCDF_ERR(status);  | 
7145  | 0  |         nc_type nc_datatype = NC_NAT;  | 
7146  | 0  |         status = nc_inq_vartype(nOldGrpId, i, &nc_datatype);  | 
7147  | 0  |         NCDF_ERR(status);  | 
7148  | 0  |         int nVarDimCount = -1;  | 
7149  | 0  |         status = nc_inq_varndims(nOldGrpId, i, &nVarDimCount);  | 
7150  | 0  |         NCDF_ERR(status);  | 
7151  | 0  |         status = nc_inq_vardimid(nOldGrpId, i, anDimIds);  | 
7152  | 0  |         NCDF_ERR(status);  | 
7153  | 0  |         int nNewVarId = -1;  | 
7154  | 0  |         status = nc_def_var(nNewGrpId, szVarName, nc_datatype, nVarDimCount,  | 
7155  | 0  |                             anDimIds, &nNewVarId);  | 
7156  | 0  |         NCDF_ERR(status);  | 
7157  | 0  |         CPLAssert(i == nNewVarId);  | 
7158  | 0  |         if (status != NC_NOERR)  | 
7159  | 0  |         { | 
7160  | 0  |             return false;  | 
7161  | 0  |         }  | 
7162  |  |  | 
7163  | 0  |         if (!CloneAttributes(nOldGrpId, nNewGrpId, i, i))  | 
7164  | 0  |         { | 
7165  | 0  |             return false;  | 
7166  | 0  |         }  | 
7167  | 0  |     }  | 
7168  |  |  | 
7169  | 0  |     status = nc_enddef(nNewGrpId);  | 
7170  | 0  |     NCDF_ERR(status);  | 
7171  | 0  |     if (status != NC_NOERR)  | 
7172  | 0  |     { | 
7173  | 0  |         return false;  | 
7174  | 0  |     }  | 
7175  |  |  | 
7176  |  |     // Clone variable content  | 
7177  | 0  |     for (int i = 0; i < nVarCount; i++)  | 
7178  | 0  |     { | 
7179  | 0  |         if (!CloneVariableContent(nOldGrpId, nNewGrpId, i, i))  | 
7180  | 0  |         { | 
7181  | 0  |             return false;  | 
7182  | 0  |         }  | 
7183  | 0  |     }  | 
7184  |  |  | 
7185  | 0  |     return true;  | 
7186  | 0  | }  | 
7187  |  |  | 
7188  |  | /************************************************************************/  | 
7189  |  | /*                              GrowDim()                               */  | 
7190  |  | /************************************************************************/  | 
7191  |  |  | 
7192  |  | bool netCDFDataset::GrowDim(int nLayerId, int nDimIdToGrow, size_t nNewSize)  | 
7193  | 0  | { | 
7194  | 0  |     int nCreationMode;  | 
7195  |  |     // Set nCreationMode based on eFormat.  | 
7196  | 0  |     switch (eFormat)  | 
7197  | 0  |     { | 
7198  | 0  | #ifdef NETCDF_HAS_NC2  | 
7199  | 0  |         case NCDF_FORMAT_NC2:  | 
7200  | 0  |             nCreationMode = NC_CLOBBER | NC_64BIT_OFFSET;  | 
7201  | 0  |             break;  | 
7202  | 0  | #endif  | 
7203  | 0  |         case NCDF_FORMAT_NC4:  | 
7204  | 0  |             nCreationMode = NC_CLOBBER | NC_NETCDF4;  | 
7205  | 0  |             break;  | 
7206  | 0  |         case NCDF_FORMAT_NC4C:  | 
7207  | 0  |             nCreationMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;  | 
7208  | 0  |             break;  | 
7209  | 0  |         case NCDF_FORMAT_NC:  | 
7210  | 0  |         default:  | 
7211  | 0  |             nCreationMode = NC_CLOBBER;  | 
7212  | 0  |             break;  | 
7213  | 0  |     }  | 
7214  |  |  | 
7215  | 0  |     int new_cdfid = -1;  | 
7216  | 0  |     CPLString osTmpFilename(osFilename + ".tmp");  | 
7217  | 0  |     CPLString osFilenameForNCCreate(osTmpFilename);  | 
7218  |  | #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)  | 
7219  |  |     if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES"))) | 
7220  |  |     { | 
7221  |  |         char *pszTemp =  | 
7222  |  |             CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");  | 
7223  |  |         osFilenameForNCCreate = pszTemp;  | 
7224  |  |         CPLFree(pszTemp);  | 
7225  |  |     }  | 
7226  |  | #endif  | 
7227  | 0  |     int status = nc_create(osFilenameForNCCreate, nCreationMode, &new_cdfid);  | 
7228  | 0  |     NCDF_ERR(status);  | 
7229  | 0  |     if (status != NC_NOERR)  | 
7230  | 0  |         return false;  | 
7231  |  |  | 
7232  | 0  |     if (!CloneGrp(cdfid, new_cdfid, eFormat == NCDF_FORMAT_NC4, nLayerId,  | 
7233  | 0  |                   nDimIdToGrow, nNewSize))  | 
7234  | 0  |     { | 
7235  | 0  |         GDAL_nc_close(new_cdfid);  | 
7236  | 0  |         return false;  | 
7237  | 0  |     }  | 
7238  |  |  | 
7239  | 0  |     int nGroupCount = 0;  | 
7240  | 0  |     std::vector<CPLString> oListGrpName;  | 
7241  | 0  |     if (eFormat == NCDF_FORMAT_NC4 &&  | 
7242  | 0  |         nc_inq_grps(cdfid, &nGroupCount, nullptr) == NC_NOERR &&  | 
7243  | 0  |         nGroupCount > 0)  | 
7244  | 0  |     { | 
7245  | 0  |         int *panGroupIds =  | 
7246  | 0  |             static_cast<int *>(CPLMalloc(sizeof(int) * nGroupCount));  | 
7247  | 0  |         status = nc_inq_grps(cdfid, nullptr, panGroupIds);  | 
7248  | 0  |         NCDF_ERR(status);  | 
7249  | 0  |         for (int i = 0; i < nGroupCount; i++)  | 
7250  | 0  |         { | 
7251  | 0  |             char szGroupName[NC_MAX_NAME + 1];  | 
7252  | 0  |             szGroupName[0] = 0;  | 
7253  | 0  |             NCDF_ERR(nc_inq_grpname(panGroupIds[i], szGroupName));  | 
7254  | 0  |             int nNewGrpId = -1;  | 
7255  | 0  |             status = nc_def_grp(new_cdfid, szGroupName, &nNewGrpId);  | 
7256  | 0  |             NCDF_ERR(status);  | 
7257  | 0  |             if (status != NC_NOERR)  | 
7258  | 0  |             { | 
7259  | 0  |                 CPLFree(panGroupIds);  | 
7260  | 0  |                 GDAL_nc_close(new_cdfid);  | 
7261  | 0  |                 return false;  | 
7262  | 0  |             }  | 
7263  | 0  |             if (!CloneGrp(panGroupIds[i], nNewGrpId, /*bIsNC4=*/true, nLayerId,  | 
7264  | 0  |                           nDimIdToGrow, nNewSize))  | 
7265  | 0  |             { | 
7266  | 0  |                 CPLFree(panGroupIds);  | 
7267  | 0  |                 GDAL_nc_close(new_cdfid);  | 
7268  | 0  |                 return false;  | 
7269  | 0  |             }  | 
7270  | 0  |         }  | 
7271  | 0  |         CPLFree(panGroupIds);  | 
7272  |  | 
  | 
7273  | 0  |         for (int i = 0; i < this->GetLayerCount(); i++)  | 
7274  | 0  |         { | 
7275  | 0  |             auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());  | 
7276  | 0  |             if (poLayer)  | 
7277  | 0  |             { | 
7278  | 0  |                 char szGroupName[NC_MAX_NAME + 1];  | 
7279  | 0  |                 szGroupName[0] = 0;  | 
7280  | 0  |                 status = nc_inq_grpname(poLayer->GetCDFID(), szGroupName);  | 
7281  | 0  |                 NCDF_ERR(status);  | 
7282  | 0  |                 oListGrpName.push_back(szGroupName);  | 
7283  | 0  |             }  | 
7284  | 0  |         }  | 
7285  | 0  |     }  | 
7286  |  |  | 
7287  | 0  |     GDAL_nc_close(cdfid);  | 
7288  | 0  |     cdfid = -1;  | 
7289  | 0  |     GDAL_nc_close(new_cdfid);  | 
7290  |  | 
  | 
7291  | 0  |     CPLString osOriFilename(osFilename + ".ori");  | 
7292  | 0  |     if (VSIRename(osFilename, osOriFilename) != 0 ||  | 
7293  | 0  |         VSIRename(osTmpFilename, osFilename) != 0)  | 
7294  | 0  |     { | 
7295  | 0  |         CPLError(CE_Failure, CPLE_FileIO, "Renaming of files failed");  | 
7296  | 0  |         return false;  | 
7297  | 0  |     }  | 
7298  | 0  |     VSIUnlink(osOriFilename);  | 
7299  |  | 
  | 
7300  | 0  |     CPLString osFilenameForNCOpen(osFilename);  | 
7301  |  | #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)  | 
7302  |  |     if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES"))) | 
7303  |  |     { | 
7304  |  |         char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");  | 
7305  |  |         osFilenameForNCOpen = pszTemp;  | 
7306  |  |         CPLFree(pszTemp);  | 
7307  |  |     }  | 
7308  |  | #endif  | 
7309  | 0  |     status = GDAL_nc_open(osFilenameForNCOpen, NC_WRITE, &cdfid);  | 
7310  | 0  |     NCDF_ERR(status);  | 
7311  | 0  |     if (status != NC_NOERR)  | 
7312  | 0  |         return false;  | 
7313  | 0  |     bDefineMode = false;  | 
7314  |  | 
  | 
7315  | 0  |     if (!oListGrpName.empty())  | 
7316  | 0  |     { | 
7317  | 0  |         for (int i = 0; i < this->GetLayerCount(); i++)  | 
7318  | 0  |         { | 
7319  | 0  |             auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());  | 
7320  | 0  |             if (poLayer)  | 
7321  | 0  |             { | 
7322  | 0  |                 int nNewLayerCDFID = -1;  | 
7323  | 0  |                 status = nc_inq_ncid(cdfid, oListGrpName[i].c_str(),  | 
7324  | 0  |                                      &nNewLayerCDFID);  | 
7325  | 0  |                 NCDF_ERR(status);  | 
7326  | 0  |                 poLayer->SetCDFID(nNewLayerCDFID);  | 
7327  | 0  |             }  | 
7328  | 0  |         }  | 
7329  | 0  |     }  | 
7330  | 0  |     else  | 
7331  | 0  |     { | 
7332  | 0  |         for (int i = 0; i < this->GetLayerCount(); i++)  | 
7333  | 0  |         { | 
7334  | 0  |             auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());  | 
7335  | 0  |             if (poLayer)  | 
7336  | 0  |                 poLayer->SetCDFID(cdfid);  | 
7337  | 0  |         }  | 
7338  | 0  |     }  | 
7339  |  | 
  | 
7340  | 0  |     return true;  | 
7341  | 0  | }  | 
7342  |  |  | 
7343  |  | #ifdef ENABLE_NCDUMP  | 
7344  |  |  | 
7345  |  | /************************************************************************/  | 
7346  |  | /*                      netCDFDatasetCreateTempFile()                   */  | 
7347  |  | /************************************************************************/  | 
7348  |  |  | 
7349  |  | /* Create a netCDF file from a text dump (format of ncdump) */  | 
7350  |  | /* Mostly to easy fuzzing of the driver, while still generating valid */  | 
7351  |  | /* netCDF files. */  | 
7352  |  | /* Note: not all data types are supported ! */  | 
7353  |  | bool netCDFDatasetCreateTempFile(NetCDFFormatEnum eFormat,  | 
7354  |  |                                  const char *pszTmpFilename, VSILFILE *fpSrc)  | 
7355  | 399  | { | 
7356  | 399  |     CPL_IGNORE_RET_VAL(eFormat);  | 
7357  | 399  |     int nCreateMode = NC_CLOBBER;  | 
7358  | 399  |     if (eFormat == NCDF_FORMAT_NC4)  | 
7359  | 8  |         nCreateMode |= NC_NETCDF4;  | 
7360  | 391  |     else if (eFormat == NCDF_FORMAT_NC4C)  | 
7361  | 141  |         nCreateMode |= NC_NETCDF4 | NC_CLASSIC_MODEL;  | 
7362  | 399  |     int nCdfId = -1;  | 
7363  | 399  |     int status = nc_create(pszTmpFilename, nCreateMode, &nCdfId);  | 
7364  | 399  |     if (status != NC_NOERR)  | 
7365  | 0  |     { | 
7366  | 0  |         return false;  | 
7367  | 0  |     }  | 
7368  | 399  |     VSIFSeekL(fpSrc, 0, SEEK_SET);  | 
7369  | 399  |     const char *pszLine;  | 
7370  | 399  |     constexpr int SECTION_NONE = 0;  | 
7371  | 399  |     constexpr int SECTION_DIMENSIONS = 1;  | 
7372  | 399  |     constexpr int SECTION_VARIABLES = 2;  | 
7373  | 399  |     constexpr int SECTION_DATA = 3;  | 
7374  | 399  |     int nActiveSection = SECTION_NONE;  | 
7375  | 399  |     std::map<CPLString, int> oMapDimToId;  | 
7376  | 399  |     std::map<int, int> oMapDimIdToDimLen;  | 
7377  | 399  |     std::map<CPLString, int> oMapVarToId;  | 
7378  | 399  |     std::map<int, std::vector<int>> oMapVarIdToVectorOfDimId;  | 
7379  | 399  |     std::map<int, int> oMapVarIdToType;  | 
7380  | 399  |     std::set<CPLString> oSetAttrDefined;  | 
7381  | 399  |     oMapVarToId[""] = -1;  | 
7382  | 399  |     size_t nTotalVarSize = 0;  | 
7383  | 664k  |     while ((pszLine = CPLReadLineL(fpSrc)) != nullptr)  | 
7384  | 664k  |     { | 
7385  | 664k  |         if (STARTS_WITH(pszLine, "dimensions:") &&  | 
7386  | 664k  |             nActiveSection == SECTION_NONE)  | 
7387  | 399  |         { | 
7388  | 399  |             nActiveSection = SECTION_DIMENSIONS;  | 
7389  | 399  |         }  | 
7390  | 663k  |         else if (STARTS_WITH(pszLine, "variables:") &&  | 
7391  | 663k  |                  nActiveSection == SECTION_DIMENSIONS)  | 
7392  | 365  |         { | 
7393  | 365  |             nActiveSection = SECTION_VARIABLES;  | 
7394  | 365  |         }  | 
7395  | 663k  |         else if (STARTS_WITH(pszLine, "data:") &&  | 
7396  | 663k  |                  nActiveSection == SECTION_VARIABLES)  | 
7397  | 219  |         { | 
7398  | 219  |             nActiveSection = SECTION_DATA;  | 
7399  | 219  |             status = nc_enddef(nCdfId);  | 
7400  | 219  |             if (status != NC_NOERR)  | 
7401  | 0  |             { | 
7402  | 0  |                 CPLDebug("netCDF", "nc_enddef() failed: %s", | 
7403  | 0  |                          nc_strerror(status));  | 
7404  | 0  |             }  | 
7405  | 219  |         }  | 
7406  | 663k  |         else if (nActiveSection == SECTION_DIMENSIONS)  | 
7407  | 103k  |         { | 
7408  | 103k  |             char **papszTokens = CSLTokenizeString2(pszLine, " \t=;", 0);  | 
7409  | 103k  |             if (CSLCount(papszTokens) == 2)  | 
7410  | 23.6k  |             { | 
7411  | 23.6k  |                 const char *pszDimName = papszTokens[0];  | 
7412  | 23.6k  |                 bool bValidName = true;  | 
7413  | 23.6k  |                 if (STARTS_WITH(pszDimName, "_nc4_non_coord_"))  | 
7414  | 0  |                 { | 
7415  |  |                     // This is an internal netcdf prefix. Using it may  | 
7416  |  |                     // cause memory leaks.  | 
7417  | 0  |                     bValidName = false;  | 
7418  | 0  |                 }  | 
7419  | 23.6k  |                 if (!bValidName)  | 
7420  | 0  |                 { | 
7421  | 0  |                     CPLDebug("netCDF", | 
7422  | 0  |                              "nc_def_dim(%s) failed: invalid name found",  | 
7423  | 0  |                              pszDimName);  | 
7424  | 0  |                     CSLDestroy(papszTokens);  | 
7425  | 0  |                     continue;  | 
7426  | 0  |                 }  | 
7427  |  |  | 
7428  | 23.6k  |                 const bool bIsASCII =  | 
7429  | 23.6k  |                     CPLIsASCII(pszDimName, static_cast<size_t>(-1));  | 
7430  | 23.6k  |                 if (!bIsASCII)  | 
7431  | 3.09k  |                 { | 
7432  |  |                     // Workaround https://github.com/Unidata/netcdf-c/pull/450  | 
7433  | 3.09k  |                     CPLDebug("netCDF", | 
7434  | 3.09k  |                              "nc_def_dim(%s) failed: rejected because "  | 
7435  | 3.09k  |                              "of non-ASCII characters",  | 
7436  | 3.09k  |                              pszDimName);  | 
7437  | 3.09k  |                     CSLDestroy(papszTokens);  | 
7438  | 3.09k  |                     continue;  | 
7439  | 3.09k  |                 }  | 
7440  | 20.5k  |                 int nDimSize = EQUAL(papszTokens[1], "UNLIMITED")  | 
7441  | 20.5k  |                                    ? NC_UNLIMITED  | 
7442  | 20.5k  |                                    : atoi(papszTokens[1]);  | 
7443  | 20.5k  |                 if (nDimSize >= 1000)  | 
7444  | 83  |                     nDimSize = 1000;  // to avoid very long processing  | 
7445  | 20.5k  |                 if (nDimSize >= 0)  | 
7446  | 14.1k  |                 { | 
7447  | 14.1k  |                     int nDimId = -1;  | 
7448  | 14.1k  |                     status = nc_def_dim(nCdfId, pszDimName, nDimSize, &nDimId);  | 
7449  | 14.1k  |                     if (status != NC_NOERR)  | 
7450  | 13.0k  |                     { | 
7451  | 13.0k  |                         CPLDebug("netCDF", "nc_def_dim(%s, %d) failed: %s", | 
7452  | 13.0k  |                                  pszDimName, nDimSize, nc_strerror(status));  | 
7453  | 13.0k  |                     }  | 
7454  | 1.09k  |                     else  | 
7455  | 1.09k  |                     { | 
7456  |  | #ifdef DEBUG_VERBOSE  | 
7457  |  |                         CPLDebug("netCDF", "nc_def_dim(%s, %d) (%s) succeeded", | 
7458  |  |                                  pszDimName, nDimSize, pszLine);  | 
7459  |  | #endif  | 
7460  | 1.09k  |                         oMapDimToId[pszDimName] = nDimId;  | 
7461  | 1.09k  |                         oMapDimIdToDimLen[nDimId] = nDimSize;  | 
7462  | 1.09k  |                     }  | 
7463  | 14.1k  |                 }  | 
7464  | 20.5k  |             }  | 
7465  | 100k  |             CSLDestroy(papszTokens);  | 
7466  | 100k  |         }  | 
7467  | 559k  |         else if (nActiveSection == SECTION_VARIABLES)  | 
7468  | 377k  |         { | 
7469  | 527k  |             while (*pszLine == ' ' || *pszLine == '\t')  | 
7470  | 150k  |                 pszLine++;  | 
7471  | 377k  |             const char *pszColumn = strchr(pszLine, ':');  | 
7472  | 377k  |             const char *pszEqual = strchr(pszLine, '=');  | 
7473  | 377k  |             if (pszColumn == nullptr)  | 
7474  | 315k  |             { | 
7475  | 315k  |                 char **papszTokens = CSLTokenizeString2(pszLine, " \t=(),;", 0);  | 
7476  | 315k  |                 if (CSLCount(papszTokens) >= 2)  | 
7477  | 192k  |                 { | 
7478  | 192k  |                     const char *pszVarName = papszTokens[1];  | 
7479  | 192k  |                     bool bValidName = true;  | 
7480  | 192k  |                     if (STARTS_WITH(pszVarName, "_nc4_non_coord_"))  | 
7481  | 0  |                     { | 
7482  |  |                         // This is an internal netcdf prefix. Using it may  | 
7483  |  |                         // cause memory leaks.  | 
7484  | 0  |                         bValidName = false;  | 
7485  | 0  |                     }  | 
7486  | 1.64M  |                     for (int i = 0; pszVarName[i]; i++)  | 
7487  | 1.45M  |                     { | 
7488  | 1.45M  |                         if (!((pszVarName[i] >= 'a' && pszVarName[i] <= 'z') ||  | 
7489  | 1.45M  |                               (pszVarName[i] >= 'A' && pszVarName[i] <= 'Z') ||  | 
7490  | 1.45M  |                               (pszVarName[i] >= '0' && pszVarName[i] <= '9') ||  | 
7491  | 1.45M  |                               pszVarName[i] == '_'))  | 
7492  | 372k  |                         { | 
7493  | 372k  |                             bValidName = false;  | 
7494  | 372k  |                         }  | 
7495  | 1.45M  |                     }  | 
7496  | 192k  |                     if (!bValidName)  | 
7497  | 82.2k  |                     { | 
7498  | 82.2k  |                         CPLDebug(  | 
7499  | 82.2k  |                             "netCDF",  | 
7500  | 82.2k  |                             "nc_def_var(%s) failed: illegal character found",  | 
7501  | 82.2k  |                             pszVarName);  | 
7502  | 82.2k  |                         CSLDestroy(papszTokens);  | 
7503  | 82.2k  |                         continue;  | 
7504  | 82.2k  |                     }  | 
7505  | 110k  |                     if (oMapVarToId.find(pszVarName) != oMapVarToId.end())  | 
7506  | 41.2k  |                     { | 
7507  | 41.2k  |                         CPLDebug("netCDF", | 
7508  | 41.2k  |                                  "nc_def_var(%s) failed: already defined",  | 
7509  | 41.2k  |                                  pszVarName);  | 
7510  | 41.2k  |                         CSLDestroy(papszTokens);  | 
7511  | 41.2k  |                         continue;  | 
7512  | 41.2k  |                     }  | 
7513  | 69.3k  |                     const char *pszVarType = papszTokens[0];  | 
7514  | 69.3k  |                     int nc_datatype = NC_BYTE;  | 
7515  | 69.3k  |                     size_t nDataTypeSize = 1;  | 
7516  | 69.3k  |                     if (EQUAL(pszVarType, "char"))  | 
7517  | 1.31k  |                     { | 
7518  | 1.31k  |                         nc_datatype = NC_CHAR;  | 
7519  | 1.31k  |                         nDataTypeSize = 1;  | 
7520  | 1.31k  |                     }  | 
7521  | 68.0k  |                     else if (EQUAL(pszVarType, "byte"))  | 
7522  | 0  |                     { | 
7523  | 0  |                         nc_datatype = NC_BYTE;  | 
7524  | 0  |                         nDataTypeSize = 1;  | 
7525  | 0  |                     }  | 
7526  | 68.0k  |                     else if (EQUAL(pszVarType, "short"))  | 
7527  | 0  |                     { | 
7528  | 0  |                         nc_datatype = NC_SHORT;  | 
7529  | 0  |                         nDataTypeSize = 2;  | 
7530  | 0  |                     }  | 
7531  | 68.0k  |                     else if (EQUAL(pszVarType, "int"))  | 
7532  | 35  |                     { | 
7533  | 35  |                         nc_datatype = NC_INT;  | 
7534  | 35  |                         nDataTypeSize = 4;  | 
7535  | 35  |                     }  | 
7536  | 68.0k  |                     else if (EQUAL(pszVarType, "float"))  | 
7537  | 0  |                     { | 
7538  | 0  |                         nc_datatype = NC_FLOAT;  | 
7539  | 0  |                         nDataTypeSize = 4;  | 
7540  | 0  |                     }  | 
7541  | 68.0k  |                     else if (EQUAL(pszVarType, "double"))  | 
7542  | 43  |                     { | 
7543  | 43  |                         nc_datatype = NC_DOUBLE;  | 
7544  | 43  |                         nDataTypeSize = 8;  | 
7545  | 43  |                     }  | 
7546  | 67.9k  |                     else if (EQUAL(pszVarType, "ubyte"))  | 
7547  | 0  |                     { | 
7548  | 0  |                         nc_datatype = NC_UBYTE;  | 
7549  | 0  |                         nDataTypeSize = 1;  | 
7550  | 0  |                     }  | 
7551  | 67.9k  |                     else if (EQUAL(pszVarType, "ushort"))  | 
7552  | 0  |                     { | 
7553  | 0  |                         nc_datatype = NC_USHORT;  | 
7554  | 0  |                         nDataTypeSize = 2;  | 
7555  | 0  |                     }  | 
7556  | 67.9k  |                     else if (EQUAL(pszVarType, "uint"))  | 
7557  | 18  |                     { | 
7558  | 18  |                         nc_datatype = NC_UINT;  | 
7559  | 18  |                         nDataTypeSize = 4;  | 
7560  | 18  |                     }  | 
7561  | 67.9k  |                     else if (EQUAL(pszVarType, "int64"))  | 
7562  | 0  |                     { | 
7563  | 0  |                         nc_datatype = NC_INT64;  | 
7564  | 0  |                         nDataTypeSize = 8;  | 
7565  | 0  |                     }  | 
7566  | 67.9k  |                     else if (EQUAL(pszVarType, "uint64"))  | 
7567  | 0  |                     { | 
7568  | 0  |                         nc_datatype = NC_UINT64;  | 
7569  | 0  |                         nDataTypeSize = 8;  | 
7570  | 0  |                     }  | 
7571  |  |  | 
7572  | 69.3k  |                     int nDims = CSLCount(papszTokens) - 2;  | 
7573  | 69.3k  |                     if (nDims >= 32)  | 
7574  | 88  |                     { | 
7575  |  |                         // The number of dimensions in a netCDFv4 file is  | 
7576  |  |                         // limited by #define H5S_MAX_RANK    32  | 
7577  |  |                         // but libnetcdf doesn't check that...  | 
7578  | 88  |                         CPLDebug("netCDF", | 
7579  | 88  |                                  "nc_def_var(%s) failed: too many dimensions",  | 
7580  | 88  |                                  pszVarName);  | 
7581  | 88  |                         CSLDestroy(papszTokens);  | 
7582  | 88  |                         continue;  | 
7583  | 88  |                     }  | 
7584  | 69.2k  |                     std::vector<int> aoDimIds;  | 
7585  | 69.2k  |                     bool bFailed = false;  | 
7586  | 69.2k  |                     size_t nSize = 1;  | 
7587  | 83.0k  |                     for (int i = 0; i < nDims; i++)  | 
7588  | 76.9k  |                     { | 
7589  | 76.9k  |                         const char *pszDimName = papszTokens[2 + i];  | 
7590  | 76.9k  |                         if (oMapDimToId.find(pszDimName) == oMapDimToId.end())  | 
7591  | 63.2k  |                         { | 
7592  | 63.2k  |                             bFailed = true;  | 
7593  | 63.2k  |                             break;  | 
7594  | 63.2k  |                         }  | 
7595  | 13.7k  |                         const int nDimId = oMapDimToId[pszDimName];  | 
7596  | 13.7k  |                         aoDimIds.push_back(nDimId);  | 
7597  |  |  | 
7598  | 13.7k  |                         const size_t nDimSize = oMapDimIdToDimLen[nDimId];  | 
7599  | 13.7k  |                         if (nDimSize != 0)  | 
7600  | 5.51k  |                         { | 
7601  | 5.51k  |                             if (nSize >  | 
7602  | 5.51k  |                                 std::numeric_limits<size_t>::max() / nDimSize)  | 
7603  | 0  |                             { | 
7604  | 0  |                                 bFailed = true;  | 
7605  | 0  |                                 break;  | 
7606  | 0  |                             }  | 
7607  | 5.51k  |                             else  | 
7608  | 5.51k  |                             { | 
7609  | 5.51k  |                                 nSize *= nDimSize;  | 
7610  | 5.51k  |                             }  | 
7611  | 5.51k  |                         }  | 
7612  | 13.7k  |                     }  | 
7613  | 69.2k  |                     if (bFailed)  | 
7614  | 63.2k  |                     { | 
7615  | 63.2k  |                         CPLDebug("netCDF", | 
7616  | 63.2k  |                                  "nc_def_var(%s) failed: unknown dimension(s)",  | 
7617  | 63.2k  |                                  pszVarName);  | 
7618  | 63.2k  |                         CSLDestroy(papszTokens);  | 
7619  | 63.2k  |                         continue;  | 
7620  | 63.2k  |                     }  | 
7621  | 6.05k  |                     if (nSize > 100U * 1024 * 1024 / nDataTypeSize)  | 
7622  | 0  |                     { | 
7623  | 0  |                         CPLDebug("netCDF", | 
7624  | 0  |                                  "nc_def_var(%s) failed: too large data",  | 
7625  | 0  |                                  pszVarName);  | 
7626  | 0  |                         CSLDestroy(papszTokens);  | 
7627  | 0  |                         continue;  | 
7628  | 0  |                     }  | 
7629  | 6.05k  |                     if (nTotalVarSize >  | 
7630  | 6.05k  |                             std::numeric_limits<size_t>::max() - nSize ||  | 
7631  | 6.05k  |                         nTotalVarSize + nSize > 100 * 1024 * 1024)  | 
7632  | 0  |                     { | 
7633  | 0  |                         CPLDebug("netCDF", | 
7634  | 0  |                                  "nc_def_var(%s) failed: too large data",  | 
7635  | 0  |                                  pszVarName);  | 
7636  | 0  |                         CSLDestroy(papszTokens);  | 
7637  | 0  |                         continue;  | 
7638  | 0  |                     }  | 
7639  | 6.05k  |                     nTotalVarSize += nSize;  | 
7640  |  |  | 
7641  | 6.05k  |                     int nVarId = -1;  | 
7642  | 6.05k  |                     status =  | 
7643  | 6.05k  |                         nc_def_var(nCdfId, pszVarName, nc_datatype, nDims,  | 
7644  | 6.05k  |                                    (nDims) ? &aoDimIds[0] : nullptr, &nVarId);  | 
7645  | 6.05k  |                     if (status != NC_NOERR)  | 
7646  | 255  |                     { | 
7647  | 255  |                         CPLDebug("netCDF", "nc_def_var(%s) failed: %s", | 
7648  | 255  |                                  pszVarName, nc_strerror(status));  | 
7649  | 255  |                     }  | 
7650  | 5.79k  |                     else  | 
7651  | 5.79k  |                     { | 
7652  |  | #ifdef DEBUG_VERBOSE  | 
7653  |  |                         CPLDebug("netCDF", "nc_def_var(%s) (%s) succeeded", | 
7654  |  |                                  pszVarName, pszLine);  | 
7655  |  | #endif  | 
7656  | 5.79k  |                         oMapVarToId[pszVarName] = nVarId;  | 
7657  | 5.79k  |                         oMapVarIdToType[nVarId] = nc_datatype;  | 
7658  | 5.79k  |                         oMapVarIdToVectorOfDimId[nVarId] = std::move(aoDimIds);  | 
7659  | 5.79k  |                     }  | 
7660  | 6.05k  |                 }  | 
7661  | 128k  |                 CSLDestroy(papszTokens);  | 
7662  | 128k  |             }  | 
7663  | 62.2k  |             else if (pszEqual != nullptr && pszEqual - pszColumn > 0)  | 
7664  | 20.3k  |             { | 
7665  | 20.3k  |                 CPLString osVarName(pszLine, pszColumn - pszLine);  | 
7666  | 20.3k  |                 CPLString osAttrName(pszColumn + 1, pszEqual - pszColumn - 1);  | 
7667  | 20.3k  |                 osAttrName.Trim();  | 
7668  | 20.3k  |                 if (oMapVarToId.find(osVarName) == oMapVarToId.end())  | 
7669  | 5.97k  |                 { | 
7670  | 5.97k  |                     CPLDebug("netCDF", | 
7671  | 5.97k  |                              "nc_put_att(%s:%s) failed: "  | 
7672  | 5.97k  |                              "no corresponding variable",  | 
7673  | 5.97k  |                              osVarName.c_str(), osAttrName.c_str());  | 
7674  | 5.97k  |                     continue;  | 
7675  | 5.97k  |                 }  | 
7676  | 14.3k  |                 bool bValidName = true;  | 
7677  | 239k  |                 for (size_t i = 0; i < osAttrName.size(); i++)  | 
7678  | 225k  |                 { | 
7679  | 225k  |                     if (!((osAttrName[i] >= 'a' && osAttrName[i] <= 'z') ||  | 
7680  | 225k  |                           (osAttrName[i] >= 'A' && osAttrName[i] <= 'Z') ||  | 
7681  | 225k  |                           (osAttrName[i] >= '0' && osAttrName[i] <= '9') ||  | 
7682  | 225k  |                           osAttrName[i] == '_'))  | 
7683  | 84.0k  |                     { | 
7684  | 84.0k  |                         bValidName = false;  | 
7685  | 84.0k  |                     }  | 
7686  | 225k  |                 }  | 
7687  | 14.3k  |                 if (!bValidName)  | 
7688  | 2.36k  |                 { | 
7689  | 2.36k  |                     CPLDebug(  | 
7690  | 2.36k  |                         "netCDF",  | 
7691  | 2.36k  |                         "nc_put_att(%s:%s) failed: illegal character found",  | 
7692  | 2.36k  |                         osVarName.c_str(), osAttrName.c_str());  | 
7693  | 2.36k  |                     continue;  | 
7694  | 2.36k  |                 }  | 
7695  | 11.9k  |                 if (oSetAttrDefined.find(osVarName + ":" + osAttrName) !=  | 
7696  | 11.9k  |                     oSetAttrDefined.end())  | 
7697  | 7.59k  |                 { | 
7698  | 7.59k  |                     CPLDebug("netCDF", | 
7699  | 7.59k  |                              "nc_put_att(%s:%s) failed: already defined",  | 
7700  | 7.59k  |                              osVarName.c_str(), osAttrName.c_str());  | 
7701  | 7.59k  |                     continue;  | 
7702  | 7.59k  |                 }  | 
7703  |  |  | 
7704  | 4.39k  |                 const int nVarId = oMapVarToId[osVarName];  | 
7705  | 4.39k  |                 const char *pszValue = pszEqual + 1;  | 
7706  | 6.73k  |                 while (*pszValue == ' ')  | 
7707  | 2.33k  |                     pszValue++;  | 
7708  |  |  | 
7709  | 4.39k  |                 status = NC_EBADTYPE;  | 
7710  | 4.39k  |                 if (*pszValue == '"')  | 
7711  | 493  |                 { | 
7712  |  |                     // For _FillValue, the attribute type should match  | 
7713  |  |                     // the variable type. Leaks memory with NC4 otherwise  | 
7714  | 493  |                     if (osAttrName == "_FillValue")  | 
7715  | 0  |                     { | 
7716  | 0  |                         CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s", | 
7717  | 0  |                                  osVarName.c_str(), osAttrName.c_str(),  | 
7718  | 0  |                                  nc_strerror(status));  | 
7719  | 0  |                         continue;  | 
7720  | 0  |                     }  | 
7721  |  |  | 
7722  |  |                     // Unquote and unescape string value  | 
7723  | 493  |                     CPLString osVal(pszValue + 1);  | 
7724  | 5.24k  |                     while (!osVal.empty())  | 
7725  | 5.00k  |                     { | 
7726  | 5.00k  |                         if (osVal.back() == ';' || osVal.back() == ' ')  | 
7727  | 4.75k  |                         { | 
7728  | 4.75k  |                             osVal.pop_back();  | 
7729  | 4.75k  |                         }  | 
7730  | 258  |                         else if (osVal.back() == '"')  | 
7731  | 19  |                         { | 
7732  | 19  |                             osVal.pop_back();  | 
7733  | 19  |                             break;  | 
7734  | 19  |                         }  | 
7735  | 239  |                         else  | 
7736  | 239  |                         { | 
7737  | 239  |                             break;  | 
7738  | 239  |                         }  | 
7739  | 5.00k  |                     }  | 
7740  | 493  |                     osVal.replaceAll("\\\"", '"'); | 
7741  | 493  |                     status = nc_put_att_text(nCdfId, nVarId, osAttrName,  | 
7742  | 493  |                                              osVal.size(), osVal.c_str());  | 
7743  | 493  |                 }  | 
7744  | 3.90k  |                 else  | 
7745  | 3.90k  |                 { | 
7746  | 3.90k  |                     CPLString osVal(pszValue);  | 
7747  | 3.94k  |                     while (!osVal.empty())  | 
7748  | 3.85k  |                     { | 
7749  | 3.85k  |                         if (osVal.back() == ';' || osVal.back() == ' ')  | 
7750  | 38  |                         { | 
7751  | 38  |                             osVal.pop_back();  | 
7752  | 38  |                         }  | 
7753  | 3.81k  |                         else  | 
7754  | 3.81k  |                         { | 
7755  | 3.81k  |                             break;  | 
7756  | 3.81k  |                         }  | 
7757  | 3.85k  |                     }  | 
7758  | 3.90k  |                     int nc_datatype = -1;  | 
7759  | 3.90k  |                     if (!osVal.empty() && osVal.back() == 'b')  | 
7760  | 2  |                     { | 
7761  | 2  |                         nc_datatype = NC_BYTE;  | 
7762  | 2  |                         osVal.pop_back();  | 
7763  | 2  |                     }  | 
7764  | 3.90k  |                     else if (!osVal.empty() && osVal.back() == 's')  | 
7765  | 0  |                     { | 
7766  | 0  |                         nc_datatype = NC_SHORT;  | 
7767  | 0  |                         osVal.pop_back();  | 
7768  | 0  |                     }  | 
7769  | 3.90k  |                     if (CPLGetValueType(osVal) == CPL_VALUE_INTEGER)  | 
7770  | 1.72k  |                     { | 
7771  | 1.72k  |                         if (nc_datatype < 0)  | 
7772  | 1.72k  |                             nc_datatype = NC_INT;  | 
7773  | 1.72k  |                     }  | 
7774  | 2.17k  |                     else if (CPLGetValueType(osVal) == CPL_VALUE_REAL)  | 
7775  | 141  |                     { | 
7776  | 141  |                         nc_datatype = NC_DOUBLE;  | 
7777  | 141  |                     }  | 
7778  | 2.03k  |                     else  | 
7779  | 2.03k  |                     { | 
7780  | 2.03k  |                         nc_datatype = -1;  | 
7781  | 2.03k  |                     }  | 
7782  |  |  | 
7783  |  |                     // For _FillValue, check that the attribute type matches  | 
7784  |  |                     // the variable type. Leaks memory with NC4 otherwise  | 
7785  | 3.90k  |                     if (osAttrName == "_FillValue")  | 
7786  | 0  |                     { | 
7787  | 0  |                         if (nVarId < 0 ||  | 
7788  | 0  |                             nc_datatype != oMapVarIdToType[nVarId])  | 
7789  | 0  |                         { | 
7790  | 0  |                             nc_datatype = -1;  | 
7791  | 0  |                         }  | 
7792  | 0  |                     }  | 
7793  |  |  | 
7794  | 3.90k  |                     if (nc_datatype == NC_BYTE)  | 
7795  | 1  |                     { | 
7796  | 1  |                         signed char chVal =  | 
7797  | 1  |                             static_cast<signed char>(atoi(osVal));  | 
7798  | 1  |                         status = nc_put_att_schar(nCdfId, nVarId, osAttrName,  | 
7799  | 1  |                                                   NC_BYTE, 1, &chVal);  | 
7800  | 1  |                     }  | 
7801  | 3.90k  |                     else if (nc_datatype == NC_SHORT)  | 
7802  | 0  |                     { | 
7803  | 0  |                         short nVal = static_cast<short>(atoi(osVal));  | 
7804  | 0  |                         status = nc_put_att_short(nCdfId, nVarId, osAttrName,  | 
7805  | 0  |                                                   NC_SHORT, 1, &nVal);  | 
7806  | 0  |                     }  | 
7807  | 3.90k  |                     else if (nc_datatype == NC_INT)  | 
7808  | 1.72k  |                     { | 
7809  | 1.72k  |                         int nVal = static_cast<int>(atoi(osVal));  | 
7810  | 1.72k  |                         status = nc_put_att_int(nCdfId, nVarId, osAttrName,  | 
7811  | 1.72k  |                                                 NC_INT, 1, &nVal);  | 
7812  | 1.72k  |                     }  | 
7813  | 2.17k  |                     else if (nc_datatype == NC_DOUBLE)  | 
7814  | 141  |                     { | 
7815  | 141  |                         double dfVal = CPLAtof(osVal);  | 
7816  | 141  |                         status = nc_put_att_double(nCdfId, nVarId, osAttrName,  | 
7817  | 141  |                                                    NC_DOUBLE, 1, &dfVal);  | 
7818  | 141  |                     }  | 
7819  | 3.90k  |                 }  | 
7820  | 4.39k  |                 if (status != NC_NOERR)  | 
7821  | 2.10k  |                 { | 
7822  | 2.10k  |                     CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s", | 
7823  | 2.10k  |                              osVarName.c_str(), osAttrName.c_str(),  | 
7824  | 2.10k  |                              nc_strerror(status));  | 
7825  | 2.10k  |                 }  | 
7826  | 2.29k  |                 else  | 
7827  | 2.29k  |                 { | 
7828  | 2.29k  |                     oSetAttrDefined.insert(osVarName + ":" + osAttrName);  | 
7829  |  | #ifdef DEBUG_VERBOSE  | 
7830  |  |                     CPLDebug("netCDF", "nc_put_att_(%s:%s) (%s) succeeded", | 
7831  |  |                              osVarName.c_str(), osAttrName.c_str(), pszLine);  | 
7832  |  | #endif  | 
7833  | 2.29k  |                 }  | 
7834  | 4.39k  |             }  | 
7835  | 377k  |         }  | 
7836  | 182k  |         else if (nActiveSection == SECTION_DATA)  | 
7837  | 181k  |         { | 
7838  | 301k  |             while (*pszLine == ' ' || *pszLine == '\t')  | 
7839  | 120k  |                 pszLine++;  | 
7840  | 181k  |             const char *pszEqual = strchr(pszLine, '=');  | 
7841  | 181k  |             if (pszEqual)  | 
7842  | 41.6k  |             { | 
7843  | 41.6k  |                 CPLString osVarName(pszLine, pszEqual - pszLine);  | 
7844  | 41.6k  |                 osVarName.Trim();  | 
7845  | 41.6k  |                 if (oMapVarToId.find(osVarName) == oMapVarToId.end())  | 
7846  | 39.8k  |                     continue;  | 
7847  | 1.73k  |                 const int nVarId = oMapVarToId[osVarName];  | 
7848  | 1.73k  |                 CPLString osAccVal(pszEqual + 1);  | 
7849  | 1.73k  |                 osAccVal.Trim();  | 
7850  | 26.4k  |                 while (osAccVal.empty() || osAccVal.back() != ';')  | 
7851  | 24.8k  |                 { | 
7852  | 24.8k  |                     pszLine = CPLReadLineL(fpSrc);  | 
7853  | 24.8k  |                     if (pszLine == nullptr)  | 
7854  | 143  |                         break;  | 
7855  | 24.7k  |                     CPLString osVal(pszLine);  | 
7856  | 24.7k  |                     osVal.Trim();  | 
7857  | 24.7k  |                     osAccVal += osVal;  | 
7858  | 24.7k  |                 }  | 
7859  | 1.73k  |                 if (pszLine == nullptr)  | 
7860  | 143  |                     break;  | 
7861  | 1.58k  |                 osAccVal.pop_back();  | 
7862  |  |  | 
7863  | 1.58k  |                 const std::vector<int> aoDimIds =  | 
7864  | 1.58k  |                     oMapVarIdToVectorOfDimId[nVarId];  | 
7865  | 1.58k  |                 size_t nSize = 1;  | 
7866  | 1.58k  |                 std::vector<size_t> aoStart, aoEdge;  | 
7867  | 1.58k  |                 aoStart.resize(aoDimIds.size());  | 
7868  | 1.58k  |                 aoEdge.resize(aoDimIds.size());  | 
7869  | 4.26k  |                 for (size_t i = 0; i < aoDimIds.size(); ++i)  | 
7870  | 2.67k  |                 { | 
7871  | 2.67k  |                     const size_t nDimSize = oMapDimIdToDimLen[aoDimIds[i]];  | 
7872  | 2.67k  |                     if (nDimSize != 0 &&  | 
7873  | 2.67k  |                         nSize > std::numeric_limits<size_t>::max() / nDimSize)  | 
7874  | 0  |                     { | 
7875  | 0  |                         nSize = 0;  | 
7876  | 0  |                     }  | 
7877  | 2.67k  |                     else  | 
7878  | 2.67k  |                     { | 
7879  | 2.67k  |                         nSize *= nDimSize;  | 
7880  | 2.67k  |                     }  | 
7881  | 2.67k  |                     aoStart[i] = 0;  | 
7882  | 2.67k  |                     aoEdge[i] = nDimSize;  | 
7883  | 2.67k  |                 }  | 
7884  |  |  | 
7885  | 1.58k  |                 status = NC_EBADTYPE;  | 
7886  | 1.58k  |                 if (nSize == 0)  | 
7887  | 0  |                 { | 
7888  |  |                     // Might happen with a unlimited dimension  | 
7889  | 0  |                 }  | 
7890  | 1.58k  |                 else if (oMapVarIdToType[nVarId] == NC_DOUBLE)  | 
7891  | 0  |                 { | 
7892  | 0  |                     if (!aoStart.empty())  | 
7893  | 0  |                     { | 
7894  | 0  |                         char **papszTokens =  | 
7895  | 0  |                             CSLTokenizeString2(osAccVal, " ,;", 0);  | 
7896  | 0  |                         size_t nTokens = CSLCount(papszTokens);  | 
7897  | 0  |                         if (nTokens >= nSize)  | 
7898  | 0  |                         { | 
7899  | 0  |                             double *padfVals = static_cast<double *>(  | 
7900  | 0  |                                 VSI_CALLOC_VERBOSE(nSize, sizeof(double)));  | 
7901  | 0  |                             if (padfVals)  | 
7902  | 0  |                             { | 
7903  | 0  |                                 for (size_t i = 0; i < nSize; i++)  | 
7904  | 0  |                                 { | 
7905  | 0  |                                     padfVals[i] = CPLAtof(papszTokens[i]);  | 
7906  | 0  |                                 }  | 
7907  | 0  |                                 status = nc_put_vara_double(  | 
7908  | 0  |                                     nCdfId, nVarId, &aoStart[0], &aoEdge[0],  | 
7909  | 0  |                                     padfVals);  | 
7910  | 0  |                                 VSIFree(padfVals);  | 
7911  | 0  |                             }  | 
7912  | 0  |                         }  | 
7913  | 0  |                         CSLDestroy(papszTokens);  | 
7914  | 0  |                     }  | 
7915  | 0  |                 }  | 
7916  | 1.58k  |                 else if (oMapVarIdToType[nVarId] == NC_BYTE)  | 
7917  | 304  |                 { | 
7918  | 304  |                     if (!aoStart.empty())  | 
7919  | 301  |                     { | 
7920  | 301  |                         char **papszTokens =  | 
7921  | 301  |                             CSLTokenizeString2(osAccVal, " ,;", 0);  | 
7922  | 301  |                         size_t nTokens = CSLCount(papszTokens);  | 
7923  | 301  |                         if (nTokens >= nSize)  | 
7924  | 1  |                         { | 
7925  | 1  |                             signed char *panVals = static_cast<signed char *>(  | 
7926  | 1  |                                 VSI_CALLOC_VERBOSE(nSize, sizeof(signed char)));  | 
7927  | 1  |                             if (panVals)  | 
7928  | 1  |                             { | 
7929  | 1.00k  |                                 for (size_t i = 0; i < nSize; i++)  | 
7930  | 1.00k  |                                 { | 
7931  | 1.00k  |                                     panVals[i] = static_cast<signed char>(  | 
7932  | 1.00k  |                                         atoi(papszTokens[i]));  | 
7933  | 1.00k  |                                 }  | 
7934  | 1  |                                 status = nc_put_vara_schar(nCdfId, nVarId,  | 
7935  | 1  |                                                            &aoStart[0],  | 
7936  | 1  |                                                            &aoEdge[0], panVals);  | 
7937  | 1  |                                 VSIFree(panVals);  | 
7938  | 1  |                             }  | 
7939  | 1  |                         }  | 
7940  | 301  |                         CSLDestroy(papszTokens);  | 
7941  | 301  |                     }  | 
7942  | 304  |                 }  | 
7943  | 1.28k  |                 else if (oMapVarIdToType[nVarId] == NC_CHAR)  | 
7944  | 1.06k  |                 { | 
7945  | 1.06k  |                     if (aoStart.size() == 2)  | 
7946  | 1.06k  |                     { | 
7947  | 1.06k  |                         std::vector<CPLString> aoStrings;  | 
7948  | 1.06k  |                         bool bInString = false;  | 
7949  | 1.06k  |                         CPLString osCurString;  | 
7950  | 217k  |                         for (size_t i = 0; i < osAccVal.size();)  | 
7951  | 216k  |                         { | 
7952  | 216k  |                             if (!bInString)  | 
7953  | 78.3k  |                             { | 
7954  | 78.3k  |                                 if (osAccVal[i] == '"')  | 
7955  | 12.7k  |                                 { | 
7956  | 12.7k  |                                     bInString = true;  | 
7957  | 12.7k  |                                     osCurString.clear();  | 
7958  | 12.7k  |                                 }  | 
7959  | 78.3k  |                                 i++;  | 
7960  | 78.3k  |                             }  | 
7961  | 138k  |                             else if (osAccVal[i] == '\\' &&  | 
7962  | 138k  |                                      i + 1 < osAccVal.size() &&  | 
7963  | 138k  |                                      osAccVal[i + 1] == '"')  | 
7964  | 1.07k  |                             { | 
7965  | 1.07k  |                                 osCurString += '"';  | 
7966  | 1.07k  |                                 i += 2;  | 
7967  | 1.07k  |                             }  | 
7968  | 137k  |                             else if (osAccVal[i] == '"')  | 
7969  | 12.0k  |                             { | 
7970  | 12.0k  |                                 aoStrings.push_back(osCurString);  | 
7971  | 12.0k  |                                 osCurString.clear();  | 
7972  | 12.0k  |                                 bInString = false;  | 
7973  | 12.0k  |                                 i++;  | 
7974  | 12.0k  |                             }  | 
7975  | 125k  |                             else  | 
7976  | 125k  |                             { | 
7977  | 125k  |                                 osCurString += osAccVal[i];  | 
7978  | 125k  |                                 i++;  | 
7979  | 125k  |                             }  | 
7980  | 216k  |                         }  | 
7981  | 1.06k  |                         const size_t nRecords = oMapDimIdToDimLen[aoDimIds[0]];  | 
7982  | 1.06k  |                         const size_t nWidth = oMapDimIdToDimLen[aoDimIds[1]];  | 
7983  | 1.06k  |                         size_t nIters = aoStrings.size();  | 
7984  | 1.06k  |                         if (nIters > nRecords)  | 
7985  | 58  |                             nIters = nRecords;  | 
7986  | 11.9k  |                         for (size_t i = 0; i < nIters; i++)  | 
7987  | 10.9k  |                         { | 
7988  | 10.9k  |                             size_t anIndex[2];  | 
7989  | 10.9k  |                             anIndex[0] = i;  | 
7990  | 10.9k  |                             anIndex[1] = 0;  | 
7991  | 10.9k  |                             size_t anCount[2];  | 
7992  | 10.9k  |                             anCount[0] = 1;  | 
7993  | 10.9k  |                             anCount[1] = aoStrings[i].size();  | 
7994  | 10.9k  |                             if (anCount[1] > nWidth)  | 
7995  | 21  |                                 anCount[1] = nWidth;  | 
7996  | 10.9k  |                             status =  | 
7997  | 10.9k  |                                 nc_put_vara_text(nCdfId, nVarId, anIndex,  | 
7998  | 10.9k  |                                                  anCount, aoStrings[i].c_str());  | 
7999  | 10.9k  |                             if (status != NC_NOERR)  | 
8000  | 0  |                                 break;  | 
8001  | 10.9k  |                         }  | 
8002  | 1.06k  |                     }  | 
8003  | 1.06k  |                 }  | 
8004  | 1.58k  |                 if (status != NC_NOERR)  | 
8005  | 527  |                 { | 
8006  | 527  |                     CPLDebug("netCDF", "nc_put_var_(%s) failed: %s", | 
8007  | 527  |                              osVarName.c_str(), nc_strerror(status));  | 
8008  | 527  |                 }  | 
8009  | 1.58k  |             }  | 
8010  | 181k  |         }  | 
8011  | 664k  |     }  | 
8012  |  |  | 
8013  | 399  |     GDAL_nc_close(nCdfId);  | 
8014  | 399  |     return true;  | 
8015  | 399  | }  | 
8016  |  |  | 
8017  |  | #endif  // ENABLE_NCDUMP  | 
8018  |  |  | 
8019  |  | /************************************************************************/  | 
8020  |  | /*                                Open()                                */  | 
8021  |  | /************************************************************************/  | 
8022  |  |  | 
8023  |  | GDALDataset *netCDFDataset::Open(GDALOpenInfo *poOpenInfo)  | 
8024  |  |  | 
8025  | 399  | { | 
8026  |  | #ifdef NCDF_DEBUG  | 
8027  |  |     CPLDebug("GDAL_netCDF", "\n=====\nOpen(), filename=[%s]", | 
8028  |  |              poOpenInfo->pszFilename);  | 
8029  |  | #endif  | 
8030  |  |  | 
8031  |  |     // Does this appear to be a netcdf file?  | 
8032  | 399  |     NetCDFFormatEnum eTmpFormat = NCDF_FORMAT_NONE;  | 
8033  | 399  |     if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))  | 
8034  | 399  |     { | 
8035  | 399  |         eTmpFormat = netCDFIdentifyFormat(poOpenInfo, /* bCheckExt = */ true);  | 
8036  |  | #ifdef NCDF_DEBUG  | 
8037  |  |         CPLDebug("GDAL_netCDF", "identified format %d", eTmpFormat); | 
8038  |  | #endif  | 
8039  |  |         // Note: not calling Identify() directly, because we want the file type.  | 
8040  |  |         // Only support NCDF_FORMAT* formats.  | 
8041  | 399  |         if (NCDF_FORMAT_NC == eTmpFormat || NCDF_FORMAT_NC2 == eTmpFormat ||  | 
8042  | 399  |             NCDF_FORMAT_NC4 == eTmpFormat || NCDF_FORMAT_NC4C == eTmpFormat)  | 
8043  | 399  |         { | 
8044  |  |             // ok  | 
8045  | 399  |         }  | 
8046  | 0  |         else if (eTmpFormat == NCDF_FORMAT_HDF4 &&  | 
8047  | 0  |                  poOpenInfo->IsSingleAllowedDriver("netCDF")) | 
8048  | 0  |         { | 
8049  |  |             // ok  | 
8050  | 0  |         }  | 
8051  | 0  |         else  | 
8052  | 0  |         { | 
8053  | 0  |             return nullptr;  | 
8054  | 0  |         }  | 
8055  | 399  |     }  | 
8056  | 0  |     else  | 
8057  | 0  |     { | 
8058  | 0  | #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION  | 
8059  |  |         // We don't necessarily want to catch bugs in libnetcdf ...  | 
8060  | 0  |         if (CPLGetConfigOption("DISABLE_OPEN_REAL_NETCDF_FILES", nullptr)) | 
8061  | 0  |         { | 
8062  | 0  |             return nullptr;  | 
8063  | 0  |         }  | 
8064  | 0  | #endif  | 
8065  | 0  |     }  | 
8066  |  |  | 
8067  | 399  |     if (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER)  | 
8068  | 0  |     { | 
8069  | 0  |         return OpenMultiDim(poOpenInfo);  | 
8070  | 0  |     }  | 
8071  |  |  | 
8072  | 399  |     CPLMutexHolderD(&hNCMutex);  | 
8073  |  |  | 
8074  | 399  |     CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock with  | 
8075  |  |         // GDALDataset own mutex.  | 
8076  | 399  |     netCDFDataset *poDS = new netCDFDataset();  | 
8077  | 399  |     poDS->papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);  | 
8078  | 399  |     CPLAcquireMutex(hNCMutex, 1000.0);  | 
8079  |  |  | 
8080  | 399  |     poDS->SetDescription(poOpenInfo->pszFilename);  | 
8081  |  |  | 
8082  |  |     // Check if filename start with NETCDF: tag.  | 
8083  | 399  |     bool bTreatAsSubdataset = false;  | 
8084  | 399  |     CPLString osSubdatasetName;  | 
8085  |  |  | 
8086  | 399  | #ifdef ENABLE_NCDUMP  | 
8087  | 399  |     const char *pszHeader =  | 
8088  | 399  |         reinterpret_cast<const char *>(poOpenInfo->pabyHeader);  | 
8089  | 399  |     if (poOpenInfo->fpL != nullptr && STARTS_WITH(pszHeader, "netcdf ") &&  | 
8090  | 399  |         strstr(pszHeader, "dimensions:") && strstr(pszHeader, "variables:"))  | 
8091  | 399  |     { | 
8092  |  |         // By default create a temporary file that will be destroyed,  | 
8093  |  |         // unless NETCDF_TMP_FILE is defined. Can be useful to see which  | 
8094  |  |         // netCDF file has been generated from a potential fuzzed input.  | 
8095  | 399  |         poDS->osFilename = CPLGetConfigOption("NETCDF_TMP_FILE", ""); | 
8096  | 399  |         if (poDS->osFilename.empty())  | 
8097  | 399  |         { | 
8098  | 399  |             poDS->bFileToDestroyAtClosing = true;  | 
8099  | 399  |             poDS->osFilename = CPLGenerateTempFilenameSafe("netcdf_tmp"); | 
8100  | 399  |         }  | 
8101  | 399  |         if (!netCDFDatasetCreateTempFile(eTmpFormat, poDS->osFilename,  | 
8102  | 399  |                                          poOpenInfo->fpL))  | 
8103  | 0  |         { | 
8104  | 0  |             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll  | 
8105  |  |                 // deadlock with GDALDataset own mutex.  | 
8106  | 0  |             delete poDS;  | 
8107  | 0  |             CPLAcquireMutex(hNCMutex, 1000.0);  | 
8108  | 0  |             return nullptr;  | 
8109  | 0  |         }  | 
8110  | 399  |         bTreatAsSubdataset = false;  | 
8111  | 399  |         poDS->eFormat = eTmpFormat;  | 
8112  | 399  |     }  | 
8113  | 0  |     else  | 
8114  | 0  | #endif  | 
8115  |  |  | 
8116  | 0  |         if (STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))  | 
8117  | 0  |     { | 
8118  | 0  |         char **papszName =  | 
8119  | 0  |             CSLTokenizeString2(poOpenInfo->pszFilename, ":",  | 
8120  | 0  |                                CSLT_HONOURSTRINGS | CSLT_PRESERVEESCAPES);  | 
8121  |  | 
  | 
8122  | 0  |         if (CSLCount(papszName) >= 3 &&  | 
8123  | 0  |             ((strlen(papszName[1]) == 1 && /* D:\\bla */  | 
8124  | 0  |               (papszName[2][0] == '/' || papszName[2][0] == '\\')) ||  | 
8125  | 0  |              EQUAL(papszName[1], "http") || EQUAL(papszName[1], "https") ||  | 
8126  | 0  |              EQUAL(papszName[1], "/vsicurl/http") ||  | 
8127  | 0  |              EQUAL(papszName[1], "/vsicurl/https") ||  | 
8128  | 0  |              EQUAL(papszName[1], "/vsicurl_streaming/http") ||  | 
8129  | 0  |              EQUAL(papszName[1], "/vsicurl_streaming/https")))  | 
8130  | 0  |         { | 
8131  | 0  |             const int nCountBefore = CSLCount(papszName);  | 
8132  | 0  |             CPLString osTmp = papszName[1];  | 
8133  | 0  |             osTmp += ':';  | 
8134  | 0  |             osTmp += papszName[2];  | 
8135  | 0  |             CPLFree(papszName[1]);  | 
8136  | 0  |             CPLFree(papszName[2]);  | 
8137  | 0  |             papszName[1] = CPLStrdup(osTmp);  | 
8138  | 0  |             memmove(papszName + 2, papszName + 3,  | 
8139  | 0  |                     (nCountBefore - 2) * sizeof(char *));  | 
8140  | 0  |         }  | 
8141  |  | 
  | 
8142  | 0  |         if (CSLCount(papszName) == 3)  | 
8143  | 0  |         { | 
8144  | 0  |             poDS->osFilename = papszName[1];  | 
8145  | 0  |             osSubdatasetName = papszName[2];  | 
8146  | 0  |             bTreatAsSubdataset = true;  | 
8147  | 0  |             CSLDestroy(papszName);  | 
8148  | 0  |         }  | 
8149  | 0  |         else if (CSLCount(papszName) == 2)  | 
8150  | 0  |         { | 
8151  | 0  |             poDS->osFilename = papszName[1];  | 
8152  | 0  |             osSubdatasetName = "";  | 
8153  | 0  |             bTreatAsSubdataset = false;  | 
8154  | 0  |             CSLDestroy(papszName);  | 
8155  | 0  |         }  | 
8156  | 0  |         else  | 
8157  | 0  |         { | 
8158  | 0  |             CSLDestroy(papszName);  | 
8159  | 0  |             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll  | 
8160  |  |                 // deadlock with GDALDataset own mutex.  | 
8161  | 0  |             delete poDS;  | 
8162  | 0  |             CPLAcquireMutex(hNCMutex, 1000.0);  | 
8163  | 0  |             CPLError(CE_Failure, CPLE_AppDefined,  | 
8164  | 0  |                      "Failed to parse NETCDF: prefix string into expected 2, 3 "  | 
8165  | 0  |                      "or 4 fields.");  | 
8166  | 0  |             return nullptr;  | 
8167  | 0  |         }  | 
8168  |  |  | 
8169  | 0  |         if (!STARTS_WITH(poDS->osFilename, "http://") &&  | 
8170  | 0  |             !STARTS_WITH(poDS->osFilename, "https://"))  | 
8171  | 0  |         { | 
8172  |  |             // Identify Format from real file, with bCheckExt=FALSE.  | 
8173  | 0  |             GDALOpenInfo *poOpenInfo2 =  | 
8174  | 0  |                 new GDALOpenInfo(poDS->osFilename.c_str(), GA_ReadOnly);  | 
8175  | 0  |             poDS->eFormat =  | 
8176  | 0  |                 netCDFIdentifyFormat(poOpenInfo2, /* bCheckExt = */ false);  | 
8177  | 0  |             delete poOpenInfo2;  | 
8178  | 0  |             if (NCDF_FORMAT_NONE == poDS->eFormat ||  | 
8179  | 0  |                 NCDF_FORMAT_UNKNOWN == poDS->eFormat)  | 
8180  | 0  |             { | 
8181  | 0  |                 CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll  | 
8182  |  |                     // deadlock with GDALDataset own mutex.  | 
8183  | 0  |                 delete poDS;  | 
8184  | 0  |                 CPLAcquireMutex(hNCMutex, 1000.0);  | 
8185  | 0  |                 return nullptr;  | 
8186  | 0  |             }  | 
8187  | 0  |         }  | 
8188  | 0  |     }  | 
8189  | 0  |     else  | 
8190  | 0  |     { | 
8191  | 0  |         poDS->osFilename = poOpenInfo->pszFilename;  | 
8192  | 0  |         bTreatAsSubdataset = false;  | 
8193  | 0  |         poDS->eFormat = eTmpFormat;  | 
8194  | 0  |     }  | 
8195  |  |  | 
8196  |  | // Try opening the dataset.  | 
8197  |  | #if defined(NCDF_DEBUG) && defined(ENABLE_UFFD)  | 
8198  |  |     CPLDebug("GDAL_netCDF", "calling nc_open_mem(%s)", | 
8199  |  |              poDS->osFilename.c_str());  | 
8200  |  | #elif defined(NCDF_DEBUG) && !defined(ENABLE_UFFD)  | 
8201  |  |     CPLDebug("GDAL_netCDF", "calling nc_open(%s)", poDS->osFilename.c_str()); | 
8202  |  | #endif  | 
8203  | 399  |     int cdfid = -1;  | 
8204  | 399  |     const int nMode = ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE) != 0)  | 
8205  | 399  |                           ? NC_WRITE  | 
8206  | 399  |                           : NC_NOWRITE;  | 
8207  | 399  |     CPLString osFilenameForNCOpen(poDS->osFilename);  | 
8208  |  | #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)  | 
8209  |  |     if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES"))) | 
8210  |  |     { | 
8211  |  |         char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");  | 
8212  |  |         osFilenameForNCOpen = pszTemp;  | 
8213  |  |         CPLFree(pszTemp);  | 
8214  |  |     }  | 
8215  |  | #endif  | 
8216  | 399  |     int status2 = -1;  | 
8217  |  |  | 
8218  | 399  | #ifdef ENABLE_UFFD  | 
8219  | 399  |     cpl_uffd_context *pCtx = nullptr;  | 
8220  | 399  | #endif  | 
8221  |  |  | 
8222  | 399  |     if (STARTS_WITH(osFilenameForNCOpen, "/vsimem/") &&  | 
8223  | 399  |         poOpenInfo->eAccess == GA_ReadOnly)  | 
8224  | 0  |     { | 
8225  | 0  |         vsi_l_offset nLength = 0;  | 
8226  | 0  |         poDS->fpVSIMEM = VSIFOpenL(osFilenameForNCOpen, "rb");  | 
8227  | 0  |         if (poDS->fpVSIMEM)  | 
8228  | 0  |         { | 
8229  |  |             // We assume that the file will not be modified. If it is, then  | 
8230  |  |             // pabyBuffer might become invalid.  | 
8231  | 0  |             GByte *pabyBuffer =  | 
8232  | 0  |                 VSIGetMemFileBuffer(osFilenameForNCOpen, &nLength, false);  | 
8233  | 0  |             if (pabyBuffer)  | 
8234  | 0  |             { | 
8235  | 0  |                 status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen),  | 
8236  | 0  |                                       nMode, static_cast<size_t>(nLength),  | 
8237  | 0  |                                       pabyBuffer, &cdfid);  | 
8238  | 0  |             }  | 
8239  | 0  |         }  | 
8240  | 0  |     }  | 
8241  | 399  |     else  | 
8242  | 399  |     { | 
8243  | 399  |         const bool bVsiFile =  | 
8244  | 399  |             !strncmp(osFilenameForNCOpen, "/vsi", strlen("/vsi")); | 
8245  | 399  | #ifdef ENABLE_UFFD  | 
8246  | 399  |         bool bReadOnly = (poOpenInfo->eAccess == GA_ReadOnly);  | 
8247  | 399  |         void *pVma = nullptr;  | 
8248  | 399  |         uint64_t nVmaSize = 0;  | 
8249  |  |  | 
8250  | 399  |         if (bVsiFile)  | 
8251  | 0  |         { | 
8252  | 0  |             if (bReadOnly)  | 
8253  | 0  |             { | 
8254  | 0  |                 if (CPLIsUserFaultMappingSupported())  | 
8255  | 0  |                 { | 
8256  | 0  |                     pCtx = CPLCreateUserFaultMapping(osFilenameForNCOpen, &pVma,  | 
8257  | 0  |                                                      &nVmaSize);  | 
8258  | 0  |                 }  | 
8259  | 0  |                 else  | 
8260  | 0  |                 { | 
8261  | 0  |                     CPLError(CE_Failure, CPLE_AppDefined,  | 
8262  | 0  |                              "Opening a /vsi file with the netCDF driver "  | 
8263  | 0  |                              "requires Linux userfaultfd to be available. "  | 
8264  | 0  |                              "If running from Docker, "  | 
8265  | 0  |                              "--security-opt seccomp=unconfined might be "  | 
8266  | 0  |                              "needed.%s",  | 
8267  | 0  |                              ((poDS->eFormat == NCDF_FORMAT_NC4 ||  | 
8268  | 0  |                                poDS->eFormat == NCDF_FORMAT_HDF5) &&  | 
8269  | 0  |                               GDALGetDriverByName("HDF5")) | 
8270  | 0  |                                  ? " Or you may set the GDAL_SKIP=netCDF "  | 
8271  | 0  |                                    "configuration option to force the use of "  | 
8272  | 0  |                                    "the HDF5 driver."  | 
8273  | 0  |                                  : "");  | 
8274  | 0  |                 }  | 
8275  | 0  |             }  | 
8276  | 0  |             else  | 
8277  | 0  |             { | 
8278  | 0  |                 CPLError(CE_Failure, CPLE_AppDefined,  | 
8279  | 0  |                          "Opening a /vsi file with the netCDF driver is only "  | 
8280  | 0  |                          "supported in read-only mode");  | 
8281  | 0  |             }  | 
8282  | 0  |         }  | 
8283  | 399  |         if (pCtx != nullptr && pVma != nullptr && nVmaSize > 0)  | 
8284  | 0  |         { | 
8285  |  |             // netCDF code, at least for netCDF 4.7.0, is confused by filenames  | 
8286  |  |             // like /vsicurl/http[s]://example.com/foo.nc, so just pass the  | 
8287  |  |             // final part  | 
8288  | 0  |             status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen), nMode,  | 
8289  | 0  |                                   static_cast<size_t>(nVmaSize), pVma, &cdfid);  | 
8290  | 0  |         }  | 
8291  | 399  |         else  | 
8292  | 399  |             status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);  | 
8293  |  | #else  | 
8294  |  |         if (bVsiFile)  | 
8295  |  |         { | 
8296  |  |             CPLError(  | 
8297  |  |                 CE_Failure, CPLE_AppDefined,  | 
8298  |  |                 "Opening a /vsi file with the netCDF driver requires Linux "  | 
8299  |  |                 "userfaultfd to be available.%s",  | 
8300  |  |                 ((poDS->eFormat == NCDF_FORMAT_NC4 ||  | 
8301  |  |                   poDS->eFormat == NCDF_FORMAT_HDF5) &&  | 
8302  |  |                  GDALGetDriverByName("HDF5")) | 
8303  |  |                     ? " Or you may set the GDAL_SKIP=netCDF "  | 
8304  |  |                       "configuration option to force the use of the HDF5 "  | 
8305  |  |                       "driver."  | 
8306  |  |                     : "");  | 
8307  |  |             status2 = NC_EIO;  | 
8308  |  |         }  | 
8309  |  |         else  | 
8310  |  |         { | 
8311  |  |             status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);  | 
8312  |  |         }  | 
8313  |  | #endif  | 
8314  | 399  |     }  | 
8315  | 399  |     if (status2 != NC_NOERR)  | 
8316  | 0  |     { | 
8317  |  | #ifdef NCDF_DEBUG  | 
8318  |  |         CPLDebug("GDAL_netCDF", "error opening"); | 
8319  |  | #endif  | 
8320  | 0  |         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock  | 
8321  |  |             // with GDALDataset own mutex.  | 
8322  | 0  |         delete poDS;  | 
8323  | 0  |         CPLAcquireMutex(hNCMutex, 1000.0);  | 
8324  | 0  |         return nullptr;  | 
8325  | 0  |     }  | 
8326  |  | #ifdef NCDF_DEBUG  | 
8327  |  |     CPLDebug("GDAL_netCDF", "got cdfid=%d", cdfid); | 
8328  |  | #endif  | 
8329  |  |  | 
8330  | 399  | #if defined(ENABLE_NCDUMP) && !defined(_WIN32)  | 
8331  |  |     // Try to destroy the temporary file right now on Unix  | 
8332  | 399  |     if (poDS->bFileToDestroyAtClosing)  | 
8333  | 399  |     { | 
8334  | 399  |         if (VSIUnlink(poDS->osFilename) == 0)  | 
8335  | 399  |         { | 
8336  | 399  |             poDS->bFileToDestroyAtClosing = false;  | 
8337  | 399  |         }  | 
8338  | 399  |     }  | 
8339  | 399  | #endif  | 
8340  |  |  | 
8341  |  |     // Is this a real netCDF file?  | 
8342  | 399  |     int ndims;  | 
8343  | 399  |     int ngatts;  | 
8344  | 399  |     int nvars;  | 
8345  | 399  |     int unlimdimid;  | 
8346  | 399  |     int status = nc_inq(cdfid, &ndims, &nvars, &ngatts, &unlimdimid);  | 
8347  | 399  |     if (status != NC_NOERR)  | 
8348  | 0  |     { | 
8349  | 0  |         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock  | 
8350  |  |             // with GDALDataset own mutex.  | 
8351  | 0  |         delete poDS;  | 
8352  | 0  |         CPLAcquireMutex(hNCMutex, 1000.0);  | 
8353  | 0  |         return nullptr;  | 
8354  | 0  |     }  | 
8355  |  |  | 
8356  |  |     // Get file type from netcdf.  | 
8357  | 399  |     int nTmpFormat = NCDF_FORMAT_NONE;  | 
8358  | 399  |     status = nc_inq_format(cdfid, &nTmpFormat);  | 
8359  | 399  |     if (status != NC_NOERR)  | 
8360  | 0  |     { | 
8361  | 0  |         NCDF_ERR(status);  | 
8362  | 0  |     }  | 
8363  | 399  |     else  | 
8364  | 399  |     { | 
8365  | 399  |         CPLDebug("GDAL_netCDF", | 
8366  | 399  |                  "driver detected file type=%d, libnetcdf detected type=%d",  | 
8367  | 399  |                  poDS->eFormat, nTmpFormat);  | 
8368  | 399  |         if (static_cast<NetCDFFormatEnum>(nTmpFormat) != poDS->eFormat)  | 
8369  | 0  |         { | 
8370  |  |             // Warn if file detection conflicts with that from libnetcdf  | 
8371  |  |             // except for NC4C, which we have no way of detecting initially.  | 
8372  | 0  |             if (nTmpFormat != NCDF_FORMAT_NC4C &&  | 
8373  | 0  |                 !STARTS_WITH(poDS->osFilename, "http://") &&  | 
8374  | 0  |                 !STARTS_WITH(poDS->osFilename, "https://"))  | 
8375  | 0  |             { | 
8376  | 0  |                 CPLError(CE_Warning, CPLE_AppDefined,  | 
8377  | 0  |                          "NetCDF driver detected file type=%d, but libnetcdf "  | 
8378  | 0  |                          "detected type=%d",  | 
8379  | 0  |                          poDS->eFormat, nTmpFormat);  | 
8380  | 0  |             }  | 
8381  | 0  |             CPLDebug("GDAL_netCDF", "setting file type to %d, was %d", | 
8382  | 0  |                      nTmpFormat, poDS->eFormat);  | 
8383  | 0  |             poDS->eFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);  | 
8384  | 0  |         }  | 
8385  | 399  |     }  | 
8386  |  |  | 
8387  |  |     // Does the request variable exist?  | 
8388  | 399  |     if (bTreatAsSubdataset)  | 
8389  | 0  |     { | 
8390  | 0  |         int dummy;  | 
8391  | 0  |         if (NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &dummy,  | 
8392  | 0  |                                &dummy) != CE_None)  | 
8393  | 0  |         { | 
8394  | 0  |             CPLError(CE_Warning, CPLE_AppDefined,  | 
8395  | 0  |                      "%s is a netCDF file, but %s is not a variable.",  | 
8396  | 0  |                      poOpenInfo->pszFilename, osSubdatasetName.c_str());  | 
8397  |  | 
  | 
8398  | 0  |             GDAL_nc_close(cdfid);  | 
8399  | 0  | #ifdef ENABLE_UFFD  | 
8400  | 0  |             NETCDF_UFFD_UNMAP(pCtx);  | 
8401  | 0  | #endif  | 
8402  | 0  |             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll  | 
8403  |  |                 // deadlock with GDALDataset own mutex.  | 
8404  | 0  |             delete poDS;  | 
8405  | 0  |             CPLAcquireMutex(hNCMutex, 1000.0);  | 
8406  | 0  |             return nullptr;  | 
8407  | 0  |         }  | 
8408  | 0  |     }  | 
8409  |  |  | 
8410  |  |     // Figure out whether or not the listed dataset has support for simple  | 
8411  |  |     // geometries (CF-1.8)  | 
8412  | 399  |     poDS->nCFVersion = nccfdriver::getCFVersion(cdfid);  | 
8413  | 399  |     bool bHasSimpleGeometries = false;  // but not necessarily valid  | 
8414  | 399  |     if (poDS->nCFVersion >= 1.8)  | 
8415  | 0  |     { | 
8416  | 0  |         bHasSimpleGeometries = poDS->DetectAndFillSGLayers(cdfid);  | 
8417  | 0  |         if (bHasSimpleGeometries)  | 
8418  | 0  |         { | 
8419  | 0  |             poDS->bSGSupport = true;  | 
8420  | 0  |             poDS->vcdf.enableFullVirtualMode();  | 
8421  | 0  |         }  | 
8422  | 0  |     }  | 
8423  |  |  | 
8424  | 399  |     char szConventions[NC_MAX_NAME + 1];  | 
8425  | 399  |     szConventions[0] = '\0';  | 
8426  | 399  |     nc_type nAttype = NC_NAT;  | 
8427  | 399  |     size_t nAttlen = 0;  | 
8428  | 399  |     nc_inq_att(cdfid, NC_GLOBAL, "Conventions", &nAttype, &nAttlen);  | 
8429  | 399  |     if (nAttlen >= sizeof(szConventions) ||  | 
8430  | 399  |         nc_get_att_text(cdfid, NC_GLOBAL, "Conventions", szConventions) !=  | 
8431  | 399  |             NC_NOERR)  | 
8432  | 364  |     { | 
8433  | 364  |         CPLDebug("GDAL_netCDF", "No UNIDATA NC_GLOBAL:Conventions attribute"); | 
8434  |  |         // Note that 'Conventions' is always capital 'C' in CF spec.  | 
8435  | 364  |     }  | 
8436  | 35  |     else  | 
8437  | 35  |     { | 
8438  | 35  |         szConventions[nAttlen] = '\0';  | 
8439  | 35  |     }  | 
8440  |  |  | 
8441  |  |     // Create band information objects.  | 
8442  | 399  |     CPLDebug("GDAL_netCDF", "var_count = %d", nvars); | 
8443  |  |  | 
8444  |  |     // Create a corresponding GDALDataset.  | 
8445  |  |     // Create Netcdf Subdataset if filename as NETCDF tag.  | 
8446  | 399  |     poDS->cdfid = cdfid;  | 
8447  | 399  | #ifdef ENABLE_UFFD  | 
8448  | 399  |     poDS->pCtx = pCtx;  | 
8449  | 399  | #endif  | 
8450  | 399  |     poDS->eAccess = poOpenInfo->eAccess;  | 
8451  | 399  |     poDS->bDefineMode = false;  | 
8452  |  |  | 
8453  | 399  |     poDS->ReadAttributes(cdfid, NC_GLOBAL);  | 
8454  |  |  | 
8455  |  |     // Identify coordinate and boundary variables that we should  | 
8456  |  |     // ignore as Raster Bands.  | 
8457  | 399  |     char **papszIgnoreVars = nullptr;  | 
8458  | 399  |     NCDFGetCoordAndBoundVarFullNames(cdfid, &papszIgnoreVars);  | 
8459  |  |     // Filter variables to keep only valid 2+D raster bands and vector fields.  | 
8460  | 399  |     int nRasterVars = 0;  | 
8461  | 399  |     int nIgnoredVars = 0;  | 
8462  | 399  |     int nGroupID = -1;  | 
8463  | 399  |     int nVarID = -1;  | 
8464  |  |  | 
8465  | 399  |     std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>  | 
8466  | 399  |         oMap2DDimsToGroupAndVar;  | 
8467  | 399  |     if ((poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&  | 
8468  | 399  |         STARTS_WITH(CSLFetchNameValueDef(poDS->papszMetadata,  | 
8469  | 399  |                                          "NC_GLOBAL#mission_name", ""),  | 
8470  | 399  |                     "Sentinel 3") &&  | 
8471  | 399  |         EQUAL(CSLFetchNameValueDef(poDS->papszMetadata,  | 
8472  | 399  |                                    "NC_GLOBAL#altimeter_sensor_name", ""),  | 
8473  | 399  |               "SRAL") &&  | 
8474  | 399  |         EQUAL(CSLFetchNameValueDef(poDS->papszMetadata,  | 
8475  | 399  |                                    "NC_GLOBAL#radiometer_sensor_name", ""),  | 
8476  | 399  |               "MWR"))  | 
8477  | 0  |     { | 
8478  | 0  |         if (poDS->eAccess == GA_Update)  | 
8479  | 0  |         { | 
8480  | 0  |             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll  | 
8481  |  |                 // deadlock with GDALDataset own mutex.  | 
8482  | 0  |             delete poDS;  | 
8483  | 0  |             return nullptr;  | 
8484  | 0  |         }  | 
8485  | 0  |         poDS->ProcessSentinel3_SRAL_MWR();  | 
8486  | 0  |     }  | 
8487  | 399  |     else  | 
8488  | 399  |     { | 
8489  | 399  |         poDS->FilterVars(cdfid, (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0,  | 
8490  | 399  |                          (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&  | 
8491  | 399  |                              !bHasSimpleGeometries,  | 
8492  | 399  |                          papszIgnoreVars, &nRasterVars, &nGroupID, &nVarID,  | 
8493  | 399  |                          &nIgnoredVars, oMap2DDimsToGroupAndVar);  | 
8494  | 399  |     }  | 
8495  | 399  |     CSLDestroy(papszIgnoreVars);  | 
8496  |  |  | 
8497  |  |     // Case where there is no raster variable  | 
8498  | 399  |     if (nRasterVars == 0 && !bTreatAsSubdataset)  | 
8499  | 399  |     { | 
8500  | 399  |         poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);  | 
8501  | 399  |         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock  | 
8502  |  |             // with GDALDataset own mutex.  | 
8503  | 399  |         poDS->TryLoadXML();  | 
8504  |  |         // If the dataset has been opened in raster mode only, exit  | 
8505  | 399  |         if ((poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0 &&  | 
8506  | 399  |             (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) == 0)  | 
8507  | 0  |         { | 
8508  | 0  |             delete poDS;  | 
8509  | 0  |             poDS = nullptr;  | 
8510  | 0  |         }  | 
8511  |  |         // Otherwise if the dataset is opened in vector mode, that there is  | 
8512  |  |         // no vector layer and we are in read-only, exit too.  | 
8513  | 399  |         else if (poDS->GetLayerCount() == 0 &&  | 
8514  | 399  |                  (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&  | 
8515  | 399  |                  poOpenInfo->eAccess == GA_ReadOnly)  | 
8516  | 376  |         { | 
8517  | 376  |             delete poDS;  | 
8518  | 376  |             poDS = nullptr;  | 
8519  | 376  |         }  | 
8520  | 399  |         CPLAcquireMutex(hNCMutex, 1000.0);  | 
8521  | 399  |         return poDS;  | 
8522  | 399  |     }  | 
8523  |  |  | 
8524  |  |     // We have more than one variable with 2 dimensions in the  | 
8525  |  |     // file, then treat this as a subdataset container dataset.  | 
8526  | 0  |     bool bSeveralVariablesAsBands = false;  | 
8527  | 0  |     const bool bListAllArrays = CPLTestBool(  | 
8528  | 0  |         CSLFetchNameValueDef(poDS->papszOpenOptions, "LIST_ALL_ARRAYS", "NO"));  | 
8529  | 0  |     if (bListAllArrays || ((nRasterVars > 1) && !bTreatAsSubdataset))  | 
8530  | 0  |     { | 
8531  | 0  |         if (CPLFetchBool(poOpenInfo->papszOpenOptions, "VARIABLES_AS_BANDS",  | 
8532  | 0  |                          false) &&  | 
8533  | 0  |             oMap2DDimsToGroupAndVar.size() == 1)  | 
8534  | 0  |         { | 
8535  | 0  |             std::tie(nGroupID, nVarID) =  | 
8536  | 0  |                 oMap2DDimsToGroupAndVar.begin()->second.front();  | 
8537  | 0  |             bSeveralVariablesAsBands = true;  | 
8538  | 0  |         }  | 
8539  | 0  |         else  | 
8540  | 0  |         { | 
8541  | 0  |             poDS->CreateSubDatasetList(cdfid);  | 
8542  | 0  |             poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);  | 
8543  | 0  |             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll  | 
8544  |  |                 // deadlock with GDALDataset own mutex.  | 
8545  | 0  |             poDS->TryLoadXML();  | 
8546  | 0  |             CPLAcquireMutex(hNCMutex, 1000.0);  | 
8547  | 0  |             return poDS;  | 
8548  | 0  |         }  | 
8549  | 0  |     }  | 
8550  |  |  | 
8551  |  |     // If we are not treating things as a subdataset, then capture  | 
8552  |  |     // the name of the single available variable as the subdataset.  | 
8553  | 0  |     if (!bTreatAsSubdataset)  | 
8554  | 0  |     { | 
8555  | 0  |         char *pszVarName = nullptr;  | 
8556  | 0  |         NCDF_ERR(NCDFGetVarFullName(nGroupID, nVarID, &pszVarName));  | 
8557  | 0  |         osSubdatasetName = (pszVarName != nullptr ? pszVarName : "");  | 
8558  | 0  |         CPLFree(pszVarName);  | 
8559  | 0  |     }  | 
8560  |  |  | 
8561  |  |     // We have ignored at least one variable, so we should report them  | 
8562  |  |     // as subdatasets for reference.  | 
8563  | 0  |     if (nIgnoredVars > 0 && !bTreatAsSubdataset)  | 
8564  | 0  |     { | 
8565  | 0  |         CPLDebug("GDAL_netCDF", | 
8566  | 0  |                  "As %d variables were ignored, creating subdataset list "  | 
8567  | 0  |                  "for reference. Variable #%d [%s] is the main variable",  | 
8568  | 0  |                  nIgnoredVars, nVarID, osSubdatasetName.c_str());  | 
8569  | 0  |         poDS->CreateSubDatasetList(cdfid);  | 
8570  | 0  |     }  | 
8571  |  |  | 
8572  |  |     // Open the NETCDF subdataset NETCDF:"filename":subdataset.  | 
8573  | 0  |     int var = -1;  | 
8574  | 0  |     NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &nGroupID, &var);  | 
8575  |  |     // Now we can forget the root cdfid and only use the selected group.  | 
8576  | 0  |     cdfid = nGroupID;  | 
8577  | 0  |     int nd = 0;  | 
8578  | 0  |     nc_inq_varndims(cdfid, var, &nd);  | 
8579  |  | 
  | 
8580  | 0  |     poDS->m_anDimIds.resize(nd);  | 
8581  |  |  | 
8582  |  |     // X, Y, Z position in array  | 
8583  | 0  |     std::vector<int> anBandDimPos(nd);  | 
8584  |  | 
  | 
8585  | 0  |     nc_inq_vardimid(cdfid, var, poDS->m_anDimIds.data());  | 
8586  |  |  | 
8587  |  |     // Check if somebody tried to pass a variable with less than 1D.  | 
8588  | 0  |     if (nd < 1)  | 
8589  | 0  |     { | 
8590  | 0  |         CPLError(CE_Warning, CPLE_AppDefined,  | 
8591  | 0  |                  "Variable has %d dimension(s) - not supported.", nd);  | 
8592  | 0  |         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock  | 
8593  |  |             // with GDALDataset own mutex.  | 
8594  | 0  |         delete poDS;  | 
8595  | 0  |         CPLAcquireMutex(hNCMutex, 1000.0);  | 
8596  | 0  |         return nullptr;  | 
8597  | 0  |     }  | 
8598  |  |  | 
8599  |  |     // CF-1 Convention  | 
8600  |  |     //  | 
8601  |  |     // Dimensions to appear in the relative order T, then Z, then Y,  | 
8602  |  |     // then X  to the file. All other dimensions should, whenever  | 
8603  |  |     // possible, be placed to the left of the spatiotemporal  | 
8604  |  |     // dimensions.  | 
8605  |  |  | 
8606  |  |     // Verify that dimensions are in the {T,Z,Y,X} or {T,Z,Y,X} order | 
8607  |  |     // Ideally we should detect for other ordering and act accordingly  | 
8608  |  |     // Only done if file has Conventions=CF-* and only prints warning  | 
8609  |  |     // To disable set GDAL_NETCDF_VERIFY_DIMS=NO and to use only  | 
8610  |  |     // attributes (not varnames) set GDAL_NETCDF_VERIFY_DIMS=STRICT  | 
8611  | 0  |     const bool bCheckDims =  | 
8612  | 0  |         CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES")) && | 
8613  | 0  |         STARTS_WITH_CI(szConventions, "CF");  | 
8614  |  | 
  | 
8615  | 0  |     if (nd >= 2 && bCheckDims)  | 
8616  | 0  |     { | 
8617  | 0  |         char szDimName1[NC_MAX_NAME + 1] = {}; | 
8618  | 0  |         char szDimName2[NC_MAX_NAME + 1] = {}; | 
8619  | 0  |         status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 1], szDimName1);  | 
8620  | 0  |         NCDF_ERR(status);  | 
8621  | 0  |         status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 2], szDimName2);  | 
8622  | 0  |         NCDF_ERR(status);  | 
8623  | 0  |         if (NCDFIsVarLongitude(cdfid, -1, szDimName1) == false &&  | 
8624  | 0  |             NCDFIsVarProjectionX(cdfid, -1, szDimName1) == false)  | 
8625  | 0  |         { | 
8626  | 0  |             CPLError(CE_Warning, CPLE_AppDefined,  | 
8627  | 0  |                      "dimension #%d (%s) is not a Longitude/X dimension.",  | 
8628  | 0  |                      nd - 1, szDimName1);  | 
8629  | 0  |         }  | 
8630  | 0  |         if (NCDFIsVarLatitude(cdfid, -1, szDimName2) == false &&  | 
8631  | 0  |             NCDFIsVarProjectionY(cdfid, -1, szDimName2) == false)  | 
8632  | 0  |         { | 
8633  | 0  |             CPLError(CE_Warning, CPLE_AppDefined,  | 
8634  | 0  |                      "dimension #%d (%s) is not a Latitude/Y dimension.",  | 
8635  | 0  |                      nd - 2, szDimName2);  | 
8636  | 0  |         }  | 
8637  | 0  |         if ((NCDFIsVarLongitude(cdfid, -1, szDimName2) ||  | 
8638  | 0  |              NCDFIsVarProjectionX(cdfid, -1, szDimName2)) &&  | 
8639  | 0  |             (NCDFIsVarLatitude(cdfid, -1, szDimName1) ||  | 
8640  | 0  |              NCDFIsVarProjectionY(cdfid, -1, szDimName1)))  | 
8641  | 0  |         { | 
8642  | 0  |             poDS->bSwitchedXY = true;  | 
8643  | 0  |         }  | 
8644  | 0  |         if (nd >= 3)  | 
8645  | 0  |         { | 
8646  | 0  |             char szDimName3[NC_MAX_NAME + 1] = {}; | 
8647  | 0  |             status =  | 
8648  | 0  |                 nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 3], szDimName3);  | 
8649  | 0  |             NCDF_ERR(status);  | 
8650  | 0  |             if (nd >= 4)  | 
8651  | 0  |             { | 
8652  | 0  |                 char szDimName4[NC_MAX_NAME + 1] = {}; | 
8653  | 0  |                 status =  | 
8654  | 0  |                     nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 4], szDimName4);  | 
8655  | 0  |                 NCDF_ERR(status);  | 
8656  | 0  |                 if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false)  | 
8657  | 0  |                 { | 
8658  | 0  |                     CPLError(CE_Warning, CPLE_AppDefined,  | 
8659  | 0  |                              "dimension #%d (%s) is not a Vertical dimension.",  | 
8660  | 0  |                              nd - 3, szDimName3);  | 
8661  | 0  |                 }  | 
8662  | 0  |                 if (NCDFIsVarTimeCoord(cdfid, -1, szDimName4) == false)  | 
8663  | 0  |                 { | 
8664  | 0  |                     CPLError(CE_Warning, CPLE_AppDefined,  | 
8665  | 0  |                              "dimension #%d (%s) is not a Time dimension.",  | 
8666  | 0  |                              nd - 4, szDimName4);  | 
8667  | 0  |                 }  | 
8668  | 0  |             }  | 
8669  | 0  |             else  | 
8670  | 0  |             { | 
8671  | 0  |                 if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false &&  | 
8672  | 0  |                     NCDFIsVarTimeCoord(cdfid, -1, szDimName3) == false)  | 
8673  | 0  |                 { | 
8674  | 0  |                     CPLError(CE_Warning, CPLE_AppDefined,  | 
8675  | 0  |                              "dimension #%d (%s) is not a "  | 
8676  | 0  |                              "Time or Vertical dimension.",  | 
8677  | 0  |                              nd - 3, szDimName3);  | 
8678  | 0  |                 }  | 
8679  | 0  |             }  | 
8680  | 0  |         }  | 
8681  | 0  |     }  | 
8682  |  |  | 
8683  |  |     // For example for EMIT data (https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/),  | 
8684  |  |     // dimension order is downtrack, crosstrack, bands  | 
8685  | 0  |     bool bYXBandOrder = false;  | 
8686  | 0  |     if (nd == 3)  | 
8687  | 0  |     { | 
8688  | 0  |         char szDimName[NC_MAX_NAME + 1] = {}; | 
8689  | 0  |         status = nc_inq_dimname(cdfid, poDS->m_anDimIds[2], szDimName);  | 
8690  | 0  |         NCDF_ERR(status);  | 
8691  | 0  |         bYXBandOrder =  | 
8692  | 0  |             strcmp(szDimName, "bands") == 0 || strcmp(szDimName, "band") == 0;  | 
8693  | 0  |     }  | 
8694  |  |  | 
8695  |  |     // Get X dimensions information.  | 
8696  | 0  |     size_t xdim;  | 
8697  | 0  |     poDS->nXDimID = poDS->m_anDimIds[bYXBandOrder ? 1 : nd - 1];  | 
8698  | 0  |     nc_inq_dimlen(cdfid, poDS->nXDimID, &xdim);  | 
8699  |  |  | 
8700  |  |     // Get Y dimension information.  | 
8701  | 0  |     size_t ydim;  | 
8702  | 0  |     if (nd >= 2)  | 
8703  | 0  |     { | 
8704  | 0  |         poDS->nYDimID = poDS->m_anDimIds[bYXBandOrder ? 0 : nd - 2];  | 
8705  | 0  |         nc_inq_dimlen(cdfid, poDS->nYDimID, &ydim);  | 
8706  | 0  |     }  | 
8707  | 0  |     else  | 
8708  | 0  |     { | 
8709  | 0  |         poDS->nYDimID = -1;  | 
8710  | 0  |         ydim = 1;  | 
8711  | 0  |     }  | 
8712  |  | 
  | 
8713  | 0  |     if (xdim > INT_MAX || ydim > INT_MAX)  | 
8714  | 0  |     { | 
8715  | 0  |         CPLError(CE_Failure, CPLE_AppDefined,  | 
8716  | 0  |                  "Invalid raster dimensions: " CPL_FRMT_GUIB "x" CPL_FRMT_GUIB,  | 
8717  | 0  |                  static_cast<GUIntBig>(xdim), static_cast<GUIntBig>(ydim));  | 
8718  | 0  |         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock  | 
8719  |  |             // with GDALDataset own mutex.  | 
8720  | 0  |         delete poDS;  | 
8721  | 0  |         CPLAcquireMutex(hNCMutex, 1000.0);  | 
8722  | 0  |         return nullptr;  | 
8723  | 0  |     }  | 
8724  |  |  | 
8725  | 0  |     poDS->nRasterXSize = static_cast<int>(xdim);  | 
8726  | 0  |     poDS->nRasterYSize = static_cast<int>(ydim);  | 
8727  |  | 
  | 
8728  | 0  |     unsigned int k = 0;  | 
8729  | 0  |     for (int j = 0; j < nd; j++)  | 
8730  | 0  |     { | 
8731  | 0  |         if (poDS->m_anDimIds[j] == poDS->nXDimID)  | 
8732  | 0  |         { | 
8733  | 0  |             anBandDimPos[0] = j;  // Save Position of XDim  | 
8734  | 0  |             k++;  | 
8735  | 0  |         }  | 
8736  | 0  |         if (poDS->m_anDimIds[j] == poDS->nYDimID)  | 
8737  | 0  |         { | 
8738  | 0  |             anBandDimPos[1] = j;  // Save Position of YDim  | 
8739  | 0  |             k++;  | 
8740  | 0  |         }  | 
8741  | 0  |     }  | 
8742  |  |     // X and Y Dimension Ids were not found!  | 
8743  | 0  |     if ((nd >= 2 && k != 2) || (nd == 1 && k != 1))  | 
8744  | 0  |     { | 
8745  | 0  |         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock  | 
8746  |  |             // with GDALDataset own mutex.  | 
8747  | 0  |         delete poDS;  | 
8748  | 0  |         CPLAcquireMutex(hNCMutex, 1000.0);  | 
8749  | 0  |         return nullptr;  | 
8750  | 0  |     }  | 
8751  |  |  | 
8752  |  |     // Read Metadata for this variable.  | 
8753  |  |  | 
8754  |  |     // Should disable as is also done at band level, except driver needs the  | 
8755  |  |     // variables as metadata (e.g. projection).  | 
8756  | 0  |     poDS->ReadAttributes(cdfid, var);  | 
8757  |  |  | 
8758  |  |     // Read Metadata for each dimension.  | 
8759  | 0  |     int *panDimIds = nullptr;  | 
8760  | 0  |     NCDFGetVisibleDims(cdfid, &ndims, &panDimIds);  | 
8761  |  |     // With NetCDF-4 groups panDimIds is not always [0..dim_count-1] like  | 
8762  |  |     // in NetCDF-3 because we see only the dimensions of the selected group  | 
8763  |  |     // and its parents.  | 
8764  |  |     // poDS->papszDimName is indexed by dim IDs, so it must contains all IDs  | 
8765  |  |     // [0..max(panDimIds)], but they are not all useful so we fill names  | 
8766  |  |     // of useless dims with empty string.  | 
8767  | 0  |     if (panDimIds)  | 
8768  | 0  |     { | 
8769  | 0  |         const int nMaxDimId = *std::max_element(panDimIds, panDimIds + ndims);  | 
8770  | 0  |         std::set<int> oSetExistingDimIds;  | 
8771  | 0  |         for (int i = 0; i < ndims; i++)  | 
8772  | 0  |         { | 
8773  | 0  |             oSetExistingDimIds.insert(panDimIds[i]);  | 
8774  | 0  |         }  | 
8775  | 0  |         std::set<int> oSetDimIdsUsedByVar;  | 
8776  | 0  |         for (int i = 0; i < nd; i++)  | 
8777  | 0  |         { | 
8778  | 0  |             oSetDimIdsUsedByVar.insert(poDS->m_anDimIds[i]);  | 
8779  | 0  |         }  | 
8780  | 0  |         for (int j = 0; j <= nMaxDimId; j++)  | 
8781  | 0  |         { | 
8782  |  |             // Is j dim used?  | 
8783  | 0  |             if (oSetExistingDimIds.find(j) != oSetExistingDimIds.end())  | 
8784  | 0  |             { | 
8785  |  |                 // Useful dim.  | 
8786  | 0  |                 char szTemp[NC_MAX_NAME + 1] = {}; | 
8787  | 0  |                 status = nc_inq_dimname(cdfid, j, szTemp);  | 
8788  | 0  |                 if (status != NC_NOERR)  | 
8789  | 0  |                 { | 
8790  | 0  |                     CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll  | 
8791  |  |                         // deadlock with GDALDataset own  | 
8792  |  |                         // mutex.  | 
8793  | 0  |                     delete poDS;  | 
8794  | 0  |                     CPLAcquireMutex(hNCMutex, 1000.0);  | 
8795  | 0  |                     return nullptr;  | 
8796  | 0  |                 }  | 
8797  | 0  |                 poDS->papszDimName.AddString(szTemp);  | 
8798  |  | 
  | 
8799  | 0  |                 if (oSetDimIdsUsedByVar.find(j) != oSetDimIdsUsedByVar.end())  | 
8800  | 0  |                 { | 
8801  | 0  |                     int nDimGroupId = -1;  | 
8802  | 0  |                     int nDimVarId = -1;  | 
8803  | 0  |                     if (NCDFResolveVar(cdfid, poDS->papszDimName[j],  | 
8804  | 0  |                                        &nDimGroupId, &nDimVarId) == CE_None)  | 
8805  | 0  |                     { | 
8806  | 0  |                         poDS->ReadAttributes(nDimGroupId, nDimVarId);  | 
8807  | 0  |                     }  | 
8808  | 0  |                 }  | 
8809  | 0  |             }  | 
8810  | 0  |             else  | 
8811  | 0  |             { | 
8812  |  |                 // Useless dim.  | 
8813  | 0  |                 poDS->papszDimName.AddString(""); | 
8814  | 0  |             }  | 
8815  | 0  |         }  | 
8816  | 0  |         CPLFree(panDimIds);  | 
8817  | 0  |     }  | 
8818  |  |  | 
8819  |  |     // Set projection info.  | 
8820  | 0  |     std::vector<std::string> aosRemovedMDItems;  | 
8821  | 0  |     if (nd > 1)  | 
8822  | 0  |     { | 
8823  | 0  |         poDS->SetProjectionFromVar(cdfid, var,  | 
8824  | 0  |                                    /*bReadSRSOnly=*/false,  | 
8825  | 0  |                                    /* pszGivenGM = */ nullptr,  | 
8826  | 0  |                                    /* returnProjStr = */ nullptr,  | 
8827  | 0  |                                    /* sg = */ nullptr, &aosRemovedMDItems);  | 
8828  | 0  |     }  | 
8829  |  |  | 
8830  |  |     // Override bottom-up with GDAL_NETCDF_BOTTOMUP config option.  | 
8831  | 0  |     const char *pszValue = CPLGetConfigOption("GDAL_NETCDF_BOTTOMUP", nullptr); | 
8832  | 0  |     if (pszValue)  | 
8833  | 0  |     { | 
8834  | 0  |         poDS->bBottomUp = CPLTestBool(pszValue);  | 
8835  | 0  |         CPLDebug("GDAL_netCDF", | 
8836  | 0  |                  "set bBottomUp=%d because GDAL_NETCDF_BOTTOMUP=%s",  | 
8837  | 0  |                  static_cast<int>(poDS->bBottomUp), pszValue);  | 
8838  | 0  |     }  | 
8839  |  |  | 
8840  |  |     // Save non-spatial dimension info.  | 
8841  |  | 
  | 
8842  | 0  |     int *panBandZLev = nullptr;  | 
8843  | 0  |     int nDim = (nd >= 2) ? 2 : 1;  | 
8844  | 0  |     size_t lev_count;  | 
8845  | 0  |     size_t nTotLevCount = 1;  | 
8846  | 0  |     nc_type nType = NC_NAT;  | 
8847  |  | 
  | 
8848  | 0  |     CPLString osExtraDimNames;  | 
8849  |  | 
  | 
8850  | 0  |     if (nd > 2)  | 
8851  | 0  |     { | 
8852  | 0  |         nDim = 2;  | 
8853  | 0  |         panBandZLev = static_cast<int *>(CPLCalloc(nd - 2, sizeof(int)));  | 
8854  |  | 
  | 
8855  | 0  |         osExtraDimNames = "{"; | 
8856  |  | 
  | 
8857  | 0  |         char szDimName[NC_MAX_NAME + 1] = {}; | 
8858  |  | 
  | 
8859  | 0  |         bool bREPORT_EXTRA_DIM_VALUESWarningEmitted = false;  | 
8860  | 0  |         for (int j = 0; j < nd; j++)  | 
8861  | 0  |         { | 
8862  | 0  |             if ((poDS->m_anDimIds[j] != poDS->nXDimID) &&  | 
8863  | 0  |                 (poDS->m_anDimIds[j] != poDS->nYDimID))  | 
8864  | 0  |             { | 
8865  | 0  |                 nc_inq_dimlen(cdfid, poDS->m_anDimIds[j], &lev_count);  | 
8866  | 0  |                 nTotLevCount *= lev_count;  | 
8867  | 0  |                 panBandZLev[nDim - 2] = static_cast<int>(lev_count);  | 
8868  | 0  |                 anBandDimPos[nDim] = j;  // Save Position of ZDim  | 
8869  |  |                 // Save non-spatial dimension names.  | 
8870  | 0  |                 if (nc_inq_dimname(cdfid, poDS->m_anDimIds[j], szDimName) ==  | 
8871  | 0  |                     NC_NOERR)  | 
8872  | 0  |                 { | 
8873  | 0  |                     osExtraDimNames += szDimName;  | 
8874  | 0  |                     if (j < nd - 3)  | 
8875  | 0  |                     { | 
8876  | 0  |                         osExtraDimNames += ",";  | 
8877  | 0  |                     }  | 
8878  |  | 
  | 
8879  | 0  |                     int nIdxGroupID = -1;  | 
8880  | 0  |                     int nIdxVarID = Get1DVariableIndexedByDimension(  | 
8881  | 0  |                         cdfid, poDS->m_anDimIds[j], szDimName, true,  | 
8882  | 0  |                         &nIdxGroupID);  | 
8883  | 0  |                     poDS->m_anExtraDimGroupIds.push_back(nIdxGroupID);  | 
8884  | 0  |                     poDS->m_anExtraDimVarIds.push_back(nIdxVarID);  | 
8885  |  | 
  | 
8886  | 0  |                     if (nIdxVarID >= 0)  | 
8887  | 0  |                     { | 
8888  | 0  |                         nc_inq_vartype(nIdxGroupID, nIdxVarID, &nType);  | 
8889  | 0  |                         char szExtraDimDef[NC_MAX_NAME + 1];  | 
8890  | 0  |                         snprintf(szExtraDimDef, sizeof(szExtraDimDef),  | 
8891  | 0  |                                  "{%ld,%d}", (long)lev_count, nType); | 
8892  | 0  |                         char szTemp[NC_MAX_NAME + 32 + 1];  | 
8893  | 0  |                         snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",  | 
8894  | 0  |                                  szDimName);  | 
8895  | 0  |                         poDS->papszMetadata = CSLSetNameValue(  | 
8896  | 0  |                             poDS->papszMetadata, szTemp, szExtraDimDef);  | 
8897  |  |  | 
8898  |  |                         // Retrieving data for unlimited dimensions might be  | 
8899  |  |                         // costly on network storage, so don't do it.  | 
8900  |  |                         // Each band will capture the value along the extra  | 
8901  |  |                         // dimension in its NETCDF_DIM_xxxx band metadata item  | 
8902  |  |                         // Addresses use case of  | 
8903  |  |                         // https://lists.osgeo.org/pipermail/gdal-dev/2023-May/057209.html  | 
8904  | 0  |                         const bool bIsLocal =  | 
8905  | 0  |                             VSIIsLocal(osFilenameForNCOpen.c_str());  | 
8906  | 0  |                         bool bListDimValues =  | 
8907  | 0  |                             bIsLocal || lev_count == 1 ||  | 
8908  | 0  |                             !NCDFIsUnlimitedDim(poDS->eFormat ==  | 
8909  | 0  |                                                     NCDF_FORMAT_NC4,  | 
8910  | 0  |                                                 cdfid, poDS->m_anDimIds[j]);  | 
8911  | 0  |                         const char *pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES =  | 
8912  | 0  |                             CPLGetConfigOption(  | 
8913  | 0  |                                 "GDAL_NETCDF_REPORT_EXTRA_DIM_VALUES", nullptr);  | 
8914  | 0  |                         if (pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES)  | 
8915  | 0  |                         { | 
8916  | 0  |                             bListDimValues = CPLTestBool(  | 
8917  | 0  |                                 pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES);  | 
8918  | 0  |                         }  | 
8919  | 0  |                         else if (!bListDimValues && !bIsLocal &&  | 
8920  | 0  |                                  !bREPORT_EXTRA_DIM_VALUESWarningEmitted)  | 
8921  | 0  |                         { | 
8922  | 0  |                             bREPORT_EXTRA_DIM_VALUESWarningEmitted = true;  | 
8923  | 0  |                             CPLDebug(  | 
8924  | 0  |                                 "GDAL_netCDF",  | 
8925  | 0  |                                 "Listing extra dimension values is skipped "  | 
8926  | 0  |                                 "because this dataset is hosted on a network "  | 
8927  | 0  |                                 "file system, and such an operation could be "  | 
8928  | 0  |                                 "slow. If you still want to proceed, set the "  | 
8929  | 0  |                                 "GDAL_NETCDF_REPORT_EXTRA_DIM_VALUES "  | 
8930  | 0  |                                 "configuration option to YES");  | 
8931  | 0  |                         }  | 
8932  | 0  |                         if (bListDimValues)  | 
8933  | 0  |                         { | 
8934  | 0  |                             char *pszTemp = nullptr;  | 
8935  | 0  |                             if (NCDFGet1DVar(nIdxGroupID, nIdxVarID,  | 
8936  | 0  |                                              &pszTemp) == CE_None)  | 
8937  | 0  |                             { | 
8938  | 0  |                                 snprintf(szTemp, sizeof(szTemp),  | 
8939  | 0  |                                          "NETCDF_DIM_%s_VALUES", szDimName);  | 
8940  | 0  |                                 poDS->papszMetadata = CSLSetNameValue(  | 
8941  | 0  |                                     poDS->papszMetadata, szTemp, pszTemp);  | 
8942  | 0  |                                 CPLFree(pszTemp);  | 
8943  | 0  |                             }  | 
8944  | 0  |                         }  | 
8945  | 0  |                     }  | 
8946  | 0  |                 }  | 
8947  | 0  |                 else  | 
8948  | 0  |                 { | 
8949  | 0  |                     poDS->m_anExtraDimGroupIds.push_back(-1);  | 
8950  | 0  |                     poDS->m_anExtraDimVarIds.push_back(-1);  | 
8951  | 0  |                 }  | 
8952  |  | 
  | 
8953  | 0  |                 nDim++;  | 
8954  | 0  |             }  | 
8955  | 0  |         }  | 
8956  | 0  |         osExtraDimNames += "}";  | 
8957  | 0  |         poDS->papszMetadata = CSLSetNameValue(  | 
8958  | 0  |             poDS->papszMetadata, "NETCDF_DIM_EXTRA", osExtraDimNames);  | 
8959  | 0  |     }  | 
8960  |  |  | 
8961  |  |     // Store Metadata.  | 
8962  | 0  |     for (const auto &osStr : aosRemovedMDItems)  | 
8963  | 0  |         poDS->papszMetadata =  | 
8964  | 0  |             CSLSetNameValue(poDS->papszMetadata, osStr.c_str(), nullptr);  | 
8965  |  | 
  | 
8966  | 0  |     poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);  | 
8967  |  |  | 
8968  |  |     // Create bands.  | 
8969  |  |  | 
8970  |  |     // Arbitrary threshold.  | 
8971  | 0  |     int nMaxBandCount =  | 
8972  | 0  |         atoi(CPLGetConfigOption("GDAL_MAX_BAND_COUNT", "32768")); | 
8973  | 0  |     if (nMaxBandCount <= 0)  | 
8974  | 0  |         nMaxBandCount = 32768;  | 
8975  | 0  |     if (nTotLevCount > static_cast<unsigned int>(nMaxBandCount))  | 
8976  | 0  |     { | 
8977  | 0  |         CPLError(CE_Warning, CPLE_AppDefined,  | 
8978  | 0  |                  "Limiting number of bands to %d instead of %u", nMaxBandCount,  | 
8979  | 0  |                  static_cast<unsigned int>(nTotLevCount));  | 
8980  | 0  |         nTotLevCount = static_cast<unsigned int>(nMaxBandCount);  | 
8981  | 0  |     }  | 
8982  | 0  |     if (poDS->nRasterXSize == 0 || poDS->nRasterYSize == 0)  | 
8983  | 0  |     { | 
8984  | 0  |         poDS->nRasterXSize = 0;  | 
8985  | 0  |         poDS->nRasterYSize = 0;  | 
8986  | 0  |         nTotLevCount = 0;  | 
8987  | 0  |         if (poDS->GetLayerCount() == 0)  | 
8988  | 0  |         { | 
8989  | 0  |             CPLFree(panBandZLev);  | 
8990  | 0  |             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll  | 
8991  |  |                 // deadlock with GDALDataset own mutex.  | 
8992  | 0  |             delete poDS;  | 
8993  | 0  |             CPLAcquireMutex(hNCMutex, 1000.0);  | 
8994  | 0  |             return nullptr;  | 
8995  | 0  |         }  | 
8996  | 0  |     }  | 
8997  | 0  |     if (bSeveralVariablesAsBands)  | 
8998  | 0  |     { | 
8999  | 0  |         const auto &listVariables = oMap2DDimsToGroupAndVar.begin()->second;  | 
9000  | 0  |         for (int iBand = 0; iBand < static_cast<int>(listVariables.size());  | 
9001  | 0  |              ++iBand)  | 
9002  | 0  |         { | 
9003  | 0  |             int bandVarGroupId = listVariables[iBand].first;  | 
9004  | 0  |             int bandVarId = listVariables[iBand].second;  | 
9005  | 0  |             netCDFRasterBand *poBand = new netCDFRasterBand(  | 
9006  | 0  |                 netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, bandVarGroupId,  | 
9007  | 0  |                 bandVarId, nDim, 0, nullptr, anBandDimPos.data(), iBand + 1);  | 
9008  | 0  |             poDS->SetBand(iBand + 1, poBand);  | 
9009  | 0  |         }  | 
9010  | 0  |     }  | 
9011  | 0  |     else  | 
9012  | 0  |     { | 
9013  | 0  |         for (unsigned int lev = 0; lev < nTotLevCount; lev++)  | 
9014  | 0  |         { | 
9015  | 0  |             netCDFRasterBand *poBand = new netCDFRasterBand(  | 
9016  | 0  |                 netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, cdfid, var, nDim,  | 
9017  | 0  |                 lev, panBandZLev, anBandDimPos.data(), lev + 1);  | 
9018  | 0  |             poDS->SetBand(lev + 1, poBand);  | 
9019  | 0  |         }  | 
9020  | 0  |     }  | 
9021  |  | 
  | 
9022  | 0  |     if (panBandZLev)  | 
9023  | 0  |         CPLFree(panBandZLev);  | 
9024  |  |     // Handle angular geographic coordinates here  | 
9025  |  |  | 
9026  |  |     // Initialize any PAM information.  | 
9027  | 0  |     if (bTreatAsSubdataset)  | 
9028  | 0  |     { | 
9029  | 0  |         poDS->SetPhysicalFilename(poDS->osFilename);  | 
9030  | 0  |         poDS->SetSubdatasetName(osSubdatasetName);  | 
9031  | 0  |     }  | 
9032  |  | 
  | 
9033  | 0  |     CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock with  | 
9034  |  |         // GDALDataset own mutex.  | 
9035  | 0  |     poDS->TryLoadXML();  | 
9036  |  | 
  | 
9037  | 0  |     if (bTreatAsSubdataset)  | 
9038  | 0  |         poDS->oOvManager.Initialize(poDS, ":::VIRTUAL:::");  | 
9039  | 0  |     else  | 
9040  | 0  |         poDS->oOvManager.Initialize(poDS, poDS->osFilename);  | 
9041  |  | 
  | 
9042  | 0  |     CPLAcquireMutex(hNCMutex, 1000.0);  | 
9043  |  | 
  | 
9044  | 0  |     return poDS;  | 
9045  | 0  | }  | 
9046  |  |  | 
9047  |  | /************************************************************************/  | 
9048  |  | /*                            CopyMetadata()                            */  | 
9049  |  | /*                                                                      */  | 
9050  |  | /*      Create a copy of metadata for NC_GLOBAL or a variable           */  | 
9051  |  | /************************************************************************/  | 
9052  |  |  | 
9053  |  | static void CopyMetadata(GDALDataset *poSrcDS, GDALRasterBand *poSrcBand,  | 
9054  |  |                          GDALRasterBand *poDstBand, int nCdfId, int CDFVarID,  | 
9055  |  |                          const char *pszPrefix)  | 
9056  | 0  | { | 
9057  |  |     // Remove the following band meta but set them later from band data.  | 
9058  | 0  |     const char *const papszIgnoreBand[] = { | 
9059  | 0  |         CF_ADD_OFFSET,  CF_SCALE_FACTOR, "valid_range", "_Unsigned",  | 
9060  | 0  |         NCDF_FillValue, "coordinates",   nullptr};  | 
9061  | 0  |     const char *const papszIgnoreGlobal[] = {"NETCDF_DIM_EXTRA", nullptr}; | 
9062  |  | 
  | 
9063  | 0  |     CSLConstList papszMetadata = nullptr;  | 
9064  | 0  |     if (poSrcDS)  | 
9065  | 0  |     { | 
9066  | 0  |         papszMetadata = poSrcDS->GetMetadata();  | 
9067  | 0  |     }  | 
9068  | 0  |     else if (poSrcBand)  | 
9069  | 0  |     { | 
9070  | 0  |         papszMetadata = poSrcBand->GetMetadata();  | 
9071  | 0  |     }  | 
9072  |  | 
  | 
9073  | 0  |     for (const auto &[pszKey, pszValue] : cpl::IterateNameValue(papszMetadata))  | 
9074  | 0  |     { | 
9075  |  | #ifdef NCDF_DEBUG  | 
9076  |  |         CPLDebug("GDAL_netCDF", "copy metadata [%s]=[%s]", pszKey, pszValue); | 
9077  |  | #endif  | 
9078  |  | 
  | 
9079  | 0  |         CPLString osMetaName(pszKey);  | 
9080  |  |  | 
9081  |  |         // Check for items that match pszPrefix if applicable.  | 
9082  | 0  |         if (pszPrefix && !EQUAL(pszPrefix, ""))  | 
9083  | 0  |         { | 
9084  |  |             // Remove prefix.  | 
9085  | 0  |             if (STARTS_WITH(osMetaName.c_str(), pszPrefix))  | 
9086  | 0  |             { | 
9087  | 0  |                 osMetaName = osMetaName.substr(strlen(pszPrefix));  | 
9088  | 0  |             }  | 
9089  |  |             // Only copy items that match prefix.  | 
9090  | 0  |             else  | 
9091  | 0  |             { | 
9092  | 0  |                 continue;  | 
9093  | 0  |             }  | 
9094  | 0  |         }  | 
9095  |  |  | 
9096  |  |         // Fix various issues with metadata translation.  | 
9097  | 0  |         if (CDFVarID == NC_GLOBAL)  | 
9098  | 0  |         { | 
9099  |  |             // Do not copy items in papszIgnoreGlobal and NETCDF_DIM_*.  | 
9100  | 0  |             if ((CSLFindString(papszIgnoreGlobal, osMetaName) != -1) ||  | 
9101  | 0  |                 (STARTS_WITH(osMetaName, "NETCDF_DIM_")))  | 
9102  | 0  |                 continue;  | 
9103  |  |             // Remove NC_GLOBAL prefix for netcdf global Metadata.  | 
9104  | 0  |             else if (STARTS_WITH(osMetaName, "NC_GLOBAL#"))  | 
9105  | 0  |             { | 
9106  | 0  |                 osMetaName = osMetaName.substr(strlen("NC_GLOBAL#")); | 
9107  | 0  |             }  | 
9108  |  |             // GDAL Metadata renamed as GDAL-[meta].  | 
9109  | 0  |             else if (strstr(osMetaName, "#") == nullptr)  | 
9110  | 0  |             { | 
9111  | 0  |                 osMetaName = "GDAL_" + osMetaName;  | 
9112  | 0  |             }  | 
9113  |  |             // Keep time, lev and depth information for safe-keeping.  | 
9114  |  |             // Time and vertical coordinate handling need improvements.  | 
9115  |  |             /*  | 
9116  |  |             else if( STARTS_WITH(szMetaName, "time#") )  | 
9117  |  |             { | 
9118  |  |                 szMetaName[4] = '-';  | 
9119  |  |             }  | 
9120  |  |             else if( STARTS_WITH(szMetaName, "lev#") )  | 
9121  |  |             { | 
9122  |  |                 szMetaName[3] = '-';  | 
9123  |  |             }  | 
9124  |  |             else if( STARTS_WITH(szMetaName, "depth#") )  | 
9125  |  |             { | 
9126  |  |                 szMetaName[5] = '-';  | 
9127  |  |             }  | 
9128  |  |             */  | 
9129  |  |             // Only copy data without # (previously all data was copied).  | 
9130  | 0  |             if (strstr(osMetaName, "#") != nullptr)  | 
9131  | 0  |                 continue;  | 
9132  |  |             // netCDF attributes do not like the '#' character.  | 
9133  |  |             // for( unsigned int h=0; h < strlen(szMetaName) -1 ; h++ ) { | 
9134  |  |             //     if( szMetaName[h] == '#') szMetaName[h] = '-';  | 
9135  |  |             // }  | 
9136  | 0  |         }  | 
9137  | 0  |         else  | 
9138  | 0  |         { | 
9139  |  |             // Do not copy varname, stats, NETCDF_DIM_*, nodata  | 
9140  |  |             // and items in papszIgnoreBand.  | 
9141  | 0  |             if (STARTS_WITH(osMetaName, "NETCDF_VARNAME") ||  | 
9142  | 0  |                 STARTS_WITH(osMetaName, "STATISTICS_") ||  | 
9143  | 0  |                 STARTS_WITH(osMetaName, "NETCDF_DIM_") ||  | 
9144  | 0  |                 STARTS_WITH(osMetaName, "missing_value") ||  | 
9145  | 0  |                 STARTS_WITH(osMetaName, "_FillValue") ||  | 
9146  | 0  |                 CSLFindString(papszIgnoreBand, osMetaName) != -1)  | 
9147  | 0  |                 continue;  | 
9148  | 0  |         }  | 
9149  |  |  | 
9150  |  | #ifdef NCDF_DEBUG  | 
9151  |  |         CPLDebug("GDAL_netCDF", "copy name=[%s] value=[%s]", osMetaName.c_str(), | 
9152  |  |                  pszValue);  | 
9153  |  | #endif  | 
9154  | 0  |         if (NCDFPutAttr(nCdfId, CDFVarID, osMetaName, pszValue) != CE_None)  | 
9155  | 0  |         { | 
9156  | 0  |             CPLDebug("GDAL_netCDF", "NCDFPutAttr(%d, %d, %s, %s) failed", | 
9157  | 0  |                      nCdfId, CDFVarID, osMetaName.c_str(), pszValue);  | 
9158  | 0  |         }  | 
9159  | 0  |     }  | 
9160  |  |  | 
9161  |  |     // Set add_offset and scale_factor here if present.  | 
9162  | 0  |     if (poSrcBand && poDstBand)  | 
9163  | 0  |     { | 
9164  |  | 
  | 
9165  | 0  |         int bGotAddOffset = FALSE;  | 
9166  | 0  |         const double dfAddOffset = poSrcBand->GetOffset(&bGotAddOffset);  | 
9167  | 0  |         int bGotScale = FALSE;  | 
9168  | 0  |         const double dfScale = poSrcBand->GetScale(&bGotScale);  | 
9169  |  | 
  | 
9170  | 0  |         if (bGotAddOffset && dfAddOffset != 0.0)  | 
9171  | 0  |             poDstBand->SetOffset(dfAddOffset);  | 
9172  | 0  |         if (bGotScale && dfScale != 1.0)  | 
9173  | 0  |             poDstBand->SetScale(dfScale);  | 
9174  | 0  |     }  | 
9175  | 0  | }  | 
9176  |  |  | 
9177  |  | /************************************************************************/  | 
9178  |  | /*                            CreateLL()                                */  | 
9179  |  | /*                                                                      */  | 
9180  |  | /*      Shared functionality between netCDFDataset::Create() and        */  | 
9181  |  | /*      netCDF::CreateCopy() for creating netcdf file based on a set of */  | 
9182  |  | /*      options and a configuration.                                    */  | 
9183  |  | /************************************************************************/  | 
9184  |  |  | 
9185  |  | netCDFDataset *netCDFDataset::CreateLL(const char *pszFilename, int nXSize,  | 
9186  |  |                                        int nYSize, int nBandsIn,  | 
9187  |  |                                        char **papszOptions)  | 
9188  | 0  | { | 
9189  | 0  |     if (!((nXSize == 0 && nYSize == 0 && nBandsIn == 0) ||  | 
9190  | 0  |           (nXSize > 0 && nYSize > 0 && nBandsIn > 0)))  | 
9191  | 0  |     { | 
9192  | 0  |         return nullptr;  | 
9193  | 0  |     }  | 
9194  |  |  | 
9195  | 0  |     CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock with  | 
9196  |  |         // GDALDataset own mutex.  | 
9197  | 0  |     netCDFDataset *poDS = new netCDFDataset();  | 
9198  | 0  |     CPLAcquireMutex(hNCMutex, 1000.0);  | 
9199  |  | 
  | 
9200  | 0  |     poDS->nRasterXSize = nXSize;  | 
9201  | 0  |     poDS->nRasterYSize = nYSize;  | 
9202  | 0  |     poDS->eAccess = GA_Update;  | 
9203  | 0  |     poDS->osFilename = pszFilename;  | 
9204  |  |  | 
9205  |  |     // From gtiff driver, is this ok?  | 
9206  |  |     /*  | 
9207  |  |     poDS->nBlockXSize = nXSize;  | 
9208  |  |     poDS->nBlockYSize = 1;  | 
9209  |  |     poDS->nBlocksPerBand =  | 
9210  |  |         DIV_ROUND_UP((nYSize, poDS->nBlockYSize))  | 
9211  |  |         * DIV_ROUND_UP((nXSize, poDS->nBlockXSize));  | 
9212  |  |         */  | 
9213  |  |  | 
9214  |  |     // process options.  | 
9215  | 0  |     poDS->papszCreationOptions = CSLDuplicate(papszOptions);  | 
9216  | 0  |     poDS->ProcessCreationOptions();  | 
9217  |  | 
  | 
9218  | 0  |     if (poDS->eMultipleLayerBehavior == SEPARATE_FILES)  | 
9219  | 0  |     { | 
9220  | 0  |         VSIStatBuf sStat;  | 
9221  | 0  |         if (VSIStat(pszFilename, &sStat) == 0)  | 
9222  | 0  |         { | 
9223  | 0  |             if (!VSI_ISDIR(sStat.st_mode))  | 
9224  | 0  |             { | 
9225  | 0  |                 CPLError(CE_Failure, CPLE_FileIO,  | 
9226  | 0  |                          "%s is an existing file, but not a directory",  | 
9227  | 0  |                          pszFilename);  | 
9228  | 0  |                 CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll  | 
9229  |  |                     // deadlock with GDALDataset own  | 
9230  |  |                     // mutex.  | 
9231  | 0  |                 delete poDS;  | 
9232  | 0  |                 CPLAcquireMutex(hNCMutex, 1000.0);  | 
9233  | 0  |                 return nullptr;  | 
9234  | 0  |             }  | 
9235  | 0  |         }  | 
9236  | 0  |         else if (VSIMkdir(pszFilename, 0755) != 0)  | 
9237  | 0  |         { | 
9238  | 0  |             CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s directory",  | 
9239  | 0  |                      pszFilename);  | 
9240  | 0  |             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll  | 
9241  |  |                 // deadlock with GDALDataset own mutex.  | 
9242  | 0  |             delete poDS;  | 
9243  | 0  |             CPLAcquireMutex(hNCMutex, 1000.0);  | 
9244  | 0  |             return nullptr;  | 
9245  | 0  |         }  | 
9246  |  |  | 
9247  | 0  |         return poDS;  | 
9248  | 0  |     }  | 
9249  |  |     // Create the dataset.  | 
9250  | 0  |     CPLString osFilenameForNCCreate(pszFilename);  | 
9251  |  | #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)  | 
9252  |  |     if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES"))) | 
9253  |  |     { | 
9254  |  |         char *pszTemp =  | 
9255  |  |             CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");  | 
9256  |  |         osFilenameForNCCreate = pszTemp;  | 
9257  |  |         CPLFree(pszTemp);  | 
9258  |  |     }  | 
9259  |  | #endif  | 
9260  |  | 
  | 
9261  |  | #if defined(_WIN32)  | 
9262  |  |     { | 
9263  |  |         // Works around bug of msys2 netCDF 4.9.0 package where nc_create()  | 
9264  |  |         // crashes  | 
9265  |  |         VSIStatBuf sStat;  | 
9266  |  |         const std::string osDirname =  | 
9267  |  |             CPLGetDirnameSafe(osFilenameForNCCreate.c_str());  | 
9268  |  |         if (VSIStat(osDirname.c_str(), &sStat) != 0)  | 
9269  |  |         { | 
9270  |  |             CPLError(CE_Failure, CPLE_OpenFailed,  | 
9271  |  |                      "Unable to create netCDF file %s: non existing output "  | 
9272  |  |                      "directory",  | 
9273  |  |                      pszFilename);  | 
9274  |  |             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll  | 
9275  |  |                 // deadlock with GDALDataset own mutex.  | 
9276  |  |             delete poDS;  | 
9277  |  |             CPLAcquireMutex(hNCMutex, 1000.0);  | 
9278  |  |             return nullptr;  | 
9279  |  |         }  | 
9280  |  |     }  | 
9281  |  | #endif  | 
9282  |  | 
  | 
9283  | 0  |     int status =  | 
9284  | 0  |         nc_create(osFilenameForNCCreate, poDS->nCreateMode, &(poDS->cdfid));  | 
9285  |  |  | 
9286  |  |     // Put into define mode.  | 
9287  | 0  |     poDS->SetDefineMode(true);  | 
9288  |  | 
  | 
9289  | 0  |     if (status != NC_NOERR)  | 
9290  | 0  |     { | 
9291  | 0  |         CPLError(CE_Failure, CPLE_OpenFailed,  | 
9292  | 0  |                  "Unable to create netCDF file %s (Error code %d): %s .",  | 
9293  | 0  |                  pszFilename, status, nc_strerror(status));  | 
9294  | 0  |         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock  | 
9295  |  |             // with GDALDataset own mutex.  | 
9296  | 0  |         delete poDS;  | 
9297  | 0  |         CPLAcquireMutex(hNCMutex, 1000.0);  | 
9298  | 0  |         return nullptr;  | 
9299  | 0  |     }  | 
9300  |  |  | 
9301  |  |     // Define dimensions.  | 
9302  | 0  |     if (nXSize > 0 && nYSize > 0)  | 
9303  | 0  |     { | 
9304  | 0  |         poDS->papszDimName.AddString(NCDF_DIMNAME_X);  | 
9305  | 0  |         status =  | 
9306  | 0  |             nc_def_dim(poDS->cdfid, NCDF_DIMNAME_X, nXSize, &(poDS->nXDimID));  | 
9307  | 0  |         NCDF_ERR(status);  | 
9308  | 0  |         CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d", | 
9309  | 0  |                  poDS->cdfid, NCDF_DIMNAME_X, nXSize, poDS->nXDimID);  | 
9310  |  | 
  | 
9311  | 0  |         poDS->papszDimName.AddString(NCDF_DIMNAME_Y);  | 
9312  | 0  |         status =  | 
9313  | 0  |             nc_def_dim(poDS->cdfid, NCDF_DIMNAME_Y, nYSize, &(poDS->nYDimID));  | 
9314  | 0  |         NCDF_ERR(status);  | 
9315  | 0  |         CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d", | 
9316  | 0  |                  poDS->cdfid, NCDF_DIMNAME_Y, nYSize, poDS->nYDimID);  | 
9317  | 0  |     }  | 
9318  |  | 
  | 
9319  | 0  |     return poDS;  | 
9320  | 0  | }  | 
9321  |  |  | 
9322  |  | /************************************************************************/  | 
9323  |  | /*                            Create()                                  */  | 
9324  |  | /************************************************************************/  | 
9325  |  |  | 
9326  |  | GDALDataset *netCDFDataset::Create(const char *pszFilename, int nXSize,  | 
9327  |  |                                    int nYSize, int nBandsIn, GDALDataType eType,  | 
9328  |  |                                    char **papszOptions)  | 
9329  | 0  | { | 
9330  | 0  |     CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::Create(%s, ...)", | 
9331  | 0  |              pszFilename);  | 
9332  |  | 
  | 
9333  | 0  |     const char *legacyCreationOp =  | 
9334  | 0  |         CSLFetchNameValueDef(papszOptions, "GEOMETRY_ENCODING", "CF_1.8");  | 
9335  | 0  |     std::string legacyCreationOp_s = std::string(legacyCreationOp);  | 
9336  |  |  | 
9337  |  |     // Check legacy creation op FIRST  | 
9338  |  | 
  | 
9339  | 0  |     bool legacyCreateMode = false;  | 
9340  |  | 
  | 
9341  | 0  |     if (nXSize != 0 || nYSize != 0 || nBandsIn != 0)  | 
9342  | 0  |     { | 
9343  | 0  |         legacyCreateMode = true;  | 
9344  | 0  |     }  | 
9345  | 0  |     else if (legacyCreationOp_s == "CF_1.8")  | 
9346  | 0  |     { | 
9347  | 0  |         legacyCreateMode = false;  | 
9348  | 0  |     }  | 
9349  |  |  | 
9350  | 0  |     else if (legacyCreationOp_s == "WKT")  | 
9351  | 0  |     { | 
9352  | 0  |         legacyCreateMode = true;  | 
9353  | 0  |     }  | 
9354  |  |  | 
9355  | 0  |     else  | 
9356  | 0  |     { | 
9357  | 0  |         CPLError(  | 
9358  | 0  |             CE_Failure, CPLE_NotSupported,  | 
9359  | 0  |             "Dataset creation option GEOMETRY_ENCODING=%s is not supported.",  | 
9360  | 0  |             legacyCreationOp_s.c_str());  | 
9361  | 0  |         return nullptr;  | 
9362  | 0  |     }  | 
9363  |  |  | 
9364  | 0  |     CPLStringList aosOptions(CSLDuplicate(papszOptions));  | 
9365  | 0  |     if (aosOptions.FetchNameValue("FORMAT") == nullptr && | 
9366  | 0  |         (eType == GDT_UInt16 || eType == GDT_UInt32 || eType == GDT_UInt64 ||  | 
9367  | 0  |          eType == GDT_Int64))  | 
9368  | 0  |     { | 
9369  | 0  |         CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type"); | 
9370  | 0  |         aosOptions.SetNameValue("FORMAT", "NC4"); | 
9371  | 0  |     }  | 
9372  |  | 
  | 
9373  | 0  |     CPLStringList aosBandNames;  | 
9374  | 0  |     if (const char *pszBandNames = aosOptions.FetchNameValue("BAND_NAMES")) | 
9375  | 0  |     { | 
9376  | 0  |         aosBandNames =  | 
9377  | 0  |             CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);  | 
9378  |  | 
  | 
9379  | 0  |         if (aosBandNames.Count() != nBandsIn)  | 
9380  | 0  |         { | 
9381  | 0  |             CPLError(CE_Failure, CPLE_OpenFailed,  | 
9382  | 0  |                      "Attempted to create netCDF with %d bands but %d names "  | 
9383  | 0  |                      "provided in BAND_NAMES.",  | 
9384  | 0  |                      nBandsIn, aosBandNames.Count());  | 
9385  |  | 
  | 
9386  | 0  |             return nullptr;  | 
9387  | 0  |         }  | 
9388  | 0  |     }  | 
9389  |  |  | 
9390  | 0  |     CPLMutexHolderD(&hNCMutex);  | 
9391  |  | 
  | 
9392  | 0  |     auto poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize, nBandsIn,  | 
9393  | 0  |                                         aosOptions.List());  | 
9394  |  | 
  | 
9395  | 0  |     if (!poDS)  | 
9396  | 0  |         return nullptr;  | 
9397  |  |  | 
9398  | 0  |     if (!legacyCreateMode)  | 
9399  | 0  |     { | 
9400  | 0  |         poDS->bSGSupport = true;  | 
9401  | 0  |         poDS->vcdf.enableFullVirtualMode();  | 
9402  | 0  |     }  | 
9403  |  |  | 
9404  | 0  |     else  | 
9405  | 0  |     { | 
9406  | 0  |         poDS->bSGSupport = false;  | 
9407  | 0  |     }  | 
9408  |  |  | 
9409  |  |     // Should we write signed or unsigned byte?  | 
9410  |  |     // TODO should this only be done in Create()  | 
9411  | 0  |     poDS->bSignedData = true;  | 
9412  | 0  |     const char *pszValue = CSLFetchNameValueDef(papszOptions, "PIXELTYPE", "");  | 
9413  | 0  |     if (eType == GDT_Byte && !EQUAL(pszValue, "SIGNEDBYTE"))  | 
9414  | 0  |         poDS->bSignedData = false;  | 
9415  |  |  | 
9416  |  |     // Add Conventions, GDAL info and history.  | 
9417  | 0  |     if (poDS->cdfid >= 0)  | 
9418  | 0  |     { | 
9419  | 0  |         const char *CF_Vector_Conv =  | 
9420  | 0  |             poDS->bSGSupport ||  | 
9421  |  |                     // Use of variable length strings require CF-1.8  | 
9422  | 0  |                     EQUAL(aosOptions.FetchNameValueDef("FORMAT", ""), "NC4") | 
9423  | 0  |                 ? NCDF_CONVENTIONS_CF_V1_8  | 
9424  | 0  |                 : NCDF_CONVENTIONS_CF_V1_6;  | 
9425  | 0  |         poDS->bWriteGDALVersion = CPLTestBool(  | 
9426  | 0  |             CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));  | 
9427  | 0  |         poDS->bWriteGDALHistory = CPLTestBool(  | 
9428  | 0  |             CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));  | 
9429  | 0  |         NCDFAddGDALHistory(poDS->cdfid, pszFilename, poDS->bWriteGDALVersion,  | 
9430  | 0  |                            poDS->bWriteGDALHistory, "", "Create",  | 
9431  | 0  |                            (nBandsIn == 0) ? CF_Vector_Conv  | 
9432  | 0  |                                            : GDAL_DEFAULT_NCDF_CONVENTIONS);  | 
9433  | 0  |     }  | 
9434  |  |  | 
9435  |  |     // Define bands.  | 
9436  | 0  |     for (int iBand = 1; iBand <= nBandsIn; iBand++)  | 
9437  | 0  |     { | 
9438  | 0  |         const char *pszBandName =  | 
9439  | 0  |             aosBandNames.empty() ? nullptr : aosBandNames[iBand - 1];  | 
9440  |  | 
  | 
9441  | 0  |         poDS->SetBand(iBand, new netCDFRasterBand(  | 
9442  | 0  |                                  netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS,  | 
9443  | 0  |                                  eType, iBand, poDS->bSignedData, pszBandName));  | 
9444  | 0  |     }  | 
9445  |  | 
  | 
9446  | 0  |     CPLDebug("GDAL_netCDF", "netCDFDataset::Create(%s, ...) done", pszFilename); | 
9447  |  |     // Return same dataset.  | 
9448  | 0  |     return poDS;  | 
9449  | 0  | }  | 
9450  |  |  | 
9451  |  | template <class T>  | 
9452  |  | static CPLErr NCDFCopyBand(GDALRasterBand *poSrcBand, GDALRasterBand *poDstBand,  | 
9453  |  |                            int nXSize, int nYSize, GDALProgressFunc pfnProgress,  | 
9454  |  |                            void *pProgressData)  | 
9455  | 0  | { | 
9456  | 0  |     GDALDataType eDT = poSrcBand->GetRasterDataType();  | 
9457  | 0  |     CPLErr eErr = CE_None;  | 
9458  | 0  |     T *patScanline = static_cast<T *>(CPLMalloc(nXSize * sizeof(T)));  | 
9459  |  | 
  | 
9460  | 0  |     for (int iLine = 0; iLine < nYSize && eErr == CE_None; iLine++)  | 
9461  | 0  |     { | 
9462  | 0  |         eErr = poSrcBand->RasterIO(GF_Read, 0, iLine, nXSize, 1, patScanline,  | 
9463  | 0  |                                    nXSize, 1, eDT, 0, 0, nullptr);  | 
9464  | 0  |         if (eErr != CE_None)  | 
9465  | 0  |         { | 
9466  | 0  |             CPLDebug(  | 
9467  | 0  |                 "GDAL_netCDF",  | 
9468  | 0  |                 "NCDFCopyBand(), poSrcBand->RasterIO() returned error code %d",  | 
9469  | 0  |                 eErr);  | 
9470  | 0  |         }  | 
9471  | 0  |         else  | 
9472  | 0  |         { | 
9473  | 0  |             eErr =  | 
9474  | 0  |                 poDstBand->RasterIO(GF_Write, 0, iLine, nXSize, 1, patScanline,  | 
9475  | 0  |                                     nXSize, 1, eDT, 0, 0, nullptr);  | 
9476  | 0  |             if (eErr != CE_None)  | 
9477  | 0  |                 CPLDebug("GDAL_netCDF", | 
9478  | 0  |                          "NCDFCopyBand(), poDstBand->RasterIO() returned error "  | 
9479  | 0  |                          "code %d",  | 
9480  | 0  |                          eErr);  | 
9481  | 0  |         }  | 
9482  |  | 
  | 
9483  | 0  |         if (nYSize > 10 && (iLine % (nYSize / 10) == 1))  | 
9484  | 0  |         { | 
9485  | 0  |             if (!pfnProgress(1.0 * iLine / nYSize, nullptr, pProgressData))  | 
9486  | 0  |             { | 
9487  | 0  |                 eErr = CE_Failure;  | 
9488  | 0  |                 CPLError(CE_Failure, CPLE_UserInterrupt,  | 
9489  | 0  |                          "User terminated CreateCopy()");  | 
9490  | 0  |             }  | 
9491  | 0  |         }  | 
9492  | 0  |     }  | 
9493  |  | 
  | 
9494  | 0  |     CPLFree(patScanline);  | 
9495  |  | 
  | 
9496  | 0  |     pfnProgress(1.0, nullptr, pProgressData);  | 
9497  |  | 
  | 
9498  | 0  |     return eErr;  | 
9499  | 0  | } Unexecuted instantiation: netcdfdataset.cpp:CPLErr NCDFCopyBand<unsigned char>(GDALRasterBand*, GDALRasterBand*, int, int, int (*)(double, char const*, void*), void*) Unexecuted instantiation: netcdfdataset.cpp:CPLErr NCDFCopyBand<signed char>(GDALRasterBand*, GDALRasterBand*, int, int, int (*)(double, char const*, void*), void*) Unexecuted instantiation: netcdfdataset.cpp:CPLErr NCDFCopyBand<short>(GDALRasterBand*, GDALRasterBand*, int, int, int (*)(double, char const*, void*), void*) Unexecuted instantiation: netcdfdataset.cpp:CPLErr NCDFCopyBand<unsigned short>(GDALRasterBand*, GDALRasterBand*, int, int, int (*)(double, char const*, void*), void*) Unexecuted instantiation: netcdfdataset.cpp:CPLErr NCDFCopyBand<unsigned int>(GDALRasterBand*, GDALRasterBand*, int, int, int (*)(double, char const*, void*), void*) Unexecuted instantiation: netcdfdataset.cpp:CPLErr NCDFCopyBand<int>(GDALRasterBand*, GDALRasterBand*, int, int, int (*)(double, char const*, void*), void*) Unexecuted instantiation: netcdfdataset.cpp:CPLErr NCDFCopyBand<unsigned long>(GDALRasterBand*, GDALRasterBand*, int, int, int (*)(double, char const*, void*), void*) Unexecuted instantiation: netcdfdataset.cpp:CPLErr NCDFCopyBand<long>(GDALRasterBand*, GDALRasterBand*, int, int, int (*)(double, char const*, void*), void*) Unexecuted instantiation: netcdfdataset.cpp:CPLErr NCDFCopyBand<float>(GDALRasterBand*, GDALRasterBand*, int, int, int (*)(double, char const*, void*), void*) Unexecuted instantiation: netcdfdataset.cpp:CPLErr NCDFCopyBand<double>(GDALRasterBand*, GDALRasterBand*, int, int, int (*)(double, char const*, void*), void*)  | 
9500  |  |  | 
9501  |  | /************************************************************************/  | 
9502  |  | /*                            CreateCopy()                              */  | 
9503  |  | /************************************************************************/  | 
9504  |  |  | 
9505  |  | GDALDataset *  | 
9506  |  | netCDFDataset::CreateCopy(const char *pszFilename, GDALDataset *poSrcDS,  | 
9507  |  |                           CPL_UNUSED int bStrict, char **papszOptions,  | 
9508  |  |                           GDALProgressFunc pfnProgress, void *pProgressData)  | 
9509  | 0  | { | 
9510  | 0  |     CPLMutexHolderD(&hNCMutex);  | 
9511  |  | 
  | 
9512  | 0  |     CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::CreateCopy(%s, ...)", | 
9513  | 0  |              pszFilename);  | 
9514  |  | 
  | 
9515  | 0  |     if (poSrcDS->GetRootGroup())  | 
9516  | 0  |     { | 
9517  | 0  |         auto poDrv = GDALDriver::FromHandle(GDALGetDriverByName("netCDF")); | 
9518  | 0  |         if (poDrv)  | 
9519  | 0  |         { | 
9520  | 0  |             return poDrv->DefaultCreateCopy(pszFilename, poSrcDS, bStrict,  | 
9521  | 0  |                                             papszOptions, pfnProgress,  | 
9522  | 0  |                                             pProgressData);  | 
9523  | 0  |         }  | 
9524  | 0  |     }  | 
9525  |  |  | 
9526  | 0  |     const int nBands = poSrcDS->GetRasterCount();  | 
9527  | 0  |     const int nXSize = poSrcDS->GetRasterXSize();  | 
9528  | 0  |     const int nYSize = poSrcDS->GetRasterYSize();  | 
9529  | 0  |     const char *pszWKT = poSrcDS->GetProjectionRef();  | 
9530  |  |  | 
9531  |  |     // Check input bands for errors.  | 
9532  | 0  |     if (nBands == 0)  | 
9533  | 0  |     { | 
9534  | 0  |         CPLError(CE_Failure, CPLE_NotSupported,  | 
9535  | 0  |                  "NetCDF driver does not support "  | 
9536  | 0  |                  "source dataset with zero band.");  | 
9537  | 0  |         return nullptr;  | 
9538  | 0  |     }  | 
9539  |  |  | 
9540  | 0  |     GDALDataType eDT = GDT_Unknown;  | 
9541  | 0  |     GDALRasterBand *poSrcBand = nullptr;  | 
9542  | 0  |     for (int iBand = 1; iBand <= nBands; iBand++)  | 
9543  | 0  |     { | 
9544  | 0  |         poSrcBand = poSrcDS->GetRasterBand(iBand);  | 
9545  | 0  |         eDT = poSrcBand->GetRasterDataType();  | 
9546  | 0  |         if (eDT == GDT_Unknown || GDALDataTypeIsComplex(eDT))  | 
9547  | 0  |         { | 
9548  | 0  |             CPLError(CE_Failure, CPLE_NotSupported,  | 
9549  | 0  |                      "NetCDF driver does not support source dataset with band "  | 
9550  | 0  |                      "of complex type.");  | 
9551  | 0  |             return nullptr;  | 
9552  | 0  |         }  | 
9553  | 0  |     }  | 
9554  |  |  | 
9555  | 0  |     CPLStringList aosBandNames;  | 
9556  | 0  |     if (const char *pszBandNames =  | 
9557  | 0  |             CSLFetchNameValue(papszOptions, "BAND_NAMES"))  | 
9558  | 0  |     { | 
9559  | 0  |         aosBandNames =  | 
9560  | 0  |             CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);  | 
9561  |  | 
  | 
9562  | 0  |         if (aosBandNames.Count() != nBands)  | 
9563  | 0  |         { | 
9564  | 0  |             CPLError(CE_Failure, CPLE_OpenFailed,  | 
9565  | 0  |                      "Attempted to create netCDF with %d bands but %d names "  | 
9566  | 0  |                      "provided in BAND_NAMES.",  | 
9567  | 0  |                      nBands, aosBandNames.Count());  | 
9568  |  | 
  | 
9569  | 0  |             return nullptr;  | 
9570  | 0  |         }  | 
9571  | 0  |     }  | 
9572  |  |  | 
9573  | 0  |     if (!pfnProgress(0.0, nullptr, pProgressData))  | 
9574  | 0  |         return nullptr;  | 
9575  |  |  | 
9576  |  |     // Same as in Create().  | 
9577  | 0  |     CPLStringList aosOptions(CSLDuplicate(papszOptions));  | 
9578  | 0  |     if (aosOptions.FetchNameValue("FORMAT") == nullptr && | 
9579  | 0  |         (eDT == GDT_UInt16 || eDT == GDT_UInt32 || eDT == GDT_UInt64 ||  | 
9580  | 0  |          eDT == GDT_Int64))  | 
9581  | 0  |     { | 
9582  | 0  |         CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type"); | 
9583  | 0  |         aosOptions.SetNameValue("FORMAT", "NC4"); | 
9584  | 0  |     }  | 
9585  | 0  |     netCDFDataset *poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize,  | 
9586  | 0  |                                                   nBands, aosOptions.List());  | 
9587  | 0  |     if (!poDS)  | 
9588  | 0  |         return nullptr;  | 
9589  |  |  | 
9590  |  |     // Copy global metadata.  | 
9591  |  |     // Add Conventions, GDAL info and history.  | 
9592  | 0  |     CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid, NC_GLOBAL, nullptr);  | 
9593  | 0  |     const bool bWriteGDALVersion = CPLTestBool(  | 
9594  | 0  |         CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));  | 
9595  | 0  |     const bool bWriteGDALHistory = CPLTestBool(  | 
9596  | 0  |         CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));  | 
9597  | 0  |     NCDFAddGDALHistory(  | 
9598  | 0  |         poDS->cdfid, pszFilename, bWriteGDALVersion, bWriteGDALHistory,  | 
9599  | 0  |         poSrcDS->GetMetadataItem("NC_GLOBAL#history"), "CreateCopy", | 
9600  | 0  |         poSrcDS->GetMetadataItem("NC_GLOBAL#Conventions")); | 
9601  |  | 
  | 
9602  | 0  |     pfnProgress(0.1, nullptr, pProgressData);  | 
9603  |  |  | 
9604  |  |     // Check for extra dimensions.  | 
9605  | 0  |     int nDim = 2;  | 
9606  | 0  |     char **papszExtraDimNames =  | 
9607  | 0  |         NCDFTokenizeArray(poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", "")); | 
9608  | 0  |     char **papszExtraDimValues = nullptr;  | 
9609  |  | 
  | 
9610  | 0  |     if (papszExtraDimNames != nullptr && CSLCount(papszExtraDimNames) > 0)  | 
9611  | 0  |     { | 
9612  | 0  |         size_t nDimSizeTot = 1;  | 
9613  |  |         // first make sure dimensions lengths compatible with band count  | 
9614  |  |         // for( int i=0; i<CSLCount(papszExtraDimNames ); i++ ) { | 
9615  | 0  |         for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--)  | 
9616  | 0  |         { | 
9617  | 0  |             char szTemp[NC_MAX_NAME + 32 + 1];  | 
9618  | 0  |             snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",  | 
9619  | 0  |                      papszExtraDimNames[i]);  | 
9620  | 0  |             papszExtraDimValues =  | 
9621  | 0  |                 NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));  | 
9622  | 0  |             const size_t nDimSize = atol(papszExtraDimValues[0]);  | 
9623  | 0  |             CSLDestroy(papszExtraDimValues);  | 
9624  | 0  |             nDimSizeTot *= nDimSize;  | 
9625  | 0  |         }  | 
9626  | 0  |         if (nDimSizeTot == (size_t)nBands)  | 
9627  | 0  |         { | 
9628  | 0  |             nDim = 2 + CSLCount(papszExtraDimNames);  | 
9629  | 0  |         }  | 
9630  | 0  |         else  | 
9631  | 0  |         { | 
9632  |  |             // if nBands != #bands computed raise a warning  | 
9633  |  |             // just issue a debug message, because it was probably intentional  | 
9634  | 0  |             CPLDebug("GDAL_netCDF", | 
9635  | 0  |                      "Warning: Number of bands (%d) is not compatible with "  | 
9636  | 0  |                      "dimensions "  | 
9637  | 0  |                      "(total=%ld names=%s)",  | 
9638  | 0  |                      nBands, (long)nDimSizeTot,  | 
9639  | 0  |                      poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", "")); | 
9640  | 0  |             CSLDestroy(papszExtraDimNames);  | 
9641  | 0  |             papszExtraDimNames = nullptr;  | 
9642  | 0  |         }  | 
9643  | 0  |     }  | 
9644  |  | 
  | 
9645  | 0  |     int *panDimIds = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));  | 
9646  | 0  |     int *panBandDimPos = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));  | 
9647  |  | 
  | 
9648  | 0  |     nc_type nVarType;  | 
9649  | 0  |     int *panBandZLev = nullptr;  | 
9650  | 0  |     int *panDimVarIds = nullptr;  | 
9651  |  | 
  | 
9652  | 0  |     if (nDim > 2)  | 
9653  | 0  |     { | 
9654  | 0  |         panBandZLev = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));  | 
9655  | 0  |         panDimVarIds = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));  | 
9656  |  |  | 
9657  |  |         // Define all dims.  | 
9658  | 0  |         for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--)  | 
9659  | 0  |         { | 
9660  | 0  |             poDS->papszDimName.AddString(papszExtraDimNames[i]);  | 
9661  | 0  |             char szTemp[NC_MAX_NAME + 32 + 1];  | 
9662  | 0  |             snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",  | 
9663  | 0  |                      papszExtraDimNames[i]);  | 
9664  | 0  |             papszExtraDimValues =  | 
9665  | 0  |                 NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));  | 
9666  | 0  |             const int nDimSize = papszExtraDimValues && papszExtraDimValues[0]  | 
9667  | 0  |                                      ? atoi(papszExtraDimValues[0])  | 
9668  | 0  |                                      : 0;  | 
9669  |  |             // nc_type is an enum in netcdf-3, needs casting.  | 
9670  | 0  |             nVarType = static_cast<nc_type>(papszExtraDimValues &&  | 
9671  | 0  |                                                     papszExtraDimValues[0] &&  | 
9672  | 0  |                                                     papszExtraDimValues[1]  | 
9673  | 0  |                                                 ? atol(papszExtraDimValues[1])  | 
9674  | 0  |                                                 : 0);  | 
9675  | 0  |             CSLDestroy(papszExtraDimValues);  | 
9676  | 0  |             panBandZLev[i] = nDimSize;  | 
9677  | 0  |             panBandDimPos[i + 2] = i;  // Save Position of ZDim.  | 
9678  |  |  | 
9679  |  |             // Define dim.  | 
9680  | 0  |             int status = nc_def_dim(poDS->cdfid, papszExtraDimNames[i],  | 
9681  | 0  |                                     nDimSize, &(panDimIds[i]));  | 
9682  | 0  |             NCDF_ERR(status);  | 
9683  |  |  | 
9684  |  |             // Define dim var.  | 
9685  | 0  |             int anDim[1] = {panDimIds[i]}; | 
9686  | 0  |             status = nc_def_var(poDS->cdfid, papszExtraDimNames[i], nVarType, 1,  | 
9687  | 0  |                                 anDim, &(panDimVarIds[i]));  | 
9688  | 0  |             NCDF_ERR(status);  | 
9689  |  |  | 
9690  |  |             // Add dim metadata, using global var# items.  | 
9691  | 0  |             snprintf(szTemp, sizeof(szTemp), "%s#", papszExtraDimNames[i]);  | 
9692  | 0  |             CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid,  | 
9693  | 0  |                          panDimVarIds[i], szTemp);  | 
9694  | 0  |         }  | 
9695  | 0  |     }  | 
9696  |  |  | 
9697  |  |     // Copy GeoTransform and Projection.  | 
9698  |  |  | 
9699  |  |     // Copy geolocation info.  | 
9700  | 0  |     char **papszGeolocationInfo = poSrcDS->GetMetadata("GEOLOCATION"); | 
9701  | 0  |     if (papszGeolocationInfo != nullptr)  | 
9702  | 0  |         poDS->GDALPamDataset::SetMetadata(papszGeolocationInfo, "GEOLOCATION");  | 
9703  |  |  | 
9704  |  |     // Copy geotransform.  | 
9705  | 0  |     bool bGotGeoTransform = false;  | 
9706  | 0  |     double adfGeoTransform[6];  | 
9707  | 0  |     CPLErr eErr = poSrcDS->GetGeoTransform(adfGeoTransform);  | 
9708  | 0  |     if (eErr == CE_None)  | 
9709  | 0  |     { | 
9710  | 0  |         poDS->SetGeoTransform(adfGeoTransform);  | 
9711  |  |         // Disable AddProjectionVars() from being called.  | 
9712  | 0  |         bGotGeoTransform = true;  | 
9713  | 0  |         poDS->m_bHasGeoTransform = false;  | 
9714  | 0  |     }  | 
9715  |  |  | 
9716  |  |     // Copy projection.  | 
9717  | 0  |     void *pScaledProgress = nullptr;  | 
9718  | 0  |     if (bGotGeoTransform || (pszWKT && pszWKT[0] != 0))  | 
9719  | 0  |     { | 
9720  | 0  |         poDS->SetProjection(pszWKT ? pszWKT : "");  | 
9721  |  |  | 
9722  |  |         // Now we can call AddProjectionVars() directly.  | 
9723  | 0  |         poDS->m_bHasGeoTransform = bGotGeoTransform;  | 
9724  | 0  |         poDS->AddProjectionVars(true, nullptr, nullptr);  | 
9725  | 0  |         pScaledProgress =  | 
9726  | 0  |             GDALCreateScaledProgress(0.1, 0.25, pfnProgress, pProgressData);  | 
9727  | 0  |         poDS->AddProjectionVars(false, GDALScaledProgress, pScaledProgress);  | 
9728  | 0  |         GDALDestroyScaledProgress(pScaledProgress);  | 
9729  | 0  |     }  | 
9730  | 0  |     else  | 
9731  | 0  |     { | 
9732  | 0  |         poDS->bBottomUp =  | 
9733  | 0  |             CPL_TO_BOOL(CSLFetchBoolean(papszOptions, "WRITE_BOTTOMUP", TRUE));  | 
9734  | 0  |         if (papszGeolocationInfo)  | 
9735  | 0  |         { | 
9736  | 0  |             poDS->AddProjectionVars(true, nullptr, nullptr);  | 
9737  | 0  |             poDS->AddProjectionVars(false, nullptr, nullptr);  | 
9738  | 0  |         }  | 
9739  | 0  |     }  | 
9740  |  |  | 
9741  |  |     // Save X,Y dim positions.  | 
9742  | 0  |     panDimIds[nDim - 1] = poDS->nXDimID;  | 
9743  | 0  |     panBandDimPos[0] = nDim - 1;  | 
9744  | 0  |     panDimIds[nDim - 2] = poDS->nYDimID;  | 
9745  | 0  |     panBandDimPos[1] = nDim - 2;  | 
9746  |  |  | 
9747  |  |     // Write extra dim values - after projection for optimization.  | 
9748  | 0  |     if (nDim > 2)  | 
9749  | 0  |     { | 
9750  |  |         // Make sure we are in data mode.  | 
9751  | 0  |         static_cast<netCDFDataset *>(poDS)->SetDefineMode(false);  | 
9752  | 0  |         for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--)  | 
9753  | 0  |         { | 
9754  | 0  |             char szTemp[NC_MAX_NAME + 32 + 1];  | 
9755  | 0  |             snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_VALUES",  | 
9756  | 0  |                      papszExtraDimNames[i]);  | 
9757  | 0  |             if (poSrcDS->GetMetadataItem(szTemp) != nullptr)  | 
9758  | 0  |             { | 
9759  | 0  |                 NCDFPut1DVar(poDS->cdfid, panDimVarIds[i],  | 
9760  | 0  |                              poSrcDS->GetMetadataItem(szTemp));  | 
9761  | 0  |             }  | 
9762  | 0  |         }  | 
9763  | 0  |     }  | 
9764  |  | 
  | 
9765  | 0  |     pfnProgress(0.25, nullptr, pProgressData);  | 
9766  |  |  | 
9767  |  |     // Define Bands.  | 
9768  | 0  |     netCDFRasterBand *poBand = nullptr;  | 
9769  | 0  |     int nBandID = -1;  | 
9770  |  | 
  | 
9771  | 0  |     for (int iBand = 1; iBand <= nBands; iBand++)  | 
9772  | 0  |     { | 
9773  | 0  |         CPLDebug("GDAL_netCDF", "creating band # %d/%d nDim = %d", iBand, | 
9774  | 0  |                  nBands, nDim);  | 
9775  |  | 
  | 
9776  | 0  |         poSrcBand = poSrcDS->GetRasterBand(iBand);  | 
9777  | 0  |         eDT = poSrcBand->GetRasterDataType();  | 
9778  |  |  | 
9779  |  |         // Get var name from NETCDF_VARNAME.  | 
9780  | 0  |         const char *pszNETCDF_VARNAME =  | 
9781  | 0  |             poSrcBand->GetMetadataItem("NETCDF_VARNAME"); | 
9782  | 0  |         char szBandName[NC_MAX_NAME + 1];  | 
9783  | 0  |         if (!aosBandNames.empty())  | 
9784  | 0  |         { | 
9785  | 0  |             snprintf(szBandName, sizeof(szBandName), "%s",  | 
9786  | 0  |                      aosBandNames[iBand - 1]);  | 
9787  | 0  |         }  | 
9788  | 0  |         else if (pszNETCDF_VARNAME)  | 
9789  | 0  |         { | 
9790  | 0  |             if (nBands > 1 && papszExtraDimNames == nullptr)  | 
9791  | 0  |                 snprintf(szBandName, sizeof(szBandName), "%s%d",  | 
9792  | 0  |                          pszNETCDF_VARNAME, iBand);  | 
9793  | 0  |             else  | 
9794  | 0  |                 snprintf(szBandName, sizeof(szBandName), "%s",  | 
9795  | 0  |                          pszNETCDF_VARNAME);  | 
9796  | 0  |         }  | 
9797  | 0  |         else  | 
9798  | 0  |         { | 
9799  | 0  |             szBandName[0] = '\0';  | 
9800  | 0  |         }  | 
9801  |  |  | 
9802  |  |         // Get long_name from <var>#long_name.  | 
9803  | 0  |         const char *pszLongName = "";  | 
9804  | 0  |         if (pszNETCDF_VARNAME)  | 
9805  | 0  |         { | 
9806  | 0  |             pszLongName =  | 
9807  | 0  |                 poSrcDS->GetMetadataItem(std::string(pszNETCDF_VARNAME)  | 
9808  | 0  |                                              .append("#") | 
9809  | 0  |                                              .append(CF_LNG_NAME)  | 
9810  | 0  |                                              .c_str());  | 
9811  | 0  |             if (!pszLongName)  | 
9812  | 0  |                 pszLongName = "";  | 
9813  | 0  |         }  | 
9814  |  | 
  | 
9815  | 0  |         constexpr bool bSignedData = false;  | 
9816  |  | 
  | 
9817  | 0  |         if (nDim > 2)  | 
9818  | 0  |             poBand = new netCDFRasterBand(  | 
9819  | 0  |                 netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,  | 
9820  | 0  |                 bSignedData, szBandName, pszLongName, nBandID, nDim, iBand - 1,  | 
9821  | 0  |                 panBandZLev, panBandDimPos, panDimIds);  | 
9822  | 0  |         else  | 
9823  | 0  |             poBand = new netCDFRasterBand(  | 
9824  | 0  |                 netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,  | 
9825  | 0  |                 bSignedData, szBandName, pszLongName);  | 
9826  |  | 
  | 
9827  | 0  |         poDS->SetBand(iBand, poBand);  | 
9828  |  |  | 
9829  |  |         // Set nodata value, if any.  | 
9830  | 0  |         GDALCopyNoDataValue(poBand, poSrcBand);  | 
9831  |  |  | 
9832  |  |         // Copy Metadata for band.  | 
9833  | 0  |         CopyMetadata(nullptr, poSrcDS->GetRasterBand(iBand), poBand,  | 
9834  | 0  |                      poDS->cdfid, poBand->nZId);  | 
9835  |  |  | 
9836  |  |         // If more than 2D pass the first band's netcdf var ID to subsequent  | 
9837  |  |         // bands.  | 
9838  | 0  |         if (nDim > 2)  | 
9839  | 0  |             nBandID = poBand->nZId;  | 
9840  | 0  |     }  | 
9841  |  |  | 
9842  |  |     // Write projection variable to band variable.  | 
9843  | 0  |     poDS->AddGridMappingRef();  | 
9844  |  | 
  | 
9845  | 0  |     pfnProgress(0.5, nullptr, pProgressData);  | 
9846  |  |  | 
9847  |  |     // Write bands.  | 
9848  |  |  | 
9849  |  |     // Make sure we are in data mode.  | 
9850  | 0  |     poDS->SetDefineMode(false);  | 
9851  |  | 
  | 
9852  | 0  |     double dfTemp = 0.5;  | 
9853  |  | 
  | 
9854  | 0  |     eErr = CE_None;  | 
9855  |  | 
  | 
9856  | 0  |     for (int iBand = 1; iBand <= nBands && eErr == CE_None; iBand++)  | 
9857  | 0  |     { | 
9858  | 0  |         const double dfTemp2 = dfTemp + 0.4 / nBands;  | 
9859  | 0  |         pScaledProgress = GDALCreateScaledProgress(dfTemp, dfTemp2, pfnProgress,  | 
9860  | 0  |                                                    pProgressData);  | 
9861  | 0  |         dfTemp = dfTemp2;  | 
9862  |  | 
  | 
9863  | 0  |         CPLDebug("GDAL_netCDF", "copying band data # %d/%d ", iBand, nBands); | 
9864  |  | 
  | 
9865  | 0  |         poSrcBand = poSrcDS->GetRasterBand(iBand);  | 
9866  | 0  |         eDT = poSrcBand->GetRasterDataType();  | 
9867  |  | 
  | 
9868  | 0  |         GDALRasterBand *poDstBand = poDS->GetRasterBand(iBand);  | 
9869  |  |  | 
9870  |  |         // Copy band data.  | 
9871  | 0  |         if (eDT == GDT_Byte)  | 
9872  | 0  |         { | 
9873  | 0  |             CPLDebug("GDAL_netCDF", "GByte Band#%d", iBand); | 
9874  | 0  |             eErr = NCDFCopyBand<GByte>(poSrcBand, poDstBand, nXSize, nYSize,  | 
9875  | 0  |                                        GDALScaledProgress, pScaledProgress);  | 
9876  | 0  |         }  | 
9877  | 0  |         else if (eDT == GDT_Int8)  | 
9878  | 0  |         { | 
9879  | 0  |             CPLDebug("GDAL_netCDF", "GInt8 Band#%d", iBand); | 
9880  | 0  |             eErr = NCDFCopyBand<GInt8>(poSrcBand, poDstBand, nXSize, nYSize,  | 
9881  | 0  |                                        GDALScaledProgress, pScaledProgress);  | 
9882  | 0  |         }  | 
9883  | 0  |         else if (eDT == GDT_UInt16)  | 
9884  | 0  |         { | 
9885  | 0  |             CPLDebug("GDAL_netCDF", "GUInt16 Band#%d", iBand); | 
9886  | 0  |             eErr = NCDFCopyBand<GInt16>(poSrcBand, poDstBand, nXSize, nYSize,  | 
9887  | 0  |                                         GDALScaledProgress, pScaledProgress);  | 
9888  | 0  |         }  | 
9889  | 0  |         else if (eDT == GDT_Int16)  | 
9890  | 0  |         { | 
9891  | 0  |             CPLDebug("GDAL_netCDF", "GInt16 Band#%d", iBand); | 
9892  | 0  |             eErr = NCDFCopyBand<GUInt16>(poSrcBand, poDstBand, nXSize, nYSize,  | 
9893  | 0  |                                          GDALScaledProgress, pScaledProgress);  | 
9894  | 0  |         }  | 
9895  | 0  |         else if (eDT == GDT_UInt32)  | 
9896  | 0  |         { | 
9897  | 0  |             CPLDebug("GDAL_netCDF", "GUInt32 Band#%d", iBand); | 
9898  | 0  |             eErr = NCDFCopyBand<GUInt32>(poSrcBand, poDstBand, nXSize, nYSize,  | 
9899  | 0  |                                          GDALScaledProgress, pScaledProgress);  | 
9900  | 0  |         }  | 
9901  | 0  |         else if (eDT == GDT_Int32)  | 
9902  | 0  |         { | 
9903  | 0  |             CPLDebug("GDAL_netCDF", "GInt32 Band#%d", iBand); | 
9904  | 0  |             eErr = NCDFCopyBand<GInt32>(poSrcBand, poDstBand, nXSize, nYSize,  | 
9905  | 0  |                                         GDALScaledProgress, pScaledProgress);  | 
9906  | 0  |         }  | 
9907  | 0  |         else if (eDT == GDT_UInt64)  | 
9908  | 0  |         { | 
9909  | 0  |             CPLDebug("GDAL_netCDF", "GUInt64 Band#%d", iBand); | 
9910  | 0  |             eErr = NCDFCopyBand<std::uint64_t>(poSrcBand, poDstBand, nXSize,  | 
9911  | 0  |                                                nYSize, GDALScaledProgress,  | 
9912  | 0  |                                                pScaledProgress);  | 
9913  | 0  |         }  | 
9914  | 0  |         else if (eDT == GDT_Int64)  | 
9915  | 0  |         { | 
9916  | 0  |             CPLDebug("GDAL_netCDF", "GInt64 Band#%d", iBand); | 
9917  | 0  |             eErr =  | 
9918  | 0  |                 NCDFCopyBand<std::int64_t>(poSrcBand, poDstBand, nXSize, nYSize,  | 
9919  | 0  |                                            GDALScaledProgress, pScaledProgress);  | 
9920  | 0  |         }  | 
9921  | 0  |         else if (eDT == GDT_Float32)  | 
9922  | 0  |         { | 
9923  | 0  |             CPLDebug("GDAL_netCDF", "float Band#%d", iBand); | 
9924  | 0  |             eErr = NCDFCopyBand<float>(poSrcBand, poDstBand, nXSize, nYSize,  | 
9925  | 0  |                                        GDALScaledProgress, pScaledProgress);  | 
9926  | 0  |         }  | 
9927  | 0  |         else if (eDT == GDT_Float64)  | 
9928  | 0  |         { | 
9929  | 0  |             CPLDebug("GDAL_netCDF", "double Band#%d", iBand); | 
9930  | 0  |             eErr = NCDFCopyBand<double>(poSrcBand, poDstBand, nXSize, nYSize,  | 
9931  | 0  |                                         GDALScaledProgress, pScaledProgress);  | 
9932  | 0  |         }  | 
9933  | 0  |         else  | 
9934  | 0  |         { | 
9935  | 0  |             CPLError(CE_Failure, CPLE_NotSupported,  | 
9936  | 0  |                      "The NetCDF driver does not support GDAL data type %d",  | 
9937  | 0  |                      eDT);  | 
9938  | 0  |         }  | 
9939  |  | 
  | 
9940  | 0  |         GDALDestroyScaledProgress(pScaledProgress);  | 
9941  | 0  |     }  | 
9942  |  | 
  | 
9943  | 0  |     delete (poDS);  | 
9944  |  | 
  | 
9945  | 0  |     CPLFree(panDimIds);  | 
9946  | 0  |     CPLFree(panBandDimPos);  | 
9947  | 0  |     CPLFree(panBandZLev);  | 
9948  | 0  |     CPLFree(panDimVarIds);  | 
9949  | 0  |     if (papszExtraDimNames)  | 
9950  | 0  |         CSLDestroy(papszExtraDimNames);  | 
9951  |  | 
  | 
9952  | 0  |     if (eErr != CE_None)  | 
9953  | 0  |         return nullptr;  | 
9954  |  |  | 
9955  | 0  |     pfnProgress(0.95, nullptr, pProgressData);  | 
9956  |  |  | 
9957  |  |     // Re-open dataset so we can return it.  | 
9958  | 0  |     CPLStringList aosOpenOptions;  | 
9959  | 0  |     aosOpenOptions.AddString("VARIABLES_AS_BANDS=YES"); | 
9960  | 0  |     GDALOpenInfo oOpenInfo(pszFilename, GA_Update);  | 
9961  | 0  |     oOpenInfo.nOpenFlags = GDAL_OF_RASTER | GDAL_OF_UPDATE;  | 
9962  | 0  |     oOpenInfo.papszOpenOptions = aosOpenOptions.List();  | 
9963  | 0  |     auto poRetDS = Open(&oOpenInfo);  | 
9964  |  |  | 
9965  |  |     // PAM cloning is disabled. See bug #4244.  | 
9966  |  |     // if( poDS )  | 
9967  |  |     //     poDS->CloneInfo(poSrcDS, GCIF_PAM_DEFAULT);  | 
9968  |  | 
  | 
9969  | 0  |     pfnProgress(1.0, nullptr, pProgressData);  | 
9970  |  | 
  | 
9971  | 0  |     return poRetDS;  | 
9972  | 0  | }  | 
9973  |  |  | 
9974  |  | // Note: some logic depends on bIsProjected and bIsGeoGraphic.  | 
9975  |  | // May not be known when Create() is called, see AddProjectionVars().  | 
9976  |  | void netCDFDataset::ProcessCreationOptions()  | 
9977  | 0  | { | 
9978  | 0  |     const char *pszConfig =  | 
9979  | 0  |         CSLFetchNameValue(papszCreationOptions, "CONFIG_FILE");  | 
9980  | 0  |     if (pszConfig != nullptr)  | 
9981  | 0  |     { | 
9982  | 0  |         if (oWriterConfig.Parse(pszConfig))  | 
9983  | 0  |         { | 
9984  |  |             // Override dataset creation options from the config file  | 
9985  | 0  |             std::map<CPLString, CPLString>::iterator oIter;  | 
9986  | 0  |             for (oIter = oWriterConfig.m_oDatasetCreationOptions.begin();  | 
9987  | 0  |                  oIter != oWriterConfig.m_oDatasetCreationOptions.end();  | 
9988  | 0  |                  ++oIter)  | 
9989  | 0  |             { | 
9990  | 0  |                 papszCreationOptions = CSLSetNameValue(  | 
9991  | 0  |                     papszCreationOptions, oIter->first, oIter->second);  | 
9992  | 0  |             }  | 
9993  | 0  |         }  | 
9994  | 0  |     }  | 
9995  |  |  | 
9996  |  |     // File format.  | 
9997  | 0  |     eFormat = NCDF_FORMAT_NC;  | 
9998  | 0  |     const char *pszValue = CSLFetchNameValue(papszCreationOptions, "FORMAT");  | 
9999  | 0  |     if (pszValue != nullptr)  | 
10000  | 0  |     { | 
10001  | 0  |         if (EQUAL(pszValue, "NC"))  | 
10002  | 0  |         { | 
10003  | 0  |             eFormat = NCDF_FORMAT_NC;  | 
10004  | 0  |         }  | 
10005  | 0  | #ifdef NETCDF_HAS_NC2  | 
10006  | 0  |         else if (EQUAL(pszValue, "NC2"))  | 
10007  | 0  |         { | 
10008  | 0  |             eFormat = NCDF_FORMAT_NC2;  | 
10009  | 0  |         }  | 
10010  | 0  | #endif  | 
10011  | 0  |         else if (EQUAL(pszValue, "NC4"))  | 
10012  | 0  |         { | 
10013  | 0  |             eFormat = NCDF_FORMAT_NC4;  | 
10014  | 0  |         }  | 
10015  | 0  |         else if (EQUAL(pszValue, "NC4C"))  | 
10016  | 0  |         { | 
10017  | 0  |             eFormat = NCDF_FORMAT_NC4C;  | 
10018  | 0  |         }  | 
10019  | 0  |         else  | 
10020  | 0  |         { | 
10021  | 0  |             CPLError(CE_Failure, CPLE_NotSupported,  | 
10022  | 0  |                      "FORMAT=%s in not supported, using the default NC format.",  | 
10023  | 0  |                      pszValue);  | 
10024  | 0  |         }  | 
10025  | 0  |     }  | 
10026  |  |  | 
10027  |  |     // COMPRESS option.  | 
10028  | 0  |     pszValue = CSLFetchNameValue(papszCreationOptions, "COMPRESS");  | 
10029  | 0  |     if (pszValue != nullptr)  | 
10030  | 0  |     { | 
10031  | 0  |         if (EQUAL(pszValue, "NONE"))  | 
10032  | 0  |         { | 
10033  | 0  |             eCompress = NCDF_COMPRESS_NONE;  | 
10034  | 0  |         }  | 
10035  | 0  |         else if (EQUAL(pszValue, "DEFLATE"))  | 
10036  | 0  |         { | 
10037  | 0  |             eCompress = NCDF_COMPRESS_DEFLATE;  | 
10038  | 0  |             if (!((eFormat == NCDF_FORMAT_NC4) ||  | 
10039  | 0  |                   (eFormat == NCDF_FORMAT_NC4C)))  | 
10040  | 0  |             { | 
10041  | 0  |                 CPLError(CE_Warning, CPLE_IllegalArg,  | 
10042  | 0  |                          "NOTICE: Format set to NC4C because compression is "  | 
10043  | 0  |                          "set to DEFLATE.");  | 
10044  | 0  |                 eFormat = NCDF_FORMAT_NC4C;  | 
10045  | 0  |             }  | 
10046  | 0  |         }  | 
10047  | 0  |         else  | 
10048  | 0  |         { | 
10049  | 0  |             CPLError(CE_Failure, CPLE_NotSupported,  | 
10050  | 0  |                      "COMPRESS=%s is not supported.", pszValue);  | 
10051  | 0  |         }  | 
10052  | 0  |     }  | 
10053  |  |  | 
10054  |  |     // ZLEVEL option.  | 
10055  | 0  |     pszValue = CSLFetchNameValue(papszCreationOptions, "ZLEVEL");  | 
10056  | 0  |     if (pszValue != nullptr)  | 
10057  | 0  |     { | 
10058  | 0  |         nZLevel = atoi(pszValue);  | 
10059  | 0  |         if (!(nZLevel >= 1 && nZLevel <= 9))  | 
10060  | 0  |         { | 
10061  | 0  |             CPLError(CE_Warning, CPLE_IllegalArg,  | 
10062  | 0  |                      "ZLEVEL=%s value not recognised, ignoring.", pszValue);  | 
10063  | 0  |             nZLevel = NCDF_DEFLATE_LEVEL;  | 
10064  | 0  |         }  | 
10065  | 0  |     }  | 
10066  |  |  | 
10067  |  |     // CHUNKING option.  | 
10068  | 0  |     bChunking =  | 
10069  | 0  |         CPL_TO_BOOL(CSLFetchBoolean(papszCreationOptions, "CHUNKING", TRUE));  | 
10070  |  |  | 
10071  |  |     // MULTIPLE_LAYERS option.  | 
10072  | 0  |     const char *pszMultipleLayerBehavior =  | 
10073  | 0  |         CSLFetchNameValueDef(papszCreationOptions, "MULTIPLE_LAYERS", "NO");  | 
10074  | 0  |     const char *pszGeometryEnc = CSLFetchNameValueDef(  | 
10075  | 0  |         papszCreationOptions, "GEOMETRY_ENCODING", "CF_1.8");  | 
10076  | 0  |     if (EQUAL(pszMultipleLayerBehavior, "NO") ||  | 
10077  | 0  |         EQUAL(pszGeometryEnc, "CF_1.8"))  | 
10078  | 0  |     { | 
10079  | 0  |         eMultipleLayerBehavior = SINGLE_LAYER;  | 
10080  | 0  |     }  | 
10081  | 0  |     else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_FILES"))  | 
10082  | 0  |     { | 
10083  | 0  |         eMultipleLayerBehavior = SEPARATE_FILES;  | 
10084  | 0  |     }  | 
10085  | 0  |     else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_GROUPS"))  | 
10086  | 0  |     { | 
10087  | 0  |         if (eFormat == NCDF_FORMAT_NC4)  | 
10088  | 0  |         { | 
10089  | 0  |             eMultipleLayerBehavior = SEPARATE_GROUPS;  | 
10090  | 0  |         }  | 
10091  | 0  |         else  | 
10092  | 0  |         { | 
10093  | 0  |             CPLError(CE_Warning, CPLE_IllegalArg,  | 
10094  | 0  |                      "MULTIPLE_LAYERS=%s is recognised only with FORMAT=NC4",  | 
10095  | 0  |                      pszMultipleLayerBehavior);  | 
10096  | 0  |         }  | 
10097  | 0  |     }  | 
10098  | 0  |     else  | 
10099  | 0  |     { | 
10100  | 0  |         CPLError(CE_Warning, CPLE_IllegalArg,  | 
10101  | 0  |                  "MULTIPLE_LAYERS=%s not recognised", pszMultipleLayerBehavior);  | 
10102  | 0  |     }  | 
10103  |  |  | 
10104  |  |     // Set nCreateMode based on eFormat.  | 
10105  | 0  |     switch (eFormat)  | 
10106  | 0  |     { | 
10107  | 0  | #ifdef NETCDF_HAS_NC2  | 
10108  | 0  |         case NCDF_FORMAT_NC2:  | 
10109  | 0  |             nCreateMode = NC_CLOBBER | NC_64BIT_OFFSET;  | 
10110  | 0  |             break;  | 
10111  | 0  | #endif  | 
10112  | 0  |         case NCDF_FORMAT_NC4:  | 
10113  | 0  |             nCreateMode = NC_CLOBBER | NC_NETCDF4;  | 
10114  | 0  |             break;  | 
10115  | 0  |         case NCDF_FORMAT_NC4C:  | 
10116  | 0  |             nCreateMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;  | 
10117  | 0  |             break;  | 
10118  | 0  |         case NCDF_FORMAT_NC:  | 
10119  | 0  |         default:  | 
10120  | 0  |             nCreateMode = NC_CLOBBER;  | 
10121  | 0  |             break;  | 
10122  | 0  |     }  | 
10123  |  |  | 
10124  | 0  |     CPLDebug("GDAL_netCDF", "file options: format=%d compress=%d zlevel=%d", | 
10125  | 0  |              eFormat, eCompress, nZLevel);  | 
10126  | 0  | }  | 
10127  |  |  | 
10128  |  | int netCDFDataset::DefVarDeflate(int nVarId, bool bChunkingArg)  | 
10129  | 0  | { | 
10130  | 0  |     if (eCompress == NCDF_COMPRESS_DEFLATE)  | 
10131  | 0  |     { | 
10132  |  |         // Must set chunk size to avoid huge performance hit (set  | 
10133  |  |         // bChunkingArg=TRUE)  | 
10134  |  |         // perhaps another solution it to change the chunk cache?  | 
10135  |  |         // http://www.unidata.ucar.edu/software/netcdf/docs/netcdf.html#Chunk-Cache  | 
10136  |  |         // TODO: make sure this is okay.  | 
10137  | 0  |         CPLDebug("GDAL_netCDF", "DefVarDeflate(%d, %d) nZlevel=%d", nVarId, | 
10138  | 0  |                  static_cast<int>(bChunkingArg), nZLevel);  | 
10139  |  | 
  | 
10140  | 0  |         int status = nc_def_var_deflate(cdfid, nVarId, 1, 1, nZLevel);  | 
10141  | 0  |         NCDF_ERR(status);  | 
10142  |  | 
  | 
10143  | 0  |         if (status == NC_NOERR && bChunkingArg && bChunking)  | 
10144  | 0  |         { | 
10145  |  |             // set chunking to be 1 for all dims, except X dim  | 
10146  |  |             // size_t chunksize[] = { 1, (size_t)nRasterXSize }; | 
10147  | 0  |             size_t chunksize[MAX_NC_DIMS];  | 
10148  | 0  |             int nd;  | 
10149  | 0  |             nc_inq_varndims(cdfid, nVarId, &nd);  | 
10150  | 0  |             chunksize[0] = (size_t)1;  | 
10151  | 0  |             chunksize[1] = (size_t)1;  | 
10152  | 0  |             for (int i = 2; i < nd; i++)  | 
10153  | 0  |                 chunksize[i] = (size_t)1;  | 
10154  | 0  |             chunksize[nd - 1] = (size_t)nRasterXSize;  | 
10155  |  |  | 
10156  |  |             // Config options just for testing purposes  | 
10157  | 0  |             const char *pszBlockXSize =  | 
10158  | 0  |                 CPLGetConfigOption("BLOCKXSIZE", nullptr); | 
10159  | 0  |             if (pszBlockXSize)  | 
10160  | 0  |                 chunksize[nd - 1] = (size_t)atoi(pszBlockXSize);  | 
10161  |  | 
  | 
10162  | 0  |             const char *pszBlockYSize =  | 
10163  | 0  |                 CPLGetConfigOption("BLOCKYSIZE", nullptr); | 
10164  | 0  |             if (nd >= 2 && pszBlockYSize)  | 
10165  | 0  |                 chunksize[nd - 2] = (size_t)atoi(pszBlockYSize);  | 
10166  |  | 
  | 
10167  | 0  |             CPLDebug("GDAL_netCDF", | 
10168  | 0  |                      "DefVarDeflate() chunksize={%ld, %ld} chunkX=%ld nd=%d", | 
10169  | 0  |                      (long)chunksize[0], (long)chunksize[1],  | 
10170  | 0  |                      (long)chunksize[nd - 1], nd);  | 
10171  |  | #ifdef NCDF_DEBUG  | 
10172  |  |             for (int i = 0; i < nd; i++)  | 
10173  |  |                 CPLDebug("GDAL_netCDF", "DefVarDeflate() chunk[%d]=%ld", i, | 
10174  |  |                          chunksize[i]);  | 
10175  |  | #endif  | 
10176  |  | 
  | 
10177  | 0  |             status = nc_def_var_chunking(cdfid, nVarId, NC_CHUNKED, chunksize);  | 
10178  | 0  |             NCDF_ERR(status);  | 
10179  | 0  |         }  | 
10180  | 0  |         else  | 
10181  | 0  |         { | 
10182  | 0  |             CPLDebug("GDAL_netCDF", "chunksize not set"); | 
10183  | 0  |         }  | 
10184  | 0  |         return status;  | 
10185  | 0  |     }  | 
10186  | 0  |     return NC_NOERR;  | 
10187  | 0  | }  | 
10188  |  |  | 
10189  |  | /************************************************************************/  | 
10190  |  | /*                           NCDFUnloadDriver()                         */  | 
10191  |  | /************************************************************************/  | 
10192  |  |  | 
10193  |  | static void NCDFUnloadDriver(CPL_UNUSED GDALDriver *poDriver)  | 
10194  | 0  | { | 
10195  | 0  |     if (hNCMutex != nullptr)  | 
10196  | 0  |         CPLDestroyMutex(hNCMutex);  | 
10197  | 0  |     hNCMutex = nullptr;  | 
10198  | 0  | }  | 
10199  |  |  | 
10200  |  | /************************************************************************/  | 
10201  |  | /*                          GDALRegister_netCDF()                       */  | 
10202  |  | /************************************************************************/  | 
10203  |  |  | 
10204  |  | class GDALnetCDFDriver final : public GDALDriver  | 
10205  |  | { | 
10206  |  |   public:  | 
10207  | 2  |     GDALnetCDFDriver() = default;  | 
10208  |  |  | 
10209  |  |     const char *GetMetadataItem(const char *pszName,  | 
10210  |  |                                 const char *pszDomain) override;  | 
10211  |  |  | 
10212  |  |     char **GetMetadata(const char *pszDomain) override  | 
10213  | 0  |     { | 
10214  | 0  |         std::lock_guard oLock(m_oMutex);  | 
10215  | 0  |         InitializeDCAPVirtualIO();  | 
10216  | 0  |         return GDALDriver::GetMetadata(pszDomain);  | 
10217  | 0  |     }  | 
10218  |  |  | 
10219  |  |   private:  | 
10220  |  |     std::mutex m_oMutex{}; | 
10221  |  |     bool m_bInitialized = false;  | 
10222  |  |  | 
10223  |  |     void InitializeDCAPVirtualIO()  | 
10224  | 0  |     { | 
10225  | 0  |         if (!m_bInitialized)  | 
10226  | 0  |         { | 
10227  | 0  |             m_bInitialized = true;  | 
10228  |  | 
  | 
10229  | 0  | #ifdef ENABLE_UFFD  | 
10230  | 0  |             if (CPLIsUserFaultMappingSupported())  | 
10231  | 0  |             { | 
10232  | 0  |                 SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");  | 
10233  | 0  |             }  | 
10234  | 0  | #endif  | 
10235  | 0  |         }  | 
10236  | 0  |     }  | 
10237  |  | };  | 
10238  |  |  | 
10239  |  | const char *GDALnetCDFDriver::GetMetadataItem(const char *pszName,  | 
10240  |  |                                               const char *pszDomain)  | 
10241  | 989k  | { | 
10242  | 989k  |     std::lock_guard oLock(m_oMutex);  | 
10243  | 989k  |     if (EQUAL(pszName, GDAL_DCAP_VIRTUALIO))  | 
10244  | 0  |     { | 
10245  | 0  |         InitializeDCAPVirtualIO();  | 
10246  | 0  |     }  | 
10247  | 989k  |     return GDALDriver::GetMetadataItem(pszName, pszDomain);  | 
10248  | 989k  | }  | 
10249  |  |  | 
10250  |  | void GDALRegister_netCDF()  | 
10251  |  |  | 
10252  | 2  | { | 
10253  | 2  |     if (!GDAL_CHECK_VERSION("netCDF driver")) | 
10254  | 0  |         return;  | 
10255  |  |  | 
10256  | 2  |     if (GDALGetDriverByName(DRIVER_NAME) != nullptr)  | 
10257  | 0  |         return;  | 
10258  |  |  | 
10259  | 2  |     GDALDriver *poDriver = new GDALnetCDFDriver();  | 
10260  | 2  |     netCDFDriverSetCommonMetadata(poDriver);  | 
10261  |  |  | 
10262  | 2  |     poDriver->SetMetadataItem("NETCDF_CONVENTIONS", | 
10263  | 2  |                               GDAL_DEFAULT_NCDF_CONVENTIONS);  | 
10264  | 2  |     poDriver->SetMetadataItem("NETCDF_VERSION", nc_inq_libvers()); | 
10265  |  |  | 
10266  |  |     // Set pfns and register driver.  | 
10267  | 2  |     poDriver->pfnOpen = netCDFDataset::Open;  | 
10268  | 2  |     poDriver->pfnCreateCopy = netCDFDataset::CreateCopy;  | 
10269  | 2  |     poDriver->pfnCreate = netCDFDataset::Create;  | 
10270  | 2  |     poDriver->pfnCreateMultiDimensional = netCDFDataset::CreateMultiDimensional;  | 
10271  | 2  |     poDriver->pfnUnloadDriver = NCDFUnloadDriver;  | 
10272  |  |  | 
10273  | 2  |     GetGDALDriverManager()->RegisterDriver(poDriver);  | 
10274  | 2  | }  | 
10275  |  |  | 
10276  |  | /************************************************************************/  | 
10277  |  | /*                          New functions                               */  | 
10278  |  | /************************************************************************/  | 
10279  |  |  | 
10280  |  | /* Test for GDAL version string >= target */  | 
10281  |  | static bool NCDFIsGDALVersionGTE(const char *pszVersion, int nTarget)  | 
10282  | 0  | { | 
10283  |  |  | 
10284  |  |     // Valid strings are "GDAL 1.9dev, released 2011/01/18" and "GDAL 1.8.1 ".  | 
10285  | 0  |     if (pszVersion == nullptr || EQUAL(pszVersion, ""))  | 
10286  | 0  |         return false;  | 
10287  | 0  |     else if (!STARTS_WITH_CI(pszVersion, "GDAL "))  | 
10288  | 0  |         return false;  | 
10289  |  |     // 2.0dev of 2011/12/29 has been later renamed as 1.10dev.  | 
10290  | 0  |     else if (EQUAL("GDAL 2.0dev, released 2011/12/29", pszVersion)) | 
10291  | 0  |         return nTarget <= GDAL_COMPUTE_VERSION(1, 10, 0);  | 
10292  | 0  |     else if (STARTS_WITH_CI(pszVersion, "GDAL 1.9dev"))  | 
10293  | 0  |         return nTarget <= 1900;  | 
10294  | 0  |     else if (STARTS_WITH_CI(pszVersion, "GDAL 1.8dev"))  | 
10295  | 0  |         return nTarget <= 1800;  | 
10296  |  |  | 
10297  | 0  |     char **papszTokens = CSLTokenizeString2(pszVersion + 5, ".", 0);  | 
10298  |  | 
  | 
10299  | 0  |     int nVersions[] = {0, 0, 0, 0}; | 
10300  | 0  |     for (int iToken = 0; papszTokens && iToken < 4 && papszTokens[iToken];  | 
10301  | 0  |          iToken++)  | 
10302  | 0  |     { | 
10303  | 0  |         nVersions[iToken] = atoi(papszTokens[iToken]);  | 
10304  | 0  |         if (nVersions[iToken] < 0)  | 
10305  | 0  |             nVersions[iToken] = 0;  | 
10306  | 0  |         else if (nVersions[iToken] > 99)  | 
10307  | 0  |             nVersions[iToken] = 99;  | 
10308  | 0  |     }  | 
10309  |  | 
  | 
10310  | 0  |     int nVersion = 0;  | 
10311  | 0  |     if (nVersions[0] > 1 || nVersions[1] >= 10)  | 
10312  | 0  |         nVersion =  | 
10313  | 0  |             GDAL_COMPUTE_VERSION(nVersions[0], nVersions[1], nVersions[2]);  | 
10314  | 0  |     else  | 
10315  | 0  |         nVersion = nVersions[0] * 1000 + nVersions[1] * 100 +  | 
10316  | 0  |                    nVersions[2] * 10 + nVersions[3];  | 
10317  |  | 
  | 
10318  | 0  |     CSLDestroy(papszTokens);  | 
10319  | 0  |     return nTarget <= nVersion;  | 
10320  | 0  | }  | 
10321  |  |  | 
10322  |  | // Add Conventions, GDAL version and history.  | 
10323  |  | static void NCDFAddGDALHistory(int fpImage, const char *pszFilename,  | 
10324  |  |                                bool bWriteGDALVersion, bool bWriteGDALHistory,  | 
10325  |  |                                const char *pszOldHist,  | 
10326  |  |                                const char *pszFunctionName,  | 
10327  |  |                                const char *pszCFVersion)  | 
10328  | 0  | { | 
10329  | 0  |     if (pszCFVersion == nullptr)  | 
10330  | 0  |     { | 
10331  | 0  |         pszCFVersion = GDAL_DEFAULT_NCDF_CONVENTIONS;  | 
10332  | 0  |     }  | 
10333  | 0  |     int status = nc_put_att_text(fpImage, NC_GLOBAL, "Conventions",  | 
10334  | 0  |                                  strlen(pszCFVersion), pszCFVersion);  | 
10335  | 0  |     NCDF_ERR(status);  | 
10336  |  | 
  | 
10337  | 0  |     if (bWriteGDALVersion)  | 
10338  | 0  |     { | 
10339  | 0  |         const char *pszNCDF_GDAL = GDALVersionInfo("--version"); | 
10340  | 0  |         status = nc_put_att_text(fpImage, NC_GLOBAL, "GDAL",  | 
10341  | 0  |                                  strlen(pszNCDF_GDAL), pszNCDF_GDAL);  | 
10342  | 0  |         NCDF_ERR(status);  | 
10343  | 0  |     }  | 
10344  |  | 
  | 
10345  | 0  |     if (bWriteGDALHistory)  | 
10346  | 0  |     { | 
10347  |  |         // Add history.  | 
10348  | 0  |         CPLString osTmp;  | 
10349  |  | #ifdef GDAL_SET_CMD_LINE_DEFINED_TMP  | 
10350  |  |         if (!EQUAL(GDALGetCmdLine(), ""))  | 
10351  |  |             osTmp = GDALGetCmdLine();  | 
10352  |  |         else  | 
10353  |  |             osTmp =  | 
10354  |  |                 CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename); | 
10355  |  | #else  | 
10356  | 0  |         osTmp = CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename); | 
10357  | 0  | #endif  | 
10358  |  | 
  | 
10359  | 0  |         NCDFAddHistory(fpImage, osTmp.c_str(), pszOldHist);  | 
10360  | 0  |     }  | 
10361  | 0  |     else if (pszOldHist != nullptr)  | 
10362  | 0  |     { | 
10363  | 0  |         status = nc_put_att_text(fpImage, NC_GLOBAL, "history",  | 
10364  | 0  |                                  strlen(pszOldHist), pszOldHist);  | 
10365  | 0  |         NCDF_ERR(status);  | 
10366  | 0  |     }  | 
10367  | 0  | }  | 
10368  |  |  | 
10369  |  | // Code taken from cdo and libcdi, used for writing the history attribute.  | 
10370  |  |  | 
10371  |  | // void cdoDefHistory(int fileID, char *histstring)  | 
10372  |  | static void NCDFAddHistory(int fpImage, const char *pszAddHist,  | 
10373  |  |                            const char *pszOldHist)  | 
10374  | 0  | { | 
10375  |  |     // Check pszOldHist - as if there was no previous history, it will be  | 
10376  |  |     // a null pointer - if so set as empty.  | 
10377  | 0  |     if (nullptr == pszOldHist)  | 
10378  | 0  |     { | 
10379  | 0  |         pszOldHist = "";  | 
10380  | 0  |     }  | 
10381  |  | 
  | 
10382  | 0  |     char strtime[32];  | 
10383  | 0  |     strtime[0] = '\0';  | 
10384  |  | 
  | 
10385  | 0  |     time_t tp = time(nullptr);  | 
10386  | 0  |     if (tp != -1)  | 
10387  | 0  |     { | 
10388  | 0  |         struct tm ltime;  | 
10389  | 0  |         VSILocalTime(&tp, <ime);  | 
10390  | 0  |         (void)strftime(strtime, sizeof(strtime),  | 
10391  | 0  |                        "%a %b %d %H:%M:%S %Y: ", <ime);  | 
10392  | 0  |     }  | 
10393  |  |  | 
10394  |  |     // status = nc_get_att_text(fpImage, NC_GLOBAL,  | 
10395  |  |     //                           "history", pszOldHist);  | 
10396  |  |     // printf("status: %d pszOldHist: [%s]\n",status,pszOldHist); | 
10397  |  | 
  | 
10398  | 0  |     size_t nNewHistSize =  | 
10399  | 0  |         strlen(pszOldHist) + strlen(strtime) + strlen(pszAddHist) + 1 + 1;  | 
10400  | 0  |     char *pszNewHist =  | 
10401  | 0  |         static_cast<char *>(CPLMalloc(nNewHistSize * sizeof(char)));  | 
10402  |  | 
  | 
10403  | 0  |     strcpy(pszNewHist, strtime);  | 
10404  | 0  |     strcat(pszNewHist, pszAddHist);  | 
10405  |  |  | 
10406  |  |     // int disableHistory = FALSE;  | 
10407  |  |     // if( !disableHistory )  | 
10408  | 0  |     { | 
10409  | 0  |         if (!EQUAL(pszOldHist, ""))  | 
10410  | 0  |             strcat(pszNewHist, "\n");  | 
10411  | 0  |         strcat(pszNewHist, pszOldHist);  | 
10412  | 0  |     }  | 
10413  |  | 
  | 
10414  | 0  |     const int status = nc_put_att_text(fpImage, NC_GLOBAL, "history",  | 
10415  | 0  |                                        strlen(pszNewHist), pszNewHist);  | 
10416  | 0  |     NCDF_ERR(status);  | 
10417  |  | 
  | 
10418  | 0  |     CPLFree(pszNewHist);  | 
10419  | 0  | }  | 
10420  |  |  | 
10421  |  | static CPLErr NCDFSafeStrcat(char **ppszDest, const char *pszSrc,  | 
10422  |  |                              size_t *nDestSize)  | 
10423  | 1.79k  | { | 
10424  |  |     /* Reallocate the data string until the content fits */  | 
10425  | 1.79k  |     while (*nDestSize < (strlen(*ppszDest) + strlen(pszSrc) + 1))  | 
10426  | 0  |     { | 
10427  | 0  |         (*nDestSize) *= 2;  | 
10428  | 0  |         *ppszDest = static_cast<char *>(  | 
10429  | 0  |             CPLRealloc(reinterpret_cast<void *>(*ppszDest), *nDestSize));  | 
10430  |  | #ifdef NCDF_DEBUG  | 
10431  |  |         CPLDebug("GDAL_netCDF", "NCDFSafeStrcat() resized str from %ld to %ld", | 
10432  |  |                  (*nDestSize) / 2, *nDestSize);  | 
10433  |  | #endif  | 
10434  | 0  |     }  | 
10435  | 1.79k  |     strcat(*ppszDest, pszSrc);  | 
10436  |  |  | 
10437  | 1.79k  |     return CE_None;  | 
10438  | 1.79k  | }  | 
10439  |  |  | 
10440  |  | /* helper function for NCDFGetAttr() */  | 
10441  |  | /* if pdfValue != nullptr, sets *pdfValue to first value returned */  | 
10442  |  | /* if ppszValue != nullptr, sets *ppszValue with all attribute values */  | 
10443  |  | /* *ppszValue is the responsibility of the caller and must be freed */  | 
10444  |  | static CPLErr NCDFGetAttr1(int nCdfId, int nVarId, const char *pszAttrName,  | 
10445  |  |                            double *pdfValue, char **ppszValue)  | 
10446  | 17.1k  | { | 
10447  | 17.1k  |     nc_type nAttrType = NC_NAT;  | 
10448  | 17.1k  |     size_t nAttrLen = 0;  | 
10449  |  |  | 
10450  | 17.1k  |     if (ppszValue)  | 
10451  | 17.1k  |         *ppszValue = nullptr;  | 
10452  |  |  | 
10453  | 17.1k  |     int status = nc_inq_att(nCdfId, nVarId, pszAttrName, &nAttrType, &nAttrLen);  | 
10454  | 17.1k  |     if (status != NC_NOERR)  | 
10455  | 14.8k  |         return CE_Failure;  | 
10456  |  |  | 
10457  |  | #ifdef NCDF_DEBUG  | 
10458  |  |     CPLDebug("GDAL_netCDF", "NCDFGetAttr1(%s) len=%ld type=%d", pszAttrName, | 
10459  |  |              nAttrLen, nAttrType);  | 
10460  |  | #endif  | 
10461  | 2.30k  |     if (nAttrLen == 0 && nAttrType != NC_CHAR)  | 
10462  | 0  |         return CE_Failure;  | 
10463  |  |  | 
10464  |  |     /* Allocate guaranteed minimum size (use 10 or 20 if not a string) */  | 
10465  | 2.30k  |     size_t nAttrValueSize = nAttrLen + 1;  | 
10466  | 2.30k  |     if (nAttrType != NC_CHAR && nAttrValueSize < 10)  | 
10467  | 1.79k  |         nAttrValueSize = 10;  | 
10468  | 2.30k  |     if (nAttrType == NC_DOUBLE && nAttrValueSize < 20)  | 
10469  | 138  |         nAttrValueSize = 20;  | 
10470  | 2.30k  |     if (nAttrType == NC_INT64 && nAttrValueSize < 20)  | 
10471  | 0  |         nAttrValueSize = 22;  | 
10472  | 2.30k  |     char *pszAttrValue =  | 
10473  | 2.30k  |         static_cast<char *>(CPLCalloc(nAttrValueSize, sizeof(char)));  | 
10474  | 2.30k  |     *pszAttrValue = '\0';  | 
10475  |  |  | 
10476  | 2.30k  |     if (nAttrLen > 1 && nAttrType != NC_CHAR)  | 
10477  | 0  |         NCDFSafeStrcat(&pszAttrValue, "{", &nAttrValueSize); | 
10478  |  |  | 
10479  | 2.30k  |     double dfValue = 0.0;  | 
10480  | 2.30k  |     size_t m = 0;  | 
10481  | 2.30k  |     char szTemp[256];  | 
10482  | 2.30k  |     bool bSetDoubleFromStr = false;  | 
10483  |  |  | 
10484  | 2.30k  |     switch (nAttrType)  | 
10485  | 2.30k  |     { | 
10486  | 509  |         case NC_CHAR:  | 
10487  | 509  |             CPL_IGNORE_RET_VAL(  | 
10488  | 509  |                 nc_get_att_text(nCdfId, nVarId, pszAttrName, pszAttrValue));  | 
10489  | 509  |             pszAttrValue[nAttrLen] = '\0';  | 
10490  | 509  |             bSetDoubleFromStr = true;  | 
10491  | 509  |             dfValue = 0.0;  | 
10492  | 509  |             break;  | 
10493  | 1  |         case NC_BYTE:  | 
10494  | 1  |         { | 
10495  | 1  |             signed char *pscTemp = static_cast<signed char *>(  | 
10496  | 1  |                 CPLCalloc(nAttrLen, sizeof(signed char)));  | 
10497  | 1  |             nc_get_att_schar(nCdfId, nVarId, pszAttrName, pscTemp);  | 
10498  | 1  |             dfValue = static_cast<double>(pscTemp[0]);  | 
10499  | 1  |             if (nAttrLen > 1)  | 
10500  | 0  |             { | 
10501  | 0  |                 for (m = 0; m < nAttrLen - 1; m++)  | 
10502  | 0  |                 { | 
10503  | 0  |                     snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);  | 
10504  | 0  |                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);  | 
10505  | 0  |                 }  | 
10506  | 0  |             }  | 
10507  | 1  |             snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);  | 
10508  | 1  |             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);  | 
10509  | 1  |             CPLFree(pscTemp);  | 
10510  | 1  |             break;  | 
10511  | 0  |         }  | 
10512  | 0  |         case NC_SHORT:  | 
10513  | 0  |         { | 
10514  | 0  |             short *psTemp =  | 
10515  | 0  |                 static_cast<short *>(CPLCalloc(nAttrLen, sizeof(short)));  | 
10516  | 0  |             nc_get_att_short(nCdfId, nVarId, pszAttrName, psTemp);  | 
10517  | 0  |             dfValue = static_cast<double>(psTemp[0]);  | 
10518  | 0  |             if (nAttrLen > 1)  | 
10519  | 0  |             { | 
10520  | 0  |                 for (m = 0; m < nAttrLen - 1; m++)  | 
10521  | 0  |                 { | 
10522  | 0  |                     snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);  | 
10523  | 0  |                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);  | 
10524  | 0  |                 }  | 
10525  | 0  |             }  | 
10526  | 0  |             snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);  | 
10527  | 0  |             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);  | 
10528  | 0  |             CPLFree(psTemp);  | 
10529  | 0  |             break;  | 
10530  | 0  |         }  | 
10531  | 1.65k  |         case NC_INT:  | 
10532  | 1.65k  |         { | 
10533  | 1.65k  |             int *pnTemp = static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));  | 
10534  | 1.65k  |             nc_get_att_int(nCdfId, nVarId, pszAttrName, pnTemp);  | 
10535  | 1.65k  |             dfValue = static_cast<double>(pnTemp[0]);  | 
10536  | 1.65k  |             if (nAttrLen > 1)  | 
10537  | 0  |             { | 
10538  | 0  |                 for (m = 0; m < nAttrLen - 1; m++)  | 
10539  | 0  |                 { | 
10540  | 0  |                     snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);  | 
10541  | 0  |                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);  | 
10542  | 0  |                 }  | 
10543  | 0  |             }  | 
10544  | 1.65k  |             snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);  | 
10545  | 1.65k  |             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);  | 
10546  | 1.65k  |             CPLFree(pnTemp);  | 
10547  | 1.65k  |             break;  | 
10548  | 0  |         }  | 
10549  | 0  |         case NC_FLOAT:  | 
10550  | 0  |         { | 
10551  | 0  |             float *pfTemp =  | 
10552  | 0  |                 static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));  | 
10553  | 0  |             nc_get_att_float(nCdfId, nVarId, pszAttrName, pfTemp);  | 
10554  | 0  |             dfValue = static_cast<double>(pfTemp[0]);  | 
10555  | 0  |             if (nAttrLen > 1)  | 
10556  | 0  |             { | 
10557  | 0  |                 for (m = 0; m < nAttrLen - 1; m++)  | 
10558  | 0  |                 { | 
10559  | 0  |                     CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);  | 
10560  | 0  |                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);  | 
10561  | 0  |                 }  | 
10562  | 0  |             }  | 
10563  | 0  |             CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);  | 
10564  | 0  |             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);  | 
10565  | 0  |             CPLFree(pfTemp);  | 
10566  | 0  |             break;  | 
10567  | 0  |         }  | 
10568  | 138  |         case NC_DOUBLE:  | 
10569  | 138  |         { | 
10570  | 138  |             double *pdfTemp =  | 
10571  | 138  |                 static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));  | 
10572  | 138  |             nc_get_att_double(nCdfId, nVarId, pszAttrName, pdfTemp);  | 
10573  | 138  |             dfValue = pdfTemp[0];  | 
10574  | 138  |             if (nAttrLen > 1)  | 
10575  | 0  |             { | 
10576  | 0  |                 for (m = 0; m < nAttrLen - 1; m++)  | 
10577  | 0  |                 { | 
10578  | 0  |                     CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);  | 
10579  | 0  |                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);  | 
10580  | 0  |                 }  | 
10581  | 0  |             }  | 
10582  | 138  |             CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);  | 
10583  | 138  |             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);  | 
10584  | 138  |             CPLFree(pdfTemp);  | 
10585  | 138  |             break;  | 
10586  | 0  |         }  | 
10587  | 0  |         case NC_STRING:  | 
10588  | 0  |         { | 
10589  | 0  |             char **ppszTemp =  | 
10590  | 0  |                 static_cast<char **>(CPLCalloc(nAttrLen, sizeof(char *)));  | 
10591  | 0  |             nc_get_att_string(nCdfId, nVarId, pszAttrName, ppszTemp);  | 
10592  | 0  |             bSetDoubleFromStr = true;  | 
10593  | 0  |             dfValue = 0.0;  | 
10594  | 0  |             if (nAttrLen > 1)  | 
10595  | 0  |             { | 
10596  | 0  |                 for (m = 0; m < nAttrLen - 1; m++)  | 
10597  | 0  |                 { | 
10598  | 0  |                     NCDFSafeStrcat(&pszAttrValue,  | 
10599  | 0  |                                    ppszTemp[m] ? ppszTemp[m] : "{NULL}", | 
10600  | 0  |                                    &nAttrValueSize);  | 
10601  | 0  |                     NCDFSafeStrcat(&pszAttrValue, ",", &nAttrValueSize);  | 
10602  | 0  |                 }  | 
10603  | 0  |             }  | 
10604  | 0  |             NCDFSafeStrcat(&pszAttrValue, ppszTemp[m] ? ppszTemp[m] : "{NULL}", | 
10605  | 0  |                            &nAttrValueSize);  | 
10606  | 0  |             nc_free_string(nAttrLen, ppszTemp);  | 
10607  | 0  |             CPLFree(ppszTemp);  | 
10608  | 0  |             break;  | 
10609  | 0  |         }  | 
10610  | 0  |         case NC_UBYTE:  | 
10611  | 0  |         { | 
10612  | 0  |             unsigned char *pucTemp = static_cast<unsigned char *>(  | 
10613  | 0  |                 CPLCalloc(nAttrLen, sizeof(unsigned char)));  | 
10614  | 0  |             nc_get_att_uchar(nCdfId, nVarId, pszAttrName, pucTemp);  | 
10615  | 0  |             dfValue = static_cast<double>(pucTemp[0]);  | 
10616  | 0  |             if (nAttrLen > 1)  | 
10617  | 0  |             { | 
10618  | 0  |                 for (m = 0; m < nAttrLen - 1; m++)  | 
10619  | 0  |                 { | 
10620  | 0  |                     CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);  | 
10621  | 0  |                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);  | 
10622  | 0  |                 }  | 
10623  | 0  |             }  | 
10624  | 0  |             CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);  | 
10625  | 0  |             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);  | 
10626  | 0  |             CPLFree(pucTemp);  | 
10627  | 0  |             break;  | 
10628  | 0  |         }  | 
10629  | 0  |         case NC_USHORT:  | 
10630  | 0  |         { | 
10631  | 0  |             unsigned short *pusTemp;  | 
10632  | 0  |             pusTemp = static_cast<unsigned short *>(  | 
10633  | 0  |                 CPLCalloc(nAttrLen, sizeof(unsigned short)));  | 
10634  | 0  |             nc_get_att_ushort(nCdfId, nVarId, pszAttrName, pusTemp);  | 
10635  | 0  |             dfValue = static_cast<double>(pusTemp[0]);  | 
10636  | 0  |             if (nAttrLen > 1)  | 
10637  | 0  |             { | 
10638  | 0  |                 for (m = 0; m < nAttrLen - 1; m++)  | 
10639  | 0  |                 { | 
10640  | 0  |                     CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);  | 
10641  | 0  |                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);  | 
10642  | 0  |                 }  | 
10643  | 0  |             }  | 
10644  | 0  |             CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);  | 
10645  | 0  |             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);  | 
10646  | 0  |             CPLFree(pusTemp);  | 
10647  | 0  |             break;  | 
10648  | 0  |         }  | 
10649  | 0  |         case NC_UINT:  | 
10650  | 0  |         { | 
10651  | 0  |             unsigned int *punTemp =  | 
10652  | 0  |                 static_cast<unsigned int *>(CPLCalloc(nAttrLen, sizeof(int)));  | 
10653  | 0  |             nc_get_att_uint(nCdfId, nVarId, pszAttrName, punTemp);  | 
10654  | 0  |             dfValue = static_cast<double>(punTemp[0]);  | 
10655  | 0  |             if (nAttrLen > 1)  | 
10656  | 0  |             { | 
10657  | 0  |                 for (m = 0; m < nAttrLen - 1; m++)  | 
10658  | 0  |                 { | 
10659  | 0  |                     CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);  | 
10660  | 0  |                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);  | 
10661  | 0  |                 }  | 
10662  | 0  |             }  | 
10663  | 0  |             CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);  | 
10664  | 0  |             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);  | 
10665  | 0  |             CPLFree(punTemp);  | 
10666  | 0  |             break;  | 
10667  | 0  |         }  | 
10668  | 0  |         case NC_INT64:  | 
10669  | 0  |         { | 
10670  | 0  |             GIntBig *panTemp =  | 
10671  | 0  |                 static_cast<GIntBig *>(CPLCalloc(nAttrLen, sizeof(GIntBig)));  | 
10672  | 0  |             nc_get_att_longlong(nCdfId, nVarId, pszAttrName, panTemp);  | 
10673  | 0  |             dfValue = static_cast<double>(panTemp[0]);  | 
10674  | 0  |             if (nAttrLen > 1)  | 
10675  | 0  |             { | 
10676  | 0  |                 for (m = 0; m < nAttrLen - 1; m++)  | 
10677  | 0  |                 { | 
10678  | 0  |                     CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",",  | 
10679  | 0  |                                 panTemp[m]);  | 
10680  | 0  |                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);  | 
10681  | 0  |                 }  | 
10682  | 0  |             }  | 
10683  | 0  |             CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, panTemp[m]);  | 
10684  | 0  |             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);  | 
10685  | 0  |             CPLFree(panTemp);  | 
10686  | 0  |             break;  | 
10687  | 0  |         }  | 
10688  | 0  |         case NC_UINT64:  | 
10689  | 0  |         { | 
10690  | 0  |             GUIntBig *panTemp =  | 
10691  | 0  |                 static_cast<GUIntBig *>(CPLCalloc(nAttrLen, sizeof(GUIntBig)));  | 
10692  | 0  |             nc_get_att_ulonglong(nCdfId, nVarId, pszAttrName, panTemp);  | 
10693  | 0  |             dfValue = static_cast<double>(panTemp[0]);  | 
10694  | 0  |             if (nAttrLen > 1)  | 
10695  | 0  |             { | 
10696  | 0  |                 for (m = 0; m < nAttrLen - 1; m++)  | 
10697  | 0  |                 { | 
10698  | 0  |                     CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",",  | 
10699  | 0  |                                 panTemp[m]);  | 
10700  | 0  |                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);  | 
10701  | 0  |                 }  | 
10702  | 0  |             }  | 
10703  | 0  |             CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, panTemp[m]);  | 
10704  | 0  |             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);  | 
10705  | 0  |             CPLFree(panTemp);  | 
10706  | 0  |             break;  | 
10707  | 0  |         }  | 
10708  | 0  |         default:  | 
10709  | 0  |             CPLDebug("GDAL_netCDF", | 
10710  | 0  |                      "NCDFGetAttr unsupported type %d for attribute %s",  | 
10711  | 0  |                      nAttrType, pszAttrName);  | 
10712  | 0  |             break;  | 
10713  | 2.30k  |     }  | 
10714  |  |  | 
10715  | 2.30k  |     if (nAttrLen > 1 && nAttrType != NC_CHAR)  | 
10716  | 0  |         NCDFSafeStrcat(&pszAttrValue, "}", &nAttrValueSize);  | 
10717  |  |  | 
10718  | 2.30k  |     if (bSetDoubleFromStr)  | 
10719  | 509  |     { | 
10720  | 509  |         if (CPLGetValueType(pszAttrValue) == CPL_VALUE_STRING)  | 
10721  | 509  |         { | 
10722  | 509  |             if (ppszValue == nullptr && pdfValue != nullptr)  | 
10723  | 0  |             { | 
10724  | 0  |                 CPLFree(pszAttrValue);  | 
10725  | 0  |                 return CE_Failure;  | 
10726  | 0  |             }  | 
10727  | 509  |         }  | 
10728  | 509  |         dfValue = CPLAtof(pszAttrValue);  | 
10729  | 509  |     }  | 
10730  |  |  | 
10731  |  |     /* set return values */  | 
10732  | 2.30k  |     if (ppszValue)  | 
10733  | 2.30k  |         *ppszValue = pszAttrValue;  | 
10734  | 0  |     else  | 
10735  | 0  |         CPLFree(pszAttrValue);  | 
10736  |  |  | 
10737  | 2.30k  |     if (pdfValue)  | 
10738  | 0  |         *pdfValue = dfValue;  | 
10739  |  |  | 
10740  | 2.30k  |     return CE_None;  | 
10741  | 2.30k  | }  | 
10742  |  |  | 
10743  |  | /* sets pdfValue to first value found */  | 
10744  |  | CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,  | 
10745  |  |                    double *pdfValue)  | 
10746  | 4  | { | 
10747  | 4  |     return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, pdfValue, nullptr);  | 
10748  | 4  | }  | 
10749  |  |  | 
10750  |  | /* pszValue is the responsibility of the caller and must be freed */  | 
10751  |  | CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,  | 
10752  |  |                    char **pszValue)  | 
10753  | 17.1k  | { | 
10754  | 17.1k  |     return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, nullptr, pszValue);  | 
10755  | 17.1k  | }  | 
10756  |  |  | 
10757  |  | /* By default write NC_CHAR, but detect for int/float/double and */  | 
10758  |  | /* NC4 string arrays */  | 
10759  |  | static CPLErr NCDFPutAttr(int nCdfId, int nVarId, const char *pszAttrName,  | 
10760  |  |                           const char *pszValue)  | 
10761  | 0  | { | 
10762  | 0  |     int status = 0;  | 
10763  | 0  |     char *pszTemp = nullptr;  | 
10764  |  |  | 
10765  |  |     /* get the attribute values as tokens */  | 
10766  | 0  |     char **papszValues = NCDFTokenizeArray(pszValue);  | 
10767  | 0  |     if (papszValues == nullptr)  | 
10768  | 0  |         return CE_Failure;  | 
10769  |  |  | 
10770  | 0  |     size_t nAttrLen = CSLCount(papszValues);  | 
10771  |  |  | 
10772  |  |     /* first detect type */  | 
10773  | 0  |     nc_type nAttrType = NC_CHAR;  | 
10774  | 0  |     nc_type nTmpAttrType = NC_CHAR;  | 
10775  | 0  |     for (size_t i = 0; i < nAttrLen; i++)  | 
10776  | 0  |     { | 
10777  | 0  |         nTmpAttrType = NC_CHAR;  | 
10778  | 0  |         bool bFoundType = false;  | 
10779  | 0  |         errno = 0;  | 
10780  | 0  |         int nValue = static_cast<int>(strtol(papszValues[i], &pszTemp, 10));  | 
10781  |  |         /* test for int */  | 
10782  |  |         /* TODO test for Byte and short - can this be done safely? */  | 
10783  | 0  |         if (errno == 0 && papszValues[i] != pszTemp && *pszTemp == 0)  | 
10784  | 0  |         { | 
10785  | 0  |             char szTemp[256];  | 
10786  | 0  |             CPLsnprintf(szTemp, sizeof(szTemp), "%d", nValue);  | 
10787  | 0  |             if (EQUAL(szTemp, papszValues[i]))  | 
10788  | 0  |             { | 
10789  | 0  |                 bFoundType = true;  | 
10790  | 0  |                 nTmpAttrType = NC_INT;  | 
10791  | 0  |             }  | 
10792  | 0  |             else  | 
10793  | 0  |             { | 
10794  | 0  |                 unsigned int unValue = static_cast<unsigned int>(  | 
10795  | 0  |                     strtoul(papszValues[i], &pszTemp, 10));  | 
10796  | 0  |                 CPLsnprintf(szTemp, sizeof(szTemp), "%u", unValue);  | 
10797  | 0  |                 if (EQUAL(szTemp, papszValues[i]))  | 
10798  | 0  |                 { | 
10799  | 0  |                     bFoundType = true;  | 
10800  | 0  |                     nTmpAttrType = NC_UINT;  | 
10801  | 0  |                 }  | 
10802  | 0  |             }  | 
10803  | 0  |         }  | 
10804  | 0  |         if (!bFoundType)  | 
10805  | 0  |         { | 
10806  |  |             /* test for double */  | 
10807  | 0  |             errno = 0;  | 
10808  | 0  |             double dfValue = CPLStrtod(papszValues[i], &pszTemp);  | 
10809  | 0  |             if ((errno == 0) && (papszValues[i] != pszTemp) && (*pszTemp == 0))  | 
10810  | 0  |             { | 
10811  |  |                 // Test for float instead of double.  | 
10812  |  |                 // strtof() is C89, which is not available in MSVC.  | 
10813  |  |                 // See if we loose precision if we cast to float and write to  | 
10814  |  |                 // char*.  | 
10815  | 0  |                 float fValue = float(dfValue);  | 
10816  | 0  |                 char szTemp[256];  | 
10817  | 0  |                 CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", fValue);  | 
10818  | 0  |                 if (EQUAL(szTemp, papszValues[i]))  | 
10819  | 0  |                     nTmpAttrType = NC_FLOAT;  | 
10820  | 0  |                 else  | 
10821  | 0  |                     nTmpAttrType = NC_DOUBLE;  | 
10822  | 0  |             }  | 
10823  | 0  |         }  | 
10824  | 0  |         if ((nTmpAttrType <= NC_DOUBLE && nAttrType <= NC_DOUBLE &&  | 
10825  | 0  |              nTmpAttrType > nAttrType) ||  | 
10826  | 0  |             (nTmpAttrType == NC_UINT && nAttrType < NC_FLOAT) ||  | 
10827  | 0  |             (nTmpAttrType >= NC_FLOAT && nAttrType == NC_UINT))  | 
10828  | 0  |             nAttrType = nTmpAttrType;  | 
10829  | 0  |     }  | 
10830  |  | 
  | 
10831  |  | #ifdef DEBUG  | 
10832  |  |     if (EQUAL(pszAttrName, "DEBUG_EMPTY_DOUBLE_ATTR"))  | 
10833  |  |     { | 
10834  |  |         nAttrType = NC_DOUBLE;  | 
10835  |  |         nAttrLen = 0;  | 
10836  |  |     }  | 
10837  |  | #endif  | 
10838  |  |  | 
10839  |  |     /* now write the data */  | 
10840  | 0  |     if (nAttrType == NC_CHAR)  | 
10841  | 0  |     { | 
10842  | 0  |         int nTmpFormat = 0;  | 
10843  | 0  |         if (nAttrLen > 1)  | 
10844  | 0  |         { | 
10845  | 0  |             status = nc_inq_format(nCdfId, &nTmpFormat);  | 
10846  | 0  |             NCDF_ERR(status);  | 
10847  | 0  |         }  | 
10848  | 0  |         if (nAttrLen > 1 && nTmpFormat == NCDF_FORMAT_NC4)  | 
10849  | 0  |             status = nc_put_att_string(nCdfId, nVarId, pszAttrName, nAttrLen,  | 
10850  | 0  |                                        const_cast<const char **>(papszValues));  | 
10851  | 0  |         else  | 
10852  | 0  |             status = nc_put_att_text(nCdfId, nVarId, pszAttrName,  | 
10853  | 0  |                                      strlen(pszValue), pszValue);  | 
10854  | 0  |         NCDF_ERR(status);  | 
10855  | 0  |     }  | 
10856  | 0  |     else  | 
10857  | 0  |     { | 
10858  | 0  |         switch (nAttrType)  | 
10859  | 0  |         { | 
10860  | 0  |             case NC_INT:  | 
10861  | 0  |             { | 
10862  | 0  |                 int *pnTemp =  | 
10863  | 0  |                     static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));  | 
10864  | 0  |                 for (size_t i = 0; i < nAttrLen; i++)  | 
10865  | 0  |                 { | 
10866  | 0  |                     pnTemp[i] =  | 
10867  | 0  |                         static_cast<int>(strtol(papszValues[i], &pszTemp, 10));  | 
10868  | 0  |                 }  | 
10869  | 0  |                 status = nc_put_att_int(nCdfId, nVarId, pszAttrName, NC_INT,  | 
10870  | 0  |                                         nAttrLen, pnTemp);  | 
10871  | 0  |                 NCDF_ERR(status);  | 
10872  | 0  |                 CPLFree(pnTemp);  | 
10873  | 0  |                 break;  | 
10874  | 0  |             }  | 
10875  | 0  |             case NC_UINT:  | 
10876  | 0  |             { | 
10877  | 0  |                 unsigned int *punTemp = static_cast<unsigned int *>(  | 
10878  | 0  |                     CPLCalloc(nAttrLen, sizeof(unsigned int)));  | 
10879  | 0  |                 for (size_t i = 0; i < nAttrLen; i++)  | 
10880  | 0  |                 { | 
10881  | 0  |                     punTemp[i] = static_cast<unsigned int>(  | 
10882  | 0  |                         strtol(papszValues[i], &pszTemp, 10));  | 
10883  | 0  |                 }  | 
10884  | 0  |                 status = nc_put_att_uint(nCdfId, nVarId, pszAttrName, NC_UINT,  | 
10885  | 0  |                                          nAttrLen, punTemp);  | 
10886  | 0  |                 NCDF_ERR(status);  | 
10887  | 0  |                 CPLFree(punTemp);  | 
10888  | 0  |                 break;  | 
10889  | 0  |             }  | 
10890  | 0  |             case NC_FLOAT:  | 
10891  | 0  |             { | 
10892  | 0  |                 float *pfTemp =  | 
10893  | 0  |                     static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));  | 
10894  | 0  |                 for (size_t i = 0; i < nAttrLen; i++)  | 
10895  | 0  |                 { | 
10896  | 0  |                     pfTemp[i] =  | 
10897  | 0  |                         static_cast<float>(CPLStrtod(papszValues[i], &pszTemp));  | 
10898  | 0  |                 }  | 
10899  | 0  |                 status = nc_put_att_float(nCdfId, nVarId, pszAttrName, NC_FLOAT,  | 
10900  | 0  |                                           nAttrLen, pfTemp);  | 
10901  | 0  |                 NCDF_ERR(status);  | 
10902  | 0  |                 CPLFree(pfTemp);  | 
10903  | 0  |                 break;  | 
10904  | 0  |             }  | 
10905  | 0  |             case NC_DOUBLE:  | 
10906  | 0  |             { | 
10907  | 0  |                 double *pdfTemp =  | 
10908  | 0  |                     static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));  | 
10909  | 0  |                 for (size_t i = 0; i < nAttrLen; i++)  | 
10910  | 0  |                 { | 
10911  | 0  |                     pdfTemp[i] = CPLStrtod(papszValues[i], &pszTemp);  | 
10912  | 0  |                 }  | 
10913  | 0  |                 status = nc_put_att_double(nCdfId, nVarId, pszAttrName,  | 
10914  | 0  |                                            NC_DOUBLE, nAttrLen, pdfTemp);  | 
10915  | 0  |                 NCDF_ERR(status);  | 
10916  | 0  |                 CPLFree(pdfTemp);  | 
10917  | 0  |                 break;  | 
10918  | 0  |             }  | 
10919  | 0  |             default:  | 
10920  | 0  |                 if (papszValues)  | 
10921  | 0  |                     CSLDestroy(papszValues);  | 
10922  | 0  |                 return CE_Failure;  | 
10923  | 0  |                 break;  | 
10924  | 0  |         }  | 
10925  | 0  |     }  | 
10926  |  |  | 
10927  | 0  |     if (papszValues)  | 
10928  | 0  |         CSLDestroy(papszValues);  | 
10929  |  | 
  | 
10930  | 0  |     return CE_None;  | 
10931  | 0  | }  | 
10932  |  |  | 
10933  |  | static CPLErr NCDFGet1DVar(int nCdfId, int nVarId, char **pszValue)  | 
10934  | 0  | { | 
10935  |  |     /* get var information */  | 
10936  | 0  |     int nVarDimId = -1;  | 
10937  | 0  |     int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);  | 
10938  | 0  |     if (status != NC_NOERR || nVarDimId != 1)  | 
10939  | 0  |         return CE_Failure;  | 
10940  |  |  | 
10941  | 0  |     status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);  | 
10942  | 0  |     if (status != NC_NOERR)  | 
10943  | 0  |         return CE_Failure;  | 
10944  |  |  | 
10945  | 0  |     nc_type nVarType = NC_NAT;  | 
10946  | 0  |     status = nc_inq_vartype(nCdfId, nVarId, &nVarType);  | 
10947  | 0  |     if (status != NC_NOERR)  | 
10948  | 0  |         return CE_Failure;  | 
10949  |  |  | 
10950  | 0  |     size_t nVarLen = 0;  | 
10951  | 0  |     status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);  | 
10952  | 0  |     if (status != NC_NOERR)  | 
10953  | 0  |         return CE_Failure;  | 
10954  |  |  | 
10955  | 0  |     size_t start[1] = {0}; | 
10956  | 0  |     size_t count[1] = {nVarLen}; | 
10957  |  |  | 
10958  |  |     /* Allocate guaranteed minimum size */  | 
10959  | 0  |     size_t nVarValueSize = NCDF_MAX_STR_LEN;  | 
10960  | 0  |     char *pszVarValue =  | 
10961  | 0  |         static_cast<char *>(CPLCalloc(nVarValueSize, sizeof(char)));  | 
10962  | 0  |     *pszVarValue = '\0';  | 
10963  |  | 
  | 
10964  | 0  |     if (nVarLen == 0)  | 
10965  | 0  |     { | 
10966  |  |         /* set return values */  | 
10967  | 0  |         *pszValue = pszVarValue;  | 
10968  |  | 
  | 
10969  | 0  |         return CE_None;  | 
10970  | 0  |     }  | 
10971  |  |  | 
10972  | 0  |     if (nVarLen > 1 && nVarType != NC_CHAR)  | 
10973  | 0  |         NCDFSafeStrcat(&pszVarValue, "{", &nVarValueSize); | 
10974  |  | 
  | 
10975  | 0  |     switch (nVarType)  | 
10976  | 0  |     { | 
10977  | 0  |         case NC_CHAR:  | 
10978  | 0  |             nc_get_vara_text(nCdfId, nVarId, start, count, pszVarValue);  | 
10979  | 0  |             pszVarValue[nVarLen] = '\0';  | 
10980  | 0  |             break;  | 
10981  | 0  |         case NC_BYTE:  | 
10982  | 0  |         { | 
10983  | 0  |             signed char *pscTemp = static_cast<signed char *>(  | 
10984  | 0  |                 CPLCalloc(nVarLen, sizeof(signed char)));  | 
10985  | 0  |             nc_get_vara_schar(nCdfId, nVarId, start, count, pscTemp);  | 
10986  | 0  |             char szTemp[256];  | 
10987  | 0  |             size_t m = 0;  | 
10988  | 0  |             for (; m < nVarLen - 1; m++)  | 
10989  | 0  |             { | 
10990  | 0  |                 snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);  | 
10991  | 0  |                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);  | 
10992  | 0  |             }  | 
10993  | 0  |             snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);  | 
10994  | 0  |             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);  | 
10995  | 0  |             CPLFree(pscTemp);  | 
10996  | 0  |             break;  | 
10997  | 0  |         }  | 
10998  | 0  |         case NC_SHORT:  | 
10999  | 0  |         { | 
11000  | 0  |             short *psTemp =  | 
11001  | 0  |                 static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));  | 
11002  | 0  |             nc_get_vara_short(nCdfId, nVarId, start, count, psTemp);  | 
11003  | 0  |             char szTemp[256];  | 
11004  | 0  |             size_t m = 0;  | 
11005  | 0  |             for (; m < nVarLen - 1; m++)  | 
11006  | 0  |             { | 
11007  | 0  |                 snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);  | 
11008  | 0  |                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);  | 
11009  | 0  |             }  | 
11010  | 0  |             snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);  | 
11011  | 0  |             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);  | 
11012  | 0  |             CPLFree(psTemp);  | 
11013  | 0  |             break;  | 
11014  | 0  |         }  | 
11015  | 0  |         case NC_INT:  | 
11016  | 0  |         { | 
11017  | 0  |             int *pnTemp = static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));  | 
11018  | 0  |             nc_get_vara_int(nCdfId, nVarId, start, count, pnTemp);  | 
11019  | 0  |             char szTemp[256];  | 
11020  | 0  |             size_t m = 0;  | 
11021  | 0  |             for (; m < nVarLen - 1; m++)  | 
11022  | 0  |             { | 
11023  | 0  |                 snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);  | 
11024  | 0  |                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);  | 
11025  | 0  |             }  | 
11026  | 0  |             snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);  | 
11027  | 0  |             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);  | 
11028  | 0  |             CPLFree(pnTemp);  | 
11029  | 0  |             break;  | 
11030  | 0  |         }  | 
11031  | 0  |         case NC_FLOAT:  | 
11032  | 0  |         { | 
11033  | 0  |             float *pfTemp =  | 
11034  | 0  |                 static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));  | 
11035  | 0  |             nc_get_vara_float(nCdfId, nVarId, start, count, pfTemp);  | 
11036  | 0  |             char szTemp[256];  | 
11037  | 0  |             size_t m = 0;  | 
11038  | 0  |             for (; m < nVarLen - 1; m++)  | 
11039  | 0  |             { | 
11040  | 0  |                 CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);  | 
11041  | 0  |                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);  | 
11042  | 0  |             }  | 
11043  | 0  |             CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);  | 
11044  | 0  |             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);  | 
11045  | 0  |             CPLFree(pfTemp);  | 
11046  | 0  |             break;  | 
11047  | 0  |         }  | 
11048  | 0  |         case NC_DOUBLE:  | 
11049  | 0  |         { | 
11050  | 0  |             double *pdfTemp =  | 
11051  | 0  |                 static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));  | 
11052  | 0  |             nc_get_vara_double(nCdfId, nVarId, start, count, pdfTemp);  | 
11053  | 0  |             char szTemp[256];  | 
11054  | 0  |             size_t m = 0;  | 
11055  | 0  |             for (; m < nVarLen - 1; m++)  | 
11056  | 0  |             { | 
11057  | 0  |                 CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);  | 
11058  | 0  |                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);  | 
11059  | 0  |             }  | 
11060  | 0  |             CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);  | 
11061  | 0  |             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);  | 
11062  | 0  |             CPLFree(pdfTemp);  | 
11063  | 0  |             break;  | 
11064  | 0  |         }  | 
11065  | 0  |         case NC_STRING:  | 
11066  | 0  |         { | 
11067  | 0  |             char **ppszTemp =  | 
11068  | 0  |                 static_cast<char **>(CPLCalloc(nVarLen, sizeof(char *)));  | 
11069  | 0  |             nc_get_vara_string(nCdfId, nVarId, start, count, ppszTemp);  | 
11070  | 0  |             size_t m = 0;  | 
11071  | 0  |             for (; m < nVarLen - 1; m++)  | 
11072  | 0  |             { | 
11073  | 0  |                 NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);  | 
11074  | 0  |                 NCDFSafeStrcat(&pszVarValue, ",", &nVarValueSize);  | 
11075  | 0  |             }  | 
11076  | 0  |             NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);  | 
11077  | 0  |             nc_free_string(nVarLen, ppszTemp);  | 
11078  | 0  |             CPLFree(ppszTemp);  | 
11079  | 0  |             break;  | 
11080  | 0  |         }  | 
11081  | 0  |         case NC_UBYTE:  | 
11082  | 0  |         { | 
11083  | 0  |             unsigned char *pucTemp;  | 
11084  | 0  |             pucTemp = static_cast<unsigned char *>(  | 
11085  | 0  |                 CPLCalloc(nVarLen, sizeof(unsigned char)));  | 
11086  | 0  |             nc_get_vara_uchar(nCdfId, nVarId, start, count, pucTemp);  | 
11087  | 0  |             char szTemp[256];  | 
11088  | 0  |             size_t m = 0;  | 
11089  | 0  |             for (; m < nVarLen - 1; m++)  | 
11090  | 0  |             { | 
11091  | 0  |                 CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);  | 
11092  | 0  |                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);  | 
11093  | 0  |             }  | 
11094  | 0  |             CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);  | 
11095  | 0  |             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);  | 
11096  | 0  |             CPLFree(pucTemp);  | 
11097  | 0  |             break;  | 
11098  | 0  |         }  | 
11099  | 0  |         case NC_USHORT:  | 
11100  | 0  |         { | 
11101  | 0  |             unsigned short *pusTemp;  | 
11102  | 0  |             pusTemp = static_cast<unsigned short *>(  | 
11103  | 0  |                 CPLCalloc(nVarLen, sizeof(unsigned short)));  | 
11104  | 0  |             nc_get_vara_ushort(nCdfId, nVarId, start, count, pusTemp);  | 
11105  | 0  |             char szTemp[256];  | 
11106  | 0  |             size_t m = 0;  | 
11107  | 0  |             for (; m < nVarLen - 1; m++)  | 
11108  | 0  |             { | 
11109  | 0  |                 CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);  | 
11110  | 0  |                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);  | 
11111  | 0  |             }  | 
11112  | 0  |             CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);  | 
11113  | 0  |             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);  | 
11114  | 0  |             CPLFree(pusTemp);  | 
11115  | 0  |             break;  | 
11116  | 0  |         }  | 
11117  | 0  |         case NC_UINT:  | 
11118  | 0  |         { | 
11119  | 0  |             unsigned int *punTemp;  | 
11120  | 0  |             punTemp = static_cast<unsigned int *>(  | 
11121  | 0  |                 CPLCalloc(nVarLen, sizeof(unsigned int)));  | 
11122  | 0  |             nc_get_vara_uint(nCdfId, nVarId, start, count, punTemp);  | 
11123  | 0  |             char szTemp[256];  | 
11124  | 0  |             size_t m = 0;  | 
11125  | 0  |             for (; m < nVarLen - 1; m++)  | 
11126  | 0  |             { | 
11127  | 0  |                 CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);  | 
11128  | 0  |                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);  | 
11129  | 0  |             }  | 
11130  | 0  |             CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);  | 
11131  | 0  |             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);  | 
11132  | 0  |             CPLFree(punTemp);  | 
11133  | 0  |             break;  | 
11134  | 0  |         }  | 
11135  | 0  |         case NC_INT64:  | 
11136  | 0  |         { | 
11137  | 0  |             long long *pnTemp =  | 
11138  | 0  |                 static_cast<long long *>(CPLCalloc(nVarLen, sizeof(long long)));  | 
11139  | 0  |             nc_get_vara_longlong(nCdfId, nVarId, start, count, pnTemp);  | 
11140  | 0  |             char szTemp[256];  | 
11141  | 0  |             size_t m = 0;  | 
11142  | 0  |             for (; m < nVarLen - 1; m++)  | 
11143  | 0  |             { | 
11144  | 0  |                 snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",", pnTemp[m]);  | 
11145  | 0  |                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);  | 
11146  | 0  |             }  | 
11147  | 0  |             snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, pnTemp[m]);  | 
11148  | 0  |             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);  | 
11149  | 0  |             CPLFree(pnTemp);  | 
11150  | 0  |             break;  | 
11151  | 0  |         }  | 
11152  | 0  |         case NC_UINT64:  | 
11153  | 0  |         { | 
11154  | 0  |             unsigned long long *pnTemp = static_cast<unsigned long long *>(  | 
11155  | 0  |                 CPLCalloc(nVarLen, sizeof(unsigned long long)));  | 
11156  | 0  |             nc_get_vara_ulonglong(nCdfId, nVarId, start, count, pnTemp);  | 
11157  | 0  |             char szTemp[256];  | 
11158  | 0  |             size_t m = 0;  | 
11159  | 0  |             for (; m < nVarLen - 1; m++)  | 
11160  | 0  |             { | 
11161  | 0  |                 snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",", pnTemp[m]);  | 
11162  | 0  |                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);  | 
11163  | 0  |             }  | 
11164  | 0  |             snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, pnTemp[m]);  | 
11165  | 0  |             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);  | 
11166  | 0  |             CPLFree(pnTemp);  | 
11167  | 0  |             break;  | 
11168  | 0  |         }  | 
11169  | 0  |         default:  | 
11170  | 0  |             CPLDebug("GDAL_netCDF", "NCDFGetVar1D unsupported type %d", | 
11171  | 0  |                      nVarType);  | 
11172  | 0  |             CPLFree(pszVarValue);  | 
11173  | 0  |             pszVarValue = nullptr;  | 
11174  | 0  |             break;  | 
11175  | 0  |     }  | 
11176  |  |  | 
11177  | 0  |     if (pszVarValue != nullptr && nVarLen > 1 && nVarType != NC_CHAR)  | 
11178  | 0  |         NCDFSafeStrcat(&pszVarValue, "}", &nVarValueSize);  | 
11179  |  |  | 
11180  |  |     /* set return values */  | 
11181  | 0  |     *pszValue = pszVarValue;  | 
11182  |  | 
  | 
11183  | 0  |     return CE_None;  | 
11184  | 0  | }  | 
11185  |  |  | 
11186  |  | static CPLErr NCDFPut1DVar(int nCdfId, int nVarId, const char *pszValue)  | 
11187  | 0  | { | 
11188  | 0  |     if (EQUAL(pszValue, ""))  | 
11189  | 0  |         return CE_Failure;  | 
11190  |  |  | 
11191  |  |     /* get var information */  | 
11192  | 0  |     int nVarDimId = -1;  | 
11193  | 0  |     int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);  | 
11194  | 0  |     if (status != NC_NOERR || nVarDimId != 1)  | 
11195  | 0  |         return CE_Failure;  | 
11196  |  |  | 
11197  | 0  |     status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);  | 
11198  | 0  |     if (status != NC_NOERR)  | 
11199  | 0  |         return CE_Failure;  | 
11200  |  |  | 
11201  | 0  |     nc_type nVarType = NC_CHAR;  | 
11202  | 0  |     status = nc_inq_vartype(nCdfId, nVarId, &nVarType);  | 
11203  | 0  |     if (status != NC_NOERR)  | 
11204  | 0  |         return CE_Failure;  | 
11205  |  |  | 
11206  | 0  |     size_t nVarLen = 0;  | 
11207  | 0  |     status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);  | 
11208  | 0  |     if (status != NC_NOERR)  | 
11209  | 0  |         return CE_Failure;  | 
11210  |  |  | 
11211  | 0  |     size_t start[1] = {0}; | 
11212  | 0  |     size_t count[1] = {nVarLen}; | 
11213  |  |  | 
11214  |  |     /* get the values as tokens */  | 
11215  | 0  |     char **papszValues = NCDFTokenizeArray(pszValue);  | 
11216  | 0  |     if (papszValues == nullptr)  | 
11217  | 0  |         return CE_Failure;  | 
11218  |  |  | 
11219  | 0  |     nVarLen = CSLCount(papszValues);  | 
11220  |  |  | 
11221  |  |     /* now write the data */  | 
11222  | 0  |     if (nVarType == NC_CHAR)  | 
11223  | 0  |     { | 
11224  | 0  |         status = nc_put_vara_text(nCdfId, nVarId, start, count, pszValue);  | 
11225  | 0  |         NCDF_ERR(status);  | 
11226  | 0  |     }  | 
11227  | 0  |     else  | 
11228  | 0  |     { | 
11229  | 0  |         switch (nVarType)  | 
11230  | 0  |         { | 
11231  | 0  |             case NC_BYTE:  | 
11232  | 0  |             { | 
11233  | 0  |                 signed char *pscTemp = static_cast<signed char *>(  | 
11234  | 0  |                     CPLCalloc(nVarLen, sizeof(signed char)));  | 
11235  | 0  |                 for (size_t i = 0; i < nVarLen; i++)  | 
11236  | 0  |                 { | 
11237  | 0  |                     char *pszTemp = nullptr;  | 
11238  | 0  |                     pscTemp[i] = static_cast<signed char>(  | 
11239  | 0  |                         strtol(papszValues[i], &pszTemp, 10));  | 
11240  | 0  |                 }  | 
11241  | 0  |                 status =  | 
11242  | 0  |                     nc_put_vara_schar(nCdfId, nVarId, start, count, pscTemp);  | 
11243  | 0  |                 NCDF_ERR(status);  | 
11244  | 0  |                 CPLFree(pscTemp);  | 
11245  | 0  |                 break;  | 
11246  | 0  |             }  | 
11247  | 0  |             case NC_SHORT:  | 
11248  | 0  |             { | 
11249  | 0  |                 short *psTemp =  | 
11250  | 0  |                     static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));  | 
11251  | 0  |                 for (size_t i = 0; i < nVarLen; i++)  | 
11252  | 0  |                 { | 
11253  | 0  |                     char *pszTemp = nullptr;  | 
11254  | 0  |                     psTemp[i] = static_cast<short>(  | 
11255  | 0  |                         strtol(papszValues[i], &pszTemp, 10));  | 
11256  | 0  |                 }  | 
11257  | 0  |                 status =  | 
11258  | 0  |                     nc_put_vara_short(nCdfId, nVarId, start, count, psTemp);  | 
11259  | 0  |                 NCDF_ERR(status);  | 
11260  | 0  |                 CPLFree(psTemp);  | 
11261  | 0  |                 break;  | 
11262  | 0  |             }  | 
11263  | 0  |             case NC_INT:  | 
11264  | 0  |             { | 
11265  | 0  |                 int *pnTemp =  | 
11266  | 0  |                     static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));  | 
11267  | 0  |                 for (size_t i = 0; i < nVarLen; i++)  | 
11268  | 0  |                 { | 
11269  | 0  |                     char *pszTemp = nullptr;  | 
11270  | 0  |                     pnTemp[i] =  | 
11271  | 0  |                         static_cast<int>(strtol(papszValues[i], &pszTemp, 10));  | 
11272  | 0  |                 }  | 
11273  | 0  |                 status = nc_put_vara_int(nCdfId, nVarId, start, count, pnTemp);  | 
11274  | 0  |                 NCDF_ERR(status);  | 
11275  | 0  |                 CPLFree(pnTemp);  | 
11276  | 0  |                 break;  | 
11277  | 0  |             }  | 
11278  | 0  |             case NC_FLOAT:  | 
11279  | 0  |             { | 
11280  | 0  |                 float *pfTemp =  | 
11281  | 0  |                     static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));  | 
11282  | 0  |                 for (size_t i = 0; i < nVarLen; i++)  | 
11283  | 0  |                 { | 
11284  | 0  |                     char *pszTemp = nullptr;  | 
11285  | 0  |                     pfTemp[i] =  | 
11286  | 0  |                         static_cast<float>(CPLStrtod(papszValues[i], &pszTemp));  | 
11287  | 0  |                 }  | 
11288  | 0  |                 status =  | 
11289  | 0  |                     nc_put_vara_float(nCdfId, nVarId, start, count, pfTemp);  | 
11290  | 0  |                 NCDF_ERR(status);  | 
11291  | 0  |                 CPLFree(pfTemp);  | 
11292  | 0  |                 break;  | 
11293  | 0  |             }  | 
11294  | 0  |             case NC_DOUBLE:  | 
11295  | 0  |             { | 
11296  | 0  |                 double *pdfTemp =  | 
11297  | 0  |                     static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));  | 
11298  | 0  |                 for (size_t i = 0; i < nVarLen; i++)  | 
11299  | 0  |                 { | 
11300  | 0  |                     char *pszTemp = nullptr;  | 
11301  | 0  |                     pdfTemp[i] = CPLStrtod(papszValues[i], &pszTemp);  | 
11302  | 0  |                 }  | 
11303  | 0  |                 status =  | 
11304  | 0  |                     nc_put_vara_double(nCdfId, nVarId, start, count, pdfTemp);  | 
11305  | 0  |                 NCDF_ERR(status);  | 
11306  | 0  |                 CPLFree(pdfTemp);  | 
11307  | 0  |                 break;  | 
11308  | 0  |             }  | 
11309  | 0  |             default:  | 
11310  | 0  |             { | 
11311  | 0  |                 int nTmpFormat = 0;  | 
11312  | 0  |                 status = nc_inq_format(nCdfId, &nTmpFormat);  | 
11313  | 0  |                 NCDF_ERR(status);  | 
11314  | 0  |                 if (nTmpFormat == NCDF_FORMAT_NC4)  | 
11315  | 0  |                 { | 
11316  | 0  |                     switch (nVarType)  | 
11317  | 0  |                     { | 
11318  | 0  |                         case NC_STRING:  | 
11319  | 0  |                         { | 
11320  | 0  |                             status =  | 
11321  | 0  |                                 nc_put_vara_string(nCdfId, nVarId, start, count,  | 
11322  | 0  |                                                    (const char **)papszValues);  | 
11323  | 0  |                             NCDF_ERR(status);  | 
11324  | 0  |                             break;  | 
11325  | 0  |                         }  | 
11326  | 0  |                         case NC_UBYTE:  | 
11327  | 0  |                         { | 
11328  | 0  |                             unsigned char *pucTemp =  | 
11329  | 0  |                                 static_cast<unsigned char *>(  | 
11330  | 0  |                                     CPLCalloc(nVarLen, sizeof(unsigned char)));  | 
11331  | 0  |                             for (size_t i = 0; i < nVarLen; i++)  | 
11332  | 0  |                             { | 
11333  | 0  |                                 char *pszTemp = nullptr;  | 
11334  | 0  |                                 pucTemp[i] = static_cast<unsigned char>(  | 
11335  | 0  |                                     strtoul(papszValues[i], &pszTemp, 10));  | 
11336  | 0  |                             }  | 
11337  | 0  |                             status = nc_put_vara_uchar(nCdfId, nVarId, start,  | 
11338  | 0  |                                                        count, pucTemp);  | 
11339  | 0  |                             NCDF_ERR(status);  | 
11340  | 0  |                             CPLFree(pucTemp);  | 
11341  | 0  |                             break;  | 
11342  | 0  |                         }  | 
11343  | 0  |                         case NC_USHORT:  | 
11344  | 0  |                         { | 
11345  | 0  |                             unsigned short *pusTemp =  | 
11346  | 0  |                                 static_cast<unsigned short *>(  | 
11347  | 0  |                                     CPLCalloc(nVarLen, sizeof(unsigned short)));  | 
11348  | 0  |                             for (size_t i = 0; i < nVarLen; i++)  | 
11349  | 0  |                             { | 
11350  | 0  |                                 char *pszTemp = nullptr;  | 
11351  | 0  |                                 pusTemp[i] = static_cast<unsigned short>(  | 
11352  | 0  |                                     strtoul(papszValues[i], &pszTemp, 10));  | 
11353  | 0  |                             }  | 
11354  | 0  |                             status = nc_put_vara_ushort(nCdfId, nVarId, start,  | 
11355  | 0  |                                                         count, pusTemp);  | 
11356  | 0  |                             NCDF_ERR(status);  | 
11357  | 0  |                             CPLFree(pusTemp);  | 
11358  | 0  |                             break;  | 
11359  | 0  |                         }  | 
11360  | 0  |                         case NC_UINT:  | 
11361  | 0  |                         { | 
11362  | 0  |                             unsigned int *punTemp = static_cast<unsigned int *>(  | 
11363  | 0  |                                 CPLCalloc(nVarLen, sizeof(unsigned int)));  | 
11364  | 0  |                             for (size_t i = 0; i < nVarLen; i++)  | 
11365  | 0  |                             { | 
11366  | 0  |                                 char *pszTemp = nullptr;  | 
11367  | 0  |                                 punTemp[i] = static_cast<unsigned int>(  | 
11368  | 0  |                                     strtoul(papszValues[i], &pszTemp, 10));  | 
11369  | 0  |                             }  | 
11370  | 0  |                             status = nc_put_vara_uint(nCdfId, nVarId, start,  | 
11371  | 0  |                                                       count, punTemp);  | 
11372  | 0  |                             NCDF_ERR(status);  | 
11373  | 0  |                             CPLFree(punTemp);  | 
11374  | 0  |                             break;  | 
11375  | 0  |                         }  | 
11376  | 0  |                         default:  | 
11377  | 0  |                             if (papszValues)  | 
11378  | 0  |                                 CSLDestroy(papszValues);  | 
11379  | 0  |                             return CE_Failure;  | 
11380  | 0  |                             break;  | 
11381  | 0  |                     }  | 
11382  | 0  |                 }  | 
11383  | 0  |                 break;  | 
11384  | 0  |             }  | 
11385  | 0  |         }  | 
11386  | 0  |     }  | 
11387  |  |  | 
11388  | 0  |     if (papszValues)  | 
11389  | 0  |         CSLDestroy(papszValues);  | 
11390  |  | 
  | 
11391  | 0  |     return CE_None;  | 
11392  | 0  | }  | 
11393  |  |  | 
11394  |  | /************************************************************************/  | 
11395  |  | /*                           GetDefaultNoDataValue()                    */  | 
11396  |  | /************************************************************************/  | 
11397  |  |  | 
11398  |  | double NCDFGetDefaultNoDataValue(int nCdfId, int nVarId, int nVarType,  | 
11399  |  |                                  bool &bGotNoData)  | 
11400  |  |  | 
11401  | 0  | { | 
11402  | 0  |     int nNoFill = 0;  | 
11403  | 0  |     double dfNoData = 0.0;  | 
11404  |  | 
  | 
11405  | 0  |     switch (nVarType)  | 
11406  | 0  |     { | 
11407  | 0  |         case NC_CHAR:  | 
11408  | 0  |         case NC_BYTE:  | 
11409  | 0  |         case NC_UBYTE:  | 
11410  |  |             // Don't do default fill-values for bytes, too risky.  | 
11411  |  |             // This function should not be called in those cases.  | 
11412  | 0  |             CPLAssert(false);  | 
11413  | 0  |             break;  | 
11414  | 0  |         case NC_SHORT:  | 
11415  | 0  |         { | 
11416  | 0  |             short nFillVal = 0;  | 
11417  | 0  |             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==  | 
11418  | 0  |                 NC_NOERR)  | 
11419  | 0  |             { | 
11420  | 0  |                 if (!nNoFill)  | 
11421  | 0  |                 { | 
11422  | 0  |                     bGotNoData = true;  | 
11423  | 0  |                     dfNoData = nFillVal;  | 
11424  | 0  |                 }  | 
11425  | 0  |             }  | 
11426  | 0  |             else  | 
11427  | 0  |                 dfNoData = NC_FILL_SHORT;  | 
11428  | 0  |             break;  | 
11429  | 0  |         }  | 
11430  | 0  |         case NC_INT:  | 
11431  | 0  |         { | 
11432  | 0  |             int nFillVal = 0;  | 
11433  | 0  |             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==  | 
11434  | 0  |                 NC_NOERR)  | 
11435  | 0  |             { | 
11436  | 0  |                 if (!nNoFill)  | 
11437  | 0  |                 { | 
11438  | 0  |                     bGotNoData = true;  | 
11439  | 0  |                     dfNoData = nFillVal;  | 
11440  | 0  |                 }  | 
11441  | 0  |             }  | 
11442  | 0  |             else  | 
11443  | 0  |                 dfNoData = NC_FILL_INT;  | 
11444  | 0  |             break;  | 
11445  | 0  |         }  | 
11446  | 0  |         case NC_FLOAT:  | 
11447  | 0  |         { | 
11448  | 0  |             float fFillVal = 0;  | 
11449  | 0  |             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &fFillVal) ==  | 
11450  | 0  |                 NC_NOERR)  | 
11451  | 0  |             { | 
11452  | 0  |                 if (!nNoFill)  | 
11453  | 0  |                 { | 
11454  | 0  |                     bGotNoData = true;  | 
11455  | 0  |                     dfNoData = fFillVal;  | 
11456  | 0  |                 }  | 
11457  | 0  |             }  | 
11458  | 0  |             else  | 
11459  | 0  |                 dfNoData = NC_FILL_FLOAT;  | 
11460  | 0  |             break;  | 
11461  | 0  |         }  | 
11462  | 0  |         case NC_DOUBLE:  | 
11463  | 0  |         { | 
11464  | 0  |             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &dfNoData) ==  | 
11465  | 0  |                 NC_NOERR)  | 
11466  | 0  |             { | 
11467  | 0  |                 if (!nNoFill)  | 
11468  | 0  |                 { | 
11469  | 0  |                     bGotNoData = true;  | 
11470  | 0  |                 }  | 
11471  | 0  |             }  | 
11472  | 0  |             else  | 
11473  | 0  |                 dfNoData = NC_FILL_DOUBLE;  | 
11474  | 0  |             break;  | 
11475  | 0  |         }  | 
11476  | 0  |         case NC_USHORT:  | 
11477  | 0  |         { | 
11478  | 0  |             unsigned short nFillVal = 0;  | 
11479  | 0  |             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==  | 
11480  | 0  |                 NC_NOERR)  | 
11481  | 0  |             { | 
11482  | 0  |                 if (!nNoFill)  | 
11483  | 0  |                 { | 
11484  | 0  |                     bGotNoData = true;  | 
11485  | 0  |                     dfNoData = nFillVal;  | 
11486  | 0  |                 }  | 
11487  | 0  |             }  | 
11488  | 0  |             else  | 
11489  | 0  |                 dfNoData = NC_FILL_USHORT;  | 
11490  | 0  |             break;  | 
11491  | 0  |         }  | 
11492  | 0  |         case NC_UINT:  | 
11493  | 0  |         { | 
11494  | 0  |             unsigned int nFillVal = 0;  | 
11495  | 0  |             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==  | 
11496  | 0  |                 NC_NOERR)  | 
11497  | 0  |             { | 
11498  | 0  |                 if (!nNoFill)  | 
11499  | 0  |                 { | 
11500  | 0  |                     bGotNoData = true;  | 
11501  | 0  |                     dfNoData = nFillVal;  | 
11502  | 0  |                 }  | 
11503  | 0  |             }  | 
11504  | 0  |             else  | 
11505  | 0  |                 dfNoData = NC_FILL_UINT;  | 
11506  | 0  |             break;  | 
11507  | 0  |         }  | 
11508  | 0  |         default:  | 
11509  | 0  |             dfNoData = 0.0;  | 
11510  | 0  |             break;  | 
11511  | 0  |     }  | 
11512  |  |  | 
11513  | 0  |     return dfNoData;  | 
11514  | 0  | }  | 
11515  |  |  | 
11516  |  | /************************************************************************/  | 
11517  |  | /*                      NCDFGetDefaultNoDataValueAsInt64()              */  | 
11518  |  | /************************************************************************/  | 
11519  |  |  | 
11520  |  | int64_t NCDFGetDefaultNoDataValueAsInt64(int nCdfId, int nVarId,  | 
11521  |  |                                          bool &bGotNoData)  | 
11522  |  |  | 
11523  | 0  | { | 
11524  | 0  |     int nNoFill = 0;  | 
11525  | 0  |     long long nFillVal = 0;  | 
11526  | 0  |     if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)  | 
11527  | 0  |     { | 
11528  | 0  |         if (!nNoFill)  | 
11529  | 0  |         { | 
11530  | 0  |             bGotNoData = true;  | 
11531  | 0  |             return static_cast<int64_t>(nFillVal);  | 
11532  | 0  |         }  | 
11533  | 0  |     }  | 
11534  | 0  |     else  | 
11535  | 0  |         return static_cast<int64_t>(NC_FILL_INT64);  | 
11536  | 0  |     return 0;  | 
11537  | 0  | }  | 
11538  |  |  | 
11539  |  | /************************************************************************/  | 
11540  |  | /*                     NCDFGetDefaultNoDataValueAsUInt64()              */  | 
11541  |  | /************************************************************************/  | 
11542  |  |  | 
11543  |  | uint64_t NCDFGetDefaultNoDataValueAsUInt64(int nCdfId, int nVarId,  | 
11544  |  |                                            bool &bGotNoData)  | 
11545  |  |  | 
11546  | 0  | { | 
11547  | 0  |     int nNoFill = 0;  | 
11548  | 0  |     unsigned long long nFillVal = 0;  | 
11549  | 0  |     if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)  | 
11550  | 0  |     { | 
11551  | 0  |         if (!nNoFill)  | 
11552  | 0  |         { | 
11553  | 0  |             bGotNoData = true;  | 
11554  | 0  |             return static_cast<uint64_t>(nFillVal);  | 
11555  | 0  |         }  | 
11556  | 0  |     }  | 
11557  | 0  |     else  | 
11558  | 0  |         return static_cast<uint64_t>(NC_FILL_UINT64);  | 
11559  | 0  |     return 0;  | 
11560  | 0  | }  | 
11561  |  |  | 
11562  |  | static int NCDFDoesVarContainAttribVal(int nCdfId,  | 
11563  |  |                                        const char *const *papszAttribNames,  | 
11564  |  |                                        const char *const *papszAttribValues,  | 
11565  |  |                                        int nVarId, const char *pszVarName,  | 
11566  |  |                                        bool bStrict = true)  | 
11567  | 744  | { | 
11568  | 744  |     if (nVarId == -1 && pszVarName != nullptr)  | 
11569  | 744  |         NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);  | 
11570  |  |  | 
11571  | 744  |     if (nVarId == -1)  | 
11572  | 26  |         return -1;  | 
11573  |  |  | 
11574  | 718  |     bool bFound = false;  | 
11575  | 3.44k  |     for (int i = 0; !bFound && papszAttribNames != nullptr &&  | 
11576  | 3.44k  |                     papszAttribNames[i] != nullptr;  | 
11577  | 2.73k  |          i++)  | 
11578  | 2.73k  |     { | 
11579  | 2.73k  |         char *pszTemp = nullptr;  | 
11580  | 2.73k  |         if (NCDFGetAttr(nCdfId, nVarId, papszAttribNames[i], &pszTemp) ==  | 
11581  | 2.73k  |                 CE_None &&  | 
11582  | 2.73k  |             pszTemp != nullptr)  | 
11583  | 4  |         { | 
11584  | 4  |             if (bStrict)  | 
11585  | 4  |             { | 
11586  | 4  |                 if (EQUAL(pszTemp, papszAttribValues[i]))  | 
11587  | 0  |                     bFound = true;  | 
11588  | 4  |             }  | 
11589  | 0  |             else  | 
11590  | 0  |             { | 
11591  | 0  |                 if (EQUALN(pszTemp, papszAttribValues[i],  | 
11592  | 0  |                            strlen(papszAttribValues[i])))  | 
11593  | 0  |                     bFound = true;  | 
11594  | 0  |             }  | 
11595  | 4  |             CPLFree(pszTemp);  | 
11596  | 4  |         }  | 
11597  | 2.73k  |     }  | 
11598  | 718  |     return bFound;  | 
11599  | 744  | }  | 
11600  |  |  | 
11601  |  | static int NCDFDoesVarContainAttribVal2(int nCdfId, const char *papszAttribName,  | 
11602  |  |                                         const char *const *papszAttribValues,  | 
11603  |  |                                         int nVarId, const char *pszVarName,  | 
11604  |  |                                         int bStrict = true)  | 
11605  | 279  | { | 
11606  | 279  |     if (nVarId == -1 && pszVarName != nullptr)  | 
11607  | 279  |         NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);  | 
11608  |  |  | 
11609  | 279  |     if (nVarId == -1)  | 
11610  | 0  |         return -1;  | 
11611  |  |  | 
11612  | 279  |     bool bFound = false;  | 
11613  | 279  |     char *pszTemp = nullptr;  | 
11614  | 279  |     if (NCDFGetAttr(nCdfId, nVarId, papszAttribName, &pszTemp) != CE_None ||  | 
11615  | 279  |         pszTemp == nullptr)  | 
11616  | 279  |         return FALSE;  | 
11617  |  |  | 
11618  | 0  |     for (int i = 0; !bFound && i < CSLCount(papszAttribValues); i++)  | 
11619  | 0  |     { | 
11620  | 0  |         if (bStrict)  | 
11621  | 0  |         { | 
11622  | 0  |             if (EQUAL(pszTemp, papszAttribValues[i]))  | 
11623  | 0  |                 bFound = true;  | 
11624  | 0  |         }  | 
11625  | 0  |         else  | 
11626  | 0  |         { | 
11627  | 0  |             if (EQUALN(pszTemp, papszAttribValues[i],  | 
11628  | 0  |                        strlen(papszAttribValues[i])))  | 
11629  | 0  |                 bFound = true;  | 
11630  | 0  |         }  | 
11631  | 0  |     }  | 
11632  |  | 
  | 
11633  | 0  |     CPLFree(pszTemp);  | 
11634  |  | 
  | 
11635  | 0  |     return bFound;  | 
11636  | 279  | }  | 
11637  |  |  | 
11638  |  | static bool NCDFEqual(const char *papszName, const char *const *papszValues)  | 
11639  | 26  | { | 
11640  | 26  |     if (papszName == nullptr || EQUAL(papszName, ""))  | 
11641  | 0  |         return false;  | 
11642  |  |  | 
11643  | 78  |     for (int i = 0; papszValues && papszValues[i]; ++i)  | 
11644  | 52  |     { | 
11645  | 52  |         if (EQUAL(papszName, papszValues[i]))  | 
11646  | 0  |             return true;  | 
11647  | 52  |     }  | 
11648  |  |  | 
11649  | 26  |     return false;  | 
11650  | 26  | }  | 
11651  |  |  | 
11652  |  | // Test that a variable is longitude/latitude coordinate,  | 
11653  |  | // following CF 4.1 and 4.2.  | 
11654  |  | bool NCDFIsVarLongitude(int nCdfId, int nVarId, const char *pszVarName)  | 
11655  | 151  | { | 
11656  |  |     // Check for matching attributes.  | 
11657  | 151  |     int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLongitudeAttribNames,  | 
11658  | 151  |                                            papszCFLongitudeAttribValues, nVarId,  | 
11659  | 151  |                                            pszVarName);  | 
11660  |  |     // If not found using attributes then check using var name  | 
11661  |  |     // unless GDAL_NETCDF_VERIFY_DIMS=STRICT.  | 
11662  | 151  |     if (bVal == -1)  | 
11663  | 6  |     { | 
11664  | 6  |         if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"), | 
11665  | 6  |                    "STRICT"))  | 
11666  | 6  |             bVal = NCDFEqual(pszVarName, papszCFLongitudeVarNames);  | 
11667  | 0  |         else  | 
11668  | 0  |             bVal = FALSE;  | 
11669  | 6  |     }  | 
11670  | 145  |     else if (bVal)  | 
11671  | 0  |     { | 
11672  |  |         // Check that the units is not 'm' or '1'. See #6759  | 
11673  | 0  |         char *pszTemp = nullptr;  | 
11674  | 0  |         if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&  | 
11675  | 0  |             pszTemp != nullptr)  | 
11676  | 0  |         { | 
11677  | 0  |             if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))  | 
11678  | 0  |                 bVal = false;  | 
11679  | 0  |             CPLFree(pszTemp);  | 
11680  | 0  |         }  | 
11681  | 0  |     }  | 
11682  |  |  | 
11683  | 151  |     return CPL_TO_BOOL(bVal);  | 
11684  | 151  | }  | 
11685  |  |  | 
11686  |  | bool NCDFIsVarLatitude(int nCdfId, int nVarId, const char *pszVarName)  | 
11687  | 151  | { | 
11688  | 151  |     int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLatitudeAttribNames,  | 
11689  | 151  |                                            papszCFLatitudeAttribValues, nVarId,  | 
11690  | 151  |                                            pszVarName);  | 
11691  | 151  |     if (bVal == -1)  | 
11692  | 7  |     { | 
11693  | 7  |         if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"), | 
11694  | 7  |                    "STRICT"))  | 
11695  | 7  |             bVal = NCDFEqual(pszVarName, papszCFLatitudeVarNames);  | 
11696  | 0  |         else  | 
11697  | 0  |             bVal = FALSE;  | 
11698  | 7  |     }  | 
11699  | 144  |     else if (bVal)  | 
11700  | 0  |     { | 
11701  |  |         // Check that the units is not 'm' or '1'. See #6759  | 
11702  | 0  |         char *pszTemp = nullptr;  | 
11703  | 0  |         if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&  | 
11704  | 0  |             pszTemp != nullptr)  | 
11705  | 0  |         { | 
11706  | 0  |             if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))  | 
11707  | 0  |                 bVal = false;  | 
11708  | 0  |             CPLFree(pszTemp);  | 
11709  | 0  |         }  | 
11710  | 0  |     }  | 
11711  |  |  | 
11712  | 151  |     return CPL_TO_BOOL(bVal);  | 
11713  | 151  | }  | 
11714  |  |  | 
11715  |  | bool NCDFIsVarProjectionX(int nCdfId, int nVarId, const char *pszVarName)  | 
11716  | 151  | { | 
11717  | 151  |     int bVal = NCDFDoesVarContainAttribVal(  | 
11718  | 151  |         nCdfId, papszCFProjectionXAttribNames, papszCFProjectionXAttribValues,  | 
11719  | 151  |         nVarId, pszVarName);  | 
11720  | 151  |     if (bVal == -1)  | 
11721  | 6  |     { | 
11722  | 6  |         if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"), | 
11723  | 6  |                    "STRICT"))  | 
11724  | 6  |             bVal = NCDFEqual(pszVarName, papszCFProjectionXVarNames);  | 
11725  | 0  |         else  | 
11726  | 0  |             bVal = FALSE;  | 
11727  | 6  |     }  | 
11728  | 145  |     else if (bVal)  | 
11729  | 0  |     { | 
11730  |  |         // Check that the units is not '1'  | 
11731  | 0  |         char *pszTemp = nullptr;  | 
11732  | 0  |         if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&  | 
11733  | 0  |             pszTemp != nullptr)  | 
11734  | 0  |         { | 
11735  | 0  |             if (EQUAL(pszTemp, "1"))  | 
11736  | 0  |                 bVal = false;  | 
11737  | 0  |             CPLFree(pszTemp);  | 
11738  | 0  |         }  | 
11739  | 0  |     }  | 
11740  |  |  | 
11741  | 151  |     return CPL_TO_BOOL(bVal);  | 
11742  | 151  | }  | 
11743  |  |  | 
11744  |  | bool NCDFIsVarProjectionY(int nCdfId, int nVarId, const char *pszVarName)  | 
11745  | 151  | { | 
11746  | 151  |     int bVal = NCDFDoesVarContainAttribVal(  | 
11747  | 151  |         nCdfId, papszCFProjectionYAttribNames, papszCFProjectionYAttribValues,  | 
11748  | 151  |         nVarId, pszVarName);  | 
11749  | 151  |     if (bVal == -1)  | 
11750  | 7  |     { | 
11751  | 7  |         if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"), | 
11752  | 7  |                    "STRICT"))  | 
11753  | 7  |             bVal = NCDFEqual(pszVarName, papszCFProjectionYVarNames);  | 
11754  | 0  |         else  | 
11755  | 0  |             bVal = FALSE;  | 
11756  | 7  |     }  | 
11757  | 144  |     else if (bVal)  | 
11758  | 0  |     { | 
11759  |  |         // Check that the units is not '1'  | 
11760  | 0  |         char *pszTemp = nullptr;  | 
11761  | 0  |         if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&  | 
11762  | 0  |             pszTemp != nullptr)  | 
11763  | 0  |         { | 
11764  | 0  |             if (EQUAL(pszTemp, "1"))  | 
11765  | 0  |                 bVal = false;  | 
11766  | 0  |             CPLFree(pszTemp);  | 
11767  | 0  |         }  | 
11768  | 0  |     }  | 
11769  |  |  | 
11770  | 151  |     return CPL_TO_BOOL(bVal);  | 
11771  | 151  | }  | 
11772  |  |  | 
11773  |  | /* test that a variable is a vertical coordinate, following CF 4.3 */  | 
11774  |  | bool NCDFIsVarVerticalCoord(int nCdfId, int nVarId, const char *pszVarName)  | 
11775  | 139  | { | 
11776  |  |     /* check for matching attributes */  | 
11777  | 139  |     if (NCDFDoesVarContainAttribVal(nCdfId, papszCFVerticalAttribNames,  | 
11778  | 139  |                                     papszCFVerticalAttribValues, nVarId,  | 
11779  | 139  |                                     pszVarName))  | 
11780  | 0  |         return true;  | 
11781  |  |     /* check for matching units */  | 
11782  | 139  |     else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,  | 
11783  | 139  |                                           papszCFVerticalUnitsValues, nVarId,  | 
11784  | 139  |                                           pszVarName))  | 
11785  | 0  |         return true;  | 
11786  |  |     /* check for matching standard name */  | 
11787  | 139  |     else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_STD_NAME,  | 
11788  | 139  |                                           papszCFVerticalStandardNameValues,  | 
11789  | 139  |                                           nVarId, pszVarName))  | 
11790  | 0  |         return true;  | 
11791  | 139  |     else  | 
11792  | 139  |         return false;  | 
11793  | 139  | }  | 
11794  |  |  | 
11795  |  | /* test that a variable is a time coordinate, following CF 4.4 */  | 
11796  |  | bool NCDFIsVarTimeCoord(int nCdfId, int nVarId, const char *pszVarName)  | 
11797  | 1  | { | 
11798  |  |     /* check for matching attributes */  | 
11799  | 1  |     if (NCDFDoesVarContainAttribVal(nCdfId, papszCFTimeAttribNames,  | 
11800  | 1  |                                     papszCFTimeAttribValues, nVarId,  | 
11801  | 1  |                                     pszVarName))  | 
11802  | 0  |         return true;  | 
11803  |  |     /* check for matching units */  | 
11804  | 1  |     else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,  | 
11805  | 1  |                                           papszCFTimeUnitsValues, nVarId,  | 
11806  | 1  |                                           pszVarName, false))  | 
11807  | 0  |         return true;  | 
11808  | 1  |     else  | 
11809  | 1  |         return false;  | 
11810  | 1  | }  | 
11811  |  |  | 
11812  |  | // Parse a string, and return as a string list.  | 
11813  |  | // If it an array of the form {a,b}, then tokenize it. | 
11814  |  | // Otherwise, return a copy.  | 
11815  |  | static char **NCDFTokenizeArray(const char *pszValue)  | 
11816  | 0  | { | 
11817  | 0  |     if (pszValue == nullptr || EQUAL(pszValue, ""))  | 
11818  | 0  |         return nullptr;  | 
11819  |  |  | 
11820  | 0  |     char **papszValues = nullptr;  | 
11821  | 0  |     const int nLen = static_cast<int>(strlen(pszValue));  | 
11822  |  | 
  | 
11823  | 0  |     if (pszValue[0] == '{' && nLen > 2 && pszValue[nLen - 1] == '}') | 
11824  | 0  |     { | 
11825  | 0  |         char *pszTemp = static_cast<char *>(CPLMalloc((nLen - 2) + 1));  | 
11826  | 0  |         strncpy(pszTemp, pszValue + 1, nLen - 2);  | 
11827  | 0  |         pszTemp[nLen - 2] = '\0';  | 
11828  | 0  |         papszValues = CSLTokenizeString2(pszTemp, ",", CSLT_ALLOWEMPTYTOKENS);  | 
11829  | 0  |         CPLFree(pszTemp);  | 
11830  | 0  |     }  | 
11831  | 0  |     else  | 
11832  | 0  |     { | 
11833  | 0  |         papszValues = reinterpret_cast<char **>(CPLCalloc(2, sizeof(char *)));  | 
11834  | 0  |         papszValues[0] = CPLStrdup(pszValue);  | 
11835  | 0  |         papszValues[1] = nullptr;  | 
11836  | 0  |     }  | 
11837  |  | 
  | 
11838  | 0  |     return papszValues;  | 
11839  | 0  | }  | 
11840  |  |  | 
11841  |  | // Open a NetCDF subdataset from full path /group1/group2/.../groupn/var.  | 
11842  |  | // Leading slash is optional.  | 
11843  |  | static CPLErr NCDFOpenSubDataset(int nCdfId, const char *pszSubdatasetName,  | 
11844  |  |                                  int *pnGroupId, int *pnVarId)  | 
11845  | 11.1k  | { | 
11846  | 11.1k  |     *pnGroupId = -1;  | 
11847  | 11.1k  |     *pnVarId = -1;  | 
11848  |  |  | 
11849  |  |     // Open group.  | 
11850  | 11.1k  |     char *pszGroupFullName =  | 
11851  | 11.1k  |         CPLStrdup(CPLGetPathSafe(pszSubdatasetName).c_str());  | 
11852  |  |     // Add a leading slash if needed.  | 
11853  | 11.1k  |     if (pszGroupFullName[0] != '/')  | 
11854  | 0  |     { | 
11855  | 0  |         char *old = pszGroupFullName;  | 
11856  | 0  |         pszGroupFullName = CPLStrdup(CPLSPrintf("/%s", pszGroupFullName)); | 
11857  | 0  |         CPLFree(old);  | 
11858  | 0  |     }  | 
11859  |  |     // Detect root group.  | 
11860  | 11.1k  |     if (EQUAL(pszGroupFullName, "/"))  | 
11861  | 9.21k  |     { | 
11862  | 9.21k  |         *pnGroupId = nCdfId;  | 
11863  | 9.21k  |         CPLFree(pszGroupFullName);  | 
11864  | 9.21k  |     }  | 
11865  | 1.93k  |     else  | 
11866  | 1.93k  |     { | 
11867  | 1.93k  |         int status = nc_inq_grp_full_ncid(nCdfId, pszGroupFullName, pnGroupId);  | 
11868  | 1.93k  |         CPLFree(pszGroupFullName);  | 
11869  | 1.93k  |         NCDF_ERR_RET(status);  | 
11870  | 1.93k  |     }  | 
11871  |  |  | 
11872  |  |     // Open var.  | 
11873  | 9.21k  |     const char *pszVarName = CPLGetFilename(pszSubdatasetName);  | 
11874  | 9.21k  |     NCDF_ERR_RET(nc_inq_varid(*pnGroupId, pszVarName, pnVarId));  | 
11875  |  |  | 
11876  | 0  |     return CE_None;  | 
11877  | 9.21k  | }  | 
11878  |  |  | 
11879  |  | // Get all dimensions visible from a given NetCDF (or group) ID and any of  | 
11880  |  | // its parents.  | 
11881  |  | static CPLErr NCDFGetVisibleDims(int nGroupId, int *pnDims, int **ppanDimIds)  | 
11882  | 0  | { | 
11883  | 0  |     int nDims = 0;  | 
11884  | 0  |     int *panDimIds = nullptr;  | 
11885  | 0  |     NCDF_ERR_RET(nc_inq_dimids(nGroupId, &nDims, nullptr, true));  | 
11886  |  |  | 
11887  | 0  |     panDimIds = static_cast<int *>(CPLMalloc(nDims * sizeof(int)));  | 
11888  |  | 
  | 
11889  | 0  |     int status = nc_inq_dimids(nGroupId, nullptr, panDimIds, true);  | 
11890  | 0  |     if (status != NC_NOERR)  | 
11891  | 0  |         CPLFree(panDimIds);  | 
11892  | 0  |     NCDF_ERR_RET(status);  | 
11893  |  |  | 
11894  | 0  |     *pnDims = nDims;  | 
11895  | 0  |     *ppanDimIds = panDimIds;  | 
11896  |  | 
  | 
11897  | 0  |     return CE_None;  | 
11898  | 0  | }  | 
11899  |  |  | 
11900  |  | // Get direct sub-groups IDs of a given NetCDF (or group) ID.  | 
11901  |  | // Consider only direct children, does not get children of children.  | 
11902  |  | static CPLErr NCDFGetSubGroups(int nGroupId, int *pnSubGroups,  | 
11903  |  |                                int **ppanSubGroupIds)  | 
11904  | 99.0k  | { | 
11905  | 99.0k  |     *pnSubGroups = 0;  | 
11906  | 99.0k  |     *ppanSubGroupIds = nullptr;  | 
11907  |  |  | 
11908  | 99.0k  |     int nSubGroups;  | 
11909  | 99.0k  |     NCDF_ERR_RET(nc_inq_grps(nGroupId, &nSubGroups, nullptr));  | 
11910  | 99.0k  |     int *panSubGroupIds =  | 
11911  | 99.0k  |         static_cast<int *>(CPLMalloc(nSubGroups * sizeof(int)));  | 
11912  | 99.0k  |     NCDF_ERR_RET(nc_inq_grps(nGroupId, nullptr, panSubGroupIds));  | 
11913  | 99.0k  |     *pnSubGroups = nSubGroups;  | 
11914  | 99.0k  |     *ppanSubGroupIds = panSubGroupIds;  | 
11915  |  |  | 
11916  | 99.0k  |     return CE_None;  | 
11917  | 99.0k  | }  | 
11918  |  |  | 
11919  |  | // Get the full name of a given NetCDF (or group) ID  | 
11920  |  | // (e.g. /group1/group2/.../groupn).  | 
11921  |  | // bNC3Compat remove the leading slash for top-level variables for  | 
11922  |  | // backward compatibility (top-level variables are the ones in the root group).  | 
11923  |  | static CPLErr NCDFGetGroupFullName(int nGroupId, char **ppszFullName,  | 
11924  |  |                                    bool bNC3Compat)  | 
11925  | 7.06k  | { | 
11926  | 7.06k  |     *ppszFullName = nullptr;  | 
11927  |  |  | 
11928  | 7.06k  |     size_t nFullNameLen;  | 
11929  | 7.06k  |     NCDF_ERR_RET(nc_inq_grpname_len(nGroupId, &nFullNameLen));  | 
11930  | 7.06k  |     *ppszFullName =  | 
11931  | 7.06k  |         static_cast<char *>(CPLMalloc((nFullNameLen + 1) * sizeof(char)));  | 
11932  | 7.06k  |     int status = nc_inq_grpname_full(nGroupId, &nFullNameLen, *ppszFullName);  | 
11933  | 7.06k  |     if (status != NC_NOERR)  | 
11934  | 0  |     { | 
11935  | 0  |         CPLFree(*ppszFullName);  | 
11936  | 0  |         *ppszFullName = nullptr;  | 
11937  | 0  |         NCDF_ERR_RET(status);  | 
11938  | 0  |     }  | 
11939  |  |  | 
11940  | 7.06k  |     if (bNC3Compat && EQUAL(*ppszFullName, "/"))  | 
11941  | 7.06k  |         (*ppszFullName)[0] = '\0';  | 
11942  |  |  | 
11943  | 7.06k  |     return CE_None;  | 
11944  | 7.06k  | }  | 
11945  |  |  | 
11946  |  | CPLString NCDFGetGroupFullName(int nGroupId)  | 
11947  | 0  | { | 
11948  | 0  |     char *pszFullname = nullptr;  | 
11949  | 0  |     NCDFGetGroupFullName(nGroupId, &pszFullname, false);  | 
11950  | 0  |     CPLString osRet(pszFullname ? pszFullname : "");  | 
11951  | 0  |     CPLFree(pszFullname);  | 
11952  | 0  |     return osRet;  | 
11953  | 0  | }  | 
11954  |  |  | 
11955  |  | // Get the full name of a given NetCDF variable ID  | 
11956  |  | // (e.g. /group1/group2/.../groupn/var).  | 
11957  |  | // Handle also NC_GLOBAL as nVarId.  | 
11958  |  | // bNC3Compat remove the leading slash for top-level variables for  | 
11959  |  | // backward compatibility (top-level variables are the ones in the root group).  | 
11960  |  | static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId, char **ppszFullName,  | 
11961  |  |                                  bool bNC3Compat)  | 
11962  | 7.04k  | { | 
11963  | 7.04k  |     *ppszFullName = nullptr;  | 
11964  | 7.04k  |     char *pszGroupFullName = nullptr;  | 
11965  | 7.04k  |     ERR_RET(NCDFGetGroupFullName(nGroupId, &pszGroupFullName, bNC3Compat));  | 
11966  | 7.04k  |     char szVarName[NC_MAX_NAME + 1];  | 
11967  | 7.04k  |     if (nVarId == NC_GLOBAL)  | 
11968  | 399  |     { | 
11969  | 399  |         strcpy(szVarName, "NC_GLOBAL");  | 
11970  | 399  |     }  | 
11971  | 6.64k  |     else  | 
11972  | 6.64k  |     { | 
11973  | 6.64k  |         int status = nc_inq_varname(nGroupId, nVarId, szVarName);  | 
11974  | 6.64k  |         if (status != NC_NOERR)  | 
11975  | 0  |         { | 
11976  | 0  |             CPLFree(pszGroupFullName);  | 
11977  | 0  |             NCDF_ERR_RET(status);  | 
11978  | 0  |         }  | 
11979  | 6.64k  |     }  | 
11980  | 7.04k  |     const char *pszSep = "/";  | 
11981  | 7.04k  |     if (EQUAL(pszGroupFullName, "/") || EQUAL(pszGroupFullName, ""))  | 
11982  | 7.04k  |         pszSep = "";  | 
11983  | 7.04k  |     *ppszFullName =  | 
11984  | 7.04k  |         CPLStrdup(CPLSPrintf("%s%s%s", pszGroupFullName, pszSep, szVarName)); | 
11985  | 7.04k  |     CPLFree(pszGroupFullName);  | 
11986  | 7.04k  |     return CE_None;  | 
11987  | 7.04k  | }  | 
11988  |  |  | 
11989  |  | // Get the NetCDF root group ID of a given group ID.  | 
11990  |  | static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId)  | 
11991  | 11.1k  | { | 
11992  | 11.1k  |     *pnRootGroupId = -1;  | 
11993  |  |     // Recurse on parent group.  | 
11994  | 11.1k  |     int nParentGroupId;  | 
11995  | 11.1k  |     int status = nc_inq_grp_parent(nStartGroupId, &nParentGroupId);  | 
11996  | 11.1k  |     if (status == NC_NOERR)  | 
11997  | 0  |         return NCDFGetRootGroup(nParentGroupId, pnRootGroupId);  | 
11998  | 11.1k  |     else if (status != NC_ENOGRP)  | 
11999  | 0  |         NCDF_ERR_RET(status);  | 
12000  | 11.1k  |     else  // No more parent group.  | 
12001  | 11.1k  |     { | 
12002  | 11.1k  |         *pnRootGroupId = nStartGroupId;  | 
12003  | 11.1k  |     }  | 
12004  |  |  | 
12005  | 11.1k  |     return CE_None;  | 
12006  | 11.1k  | }  | 
12007  |  |  | 
12008  |  | // Implementation of NCDFResolveVar/Att.  | 
12009  |  | static CPLErr NCDFResolveElem(int nStartGroupId, const char *pszVar,  | 
12010  |  |                               const char *pszAtt, int *pnGroupId, int *pnId,  | 
12011  |  |                               bool bMandatory)  | 
12012  | 99.6k  | { | 
12013  | 99.6k  |     if (!pszVar && !pszAtt)  | 
12014  | 0  |     { | 
12015  | 0  |         CPLError(CE_Failure, CPLE_IllegalArg,  | 
12016  | 0  |                  "pszVar and pszAtt NCDFResolveElem() args are both null.");  | 
12017  | 0  |         return CE_Failure;  | 
12018  | 0  |     }  | 
12019  |  |  | 
12020  | 99.6k  |     enum  | 
12021  | 99.6k  |     { | 
12022  | 99.6k  |         NCRM_PARENT,  | 
12023  | 99.6k  |         NCRM_WIDTH_WISE  | 
12024  | 99.6k  |     } eNCResolveMode = NCRM_PARENT;  | 
12025  |  |  | 
12026  | 99.6k  |     std::queue<int> aoQueueGroupIdsToVisit;  | 
12027  | 99.6k  |     aoQueueGroupIdsToVisit.push(nStartGroupId);  | 
12028  |  |  | 
12029  | 197k  |     while (!aoQueueGroupIdsToVisit.empty())  | 
12030  | 99.6k  |     { | 
12031  |  |         // Get the first group of the FIFO queue.  | 
12032  | 99.6k  |         *pnGroupId = aoQueueGroupIdsToVisit.front();  | 
12033  | 99.6k  |         aoQueueGroupIdsToVisit.pop();  | 
12034  |  |  | 
12035  |  |         // Look if this group contains the searched element.  | 
12036  | 99.6k  |         int status;  | 
12037  | 99.6k  |         if (pszVar)  | 
12038  | 99.6k  |             status = nc_inq_varid(*pnGroupId, pszVar, pnId);  | 
12039  | 0  |         else  // pszAtt != nullptr.  | 
12040  | 0  |             status = nc_inq_attid(*pnGroupId, NC_GLOBAL, pszAtt, pnId);  | 
12041  |  |  | 
12042  | 99.6k  |         if (status == NC_NOERR)  | 
12043  | 1.75k  |         { | 
12044  | 1.75k  |             return CE_None;  | 
12045  | 1.75k  |         }  | 
12046  | 97.8k  |         else if ((pszVar && status != NC_ENOTVAR) ||  | 
12047  | 97.8k  |                  (pszAtt && status != NC_ENOTATT))  | 
12048  | 3.82k  |         { | 
12049  | 3.82k  |             NCDF_ERR(status);  | 
12050  | 3.82k  |         }  | 
12051  |  |         // Element not found, in NC4 case we must search in other groups  | 
12052  |  |         // following the CF logic.  | 
12053  |  |  | 
12054  |  |         // The first resolve mode consists to search on parent groups.  | 
12055  | 97.8k  |         if (eNCResolveMode == NCRM_PARENT)  | 
12056  | 97.8k  |         { | 
12057  | 97.8k  |             int nParentGroupId = -1;  | 
12058  | 97.8k  |             int status2 = nc_inq_grp_parent(*pnGroupId, &nParentGroupId);  | 
12059  | 97.8k  |             if (status2 == NC_NOERR)  | 
12060  | 0  |                 aoQueueGroupIdsToVisit.push(nParentGroupId);  | 
12061  | 97.8k  |             else if (status2 != NC_ENOGRP)  | 
12062  | 0  |                 NCDF_ERR(status2);  | 
12063  | 97.8k  |             else if (pszVar)  | 
12064  |  |                 // When resolving a variable, if there is no more  | 
12065  |  |                 // parent group then we switch to width-wise search mode  | 
12066  |  |                 // starting from the latest found parent group.  | 
12067  | 97.8k  |                 eNCResolveMode = NCRM_WIDTH_WISE;  | 
12068  | 97.8k  |         }  | 
12069  |  |  | 
12070  |  |         // The second resolve mode is a width-wise search.  | 
12071  | 97.8k  |         if (eNCResolveMode == NCRM_WIDTH_WISE)  | 
12072  | 97.8k  |         { | 
12073  |  |             // Enqueue all direct sub-groups.  | 
12074  | 97.8k  |             int nSubGroups = 0;  | 
12075  | 97.8k  |             int *panSubGroupIds = nullptr;  | 
12076  | 97.8k  |             NCDFGetSubGroups(*pnGroupId, &nSubGroups, &panSubGroupIds);  | 
12077  | 97.8k  |             for (int i = 0; i < nSubGroups; i++)  | 
12078  | 0  |                 aoQueueGroupIdsToVisit.push(panSubGroupIds[i]);  | 
12079  | 97.8k  |             CPLFree(panSubGroupIds);  | 
12080  | 97.8k  |         }  | 
12081  | 97.8k  |     }  | 
12082  |  |  | 
12083  | 97.8k  |     if (bMandatory)  | 
12084  | 0  |     { | 
12085  | 0  |         char *pszStartGroupFullName = nullptr;  | 
12086  | 0  |         NCDFGetGroupFullName(nStartGroupId, &pszStartGroupFullName);  | 
12087  | 0  |         CPLError(CE_Failure, CPLE_AppDefined,  | 
12088  | 0  |                  "Cannot resolve mandatory %s %s from group %s",  | 
12089  | 0  |                  (pszVar ? pszVar : pszAtt),  | 
12090  | 0  |                  (pszVar ? "variable" : "attribute"),  | 
12091  | 0  |                  (pszStartGroupFullName ? pszStartGroupFullName : ""));  | 
12092  | 0  |         CPLFree(pszStartGroupFullName);  | 
12093  | 0  |     }  | 
12094  |  |  | 
12095  | 97.8k  |     *pnGroupId = -1;  | 
12096  | 97.8k  |     *pnId = -1;  | 
12097  | 97.8k  |     return CE_Failure;  | 
12098  | 99.6k  | }  | 
12099  |  |  | 
12100  |  | // Resolve a variable name from a given starting group following the CF logic:  | 
12101  |  | // - if var name is an absolute path then directly open it  | 
12102  |  | // - first search in the starting group and its parent groups  | 
12103  |  | // - then if there is no more parent group we switch to a width-wise search  | 
12104  |  | //   mode starting from the latest found parent group.  | 
12105  |  | // The full CF logic is described here:  | 
12106  |  | // https://github.com/diwg/cf2/blob/master/group/cf2-group.adoc#scope  | 
12107  |  | // If bMandatory then print an error if resolving fails.  | 
12108  |  | // TODO: implement support of relative paths.  | 
12109  |  | // TODO: to follow strictly the CF logic, when searching for a coordinate  | 
12110  |  | //       variable, we must stop the parent search mode once the corresponding  | 
12111  |  | //       dimension is found and start the width-wise search from this group.  | 
12112  |  | // TODO: to follow strictly the CF logic, when searching in width-wise mode  | 
12113  |  | //       we should skip every groups already visited during the parent  | 
12114  |  | //       search mode (but revisiting them should have no impact so we could  | 
12115  |  | //       let as it is if it is simpler...)  | 
12116  |  | // TODO: CF specifies that the width-wise search order is "left-to-right" so  | 
12117  |  | //       maybe we must sort sibling groups alphabetically? but maybe not  | 
12118  |  | //       necessary if nc_inq_grps() already sort them?  | 
12119  |  | CPLErr NCDFResolveVar(int nStartGroupId, const char *pszVar, int *pnGroupId,  | 
12120  |  |                       int *pnVarId, bool bMandatory)  | 
12121  | 110k  | { | 
12122  | 110k  |     *pnGroupId = -1;  | 
12123  | 110k  |     *pnVarId = -1;  | 
12124  | 110k  |     int nGroupId = nStartGroupId, nVarId;  | 
12125  | 110k  |     if (pszVar[0] == '/')  | 
12126  | 11.1k  |     { | 
12127  |  |         // This is an absolute path: we can open the var directly.  | 
12128  | 11.1k  |         int nRootGroupId;  | 
12129  | 11.1k  |         ERR_RET(NCDFGetRootGroup(nStartGroupId, &nRootGroupId));  | 
12130  | 11.1k  |         ERR_RET(NCDFOpenSubDataset(nRootGroupId, pszVar, &nGroupId, &nVarId));  | 
12131  | 11.1k  |     }  | 
12132  | 99.6k  |     else  | 
12133  | 99.6k  |     { | 
12134  |  |         // We have to search the variable following the CF logic.  | 
12135  | 99.6k  |         ERR_RET(NCDFResolveElem(nStartGroupId, pszVar, nullptr, &nGroupId,  | 
12136  | 99.6k  |                                 &nVarId, bMandatory));  | 
12137  | 99.6k  |     }  | 
12138  | 1.75k  |     *pnGroupId = nGroupId;  | 
12139  | 1.75k  |     *pnVarId = nVarId;  | 
12140  | 1.75k  |     return CE_None;  | 
12141  | 110k  | }  | 
12142  |  |  | 
12143  |  | // Like NCDFResolveVar but returns directly the var full name.  | 
12144  |  | static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,  | 
12145  |  |                                      char **ppszFullName, bool bMandatory)  | 
12146  | 109k  | { | 
12147  | 109k  |     *ppszFullName = nullptr;  | 
12148  | 109k  |     int nGroupId, nVarId;  | 
12149  | 109k  |     ERR_RET(  | 
12150  | 109k  |         NCDFResolveVar(nStartGroupId, pszVar, &nGroupId, &nVarId, bMandatory));  | 
12151  | 753  |     return NCDFGetVarFullName(nGroupId, nVarId, ppszFullName);  | 
12152  | 109k  | }  | 
12153  |  |  | 
12154  |  | // Like NCDFResolveVar but resolves an attribute instead a variable and  | 
12155  |  | // returns its integer value.  | 
12156  |  | // Only GLOBAL attributes are supported for the moment.  | 
12157  |  | static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,  | 
12158  |  |                                 const char *pszAtt, int *pnAtt, bool bMandatory)  | 
12159  | 0  | { | 
12160  | 0  |     int nGroupId = nStartGroupId, nAttId = nStartVarId;  | 
12161  | 0  |     ERR_RET(NCDFResolveElem(nStartGroupId, nullptr, pszAtt, &nGroupId, &nAttId,  | 
12162  | 0  |                             bMandatory));  | 
12163  | 0  |     NCDF_ERR_RET(nc_get_att_int(nGroupId, NC_GLOBAL, pszAtt, pnAtt));  | 
12164  | 0  |     return CE_None;  | 
12165  | 0  | }  | 
12166  |  |  | 
12167  |  | // Filter variables to keep only valid 2+D raster bands and vector fields in  | 
12168  |  | // a given a NetCDF (or group) ID and its sub-groups.  | 
12169  |  | // Coordinate or boundary variables are ignored.  | 
12170  |  | // It also creates corresponding vector layers.  | 
12171  |  | CPLErr netCDFDataset::FilterVars(  | 
12172  |  |     int nCdfId, bool bKeepRasters, bool bKeepVectors, char **papszIgnoreVars,  | 
12173  |  |     int *pnRasterVars, int *pnGroupId, int *pnVarId, int *pnIgnoredVars,  | 
12174  |  |     std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>  | 
12175  |  |         &oMap2DDimsToGroupAndVar)  | 
12176  | 399  | { | 
12177  | 399  |     int nVars = 0;  | 
12178  | 399  |     int nRasterVars = 0;  | 
12179  | 399  |     NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));  | 
12180  |  |  | 
12181  | 399  |     std::vector<int> anPotentialVectorVarID;  | 
12182  |  |     // oMapDimIdToCount[x] = number of times dim x is the first dimension of  | 
12183  |  |     // potential vector variables  | 
12184  | 399  |     std::map<int, int> oMapDimIdToCount;  | 
12185  | 399  |     int nVarXId = -1;  | 
12186  | 399  |     int nVarYId = -1;  | 
12187  | 399  |     int nVarZId = -1;  | 
12188  | 399  |     int nVarTimeId = -1;  | 
12189  | 399  |     int nVarTimeDimId = -1;  | 
12190  | 399  |     bool bIsVectorOnly = true;  | 
12191  | 399  |     int nProfileDimId = -1;  | 
12192  | 399  |     int nParentIndexVarID = -1;  | 
12193  |  |  | 
12194  | 6.19k  |     for (int v = 0; v < nVars; v++)  | 
12195  | 5.79k  |     { | 
12196  | 5.79k  |         int nVarDims;  | 
12197  | 5.79k  |         NCDF_ERR_RET(nc_inq_varndims(nCdfId, v, &nVarDims));  | 
12198  |  |         // Should we ignore this variable?  | 
12199  | 5.79k  |         char szTemp[NC_MAX_NAME + 1];  | 
12200  | 5.79k  |         szTemp[0] = '\0';  | 
12201  | 5.79k  |         NCDF_ERR_RET(nc_inq_varname(nCdfId, v, szTemp));  | 
12202  |  |  | 
12203  | 5.79k  |         if (strstr(szTemp, "_node_coordinates") ||  | 
12204  | 5.79k  |             strstr(szTemp, "_node_count"))  | 
12205  | 0  |         { | 
12206  |  |             // Ignore CF-1.8 Simple Geometries helper variables  | 
12207  | 0  |             continue;  | 
12208  | 0  |         }  | 
12209  |  |  | 
12210  | 5.79k  |         if (nVarDims == 1 && (NCDFIsVarLongitude(nCdfId, -1, szTemp) ||  | 
12211  | 139  |                               NCDFIsVarProjectionX(nCdfId, -1, szTemp)))  | 
12212  | 0  |         { | 
12213  | 0  |             nVarXId = v;  | 
12214  | 0  |         }  | 
12215  | 5.79k  |         else if (nVarDims == 1 && (NCDFIsVarLatitude(nCdfId, -1, szTemp) ||  | 
12216  | 139  |                                    NCDFIsVarProjectionY(nCdfId, -1, szTemp)))  | 
12217  | 0  |         { | 
12218  | 0  |             nVarYId = v;  | 
12219  | 0  |         }  | 
12220  | 5.79k  |         else if (nVarDims == 1 && NCDFIsVarVerticalCoord(nCdfId, -1, szTemp))  | 
12221  | 0  |         { | 
12222  | 0  |             nVarZId = v;  | 
12223  | 0  |         }  | 
12224  | 5.79k  |         else  | 
12225  | 5.79k  |         { | 
12226  | 5.79k  |             char *pszVarFullName = nullptr;  | 
12227  | 5.79k  |             CPLErr eErr = NCDFGetVarFullName(nCdfId, v, &pszVarFullName);  | 
12228  | 5.79k  |             if (eErr != CE_None)  | 
12229  | 0  |             { | 
12230  | 0  |                 CPLFree(pszVarFullName);  | 
12231  | 0  |                 continue;  | 
12232  | 0  |             }  | 
12233  | 5.79k  |             bool bIgnoreVar =  | 
12234  | 5.79k  |                 (CSLFindString(papszIgnoreVars, pszVarFullName) != -1);  | 
12235  | 5.79k  |             CPLFree(pszVarFullName);  | 
12236  | 5.79k  |             if (bIgnoreVar)  | 
12237  | 74  |             { | 
12238  | 74  |                 if (nVarDims == 1 && NCDFIsVarTimeCoord(nCdfId, -1, szTemp))  | 
12239  | 0  |                 { | 
12240  | 0  |                     nVarTimeId = v;  | 
12241  | 0  |                     nc_inq_vardimid(nCdfId, v, &nVarTimeDimId);  | 
12242  | 0  |                 }  | 
12243  | 74  |                 else if (nVarDims > 1)  | 
12244  | 0  |                 { | 
12245  | 0  |                     (*pnIgnoredVars)++;  | 
12246  | 0  |                     CPLDebug("GDAL_netCDF", "variable #%d [%s] was ignored", v, | 
12247  | 0  |                              szTemp);  | 
12248  | 0  |                 }  | 
12249  | 74  |             }  | 
12250  |  |             // Only accept 2+D vars.  | 
12251  | 5.72k  |             else if (nVarDims >= 2)  | 
12252  | 474  |             { | 
12253  | 474  |                 bool bRasterCandidate = true;  | 
12254  |  |                 // Identify variables that might be vector variables  | 
12255  | 474  |                 if (nVarDims == 2)  | 
12256  | 209  |                 { | 
12257  | 209  |                     int anDimIds[2] = {-1, -1}; | 
12258  | 209  |                     nc_inq_vardimid(nCdfId, v, anDimIds);  | 
12259  |  |  | 
12260  | 209  |                     nc_type vartype = NC_NAT;  | 
12261  | 209  |                     nc_inq_vartype(nCdfId, v, &vartype);  | 
12262  |  |  | 
12263  | 209  |                     char szDimNameFirst[NC_MAX_NAME + 1];  | 
12264  | 209  |                     char szDimNameSecond[NC_MAX_NAME + 1];  | 
12265  | 209  |                     szDimNameFirst[0] = '\0';  | 
12266  | 209  |                     szDimNameSecond[0] = '\0';  | 
12267  | 209  |                     if (vartype == NC_CHAR &&  | 
12268  | 209  |                         nc_inq_dimname(nCdfId, anDimIds[0], szDimNameFirst) ==  | 
12269  | 12  |                             NC_NOERR &&  | 
12270  | 209  |                         nc_inq_dimname(nCdfId, anDimIds[1], szDimNameSecond) ==  | 
12271  | 12  |                             NC_NOERR &&  | 
12272  | 209  |                         !NCDFIsVarLongitude(nCdfId, -1, szDimNameSecond) &&  | 
12273  | 209  |                         !NCDFIsVarProjectionX(nCdfId, -1, szDimNameSecond) &&  | 
12274  | 209  |                         !NCDFIsVarLatitude(nCdfId, -1, szDimNameFirst) &&  | 
12275  | 209  |                         !NCDFIsVarProjectionY(nCdfId, -1, szDimNameFirst))  | 
12276  | 12  |                     { | 
12277  | 12  |                         anPotentialVectorVarID.push_back(v);  | 
12278  | 12  |                         oMapDimIdToCount[anDimIds[0]]++;  | 
12279  | 12  |                         if (strstr(szDimNameSecond, "_max_width"))  | 
12280  | 12  |                         { | 
12281  | 12  |                             bRasterCandidate = false;  | 
12282  | 12  |                         }  | 
12283  | 0  |                         else  | 
12284  | 0  |                         { | 
12285  | 0  |                             std::array<int, 3> oKey{anDimIds[0], anDimIds[1], | 
12286  | 0  |                                                     vartype};  | 
12287  | 0  |                             oMap2DDimsToGroupAndVar[oKey].emplace_back(  | 
12288  | 0  |                                 std::pair(nCdfId, v));  | 
12289  | 0  |                         }  | 
12290  | 12  |                     }  | 
12291  | 197  |                     else  | 
12292  | 197  |                     { | 
12293  | 197  |                         std::array<int, 3> oKey{anDimIds[0], anDimIds[1], | 
12294  | 197  |                                                 vartype};  | 
12295  | 197  |                         oMap2DDimsToGroupAndVar[oKey].emplace_back(  | 
12296  | 197  |                             std::pair(nCdfId, v));  | 
12297  | 197  |                         bIsVectorOnly = false;  | 
12298  | 197  |                     }  | 
12299  | 209  |                 }  | 
12300  | 265  |                 else  | 
12301  | 265  |                 { | 
12302  | 265  |                     bIsVectorOnly = false;  | 
12303  | 265  |                 }  | 
12304  | 474  |                 if (bKeepRasters && bRasterCandidate)  | 
12305  | 0  |                 { | 
12306  | 0  |                     *pnGroupId = nCdfId;  | 
12307  | 0  |                     *pnVarId = v;  | 
12308  | 0  |                     nRasterVars++;  | 
12309  | 0  |                 }  | 
12310  | 474  |             }  | 
12311  | 5.25k  |             else if (nVarDims == 1)  | 
12312  | 138  |             { | 
12313  | 138  |                 nc_type atttype = NC_NAT;  | 
12314  | 138  |                 size_t attlen = 0;  | 
12315  | 138  |                 if (nc_inq_att(nCdfId, v, "instance_dimension", &atttype,  | 
12316  | 138  |                                &attlen) == NC_NOERR &&  | 
12317  | 138  |                     atttype == NC_CHAR && attlen < NC_MAX_NAME)  | 
12318  | 0  |                 { | 
12319  | 0  |                     char szInstanceDimension[NC_MAX_NAME + 1];  | 
12320  | 0  |                     if (nc_get_att_text(nCdfId, v, "instance_dimension",  | 
12321  | 0  |                                         szInstanceDimension) == NC_NOERR)  | 
12322  | 0  |                     { | 
12323  | 0  |                         szInstanceDimension[attlen] = 0;  | 
12324  | 0  |                         int status = nc_inq_dimid(nCdfId, szInstanceDimension,  | 
12325  | 0  |                                                   &nProfileDimId);  | 
12326  | 0  |                         if (status == NC_NOERR)  | 
12327  | 0  |                             nParentIndexVarID = v;  | 
12328  | 0  |                         else  | 
12329  | 0  |                             nProfileDimId = -1;  | 
12330  | 0  |                         if (status == NC_EBADDIM)  | 
12331  | 0  |                             CPLError(CE_Warning, CPLE_AppDefined,  | 
12332  | 0  |                                      "Attribute instance_dimension='%s' refers "  | 
12333  | 0  |                                      "to a non existing dimension",  | 
12334  | 0  |                                      szInstanceDimension);  | 
12335  | 0  |                         else  | 
12336  | 0  |                             NCDF_ERR(status);  | 
12337  | 0  |                     }  | 
12338  | 0  |                 }  | 
12339  | 138  |                 if (v != nParentIndexVarID)  | 
12340  | 138  |                 { | 
12341  | 138  |                     anPotentialVectorVarID.push_back(v);  | 
12342  | 138  |                     int nDimId = -1;  | 
12343  | 138  |                     nc_inq_vardimid(nCdfId, v, &nDimId);  | 
12344  | 138  |                     oMapDimIdToCount[nDimId]++;  | 
12345  | 138  |                 }  | 
12346  | 138  |             }  | 
12347  | 5.79k  |         }  | 
12348  | 5.79k  |     }  | 
12349  |  |  | 
12350  |  |     // If we are opened in raster-only mode and that there are only 1D or 2D  | 
12351  |  |     // variables and that the 2D variables have no X/Y dim, and all  | 
12352  |  |     // variables refer to the same main dimension (or 2 dimensions for  | 
12353  |  |     // featureType=profile), then it is a pure vector dataset  | 
12354  | 399  |     CPLString osFeatureType(  | 
12355  | 399  |         CSLFetchNameValueDef(papszMetadata, "NC_GLOBAL#featureType", ""));  | 
12356  | 399  |     if (bKeepRasters && !bKeepVectors && bIsVectorOnly && nRasterVars > 0 &&  | 
12357  | 399  |         !anPotentialVectorVarID.empty() &&  | 
12358  | 399  |         (oMapDimIdToCount.size() == 1 ||  | 
12359  | 0  |          (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2 &&  | 
12360  | 0  |           nProfileDimId >= 0)))  | 
12361  | 0  |     { | 
12362  | 0  |         anPotentialVectorVarID.resize(0);  | 
12363  | 0  |     }  | 
12364  | 399  |     else  | 
12365  | 399  |     { | 
12366  | 399  |         *pnRasterVars += nRasterVars;  | 
12367  | 399  |     }  | 
12368  |  |  | 
12369  | 399  |     if (!anPotentialVectorVarID.empty() && bKeepVectors)  | 
12370  | 61  |     { | 
12371  |  |         // Take the dimension that is referenced the most times.  | 
12372  | 61  |         if (!(oMapDimIdToCount.size() == 1 ||  | 
12373  | 61  |               (EQUAL(osFeatureType, "profile") &&  | 
12374  | 38  |                oMapDimIdToCount.size() == 2 && nProfileDimId >= 0)))  | 
12375  | 38  |         { | 
12376  | 38  |             CPLError(CE_Warning, CPLE_AppDefined,  | 
12377  | 38  |                      "The dataset has several variables that could be "  | 
12378  | 38  |                      "identified as vector fields, but not all share the same "  | 
12379  | 38  |                      "primary dimension. Consequently they will be ignored.");  | 
12380  | 38  |         }  | 
12381  | 23  |         else  | 
12382  | 23  |         { | 
12383  | 23  |             if (nVarTimeId >= 0 &&  | 
12384  | 23  |                 oMapDimIdToCount.find(nVarTimeDimId) != oMapDimIdToCount.end())  | 
12385  | 0  |             { | 
12386  | 0  |                 anPotentialVectorVarID.push_back(nVarTimeId);  | 
12387  | 0  |             }  | 
12388  | 23  |             CreateGrpVectorLayers(nCdfId, osFeatureType, anPotentialVectorVarID,  | 
12389  | 23  |                                   oMapDimIdToCount, nVarXId, nVarYId, nVarZId,  | 
12390  | 23  |                                   nProfileDimId, nParentIndexVarID,  | 
12391  | 23  |                                   bKeepRasters);  | 
12392  | 23  |         }  | 
12393  | 61  |     }  | 
12394  |  |  | 
12395  |  |     // Recurse on sub-groups.  | 
12396  | 399  |     int nSubGroups = 0;  | 
12397  | 399  |     int *panSubGroupIds = nullptr;  | 
12398  | 399  |     NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);  | 
12399  | 399  |     for (int i = 0; i < nSubGroups; i++)  | 
12400  | 0  |     { | 
12401  | 0  |         FilterVars(panSubGroupIds[i], bKeepRasters, bKeepVectors,  | 
12402  | 0  |                    papszIgnoreVars, pnRasterVars, pnGroupId, pnVarId,  | 
12403  | 0  |                    pnIgnoredVars, oMap2DDimsToGroupAndVar);  | 
12404  | 0  |     }  | 
12405  | 399  |     CPLFree(panSubGroupIds);  | 
12406  |  |  | 
12407  | 399  |     return CE_None;  | 
12408  | 399  | }  | 
12409  |  |  | 
12410  |  | // Create vector layers from given potentially identified vector variables  | 
12411  |  | // resulting from the scanning of a NetCDF (or group) ID.  | 
12412  |  | CPLErr netCDFDataset::CreateGrpVectorLayers(  | 
12413  |  |     int nCdfId, const CPLString &osFeatureType,  | 
12414  |  |     const std::vector<int> &anPotentialVectorVarID,  | 
12415  |  |     const std::map<int, int> &oMapDimIdToCount, int nVarXId, int nVarYId,  | 
12416  |  |     int nVarZId, int nProfileDimId, int nParentIndexVarID, bool bKeepRasters)  | 
12417  | 23  | { | 
12418  | 23  |     char *pszGroupName = nullptr;  | 
12419  | 23  |     NCDFGetGroupFullName(nCdfId, &pszGroupName);  | 
12420  | 23  |     if (pszGroupName == nullptr || pszGroupName[0] == '\0')  | 
12421  | 23  |     { | 
12422  | 23  |         CPLFree(pszGroupName);  | 
12423  | 23  |         pszGroupName = CPLStrdup(CPLGetBasenameSafe(osFilename).c_str());  | 
12424  | 23  |     }  | 
12425  | 23  |     OGRwkbGeometryType eGType = wkbUnknown;  | 
12426  | 23  |     CPLString osLayerName = CSLFetchNameValueDef(  | 
12427  | 23  |         papszMetadata, "NC_GLOBAL#ogr_layer_name", pszGroupName);  | 
12428  | 23  |     CPLFree(pszGroupName);  | 
12429  | 23  |     papszMetadata =  | 
12430  | 23  |         CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_name", nullptr);  | 
12431  |  |  | 
12432  | 23  |     if (EQUAL(osFeatureType, "point") || EQUAL(osFeatureType, "profile"))  | 
12433  | 0  |     { | 
12434  | 0  |         papszMetadata =  | 
12435  | 0  |             CSLSetNameValue(papszMetadata, "NC_GLOBAL#featureType", nullptr);  | 
12436  | 0  |         eGType = wkbPoint;  | 
12437  | 0  |     }  | 
12438  |  |  | 
12439  | 23  |     const char *pszLayerType =  | 
12440  | 23  |         CSLFetchNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_type");  | 
12441  | 23  |     if (pszLayerType != nullptr)  | 
12442  | 0  |     { | 
12443  | 0  |         eGType = OGRFromOGCGeomType(pszLayerType);  | 
12444  | 0  |         papszMetadata =  | 
12445  | 0  |             CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_type", nullptr);  | 
12446  | 0  |     }  | 
12447  |  |  | 
12448  | 23  |     CPLString osGeometryField =  | 
12449  | 23  |         CSLFetchNameValueDef(papszMetadata, "NC_GLOBAL#ogr_geometry_field", "");  | 
12450  | 23  |     papszMetadata =  | 
12451  | 23  |         CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_geometry_field", nullptr);  | 
12452  |  |  | 
12453  | 23  |     int nFirstVarId = -1;  | 
12454  | 23  |     int nVectorDim = oMapDimIdToCount.rbegin()->first;  | 
12455  | 23  |     if (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2)  | 
12456  | 0  |     { | 
12457  | 0  |         if (nVectorDim == nProfileDimId)  | 
12458  | 0  |             nVectorDim = oMapDimIdToCount.begin()->first;  | 
12459  | 0  |     }  | 
12460  | 23  |     else  | 
12461  | 23  |     { | 
12462  | 23  |         nProfileDimId = -1;  | 
12463  | 23  |     }  | 
12464  | 23  |     for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)  | 
12465  | 23  |     { | 
12466  | 23  |         int anDimIds[2] = {-1, -1}; | 
12467  | 23  |         nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);  | 
12468  | 23  |         if (nVectorDim == anDimIds[0])  | 
12469  | 23  |         { | 
12470  | 23  |             nFirstVarId = anPotentialVectorVarID[j];  | 
12471  | 23  |             break;  | 
12472  | 23  |         }  | 
12473  | 23  |     }  | 
12474  |  |  | 
12475  |  |     // In case where coordinates are explicitly specified for one of the  | 
12476  |  |     // field/variable, use them in priority over the ones that might have been  | 
12477  |  |     // identified above.  | 
12478  | 23  |     char *pszCoordinates = nullptr;  | 
12479  | 23  |     if (NCDFGetAttr(nCdfId, nFirstVarId, "coordinates", &pszCoordinates) ==  | 
12480  | 23  |         CE_None)  | 
12481  | 9  |     { | 
12482  | 9  |         char **papszTokens = NCDFTokenizeCoordinatesAttribute(pszCFCoordinates);  | 
12483  | 9  |         for (int i = 0; papszTokens != nullptr && papszTokens[i] != nullptr;  | 
12484  | 9  |              i++)  | 
12485  | 0  |         { | 
12486  | 0  |             if (NCDFIsVarLongitude(nCdfId, -1, papszTokens[i]) ||  | 
12487  | 0  |                 NCDFIsVarProjectionX(nCdfId, -1, papszTokens[i]))  | 
12488  | 0  |             { | 
12489  | 0  |                 nVarXId = -1;  | 
12490  | 0  |                 CPL_IGNORE_RET_VAL(  | 
12491  | 0  |                     nc_inq_varid(nCdfId, papszTokens[i], &nVarXId));  | 
12492  | 0  |             }  | 
12493  | 0  |             else if (NCDFIsVarLatitude(nCdfId, -1, papszTokens[i]) ||  | 
12494  | 0  |                      NCDFIsVarProjectionY(nCdfId, -1, papszTokens[i]))  | 
12495  | 0  |             { | 
12496  | 0  |                 nVarYId = -1;  | 
12497  | 0  |                 CPL_IGNORE_RET_VAL(  | 
12498  | 0  |                     nc_inq_varid(nCdfId, papszTokens[i], &nVarYId));  | 
12499  | 0  |             }  | 
12500  | 0  |             else if (NCDFIsVarVerticalCoord(nCdfId, -1, papszTokens[i]))  | 
12501  | 0  |             { | 
12502  | 0  |                 nVarZId = -1;  | 
12503  | 0  |                 CPL_IGNORE_RET_VAL(  | 
12504  | 0  |                     nc_inq_varid(nCdfId, papszTokens[i], &nVarZId));  | 
12505  | 0  |             }  | 
12506  | 0  |         }  | 
12507  | 9  |         CSLDestroy(papszTokens);  | 
12508  | 9  |     }  | 
12509  | 23  |     CPLFree(pszCoordinates);  | 
12510  |  |  | 
12511  |  |     // Check that the X,Y,Z vars share 1D and share the same dimension as  | 
12512  |  |     // attribute variables.  | 
12513  | 23  |     if (nVarXId >= 0 && nVarYId >= 0)  | 
12514  | 0  |     { | 
12515  | 0  |         int nVarDimCount = -1;  | 
12516  | 0  |         int nVarDimId = -1;  | 
12517  | 0  |         if (nc_inq_varndims(nCdfId, nVarXId, &nVarDimCount) != NC_NOERR ||  | 
12518  | 0  |             nVarDimCount != 1 ||  | 
12519  | 0  |             nc_inq_vardimid(nCdfId, nVarXId, &nVarDimId) != NC_NOERR ||  | 
12520  | 0  |             nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim) ||  | 
12521  | 0  |             nc_inq_varndims(nCdfId, nVarYId, &nVarDimCount) != NC_NOERR ||  | 
12522  | 0  |             nVarDimCount != 1 ||  | 
12523  | 0  |             nc_inq_vardimid(nCdfId, nVarYId, &nVarDimId) != NC_NOERR ||  | 
12524  | 0  |             nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim))  | 
12525  | 0  |         { | 
12526  | 0  |             nVarXId = nVarYId = -1;  | 
12527  | 0  |         }  | 
12528  | 0  |         else if (nVarZId >= 0 &&  | 
12529  | 0  |                  (nc_inq_varndims(nCdfId, nVarZId, &nVarDimCount) != NC_NOERR ||  | 
12530  | 0  |                   nVarDimCount != 1 ||  | 
12531  | 0  |                   nc_inq_vardimid(nCdfId, nVarZId, &nVarDimId) != NC_NOERR ||  | 
12532  | 0  |                   nVarDimId != nVectorDim))  | 
12533  | 0  |         { | 
12534  | 0  |             nVarZId = -1;  | 
12535  | 0  |         }  | 
12536  | 0  |     }  | 
12537  |  |  | 
12538  | 23  |     if (eGType == wkbUnknown && nVarXId >= 0 && nVarYId >= 0)  | 
12539  | 0  |     { | 
12540  | 0  |         eGType = wkbPoint;  | 
12541  | 0  |     }  | 
12542  | 23  |     if (eGType == wkbPoint && nVarXId >= 0 && nVarYId >= 0 && nVarZId >= 0)  | 
12543  | 0  |     { | 
12544  | 0  |         eGType = wkbPoint25D;  | 
12545  | 0  |     }  | 
12546  | 23  |     if (eGType == wkbUnknown && osGeometryField.empty())  | 
12547  | 23  |     { | 
12548  | 23  |         eGType = wkbNone;  | 
12549  | 23  |     }  | 
12550  |  |  | 
12551  |  |     // Read projection info  | 
12552  | 23  |     char **papszMetadataBackup = CSLDuplicate(papszMetadata);  | 
12553  | 23  |     ReadAttributes(nCdfId, nFirstVarId);  | 
12554  | 23  |     if (!this->bSGSupport)  | 
12555  | 23  |         SetProjectionFromVar(nCdfId, nFirstVarId, true);  | 
12556  | 23  |     const char *pszValue = FetchAttr(nCdfId, nFirstVarId, CF_GRD_MAPPING);  | 
12557  | 23  |     char *pszGridMapping = (pszValue ? CPLStrdup(pszValue) : nullptr);  | 
12558  | 23  |     CSLDestroy(papszMetadata);  | 
12559  | 23  |     papszMetadata = papszMetadataBackup;  | 
12560  |  |  | 
12561  | 23  |     OGRSpatialReference *poSRS = nullptr;  | 
12562  | 23  |     if (!m_oSRS.IsEmpty())  | 
12563  | 0  |     { | 
12564  | 0  |         poSRS = m_oSRS.Clone();  | 
12565  | 0  |     }  | 
12566  |  |     // Reset if there's a 2D raster  | 
12567  | 23  |     m_bHasProjection = false;  | 
12568  | 23  |     m_bHasGeoTransform = false;  | 
12569  |  |  | 
12570  | 23  |     if (!bKeepRasters)  | 
12571  | 23  |     { | 
12572  |  |         // Strip out uninteresting metadata.  | 
12573  | 23  |         papszMetadata =  | 
12574  | 23  |             CSLSetNameValue(papszMetadata, "NC_GLOBAL#Conventions", nullptr);  | 
12575  | 23  |         papszMetadata =  | 
12576  | 23  |             CSLSetNameValue(papszMetadata, "NC_GLOBAL#GDAL", nullptr);  | 
12577  | 23  |         papszMetadata =  | 
12578  | 23  |             CSLSetNameValue(papszMetadata, "NC_GLOBAL#history", nullptr);  | 
12579  | 23  |     }  | 
12580  |  |  | 
12581  | 23  |     std::shared_ptr<netCDFLayer> poLayer(  | 
12582  | 23  |         new netCDFLayer(this, nCdfId, osLayerName, eGType, poSRS));  | 
12583  | 23  |     if (poSRS != nullptr)  | 
12584  | 0  |         poSRS->Release();  | 
12585  | 23  |     poLayer->SetRecordDimID(nVectorDim);  | 
12586  | 23  |     if (wkbFlatten(eGType) == wkbPoint && nVarXId >= 0 && nVarYId >= 0)  | 
12587  | 0  |     { | 
12588  | 0  |         poLayer->SetXYZVars(nVarXId, nVarYId, nVarZId);  | 
12589  | 0  |     }  | 
12590  | 23  |     else if (!osGeometryField.empty())  | 
12591  | 0  |     { | 
12592  | 0  |         poLayer->SetWKTGeometryField(osGeometryField);  | 
12593  | 0  |     }  | 
12594  | 23  |     if (pszGridMapping != nullptr)  | 
12595  | 0  |     { | 
12596  | 0  |         poLayer->SetGridMapping(pszGridMapping);  | 
12597  | 0  |         CPLFree(pszGridMapping);  | 
12598  | 0  |     }  | 
12599  | 23  |     poLayer->SetProfile(nProfileDimId, nParentIndexVarID);  | 
12600  |  |  | 
12601  | 64  |     for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)  | 
12602  | 41  |     { | 
12603  | 41  |         int anDimIds[2] = {-1, -1}; | 
12604  | 41  |         nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);  | 
12605  | 41  |         if (anDimIds[0] == nVectorDim ||  | 
12606  | 41  |             (nProfileDimId >= 0 && anDimIds[0] == nProfileDimId))  | 
12607  | 41  |         { | 
12608  |  | #ifdef NCDF_DEBUG  | 
12609  |  |             char szTemp2[NC_MAX_NAME + 1] = {}; | 
12610  |  |             CPL_IGNORE_RET_VAL(  | 
12611  |  |                 nc_inq_varname(nCdfId, anPotentialVectorVarID[j], szTemp2));  | 
12612  |  |             CPLDebug("GDAL_netCDF", "Variable %s is a vector field", szTemp2); | 
12613  |  | #endif  | 
12614  | 41  |             poLayer->AddField(anPotentialVectorVarID[j]);  | 
12615  | 41  |         }  | 
12616  | 41  |     }  | 
12617  |  |  | 
12618  | 23  |     if (poLayer->GetLayerDefn()->GetFieldCount() != 0 ||  | 
12619  | 23  |         poLayer->GetGeomType() != wkbNone)  | 
12620  | 23  |     { | 
12621  | 23  |         papoLayers.push_back(poLayer);  | 
12622  | 23  |     }  | 
12623  |  |  | 
12624  | 23  |     return CE_None;  | 
12625  | 23  | }  | 
12626  |  |  | 
12627  |  | // Get all coordinate and boundary variables full names referenced in  | 
12628  |  | // a given a NetCDF (or group) ID and its sub-groups.  | 
12629  |  | // These variables are identified in other variable's  | 
12630  |  | // "coordinates" and "bounds" attribute.  | 
12631  |  | // Searching coordinate and boundary variables may need to explore  | 
12632  |  | // parents groups (or other groups in case of reference given in form of an  | 
12633  |  | // absolute path).  | 
12634  |  | // See CF sections 5.2, 5.6 and 7.1  | 
12635  |  | static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId, char ***ppapszVars)  | 
12636  | 399  | { | 
12637  | 399  |     int nVars = 0;  | 
12638  | 399  |     NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));  | 
12639  |  |  | 
12640  | 6.19k  |     for (int v = 0; v < nVars; v++)  | 
12641  | 5.79k  |     { | 
12642  | 5.79k  |         char *pszTemp = nullptr;  | 
12643  | 5.79k  |         char **papszTokens = nullptr;  | 
12644  | 5.79k  |         if (NCDFGetAttr(nCdfId, v, "coordinates", &pszTemp) == CE_None)  | 
12645  | 92  |             papszTokens = NCDFTokenizeCoordinatesAttribute(pszTemp);  | 
12646  | 5.79k  |         CPLFree(pszTemp);  | 
12647  | 5.79k  |         pszTemp = nullptr;  | 
12648  | 5.79k  |         if (NCDFGetAttr(nCdfId, v, "bounds", &pszTemp) == CE_None &&  | 
12649  | 5.79k  |             pszTemp != nullptr && !EQUAL(pszTemp, ""))  | 
12650  | 0  |             papszTokens = CSLAddString(papszTokens, pszTemp);  | 
12651  | 5.79k  |         CPLFree(pszTemp);  | 
12652  | 115k  |         for (int i = 0; papszTokens != nullptr && papszTokens[i] != nullptr;  | 
12653  | 109k  |              i++)  | 
12654  | 109k  |         { | 
12655  | 109k  |             char *pszVarFullName = nullptr;  | 
12656  | 109k  |             if (NCDFResolveVarFullName(nCdfId, papszTokens[i],  | 
12657  | 109k  |                                        &pszVarFullName) == CE_None)  | 
12658  | 753  |                 *ppapszVars = CSLAddString(*ppapszVars, pszVarFullName);  | 
12659  | 109k  |             CPLFree(pszVarFullName);  | 
12660  | 109k  |         }  | 
12661  | 5.79k  |         CSLDestroy(papszTokens);  | 
12662  | 5.79k  |     }  | 
12663  |  |  | 
12664  |  |     // Recurse on sub-groups.  | 
12665  | 399  |     int nSubGroups;  | 
12666  | 399  |     int *panSubGroupIds = nullptr;  | 
12667  | 399  |     NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);  | 
12668  | 399  |     for (int i = 0; i < nSubGroups; i++)  | 
12669  | 0  |     { | 
12670  | 0  |         NCDFGetCoordAndBoundVarFullNames(panSubGroupIds[i], ppapszVars);  | 
12671  | 0  |     }  | 
12672  | 399  |     CPLFree(panSubGroupIds);  | 
12673  |  |  | 
12674  | 399  |     return CE_None;  | 
12675  | 399  | }  | 
12676  |  |  | 
12677  |  | // Check if give type is user defined  | 
12678  |  | bool NCDFIsUserDefinedType(int /*ncid*/, int type)  | 
12679  | 0  | { | 
12680  | 0  |     return type >= NC_FIRSTUSERTYPEID;  | 
12681  | 0  | }  | 
12682  |  |  | 
12683  |  | char **NCDFTokenizeCoordinatesAttribute(const char *pszCoordinates)  | 
12684  | 101  | { | 
12685  |  |     // CF conventions use space as the separator for variable names in the  | 
12686  |  |     // coordinates attribute, but some products such as  | 
12687  |  |     // https://sentinel.esa.int/web/sentinel/technical-guides/sentinel-3-synergy/products-algorithms/level-2-aod-algorithms-and-products/level-2-aod-products-description  | 
12688  |  |     // use comma.  | 
12689  | 101  |     return CSLTokenizeString2(pszCoordinates, ", ", 0);  | 
12690  | 101  | }  |