Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: netCDF read/write Driver
4 : * Purpose: GDAL bindings over netCDF library.
5 : * Author: Frank Warmerdam, warmerdam@pobox.com
6 : * Even Rouault <even.rouault at spatialys.com>
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2004, Frank Warmerdam
10 : * Copyright (c) 2007-2016, Even Rouault <even.rouault at spatialys.com>
11 : * Copyright (c) 2010, Kyle Shannon <kyle at pobox dot com>
12 : * Copyright (c) 2021, CLS
13 : *
14 : * SPDX-License-Identifier: MIT
15 : ****************************************************************************/
16 :
17 : #include "cpl_port.h"
18 :
19 : #include <array>
20 : #include <cassert>
21 : #include <cctype>
22 : #include <cerrno>
23 : #include <climits>
24 : #include <cmath>
25 : #include <cstdio>
26 : #include <cstdlib>
27 : #include <cstring>
28 : #include <ctime>
29 : #include <algorithm>
30 : #include <limits>
31 : #include <map>
32 : #include <mutex>
33 : #include <set>
34 : #include <queue>
35 : #include <string>
36 : #include <tuple>
37 : #include <utility>
38 : #include <vector>
39 :
40 : // Must be included after standard includes, otherwise VS2015 fails when
41 : // including <ctime>
42 : #include "netcdfdataset.h"
43 : #include "netcdfdrivercore.h"
44 : #include "netcdfsg.h"
45 : #include "netcdfuffd.h"
46 :
47 : #include "netcdf_mem.h"
48 :
49 : #include "cpl_conv.h"
50 : #include "cpl_error.h"
51 : #include "cpl_float.h"
52 : #include "cpl_json.h"
53 : #include "cpl_minixml.h"
54 : #include "cpl_multiproc.h"
55 : #include "cpl_progress.h"
56 : #include "cpl_time.h"
57 : #include "gdal.h"
58 : #include "gdal_frmts.h"
59 : #include "gdal_priv_templates.hpp"
60 : #include "ogr_core.h"
61 : #include "ogr_srs_api.h"
62 :
63 : // netCDF 4.8 switched to expecting filenames in UTF-8 on Windows
64 : // But with netCDF 4.9 and https://github.com/Unidata/netcdf-c/pull/2277/,
65 : // this is apparently back to expecting filenames in current codepage...
66 : // Detect netCDF 4.8 with NC_ENCZARR
67 : // Detect netCDF 4.9 with NC_NOATTCREORD
68 : #if defined(NC_ENCZARR) && !defined(NC_NOATTCREORD)
69 : #define NETCDF_USES_UTF8
70 : #endif
71 :
72 : // Internal function declarations.
73 :
74 : static bool NCDFIsGDALVersionGTE(const char *pszVersion, int nTarget);
75 :
76 : static void
77 : NCDFAddGDALHistory(int fpImage, const char *pszFilename, bool bWriteGDALVersion,
78 : bool bWriteGDALHistory, const char *pszOldHist,
79 : const char *pszFunctionName,
80 : const char *pszCFVersion = GDAL_DEFAULT_NCDF_CONVENTIONS);
81 :
82 : static void NCDFAddHistory(int fpImage, const char *pszAddHist,
83 : const char *pszOldHist);
84 :
85 : static CPLErr NCDFSafeStrcat(char **ppszDest, const char *pszSrc,
86 : size_t *nDestSize);
87 :
88 : // Var / attribute helper functions.
89 : static CPLErr NCDFPutAttr(int nCdfId, int nVarId, const char *pszAttrName,
90 : const char *pszValue);
91 :
92 : // Replace this where used.
93 : static CPLErr NCDFGet1DVar(int nCdfId, int nVarId, char **pszValue);
94 : static CPLErr NCDFPut1DVar(int nCdfId, int nVarId, const char *pszValue);
95 :
96 : // Replace this where used.
97 : static 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 674 : int GDAL_nc_open(const char *pszFilename, int nMode, int *pID)
137 : {
138 1348 : std::string osKey(pszFilename);
139 674 : osKey += "#####";
140 674 : osKey += std::to_string(nMode);
141 674 : auto oIter = goMapNameToNetCDFId.find(osKey);
142 674 : if (oIter == goMapNameToNetCDFId.end())
143 : {
144 624 : int ret = nc_open(pszFilename, nMode, pID);
145 624 : if (ret != NC_NOERR)
146 3 : return ret;
147 621 : goMapNameToNetCDFId[osKey] = *pID;
148 621 : goMapNetCDFIdToKeyAndCount[*pID] =
149 1242 : std::pair<std::string, int>(osKey, 1);
150 621 : return ret;
151 : }
152 : else
153 : {
154 50 : *pID = oIter->second;
155 50 : goMapNetCDFIdToKeyAndCount[oIter->second].second++;
156 50 : return NC_NOERR;
157 : }
158 : }
159 :
160 931 : int GDAL_nc_close(int cdfid)
161 : {
162 931 : int ret = NC_NOERR;
163 931 : auto oIter = goMapNetCDFIdToKeyAndCount.find(cdfid);
164 931 : if (oIter != goMapNetCDFIdToKeyAndCount.end())
165 : {
166 671 : if (--oIter->second.second == 0)
167 : {
168 621 : ret = nc_close(cdfid);
169 621 : goMapNameToNetCDFId.erase(oIter->second.first);
170 621 : goMapNetCDFIdToKeyAndCount.erase(oIter);
171 : }
172 : }
173 : else
174 : {
175 : // we can go here if file opened with nc_open_mem() or nc_create()
176 260 : ret = nc_close(cdfid);
177 : }
178 931 : return ret;
179 : }
180 :
181 : /************************************************************************/
182 : /* ==================================================================== */
183 : /* netCDFRasterBand */
184 : /* ==================================================================== */
185 : /************************************************************************/
186 :
187 : class netCDFRasterBand final : public GDALPamRasterBand
188 : {
189 : friend class netCDFDataset;
190 :
191 : nc_type nc_datatype;
192 : int cdfid;
193 : int nZId;
194 : int nZDim;
195 : int nLevel;
196 : int nBandXPos;
197 : int nBandYPos;
198 : int *panBandZPos;
199 : int *panBandZLev;
200 : bool m_bNoDataSet = false;
201 : double m_dfNoDataValue = 0;
202 : bool m_bNoDataSetAsInt64 = false;
203 : int64_t m_nNodataValueInt64 = 0;
204 : bool m_bNoDataSetAsUInt64 = false;
205 : uint64_t m_nNodataValueUInt64 = 0;
206 : bool bValidRangeValid = false;
207 : double adfValidRange[2]{0, 0};
208 : bool m_bHaveScale = false;
209 : bool m_bHaveOffset = false;
210 : double m_dfScale = 1;
211 : double m_dfOffset = 0;
212 : CPLString m_osUnitType{};
213 : bool bSignedData;
214 : bool bCheckLongitude;
215 : bool m_bCreateMetadataFromOtherVarsDone = false;
216 :
217 : void CreateMetadataFromAttributes();
218 : void CreateMetadataFromOtherVars();
219 :
220 : template <class T>
221 : void CheckData(void *pImage, void *pImageNC, size_t nTmpBlockXSize,
222 : size_t nTmpBlockYSize, bool bCheckIsNan = false);
223 : template <class T>
224 : void CheckDataCpx(void *pImage, void *pImageNC, size_t nTmpBlockXSize,
225 : size_t nTmpBlockYSize, bool bCheckIsNan = false);
226 : void SetBlockSize();
227 :
228 : bool FetchNetcdfChunk(size_t xstart, size_t ystart, void *pImage);
229 :
230 : void SetNoDataValueNoUpdate(double dfNoData);
231 : void SetNoDataValueNoUpdate(int64_t nNoData);
232 : void SetNoDataValueNoUpdate(uint64_t nNoData);
233 :
234 : void SetOffsetNoUpdate(double dfVal);
235 : void SetScaleNoUpdate(double dfVal);
236 : void SetUnitTypeNoUpdate(const char *pszNewValue);
237 :
238 : protected:
239 : CPLXMLNode *SerializeToXML(const char *pszUnused) override;
240 :
241 : public:
242 : struct CONSTRUCTOR_OPEN
243 : {
244 : };
245 :
246 : struct CONSTRUCTOR_CREATE
247 : {
248 : };
249 :
250 : netCDFRasterBand(const CONSTRUCTOR_OPEN &, netCDFDataset *poDS,
251 : int nGroupId, int nZId, int nZDim, int nLevel,
252 : const int *panBandZLen, const int *panBandPos, int nBand);
253 : netCDFRasterBand(const CONSTRUCTOR_CREATE &, netCDFDataset *poDS,
254 : GDALDataType eType, int nBand, bool bSigned = true,
255 : const char *pszBandName = nullptr,
256 : const char *pszLongName = nullptr, int nZId = -1,
257 : int nZDim = 2, int nLevel = 0,
258 : const int *panBandZLev = nullptr,
259 : const int *panBandZPos = nullptr,
260 : const int *paDimIds = nullptr);
261 : 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 481 : netCDFRasterBand::netCDFRasterBand(const netCDFRasterBand::CONSTRUCTOR_OPEN &,
294 : netCDFDataset *poNCDFDS, int nGroupId,
295 : int nZIdIn, int nZDimIn, int nLevelIn,
296 : const int *panBandZLevIn,
297 481 : const int *panBandZPosIn, int nBandIn)
298 : : nc_datatype(NC_NAT), cdfid(nGroupId), nZId(nZIdIn), nZDim(nZDimIn),
299 481 : nLevel(nLevelIn), nBandXPos(panBandZPosIn[0]),
300 481 : nBandYPos(nZDim == 1 ? -1 : panBandZPosIn[1]), panBandZPos(nullptr),
301 : panBandZLev(nullptr),
302 : bSignedData(true), // Default signed, except for Byte.
303 962 : bCheckLongitude(false)
304 : {
305 481 : poDS = poNCDFDS;
306 481 : nBand = nBandIn;
307 :
308 : // Take care of all other dimensions.
309 481 : if (nZDim > 2)
310 : {
311 176 : panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
312 176 : panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
313 :
314 478 : for (int i = 0; i < nZDim - 2; i++)
315 : {
316 302 : panBandZPos[i] = panBandZPosIn[i + 2];
317 302 : panBandZLev[i] = panBandZLevIn[i];
318 : }
319 : }
320 :
321 481 : nRasterXSize = poDS->GetRasterXSize();
322 481 : nRasterYSize = poDS->GetRasterYSize();
323 481 : nBlockXSize = poDS->GetRasterXSize();
324 481 : nBlockYSize = 1;
325 :
326 : // Get the type of the "z" variable, our target raster array.
327 481 : if (nc_inq_var(cdfid, nZId, nullptr, &nc_datatype, nullptr, nullptr,
328 481 : nullptr) != NC_NOERR)
329 : {
330 0 : CPLError(CE_Failure, CPLE_AppDefined, "Error in nc_var_inq() on 'z'.");
331 0 : return;
332 : }
333 :
334 481 : if (NCDFIsUserDefinedType(cdfid, nc_datatype))
335 : {
336 : // First enquire and check that the number of fields is 2
337 : size_t nfields, compoundsize;
338 5 : if (nc_inq_compound(cdfid, nc_datatype, nullptr, &compoundsize,
339 5 : &nfields) != NC_NOERR)
340 : {
341 0 : CPLError(CE_Failure, CPLE_AppDefined,
342 : "Error in nc_inq_compound() on 'z'.");
343 0 : return;
344 : }
345 :
346 5 : if (nfields != 2)
347 : {
348 0 : CPLError(CE_Failure, CPLE_AppDefined,
349 : "Unsupported data type encountered in nc_inq_compound() "
350 : "on 'z'.");
351 0 : return;
352 : }
353 :
354 : // Now check that that two types are the same in the struct.
355 : nc_type field_type1, field_type2;
356 : int field_dims1, field_dims2;
357 5 : if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
358 : &field_type1, &field_dims1,
359 5 : nullptr) != NC_NOERR)
360 : {
361 0 : CPLError(
362 : CE_Failure, CPLE_AppDefined,
363 : "Error in querying Field 1 in nc_inq_compound_field() on 'z'.");
364 0 : return;
365 : }
366 :
367 5 : if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
368 : &field_type2, &field_dims2,
369 5 : nullptr) != NC_NOERR)
370 : {
371 0 : CPLError(
372 : CE_Failure, CPLE_AppDefined,
373 : "Error in querying Field 2 in nc_inq_compound_field() on 'z'.");
374 0 : return;
375 : }
376 :
377 5 : if ((field_type1 != field_type2) || (field_dims1 != field_dims2) ||
378 5 : (field_dims1 != 0))
379 : {
380 0 : CPLError(CE_Failure, CPLE_AppDefined,
381 : "Error in interpreting compound data type on 'z'.");
382 0 : return;
383 : }
384 :
385 5 : if (field_type1 == NC_SHORT)
386 0 : eDataType = GDT_CInt16;
387 5 : else if (field_type1 == NC_INT)
388 0 : eDataType = GDT_CInt32;
389 5 : else if (field_type1 == NC_FLOAT)
390 4 : eDataType = GDT_CFloat32;
391 1 : else if (field_type1 == NC_DOUBLE)
392 1 : eDataType = GDT_CFloat64;
393 : else
394 : {
395 0 : CPLError(CE_Failure, CPLE_AppDefined,
396 : "Unsupported netCDF compound data type encountered.");
397 0 : return;
398 : }
399 : }
400 : else
401 : {
402 476 : if (nc_datatype == NC_BYTE)
403 146 : eDataType = GDT_Byte;
404 330 : else if (nc_datatype == NC_CHAR)
405 0 : eDataType = GDT_Byte;
406 330 : else if (nc_datatype == NC_SHORT)
407 41 : eDataType = GDT_Int16;
408 289 : else if (nc_datatype == NC_INT)
409 89 : eDataType = GDT_Int32;
410 200 : else if (nc_datatype == NC_FLOAT)
411 123 : eDataType = GDT_Float32;
412 77 : else if (nc_datatype == NC_DOUBLE)
413 40 : eDataType = GDT_Float64;
414 37 : else if (nc_datatype == NC_UBYTE)
415 15 : eDataType = GDT_Byte;
416 22 : else if (nc_datatype == NC_USHORT)
417 4 : eDataType = GDT_UInt16;
418 18 : else if (nc_datatype == NC_UINT)
419 3 : eDataType = GDT_UInt32;
420 15 : else if (nc_datatype == NC_INT64)
421 8 : eDataType = GDT_Int64;
422 7 : else if (nc_datatype == NC_UINT64)
423 7 : eDataType = GDT_UInt64;
424 : else
425 : {
426 0 : if (nBand == 1)
427 0 : CPLError(CE_Warning, CPLE_AppDefined,
428 : "Unsupported netCDF datatype (%d), treat as Float32.",
429 0 : static_cast<int>(nc_datatype));
430 0 : eDataType = GDT_Float32;
431 0 : nc_datatype = NC_FLOAT;
432 : }
433 : }
434 :
435 : // Find and set No Data for this variable.
436 481 : nc_type atttype = NC_NAT;
437 481 : size_t attlen = 0;
438 481 : const char *pszNoValueName = nullptr;
439 :
440 : // Find attribute name, either _FillValue or missing_value.
441 481 : int status = nc_inq_att(cdfid, nZId, NCDF_FillValue, &atttype, &attlen);
442 481 : if (status == NC_NOERR)
443 : {
444 248 : pszNoValueName = NCDF_FillValue;
445 : }
446 : else
447 : {
448 233 : status = nc_inq_att(cdfid, nZId, "missing_value", &atttype, &attlen);
449 233 : if (status == NC_NOERR)
450 : {
451 12 : pszNoValueName = "missing_value";
452 : }
453 : }
454 :
455 : // Fetch missing value.
456 481 : double dfNoData = 0.0;
457 481 : bool bGotNoData = false;
458 481 : int64_t nNoDataAsInt64 = 0;
459 481 : bool bGotNoDataAsInt64 = false;
460 481 : uint64_t nNoDataAsUInt64 = 0;
461 481 : bool bGotNoDataAsUInt64 = false;
462 481 : if (status == NC_NOERR)
463 : {
464 260 : nc_type nAttrType = NC_NAT;
465 260 : size_t nAttrLen = 0;
466 260 : status = nc_inq_att(cdfid, nZId, pszNoValueName, &nAttrType, &nAttrLen);
467 260 : if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_INT64)
468 : {
469 : long long v;
470 7 : nc_get_att_longlong(cdfid, nZId, pszNoValueName, &v);
471 7 : bGotNoData = true;
472 7 : bGotNoDataAsInt64 = true;
473 7 : nNoDataAsInt64 = static_cast<int64_t>(v);
474 : }
475 253 : else if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_UINT64)
476 : {
477 : unsigned long long v;
478 7 : nc_get_att_ulonglong(cdfid, nZId, pszNoValueName, &v);
479 7 : bGotNoData = true;
480 7 : bGotNoDataAsUInt64 = true;
481 7 : nNoDataAsUInt64 = static_cast<uint64_t>(v);
482 : }
483 246 : else if (NCDFGetAttr(cdfid, nZId, pszNoValueName, &dfNoData) == CE_None)
484 : {
485 245 : bGotNoData = true;
486 : }
487 : }
488 :
489 : // If NoData was not found, use the default value, but for non-Byte types
490 : // as it is not recommended:
491 : // https://www.unidata.ucar.edu/software/netcdf/docs/attribute_conventions.html
492 481 : nc_type vartype = NC_NAT;
493 481 : if (!bGotNoData)
494 : {
495 222 : nc_inq_vartype(cdfid, nZId, &vartype);
496 222 : if (vartype == NC_INT64)
497 : {
498 : nNoDataAsInt64 =
499 1 : NCDFGetDefaultNoDataValueAsInt64(cdfid, nZId, bGotNoData);
500 1 : bGotNoDataAsInt64 = bGotNoData;
501 : }
502 221 : else if (vartype == NC_UINT64)
503 : {
504 : nNoDataAsUInt64 =
505 0 : NCDFGetDefaultNoDataValueAsUInt64(cdfid, nZId, bGotNoData);
506 0 : bGotNoDataAsUInt64 = bGotNoData;
507 : }
508 221 : else if (vartype != NC_CHAR && vartype != NC_BYTE &&
509 98 : vartype != NC_UBYTE)
510 : {
511 89 : dfNoData =
512 89 : NCDFGetDefaultNoDataValue(cdfid, nZId, vartype, bGotNoData);
513 89 : if (bGotNoData)
514 : {
515 78 : CPLDebug("GDAL_netCDF",
516 : "did not get nodata value for variable #%d, using "
517 : "default %f",
518 : nZId, dfNoData);
519 : }
520 : }
521 : }
522 :
523 481 : bool bHasUnderscoreUnsignedAttr = false;
524 481 : bool bUnderscoreUnsignedAttrVal = false;
525 : {
526 481 : char *pszTemp = nullptr;
527 481 : if (NCDFGetAttr(cdfid, nZId, "_Unsigned", &pszTemp) == CE_None)
528 : {
529 138 : if (EQUAL(pszTemp, "true"))
530 : {
531 130 : bHasUnderscoreUnsignedAttr = true;
532 130 : bUnderscoreUnsignedAttrVal = true;
533 : }
534 8 : else if (EQUAL(pszTemp, "false"))
535 : {
536 8 : bHasUnderscoreUnsignedAttr = true;
537 8 : bUnderscoreUnsignedAttrVal = false;
538 : }
539 138 : CPLFree(pszTemp);
540 : }
541 : }
542 :
543 : // Look for valid_range or valid_min/valid_max.
544 :
545 : // First look for valid_range.
546 481 : if (CPLFetchBool(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE", true))
547 : {
548 479 : char *pszValidRange = nullptr;
549 479 : if (NCDFGetAttr(cdfid, nZId, "valid_range", &pszValidRange) ==
550 131 : CE_None &&
551 610 : pszValidRange[0] == '{' &&
552 131 : pszValidRange[strlen(pszValidRange) - 1] == '}')
553 : {
554 : const std::string osValidRange =
555 393 : std::string(pszValidRange).substr(1, strlen(pszValidRange) - 2);
556 : const CPLStringList aosValidRange(
557 262 : CSLTokenizeString2(osValidRange.c_str(), ",", 0));
558 131 : if (aosValidRange.size() == 2 &&
559 262 : CPLGetValueType(aosValidRange[0]) != CPL_VALUE_STRING &&
560 131 : CPLGetValueType(aosValidRange[1]) != CPL_VALUE_STRING)
561 : {
562 131 : bValidRangeValid = true;
563 131 : adfValidRange[0] = CPLAtof(aosValidRange[0]);
564 131 : adfValidRange[1] = CPLAtof(aosValidRange[1]);
565 : }
566 : }
567 479 : CPLFree(pszValidRange);
568 :
569 : // If not found look for valid_min and valid_max.
570 479 : if (!bValidRangeValid)
571 : {
572 348 : double dfMin = 0;
573 348 : double dfMax = 0;
574 363 : if (NCDFGetAttr(cdfid, nZId, "valid_min", &dfMin) == CE_None &&
575 15 : NCDFGetAttr(cdfid, nZId, "valid_max", &dfMax) == CE_None)
576 : {
577 8 : adfValidRange[0] = dfMin;
578 8 : adfValidRange[1] = dfMax;
579 8 : bValidRangeValid = true;
580 : }
581 : }
582 :
583 479 : if (bValidRangeValid &&
584 139 : (adfValidRange[0] < 0 || adfValidRange[1] < 0) &&
585 17 : nc_datatype == NC_SHORT && bHasUnderscoreUnsignedAttr &&
586 : bUnderscoreUnsignedAttrVal)
587 : {
588 2 : if (adfValidRange[0] < 0)
589 0 : adfValidRange[0] += 65536;
590 2 : if (adfValidRange[1] < 0)
591 2 : adfValidRange[1] += 65536;
592 2 : if (adfValidRange[0] <= adfValidRange[1])
593 : {
594 : // Updating metadata item
595 2 : GDALPamRasterBand::SetMetadataItem(
596 : "valid_range",
597 2 : CPLSPrintf("{%d,%d}", static_cast<int>(adfValidRange[0]),
598 2 : static_cast<int>(adfValidRange[1])));
599 : }
600 : }
601 :
602 479 : if (bValidRangeValid && adfValidRange[0] > adfValidRange[1])
603 : {
604 0 : CPLError(CE_Warning, CPLE_AppDefined,
605 : "netCDFDataset::valid_range: min > max:\n"
606 : " min: %lf\n max: %lf\n",
607 : adfValidRange[0], adfValidRange[1]);
608 0 : bValidRangeValid = false;
609 0 : adfValidRange[0] = 0.0;
610 0 : adfValidRange[1] = 0.0;
611 : }
612 : }
613 :
614 : // Special For Byte Bands: check for signed/unsigned byte.
615 481 : if (nc_datatype == NC_BYTE)
616 : {
617 : // netcdf uses signed byte by default, but GDAL uses unsigned by default
618 : // This may cause unexpected results, but is needed for back-compat.
619 146 : if (poNCDFDS->bIsGdalFile)
620 124 : bSignedData = false;
621 : else
622 22 : bSignedData = true;
623 :
624 : // For NC4 format NC_BYTE is (normally) signed, NC_UBYTE is unsigned.
625 : // But in case a NC3 file was converted automatically and has hints
626 : // that it is unsigned, take them into account
627 146 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
628 : {
629 3 : bSignedData = true;
630 : }
631 :
632 : // If we got valid_range, test for signed/unsigned range.
633 : // https://docs.unidata.ucar.edu/netcdf-c/current/attribute_conventions.html
634 146 : if (bValidRangeValid)
635 : {
636 : // If we got valid_range={0,255}, treat as unsigned.
637 127 : if (adfValidRange[0] == 0 && adfValidRange[1] == 255)
638 : {
639 119 : bSignedData = false;
640 : // Reset valid_range.
641 119 : bValidRangeValid = false;
642 : }
643 : // If we got valid_range={-128,127}, treat as signed.
644 8 : else if (adfValidRange[0] == -128 && adfValidRange[1] == 127)
645 : {
646 8 : bSignedData = true;
647 : // Reset valid_range.
648 8 : bValidRangeValid = false;
649 : }
650 : }
651 : // Else test for _Unsigned.
652 : // https://docs.unidata.ucar.edu/nug/current/best_practices.html
653 : else
654 : {
655 19 : if (bHasUnderscoreUnsignedAttr)
656 7 : bSignedData = !bUnderscoreUnsignedAttrVal;
657 : }
658 :
659 146 : if (bSignedData)
660 : {
661 20 : eDataType = GDT_Int8;
662 : }
663 126 : else if (dfNoData < 0)
664 : {
665 : // Fix nodata value as it was stored signed.
666 6 : dfNoData += 256;
667 6 : if (pszNoValueName)
668 : {
669 : // Updating metadata item
670 6 : GDALPamRasterBand::SetMetadataItem(
671 : pszNoValueName,
672 : CPLSPrintf("%d", static_cast<int>(dfNoData)));
673 : }
674 : }
675 : }
676 335 : else if (nc_datatype == NC_SHORT)
677 : {
678 41 : if (bHasUnderscoreUnsignedAttr)
679 : {
680 4 : bSignedData = !bUnderscoreUnsignedAttrVal;
681 4 : if (!bSignedData)
682 4 : eDataType = GDT_UInt16;
683 : }
684 :
685 : // Fix nodata value as it was stored signed.
686 41 : if (!bSignedData && dfNoData < 0)
687 : {
688 4 : dfNoData += 65536;
689 4 : if (pszNoValueName)
690 : {
691 : // Updating metadata item
692 4 : GDALPamRasterBand::SetMetadataItem(
693 : pszNoValueName,
694 : CPLSPrintf("%d", static_cast<int>(dfNoData)));
695 : }
696 : }
697 : }
698 :
699 294 : else if (nc_datatype == NC_UBYTE || nc_datatype == NC_USHORT ||
700 275 : nc_datatype == NC_UINT || nc_datatype == NC_UINT64)
701 : {
702 29 : bSignedData = false;
703 : }
704 :
705 481 : CPLDebug("GDAL_netCDF", "netcdf type=%d gdal type=%d signedByte=%d",
706 481 : nc_datatype, eDataType, static_cast<int>(bSignedData));
707 :
708 481 : if (bGotNoData)
709 : {
710 : // Set nodata value.
711 338 : if (bGotNoDataAsInt64)
712 : {
713 8 : if (eDataType == GDT_Int64)
714 : {
715 8 : SetNoDataValueNoUpdate(nNoDataAsInt64);
716 : }
717 0 : else if (eDataType == GDT_UInt64 && nNoDataAsInt64 >= 0)
718 : {
719 0 : SetNoDataValueNoUpdate(static_cast<uint64_t>(nNoDataAsInt64));
720 : }
721 : else
722 : {
723 0 : SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsInt64));
724 : }
725 : }
726 330 : else if (bGotNoDataAsUInt64)
727 : {
728 7 : if (eDataType == GDT_UInt64)
729 : {
730 7 : SetNoDataValueNoUpdate(nNoDataAsUInt64);
731 : }
732 0 : else if (eDataType == GDT_Int64 &&
733 : nNoDataAsUInt64 <=
734 0 : static_cast<uint64_t>(
735 0 : std::numeric_limits<int64_t>::max()))
736 : {
737 0 : SetNoDataValueNoUpdate(static_cast<int64_t>(nNoDataAsUInt64));
738 : }
739 : else
740 : {
741 0 : SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsUInt64));
742 : }
743 : }
744 : else
745 : {
746 : #ifdef NCDF_DEBUG
747 : CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) read", dfNoData);
748 : #endif
749 323 : if (eDataType == GDT_Int64 && GDALIsValueExactAs<int64_t>(dfNoData))
750 : {
751 0 : SetNoDataValueNoUpdate(static_cast<int64_t>(dfNoData));
752 : }
753 323 : else if (eDataType == GDT_UInt64 &&
754 0 : GDALIsValueExactAs<uint64_t>(dfNoData))
755 : {
756 0 : SetNoDataValueNoUpdate(static_cast<uint64_t>(dfNoData));
757 : }
758 : else
759 : {
760 323 : SetNoDataValueNoUpdate(dfNoData);
761 : }
762 : }
763 : }
764 :
765 481 : 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 481 : if (nc_inq_attid(cdfid, nZId, CF_ADD_OFFSET, nullptr) == NC_NOERR)
771 : {
772 16 : double dfOffset = 0;
773 16 : status = nc_get_att_double(cdfid, nZId, CF_ADD_OFFSET, &dfOffset);
774 16 : CPLDebug("GDAL_netCDF", "got add_offset=%.16g, status=%d", dfOffset,
775 : status);
776 16 : SetOffsetNoUpdate(dfOffset);
777 : }
778 :
779 481 : bool bHasScale = false;
780 481 : if (nc_inq_attid(cdfid, nZId, CF_SCALE_FACTOR, nullptr) == NC_NOERR)
781 : {
782 20 : bHasScale = true;
783 20 : double dfScale = 1;
784 20 : status = nc_get_att_double(cdfid, nZId, CF_SCALE_FACTOR, &dfScale);
785 20 : CPLDebug("GDAL_netCDF", "got scale_factor=%.16g, status=%d", dfScale,
786 : status);
787 20 : SetScaleNoUpdate(dfScale);
788 : }
789 :
790 12 : if (bValidRangeValid && GDALDataTypeIsInteger(eDataType) &&
791 4 : eDataType != GDT_Int64 && eDataType != GDT_UInt64 &&
792 4 : (std::fabs(std::round(adfValidRange[0]) - adfValidRange[0]) > 1e-5 ||
793 493 : std::fabs(std::round(adfValidRange[1]) - adfValidRange[1]) > 1e-5) &&
794 1 : CSLFetchNameValue(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE") ==
795 : nullptr)
796 : {
797 1 : CPLError(CE_Warning, CPLE_AppDefined,
798 : "validity range = %f, %f contains floating-point values, "
799 : "whereas data type is integer. valid_range is thus likely "
800 : "wrong%s. Ignoring it.",
801 : adfValidRange[0], adfValidRange[1],
802 : bHasScale ? " (likely scaled using scale_factor/add_factor "
803 : "whereas it should be using the packed data type)"
804 : : "");
805 1 : bValidRangeValid = false;
806 1 : adfValidRange[0] = 0.0;
807 1 : adfValidRange[1] = 0.0;
808 : }
809 :
810 : // Should we check for longitude values > 360?
811 481 : bCheckLongitude =
812 962 : CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")) &&
813 481 : NCDFIsVarLongitude(cdfid, nZId, nullptr);
814 :
815 : // Attempt to fetch the units attribute for the variable and set it.
816 481 : SetUnitTypeNoUpdate(netCDFRasterBand::GetMetadataItem(CF_UNITS));
817 :
818 481 : SetBlockSize();
819 : }
820 :
821 663 : void netCDFRasterBand::SetBlockSize()
822 : {
823 : // Check for variable chunking (netcdf-4 only).
824 : // GDAL block size should be set to hdf5 chunk size.
825 663 : int nTmpFormat = 0;
826 663 : int status = nc_inq_format(cdfid, &nTmpFormat);
827 663 : NetCDFFormatEnum eTmpFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
828 663 : if ((status == NC_NOERR) &&
829 566 : (eTmpFormat == NCDF_FORMAT_NC4 || eTmpFormat == NCDF_FORMAT_NC4C))
830 : {
831 113 : size_t chunksize[MAX_NC_DIMS] = {};
832 : // Check for chunksize and set it as the blocksize (optimizes read).
833 113 : status = nc_inq_var_chunking(cdfid, nZId, &nTmpFormat, chunksize);
834 113 : if ((status == NC_NOERR) && (nTmpFormat == NC_CHUNKED))
835 : {
836 13 : nBlockXSize = (int)chunksize[nZDim - 1];
837 13 : if (nZDim >= 2)
838 13 : nBlockYSize = (int)chunksize[nZDim - 2];
839 : else
840 0 : nBlockYSize = 1;
841 : }
842 : }
843 :
844 : // Deal with bottom-up datasets and nBlockYSize != 1.
845 663 : auto poGDS = static_cast<netCDFDataset *>(poDS);
846 663 : if (poGDS->bBottomUp && nBlockYSize != 1 && poGDS->poChunkCache == nullptr)
847 : {
848 5 : if (poGDS->eAccess == GA_ReadOnly)
849 : {
850 : // Try to cache 1 or 2 'rows' of netCDF chunks along the whole
851 : // width of the raster
852 5 : size_t nChunks =
853 5 : static_cast<size_t>(DIV_ROUND_UP(nRasterXSize, nBlockXSize));
854 5 : if ((nRasterYSize % nBlockYSize) != 0)
855 1 : nChunks *= 2;
856 : const size_t nChunkSize =
857 5 : static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
858 5 : nBlockXSize * nBlockYSize;
859 5 : constexpr size_t MAX_CACHE_SIZE = 100 * 1024 * 1024;
860 5 : nChunks = std::min(nChunks, MAX_CACHE_SIZE / nChunkSize);
861 5 : if (nChunks)
862 : {
863 5 : poGDS->poChunkCache.reset(
864 5 : new netCDFDataset::ChunkCacheType(nChunks));
865 : }
866 : }
867 : else
868 : {
869 0 : nBlockYSize = 1;
870 : }
871 : }
872 663 : }
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 182 : 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 182 : const int *paDimIds)
884 182 : : nc_datatype(NC_NAT), cdfid(poNCDFDS->GetCDFID()), nZId(nZIdIn),
885 : nZDim(nZDimIn), nLevel(nLevelIn), nBandXPos(1), nBandYPos(0),
886 : panBandZPos(nullptr), panBandZLev(nullptr), bSignedData(bSigned),
887 182 : bCheckLongitude(false), m_bCreateMetadataFromOtherVarsDone(true)
888 : {
889 182 : poDS = poNCDFDS;
890 182 : nBand = nBandIn;
891 :
892 182 : nRasterXSize = poDS->GetRasterXSize();
893 182 : nRasterYSize = poDS->GetRasterYSize();
894 182 : nBlockXSize = poDS->GetRasterXSize();
895 182 : nBlockYSize = 1;
896 :
897 182 : if (poDS->GetAccess() != GA_Update)
898 : {
899 0 : CPLError(CE_Failure, CPLE_NotSupported,
900 : "Dataset is not in update mode, "
901 : "wrong netCDFRasterBand constructor");
902 0 : return;
903 : }
904 :
905 : // Take care of all other dimensions.
906 182 : if (nZDim > 2 && paDimIds != nullptr)
907 : {
908 27 : nBandXPos = panBandZPosIn[0];
909 27 : nBandYPos = panBandZPosIn[1];
910 :
911 27 : panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
912 27 : panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
913 :
914 76 : for (int i = 0; i < nZDim - 2; i++)
915 : {
916 49 : panBandZPos[i] = panBandZPosIn[i + 2];
917 49 : panBandZLev[i] = panBandZLevIn[i];
918 : }
919 : }
920 :
921 : // Get the type of the "z" variable, our target raster array.
922 182 : eDataType = eTypeIn;
923 :
924 182 : switch (eDataType)
925 : {
926 77 : case GDT_Byte:
927 77 : nc_datatype = NC_BYTE;
928 : // NC_UBYTE (unsigned byte) is only available for NC4.
929 77 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
930 3 : nc_datatype = NC_UBYTE;
931 77 : break;
932 7 : case GDT_Int8:
933 7 : nc_datatype = NC_BYTE;
934 7 : break;
935 11 : case GDT_Int16:
936 11 : nc_datatype = NC_SHORT;
937 11 : break;
938 24 : case GDT_Int32:
939 24 : nc_datatype = NC_INT;
940 24 : break;
941 13 : case GDT_Float32:
942 13 : nc_datatype = NC_FLOAT;
943 13 : break;
944 8 : case GDT_Float64:
945 8 : nc_datatype = NC_DOUBLE;
946 8 : break;
947 7 : case GDT_Int64:
948 7 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
949 : {
950 7 : nc_datatype = NC_INT64;
951 : }
952 : else
953 : {
954 0 : if (nBand == 1)
955 0 : CPLError(
956 : CE_Warning, CPLE_AppDefined,
957 : "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
958 : "Int64");
959 0 : nc_datatype = NC_DOUBLE;
960 0 : eDataType = GDT_Float64;
961 : }
962 7 : break;
963 7 : case GDT_UInt64:
964 7 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
965 : {
966 7 : nc_datatype = NC_UINT64;
967 : }
968 : else
969 : {
970 0 : if (nBand == 1)
971 0 : CPLError(
972 : CE_Warning, CPLE_AppDefined,
973 : "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
974 : "UInt64");
975 0 : nc_datatype = NC_DOUBLE;
976 0 : eDataType = GDT_Float64;
977 : }
978 7 : break;
979 6 : case GDT_UInt16:
980 6 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
981 : {
982 6 : nc_datatype = NC_USHORT;
983 6 : break;
984 : }
985 : [[fallthrough]];
986 : case GDT_UInt32:
987 6 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
988 : {
989 6 : nc_datatype = NC_UINT;
990 6 : break;
991 : }
992 : [[fallthrough]];
993 : default:
994 16 : if (nBand == 1)
995 8 : CPLError(CE_Warning, CPLE_AppDefined,
996 : "Unsupported GDAL datatype (%d), treat as NC_FLOAT.",
997 8 : static_cast<int>(eDataType));
998 16 : nc_datatype = NC_FLOAT;
999 16 : eDataType = GDT_Float32;
1000 16 : break;
1001 : }
1002 :
1003 : // Define the variable if necessary (if nZId == -1).
1004 182 : bool bDefineVar = false;
1005 :
1006 182 : if (nZId == -1)
1007 : {
1008 160 : bDefineVar = true;
1009 :
1010 : // Make sure we are in define mode.
1011 160 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1012 :
1013 : char szTempPrivate[256 + 1];
1014 160 : const char *pszTemp = nullptr;
1015 160 : if (!pszBandName || EQUAL(pszBandName, ""))
1016 : {
1017 138 : snprintf(szTempPrivate, sizeof(szTempPrivate), "Band%d", nBand);
1018 138 : pszTemp = szTempPrivate;
1019 : }
1020 : else
1021 : {
1022 22 : pszTemp = pszBandName;
1023 : }
1024 :
1025 : int status;
1026 160 : if (nZDim > 2 && paDimIds != nullptr)
1027 : {
1028 5 : status =
1029 5 : nc_def_var(cdfid, pszTemp, nc_datatype, nZDim, paDimIds, &nZId);
1030 : }
1031 : else
1032 : {
1033 155 : int anBandDims[2] = {poNCDFDS->nYDimID, poNCDFDS->nXDimID};
1034 : status =
1035 155 : nc_def_var(cdfid, pszTemp, nc_datatype, 2, anBandDims, &nZId);
1036 : }
1037 160 : NCDF_ERR(status);
1038 160 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d) id=%d", cdfid, pszTemp,
1039 : nc_datatype, nZId);
1040 :
1041 160 : if (!pszLongName || EQUAL(pszLongName, ""))
1042 : {
1043 153 : snprintf(szTempPrivate, sizeof(szTempPrivate),
1044 : "GDAL Band Number %d", nBand);
1045 153 : pszTemp = szTempPrivate;
1046 : }
1047 : else
1048 : {
1049 7 : pszTemp = pszLongName;
1050 : }
1051 : status =
1052 160 : nc_put_att_text(cdfid, nZId, CF_LNG_NAME, strlen(pszTemp), pszTemp);
1053 160 : NCDF_ERR(status);
1054 :
1055 160 : poNCDFDS->DefVarDeflate(nZId, true);
1056 : }
1057 :
1058 : // For Byte data add signed/unsigned info.
1059 182 : if (eDataType == GDT_Byte || eDataType == GDT_Int8)
1060 : {
1061 84 : if (bDefineVar)
1062 : {
1063 : // Only add attributes if creating variable.
1064 : // For unsigned NC_BYTE (except NC4 format),
1065 : // add valid_range and _Unsigned ( defined in CF-1 and NUG ).
1066 76 : if (nc_datatype == NC_BYTE && poNCDFDS->eFormat != NCDF_FORMAT_NC4)
1067 : {
1068 73 : CPLDebug("GDAL_netCDF",
1069 : "adding valid_range attributes for Byte Band");
1070 73 : short l_adfValidRange[2] = {0, 0};
1071 : int status;
1072 73 : if (bSignedData || eDataType == GDT_Int8)
1073 : {
1074 7 : l_adfValidRange[0] = -128;
1075 7 : l_adfValidRange[1] = 127;
1076 7 : status =
1077 7 : nc_put_att_text(cdfid, nZId, "_Unsigned", 5, "false");
1078 : }
1079 : else
1080 : {
1081 66 : l_adfValidRange[0] = 0;
1082 66 : l_adfValidRange[1] = 255;
1083 : status =
1084 66 : nc_put_att_text(cdfid, nZId, "_Unsigned", 4, "true");
1085 : }
1086 73 : NCDF_ERR(status);
1087 73 : status = nc_put_att_short(cdfid, nZId, "valid_range", NC_SHORT,
1088 : 2, l_adfValidRange);
1089 73 : NCDF_ERR(status);
1090 : }
1091 : }
1092 : }
1093 :
1094 182 : if (nc_datatype != NC_BYTE && nc_datatype != NC_CHAR &&
1095 101 : nc_datatype != NC_UBYTE)
1096 : {
1097 : // Set default nodata.
1098 98 : bool bIgnored = false;
1099 : double dfNoData =
1100 98 : NCDFGetDefaultNoDataValue(cdfid, nZId, nc_datatype, bIgnored);
1101 : #ifdef NCDF_DEBUG
1102 : CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) default", dfNoData);
1103 : #endif
1104 98 : netCDFRasterBand::SetNoDataValue(dfNoData);
1105 : }
1106 :
1107 182 : SetBlockSize();
1108 : }
1109 :
1110 : /************************************************************************/
1111 : /* ~netCDFRasterBand() */
1112 : /************************************************************************/
1113 :
1114 1326 : netCDFRasterBand::~netCDFRasterBand()
1115 : {
1116 663 : netCDFRasterBand::FlushCache(true);
1117 663 : CPLFree(panBandZPos);
1118 663 : CPLFree(panBandZLev);
1119 1326 : }
1120 :
1121 : /************************************************************************/
1122 : /* GetMetadata() */
1123 : /************************************************************************/
1124 :
1125 50 : char **netCDFRasterBand::GetMetadata(const char *pszDomain)
1126 : {
1127 50 : if (!m_bCreateMetadataFromOtherVarsDone)
1128 48 : CreateMetadataFromOtherVars();
1129 50 : return GDALPamRasterBand::GetMetadata(pszDomain);
1130 : }
1131 :
1132 : /************************************************************************/
1133 : /* GetMetadataItem() */
1134 : /************************************************************************/
1135 :
1136 555 : const char *netCDFRasterBand::GetMetadataItem(const char *pszName,
1137 : const char *pszDomain)
1138 : {
1139 555 : if (!m_bCreateMetadataFromOtherVarsDone &&
1140 539 : STARTS_WITH(pszName, "NETCDF_DIM_") &&
1141 1 : (!pszDomain || pszDomain[0] == 0))
1142 1 : CreateMetadataFromOtherVars();
1143 555 : return GDALPamRasterBand::GetMetadataItem(pszName, pszDomain);
1144 : }
1145 :
1146 : /************************************************************************/
1147 : /* SetMetadataItem() */
1148 : /************************************************************************/
1149 :
1150 7 : CPLErr netCDFRasterBand::SetMetadataItem(const char *pszName,
1151 : const char *pszValue,
1152 : const char *pszDomain)
1153 : {
1154 9 : if (GetAccess() == GA_Update &&
1155 9 : (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)
1156 : {
1157 : // Same logic as in CopyMetadata()
1158 :
1159 2 : const char *const papszIgnoreBand[] = {
1160 : CF_ADD_OFFSET, CF_SCALE_FACTOR, "valid_range", "_Unsigned",
1161 : NCDF_FillValue, "coordinates", nullptr};
1162 : // Do not copy varname, stats, NETCDF_DIM_*, nodata
1163 : // and items in papszIgnoreBand.
1164 6 : if (STARTS_WITH(pszName, "NETCDF_VARNAME") ||
1165 2 : STARTS_WITH(pszName, "STATISTICS_") ||
1166 2 : STARTS_WITH(pszName, "NETCDF_DIM_") ||
1167 2 : STARTS_WITH(pszName, "missing_value") ||
1168 6 : STARTS_WITH(pszName, "_FillValue") ||
1169 2 : CSLFindString(papszIgnoreBand, pszName) != -1)
1170 : {
1171 : // do nothing
1172 : }
1173 : else
1174 : {
1175 2 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1176 :
1177 2 : if (!NCDFPutAttr(cdfid, nZId, pszName, pszValue))
1178 2 : return CE_Failure;
1179 : }
1180 : }
1181 :
1182 5 : return GDALPamRasterBand::SetMetadataItem(pszName, pszValue, pszDomain);
1183 : }
1184 :
1185 : /************************************************************************/
1186 : /* SetMetadata() */
1187 : /************************************************************************/
1188 :
1189 2 : CPLErr netCDFRasterBand::SetMetadata(char **papszMD, const char *pszDomain)
1190 : {
1191 4 : if (GetAccess() == GA_Update &&
1192 2 : (pszDomain == nullptr || pszDomain[0] == '\0'))
1193 : {
1194 : // We don't handle metadata item removal for now
1195 4 : for (const char *const *papszIter = papszMD; papszIter && *papszIter;
1196 : ++papszIter)
1197 : {
1198 2 : char *pszName = nullptr;
1199 2 : const char *pszValue = CPLParseNameValue(*papszIter, &pszName);
1200 2 : if (pszName && pszValue)
1201 2 : SetMetadataItem(pszName, pszValue);
1202 2 : CPLFree(pszName);
1203 : }
1204 : }
1205 2 : return GDALPamRasterBand::SetMetadata(papszMD, pszDomain);
1206 : }
1207 :
1208 : /************************************************************************/
1209 : /* GetOffset() */
1210 : /************************************************************************/
1211 50 : double netCDFRasterBand::GetOffset(int *pbSuccess)
1212 : {
1213 50 : if (pbSuccess != nullptr)
1214 45 : *pbSuccess = static_cast<int>(m_bHaveOffset);
1215 :
1216 50 : return m_dfOffset;
1217 : }
1218 :
1219 : /************************************************************************/
1220 : /* SetOffset() */
1221 : /************************************************************************/
1222 1 : CPLErr netCDFRasterBand::SetOffset(double dfNewOffset)
1223 : {
1224 2 : CPLMutexHolderD(&hNCMutex);
1225 :
1226 : // Write value if in update mode.
1227 1 : if (poDS->GetAccess() == GA_Update)
1228 : {
1229 : // Make sure we are in define mode.
1230 1 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1231 :
1232 1 : const int status = nc_put_att_double(cdfid, nZId, CF_ADD_OFFSET,
1233 : NC_DOUBLE, 1, &dfNewOffset);
1234 :
1235 1 : NCDF_ERR(status);
1236 1 : if (status == NC_NOERR)
1237 : {
1238 1 : SetOffsetNoUpdate(dfNewOffset);
1239 1 : return CE_None;
1240 : }
1241 :
1242 0 : return CE_Failure;
1243 : }
1244 :
1245 0 : SetOffsetNoUpdate(dfNewOffset);
1246 0 : return CE_None;
1247 : }
1248 :
1249 : /************************************************************************/
1250 : /* SetOffsetNoUpdate() */
1251 : /************************************************************************/
1252 17 : void netCDFRasterBand::SetOffsetNoUpdate(double dfVal)
1253 : {
1254 17 : m_dfOffset = dfVal;
1255 17 : m_bHaveOffset = true;
1256 17 : }
1257 :
1258 : /************************************************************************/
1259 : /* GetScale() */
1260 : /************************************************************************/
1261 50 : double netCDFRasterBand::GetScale(int *pbSuccess)
1262 : {
1263 50 : if (pbSuccess != nullptr)
1264 45 : *pbSuccess = static_cast<int>(m_bHaveScale);
1265 :
1266 50 : return m_dfScale;
1267 : }
1268 :
1269 : /************************************************************************/
1270 : /* SetScale() */
1271 : /************************************************************************/
1272 1 : CPLErr netCDFRasterBand::SetScale(double dfNewScale)
1273 : {
1274 2 : CPLMutexHolderD(&hNCMutex);
1275 :
1276 : // Write value if in update mode.
1277 1 : if (poDS->GetAccess() == GA_Update)
1278 : {
1279 : // Make sure we are in define mode.
1280 1 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1281 :
1282 1 : const int status = nc_put_att_double(cdfid, nZId, CF_SCALE_FACTOR,
1283 : NC_DOUBLE, 1, &dfNewScale);
1284 :
1285 1 : NCDF_ERR(status);
1286 1 : if (status == NC_NOERR)
1287 : {
1288 1 : SetScaleNoUpdate(dfNewScale);
1289 1 : return CE_None;
1290 : }
1291 :
1292 0 : return CE_Failure;
1293 : }
1294 :
1295 0 : SetScaleNoUpdate(dfNewScale);
1296 0 : return CE_None;
1297 : }
1298 :
1299 : /************************************************************************/
1300 : /* SetScaleNoUpdate() */
1301 : /************************************************************************/
1302 21 : void netCDFRasterBand::SetScaleNoUpdate(double dfVal)
1303 : {
1304 21 : m_dfScale = dfVal;
1305 21 : m_bHaveScale = true;
1306 21 : }
1307 :
1308 : /************************************************************************/
1309 : /* GetUnitType() */
1310 : /************************************************************************/
1311 :
1312 22 : const char *netCDFRasterBand::GetUnitType()
1313 :
1314 : {
1315 22 : if (!m_osUnitType.empty())
1316 6 : return m_osUnitType;
1317 :
1318 16 : return GDALRasterBand::GetUnitType();
1319 : }
1320 :
1321 : /************************************************************************/
1322 : /* SetUnitType() */
1323 : /************************************************************************/
1324 :
1325 1 : CPLErr netCDFRasterBand::SetUnitType(const char *pszNewValue)
1326 :
1327 : {
1328 2 : CPLMutexHolderD(&hNCMutex);
1329 :
1330 2 : const std::string osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
1331 :
1332 1 : if (!osUnitType.empty())
1333 : {
1334 : // Write value if in update mode.
1335 1 : if (poDS->GetAccess() == GA_Update)
1336 : {
1337 : // Make sure we are in define mode.
1338 1 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(TRUE);
1339 :
1340 1 : const int status = nc_put_att_text(
1341 : cdfid, nZId, CF_UNITS, osUnitType.size(), osUnitType.c_str());
1342 :
1343 1 : NCDF_ERR(status);
1344 1 : if (status == NC_NOERR)
1345 : {
1346 1 : SetUnitTypeNoUpdate(pszNewValue);
1347 1 : return CE_None;
1348 : }
1349 :
1350 0 : return CE_Failure;
1351 : }
1352 : }
1353 :
1354 0 : SetUnitTypeNoUpdate(pszNewValue);
1355 :
1356 0 : return CE_None;
1357 : }
1358 :
1359 : /************************************************************************/
1360 : /* SetUnitTypeNoUpdate() */
1361 : /************************************************************************/
1362 :
1363 482 : void netCDFRasterBand::SetUnitTypeNoUpdate(const char *pszNewValue)
1364 : {
1365 482 : m_osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
1366 482 : }
1367 :
1368 : /************************************************************************/
1369 : /* GetNoDataValue() */
1370 : /************************************************************************/
1371 :
1372 162 : double netCDFRasterBand::GetNoDataValue(int *pbSuccess)
1373 :
1374 : {
1375 162 : if (m_bNoDataSetAsInt64)
1376 : {
1377 0 : if (pbSuccess)
1378 0 : *pbSuccess = TRUE;
1379 0 : return GDALGetNoDataValueCastToDouble(m_nNodataValueInt64);
1380 : }
1381 :
1382 162 : if (m_bNoDataSetAsUInt64)
1383 : {
1384 0 : if (pbSuccess)
1385 0 : *pbSuccess = TRUE;
1386 0 : return GDALGetNoDataValueCastToDouble(m_nNodataValueUInt64);
1387 : }
1388 :
1389 162 : if (m_bNoDataSet)
1390 : {
1391 125 : if (pbSuccess)
1392 109 : *pbSuccess = TRUE;
1393 125 : return m_dfNoDataValue;
1394 : }
1395 :
1396 37 : return GDALPamRasterBand::GetNoDataValue(pbSuccess);
1397 : }
1398 :
1399 : /************************************************************************/
1400 : /* GetNoDataValueAsInt64() */
1401 : /************************************************************************/
1402 :
1403 4 : int64_t netCDFRasterBand::GetNoDataValueAsInt64(int *pbSuccess)
1404 :
1405 : {
1406 4 : if (m_bNoDataSetAsInt64)
1407 : {
1408 4 : if (pbSuccess)
1409 4 : *pbSuccess = TRUE;
1410 :
1411 4 : return m_nNodataValueInt64;
1412 : }
1413 :
1414 0 : return GDALPamRasterBand::GetNoDataValueAsInt64(pbSuccess);
1415 : }
1416 :
1417 : /************************************************************************/
1418 : /* GetNoDataValueAsUInt64() */
1419 : /************************************************************************/
1420 :
1421 4 : uint64_t netCDFRasterBand::GetNoDataValueAsUInt64(int *pbSuccess)
1422 :
1423 : {
1424 4 : if (m_bNoDataSetAsUInt64)
1425 : {
1426 4 : if (pbSuccess)
1427 4 : *pbSuccess = TRUE;
1428 :
1429 4 : return m_nNodataValueUInt64;
1430 : }
1431 :
1432 0 : return GDALPamRasterBand::GetNoDataValueAsUInt64(pbSuccess);
1433 : }
1434 :
1435 : /************************************************************************/
1436 : /* SetNoDataValue() */
1437 : /************************************************************************/
1438 :
1439 134 : CPLErr netCDFRasterBand::SetNoDataValue(double dfNoData)
1440 :
1441 : {
1442 268 : CPLMutexHolderD(&hNCMutex);
1443 :
1444 : // If already set to new value, don't do anything.
1445 134 : if (m_bNoDataSet && CPLIsEqual(dfNoData, m_dfNoDataValue))
1446 19 : return CE_None;
1447 :
1448 : // Write value if in update mode.
1449 115 : if (poDS->GetAccess() == GA_Update)
1450 : {
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 125 : if (m_bNoDataSet &&
1455 10 : !cpl::down_cast<netCDFDataset *>(poDS)->GetDefineMode())
1456 : {
1457 0 : CPLDebug("GDAL_netCDF",
1458 : "Setting NoDataValue to %.17g (previously set to %.17g) "
1459 : "but file is no longer in define mode (id #%d, band #%d)",
1460 : dfNoData, m_dfNoDataValue, cdfid, nBand);
1461 : }
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 115 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1472 :
1473 : int status;
1474 115 : if (eDataType == GDT_Byte)
1475 : {
1476 6 : if (bSignedData)
1477 : {
1478 0 : signed char cNoDataValue = static_cast<signed char>(dfNoData);
1479 0 : status = nc_put_att_schar(cdfid, nZId, NCDF_FillValue,
1480 : nc_datatype, 1, &cNoDataValue);
1481 : }
1482 : else
1483 : {
1484 6 : const unsigned char ucNoDataValue =
1485 6 : static_cast<unsigned char>(dfNoData);
1486 6 : status = nc_put_att_uchar(cdfid, nZId, NCDF_FillValue,
1487 : nc_datatype, 1, &ucNoDataValue);
1488 : }
1489 : }
1490 109 : else if (eDataType == GDT_Int16)
1491 : {
1492 14 : short nsNoDataValue = static_cast<short>(dfNoData);
1493 14 : status = nc_put_att_short(cdfid, nZId, NCDF_FillValue, nc_datatype,
1494 : 1, &nsNoDataValue);
1495 : }
1496 95 : else if (eDataType == GDT_Int32)
1497 : {
1498 27 : int nNoDataValue = static_cast<int>(dfNoData);
1499 27 : status = nc_put_att_int(cdfid, nZId, NCDF_FillValue, nc_datatype, 1,
1500 : &nNoDataValue);
1501 : }
1502 68 : else if (eDataType == GDT_Float32)
1503 : {
1504 31 : float fNoDataValue = static_cast<float>(dfNoData);
1505 31 : status = nc_put_att_float(cdfid, nZId, NCDF_FillValue, nc_datatype,
1506 : 1, &fNoDataValue);
1507 : }
1508 43 : else if (eDataType == GDT_UInt16 &&
1509 6 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat ==
1510 : NCDF_FORMAT_NC4)
1511 : {
1512 6 : unsigned short usNoDataValue =
1513 6 : static_cast<unsigned short>(dfNoData);
1514 6 : status = nc_put_att_ushort(cdfid, nZId, NCDF_FillValue, nc_datatype,
1515 : 1, &usNoDataValue);
1516 : }
1517 38 : else if (eDataType == GDT_UInt32 &&
1518 7 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat ==
1519 : NCDF_FORMAT_NC4)
1520 : {
1521 7 : unsigned int unNoDataValue = static_cast<unsigned int>(dfNoData);
1522 7 : status = nc_put_att_uint(cdfid, nZId, NCDF_FillValue, nc_datatype,
1523 : 1, &unNoDataValue);
1524 : }
1525 : else
1526 : {
1527 24 : status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
1528 : 1, &dfNoData);
1529 : }
1530 :
1531 115 : NCDF_ERR(status);
1532 :
1533 : // Update status if write worked.
1534 115 : if (status == NC_NOERR)
1535 : {
1536 115 : SetNoDataValueNoUpdate(dfNoData);
1537 115 : return CE_None;
1538 : }
1539 :
1540 0 : return CE_Failure;
1541 : }
1542 :
1543 0 : SetNoDataValueNoUpdate(dfNoData);
1544 0 : return CE_None;
1545 : }
1546 :
1547 : /************************************************************************/
1548 : /* SetNoDataValueNoUpdate() */
1549 : /************************************************************************/
1550 :
1551 438 : void netCDFRasterBand::SetNoDataValueNoUpdate(double dfNoData)
1552 : {
1553 438 : m_dfNoDataValue = dfNoData;
1554 438 : m_bNoDataSet = true;
1555 438 : m_bNoDataSetAsInt64 = false;
1556 438 : m_bNoDataSetAsUInt64 = false;
1557 438 : }
1558 :
1559 : /************************************************************************/
1560 : /* SetNoDataValueAsInt64() */
1561 : /************************************************************************/
1562 :
1563 3 : CPLErr netCDFRasterBand::SetNoDataValueAsInt64(int64_t nNoData)
1564 :
1565 : {
1566 6 : CPLMutexHolderD(&hNCMutex);
1567 :
1568 : // If already set to new value, don't do anything.
1569 3 : if (m_bNoDataSetAsInt64 && nNoData == m_nNodataValueInt64)
1570 0 : return CE_None;
1571 :
1572 : // Write value if in update mode.
1573 3 : if (poDS->GetAccess() == GA_Update)
1574 : {
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 3 : if (m_bNoDataSetAsInt64 &&
1579 0 : !cpl::down_cast<netCDFDataset *>(poDS)->GetDefineMode())
1580 : {
1581 0 : CPLDebug("GDAL_netCDF",
1582 : "Setting NoDataValue to " CPL_FRMT_GIB
1583 : " (previously set to " CPL_FRMT_GIB ") "
1584 : "but file is no longer in define mode (id #%d, band #%d)",
1585 : static_cast<GIntBig>(nNoData),
1586 0 : static_cast<GIntBig>(m_nNodataValueInt64), cdfid, nBand);
1587 : }
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 3 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1599 :
1600 : int status;
1601 6 : if (eDataType == GDT_Int64 &&
1602 3 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
1603 : {
1604 3 : long long tmp = static_cast<long long>(nNoData);
1605 3 : status = nc_put_att_longlong(cdfid, nZId, NCDF_FillValue,
1606 : nc_datatype, 1, &tmp);
1607 : }
1608 : else
1609 : {
1610 0 : double dfNoData = static_cast<double>(nNoData);
1611 0 : status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
1612 : 1, &dfNoData);
1613 : }
1614 :
1615 3 : NCDF_ERR(status);
1616 :
1617 : // Update status if write worked.
1618 3 : if (status == NC_NOERR)
1619 : {
1620 3 : SetNoDataValueNoUpdate(nNoData);
1621 3 : return CE_None;
1622 : }
1623 :
1624 0 : return CE_Failure;
1625 : }
1626 :
1627 0 : SetNoDataValueNoUpdate(nNoData);
1628 0 : return CE_None;
1629 : }
1630 :
1631 : /************************************************************************/
1632 : /* SetNoDataValueNoUpdate() */
1633 : /************************************************************************/
1634 :
1635 11 : void netCDFRasterBand::SetNoDataValueNoUpdate(int64_t nNoData)
1636 : {
1637 11 : m_nNodataValueInt64 = nNoData;
1638 11 : m_bNoDataSet = false;
1639 11 : m_bNoDataSetAsInt64 = true;
1640 11 : m_bNoDataSetAsUInt64 = false;
1641 11 : }
1642 :
1643 : /************************************************************************/
1644 : /* SetNoDataValueAsUInt64() */
1645 : /************************************************************************/
1646 :
1647 3 : CPLErr netCDFRasterBand::SetNoDataValueAsUInt64(uint64_t nNoData)
1648 :
1649 : {
1650 6 : CPLMutexHolderD(&hNCMutex);
1651 :
1652 : // If already set to new value, don't do anything.
1653 3 : if (m_bNoDataSetAsUInt64 && nNoData == m_nNodataValueUInt64)
1654 0 : return CE_None;
1655 :
1656 : // Write value if in update mode.
1657 3 : if (poDS->GetAccess() == GA_Update)
1658 : {
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 3 : if (m_bNoDataSetAsUInt64 &&
1663 0 : !cpl::down_cast<netCDFDataset *>(poDS)->GetDefineMode())
1664 : {
1665 0 : CPLDebug("GDAL_netCDF",
1666 : "Setting NoDataValue to " CPL_FRMT_GUIB
1667 : " (previously set to " CPL_FRMT_GUIB ") "
1668 : "but file is no longer in define mode (id #%d, band #%d)",
1669 : static_cast<GUIntBig>(nNoData),
1670 0 : static_cast<GUIntBig>(m_nNodataValueUInt64), cdfid, nBand);
1671 : }
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 3 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1683 :
1684 : int status;
1685 6 : if (eDataType == GDT_UInt64 &&
1686 3 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
1687 : {
1688 3 : unsigned long long tmp = static_cast<long long>(nNoData);
1689 3 : status = nc_put_att_ulonglong(cdfid, nZId, NCDF_FillValue,
1690 : nc_datatype, 1, &tmp);
1691 : }
1692 : else
1693 : {
1694 0 : double dfNoData = static_cast<double>(nNoData);
1695 0 : status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
1696 : 1, &dfNoData);
1697 : }
1698 :
1699 3 : NCDF_ERR(status);
1700 :
1701 : // Update status if write worked.
1702 3 : if (status == NC_NOERR)
1703 : {
1704 3 : SetNoDataValueNoUpdate(nNoData);
1705 3 : return CE_None;
1706 : }
1707 :
1708 0 : return CE_Failure;
1709 : }
1710 :
1711 0 : SetNoDataValueNoUpdate(nNoData);
1712 0 : return CE_None;
1713 : }
1714 :
1715 : /************************************************************************/
1716 : /* SetNoDataValueNoUpdate() */
1717 : /************************************************************************/
1718 :
1719 10 : void netCDFRasterBand::SetNoDataValueNoUpdate(uint64_t nNoData)
1720 : {
1721 10 : m_nNodataValueUInt64 = nNoData;
1722 10 : m_bNoDataSet = false;
1723 10 : m_bNoDataSetAsInt64 = false;
1724 10 : m_bNoDataSetAsUInt64 = true;
1725 10 : }
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 5 : CPLXMLNode *netCDFRasterBand::SerializeToXML(const char * /* pszUnused */)
1772 : {
1773 : // Overridden from GDALPamDataset to add only band histogram
1774 : // and statistics. See bug #4244.
1775 5 : if (psPam == nullptr)
1776 0 : return nullptr;
1777 :
1778 : // Setup root node and attributes.
1779 : CPLXMLNode *psTree =
1780 5 : CPLCreateXMLNode(nullptr, CXT_Element, "PAMRasterBand");
1781 :
1782 5 : if (GetBand() > 0)
1783 : {
1784 10 : CPLString oFmt;
1785 5 : CPLSetXMLValue(psTree, "#band", oFmt.Printf("%d", GetBand()));
1786 : }
1787 :
1788 : // Histograms.
1789 5 : if (psPam->psSavedHistograms != nullptr)
1790 1 : CPLAddXMLChild(psTree, CPLCloneXMLTree(psPam->psSavedHistograms));
1791 :
1792 : // Metadata (statistics only).
1793 5 : GDALMultiDomainMetadata oMDMDStats;
1794 5 : const char *papszMDStats[] = {"STATISTICS_MINIMUM", "STATISTICS_MAXIMUM",
1795 : "STATISTICS_MEAN", "STATISTICS_STDDEV",
1796 : nullptr};
1797 25 : for (int i = 0; i < CSLCount(papszMDStats); i++)
1798 : {
1799 20 : const char *pszMDI = GetMetadataItem(papszMDStats[i]);
1800 20 : if (pszMDI)
1801 4 : oMDMDStats.SetMetadataItem(papszMDStats[i], pszMDI);
1802 : }
1803 5 : CPLXMLNode *psMD = oMDMDStats.Serialize();
1804 :
1805 5 : if (psMD != nullptr)
1806 : {
1807 1 : if (psMD->psChild == nullptr)
1808 0 : CPLDestroyXMLNode(psMD);
1809 : else
1810 1 : CPLAddXMLChild(psTree, psMD);
1811 : }
1812 :
1813 : // We don't want to return anything if we had no metadata to attach.
1814 5 : if (psTree->psChild == nullptr || psTree->psChild->psNext == nullptr)
1815 : {
1816 3 : CPLDestroyXMLNode(psTree);
1817 3 : psTree = nullptr;
1818 : }
1819 :
1820 5 : return psTree;
1821 : }
1822 :
1823 : /************************************************************************/
1824 : /* Get1DVariableIndexedByDimension() */
1825 : /************************************************************************/
1826 :
1827 81 : static int Get1DVariableIndexedByDimension(int cdfid, int nDimId,
1828 : const char *pszDimName,
1829 : bool bVerboseError, int *pnGroupID)
1830 : {
1831 81 : *pnGroupID = -1;
1832 81 : 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 81 : if (NCDFResolveVar(cdfid, pszDimName, pnGroupID, &nVarID) == CE_None)
1836 : {
1837 67 : int nDimCountOfVariable = 0;
1838 67 : nc_inq_varndims(*pnGroupID, nVarID, &nDimCountOfVariable);
1839 67 : if (nDimCountOfVariable == 1)
1840 : {
1841 67 : int nDimIdOfVariable = -1;
1842 67 : nc_inq_vardimid(*pnGroupID, nVarID, &nDimIdOfVariable);
1843 67 : if (nDimIdOfVariable == nDimId)
1844 : {
1845 67 : return nVarID;
1846 : }
1847 : }
1848 : }
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 14 : int nvars = 0;
1855 14 : CPL_IGNORE_RET_VAL(nc_inq(cdfid, nullptr, &nvars, nullptr, nullptr));
1856 :
1857 14 : int nCountCandidateVars = 0;
1858 14 : int nCandidateVarID = -1;
1859 65 : for (int k = 0; k < nvars; k++)
1860 : {
1861 51 : int nDimCountOfVariable = 0;
1862 51 : nc_inq_varndims(cdfid, k, &nDimCountOfVariable);
1863 51 : if (nDimCountOfVariable == 1)
1864 : {
1865 27 : int nDimIdOfVariable = -1;
1866 27 : nc_inq_vardimid(cdfid, k, &nDimIdOfVariable);
1867 27 : if (nDimIdOfVariable == nDimId)
1868 : {
1869 7 : nCountCandidateVars++;
1870 7 : nCandidateVarID = k;
1871 : }
1872 : }
1873 : }
1874 14 : if (nCountCandidateVars > 1)
1875 : {
1876 1 : if (bVerboseError)
1877 : {
1878 1 : CPLError(CE_Warning, CPLE_AppDefined,
1879 : "Several 1D variables are indexed by dimension %s",
1880 : pszDimName);
1881 : }
1882 1 : *pnGroupID = -1;
1883 1 : return -1;
1884 : }
1885 13 : else if (nCandidateVarID < 0)
1886 : {
1887 8 : if (bVerboseError)
1888 : {
1889 8 : CPLError(CE_Warning, CPLE_AppDefined,
1890 : "No 1D variable is indexed by dimension %s", pszDimName);
1891 : }
1892 : }
1893 13 : *pnGroupID = cdfid;
1894 13 : return nCandidateVarID;
1895 : }
1896 :
1897 : /************************************************************************/
1898 : /* CreateMetadataFromAttributes() */
1899 : /************************************************************************/
1900 :
1901 481 : void netCDFRasterBand::CreateMetadataFromAttributes()
1902 : {
1903 481 : char szVarName[NC_MAX_NAME + 1] = {};
1904 481 : int status = nc_inq_varname(cdfid, nZId, szVarName);
1905 481 : NCDF_ERR(status);
1906 :
1907 481 : GDALPamRasterBand::SetMetadataItem("NETCDF_VARNAME", szVarName);
1908 :
1909 : // Get attribute metadata.
1910 481 : int nAtt = 0;
1911 481 : NCDF_ERR(nc_inq_varnatts(cdfid, nZId, &nAtt));
1912 :
1913 2039 : for (int i = 0; i < nAtt; i++)
1914 : {
1915 1558 : char szMetaName[NC_MAX_NAME + 1] = {};
1916 1558 : status = nc_inq_attname(cdfid, nZId, i, szMetaName);
1917 1558 : if (status != NC_NOERR)
1918 12 : continue;
1919 :
1920 1558 : if (GDALPamRasterBand::GetMetadataItem(szMetaName) != nullptr)
1921 : {
1922 12 : continue;
1923 : }
1924 :
1925 1546 : char *pszMetaValue = nullptr;
1926 1546 : if (NCDFGetAttr(cdfid, nZId, szMetaName, &pszMetaValue) == CE_None)
1927 : {
1928 1546 : GDALPamRasterBand::SetMetadataItem(szMetaName, pszMetaValue);
1929 : }
1930 : else
1931 : {
1932 0 : CPLDebug("GDAL_netCDF", "invalid Band metadata %s", szMetaName);
1933 : }
1934 :
1935 1546 : if (pszMetaValue)
1936 : {
1937 1546 : CPLFree(pszMetaValue);
1938 1546 : pszMetaValue = nullptr;
1939 : }
1940 : }
1941 481 : }
1942 :
1943 : /************************************************************************/
1944 : /* CreateMetadataFromOtherVars() */
1945 : /************************************************************************/
1946 :
1947 49 : void netCDFRasterBand::CreateMetadataFromOtherVars()
1948 :
1949 : {
1950 49 : CPLAssert(!m_bCreateMetadataFromOtherVarsDone);
1951 49 : m_bCreateMetadataFromOtherVarsDone = true;
1952 :
1953 49 : netCDFDataset *l_poDS = cpl::down_cast<netCDFDataset *>(poDS);
1954 49 : const int nPamFlagsBackup = l_poDS->nPamFlags;
1955 :
1956 : // Compute all dimensions from Band number and save in Metadata.
1957 49 : int nd = 0;
1958 49 : 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 49 : int Sum = 1;
1969 49 : if (nd == 3)
1970 : {
1971 5 : Sum *= panBandZLev[0];
1972 : }
1973 :
1974 : // Loop over non-spatial dimensions.
1975 49 : int Taken = 0;
1976 :
1977 89 : for (int i = 0; i < nd - 2; i++)
1978 : {
1979 : int result;
1980 40 : if (i != nd - 2 - 1)
1981 : {
1982 18 : Sum = 1;
1983 37 : for (int j = i + 1; j < nd - 2; j++)
1984 : {
1985 19 : Sum *= panBandZLev[j];
1986 : }
1987 18 : result = static_cast<int>((nLevel - Taken) / Sum);
1988 : }
1989 : else
1990 : {
1991 22 : result = static_cast<int>((nLevel - Taken) % Sum);
1992 : }
1993 :
1994 40 : char szName[NC_MAX_NAME + 1] = {};
1995 40 : snprintf(szName, sizeof(szName), "%s",
1996 40 : l_poDS->papszDimName[l_poDS->m_anDimIds[panBandZPos[i]]]);
1997 :
1998 : char szMetaName[NC_MAX_NAME + 1 + 32];
1999 40 : snprintf(szMetaName, sizeof(szMetaName), "NETCDF_DIM_%s", szName);
2000 :
2001 40 : const int nGroupID = l_poDS->m_anExtraDimGroupIds[i];
2002 40 : const int nVarID = l_poDS->m_anExtraDimVarIds[i];
2003 40 : if (nVarID < 0)
2004 : {
2005 2 : GDALPamRasterBand::SetMetadataItem(szMetaName,
2006 : CPLSPrintf("%d", result + 1));
2007 : }
2008 : else
2009 : {
2010 : // TODO: Make sure all the status checks make sense.
2011 :
2012 38 : nc_type nVarType = NC_NAT;
2013 38 : /* status = */ nc_inq_vartype(nGroupID, nVarID, &nVarType);
2014 :
2015 38 : int nDims = 0;
2016 38 : /* status = */ nc_inq_varndims(nGroupID, nVarID, &nDims);
2017 :
2018 38 : char szMetaTemp[256] = {};
2019 38 : if (nDims == 1)
2020 : {
2021 38 : size_t count[1] = {1};
2022 38 : size_t start[1] = {static_cast<size_t>(result)};
2023 :
2024 38 : switch (nVarType)
2025 : {
2026 0 : case NC_BYTE:
2027 : // TODO: Check for signed/unsigned byte.
2028 : signed char cData;
2029 0 : /* status = */ nc_get_vara_schar(nGroupID, nVarID,
2030 : start, count, &cData);
2031 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", cData);
2032 0 : break;
2033 0 : case NC_SHORT:
2034 : short sData;
2035 0 : /* status = */ nc_get_vara_short(nGroupID, nVarID,
2036 : start, count, &sData);
2037 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", sData);
2038 0 : break;
2039 19 : case NC_INT:
2040 : {
2041 : int nData;
2042 19 : /* status = */ nc_get_vara_int(nGroupID, nVarID, start,
2043 : count, &nData);
2044 19 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", nData);
2045 19 : break;
2046 : }
2047 0 : case NC_FLOAT:
2048 : float fData;
2049 0 : /* status = */ nc_get_vara_float(nGroupID, nVarID,
2050 : start, count, &fData);
2051 0 : CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.8g",
2052 : fData);
2053 0 : break;
2054 18 : case NC_DOUBLE:
2055 : double dfData;
2056 18 : /* status = */ nc_get_vara_double(
2057 : nGroupID, nVarID, start, count, &dfData);
2058 18 : CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.16g",
2059 : dfData);
2060 18 : break;
2061 0 : case NC_UBYTE:
2062 : unsigned char ucData;
2063 0 : /* status = */ nc_get_vara_uchar(nGroupID, nVarID,
2064 : start, count, &ucData);
2065 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", ucData);
2066 0 : break;
2067 0 : case NC_USHORT:
2068 : unsigned short usData;
2069 0 : /* status = */ nc_get_vara_ushort(
2070 : nGroupID, nVarID, start, count, &usData);
2071 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", usData);
2072 0 : break;
2073 0 : case NC_UINT:
2074 : {
2075 : unsigned int unData;
2076 0 : /* status = */ nc_get_vara_uint(nGroupID, nVarID, start,
2077 : count, &unData);
2078 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", unData);
2079 0 : break;
2080 : }
2081 1 : case NC_INT64:
2082 : {
2083 : long long nData;
2084 1 : /* status = */ nc_get_vara_longlong(
2085 : nGroupID, nVarID, start, count, &nData);
2086 1 : snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GIB,
2087 : nData);
2088 1 : break;
2089 : }
2090 0 : case NC_UINT64:
2091 : {
2092 : unsigned long long unData;
2093 0 : /* status = */ nc_get_vara_ulonglong(
2094 : nGroupID, nVarID, start, count, &unData);
2095 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GUIB,
2096 : unData);
2097 0 : break;
2098 : }
2099 0 : default:
2100 0 : CPLDebug("GDAL_netCDF", "invalid dim %s, type=%d",
2101 : szMetaTemp, nVarType);
2102 0 : break;
2103 : }
2104 : }
2105 : else
2106 : {
2107 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", result + 1);
2108 : }
2109 :
2110 : // Save dimension value.
2111 : // NOTE: removed #original_units as not part of CF-1.
2112 :
2113 38 : GDALPamRasterBand::SetMetadataItem(szMetaName, szMetaTemp);
2114 : }
2115 :
2116 : // Avoid int32 overflow. Perhaps something more sensible to do here ?
2117 40 : if (result > 0 && Sum > INT_MAX / result)
2118 0 : break;
2119 40 : if (Taken > INT_MAX - result * Sum)
2120 0 : break;
2121 :
2122 40 : Taken += result * Sum;
2123 : } // End loop non-spatial dimensions.
2124 :
2125 49 : l_poDS->nPamFlags = nPamFlagsBackup;
2126 49 : }
2127 :
2128 : /************************************************************************/
2129 : /* CheckData() */
2130 : /************************************************************************/
2131 : template <class T>
2132 5732 : void netCDFRasterBand::CheckData(void *pImage, void *pImageNC,
2133 : size_t nTmpBlockXSize, size_t nTmpBlockYSize,
2134 : bool bCheckIsNan)
2135 : {
2136 5732 : 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 5732 : if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
2142 : {
2143 6 : T *ptrWrite = static_cast<T *>(pImage);
2144 6 : T *ptrRead = static_cast<T *>(pImageNC);
2145 29 : for (size_t j = 0; j < nTmpBlockYSize;
2146 23 : j++, ptrWrite += nBlockXSize, ptrRead += nTmpBlockXSize)
2147 : {
2148 23 : memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T));
2149 : }
2150 : }
2151 :
2152 : // Is valid data checking needed or requested?
2153 5732 : if (bValidRangeValid || bCheckIsNan)
2154 : {
2155 1265 : T *ptrImage = static_cast<T *>(pImage);
2156 2584 : for (size_t j = 0; j < nTmpBlockYSize; j++)
2157 : {
2158 : // k moves along the gdal block, skipping the out-of-range pixels.
2159 1319 : size_t k = j * nBlockXSize;
2160 96938 : for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
2161 : {
2162 : // Check for nodata and nan.
2163 95619 : if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
2164 6301 : continue;
2165 89318 : if (bCheckIsNan && std::isnan((double)ptrImage[k]))
2166 : {
2167 5737 : ptrImage[k] = (T)m_dfNoDataValue;
2168 5737 : continue;
2169 : }
2170 : // Check for valid_range.
2171 83581 : if (bValidRangeValid)
2172 : {
2173 40986 : if (((adfValidRange[0] != m_dfNoDataValue) &&
2174 40986 : (ptrImage[k] < (T)adfValidRange[0])) ||
2175 40983 : ((adfValidRange[1] != m_dfNoDataValue) &&
2176 40983 : (ptrImage[k] > (T)adfValidRange[1])))
2177 : {
2178 4 : ptrImage[k] = (T)m_dfNoDataValue;
2179 : }
2180 : }
2181 : }
2182 : }
2183 : }
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 5732 : const bool bIsSigned = std::numeric_limits<T>::is_signed;
2189 5419 : if (bCheckLongitude && bIsSigned &&
2190 9 : !CPLIsEqual((double)((T *)pImage)[0], m_dfNoDataValue) &&
2191 8 : !CPLIsEqual((double)((T *)pImage)[nTmpBlockXSize - 1],
2192 2714 : m_dfNoDataValue) &&
2193 8 : std::min(((T *)pImage)[0], ((T *)pImage)[nTmpBlockXSize - 1]) > 180.0)
2194 : {
2195 0 : T *ptrImage = static_cast<T *>(pImage);
2196 0 : for (size_t j = 0; j < nTmpBlockYSize; j++)
2197 : {
2198 0 : size_t k = j * nBlockXSize;
2199 0 : for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
2200 : {
2201 0 : if (!CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
2202 0 : ptrImage[k] = static_cast<T>(ptrImage[k] - 360);
2203 : }
2204 : }
2205 : }
2206 : else
2207 : {
2208 5732 : bCheckLongitude = false;
2209 : }
2210 5732 : }
2211 :
2212 : /************************************************************************/
2213 : /* CheckDataCpx() */
2214 : /************************************************************************/
2215 : template <class T>
2216 25 : void netCDFRasterBand::CheckDataCpx(void *pImage, void *pImageNC,
2217 : size_t nTmpBlockXSize,
2218 : size_t nTmpBlockYSize, bool bCheckIsNan)
2219 : {
2220 25 : 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 25 : if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
2226 : {
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 : {
2233 0 : memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T) * 2);
2234 : }
2235 : }
2236 :
2237 : // Is valid data checking needed or requested?
2238 25 : if (bValidRangeValid || bCheckIsNan)
2239 : {
2240 0 : T *ptrImage = static_cast<T *>(pImage);
2241 0 : for (size_t j = 0; j < nTmpBlockYSize; j++)
2242 : {
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 : {
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 : {
2252 0 : ptrImage[k] = (T)m_dfNoDataValue;
2253 0 : continue;
2254 : }
2255 : // Check for valid_range.
2256 0 : if (bValidRangeValid)
2257 : {
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 : {
2263 0 : ptrImage[k] = (T)m_dfNoDataValue;
2264 : }
2265 : }
2266 : }
2267 : }
2268 : }
2269 25 : }
2270 :
2271 : /************************************************************************/
2272 : /* FetchNetcdfChunk() */
2273 : /************************************************************************/
2274 :
2275 5757 : bool netCDFRasterBand::FetchNetcdfChunk(size_t xstart, size_t ystart,
2276 : void *pImage)
2277 : {
2278 5757 : size_t start[MAX_NC_DIMS] = {};
2279 5757 : size_t edge[MAX_NC_DIMS] = {};
2280 :
2281 5757 : start[nBandXPos] = xstart;
2282 5757 : edge[nBandXPos] = nBlockXSize;
2283 5757 : if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
2284 6 : edge[nBandXPos] = nRasterXSize - start[nBandXPos];
2285 5757 : if (nBandYPos >= 0)
2286 : {
2287 5753 : start[nBandYPos] = ystart;
2288 5753 : edge[nBandYPos] = nBlockYSize;
2289 5753 : if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
2290 4 : edge[nBandYPos] = nRasterYSize - start[nBandYPos];
2291 : }
2292 5757 : 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 5757 : int nd = 0;
2301 5757 : nc_inq_varndims(cdfid, nZId, &nd);
2302 5757 : if (nd == 3)
2303 : {
2304 1078 : start[panBandZPos[0]] = nLevel; // z
2305 1078 : edge[panBandZPos[0]] = 1;
2306 : }
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 5757 : if (nd > 3)
2317 : {
2318 160 : int Sum = -1;
2319 160 : int Taken = 0;
2320 480 : for (int i = 0; i < nd - 2; i++)
2321 : {
2322 320 : if (i != nd - 2 - 1)
2323 : {
2324 160 : Sum = 1;
2325 320 : for (int j = i + 1; j < nd - 2; j++)
2326 : {
2327 160 : Sum *= panBandZLev[j];
2328 : }
2329 160 : start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
2330 160 : edge[panBandZPos[i]] = 1;
2331 : }
2332 : else
2333 : {
2334 160 : start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
2335 160 : edge[panBandZPos[i]] = 1;
2336 : }
2337 320 : Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
2338 : }
2339 : }
2340 :
2341 : // Make sure we are in data mode.
2342 5757 : 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 5757 : void *pImageNC = pImage;
2349 5757 : if (edge[nBandXPos] != static_cast<size_t>(nBlockXSize))
2350 : {
2351 6 : pImageNC = static_cast<GByte *>(pImage) +
2352 6 : ((static_cast<size_t>(nBlockXSize) * nBlockYSize -
2353 12 : edge[nBandXPos] * nYChunkSize) *
2354 6 : GDALGetDataTypeSizeBytes(eDataType));
2355 : }
2356 :
2357 : // Read data according to type.
2358 : int status;
2359 5757 : if (eDataType == GDT_Byte)
2360 : {
2361 3005 : if (bSignedData)
2362 : {
2363 0 : status = nc_get_vara_schar(cdfid, nZId, start, edge,
2364 : static_cast<signed char *>(pImageNC));
2365 0 : if (status == NC_NOERR)
2366 0 : CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
2367 : nYChunkSize, false);
2368 : }
2369 : else
2370 : {
2371 3005 : status = nc_get_vara_uchar(cdfid, nZId, start, edge,
2372 : static_cast<unsigned char *>(pImageNC));
2373 3005 : if (status == NC_NOERR)
2374 3005 : CheckData<unsigned char>(pImage, pImageNC, edge[nBandXPos],
2375 : nYChunkSize, false);
2376 : }
2377 : }
2378 2752 : else if (eDataType == GDT_Int8)
2379 : {
2380 60 : status = nc_get_vara_schar(cdfid, nZId, start, edge,
2381 : static_cast<signed char *>(pImageNC));
2382 60 : if (status == NC_NOERR)
2383 60 : CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
2384 : nYChunkSize, false);
2385 : }
2386 2692 : else if (nc_datatype == NC_SHORT)
2387 : {
2388 465 : status = nc_get_vara_short(cdfid, nZId, start, edge,
2389 : static_cast<short *>(pImageNC));
2390 465 : if (status == NC_NOERR)
2391 : {
2392 465 : if (eDataType == GDT_Int16)
2393 : {
2394 462 : CheckData<GInt16>(pImage, pImageNC, edge[nBandXPos],
2395 : nYChunkSize, false);
2396 : }
2397 : else
2398 : {
2399 3 : CheckData<GUInt16>(pImage, pImageNC, edge[nBandXPos],
2400 : nYChunkSize, false);
2401 : }
2402 : }
2403 : }
2404 2227 : else if (eDataType == GDT_Int32)
2405 : {
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 912 : status = nc_get_vara_int(cdfid, nZId, start, edge,
2414 : static_cast<int *>(pImageNC));
2415 912 : if (status == NC_NOERR)
2416 912 : CheckData<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2417 : false);
2418 : #endif
2419 : }
2420 1315 : else if (eDataType == GDT_Float32)
2421 : {
2422 1178 : status = nc_get_vara_float(cdfid, nZId, start, edge,
2423 : static_cast<float *>(pImageNC));
2424 1178 : if (status == NC_NOERR)
2425 1178 : CheckData<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2426 : true);
2427 : }
2428 137 : else if (eDataType == GDT_Float64)
2429 : {
2430 86 : status = nc_get_vara_double(cdfid, nZId, start, edge,
2431 : static_cast<double *>(pImageNC));
2432 86 : if (status == NC_NOERR)
2433 86 : CheckData<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2434 : true);
2435 : }
2436 51 : else if (eDataType == GDT_UInt16)
2437 : {
2438 6 : status = nc_get_vara_ushort(cdfid, nZId, start, edge,
2439 : static_cast<unsigned short *>(pImageNC));
2440 6 : if (status == NC_NOERR)
2441 6 : CheckData<unsigned short>(pImage, pImageNC, edge[nBandXPos],
2442 : nYChunkSize, false);
2443 : }
2444 45 : else if (eDataType == GDT_UInt32)
2445 : {
2446 6 : status = nc_get_vara_uint(cdfid, nZId, start, edge,
2447 : static_cast<unsigned int *>(pImageNC));
2448 6 : if (status == NC_NOERR)
2449 6 : CheckData<unsigned int>(pImage, pImageNC, edge[nBandXPos],
2450 : nYChunkSize, false);
2451 : }
2452 39 : else if (eDataType == GDT_Int64)
2453 : {
2454 7 : status = nc_get_vara_longlong(cdfid, nZId, start, edge,
2455 : static_cast<long long *>(pImageNC));
2456 7 : if (status == NC_NOERR)
2457 7 : CheckData<std::int64_t>(pImage, pImageNC, edge[nBandXPos],
2458 : nYChunkSize, false);
2459 : }
2460 32 : else if (eDataType == GDT_UInt64)
2461 : {
2462 : status =
2463 7 : nc_get_vara_ulonglong(cdfid, nZId, start, edge,
2464 : static_cast<unsigned long long *>(pImageNC));
2465 7 : if (status == NC_NOERR)
2466 7 : CheckData<std::uint64_t>(pImage, pImageNC, edge[nBandXPos],
2467 : nYChunkSize, false);
2468 : }
2469 25 : else if (eDataType == GDT_CInt16)
2470 : {
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 : false);
2475 : }
2476 25 : else if (eDataType == GDT_CInt32)
2477 : {
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 : false);
2482 : }
2483 25 : else if (eDataType == GDT_CFloat32)
2484 : {
2485 20 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2486 20 : if (status == NC_NOERR)
2487 20 : CheckDataCpx<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2488 : false);
2489 : }
2490 5 : else if (eDataType == GDT_CFloat64)
2491 : {
2492 5 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2493 5 : if (status == NC_NOERR)
2494 5 : CheckDataCpx<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2495 : false);
2496 : }
2497 :
2498 : else
2499 0 : status = NC_EBADTYPE;
2500 :
2501 5757 : if (status != NC_NOERR)
2502 : {
2503 0 : CPLError(CE_Failure, CPLE_AppDefined,
2504 : "netCDF chunk fetch failed: #%d (%s)", status,
2505 : nc_strerror(status));
2506 0 : return false;
2507 : }
2508 5757 : return true;
2509 : }
2510 :
2511 : /************************************************************************/
2512 : /* IReadBlock() */
2513 : /************************************************************************/
2514 :
2515 5757 : CPLErr netCDFRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
2516 : void *pImage)
2517 :
2518 : {
2519 11514 : CPLMutexHolderD(&hNCMutex);
2520 :
2521 : // Locate X, Y and Z position in the array.
2522 :
2523 5757 : size_t xstart = static_cast<size_t>(nBlockXOff) * nBlockXSize;
2524 5757 : size_t ystart = 0;
2525 :
2526 : // Check y order.
2527 5757 : if (nBandYPos >= 0)
2528 : {
2529 5753 : auto poGDS = static_cast<netCDFDataset *>(poDS);
2530 5753 : if (poGDS->bBottomUp)
2531 : {
2532 4838 : if (nBlockYSize == 1)
2533 : {
2534 4825 : ystart = nRasterYSize - 1 - nBlockYOff;
2535 : }
2536 : else
2537 : {
2538 : // in GDAL space
2539 13 : ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
2540 : const size_t yend =
2541 26 : std::min(ystart + nBlockYSize - 1,
2542 13 : static_cast<size_t>(nRasterYSize - 1));
2543 : // in netCDF space
2544 13 : const size_t nFirstChunkLine = nRasterYSize - 1 - yend;
2545 13 : const size_t nLastChunkLine = nRasterYSize - 1 - ystart;
2546 13 : const size_t nFirstChunkBlock = nFirstChunkLine / nBlockYSize;
2547 13 : const size_t nLastChunkBlock = nLastChunkLine / nBlockYSize;
2548 :
2549 : const auto firstKey = netCDFDataset::ChunkKey(
2550 13 : nBlockXOff, nFirstChunkBlock, nBand);
2551 : const auto secondKey =
2552 13 : netCDFDataset::ChunkKey(nBlockXOff, nLastChunkBlock, nBand);
2553 :
2554 : // Retrieve data from the one or 2 needed netCDF chunks
2555 13 : std::shared_ptr<std::vector<GByte>> firstChunk;
2556 13 : std::shared_ptr<std::vector<GByte>> secondChunk;
2557 13 : if (poGDS->poChunkCache)
2558 : {
2559 13 : poGDS->poChunkCache->tryGet(firstKey, firstChunk);
2560 13 : if (firstKey != secondKey)
2561 6 : poGDS->poChunkCache->tryGet(secondKey, secondChunk);
2562 : }
2563 : const size_t nChunkLineSize =
2564 13 : static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
2565 13 : nBlockXSize;
2566 13 : const size_t nChunkSize = nChunkLineSize * nBlockYSize;
2567 13 : if (!firstChunk)
2568 : {
2569 11 : firstChunk.reset(new std::vector<GByte>(nChunkSize));
2570 11 : if (!FetchNetcdfChunk(xstart,
2571 11 : nFirstChunkBlock * nBlockYSize,
2572 11 : firstChunk.get()->data()))
2573 0 : return CE_Failure;
2574 11 : if (poGDS->poChunkCache)
2575 11 : poGDS->poChunkCache->insert(firstKey, firstChunk);
2576 : }
2577 13 : if (!secondChunk && firstKey != secondKey)
2578 : {
2579 2 : secondChunk.reset(new std::vector<GByte>(nChunkSize));
2580 2 : if (!FetchNetcdfChunk(xstart, nLastChunkBlock * nBlockYSize,
2581 2 : secondChunk.get()->data()))
2582 0 : return CE_Failure;
2583 2 : if (poGDS->poChunkCache)
2584 2 : poGDS->poChunkCache->insert(secondKey, secondChunk);
2585 : }
2586 :
2587 : // Assemble netCDF chunks into GDAL block
2588 13 : GByte *pabyImage = static_cast<GByte *>(pImage);
2589 13 : const size_t nFirstChunkBlockLine =
2590 13 : nFirstChunkBlock * nBlockYSize;
2591 13 : const size_t nLastChunkBlockLine =
2592 13 : nLastChunkBlock * nBlockYSize;
2593 146 : for (size_t iLine = ystart; iLine <= yend; iLine++)
2594 : {
2595 133 : const size_t nLineFromBottom = nRasterYSize - 1 - iLine;
2596 133 : const size_t nChunkY = nLineFromBottom / nBlockYSize;
2597 133 : if (nChunkY == nFirstChunkBlock)
2598 : {
2599 121 : memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
2600 121 : firstChunk.get()->data() +
2601 121 : (nLineFromBottom - nFirstChunkBlockLine) *
2602 : nChunkLineSize,
2603 : nChunkLineSize);
2604 : }
2605 : else
2606 : {
2607 12 : CPLAssert(nChunkY == nLastChunkBlock);
2608 12 : assert(secondChunk);
2609 12 : memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
2610 12 : secondChunk.get()->data() +
2611 12 : (nLineFromBottom - nLastChunkBlockLine) *
2612 : nChunkLineSize,
2613 : nChunkLineSize);
2614 : }
2615 : }
2616 13 : return CE_None;
2617 : }
2618 : }
2619 : else
2620 : {
2621 915 : ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
2622 : }
2623 : }
2624 :
2625 5744 : return FetchNetcdfChunk(xstart, ystart, pImage) ? CE_None : CE_Failure;
2626 : }
2627 :
2628 : /************************************************************************/
2629 : /* IWriteBlock() */
2630 : /************************************************************************/
2631 :
2632 6401 : CPLErr netCDFRasterBand::IWriteBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
2633 : void *pImage)
2634 : {
2635 12802 : 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 6401 : int nd = 0;
2645 6401 : nc_inq_varndims(cdfid, nZId, &nd);
2646 :
2647 : // Locate X, Y and Z position in the array.
2648 :
2649 : size_t start[MAX_NC_DIMS];
2650 6401 : memset(start, 0, sizeof(start));
2651 6401 : start[nBandXPos] = static_cast<size_t>(nBlockXOff) * nBlockXSize;
2652 :
2653 : // check y order.
2654 6401 : if (static_cast<netCDFDataset *>(poDS)->bBottomUp)
2655 : {
2656 6377 : if (nBlockYSize == 1)
2657 : {
2658 6377 : start[nBandYPos] = nRasterYSize - 1 - nBlockYOff;
2659 : }
2660 : else
2661 : {
2662 0 : CPLError(CE_Failure, CPLE_AppDefined,
2663 : "nBlockYSize = %d, only 1 supported when "
2664 : "writing bottom-up dataset",
2665 : nBlockYSize);
2666 0 : return CE_Failure;
2667 : }
2668 : }
2669 : else
2670 : {
2671 24 : start[nBandYPos] = static_cast<size_t>(nBlockYOff) * nBlockYSize; // y
2672 : }
2673 :
2674 6401 : size_t edge[MAX_NC_DIMS] = {};
2675 :
2676 6401 : edge[nBandXPos] = nBlockXSize;
2677 6401 : if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
2678 0 : edge[nBandXPos] = nRasterXSize - start[nBandXPos];
2679 6401 : edge[nBandYPos] = nBlockYSize;
2680 6401 : if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
2681 0 : edge[nBandYPos] = nRasterYSize - start[nBandYPos];
2682 :
2683 6401 : if (nd == 3)
2684 : {
2685 610 : start[panBandZPos[0]] = nLevel; // z
2686 610 : edge[panBandZPos[0]] = 1;
2687 : }
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 6401 : if (nd > 3)
2698 : {
2699 178 : int Sum = -1;
2700 178 : int Taken = 0;
2701 534 : for (int i = 0; i < nd - 2; i++)
2702 : {
2703 356 : if (i != nd - 2 - 1)
2704 : {
2705 178 : Sum = 1;
2706 356 : for (int j = i + 1; j < nd - 2; j++)
2707 : {
2708 178 : Sum *= panBandZLev[j];
2709 : }
2710 178 : start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
2711 178 : edge[panBandZPos[i]] = 1;
2712 : }
2713 : else
2714 : {
2715 178 : start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
2716 178 : edge[panBandZPos[i]] = 1;
2717 : }
2718 356 : Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
2719 : }
2720 : }
2721 :
2722 : // Make sure we are in data mode.
2723 6401 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
2724 :
2725 : // Copy data according to type.
2726 6401 : int status = 0;
2727 6401 : if (eDataType == GDT_Byte)
2728 : {
2729 5842 : if (bSignedData)
2730 0 : status = nc_put_vara_schar(cdfid, nZId, start, edge,
2731 : static_cast<signed char *>(pImage));
2732 : else
2733 5842 : status = nc_put_vara_uchar(cdfid, nZId, start, edge,
2734 : static_cast<unsigned char *>(pImage));
2735 : }
2736 559 : else if (eDataType == GDT_Int8)
2737 : {
2738 40 : status = nc_put_vara_schar(cdfid, nZId, start, edge,
2739 : static_cast<signed char *>(pImage));
2740 : }
2741 519 : else if (nc_datatype == NC_SHORT)
2742 : {
2743 101 : status = nc_put_vara_short(cdfid, nZId, start, edge,
2744 : static_cast<short *>(pImage));
2745 : }
2746 418 : else if (eDataType == GDT_Int32)
2747 : {
2748 210 : status = nc_put_vara_int(cdfid, nZId, start, edge,
2749 : static_cast<int *>(pImage));
2750 : }
2751 208 : else if (eDataType == GDT_Float32)
2752 : {
2753 128 : status = nc_put_vara_float(cdfid, nZId, start, edge,
2754 : static_cast<float *>(pImage));
2755 : }
2756 80 : else if (eDataType == GDT_Float64)
2757 : {
2758 50 : status = nc_put_vara_double(cdfid, nZId, start, edge,
2759 : static_cast<double *>(pImage));
2760 : }
2761 30 : else if (eDataType == GDT_UInt16 &&
2762 12 : static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2763 : {
2764 12 : status = nc_put_vara_ushort(cdfid, nZId, start, edge,
2765 : static_cast<unsigned short *>(pImage));
2766 : }
2767 18 : else if (eDataType == GDT_UInt32 &&
2768 12 : static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2769 : {
2770 12 : status = nc_put_vara_uint(cdfid, nZId, start, edge,
2771 : static_cast<unsigned int *>(pImage));
2772 : }
2773 6 : else if (eDataType == GDT_UInt64 &&
2774 3 : static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2775 : {
2776 3 : status =
2777 3 : nc_put_vara_ulonglong(cdfid, nZId, start, edge,
2778 : static_cast<unsigned long long *>(pImage));
2779 : }
2780 3 : else if (eDataType == GDT_Int64 &&
2781 3 : static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2782 : {
2783 3 : status = nc_put_vara_longlong(cdfid, nZId, start, edge,
2784 : static_cast<long long *>(pImage));
2785 : }
2786 : else
2787 : {
2788 0 : CPLError(CE_Failure, CPLE_NotSupported,
2789 : "The NetCDF driver does not support GDAL data type %d",
2790 0 : eDataType);
2791 0 : status = NC_EBADTYPE;
2792 : }
2793 6401 : NCDF_ERR(status);
2794 :
2795 6401 : if (status != NC_NOERR)
2796 : {
2797 0 : CPLError(CE_Failure, CPLE_AppDefined,
2798 : "netCDF scanline write failed: %s", nc_strerror(status));
2799 0 : return CE_Failure;
2800 : }
2801 :
2802 6401 : return CE_None;
2803 : }
2804 :
2805 : /************************************************************************/
2806 : /* ==================================================================== */
2807 : /* netCDFDataset */
2808 : /* ==================================================================== */
2809 : /************************************************************************/
2810 :
2811 : /************************************************************************/
2812 : /* netCDFDataset() */
2813 : /************************************************************************/
2814 :
2815 1019 : netCDFDataset::netCDFDataset()
2816 : :
2817 : // Basic dataset vars.
2818 : #ifdef ENABLE_NCDUMP
2819 : bFileToDestroyAtClosing(false),
2820 : #endif
2821 : cdfid(-1), nSubDatasets(0), papszSubDatasets(nullptr),
2822 : papszMetadata(nullptr), bBottomUp(true), eFormat(NCDF_FORMAT_NONE),
2823 : bIsGdalFile(false), bIsGdalCfFile(false), pszCFProjection(nullptr),
2824 : pszCFCoordinates(nullptr), nCFVersion(1.6), bSGSupport(false),
2825 1019 : eMultipleLayerBehavior(SINGLE_LAYER), logCount(0), vcdf(this, cdfid),
2826 1019 : GeometryScribe(vcdf, this->generateLogName()),
2827 1019 : FieldScribe(vcdf, this->generateLogName()),
2828 2038 : bufManager(CPLGetUsablePhysicalRAM() / 5),
2829 :
2830 : // projection/GT.
2831 : nXDimID(-1), nYDimID(-1), bIsProjected(false),
2832 : bIsGeographic(false), // Can be not projected, and also not geographic
2833 : // State vars.
2834 : bDefineMode(true), bAddedGridMappingRef(false),
2835 :
2836 : // Create vars.
2837 : papszCreationOptions(nullptr), eCompress(NCDF_COMPRESS_NONE),
2838 : nZLevel(NCDF_DEFLATE_LEVEL), bChunking(false), nCreateMode(NC_CLOBBER),
2839 3057 : bSignedData(true)
2840 : {
2841 1019 : m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2842 :
2843 : // Set buffers
2844 1019 : bufManager.addBuffer(&(GeometryScribe.getMemBuffer()));
2845 1019 : bufManager.addBuffer(&(FieldScribe.getMemBuffer()));
2846 1019 : }
2847 :
2848 : /************************************************************************/
2849 : /* ~netCDFDataset() */
2850 : /************************************************************************/
2851 :
2852 1958 : netCDFDataset::~netCDFDataset()
2853 :
2854 : {
2855 1019 : netCDFDataset::Close();
2856 1958 : }
2857 :
2858 : /************************************************************************/
2859 : /* Close() */
2860 : /************************************************************************/
2861 :
2862 1773 : CPLErr netCDFDataset::Close()
2863 : {
2864 1773 : CPLErr eErr = CE_None;
2865 1773 : if (nOpenFlags != OPEN_FLAGS_CLOSED)
2866 : {
2867 2038 : CPLMutexHolderD(&hNCMutex);
2868 :
2869 : #ifdef NCDF_DEBUG
2870 : CPLDebug("GDAL_netCDF",
2871 : "netCDFDataset::~netCDFDataset(), cdfid=%d filename=%s", cdfid,
2872 : osFilename.c_str());
2873 : #endif
2874 :
2875 : // Write data related to geotransform
2876 1252 : if (GetAccess() == GA_Update && !m_bAddedProjectionVarsData &&
2877 233 : (m_bHasProjection || m_bHasGeoTransform))
2878 : {
2879 : // Ensure projection is written if GeoTransform OR Projection are
2880 : // missing.
2881 37 : if (!m_bAddedProjectionVarsDefs)
2882 : {
2883 2 : AddProjectionVars(true, nullptr, nullptr);
2884 : }
2885 37 : AddProjectionVars(false, nullptr, nullptr);
2886 : }
2887 :
2888 1019 : if (netCDFDataset::FlushCache(true) != CE_None)
2889 0 : eErr = CE_Failure;
2890 :
2891 1019 : if (GetAccess() == GA_Update && !SGCommitPendingTransaction())
2892 0 : eErr = CE_Failure;
2893 :
2894 1021 : for (size_t i = 0; i < apoVectorDatasets.size(); i++)
2895 2 : delete apoVectorDatasets[i];
2896 :
2897 : // Make sure projection variable is written to band variable.
2898 1019 : if (GetAccess() == GA_Update && !bAddedGridMappingRef)
2899 : {
2900 250 : if (!AddGridMappingRef())
2901 0 : eErr = CE_Failure;
2902 : }
2903 :
2904 1019 : CSLDestroy(papszMetadata);
2905 1019 : CSLDestroy(papszSubDatasets);
2906 1019 : CSLDestroy(papszCreationOptions);
2907 :
2908 1019 : CPLFree(pszCFProjection);
2909 :
2910 1019 : if (cdfid > 0)
2911 : {
2912 : #ifdef NCDF_DEBUG
2913 : CPLDebug("GDAL_netCDF", "calling nc_close( %d)", cdfid);
2914 : #endif
2915 662 : int status = GDAL_nc_close(cdfid);
2916 : #ifdef ENABLE_UFFD
2917 662 : NETCDF_UFFD_UNMAP(pCtx);
2918 : #endif
2919 662 : NCDF_ERR(status);
2920 662 : if (status != NC_NOERR)
2921 0 : eErr = CE_Failure;
2922 : }
2923 :
2924 1019 : if (fpVSIMEM)
2925 15 : VSIFCloseL(fpVSIMEM);
2926 :
2927 : #ifdef ENABLE_NCDUMP
2928 1019 : if (bFileToDestroyAtClosing)
2929 0 : VSIUnlink(osFilename);
2930 : #endif
2931 :
2932 1019 : if (GDALPamDataset::Close() != CE_None)
2933 0 : eErr = CE_Failure;
2934 : }
2935 1773 : return eErr;
2936 : }
2937 :
2938 : /************************************************************************/
2939 : /* SetDefineMode() */
2940 : /************************************************************************/
2941 14233 : bool netCDFDataset::SetDefineMode(bool bNewDefineMode)
2942 : {
2943 : // Do nothing if already in new define mode
2944 : // or if dataset is in read-only mode or if dataset is true NC4 dataset.
2945 14792 : if (bDefineMode == bNewDefineMode || GetAccess() == GA_ReadOnly ||
2946 559 : eFormat == NCDF_FORMAT_NC4)
2947 13821 : return true;
2948 :
2949 412 : CPLDebug("GDAL_netCDF", "SetDefineMode(%d) old=%d",
2950 412 : static_cast<int>(bNewDefineMode), static_cast<int>(bDefineMode));
2951 :
2952 412 : bDefineMode = bNewDefineMode;
2953 :
2954 : int status;
2955 412 : if (bDefineMode)
2956 143 : status = nc_redef(cdfid);
2957 : else
2958 269 : status = nc_enddef(cdfid);
2959 :
2960 412 : NCDF_ERR(status);
2961 412 : return status == NC_NOERR;
2962 : }
2963 :
2964 : /************************************************************************/
2965 : /* GetMetadataDomainList() */
2966 : /************************************************************************/
2967 :
2968 27 : char **netCDFDataset::GetMetadataDomainList()
2969 : {
2970 27 : char **papszDomains = BuildMetadataDomainList(
2971 : GDALDataset::GetMetadataDomainList(), TRUE, "SUBDATASETS", nullptr);
2972 28 : for (const auto &kv : m_oMapDomainToJSon)
2973 1 : papszDomains = CSLAddString(papszDomains, ("json:" + kv.first).c_str());
2974 27 : return papszDomains;
2975 : }
2976 :
2977 : /************************************************************************/
2978 : /* GetMetadata() */
2979 : /************************************************************************/
2980 369 : char **netCDFDataset::GetMetadata(const char *pszDomain)
2981 : {
2982 369 : if (pszDomain != nullptr && STARTS_WITH_CI(pszDomain, "SUBDATASETS"))
2983 39 : return papszSubDatasets;
2984 :
2985 330 : if (pszDomain != nullptr && STARTS_WITH(pszDomain, "json:"))
2986 : {
2987 1 : auto iter = m_oMapDomainToJSon.find(pszDomain + strlen("json:"));
2988 1 : if (iter != m_oMapDomainToJSon.end())
2989 1 : return iter->second.List();
2990 : }
2991 :
2992 329 : return GDALDataset::GetMetadata(pszDomain);
2993 : }
2994 :
2995 : /************************************************************************/
2996 : /* SetMetadataItem() */
2997 : /************************************************************************/
2998 :
2999 43 : CPLErr netCDFDataset::SetMetadataItem(const char *pszName, const char *pszValue,
3000 : const char *pszDomain)
3001 : {
3002 85 : if (GetAccess() == GA_Update &&
3003 85 : (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)
3004 : {
3005 42 : std::string osName(pszName);
3006 :
3007 : // Same logic as in CopyMetadata()
3008 42 : if (STARTS_WITH(osName.c_str(), "NC_GLOBAL#"))
3009 8 : osName = osName.substr(strlen("NC_GLOBAL#"));
3010 34 : else if (strchr(osName.c_str(), '#') == nullptr)
3011 5 : osName = "GDAL_" + osName;
3012 :
3013 84 : if (STARTS_WITH(osName.c_str(), "NETCDF_DIM_") ||
3014 42 : strchr(osName.c_str(), '#') != nullptr)
3015 : {
3016 : // do nothing
3017 29 : return CE_None;
3018 : }
3019 : else
3020 : {
3021 13 : SetDefineMode(true);
3022 :
3023 13 : if (!NCDFPutAttr(cdfid, NC_GLOBAL, osName.c_str(), pszValue))
3024 13 : return CE_Failure;
3025 : }
3026 : }
3027 :
3028 1 : return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);
3029 : }
3030 :
3031 : /************************************************************************/
3032 : /* SetMetadata() */
3033 : /************************************************************************/
3034 :
3035 8 : CPLErr netCDFDataset::SetMetadata(char **papszMD, const char *pszDomain)
3036 : {
3037 13 : if (GetAccess() == GA_Update &&
3038 5 : (pszDomain == nullptr || pszDomain[0] == '\0'))
3039 : {
3040 : // We don't handle metadata item removal for now
3041 50 : for (const char *const *papszIter = papszMD; papszIter && *papszIter;
3042 : ++papszIter)
3043 : {
3044 42 : char *pszName = nullptr;
3045 42 : const char *pszValue = CPLParseNameValue(*papszIter, &pszName);
3046 42 : if (pszName && pszValue)
3047 42 : SetMetadataItem(pszName, pszValue);
3048 42 : CPLFree(pszName);
3049 : }
3050 8 : return CE_None;
3051 : }
3052 0 : return GDALPamDataset::SetMetadata(papszMD, pszDomain);
3053 : }
3054 :
3055 : /************************************************************************/
3056 : /* GetSpatialRef() */
3057 : /************************************************************************/
3058 :
3059 190 : const OGRSpatialReference *netCDFDataset::GetSpatialRef() const
3060 : {
3061 190 : if (m_bHasProjection)
3062 75 : return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
3063 :
3064 115 : return GDALPamDataset::GetSpatialRef();
3065 : }
3066 :
3067 : /************************************************************************/
3068 : /* FetchCopyParam() */
3069 : /************************************************************************/
3070 :
3071 440 : double netCDFDataset::FetchCopyParam(const char *pszGridMappingValue,
3072 : const char *pszParam, double dfDefault,
3073 : bool *pbFound)
3074 :
3075 : {
3076 : char *pszTemp =
3077 440 : CPLStrdup(CPLSPrintf("%s#%s", pszGridMappingValue, pszParam));
3078 440 : const char *pszValue = CSLFetchNameValue(papszMetadata, pszTemp);
3079 440 : CPLFree(pszTemp);
3080 :
3081 440 : if (pbFound)
3082 : {
3083 440 : *pbFound = (pszValue != nullptr);
3084 : }
3085 :
3086 440 : if (pszValue)
3087 : {
3088 0 : return CPLAtofM(pszValue);
3089 : }
3090 :
3091 440 : return dfDefault;
3092 : }
3093 :
3094 : /************************************************************************/
3095 : /* FetchStandardParallels() */
3096 : /************************************************************************/
3097 :
3098 : std::vector<std::string>
3099 0 : netCDFDataset::FetchStandardParallels(const char *pszGridMappingValue)
3100 : {
3101 : // cf-1.0 tags
3102 0 : const char *pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL);
3103 :
3104 0 : std::vector<std::string> ret;
3105 0 : if (pszValue != nullptr)
3106 : {
3107 0 : CPLStringList aosValues;
3108 0 : if (pszValue[0] != '{' &&
3109 0 : CPLString(pszValue).Trim().find(' ') != std::string::npos)
3110 : {
3111 : // Some files like
3112 : // ftp://data.knmi.nl/download/KNW-NetCDF-3D/1.0/noversion/2013/11/14/KNW-1.0_H37-ERA_NL_20131114.nc
3113 : // do not use standard formatting for arrays, but just space
3114 : // separated syntax
3115 0 : aosValues = CSLTokenizeString2(pszValue, " ", 0);
3116 : }
3117 : else
3118 : {
3119 0 : aosValues = NCDFTokenizeArray(pszValue);
3120 : }
3121 0 : for (int i = 0; i < aosValues.size(); i++)
3122 : {
3123 0 : ret.push_back(aosValues[i]);
3124 : }
3125 : }
3126 : // Try gdal tags.
3127 : else
3128 : {
3129 0 : pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_1);
3130 :
3131 0 : if (pszValue != nullptr)
3132 0 : ret.push_back(pszValue);
3133 :
3134 0 : pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_2);
3135 :
3136 0 : if (pszValue != nullptr)
3137 0 : ret.push_back(pszValue);
3138 : }
3139 :
3140 0 : return ret;
3141 : }
3142 :
3143 : /************************************************************************/
3144 : /* FetchAttr() */
3145 : /************************************************************************/
3146 :
3147 3751 : const char *netCDFDataset::FetchAttr(const char *pszVarFullName,
3148 : const char *pszAttr)
3149 :
3150 : {
3151 3751 : char *pszKey = CPLStrdup(CPLSPrintf("%s#%s", pszVarFullName, pszAttr));
3152 3751 : const char *pszValue = CSLFetchNameValue(papszMetadata, pszKey);
3153 3751 : CPLFree(pszKey);
3154 3751 : return pszValue;
3155 : }
3156 :
3157 2481 : const char *netCDFDataset::FetchAttr(int nGroupId, int nVarId,
3158 : const char *pszAttr)
3159 :
3160 : {
3161 2481 : char *pszVarFullName = nullptr;
3162 2481 : NCDFGetVarFullName(nGroupId, nVarId, &pszVarFullName);
3163 2481 : const char *pszValue = FetchAttr(pszVarFullName, pszAttr);
3164 2481 : CPLFree(pszVarFullName);
3165 2481 : return pszValue;
3166 : }
3167 :
3168 : /************************************************************************/
3169 : /* IsDifferenceBelow() */
3170 : /************************************************************************/
3171 :
3172 1091 : static bool IsDifferenceBelow(double dfA, double dfB, double dfError)
3173 : {
3174 1091 : const double dfAbsDiff = fabs(dfA - dfB);
3175 1091 : return dfAbsDiff <= dfError;
3176 : }
3177 :
3178 : /************************************************************************/
3179 : /* SetProjectionFromVar() */
3180 : /************************************************************************/
3181 523 : void netCDFDataset::SetProjectionFromVar(
3182 : int nGroupId, int nVarId, bool bReadSRSOnly, const char *pszGivenGM,
3183 : std::string *returnProjStr, nccfdriver::SGeometry_Reader *sg,
3184 : std::vector<std::string> *paosRemovedMDItems)
3185 : {
3186 523 : bool bGotGeogCS = false;
3187 523 : bool bGotCfSRS = false;
3188 523 : bool bGotCfWktSRS = false;
3189 523 : bool bGotGdalSRS = false;
3190 523 : bool bGotCfGT = false;
3191 523 : bool bGotGdalGT = false;
3192 :
3193 : // These values from CF metadata.
3194 523 : OGRSpatialReference oSRS;
3195 523 : oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3196 523 : size_t xdim = nRasterXSize;
3197 523 : size_t ydim = nRasterYSize;
3198 :
3199 : // These values from GDAL metadata.
3200 523 : const char *pszWKT = nullptr;
3201 523 : const char *pszGeoTransform = nullptr;
3202 :
3203 523 : netCDFDataset *poDS = this; // Perhaps this should be removed for clarity.
3204 :
3205 523 : CPLDebug("GDAL_netCDF", "\n=====\nSetProjectionFromVar( %d, %d)", nGroupId,
3206 : nVarId);
3207 :
3208 : // Get x/y range information.
3209 :
3210 : // Temp variables to use in SetGeoTransform() and SetProjection().
3211 523 : GDALGeoTransform tmpGT;
3212 :
3213 : // Look for grid_mapping metadata.
3214 523 : const char *pszValue = pszGivenGM;
3215 523 : CPLString osTmpGridMapping; // let is in this outer scope as pszValue may
3216 : // point to it
3217 523 : if (pszValue == nullptr)
3218 : {
3219 480 : pszValue = FetchAttr(nGroupId, nVarId, CF_GRD_MAPPING);
3220 480 : if (pszValue && strchr(pszValue, ':') && strchr(pszValue, ' '))
3221 : {
3222 : // Expanded form of grid_mapping
3223 : // e.g. "crsOSGB: x y crsWGS84: lat lon"
3224 : // Pickup the grid_mapping whose coordinates are dimensions of the
3225 : // variable
3226 6 : CPLStringList aosTokens(CSLTokenizeString2(pszValue, " ", 0));
3227 3 : if ((aosTokens.size() % 3) == 0)
3228 : {
3229 3 : for (int i = 0; i < aosTokens.size() / 3; i++)
3230 : {
3231 3 : if (CSLFindString(poDS->papszDimName,
3232 9 : aosTokens[3 * i + 1]) >= 0 &&
3233 3 : CSLFindString(poDS->papszDimName,
3234 3 : aosTokens[3 * i + 2]) >= 0)
3235 : {
3236 3 : osTmpGridMapping = aosTokens[3 * i];
3237 6 : if (!osTmpGridMapping.empty() &&
3238 3 : osTmpGridMapping.back() == ':')
3239 : {
3240 3 : osTmpGridMapping.resize(osTmpGridMapping.size() -
3241 : 1);
3242 : }
3243 3 : pszValue = osTmpGridMapping.c_str();
3244 3 : break;
3245 : }
3246 : }
3247 : }
3248 : }
3249 : }
3250 523 : char *pszGridMappingValue = CPLStrdup(pszValue ? pszValue : "");
3251 :
3252 523 : if (!EQUAL(pszGridMappingValue, ""))
3253 : {
3254 : // Read grid_mapping metadata.
3255 221 : int nProjGroupID = -1;
3256 221 : int nProjVarID = -1;
3257 221 : if (NCDFResolveVar(nGroupId, pszGridMappingValue, &nProjGroupID,
3258 221 : &nProjVarID) == CE_None)
3259 : {
3260 220 : poDS->ReadAttributes(nProjGroupID, nProjVarID);
3261 :
3262 : // Look for GDAL spatial_ref and GeoTransform within grid_mapping.
3263 220 : CPLFree(pszGridMappingValue);
3264 220 : pszGridMappingValue = nullptr;
3265 220 : NCDFGetVarFullName(nProjGroupID, nProjVarID, &pszGridMappingValue);
3266 220 : if (pszGridMappingValue)
3267 : {
3268 220 : CPLDebug("GDAL_netCDF", "got grid_mapping %s",
3269 : pszGridMappingValue);
3270 220 : pszWKT = FetchAttr(pszGridMappingValue, NCDF_SPATIAL_REF);
3271 220 : if (!pszWKT)
3272 : {
3273 35 : pszWKT = FetchAttr(pszGridMappingValue, NCDF_CRS_WKT);
3274 : }
3275 : else
3276 : {
3277 185 : bGotGdalSRS = true;
3278 185 : CPLDebug("GDAL_netCDF", "setting WKT from GDAL");
3279 : }
3280 220 : if (pszWKT)
3281 : {
3282 190 : if (!bGotGdalSRS)
3283 : {
3284 5 : bGotCfWktSRS = true;
3285 5 : CPLDebug("GDAL_netCDF", "setting WKT from CF");
3286 : }
3287 190 : if (returnProjStr != nullptr)
3288 : {
3289 41 : (*returnProjStr) = std::string(pszWKT);
3290 : }
3291 : else
3292 : {
3293 149 : m_bAddedProjectionVarsDefs = true;
3294 149 : m_bAddedProjectionVarsData = true;
3295 298 : OGRSpatialReference oSRSTmp;
3296 149 : oSRSTmp.SetAxisMappingStrategy(
3297 : OAMS_TRADITIONAL_GIS_ORDER);
3298 149 : oSRSTmp.importFromWkt(pszWKT);
3299 149 : SetSpatialRefNoUpdate(&oSRSTmp);
3300 : }
3301 : pszGeoTransform =
3302 190 : FetchAttr(pszGridMappingValue, NCDF_GEOTRANSFORM);
3303 : }
3304 : }
3305 : else
3306 : {
3307 0 : pszGridMappingValue = CPLStrdup("");
3308 : }
3309 : }
3310 : }
3311 :
3312 : // Get information about the file.
3313 : //
3314 : // Was this file created by the GDAL netcdf driver?
3315 : // Was this file created by the newer (CF-conformant) driver?
3316 : //
3317 : // 1) If GDAL netcdf metadata is set, and version >= 1.9,
3318 : // it was created with the new driver
3319 : // 2) Else, if spatial_ref and GeoTransform are present in the
3320 : // grid_mapping variable, it was created by the old driver
3321 523 : pszValue = FetchAttr("NC_GLOBAL", "GDAL");
3322 :
3323 523 : if (pszValue && NCDFIsGDALVersionGTE(pszValue, 1900))
3324 : {
3325 241 : bIsGdalFile = true;
3326 241 : bIsGdalCfFile = true;
3327 : }
3328 282 : else if (pszWKT != nullptr && pszGeoTransform != nullptr)
3329 : {
3330 18 : bIsGdalFile = true;
3331 18 : bIsGdalCfFile = false;
3332 : }
3333 :
3334 : // Set default bottom-up default value.
3335 : // Y axis dimension and absence of GT can modify this value.
3336 : // Override with Config option GDAL_NETCDF_BOTTOMUP.
3337 :
3338 : // New driver is bottom-up by default.
3339 523 : if ((bIsGdalFile && !bIsGdalCfFile) || bSwitchedXY)
3340 20 : poDS->bBottomUp = false;
3341 : else
3342 503 : poDS->bBottomUp = true;
3343 :
3344 523 : CPLDebug("GDAL_netCDF",
3345 : "bIsGdalFile=%d bIsGdalCfFile=%d bSwitchedXY=%d bBottomUp=%d",
3346 523 : static_cast<int>(bIsGdalFile), static_cast<int>(bIsGdalCfFile),
3347 523 : static_cast<int>(bSwitchedXY), static_cast<int>(bBottomUp));
3348 :
3349 : // Read projection coordinates.
3350 :
3351 523 : int nGroupDimXID = -1;
3352 523 : int nVarDimXID = -1;
3353 523 : int nGroupDimYID = -1;
3354 523 : int nVarDimYID = -1;
3355 523 : if (sg != nullptr)
3356 : {
3357 43 : nGroupDimXID = sg->get_ncID();
3358 43 : nGroupDimYID = sg->get_ncID();
3359 43 : nVarDimXID = sg->getNodeCoordVars()[0];
3360 43 : nVarDimYID = sg->getNodeCoordVars()[1];
3361 : }
3362 :
3363 523 : if (!bReadSRSOnly)
3364 : {
3365 351 : NCDFResolveVar(nGroupId, poDS->papszDimName[nXDimID], &nGroupDimXID,
3366 : &nVarDimXID);
3367 351 : NCDFResolveVar(nGroupId, poDS->papszDimName[nYDimID], &nGroupDimYID,
3368 : &nVarDimYID);
3369 : // TODO: if above resolving fails we should also search for coordinate
3370 : // variables without same name than dimension using the same resolving
3371 : // logic. This should handle for example NASA Ocean Color L2 products.
3372 :
3373 : const bool bIgnoreXYAxisNameChecks =
3374 702 : CPLTestBool(CSLFetchNameValueDef(
3375 351 : papszOpenOptions, "IGNORE_XY_AXIS_NAME_CHECKS",
3376 : CPLGetConfigOption("GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS",
3377 351 : "NO"))) ||
3378 : // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a res
3379 : // and transform attributes
3380 351 : (FetchAttr(nGroupId, nVarId, "res") != nullptr &&
3381 702 : FetchAttr(nGroupId, nVarId, "transform") != nullptr) ||
3382 350 : FetchAttr(nGroupId, NC_GLOBAL, "GMT_version") != nullptr;
3383 :
3384 : // Check that they are 1D or 2D variables
3385 351 : if (nVarDimXID >= 0)
3386 : {
3387 255 : int ndims = -1;
3388 255 : nc_inq_varndims(nGroupId, nVarDimXID, &ndims);
3389 255 : if (ndims == 0 || ndims > 2)
3390 0 : nVarDimXID = -1;
3391 255 : else if (!bIgnoreXYAxisNameChecks)
3392 : {
3393 253 : if (!NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
3394 163 : !NCDFIsVarProjectionX(nGroupId, nVarDimXID, nullptr) &&
3395 : // In case of inversion of X/Y
3396 448 : !NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr) &&
3397 32 : !NCDFIsVarProjectionY(nGroupId, nVarDimXID, nullptr))
3398 : {
3399 : char szVarNameX[NC_MAX_NAME + 1];
3400 32 : CPL_IGNORE_RET_VAL(
3401 32 : nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
3402 32 : if (!(ndims == 1 &&
3403 31 : (EQUAL(szVarNameX, CF_LONGITUDE_STD_NAME) ||
3404 30 : EQUAL(szVarNameX, CF_LONGITUDE_VAR_NAME))))
3405 : {
3406 31 : CPLDebug(
3407 : "netCDF",
3408 : "Georeferencing ignored due to non-specific "
3409 : "enough X axis name. "
3410 : "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
3411 : "as configuration option to bypass this check");
3412 31 : nVarDimXID = -1;
3413 : }
3414 : }
3415 : }
3416 : }
3417 :
3418 351 : if (nVarDimYID >= 0)
3419 : {
3420 257 : int ndims = -1;
3421 257 : nc_inq_varndims(nGroupId, nVarDimYID, &ndims);
3422 257 : if (ndims == 0 || ndims > 2)
3423 1 : nVarDimYID = -1;
3424 256 : else if (!bIgnoreXYAxisNameChecks)
3425 : {
3426 254 : if (!NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr) &&
3427 164 : !NCDFIsVarProjectionY(nGroupId, nVarDimYID, nullptr) &&
3428 : // In case of inversion of X/Y
3429 451 : !NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
3430 33 : !NCDFIsVarProjectionX(nGroupId, nVarDimYID, nullptr))
3431 : {
3432 : char szVarNameY[NC_MAX_NAME + 1];
3433 33 : CPL_IGNORE_RET_VAL(
3434 33 : nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
3435 33 : if (!(ndims == 1 &&
3436 33 : (EQUAL(szVarNameY, CF_LATITUDE_STD_NAME) ||
3437 32 : EQUAL(szVarNameY, CF_LATITUDE_VAR_NAME))))
3438 : {
3439 32 : CPLDebug(
3440 : "netCDF",
3441 : "Georeferencing ignored due to non-specific "
3442 : "enough Y axis name. "
3443 : "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
3444 : "as configuration option to bypass this check");
3445 32 : nVarDimYID = -1;
3446 : }
3447 : }
3448 : }
3449 : }
3450 :
3451 351 : if ((nVarDimXID >= 0 && xdim == 1) || (nVarDimXID >= 0 && ydim == 1))
3452 : {
3453 0 : CPLError(CE_Warning, CPLE_AppDefined,
3454 : "1-pixel width/height files not supported, "
3455 : "xdim: %ld ydim: %ld",
3456 : static_cast<long>(xdim), static_cast<long>(ydim));
3457 0 : nVarDimXID = -1;
3458 0 : nVarDimYID = -1;
3459 : }
3460 : }
3461 :
3462 523 : const char *pszUnits = nullptr;
3463 523 : if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
3464 : {
3465 267 : const char *pszUnitsX = FetchAttr(nGroupDimXID, nVarDimXID, "units");
3466 267 : const char *pszUnitsY = FetchAttr(nGroupDimYID, nVarDimYID, "units");
3467 : // Normalize degrees_east/degrees_north to degrees
3468 : // Cf https://github.com/OSGeo/gdal/issues/11009
3469 267 : if (pszUnitsX && EQUAL(pszUnitsX, "degrees_east"))
3470 79 : pszUnitsX = "degrees";
3471 267 : if (pszUnitsY && EQUAL(pszUnitsY, "degrees_north"))
3472 79 : pszUnitsY = "degrees";
3473 :
3474 267 : if (pszUnitsX && pszUnitsY)
3475 : {
3476 220 : if (EQUAL(pszUnitsX, pszUnitsY))
3477 217 : pszUnits = pszUnitsX;
3478 3 : else if (!pszWKT && !EQUAL(pszGridMappingValue, ""))
3479 : {
3480 0 : CPLError(CE_Failure, CPLE_AppDefined,
3481 : "X axis unit (%s) is different from Y axis "
3482 : "unit (%s). SRS will ignore axis unit and be "
3483 : "likely wrong.",
3484 : pszUnitsX, pszUnitsY);
3485 : }
3486 : }
3487 47 : else if (pszUnitsX && !pszWKT && !EQUAL(pszGridMappingValue, ""))
3488 : {
3489 0 : CPLError(CE_Failure, CPLE_AppDefined,
3490 : "X axis unit is defined, but not Y one ."
3491 : "SRS will ignore axis unit and be likely wrong.");
3492 : }
3493 47 : else if (pszUnitsY && !pszWKT && !EQUAL(pszGridMappingValue, ""))
3494 : {
3495 0 : CPLError(CE_Failure, CPLE_AppDefined,
3496 : "Y axis unit is defined, but not X one ."
3497 : "SRS will ignore axis unit and be likely wrong.");
3498 : }
3499 : }
3500 :
3501 523 : if (!pszWKT && !EQUAL(pszGridMappingValue, ""))
3502 : {
3503 31 : CPLStringList aosGridMappingKeyValues;
3504 31 : const size_t nLenGridMappingValue = strlen(pszGridMappingValue);
3505 789 : for (const char *const *papszIter = papszMetadata;
3506 789 : papszIter && *papszIter; ++papszIter)
3507 : {
3508 758 : if (STARTS_WITH(*papszIter, pszGridMappingValue) &&
3509 236 : (*papszIter)[nLenGridMappingValue] == '#')
3510 : {
3511 236 : char *pszKey = nullptr;
3512 472 : pszValue = CPLParseNameValue(
3513 236 : *papszIter + nLenGridMappingValue + 1, &pszKey);
3514 236 : if (pszKey && pszValue)
3515 236 : aosGridMappingKeyValues.SetNameValue(pszKey, pszValue);
3516 236 : CPLFree(pszKey);
3517 : }
3518 : }
3519 :
3520 31 : bGotGeogCS = aosGridMappingKeyValues.FetchNameValue(
3521 : CF_PP_SEMI_MAJOR_AXIS) != nullptr;
3522 :
3523 31 : oSRS.importFromCF1(aosGridMappingKeyValues.List(), pszUnits);
3524 31 : bGotCfSRS = oSRS.IsGeographic() || oSRS.IsProjected();
3525 : }
3526 : else
3527 : {
3528 : // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a "crs"
3529 : // attribute hold on the variable of interest that contains a PROJ.4
3530 : // string
3531 492 : pszValue = FetchAttr(nGroupId, nVarId, "crs");
3532 493 : if (pszValue &&
3533 1 : (strstr(pszValue, "+proj=") != nullptr ||
3534 0 : strstr(pszValue, "GEOGCS") != nullptr ||
3535 0 : strstr(pszValue, "PROJCS") != nullptr ||
3536 493 : strstr(pszValue, "EPSG:") != nullptr) &&
3537 1 : oSRS.SetFromUserInput(pszValue) == OGRERR_NONE)
3538 : {
3539 1 : bGotCfSRS = true;
3540 : }
3541 : }
3542 :
3543 : // Set Projection from CF.
3544 523 : double dfLinearUnitsConvFactor = 1.0;
3545 523 : if ((bGotGeogCS || bGotCfSRS))
3546 : {
3547 31 : if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
3548 : {
3549 : // Set SRS Units.
3550 :
3551 : // Check units for x and y.
3552 28 : if (oSRS.IsProjected())
3553 : {
3554 25 : dfLinearUnitsConvFactor = oSRS.GetLinearUnits(nullptr);
3555 :
3556 : // If the user doesn't ask to preserve the axis unit,
3557 : // then normalize to metre
3558 31 : if (dfLinearUnitsConvFactor != 1.0 &&
3559 6 : !CPLFetchBool(GetOpenOptions(), "PRESERVE_AXIS_UNIT_IN_CRS",
3560 : false))
3561 : {
3562 5 : oSRS.SetLinearUnits("metre", 1.0);
3563 5 : oSRS.SetAuthority("PROJCS|UNIT", "EPSG", 9001);
3564 : }
3565 : else
3566 : {
3567 20 : dfLinearUnitsConvFactor = 1.0;
3568 : }
3569 : }
3570 : }
3571 :
3572 : // Set projection.
3573 31 : char *pszTempProjection = nullptr;
3574 31 : oSRS.exportToWkt(&pszTempProjection);
3575 31 : if (pszTempProjection)
3576 : {
3577 31 : CPLDebug("GDAL_netCDF", "setting WKT from CF");
3578 31 : if (returnProjStr != nullptr)
3579 : {
3580 2 : (*returnProjStr) = std::string(pszTempProjection);
3581 : }
3582 : else
3583 : {
3584 29 : m_bAddedProjectionVarsDefs = true;
3585 29 : m_bAddedProjectionVarsData = true;
3586 29 : SetSpatialRefNoUpdate(&oSRS);
3587 : }
3588 : }
3589 31 : CPLFree(pszTempProjection);
3590 : }
3591 :
3592 523 : if (!bReadSRSOnly && (nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 &&
3593 : ydim > 0)
3594 : {
3595 : double *pdfXCoord =
3596 224 : static_cast<double *>(CPLCalloc(xdim, sizeof(double)));
3597 : double *pdfYCoord =
3598 224 : static_cast<double *>(CPLCalloc(ydim, sizeof(double)));
3599 :
3600 224 : size_t start[2] = {0, 0};
3601 224 : size_t edge[2] = {xdim, 0};
3602 224 : int status = nc_get_vara_double(nGroupDimXID, nVarDimXID, start, edge,
3603 : pdfXCoord);
3604 224 : NCDF_ERR(status);
3605 :
3606 224 : edge[0] = ydim;
3607 224 : status = nc_get_vara_double(nGroupDimYID, nVarDimYID, start, edge,
3608 : pdfYCoord);
3609 224 : NCDF_ERR(status);
3610 :
3611 224 : nc_type nc_var_dimx_datatype = NC_NAT;
3612 : status =
3613 224 : nc_inq_vartype(nGroupDimXID, nVarDimXID, &nc_var_dimx_datatype);
3614 224 : NCDF_ERR(status);
3615 :
3616 224 : nc_type nc_var_dimy_datatype = NC_NAT;
3617 : status =
3618 224 : nc_inq_vartype(nGroupDimYID, nVarDimYID, &nc_var_dimy_datatype);
3619 224 : NCDF_ERR(status);
3620 :
3621 224 : if (!poDS->bSwitchedXY)
3622 : {
3623 : // Convert ]180,540] longitude values to ]-180,0].
3624 312 : if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
3625 90 : CPLTestBool(
3626 : CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")))
3627 : {
3628 : // If minimum longitude is > 180, subtract 360 from all.
3629 : // Add a check on the maximum X value too, since
3630 : // NCDFIsVarLongitude() is not very specific by default (see
3631 : // https://github.com/OSGeo/gdal/issues/1440)
3632 97 : if (std::min(pdfXCoord[0], pdfXCoord[xdim - 1]) > 180.0 &&
3633 7 : std::max(pdfXCoord[0], pdfXCoord[xdim - 1]) <= 540)
3634 : {
3635 0 : CPLDebug(
3636 : "GDAL_netCDF",
3637 : "Offsetting longitudes from ]180,540] to ]-180,180]. "
3638 : "Can be disabled with GDAL_NETCDF_CENTERLONG_180=NO");
3639 0 : for (size_t i = 0; i < xdim; i++)
3640 0 : pdfXCoord[i] -= 360;
3641 : }
3642 : }
3643 : }
3644 :
3645 : // Is pixel spacing uniform across the map?
3646 :
3647 : // Check Longitude.
3648 :
3649 224 : bool bLonSpacingOK = false;
3650 224 : if (xdim == 2)
3651 : {
3652 28 : bLonSpacingOK = true;
3653 : }
3654 : else
3655 : {
3656 196 : bool bWestIsLeft = (pdfXCoord[0] < pdfXCoord[xdim - 1]);
3657 :
3658 : // fix longitudes if longitudes should increase from
3659 : // west to east, but west > east
3660 276 : if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
3661 80 : !bWestIsLeft)
3662 : {
3663 2 : size_t ndecreases = 0;
3664 :
3665 : // there is lon wrap if longitudes increase
3666 : // with one single decrease
3667 107 : for (size_t i = 1; i < xdim; i++)
3668 : {
3669 105 : if (pdfXCoord[i] < pdfXCoord[i - 1])
3670 1 : ndecreases++;
3671 : }
3672 :
3673 2 : if (ndecreases == 1)
3674 : {
3675 1 : CPLDebug("GDAL_netCDF", "longitude wrap detected");
3676 4 : for (size_t i = 0; i < xdim; i++)
3677 : {
3678 3 : if (pdfXCoord[i] > pdfXCoord[xdim - 1])
3679 1 : pdfXCoord[i] -= 360;
3680 : }
3681 : }
3682 : }
3683 :
3684 196 : const double dfSpacingBegin = pdfXCoord[1] - pdfXCoord[0];
3685 196 : const double dfSpacingMiddle =
3686 196 : pdfXCoord[xdim / 2 + 1] - pdfXCoord[xdim / 2];
3687 196 : const double dfSpacingLast =
3688 196 : pdfXCoord[xdim - 1] - pdfXCoord[xdim - 2];
3689 :
3690 196 : CPLDebug("GDAL_netCDF",
3691 : "xdim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
3692 : "dfSpacingLast: %f",
3693 : static_cast<long>(xdim), dfSpacingBegin, dfSpacingMiddle,
3694 : dfSpacingLast);
3695 : #ifdef NCDF_DEBUG
3696 : CPLDebug("GDAL_netCDF", "xcoords: %f %f %f %f %f %f", pdfXCoord[0],
3697 : pdfXCoord[1], pdfXCoord[xdim / 2],
3698 : pdfXCoord[(xdim / 2) + 1], pdfXCoord[xdim - 2],
3699 : pdfXCoord[xdim - 1]);
3700 : #endif
3701 :
3702 : // ftp://ftp.cdc.noaa.gov/Datasets/NARR/Dailies/monolevel/vwnd.10m.2015.nc
3703 : // requires a 0.02% tolerance, so let's settle for 0.05%
3704 :
3705 : // For float variables, increase to 0.2% (as seen in
3706 : // https://github.com/OSGeo/gdal/issues/3663)
3707 196 : const double dfEpsRel =
3708 196 : nc_var_dimx_datatype == NC_FLOAT ? 0.002 : 0.0005;
3709 :
3710 : const double dfEps =
3711 : dfEpsRel *
3712 392 : std::max(fabs(dfSpacingBegin),
3713 196 : std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
3714 386 : if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
3715 386 : IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
3716 190 : IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
3717 : {
3718 190 : bLonSpacingOK = true;
3719 : }
3720 6 : else if (CPLTestBool(CPLGetConfigOption(
3721 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
3722 : {
3723 0 : bLonSpacingOK = true;
3724 0 : CPLDebug(
3725 : "GDAL_netCDF",
3726 : "Longitude/X is not equally spaced, but will be considered "
3727 : "as such because of "
3728 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
3729 : }
3730 : }
3731 :
3732 224 : if (bLonSpacingOK == false)
3733 : {
3734 6 : CPLDebug(
3735 : "GDAL_netCDF", "%s",
3736 : "Longitude/X is not equally spaced (with a 0.05% tolerance). "
3737 : "You may set the "
3738 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
3739 : "option to YES to ignore this check");
3740 : }
3741 :
3742 : // Check Latitude.
3743 224 : bool bLatSpacingOK = false;
3744 :
3745 224 : if (ydim == 2)
3746 : {
3747 48 : bLatSpacingOK = true;
3748 : }
3749 : else
3750 : {
3751 176 : const double dfSpacingBegin = pdfYCoord[1] - pdfYCoord[0];
3752 176 : const double dfSpacingMiddle =
3753 176 : pdfYCoord[ydim / 2 + 1] - pdfYCoord[ydim / 2];
3754 :
3755 176 : const double dfSpacingLast =
3756 176 : pdfYCoord[ydim - 1] - pdfYCoord[ydim - 2];
3757 :
3758 176 : CPLDebug("GDAL_netCDF",
3759 : "ydim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
3760 : "dfSpacingLast: %f",
3761 : (long)ydim, dfSpacingBegin, dfSpacingMiddle,
3762 : dfSpacingLast);
3763 : #ifdef NCDF_DEBUG
3764 : CPLDebug("GDAL_netCDF", "ycoords: %f %f %f %f %f %f", pdfYCoord[0],
3765 : pdfYCoord[1], pdfYCoord[ydim / 2],
3766 : pdfYCoord[(ydim / 2) + 1], pdfYCoord[ydim - 2],
3767 : pdfYCoord[ydim - 1]);
3768 : #endif
3769 :
3770 176 : const double dfEpsRel =
3771 176 : nc_var_dimy_datatype == NC_FLOAT ? 0.002 : 0.0005;
3772 :
3773 : const double dfEps =
3774 : dfEpsRel *
3775 352 : std::max(fabs(dfSpacingBegin),
3776 176 : std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
3777 350 : if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
3778 350 : IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
3779 165 : IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
3780 : {
3781 165 : bLatSpacingOK = true;
3782 : }
3783 11 : else if (CPLTestBool(CPLGetConfigOption(
3784 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
3785 : {
3786 0 : bLatSpacingOK = true;
3787 0 : CPLDebug(
3788 : "GDAL_netCDF",
3789 : "Latitude/Y is not equally spaced, but will be considered "
3790 : "as such because of "
3791 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
3792 : }
3793 11 : else if (!oSRS.IsProjected() &&
3794 11 : fabs(dfSpacingBegin - dfSpacingLast) <= 0.1 &&
3795 30 : fabs(dfSpacingBegin - dfSpacingMiddle) <= 0.1 &&
3796 8 : fabs(dfSpacingMiddle - dfSpacingLast) <= 0.1)
3797 : {
3798 8 : bLatSpacingOK = true;
3799 8 : CPLError(CE_Warning, CPLE_AppDefined,
3800 : "Latitude grid not spaced evenly. "
3801 : "Setting projection for grid spacing is "
3802 : "within 0.1 degrees threshold.");
3803 :
3804 8 : CPLDebug("GDAL_netCDF",
3805 : "Latitude grid not spaced evenly, but within 0.1 "
3806 : "degree threshold (probably a Gaussian grid). "
3807 : "Saving original latitude values in Y_VALUES "
3808 : "geolocation metadata");
3809 8 : Set1DGeolocation(nGroupDimYID, nVarDimYID, "Y");
3810 : }
3811 :
3812 176 : if (bLatSpacingOK == false)
3813 : {
3814 3 : CPLDebug(
3815 : "GDAL_netCDF", "%s",
3816 : "Latitude/Y is not equally spaced (with a 0.05% "
3817 : "tolerance). "
3818 : "You may set the "
3819 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
3820 : "option to YES to ignore this check");
3821 : }
3822 : }
3823 :
3824 224 : if (bLonSpacingOK && bLatSpacingOK)
3825 : {
3826 : // We have gridded data so we can set the Georeferencing info.
3827 :
3828 : // Enable GeoTransform.
3829 :
3830 : // In the following "actual_range" and "node_offset"
3831 : // are attributes used by netCDF files created by GMT.
3832 : // If we find them we know how to proceed. Else, use
3833 : // the original algorithm.
3834 217 : bGotCfGT = true;
3835 :
3836 217 : int node_offset = 0;
3837 : const bool bUseActualRange =
3838 217 : NCDFResolveAttInt(nGroupId, NC_GLOBAL, "node_offset",
3839 217 : &node_offset) == CE_None;
3840 :
3841 217 : double adfActualRange[2] = {0.0, 0.0};
3842 217 : double xMinMax[2] = {0.0, 0.0};
3843 217 : double yMinMax[2] = {0.0, 0.0};
3844 :
3845 : const auto RoundMinMaxForFloatVals =
3846 60 : [](double &dfMin, double &dfMax, int nIntervals)
3847 : {
3848 : // Helps for a case where longitudes range from
3849 : // -179.99 to 180.0 with a 0.01 degree spacing.
3850 : // However as this is encoded in a float array,
3851 : // -179.99 is actually read as -179.99000549316406 as
3852 : // a double. Try to detect that and correct the rounding
3853 :
3854 88 : const auto IsAlmostInteger = [](double dfVal)
3855 : {
3856 88 : constexpr double THRESHOLD_INTEGER = 1e-3;
3857 88 : return std::fabs(dfVal - std::round(dfVal)) <=
3858 88 : THRESHOLD_INTEGER;
3859 : };
3860 :
3861 60 : const double dfSpacing = (dfMax - dfMin) / nIntervals;
3862 60 : if (dfSpacing > 0)
3863 : {
3864 48 : const double dfInvSpacing = 1.0 / dfSpacing;
3865 48 : if (IsAlmostInteger(dfInvSpacing))
3866 : {
3867 20 : const double dfRoundedSpacing =
3868 20 : 1.0 / std::round(dfInvSpacing);
3869 20 : const double dfMinDivRoundedSpacing =
3870 20 : dfMin / dfRoundedSpacing;
3871 20 : const double dfMaxDivRoundedSpacing =
3872 20 : dfMax / dfRoundedSpacing;
3873 40 : if (IsAlmostInteger(dfMinDivRoundedSpacing) &&
3874 20 : IsAlmostInteger(dfMaxDivRoundedSpacing))
3875 : {
3876 20 : const double dfRoundedMin =
3877 20 : std::round(dfMinDivRoundedSpacing) *
3878 : dfRoundedSpacing;
3879 20 : const double dfRoundedMax =
3880 20 : std::round(dfMaxDivRoundedSpacing) *
3881 : dfRoundedSpacing;
3882 20 : if (static_cast<float>(dfMin) ==
3883 20 : static_cast<float>(dfRoundedMin) &&
3884 8 : static_cast<float>(dfMax) ==
3885 8 : static_cast<float>(dfRoundedMax))
3886 : {
3887 7 : dfMin = dfRoundedMin;
3888 7 : dfMax = dfRoundedMax;
3889 : }
3890 : }
3891 : }
3892 : }
3893 60 : };
3894 :
3895 220 : if (bUseActualRange &&
3896 3 : !nc_get_att_double(nGroupDimXID, nVarDimXID, "actual_range",
3897 : adfActualRange))
3898 : {
3899 1 : xMinMax[0] = adfActualRange[0];
3900 1 : xMinMax[1] = adfActualRange[1];
3901 :
3902 : // Present xMinMax[] in the same order as padfXCoord
3903 1 : if ((xMinMax[0] - xMinMax[1]) *
3904 1 : (pdfXCoord[0] - pdfXCoord[xdim - 1]) <
3905 : 0)
3906 : {
3907 0 : std::swap(xMinMax[0], xMinMax[1]);
3908 : }
3909 : }
3910 : else
3911 : {
3912 216 : xMinMax[0] = pdfXCoord[0];
3913 216 : xMinMax[1] = pdfXCoord[xdim - 1];
3914 216 : node_offset = 0;
3915 :
3916 216 : if (nc_var_dimx_datatype == NC_FLOAT)
3917 : {
3918 30 : RoundMinMaxForFloatVals(xMinMax[0], xMinMax[1],
3919 30 : poDS->nRasterXSize - 1);
3920 : }
3921 : }
3922 :
3923 220 : if (bUseActualRange &&
3924 3 : !nc_get_att_double(nGroupDimYID, nVarDimYID, "actual_range",
3925 : adfActualRange))
3926 : {
3927 1 : yMinMax[0] = adfActualRange[0];
3928 1 : yMinMax[1] = adfActualRange[1];
3929 :
3930 : // Present yMinMax[] in the same order as pdfYCoord
3931 1 : if ((yMinMax[0] - yMinMax[1]) *
3932 1 : (pdfYCoord[0] - pdfYCoord[ydim - 1]) <
3933 : 0)
3934 : {
3935 0 : std::swap(yMinMax[0], yMinMax[1]);
3936 : }
3937 : }
3938 : else
3939 : {
3940 216 : yMinMax[0] = pdfYCoord[0];
3941 216 : yMinMax[1] = pdfYCoord[ydim - 1];
3942 216 : node_offset = 0;
3943 :
3944 216 : if (nc_var_dimy_datatype == NC_FLOAT)
3945 : {
3946 30 : RoundMinMaxForFloatVals(yMinMax[0], yMinMax[1],
3947 30 : poDS->nRasterYSize - 1);
3948 : }
3949 : }
3950 :
3951 217 : double dfCoordOffset = 0.0;
3952 217 : double dfCoordScale = 1.0;
3953 217 : if (!nc_get_att_double(nGroupId, nVarDimXID, CF_ADD_OFFSET,
3954 221 : &dfCoordOffset) &&
3955 4 : !nc_get_att_double(nGroupId, nVarDimXID, CF_SCALE_FACTOR,
3956 : &dfCoordScale))
3957 : {
3958 4 : xMinMax[0] = dfCoordOffset + xMinMax[0] * dfCoordScale;
3959 4 : xMinMax[1] = dfCoordOffset + xMinMax[1] * dfCoordScale;
3960 : }
3961 :
3962 217 : if (!nc_get_att_double(nGroupId, nVarDimYID, CF_ADD_OFFSET,
3963 221 : &dfCoordOffset) &&
3964 4 : !nc_get_att_double(nGroupId, nVarDimYID, CF_SCALE_FACTOR,
3965 : &dfCoordScale))
3966 : {
3967 4 : yMinMax[0] = dfCoordOffset + yMinMax[0] * dfCoordScale;
3968 4 : yMinMax[1] = dfCoordOffset + yMinMax[1] * dfCoordScale;
3969 : }
3970 :
3971 : // Check for reverse order of y-coordinate.
3972 217 : if (!bSwitchedXY)
3973 : {
3974 215 : poDS->bBottomUp = (yMinMax[0] <= yMinMax[1]);
3975 215 : CPLDebug("GDAL_netCDF", "set bBottomUp = %d from Y axis",
3976 215 : static_cast<int>(poDS->bBottomUp));
3977 215 : if (!poDS->bBottomUp)
3978 : {
3979 32 : std::swap(yMinMax[0], yMinMax[1]);
3980 : }
3981 : }
3982 :
3983 : // Geostationary satellites can specify units in (micro)radians
3984 : // So we check if they do, and if so convert to linear units
3985 : // (meters)
3986 217 : const char *pszProjName = oSRS.GetAttrValue("PROJECTION");
3987 217 : if (pszProjName != nullptr)
3988 : {
3989 24 : if (EQUAL(pszProjName, SRS_PT_GEOSTATIONARY_SATELLITE))
3990 : {
3991 : double satelliteHeight =
3992 3 : oSRS.GetProjParm(SRS_PP_SATELLITE_HEIGHT, 1.0);
3993 3 : size_t nAttlen = 0;
3994 : char szUnits[NC_MAX_NAME + 1];
3995 3 : szUnits[0] = '\0';
3996 3 : nc_type nAttype = NC_NAT;
3997 3 : nc_inq_att(nGroupId, nVarDimXID, "units", &nAttype,
3998 : &nAttlen);
3999 6 : if (nAttlen < sizeof(szUnits) &&
4000 3 : nc_get_att_text(nGroupId, nVarDimXID, "units",
4001 : szUnits) == NC_NOERR)
4002 : {
4003 3 : szUnits[nAttlen] = '\0';
4004 3 : if (EQUAL(szUnits, "microradian"))
4005 : {
4006 1 : xMinMax[0] =
4007 1 : xMinMax[0] * satelliteHeight * 0.000001;
4008 1 : xMinMax[1] =
4009 1 : xMinMax[1] * satelliteHeight * 0.000001;
4010 : }
4011 2 : else if (EQUAL(szUnits, "rad") ||
4012 1 : EQUAL(szUnits, "radian"))
4013 : {
4014 2 : xMinMax[0] = xMinMax[0] * satelliteHeight;
4015 2 : xMinMax[1] = xMinMax[1] * satelliteHeight;
4016 : }
4017 : }
4018 3 : szUnits[0] = '\0';
4019 3 : nc_inq_att(nGroupId, nVarDimYID, "units", &nAttype,
4020 : &nAttlen);
4021 6 : if (nAttlen < sizeof(szUnits) &&
4022 3 : nc_get_att_text(nGroupId, nVarDimYID, "units",
4023 : szUnits) == NC_NOERR)
4024 : {
4025 3 : szUnits[nAttlen] = '\0';
4026 3 : if (EQUAL(szUnits, "microradian"))
4027 : {
4028 1 : yMinMax[0] =
4029 1 : yMinMax[0] * satelliteHeight * 0.000001;
4030 1 : yMinMax[1] =
4031 1 : yMinMax[1] * satelliteHeight * 0.000001;
4032 : }
4033 2 : else if (EQUAL(szUnits, "rad") ||
4034 1 : EQUAL(szUnits, "radian"))
4035 : {
4036 2 : yMinMax[0] = yMinMax[0] * satelliteHeight;
4037 2 : yMinMax[1] = yMinMax[1] * satelliteHeight;
4038 : }
4039 : }
4040 : }
4041 : }
4042 :
4043 217 : tmpGT[0] = xMinMax[0];
4044 434 : tmpGT[1] = (xMinMax[1] - xMinMax[0]) /
4045 217 : (poDS->nRasterXSize + (node_offset - 1));
4046 217 : tmpGT[2] = 0;
4047 217 : if (bSwitchedXY)
4048 : {
4049 2 : tmpGT[3] = yMinMax[0];
4050 2 : tmpGT[4] = 0;
4051 2 : tmpGT[5] = (yMinMax[1] - yMinMax[0]) /
4052 2 : (poDS->nRasterYSize + (node_offset - 1));
4053 : }
4054 : else
4055 : {
4056 215 : tmpGT[3] = yMinMax[1];
4057 215 : tmpGT[4] = 0;
4058 215 : tmpGT[5] = (yMinMax[0] - yMinMax[1]) /
4059 215 : (poDS->nRasterYSize + (node_offset - 1));
4060 : }
4061 :
4062 : // Compute the center of the pixel.
4063 217 : if (!node_offset)
4064 : {
4065 : // Otherwise its already the pixel center.
4066 217 : tmpGT[0] -= (tmpGT[1] / 2);
4067 217 : tmpGT[3] -= (tmpGT[5] / 2);
4068 : }
4069 : }
4070 :
4071 : const auto AreSRSEqualThroughProj4String =
4072 2 : [](const OGRSpatialReference &oSRS1,
4073 : const OGRSpatialReference &oSRS2)
4074 : {
4075 2 : char *pszProj4Str1 = nullptr;
4076 2 : oSRS1.exportToProj4(&pszProj4Str1);
4077 :
4078 2 : char *pszProj4Str2 = nullptr;
4079 2 : oSRS2.exportToProj4(&pszProj4Str2);
4080 :
4081 : {
4082 2 : char *pszTmp = strstr(pszProj4Str1, "+datum=");
4083 2 : if (pszTmp)
4084 0 : memcpy(pszTmp, "+ellps=", strlen("+ellps="));
4085 : }
4086 :
4087 : {
4088 2 : char *pszTmp = strstr(pszProj4Str2, "+datum=");
4089 2 : if (pszTmp)
4090 2 : memcpy(pszTmp, "+ellps=", strlen("+ellps="));
4091 : }
4092 :
4093 2 : bool bRet = false;
4094 2 : if (pszProj4Str1 && pszProj4Str2 &&
4095 2 : EQUAL(pszProj4Str1, pszProj4Str2))
4096 : {
4097 1 : bRet = true;
4098 : }
4099 :
4100 2 : CPLFree(pszProj4Str1);
4101 2 : CPLFree(pszProj4Str2);
4102 2 : return bRet;
4103 : };
4104 :
4105 224 : if (dfLinearUnitsConvFactor != 1.0)
4106 : {
4107 35 : for (int i = 0; i < 6; ++i)
4108 30 : tmpGT[i] *= dfLinearUnitsConvFactor;
4109 :
4110 5 : if (paosRemovedMDItems)
4111 : {
4112 : char szVarNameX[NC_MAX_NAME + 1];
4113 5 : CPL_IGNORE_RET_VAL(
4114 5 : nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
4115 :
4116 : char szVarNameY[NC_MAX_NAME + 1];
4117 5 : CPL_IGNORE_RET_VAL(
4118 5 : nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
4119 :
4120 5 : paosRemovedMDItems->push_back(
4121 : CPLSPrintf("%s#units", szVarNameX));
4122 5 : paosRemovedMDItems->push_back(
4123 : CPLSPrintf("%s#units", szVarNameY));
4124 : }
4125 : }
4126 :
4127 : // If there is a global "geospatial_bounds_crs" attribute, check that it
4128 : // is consistent with the SRS, and if so, use it as the SRS
4129 : const char *pszGBCRS =
4130 224 : FetchAttr(nGroupId, NC_GLOBAL, "geospatial_bounds_crs");
4131 224 : if (pszGBCRS && STARTS_WITH(pszGBCRS, "EPSG:"))
4132 : {
4133 4 : OGRSpatialReference oSRSFromGBCRS;
4134 2 : oSRSFromGBCRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
4135 2 : if (oSRSFromGBCRS.SetFromUserInput(
4136 : pszGBCRS,
4137 : OGRSpatialReference::
4138 4 : SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE &&
4139 2 : AreSRSEqualThroughProj4String(oSRS, oSRSFromGBCRS))
4140 : {
4141 1 : oSRS = std::move(oSRSFromGBCRS);
4142 1 : SetSpatialRefNoUpdate(&oSRS);
4143 : }
4144 : }
4145 :
4146 224 : CPLFree(pdfXCoord);
4147 224 : CPLFree(pdfYCoord);
4148 : } // end if(has dims)
4149 :
4150 : // Process custom GeoTransform GDAL value.
4151 523 : if (!EQUAL(pszGridMappingValue, ""))
4152 : {
4153 221 : if (pszGeoTransform != nullptr)
4154 : {
4155 : CPLStringList aosGeoTransform(
4156 222 : CSLTokenizeString2(pszGeoTransform, " ", CSLT_HONOURSTRINGS));
4157 111 : if (aosGeoTransform.size() == 6)
4158 : {
4159 111 : GDALGeoTransform gtFromAttribute;
4160 777 : for (int i = 0; i < 6; i++)
4161 : {
4162 666 : gtFromAttribute[i] = CPLAtof(aosGeoTransform[i]);
4163 : }
4164 :
4165 111 : if (bGotCfGT)
4166 : {
4167 94 : constexpr double GT_RELERROR_WARN_THRESHOLD = 1e-6;
4168 94 : double dfMaxAbsoluteError = 0.0;
4169 658 : for (int i = 0; i < 6; i++)
4170 : {
4171 : double dfAbsoluteError =
4172 564 : std::abs(tmpGT[i] - gtFromAttribute[i]);
4173 564 : if (dfAbsoluteError >
4174 564 : std::abs(gtFromAttribute[i] *
4175 : GT_RELERROR_WARN_THRESHOLD))
4176 : {
4177 2 : dfMaxAbsoluteError =
4178 2 : std::max(dfMaxAbsoluteError, dfAbsoluteError);
4179 : }
4180 : }
4181 :
4182 94 : if (dfMaxAbsoluteError > 0)
4183 : {
4184 2 : CPLError(CE_Warning, CPLE_AppDefined,
4185 : "GeoTransform read from attribute of %s "
4186 : "variable differs from value calculated from "
4187 : "dimension variables (max diff = %g). Using "
4188 : "value from attribute.",
4189 : pszGridMappingValue, dfMaxAbsoluteError);
4190 : }
4191 : }
4192 :
4193 111 : tmpGT = std::move(gtFromAttribute);
4194 111 : bGotGdalGT = true;
4195 : }
4196 : }
4197 : else
4198 : {
4199 : // Look for corner array values.
4200 : // CPLDebug("GDAL_netCDF",
4201 : // "looking for geotransform corners");
4202 110 : bool bGotNN = false;
4203 110 : double dfNN = FetchCopyParam(pszGridMappingValue,
4204 : "Northernmost_Northing", 0, &bGotNN);
4205 :
4206 110 : bool bGotSN = false;
4207 110 : double dfSN = FetchCopyParam(pszGridMappingValue,
4208 : "Southernmost_Northing", 0, &bGotSN);
4209 :
4210 110 : bool bGotEE = false;
4211 110 : double dfEE = FetchCopyParam(pszGridMappingValue,
4212 : "Easternmost_Easting", 0, &bGotEE);
4213 :
4214 110 : bool bGotWE = false;
4215 110 : double dfWE = FetchCopyParam(pszGridMappingValue,
4216 : "Westernmost_Easting", 0, &bGotWE);
4217 :
4218 : // Only set the GeoTransform if we got all the values.
4219 110 : if (bGotNN && bGotSN && bGotEE && bGotWE)
4220 : {
4221 0 : bGotGdalGT = true;
4222 :
4223 0 : tmpGT[0] = dfWE;
4224 0 : tmpGT[1] = (dfEE - dfWE) / (poDS->GetRasterXSize() - 1);
4225 0 : tmpGT[2] = 0.0;
4226 0 : tmpGT[3] = dfNN;
4227 0 : tmpGT[4] = 0.0;
4228 0 : tmpGT[5] = (dfSN - dfNN) / (poDS->GetRasterYSize() - 1);
4229 : // Compute the center of the pixel.
4230 0 : tmpGT[0] = dfWE - (tmpGT[1] / 2);
4231 0 : tmpGT[3] = dfNN - (tmpGT[5] / 2);
4232 : }
4233 : } // (pszGeoTransform != NULL)
4234 :
4235 221 : if (bGotGdalSRS && !bGotGdalGT)
4236 74 : CPLDebug("GDAL_netCDF", "Got SRS but no geotransform from GDAL!");
4237 : }
4238 :
4239 523 : if (!pszWKT && !bGotCfSRS)
4240 : {
4241 : // Some netCDF files have a srid attribute (#6613) like
4242 : // urn:ogc:def:crs:EPSG::6931
4243 302 : const char *pszSRID = FetchAttr(pszGridMappingValue, "srid");
4244 302 : if (pszSRID != nullptr)
4245 : {
4246 0 : oSRS.Clear();
4247 0 : if (oSRS.SetFromUserInput(
4248 : pszSRID,
4249 : OGRSpatialReference::
4250 0 : SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE)
4251 : {
4252 0 : char *pszWKTExport = nullptr;
4253 0 : CPLDebug("GDAL_netCDF", "Got SRS from %s", pszSRID);
4254 0 : oSRS.exportToWkt(&pszWKTExport);
4255 0 : if (returnProjStr != nullptr)
4256 : {
4257 0 : (*returnProjStr) = std::string(pszWKTExport);
4258 : }
4259 : else
4260 : {
4261 0 : m_bAddedProjectionVarsDefs = true;
4262 0 : m_bAddedProjectionVarsData = true;
4263 0 : SetSpatialRefNoUpdate(&oSRS);
4264 : }
4265 0 : CPLFree(pszWKTExport);
4266 : }
4267 : }
4268 : }
4269 :
4270 523 : CPLFree(pszGridMappingValue);
4271 :
4272 523 : if (bReadSRSOnly)
4273 172 : return;
4274 :
4275 : // Determines the SRS to be used by the geolocation array, if any
4276 702 : std::string osGeolocWKT = SRS_WKT_WGS84_LAT_LONG;
4277 351 : if (!m_oSRS.IsEmpty())
4278 : {
4279 264 : OGRSpatialReference oGeogCRS;
4280 132 : oGeogCRS.CopyGeogCSFrom(&m_oSRS);
4281 132 : char *pszWKTTmp = nullptr;
4282 132 : const char *const apszOptions[] = {"FORMAT=WKT2_2019", nullptr};
4283 132 : if (oGeogCRS.exportToWkt(&pszWKTTmp, apszOptions) == OGRERR_NONE)
4284 : {
4285 132 : osGeolocWKT = pszWKTTmp;
4286 : }
4287 132 : CPLFree(pszWKTTmp);
4288 : }
4289 :
4290 : // Process geolocation arrays from CF "coordinates" attribute.
4291 702 : std::string osGeolocXName, osGeolocYName;
4292 351 : if (ProcessCFGeolocation(nGroupId, nVarId, osGeolocWKT, osGeolocXName,
4293 351 : osGeolocYName))
4294 : {
4295 53 : bool bCanCancelGT = true;
4296 53 : if ((nVarDimXID != -1) && (nVarDimYID != -1))
4297 : {
4298 : char szVarNameX[NC_MAX_NAME + 1];
4299 44 : CPL_IGNORE_RET_VAL(
4300 44 : nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
4301 : char szVarNameY[NC_MAX_NAME + 1];
4302 44 : CPL_IGNORE_RET_VAL(
4303 44 : nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
4304 44 : bCanCancelGT =
4305 44 : !(osGeolocXName == szVarNameX && osGeolocYName == szVarNameY);
4306 : }
4307 88 : if (bCanCancelGT && !m_oSRS.IsGeographic() && !m_oSRS.IsProjected() &&
4308 35 : !bSwitchedXY)
4309 : {
4310 33 : bGotCfGT = false;
4311 : }
4312 : }
4313 121 : else if (!bGotCfGT && !bReadSRSOnly && (nVarDimXID != -1) &&
4314 422 : (nVarDimYID != -1) && xdim > 0 && ydim > 0 &&
4315 3 : ((!bSwitchedXY &&
4316 3 : NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
4317 1 : NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr)) ||
4318 2 : (bSwitchedXY &&
4319 0 : NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
4320 0 : NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr))))
4321 : {
4322 : // Case of autotest/gdrivers/data/netcdf/GLMELT_4X5.OCN.nc
4323 : // which is indexed by lat, lon variables, but lat has irregular
4324 : // spacing.
4325 1 : const char *pszGeolocXFullName = poDS->papszDimName[poDS->nXDimID];
4326 1 : const char *pszGeolocYFullName = poDS->papszDimName[poDS->nYDimID];
4327 1 : if (bSwitchedXY)
4328 : {
4329 0 : std::swap(pszGeolocXFullName, pszGeolocYFullName);
4330 0 : GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", "GEOLOCATION");
4331 : }
4332 :
4333 1 : CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
4334 : pszGeolocXFullName, pszGeolocYFullName);
4335 :
4336 1 : GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
4337 : "GEOLOCATION");
4338 :
4339 2 : CPLString osTMP;
4340 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4341 1 : pszGeolocXFullName);
4342 :
4343 1 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
4344 1 : GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
4345 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4346 1 : pszGeolocYFullName);
4347 :
4348 1 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
4349 1 : GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
4350 :
4351 1 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
4352 1 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
4353 :
4354 1 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
4355 1 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
4356 :
4357 1 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
4358 : "PIXEL_CENTER", "GEOLOCATION");
4359 : }
4360 :
4361 : // Set GeoTransform if we got a complete one - after projection has been set
4362 351 : if (bGotCfGT || bGotGdalGT)
4363 : {
4364 199 : m_bAddedProjectionVarsDefs = true;
4365 199 : m_bAddedProjectionVarsData = true;
4366 199 : SetGeoTransformNoUpdate(tmpGT);
4367 : }
4368 :
4369 : // Debugging reports.
4370 351 : CPLDebug("GDAL_netCDF",
4371 : "bGotGeogCS=%d bGotCfSRS=%d bGotCfGT=%d bGotCfWktSRS=%d "
4372 : "bGotGdalSRS=%d bGotGdalGT=%d",
4373 : static_cast<int>(bGotGeogCS), static_cast<int>(bGotCfSRS),
4374 : static_cast<int>(bGotCfGT), static_cast<int>(bGotCfWktSRS),
4375 : static_cast<int>(bGotGdalSRS), static_cast<int>(bGotGdalGT));
4376 :
4377 351 : if (!bGotCfGT && !bGotGdalGT)
4378 152 : CPLDebug("GDAL_netCDF", "did not get geotransform from CF nor GDAL!");
4379 :
4380 351 : if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfGT && !bGotCfWktSRS)
4381 152 : CPLDebug("GDAL_netCDF", "did not get projection from CF nor GDAL!");
4382 :
4383 : // wish of 6195
4384 : // we don't have CS/SRS, but we do have GT, and we live in -180,360 -90,90
4385 351 : if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfWktSRS)
4386 : {
4387 219 : if (bGotCfGT || bGotGdalGT)
4388 : {
4389 134 : bool bAssumedLongLat = CPLTestBool(CSLFetchNameValueDef(
4390 67 : papszOpenOptions, "ASSUME_LONGLAT",
4391 : CPLGetConfigOption("GDAL_NETCDF_ASSUME_LONGLAT", "NO")));
4392 :
4393 2 : if (bAssumedLongLat && tmpGT[0] >= -180 && tmpGT[0] < 360 &&
4394 2 : (tmpGT[0] + tmpGT[1] * poDS->GetRasterXSize()) <= 360 &&
4395 71 : tmpGT[3] <= 90 && tmpGT[3] > -90 &&
4396 2 : (tmpGT[3] + tmpGT[5] * poDS->GetRasterYSize()) >= -90)
4397 : {
4398 :
4399 2 : poDS->bIsGeographic = true;
4400 2 : char *pszTempProjection = nullptr;
4401 : // seems odd to use 4326 so OGC:CRS84
4402 2 : oSRS.SetFromUserInput("OGC:CRS84");
4403 2 : oSRS.exportToWkt(&pszTempProjection);
4404 2 : if (returnProjStr != nullptr)
4405 : {
4406 0 : (*returnProjStr) = std::string(pszTempProjection);
4407 : }
4408 : else
4409 : {
4410 2 : m_bAddedProjectionVarsDefs = true;
4411 2 : m_bAddedProjectionVarsData = true;
4412 2 : SetSpatialRefNoUpdate(&oSRS);
4413 : }
4414 2 : CPLFree(pszTempProjection);
4415 :
4416 2 : CPLDebug("netCDF",
4417 : "Assumed Longitude Latitude CRS 'OGC:CRS84' because "
4418 : "none otherwise available and geotransform within "
4419 : "suitable bounds. "
4420 : "Set GDAL_NETCDF_ASSUME_LONGLAT=NO as configuration "
4421 : "option or "
4422 : " ASSUME_LONGLAT=NO as open option to bypass this "
4423 : "assumption.");
4424 : }
4425 : }
4426 : }
4427 :
4428 : // Search for Well-known GeogCS if got only CF WKT
4429 : // Disabled for now, as a named datum also include control points
4430 : // (see mailing list and bug#4281
4431 : // For example, WGS84 vs. GDA94 (EPSG:3577) - AEA in netcdf_cf.py
4432 :
4433 : // Disabled for now, but could be set in a config option.
4434 : #if 0
4435 : bool bLookForWellKnownGCS = false; // This could be a Config Option.
4436 :
4437 : if( bLookForWellKnownGCS && bGotCfSRS && !bGotGdalSRS )
4438 : {
4439 : // ET - Could use a more exhaustive method by scanning all EPSG codes in
4440 : // data/gcs.csv as proposed by Even in the gdal-dev mailing list "help
4441 : // for comparing two WKT".
4442 : // This code could be contributed to a new function.
4443 : // OGRSpatialReference * OGRSpatialReference::FindMatchingGeogCS(
4444 : // const OGRSpatialReference *poOther) */
4445 : CPLDebug("GDAL_netCDF", "Searching for Well-known GeogCS");
4446 : const char *pszWKGCSList[] = { "WGS84", "WGS72", "NAD27", "NAD83" };
4447 : char *pszWKGCS = NULL;
4448 : oSRS.exportToPrettyWkt(&pszWKGCS);
4449 : for( size_t i = 0; i < sizeof(pszWKGCSList) / 8; i++ )
4450 : {
4451 : pszWKGCS = CPLStrdup(pszWKGCSList[i]);
4452 : OGRSpatialReference oSRSTmp;
4453 : oSRSTmp.SetWellKnownGeogCS(pszWKGCSList[i]);
4454 : // Set datum to unknown, bug #4281.
4455 : if( oSRSTmp.GetAttrNode("DATUM" ) )
4456 : oSRSTmp.GetAttrNode("DATUM")->GetChild(0)->SetValue("unknown");
4457 : // Could use OGRSpatialReference::StripCTParms(), but let's keep
4458 : // TOWGS84.
4459 : oSRSTmp.GetRoot()->StripNodes("AXIS");
4460 : oSRSTmp.GetRoot()->StripNodes("AUTHORITY");
4461 : oSRSTmp.GetRoot()->StripNodes("EXTENSION");
4462 :
4463 : oSRSTmp.exportToPrettyWkt(&pszWKGCS);
4464 : if( oSRS.IsSameGeogCS(&oSRSTmp) )
4465 : {
4466 : oSRS.SetWellKnownGeogCS(pszWKGCSList[i]);
4467 : oSRS.exportToWkt(&(pszTempProjection));
4468 : SetProjection(pszTempProjection);
4469 : CPLFree(pszTempProjection);
4470 : }
4471 : }
4472 : }
4473 : #endif
4474 : }
4475 :
4476 129 : void netCDFDataset::SetProjectionFromVar(int nGroupId, int nVarId,
4477 : bool bReadSRSOnly)
4478 : {
4479 129 : SetProjectionFromVar(nGroupId, nVarId, bReadSRSOnly, nullptr, nullptr,
4480 : nullptr, nullptr);
4481 129 : }
4482 :
4483 287 : bool netCDFDataset::ProcessNASAL2OceanGeoLocation(int nGroupId, int nVarId)
4484 : {
4485 : // Cf https://oceancolor.gsfc.nasa.gov/docs/format/l2nc/
4486 : // and https://github.com/OSGeo/gdal/issues/7605
4487 :
4488 : // Check for a structure like:
4489 : /* netcdf SNPP_VIIRS.20230406T024200.L2.OC.NRT {
4490 : dimensions:
4491 : number_of_lines = 3248 ;
4492 : pixels_per_line = 3200 ;
4493 : [...]
4494 : pixel_control_points = 3200 ;
4495 : [...]
4496 : group: geophysical_data {
4497 : variables:
4498 : short aot_862(number_of_lines, pixels_per_line) ; <-- nVarId
4499 : [...]
4500 : }
4501 : group: navigation_data {
4502 : variables:
4503 : float longitude(number_of_lines, pixel_control_points) ;
4504 : [...]
4505 : float latitude(number_of_lines, pixel_control_points) ;
4506 : [...]
4507 : }
4508 : }
4509 : */
4510 : // Note that the longitude and latitude arrays are not indexed by the
4511 : // same dimensions. Handle only the case where
4512 : // pixel_control_points == pixels_per_line
4513 : // If there was a subsampling of the geolocation arrays, we'd need to
4514 : // add more logic.
4515 :
4516 574 : std::string osGroupName;
4517 287 : osGroupName.resize(NC_MAX_NAME);
4518 287 : NCDF_ERR(nc_inq_grpname(nGroupId, &osGroupName[0]));
4519 287 : osGroupName.resize(strlen(osGroupName.data()));
4520 287 : if (osGroupName != "geophysical_data")
4521 286 : return false;
4522 :
4523 1 : int nVarDims = 0;
4524 1 : NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
4525 1 : if (nVarDims != 2)
4526 0 : return false;
4527 :
4528 1 : int nNavigationDataGrpId = 0;
4529 1 : if (nc_inq_grp_ncid(cdfid, "navigation_data", &nNavigationDataGrpId) !=
4530 : NC_NOERR)
4531 0 : return false;
4532 :
4533 : std::array<int, 2> anVarDimIds;
4534 1 : NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
4535 :
4536 1 : int nLongitudeId = 0;
4537 1 : int nLatitudeId = 0;
4538 1 : if (nc_inq_varid(nNavigationDataGrpId, "longitude", &nLongitudeId) !=
4539 2 : NC_NOERR ||
4540 1 : nc_inq_varid(nNavigationDataGrpId, "latitude", &nLatitudeId) !=
4541 : NC_NOERR)
4542 : {
4543 0 : return false;
4544 : }
4545 :
4546 1 : int nDimsLongitude = 0;
4547 1 : NCDF_ERR(
4548 : nc_inq_varndims(nNavigationDataGrpId, nLongitudeId, &nDimsLongitude));
4549 1 : int nDimsLatitude = 0;
4550 1 : NCDF_ERR(
4551 : nc_inq_varndims(nNavigationDataGrpId, nLatitudeId, &nDimsLatitude));
4552 1 : if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
4553 : {
4554 0 : return false;
4555 : }
4556 :
4557 : std::array<int, 2> anDimLongitudeIds;
4558 1 : NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLongitudeId,
4559 : anDimLongitudeIds.data()));
4560 : std::array<int, 2> anDimLatitudeIds;
4561 1 : NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLatitudeId,
4562 : anDimLatitudeIds.data()));
4563 1 : if (anDimLongitudeIds != anDimLatitudeIds)
4564 : {
4565 0 : return false;
4566 : }
4567 :
4568 : std::array<size_t, 2> anSizeVarDimIds;
4569 : std::array<size_t, 2> anSizeLongLatIds;
4570 2 : if (!(nc_inq_dimlen(cdfid, anVarDimIds[0], &anSizeVarDimIds[0]) ==
4571 1 : NC_NOERR &&
4572 1 : nc_inq_dimlen(cdfid, anVarDimIds[1], &anSizeVarDimIds[1]) ==
4573 1 : NC_NOERR &&
4574 1 : nc_inq_dimlen(cdfid, anDimLongitudeIds[0], &anSizeLongLatIds[0]) ==
4575 1 : NC_NOERR &&
4576 1 : nc_inq_dimlen(cdfid, anDimLongitudeIds[1], &anSizeLongLatIds[1]) ==
4577 : NC_NOERR &&
4578 1 : anSizeVarDimIds == anSizeLongLatIds))
4579 : {
4580 0 : return false;
4581 : }
4582 :
4583 1 : const char *pszGeolocXFullName = "/navigation_data/longitude";
4584 1 : const char *pszGeolocYFullName = "/navigation_data/latitude";
4585 :
4586 1 : if (bSwitchedXY)
4587 : {
4588 0 : std::swap(pszGeolocXFullName, pszGeolocYFullName);
4589 0 : GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", "GEOLOCATION");
4590 : }
4591 :
4592 1 : CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
4593 : pszGeolocXFullName, pszGeolocYFullName);
4594 :
4595 1 : GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
4596 : "GEOLOCATION");
4597 :
4598 1 : CPLString osTMP;
4599 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
4600 :
4601 1 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
4602 1 : GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
4603 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
4604 :
4605 1 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
4606 1 : GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
4607 :
4608 1 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
4609 1 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
4610 :
4611 1 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
4612 1 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
4613 :
4614 1 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
4615 : "GEOLOCATION");
4616 1 : return true;
4617 : }
4618 :
4619 286 : bool netCDFDataset::ProcessNASAEMITGeoLocation(int nGroupId, int nVarId)
4620 : {
4621 : // Cf https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/
4622 :
4623 : // Check for a structure like:
4624 : /* netcdf EMIT_L2A_RFL_001_20220903T163129_2224611_012 {
4625 : dimensions:
4626 : downtrack = 1280 ;
4627 : crosstrack = 1242 ;
4628 : bands = 285 ;
4629 : [...]
4630 :
4631 : variables:
4632 : float reflectance(downtrack, crosstrack, bands) ;
4633 :
4634 : group: location {
4635 : variables:
4636 : double lon(downtrack, crosstrack) ;
4637 : lon:_FillValue = -9999. ;
4638 : lon:long_name = "Longitude (WGS-84)" ;
4639 : lon:units = "degrees east" ;
4640 : double lat(downtrack, crosstrack) ;
4641 : lat:_FillValue = -9999. ;
4642 : lat:long_name = "Latitude (WGS-84)" ;
4643 : lat:units = "degrees north" ;
4644 : } // group location
4645 :
4646 : }
4647 : or
4648 : netcdf EMIT_L2B_MIN_001_20231024T055538_2329704_040 {
4649 : dimensions:
4650 : downtrack = 1664 ;
4651 : crosstrack = 1242 ;
4652 : [...]
4653 : variables:
4654 : float group_1_band_depth(downtrack, crosstrack) ;
4655 : group_1_band_depth:_FillValue = -9999.f ;
4656 : group_1_band_depth:long_name = "Group 1 Band Depth" ;
4657 : group_1_band_depth:units = "unitless" ;
4658 : [...]
4659 : group: location {
4660 : variables:
4661 : double lon(downtrack, crosstrack) ;
4662 : lon:_FillValue = -9999. ;
4663 : lon:long_name = "Longitude (WGS-84)" ;
4664 : lon:units = "degrees east" ;
4665 : double lat(downtrack, crosstrack) ;
4666 : lat:_FillValue = -9999. ;
4667 : lat:long_name = "Latitude (WGS-84)" ;
4668 : lat:units = "degrees north" ;
4669 : }
4670 : */
4671 :
4672 286 : int nVarDims = 0;
4673 286 : NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
4674 286 : if (nVarDims != 2 && nVarDims != 3)
4675 14 : return false;
4676 :
4677 272 : int nLocationGrpId = 0;
4678 272 : if (nc_inq_grp_ncid(cdfid, "location", &nLocationGrpId) != NC_NOERR)
4679 58 : return false;
4680 :
4681 : std::array<int, 3> anVarDimIds;
4682 214 : NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
4683 214 : if (nYDimID != anVarDimIds[0] || nXDimID != anVarDimIds[1])
4684 21 : return false;
4685 :
4686 193 : int nLongitudeId = 0;
4687 193 : int nLatitudeId = 0;
4688 231 : if (nc_inq_varid(nLocationGrpId, "lon", &nLongitudeId) != NC_NOERR ||
4689 38 : nc_inq_varid(nLocationGrpId, "lat", &nLatitudeId) != NC_NOERR)
4690 : {
4691 155 : return false;
4692 : }
4693 :
4694 38 : int nDimsLongitude = 0;
4695 38 : NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLongitudeId, &nDimsLongitude));
4696 38 : int nDimsLatitude = 0;
4697 38 : NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLatitudeId, &nDimsLatitude));
4698 38 : if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
4699 : {
4700 34 : return false;
4701 : }
4702 :
4703 : std::array<int, 2> anDimLongitudeIds;
4704 4 : NCDF_ERR(nc_inq_vardimid(nLocationGrpId, nLongitudeId,
4705 : anDimLongitudeIds.data()));
4706 : std::array<int, 2> anDimLatitudeIds;
4707 4 : NCDF_ERR(
4708 : nc_inq_vardimid(nLocationGrpId, nLatitudeId, anDimLatitudeIds.data()));
4709 4 : if (anDimLongitudeIds != anDimLatitudeIds)
4710 : {
4711 0 : return false;
4712 : }
4713 :
4714 8 : if (anDimLongitudeIds[0] != anVarDimIds[0] ||
4715 4 : anDimLongitudeIds[1] != anVarDimIds[1])
4716 : {
4717 0 : return false;
4718 : }
4719 :
4720 4 : const char *pszGeolocXFullName = "/location/lon";
4721 4 : const char *pszGeolocYFullName = "/location/lat";
4722 :
4723 4 : CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
4724 : pszGeolocXFullName, pszGeolocYFullName);
4725 :
4726 4 : GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
4727 : "GEOLOCATION");
4728 :
4729 4 : CPLString osTMP;
4730 4 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
4731 :
4732 4 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
4733 4 : GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
4734 4 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
4735 :
4736 4 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
4737 4 : GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
4738 :
4739 4 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
4740 4 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
4741 :
4742 4 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
4743 4 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
4744 :
4745 4 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
4746 : "GEOLOCATION");
4747 4 : return true;
4748 : }
4749 :
4750 351 : int netCDFDataset::ProcessCFGeolocation(int nGroupId, int nVarId,
4751 : const std::string &osGeolocWKT,
4752 : std::string &osGeolocXNameOut,
4753 : std::string &osGeolocYNameOut)
4754 : {
4755 351 : bool bAddGeoloc = false;
4756 351 : char *pszCoordinates = nullptr;
4757 :
4758 : // If there is no explicit "coordinates" attribute, check if there are
4759 : // "lon" and "lat" 2D variables whose dimensions are the last
4760 : // 2 ones of the variable of interest.
4761 351 : if (NCDFGetAttr(nGroupId, nVarId, "coordinates", &pszCoordinates) !=
4762 : CE_None)
4763 : {
4764 304 : CPLFree(pszCoordinates);
4765 304 : pszCoordinates = nullptr;
4766 :
4767 304 : int nVarDims = 0;
4768 304 : NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
4769 304 : if (nVarDims >= 2)
4770 : {
4771 608 : std::vector<int> anVarDimIds(nVarDims);
4772 304 : NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
4773 :
4774 304 : int nLongitudeId = 0;
4775 304 : int nLatitudeId = 0;
4776 372 : if (nc_inq_varid(nGroupId, "lon", &nLongitudeId) == NC_NOERR &&
4777 68 : nc_inq_varid(nGroupId, "lat", &nLatitudeId) == NC_NOERR)
4778 : {
4779 68 : int nDimsLongitude = 0;
4780 68 : NCDF_ERR(
4781 : nc_inq_varndims(nGroupId, nLongitudeId, &nDimsLongitude));
4782 68 : int nDimsLatitude = 0;
4783 68 : NCDF_ERR(
4784 : nc_inq_varndims(nGroupId, nLatitudeId, &nDimsLatitude));
4785 68 : if (nDimsLongitude == 2 && nDimsLatitude == 2)
4786 : {
4787 34 : std::vector<int> anDimLongitudeIds(2);
4788 17 : NCDF_ERR(nc_inq_vardimid(nGroupId, nLongitudeId,
4789 : anDimLongitudeIds.data()));
4790 34 : std::vector<int> anDimLatitudeIds(2);
4791 17 : NCDF_ERR(nc_inq_vardimid(nGroupId, nLatitudeId,
4792 : anDimLatitudeIds.data()));
4793 17 : if (anDimLongitudeIds == anDimLatitudeIds &&
4794 34 : anVarDimIds[anVarDimIds.size() - 2] ==
4795 51 : anDimLongitudeIds[0] &&
4796 34 : anVarDimIds[anVarDimIds.size() - 1] ==
4797 17 : anDimLongitudeIds[1])
4798 : {
4799 17 : pszCoordinates = CPLStrdup("lon lat");
4800 : }
4801 : }
4802 : }
4803 : }
4804 : }
4805 :
4806 351 : if (pszCoordinates)
4807 : {
4808 : // Get X and Y geolocation names from coordinates attribute.
4809 : const CPLStringList aosCoordinates(
4810 128 : NCDFTokenizeCoordinatesAttribute(pszCoordinates));
4811 64 : if (aosCoordinates.size() >= 2)
4812 : {
4813 : char szGeolocXName[NC_MAX_NAME + 1];
4814 : char szGeolocYName[NC_MAX_NAME + 1];
4815 61 : szGeolocXName[0] = '\0';
4816 61 : szGeolocYName[0] = '\0';
4817 :
4818 : // Test that each variable is longitude/latitude.
4819 196 : for (int i = 0; i < aosCoordinates.size(); i++)
4820 : {
4821 135 : if (NCDFIsVarLongitude(nGroupId, -1, aosCoordinates[i]))
4822 : {
4823 50 : int nOtherGroupId = -1;
4824 50 : int nOtherVarId = -1;
4825 : // Check that the variable actually exists
4826 : // Needed on Sentinel-3 products
4827 50 : if (NCDFResolveVar(nGroupId, aosCoordinates[i],
4828 50 : &nOtherGroupId, &nOtherVarId) == CE_None)
4829 : {
4830 48 : snprintf(szGeolocXName, sizeof(szGeolocXName), "%s",
4831 : aosCoordinates[i]);
4832 : }
4833 : }
4834 85 : else if (NCDFIsVarLatitude(nGroupId, -1, aosCoordinates[i]))
4835 : {
4836 50 : int nOtherGroupId = -1;
4837 50 : int nOtherVarId = -1;
4838 : // Check that the variable actually exists
4839 : // Needed on Sentinel-3 products
4840 50 : if (NCDFResolveVar(nGroupId, aosCoordinates[i],
4841 50 : &nOtherGroupId, &nOtherVarId) == CE_None)
4842 : {
4843 48 : snprintf(szGeolocYName, sizeof(szGeolocYName), "%s",
4844 : aosCoordinates[i]);
4845 : }
4846 : }
4847 : }
4848 : // Add GEOLOCATION metadata.
4849 61 : if (!EQUAL(szGeolocXName, "") && !EQUAL(szGeolocYName, ""))
4850 : {
4851 48 : osGeolocXNameOut = szGeolocXName;
4852 48 : osGeolocYNameOut = szGeolocYName;
4853 :
4854 48 : char *pszGeolocXFullName = nullptr;
4855 48 : char *pszGeolocYFullName = nullptr;
4856 48 : if (NCDFResolveVarFullName(nGroupId, szGeolocXName,
4857 96 : &pszGeolocXFullName) == CE_None &&
4858 48 : NCDFResolveVarFullName(nGroupId, szGeolocYName,
4859 : &pszGeolocYFullName) == CE_None)
4860 : {
4861 48 : if (bSwitchedXY)
4862 : {
4863 2 : std::swap(pszGeolocXFullName, pszGeolocYFullName);
4864 2 : GDALPamDataset::SetMetadataItem("SWAP_XY", "YES",
4865 : "GEOLOCATION");
4866 : }
4867 :
4868 48 : bAddGeoloc = true;
4869 48 : CPLDebug("GDAL_netCDF",
4870 : "using variables %s and %s for GEOLOCATION",
4871 : pszGeolocXFullName, pszGeolocYFullName);
4872 :
4873 48 : GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
4874 : "GEOLOCATION");
4875 :
4876 96 : CPLString osTMP;
4877 48 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4878 48 : pszGeolocXFullName);
4879 :
4880 48 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP,
4881 : "GEOLOCATION");
4882 48 : GDALPamDataset::SetMetadataItem("X_BAND", "1",
4883 : "GEOLOCATION");
4884 48 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4885 48 : pszGeolocYFullName);
4886 :
4887 48 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP,
4888 : "GEOLOCATION");
4889 48 : GDALPamDataset::SetMetadataItem("Y_BAND", "1",
4890 : "GEOLOCATION");
4891 :
4892 48 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0",
4893 : "GEOLOCATION");
4894 48 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1",
4895 : "GEOLOCATION");
4896 :
4897 48 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0",
4898 : "GEOLOCATION");
4899 48 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1",
4900 : "GEOLOCATION");
4901 :
4902 48 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
4903 : "PIXEL_CENTER",
4904 : "GEOLOCATION");
4905 : }
4906 : else
4907 : {
4908 0 : CPLDebug("GDAL_netCDF",
4909 : "cannot resolve location of "
4910 : "lat/lon variables specified by the coordinates "
4911 : "attribute [%s]",
4912 : pszCoordinates);
4913 : }
4914 48 : CPLFree(pszGeolocXFullName);
4915 48 : CPLFree(pszGeolocYFullName);
4916 : }
4917 : else
4918 : {
4919 13 : CPLDebug("GDAL_netCDF",
4920 : "coordinates attribute [%s] is unsupported",
4921 : pszCoordinates);
4922 : }
4923 : }
4924 : else
4925 : {
4926 3 : CPLDebug("GDAL_netCDF",
4927 : "coordinates attribute [%s] with %d element(s) is "
4928 : "unsupported",
4929 : pszCoordinates, aosCoordinates.size());
4930 : }
4931 : }
4932 :
4933 : else
4934 : {
4935 287 : bAddGeoloc = ProcessNASAL2OceanGeoLocation(nGroupId, nVarId);
4936 :
4937 287 : if (!bAddGeoloc)
4938 286 : bAddGeoloc = ProcessNASAEMITGeoLocation(nGroupId, nVarId);
4939 : }
4940 :
4941 351 : CPLFree(pszCoordinates);
4942 :
4943 351 : return bAddGeoloc;
4944 : }
4945 :
4946 8 : CPLErr netCDFDataset::Set1DGeolocation(int nGroupId, int nVarId,
4947 : const char *szDimName)
4948 : {
4949 : // Get values.
4950 8 : char *pszVarValues = nullptr;
4951 8 : CPLErr eErr = NCDFGet1DVar(nGroupId, nVarId, &pszVarValues);
4952 8 : if (eErr != CE_None)
4953 0 : return eErr;
4954 :
4955 : // Write metadata.
4956 8 : char szTemp[NC_MAX_NAME + 1 + 32] = {};
4957 8 : snprintf(szTemp, sizeof(szTemp), "%s_VALUES", szDimName);
4958 8 : GDALPamDataset::SetMetadataItem(szTemp, pszVarValues, "GEOLOCATION2");
4959 :
4960 8 : CPLFree(pszVarValues);
4961 :
4962 8 : return CE_None;
4963 : }
4964 :
4965 0 : double *netCDFDataset::Get1DGeolocation(CPL_UNUSED const char *szDimName,
4966 : int &nVarLen)
4967 : {
4968 0 : nVarLen = 0;
4969 :
4970 : // Get Y_VALUES as tokens.
4971 : char **papszValues =
4972 0 : NCDFTokenizeArray(GetMetadataItem("Y_VALUES", "GEOLOCATION2"));
4973 0 : if (papszValues == nullptr)
4974 0 : return nullptr;
4975 :
4976 : // Initialize and fill array.
4977 0 : nVarLen = CSLCount(papszValues);
4978 : double *pdfVarValues =
4979 0 : static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
4980 :
4981 0 : for (int i = 0, j = 0; i < nVarLen; i++)
4982 : {
4983 0 : if (!bBottomUp)
4984 0 : j = nVarLen - 1 - i;
4985 : else
4986 0 : j = i; // Invert latitude values.
4987 0 : char *pszTemp = nullptr;
4988 0 : pdfVarValues[j] = CPLStrtod(papszValues[i], &pszTemp);
4989 : }
4990 0 : CSLDestroy(papszValues);
4991 :
4992 0 : return pdfVarValues;
4993 : }
4994 :
4995 : /************************************************************************/
4996 : /* SetSpatialRefNoUpdate() */
4997 : /************************************************************************/
4998 :
4999 257 : void netCDFDataset::SetSpatialRefNoUpdate(const OGRSpatialReference *poSRS)
5000 : {
5001 257 : m_oSRS.Clear();
5002 257 : if (poSRS)
5003 250 : m_oSRS = *poSRS;
5004 257 : m_bHasProjection = true;
5005 257 : }
5006 :
5007 : /************************************************************************/
5008 : /* SetSpatialRef() */
5009 : /************************************************************************/
5010 :
5011 76 : CPLErr netCDFDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
5012 : {
5013 152 : CPLMutexHolderD(&hNCMutex);
5014 :
5015 76 : if (GetAccess() != GA_Update || m_bHasProjection)
5016 : {
5017 0 : CPLError(CE_Failure, CPLE_AppDefined,
5018 : "netCDFDataset::_SetProjection() should only be called once "
5019 : "in update mode!");
5020 0 : return CE_Failure;
5021 : }
5022 :
5023 76 : if (m_bHasGeoTransform)
5024 : {
5025 32 : SetSpatialRefNoUpdate(poSRS);
5026 :
5027 : // For NC4/NC4C, writing both projection variables and data,
5028 : // followed by redefining nodata value, cancels the projection
5029 : // info from the Band variable, so for now only write the
5030 : // variable definitions, and write data at the end.
5031 : // See https://trac.osgeo.org/gdal/ticket/7245
5032 32 : return AddProjectionVars(true, nullptr, nullptr);
5033 : }
5034 :
5035 44 : SetSpatialRefNoUpdate(poSRS);
5036 :
5037 44 : return CE_None;
5038 : }
5039 :
5040 : /************************************************************************/
5041 : /* SetGeoTransformNoUpdate() */
5042 : /************************************************************************/
5043 :
5044 276 : void netCDFDataset::SetGeoTransformNoUpdate(const GDALGeoTransform >)
5045 : {
5046 276 : m_gt = gt;
5047 276 : m_bHasGeoTransform = true;
5048 276 : }
5049 :
5050 : /************************************************************************/
5051 : /* SetGeoTransform() */
5052 : /************************************************************************/
5053 :
5054 77 : CPLErr netCDFDataset::SetGeoTransform(const GDALGeoTransform >)
5055 : {
5056 154 : CPLMutexHolderD(&hNCMutex);
5057 :
5058 77 : if (GetAccess() != GA_Update || m_bHasGeoTransform)
5059 : {
5060 0 : CPLError(CE_Failure, CPLE_AppDefined,
5061 : "netCDFDataset::SetGeoTransform() should only be called once "
5062 : "in update mode!");
5063 0 : return CE_Failure;
5064 : }
5065 :
5066 77 : CPLDebug("GDAL_netCDF", "SetGeoTransform(%f,%f,%f,%f,%f,%f)", gt[0], gt[1],
5067 : gt[2], gt[3], gt[4], gt[5]);
5068 :
5069 77 : SetGeoTransformNoUpdate(gt);
5070 :
5071 77 : if (m_bHasProjection)
5072 : {
5073 :
5074 : // For NC4/NC4C, writing both projection variables and data,
5075 : // followed by redefining nodata value, cancels the projection
5076 : // info from the Band variable, so for now only write the
5077 : // variable definitions, and write data at the end.
5078 : // See https://trac.osgeo.org/gdal/ticket/7245
5079 3 : return AddProjectionVars(true, nullptr, nullptr);
5080 : }
5081 :
5082 74 : return CE_None;
5083 : }
5084 :
5085 : /************************************************************************/
5086 : /* NCDFWriteSRSVariable() */
5087 : /************************************************************************/
5088 :
5089 128 : int NCDFWriteSRSVariable(int cdfid, const OGRSpatialReference *poSRS,
5090 : char **ppszCFProjection, bool bWriteGDALTags,
5091 : const std::string &srsVarName)
5092 : {
5093 128 : char *pszCFProjection = nullptr;
5094 128 : char **papszKeyValues = nullptr;
5095 128 : poSRS->exportToCF1(&pszCFProjection, &papszKeyValues, nullptr, nullptr);
5096 :
5097 128 : if (bWriteGDALTags)
5098 : {
5099 127 : const char *pszWKT = CSLFetchNameValue(papszKeyValues, NCDF_CRS_WKT);
5100 127 : if (pszWKT)
5101 : {
5102 : // SPATIAL_REF is deprecated. Will be removed in GDAL 4.
5103 127 : papszKeyValues =
5104 127 : CSLSetNameValue(papszKeyValues, NCDF_SPATIAL_REF, pszWKT);
5105 : }
5106 : }
5107 :
5108 128 : const int nValues = CSLCount(papszKeyValues);
5109 :
5110 : int NCDFVarID;
5111 256 : std::string varNameRadix(pszCFProjection);
5112 128 : int nCounter = 2;
5113 : while (true)
5114 : {
5115 130 : NCDFVarID = -1;
5116 130 : nc_inq_varid(cdfid, pszCFProjection, &NCDFVarID);
5117 130 : if (NCDFVarID < 0)
5118 125 : break;
5119 :
5120 5 : int nbAttr = 0;
5121 5 : NCDF_ERR(nc_inq_varnatts(cdfid, NCDFVarID, &nbAttr));
5122 5 : bool bSame = nbAttr == nValues;
5123 41 : for (int i = 0; bSame && (i < nbAttr); i++)
5124 : {
5125 : char szAttrName[NC_MAX_NAME + 1];
5126 38 : szAttrName[0] = 0;
5127 38 : NCDF_ERR(nc_inq_attname(cdfid, NCDFVarID, i, szAttrName));
5128 :
5129 : const char *pszValue =
5130 38 : CSLFetchNameValue(papszKeyValues, szAttrName);
5131 38 : if (!pszValue)
5132 : {
5133 0 : bSame = false;
5134 2 : break;
5135 : }
5136 :
5137 38 : nc_type atttype = NC_NAT;
5138 38 : size_t attlen = 0;
5139 38 : NCDF_ERR(
5140 : nc_inq_att(cdfid, NCDFVarID, szAttrName, &atttype, &attlen));
5141 38 : if (atttype != NC_CHAR && atttype != NC_DOUBLE)
5142 : {
5143 0 : bSame = false;
5144 0 : break;
5145 : }
5146 38 : if (atttype == NC_CHAR)
5147 : {
5148 15 : if (CPLGetValueType(pszValue) != CPL_VALUE_STRING)
5149 : {
5150 0 : bSame = false;
5151 0 : break;
5152 : }
5153 15 : std::string val;
5154 15 : val.resize(attlen);
5155 15 : nc_get_att_text(cdfid, NCDFVarID, szAttrName, &val[0]);
5156 15 : if (val != pszValue)
5157 : {
5158 0 : bSame = false;
5159 0 : break;
5160 : }
5161 : }
5162 : else
5163 : {
5164 : const CPLStringList aosTokens(
5165 23 : CSLTokenizeString2(pszValue, ",", 0));
5166 23 : if (static_cast<size_t>(aosTokens.size()) != attlen)
5167 : {
5168 0 : bSame = false;
5169 0 : break;
5170 : }
5171 : double vals[2];
5172 23 : nc_get_att_double(cdfid, NCDFVarID, szAttrName, vals);
5173 44 : if (vals[0] != CPLAtof(aosTokens[0]) ||
5174 21 : (attlen == 2 && vals[1] != CPLAtof(aosTokens[1])))
5175 : {
5176 2 : bSame = false;
5177 2 : break;
5178 : }
5179 : }
5180 : }
5181 5 : if (bSame)
5182 : {
5183 3 : *ppszCFProjection = pszCFProjection;
5184 3 : CSLDestroy(papszKeyValues);
5185 3 : return NCDFVarID;
5186 : }
5187 2 : CPLFree(pszCFProjection);
5188 2 : pszCFProjection =
5189 2 : CPLStrdup(CPLSPrintf("%s_%d", varNameRadix.c_str(), nCounter));
5190 2 : nCounter++;
5191 2 : }
5192 :
5193 125 : *ppszCFProjection = pszCFProjection;
5194 :
5195 : const char *pszVarName;
5196 :
5197 125 : if (srsVarName != "")
5198 : {
5199 38 : pszVarName = srsVarName.c_str();
5200 : }
5201 : else
5202 : {
5203 87 : pszVarName = pszCFProjection;
5204 : }
5205 :
5206 125 : int status = nc_def_var(cdfid, pszVarName, NC_CHAR, 0, nullptr, &NCDFVarID);
5207 125 : NCDF_ERR(status);
5208 1199 : for (int i = 0; i < nValues; ++i)
5209 : {
5210 1074 : char *pszKey = nullptr;
5211 1074 : const char *pszValue = CPLParseNameValue(papszKeyValues[i], &pszKey);
5212 1074 : if (pszKey && pszValue)
5213 : {
5214 2148 : const CPLStringList aosTokens(CSLTokenizeString2(pszValue, ",", 0));
5215 1074 : double adfValues[2] = {0, 0};
5216 1074 : const int nDoubleCount = std::min(2, aosTokens.size());
5217 1074 : if (!(aosTokens.size() == 2 &&
5218 2147 : CPLGetValueType(aosTokens[0]) != CPL_VALUE_STRING) &&
5219 1073 : CPLGetValueType(pszValue) == CPL_VALUE_STRING)
5220 : {
5221 499 : status = nc_put_att_text(cdfid, NCDFVarID, pszKey,
5222 : strlen(pszValue), pszValue);
5223 : }
5224 : else
5225 : {
5226 1151 : for (int j = 0; j < nDoubleCount; ++j)
5227 576 : adfValues[j] = CPLAtof(aosTokens[j]);
5228 575 : status = nc_put_att_double(cdfid, NCDFVarID, pszKey, NC_DOUBLE,
5229 : nDoubleCount, adfValues);
5230 : }
5231 1074 : NCDF_ERR(status);
5232 : }
5233 1074 : CPLFree(pszKey);
5234 : }
5235 :
5236 125 : CSLDestroy(papszKeyValues);
5237 125 : return NCDFVarID;
5238 : }
5239 :
5240 : /************************************************************************/
5241 : /* NCDFWriteLonLatVarsAttributes() */
5242 : /************************************************************************/
5243 :
5244 101 : void NCDFWriteLonLatVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarLonID,
5245 : int nVarLatID)
5246 : {
5247 :
5248 : try
5249 : {
5250 101 : vcdf.nc_put_vatt_text(nVarLatID, CF_STD_NAME, CF_LATITUDE_STD_NAME);
5251 101 : vcdf.nc_put_vatt_text(nVarLatID, CF_LNG_NAME, CF_LATITUDE_LNG_NAME);
5252 101 : vcdf.nc_put_vatt_text(nVarLatID, CF_UNITS, CF_DEGREES_NORTH);
5253 101 : vcdf.nc_put_vatt_text(nVarLonID, CF_STD_NAME, CF_LONGITUDE_STD_NAME);
5254 101 : vcdf.nc_put_vatt_text(nVarLonID, CF_LNG_NAME, CF_LONGITUDE_LNG_NAME);
5255 101 : vcdf.nc_put_vatt_text(nVarLonID, CF_UNITS, CF_DEGREES_EAST);
5256 : }
5257 0 : catch (nccfdriver::SG_Exception &e)
5258 : {
5259 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5260 : }
5261 101 : }
5262 :
5263 : /************************************************************************/
5264 : /* NCDFWriteRLonRLatVarsAttributes() */
5265 : /************************************************************************/
5266 :
5267 0 : void NCDFWriteRLonRLatVarsAttributes(nccfdriver::netCDFVID &vcdf,
5268 : int nVarRLonID, int nVarRLatID)
5269 : {
5270 : try
5271 : {
5272 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_STD_NAME, "grid_latitude");
5273 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_LNG_NAME,
5274 : "latitude in rotated pole grid");
5275 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_UNITS, "degrees");
5276 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_AXIS, "Y");
5277 :
5278 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_STD_NAME, "grid_longitude");
5279 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_LNG_NAME,
5280 : "longitude in rotated pole grid");
5281 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_UNITS, "degrees");
5282 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_AXIS, "X");
5283 : }
5284 0 : catch (nccfdriver::SG_Exception &e)
5285 : {
5286 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5287 : }
5288 0 : }
5289 :
5290 : /************************************************************************/
5291 : /* NCDFGetProjectedCFUnit() */
5292 : /************************************************************************/
5293 :
5294 40 : std::string NCDFGetProjectedCFUnit(const OGRSpatialReference *poSRS)
5295 : {
5296 40 : char *pszUnitsToWrite = nullptr;
5297 40 : poSRS->exportToCF1(nullptr, nullptr, &pszUnitsToWrite, nullptr);
5298 40 : std::string osRet = pszUnitsToWrite ? pszUnitsToWrite : std::string();
5299 40 : CPLFree(pszUnitsToWrite);
5300 80 : return osRet;
5301 : }
5302 :
5303 : /************************************************************************/
5304 : /* NCDFWriteXYVarsAttributes() */
5305 : /************************************************************************/
5306 :
5307 29 : void NCDFWriteXYVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarXID,
5308 : int nVarYID, const OGRSpatialReference *poSRS)
5309 : {
5310 58 : const std::string osUnitsToWrite = NCDFGetProjectedCFUnit(poSRS);
5311 :
5312 : try
5313 : {
5314 29 : vcdf.nc_put_vatt_text(nVarXID, CF_STD_NAME, CF_PROJ_X_COORD);
5315 29 : vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME, CF_PROJ_X_COORD_LONG_NAME);
5316 29 : if (!osUnitsToWrite.empty())
5317 29 : vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, osUnitsToWrite.c_str());
5318 29 : vcdf.nc_put_vatt_text(nVarYID, CF_STD_NAME, CF_PROJ_Y_COORD);
5319 29 : vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME, CF_PROJ_Y_COORD_LONG_NAME);
5320 29 : if (!osUnitsToWrite.empty())
5321 29 : vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, osUnitsToWrite.c_str());
5322 : }
5323 0 : catch (nccfdriver::SG_Exception &e)
5324 : {
5325 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5326 : }
5327 29 : }
5328 :
5329 : /************************************************************************/
5330 : /* AddProjectionVars() */
5331 : /************************************************************************/
5332 :
5333 164 : CPLErr netCDFDataset::AddProjectionVars(bool bDefsOnly,
5334 : GDALProgressFunc pfnProgress,
5335 : void *pProgressData)
5336 : {
5337 164 : if (nCFVersion >= 1.8)
5338 0 : return CE_None; // do nothing
5339 :
5340 164 : bool bWriteGridMapping = false;
5341 164 : bool bWriteLonLat = false;
5342 164 : bool bHasGeoloc = false;
5343 164 : bool bWriteGDALTags = false;
5344 164 : bool bWriteGeoTransform = false;
5345 :
5346 : // For GEOLOCATION information.
5347 164 : GDALDatasetH hDS_X = nullptr;
5348 164 : GDALRasterBandH hBand_X = nullptr;
5349 164 : GDALDatasetH hDS_Y = nullptr;
5350 164 : GDALRasterBandH hBand_Y = nullptr;
5351 :
5352 328 : OGRSpatialReference oSRS(m_oSRS);
5353 164 : if (!m_oSRS.IsEmpty())
5354 : {
5355 138 : if (oSRS.IsProjected())
5356 50 : bIsProjected = true;
5357 88 : else if (oSRS.IsGeographic())
5358 88 : bIsGeographic = true;
5359 : }
5360 :
5361 164 : if (bDefsOnly)
5362 : {
5363 82 : char *pszProjection = nullptr;
5364 82 : m_oSRS.exportToWkt(&pszProjection);
5365 82 : CPLDebug("GDAL_netCDF",
5366 : "SetProjection, WKT now = [%s]\nprojected: %d geographic: %d",
5367 82 : pszProjection ? pszProjection : "(null)",
5368 82 : static_cast<int>(bIsProjected),
5369 82 : static_cast<int>(bIsGeographic));
5370 82 : CPLFree(pszProjection);
5371 :
5372 82 : if (!m_bHasGeoTransform)
5373 5 : CPLDebug("GDAL_netCDF",
5374 : "netCDFDataset::AddProjectionVars() called, "
5375 : "but GeoTransform has not yet been defined!");
5376 :
5377 82 : if (!m_bHasProjection)
5378 6 : CPLDebug("GDAL_netCDF",
5379 : "netCDFDataset::AddProjectionVars() called, "
5380 : "but Projection has not yet been defined!");
5381 : }
5382 :
5383 : // Check GEOLOCATION information.
5384 164 : char **papszGeolocationInfo = netCDFDataset::GetMetadata("GEOLOCATION");
5385 164 : if (papszGeolocationInfo != nullptr)
5386 : {
5387 : // Look for geolocation datasets.
5388 : const char *pszDSName =
5389 10 : CSLFetchNameValue(papszGeolocationInfo, "X_DATASET");
5390 10 : if (pszDSName != nullptr)
5391 10 : hDS_X = GDALOpenShared(pszDSName, GA_ReadOnly);
5392 10 : pszDSName = CSLFetchNameValue(papszGeolocationInfo, "Y_DATASET");
5393 10 : if (pszDSName != nullptr)
5394 10 : hDS_Y = GDALOpenShared(pszDSName, GA_ReadOnly);
5395 :
5396 10 : if (hDS_X != nullptr && hDS_Y != nullptr)
5397 : {
5398 10 : int nBand = std::max(1, atoi(CSLFetchNameValueDef(
5399 10 : papszGeolocationInfo, "X_BAND", "0")));
5400 10 : hBand_X = GDALGetRasterBand(hDS_X, nBand);
5401 10 : nBand = std::max(1, atoi(CSLFetchNameValueDef(papszGeolocationInfo,
5402 10 : "Y_BAND", "0")));
5403 10 : hBand_Y = GDALGetRasterBand(hDS_Y, nBand);
5404 :
5405 : // If geoloc bands are found, do basic validation based on their
5406 : // dimensions.
5407 10 : if (hBand_X != nullptr && hBand_Y != nullptr)
5408 : {
5409 10 : int nXSize_XBand = GDALGetRasterXSize(hDS_X);
5410 10 : int nYSize_XBand = GDALGetRasterYSize(hDS_X);
5411 10 : int nXSize_YBand = GDALGetRasterXSize(hDS_Y);
5412 10 : int nYSize_YBand = GDALGetRasterYSize(hDS_Y);
5413 :
5414 : // TODO 1D geolocation arrays not implemented.
5415 10 : if (nYSize_XBand == 1 && nYSize_YBand == 1)
5416 : {
5417 0 : bHasGeoloc = false;
5418 0 : CPLDebug("GDAL_netCDF",
5419 : "1D GEOLOCATION arrays not supported yet");
5420 : }
5421 : // 2D bands must have same sizes as the raster bands.
5422 10 : else if (nXSize_XBand != nRasterXSize ||
5423 10 : nYSize_XBand != nRasterYSize ||
5424 10 : nXSize_YBand != nRasterXSize ||
5425 10 : nYSize_YBand != nRasterYSize)
5426 : {
5427 0 : bHasGeoloc = false;
5428 0 : CPLDebug("GDAL_netCDF",
5429 : "GEOLOCATION array sizes (%dx%d %dx%d) differ "
5430 : "from raster (%dx%d), not supported",
5431 : nXSize_XBand, nYSize_XBand, nXSize_YBand,
5432 : nYSize_YBand, nRasterXSize, nRasterYSize);
5433 : }
5434 : else
5435 : {
5436 10 : bHasGeoloc = true;
5437 10 : CPLDebug("GDAL_netCDF",
5438 : "dataset has GEOLOCATION information, will try to "
5439 : "write it");
5440 : }
5441 : }
5442 : }
5443 : }
5444 :
5445 : // Process projection options.
5446 164 : if (bIsProjected)
5447 : {
5448 : bool bIsCfProjection =
5449 50 : oSRS.exportToCF1(nullptr, nullptr, nullptr, nullptr) == OGRERR_NONE;
5450 50 : bWriteGridMapping = true;
5451 50 : bWriteGDALTags = CPL_TO_BOOL(
5452 50 : CSLFetchBoolean(papszCreationOptions, "WRITE_GDAL_TAGS", TRUE));
5453 : // Force WRITE_GDAL_TAGS if is not a CF projection.
5454 50 : if (!bWriteGDALTags && !bIsCfProjection)
5455 0 : bWriteGDALTags = true;
5456 50 : if (bWriteGDALTags)
5457 50 : bWriteGeoTransform = true;
5458 :
5459 : // Write lon/lat: default is NO, except if has geolocation.
5460 : // With IF_NEEDED: write if has geoloc or is not CF projection.
5461 : const char *pszValue =
5462 50 : CSLFetchNameValue(papszCreationOptions, "WRITE_LONLAT");
5463 50 : if (pszValue)
5464 : {
5465 2 : if (EQUAL(pszValue, "IF_NEEDED"))
5466 : {
5467 0 : bWriteLonLat = bHasGeoloc || !bIsCfProjection;
5468 : }
5469 : else
5470 : {
5471 2 : bWriteLonLat = CPLTestBool(pszValue);
5472 : }
5473 : }
5474 : else
5475 : {
5476 48 : bWriteLonLat = bHasGeoloc;
5477 : }
5478 :
5479 : // Save value of pszCFCoordinates for later.
5480 50 : if (bWriteLonLat)
5481 : {
5482 4 : pszCFCoordinates = NCDF_LONLAT;
5483 : }
5484 : }
5485 : else
5486 : {
5487 : // Files without a Datum will not have a grid_mapping variable and
5488 : // geographic information.
5489 114 : bWriteGridMapping = bIsGeographic;
5490 :
5491 114 : if (bHasGeoloc)
5492 : {
5493 8 : bWriteLonLat = true;
5494 : }
5495 : else
5496 : {
5497 106 : bWriteGDALTags = CPL_TO_BOOL(CSLFetchBoolean(
5498 106 : papszCreationOptions, "WRITE_GDAL_TAGS", bWriteGridMapping));
5499 106 : if (bWriteGDALTags)
5500 88 : bWriteGeoTransform = true;
5501 :
5502 106 : const char *pszValue = CSLFetchNameValueDef(papszCreationOptions,
5503 : "WRITE_LONLAT", "YES");
5504 106 : if (EQUAL(pszValue, "IF_NEEDED"))
5505 0 : bWriteLonLat = true;
5506 : else
5507 106 : bWriteLonLat = CPLTestBool(pszValue);
5508 : // Don't write lon/lat if no source geotransform.
5509 106 : if (!m_bHasGeoTransform)
5510 0 : bWriteLonLat = false;
5511 : // If we don't write lon/lat, set dimnames to X/Y and write gdal
5512 : // tags.
5513 106 : if (!bWriteLonLat)
5514 : {
5515 0 : CPLError(CE_Warning, CPLE_AppDefined,
5516 : "creating geographic file without lon/lat values!");
5517 0 : if (m_bHasGeoTransform)
5518 : {
5519 0 : bWriteGDALTags = true; // Not desirable if no geotransform.
5520 0 : bWriteGeoTransform = true;
5521 : }
5522 : }
5523 : }
5524 : }
5525 :
5526 : // Make sure we write grid_mapping if we need to write GDAL tags.
5527 164 : if (bWriteGDALTags)
5528 138 : bWriteGridMapping = true;
5529 :
5530 : // bottom-up value: new driver is bottom-up by default.
5531 : // Override with WRITE_BOTTOMUP.
5532 164 : bBottomUp = CPL_TO_BOOL(
5533 164 : CSLFetchBoolean(papszCreationOptions, "WRITE_BOTTOMUP", TRUE));
5534 :
5535 164 : if (bDefsOnly)
5536 : {
5537 82 : CPLDebug(
5538 : "GDAL_netCDF",
5539 : "bIsProjected=%d bIsGeographic=%d bWriteGridMapping=%d "
5540 : "bWriteGDALTags=%d bWriteLonLat=%d bBottomUp=%d bHasGeoloc=%d",
5541 82 : static_cast<int>(bIsProjected), static_cast<int>(bIsGeographic),
5542 : static_cast<int>(bWriteGridMapping),
5543 : static_cast<int>(bWriteGDALTags), static_cast<int>(bWriteLonLat),
5544 82 : static_cast<int>(bBottomUp), static_cast<int>(bHasGeoloc));
5545 : }
5546 :
5547 : // Exit if nothing to do.
5548 164 : if (!bIsProjected && !bWriteLonLat)
5549 0 : return CE_None;
5550 :
5551 : // Define dimension names.
5552 :
5553 164 : constexpr const char *ROTATED_POLE_VAR_NAME = "rotated_pole";
5554 :
5555 164 : if (bDefsOnly)
5556 : {
5557 82 : int nVarLonID = -1;
5558 82 : int nVarLatID = -1;
5559 82 : int nVarXID = -1;
5560 82 : int nVarYID = -1;
5561 :
5562 82 : m_bAddedProjectionVarsDefs = true;
5563 :
5564 : // Make sure we are in define mode.
5565 82 : SetDefineMode(true);
5566 :
5567 : // Write projection attributes.
5568 82 : if (bWriteGridMapping)
5569 : {
5570 69 : const int NCDFVarID = NCDFWriteSRSVariable(
5571 : cdfid, &oSRS, &pszCFProjection, bWriteGDALTags);
5572 69 : if (NCDFVarID < 0)
5573 0 : return CE_Failure;
5574 :
5575 : // Optional GDAL custom projection tags.
5576 69 : if (bWriteGDALTags)
5577 : {
5578 138 : CPLString osGeoTransform;
5579 483 : for (int i = 0; i < 6; i++)
5580 : {
5581 414 : osGeoTransform += CPLSPrintf("%.17g ", m_gt[i]);
5582 : }
5583 69 : CPLDebug("GDAL_netCDF", "szGeoTransform = %s",
5584 : osGeoTransform.c_str());
5585 :
5586 : // if( strlen(pszProj4Defn) > 0 ) {
5587 : // nc_put_att_text(cdfid, NCDFVarID, "proj4",
5588 : // strlen(pszProj4Defn), pszProj4Defn);
5589 : // }
5590 :
5591 : // For now, write the geotransform for back-compat or else
5592 : // the old (1.8.1) driver overrides the CF geotransform with
5593 : // empty values from dfNN, dfSN, dfEE, dfWE;
5594 :
5595 : // TODO: fix this in 1.8 branch, and then remove this here.
5596 69 : if (bWriteGeoTransform && m_bHasGeoTransform)
5597 : {
5598 : {
5599 68 : const int status = nc_put_att_text(
5600 : cdfid, NCDFVarID, NCDF_GEOTRANSFORM,
5601 : osGeoTransform.size(), osGeoTransform.c_str());
5602 68 : NCDF_ERR(status);
5603 : }
5604 : }
5605 : }
5606 :
5607 : // Write projection variable to band variable.
5608 : // Need to call later if there are no bands.
5609 69 : AddGridMappingRef();
5610 : } // end if( bWriteGridMapping )
5611 :
5612 : // Write CF Projection vars.
5613 :
5614 82 : const bool bIsRotatedPole =
5615 151 : pszCFProjection != nullptr &&
5616 69 : EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
5617 82 : if (bIsRotatedPole)
5618 : {
5619 : // Rename dims to rlat/rlon.
5620 : papszDimName
5621 0 : .Clear(); // If we add other dims one day, this has to change
5622 0 : papszDimName.AddString(NCDF_DIMNAME_RLAT);
5623 0 : papszDimName.AddString(NCDF_DIMNAME_RLON);
5624 :
5625 0 : int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_RLAT);
5626 0 : NCDF_ERR(status);
5627 0 : status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_RLON);
5628 0 : NCDF_ERR(status);
5629 : }
5630 : // Rename dimensions if lon/lat.
5631 82 : else if (!bIsProjected && !bHasGeoloc)
5632 : {
5633 : // Rename dims to lat/lon.
5634 : papszDimName
5635 53 : .Clear(); // If we add other dims one day, this has to change
5636 53 : papszDimName.AddString(NCDF_DIMNAME_LAT);
5637 53 : papszDimName.AddString(NCDF_DIMNAME_LON);
5638 :
5639 53 : int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_LAT);
5640 53 : NCDF_ERR(status);
5641 53 : status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_LON);
5642 53 : NCDF_ERR(status);
5643 : }
5644 :
5645 : // Write X/Y attributes.
5646 : else /* if( bIsProjected || bHasGeoloc ) */
5647 : {
5648 : // X
5649 : int anXDims[1];
5650 29 : anXDims[0] = nXDimID;
5651 29 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
5652 : CF_PROJ_X_VAR_NAME, NC_DOUBLE);
5653 29 : int status = nc_def_var(cdfid, CF_PROJ_X_VAR_NAME, NC_DOUBLE, 1,
5654 : anXDims, &nVarXID);
5655 29 : NCDF_ERR(status);
5656 :
5657 : // Y
5658 : int anYDims[1];
5659 29 : anYDims[0] = nYDimID;
5660 29 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
5661 : CF_PROJ_Y_VAR_NAME, NC_DOUBLE);
5662 29 : status = nc_def_var(cdfid, CF_PROJ_Y_VAR_NAME, NC_DOUBLE, 1,
5663 : anYDims, &nVarYID);
5664 29 : NCDF_ERR(status);
5665 :
5666 29 : if (bIsProjected)
5667 : {
5668 25 : NCDFWriteXYVarsAttributes(this->vcdf, nVarXID, nVarYID, &oSRS);
5669 : }
5670 : else
5671 : {
5672 4 : CPLAssert(bHasGeoloc);
5673 : try
5674 : {
5675 4 : vcdf.nc_put_vatt_text(nVarXID, CF_AXIS, CF_SG_X_AXIS);
5676 4 : vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME,
5677 : "x-coordinate in Cartesian system");
5678 4 : vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, "m");
5679 4 : vcdf.nc_put_vatt_text(nVarYID, CF_AXIS, CF_SG_Y_AXIS);
5680 4 : vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME,
5681 : "y-coordinate in Cartesian system");
5682 4 : vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, "m");
5683 :
5684 4 : pszCFCoordinates = NCDF_LONLAT;
5685 : }
5686 0 : catch (nccfdriver::SG_Exception &e)
5687 : {
5688 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5689 0 : return CE_Failure;
5690 : }
5691 : }
5692 : }
5693 :
5694 : // Write lat/lon attributes if needed.
5695 82 : if (bWriteLonLat)
5696 : {
5697 59 : int *panLatDims = nullptr;
5698 59 : int *panLonDims = nullptr;
5699 59 : int nLatDims = -1;
5700 59 : int nLonDims = -1;
5701 :
5702 : // Get information.
5703 59 : if (bHasGeoloc)
5704 : {
5705 : // Geoloc
5706 5 : nLatDims = 2;
5707 : panLatDims =
5708 5 : static_cast<int *>(CPLCalloc(nLatDims, sizeof(int)));
5709 5 : panLatDims[0] = nYDimID;
5710 5 : panLatDims[1] = nXDimID;
5711 5 : nLonDims = 2;
5712 : panLonDims =
5713 5 : static_cast<int *>(CPLCalloc(nLonDims, sizeof(int)));
5714 5 : panLonDims[0] = nYDimID;
5715 5 : panLonDims[1] = nXDimID;
5716 : }
5717 54 : else if (bIsProjected)
5718 : {
5719 : // Projected
5720 1 : nLatDims = 2;
5721 : panLatDims =
5722 1 : static_cast<int *>(CPLCalloc(nLatDims, sizeof(int)));
5723 1 : panLatDims[0] = nYDimID;
5724 1 : panLatDims[1] = nXDimID;
5725 1 : nLonDims = 2;
5726 : panLonDims =
5727 1 : static_cast<int *>(CPLCalloc(nLonDims, sizeof(int)));
5728 1 : panLonDims[0] = nYDimID;
5729 1 : panLonDims[1] = nXDimID;
5730 : }
5731 : else
5732 : {
5733 : // Geographic
5734 53 : nLatDims = 1;
5735 : panLatDims =
5736 53 : static_cast<int *>(CPLCalloc(nLatDims, sizeof(int)));
5737 53 : panLatDims[0] = nYDimID;
5738 53 : nLonDims = 1;
5739 : panLonDims =
5740 53 : static_cast<int *>(CPLCalloc(nLonDims, sizeof(int)));
5741 53 : panLonDims[0] = nXDimID;
5742 : }
5743 :
5744 59 : nc_type eLonLatType = NC_NAT;
5745 59 : if (bIsProjected)
5746 : {
5747 2 : eLonLatType = NC_FLOAT;
5748 4 : const char *pszValue = CSLFetchNameValueDef(
5749 2 : papszCreationOptions, "TYPE_LONLAT", "FLOAT");
5750 2 : if (EQUAL(pszValue, "DOUBLE"))
5751 0 : eLonLatType = NC_DOUBLE;
5752 : }
5753 : else
5754 : {
5755 57 : eLonLatType = NC_DOUBLE;
5756 114 : const char *pszValue = CSLFetchNameValueDef(
5757 57 : papszCreationOptions, "TYPE_LONLAT", "DOUBLE");
5758 57 : if (EQUAL(pszValue, "FLOAT"))
5759 0 : eLonLatType = NC_FLOAT;
5760 : }
5761 :
5762 : // Def vars and attributes.
5763 : {
5764 59 : const char *pszVarName =
5765 59 : bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME;
5766 59 : int status = nc_def_var(cdfid, pszVarName, eLonLatType,
5767 : nLatDims, panLatDims, &nVarLatID);
5768 59 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
5769 : cdfid, pszVarName, eLonLatType, nLatDims, nVarLatID);
5770 59 : NCDF_ERR(status);
5771 59 : DefVarDeflate(nVarLatID, false); // Don't set chunking.
5772 : }
5773 :
5774 : {
5775 59 : const char *pszVarName =
5776 59 : bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME;
5777 59 : int status = nc_def_var(cdfid, pszVarName, eLonLatType,
5778 : nLonDims, panLonDims, &nVarLonID);
5779 59 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
5780 : cdfid, pszVarName, eLonLatType, nLatDims, nVarLonID);
5781 59 : NCDF_ERR(status);
5782 59 : DefVarDeflate(nVarLonID, false); // Don't set chunking.
5783 : }
5784 :
5785 59 : if (bIsRotatedPole)
5786 0 : NCDFWriteRLonRLatVarsAttributes(this->vcdf, nVarLonID,
5787 : nVarLatID);
5788 : else
5789 59 : NCDFWriteLonLatVarsAttributes(this->vcdf, nVarLonID, nVarLatID);
5790 :
5791 59 : CPLFree(panLatDims);
5792 59 : CPLFree(panLonDims);
5793 : }
5794 : }
5795 :
5796 164 : if (!bDefsOnly)
5797 : {
5798 82 : m_bAddedProjectionVarsData = true;
5799 :
5800 82 : int nVarXID = -1;
5801 82 : int nVarYID = -1;
5802 :
5803 82 : nc_inq_varid(cdfid, CF_PROJ_X_VAR_NAME, &nVarXID);
5804 82 : nc_inq_varid(cdfid, CF_PROJ_Y_VAR_NAME, &nVarYID);
5805 :
5806 82 : int nVarLonID = -1;
5807 82 : int nVarLatID = -1;
5808 :
5809 82 : const bool bIsRotatedPole =
5810 151 : pszCFProjection != nullptr &&
5811 69 : EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
5812 82 : nc_inq_varid(cdfid,
5813 : bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME,
5814 : &nVarLonID);
5815 82 : nc_inq_varid(cdfid,
5816 : bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME,
5817 : &nVarLatID);
5818 :
5819 : // Get projection values.
5820 :
5821 82 : double *padLonVal = nullptr;
5822 82 : double *padLatVal = nullptr;
5823 :
5824 82 : if (bIsProjected)
5825 : {
5826 25 : OGRSpatialReference *poLatLonSRS = nullptr;
5827 25 : OGRCoordinateTransformation *poTransform = nullptr;
5828 :
5829 : size_t startX[1];
5830 : size_t countX[1];
5831 : size_t startY[1];
5832 : size_t countY[1];
5833 :
5834 25 : CPLDebug("GDAL_netCDF", "Getting (X,Y) values");
5835 :
5836 : double *padXVal =
5837 25 : static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
5838 : double *padYVal =
5839 25 : static_cast<double *>(CPLMalloc(nRasterYSize * sizeof(double)));
5840 :
5841 : // Get Y values.
5842 25 : const double dfY0 = (!bBottomUp) ? m_gt[3] :
5843 : // Invert latitude values.
5844 25 : m_gt[3] + (m_gt[5] * nRasterYSize);
5845 25 : const double dfDY = m_gt[5];
5846 :
5847 1456 : for (int j = 0; j < nRasterYSize; j++)
5848 : {
5849 : // The data point is centered inside the pixel.
5850 1431 : if (!bBottomUp)
5851 0 : padYVal[j] = dfY0 + (j + 0.5) * dfDY;
5852 : else // Invert latitude values.
5853 1431 : padYVal[j] = dfY0 - (j + 0.5) * dfDY;
5854 : }
5855 25 : startX[0] = 0;
5856 25 : countX[0] = nRasterXSize;
5857 :
5858 : // Get X values.
5859 25 : const double dfX0 = m_gt[0];
5860 25 : const double dfDX = m_gt[1];
5861 :
5862 1477 : for (int i = 0; i < nRasterXSize; i++)
5863 : {
5864 : // The data point is centered inside the pixel.
5865 1452 : padXVal[i] = dfX0 + (i + 0.5) * dfDX;
5866 : }
5867 25 : startY[0] = 0;
5868 25 : countY[0] = nRasterYSize;
5869 :
5870 : // Write X/Y values.
5871 :
5872 : // Make sure we are in data mode.
5873 25 : SetDefineMode(false);
5874 :
5875 25 : CPLDebug("GDAL_netCDF", "Writing X values");
5876 : int status =
5877 25 : nc_put_vara_double(cdfid, nVarXID, startX, countX, padXVal);
5878 25 : NCDF_ERR(status);
5879 :
5880 25 : CPLDebug("GDAL_netCDF", "Writing Y values");
5881 : status =
5882 25 : nc_put_vara_double(cdfid, nVarYID, startY, countY, padYVal);
5883 25 : NCDF_ERR(status);
5884 :
5885 25 : if (pfnProgress)
5886 21 : pfnProgress(0.20, nullptr, pProgressData);
5887 :
5888 : // Write lon/lat arrays (CF coordinates) if requested.
5889 :
5890 : // Get OGR transform if GEOLOCATION is not available.
5891 25 : if (bWriteLonLat && !bHasGeoloc)
5892 : {
5893 1 : poLatLonSRS = m_oSRS.CloneGeogCS();
5894 1 : if (poLatLonSRS != nullptr)
5895 : {
5896 1 : poLatLonSRS->SetAxisMappingStrategy(
5897 : OAMS_TRADITIONAL_GIS_ORDER);
5898 : poTransform =
5899 1 : OGRCreateCoordinateTransformation(&m_oSRS, poLatLonSRS);
5900 : }
5901 : // If no OGR transform, then don't write CF lon/lat.
5902 1 : if (poTransform == nullptr)
5903 : {
5904 0 : CPLError(CE_Failure, CPLE_AppDefined,
5905 : "Unable to get Coordinate Transform");
5906 0 : bWriteLonLat = false;
5907 : }
5908 : }
5909 :
5910 25 : if (bWriteLonLat)
5911 : {
5912 2 : if (!bHasGeoloc)
5913 1 : CPLDebug("GDAL_netCDF", "Transforming (X,Y)->(lon,lat)");
5914 : else
5915 1 : CPLDebug("GDAL_netCDF",
5916 : "Writing (lon,lat) from GEOLOCATION arrays");
5917 :
5918 2 : bool bOK = true;
5919 2 : double dfProgress = 0.2;
5920 :
5921 2 : size_t start[] = {0, 0};
5922 2 : size_t count[] = {1, (size_t)nRasterXSize};
5923 : padLatVal = static_cast<double *>(
5924 2 : CPLMalloc(nRasterXSize * sizeof(double)));
5925 : padLonVal = static_cast<double *>(
5926 2 : CPLMalloc(nRasterXSize * sizeof(double)));
5927 :
5928 61 : for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR;
5929 : j++)
5930 : {
5931 59 : start[0] = j;
5932 :
5933 : // Get values from geotransform.
5934 59 : if (!bHasGeoloc)
5935 : {
5936 : // Fill values to transform.
5937 420 : for (int i = 0; i < nRasterXSize; i++)
5938 : {
5939 400 : padLatVal[i] = padYVal[j];
5940 400 : padLonVal[i] = padXVal[i];
5941 : }
5942 :
5943 : // Do the transform.
5944 20 : bOK = CPL_TO_BOOL(poTransform->Transform(
5945 20 : nRasterXSize, padLonVal, padLatVal, nullptr));
5946 20 : if (!bOK)
5947 : {
5948 0 : CPLError(CE_Failure, CPLE_AppDefined,
5949 : "Unable to Transform (X,Y) to (lon,lat).");
5950 : }
5951 : }
5952 : // Get values from geoloc arrays.
5953 : else
5954 : {
5955 39 : CPLErr eErr = GDALRasterIO(
5956 : hBand_Y, GF_Read, 0, j, nRasterXSize, 1, padLatVal,
5957 : nRasterXSize, 1, GDT_Float64, 0, 0);
5958 39 : if (eErr == CE_None)
5959 : {
5960 39 : eErr = GDALRasterIO(
5961 : hBand_X, GF_Read, 0, j, nRasterXSize, 1,
5962 : padLonVal, nRasterXSize, 1, GDT_Float64, 0, 0);
5963 : }
5964 :
5965 39 : if (eErr == CE_None)
5966 : {
5967 39 : bOK = true;
5968 : }
5969 : else
5970 : {
5971 0 : bOK = false;
5972 0 : CPLError(CE_Failure, CPLE_AppDefined,
5973 : "Unable to get scanline %d", j);
5974 : }
5975 : }
5976 :
5977 : // Write data.
5978 59 : if (bOK)
5979 : {
5980 59 : status = nc_put_vara_double(cdfid, nVarLatID, start,
5981 : count, padLatVal);
5982 59 : NCDF_ERR(status);
5983 59 : status = nc_put_vara_double(cdfid, nVarLonID, start,
5984 : count, padLonVal);
5985 59 : NCDF_ERR(status);
5986 : }
5987 :
5988 59 : if (pfnProgress && (nRasterYSize / 10) > 0 &&
5989 59 : (j % (nRasterYSize / 10) == 0))
5990 : {
5991 23 : dfProgress += 0.08;
5992 23 : pfnProgress(dfProgress, nullptr, pProgressData);
5993 : }
5994 : }
5995 : }
5996 :
5997 25 : if (poLatLonSRS != nullptr)
5998 1 : delete poLatLonSRS;
5999 25 : if (poTransform != nullptr)
6000 1 : delete poTransform;
6001 :
6002 25 : CPLFree(padXVal);
6003 25 : CPLFree(padYVal);
6004 : } // Projected
6005 :
6006 : // If not projected/geographic and has geoloc
6007 57 : else if (!bIsGeographic && bHasGeoloc)
6008 : {
6009 : // Use
6010 : // https://cfconventions.org/Data/cf-conventions/cf-conventions-1.9/cf-conventions.html#_two_dimensional_latitude_longitude_coordinate_variables
6011 :
6012 4 : bool bOK = true;
6013 4 : double dfProgress = 0.2;
6014 :
6015 : // Make sure we are in data mode.
6016 4 : SetDefineMode(false);
6017 :
6018 : size_t startX[1];
6019 : size_t countX[1];
6020 : size_t startY[1];
6021 : size_t countY[1];
6022 4 : startX[0] = 0;
6023 4 : countX[0] = nRasterXSize;
6024 :
6025 4 : startY[0] = 0;
6026 4 : countY[0] = nRasterYSize;
6027 :
6028 8 : std::vector<double> adfXVal(nRasterXSize);
6029 16 : for (int i = 0; i < nRasterXSize; i++)
6030 12 : adfXVal[i] = i;
6031 :
6032 8 : std::vector<double> adfYVal(nRasterYSize);
6033 12 : for (int i = 0; i < nRasterYSize; i++)
6034 8 : adfYVal[i] = bBottomUp ? nRasterYSize - 1 - i : i;
6035 :
6036 4 : CPLDebug("GDAL_netCDF", "Writing X values");
6037 4 : int status = nc_put_vara_double(cdfid, nVarXID, startX, countX,
6038 4 : adfXVal.data());
6039 4 : NCDF_ERR(status);
6040 :
6041 4 : CPLDebug("GDAL_netCDF", "Writing Y values");
6042 4 : status = nc_put_vara_double(cdfid, nVarYID, startY, countY,
6043 4 : adfYVal.data());
6044 4 : NCDF_ERR(status);
6045 :
6046 4 : if (pfnProgress)
6047 0 : pfnProgress(0.20, nullptr, pProgressData);
6048 :
6049 4 : size_t start[] = {0, 0};
6050 4 : size_t count[] = {1, (size_t)nRasterXSize};
6051 : padLatVal =
6052 4 : static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
6053 : padLonVal =
6054 4 : static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
6055 :
6056 12 : for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR; j++)
6057 : {
6058 8 : start[0] = j;
6059 :
6060 8 : CPLErr eErr = GDALRasterIO(hBand_Y, GF_Read, 0,
6061 8 : bBottomUp ? nRasterYSize - 1 - j : j,
6062 : nRasterXSize, 1, padLatVal,
6063 : nRasterXSize, 1, GDT_Float64, 0, 0);
6064 8 : if (eErr == CE_None)
6065 : {
6066 8 : eErr = GDALRasterIO(hBand_X, GF_Read, 0,
6067 8 : bBottomUp ? nRasterYSize - 1 - j : j,
6068 : nRasterXSize, 1, padLonVal,
6069 : nRasterXSize, 1, GDT_Float64, 0, 0);
6070 : }
6071 :
6072 8 : if (eErr == CE_None)
6073 : {
6074 8 : bOK = true;
6075 : }
6076 : else
6077 : {
6078 0 : bOK = false;
6079 0 : CPLError(CE_Failure, CPLE_AppDefined,
6080 : "Unable to get scanline %d", j);
6081 : }
6082 :
6083 : // Write data.
6084 8 : if (bOK)
6085 : {
6086 8 : status = nc_put_vara_double(cdfid, nVarLatID, start, count,
6087 : padLatVal);
6088 8 : NCDF_ERR(status);
6089 8 : status = nc_put_vara_double(cdfid, nVarLonID, start, count,
6090 : padLonVal);
6091 8 : NCDF_ERR(status);
6092 : }
6093 :
6094 8 : if (pfnProgress && (nRasterYSize / 10) > 0 &&
6095 0 : (j % (nRasterYSize / 10) == 0))
6096 : {
6097 0 : dfProgress += 0.08;
6098 0 : pfnProgress(dfProgress, nullptr, pProgressData);
6099 : }
6100 4 : }
6101 : }
6102 :
6103 : // If not projected, assume geographic to catch grids without Datum.
6104 53 : else if (bWriteLonLat)
6105 : {
6106 : // Get latitude values.
6107 53 : const double dfY0 = (!bBottomUp) ? m_gt[3] :
6108 : // Invert latitude values.
6109 53 : m_gt[3] + (m_gt[5] * nRasterYSize);
6110 53 : const double dfDY = m_gt[5];
6111 :
6112 : // Override lat values with the ones in GEOLOCATION/Y_VALUES.
6113 53 : if (netCDFDataset::GetMetadataItem("Y_VALUES", "GEOLOCATION") !=
6114 : nullptr)
6115 : {
6116 0 : int nTemp = 0;
6117 0 : padLatVal = Get1DGeolocation("Y_VALUES", nTemp);
6118 : // Make sure we got the correct amount, if not fallback to GT */
6119 : // could add test fabs(fabs(padLatVal[0]) - fabs(dfY0)) <= 0.1))
6120 0 : if (nTemp == nRasterYSize)
6121 : {
6122 0 : CPLDebug(
6123 : "GDAL_netCDF",
6124 : "Using Y_VALUES geolocation metadata for lat values");
6125 : }
6126 : else
6127 : {
6128 0 : CPLDebug("GDAL_netCDF",
6129 : "Got %d elements from Y_VALUES geolocation "
6130 : "metadata, need %d",
6131 : nTemp, nRasterYSize);
6132 0 : if (padLatVal)
6133 : {
6134 0 : CPLFree(padLatVal);
6135 0 : padLatVal = nullptr;
6136 : }
6137 : }
6138 : }
6139 :
6140 53 : if (padLatVal == nullptr)
6141 : {
6142 : padLatVal = static_cast<double *>(
6143 53 : CPLMalloc(nRasterYSize * sizeof(double)));
6144 7105 : for (int i = 0; i < nRasterYSize; i++)
6145 : {
6146 : // The data point is centered inside the pixel.
6147 7052 : if (!bBottomUp)
6148 0 : padLatVal[i] = dfY0 + (i + 0.5) * dfDY;
6149 : else // Invert latitude values.
6150 7052 : padLatVal[i] = dfY0 - (i + 0.5) * dfDY;
6151 : }
6152 : }
6153 :
6154 53 : size_t startLat[1] = {0};
6155 53 : size_t countLat[1] = {static_cast<size_t>(nRasterYSize)};
6156 :
6157 : // Get longitude values.
6158 53 : const double dfX0 = m_gt[0];
6159 53 : const double dfDX = m_gt[1];
6160 :
6161 : padLonVal =
6162 53 : static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
6163 7157 : for (int i = 0; i < nRasterXSize; i++)
6164 : {
6165 : // The data point is centered inside the pixel.
6166 7104 : padLonVal[i] = dfX0 + (i + 0.5) * dfDX;
6167 : }
6168 :
6169 53 : size_t startLon[1] = {0};
6170 53 : size_t countLon[1] = {static_cast<size_t>(nRasterXSize)};
6171 :
6172 : // Write latitude and longitude values.
6173 :
6174 : // Make sure we are in data mode.
6175 53 : SetDefineMode(false);
6176 :
6177 : // Write values.
6178 53 : CPLDebug("GDAL_netCDF", "Writing lat values");
6179 :
6180 53 : int status = nc_put_vara_double(cdfid, nVarLatID, startLat,
6181 : countLat, padLatVal);
6182 53 : NCDF_ERR(status);
6183 :
6184 53 : CPLDebug("GDAL_netCDF", "Writing lon values");
6185 53 : status = nc_put_vara_double(cdfid, nVarLonID, startLon, countLon,
6186 : padLonVal);
6187 53 : NCDF_ERR(status);
6188 :
6189 : } // Not projected.
6190 :
6191 82 : CPLFree(padLatVal);
6192 82 : CPLFree(padLonVal);
6193 :
6194 82 : if (pfnProgress)
6195 41 : pfnProgress(1.00, nullptr, pProgressData);
6196 : }
6197 :
6198 164 : if (hDS_X != nullptr)
6199 : {
6200 10 : GDALClose(hDS_X);
6201 : }
6202 164 : if (hDS_Y != nullptr)
6203 : {
6204 10 : GDALClose(hDS_Y);
6205 : }
6206 :
6207 164 : return CE_None;
6208 : }
6209 :
6210 : // Write Projection variable to band variable.
6211 : // Moved from AddProjectionVars() for cases when bands are added after
6212 : // projection.
6213 377 : bool netCDFDataset::AddGridMappingRef()
6214 : {
6215 377 : bool bRet = true;
6216 377 : bool bOldDefineMode = bDefineMode;
6217 :
6218 568 : if ((GetAccess() == GA_Update) && (nBands >= 1) && (GetRasterBand(1)) &&
6219 191 : ((pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, "")) ||
6220 185 : (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))))
6221 : {
6222 73 : bAddedGridMappingRef = true;
6223 :
6224 : // Make sure we are in define mode.
6225 73 : SetDefineMode(true);
6226 :
6227 192 : for (int i = 1; i <= nBands; i++)
6228 : {
6229 : const int nVarId =
6230 119 : static_cast<netCDFRasterBand *>(GetRasterBand(i))->nZId;
6231 :
6232 119 : if (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))
6233 : {
6234 : int status =
6235 230 : nc_put_att_text(cdfid, nVarId, CF_GRD_MAPPING,
6236 115 : strlen(pszCFProjection), pszCFProjection);
6237 115 : NCDF_ERR(status);
6238 115 : if (status != NC_NOERR)
6239 0 : bRet = false;
6240 : }
6241 119 : if (pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, ""))
6242 : {
6243 : int status =
6244 6 : nc_put_att_text(cdfid, nVarId, CF_COORDINATES,
6245 : strlen(pszCFCoordinates), pszCFCoordinates);
6246 6 : NCDF_ERR(status);
6247 6 : if (status != NC_NOERR)
6248 0 : bRet = false;
6249 : }
6250 : }
6251 :
6252 : // Go back to previous define mode.
6253 73 : SetDefineMode(bOldDefineMode);
6254 : }
6255 377 : return bRet;
6256 : }
6257 :
6258 : /************************************************************************/
6259 : /* GetGeoTransform() */
6260 : /************************************************************************/
6261 :
6262 118 : CPLErr netCDFDataset::GetGeoTransform(GDALGeoTransform >) const
6263 :
6264 : {
6265 118 : gt = m_gt;
6266 118 : if (m_bHasGeoTransform)
6267 87 : return CE_None;
6268 :
6269 31 : return GDALPamDataset::GetGeoTransform(gt);
6270 : }
6271 :
6272 : /************************************************************************/
6273 : /* rint() */
6274 : /************************************************************************/
6275 :
6276 0 : double netCDFDataset::rint(double dfX)
6277 : {
6278 0 : return std::round(dfX);
6279 : }
6280 :
6281 : /************************************************************************/
6282 : /* NCDFReadIsoMetadata() */
6283 : /************************************************************************/
6284 :
6285 16 : static void NCDFReadMetadataAsJson(int cdfid, CPLJSONObject &obj)
6286 : {
6287 16 : int nbAttr = 0;
6288 16 : NCDF_ERR(nc_inq_varnatts(cdfid, NC_GLOBAL, &nbAttr));
6289 :
6290 32 : std::map<std::string, CPLJSONArray> oMapNameToArray;
6291 40 : for (int l = 0; l < nbAttr; l++)
6292 : {
6293 : char szAttrName[NC_MAX_NAME + 1];
6294 24 : szAttrName[0] = 0;
6295 24 : NCDF_ERR(nc_inq_attname(cdfid, NC_GLOBAL, l, szAttrName));
6296 :
6297 24 : char *pszMetaValue = nullptr;
6298 24 : if (NCDFGetAttr(cdfid, NC_GLOBAL, szAttrName, &pszMetaValue) == CE_None)
6299 : {
6300 24 : nc_type nAttrType = NC_NAT;
6301 24 : size_t nAttrLen = 0;
6302 :
6303 24 : NCDF_ERR(nc_inq_att(cdfid, NC_GLOBAL, szAttrName, &nAttrType,
6304 : &nAttrLen));
6305 :
6306 24 : std::string osAttrName(szAttrName);
6307 24 : const auto sharpPos = osAttrName.find('#');
6308 24 : if (sharpPos == std::string::npos)
6309 : {
6310 16 : if (nAttrType == NC_DOUBLE || nAttrType == NC_FLOAT)
6311 4 : obj.Add(osAttrName, CPLAtof(pszMetaValue));
6312 : else
6313 12 : obj.Add(osAttrName, pszMetaValue);
6314 : }
6315 : else
6316 : {
6317 8 : osAttrName.resize(sharpPos);
6318 8 : auto iter = oMapNameToArray.find(osAttrName);
6319 8 : if (iter == oMapNameToArray.end())
6320 : {
6321 8 : CPLJSONArray array;
6322 4 : obj.Add(osAttrName, array);
6323 4 : oMapNameToArray[osAttrName] = array;
6324 4 : array.Add(pszMetaValue);
6325 : }
6326 : else
6327 : {
6328 4 : iter->second.Add(pszMetaValue);
6329 : }
6330 : }
6331 24 : CPLFree(pszMetaValue);
6332 24 : pszMetaValue = nullptr;
6333 : }
6334 : }
6335 :
6336 16 : int nSubGroups = 0;
6337 16 : int *panSubGroupIds = nullptr;
6338 16 : NCDFGetSubGroups(cdfid, &nSubGroups, &panSubGroupIds);
6339 16 : oMapNameToArray.clear();
6340 28 : for (int i = 0; i < nSubGroups; i++)
6341 : {
6342 24 : CPLJSONObject subObj;
6343 12 : NCDFReadMetadataAsJson(panSubGroupIds[i], subObj);
6344 :
6345 24 : std::string osGroupName;
6346 12 : osGroupName.resize(NC_MAX_NAME);
6347 12 : NCDF_ERR(nc_inq_grpname(panSubGroupIds[i], &osGroupName[0]));
6348 12 : osGroupName.resize(strlen(osGroupName.data()));
6349 12 : const auto sharpPos = osGroupName.find('#');
6350 12 : if (sharpPos == std::string::npos)
6351 : {
6352 4 : obj.Add(osGroupName, subObj);
6353 : }
6354 : else
6355 : {
6356 8 : osGroupName.resize(sharpPos);
6357 8 : auto iter = oMapNameToArray.find(osGroupName);
6358 8 : if (iter == oMapNameToArray.end())
6359 : {
6360 8 : CPLJSONArray array;
6361 4 : obj.Add(osGroupName, array);
6362 4 : oMapNameToArray[osGroupName] = array;
6363 4 : array.Add(subObj);
6364 : }
6365 : else
6366 : {
6367 4 : iter->second.Add(subObj);
6368 : }
6369 : }
6370 : }
6371 16 : CPLFree(panSubGroupIds);
6372 16 : }
6373 :
6374 4 : std::string NCDFReadMetadataAsJson(int cdfid)
6375 : {
6376 8 : CPLJSONDocument oDoc;
6377 8 : CPLJSONObject oRoot = oDoc.GetRoot();
6378 4 : NCDFReadMetadataAsJson(cdfid, oRoot);
6379 8 : return oDoc.SaveAsString();
6380 : }
6381 :
6382 : /************************************************************************/
6383 : /* ReadAttributes() */
6384 : /************************************************************************/
6385 1816 : CPLErr netCDFDataset::ReadAttributes(int cdfidIn, int var)
6386 :
6387 : {
6388 1816 : char *pszVarFullName = nullptr;
6389 1816 : ERR_RET(NCDFGetVarFullName(cdfidIn, var, &pszVarFullName));
6390 :
6391 : // For metadata in Sentinel 5
6392 1816 : if (STARTS_WITH(pszVarFullName, "/METADATA/"))
6393 : {
6394 6 : for (const char *key :
6395 : {"ISO_METADATA", "ESA_METADATA", "EOP_METADATA", "QA_STATISTICS",
6396 8 : "GRANULE_DESCRIPTION", "ALGORITHM_SETTINGS"})
6397 : {
6398 14 : if (var == NC_GLOBAL &&
6399 7 : strcmp(pszVarFullName,
6400 : CPLSPrintf("/METADATA/%s/NC_GLOBAL", key)) == 0)
6401 : {
6402 1 : CPLFree(pszVarFullName);
6403 1 : CPLStringList aosList;
6404 2 : aosList.AddString(CPLString(NCDFReadMetadataAsJson(cdfidIn))
6405 1 : .replaceAll("\\/", '/'));
6406 1 : m_oMapDomainToJSon[key] = std::move(aosList);
6407 1 : return CE_None;
6408 : }
6409 : }
6410 : }
6411 1815 : if (STARTS_WITH(pszVarFullName, "/PRODUCT/SUPPORT_DATA/"))
6412 : {
6413 0 : CPLFree(pszVarFullName);
6414 0 : CPLStringList aosList;
6415 : aosList.AddString(
6416 0 : CPLString(NCDFReadMetadataAsJson(cdfidIn)).replaceAll("\\/", '/'));
6417 0 : m_oMapDomainToJSon["SUPPORT_DATA"] = std::move(aosList);
6418 0 : return CE_None;
6419 : }
6420 :
6421 1815 : size_t nMetaNameSize =
6422 1815 : sizeof(char) * (strlen(pszVarFullName) + 1 + NC_MAX_NAME + 1);
6423 1815 : char *pszMetaName = static_cast<char *>(CPLMalloc(nMetaNameSize));
6424 :
6425 1815 : int nbAttr = 0;
6426 1815 : NCDF_ERR(nc_inq_varnatts(cdfidIn, var, &nbAttr));
6427 :
6428 9113 : for (int l = 0; l < nbAttr; l++)
6429 : {
6430 : char szAttrName[NC_MAX_NAME + 1];
6431 7298 : szAttrName[0] = 0;
6432 7298 : NCDF_ERR(nc_inq_attname(cdfidIn, var, l, szAttrName));
6433 7298 : snprintf(pszMetaName, nMetaNameSize, "%s#%s", pszVarFullName,
6434 : szAttrName);
6435 :
6436 7298 : char *pszMetaTemp = nullptr;
6437 7298 : if (NCDFGetAttr(cdfidIn, var, szAttrName, &pszMetaTemp) == CE_None)
6438 : {
6439 7297 : papszMetadata =
6440 7297 : CSLSetNameValue(papszMetadata, pszMetaName, pszMetaTemp);
6441 7297 : CPLFree(pszMetaTemp);
6442 7297 : pszMetaTemp = nullptr;
6443 : }
6444 : else
6445 : {
6446 1 : CPLDebug("GDAL_netCDF", "invalid metadata %s", pszMetaName);
6447 : }
6448 : }
6449 :
6450 1815 : CPLFree(pszVarFullName);
6451 1815 : CPLFree(pszMetaName);
6452 :
6453 1815 : if (var == NC_GLOBAL)
6454 : {
6455 : // Recurse on sub-groups.
6456 528 : int nSubGroups = 0;
6457 528 : int *panSubGroupIds = nullptr;
6458 528 : NCDFGetSubGroups(cdfidIn, &nSubGroups, &panSubGroupIds);
6459 560 : for (int i = 0; i < nSubGroups; i++)
6460 : {
6461 32 : ReadAttributes(panSubGroupIds[i], var);
6462 : }
6463 528 : CPLFree(panSubGroupIds);
6464 : }
6465 :
6466 1815 : return CE_None;
6467 : }
6468 :
6469 : /************************************************************************/
6470 : /* netCDFDataset::CreateSubDatasetList() */
6471 : /************************************************************************/
6472 55 : void netCDFDataset::CreateSubDatasetList(int nGroupId)
6473 : {
6474 : char szVarStdName[NC_MAX_NAME + 1];
6475 55 : int *ponDimIds = nullptr;
6476 : nc_type nAttype;
6477 : size_t nAttlen;
6478 :
6479 55 : netCDFDataset *poDS = this;
6480 :
6481 : int nVarCount;
6482 55 : nc_inq_nvars(nGroupId, &nVarCount);
6483 :
6484 55 : const bool bListAllArrays = CPLTestBool(
6485 55 : CSLFetchNameValueDef(papszOpenOptions, "LIST_ALL_ARRAYS", "NO"));
6486 :
6487 338 : for (int nVar = 0; nVar < nVarCount; nVar++)
6488 : {
6489 :
6490 : int nDims;
6491 283 : nc_inq_varndims(nGroupId, nVar, &nDims);
6492 :
6493 283 : if ((bListAllArrays && nDims > 0) || nDims >= 2)
6494 : {
6495 162 : ponDimIds = static_cast<int *>(CPLCalloc(nDims, sizeof(int)));
6496 162 : nc_inq_vardimid(nGroupId, nVar, ponDimIds);
6497 :
6498 : // Create Sub dataset list.
6499 162 : CPLString osDim;
6500 499 : for (int i = 0; i < nDims; i++)
6501 : {
6502 : size_t nDimLen;
6503 337 : nc_inq_dimlen(nGroupId, ponDimIds[i], &nDimLen);
6504 337 : if (!osDim.empty())
6505 175 : osDim += 'x';
6506 337 : osDim += CPLSPrintf("%d", (int)nDimLen);
6507 : }
6508 162 : CPLFree(ponDimIds);
6509 :
6510 : nc_type nVarType;
6511 162 : nc_inq_vartype(nGroupId, nVar, &nVarType);
6512 162 : const char *pszType = "";
6513 162 : switch (nVarType)
6514 : {
6515 38 : case NC_BYTE:
6516 38 : pszType = "8-bit integer";
6517 38 : break;
6518 2 : case NC_CHAR:
6519 2 : pszType = "8-bit character";
6520 2 : break;
6521 6 : case NC_SHORT:
6522 6 : pszType = "16-bit integer";
6523 6 : break;
6524 10 : case NC_INT:
6525 10 : pszType = "32-bit integer";
6526 10 : break;
6527 54 : case NC_FLOAT:
6528 54 : pszType = "32-bit floating-point";
6529 54 : break;
6530 34 : case NC_DOUBLE:
6531 34 : pszType = "64-bit floating-point";
6532 34 : break;
6533 4 : case NC_UBYTE:
6534 4 : pszType = "8-bit unsigned integer";
6535 4 : break;
6536 1 : case NC_USHORT:
6537 1 : pszType = "16-bit unsigned integer";
6538 1 : break;
6539 1 : case NC_UINT:
6540 1 : pszType = "32-bit unsigned integer";
6541 1 : break;
6542 1 : case NC_INT64:
6543 1 : pszType = "64-bit integer";
6544 1 : break;
6545 1 : case NC_UINT64:
6546 1 : pszType = "64-bit unsigned integer";
6547 1 : break;
6548 10 : default:
6549 10 : break;
6550 : }
6551 :
6552 162 : char *pszName = nullptr;
6553 162 : if (NCDFGetVarFullName(nGroupId, nVar, &pszName) != CE_None)
6554 0 : continue;
6555 :
6556 162 : nSubDatasets++;
6557 :
6558 162 : nAttlen = 0;
6559 162 : nc_inq_att(nGroupId, nVar, CF_STD_NAME, &nAttype, &nAttlen);
6560 324 : if (nAttlen < sizeof(szVarStdName) &&
6561 162 : nc_get_att_text(nGroupId, nVar, CF_STD_NAME, szVarStdName) ==
6562 : NC_NOERR)
6563 : {
6564 56 : szVarStdName[nAttlen] = '\0';
6565 : }
6566 : else
6567 : {
6568 106 : snprintf(szVarStdName, sizeof(szVarStdName), "%s", pszName);
6569 : }
6570 :
6571 : char szTemp[NC_MAX_NAME + 1];
6572 162 : snprintf(szTemp, sizeof(szTemp), "SUBDATASET_%d_NAME",
6573 : nSubDatasets);
6574 :
6575 162 : if (strchr(pszName, ' ') || strchr(pszName, ':'))
6576 : {
6577 1 : poDS->papszSubDatasets = CSLSetNameValue(
6578 : poDS->papszSubDatasets, szTemp,
6579 : CPLSPrintf("NETCDF:\"%s\":\"%s\"", poDS->osFilename.c_str(),
6580 : pszName));
6581 : }
6582 : else
6583 : {
6584 161 : poDS->papszSubDatasets = CSLSetNameValue(
6585 : poDS->papszSubDatasets, szTemp,
6586 : CPLSPrintf("NETCDF:\"%s\":%s", poDS->osFilename.c_str(),
6587 : pszName));
6588 : }
6589 :
6590 162 : CPLFree(pszName);
6591 :
6592 162 : snprintf(szTemp, sizeof(szTemp), "SUBDATASET_%d_DESC",
6593 : nSubDatasets);
6594 :
6595 162 : poDS->papszSubDatasets =
6596 162 : CSLSetNameValue(poDS->papszSubDatasets, szTemp,
6597 : CPLSPrintf("[%s] %s (%s)", osDim.c_str(),
6598 : szVarStdName, pszType));
6599 : }
6600 : }
6601 :
6602 : // Recurse on sub groups.
6603 55 : int nSubGroups = 0;
6604 55 : int *panSubGroupIds = nullptr;
6605 55 : NCDFGetSubGroups(nGroupId, &nSubGroups, &panSubGroupIds);
6606 62 : for (int i = 0; i < nSubGroups; i++)
6607 : {
6608 7 : CreateSubDatasetList(panSubGroupIds[i]);
6609 : }
6610 55 : CPLFree(panSubGroupIds);
6611 55 : }
6612 :
6613 : /************************************************************************/
6614 : /* TestCapability() */
6615 : /************************************************************************/
6616 :
6617 249 : int netCDFDataset::TestCapability(const char *pszCap) const
6618 : {
6619 249 : if (EQUAL(pszCap, ODsCCreateLayer))
6620 : {
6621 225 : return eAccess == GA_Update && nBands == 0 &&
6622 219 : (eMultipleLayerBehavior != SINGLE_LAYER ||
6623 230 : this->GetLayerCount() == 0 || bSGSupport);
6624 : }
6625 136 : else if (EQUAL(pszCap, ODsCZGeometries))
6626 2 : return true;
6627 :
6628 134 : return false;
6629 : }
6630 :
6631 : /************************************************************************/
6632 : /* GetLayer() */
6633 : /************************************************************************/
6634 :
6635 385 : const OGRLayer *netCDFDataset::GetLayer(int nIdx) const
6636 : {
6637 385 : if (nIdx < 0 || nIdx >= this->GetLayerCount())
6638 2 : return nullptr;
6639 383 : return papoLayers[nIdx].get();
6640 : }
6641 :
6642 : /************************************************************************/
6643 : /* ICreateLayer() */
6644 : /************************************************************************/
6645 :
6646 60 : OGRLayer *netCDFDataset::ICreateLayer(const char *pszName,
6647 : const OGRGeomFieldDefn *poGeomFieldDefn,
6648 : CSLConstList papszOptions)
6649 : {
6650 60 : int nLayerCDFId = cdfid;
6651 60 : if (!TestCapability(ODsCCreateLayer))
6652 0 : return nullptr;
6653 :
6654 60 : const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
6655 : const auto poSpatialRef =
6656 60 : poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
6657 :
6658 120 : CPLString osNetCDFLayerName(pszName);
6659 60 : const netCDFWriterConfigLayer *poLayerConfig = nullptr;
6660 60 : if (oWriterConfig.m_bIsValid)
6661 : {
6662 : std::map<CPLString, netCDFWriterConfigLayer>::const_iterator
6663 2 : oLayerIter = oWriterConfig.m_oLayers.find(pszName);
6664 2 : if (oLayerIter != oWriterConfig.m_oLayers.end())
6665 : {
6666 1 : poLayerConfig = &(oLayerIter->second);
6667 1 : osNetCDFLayerName = poLayerConfig->m_osNetCDFName;
6668 : }
6669 : }
6670 :
6671 60 : netCDFDataset *poLayerDataset = nullptr;
6672 60 : if (eMultipleLayerBehavior == SEPARATE_FILES)
6673 : {
6674 3 : if (CPLLaunderForFilenameSafe(osNetCDFLayerName.c_str(), nullptr) !=
6675 : osNetCDFLayerName)
6676 : {
6677 1 : CPLError(CE_Failure, CPLE_AppDefined,
6678 : "Illegal characters in '%s' to form a valid filename",
6679 : osNetCDFLayerName.c_str());
6680 1 : return nullptr;
6681 : }
6682 2 : char **papszDatasetOptions = nullptr;
6683 2 : papszDatasetOptions = CSLSetNameValue(
6684 : papszDatasetOptions, "CONFIG_FILE",
6685 2 : CSLFetchNameValue(papszCreationOptions, "CONFIG_FILE"));
6686 : papszDatasetOptions =
6687 2 : CSLSetNameValue(papszDatasetOptions, "FORMAT",
6688 2 : CSLFetchNameValue(papszCreationOptions, "FORMAT"));
6689 2 : papszDatasetOptions = CSLSetNameValue(
6690 : papszDatasetOptions, "WRITE_GDAL_TAGS",
6691 2 : CSLFetchNameValue(papszCreationOptions, "WRITE_GDAL_TAGS"));
6692 : const CPLString osLayerFilename(
6693 2 : CPLFormFilenameSafe(osFilename, osNetCDFLayerName, "nc"));
6694 2 : CPLAcquireMutex(hNCMutex, 1000.0);
6695 2 : poLayerDataset =
6696 2 : CreateLL(osLayerFilename, 0, 0, 0, papszDatasetOptions);
6697 2 : CPLReleaseMutex(hNCMutex);
6698 2 : CSLDestroy(papszDatasetOptions);
6699 2 : if (poLayerDataset == nullptr)
6700 0 : return nullptr;
6701 :
6702 2 : nLayerCDFId = poLayerDataset->cdfid;
6703 2 : NCDFAddGDALHistory(nLayerCDFId, osLayerFilename, bWriteGDALVersion,
6704 2 : bWriteGDALHistory, "", "Create",
6705 : NCDF_CONVENTIONS_CF_V1_6);
6706 : }
6707 57 : else if (eMultipleLayerBehavior == SEPARATE_GROUPS)
6708 : {
6709 2 : SetDefineMode(true);
6710 :
6711 2 : nLayerCDFId = -1;
6712 2 : int status = nc_def_grp(cdfid, osNetCDFLayerName, &nLayerCDFId);
6713 2 : NCDF_ERR(status);
6714 2 : if (status != NC_NOERR)
6715 0 : return nullptr;
6716 :
6717 2 : NCDFAddGDALHistory(nLayerCDFId, osFilename, bWriteGDALVersion,
6718 2 : bWriteGDALHistory, "", "Create",
6719 : NCDF_CONVENTIONS_CF_V1_6);
6720 : }
6721 :
6722 : // Make a clone to workaround a bug in released MapServer versions
6723 : // that destroys the passed SRS instead of releasing it .
6724 59 : OGRSpatialReference *poSRS = nullptr;
6725 59 : if (poSpatialRef)
6726 : {
6727 43 : poSRS = poSpatialRef->Clone();
6728 43 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
6729 : }
6730 : std::shared_ptr<netCDFLayer> poLayer(
6731 59 : new netCDFLayer(poLayerDataset ? poLayerDataset : this, nLayerCDFId,
6732 118 : osNetCDFLayerName, eGType, poSRS));
6733 59 : if (poSRS != nullptr)
6734 43 : poSRS->Release();
6735 :
6736 : // Fetch layer creation options coming from config file
6737 59 : char **papszNewOptions = CSLDuplicate(papszOptions);
6738 59 : if (oWriterConfig.m_bIsValid)
6739 : {
6740 2 : std::map<CPLString, CPLString>::const_iterator oIter;
6741 3 : for (oIter = oWriterConfig.m_oLayerCreationOptions.begin();
6742 3 : oIter != oWriterConfig.m_oLayerCreationOptions.end(); ++oIter)
6743 : {
6744 : papszNewOptions =
6745 1 : CSLSetNameValue(papszNewOptions, oIter->first, oIter->second);
6746 : }
6747 2 : if (poLayerConfig != nullptr)
6748 : {
6749 3 : for (oIter = poLayerConfig->m_oLayerCreationOptions.begin();
6750 3 : oIter != poLayerConfig->m_oLayerCreationOptions.end(); ++oIter)
6751 : {
6752 2 : papszNewOptions = CSLSetNameValue(papszNewOptions, oIter->first,
6753 2 : oIter->second);
6754 : }
6755 : }
6756 : }
6757 :
6758 59 : const bool bRet = poLayer->Create(papszNewOptions, poLayerConfig);
6759 59 : CSLDestroy(papszNewOptions);
6760 :
6761 59 : if (!bRet)
6762 : {
6763 0 : return nullptr;
6764 : }
6765 :
6766 59 : if (poLayerDataset != nullptr)
6767 2 : apoVectorDatasets.push_back(poLayerDataset);
6768 :
6769 59 : papoLayers.push_back(poLayer);
6770 59 : return poLayer.get();
6771 : }
6772 :
6773 : /************************************************************************/
6774 : /* CloneAttributes() */
6775 : /************************************************************************/
6776 :
6777 137 : bool netCDFDataset::CloneAttributes(int old_cdfid, int new_cdfid, int nSrcVarId,
6778 : int nDstVarId)
6779 : {
6780 137 : int nAttCount = -1;
6781 137 : int status = nc_inq_varnatts(old_cdfid, nSrcVarId, &nAttCount);
6782 137 : NCDF_ERR(status);
6783 :
6784 693 : for (int i = 0; i < nAttCount; i++)
6785 : {
6786 : char szName[NC_MAX_NAME + 1];
6787 556 : szName[0] = 0;
6788 556 : status = nc_inq_attname(old_cdfid, nSrcVarId, i, szName);
6789 556 : NCDF_ERR(status);
6790 :
6791 : status =
6792 556 : nc_copy_att(old_cdfid, nSrcVarId, szName, new_cdfid, nDstVarId);
6793 556 : NCDF_ERR(status);
6794 556 : if (status != NC_NOERR)
6795 0 : return false;
6796 : }
6797 :
6798 137 : return true;
6799 : }
6800 :
6801 : /************************************************************************/
6802 : /* CloneVariableContent() */
6803 : /************************************************************************/
6804 :
6805 121 : bool netCDFDataset::CloneVariableContent(int old_cdfid, int new_cdfid,
6806 : int nSrcVarId, int nDstVarId)
6807 : {
6808 121 : int nVarDimCount = -1;
6809 121 : int status = nc_inq_varndims(old_cdfid, nSrcVarId, &nVarDimCount);
6810 121 : NCDF_ERR(status);
6811 121 : int anDimIds[] = {-1, 1};
6812 121 : status = nc_inq_vardimid(old_cdfid, nSrcVarId, anDimIds);
6813 121 : NCDF_ERR(status);
6814 121 : nc_type nc_datatype = NC_NAT;
6815 121 : status = nc_inq_vartype(old_cdfid, nSrcVarId, &nc_datatype);
6816 121 : NCDF_ERR(status);
6817 121 : size_t nTypeSize = 0;
6818 121 : switch (nc_datatype)
6819 : {
6820 35 : case NC_BYTE:
6821 : case NC_CHAR:
6822 35 : nTypeSize = 1;
6823 35 : break;
6824 4 : case NC_SHORT:
6825 4 : nTypeSize = 2;
6826 4 : break;
6827 24 : case NC_INT:
6828 24 : nTypeSize = 4;
6829 24 : break;
6830 4 : case NC_FLOAT:
6831 4 : nTypeSize = 4;
6832 4 : break;
6833 43 : case NC_DOUBLE:
6834 43 : nTypeSize = 8;
6835 43 : break;
6836 2 : case NC_UBYTE:
6837 2 : nTypeSize = 1;
6838 2 : break;
6839 2 : case NC_USHORT:
6840 2 : nTypeSize = 2;
6841 2 : break;
6842 2 : case NC_UINT:
6843 2 : nTypeSize = 4;
6844 2 : break;
6845 4 : case NC_INT64:
6846 : case NC_UINT64:
6847 4 : nTypeSize = 8;
6848 4 : break;
6849 1 : case NC_STRING:
6850 1 : nTypeSize = sizeof(char *);
6851 1 : break;
6852 0 : default:
6853 : {
6854 0 : CPLError(CE_Failure, CPLE_NotSupported, "Unsupported data type: %d",
6855 : nc_datatype);
6856 0 : return false;
6857 : }
6858 : }
6859 :
6860 121 : size_t nElems = 1;
6861 : size_t anStart[NC_MAX_DIMS];
6862 : size_t anCount[NC_MAX_DIMS];
6863 121 : size_t nRecords = 1;
6864 261 : for (int i = 0; i < nVarDimCount; i++)
6865 : {
6866 140 : anStart[i] = 0;
6867 140 : if (i == 0)
6868 : {
6869 116 : anCount[i] = 1;
6870 116 : status = nc_inq_dimlen(old_cdfid, anDimIds[i], &nRecords);
6871 116 : NCDF_ERR(status);
6872 : }
6873 : else
6874 : {
6875 24 : anCount[i] = 0;
6876 24 : status = nc_inq_dimlen(old_cdfid, anDimIds[i], &anCount[i]);
6877 24 : NCDF_ERR(status);
6878 24 : nElems *= anCount[i];
6879 : }
6880 : }
6881 :
6882 : /* Workaround in some cases a netCDF bug:
6883 : * https://github.com/Unidata/netcdf-c/pull/1442 */
6884 121 : if (nRecords > 0 && nRecords < 10 * 1000 * 1000 / (nElems * nTypeSize))
6885 : {
6886 119 : nElems *= nRecords;
6887 119 : anCount[0] = nRecords;
6888 119 : nRecords = 1;
6889 : }
6890 :
6891 121 : void *pBuffer = VSI_MALLOC2_VERBOSE(nElems, nTypeSize);
6892 121 : if (pBuffer == nullptr)
6893 0 : return false;
6894 :
6895 240 : for (size_t iRecord = 0; iRecord < nRecords; iRecord++)
6896 : {
6897 119 : anStart[0] = iRecord;
6898 :
6899 119 : switch (nc_datatype)
6900 : {
6901 5 : case NC_BYTE:
6902 : status =
6903 5 : nc_get_vara_schar(old_cdfid, nSrcVarId, anStart, anCount,
6904 : static_cast<signed char *>(pBuffer));
6905 5 : if (!status)
6906 5 : status = nc_put_vara_schar(
6907 : new_cdfid, nDstVarId, anStart, anCount,
6908 : static_cast<signed char *>(pBuffer));
6909 5 : break;
6910 28 : case NC_CHAR:
6911 : status =
6912 28 : nc_get_vara_text(old_cdfid, nSrcVarId, anStart, anCount,
6913 : static_cast<char *>(pBuffer));
6914 28 : if (!status)
6915 : status =
6916 28 : nc_put_vara_text(new_cdfid, nDstVarId, anStart, anCount,
6917 : static_cast<char *>(pBuffer));
6918 28 : break;
6919 4 : case NC_SHORT:
6920 : status =
6921 4 : nc_get_vara_short(old_cdfid, nSrcVarId, anStart, anCount,
6922 : static_cast<short *>(pBuffer));
6923 4 : if (!status)
6924 4 : status = nc_put_vara_short(new_cdfid, nDstVarId, anStart,
6925 : anCount,
6926 : static_cast<short *>(pBuffer));
6927 4 : break;
6928 24 : case NC_INT:
6929 24 : status = nc_get_vara_int(old_cdfid, nSrcVarId, anStart, anCount,
6930 : static_cast<int *>(pBuffer));
6931 24 : if (!status)
6932 : status =
6933 24 : nc_put_vara_int(new_cdfid, nDstVarId, anStart, anCount,
6934 : static_cast<int *>(pBuffer));
6935 24 : break;
6936 4 : case NC_FLOAT:
6937 : status =
6938 4 : nc_get_vara_float(old_cdfid, nSrcVarId, anStart, anCount,
6939 : static_cast<float *>(pBuffer));
6940 4 : if (!status)
6941 4 : status = nc_put_vara_float(new_cdfid, nDstVarId, anStart,
6942 : anCount,
6943 : static_cast<float *>(pBuffer));
6944 4 : break;
6945 43 : case NC_DOUBLE:
6946 : status =
6947 43 : nc_get_vara_double(old_cdfid, nSrcVarId, anStart, anCount,
6948 : static_cast<double *>(pBuffer));
6949 43 : if (!status)
6950 43 : status = nc_put_vara_double(new_cdfid, nDstVarId, anStart,
6951 : anCount,
6952 : static_cast<double *>(pBuffer));
6953 43 : break;
6954 1 : case NC_STRING:
6955 : status =
6956 1 : nc_get_vara_string(old_cdfid, nSrcVarId, anStart, anCount,
6957 : static_cast<char **>(pBuffer));
6958 1 : if (!status)
6959 : {
6960 1 : status = nc_put_vara_string(
6961 : new_cdfid, nDstVarId, anStart, anCount,
6962 : static_cast<const char **>(pBuffer));
6963 1 : nc_free_string(nElems, static_cast<char **>(pBuffer));
6964 : }
6965 1 : break;
6966 :
6967 2 : case NC_UBYTE:
6968 : status =
6969 2 : nc_get_vara_uchar(old_cdfid, nSrcVarId, anStart, anCount,
6970 : static_cast<unsigned char *>(pBuffer));
6971 2 : if (!status)
6972 2 : status = nc_put_vara_uchar(
6973 : new_cdfid, nDstVarId, anStart, anCount,
6974 : static_cast<unsigned char *>(pBuffer));
6975 2 : break;
6976 2 : case NC_USHORT:
6977 : status =
6978 2 : nc_get_vara_ushort(old_cdfid, nSrcVarId, anStart, anCount,
6979 : static_cast<unsigned short *>(pBuffer));
6980 2 : if (!status)
6981 2 : status = nc_put_vara_ushort(
6982 : new_cdfid, nDstVarId, anStart, anCount,
6983 : static_cast<unsigned short *>(pBuffer));
6984 2 : break;
6985 2 : case NC_UINT:
6986 : status =
6987 2 : nc_get_vara_uint(old_cdfid, nSrcVarId, anStart, anCount,
6988 : static_cast<unsigned int *>(pBuffer));
6989 2 : if (!status)
6990 : status =
6991 2 : nc_put_vara_uint(new_cdfid, nDstVarId, anStart, anCount,
6992 : static_cast<unsigned int *>(pBuffer));
6993 2 : break;
6994 2 : case NC_INT64:
6995 : status =
6996 2 : nc_get_vara_longlong(old_cdfid, nSrcVarId, anStart, anCount,
6997 : static_cast<long long *>(pBuffer));
6998 2 : if (!status)
6999 2 : status = nc_put_vara_longlong(
7000 : new_cdfid, nDstVarId, anStart, anCount,
7001 : static_cast<long long *>(pBuffer));
7002 2 : break;
7003 2 : case NC_UINT64:
7004 2 : status = nc_get_vara_ulonglong(
7005 : old_cdfid, nSrcVarId, anStart, anCount,
7006 : static_cast<unsigned long long *>(pBuffer));
7007 2 : if (!status)
7008 2 : status = nc_put_vara_ulonglong(
7009 : new_cdfid, nDstVarId, anStart, anCount,
7010 : static_cast<unsigned long long *>(pBuffer));
7011 2 : break;
7012 0 : default:
7013 0 : status = NC_EBADTYPE;
7014 : }
7015 :
7016 119 : NCDF_ERR(status);
7017 119 : if (status != NC_NOERR)
7018 : {
7019 0 : VSIFree(pBuffer);
7020 0 : return false;
7021 : }
7022 : }
7023 :
7024 121 : VSIFree(pBuffer);
7025 121 : return true;
7026 : }
7027 :
7028 : /************************************************************************/
7029 : /* NCDFIsUnlimitedDim() */
7030 : /************************************************************************/
7031 :
7032 58 : bool NCDFIsUnlimitedDim(bool bIsNC4, int cdfid, int nDimId)
7033 : {
7034 58 : if (bIsNC4)
7035 : {
7036 16 : int nUnlimitedDims = 0;
7037 16 : nc_inq_unlimdims(cdfid, &nUnlimitedDims, nullptr);
7038 16 : bool bFound = false;
7039 16 : if (nUnlimitedDims)
7040 : {
7041 : int *panUnlimitedDimIds =
7042 16 : static_cast<int *>(CPLMalloc(sizeof(int) * nUnlimitedDims));
7043 16 : nc_inq_unlimdims(cdfid, nullptr, panUnlimitedDimIds);
7044 30 : for (int i = 0; i < nUnlimitedDims; i++)
7045 : {
7046 22 : if (panUnlimitedDimIds[i] == nDimId)
7047 : {
7048 8 : bFound = true;
7049 8 : break;
7050 : }
7051 : }
7052 16 : CPLFree(panUnlimitedDimIds);
7053 : }
7054 16 : return bFound;
7055 : }
7056 : else
7057 : {
7058 42 : int nUnlimitedDimId = -1;
7059 42 : nc_inq(cdfid, nullptr, nullptr, nullptr, &nUnlimitedDimId);
7060 42 : return nDimId == nUnlimitedDimId;
7061 : }
7062 : }
7063 :
7064 : /************************************************************************/
7065 : /* CloneGrp() */
7066 : /************************************************************************/
7067 :
7068 16 : bool netCDFDataset::CloneGrp(int nOldGrpId, int nNewGrpId, bool bIsNC4,
7069 : int nLayerId, int nDimIdToGrow, size_t nNewSize)
7070 : {
7071 : // Clone dimensions
7072 16 : int nDimCount = -1;
7073 16 : int status = nc_inq_ndims(nOldGrpId, &nDimCount);
7074 16 : NCDF_ERR(status);
7075 16 : if (nDimCount < 0 || nDimCount > NC_MAX_DIMS)
7076 0 : return false;
7077 : int anDimIds[NC_MAX_DIMS];
7078 16 : int nUnlimiDimID = -1;
7079 16 : status = nc_inq_unlimdim(nOldGrpId, &nUnlimiDimID);
7080 16 : NCDF_ERR(status);
7081 16 : if (bIsNC4)
7082 : {
7083 : // In NC4, the dimension ids of a group are not necessarily in
7084 : // [0,nDimCount-1] range
7085 8 : int nDimCount2 = -1;
7086 8 : status = nc_inq_dimids(nOldGrpId, &nDimCount2, anDimIds, FALSE);
7087 8 : NCDF_ERR(status);
7088 8 : CPLAssert(nDimCount == nDimCount2);
7089 : }
7090 : else
7091 : {
7092 36 : for (int i = 0; i < nDimCount; i++)
7093 28 : anDimIds[i] = i;
7094 : }
7095 60 : for (int i = 0; i < nDimCount; i++)
7096 : {
7097 : char szDimName[NC_MAX_NAME + 1];
7098 44 : szDimName[0] = 0;
7099 44 : size_t nLen = 0;
7100 44 : const int nDimId = anDimIds[i];
7101 44 : status = nc_inq_dim(nOldGrpId, nDimId, szDimName, &nLen);
7102 44 : NCDF_ERR(status);
7103 44 : if (NCDFIsUnlimitedDim(bIsNC4, nOldGrpId, nDimId))
7104 16 : nLen = NC_UNLIMITED;
7105 28 : else if (nDimId == nDimIdToGrow && nOldGrpId == nLayerId)
7106 13 : nLen = nNewSize;
7107 44 : int nNewDimId = -1;
7108 44 : status = nc_def_dim(nNewGrpId, szDimName, nLen, &nNewDimId);
7109 44 : NCDF_ERR(status);
7110 44 : CPLAssert(nDimId == nNewDimId);
7111 44 : if (status != NC_NOERR)
7112 : {
7113 0 : return false;
7114 : }
7115 : }
7116 :
7117 : // Clone main attributes
7118 16 : if (!CloneAttributes(nOldGrpId, nNewGrpId, NC_GLOBAL, NC_GLOBAL))
7119 : {
7120 0 : return false;
7121 : }
7122 :
7123 : // Clone variable definitions
7124 16 : int nVarCount = -1;
7125 16 : status = nc_inq_nvars(nOldGrpId, &nVarCount);
7126 16 : NCDF_ERR(status);
7127 :
7128 137 : for (int i = 0; i < nVarCount; i++)
7129 : {
7130 : char szVarName[NC_MAX_NAME + 1];
7131 121 : szVarName[0] = 0;
7132 121 : status = nc_inq_varname(nOldGrpId, i, szVarName);
7133 121 : NCDF_ERR(status);
7134 121 : nc_type nc_datatype = NC_NAT;
7135 121 : status = nc_inq_vartype(nOldGrpId, i, &nc_datatype);
7136 121 : NCDF_ERR(status);
7137 121 : int nVarDimCount = -1;
7138 121 : status = nc_inq_varndims(nOldGrpId, i, &nVarDimCount);
7139 121 : NCDF_ERR(status);
7140 121 : status = nc_inq_vardimid(nOldGrpId, i, anDimIds);
7141 121 : NCDF_ERR(status);
7142 121 : int nNewVarId = -1;
7143 121 : status = nc_def_var(nNewGrpId, szVarName, nc_datatype, nVarDimCount,
7144 : anDimIds, &nNewVarId);
7145 121 : NCDF_ERR(status);
7146 121 : CPLAssert(i == nNewVarId);
7147 121 : if (status != NC_NOERR)
7148 : {
7149 0 : return false;
7150 : }
7151 :
7152 121 : if (!CloneAttributes(nOldGrpId, nNewGrpId, i, i))
7153 : {
7154 0 : return false;
7155 : }
7156 : }
7157 :
7158 16 : status = nc_enddef(nNewGrpId);
7159 16 : NCDF_ERR(status);
7160 16 : if (status != NC_NOERR)
7161 : {
7162 0 : return false;
7163 : }
7164 :
7165 : // Clone variable content
7166 137 : for (int i = 0; i < nVarCount; i++)
7167 : {
7168 121 : if (!CloneVariableContent(nOldGrpId, nNewGrpId, i, i))
7169 : {
7170 0 : return false;
7171 : }
7172 : }
7173 :
7174 16 : return true;
7175 : }
7176 :
7177 : /************************************************************************/
7178 : /* GrowDim() */
7179 : /************************************************************************/
7180 :
7181 13 : bool netCDFDataset::GrowDim(int nLayerId, int nDimIdToGrow, size_t nNewSize)
7182 : {
7183 : int nCreationMode;
7184 : // Set nCreationMode based on eFormat.
7185 13 : switch (eFormat)
7186 : {
7187 : #ifdef NETCDF_HAS_NC2
7188 0 : case NCDF_FORMAT_NC2:
7189 0 : nCreationMode = NC_CLOBBER | NC_64BIT_OFFSET;
7190 0 : break;
7191 : #endif
7192 5 : case NCDF_FORMAT_NC4:
7193 5 : nCreationMode = NC_CLOBBER | NC_NETCDF4;
7194 5 : break;
7195 0 : case NCDF_FORMAT_NC4C:
7196 0 : nCreationMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
7197 0 : break;
7198 8 : case NCDF_FORMAT_NC:
7199 : default:
7200 8 : nCreationMode = NC_CLOBBER;
7201 8 : break;
7202 : }
7203 :
7204 13 : int new_cdfid = -1;
7205 26 : CPLString osTmpFilename(osFilename + ".tmp");
7206 26 : CPLString osFilenameForNCCreate(osTmpFilename);
7207 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
7208 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
7209 : {
7210 : char *pszTemp =
7211 : CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
7212 : osFilenameForNCCreate = pszTemp;
7213 : CPLFree(pszTemp);
7214 : }
7215 : #endif
7216 13 : int status = nc_create(osFilenameForNCCreate, nCreationMode, &new_cdfid);
7217 13 : NCDF_ERR(status);
7218 13 : if (status != NC_NOERR)
7219 0 : return false;
7220 :
7221 13 : if (!CloneGrp(cdfid, new_cdfid, eFormat == NCDF_FORMAT_NC4, nLayerId,
7222 : nDimIdToGrow, nNewSize))
7223 : {
7224 0 : GDAL_nc_close(new_cdfid);
7225 0 : return false;
7226 : }
7227 :
7228 13 : int nGroupCount = 0;
7229 26 : std::vector<CPLString> oListGrpName;
7230 31 : if (eFormat == NCDF_FORMAT_NC4 &&
7231 18 : nc_inq_grps(cdfid, &nGroupCount, nullptr) == NC_NOERR &&
7232 5 : nGroupCount > 0)
7233 : {
7234 : int *panGroupIds =
7235 2 : static_cast<int *>(CPLMalloc(sizeof(int) * nGroupCount));
7236 2 : status = nc_inq_grps(cdfid, nullptr, panGroupIds);
7237 2 : NCDF_ERR(status);
7238 5 : for (int i = 0; i < nGroupCount; i++)
7239 : {
7240 : char szGroupName[NC_MAX_NAME + 1];
7241 3 : szGroupName[0] = 0;
7242 3 : NCDF_ERR(nc_inq_grpname(panGroupIds[i], szGroupName));
7243 3 : int nNewGrpId = -1;
7244 3 : status = nc_def_grp(new_cdfid, szGroupName, &nNewGrpId);
7245 3 : NCDF_ERR(status);
7246 3 : if (status != NC_NOERR)
7247 : {
7248 0 : CPLFree(panGroupIds);
7249 0 : GDAL_nc_close(new_cdfid);
7250 0 : return false;
7251 : }
7252 3 : if (!CloneGrp(panGroupIds[i], nNewGrpId, /*bIsNC4=*/true, nLayerId,
7253 : nDimIdToGrow, nNewSize))
7254 : {
7255 0 : CPLFree(panGroupIds);
7256 0 : GDAL_nc_close(new_cdfid);
7257 0 : return false;
7258 : }
7259 : }
7260 2 : CPLFree(panGroupIds);
7261 :
7262 5 : for (int i = 0; i < this->GetLayerCount(); i++)
7263 : {
7264 3 : auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
7265 3 : if (poLayer)
7266 : {
7267 : char szGroupName[NC_MAX_NAME + 1];
7268 3 : szGroupName[0] = 0;
7269 3 : status = nc_inq_grpname(poLayer->GetCDFID(), szGroupName);
7270 3 : NCDF_ERR(status);
7271 3 : oListGrpName.push_back(szGroupName);
7272 : }
7273 : }
7274 : }
7275 :
7276 13 : GDAL_nc_close(cdfid);
7277 13 : cdfid = -1;
7278 13 : GDAL_nc_close(new_cdfid);
7279 :
7280 26 : CPLString osOriFilename(osFilename + ".ori");
7281 26 : if (VSIRename(osFilename, osOriFilename) != 0 ||
7282 13 : VSIRename(osTmpFilename, osFilename) != 0)
7283 : {
7284 0 : CPLError(CE_Failure, CPLE_FileIO, "Renaming of files failed");
7285 0 : return false;
7286 : }
7287 13 : VSIUnlink(osOriFilename);
7288 :
7289 26 : CPLString osFilenameForNCOpen(osFilename);
7290 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
7291 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
7292 : {
7293 : char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
7294 : osFilenameForNCOpen = pszTemp;
7295 : CPLFree(pszTemp);
7296 : }
7297 : #endif
7298 13 : status = GDAL_nc_open(osFilenameForNCOpen, NC_WRITE, &cdfid);
7299 13 : NCDF_ERR(status);
7300 13 : if (status != NC_NOERR)
7301 0 : return false;
7302 13 : bDefineMode = false;
7303 :
7304 13 : if (!oListGrpName.empty())
7305 : {
7306 5 : for (int i = 0; i < this->GetLayerCount(); i++)
7307 : {
7308 3 : auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
7309 3 : if (poLayer)
7310 : {
7311 3 : int nNewLayerCDFID = -1;
7312 3 : status = nc_inq_ncid(cdfid, oListGrpName[i].c_str(),
7313 : &nNewLayerCDFID);
7314 3 : NCDF_ERR(status);
7315 3 : poLayer->SetCDFID(nNewLayerCDFID);
7316 : }
7317 : }
7318 : }
7319 : else
7320 : {
7321 22 : for (int i = 0; i < this->GetLayerCount(); i++)
7322 : {
7323 11 : auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
7324 11 : if (poLayer)
7325 11 : poLayer->SetCDFID(cdfid);
7326 : }
7327 : }
7328 :
7329 13 : return true;
7330 : }
7331 :
7332 : #ifdef ENABLE_NCDUMP
7333 :
7334 : /************************************************************************/
7335 : /* netCDFDatasetCreateTempFile() */
7336 : /************************************************************************/
7337 :
7338 : /* Create a netCDF file from a text dump (format of ncdump) */
7339 : /* Mostly to easy fuzzing of the driver, while still generating valid */
7340 : /* netCDF files. */
7341 : /* Note: not all data types are supported ! */
7342 4 : bool netCDFDatasetCreateTempFile(NetCDFFormatEnum eFormat,
7343 : const char *pszTmpFilename, VSILFILE *fpSrc)
7344 : {
7345 4 : CPL_IGNORE_RET_VAL(eFormat);
7346 4 : int nCreateMode = NC_CLOBBER;
7347 4 : if (eFormat == NCDF_FORMAT_NC4)
7348 1 : nCreateMode |= NC_NETCDF4;
7349 3 : else if (eFormat == NCDF_FORMAT_NC4C)
7350 0 : nCreateMode |= NC_NETCDF4 | NC_CLASSIC_MODEL;
7351 4 : int nCdfId = -1;
7352 4 : int status = nc_create(pszTmpFilename, nCreateMode, &nCdfId);
7353 4 : if (status != NC_NOERR)
7354 : {
7355 0 : return false;
7356 : }
7357 4 : VSIFSeekL(fpSrc, 0, SEEK_SET);
7358 : const char *pszLine;
7359 4 : constexpr int SECTION_NONE = 0;
7360 4 : constexpr int SECTION_DIMENSIONS = 1;
7361 4 : constexpr int SECTION_VARIABLES = 2;
7362 4 : constexpr int SECTION_DATA = 3;
7363 4 : int nActiveSection = SECTION_NONE;
7364 8 : std::map<CPLString, int> oMapDimToId;
7365 8 : std::map<int, int> oMapDimIdToDimLen;
7366 8 : std::map<CPLString, int> oMapVarToId;
7367 8 : std::map<int, std::vector<int>> oMapVarIdToVectorOfDimId;
7368 8 : std::map<int, int> oMapVarIdToType;
7369 4 : std::set<CPLString> oSetAttrDefined;
7370 4 : oMapVarToId[""] = -1;
7371 4 : size_t nTotalVarSize = 0;
7372 208 : while ((pszLine = CPLReadLineL(fpSrc)) != nullptr)
7373 : {
7374 204 : if (STARTS_WITH(pszLine, "dimensions:") &&
7375 : nActiveSection == SECTION_NONE)
7376 : {
7377 4 : nActiveSection = SECTION_DIMENSIONS;
7378 : }
7379 200 : else if (STARTS_WITH(pszLine, "variables:") &&
7380 : nActiveSection == SECTION_DIMENSIONS)
7381 : {
7382 4 : nActiveSection = SECTION_VARIABLES;
7383 : }
7384 196 : else if (STARTS_WITH(pszLine, "data:") &&
7385 : nActiveSection == SECTION_VARIABLES)
7386 : {
7387 4 : nActiveSection = SECTION_DATA;
7388 4 : status = nc_enddef(nCdfId);
7389 4 : if (status != NC_NOERR)
7390 : {
7391 0 : CPLDebug("netCDF", "nc_enddef() failed: %s",
7392 : nc_strerror(status));
7393 : }
7394 : }
7395 192 : else if (nActiveSection == SECTION_DIMENSIONS)
7396 : {
7397 9 : char **papszTokens = CSLTokenizeString2(pszLine, " \t=;", 0);
7398 9 : if (CSLCount(papszTokens) == 2)
7399 : {
7400 9 : const char *pszDimName = papszTokens[0];
7401 9 : bool bValidName = true;
7402 9 : if (STARTS_WITH(pszDimName, "_nc4_non_coord_"))
7403 : {
7404 : // This is an internal netcdf prefix. Using it may
7405 : // cause memory leaks.
7406 0 : bValidName = false;
7407 : }
7408 9 : if (!bValidName)
7409 : {
7410 0 : CPLDebug("netCDF",
7411 : "nc_def_dim(%s) failed: invalid name found",
7412 : pszDimName);
7413 0 : CSLDestroy(papszTokens);
7414 0 : continue;
7415 : }
7416 :
7417 : const bool bIsASCII =
7418 9 : CPLIsASCII(pszDimName, static_cast<size_t>(-1));
7419 9 : if (!bIsASCII)
7420 : {
7421 : // Workaround https://github.com/Unidata/netcdf-c/pull/450
7422 0 : CPLDebug("netCDF",
7423 : "nc_def_dim(%s) failed: rejected because "
7424 : "of non-ASCII characters",
7425 : pszDimName);
7426 0 : CSLDestroy(papszTokens);
7427 0 : continue;
7428 : }
7429 9 : int nDimSize = EQUAL(papszTokens[1], "UNLIMITED")
7430 : ? NC_UNLIMITED
7431 9 : : atoi(papszTokens[1]);
7432 9 : if (nDimSize >= 1000)
7433 1 : nDimSize = 1000; // to avoid very long processing
7434 9 : if (nDimSize >= 0)
7435 : {
7436 9 : int nDimId = -1;
7437 9 : status = nc_def_dim(nCdfId, pszDimName, nDimSize, &nDimId);
7438 9 : if (status != NC_NOERR)
7439 : {
7440 0 : CPLDebug("netCDF", "nc_def_dim(%s, %d) failed: %s",
7441 : pszDimName, nDimSize, nc_strerror(status));
7442 : }
7443 : else
7444 : {
7445 : #ifdef DEBUG_VERBOSE
7446 : CPLDebug("netCDF", "nc_def_dim(%s, %d) (%s) succeeded",
7447 : pszDimName, nDimSize, pszLine);
7448 : #endif
7449 9 : oMapDimToId[pszDimName] = nDimId;
7450 9 : oMapDimIdToDimLen[nDimId] = nDimSize;
7451 : }
7452 : }
7453 : }
7454 9 : CSLDestroy(papszTokens);
7455 : }
7456 183 : else if (nActiveSection == SECTION_VARIABLES)
7457 : {
7458 390 : while (*pszLine == ' ' || *pszLine == '\t')
7459 249 : pszLine++;
7460 141 : const char *pszColumn = strchr(pszLine, ':');
7461 141 : const char *pszEqual = strchr(pszLine, '=');
7462 141 : if (pszColumn == nullptr)
7463 : {
7464 21 : char **papszTokens = CSLTokenizeString2(pszLine, " \t=(),;", 0);
7465 21 : if (CSLCount(papszTokens) >= 2)
7466 : {
7467 17 : const char *pszVarName = papszTokens[1];
7468 17 : bool bValidName = true;
7469 17 : if (STARTS_WITH(pszVarName, "_nc4_non_coord_"))
7470 : {
7471 : // This is an internal netcdf prefix. Using it may
7472 : // cause memory leaks.
7473 0 : bValidName = false;
7474 : }
7475 138 : for (int i = 0; pszVarName[i]; i++)
7476 : {
7477 121 : if (!((pszVarName[i] >= 'a' && pszVarName[i] <= 'z') ||
7478 28 : (pszVarName[i] >= 'A' && pszVarName[i] <= 'Z') ||
7479 9 : (pszVarName[i] >= '0' && pszVarName[i] <= '9') ||
7480 6 : pszVarName[i] == '_'))
7481 : {
7482 0 : bValidName = false;
7483 : }
7484 : }
7485 17 : if (!bValidName)
7486 : {
7487 0 : CPLDebug(
7488 : "netCDF",
7489 : "nc_def_var(%s) failed: illegal character found",
7490 : pszVarName);
7491 0 : CSLDestroy(papszTokens);
7492 0 : continue;
7493 : }
7494 17 : if (oMapVarToId.find(pszVarName) != oMapVarToId.end())
7495 : {
7496 0 : CPLDebug("netCDF",
7497 : "nc_def_var(%s) failed: already defined",
7498 : pszVarName);
7499 0 : CSLDestroy(papszTokens);
7500 0 : continue;
7501 : }
7502 17 : const char *pszVarType = papszTokens[0];
7503 17 : int nc_datatype = NC_BYTE;
7504 17 : size_t nDataTypeSize = 1;
7505 17 : if (EQUAL(pszVarType, "char"))
7506 : {
7507 6 : nc_datatype = NC_CHAR;
7508 6 : nDataTypeSize = 1;
7509 : }
7510 11 : else if (EQUAL(pszVarType, "byte"))
7511 : {
7512 3 : nc_datatype = NC_BYTE;
7513 3 : nDataTypeSize = 1;
7514 : }
7515 8 : else if (EQUAL(pszVarType, "short"))
7516 : {
7517 0 : nc_datatype = NC_SHORT;
7518 0 : nDataTypeSize = 2;
7519 : }
7520 8 : else if (EQUAL(pszVarType, "int"))
7521 : {
7522 0 : nc_datatype = NC_INT;
7523 0 : nDataTypeSize = 4;
7524 : }
7525 8 : else if (EQUAL(pszVarType, "float"))
7526 : {
7527 0 : nc_datatype = NC_FLOAT;
7528 0 : nDataTypeSize = 4;
7529 : }
7530 8 : else if (EQUAL(pszVarType, "double"))
7531 : {
7532 8 : nc_datatype = NC_DOUBLE;
7533 8 : nDataTypeSize = 8;
7534 : }
7535 0 : else if (EQUAL(pszVarType, "ubyte"))
7536 : {
7537 0 : nc_datatype = NC_UBYTE;
7538 0 : nDataTypeSize = 1;
7539 : }
7540 0 : else if (EQUAL(pszVarType, "ushort"))
7541 : {
7542 0 : nc_datatype = NC_USHORT;
7543 0 : nDataTypeSize = 2;
7544 : }
7545 0 : else if (EQUAL(pszVarType, "uint"))
7546 : {
7547 0 : nc_datatype = NC_UINT;
7548 0 : nDataTypeSize = 4;
7549 : }
7550 0 : else if (EQUAL(pszVarType, "int64"))
7551 : {
7552 0 : nc_datatype = NC_INT64;
7553 0 : nDataTypeSize = 8;
7554 : }
7555 0 : else if (EQUAL(pszVarType, "uint64"))
7556 : {
7557 0 : nc_datatype = NC_UINT64;
7558 0 : nDataTypeSize = 8;
7559 : }
7560 :
7561 17 : int nDims = CSLCount(papszTokens) - 2;
7562 17 : if (nDims >= 32)
7563 : {
7564 : // The number of dimensions in a netCDFv4 file is
7565 : // limited by #define H5S_MAX_RANK 32
7566 : // but libnetcdf doesn't check that...
7567 0 : CPLDebug("netCDF",
7568 : "nc_def_var(%s) failed: too many dimensions",
7569 : pszVarName);
7570 0 : CSLDestroy(papszTokens);
7571 0 : continue;
7572 : }
7573 17 : std::vector<int> aoDimIds;
7574 17 : bool bFailed = false;
7575 17 : size_t nSize = 1;
7576 35 : for (int i = 0; i < nDims; i++)
7577 : {
7578 18 : const char *pszDimName = papszTokens[2 + i];
7579 18 : if (oMapDimToId.find(pszDimName) == oMapDimToId.end())
7580 : {
7581 0 : bFailed = true;
7582 0 : break;
7583 : }
7584 18 : const int nDimId = oMapDimToId[pszDimName];
7585 18 : aoDimIds.push_back(nDimId);
7586 :
7587 18 : const size_t nDimSize = oMapDimIdToDimLen[nDimId];
7588 18 : if (nDimSize != 0)
7589 : {
7590 18 : if (nSize >
7591 18 : std::numeric_limits<size_t>::max() / nDimSize)
7592 : {
7593 0 : bFailed = true;
7594 0 : break;
7595 : }
7596 : else
7597 : {
7598 18 : nSize *= nDimSize;
7599 : }
7600 : }
7601 : }
7602 17 : if (bFailed)
7603 : {
7604 0 : CPLDebug("netCDF",
7605 : "nc_def_var(%s) failed: unknown dimension(s)",
7606 : pszVarName);
7607 0 : CSLDestroy(papszTokens);
7608 0 : continue;
7609 : }
7610 17 : if (nSize > 100U * 1024 * 1024 / nDataTypeSize)
7611 : {
7612 0 : CPLDebug("netCDF",
7613 : "nc_def_var(%s) failed: too large data",
7614 : pszVarName);
7615 0 : CSLDestroy(papszTokens);
7616 0 : continue;
7617 : }
7618 17 : if (nTotalVarSize >
7619 34 : std::numeric_limits<size_t>::max() - nSize ||
7620 17 : nTotalVarSize + nSize > 100 * 1024 * 1024)
7621 : {
7622 0 : CPLDebug("netCDF",
7623 : "nc_def_var(%s) failed: too large data",
7624 : pszVarName);
7625 0 : CSLDestroy(papszTokens);
7626 0 : continue;
7627 : }
7628 17 : nTotalVarSize += nSize;
7629 :
7630 17 : int nVarId = -1;
7631 : status =
7632 30 : nc_def_var(nCdfId, pszVarName, nc_datatype, nDims,
7633 13 : (nDims) ? &aoDimIds[0] : nullptr, &nVarId);
7634 17 : if (status != NC_NOERR)
7635 : {
7636 0 : CPLDebug("netCDF", "nc_def_var(%s) failed: %s",
7637 : pszVarName, nc_strerror(status));
7638 : }
7639 : else
7640 : {
7641 : #ifdef DEBUG_VERBOSE
7642 : CPLDebug("netCDF", "nc_def_var(%s) (%s) succeeded",
7643 : pszVarName, pszLine);
7644 : #endif
7645 17 : oMapVarToId[pszVarName] = nVarId;
7646 17 : oMapVarIdToType[nVarId] = nc_datatype;
7647 17 : oMapVarIdToVectorOfDimId[nVarId] = std::move(aoDimIds);
7648 : }
7649 : }
7650 21 : CSLDestroy(papszTokens);
7651 : }
7652 120 : else if (pszEqual != nullptr && pszEqual - pszColumn > 0)
7653 : {
7654 116 : CPLString osVarName(pszLine, pszColumn - pszLine);
7655 116 : CPLString osAttrName(pszColumn + 1, pszEqual - pszColumn - 1);
7656 116 : osAttrName.Trim();
7657 116 : if (oMapVarToId.find(osVarName) == oMapVarToId.end())
7658 : {
7659 0 : CPLDebug("netCDF",
7660 : "nc_put_att(%s:%s) failed: "
7661 : "no corresponding variable",
7662 : osVarName.c_str(), osAttrName.c_str());
7663 0 : continue;
7664 : }
7665 116 : bool bValidName = true;
7666 1743 : for (size_t i = 0; i < osAttrName.size(); i++)
7667 : {
7668 1865 : if (!((osAttrName[i] >= 'a' && osAttrName[i] <= 'z') ||
7669 238 : (osAttrName[i] >= 'A' && osAttrName[i] <= 'Z') ||
7670 158 : (osAttrName[i] >= '0' && osAttrName[i] <= '9') ||
7671 158 : osAttrName[i] == '_'))
7672 : {
7673 0 : bValidName = false;
7674 : }
7675 : }
7676 116 : if (!bValidName)
7677 : {
7678 0 : CPLDebug(
7679 : "netCDF",
7680 : "nc_put_att(%s:%s) failed: illegal character found",
7681 : osVarName.c_str(), osAttrName.c_str());
7682 0 : continue;
7683 : }
7684 116 : if (oSetAttrDefined.find(osVarName + ":" + osAttrName) !=
7685 232 : oSetAttrDefined.end())
7686 : {
7687 0 : CPLDebug("netCDF",
7688 : "nc_put_att(%s:%s) failed: already defined",
7689 : osVarName.c_str(), osAttrName.c_str());
7690 0 : continue;
7691 : }
7692 :
7693 116 : const int nVarId = oMapVarToId[osVarName];
7694 116 : const char *pszValue = pszEqual + 1;
7695 232 : while (*pszValue == ' ')
7696 116 : pszValue++;
7697 :
7698 116 : status = NC_EBADTYPE;
7699 116 : if (*pszValue == '"')
7700 : {
7701 : // For _FillValue, the attribute type should match
7702 : // the variable type. Leaks memory with NC4 otherwise
7703 74 : if (osAttrName == "_FillValue")
7704 : {
7705 0 : CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
7706 : osVarName.c_str(), osAttrName.c_str(),
7707 : nc_strerror(status));
7708 0 : continue;
7709 : }
7710 :
7711 : // Unquote and unescape string value
7712 74 : CPLString osVal(pszValue + 1);
7713 222 : while (!osVal.empty())
7714 : {
7715 222 : if (osVal.back() == ';' || osVal.back() == ' ')
7716 : {
7717 148 : osVal.pop_back();
7718 : }
7719 74 : else if (osVal.back() == '"')
7720 : {
7721 74 : osVal.pop_back();
7722 74 : break;
7723 : }
7724 : else
7725 : {
7726 0 : break;
7727 : }
7728 : }
7729 74 : osVal.replaceAll("\\\"", '"');
7730 74 : status = nc_put_att_text(nCdfId, nVarId, osAttrName,
7731 : osVal.size(), osVal.c_str());
7732 : }
7733 : else
7734 : {
7735 84 : CPLString osVal(pszValue);
7736 126 : while (!osVal.empty())
7737 : {
7738 126 : if (osVal.back() == ';' || osVal.back() == ' ')
7739 : {
7740 84 : osVal.pop_back();
7741 : }
7742 : else
7743 : {
7744 42 : break;
7745 : }
7746 : }
7747 42 : int nc_datatype = -1;
7748 42 : if (!osVal.empty() && osVal.back() == 'b')
7749 : {
7750 3 : nc_datatype = NC_BYTE;
7751 3 : osVal.pop_back();
7752 : }
7753 39 : else if (!osVal.empty() && osVal.back() == 's')
7754 : {
7755 3 : nc_datatype = NC_SHORT;
7756 3 : osVal.pop_back();
7757 : }
7758 42 : if (CPLGetValueType(osVal) == CPL_VALUE_INTEGER)
7759 : {
7760 7 : if (nc_datatype < 0)
7761 4 : nc_datatype = NC_INT;
7762 : }
7763 35 : else if (CPLGetValueType(osVal) == CPL_VALUE_REAL)
7764 : {
7765 32 : nc_datatype = NC_DOUBLE;
7766 : }
7767 : else
7768 : {
7769 3 : nc_datatype = -1;
7770 : }
7771 :
7772 : // For _FillValue, check that the attribute type matches
7773 : // the variable type. Leaks memory with NC4 otherwise
7774 42 : if (osAttrName == "_FillValue")
7775 : {
7776 6 : if (nVarId < 0 ||
7777 3 : nc_datatype != oMapVarIdToType[nVarId])
7778 : {
7779 0 : nc_datatype = -1;
7780 : }
7781 : }
7782 :
7783 42 : if (nc_datatype == NC_BYTE)
7784 : {
7785 : signed char chVal =
7786 3 : static_cast<signed char>(atoi(osVal));
7787 3 : status = nc_put_att_schar(nCdfId, nVarId, osAttrName,
7788 : NC_BYTE, 1, &chVal);
7789 : }
7790 39 : else if (nc_datatype == NC_SHORT)
7791 : {
7792 0 : short nVal = static_cast<short>(atoi(osVal));
7793 0 : status = nc_put_att_short(nCdfId, nVarId, osAttrName,
7794 : NC_SHORT, 1, &nVal);
7795 : }
7796 39 : else if (nc_datatype == NC_INT)
7797 : {
7798 4 : int nVal = static_cast<int>(atoi(osVal));
7799 4 : status = nc_put_att_int(nCdfId, nVarId, osAttrName,
7800 : NC_INT, 1, &nVal);
7801 : }
7802 35 : else if (nc_datatype == NC_DOUBLE)
7803 : {
7804 32 : double dfVal = CPLAtof(osVal);
7805 32 : status = nc_put_att_double(nCdfId, nVarId, osAttrName,
7806 : NC_DOUBLE, 1, &dfVal);
7807 : }
7808 : }
7809 116 : if (status != NC_NOERR)
7810 : {
7811 3 : CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
7812 : osVarName.c_str(), osAttrName.c_str(),
7813 : nc_strerror(status));
7814 : }
7815 : else
7816 : {
7817 113 : oSetAttrDefined.insert(osVarName + ":" + osAttrName);
7818 : #ifdef DEBUG_VERBOSE
7819 : CPLDebug("netCDF", "nc_put_att_(%s:%s) (%s) succeeded",
7820 : osVarName.c_str(), osAttrName.c_str(), pszLine);
7821 : #endif
7822 : }
7823 : }
7824 : }
7825 42 : else if (nActiveSection == SECTION_DATA)
7826 : {
7827 55 : while (*pszLine == ' ' || *pszLine == '\t')
7828 17 : pszLine++;
7829 38 : const char *pszEqual = strchr(pszLine, '=');
7830 38 : if (pszEqual)
7831 : {
7832 17 : CPLString osVarName(pszLine, pszEqual - pszLine);
7833 17 : osVarName.Trim();
7834 17 : if (oMapVarToId.find(osVarName) == oMapVarToId.end())
7835 0 : continue;
7836 17 : const int nVarId = oMapVarToId[osVarName];
7837 17 : CPLString osAccVal(pszEqual + 1);
7838 17 : osAccVal.Trim();
7839 153 : while (osAccVal.empty() || osAccVal.back() != ';')
7840 : {
7841 136 : pszLine = CPLReadLineL(fpSrc);
7842 136 : if (pszLine == nullptr)
7843 0 : break;
7844 272 : CPLString osVal(pszLine);
7845 136 : osVal.Trim();
7846 136 : osAccVal += osVal;
7847 : }
7848 17 : if (pszLine == nullptr)
7849 0 : break;
7850 17 : osAccVal.pop_back();
7851 :
7852 : const std::vector<int> aoDimIds =
7853 34 : oMapVarIdToVectorOfDimId[nVarId];
7854 17 : size_t nSize = 1;
7855 34 : std::vector<size_t> aoStart, aoEdge;
7856 17 : aoStart.resize(aoDimIds.size());
7857 17 : aoEdge.resize(aoDimIds.size());
7858 35 : for (size_t i = 0; i < aoDimIds.size(); ++i)
7859 : {
7860 18 : const size_t nDimSize = oMapDimIdToDimLen[aoDimIds[i]];
7861 36 : if (nDimSize != 0 &&
7862 18 : nSize > std::numeric_limits<size_t>::max() / nDimSize)
7863 : {
7864 0 : nSize = 0;
7865 : }
7866 : else
7867 : {
7868 18 : nSize *= nDimSize;
7869 : }
7870 18 : aoStart[i] = 0;
7871 18 : aoEdge[i] = nDimSize;
7872 : }
7873 :
7874 17 : status = NC_EBADTYPE;
7875 17 : if (nSize == 0)
7876 : {
7877 : // Might happen with a unlimited dimension
7878 : }
7879 17 : else if (oMapVarIdToType[nVarId] == NC_DOUBLE)
7880 : {
7881 8 : if (!aoStart.empty())
7882 : {
7883 : char **papszTokens =
7884 8 : CSLTokenizeString2(osAccVal, " ,;", 0);
7885 8 : size_t nTokens = CSLCount(papszTokens);
7886 8 : if (nTokens >= nSize)
7887 : {
7888 : double *padfVals = static_cast<double *>(
7889 8 : VSI_CALLOC_VERBOSE(nSize, sizeof(double)));
7890 8 : if (padfVals)
7891 : {
7892 132 : for (size_t i = 0; i < nSize; i++)
7893 : {
7894 124 : padfVals[i] = CPLAtof(papszTokens[i]);
7895 : }
7896 8 : status = nc_put_vara_double(
7897 8 : nCdfId, nVarId, &aoStart[0], &aoEdge[0],
7898 : padfVals);
7899 8 : VSIFree(padfVals);
7900 : }
7901 : }
7902 8 : CSLDestroy(papszTokens);
7903 : }
7904 : }
7905 9 : else if (oMapVarIdToType[nVarId] == NC_BYTE)
7906 : {
7907 3 : if (!aoStart.empty())
7908 : {
7909 : char **papszTokens =
7910 3 : CSLTokenizeString2(osAccVal, " ,;", 0);
7911 3 : size_t nTokens = CSLCount(papszTokens);
7912 3 : if (nTokens >= nSize)
7913 : {
7914 : signed char *panVals = static_cast<signed char *>(
7915 3 : VSI_CALLOC_VERBOSE(nSize, sizeof(signed char)));
7916 3 : if (panVals)
7917 : {
7918 1203 : for (size_t i = 0; i < nSize; i++)
7919 : {
7920 1200 : panVals[i] = static_cast<signed char>(
7921 1200 : atoi(papszTokens[i]));
7922 : }
7923 3 : status = nc_put_vara_schar(nCdfId, nVarId,
7924 3 : &aoStart[0],
7925 3 : &aoEdge[0], panVals);
7926 3 : VSIFree(panVals);
7927 : }
7928 : }
7929 3 : CSLDestroy(papszTokens);
7930 : }
7931 : }
7932 6 : else if (oMapVarIdToType[nVarId] == NC_CHAR)
7933 : {
7934 6 : if (aoStart.size() == 2)
7935 : {
7936 4 : std::vector<CPLString> aoStrings;
7937 2 : bool bInString = false;
7938 4 : CPLString osCurString;
7939 935 : for (size_t i = 0; i < osAccVal.size();)
7940 : {
7941 933 : if (!bInString)
7942 : {
7943 8 : if (osAccVal[i] == '"')
7944 : {
7945 4 : bInString = true;
7946 4 : osCurString.clear();
7947 : }
7948 8 : i++;
7949 : }
7950 926 : else if (osAccVal[i] == '\\' &&
7951 926 : i + 1 < osAccVal.size() &&
7952 1 : osAccVal[i + 1] == '"')
7953 : {
7954 1 : osCurString += '"';
7955 1 : i += 2;
7956 : }
7957 924 : else if (osAccVal[i] == '"')
7958 : {
7959 4 : aoStrings.push_back(osCurString);
7960 4 : osCurString.clear();
7961 4 : bInString = false;
7962 4 : i++;
7963 : }
7964 : else
7965 : {
7966 920 : osCurString += osAccVal[i];
7967 920 : i++;
7968 : }
7969 : }
7970 2 : const size_t nRecords = oMapDimIdToDimLen[aoDimIds[0]];
7971 2 : const size_t nWidth = oMapDimIdToDimLen[aoDimIds[1]];
7972 2 : size_t nIters = aoStrings.size();
7973 2 : if (nIters > nRecords)
7974 0 : nIters = nRecords;
7975 6 : for (size_t i = 0; i < nIters; i++)
7976 : {
7977 : size_t anIndex[2];
7978 4 : anIndex[0] = i;
7979 4 : anIndex[1] = 0;
7980 : size_t anCount[2];
7981 4 : anCount[0] = 1;
7982 4 : anCount[1] = aoStrings[i].size();
7983 4 : if (anCount[1] > nWidth)
7984 0 : anCount[1] = nWidth;
7985 : status =
7986 4 : nc_put_vara_text(nCdfId, nVarId, anIndex,
7987 4 : anCount, aoStrings[i].c_str());
7988 4 : if (status != NC_NOERR)
7989 0 : break;
7990 : }
7991 : }
7992 : }
7993 17 : if (status != NC_NOERR)
7994 : {
7995 4 : CPLDebug("netCDF", "nc_put_var_(%s) failed: %s",
7996 : osVarName.c_str(), nc_strerror(status));
7997 : }
7998 : }
7999 : }
8000 : }
8001 :
8002 4 : GDAL_nc_close(nCdfId);
8003 4 : return true;
8004 : }
8005 :
8006 : #endif // ENABLE_NCDUMP
8007 :
8008 : /************************************************************************/
8009 : /* Open() */
8010 : /************************************************************************/
8011 :
8012 685 : GDALDataset *netCDFDataset::Open(GDALOpenInfo *poOpenInfo)
8013 :
8014 : {
8015 : #ifdef NCDF_DEBUG
8016 : CPLDebug("GDAL_netCDF", "\n=====\nOpen(), filename=[%s]",
8017 : poOpenInfo->pszFilename);
8018 : #endif
8019 :
8020 : // Does this appear to be a netcdf file?
8021 685 : NetCDFFormatEnum eTmpFormat = NCDF_FORMAT_NONE;
8022 685 : if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
8023 : {
8024 625 : eTmpFormat = netCDFIdentifyFormat(poOpenInfo, /* bCheckExt = */ true);
8025 : #ifdef NCDF_DEBUG
8026 : CPLDebug("GDAL_netCDF", "identified format %d", eTmpFormat);
8027 : #endif
8028 : // Note: not calling Identify() directly, because we want the file type.
8029 : // Only support NCDF_FORMAT* formats.
8030 625 : if (NCDF_FORMAT_NC == eTmpFormat || NCDF_FORMAT_NC2 == eTmpFormat ||
8031 2 : NCDF_FORMAT_NC4 == eTmpFormat || NCDF_FORMAT_NC4C == eTmpFormat)
8032 : {
8033 : // ok
8034 : }
8035 2 : else if (eTmpFormat == NCDF_FORMAT_HDF4 &&
8036 0 : poOpenInfo->IsSingleAllowedDriver("netCDF"))
8037 : {
8038 : // ok
8039 : }
8040 : else
8041 : {
8042 2 : return nullptr;
8043 : }
8044 : }
8045 : else
8046 : {
8047 : #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
8048 : // We don't necessarily want to catch bugs in libnetcdf ...
8049 : if (CPLGetConfigOption("DISABLE_OPEN_REAL_NETCDF_FILES", nullptr))
8050 : {
8051 : return nullptr;
8052 : }
8053 : #endif
8054 : }
8055 :
8056 683 : if (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER)
8057 : {
8058 186 : return OpenMultiDim(poOpenInfo);
8059 : }
8060 :
8061 994 : CPLMutexHolderD(&hNCMutex);
8062 :
8063 497 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
8064 : // GDALDataset own mutex.
8065 497 : netCDFDataset *poDS = new netCDFDataset();
8066 497 : poDS->papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
8067 497 : CPLAcquireMutex(hNCMutex, 1000.0);
8068 :
8069 497 : poDS->SetDescription(poOpenInfo->pszFilename);
8070 :
8071 : // Check if filename start with NETCDF: tag.
8072 497 : bool bTreatAsSubdataset = false;
8073 994 : CPLString osSubdatasetName;
8074 :
8075 : #ifdef ENABLE_NCDUMP
8076 497 : const char *pszHeader =
8077 : reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
8078 497 : if (poOpenInfo->fpL != nullptr && STARTS_WITH(pszHeader, "netcdf ") &&
8079 3 : strstr(pszHeader, "dimensions:") && strstr(pszHeader, "variables:"))
8080 : {
8081 : // By default create a temporary file that will be destroyed,
8082 : // unless NETCDF_TMP_FILE is defined. Can be useful to see which
8083 : // netCDF file has been generated from a potential fuzzed input.
8084 3 : poDS->osFilename = CPLGetConfigOption("NETCDF_TMP_FILE", "");
8085 3 : if (poDS->osFilename.empty())
8086 : {
8087 3 : poDS->bFileToDestroyAtClosing = true;
8088 3 : poDS->osFilename = CPLGenerateTempFilenameSafe("netcdf_tmp");
8089 : }
8090 3 : if (!netCDFDatasetCreateTempFile(eTmpFormat, poDS->osFilename,
8091 : poOpenInfo->fpL))
8092 : {
8093 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8094 : // deadlock with GDALDataset own mutex.
8095 0 : delete poDS;
8096 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8097 0 : return nullptr;
8098 : }
8099 3 : bTreatAsSubdataset = false;
8100 3 : poDS->eFormat = eTmpFormat;
8101 : }
8102 : else
8103 : #endif
8104 :
8105 494 : if (STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
8106 : {
8107 : char **papszName =
8108 60 : CSLTokenizeString2(poOpenInfo->pszFilename, ":",
8109 : CSLT_HONOURSTRINGS | CSLT_PRESERVEESCAPES);
8110 :
8111 120 : if (CSLCount(papszName) >= 3 &&
8112 60 : ((strlen(papszName[1]) == 1 && /* D:\\bla */
8113 0 : (papszName[2][0] == '/' || papszName[2][0] == '\\')) ||
8114 60 : EQUAL(papszName[1], "http") || EQUAL(papszName[1], "https") ||
8115 60 : EQUAL(papszName[1], "/vsicurl/http") ||
8116 60 : EQUAL(papszName[1], "/vsicurl/https") ||
8117 60 : EQUAL(papszName[1], "/vsicurl_streaming/http") ||
8118 60 : EQUAL(papszName[1], "/vsicurl_streaming/https")))
8119 : {
8120 0 : const int nCountBefore = CSLCount(papszName);
8121 0 : CPLString osTmp = papszName[1];
8122 0 : osTmp += ':';
8123 0 : osTmp += papszName[2];
8124 0 : CPLFree(papszName[1]);
8125 0 : CPLFree(papszName[2]);
8126 0 : papszName[1] = CPLStrdup(osTmp);
8127 0 : memmove(papszName + 2, papszName + 3,
8128 0 : (nCountBefore - 2) * sizeof(char *));
8129 : }
8130 :
8131 60 : if (CSLCount(papszName) == 3)
8132 : {
8133 60 : poDS->osFilename = papszName[1];
8134 60 : osSubdatasetName = papszName[2];
8135 60 : bTreatAsSubdataset = true;
8136 60 : CSLDestroy(papszName);
8137 : }
8138 0 : else if (CSLCount(papszName) == 2)
8139 : {
8140 0 : poDS->osFilename = papszName[1];
8141 0 : osSubdatasetName = "";
8142 0 : bTreatAsSubdataset = false;
8143 0 : CSLDestroy(papszName);
8144 : }
8145 : else
8146 : {
8147 0 : CSLDestroy(papszName);
8148 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8149 : // deadlock with GDALDataset own mutex.
8150 0 : delete poDS;
8151 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8152 0 : CPLError(CE_Failure, CPLE_AppDefined,
8153 : "Failed to parse NETCDF: prefix string into expected 2, 3 "
8154 : "or 4 fields.");
8155 0 : return nullptr;
8156 : }
8157 :
8158 120 : if (!STARTS_WITH(poDS->osFilename, "http://") &&
8159 60 : !STARTS_WITH(poDS->osFilename, "https://"))
8160 : {
8161 : // Identify Format from real file, with bCheckExt=FALSE.
8162 : GDALOpenInfo *poOpenInfo2 =
8163 60 : new GDALOpenInfo(poDS->osFilename.c_str(), GA_ReadOnly);
8164 60 : poDS->eFormat =
8165 60 : netCDFIdentifyFormat(poOpenInfo2, /* bCheckExt = */ false);
8166 60 : delete poOpenInfo2;
8167 60 : if (NCDF_FORMAT_NONE == poDS->eFormat ||
8168 60 : NCDF_FORMAT_UNKNOWN == poDS->eFormat)
8169 : {
8170 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8171 : // deadlock with GDALDataset own mutex.
8172 0 : delete poDS;
8173 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8174 0 : return nullptr;
8175 : }
8176 : }
8177 : }
8178 : else
8179 : {
8180 434 : poDS->osFilename = poOpenInfo->pszFilename;
8181 434 : bTreatAsSubdataset = false;
8182 434 : poDS->eFormat = eTmpFormat;
8183 : }
8184 :
8185 : // Try opening the dataset.
8186 : #if defined(NCDF_DEBUG) && defined(ENABLE_UFFD)
8187 : CPLDebug("GDAL_netCDF", "calling nc_open_mem(%s)",
8188 : poDS->osFilename.c_str());
8189 : #elif defined(NCDF_DEBUG) && !defined(ENABLE_UFFD)
8190 : CPLDebug("GDAL_netCDF", "calling nc_open(%s)", poDS->osFilename.c_str());
8191 : #endif
8192 497 : int cdfid = -1;
8193 497 : const int nMode = ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE) != 0)
8194 : ? NC_WRITE
8195 : : NC_NOWRITE;
8196 994 : CPLString osFilenameForNCOpen(poDS->osFilename);
8197 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
8198 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
8199 : {
8200 : char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
8201 : osFilenameForNCOpen = pszTemp;
8202 : CPLFree(pszTemp);
8203 : }
8204 : #endif
8205 497 : int status2 = -1;
8206 :
8207 : #ifdef ENABLE_UFFD
8208 497 : cpl_uffd_context *pCtx = nullptr;
8209 : #endif
8210 :
8211 512 : if (STARTS_WITH(osFilenameForNCOpen, "/vsimem/") &&
8212 15 : poOpenInfo->eAccess == GA_ReadOnly)
8213 : {
8214 15 : vsi_l_offset nLength = 0;
8215 15 : poDS->fpVSIMEM = VSIFOpenL(osFilenameForNCOpen, "rb");
8216 15 : if (poDS->fpVSIMEM)
8217 : {
8218 : // We assume that the file will not be modified. If it is, then
8219 : // pabyBuffer might become invalid.
8220 : GByte *pabyBuffer =
8221 15 : VSIGetMemFileBuffer(osFilenameForNCOpen, &nLength, false);
8222 15 : if (pabyBuffer)
8223 : {
8224 15 : status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen),
8225 : nMode, static_cast<size_t>(nLength),
8226 : pabyBuffer, &cdfid);
8227 : }
8228 : }
8229 : }
8230 : else
8231 : {
8232 : const bool bVsiFile =
8233 482 : !strncmp(osFilenameForNCOpen, "/vsi", strlen("/vsi"));
8234 : #ifdef ENABLE_UFFD
8235 482 : bool bReadOnly = (poOpenInfo->eAccess == GA_ReadOnly);
8236 482 : void *pVma = nullptr;
8237 482 : uint64_t nVmaSize = 0;
8238 :
8239 482 : if (bVsiFile)
8240 : {
8241 2 : if (bReadOnly)
8242 : {
8243 2 : if (CPLIsUserFaultMappingSupported())
8244 : {
8245 2 : pCtx = CPLCreateUserFaultMapping(osFilenameForNCOpen, &pVma,
8246 : &nVmaSize);
8247 : }
8248 : else
8249 : {
8250 0 : CPLError(CE_Failure, CPLE_AppDefined,
8251 : "Opening a /vsi file with the netCDF driver "
8252 : "requires Linux userfaultfd to be available. "
8253 : "If running from Docker, "
8254 : "--security-opt seccomp=unconfined might be "
8255 : "needed.%s",
8256 0 : ((poDS->eFormat == NCDF_FORMAT_NC4 ||
8257 0 : poDS->eFormat == NCDF_FORMAT_HDF5) &&
8258 0 : GDALGetDriverByName("HDF5"))
8259 : ? " Or you may set the GDAL_SKIP=netCDF "
8260 : "configuration option to force the use of "
8261 : "the HDF5 driver."
8262 : : "");
8263 : }
8264 : }
8265 : else
8266 : {
8267 0 : CPLError(CE_Failure, CPLE_AppDefined,
8268 : "Opening a /vsi file with the netCDF driver is only "
8269 : "supported in read-only mode");
8270 : }
8271 : }
8272 482 : if (pCtx != nullptr && pVma != nullptr && nVmaSize > 0)
8273 : {
8274 : // netCDF code, at least for netCDF 4.7.0, is confused by filenames
8275 : // like /vsicurl/http[s]://example.com/foo.nc, so just pass the
8276 : // final part
8277 2 : status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen), nMode,
8278 : static_cast<size_t>(nVmaSize), pVma, &cdfid);
8279 : }
8280 : else
8281 480 : status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
8282 : #else
8283 : if (bVsiFile)
8284 : {
8285 : CPLError(
8286 : CE_Failure, CPLE_AppDefined,
8287 : "Opening a /vsi file with the netCDF driver requires Linux "
8288 : "userfaultfd to be available.%s",
8289 : ((poDS->eFormat == NCDF_FORMAT_NC4 ||
8290 : poDS->eFormat == NCDF_FORMAT_HDF5) &&
8291 : GDALGetDriverByName("HDF5"))
8292 : ? " Or you may set the GDAL_SKIP=netCDF "
8293 : "configuration option to force the use of the HDF5 "
8294 : "driver."
8295 : : "");
8296 : status2 = NC_EIO;
8297 : }
8298 : else
8299 : {
8300 : status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
8301 : }
8302 : #endif
8303 : }
8304 497 : if (status2 != NC_NOERR)
8305 : {
8306 : #ifdef NCDF_DEBUG
8307 : CPLDebug("GDAL_netCDF", "error opening");
8308 : #endif
8309 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8310 : // with GDALDataset own mutex.
8311 0 : delete poDS;
8312 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8313 0 : return nullptr;
8314 : }
8315 : #ifdef NCDF_DEBUG
8316 : CPLDebug("GDAL_netCDF", "got cdfid=%d", cdfid);
8317 : #endif
8318 :
8319 : #if defined(ENABLE_NCDUMP) && !defined(_WIN32)
8320 : // Try to destroy the temporary file right now on Unix
8321 497 : if (poDS->bFileToDestroyAtClosing)
8322 : {
8323 3 : if (VSIUnlink(poDS->osFilename) == 0)
8324 : {
8325 3 : poDS->bFileToDestroyAtClosing = false;
8326 : }
8327 : }
8328 : #endif
8329 :
8330 : // Is this a real netCDF file?
8331 : int ndims;
8332 : int ngatts;
8333 : int nvars;
8334 : int unlimdimid;
8335 497 : int status = nc_inq(cdfid, &ndims, &nvars, &ngatts, &unlimdimid);
8336 497 : if (status != NC_NOERR)
8337 : {
8338 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8339 : // with GDALDataset own mutex.
8340 0 : delete poDS;
8341 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8342 0 : return nullptr;
8343 : }
8344 :
8345 : // Get file type from netcdf.
8346 497 : int nTmpFormat = NCDF_FORMAT_NONE;
8347 497 : status = nc_inq_format(cdfid, &nTmpFormat);
8348 497 : if (status != NC_NOERR)
8349 : {
8350 0 : NCDF_ERR(status);
8351 : }
8352 : else
8353 : {
8354 497 : CPLDebug("GDAL_netCDF",
8355 : "driver detected file type=%d, libnetcdf detected type=%d",
8356 497 : poDS->eFormat, nTmpFormat);
8357 497 : if (static_cast<NetCDFFormatEnum>(nTmpFormat) != poDS->eFormat)
8358 : {
8359 : // Warn if file detection conflicts with that from libnetcdf
8360 : // except for NC4C, which we have no way of detecting initially.
8361 26 : if (nTmpFormat != NCDF_FORMAT_NC4C &&
8362 13 : !STARTS_WITH(poDS->osFilename, "http://") &&
8363 0 : !STARTS_WITH(poDS->osFilename, "https://"))
8364 : {
8365 0 : CPLError(CE_Warning, CPLE_AppDefined,
8366 : "NetCDF driver detected file type=%d, but libnetcdf "
8367 : "detected type=%d",
8368 0 : poDS->eFormat, nTmpFormat);
8369 : }
8370 13 : CPLDebug("GDAL_netCDF", "setting file type to %d, was %d",
8371 13 : nTmpFormat, poDS->eFormat);
8372 13 : poDS->eFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
8373 : }
8374 : }
8375 :
8376 : // Does the request variable exist?
8377 497 : if (bTreatAsSubdataset)
8378 : {
8379 : int dummy;
8380 60 : if (NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &dummy,
8381 60 : &dummy) != CE_None)
8382 : {
8383 0 : CPLError(CE_Warning, CPLE_AppDefined,
8384 : "%s is a netCDF file, but %s is not a variable.",
8385 : poOpenInfo->pszFilename, osSubdatasetName.c_str());
8386 :
8387 0 : GDAL_nc_close(cdfid);
8388 : #ifdef ENABLE_UFFD
8389 0 : NETCDF_UFFD_UNMAP(pCtx);
8390 : #endif
8391 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8392 : // deadlock with GDALDataset own mutex.
8393 0 : delete poDS;
8394 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8395 0 : return nullptr;
8396 : }
8397 : }
8398 :
8399 : // Figure out whether or not the listed dataset has support for simple
8400 : // geometries (CF-1.8)
8401 497 : poDS->nCFVersion = nccfdriver::getCFVersion(cdfid);
8402 497 : bool bHasSimpleGeometries = false; // but not necessarily valid
8403 497 : if (poDS->nCFVersion >= 1.8)
8404 : {
8405 75 : bHasSimpleGeometries = poDS->DetectAndFillSGLayers(cdfid);
8406 75 : if (bHasSimpleGeometries)
8407 : {
8408 67 : poDS->bSGSupport = true;
8409 67 : poDS->vcdf.enableFullVirtualMode();
8410 : }
8411 : }
8412 :
8413 : char szConventions[NC_MAX_NAME + 1];
8414 497 : szConventions[0] = '\0';
8415 497 : nc_type nAttype = NC_NAT;
8416 497 : size_t nAttlen = 0;
8417 497 : nc_inq_att(cdfid, NC_GLOBAL, "Conventions", &nAttype, &nAttlen);
8418 994 : if (nAttlen >= sizeof(szConventions) ||
8419 497 : nc_get_att_text(cdfid, NC_GLOBAL, "Conventions", szConventions) !=
8420 : NC_NOERR)
8421 : {
8422 59 : CPLDebug("GDAL_netCDF", "No UNIDATA NC_GLOBAL:Conventions attribute");
8423 : // Note that 'Conventions' is always capital 'C' in CF spec.
8424 : }
8425 : else
8426 : {
8427 438 : szConventions[nAttlen] = '\0';
8428 : }
8429 :
8430 : // Create band information objects.
8431 497 : CPLDebug("GDAL_netCDF", "var_count = %d", nvars);
8432 :
8433 : // Create a corresponding GDALDataset.
8434 : // Create Netcdf Subdataset if filename as NETCDF tag.
8435 497 : poDS->cdfid = cdfid;
8436 : #ifdef ENABLE_UFFD
8437 497 : poDS->pCtx = pCtx;
8438 : #endif
8439 497 : poDS->eAccess = poOpenInfo->eAccess;
8440 497 : poDS->bDefineMode = false;
8441 :
8442 497 : poDS->ReadAttributes(cdfid, NC_GLOBAL);
8443 :
8444 : // Identify coordinate and boundary variables that we should
8445 : // ignore as Raster Bands.
8446 497 : char **papszIgnoreVars = nullptr;
8447 497 : NCDFGetCoordAndBoundVarFullNames(cdfid, &papszIgnoreVars);
8448 : // Filter variables to keep only valid 2+D raster bands and vector fields.
8449 497 : int nRasterVars = 0;
8450 497 : int nIgnoredVars = 0;
8451 497 : int nGroupID = -1;
8452 497 : int nVarID = -1;
8453 :
8454 : std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>
8455 994 : oMap2DDimsToGroupAndVar;
8456 1147 : if ((poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
8457 153 : STARTS_WITH(CSLFetchNameValueDef(poDS->papszMetadata,
8458 : "NC_GLOBAL#mission_name", ""),
8459 1 : "Sentinel 3") &&
8460 1 : EQUAL(CSLFetchNameValueDef(poDS->papszMetadata,
8461 : "NC_GLOBAL#altimeter_sensor_name", ""),
8462 650 : "SRAL") &&
8463 1 : EQUAL(CSLFetchNameValueDef(poDS->papszMetadata,
8464 : "NC_GLOBAL#radiometer_sensor_name", ""),
8465 : "MWR"))
8466 : {
8467 1 : if (poDS->eAccess == GA_Update)
8468 : {
8469 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8470 : // deadlock with GDALDataset own mutex.
8471 0 : delete poDS;
8472 0 : return nullptr;
8473 : }
8474 1 : poDS->ProcessSentinel3_SRAL_MWR();
8475 : }
8476 : else
8477 : {
8478 496 : poDS->FilterVars(cdfid, (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0,
8479 648 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
8480 152 : !bHasSimpleGeometries,
8481 : papszIgnoreVars, &nRasterVars, &nGroupID, &nVarID,
8482 : &nIgnoredVars, oMap2DDimsToGroupAndVar);
8483 : }
8484 497 : CSLDestroy(papszIgnoreVars);
8485 :
8486 497 : const bool bListAllArrays = CPLTestBool(
8487 497 : CSLFetchNameValueDef(poDS->papszOpenOptions, "LIST_ALL_ARRAYS", "NO"));
8488 :
8489 : // Case where there is no raster variable
8490 497 : if (!bListAllArrays && nRasterVars == 0 && !bTreatAsSubdataset)
8491 : {
8492 119 : poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
8493 119 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8494 : // with GDALDataset own mutex.
8495 119 : poDS->TryLoadXML();
8496 : // If the dataset has been opened in raster mode only, exit
8497 119 : if ((poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0 &&
8498 9 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) == 0)
8499 : {
8500 4 : delete poDS;
8501 4 : poDS = nullptr;
8502 : }
8503 : // Otherwise if the dataset is opened in vector mode, that there is
8504 : // no vector layer and we are in read-only, exit too.
8505 115 : else if (poDS->GetLayerCount() == 0 &&
8506 123 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
8507 8 : poOpenInfo->eAccess == GA_ReadOnly)
8508 : {
8509 8 : delete poDS;
8510 8 : poDS = nullptr;
8511 : }
8512 119 : CPLAcquireMutex(hNCMutex, 1000.0);
8513 119 : return poDS;
8514 : }
8515 :
8516 : // We have more than one variable with 2 dimensions in the
8517 : // file, then treat this as a subdataset container dataset.
8518 378 : bool bSeveralVariablesAsBands = false;
8519 378 : if (bListAllArrays || ((nRasterVars > 1) && !bTreatAsSubdataset))
8520 : {
8521 29 : if (CPLFetchBool(poOpenInfo->papszOpenOptions, "VARIABLES_AS_BANDS",
8522 35 : false) &&
8523 6 : oMap2DDimsToGroupAndVar.size() == 1)
8524 : {
8525 6 : std::tie(nGroupID, nVarID) =
8526 12 : oMap2DDimsToGroupAndVar.begin()->second.front();
8527 6 : bSeveralVariablesAsBands = true;
8528 : }
8529 : else
8530 : {
8531 23 : poDS->CreateSubDatasetList(cdfid);
8532 23 : poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
8533 23 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8534 : // deadlock with GDALDataset own mutex.
8535 23 : poDS->TryLoadXML();
8536 23 : CPLAcquireMutex(hNCMutex, 1000.0);
8537 23 : return poDS;
8538 : }
8539 : }
8540 :
8541 : // If we are not treating things as a subdataset, then capture
8542 : // the name of the single available variable as the subdataset.
8543 355 : if (!bTreatAsSubdataset)
8544 : {
8545 295 : char *pszVarName = nullptr;
8546 295 : NCDF_ERR(NCDFGetVarFullName(nGroupID, nVarID, &pszVarName));
8547 295 : osSubdatasetName = (pszVarName != nullptr ? pszVarName : "");
8548 295 : CPLFree(pszVarName);
8549 : }
8550 :
8551 : // We have ignored at least one variable, so we should report them
8552 : // as subdatasets for reference.
8553 355 : if (nIgnoredVars > 0 && !bTreatAsSubdataset)
8554 : {
8555 25 : CPLDebug("GDAL_netCDF",
8556 : "As %d variables were ignored, creating subdataset list "
8557 : "for reference. Variable #%d [%s] is the main variable",
8558 : nIgnoredVars, nVarID, osSubdatasetName.c_str());
8559 25 : poDS->CreateSubDatasetList(cdfid);
8560 : }
8561 :
8562 : // Open the NETCDF subdataset NETCDF:"filename":subdataset.
8563 355 : int var = -1;
8564 355 : NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &nGroupID, &var);
8565 : // Now we can forget the root cdfid and only use the selected group.
8566 355 : cdfid = nGroupID;
8567 355 : int nd = 0;
8568 355 : nc_inq_varndims(cdfid, var, &nd);
8569 :
8570 355 : poDS->m_anDimIds.resize(nd);
8571 :
8572 : // X, Y, Z position in array
8573 710 : std::vector<int> anBandDimPos(nd);
8574 :
8575 355 : nc_inq_vardimid(cdfid, var, poDS->m_anDimIds.data());
8576 :
8577 : // Check if somebody tried to pass a variable with less than 1D.
8578 355 : if (nd < 1)
8579 : {
8580 0 : CPLError(CE_Warning, CPLE_AppDefined,
8581 : "Variable has %d dimension(s) - not supported.", nd);
8582 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8583 : // with GDALDataset own mutex.
8584 0 : delete poDS;
8585 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8586 0 : return nullptr;
8587 : }
8588 :
8589 : // CF-1 Convention
8590 : //
8591 : // Dimensions to appear in the relative order T, then Z, then Y,
8592 : // then X to the file. All other dimensions should, whenever
8593 : // possible, be placed to the left of the spatiotemporal
8594 : // dimensions.
8595 :
8596 : // Verify that dimensions are in the {T,Z,Y,X} or {T,Z,Y,X} order
8597 : // Ideally we should detect for other ordering and act accordingly
8598 : // Only done if file has Conventions=CF-* and only prints warning
8599 : // To disable set GDAL_NETCDF_VERIFY_DIMS=NO and to use only
8600 : // attributes (not varnames) set GDAL_NETCDF_VERIFY_DIMS=STRICT
8601 : const bool bCheckDims =
8602 710 : CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES")) &&
8603 355 : STARTS_WITH_CI(szConventions, "CF");
8604 :
8605 355 : bool bYXBandOrder = false;
8606 355 : if (nd == 3)
8607 : {
8608 : // If there's a coordinates attributes, and the variable it points to
8609 : // are 2D variables indexed by the same first and second dimension than
8610 : // our variable of interest, then it is Y,X,Band order.
8611 46 : char *pszCoordinates = nullptr;
8612 46 : if (NCDFGetAttr(cdfid, var, "coordinates", &pszCoordinates) ==
8613 63 : CE_None &&
8614 17 : pszCoordinates)
8615 : {
8616 : const CPLStringList aosCoordinates(
8617 34 : NCDFTokenizeCoordinatesAttribute(pszCoordinates));
8618 17 : if (aosCoordinates.size() == 2)
8619 : {
8620 : // Test that each variable is longitude/latitude.
8621 13 : for (int i = 0; i < aosCoordinates.size(); i++)
8622 : {
8623 13 : if (NCDFIsVarLongitude(cdfid, -1, aosCoordinates[i]) ||
8624 4 : NCDFIsVarLatitude(cdfid, -1, aosCoordinates[i]))
8625 : {
8626 9 : int nOtherGroupId = -1;
8627 9 : int nOtherVarId = -1;
8628 9 : if (NCDFResolveVar(cdfid, aosCoordinates[i],
8629 : &nOtherGroupId,
8630 9 : &nOtherVarId) == CE_None)
8631 : {
8632 9 : int coordDimCount = 0;
8633 9 : nc_inq_varndims(nOtherGroupId, nOtherVarId,
8634 : &coordDimCount);
8635 9 : if (coordDimCount == 2)
8636 : {
8637 3 : int coordDimIds[2] = {0, 0};
8638 3 : nc_inq_vardimid(nOtherGroupId, nOtherVarId,
8639 : coordDimIds);
8640 4 : if (coordDimIds[0] == poDS->m_anDimIds[0] &&
8641 1 : coordDimIds[1] == poDS->m_anDimIds[1])
8642 : {
8643 1 : bYXBandOrder = true;
8644 1 : break;
8645 : }
8646 : }
8647 : }
8648 : }
8649 : }
8650 : }
8651 : }
8652 46 : CPLFree(pszCoordinates);
8653 :
8654 46 : if (!bYXBandOrder)
8655 : {
8656 45 : char szDim0Name[NC_MAX_NAME + 1] = {};
8657 45 : char szDim1Name[NC_MAX_NAME + 1] = {};
8658 45 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[0], szDim0Name);
8659 45 : NCDF_ERR(status);
8660 45 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[1], szDim1Name);
8661 45 : NCDF_ERR(status);
8662 :
8663 45 : if (strcmp(szDim0Name, "number_of_lines") == 0 &&
8664 1 : strcmp(szDim1Name, "pixels_per_line") == 0)
8665 : {
8666 : // Like in PACE OCI products
8667 1 : bYXBandOrder = true;
8668 : }
8669 : else
8670 : {
8671 : // For example for EMIT data (https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/),
8672 : // dimension order is downtrack, crosstrack, bands
8673 44 : char szDim2Name[NC_MAX_NAME + 1] = {};
8674 44 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[2], szDim2Name);
8675 44 : NCDF_ERR(status);
8676 86 : bYXBandOrder = strcmp(szDim2Name, "bands") == 0 ||
8677 42 : strcmp(szDim2Name, "band") == 0;
8678 : }
8679 : }
8680 : }
8681 :
8682 355 : if (nd >= 2 && bCheckDims && !bYXBandOrder)
8683 : {
8684 268 : char szDimName1[NC_MAX_NAME + 1] = {};
8685 268 : char szDimName2[NC_MAX_NAME + 1] = {};
8686 268 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 1], szDimName1);
8687 268 : NCDF_ERR(status);
8688 268 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 2], szDimName2);
8689 268 : NCDF_ERR(status);
8690 427 : if (NCDFIsVarLongitude(cdfid, -1, szDimName1) == false &&
8691 159 : NCDFIsVarProjectionX(cdfid, -1, szDimName1) == false)
8692 : {
8693 4 : CPLError(CE_Warning, CPLE_AppDefined,
8694 : "dimension #%d (%s) is not a Longitude/X dimension.",
8695 : nd - 1, szDimName1);
8696 : }
8697 427 : if (NCDFIsVarLatitude(cdfid, -1, szDimName2) == false &&
8698 159 : NCDFIsVarProjectionY(cdfid, -1, szDimName2) == false)
8699 : {
8700 4 : CPLError(CE_Warning, CPLE_AppDefined,
8701 : "dimension #%d (%s) is not a Latitude/Y dimension.",
8702 : nd - 2, szDimName2);
8703 : }
8704 268 : if ((NCDFIsVarLongitude(cdfid, -1, szDimName2) ||
8705 270 : NCDFIsVarProjectionX(cdfid, -1, szDimName2)) &&
8706 2 : (NCDFIsVarLatitude(cdfid, -1, szDimName1) ||
8707 0 : NCDFIsVarProjectionY(cdfid, -1, szDimName1)))
8708 : {
8709 2 : poDS->bSwitchedXY = true;
8710 : }
8711 268 : if (nd >= 3)
8712 : {
8713 52 : char szDimName3[NC_MAX_NAME + 1] = {};
8714 : status =
8715 52 : nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 3], szDimName3);
8716 52 : NCDF_ERR(status);
8717 52 : if (nd >= 4)
8718 : {
8719 13 : char szDimName4[NC_MAX_NAME + 1] = {};
8720 : status =
8721 13 : nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 4], szDimName4);
8722 13 : NCDF_ERR(status);
8723 13 : if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false)
8724 : {
8725 0 : CPLError(CE_Warning, CPLE_AppDefined,
8726 : "dimension #%d (%s) is not a Vertical dimension.",
8727 : nd - 3, szDimName3);
8728 : }
8729 13 : if (NCDFIsVarTimeCoord(cdfid, -1, szDimName4) == false)
8730 : {
8731 0 : CPLError(CE_Warning, CPLE_AppDefined,
8732 : "dimension #%d (%s) is not a Time dimension.",
8733 : nd - 4, szDimName4);
8734 : }
8735 : }
8736 : else
8737 : {
8738 75 : if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false &&
8739 36 : NCDFIsVarTimeCoord(cdfid, -1, szDimName3) == false)
8740 : {
8741 0 : CPLError(CE_Warning, CPLE_AppDefined,
8742 : "dimension #%d (%s) is not a "
8743 : "Time or Vertical dimension.",
8744 : nd - 3, szDimName3);
8745 : }
8746 : }
8747 : }
8748 : }
8749 :
8750 : // Get X dimensions information.
8751 : size_t xdim;
8752 355 : poDS->nXDimID = poDS->m_anDimIds[bYXBandOrder ? 1 : nd - 1];
8753 355 : nc_inq_dimlen(cdfid, poDS->nXDimID, &xdim);
8754 :
8755 : // Get Y dimension information.
8756 : size_t ydim;
8757 355 : if (nd >= 2)
8758 : {
8759 351 : poDS->nYDimID = poDS->m_anDimIds[bYXBandOrder ? 0 : nd - 2];
8760 351 : nc_inq_dimlen(cdfid, poDS->nYDimID, &ydim);
8761 : }
8762 : else
8763 : {
8764 4 : poDS->nYDimID = -1;
8765 4 : ydim = 1;
8766 : }
8767 :
8768 355 : if (xdim > INT_MAX || ydim > INT_MAX)
8769 : {
8770 0 : CPLError(CE_Failure, CPLE_AppDefined,
8771 : "Invalid raster dimensions: " CPL_FRMT_GUIB "x" CPL_FRMT_GUIB,
8772 : static_cast<GUIntBig>(xdim), static_cast<GUIntBig>(ydim));
8773 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8774 : // with GDALDataset own mutex.
8775 0 : delete poDS;
8776 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8777 0 : return nullptr;
8778 : }
8779 :
8780 355 : poDS->nRasterXSize = static_cast<int>(xdim);
8781 355 : poDS->nRasterYSize = static_cast<int>(ydim);
8782 :
8783 355 : unsigned int k = 0;
8784 1142 : for (int j = 0; j < nd; j++)
8785 : {
8786 787 : if (poDS->m_anDimIds[j] == poDS->nXDimID)
8787 : {
8788 355 : anBandDimPos[0] = j; // Save Position of XDim
8789 355 : k++;
8790 : }
8791 787 : if (poDS->m_anDimIds[j] == poDS->nYDimID)
8792 : {
8793 351 : anBandDimPos[1] = j; // Save Position of YDim
8794 351 : k++;
8795 : }
8796 : }
8797 : // X and Y Dimension Ids were not found!
8798 355 : if ((nd >= 2 && k != 2) || (nd == 1 && k != 1))
8799 : {
8800 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8801 : // with GDALDataset own mutex.
8802 0 : delete poDS;
8803 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8804 0 : return nullptr;
8805 : }
8806 :
8807 : // Read Metadata for this variable.
8808 :
8809 : // Should disable as is also done at band level, except driver needs the
8810 : // variables as metadata (e.g. projection).
8811 355 : poDS->ReadAttributes(cdfid, var);
8812 :
8813 : // Read Metadata for each dimension.
8814 355 : int *panDimIds = nullptr;
8815 355 : NCDFGetVisibleDims(cdfid, &ndims, &panDimIds);
8816 : // With NetCDF-4 groups panDimIds is not always [0..dim_count-1] like
8817 : // in NetCDF-3 because we see only the dimensions of the selected group
8818 : // and its parents.
8819 : // poDS->papszDimName is indexed by dim IDs, so it must contains all IDs
8820 : // [0..max(panDimIds)], but they are not all useful so we fill names
8821 : // of useless dims with empty string.
8822 355 : if (panDimIds)
8823 : {
8824 355 : const int nMaxDimId = *std::max_element(panDimIds, panDimIds + ndims);
8825 355 : std::set<int> oSetExistingDimIds;
8826 1182 : for (int i = 0; i < ndims; i++)
8827 : {
8828 827 : oSetExistingDimIds.insert(panDimIds[i]);
8829 : }
8830 355 : std::set<int> oSetDimIdsUsedByVar;
8831 1142 : for (int i = 0; i < nd; i++)
8832 : {
8833 787 : oSetDimIdsUsedByVar.insert(poDS->m_anDimIds[i]);
8834 : }
8835 1184 : for (int j = 0; j <= nMaxDimId; j++)
8836 : {
8837 : // Is j dim used?
8838 829 : if (oSetExistingDimIds.find(j) != oSetExistingDimIds.end())
8839 : {
8840 : // Useful dim.
8841 827 : char szTemp[NC_MAX_NAME + 1] = {};
8842 827 : status = nc_inq_dimname(cdfid, j, szTemp);
8843 827 : if (status != NC_NOERR)
8844 : {
8845 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8846 : // deadlock with GDALDataset own
8847 : // mutex.
8848 0 : delete poDS;
8849 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8850 0 : return nullptr;
8851 : }
8852 827 : poDS->papszDimName.AddString(szTemp);
8853 :
8854 827 : if (oSetDimIdsUsedByVar.find(j) != oSetDimIdsUsedByVar.end())
8855 : {
8856 787 : int nDimGroupId = -1;
8857 787 : int nDimVarId = -1;
8858 787 : if (NCDFResolveVar(cdfid, poDS->papszDimName[j],
8859 787 : &nDimGroupId, &nDimVarId) == CE_None)
8860 : {
8861 583 : poDS->ReadAttributes(nDimGroupId, nDimVarId);
8862 : }
8863 : }
8864 : }
8865 : else
8866 : {
8867 : // Useless dim.
8868 2 : poDS->papszDimName.AddString("");
8869 : }
8870 : }
8871 355 : CPLFree(panDimIds);
8872 : }
8873 :
8874 : // Set projection info.
8875 710 : std::vector<std::string> aosRemovedMDItems;
8876 355 : if (nd > 1)
8877 : {
8878 351 : poDS->SetProjectionFromVar(cdfid, var,
8879 : /*bReadSRSOnly=*/false,
8880 : /* pszGivenGM = */ nullptr,
8881 : /* returnProjStr = */ nullptr,
8882 : /* sg = */ nullptr, &aosRemovedMDItems);
8883 : }
8884 :
8885 : // Override bottom-up with GDAL_NETCDF_BOTTOMUP config option.
8886 355 : const char *pszValue = CPLGetConfigOption("GDAL_NETCDF_BOTTOMUP", nullptr);
8887 355 : if (pszValue)
8888 : {
8889 24 : poDS->bBottomUp = CPLTestBool(pszValue);
8890 24 : CPLDebug("GDAL_netCDF",
8891 : "set bBottomUp=%d because GDAL_NETCDF_BOTTOMUP=%s",
8892 24 : static_cast<int>(poDS->bBottomUp), pszValue);
8893 : }
8894 :
8895 : // Save non-spatial dimension info.
8896 :
8897 355 : int *panBandZLev = nullptr;
8898 355 : int nDim = (nd >= 2) ? 2 : 1;
8899 : size_t lev_count;
8900 355 : size_t nTotLevCount = 1;
8901 355 : nc_type nType = NC_NAT;
8902 :
8903 710 : CPLString osExtraDimNames;
8904 :
8905 355 : if (nd > 2)
8906 : {
8907 62 : nDim = 2;
8908 62 : panBandZLev = static_cast<int *>(CPLCalloc(nd - 2, sizeof(int)));
8909 :
8910 62 : osExtraDimNames = "{";
8911 :
8912 62 : char szDimName[NC_MAX_NAME + 1] = {};
8913 :
8914 62 : bool bREPORT_EXTRA_DIM_VALUESWarningEmitted = false;
8915 267 : for (int j = 0; j < nd; j++)
8916 : {
8917 348 : if ((poDS->m_anDimIds[j] != poDS->nXDimID) &&
8918 143 : (poDS->m_anDimIds[j] != poDS->nYDimID))
8919 : {
8920 81 : nc_inq_dimlen(cdfid, poDS->m_anDimIds[j], &lev_count);
8921 81 : nTotLevCount *= lev_count;
8922 81 : panBandZLev[nDim - 2] = static_cast<int>(lev_count);
8923 81 : anBandDimPos[nDim] = j; // Save Position of ZDim
8924 : // Save non-spatial dimension names.
8925 81 : if (nc_inq_dimname(cdfid, poDS->m_anDimIds[j], szDimName) ==
8926 : NC_NOERR)
8927 : {
8928 81 : osExtraDimNames += szDimName;
8929 81 : if (j < nd - 3)
8930 : {
8931 19 : osExtraDimNames += ",";
8932 : }
8933 :
8934 81 : int nIdxGroupID = -1;
8935 81 : int nIdxVarID = Get1DVariableIndexedByDimension(
8936 81 : cdfid, poDS->m_anDimIds[j], szDimName, true,
8937 81 : &nIdxGroupID);
8938 81 : poDS->m_anExtraDimGroupIds.push_back(nIdxGroupID);
8939 81 : poDS->m_anExtraDimVarIds.push_back(nIdxVarID);
8940 :
8941 81 : if (nIdxVarID >= 0)
8942 : {
8943 72 : nc_inq_vartype(nIdxGroupID, nIdxVarID, &nType);
8944 : char szExtraDimDef[NC_MAX_NAME + 1];
8945 72 : snprintf(szExtraDimDef, sizeof(szExtraDimDef),
8946 : "{%ld,%d}", (long)lev_count, nType);
8947 : char szTemp[NC_MAX_NAME + 32 + 1];
8948 72 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
8949 : szDimName);
8950 72 : poDS->papszMetadata = CSLSetNameValue(
8951 : poDS->papszMetadata, szTemp, szExtraDimDef);
8952 :
8953 : // Retrieving data for unlimited dimensions might be
8954 : // costly on network storage, so don't do it.
8955 : // Each band will capture the value along the extra
8956 : // dimension in its NETCDF_DIM_xxxx band metadata item
8957 : // Addresses use case of
8958 : // https://lists.osgeo.org/pipermail/gdal-dev/2023-May/057209.html
8959 : const bool bIsLocal =
8960 72 : VSIIsLocal(osFilenameForNCOpen.c_str());
8961 : bool bListDimValues =
8962 73 : bIsLocal || lev_count == 1 ||
8963 1 : !NCDFIsUnlimitedDim(poDS->eFormat ==
8964 : NCDF_FORMAT_NC4,
8965 1 : cdfid, poDS->m_anDimIds[j]);
8966 : const char *pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES =
8967 72 : CPLGetConfigOption(
8968 : "GDAL_NETCDF_REPORT_EXTRA_DIM_VALUES", nullptr);
8969 72 : if (pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES)
8970 : {
8971 2 : bListDimValues = CPLTestBool(
8972 : pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES);
8973 : }
8974 70 : else if (!bListDimValues && !bIsLocal &&
8975 1 : !bREPORT_EXTRA_DIM_VALUESWarningEmitted)
8976 : {
8977 1 : bREPORT_EXTRA_DIM_VALUESWarningEmitted = true;
8978 1 : CPLDebug(
8979 : "GDAL_netCDF",
8980 : "Listing extra dimension values is skipped "
8981 : "because this dataset is hosted on a network "
8982 : "file system, and such an operation could be "
8983 : "slow. If you still want to proceed, set the "
8984 : "GDAL_NETCDF_REPORT_EXTRA_DIM_VALUES "
8985 : "configuration option to YES");
8986 : }
8987 72 : if (bListDimValues)
8988 : {
8989 70 : char *pszTemp = nullptr;
8990 70 : if (NCDFGet1DVar(nIdxGroupID, nIdxVarID,
8991 70 : &pszTemp) == CE_None)
8992 : {
8993 70 : snprintf(szTemp, sizeof(szTemp),
8994 : "NETCDF_DIM_%s_VALUES", szDimName);
8995 70 : poDS->papszMetadata = CSLSetNameValue(
8996 : poDS->papszMetadata, szTemp, pszTemp);
8997 70 : CPLFree(pszTemp);
8998 : }
8999 : }
9000 : }
9001 : }
9002 : else
9003 : {
9004 0 : poDS->m_anExtraDimGroupIds.push_back(-1);
9005 0 : poDS->m_anExtraDimVarIds.push_back(-1);
9006 : }
9007 :
9008 81 : nDim++;
9009 : }
9010 : }
9011 62 : osExtraDimNames += "}";
9012 62 : poDS->papszMetadata = CSLSetNameValue(
9013 : poDS->papszMetadata, "NETCDF_DIM_EXTRA", osExtraDimNames);
9014 : }
9015 :
9016 : // Store Metadata.
9017 365 : for (const auto &osStr : aosRemovedMDItems)
9018 10 : poDS->papszMetadata =
9019 10 : CSLSetNameValue(poDS->papszMetadata, osStr.c_str(), nullptr);
9020 :
9021 355 : poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
9022 :
9023 : // Create bands.
9024 :
9025 : // Arbitrary threshold.
9026 : int nMaxBandCount =
9027 355 : atoi(CPLGetConfigOption("GDAL_MAX_BAND_COUNT", "32768"));
9028 355 : if (nMaxBandCount <= 0)
9029 0 : nMaxBandCount = 32768;
9030 355 : if (nTotLevCount > static_cast<unsigned int>(nMaxBandCount))
9031 : {
9032 0 : CPLError(CE_Warning, CPLE_AppDefined,
9033 : "Limiting number of bands to %d instead of %u", nMaxBandCount,
9034 : static_cast<unsigned int>(nTotLevCount));
9035 0 : nTotLevCount = static_cast<unsigned int>(nMaxBandCount);
9036 : }
9037 355 : if (poDS->nRasterXSize == 0 || poDS->nRasterYSize == 0)
9038 : {
9039 0 : poDS->nRasterXSize = 0;
9040 0 : poDS->nRasterYSize = 0;
9041 0 : nTotLevCount = 0;
9042 0 : if (poDS->GetLayerCount() == 0)
9043 : {
9044 0 : CPLFree(panBandZLev);
9045 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9046 : // deadlock with GDALDataset own mutex.
9047 0 : delete poDS;
9048 0 : CPLAcquireMutex(hNCMutex, 1000.0);
9049 0 : return nullptr;
9050 : }
9051 : }
9052 355 : if (bSeveralVariablesAsBands)
9053 : {
9054 6 : const auto &listVariables = oMap2DDimsToGroupAndVar.begin()->second;
9055 24 : for (int iBand = 0; iBand < static_cast<int>(listVariables.size());
9056 : ++iBand)
9057 : {
9058 18 : int bandVarGroupId = listVariables[iBand].first;
9059 18 : int bandVarId = listVariables[iBand].second;
9060 : netCDFRasterBand *poBand = new netCDFRasterBand(
9061 0 : netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, bandVarGroupId,
9062 18 : bandVarId, nDim, 0, nullptr, anBandDimPos.data(), iBand + 1);
9063 18 : poDS->SetBand(iBand + 1, poBand);
9064 : }
9065 : }
9066 : else
9067 : {
9068 812 : for (unsigned int lev = 0; lev < nTotLevCount; lev++)
9069 : {
9070 : netCDFRasterBand *poBand = new netCDFRasterBand(
9071 0 : netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, cdfid, var, nDim,
9072 463 : lev, panBandZLev, anBandDimPos.data(), lev + 1);
9073 463 : poDS->SetBand(lev + 1, poBand);
9074 : }
9075 : }
9076 :
9077 355 : if (panBandZLev)
9078 62 : CPLFree(panBandZLev);
9079 : // Handle angular geographic coordinates here
9080 :
9081 : // Initialize any PAM information.
9082 355 : if (bTreatAsSubdataset)
9083 : {
9084 60 : poDS->SetPhysicalFilename(poDS->osFilename);
9085 60 : poDS->SetSubdatasetName(osSubdatasetName);
9086 : }
9087 :
9088 355 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
9089 : // GDALDataset own mutex.
9090 355 : poDS->TryLoadXML();
9091 :
9092 355 : if (bTreatAsSubdataset)
9093 60 : poDS->oOvManager.Initialize(poDS, ":::VIRTUAL:::");
9094 : else
9095 295 : poDS->oOvManager.Initialize(poDS, poDS->osFilename);
9096 :
9097 355 : CPLAcquireMutex(hNCMutex, 1000.0);
9098 :
9099 355 : return poDS;
9100 : }
9101 :
9102 : /************************************************************************/
9103 : /* CopyMetadata() */
9104 : /* */
9105 : /* Create a copy of metadata for NC_GLOBAL or a variable */
9106 : /************************************************************************/
9107 :
9108 157 : static void CopyMetadata(GDALDataset *poSrcDS, GDALRasterBand *poSrcBand,
9109 : GDALRasterBand *poDstBand, int nCdfId, int CDFVarID,
9110 : const char *pszPrefix)
9111 : {
9112 : // Remove the following band meta but set them later from band data.
9113 157 : const char *const papszIgnoreBand[] = {
9114 : CF_ADD_OFFSET, CF_SCALE_FACTOR, "valid_range", "_Unsigned",
9115 : NCDF_FillValue, "coordinates", nullptr};
9116 157 : const char *const papszIgnoreGlobal[] = {"NETCDF_DIM_EXTRA", nullptr};
9117 :
9118 157 : CSLConstList papszMetadata = nullptr;
9119 157 : if (poSrcDS)
9120 : {
9121 66 : papszMetadata = poSrcDS->GetMetadata();
9122 : }
9123 91 : else if (poSrcBand)
9124 : {
9125 91 : papszMetadata = poSrcBand->GetMetadata();
9126 : }
9127 :
9128 637 : for (const auto &[pszKey, pszValue] : cpl::IterateNameValue(papszMetadata))
9129 : {
9130 : #ifdef NCDF_DEBUG
9131 : CPLDebug("GDAL_netCDF", "copy metadata [%s]=[%s]", pszKey, pszValue);
9132 : #endif
9133 :
9134 480 : CPLString osMetaName(pszKey);
9135 :
9136 : // Check for items that match pszPrefix if applicable.
9137 480 : if (pszPrefix && !EQUAL(pszPrefix, ""))
9138 : {
9139 : // Remove prefix.
9140 115 : if (STARTS_WITH(osMetaName.c_str(), pszPrefix))
9141 : {
9142 17 : osMetaName = osMetaName.substr(strlen(pszPrefix));
9143 : }
9144 : // Only copy items that match prefix.
9145 : else
9146 : {
9147 98 : continue;
9148 : }
9149 : }
9150 :
9151 : // Fix various issues with metadata translation.
9152 382 : if (CDFVarID == NC_GLOBAL)
9153 : {
9154 : // Do not copy items in papszIgnoreGlobal and NETCDF_DIM_*.
9155 481 : if ((CSLFindString(papszIgnoreGlobal, osMetaName) != -1) ||
9156 238 : (STARTS_WITH(osMetaName, "NETCDF_DIM_")))
9157 21 : continue;
9158 : // Remove NC_GLOBAL prefix for netcdf global Metadata.
9159 222 : else if (STARTS_WITH(osMetaName, "NC_GLOBAL#"))
9160 : {
9161 33 : osMetaName = osMetaName.substr(strlen("NC_GLOBAL#"));
9162 : }
9163 : // GDAL Metadata renamed as GDAL-[meta].
9164 189 : else if (strstr(osMetaName, "#") == nullptr)
9165 : {
9166 16 : osMetaName = "GDAL_" + osMetaName;
9167 : }
9168 : // Keep time, lev and depth information for safe-keeping.
9169 : // Time and vertical coordinate handling need improvements.
9170 : /*
9171 : else if( STARTS_WITH(szMetaName, "time#") )
9172 : {
9173 : szMetaName[4] = '-';
9174 : }
9175 : else if( STARTS_WITH(szMetaName, "lev#") )
9176 : {
9177 : szMetaName[3] = '-';
9178 : }
9179 : else if( STARTS_WITH(szMetaName, "depth#") )
9180 : {
9181 : szMetaName[5] = '-';
9182 : }
9183 : */
9184 : // Only copy data without # (previously all data was copied).
9185 222 : if (strstr(osMetaName, "#") != nullptr)
9186 173 : continue;
9187 : // netCDF attributes do not like the '#' character.
9188 : // for( unsigned int h=0; h < strlen(szMetaName) -1 ; h++ ) {
9189 : // if( szMetaName[h] == '#') szMetaName[h] = '-';
9190 : // }
9191 : }
9192 : else
9193 : {
9194 : // Do not copy varname, stats, NETCDF_DIM_*, nodata
9195 : // and items in papszIgnoreBand.
9196 139 : if (STARTS_WITH(osMetaName, "NETCDF_VARNAME") ||
9197 107 : STARTS_WITH(osMetaName, "STATISTICS_") ||
9198 107 : STARTS_WITH(osMetaName, "NETCDF_DIM_") ||
9199 74 : STARTS_WITH(osMetaName, "missing_value") ||
9200 293 : STARTS_WITH(osMetaName, "_FillValue") ||
9201 47 : CSLFindString(papszIgnoreBand, osMetaName) != -1)
9202 97 : continue;
9203 : }
9204 :
9205 : #ifdef NCDF_DEBUG
9206 : CPLDebug("GDAL_netCDF", "copy name=[%s] value=[%s]", osMetaName.c_str(),
9207 : pszValue);
9208 : #endif
9209 91 : if (NCDFPutAttr(nCdfId, CDFVarID, osMetaName, pszValue) != CE_None)
9210 : {
9211 0 : CPLDebug("GDAL_netCDF", "NCDFPutAttr(%d, %d, %s, %s) failed",
9212 : nCdfId, CDFVarID, osMetaName.c_str(), pszValue);
9213 : }
9214 : }
9215 :
9216 : // Set add_offset and scale_factor here if present.
9217 157 : if (poSrcBand && poDstBand)
9218 : {
9219 :
9220 91 : int bGotAddOffset = FALSE;
9221 91 : const double dfAddOffset = poSrcBand->GetOffset(&bGotAddOffset);
9222 91 : int bGotScale = FALSE;
9223 91 : const double dfScale = poSrcBand->GetScale(&bGotScale);
9224 :
9225 91 : if (bGotAddOffset && dfAddOffset != 0.0)
9226 1 : poDstBand->SetOffset(dfAddOffset);
9227 91 : if (bGotScale && dfScale != 1.0)
9228 1 : poDstBand->SetScale(dfScale);
9229 : }
9230 157 : }
9231 :
9232 : /************************************************************************/
9233 : /* CreateLL() */
9234 : /* */
9235 : /* Shared functionality between netCDFDataset::Create() and */
9236 : /* netCDF::CreateCopy() for creating netcdf file based on a set of */
9237 : /* options and a configuration. */
9238 : /************************************************************************/
9239 :
9240 199 : netCDFDataset *netCDFDataset::CreateLL(const char *pszFilename, int nXSize,
9241 : int nYSize, int nBandsIn,
9242 : char **papszOptions)
9243 : {
9244 199 : if (!((nXSize == 0 && nYSize == 0 && nBandsIn == 0) ||
9245 126 : (nXSize > 0 && nYSize > 0 && nBandsIn > 0)))
9246 : {
9247 1 : return nullptr;
9248 : }
9249 :
9250 198 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
9251 : // GDALDataset own mutex.
9252 198 : netCDFDataset *poDS = new netCDFDataset();
9253 198 : CPLAcquireMutex(hNCMutex, 1000.0);
9254 :
9255 198 : poDS->nRasterXSize = nXSize;
9256 198 : poDS->nRasterYSize = nYSize;
9257 198 : poDS->eAccess = GA_Update;
9258 198 : poDS->osFilename = pszFilename;
9259 :
9260 : // From gtiff driver, is this ok?
9261 : /*
9262 : poDS->nBlockXSize = nXSize;
9263 : poDS->nBlockYSize = 1;
9264 : poDS->nBlocksPerBand =
9265 : DIV_ROUND_UP((nYSize, poDS->nBlockYSize))
9266 : * DIV_ROUND_UP((nXSize, poDS->nBlockXSize));
9267 : */
9268 :
9269 : // process options.
9270 198 : poDS->papszCreationOptions = CSLDuplicate(papszOptions);
9271 198 : poDS->ProcessCreationOptions();
9272 :
9273 198 : if (poDS->eMultipleLayerBehavior == SEPARATE_FILES)
9274 : {
9275 : VSIStatBuf sStat;
9276 3 : if (VSIStat(pszFilename, &sStat) == 0)
9277 : {
9278 0 : if (!VSI_ISDIR(sStat.st_mode))
9279 : {
9280 0 : CPLError(CE_Failure, CPLE_FileIO,
9281 : "%s is an existing file, but not a directory",
9282 : pszFilename);
9283 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9284 : // deadlock with GDALDataset own
9285 : // mutex.
9286 0 : delete poDS;
9287 0 : CPLAcquireMutex(hNCMutex, 1000.0);
9288 0 : return nullptr;
9289 : }
9290 : }
9291 3 : else if (VSIMkdir(pszFilename, 0755) != 0)
9292 : {
9293 1 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s directory",
9294 : pszFilename);
9295 1 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9296 : // deadlock with GDALDataset own mutex.
9297 1 : delete poDS;
9298 1 : CPLAcquireMutex(hNCMutex, 1000.0);
9299 1 : return nullptr;
9300 : }
9301 :
9302 2 : return poDS;
9303 : }
9304 : // Create the dataset.
9305 390 : CPLString osFilenameForNCCreate(pszFilename);
9306 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
9307 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
9308 : {
9309 : char *pszTemp =
9310 : CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
9311 : osFilenameForNCCreate = pszTemp;
9312 : CPLFree(pszTemp);
9313 : }
9314 : #endif
9315 :
9316 : #if defined(_WIN32)
9317 : {
9318 : // Works around bug of msys2 netCDF 4.9.0 package where nc_create()
9319 : // crashes
9320 : VSIStatBuf sStat;
9321 : const std::string osDirname =
9322 : CPLGetDirnameSafe(osFilenameForNCCreate.c_str());
9323 : if (VSIStat(osDirname.c_str(), &sStat) != 0)
9324 : {
9325 : CPLError(CE_Failure, CPLE_OpenFailed,
9326 : "Unable to create netCDF file %s: non existing output "
9327 : "directory",
9328 : pszFilename);
9329 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9330 : // deadlock with GDALDataset own mutex.
9331 : delete poDS;
9332 : CPLAcquireMutex(hNCMutex, 1000.0);
9333 : return nullptr;
9334 : }
9335 : }
9336 : #endif
9337 :
9338 : int status =
9339 195 : nc_create(osFilenameForNCCreate, poDS->nCreateMode, &(poDS->cdfid));
9340 :
9341 : // Put into define mode.
9342 195 : poDS->SetDefineMode(true);
9343 :
9344 195 : if (status != NC_NOERR)
9345 : {
9346 30 : CPLError(CE_Failure, CPLE_OpenFailed,
9347 : "Unable to create netCDF file %s (Error code %d): %s .",
9348 : pszFilename, status, nc_strerror(status));
9349 30 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
9350 : // with GDALDataset own mutex.
9351 30 : delete poDS;
9352 30 : CPLAcquireMutex(hNCMutex, 1000.0);
9353 30 : return nullptr;
9354 : }
9355 :
9356 : // Define dimensions.
9357 165 : if (nXSize > 0 && nYSize > 0)
9358 : {
9359 112 : poDS->papszDimName.AddString(NCDF_DIMNAME_X);
9360 : status =
9361 112 : nc_def_dim(poDS->cdfid, NCDF_DIMNAME_X, nXSize, &(poDS->nXDimID));
9362 112 : NCDF_ERR(status);
9363 112 : CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
9364 : poDS->cdfid, NCDF_DIMNAME_X, nXSize, poDS->nXDimID);
9365 :
9366 112 : poDS->papszDimName.AddString(NCDF_DIMNAME_Y);
9367 : status =
9368 112 : nc_def_dim(poDS->cdfid, NCDF_DIMNAME_Y, nYSize, &(poDS->nYDimID));
9369 112 : NCDF_ERR(status);
9370 112 : CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
9371 : poDS->cdfid, NCDF_DIMNAME_Y, nYSize, poDS->nYDimID);
9372 : }
9373 :
9374 165 : return poDS;
9375 : }
9376 :
9377 : /************************************************************************/
9378 : /* Create() */
9379 : /************************************************************************/
9380 :
9381 127 : GDALDataset *netCDFDataset::Create(const char *pszFilename, int nXSize,
9382 : int nYSize, int nBandsIn, GDALDataType eType,
9383 : char **papszOptions)
9384 : {
9385 127 : CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::Create(%s, ...)",
9386 : pszFilename);
9387 :
9388 : const char *legacyCreationOp =
9389 127 : CSLFetchNameValueDef(papszOptions, "GEOMETRY_ENCODING", "CF_1.8");
9390 254 : std::string legacyCreationOp_s = std::string(legacyCreationOp);
9391 :
9392 : // Check legacy creation op FIRST
9393 :
9394 127 : bool legacyCreateMode = false;
9395 :
9396 127 : if (nXSize != 0 || nYSize != 0 || nBandsIn != 0)
9397 : {
9398 56 : legacyCreateMode = true;
9399 : }
9400 71 : else if (legacyCreationOp_s == "CF_1.8")
9401 : {
9402 54 : legacyCreateMode = false;
9403 : }
9404 :
9405 17 : else if (legacyCreationOp_s == "WKT")
9406 : {
9407 17 : legacyCreateMode = true;
9408 : }
9409 :
9410 : else
9411 : {
9412 0 : CPLError(
9413 : CE_Failure, CPLE_NotSupported,
9414 : "Dataset creation option GEOMETRY_ENCODING=%s is not supported.",
9415 : legacyCreationOp_s.c_str());
9416 0 : return nullptr;
9417 : }
9418 :
9419 254 : CPLStringList aosOptions(CSLDuplicate(papszOptions));
9420 240 : if (aosOptions.FetchNameValue("FORMAT") == nullptr &&
9421 113 : (eType == GDT_UInt16 || eType == GDT_UInt32 || eType == GDT_UInt64 ||
9422 : eType == GDT_Int64))
9423 : {
9424 10 : CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
9425 10 : aosOptions.SetNameValue("FORMAT", "NC4");
9426 : }
9427 :
9428 254 : CPLStringList aosBandNames;
9429 127 : if (const char *pszBandNames = aosOptions.FetchNameValue("BAND_NAMES"))
9430 : {
9431 : aosBandNames =
9432 2 : CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);
9433 :
9434 2 : if (aosBandNames.Count() != nBandsIn)
9435 : {
9436 1 : CPLError(CE_Failure, CPLE_OpenFailed,
9437 : "Attempted to create netCDF with %d bands but %d names "
9438 : "provided in BAND_NAMES.",
9439 : nBandsIn, aosBandNames.Count());
9440 :
9441 1 : return nullptr;
9442 : }
9443 : }
9444 :
9445 252 : CPLMutexHolderD(&hNCMutex);
9446 :
9447 126 : auto poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize, nBandsIn,
9448 : aosOptions.List());
9449 :
9450 126 : if (!poDS)
9451 19 : return nullptr;
9452 :
9453 107 : if (!legacyCreateMode)
9454 : {
9455 37 : poDS->bSGSupport = true;
9456 37 : poDS->vcdf.enableFullVirtualMode();
9457 : }
9458 :
9459 : else
9460 : {
9461 70 : poDS->bSGSupport = false;
9462 : }
9463 :
9464 : // Should we write signed or unsigned byte?
9465 : // TODO should this only be done in Create()
9466 107 : poDS->bSignedData = true;
9467 107 : const char *pszValue = CSLFetchNameValueDef(papszOptions, "PIXELTYPE", "");
9468 107 : if (eType == GDT_Byte && !EQUAL(pszValue, "SIGNEDBYTE"))
9469 15 : poDS->bSignedData = false;
9470 :
9471 : // Add Conventions, GDAL info and history.
9472 107 : if (poDS->cdfid >= 0)
9473 : {
9474 : const char *CF_Vector_Conv =
9475 173 : poDS->bSGSupport ||
9476 : // Use of variable length strings require CF-1.8
9477 68 : EQUAL(aosOptions.FetchNameValueDef("FORMAT", ""), "NC4")
9478 : ? NCDF_CONVENTIONS_CF_V1_8
9479 173 : : NCDF_CONVENTIONS_CF_V1_6;
9480 105 : poDS->bWriteGDALVersion = CPLTestBool(
9481 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
9482 105 : poDS->bWriteGDALHistory = CPLTestBool(
9483 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
9484 105 : NCDFAddGDALHistory(poDS->cdfid, pszFilename, poDS->bWriteGDALVersion,
9485 105 : poDS->bWriteGDALHistory, "", "Create",
9486 : (nBandsIn == 0) ? CF_Vector_Conv
9487 : : GDAL_DEFAULT_NCDF_CONVENTIONS);
9488 : }
9489 :
9490 : // Define bands.
9491 198 : for (int iBand = 1; iBand <= nBandsIn; iBand++)
9492 : {
9493 : const char *pszBandName =
9494 91 : aosBandNames.empty() ? nullptr : aosBandNames[iBand - 1];
9495 :
9496 91 : poDS->SetBand(iBand, new netCDFRasterBand(
9497 91 : netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS,
9498 91 : eType, iBand, poDS->bSignedData, pszBandName));
9499 : }
9500 :
9501 107 : CPLDebug("GDAL_netCDF", "netCDFDataset::Create(%s, ...) done", pszFilename);
9502 : // Return same dataset.
9503 107 : return poDS;
9504 : }
9505 :
9506 : template <class T>
9507 91 : static CPLErr NCDFCopyBand(GDALRasterBand *poSrcBand, GDALRasterBand *poDstBand,
9508 : int nXSize, int nYSize, GDALProgressFunc pfnProgress,
9509 : void *pProgressData)
9510 : {
9511 91 : GDALDataType eDT = poSrcBand->GetRasterDataType();
9512 91 : CPLErr eErr = CE_None;
9513 91 : T *patScanline = static_cast<T *>(CPLMalloc(nXSize * sizeof(T)));
9514 :
9515 6308 : for (int iLine = 0; iLine < nYSize && eErr == CE_None; iLine++)
9516 : {
9517 6217 : eErr = poSrcBand->RasterIO(GF_Read, 0, iLine, nXSize, 1, patScanline,
9518 : nXSize, 1, eDT, 0, 0, nullptr);
9519 6217 : if (eErr != CE_None)
9520 : {
9521 0 : CPLDebug(
9522 : "GDAL_netCDF",
9523 : "NCDFCopyBand(), poSrcBand->RasterIO() returned error code %d",
9524 : eErr);
9525 : }
9526 : else
9527 : {
9528 6217 : eErr =
9529 : poDstBand->RasterIO(GF_Write, 0, iLine, nXSize, 1, patScanline,
9530 : nXSize, 1, eDT, 0, 0, nullptr);
9531 6217 : if (eErr != CE_None)
9532 0 : CPLDebug("GDAL_netCDF",
9533 : "NCDFCopyBand(), poDstBand->RasterIO() returned error "
9534 : "code %d",
9535 : eErr);
9536 : }
9537 :
9538 6217 : if (nYSize > 10 && (iLine % (nYSize / 10) == 1))
9539 : {
9540 277 : if (!pfnProgress(1.0 * iLine / nYSize, nullptr, pProgressData))
9541 : {
9542 0 : eErr = CE_Failure;
9543 0 : CPLError(CE_Failure, CPLE_UserInterrupt,
9544 : "User terminated CreateCopy()");
9545 : }
9546 : }
9547 : }
9548 :
9549 91 : CPLFree(patScanline);
9550 :
9551 91 : pfnProgress(1.0, nullptr, pProgressData);
9552 :
9553 91 : return eErr;
9554 : }
9555 :
9556 : /************************************************************************/
9557 : /* CreateCopy() */
9558 : /************************************************************************/
9559 :
9560 : GDALDataset *
9561 83 : netCDFDataset::CreateCopy(const char *pszFilename, GDALDataset *poSrcDS,
9562 : CPL_UNUSED int bStrict, char **papszOptions,
9563 : GDALProgressFunc pfnProgress, void *pProgressData)
9564 : {
9565 166 : CPLMutexHolderD(&hNCMutex);
9566 :
9567 83 : CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::CreateCopy(%s, ...)",
9568 : pszFilename);
9569 :
9570 83 : if (poSrcDS->GetRootGroup())
9571 : {
9572 6 : auto poDrv = GDALDriver::FromHandle(GDALGetDriverByName("netCDF"));
9573 6 : if (poDrv)
9574 : {
9575 6 : return poDrv->DefaultCreateCopy(pszFilename, poSrcDS, bStrict,
9576 : papszOptions, pfnProgress,
9577 6 : pProgressData);
9578 : }
9579 : }
9580 :
9581 77 : const int nBands = poSrcDS->GetRasterCount();
9582 77 : const int nXSize = poSrcDS->GetRasterXSize();
9583 77 : const int nYSize = poSrcDS->GetRasterYSize();
9584 77 : const char *pszWKT = poSrcDS->GetProjectionRef();
9585 :
9586 : // Check input bands for errors.
9587 77 : if (nBands == 0)
9588 : {
9589 1 : CPLError(CE_Failure, CPLE_NotSupported,
9590 : "NetCDF driver does not support "
9591 : "source dataset with zero band.");
9592 1 : return nullptr;
9593 : }
9594 :
9595 76 : GDALDataType eDT = GDT_Unknown;
9596 76 : GDALRasterBand *poSrcBand = nullptr;
9597 181 : for (int iBand = 1; iBand <= nBands; iBand++)
9598 : {
9599 109 : poSrcBand = poSrcDS->GetRasterBand(iBand);
9600 109 : eDT = poSrcBand->GetRasterDataType();
9601 109 : if (eDT == GDT_Unknown || GDALDataTypeIsComplex(eDT))
9602 : {
9603 4 : CPLError(CE_Failure, CPLE_NotSupported,
9604 : "NetCDF driver does not support source dataset with band "
9605 : "of complex type.");
9606 4 : return nullptr;
9607 : }
9608 : }
9609 :
9610 144 : CPLStringList aosBandNames;
9611 72 : if (const char *pszBandNames =
9612 72 : CSLFetchNameValue(papszOptions, "BAND_NAMES"))
9613 : {
9614 : aosBandNames =
9615 2 : CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);
9616 :
9617 2 : if (aosBandNames.Count() != nBands)
9618 : {
9619 1 : CPLError(CE_Failure, CPLE_OpenFailed,
9620 : "Attempted to create netCDF with %d bands but %d names "
9621 : "provided in BAND_NAMES.",
9622 : nBands, aosBandNames.Count());
9623 :
9624 1 : return nullptr;
9625 : }
9626 : }
9627 :
9628 71 : if (!pfnProgress(0.0, nullptr, pProgressData))
9629 0 : return nullptr;
9630 :
9631 : // Same as in Create().
9632 142 : CPLStringList aosOptions(CSLDuplicate(papszOptions));
9633 133 : if (aosOptions.FetchNameValue("FORMAT") == nullptr &&
9634 62 : (eDT == GDT_UInt16 || eDT == GDT_UInt32 || eDT == GDT_UInt64 ||
9635 : eDT == GDT_Int64))
9636 : {
9637 6 : CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
9638 6 : aosOptions.SetNameValue("FORMAT", "NC4");
9639 : }
9640 71 : netCDFDataset *poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize,
9641 : nBands, aosOptions.List());
9642 71 : if (!poDS)
9643 13 : return nullptr;
9644 :
9645 : // Copy global metadata.
9646 : // Add Conventions, GDAL info and history.
9647 58 : CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid, NC_GLOBAL, nullptr);
9648 58 : const bool bWriteGDALVersion = CPLTestBool(
9649 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
9650 58 : const bool bWriteGDALHistory = CPLTestBool(
9651 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
9652 58 : NCDFAddGDALHistory(
9653 : poDS->cdfid, pszFilename, bWriteGDALVersion, bWriteGDALHistory,
9654 58 : poSrcDS->GetMetadataItem("NC_GLOBAL#history"), "CreateCopy",
9655 58 : poSrcDS->GetMetadataItem("NC_GLOBAL#Conventions"));
9656 :
9657 58 : pfnProgress(0.1, nullptr, pProgressData);
9658 :
9659 : // Check for extra dimensions.
9660 58 : int nDim = 2;
9661 : char **papszExtraDimNames =
9662 58 : NCDFTokenizeArray(poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
9663 58 : char **papszExtraDimValues = nullptr;
9664 :
9665 58 : if (papszExtraDimNames != nullptr && CSLCount(papszExtraDimNames) > 0)
9666 : {
9667 5 : size_t nDimSizeTot = 1;
9668 : // first make sure dimensions lengths compatible with band count
9669 : // for( int i=0; i<CSLCount(papszExtraDimNames ); i++ ) {
9670 13 : for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--)
9671 : {
9672 : char szTemp[NC_MAX_NAME + 32 + 1];
9673 8 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
9674 8 : papszExtraDimNames[i]);
9675 : papszExtraDimValues =
9676 8 : NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
9677 8 : const size_t nDimSize = atol(papszExtraDimValues[0]);
9678 8 : CSLDestroy(papszExtraDimValues);
9679 8 : nDimSizeTot *= nDimSize;
9680 : }
9681 5 : if (nDimSizeTot == (size_t)nBands)
9682 : {
9683 5 : nDim = 2 + CSLCount(papszExtraDimNames);
9684 : }
9685 : else
9686 : {
9687 : // if nBands != #bands computed raise a warning
9688 : // just issue a debug message, because it was probably intentional
9689 0 : CPLDebug("GDAL_netCDF",
9690 : "Warning: Number of bands (%d) is not compatible with "
9691 : "dimensions "
9692 : "(total=%ld names=%s)",
9693 : nBands, (long)nDimSizeTot,
9694 0 : poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
9695 0 : CSLDestroy(papszExtraDimNames);
9696 0 : papszExtraDimNames = nullptr;
9697 : }
9698 : }
9699 :
9700 58 : int *panDimIds = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
9701 58 : int *panBandDimPos = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
9702 :
9703 : nc_type nVarType;
9704 58 : int *panBandZLev = nullptr;
9705 58 : int *panDimVarIds = nullptr;
9706 :
9707 58 : if (nDim > 2)
9708 : {
9709 5 : panBandZLev = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
9710 5 : panDimVarIds = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
9711 :
9712 : // Define all dims.
9713 13 : for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--)
9714 : {
9715 8 : poDS->papszDimName.AddString(papszExtraDimNames[i]);
9716 : char szTemp[NC_MAX_NAME + 32 + 1];
9717 8 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
9718 8 : papszExtraDimNames[i]);
9719 : papszExtraDimValues =
9720 8 : NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
9721 8 : const int nDimSize = papszExtraDimValues && papszExtraDimValues[0]
9722 16 : ? atoi(papszExtraDimValues[0])
9723 : : 0;
9724 : // nc_type is an enum in netcdf-3, needs casting.
9725 8 : nVarType = static_cast<nc_type>(papszExtraDimValues &&
9726 8 : papszExtraDimValues[0] &&
9727 8 : papszExtraDimValues[1]
9728 8 : ? atol(papszExtraDimValues[1])
9729 : : 0);
9730 8 : CSLDestroy(papszExtraDimValues);
9731 8 : panBandZLev[i] = nDimSize;
9732 8 : panBandDimPos[i + 2] = i; // Save Position of ZDim.
9733 :
9734 : // Define dim.
9735 16 : int status = nc_def_dim(poDS->cdfid, papszExtraDimNames[i],
9736 8 : nDimSize, &(panDimIds[i]));
9737 8 : NCDF_ERR(status);
9738 :
9739 : // Define dim var.
9740 8 : int anDim[1] = {panDimIds[i]};
9741 16 : status = nc_def_var(poDS->cdfid, papszExtraDimNames[i], nVarType, 1,
9742 8 : anDim, &(panDimVarIds[i]));
9743 8 : NCDF_ERR(status);
9744 :
9745 : // Add dim metadata, using global var# items.
9746 8 : snprintf(szTemp, sizeof(szTemp), "%s#", papszExtraDimNames[i]);
9747 8 : CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid,
9748 8 : panDimVarIds[i], szTemp);
9749 : }
9750 : }
9751 :
9752 : // Copy GeoTransform and Projection.
9753 :
9754 : // Copy geolocation info.
9755 58 : char **papszGeolocationInfo = poSrcDS->GetMetadata("GEOLOCATION");
9756 58 : if (papszGeolocationInfo != nullptr)
9757 5 : poDS->GDALPamDataset::SetMetadata(papszGeolocationInfo, "GEOLOCATION");
9758 :
9759 : // Copy geotransform.
9760 58 : bool bGotGeoTransform = false;
9761 58 : GDALGeoTransform gt;
9762 58 : CPLErr eErr = poSrcDS->GetGeoTransform(gt);
9763 58 : if (eErr == CE_None)
9764 : {
9765 40 : poDS->SetGeoTransform(gt);
9766 : // Disable AddProjectionVars() from being called.
9767 40 : bGotGeoTransform = true;
9768 40 : poDS->m_bHasGeoTransform = false;
9769 : }
9770 :
9771 : // Copy projection.
9772 58 : void *pScaledProgress = nullptr;
9773 58 : if (bGotGeoTransform || (pszWKT && pszWKT[0] != 0))
9774 : {
9775 41 : poDS->SetProjection(pszWKT ? pszWKT : "");
9776 :
9777 : // Now we can call AddProjectionVars() directly.
9778 41 : poDS->m_bHasGeoTransform = bGotGeoTransform;
9779 41 : poDS->AddProjectionVars(true, nullptr, nullptr);
9780 : pScaledProgress =
9781 41 : GDALCreateScaledProgress(0.1, 0.25, pfnProgress, pProgressData);
9782 41 : poDS->AddProjectionVars(false, GDALScaledProgress, pScaledProgress);
9783 41 : GDALDestroyScaledProgress(pScaledProgress);
9784 : }
9785 : else
9786 : {
9787 17 : poDS->bBottomUp =
9788 17 : CPL_TO_BOOL(CSLFetchBoolean(papszOptions, "WRITE_BOTTOMUP", TRUE));
9789 17 : if (papszGeolocationInfo)
9790 : {
9791 4 : poDS->AddProjectionVars(true, nullptr, nullptr);
9792 4 : poDS->AddProjectionVars(false, nullptr, nullptr);
9793 : }
9794 : }
9795 :
9796 : // Save X,Y dim positions.
9797 58 : panDimIds[nDim - 1] = poDS->nXDimID;
9798 58 : panBandDimPos[0] = nDim - 1;
9799 58 : panDimIds[nDim - 2] = poDS->nYDimID;
9800 58 : panBandDimPos[1] = nDim - 2;
9801 :
9802 : // Write extra dim values - after projection for optimization.
9803 58 : if (nDim > 2)
9804 : {
9805 : // Make sure we are in data mode.
9806 5 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
9807 13 : for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--)
9808 : {
9809 : char szTemp[NC_MAX_NAME + 32 + 1];
9810 8 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_VALUES",
9811 8 : papszExtraDimNames[i]);
9812 8 : if (poSrcDS->GetMetadataItem(szTemp) != nullptr)
9813 : {
9814 8 : NCDFPut1DVar(poDS->cdfid, panDimVarIds[i],
9815 8 : poSrcDS->GetMetadataItem(szTemp));
9816 : }
9817 : }
9818 : }
9819 :
9820 58 : pfnProgress(0.25, nullptr, pProgressData);
9821 :
9822 : // Define Bands.
9823 58 : netCDFRasterBand *poBand = nullptr;
9824 58 : int nBandID = -1;
9825 :
9826 149 : for (int iBand = 1; iBand <= nBands; iBand++)
9827 : {
9828 91 : CPLDebug("GDAL_netCDF", "creating band # %d/%d nDim = %d", iBand,
9829 : nBands, nDim);
9830 :
9831 91 : poSrcBand = poSrcDS->GetRasterBand(iBand);
9832 91 : eDT = poSrcBand->GetRasterDataType();
9833 :
9834 : // Get var name from NETCDF_VARNAME.
9835 : const char *pszNETCDF_VARNAME =
9836 91 : poSrcBand->GetMetadataItem("NETCDF_VARNAME");
9837 : char szBandName[NC_MAX_NAME + 1];
9838 91 : if (!aosBandNames.empty())
9839 : {
9840 2 : snprintf(szBandName, sizeof(szBandName), "%s",
9841 : aosBandNames[iBand - 1]);
9842 : }
9843 89 : else if (pszNETCDF_VARNAME)
9844 : {
9845 32 : if (nBands > 1 && papszExtraDimNames == nullptr)
9846 0 : snprintf(szBandName, sizeof(szBandName), "%s%d",
9847 : pszNETCDF_VARNAME, iBand);
9848 : else
9849 32 : snprintf(szBandName, sizeof(szBandName), "%s",
9850 : pszNETCDF_VARNAME);
9851 : }
9852 : else
9853 : {
9854 57 : szBandName[0] = '\0';
9855 : }
9856 :
9857 : // Get long_name from <var>#long_name.
9858 91 : const char *pszLongName = "";
9859 91 : if (pszNETCDF_VARNAME)
9860 : {
9861 : pszLongName =
9862 64 : poSrcDS->GetMetadataItem(std::string(pszNETCDF_VARNAME)
9863 32 : .append("#")
9864 32 : .append(CF_LNG_NAME)
9865 32 : .c_str());
9866 32 : if (!pszLongName)
9867 25 : pszLongName = "";
9868 : }
9869 :
9870 91 : constexpr bool bSignedData = false;
9871 :
9872 91 : if (nDim > 2)
9873 27 : poBand = new netCDFRasterBand(
9874 27 : netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
9875 : bSignedData, szBandName, pszLongName, nBandID, nDim, iBand - 1,
9876 27 : panBandZLev, panBandDimPos, panDimIds);
9877 : else
9878 64 : poBand = new netCDFRasterBand(
9879 64 : netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
9880 64 : bSignedData, szBandName, pszLongName);
9881 :
9882 91 : poDS->SetBand(iBand, poBand);
9883 :
9884 : // Set nodata value, if any.
9885 91 : GDALCopyNoDataValue(poBand, poSrcBand);
9886 :
9887 : // Copy Metadata for band.
9888 91 : CopyMetadata(nullptr, poSrcDS->GetRasterBand(iBand), poBand,
9889 : poDS->cdfid, poBand->nZId);
9890 :
9891 : // If more than 2D pass the first band's netcdf var ID to subsequent
9892 : // bands.
9893 91 : if (nDim > 2)
9894 27 : nBandID = poBand->nZId;
9895 : }
9896 :
9897 : // Write projection variable to band variable.
9898 58 : poDS->AddGridMappingRef();
9899 :
9900 58 : pfnProgress(0.5, nullptr, pProgressData);
9901 :
9902 : // Write bands.
9903 :
9904 : // Make sure we are in data mode.
9905 58 : poDS->SetDefineMode(false);
9906 :
9907 58 : double dfTemp = 0.5;
9908 :
9909 58 : eErr = CE_None;
9910 :
9911 149 : for (int iBand = 1; iBand <= nBands && eErr == CE_None; iBand++)
9912 : {
9913 91 : const double dfTemp2 = dfTemp + 0.4 / nBands;
9914 91 : pScaledProgress = GDALCreateScaledProgress(dfTemp, dfTemp2, pfnProgress,
9915 : pProgressData);
9916 91 : dfTemp = dfTemp2;
9917 :
9918 91 : CPLDebug("GDAL_netCDF", "copying band data # %d/%d ", iBand, nBands);
9919 :
9920 91 : poSrcBand = poSrcDS->GetRasterBand(iBand);
9921 91 : eDT = poSrcBand->GetRasterDataType();
9922 :
9923 91 : GDALRasterBand *poDstBand = poDS->GetRasterBand(iBand);
9924 :
9925 : // Copy band data.
9926 91 : if (eDT == GDT_Byte)
9927 : {
9928 51 : CPLDebug("GDAL_netCDF", "GByte Band#%d", iBand);
9929 51 : eErr = NCDFCopyBand<GByte>(poSrcBand, poDstBand, nXSize, nYSize,
9930 : GDALScaledProgress, pScaledProgress);
9931 : }
9932 40 : else if (eDT == GDT_Int8)
9933 : {
9934 1 : CPLDebug("GDAL_netCDF", "GInt8 Band#%d", iBand);
9935 1 : eErr = NCDFCopyBand<GInt8>(poSrcBand, poDstBand, nXSize, nYSize,
9936 : GDALScaledProgress, pScaledProgress);
9937 : }
9938 39 : else if (eDT == GDT_UInt16)
9939 : {
9940 2 : CPLDebug("GDAL_netCDF", "GUInt16 Band#%d", iBand);
9941 2 : eErr = NCDFCopyBand<GInt16>(poSrcBand, poDstBand, nXSize, nYSize,
9942 : GDALScaledProgress, pScaledProgress);
9943 : }
9944 37 : else if (eDT == GDT_Int16)
9945 : {
9946 5 : CPLDebug("GDAL_netCDF", "GInt16 Band#%d", iBand);
9947 5 : eErr = NCDFCopyBand<GUInt16>(poSrcBand, poDstBand, nXSize, nYSize,
9948 : GDALScaledProgress, pScaledProgress);
9949 : }
9950 32 : else if (eDT == GDT_UInt32)
9951 : {
9952 2 : CPLDebug("GDAL_netCDF", "GUInt32 Band#%d", iBand);
9953 2 : eErr = NCDFCopyBand<GUInt32>(poSrcBand, poDstBand, nXSize, nYSize,
9954 : GDALScaledProgress, pScaledProgress);
9955 : }
9956 30 : else if (eDT == GDT_Int32)
9957 : {
9958 18 : CPLDebug("GDAL_netCDF", "GInt32 Band#%d", iBand);
9959 18 : eErr = NCDFCopyBand<GInt32>(poSrcBand, poDstBand, nXSize, nYSize,
9960 : GDALScaledProgress, pScaledProgress);
9961 : }
9962 12 : else if (eDT == GDT_UInt64)
9963 : {
9964 2 : CPLDebug("GDAL_netCDF", "GUInt64 Band#%d", iBand);
9965 2 : eErr = NCDFCopyBand<std::uint64_t>(poSrcBand, poDstBand, nXSize,
9966 : nYSize, GDALScaledProgress,
9967 : pScaledProgress);
9968 : }
9969 10 : else if (eDT == GDT_Int64)
9970 : {
9971 2 : CPLDebug("GDAL_netCDF", "GInt64 Band#%d", iBand);
9972 : eErr =
9973 2 : NCDFCopyBand<std::int64_t>(poSrcBand, poDstBand, nXSize, nYSize,
9974 : GDALScaledProgress, pScaledProgress);
9975 : }
9976 8 : else if (eDT == GDT_Float32)
9977 : {
9978 6 : CPLDebug("GDAL_netCDF", "float Band#%d", iBand);
9979 6 : eErr = NCDFCopyBand<float>(poSrcBand, poDstBand, nXSize, nYSize,
9980 : GDALScaledProgress, pScaledProgress);
9981 : }
9982 2 : else if (eDT == GDT_Float64)
9983 : {
9984 2 : CPLDebug("GDAL_netCDF", "double Band#%d", iBand);
9985 2 : eErr = NCDFCopyBand<double>(poSrcBand, poDstBand, nXSize, nYSize,
9986 : GDALScaledProgress, pScaledProgress);
9987 : }
9988 : else
9989 : {
9990 0 : CPLError(CE_Failure, CPLE_NotSupported,
9991 : "The NetCDF driver does not support GDAL data type %d",
9992 : eDT);
9993 : }
9994 :
9995 91 : GDALDestroyScaledProgress(pScaledProgress);
9996 : }
9997 :
9998 58 : delete (poDS);
9999 :
10000 58 : CPLFree(panDimIds);
10001 58 : CPLFree(panBandDimPos);
10002 58 : CPLFree(panBandZLev);
10003 58 : CPLFree(panDimVarIds);
10004 58 : if (papszExtraDimNames)
10005 5 : CSLDestroy(papszExtraDimNames);
10006 :
10007 58 : if (eErr != CE_None)
10008 0 : return nullptr;
10009 :
10010 58 : pfnProgress(0.95, nullptr, pProgressData);
10011 :
10012 : // Re-open dataset so we can return it.
10013 116 : CPLStringList aosOpenOptions;
10014 58 : aosOpenOptions.AddString("VARIABLES_AS_BANDS=YES");
10015 58 : GDALOpenInfo oOpenInfo(pszFilename, GA_Update);
10016 58 : oOpenInfo.nOpenFlags = GDAL_OF_RASTER | GDAL_OF_UPDATE;
10017 58 : oOpenInfo.papszOpenOptions = aosOpenOptions.List();
10018 58 : auto poRetDS = Open(&oOpenInfo);
10019 :
10020 : // PAM cloning is disabled. See bug #4244.
10021 : // if( poDS )
10022 : // poDS->CloneInfo(poSrcDS, GCIF_PAM_DEFAULT);
10023 :
10024 58 : pfnProgress(1.0, nullptr, pProgressData);
10025 :
10026 58 : return poRetDS;
10027 : }
10028 :
10029 : // Note: some logic depends on bIsProjected and bIsGeoGraphic.
10030 : // May not be known when Create() is called, see AddProjectionVars().
10031 256 : void netCDFDataset::ProcessCreationOptions()
10032 : {
10033 : const char *pszConfig =
10034 256 : CSLFetchNameValue(papszCreationOptions, "CONFIG_FILE");
10035 256 : if (pszConfig != nullptr)
10036 : {
10037 4 : if (oWriterConfig.Parse(pszConfig))
10038 : {
10039 : // Override dataset creation options from the config file
10040 2 : std::map<CPLString, CPLString>::iterator oIter;
10041 3 : for (oIter = oWriterConfig.m_oDatasetCreationOptions.begin();
10042 3 : oIter != oWriterConfig.m_oDatasetCreationOptions.end();
10043 1 : ++oIter)
10044 : {
10045 2 : papszCreationOptions = CSLSetNameValue(
10046 2 : papszCreationOptions, oIter->first, oIter->second);
10047 : }
10048 : }
10049 : }
10050 :
10051 : // File format.
10052 256 : eFormat = NCDF_FORMAT_NC;
10053 256 : const char *pszValue = CSLFetchNameValue(papszCreationOptions, "FORMAT");
10054 256 : if (pszValue != nullptr)
10055 : {
10056 94 : if (EQUAL(pszValue, "NC"))
10057 : {
10058 3 : eFormat = NCDF_FORMAT_NC;
10059 : }
10060 : #ifdef NETCDF_HAS_NC2
10061 91 : else if (EQUAL(pszValue, "NC2"))
10062 : {
10063 1 : eFormat = NCDF_FORMAT_NC2;
10064 : }
10065 : #endif
10066 90 : else if (EQUAL(pszValue, "NC4"))
10067 : {
10068 86 : eFormat = NCDF_FORMAT_NC4;
10069 : }
10070 4 : else if (EQUAL(pszValue, "NC4C"))
10071 : {
10072 4 : eFormat = NCDF_FORMAT_NC4C;
10073 : }
10074 : else
10075 : {
10076 0 : CPLError(CE_Failure, CPLE_NotSupported,
10077 : "FORMAT=%s in not supported, using the default NC format.",
10078 : pszValue);
10079 : }
10080 : }
10081 :
10082 : // COMPRESS option.
10083 256 : pszValue = CSLFetchNameValue(papszCreationOptions, "COMPRESS");
10084 256 : if (pszValue != nullptr)
10085 : {
10086 3 : if (EQUAL(pszValue, "NONE"))
10087 : {
10088 1 : eCompress = NCDF_COMPRESS_NONE;
10089 : }
10090 2 : else if (EQUAL(pszValue, "DEFLATE"))
10091 : {
10092 2 : eCompress = NCDF_COMPRESS_DEFLATE;
10093 2 : if (!((eFormat == NCDF_FORMAT_NC4) ||
10094 2 : (eFormat == NCDF_FORMAT_NC4C)))
10095 : {
10096 1 : CPLError(CE_Warning, CPLE_IllegalArg,
10097 : "NOTICE: Format set to NC4C because compression is "
10098 : "set to DEFLATE.");
10099 1 : eFormat = NCDF_FORMAT_NC4C;
10100 : }
10101 : }
10102 : else
10103 : {
10104 0 : CPLError(CE_Failure, CPLE_NotSupported,
10105 : "COMPRESS=%s is not supported.", pszValue);
10106 : }
10107 : }
10108 :
10109 : // ZLEVEL option.
10110 256 : pszValue = CSLFetchNameValue(papszCreationOptions, "ZLEVEL");
10111 256 : if (pszValue != nullptr)
10112 : {
10113 1 : nZLevel = atoi(pszValue);
10114 1 : if (!(nZLevel >= 1 && nZLevel <= 9))
10115 : {
10116 0 : CPLError(CE_Warning, CPLE_IllegalArg,
10117 : "ZLEVEL=%s value not recognised, ignoring.", pszValue);
10118 0 : nZLevel = NCDF_DEFLATE_LEVEL;
10119 : }
10120 : }
10121 :
10122 : // CHUNKING option.
10123 256 : bChunking =
10124 256 : CPL_TO_BOOL(CSLFetchBoolean(papszCreationOptions, "CHUNKING", TRUE));
10125 :
10126 : // MULTIPLE_LAYERS option.
10127 : const char *pszMultipleLayerBehavior =
10128 256 : CSLFetchNameValueDef(papszCreationOptions, "MULTIPLE_LAYERS", "NO");
10129 512 : const char *pszGeometryEnc = CSLFetchNameValueDef(
10130 256 : papszCreationOptions, "GEOMETRY_ENCODING", "CF_1.8");
10131 256 : if (EQUAL(pszMultipleLayerBehavior, "NO") ||
10132 4 : EQUAL(pszGeometryEnc, "CF_1.8"))
10133 : {
10134 252 : eMultipleLayerBehavior = SINGLE_LAYER;
10135 : }
10136 4 : else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_FILES"))
10137 : {
10138 3 : eMultipleLayerBehavior = SEPARATE_FILES;
10139 : }
10140 1 : else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_GROUPS"))
10141 : {
10142 1 : if (eFormat == NCDF_FORMAT_NC4)
10143 : {
10144 1 : eMultipleLayerBehavior = SEPARATE_GROUPS;
10145 : }
10146 : else
10147 : {
10148 0 : CPLError(CE_Warning, CPLE_IllegalArg,
10149 : "MULTIPLE_LAYERS=%s is recognised only with FORMAT=NC4",
10150 : pszMultipleLayerBehavior);
10151 : }
10152 : }
10153 : else
10154 : {
10155 0 : CPLError(CE_Warning, CPLE_IllegalArg,
10156 : "MULTIPLE_LAYERS=%s not recognised", pszMultipleLayerBehavior);
10157 : }
10158 :
10159 : // Set nCreateMode based on eFormat.
10160 256 : switch (eFormat)
10161 : {
10162 : #ifdef NETCDF_HAS_NC2
10163 1 : case NCDF_FORMAT_NC2:
10164 1 : nCreateMode = NC_CLOBBER | NC_64BIT_OFFSET;
10165 1 : break;
10166 : #endif
10167 86 : case NCDF_FORMAT_NC4:
10168 86 : nCreateMode = NC_CLOBBER | NC_NETCDF4;
10169 86 : break;
10170 5 : case NCDF_FORMAT_NC4C:
10171 5 : nCreateMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
10172 5 : break;
10173 164 : case NCDF_FORMAT_NC:
10174 : default:
10175 164 : nCreateMode = NC_CLOBBER;
10176 164 : break;
10177 : }
10178 :
10179 256 : CPLDebug("GDAL_netCDF", "file options: format=%d compress=%d zlevel=%d",
10180 256 : eFormat, eCompress, nZLevel);
10181 256 : }
10182 :
10183 278 : int netCDFDataset::DefVarDeflate(int nVarId, bool bChunkingArg)
10184 : {
10185 278 : if (eCompress == NCDF_COMPRESS_DEFLATE)
10186 : {
10187 : // Must set chunk size to avoid huge performance hit (set
10188 : // bChunkingArg=TRUE)
10189 : // perhaps another solution it to change the chunk cache?
10190 : // http://www.unidata.ucar.edu/software/netcdf/docs/netcdf.html#Chunk-Cache
10191 : // TODO: make sure this is okay.
10192 2 : CPLDebug("GDAL_netCDF", "DefVarDeflate(%d, %d) nZlevel=%d", nVarId,
10193 : static_cast<int>(bChunkingArg), nZLevel);
10194 :
10195 2 : int status = nc_def_var_deflate(cdfid, nVarId, 1, 1, nZLevel);
10196 2 : NCDF_ERR(status);
10197 :
10198 2 : if (status == NC_NOERR && bChunkingArg && bChunking)
10199 : {
10200 : // set chunking to be 1 for all dims, except X dim
10201 : // size_t chunksize[] = { 1, (size_t)nRasterXSize };
10202 : size_t chunksize[MAX_NC_DIMS];
10203 : int nd;
10204 2 : nc_inq_varndims(cdfid, nVarId, &nd);
10205 2 : chunksize[0] = (size_t)1;
10206 2 : chunksize[1] = (size_t)1;
10207 2 : for (int i = 2; i < nd; i++)
10208 0 : chunksize[i] = (size_t)1;
10209 2 : chunksize[nd - 1] = (size_t)nRasterXSize;
10210 :
10211 : // Config options just for testing purposes
10212 : const char *pszBlockXSize =
10213 2 : CPLGetConfigOption("BLOCKXSIZE", nullptr);
10214 2 : if (pszBlockXSize)
10215 0 : chunksize[nd - 1] = (size_t)atoi(pszBlockXSize);
10216 :
10217 : const char *pszBlockYSize =
10218 2 : CPLGetConfigOption("BLOCKYSIZE", nullptr);
10219 2 : if (nd >= 2 && pszBlockYSize)
10220 0 : chunksize[nd - 2] = (size_t)atoi(pszBlockYSize);
10221 :
10222 2 : CPLDebug("GDAL_netCDF",
10223 : "DefVarDeflate() chunksize={%ld, %ld} chunkX=%ld nd=%d",
10224 2 : (long)chunksize[0], (long)chunksize[1],
10225 2 : (long)chunksize[nd - 1], nd);
10226 : #ifdef NCDF_DEBUG
10227 : for (int i = 0; i < nd; i++)
10228 : CPLDebug("GDAL_netCDF", "DefVarDeflate() chunk[%d]=%ld", i,
10229 : chunksize[i]);
10230 : #endif
10231 :
10232 2 : status = nc_def_var_chunking(cdfid, nVarId, NC_CHUNKED, chunksize);
10233 2 : NCDF_ERR(status);
10234 : }
10235 : else
10236 : {
10237 0 : CPLDebug("GDAL_netCDF", "chunksize not set");
10238 : }
10239 2 : return status;
10240 : }
10241 276 : return NC_NOERR;
10242 : }
10243 :
10244 : /************************************************************************/
10245 : /* NCDFUnloadDriver() */
10246 : /************************************************************************/
10247 :
10248 8 : static void NCDFUnloadDriver(CPL_UNUSED GDALDriver *poDriver)
10249 : {
10250 8 : if (hNCMutex != nullptr)
10251 4 : CPLDestroyMutex(hNCMutex);
10252 8 : hNCMutex = nullptr;
10253 8 : }
10254 :
10255 : /************************************************************************/
10256 : /* GDALRegister_netCDF() */
10257 : /************************************************************************/
10258 :
10259 : class GDALnetCDFDriver final : public GDALDriver
10260 : {
10261 : public:
10262 19 : GDALnetCDFDriver() = default;
10263 :
10264 : const char *GetMetadataItem(const char *pszName,
10265 : const char *pszDomain) override;
10266 :
10267 90 : char **GetMetadata(const char *pszDomain) override
10268 : {
10269 180 : std::lock_guard oLock(m_oMutex);
10270 90 : InitializeDCAPVirtualIO();
10271 180 : return GDALDriver::GetMetadata(pszDomain);
10272 : }
10273 :
10274 : private:
10275 : std::mutex m_oMutex{};
10276 : bool m_bInitialized = false;
10277 :
10278 103 : void InitializeDCAPVirtualIO()
10279 : {
10280 103 : if (!m_bInitialized)
10281 : {
10282 12 : m_bInitialized = true;
10283 :
10284 : #ifdef ENABLE_UFFD
10285 12 : if (CPLIsUserFaultMappingSupported())
10286 : {
10287 12 : SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
10288 : }
10289 : #endif
10290 : }
10291 103 : }
10292 : };
10293 :
10294 1391 : const char *GDALnetCDFDriver::GetMetadataItem(const char *pszName,
10295 : const char *pszDomain)
10296 : {
10297 2782 : std::lock_guard oLock(m_oMutex);
10298 1391 : if (EQUAL(pszName, GDAL_DCAP_VIRTUALIO))
10299 : {
10300 13 : InitializeDCAPVirtualIO();
10301 : }
10302 2782 : return GDALDriver::GetMetadataItem(pszName, pszDomain);
10303 : }
10304 :
10305 19 : void GDALRegister_netCDF()
10306 :
10307 : {
10308 19 : if (!GDAL_CHECK_VERSION("netCDF driver"))
10309 0 : return;
10310 :
10311 19 : if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
10312 0 : return;
10313 :
10314 19 : GDALDriver *poDriver = new GDALnetCDFDriver();
10315 19 : netCDFDriverSetCommonMetadata(poDriver);
10316 :
10317 19 : poDriver->SetMetadataItem("NETCDF_CONVENTIONS",
10318 19 : GDAL_DEFAULT_NCDF_CONVENTIONS);
10319 19 : poDriver->SetMetadataItem("NETCDF_VERSION", nc_inq_libvers());
10320 :
10321 : // Set pfns and register driver.
10322 19 : poDriver->pfnOpen = netCDFDataset::Open;
10323 19 : poDriver->pfnCreateCopy = netCDFDataset::CreateCopy;
10324 19 : poDriver->pfnCreate = netCDFDataset::Create;
10325 19 : poDriver->pfnCreateMultiDimensional = netCDFDataset::CreateMultiDimensional;
10326 19 : poDriver->pfnUnloadDriver = NCDFUnloadDriver;
10327 :
10328 19 : GetGDALDriverManager()->RegisterDriver(poDriver);
10329 : }
10330 :
10331 : /************************************************************************/
10332 : /* New functions */
10333 : /************************************************************************/
10334 :
10335 : /* Test for GDAL version string >= target */
10336 241 : static bool NCDFIsGDALVersionGTE(const char *pszVersion, int nTarget)
10337 : {
10338 :
10339 : // Valid strings are "GDAL 1.9dev, released 2011/01/18" and "GDAL 1.8.1 ".
10340 241 : if (pszVersion == nullptr || EQUAL(pszVersion, ""))
10341 0 : return false;
10342 241 : else if (!STARTS_WITH_CI(pszVersion, "GDAL "))
10343 0 : return false;
10344 : // 2.0dev of 2011/12/29 has been later renamed as 1.10dev.
10345 241 : else if (EQUAL("GDAL 2.0dev, released 2011/12/29", pszVersion))
10346 0 : return nTarget <= GDAL_COMPUTE_VERSION(1, 10, 0);
10347 241 : else if (STARTS_WITH_CI(pszVersion, "GDAL 1.9dev"))
10348 2 : return nTarget <= 1900;
10349 239 : else if (STARTS_WITH_CI(pszVersion, "GDAL 1.8dev"))
10350 0 : return nTarget <= 1800;
10351 :
10352 239 : char **papszTokens = CSLTokenizeString2(pszVersion + 5, ".", 0);
10353 :
10354 239 : int nVersions[] = {0, 0, 0, 0};
10355 956 : for (int iToken = 0; papszTokens && iToken < 4 && papszTokens[iToken];
10356 : iToken++)
10357 : {
10358 717 : nVersions[iToken] = atoi(papszTokens[iToken]);
10359 717 : if (nVersions[iToken] < 0)
10360 0 : nVersions[iToken] = 0;
10361 717 : else if (nVersions[iToken] > 99)
10362 0 : nVersions[iToken] = 99;
10363 : }
10364 :
10365 239 : int nVersion = 0;
10366 239 : if (nVersions[0] > 1 || nVersions[1] >= 10)
10367 239 : nVersion =
10368 239 : GDAL_COMPUTE_VERSION(nVersions[0], nVersions[1], nVersions[2]);
10369 : else
10370 0 : nVersion = nVersions[0] * 1000 + nVersions[1] * 100 +
10371 0 : nVersions[2] * 10 + nVersions[3];
10372 :
10373 239 : CSLDestroy(papszTokens);
10374 239 : return nTarget <= nVersion;
10375 : }
10376 :
10377 : // Add Conventions, GDAL version and history.
10378 167 : static void NCDFAddGDALHistory(int fpImage, const char *pszFilename,
10379 : bool bWriteGDALVersion, bool bWriteGDALHistory,
10380 : const char *pszOldHist,
10381 : const char *pszFunctionName,
10382 : const char *pszCFVersion)
10383 : {
10384 167 : if (pszCFVersion == nullptr)
10385 : {
10386 42 : pszCFVersion = GDAL_DEFAULT_NCDF_CONVENTIONS;
10387 : }
10388 167 : int status = nc_put_att_text(fpImage, NC_GLOBAL, "Conventions",
10389 : strlen(pszCFVersion), pszCFVersion);
10390 167 : NCDF_ERR(status);
10391 :
10392 167 : if (bWriteGDALVersion)
10393 : {
10394 165 : const char *pszNCDF_GDAL = GDALVersionInfo("--version");
10395 165 : status = nc_put_att_text(fpImage, NC_GLOBAL, "GDAL",
10396 : strlen(pszNCDF_GDAL), pszNCDF_GDAL);
10397 165 : NCDF_ERR(status);
10398 : }
10399 :
10400 167 : if (bWriteGDALHistory)
10401 : {
10402 : // Add history.
10403 330 : CPLString osTmp;
10404 : #ifdef GDAL_SET_CMD_LINE_DEFINED_TMP
10405 : if (!EQUAL(GDALGetCmdLine(), ""))
10406 : osTmp = GDALGetCmdLine();
10407 : else
10408 : osTmp =
10409 : CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
10410 : #else
10411 165 : osTmp = CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
10412 : #endif
10413 :
10414 165 : NCDFAddHistory(fpImage, osTmp.c_str(), pszOldHist);
10415 : }
10416 2 : else if (pszOldHist != nullptr)
10417 : {
10418 0 : status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
10419 : strlen(pszOldHist), pszOldHist);
10420 0 : NCDF_ERR(status);
10421 : }
10422 167 : }
10423 :
10424 : // Code taken from cdo and libcdi, used for writing the history attribute.
10425 :
10426 : // void cdoDefHistory(int fileID, char *histstring)
10427 165 : static void NCDFAddHistory(int fpImage, const char *pszAddHist,
10428 : const char *pszOldHist)
10429 : {
10430 : // Check pszOldHist - as if there was no previous history, it will be
10431 : // a null pointer - if so set as empty.
10432 165 : if (nullptr == pszOldHist)
10433 : {
10434 53 : pszOldHist = "";
10435 : }
10436 :
10437 : char strtime[32];
10438 165 : strtime[0] = '\0';
10439 :
10440 165 : time_t tp = time(nullptr);
10441 165 : if (tp != -1)
10442 : {
10443 : struct tm ltime;
10444 165 : VSILocalTime(&tp, <ime);
10445 165 : (void)strftime(strtime, sizeof(strtime),
10446 : "%a %b %d %H:%M:%S %Y: ", <ime);
10447 : }
10448 :
10449 : // status = nc_get_att_text(fpImage, NC_GLOBAL,
10450 : // "history", pszOldHist);
10451 : // printf("status: %d pszOldHist: [%s]\n",status,pszOldHist);
10452 :
10453 165 : size_t nNewHistSize =
10454 165 : strlen(pszOldHist) + strlen(strtime) + strlen(pszAddHist) + 1 + 1;
10455 : char *pszNewHist =
10456 165 : static_cast<char *>(CPLMalloc(nNewHistSize * sizeof(char)));
10457 :
10458 165 : strcpy(pszNewHist, strtime);
10459 165 : strcat(pszNewHist, pszAddHist);
10460 :
10461 : // int disableHistory = FALSE;
10462 : // if( !disableHistory )
10463 : {
10464 165 : if (!EQUAL(pszOldHist, ""))
10465 3 : strcat(pszNewHist, "\n");
10466 165 : strcat(pszNewHist, pszOldHist);
10467 : }
10468 :
10469 165 : const int status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
10470 : strlen(pszNewHist), pszNewHist);
10471 165 : NCDF_ERR(status);
10472 :
10473 165 : CPLFree(pszNewHist);
10474 165 : }
10475 :
10476 6185 : static CPLErr NCDFSafeStrcat(char **ppszDest, const char *pszSrc,
10477 : size_t *nDestSize)
10478 : {
10479 : /* Reallocate the data string until the content fits */
10480 6185 : while (*nDestSize < (strlen(*ppszDest) + strlen(pszSrc) + 1))
10481 : {
10482 408 : (*nDestSize) *= 2;
10483 408 : *ppszDest = static_cast<char *>(
10484 408 : CPLRealloc(reinterpret_cast<void *>(*ppszDest), *nDestSize));
10485 : #ifdef NCDF_DEBUG
10486 : CPLDebug("GDAL_netCDF", "NCDFSafeStrcat() resized str from %ld to %ld",
10487 : (*nDestSize) / 2, *nDestSize);
10488 : #endif
10489 : }
10490 5777 : strcat(*ppszDest, pszSrc);
10491 :
10492 5777 : return CE_None;
10493 : }
10494 :
10495 : /* helper function for NCDFGetAttr() */
10496 : /* if pdfValue != nullptr, sets *pdfValue to first value returned */
10497 : /* if ppszValue != nullptr, sets *ppszValue with all attribute values */
10498 : /* *ppszValue is the responsibility of the caller and must be freed */
10499 63669 : static CPLErr NCDFGetAttr1(int nCdfId, int nVarId, const char *pszAttrName,
10500 : double *pdfValue, char **ppszValue)
10501 : {
10502 63669 : nc_type nAttrType = NC_NAT;
10503 63669 : size_t nAttrLen = 0;
10504 :
10505 63669 : if (ppszValue)
10506 62514 : *ppszValue = nullptr;
10507 :
10508 63669 : int status = nc_inq_att(nCdfId, nVarId, pszAttrName, &nAttrType, &nAttrLen);
10509 63669 : if (status != NC_NOERR)
10510 34177 : return CE_Failure;
10511 :
10512 : #ifdef NCDF_DEBUG
10513 : CPLDebug("GDAL_netCDF", "NCDFGetAttr1(%s) len=%ld type=%d", pszAttrName,
10514 : nAttrLen, nAttrType);
10515 : #endif
10516 29492 : if (nAttrLen == 0 && nAttrType != NC_CHAR)
10517 1 : return CE_Failure;
10518 :
10519 : /* Allocate guaranteed minimum size (use 10 or 20 if not a string) */
10520 29491 : size_t nAttrValueSize = nAttrLen + 1;
10521 29491 : if (nAttrType != NC_CHAR && nAttrValueSize < 10)
10522 3232 : nAttrValueSize = 10;
10523 29491 : if (nAttrType == NC_DOUBLE && nAttrValueSize < 20)
10524 1578 : nAttrValueSize = 20;
10525 29491 : if (nAttrType == NC_INT64 && nAttrValueSize < 20)
10526 22 : nAttrValueSize = 22;
10527 : char *pszAttrValue =
10528 29491 : static_cast<char *>(CPLCalloc(nAttrValueSize, sizeof(char)));
10529 29491 : *pszAttrValue = '\0';
10530 :
10531 29491 : if (nAttrLen > 1 && nAttrType != NC_CHAR)
10532 602 : NCDFSafeStrcat(&pszAttrValue, "{", &nAttrValueSize);
10533 :
10534 29491 : double dfValue = 0.0;
10535 29491 : size_t m = 0;
10536 : char szTemp[256];
10537 29491 : bool bSetDoubleFromStr = false;
10538 :
10539 29491 : switch (nAttrType)
10540 : {
10541 26257 : case NC_CHAR:
10542 26257 : CPL_IGNORE_RET_VAL(
10543 26257 : nc_get_att_text(nCdfId, nVarId, pszAttrName, pszAttrValue));
10544 26257 : pszAttrValue[nAttrLen] = '\0';
10545 26257 : bSetDoubleFromStr = true;
10546 26257 : dfValue = 0.0;
10547 26257 : break;
10548 94 : case NC_BYTE:
10549 : {
10550 : signed char *pscTemp = static_cast<signed char *>(
10551 94 : CPLCalloc(nAttrLen, sizeof(signed char)));
10552 94 : nc_get_att_schar(nCdfId, nVarId, pszAttrName, pscTemp);
10553 94 : dfValue = static_cast<double>(pscTemp[0]);
10554 94 : if (nAttrLen > 1)
10555 : {
10556 24 : for (m = 0; m < nAttrLen - 1; m++)
10557 : {
10558 13 : snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
10559 13 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10560 : }
10561 : }
10562 94 : snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
10563 94 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10564 94 : CPLFree(pscTemp);
10565 94 : break;
10566 : }
10567 487 : case NC_SHORT:
10568 : {
10569 : short *psTemp =
10570 487 : static_cast<short *>(CPLCalloc(nAttrLen, sizeof(short)));
10571 487 : nc_get_att_short(nCdfId, nVarId, pszAttrName, psTemp);
10572 487 : dfValue = static_cast<double>(psTemp[0]);
10573 487 : if (nAttrLen > 1)
10574 : {
10575 768 : for (m = 0; m < nAttrLen - 1; m++)
10576 : {
10577 384 : snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
10578 384 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10579 : }
10580 : }
10581 487 : snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
10582 487 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10583 487 : CPLFree(psTemp);
10584 487 : break;
10585 : }
10586 528 : case NC_INT:
10587 : {
10588 528 : int *pnTemp = static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
10589 528 : nc_get_att_int(nCdfId, nVarId, pszAttrName, pnTemp);
10590 528 : dfValue = static_cast<double>(pnTemp[0]);
10591 528 : if (nAttrLen > 1)
10592 : {
10593 218 : for (m = 0; m < nAttrLen - 1; m++)
10594 : {
10595 139 : snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
10596 139 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10597 : }
10598 : }
10599 528 : snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
10600 528 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10601 528 : CPLFree(pnTemp);
10602 528 : break;
10603 : }
10604 395 : case NC_FLOAT:
10605 : {
10606 : float *pfTemp =
10607 395 : static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
10608 395 : nc_get_att_float(nCdfId, nVarId, pszAttrName, pfTemp);
10609 395 : dfValue = static_cast<double>(pfTemp[0]);
10610 395 : if (nAttrLen > 1)
10611 : {
10612 60 : for (m = 0; m < nAttrLen - 1; m++)
10613 : {
10614 30 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
10615 30 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10616 : }
10617 : }
10618 395 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
10619 395 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10620 395 : CPLFree(pfTemp);
10621 395 : break;
10622 : }
10623 1578 : case NC_DOUBLE:
10624 : {
10625 : double *pdfTemp =
10626 1578 : static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
10627 1578 : nc_get_att_double(nCdfId, nVarId, pszAttrName, pdfTemp);
10628 1578 : dfValue = pdfTemp[0];
10629 1578 : if (nAttrLen > 1)
10630 : {
10631 166 : for (m = 0; m < nAttrLen - 1; m++)
10632 : {
10633 90 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
10634 90 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10635 : }
10636 : }
10637 1578 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
10638 1578 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10639 1578 : CPLFree(pdfTemp);
10640 1578 : break;
10641 : }
10642 10 : case NC_STRING:
10643 : {
10644 : char **ppszTemp =
10645 10 : static_cast<char **>(CPLCalloc(nAttrLen, sizeof(char *)));
10646 10 : nc_get_att_string(nCdfId, nVarId, pszAttrName, ppszTemp);
10647 10 : bSetDoubleFromStr = true;
10648 10 : dfValue = 0.0;
10649 10 : if (nAttrLen > 1)
10650 : {
10651 19 : for (m = 0; m < nAttrLen - 1; m++)
10652 : {
10653 12 : NCDFSafeStrcat(&pszAttrValue,
10654 12 : ppszTemp[m] ? ppszTemp[m] : "{NULL}",
10655 : &nAttrValueSize);
10656 12 : NCDFSafeStrcat(&pszAttrValue, ",", &nAttrValueSize);
10657 : }
10658 : }
10659 10 : NCDFSafeStrcat(&pszAttrValue, ppszTemp[m] ? ppszTemp[m] : "{NULL}",
10660 : &nAttrValueSize);
10661 10 : nc_free_string(nAttrLen, ppszTemp);
10662 10 : CPLFree(ppszTemp);
10663 10 : break;
10664 : }
10665 28 : case NC_UBYTE:
10666 : {
10667 : unsigned char *pucTemp = static_cast<unsigned char *>(
10668 28 : CPLCalloc(nAttrLen, sizeof(unsigned char)));
10669 28 : nc_get_att_uchar(nCdfId, nVarId, pszAttrName, pucTemp);
10670 28 : dfValue = static_cast<double>(pucTemp[0]);
10671 28 : if (nAttrLen > 1)
10672 : {
10673 0 : for (m = 0; m < nAttrLen - 1; m++)
10674 : {
10675 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
10676 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10677 : }
10678 : }
10679 28 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
10680 28 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10681 28 : CPLFree(pucTemp);
10682 28 : break;
10683 : }
10684 26 : case NC_USHORT:
10685 : {
10686 : unsigned short *pusTemp;
10687 : pusTemp = static_cast<unsigned short *>(
10688 26 : CPLCalloc(nAttrLen, sizeof(unsigned short)));
10689 26 : nc_get_att_ushort(nCdfId, nVarId, pszAttrName, pusTemp);
10690 26 : dfValue = static_cast<double>(pusTemp[0]);
10691 26 : if (nAttrLen > 1)
10692 : {
10693 10 : for (m = 0; m < nAttrLen - 1; m++)
10694 : {
10695 5 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
10696 5 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10697 : }
10698 : }
10699 26 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
10700 26 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10701 26 : CPLFree(pusTemp);
10702 26 : break;
10703 : }
10704 18 : case NC_UINT:
10705 : {
10706 : unsigned int *punTemp =
10707 18 : static_cast<unsigned int *>(CPLCalloc(nAttrLen, sizeof(int)));
10708 18 : nc_get_att_uint(nCdfId, nVarId, pszAttrName, punTemp);
10709 18 : dfValue = static_cast<double>(punTemp[0]);
10710 18 : if (nAttrLen > 1)
10711 : {
10712 0 : for (m = 0; m < nAttrLen - 1; m++)
10713 : {
10714 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
10715 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10716 : }
10717 : }
10718 18 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
10719 18 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10720 18 : CPLFree(punTemp);
10721 18 : break;
10722 : }
10723 22 : case NC_INT64:
10724 : {
10725 : GIntBig *panTemp =
10726 22 : static_cast<GIntBig *>(CPLCalloc(nAttrLen, sizeof(GIntBig)));
10727 22 : nc_get_att_longlong(nCdfId, nVarId, pszAttrName, panTemp);
10728 22 : dfValue = static_cast<double>(panTemp[0]);
10729 22 : if (nAttrLen > 1)
10730 : {
10731 0 : for (m = 0; m < nAttrLen - 1; m++)
10732 : {
10733 0 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",",
10734 0 : panTemp[m]);
10735 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10736 : }
10737 : }
10738 22 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, panTemp[m]);
10739 22 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10740 22 : CPLFree(panTemp);
10741 22 : break;
10742 : }
10743 22 : case NC_UINT64:
10744 : {
10745 : GUIntBig *panTemp =
10746 22 : static_cast<GUIntBig *>(CPLCalloc(nAttrLen, sizeof(GUIntBig)));
10747 22 : nc_get_att_ulonglong(nCdfId, nVarId, pszAttrName, panTemp);
10748 22 : dfValue = static_cast<double>(panTemp[0]);
10749 22 : if (nAttrLen > 1)
10750 : {
10751 0 : for (m = 0; m < nAttrLen - 1; m++)
10752 : {
10753 0 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",",
10754 0 : panTemp[m]);
10755 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10756 : }
10757 : }
10758 22 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, panTemp[m]);
10759 22 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10760 22 : CPLFree(panTemp);
10761 22 : break;
10762 : }
10763 26 : default:
10764 26 : CPLDebug("GDAL_netCDF",
10765 : "NCDFGetAttr unsupported type %d for attribute %s",
10766 : nAttrType, pszAttrName);
10767 26 : break;
10768 : }
10769 :
10770 29491 : if (nAttrLen > 1 && nAttrType != NC_CHAR)
10771 602 : NCDFSafeStrcat(&pszAttrValue, "}", &nAttrValueSize);
10772 :
10773 29491 : if (bSetDoubleFromStr)
10774 : {
10775 26267 : if (CPLGetValueType(pszAttrValue) == CPL_VALUE_STRING)
10776 : {
10777 26085 : if (ppszValue == nullptr && pdfValue != nullptr)
10778 : {
10779 1 : CPLFree(pszAttrValue);
10780 1 : return CE_Failure;
10781 : }
10782 : }
10783 26266 : dfValue = CPLAtof(pszAttrValue);
10784 : }
10785 :
10786 : /* set return values */
10787 29490 : if (ppszValue)
10788 29178 : *ppszValue = pszAttrValue;
10789 : else
10790 312 : CPLFree(pszAttrValue);
10791 :
10792 29490 : if (pdfValue)
10793 312 : *pdfValue = dfValue;
10794 :
10795 29490 : return CE_None;
10796 : }
10797 :
10798 : /* sets pdfValue to first value found */
10799 1155 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
10800 : double *pdfValue)
10801 : {
10802 1155 : return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, pdfValue, nullptr);
10803 : }
10804 :
10805 : /* pszValue is the responsibility of the caller and must be freed */
10806 62514 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
10807 : char **pszValue)
10808 : {
10809 62514 : return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, nullptr, pszValue);
10810 : }
10811 :
10812 : /* By default write NC_CHAR, but detect for int/float/double and */
10813 : /* NC4 string arrays */
10814 106 : static CPLErr NCDFPutAttr(int nCdfId, int nVarId, const char *pszAttrName,
10815 : const char *pszValue)
10816 : {
10817 106 : int status = 0;
10818 106 : char *pszTemp = nullptr;
10819 :
10820 : /* get the attribute values as tokens */
10821 106 : char **papszValues = NCDFTokenizeArray(pszValue);
10822 106 : if (papszValues == nullptr)
10823 0 : return CE_Failure;
10824 :
10825 106 : size_t nAttrLen = CSLCount(papszValues);
10826 :
10827 : /* first detect type */
10828 106 : nc_type nAttrType = NC_CHAR;
10829 106 : nc_type nTmpAttrType = NC_CHAR;
10830 225 : for (size_t i = 0; i < nAttrLen; i++)
10831 : {
10832 119 : nTmpAttrType = NC_CHAR;
10833 119 : bool bFoundType = false;
10834 119 : errno = 0;
10835 119 : int nValue = static_cast<int>(strtol(papszValues[i], &pszTemp, 10));
10836 : /* test for int */
10837 : /* TODO test for Byte and short - can this be done safely? */
10838 119 : if (errno == 0 && papszValues[i] != pszTemp && *pszTemp == 0)
10839 : {
10840 : char szTemp[256];
10841 19 : CPLsnprintf(szTemp, sizeof(szTemp), "%d", nValue);
10842 19 : if (EQUAL(szTemp, papszValues[i]))
10843 : {
10844 19 : bFoundType = true;
10845 19 : nTmpAttrType = NC_INT;
10846 : }
10847 : else
10848 : {
10849 : unsigned int unValue = static_cast<unsigned int>(
10850 0 : strtoul(papszValues[i], &pszTemp, 10));
10851 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", unValue);
10852 0 : if (EQUAL(szTemp, papszValues[i]))
10853 : {
10854 0 : bFoundType = true;
10855 0 : nTmpAttrType = NC_UINT;
10856 : }
10857 : }
10858 : }
10859 119 : if (!bFoundType)
10860 : {
10861 : /* test for double */
10862 100 : errno = 0;
10863 100 : double dfValue = CPLStrtod(papszValues[i], &pszTemp);
10864 100 : if ((errno == 0) && (papszValues[i] != pszTemp) && (*pszTemp == 0))
10865 : {
10866 : // Test for float instead of double.
10867 : // strtof() is C89, which is not available in MSVC.
10868 : // See if we loose precision if we cast to float and write to
10869 : // char*.
10870 14 : float fValue = float(dfValue);
10871 : char szTemp[256];
10872 14 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", fValue);
10873 14 : if (EQUAL(szTemp, papszValues[i]))
10874 8 : nTmpAttrType = NC_FLOAT;
10875 : else
10876 6 : nTmpAttrType = NC_DOUBLE;
10877 : }
10878 : }
10879 119 : if ((nTmpAttrType <= NC_DOUBLE && nAttrType <= NC_DOUBLE &&
10880 99 : nTmpAttrType > nAttrType) ||
10881 99 : (nTmpAttrType == NC_UINT && nAttrType < NC_FLOAT) ||
10882 5 : (nTmpAttrType >= NC_FLOAT && nAttrType == NC_UINT))
10883 20 : nAttrType = nTmpAttrType;
10884 : }
10885 :
10886 : #ifdef DEBUG
10887 106 : if (EQUAL(pszAttrName, "DEBUG_EMPTY_DOUBLE_ATTR"))
10888 : {
10889 0 : nAttrType = NC_DOUBLE;
10890 0 : nAttrLen = 0;
10891 : }
10892 : #endif
10893 :
10894 : /* now write the data */
10895 106 : if (nAttrType == NC_CHAR)
10896 : {
10897 86 : int nTmpFormat = 0;
10898 86 : if (nAttrLen > 1)
10899 : {
10900 0 : status = nc_inq_format(nCdfId, &nTmpFormat);
10901 0 : NCDF_ERR(status);
10902 : }
10903 86 : if (nAttrLen > 1 && nTmpFormat == NCDF_FORMAT_NC4)
10904 0 : status = nc_put_att_string(nCdfId, nVarId, pszAttrName, nAttrLen,
10905 : const_cast<const char **>(papszValues));
10906 : else
10907 86 : status = nc_put_att_text(nCdfId, nVarId, pszAttrName,
10908 : strlen(pszValue), pszValue);
10909 86 : NCDF_ERR(status);
10910 : }
10911 : else
10912 : {
10913 20 : switch (nAttrType)
10914 : {
10915 11 : case NC_INT:
10916 : {
10917 : int *pnTemp =
10918 11 : static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
10919 30 : for (size_t i = 0; i < nAttrLen; i++)
10920 : {
10921 19 : pnTemp[i] =
10922 19 : static_cast<int>(strtol(papszValues[i], &pszTemp, 10));
10923 : }
10924 11 : status = nc_put_att_int(nCdfId, nVarId, pszAttrName, NC_INT,
10925 : nAttrLen, pnTemp);
10926 11 : NCDF_ERR(status);
10927 11 : CPLFree(pnTemp);
10928 11 : break;
10929 : }
10930 0 : case NC_UINT:
10931 : {
10932 : unsigned int *punTemp = static_cast<unsigned int *>(
10933 0 : CPLCalloc(nAttrLen, sizeof(unsigned int)));
10934 0 : for (size_t i = 0; i < nAttrLen; i++)
10935 : {
10936 0 : punTemp[i] = static_cast<unsigned int>(
10937 0 : strtol(papszValues[i], &pszTemp, 10));
10938 : }
10939 0 : status = nc_put_att_uint(nCdfId, nVarId, pszAttrName, NC_UINT,
10940 : nAttrLen, punTemp);
10941 0 : NCDF_ERR(status);
10942 0 : CPLFree(punTemp);
10943 0 : break;
10944 : }
10945 6 : case NC_FLOAT:
10946 : {
10947 : float *pfTemp =
10948 6 : static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
10949 14 : for (size_t i = 0; i < nAttrLen; i++)
10950 : {
10951 8 : pfTemp[i] =
10952 8 : static_cast<float>(CPLStrtod(papszValues[i], &pszTemp));
10953 : }
10954 6 : status = nc_put_att_float(nCdfId, nVarId, pszAttrName, NC_FLOAT,
10955 : nAttrLen, pfTemp);
10956 6 : NCDF_ERR(status);
10957 6 : CPLFree(pfTemp);
10958 6 : break;
10959 : }
10960 3 : case NC_DOUBLE:
10961 : {
10962 : double *pdfTemp =
10963 3 : static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
10964 9 : for (size_t i = 0; i < nAttrLen; i++)
10965 : {
10966 6 : pdfTemp[i] = CPLStrtod(papszValues[i], &pszTemp);
10967 : }
10968 3 : status = nc_put_att_double(nCdfId, nVarId, pszAttrName,
10969 : NC_DOUBLE, nAttrLen, pdfTemp);
10970 3 : NCDF_ERR(status);
10971 3 : CPLFree(pdfTemp);
10972 3 : break;
10973 : }
10974 0 : default:
10975 0 : if (papszValues)
10976 0 : CSLDestroy(papszValues);
10977 0 : return CE_Failure;
10978 : break;
10979 : }
10980 : }
10981 :
10982 106 : if (papszValues)
10983 106 : CSLDestroy(papszValues);
10984 :
10985 106 : return CE_None;
10986 : }
10987 :
10988 78 : static CPLErr NCDFGet1DVar(int nCdfId, int nVarId, char **pszValue)
10989 : {
10990 : /* get var information */
10991 78 : int nVarDimId = -1;
10992 78 : int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
10993 78 : if (status != NC_NOERR || nVarDimId != 1)
10994 0 : return CE_Failure;
10995 :
10996 78 : status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
10997 78 : if (status != NC_NOERR)
10998 0 : return CE_Failure;
10999 :
11000 78 : nc_type nVarType = NC_NAT;
11001 78 : status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
11002 78 : if (status != NC_NOERR)
11003 0 : return CE_Failure;
11004 :
11005 78 : size_t nVarLen = 0;
11006 78 : status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
11007 78 : if (status != NC_NOERR)
11008 0 : return CE_Failure;
11009 :
11010 78 : size_t start[1] = {0};
11011 78 : size_t count[1] = {nVarLen};
11012 :
11013 : /* Allocate guaranteed minimum size */
11014 78 : size_t nVarValueSize = NCDF_MAX_STR_LEN;
11015 : char *pszVarValue =
11016 78 : static_cast<char *>(CPLCalloc(nVarValueSize, sizeof(char)));
11017 78 : *pszVarValue = '\0';
11018 :
11019 78 : if (nVarLen == 0)
11020 : {
11021 : /* set return values */
11022 1 : *pszValue = pszVarValue;
11023 :
11024 1 : return CE_None;
11025 : }
11026 :
11027 77 : if (nVarLen > 1 && nVarType != NC_CHAR)
11028 42 : NCDFSafeStrcat(&pszVarValue, "{", &nVarValueSize);
11029 :
11030 77 : switch (nVarType)
11031 : {
11032 0 : case NC_CHAR:
11033 0 : nc_get_vara_text(nCdfId, nVarId, start, count, pszVarValue);
11034 0 : pszVarValue[nVarLen] = '\0';
11035 0 : break;
11036 0 : case NC_BYTE:
11037 : {
11038 : signed char *pscTemp = static_cast<signed char *>(
11039 0 : CPLCalloc(nVarLen, sizeof(signed char)));
11040 0 : nc_get_vara_schar(nCdfId, nVarId, start, count, pscTemp);
11041 : char szTemp[256];
11042 0 : size_t m = 0;
11043 0 : for (; m < nVarLen - 1; m++)
11044 : {
11045 0 : snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
11046 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11047 : }
11048 0 : snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
11049 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11050 0 : CPLFree(pscTemp);
11051 0 : break;
11052 : }
11053 0 : case NC_SHORT:
11054 : {
11055 : short *psTemp =
11056 0 : static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
11057 0 : nc_get_vara_short(nCdfId, nVarId, start, count, psTemp);
11058 : char szTemp[256];
11059 0 : size_t m = 0;
11060 0 : for (; m < nVarLen - 1; m++)
11061 : {
11062 0 : snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
11063 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11064 : }
11065 0 : snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
11066 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11067 0 : CPLFree(psTemp);
11068 0 : break;
11069 : }
11070 21 : case NC_INT:
11071 : {
11072 21 : int *pnTemp = static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
11073 21 : nc_get_vara_int(nCdfId, nVarId, start, count, pnTemp);
11074 : char szTemp[256];
11075 21 : size_t m = 0;
11076 44 : for (; m < nVarLen - 1; m++)
11077 : {
11078 23 : snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
11079 23 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11080 : }
11081 21 : snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
11082 21 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11083 21 : CPLFree(pnTemp);
11084 21 : break;
11085 : }
11086 8 : case NC_FLOAT:
11087 : {
11088 : float *pfTemp =
11089 8 : static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
11090 8 : nc_get_vara_float(nCdfId, nVarId, start, count, pfTemp);
11091 : char szTemp[256];
11092 8 : size_t m = 0;
11093 325 : for (; m < nVarLen - 1; m++)
11094 : {
11095 317 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
11096 317 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11097 : }
11098 8 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
11099 8 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11100 8 : CPLFree(pfTemp);
11101 8 : break;
11102 : }
11103 47 : case NC_DOUBLE:
11104 : {
11105 : double *pdfTemp =
11106 47 : static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
11107 47 : nc_get_vara_double(nCdfId, nVarId, start, count, pdfTemp);
11108 : char szTemp[256];
11109 47 : size_t m = 0;
11110 225 : for (; m < nVarLen - 1; m++)
11111 : {
11112 178 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
11113 178 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11114 : }
11115 47 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
11116 47 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11117 47 : CPLFree(pdfTemp);
11118 47 : break;
11119 : }
11120 0 : case NC_STRING:
11121 : {
11122 : char **ppszTemp =
11123 0 : static_cast<char **>(CPLCalloc(nVarLen, sizeof(char *)));
11124 0 : nc_get_vara_string(nCdfId, nVarId, start, count, ppszTemp);
11125 0 : size_t m = 0;
11126 0 : for (; m < nVarLen - 1; m++)
11127 : {
11128 0 : NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
11129 0 : NCDFSafeStrcat(&pszVarValue, ",", &nVarValueSize);
11130 : }
11131 0 : NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
11132 0 : nc_free_string(nVarLen, ppszTemp);
11133 0 : CPLFree(ppszTemp);
11134 0 : break;
11135 : }
11136 0 : case NC_UBYTE:
11137 : {
11138 : unsigned char *pucTemp;
11139 : pucTemp = static_cast<unsigned char *>(
11140 0 : CPLCalloc(nVarLen, sizeof(unsigned char)));
11141 0 : nc_get_vara_uchar(nCdfId, nVarId, start, count, pucTemp);
11142 : char szTemp[256];
11143 0 : size_t m = 0;
11144 0 : for (; m < nVarLen - 1; m++)
11145 : {
11146 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
11147 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11148 : }
11149 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
11150 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11151 0 : CPLFree(pucTemp);
11152 0 : break;
11153 : }
11154 0 : case NC_USHORT:
11155 : {
11156 : unsigned short *pusTemp;
11157 : pusTemp = static_cast<unsigned short *>(
11158 0 : CPLCalloc(nVarLen, sizeof(unsigned short)));
11159 0 : nc_get_vara_ushort(nCdfId, nVarId, start, count, pusTemp);
11160 : char szTemp[256];
11161 0 : size_t m = 0;
11162 0 : for (; m < nVarLen - 1; m++)
11163 : {
11164 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
11165 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11166 : }
11167 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
11168 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11169 0 : CPLFree(pusTemp);
11170 0 : break;
11171 : }
11172 0 : case NC_UINT:
11173 : {
11174 : unsigned int *punTemp;
11175 : punTemp = static_cast<unsigned int *>(
11176 0 : CPLCalloc(nVarLen, sizeof(unsigned int)));
11177 0 : nc_get_vara_uint(nCdfId, nVarId, start, count, punTemp);
11178 : char szTemp[256];
11179 0 : size_t m = 0;
11180 0 : for (; m < nVarLen - 1; m++)
11181 : {
11182 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
11183 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11184 : }
11185 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
11186 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11187 0 : CPLFree(punTemp);
11188 0 : break;
11189 : }
11190 1 : case NC_INT64:
11191 : {
11192 : long long *pnTemp =
11193 1 : static_cast<long long *>(CPLCalloc(nVarLen, sizeof(long long)));
11194 1 : nc_get_vara_longlong(nCdfId, nVarId, start, count, pnTemp);
11195 : char szTemp[256];
11196 1 : size_t m = 0;
11197 2 : for (; m < nVarLen - 1; m++)
11198 : {
11199 1 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",", pnTemp[m]);
11200 1 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11201 : }
11202 1 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, pnTemp[m]);
11203 1 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11204 1 : CPLFree(pnTemp);
11205 1 : break;
11206 : }
11207 0 : case NC_UINT64:
11208 : {
11209 : unsigned long long *pnTemp = static_cast<unsigned long long *>(
11210 0 : CPLCalloc(nVarLen, sizeof(unsigned long long)));
11211 0 : nc_get_vara_ulonglong(nCdfId, nVarId, start, count, pnTemp);
11212 : char szTemp[256];
11213 0 : size_t m = 0;
11214 0 : for (; m < nVarLen - 1; m++)
11215 : {
11216 0 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",", pnTemp[m]);
11217 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11218 : }
11219 0 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, pnTemp[m]);
11220 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11221 0 : CPLFree(pnTemp);
11222 0 : break;
11223 : }
11224 0 : default:
11225 0 : CPLDebug("GDAL_netCDF", "NCDFGetVar1D unsupported type %d",
11226 : nVarType);
11227 0 : CPLFree(pszVarValue);
11228 0 : pszVarValue = nullptr;
11229 0 : break;
11230 : }
11231 :
11232 77 : if (pszVarValue != nullptr && nVarLen > 1 && nVarType != NC_CHAR)
11233 42 : NCDFSafeStrcat(&pszVarValue, "}", &nVarValueSize);
11234 :
11235 : /* set return values */
11236 77 : *pszValue = pszVarValue;
11237 :
11238 77 : return CE_None;
11239 : }
11240 :
11241 8 : static CPLErr NCDFPut1DVar(int nCdfId, int nVarId, const char *pszValue)
11242 : {
11243 8 : if (EQUAL(pszValue, ""))
11244 0 : return CE_Failure;
11245 :
11246 : /* get var information */
11247 8 : int nVarDimId = -1;
11248 8 : int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
11249 8 : if (status != NC_NOERR || nVarDimId != 1)
11250 0 : return CE_Failure;
11251 :
11252 8 : status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
11253 8 : if (status != NC_NOERR)
11254 0 : return CE_Failure;
11255 :
11256 8 : nc_type nVarType = NC_CHAR;
11257 8 : status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
11258 8 : if (status != NC_NOERR)
11259 0 : return CE_Failure;
11260 :
11261 8 : size_t nVarLen = 0;
11262 8 : status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
11263 8 : if (status != NC_NOERR)
11264 0 : return CE_Failure;
11265 :
11266 8 : size_t start[1] = {0};
11267 8 : size_t count[1] = {nVarLen};
11268 :
11269 : /* get the values as tokens */
11270 8 : char **papszValues = NCDFTokenizeArray(pszValue);
11271 8 : if (papszValues == nullptr)
11272 0 : return CE_Failure;
11273 :
11274 8 : nVarLen = CSLCount(papszValues);
11275 :
11276 : /* now write the data */
11277 8 : if (nVarType == NC_CHAR)
11278 : {
11279 0 : status = nc_put_vara_text(nCdfId, nVarId, start, count, pszValue);
11280 0 : NCDF_ERR(status);
11281 : }
11282 : else
11283 : {
11284 8 : switch (nVarType)
11285 : {
11286 0 : case NC_BYTE:
11287 : {
11288 : signed char *pscTemp = static_cast<signed char *>(
11289 0 : CPLCalloc(nVarLen, sizeof(signed char)));
11290 0 : for (size_t i = 0; i < nVarLen; i++)
11291 : {
11292 0 : char *pszTemp = nullptr;
11293 0 : pscTemp[i] = static_cast<signed char>(
11294 0 : strtol(papszValues[i], &pszTemp, 10));
11295 : }
11296 : status =
11297 0 : nc_put_vara_schar(nCdfId, nVarId, start, count, pscTemp);
11298 0 : NCDF_ERR(status);
11299 0 : CPLFree(pscTemp);
11300 0 : break;
11301 : }
11302 0 : case NC_SHORT:
11303 : {
11304 : short *psTemp =
11305 0 : static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
11306 0 : for (size_t i = 0; i < nVarLen; i++)
11307 : {
11308 0 : char *pszTemp = nullptr;
11309 0 : psTemp[i] = static_cast<short>(
11310 0 : strtol(papszValues[i], &pszTemp, 10));
11311 : }
11312 : status =
11313 0 : nc_put_vara_short(nCdfId, nVarId, start, count, psTemp);
11314 0 : NCDF_ERR(status);
11315 0 : CPLFree(psTemp);
11316 0 : break;
11317 : }
11318 3 : case NC_INT:
11319 : {
11320 : int *pnTemp =
11321 3 : static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
11322 11 : for (size_t i = 0; i < nVarLen; i++)
11323 : {
11324 8 : char *pszTemp = nullptr;
11325 8 : pnTemp[i] =
11326 8 : static_cast<int>(strtol(papszValues[i], &pszTemp, 10));
11327 : }
11328 3 : status = nc_put_vara_int(nCdfId, nVarId, start, count, pnTemp);
11329 3 : NCDF_ERR(status);
11330 3 : CPLFree(pnTemp);
11331 3 : break;
11332 : }
11333 0 : case NC_FLOAT:
11334 : {
11335 : float *pfTemp =
11336 0 : static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
11337 0 : for (size_t i = 0; i < nVarLen; i++)
11338 : {
11339 0 : char *pszTemp = nullptr;
11340 0 : pfTemp[i] =
11341 0 : static_cast<float>(CPLStrtod(papszValues[i], &pszTemp));
11342 : }
11343 : status =
11344 0 : nc_put_vara_float(nCdfId, nVarId, start, count, pfTemp);
11345 0 : NCDF_ERR(status);
11346 0 : CPLFree(pfTemp);
11347 0 : break;
11348 : }
11349 5 : case NC_DOUBLE:
11350 : {
11351 : double *pdfTemp =
11352 5 : static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
11353 19 : for (size_t i = 0; i < nVarLen; i++)
11354 : {
11355 14 : char *pszTemp = nullptr;
11356 14 : pdfTemp[i] = CPLStrtod(papszValues[i], &pszTemp);
11357 : }
11358 : status =
11359 5 : nc_put_vara_double(nCdfId, nVarId, start, count, pdfTemp);
11360 5 : NCDF_ERR(status);
11361 5 : CPLFree(pdfTemp);
11362 5 : break;
11363 : }
11364 0 : default:
11365 : {
11366 0 : int nTmpFormat = 0;
11367 0 : status = nc_inq_format(nCdfId, &nTmpFormat);
11368 0 : NCDF_ERR(status);
11369 0 : if (nTmpFormat == NCDF_FORMAT_NC4)
11370 : {
11371 0 : switch (nVarType)
11372 : {
11373 0 : case NC_STRING:
11374 : {
11375 : status =
11376 0 : nc_put_vara_string(nCdfId, nVarId, start, count,
11377 : (const char **)papszValues);
11378 0 : NCDF_ERR(status);
11379 0 : break;
11380 : }
11381 0 : case NC_UBYTE:
11382 : {
11383 : unsigned char *pucTemp =
11384 : static_cast<unsigned char *>(
11385 0 : CPLCalloc(nVarLen, sizeof(unsigned char)));
11386 0 : for (size_t i = 0; i < nVarLen; i++)
11387 : {
11388 0 : char *pszTemp = nullptr;
11389 0 : pucTemp[i] = static_cast<unsigned char>(
11390 0 : strtoul(papszValues[i], &pszTemp, 10));
11391 : }
11392 0 : status = nc_put_vara_uchar(nCdfId, nVarId, start,
11393 : count, pucTemp);
11394 0 : NCDF_ERR(status);
11395 0 : CPLFree(pucTemp);
11396 0 : break;
11397 : }
11398 0 : case NC_USHORT:
11399 : {
11400 : unsigned short *pusTemp =
11401 : static_cast<unsigned short *>(
11402 0 : CPLCalloc(nVarLen, sizeof(unsigned short)));
11403 0 : for (size_t i = 0; i < nVarLen; i++)
11404 : {
11405 0 : char *pszTemp = nullptr;
11406 0 : pusTemp[i] = static_cast<unsigned short>(
11407 0 : strtoul(papszValues[i], &pszTemp, 10));
11408 : }
11409 0 : status = nc_put_vara_ushort(nCdfId, nVarId, start,
11410 : count, pusTemp);
11411 0 : NCDF_ERR(status);
11412 0 : CPLFree(pusTemp);
11413 0 : break;
11414 : }
11415 0 : case NC_UINT:
11416 : {
11417 : unsigned int *punTemp = static_cast<unsigned int *>(
11418 0 : CPLCalloc(nVarLen, sizeof(unsigned int)));
11419 0 : for (size_t i = 0; i < nVarLen; i++)
11420 : {
11421 0 : char *pszTemp = nullptr;
11422 0 : punTemp[i] = static_cast<unsigned int>(
11423 0 : strtoul(papszValues[i], &pszTemp, 10));
11424 : }
11425 0 : status = nc_put_vara_uint(nCdfId, nVarId, start,
11426 : count, punTemp);
11427 0 : NCDF_ERR(status);
11428 0 : CPLFree(punTemp);
11429 0 : break;
11430 : }
11431 0 : default:
11432 0 : if (papszValues)
11433 0 : CSLDestroy(papszValues);
11434 0 : return CE_Failure;
11435 : break;
11436 : }
11437 : }
11438 0 : break;
11439 : }
11440 : }
11441 : }
11442 :
11443 8 : if (papszValues)
11444 8 : CSLDestroy(papszValues);
11445 :
11446 8 : return CE_None;
11447 : }
11448 :
11449 : /************************************************************************/
11450 : /* GetDefaultNoDataValue() */
11451 : /************************************************************************/
11452 :
11453 196 : double NCDFGetDefaultNoDataValue(int nCdfId, int nVarId, int nVarType,
11454 : bool &bGotNoData)
11455 :
11456 : {
11457 196 : int nNoFill = 0;
11458 196 : double dfNoData = 0.0;
11459 :
11460 196 : switch (nVarType)
11461 : {
11462 0 : case NC_CHAR:
11463 : case NC_BYTE:
11464 : case NC_UBYTE:
11465 : // Don't do default fill-values for bytes, too risky.
11466 : // This function should not be called in those cases.
11467 0 : CPLAssert(false);
11468 : break;
11469 24 : case NC_SHORT:
11470 : {
11471 24 : short nFillVal = 0;
11472 24 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11473 : NC_NOERR)
11474 : {
11475 24 : if (!nNoFill)
11476 : {
11477 23 : bGotNoData = true;
11478 23 : dfNoData = nFillVal;
11479 : }
11480 : }
11481 : else
11482 0 : dfNoData = NC_FILL_SHORT;
11483 24 : break;
11484 : }
11485 26 : case NC_INT:
11486 : {
11487 26 : int nFillVal = 0;
11488 26 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11489 : NC_NOERR)
11490 : {
11491 26 : if (!nNoFill)
11492 : {
11493 25 : bGotNoData = true;
11494 25 : dfNoData = nFillVal;
11495 : }
11496 : }
11497 : else
11498 0 : dfNoData = NC_FILL_INT;
11499 26 : break;
11500 : }
11501 79 : case NC_FLOAT:
11502 : {
11503 79 : float fFillVal = 0;
11504 79 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &fFillVal) ==
11505 : NC_NOERR)
11506 : {
11507 79 : if (!nNoFill)
11508 : {
11509 75 : bGotNoData = true;
11510 75 : dfNoData = fFillVal;
11511 : }
11512 : }
11513 : else
11514 0 : dfNoData = NC_FILL_FLOAT;
11515 79 : break;
11516 : }
11517 34 : case NC_DOUBLE:
11518 : {
11519 34 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &dfNoData) ==
11520 : NC_NOERR)
11521 : {
11522 34 : if (!nNoFill)
11523 : {
11524 34 : bGotNoData = true;
11525 : }
11526 : }
11527 : else
11528 0 : dfNoData = NC_FILL_DOUBLE;
11529 34 : break;
11530 : }
11531 7 : case NC_USHORT:
11532 : {
11533 7 : unsigned short nFillVal = 0;
11534 7 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11535 : NC_NOERR)
11536 : {
11537 7 : if (!nNoFill)
11538 : {
11539 7 : bGotNoData = true;
11540 7 : dfNoData = nFillVal;
11541 : }
11542 : }
11543 : else
11544 0 : dfNoData = NC_FILL_USHORT;
11545 7 : break;
11546 : }
11547 7 : case NC_UINT:
11548 : {
11549 7 : unsigned int nFillVal = 0;
11550 7 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11551 : NC_NOERR)
11552 : {
11553 7 : if (!nNoFill)
11554 : {
11555 7 : bGotNoData = true;
11556 7 : dfNoData = nFillVal;
11557 : }
11558 : }
11559 : else
11560 0 : dfNoData = NC_FILL_UINT;
11561 7 : break;
11562 : }
11563 19 : default:
11564 19 : dfNoData = 0.0;
11565 19 : break;
11566 : }
11567 :
11568 196 : return dfNoData;
11569 : }
11570 :
11571 : /************************************************************************/
11572 : /* NCDFGetDefaultNoDataValueAsInt64() */
11573 : /************************************************************************/
11574 :
11575 2 : int64_t NCDFGetDefaultNoDataValueAsInt64(int nCdfId, int nVarId,
11576 : bool &bGotNoData)
11577 :
11578 : {
11579 2 : int nNoFill = 0;
11580 2 : long long nFillVal = 0;
11581 2 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
11582 : {
11583 2 : if (!nNoFill)
11584 : {
11585 2 : bGotNoData = true;
11586 2 : return static_cast<int64_t>(nFillVal);
11587 : }
11588 : }
11589 : else
11590 0 : return static_cast<int64_t>(NC_FILL_INT64);
11591 0 : return 0;
11592 : }
11593 :
11594 : /************************************************************************/
11595 : /* NCDFGetDefaultNoDataValueAsUInt64() */
11596 : /************************************************************************/
11597 :
11598 1 : uint64_t NCDFGetDefaultNoDataValueAsUInt64(int nCdfId, int nVarId,
11599 : bool &bGotNoData)
11600 :
11601 : {
11602 1 : int nNoFill = 0;
11603 1 : unsigned long long nFillVal = 0;
11604 1 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
11605 : {
11606 1 : if (!nNoFill)
11607 : {
11608 1 : bGotNoData = true;
11609 1 : return static_cast<uint64_t>(nFillVal);
11610 : }
11611 : }
11612 : else
11613 0 : return static_cast<uint64_t>(NC_FILL_UINT64);
11614 0 : return 0;
11615 : }
11616 :
11617 11152 : static int NCDFDoesVarContainAttribVal(int nCdfId,
11618 : const char *const *papszAttribNames,
11619 : const char *const *papszAttribValues,
11620 : int nVarId, const char *pszVarName,
11621 : bool bStrict = true)
11622 : {
11623 11152 : if (nVarId == -1 && pszVarName != nullptr)
11624 8156 : NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
11625 :
11626 11152 : if (nVarId == -1)
11627 878 : return -1;
11628 :
11629 10274 : bool bFound = false;
11630 47959 : for (int i = 0; !bFound && papszAttribNames != nullptr &&
11631 45679 : papszAttribNames[i] != nullptr;
11632 : i++)
11633 : {
11634 37685 : char *pszTemp = nullptr;
11635 37685 : if (NCDFGetAttr(nCdfId, nVarId, papszAttribNames[i], &pszTemp) ==
11636 54137 : CE_None &&
11637 16452 : pszTemp != nullptr)
11638 : {
11639 16452 : if (bStrict)
11640 : {
11641 16452 : if (EQUAL(pszTemp, papszAttribValues[i]))
11642 2280 : bFound = true;
11643 : }
11644 : else
11645 : {
11646 0 : if (EQUALN(pszTemp, papszAttribValues[i],
11647 : strlen(papszAttribValues[i])))
11648 0 : bFound = true;
11649 : }
11650 16452 : CPLFree(pszTemp);
11651 : }
11652 : }
11653 10274 : return bFound;
11654 : }
11655 :
11656 1962 : static int NCDFDoesVarContainAttribVal2(int nCdfId, const char *papszAttribName,
11657 : const char *const *papszAttribValues,
11658 : int nVarId, const char *pszVarName,
11659 : int bStrict = true)
11660 : {
11661 1962 : if (nVarId == -1 && pszVarName != nullptr)
11662 1579 : NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
11663 :
11664 1962 : if (nVarId == -1)
11665 0 : return -1;
11666 :
11667 1962 : bool bFound = false;
11668 1962 : char *pszTemp = nullptr;
11669 2341 : if (NCDFGetAttr(nCdfId, nVarId, papszAttribName, &pszTemp) != CE_None ||
11670 379 : pszTemp == nullptr)
11671 1583 : return FALSE;
11672 :
11673 7703 : for (int i = 0; !bFound && i < CSLCount(papszAttribValues); i++)
11674 : {
11675 7324 : if (bStrict)
11676 : {
11677 7296 : if (EQUAL(pszTemp, papszAttribValues[i]))
11678 31 : bFound = true;
11679 : }
11680 : else
11681 : {
11682 28 : if (EQUALN(pszTemp, papszAttribValues[i],
11683 : strlen(papszAttribValues[i])))
11684 0 : bFound = true;
11685 : }
11686 : }
11687 :
11688 379 : CPLFree(pszTemp);
11689 :
11690 379 : return bFound;
11691 : }
11692 :
11693 876 : static bool NCDFEqual(const char *papszName, const char *const *papszValues)
11694 : {
11695 876 : if (papszName == nullptr || EQUAL(papszName, ""))
11696 0 : return false;
11697 :
11698 2392 : for (int i = 0; papszValues && papszValues[i]; ++i)
11699 : {
11700 1636 : if (EQUAL(papszName, papszValues[i]))
11701 120 : return true;
11702 : }
11703 :
11704 756 : return false;
11705 : }
11706 :
11707 : // Test that a variable is longitude/latitude coordinate,
11708 : // following CF 4.1 and 4.2.
11709 3816 : bool NCDFIsVarLongitude(int nCdfId, int nVarId, const char *pszVarName)
11710 : {
11711 : // Check for matching attributes.
11712 3816 : int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLongitudeAttribNames,
11713 : papszCFLongitudeAttribValues, nVarId,
11714 : pszVarName);
11715 : // If not found using attributes then check using var name
11716 : // unless GDAL_NETCDF_VERIFY_DIMS=STRICT.
11717 3816 : if (bVal == -1)
11718 : {
11719 280 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11720 : "STRICT"))
11721 280 : bVal = NCDFEqual(pszVarName, papszCFLongitudeVarNames);
11722 : else
11723 0 : bVal = FALSE;
11724 : }
11725 3536 : else if (bVal)
11726 : {
11727 : // Check that the units is not 'm' or '1'. See #6759
11728 795 : char *pszTemp = nullptr;
11729 1175 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11730 380 : pszTemp != nullptr)
11731 : {
11732 380 : if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
11733 97 : bVal = false;
11734 380 : CPLFree(pszTemp);
11735 : }
11736 : }
11737 :
11738 3816 : return CPL_TO_BOOL(bVal);
11739 : }
11740 :
11741 2178 : bool NCDFIsVarLatitude(int nCdfId, int nVarId, const char *pszVarName)
11742 : {
11743 2178 : int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLatitudeAttribNames,
11744 : papszCFLatitudeAttribValues, nVarId,
11745 : pszVarName);
11746 2178 : if (bVal == -1)
11747 : {
11748 163 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11749 : "STRICT"))
11750 163 : bVal = NCDFEqual(pszVarName, papszCFLatitudeVarNames);
11751 : else
11752 0 : bVal = FALSE;
11753 : }
11754 2015 : else if (bVal)
11755 : {
11756 : // Check that the units is not 'm' or '1'. See #6759
11757 548 : char *pszTemp = nullptr;
11758 690 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11759 142 : pszTemp != nullptr)
11760 : {
11761 142 : if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
11762 36 : bVal = false;
11763 142 : CPLFree(pszTemp);
11764 : }
11765 : }
11766 :
11767 2178 : return CPL_TO_BOOL(bVal);
11768 : }
11769 :
11770 2305 : bool NCDFIsVarProjectionX(int nCdfId, int nVarId, const char *pszVarName)
11771 : {
11772 2305 : int bVal = NCDFDoesVarContainAttribVal(
11773 : nCdfId, papszCFProjectionXAttribNames, papszCFProjectionXAttribValues,
11774 : nVarId, pszVarName);
11775 2305 : if (bVal == -1)
11776 : {
11777 274 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11778 : "STRICT"))
11779 274 : bVal = NCDFEqual(pszVarName, papszCFProjectionXVarNames);
11780 : else
11781 0 : bVal = FALSE;
11782 : }
11783 2031 : else if (bVal)
11784 : {
11785 : // Check that the units is not '1'
11786 381 : char *pszTemp = nullptr;
11787 544 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11788 163 : pszTemp != nullptr)
11789 : {
11790 163 : if (EQUAL(pszTemp, "1"))
11791 5 : bVal = false;
11792 163 : CPLFree(pszTemp);
11793 : }
11794 : }
11795 :
11796 2305 : return CPL_TO_BOOL(bVal);
11797 : }
11798 :
11799 1629 : bool NCDFIsVarProjectionY(int nCdfId, int nVarId, const char *pszVarName)
11800 : {
11801 1629 : int bVal = NCDFDoesVarContainAttribVal(
11802 : nCdfId, papszCFProjectionYAttribNames, papszCFProjectionYAttribValues,
11803 : nVarId, pszVarName);
11804 1629 : if (bVal == -1)
11805 : {
11806 159 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11807 : "STRICT"))
11808 159 : bVal = NCDFEqual(pszVarName, papszCFProjectionYVarNames);
11809 : else
11810 0 : bVal = FALSE;
11811 : }
11812 1470 : else if (bVal)
11813 : {
11814 : // Check that the units is not '1'
11815 377 : char *pszTemp = nullptr;
11816 537 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11817 160 : pszTemp != nullptr)
11818 : {
11819 160 : if (EQUAL(pszTemp, "1"))
11820 5 : bVal = false;
11821 160 : CPLFree(pszTemp);
11822 : }
11823 : }
11824 :
11825 1629 : return CPL_TO_BOOL(bVal);
11826 : }
11827 :
11828 : /* test that a variable is a vertical coordinate, following CF 4.3 */
11829 1022 : bool NCDFIsVarVerticalCoord(int nCdfId, int nVarId, const char *pszVarName)
11830 : {
11831 : /* check for matching attributes */
11832 1022 : if (NCDFDoesVarContainAttribVal(nCdfId, papszCFVerticalAttribNames,
11833 : papszCFVerticalAttribValues, nVarId,
11834 1022 : pszVarName))
11835 72 : return true;
11836 : /* check for matching units */
11837 950 : else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
11838 : papszCFVerticalUnitsValues, nVarId,
11839 950 : pszVarName))
11840 31 : return true;
11841 : /* check for matching standard name */
11842 919 : else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_STD_NAME,
11843 : papszCFVerticalStandardNameValues,
11844 919 : nVarId, pszVarName))
11845 0 : return true;
11846 : else
11847 919 : return false;
11848 : }
11849 :
11850 : /* test that a variable is a time coordinate, following CF 4.4 */
11851 202 : bool NCDFIsVarTimeCoord(int nCdfId, int nVarId, const char *pszVarName)
11852 : {
11853 : /* check for matching attributes */
11854 202 : if (NCDFDoesVarContainAttribVal(nCdfId, papszCFTimeAttribNames,
11855 : papszCFTimeAttribValues, nVarId,
11856 202 : pszVarName))
11857 109 : return true;
11858 : /* check for matching units */
11859 93 : else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
11860 : papszCFTimeUnitsValues, nVarId,
11861 93 : pszVarName, false))
11862 0 : return true;
11863 : else
11864 93 : return false;
11865 : }
11866 :
11867 : // Parse a string, and return as a string list.
11868 : // If it an array of the form {a,b}, then tokenize it.
11869 : // Otherwise, return a copy.
11870 188 : static char **NCDFTokenizeArray(const char *pszValue)
11871 : {
11872 188 : if (pszValue == nullptr || EQUAL(pszValue, ""))
11873 53 : return nullptr;
11874 :
11875 135 : char **papszValues = nullptr;
11876 135 : const int nLen = static_cast<int>(strlen(pszValue));
11877 :
11878 135 : if (pszValue[0] == '{' && nLen > 2 && pszValue[nLen - 1] == '}')
11879 : {
11880 41 : char *pszTemp = static_cast<char *>(CPLMalloc((nLen - 2) + 1));
11881 41 : strncpy(pszTemp, pszValue + 1, nLen - 2);
11882 41 : pszTemp[nLen - 2] = '\0';
11883 41 : papszValues = CSLTokenizeString2(pszTemp, ",", CSLT_ALLOWEMPTYTOKENS);
11884 41 : CPLFree(pszTemp);
11885 : }
11886 : else
11887 : {
11888 94 : papszValues = static_cast<char **>(CPLCalloc(2, sizeof(char *)));
11889 94 : papszValues[0] = CPLStrdup(pszValue);
11890 94 : papszValues[1] = nullptr;
11891 : }
11892 :
11893 135 : return papszValues;
11894 : }
11895 :
11896 : // Open a NetCDF subdataset from full path /group1/group2/.../groupn/var.
11897 : // Leading slash is optional.
11898 415 : static CPLErr NCDFOpenSubDataset(int nCdfId, const char *pszSubdatasetName,
11899 : int *pnGroupId, int *pnVarId)
11900 : {
11901 415 : *pnGroupId = -1;
11902 415 : *pnVarId = -1;
11903 :
11904 : // Open group.
11905 : char *pszGroupFullName =
11906 415 : CPLStrdup(CPLGetPathSafe(pszSubdatasetName).c_str());
11907 : // Add a leading slash if needed.
11908 415 : if (pszGroupFullName[0] != '/')
11909 : {
11910 398 : char *old = pszGroupFullName;
11911 398 : pszGroupFullName = CPLStrdup(CPLSPrintf("/%s", pszGroupFullName));
11912 398 : CPLFree(old);
11913 : }
11914 : // Detect root group.
11915 415 : if (EQUAL(pszGroupFullName, "/"))
11916 : {
11917 398 : *pnGroupId = nCdfId;
11918 398 : CPLFree(pszGroupFullName);
11919 : }
11920 : else
11921 : {
11922 17 : int status = nc_inq_grp_full_ncid(nCdfId, pszGroupFullName, pnGroupId);
11923 17 : CPLFree(pszGroupFullName);
11924 17 : NCDF_ERR_RET(status);
11925 : }
11926 :
11927 : // Open var.
11928 415 : const char *pszVarName = CPLGetFilename(pszSubdatasetName);
11929 415 : NCDF_ERR_RET(nc_inq_varid(*pnGroupId, pszVarName, pnVarId));
11930 :
11931 415 : return CE_None;
11932 : }
11933 :
11934 : // Get all dimensions visible from a given NetCDF (or group) ID and any of
11935 : // its parents.
11936 355 : static CPLErr NCDFGetVisibleDims(int nGroupId, int *pnDims, int **ppanDimIds)
11937 : {
11938 355 : int nDims = 0;
11939 355 : int *panDimIds = nullptr;
11940 355 : NCDF_ERR_RET(nc_inq_dimids(nGroupId, &nDims, nullptr, true));
11941 :
11942 355 : panDimIds = static_cast<int *>(CPLMalloc(nDims * sizeof(int)));
11943 :
11944 355 : int status = nc_inq_dimids(nGroupId, nullptr, panDimIds, true);
11945 355 : if (status != NC_NOERR)
11946 0 : CPLFree(panDimIds);
11947 355 : NCDF_ERR_RET(status);
11948 :
11949 355 : *pnDims = nDims;
11950 355 : *ppanDimIds = panDimIds;
11951 :
11952 355 : return CE_None;
11953 : }
11954 :
11955 : // Get direct sub-groups IDs of a given NetCDF (or group) ID.
11956 : // Consider only direct children, does not get children of children.
11957 3111 : static CPLErr NCDFGetSubGroups(int nGroupId, int *pnSubGroups,
11958 : int **ppanSubGroupIds)
11959 : {
11960 3111 : *pnSubGroups = 0;
11961 3111 : *ppanSubGroupIds = nullptr;
11962 :
11963 : int nSubGroups;
11964 3111 : NCDF_ERR_RET(nc_inq_grps(nGroupId, &nSubGroups, nullptr));
11965 : int *panSubGroupIds =
11966 3111 : static_cast<int *>(CPLMalloc(nSubGroups * sizeof(int)));
11967 3111 : NCDF_ERR_RET(nc_inq_grps(nGroupId, nullptr, panSubGroupIds));
11968 3111 : *pnSubGroups = nSubGroups;
11969 3111 : *ppanSubGroupIds = panSubGroupIds;
11970 :
11971 3111 : return CE_None;
11972 : }
11973 :
11974 : // Get the full name of a given NetCDF (or group) ID
11975 : // (e.g. /group1/group2/.../groupn).
11976 : // bNC3Compat remove the leading slash for top-level variables for
11977 : // backward compatibility (top-level variables are the ones in the root group).
11978 15659 : static CPLErr NCDFGetGroupFullName(int nGroupId, char **ppszFullName,
11979 : bool bNC3Compat)
11980 : {
11981 15659 : *ppszFullName = nullptr;
11982 :
11983 : size_t nFullNameLen;
11984 15659 : NCDF_ERR_RET(nc_inq_grpname_len(nGroupId, &nFullNameLen));
11985 15659 : *ppszFullName =
11986 15659 : static_cast<char *>(CPLMalloc((nFullNameLen + 1) * sizeof(char)));
11987 15659 : int status = nc_inq_grpname_full(nGroupId, &nFullNameLen, *ppszFullName);
11988 15659 : if (status != NC_NOERR)
11989 : {
11990 0 : CPLFree(*ppszFullName);
11991 0 : *ppszFullName = nullptr;
11992 0 : NCDF_ERR_RET(status);
11993 : }
11994 :
11995 15659 : if (bNC3Compat && EQUAL(*ppszFullName, "/"))
11996 8010 : (*ppszFullName)[0] = '\0';
11997 :
11998 15659 : return CE_None;
11999 : }
12000 :
12001 7441 : CPLString NCDFGetGroupFullName(int nGroupId)
12002 : {
12003 7441 : char *pszFullname = nullptr;
12004 7441 : NCDFGetGroupFullName(nGroupId, &pszFullname, false);
12005 7441 : CPLString osRet(pszFullname ? pszFullname : "");
12006 7441 : CPLFree(pszFullname);
12007 14882 : return osRet;
12008 : }
12009 :
12010 : // Get the full name of a given NetCDF variable ID
12011 : // (e.g. /group1/group2/.../groupn/var).
12012 : // Handle also NC_GLOBAL as nVarId.
12013 : // bNC3Compat remove the leading slash for top-level variables for
12014 : // backward compatibility (top-level variables are the ones in the root group).
12015 8169 : static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId, char **ppszFullName,
12016 : bool bNC3Compat)
12017 : {
12018 8169 : *ppszFullName = nullptr;
12019 8169 : char *pszGroupFullName = nullptr;
12020 8169 : ERR_RET(NCDFGetGroupFullName(nGroupId, &pszGroupFullName, bNC3Compat));
12021 : char szVarName[NC_MAX_NAME + 1];
12022 8169 : if (nVarId == NC_GLOBAL)
12023 : {
12024 1103 : strcpy(szVarName, "NC_GLOBAL");
12025 : }
12026 : else
12027 : {
12028 7066 : int status = nc_inq_varname(nGroupId, nVarId, szVarName);
12029 7066 : if (status != NC_NOERR)
12030 : {
12031 0 : CPLFree(pszGroupFullName);
12032 0 : NCDF_ERR_RET(status);
12033 : }
12034 : }
12035 8169 : const char *pszSep = "/";
12036 8169 : if (EQUAL(pszGroupFullName, "/") || EQUAL(pszGroupFullName, ""))
12037 7963 : pszSep = "";
12038 8169 : *ppszFullName =
12039 8169 : CPLStrdup(CPLSPrintf("%s%s%s", pszGroupFullName, pszSep, szVarName));
12040 8169 : CPLFree(pszGroupFullName);
12041 8169 : return CE_None;
12042 : }
12043 :
12044 : // Get the NetCDF root group ID of a given group ID.
12045 0 : static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId)
12046 : {
12047 0 : *pnRootGroupId = -1;
12048 : // Recurse on parent group.
12049 : int nParentGroupId;
12050 0 : int status = nc_inq_grp_parent(nStartGroupId, &nParentGroupId);
12051 0 : if (status == NC_NOERR)
12052 0 : return NCDFGetRootGroup(nParentGroupId, pnRootGroupId);
12053 0 : else if (status != NC_ENOGRP)
12054 0 : NCDF_ERR_RET(status);
12055 : else // No more parent group.
12056 : {
12057 0 : *pnRootGroupId = nStartGroupId;
12058 : }
12059 :
12060 0 : return CE_None;
12061 : }
12062 :
12063 : // Implementation of NCDFResolveVar/Att.
12064 13260 : static CPLErr NCDFResolveElem(int nStartGroupId, const char *pszVar,
12065 : const char *pszAtt, int *pnGroupId, int *pnId,
12066 : bool bMandatory)
12067 : {
12068 13260 : if (!pszVar && !pszAtt)
12069 : {
12070 0 : CPLError(CE_Failure, CPLE_IllegalArg,
12071 : "pszVar and pszAtt NCDFResolveElem() args are both null.");
12072 0 : return CE_Failure;
12073 : }
12074 :
12075 : enum
12076 : {
12077 : NCRM_PARENT,
12078 : NCRM_WIDTH_WISE
12079 13260 : } eNCResolveMode = NCRM_PARENT;
12080 :
12081 26520 : std::queue<int> aoQueueGroupIdsToVisit;
12082 13260 : aoQueueGroupIdsToVisit.push(nStartGroupId);
12083 :
12084 14985 : while (!aoQueueGroupIdsToVisit.empty())
12085 : {
12086 : // Get the first group of the FIFO queue.
12087 13454 : *pnGroupId = aoQueueGroupIdsToVisit.front();
12088 13454 : aoQueueGroupIdsToVisit.pop();
12089 :
12090 : // Look if this group contains the searched element.
12091 : int status;
12092 13454 : if (pszVar)
12093 13234 : status = nc_inq_varid(*pnGroupId, pszVar, pnId);
12094 : else // pszAtt != nullptr.
12095 220 : status = nc_inq_attid(*pnGroupId, NC_GLOBAL, pszAtt, pnId);
12096 :
12097 13454 : if (status == NC_NOERR)
12098 : {
12099 11729 : return CE_None;
12100 : }
12101 1725 : else if ((pszVar && status != NC_ENOTVAR) ||
12102 217 : (pszAtt && status != NC_ENOTATT))
12103 : {
12104 0 : NCDF_ERR(status);
12105 : }
12106 : // Element not found, in NC4 case we must search in other groups
12107 : // following the CF logic.
12108 :
12109 : // The first resolve mode consists to search on parent groups.
12110 1725 : if (eNCResolveMode == NCRM_PARENT)
12111 : {
12112 1606 : int nParentGroupId = -1;
12113 1606 : int status2 = nc_inq_grp_parent(*pnGroupId, &nParentGroupId);
12114 1606 : if (status2 == NC_NOERR)
12115 62 : aoQueueGroupIdsToVisit.push(nParentGroupId);
12116 1544 : else if (status2 != NC_ENOGRP)
12117 0 : NCDF_ERR(status2);
12118 1544 : else if (pszVar)
12119 : // When resolving a variable, if there is no more
12120 : // parent group then we switch to width-wise search mode
12121 : // starting from the latest found parent group.
12122 1330 : eNCResolveMode = NCRM_WIDTH_WISE;
12123 : }
12124 :
12125 : // The second resolve mode is a width-wise search.
12126 1725 : if (eNCResolveMode == NCRM_WIDTH_WISE)
12127 : {
12128 : // Enqueue all direct sub-groups.
12129 1449 : int nSubGroups = 0;
12130 1449 : int *panSubGroupIds = nullptr;
12131 1449 : NCDFGetSubGroups(*pnGroupId, &nSubGroups, &panSubGroupIds);
12132 1581 : for (int i = 0; i < nSubGroups; i++)
12133 132 : aoQueueGroupIdsToVisit.push(panSubGroupIds[i]);
12134 1449 : CPLFree(panSubGroupIds);
12135 : }
12136 : }
12137 :
12138 1531 : if (bMandatory)
12139 : {
12140 0 : char *pszStartGroupFullName = nullptr;
12141 0 : NCDFGetGroupFullName(nStartGroupId, &pszStartGroupFullName);
12142 0 : CPLError(CE_Failure, CPLE_AppDefined,
12143 : "Cannot resolve mandatory %s %s from group %s",
12144 : (pszVar ? pszVar : pszAtt),
12145 : (pszVar ? "variable" : "attribute"),
12146 0 : (pszStartGroupFullName ? pszStartGroupFullName : ""));
12147 0 : CPLFree(pszStartGroupFullName);
12148 : }
12149 :
12150 1531 : *pnGroupId = -1;
12151 1531 : *pnId = -1;
12152 1531 : return CE_Failure;
12153 : }
12154 :
12155 : // Resolve a variable name from a given starting group following the CF logic:
12156 : // - if var name is an absolute path then directly open it
12157 : // - first search in the starting group and its parent groups
12158 : // - then if there is no more parent group we switch to a width-wise search
12159 : // mode starting from the latest found parent group.
12160 : // The full CF logic is described here:
12161 : // https://github.com/diwg/cf2/blob/master/group/cf2-group.adoc#scope
12162 : // If bMandatory then print an error if resolving fails.
12163 : // TODO: implement support of relative paths.
12164 : // TODO: to follow strictly the CF logic, when searching for a coordinate
12165 : // variable, we must stop the parent search mode once the corresponding
12166 : // dimension is found and start the width-wise search from this group.
12167 : // TODO: to follow strictly the CF logic, when searching in width-wise mode
12168 : // we should skip every groups already visited during the parent
12169 : // search mode (but revisiting them should have no impact so we could
12170 : // let as it is if it is simpler...)
12171 : // TODO: CF specifies that the width-wise search order is "left-to-right" so
12172 : // maybe we must sort sibling groups alphabetically? but maybe not
12173 : // necessary if nc_inq_grps() already sort them?
12174 13043 : CPLErr NCDFResolveVar(int nStartGroupId, const char *pszVar, int *pnGroupId,
12175 : int *pnVarId, bool bMandatory)
12176 : {
12177 13043 : *pnGroupId = -1;
12178 13043 : *pnVarId = -1;
12179 13043 : int nGroupId = nStartGroupId, nVarId;
12180 13043 : if (pszVar[0] == '/')
12181 : {
12182 : // This is an absolute path: we can open the var directly.
12183 : int nRootGroupId;
12184 0 : ERR_RET(NCDFGetRootGroup(nStartGroupId, &nRootGroupId));
12185 0 : ERR_RET(NCDFOpenSubDataset(nRootGroupId, pszVar, &nGroupId, &nVarId));
12186 : }
12187 : else
12188 : {
12189 : // We have to search the variable following the CF logic.
12190 13043 : ERR_RET(NCDFResolveElem(nStartGroupId, pszVar, nullptr, &nGroupId,
12191 : &nVarId, bMandatory));
12192 : }
12193 11726 : *pnGroupId = nGroupId;
12194 11726 : *pnVarId = nVarId;
12195 11726 : return CE_None;
12196 : }
12197 :
12198 : // Like NCDFResolveVar but returns directly the var full name.
12199 1384 : static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,
12200 : char **ppszFullName, bool bMandatory)
12201 : {
12202 1384 : *ppszFullName = nullptr;
12203 : int nGroupId, nVarId;
12204 1384 : ERR_RET(
12205 : NCDFResolveVar(nStartGroupId, pszVar, &nGroupId, &nVarId, bMandatory));
12206 1358 : return NCDFGetVarFullName(nGroupId, nVarId, ppszFullName);
12207 : }
12208 :
12209 : // Like NCDFResolveVar but resolves an attribute instead a variable and
12210 : // returns its integer value.
12211 : // Only GLOBAL attributes are supported for the moment.
12212 217 : static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,
12213 : const char *pszAtt, int *pnAtt, bool bMandatory)
12214 : {
12215 217 : int nGroupId = nStartGroupId, nAttId = nStartVarId;
12216 217 : ERR_RET(NCDFResolveElem(nStartGroupId, nullptr, pszAtt, &nGroupId, &nAttId,
12217 : bMandatory));
12218 3 : NCDF_ERR_RET(nc_get_att_int(nGroupId, NC_GLOBAL, pszAtt, pnAtt));
12219 3 : return CE_None;
12220 : }
12221 :
12222 : // Filter variables to keep only valid 2+D raster bands and vector fields in
12223 : // a given a NetCDF (or group) ID and its sub-groups.
12224 : // Coordinate or boundary variables are ignored.
12225 : // It also creates corresponding vector layers.
12226 531 : CPLErr netCDFDataset::FilterVars(
12227 : int nCdfId, bool bKeepRasters, bool bKeepVectors, char **papszIgnoreVars,
12228 : int *pnRasterVars, int *pnGroupId, int *pnVarId, int *pnIgnoredVars,
12229 : std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>
12230 : &oMap2DDimsToGroupAndVar)
12231 : {
12232 531 : int nVars = 0;
12233 531 : int nRasterVars = 0;
12234 531 : NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
12235 :
12236 1062 : std::vector<int> anPotentialVectorVarID;
12237 : // oMapDimIdToCount[x] = number of times dim x is the first dimension of
12238 : // potential vector variables
12239 1062 : std::map<int, int> oMapDimIdToCount;
12240 531 : int nVarXId = -1;
12241 531 : int nVarYId = -1;
12242 531 : int nVarZId = -1;
12243 531 : int nVarTimeId = -1;
12244 531 : int nVarTimeDimId = -1;
12245 531 : bool bIsVectorOnly = true;
12246 531 : int nProfileDimId = -1;
12247 531 : int nParentIndexVarID = -1;
12248 :
12249 3234 : for (int v = 0; v < nVars; v++)
12250 : {
12251 : int nVarDims;
12252 2703 : NCDF_ERR_RET(nc_inq_varndims(nCdfId, v, &nVarDims));
12253 : // Should we ignore this variable?
12254 : char szTemp[NC_MAX_NAME + 1];
12255 2703 : szTemp[0] = '\0';
12256 2703 : NCDF_ERR_RET(nc_inq_varname(nCdfId, v, szTemp));
12257 :
12258 2703 : if (strstr(szTemp, "_node_coordinates") ||
12259 2703 : strstr(szTemp, "_node_count"))
12260 : {
12261 : // Ignore CF-1.8 Simple Geometries helper variables
12262 69 : continue;
12263 : }
12264 :
12265 3933 : if (nVarDims == 1 && (NCDFIsVarLongitude(nCdfId, -1, szTemp) ||
12266 1299 : NCDFIsVarProjectionX(nCdfId, -1, szTemp)))
12267 : {
12268 360 : nVarXId = v;
12269 : }
12270 3213 : else if (nVarDims == 1 && (NCDFIsVarLatitude(nCdfId, -1, szTemp) ||
12271 939 : NCDFIsVarProjectionY(nCdfId, -1, szTemp)))
12272 : {
12273 359 : nVarYId = v;
12274 : }
12275 1915 : else if (nVarDims == 1 && NCDFIsVarVerticalCoord(nCdfId, -1, szTemp))
12276 : {
12277 78 : nVarZId = v;
12278 : }
12279 : else
12280 : {
12281 1837 : char *pszVarFullName = nullptr;
12282 1837 : CPLErr eErr = NCDFGetVarFullName(nCdfId, v, &pszVarFullName);
12283 1837 : if (eErr != CE_None)
12284 : {
12285 0 : CPLFree(pszVarFullName);
12286 0 : continue;
12287 : }
12288 : bool bIgnoreVar =
12289 1837 : (CSLFindString(papszIgnoreVars, pszVarFullName) != -1);
12290 1837 : CPLFree(pszVarFullName);
12291 1837 : if (bIgnoreVar)
12292 : {
12293 104 : if (nVarDims == 1 && NCDFIsVarTimeCoord(nCdfId, -1, szTemp))
12294 : {
12295 11 : nVarTimeId = v;
12296 11 : nc_inq_vardimid(nCdfId, v, &nVarTimeDimId);
12297 : }
12298 93 : else if (nVarDims > 1)
12299 : {
12300 89 : (*pnIgnoredVars)++;
12301 89 : CPLDebug("GDAL_netCDF", "variable #%d [%s] was ignored", v,
12302 : szTemp);
12303 : }
12304 : }
12305 : // Only accept 2+D vars.
12306 1733 : else if (nVarDims >= 2)
12307 : {
12308 707 : bool bRasterCandidate = true;
12309 : // Identify variables that might be vector variables
12310 707 : if (nVarDims == 2)
12311 : {
12312 631 : int anDimIds[2] = {-1, -1};
12313 631 : nc_inq_vardimid(nCdfId, v, anDimIds);
12314 :
12315 631 : nc_type vartype = NC_NAT;
12316 631 : nc_inq_vartype(nCdfId, v, &vartype);
12317 :
12318 : char szDimNameFirst[NC_MAX_NAME + 1];
12319 : char szDimNameSecond[NC_MAX_NAME + 1];
12320 631 : szDimNameFirst[0] = '\0';
12321 631 : szDimNameSecond[0] = '\0';
12322 1419 : if (vartype == NC_CHAR &&
12323 157 : nc_inq_dimname(nCdfId, anDimIds[0], szDimNameFirst) ==
12324 157 : NC_NOERR &&
12325 157 : nc_inq_dimname(nCdfId, anDimIds[1], szDimNameSecond) ==
12326 157 : NC_NOERR &&
12327 157 : !NCDFIsVarLongitude(nCdfId, -1, szDimNameSecond) &&
12328 157 : !NCDFIsVarProjectionX(nCdfId, -1, szDimNameSecond) &&
12329 945 : !NCDFIsVarLatitude(nCdfId, -1, szDimNameFirst) &&
12330 157 : !NCDFIsVarProjectionY(nCdfId, -1, szDimNameFirst))
12331 : {
12332 157 : anPotentialVectorVarID.push_back(v);
12333 157 : oMapDimIdToCount[anDimIds[0]]++;
12334 157 : if (strstr(szDimNameSecond, "_max_width"))
12335 : {
12336 127 : bRasterCandidate = false;
12337 : }
12338 : else
12339 : {
12340 30 : std::array<int, 3> oKey{anDimIds[0], anDimIds[1],
12341 30 : vartype};
12342 30 : oMap2DDimsToGroupAndVar[oKey].emplace_back(
12343 30 : std::pair(nCdfId, v));
12344 : }
12345 : }
12346 : else
12347 : {
12348 474 : std::array<int, 3> oKey{anDimIds[0], anDimIds[1],
12349 474 : vartype};
12350 474 : oMap2DDimsToGroupAndVar[oKey].emplace_back(
12351 474 : std::pair(nCdfId, v));
12352 474 : bIsVectorOnly = false;
12353 : }
12354 : }
12355 : else
12356 : {
12357 76 : bIsVectorOnly = false;
12358 : }
12359 707 : if (bKeepRasters && bRasterCandidate)
12360 : {
12361 551 : *pnGroupId = nCdfId;
12362 551 : *pnVarId = v;
12363 551 : nRasterVars++;
12364 : }
12365 : }
12366 1026 : else if (nVarDims == 1)
12367 : {
12368 730 : nc_type atttype = NC_NAT;
12369 730 : size_t attlen = 0;
12370 730 : if (nc_inq_att(nCdfId, v, "instance_dimension", &atttype,
12371 14 : &attlen) == NC_NOERR &&
12372 730 : atttype == NC_CHAR && attlen < NC_MAX_NAME)
12373 : {
12374 : char szInstanceDimension[NC_MAX_NAME + 1];
12375 14 : if (nc_get_att_text(nCdfId, v, "instance_dimension",
12376 14 : szInstanceDimension) == NC_NOERR)
12377 : {
12378 14 : szInstanceDimension[attlen] = 0;
12379 14 : int status = nc_inq_dimid(nCdfId, szInstanceDimension,
12380 : &nProfileDimId);
12381 14 : if (status == NC_NOERR)
12382 14 : nParentIndexVarID = v;
12383 : else
12384 0 : nProfileDimId = -1;
12385 14 : if (status == NC_EBADDIM)
12386 0 : CPLError(CE_Warning, CPLE_AppDefined,
12387 : "Attribute instance_dimension='%s' refers "
12388 : "to a non existing dimension",
12389 : szInstanceDimension);
12390 : else
12391 14 : NCDF_ERR(status);
12392 : }
12393 : }
12394 730 : if (v != nParentIndexVarID)
12395 : {
12396 716 : anPotentialVectorVarID.push_back(v);
12397 716 : int nDimId = -1;
12398 716 : nc_inq_vardimid(nCdfId, v, &nDimId);
12399 716 : oMapDimIdToCount[nDimId]++;
12400 : }
12401 : }
12402 : }
12403 : }
12404 :
12405 : // If we are opened in raster-only mode and that there are only 1D or 2D
12406 : // variables and that the 2D variables have no X/Y dim, and all
12407 : // variables refer to the same main dimension (or 2 dimensions for
12408 : // featureType=profile), then it is a pure vector dataset
12409 : CPLString osFeatureType(
12410 531 : CSLFetchNameValueDef(papszMetadata, "NC_GLOBAL#featureType", ""));
12411 420 : if (bKeepRasters && !bKeepVectors && bIsVectorOnly && nRasterVars > 0 &&
12412 951 : !anPotentialVectorVarID.empty() &&
12413 0 : (oMapDimIdToCount.size() == 1 ||
12414 0 : (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2 &&
12415 0 : nProfileDimId >= 0)))
12416 : {
12417 0 : anPotentialVectorVarID.resize(0);
12418 : }
12419 : else
12420 : {
12421 531 : *pnRasterVars += nRasterVars;
12422 : }
12423 :
12424 531 : if (!anPotentialVectorVarID.empty() && bKeepVectors)
12425 : {
12426 : // Take the dimension that is referenced the most times.
12427 64 : if (!(oMapDimIdToCount.size() == 1 ||
12428 27 : (EQUAL(osFeatureType, "profile") &&
12429 26 : oMapDimIdToCount.size() == 2 && nProfileDimId >= 0)))
12430 : {
12431 1 : CPLError(CE_Warning, CPLE_AppDefined,
12432 : "The dataset has several variables that could be "
12433 : "identified as vector fields, but not all share the same "
12434 : "primary dimension. Consequently they will be ignored.");
12435 : }
12436 : else
12437 : {
12438 50 : if (nVarTimeId >= 0 &&
12439 50 : oMapDimIdToCount.find(nVarTimeDimId) != oMapDimIdToCount.end())
12440 : {
12441 1 : anPotentialVectorVarID.push_back(nVarTimeId);
12442 : }
12443 49 : CreateGrpVectorLayers(nCdfId, osFeatureType, anPotentialVectorVarID,
12444 : oMapDimIdToCount, nVarXId, nVarYId, nVarZId,
12445 : nProfileDimId, nParentIndexVarID,
12446 : bKeepRasters);
12447 : }
12448 : }
12449 :
12450 : // Recurse on sub-groups.
12451 531 : int nSubGroups = 0;
12452 531 : int *panSubGroupIds = nullptr;
12453 531 : NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
12454 566 : for (int i = 0; i < nSubGroups; i++)
12455 : {
12456 35 : FilterVars(panSubGroupIds[i], bKeepRasters, bKeepVectors,
12457 : papszIgnoreVars, pnRasterVars, pnGroupId, pnVarId,
12458 : pnIgnoredVars, oMap2DDimsToGroupAndVar);
12459 : }
12460 531 : CPLFree(panSubGroupIds);
12461 :
12462 531 : return CE_None;
12463 : }
12464 :
12465 : // Create vector layers from given potentially identified vector variables
12466 : // resulting from the scanning of a NetCDF (or group) ID.
12467 49 : CPLErr netCDFDataset::CreateGrpVectorLayers(
12468 : int nCdfId, const CPLString &osFeatureType,
12469 : const std::vector<int> &anPotentialVectorVarID,
12470 : const std::map<int, int> &oMapDimIdToCount, int nVarXId, int nVarYId,
12471 : int nVarZId, int nProfileDimId, int nParentIndexVarID, bool bKeepRasters)
12472 : {
12473 49 : char *pszGroupName = nullptr;
12474 49 : NCDFGetGroupFullName(nCdfId, &pszGroupName);
12475 49 : if (pszGroupName == nullptr || pszGroupName[0] == '\0')
12476 : {
12477 47 : CPLFree(pszGroupName);
12478 47 : pszGroupName = CPLStrdup(CPLGetBasenameSafe(osFilename).c_str());
12479 : }
12480 49 : OGRwkbGeometryType eGType = wkbUnknown;
12481 : CPLString osLayerName = CSLFetchNameValueDef(
12482 98 : papszMetadata, "NC_GLOBAL#ogr_layer_name", pszGroupName);
12483 49 : CPLFree(pszGroupName);
12484 49 : papszMetadata =
12485 49 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_name", nullptr);
12486 :
12487 49 : if (EQUAL(osFeatureType, "point") || EQUAL(osFeatureType, "profile"))
12488 : {
12489 33 : papszMetadata =
12490 33 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#featureType", nullptr);
12491 33 : eGType = wkbPoint;
12492 : }
12493 :
12494 : const char *pszLayerType =
12495 49 : CSLFetchNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_type");
12496 49 : if (pszLayerType != nullptr)
12497 : {
12498 9 : eGType = OGRFromOGCGeomType(pszLayerType);
12499 9 : papszMetadata =
12500 9 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_type", nullptr);
12501 : }
12502 :
12503 : CPLString osGeometryField =
12504 98 : CSLFetchNameValueDef(papszMetadata, "NC_GLOBAL#ogr_geometry_field", "");
12505 49 : papszMetadata =
12506 49 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_geometry_field", nullptr);
12507 :
12508 49 : int nFirstVarId = -1;
12509 49 : int nVectorDim = oMapDimIdToCount.rbegin()->first;
12510 49 : if (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2)
12511 : {
12512 13 : if (nVectorDim == nProfileDimId)
12513 0 : nVectorDim = oMapDimIdToCount.begin()->first;
12514 : }
12515 : else
12516 : {
12517 36 : nProfileDimId = -1;
12518 : }
12519 62 : for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)
12520 : {
12521 62 : int anDimIds[2] = {-1, -1};
12522 62 : nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);
12523 62 : if (nVectorDim == anDimIds[0])
12524 : {
12525 49 : nFirstVarId = anPotentialVectorVarID[j];
12526 49 : break;
12527 : }
12528 : }
12529 :
12530 : // In case where coordinates are explicitly specified for one of the
12531 : // field/variable, use them in priority over the ones that might have been
12532 : // identified above.
12533 49 : char *pszCoordinates = nullptr;
12534 49 : if (NCDFGetAttr(nCdfId, nFirstVarId, "coordinates", &pszCoordinates) ==
12535 : CE_None)
12536 : {
12537 34 : char **papszTokens = NCDFTokenizeCoordinatesAttribute(pszCFCoordinates);
12538 34 : for (int i = 0; papszTokens != nullptr && papszTokens[i] != nullptr;
12539 : i++)
12540 : {
12541 0 : if (NCDFIsVarLongitude(nCdfId, -1, papszTokens[i]) ||
12542 0 : NCDFIsVarProjectionX(nCdfId, -1, papszTokens[i]))
12543 : {
12544 0 : nVarXId = -1;
12545 0 : CPL_IGNORE_RET_VAL(
12546 0 : nc_inq_varid(nCdfId, papszTokens[i], &nVarXId));
12547 : }
12548 0 : else if (NCDFIsVarLatitude(nCdfId, -1, papszTokens[i]) ||
12549 0 : NCDFIsVarProjectionY(nCdfId, -1, papszTokens[i]))
12550 : {
12551 0 : nVarYId = -1;
12552 0 : CPL_IGNORE_RET_VAL(
12553 0 : nc_inq_varid(nCdfId, papszTokens[i], &nVarYId));
12554 : }
12555 0 : else if (NCDFIsVarVerticalCoord(nCdfId, -1, papszTokens[i]))
12556 : {
12557 0 : nVarZId = -1;
12558 0 : CPL_IGNORE_RET_VAL(
12559 0 : nc_inq_varid(nCdfId, papszTokens[i], &nVarZId));
12560 : }
12561 : }
12562 34 : CSLDestroy(papszTokens);
12563 : }
12564 49 : CPLFree(pszCoordinates);
12565 :
12566 : // Check that the X,Y,Z vars share 1D and share the same dimension as
12567 : // attribute variables.
12568 49 : if (nVarXId >= 0 && nVarYId >= 0)
12569 : {
12570 38 : int nVarDimCount = -1;
12571 38 : int nVarDimId = -1;
12572 38 : if (nc_inq_varndims(nCdfId, nVarXId, &nVarDimCount) != NC_NOERR ||
12573 38 : nVarDimCount != 1 ||
12574 38 : nc_inq_vardimid(nCdfId, nVarXId, &nVarDimId) != NC_NOERR ||
12575 38 : nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim) ||
12576 35 : nc_inq_varndims(nCdfId, nVarYId, &nVarDimCount) != NC_NOERR ||
12577 35 : nVarDimCount != 1 ||
12578 111 : nc_inq_vardimid(nCdfId, nVarYId, &nVarDimId) != NC_NOERR ||
12579 35 : nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim))
12580 : {
12581 3 : nVarXId = nVarYId = -1;
12582 : }
12583 69 : else if (nVarZId >= 0 &&
12584 34 : (nc_inq_varndims(nCdfId, nVarZId, &nVarDimCount) != NC_NOERR ||
12585 34 : nVarDimCount != 1 ||
12586 34 : nc_inq_vardimid(nCdfId, nVarZId, &nVarDimId) != NC_NOERR ||
12587 34 : nVarDimId != nVectorDim))
12588 : {
12589 0 : nVarZId = -1;
12590 : }
12591 : }
12592 :
12593 49 : if (eGType == wkbUnknown && nVarXId >= 0 && nVarYId >= 0)
12594 : {
12595 2 : eGType = wkbPoint;
12596 : }
12597 49 : if (eGType == wkbPoint && nVarXId >= 0 && nVarYId >= 0 && nVarZId >= 0)
12598 : {
12599 34 : eGType = wkbPoint25D;
12600 : }
12601 49 : if (eGType == wkbUnknown && osGeometryField.empty())
12602 : {
12603 5 : eGType = wkbNone;
12604 : }
12605 :
12606 : // Read projection info
12607 49 : char **papszMetadataBackup = CSLDuplicate(papszMetadata);
12608 49 : ReadAttributes(nCdfId, nFirstVarId);
12609 49 : if (!this->bSGSupport)
12610 49 : SetProjectionFromVar(nCdfId, nFirstVarId, true);
12611 49 : const char *pszValue = FetchAttr(nCdfId, nFirstVarId, CF_GRD_MAPPING);
12612 49 : char *pszGridMapping = (pszValue ? CPLStrdup(pszValue) : nullptr);
12613 49 : CSLDestroy(papszMetadata);
12614 49 : papszMetadata = papszMetadataBackup;
12615 :
12616 49 : OGRSpatialReference *poSRS = nullptr;
12617 49 : if (!m_oSRS.IsEmpty())
12618 : {
12619 21 : poSRS = m_oSRS.Clone();
12620 : }
12621 : // Reset if there's a 2D raster
12622 49 : m_bHasProjection = false;
12623 49 : m_bHasGeoTransform = false;
12624 :
12625 49 : if (!bKeepRasters)
12626 : {
12627 : // Strip out uninteresting metadata.
12628 45 : papszMetadata =
12629 45 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#Conventions", nullptr);
12630 45 : papszMetadata =
12631 45 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#GDAL", nullptr);
12632 45 : papszMetadata =
12633 45 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#history", nullptr);
12634 : }
12635 :
12636 : std::shared_ptr<netCDFLayer> poLayer(
12637 49 : new netCDFLayer(this, nCdfId, osLayerName, eGType, poSRS));
12638 49 : if (poSRS != nullptr)
12639 21 : poSRS->Release();
12640 49 : poLayer->SetRecordDimID(nVectorDim);
12641 49 : if (wkbFlatten(eGType) == wkbPoint && nVarXId >= 0 && nVarYId >= 0)
12642 : {
12643 35 : poLayer->SetXYZVars(nVarXId, nVarYId, nVarZId);
12644 : }
12645 14 : else if (!osGeometryField.empty())
12646 : {
12647 9 : poLayer->SetWKTGeometryField(osGeometryField);
12648 : }
12649 49 : if (pszGridMapping != nullptr)
12650 : {
12651 21 : poLayer->SetGridMapping(pszGridMapping);
12652 21 : CPLFree(pszGridMapping);
12653 : }
12654 49 : poLayer->SetProfile(nProfileDimId, nParentIndexVarID);
12655 :
12656 574 : for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)
12657 : {
12658 525 : int anDimIds[2] = {-1, -1};
12659 525 : nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);
12660 525 : if (anDimIds[0] == nVectorDim ||
12661 24 : (nProfileDimId >= 0 && anDimIds[0] == nProfileDimId))
12662 : {
12663 : #ifdef NCDF_DEBUG
12664 : char szTemp2[NC_MAX_NAME + 1] = {};
12665 : CPL_IGNORE_RET_VAL(
12666 : nc_inq_varname(nCdfId, anPotentialVectorVarID[j], szTemp2));
12667 : CPLDebug("GDAL_netCDF", "Variable %s is a vector field", szTemp2);
12668 : #endif
12669 525 : poLayer->AddField(anPotentialVectorVarID[j]);
12670 : }
12671 : }
12672 :
12673 49 : if (poLayer->GetLayerDefn()->GetFieldCount() != 0 ||
12674 0 : poLayer->GetGeomType() != wkbNone)
12675 : {
12676 49 : papoLayers.push_back(poLayer);
12677 : }
12678 :
12679 98 : return CE_None;
12680 : }
12681 :
12682 : // Get all coordinate and boundary variables full names referenced in
12683 : // a given a NetCDF (or group) ID and its sub-groups.
12684 : // These variables are identified in other variable's
12685 : // "coordinates" and "bounds" attribute.
12686 : // Searching coordinate and boundary variables may need to explore
12687 : // parents groups (or other groups in case of reference given in form of an
12688 : // absolute path).
12689 : // See CF sections 5.2, 5.6 and 7.1
12690 532 : static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId, char ***ppapszVars)
12691 : {
12692 532 : int nVars = 0;
12693 532 : NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
12694 :
12695 3249 : for (int v = 0; v < nVars; v++)
12696 : {
12697 2717 : char *pszTemp = nullptr;
12698 2717 : char **papszTokens = nullptr;
12699 2717 : if (NCDFGetAttr(nCdfId, v, "coordinates", &pszTemp) == CE_None)
12700 446 : papszTokens = NCDFTokenizeCoordinatesAttribute(pszTemp);
12701 2717 : CPLFree(pszTemp);
12702 2717 : pszTemp = nullptr;
12703 2717 : if (NCDFGetAttr(nCdfId, v, "bounds", &pszTemp) == CE_None &&
12704 2717 : pszTemp != nullptr && !EQUAL(pszTemp, ""))
12705 17 : papszTokens = CSLAddString(papszTokens, pszTemp);
12706 2717 : CPLFree(pszTemp);
12707 4005 : for (int i = 0; papszTokens != nullptr && papszTokens[i] != nullptr;
12708 : i++)
12709 : {
12710 1288 : char *pszVarFullName = nullptr;
12711 1288 : if (NCDFResolveVarFullName(nCdfId, papszTokens[i],
12712 1288 : &pszVarFullName) == CE_None)
12713 1262 : *ppapszVars = CSLAddString(*ppapszVars, pszVarFullName);
12714 1288 : CPLFree(pszVarFullName);
12715 : }
12716 2717 : CSLDestroy(papszTokens);
12717 : }
12718 :
12719 : // Recurse on sub-groups.
12720 : int nSubGroups;
12721 532 : int *panSubGroupIds = nullptr;
12722 532 : NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
12723 567 : for (int i = 0; i < nSubGroups; i++)
12724 : {
12725 35 : NCDFGetCoordAndBoundVarFullNames(panSubGroupIds[i], ppapszVars);
12726 : }
12727 532 : CPLFree(panSubGroupIds);
12728 :
12729 532 : return CE_None;
12730 : }
12731 :
12732 : // Check if give type is user defined
12733 1538 : bool NCDFIsUserDefinedType(int /*ncid*/, int type)
12734 : {
12735 1538 : return type >= NC_FIRSTUSERTYPEID;
12736 : }
12737 :
12738 577 : char **NCDFTokenizeCoordinatesAttribute(const char *pszCoordinates)
12739 : {
12740 : // CF conventions use space as the separator for variable names in the
12741 : // coordinates attribute, but some products such as
12742 : // https://sentinel.esa.int/web/sentinel/technical-guides/sentinel-3-synergy/products-algorithms/level-2-aod-algorithms-and-products/level-2-aod-products-description
12743 : // use comma.
12744 577 : return CSLTokenizeString2(pszCoordinates, ", ", 0);
12745 : }
|