Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: netCDF read/write Driver
4 : * Purpose: GDAL bindings over netCDF library.
5 : * Author: Frank Warmerdam, warmerdam@pobox.com
6 : * Even Rouault <even.rouault at spatialys.com>
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2004, Frank Warmerdam
10 : * Copyright (c) 2007-2016, Even Rouault <even.rouault at spatialys.com>
11 : * Copyright (c) 2010, Kyle Shannon <kyle at pobox dot com>
12 : * Copyright (c) 2021, CLS
13 : *
14 : * SPDX-License-Identifier: MIT
15 : ****************************************************************************/
16 :
17 : #include "cpl_port.h"
18 :
19 : #include <array>
20 : #include <cassert>
21 : #include <cctype>
22 : #include <cerrno>
23 : #include <climits>
24 : #include <cmath>
25 : #include <cstdio>
26 : #include <cstdlib>
27 : #include <cstring>
28 : #include <ctime>
29 : #include <algorithm>
30 : #include <limits>
31 : #include <map>
32 : #include <set>
33 : #include <queue>
34 : #include <string>
35 : #include <tuple>
36 : #include <utility>
37 : #include <vector>
38 :
39 : // Must be included after standard includes, otherwise VS2015 fails when
40 : // including <ctime>
41 : #include "netcdfdataset.h"
42 : #include "netcdfdrivercore.h"
43 : #include "netcdfsg.h"
44 : #include "netcdfuffd.h"
45 :
46 : #include "netcdf_mem.h"
47 :
48 : #include "cpl_conv.h"
49 : #include "cpl_error.h"
50 : #include "cpl_json.h"
51 : #include "cpl_minixml.h"
52 : #include "cpl_multiproc.h"
53 : #include "cpl_progress.h"
54 : #include "cpl_time.h"
55 : #include "gdal.h"
56 : #include "gdal_frmts.h"
57 : #include "gdal_priv_templates.hpp"
58 : #include "ogr_core.h"
59 : #include "ogr_srs_api.h"
60 :
61 : // netCDF 4.8 switched to expecting filenames in UTF-8 on Windows
62 : // But with netCDF 4.9 and https://github.com/Unidata/netcdf-c/pull/2277/,
63 : // this is apparently back to expecting filenames in current codepage...
64 : // Detect netCDF 4.8 with NC_ENCZARR
65 : // Detect netCDF 4.9 with NC_NOATTCREORD
66 : #if defined(NC_ENCZARR) && !defined(NC_NOATTCREORD)
67 : #define NETCDF_USES_UTF8
68 : #endif
69 :
70 : // Internal function declarations.
71 :
72 : static bool NCDFIsGDALVersionGTE(const char *pszVersion, int nTarget);
73 :
74 : static void
75 : NCDFAddGDALHistory(int fpImage, const char *pszFilename, bool bWriteGDALVersion,
76 : bool bWriteGDALHistory, const char *pszOldHist,
77 : const char *pszFunctionName,
78 : const char *pszCFVersion = GDAL_DEFAULT_NCDF_CONVENTIONS);
79 :
80 : static void NCDFAddHistory(int fpImage, const char *pszAddHist,
81 : const char *pszOldHist);
82 :
83 : static CPLErr NCDFSafeStrcat(char **ppszDest, const char *pszSrc,
84 : size_t *nDestSize);
85 :
86 : // Var / attribute helper functions.
87 : static CPLErr NCDFPutAttr(int nCdfId, int nVarId, const char *pszAttrName,
88 : const char *pszValue);
89 :
90 : // Replace this where used.
91 : static CPLErr NCDFGet1DVar(int nCdfId, int nVarId, char **pszValue);
92 : static CPLErr NCDFPut1DVar(int nCdfId, int nVarId, const char *pszValue);
93 :
94 : // Replace this where used.
95 : static char **NCDFTokenizeArray(const char *pszValue);
96 : static void CopyMetadata(GDALDataset *poSrcDS, GDALRasterBand *poSrcBand,
97 : GDALRasterBand *poDstBand, int fpImage, int CDFVarID,
98 : const char *pszMatchPrefix = nullptr);
99 :
100 : // NetCDF-4 groups helper functions.
101 : // They all work also for NetCDF-3 files which are considered as
102 : // NetCDF-4 file with only one group.
103 : static CPLErr NCDFOpenSubDataset(int nCdfId, const char *pszSubdatasetName,
104 : int *pnGroupId, int *pnVarId);
105 : static CPLErr NCDFGetVisibleDims(int nGroupId, int *pnDims, int **ppanDimIds);
106 : static CPLErr NCDFGetSubGroups(int nGroupId, int *pnSubGroups,
107 : int **ppanSubGroupIds);
108 : static CPLErr NCDFGetGroupFullName(int nGroupId, char **ppszFullName,
109 : bool bNC3Compat = true);
110 : static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId, char **ppszFullName,
111 : bool bNC3Compat = true);
112 : static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId);
113 :
114 : static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,
115 : char **ppszFullName,
116 : bool bMandatory = false);
117 : static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,
118 : const char *pszAtt, int *pnAtt,
119 : bool bMandatory = false);
120 : static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId, char ***ppapszVars);
121 :
122 : // Uncomment this for more debug output.
123 : // #define NCDF_DEBUG 1
124 :
125 : CPLMutex *hNCMutex = nullptr;
126 :
127 : // Workaround https://github.com/OSGeo/gdal/issues/6253
128 : // Having 2 netCDF handles on the same file doesn't work in a multi-threaded
129 : // way. Apparently having the same handle works better (this is OK since
130 : // we have a global mutex on the netCDF library)
131 : static std::map<std::string, int> goMapNameToNetCDFId;
132 : static std::map<int, std::pair<std::string, int>> goMapNetCDFIdToKeyAndCount;
133 :
134 650 : int GDAL_nc_open(const char *pszFilename, int nMode, int *pID)
135 : {
136 1300 : std::string osKey(pszFilename);
137 650 : osKey += "#####";
138 650 : osKey += std::to_string(nMode);
139 650 : auto oIter = goMapNameToNetCDFId.find(osKey);
140 650 : if (oIter == goMapNameToNetCDFId.end())
141 : {
142 603 : int ret = nc_open(pszFilename, nMode, pID);
143 603 : if (ret != NC_NOERR)
144 3 : return ret;
145 600 : goMapNameToNetCDFId[osKey] = *pID;
146 600 : goMapNetCDFIdToKeyAndCount[*pID] =
147 1200 : std::pair<std::string, int>(osKey, 1);
148 600 : return ret;
149 : }
150 : else
151 : {
152 47 : *pID = oIter->second;
153 47 : goMapNetCDFIdToKeyAndCount[oIter->second].second++;
154 47 : return NC_NOERR;
155 : }
156 : }
157 :
158 903 : int GDAL_nc_close(int cdfid)
159 : {
160 903 : int ret = NC_NOERR;
161 903 : auto oIter = goMapNetCDFIdToKeyAndCount.find(cdfid);
162 903 : if (oIter != goMapNetCDFIdToKeyAndCount.end())
163 : {
164 647 : if (--oIter->second.second == 0)
165 : {
166 600 : ret = nc_close(cdfid);
167 600 : goMapNameToNetCDFId.erase(oIter->second.first);
168 600 : goMapNetCDFIdToKeyAndCount.erase(oIter);
169 : }
170 : }
171 : else
172 : {
173 : // we can go here if file opened with nc_open_mem() or nc_create()
174 256 : ret = nc_close(cdfid);
175 : }
176 903 : return ret;
177 : }
178 :
179 : /************************************************************************/
180 : /* ==================================================================== */
181 : /* netCDFRasterBand */
182 : /* ==================================================================== */
183 : /************************************************************************/
184 :
185 : class netCDFRasterBand final : public GDALPamRasterBand
186 : {
187 : friend class netCDFDataset;
188 :
189 : nc_type nc_datatype;
190 : int cdfid;
191 : int nZId;
192 : int nZDim;
193 : int nLevel;
194 : int nBandXPos;
195 : int nBandYPos;
196 : int *panBandZPos;
197 : int *panBandZLev;
198 : bool m_bNoDataSet = false;
199 : double m_dfNoDataValue = 0;
200 : bool m_bNoDataSetAsInt64 = false;
201 : int64_t m_nNodataValueInt64 = 0;
202 : bool m_bNoDataSetAsUInt64 = false;
203 : uint64_t m_nNodataValueUInt64 = 0;
204 : bool bValidRangeValid = false;
205 : double adfValidRange[2]{0, 0};
206 : bool m_bHaveScale = false;
207 : bool m_bHaveOffset = false;
208 : double m_dfScale = 1;
209 : double m_dfOffset = 0;
210 : CPLString m_osUnitType{};
211 : bool bSignedData;
212 : bool bCheckLongitude;
213 : bool m_bCreateMetadataFromOtherVarsDone = false;
214 :
215 : void CreateMetadataFromAttributes();
216 : void CreateMetadataFromOtherVars();
217 :
218 : template <class T>
219 : void CheckData(void *pImage, void *pImageNC, size_t nTmpBlockXSize,
220 : size_t nTmpBlockYSize, bool bCheckIsNan = false);
221 : template <class T>
222 : void CheckDataCpx(void *pImage, void *pImageNC, size_t nTmpBlockXSize,
223 : size_t nTmpBlockYSize, bool bCheckIsNan = false);
224 : void SetBlockSize();
225 :
226 : bool FetchNetcdfChunk(size_t xstart, size_t ystart, void *pImage);
227 :
228 : void SetNoDataValueNoUpdate(double dfNoData);
229 : void SetNoDataValueNoUpdate(int64_t nNoData);
230 : void SetNoDataValueNoUpdate(uint64_t nNoData);
231 :
232 : void SetOffsetNoUpdate(double dfVal);
233 : void SetScaleNoUpdate(double dfVal);
234 : void SetUnitTypeNoUpdate(const char *pszNewValue);
235 :
236 : protected:
237 : CPLXMLNode *SerializeToXML(const char *pszUnused) override;
238 :
239 : public:
240 : struct CONSTRUCTOR_OPEN
241 : {
242 : };
243 :
244 : struct CONSTRUCTOR_CREATE
245 : {
246 : };
247 :
248 : netCDFRasterBand(const CONSTRUCTOR_OPEN &, netCDFDataset *poDS,
249 : int nGroupId, int nZId, int nZDim, int nLevel,
250 : const int *panBandZLen, const int *panBandPos, int nBand);
251 : netCDFRasterBand(const CONSTRUCTOR_CREATE &, netCDFDataset *poDS,
252 : GDALDataType eType, int nBand, bool bSigned = true,
253 : const char *pszBandName = nullptr,
254 : const char *pszLongName = nullptr, int nZId = -1,
255 : int nZDim = 2, int nLevel = 0,
256 : const int *panBandZLev = nullptr,
257 : const int *panBandZPos = nullptr,
258 : const int *paDimIds = nullptr);
259 : virtual ~netCDFRasterBand();
260 :
261 : virtual double GetNoDataValue(int *) override;
262 : virtual int64_t GetNoDataValueAsInt64(int *pbSuccess = nullptr) override;
263 : virtual uint64_t GetNoDataValueAsUInt64(int *pbSuccess = nullptr) override;
264 : virtual CPLErr SetNoDataValue(double) override;
265 : virtual CPLErr SetNoDataValueAsInt64(int64_t nNoData) override;
266 : virtual CPLErr SetNoDataValueAsUInt64(uint64_t nNoData) override;
267 : // virtual CPLErr DeleteNoDataValue();
268 : virtual double GetOffset(int *) override;
269 : virtual CPLErr SetOffset(double) override;
270 : virtual double GetScale(int *) override;
271 : virtual CPLErr SetScale(double) override;
272 : virtual const char *GetUnitType() override;
273 : virtual CPLErr SetUnitType(const char *) override;
274 : virtual CPLErr IReadBlock(int, int, void *) override;
275 : virtual CPLErr IWriteBlock(int, int, void *) override;
276 :
277 : char **GetMetadata(const char *pszDomain = "") override;
278 : const char *GetMetadataItem(const char *pszName,
279 : const char *pszDomain = "") override;
280 :
281 : virtual CPLErr SetMetadataItem(const char *pszName, const char *pszValue,
282 : const char *pszDomain = "") override;
283 : virtual CPLErr SetMetadata(char **papszMD,
284 : const char *pszDomain = "") override;
285 : };
286 :
287 : /************************************************************************/
288 : /* netCDFRasterBand() */
289 : /************************************************************************/
290 :
291 466 : netCDFRasterBand::netCDFRasterBand(const netCDFRasterBand::CONSTRUCTOR_OPEN &,
292 : netCDFDataset *poNCDFDS, int nGroupId,
293 : int nZIdIn, int nZDimIn, int nLevelIn,
294 : const int *panBandZLevIn,
295 466 : const int *panBandZPosIn, int nBandIn)
296 : : nc_datatype(NC_NAT), cdfid(nGroupId), nZId(nZIdIn), nZDim(nZDimIn),
297 466 : nLevel(nLevelIn), nBandXPos(panBandZPosIn[0]),
298 466 : nBandYPos(nZDim == 1 ? -1 : panBandZPosIn[1]), panBandZPos(nullptr),
299 : panBandZLev(nullptr),
300 : bSignedData(true), // Default signed, except for Byte.
301 932 : bCheckLongitude(false)
302 : {
303 466 : poDS = poNCDFDS;
304 466 : nBand = nBandIn;
305 :
306 : // Take care of all other dimensions.
307 466 : if (nZDim > 2)
308 : {
309 168 : panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
310 168 : panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
311 :
312 462 : for (int i = 0; i < nZDim - 2; i++)
313 : {
314 294 : panBandZPos[i] = panBandZPosIn[i + 2];
315 294 : panBandZLev[i] = panBandZLevIn[i];
316 : }
317 : }
318 :
319 466 : nRasterXSize = poDS->GetRasterXSize();
320 466 : nRasterYSize = poDS->GetRasterYSize();
321 466 : nBlockXSize = poDS->GetRasterXSize();
322 466 : nBlockYSize = 1;
323 :
324 : // Get the type of the "z" variable, our target raster array.
325 466 : if (nc_inq_var(cdfid, nZId, nullptr, &nc_datatype, nullptr, nullptr,
326 466 : nullptr) != NC_NOERR)
327 : {
328 0 : CPLError(CE_Failure, CPLE_AppDefined, "Error in nc_var_inq() on 'z'.");
329 0 : return;
330 : }
331 :
332 466 : if (NCDFIsUserDefinedType(cdfid, nc_datatype))
333 : {
334 : // First enquire and check that the number of fields is 2
335 : size_t nfields, compoundsize;
336 5 : if (nc_inq_compound(cdfid, nc_datatype, nullptr, &compoundsize,
337 5 : &nfields) != NC_NOERR)
338 : {
339 0 : CPLError(CE_Failure, CPLE_AppDefined,
340 : "Error in nc_inq_compound() on 'z'.");
341 0 : return;
342 : }
343 :
344 5 : if (nfields != 2)
345 : {
346 0 : CPLError(CE_Failure, CPLE_AppDefined,
347 : "Unsupported data type encountered in nc_inq_compound() "
348 : "on 'z'.");
349 0 : return;
350 : }
351 :
352 : // Now check that that two types are the same in the struct.
353 : nc_type field_type1, field_type2;
354 : int field_dims1, field_dims2;
355 5 : if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
356 : &field_type1, &field_dims1,
357 5 : nullptr) != NC_NOERR)
358 : {
359 0 : CPLError(
360 : CE_Failure, CPLE_AppDefined,
361 : "Error in querying Field 1 in nc_inq_compound_field() on 'z'.");
362 0 : return;
363 : }
364 :
365 5 : if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
366 : &field_type2, &field_dims2,
367 5 : nullptr) != NC_NOERR)
368 : {
369 0 : CPLError(
370 : CE_Failure, CPLE_AppDefined,
371 : "Error in querying Field 2 in nc_inq_compound_field() on 'z'.");
372 0 : return;
373 : }
374 :
375 5 : if ((field_type1 != field_type2) || (field_dims1 != field_dims2) ||
376 5 : (field_dims1 != 0))
377 : {
378 0 : CPLError(CE_Failure, CPLE_AppDefined,
379 : "Error in interpreting compound data type on 'z'.");
380 0 : return;
381 : }
382 :
383 5 : if (field_type1 == NC_SHORT)
384 0 : eDataType = GDT_CInt16;
385 5 : else if (field_type1 == NC_INT)
386 0 : eDataType = GDT_CInt32;
387 5 : else if (field_type1 == NC_FLOAT)
388 4 : eDataType = GDT_CFloat32;
389 1 : else if (field_type1 == NC_DOUBLE)
390 1 : eDataType = GDT_CFloat64;
391 : else
392 : {
393 0 : CPLError(CE_Failure, CPLE_AppDefined,
394 : "Unsupported netCDF compound data type encountered.");
395 0 : return;
396 : }
397 : }
398 : else
399 : {
400 461 : if (nc_datatype == NC_BYTE)
401 140 : eDataType = GDT_Byte;
402 321 : else if (nc_datatype == NC_CHAR)
403 0 : eDataType = GDT_Byte;
404 321 : else if (nc_datatype == NC_SHORT)
405 41 : eDataType = GDT_Int16;
406 280 : else if (nc_datatype == NC_INT)
407 89 : eDataType = GDT_Int32;
408 191 : else if (nc_datatype == NC_FLOAT)
409 115 : eDataType = GDT_Float32;
410 76 : else if (nc_datatype == NC_DOUBLE)
411 40 : eDataType = GDT_Float64;
412 36 : else if (nc_datatype == NC_UBYTE)
413 14 : eDataType = GDT_Byte;
414 22 : else if (nc_datatype == NC_USHORT)
415 4 : eDataType = GDT_UInt16;
416 18 : else if (nc_datatype == NC_UINT)
417 3 : eDataType = GDT_UInt32;
418 15 : else if (nc_datatype == NC_INT64)
419 8 : eDataType = GDT_Int64;
420 7 : else if (nc_datatype == NC_UINT64)
421 7 : eDataType = GDT_UInt64;
422 : else
423 : {
424 0 : if (nBand == 1)
425 0 : CPLError(CE_Warning, CPLE_AppDefined,
426 : "Unsupported netCDF datatype (%d), treat as Float32.",
427 0 : static_cast<int>(nc_datatype));
428 0 : eDataType = GDT_Float32;
429 0 : nc_datatype = NC_FLOAT;
430 : }
431 : }
432 :
433 : // Find and set No Data for this variable.
434 466 : nc_type atttype = NC_NAT;
435 466 : size_t attlen = 0;
436 466 : const char *pszNoValueName = nullptr;
437 :
438 : // Find attribute name, either _FillValue or missing_value.
439 466 : int status = nc_inq_att(cdfid, nZId, NCDF_FillValue, &atttype, &attlen);
440 466 : if (status == NC_NOERR)
441 : {
442 248 : pszNoValueName = NCDF_FillValue;
443 : }
444 : else
445 : {
446 218 : status = nc_inq_att(cdfid, nZId, "missing_value", &atttype, &attlen);
447 218 : if (status == NC_NOERR)
448 : {
449 12 : pszNoValueName = "missing_value";
450 : }
451 : }
452 :
453 : // Fetch missing value.
454 466 : double dfNoData = 0.0;
455 466 : bool bGotNoData = false;
456 466 : int64_t nNoDataAsInt64 = 0;
457 466 : bool bGotNoDataAsInt64 = false;
458 466 : uint64_t nNoDataAsUInt64 = 0;
459 466 : bool bGotNoDataAsUInt64 = false;
460 466 : if (status == NC_NOERR)
461 : {
462 260 : nc_type nAttrType = NC_NAT;
463 260 : size_t nAttrLen = 0;
464 260 : status = nc_inq_att(cdfid, nZId, pszNoValueName, &nAttrType, &nAttrLen);
465 260 : if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_INT64)
466 : {
467 : long long v;
468 7 : nc_get_att_longlong(cdfid, nZId, pszNoValueName, &v);
469 7 : bGotNoData = true;
470 7 : bGotNoDataAsInt64 = true;
471 7 : nNoDataAsInt64 = static_cast<int64_t>(v);
472 : }
473 253 : else if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_UINT64)
474 : {
475 : unsigned long long v;
476 7 : nc_get_att_ulonglong(cdfid, nZId, pszNoValueName, &v);
477 7 : bGotNoData = true;
478 7 : bGotNoDataAsUInt64 = true;
479 7 : nNoDataAsUInt64 = static_cast<uint64_t>(v);
480 : }
481 246 : else if (NCDFGetAttr(cdfid, nZId, pszNoValueName, &dfNoData) == CE_None)
482 : {
483 245 : bGotNoData = true;
484 : }
485 : }
486 :
487 : // If NoData was not found, use the default value, but for non-Byte types
488 : // as it is not recommended:
489 : // https://www.unidata.ucar.edu/software/netcdf/docs/attribute_conventions.html
490 466 : nc_type vartype = NC_NAT;
491 466 : if (!bGotNoData)
492 : {
493 207 : nc_inq_vartype(cdfid, nZId, &vartype);
494 207 : if (vartype == NC_INT64)
495 : {
496 : nNoDataAsInt64 =
497 1 : NCDFGetDefaultNoDataValueAsInt64(cdfid, nZId, bGotNoData);
498 1 : bGotNoDataAsInt64 = bGotNoData;
499 : }
500 206 : else if (vartype == NC_UINT64)
501 : {
502 : nNoDataAsUInt64 =
503 0 : NCDFGetDefaultNoDataValueAsUInt64(cdfid, nZId, bGotNoData);
504 0 : bGotNoDataAsUInt64 = bGotNoData;
505 : }
506 206 : else if (vartype != NC_CHAR && vartype != NC_BYTE &&
507 89 : vartype != NC_UBYTE)
508 : {
509 81 : dfNoData =
510 81 : NCDFGetDefaultNoDataValue(cdfid, nZId, vartype, bGotNoData);
511 81 : if (bGotNoData)
512 : {
513 70 : CPLDebug("GDAL_netCDF",
514 : "did not get nodata value for variable #%d, using "
515 : "default %f",
516 : nZId, dfNoData);
517 : }
518 : }
519 : }
520 :
521 466 : bool bHasUnderscoreUnsignedAttr = false;
522 466 : bool bUnderscoreUnsignedAttrVal = false;
523 : {
524 466 : char *pszTemp = nullptr;
525 466 : if (NCDFGetAttr(cdfid, nZId, "_Unsigned", &pszTemp) == CE_None)
526 : {
527 132 : if (EQUAL(pszTemp, "true"))
528 : {
529 124 : bHasUnderscoreUnsignedAttr = true;
530 124 : bUnderscoreUnsignedAttrVal = true;
531 : }
532 8 : else if (EQUAL(pszTemp, "false"))
533 : {
534 8 : bHasUnderscoreUnsignedAttr = true;
535 8 : bUnderscoreUnsignedAttrVal = false;
536 : }
537 132 : CPLFree(pszTemp);
538 : }
539 : }
540 :
541 : // Look for valid_range or valid_min/valid_max.
542 :
543 : // First look for valid_range.
544 466 : if (CPLFetchBool(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE", true))
545 : {
546 464 : char *pszValidRange = nullptr;
547 464 : if (NCDFGetAttr(cdfid, nZId, "valid_range", &pszValidRange) ==
548 125 : CE_None &&
549 589 : pszValidRange[0] == '{' &&
550 125 : pszValidRange[strlen(pszValidRange) - 1] == '}')
551 : {
552 : const std::string osValidRange =
553 375 : std::string(pszValidRange).substr(1, strlen(pszValidRange) - 2);
554 : const CPLStringList aosValidRange(
555 250 : CSLTokenizeString2(osValidRange.c_str(), ",", 0));
556 125 : if (aosValidRange.size() == 2 &&
557 250 : CPLGetValueType(aosValidRange[0]) != CPL_VALUE_STRING &&
558 125 : CPLGetValueType(aosValidRange[1]) != CPL_VALUE_STRING)
559 : {
560 125 : bValidRangeValid = true;
561 125 : adfValidRange[0] = CPLAtof(aosValidRange[0]);
562 125 : adfValidRange[1] = CPLAtof(aosValidRange[1]);
563 : }
564 : }
565 464 : CPLFree(pszValidRange);
566 :
567 : // If not found look for valid_min and valid_max.
568 464 : if (!bValidRangeValid)
569 : {
570 339 : double dfMin = 0;
571 339 : double dfMax = 0;
572 354 : if (NCDFGetAttr(cdfid, nZId, "valid_min", &dfMin) == CE_None &&
573 15 : NCDFGetAttr(cdfid, nZId, "valid_max", &dfMax) == CE_None)
574 : {
575 8 : adfValidRange[0] = dfMin;
576 8 : adfValidRange[1] = dfMax;
577 8 : bValidRangeValid = true;
578 : }
579 : }
580 :
581 464 : if (bValidRangeValid &&
582 133 : (adfValidRange[0] < 0 || adfValidRange[1] < 0) &&
583 17 : nc_datatype == NC_SHORT && bHasUnderscoreUnsignedAttr &&
584 : bUnderscoreUnsignedAttrVal)
585 : {
586 2 : if (adfValidRange[0] < 0)
587 0 : adfValidRange[0] += 65536;
588 2 : if (adfValidRange[1] < 0)
589 2 : adfValidRange[1] += 65536;
590 2 : if (adfValidRange[0] <= adfValidRange[1])
591 : {
592 : // Updating metadata item
593 2 : GDALPamRasterBand::SetMetadataItem(
594 : "valid_range",
595 2 : CPLSPrintf("{%d,%d}", static_cast<int>(adfValidRange[0]),
596 2 : static_cast<int>(adfValidRange[1])));
597 : }
598 : }
599 :
600 464 : if (bValidRangeValid && adfValidRange[0] > adfValidRange[1])
601 : {
602 0 : CPLError(CE_Warning, CPLE_AppDefined,
603 : "netCDFDataset::valid_range: min > max:\n"
604 : " min: %lf\n max: %lf\n",
605 : adfValidRange[0], adfValidRange[1]);
606 0 : bValidRangeValid = false;
607 0 : adfValidRange[0] = 0.0;
608 0 : adfValidRange[1] = 0.0;
609 : }
610 : }
611 :
612 : // Special For Byte Bands: check for signed/unsigned byte.
613 466 : if (nc_datatype == NC_BYTE)
614 : {
615 : // netcdf uses signed byte by default, but GDAL uses unsigned by default
616 : // This may cause unexpected results, but is needed for back-compat.
617 140 : if (poNCDFDS->bIsGdalFile)
618 119 : bSignedData = false;
619 : else
620 21 : bSignedData = true;
621 :
622 : // For NC4 format NC_BYTE is (normally) signed, NC_UBYTE is unsigned.
623 : // But in case a NC3 file was converted automatically and has hints
624 : // that it is unsigned, take them into account
625 140 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
626 : {
627 3 : bSignedData = true;
628 : }
629 :
630 : // If we got valid_range, test for signed/unsigned range.
631 : // https://docs.unidata.ucar.edu/netcdf-c/current/attribute_conventions.html
632 140 : if (bValidRangeValid)
633 : {
634 : // If we got valid_range={0,255}, treat as unsigned.
635 121 : if (adfValidRange[0] == 0 && adfValidRange[1] == 255)
636 : {
637 113 : bSignedData = false;
638 : // Reset valid_range.
639 113 : bValidRangeValid = false;
640 : }
641 : // If we got valid_range={-128,127}, treat as signed.
642 8 : else if (adfValidRange[0] == -128 && adfValidRange[1] == 127)
643 : {
644 8 : bSignedData = true;
645 : // Reset valid_range.
646 8 : bValidRangeValid = false;
647 : }
648 : }
649 : // Else test for _Unsigned.
650 : // https://docs.unidata.ucar.edu/nug/current/best_practices.html
651 : else
652 : {
653 19 : if (bHasUnderscoreUnsignedAttr)
654 7 : bSignedData = !bUnderscoreUnsignedAttrVal;
655 : }
656 :
657 140 : if (bSignedData)
658 : {
659 20 : eDataType = GDT_Int8;
660 : }
661 120 : else if (dfNoData < 0)
662 : {
663 : // Fix nodata value as it was stored signed.
664 6 : dfNoData += 256;
665 6 : if (pszNoValueName)
666 : {
667 : // Updating metadata item
668 6 : GDALPamRasterBand::SetMetadataItem(
669 : pszNoValueName,
670 : CPLSPrintf("%d", static_cast<int>(dfNoData)));
671 : }
672 : }
673 : }
674 326 : else if (nc_datatype == NC_SHORT)
675 : {
676 41 : if (bHasUnderscoreUnsignedAttr)
677 : {
678 4 : bSignedData = !bUnderscoreUnsignedAttrVal;
679 4 : if (!bSignedData)
680 4 : eDataType = GDT_UInt16;
681 : }
682 :
683 : // Fix nodata value as it was stored signed.
684 41 : if (!bSignedData && dfNoData < 0)
685 : {
686 4 : dfNoData += 65536;
687 4 : if (pszNoValueName)
688 : {
689 : // Updating metadata item
690 4 : GDALPamRasterBand::SetMetadataItem(
691 : pszNoValueName,
692 : CPLSPrintf("%d", static_cast<int>(dfNoData)));
693 : }
694 : }
695 : }
696 :
697 285 : else if (nc_datatype == NC_UBYTE || nc_datatype == NC_USHORT ||
698 267 : nc_datatype == NC_UINT || nc_datatype == NC_UINT64)
699 : {
700 28 : bSignedData = false;
701 : }
702 :
703 466 : CPLDebug("GDAL_netCDF", "netcdf type=%d gdal type=%d signedByte=%d",
704 466 : nc_datatype, eDataType, static_cast<int>(bSignedData));
705 :
706 466 : if (bGotNoData)
707 : {
708 : // Set nodata value.
709 330 : if (bGotNoDataAsInt64)
710 : {
711 8 : if (eDataType == GDT_Int64)
712 : {
713 8 : SetNoDataValueNoUpdate(nNoDataAsInt64);
714 : }
715 0 : else if (eDataType == GDT_UInt64 && nNoDataAsInt64 >= 0)
716 : {
717 0 : SetNoDataValueNoUpdate(static_cast<uint64_t>(nNoDataAsInt64));
718 : }
719 : else
720 : {
721 0 : SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsInt64));
722 : }
723 : }
724 322 : else if (bGotNoDataAsUInt64)
725 : {
726 7 : if (eDataType == GDT_UInt64)
727 : {
728 7 : SetNoDataValueNoUpdate(nNoDataAsUInt64);
729 : }
730 0 : else if (eDataType == GDT_Int64 &&
731 : nNoDataAsUInt64 <=
732 0 : static_cast<uint64_t>(
733 0 : std::numeric_limits<int64_t>::max()))
734 : {
735 0 : SetNoDataValueNoUpdate(static_cast<int64_t>(nNoDataAsUInt64));
736 : }
737 : else
738 : {
739 0 : SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsUInt64));
740 : }
741 : }
742 : else
743 : {
744 : #ifdef NCDF_DEBUG
745 : CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) read", dfNoData);
746 : #endif
747 315 : if (eDataType == GDT_Int64 && GDALIsValueExactAs<int64_t>(dfNoData))
748 : {
749 0 : SetNoDataValueNoUpdate(static_cast<int64_t>(dfNoData));
750 : }
751 315 : else if (eDataType == GDT_UInt64 &&
752 0 : GDALIsValueExactAs<uint64_t>(dfNoData))
753 : {
754 0 : SetNoDataValueNoUpdate(static_cast<uint64_t>(dfNoData));
755 : }
756 : else
757 : {
758 315 : SetNoDataValueNoUpdate(dfNoData);
759 : }
760 : }
761 : }
762 :
763 466 : CreateMetadataFromAttributes();
764 :
765 : // Attempt to fetch the scale_factor and add_offset attributes for the
766 : // variable and set them. If these values are not available, set
767 : // offset to 0 and scale to 1.
768 466 : if (nc_inq_attid(cdfid, nZId, CF_ADD_OFFSET, nullptr) == NC_NOERR)
769 : {
770 16 : double dfOffset = 0;
771 16 : status = nc_get_att_double(cdfid, nZId, CF_ADD_OFFSET, &dfOffset);
772 16 : CPLDebug("GDAL_netCDF", "got add_offset=%.16g, status=%d", dfOffset,
773 : status);
774 16 : SetOffsetNoUpdate(dfOffset);
775 : }
776 :
777 466 : bool bHasScale = false;
778 466 : if (nc_inq_attid(cdfid, nZId, CF_SCALE_FACTOR, nullptr) == NC_NOERR)
779 : {
780 20 : bHasScale = true;
781 20 : double dfScale = 1;
782 20 : status = nc_get_att_double(cdfid, nZId, CF_SCALE_FACTOR, &dfScale);
783 20 : CPLDebug("GDAL_netCDF", "got scale_factor=%.16g, status=%d", dfScale,
784 : status);
785 20 : SetScaleNoUpdate(dfScale);
786 : }
787 :
788 12 : if (bValidRangeValid && GDALDataTypeIsInteger(eDataType) &&
789 4 : eDataType != GDT_Int64 && eDataType != GDT_UInt64 &&
790 4 : (std::fabs(std::round(adfValidRange[0]) - adfValidRange[0]) > 1e-5 ||
791 478 : std::fabs(std::round(adfValidRange[1]) - adfValidRange[1]) > 1e-5) &&
792 1 : CSLFetchNameValue(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE") ==
793 : nullptr)
794 : {
795 1 : CPLError(CE_Warning, CPLE_AppDefined,
796 : "validity range = %f, %f contains floating-point values, "
797 : "whereas data type is integer. valid_range is thus likely "
798 : "wrong%s. Ignoring it.",
799 : adfValidRange[0], adfValidRange[1],
800 : bHasScale ? " (likely scaled using scale_factor/add_factor "
801 : "whereas it should be using the packed data type)"
802 : : "");
803 1 : bValidRangeValid = false;
804 1 : adfValidRange[0] = 0.0;
805 1 : adfValidRange[1] = 0.0;
806 : }
807 :
808 : // Should we check for longitude values > 360?
809 466 : bCheckLongitude =
810 932 : CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")) &&
811 466 : NCDFIsVarLongitude(cdfid, nZId, nullptr);
812 :
813 : // Attempt to fetch the units attribute for the variable and set it.
814 466 : SetUnitTypeNoUpdate(netCDFRasterBand::GetMetadataItem(CF_UNITS));
815 :
816 466 : SetBlockSize();
817 : }
818 :
819 645 : void netCDFRasterBand::SetBlockSize()
820 : {
821 : // Check for variable chunking (netcdf-4 only).
822 : // GDAL block size should be set to hdf5 chunk size.
823 645 : int nTmpFormat = 0;
824 645 : int status = nc_inq_format(cdfid, &nTmpFormat);
825 645 : NetCDFFormatEnum eTmpFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
826 645 : if ((status == NC_NOERR) &&
827 553 : (eTmpFormat == NCDF_FORMAT_NC4 || eTmpFormat == NCDF_FORMAT_NC4C))
828 : {
829 106 : size_t chunksize[MAX_NC_DIMS] = {};
830 : // Check for chunksize and set it as the blocksize (optimizes read).
831 106 : status = nc_inq_var_chunking(cdfid, nZId, &nTmpFormat, chunksize);
832 106 : if ((status == NC_NOERR) && (nTmpFormat == NC_CHUNKED))
833 : {
834 11 : nBlockXSize = (int)chunksize[nZDim - 1];
835 11 : if (nZDim >= 2)
836 11 : nBlockYSize = (int)chunksize[nZDim - 2];
837 : else
838 0 : nBlockYSize = 1;
839 : }
840 : }
841 :
842 : // Deal with bottom-up datasets and nBlockYSize != 1.
843 645 : auto poGDS = static_cast<netCDFDataset *>(poDS);
844 645 : if (poGDS->bBottomUp && nBlockYSize != 1 && poGDS->poChunkCache == nullptr)
845 : {
846 5 : if (poGDS->eAccess == GA_ReadOnly)
847 : {
848 : // Try to cache 1 or 2 'rows' of netCDF chunks along the whole
849 : // width of the raster
850 5 : size_t nChunks =
851 5 : static_cast<size_t>(DIV_ROUND_UP(nRasterXSize, nBlockXSize));
852 5 : if ((nRasterYSize % nBlockYSize) != 0)
853 1 : nChunks *= 2;
854 : const size_t nChunkSize =
855 5 : static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
856 5 : nBlockXSize * nBlockYSize;
857 5 : constexpr size_t MAX_CACHE_SIZE = 100 * 1024 * 1024;
858 5 : nChunks = std::min(nChunks, MAX_CACHE_SIZE / nChunkSize);
859 5 : if (nChunks)
860 : {
861 5 : poGDS->poChunkCache.reset(
862 5 : new netCDFDataset::ChunkCacheType(nChunks));
863 : }
864 : }
865 : else
866 : {
867 0 : nBlockYSize = 1;
868 : }
869 : }
870 645 : }
871 :
872 : // Constructor in create mode.
873 : // If nZId and following variables are not passed, the band will have 2
874 : // dimensions.
875 : // TODO: Get metadata, missing val from band #1 if nZDim > 2.
876 179 : netCDFRasterBand::netCDFRasterBand(
877 : const netCDFRasterBand::CONSTRUCTOR_CREATE &, netCDFDataset *poNCDFDS,
878 : const GDALDataType eTypeIn, int nBandIn, bool bSigned,
879 : const char *pszBandName, const char *pszLongName, int nZIdIn, int nZDimIn,
880 : int nLevelIn, const int *panBandZLevIn, const int *panBandZPosIn,
881 179 : const int *paDimIds)
882 179 : : nc_datatype(NC_NAT), cdfid(poNCDFDS->GetCDFID()), nZId(nZIdIn),
883 : nZDim(nZDimIn), nLevel(nLevelIn), nBandXPos(1), nBandYPos(0),
884 : panBandZPos(nullptr), panBandZLev(nullptr), bSignedData(bSigned),
885 179 : bCheckLongitude(false), m_bCreateMetadataFromOtherVarsDone(true)
886 : {
887 179 : poDS = poNCDFDS;
888 179 : nBand = nBandIn;
889 :
890 179 : nRasterXSize = poDS->GetRasterXSize();
891 179 : nRasterYSize = poDS->GetRasterYSize();
892 179 : nBlockXSize = poDS->GetRasterXSize();
893 179 : nBlockYSize = 1;
894 :
895 179 : if (poDS->GetAccess() != GA_Update)
896 : {
897 0 : CPLError(CE_Failure, CPLE_NotSupported,
898 : "Dataset is not in update mode, "
899 : "wrong netCDFRasterBand constructor");
900 0 : return;
901 : }
902 :
903 : // Take care of all other dimensions.
904 179 : if (nZDim > 2 && paDimIds != nullptr)
905 : {
906 27 : nBandXPos = panBandZPosIn[0];
907 27 : nBandYPos = panBandZPosIn[1];
908 :
909 27 : panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
910 27 : panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
911 :
912 76 : for (int i = 0; i < nZDim - 2; i++)
913 : {
914 49 : panBandZPos[i] = panBandZPosIn[i + 2];
915 49 : panBandZLev[i] = panBandZLevIn[i];
916 : }
917 : }
918 :
919 : // Get the type of the "z" variable, our target raster array.
920 179 : eDataType = eTypeIn;
921 :
922 179 : switch (eDataType)
923 : {
924 74 : case GDT_Byte:
925 74 : nc_datatype = NC_BYTE;
926 : // NC_UBYTE (unsigned byte) is only available for NC4.
927 74 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
928 3 : nc_datatype = NC_UBYTE;
929 74 : break;
930 7 : case GDT_Int8:
931 7 : nc_datatype = NC_BYTE;
932 7 : break;
933 11 : case GDT_Int16:
934 11 : nc_datatype = NC_SHORT;
935 11 : break;
936 24 : case GDT_Int32:
937 24 : nc_datatype = NC_INT;
938 24 : break;
939 13 : case GDT_Float32:
940 13 : nc_datatype = NC_FLOAT;
941 13 : break;
942 8 : case GDT_Float64:
943 8 : nc_datatype = NC_DOUBLE;
944 8 : break;
945 7 : case GDT_Int64:
946 7 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
947 : {
948 7 : nc_datatype = NC_INT64;
949 : }
950 : else
951 : {
952 0 : if (nBand == 1)
953 0 : CPLError(
954 : CE_Warning, CPLE_AppDefined,
955 : "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
956 : "Int64");
957 0 : nc_datatype = NC_DOUBLE;
958 0 : eDataType = GDT_Float64;
959 : }
960 7 : break;
961 7 : case GDT_UInt64:
962 7 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
963 : {
964 7 : nc_datatype = NC_UINT64;
965 : }
966 : else
967 : {
968 0 : if (nBand == 1)
969 0 : CPLError(
970 : CE_Warning, CPLE_AppDefined,
971 : "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
972 : "UInt64");
973 0 : nc_datatype = NC_DOUBLE;
974 0 : eDataType = GDT_Float64;
975 : }
976 7 : break;
977 6 : case GDT_UInt16:
978 6 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
979 : {
980 6 : nc_datatype = NC_USHORT;
981 6 : break;
982 : }
983 : [[fallthrough]];
984 : case GDT_UInt32:
985 6 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
986 : {
987 6 : nc_datatype = NC_UINT;
988 6 : break;
989 : }
990 : [[fallthrough]];
991 : default:
992 16 : if (nBand == 1)
993 8 : CPLError(CE_Warning, CPLE_AppDefined,
994 : "Unsupported GDAL datatype (%d), treat as NC_FLOAT.",
995 8 : static_cast<int>(eDataType));
996 16 : nc_datatype = NC_FLOAT;
997 16 : eDataType = GDT_Float32;
998 16 : break;
999 : }
1000 :
1001 : // Define the variable if necessary (if nZId == -1).
1002 179 : bool bDefineVar = false;
1003 :
1004 179 : if (nZId == -1)
1005 : {
1006 157 : bDefineVar = true;
1007 :
1008 : // Make sure we are in define mode.
1009 157 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1010 :
1011 : char szTempPrivate[256 + 1];
1012 157 : const char *pszTemp = nullptr;
1013 157 : if (!pszBandName || EQUAL(pszBandName, ""))
1014 : {
1015 135 : snprintf(szTempPrivate, sizeof(szTempPrivate), "Band%d", nBand);
1016 135 : pszTemp = szTempPrivate;
1017 : }
1018 : else
1019 : {
1020 22 : pszTemp = pszBandName;
1021 : }
1022 :
1023 : int status;
1024 157 : if (nZDim > 2 && paDimIds != nullptr)
1025 : {
1026 5 : status =
1027 5 : nc_def_var(cdfid, pszTemp, nc_datatype, nZDim, paDimIds, &nZId);
1028 : }
1029 : else
1030 : {
1031 152 : int anBandDims[2] = {poNCDFDS->nYDimID, poNCDFDS->nXDimID};
1032 : status =
1033 152 : nc_def_var(cdfid, pszTemp, nc_datatype, 2, anBandDims, &nZId);
1034 : }
1035 157 : NCDF_ERR(status);
1036 157 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d) id=%d", cdfid, pszTemp,
1037 : nc_datatype, nZId);
1038 :
1039 157 : if (!pszLongName || EQUAL(pszLongName, ""))
1040 : {
1041 150 : snprintf(szTempPrivate, sizeof(szTempPrivate),
1042 : "GDAL Band Number %d", nBand);
1043 150 : pszTemp = szTempPrivate;
1044 : }
1045 : else
1046 : {
1047 7 : pszTemp = pszLongName;
1048 : }
1049 : status =
1050 157 : nc_put_att_text(cdfid, nZId, CF_LNG_NAME, strlen(pszTemp), pszTemp);
1051 157 : NCDF_ERR(status);
1052 :
1053 157 : poNCDFDS->DefVarDeflate(nZId, true);
1054 : }
1055 :
1056 : // For Byte data add signed/unsigned info.
1057 179 : if (eDataType == GDT_Byte || eDataType == GDT_Int8)
1058 : {
1059 81 : if (bDefineVar)
1060 : {
1061 : // Only add attributes if creating variable.
1062 : // For unsigned NC_BYTE (except NC4 format),
1063 : // add valid_range and _Unsigned ( defined in CF-1 and NUG ).
1064 73 : if (nc_datatype == NC_BYTE && poNCDFDS->eFormat != NCDF_FORMAT_NC4)
1065 : {
1066 70 : CPLDebug("GDAL_netCDF",
1067 : "adding valid_range attributes for Byte Band");
1068 70 : short l_adfValidRange[2] = {0, 0};
1069 : int status;
1070 70 : if (bSignedData || eDataType == GDT_Int8)
1071 : {
1072 7 : l_adfValidRange[0] = -128;
1073 7 : l_adfValidRange[1] = 127;
1074 7 : status =
1075 7 : nc_put_att_text(cdfid, nZId, "_Unsigned", 5, "false");
1076 : }
1077 : else
1078 : {
1079 63 : l_adfValidRange[0] = 0;
1080 63 : l_adfValidRange[1] = 255;
1081 : status =
1082 63 : nc_put_att_text(cdfid, nZId, "_Unsigned", 4, "true");
1083 : }
1084 70 : NCDF_ERR(status);
1085 70 : status = nc_put_att_short(cdfid, nZId, "valid_range", NC_SHORT,
1086 : 2, l_adfValidRange);
1087 70 : NCDF_ERR(status);
1088 : }
1089 : }
1090 : }
1091 :
1092 179 : if (nc_datatype != NC_BYTE && nc_datatype != NC_CHAR &&
1093 101 : nc_datatype != NC_UBYTE)
1094 : {
1095 : // Set default nodata.
1096 98 : bool bIgnored = false;
1097 : double dfNoData =
1098 98 : NCDFGetDefaultNoDataValue(cdfid, nZId, nc_datatype, bIgnored);
1099 : #ifdef NCDF_DEBUG
1100 : CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) default", dfNoData);
1101 : #endif
1102 98 : netCDFRasterBand::SetNoDataValue(dfNoData);
1103 : }
1104 :
1105 179 : SetBlockSize();
1106 : }
1107 :
1108 : /************************************************************************/
1109 : /* ~netCDFRasterBand() */
1110 : /************************************************************************/
1111 :
1112 1290 : netCDFRasterBand::~netCDFRasterBand()
1113 : {
1114 645 : netCDFRasterBand::FlushCache(true);
1115 645 : CPLFree(panBandZPos);
1116 645 : CPLFree(panBandZLev);
1117 1290 : }
1118 :
1119 : /************************************************************************/
1120 : /* GetMetadata() */
1121 : /************************************************************************/
1122 :
1123 47 : char **netCDFRasterBand::GetMetadata(const char *pszDomain)
1124 : {
1125 47 : if (!m_bCreateMetadataFromOtherVarsDone)
1126 47 : CreateMetadataFromOtherVars();
1127 47 : return GDALPamRasterBand::GetMetadata(pszDomain);
1128 : }
1129 :
1130 : /************************************************************************/
1131 : /* GetMetadataItem() */
1132 : /************************************************************************/
1133 :
1134 531 : const char *netCDFRasterBand::GetMetadataItem(const char *pszName,
1135 : const char *pszDomain)
1136 : {
1137 531 : if (!m_bCreateMetadataFromOtherVarsDone &&
1138 515 : STARTS_WITH(pszName, "NETCDF_DIM_") &&
1139 1 : (!pszDomain || pszDomain[0] == 0))
1140 1 : CreateMetadataFromOtherVars();
1141 531 : return GDALPamRasterBand::GetMetadataItem(pszName, pszDomain);
1142 : }
1143 :
1144 : /************************************************************************/
1145 : /* SetMetadataItem() */
1146 : /************************************************************************/
1147 :
1148 6 : CPLErr netCDFRasterBand::SetMetadataItem(const char *pszName,
1149 : const char *pszValue,
1150 : const char *pszDomain)
1151 : {
1152 7 : if (GetAccess() == GA_Update &&
1153 7 : (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)
1154 : {
1155 : // Same logic as in CopyMetadata()
1156 :
1157 1 : const char *const papszIgnoreBand[] = {
1158 : CF_ADD_OFFSET, CF_SCALE_FACTOR, "valid_range", "_Unsigned",
1159 : NCDF_FillValue, "coordinates", nullptr};
1160 : // Do not copy varname, stats, NETCDF_DIM_*, nodata
1161 : // and items in papszIgnoreBand.
1162 3 : if (STARTS_WITH(pszName, "NETCDF_VARNAME") ||
1163 1 : STARTS_WITH(pszName, "STATISTICS_") ||
1164 1 : STARTS_WITH(pszName, "NETCDF_DIM_") ||
1165 1 : STARTS_WITH(pszName, "missing_value") ||
1166 3 : STARTS_WITH(pszName, "_FillValue") ||
1167 1 : CSLFindString(papszIgnoreBand, pszName) != -1)
1168 : {
1169 : // do nothing
1170 : }
1171 : else
1172 : {
1173 1 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1174 :
1175 1 : if (!NCDFPutAttr(cdfid, nZId, pszName, pszValue))
1176 1 : return CE_Failure;
1177 : }
1178 : }
1179 :
1180 5 : return GDALPamRasterBand::SetMetadataItem(pszName, pszValue, pszDomain);
1181 : }
1182 :
1183 : /************************************************************************/
1184 : /* SetMetadata() */
1185 : /************************************************************************/
1186 :
1187 1 : CPLErr netCDFRasterBand::SetMetadata(char **papszMD, const char *pszDomain)
1188 : {
1189 2 : if (GetAccess() == GA_Update &&
1190 1 : (pszDomain == nullptr || pszDomain[0] == '\0'))
1191 : {
1192 : // We don't handle metadata item removal for now
1193 2 : for (const char *const *papszIter = papszMD; papszIter && *papszIter;
1194 : ++papszIter)
1195 : {
1196 1 : char *pszName = nullptr;
1197 1 : const char *pszValue = CPLParseNameValue(*papszIter, &pszName);
1198 1 : if (pszName && pszValue)
1199 1 : SetMetadataItem(pszName, pszValue);
1200 1 : CPLFree(pszName);
1201 : }
1202 : }
1203 1 : return GDALPamRasterBand::SetMetadata(papszMD, pszDomain);
1204 : }
1205 :
1206 : /************************************************************************/
1207 : /* GetOffset() */
1208 : /************************************************************************/
1209 49 : double netCDFRasterBand::GetOffset(int *pbSuccess)
1210 : {
1211 49 : if (pbSuccess != nullptr)
1212 44 : *pbSuccess = static_cast<int>(m_bHaveOffset);
1213 :
1214 49 : return m_dfOffset;
1215 : }
1216 :
1217 : /************************************************************************/
1218 : /* SetOffset() */
1219 : /************************************************************************/
1220 1 : CPLErr netCDFRasterBand::SetOffset(double dfNewOffset)
1221 : {
1222 2 : CPLMutexHolderD(&hNCMutex);
1223 :
1224 : // Write value if in update mode.
1225 1 : if (poDS->GetAccess() == GA_Update)
1226 : {
1227 : // Make sure we are in define mode.
1228 1 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1229 :
1230 1 : const int status = nc_put_att_double(cdfid, nZId, CF_ADD_OFFSET,
1231 : NC_DOUBLE, 1, &dfNewOffset);
1232 :
1233 1 : NCDF_ERR(status);
1234 1 : if (status == NC_NOERR)
1235 : {
1236 1 : SetOffsetNoUpdate(dfNewOffset);
1237 1 : return CE_None;
1238 : }
1239 :
1240 0 : return CE_Failure;
1241 : }
1242 :
1243 0 : SetOffsetNoUpdate(dfNewOffset);
1244 0 : return CE_None;
1245 : }
1246 :
1247 : /************************************************************************/
1248 : /* SetOffsetNoUpdate() */
1249 : /************************************************************************/
1250 17 : void netCDFRasterBand::SetOffsetNoUpdate(double dfVal)
1251 : {
1252 17 : m_dfOffset = dfVal;
1253 17 : m_bHaveOffset = true;
1254 17 : }
1255 :
1256 : /************************************************************************/
1257 : /* GetScale() */
1258 : /************************************************************************/
1259 49 : double netCDFRasterBand::GetScale(int *pbSuccess)
1260 : {
1261 49 : if (pbSuccess != nullptr)
1262 44 : *pbSuccess = static_cast<int>(m_bHaveScale);
1263 :
1264 49 : return m_dfScale;
1265 : }
1266 :
1267 : /************************************************************************/
1268 : /* SetScale() */
1269 : /************************************************************************/
1270 1 : CPLErr netCDFRasterBand::SetScale(double dfNewScale)
1271 : {
1272 2 : CPLMutexHolderD(&hNCMutex);
1273 :
1274 : // Write value if in update mode.
1275 1 : if (poDS->GetAccess() == GA_Update)
1276 : {
1277 : // Make sure we are in define mode.
1278 1 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1279 :
1280 1 : const int status = nc_put_att_double(cdfid, nZId, CF_SCALE_FACTOR,
1281 : NC_DOUBLE, 1, &dfNewScale);
1282 :
1283 1 : NCDF_ERR(status);
1284 1 : if (status == NC_NOERR)
1285 : {
1286 1 : SetScaleNoUpdate(dfNewScale);
1287 1 : return CE_None;
1288 : }
1289 :
1290 0 : return CE_Failure;
1291 : }
1292 :
1293 0 : SetScaleNoUpdate(dfNewScale);
1294 0 : return CE_None;
1295 : }
1296 :
1297 : /************************************************************************/
1298 : /* SetScaleNoUpdate() */
1299 : /************************************************************************/
1300 21 : void netCDFRasterBand::SetScaleNoUpdate(double dfVal)
1301 : {
1302 21 : m_dfScale = dfVal;
1303 21 : m_bHaveScale = true;
1304 21 : }
1305 :
1306 : /************************************************************************/
1307 : /* GetUnitType() */
1308 : /************************************************************************/
1309 :
1310 21 : const char *netCDFRasterBand::GetUnitType()
1311 :
1312 : {
1313 21 : if (!m_osUnitType.empty())
1314 6 : return m_osUnitType;
1315 :
1316 15 : return GDALRasterBand::GetUnitType();
1317 : }
1318 :
1319 : /************************************************************************/
1320 : /* SetUnitType() */
1321 : /************************************************************************/
1322 :
1323 1 : CPLErr netCDFRasterBand::SetUnitType(const char *pszNewValue)
1324 :
1325 : {
1326 2 : CPLMutexHolderD(&hNCMutex);
1327 :
1328 2 : const std::string osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
1329 :
1330 1 : if (!osUnitType.empty())
1331 : {
1332 : // Write value if in update mode.
1333 1 : if (poDS->GetAccess() == GA_Update)
1334 : {
1335 : // Make sure we are in define mode.
1336 1 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(TRUE);
1337 :
1338 1 : const int status = nc_put_att_text(
1339 : cdfid, nZId, CF_UNITS, osUnitType.size(), osUnitType.c_str());
1340 :
1341 1 : NCDF_ERR(status);
1342 1 : if (status == NC_NOERR)
1343 : {
1344 1 : SetUnitTypeNoUpdate(pszNewValue);
1345 1 : return CE_None;
1346 : }
1347 :
1348 0 : return CE_Failure;
1349 : }
1350 : }
1351 :
1352 0 : SetUnitTypeNoUpdate(pszNewValue);
1353 :
1354 0 : return CE_None;
1355 : }
1356 :
1357 : /************************************************************************/
1358 : /* SetUnitTypeNoUpdate() */
1359 : /************************************************************************/
1360 :
1361 467 : void netCDFRasterBand::SetUnitTypeNoUpdate(const char *pszNewValue)
1362 : {
1363 467 : m_osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
1364 467 : }
1365 :
1366 : /************************************************************************/
1367 : /* GetNoDataValue() */
1368 : /************************************************************************/
1369 :
1370 153 : double netCDFRasterBand::GetNoDataValue(int *pbSuccess)
1371 :
1372 : {
1373 153 : if (m_bNoDataSetAsInt64)
1374 : {
1375 0 : if (pbSuccess)
1376 0 : *pbSuccess = TRUE;
1377 0 : return GDALGetNoDataValueCastToDouble(m_nNodataValueInt64);
1378 : }
1379 :
1380 153 : if (m_bNoDataSetAsUInt64)
1381 : {
1382 0 : if (pbSuccess)
1383 0 : *pbSuccess = TRUE;
1384 0 : return GDALGetNoDataValueCastToDouble(m_nNodataValueUInt64);
1385 : }
1386 :
1387 153 : if (m_bNoDataSet)
1388 : {
1389 118 : if (pbSuccess)
1390 102 : *pbSuccess = TRUE;
1391 118 : return m_dfNoDataValue;
1392 : }
1393 :
1394 35 : return GDALPamRasterBand::GetNoDataValue(pbSuccess);
1395 : }
1396 :
1397 : /************************************************************************/
1398 : /* GetNoDataValueAsInt64() */
1399 : /************************************************************************/
1400 :
1401 4 : int64_t netCDFRasterBand::GetNoDataValueAsInt64(int *pbSuccess)
1402 :
1403 : {
1404 4 : if (m_bNoDataSetAsInt64)
1405 : {
1406 4 : if (pbSuccess)
1407 4 : *pbSuccess = TRUE;
1408 :
1409 4 : return m_nNodataValueInt64;
1410 : }
1411 :
1412 0 : return GDALPamRasterBand::GetNoDataValueAsInt64(pbSuccess);
1413 : }
1414 :
1415 : /************************************************************************/
1416 : /* GetNoDataValueAsUInt64() */
1417 : /************************************************************************/
1418 :
1419 4 : uint64_t netCDFRasterBand::GetNoDataValueAsUInt64(int *pbSuccess)
1420 :
1421 : {
1422 4 : if (m_bNoDataSetAsUInt64)
1423 : {
1424 4 : if (pbSuccess)
1425 4 : *pbSuccess = TRUE;
1426 :
1427 4 : return m_nNodataValueUInt64;
1428 : }
1429 :
1430 0 : return GDALPamRasterBand::GetNoDataValueAsUInt64(pbSuccess);
1431 : }
1432 :
1433 : /************************************************************************/
1434 : /* SetNoDataValue() */
1435 : /************************************************************************/
1436 :
1437 134 : CPLErr netCDFRasterBand::SetNoDataValue(double dfNoData)
1438 :
1439 : {
1440 268 : CPLMutexHolderD(&hNCMutex);
1441 :
1442 : // If already set to new value, don't do anything.
1443 134 : if (m_bNoDataSet && CPLIsEqual(dfNoData, m_dfNoDataValue))
1444 19 : return CE_None;
1445 :
1446 : // Write value if in update mode.
1447 115 : if (poDS->GetAccess() == GA_Update)
1448 : {
1449 : // netcdf-4 does not allow to set _FillValue after leaving define mode,
1450 : // but it is ok if variable has not been written to, so only print
1451 : // debug. See bug #4484.
1452 125 : if (m_bNoDataSet &&
1453 10 : !reinterpret_cast<netCDFDataset *>(poDS)->GetDefineMode())
1454 : {
1455 0 : CPLDebug("GDAL_netCDF",
1456 : "Setting NoDataValue to %.17g (previously set to %.17g) "
1457 : "but file is no longer in define mode (id #%d, band #%d)",
1458 : dfNoData, m_dfNoDataValue, cdfid, nBand);
1459 : }
1460 : #ifdef NCDF_DEBUG
1461 : else
1462 : {
1463 : CPLDebug("GDAL_netCDF",
1464 : "Setting NoDataValue to %.17g (id #%d, band #%d)",
1465 : dfNoData, cdfid, nBand);
1466 : }
1467 : #endif
1468 : // Make sure we are in define mode.
1469 115 : reinterpret_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1470 :
1471 : int status;
1472 115 : if (eDataType == GDT_Byte)
1473 : {
1474 6 : if (bSignedData)
1475 : {
1476 0 : signed char cNoDataValue = static_cast<signed char>(dfNoData);
1477 0 : status = nc_put_att_schar(cdfid, nZId, NCDF_FillValue,
1478 : nc_datatype, 1, &cNoDataValue);
1479 : }
1480 : else
1481 : {
1482 6 : const unsigned char ucNoDataValue =
1483 6 : static_cast<unsigned char>(dfNoData);
1484 6 : status = nc_put_att_uchar(cdfid, nZId, NCDF_FillValue,
1485 : nc_datatype, 1, &ucNoDataValue);
1486 : }
1487 : }
1488 109 : else if (eDataType == GDT_Int16)
1489 : {
1490 14 : short nsNoDataValue = static_cast<short>(dfNoData);
1491 14 : status = nc_put_att_short(cdfid, nZId, NCDF_FillValue, nc_datatype,
1492 : 1, &nsNoDataValue);
1493 : }
1494 95 : else if (eDataType == GDT_Int32)
1495 : {
1496 27 : int nNoDataValue = static_cast<int>(dfNoData);
1497 27 : status = nc_put_att_int(cdfid, nZId, NCDF_FillValue, nc_datatype, 1,
1498 : &nNoDataValue);
1499 : }
1500 68 : else if (eDataType == GDT_Float32)
1501 : {
1502 31 : float fNoDataValue = static_cast<float>(dfNoData);
1503 31 : status = nc_put_att_float(cdfid, nZId, NCDF_FillValue, nc_datatype,
1504 : 1, &fNoDataValue);
1505 : }
1506 37 : else if (eDataType == GDT_UInt16 &&
1507 6 : reinterpret_cast<netCDFDataset *>(poDS)->eFormat ==
1508 : NCDF_FORMAT_NC4)
1509 : {
1510 6 : unsigned short usNoDataValue =
1511 6 : static_cast<unsigned short>(dfNoData);
1512 6 : status = nc_put_att_ushort(cdfid, nZId, NCDF_FillValue, nc_datatype,
1513 6 : 1, &usNoDataValue);
1514 : }
1515 31 : else if (eDataType == GDT_UInt32 &&
1516 7 : reinterpret_cast<netCDFDataset *>(poDS)->eFormat ==
1517 : NCDF_FORMAT_NC4)
1518 : {
1519 7 : unsigned int unNoDataValue = static_cast<unsigned int>(dfNoData);
1520 7 : status = nc_put_att_uint(cdfid, nZId, NCDF_FillValue, nc_datatype,
1521 7 : 1, &unNoDataValue);
1522 : }
1523 : else
1524 : {
1525 24 : status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
1526 : 1, &dfNoData);
1527 : }
1528 :
1529 115 : NCDF_ERR(status);
1530 :
1531 : // Update status if write worked.
1532 115 : if (status == NC_NOERR)
1533 : {
1534 115 : SetNoDataValueNoUpdate(dfNoData);
1535 115 : return CE_None;
1536 : }
1537 :
1538 0 : return CE_Failure;
1539 : }
1540 :
1541 0 : SetNoDataValueNoUpdate(dfNoData);
1542 0 : return CE_None;
1543 : }
1544 :
1545 : /************************************************************************/
1546 : /* SetNoDataValueNoUpdate() */
1547 : /************************************************************************/
1548 :
1549 430 : void netCDFRasterBand::SetNoDataValueNoUpdate(double dfNoData)
1550 : {
1551 430 : m_dfNoDataValue = dfNoData;
1552 430 : m_bNoDataSet = true;
1553 430 : m_bNoDataSetAsInt64 = false;
1554 430 : m_bNoDataSetAsUInt64 = false;
1555 430 : }
1556 :
1557 : /************************************************************************/
1558 : /* SetNoDataValueAsInt64() */
1559 : /************************************************************************/
1560 :
1561 3 : CPLErr netCDFRasterBand::SetNoDataValueAsInt64(int64_t nNoData)
1562 :
1563 : {
1564 6 : CPLMutexHolderD(&hNCMutex);
1565 :
1566 : // If already set to new value, don't do anything.
1567 3 : if (m_bNoDataSetAsInt64 && nNoData == m_nNodataValueInt64)
1568 0 : return CE_None;
1569 :
1570 : // Write value if in update mode.
1571 3 : if (poDS->GetAccess() == GA_Update)
1572 : {
1573 : // netcdf-4 does not allow to set NCDF_FillValue after leaving define mode,
1574 : // but it is ok if variable has not been written to, so only print
1575 : // debug. See bug #4484.
1576 3 : if (m_bNoDataSetAsInt64 &&
1577 0 : !reinterpret_cast<netCDFDataset *>(poDS)->GetDefineMode())
1578 : {
1579 0 : CPLDebug("GDAL_netCDF",
1580 : "Setting NoDataValue to " CPL_FRMT_GIB
1581 : " (previously set to " CPL_FRMT_GIB ") "
1582 : "but file is no longer in define mode (id #%d, band #%d)",
1583 : static_cast<GIntBig>(nNoData),
1584 0 : static_cast<GIntBig>(m_nNodataValueInt64), cdfid, nBand);
1585 : }
1586 : #ifdef NCDF_DEBUG
1587 : else
1588 : {
1589 : CPLDebug("GDAL_netCDF",
1590 : "Setting NoDataValue to " CPL_FRMT_GIB
1591 : " (id #%d, band #%d)",
1592 : static_cast<GIntBig>(nNoData), cdfid, nBand);
1593 : }
1594 : #endif
1595 : // Make sure we are in define mode.
1596 3 : reinterpret_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1597 :
1598 : int status;
1599 3 : if (eDataType == GDT_Int64 &&
1600 3 : reinterpret_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
1601 : {
1602 3 : long long tmp = static_cast<long long>(nNoData);
1603 3 : status = nc_put_att_longlong(cdfid, nZId, NCDF_FillValue,
1604 3 : nc_datatype, 1, &tmp);
1605 : }
1606 : else
1607 : {
1608 0 : double dfNoData = static_cast<double>(nNoData);
1609 0 : status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
1610 : 1, &dfNoData);
1611 : }
1612 :
1613 3 : NCDF_ERR(status);
1614 :
1615 : // Update status if write worked.
1616 3 : if (status == NC_NOERR)
1617 : {
1618 3 : SetNoDataValueNoUpdate(nNoData);
1619 3 : return CE_None;
1620 : }
1621 :
1622 0 : return CE_Failure;
1623 : }
1624 :
1625 0 : SetNoDataValueNoUpdate(nNoData);
1626 0 : return CE_None;
1627 : }
1628 :
1629 : /************************************************************************/
1630 : /* SetNoDataValueNoUpdate() */
1631 : /************************************************************************/
1632 :
1633 11 : void netCDFRasterBand::SetNoDataValueNoUpdate(int64_t nNoData)
1634 : {
1635 11 : m_nNodataValueInt64 = nNoData;
1636 11 : m_bNoDataSet = false;
1637 11 : m_bNoDataSetAsInt64 = true;
1638 11 : m_bNoDataSetAsUInt64 = false;
1639 11 : }
1640 :
1641 : /************************************************************************/
1642 : /* SetNoDataValueAsUInt64() */
1643 : /************************************************************************/
1644 :
1645 3 : CPLErr netCDFRasterBand::SetNoDataValueAsUInt64(uint64_t nNoData)
1646 :
1647 : {
1648 6 : CPLMutexHolderD(&hNCMutex);
1649 :
1650 : // If already set to new value, don't do anything.
1651 3 : if (m_bNoDataSetAsUInt64 && nNoData == m_nNodataValueUInt64)
1652 0 : return CE_None;
1653 :
1654 : // Write value if in update mode.
1655 3 : if (poDS->GetAccess() == GA_Update)
1656 : {
1657 : // netcdf-4 does not allow to set _FillValue after leaving define mode,
1658 : // but it is ok if variable has not been written to, so only print
1659 : // debug. See bug #4484.
1660 3 : if (m_bNoDataSetAsUInt64 &&
1661 0 : !reinterpret_cast<netCDFDataset *>(poDS)->GetDefineMode())
1662 : {
1663 0 : CPLDebug("GDAL_netCDF",
1664 : "Setting NoDataValue to " CPL_FRMT_GUIB
1665 : " (previously set to " CPL_FRMT_GUIB ") "
1666 : "but file is no longer in define mode (id #%d, band #%d)",
1667 : static_cast<GUIntBig>(nNoData),
1668 0 : static_cast<GUIntBig>(m_nNodataValueUInt64), cdfid, nBand);
1669 : }
1670 : #ifdef NCDF_DEBUG
1671 : else
1672 : {
1673 : CPLDebug("GDAL_netCDF",
1674 : "Setting NoDataValue to " CPL_FRMT_GUIB
1675 : " (id #%d, band #%d)",
1676 : static_cast<GUIntBig>(nNoData), cdfid, nBand);
1677 : }
1678 : #endif
1679 : // Make sure we are in define mode.
1680 3 : reinterpret_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1681 :
1682 : int status;
1683 3 : if (eDataType == GDT_UInt64 &&
1684 3 : reinterpret_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
1685 : {
1686 3 : unsigned long long tmp = static_cast<long long>(nNoData);
1687 3 : status = nc_put_att_ulonglong(cdfid, nZId, NCDF_FillValue,
1688 3 : nc_datatype, 1, &tmp);
1689 : }
1690 : else
1691 : {
1692 0 : double dfNoData = static_cast<double>(nNoData);
1693 0 : status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
1694 : 1, &dfNoData);
1695 : }
1696 :
1697 3 : NCDF_ERR(status);
1698 :
1699 : // Update status if write worked.
1700 3 : if (status == NC_NOERR)
1701 : {
1702 3 : SetNoDataValueNoUpdate(nNoData);
1703 3 : return CE_None;
1704 : }
1705 :
1706 0 : return CE_Failure;
1707 : }
1708 :
1709 0 : SetNoDataValueNoUpdate(nNoData);
1710 0 : return CE_None;
1711 : }
1712 :
1713 : /************************************************************************/
1714 : /* SetNoDataValueNoUpdate() */
1715 : /************************************************************************/
1716 :
1717 10 : void netCDFRasterBand::SetNoDataValueNoUpdate(uint64_t nNoData)
1718 : {
1719 10 : m_nNodataValueUInt64 = nNoData;
1720 10 : m_bNoDataSet = false;
1721 10 : m_bNoDataSetAsInt64 = false;
1722 10 : m_bNoDataSetAsUInt64 = true;
1723 10 : }
1724 :
1725 : /************************************************************************/
1726 : /* DeleteNoDataValue() */
1727 : /************************************************************************/
1728 :
1729 : #ifdef notdef
1730 : CPLErr netCDFRasterBand::DeleteNoDataValue()
1731 :
1732 : {
1733 : CPLMutexHolderD(&hNCMutex);
1734 :
1735 : if (!bNoDataSet)
1736 : return CE_None;
1737 :
1738 : // Write value if in update mode.
1739 : if (poDS->GetAccess() == GA_Update)
1740 : {
1741 : // Make sure we are in define mode.
1742 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1743 :
1744 : status = nc_del_att(cdfid, nZId, NCDF_FillValue);
1745 :
1746 : NCDF_ERR(status);
1747 :
1748 : // Update status if write worked.
1749 : if (status == NC_NOERR)
1750 : {
1751 : dfNoDataValue = 0.0;
1752 : bNoDataSet = false;
1753 : return CE_None;
1754 : }
1755 :
1756 : return CE_Failure;
1757 : }
1758 :
1759 : dfNoDataValue = 0.0;
1760 : bNoDataSet = false;
1761 : return CE_None;
1762 : }
1763 : #endif
1764 :
1765 : /************************************************************************/
1766 : /* SerializeToXML() */
1767 : /************************************************************************/
1768 :
1769 4 : CPLXMLNode *netCDFRasterBand::SerializeToXML(const char * /* pszUnused */)
1770 : {
1771 : // Overridden from GDALPamDataset to add only band histogram
1772 : // and statistics. See bug #4244.
1773 4 : if (psPam == nullptr)
1774 0 : return nullptr;
1775 :
1776 : // Setup root node and attributes.
1777 : CPLXMLNode *psTree =
1778 4 : CPLCreateXMLNode(nullptr, CXT_Element, "PAMRasterBand");
1779 :
1780 4 : if (GetBand() > 0)
1781 : {
1782 8 : CPLString oFmt;
1783 4 : CPLSetXMLValue(psTree, "#band", oFmt.Printf("%d", GetBand()));
1784 : }
1785 :
1786 : // Histograms.
1787 4 : if (psPam->psSavedHistograms != nullptr)
1788 1 : CPLAddXMLChild(psTree, CPLCloneXMLTree(psPam->psSavedHistograms));
1789 :
1790 : // Metadata (statistics only).
1791 4 : GDALMultiDomainMetadata oMDMDStats;
1792 4 : const char *papszMDStats[] = {"STATISTICS_MINIMUM", "STATISTICS_MAXIMUM",
1793 : "STATISTICS_MEAN", "STATISTICS_STDDEV",
1794 : nullptr};
1795 20 : for (int i = 0; i < CSLCount(papszMDStats); i++)
1796 : {
1797 16 : const char *pszMDI = GetMetadataItem(papszMDStats[i]);
1798 16 : if (pszMDI)
1799 4 : oMDMDStats.SetMetadataItem(papszMDStats[i], pszMDI);
1800 : }
1801 4 : CPLXMLNode *psMD = oMDMDStats.Serialize();
1802 :
1803 4 : if (psMD != nullptr)
1804 : {
1805 1 : if (psMD->psChild == nullptr)
1806 0 : CPLDestroyXMLNode(psMD);
1807 : else
1808 1 : CPLAddXMLChild(psTree, psMD);
1809 : }
1810 :
1811 : // We don't want to return anything if we had no metadata to attach.
1812 4 : if (psTree->psChild == nullptr || psTree->psChild->psNext == nullptr)
1813 : {
1814 2 : CPLDestroyXMLNode(psTree);
1815 2 : psTree = nullptr;
1816 : }
1817 :
1818 4 : return psTree;
1819 : }
1820 :
1821 : /************************************************************************/
1822 : /* Get1DVariableIndexedByDimension() */
1823 : /************************************************************************/
1824 :
1825 79 : static int Get1DVariableIndexedByDimension(int cdfid, int nDimId,
1826 : const char *pszDimName,
1827 : bool bVerboseError, int *pnGroupID)
1828 : {
1829 79 : *pnGroupID = -1;
1830 79 : int nVarID = -1;
1831 : // First try to find a variable whose name is identical to the dimension
1832 : // name, and check that it is indeed indexed by this dimension
1833 79 : if (NCDFResolveVar(cdfid, pszDimName, pnGroupID, &nVarID) == CE_None)
1834 : {
1835 65 : int nDimCountOfVariable = 0;
1836 65 : nc_inq_varndims(*pnGroupID, nVarID, &nDimCountOfVariable);
1837 65 : if (nDimCountOfVariable == 1)
1838 : {
1839 65 : int nDimIdOfVariable = -1;
1840 65 : nc_inq_vardimid(*pnGroupID, nVarID, &nDimIdOfVariable);
1841 65 : if (nDimIdOfVariable == nDimId)
1842 : {
1843 65 : return nVarID;
1844 : }
1845 : }
1846 : }
1847 :
1848 : // Otherwise iterate over the variables to find potential candidates
1849 : // TODO: should be modified to search also in other groups using the same
1850 : // logic than in NCDFResolveVar(), but maybe not needed if it's a
1851 : // very rare case? and I think this is not CF compliant.
1852 14 : int nvars = 0;
1853 14 : CPL_IGNORE_RET_VAL(nc_inq(cdfid, nullptr, &nvars, nullptr, nullptr));
1854 :
1855 14 : int nCountCandidateVars = 0;
1856 14 : int nCandidateVarID = -1;
1857 65 : for (int k = 0; k < nvars; k++)
1858 : {
1859 51 : int nDimCountOfVariable = 0;
1860 51 : nc_inq_varndims(cdfid, k, &nDimCountOfVariable);
1861 51 : if (nDimCountOfVariable == 1)
1862 : {
1863 27 : int nDimIdOfVariable = -1;
1864 27 : nc_inq_vardimid(cdfid, k, &nDimIdOfVariable);
1865 27 : if (nDimIdOfVariable == nDimId)
1866 : {
1867 7 : nCountCandidateVars++;
1868 7 : nCandidateVarID = k;
1869 : }
1870 : }
1871 : }
1872 14 : if (nCountCandidateVars > 1)
1873 : {
1874 1 : if (bVerboseError)
1875 : {
1876 1 : CPLError(CE_Warning, CPLE_AppDefined,
1877 : "Several 1D variables are indexed by dimension %s",
1878 : pszDimName);
1879 : }
1880 1 : *pnGroupID = -1;
1881 1 : return -1;
1882 : }
1883 13 : else if (nCandidateVarID < 0)
1884 : {
1885 8 : if (bVerboseError)
1886 : {
1887 8 : CPLError(CE_Warning, CPLE_AppDefined,
1888 : "No 1D variable is indexed by dimension %s", pszDimName);
1889 : }
1890 : }
1891 13 : *pnGroupID = cdfid;
1892 13 : return nCandidateVarID;
1893 : }
1894 :
1895 : /************************************************************************/
1896 : /* CreateMetadataFromAttributes() */
1897 : /************************************************************************/
1898 :
1899 466 : void netCDFRasterBand::CreateMetadataFromAttributes()
1900 : {
1901 466 : char szVarName[NC_MAX_NAME + 1] = {};
1902 466 : int status = nc_inq_varname(cdfid, nZId, szVarName);
1903 466 : NCDF_ERR(status);
1904 :
1905 466 : GDALPamRasterBand::SetMetadataItem("NETCDF_VARNAME", szVarName);
1906 :
1907 : // Get attribute metadata.
1908 466 : int nAtt = 0;
1909 466 : NCDF_ERR(nc_inq_varnatts(cdfid, nZId, &nAtt));
1910 :
1911 1962 : for (int i = 0; i < nAtt; i++)
1912 : {
1913 1496 : char szMetaName[NC_MAX_NAME + 1] = {};
1914 1496 : status = nc_inq_attname(cdfid, nZId, i, szMetaName);
1915 1496 : if (status != NC_NOERR)
1916 12 : continue;
1917 :
1918 1496 : if (GDALPamRasterBand::GetMetadataItem(szMetaName) != nullptr)
1919 : {
1920 12 : continue;
1921 : }
1922 :
1923 1484 : char *pszMetaValue = nullptr;
1924 1484 : if (NCDFGetAttr(cdfid, nZId, szMetaName, &pszMetaValue) == CE_None)
1925 : {
1926 1484 : GDALPamRasterBand::SetMetadataItem(szMetaName, pszMetaValue);
1927 : }
1928 : else
1929 : {
1930 0 : CPLDebug("GDAL_netCDF", "invalid Band metadata %s", szMetaName);
1931 : }
1932 :
1933 1484 : if (pszMetaValue)
1934 : {
1935 1484 : CPLFree(pszMetaValue);
1936 1484 : pszMetaValue = nullptr;
1937 : }
1938 : }
1939 466 : }
1940 :
1941 : /************************************************************************/
1942 : /* CreateMetadataFromOtherVars() */
1943 : /************************************************************************/
1944 :
1945 48 : void netCDFRasterBand::CreateMetadataFromOtherVars()
1946 :
1947 : {
1948 48 : CPLAssert(!m_bCreateMetadataFromOtherVarsDone);
1949 48 : m_bCreateMetadataFromOtherVarsDone = true;
1950 :
1951 48 : netCDFDataset *l_poDS = reinterpret_cast<netCDFDataset *>(poDS);
1952 48 : const int nPamFlagsBackup = l_poDS->nPamFlags;
1953 :
1954 : // Compute all dimensions from Band number and save in Metadata.
1955 48 : int nd = 0;
1956 48 : nc_inq_varndims(cdfid, nZId, &nd);
1957 : // Compute multidimention band position.
1958 : //
1959 : // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
1960 : // if Data[2,3,4,x,y]
1961 : //
1962 : // BandPos0 = (nBand) / (3*4)
1963 : // BandPos1 = (nBand - BandPos0*(3*4)) / (4)
1964 : // BandPos2 = (nBand - BandPos0*(3*4)) % (4)
1965 :
1966 48 : int Sum = 1;
1967 48 : if (nd == 3)
1968 : {
1969 5 : Sum *= panBandZLev[0];
1970 : }
1971 :
1972 : // Loop over non-spatial dimensions.
1973 48 : int Taken = 0;
1974 :
1975 88 : for (int i = 0; i < nd - 2; i++)
1976 : {
1977 : int result;
1978 40 : if (i != nd - 2 - 1)
1979 : {
1980 18 : Sum = 1;
1981 37 : for (int j = i + 1; j < nd - 2; j++)
1982 : {
1983 19 : Sum *= panBandZLev[j];
1984 : }
1985 18 : result = static_cast<int>((nLevel - Taken) / Sum);
1986 : }
1987 : else
1988 : {
1989 22 : result = static_cast<int>((nLevel - Taken) % Sum);
1990 : }
1991 :
1992 40 : char szName[NC_MAX_NAME + 1] = {};
1993 40 : snprintf(szName, sizeof(szName), "%s",
1994 40 : l_poDS->papszDimName[l_poDS->m_anDimIds[panBandZPos[i]]]);
1995 :
1996 : char szMetaName[NC_MAX_NAME + 1 + 32];
1997 40 : snprintf(szMetaName, sizeof(szMetaName), "NETCDF_DIM_%s", szName);
1998 :
1999 40 : const int nGroupID = l_poDS->m_anExtraDimGroupIds[i];
2000 40 : const int nVarID = l_poDS->m_anExtraDimVarIds[i];
2001 40 : if (nVarID < 0)
2002 : {
2003 2 : GDALPamRasterBand::SetMetadataItem(szMetaName,
2004 : CPLSPrintf("%d", result + 1));
2005 : }
2006 : else
2007 : {
2008 : // TODO: Make sure all the status checks make sense.
2009 :
2010 38 : nc_type nVarType = NC_NAT;
2011 38 : /* status = */ nc_inq_vartype(nGroupID, nVarID, &nVarType);
2012 :
2013 38 : int nDims = 0;
2014 38 : /* status = */ nc_inq_varndims(nGroupID, nVarID, &nDims);
2015 :
2016 38 : char szMetaTemp[256] = {};
2017 38 : if (nDims == 1)
2018 : {
2019 38 : size_t count[1] = {1};
2020 38 : size_t start[1] = {static_cast<size_t>(result)};
2021 :
2022 38 : switch (nVarType)
2023 : {
2024 0 : case NC_BYTE:
2025 : // TODO: Check for signed/unsigned byte.
2026 : signed char cData;
2027 0 : /* status = */ nc_get_vara_schar(nGroupID, nVarID,
2028 : start, count, &cData);
2029 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", cData);
2030 0 : break;
2031 0 : case NC_SHORT:
2032 : short sData;
2033 0 : /* status = */ nc_get_vara_short(nGroupID, nVarID,
2034 : start, count, &sData);
2035 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", sData);
2036 0 : break;
2037 19 : case NC_INT:
2038 : {
2039 : int nData;
2040 19 : /* status = */ nc_get_vara_int(nGroupID, nVarID, start,
2041 : count, &nData);
2042 19 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", nData);
2043 19 : break;
2044 : }
2045 0 : case NC_FLOAT:
2046 : float fData;
2047 0 : /* status = */ nc_get_vara_float(nGroupID, nVarID,
2048 : start, count, &fData);
2049 0 : CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.8g",
2050 : fData);
2051 0 : break;
2052 18 : case NC_DOUBLE:
2053 : double dfData;
2054 18 : /* status = */ nc_get_vara_double(
2055 : nGroupID, nVarID, start, count, &dfData);
2056 18 : CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.16g",
2057 : dfData);
2058 18 : break;
2059 0 : case NC_UBYTE:
2060 : unsigned char ucData;
2061 0 : /* status = */ nc_get_vara_uchar(nGroupID, nVarID,
2062 : start, count, &ucData);
2063 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", ucData);
2064 0 : break;
2065 0 : case NC_USHORT:
2066 : unsigned short usData;
2067 0 : /* status = */ nc_get_vara_ushort(
2068 : nGroupID, nVarID, start, count, &usData);
2069 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", usData);
2070 0 : break;
2071 0 : case NC_UINT:
2072 : {
2073 : unsigned int unData;
2074 0 : /* status = */ nc_get_vara_uint(nGroupID, nVarID, start,
2075 : count, &unData);
2076 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", unData);
2077 0 : break;
2078 : }
2079 1 : case NC_INT64:
2080 : {
2081 : long long nData;
2082 1 : /* status = */ nc_get_vara_longlong(
2083 : nGroupID, nVarID, start, count, &nData);
2084 1 : snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GIB,
2085 : nData);
2086 1 : break;
2087 : }
2088 0 : case NC_UINT64:
2089 : {
2090 : unsigned long long unData;
2091 0 : /* status = */ nc_get_vara_ulonglong(
2092 : nGroupID, nVarID, start, count, &unData);
2093 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GUIB,
2094 : unData);
2095 0 : break;
2096 : }
2097 0 : default:
2098 0 : CPLDebug("GDAL_netCDF", "invalid dim %s, type=%d",
2099 : szMetaTemp, nVarType);
2100 0 : break;
2101 : }
2102 : }
2103 : else
2104 : {
2105 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", result + 1);
2106 : }
2107 :
2108 : // Save dimension value.
2109 : // NOTE: removed #original_units as not part of CF-1.
2110 :
2111 38 : GDALPamRasterBand::SetMetadataItem(szMetaName, szMetaTemp);
2112 : }
2113 :
2114 : // Avoid int32 overflow. Perhaps something more sensible to do here ?
2115 40 : if (result > 0 && Sum > INT_MAX / result)
2116 0 : break;
2117 40 : if (Taken > INT_MAX - result * Sum)
2118 0 : break;
2119 :
2120 40 : Taken += result * Sum;
2121 : } // End loop non-spatial dimensions.
2122 :
2123 48 : l_poDS->nPamFlags = nPamFlagsBackup;
2124 48 : }
2125 :
2126 : /************************************************************************/
2127 : /* CheckData() */
2128 : /************************************************************************/
2129 : template <class T>
2130 5730 : void netCDFRasterBand::CheckData(void *pImage, void *pImageNC,
2131 : size_t nTmpBlockXSize, size_t nTmpBlockYSize,
2132 : bool bCheckIsNan)
2133 : {
2134 5730 : CPLAssert(pImage != nullptr && pImageNC != nullptr);
2135 :
2136 : // If this block is not a full block (in the x axis), we need to re-arrange
2137 : // the data this is because partial blocks are not arranged the same way in
2138 : // netcdf and gdal.
2139 5730 : if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
2140 : {
2141 6 : T *ptrWrite = static_cast<T *>(pImage);
2142 6 : T *ptrRead = static_cast<T *>(pImageNC);
2143 29 : for (size_t j = 0; j < nTmpBlockYSize;
2144 23 : j++, ptrWrite += nBlockXSize, ptrRead += nTmpBlockXSize)
2145 : {
2146 23 : memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T));
2147 : }
2148 : }
2149 :
2150 : // Is valid data checking needed or requested?
2151 5730 : if (bValidRangeValid || bCheckIsNan)
2152 : {
2153 1265 : T *ptrImage = static_cast<T *>(pImage);
2154 2584 : for (size_t j = 0; j < nTmpBlockYSize; j++)
2155 : {
2156 : // k moves along the gdal block, skipping the out-of-range pixels.
2157 1319 : size_t k = j * nBlockXSize;
2158 96938 : for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
2159 : {
2160 : // Check for nodata and nan.
2161 95619 : if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
2162 6301 : continue;
2163 89318 : if (bCheckIsNan && std::isnan((double)ptrImage[k]))
2164 : {
2165 5737 : ptrImage[k] = (T)m_dfNoDataValue;
2166 5737 : continue;
2167 : }
2168 : // Check for valid_range.
2169 83581 : if (bValidRangeValid)
2170 : {
2171 40986 : if (((adfValidRange[0] != m_dfNoDataValue) &&
2172 40986 : (ptrImage[k] < (T)adfValidRange[0])) ||
2173 40983 : ((adfValidRange[1] != m_dfNoDataValue) &&
2174 40983 : (ptrImage[k] > (T)adfValidRange[1])))
2175 : {
2176 4 : ptrImage[k] = (T)m_dfNoDataValue;
2177 : }
2178 : }
2179 : }
2180 : }
2181 : }
2182 :
2183 : // If minimum longitude is > 180, subtract 360 from all.
2184 : // If not, disable checking for further calls (check just once).
2185 : // Only check first and last block elements since lon must be monotonic.
2186 5730 : const bool bIsSigned = std::numeric_limits<T>::is_signed;
2187 5419 : if (bCheckLongitude && bIsSigned &&
2188 9 : !CPLIsEqual((double)((T *)pImage)[0], m_dfNoDataValue) &&
2189 8 : !CPLIsEqual((double)((T *)pImage)[nTmpBlockXSize - 1],
2190 2714 : m_dfNoDataValue) &&
2191 8 : std::min(((T *)pImage)[0], ((T *)pImage)[nTmpBlockXSize - 1]) > 180.0)
2192 : {
2193 0 : T *ptrImage = static_cast<T *>(pImage);
2194 0 : for (size_t j = 0; j < nTmpBlockYSize; j++)
2195 : {
2196 0 : size_t k = j * nBlockXSize;
2197 0 : for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
2198 : {
2199 0 : if (!CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
2200 0 : ptrImage[k] = static_cast<T>(ptrImage[k] - 360);
2201 : }
2202 : }
2203 : }
2204 : else
2205 : {
2206 5730 : bCheckLongitude = false;
2207 : }
2208 5730 : }
2209 :
2210 : /************************************************************************/
2211 : /* CheckDataCpx() */
2212 : /************************************************************************/
2213 : template <class T>
2214 25 : void netCDFRasterBand::CheckDataCpx(void *pImage, void *pImageNC,
2215 : size_t nTmpBlockXSize,
2216 : size_t nTmpBlockYSize, bool bCheckIsNan)
2217 : {
2218 25 : CPLAssert(pImage != nullptr && pImageNC != nullptr);
2219 :
2220 : // If this block is not a full block (in the x axis), we need to re-arrange
2221 : // the data this is because partial blocks are not arranged the same way in
2222 : // netcdf and gdal.
2223 25 : if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
2224 : {
2225 0 : T *ptrWrite = static_cast<T *>(pImage);
2226 0 : T *ptrRead = static_cast<T *>(pImageNC);
2227 0 : for (size_t j = 0; j < nTmpBlockYSize; j++,
2228 0 : ptrWrite += (2 * nBlockXSize),
2229 0 : ptrRead += (2 * nTmpBlockXSize))
2230 : {
2231 0 : memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T) * 2);
2232 : }
2233 : }
2234 :
2235 : // Is valid data checking needed or requested?
2236 25 : if (bValidRangeValid || bCheckIsNan)
2237 : {
2238 0 : T *ptrImage = static_cast<T *>(pImage);
2239 0 : for (size_t j = 0; j < nTmpBlockYSize; j++)
2240 : {
2241 : // k moves along the gdal block, skipping the out-of-range pixels.
2242 0 : size_t k = 2 * j * nBlockXSize;
2243 0 : for (size_t i = 0; i < (2 * nTmpBlockXSize); i++, k++)
2244 : {
2245 : // Check for nodata and nan.
2246 0 : if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
2247 0 : continue;
2248 0 : if (bCheckIsNan && std::isnan((double)ptrImage[k]))
2249 : {
2250 0 : ptrImage[k] = (T)m_dfNoDataValue;
2251 0 : continue;
2252 : }
2253 : // Check for valid_range.
2254 0 : if (bValidRangeValid)
2255 : {
2256 0 : if (((adfValidRange[0] != m_dfNoDataValue) &&
2257 0 : (ptrImage[k] < (T)adfValidRange[0])) ||
2258 0 : ((adfValidRange[1] != m_dfNoDataValue) &&
2259 0 : (ptrImage[k] > (T)adfValidRange[1])))
2260 : {
2261 0 : ptrImage[k] = (T)m_dfNoDataValue;
2262 : }
2263 : }
2264 : }
2265 : }
2266 : }
2267 25 : }
2268 :
2269 : /************************************************************************/
2270 : /* FetchNetcdfChunk() */
2271 : /************************************************************************/
2272 :
2273 5755 : bool netCDFRasterBand::FetchNetcdfChunk(size_t xstart, size_t ystart,
2274 : void *pImage)
2275 : {
2276 5755 : size_t start[MAX_NC_DIMS] = {};
2277 5755 : size_t edge[MAX_NC_DIMS] = {};
2278 :
2279 5755 : start[nBandXPos] = xstart;
2280 5755 : edge[nBandXPos] = nBlockXSize;
2281 5755 : if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
2282 6 : edge[nBandXPos] = nRasterXSize - start[nBandXPos];
2283 5755 : if (nBandYPos >= 0)
2284 : {
2285 5751 : start[nBandYPos] = ystart;
2286 5751 : edge[nBandYPos] = nBlockYSize;
2287 5751 : if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
2288 4 : edge[nBandYPos] = nRasterYSize - start[nBandYPos];
2289 : }
2290 5755 : const size_t nYChunkSize = nBandYPos < 0 ? 1 : edge[nBandYPos];
2291 :
2292 : #ifdef NCDF_DEBUG
2293 : CPLDebug("GDAL_netCDF", "start={%ld,%ld} edge={%ld,%ld} bBottomUp=%d",
2294 : start[nBandXPos], nBandYPos < 0 ? 0 : start[nBandYPos],
2295 : edge[nBandXPos], nYChunkSize, ((netCDFDataset *)poDS)->bBottomUp);
2296 : #endif
2297 :
2298 5755 : int nd = 0;
2299 5755 : nc_inq_varndims(cdfid, nZId, &nd);
2300 5755 : if (nd == 3)
2301 : {
2302 1078 : start[panBandZPos[0]] = nLevel; // z
2303 1078 : edge[panBandZPos[0]] = 1;
2304 : }
2305 :
2306 : // Compute multidimention band position.
2307 : //
2308 : // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
2309 : // if Data[2,3,4,x,y]
2310 : //
2311 : // BandPos0 = (nBand) / (3*4)
2312 : // BandPos1 = (nBand - (3*4)) / (4)
2313 : // BandPos2 = (nBand - (3*4)) % (4)
2314 5755 : if (nd > 3)
2315 : {
2316 160 : int Sum = -1;
2317 160 : int Taken = 0;
2318 480 : for (int i = 0; i < nd - 2; i++)
2319 : {
2320 320 : if (i != nd - 2 - 1)
2321 : {
2322 160 : Sum = 1;
2323 320 : for (int j = i + 1; j < nd - 2; j++)
2324 : {
2325 160 : Sum *= panBandZLev[j];
2326 : }
2327 160 : start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
2328 160 : edge[panBandZPos[i]] = 1;
2329 : }
2330 : else
2331 : {
2332 160 : start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
2333 160 : edge[panBandZPos[i]] = 1;
2334 : }
2335 320 : Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
2336 : }
2337 : }
2338 :
2339 : // Make sure we are in data mode.
2340 5755 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
2341 :
2342 : // If this block is not a full block in the x axis, we need to
2343 : // re-arrange the data because partial blocks are not arranged the
2344 : // same way in netcdf and gdal, so we first we read the netcdf data at
2345 : // the end of the gdal block buffer then re-arrange rows in CheckData().
2346 5755 : void *pImageNC = pImage;
2347 5755 : if (edge[nBandXPos] != static_cast<size_t>(nBlockXSize))
2348 : {
2349 6 : pImageNC = static_cast<GByte *>(pImage) +
2350 6 : ((static_cast<size_t>(nBlockXSize) * nBlockYSize -
2351 12 : edge[nBandXPos] * nYChunkSize) *
2352 6 : (GDALGetDataTypeSize(eDataType) / 8));
2353 : }
2354 :
2355 : // Read data according to type.
2356 : int status;
2357 5755 : if (eDataType == GDT_Byte)
2358 : {
2359 3003 : if (bSignedData)
2360 : {
2361 0 : status = nc_get_vara_schar(cdfid, nZId, start, edge,
2362 : static_cast<signed char *>(pImageNC));
2363 0 : if (status == NC_NOERR)
2364 0 : CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
2365 : nYChunkSize, false);
2366 : }
2367 : else
2368 : {
2369 3003 : status = nc_get_vara_uchar(cdfid, nZId, start, edge,
2370 : static_cast<unsigned char *>(pImageNC));
2371 3003 : if (status == NC_NOERR)
2372 3003 : CheckData<unsigned char>(pImage, pImageNC, edge[nBandXPos],
2373 : nYChunkSize, false);
2374 : }
2375 : }
2376 2752 : else if (eDataType == GDT_Int8)
2377 : {
2378 60 : status = nc_get_vara_schar(cdfid, nZId, start, edge,
2379 : static_cast<signed char *>(pImageNC));
2380 60 : if (status == NC_NOERR)
2381 60 : CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
2382 : nYChunkSize, false);
2383 : }
2384 2692 : else if (nc_datatype == NC_SHORT)
2385 : {
2386 465 : status = nc_get_vara_short(cdfid, nZId, start, edge,
2387 : static_cast<short *>(pImageNC));
2388 465 : if (status == NC_NOERR)
2389 : {
2390 465 : if (eDataType == GDT_Int16)
2391 : {
2392 462 : CheckData<GInt16>(pImage, pImageNC, edge[nBandXPos],
2393 : nYChunkSize, false);
2394 : }
2395 : else
2396 : {
2397 3 : CheckData<GUInt16>(pImage, pImageNC, edge[nBandXPos],
2398 : nYChunkSize, false);
2399 : }
2400 : }
2401 : }
2402 2227 : else if (eDataType == GDT_Int32)
2403 : {
2404 : #if SIZEOF_UNSIGNED_LONG == 4
2405 : status = nc_get_vara_long(cdfid, nZId, start, edge,
2406 : static_cast<long *>(pImageNC));
2407 : if (status == NC_NOERR)
2408 : CheckData<long>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2409 : false);
2410 : #else
2411 912 : status = nc_get_vara_int(cdfid, nZId, start, edge,
2412 : static_cast<int *>(pImageNC));
2413 912 : if (status == NC_NOERR)
2414 912 : CheckData<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2415 : false);
2416 : #endif
2417 : }
2418 1315 : else if (eDataType == GDT_Float32)
2419 : {
2420 1178 : status = nc_get_vara_float(cdfid, nZId, start, edge,
2421 : static_cast<float *>(pImageNC));
2422 1178 : if (status == NC_NOERR)
2423 1178 : CheckData<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2424 : true);
2425 : }
2426 137 : else if (eDataType == GDT_Float64)
2427 : {
2428 86 : status = nc_get_vara_double(cdfid, nZId, start, edge,
2429 : static_cast<double *>(pImageNC));
2430 86 : if (status == NC_NOERR)
2431 86 : CheckData<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2432 : true);
2433 : }
2434 51 : else if (eDataType == GDT_UInt16)
2435 : {
2436 6 : status = nc_get_vara_ushort(cdfid, nZId, start, edge,
2437 : static_cast<unsigned short *>(pImageNC));
2438 6 : if (status == NC_NOERR)
2439 6 : CheckData<unsigned short>(pImage, pImageNC, edge[nBandXPos],
2440 : nYChunkSize, false);
2441 : }
2442 45 : else if (eDataType == GDT_UInt32)
2443 : {
2444 6 : status = nc_get_vara_uint(cdfid, nZId, start, edge,
2445 : static_cast<unsigned int *>(pImageNC));
2446 6 : if (status == NC_NOERR)
2447 6 : CheckData<unsigned int>(pImage, pImageNC, edge[nBandXPos],
2448 : nYChunkSize, false);
2449 : }
2450 39 : else if (eDataType == GDT_Int64)
2451 : {
2452 7 : status = nc_get_vara_longlong(cdfid, nZId, start, edge,
2453 : static_cast<long long *>(pImageNC));
2454 7 : if (status == NC_NOERR)
2455 7 : CheckData<std::int64_t>(pImage, pImageNC, edge[nBandXPos],
2456 : nYChunkSize, false);
2457 : }
2458 32 : else if (eDataType == GDT_UInt64)
2459 : {
2460 : status =
2461 7 : nc_get_vara_ulonglong(cdfid, nZId, start, edge,
2462 : static_cast<unsigned long long *>(pImageNC));
2463 7 : if (status == NC_NOERR)
2464 7 : CheckData<std::uint64_t>(pImage, pImageNC, edge[nBandXPos],
2465 : nYChunkSize, false);
2466 : }
2467 25 : else if (eDataType == GDT_CInt16)
2468 : {
2469 0 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2470 0 : if (status == NC_NOERR)
2471 0 : CheckDataCpx<short>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2472 : false);
2473 : }
2474 25 : else if (eDataType == GDT_CInt32)
2475 : {
2476 0 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2477 0 : if (status == NC_NOERR)
2478 0 : CheckDataCpx<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2479 : false);
2480 : }
2481 25 : else if (eDataType == GDT_CFloat32)
2482 : {
2483 20 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2484 20 : if (status == NC_NOERR)
2485 20 : CheckDataCpx<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2486 : false);
2487 : }
2488 5 : else if (eDataType == GDT_CFloat64)
2489 : {
2490 5 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2491 5 : if (status == NC_NOERR)
2492 5 : CheckDataCpx<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2493 : false);
2494 : }
2495 :
2496 : else
2497 0 : status = NC_EBADTYPE;
2498 :
2499 5755 : if (status != NC_NOERR)
2500 : {
2501 0 : CPLError(CE_Failure, CPLE_AppDefined,
2502 : "netCDF chunk fetch failed: #%d (%s)", status,
2503 : nc_strerror(status));
2504 0 : return false;
2505 : }
2506 5755 : return true;
2507 : }
2508 :
2509 : /************************************************************************/
2510 : /* IReadBlock() */
2511 : /************************************************************************/
2512 :
2513 5755 : CPLErr netCDFRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
2514 : void *pImage)
2515 :
2516 : {
2517 11510 : CPLMutexHolderD(&hNCMutex);
2518 :
2519 : // Locate X, Y and Z position in the array.
2520 :
2521 5755 : size_t xstart = static_cast<size_t>(nBlockXOff) * nBlockXSize;
2522 5755 : size_t ystart = 0;
2523 :
2524 : // Check y order.
2525 5755 : if (nBandYPos >= 0)
2526 : {
2527 5751 : auto poGDS = static_cast<netCDFDataset *>(poDS);
2528 5751 : if (poGDS->bBottomUp)
2529 : {
2530 4836 : if (nBlockYSize == 1)
2531 : {
2532 4823 : ystart = nRasterYSize - 1 - nBlockYOff;
2533 : }
2534 : else
2535 : {
2536 : // in GDAL space
2537 13 : ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
2538 : const size_t yend =
2539 26 : std::min(ystart + nBlockYSize - 1,
2540 13 : static_cast<size_t>(nRasterYSize - 1));
2541 : // in netCDF space
2542 13 : const size_t nFirstChunkLine = nRasterYSize - 1 - yend;
2543 13 : const size_t nLastChunkLine = nRasterYSize - 1 - ystart;
2544 13 : const size_t nFirstChunkBlock = nFirstChunkLine / nBlockYSize;
2545 13 : const size_t nLastChunkBlock = nLastChunkLine / nBlockYSize;
2546 :
2547 : const auto firstKey = netCDFDataset::ChunkKey(
2548 13 : nBlockXOff, nFirstChunkBlock, nBand);
2549 : const auto secondKey =
2550 13 : netCDFDataset::ChunkKey(nBlockXOff, nLastChunkBlock, nBand);
2551 :
2552 : // Retrieve data from the one or 2 needed netCDF chunks
2553 13 : std::shared_ptr<std::vector<GByte>> firstChunk;
2554 13 : std::shared_ptr<std::vector<GByte>> secondChunk;
2555 13 : if (poGDS->poChunkCache)
2556 : {
2557 13 : poGDS->poChunkCache->tryGet(firstKey, firstChunk);
2558 13 : if (firstKey != secondKey)
2559 6 : poGDS->poChunkCache->tryGet(secondKey, secondChunk);
2560 : }
2561 : const size_t nChunkLineSize =
2562 13 : static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
2563 13 : nBlockXSize;
2564 13 : const size_t nChunkSize = nChunkLineSize * nBlockYSize;
2565 13 : if (!firstChunk)
2566 : {
2567 11 : firstChunk.reset(new std::vector<GByte>(nChunkSize));
2568 11 : if (!FetchNetcdfChunk(xstart,
2569 11 : nFirstChunkBlock * nBlockYSize,
2570 11 : firstChunk.get()->data()))
2571 0 : return CE_Failure;
2572 11 : if (poGDS->poChunkCache)
2573 11 : poGDS->poChunkCache->insert(firstKey, firstChunk);
2574 : }
2575 13 : if (!secondChunk && firstKey != secondKey)
2576 : {
2577 2 : secondChunk.reset(new std::vector<GByte>(nChunkSize));
2578 2 : if (!FetchNetcdfChunk(xstart, nLastChunkBlock * nBlockYSize,
2579 2 : secondChunk.get()->data()))
2580 0 : return CE_Failure;
2581 2 : if (poGDS->poChunkCache)
2582 2 : poGDS->poChunkCache->insert(secondKey, secondChunk);
2583 : }
2584 :
2585 : // Assemble netCDF chunks into GDAL block
2586 13 : GByte *pabyImage = static_cast<GByte *>(pImage);
2587 13 : const size_t nFirstChunkBlockLine =
2588 13 : nFirstChunkBlock * nBlockYSize;
2589 13 : const size_t nLastChunkBlockLine =
2590 13 : nLastChunkBlock * nBlockYSize;
2591 146 : for (size_t iLine = ystart; iLine <= yend; iLine++)
2592 : {
2593 133 : const size_t nLineFromBottom = nRasterYSize - 1 - iLine;
2594 133 : const size_t nChunkY = nLineFromBottom / nBlockYSize;
2595 133 : if (nChunkY == nFirstChunkBlock)
2596 : {
2597 121 : memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
2598 121 : firstChunk.get()->data() +
2599 121 : (nLineFromBottom - nFirstChunkBlockLine) *
2600 : nChunkLineSize,
2601 : nChunkLineSize);
2602 : }
2603 : else
2604 : {
2605 12 : CPLAssert(nChunkY == nLastChunkBlock);
2606 12 : assert(secondChunk);
2607 12 : memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
2608 12 : secondChunk.get()->data() +
2609 12 : (nLineFromBottom - nLastChunkBlockLine) *
2610 : nChunkLineSize,
2611 : nChunkLineSize);
2612 : }
2613 : }
2614 13 : return CE_None;
2615 : }
2616 : }
2617 : else
2618 : {
2619 915 : ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
2620 : }
2621 : }
2622 :
2623 5742 : return FetchNetcdfChunk(xstart, ystart, pImage) ? CE_None : CE_Failure;
2624 : }
2625 :
2626 : /************************************************************************/
2627 : /* IWriteBlock() */
2628 : /************************************************************************/
2629 :
2630 2652 : CPLErr netCDFRasterBand::IWriteBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
2631 : void *pImage)
2632 : {
2633 5304 : CPLMutexHolderD(&hNCMutex);
2634 :
2635 : #ifdef NCDF_DEBUG
2636 : if (nBlockYOff == 0 || (nBlockYOff == nRasterYSize - 1))
2637 : CPLDebug("GDAL_netCDF",
2638 : "netCDFRasterBand::IWriteBlock( %d, %d, ...) nBand=%d",
2639 : nBlockXOff, nBlockYOff, nBand);
2640 : #endif
2641 :
2642 2652 : int nd = 0;
2643 2652 : nc_inq_varndims(cdfid, nZId, &nd);
2644 :
2645 : // Locate X, Y and Z position in the array.
2646 :
2647 : size_t start[MAX_NC_DIMS];
2648 2652 : memset(start, 0, sizeof(start));
2649 2652 : start[nBandXPos] = static_cast<size_t>(nBlockXOff) * nBlockXSize;
2650 :
2651 : // check y order.
2652 2652 : if (static_cast<netCDFDataset *>(poDS)->bBottomUp)
2653 : {
2654 2628 : if (nBlockYSize == 1)
2655 : {
2656 2628 : start[nBandYPos] = nRasterYSize - 1 - nBlockYOff;
2657 : }
2658 : else
2659 : {
2660 0 : CPLError(CE_Failure, CPLE_AppDefined,
2661 : "nBlockYSize = %d, only 1 supported when "
2662 : "writing bottom-up dataset",
2663 : nBlockYSize);
2664 0 : return CE_Failure;
2665 : }
2666 : }
2667 : else
2668 : {
2669 24 : start[nBandYPos] = static_cast<size_t>(nBlockYOff) * nBlockYSize; // y
2670 : }
2671 :
2672 2652 : size_t edge[MAX_NC_DIMS] = {};
2673 :
2674 2652 : edge[nBandXPos] = nBlockXSize;
2675 2652 : if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
2676 0 : edge[nBandXPos] = nRasterXSize - start[nBandXPos];
2677 2652 : edge[nBandYPos] = nBlockYSize;
2678 2652 : if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
2679 0 : edge[nBandYPos] = nRasterYSize - start[nBandYPos];
2680 :
2681 2652 : if (nd == 3)
2682 : {
2683 610 : start[panBandZPos[0]] = nLevel; // z
2684 610 : edge[panBandZPos[0]] = 1;
2685 : }
2686 :
2687 : // Compute multidimention band position.
2688 : //
2689 : // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
2690 : // if Data[2,3,4,x,y]
2691 : //
2692 : // BandPos0 = (nBand) / (3*4)
2693 : // BandPos1 = (nBand - (3*4)) / (4)
2694 : // BandPos2 = (nBand - (3*4)) % (4)
2695 2652 : if (nd > 3)
2696 : {
2697 178 : int Sum = -1;
2698 178 : int Taken = 0;
2699 534 : for (int i = 0; i < nd - 2; i++)
2700 : {
2701 356 : if (i != nd - 2 - 1)
2702 : {
2703 178 : Sum = 1;
2704 356 : for (int j = i + 1; j < nd - 2; j++)
2705 : {
2706 178 : Sum *= panBandZLev[j];
2707 : }
2708 178 : start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
2709 178 : edge[panBandZPos[i]] = 1;
2710 : }
2711 : else
2712 : {
2713 178 : start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
2714 178 : edge[panBandZPos[i]] = 1;
2715 : }
2716 356 : Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
2717 : }
2718 : }
2719 :
2720 : // Make sure we are in data mode.
2721 2652 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
2722 :
2723 : // Copy data according to type.
2724 2652 : int status = 0;
2725 2652 : if (eDataType == GDT_Byte)
2726 : {
2727 2093 : if (bSignedData)
2728 0 : status = nc_put_vara_schar(cdfid, nZId, start, edge,
2729 : static_cast<signed char *>(pImage));
2730 : else
2731 2093 : status = nc_put_vara_uchar(cdfid, nZId, start, edge,
2732 : static_cast<unsigned char *>(pImage));
2733 : }
2734 559 : else if (eDataType == GDT_Int8)
2735 : {
2736 40 : status = nc_put_vara_schar(cdfid, nZId, start, edge,
2737 : static_cast<signed char *>(pImage));
2738 : }
2739 519 : else if (nc_datatype == NC_SHORT)
2740 : {
2741 101 : status = nc_put_vara_short(cdfid, nZId, start, edge,
2742 : static_cast<short *>(pImage));
2743 : }
2744 418 : else if (eDataType == GDT_Int32)
2745 : {
2746 210 : status = nc_put_vara_int(cdfid, nZId, start, edge,
2747 : static_cast<int *>(pImage));
2748 : }
2749 208 : else if (eDataType == GDT_Float32)
2750 : {
2751 128 : status = nc_put_vara_float(cdfid, nZId, start, edge,
2752 : static_cast<float *>(pImage));
2753 : }
2754 80 : else if (eDataType == GDT_Float64)
2755 : {
2756 50 : status = nc_put_vara_double(cdfid, nZId, start, edge,
2757 : static_cast<double *>(pImage));
2758 : }
2759 30 : else if (eDataType == GDT_UInt16 &&
2760 12 : static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2761 : {
2762 12 : status = nc_put_vara_ushort(cdfid, nZId, start, edge,
2763 : static_cast<unsigned short *>(pImage));
2764 : }
2765 18 : else if (eDataType == GDT_UInt32 &&
2766 12 : static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2767 : {
2768 12 : status = nc_put_vara_uint(cdfid, nZId, start, edge,
2769 : static_cast<unsigned int *>(pImage));
2770 : }
2771 6 : else if (eDataType == GDT_UInt64 &&
2772 3 : static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2773 : {
2774 3 : status =
2775 3 : nc_put_vara_ulonglong(cdfid, nZId, start, edge,
2776 : static_cast<unsigned long long *>(pImage));
2777 : }
2778 3 : else if (eDataType == GDT_Int64 &&
2779 3 : static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2780 : {
2781 3 : status = nc_put_vara_longlong(cdfid, nZId, start, edge,
2782 : static_cast<long long *>(pImage));
2783 : }
2784 : else
2785 : {
2786 0 : CPLError(CE_Failure, CPLE_NotSupported,
2787 : "The NetCDF driver does not support GDAL data type %d",
2788 0 : eDataType);
2789 0 : status = NC_EBADTYPE;
2790 : }
2791 2652 : NCDF_ERR(status);
2792 :
2793 2652 : if (status != NC_NOERR)
2794 : {
2795 0 : CPLError(CE_Failure, CPLE_AppDefined,
2796 : "netCDF scanline write failed: %s", nc_strerror(status));
2797 0 : return CE_Failure;
2798 : }
2799 :
2800 2652 : return CE_None;
2801 : }
2802 :
2803 : /************************************************************************/
2804 : /* ==================================================================== */
2805 : /* netCDFDataset */
2806 : /* ==================================================================== */
2807 : /************************************************************************/
2808 :
2809 : /************************************************************************/
2810 : /* netCDFDataset() */
2811 : /************************************************************************/
2812 :
2813 971 : netCDFDataset::netCDFDataset()
2814 : :
2815 : // Basic dataset vars.
2816 : #ifdef ENABLE_NCDUMP
2817 : bFileToDestroyAtClosing(false),
2818 : #endif
2819 : cdfid(-1), nSubDatasets(0), papszSubDatasets(nullptr),
2820 : papszMetadata(nullptr), bBottomUp(true), eFormat(NCDF_FORMAT_NONE),
2821 : bIsGdalFile(false), bIsGdalCfFile(false), pszCFProjection(nullptr),
2822 : pszCFCoordinates(nullptr), nCFVersion(1.6), bSGSupport(false),
2823 971 : eMultipleLayerBehavior(SINGLE_LAYER), logCount(0), vcdf(this, cdfid),
2824 971 : GeometryScribe(vcdf, this->generateLogName()),
2825 971 : FieldScribe(vcdf, this->generateLogName()),
2826 1942 : bufManager(CPLGetUsablePhysicalRAM() / 5),
2827 :
2828 : // projection/GT.
2829 : nXDimID(-1), nYDimID(-1), bIsProjected(false),
2830 : bIsGeographic(false), // Can be not projected, and also not geographic
2831 : // State vars.
2832 : bDefineMode(true), bAddedGridMappingRef(false),
2833 :
2834 : // Create vars.
2835 : papszCreationOptions(nullptr), eCompress(NCDF_COMPRESS_NONE),
2836 : nZLevel(NCDF_DEFLATE_LEVEL), bChunking(false), nCreateMode(NC_CLOBBER),
2837 2913 : bSignedData(true)
2838 : {
2839 971 : m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2840 :
2841 : // Projection/GT.
2842 971 : m_adfGeoTransform[0] = 0.0;
2843 971 : m_adfGeoTransform[1] = 1.0;
2844 971 : m_adfGeoTransform[2] = 0.0;
2845 971 : m_adfGeoTransform[3] = 0.0;
2846 971 : m_adfGeoTransform[4] = 0.0;
2847 971 : m_adfGeoTransform[5] = 1.0;
2848 :
2849 : // Set buffers
2850 971 : bufManager.addBuffer(&(GeometryScribe.getMemBuffer()));
2851 971 : bufManager.addBuffer(&(FieldScribe.getMemBuffer()));
2852 971 : }
2853 :
2854 : /************************************************************************/
2855 : /* ~netCDFDataset() */
2856 : /************************************************************************/
2857 :
2858 1881 : netCDFDataset::~netCDFDataset()
2859 :
2860 : {
2861 971 : netCDFDataset::Close();
2862 1881 : }
2863 :
2864 : /************************************************************************/
2865 : /* Close() */
2866 : /************************************************************************/
2867 :
2868 1704 : CPLErr netCDFDataset::Close()
2869 : {
2870 1704 : CPLErr eErr = CE_None;
2871 1704 : if (nOpenFlags != OPEN_FLAGS_CLOSED)
2872 : {
2873 1942 : CPLMutexHolderD(&hNCMutex);
2874 :
2875 : #ifdef NCDF_DEBUG
2876 : CPLDebug("GDAL_netCDF",
2877 : "netCDFDataset::~netCDFDataset(), cdfid=%d filename=%s", cdfid,
2878 : osFilename.c_str());
2879 : #endif
2880 :
2881 : // Write data related to geotransform
2882 1202 : if (GetAccess() == GA_Update && !m_bAddedProjectionVarsData &&
2883 231 : (m_bHasProjection || m_bHasGeoTransform))
2884 : {
2885 : // Ensure projection is written if GeoTransform OR Projection are
2886 : // missing.
2887 37 : if (!m_bAddedProjectionVarsDefs)
2888 : {
2889 2 : AddProjectionVars(true, nullptr, nullptr);
2890 : }
2891 37 : AddProjectionVars(false, nullptr, nullptr);
2892 : }
2893 :
2894 971 : if (netCDFDataset::FlushCache(true) != CE_None)
2895 0 : eErr = CE_Failure;
2896 :
2897 971 : if (GetAccess() == GA_Update && !SGCommitPendingTransaction())
2898 0 : eErr = CE_Failure;
2899 :
2900 973 : for (size_t i = 0; i < apoVectorDatasets.size(); i++)
2901 2 : delete apoVectorDatasets[i];
2902 :
2903 : // Make sure projection variable is written to band variable.
2904 971 : if (GetAccess() == GA_Update && !bAddedGridMappingRef)
2905 : {
2906 244 : if (!AddGridMappingRef())
2907 0 : eErr = CE_Failure;
2908 : }
2909 :
2910 971 : CSLDestroy(papszMetadata);
2911 971 : CSLDestroy(papszSubDatasets);
2912 971 : CSLDestroy(papszCreationOptions);
2913 :
2914 971 : CPLFree(pszCFProjection);
2915 :
2916 971 : if (cdfid > 0)
2917 : {
2918 : #ifdef NCDF_DEBUG
2919 : CPLDebug("GDAL_netCDF", "calling nc_close( %d)", cdfid);
2920 : #endif
2921 648 : int status = GDAL_nc_close(cdfid);
2922 : #ifdef ENABLE_UFFD
2923 648 : NETCDF_UFFD_UNMAP(pCtx);
2924 : #endif
2925 648 : NCDF_ERR(status);
2926 648 : if (status != NC_NOERR)
2927 0 : eErr = CE_Failure;
2928 : }
2929 :
2930 971 : if (fpVSIMEM)
2931 15 : VSIFCloseL(fpVSIMEM);
2932 :
2933 : #ifdef ENABLE_NCDUMP
2934 971 : if (bFileToDestroyAtClosing)
2935 0 : VSIUnlink(osFilename);
2936 : #endif
2937 :
2938 971 : if (GDALPamDataset::Close() != CE_None)
2939 0 : eErr = CE_Failure;
2940 : }
2941 1704 : return eErr;
2942 : }
2943 :
2944 : /************************************************************************/
2945 : /* SetDefineMode() */
2946 : /************************************************************************/
2947 10452 : bool netCDFDataset::SetDefineMode(bool bNewDefineMode)
2948 : {
2949 : // Do nothing if already in new define mode
2950 : // or if dataset is in read-only mode or if dataset is true NC4 dataset.
2951 11000 : if (bDefineMode == bNewDefineMode || GetAccess() == GA_ReadOnly ||
2952 548 : eFormat == NCDF_FORMAT_NC4)
2953 10051 : return true;
2954 :
2955 401 : CPLDebug("GDAL_netCDF", "SetDefineMode(%d) old=%d",
2956 401 : static_cast<int>(bNewDefineMode), static_cast<int>(bDefineMode));
2957 :
2958 401 : bDefineMode = bNewDefineMode;
2959 :
2960 : int status;
2961 401 : if (bDefineMode)
2962 139 : status = nc_redef(cdfid);
2963 : else
2964 262 : status = nc_enddef(cdfid);
2965 :
2966 401 : NCDF_ERR(status);
2967 401 : return status == NC_NOERR;
2968 : }
2969 :
2970 : /************************************************************************/
2971 : /* GetMetadataDomainList() */
2972 : /************************************************************************/
2973 :
2974 27 : char **netCDFDataset::GetMetadataDomainList()
2975 : {
2976 27 : char **papszDomains = BuildMetadataDomainList(
2977 : GDALDataset::GetMetadataDomainList(), TRUE, "SUBDATASETS", nullptr);
2978 28 : for (const auto &kv : m_oMapDomainToJSon)
2979 1 : papszDomains = CSLAddString(papszDomains, ("json:" + kv.first).c_str());
2980 27 : return papszDomains;
2981 : }
2982 :
2983 : /************************************************************************/
2984 : /* GetMetadata() */
2985 : /************************************************************************/
2986 354 : char **netCDFDataset::GetMetadata(const char *pszDomain)
2987 : {
2988 354 : if (pszDomain != nullptr && STARTS_WITH_CI(pszDomain, "SUBDATASETS"))
2989 36 : return papszSubDatasets;
2990 :
2991 318 : if (pszDomain != nullptr && STARTS_WITH(pszDomain, "json:"))
2992 : {
2993 1 : auto iter = m_oMapDomainToJSon.find(pszDomain + strlen("json:"));
2994 1 : if (iter != m_oMapDomainToJSon.end())
2995 1 : return iter->second.List();
2996 : }
2997 :
2998 317 : return GDALDataset::GetMetadata(pszDomain);
2999 : }
3000 :
3001 : /************************************************************************/
3002 : /* SetMetadataItem() */
3003 : /************************************************************************/
3004 :
3005 42 : CPLErr netCDFDataset::SetMetadataItem(const char *pszName, const char *pszValue,
3006 : const char *pszDomain)
3007 : {
3008 83 : if (GetAccess() == GA_Update &&
3009 83 : (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)
3010 : {
3011 41 : std::string osName(pszName);
3012 :
3013 : // Same logic as in CopyMetadata()
3014 41 : if (STARTS_WITH(osName.c_str(), "NC_GLOBAL#"))
3015 8 : osName = osName.substr(strlen("NC_GLOBAL#"));
3016 33 : else if (strchr(osName.c_str(), '#') == nullptr)
3017 4 : osName = "GDAL_" + osName;
3018 :
3019 82 : if (STARTS_WITH(osName.c_str(), "NETCDF_DIM_") ||
3020 41 : strchr(osName.c_str(), '#') != nullptr)
3021 : {
3022 : // do nothing
3023 29 : return CE_None;
3024 : }
3025 : else
3026 : {
3027 12 : SetDefineMode(true);
3028 :
3029 12 : if (!NCDFPutAttr(cdfid, NC_GLOBAL, osName.c_str(), pszValue))
3030 12 : return CE_Failure;
3031 : }
3032 : }
3033 :
3034 1 : return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);
3035 : }
3036 :
3037 : /************************************************************************/
3038 : /* SetMetadata() */
3039 : /************************************************************************/
3040 :
3041 7 : CPLErr netCDFDataset::SetMetadata(char **papszMD, const char *pszDomain)
3042 : {
3043 11 : if (GetAccess() == GA_Update &&
3044 4 : (pszDomain == nullptr || pszDomain[0] == '\0'))
3045 : {
3046 : // We don't handle metadata item removal for now
3047 48 : for (const char *const *papszIter = papszMD; papszIter && *papszIter;
3048 : ++papszIter)
3049 : {
3050 41 : char *pszName = nullptr;
3051 41 : const char *pszValue = CPLParseNameValue(*papszIter, &pszName);
3052 41 : if (pszName && pszValue)
3053 41 : SetMetadataItem(pszName, pszValue);
3054 41 : CPLFree(pszName);
3055 : }
3056 7 : return CE_None;
3057 : }
3058 0 : return GDALPamDataset::SetMetadata(papszMD, pszDomain);
3059 : }
3060 :
3061 : /************************************************************************/
3062 : /* GetSpatialRef() */
3063 : /************************************************************************/
3064 :
3065 168 : const OGRSpatialReference *netCDFDataset::GetSpatialRef() const
3066 : {
3067 168 : if (m_bHasProjection)
3068 69 : return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
3069 :
3070 99 : return GDALPamDataset::GetSpatialRef();
3071 : }
3072 :
3073 : /************************************************************************/
3074 : /* FetchCopyParam() */
3075 : /************************************************************************/
3076 :
3077 312 : double netCDFDataset::FetchCopyParam(const char *pszGridMappingValue,
3078 : const char *pszParam, double dfDefault,
3079 : bool *pbFound)
3080 :
3081 : {
3082 : char *pszTemp =
3083 312 : CPLStrdup(CPLSPrintf("%s#%s", pszGridMappingValue, pszParam));
3084 312 : const char *pszValue = CSLFetchNameValue(papszMetadata, pszTemp);
3085 312 : CPLFree(pszTemp);
3086 :
3087 312 : if (pbFound)
3088 : {
3089 312 : *pbFound = (pszValue != nullptr);
3090 : }
3091 :
3092 312 : if (pszValue)
3093 : {
3094 0 : return CPLAtofM(pszValue);
3095 : }
3096 :
3097 312 : return dfDefault;
3098 : }
3099 :
3100 : /************************************************************************/
3101 : /* FetchStandardParallels() */
3102 : /************************************************************************/
3103 :
3104 : std::vector<std::string>
3105 0 : netCDFDataset::FetchStandardParallels(const char *pszGridMappingValue)
3106 : {
3107 : // cf-1.0 tags
3108 0 : const char *pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL);
3109 :
3110 0 : std::vector<std::string> ret;
3111 0 : if (pszValue != nullptr)
3112 : {
3113 0 : CPLStringList aosValues;
3114 0 : if (pszValue[0] != '{' &&
3115 0 : CPLString(pszValue).Trim().find(' ') != std::string::npos)
3116 : {
3117 : // Some files like
3118 : // ftp://data.knmi.nl/download/KNW-NetCDF-3D/1.0/noversion/2013/11/14/KNW-1.0_H37-ERA_NL_20131114.nc
3119 : // do not use standard formatting for arrays, but just space
3120 : // separated syntax
3121 0 : aosValues = CSLTokenizeString2(pszValue, " ", 0);
3122 : }
3123 : else
3124 : {
3125 0 : aosValues = NCDFTokenizeArray(pszValue);
3126 : }
3127 0 : for (int i = 0; i < aosValues.size(); i++)
3128 : {
3129 0 : ret.push_back(aosValues[i]);
3130 : }
3131 : }
3132 : // Try gdal tags.
3133 : else
3134 : {
3135 0 : pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_1);
3136 :
3137 0 : if (pszValue != nullptr)
3138 0 : ret.push_back(pszValue);
3139 :
3140 0 : pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_2);
3141 :
3142 0 : if (pszValue != nullptr)
3143 0 : ret.push_back(pszValue);
3144 : }
3145 :
3146 0 : return ret;
3147 : }
3148 :
3149 : /************************************************************************/
3150 : /* FetchAttr() */
3151 : /************************************************************************/
3152 :
3153 3592 : const char *netCDFDataset::FetchAttr(const char *pszVarFullName,
3154 : const char *pszAttr)
3155 :
3156 : {
3157 3592 : char *pszKey = CPLStrdup(CPLSPrintf("%s#%s", pszVarFullName, pszAttr));
3158 3592 : const char *pszValue = CSLFetchNameValue(papszMetadata, pszKey);
3159 3592 : CPLFree(pszKey);
3160 3592 : return pszValue;
3161 : }
3162 :
3163 2389 : const char *netCDFDataset::FetchAttr(int nGroupId, int nVarId,
3164 : const char *pszAttr)
3165 :
3166 : {
3167 2389 : char *pszVarFullName = nullptr;
3168 2389 : NCDFGetVarFullName(nGroupId, nVarId, &pszVarFullName);
3169 2389 : const char *pszValue = FetchAttr(pszVarFullName, pszAttr);
3170 2389 : CPLFree(pszVarFullName);
3171 2389 : return pszValue;
3172 : }
3173 :
3174 : /************************************************************************/
3175 : /* IsDifferenceBelow() */
3176 : /************************************************************************/
3177 :
3178 1055 : static bool IsDifferenceBelow(double dfA, double dfB, double dfError)
3179 : {
3180 1055 : const double dfAbsDiff = fabs(dfA - dfB);
3181 1055 : return dfAbsDiff <= dfError;
3182 : }
3183 :
3184 : /************************************************************************/
3185 : /* SetProjectionFromVar() */
3186 : /************************************************************************/
3187 495 : void netCDFDataset::SetProjectionFromVar(
3188 : int nGroupId, int nVarId, bool bReadSRSOnly, const char *pszGivenGM,
3189 : std::string *returnProjStr, nccfdriver::SGeometry_Reader *sg,
3190 : std::vector<std::string> *paosRemovedMDItems)
3191 : {
3192 495 : bool bGotGeogCS = false;
3193 495 : bool bGotCfSRS = false;
3194 495 : bool bGotCfWktSRS = false;
3195 495 : bool bGotGdalSRS = false;
3196 495 : bool bGotCfGT = false;
3197 495 : bool bGotGdalGT = false;
3198 :
3199 : // These values from CF metadata.
3200 495 : OGRSpatialReference oSRS;
3201 495 : oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3202 495 : size_t xdim = nRasterXSize;
3203 495 : size_t ydim = nRasterYSize;
3204 :
3205 : // These values from GDAL metadata.
3206 495 : const char *pszWKT = nullptr;
3207 495 : const char *pszGeoTransform = nullptr;
3208 :
3209 495 : netCDFDataset *poDS = this; // Perhaps this should be removed for clarity.
3210 :
3211 495 : CPLDebug("GDAL_netCDF", "\n=====\nSetProjectionFromVar( %d, %d)", nGroupId,
3212 : nVarId);
3213 :
3214 : // Get x/y range information.
3215 :
3216 : // Temp variables to use in SetGeoTransform() and SetProjection().
3217 495 : double adfTempGeoTransform[6] = {0.0, 1.0, 0.0, 0.0, 0.0, 1.0};
3218 :
3219 : // Look for grid_mapping metadata.
3220 495 : const char *pszValue = pszGivenGM;
3221 495 : CPLString osTmpGridMapping; // let is in this outer scope as pszValue may
3222 : // point to it
3223 495 : if (pszValue == nullptr)
3224 : {
3225 452 : pszValue = FetchAttr(nGroupId, nVarId, CF_GRD_MAPPING);
3226 452 : if (pszValue && strchr(pszValue, ':') && strchr(pszValue, ' '))
3227 : {
3228 : // Expanded form of grid_mapping
3229 : // e.g. "crsOSGB: x y crsWGS84: lat lon"
3230 : // Pickup the grid_mapping whose coordinates are dimensions of the
3231 : // variable
3232 6 : CPLStringList aosTokens(CSLTokenizeString2(pszValue, " ", 0));
3233 3 : if ((aosTokens.size() % 3) == 0)
3234 : {
3235 3 : for (int i = 0; i < aosTokens.size() / 3; i++)
3236 : {
3237 3 : if (CSLFindString(poDS->papszDimName,
3238 9 : aosTokens[3 * i + 1]) >= 0 &&
3239 3 : CSLFindString(poDS->papszDimName,
3240 3 : aosTokens[3 * i + 2]) >= 0)
3241 : {
3242 3 : osTmpGridMapping = aosTokens[3 * i];
3243 6 : if (!osTmpGridMapping.empty() &&
3244 3 : osTmpGridMapping.back() == ':')
3245 : {
3246 3 : osTmpGridMapping.resize(osTmpGridMapping.size() -
3247 : 1);
3248 : }
3249 3 : pszValue = osTmpGridMapping.c_str();
3250 3 : break;
3251 : }
3252 : }
3253 : }
3254 : }
3255 : }
3256 495 : char *pszGridMappingValue = CPLStrdup(pszValue ? pszValue : "");
3257 :
3258 495 : if (!EQUAL(pszGridMappingValue, ""))
3259 : {
3260 : // Read grid_mapping metadata.
3261 211 : int nProjGroupID = -1;
3262 211 : int nProjVarID = -1;
3263 211 : if (NCDFResolveVar(nGroupId, pszGridMappingValue, &nProjGroupID,
3264 211 : &nProjVarID) == CE_None)
3265 : {
3266 210 : poDS->ReadAttributes(nProjGroupID, nProjVarID);
3267 :
3268 : // Look for GDAL spatial_ref and GeoTransform within grid_mapping.
3269 210 : CPLFree(pszGridMappingValue);
3270 210 : pszGridMappingValue = nullptr;
3271 210 : NCDFGetVarFullName(nProjGroupID, nProjVarID, &pszGridMappingValue);
3272 210 : if (pszGridMappingValue)
3273 : {
3274 210 : CPLDebug("GDAL_netCDF", "got grid_mapping %s",
3275 : pszGridMappingValue);
3276 210 : pszWKT = FetchAttr(pszGridMappingValue, NCDF_SPATIAL_REF);
3277 210 : if (!pszWKT)
3278 : {
3279 34 : pszWKT = FetchAttr(pszGridMappingValue, NCDF_CRS_WKT);
3280 : }
3281 : else
3282 : {
3283 176 : bGotGdalSRS = true;
3284 176 : CPLDebug("GDAL_netCDF", "setting WKT from GDAL");
3285 : }
3286 210 : if (pszWKT)
3287 : {
3288 180 : if (!bGotGdalSRS)
3289 : {
3290 4 : bGotCfWktSRS = true;
3291 4 : CPLDebug("GDAL_netCDF", "setting WKT from CF");
3292 : }
3293 180 : if (returnProjStr != nullptr)
3294 : {
3295 41 : (*returnProjStr) = std::string(pszWKT);
3296 : }
3297 : else
3298 : {
3299 139 : m_bAddedProjectionVarsDefs = true;
3300 139 : m_bAddedProjectionVarsData = true;
3301 278 : OGRSpatialReference oSRSTmp;
3302 139 : oSRSTmp.SetAxisMappingStrategy(
3303 : OAMS_TRADITIONAL_GIS_ORDER);
3304 139 : oSRSTmp.importFromWkt(pszWKT);
3305 139 : SetSpatialRefNoUpdate(&oSRSTmp);
3306 : }
3307 : pszGeoTransform =
3308 180 : FetchAttr(pszGridMappingValue, NCDF_GEOTRANSFORM);
3309 : }
3310 : }
3311 : else
3312 : {
3313 0 : pszGridMappingValue = CPLStrdup("");
3314 : }
3315 : }
3316 : }
3317 :
3318 : // Get information about the file.
3319 : //
3320 : // Was this file created by the GDAL netcdf driver?
3321 : // Was this file created by the newer (CF-conformant) driver?
3322 : //
3323 : // 1) If GDAL netcdf metadata is set, and version >= 1.9,
3324 : // it was created with the new driver
3325 : // 2) Else, if spatial_ref and GeoTransform are present in the
3326 : // grid_mapping variable, it was created by the old driver
3327 495 : pszValue = FetchAttr("NC_GLOBAL", "GDAL");
3328 :
3329 495 : if (pszValue && NCDFIsGDALVersionGTE(pszValue, 1900))
3330 : {
3331 236 : bIsGdalFile = true;
3332 236 : bIsGdalCfFile = true;
3333 : }
3334 259 : else if (pszWKT != nullptr && pszGeoTransform != nullptr)
3335 : {
3336 14 : bIsGdalFile = true;
3337 14 : bIsGdalCfFile = false;
3338 : }
3339 :
3340 : // Set default bottom-up default value.
3341 : // Y axis dimension and absence of GT can modify this value.
3342 : // Override with Config option GDAL_NETCDF_BOTTOMUP.
3343 :
3344 : // New driver is bottom-up by default.
3345 495 : if ((bIsGdalFile && !bIsGdalCfFile) || bSwitchedXY)
3346 16 : poDS->bBottomUp = false;
3347 : else
3348 479 : poDS->bBottomUp = true;
3349 :
3350 495 : CPLDebug("GDAL_netCDF",
3351 : "bIsGdalFile=%d bIsGdalCfFile=%d bSwitchedXY=%d bBottomUp=%d",
3352 495 : static_cast<int>(bIsGdalFile), static_cast<int>(bIsGdalCfFile),
3353 495 : static_cast<int>(bSwitchedXY), static_cast<int>(bBottomUp));
3354 :
3355 : // Read projection coordinates.
3356 :
3357 495 : int nGroupDimXID = -1;
3358 495 : int nVarDimXID = -1;
3359 495 : int nGroupDimYID = -1;
3360 495 : int nVarDimYID = -1;
3361 495 : if (sg != nullptr)
3362 : {
3363 43 : nGroupDimXID = sg->get_ncID();
3364 43 : nGroupDimYID = sg->get_ncID();
3365 43 : nVarDimXID = sg->getNodeCoordVars()[0];
3366 43 : nVarDimYID = sg->getNodeCoordVars()[1];
3367 : }
3368 :
3369 495 : if (!bReadSRSOnly)
3370 : {
3371 342 : NCDFResolveVar(nGroupId, poDS->papszDimName[nXDimID], &nGroupDimXID,
3372 : &nVarDimXID);
3373 342 : NCDFResolveVar(nGroupId, poDS->papszDimName[nYDimID], &nGroupDimYID,
3374 : &nVarDimYID);
3375 : // TODO: if above resolving fails we should also search for coordinate
3376 : // variables without same name than dimension using the same resolving
3377 : // logic. This should handle for example NASA Ocean Color L2 products.
3378 :
3379 : const bool bIgnoreXYAxisNameChecks =
3380 684 : CPLTestBool(CSLFetchNameValueDef(
3381 342 : papszOpenOptions, "IGNORE_XY_AXIS_NAME_CHECKS",
3382 : CPLGetConfigOption("GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS",
3383 342 : "NO"))) ||
3384 : // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a res
3385 : // and transform attributes
3386 342 : (FetchAttr(nGroupId, nVarId, "res") != nullptr &&
3387 684 : FetchAttr(nGroupId, nVarId, "transform") != nullptr) ||
3388 341 : FetchAttr(nGroupId, NC_GLOBAL, "GMT_version") != nullptr;
3389 :
3390 : // Check that they are 1D or 2D variables
3391 342 : if (nVarDimXID >= 0)
3392 : {
3393 248 : int ndims = -1;
3394 248 : nc_inq_varndims(nGroupId, nVarDimXID, &ndims);
3395 248 : if (ndims == 0 || ndims > 2)
3396 0 : nVarDimXID = -1;
3397 248 : else if (!bIgnoreXYAxisNameChecks)
3398 : {
3399 246 : if (!NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
3400 159 : !NCDFIsVarProjectionX(nGroupId, nVarDimXID, nullptr) &&
3401 : // In case of inversion of X/Y
3402 436 : !NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr) &&
3403 31 : !NCDFIsVarProjectionY(nGroupId, nVarDimXID, nullptr))
3404 : {
3405 : char szVarNameX[NC_MAX_NAME + 1];
3406 31 : CPL_IGNORE_RET_VAL(
3407 31 : nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
3408 31 : if (!(ndims == 1 &&
3409 30 : (EQUAL(szVarNameX, CF_LONGITUDE_STD_NAME) ||
3410 29 : EQUAL(szVarNameX, CF_LONGITUDE_VAR_NAME))))
3411 : {
3412 30 : CPLDebug(
3413 : "netCDF",
3414 : "Georeferencing ignored due to non-specific "
3415 : "enough X axis name. "
3416 : "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
3417 : "as configuration option to bypass this check");
3418 30 : nVarDimXID = -1;
3419 : }
3420 : }
3421 : }
3422 : }
3423 :
3424 342 : if (nVarDimYID >= 0)
3425 : {
3426 250 : int ndims = -1;
3427 250 : nc_inq_varndims(nGroupId, nVarDimYID, &ndims);
3428 250 : if (ndims == 0 || ndims > 2)
3429 1 : nVarDimYID = -1;
3430 249 : else if (!bIgnoreXYAxisNameChecks)
3431 : {
3432 247 : if (!NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr) &&
3433 160 : !NCDFIsVarProjectionY(nGroupId, nVarDimYID, nullptr) &&
3434 : // In case of inversion of X/Y
3435 439 : !NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
3436 32 : !NCDFIsVarProjectionX(nGroupId, nVarDimYID, nullptr))
3437 : {
3438 : char szVarNameY[NC_MAX_NAME + 1];
3439 32 : CPL_IGNORE_RET_VAL(
3440 32 : nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
3441 32 : if (!(ndims == 1 &&
3442 32 : (EQUAL(szVarNameY, CF_LATITUDE_STD_NAME) ||
3443 31 : EQUAL(szVarNameY, CF_LATITUDE_VAR_NAME))))
3444 : {
3445 31 : CPLDebug(
3446 : "netCDF",
3447 : "Georeferencing ignored due to non-specific "
3448 : "enough Y axis name. "
3449 : "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
3450 : "as configuration option to bypass this check");
3451 31 : nVarDimYID = -1;
3452 : }
3453 : }
3454 : }
3455 : }
3456 :
3457 342 : if ((nVarDimXID >= 0 && xdim == 1) || (nVarDimXID >= 0 && ydim == 1))
3458 : {
3459 0 : CPLError(CE_Warning, CPLE_AppDefined,
3460 : "1-pixel width/height files not supported, "
3461 : "xdim: %ld ydim: %ld",
3462 : static_cast<long>(xdim), static_cast<long>(ydim));
3463 0 : nVarDimXID = -1;
3464 0 : nVarDimYID = -1;
3465 : }
3466 : }
3467 :
3468 495 : const char *pszUnits = nullptr;
3469 495 : if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
3470 : {
3471 261 : const char *pszUnitsX = FetchAttr(nGroupDimXID, nVarDimXID, "units");
3472 261 : const char *pszUnitsY = FetchAttr(nGroupDimYID, nVarDimYID, "units");
3473 : // Normalize degrees_east/degrees_north to degrees
3474 : // Cf https://github.com/OSGeo/gdal/issues/11009
3475 261 : if (pszUnitsX && EQUAL(pszUnitsX, "degrees_east"))
3476 76 : pszUnitsX = "degrees";
3477 261 : if (pszUnitsY && EQUAL(pszUnitsY, "degrees_north"))
3478 76 : pszUnitsY = "degrees";
3479 :
3480 261 : if (pszUnitsX && pszUnitsY)
3481 : {
3482 214 : if (EQUAL(pszUnitsX, pszUnitsY))
3483 211 : pszUnits = pszUnitsX;
3484 3 : else if (!pszWKT && !EQUAL(pszGridMappingValue, ""))
3485 : {
3486 0 : CPLError(CE_Failure, CPLE_AppDefined,
3487 : "X axis unit (%s) is different from Y axis "
3488 : "unit (%s). SRS will ignore axis unit and be "
3489 : "likely wrong.",
3490 : pszUnitsX, pszUnitsY);
3491 : }
3492 : }
3493 47 : else if (pszUnitsX && !pszWKT && !EQUAL(pszGridMappingValue, ""))
3494 : {
3495 0 : CPLError(CE_Failure, CPLE_AppDefined,
3496 : "X axis unit is defined, but not Y one ."
3497 : "SRS will ignore axis unit and be likely wrong.");
3498 : }
3499 47 : else if (pszUnitsY && !pszWKT && !EQUAL(pszGridMappingValue, ""))
3500 : {
3501 0 : CPLError(CE_Failure, CPLE_AppDefined,
3502 : "Y axis unit is defined, but not X one ."
3503 : "SRS will ignore axis unit and be likely wrong.");
3504 : }
3505 : }
3506 :
3507 495 : if (!pszWKT && !EQUAL(pszGridMappingValue, ""))
3508 : {
3509 31 : CPLStringList aosGridMappingKeyValues;
3510 31 : const size_t nLenGridMappingValue = strlen(pszGridMappingValue);
3511 789 : for (const char *const *papszIter = papszMetadata;
3512 789 : papszIter && *papszIter; ++papszIter)
3513 : {
3514 758 : if (STARTS_WITH(*papszIter, pszGridMappingValue) &&
3515 236 : (*papszIter)[nLenGridMappingValue] == '#')
3516 : {
3517 236 : char *pszKey = nullptr;
3518 472 : pszValue = CPLParseNameValue(
3519 236 : *papszIter + nLenGridMappingValue + 1, &pszKey);
3520 236 : if (pszKey && pszValue)
3521 236 : aosGridMappingKeyValues.SetNameValue(pszKey, pszValue);
3522 236 : CPLFree(pszKey);
3523 : }
3524 : }
3525 :
3526 31 : bGotGeogCS = aosGridMappingKeyValues.FetchNameValue(
3527 : CF_PP_SEMI_MAJOR_AXIS) != nullptr;
3528 :
3529 31 : oSRS.importFromCF1(aosGridMappingKeyValues.List(), pszUnits);
3530 31 : bGotCfSRS = oSRS.IsGeographic() || oSRS.IsProjected();
3531 : }
3532 : else
3533 : {
3534 : // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a "crs"
3535 : // attribute hold on the variable of interest that contains a PROJ.4
3536 : // string
3537 464 : pszValue = FetchAttr(nGroupId, nVarId, "crs");
3538 465 : if (pszValue &&
3539 1 : (strstr(pszValue, "+proj=") != nullptr ||
3540 0 : strstr(pszValue, "GEOGCS") != nullptr ||
3541 0 : strstr(pszValue, "PROJCS") != nullptr ||
3542 465 : strstr(pszValue, "EPSG:") != nullptr) &&
3543 1 : oSRS.SetFromUserInput(pszValue) == OGRERR_NONE)
3544 : {
3545 1 : bGotCfSRS = true;
3546 : }
3547 : }
3548 :
3549 : // Set Projection from CF.
3550 495 : double dfLinearUnitsConvFactor = 1.0;
3551 495 : if ((bGotGeogCS || bGotCfSRS))
3552 : {
3553 31 : if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
3554 : {
3555 : // Set SRS Units.
3556 :
3557 : // Check units for x and y.
3558 28 : if (oSRS.IsProjected())
3559 : {
3560 25 : dfLinearUnitsConvFactor = oSRS.GetLinearUnits(nullptr);
3561 :
3562 : // If the user doesn't ask to preserve the axis unit,
3563 : // then normalize to metre
3564 31 : if (dfLinearUnitsConvFactor != 1.0 &&
3565 6 : !CPLFetchBool(GetOpenOptions(), "PRESERVE_AXIS_UNIT_IN_CRS",
3566 : false))
3567 : {
3568 5 : oSRS.SetLinearUnits("metre", 1.0);
3569 5 : oSRS.SetAuthority("PROJCS|UNIT", "EPSG", 9001);
3570 : }
3571 : else
3572 : {
3573 20 : dfLinearUnitsConvFactor = 1.0;
3574 : }
3575 : }
3576 : }
3577 :
3578 : // Set projection.
3579 31 : char *pszTempProjection = nullptr;
3580 31 : oSRS.exportToWkt(&pszTempProjection);
3581 31 : if (pszTempProjection)
3582 : {
3583 31 : CPLDebug("GDAL_netCDF", "setting WKT from CF");
3584 31 : if (returnProjStr != nullptr)
3585 : {
3586 2 : (*returnProjStr) = std::string(pszTempProjection);
3587 : }
3588 : else
3589 : {
3590 29 : m_bAddedProjectionVarsDefs = true;
3591 29 : m_bAddedProjectionVarsData = true;
3592 29 : SetSpatialRefNoUpdate(&oSRS);
3593 : }
3594 : }
3595 31 : CPLFree(pszTempProjection);
3596 : }
3597 :
3598 495 : if (!bReadSRSOnly && (nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 &&
3599 : ydim > 0)
3600 : {
3601 : double *pdfXCoord =
3602 218 : static_cast<double *>(CPLCalloc(xdim, sizeof(double)));
3603 : double *pdfYCoord =
3604 218 : static_cast<double *>(CPLCalloc(ydim, sizeof(double)));
3605 :
3606 218 : size_t start[2] = {0, 0};
3607 218 : size_t edge[2] = {xdim, 0};
3608 218 : int status = nc_get_vara_double(nGroupDimXID, nVarDimXID, start, edge,
3609 : pdfXCoord);
3610 218 : NCDF_ERR(status);
3611 :
3612 218 : edge[0] = ydim;
3613 218 : status = nc_get_vara_double(nGroupDimYID, nVarDimYID, start, edge,
3614 : pdfYCoord);
3615 218 : NCDF_ERR(status);
3616 :
3617 218 : nc_type nc_var_dimx_datatype = NC_NAT;
3618 : status =
3619 218 : nc_inq_vartype(nGroupDimXID, nVarDimXID, &nc_var_dimx_datatype);
3620 218 : NCDF_ERR(status);
3621 :
3622 218 : nc_type nc_var_dimy_datatype = NC_NAT;
3623 : status =
3624 218 : nc_inq_vartype(nGroupDimYID, nVarDimYID, &nc_var_dimy_datatype);
3625 218 : NCDF_ERR(status);
3626 :
3627 218 : if (!poDS->bSwitchedXY)
3628 : {
3629 : // Convert ]180,540] longitude values to ]-180,0].
3630 303 : if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
3631 87 : CPLTestBool(
3632 : CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")))
3633 : {
3634 : // If minimum longitude is > 180, subtract 360 from all.
3635 : // Add a check on the maximum X value too, since
3636 : // NCDFIsVarLongitude() is not very specific by default (see
3637 : // https://github.com/OSGeo/gdal/issues/1440)
3638 94 : if (std::min(pdfXCoord[0], pdfXCoord[xdim - 1]) > 180.0 &&
3639 7 : std::max(pdfXCoord[0], pdfXCoord[xdim - 1]) <= 540)
3640 : {
3641 0 : CPLDebug(
3642 : "GDAL_netCDF",
3643 : "Offsetting longitudes from ]180,540] to ]-180,180]. "
3644 : "Can be disabled with GDAL_NETCDF_CENTERLONG_180=NO");
3645 0 : for (size_t i = 0; i < xdim; i++)
3646 0 : pdfXCoord[i] -= 360;
3647 : }
3648 : }
3649 : }
3650 :
3651 : // Is pixel spacing uniform across the map?
3652 :
3653 : // Check Longitude.
3654 :
3655 218 : bool bLonSpacingOK = false;
3656 218 : if (xdim == 2)
3657 : {
3658 28 : bLonSpacingOK = true;
3659 : }
3660 : else
3661 : {
3662 190 : bool bWestIsLeft = (pdfXCoord[0] < pdfXCoord[xdim - 1]);
3663 :
3664 : // fix longitudes if longitudes should increase from
3665 : // west to east, but west > east
3666 267 : if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
3667 77 : !bWestIsLeft)
3668 : {
3669 2 : size_t ndecreases = 0;
3670 :
3671 : // there is lon wrap if longitudes increase
3672 : // with one single decrease
3673 107 : for (size_t i = 1; i < xdim; i++)
3674 : {
3675 105 : if (pdfXCoord[i] < pdfXCoord[i - 1])
3676 1 : ndecreases++;
3677 : }
3678 :
3679 2 : if (ndecreases == 1)
3680 : {
3681 1 : CPLDebug("GDAL_netCDF", "longitude wrap detected");
3682 4 : for (size_t i = 0; i < xdim; i++)
3683 : {
3684 3 : if (pdfXCoord[i] > pdfXCoord[xdim - 1])
3685 1 : pdfXCoord[i] -= 360;
3686 : }
3687 : }
3688 : }
3689 :
3690 190 : const double dfSpacingBegin = pdfXCoord[1] - pdfXCoord[0];
3691 190 : const double dfSpacingMiddle =
3692 190 : pdfXCoord[xdim / 2 + 1] - pdfXCoord[xdim / 2];
3693 190 : const double dfSpacingLast =
3694 190 : pdfXCoord[xdim - 1] - pdfXCoord[xdim - 2];
3695 :
3696 190 : CPLDebug("GDAL_netCDF",
3697 : "xdim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
3698 : "dfSpacingLast: %f",
3699 : static_cast<long>(xdim), dfSpacingBegin, dfSpacingMiddle,
3700 : dfSpacingLast);
3701 : #ifdef NCDF_DEBUG
3702 : CPLDebug("GDAL_netCDF", "xcoords: %f %f %f %f %f %f", pdfXCoord[0],
3703 : pdfXCoord[1], pdfXCoord[xdim / 2],
3704 : pdfXCoord[(xdim / 2) + 1], pdfXCoord[xdim - 2],
3705 : pdfXCoord[xdim - 1]);
3706 : #endif
3707 :
3708 : // ftp://ftp.cdc.noaa.gov/Datasets/NARR/Dailies/monolevel/vwnd.10m.2015.nc
3709 : // requires a 0.02% tolerance, so let's settle for 0.05%
3710 :
3711 : // For float variables, increase to 0.2% (as seen in
3712 : // https://github.com/OSGeo/gdal/issues/3663)
3713 190 : const double dfEpsRel =
3714 190 : nc_var_dimx_datatype == NC_FLOAT ? 0.002 : 0.0005;
3715 :
3716 : const double dfEps =
3717 : dfEpsRel *
3718 380 : std::max(fabs(dfSpacingBegin),
3719 190 : std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
3720 374 : if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
3721 374 : IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
3722 184 : IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
3723 : {
3724 184 : bLonSpacingOK = true;
3725 : }
3726 6 : else if (CPLTestBool(CPLGetConfigOption(
3727 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
3728 : {
3729 0 : bLonSpacingOK = true;
3730 0 : CPLDebug(
3731 : "GDAL_netCDF",
3732 : "Longitude/X is not equally spaced, but will be considered "
3733 : "as such because of "
3734 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
3735 : }
3736 : }
3737 :
3738 218 : if (bLonSpacingOK == false)
3739 : {
3740 6 : CPLDebug(
3741 : "GDAL_netCDF", "%s",
3742 : "Longitude/X is not equally spaced (with a 0.05% tolerance). "
3743 : "You may set the "
3744 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
3745 : "option to YES to ignore this check");
3746 : }
3747 :
3748 : // Check Latitude.
3749 218 : bool bLatSpacingOK = false;
3750 :
3751 218 : if (ydim == 2)
3752 : {
3753 48 : bLatSpacingOK = true;
3754 : }
3755 : else
3756 : {
3757 170 : const double dfSpacingBegin = pdfYCoord[1] - pdfYCoord[0];
3758 170 : const double dfSpacingMiddle =
3759 170 : pdfYCoord[ydim / 2 + 1] - pdfYCoord[ydim / 2];
3760 :
3761 170 : const double dfSpacingLast =
3762 170 : pdfYCoord[ydim - 1] - pdfYCoord[ydim - 2];
3763 :
3764 170 : CPLDebug("GDAL_netCDF",
3765 : "ydim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
3766 : "dfSpacingLast: %f",
3767 : (long)ydim, dfSpacingBegin, dfSpacingMiddle,
3768 : dfSpacingLast);
3769 : #ifdef NCDF_DEBUG
3770 : CPLDebug("GDAL_netCDF", "ycoords: %f %f %f %f %f %f", pdfYCoord[0],
3771 : pdfYCoord[1], pdfYCoord[ydim / 2],
3772 : pdfYCoord[(ydim / 2) + 1], pdfYCoord[ydim - 2],
3773 : pdfYCoord[ydim - 1]);
3774 : #endif
3775 :
3776 170 : const double dfEpsRel =
3777 170 : nc_var_dimy_datatype == NC_FLOAT ? 0.002 : 0.0005;
3778 :
3779 : const double dfEps =
3780 : dfEpsRel *
3781 340 : std::max(fabs(dfSpacingBegin),
3782 170 : std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
3783 338 : if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
3784 338 : IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
3785 159 : IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
3786 : {
3787 159 : bLatSpacingOK = true;
3788 : }
3789 11 : else if (CPLTestBool(CPLGetConfigOption(
3790 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
3791 : {
3792 0 : bLatSpacingOK = true;
3793 0 : CPLDebug(
3794 : "GDAL_netCDF",
3795 : "Latitude/Y is not equally spaced, but will be considered "
3796 : "as such because of "
3797 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
3798 : }
3799 11 : else if (!oSRS.IsProjected() &&
3800 11 : fabs(dfSpacingBegin - dfSpacingLast) <= 0.1 &&
3801 30 : fabs(dfSpacingBegin - dfSpacingMiddle) <= 0.1 &&
3802 8 : fabs(dfSpacingMiddle - dfSpacingLast) <= 0.1)
3803 : {
3804 8 : bLatSpacingOK = true;
3805 8 : CPLError(CE_Warning, CPLE_AppDefined,
3806 : "Latitude grid not spaced evenly. "
3807 : "Setting projection for grid spacing is "
3808 : "within 0.1 degrees threshold.");
3809 :
3810 8 : CPLDebug("GDAL_netCDF",
3811 : "Latitude grid not spaced evenly, but within 0.1 "
3812 : "degree threshold (probably a Gaussian grid). "
3813 : "Saving original latitude values in Y_VALUES "
3814 : "geolocation metadata");
3815 8 : Set1DGeolocation(nGroupDimYID, nVarDimYID, "Y");
3816 : }
3817 :
3818 170 : if (bLatSpacingOK == false)
3819 : {
3820 3 : CPLDebug(
3821 : "GDAL_netCDF", "%s",
3822 : "Latitude/Y is not equally spaced (with a 0.05% "
3823 : "tolerance). "
3824 : "You may set the "
3825 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
3826 : "option to YES to ignore this check");
3827 : }
3828 : }
3829 :
3830 218 : if (bLonSpacingOK && bLatSpacingOK)
3831 : {
3832 : // We have gridded data so we can set the Georeferencing info.
3833 :
3834 : // Enable GeoTransform.
3835 :
3836 : // In the following "actual_range" and "node_offset"
3837 : // are attributes used by netCDF files created by GMT.
3838 : // If we find them we know how to proceed. Else, use
3839 : // the original algorithm.
3840 211 : bGotCfGT = true;
3841 :
3842 211 : int node_offset = 0;
3843 211 : NCDFResolveAttInt(nGroupId, NC_GLOBAL, "node_offset", &node_offset);
3844 :
3845 211 : double adfActualRange[2] = {0.0, 0.0};
3846 211 : double xMinMax[2] = {0.0, 0.0};
3847 211 : double yMinMax[2] = {0.0, 0.0};
3848 :
3849 : const auto RoundMinMaxForFloatVals =
3850 58 : [](double &dfMin, double &dfMax, int nIntervals)
3851 : {
3852 : // Helps for a case where longitudes range from
3853 : // -179.99 to 180.0 with a 0.01 degree spacing.
3854 : // However as this is encoded in a float array,
3855 : // -179.99 is actually read as -179.99000549316406 as
3856 : // a double. Try to detect that and correct the rounding
3857 :
3858 87 : const auto IsAlmostInteger = [](double dfVal)
3859 : {
3860 87 : constexpr double THRESHOLD_INTEGER = 1e-3;
3861 87 : return std::fabs(dfVal - std::round(dfVal)) <=
3862 87 : THRESHOLD_INTEGER;
3863 : };
3864 :
3865 58 : const double dfSpacing = (dfMax - dfMin) / nIntervals;
3866 58 : if (dfSpacing > 0)
3867 : {
3868 47 : const double dfInvSpacing = 1.0 / dfSpacing;
3869 47 : if (IsAlmostInteger(dfInvSpacing))
3870 : {
3871 20 : const double dfRoundedSpacing =
3872 20 : 1.0 / std::round(dfInvSpacing);
3873 20 : const double dfMinDivRoundedSpacing =
3874 20 : dfMin / dfRoundedSpacing;
3875 20 : const double dfMaxDivRoundedSpacing =
3876 20 : dfMax / dfRoundedSpacing;
3877 40 : if (IsAlmostInteger(dfMinDivRoundedSpacing) &&
3878 20 : IsAlmostInteger(dfMaxDivRoundedSpacing))
3879 : {
3880 20 : const double dfRoundedMin =
3881 20 : std::round(dfMinDivRoundedSpacing) *
3882 : dfRoundedSpacing;
3883 20 : const double dfRoundedMax =
3884 20 : std::round(dfMaxDivRoundedSpacing) *
3885 : dfRoundedSpacing;
3886 20 : if (static_cast<float>(dfMin) ==
3887 20 : static_cast<float>(dfRoundedMin) &&
3888 8 : static_cast<float>(dfMax) ==
3889 8 : static_cast<float>(dfRoundedMax))
3890 : {
3891 7 : dfMin = dfRoundedMin;
3892 7 : dfMax = dfRoundedMax;
3893 : }
3894 : }
3895 : }
3896 : }
3897 58 : };
3898 :
3899 211 : if (!nc_get_att_double(nGroupDimXID, nVarDimXID, "actual_range",
3900 : adfActualRange))
3901 : {
3902 3 : xMinMax[0] = adfActualRange[0];
3903 3 : xMinMax[1] = adfActualRange[1];
3904 :
3905 : // Present xMinMax[] in the same order as padfXCoord
3906 3 : if ((xMinMax[0] - xMinMax[1]) *
3907 3 : (pdfXCoord[0] - pdfXCoord[xdim - 1]) <
3908 : 0)
3909 : {
3910 0 : std::swap(xMinMax[0], xMinMax[1]);
3911 : }
3912 : }
3913 : else
3914 : {
3915 208 : xMinMax[0] = pdfXCoord[0];
3916 208 : xMinMax[1] = pdfXCoord[xdim - 1];
3917 208 : node_offset = 0;
3918 :
3919 208 : if (nc_var_dimx_datatype == NC_FLOAT)
3920 : {
3921 29 : RoundMinMaxForFloatVals(xMinMax[0], xMinMax[1],
3922 29 : poDS->nRasterXSize - 1);
3923 : }
3924 : }
3925 :
3926 211 : if (!nc_get_att_double(nGroupDimYID, nVarDimYID, "actual_range",
3927 : adfActualRange))
3928 : {
3929 3 : yMinMax[0] = adfActualRange[0];
3930 3 : yMinMax[1] = adfActualRange[1];
3931 :
3932 : // Present yMinMax[] in the same order as pdfYCoord
3933 3 : if ((yMinMax[0] - yMinMax[1]) *
3934 3 : (pdfYCoord[0] - pdfYCoord[ydim - 1]) <
3935 : 0)
3936 : {
3937 1 : std::swap(yMinMax[0], yMinMax[1]);
3938 : }
3939 : }
3940 : else
3941 : {
3942 208 : yMinMax[0] = pdfYCoord[0];
3943 208 : yMinMax[1] = pdfYCoord[ydim - 1];
3944 208 : node_offset = 0;
3945 :
3946 208 : if (nc_var_dimy_datatype == NC_FLOAT)
3947 : {
3948 29 : RoundMinMaxForFloatVals(yMinMax[0], yMinMax[1],
3949 29 : poDS->nRasterYSize - 1);
3950 : }
3951 : }
3952 :
3953 211 : double dfCoordOffset = 0.0;
3954 211 : double dfCoordScale = 1.0;
3955 211 : if (!nc_get_att_double(nGroupId, nVarDimXID, CF_ADD_OFFSET,
3956 215 : &dfCoordOffset) &&
3957 4 : !nc_get_att_double(nGroupId, nVarDimXID, CF_SCALE_FACTOR,
3958 : &dfCoordScale))
3959 : {
3960 4 : xMinMax[0] = dfCoordOffset + xMinMax[0] * dfCoordScale;
3961 4 : xMinMax[1] = dfCoordOffset + xMinMax[1] * dfCoordScale;
3962 : }
3963 :
3964 211 : if (!nc_get_att_double(nGroupId, nVarDimYID, CF_ADD_OFFSET,
3965 215 : &dfCoordOffset) &&
3966 4 : !nc_get_att_double(nGroupId, nVarDimYID, CF_SCALE_FACTOR,
3967 : &dfCoordScale))
3968 : {
3969 4 : yMinMax[0] = dfCoordOffset + yMinMax[0] * dfCoordScale;
3970 4 : yMinMax[1] = dfCoordOffset + yMinMax[1] * dfCoordScale;
3971 : }
3972 :
3973 : // Check for reverse order of y-coordinate.
3974 211 : if (!bSwitchedXY)
3975 : {
3976 209 : poDS->bBottomUp = (yMinMax[0] <= yMinMax[1]);
3977 209 : CPLDebug("GDAL_netCDF", "set bBottomUp = %d from Y axis",
3978 209 : static_cast<int>(poDS->bBottomUp));
3979 209 : if (!poDS->bBottomUp)
3980 : {
3981 32 : std::swap(yMinMax[0], yMinMax[1]);
3982 : }
3983 : }
3984 :
3985 : // Geostationary satellites can specify units in (micro)radians
3986 : // So we check if they do, and if so convert to linear units
3987 : // (meters)
3988 211 : const char *pszProjName = oSRS.GetAttrValue("PROJECTION");
3989 211 : if (pszProjName != nullptr)
3990 : {
3991 24 : if (EQUAL(pszProjName, SRS_PT_GEOSTATIONARY_SATELLITE))
3992 : {
3993 : double satelliteHeight =
3994 3 : oSRS.GetProjParm(SRS_PP_SATELLITE_HEIGHT, 1.0);
3995 3 : size_t nAttlen = 0;
3996 : char szUnits[NC_MAX_NAME + 1];
3997 3 : szUnits[0] = '\0';
3998 3 : nc_type nAttype = NC_NAT;
3999 3 : nc_inq_att(nGroupId, nVarDimXID, "units", &nAttype,
4000 : &nAttlen);
4001 6 : if (nAttlen < sizeof(szUnits) &&
4002 3 : nc_get_att_text(nGroupId, nVarDimXID, "units",
4003 : szUnits) == NC_NOERR)
4004 : {
4005 3 : szUnits[nAttlen] = '\0';
4006 3 : if (EQUAL(szUnits, "microradian"))
4007 : {
4008 1 : xMinMax[0] =
4009 1 : xMinMax[0] * satelliteHeight * 0.000001;
4010 1 : xMinMax[1] =
4011 1 : xMinMax[1] * satelliteHeight * 0.000001;
4012 : }
4013 2 : else if (EQUAL(szUnits, "rad") ||
4014 1 : EQUAL(szUnits, "radian"))
4015 : {
4016 2 : xMinMax[0] = xMinMax[0] * satelliteHeight;
4017 2 : xMinMax[1] = xMinMax[1] * satelliteHeight;
4018 : }
4019 : }
4020 3 : szUnits[0] = '\0';
4021 3 : nc_inq_att(nGroupId, nVarDimYID, "units", &nAttype,
4022 : &nAttlen);
4023 6 : if (nAttlen < sizeof(szUnits) &&
4024 3 : nc_get_att_text(nGroupId, nVarDimYID, "units",
4025 : szUnits) == NC_NOERR)
4026 : {
4027 3 : szUnits[nAttlen] = '\0';
4028 3 : if (EQUAL(szUnits, "microradian"))
4029 : {
4030 1 : yMinMax[0] =
4031 1 : yMinMax[0] * satelliteHeight * 0.000001;
4032 1 : yMinMax[1] =
4033 1 : yMinMax[1] * satelliteHeight * 0.000001;
4034 : }
4035 2 : else if (EQUAL(szUnits, "rad") ||
4036 1 : EQUAL(szUnits, "radian"))
4037 : {
4038 2 : yMinMax[0] = yMinMax[0] * satelliteHeight;
4039 2 : yMinMax[1] = yMinMax[1] * satelliteHeight;
4040 : }
4041 : }
4042 : }
4043 : }
4044 :
4045 211 : adfTempGeoTransform[0] = xMinMax[0];
4046 211 : adfTempGeoTransform[1] = (xMinMax[1] - xMinMax[0]) /
4047 211 : (poDS->nRasterXSize + (node_offset - 1));
4048 211 : adfTempGeoTransform[2] = 0;
4049 211 : if (bSwitchedXY)
4050 : {
4051 2 : adfTempGeoTransform[3] = yMinMax[0];
4052 2 : adfTempGeoTransform[4] = 0;
4053 2 : adfTempGeoTransform[5] =
4054 2 : (yMinMax[1] - yMinMax[0]) /
4055 2 : (poDS->nRasterYSize + (node_offset - 1));
4056 : }
4057 : else
4058 : {
4059 209 : adfTempGeoTransform[3] = yMinMax[1];
4060 209 : adfTempGeoTransform[4] = 0;
4061 209 : adfTempGeoTransform[5] =
4062 209 : (yMinMax[0] - yMinMax[1]) /
4063 209 : (poDS->nRasterYSize + (node_offset - 1));
4064 : }
4065 :
4066 : // Compute the center of the pixel.
4067 211 : if (!node_offset)
4068 : {
4069 : // Otherwise its already the pixel center.
4070 211 : adfTempGeoTransform[0] -= (adfTempGeoTransform[1] / 2);
4071 211 : adfTempGeoTransform[3] -= (adfTempGeoTransform[5] / 2);
4072 : }
4073 : }
4074 :
4075 : const auto AreSRSEqualThroughProj4String =
4076 2 : [](const OGRSpatialReference &oSRS1,
4077 : const OGRSpatialReference &oSRS2)
4078 : {
4079 2 : char *pszProj4Str1 = nullptr;
4080 2 : oSRS1.exportToProj4(&pszProj4Str1);
4081 :
4082 2 : char *pszProj4Str2 = nullptr;
4083 2 : oSRS2.exportToProj4(&pszProj4Str2);
4084 :
4085 : {
4086 2 : char *pszTmp = strstr(pszProj4Str1, "+datum=");
4087 2 : if (pszTmp)
4088 0 : memcpy(pszTmp, "+ellps=", strlen("+ellps="));
4089 : }
4090 :
4091 : {
4092 2 : char *pszTmp = strstr(pszProj4Str2, "+datum=");
4093 2 : if (pszTmp)
4094 2 : memcpy(pszTmp, "+ellps=", strlen("+ellps="));
4095 : }
4096 :
4097 2 : bool bRet = false;
4098 2 : if (pszProj4Str1 && pszProj4Str2 &&
4099 2 : EQUAL(pszProj4Str1, pszProj4Str2))
4100 : {
4101 1 : bRet = true;
4102 : }
4103 :
4104 2 : CPLFree(pszProj4Str1);
4105 2 : CPLFree(pszProj4Str2);
4106 2 : return bRet;
4107 : };
4108 :
4109 218 : if (dfLinearUnitsConvFactor != 1.0)
4110 : {
4111 35 : for (int i = 0; i < 6; ++i)
4112 30 : adfTempGeoTransform[i] *= dfLinearUnitsConvFactor;
4113 :
4114 5 : if (paosRemovedMDItems)
4115 : {
4116 : char szVarNameX[NC_MAX_NAME + 1];
4117 5 : CPL_IGNORE_RET_VAL(
4118 5 : nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
4119 :
4120 : char szVarNameY[NC_MAX_NAME + 1];
4121 5 : CPL_IGNORE_RET_VAL(
4122 5 : nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
4123 :
4124 5 : paosRemovedMDItems->push_back(
4125 : CPLSPrintf("%s#units", szVarNameX));
4126 5 : paosRemovedMDItems->push_back(
4127 : CPLSPrintf("%s#units", szVarNameY));
4128 : }
4129 : }
4130 :
4131 : // If there is a global "geospatial_bounds_crs" attribute, check that it
4132 : // is consistent with the SRS, and if so, use it as the SRS
4133 : const char *pszGBCRS =
4134 218 : FetchAttr(nGroupId, NC_GLOBAL, "geospatial_bounds_crs");
4135 218 : if (pszGBCRS && STARTS_WITH(pszGBCRS, "EPSG:"))
4136 : {
4137 4 : OGRSpatialReference oSRSFromGBCRS;
4138 2 : oSRSFromGBCRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
4139 2 : if (oSRSFromGBCRS.SetFromUserInput(
4140 : pszGBCRS,
4141 : OGRSpatialReference::
4142 4 : SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE &&
4143 2 : AreSRSEqualThroughProj4String(oSRS, oSRSFromGBCRS))
4144 : {
4145 1 : oSRS = std::move(oSRSFromGBCRS);
4146 1 : SetSpatialRefNoUpdate(&oSRS);
4147 : }
4148 : }
4149 :
4150 218 : CPLFree(pdfXCoord);
4151 218 : CPLFree(pdfYCoord);
4152 : } // end if(has dims)
4153 :
4154 : // Process custom GeoTransform GDAL value.
4155 495 : if (!EQUAL(pszGridMappingValue, "") && !bGotCfGT)
4156 : {
4157 : // TODO: Read the GT values and detect for conflict with CF.
4158 : // This could resolve the GT precision loss issue.
4159 :
4160 91 : if (pszGeoTransform != nullptr)
4161 : {
4162 : char **papszGeoTransform =
4163 13 : CSLTokenizeString2(pszGeoTransform, " ", CSLT_HONOURSTRINGS);
4164 13 : if (CSLCount(papszGeoTransform) == 6)
4165 : {
4166 13 : bGotGdalGT = true;
4167 91 : for (int i = 0; i < 6; i++)
4168 78 : adfTempGeoTransform[i] = CPLAtof(papszGeoTransform[i]);
4169 : }
4170 13 : CSLDestroy(papszGeoTransform);
4171 : }
4172 : else
4173 : {
4174 : // Look for corner array values.
4175 : // CPLDebug("GDAL_netCDF",
4176 : // "looking for geotransform corners");
4177 78 : bool bGotNN = false;
4178 78 : double dfNN = FetchCopyParam(pszGridMappingValue,
4179 : "Northernmost_Northing", 0, &bGotNN);
4180 :
4181 78 : bool bGotSN = false;
4182 78 : double dfSN = FetchCopyParam(pszGridMappingValue,
4183 : "Southernmost_Northing", 0, &bGotSN);
4184 :
4185 78 : bool bGotEE = false;
4186 78 : double dfEE = FetchCopyParam(pszGridMappingValue,
4187 : "Easternmost_Easting", 0, &bGotEE);
4188 :
4189 78 : bool bGotWE = false;
4190 78 : double dfWE = FetchCopyParam(pszGridMappingValue,
4191 : "Westernmost_Easting", 0, &bGotWE);
4192 :
4193 : // Only set the GeoTransform if we got all the values.
4194 78 : if (bGotNN && bGotSN && bGotEE && bGotWE)
4195 : {
4196 0 : bGotGdalGT = true;
4197 :
4198 0 : adfTempGeoTransform[0] = dfWE;
4199 0 : adfTempGeoTransform[1] =
4200 0 : (dfEE - dfWE) / (poDS->GetRasterXSize() - 1);
4201 0 : adfTempGeoTransform[2] = 0.0;
4202 0 : adfTempGeoTransform[3] = dfNN;
4203 0 : adfTempGeoTransform[4] = 0.0;
4204 0 : adfTempGeoTransform[5] =
4205 0 : (dfSN - dfNN) / (poDS->GetRasterYSize() - 1);
4206 : // Compute the center of the pixel.
4207 0 : adfTempGeoTransform[0] = dfWE - (adfTempGeoTransform[1] / 2);
4208 0 : adfTempGeoTransform[3] = dfNN - (adfTempGeoTransform[5] / 2);
4209 : }
4210 : } // (pszGeoTransform != NULL)
4211 :
4212 91 : if (bGotGdalSRS && !bGotGdalGT)
4213 72 : CPLDebug("GDAL_netCDF", "Got SRS but no geotransform from GDAL!");
4214 : }
4215 :
4216 495 : if (!pszWKT && !bGotCfSRS)
4217 : {
4218 : // Some netCDF files have a srid attribute (#6613) like
4219 : // urn:ogc:def:crs:EPSG::6931
4220 284 : const char *pszSRID = FetchAttr(pszGridMappingValue, "srid");
4221 284 : if (pszSRID != nullptr)
4222 : {
4223 0 : oSRS.Clear();
4224 0 : if (oSRS.SetFromUserInput(
4225 : pszSRID,
4226 : OGRSpatialReference::
4227 0 : SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE)
4228 : {
4229 0 : char *pszWKTExport = nullptr;
4230 0 : CPLDebug("GDAL_netCDF", "Got SRS from %s", pszSRID);
4231 0 : oSRS.exportToWkt(&pszWKTExport);
4232 0 : if (returnProjStr != nullptr)
4233 : {
4234 0 : (*returnProjStr) = std::string(pszWKTExport);
4235 : }
4236 : else
4237 : {
4238 0 : m_bAddedProjectionVarsDefs = true;
4239 0 : m_bAddedProjectionVarsData = true;
4240 0 : SetSpatialRefNoUpdate(&oSRS);
4241 : }
4242 0 : CPLFree(pszWKTExport);
4243 : }
4244 : }
4245 : }
4246 :
4247 495 : CPLFree(pszGridMappingValue);
4248 :
4249 495 : if (bReadSRSOnly)
4250 153 : return;
4251 :
4252 : // Determines the SRS to be used by the geolocation array, if any
4253 684 : std::string osGeolocWKT = SRS_WKT_WGS84_LAT_LONG;
4254 342 : if (!m_oSRS.IsEmpty())
4255 : {
4256 252 : OGRSpatialReference oGeogCRS;
4257 126 : oGeogCRS.CopyGeogCSFrom(&m_oSRS);
4258 126 : char *pszWKTTmp = nullptr;
4259 126 : const char *const apszOptions[] = {"FORMAT=WKT2_2019", nullptr};
4260 126 : if (oGeogCRS.exportToWkt(&pszWKTTmp, apszOptions) == OGRERR_NONE)
4261 : {
4262 126 : osGeolocWKT = pszWKTTmp;
4263 : }
4264 126 : CPLFree(pszWKTTmp);
4265 : }
4266 :
4267 : // Process geolocation arrays from CF "coordinates" attribute.
4268 684 : std::string osGeolocXName, osGeolocYName;
4269 342 : if (ProcessCFGeolocation(nGroupId, nVarId, osGeolocWKT, osGeolocXName,
4270 342 : osGeolocYName))
4271 : {
4272 52 : bool bCanCancelGT = true;
4273 52 : if ((nVarDimXID != -1) && (nVarDimYID != -1))
4274 : {
4275 : char szVarNameX[NC_MAX_NAME + 1];
4276 44 : CPL_IGNORE_RET_VAL(
4277 44 : nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
4278 : char szVarNameY[NC_MAX_NAME + 1];
4279 44 : CPL_IGNORE_RET_VAL(
4280 44 : nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
4281 44 : bCanCancelGT =
4282 44 : !(osGeolocXName == szVarNameX && osGeolocYName == szVarNameY);
4283 : }
4284 86 : if (bCanCancelGT && !m_oSRS.IsGeographic() && !m_oSRS.IsProjected() &&
4285 34 : !bSwitchedXY)
4286 : {
4287 32 : bGotCfGT = false;
4288 : }
4289 : }
4290 119 : else if (!bGotCfGT && !bReadSRSOnly && (nVarDimXID != -1) &&
4291 412 : (nVarDimYID != -1) && xdim > 0 && ydim > 0 &&
4292 3 : ((!bSwitchedXY &&
4293 3 : NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
4294 1 : NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr)) ||
4295 2 : (bSwitchedXY &&
4296 0 : NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
4297 0 : NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr))))
4298 : {
4299 : // Case of autotest/gdrivers/data/netcdf/GLMELT_4X5.OCN.nc
4300 : // which is indexed by lat, lon variables, but lat has irregular
4301 : // spacing.
4302 1 : const char *pszGeolocXFullName = poDS->papszDimName[poDS->nXDimID];
4303 1 : const char *pszGeolocYFullName = poDS->papszDimName[poDS->nYDimID];
4304 1 : if (bSwitchedXY)
4305 : {
4306 0 : std::swap(pszGeolocXFullName, pszGeolocYFullName);
4307 0 : GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", "GEOLOCATION");
4308 : }
4309 :
4310 1 : CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
4311 : pszGeolocXFullName, pszGeolocYFullName);
4312 :
4313 1 : GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
4314 : "GEOLOCATION");
4315 :
4316 2 : CPLString osTMP;
4317 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4318 1 : pszGeolocXFullName);
4319 :
4320 1 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
4321 1 : GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
4322 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4323 1 : pszGeolocYFullName);
4324 :
4325 1 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
4326 1 : GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
4327 :
4328 1 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
4329 1 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
4330 :
4331 1 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
4332 1 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
4333 :
4334 1 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
4335 : "PIXEL_CENTER", "GEOLOCATION");
4336 : }
4337 :
4338 : // Set GeoTransform if we got a complete one - after projection has been set
4339 342 : if (bGotCfGT || bGotGdalGT)
4340 : {
4341 193 : m_bAddedProjectionVarsDefs = true;
4342 193 : m_bAddedProjectionVarsData = true;
4343 193 : SetGeoTransformNoUpdate(adfTempGeoTransform);
4344 : }
4345 :
4346 : // Debugging reports.
4347 342 : CPLDebug("GDAL_netCDF",
4348 : "bGotGeogCS=%d bGotCfSRS=%d bGotCfGT=%d bGotCfWktSRS=%d "
4349 : "bGotGdalSRS=%d bGotGdalGT=%d",
4350 : static_cast<int>(bGotGeogCS), static_cast<int>(bGotCfSRS),
4351 : static_cast<int>(bGotCfGT), static_cast<int>(bGotCfWktSRS),
4352 : static_cast<int>(bGotGdalSRS), static_cast<int>(bGotGdalGT));
4353 :
4354 342 : if (!bGotCfGT && !bGotGdalGT)
4355 149 : CPLDebug("GDAL_netCDF", "did not get geotransform from CF nor GDAL!");
4356 :
4357 342 : if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfGT && !bGotCfWktSRS)
4358 149 : CPLDebug("GDAL_netCDF", "did not get projection from CF nor GDAL!");
4359 :
4360 : // wish of 6195
4361 : // we don't have CS/SRS, but we do have GT, and we live in -180,360 -90,90
4362 342 : if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfWktSRS)
4363 : {
4364 216 : if (bGotCfGT || bGotGdalGT)
4365 : {
4366 134 : bool bAssumedLongLat = CPLTestBool(CSLFetchNameValueDef(
4367 67 : papszOpenOptions, "ASSUME_LONGLAT",
4368 : CPLGetConfigOption("GDAL_NETCDF_ASSUME_LONGLAT", "NO")));
4369 :
4370 2 : if (bAssumedLongLat && adfTempGeoTransform[0] >= -180 &&
4371 2 : adfTempGeoTransform[0] < 360 &&
4372 4 : (adfTempGeoTransform[0] +
4373 2 : adfTempGeoTransform[1] * poDS->GetRasterXSize()) <= 360 &&
4374 71 : adfTempGeoTransform[3] <= 90 && adfTempGeoTransform[3] > -90 &&
4375 4 : (adfTempGeoTransform[3] +
4376 2 : adfTempGeoTransform[5] * poDS->GetRasterYSize()) >= -90)
4377 : {
4378 :
4379 2 : poDS->bIsGeographic = true;
4380 2 : char *pszTempProjection = nullptr;
4381 : // seems odd to use 4326 so OGC:CRS84
4382 2 : oSRS.SetFromUserInput("OGC:CRS84");
4383 2 : oSRS.exportToWkt(&pszTempProjection);
4384 2 : if (returnProjStr != nullptr)
4385 : {
4386 0 : (*returnProjStr) = std::string(pszTempProjection);
4387 : }
4388 : else
4389 : {
4390 2 : m_bAddedProjectionVarsDefs = true;
4391 2 : m_bAddedProjectionVarsData = true;
4392 2 : SetSpatialRefNoUpdate(&oSRS);
4393 : }
4394 2 : CPLFree(pszTempProjection);
4395 :
4396 2 : CPLDebug("netCDF",
4397 : "Assumed Longitude Latitude CRS 'OGC:CRS84' because "
4398 : "none otherwise available and geotransform within "
4399 : "suitable bounds. "
4400 : "Set GDAL_NETCDF_ASSUME_LONGLAT=NO as configuration "
4401 : "option or "
4402 : " ASSUME_LONGLAT=NO as open option to bypass this "
4403 : "assumption.");
4404 : }
4405 : }
4406 : }
4407 :
4408 : // Search for Well-known GeogCS if got only CF WKT
4409 : // Disabled for now, as a named datum also include control points
4410 : // (see mailing list and bug#4281
4411 : // For example, WGS84 vs. GDA94 (EPSG:3577) - AEA in netcdf_cf.py
4412 :
4413 : // Disabled for now, but could be set in a config option.
4414 : #if 0
4415 : bool bLookForWellKnownGCS = false; // This could be a Config Option.
4416 :
4417 : if( bLookForWellKnownGCS && bGotCfSRS && !bGotGdalSRS )
4418 : {
4419 : // ET - Could use a more exhaustive method by scanning all EPSG codes in
4420 : // data/gcs.csv as proposed by Even in the gdal-dev mailing list "help
4421 : // for comparing two WKT".
4422 : // This code could be contributed to a new function.
4423 : // OGRSpatialReference * OGRSpatialReference::FindMatchingGeogCS(
4424 : // const OGRSpatialReference *poOther) */
4425 : CPLDebug("GDAL_netCDF", "Searching for Well-known GeogCS");
4426 : const char *pszWKGCSList[] = { "WGS84", "WGS72", "NAD27", "NAD83" };
4427 : char *pszWKGCS = NULL;
4428 : oSRS.exportToPrettyWkt(&pszWKGCS);
4429 : for( size_t i = 0; i < sizeof(pszWKGCSList) / 8; i++ )
4430 : {
4431 : pszWKGCS = CPLStrdup(pszWKGCSList[i]);
4432 : OGRSpatialReference oSRSTmp;
4433 : oSRSTmp.SetWellKnownGeogCS(pszWKGCSList[i]);
4434 : // Set datum to unknown, bug #4281.
4435 : if( oSRSTmp.GetAttrNode("DATUM" ) )
4436 : oSRSTmp.GetAttrNode("DATUM")->GetChild(0)->SetValue("unknown");
4437 : // Could use OGRSpatialReference::StripCTParms(), but let's keep
4438 : // TOWGS84.
4439 : oSRSTmp.GetRoot()->StripNodes("AXIS");
4440 : oSRSTmp.GetRoot()->StripNodes("AUTHORITY");
4441 : oSRSTmp.GetRoot()->StripNodes("EXTENSION");
4442 :
4443 : oSRSTmp.exportToPrettyWkt(&pszWKGCS);
4444 : if( oSRS.IsSameGeogCS(&oSRSTmp) )
4445 : {
4446 : oSRS.SetWellKnownGeogCS(pszWKGCSList[i]);
4447 : oSRS.exportToWkt(&(pszTempProjection));
4448 : SetProjection(pszTempProjection);
4449 : CPLFree(pszTempProjection);
4450 : }
4451 : }
4452 : }
4453 : #endif
4454 : }
4455 :
4456 110 : void netCDFDataset::SetProjectionFromVar(int nGroupId, int nVarId,
4457 : bool bReadSRSOnly)
4458 : {
4459 110 : SetProjectionFromVar(nGroupId, nVarId, bReadSRSOnly, nullptr, nullptr,
4460 : nullptr, nullptr);
4461 110 : }
4462 :
4463 279 : bool netCDFDataset::ProcessNASAL2OceanGeoLocation(int nGroupId, int nVarId)
4464 : {
4465 : // Cf https://oceancolor.gsfc.nasa.gov/docs/format/l2nc/
4466 : // and https://github.com/OSGeo/gdal/issues/7605
4467 :
4468 : // Check for a structure like:
4469 : /* netcdf SNPP_VIIRS.20230406T024200.L2.OC.NRT {
4470 : dimensions:
4471 : number_of_lines = 3248 ;
4472 : pixels_per_line = 3200 ;
4473 : [...]
4474 : pixel_control_points = 3200 ;
4475 : [...]
4476 : group: geophysical_data {
4477 : variables:
4478 : short aot_862(number_of_lines, pixels_per_line) ; <-- nVarId
4479 : [...]
4480 : }
4481 : group: navigation_data {
4482 : variables:
4483 : float longitude(number_of_lines, pixel_control_points) ;
4484 : [...]
4485 : float latitude(number_of_lines, pixel_control_points) ;
4486 : [...]
4487 : }
4488 : }
4489 : */
4490 : // Note that the longitude and latitude arrays are not indexed by the
4491 : // same dimensions. Handle only the case where
4492 : // pixel_control_points == pixels_per_line
4493 : // If there was a subsampling of the geolocation arrays, we'd need to
4494 : // add more logic.
4495 :
4496 558 : std::string osGroupName;
4497 279 : osGroupName.resize(NC_MAX_NAME);
4498 279 : NCDF_ERR(nc_inq_grpname(nGroupId, &osGroupName[0]));
4499 279 : osGroupName.resize(strlen(osGroupName.data()));
4500 279 : if (osGroupName != "geophysical_data")
4501 278 : return false;
4502 :
4503 1 : int nVarDims = 0;
4504 1 : NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
4505 1 : if (nVarDims != 2)
4506 0 : return false;
4507 :
4508 1 : int nNavigationDataGrpId = 0;
4509 1 : if (nc_inq_grp_ncid(cdfid, "navigation_data", &nNavigationDataGrpId) !=
4510 : NC_NOERR)
4511 0 : return false;
4512 :
4513 : std::array<int, 2> anVarDimIds;
4514 1 : NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
4515 :
4516 1 : int nLongitudeId = 0;
4517 1 : int nLatitudeId = 0;
4518 1 : if (nc_inq_varid(nNavigationDataGrpId, "longitude", &nLongitudeId) !=
4519 2 : NC_NOERR ||
4520 1 : nc_inq_varid(nNavigationDataGrpId, "latitude", &nLatitudeId) !=
4521 : NC_NOERR)
4522 : {
4523 0 : return false;
4524 : }
4525 :
4526 1 : int nDimsLongitude = 0;
4527 1 : NCDF_ERR(
4528 : nc_inq_varndims(nNavigationDataGrpId, nLongitudeId, &nDimsLongitude));
4529 1 : int nDimsLatitude = 0;
4530 1 : NCDF_ERR(
4531 : nc_inq_varndims(nNavigationDataGrpId, nLatitudeId, &nDimsLatitude));
4532 1 : if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
4533 : {
4534 0 : return false;
4535 : }
4536 :
4537 : std::array<int, 2> anDimLongitudeIds;
4538 1 : NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLongitudeId,
4539 : anDimLongitudeIds.data()));
4540 : std::array<int, 2> anDimLatitudeIds;
4541 1 : NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLatitudeId,
4542 : anDimLatitudeIds.data()));
4543 1 : if (anDimLongitudeIds != anDimLatitudeIds)
4544 : {
4545 0 : return false;
4546 : }
4547 :
4548 : std::array<size_t, 2> anSizeVarDimIds;
4549 : std::array<size_t, 2> anSizeLongLatIds;
4550 2 : if (!(nc_inq_dimlen(cdfid, anVarDimIds[0], &anSizeVarDimIds[0]) ==
4551 1 : NC_NOERR &&
4552 1 : nc_inq_dimlen(cdfid, anVarDimIds[1], &anSizeVarDimIds[1]) ==
4553 1 : NC_NOERR &&
4554 1 : nc_inq_dimlen(cdfid, anDimLongitudeIds[0], &anSizeLongLatIds[0]) ==
4555 1 : NC_NOERR &&
4556 1 : nc_inq_dimlen(cdfid, anDimLongitudeIds[1], &anSizeLongLatIds[1]) ==
4557 : NC_NOERR &&
4558 1 : anSizeVarDimIds == anSizeLongLatIds))
4559 : {
4560 0 : return false;
4561 : }
4562 :
4563 1 : const char *pszGeolocXFullName = "/navigation_data/longitude";
4564 1 : const char *pszGeolocYFullName = "/navigation_data/latitude";
4565 :
4566 1 : if (bSwitchedXY)
4567 : {
4568 0 : std::swap(pszGeolocXFullName, pszGeolocYFullName);
4569 0 : GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", "GEOLOCATION");
4570 : }
4571 :
4572 1 : CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
4573 : pszGeolocXFullName, pszGeolocYFullName);
4574 :
4575 1 : GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
4576 : "GEOLOCATION");
4577 :
4578 1 : CPLString osTMP;
4579 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
4580 :
4581 1 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
4582 1 : GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
4583 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
4584 :
4585 1 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
4586 1 : GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
4587 :
4588 1 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
4589 1 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
4590 :
4591 1 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
4592 1 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
4593 :
4594 1 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
4595 : "GEOLOCATION");
4596 1 : return true;
4597 : }
4598 :
4599 278 : bool netCDFDataset::ProcessNASAEMITGeoLocation(int nGroupId, int nVarId)
4600 : {
4601 : // Cf https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/
4602 :
4603 : // Check for a structure like:
4604 : /* netcdf EMIT_L2A_RFL_001_20220903T163129_2224611_012 {
4605 : dimensions:
4606 : downtrack = 1280 ;
4607 : crosstrack = 1242 ;
4608 : bands = 285 ;
4609 : [...]
4610 :
4611 : variables:
4612 : float reflectance(downtrack, crosstrack, bands) ;
4613 :
4614 : group: location {
4615 : variables:
4616 : double lon(downtrack, crosstrack) ;
4617 : lon:_FillValue = -9999. ;
4618 : lon:long_name = "Longitude (WGS-84)" ;
4619 : lon:units = "degrees east" ;
4620 : double lat(downtrack, crosstrack) ;
4621 : lat:_FillValue = -9999. ;
4622 : lat:long_name = "Latitude (WGS-84)" ;
4623 : lat:units = "degrees north" ;
4624 : } // group location
4625 :
4626 : }
4627 : or
4628 : netcdf EMIT_L2B_MIN_001_20231024T055538_2329704_040 {
4629 : dimensions:
4630 : downtrack = 1664 ;
4631 : crosstrack = 1242 ;
4632 : [...]
4633 : variables:
4634 : float group_1_band_depth(downtrack, crosstrack) ;
4635 : group_1_band_depth:_FillValue = -9999.f ;
4636 : group_1_band_depth:long_name = "Group 1 Band Depth" ;
4637 : group_1_band_depth:units = "unitless" ;
4638 : [...]
4639 : group: location {
4640 : variables:
4641 : double lon(downtrack, crosstrack) ;
4642 : lon:_FillValue = -9999. ;
4643 : lon:long_name = "Longitude (WGS-84)" ;
4644 : lon:units = "degrees east" ;
4645 : double lat(downtrack, crosstrack) ;
4646 : lat:_FillValue = -9999. ;
4647 : lat:long_name = "Latitude (WGS-84)" ;
4648 : lat:units = "degrees north" ;
4649 : }
4650 : */
4651 :
4652 278 : int nVarDims = 0;
4653 278 : NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
4654 278 : if (nVarDims != 2 && nVarDims != 3)
4655 14 : return false;
4656 :
4657 264 : int nLocationGrpId = 0;
4658 264 : if (nc_inq_grp_ncid(cdfid, "location", &nLocationGrpId) != NC_NOERR)
4659 56 : return false;
4660 :
4661 : std::array<int, 3> anVarDimIds;
4662 208 : NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
4663 208 : if (nYDimID != anVarDimIds[0] || nXDimID != anVarDimIds[1])
4664 21 : return false;
4665 :
4666 187 : int nLongitudeId = 0;
4667 187 : int nLatitudeId = 0;
4668 222 : if (nc_inq_varid(nLocationGrpId, "lon", &nLongitudeId) != NC_NOERR ||
4669 35 : nc_inq_varid(nLocationGrpId, "lat", &nLatitudeId) != NC_NOERR)
4670 : {
4671 152 : return false;
4672 : }
4673 :
4674 35 : int nDimsLongitude = 0;
4675 35 : NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLongitudeId, &nDimsLongitude));
4676 35 : int nDimsLatitude = 0;
4677 35 : NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLatitudeId, &nDimsLatitude));
4678 35 : if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
4679 : {
4680 31 : return false;
4681 : }
4682 :
4683 : std::array<int, 2> anDimLongitudeIds;
4684 4 : NCDF_ERR(nc_inq_vardimid(nLocationGrpId, nLongitudeId,
4685 : anDimLongitudeIds.data()));
4686 : std::array<int, 2> anDimLatitudeIds;
4687 4 : NCDF_ERR(
4688 : nc_inq_vardimid(nLocationGrpId, nLatitudeId, anDimLatitudeIds.data()));
4689 4 : if (anDimLongitudeIds != anDimLatitudeIds)
4690 : {
4691 0 : return false;
4692 : }
4693 :
4694 8 : if (anDimLongitudeIds[0] != anVarDimIds[0] ||
4695 4 : anDimLongitudeIds[1] != anVarDimIds[1])
4696 : {
4697 0 : return false;
4698 : }
4699 :
4700 4 : const char *pszGeolocXFullName = "/location/lon";
4701 4 : const char *pszGeolocYFullName = "/location/lat";
4702 :
4703 4 : CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
4704 : pszGeolocXFullName, pszGeolocYFullName);
4705 :
4706 4 : GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
4707 : "GEOLOCATION");
4708 :
4709 4 : CPLString osTMP;
4710 4 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
4711 :
4712 4 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
4713 4 : GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
4714 4 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
4715 :
4716 4 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
4717 4 : GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
4718 :
4719 4 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
4720 4 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
4721 :
4722 4 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
4723 4 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
4724 :
4725 4 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
4726 : "GEOLOCATION");
4727 4 : return true;
4728 : }
4729 :
4730 342 : int netCDFDataset::ProcessCFGeolocation(int nGroupId, int nVarId,
4731 : const std::string &osGeolocWKT,
4732 : std::string &osGeolocXNameOut,
4733 : std::string &osGeolocYNameOut)
4734 : {
4735 342 : bool bAddGeoloc = false;
4736 342 : char *pszCoordinates = nullptr;
4737 :
4738 : // If there is no explicit "coordinates" attribute, check if there are
4739 : // "lon" and "lat" 2D variables whose dimensions are the last
4740 : // 2 ones of the variable of interest.
4741 342 : if (NCDFGetAttr(nGroupId, nVarId, "coordinates", &pszCoordinates) !=
4742 : CE_None)
4743 : {
4744 296 : CPLFree(pszCoordinates);
4745 296 : pszCoordinates = nullptr;
4746 :
4747 296 : int nVarDims = 0;
4748 296 : NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
4749 296 : if (nVarDims >= 2)
4750 : {
4751 592 : std::vector<int> anVarDimIds(nVarDims);
4752 296 : NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
4753 :
4754 296 : int nLongitudeId = 0;
4755 296 : int nLatitudeId = 0;
4756 361 : if (nc_inq_varid(nGroupId, "lon", &nLongitudeId) == NC_NOERR &&
4757 65 : nc_inq_varid(nGroupId, "lat", &nLatitudeId) == NC_NOERR)
4758 : {
4759 65 : int nDimsLongitude = 0;
4760 65 : NCDF_ERR(
4761 : nc_inq_varndims(nGroupId, nLongitudeId, &nDimsLongitude));
4762 65 : int nDimsLatitude = 0;
4763 65 : NCDF_ERR(
4764 : nc_inq_varndims(nGroupId, nLatitudeId, &nDimsLatitude));
4765 65 : if (nDimsLongitude == 2 && nDimsLatitude == 2)
4766 : {
4767 34 : std::vector<int> anDimLongitudeIds(2);
4768 17 : NCDF_ERR(nc_inq_vardimid(nGroupId, nLongitudeId,
4769 : anDimLongitudeIds.data()));
4770 34 : std::vector<int> anDimLatitudeIds(2);
4771 17 : NCDF_ERR(nc_inq_vardimid(nGroupId, nLatitudeId,
4772 : anDimLatitudeIds.data()));
4773 17 : if (anDimLongitudeIds == anDimLatitudeIds &&
4774 34 : anVarDimIds[anVarDimIds.size() - 2] ==
4775 51 : anDimLongitudeIds[0] &&
4776 34 : anVarDimIds[anVarDimIds.size() - 1] ==
4777 17 : anDimLongitudeIds[1])
4778 : {
4779 17 : pszCoordinates = CPLStrdup("lon lat");
4780 : }
4781 : }
4782 : }
4783 : }
4784 : }
4785 :
4786 342 : if (pszCoordinates)
4787 : {
4788 : // Get X and Y geolocation names from coordinates attribute.
4789 : const CPLStringList aosCoordinates(
4790 126 : NCDFTokenizeCoordinatesAttribute(pszCoordinates));
4791 63 : if (aosCoordinates.size() >= 2)
4792 : {
4793 : char szGeolocXName[NC_MAX_NAME + 1];
4794 : char szGeolocYName[NC_MAX_NAME + 1];
4795 60 : szGeolocXName[0] = '\0';
4796 60 : szGeolocYName[0] = '\0';
4797 :
4798 : // Test that each variable is longitude/latitude.
4799 193 : for (int i = 0; i < aosCoordinates.size(); i++)
4800 : {
4801 133 : if (NCDFIsVarLongitude(nGroupId, -1, aosCoordinates[i]))
4802 : {
4803 49 : int nOtherGroupId = -1;
4804 49 : int nOtherVarId = -1;
4805 : // Check that the variable actually exists
4806 : // Needed on Sentinel-3 products
4807 49 : if (NCDFResolveVar(nGroupId, aosCoordinates[i],
4808 49 : &nOtherGroupId, &nOtherVarId) == CE_None)
4809 : {
4810 47 : snprintf(szGeolocXName, sizeof(szGeolocXName), "%s",
4811 : aosCoordinates[i]);
4812 : }
4813 : }
4814 84 : else if (NCDFIsVarLatitude(nGroupId, -1, aosCoordinates[i]))
4815 : {
4816 49 : int nOtherGroupId = -1;
4817 49 : int nOtherVarId = -1;
4818 : // Check that the variable actually exists
4819 : // Needed on Sentinel-3 products
4820 49 : if (NCDFResolveVar(nGroupId, aosCoordinates[i],
4821 49 : &nOtherGroupId, &nOtherVarId) == CE_None)
4822 : {
4823 47 : snprintf(szGeolocYName, sizeof(szGeolocYName), "%s",
4824 : aosCoordinates[i]);
4825 : }
4826 : }
4827 : }
4828 : // Add GEOLOCATION metadata.
4829 60 : if (!EQUAL(szGeolocXName, "") && !EQUAL(szGeolocYName, ""))
4830 : {
4831 47 : osGeolocXNameOut = szGeolocXName;
4832 47 : osGeolocYNameOut = szGeolocYName;
4833 :
4834 47 : char *pszGeolocXFullName = nullptr;
4835 47 : char *pszGeolocYFullName = nullptr;
4836 47 : if (NCDFResolveVarFullName(nGroupId, szGeolocXName,
4837 94 : &pszGeolocXFullName) == CE_None &&
4838 47 : NCDFResolveVarFullName(nGroupId, szGeolocYName,
4839 : &pszGeolocYFullName) == CE_None)
4840 : {
4841 47 : if (bSwitchedXY)
4842 : {
4843 2 : std::swap(pszGeolocXFullName, pszGeolocYFullName);
4844 2 : GDALPamDataset::SetMetadataItem("SWAP_XY", "YES",
4845 : "GEOLOCATION");
4846 : }
4847 :
4848 47 : bAddGeoloc = true;
4849 47 : CPLDebug("GDAL_netCDF",
4850 : "using variables %s and %s for GEOLOCATION",
4851 : pszGeolocXFullName, pszGeolocYFullName);
4852 :
4853 47 : GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
4854 : "GEOLOCATION");
4855 :
4856 94 : CPLString osTMP;
4857 47 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4858 47 : pszGeolocXFullName);
4859 :
4860 47 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP,
4861 : "GEOLOCATION");
4862 47 : GDALPamDataset::SetMetadataItem("X_BAND", "1",
4863 : "GEOLOCATION");
4864 47 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4865 47 : pszGeolocYFullName);
4866 :
4867 47 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP,
4868 : "GEOLOCATION");
4869 47 : GDALPamDataset::SetMetadataItem("Y_BAND", "1",
4870 : "GEOLOCATION");
4871 :
4872 47 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0",
4873 : "GEOLOCATION");
4874 47 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1",
4875 : "GEOLOCATION");
4876 :
4877 47 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0",
4878 : "GEOLOCATION");
4879 47 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1",
4880 : "GEOLOCATION");
4881 :
4882 47 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
4883 : "PIXEL_CENTER",
4884 : "GEOLOCATION");
4885 : }
4886 : else
4887 : {
4888 0 : CPLDebug("GDAL_netCDF",
4889 : "cannot resolve location of "
4890 : "lat/lon variables specified by the coordinates "
4891 : "attribute [%s]",
4892 : pszCoordinates);
4893 : }
4894 47 : CPLFree(pszGeolocXFullName);
4895 47 : CPLFree(pszGeolocYFullName);
4896 : }
4897 : else
4898 : {
4899 13 : CPLDebug("GDAL_netCDF",
4900 : "coordinates attribute [%s] is unsupported",
4901 : pszCoordinates);
4902 : }
4903 : }
4904 : else
4905 : {
4906 3 : CPLDebug("GDAL_netCDF",
4907 : "coordinates attribute [%s] with %d element(s) is "
4908 : "unsupported",
4909 : pszCoordinates, aosCoordinates.size());
4910 : }
4911 : }
4912 :
4913 : else
4914 : {
4915 279 : bAddGeoloc = ProcessNASAL2OceanGeoLocation(nGroupId, nVarId);
4916 :
4917 279 : if (!bAddGeoloc)
4918 278 : bAddGeoloc = ProcessNASAEMITGeoLocation(nGroupId, nVarId);
4919 : }
4920 :
4921 342 : CPLFree(pszCoordinates);
4922 :
4923 342 : return bAddGeoloc;
4924 : }
4925 :
4926 8 : CPLErr netCDFDataset::Set1DGeolocation(int nGroupId, int nVarId,
4927 : const char *szDimName)
4928 : {
4929 : // Get values.
4930 8 : char *pszVarValues = nullptr;
4931 8 : CPLErr eErr = NCDFGet1DVar(nGroupId, nVarId, &pszVarValues);
4932 8 : if (eErr != CE_None)
4933 0 : return eErr;
4934 :
4935 : // Write metadata.
4936 8 : char szTemp[NC_MAX_NAME + 1 + 32] = {};
4937 8 : snprintf(szTemp, sizeof(szTemp), "%s_VALUES", szDimName);
4938 8 : GDALPamDataset::SetMetadataItem(szTemp, pszVarValues, "GEOLOCATION2");
4939 :
4940 8 : CPLFree(pszVarValues);
4941 :
4942 8 : return CE_None;
4943 : }
4944 :
4945 0 : double *netCDFDataset::Get1DGeolocation(CPL_UNUSED const char *szDimName,
4946 : int &nVarLen)
4947 : {
4948 0 : nVarLen = 0;
4949 :
4950 : // Get Y_VALUES as tokens.
4951 : char **papszValues =
4952 0 : NCDFTokenizeArray(GetMetadataItem("Y_VALUES", "GEOLOCATION2"));
4953 0 : if (papszValues == nullptr)
4954 0 : return nullptr;
4955 :
4956 : // Initialize and fill array.
4957 0 : nVarLen = CSLCount(papszValues);
4958 : double *pdfVarValues =
4959 0 : static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
4960 :
4961 0 : for (int i = 0, j = 0; i < nVarLen; i++)
4962 : {
4963 0 : if (!bBottomUp)
4964 0 : j = nVarLen - 1 - i;
4965 : else
4966 0 : j = i; // Invert latitude values.
4967 0 : char *pszTemp = nullptr;
4968 0 : pdfVarValues[j] = CPLStrtod(papszValues[i], &pszTemp);
4969 : }
4970 0 : CSLDestroy(papszValues);
4971 :
4972 0 : return pdfVarValues;
4973 : }
4974 :
4975 : /************************************************************************/
4976 : /* SetSpatialRefNoUpdate() */
4977 : /************************************************************************/
4978 :
4979 244 : void netCDFDataset::SetSpatialRefNoUpdate(const OGRSpatialReference *poSRS)
4980 : {
4981 244 : m_oSRS.Clear();
4982 244 : if (poSRS)
4983 237 : m_oSRS = *poSRS;
4984 244 : m_bHasProjection = true;
4985 244 : }
4986 :
4987 : /************************************************************************/
4988 : /* SetSpatialRef() */
4989 : /************************************************************************/
4990 :
4991 73 : CPLErr netCDFDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
4992 : {
4993 146 : CPLMutexHolderD(&hNCMutex);
4994 :
4995 73 : if (GetAccess() != GA_Update || m_bHasProjection)
4996 : {
4997 0 : CPLError(CE_Failure, CPLE_AppDefined,
4998 : "netCDFDataset::_SetProjection() should only be called once "
4999 : "in update mode!");
5000 0 : return CE_Failure;
5001 : }
5002 :
5003 73 : if (m_bHasGeoTransform)
5004 : {
5005 32 : SetSpatialRefNoUpdate(poSRS);
5006 :
5007 : // For NC4/NC4C, writing both projection variables and data,
5008 : // followed by redefining nodata value, cancels the projection
5009 : // info from the Band variable, so for now only write the
5010 : // variable definitions, and write data at the end.
5011 : // See https://trac.osgeo.org/gdal/ticket/7245
5012 32 : return AddProjectionVars(true, nullptr, nullptr);
5013 : }
5014 :
5015 41 : SetSpatialRefNoUpdate(poSRS);
5016 :
5017 41 : return CE_None;
5018 : }
5019 :
5020 : /************************************************************************/
5021 : /* SetGeoTransformNoUpdate() */
5022 : /************************************************************************/
5023 :
5024 267 : void netCDFDataset::SetGeoTransformNoUpdate(double *padfTransform)
5025 : {
5026 267 : memcpy(m_adfGeoTransform, padfTransform, sizeof(double) * 6);
5027 267 : m_bHasGeoTransform = true;
5028 267 : }
5029 :
5030 : /************************************************************************/
5031 : /* SetGeoTransform() */
5032 : /************************************************************************/
5033 :
5034 74 : CPLErr netCDFDataset::SetGeoTransform(double *padfTransform)
5035 : {
5036 148 : CPLMutexHolderD(&hNCMutex);
5037 :
5038 74 : if (GetAccess() != GA_Update || m_bHasGeoTransform)
5039 : {
5040 0 : CPLError(CE_Failure, CPLE_AppDefined,
5041 : "netCDFDataset::SetGeoTransform() should only be called once "
5042 : "in update mode!");
5043 0 : return CE_Failure;
5044 : }
5045 :
5046 74 : CPLDebug("GDAL_netCDF", "SetGeoTransform(%f,%f,%f,%f,%f,%f)",
5047 74 : padfTransform[0], padfTransform[1], padfTransform[2],
5048 74 : padfTransform[3], padfTransform[4], padfTransform[5]);
5049 :
5050 74 : if (m_bHasProjection)
5051 : {
5052 3 : SetGeoTransformNoUpdate(padfTransform);
5053 :
5054 : // For NC4/NC4C, writing both projection variables and data,
5055 : // followed by redefining nodata value, cancels the projection
5056 : // info from the Band variable, so for now only write the
5057 : // variable definitions, and write data at the end.
5058 : // See https://trac.osgeo.org/gdal/ticket/7245
5059 3 : return AddProjectionVars(true, nullptr, nullptr);
5060 : }
5061 :
5062 71 : SetGeoTransformNoUpdate(padfTransform);
5063 71 : return CE_None;
5064 : }
5065 :
5066 : /************************************************************************/
5067 : /* NCDFWriteSRSVariable() */
5068 : /************************************************************************/
5069 :
5070 125 : int NCDFWriteSRSVariable(int cdfid, const OGRSpatialReference *poSRS,
5071 : char **ppszCFProjection, bool bWriteGDALTags,
5072 : const std::string &srsVarName)
5073 : {
5074 125 : char *pszCFProjection = nullptr;
5075 125 : char **papszKeyValues = nullptr;
5076 125 : poSRS->exportToCF1(&pszCFProjection, &papszKeyValues, nullptr, nullptr);
5077 :
5078 125 : if (bWriteGDALTags)
5079 : {
5080 124 : const char *pszWKT = CSLFetchNameValue(papszKeyValues, NCDF_CRS_WKT);
5081 124 : if (pszWKT)
5082 : {
5083 : // SPATIAL_REF is deprecated. Will be removed in GDAL 4.
5084 124 : papszKeyValues =
5085 124 : CSLSetNameValue(papszKeyValues, NCDF_SPATIAL_REF, pszWKT);
5086 : }
5087 : }
5088 :
5089 125 : const int nValues = CSLCount(papszKeyValues);
5090 :
5091 : int NCDFVarID;
5092 250 : std::string varNameRadix(pszCFProjection);
5093 125 : int nCounter = 2;
5094 : while (true)
5095 : {
5096 127 : NCDFVarID = -1;
5097 127 : nc_inq_varid(cdfid, pszCFProjection, &NCDFVarID);
5098 127 : if (NCDFVarID < 0)
5099 122 : break;
5100 :
5101 5 : int nbAttr = 0;
5102 5 : NCDF_ERR(nc_inq_varnatts(cdfid, NCDFVarID, &nbAttr));
5103 5 : bool bSame = nbAttr == nValues;
5104 41 : for (int i = 0; bSame && (i < nbAttr); i++)
5105 : {
5106 : char szAttrName[NC_MAX_NAME + 1];
5107 38 : szAttrName[0] = 0;
5108 38 : NCDF_ERR(nc_inq_attname(cdfid, NCDFVarID, i, szAttrName));
5109 :
5110 : const char *pszValue =
5111 38 : CSLFetchNameValue(papszKeyValues, szAttrName);
5112 38 : if (!pszValue)
5113 : {
5114 0 : bSame = false;
5115 2 : break;
5116 : }
5117 :
5118 38 : nc_type atttype = NC_NAT;
5119 38 : size_t attlen = 0;
5120 38 : NCDF_ERR(
5121 : nc_inq_att(cdfid, NCDFVarID, szAttrName, &atttype, &attlen));
5122 38 : if (atttype != NC_CHAR && atttype != NC_DOUBLE)
5123 : {
5124 0 : bSame = false;
5125 0 : break;
5126 : }
5127 38 : if (atttype == NC_CHAR)
5128 : {
5129 15 : if (CPLGetValueType(pszValue) != CPL_VALUE_STRING)
5130 : {
5131 0 : bSame = false;
5132 0 : break;
5133 : }
5134 15 : std::string val;
5135 15 : val.resize(attlen);
5136 15 : nc_get_att_text(cdfid, NCDFVarID, szAttrName, &val[0]);
5137 15 : if (val != pszValue)
5138 : {
5139 0 : bSame = false;
5140 0 : break;
5141 : }
5142 : }
5143 : else
5144 : {
5145 : const CPLStringList aosTokens(
5146 23 : CSLTokenizeString2(pszValue, ",", 0));
5147 23 : if (static_cast<size_t>(aosTokens.size()) != attlen)
5148 : {
5149 0 : bSame = false;
5150 0 : break;
5151 : }
5152 : double vals[2];
5153 23 : nc_get_att_double(cdfid, NCDFVarID, szAttrName, vals);
5154 44 : if (vals[0] != CPLAtof(aosTokens[0]) ||
5155 21 : (attlen == 2 && vals[1] != CPLAtof(aosTokens[1])))
5156 : {
5157 2 : bSame = false;
5158 2 : break;
5159 : }
5160 : }
5161 : }
5162 5 : if (bSame)
5163 : {
5164 3 : *ppszCFProjection = pszCFProjection;
5165 3 : CSLDestroy(papszKeyValues);
5166 3 : return NCDFVarID;
5167 : }
5168 2 : CPLFree(pszCFProjection);
5169 2 : pszCFProjection =
5170 2 : CPLStrdup(CPLSPrintf("%s_%d", varNameRadix.c_str(), nCounter));
5171 2 : nCounter++;
5172 2 : }
5173 :
5174 122 : *ppszCFProjection = pszCFProjection;
5175 :
5176 : const char *pszVarName;
5177 :
5178 122 : if (srsVarName != "")
5179 : {
5180 38 : pszVarName = srsVarName.c_str();
5181 : }
5182 : else
5183 : {
5184 84 : pszVarName = pszCFProjection;
5185 : }
5186 :
5187 122 : int status = nc_def_var(cdfid, pszVarName, NC_CHAR, 0, nullptr, &NCDFVarID);
5188 122 : NCDF_ERR(status);
5189 1170 : for (int i = 0; i < nValues; ++i)
5190 : {
5191 1048 : char *pszKey = nullptr;
5192 1048 : const char *pszValue = CPLParseNameValue(papszKeyValues[i], &pszKey);
5193 1048 : if (pszKey && pszValue)
5194 : {
5195 2096 : const CPLStringList aosTokens(CSLTokenizeString2(pszValue, ",", 0));
5196 1048 : double adfValues[2] = {0, 0};
5197 1048 : const int nDoubleCount = std::min(2, aosTokens.size());
5198 1048 : if (!(aosTokens.size() == 2 &&
5199 2095 : CPLGetValueType(aosTokens[0]) != CPL_VALUE_STRING) &&
5200 1047 : CPLGetValueType(pszValue) == CPL_VALUE_STRING)
5201 : {
5202 487 : status = nc_put_att_text(cdfid, NCDFVarID, pszKey,
5203 : strlen(pszValue), pszValue);
5204 : }
5205 : else
5206 : {
5207 1123 : for (int j = 0; j < nDoubleCount; ++j)
5208 562 : adfValues[j] = CPLAtof(aosTokens[j]);
5209 561 : status = nc_put_att_double(cdfid, NCDFVarID, pszKey, NC_DOUBLE,
5210 : nDoubleCount, adfValues);
5211 : }
5212 1048 : NCDF_ERR(status);
5213 : }
5214 1048 : CPLFree(pszKey);
5215 : }
5216 :
5217 122 : CSLDestroy(papszKeyValues);
5218 122 : return NCDFVarID;
5219 : }
5220 :
5221 : /************************************************************************/
5222 : /* NCDFWriteLonLatVarsAttributes() */
5223 : /************************************************************************/
5224 :
5225 99 : void NCDFWriteLonLatVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarLonID,
5226 : int nVarLatID)
5227 : {
5228 :
5229 : try
5230 : {
5231 99 : vcdf.nc_put_vatt_text(nVarLatID, CF_STD_NAME, CF_LATITUDE_STD_NAME);
5232 99 : vcdf.nc_put_vatt_text(nVarLatID, CF_LNG_NAME, CF_LATITUDE_LNG_NAME);
5233 99 : vcdf.nc_put_vatt_text(nVarLatID, CF_UNITS, CF_DEGREES_NORTH);
5234 99 : vcdf.nc_put_vatt_text(nVarLonID, CF_STD_NAME, CF_LONGITUDE_STD_NAME);
5235 99 : vcdf.nc_put_vatt_text(nVarLonID, CF_LNG_NAME, CF_LONGITUDE_LNG_NAME);
5236 99 : vcdf.nc_put_vatt_text(nVarLonID, CF_UNITS, CF_DEGREES_EAST);
5237 : }
5238 0 : catch (nccfdriver::SG_Exception &e)
5239 : {
5240 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5241 : }
5242 99 : }
5243 :
5244 : /************************************************************************/
5245 : /* NCDFWriteRLonRLatVarsAttributes() */
5246 : /************************************************************************/
5247 :
5248 0 : void NCDFWriteRLonRLatVarsAttributes(nccfdriver::netCDFVID &vcdf,
5249 : int nVarRLonID, int nVarRLatID)
5250 : {
5251 : try
5252 : {
5253 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_STD_NAME, "grid_latitude");
5254 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_LNG_NAME,
5255 : "latitude in rotated pole grid");
5256 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_UNITS, "degrees");
5257 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_AXIS, "Y");
5258 :
5259 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_STD_NAME, "grid_longitude");
5260 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_LNG_NAME,
5261 : "longitude in rotated pole grid");
5262 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_UNITS, "degrees");
5263 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_AXIS, "X");
5264 : }
5265 0 : catch (nccfdriver::SG_Exception &e)
5266 : {
5267 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5268 : }
5269 0 : }
5270 :
5271 : /************************************************************************/
5272 : /* NCDFGetProjectedCFUnit() */
5273 : /************************************************************************/
5274 :
5275 39 : std::string NCDFGetProjectedCFUnit(const OGRSpatialReference *poSRS)
5276 : {
5277 39 : char *pszUnitsToWrite = nullptr;
5278 39 : poSRS->exportToCF1(nullptr, nullptr, &pszUnitsToWrite, nullptr);
5279 39 : const std::string osRet = pszUnitsToWrite ? pszUnitsToWrite : std::string();
5280 39 : CPLFree(pszUnitsToWrite);
5281 78 : return osRet;
5282 : }
5283 :
5284 : /************************************************************************/
5285 : /* NCDFWriteXYVarsAttributes() */
5286 : /************************************************************************/
5287 :
5288 28 : void NCDFWriteXYVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarXID,
5289 : int nVarYID, const OGRSpatialReference *poSRS)
5290 : {
5291 56 : const std::string osUnitsToWrite = NCDFGetProjectedCFUnit(poSRS);
5292 :
5293 : try
5294 : {
5295 28 : vcdf.nc_put_vatt_text(nVarXID, CF_STD_NAME, CF_PROJ_X_COORD);
5296 28 : vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME, CF_PROJ_X_COORD_LONG_NAME);
5297 28 : if (!osUnitsToWrite.empty())
5298 28 : vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, osUnitsToWrite.c_str());
5299 28 : vcdf.nc_put_vatt_text(nVarYID, CF_STD_NAME, CF_PROJ_Y_COORD);
5300 28 : vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME, CF_PROJ_Y_COORD_LONG_NAME);
5301 28 : if (!osUnitsToWrite.empty())
5302 28 : vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, osUnitsToWrite.c_str());
5303 : }
5304 0 : catch (nccfdriver::SG_Exception &e)
5305 : {
5306 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5307 : }
5308 28 : }
5309 :
5310 : /************************************************************************/
5311 : /* AddProjectionVars() */
5312 : /************************************************************************/
5313 :
5314 158 : CPLErr netCDFDataset::AddProjectionVars(bool bDefsOnly,
5315 : GDALProgressFunc pfnProgress,
5316 : void *pProgressData)
5317 : {
5318 158 : if (nCFVersion >= 1.8)
5319 0 : return CE_None; // do nothing
5320 :
5321 158 : bool bWriteGridMapping = false;
5322 158 : bool bWriteLonLat = false;
5323 158 : bool bHasGeoloc = false;
5324 158 : bool bWriteGDALTags = false;
5325 158 : bool bWriteGeoTransform = false;
5326 :
5327 : // For GEOLOCATION information.
5328 158 : GDALDatasetH hDS_X = nullptr;
5329 158 : GDALRasterBandH hBand_X = nullptr;
5330 158 : GDALDatasetH hDS_Y = nullptr;
5331 158 : GDALRasterBandH hBand_Y = nullptr;
5332 :
5333 316 : OGRSpatialReference oSRS(m_oSRS);
5334 158 : if (!m_oSRS.IsEmpty())
5335 : {
5336 132 : if (oSRS.IsProjected())
5337 48 : bIsProjected = true;
5338 84 : else if (oSRS.IsGeographic())
5339 84 : bIsGeographic = true;
5340 : }
5341 :
5342 158 : if (bDefsOnly)
5343 : {
5344 79 : char *pszProjection = nullptr;
5345 79 : m_oSRS.exportToWkt(&pszProjection);
5346 79 : CPLDebug("GDAL_netCDF",
5347 : "SetProjection, WKT now = [%s]\nprojected: %d geographic: %d",
5348 79 : pszProjection ? pszProjection : "(null)",
5349 79 : static_cast<int>(bIsProjected),
5350 79 : static_cast<int>(bIsGeographic));
5351 79 : CPLFree(pszProjection);
5352 :
5353 79 : if (!m_bHasGeoTransform)
5354 5 : CPLDebug("GDAL_netCDF",
5355 : "netCDFDataset::AddProjectionVars() called, "
5356 : "but GeoTransform has not yet been defined!");
5357 :
5358 79 : if (!m_bHasProjection)
5359 6 : CPLDebug("GDAL_netCDF",
5360 : "netCDFDataset::AddProjectionVars() called, "
5361 : "but Projection has not yet been defined!");
5362 : }
5363 :
5364 : // Check GEOLOCATION information.
5365 158 : char **papszGeolocationInfo = netCDFDataset::GetMetadata("GEOLOCATION");
5366 158 : if (papszGeolocationInfo != nullptr)
5367 : {
5368 : // Look for geolocation datasets.
5369 : const char *pszDSName =
5370 10 : CSLFetchNameValue(papszGeolocationInfo, "X_DATASET");
5371 10 : if (pszDSName != nullptr)
5372 10 : hDS_X = GDALOpenShared(pszDSName, GA_ReadOnly);
5373 10 : pszDSName = CSLFetchNameValue(papszGeolocationInfo, "Y_DATASET");
5374 10 : if (pszDSName != nullptr)
5375 10 : hDS_Y = GDALOpenShared(pszDSName, GA_ReadOnly);
5376 :
5377 10 : if (hDS_X != nullptr && hDS_Y != nullptr)
5378 : {
5379 10 : int nBand = std::max(1, atoi(CSLFetchNameValueDef(
5380 10 : papszGeolocationInfo, "X_BAND", "0")));
5381 10 : hBand_X = GDALGetRasterBand(hDS_X, nBand);
5382 10 : nBand = std::max(1, atoi(CSLFetchNameValueDef(papszGeolocationInfo,
5383 10 : "Y_BAND", "0")));
5384 10 : hBand_Y = GDALGetRasterBand(hDS_Y, nBand);
5385 :
5386 : // If geoloc bands are found, do basic validation based on their
5387 : // dimensions.
5388 10 : if (hBand_X != nullptr && hBand_Y != nullptr)
5389 : {
5390 10 : int nXSize_XBand = GDALGetRasterXSize(hDS_X);
5391 10 : int nYSize_XBand = GDALGetRasterYSize(hDS_X);
5392 10 : int nXSize_YBand = GDALGetRasterXSize(hDS_Y);
5393 10 : int nYSize_YBand = GDALGetRasterYSize(hDS_Y);
5394 :
5395 : // TODO 1D geolocation arrays not implemented.
5396 10 : if (nYSize_XBand == 1 && nYSize_YBand == 1)
5397 : {
5398 0 : bHasGeoloc = false;
5399 0 : CPLDebug("GDAL_netCDF",
5400 : "1D GEOLOCATION arrays not supported yet");
5401 : }
5402 : // 2D bands must have same sizes as the raster bands.
5403 10 : else if (nXSize_XBand != nRasterXSize ||
5404 10 : nYSize_XBand != nRasterYSize ||
5405 10 : nXSize_YBand != nRasterXSize ||
5406 10 : nYSize_YBand != nRasterYSize)
5407 : {
5408 0 : bHasGeoloc = false;
5409 0 : CPLDebug("GDAL_netCDF",
5410 : "GEOLOCATION array sizes (%dx%d %dx%d) differ "
5411 : "from raster (%dx%d), not supported",
5412 : nXSize_XBand, nYSize_XBand, nXSize_YBand,
5413 : nYSize_YBand, nRasterXSize, nRasterYSize);
5414 : }
5415 : else
5416 : {
5417 10 : bHasGeoloc = true;
5418 10 : CPLDebug("GDAL_netCDF",
5419 : "dataset has GEOLOCATION information, will try to "
5420 : "write it");
5421 : }
5422 : }
5423 : }
5424 : }
5425 :
5426 : // Process projection options.
5427 158 : if (bIsProjected)
5428 : {
5429 : bool bIsCfProjection =
5430 48 : oSRS.exportToCF1(nullptr, nullptr, nullptr, nullptr) == OGRERR_NONE;
5431 48 : bWriteGridMapping = true;
5432 48 : bWriteGDALTags = CPL_TO_BOOL(
5433 48 : CSLFetchBoolean(papszCreationOptions, "WRITE_GDAL_TAGS", TRUE));
5434 : // Force WRITE_GDAL_TAGS if is not a CF projection.
5435 48 : if (!bWriteGDALTags && !bIsCfProjection)
5436 0 : bWriteGDALTags = true;
5437 48 : if (bWriteGDALTags)
5438 48 : bWriteGeoTransform = true;
5439 :
5440 : // Write lon/lat: default is NO, except if has geolocation.
5441 : // With IF_NEEDED: write if has geoloc or is not CF projection.
5442 : const char *pszValue =
5443 48 : CSLFetchNameValue(papszCreationOptions, "WRITE_LONLAT");
5444 48 : if (pszValue)
5445 : {
5446 2 : if (EQUAL(pszValue, "IF_NEEDED"))
5447 : {
5448 0 : bWriteLonLat = bHasGeoloc || !bIsCfProjection;
5449 : }
5450 : else
5451 : {
5452 2 : bWriteLonLat = CPLTestBool(pszValue);
5453 : }
5454 : }
5455 : else
5456 : {
5457 46 : bWriteLonLat = bHasGeoloc;
5458 : }
5459 :
5460 : // Save value of pszCFCoordinates for later.
5461 48 : if (bWriteLonLat)
5462 : {
5463 4 : pszCFCoordinates = NCDF_LONLAT;
5464 : }
5465 : }
5466 : else
5467 : {
5468 : // Files without a Datum will not have a grid_mapping variable and
5469 : // geographic information.
5470 110 : bWriteGridMapping = bIsGeographic;
5471 :
5472 110 : if (bHasGeoloc)
5473 : {
5474 8 : bWriteLonLat = true;
5475 : }
5476 : else
5477 : {
5478 102 : bWriteGDALTags = CPL_TO_BOOL(CSLFetchBoolean(
5479 102 : papszCreationOptions, "WRITE_GDAL_TAGS", bWriteGridMapping));
5480 102 : if (bWriteGDALTags)
5481 84 : bWriteGeoTransform = true;
5482 :
5483 102 : const char *pszValue = CSLFetchNameValueDef(papszCreationOptions,
5484 : "WRITE_LONLAT", "YES");
5485 102 : if (EQUAL(pszValue, "IF_NEEDED"))
5486 0 : bWriteLonLat = true;
5487 : else
5488 102 : bWriteLonLat = CPLTestBool(pszValue);
5489 : // Don't write lon/lat if no source geotransform.
5490 102 : if (!m_bHasGeoTransform)
5491 0 : bWriteLonLat = false;
5492 : // If we don't write lon/lat, set dimnames to X/Y and write gdal
5493 : // tags.
5494 102 : if (!bWriteLonLat)
5495 : {
5496 0 : CPLError(CE_Warning, CPLE_AppDefined,
5497 : "creating geographic file without lon/lat values!");
5498 0 : if (m_bHasGeoTransform)
5499 : {
5500 0 : bWriteGDALTags = true; // Not desirable if no geotransform.
5501 0 : bWriteGeoTransform = true;
5502 : }
5503 : }
5504 : }
5505 : }
5506 :
5507 : // Make sure we write grid_mapping if we need to write GDAL tags.
5508 158 : if (bWriteGDALTags)
5509 132 : bWriteGridMapping = true;
5510 :
5511 : // bottom-up value: new driver is bottom-up by default.
5512 : // Override with WRITE_BOTTOMUP.
5513 158 : bBottomUp = CPL_TO_BOOL(
5514 158 : CSLFetchBoolean(papszCreationOptions, "WRITE_BOTTOMUP", TRUE));
5515 :
5516 158 : if (bDefsOnly)
5517 : {
5518 79 : CPLDebug(
5519 : "GDAL_netCDF",
5520 : "bIsProjected=%d bIsGeographic=%d bWriteGridMapping=%d "
5521 : "bWriteGDALTags=%d bWriteLonLat=%d bBottomUp=%d bHasGeoloc=%d",
5522 79 : static_cast<int>(bIsProjected), static_cast<int>(bIsGeographic),
5523 : static_cast<int>(bWriteGridMapping),
5524 : static_cast<int>(bWriteGDALTags), static_cast<int>(bWriteLonLat),
5525 79 : static_cast<int>(bBottomUp), static_cast<int>(bHasGeoloc));
5526 : }
5527 :
5528 : // Exit if nothing to do.
5529 158 : if (!bIsProjected && !bWriteLonLat)
5530 0 : return CE_None;
5531 :
5532 : // Define dimension names.
5533 :
5534 158 : constexpr const char *ROTATED_POLE_VAR_NAME = "rotated_pole";
5535 :
5536 158 : if (bDefsOnly)
5537 : {
5538 79 : int nVarLonID = -1;
5539 79 : int nVarLatID = -1;
5540 79 : int nVarXID = -1;
5541 79 : int nVarYID = -1;
5542 :
5543 79 : m_bAddedProjectionVarsDefs = true;
5544 :
5545 : // Make sure we are in define mode.
5546 79 : SetDefineMode(true);
5547 :
5548 : // Write projection attributes.
5549 79 : if (bWriteGridMapping)
5550 : {
5551 66 : const int NCDFVarID = NCDFWriteSRSVariable(
5552 : cdfid, &oSRS, &pszCFProjection, bWriteGDALTags);
5553 66 : if (NCDFVarID < 0)
5554 0 : return CE_Failure;
5555 :
5556 : // Optional GDAL custom projection tags.
5557 66 : if (bWriteGDALTags)
5558 : {
5559 132 : CPLString osGeoTransform;
5560 462 : for (int i = 0; i < 6; i++)
5561 : {
5562 : osGeoTransform +=
5563 396 : CPLSPrintf("%.16g ", m_adfGeoTransform[i]);
5564 : }
5565 66 : CPLDebug("GDAL_netCDF", "szGeoTransform = %s",
5566 : osGeoTransform.c_str());
5567 :
5568 : // if( strlen(pszProj4Defn) > 0 ) {
5569 : // nc_put_att_text(cdfid, NCDFVarID, "proj4",
5570 : // strlen(pszProj4Defn), pszProj4Defn);
5571 : // }
5572 :
5573 : // For now, write the geotransform for back-compat or else
5574 : // the old (1.8.1) driver overrides the CF geotransform with
5575 : // empty values from dfNN, dfSN, dfEE, dfWE;
5576 :
5577 : // TODO: fix this in 1.8 branch, and then remove this here.
5578 66 : if (bWriteGeoTransform && m_bHasGeoTransform)
5579 : {
5580 : {
5581 65 : const int status = nc_put_att_text(
5582 : cdfid, NCDFVarID, NCDF_GEOTRANSFORM,
5583 : osGeoTransform.size(), osGeoTransform.c_str());
5584 65 : NCDF_ERR(status);
5585 : }
5586 : }
5587 : }
5588 :
5589 : // Write projection variable to band variable.
5590 : // Need to call later if there are no bands.
5591 66 : AddGridMappingRef();
5592 : } // end if( bWriteGridMapping )
5593 :
5594 : // Write CF Projection vars.
5595 :
5596 79 : const bool bIsRotatedPole =
5597 145 : pszCFProjection != nullptr &&
5598 66 : EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
5599 79 : if (bIsRotatedPole)
5600 : {
5601 : // Rename dims to rlat/rlon.
5602 : papszDimName
5603 0 : .Clear(); // If we add other dims one day, this has to change
5604 0 : papszDimName.AddString(NCDF_DIMNAME_RLAT);
5605 0 : papszDimName.AddString(NCDF_DIMNAME_RLON);
5606 :
5607 0 : int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_RLAT);
5608 0 : NCDF_ERR(status);
5609 0 : status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_RLON);
5610 0 : NCDF_ERR(status);
5611 : }
5612 : // Rename dimensions if lon/lat.
5613 79 : else if (!bIsProjected && !bHasGeoloc)
5614 : {
5615 : // Rename dims to lat/lon.
5616 : papszDimName
5617 51 : .Clear(); // If we add other dims one day, this has to change
5618 51 : papszDimName.AddString(NCDF_DIMNAME_LAT);
5619 51 : papszDimName.AddString(NCDF_DIMNAME_LON);
5620 :
5621 51 : int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_LAT);
5622 51 : NCDF_ERR(status);
5623 51 : status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_LON);
5624 51 : NCDF_ERR(status);
5625 : }
5626 :
5627 : // Write X/Y attributes.
5628 : else /* if( bIsProjected || bHasGeoloc ) */
5629 : {
5630 : // X
5631 : int anXDims[1];
5632 28 : anXDims[0] = nXDimID;
5633 28 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
5634 : CF_PROJ_X_VAR_NAME, NC_DOUBLE);
5635 28 : int status = nc_def_var(cdfid, CF_PROJ_X_VAR_NAME, NC_DOUBLE, 1,
5636 : anXDims, &nVarXID);
5637 28 : NCDF_ERR(status);
5638 :
5639 : // Y
5640 : int anYDims[1];
5641 28 : anYDims[0] = nYDimID;
5642 28 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
5643 : CF_PROJ_Y_VAR_NAME, NC_DOUBLE);
5644 28 : status = nc_def_var(cdfid, CF_PROJ_Y_VAR_NAME, NC_DOUBLE, 1,
5645 : anYDims, &nVarYID);
5646 28 : NCDF_ERR(status);
5647 :
5648 28 : if (bIsProjected)
5649 : {
5650 24 : NCDFWriteXYVarsAttributes(this->vcdf, nVarXID, nVarYID, &oSRS);
5651 : }
5652 : else
5653 : {
5654 4 : CPLAssert(bHasGeoloc);
5655 : try
5656 : {
5657 4 : vcdf.nc_put_vatt_text(nVarXID, CF_AXIS, CF_SG_X_AXIS);
5658 4 : vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME,
5659 : "x-coordinate in Cartesian system");
5660 4 : vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, "m");
5661 4 : vcdf.nc_put_vatt_text(nVarYID, CF_AXIS, CF_SG_Y_AXIS);
5662 4 : vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME,
5663 : "y-coordinate in Cartesian system");
5664 4 : vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, "m");
5665 :
5666 4 : pszCFCoordinates = NCDF_LONLAT;
5667 : }
5668 0 : catch (nccfdriver::SG_Exception &e)
5669 : {
5670 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5671 0 : return CE_Failure;
5672 : }
5673 : }
5674 : }
5675 :
5676 : // Write lat/lon attributes if needed.
5677 79 : if (bWriteLonLat)
5678 : {
5679 57 : int *panLatDims = nullptr;
5680 57 : int *panLonDims = nullptr;
5681 57 : int nLatDims = -1;
5682 57 : int nLonDims = -1;
5683 :
5684 : // Get information.
5685 57 : if (bHasGeoloc)
5686 : {
5687 : // Geoloc
5688 5 : nLatDims = 2;
5689 : panLatDims =
5690 5 : static_cast<int *>(CPLCalloc(nLatDims, sizeof(int)));
5691 5 : panLatDims[0] = nYDimID;
5692 5 : panLatDims[1] = nXDimID;
5693 5 : nLonDims = 2;
5694 : panLonDims =
5695 5 : static_cast<int *>(CPLCalloc(nLonDims, sizeof(int)));
5696 5 : panLonDims[0] = nYDimID;
5697 5 : panLonDims[1] = nXDimID;
5698 : }
5699 52 : else if (bIsProjected)
5700 : {
5701 : // Projected
5702 1 : nLatDims = 2;
5703 : panLatDims =
5704 1 : static_cast<int *>(CPLCalloc(nLatDims, sizeof(int)));
5705 1 : panLatDims[0] = nYDimID;
5706 1 : panLatDims[1] = nXDimID;
5707 1 : nLonDims = 2;
5708 : panLonDims =
5709 1 : static_cast<int *>(CPLCalloc(nLonDims, sizeof(int)));
5710 1 : panLonDims[0] = nYDimID;
5711 1 : panLonDims[1] = nXDimID;
5712 : }
5713 : else
5714 : {
5715 : // Geographic
5716 51 : nLatDims = 1;
5717 : panLatDims =
5718 51 : static_cast<int *>(CPLCalloc(nLatDims, sizeof(int)));
5719 51 : panLatDims[0] = nYDimID;
5720 51 : nLonDims = 1;
5721 : panLonDims =
5722 51 : static_cast<int *>(CPLCalloc(nLonDims, sizeof(int)));
5723 51 : panLonDims[0] = nXDimID;
5724 : }
5725 :
5726 57 : nc_type eLonLatType = NC_NAT;
5727 57 : if (bIsProjected)
5728 : {
5729 2 : eLonLatType = NC_FLOAT;
5730 4 : const char *pszValue = CSLFetchNameValueDef(
5731 2 : papszCreationOptions, "TYPE_LONLAT", "FLOAT");
5732 2 : if (EQUAL(pszValue, "DOUBLE"))
5733 0 : eLonLatType = NC_DOUBLE;
5734 : }
5735 : else
5736 : {
5737 55 : eLonLatType = NC_DOUBLE;
5738 110 : const char *pszValue = CSLFetchNameValueDef(
5739 55 : papszCreationOptions, "TYPE_LONLAT", "DOUBLE");
5740 55 : if (EQUAL(pszValue, "FLOAT"))
5741 0 : eLonLatType = NC_FLOAT;
5742 : }
5743 :
5744 : // Def vars and attributes.
5745 : {
5746 57 : const char *pszVarName =
5747 57 : bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME;
5748 57 : int status = nc_def_var(cdfid, pszVarName, eLonLatType,
5749 : nLatDims, panLatDims, &nVarLatID);
5750 57 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
5751 : cdfid, pszVarName, eLonLatType, nLatDims, nVarLatID);
5752 57 : NCDF_ERR(status);
5753 57 : DefVarDeflate(nVarLatID, false); // Don't set chunking.
5754 : }
5755 :
5756 : {
5757 57 : const char *pszVarName =
5758 57 : bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME;
5759 57 : int status = nc_def_var(cdfid, pszVarName, eLonLatType,
5760 : nLonDims, panLonDims, &nVarLonID);
5761 57 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
5762 : cdfid, pszVarName, eLonLatType, nLatDims, nVarLonID);
5763 57 : NCDF_ERR(status);
5764 57 : DefVarDeflate(nVarLonID, false); // Don't set chunking.
5765 : }
5766 :
5767 57 : if (bIsRotatedPole)
5768 0 : NCDFWriteRLonRLatVarsAttributes(this->vcdf, nVarLonID,
5769 : nVarLatID);
5770 : else
5771 57 : NCDFWriteLonLatVarsAttributes(this->vcdf, nVarLonID, nVarLatID);
5772 :
5773 57 : CPLFree(panLatDims);
5774 57 : CPLFree(panLonDims);
5775 : }
5776 : }
5777 :
5778 158 : if (!bDefsOnly)
5779 : {
5780 79 : m_bAddedProjectionVarsData = true;
5781 :
5782 79 : int nVarXID = -1;
5783 79 : int nVarYID = -1;
5784 :
5785 79 : nc_inq_varid(cdfid, CF_PROJ_X_VAR_NAME, &nVarXID);
5786 79 : nc_inq_varid(cdfid, CF_PROJ_Y_VAR_NAME, &nVarYID);
5787 :
5788 79 : int nVarLonID = -1;
5789 79 : int nVarLatID = -1;
5790 :
5791 79 : const bool bIsRotatedPole =
5792 145 : pszCFProjection != nullptr &&
5793 66 : EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
5794 79 : nc_inq_varid(cdfid,
5795 : bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME,
5796 : &nVarLonID);
5797 79 : nc_inq_varid(cdfid,
5798 : bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME,
5799 : &nVarLatID);
5800 :
5801 : // Get projection values.
5802 :
5803 79 : double *padLonVal = nullptr;
5804 79 : double *padLatVal = nullptr;
5805 :
5806 79 : if (bIsProjected)
5807 : {
5808 24 : OGRSpatialReference *poLatLonSRS = nullptr;
5809 24 : OGRCoordinateTransformation *poTransform = nullptr;
5810 :
5811 : size_t startX[1];
5812 : size_t countX[1];
5813 : size_t startY[1];
5814 : size_t countY[1];
5815 :
5816 24 : CPLDebug("GDAL_netCDF", "Getting (X,Y) values");
5817 :
5818 : double *padXVal =
5819 24 : static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
5820 : double *padYVal =
5821 24 : static_cast<double *>(CPLMalloc(nRasterYSize * sizeof(double)));
5822 :
5823 : // Get Y values.
5824 24 : const double dfY0 = (!bBottomUp) ? m_adfGeoTransform[3] :
5825 : // Invert latitude values.
5826 24 : m_adfGeoTransform[3] +
5827 24 : (m_adfGeoTransform[5] * nRasterYSize);
5828 24 : const double dfDY = m_adfGeoTransform[5];
5829 :
5830 1435 : for (int j = 0; j < nRasterYSize; j++)
5831 : {
5832 : // The data point is centered inside the pixel.
5833 1411 : if (!bBottomUp)
5834 0 : padYVal[j] = dfY0 + (j + 0.5) * dfDY;
5835 : else // Invert latitude values.
5836 1411 : padYVal[j] = dfY0 - (j + 0.5) * dfDY;
5837 : }
5838 24 : startX[0] = 0;
5839 24 : countX[0] = nRasterXSize;
5840 :
5841 : // Get X values.
5842 24 : const double dfX0 = m_adfGeoTransform[0];
5843 24 : const double dfDX = m_adfGeoTransform[1];
5844 :
5845 1456 : for (int i = 0; i < nRasterXSize; i++)
5846 : {
5847 : // The data point is centered inside the pixel.
5848 1432 : padXVal[i] = dfX0 + (i + 0.5) * dfDX;
5849 : }
5850 24 : startY[0] = 0;
5851 24 : countY[0] = nRasterYSize;
5852 :
5853 : // Write X/Y values.
5854 :
5855 : // Make sure we are in data mode.
5856 24 : SetDefineMode(false);
5857 :
5858 24 : CPLDebug("GDAL_netCDF", "Writing X values");
5859 : int status =
5860 24 : nc_put_vara_double(cdfid, nVarXID, startX, countX, padXVal);
5861 24 : NCDF_ERR(status);
5862 :
5863 24 : CPLDebug("GDAL_netCDF", "Writing Y values");
5864 : status =
5865 24 : nc_put_vara_double(cdfid, nVarYID, startY, countY, padYVal);
5866 24 : NCDF_ERR(status);
5867 :
5868 24 : if (pfnProgress)
5869 20 : pfnProgress(0.20, nullptr, pProgressData);
5870 :
5871 : // Write lon/lat arrays (CF coordinates) if requested.
5872 :
5873 : // Get OGR transform if GEOLOCATION is not available.
5874 24 : if (bWriteLonLat && !bHasGeoloc)
5875 : {
5876 1 : poLatLonSRS = m_oSRS.CloneGeogCS();
5877 1 : if (poLatLonSRS != nullptr)
5878 : {
5879 1 : poLatLonSRS->SetAxisMappingStrategy(
5880 : OAMS_TRADITIONAL_GIS_ORDER);
5881 : poTransform =
5882 1 : OGRCreateCoordinateTransformation(&m_oSRS, poLatLonSRS);
5883 : }
5884 : // If no OGR transform, then don't write CF lon/lat.
5885 1 : if (poTransform == nullptr)
5886 : {
5887 0 : CPLError(CE_Failure, CPLE_AppDefined,
5888 : "Unable to get Coordinate Transform");
5889 0 : bWriteLonLat = false;
5890 : }
5891 : }
5892 :
5893 24 : if (bWriteLonLat)
5894 : {
5895 2 : if (!bHasGeoloc)
5896 1 : CPLDebug("GDAL_netCDF", "Transforming (X,Y)->(lon,lat)");
5897 : else
5898 1 : CPLDebug("GDAL_netCDF",
5899 : "Writing (lon,lat) from GEOLOCATION arrays");
5900 :
5901 2 : bool bOK = true;
5902 2 : double dfProgress = 0.2;
5903 :
5904 2 : size_t start[] = {0, 0};
5905 2 : size_t count[] = {1, (size_t)nRasterXSize};
5906 : padLatVal = static_cast<double *>(
5907 2 : CPLMalloc(nRasterXSize * sizeof(double)));
5908 : padLonVal = static_cast<double *>(
5909 2 : CPLMalloc(nRasterXSize * sizeof(double)));
5910 :
5911 61 : for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR;
5912 : j++)
5913 : {
5914 59 : start[0] = j;
5915 :
5916 : // Get values from geotransform.
5917 59 : if (!bHasGeoloc)
5918 : {
5919 : // Fill values to transform.
5920 420 : for (int i = 0; i < nRasterXSize; i++)
5921 : {
5922 400 : padLatVal[i] = padYVal[j];
5923 400 : padLonVal[i] = padXVal[i];
5924 : }
5925 :
5926 : // Do the transform.
5927 20 : bOK = CPL_TO_BOOL(poTransform->Transform(
5928 20 : nRasterXSize, padLonVal, padLatVal, nullptr));
5929 20 : if (!bOK)
5930 : {
5931 0 : CPLError(CE_Failure, CPLE_AppDefined,
5932 : "Unable to Transform (X,Y) to (lon,lat).");
5933 : }
5934 : }
5935 : // Get values from geoloc arrays.
5936 : else
5937 : {
5938 39 : CPLErr eErr = GDALRasterIO(
5939 : hBand_Y, GF_Read, 0, j, nRasterXSize, 1, padLatVal,
5940 : nRasterXSize, 1, GDT_Float64, 0, 0);
5941 39 : if (eErr == CE_None)
5942 : {
5943 39 : eErr = GDALRasterIO(
5944 : hBand_X, GF_Read, 0, j, nRasterXSize, 1,
5945 : padLonVal, nRasterXSize, 1, GDT_Float64, 0, 0);
5946 : }
5947 :
5948 39 : if (eErr == CE_None)
5949 : {
5950 39 : bOK = true;
5951 : }
5952 : else
5953 : {
5954 0 : bOK = false;
5955 0 : CPLError(CE_Failure, CPLE_AppDefined,
5956 : "Unable to get scanline %d", j);
5957 : }
5958 : }
5959 :
5960 : // Write data.
5961 59 : if (bOK)
5962 : {
5963 59 : status = nc_put_vara_double(cdfid, nVarLatID, start,
5964 : count, padLatVal);
5965 59 : NCDF_ERR(status);
5966 59 : status = nc_put_vara_double(cdfid, nVarLonID, start,
5967 : count, padLonVal);
5968 59 : NCDF_ERR(status);
5969 : }
5970 :
5971 59 : if (pfnProgress && (nRasterYSize / 10) > 0 &&
5972 59 : (j % (nRasterYSize / 10) == 0))
5973 : {
5974 23 : dfProgress += 0.08;
5975 23 : pfnProgress(dfProgress, nullptr, pProgressData);
5976 : }
5977 : }
5978 : }
5979 :
5980 24 : if (poLatLonSRS != nullptr)
5981 1 : delete poLatLonSRS;
5982 24 : if (poTransform != nullptr)
5983 1 : delete poTransform;
5984 :
5985 24 : CPLFree(padXVal);
5986 24 : CPLFree(padYVal);
5987 : } // Projected
5988 :
5989 : // If not projected/geographic and has geoloc
5990 55 : else if (!bIsGeographic && bHasGeoloc)
5991 : {
5992 : // Use
5993 : // https://cfconventions.org/Data/cf-conventions/cf-conventions-1.9/cf-conventions.html#_two_dimensional_latitude_longitude_coordinate_variables
5994 :
5995 4 : bool bOK = true;
5996 4 : double dfProgress = 0.2;
5997 :
5998 : // Make sure we are in data mode.
5999 4 : SetDefineMode(false);
6000 :
6001 : size_t startX[1];
6002 : size_t countX[1];
6003 : size_t startY[1];
6004 : size_t countY[1];
6005 4 : startX[0] = 0;
6006 4 : countX[0] = nRasterXSize;
6007 :
6008 4 : startY[0] = 0;
6009 4 : countY[0] = nRasterYSize;
6010 :
6011 8 : std::vector<double> adfXVal(nRasterXSize);
6012 16 : for (int i = 0; i < nRasterXSize; i++)
6013 12 : adfXVal[i] = i;
6014 :
6015 8 : std::vector<double> adfYVal(nRasterYSize);
6016 12 : for (int i = 0; i < nRasterYSize; i++)
6017 8 : adfYVal[i] = bBottomUp ? nRasterYSize - 1 - i : i;
6018 :
6019 4 : CPLDebug("GDAL_netCDF", "Writing X values");
6020 4 : int status = nc_put_vara_double(cdfid, nVarXID, startX, countX,
6021 4 : adfXVal.data());
6022 4 : NCDF_ERR(status);
6023 :
6024 4 : CPLDebug("GDAL_netCDF", "Writing Y values");
6025 4 : status = nc_put_vara_double(cdfid, nVarYID, startY, countY,
6026 4 : adfYVal.data());
6027 4 : NCDF_ERR(status);
6028 :
6029 4 : if (pfnProgress)
6030 0 : pfnProgress(0.20, nullptr, pProgressData);
6031 :
6032 4 : size_t start[] = {0, 0};
6033 4 : size_t count[] = {1, (size_t)nRasterXSize};
6034 : padLatVal =
6035 4 : static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
6036 : padLonVal =
6037 4 : static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
6038 :
6039 12 : for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR; j++)
6040 : {
6041 8 : start[0] = j;
6042 :
6043 8 : CPLErr eErr = GDALRasterIO(hBand_Y, GF_Read, 0,
6044 8 : bBottomUp ? nRasterYSize - 1 - j : j,
6045 : nRasterXSize, 1, padLatVal,
6046 : nRasterXSize, 1, GDT_Float64, 0, 0);
6047 8 : if (eErr == CE_None)
6048 : {
6049 8 : eErr = GDALRasterIO(hBand_X, GF_Read, 0,
6050 8 : bBottomUp ? nRasterYSize - 1 - j : j,
6051 : nRasterXSize, 1, padLonVal,
6052 : nRasterXSize, 1, GDT_Float64, 0, 0);
6053 : }
6054 :
6055 8 : if (eErr == CE_None)
6056 : {
6057 8 : bOK = true;
6058 : }
6059 : else
6060 : {
6061 0 : bOK = false;
6062 0 : CPLError(CE_Failure, CPLE_AppDefined,
6063 : "Unable to get scanline %d", j);
6064 : }
6065 :
6066 : // Write data.
6067 8 : if (bOK)
6068 : {
6069 8 : status = nc_put_vara_double(cdfid, nVarLatID, start, count,
6070 : padLatVal);
6071 8 : NCDF_ERR(status);
6072 8 : status = nc_put_vara_double(cdfid, nVarLonID, start, count,
6073 : padLonVal);
6074 8 : NCDF_ERR(status);
6075 : }
6076 :
6077 8 : if (pfnProgress && (nRasterYSize / 10) > 0 &&
6078 0 : (j % (nRasterYSize / 10) == 0))
6079 : {
6080 0 : dfProgress += 0.08;
6081 0 : pfnProgress(dfProgress, nullptr, pProgressData);
6082 : }
6083 4 : }
6084 : }
6085 :
6086 : // If not projected, assume geographic to catch grids without Datum.
6087 51 : else if (bWriteLonLat)
6088 : {
6089 : // Get latitude values.
6090 51 : const double dfY0 = (!bBottomUp) ? m_adfGeoTransform[3] :
6091 : // Invert latitude values.
6092 51 : m_adfGeoTransform[3] +
6093 51 : (m_adfGeoTransform[5] * nRasterYSize);
6094 51 : const double dfDY = m_adfGeoTransform[5];
6095 :
6096 : // Override lat values with the ones in GEOLOCATION/Y_VALUES.
6097 51 : if (netCDFDataset::GetMetadataItem("Y_VALUES", "GEOLOCATION") !=
6098 : nullptr)
6099 : {
6100 0 : int nTemp = 0;
6101 0 : padLatVal = Get1DGeolocation("Y_VALUES", nTemp);
6102 : // Make sure we got the correct amount, if not fallback to GT */
6103 : // could add test fabs(fabs(padLatVal[0]) - fabs(dfY0)) <= 0.1))
6104 0 : if (nTemp == nRasterYSize)
6105 : {
6106 0 : CPLDebug(
6107 : "GDAL_netCDF",
6108 : "Using Y_VALUES geolocation metadata for lat values");
6109 : }
6110 : else
6111 : {
6112 0 : CPLDebug("GDAL_netCDF",
6113 : "Got %d elements from Y_VALUES geolocation "
6114 : "metadata, need %d",
6115 : nTemp, nRasterYSize);
6116 0 : if (padLatVal)
6117 : {
6118 0 : CPLFree(padLatVal);
6119 0 : padLatVal = nullptr;
6120 : }
6121 : }
6122 : }
6123 :
6124 51 : if (padLatVal == nullptr)
6125 : {
6126 : padLatVal = static_cast<double *>(
6127 51 : CPLMalloc(nRasterYSize * sizeof(double)));
6128 3375 : for (int i = 0; i < nRasterYSize; i++)
6129 : {
6130 : // The data point is centered inside the pixel.
6131 3324 : if (!bBottomUp)
6132 0 : padLatVal[i] = dfY0 + (i + 0.5) * dfDY;
6133 : else // Invert latitude values.
6134 3324 : padLatVal[i] = dfY0 - (i + 0.5) * dfDY;
6135 : }
6136 : }
6137 :
6138 51 : size_t startLat[1] = {0};
6139 51 : size_t countLat[1] = {static_cast<size_t>(nRasterYSize)};
6140 :
6141 : // Get longitude values.
6142 51 : const double dfX0 = m_adfGeoTransform[0];
6143 51 : const double dfDX = m_adfGeoTransform[1];
6144 :
6145 : padLonVal =
6146 51 : static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
6147 3427 : for (int i = 0; i < nRasterXSize; i++)
6148 : {
6149 : // The data point is centered inside the pixel.
6150 3376 : padLonVal[i] = dfX0 + (i + 0.5) * dfDX;
6151 : }
6152 :
6153 51 : size_t startLon[1] = {0};
6154 51 : size_t countLon[1] = {static_cast<size_t>(nRasterXSize)};
6155 :
6156 : // Write latitude and longitude values.
6157 :
6158 : // Make sure we are in data mode.
6159 51 : SetDefineMode(false);
6160 :
6161 : // Write values.
6162 51 : CPLDebug("GDAL_netCDF", "Writing lat values");
6163 :
6164 51 : int status = nc_put_vara_double(cdfid, nVarLatID, startLat,
6165 : countLat, padLatVal);
6166 51 : NCDF_ERR(status);
6167 :
6168 51 : CPLDebug("GDAL_netCDF", "Writing lon values");
6169 51 : status = nc_put_vara_double(cdfid, nVarLonID, startLon, countLon,
6170 : padLonVal);
6171 51 : NCDF_ERR(status);
6172 :
6173 : } // Not projected.
6174 :
6175 79 : CPLFree(padLatVal);
6176 79 : CPLFree(padLonVal);
6177 :
6178 79 : if (pfnProgress)
6179 38 : pfnProgress(1.00, nullptr, pProgressData);
6180 : }
6181 :
6182 158 : if (hDS_X != nullptr)
6183 : {
6184 10 : GDALClose(hDS_X);
6185 : }
6186 158 : if (hDS_Y != nullptr)
6187 : {
6188 10 : GDALClose(hDS_Y);
6189 : }
6190 :
6191 158 : return CE_None;
6192 : }
6193 :
6194 : // Write Projection variable to band variable.
6195 : // Moved from AddProjectionVars() for cases when bands are added after
6196 : // projection.
6197 365 : bool netCDFDataset::AddGridMappingRef()
6198 : {
6199 365 : bool bRet = true;
6200 365 : bool bOldDefineMode = bDefineMode;
6201 :
6202 549 : if ((GetAccess() == GA_Update) && (nBands >= 1) && (GetRasterBand(1)) &&
6203 184 : ((pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, "")) ||
6204 178 : (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))))
6205 : {
6206 70 : bAddedGridMappingRef = true;
6207 :
6208 : // Make sure we are in define mode.
6209 70 : SetDefineMode(true);
6210 :
6211 186 : for (int i = 1; i <= nBands; i++)
6212 : {
6213 : const int nVarId =
6214 116 : static_cast<netCDFRasterBand *>(GetRasterBand(i))->nZId;
6215 :
6216 116 : if (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))
6217 : {
6218 : int status =
6219 224 : nc_put_att_text(cdfid, nVarId, CF_GRD_MAPPING,
6220 112 : strlen(pszCFProjection), pszCFProjection);
6221 112 : NCDF_ERR(status);
6222 112 : if (status != NC_NOERR)
6223 0 : bRet = false;
6224 : }
6225 116 : if (pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, ""))
6226 : {
6227 : int status =
6228 6 : nc_put_att_text(cdfid, nVarId, CF_COORDINATES,
6229 : strlen(pszCFCoordinates), pszCFCoordinates);
6230 6 : NCDF_ERR(status);
6231 6 : if (status != NC_NOERR)
6232 0 : bRet = false;
6233 : }
6234 : }
6235 :
6236 : // Go back to previous define mode.
6237 70 : SetDefineMode(bOldDefineMode);
6238 : }
6239 365 : return bRet;
6240 : }
6241 :
6242 : /************************************************************************/
6243 : /* GetGeoTransform() */
6244 : /************************************************************************/
6245 :
6246 107 : CPLErr netCDFDataset::GetGeoTransform(double *padfTransform)
6247 :
6248 : {
6249 107 : memcpy(padfTransform, m_adfGeoTransform, sizeof(double) * 6);
6250 107 : if (m_bHasGeoTransform)
6251 77 : return CE_None;
6252 :
6253 30 : return GDALPamDataset::GetGeoTransform(padfTransform);
6254 : }
6255 :
6256 : /************************************************************************/
6257 : /* rint() */
6258 : /************************************************************************/
6259 :
6260 0 : double netCDFDataset::rint(double dfX)
6261 : {
6262 0 : return std::round(dfX);
6263 : }
6264 :
6265 : /************************************************************************/
6266 : /* NCDFReadIsoMetadata() */
6267 : /************************************************************************/
6268 :
6269 16 : static void NCDFReadMetadataAsJson(int cdfid, CPLJSONObject &obj)
6270 : {
6271 16 : int nbAttr = 0;
6272 16 : NCDF_ERR(nc_inq_varnatts(cdfid, NC_GLOBAL, &nbAttr));
6273 :
6274 32 : std::map<std::string, CPLJSONArray> oMapNameToArray;
6275 40 : for (int l = 0; l < nbAttr; l++)
6276 : {
6277 : char szAttrName[NC_MAX_NAME + 1];
6278 24 : szAttrName[0] = 0;
6279 24 : NCDF_ERR(nc_inq_attname(cdfid, NC_GLOBAL, l, szAttrName));
6280 :
6281 24 : char *pszMetaValue = nullptr;
6282 24 : if (NCDFGetAttr(cdfid, NC_GLOBAL, szAttrName, &pszMetaValue) == CE_None)
6283 : {
6284 24 : nc_type nAttrType = NC_NAT;
6285 24 : size_t nAttrLen = 0;
6286 :
6287 24 : NCDF_ERR(nc_inq_att(cdfid, NC_GLOBAL, szAttrName, &nAttrType,
6288 : &nAttrLen));
6289 :
6290 24 : std::string osAttrName(szAttrName);
6291 24 : const auto sharpPos = osAttrName.find('#');
6292 24 : if (sharpPos == std::string::npos)
6293 : {
6294 16 : if (nAttrType == NC_DOUBLE || nAttrType == NC_FLOAT)
6295 4 : obj.Add(osAttrName, CPLAtof(pszMetaValue));
6296 : else
6297 12 : obj.Add(osAttrName, pszMetaValue);
6298 : }
6299 : else
6300 : {
6301 8 : osAttrName.resize(sharpPos);
6302 8 : auto iter = oMapNameToArray.find(osAttrName);
6303 8 : if (iter == oMapNameToArray.end())
6304 : {
6305 8 : CPLJSONArray array;
6306 4 : obj.Add(osAttrName, array);
6307 4 : oMapNameToArray[osAttrName] = array;
6308 4 : array.Add(pszMetaValue);
6309 : }
6310 : else
6311 : {
6312 4 : iter->second.Add(pszMetaValue);
6313 : }
6314 : }
6315 24 : CPLFree(pszMetaValue);
6316 24 : pszMetaValue = nullptr;
6317 : }
6318 : }
6319 :
6320 16 : int nSubGroups = 0;
6321 16 : int *panSubGroupIds = nullptr;
6322 16 : NCDFGetSubGroups(cdfid, &nSubGroups, &panSubGroupIds);
6323 16 : oMapNameToArray.clear();
6324 28 : for (int i = 0; i < nSubGroups; i++)
6325 : {
6326 24 : CPLJSONObject subObj;
6327 12 : NCDFReadMetadataAsJson(panSubGroupIds[i], subObj);
6328 :
6329 24 : std::string osGroupName;
6330 12 : osGroupName.resize(NC_MAX_NAME);
6331 12 : NCDF_ERR(nc_inq_grpname(panSubGroupIds[i], &osGroupName[0]));
6332 12 : osGroupName.resize(strlen(osGroupName.data()));
6333 12 : const auto sharpPos = osGroupName.find('#');
6334 12 : if (sharpPos == std::string::npos)
6335 : {
6336 4 : obj.Add(osGroupName, subObj);
6337 : }
6338 : else
6339 : {
6340 8 : osGroupName.resize(sharpPos);
6341 8 : auto iter = oMapNameToArray.find(osGroupName);
6342 8 : if (iter == oMapNameToArray.end())
6343 : {
6344 8 : CPLJSONArray array;
6345 4 : obj.Add(osGroupName, array);
6346 4 : oMapNameToArray[osGroupName] = array;
6347 4 : array.Add(subObj);
6348 : }
6349 : else
6350 : {
6351 4 : iter->second.Add(subObj);
6352 : }
6353 : }
6354 : }
6355 16 : CPLFree(panSubGroupIds);
6356 16 : }
6357 :
6358 4 : std::string NCDFReadMetadataAsJson(int cdfid)
6359 : {
6360 8 : CPLJSONDocument oDoc;
6361 8 : CPLJSONObject oRoot = oDoc.GetRoot();
6362 4 : NCDFReadMetadataAsJson(cdfid, oRoot);
6363 8 : return oDoc.SaveAsString();
6364 : }
6365 :
6366 : /************************************************************************/
6367 : /* ReadAttributes() */
6368 : /************************************************************************/
6369 1748 : CPLErr netCDFDataset::ReadAttributes(int cdfidIn, int var)
6370 :
6371 : {
6372 1748 : char *pszVarFullName = nullptr;
6373 1748 : ERR_RET(NCDFGetVarFullName(cdfidIn, var, &pszVarFullName));
6374 :
6375 : // For metadata in Sentinel 5
6376 1748 : if (STARTS_WITH(pszVarFullName, "/METADATA/"))
6377 : {
6378 6 : for (const char *key :
6379 : {"ISO_METADATA", "ESA_METADATA", "EOP_METADATA", "QA_STATISTICS",
6380 8 : "GRANULE_DESCRIPTION", "ALGORITHM_SETTINGS"})
6381 : {
6382 14 : if (var == NC_GLOBAL &&
6383 7 : strcmp(pszVarFullName,
6384 : CPLSPrintf("/METADATA/%s/NC_GLOBAL", key)) == 0)
6385 : {
6386 1 : CPLFree(pszVarFullName);
6387 1 : CPLStringList aosList;
6388 2 : aosList.AddString(CPLString(NCDFReadMetadataAsJson(cdfidIn))
6389 1 : .replaceAll("\\/", '/'));
6390 1 : m_oMapDomainToJSon[key] = std::move(aosList);
6391 1 : return CE_None;
6392 : }
6393 : }
6394 : }
6395 1747 : if (STARTS_WITH(pszVarFullName, "/PRODUCT/SUPPORT_DATA/"))
6396 : {
6397 0 : CPLFree(pszVarFullName);
6398 0 : CPLStringList aosList;
6399 : aosList.AddString(
6400 0 : CPLString(NCDFReadMetadataAsJson(cdfidIn)).replaceAll("\\/", '/'));
6401 0 : m_oMapDomainToJSon["SUPPORT_DATA"] = std::move(aosList);
6402 0 : return CE_None;
6403 : }
6404 :
6405 1747 : size_t nMetaNameSize =
6406 1747 : sizeof(char) * (strlen(pszVarFullName) + 1 + NC_MAX_NAME + 1);
6407 1747 : char *pszMetaName = static_cast<char *>(CPLMalloc(nMetaNameSize));
6408 :
6409 1747 : int nbAttr = 0;
6410 1747 : NCDF_ERR(nc_inq_varnatts(cdfidIn, var, &nbAttr));
6411 :
6412 8748 : for (int l = 0; l < nbAttr; l++)
6413 : {
6414 : char szAttrName[NC_MAX_NAME + 1];
6415 7001 : szAttrName[0] = 0;
6416 7001 : NCDF_ERR(nc_inq_attname(cdfidIn, var, l, szAttrName));
6417 7001 : snprintf(pszMetaName, nMetaNameSize, "%s#%s", pszVarFullName,
6418 : szAttrName);
6419 :
6420 7001 : char *pszMetaTemp = nullptr;
6421 7001 : if (NCDFGetAttr(cdfidIn, var, szAttrName, &pszMetaTemp) == CE_None)
6422 : {
6423 7000 : papszMetadata =
6424 7000 : CSLSetNameValue(papszMetadata, pszMetaName, pszMetaTemp);
6425 7000 : CPLFree(pszMetaTemp);
6426 7000 : pszMetaTemp = nullptr;
6427 : }
6428 : else
6429 : {
6430 1 : CPLDebug("GDAL_netCDF", "invalid metadata %s", pszMetaName);
6431 : }
6432 : }
6433 :
6434 1747 : CPLFree(pszVarFullName);
6435 1747 : CPLFree(pszMetaName);
6436 :
6437 1747 : if (var == NC_GLOBAL)
6438 : {
6439 : // Recurse on sub-groups.
6440 514 : int nSubGroups = 0;
6441 514 : int *panSubGroupIds = nullptr;
6442 514 : NCDFGetSubGroups(cdfidIn, &nSubGroups, &panSubGroupIds);
6443 543 : for (int i = 0; i < nSubGroups; i++)
6444 : {
6445 29 : ReadAttributes(panSubGroupIds[i], var);
6446 : }
6447 514 : CPLFree(panSubGroupIds);
6448 : }
6449 :
6450 1747 : return CE_None;
6451 : }
6452 :
6453 : /************************************************************************/
6454 : /* netCDFDataset::CreateSubDatasetList() */
6455 : /************************************************************************/
6456 50 : void netCDFDataset::CreateSubDatasetList(int nGroupId)
6457 : {
6458 : char szVarStdName[NC_MAX_NAME + 1];
6459 50 : int *ponDimIds = nullptr;
6460 : nc_type nAttype;
6461 : size_t nAttlen;
6462 :
6463 50 : netCDFDataset *poDS = this;
6464 :
6465 : int nVarCount;
6466 50 : nc_inq_nvars(nGroupId, &nVarCount);
6467 :
6468 324 : for (int nVar = 0; nVar < nVarCount; nVar++)
6469 : {
6470 :
6471 : int nDims;
6472 274 : nc_inq_varndims(nGroupId, nVar, &nDims);
6473 :
6474 274 : if (nDims >= 2)
6475 : {
6476 155 : ponDimIds = static_cast<int *>(CPLCalloc(nDims, sizeof(int)));
6477 155 : nc_inq_vardimid(nGroupId, nVar, ponDimIds);
6478 :
6479 : // Create Sub dataset list.
6480 155 : CPLString osDim;
6481 480 : for (int i = 0; i < nDims; i++)
6482 : {
6483 : size_t nDimLen;
6484 325 : nc_inq_dimlen(nGroupId, ponDimIds[i], &nDimLen);
6485 325 : osDim += CPLSPrintf("%dx", (int)nDimLen);
6486 : }
6487 155 : CPLFree(ponDimIds);
6488 :
6489 : nc_type nVarType;
6490 155 : nc_inq_vartype(nGroupId, nVar, &nVarType);
6491 : // Get rid of the last "x" character.
6492 155 : osDim.pop_back();
6493 155 : const char *pszType = "";
6494 155 : switch (nVarType)
6495 : {
6496 37 : case NC_BYTE:
6497 37 : pszType = "8-bit integer";
6498 37 : break;
6499 2 : case NC_CHAR:
6500 2 : pszType = "8-bit character";
6501 2 : break;
6502 6 : case NC_SHORT:
6503 6 : pszType = "16-bit integer";
6504 6 : break;
6505 10 : case NC_INT:
6506 10 : pszType = "32-bit integer";
6507 10 : break;
6508 51 : case NC_FLOAT:
6509 51 : pszType = "32-bit floating-point";
6510 51 : break;
6511 31 : case NC_DOUBLE:
6512 31 : pszType = "64-bit floating-point";
6513 31 : break;
6514 4 : case NC_UBYTE:
6515 4 : pszType = "8-bit unsigned integer";
6516 4 : break;
6517 1 : case NC_USHORT:
6518 1 : pszType = "16-bit unsigned integer";
6519 1 : break;
6520 1 : case NC_UINT:
6521 1 : pszType = "32-bit unsigned integer";
6522 1 : break;
6523 1 : case NC_INT64:
6524 1 : pszType = "64-bit integer";
6525 1 : break;
6526 1 : case NC_UINT64:
6527 1 : pszType = "64-bit unsigned integer";
6528 1 : break;
6529 10 : default:
6530 10 : break;
6531 : }
6532 :
6533 155 : char *pszName = nullptr;
6534 155 : if (NCDFGetVarFullName(nGroupId, nVar, &pszName) != CE_None)
6535 0 : continue;
6536 :
6537 155 : nSubDatasets++;
6538 :
6539 155 : nAttlen = 0;
6540 155 : nc_inq_att(nGroupId, nVar, CF_STD_NAME, &nAttype, &nAttlen);
6541 310 : if (nAttlen < sizeof(szVarStdName) &&
6542 155 : nc_get_att_text(nGroupId, nVar, CF_STD_NAME, szVarStdName) ==
6543 : NC_NOERR)
6544 : {
6545 51 : szVarStdName[nAttlen] = '\0';
6546 : }
6547 : else
6548 : {
6549 104 : snprintf(szVarStdName, sizeof(szVarStdName), "%s", pszName);
6550 : }
6551 :
6552 : char szTemp[NC_MAX_NAME + 1];
6553 155 : snprintf(szTemp, sizeof(szTemp), "SUBDATASET_%d_NAME",
6554 : nSubDatasets);
6555 :
6556 155 : if (strchr(pszName, ' ') || strchr(pszName, ':'))
6557 : {
6558 1 : poDS->papszSubDatasets = CSLSetNameValue(
6559 : poDS->papszSubDatasets, szTemp,
6560 : CPLSPrintf("NETCDF:\"%s\":\"%s\"", poDS->osFilename.c_str(),
6561 : pszName));
6562 : }
6563 : else
6564 : {
6565 154 : poDS->papszSubDatasets = CSLSetNameValue(
6566 : poDS->papszSubDatasets, szTemp,
6567 : CPLSPrintf("NETCDF:\"%s\":%s", poDS->osFilename.c_str(),
6568 : pszName));
6569 : }
6570 :
6571 155 : CPLFree(pszName);
6572 :
6573 155 : snprintf(szTemp, sizeof(szTemp), "SUBDATASET_%d_DESC",
6574 : nSubDatasets);
6575 :
6576 155 : poDS->papszSubDatasets =
6577 155 : CSLSetNameValue(poDS->papszSubDatasets, szTemp,
6578 : CPLSPrintf("[%s] %s (%s)", osDim.c_str(),
6579 : szVarStdName, pszType));
6580 : }
6581 : }
6582 :
6583 : // Recurse on sub groups.
6584 50 : int nSubGroups = 0;
6585 50 : int *panSubGroupIds = nullptr;
6586 50 : NCDFGetSubGroups(nGroupId, &nSubGroups, &panSubGroupIds);
6587 55 : for (int i = 0; i < nSubGroups; i++)
6588 : {
6589 5 : CreateSubDatasetList(panSubGroupIds[i]);
6590 : }
6591 50 : CPLFree(panSubGroupIds);
6592 50 : }
6593 :
6594 : /************************************************************************/
6595 : /* TestCapability() */
6596 : /************************************************************************/
6597 :
6598 248 : int netCDFDataset::TestCapability(const char *pszCap)
6599 : {
6600 248 : if (EQUAL(pszCap, ODsCCreateLayer))
6601 : {
6602 223 : return eAccess == GA_Update && nBands == 0 &&
6603 218 : (eMultipleLayerBehavior != SINGLE_LAYER ||
6604 229 : this->GetLayerCount() == 0 || bSGSupport);
6605 : }
6606 136 : else if (EQUAL(pszCap, ODsCZGeometries))
6607 2 : return true;
6608 :
6609 134 : return false;
6610 : }
6611 :
6612 : /************************************************************************/
6613 : /* GetLayer() */
6614 : /************************************************************************/
6615 :
6616 384 : OGRLayer *netCDFDataset::GetLayer(int nIdx)
6617 : {
6618 384 : if (nIdx < 0 || nIdx >= this->GetLayerCount())
6619 2 : return nullptr;
6620 382 : return papoLayers[nIdx].get();
6621 : }
6622 :
6623 : /************************************************************************/
6624 : /* ICreateLayer() */
6625 : /************************************************************************/
6626 :
6627 59 : OGRLayer *netCDFDataset::ICreateLayer(const char *pszName,
6628 : const OGRGeomFieldDefn *poGeomFieldDefn,
6629 : CSLConstList papszOptions)
6630 : {
6631 59 : int nLayerCDFId = cdfid;
6632 59 : if (!TestCapability(ODsCCreateLayer))
6633 0 : return nullptr;
6634 :
6635 59 : const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
6636 : const auto poSpatialRef =
6637 59 : poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
6638 :
6639 118 : CPLString osNetCDFLayerName(pszName);
6640 59 : const netCDFWriterConfigLayer *poLayerConfig = nullptr;
6641 59 : if (oWriterConfig.m_bIsValid)
6642 : {
6643 : std::map<CPLString, netCDFWriterConfigLayer>::const_iterator
6644 2 : oLayerIter = oWriterConfig.m_oLayers.find(pszName);
6645 2 : if (oLayerIter != oWriterConfig.m_oLayers.end())
6646 : {
6647 1 : poLayerConfig = &(oLayerIter->second);
6648 1 : osNetCDFLayerName = poLayerConfig->m_osNetCDFName;
6649 : }
6650 : }
6651 :
6652 59 : netCDFDataset *poLayerDataset = nullptr;
6653 59 : if (eMultipleLayerBehavior == SEPARATE_FILES)
6654 : {
6655 2 : char **papszDatasetOptions = nullptr;
6656 2 : papszDatasetOptions = CSLSetNameValue(
6657 : papszDatasetOptions, "CONFIG_FILE",
6658 2 : CSLFetchNameValue(papszCreationOptions, "CONFIG_FILE"));
6659 : papszDatasetOptions =
6660 2 : CSLSetNameValue(papszDatasetOptions, "FORMAT",
6661 2 : CSLFetchNameValue(papszCreationOptions, "FORMAT"));
6662 2 : papszDatasetOptions = CSLSetNameValue(
6663 : papszDatasetOptions, "WRITE_GDAL_TAGS",
6664 2 : CSLFetchNameValue(papszCreationOptions, "WRITE_GDAL_TAGS"));
6665 : const CPLString osLayerFilename(
6666 2 : CPLFormFilenameSafe(osFilename, osNetCDFLayerName, "nc"));
6667 2 : CPLAcquireMutex(hNCMutex, 1000.0);
6668 2 : poLayerDataset =
6669 2 : CreateLL(osLayerFilename, 0, 0, 0, papszDatasetOptions);
6670 2 : CPLReleaseMutex(hNCMutex);
6671 2 : CSLDestroy(papszDatasetOptions);
6672 2 : if (poLayerDataset == nullptr)
6673 0 : return nullptr;
6674 :
6675 2 : nLayerCDFId = poLayerDataset->cdfid;
6676 2 : NCDFAddGDALHistory(nLayerCDFId, osLayerFilename, bWriteGDALVersion,
6677 2 : bWriteGDALHistory, "", "Create",
6678 : NCDF_CONVENTIONS_CF_V1_6);
6679 : }
6680 57 : else if (eMultipleLayerBehavior == SEPARATE_GROUPS)
6681 : {
6682 2 : SetDefineMode(true);
6683 :
6684 2 : nLayerCDFId = -1;
6685 2 : int status = nc_def_grp(cdfid, osNetCDFLayerName, &nLayerCDFId);
6686 2 : NCDF_ERR(status);
6687 2 : if (status != NC_NOERR)
6688 0 : return nullptr;
6689 :
6690 2 : NCDFAddGDALHistory(nLayerCDFId, osFilename, bWriteGDALVersion,
6691 2 : bWriteGDALHistory, "", "Create",
6692 : NCDF_CONVENTIONS_CF_V1_6);
6693 : }
6694 :
6695 : // Make a clone to workaround a bug in released MapServer versions
6696 : // that destroys the passed SRS instead of releasing it .
6697 59 : OGRSpatialReference *poSRS = nullptr;
6698 59 : if (poSpatialRef)
6699 : {
6700 43 : poSRS = poSpatialRef->Clone();
6701 43 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
6702 : }
6703 : std::shared_ptr<netCDFLayer> poLayer(
6704 59 : new netCDFLayer(poLayerDataset ? poLayerDataset : this, nLayerCDFId,
6705 118 : osNetCDFLayerName, eGType, poSRS));
6706 59 : if (poSRS != nullptr)
6707 43 : poSRS->Release();
6708 :
6709 : // Fetch layer creation options coming from config file
6710 59 : char **papszNewOptions = CSLDuplicate(papszOptions);
6711 59 : if (oWriterConfig.m_bIsValid)
6712 : {
6713 2 : std::map<CPLString, CPLString>::const_iterator oIter;
6714 3 : for (oIter = oWriterConfig.m_oLayerCreationOptions.begin();
6715 3 : oIter != oWriterConfig.m_oLayerCreationOptions.end(); ++oIter)
6716 : {
6717 : papszNewOptions =
6718 1 : CSLSetNameValue(papszNewOptions, oIter->first, oIter->second);
6719 : }
6720 2 : if (poLayerConfig != nullptr)
6721 : {
6722 3 : for (oIter = poLayerConfig->m_oLayerCreationOptions.begin();
6723 3 : oIter != poLayerConfig->m_oLayerCreationOptions.end(); ++oIter)
6724 : {
6725 2 : papszNewOptions = CSLSetNameValue(papszNewOptions, oIter->first,
6726 2 : oIter->second);
6727 : }
6728 : }
6729 : }
6730 :
6731 59 : const bool bRet = poLayer->Create(papszNewOptions, poLayerConfig);
6732 59 : CSLDestroy(papszNewOptions);
6733 :
6734 59 : if (!bRet)
6735 : {
6736 0 : return nullptr;
6737 : }
6738 :
6739 59 : if (poLayerDataset != nullptr)
6740 2 : apoVectorDatasets.push_back(poLayerDataset);
6741 :
6742 59 : papoLayers.push_back(poLayer);
6743 59 : return poLayer.get();
6744 : }
6745 :
6746 : /************************************************************************/
6747 : /* CloneAttributes() */
6748 : /************************************************************************/
6749 :
6750 137 : bool netCDFDataset::CloneAttributes(int old_cdfid, int new_cdfid, int nSrcVarId,
6751 : int nDstVarId)
6752 : {
6753 137 : int nAttCount = -1;
6754 137 : int status = nc_inq_varnatts(old_cdfid, nSrcVarId, &nAttCount);
6755 137 : NCDF_ERR(status);
6756 :
6757 693 : for (int i = 0; i < nAttCount; i++)
6758 : {
6759 : char szName[NC_MAX_NAME + 1];
6760 556 : szName[0] = 0;
6761 556 : status = nc_inq_attname(old_cdfid, nSrcVarId, i, szName);
6762 556 : NCDF_ERR(status);
6763 :
6764 : status =
6765 556 : nc_copy_att(old_cdfid, nSrcVarId, szName, new_cdfid, nDstVarId);
6766 556 : NCDF_ERR(status);
6767 556 : if (status != NC_NOERR)
6768 0 : return false;
6769 : }
6770 :
6771 137 : return true;
6772 : }
6773 :
6774 : /************************************************************************/
6775 : /* CloneVariableContent() */
6776 : /************************************************************************/
6777 :
6778 121 : bool netCDFDataset::CloneVariableContent(int old_cdfid, int new_cdfid,
6779 : int nSrcVarId, int nDstVarId)
6780 : {
6781 121 : int nVarDimCount = -1;
6782 121 : int status = nc_inq_varndims(old_cdfid, nSrcVarId, &nVarDimCount);
6783 121 : NCDF_ERR(status);
6784 121 : int anDimIds[] = {-1, 1};
6785 121 : status = nc_inq_vardimid(old_cdfid, nSrcVarId, anDimIds);
6786 121 : NCDF_ERR(status);
6787 121 : nc_type nc_datatype = NC_NAT;
6788 121 : status = nc_inq_vartype(old_cdfid, nSrcVarId, &nc_datatype);
6789 121 : NCDF_ERR(status);
6790 121 : size_t nTypeSize = 0;
6791 121 : switch (nc_datatype)
6792 : {
6793 35 : case NC_BYTE:
6794 : case NC_CHAR:
6795 35 : nTypeSize = 1;
6796 35 : break;
6797 4 : case NC_SHORT:
6798 4 : nTypeSize = 2;
6799 4 : break;
6800 24 : case NC_INT:
6801 24 : nTypeSize = 4;
6802 24 : break;
6803 4 : case NC_FLOAT:
6804 4 : nTypeSize = 4;
6805 4 : break;
6806 43 : case NC_DOUBLE:
6807 43 : nTypeSize = 8;
6808 43 : break;
6809 2 : case NC_UBYTE:
6810 2 : nTypeSize = 1;
6811 2 : break;
6812 2 : case NC_USHORT:
6813 2 : nTypeSize = 2;
6814 2 : break;
6815 2 : case NC_UINT:
6816 2 : nTypeSize = 4;
6817 2 : break;
6818 4 : case NC_INT64:
6819 : case NC_UINT64:
6820 4 : nTypeSize = 8;
6821 4 : break;
6822 1 : case NC_STRING:
6823 1 : nTypeSize = sizeof(char *);
6824 1 : break;
6825 0 : default:
6826 : {
6827 0 : CPLError(CE_Failure, CPLE_NotSupported, "Unsupported data type: %d",
6828 : nc_datatype);
6829 0 : return false;
6830 : }
6831 : }
6832 :
6833 121 : size_t nElems = 1;
6834 : size_t anStart[NC_MAX_DIMS];
6835 : size_t anCount[NC_MAX_DIMS];
6836 121 : size_t nRecords = 1;
6837 261 : for (int i = 0; i < nVarDimCount; i++)
6838 : {
6839 140 : anStart[i] = 0;
6840 140 : if (i == 0)
6841 : {
6842 116 : anCount[i] = 1;
6843 116 : status = nc_inq_dimlen(old_cdfid, anDimIds[i], &nRecords);
6844 116 : NCDF_ERR(status);
6845 : }
6846 : else
6847 : {
6848 24 : anCount[i] = 0;
6849 24 : status = nc_inq_dimlen(old_cdfid, anDimIds[i], &anCount[i]);
6850 24 : NCDF_ERR(status);
6851 24 : nElems *= anCount[i];
6852 : }
6853 : }
6854 :
6855 : /* Workaround in some cases a netCDF bug:
6856 : * https://github.com/Unidata/netcdf-c/pull/1442 */
6857 121 : if (nRecords > 0 && nRecords < 10 * 1000 * 1000 / (nElems * nTypeSize))
6858 : {
6859 119 : nElems *= nRecords;
6860 119 : anCount[0] = nRecords;
6861 119 : nRecords = 1;
6862 : }
6863 :
6864 121 : void *pBuffer = VSI_MALLOC2_VERBOSE(nElems, nTypeSize);
6865 121 : if (pBuffer == nullptr)
6866 0 : return false;
6867 :
6868 240 : for (size_t iRecord = 0; iRecord < nRecords; iRecord++)
6869 : {
6870 119 : anStart[0] = iRecord;
6871 :
6872 119 : switch (nc_datatype)
6873 : {
6874 5 : case NC_BYTE:
6875 : status =
6876 5 : nc_get_vara_schar(old_cdfid, nSrcVarId, anStart, anCount,
6877 : static_cast<signed char *>(pBuffer));
6878 5 : if (!status)
6879 5 : status = nc_put_vara_schar(
6880 : new_cdfid, nDstVarId, anStart, anCount,
6881 : static_cast<signed char *>(pBuffer));
6882 5 : break;
6883 28 : case NC_CHAR:
6884 : status =
6885 28 : nc_get_vara_text(old_cdfid, nSrcVarId, anStart, anCount,
6886 : static_cast<char *>(pBuffer));
6887 28 : if (!status)
6888 : status =
6889 28 : nc_put_vara_text(new_cdfid, nDstVarId, anStart, anCount,
6890 : static_cast<char *>(pBuffer));
6891 28 : break;
6892 4 : case NC_SHORT:
6893 : status =
6894 4 : nc_get_vara_short(old_cdfid, nSrcVarId, anStart, anCount,
6895 : static_cast<short *>(pBuffer));
6896 4 : if (!status)
6897 4 : status = nc_put_vara_short(new_cdfid, nDstVarId, anStart,
6898 : anCount,
6899 : static_cast<short *>(pBuffer));
6900 4 : break;
6901 24 : case NC_INT:
6902 24 : status = nc_get_vara_int(old_cdfid, nSrcVarId, anStart, anCount,
6903 : static_cast<int *>(pBuffer));
6904 24 : if (!status)
6905 : status =
6906 24 : nc_put_vara_int(new_cdfid, nDstVarId, anStart, anCount,
6907 : static_cast<int *>(pBuffer));
6908 24 : break;
6909 4 : case NC_FLOAT:
6910 : status =
6911 4 : nc_get_vara_float(old_cdfid, nSrcVarId, anStart, anCount,
6912 : static_cast<float *>(pBuffer));
6913 4 : if (!status)
6914 4 : status = nc_put_vara_float(new_cdfid, nDstVarId, anStart,
6915 : anCount,
6916 : static_cast<float *>(pBuffer));
6917 4 : break;
6918 43 : case NC_DOUBLE:
6919 : status =
6920 43 : nc_get_vara_double(old_cdfid, nSrcVarId, anStart, anCount,
6921 : static_cast<double *>(pBuffer));
6922 43 : if (!status)
6923 43 : status = nc_put_vara_double(new_cdfid, nDstVarId, anStart,
6924 : anCount,
6925 : static_cast<double *>(pBuffer));
6926 43 : break;
6927 1 : case NC_STRING:
6928 : status =
6929 1 : nc_get_vara_string(old_cdfid, nSrcVarId, anStart, anCount,
6930 : static_cast<char **>(pBuffer));
6931 1 : if (!status)
6932 : {
6933 1 : status = nc_put_vara_string(
6934 : new_cdfid, nDstVarId, anStart, anCount,
6935 : static_cast<const char **>(pBuffer));
6936 1 : nc_free_string(nElems, static_cast<char **>(pBuffer));
6937 : }
6938 1 : break;
6939 :
6940 2 : case NC_UBYTE:
6941 : status =
6942 2 : nc_get_vara_uchar(old_cdfid, nSrcVarId, anStart, anCount,
6943 : static_cast<unsigned char *>(pBuffer));
6944 2 : if (!status)
6945 2 : status = nc_put_vara_uchar(
6946 : new_cdfid, nDstVarId, anStart, anCount,
6947 : static_cast<unsigned char *>(pBuffer));
6948 2 : break;
6949 2 : case NC_USHORT:
6950 : status =
6951 2 : nc_get_vara_ushort(old_cdfid, nSrcVarId, anStart, anCount,
6952 : static_cast<unsigned short *>(pBuffer));
6953 2 : if (!status)
6954 2 : status = nc_put_vara_ushort(
6955 : new_cdfid, nDstVarId, anStart, anCount,
6956 : static_cast<unsigned short *>(pBuffer));
6957 2 : break;
6958 2 : case NC_UINT:
6959 : status =
6960 2 : nc_get_vara_uint(old_cdfid, nSrcVarId, anStart, anCount,
6961 : static_cast<unsigned int *>(pBuffer));
6962 2 : if (!status)
6963 : status =
6964 2 : nc_put_vara_uint(new_cdfid, nDstVarId, anStart, anCount,
6965 : static_cast<unsigned int *>(pBuffer));
6966 2 : break;
6967 2 : case NC_INT64:
6968 : status =
6969 2 : nc_get_vara_longlong(old_cdfid, nSrcVarId, anStart, anCount,
6970 : static_cast<long long *>(pBuffer));
6971 2 : if (!status)
6972 2 : status = nc_put_vara_longlong(
6973 : new_cdfid, nDstVarId, anStart, anCount,
6974 : static_cast<long long *>(pBuffer));
6975 2 : break;
6976 2 : case NC_UINT64:
6977 2 : status = nc_get_vara_ulonglong(
6978 : old_cdfid, nSrcVarId, anStart, anCount,
6979 : static_cast<unsigned long long *>(pBuffer));
6980 2 : if (!status)
6981 2 : status = nc_put_vara_ulonglong(
6982 : new_cdfid, nDstVarId, anStart, anCount,
6983 : static_cast<unsigned long long *>(pBuffer));
6984 2 : break;
6985 0 : default:
6986 0 : status = NC_EBADTYPE;
6987 : }
6988 :
6989 119 : NCDF_ERR(status);
6990 119 : if (status != NC_NOERR)
6991 : {
6992 0 : VSIFree(pBuffer);
6993 0 : return false;
6994 : }
6995 : }
6996 :
6997 121 : VSIFree(pBuffer);
6998 121 : return true;
6999 : }
7000 :
7001 : /************************************************************************/
7002 : /* NCDFIsUnlimitedDim() */
7003 : /************************************************************************/
7004 :
7005 58 : bool NCDFIsUnlimitedDim(bool bIsNC4, int cdfid, int nDimId)
7006 : {
7007 58 : if (bIsNC4)
7008 : {
7009 16 : int nUnlimitedDims = 0;
7010 16 : nc_inq_unlimdims(cdfid, &nUnlimitedDims, nullptr);
7011 16 : bool bFound = false;
7012 16 : if (nUnlimitedDims)
7013 : {
7014 : int *panUnlimitedDimIds =
7015 16 : static_cast<int *>(CPLMalloc(sizeof(int) * nUnlimitedDims));
7016 16 : nc_inq_unlimdims(cdfid, nullptr, panUnlimitedDimIds);
7017 30 : for (int i = 0; i < nUnlimitedDims; i++)
7018 : {
7019 22 : if (panUnlimitedDimIds[i] == nDimId)
7020 : {
7021 8 : bFound = true;
7022 8 : break;
7023 : }
7024 : }
7025 16 : CPLFree(panUnlimitedDimIds);
7026 : }
7027 16 : return bFound;
7028 : }
7029 : else
7030 : {
7031 42 : int nUnlimitedDimId = -1;
7032 42 : nc_inq(cdfid, nullptr, nullptr, nullptr, &nUnlimitedDimId);
7033 42 : return nDimId == nUnlimitedDimId;
7034 : }
7035 : }
7036 :
7037 : /************************************************************************/
7038 : /* CloneGrp() */
7039 : /************************************************************************/
7040 :
7041 16 : bool netCDFDataset::CloneGrp(int nOldGrpId, int nNewGrpId, bool bIsNC4,
7042 : int nLayerId, int nDimIdToGrow, size_t nNewSize)
7043 : {
7044 : // Clone dimensions
7045 16 : int nDimCount = -1;
7046 16 : int status = nc_inq_ndims(nOldGrpId, &nDimCount);
7047 16 : NCDF_ERR(status);
7048 16 : if (nDimCount < 0 || nDimCount > NC_MAX_DIMS)
7049 0 : return false;
7050 : int anDimIds[NC_MAX_DIMS];
7051 16 : int nUnlimiDimID = -1;
7052 16 : status = nc_inq_unlimdim(nOldGrpId, &nUnlimiDimID);
7053 16 : NCDF_ERR(status);
7054 16 : if (bIsNC4)
7055 : {
7056 : // In NC4, the dimension ids of a group are not necessarily in
7057 : // [0,nDimCount-1] range
7058 8 : int nDimCount2 = -1;
7059 8 : status = nc_inq_dimids(nOldGrpId, &nDimCount2, anDimIds, FALSE);
7060 8 : NCDF_ERR(status);
7061 8 : CPLAssert(nDimCount == nDimCount2);
7062 : }
7063 : else
7064 : {
7065 36 : for (int i = 0; i < nDimCount; i++)
7066 28 : anDimIds[i] = i;
7067 : }
7068 60 : for (int i = 0; i < nDimCount; i++)
7069 : {
7070 : char szDimName[NC_MAX_NAME + 1];
7071 44 : szDimName[0] = 0;
7072 44 : size_t nLen = 0;
7073 44 : const int nDimId = anDimIds[i];
7074 44 : status = nc_inq_dim(nOldGrpId, nDimId, szDimName, &nLen);
7075 44 : NCDF_ERR(status);
7076 44 : if (NCDFIsUnlimitedDim(bIsNC4, nOldGrpId, nDimId))
7077 16 : nLen = NC_UNLIMITED;
7078 28 : else if (nDimId == nDimIdToGrow && nOldGrpId == nLayerId)
7079 13 : nLen = nNewSize;
7080 44 : int nNewDimId = -1;
7081 44 : status = nc_def_dim(nNewGrpId, szDimName, nLen, &nNewDimId);
7082 44 : NCDF_ERR(status);
7083 44 : CPLAssert(nDimId == nNewDimId);
7084 44 : if (status != NC_NOERR)
7085 : {
7086 0 : return false;
7087 : }
7088 : }
7089 :
7090 : // Clone main attributes
7091 16 : if (!CloneAttributes(nOldGrpId, nNewGrpId, NC_GLOBAL, NC_GLOBAL))
7092 : {
7093 0 : return false;
7094 : }
7095 :
7096 : // Clone variable definitions
7097 16 : int nVarCount = -1;
7098 16 : status = nc_inq_nvars(nOldGrpId, &nVarCount);
7099 16 : NCDF_ERR(status);
7100 :
7101 137 : for (int i = 0; i < nVarCount; i++)
7102 : {
7103 : char szVarName[NC_MAX_NAME + 1];
7104 121 : szVarName[0] = 0;
7105 121 : status = nc_inq_varname(nOldGrpId, i, szVarName);
7106 121 : NCDF_ERR(status);
7107 121 : nc_type nc_datatype = NC_NAT;
7108 121 : status = nc_inq_vartype(nOldGrpId, i, &nc_datatype);
7109 121 : NCDF_ERR(status);
7110 121 : int nVarDimCount = -1;
7111 121 : status = nc_inq_varndims(nOldGrpId, i, &nVarDimCount);
7112 121 : NCDF_ERR(status);
7113 121 : status = nc_inq_vardimid(nOldGrpId, i, anDimIds);
7114 121 : NCDF_ERR(status);
7115 121 : int nNewVarId = -1;
7116 121 : status = nc_def_var(nNewGrpId, szVarName, nc_datatype, nVarDimCount,
7117 : anDimIds, &nNewVarId);
7118 121 : NCDF_ERR(status);
7119 121 : CPLAssert(i == nNewVarId);
7120 121 : if (status != NC_NOERR)
7121 : {
7122 0 : return false;
7123 : }
7124 :
7125 121 : if (!CloneAttributes(nOldGrpId, nNewGrpId, i, i))
7126 : {
7127 0 : return false;
7128 : }
7129 : }
7130 :
7131 16 : status = nc_enddef(nNewGrpId);
7132 16 : NCDF_ERR(status);
7133 16 : if (status != NC_NOERR)
7134 : {
7135 0 : return false;
7136 : }
7137 :
7138 : // Clone variable content
7139 137 : for (int i = 0; i < nVarCount; i++)
7140 : {
7141 121 : if (!CloneVariableContent(nOldGrpId, nNewGrpId, i, i))
7142 : {
7143 0 : return false;
7144 : }
7145 : }
7146 :
7147 16 : return true;
7148 : }
7149 :
7150 : /************************************************************************/
7151 : /* GrowDim() */
7152 : /************************************************************************/
7153 :
7154 13 : bool netCDFDataset::GrowDim(int nLayerId, int nDimIdToGrow, size_t nNewSize)
7155 : {
7156 : int nCreationMode;
7157 : // Set nCreationMode based on eFormat.
7158 13 : switch (eFormat)
7159 : {
7160 : #ifdef NETCDF_HAS_NC2
7161 0 : case NCDF_FORMAT_NC2:
7162 0 : nCreationMode = NC_CLOBBER | NC_64BIT_OFFSET;
7163 0 : break;
7164 : #endif
7165 5 : case NCDF_FORMAT_NC4:
7166 5 : nCreationMode = NC_CLOBBER | NC_NETCDF4;
7167 5 : break;
7168 0 : case NCDF_FORMAT_NC4C:
7169 0 : nCreationMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
7170 0 : break;
7171 8 : case NCDF_FORMAT_NC:
7172 : default:
7173 8 : nCreationMode = NC_CLOBBER;
7174 8 : break;
7175 : }
7176 :
7177 13 : int new_cdfid = -1;
7178 26 : CPLString osTmpFilename(osFilename + ".tmp");
7179 26 : CPLString osFilenameForNCCreate(osTmpFilename);
7180 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
7181 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
7182 : {
7183 : char *pszTemp =
7184 : CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
7185 : osFilenameForNCCreate = pszTemp;
7186 : CPLFree(pszTemp);
7187 : }
7188 : #endif
7189 13 : int status = nc_create(osFilenameForNCCreate, nCreationMode, &new_cdfid);
7190 13 : NCDF_ERR(status);
7191 13 : if (status != NC_NOERR)
7192 0 : return false;
7193 :
7194 13 : if (!CloneGrp(cdfid, new_cdfid, eFormat == NCDF_FORMAT_NC4, nLayerId,
7195 : nDimIdToGrow, nNewSize))
7196 : {
7197 0 : GDAL_nc_close(new_cdfid);
7198 0 : return false;
7199 : }
7200 :
7201 13 : int nGroupCount = 0;
7202 26 : std::vector<CPLString> oListGrpName;
7203 31 : if (eFormat == NCDF_FORMAT_NC4 &&
7204 18 : nc_inq_grps(cdfid, &nGroupCount, nullptr) == NC_NOERR &&
7205 5 : nGroupCount > 0)
7206 : {
7207 : int *panGroupIds =
7208 2 : static_cast<int *>(CPLMalloc(sizeof(int) * nGroupCount));
7209 2 : status = nc_inq_grps(cdfid, nullptr, panGroupIds);
7210 2 : NCDF_ERR(status);
7211 5 : for (int i = 0; i < nGroupCount; i++)
7212 : {
7213 : char szGroupName[NC_MAX_NAME + 1];
7214 3 : szGroupName[0] = 0;
7215 3 : NCDF_ERR(nc_inq_grpname(panGroupIds[i], szGroupName));
7216 3 : int nNewGrpId = -1;
7217 3 : status = nc_def_grp(new_cdfid, szGroupName, &nNewGrpId);
7218 3 : NCDF_ERR(status);
7219 3 : if (status != NC_NOERR)
7220 : {
7221 0 : CPLFree(panGroupIds);
7222 0 : GDAL_nc_close(new_cdfid);
7223 0 : return false;
7224 : }
7225 3 : if (!CloneGrp(panGroupIds[i], nNewGrpId, /*bIsNC4=*/true, nLayerId,
7226 : nDimIdToGrow, nNewSize))
7227 : {
7228 0 : CPLFree(panGroupIds);
7229 0 : GDAL_nc_close(new_cdfid);
7230 0 : return false;
7231 : }
7232 : }
7233 2 : CPLFree(panGroupIds);
7234 :
7235 5 : for (int i = 0; i < this->GetLayerCount(); i++)
7236 : {
7237 3 : auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
7238 3 : if (poLayer)
7239 : {
7240 : char szGroupName[NC_MAX_NAME + 1];
7241 3 : szGroupName[0] = 0;
7242 3 : status = nc_inq_grpname(poLayer->GetCDFID(), szGroupName);
7243 3 : NCDF_ERR(status);
7244 3 : oListGrpName.push_back(szGroupName);
7245 : }
7246 : }
7247 : }
7248 :
7249 13 : GDAL_nc_close(cdfid);
7250 13 : cdfid = -1;
7251 13 : GDAL_nc_close(new_cdfid);
7252 :
7253 26 : CPLString osOriFilename(osFilename + ".ori");
7254 26 : if (VSIRename(osFilename, osOriFilename) != 0 ||
7255 13 : VSIRename(osTmpFilename, osFilename) != 0)
7256 : {
7257 0 : CPLError(CE_Failure, CPLE_FileIO, "Renaming of files failed");
7258 0 : return false;
7259 : }
7260 13 : VSIUnlink(osOriFilename);
7261 :
7262 26 : CPLString osFilenameForNCOpen(osFilename);
7263 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
7264 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
7265 : {
7266 : char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
7267 : osFilenameForNCOpen = pszTemp;
7268 : CPLFree(pszTemp);
7269 : }
7270 : #endif
7271 13 : status = GDAL_nc_open(osFilenameForNCOpen, NC_WRITE, &cdfid);
7272 13 : NCDF_ERR(status);
7273 13 : if (status != NC_NOERR)
7274 0 : return false;
7275 13 : bDefineMode = false;
7276 :
7277 13 : if (!oListGrpName.empty())
7278 : {
7279 5 : for (int i = 0; i < this->GetLayerCount(); i++)
7280 : {
7281 3 : auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
7282 3 : if (poLayer)
7283 : {
7284 3 : int nNewLayerCDFID = -1;
7285 3 : status = nc_inq_ncid(cdfid, oListGrpName[i].c_str(),
7286 : &nNewLayerCDFID);
7287 3 : NCDF_ERR(status);
7288 3 : poLayer->SetCDFID(nNewLayerCDFID);
7289 : }
7290 : }
7291 : }
7292 : else
7293 : {
7294 22 : for (int i = 0; i < this->GetLayerCount(); i++)
7295 : {
7296 11 : auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
7297 11 : if (poLayer)
7298 11 : poLayer->SetCDFID(cdfid);
7299 : }
7300 : }
7301 :
7302 13 : return true;
7303 : }
7304 :
7305 : #ifdef ENABLE_NCDUMP
7306 :
7307 : /************************************************************************/
7308 : /* netCDFDatasetCreateTempFile() */
7309 : /************************************************************************/
7310 :
7311 : /* Create a netCDF file from a text dump (format of ncdump) */
7312 : /* Mostly to easy fuzzing of the driver, while still generating valid */
7313 : /* netCDF files. */
7314 : /* Note: not all data types are supported ! */
7315 4 : bool netCDFDatasetCreateTempFile(NetCDFFormatEnum eFormat,
7316 : const char *pszTmpFilename, VSILFILE *fpSrc)
7317 : {
7318 4 : CPL_IGNORE_RET_VAL(eFormat);
7319 4 : int nCreateMode = NC_CLOBBER;
7320 4 : if (eFormat == NCDF_FORMAT_NC4)
7321 1 : nCreateMode |= NC_NETCDF4;
7322 3 : else if (eFormat == NCDF_FORMAT_NC4C)
7323 0 : nCreateMode |= NC_NETCDF4 | NC_CLASSIC_MODEL;
7324 4 : int nCdfId = -1;
7325 4 : int status = nc_create(pszTmpFilename, nCreateMode, &nCdfId);
7326 4 : if (status != NC_NOERR)
7327 : {
7328 0 : return false;
7329 : }
7330 4 : VSIFSeekL(fpSrc, 0, SEEK_SET);
7331 : const char *pszLine;
7332 4 : constexpr int SECTION_NONE = 0;
7333 4 : constexpr int SECTION_DIMENSIONS = 1;
7334 4 : constexpr int SECTION_VARIABLES = 2;
7335 4 : constexpr int SECTION_DATA = 3;
7336 4 : int nActiveSection = SECTION_NONE;
7337 8 : std::map<CPLString, int> oMapDimToId;
7338 8 : std::map<int, int> oMapDimIdToDimLen;
7339 8 : std::map<CPLString, int> oMapVarToId;
7340 8 : std::map<int, std::vector<int>> oMapVarIdToVectorOfDimId;
7341 8 : std::map<int, int> oMapVarIdToType;
7342 4 : std::set<CPLString> oSetAttrDefined;
7343 4 : oMapVarToId[""] = -1;
7344 4 : size_t nTotalVarSize = 0;
7345 208 : while ((pszLine = CPLReadLineL(fpSrc)) != nullptr)
7346 : {
7347 204 : if (STARTS_WITH(pszLine, "dimensions:") &&
7348 : nActiveSection == SECTION_NONE)
7349 : {
7350 4 : nActiveSection = SECTION_DIMENSIONS;
7351 : }
7352 200 : else if (STARTS_WITH(pszLine, "variables:") &&
7353 : nActiveSection == SECTION_DIMENSIONS)
7354 : {
7355 4 : nActiveSection = SECTION_VARIABLES;
7356 : }
7357 196 : else if (STARTS_WITH(pszLine, "data:") &&
7358 : nActiveSection == SECTION_VARIABLES)
7359 : {
7360 4 : nActiveSection = SECTION_DATA;
7361 4 : status = nc_enddef(nCdfId);
7362 4 : if (status != NC_NOERR)
7363 : {
7364 0 : CPLDebug("netCDF", "nc_enddef() failed: %s",
7365 : nc_strerror(status));
7366 : }
7367 : }
7368 192 : else if (nActiveSection == SECTION_DIMENSIONS)
7369 : {
7370 9 : char **papszTokens = CSLTokenizeString2(pszLine, " \t=;", 0);
7371 9 : if (CSLCount(papszTokens) == 2)
7372 : {
7373 9 : const char *pszDimName = papszTokens[0];
7374 9 : bool bValidName = true;
7375 9 : if (STARTS_WITH(pszDimName, "_nc4_non_coord_"))
7376 : {
7377 : // This is an internal netcdf prefix. Using it may
7378 : // cause memory leaks.
7379 0 : bValidName = false;
7380 : }
7381 9 : if (!bValidName)
7382 : {
7383 0 : CPLDebug("netCDF",
7384 : "nc_def_dim(%s) failed: invalid name found",
7385 : pszDimName);
7386 0 : CSLDestroy(papszTokens);
7387 0 : continue;
7388 : }
7389 :
7390 : const bool bIsASCII =
7391 9 : CPLIsASCII(pszDimName, static_cast<size_t>(-1));
7392 9 : if (!bIsASCII)
7393 : {
7394 : // Workaround https://github.com/Unidata/netcdf-c/pull/450
7395 0 : CPLDebug("netCDF",
7396 : "nc_def_dim(%s) failed: rejected because "
7397 : "of non-ASCII characters",
7398 : pszDimName);
7399 0 : CSLDestroy(papszTokens);
7400 0 : continue;
7401 : }
7402 9 : int nDimSize = EQUAL(papszTokens[1], "UNLIMITED")
7403 : ? NC_UNLIMITED
7404 9 : : atoi(papszTokens[1]);
7405 9 : if (nDimSize >= 1000)
7406 1 : nDimSize = 1000; // to avoid very long processing
7407 9 : if (nDimSize >= 0)
7408 : {
7409 9 : int nDimId = -1;
7410 9 : status = nc_def_dim(nCdfId, pszDimName, nDimSize, &nDimId);
7411 9 : if (status != NC_NOERR)
7412 : {
7413 0 : CPLDebug("netCDF", "nc_def_dim(%s, %d) failed: %s",
7414 : pszDimName, nDimSize, nc_strerror(status));
7415 : }
7416 : else
7417 : {
7418 : #ifdef DEBUG_VERBOSE
7419 : CPLDebug("netCDF", "nc_def_dim(%s, %d) (%s) succeeded",
7420 : pszDimName, nDimSize, pszLine);
7421 : #endif
7422 9 : oMapDimToId[pszDimName] = nDimId;
7423 9 : oMapDimIdToDimLen[nDimId] = nDimSize;
7424 : }
7425 : }
7426 : }
7427 9 : CSLDestroy(papszTokens);
7428 : }
7429 183 : else if (nActiveSection == SECTION_VARIABLES)
7430 : {
7431 390 : while (*pszLine == ' ' || *pszLine == '\t')
7432 249 : pszLine++;
7433 141 : const char *pszColumn = strchr(pszLine, ':');
7434 141 : const char *pszEqual = strchr(pszLine, '=');
7435 141 : if (pszColumn == nullptr)
7436 : {
7437 21 : char **papszTokens = CSLTokenizeString2(pszLine, " \t=(),;", 0);
7438 21 : if (CSLCount(papszTokens) >= 2)
7439 : {
7440 17 : const char *pszVarName = papszTokens[1];
7441 17 : bool bValidName = true;
7442 17 : if (STARTS_WITH(pszVarName, "_nc4_non_coord_"))
7443 : {
7444 : // This is an internal netcdf prefix. Using it may
7445 : // cause memory leaks.
7446 0 : bValidName = false;
7447 : }
7448 138 : for (int i = 0; pszVarName[i]; i++)
7449 : {
7450 121 : if (!((pszVarName[i] >= 'a' && pszVarName[i] <= 'z') ||
7451 28 : (pszVarName[i] >= 'A' && pszVarName[i] <= 'Z') ||
7452 9 : (pszVarName[i] >= '0' && pszVarName[i] <= '9') ||
7453 6 : pszVarName[i] == '_'))
7454 : {
7455 0 : bValidName = false;
7456 : }
7457 : }
7458 17 : if (!bValidName)
7459 : {
7460 0 : CPLDebug(
7461 : "netCDF",
7462 : "nc_def_var(%s) failed: illegal character found",
7463 : pszVarName);
7464 0 : CSLDestroy(papszTokens);
7465 0 : continue;
7466 : }
7467 17 : if (oMapVarToId.find(pszVarName) != oMapVarToId.end())
7468 : {
7469 0 : CPLDebug("netCDF",
7470 : "nc_def_var(%s) failed: already defined",
7471 : pszVarName);
7472 0 : CSLDestroy(papszTokens);
7473 0 : continue;
7474 : }
7475 17 : const char *pszVarType = papszTokens[0];
7476 17 : int nc_datatype = NC_BYTE;
7477 17 : size_t nDataTypeSize = 1;
7478 17 : if (EQUAL(pszVarType, "char"))
7479 : {
7480 6 : nc_datatype = NC_CHAR;
7481 6 : nDataTypeSize = 1;
7482 : }
7483 11 : else if (EQUAL(pszVarType, "byte"))
7484 : {
7485 3 : nc_datatype = NC_BYTE;
7486 3 : nDataTypeSize = 1;
7487 : }
7488 8 : else if (EQUAL(pszVarType, "short"))
7489 : {
7490 0 : nc_datatype = NC_SHORT;
7491 0 : nDataTypeSize = 2;
7492 : }
7493 8 : else if (EQUAL(pszVarType, "int"))
7494 : {
7495 0 : nc_datatype = NC_INT;
7496 0 : nDataTypeSize = 4;
7497 : }
7498 8 : else if (EQUAL(pszVarType, "float"))
7499 : {
7500 0 : nc_datatype = NC_FLOAT;
7501 0 : nDataTypeSize = 4;
7502 : }
7503 8 : else if (EQUAL(pszVarType, "double"))
7504 : {
7505 8 : nc_datatype = NC_DOUBLE;
7506 8 : nDataTypeSize = 8;
7507 : }
7508 0 : else if (EQUAL(pszVarType, "ubyte"))
7509 : {
7510 0 : nc_datatype = NC_UBYTE;
7511 0 : nDataTypeSize = 1;
7512 : }
7513 0 : else if (EQUAL(pszVarType, "ushort"))
7514 : {
7515 0 : nc_datatype = NC_USHORT;
7516 0 : nDataTypeSize = 2;
7517 : }
7518 0 : else if (EQUAL(pszVarType, "uint"))
7519 : {
7520 0 : nc_datatype = NC_UINT;
7521 0 : nDataTypeSize = 4;
7522 : }
7523 0 : else if (EQUAL(pszVarType, "int64"))
7524 : {
7525 0 : nc_datatype = NC_INT64;
7526 0 : nDataTypeSize = 8;
7527 : }
7528 0 : else if (EQUAL(pszVarType, "uint64"))
7529 : {
7530 0 : nc_datatype = NC_UINT64;
7531 0 : nDataTypeSize = 8;
7532 : }
7533 :
7534 17 : int nDims = CSLCount(papszTokens) - 2;
7535 17 : if (nDims >= 32)
7536 : {
7537 : // The number of dimensions in a netCDFv4 file is
7538 : // limited by #define H5S_MAX_RANK 32
7539 : // but libnetcdf doesn't check that...
7540 0 : CPLDebug("netCDF",
7541 : "nc_def_var(%s) failed: too many dimensions",
7542 : pszVarName);
7543 0 : CSLDestroy(papszTokens);
7544 0 : continue;
7545 : }
7546 17 : std::vector<int> aoDimIds;
7547 17 : bool bFailed = false;
7548 17 : size_t nSize = 1;
7549 35 : for (int i = 0; i < nDims; i++)
7550 : {
7551 18 : const char *pszDimName = papszTokens[2 + i];
7552 18 : if (oMapDimToId.find(pszDimName) == oMapDimToId.end())
7553 : {
7554 0 : bFailed = true;
7555 0 : break;
7556 : }
7557 18 : const int nDimId = oMapDimToId[pszDimName];
7558 18 : aoDimIds.push_back(nDimId);
7559 :
7560 18 : const size_t nDimSize = oMapDimIdToDimLen[nDimId];
7561 18 : if (nDimSize != 0)
7562 : {
7563 18 : if (nSize >
7564 18 : std::numeric_limits<size_t>::max() / nDimSize)
7565 : {
7566 0 : bFailed = true;
7567 0 : break;
7568 : }
7569 : else
7570 : {
7571 18 : nSize *= nDimSize;
7572 : }
7573 : }
7574 : }
7575 17 : if (bFailed)
7576 : {
7577 0 : CPLDebug("netCDF",
7578 : "nc_def_var(%s) failed: unknown dimension(s)",
7579 : pszVarName);
7580 0 : CSLDestroy(papszTokens);
7581 0 : continue;
7582 : }
7583 17 : if (nSize > 100U * 1024 * 1024 / nDataTypeSize)
7584 : {
7585 0 : CPLDebug("netCDF",
7586 : "nc_def_var(%s) failed: too large data",
7587 : pszVarName);
7588 0 : CSLDestroy(papszTokens);
7589 0 : continue;
7590 : }
7591 17 : if (nTotalVarSize >
7592 34 : std::numeric_limits<size_t>::max() - nSize ||
7593 17 : nTotalVarSize + nSize > 100 * 1024 * 1024)
7594 : {
7595 0 : CPLDebug("netCDF",
7596 : "nc_def_var(%s) failed: too large data",
7597 : pszVarName);
7598 0 : CSLDestroy(papszTokens);
7599 0 : continue;
7600 : }
7601 17 : nTotalVarSize += nSize;
7602 :
7603 17 : int nVarId = -1;
7604 : status =
7605 30 : nc_def_var(nCdfId, pszVarName, nc_datatype, nDims,
7606 13 : (nDims) ? &aoDimIds[0] : nullptr, &nVarId);
7607 17 : if (status != NC_NOERR)
7608 : {
7609 0 : CPLDebug("netCDF", "nc_def_var(%s) failed: %s",
7610 : pszVarName, nc_strerror(status));
7611 : }
7612 : else
7613 : {
7614 : #ifdef DEBUG_VERBOSE
7615 : CPLDebug("netCDF", "nc_def_var(%s) (%s) succeeded",
7616 : pszVarName, pszLine);
7617 : #endif
7618 17 : oMapVarToId[pszVarName] = nVarId;
7619 17 : oMapVarIdToType[nVarId] = nc_datatype;
7620 17 : oMapVarIdToVectorOfDimId[nVarId] = std::move(aoDimIds);
7621 : }
7622 : }
7623 21 : CSLDestroy(papszTokens);
7624 : }
7625 120 : else if (pszEqual != nullptr && pszEqual - pszColumn > 0)
7626 : {
7627 116 : CPLString osVarName(pszLine, pszColumn - pszLine);
7628 116 : CPLString osAttrName(pszColumn + 1, pszEqual - pszColumn - 1);
7629 116 : osAttrName.Trim();
7630 116 : if (oMapVarToId.find(osVarName) == oMapVarToId.end())
7631 : {
7632 0 : CPLDebug("netCDF",
7633 : "nc_put_att(%s:%s) failed: "
7634 : "no corresponding variable",
7635 : osVarName.c_str(), osAttrName.c_str());
7636 0 : continue;
7637 : }
7638 116 : bool bValidName = true;
7639 1743 : for (size_t i = 0; i < osAttrName.size(); i++)
7640 : {
7641 1865 : if (!((osAttrName[i] >= 'a' && osAttrName[i] <= 'z') ||
7642 238 : (osAttrName[i] >= 'A' && osAttrName[i] <= 'Z') ||
7643 158 : (osAttrName[i] >= '0' && osAttrName[i] <= '9') ||
7644 158 : osAttrName[i] == '_'))
7645 : {
7646 0 : bValidName = false;
7647 : }
7648 : }
7649 116 : if (!bValidName)
7650 : {
7651 0 : CPLDebug(
7652 : "netCDF",
7653 : "nc_put_att(%s:%s) failed: illegal character found",
7654 : osVarName.c_str(), osAttrName.c_str());
7655 0 : continue;
7656 : }
7657 116 : if (oSetAttrDefined.find(osVarName + ":" + osAttrName) !=
7658 232 : oSetAttrDefined.end())
7659 : {
7660 0 : CPLDebug("netCDF",
7661 : "nc_put_att(%s:%s) failed: already defined",
7662 : osVarName.c_str(), osAttrName.c_str());
7663 0 : continue;
7664 : }
7665 :
7666 116 : const int nVarId = oMapVarToId[osVarName];
7667 116 : const char *pszValue = pszEqual + 1;
7668 232 : while (*pszValue == ' ')
7669 116 : pszValue++;
7670 :
7671 116 : status = NC_EBADTYPE;
7672 116 : if (*pszValue == '"')
7673 : {
7674 : // For _FillValue, the attribute type should match
7675 : // the variable type. Leaks memory with NC4 otherwise
7676 74 : if (osAttrName == "_FillValue")
7677 : {
7678 0 : CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
7679 : osVarName.c_str(), osAttrName.c_str(),
7680 : nc_strerror(status));
7681 0 : continue;
7682 : }
7683 :
7684 : // Unquote and unescape string value
7685 74 : CPLString osVal(pszValue + 1);
7686 222 : while (!osVal.empty())
7687 : {
7688 222 : if (osVal.back() == ';' || osVal.back() == ' ')
7689 : {
7690 148 : osVal.pop_back();
7691 : }
7692 74 : else if (osVal.back() == '"')
7693 : {
7694 74 : osVal.pop_back();
7695 74 : break;
7696 : }
7697 : else
7698 : {
7699 0 : break;
7700 : }
7701 : }
7702 74 : osVal.replaceAll("\\\"", '"');
7703 74 : status = nc_put_att_text(nCdfId, nVarId, osAttrName,
7704 : osVal.size(), osVal.c_str());
7705 : }
7706 : else
7707 : {
7708 84 : CPLString osVal(pszValue);
7709 126 : while (!osVal.empty())
7710 : {
7711 126 : if (osVal.back() == ';' || osVal.back() == ' ')
7712 : {
7713 84 : osVal.pop_back();
7714 : }
7715 : else
7716 : {
7717 42 : break;
7718 : }
7719 : }
7720 42 : int nc_datatype = -1;
7721 42 : if (!osVal.empty() && osVal.back() == 'b')
7722 : {
7723 3 : nc_datatype = NC_BYTE;
7724 3 : osVal.pop_back();
7725 : }
7726 39 : else if (!osVal.empty() && osVal.back() == 's')
7727 : {
7728 3 : nc_datatype = NC_SHORT;
7729 3 : osVal.pop_back();
7730 : }
7731 42 : if (CPLGetValueType(osVal) == CPL_VALUE_INTEGER)
7732 : {
7733 7 : if (nc_datatype < 0)
7734 4 : nc_datatype = NC_INT;
7735 : }
7736 35 : else if (CPLGetValueType(osVal) == CPL_VALUE_REAL)
7737 : {
7738 32 : nc_datatype = NC_DOUBLE;
7739 : }
7740 : else
7741 : {
7742 3 : nc_datatype = -1;
7743 : }
7744 :
7745 : // For _FillValue, check that the attribute type matches
7746 : // the variable type. Leaks memory with NC4 otherwise
7747 42 : if (osAttrName == "_FillValue")
7748 : {
7749 6 : if (nVarId < 0 ||
7750 3 : nc_datatype != oMapVarIdToType[nVarId])
7751 : {
7752 0 : nc_datatype = -1;
7753 : }
7754 : }
7755 :
7756 42 : if (nc_datatype == NC_BYTE)
7757 : {
7758 : signed char chVal =
7759 3 : static_cast<signed char>(atoi(osVal));
7760 3 : status = nc_put_att_schar(nCdfId, nVarId, osAttrName,
7761 : NC_BYTE, 1, &chVal);
7762 : }
7763 39 : else if (nc_datatype == NC_SHORT)
7764 : {
7765 0 : short nVal = static_cast<short>(atoi(osVal));
7766 0 : status = nc_put_att_short(nCdfId, nVarId, osAttrName,
7767 : NC_SHORT, 1, &nVal);
7768 : }
7769 39 : else if (nc_datatype == NC_INT)
7770 : {
7771 4 : int nVal = static_cast<int>(atoi(osVal));
7772 4 : status = nc_put_att_int(nCdfId, nVarId, osAttrName,
7773 : NC_INT, 1, &nVal);
7774 : }
7775 35 : else if (nc_datatype == NC_DOUBLE)
7776 : {
7777 32 : double dfVal = CPLAtof(osVal);
7778 32 : status = nc_put_att_double(nCdfId, nVarId, osAttrName,
7779 : NC_DOUBLE, 1, &dfVal);
7780 : }
7781 : }
7782 116 : if (status != NC_NOERR)
7783 : {
7784 3 : CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
7785 : osVarName.c_str(), osAttrName.c_str(),
7786 : nc_strerror(status));
7787 : }
7788 : else
7789 : {
7790 113 : oSetAttrDefined.insert(osVarName + ":" + osAttrName);
7791 : #ifdef DEBUG_VERBOSE
7792 : CPLDebug("netCDF", "nc_put_att_(%s:%s) (%s) succeeded",
7793 : osVarName.c_str(), osAttrName.c_str(), pszLine);
7794 : #endif
7795 : }
7796 : }
7797 : }
7798 42 : else if (nActiveSection == SECTION_DATA)
7799 : {
7800 55 : while (*pszLine == ' ' || *pszLine == '\t')
7801 17 : pszLine++;
7802 38 : const char *pszEqual = strchr(pszLine, '=');
7803 38 : if (pszEqual)
7804 : {
7805 17 : CPLString osVarName(pszLine, pszEqual - pszLine);
7806 17 : osVarName.Trim();
7807 17 : if (oMapVarToId.find(osVarName) == oMapVarToId.end())
7808 0 : continue;
7809 17 : const int nVarId = oMapVarToId[osVarName];
7810 17 : CPLString osAccVal(pszEqual + 1);
7811 17 : osAccVal.Trim();
7812 153 : while (osAccVal.empty() || osAccVal.back() != ';')
7813 : {
7814 136 : pszLine = CPLReadLineL(fpSrc);
7815 136 : if (pszLine == nullptr)
7816 0 : break;
7817 272 : CPLString osVal(pszLine);
7818 136 : osVal.Trim();
7819 136 : osAccVal += osVal;
7820 : }
7821 17 : if (pszLine == nullptr)
7822 0 : break;
7823 17 : osAccVal.pop_back();
7824 :
7825 : const std::vector<int> aoDimIds =
7826 34 : oMapVarIdToVectorOfDimId[nVarId];
7827 17 : size_t nSize = 1;
7828 34 : std::vector<size_t> aoStart, aoEdge;
7829 17 : aoStart.resize(aoDimIds.size());
7830 17 : aoEdge.resize(aoDimIds.size());
7831 35 : for (size_t i = 0; i < aoDimIds.size(); ++i)
7832 : {
7833 18 : const size_t nDimSize = oMapDimIdToDimLen[aoDimIds[i]];
7834 36 : if (nDimSize != 0 &&
7835 18 : nSize > std::numeric_limits<size_t>::max() / nDimSize)
7836 : {
7837 0 : nSize = 0;
7838 : }
7839 : else
7840 : {
7841 18 : nSize *= nDimSize;
7842 : }
7843 18 : aoStart[i] = 0;
7844 18 : aoEdge[i] = nDimSize;
7845 : }
7846 :
7847 17 : status = NC_EBADTYPE;
7848 17 : if (nSize == 0)
7849 : {
7850 : // Might happen with a unlimited dimension
7851 : }
7852 17 : else if (oMapVarIdToType[nVarId] == NC_DOUBLE)
7853 : {
7854 8 : if (!aoStart.empty())
7855 : {
7856 : char **papszTokens =
7857 8 : CSLTokenizeString2(osAccVal, " ,;", 0);
7858 8 : size_t nTokens = CSLCount(papszTokens);
7859 8 : if (nTokens >= nSize)
7860 : {
7861 : double *padfVals = static_cast<double *>(
7862 8 : VSI_CALLOC_VERBOSE(nSize, sizeof(double)));
7863 8 : if (padfVals)
7864 : {
7865 132 : for (size_t i = 0; i < nSize; i++)
7866 : {
7867 124 : padfVals[i] = CPLAtof(papszTokens[i]);
7868 : }
7869 8 : status = nc_put_vara_double(
7870 8 : nCdfId, nVarId, &aoStart[0], &aoEdge[0],
7871 : padfVals);
7872 8 : VSIFree(padfVals);
7873 : }
7874 : }
7875 8 : CSLDestroy(papszTokens);
7876 : }
7877 : }
7878 9 : else if (oMapVarIdToType[nVarId] == NC_BYTE)
7879 : {
7880 3 : if (!aoStart.empty())
7881 : {
7882 : char **papszTokens =
7883 3 : CSLTokenizeString2(osAccVal, " ,;", 0);
7884 3 : size_t nTokens = CSLCount(papszTokens);
7885 3 : if (nTokens >= nSize)
7886 : {
7887 : signed char *panVals = static_cast<signed char *>(
7888 3 : VSI_CALLOC_VERBOSE(nSize, sizeof(signed char)));
7889 3 : if (panVals)
7890 : {
7891 1203 : for (size_t i = 0; i < nSize; i++)
7892 : {
7893 1200 : panVals[i] = static_cast<signed char>(
7894 1200 : atoi(papszTokens[i]));
7895 : }
7896 3 : status = nc_put_vara_schar(nCdfId, nVarId,
7897 3 : &aoStart[0],
7898 3 : &aoEdge[0], panVals);
7899 3 : VSIFree(panVals);
7900 : }
7901 : }
7902 3 : CSLDestroy(papszTokens);
7903 : }
7904 : }
7905 6 : else if (oMapVarIdToType[nVarId] == NC_CHAR)
7906 : {
7907 6 : if (aoStart.size() == 2)
7908 : {
7909 4 : std::vector<CPLString> aoStrings;
7910 2 : bool bInString = false;
7911 4 : CPLString osCurString;
7912 935 : for (size_t i = 0; i < osAccVal.size();)
7913 : {
7914 933 : if (!bInString)
7915 : {
7916 8 : if (osAccVal[i] == '"')
7917 : {
7918 4 : bInString = true;
7919 4 : osCurString.clear();
7920 : }
7921 8 : i++;
7922 : }
7923 926 : else if (osAccVal[i] == '\\' &&
7924 926 : i + 1 < osAccVal.size() &&
7925 1 : osAccVal[i + 1] == '"')
7926 : {
7927 1 : osCurString += '"';
7928 1 : i += 2;
7929 : }
7930 924 : else if (osAccVal[i] == '"')
7931 : {
7932 4 : aoStrings.push_back(osCurString);
7933 4 : osCurString.clear();
7934 4 : bInString = false;
7935 4 : i++;
7936 : }
7937 : else
7938 : {
7939 920 : osCurString += osAccVal[i];
7940 920 : i++;
7941 : }
7942 : }
7943 2 : const size_t nRecords = oMapDimIdToDimLen[aoDimIds[0]];
7944 2 : const size_t nWidth = oMapDimIdToDimLen[aoDimIds[1]];
7945 2 : size_t nIters = aoStrings.size();
7946 2 : if (nIters > nRecords)
7947 0 : nIters = nRecords;
7948 6 : for (size_t i = 0; i < nIters; i++)
7949 : {
7950 : size_t anIndex[2];
7951 4 : anIndex[0] = i;
7952 4 : anIndex[1] = 0;
7953 : size_t anCount[2];
7954 4 : anCount[0] = 1;
7955 4 : anCount[1] = aoStrings[i].size();
7956 4 : if (anCount[1] > nWidth)
7957 0 : anCount[1] = nWidth;
7958 : status =
7959 4 : nc_put_vara_text(nCdfId, nVarId, anIndex,
7960 4 : anCount, aoStrings[i].c_str());
7961 4 : if (status != NC_NOERR)
7962 0 : break;
7963 : }
7964 : }
7965 : }
7966 17 : if (status != NC_NOERR)
7967 : {
7968 4 : CPLDebug("netCDF", "nc_put_var_(%s) failed: %s",
7969 : osVarName.c_str(), nc_strerror(status));
7970 : }
7971 : }
7972 : }
7973 : }
7974 :
7975 4 : GDAL_nc_close(nCdfId);
7976 4 : return true;
7977 : }
7978 :
7979 : #endif // ENABLE_NCDUMP
7980 :
7981 : /************************************************************************/
7982 : /* Open() */
7983 : /************************************************************************/
7984 :
7985 661 : GDALDataset *netCDFDataset::Open(GDALOpenInfo *poOpenInfo)
7986 :
7987 : {
7988 : #ifdef NCDF_DEBUG
7989 : CPLDebug("GDAL_netCDF", "\n=====\nOpen(), filename=[%s]",
7990 : poOpenInfo->pszFilename);
7991 : #endif
7992 :
7993 : // Does this appear to be a netcdf file?
7994 661 : NetCDFFormatEnum eTmpFormat = NCDF_FORMAT_NONE;
7995 661 : if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
7996 : {
7997 602 : eTmpFormat = netCDFIdentifyFormat(poOpenInfo, /* bCheckExt = */ true);
7998 : #ifdef NCDF_DEBUG
7999 : CPLDebug("GDAL_netCDF", "identified format %d", eTmpFormat);
8000 : #endif
8001 : // Note: not calling Identify() directly, because we want the file type.
8002 : // Only support NCDF_FORMAT* formats.
8003 602 : if (NCDF_FORMAT_NC == eTmpFormat || NCDF_FORMAT_NC2 == eTmpFormat ||
8004 2 : NCDF_FORMAT_NC4 == eTmpFormat || NCDF_FORMAT_NC4C == eTmpFormat)
8005 : {
8006 : // ok
8007 : }
8008 2 : else if (eTmpFormat == NCDF_FORMAT_HDF4 &&
8009 0 : poOpenInfo->IsSingleAllowedDriver("netCDF"))
8010 : {
8011 : // ok
8012 : }
8013 : else
8014 : {
8015 2 : return nullptr;
8016 : }
8017 : }
8018 : else
8019 : {
8020 : #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
8021 : // We don't necessarily want to catch bugs in libnetcdf ...
8022 : if (CPLGetConfigOption("DISABLE_OPEN_REAL_NETCDF_FILES", nullptr))
8023 : {
8024 : return nullptr;
8025 : }
8026 : #endif
8027 : }
8028 :
8029 659 : if (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER)
8030 : {
8031 173 : return OpenMultiDim(poOpenInfo);
8032 : }
8033 :
8034 972 : CPLMutexHolderD(&hNCMutex);
8035 :
8036 486 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
8037 : // GDALDataset own mutex.
8038 486 : netCDFDataset *poDS = new netCDFDataset();
8039 486 : poDS->papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
8040 486 : CPLAcquireMutex(hNCMutex, 1000.0);
8041 :
8042 486 : poDS->SetDescription(poOpenInfo->pszFilename);
8043 :
8044 : // Check if filename start with NETCDF: tag.
8045 486 : bool bTreatAsSubdataset = false;
8046 972 : CPLString osSubdatasetName;
8047 :
8048 : #ifdef ENABLE_NCDUMP
8049 486 : const char *pszHeader =
8050 : reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
8051 486 : if (poOpenInfo->fpL != nullptr && STARTS_WITH(pszHeader, "netcdf ") &&
8052 3 : strstr(pszHeader, "dimensions:") && strstr(pszHeader, "variables:"))
8053 : {
8054 : // By default create a temporary file that will be destroyed,
8055 : // unless NETCDF_TMP_FILE is defined. Can be useful to see which
8056 : // netCDF file has been generated from a potential fuzzed input.
8057 3 : poDS->osFilename = CPLGetConfigOption("NETCDF_TMP_FILE", "");
8058 3 : if (poDS->osFilename.empty())
8059 : {
8060 3 : poDS->bFileToDestroyAtClosing = true;
8061 3 : poDS->osFilename = CPLGenerateTempFilenameSafe("netcdf_tmp");
8062 : }
8063 3 : if (!netCDFDatasetCreateTempFile(eTmpFormat, poDS->osFilename,
8064 : poOpenInfo->fpL))
8065 : {
8066 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8067 : // deadlock with GDALDataset own mutex.
8068 0 : delete poDS;
8069 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8070 0 : return nullptr;
8071 : }
8072 3 : bTreatAsSubdataset = false;
8073 3 : poDS->eFormat = eTmpFormat;
8074 : }
8075 : else
8076 : #endif
8077 :
8078 483 : if (STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
8079 : {
8080 : char **papszName =
8081 59 : CSLTokenizeString2(poOpenInfo->pszFilename, ":",
8082 : CSLT_HONOURSTRINGS | CSLT_PRESERVEESCAPES);
8083 :
8084 118 : if (CSLCount(papszName) >= 3 &&
8085 59 : ((strlen(papszName[1]) == 1 && /* D:\\bla */
8086 0 : (papszName[2][0] == '/' || papszName[2][0] == '\\')) ||
8087 59 : EQUAL(papszName[1], "http") || EQUAL(papszName[1], "https") ||
8088 59 : EQUAL(papszName[1], "/vsicurl/http") ||
8089 59 : EQUAL(papszName[1], "/vsicurl/https") ||
8090 59 : EQUAL(papszName[1], "/vsicurl_streaming/http") ||
8091 59 : EQUAL(papszName[1], "/vsicurl_streaming/https")))
8092 : {
8093 0 : const int nCountBefore = CSLCount(papszName);
8094 0 : CPLString osTmp = papszName[1];
8095 0 : osTmp += ':';
8096 0 : osTmp += papszName[2];
8097 0 : CPLFree(papszName[1]);
8098 0 : CPLFree(papszName[2]);
8099 0 : papszName[1] = CPLStrdup(osTmp);
8100 0 : memmove(papszName + 2, papszName + 3,
8101 0 : (nCountBefore - 2) * sizeof(char *));
8102 : }
8103 :
8104 59 : if (CSLCount(papszName) == 3)
8105 : {
8106 59 : poDS->osFilename = papszName[1];
8107 59 : osSubdatasetName = papszName[2];
8108 59 : bTreatAsSubdataset = true;
8109 59 : CSLDestroy(papszName);
8110 : }
8111 0 : else if (CSLCount(papszName) == 2)
8112 : {
8113 0 : poDS->osFilename = papszName[1];
8114 0 : osSubdatasetName = "";
8115 0 : bTreatAsSubdataset = false;
8116 0 : CSLDestroy(papszName);
8117 : }
8118 : else
8119 : {
8120 0 : CSLDestroy(papszName);
8121 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8122 : // deadlock with GDALDataset own mutex.
8123 0 : delete poDS;
8124 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8125 0 : CPLError(CE_Failure, CPLE_AppDefined,
8126 : "Failed to parse NETCDF: prefix string into expected 2, 3 "
8127 : "or 4 fields.");
8128 0 : return nullptr;
8129 : }
8130 :
8131 118 : if (!STARTS_WITH(poDS->osFilename, "http://") &&
8132 59 : !STARTS_WITH(poDS->osFilename, "https://"))
8133 : {
8134 : // Identify Format from real file, with bCheckExt=FALSE.
8135 : GDALOpenInfo *poOpenInfo2 =
8136 59 : new GDALOpenInfo(poDS->osFilename.c_str(), GA_ReadOnly);
8137 59 : poDS->eFormat =
8138 59 : netCDFIdentifyFormat(poOpenInfo2, /* bCheckExt = */ false);
8139 59 : delete poOpenInfo2;
8140 59 : if (NCDF_FORMAT_NONE == poDS->eFormat ||
8141 59 : NCDF_FORMAT_UNKNOWN == poDS->eFormat)
8142 : {
8143 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8144 : // deadlock with GDALDataset own mutex.
8145 0 : delete poDS;
8146 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8147 0 : return nullptr;
8148 : }
8149 : }
8150 : }
8151 : else
8152 : {
8153 424 : poDS->osFilename = poOpenInfo->pszFilename;
8154 424 : bTreatAsSubdataset = false;
8155 424 : poDS->eFormat = eTmpFormat;
8156 : }
8157 :
8158 : // Try opening the dataset.
8159 : #if defined(NCDF_DEBUG) && defined(ENABLE_UFFD)
8160 : CPLDebug("GDAL_netCDF", "calling nc_open_mem(%s)",
8161 : poDS->osFilename.c_str());
8162 : #elif defined(NCDF_DEBUG) && !defined(ENABLE_UFFD)
8163 : CPLDebug("GDAL_netCDF", "calling nc_open(%s)", poDS->osFilename.c_str());
8164 : #endif
8165 486 : int cdfid = -1;
8166 486 : const int nMode = ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE) != 0)
8167 : ? NC_WRITE
8168 : : NC_NOWRITE;
8169 972 : CPLString osFilenameForNCOpen(poDS->osFilename);
8170 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
8171 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
8172 : {
8173 : char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
8174 : osFilenameForNCOpen = pszTemp;
8175 : CPLFree(pszTemp);
8176 : }
8177 : #endif
8178 486 : int status2 = -1;
8179 :
8180 : #ifdef ENABLE_UFFD
8181 486 : cpl_uffd_context *pCtx = nullptr;
8182 : #endif
8183 :
8184 501 : if (STARTS_WITH(osFilenameForNCOpen, "/vsimem/") &&
8185 15 : poOpenInfo->eAccess == GA_ReadOnly)
8186 : {
8187 15 : vsi_l_offset nLength = 0;
8188 15 : poDS->fpVSIMEM = VSIFOpenL(osFilenameForNCOpen, "rb");
8189 15 : if (poDS->fpVSIMEM)
8190 : {
8191 : // We assume that the file will not be modified. If it is, then
8192 : // pabyBuffer might become invalid.
8193 : GByte *pabyBuffer =
8194 15 : VSIGetMemFileBuffer(osFilenameForNCOpen, &nLength, false);
8195 15 : if (pabyBuffer)
8196 : {
8197 15 : status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen),
8198 : nMode, static_cast<size_t>(nLength),
8199 : pabyBuffer, &cdfid);
8200 : }
8201 : }
8202 : }
8203 : else
8204 : {
8205 : const bool bVsiFile =
8206 471 : !strncmp(osFilenameForNCOpen, "/vsi", strlen("/vsi"));
8207 : #ifdef ENABLE_UFFD
8208 471 : bool bReadOnly = (poOpenInfo->eAccess == GA_ReadOnly);
8209 471 : void *pVma = nullptr;
8210 471 : uint64_t nVmaSize = 0;
8211 :
8212 471 : if (bVsiFile)
8213 : {
8214 2 : if (bReadOnly)
8215 : {
8216 2 : if (CPLIsUserFaultMappingSupported())
8217 : {
8218 2 : pCtx = CPLCreateUserFaultMapping(osFilenameForNCOpen, &pVma,
8219 : &nVmaSize);
8220 : }
8221 : else
8222 : {
8223 0 : CPLError(CE_Failure, CPLE_AppDefined,
8224 : "Opening a /vsi file with the netCDF driver "
8225 : "requires Linux userfaultfd to be available. "
8226 : "If running from Docker, "
8227 : "--security-opt seccomp=unconfined might be "
8228 : "needed.%s",
8229 0 : ((poDS->eFormat == NCDF_FORMAT_NC4 ||
8230 0 : poDS->eFormat == NCDF_FORMAT_HDF5) &&
8231 0 : GDALGetDriverByName("HDF5"))
8232 : ? " Or you may set the GDAL_SKIP=netCDF "
8233 : "configuration option to force the use of "
8234 : "the HDF5 driver."
8235 : : "");
8236 : }
8237 : }
8238 : else
8239 : {
8240 0 : CPLError(CE_Failure, CPLE_AppDefined,
8241 : "Opening a /vsi file with the netCDF driver is only "
8242 : "supported in read-only mode");
8243 : }
8244 : }
8245 471 : if (pCtx != nullptr && pVma != nullptr && nVmaSize > 0)
8246 : {
8247 : // netCDF code, at least for netCDF 4.7.0, is confused by filenames
8248 : // like /vsicurl/http[s]://example.com/foo.nc, so just pass the
8249 : // final part
8250 2 : status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen), nMode,
8251 : static_cast<size_t>(nVmaSize), pVma, &cdfid);
8252 : }
8253 : else
8254 469 : status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
8255 : #else
8256 : if (bVsiFile)
8257 : {
8258 : CPLError(
8259 : CE_Failure, CPLE_AppDefined,
8260 : "Opening a /vsi file with the netCDF driver requires Linux "
8261 : "userfaultfd to be available.%s",
8262 : ((poDS->eFormat == NCDF_FORMAT_NC4 ||
8263 : poDS->eFormat == NCDF_FORMAT_HDF5) &&
8264 : GDALGetDriverByName("HDF5"))
8265 : ? " Or you may set the GDAL_SKIP=netCDF "
8266 : "configuration option to force the use of the HDF5 "
8267 : "driver."
8268 : : "");
8269 : status2 = NC_EIO;
8270 : }
8271 : else
8272 : {
8273 : status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
8274 : }
8275 : #endif
8276 : }
8277 486 : if (status2 != NC_NOERR)
8278 : {
8279 : #ifdef NCDF_DEBUG
8280 : CPLDebug("GDAL_netCDF", "error opening");
8281 : #endif
8282 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8283 : // with GDALDataset own mutex.
8284 0 : delete poDS;
8285 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8286 0 : return nullptr;
8287 : }
8288 : #ifdef NCDF_DEBUG
8289 : CPLDebug("GDAL_netCDF", "got cdfid=%d", cdfid);
8290 : #endif
8291 :
8292 : #if defined(ENABLE_NCDUMP) && !defined(_WIN32)
8293 : // Try to destroy the temporary file right now on Unix
8294 486 : if (poDS->bFileToDestroyAtClosing)
8295 : {
8296 3 : if (VSIUnlink(poDS->osFilename) == 0)
8297 : {
8298 3 : poDS->bFileToDestroyAtClosing = false;
8299 : }
8300 : }
8301 : #endif
8302 :
8303 : // Is this a real netCDF file?
8304 : int ndims;
8305 : int ngatts;
8306 : int nvars;
8307 : int unlimdimid;
8308 486 : int status = nc_inq(cdfid, &ndims, &nvars, &ngatts, &unlimdimid);
8309 486 : if (status != NC_NOERR)
8310 : {
8311 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8312 : // with GDALDataset own mutex.
8313 0 : delete poDS;
8314 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8315 0 : return nullptr;
8316 : }
8317 :
8318 : // Get file type from netcdf.
8319 486 : int nTmpFormat = NCDF_FORMAT_NONE;
8320 486 : status = nc_inq_format(cdfid, &nTmpFormat);
8321 486 : if (status != NC_NOERR)
8322 : {
8323 0 : NCDF_ERR(status);
8324 : }
8325 : else
8326 : {
8327 486 : CPLDebug("GDAL_netCDF",
8328 : "driver detected file type=%d, libnetcdf detected type=%d",
8329 486 : poDS->eFormat, nTmpFormat);
8330 486 : if (static_cast<NetCDFFormatEnum>(nTmpFormat) != poDS->eFormat)
8331 : {
8332 : // Warn if file detection conflicts with that from libnetcdf
8333 : // except for NC4C, which we have no way of detecting initially.
8334 24 : if (nTmpFormat != NCDF_FORMAT_NC4C &&
8335 12 : !STARTS_WITH(poDS->osFilename, "http://") &&
8336 0 : !STARTS_WITH(poDS->osFilename, "https://"))
8337 : {
8338 0 : CPLError(CE_Warning, CPLE_AppDefined,
8339 : "NetCDF driver detected file type=%d, but libnetcdf "
8340 : "detected type=%d",
8341 0 : poDS->eFormat, nTmpFormat);
8342 : }
8343 12 : CPLDebug("GDAL_netCDF", "setting file type to %d, was %d",
8344 12 : nTmpFormat, poDS->eFormat);
8345 12 : poDS->eFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
8346 : }
8347 : }
8348 :
8349 : // Does the request variable exist?
8350 486 : if (bTreatAsSubdataset)
8351 : {
8352 : int dummy;
8353 59 : if (NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &dummy,
8354 59 : &dummy) != CE_None)
8355 : {
8356 0 : CPLError(CE_Warning, CPLE_AppDefined,
8357 : "%s is a netCDF file, but %s is not a variable.",
8358 : poOpenInfo->pszFilename, osSubdatasetName.c_str());
8359 :
8360 0 : GDAL_nc_close(cdfid);
8361 : #ifdef ENABLE_UFFD
8362 0 : NETCDF_UFFD_UNMAP(pCtx);
8363 : #endif
8364 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8365 : // deadlock with GDALDataset own mutex.
8366 0 : delete poDS;
8367 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8368 0 : return nullptr;
8369 : }
8370 : }
8371 :
8372 : // Figure out whether or not the listed dataset has support for simple
8373 : // geometries (CF-1.8)
8374 486 : poDS->nCFVersion = nccfdriver::getCFVersion(cdfid);
8375 486 : bool bHasSimpleGeometries = false; // but not necessarily valid
8376 486 : if (poDS->nCFVersion >= 1.8)
8377 : {
8378 75 : bHasSimpleGeometries = poDS->DetectAndFillSGLayers(cdfid);
8379 75 : if (bHasSimpleGeometries)
8380 : {
8381 67 : poDS->bSGSupport = true;
8382 67 : poDS->vcdf.enableFullVirtualMode();
8383 : }
8384 : }
8385 :
8386 : char szConventions[NC_MAX_NAME + 1];
8387 486 : szConventions[0] = '\0';
8388 486 : nc_type nAttype = NC_NAT;
8389 486 : size_t nAttlen = 0;
8390 486 : nc_inq_att(cdfid, NC_GLOBAL, "Conventions", &nAttype, &nAttlen);
8391 972 : if (nAttlen >= sizeof(szConventions) ||
8392 486 : nc_get_att_text(cdfid, NC_GLOBAL, "Conventions", szConventions) !=
8393 : NC_NOERR)
8394 : {
8395 56 : CPLDebug("GDAL_netCDF", "No UNIDATA NC_GLOBAL:Conventions attribute");
8396 : // Note that 'Conventions' is always capital 'C' in CF spec.
8397 : }
8398 : else
8399 : {
8400 430 : szConventions[nAttlen] = '\0';
8401 : }
8402 :
8403 : // Create band information objects.
8404 486 : CPLDebug("GDAL_netCDF", "var_count = %d", nvars);
8405 :
8406 : // Create a corresponding GDALDataset.
8407 : // Create Netcdf Subdataset if filename as NETCDF tag.
8408 486 : poDS->cdfid = cdfid;
8409 : #ifdef ENABLE_UFFD
8410 486 : poDS->pCtx = pCtx;
8411 : #endif
8412 486 : poDS->eAccess = poOpenInfo->eAccess;
8413 486 : poDS->bDefineMode = false;
8414 :
8415 486 : poDS->ReadAttributes(cdfid, NC_GLOBAL);
8416 :
8417 : // Identify coordinate and boundary variables that we should
8418 : // ignore as Raster Bands.
8419 486 : char **papszIgnoreVars = nullptr;
8420 486 : NCDFGetCoordAndBoundVarFullNames(cdfid, &papszIgnoreVars);
8421 : // Filter variables to keep only valid 2+D raster bands and vector fields.
8422 486 : int nRasterVars = 0;
8423 486 : int nIgnoredVars = 0;
8424 486 : int nGroupID = -1;
8425 486 : int nVarID = -1;
8426 :
8427 : std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>
8428 972 : oMap2DDimsToGroupAndVar;
8429 1121 : if ((poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
8430 149 : STARTS_WITH(CSLFetchNameValueDef(poDS->papszMetadata,
8431 : "NC_GLOBAL#mission_name", ""),
8432 1 : "Sentinel 3") &&
8433 1 : EQUAL(CSLFetchNameValueDef(poDS->papszMetadata,
8434 : "NC_GLOBAL#altimeter_sensor_name", ""),
8435 635 : "SRAL") &&
8436 1 : EQUAL(CSLFetchNameValueDef(poDS->papszMetadata,
8437 : "NC_GLOBAL#radiometer_sensor_name", ""),
8438 : "MWR"))
8439 : {
8440 1 : if (poDS->eAccess == GA_Update)
8441 : {
8442 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8443 : // deadlock with GDALDataset own mutex.
8444 0 : delete poDS;
8445 0 : return nullptr;
8446 : }
8447 1 : poDS->ProcessSentinel3_SRAL_MWR();
8448 : }
8449 : else
8450 : {
8451 485 : poDS->FilterVars(cdfid, (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0,
8452 633 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
8453 148 : !bHasSimpleGeometries,
8454 : papszIgnoreVars, &nRasterVars, &nGroupID, &nVarID,
8455 : &nIgnoredVars, oMap2DDimsToGroupAndVar);
8456 : }
8457 486 : CSLDestroy(papszIgnoreVars);
8458 :
8459 : // Case where there is no raster variable
8460 486 : if (nRasterVars == 0 && !bTreatAsSubdataset)
8461 : {
8462 119 : poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
8463 119 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8464 : // with GDALDataset own mutex.
8465 119 : poDS->TryLoadXML();
8466 : // If the dataset has been opened in raster mode only, exit
8467 119 : if ((poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0 &&
8468 9 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) == 0)
8469 : {
8470 4 : delete poDS;
8471 4 : poDS = nullptr;
8472 : }
8473 : // Otherwise if the dataset is opened in vector mode, that there is
8474 : // no vector layer and we are in read-only, exit too.
8475 115 : else if (poDS->GetLayerCount() == 0 &&
8476 123 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
8477 8 : poOpenInfo->eAccess == GA_ReadOnly)
8478 : {
8479 8 : delete poDS;
8480 8 : poDS = nullptr;
8481 : }
8482 119 : CPLAcquireMutex(hNCMutex, 1000.0);
8483 119 : return poDS;
8484 : }
8485 :
8486 : // We have more than one variable with 2 dimensions in the
8487 : // file, then treat this as a subdataset container dataset.
8488 367 : bool bSeveralVariablesAsBands = false;
8489 367 : if ((nRasterVars > 1) && !bTreatAsSubdataset)
8490 : {
8491 27 : if (CPLFetchBool(poOpenInfo->papszOpenOptions, "VARIABLES_AS_BANDS",
8492 33 : false) &&
8493 6 : oMap2DDimsToGroupAndVar.size() == 1)
8494 : {
8495 6 : std::tie(nGroupID, nVarID) =
8496 12 : oMap2DDimsToGroupAndVar.begin()->second.front();
8497 6 : bSeveralVariablesAsBands = true;
8498 : }
8499 : else
8500 : {
8501 21 : poDS->CreateSubDatasetList(cdfid);
8502 21 : poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
8503 21 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8504 : // deadlock with GDALDataset own mutex.
8505 21 : poDS->TryLoadXML();
8506 21 : CPLAcquireMutex(hNCMutex, 1000.0);
8507 21 : return poDS;
8508 : }
8509 : }
8510 :
8511 : // If we are not treating things as a subdataset, then capture
8512 : // the name of the single available variable as the subdataset.
8513 346 : if (!bTreatAsSubdataset)
8514 : {
8515 287 : char *pszVarName = nullptr;
8516 287 : NCDF_ERR(NCDFGetVarFullName(nGroupID, nVarID, &pszVarName));
8517 287 : osSubdatasetName = (pszVarName != nullptr ? pszVarName : "");
8518 287 : CPLFree(pszVarName);
8519 : }
8520 :
8521 : // We have ignored at least one variable, so we should report them
8522 : // as subdatasets for reference.
8523 346 : if (nIgnoredVars > 0 && !bTreatAsSubdataset)
8524 : {
8525 24 : CPLDebug("GDAL_netCDF",
8526 : "As %d variables were ignored, creating subdataset list "
8527 : "for reference. Variable #%d [%s] is the main variable",
8528 : nIgnoredVars, nVarID, osSubdatasetName.c_str());
8529 24 : poDS->CreateSubDatasetList(cdfid);
8530 : }
8531 :
8532 : // Open the NETCDF subdataset NETCDF:"filename":subdataset.
8533 346 : int var = -1;
8534 346 : NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &nGroupID, &var);
8535 : // Now we can forget the root cdfid and only use the selected group.
8536 346 : cdfid = nGroupID;
8537 346 : int nd = 0;
8538 346 : nc_inq_varndims(cdfid, var, &nd);
8539 :
8540 346 : poDS->m_anDimIds.resize(nd);
8541 :
8542 : // X, Y, Z position in array
8543 692 : std::vector<int> anBandDimPos(nd);
8544 :
8545 346 : nc_inq_vardimid(cdfid, var, poDS->m_anDimIds.data());
8546 :
8547 : // Check if somebody tried to pass a variable with less than 1D.
8548 346 : if (nd < 1)
8549 : {
8550 0 : CPLError(CE_Warning, CPLE_AppDefined,
8551 : "Variable has %d dimension(s) - not supported.", nd);
8552 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8553 : // with GDALDataset own mutex.
8554 0 : delete poDS;
8555 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8556 0 : return nullptr;
8557 : }
8558 :
8559 : // CF-1 Convention
8560 : //
8561 : // Dimensions to appear in the relative order T, then Z, then Y,
8562 : // then X to the file. All other dimensions should, whenever
8563 : // possible, be placed to the left of the spatiotemporal
8564 : // dimensions.
8565 :
8566 : // Verify that dimensions are in the {T,Z,Y,X} or {T,Z,Y,X} order
8567 : // Ideally we should detect for other ordering and act accordingly
8568 : // Only done if file has Conventions=CF-* and only prints warning
8569 : // To disable set GDAL_NETCDF_VERIFY_DIMS=NO and to use only
8570 : // attributes (not varnames) set GDAL_NETCDF_VERIFY_DIMS=STRICT
8571 : const bool bCheckDims =
8572 692 : CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES")) &&
8573 346 : STARTS_WITH_CI(szConventions, "CF");
8574 :
8575 346 : if (nd >= 2 && bCheckDims)
8576 : {
8577 262 : char szDimName1[NC_MAX_NAME + 1] = {};
8578 262 : char szDimName2[NC_MAX_NAME + 1] = {};
8579 262 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 1], szDimName1);
8580 262 : NCDF_ERR(status);
8581 262 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 2], szDimName2);
8582 262 : NCDF_ERR(status);
8583 418 : if (NCDFIsVarLongitude(cdfid, -1, szDimName1) == false &&
8584 156 : NCDFIsVarProjectionX(cdfid, -1, szDimName1) == false)
8585 : {
8586 4 : CPLError(CE_Warning, CPLE_AppDefined,
8587 : "dimension #%d (%s) is not a Longitude/X dimension.",
8588 : nd - 1, szDimName1);
8589 : }
8590 418 : if (NCDFIsVarLatitude(cdfid, -1, szDimName2) == false &&
8591 156 : NCDFIsVarProjectionY(cdfid, -1, szDimName2) == false)
8592 : {
8593 4 : CPLError(CE_Warning, CPLE_AppDefined,
8594 : "dimension #%d (%s) is not a Latitude/Y dimension.",
8595 : nd - 2, szDimName2);
8596 : }
8597 262 : if ((NCDFIsVarLongitude(cdfid, -1, szDimName2) ||
8598 264 : NCDFIsVarProjectionX(cdfid, -1, szDimName2)) &&
8599 2 : (NCDFIsVarLatitude(cdfid, -1, szDimName1) ||
8600 0 : NCDFIsVarProjectionY(cdfid, -1, szDimName1)))
8601 : {
8602 2 : poDS->bSwitchedXY = true;
8603 : }
8604 262 : if (nd >= 3)
8605 : {
8606 52 : char szDimName3[NC_MAX_NAME + 1] = {};
8607 : status =
8608 52 : nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 3], szDimName3);
8609 52 : NCDF_ERR(status);
8610 52 : if (nd >= 4)
8611 : {
8612 13 : char szDimName4[NC_MAX_NAME + 1] = {};
8613 : status =
8614 13 : nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 4], szDimName4);
8615 13 : NCDF_ERR(status);
8616 13 : if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false)
8617 : {
8618 0 : CPLError(CE_Warning, CPLE_AppDefined,
8619 : "dimension #%d (%s) is not a Vertical dimension.",
8620 : nd - 3, szDimName3);
8621 : }
8622 13 : if (NCDFIsVarTimeCoord(cdfid, -1, szDimName4) == false)
8623 : {
8624 0 : CPLError(CE_Warning, CPLE_AppDefined,
8625 : "dimension #%d (%s) is not a Time dimension.",
8626 : nd - 4, szDimName4);
8627 : }
8628 : }
8629 : else
8630 : {
8631 75 : if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false &&
8632 36 : NCDFIsVarTimeCoord(cdfid, -1, szDimName3) == false)
8633 : {
8634 0 : CPLError(CE_Warning, CPLE_AppDefined,
8635 : "dimension #%d (%s) is not a "
8636 : "Time or Vertical dimension.",
8637 : nd - 3, szDimName3);
8638 : }
8639 : }
8640 : }
8641 : }
8642 :
8643 : // For example for EMIT data (https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/),
8644 : // dimension order is downtrack, crosstrack, bands
8645 346 : bool bYXBandOrder = false;
8646 346 : if (nd == 3)
8647 : {
8648 44 : char szDimName[NC_MAX_NAME + 1] = {};
8649 44 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[2], szDimName);
8650 44 : NCDF_ERR(status);
8651 44 : bYXBandOrder =
8652 44 : strcmp(szDimName, "bands") == 0 || strcmp(szDimName, "band") == 0;
8653 : }
8654 :
8655 : // Get X dimensions information.
8656 : size_t xdim;
8657 346 : poDS->nXDimID = poDS->m_anDimIds[bYXBandOrder ? 1 : nd - 1];
8658 346 : nc_inq_dimlen(cdfid, poDS->nXDimID, &xdim);
8659 :
8660 : // Get Y dimension information.
8661 : size_t ydim;
8662 346 : if (nd >= 2)
8663 : {
8664 342 : poDS->nYDimID = poDS->m_anDimIds[bYXBandOrder ? 0 : nd - 2];
8665 342 : nc_inq_dimlen(cdfid, poDS->nYDimID, &ydim);
8666 : }
8667 : else
8668 : {
8669 4 : poDS->nYDimID = -1;
8670 4 : ydim = 1;
8671 : }
8672 :
8673 346 : if (xdim > INT_MAX || ydim > INT_MAX)
8674 : {
8675 0 : CPLError(CE_Failure, CPLE_AppDefined,
8676 : "Invalid raster dimensions: " CPL_FRMT_GUIB "x" CPL_FRMT_GUIB,
8677 : static_cast<GUIntBig>(xdim), static_cast<GUIntBig>(ydim));
8678 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8679 : // with GDALDataset own mutex.
8680 0 : delete poDS;
8681 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8682 0 : return nullptr;
8683 : }
8684 :
8685 346 : poDS->nRasterXSize = static_cast<int>(xdim);
8686 346 : poDS->nRasterYSize = static_cast<int>(ydim);
8687 :
8688 346 : unsigned int k = 0;
8689 1113 : for (int j = 0; j < nd; j++)
8690 : {
8691 767 : if (poDS->m_anDimIds[j] == poDS->nXDimID)
8692 : {
8693 346 : anBandDimPos[0] = j; // Save Position of XDim
8694 346 : k++;
8695 : }
8696 767 : if (poDS->m_anDimIds[j] == poDS->nYDimID)
8697 : {
8698 342 : anBandDimPos[1] = j; // Save Position of YDim
8699 342 : k++;
8700 : }
8701 : }
8702 : // X and Y Dimension Ids were not found!
8703 346 : if ((nd >= 2 && k != 2) || (nd == 1 && k != 1))
8704 : {
8705 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8706 : // with GDALDataset own mutex.
8707 0 : delete poDS;
8708 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8709 0 : return nullptr;
8710 : }
8711 :
8712 : // Read Metadata for this variable.
8713 :
8714 : // Should disable as is also done at band level, except driver needs the
8715 : // variables as metadata (e.g. projection).
8716 346 : poDS->ReadAttributes(cdfid, var);
8717 :
8718 : // Read Metadata for each dimension.
8719 346 : int *panDimIds = nullptr;
8720 346 : NCDFGetVisibleDims(cdfid, &ndims, &panDimIds);
8721 : // With NetCDF-4 groups panDimIds is not always [0..dim_count-1] like
8722 : // in NetCDF-3 because we see only the dimensions of the selected group
8723 : // and its parents.
8724 : // poDS->papszDimName is indexed by dim IDs, so it must contains all IDs
8725 : // [0..max(panDimIds)], but they are not all useful so we fill names
8726 : // of useless dims with empty string.
8727 346 : if (panDimIds)
8728 : {
8729 346 : const int nMaxDimId = *std::max_element(panDimIds, panDimIds + ndims);
8730 346 : std::set<int> oSetExistingDimIds;
8731 1149 : for (int i = 0; i < ndims; i++)
8732 : {
8733 803 : oSetExistingDimIds.insert(panDimIds[i]);
8734 : }
8735 346 : std::set<int> oSetDimIdsUsedByVar;
8736 1113 : for (int i = 0; i < nd; i++)
8737 : {
8738 767 : oSetDimIdsUsedByVar.insert(poDS->m_anDimIds[i]);
8739 : }
8740 1151 : for (int j = 0; j <= nMaxDimId; j++)
8741 : {
8742 : // Is j dim used?
8743 805 : if (oSetExistingDimIds.find(j) != oSetExistingDimIds.end())
8744 : {
8745 : // Useful dim.
8746 803 : char szTemp[NC_MAX_NAME + 1] = {};
8747 803 : status = nc_inq_dimname(cdfid, j, szTemp);
8748 803 : if (status != NC_NOERR)
8749 : {
8750 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8751 : // deadlock with GDALDataset own
8752 : // mutex.
8753 0 : delete poDS;
8754 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8755 0 : return nullptr;
8756 : }
8757 803 : poDS->papszDimName.AddString(szTemp);
8758 :
8759 803 : if (oSetDimIdsUsedByVar.find(j) != oSetDimIdsUsedByVar.end())
8760 : {
8761 767 : int nDimGroupId = -1;
8762 767 : int nDimVarId = -1;
8763 767 : if (NCDFResolveVar(cdfid, poDS->papszDimName[j],
8764 767 : &nDimGroupId, &nDimVarId) == CE_None)
8765 : {
8766 567 : poDS->ReadAttributes(nDimGroupId, nDimVarId);
8767 : }
8768 : }
8769 : }
8770 : else
8771 : {
8772 : // Useless dim.
8773 2 : poDS->papszDimName.AddString("");
8774 : }
8775 : }
8776 346 : CPLFree(panDimIds);
8777 : }
8778 :
8779 : // Set projection info.
8780 692 : std::vector<std::string> aosRemovedMDItems;
8781 346 : if (nd > 1)
8782 : {
8783 342 : poDS->SetProjectionFromVar(cdfid, var,
8784 : /*bReadSRSOnly=*/false,
8785 : /* pszGivenGM = */ nullptr,
8786 : /* returnProjStr = */ nullptr,
8787 : /* sg = */ nullptr, &aosRemovedMDItems);
8788 : }
8789 :
8790 : // Override bottom-up with GDAL_NETCDF_BOTTOMUP config option.
8791 346 : const char *pszValue = CPLGetConfigOption("GDAL_NETCDF_BOTTOMUP", nullptr);
8792 346 : if (pszValue)
8793 : {
8794 24 : poDS->bBottomUp = CPLTestBool(pszValue);
8795 24 : CPLDebug("GDAL_netCDF",
8796 : "set bBottomUp=%d because GDAL_NETCDF_BOTTOMUP=%s",
8797 24 : static_cast<int>(poDS->bBottomUp), pszValue);
8798 : }
8799 :
8800 : // Save non-spatial dimension info.
8801 :
8802 346 : int *panBandZLev = nullptr;
8803 346 : int nDim = (nd >= 2) ? 2 : 1;
8804 : size_t lev_count;
8805 346 : size_t nTotLevCount = 1;
8806 346 : nc_type nType = NC_NAT;
8807 :
8808 692 : CPLString osExtraDimNames;
8809 :
8810 346 : if (nd > 2)
8811 : {
8812 60 : nDim = 2;
8813 60 : panBandZLev = static_cast<int *>(CPLCalloc(nd - 2, sizeof(int)));
8814 :
8815 60 : osExtraDimNames = "{";
8816 :
8817 60 : char szDimName[NC_MAX_NAME + 1] = {};
8818 :
8819 60 : bool bREPORT_EXTRA_DIM_VALUESWarningEmitted = false;
8820 259 : for (int j = 0; j < nd; j++)
8821 : {
8822 338 : if ((poDS->m_anDimIds[j] != poDS->nXDimID) &&
8823 139 : (poDS->m_anDimIds[j] != poDS->nYDimID))
8824 : {
8825 79 : nc_inq_dimlen(cdfid, poDS->m_anDimIds[j], &lev_count);
8826 79 : nTotLevCount *= lev_count;
8827 79 : panBandZLev[nDim - 2] = static_cast<int>(lev_count);
8828 79 : anBandDimPos[nDim] = j; // Save Position of ZDim
8829 : // Save non-spatial dimension names.
8830 79 : if (nc_inq_dimname(cdfid, poDS->m_anDimIds[j], szDimName) ==
8831 : NC_NOERR)
8832 : {
8833 79 : osExtraDimNames += szDimName;
8834 79 : if (j < nd - 3)
8835 : {
8836 19 : osExtraDimNames += ",";
8837 : }
8838 :
8839 79 : int nIdxGroupID = -1;
8840 79 : int nIdxVarID = Get1DVariableIndexedByDimension(
8841 79 : cdfid, poDS->m_anDimIds[j], szDimName, true,
8842 79 : &nIdxGroupID);
8843 79 : poDS->m_anExtraDimGroupIds.push_back(nIdxGroupID);
8844 79 : poDS->m_anExtraDimVarIds.push_back(nIdxVarID);
8845 :
8846 79 : if (nIdxVarID >= 0)
8847 : {
8848 70 : nc_inq_vartype(nIdxGroupID, nIdxVarID, &nType);
8849 : char szExtraDimDef[NC_MAX_NAME + 1];
8850 70 : snprintf(szExtraDimDef, sizeof(szExtraDimDef),
8851 : "{%ld,%d}", (long)lev_count, nType);
8852 : char szTemp[NC_MAX_NAME + 32 + 1];
8853 70 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
8854 : szDimName);
8855 70 : poDS->papszMetadata = CSLSetNameValue(
8856 : poDS->papszMetadata, szTemp, szExtraDimDef);
8857 :
8858 : // Retrieving data for unlimited dimensions might be
8859 : // costly on network storage, so don't do it.
8860 : // Each band will capture the value along the extra
8861 : // dimension in its NETCDF_DIM_xxxx band metadata item
8862 : // Addresses use case of
8863 : // https://lists.osgeo.org/pipermail/gdal-dev/2023-May/057209.html
8864 : const bool bIsLocal =
8865 70 : VSIIsLocal(osFilenameForNCOpen.c_str());
8866 : bool bListDimValues =
8867 71 : bIsLocal || lev_count == 1 ||
8868 1 : !NCDFIsUnlimitedDim(poDS->eFormat ==
8869 : NCDF_FORMAT_NC4,
8870 1 : cdfid, poDS->m_anDimIds[j]);
8871 : const char *pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES =
8872 70 : CPLGetConfigOption(
8873 : "GDAL_NETCDF_REPORT_EXTRA_DIM_VALUES", nullptr);
8874 70 : if (pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES)
8875 : {
8876 2 : bListDimValues = CPLTestBool(
8877 : pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES);
8878 : }
8879 68 : else if (!bListDimValues && !bIsLocal &&
8880 1 : !bREPORT_EXTRA_DIM_VALUESWarningEmitted)
8881 : {
8882 1 : bREPORT_EXTRA_DIM_VALUESWarningEmitted = true;
8883 1 : CPLDebug(
8884 : "GDAL_netCDF",
8885 : "Listing extra dimension values is skipped "
8886 : "because this dataset is hosted on a network "
8887 : "file system, and such an operation could be "
8888 : "slow. If you still want to proceed, set the "
8889 : "GDAL_NETCDF_REPORT_EXTRA_DIM_VALUES "
8890 : "configuration option to YES");
8891 : }
8892 70 : if (bListDimValues)
8893 : {
8894 68 : char *pszTemp = nullptr;
8895 68 : if (NCDFGet1DVar(nIdxGroupID, nIdxVarID,
8896 68 : &pszTemp) == CE_None)
8897 : {
8898 68 : snprintf(szTemp, sizeof(szTemp),
8899 : "NETCDF_DIM_%s_VALUES", szDimName);
8900 68 : poDS->papszMetadata = CSLSetNameValue(
8901 : poDS->papszMetadata, szTemp, pszTemp);
8902 68 : CPLFree(pszTemp);
8903 : }
8904 : }
8905 : }
8906 : }
8907 : else
8908 : {
8909 0 : poDS->m_anExtraDimGroupIds.push_back(-1);
8910 0 : poDS->m_anExtraDimVarIds.push_back(-1);
8911 : }
8912 :
8913 79 : nDim++;
8914 : }
8915 : }
8916 60 : osExtraDimNames += "}";
8917 60 : poDS->papszMetadata = CSLSetNameValue(
8918 : poDS->papszMetadata, "NETCDF_DIM_EXTRA", osExtraDimNames);
8919 : }
8920 :
8921 : // Store Metadata.
8922 356 : for (const auto &osStr : aosRemovedMDItems)
8923 10 : poDS->papszMetadata =
8924 10 : CSLSetNameValue(poDS->papszMetadata, osStr.c_str(), nullptr);
8925 :
8926 346 : poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
8927 :
8928 : // Create bands.
8929 :
8930 : // Arbitrary threshold.
8931 : int nMaxBandCount =
8932 346 : atoi(CPLGetConfigOption("GDAL_MAX_BAND_COUNT", "32768"));
8933 346 : if (nMaxBandCount <= 0)
8934 0 : nMaxBandCount = 32768;
8935 346 : if (nTotLevCount > static_cast<unsigned int>(nMaxBandCount))
8936 : {
8937 0 : CPLError(CE_Warning, CPLE_AppDefined,
8938 : "Limiting number of bands to %d instead of %u", nMaxBandCount,
8939 : static_cast<unsigned int>(nTotLevCount));
8940 0 : nTotLevCount = static_cast<unsigned int>(nMaxBandCount);
8941 : }
8942 346 : if (poDS->nRasterXSize == 0 || poDS->nRasterYSize == 0)
8943 : {
8944 0 : poDS->nRasterXSize = 0;
8945 0 : poDS->nRasterYSize = 0;
8946 0 : nTotLevCount = 0;
8947 0 : if (poDS->GetLayerCount() == 0)
8948 : {
8949 0 : CPLFree(panBandZLev);
8950 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8951 : // deadlock with GDALDataset own mutex.
8952 0 : delete poDS;
8953 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8954 0 : return nullptr;
8955 : }
8956 : }
8957 346 : if (bSeveralVariablesAsBands)
8958 : {
8959 6 : const auto &listVariables = oMap2DDimsToGroupAndVar.begin()->second;
8960 24 : for (int iBand = 0; iBand < static_cast<int>(listVariables.size());
8961 : ++iBand)
8962 : {
8963 18 : int bandVarGroupId = listVariables[iBand].first;
8964 18 : int bandVarId = listVariables[iBand].second;
8965 : netCDFRasterBand *poBand = new netCDFRasterBand(
8966 0 : netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, bandVarGroupId,
8967 18 : bandVarId, nDim, 0, nullptr, anBandDimPos.data(), iBand + 1);
8968 18 : poDS->SetBand(iBand + 1, poBand);
8969 : }
8970 : }
8971 : else
8972 : {
8973 788 : for (unsigned int lev = 0; lev < nTotLevCount; lev++)
8974 : {
8975 : netCDFRasterBand *poBand = new netCDFRasterBand(
8976 0 : netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, cdfid, var, nDim,
8977 448 : lev, panBandZLev, anBandDimPos.data(), lev + 1);
8978 448 : poDS->SetBand(lev + 1, poBand);
8979 : }
8980 : }
8981 :
8982 346 : if (panBandZLev)
8983 60 : CPLFree(panBandZLev);
8984 : // Handle angular geographic coordinates here
8985 :
8986 : // Initialize any PAM information.
8987 346 : if (bTreatAsSubdataset)
8988 : {
8989 59 : poDS->SetPhysicalFilename(poDS->osFilename);
8990 59 : poDS->SetSubdatasetName(osSubdatasetName);
8991 : }
8992 :
8993 346 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
8994 : // GDALDataset own mutex.
8995 346 : poDS->TryLoadXML();
8996 :
8997 346 : if (bTreatAsSubdataset)
8998 59 : poDS->oOvManager.Initialize(poDS, ":::VIRTUAL:::");
8999 : else
9000 287 : poDS->oOvManager.Initialize(poDS, poDS->osFilename);
9001 :
9002 346 : CPLAcquireMutex(hNCMutex, 1000.0);
9003 :
9004 346 : return poDS;
9005 : }
9006 :
9007 : /************************************************************************/
9008 : /* CopyMetadata() */
9009 : /* */
9010 : /* Create a copy of metadata for NC_GLOBAL or a variable */
9011 : /************************************************************************/
9012 :
9013 151 : static void CopyMetadata(GDALDataset *poSrcDS, GDALRasterBand *poSrcBand,
9014 : GDALRasterBand *poDstBand, int nCdfId, int CDFVarID,
9015 : const char *pszPrefix)
9016 : {
9017 : // Remove the following band meta but set them later from band data.
9018 151 : const char *const papszIgnoreBand[] = {
9019 : CF_ADD_OFFSET, CF_SCALE_FACTOR, "valid_range", "_Unsigned",
9020 : NCDF_FillValue, "coordinates", nullptr};
9021 151 : const char *const papszIgnoreGlobal[] = {"NETCDF_DIM_EXTRA", nullptr};
9022 :
9023 151 : CSLConstList papszMetadata = nullptr;
9024 151 : if (poSrcDS)
9025 : {
9026 63 : papszMetadata = poSrcDS->GetMetadata();
9027 : }
9028 88 : else if (poSrcBand)
9029 : {
9030 88 : papszMetadata = poSrcBand->GetMetadata();
9031 : }
9032 :
9033 630 : for (const auto &[pszKey, pszValue] : cpl::IterateNameValue(papszMetadata))
9034 : {
9035 : #ifdef NCDF_DEBUG
9036 : CPLDebug("GDAL_netCDF", "copy metadata [%s]=[%s]", pszKey, pszValue);
9037 : #endif
9038 :
9039 479 : CPLString osMetaName(pszKey);
9040 :
9041 : // Check for items that match pszPrefix if applicable.
9042 479 : if (pszPrefix && !EQUAL(pszPrefix, ""))
9043 : {
9044 : // Remove prefix.
9045 115 : if (STARTS_WITH(osMetaName.c_str(), pszPrefix))
9046 : {
9047 17 : osMetaName = osMetaName.substr(strlen(pszPrefix));
9048 : }
9049 : // Only copy items that match prefix.
9050 : else
9051 : {
9052 98 : continue;
9053 : }
9054 : }
9055 :
9056 : // Fix various issues with metadata translation.
9057 381 : if (CDFVarID == NC_GLOBAL)
9058 : {
9059 : // Do not copy items in papszIgnoreGlobal and NETCDF_DIM_*.
9060 479 : if ((CSLFindString(papszIgnoreGlobal, osMetaName) != -1) ||
9061 237 : (STARTS_WITH(osMetaName, "NETCDF_DIM_")))
9062 21 : continue;
9063 : // Remove NC_GLOBAL prefix for netcdf global Metadata.
9064 221 : else if (STARTS_WITH(osMetaName, "NC_GLOBAL#"))
9065 : {
9066 33 : osMetaName = osMetaName.substr(strlen("NC_GLOBAL#"));
9067 : }
9068 : // GDAL Metadata renamed as GDAL-[meta].
9069 188 : else if (strstr(osMetaName, "#") == nullptr)
9070 : {
9071 15 : osMetaName = "GDAL_" + osMetaName;
9072 : }
9073 : // Keep time, lev and depth information for safe-keeping.
9074 : // Time and vertical coordinate handling need improvements.
9075 : /*
9076 : else if( STARTS_WITH(szMetaName, "time#") )
9077 : {
9078 : szMetaName[4] = '-';
9079 : }
9080 : else if( STARTS_WITH(szMetaName, "lev#") )
9081 : {
9082 : szMetaName[3] = '-';
9083 : }
9084 : else if( STARTS_WITH(szMetaName, "depth#") )
9085 : {
9086 : szMetaName[5] = '-';
9087 : }
9088 : */
9089 : // Only copy data without # (previously all data was copied).
9090 221 : if (strstr(osMetaName, "#") != nullptr)
9091 173 : continue;
9092 : // netCDF attributes do not like the '#' character.
9093 : // for( unsigned int h=0; h < strlen(szMetaName) -1 ; h++ ) {
9094 : // if( szMetaName[h] == '#') szMetaName[h] = '-';
9095 : // }
9096 : }
9097 : else
9098 : {
9099 : // Do not copy varname, stats, NETCDF_DIM_*, nodata
9100 : // and items in papszIgnoreBand.
9101 139 : if (STARTS_WITH(osMetaName, "NETCDF_VARNAME") ||
9102 107 : STARTS_WITH(osMetaName, "STATISTICS_") ||
9103 107 : STARTS_WITH(osMetaName, "NETCDF_DIM_") ||
9104 74 : STARTS_WITH(osMetaName, "missing_value") ||
9105 293 : STARTS_WITH(osMetaName, "_FillValue") ||
9106 47 : CSLFindString(papszIgnoreBand, osMetaName) != -1)
9107 97 : continue;
9108 : }
9109 :
9110 : #ifdef NCDF_DEBUG
9111 : CPLDebug("GDAL_netCDF", "copy name=[%s] value=[%s]", osMetaName.c_str(),
9112 : pszValue);
9113 : #endif
9114 90 : if (NCDFPutAttr(nCdfId, CDFVarID, osMetaName, pszValue) != CE_None)
9115 : {
9116 0 : CPLDebug("GDAL_netCDF", "NCDFPutAttr(%d, %d, %s, %s) failed",
9117 : nCdfId, CDFVarID, osMetaName.c_str(), pszValue);
9118 : }
9119 : }
9120 :
9121 : // Set add_offset and scale_factor here if present.
9122 151 : if (poSrcBand && poDstBand)
9123 : {
9124 :
9125 88 : int bGotAddOffset = FALSE;
9126 88 : const double dfAddOffset = poSrcBand->GetOffset(&bGotAddOffset);
9127 88 : int bGotScale = FALSE;
9128 88 : const double dfScale = poSrcBand->GetScale(&bGotScale);
9129 :
9130 88 : if (bGotAddOffset && dfAddOffset != 0.0)
9131 1 : poDstBand->SetOffset(dfAddOffset);
9132 88 : if (bGotScale && dfScale != 1.0)
9133 1 : poDstBand->SetScale(dfScale);
9134 : }
9135 151 : }
9136 :
9137 : /************************************************************************/
9138 : /* CreateLL() */
9139 : /* */
9140 : /* Shared functionality between netCDFDataset::Create() and */
9141 : /* netCDF::CreateCopy() for creating netcdf file based on a set of */
9142 : /* options and a configuration. */
9143 : /************************************************************************/
9144 :
9145 195 : netCDFDataset *netCDFDataset::CreateLL(const char *pszFilename, int nXSize,
9146 : int nYSize, int nBandsIn,
9147 : char **papszOptions)
9148 : {
9149 195 : if (!((nXSize == 0 && nYSize == 0 && nBandsIn == 0) ||
9150 123 : (nXSize > 0 && nYSize > 0 && nBandsIn > 0)))
9151 : {
9152 1 : return nullptr;
9153 : }
9154 :
9155 194 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
9156 : // GDALDataset own mutex.
9157 194 : netCDFDataset *poDS = new netCDFDataset();
9158 194 : CPLAcquireMutex(hNCMutex, 1000.0);
9159 :
9160 194 : poDS->nRasterXSize = nXSize;
9161 194 : poDS->nRasterYSize = nYSize;
9162 194 : poDS->eAccess = GA_Update;
9163 194 : poDS->osFilename = pszFilename;
9164 :
9165 : // From gtiff driver, is this ok?
9166 : /*
9167 : poDS->nBlockXSize = nXSize;
9168 : poDS->nBlockYSize = 1;
9169 : poDS->nBlocksPerBand =
9170 : ((nYSize + poDS->nBlockYSize - 1) / poDS->nBlockYSize)
9171 : * ((nXSize + poDS->nBlockXSize - 1) / poDS->nBlockXSize);
9172 : */
9173 :
9174 : // process options.
9175 194 : poDS->papszCreationOptions = CSLDuplicate(papszOptions);
9176 194 : poDS->ProcessCreationOptions();
9177 :
9178 194 : if (poDS->eMultipleLayerBehavior == SEPARATE_FILES)
9179 : {
9180 : VSIStatBuf sStat;
9181 2 : if (VSIStat(pszFilename, &sStat) == 0)
9182 : {
9183 0 : if (!VSI_ISDIR(sStat.st_mode))
9184 : {
9185 0 : CPLError(CE_Failure, CPLE_FileIO,
9186 : "%s is an existing file, but not a directory",
9187 : pszFilename);
9188 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9189 : // deadlock with GDALDataset own
9190 : // mutex.
9191 0 : delete poDS;
9192 0 : CPLAcquireMutex(hNCMutex, 1000.0);
9193 0 : return nullptr;
9194 : }
9195 : }
9196 2 : else if (VSIMkdir(pszFilename, 0755) != 0)
9197 : {
9198 1 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s directory",
9199 : pszFilename);
9200 1 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9201 : // deadlock with GDALDataset own mutex.
9202 1 : delete poDS;
9203 1 : CPLAcquireMutex(hNCMutex, 1000.0);
9204 1 : return nullptr;
9205 : }
9206 :
9207 1 : return poDS;
9208 : }
9209 : // Create the dataset.
9210 384 : CPLString osFilenameForNCCreate(pszFilename);
9211 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
9212 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
9213 : {
9214 : char *pszTemp =
9215 : CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
9216 : osFilenameForNCCreate = pszTemp;
9217 : CPLFree(pszTemp);
9218 : }
9219 : #endif
9220 :
9221 : #if defined(_WIN32)
9222 : {
9223 : // Works around bug of msys2 netCDF 4.9.0 package where nc_create()
9224 : // crashes
9225 : VSIStatBuf sStat;
9226 : const std::string osDirname =
9227 : CPLGetDirnameSafe(osFilenameForNCCreate.c_str());
9228 : if (VSIStat(osDirname.c_str(), &sStat) != 0)
9229 : {
9230 : CPLError(CE_Failure, CPLE_OpenFailed,
9231 : "Unable to create netCDF file %s: non existing output "
9232 : "directory",
9233 : pszFilename);
9234 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9235 : // deadlock with GDALDataset own mutex.
9236 : delete poDS;
9237 : CPLAcquireMutex(hNCMutex, 1000.0);
9238 : return nullptr;
9239 : }
9240 : }
9241 : #endif
9242 :
9243 : int status =
9244 192 : nc_create(osFilenameForNCCreate, poDS->nCreateMode, &(poDS->cdfid));
9245 :
9246 : // Put into define mode.
9247 192 : poDS->SetDefineMode(true);
9248 :
9249 192 : if (status != NC_NOERR)
9250 : {
9251 30 : CPLError(CE_Failure, CPLE_OpenFailed,
9252 : "Unable to create netCDF file %s (Error code %d): %s .",
9253 : pszFilename, status, nc_strerror(status));
9254 30 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
9255 : // with GDALDataset own mutex.
9256 30 : delete poDS;
9257 30 : CPLAcquireMutex(hNCMutex, 1000.0);
9258 30 : return nullptr;
9259 : }
9260 :
9261 : // Define dimensions.
9262 162 : if (nXSize > 0 && nYSize > 0)
9263 : {
9264 109 : poDS->papszDimName.AddString(NCDF_DIMNAME_X);
9265 : status =
9266 109 : nc_def_dim(poDS->cdfid, NCDF_DIMNAME_X, nXSize, &(poDS->nXDimID));
9267 109 : NCDF_ERR(status);
9268 109 : CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
9269 : poDS->cdfid, NCDF_DIMNAME_X, nXSize, poDS->nXDimID);
9270 :
9271 109 : poDS->papszDimName.AddString(NCDF_DIMNAME_Y);
9272 : status =
9273 109 : nc_def_dim(poDS->cdfid, NCDF_DIMNAME_Y, nYSize, &(poDS->nYDimID));
9274 109 : NCDF_ERR(status);
9275 109 : CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
9276 : poDS->cdfid, NCDF_DIMNAME_Y, nYSize, poDS->nYDimID);
9277 : }
9278 :
9279 162 : return poDS;
9280 : }
9281 :
9282 : /************************************************************************/
9283 : /* Create() */
9284 : /************************************************************************/
9285 :
9286 126 : GDALDataset *netCDFDataset::Create(const char *pszFilename, int nXSize,
9287 : int nYSize, int nBandsIn, GDALDataType eType,
9288 : char **papszOptions)
9289 : {
9290 126 : CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::Create(%s, ...)",
9291 : pszFilename);
9292 :
9293 : const char *legacyCreationOp =
9294 126 : CSLFetchNameValueDef(papszOptions, "GEOMETRY_ENCODING", "CF_1.8");
9295 252 : std::string legacyCreationOp_s = std::string(legacyCreationOp);
9296 :
9297 : // Check legacy creation op FIRST
9298 :
9299 126 : bool legacyCreateMode = false;
9300 :
9301 126 : if (nXSize != 0 || nYSize != 0 || nBandsIn != 0)
9302 : {
9303 56 : legacyCreateMode = true;
9304 : }
9305 70 : else if (legacyCreationOp_s == "CF_1.8")
9306 : {
9307 54 : legacyCreateMode = false;
9308 : }
9309 :
9310 16 : else if (legacyCreationOp_s == "WKT")
9311 : {
9312 16 : legacyCreateMode = true;
9313 : }
9314 :
9315 : else
9316 : {
9317 0 : CPLError(
9318 : CE_Failure, CPLE_NotSupported,
9319 : "Dataset creation option GEOMETRY_ENCODING=%s is not supported.",
9320 : legacyCreationOp_s.c_str());
9321 0 : return nullptr;
9322 : }
9323 :
9324 252 : CPLStringList aosOptions(CSLDuplicate(papszOptions));
9325 238 : if (aosOptions.FetchNameValue("FORMAT") == nullptr &&
9326 112 : (eType == GDT_UInt16 || eType == GDT_UInt32 || eType == GDT_UInt64 ||
9327 : eType == GDT_Int64))
9328 : {
9329 10 : CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
9330 10 : aosOptions.SetNameValue("FORMAT", "NC4");
9331 : }
9332 :
9333 252 : CPLStringList aosBandNames;
9334 126 : if (const char *pszBandNames = aosOptions.FetchNameValue("BAND_NAMES"))
9335 : {
9336 : aosBandNames =
9337 2 : CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);
9338 :
9339 2 : if (aosBandNames.Count() != nBandsIn)
9340 : {
9341 1 : CPLError(CE_Failure, CPLE_OpenFailed,
9342 : "Attempted to create netCDF with %d bands but %d names "
9343 : "provided in BAND_NAMES.",
9344 : nBandsIn, aosBandNames.Count());
9345 :
9346 1 : return nullptr;
9347 : }
9348 : }
9349 :
9350 250 : CPLMutexHolderD(&hNCMutex);
9351 :
9352 125 : auto poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize, nBandsIn,
9353 : aosOptions.List());
9354 :
9355 125 : if (!poDS)
9356 19 : return nullptr;
9357 :
9358 106 : if (!legacyCreateMode)
9359 : {
9360 37 : poDS->bSGSupport = true;
9361 37 : poDS->vcdf.enableFullVirtualMode();
9362 : }
9363 :
9364 : else
9365 : {
9366 69 : poDS->bSGSupport = false;
9367 : }
9368 :
9369 : // Should we write signed or unsigned byte?
9370 : // TODO should this only be done in Create()
9371 106 : poDS->bSignedData = true;
9372 106 : const char *pszValue = CSLFetchNameValueDef(papszOptions, "PIXELTYPE", "");
9373 106 : if (eType == GDT_Byte && !EQUAL(pszValue, "SIGNEDBYTE"))
9374 15 : poDS->bSignedData = false;
9375 :
9376 : // Add Conventions, GDAL info and history.
9377 106 : if (poDS->cdfid >= 0)
9378 : {
9379 : const char *CF_Vector_Conv =
9380 173 : poDS->bSGSupport ||
9381 : // Use of variable length strings require CF-1.8
9382 68 : EQUAL(aosOptions.FetchNameValueDef("FORMAT", ""), "NC4")
9383 : ? NCDF_CONVENTIONS_CF_V1_8
9384 173 : : NCDF_CONVENTIONS_CF_V1_6;
9385 105 : poDS->bWriteGDALVersion = CPLTestBool(
9386 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
9387 105 : poDS->bWriteGDALHistory = CPLTestBool(
9388 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
9389 105 : NCDFAddGDALHistory(poDS->cdfid, pszFilename, poDS->bWriteGDALVersion,
9390 105 : poDS->bWriteGDALHistory, "", "Create",
9391 : (nBandsIn == 0) ? CF_Vector_Conv
9392 : : GDAL_DEFAULT_NCDF_CONVENTIONS);
9393 : }
9394 :
9395 : // Define bands.
9396 197 : for (int iBand = 1; iBand <= nBandsIn; iBand++)
9397 : {
9398 : const char *pszBandName =
9399 91 : aosBandNames.empty() ? nullptr : aosBandNames[iBand - 1];
9400 :
9401 91 : poDS->SetBand(iBand, new netCDFRasterBand(
9402 91 : netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS,
9403 91 : eType, iBand, poDS->bSignedData, pszBandName));
9404 : }
9405 :
9406 106 : CPLDebug("GDAL_netCDF", "netCDFDataset::Create(%s, ...) done", pszFilename);
9407 : // Return same dataset.
9408 106 : return poDS;
9409 : }
9410 :
9411 : template <class T>
9412 88 : static CPLErr NCDFCopyBand(GDALRasterBand *poSrcBand, GDALRasterBand *poDstBand,
9413 : int nXSize, int nYSize, GDALProgressFunc pfnProgress,
9414 : void *pProgressData)
9415 : {
9416 88 : GDALDataType eDT = poSrcBand->GetRasterDataType();
9417 88 : CPLErr eErr = CE_None;
9418 88 : T *patScanline = static_cast<T *>(CPLMalloc(nXSize * sizeof(T)));
9419 :
9420 2557 : for (int iLine = 0; iLine < nYSize && eErr == CE_None; iLine++)
9421 : {
9422 2469 : eErr = poSrcBand->RasterIO(GF_Read, 0, iLine, nXSize, 1, patScanline,
9423 : nXSize, 1, eDT, 0, 0, nullptr);
9424 2469 : if (eErr != CE_None)
9425 : {
9426 0 : CPLDebug(
9427 : "GDAL_netCDF",
9428 : "NCDFCopyBand(), poSrcBand->RasterIO() returned error code %d",
9429 : eErr);
9430 : }
9431 : else
9432 : {
9433 2469 : eErr =
9434 : poDstBand->RasterIO(GF_Write, 0, iLine, nXSize, 1, patScanline,
9435 : nXSize, 1, eDT, 0, 0, nullptr);
9436 2469 : if (eErr != CE_None)
9437 0 : CPLDebug("GDAL_netCDF",
9438 : "NCDFCopyBand(), poDstBand->RasterIO() returned error "
9439 : "code %d",
9440 : eErr);
9441 : }
9442 :
9443 2469 : if (nYSize > 10 && (iLine % (nYSize / 10) == 1))
9444 : {
9445 246 : if (!pfnProgress(1.0 * iLine / nYSize, nullptr, pProgressData))
9446 : {
9447 0 : eErr = CE_Failure;
9448 0 : CPLError(CE_Failure, CPLE_UserInterrupt,
9449 : "User terminated CreateCopy()");
9450 : }
9451 : }
9452 : }
9453 :
9454 88 : CPLFree(patScanline);
9455 :
9456 88 : pfnProgress(1.0, nullptr, pProgressData);
9457 :
9458 88 : return eErr;
9459 : }
9460 :
9461 : /************************************************************************/
9462 : /* CreateCopy() */
9463 : /************************************************************************/
9464 :
9465 : GDALDataset *
9466 79 : netCDFDataset::CreateCopy(const char *pszFilename, GDALDataset *poSrcDS,
9467 : CPL_UNUSED int bStrict, char **papszOptions,
9468 : GDALProgressFunc pfnProgress, void *pProgressData)
9469 : {
9470 158 : CPLMutexHolderD(&hNCMutex);
9471 :
9472 79 : CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::CreateCopy(%s, ...)",
9473 : pszFilename);
9474 :
9475 79 : if (poSrcDS->GetRootGroup())
9476 : {
9477 5 : auto poDrv = GDALDriver::FromHandle(GDALGetDriverByName("netCDF"));
9478 5 : if (poDrv)
9479 : {
9480 5 : return poDrv->DefaultCreateCopy(pszFilename, poSrcDS, bStrict,
9481 : papszOptions, pfnProgress,
9482 5 : pProgressData);
9483 : }
9484 : }
9485 :
9486 74 : const int nBands = poSrcDS->GetRasterCount();
9487 74 : const int nXSize = poSrcDS->GetRasterXSize();
9488 74 : const int nYSize = poSrcDS->GetRasterYSize();
9489 74 : const char *pszWKT = poSrcDS->GetProjectionRef();
9490 :
9491 : // Check input bands for errors.
9492 74 : if (nBands == 0)
9493 : {
9494 1 : CPLError(CE_Failure, CPLE_NotSupported,
9495 : "NetCDF driver does not support "
9496 : "source dataset with zero band.");
9497 1 : return nullptr;
9498 : }
9499 :
9500 73 : GDALDataType eDT = GDT_Unknown;
9501 73 : GDALRasterBand *poSrcBand = nullptr;
9502 175 : for (int iBand = 1; iBand <= nBands; iBand++)
9503 : {
9504 106 : poSrcBand = poSrcDS->GetRasterBand(iBand);
9505 106 : eDT = poSrcBand->GetRasterDataType();
9506 106 : if (eDT == GDT_Unknown || GDALDataTypeIsComplex(eDT))
9507 : {
9508 4 : CPLError(CE_Failure, CPLE_NotSupported,
9509 : "NetCDF driver does not support source dataset with band "
9510 : "of complex type.");
9511 4 : return nullptr;
9512 : }
9513 : }
9514 :
9515 138 : CPLStringList aosBandNames;
9516 69 : if (const char *pszBandNames =
9517 69 : CSLFetchNameValue(papszOptions, "BAND_NAMES"))
9518 : {
9519 : aosBandNames =
9520 2 : CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);
9521 :
9522 2 : if (aosBandNames.Count() != nBands)
9523 : {
9524 1 : CPLError(CE_Failure, CPLE_OpenFailed,
9525 : "Attempted to create netCDF with %d bands but %d names "
9526 : "provided in BAND_NAMES.",
9527 : nBands, aosBandNames.Count());
9528 :
9529 1 : return nullptr;
9530 : }
9531 : }
9532 :
9533 68 : if (!pfnProgress(0.0, nullptr, pProgressData))
9534 0 : return nullptr;
9535 :
9536 : // Same as in Create().
9537 136 : CPLStringList aosOptions(CSLDuplicate(papszOptions));
9538 127 : if (aosOptions.FetchNameValue("FORMAT") == nullptr &&
9539 59 : (eDT == GDT_UInt16 || eDT == GDT_UInt32 || eDT == GDT_UInt64 ||
9540 : eDT == GDT_Int64))
9541 : {
9542 6 : CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
9543 6 : aosOptions.SetNameValue("FORMAT", "NC4");
9544 : }
9545 68 : netCDFDataset *poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize,
9546 : nBands, aosOptions.List());
9547 68 : if (!poDS)
9548 13 : return nullptr;
9549 :
9550 : // Copy global metadata.
9551 : // Add Conventions, GDAL info and history.
9552 55 : CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid, NC_GLOBAL, nullptr);
9553 55 : const bool bWriteGDALVersion = CPLTestBool(
9554 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
9555 55 : const bool bWriteGDALHistory = CPLTestBool(
9556 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
9557 55 : NCDFAddGDALHistory(
9558 : poDS->cdfid, pszFilename, bWriteGDALVersion, bWriteGDALHistory,
9559 55 : poSrcDS->GetMetadataItem("NC_GLOBAL#history"), "CreateCopy",
9560 55 : poSrcDS->GetMetadataItem("NC_GLOBAL#Conventions"));
9561 :
9562 55 : pfnProgress(0.1, nullptr, pProgressData);
9563 :
9564 : // Check for extra dimensions.
9565 55 : int nDim = 2;
9566 : char **papszExtraDimNames =
9567 55 : NCDFTokenizeArray(poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
9568 55 : char **papszExtraDimValues = nullptr;
9569 :
9570 55 : if (papszExtraDimNames != nullptr && CSLCount(papszExtraDimNames) > 0)
9571 : {
9572 5 : size_t nDimSizeTot = 1;
9573 : // first make sure dimensions lengths compatible with band count
9574 : // for( int i=0; i<CSLCount(papszExtraDimNames ); i++ ) {
9575 13 : for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--)
9576 : {
9577 : char szTemp[NC_MAX_NAME + 32 + 1];
9578 8 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
9579 8 : papszExtraDimNames[i]);
9580 : papszExtraDimValues =
9581 8 : NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
9582 8 : const size_t nDimSize = atol(papszExtraDimValues[0]);
9583 8 : CSLDestroy(papszExtraDimValues);
9584 8 : nDimSizeTot *= nDimSize;
9585 : }
9586 5 : if (nDimSizeTot == (size_t)nBands)
9587 : {
9588 5 : nDim = 2 + CSLCount(papszExtraDimNames);
9589 : }
9590 : else
9591 : {
9592 : // if nBands != #bands computed raise a warning
9593 : // just issue a debug message, because it was probably intentional
9594 0 : CPLDebug("GDAL_netCDF",
9595 : "Warning: Number of bands (%d) is not compatible with "
9596 : "dimensions "
9597 : "(total=%ld names=%s)",
9598 : nBands, (long)nDimSizeTot,
9599 0 : poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
9600 0 : CSLDestroy(papszExtraDimNames);
9601 0 : papszExtraDimNames = nullptr;
9602 : }
9603 : }
9604 :
9605 55 : int *panDimIds = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
9606 55 : int *panBandDimPos = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
9607 :
9608 : nc_type nVarType;
9609 55 : int *panBandZLev = nullptr;
9610 55 : int *panDimVarIds = nullptr;
9611 :
9612 55 : if (nDim > 2)
9613 : {
9614 5 : panBandZLev = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
9615 5 : panDimVarIds = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
9616 :
9617 : // Define all dims.
9618 13 : for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--)
9619 : {
9620 8 : poDS->papszDimName.AddString(papszExtraDimNames[i]);
9621 : char szTemp[NC_MAX_NAME + 32 + 1];
9622 8 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
9623 8 : papszExtraDimNames[i]);
9624 : papszExtraDimValues =
9625 8 : NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
9626 8 : const int nDimSize = papszExtraDimValues && papszExtraDimValues[0]
9627 16 : ? atoi(papszExtraDimValues[0])
9628 : : 0;
9629 : // nc_type is an enum in netcdf-3, needs casting.
9630 8 : nVarType = static_cast<nc_type>(papszExtraDimValues &&
9631 8 : papszExtraDimValues[0] &&
9632 8 : papszExtraDimValues[1]
9633 8 : ? atol(papszExtraDimValues[1])
9634 : : 0);
9635 8 : CSLDestroy(papszExtraDimValues);
9636 8 : panBandZLev[i] = nDimSize;
9637 8 : panBandDimPos[i + 2] = i; // Save Position of ZDim.
9638 :
9639 : // Define dim.
9640 16 : int status = nc_def_dim(poDS->cdfid, papszExtraDimNames[i],
9641 8 : nDimSize, &(panDimIds[i]));
9642 8 : NCDF_ERR(status);
9643 :
9644 : // Define dim var.
9645 8 : int anDim[1] = {panDimIds[i]};
9646 16 : status = nc_def_var(poDS->cdfid, papszExtraDimNames[i], nVarType, 1,
9647 8 : anDim, &(panDimVarIds[i]));
9648 8 : NCDF_ERR(status);
9649 :
9650 : // Add dim metadata, using global var# items.
9651 8 : snprintf(szTemp, sizeof(szTemp), "%s#", papszExtraDimNames[i]);
9652 8 : CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid,
9653 8 : panDimVarIds[i], szTemp);
9654 : }
9655 : }
9656 :
9657 : // Copy GeoTransform and Projection.
9658 :
9659 : // Copy geolocation info.
9660 55 : char **papszGeolocationInfo = poSrcDS->GetMetadata("GEOLOCATION");
9661 55 : if (papszGeolocationInfo != nullptr)
9662 5 : poDS->GDALPamDataset::SetMetadata(papszGeolocationInfo, "GEOLOCATION");
9663 :
9664 : // Copy geotransform.
9665 55 : bool bGotGeoTransform = false;
9666 : double adfGeoTransform[6];
9667 55 : CPLErr eErr = poSrcDS->GetGeoTransform(adfGeoTransform);
9668 55 : if (eErr == CE_None)
9669 : {
9670 37 : poDS->SetGeoTransform(adfGeoTransform);
9671 : // Disable AddProjectionVars() from being called.
9672 37 : bGotGeoTransform = true;
9673 37 : poDS->m_bHasGeoTransform = false;
9674 : }
9675 :
9676 : // Copy projection.
9677 55 : void *pScaledProgress = nullptr;
9678 55 : if (bGotGeoTransform || (pszWKT && pszWKT[0] != 0))
9679 : {
9680 38 : poDS->SetProjection(pszWKT ? pszWKT : "");
9681 :
9682 : // Now we can call AddProjectionVars() directly.
9683 38 : poDS->m_bHasGeoTransform = bGotGeoTransform;
9684 38 : poDS->AddProjectionVars(true, nullptr, nullptr);
9685 : pScaledProgress =
9686 38 : GDALCreateScaledProgress(0.1, 0.25, pfnProgress, pProgressData);
9687 38 : poDS->AddProjectionVars(false, GDALScaledProgress, pScaledProgress);
9688 38 : GDALDestroyScaledProgress(pScaledProgress);
9689 : }
9690 : else
9691 : {
9692 17 : poDS->bBottomUp =
9693 17 : CPL_TO_BOOL(CSLFetchBoolean(papszOptions, "WRITE_BOTTOMUP", TRUE));
9694 17 : if (papszGeolocationInfo)
9695 : {
9696 4 : poDS->AddProjectionVars(true, nullptr, nullptr);
9697 4 : poDS->AddProjectionVars(false, nullptr, nullptr);
9698 : }
9699 : }
9700 :
9701 : // Save X,Y dim positions.
9702 55 : panDimIds[nDim - 1] = poDS->nXDimID;
9703 55 : panBandDimPos[0] = nDim - 1;
9704 55 : panDimIds[nDim - 2] = poDS->nYDimID;
9705 55 : panBandDimPos[1] = nDim - 2;
9706 :
9707 : // Write extra dim values - after projection for optimization.
9708 55 : if (nDim > 2)
9709 : {
9710 : // Make sure we are in data mode.
9711 5 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
9712 13 : for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--)
9713 : {
9714 : char szTemp[NC_MAX_NAME + 32 + 1];
9715 8 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_VALUES",
9716 8 : papszExtraDimNames[i]);
9717 8 : if (poSrcDS->GetMetadataItem(szTemp) != nullptr)
9718 : {
9719 8 : NCDFPut1DVar(poDS->cdfid, panDimVarIds[i],
9720 8 : poSrcDS->GetMetadataItem(szTemp));
9721 : }
9722 : }
9723 : }
9724 :
9725 55 : pfnProgress(0.25, nullptr, pProgressData);
9726 :
9727 : // Define Bands.
9728 55 : netCDFRasterBand *poBand = nullptr;
9729 55 : int nBandID = -1;
9730 :
9731 143 : for (int iBand = 1; iBand <= nBands; iBand++)
9732 : {
9733 88 : CPLDebug("GDAL_netCDF", "creating band # %d/%d nDim = %d", iBand,
9734 : nBands, nDim);
9735 :
9736 88 : poSrcBand = poSrcDS->GetRasterBand(iBand);
9737 88 : eDT = poSrcBand->GetRasterDataType();
9738 :
9739 : // Get var name from NETCDF_VARNAME.
9740 : const char *pszNETCDF_VARNAME =
9741 88 : poSrcBand->GetMetadataItem("NETCDF_VARNAME");
9742 : char szBandName[NC_MAX_NAME + 1];
9743 88 : if (!aosBandNames.empty())
9744 : {
9745 2 : snprintf(szBandName, sizeof(szBandName), "%s",
9746 : aosBandNames[iBand - 1]);
9747 : }
9748 86 : else if (pszNETCDF_VARNAME)
9749 : {
9750 32 : if (nBands > 1 && papszExtraDimNames == nullptr)
9751 0 : snprintf(szBandName, sizeof(szBandName), "%s%d",
9752 : pszNETCDF_VARNAME, iBand);
9753 : else
9754 32 : snprintf(szBandName, sizeof(szBandName), "%s",
9755 : pszNETCDF_VARNAME);
9756 : }
9757 : else
9758 : {
9759 54 : szBandName[0] = '\0';
9760 : }
9761 :
9762 : // Get long_name from <var>#long_name.
9763 88 : const char *pszLongName = "";
9764 88 : if (pszNETCDF_VARNAME)
9765 : {
9766 : pszLongName =
9767 64 : poSrcDS->GetMetadataItem(std::string(pszNETCDF_VARNAME)
9768 32 : .append("#")
9769 32 : .append(CF_LNG_NAME)
9770 32 : .c_str());
9771 32 : if (!pszLongName)
9772 25 : pszLongName = "";
9773 : }
9774 :
9775 88 : constexpr bool bSignedData = false;
9776 :
9777 88 : if (nDim > 2)
9778 27 : poBand = new netCDFRasterBand(
9779 27 : netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
9780 : bSignedData, szBandName, pszLongName, nBandID, nDim, iBand - 1,
9781 27 : panBandZLev, panBandDimPos, panDimIds);
9782 : else
9783 61 : poBand = new netCDFRasterBand(
9784 61 : netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
9785 61 : bSignedData, szBandName, pszLongName);
9786 :
9787 88 : poDS->SetBand(iBand, poBand);
9788 :
9789 : // Set nodata value, if any.
9790 88 : GDALCopyNoDataValue(poBand, poSrcBand);
9791 :
9792 : // Copy Metadata for band.
9793 88 : CopyMetadata(nullptr, poSrcDS->GetRasterBand(iBand), poBand,
9794 : poDS->cdfid, poBand->nZId);
9795 :
9796 : // If more than 2D pass the first band's netcdf var ID to subsequent
9797 : // bands.
9798 88 : if (nDim > 2)
9799 27 : nBandID = poBand->nZId;
9800 : }
9801 :
9802 : // Write projection variable to band variable.
9803 55 : poDS->AddGridMappingRef();
9804 :
9805 55 : pfnProgress(0.5, nullptr, pProgressData);
9806 :
9807 : // Write bands.
9808 :
9809 : // Make sure we are in data mode.
9810 55 : poDS->SetDefineMode(false);
9811 :
9812 55 : double dfTemp = 0.5;
9813 :
9814 55 : eErr = CE_None;
9815 :
9816 143 : for (int iBand = 1; iBand <= nBands && eErr == CE_None; iBand++)
9817 : {
9818 88 : const double dfTemp2 = dfTemp + 0.4 / nBands;
9819 88 : pScaledProgress = GDALCreateScaledProgress(dfTemp, dfTemp2, pfnProgress,
9820 : pProgressData);
9821 88 : dfTemp = dfTemp2;
9822 :
9823 88 : CPLDebug("GDAL_netCDF", "copying band data # %d/%d ", iBand, nBands);
9824 :
9825 88 : poSrcBand = poSrcDS->GetRasterBand(iBand);
9826 88 : eDT = poSrcBand->GetRasterDataType();
9827 :
9828 88 : GDALRasterBand *poDstBand = poDS->GetRasterBand(iBand);
9829 :
9830 : // Copy band data.
9831 88 : if (eDT == GDT_Byte)
9832 : {
9833 48 : CPLDebug("GDAL_netCDF", "GByte Band#%d", iBand);
9834 48 : eErr = NCDFCopyBand<GByte>(poSrcBand, poDstBand, nXSize, nYSize,
9835 : GDALScaledProgress, pScaledProgress);
9836 : }
9837 40 : else if (eDT == GDT_Int8)
9838 : {
9839 1 : CPLDebug("GDAL_netCDF", "GInt8 Band#%d", iBand);
9840 1 : eErr = NCDFCopyBand<GInt8>(poSrcBand, poDstBand, nXSize, nYSize,
9841 : GDALScaledProgress, pScaledProgress);
9842 : }
9843 39 : else if (eDT == GDT_UInt16)
9844 : {
9845 2 : CPLDebug("GDAL_netCDF", "GUInt16 Band#%d", iBand);
9846 2 : eErr = NCDFCopyBand<GInt16>(poSrcBand, poDstBand, nXSize, nYSize,
9847 : GDALScaledProgress, pScaledProgress);
9848 : }
9849 37 : else if (eDT == GDT_Int16)
9850 : {
9851 5 : CPLDebug("GDAL_netCDF", "GInt16 Band#%d", iBand);
9852 5 : eErr = NCDFCopyBand<GUInt16>(poSrcBand, poDstBand, nXSize, nYSize,
9853 : GDALScaledProgress, pScaledProgress);
9854 : }
9855 32 : else if (eDT == GDT_UInt32)
9856 : {
9857 2 : CPLDebug("GDAL_netCDF", "GUInt32 Band#%d", iBand);
9858 2 : eErr = NCDFCopyBand<GUInt32>(poSrcBand, poDstBand, nXSize, nYSize,
9859 : GDALScaledProgress, pScaledProgress);
9860 : }
9861 30 : else if (eDT == GDT_Int32)
9862 : {
9863 18 : CPLDebug("GDAL_netCDF", "GInt32 Band#%d", iBand);
9864 18 : eErr = NCDFCopyBand<GInt32>(poSrcBand, poDstBand, nXSize, nYSize,
9865 : GDALScaledProgress, pScaledProgress);
9866 : }
9867 12 : else if (eDT == GDT_UInt64)
9868 : {
9869 2 : CPLDebug("GDAL_netCDF", "GUInt64 Band#%d", iBand);
9870 2 : eErr = NCDFCopyBand<std::uint64_t>(poSrcBand, poDstBand, nXSize,
9871 : nYSize, GDALScaledProgress,
9872 : pScaledProgress);
9873 : }
9874 10 : else if (eDT == GDT_Int64)
9875 : {
9876 2 : CPLDebug("GDAL_netCDF", "GInt64 Band#%d", iBand);
9877 : eErr =
9878 2 : NCDFCopyBand<std::int64_t>(poSrcBand, poDstBand, nXSize, nYSize,
9879 : GDALScaledProgress, pScaledProgress);
9880 : }
9881 8 : else if (eDT == GDT_Float32)
9882 : {
9883 6 : CPLDebug("GDAL_netCDF", "float Band#%d", iBand);
9884 6 : eErr = NCDFCopyBand<float>(poSrcBand, poDstBand, nXSize, nYSize,
9885 : GDALScaledProgress, pScaledProgress);
9886 : }
9887 2 : else if (eDT == GDT_Float64)
9888 : {
9889 2 : CPLDebug("GDAL_netCDF", "double Band#%d", iBand);
9890 2 : eErr = NCDFCopyBand<double>(poSrcBand, poDstBand, nXSize, nYSize,
9891 : GDALScaledProgress, pScaledProgress);
9892 : }
9893 : else
9894 : {
9895 0 : CPLError(CE_Failure, CPLE_NotSupported,
9896 : "The NetCDF driver does not support GDAL data type %d",
9897 : eDT);
9898 : }
9899 :
9900 88 : GDALDestroyScaledProgress(pScaledProgress);
9901 : }
9902 :
9903 55 : delete (poDS);
9904 :
9905 55 : CPLFree(panDimIds);
9906 55 : CPLFree(panBandDimPos);
9907 55 : CPLFree(panBandZLev);
9908 55 : CPLFree(panDimVarIds);
9909 55 : if (papszExtraDimNames)
9910 5 : CSLDestroy(papszExtraDimNames);
9911 :
9912 55 : if (eErr != CE_None)
9913 0 : return nullptr;
9914 :
9915 55 : pfnProgress(0.95, nullptr, pProgressData);
9916 :
9917 : // Re-open dataset so we can return it.
9918 110 : CPLStringList aosOpenOptions;
9919 55 : aosOpenOptions.AddString("VARIABLES_AS_BANDS=YES");
9920 55 : GDALOpenInfo oOpenInfo(pszFilename, GA_Update);
9921 55 : oOpenInfo.nOpenFlags = GDAL_OF_RASTER | GDAL_OF_UPDATE;
9922 55 : oOpenInfo.papszOpenOptions = aosOpenOptions.List();
9923 55 : auto poRetDS = Open(&oOpenInfo);
9924 :
9925 : // PAM cloning is disabled. See bug #4244.
9926 : // if( poDS )
9927 : // poDS->CloneInfo(poSrcDS, GCIF_PAM_DEFAULT);
9928 :
9929 55 : pfnProgress(1.0, nullptr, pProgressData);
9930 :
9931 55 : return poRetDS;
9932 : }
9933 :
9934 : // Note: some logic depends on bIsProjected and bIsGeoGraphic.
9935 : // May not be known when Create() is called, see AddProjectionVars().
9936 251 : void netCDFDataset::ProcessCreationOptions()
9937 : {
9938 : const char *pszConfig =
9939 251 : CSLFetchNameValue(papszCreationOptions, "CONFIG_FILE");
9940 251 : if (pszConfig != nullptr)
9941 : {
9942 4 : if (oWriterConfig.Parse(pszConfig))
9943 : {
9944 : // Override dataset creation options from the config file
9945 2 : std::map<CPLString, CPLString>::iterator oIter;
9946 3 : for (oIter = oWriterConfig.m_oDatasetCreationOptions.begin();
9947 3 : oIter != oWriterConfig.m_oDatasetCreationOptions.end();
9948 1 : ++oIter)
9949 : {
9950 2 : papszCreationOptions = CSLSetNameValue(
9951 2 : papszCreationOptions, oIter->first, oIter->second);
9952 : }
9953 : }
9954 : }
9955 :
9956 : // File format.
9957 251 : eFormat = NCDF_FORMAT_NC;
9958 251 : const char *pszValue = CSLFetchNameValue(papszCreationOptions, "FORMAT");
9959 251 : if (pszValue != nullptr)
9960 : {
9961 93 : if (EQUAL(pszValue, "NC"))
9962 : {
9963 3 : eFormat = NCDF_FORMAT_NC;
9964 : }
9965 : #ifdef NETCDF_HAS_NC2
9966 90 : else if (EQUAL(pszValue, "NC2"))
9967 : {
9968 1 : eFormat = NCDF_FORMAT_NC2;
9969 : }
9970 : #endif
9971 89 : else if (EQUAL(pszValue, "NC4"))
9972 : {
9973 85 : eFormat = NCDF_FORMAT_NC4;
9974 : }
9975 4 : else if (EQUAL(pszValue, "NC4C"))
9976 : {
9977 4 : eFormat = NCDF_FORMAT_NC4C;
9978 : }
9979 : else
9980 : {
9981 0 : CPLError(CE_Failure, CPLE_NotSupported,
9982 : "FORMAT=%s in not supported, using the default NC format.",
9983 : pszValue);
9984 : }
9985 : }
9986 :
9987 : // COMPRESS option.
9988 251 : pszValue = CSLFetchNameValue(papszCreationOptions, "COMPRESS");
9989 251 : if (pszValue != nullptr)
9990 : {
9991 2 : if (EQUAL(pszValue, "NONE"))
9992 : {
9993 1 : eCompress = NCDF_COMPRESS_NONE;
9994 : }
9995 1 : else if (EQUAL(pszValue, "DEFLATE"))
9996 : {
9997 1 : eCompress = NCDF_COMPRESS_DEFLATE;
9998 1 : if (!((eFormat == NCDF_FORMAT_NC4) ||
9999 1 : (eFormat == NCDF_FORMAT_NC4C)))
10000 : {
10001 0 : CPLError(CE_Warning, CPLE_IllegalArg,
10002 : "NOTICE: Format set to NC4C because compression is "
10003 : "set to DEFLATE.");
10004 0 : eFormat = NCDF_FORMAT_NC4C;
10005 : }
10006 : }
10007 : else
10008 : {
10009 0 : CPLError(CE_Failure, CPLE_NotSupported,
10010 : "COMPRESS=%s is not supported.", pszValue);
10011 : }
10012 : }
10013 :
10014 : // ZLEVEL option.
10015 251 : pszValue = CSLFetchNameValue(papszCreationOptions, "ZLEVEL");
10016 251 : if (pszValue != nullptr)
10017 : {
10018 1 : nZLevel = atoi(pszValue);
10019 1 : if (!(nZLevel >= 1 && nZLevel <= 9))
10020 : {
10021 0 : CPLError(CE_Warning, CPLE_IllegalArg,
10022 : "ZLEVEL=%s value not recognised, ignoring.", pszValue);
10023 0 : nZLevel = NCDF_DEFLATE_LEVEL;
10024 : }
10025 : }
10026 :
10027 : // CHUNKING option.
10028 251 : bChunking =
10029 251 : CPL_TO_BOOL(CSLFetchBoolean(papszCreationOptions, "CHUNKING", TRUE));
10030 :
10031 : // MULTIPLE_LAYERS option.
10032 : const char *pszMultipleLayerBehavior =
10033 251 : CSLFetchNameValueDef(papszCreationOptions, "MULTIPLE_LAYERS", "NO");
10034 502 : const char *pszGeometryEnc = CSLFetchNameValueDef(
10035 251 : papszCreationOptions, "GEOMETRY_ENCODING", "CF_1.8");
10036 251 : if (EQUAL(pszMultipleLayerBehavior, "NO") ||
10037 3 : EQUAL(pszGeometryEnc, "CF_1.8"))
10038 : {
10039 248 : eMultipleLayerBehavior = SINGLE_LAYER;
10040 : }
10041 3 : else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_FILES"))
10042 : {
10043 2 : eMultipleLayerBehavior = SEPARATE_FILES;
10044 : }
10045 1 : else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_GROUPS"))
10046 : {
10047 1 : if (eFormat == NCDF_FORMAT_NC4)
10048 : {
10049 1 : eMultipleLayerBehavior = SEPARATE_GROUPS;
10050 : }
10051 : else
10052 : {
10053 0 : CPLError(CE_Warning, CPLE_IllegalArg,
10054 : "MULTIPLE_LAYERS=%s is recognised only with FORMAT=NC4",
10055 : pszMultipleLayerBehavior);
10056 : }
10057 : }
10058 : else
10059 : {
10060 0 : CPLError(CE_Warning, CPLE_IllegalArg,
10061 : "MULTIPLE_LAYERS=%s not recognised", pszMultipleLayerBehavior);
10062 : }
10063 :
10064 : // Set nCreateMode based on eFormat.
10065 251 : switch (eFormat)
10066 : {
10067 : #ifdef NETCDF_HAS_NC2
10068 1 : case NCDF_FORMAT_NC2:
10069 1 : nCreateMode = NC_CLOBBER | NC_64BIT_OFFSET;
10070 1 : break;
10071 : #endif
10072 85 : case NCDF_FORMAT_NC4:
10073 85 : nCreateMode = NC_CLOBBER | NC_NETCDF4;
10074 85 : break;
10075 4 : case NCDF_FORMAT_NC4C:
10076 4 : nCreateMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
10077 4 : break;
10078 161 : case NCDF_FORMAT_NC:
10079 : default:
10080 161 : nCreateMode = NC_CLOBBER;
10081 161 : break;
10082 : }
10083 :
10084 251 : CPLDebug("GDAL_netCDF", "file options: format=%d compress=%d zlevel=%d",
10085 251 : eFormat, eCompress, nZLevel);
10086 251 : }
10087 :
10088 271 : int netCDFDataset::DefVarDeflate(int nVarId, bool bChunkingArg)
10089 : {
10090 271 : if (eCompress == NCDF_COMPRESS_DEFLATE)
10091 : {
10092 : // Must set chunk size to avoid huge performance hit (set
10093 : // bChunkingArg=TRUE)
10094 : // perhaps another solution it to change the chunk cache?
10095 : // http://www.unidata.ucar.edu/software/netcdf/docs/netcdf.html#Chunk-Cache
10096 : // TODO: make sure this is okay.
10097 1 : CPLDebug("GDAL_netCDF", "DefVarDeflate(%d, %d) nZlevel=%d", nVarId,
10098 : static_cast<int>(bChunkingArg), nZLevel);
10099 :
10100 1 : int status = nc_def_var_deflate(cdfid, nVarId, 1, 1, nZLevel);
10101 1 : NCDF_ERR(status);
10102 :
10103 1 : if (status == NC_NOERR && bChunkingArg && bChunking)
10104 : {
10105 : // set chunking to be 1 for all dims, except X dim
10106 : // size_t chunksize[] = { 1, (size_t)nRasterXSize };
10107 : size_t chunksize[MAX_NC_DIMS];
10108 : int nd;
10109 1 : nc_inq_varndims(cdfid, nVarId, &nd);
10110 1 : chunksize[0] = (size_t)1;
10111 1 : chunksize[1] = (size_t)1;
10112 1 : for (int i = 2; i < nd; i++)
10113 0 : chunksize[i] = (size_t)1;
10114 1 : chunksize[nd - 1] = (size_t)nRasterXSize;
10115 :
10116 : // Config options just for testing purposes
10117 : const char *pszBlockXSize =
10118 1 : CPLGetConfigOption("BLOCKXSIZE", nullptr);
10119 1 : if (pszBlockXSize)
10120 0 : chunksize[nd - 1] = (size_t)atoi(pszBlockXSize);
10121 :
10122 : const char *pszBlockYSize =
10123 1 : CPLGetConfigOption("BLOCKYSIZE", nullptr);
10124 1 : if (nd >= 2 && pszBlockYSize)
10125 0 : chunksize[nd - 2] = (size_t)atoi(pszBlockYSize);
10126 :
10127 1 : CPLDebug("GDAL_netCDF",
10128 : "DefVarDeflate() chunksize={%ld, %ld} chunkX=%ld nd=%d",
10129 1 : (long)chunksize[0], (long)chunksize[1],
10130 1 : (long)chunksize[nd - 1], nd);
10131 : #ifdef NCDF_DEBUG
10132 : for (int i = 0; i < nd; i++)
10133 : CPLDebug("GDAL_netCDF", "DefVarDeflate() chunk[%d]=%ld", i,
10134 : chunksize[i]);
10135 : #endif
10136 :
10137 1 : status = nc_def_var_chunking(cdfid, nVarId, NC_CHUNKED, chunksize);
10138 1 : NCDF_ERR(status);
10139 : }
10140 : else
10141 : {
10142 0 : CPLDebug("GDAL_netCDF", "chunksize not set");
10143 : }
10144 1 : return status;
10145 : }
10146 270 : return NC_NOERR;
10147 : }
10148 :
10149 : /************************************************************************/
10150 : /* NCDFUnloadDriver() */
10151 : /************************************************************************/
10152 :
10153 8 : static void NCDFUnloadDriver(CPL_UNUSED GDALDriver *poDriver)
10154 : {
10155 8 : if (hNCMutex != nullptr)
10156 4 : CPLDestroyMutex(hNCMutex);
10157 8 : hNCMutex = nullptr;
10158 8 : }
10159 :
10160 : /************************************************************************/
10161 : /* GDALRegister_netCDF() */
10162 : /************************************************************************/
10163 :
10164 : class GDALnetCDFDriver final : public GDALDriver
10165 : {
10166 : public:
10167 14 : GDALnetCDFDriver() = default;
10168 :
10169 1103 : const char *GetMetadataItem(const char *pszName,
10170 : const char *pszDomain) override
10171 : {
10172 1103 : if (EQUAL(pszName, GDAL_DCAP_VIRTUALIO))
10173 : {
10174 13 : InitializeDCAPVirtualIO();
10175 : }
10176 1103 : return GDALDriver::GetMetadataItem(pszName, pszDomain);
10177 : }
10178 :
10179 79 : char **GetMetadata(const char *pszDomain) override
10180 : {
10181 79 : InitializeDCAPVirtualIO();
10182 79 : return GDALDriver::GetMetadata(pszDomain);
10183 : }
10184 :
10185 : private:
10186 : bool m_bInitialized = false;
10187 :
10188 92 : void InitializeDCAPVirtualIO()
10189 : {
10190 92 : if (!m_bInitialized)
10191 : {
10192 11 : m_bInitialized = true;
10193 :
10194 : #ifdef ENABLE_UFFD
10195 11 : if (CPLIsUserFaultMappingSupported())
10196 : {
10197 11 : SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
10198 : }
10199 : #endif
10200 : }
10201 92 : }
10202 : };
10203 :
10204 14 : void GDALRegister_netCDF()
10205 :
10206 : {
10207 14 : if (!GDAL_CHECK_VERSION("netCDF driver"))
10208 0 : return;
10209 :
10210 14 : if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
10211 0 : return;
10212 :
10213 14 : GDALDriver *poDriver = new GDALnetCDFDriver();
10214 14 : netCDFDriverSetCommonMetadata(poDriver);
10215 :
10216 14 : poDriver->SetMetadataItem("NETCDF_CONVENTIONS",
10217 14 : GDAL_DEFAULT_NCDF_CONVENTIONS);
10218 14 : poDriver->SetMetadataItem("NETCDF_VERSION", nc_inq_libvers());
10219 :
10220 : // Set pfns and register driver.
10221 14 : poDriver->pfnOpen = netCDFDataset::Open;
10222 14 : poDriver->pfnCreateCopy = netCDFDataset::CreateCopy;
10223 14 : poDriver->pfnCreate = netCDFDataset::Create;
10224 14 : poDriver->pfnCreateMultiDimensional = netCDFDataset::CreateMultiDimensional;
10225 14 : poDriver->pfnUnloadDriver = NCDFUnloadDriver;
10226 :
10227 14 : GetGDALDriverManager()->RegisterDriver(poDriver);
10228 : }
10229 :
10230 : /************************************************************************/
10231 : /* New functions */
10232 : /************************************************************************/
10233 :
10234 : /* Test for GDAL version string >= target */
10235 236 : static bool NCDFIsGDALVersionGTE(const char *pszVersion, int nTarget)
10236 : {
10237 :
10238 : // Valid strings are "GDAL 1.9dev, released 2011/01/18" and "GDAL 1.8.1 ".
10239 236 : if (pszVersion == nullptr || EQUAL(pszVersion, ""))
10240 0 : return false;
10241 236 : else if (!STARTS_WITH_CI(pszVersion, "GDAL "))
10242 0 : return false;
10243 : // 2.0dev of 2011/12/29 has been later renamed as 1.10dev.
10244 236 : else if (EQUAL("GDAL 2.0dev, released 2011/12/29", pszVersion))
10245 0 : return nTarget <= GDAL_COMPUTE_VERSION(1, 10, 0);
10246 236 : else if (STARTS_WITH_CI(pszVersion, "GDAL 1.9dev"))
10247 2 : return nTarget <= 1900;
10248 234 : else if (STARTS_WITH_CI(pszVersion, "GDAL 1.8dev"))
10249 0 : return nTarget <= 1800;
10250 :
10251 234 : char **papszTokens = CSLTokenizeString2(pszVersion + 5, ".", 0);
10252 :
10253 234 : int nVersions[] = {0, 0, 0, 0};
10254 936 : for (int iToken = 0; papszTokens && iToken < 4 && papszTokens[iToken];
10255 : iToken++)
10256 : {
10257 702 : nVersions[iToken] = atoi(papszTokens[iToken]);
10258 702 : if (nVersions[iToken] < 0)
10259 0 : nVersions[iToken] = 0;
10260 702 : else if (nVersions[iToken] > 99)
10261 0 : nVersions[iToken] = 99;
10262 : }
10263 :
10264 234 : int nVersion = 0;
10265 234 : if (nVersions[0] > 1 || nVersions[1] >= 10)
10266 234 : nVersion =
10267 234 : GDAL_COMPUTE_VERSION(nVersions[0], nVersions[1], nVersions[2]);
10268 : else
10269 0 : nVersion = nVersions[0] * 1000 + nVersions[1] * 100 +
10270 0 : nVersions[2] * 10 + nVersions[3];
10271 :
10272 234 : CSLDestroy(papszTokens);
10273 234 : return nTarget <= nVersion;
10274 : }
10275 :
10276 : // Add Conventions, GDAL version and history.
10277 164 : static void NCDFAddGDALHistory(int fpImage, const char *pszFilename,
10278 : bool bWriteGDALVersion, bool bWriteGDALHistory,
10279 : const char *pszOldHist,
10280 : const char *pszFunctionName,
10281 : const char *pszCFVersion)
10282 : {
10283 164 : if (pszCFVersion == nullptr)
10284 : {
10285 39 : pszCFVersion = GDAL_DEFAULT_NCDF_CONVENTIONS;
10286 : }
10287 164 : int status = nc_put_att_text(fpImage, NC_GLOBAL, "Conventions",
10288 : strlen(pszCFVersion), pszCFVersion);
10289 164 : NCDF_ERR(status);
10290 :
10291 164 : if (bWriteGDALVersion)
10292 : {
10293 162 : const char *pszNCDF_GDAL = GDALVersionInfo("--version");
10294 162 : status = nc_put_att_text(fpImage, NC_GLOBAL, "GDAL",
10295 : strlen(pszNCDF_GDAL), pszNCDF_GDAL);
10296 162 : NCDF_ERR(status);
10297 : }
10298 :
10299 164 : if (bWriteGDALHistory)
10300 : {
10301 : // Add history.
10302 324 : CPLString osTmp;
10303 : #ifdef GDAL_SET_CMD_LINE_DEFINED_TMP
10304 : if (!EQUAL(GDALGetCmdLine(), ""))
10305 : osTmp = GDALGetCmdLine();
10306 : else
10307 : osTmp =
10308 : CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
10309 : #else
10310 162 : osTmp = CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
10311 : #endif
10312 :
10313 162 : NCDFAddHistory(fpImage, osTmp.c_str(), pszOldHist);
10314 : }
10315 2 : else if (pszOldHist != nullptr)
10316 : {
10317 0 : status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
10318 : strlen(pszOldHist), pszOldHist);
10319 0 : NCDF_ERR(status);
10320 : }
10321 164 : }
10322 :
10323 : // Code taken from cdo and libcdi, used for writing the history attribute.
10324 :
10325 : // void cdoDefHistory(int fileID, char *histstring)
10326 162 : static void NCDFAddHistory(int fpImage, const char *pszAddHist,
10327 : const char *pszOldHist)
10328 : {
10329 : // Check pszOldHist - as if there was no previous history, it will be
10330 : // a null pointer - if so set as empty.
10331 162 : if (nullptr == pszOldHist)
10332 : {
10333 50 : pszOldHist = "";
10334 : }
10335 :
10336 : char strtime[32];
10337 162 : strtime[0] = '\0';
10338 :
10339 162 : time_t tp = time(nullptr);
10340 162 : if (tp != -1)
10341 : {
10342 : struct tm ltime;
10343 162 : VSILocalTime(&tp, <ime);
10344 162 : (void)strftime(strtime, sizeof(strtime),
10345 : "%a %b %d %H:%M:%S %Y: ", <ime);
10346 : }
10347 :
10348 : // status = nc_get_att_text(fpImage, NC_GLOBAL,
10349 : // "history", pszOldHist);
10350 : // printf("status: %d pszOldHist: [%s]\n",status,pszOldHist);
10351 :
10352 162 : size_t nNewHistSize =
10353 162 : strlen(pszOldHist) + strlen(strtime) + strlen(pszAddHist) + 1 + 1;
10354 : char *pszNewHist =
10355 162 : static_cast<char *>(CPLMalloc(nNewHistSize * sizeof(char)));
10356 :
10357 162 : strcpy(pszNewHist, strtime);
10358 162 : strcat(pszNewHist, pszAddHist);
10359 :
10360 : // int disableHistory = FALSE;
10361 : // if( !disableHistory )
10362 : {
10363 162 : if (!EQUAL(pszOldHist, ""))
10364 3 : strcat(pszNewHist, "\n");
10365 162 : strcat(pszNewHist, pszOldHist);
10366 : }
10367 :
10368 162 : const int status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
10369 : strlen(pszNewHist), pszNewHist);
10370 162 : NCDF_ERR(status);
10371 :
10372 162 : CPLFree(pszNewHist);
10373 162 : }
10374 :
10375 5924 : static CPLErr NCDFSafeStrcat(char **ppszDest, const char *pszSrc,
10376 : size_t *nDestSize)
10377 : {
10378 : /* Reallocate the data string until the content fits */
10379 5924 : while (*nDestSize < (strlen(*ppszDest) + strlen(pszSrc) + 1))
10380 : {
10381 394 : (*nDestSize) *= 2;
10382 394 : *ppszDest = static_cast<char *>(
10383 394 : CPLRealloc(reinterpret_cast<void *>(*ppszDest), *nDestSize));
10384 : #ifdef NCDF_DEBUG
10385 : CPLDebug("GDAL_netCDF", "NCDFSafeStrcat() resized str from %ld to %ld",
10386 : (*nDestSize) / 2, *nDestSize);
10387 : #endif
10388 : }
10389 5530 : strcat(*ppszDest, pszSrc);
10390 :
10391 5530 : return CE_None;
10392 : }
10393 :
10394 : /* helper function for NCDFGetAttr() */
10395 : /* if pdfValue != nullptr, sets *pdfValue to first value returned */
10396 : /* if ppszValue != nullptr, sets *ppszValue with all attribute values */
10397 : /* *ppszValue is the responsibility of the caller and must be freed */
10398 62318 : static CPLErr NCDFGetAttr1(int nCdfId, int nVarId, const char *pszAttrName,
10399 : double *pdfValue, char **ppszValue)
10400 : {
10401 62318 : nc_type nAttrType = NC_NAT;
10402 62318 : size_t nAttrLen = 0;
10403 :
10404 62318 : if (ppszValue)
10405 61172 : *ppszValue = nullptr;
10406 :
10407 62318 : int status = nc_inq_att(nCdfId, nVarId, pszAttrName, &nAttrType, &nAttrLen);
10408 62318 : if (status != NC_NOERR)
10409 33641 : return CE_Failure;
10410 :
10411 : #ifdef NCDF_DEBUG
10412 : CPLDebug("GDAL_netCDF", "NCDFGetAttr1(%s) len=%ld type=%d", pszAttrName,
10413 : nAttrLen, nAttrType);
10414 : #endif
10415 28677 : if (nAttrLen == 0 && nAttrType != NC_CHAR)
10416 1 : return CE_Failure;
10417 :
10418 : /* Allocate guaranteed minimum size (use 10 or 20 if not a string) */
10419 28676 : size_t nAttrValueSize = nAttrLen + 1;
10420 28676 : if (nAttrType != NC_CHAR && nAttrValueSize < 10)
10421 3089 : nAttrValueSize = 10;
10422 28676 : if (nAttrType == NC_DOUBLE && nAttrValueSize < 20)
10423 1509 : nAttrValueSize = 20;
10424 28676 : if (nAttrType == NC_INT64 && nAttrValueSize < 20)
10425 20 : nAttrValueSize = 22;
10426 : char *pszAttrValue =
10427 28676 : static_cast<char *>(CPLCalloc(nAttrValueSize, sizeof(char)));
10428 28676 : *pszAttrValue = '\0';
10429 :
10430 28676 : if (nAttrLen > 1 && nAttrType != NC_CHAR)
10431 560 : NCDFSafeStrcat(&pszAttrValue, "{", &nAttrValueSize);
10432 :
10433 28676 : double dfValue = 0.0;
10434 28676 : size_t m = 0;
10435 : char szTemp[256];
10436 28676 : bool bSetDoubleFromStr = false;
10437 :
10438 28676 : switch (nAttrType)
10439 : {
10440 25585 : case NC_CHAR:
10441 25585 : CPL_IGNORE_RET_VAL(
10442 25585 : nc_get_att_text(nCdfId, nVarId, pszAttrName, pszAttrValue));
10443 25585 : pszAttrValue[nAttrLen] = '\0';
10444 25585 : bSetDoubleFromStr = true;
10445 25585 : dfValue = 0.0;
10446 25585 : break;
10447 90 : case NC_BYTE:
10448 : {
10449 : signed char *pscTemp = static_cast<signed char *>(
10450 90 : CPLCalloc(nAttrLen, sizeof(signed char)));
10451 90 : nc_get_att_schar(nCdfId, nVarId, pszAttrName, pscTemp);
10452 90 : dfValue = static_cast<double>(pscTemp[0]);
10453 90 : if (nAttrLen > 1)
10454 : {
10455 20 : for (m = 0; m < nAttrLen - 1; m++)
10456 : {
10457 11 : snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
10458 11 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10459 : }
10460 : }
10461 90 : snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
10462 90 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10463 90 : CPLFree(pscTemp);
10464 90 : break;
10465 : }
10466 463 : case NC_SHORT:
10467 : {
10468 : short *psTemp =
10469 463 : static_cast<short *>(CPLCalloc(nAttrLen, sizeof(short)));
10470 463 : nc_get_att_short(nCdfId, nVarId, pszAttrName, psTemp);
10471 463 : dfValue = static_cast<double>(psTemp[0]);
10472 463 : if (nAttrLen > 1)
10473 : {
10474 724 : for (m = 0; m < nAttrLen - 1; m++)
10475 : {
10476 362 : snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
10477 362 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10478 : }
10479 : }
10480 463 : snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
10481 463 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10482 463 : CPLFree(psTemp);
10483 463 : break;
10484 : }
10485 524 : case NC_INT:
10486 : {
10487 524 : int *pnTemp = static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
10488 524 : nc_get_att_int(nCdfId, nVarId, pszAttrName, pnTemp);
10489 524 : dfValue = static_cast<double>(pnTemp[0]);
10490 524 : if (nAttrLen > 1)
10491 : {
10492 214 : for (m = 0; m < nAttrLen - 1; m++)
10493 : {
10494 137 : snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
10495 137 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10496 : }
10497 : }
10498 524 : snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
10499 524 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10500 524 : CPLFree(pnTemp);
10501 524 : break;
10502 : }
10503 391 : case NC_FLOAT:
10504 : {
10505 : float *pfTemp =
10506 391 : static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
10507 391 : nc_get_att_float(nCdfId, nVarId, pszAttrName, pfTemp);
10508 391 : dfValue = static_cast<double>(pfTemp[0]);
10509 391 : if (nAttrLen > 1)
10510 : {
10511 56 : for (m = 0; m < nAttrLen - 1; m++)
10512 : {
10513 28 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
10514 28 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10515 : }
10516 : }
10517 391 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
10518 391 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10519 391 : CPLFree(pfTemp);
10520 391 : break;
10521 : }
10522 1509 : case NC_DOUBLE:
10523 : {
10524 : double *pdfTemp =
10525 1509 : static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
10526 1509 : nc_get_att_double(nCdfId, nVarId, pszAttrName, pdfTemp);
10527 1509 : dfValue = pdfTemp[0];
10528 1509 : if (nAttrLen > 1)
10529 : {
10530 162 : for (m = 0; m < nAttrLen - 1; m++)
10531 : {
10532 88 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
10533 88 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10534 : }
10535 : }
10536 1509 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
10537 1509 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10538 1509 : CPLFree(pdfTemp);
10539 1509 : break;
10540 : }
10541 8 : case NC_STRING:
10542 : {
10543 : char **ppszTemp =
10544 8 : static_cast<char **>(CPLCalloc(nAttrLen, sizeof(char *)));
10545 8 : nc_get_att_string(nCdfId, nVarId, pszAttrName, ppszTemp);
10546 8 : bSetDoubleFromStr = true;
10547 8 : dfValue = 0.0;
10548 8 : if (nAttrLen > 1)
10549 : {
10550 15 : for (m = 0; m < nAttrLen - 1; m++)
10551 : {
10552 10 : NCDFSafeStrcat(&pszAttrValue,
10553 10 : ppszTemp[m] ? ppszTemp[m] : "{NULL}",
10554 : &nAttrValueSize);
10555 10 : NCDFSafeStrcat(&pszAttrValue, ",", &nAttrValueSize);
10556 : }
10557 : }
10558 8 : NCDFSafeStrcat(&pszAttrValue, ppszTemp[m] ? ppszTemp[m] : "{NULL}",
10559 : &nAttrValueSize);
10560 8 : nc_free_string(nAttrLen, ppszTemp);
10561 8 : CPLFree(ppszTemp);
10562 8 : break;
10563 : }
10564 26 : case NC_UBYTE:
10565 : {
10566 : unsigned char *pucTemp = static_cast<unsigned char *>(
10567 26 : CPLCalloc(nAttrLen, sizeof(unsigned char)));
10568 26 : nc_get_att_uchar(nCdfId, nVarId, pszAttrName, pucTemp);
10569 26 : dfValue = static_cast<double>(pucTemp[0]);
10570 26 : if (nAttrLen > 1)
10571 : {
10572 0 : for (m = 0; m < nAttrLen - 1; m++)
10573 : {
10574 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
10575 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10576 : }
10577 : }
10578 26 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
10579 26 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10580 26 : CPLFree(pucTemp);
10581 26 : break;
10582 : }
10583 24 : case NC_USHORT:
10584 : {
10585 : unsigned short *pusTemp;
10586 : pusTemp = static_cast<unsigned short *>(
10587 24 : CPLCalloc(nAttrLen, sizeof(unsigned short)));
10588 24 : nc_get_att_ushort(nCdfId, nVarId, pszAttrName, pusTemp);
10589 24 : dfValue = static_cast<double>(pusTemp[0]);
10590 24 : if (nAttrLen > 1)
10591 : {
10592 10 : for (m = 0; m < nAttrLen - 1; m++)
10593 : {
10594 5 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
10595 5 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10596 : }
10597 : }
10598 24 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
10599 24 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10600 24 : CPLFree(pusTemp);
10601 24 : break;
10602 : }
10603 16 : case NC_UINT:
10604 : {
10605 : unsigned int *punTemp =
10606 16 : static_cast<unsigned int *>(CPLCalloc(nAttrLen, sizeof(int)));
10607 16 : nc_get_att_uint(nCdfId, nVarId, pszAttrName, punTemp);
10608 16 : dfValue = static_cast<double>(punTemp[0]);
10609 16 : if (nAttrLen > 1)
10610 : {
10611 0 : for (m = 0; m < nAttrLen - 1; m++)
10612 : {
10613 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
10614 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10615 : }
10616 : }
10617 16 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
10618 16 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10619 16 : CPLFree(punTemp);
10620 16 : break;
10621 : }
10622 20 : case NC_INT64:
10623 : {
10624 : GIntBig *panTemp =
10625 20 : static_cast<GIntBig *>(CPLCalloc(nAttrLen, sizeof(GIntBig)));
10626 20 : nc_get_att_longlong(nCdfId, nVarId, pszAttrName, panTemp);
10627 20 : dfValue = static_cast<double>(panTemp[0]);
10628 20 : if (nAttrLen > 1)
10629 : {
10630 0 : for (m = 0; m < nAttrLen - 1; m++)
10631 : {
10632 0 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",",
10633 0 : panTemp[m]);
10634 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10635 : }
10636 : }
10637 20 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, panTemp[m]);
10638 20 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10639 20 : CPLFree(panTemp);
10640 20 : break;
10641 : }
10642 20 : case NC_UINT64:
10643 : {
10644 : GUIntBig *panTemp =
10645 20 : static_cast<GUIntBig *>(CPLCalloc(nAttrLen, sizeof(GUIntBig)));
10646 20 : nc_get_att_ulonglong(nCdfId, nVarId, pszAttrName, panTemp);
10647 20 : dfValue = static_cast<double>(panTemp[0]);
10648 20 : if (nAttrLen > 1)
10649 : {
10650 0 : for (m = 0; m < nAttrLen - 1; m++)
10651 : {
10652 0 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",",
10653 0 : panTemp[m]);
10654 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10655 : }
10656 : }
10657 20 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, panTemp[m]);
10658 20 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10659 20 : CPLFree(panTemp);
10660 20 : break;
10661 : }
10662 0 : default:
10663 0 : CPLDebug("GDAL_netCDF",
10664 : "NCDFGetAttr unsupported type %d for attribute %s",
10665 : nAttrType, pszAttrName);
10666 0 : break;
10667 : }
10668 :
10669 28676 : if (nAttrLen > 1 && nAttrType != NC_CHAR)
10670 560 : NCDFSafeStrcat(&pszAttrValue, "}", &nAttrValueSize);
10671 :
10672 28676 : if (bSetDoubleFromStr)
10673 : {
10674 25593 : if (CPLGetValueType(pszAttrValue) == CPL_VALUE_STRING)
10675 : {
10676 25411 : if (ppszValue == nullptr && pdfValue != nullptr)
10677 : {
10678 1 : CPLFree(pszAttrValue);
10679 1 : return CE_Failure;
10680 : }
10681 : }
10682 25592 : dfValue = CPLAtof(pszAttrValue);
10683 : }
10684 :
10685 : /* set return values */
10686 28675 : if (ppszValue)
10687 28363 : *ppszValue = pszAttrValue;
10688 : else
10689 312 : CPLFree(pszAttrValue);
10690 :
10691 28675 : if (pdfValue)
10692 312 : *pdfValue = dfValue;
10693 :
10694 28675 : return CE_None;
10695 : }
10696 :
10697 : /* sets pdfValue to first value found */
10698 1146 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
10699 : double *pdfValue)
10700 : {
10701 1146 : return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, pdfValue, nullptr);
10702 : }
10703 :
10704 : /* pszValue is the responsibility of the caller and must be freed */
10705 61172 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
10706 : char **pszValue)
10707 : {
10708 61172 : return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, nullptr, pszValue);
10709 : }
10710 :
10711 : /* By default write NC_CHAR, but detect for int/float/double and */
10712 : /* NC4 string arrays */
10713 103 : static CPLErr NCDFPutAttr(int nCdfId, int nVarId, const char *pszAttrName,
10714 : const char *pszValue)
10715 : {
10716 103 : int status = 0;
10717 103 : char *pszTemp = nullptr;
10718 :
10719 : /* get the attribute values as tokens */
10720 103 : char **papszValues = NCDFTokenizeArray(pszValue);
10721 103 : if (papszValues == nullptr)
10722 0 : return CE_Failure;
10723 :
10724 103 : size_t nAttrLen = CSLCount(papszValues);
10725 :
10726 : /* first detect type */
10727 103 : nc_type nAttrType = NC_CHAR;
10728 103 : nc_type nTmpAttrType = NC_CHAR;
10729 219 : for (size_t i = 0; i < nAttrLen; i++)
10730 : {
10731 116 : nTmpAttrType = NC_CHAR;
10732 116 : bool bFoundType = false;
10733 116 : errno = 0;
10734 116 : int nValue = static_cast<int>(strtol(papszValues[i], &pszTemp, 10));
10735 : /* test for int */
10736 : /* TODO test for Byte and short - can this be done safely? */
10737 116 : if (errno == 0 && papszValues[i] != pszTemp && *pszTemp == 0)
10738 : {
10739 : char szTemp[256];
10740 19 : CPLsnprintf(szTemp, sizeof(szTemp), "%d", nValue);
10741 19 : if (EQUAL(szTemp, papszValues[i]))
10742 : {
10743 19 : bFoundType = true;
10744 19 : nTmpAttrType = NC_INT;
10745 : }
10746 : else
10747 : {
10748 : unsigned int unValue = static_cast<unsigned int>(
10749 0 : strtoul(papszValues[i], &pszTemp, 10));
10750 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", unValue);
10751 0 : if (EQUAL(szTemp, papszValues[i]))
10752 : {
10753 0 : bFoundType = true;
10754 0 : nTmpAttrType = NC_UINT;
10755 : }
10756 : }
10757 : }
10758 116 : if (!bFoundType)
10759 : {
10760 : /* test for double */
10761 97 : errno = 0;
10762 97 : double dfValue = CPLStrtod(papszValues[i], &pszTemp);
10763 97 : if ((errno == 0) && (papszValues[i] != pszTemp) && (*pszTemp == 0))
10764 : {
10765 : // Test for float instead of double.
10766 : // strtof() is C89, which is not available in MSVC.
10767 : // See if we loose precision if we cast to float and write to
10768 : // char*.
10769 14 : float fValue = float(dfValue);
10770 : char szTemp[256];
10771 14 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", fValue);
10772 14 : if (EQUAL(szTemp, papszValues[i]))
10773 8 : nTmpAttrType = NC_FLOAT;
10774 : else
10775 6 : nTmpAttrType = NC_DOUBLE;
10776 : }
10777 : }
10778 116 : if ((nTmpAttrType <= NC_DOUBLE && nAttrType <= NC_DOUBLE &&
10779 96 : nTmpAttrType > nAttrType) ||
10780 96 : (nTmpAttrType == NC_UINT && nAttrType < NC_FLOAT) ||
10781 5 : (nTmpAttrType >= NC_FLOAT && nAttrType == NC_UINT))
10782 20 : nAttrType = nTmpAttrType;
10783 : }
10784 :
10785 : #ifdef DEBUG
10786 103 : if (EQUAL(pszAttrName, "DEBUG_EMPTY_DOUBLE_ATTR"))
10787 : {
10788 0 : nAttrType = NC_DOUBLE;
10789 0 : nAttrLen = 0;
10790 : }
10791 : #endif
10792 :
10793 : /* now write the data */
10794 103 : if (nAttrType == NC_CHAR)
10795 : {
10796 83 : int nTmpFormat = 0;
10797 83 : if (nAttrLen > 1)
10798 : {
10799 0 : status = nc_inq_format(nCdfId, &nTmpFormat);
10800 0 : NCDF_ERR(status);
10801 : }
10802 83 : if (nAttrLen > 1 && nTmpFormat == NCDF_FORMAT_NC4)
10803 0 : status = nc_put_att_string(nCdfId, nVarId, pszAttrName, nAttrLen,
10804 : const_cast<const char **>(papszValues));
10805 : else
10806 83 : status = nc_put_att_text(nCdfId, nVarId, pszAttrName,
10807 : strlen(pszValue), pszValue);
10808 83 : NCDF_ERR(status);
10809 : }
10810 : else
10811 : {
10812 20 : switch (nAttrType)
10813 : {
10814 11 : case NC_INT:
10815 : {
10816 : int *pnTemp =
10817 11 : static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
10818 30 : for (size_t i = 0; i < nAttrLen; i++)
10819 : {
10820 19 : pnTemp[i] =
10821 19 : static_cast<int>(strtol(papszValues[i], &pszTemp, 10));
10822 : }
10823 11 : status = nc_put_att_int(nCdfId, nVarId, pszAttrName, NC_INT,
10824 : nAttrLen, pnTemp);
10825 11 : NCDF_ERR(status);
10826 11 : CPLFree(pnTemp);
10827 11 : break;
10828 : }
10829 0 : case NC_UINT:
10830 : {
10831 : unsigned int *punTemp = static_cast<unsigned int *>(
10832 0 : CPLCalloc(nAttrLen, sizeof(unsigned int)));
10833 0 : for (size_t i = 0; i < nAttrLen; i++)
10834 : {
10835 0 : punTemp[i] = static_cast<unsigned int>(
10836 0 : strtol(papszValues[i], &pszTemp, 10));
10837 : }
10838 0 : status = nc_put_att_uint(nCdfId, nVarId, pszAttrName, NC_UINT,
10839 : nAttrLen, punTemp);
10840 0 : NCDF_ERR(status);
10841 0 : CPLFree(punTemp);
10842 0 : break;
10843 : }
10844 6 : case NC_FLOAT:
10845 : {
10846 : float *pfTemp =
10847 6 : static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
10848 14 : for (size_t i = 0; i < nAttrLen; i++)
10849 : {
10850 8 : pfTemp[i] =
10851 8 : static_cast<float>(CPLStrtod(papszValues[i], &pszTemp));
10852 : }
10853 6 : status = nc_put_att_float(nCdfId, nVarId, pszAttrName, NC_FLOAT,
10854 : nAttrLen, pfTemp);
10855 6 : NCDF_ERR(status);
10856 6 : CPLFree(pfTemp);
10857 6 : break;
10858 : }
10859 3 : case NC_DOUBLE:
10860 : {
10861 : double *pdfTemp =
10862 3 : static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
10863 9 : for (size_t i = 0; i < nAttrLen; i++)
10864 : {
10865 6 : pdfTemp[i] = CPLStrtod(papszValues[i], &pszTemp);
10866 : }
10867 3 : status = nc_put_att_double(nCdfId, nVarId, pszAttrName,
10868 : NC_DOUBLE, nAttrLen, pdfTemp);
10869 3 : NCDF_ERR(status);
10870 3 : CPLFree(pdfTemp);
10871 3 : break;
10872 : }
10873 0 : default:
10874 0 : if (papszValues)
10875 0 : CSLDestroy(papszValues);
10876 0 : return CE_Failure;
10877 : break;
10878 : }
10879 : }
10880 :
10881 103 : if (papszValues)
10882 103 : CSLDestroy(papszValues);
10883 :
10884 103 : return CE_None;
10885 : }
10886 :
10887 76 : static CPLErr NCDFGet1DVar(int nCdfId, int nVarId, char **pszValue)
10888 : {
10889 : /* get var information */
10890 76 : int nVarDimId = -1;
10891 76 : int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
10892 76 : if (status != NC_NOERR || nVarDimId != 1)
10893 0 : return CE_Failure;
10894 :
10895 76 : status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
10896 76 : if (status != NC_NOERR)
10897 0 : return CE_Failure;
10898 :
10899 76 : nc_type nVarType = NC_NAT;
10900 76 : status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
10901 76 : if (status != NC_NOERR)
10902 0 : return CE_Failure;
10903 :
10904 76 : size_t nVarLen = 0;
10905 76 : status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
10906 76 : if (status != NC_NOERR)
10907 0 : return CE_Failure;
10908 :
10909 76 : size_t start[1] = {0};
10910 76 : size_t count[1] = {nVarLen};
10911 :
10912 : /* Allocate guaranteed minimum size */
10913 76 : size_t nVarValueSize = NCDF_MAX_STR_LEN;
10914 : char *pszVarValue =
10915 76 : static_cast<char *>(CPLCalloc(nVarValueSize, sizeof(char)));
10916 76 : *pszVarValue = '\0';
10917 :
10918 76 : if (nVarLen == 0)
10919 : {
10920 : /* set return values */
10921 1 : *pszValue = pszVarValue;
10922 :
10923 1 : return CE_None;
10924 : }
10925 :
10926 75 : if (nVarLen > 1 && nVarType != NC_CHAR)
10927 40 : NCDFSafeStrcat(&pszVarValue, "{", &nVarValueSize);
10928 :
10929 75 : switch (nVarType)
10930 : {
10931 0 : case NC_CHAR:
10932 0 : nc_get_vara_text(nCdfId, nVarId, start, count, pszVarValue);
10933 0 : pszVarValue[nVarLen] = '\0';
10934 0 : break;
10935 0 : case NC_BYTE:
10936 : {
10937 : signed char *pscTemp = static_cast<signed char *>(
10938 0 : CPLCalloc(nVarLen, sizeof(signed char)));
10939 0 : nc_get_vara_schar(nCdfId, nVarId, start, count, pscTemp);
10940 : char szTemp[256];
10941 0 : size_t m = 0;
10942 0 : for (; m < nVarLen - 1; m++)
10943 : {
10944 0 : snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
10945 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10946 : }
10947 0 : snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
10948 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10949 0 : CPLFree(pscTemp);
10950 0 : break;
10951 : }
10952 0 : case NC_SHORT:
10953 : {
10954 : short *psTemp =
10955 0 : static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
10956 0 : nc_get_vara_short(nCdfId, nVarId, start, count, psTemp);
10957 : char szTemp[256];
10958 0 : size_t m = 0;
10959 0 : for (; m < nVarLen - 1; m++)
10960 : {
10961 0 : snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
10962 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10963 : }
10964 0 : snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
10965 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10966 0 : CPLFree(psTemp);
10967 0 : break;
10968 : }
10969 19 : case NC_INT:
10970 : {
10971 19 : int *pnTemp = static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
10972 19 : nc_get_vara_int(nCdfId, nVarId, start, count, pnTemp);
10973 : char szTemp[256];
10974 19 : size_t m = 0;
10975 36 : for (; m < nVarLen - 1; m++)
10976 : {
10977 17 : snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
10978 17 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10979 : }
10980 19 : snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
10981 19 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10982 19 : CPLFree(pnTemp);
10983 19 : break;
10984 : }
10985 8 : case NC_FLOAT:
10986 : {
10987 : float *pfTemp =
10988 8 : static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
10989 8 : nc_get_vara_float(nCdfId, nVarId, start, count, pfTemp);
10990 : char szTemp[256];
10991 8 : size_t m = 0;
10992 325 : for (; m < nVarLen - 1; m++)
10993 : {
10994 317 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
10995 317 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10996 : }
10997 8 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
10998 8 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10999 8 : CPLFree(pfTemp);
11000 8 : break;
11001 : }
11002 47 : case NC_DOUBLE:
11003 : {
11004 : double *pdfTemp =
11005 47 : static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
11006 47 : nc_get_vara_double(nCdfId, nVarId, start, count, pdfTemp);
11007 : char szTemp[256];
11008 47 : size_t m = 0;
11009 225 : for (; m < nVarLen - 1; m++)
11010 : {
11011 178 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
11012 178 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11013 : }
11014 47 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
11015 47 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11016 47 : CPLFree(pdfTemp);
11017 47 : break;
11018 : }
11019 0 : case NC_STRING:
11020 : {
11021 : char **ppszTemp =
11022 0 : static_cast<char **>(CPLCalloc(nVarLen, sizeof(char *)));
11023 0 : nc_get_vara_string(nCdfId, nVarId, start, count, ppszTemp);
11024 0 : size_t m = 0;
11025 0 : for (; m < nVarLen - 1; m++)
11026 : {
11027 0 : NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
11028 0 : NCDFSafeStrcat(&pszVarValue, ",", &nVarValueSize);
11029 : }
11030 0 : NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
11031 0 : nc_free_string(nVarLen, ppszTemp);
11032 0 : CPLFree(ppszTemp);
11033 0 : break;
11034 : }
11035 0 : case NC_UBYTE:
11036 : {
11037 : unsigned char *pucTemp;
11038 : pucTemp = static_cast<unsigned char *>(
11039 0 : CPLCalloc(nVarLen, sizeof(unsigned char)));
11040 0 : nc_get_vara_uchar(nCdfId, nVarId, start, count, pucTemp);
11041 : char szTemp[256];
11042 0 : size_t m = 0;
11043 0 : for (; m < nVarLen - 1; m++)
11044 : {
11045 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
11046 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11047 : }
11048 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
11049 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11050 0 : CPLFree(pucTemp);
11051 0 : break;
11052 : }
11053 0 : case NC_USHORT:
11054 : {
11055 : unsigned short *pusTemp;
11056 : pusTemp = static_cast<unsigned short *>(
11057 0 : CPLCalloc(nVarLen, sizeof(unsigned short)));
11058 0 : nc_get_vara_ushort(nCdfId, nVarId, start, count, pusTemp);
11059 : char szTemp[256];
11060 0 : size_t m = 0;
11061 0 : for (; m < nVarLen - 1; m++)
11062 : {
11063 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
11064 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11065 : }
11066 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
11067 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11068 0 : CPLFree(pusTemp);
11069 0 : break;
11070 : }
11071 0 : case NC_UINT:
11072 : {
11073 : unsigned int *punTemp;
11074 : punTemp = static_cast<unsigned int *>(
11075 0 : CPLCalloc(nVarLen, sizeof(unsigned int)));
11076 0 : nc_get_vara_uint(nCdfId, nVarId, start, count, punTemp);
11077 : char szTemp[256];
11078 0 : size_t m = 0;
11079 0 : for (; m < nVarLen - 1; m++)
11080 : {
11081 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
11082 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11083 : }
11084 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
11085 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11086 0 : CPLFree(punTemp);
11087 0 : break;
11088 : }
11089 1 : case NC_INT64:
11090 : {
11091 : long long *pnTemp =
11092 1 : static_cast<long long *>(CPLCalloc(nVarLen, sizeof(long long)));
11093 1 : nc_get_vara_longlong(nCdfId, nVarId, start, count, pnTemp);
11094 : char szTemp[256];
11095 1 : size_t m = 0;
11096 2 : for (; m < nVarLen - 1; m++)
11097 : {
11098 1 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",", pnTemp[m]);
11099 1 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11100 : }
11101 1 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, pnTemp[m]);
11102 1 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11103 1 : CPLFree(pnTemp);
11104 1 : break;
11105 : }
11106 0 : case NC_UINT64:
11107 : {
11108 : unsigned long long *pnTemp = static_cast<unsigned long long *>(
11109 0 : CPLCalloc(nVarLen, sizeof(unsigned long long)));
11110 0 : nc_get_vara_ulonglong(nCdfId, nVarId, start, count, pnTemp);
11111 : char szTemp[256];
11112 0 : size_t m = 0;
11113 0 : for (; m < nVarLen - 1; m++)
11114 : {
11115 0 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",", pnTemp[m]);
11116 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11117 : }
11118 0 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, pnTemp[m]);
11119 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11120 0 : CPLFree(pnTemp);
11121 0 : break;
11122 : }
11123 0 : default:
11124 0 : CPLDebug("GDAL_netCDF", "NCDFGetVar1D unsupported type %d",
11125 : nVarType);
11126 0 : CPLFree(pszVarValue);
11127 0 : pszVarValue = nullptr;
11128 0 : break;
11129 : }
11130 :
11131 75 : if (pszVarValue != nullptr && nVarLen > 1 && nVarType != NC_CHAR)
11132 40 : NCDFSafeStrcat(&pszVarValue, "}", &nVarValueSize);
11133 :
11134 : /* set return values */
11135 75 : *pszValue = pszVarValue;
11136 :
11137 75 : return CE_None;
11138 : }
11139 :
11140 8 : static CPLErr NCDFPut1DVar(int nCdfId, int nVarId, const char *pszValue)
11141 : {
11142 8 : if (EQUAL(pszValue, ""))
11143 0 : return CE_Failure;
11144 :
11145 : /* get var information */
11146 8 : int nVarDimId = -1;
11147 8 : int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
11148 8 : if (status != NC_NOERR || nVarDimId != 1)
11149 0 : return CE_Failure;
11150 :
11151 8 : status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
11152 8 : if (status != NC_NOERR)
11153 0 : return CE_Failure;
11154 :
11155 8 : nc_type nVarType = NC_CHAR;
11156 8 : status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
11157 8 : if (status != NC_NOERR)
11158 0 : return CE_Failure;
11159 :
11160 8 : size_t nVarLen = 0;
11161 8 : status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
11162 8 : if (status != NC_NOERR)
11163 0 : return CE_Failure;
11164 :
11165 8 : size_t start[1] = {0};
11166 8 : size_t count[1] = {nVarLen};
11167 :
11168 : /* get the values as tokens */
11169 8 : char **papszValues = NCDFTokenizeArray(pszValue);
11170 8 : if (papszValues == nullptr)
11171 0 : return CE_Failure;
11172 :
11173 8 : nVarLen = CSLCount(papszValues);
11174 :
11175 : /* now write the data */
11176 8 : if (nVarType == NC_CHAR)
11177 : {
11178 0 : status = nc_put_vara_text(nCdfId, nVarId, start, count, pszValue);
11179 0 : NCDF_ERR(status);
11180 : }
11181 : else
11182 : {
11183 8 : switch (nVarType)
11184 : {
11185 0 : case NC_BYTE:
11186 : {
11187 : signed char *pscTemp = static_cast<signed char *>(
11188 0 : CPLCalloc(nVarLen, sizeof(signed char)));
11189 0 : for (size_t i = 0; i < nVarLen; i++)
11190 : {
11191 0 : char *pszTemp = nullptr;
11192 0 : pscTemp[i] = static_cast<signed char>(
11193 0 : strtol(papszValues[i], &pszTemp, 10));
11194 : }
11195 : status =
11196 0 : nc_put_vara_schar(nCdfId, nVarId, start, count, pscTemp);
11197 0 : NCDF_ERR(status);
11198 0 : CPLFree(pscTemp);
11199 0 : break;
11200 : }
11201 0 : case NC_SHORT:
11202 : {
11203 : short *psTemp =
11204 0 : static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
11205 0 : for (size_t i = 0; i < nVarLen; i++)
11206 : {
11207 0 : char *pszTemp = nullptr;
11208 0 : psTemp[i] = static_cast<short>(
11209 0 : strtol(papszValues[i], &pszTemp, 10));
11210 : }
11211 : status =
11212 0 : nc_put_vara_short(nCdfId, nVarId, start, count, psTemp);
11213 0 : NCDF_ERR(status);
11214 0 : CPLFree(psTemp);
11215 0 : break;
11216 : }
11217 3 : case NC_INT:
11218 : {
11219 : int *pnTemp =
11220 3 : static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
11221 11 : for (size_t i = 0; i < nVarLen; i++)
11222 : {
11223 8 : char *pszTemp = nullptr;
11224 8 : pnTemp[i] =
11225 8 : static_cast<int>(strtol(papszValues[i], &pszTemp, 10));
11226 : }
11227 3 : status = nc_put_vara_int(nCdfId, nVarId, start, count, pnTemp);
11228 3 : NCDF_ERR(status);
11229 3 : CPLFree(pnTemp);
11230 3 : break;
11231 : }
11232 0 : case NC_FLOAT:
11233 : {
11234 : float *pfTemp =
11235 0 : static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
11236 0 : for (size_t i = 0; i < nVarLen; i++)
11237 : {
11238 0 : char *pszTemp = nullptr;
11239 0 : pfTemp[i] =
11240 0 : static_cast<float>(CPLStrtod(papszValues[i], &pszTemp));
11241 : }
11242 : status =
11243 0 : nc_put_vara_float(nCdfId, nVarId, start, count, pfTemp);
11244 0 : NCDF_ERR(status);
11245 0 : CPLFree(pfTemp);
11246 0 : break;
11247 : }
11248 5 : case NC_DOUBLE:
11249 : {
11250 : double *pdfTemp =
11251 5 : static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
11252 19 : for (size_t i = 0; i < nVarLen; i++)
11253 : {
11254 14 : char *pszTemp = nullptr;
11255 14 : pdfTemp[i] = CPLStrtod(papszValues[i], &pszTemp);
11256 : }
11257 : status =
11258 5 : nc_put_vara_double(nCdfId, nVarId, start, count, pdfTemp);
11259 5 : NCDF_ERR(status);
11260 5 : CPLFree(pdfTemp);
11261 5 : break;
11262 : }
11263 0 : default:
11264 : {
11265 0 : int nTmpFormat = 0;
11266 0 : status = nc_inq_format(nCdfId, &nTmpFormat);
11267 0 : NCDF_ERR(status);
11268 0 : if (nTmpFormat == NCDF_FORMAT_NC4)
11269 : {
11270 0 : switch (nVarType)
11271 : {
11272 0 : case NC_STRING:
11273 : {
11274 : status =
11275 0 : nc_put_vara_string(nCdfId, nVarId, start, count,
11276 : (const char **)papszValues);
11277 0 : NCDF_ERR(status);
11278 0 : break;
11279 : }
11280 0 : case NC_UBYTE:
11281 : {
11282 : unsigned char *pucTemp =
11283 : static_cast<unsigned char *>(
11284 0 : CPLCalloc(nVarLen, sizeof(unsigned char)));
11285 0 : for (size_t i = 0; i < nVarLen; i++)
11286 : {
11287 0 : char *pszTemp = nullptr;
11288 0 : pucTemp[i] = static_cast<unsigned char>(
11289 0 : strtoul(papszValues[i], &pszTemp, 10));
11290 : }
11291 0 : status = nc_put_vara_uchar(nCdfId, nVarId, start,
11292 : count, pucTemp);
11293 0 : NCDF_ERR(status);
11294 0 : CPLFree(pucTemp);
11295 0 : break;
11296 : }
11297 0 : case NC_USHORT:
11298 : {
11299 : unsigned short *pusTemp =
11300 : static_cast<unsigned short *>(
11301 0 : CPLCalloc(nVarLen, sizeof(unsigned short)));
11302 0 : for (size_t i = 0; i < nVarLen; i++)
11303 : {
11304 0 : char *pszTemp = nullptr;
11305 0 : pusTemp[i] = static_cast<unsigned short>(
11306 0 : strtoul(papszValues[i], &pszTemp, 10));
11307 : }
11308 0 : status = nc_put_vara_ushort(nCdfId, nVarId, start,
11309 : count, pusTemp);
11310 0 : NCDF_ERR(status);
11311 0 : CPLFree(pusTemp);
11312 0 : break;
11313 : }
11314 0 : case NC_UINT:
11315 : {
11316 : unsigned int *punTemp = static_cast<unsigned int *>(
11317 0 : CPLCalloc(nVarLen, sizeof(unsigned int)));
11318 0 : for (size_t i = 0; i < nVarLen; i++)
11319 : {
11320 0 : char *pszTemp = nullptr;
11321 0 : punTemp[i] = static_cast<unsigned int>(
11322 0 : strtoul(papszValues[i], &pszTemp, 10));
11323 : }
11324 0 : status = nc_put_vara_uint(nCdfId, nVarId, start,
11325 : count, punTemp);
11326 0 : NCDF_ERR(status);
11327 0 : CPLFree(punTemp);
11328 0 : break;
11329 : }
11330 0 : default:
11331 0 : if (papszValues)
11332 0 : CSLDestroy(papszValues);
11333 0 : return CE_Failure;
11334 : break;
11335 : }
11336 : }
11337 0 : break;
11338 : }
11339 : }
11340 : }
11341 :
11342 8 : if (papszValues)
11343 8 : CSLDestroy(papszValues);
11344 :
11345 8 : return CE_None;
11346 : }
11347 :
11348 : /************************************************************************/
11349 : /* GetDefaultNoDataValue() */
11350 : /************************************************************************/
11351 :
11352 188 : double NCDFGetDefaultNoDataValue(int nCdfId, int nVarId, int nVarType,
11353 : bool &bGotNoData)
11354 :
11355 : {
11356 188 : int nNoFill = 0;
11357 188 : double dfNoData = 0.0;
11358 :
11359 188 : switch (nVarType)
11360 : {
11361 0 : case NC_CHAR:
11362 : case NC_BYTE:
11363 : case NC_UBYTE:
11364 : // Don't do default fill-values for bytes, too risky.
11365 : // This function should not be called in those cases.
11366 0 : CPLAssert(false);
11367 : break;
11368 24 : case NC_SHORT:
11369 : {
11370 24 : short nFillVal = 0;
11371 24 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11372 : NC_NOERR)
11373 : {
11374 24 : if (!nNoFill)
11375 : {
11376 23 : bGotNoData = true;
11377 23 : dfNoData = nFillVal;
11378 : }
11379 : }
11380 : else
11381 0 : dfNoData = NC_FILL_SHORT;
11382 24 : break;
11383 : }
11384 26 : case NC_INT:
11385 : {
11386 26 : int nFillVal = 0;
11387 26 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11388 : NC_NOERR)
11389 : {
11390 26 : if (!nNoFill)
11391 : {
11392 25 : bGotNoData = true;
11393 25 : dfNoData = nFillVal;
11394 : }
11395 : }
11396 : else
11397 0 : dfNoData = NC_FILL_INT;
11398 26 : break;
11399 : }
11400 71 : case NC_FLOAT:
11401 : {
11402 71 : float fFillVal = 0;
11403 71 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &fFillVal) ==
11404 : NC_NOERR)
11405 : {
11406 71 : if (!nNoFill)
11407 : {
11408 67 : bGotNoData = true;
11409 67 : dfNoData = fFillVal;
11410 : }
11411 : }
11412 : else
11413 0 : dfNoData = NC_FILL_FLOAT;
11414 71 : break;
11415 : }
11416 34 : case NC_DOUBLE:
11417 : {
11418 34 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &dfNoData) ==
11419 : NC_NOERR)
11420 : {
11421 34 : if (!nNoFill)
11422 : {
11423 34 : bGotNoData = true;
11424 : }
11425 : }
11426 : else
11427 0 : dfNoData = NC_FILL_DOUBLE;
11428 34 : break;
11429 : }
11430 7 : case NC_USHORT:
11431 : {
11432 7 : unsigned short nFillVal = 0;
11433 7 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11434 : NC_NOERR)
11435 : {
11436 7 : if (!nNoFill)
11437 : {
11438 7 : bGotNoData = true;
11439 7 : dfNoData = nFillVal;
11440 : }
11441 : }
11442 : else
11443 0 : dfNoData = NC_FILL_USHORT;
11444 7 : break;
11445 : }
11446 7 : case NC_UINT:
11447 : {
11448 7 : unsigned int nFillVal = 0;
11449 7 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11450 : NC_NOERR)
11451 : {
11452 7 : if (!nNoFill)
11453 : {
11454 7 : bGotNoData = true;
11455 7 : dfNoData = nFillVal;
11456 : }
11457 : }
11458 : else
11459 0 : dfNoData = NC_FILL_UINT;
11460 7 : break;
11461 : }
11462 19 : default:
11463 19 : dfNoData = 0.0;
11464 19 : break;
11465 : }
11466 :
11467 188 : return dfNoData;
11468 : }
11469 :
11470 : /************************************************************************/
11471 : /* NCDFGetDefaultNoDataValueAsInt64() */
11472 : /************************************************************************/
11473 :
11474 2 : int64_t NCDFGetDefaultNoDataValueAsInt64(int nCdfId, int nVarId,
11475 : bool &bGotNoData)
11476 :
11477 : {
11478 2 : int nNoFill = 0;
11479 2 : long long nFillVal = 0;
11480 2 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
11481 : {
11482 2 : if (!nNoFill)
11483 : {
11484 2 : bGotNoData = true;
11485 2 : return static_cast<int64_t>(nFillVal);
11486 : }
11487 : }
11488 : else
11489 0 : return static_cast<int64_t>(NC_FILL_INT64);
11490 0 : return 0;
11491 : }
11492 :
11493 : /************************************************************************/
11494 : /* NCDFGetDefaultNoDataValueAsUInt64() */
11495 : /************************************************************************/
11496 :
11497 1 : uint64_t NCDFGetDefaultNoDataValueAsUInt64(int nCdfId, int nVarId,
11498 : bool &bGotNoData)
11499 :
11500 : {
11501 1 : int nNoFill = 0;
11502 1 : unsigned long long nFillVal = 0;
11503 1 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
11504 : {
11505 1 : if (!nNoFill)
11506 : {
11507 1 : bGotNoData = true;
11508 1 : return static_cast<uint64_t>(nFillVal);
11509 : }
11510 : }
11511 : else
11512 0 : return static_cast<uint64_t>(NC_FILL_UINT64);
11513 0 : return 0;
11514 : }
11515 :
11516 10958 : static int NCDFDoesVarContainAttribVal(int nCdfId,
11517 : const char *const *papszAttribNames,
11518 : const char *const *papszAttribValues,
11519 : int nVarId, const char *pszVarName,
11520 : bool bStrict = true)
11521 : {
11522 10958 : if (nVarId == -1 && pszVarName != nullptr)
11523 8043 : NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
11524 :
11525 10958 : if (nVarId == -1)
11526 878 : return -1;
11527 :
11528 10080 : bool bFound = false;
11529 47078 : for (int i = 0; !bFound && papszAttribNames != nullptr &&
11530 44864 : papszAttribNames[i] != nullptr;
11531 : i++)
11532 : {
11533 36998 : char *pszTemp = nullptr;
11534 36998 : if (NCDFGetAttr(nCdfId, nVarId, papszAttribNames[i], &pszTemp) ==
11535 53062 : CE_None &&
11536 16064 : pszTemp != nullptr)
11537 : {
11538 16064 : if (bStrict)
11539 : {
11540 16064 : if (EQUAL(pszTemp, papszAttribValues[i]))
11541 2214 : bFound = true;
11542 : }
11543 : else
11544 : {
11545 0 : if (EQUALN(pszTemp, papszAttribValues[i],
11546 : strlen(papszAttribValues[i])))
11547 0 : bFound = true;
11548 : }
11549 16064 : CPLFree(pszTemp);
11550 : }
11551 : }
11552 10080 : return bFound;
11553 : }
11554 :
11555 1952 : static int NCDFDoesVarContainAttribVal2(int nCdfId, const char *papszAttribName,
11556 : const char *const *papszAttribValues,
11557 : int nVarId, const char *pszVarName,
11558 : int bStrict = true)
11559 : {
11560 1952 : if (nVarId == -1 && pszVarName != nullptr)
11561 1569 : NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
11562 :
11563 1952 : if (nVarId == -1)
11564 0 : return -1;
11565 :
11566 1952 : bool bFound = false;
11567 1952 : char *pszTemp = nullptr;
11568 2331 : if (NCDFGetAttr(nCdfId, nVarId, papszAttribName, &pszTemp) != CE_None ||
11569 379 : pszTemp == nullptr)
11570 1573 : return FALSE;
11571 :
11572 7703 : for (int i = 0; !bFound && i < CSLCount(papszAttribValues); i++)
11573 : {
11574 7324 : if (bStrict)
11575 : {
11576 7296 : if (EQUAL(pszTemp, papszAttribValues[i]))
11577 31 : bFound = true;
11578 : }
11579 : else
11580 : {
11581 28 : if (EQUALN(pszTemp, papszAttribValues[i],
11582 : strlen(papszAttribValues[i])))
11583 0 : bFound = true;
11584 : }
11585 : }
11586 :
11587 379 : CPLFree(pszTemp);
11588 :
11589 379 : return bFound;
11590 : }
11591 :
11592 876 : static bool NCDFEqual(const char *papszName, const char *const *papszValues)
11593 : {
11594 876 : if (papszName == nullptr || EQUAL(papszName, ""))
11595 0 : return false;
11596 :
11597 2392 : for (int i = 0; papszValues && papszValues[i]; ++i)
11598 : {
11599 1636 : if (EQUAL(papszName, papszValues[i]))
11600 120 : return true;
11601 : }
11602 :
11603 756 : return false;
11604 : }
11605 :
11606 : // Test that a variable is longitude/latitude coordinate,
11607 : // following CF 4.1 and 4.2.
11608 3727 : bool NCDFIsVarLongitude(int nCdfId, int nVarId, const char *pszVarName)
11609 : {
11610 : // Check for matching attributes.
11611 3727 : int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLongitudeAttribNames,
11612 : papszCFLongitudeAttribValues, nVarId,
11613 : pszVarName);
11614 : // If not found using attributes then check using var name
11615 : // unless GDAL_NETCDF_VERIFY_DIMS=STRICT.
11616 3727 : if (bVal == -1)
11617 : {
11618 280 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11619 : "STRICT"))
11620 280 : bVal = NCDFEqual(pszVarName, papszCFLongitudeVarNames);
11621 : else
11622 0 : bVal = FALSE;
11623 : }
11624 3447 : else if (bVal)
11625 : {
11626 : // Check that the units is not 'm' or '1'. See #6759
11627 774 : char *pszTemp = nullptr;
11628 1145 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11629 371 : pszTemp != nullptr)
11630 : {
11631 371 : if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
11632 97 : bVal = false;
11633 371 : CPLFree(pszTemp);
11634 : }
11635 : }
11636 :
11637 3727 : return CPL_TO_BOOL(bVal);
11638 : }
11639 :
11640 2142 : bool NCDFIsVarLatitude(int nCdfId, int nVarId, const char *pszVarName)
11641 : {
11642 2142 : int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLatitudeAttribNames,
11643 : papszCFLatitudeAttribValues, nVarId,
11644 : pszVarName);
11645 2142 : if (bVal == -1)
11646 : {
11647 163 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11648 : "STRICT"))
11649 163 : bVal = NCDFEqual(pszVarName, papszCFLatitudeVarNames);
11650 : else
11651 0 : bVal = FALSE;
11652 : }
11653 1979 : else if (bVal)
11654 : {
11655 : // Check that the units is not 'm' or '1'. See #6759
11656 534 : char *pszTemp = nullptr;
11657 673 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11658 139 : pszTemp != nullptr)
11659 : {
11660 139 : if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
11661 36 : bVal = false;
11662 139 : CPLFree(pszTemp);
11663 : }
11664 : }
11665 :
11666 2142 : return CPL_TO_BOOL(bVal);
11667 : }
11668 :
11669 2263 : bool NCDFIsVarProjectionX(int nCdfId, int nVarId, const char *pszVarName)
11670 : {
11671 2263 : int bVal = NCDFDoesVarContainAttribVal(
11672 : nCdfId, papszCFProjectionXAttribNames, papszCFProjectionXAttribValues,
11673 : nVarId, pszVarName);
11674 2263 : if (bVal == -1)
11675 : {
11676 274 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11677 : "STRICT"))
11678 274 : bVal = NCDFEqual(pszVarName, papszCFProjectionXVarNames);
11679 : else
11680 0 : bVal = FALSE;
11681 : }
11682 1989 : else if (bVal)
11683 : {
11684 : // Check that the units is not '1'
11685 364 : char *pszTemp = nullptr;
11686 518 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11687 154 : pszTemp != nullptr)
11688 : {
11689 154 : if (EQUAL(pszTemp, "1"))
11690 5 : bVal = false;
11691 154 : CPLFree(pszTemp);
11692 : }
11693 : }
11694 :
11695 2263 : return CPL_TO_BOOL(bVal);
11696 : }
11697 :
11698 1607 : bool NCDFIsVarProjectionY(int nCdfId, int nVarId, const char *pszVarName)
11699 : {
11700 1607 : int bVal = NCDFDoesVarContainAttribVal(
11701 : nCdfId, papszCFProjectionYAttribNames, papszCFProjectionYAttribValues,
11702 : nVarId, pszVarName);
11703 1607 : if (bVal == -1)
11704 : {
11705 159 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11706 : "STRICT"))
11707 159 : bVal = NCDFEqual(pszVarName, papszCFProjectionYVarNames);
11708 : else
11709 0 : bVal = FALSE;
11710 : }
11711 1448 : else if (bVal)
11712 : {
11713 : // Check that the units is not '1'
11714 363 : char *pszTemp = nullptr;
11715 516 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11716 153 : pszTemp != nullptr)
11717 : {
11718 153 : if (EQUAL(pszTemp, "1"))
11719 5 : bVal = false;
11720 153 : CPLFree(pszTemp);
11721 : }
11722 : }
11723 :
11724 1607 : return CPL_TO_BOOL(bVal);
11725 : }
11726 :
11727 : /* test that a variable is a vertical coordinate, following CF 4.3 */
11728 1017 : bool NCDFIsVarVerticalCoord(int nCdfId, int nVarId, const char *pszVarName)
11729 : {
11730 : /* check for matching attributes */
11731 1017 : if (NCDFDoesVarContainAttribVal(nCdfId, papszCFVerticalAttribNames,
11732 : papszCFVerticalAttribValues, nVarId,
11733 1017 : pszVarName))
11734 72 : return true;
11735 : /* check for matching units */
11736 945 : else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
11737 : papszCFVerticalUnitsValues, nVarId,
11738 945 : pszVarName))
11739 31 : return true;
11740 : /* check for matching standard name */
11741 914 : else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_STD_NAME,
11742 : papszCFVerticalStandardNameValues,
11743 914 : nVarId, pszVarName))
11744 0 : return true;
11745 : else
11746 914 : return false;
11747 : }
11748 :
11749 : /* test that a variable is a time coordinate, following CF 4.4 */
11750 202 : bool NCDFIsVarTimeCoord(int nCdfId, int nVarId, const char *pszVarName)
11751 : {
11752 : /* check for matching attributes */
11753 202 : if (NCDFDoesVarContainAttribVal(nCdfId, papszCFTimeAttribNames,
11754 : papszCFTimeAttribValues, nVarId,
11755 202 : pszVarName))
11756 109 : return true;
11757 : /* check for matching units */
11758 93 : else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
11759 : papszCFTimeUnitsValues, nVarId,
11760 93 : pszVarName, false))
11761 0 : return true;
11762 : else
11763 93 : return false;
11764 : }
11765 :
11766 : // Parse a string, and return as a string list.
11767 : // If it an array of the form {a,b}, then tokenize it.
11768 : // Otherwise, return a copy.
11769 182 : static char **NCDFTokenizeArray(const char *pszValue)
11770 : {
11771 182 : if (pszValue == nullptr || EQUAL(pszValue, ""))
11772 50 : return nullptr;
11773 :
11774 132 : char **papszValues = nullptr;
11775 132 : const int nLen = static_cast<int>(strlen(pszValue));
11776 :
11777 132 : if (pszValue[0] == '{' && nLen > 2 && pszValue[nLen - 1] == '}')
11778 : {
11779 41 : char *pszTemp = static_cast<char *>(CPLMalloc((nLen - 2) + 1));
11780 41 : strncpy(pszTemp, pszValue + 1, nLen - 2);
11781 41 : pszTemp[nLen - 2] = '\0';
11782 41 : papszValues = CSLTokenizeString2(pszTemp, ",", CSLT_ALLOWEMPTYTOKENS);
11783 41 : CPLFree(pszTemp);
11784 : }
11785 : else
11786 : {
11787 91 : papszValues = reinterpret_cast<char **>(CPLCalloc(2, sizeof(char *)));
11788 91 : papszValues[0] = CPLStrdup(pszValue);
11789 91 : papszValues[1] = nullptr;
11790 : }
11791 :
11792 132 : return papszValues;
11793 : }
11794 :
11795 : // Open a NetCDF subdataset from full path /group1/group2/.../groupn/var.
11796 : // Leading slash is optional.
11797 405 : static CPLErr NCDFOpenSubDataset(int nCdfId, const char *pszSubdatasetName,
11798 : int *pnGroupId, int *pnVarId)
11799 : {
11800 405 : *pnGroupId = -1;
11801 405 : *pnVarId = -1;
11802 :
11803 : // Open group.
11804 : char *pszGroupFullName =
11805 405 : CPLStrdup(CPLGetPathSafe(pszSubdatasetName).c_str());
11806 : // Add a leading slash if needed.
11807 405 : if (pszGroupFullName[0] != '/')
11808 : {
11809 389 : char *old = pszGroupFullName;
11810 389 : pszGroupFullName = CPLStrdup(CPLSPrintf("/%s", pszGroupFullName));
11811 389 : CPLFree(old);
11812 : }
11813 : // Detect root group.
11814 405 : if (EQUAL(pszGroupFullName, "/"))
11815 : {
11816 389 : *pnGroupId = nCdfId;
11817 389 : CPLFree(pszGroupFullName);
11818 : }
11819 : else
11820 : {
11821 16 : int status = nc_inq_grp_full_ncid(nCdfId, pszGroupFullName, pnGroupId);
11822 16 : CPLFree(pszGroupFullName);
11823 16 : NCDF_ERR_RET(status);
11824 : }
11825 :
11826 : // Open var.
11827 405 : const char *pszVarName = CPLGetFilename(pszSubdatasetName);
11828 405 : NCDF_ERR_RET(nc_inq_varid(*pnGroupId, pszVarName, pnVarId));
11829 :
11830 405 : return CE_None;
11831 : }
11832 :
11833 : // Get all dimensions visible from a given NetCDF (or group) ID and any of
11834 : // its parents.
11835 346 : static CPLErr NCDFGetVisibleDims(int nGroupId, int *pnDims, int **ppanDimIds)
11836 : {
11837 346 : int nDims = 0;
11838 346 : int *panDimIds = nullptr;
11839 346 : NCDF_ERR_RET(nc_inq_dimids(nGroupId, &nDims, nullptr, true));
11840 :
11841 346 : panDimIds = static_cast<int *>(CPLMalloc(nDims * sizeof(int)));
11842 :
11843 346 : int status = nc_inq_dimids(nGroupId, nullptr, panDimIds, true);
11844 346 : if (status != NC_NOERR)
11845 0 : CPLFree(panDimIds);
11846 346 : NCDF_ERR_RET(status);
11847 :
11848 346 : *pnDims = nDims;
11849 346 : *ppanDimIds = panDimIds;
11850 :
11851 346 : return CE_None;
11852 : }
11853 :
11854 : // Get direct sub-groups IDs of a given NetCDF (or group) ID.
11855 : // Consider only direct children, does not get children of children.
11856 3026 : static CPLErr NCDFGetSubGroups(int nGroupId, int *pnSubGroups,
11857 : int **ppanSubGroupIds)
11858 : {
11859 3026 : *pnSubGroups = 0;
11860 3026 : *ppanSubGroupIds = nullptr;
11861 :
11862 : int nSubGroups;
11863 3026 : NCDF_ERR_RET(nc_inq_grps(nGroupId, &nSubGroups, nullptr));
11864 : int *panSubGroupIds =
11865 3026 : static_cast<int *>(CPLMalloc(nSubGroups * sizeof(int)));
11866 3026 : NCDF_ERR_RET(nc_inq_grps(nGroupId, nullptr, panSubGroupIds));
11867 3026 : *pnSubGroups = nSubGroups;
11868 3026 : *ppanSubGroupIds = panSubGroupIds;
11869 :
11870 3026 : return CE_None;
11871 : }
11872 :
11873 : // Get the full name of a given NetCDF (or group) ID
11874 : // (e.g. /group1/group2/.../groupn).
11875 : // bNC3Compat remove the leading slash for top-level variables for
11876 : // backward compatibility (top-level variables are the ones in the root group).
11877 15140 : static CPLErr NCDFGetGroupFullName(int nGroupId, char **ppszFullName,
11878 : bool bNC3Compat)
11879 : {
11880 15140 : *ppszFullName = nullptr;
11881 :
11882 : size_t nFullNameLen;
11883 15140 : NCDF_ERR_RET(nc_inq_grpname_len(nGroupId, &nFullNameLen));
11884 15140 : *ppszFullName =
11885 15140 : static_cast<char *>(CPLMalloc((nFullNameLen + 1) * sizeof(char)));
11886 15140 : int status = nc_inq_grpname_full(nGroupId, &nFullNameLen, *ppszFullName);
11887 15140 : if (status != NC_NOERR)
11888 : {
11889 0 : CPLFree(*ppszFullName);
11890 0 : *ppszFullName = nullptr;
11891 0 : NCDF_ERR_RET(status);
11892 : }
11893 :
11894 15140 : if (bNC3Compat && EQUAL(*ppszFullName, "/"))
11895 7793 : (*ppszFullName)[0] = '\0';
11896 :
11897 15140 : return CE_None;
11898 : }
11899 :
11900 7159 : CPLString NCDFGetGroupFullName(int nGroupId)
11901 : {
11902 7159 : char *pszFullname = nullptr;
11903 7159 : NCDFGetGroupFullName(nGroupId, &pszFullname, false);
11904 7159 : CPLString osRet(pszFullname ? pszFullname : "");
11905 7159 : CPLFree(pszFullname);
11906 14318 : return osRet;
11907 : }
11908 :
11909 : // Get the full name of a given NetCDF variable ID
11910 : // (e.g. /group1/group2/.../groupn/var).
11911 : // Handle also NC_GLOBAL as nVarId.
11912 : // bNC3Compat remove the leading slash for top-level variables for
11913 : // backward compatibility (top-level variables are the ones in the root group).
11914 7932 : static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId, char **ppszFullName,
11915 : bool bNC3Compat)
11916 : {
11917 7932 : *ppszFullName = nullptr;
11918 7932 : char *pszGroupFullName = nullptr;
11919 7932 : ERR_RET(NCDFGetGroupFullName(nGroupId, &pszGroupFullName, bNC3Compat));
11920 : char szVarName[NC_MAX_NAME + 1];
11921 7932 : if (nVarId == NC_GLOBAL)
11922 : {
11923 1074 : strcpy(szVarName, "NC_GLOBAL");
11924 : }
11925 : else
11926 : {
11927 6858 : int status = nc_inq_varname(nGroupId, nVarId, szVarName);
11928 6858 : if (status != NC_NOERR)
11929 : {
11930 0 : CPLFree(pszGroupFullName);
11931 0 : NCDF_ERR_RET(status);
11932 : }
11933 : }
11934 7932 : const char *pszSep = "/";
11935 7932 : if (EQUAL(pszGroupFullName, "/") || EQUAL(pszGroupFullName, ""))
11936 7746 : pszSep = "";
11937 7932 : *ppszFullName =
11938 7932 : CPLStrdup(CPLSPrintf("%s%s%s", pszGroupFullName, pszSep, szVarName));
11939 7932 : CPLFree(pszGroupFullName);
11940 7932 : return CE_None;
11941 : }
11942 :
11943 : // Get the NetCDF root group ID of a given group ID.
11944 0 : static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId)
11945 : {
11946 0 : *pnRootGroupId = -1;
11947 : // Recurse on parent group.
11948 : int nParentGroupId;
11949 0 : int status = nc_inq_grp_parent(nStartGroupId, &nParentGroupId);
11950 0 : if (status == NC_NOERR)
11951 0 : return NCDFGetRootGroup(nParentGroupId, pnRootGroupId);
11952 0 : else if (status != NC_ENOGRP)
11953 0 : NCDF_ERR_RET(status);
11954 : else // No more parent group.
11955 : {
11956 0 : *pnRootGroupId = nStartGroupId;
11957 : }
11958 :
11959 0 : return CE_None;
11960 : }
11961 :
11962 : // Implementation of NCDFResolveVar/Att.
11963 13066 : static CPLErr NCDFResolveElem(int nStartGroupId, const char *pszVar,
11964 : const char *pszAtt, int *pnGroupId, int *pnId,
11965 : bool bMandatory)
11966 : {
11967 13066 : if (!pszVar && !pszAtt)
11968 : {
11969 0 : CPLError(CE_Failure, CPLE_IllegalArg,
11970 : "pszVar and pszAtt NCDFResolveElem() args are both null.");
11971 0 : return CE_Failure;
11972 : }
11973 :
11974 : enum
11975 : {
11976 : NCRM_PARENT,
11977 : NCRM_WIDTH_WISE
11978 13066 : } eNCResolveMode = NCRM_PARENT;
11979 :
11980 26132 : std::queue<int> aoQueueGroupIdsToVisit;
11981 13066 : aoQueueGroupIdsToVisit.push(nStartGroupId);
11982 :
11983 14730 : while (!aoQueueGroupIdsToVisit.empty())
11984 : {
11985 : // Get the first group of the FIFO queue.
11986 13213 : *pnGroupId = aoQueueGroupIdsToVisit.front();
11987 13213 : aoQueueGroupIdsToVisit.pop();
11988 :
11989 : // Look if this group contains the searched element.
11990 : int status;
11991 13213 : if (pszVar)
11992 12999 : status = nc_inq_varid(*pnGroupId, pszVar, pnId);
11993 : else // pszAtt != nullptr.
11994 214 : status = nc_inq_attid(*pnGroupId, NC_GLOBAL, pszAtt, pnId);
11995 :
11996 13213 : if (status == NC_NOERR)
11997 : {
11998 11549 : return CE_None;
11999 : }
12000 1664 : else if ((pszVar && status != NC_ENOTVAR) ||
12001 211 : (pszAtt && status != NC_ENOTATT))
12002 : {
12003 0 : NCDF_ERR(status);
12004 : }
12005 : // Element not found, in NC4 case we must search in other groups
12006 : // following the CF logic.
12007 :
12008 : // The first resolve mode consists to search on parent groups.
12009 1664 : if (eNCResolveMode == NCRM_PARENT)
12010 : {
12011 1564 : int nParentGroupId = -1;
12012 1564 : int status2 = nc_inq_grp_parent(*pnGroupId, &nParentGroupId);
12013 1564 : if (status2 == NC_NOERR)
12014 45 : aoQueueGroupIdsToVisit.push(nParentGroupId);
12015 1519 : else if (status2 != NC_ENOGRP)
12016 0 : NCDF_ERR(status2);
12017 1519 : else if (pszVar)
12018 : // When resolving a variable, if there is no more
12019 : // parent group then we switch to width-wise search mode
12020 : // starting from the latest found parent group.
12021 1311 : eNCResolveMode = NCRM_WIDTH_WISE;
12022 : }
12023 :
12024 : // The second resolve mode is a width-wise search.
12025 1664 : if (eNCResolveMode == NCRM_WIDTH_WISE)
12026 : {
12027 : // Enqueue all direct sub-groups.
12028 1411 : int nSubGroups = 0;
12029 1411 : int *panSubGroupIds = nullptr;
12030 1411 : NCDFGetSubGroups(*pnGroupId, &nSubGroups, &panSubGroupIds);
12031 1513 : for (int i = 0; i < nSubGroups; i++)
12032 102 : aoQueueGroupIdsToVisit.push(panSubGroupIds[i]);
12033 1411 : CPLFree(panSubGroupIds);
12034 : }
12035 : }
12036 :
12037 1517 : if (bMandatory)
12038 : {
12039 0 : char *pszStartGroupFullName = nullptr;
12040 0 : NCDFGetGroupFullName(nStartGroupId, &pszStartGroupFullName);
12041 0 : CPLError(CE_Failure, CPLE_AppDefined,
12042 : "Cannot resolve mandatory %s %s from group %s",
12043 : (pszVar ? pszVar : pszAtt),
12044 : (pszVar ? "variable" : "attribute"),
12045 0 : (pszStartGroupFullName ? pszStartGroupFullName : ""));
12046 0 : CPLFree(pszStartGroupFullName);
12047 : }
12048 :
12049 1517 : *pnGroupId = -1;
12050 1517 : *pnId = -1;
12051 1517 : return CE_Failure;
12052 : }
12053 :
12054 : // Resolve a variable name from a given starting group following the CF logic:
12055 : // - if var name is an absolute path then directly open it
12056 : // - first search in the starting group and its parent groups
12057 : // - then if there is no more parent group we switch to a width-wise search
12058 : // mode starting from the latest found parent group.
12059 : // The full CF logic is described here:
12060 : // https://github.com/diwg/cf2/blob/master/group/cf2-group.adoc#scope
12061 : // If bMandatory then print an error if resolving fails.
12062 : // TODO: implement support of relative paths.
12063 : // TODO: to follow strictly the CF logic, when searching for a coordinate
12064 : // variable, we must stop the parent search mode once the corresponding
12065 : // dimension is found and start the width-wise search from this group.
12066 : // TODO: to follow strictly the CF logic, when searching in width-wise mode
12067 : // we should skip every groups already visited during the parent
12068 : // search mode (but revisiting them should have no impact so we could
12069 : // let as it is if it is simpler...)
12070 : // TODO: CF specifies that the width-wise search order is "left-to-right" so
12071 : // maybe we must sort sibling groups alphabetically? but maybe not
12072 : // necessary if nc_inq_grps() already sort them?
12073 12855 : CPLErr NCDFResolveVar(int nStartGroupId, const char *pszVar, int *pnGroupId,
12074 : int *pnVarId, bool bMandatory)
12075 : {
12076 12855 : *pnGroupId = -1;
12077 12855 : *pnVarId = -1;
12078 12855 : int nGroupId = nStartGroupId, nVarId;
12079 12855 : if (pszVar[0] == '/')
12080 : {
12081 : // This is an absolute path: we can open the var directly.
12082 : int nRootGroupId;
12083 0 : ERR_RET(NCDFGetRootGroup(nStartGroupId, &nRootGroupId));
12084 0 : ERR_RET(NCDFOpenSubDataset(nRootGroupId, pszVar, &nGroupId, &nVarId));
12085 : }
12086 : else
12087 : {
12088 : // We have to search the variable following the CF logic.
12089 12855 : ERR_RET(NCDFResolveElem(nStartGroupId, pszVar, nullptr, &nGroupId,
12090 : &nVarId, bMandatory));
12091 : }
12092 11546 : *pnGroupId = nGroupId;
12093 11546 : *pnVarId = nVarId;
12094 11546 : return CE_None;
12095 : }
12096 :
12097 : // Like NCDFResolveVar but returns directly the var full name.
12098 1380 : static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,
12099 : char **ppszFullName, bool bMandatory)
12100 : {
12101 1380 : *ppszFullName = nullptr;
12102 : int nGroupId, nVarId;
12103 1380 : ERR_RET(
12104 : NCDFResolveVar(nStartGroupId, pszVar, &nGroupId, &nVarId, bMandatory));
12105 1354 : return NCDFGetVarFullName(nGroupId, nVarId, ppszFullName);
12106 : }
12107 :
12108 : // Like NCDFResolveVar but resolves an attribute instead a variable and
12109 : // returns its integer value.
12110 : // Only GLOBAL attributes are supported for the moment.
12111 211 : static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,
12112 : const char *pszAtt, int *pnAtt, bool bMandatory)
12113 : {
12114 211 : int nGroupId = nStartGroupId, nAttId = nStartVarId;
12115 211 : ERR_RET(NCDFResolveElem(nStartGroupId, nullptr, pszAtt, &nGroupId, &nAttId,
12116 : bMandatory));
12117 3 : NCDF_ERR_RET(nc_get_att_int(nGroupId, NC_GLOBAL, pszAtt, pnAtt));
12118 3 : return CE_None;
12119 : }
12120 :
12121 : // Filter variables to keep only valid 2+D raster bands and vector fields in
12122 : // a given a NetCDF (or group) ID and its sub-groups.
12123 : // Coordinate or boundary variables are ignored.
12124 : // It also creates corresponding vector layers.
12125 517 : CPLErr netCDFDataset::FilterVars(
12126 : int nCdfId, bool bKeepRasters, bool bKeepVectors, char **papszIgnoreVars,
12127 : int *pnRasterVars, int *pnGroupId, int *pnVarId, int *pnIgnoredVars,
12128 : std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>
12129 : &oMap2DDimsToGroupAndVar)
12130 : {
12131 517 : int nVars = 0;
12132 517 : int nRasterVars = 0;
12133 517 : NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
12134 :
12135 1034 : std::vector<int> anPotentialVectorVarID;
12136 : // oMapDimIdToCount[x] = number of times dim x is the first dimension of
12137 : // potential vector variables
12138 1034 : std::map<int, int> oMapDimIdToCount;
12139 517 : int nVarXId = -1;
12140 517 : int nVarYId = -1;
12141 517 : int nVarZId = -1;
12142 517 : int nVarTimeId = -1;
12143 517 : int nVarTimeDimId = -1;
12144 517 : bool bIsVectorOnly = true;
12145 517 : int nProfileDimId = -1;
12146 517 : int nParentIndexVarID = -1;
12147 :
12148 3157 : for (int v = 0; v < nVars; v++)
12149 : {
12150 : int nVarDims;
12151 2640 : NCDF_ERR_RET(nc_inq_varndims(nCdfId, v, &nVarDims));
12152 : // Should we ignore this variable?
12153 : char szTemp[NC_MAX_NAME + 1];
12154 2640 : szTemp[0] = '\0';
12155 2640 : NCDF_ERR_RET(nc_inq_varname(nCdfId, v, szTemp));
12156 :
12157 2640 : if (strstr(szTemp, "_node_coordinates") ||
12158 2640 : strstr(szTemp, "_node_count"))
12159 : {
12160 : // Ignore CF-1.8 Simple Geometries helper variables
12161 69 : continue;
12162 : }
12163 :
12164 3853 : if (nVarDims == 1 && (NCDFIsVarLongitude(nCdfId, -1, szTemp) ||
12165 1282 : NCDFIsVarProjectionX(nCdfId, -1, szTemp)))
12166 : {
12167 352 : nVarXId = v;
12168 : }
12169 3149 : else if (nVarDims == 1 && (NCDFIsVarLatitude(nCdfId, -1, szTemp) ||
12170 930 : NCDFIsVarProjectionY(nCdfId, -1, szTemp)))
12171 : {
12172 352 : nVarYId = v;
12173 : }
12174 1867 : else if (nVarDims == 1 && NCDFIsVarVerticalCoord(nCdfId, -1, szTemp))
12175 : {
12176 78 : nVarZId = v;
12177 : }
12178 : else
12179 : {
12180 1789 : char *pszVarFullName = nullptr;
12181 1789 : CPLErr eErr = NCDFGetVarFullName(nCdfId, v, &pszVarFullName);
12182 1789 : if (eErr != CE_None)
12183 : {
12184 0 : CPLFree(pszVarFullName);
12185 0 : continue;
12186 : }
12187 : bool bIgnoreVar =
12188 1789 : (CSLFindString(papszIgnoreVars, pszVarFullName) != -1);
12189 1789 : CPLFree(pszVarFullName);
12190 1789 : if (bIgnoreVar)
12191 : {
12192 102 : if (nVarDims == 1 && NCDFIsVarTimeCoord(nCdfId, -1, szTemp))
12193 : {
12194 11 : nVarTimeId = v;
12195 11 : nc_inq_vardimid(nCdfId, v, &nVarTimeDimId);
12196 : }
12197 91 : else if (nVarDims > 1)
12198 : {
12199 87 : (*pnIgnoredVars)++;
12200 87 : CPLDebug("GDAL_netCDF", "variable #%d [%s] was ignored", v,
12201 : szTemp);
12202 : }
12203 : }
12204 : // Only accept 2+D vars.
12205 1687 : else if (nVarDims >= 2)
12206 : {
12207 674 : bool bRasterCandidate = true;
12208 : // Identify variables that might be vector variables
12209 674 : if (nVarDims == 2)
12210 : {
12211 603 : int anDimIds[2] = {-1, -1};
12212 603 : nc_inq_vardimid(nCdfId, v, anDimIds);
12213 :
12214 603 : nc_type vartype = NC_NAT;
12215 603 : nc_inq_vartype(nCdfId, v, &vartype);
12216 :
12217 : char szDimNameFirst[NC_MAX_NAME + 1];
12218 : char szDimNameSecond[NC_MAX_NAME + 1];
12219 603 : szDimNameFirst[0] = '\0';
12220 603 : szDimNameSecond[0] = '\0';
12221 1362 : if (vartype == NC_CHAR &&
12222 156 : nc_inq_dimname(nCdfId, anDimIds[0], szDimNameFirst) ==
12223 156 : NC_NOERR &&
12224 156 : nc_inq_dimname(nCdfId, anDimIds[1], szDimNameSecond) ==
12225 156 : NC_NOERR &&
12226 156 : !NCDFIsVarLongitude(nCdfId, -1, szDimNameSecond) &&
12227 156 : !NCDFIsVarProjectionX(nCdfId, -1, szDimNameSecond) &&
12228 915 : !NCDFIsVarLatitude(nCdfId, -1, szDimNameFirst) &&
12229 156 : !NCDFIsVarProjectionY(nCdfId, -1, szDimNameFirst))
12230 : {
12231 156 : anPotentialVectorVarID.push_back(v);
12232 156 : oMapDimIdToCount[anDimIds[0]]++;
12233 156 : if (strstr(szDimNameSecond, "_max_width"))
12234 : {
12235 127 : bRasterCandidate = false;
12236 : }
12237 : else
12238 : {
12239 29 : std::array<int, 3> oKey{anDimIds[0], anDimIds[1],
12240 29 : vartype};
12241 29 : oMap2DDimsToGroupAndVar[oKey].emplace_back(
12242 29 : std::pair(nCdfId, v));
12243 : }
12244 : }
12245 : else
12246 : {
12247 447 : std::array<int, 3> oKey{anDimIds[0], anDimIds[1],
12248 447 : vartype};
12249 447 : oMap2DDimsToGroupAndVar[oKey].emplace_back(
12250 447 : std::pair(nCdfId, v));
12251 447 : bIsVectorOnly = false;
12252 : }
12253 : }
12254 : else
12255 : {
12256 71 : bIsVectorOnly = false;
12257 : }
12258 674 : if (bKeepRasters && bRasterCandidate)
12259 : {
12260 518 : *pnGroupId = nCdfId;
12261 518 : *pnVarId = v;
12262 518 : nRasterVars++;
12263 : }
12264 : }
12265 1013 : else if (nVarDims == 1)
12266 : {
12267 725 : nc_type atttype = NC_NAT;
12268 725 : size_t attlen = 0;
12269 725 : if (nc_inq_att(nCdfId, v, "instance_dimension", &atttype,
12270 14 : &attlen) == NC_NOERR &&
12271 725 : atttype == NC_CHAR && attlen < NC_MAX_NAME)
12272 : {
12273 : char szInstanceDimension[NC_MAX_NAME + 1];
12274 14 : if (nc_get_att_text(nCdfId, v, "instance_dimension",
12275 14 : szInstanceDimension) == NC_NOERR)
12276 : {
12277 14 : szInstanceDimension[attlen] = 0;
12278 14 : int status = nc_inq_dimid(nCdfId, szInstanceDimension,
12279 : &nProfileDimId);
12280 14 : if (status == NC_NOERR)
12281 14 : nParentIndexVarID = v;
12282 : else
12283 0 : nProfileDimId = -1;
12284 14 : if (status == NC_EBADDIM)
12285 0 : CPLError(CE_Warning, CPLE_AppDefined,
12286 : "Attribute instance_dimension='%s' refers "
12287 : "to a non existing dimension",
12288 : szInstanceDimension);
12289 : else
12290 14 : NCDF_ERR(status);
12291 : }
12292 : }
12293 725 : if (v != nParentIndexVarID)
12294 : {
12295 711 : anPotentialVectorVarID.push_back(v);
12296 711 : int nDimId = -1;
12297 711 : nc_inq_vardimid(nCdfId, v, &nDimId);
12298 711 : oMapDimIdToCount[nDimId]++;
12299 : }
12300 : }
12301 : }
12302 : }
12303 :
12304 : // If we are opened in raster-only mode and that there are only 1D or 2D
12305 : // variables and that the 2D variables have no X/Y dim, and all
12306 : // variables refer to the same main dimension (or 2 dimensions for
12307 : // featureType=profile), then it is a pure vector dataset
12308 : CPLString osFeatureType(
12309 517 : CSLFetchNameValueDef(papszMetadata, "NC_GLOBAL#featureType", ""));
12310 406 : if (bKeepRasters && !bKeepVectors && bIsVectorOnly && nRasterVars > 0 &&
12311 923 : !anPotentialVectorVarID.empty() &&
12312 0 : (oMapDimIdToCount.size() == 1 ||
12313 0 : (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2 &&
12314 0 : nProfileDimId >= 0)))
12315 : {
12316 0 : anPotentialVectorVarID.resize(0);
12317 : }
12318 : else
12319 : {
12320 517 : *pnRasterVars += nRasterVars;
12321 : }
12322 :
12323 517 : if (!anPotentialVectorVarID.empty() && bKeepVectors)
12324 : {
12325 : // Take the dimension that is referenced the most times.
12326 64 : if (!(oMapDimIdToCount.size() == 1 ||
12327 27 : (EQUAL(osFeatureType, "profile") &&
12328 26 : oMapDimIdToCount.size() == 2 && nProfileDimId >= 0)))
12329 : {
12330 1 : CPLError(CE_Warning, CPLE_AppDefined,
12331 : "The dataset has several variables that could be "
12332 : "identified as vector fields, but not all share the same "
12333 : "primary dimension. Consequently they will be ignored.");
12334 : }
12335 : else
12336 : {
12337 50 : if (nVarTimeId >= 0 &&
12338 50 : oMapDimIdToCount.find(nVarTimeDimId) != oMapDimIdToCount.end())
12339 : {
12340 1 : anPotentialVectorVarID.push_back(nVarTimeId);
12341 : }
12342 49 : CreateGrpVectorLayers(nCdfId, osFeatureType, anPotentialVectorVarID,
12343 : oMapDimIdToCount, nVarXId, nVarYId, nVarZId,
12344 : nProfileDimId, nParentIndexVarID,
12345 : bKeepRasters);
12346 : }
12347 : }
12348 :
12349 : // Recurse on sub-groups.
12350 517 : int nSubGroups = 0;
12351 517 : int *panSubGroupIds = nullptr;
12352 517 : NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
12353 549 : for (int i = 0; i < nSubGroups; i++)
12354 : {
12355 32 : FilterVars(panSubGroupIds[i], bKeepRasters, bKeepVectors,
12356 : papszIgnoreVars, pnRasterVars, pnGroupId, pnVarId,
12357 : pnIgnoredVars, oMap2DDimsToGroupAndVar);
12358 : }
12359 517 : CPLFree(panSubGroupIds);
12360 :
12361 517 : return CE_None;
12362 : }
12363 :
12364 : // Create vector layers from given potentially identified vector variables
12365 : // resulting from the scanning of a NetCDF (or group) ID.
12366 49 : CPLErr netCDFDataset::CreateGrpVectorLayers(
12367 : int nCdfId, const CPLString &osFeatureType,
12368 : const std::vector<int> &anPotentialVectorVarID,
12369 : const std::map<int, int> &oMapDimIdToCount, int nVarXId, int nVarYId,
12370 : int nVarZId, int nProfileDimId, int nParentIndexVarID, bool bKeepRasters)
12371 : {
12372 49 : char *pszGroupName = nullptr;
12373 49 : NCDFGetGroupFullName(nCdfId, &pszGroupName);
12374 49 : if (pszGroupName == nullptr || pszGroupName[0] == '\0')
12375 : {
12376 47 : CPLFree(pszGroupName);
12377 47 : pszGroupName = CPLStrdup(CPLGetBasenameSafe(osFilename).c_str());
12378 : }
12379 49 : OGRwkbGeometryType eGType = wkbUnknown;
12380 : CPLString osLayerName = CSLFetchNameValueDef(
12381 98 : papszMetadata, "NC_GLOBAL#ogr_layer_name", pszGroupName);
12382 49 : CPLFree(pszGroupName);
12383 49 : papszMetadata =
12384 49 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_name", nullptr);
12385 :
12386 49 : if (EQUAL(osFeatureType, "point") || EQUAL(osFeatureType, "profile"))
12387 : {
12388 33 : papszMetadata =
12389 33 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#featureType", nullptr);
12390 33 : eGType = wkbPoint;
12391 : }
12392 :
12393 : const char *pszLayerType =
12394 49 : CSLFetchNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_type");
12395 49 : if (pszLayerType != nullptr)
12396 : {
12397 9 : eGType = OGRFromOGCGeomType(pszLayerType);
12398 9 : papszMetadata =
12399 9 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_type", nullptr);
12400 : }
12401 :
12402 : CPLString osGeometryField =
12403 98 : CSLFetchNameValueDef(papszMetadata, "NC_GLOBAL#ogr_geometry_field", "");
12404 49 : papszMetadata =
12405 49 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_geometry_field", nullptr);
12406 :
12407 49 : int nFirstVarId = -1;
12408 49 : int nVectorDim = oMapDimIdToCount.rbegin()->first;
12409 49 : if (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2)
12410 : {
12411 13 : if (nVectorDim == nProfileDimId)
12412 0 : nVectorDim = oMapDimIdToCount.begin()->first;
12413 : }
12414 : else
12415 : {
12416 36 : nProfileDimId = -1;
12417 : }
12418 62 : for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)
12419 : {
12420 62 : int anDimIds[2] = {-1, -1};
12421 62 : nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);
12422 62 : if (nVectorDim == anDimIds[0])
12423 : {
12424 49 : nFirstVarId = anPotentialVectorVarID[j];
12425 49 : break;
12426 : }
12427 : }
12428 :
12429 : // In case where coordinates are explicitly specified for one of the
12430 : // field/variable, use them in priority over the ones that might have been
12431 : // identified above.
12432 49 : char *pszCoordinates = nullptr;
12433 49 : if (NCDFGetAttr(nCdfId, nFirstVarId, "coordinates", &pszCoordinates) ==
12434 : CE_None)
12435 : {
12436 34 : char **papszTokens = NCDFTokenizeCoordinatesAttribute(pszCFCoordinates);
12437 34 : for (int i = 0; papszTokens != nullptr && papszTokens[i] != nullptr;
12438 : i++)
12439 : {
12440 0 : if (NCDFIsVarLongitude(nCdfId, -1, papszTokens[i]) ||
12441 0 : NCDFIsVarProjectionX(nCdfId, -1, papszTokens[i]))
12442 : {
12443 0 : nVarXId = -1;
12444 0 : CPL_IGNORE_RET_VAL(
12445 0 : nc_inq_varid(nCdfId, papszTokens[i], &nVarXId));
12446 : }
12447 0 : else if (NCDFIsVarLatitude(nCdfId, -1, papszTokens[i]) ||
12448 0 : NCDFIsVarProjectionY(nCdfId, -1, papszTokens[i]))
12449 : {
12450 0 : nVarYId = -1;
12451 0 : CPL_IGNORE_RET_VAL(
12452 0 : nc_inq_varid(nCdfId, papszTokens[i], &nVarYId));
12453 : }
12454 0 : else if (NCDFIsVarVerticalCoord(nCdfId, -1, papszTokens[i]))
12455 : {
12456 0 : nVarZId = -1;
12457 0 : CPL_IGNORE_RET_VAL(
12458 0 : nc_inq_varid(nCdfId, papszTokens[i], &nVarZId));
12459 : }
12460 : }
12461 34 : CSLDestroy(papszTokens);
12462 : }
12463 49 : CPLFree(pszCoordinates);
12464 :
12465 : // Check that the X,Y,Z vars share 1D and share the same dimension as
12466 : // attribute variables.
12467 49 : if (nVarXId >= 0 && nVarYId >= 0)
12468 : {
12469 38 : int nVarDimCount = -1;
12470 38 : int nVarDimId = -1;
12471 38 : if (nc_inq_varndims(nCdfId, nVarXId, &nVarDimCount) != NC_NOERR ||
12472 38 : nVarDimCount != 1 ||
12473 38 : nc_inq_vardimid(nCdfId, nVarXId, &nVarDimId) != NC_NOERR ||
12474 38 : nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim) ||
12475 35 : nc_inq_varndims(nCdfId, nVarYId, &nVarDimCount) != NC_NOERR ||
12476 35 : nVarDimCount != 1 ||
12477 111 : nc_inq_vardimid(nCdfId, nVarYId, &nVarDimId) != NC_NOERR ||
12478 35 : nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim))
12479 : {
12480 3 : nVarXId = nVarYId = -1;
12481 : }
12482 69 : else if (nVarZId >= 0 &&
12483 34 : (nc_inq_varndims(nCdfId, nVarZId, &nVarDimCount) != NC_NOERR ||
12484 34 : nVarDimCount != 1 ||
12485 34 : nc_inq_vardimid(nCdfId, nVarZId, &nVarDimId) != NC_NOERR ||
12486 34 : nVarDimId != nVectorDim))
12487 : {
12488 0 : nVarZId = -1;
12489 : }
12490 : }
12491 :
12492 49 : if (eGType == wkbUnknown && nVarXId >= 0 && nVarYId >= 0)
12493 : {
12494 2 : eGType = wkbPoint;
12495 : }
12496 49 : if (eGType == wkbPoint && nVarXId >= 0 && nVarYId >= 0 && nVarZId >= 0)
12497 : {
12498 34 : eGType = wkbPoint25D;
12499 : }
12500 49 : if (eGType == wkbUnknown && osGeometryField.empty())
12501 : {
12502 5 : eGType = wkbNone;
12503 : }
12504 :
12505 : // Read projection info
12506 49 : char **papszMetadataBackup = CSLDuplicate(papszMetadata);
12507 49 : ReadAttributes(nCdfId, nFirstVarId);
12508 49 : if (!this->bSGSupport)
12509 49 : SetProjectionFromVar(nCdfId, nFirstVarId, true);
12510 49 : const char *pszValue = FetchAttr(nCdfId, nFirstVarId, CF_GRD_MAPPING);
12511 49 : char *pszGridMapping = (pszValue ? CPLStrdup(pszValue) : nullptr);
12512 49 : CSLDestroy(papszMetadata);
12513 49 : papszMetadata = papszMetadataBackup;
12514 :
12515 49 : OGRSpatialReference *poSRS = nullptr;
12516 49 : if (!m_oSRS.IsEmpty())
12517 : {
12518 21 : poSRS = m_oSRS.Clone();
12519 : }
12520 : // Reset if there's a 2D raster
12521 49 : m_bHasProjection = false;
12522 49 : m_bHasGeoTransform = false;
12523 :
12524 49 : if (!bKeepRasters)
12525 : {
12526 : // Strip out uninteresting metadata.
12527 45 : papszMetadata =
12528 45 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#Conventions", nullptr);
12529 45 : papszMetadata =
12530 45 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#GDAL", nullptr);
12531 45 : papszMetadata =
12532 45 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#history", nullptr);
12533 : }
12534 :
12535 : std::shared_ptr<netCDFLayer> poLayer(
12536 49 : new netCDFLayer(this, nCdfId, osLayerName, eGType, poSRS));
12537 49 : if (poSRS != nullptr)
12538 21 : poSRS->Release();
12539 49 : poLayer->SetRecordDimID(nVectorDim);
12540 49 : if (wkbFlatten(eGType) == wkbPoint && nVarXId >= 0 && nVarYId >= 0)
12541 : {
12542 35 : poLayer->SetXYZVars(nVarXId, nVarYId, nVarZId);
12543 : }
12544 14 : else if (!osGeometryField.empty())
12545 : {
12546 9 : poLayer->SetWKTGeometryField(osGeometryField);
12547 : }
12548 49 : if (pszGridMapping != nullptr)
12549 : {
12550 21 : poLayer->SetGridMapping(pszGridMapping);
12551 21 : CPLFree(pszGridMapping);
12552 : }
12553 49 : poLayer->SetProfile(nProfileDimId, nParentIndexVarID);
12554 :
12555 574 : for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)
12556 : {
12557 525 : int anDimIds[2] = {-1, -1};
12558 525 : nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);
12559 525 : if (anDimIds[0] == nVectorDim ||
12560 24 : (nProfileDimId >= 0 && anDimIds[0] == nProfileDimId))
12561 : {
12562 : #ifdef NCDF_DEBUG
12563 : char szTemp2[NC_MAX_NAME + 1] = {};
12564 : CPL_IGNORE_RET_VAL(
12565 : nc_inq_varname(nCdfId, anPotentialVectorVarID[j], szTemp2));
12566 : CPLDebug("GDAL_netCDF", "Variable %s is a vector field", szTemp2);
12567 : #endif
12568 525 : poLayer->AddField(anPotentialVectorVarID[j]);
12569 : }
12570 : }
12571 :
12572 49 : if (poLayer->GetLayerDefn()->GetFieldCount() != 0 ||
12573 0 : poLayer->GetGeomType() != wkbNone)
12574 : {
12575 49 : papoLayers.push_back(poLayer);
12576 : }
12577 :
12578 98 : return CE_None;
12579 : }
12580 :
12581 : // Get all coordinate and boundary variables full names referenced in
12582 : // a given a NetCDF (or group) ID and its sub-groups.
12583 : // These variables are identified in other variable's
12584 : // "coordinates" and "bounds" attribute.
12585 : // Searching coordinate and boundary variables may need to explore
12586 : // parents groups (or other groups in case of reference given in form of an
12587 : // absolute path).
12588 : // See CF sections 5.2, 5.6 and 7.1
12589 518 : static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId, char ***ppapszVars)
12590 : {
12591 518 : int nVars = 0;
12592 518 : NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
12593 :
12594 3172 : for (int v = 0; v < nVars; v++)
12595 : {
12596 2654 : char *pszTemp = nullptr;
12597 2654 : char **papszTokens = nullptr;
12598 2654 : if (NCDFGetAttr(nCdfId, v, "coordinates", &pszTemp) == CE_None)
12599 445 : papszTokens = NCDFTokenizeCoordinatesAttribute(pszTemp);
12600 2654 : CPLFree(pszTemp);
12601 2654 : pszTemp = nullptr;
12602 2654 : if (NCDFGetAttr(nCdfId, v, "bounds", &pszTemp) == CE_None &&
12603 2654 : pszTemp != nullptr && !EQUAL(pszTemp, ""))
12604 17 : papszTokens = CSLAddString(papszTokens, pszTemp);
12605 2654 : CPLFree(pszTemp);
12606 3940 : for (int i = 0; papszTokens != nullptr && papszTokens[i] != nullptr;
12607 : i++)
12608 : {
12609 1286 : char *pszVarFullName = nullptr;
12610 1286 : if (NCDFResolveVarFullName(nCdfId, papszTokens[i],
12611 1286 : &pszVarFullName) == CE_None)
12612 1260 : *ppapszVars = CSLAddString(*ppapszVars, pszVarFullName);
12613 1286 : CPLFree(pszVarFullName);
12614 : }
12615 2654 : CSLDestroy(papszTokens);
12616 : }
12617 :
12618 : // Recurse on sub-groups.
12619 : int nSubGroups;
12620 518 : int *panSubGroupIds = nullptr;
12621 518 : NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
12622 550 : for (int i = 0; i < nSubGroups; i++)
12623 : {
12624 32 : NCDFGetCoordAndBoundVarFullNames(panSubGroupIds[i], ppapszVars);
12625 : }
12626 518 : CPLFree(panSubGroupIds);
12627 :
12628 518 : return CE_None;
12629 : }
12630 :
12631 : // Check if give type is user defined
12632 898 : bool NCDFIsUserDefinedType(int /*ncid*/, int type)
12633 : {
12634 898 : return type >= NC_FIRSTUSERTYPEID;
12635 : }
12636 :
12637 558 : char **NCDFTokenizeCoordinatesAttribute(const char *pszCoordinates)
12638 : {
12639 : // CF conventions use space as the separator for variable names in the
12640 : // coordinates attribute, but some products such as
12641 : // https://sentinel.esa.int/web/sentinel/technical-guides/sentinel-3-synergy/products-algorithms/level-2-aod-algorithms-and-products/level-2-aod-products-description
12642 : // use comma.
12643 558 : return CSLTokenizeString2(pszCoordinates, ", ", 0);
12644 : }
|