Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: netCDF read/write Driver
4 : * Purpose: GDAL bindings over netCDF library.
5 : * Author: Frank Warmerdam, warmerdam@pobox.com
6 : * Even Rouault <even.rouault at spatialys.com>
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2004, Frank Warmerdam
10 : * Copyright (c) 2007-2016, Even Rouault <even.rouault at spatialys.com>
11 : * Copyright (c) 2010, Kyle Shannon <kyle at pobox dot com>
12 : * Copyright (c) 2021, CLS
13 : *
14 : * SPDX-License-Identifier: MIT
15 : ****************************************************************************/
16 :
17 : #include "cpl_port.h"
18 :
19 : #include <array>
20 : #include <cassert>
21 : #include <cctype>
22 : #include <cerrno>
23 : #include <climits>
24 : #include <cmath>
25 : #include <cstdio>
26 : #include <cstdlib>
27 : #include <cstring>
28 : #include <ctime>
29 : #include <algorithm>
30 : #include <limits>
31 : #include <map>
32 : #include <mutex>
33 : #include <set>
34 : #include <queue>
35 : #include <string>
36 : #include <tuple>
37 : #include <utility>
38 : #include <vector>
39 :
40 : // Must be included after standard includes, otherwise VS2015 fails when
41 : // including <ctime>
42 : #include "netcdfdataset.h"
43 : #include "netcdfdrivercore.h"
44 : #include "netcdfsg.h"
45 : #include "netcdfuffd.h"
46 :
47 : #include "netcdf_mem.h"
48 :
49 : #include "cpl_conv.h"
50 : #include "cpl_error.h"
51 : #include "cpl_float.h"
52 : #include "cpl_json.h"
53 : #include "cpl_minixml.h"
54 : #include "cpl_multiproc.h"
55 : #include "cpl_progress.h"
56 : #include "cpl_time.h"
57 : #include "gdal.h"
58 : #include "gdal_frmts.h"
59 : #include "gdal_priv_templates.hpp"
60 : #include "ogr_core.h"
61 : #include "ogr_srs_api.h"
62 :
63 : // netCDF 4.8 switched to expecting filenames in UTF-8 on Windows
64 : // But with netCDF 4.9 and https://github.com/Unidata/netcdf-c/pull/2277/,
65 : // this is apparently back to expecting filenames in current codepage...
66 : // Detect netCDF 4.8 with NC_ENCZARR
67 : // Detect netCDF 4.9 with NC_NOATTCREORD
68 : #if defined(NC_ENCZARR) && !defined(NC_NOATTCREORD)
69 : #define NETCDF_USES_UTF8
70 : #endif
71 :
72 : // Internal function declarations.
73 :
74 : static bool NCDFIsGDALVersionGTE(const char *pszVersion, int nTarget);
75 :
76 : static void
77 : NCDFAddGDALHistory(int fpImage, const char *pszFilename, bool bWriteGDALVersion,
78 : bool bWriteGDALHistory, const char *pszOldHist,
79 : const char *pszFunctionName,
80 : const char *pszCFVersion = GDAL_DEFAULT_NCDF_CONVENTIONS);
81 :
82 : static void NCDFAddHistory(int fpImage, const char *pszAddHist,
83 : const char *pszOldHist);
84 :
85 : static CPLErr NCDFSafeStrcat(char **ppszDest, const char *pszSrc,
86 : size_t *nDestSize);
87 :
88 : // Var / attribute helper functions.
89 : static CPLErr NCDFPutAttr(int nCdfId, int nVarId, const char *pszAttrName,
90 : const char *pszValue);
91 :
92 : // Replace this where used.
93 : static CPLErr NCDFGet1DVar(int nCdfId, int nVarId, char **pszValue);
94 : static CPLErr NCDFPut1DVar(int nCdfId, int nVarId, const char *pszValue);
95 :
96 : // Replace this where used.
97 : static CPLStringList NCDFTokenizeArray(const char *pszValue);
98 : static void CopyMetadata(GDALDataset *poSrcDS, GDALRasterBand *poSrcBand,
99 : GDALRasterBand *poDstBand, int fpImage, int CDFVarID,
100 : const char *pszMatchPrefix = nullptr);
101 :
102 : // NetCDF-4 groups helper functions.
103 : // They all work also for NetCDF-3 files which are considered as
104 : // NetCDF-4 file with only one group.
105 : static CPLErr NCDFOpenSubDataset(int nCdfId, const char *pszSubdatasetName,
106 : int *pnGroupId, int *pnVarId);
107 : static CPLErr NCDFGetVisibleDims(int nGroupId, int *pnDims, int **ppanDimIds);
108 : static CPLErr NCDFGetSubGroups(int nGroupId, int *pnSubGroups,
109 : int **ppanSubGroupIds);
110 : static CPLErr NCDFGetGroupFullName(int nGroupId, char **ppszFullName,
111 : bool bNC3Compat = true);
112 : static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId, char **ppszFullName,
113 : bool bNC3Compat = true);
114 : static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId);
115 :
116 : static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,
117 : char **ppszFullName,
118 : bool bMandatory = false);
119 : static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,
120 : const char *pszAtt, int *pnAtt,
121 : bool bMandatory = false);
122 : static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId, char ***ppapszVars);
123 :
124 : // Uncomment this for more debug output.
125 : // #define NCDF_DEBUG 1
126 :
127 : CPLMutex *hNCMutex = nullptr;
128 :
129 : // Workaround https://github.com/OSGeo/gdal/issues/6253
130 : // Having 2 netCDF handles on the same file doesn't work in a multi-threaded
131 : // way. Apparently having the same handle works better (this is OK since
132 : // we have a global mutex on the netCDF library)
133 : static std::map<std::string, int> goMapNameToNetCDFId;
134 : static std::map<int, std::pair<std::string, int>> goMapNetCDFIdToKeyAndCount;
135 :
136 755 : int GDAL_nc_open(const char *pszFilename, int nMode, int *pID)
137 : {
138 1510 : std::string osKey(pszFilename);
139 755 : osKey += "#####";
140 755 : osKey += std::to_string(nMode);
141 755 : auto oIter = goMapNameToNetCDFId.find(osKey);
142 755 : if (oIter == goMapNameToNetCDFId.end())
143 : {
144 699 : int ret = nc_open(pszFilename, nMode, pID);
145 699 : if (ret != NC_NOERR)
146 3 : return ret;
147 696 : goMapNameToNetCDFId[osKey] = *pID;
148 696 : goMapNetCDFIdToKeyAndCount[*pID] =
149 1392 : std::pair<std::string, int>(osKey, 1);
150 696 : return ret;
151 : }
152 : else
153 : {
154 56 : *pID = oIter->second;
155 56 : goMapNetCDFIdToKeyAndCount[oIter->second].second++;
156 56 : return NC_NOERR;
157 : }
158 : }
159 :
160 1064 : int GDAL_nc_close(int cdfid)
161 : {
162 1064 : int ret = NC_NOERR;
163 1064 : auto oIter = goMapNetCDFIdToKeyAndCount.find(cdfid);
164 1064 : if (oIter != goMapNetCDFIdToKeyAndCount.end())
165 : {
166 752 : if (--oIter->second.second == 0)
167 : {
168 696 : ret = nc_close(cdfid);
169 696 : goMapNameToNetCDFId.erase(oIter->second.first);
170 696 : goMapNetCDFIdToKeyAndCount.erase(oIter);
171 : }
172 : }
173 : else
174 : {
175 : // we can go here if file opened with nc_open_mem() or nc_create()
176 312 : ret = nc_close(cdfid);
177 : }
178 1064 : return ret;
179 : }
180 :
181 : /************************************************************************/
182 : /* ==================================================================== */
183 : /* netCDFRasterBand */
184 : /* ==================================================================== */
185 : /************************************************************************/
186 :
187 : class netCDFRasterBand final : public GDALPamRasterBand
188 : {
189 : friend class netCDFDataset;
190 :
191 : nc_type nc_datatype;
192 : int cdfid;
193 : int nZId;
194 : int nZDim;
195 : int nLevel;
196 : int nBandXPos;
197 : int nBandYPos;
198 : int *panBandZPos;
199 : int *panBandZLev;
200 : bool m_bNoDataSet = false;
201 : double m_dfNoDataValue = 0;
202 : bool m_bNoDataSetAsInt64 = false;
203 : int64_t m_nNodataValueInt64 = 0;
204 : bool m_bNoDataSetAsUInt64 = false;
205 : uint64_t m_nNodataValueUInt64 = 0;
206 : bool bValidRangeValid = false;
207 : double adfValidRange[2]{0, 0};
208 : bool m_bHaveScale = false;
209 : bool m_bHaveOffset = false;
210 : double m_dfScale = 1;
211 : double m_dfOffset = 0;
212 : CPLString m_osUnitType{};
213 : bool bSignedData;
214 : bool bCheckLongitude;
215 : bool m_bCreateMetadataFromOtherVarsDone = false;
216 :
217 : void CreateMetadataFromAttributes();
218 : void CreateMetadataFromOtherVars();
219 :
220 : template <class T>
221 : void CheckData(void *pImage, void *pImageNC, size_t nTmpBlockXSize,
222 : size_t nTmpBlockYSize, bool bCheckIsNan = false);
223 : template <class T>
224 : void CheckDataCpx(void *pImage, void *pImageNC, size_t nTmpBlockXSize,
225 : size_t nTmpBlockYSize, bool bCheckIsNan = false);
226 : void SetBlockSize();
227 :
228 : bool FetchNetcdfChunk(size_t xstart, size_t ystart, void *pImage);
229 :
230 : void SetNoDataValueNoUpdate(double dfNoData);
231 : void SetNoDataValueNoUpdate(int64_t nNoData);
232 : void SetNoDataValueNoUpdate(uint64_t nNoData);
233 :
234 : void SetOffsetNoUpdate(double dfVal);
235 : void SetScaleNoUpdate(double dfVal);
236 : void SetUnitTypeNoUpdate(const char *pszNewValue);
237 :
238 : protected:
239 : CPLXMLNode *SerializeToXML(const char *pszUnused) override;
240 :
241 : public:
242 : struct CONSTRUCTOR_OPEN
243 : {
244 : };
245 :
246 : struct CONSTRUCTOR_CREATE
247 : {
248 : };
249 :
250 : netCDFRasterBand(const CONSTRUCTOR_OPEN &, netCDFDataset *poDS,
251 : int nGroupId, int nZId, int nZDim, int nLevel,
252 : const int *panBandZLen, const int *panBandPos, int nBand);
253 : netCDFRasterBand(const CONSTRUCTOR_CREATE &, netCDFDataset *poDS,
254 : GDALDataType eType, int nBand, bool bSigned = true,
255 : const char *pszBandName = nullptr,
256 : const char *pszLongName = nullptr, int nZId = -1,
257 : int nZDim = 2, int nLevel = 0,
258 : const int *panBandZLev = nullptr,
259 : const int *panBandZPos = nullptr,
260 : const int *paDimIds = nullptr);
261 : ~netCDFRasterBand() override;
262 :
263 : double GetNoDataValue(int *) override;
264 : int64_t GetNoDataValueAsInt64(int *pbSuccess = nullptr) override;
265 : uint64_t GetNoDataValueAsUInt64(int *pbSuccess = nullptr) override;
266 : CPLErr SetNoDataValue(double) override;
267 : CPLErr SetNoDataValueAsInt64(int64_t nNoData) override;
268 : CPLErr SetNoDataValueAsUInt64(uint64_t nNoData) override;
269 : // virtual CPLErr DeleteNoDataValue();
270 : double GetOffset(int *) override;
271 : CPLErr SetOffset(double) override;
272 : double GetScale(int *) override;
273 : CPLErr SetScale(double) override;
274 : const char *GetUnitType() override;
275 : CPLErr SetUnitType(const char *) override;
276 : CPLErr IReadBlock(int, int, void *) override;
277 : CPLErr IWriteBlock(int, int, void *) override;
278 :
279 : CSLConstList GetMetadata(const char *pszDomain = "") override;
280 : const char *GetMetadataItem(const char *pszName,
281 : const char *pszDomain = "") override;
282 :
283 : CPLErr SetMetadataItem(const char *pszName, const char *pszValue,
284 : const char *pszDomain = "") override;
285 : CPLErr SetMetadata(CSLConstList papszMD,
286 : const char *pszDomain = "") override;
287 : };
288 :
289 : /************************************************************************/
290 : /* netCDFRasterBand() */
291 : /************************************************************************/
292 :
293 486 : netCDFRasterBand::netCDFRasterBand(const netCDFRasterBand::CONSTRUCTOR_OPEN &,
294 : netCDFDataset *poNCDFDS, int nGroupId,
295 : int nZIdIn, int nZDimIn, int nLevelIn,
296 : const int *panBandZLevIn,
297 486 : const int *panBandZPosIn, int nBandIn)
298 : : nc_datatype(NC_NAT), cdfid(nGroupId), nZId(nZIdIn), nZDim(nZDimIn),
299 486 : nLevel(nLevelIn), nBandXPos(panBandZPosIn[0]),
300 486 : nBandYPos(nZDim == 1 ? -1 : panBandZPosIn[1]), panBandZPos(nullptr),
301 : panBandZLev(nullptr),
302 : bSignedData(true), // Default signed, except for Byte.
303 972 : bCheckLongitude(false)
304 : {
305 486 : poDS = poNCDFDS;
306 486 : nBand = nBandIn;
307 :
308 : // Take care of all other dimensions.
309 486 : if (nZDim > 2)
310 : {
311 176 : panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
312 176 : panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
313 :
314 478 : for (int i = 0; i < nZDim - 2; i++)
315 : {
316 302 : panBandZPos[i] = panBandZPosIn[i + 2];
317 302 : panBandZLev[i] = panBandZLevIn[i];
318 : }
319 : }
320 :
321 486 : nRasterXSize = poDS->GetRasterXSize();
322 486 : nRasterYSize = poDS->GetRasterYSize();
323 486 : nBlockXSize = poDS->GetRasterXSize();
324 486 : nBlockYSize = 1;
325 :
326 : // Get the type of the "z" variable, our target raster array.
327 486 : if (nc_inq_var(cdfid, nZId, nullptr, &nc_datatype, nullptr, nullptr,
328 486 : nullptr) != NC_NOERR)
329 : {
330 0 : CPLError(CE_Failure, CPLE_AppDefined, "Error in nc_var_inq() on 'z'.");
331 0 : return;
332 : }
333 :
334 486 : if (NCDFIsUserDefinedType(cdfid, nc_datatype))
335 : {
336 : // First enquire and check that the number of fields is 2
337 : size_t nfields, compoundsize;
338 5 : if (nc_inq_compound(cdfid, nc_datatype, nullptr, &compoundsize,
339 5 : &nfields) != NC_NOERR)
340 : {
341 0 : CPLError(CE_Failure, CPLE_AppDefined,
342 : "Error in nc_inq_compound() on 'z'.");
343 0 : return;
344 : }
345 :
346 5 : if (nfields != 2)
347 : {
348 0 : CPLError(CE_Failure, CPLE_AppDefined,
349 : "Unsupported data type encountered in nc_inq_compound() "
350 : "on 'z'.");
351 0 : return;
352 : }
353 :
354 : // Now check that that two types are the same in the struct.
355 : nc_type field_type1, field_type2;
356 : int field_dims1, field_dims2;
357 5 : if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
358 : &field_type1, &field_dims1,
359 5 : nullptr) != NC_NOERR)
360 : {
361 0 : CPLError(
362 : CE_Failure, CPLE_AppDefined,
363 : "Error in querying Field 1 in nc_inq_compound_field() on 'z'.");
364 0 : return;
365 : }
366 :
367 5 : if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
368 : &field_type2, &field_dims2,
369 5 : nullptr) != NC_NOERR)
370 : {
371 0 : CPLError(
372 : CE_Failure, CPLE_AppDefined,
373 : "Error in querying Field 2 in nc_inq_compound_field() on 'z'.");
374 0 : return;
375 : }
376 :
377 5 : if ((field_type1 != field_type2) || (field_dims1 != field_dims2) ||
378 5 : (field_dims1 != 0))
379 : {
380 0 : CPLError(CE_Failure, CPLE_AppDefined,
381 : "Error in interpreting compound data type on 'z'.");
382 0 : return;
383 : }
384 :
385 5 : if (field_type1 == NC_SHORT)
386 0 : eDataType = GDT_CInt16;
387 5 : else if (field_type1 == NC_INT)
388 0 : eDataType = GDT_CInt32;
389 5 : else if (field_type1 == NC_FLOAT)
390 4 : eDataType = GDT_CFloat32;
391 1 : else if (field_type1 == NC_DOUBLE)
392 1 : eDataType = GDT_CFloat64;
393 : else
394 : {
395 0 : CPLError(CE_Failure, CPLE_AppDefined,
396 : "Unsupported netCDF compound data type encountered.");
397 0 : return;
398 : }
399 : }
400 : else
401 : {
402 481 : if (nc_datatype == NC_BYTE)
403 149 : eDataType = GDT_UInt8;
404 332 : else if (nc_datatype == NC_CHAR)
405 0 : eDataType = GDT_UInt8;
406 332 : else if (nc_datatype == NC_SHORT)
407 41 : eDataType = GDT_Int16;
408 291 : else if (nc_datatype == NC_INT)
409 89 : eDataType = GDT_Int32;
410 202 : else if (nc_datatype == NC_FLOAT)
411 123 : eDataType = GDT_Float32;
412 79 : else if (nc_datatype == NC_DOUBLE)
413 40 : eDataType = GDT_Float64;
414 39 : else if (nc_datatype == NC_UBYTE)
415 16 : eDataType = GDT_UInt8;
416 23 : else if (nc_datatype == NC_USHORT)
417 4 : eDataType = GDT_UInt16;
418 19 : else if (nc_datatype == NC_UINT)
419 4 : eDataType = GDT_UInt32;
420 15 : else if (nc_datatype == NC_INT64)
421 8 : eDataType = GDT_Int64;
422 7 : else if (nc_datatype == NC_UINT64)
423 7 : eDataType = GDT_UInt64;
424 : else
425 : {
426 0 : if (nBand == 1)
427 0 : CPLError(CE_Warning, CPLE_AppDefined,
428 : "Unsupported netCDF datatype (%d), treat as Float32.",
429 0 : static_cast<int>(nc_datatype));
430 0 : eDataType = GDT_Float32;
431 0 : nc_datatype = NC_FLOAT;
432 : }
433 : }
434 :
435 : // Find and set No Data for this variable.
436 486 : nc_type atttype = NC_NAT;
437 486 : size_t attlen = 0;
438 486 : const char *pszNoValueName = nullptr;
439 :
440 : // Find attribute name, either _FillValue or missing_value.
441 486 : int status = nc_inq_att(cdfid, nZId, NCDF_FillValue, &atttype, &attlen);
442 486 : if (status == NC_NOERR)
443 : {
444 249 : pszNoValueName = NCDF_FillValue;
445 : }
446 : else
447 : {
448 237 : status = nc_inq_att(cdfid, nZId, "missing_value", &atttype, &attlen);
449 237 : if (status == NC_NOERR)
450 : {
451 12 : pszNoValueName = "missing_value";
452 : }
453 : }
454 :
455 : // Fetch missing value.
456 486 : double dfNoData = 0.0;
457 486 : bool bGotNoData = false;
458 486 : int64_t nNoDataAsInt64 = 0;
459 486 : bool bGotNoDataAsInt64 = false;
460 486 : uint64_t nNoDataAsUInt64 = 0;
461 486 : bool bGotNoDataAsUInt64 = false;
462 486 : if (status == NC_NOERR)
463 : {
464 261 : nc_type nAttrType = NC_NAT;
465 261 : size_t nAttrLen = 0;
466 261 : status = nc_inq_att(cdfid, nZId, pszNoValueName, &nAttrType, &nAttrLen);
467 261 : if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_INT64)
468 : {
469 : long long v;
470 7 : nc_get_att_longlong(cdfid, nZId, pszNoValueName, &v);
471 7 : bGotNoData = true;
472 7 : bGotNoDataAsInt64 = true;
473 7 : nNoDataAsInt64 = static_cast<int64_t>(v);
474 : }
475 254 : else if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_UINT64)
476 : {
477 : unsigned long long v;
478 7 : nc_get_att_ulonglong(cdfid, nZId, pszNoValueName, &v);
479 7 : bGotNoData = true;
480 7 : bGotNoDataAsUInt64 = true;
481 7 : nNoDataAsUInt64 = static_cast<uint64_t>(v);
482 : }
483 247 : else if (NCDFGetAttr(cdfid, nZId, pszNoValueName, &dfNoData) == CE_None)
484 : {
485 246 : bGotNoData = true;
486 : }
487 : }
488 :
489 : // If NoData was not found, use the default value, but for non-Byte types
490 : // as it is not recommended:
491 : // https://www.unidata.ucar.edu/software/netcdf/docs/attribute_conventions.html
492 486 : nc_type vartype = NC_NAT;
493 486 : if (!bGotNoData)
494 : {
495 226 : nc_inq_vartype(cdfid, nZId, &vartype);
496 226 : if (vartype == NC_INT64)
497 : {
498 : nNoDataAsInt64 =
499 1 : NCDFGetDefaultNoDataValueAsInt64(cdfid, nZId, bGotNoData);
500 1 : bGotNoDataAsInt64 = bGotNoData;
501 : }
502 225 : else if (vartype == NC_UINT64)
503 : {
504 : nNoDataAsUInt64 =
505 0 : NCDFGetDefaultNoDataValueAsUInt64(cdfid, nZId, bGotNoData);
506 0 : bGotNoDataAsUInt64 = bGotNoData;
507 : }
508 225 : else if (vartype != NC_CHAR && vartype != NC_BYTE &&
509 99 : vartype != NC_UBYTE)
510 : {
511 89 : dfNoData =
512 89 : NCDFGetDefaultNoDataValue(cdfid, nZId, vartype, bGotNoData);
513 89 : if (bGotNoData)
514 : {
515 78 : CPLDebug("GDAL_netCDF",
516 : "did not get nodata value for variable #%d, using "
517 : "default %f",
518 : nZId, dfNoData);
519 : }
520 : }
521 : }
522 :
523 486 : bool bHasUnderscoreUnsignedAttr = false;
524 486 : bool bUnderscoreUnsignedAttrVal = false;
525 : {
526 486 : char *pszTemp = nullptr;
527 486 : if (NCDFGetAttr(cdfid, nZId, "_Unsigned", &pszTemp) == CE_None)
528 : {
529 141 : if (EQUAL(pszTemp, "true"))
530 : {
531 133 : bHasUnderscoreUnsignedAttr = true;
532 133 : bUnderscoreUnsignedAttrVal = true;
533 : }
534 8 : else if (EQUAL(pszTemp, "false"))
535 : {
536 8 : bHasUnderscoreUnsignedAttr = true;
537 8 : bUnderscoreUnsignedAttrVal = false;
538 : }
539 141 : CPLFree(pszTemp);
540 : }
541 : }
542 :
543 : // Look for valid_range or valid_min/valid_max.
544 :
545 : // First look for valid_range.
546 486 : if (CPLFetchBool(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE", true))
547 : {
548 484 : char *pszValidRange = nullptr;
549 484 : if (NCDFGetAttr(cdfid, nZId, "valid_range", &pszValidRange) ==
550 134 : CE_None &&
551 618 : pszValidRange[0] == '{' &&
552 134 : pszValidRange[strlen(pszValidRange) - 1] == '}')
553 : {
554 : const std::string osValidRange =
555 402 : std::string(pszValidRange).substr(1, strlen(pszValidRange) - 2);
556 : const CPLStringList aosValidRange(
557 268 : CSLTokenizeString2(osValidRange.c_str(), ",", 0));
558 134 : if (aosValidRange.size() == 2 &&
559 268 : CPLGetValueType(aosValidRange[0]) != CPL_VALUE_STRING &&
560 134 : CPLGetValueType(aosValidRange[1]) != CPL_VALUE_STRING)
561 : {
562 134 : bValidRangeValid = true;
563 134 : adfValidRange[0] = CPLAtof(aosValidRange[0]);
564 134 : adfValidRange[1] = CPLAtof(aosValidRange[1]);
565 : }
566 : }
567 484 : CPLFree(pszValidRange);
568 :
569 : // If not found look for valid_min and valid_max.
570 484 : if (!bValidRangeValid)
571 : {
572 350 : double dfMin = 0;
573 350 : double dfMax = 0;
574 365 : if (NCDFGetAttr(cdfid, nZId, "valid_min", &dfMin) == CE_None &&
575 15 : NCDFGetAttr(cdfid, nZId, "valid_max", &dfMax) == CE_None)
576 : {
577 8 : adfValidRange[0] = dfMin;
578 8 : adfValidRange[1] = dfMax;
579 8 : bValidRangeValid = true;
580 : }
581 : }
582 :
583 484 : if (bValidRangeValid &&
584 142 : (adfValidRange[0] < 0 || adfValidRange[1] < 0) &&
585 17 : nc_datatype == NC_SHORT && bHasUnderscoreUnsignedAttr &&
586 : bUnderscoreUnsignedAttrVal)
587 : {
588 2 : if (adfValidRange[0] < 0)
589 0 : adfValidRange[0] += 65536;
590 2 : if (adfValidRange[1] < 0)
591 2 : adfValidRange[1] += 65536;
592 2 : if (adfValidRange[0] <= adfValidRange[1])
593 : {
594 : // Updating metadata item
595 2 : GDALPamRasterBand::SetMetadataItem(
596 : "valid_range",
597 2 : CPLSPrintf("{%d,%d}", static_cast<int>(adfValidRange[0]),
598 2 : static_cast<int>(adfValidRange[1])));
599 : }
600 : }
601 :
602 484 : if (bValidRangeValid && adfValidRange[0] > adfValidRange[1])
603 : {
604 0 : CPLError(CE_Warning, CPLE_AppDefined,
605 : "netCDFDataset::valid_range: min > max:\n"
606 : " min: %lf\n max: %lf\n",
607 : adfValidRange[0], adfValidRange[1]);
608 0 : bValidRangeValid = false;
609 0 : adfValidRange[0] = 0.0;
610 0 : adfValidRange[1] = 0.0;
611 : }
612 : }
613 :
614 : // Special For Byte Bands: check for signed/unsigned byte.
615 486 : if (nc_datatype == NC_BYTE)
616 : {
617 : // netcdf uses signed byte by default, but GDAL uses unsigned by default
618 : // This may cause unexpected results, but is needed for back-compat.
619 149 : if (poNCDFDS->bIsGdalFile)
620 127 : bSignedData = false;
621 : else
622 22 : bSignedData = true;
623 :
624 : // For NC4 format NC_BYTE is (normally) signed, NC_UBYTE is unsigned.
625 : // But in case a NC3 file was converted automatically and has hints
626 : // that it is unsigned, take them into account
627 149 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
628 : {
629 3 : bSignedData = true;
630 : }
631 :
632 : // If we got valid_range, test for signed/unsigned range.
633 : // https://docs.unidata.ucar.edu/netcdf-c/current/attribute_conventions.html
634 149 : if (bValidRangeValid)
635 : {
636 : // If we got valid_range={0,255}, treat as unsigned.
637 130 : if (adfValidRange[0] == 0 && adfValidRange[1] == 255)
638 : {
639 122 : bSignedData = false;
640 : // Reset valid_range.
641 122 : bValidRangeValid = false;
642 : }
643 : // If we got valid_range={-128,127}, treat as signed.
644 8 : else if (adfValidRange[0] == -128 && adfValidRange[1] == 127)
645 : {
646 8 : bSignedData = true;
647 : // Reset valid_range.
648 8 : bValidRangeValid = false;
649 : }
650 : }
651 : // Else test for _Unsigned.
652 : // https://docs.unidata.ucar.edu/nug/current/best_practices.html
653 : else
654 : {
655 19 : if (bHasUnderscoreUnsignedAttr)
656 7 : bSignedData = !bUnderscoreUnsignedAttrVal;
657 : }
658 :
659 149 : if (bSignedData)
660 : {
661 20 : eDataType = GDT_Int8;
662 : }
663 129 : else if (dfNoData < 0)
664 : {
665 : // Fix nodata value as it was stored signed.
666 6 : dfNoData += 256;
667 6 : if (pszNoValueName)
668 : {
669 : // Updating metadata item
670 6 : GDALPamRasterBand::SetMetadataItem(
671 : pszNoValueName,
672 : CPLSPrintf("%d", static_cast<int>(dfNoData)));
673 : }
674 : }
675 : }
676 337 : else if (nc_datatype == NC_SHORT)
677 : {
678 41 : if (bHasUnderscoreUnsignedAttr)
679 : {
680 4 : bSignedData = !bUnderscoreUnsignedAttrVal;
681 4 : if (!bSignedData)
682 4 : eDataType = GDT_UInt16;
683 : }
684 :
685 : // Fix nodata value as it was stored signed.
686 41 : if (!bSignedData && dfNoData < 0)
687 : {
688 4 : dfNoData += 65536;
689 4 : if (pszNoValueName)
690 : {
691 : // Updating metadata item
692 4 : GDALPamRasterBand::SetMetadataItem(
693 : pszNoValueName,
694 : CPLSPrintf("%d", static_cast<int>(dfNoData)));
695 : }
696 : }
697 : }
698 :
699 296 : else if (nc_datatype == NC_UBYTE || nc_datatype == NC_USHORT ||
700 276 : nc_datatype == NC_UINT || nc_datatype == NC_UINT64)
701 : {
702 31 : bSignedData = false;
703 : }
704 :
705 486 : CPLDebug("GDAL_netCDF", "netcdf type=%d gdal type=%d signedByte=%d",
706 486 : nc_datatype, eDataType, static_cast<int>(bSignedData));
707 :
708 486 : if (bGotNoData)
709 : {
710 : // Set nodata value.
711 339 : if (bGotNoDataAsInt64)
712 : {
713 8 : if (eDataType == GDT_Int64)
714 : {
715 8 : SetNoDataValueNoUpdate(nNoDataAsInt64);
716 : }
717 0 : else if (eDataType == GDT_UInt64 && nNoDataAsInt64 >= 0)
718 : {
719 0 : SetNoDataValueNoUpdate(static_cast<uint64_t>(nNoDataAsInt64));
720 : }
721 : else
722 : {
723 0 : SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsInt64));
724 : }
725 : }
726 331 : else if (bGotNoDataAsUInt64)
727 : {
728 7 : if (eDataType == GDT_UInt64)
729 : {
730 7 : SetNoDataValueNoUpdate(nNoDataAsUInt64);
731 : }
732 0 : else if (eDataType == GDT_Int64 &&
733 : nNoDataAsUInt64 <=
734 0 : static_cast<uint64_t>(
735 0 : std::numeric_limits<int64_t>::max()))
736 : {
737 0 : SetNoDataValueNoUpdate(static_cast<int64_t>(nNoDataAsUInt64));
738 : }
739 : else
740 : {
741 0 : SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsUInt64));
742 : }
743 : }
744 : else
745 : {
746 : #ifdef NCDF_DEBUG
747 : CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) read", dfNoData);
748 : #endif
749 324 : if (eDataType == GDT_Int64 && GDALIsValueExactAs<int64_t>(dfNoData))
750 : {
751 0 : SetNoDataValueNoUpdate(static_cast<int64_t>(dfNoData));
752 : }
753 324 : else if (eDataType == GDT_UInt64 &&
754 0 : GDALIsValueExactAs<uint64_t>(dfNoData))
755 : {
756 0 : SetNoDataValueNoUpdate(static_cast<uint64_t>(dfNoData));
757 : }
758 : else
759 : {
760 324 : SetNoDataValueNoUpdate(dfNoData);
761 : }
762 : }
763 : }
764 :
765 486 : CreateMetadataFromAttributes();
766 :
767 : // Attempt to fetch the scale_factor and add_offset attributes for the
768 : // variable and set them. If these values are not available, set
769 : // offset to 0 and scale to 1.
770 486 : if (nc_inq_attid(cdfid, nZId, CF_ADD_OFFSET, nullptr) == NC_NOERR)
771 : {
772 16 : double dfOffset = 0;
773 16 : status = nc_get_att_double(cdfid, nZId, CF_ADD_OFFSET, &dfOffset);
774 16 : CPLDebug("GDAL_netCDF", "got add_offset=%.16g, status=%d", dfOffset,
775 : status);
776 16 : SetOffsetNoUpdate(dfOffset);
777 : }
778 :
779 486 : bool bHasScale = false;
780 486 : if (nc_inq_attid(cdfid, nZId, CF_SCALE_FACTOR, nullptr) == NC_NOERR)
781 : {
782 20 : bHasScale = true;
783 20 : double dfScale = 1;
784 20 : status = nc_get_att_double(cdfid, nZId, CF_SCALE_FACTOR, &dfScale);
785 20 : CPLDebug("GDAL_netCDF", "got scale_factor=%.16g, status=%d", dfScale,
786 : status);
787 20 : SetScaleNoUpdate(dfScale);
788 : }
789 :
790 12 : if (bValidRangeValid && GDALDataTypeIsInteger(eDataType) &&
791 4 : eDataType != GDT_Int64 && eDataType != GDT_UInt64 &&
792 4 : (std::fabs(std::round(adfValidRange[0]) - adfValidRange[0]) > 1e-5 ||
793 498 : std::fabs(std::round(adfValidRange[1]) - adfValidRange[1]) > 1e-5) &&
794 1 : CSLFetchNameValue(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE") ==
795 : nullptr)
796 : {
797 1 : CPLError(CE_Warning, CPLE_AppDefined,
798 : "validity range = %f, %f contains floating-point values, "
799 : "whereas data type is integer. valid_range is thus likely "
800 : "wrong%s. Ignoring it.",
801 : adfValidRange[0], adfValidRange[1],
802 : bHasScale ? " (likely scaled using scale_factor/add_factor "
803 : "whereas it should be using the packed data type)"
804 : : "");
805 1 : bValidRangeValid = false;
806 1 : adfValidRange[0] = 0.0;
807 1 : adfValidRange[1] = 0.0;
808 : }
809 :
810 : // Should we check for longitude values > 360?
811 486 : bCheckLongitude =
812 972 : CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")) &&
813 486 : NCDFIsVarLongitude(cdfid, nZId, nullptr);
814 :
815 : // Attempt to fetch the units attribute for the variable and set it.
816 486 : SetUnitTypeNoUpdate(netCDFRasterBand::GetMetadataItem(CF_UNITS));
817 :
818 486 : SetBlockSize();
819 : }
820 :
821 670 : void netCDFRasterBand::SetBlockSize()
822 : {
823 : // Check for variable chunking (netcdf-4 only).
824 : // GDAL block size should be set to hdf5 chunk size.
825 670 : int nTmpFormat = 0;
826 670 : int status = nc_inq_format(cdfid, &nTmpFormat);
827 670 : NetCDFFormatEnum eTmpFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
828 670 : if ((status == NC_NOERR) &&
829 571 : (eTmpFormat == NCDF_FORMAT_NC4 || eTmpFormat == NCDF_FORMAT_NC4C))
830 : {
831 115 : size_t chunksize[MAX_NC_DIMS] = {};
832 : // Check for chunksize and set it as the blocksize (optimizes read).
833 115 : status = nc_inq_var_chunking(cdfid, nZId, &nTmpFormat, chunksize);
834 115 : if ((status == NC_NOERR) && (nTmpFormat == NC_CHUNKED))
835 : {
836 14 : nBlockXSize = (int)chunksize[nZDim - 1];
837 14 : if (nZDim >= 2)
838 14 : nBlockYSize = (int)chunksize[nZDim - 2];
839 : else
840 0 : nBlockYSize = 1;
841 : }
842 : }
843 :
844 : // Deal with bottom-up datasets and nBlockYSize != 1.
845 670 : auto poGDS = cpl::down_cast<netCDFDataset *>(poDS);
846 670 : if (poGDS->bBottomUp && nBlockYSize != 1 && poGDS->poChunkCache == nullptr)
847 : {
848 6 : if (poGDS->eAccess == GA_ReadOnly)
849 : {
850 : // Try to cache 1 or 2 'rows' of netCDF chunks along the whole
851 : // width of the raster
852 6 : size_t nChunks =
853 6 : static_cast<size_t>(DIV_ROUND_UP(nRasterXSize, nBlockXSize));
854 6 : if ((nRasterYSize % nBlockYSize) != 0)
855 2 : nChunks *= 2;
856 : const size_t nChunkSize =
857 6 : static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
858 6 : nBlockXSize * nBlockYSize;
859 6 : constexpr size_t MAX_CACHE_SIZE = 100 * 1024 * 1024;
860 6 : nChunks = std::min(nChunks, MAX_CACHE_SIZE / nChunkSize);
861 6 : if (nChunks)
862 : {
863 6 : poGDS->poChunkCache.reset(
864 6 : new netCDFDataset::ChunkCacheType(nChunks));
865 : }
866 : }
867 : else
868 : {
869 0 : nBlockYSize = 1;
870 : }
871 : }
872 670 : }
873 :
874 : // Constructor in create mode.
875 : // If nZId and following variables are not passed, the band will have 2
876 : // dimensions.
877 : // TODO: Get metadata, missing val from band #1 if nZDim > 2.
878 184 : netCDFRasterBand::netCDFRasterBand(
879 : const netCDFRasterBand::CONSTRUCTOR_CREATE &, netCDFDataset *poNCDFDS,
880 : const GDALDataType eTypeIn, int nBandIn, bool bSigned,
881 : const char *pszBandName, const char *pszLongName, int nZIdIn, int nZDimIn,
882 : int nLevelIn, const int *panBandZLevIn, const int *panBandZPosIn,
883 184 : const int *paDimIds)
884 184 : : nc_datatype(NC_NAT), cdfid(poNCDFDS->GetCDFID()), nZId(nZIdIn),
885 : nZDim(nZDimIn), nLevel(nLevelIn), nBandXPos(1), nBandYPos(0),
886 : panBandZPos(nullptr), panBandZLev(nullptr), bSignedData(bSigned),
887 184 : bCheckLongitude(false), m_bCreateMetadataFromOtherVarsDone(true)
888 : {
889 184 : poDS = poNCDFDS;
890 184 : nBand = nBandIn;
891 :
892 184 : nRasterXSize = poDS->GetRasterXSize();
893 184 : nRasterYSize = poDS->GetRasterYSize();
894 184 : nBlockXSize = poDS->GetRasterXSize();
895 184 : nBlockYSize = 1;
896 :
897 184 : if (poDS->GetAccess() != GA_Update)
898 : {
899 0 : CPLError(CE_Failure, CPLE_NotSupported,
900 : "Dataset is not in update mode, "
901 : "wrong netCDFRasterBand constructor");
902 0 : return;
903 : }
904 :
905 : // Take care of all other dimensions.
906 184 : if (nZDim > 2 && paDimIds != nullptr)
907 : {
908 27 : nBandXPos = panBandZPosIn[0];
909 27 : nBandYPos = panBandZPosIn[1];
910 :
911 27 : panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
912 27 : panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
913 :
914 76 : for (int i = 0; i < nZDim - 2; i++)
915 : {
916 49 : panBandZPos[i] = panBandZPosIn[i + 2];
917 49 : panBandZLev[i] = panBandZLevIn[i];
918 : }
919 : }
920 :
921 : // Get the type of the "z" variable, our target raster array.
922 184 : eDataType = eTypeIn;
923 :
924 184 : switch (eDataType)
925 : {
926 79 : case GDT_UInt8:
927 79 : nc_datatype = NC_BYTE;
928 : // NC_UBYTE (unsigned byte) is only available for NC4.
929 79 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
930 3 : nc_datatype = NC_UBYTE;
931 79 : break;
932 7 : case GDT_Int8:
933 7 : nc_datatype = NC_BYTE;
934 7 : break;
935 11 : case GDT_Int16:
936 11 : nc_datatype = NC_SHORT;
937 11 : break;
938 24 : case GDT_Int32:
939 24 : nc_datatype = NC_INT;
940 24 : break;
941 13 : case GDT_Float32:
942 13 : nc_datatype = NC_FLOAT;
943 13 : break;
944 8 : case GDT_Float64:
945 8 : nc_datatype = NC_DOUBLE;
946 8 : break;
947 7 : case GDT_Int64:
948 7 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
949 : {
950 7 : nc_datatype = NC_INT64;
951 : }
952 : else
953 : {
954 0 : if (nBand == 1)
955 0 : CPLError(
956 : CE_Warning, CPLE_AppDefined,
957 : "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
958 : "Int64");
959 0 : nc_datatype = NC_DOUBLE;
960 0 : eDataType = GDT_Float64;
961 : }
962 7 : break;
963 7 : case GDT_UInt64:
964 7 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
965 : {
966 7 : nc_datatype = NC_UINT64;
967 : }
968 : else
969 : {
970 0 : if (nBand == 1)
971 0 : CPLError(
972 : CE_Warning, CPLE_AppDefined,
973 : "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
974 : "UInt64");
975 0 : nc_datatype = NC_DOUBLE;
976 0 : eDataType = GDT_Float64;
977 : }
978 7 : break;
979 6 : case GDT_UInt16:
980 6 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
981 : {
982 6 : nc_datatype = NC_USHORT;
983 6 : break;
984 : }
985 : [[fallthrough]];
986 : case GDT_UInt32:
987 6 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
988 : {
989 6 : nc_datatype = NC_UINT;
990 6 : break;
991 : }
992 : [[fallthrough]];
993 : default:
994 16 : if (nBand == 1)
995 8 : CPLError(CE_Warning, CPLE_AppDefined,
996 : "Unsupported GDAL datatype (%d), treat as NC_FLOAT.",
997 8 : static_cast<int>(eDataType));
998 16 : nc_datatype = NC_FLOAT;
999 16 : eDataType = GDT_Float32;
1000 16 : break;
1001 : }
1002 :
1003 : // Define the variable if necessary (if nZId == -1).
1004 184 : bool bDefineVar = false;
1005 :
1006 184 : if (nZId == -1)
1007 : {
1008 162 : bDefineVar = true;
1009 :
1010 : // Make sure we are in define mode.
1011 162 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1012 :
1013 : char szTempPrivate[256 + 1];
1014 162 : const char *pszTemp = nullptr;
1015 162 : if (!pszBandName || EQUAL(pszBandName, ""))
1016 : {
1017 140 : snprintf(szTempPrivate, sizeof(szTempPrivate), "Band%d", nBand);
1018 140 : pszTemp = szTempPrivate;
1019 : }
1020 : else
1021 : {
1022 22 : pszTemp = pszBandName;
1023 : }
1024 :
1025 : int status;
1026 162 : if (nZDim > 2 && paDimIds != nullptr)
1027 : {
1028 5 : status =
1029 5 : nc_def_var(cdfid, pszTemp, nc_datatype, nZDim, paDimIds, &nZId);
1030 : }
1031 : else
1032 : {
1033 157 : int anBandDims[2] = {poNCDFDS->nYDimID, poNCDFDS->nXDimID};
1034 : status =
1035 157 : nc_def_var(cdfid, pszTemp, nc_datatype, 2, anBandDims, &nZId);
1036 : }
1037 162 : NCDF_ERR(status);
1038 162 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d) id=%d", cdfid, pszTemp,
1039 : nc_datatype, nZId);
1040 :
1041 162 : if (!pszLongName || EQUAL(pszLongName, ""))
1042 : {
1043 155 : snprintf(szTempPrivate, sizeof(szTempPrivate),
1044 : "GDAL Band Number %d", nBand);
1045 155 : pszTemp = szTempPrivate;
1046 : }
1047 : else
1048 : {
1049 7 : pszTemp = pszLongName;
1050 : }
1051 : status =
1052 162 : nc_put_att_text(cdfid, nZId, CF_LNG_NAME, strlen(pszTemp), pszTemp);
1053 162 : NCDF_ERR(status);
1054 :
1055 162 : poNCDFDS->DefVarDeflate(nZId, true);
1056 : }
1057 :
1058 : // For Byte data add signed/unsigned info.
1059 184 : if (eDataType == GDT_UInt8 || eDataType == GDT_Int8)
1060 : {
1061 86 : if (bDefineVar)
1062 : {
1063 : // Only add attributes if creating variable.
1064 : // For unsigned NC_BYTE (except NC4 format),
1065 : // add valid_range and _Unsigned ( defined in CF-1 and NUG ).
1066 78 : if (nc_datatype == NC_BYTE && poNCDFDS->eFormat != NCDF_FORMAT_NC4)
1067 : {
1068 75 : CPLDebug("GDAL_netCDF",
1069 : "adding valid_range attributes for Byte Band");
1070 75 : short l_adfValidRange[2] = {0, 0};
1071 : int status;
1072 75 : if (bSignedData || eDataType == GDT_Int8)
1073 : {
1074 7 : l_adfValidRange[0] = -128;
1075 7 : l_adfValidRange[1] = 127;
1076 7 : status =
1077 7 : nc_put_att_text(cdfid, nZId, "_Unsigned", 5, "false");
1078 : }
1079 : else
1080 : {
1081 68 : l_adfValidRange[0] = 0;
1082 68 : l_adfValidRange[1] = 255;
1083 : status =
1084 68 : nc_put_att_text(cdfid, nZId, "_Unsigned", 4, "true");
1085 : }
1086 75 : NCDF_ERR(status);
1087 75 : status = nc_put_att_short(cdfid, nZId, "valid_range", NC_SHORT,
1088 : 2, l_adfValidRange);
1089 75 : NCDF_ERR(status);
1090 : }
1091 : }
1092 : }
1093 :
1094 184 : if (nc_datatype != NC_BYTE && nc_datatype != NC_CHAR &&
1095 101 : nc_datatype != NC_UBYTE)
1096 : {
1097 : // Set default nodata.
1098 98 : bool bIgnored = false;
1099 : double dfNoData =
1100 98 : NCDFGetDefaultNoDataValue(cdfid, nZId, nc_datatype, bIgnored);
1101 : #ifdef NCDF_DEBUG
1102 : CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) default", dfNoData);
1103 : #endif
1104 98 : netCDFRasterBand::SetNoDataValue(dfNoData);
1105 : }
1106 :
1107 184 : SetBlockSize();
1108 : }
1109 :
1110 : /************************************************************************/
1111 : /* ~netCDFRasterBand() */
1112 : /************************************************************************/
1113 :
1114 1340 : netCDFRasterBand::~netCDFRasterBand()
1115 : {
1116 670 : netCDFRasterBand::FlushCache(true);
1117 670 : CPLFree(panBandZPos);
1118 670 : CPLFree(panBandZLev);
1119 1340 : }
1120 :
1121 : /************************************************************************/
1122 : /* GetMetadata() */
1123 : /************************************************************************/
1124 :
1125 50 : CSLConstList netCDFRasterBand::GetMetadata(const char *pszDomain)
1126 : {
1127 50 : if (!m_bCreateMetadataFromOtherVarsDone)
1128 48 : CreateMetadataFromOtherVars();
1129 50 : return GDALPamRasterBand::GetMetadata(pszDomain);
1130 : }
1131 :
1132 : /************************************************************************/
1133 : /* GetMetadataItem() */
1134 : /************************************************************************/
1135 :
1136 560 : const char *netCDFRasterBand::GetMetadataItem(const char *pszName,
1137 : const char *pszDomain)
1138 : {
1139 560 : if (!m_bCreateMetadataFromOtherVarsDone &&
1140 544 : STARTS_WITH(pszName, "NETCDF_DIM_") &&
1141 1 : (!pszDomain || pszDomain[0] == 0))
1142 1 : CreateMetadataFromOtherVars();
1143 560 : return GDALPamRasterBand::GetMetadataItem(pszName, pszDomain);
1144 : }
1145 :
1146 : /************************************************************************/
1147 : /* SetMetadataItem() */
1148 : /************************************************************************/
1149 :
1150 7 : CPLErr netCDFRasterBand::SetMetadataItem(const char *pszName,
1151 : const char *pszValue,
1152 : const char *pszDomain)
1153 : {
1154 9 : if (GetAccess() == GA_Update &&
1155 9 : (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)
1156 : {
1157 : // Same logic as in CopyMetadata()
1158 :
1159 2 : const char *const papszIgnoreBand[] = {
1160 : CF_ADD_OFFSET, CF_SCALE_FACTOR, "valid_range", "_Unsigned",
1161 : NCDF_FillValue, "coordinates", nullptr};
1162 : // Do not copy varname, stats, NETCDF_DIM_*, nodata
1163 : // and items in papszIgnoreBand.
1164 6 : if (STARTS_WITH(pszName, "NETCDF_VARNAME") ||
1165 2 : STARTS_WITH(pszName, "STATISTICS_") ||
1166 2 : STARTS_WITH(pszName, "NETCDF_DIM_") ||
1167 2 : STARTS_WITH(pszName, "missing_value") ||
1168 6 : STARTS_WITH(pszName, "_FillValue") ||
1169 2 : CSLFindString(papszIgnoreBand, pszName) != -1)
1170 : {
1171 : // do nothing
1172 : }
1173 : else
1174 : {
1175 2 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1176 :
1177 2 : if (!NCDFPutAttr(cdfid, nZId, pszName, pszValue))
1178 2 : return CE_Failure;
1179 : }
1180 : }
1181 :
1182 5 : return GDALPamRasterBand::SetMetadataItem(pszName, pszValue, pszDomain);
1183 : }
1184 :
1185 : /************************************************************************/
1186 : /* SetMetadata() */
1187 : /************************************************************************/
1188 :
1189 2 : CPLErr netCDFRasterBand::SetMetadata(CSLConstList papszMD,
1190 : const char *pszDomain)
1191 : {
1192 4 : if (GetAccess() == GA_Update &&
1193 2 : (pszDomain == nullptr || pszDomain[0] == '\0'))
1194 : {
1195 : // We don't handle metadata item removal for now
1196 4 : for (const char *const *papszIter = papszMD; papszIter && *papszIter;
1197 : ++papszIter)
1198 : {
1199 2 : char *pszName = nullptr;
1200 2 : const char *pszValue = CPLParseNameValue(*papszIter, &pszName);
1201 2 : if (pszName && pszValue)
1202 2 : SetMetadataItem(pszName, pszValue);
1203 2 : CPLFree(pszName);
1204 : }
1205 : }
1206 2 : return GDALPamRasterBand::SetMetadata(papszMD, pszDomain);
1207 : }
1208 :
1209 : /************************************************************************/
1210 : /* GetOffset() */
1211 : /************************************************************************/
1212 50 : double netCDFRasterBand::GetOffset(int *pbSuccess)
1213 : {
1214 50 : if (pbSuccess != nullptr)
1215 45 : *pbSuccess = static_cast<int>(m_bHaveOffset);
1216 :
1217 50 : return m_dfOffset;
1218 : }
1219 :
1220 : /************************************************************************/
1221 : /* SetOffset() */
1222 : /************************************************************************/
1223 1 : CPLErr netCDFRasterBand::SetOffset(double dfNewOffset)
1224 : {
1225 2 : CPLMutexHolderD(&hNCMutex);
1226 :
1227 : // Write value if in update mode.
1228 1 : if (poDS->GetAccess() == GA_Update)
1229 : {
1230 : // Make sure we are in define mode.
1231 1 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1232 :
1233 1 : const int status = nc_put_att_double(cdfid, nZId, CF_ADD_OFFSET,
1234 : NC_DOUBLE, 1, &dfNewOffset);
1235 :
1236 1 : NCDF_ERR(status);
1237 1 : if (status == NC_NOERR)
1238 : {
1239 1 : SetOffsetNoUpdate(dfNewOffset);
1240 1 : return CE_None;
1241 : }
1242 :
1243 0 : return CE_Failure;
1244 : }
1245 :
1246 0 : SetOffsetNoUpdate(dfNewOffset);
1247 0 : return CE_None;
1248 : }
1249 :
1250 : /************************************************************************/
1251 : /* SetOffsetNoUpdate() */
1252 : /************************************************************************/
1253 17 : void netCDFRasterBand::SetOffsetNoUpdate(double dfVal)
1254 : {
1255 17 : m_dfOffset = dfVal;
1256 17 : m_bHaveOffset = true;
1257 17 : }
1258 :
1259 : /************************************************************************/
1260 : /* GetScale() */
1261 : /************************************************************************/
1262 50 : double netCDFRasterBand::GetScale(int *pbSuccess)
1263 : {
1264 50 : if (pbSuccess != nullptr)
1265 45 : *pbSuccess = static_cast<int>(m_bHaveScale);
1266 :
1267 50 : return m_dfScale;
1268 : }
1269 :
1270 : /************************************************************************/
1271 : /* SetScale() */
1272 : /************************************************************************/
1273 1 : CPLErr netCDFRasterBand::SetScale(double dfNewScale)
1274 : {
1275 2 : CPLMutexHolderD(&hNCMutex);
1276 :
1277 : // Write value if in update mode.
1278 1 : if (poDS->GetAccess() == GA_Update)
1279 : {
1280 : // Make sure we are in define mode.
1281 1 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1282 :
1283 1 : const int status = nc_put_att_double(cdfid, nZId, CF_SCALE_FACTOR,
1284 : NC_DOUBLE, 1, &dfNewScale);
1285 :
1286 1 : NCDF_ERR(status);
1287 1 : if (status == NC_NOERR)
1288 : {
1289 1 : SetScaleNoUpdate(dfNewScale);
1290 1 : return CE_None;
1291 : }
1292 :
1293 0 : return CE_Failure;
1294 : }
1295 :
1296 0 : SetScaleNoUpdate(dfNewScale);
1297 0 : return CE_None;
1298 : }
1299 :
1300 : /************************************************************************/
1301 : /* SetScaleNoUpdate() */
1302 : /************************************************************************/
1303 21 : void netCDFRasterBand::SetScaleNoUpdate(double dfVal)
1304 : {
1305 21 : m_dfScale = dfVal;
1306 21 : m_bHaveScale = true;
1307 21 : }
1308 :
1309 : /************************************************************************/
1310 : /* GetUnitType() */
1311 : /************************************************************************/
1312 :
1313 22 : const char *netCDFRasterBand::GetUnitType()
1314 :
1315 : {
1316 22 : if (!m_osUnitType.empty())
1317 6 : return m_osUnitType;
1318 :
1319 16 : return GDALRasterBand::GetUnitType();
1320 : }
1321 :
1322 : /************************************************************************/
1323 : /* SetUnitType() */
1324 : /************************************************************************/
1325 :
1326 1 : CPLErr netCDFRasterBand::SetUnitType(const char *pszNewValue)
1327 :
1328 : {
1329 2 : CPLMutexHolderD(&hNCMutex);
1330 :
1331 2 : const std::string osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
1332 :
1333 1 : if (!osUnitType.empty())
1334 : {
1335 : // Write value if in update mode.
1336 1 : if (poDS->GetAccess() == GA_Update)
1337 : {
1338 : // Make sure we are in define mode.
1339 1 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(TRUE);
1340 :
1341 1 : const int status = nc_put_att_text(
1342 : cdfid, nZId, CF_UNITS, osUnitType.size(), osUnitType.c_str());
1343 :
1344 1 : NCDF_ERR(status);
1345 1 : if (status == NC_NOERR)
1346 : {
1347 1 : SetUnitTypeNoUpdate(pszNewValue);
1348 1 : return CE_None;
1349 : }
1350 :
1351 0 : return CE_Failure;
1352 : }
1353 : }
1354 :
1355 0 : SetUnitTypeNoUpdate(pszNewValue);
1356 :
1357 0 : return CE_None;
1358 : }
1359 :
1360 : /************************************************************************/
1361 : /* SetUnitTypeNoUpdate() */
1362 : /************************************************************************/
1363 :
1364 487 : void netCDFRasterBand::SetUnitTypeNoUpdate(const char *pszNewValue)
1365 : {
1366 487 : m_osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
1367 487 : }
1368 :
1369 : /************************************************************************/
1370 : /* GetNoDataValue() */
1371 : /************************************************************************/
1372 :
1373 183 : double netCDFRasterBand::GetNoDataValue(int *pbSuccess)
1374 :
1375 : {
1376 183 : if (m_bNoDataSetAsInt64)
1377 : {
1378 0 : if (pbSuccess)
1379 0 : *pbSuccess = TRUE;
1380 0 : return GDALGetNoDataValueCastToDouble(m_nNodataValueInt64);
1381 : }
1382 :
1383 183 : if (m_bNoDataSetAsUInt64)
1384 : {
1385 0 : if (pbSuccess)
1386 0 : *pbSuccess = TRUE;
1387 0 : return GDALGetNoDataValueCastToDouble(m_nNodataValueUInt64);
1388 : }
1389 :
1390 183 : if (m_bNoDataSet)
1391 : {
1392 142 : if (pbSuccess)
1393 125 : *pbSuccess = TRUE;
1394 142 : return m_dfNoDataValue;
1395 : }
1396 :
1397 41 : return GDALPamRasterBand::GetNoDataValue(pbSuccess);
1398 : }
1399 :
1400 : /************************************************************************/
1401 : /* GetNoDataValueAsInt64() */
1402 : /************************************************************************/
1403 :
1404 4 : int64_t netCDFRasterBand::GetNoDataValueAsInt64(int *pbSuccess)
1405 :
1406 : {
1407 4 : if (m_bNoDataSetAsInt64)
1408 : {
1409 4 : if (pbSuccess)
1410 4 : *pbSuccess = TRUE;
1411 :
1412 4 : return m_nNodataValueInt64;
1413 : }
1414 :
1415 0 : return GDALPamRasterBand::GetNoDataValueAsInt64(pbSuccess);
1416 : }
1417 :
1418 : /************************************************************************/
1419 : /* GetNoDataValueAsUInt64() */
1420 : /************************************************************************/
1421 :
1422 4 : uint64_t netCDFRasterBand::GetNoDataValueAsUInt64(int *pbSuccess)
1423 :
1424 : {
1425 4 : if (m_bNoDataSetAsUInt64)
1426 : {
1427 4 : if (pbSuccess)
1428 4 : *pbSuccess = TRUE;
1429 :
1430 4 : return m_nNodataValueUInt64;
1431 : }
1432 :
1433 0 : return GDALPamRasterBand::GetNoDataValueAsUInt64(pbSuccess);
1434 : }
1435 :
1436 : /************************************************************************/
1437 : /* SetNoDataValue() */
1438 : /************************************************************************/
1439 :
1440 134 : CPLErr netCDFRasterBand::SetNoDataValue(double dfNoData)
1441 :
1442 : {
1443 268 : CPLMutexHolderD(&hNCMutex);
1444 :
1445 : // If already set to new value, don't do anything.
1446 134 : if (m_bNoDataSet && CPLIsEqual(dfNoData, m_dfNoDataValue))
1447 19 : return CE_None;
1448 :
1449 : // Write value if in update mode.
1450 115 : if (poDS->GetAccess() == GA_Update)
1451 : {
1452 : // netcdf-4 does not allow to set _FillValue after leaving define mode,
1453 : // but it is ok if variable has not been written to, so only print
1454 : // debug. See bug #4484.
1455 125 : if (m_bNoDataSet &&
1456 10 : !cpl::down_cast<netCDFDataset *>(poDS)->GetDefineMode())
1457 : {
1458 0 : CPLDebug("GDAL_netCDF",
1459 : "Setting NoDataValue to %.17g (previously set to %.17g) "
1460 : "but file is no longer in define mode (id #%d, band #%d)",
1461 : dfNoData, m_dfNoDataValue, cdfid, nBand);
1462 : }
1463 : #ifdef NCDF_DEBUG
1464 : else
1465 : {
1466 : CPLDebug("GDAL_netCDF",
1467 : "Setting NoDataValue to %.17g (id #%d, band #%d)",
1468 : dfNoData, cdfid, nBand);
1469 : }
1470 : #endif
1471 : // Make sure we are in define mode.
1472 115 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1473 :
1474 : int status;
1475 115 : if (eDataType == GDT_UInt8)
1476 : {
1477 6 : if (bSignedData)
1478 : {
1479 0 : signed char cNoDataValue = static_cast<signed char>(dfNoData);
1480 0 : status = nc_put_att_schar(cdfid, nZId, NCDF_FillValue,
1481 : nc_datatype, 1, &cNoDataValue);
1482 : }
1483 : else
1484 : {
1485 6 : const unsigned char ucNoDataValue =
1486 6 : static_cast<unsigned char>(dfNoData);
1487 6 : status = nc_put_att_uchar(cdfid, nZId, NCDF_FillValue,
1488 : nc_datatype, 1, &ucNoDataValue);
1489 : }
1490 : }
1491 109 : else if (eDataType == GDT_Int16)
1492 : {
1493 14 : short nsNoDataValue = static_cast<short>(dfNoData);
1494 14 : status = nc_put_att_short(cdfid, nZId, NCDF_FillValue, nc_datatype,
1495 : 1, &nsNoDataValue);
1496 : }
1497 95 : else if (eDataType == GDT_Int32)
1498 : {
1499 27 : int nNoDataValue = static_cast<int>(dfNoData);
1500 27 : status = nc_put_att_int(cdfid, nZId, NCDF_FillValue, nc_datatype, 1,
1501 : &nNoDataValue);
1502 : }
1503 68 : else if (eDataType == GDT_Float32)
1504 : {
1505 31 : float fNoDataValue = static_cast<float>(dfNoData);
1506 31 : status = nc_put_att_float(cdfid, nZId, NCDF_FillValue, nc_datatype,
1507 : 1, &fNoDataValue);
1508 : }
1509 43 : else if (eDataType == GDT_UInt16 &&
1510 6 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat ==
1511 : NCDF_FORMAT_NC4)
1512 : {
1513 6 : unsigned short usNoDataValue =
1514 6 : static_cast<unsigned short>(dfNoData);
1515 6 : status = nc_put_att_ushort(cdfid, nZId, NCDF_FillValue, nc_datatype,
1516 : 1, &usNoDataValue);
1517 : }
1518 38 : else if (eDataType == GDT_UInt32 &&
1519 7 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat ==
1520 : NCDF_FORMAT_NC4)
1521 : {
1522 7 : unsigned int unNoDataValue = static_cast<unsigned int>(dfNoData);
1523 7 : status = nc_put_att_uint(cdfid, nZId, NCDF_FillValue, nc_datatype,
1524 : 1, &unNoDataValue);
1525 : }
1526 : else
1527 : {
1528 24 : status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
1529 : 1, &dfNoData);
1530 : }
1531 :
1532 115 : NCDF_ERR(status);
1533 :
1534 : // Update status if write worked.
1535 115 : if (status == NC_NOERR)
1536 : {
1537 115 : SetNoDataValueNoUpdate(dfNoData);
1538 115 : return CE_None;
1539 : }
1540 :
1541 0 : return CE_Failure;
1542 : }
1543 :
1544 0 : SetNoDataValueNoUpdate(dfNoData);
1545 0 : return CE_None;
1546 : }
1547 :
1548 : /************************************************************************/
1549 : /* SetNoDataValueNoUpdate() */
1550 : /************************************************************************/
1551 :
1552 439 : void netCDFRasterBand::SetNoDataValueNoUpdate(double dfNoData)
1553 : {
1554 439 : m_dfNoDataValue = dfNoData;
1555 439 : m_bNoDataSet = true;
1556 439 : m_bNoDataSetAsInt64 = false;
1557 439 : m_bNoDataSetAsUInt64 = false;
1558 439 : }
1559 :
1560 : /************************************************************************/
1561 : /* SetNoDataValueAsInt64() */
1562 : /************************************************************************/
1563 :
1564 3 : CPLErr netCDFRasterBand::SetNoDataValueAsInt64(int64_t nNoData)
1565 :
1566 : {
1567 6 : CPLMutexHolderD(&hNCMutex);
1568 :
1569 : // If already set to new value, don't do anything.
1570 3 : if (m_bNoDataSetAsInt64 && nNoData == m_nNodataValueInt64)
1571 0 : return CE_None;
1572 :
1573 : // Write value if in update mode.
1574 3 : if (poDS->GetAccess() == GA_Update)
1575 : {
1576 : // netcdf-4 does not allow to set NCDF_FillValue after leaving define mode,
1577 : // but it is ok if variable has not been written to, so only print
1578 : // debug. See bug #4484.
1579 3 : if (m_bNoDataSetAsInt64 &&
1580 0 : !cpl::down_cast<netCDFDataset *>(poDS)->GetDefineMode())
1581 : {
1582 0 : CPLDebug("GDAL_netCDF",
1583 : "Setting NoDataValue to " CPL_FRMT_GIB
1584 : " (previously set to " CPL_FRMT_GIB ") "
1585 : "but file is no longer in define mode (id #%d, band #%d)",
1586 : static_cast<GIntBig>(nNoData),
1587 0 : static_cast<GIntBig>(m_nNodataValueInt64), cdfid, nBand);
1588 : }
1589 : #ifdef NCDF_DEBUG
1590 : else
1591 : {
1592 : CPLDebug("GDAL_netCDF",
1593 : "Setting NoDataValue to " CPL_FRMT_GIB
1594 : " (id #%d, band #%d)",
1595 : static_cast<GIntBig>(nNoData), cdfid, nBand);
1596 : }
1597 : #endif
1598 : // Make sure we are in define mode.
1599 3 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1600 :
1601 : int status;
1602 6 : if (eDataType == GDT_Int64 &&
1603 3 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
1604 : {
1605 3 : long long tmp = static_cast<long long>(nNoData);
1606 3 : status = nc_put_att_longlong(cdfid, nZId, NCDF_FillValue,
1607 : nc_datatype, 1, &tmp);
1608 : }
1609 : else
1610 : {
1611 0 : double dfNoData = static_cast<double>(nNoData);
1612 0 : status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
1613 : 1, &dfNoData);
1614 : }
1615 :
1616 3 : NCDF_ERR(status);
1617 :
1618 : // Update status if write worked.
1619 3 : if (status == NC_NOERR)
1620 : {
1621 3 : SetNoDataValueNoUpdate(nNoData);
1622 3 : return CE_None;
1623 : }
1624 :
1625 0 : return CE_Failure;
1626 : }
1627 :
1628 0 : SetNoDataValueNoUpdate(nNoData);
1629 0 : return CE_None;
1630 : }
1631 :
1632 : /************************************************************************/
1633 : /* SetNoDataValueNoUpdate() */
1634 : /************************************************************************/
1635 :
1636 11 : void netCDFRasterBand::SetNoDataValueNoUpdate(int64_t nNoData)
1637 : {
1638 11 : m_nNodataValueInt64 = nNoData;
1639 11 : m_bNoDataSet = false;
1640 11 : m_bNoDataSetAsInt64 = true;
1641 11 : m_bNoDataSetAsUInt64 = false;
1642 11 : }
1643 :
1644 : /************************************************************************/
1645 : /* SetNoDataValueAsUInt64() */
1646 : /************************************************************************/
1647 :
1648 3 : CPLErr netCDFRasterBand::SetNoDataValueAsUInt64(uint64_t nNoData)
1649 :
1650 : {
1651 6 : CPLMutexHolderD(&hNCMutex);
1652 :
1653 : // If already set to new value, don't do anything.
1654 3 : if (m_bNoDataSetAsUInt64 && nNoData == m_nNodataValueUInt64)
1655 0 : return CE_None;
1656 :
1657 : // Write value if in update mode.
1658 3 : if (poDS->GetAccess() == GA_Update)
1659 : {
1660 : // netcdf-4 does not allow to set _FillValue after leaving define mode,
1661 : // but it is ok if variable has not been written to, so only print
1662 : // debug. See bug #4484.
1663 3 : if (m_bNoDataSetAsUInt64 &&
1664 0 : !cpl::down_cast<netCDFDataset *>(poDS)->GetDefineMode())
1665 : {
1666 0 : CPLDebug("GDAL_netCDF",
1667 : "Setting NoDataValue to " CPL_FRMT_GUIB
1668 : " (previously set to " CPL_FRMT_GUIB ") "
1669 : "but file is no longer in define mode (id #%d, band #%d)",
1670 : static_cast<GUIntBig>(nNoData),
1671 0 : static_cast<GUIntBig>(m_nNodataValueUInt64), cdfid, nBand);
1672 : }
1673 : #ifdef NCDF_DEBUG
1674 : else
1675 : {
1676 : CPLDebug("GDAL_netCDF",
1677 : "Setting NoDataValue to " CPL_FRMT_GUIB
1678 : " (id #%d, band #%d)",
1679 : static_cast<GUIntBig>(nNoData), cdfid, nBand);
1680 : }
1681 : #endif
1682 : // Make sure we are in define mode.
1683 3 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1684 :
1685 : int status;
1686 6 : if (eDataType == GDT_UInt64 &&
1687 3 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
1688 : {
1689 3 : unsigned long long tmp = static_cast<long long>(nNoData);
1690 3 : status = nc_put_att_ulonglong(cdfid, nZId, NCDF_FillValue,
1691 : nc_datatype, 1, &tmp);
1692 : }
1693 : else
1694 : {
1695 0 : double dfNoData = static_cast<double>(nNoData);
1696 0 : status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
1697 : 1, &dfNoData);
1698 : }
1699 :
1700 3 : NCDF_ERR(status);
1701 :
1702 : // Update status if write worked.
1703 3 : if (status == NC_NOERR)
1704 : {
1705 3 : SetNoDataValueNoUpdate(nNoData);
1706 3 : return CE_None;
1707 : }
1708 :
1709 0 : return CE_Failure;
1710 : }
1711 :
1712 0 : SetNoDataValueNoUpdate(nNoData);
1713 0 : return CE_None;
1714 : }
1715 :
1716 : /************************************************************************/
1717 : /* SetNoDataValueNoUpdate() */
1718 : /************************************************************************/
1719 :
1720 10 : void netCDFRasterBand::SetNoDataValueNoUpdate(uint64_t nNoData)
1721 : {
1722 10 : m_nNodataValueUInt64 = nNoData;
1723 10 : m_bNoDataSet = false;
1724 10 : m_bNoDataSetAsInt64 = false;
1725 10 : m_bNoDataSetAsUInt64 = true;
1726 10 : }
1727 :
1728 : /************************************************************************/
1729 : /* DeleteNoDataValue() */
1730 : /************************************************************************/
1731 :
1732 : #ifdef notdef
1733 : CPLErr netCDFRasterBand::DeleteNoDataValue()
1734 :
1735 : {
1736 : CPLMutexHolderD(&hNCMutex);
1737 :
1738 : if (!bNoDataSet)
1739 : return CE_None;
1740 :
1741 : // Write value if in update mode.
1742 : if (poDS->GetAccess() == GA_Update)
1743 : {
1744 : // Make sure we are in define mode.
1745 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1746 :
1747 : status = nc_del_att(cdfid, nZId, NCDF_FillValue);
1748 :
1749 : NCDF_ERR(status);
1750 :
1751 : // Update status if write worked.
1752 : if (status == NC_NOERR)
1753 : {
1754 : dfNoDataValue = 0.0;
1755 : bNoDataSet = false;
1756 : return CE_None;
1757 : }
1758 :
1759 : return CE_Failure;
1760 : }
1761 :
1762 : dfNoDataValue = 0.0;
1763 : bNoDataSet = false;
1764 : return CE_None;
1765 : }
1766 : #endif
1767 :
1768 : /************************************************************************/
1769 : /* SerializeToXML() */
1770 : /************************************************************************/
1771 :
1772 5 : CPLXMLNode *netCDFRasterBand::SerializeToXML(const char * /* pszUnused */)
1773 : {
1774 : // Overridden from GDALPamDataset to add only band histogram
1775 : // and statistics. See bug #4244.
1776 5 : if (psPam == nullptr)
1777 0 : return nullptr;
1778 :
1779 : // Setup root node and attributes.
1780 : CPLXMLNode *psTree =
1781 5 : CPLCreateXMLNode(nullptr, CXT_Element, "PAMRasterBand");
1782 :
1783 5 : if (GetBand() > 0)
1784 : {
1785 10 : CPLString oFmt;
1786 5 : CPLSetXMLValue(psTree, "#band", oFmt.Printf("%d", GetBand()));
1787 : }
1788 :
1789 : // Histograms.
1790 5 : if (psPam->psSavedHistograms != nullptr)
1791 1 : CPLAddXMLChild(psTree, CPLCloneXMLTree(psPam->psSavedHistograms));
1792 :
1793 : // Metadata (statistics only).
1794 5 : GDALMultiDomainMetadata oMDMDStats;
1795 5 : const char *papszMDStats[] = {"STATISTICS_MINIMUM", "STATISTICS_MAXIMUM",
1796 : "STATISTICS_MEAN", "STATISTICS_STDDEV",
1797 : nullptr};
1798 25 : for (int i = 0; i < CSLCount(papszMDStats); i++)
1799 : {
1800 20 : const char *pszMDI = GetMetadataItem(papszMDStats[i]);
1801 20 : if (pszMDI)
1802 4 : oMDMDStats.SetMetadataItem(papszMDStats[i], pszMDI);
1803 : }
1804 5 : CPLXMLNode *psMD = oMDMDStats.Serialize();
1805 :
1806 5 : if (psMD != nullptr)
1807 : {
1808 1 : if (psMD->psChild == nullptr)
1809 0 : CPLDestroyXMLNode(psMD);
1810 : else
1811 1 : CPLAddXMLChild(psTree, psMD);
1812 : }
1813 :
1814 : // We don't want to return anything if we had no metadata to attach.
1815 5 : if (psTree->psChild == nullptr || psTree->psChild->psNext == nullptr)
1816 : {
1817 3 : CPLDestroyXMLNode(psTree);
1818 3 : psTree = nullptr;
1819 : }
1820 :
1821 5 : return psTree;
1822 : }
1823 :
1824 : /************************************************************************/
1825 : /* Get1DVariableIndexedByDimension() */
1826 : /************************************************************************/
1827 :
1828 81 : static int Get1DVariableIndexedByDimension(int cdfid, int nDimId,
1829 : const char *pszDimName,
1830 : bool bVerboseError, int *pnGroupID)
1831 : {
1832 81 : *pnGroupID = -1;
1833 81 : int nVarID = -1;
1834 : // First try to find a variable whose name is identical to the dimension
1835 : // name, and check that it is indeed indexed by this dimension
1836 81 : if (NCDFResolveVar(cdfid, pszDimName, pnGroupID, &nVarID) == CE_None)
1837 : {
1838 67 : int nDimCountOfVariable = 0;
1839 67 : nc_inq_varndims(*pnGroupID, nVarID, &nDimCountOfVariable);
1840 67 : if (nDimCountOfVariable == 1)
1841 : {
1842 67 : int nDimIdOfVariable = -1;
1843 67 : nc_inq_vardimid(*pnGroupID, nVarID, &nDimIdOfVariable);
1844 67 : if (nDimIdOfVariable == nDimId)
1845 : {
1846 67 : return nVarID;
1847 : }
1848 : }
1849 : }
1850 :
1851 : // Otherwise iterate over the variables to find potential candidates
1852 : // TODO: should be modified to search also in other groups using the same
1853 : // logic than in NCDFResolveVar(), but maybe not needed if it's a
1854 : // very rare case? and I think this is not CF compliant.
1855 14 : int nvars = 0;
1856 14 : CPL_IGNORE_RET_VAL(nc_inq(cdfid, nullptr, &nvars, nullptr, nullptr));
1857 :
1858 14 : int nCountCandidateVars = 0;
1859 14 : int nCandidateVarID = -1;
1860 65 : for (int k = 0; k < nvars; k++)
1861 : {
1862 51 : int nDimCountOfVariable = 0;
1863 51 : nc_inq_varndims(cdfid, k, &nDimCountOfVariable);
1864 51 : if (nDimCountOfVariable == 1)
1865 : {
1866 27 : int nDimIdOfVariable = -1;
1867 27 : nc_inq_vardimid(cdfid, k, &nDimIdOfVariable);
1868 27 : if (nDimIdOfVariable == nDimId)
1869 : {
1870 7 : nCountCandidateVars++;
1871 7 : nCandidateVarID = k;
1872 : }
1873 : }
1874 : }
1875 14 : if (nCountCandidateVars > 1)
1876 : {
1877 1 : if (bVerboseError)
1878 : {
1879 1 : CPLError(CE_Warning, CPLE_AppDefined,
1880 : "Several 1D variables are indexed by dimension %s",
1881 : pszDimName);
1882 : }
1883 1 : *pnGroupID = -1;
1884 1 : return -1;
1885 : }
1886 13 : else if (nCandidateVarID < 0)
1887 : {
1888 8 : if (bVerboseError)
1889 : {
1890 8 : CPLError(CE_Warning, CPLE_AppDefined,
1891 : "No 1D variable is indexed by dimension %s", pszDimName);
1892 : }
1893 : }
1894 13 : *pnGroupID = cdfid;
1895 13 : return nCandidateVarID;
1896 : }
1897 :
1898 : /************************************************************************/
1899 : /* CreateMetadataFromAttributes() */
1900 : /************************************************************************/
1901 :
1902 486 : void netCDFRasterBand::CreateMetadataFromAttributes()
1903 : {
1904 486 : char szVarName[NC_MAX_NAME + 1] = {};
1905 486 : int status = nc_inq_varname(cdfid, nZId, szVarName);
1906 486 : NCDF_ERR(status);
1907 :
1908 486 : GDALPamRasterBand::SetMetadataItem("NETCDF_VARNAME", szVarName);
1909 :
1910 : // Get attribute metadata.
1911 486 : int nAtt = 0;
1912 486 : NCDF_ERR(nc_inq_varnatts(cdfid, nZId, &nAtt));
1913 :
1914 2060 : for (int i = 0; i < nAtt; i++)
1915 : {
1916 1574 : char szMetaName[NC_MAX_NAME + 1] = {};
1917 1574 : status = nc_inq_attname(cdfid, nZId, i, szMetaName);
1918 1574 : if (status != NC_NOERR)
1919 12 : continue;
1920 :
1921 1574 : if (GDALPamRasterBand::GetMetadataItem(szMetaName) != nullptr)
1922 : {
1923 12 : continue;
1924 : }
1925 :
1926 1562 : char *pszMetaValue = nullptr;
1927 1562 : if (NCDFGetAttr(cdfid, nZId, szMetaName, &pszMetaValue) == CE_None)
1928 : {
1929 1562 : GDALPamRasterBand::SetMetadataItem(szMetaName, pszMetaValue);
1930 : }
1931 : else
1932 : {
1933 0 : CPLDebug("GDAL_netCDF", "invalid Band metadata %s", szMetaName);
1934 : }
1935 :
1936 1562 : if (pszMetaValue)
1937 : {
1938 1562 : CPLFree(pszMetaValue);
1939 1562 : pszMetaValue = nullptr;
1940 : }
1941 : }
1942 486 : }
1943 :
1944 : /************************************************************************/
1945 : /* CreateMetadataFromOtherVars() */
1946 : /************************************************************************/
1947 :
1948 49 : void netCDFRasterBand::CreateMetadataFromOtherVars()
1949 :
1950 : {
1951 49 : CPLAssert(!m_bCreateMetadataFromOtherVarsDone);
1952 49 : m_bCreateMetadataFromOtherVarsDone = true;
1953 :
1954 49 : netCDFDataset *l_poDS = cpl::down_cast<netCDFDataset *>(poDS);
1955 49 : const int nPamFlagsBackup = l_poDS->nPamFlags;
1956 :
1957 : // Compute all dimensions from Band number and save in Metadata.
1958 49 : int nd = 0;
1959 49 : nc_inq_varndims(cdfid, nZId, &nd);
1960 : // Compute multidimention band position.
1961 : //
1962 : // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
1963 : // if Data[2,3,4,x,y]
1964 : //
1965 : // BandPos0 = (nBand) / (3*4)
1966 : // BandPos1 = (nBand - BandPos0*(3*4)) / (4)
1967 : // BandPos2 = (nBand - BandPos0*(3*4)) % (4)
1968 :
1969 49 : int Sum = 1;
1970 49 : if (nd == 3)
1971 : {
1972 5 : Sum *= panBandZLev[0];
1973 : }
1974 :
1975 : // Loop over non-spatial dimensions.
1976 49 : int Taken = 0;
1977 :
1978 89 : for (int i = 0; i < nd - 2; i++)
1979 : {
1980 : int result;
1981 40 : if (i != nd - 2 - 1)
1982 : {
1983 18 : Sum = 1;
1984 37 : for (int j = i + 1; j < nd - 2; j++)
1985 : {
1986 19 : Sum *= panBandZLev[j];
1987 : }
1988 18 : result = static_cast<int>((nLevel - Taken) / Sum);
1989 : }
1990 : else
1991 : {
1992 22 : result = static_cast<int>((nLevel - Taken) % Sum);
1993 : }
1994 :
1995 40 : char szName[NC_MAX_NAME + 1] = {};
1996 40 : snprintf(szName, sizeof(szName), "%s",
1997 40 : l_poDS->papszDimName[l_poDS->m_anDimIds[panBandZPos[i]]]);
1998 :
1999 : char szMetaName[NC_MAX_NAME + 1 + 32];
2000 40 : snprintf(szMetaName, sizeof(szMetaName), "NETCDF_DIM_%s", szName);
2001 :
2002 40 : const int nGroupID = l_poDS->m_anExtraDimGroupIds[i];
2003 40 : const int nVarID = l_poDS->m_anExtraDimVarIds[i];
2004 40 : if (nVarID < 0)
2005 : {
2006 2 : GDALPamRasterBand::SetMetadataItem(szMetaName,
2007 : CPLSPrintf("%d", result + 1));
2008 : }
2009 : else
2010 : {
2011 : // TODO: Make sure all the status checks make sense.
2012 :
2013 38 : nc_type nVarType = NC_NAT;
2014 38 : /* status = */ nc_inq_vartype(nGroupID, nVarID, &nVarType);
2015 :
2016 38 : int nDims = 0;
2017 38 : /* status = */ nc_inq_varndims(nGroupID, nVarID, &nDims);
2018 :
2019 38 : char szMetaTemp[256] = {};
2020 38 : if (nDims == 1)
2021 : {
2022 38 : size_t count[1] = {1};
2023 38 : size_t start[1] = {static_cast<size_t>(result)};
2024 :
2025 38 : switch (nVarType)
2026 : {
2027 0 : case NC_BYTE:
2028 : // TODO: Check for signed/unsigned byte.
2029 : signed char cData;
2030 0 : /* status = */ nc_get_vara_schar(nGroupID, nVarID,
2031 : start, count, &cData);
2032 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", cData);
2033 0 : break;
2034 0 : case NC_SHORT:
2035 : short sData;
2036 0 : /* status = */ nc_get_vara_short(nGroupID, nVarID,
2037 : start, count, &sData);
2038 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", sData);
2039 0 : break;
2040 19 : case NC_INT:
2041 : {
2042 : int nData;
2043 19 : /* status = */ nc_get_vara_int(nGroupID, nVarID, start,
2044 : count, &nData);
2045 19 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", nData);
2046 19 : break;
2047 : }
2048 0 : case NC_FLOAT:
2049 : float fData;
2050 0 : /* status = */ nc_get_vara_float(nGroupID, nVarID,
2051 : start, count, &fData);
2052 0 : CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.8g",
2053 : fData);
2054 0 : break;
2055 18 : case NC_DOUBLE:
2056 : double dfData;
2057 18 : /* status = */ nc_get_vara_double(
2058 : nGroupID, nVarID, start, count, &dfData);
2059 18 : CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.16g",
2060 : dfData);
2061 18 : break;
2062 0 : case NC_UBYTE:
2063 : unsigned char ucData;
2064 0 : /* status = */ nc_get_vara_uchar(nGroupID, nVarID,
2065 : start, count, &ucData);
2066 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", ucData);
2067 0 : break;
2068 0 : case NC_USHORT:
2069 : unsigned short usData;
2070 0 : /* status = */ nc_get_vara_ushort(
2071 : nGroupID, nVarID, start, count, &usData);
2072 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", usData);
2073 0 : break;
2074 0 : case NC_UINT:
2075 : {
2076 : unsigned int unData;
2077 0 : /* status = */ nc_get_vara_uint(nGroupID, nVarID, start,
2078 : count, &unData);
2079 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", unData);
2080 0 : break;
2081 : }
2082 1 : case NC_INT64:
2083 : {
2084 : long long nData;
2085 1 : /* status = */ nc_get_vara_longlong(
2086 : nGroupID, nVarID, start, count, &nData);
2087 1 : snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GIB,
2088 : nData);
2089 1 : break;
2090 : }
2091 0 : case NC_UINT64:
2092 : {
2093 : unsigned long long unData;
2094 0 : /* status = */ nc_get_vara_ulonglong(
2095 : nGroupID, nVarID, start, count, &unData);
2096 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GUIB,
2097 : unData);
2098 0 : break;
2099 : }
2100 0 : default:
2101 0 : CPLDebug("GDAL_netCDF", "invalid dim %s, type=%d",
2102 : szMetaTemp, nVarType);
2103 0 : break;
2104 : }
2105 : }
2106 : else
2107 : {
2108 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", result + 1);
2109 : }
2110 :
2111 : // Save dimension value.
2112 : // NOTE: removed #original_units as not part of CF-1.
2113 :
2114 38 : GDALPamRasterBand::SetMetadataItem(szMetaName, szMetaTemp);
2115 : }
2116 :
2117 : // Avoid int32 overflow. Perhaps something more sensible to do here ?
2118 40 : if (result > 0 && Sum > INT_MAX / result)
2119 0 : break;
2120 40 : if (Taken > INT_MAX - result * Sum)
2121 0 : break;
2122 :
2123 40 : Taken += result * Sum;
2124 : } // End loop non-spatial dimensions.
2125 :
2126 49 : l_poDS->nPamFlags = nPamFlagsBackup;
2127 49 : }
2128 :
2129 : /************************************************************************/
2130 : /* CheckData() */
2131 : /************************************************************************/
2132 : template <class T>
2133 5752 : void netCDFRasterBand::CheckData(void *pImage, void *pImageNC,
2134 : size_t nTmpBlockXSize, size_t nTmpBlockYSize,
2135 : bool bCheckIsNan)
2136 : {
2137 5752 : CPLAssert(pImage != nullptr && pImageNC != nullptr);
2138 :
2139 : // If this block is not a full block (in the x axis), we need to re-arrange
2140 : // the data this is because partial blocks are not arranged the same way in
2141 : // netcdf and gdal.
2142 5752 : if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
2143 : {
2144 6 : T *ptrWrite = static_cast<T *>(pImage);
2145 6 : T *ptrRead = static_cast<T *>(pImageNC);
2146 29 : for (size_t j = 0; j < nTmpBlockYSize;
2147 23 : j++, ptrWrite += nBlockXSize, ptrRead += nTmpBlockXSize)
2148 : {
2149 23 : memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T));
2150 : }
2151 : }
2152 :
2153 : // Is valid data checking needed or requested?
2154 5752 : if (bValidRangeValid || bCheckIsNan)
2155 : {
2156 1265 : T *ptrImage = static_cast<T *>(pImage);
2157 2584 : for (size_t j = 0; j < nTmpBlockYSize; j++)
2158 : {
2159 : // k moves along the gdal block, skipping the out-of-range pixels.
2160 1319 : size_t k = j * nBlockXSize;
2161 96938 : for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
2162 : {
2163 : // Check for nodata and nan.
2164 95619 : if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
2165 6301 : continue;
2166 89318 : if (bCheckIsNan && std::isnan((double)ptrImage[k]))
2167 : {
2168 5737 : ptrImage[k] = (T)m_dfNoDataValue;
2169 5737 : continue;
2170 : }
2171 : // Check for valid_range.
2172 83581 : if (bValidRangeValid)
2173 : {
2174 40986 : if (((adfValidRange[0] != m_dfNoDataValue) &&
2175 40986 : (ptrImage[k] < (T)adfValidRange[0])) ||
2176 40983 : ((adfValidRange[1] != m_dfNoDataValue) &&
2177 40983 : (ptrImage[k] > (T)adfValidRange[1])))
2178 : {
2179 4 : ptrImage[k] = (T)m_dfNoDataValue;
2180 : }
2181 : }
2182 : }
2183 : }
2184 : }
2185 :
2186 : // If minimum longitude is > 180, subtract 360 from all.
2187 : // If not, disable checking for further calls (check just once).
2188 : // Only check first and last block elements since lon must be monotonic.
2189 5752 : const bool bIsSigned = std::numeric_limits<T>::is_signed;
2190 5419 : if (bCheckLongitude && bIsSigned &&
2191 9 : !CPLIsEqual((double)((T *)pImage)[0], m_dfNoDataValue) &&
2192 8 : !CPLIsEqual((double)((T *)pImage)[nTmpBlockXSize - 1],
2193 2714 : m_dfNoDataValue) &&
2194 8 : std::min(((T *)pImage)[0], ((T *)pImage)[nTmpBlockXSize - 1]) > 180.0)
2195 : {
2196 0 : T *ptrImage = static_cast<T *>(pImage);
2197 0 : for (size_t j = 0; j < nTmpBlockYSize; j++)
2198 : {
2199 0 : size_t k = j * nBlockXSize;
2200 0 : for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
2201 : {
2202 0 : if (!CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
2203 0 : ptrImage[k] = static_cast<T>(ptrImage[k] - 360);
2204 : }
2205 : }
2206 : }
2207 : else
2208 : {
2209 5752 : bCheckLongitude = false;
2210 : }
2211 5752 : }
2212 :
2213 : /************************************************************************/
2214 : /* CheckDataCpx() */
2215 : /************************************************************************/
2216 : template <class T>
2217 25 : void netCDFRasterBand::CheckDataCpx(void *pImage, void *pImageNC,
2218 : size_t nTmpBlockXSize,
2219 : size_t nTmpBlockYSize, bool bCheckIsNan)
2220 : {
2221 25 : CPLAssert(pImage != nullptr && pImageNC != nullptr);
2222 :
2223 : // If this block is not a full block (in the x axis), we need to re-arrange
2224 : // the data this is because partial blocks are not arranged the same way in
2225 : // netcdf and gdal.
2226 25 : if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
2227 : {
2228 0 : T *ptrWrite = static_cast<T *>(pImage);
2229 0 : T *ptrRead = static_cast<T *>(pImageNC);
2230 0 : for (size_t j = 0; j < nTmpBlockYSize; j++,
2231 0 : ptrWrite += (2 * nBlockXSize),
2232 0 : ptrRead += (2 * nTmpBlockXSize))
2233 : {
2234 0 : memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T) * 2);
2235 : }
2236 : }
2237 :
2238 : // Is valid data checking needed or requested?
2239 25 : if (bValidRangeValid || bCheckIsNan)
2240 : {
2241 0 : T *ptrImage = static_cast<T *>(pImage);
2242 0 : for (size_t j = 0; j < nTmpBlockYSize; j++)
2243 : {
2244 : // k moves along the gdal block, skipping the out-of-range pixels.
2245 0 : size_t k = 2 * j * nBlockXSize;
2246 0 : for (size_t i = 0; i < (2 * nTmpBlockXSize); i++, k++)
2247 : {
2248 : // Check for nodata and nan.
2249 0 : if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
2250 0 : continue;
2251 0 : if (bCheckIsNan && std::isnan((double)ptrImage[k]))
2252 : {
2253 0 : ptrImage[k] = (T)m_dfNoDataValue;
2254 0 : continue;
2255 : }
2256 : // Check for valid_range.
2257 0 : if (bValidRangeValid)
2258 : {
2259 0 : if (((adfValidRange[0] != m_dfNoDataValue) &&
2260 0 : (ptrImage[k] < (T)adfValidRange[0])) ||
2261 0 : ((adfValidRange[1] != m_dfNoDataValue) &&
2262 0 : (ptrImage[k] > (T)adfValidRange[1])))
2263 : {
2264 0 : ptrImage[k] = (T)m_dfNoDataValue;
2265 : }
2266 : }
2267 : }
2268 : }
2269 : }
2270 25 : }
2271 :
2272 : /************************************************************************/
2273 : /* FetchNetcdfChunk() */
2274 : /************************************************************************/
2275 :
2276 5777 : bool netCDFRasterBand::FetchNetcdfChunk(size_t xstart, size_t ystart,
2277 : void *pImage)
2278 : {
2279 5777 : size_t start[MAX_NC_DIMS] = {};
2280 5777 : size_t edge[MAX_NC_DIMS] = {};
2281 :
2282 5777 : start[nBandXPos] = xstart;
2283 5777 : edge[nBandXPos] = nBlockXSize;
2284 5777 : if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
2285 6 : edge[nBandXPos] = nRasterXSize - start[nBandXPos];
2286 5777 : if (nBandYPos >= 0)
2287 : {
2288 5773 : start[nBandYPos] = ystart;
2289 5773 : edge[nBandYPos] = nBlockYSize;
2290 5773 : if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
2291 4 : edge[nBandYPos] = nRasterYSize - start[nBandYPos];
2292 : }
2293 5777 : const size_t nYChunkSize = nBandYPos < 0 ? 1 : edge[nBandYPos];
2294 :
2295 : #ifdef NCDF_DEBUG
2296 : CPLDebug("GDAL_netCDF", "start={%ld,%ld} edge={%ld,%ld} bBottomUp=%d",
2297 : start[nBandXPos], nBandYPos < 0 ? 0 : start[nBandYPos],
2298 : edge[nBandXPos], nYChunkSize, ((netCDFDataset *)poDS)->bBottomUp);
2299 : #endif
2300 :
2301 5777 : int nd = 0;
2302 5777 : nc_inq_varndims(cdfid, nZId, &nd);
2303 5777 : if (nd == 3)
2304 : {
2305 1078 : start[panBandZPos[0]] = nLevel; // z
2306 1078 : edge[panBandZPos[0]] = 1;
2307 : }
2308 :
2309 : // Compute multidimention band position.
2310 : //
2311 : // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
2312 : // if Data[2,3,4,x,y]
2313 : //
2314 : // BandPos0 = (nBand) / (3*4)
2315 : // BandPos1 = (nBand - (3*4)) / (4)
2316 : // BandPos2 = (nBand - (3*4)) % (4)
2317 5777 : if (nd > 3)
2318 : {
2319 160 : int Sum = -1;
2320 160 : int Taken = 0;
2321 480 : for (int i = 0; i < nd - 2; i++)
2322 : {
2323 320 : if (i != nd - 2 - 1)
2324 : {
2325 160 : Sum = 1;
2326 320 : for (int j = i + 1; j < nd - 2; j++)
2327 : {
2328 160 : Sum *= panBandZLev[j];
2329 : }
2330 160 : start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
2331 160 : edge[panBandZPos[i]] = 1;
2332 : }
2333 : else
2334 : {
2335 160 : start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
2336 160 : edge[panBandZPos[i]] = 1;
2337 : }
2338 320 : Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
2339 : }
2340 : }
2341 :
2342 : // Make sure we are in data mode.
2343 5777 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
2344 :
2345 : // If this block is not a full block in the x axis, we need to
2346 : // re-arrange the data because partial blocks are not arranged the
2347 : // same way in netcdf and gdal, so we first we read the netcdf data at
2348 : // the end of the gdal block buffer then re-arrange rows in CheckData().
2349 5777 : void *pImageNC = pImage;
2350 5777 : if (edge[nBandXPos] != static_cast<size_t>(nBlockXSize))
2351 : {
2352 6 : pImageNC = static_cast<GByte *>(pImage) +
2353 6 : ((static_cast<size_t>(nBlockXSize) * nBlockYSize -
2354 12 : edge[nBandXPos] * nYChunkSize) *
2355 6 : GDALGetDataTypeSizeBytes(eDataType));
2356 : }
2357 :
2358 : // Read data according to type.
2359 : int status;
2360 5777 : if (eDataType == GDT_UInt8)
2361 : {
2362 3025 : if (bSignedData)
2363 : {
2364 0 : status = nc_get_vara_schar(cdfid, nZId, start, edge,
2365 : static_cast<signed char *>(pImageNC));
2366 0 : if (status == NC_NOERR)
2367 0 : CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
2368 : nYChunkSize, false);
2369 : }
2370 : else
2371 : {
2372 3025 : status = nc_get_vara_uchar(cdfid, nZId, start, edge,
2373 : static_cast<unsigned char *>(pImageNC));
2374 3025 : if (status == NC_NOERR)
2375 3025 : CheckData<unsigned char>(pImage, pImageNC, edge[nBandXPos],
2376 : nYChunkSize, false);
2377 : }
2378 : }
2379 2752 : else if (eDataType == GDT_Int8)
2380 : {
2381 60 : status = nc_get_vara_schar(cdfid, nZId, start, edge,
2382 : static_cast<signed char *>(pImageNC));
2383 60 : if (status == NC_NOERR)
2384 60 : CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
2385 : nYChunkSize, false);
2386 : }
2387 2692 : else if (nc_datatype == NC_SHORT)
2388 : {
2389 465 : status = nc_get_vara_short(cdfid, nZId, start, edge,
2390 : static_cast<short *>(pImageNC));
2391 465 : if (status == NC_NOERR)
2392 : {
2393 465 : if (eDataType == GDT_Int16)
2394 : {
2395 462 : CheckData<GInt16>(pImage, pImageNC, edge[nBandXPos],
2396 : nYChunkSize, false);
2397 : }
2398 : else
2399 : {
2400 3 : CheckData<GUInt16>(pImage, pImageNC, edge[nBandXPos],
2401 : nYChunkSize, false);
2402 : }
2403 : }
2404 : }
2405 2227 : else if (eDataType == GDT_Int32)
2406 : {
2407 : #if SIZEOF_UNSIGNED_LONG == 4
2408 : status = nc_get_vara_long(cdfid, nZId, start, edge,
2409 : static_cast<long *>(pImageNC));
2410 : if (status == NC_NOERR)
2411 : CheckData<long>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2412 : false);
2413 : #else
2414 912 : status = nc_get_vara_int(cdfid, nZId, start, edge,
2415 : static_cast<int *>(pImageNC));
2416 912 : if (status == NC_NOERR)
2417 912 : CheckData<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2418 : false);
2419 : #endif
2420 : }
2421 1315 : else if (eDataType == GDT_Float32)
2422 : {
2423 1178 : status = nc_get_vara_float(cdfid, nZId, start, edge,
2424 : static_cast<float *>(pImageNC));
2425 1178 : if (status == NC_NOERR)
2426 1178 : CheckData<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2427 : true);
2428 : }
2429 137 : else if (eDataType == GDT_Float64)
2430 : {
2431 86 : status = nc_get_vara_double(cdfid, nZId, start, edge,
2432 : static_cast<double *>(pImageNC));
2433 86 : if (status == NC_NOERR)
2434 86 : CheckData<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2435 : true);
2436 : }
2437 51 : else if (eDataType == GDT_UInt16)
2438 : {
2439 6 : status = nc_get_vara_ushort(cdfid, nZId, start, edge,
2440 : static_cast<unsigned short *>(pImageNC));
2441 6 : if (status == NC_NOERR)
2442 6 : CheckData<unsigned short>(pImage, pImageNC, edge[nBandXPos],
2443 : nYChunkSize, false);
2444 : }
2445 45 : else if (eDataType == GDT_UInt32)
2446 : {
2447 6 : status = nc_get_vara_uint(cdfid, nZId, start, edge,
2448 : static_cast<unsigned int *>(pImageNC));
2449 6 : if (status == NC_NOERR)
2450 6 : CheckData<unsigned int>(pImage, pImageNC, edge[nBandXPos],
2451 : nYChunkSize, false);
2452 : }
2453 39 : else if (eDataType == GDT_Int64)
2454 : {
2455 7 : status = nc_get_vara_longlong(cdfid, nZId, start, edge,
2456 : static_cast<long long *>(pImageNC));
2457 7 : if (status == NC_NOERR)
2458 7 : CheckData<std::int64_t>(pImage, pImageNC, edge[nBandXPos],
2459 : nYChunkSize, false);
2460 : }
2461 32 : else if (eDataType == GDT_UInt64)
2462 : {
2463 : status =
2464 7 : nc_get_vara_ulonglong(cdfid, nZId, start, edge,
2465 : static_cast<unsigned long long *>(pImageNC));
2466 7 : if (status == NC_NOERR)
2467 7 : CheckData<std::uint64_t>(pImage, pImageNC, edge[nBandXPos],
2468 : nYChunkSize, false);
2469 : }
2470 25 : else if (eDataType == GDT_CInt16)
2471 : {
2472 0 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2473 0 : if (status == NC_NOERR)
2474 0 : CheckDataCpx<short>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2475 : false);
2476 : }
2477 25 : else if (eDataType == GDT_CInt32)
2478 : {
2479 0 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2480 0 : if (status == NC_NOERR)
2481 0 : CheckDataCpx<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2482 : false);
2483 : }
2484 25 : else if (eDataType == GDT_CFloat32)
2485 : {
2486 20 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2487 20 : if (status == NC_NOERR)
2488 20 : CheckDataCpx<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2489 : false);
2490 : }
2491 5 : else if (eDataType == GDT_CFloat64)
2492 : {
2493 5 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2494 5 : if (status == NC_NOERR)
2495 5 : CheckDataCpx<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2496 : false);
2497 : }
2498 :
2499 : else
2500 0 : status = NC_EBADTYPE;
2501 :
2502 5777 : if (status != NC_NOERR)
2503 : {
2504 0 : CPLError(CE_Failure, CPLE_AppDefined,
2505 : "netCDF chunk fetch failed: #%d (%s)", status,
2506 : nc_strerror(status));
2507 0 : return false;
2508 : }
2509 5777 : return true;
2510 : }
2511 :
2512 : /************************************************************************/
2513 : /* IReadBlock() */
2514 : /************************************************************************/
2515 :
2516 5777 : CPLErr netCDFRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
2517 : void *pImage)
2518 :
2519 : {
2520 11554 : CPLMutexHolderD(&hNCMutex);
2521 :
2522 : // Locate X, Y and Z position in the array.
2523 :
2524 5777 : size_t xstart = static_cast<size_t>(nBlockXOff) * nBlockXSize;
2525 5777 : size_t ystart = 0;
2526 :
2527 : // Check y order.
2528 5777 : if (nBandYPos >= 0)
2529 : {
2530 5773 : auto poGDS = cpl::down_cast<netCDFDataset *>(poDS);
2531 5773 : if (poGDS->bBottomUp)
2532 : {
2533 4858 : if (nBlockYSize == 1)
2534 : {
2535 4845 : ystart = nRasterYSize - 1 - nBlockYOff;
2536 : }
2537 : else
2538 : {
2539 : // in GDAL space
2540 13 : ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
2541 : const size_t yend =
2542 26 : std::min(ystart + nBlockYSize - 1,
2543 13 : static_cast<size_t>(nRasterYSize - 1));
2544 : // in netCDF space
2545 13 : const size_t nFirstChunkLine = nRasterYSize - 1 - yend;
2546 13 : const size_t nLastChunkLine = nRasterYSize - 1 - ystart;
2547 13 : const size_t nFirstChunkBlock = nFirstChunkLine / nBlockYSize;
2548 13 : const size_t nLastChunkBlock = nLastChunkLine / nBlockYSize;
2549 :
2550 : const auto firstKey = netCDFDataset::ChunkKey(
2551 13 : nBlockXOff, nFirstChunkBlock, nBand);
2552 : const auto secondKey =
2553 13 : netCDFDataset::ChunkKey(nBlockXOff, nLastChunkBlock, nBand);
2554 :
2555 : // Retrieve data from the one or 2 needed netCDF chunks
2556 13 : std::shared_ptr<std::vector<GByte>> firstChunk;
2557 13 : std::shared_ptr<std::vector<GByte>> secondChunk;
2558 13 : if (poGDS->poChunkCache)
2559 : {
2560 13 : poGDS->poChunkCache->tryGet(firstKey, firstChunk);
2561 13 : if (firstKey != secondKey)
2562 6 : poGDS->poChunkCache->tryGet(secondKey, secondChunk);
2563 : }
2564 : const size_t nChunkLineSize =
2565 13 : static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
2566 13 : nBlockXSize;
2567 13 : const size_t nChunkSize = nChunkLineSize * nBlockYSize;
2568 13 : if (!firstChunk)
2569 : {
2570 11 : firstChunk.reset(new std::vector<GByte>(nChunkSize));
2571 11 : if (!FetchNetcdfChunk(xstart,
2572 11 : nFirstChunkBlock * nBlockYSize,
2573 11 : firstChunk.get()->data()))
2574 0 : return CE_Failure;
2575 11 : if (poGDS->poChunkCache)
2576 11 : poGDS->poChunkCache->insert(firstKey, firstChunk);
2577 : }
2578 13 : if (!secondChunk && firstKey != secondKey)
2579 : {
2580 2 : secondChunk.reset(new std::vector<GByte>(nChunkSize));
2581 2 : if (!FetchNetcdfChunk(xstart, nLastChunkBlock * nBlockYSize,
2582 2 : secondChunk.get()->data()))
2583 0 : return CE_Failure;
2584 2 : if (poGDS->poChunkCache)
2585 2 : poGDS->poChunkCache->insert(secondKey, secondChunk);
2586 : }
2587 :
2588 : // Assemble netCDF chunks into GDAL block
2589 13 : GByte *pabyImage = static_cast<GByte *>(pImage);
2590 13 : const size_t nFirstChunkBlockLine =
2591 13 : nFirstChunkBlock * nBlockYSize;
2592 13 : const size_t nLastChunkBlockLine =
2593 13 : nLastChunkBlock * nBlockYSize;
2594 146 : for (size_t iLine = ystart; iLine <= yend; iLine++)
2595 : {
2596 133 : const size_t nLineFromBottom = nRasterYSize - 1 - iLine;
2597 133 : const size_t nChunkY = nLineFromBottom / nBlockYSize;
2598 133 : if (nChunkY == nFirstChunkBlock)
2599 : {
2600 121 : memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
2601 121 : firstChunk.get()->data() +
2602 121 : (nLineFromBottom - nFirstChunkBlockLine) *
2603 : nChunkLineSize,
2604 : nChunkLineSize);
2605 : }
2606 : else
2607 : {
2608 12 : CPLAssert(nChunkY == nLastChunkBlock);
2609 12 : assert(secondChunk);
2610 12 : memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
2611 12 : secondChunk.get()->data() +
2612 12 : (nLineFromBottom - nLastChunkBlockLine) *
2613 : nChunkLineSize,
2614 : nChunkLineSize);
2615 : }
2616 : }
2617 13 : return CE_None;
2618 : }
2619 : }
2620 : else
2621 : {
2622 915 : ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
2623 : }
2624 : }
2625 :
2626 5764 : return FetchNetcdfChunk(xstart, ystart, pImage) ? CE_None : CE_Failure;
2627 : }
2628 :
2629 : /************************************************************************/
2630 : /* IWriteBlock() */
2631 : /************************************************************************/
2632 :
2633 6421 : CPLErr netCDFRasterBand::IWriteBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
2634 : void *pImage)
2635 : {
2636 12842 : CPLMutexHolderD(&hNCMutex);
2637 :
2638 : #ifdef NCDF_DEBUG
2639 : if (nBlockYOff == 0 || (nBlockYOff == nRasterYSize - 1))
2640 : CPLDebug("GDAL_netCDF",
2641 : "netCDFRasterBand::IWriteBlock( %d, %d, ...) nBand=%d",
2642 : nBlockXOff, nBlockYOff, nBand);
2643 : #endif
2644 :
2645 6421 : int nd = 0;
2646 6421 : nc_inq_varndims(cdfid, nZId, &nd);
2647 :
2648 : // Locate X, Y and Z position in the array.
2649 :
2650 : size_t start[MAX_NC_DIMS];
2651 6421 : memset(start, 0, sizeof(start));
2652 6421 : start[nBandXPos] = static_cast<size_t>(nBlockXOff) * nBlockXSize;
2653 :
2654 : // check y order.
2655 6421 : if (cpl::down_cast<netCDFDataset *>(poDS)->bBottomUp)
2656 : {
2657 6397 : if (nBlockYSize == 1)
2658 : {
2659 6397 : start[nBandYPos] = nRasterYSize - 1 - nBlockYOff;
2660 : }
2661 : else
2662 : {
2663 0 : CPLError(CE_Failure, CPLE_AppDefined,
2664 : "nBlockYSize = %d, only 1 supported when "
2665 : "writing bottom-up dataset",
2666 : nBlockYSize);
2667 0 : return CE_Failure;
2668 : }
2669 : }
2670 : else
2671 : {
2672 24 : start[nBandYPos] = static_cast<size_t>(nBlockYOff) * nBlockYSize; // y
2673 : }
2674 :
2675 6421 : size_t edge[MAX_NC_DIMS] = {};
2676 :
2677 6421 : edge[nBandXPos] = nBlockXSize;
2678 6421 : if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
2679 0 : edge[nBandXPos] = nRasterXSize - start[nBandXPos];
2680 6421 : edge[nBandYPos] = nBlockYSize;
2681 6421 : if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
2682 0 : edge[nBandYPos] = nRasterYSize - start[nBandYPos];
2683 :
2684 6421 : if (nd == 3)
2685 : {
2686 610 : start[panBandZPos[0]] = nLevel; // z
2687 610 : edge[panBandZPos[0]] = 1;
2688 : }
2689 :
2690 : // Compute multidimention band position.
2691 : //
2692 : // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
2693 : // if Data[2,3,4,x,y]
2694 : //
2695 : // BandPos0 = (nBand) / (3*4)
2696 : // BandPos1 = (nBand - (3*4)) / (4)
2697 : // BandPos2 = (nBand - (3*4)) % (4)
2698 6421 : if (nd > 3)
2699 : {
2700 178 : int Sum = -1;
2701 178 : int Taken = 0;
2702 534 : for (int i = 0; i < nd - 2; i++)
2703 : {
2704 356 : if (i != nd - 2 - 1)
2705 : {
2706 178 : Sum = 1;
2707 356 : for (int j = i + 1; j < nd - 2; j++)
2708 : {
2709 178 : Sum *= panBandZLev[j];
2710 : }
2711 178 : start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
2712 178 : edge[panBandZPos[i]] = 1;
2713 : }
2714 : else
2715 : {
2716 178 : start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
2717 178 : edge[panBandZPos[i]] = 1;
2718 : }
2719 356 : Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
2720 : }
2721 : }
2722 :
2723 : // Make sure we are in data mode.
2724 6421 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
2725 :
2726 : // Copy data according to type.
2727 6421 : int status = 0;
2728 6421 : if (eDataType == GDT_UInt8)
2729 : {
2730 5862 : if (bSignedData)
2731 0 : status = nc_put_vara_schar(cdfid, nZId, start, edge,
2732 : static_cast<signed char *>(pImage));
2733 : else
2734 5862 : status = nc_put_vara_uchar(cdfid, nZId, start, edge,
2735 : static_cast<unsigned char *>(pImage));
2736 : }
2737 559 : else if (eDataType == GDT_Int8)
2738 : {
2739 40 : status = nc_put_vara_schar(cdfid, nZId, start, edge,
2740 : static_cast<signed char *>(pImage));
2741 : }
2742 519 : else if (nc_datatype == NC_SHORT)
2743 : {
2744 101 : status = nc_put_vara_short(cdfid, nZId, start, edge,
2745 : static_cast<short *>(pImage));
2746 : }
2747 418 : else if (eDataType == GDT_Int32)
2748 : {
2749 210 : status = nc_put_vara_int(cdfid, nZId, start, edge,
2750 : static_cast<int *>(pImage));
2751 : }
2752 208 : else if (eDataType == GDT_Float32)
2753 : {
2754 128 : status = nc_put_vara_float(cdfid, nZId, start, edge,
2755 : static_cast<float *>(pImage));
2756 : }
2757 80 : else if (eDataType == GDT_Float64)
2758 : {
2759 50 : status = nc_put_vara_double(cdfid, nZId, start, edge,
2760 : static_cast<double *>(pImage));
2761 : }
2762 42 : else if (eDataType == GDT_UInt16 &&
2763 12 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2764 : {
2765 12 : status = nc_put_vara_ushort(cdfid, nZId, start, edge,
2766 : static_cast<unsigned short *>(pImage));
2767 : }
2768 30 : else if (eDataType == GDT_UInt32 &&
2769 12 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2770 : {
2771 12 : status = nc_put_vara_uint(cdfid, nZId, start, edge,
2772 : static_cast<unsigned int *>(pImage));
2773 : }
2774 9 : else if (eDataType == GDT_UInt64 &&
2775 3 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2776 : {
2777 : status =
2778 3 : nc_put_vara_ulonglong(cdfid, nZId, start, edge,
2779 : static_cast<unsigned long long *>(pImage));
2780 : }
2781 6 : else if (eDataType == GDT_Int64 &&
2782 3 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2783 : {
2784 3 : status = nc_put_vara_longlong(cdfid, nZId, start, edge,
2785 : static_cast<long long *>(pImage));
2786 : }
2787 : else
2788 : {
2789 0 : CPLError(CE_Failure, CPLE_NotSupported,
2790 : "The NetCDF driver does not support GDAL data type %d",
2791 0 : eDataType);
2792 0 : status = NC_EBADTYPE;
2793 : }
2794 6421 : NCDF_ERR(status);
2795 :
2796 6421 : if (status != NC_NOERR)
2797 : {
2798 0 : CPLError(CE_Failure, CPLE_AppDefined,
2799 : "netCDF scanline write failed: %s", nc_strerror(status));
2800 0 : return CE_Failure;
2801 : }
2802 :
2803 6421 : return CE_None;
2804 : }
2805 :
2806 : /************************************************************************/
2807 : /* ==================================================================== */
2808 : /* netCDFDataset */
2809 : /* ==================================================================== */
2810 : /************************************************************************/
2811 :
2812 : /************************************************************************/
2813 : /* netCDFDataset() */
2814 : /************************************************************************/
2815 :
2816 1170 : netCDFDataset::netCDFDataset()
2817 : :
2818 : // Basic dataset vars.
2819 : #ifdef ENABLE_NCDUMP
2820 : bFileToDestroyAtClosing(false),
2821 : #endif
2822 : cdfid(-1), nSubDatasets(0), papszSubDatasets(nullptr),
2823 : papszMetadata(nullptr), bBottomUp(true), eFormat(NCDF_FORMAT_NONE),
2824 : bIsGdalFile(false), bIsGdalCfFile(false), pszCFProjection(nullptr),
2825 : pszCFCoordinates(nullptr), nCFVersion(1.6), bSGSupport(false),
2826 1170 : eMultipleLayerBehavior(SINGLE_LAYER), logCount(0), vcdf(this, cdfid),
2827 1170 : GeometryScribe(vcdf, this->generateLogName()),
2828 1170 : FieldScribe(vcdf, this->generateLogName()),
2829 2340 : bufManager(CPLGetUsablePhysicalRAM() / 5),
2830 :
2831 : // projection/GT.
2832 : nXDimID(-1), nYDimID(-1), bIsProjected(false),
2833 : bIsGeographic(false), // Can be not projected, and also not geographic
2834 : // State vars.
2835 : bDefineMode(true), bAddedGridMappingRef(false),
2836 :
2837 : // Create vars.
2838 : papszCreationOptions(nullptr), eCompress(NCDF_COMPRESS_NONE),
2839 : nZLevel(NCDF_DEFLATE_LEVEL), bChunking(false), nCreateMode(NC_CLOBBER),
2840 3510 : bSignedData(true)
2841 : {
2842 1170 : m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2843 :
2844 : // Set buffers
2845 1170 : bufManager.addBuffer(&(GeometryScribe.getMemBuffer()));
2846 1170 : bufManager.addBuffer(&(FieldScribe.getMemBuffer()));
2847 1170 : }
2848 :
2849 : /************************************************************************/
2850 : /* ~netCDFDataset() */
2851 : /************************************************************************/
2852 :
2853 2242 : netCDFDataset::~netCDFDataset()
2854 :
2855 : {
2856 1170 : netCDFDataset::Close();
2857 2242 : }
2858 :
2859 : /************************************************************************/
2860 : /* Close() */
2861 : /************************************************************************/
2862 :
2863 1982 : CPLErr netCDFDataset::Close(GDALProgressFunc, void *)
2864 : {
2865 1982 : CPLErr eErr = CE_None;
2866 1982 : if (nOpenFlags != OPEN_FLAGS_CLOSED)
2867 : {
2868 2340 : CPLMutexHolderD(&hNCMutex);
2869 :
2870 : #ifdef NCDF_DEBUG
2871 : CPLDebug("GDAL_netCDF",
2872 : "netCDFDataset::~netCDFDataset(), cdfid=%d filename=%s", cdfid,
2873 : osFilename.c_str());
2874 : #endif
2875 :
2876 : // Write data related to geotransform
2877 1453 : if (GetAccess() == GA_Update && !m_bAddedProjectionVarsData &&
2878 283 : (m_bHasProjection || m_bHasGeoTransform))
2879 : {
2880 : // Ensure projection is written if GeoTransform OR Projection are
2881 : // missing.
2882 37 : if (!m_bAddedProjectionVarsDefs)
2883 : {
2884 2 : AddProjectionVars(true, nullptr, nullptr);
2885 : }
2886 37 : AddProjectionVars(false, nullptr, nullptr);
2887 : }
2888 :
2889 1170 : if (netCDFDataset::FlushCache(true) != CE_None)
2890 0 : eErr = CE_Failure;
2891 :
2892 1170 : if (GetAccess() == GA_Update && !SGCommitPendingTransaction())
2893 0 : eErr = CE_Failure;
2894 :
2895 1172 : for (size_t i = 0; i < apoVectorDatasets.size(); i++)
2896 2 : delete apoVectorDatasets[i];
2897 :
2898 : // Make sure projection variable is written to band variable.
2899 1170 : if (GetAccess() == GA_Update && !bAddedGridMappingRef)
2900 : {
2901 302 : if (!AddGridMappingRef())
2902 0 : eErr = CE_Failure;
2903 : }
2904 :
2905 1170 : CSLDestroy(papszMetadata);
2906 1170 : CSLDestroy(papszSubDatasets);
2907 1170 : CSLDestroy(papszCreationOptions);
2908 :
2909 1170 : CPLFree(pszCFProjection);
2910 :
2911 1170 : if (cdfid > 0)
2912 : {
2913 : #ifdef NCDF_DEBUG
2914 : CPLDebug("GDAL_netCDF", "calling nc_close( %d)", cdfid);
2915 : #endif
2916 669 : int status = GDAL_nc_close(cdfid);
2917 : #ifdef ENABLE_UFFD
2918 669 : NETCDF_UFFD_UNMAP(pCtx);
2919 : #endif
2920 669 : NCDF_ERR(status);
2921 669 : if (status != NC_NOERR)
2922 0 : eErr = CE_Failure;
2923 : }
2924 :
2925 1170 : if (fpVSIMEM)
2926 15 : VSIFCloseL(fpVSIMEM);
2927 :
2928 : #ifdef ENABLE_NCDUMP
2929 1170 : if (bFileToDestroyAtClosing)
2930 0 : VSIUnlink(osFilename);
2931 : #endif
2932 :
2933 1170 : if (GDALPamDataset::Close() != CE_None)
2934 0 : eErr = CE_Failure;
2935 : }
2936 1982 : return eErr;
2937 : }
2938 :
2939 : /************************************************************************/
2940 : /* SetDefineMode() */
2941 : /************************************************************************/
2942 14287 : bool netCDFDataset::SetDefineMode(bool bNewDefineMode)
2943 : {
2944 : // Do nothing if already in new define mode
2945 : // or if dataset is in read-only mode or if dataset is true NC4 dataset.
2946 14852 : if (bDefineMode == bNewDefineMode || GetAccess() == GA_ReadOnly ||
2947 565 : eFormat == NCDF_FORMAT_NC4)
2948 13869 : return true;
2949 :
2950 418 : CPLDebug("GDAL_netCDF", "SetDefineMode(%d) old=%d",
2951 418 : static_cast<int>(bNewDefineMode), static_cast<int>(bDefineMode));
2952 :
2953 418 : bDefineMode = bNewDefineMode;
2954 :
2955 : int status;
2956 418 : if (bDefineMode)
2957 145 : status = nc_redef(cdfid);
2958 : else
2959 273 : status = nc_enddef(cdfid);
2960 :
2961 418 : NCDF_ERR(status);
2962 418 : return status == NC_NOERR;
2963 : }
2964 :
2965 : /************************************************************************/
2966 : /* GetMetadataDomainList() */
2967 : /************************************************************************/
2968 :
2969 27 : char **netCDFDataset::GetMetadataDomainList()
2970 : {
2971 27 : char **papszDomains = BuildMetadataDomainList(
2972 : GDALDataset::GetMetadataDomainList(), TRUE, "SUBDATASETS", nullptr);
2973 28 : for (const auto &kv : m_oMapDomainToJSon)
2974 1 : papszDomains = CSLAddString(papszDomains, ("json:" + kv.first).c_str());
2975 27 : return papszDomains;
2976 : }
2977 :
2978 : /************************************************************************/
2979 : /* GetMetadata() */
2980 : /************************************************************************/
2981 373 : CSLConstList netCDFDataset::GetMetadata(const char *pszDomain)
2982 : {
2983 373 : if (pszDomain != nullptr && STARTS_WITH_CI(pszDomain, "SUBDATASETS"))
2984 39 : return papszSubDatasets;
2985 :
2986 334 : if (pszDomain != nullptr && STARTS_WITH(pszDomain, "json:"))
2987 : {
2988 1 : auto iter = m_oMapDomainToJSon.find(pszDomain + strlen("json:"));
2989 1 : if (iter != m_oMapDomainToJSon.end())
2990 1 : return iter->second.List();
2991 : }
2992 :
2993 333 : return GDALDataset::GetMetadata(pszDomain);
2994 : }
2995 :
2996 : /************************************************************************/
2997 : /* SetMetadataItem() */
2998 : /************************************************************************/
2999 :
3000 43 : CPLErr netCDFDataset::SetMetadataItem(const char *pszName, const char *pszValue,
3001 : const char *pszDomain)
3002 : {
3003 85 : if (GetAccess() == GA_Update &&
3004 85 : (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)
3005 : {
3006 42 : std::string osName(pszName);
3007 :
3008 : // Same logic as in CopyMetadata()
3009 42 : if (STARTS_WITH(osName.c_str(), "NC_GLOBAL#"))
3010 8 : osName = osName.substr(strlen("NC_GLOBAL#"));
3011 34 : else if (strchr(osName.c_str(), '#') == nullptr)
3012 5 : osName = "GDAL_" + osName;
3013 :
3014 84 : if (STARTS_WITH(osName.c_str(), "NETCDF_DIM_") ||
3015 42 : strchr(osName.c_str(), '#') != nullptr)
3016 : {
3017 : // do nothing
3018 29 : return CE_None;
3019 : }
3020 : else
3021 : {
3022 13 : SetDefineMode(true);
3023 :
3024 13 : if (!NCDFPutAttr(cdfid, NC_GLOBAL, osName.c_str(), pszValue))
3025 13 : return CE_Failure;
3026 : }
3027 : }
3028 :
3029 1 : return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);
3030 : }
3031 :
3032 : /************************************************************************/
3033 : /* SetMetadata() */
3034 : /************************************************************************/
3035 :
3036 8 : CPLErr netCDFDataset::SetMetadata(CSLConstList papszMD, const char *pszDomain)
3037 : {
3038 13 : if (GetAccess() == GA_Update &&
3039 5 : (pszDomain == nullptr || pszDomain[0] == '\0'))
3040 : {
3041 : // We don't handle metadata item removal for now
3042 50 : for (const char *const *papszIter = papszMD; papszIter && *papszIter;
3043 : ++papszIter)
3044 : {
3045 42 : char *pszName = nullptr;
3046 42 : const char *pszValue = CPLParseNameValue(*papszIter, &pszName);
3047 42 : if (pszName && pszValue)
3048 42 : SetMetadataItem(pszName, pszValue);
3049 42 : CPLFree(pszName);
3050 : }
3051 8 : return CE_None;
3052 : }
3053 0 : return GDALPamDataset::SetMetadata(papszMD, pszDomain);
3054 : }
3055 :
3056 : /************************************************************************/
3057 : /* GetSpatialRef() */
3058 : /************************************************************************/
3059 :
3060 208 : const OGRSpatialReference *netCDFDataset::GetSpatialRef() const
3061 : {
3062 208 : if (m_bHasProjection)
3063 80 : return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
3064 :
3065 128 : return GDALPamDataset::GetSpatialRef();
3066 : }
3067 :
3068 : /************************************************************************/
3069 : /* FetchCopyParam() */
3070 : /************************************************************************/
3071 :
3072 444 : double netCDFDataset::FetchCopyParam(const char *pszGridMappingValue,
3073 : const char *pszParam, double dfDefault,
3074 : bool *pbFound) const
3075 :
3076 : {
3077 : char *pszTemp =
3078 444 : CPLStrdup(CPLSPrintf("%s#%s", pszGridMappingValue, pszParam));
3079 444 : const char *pszValue = CSLFetchNameValue(papszMetadata, pszTemp);
3080 444 : CPLFree(pszTemp);
3081 :
3082 444 : if (pbFound)
3083 : {
3084 444 : *pbFound = (pszValue != nullptr);
3085 : }
3086 :
3087 444 : if (pszValue)
3088 : {
3089 0 : return CPLAtofM(pszValue);
3090 : }
3091 :
3092 444 : return dfDefault;
3093 : }
3094 :
3095 : /************************************************************************/
3096 : /* FetchStandardParallels() */
3097 : /************************************************************************/
3098 :
3099 : std::vector<std::string>
3100 0 : netCDFDataset::FetchStandardParallels(const char *pszGridMappingValue) const
3101 : {
3102 : // cf-1.0 tags
3103 0 : const char *pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL);
3104 :
3105 0 : std::vector<std::string> ret;
3106 0 : if (pszValue != nullptr)
3107 : {
3108 0 : CPLStringList aosValues;
3109 0 : if (pszValue[0] != '{' &&
3110 0 : CPLString(pszValue).Trim().find(' ') != std::string::npos)
3111 : {
3112 : // Some files like
3113 : // ftp://data.knmi.nl/download/KNW-NetCDF-3D/1.0/noversion/2013/11/14/KNW-1.0_H37-ERA_NL_20131114.nc
3114 : // do not use standard formatting for arrays, but just space
3115 : // separated syntax
3116 0 : aosValues = CSLTokenizeString2(pszValue, " ", 0);
3117 : }
3118 : else
3119 : {
3120 0 : aosValues = NCDFTokenizeArray(pszValue);
3121 : }
3122 0 : for (int i = 0; i < aosValues.size(); i++)
3123 : {
3124 0 : ret.push_back(aosValues[i]);
3125 : }
3126 : }
3127 : // Try gdal tags.
3128 : else
3129 : {
3130 0 : pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_1);
3131 :
3132 0 : if (pszValue != nullptr)
3133 0 : ret.push_back(pszValue);
3134 :
3135 0 : pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_2);
3136 :
3137 0 : if (pszValue != nullptr)
3138 0 : ret.push_back(pszValue);
3139 : }
3140 :
3141 0 : return ret;
3142 : }
3143 :
3144 : /************************************************************************/
3145 : /* FetchAttr() */
3146 : /************************************************************************/
3147 :
3148 3878 : const char *netCDFDataset::FetchAttr(const char *pszVarFullName,
3149 : const char *pszAttr) const
3150 :
3151 : {
3152 3878 : char *pszKey = CPLStrdup(CPLSPrintf("%s#%s", pszVarFullName, pszAttr));
3153 3878 : const char *pszValue = CSLFetchNameValue(papszMetadata, pszKey);
3154 3878 : CPLFree(pszKey);
3155 3878 : return pszValue;
3156 : }
3157 :
3158 2552 : const char *netCDFDataset::FetchAttr(int nGroupId, int nVarId,
3159 : const char *pszAttr) const
3160 :
3161 : {
3162 2552 : char *pszVarFullName = nullptr;
3163 2552 : NCDFGetVarFullName(nGroupId, nVarId, &pszVarFullName);
3164 2552 : const char *pszValue = FetchAttr(pszVarFullName, pszAttr);
3165 2552 : CPLFree(pszVarFullName);
3166 2552 : return pszValue;
3167 : }
3168 :
3169 : /************************************************************************/
3170 : /* IsDifferenceBelow() */
3171 : /************************************************************************/
3172 :
3173 1115 : static bool IsDifferenceBelow(double dfA, double dfB, double dfError)
3174 : {
3175 1115 : const double dfAbsDiff = fabs(dfA - dfB);
3176 1115 : return dfAbsDiff <= dfError;
3177 : }
3178 :
3179 : /************************************************************************/
3180 : /* SetProjectionFromVar() */
3181 : /************************************************************************/
3182 546 : void netCDFDataset::SetProjectionFromVar(
3183 : int nGroupId, int nVarId, bool bReadSRSOnly, const char *pszGivenGM,
3184 : std::string *returnProjStr, nccfdriver::SGeometry_Reader *sg,
3185 : std::vector<std::string> *paosRemovedMDItems)
3186 : {
3187 546 : bool bGotGeogCS = false;
3188 546 : bool bGotCfSRS = false;
3189 546 : bool bGotCfWktSRS = false;
3190 546 : bool bGotGdalSRS = false;
3191 546 : bool bGotCfGT = false;
3192 546 : bool bGotGdalGT = false;
3193 :
3194 : // These values from CF metadata.
3195 546 : OGRSpatialReference oSRS;
3196 546 : oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3197 546 : size_t xdim = nRasterXSize;
3198 546 : size_t ydim = nRasterYSize;
3199 :
3200 : // These values from GDAL metadata.
3201 546 : const char *pszWKT = nullptr;
3202 546 : const char *pszGeoTransform = nullptr;
3203 :
3204 546 : netCDFDataset *poDS = this; // Perhaps this should be removed for clarity.
3205 :
3206 546 : CPLDebug("GDAL_netCDF", "\n=====\nSetProjectionFromVar( %d, %d)", nGroupId,
3207 : nVarId);
3208 :
3209 : // Get x/y range information.
3210 :
3211 : // Temp variables to use in SetGeoTransform() and SetProjection().
3212 546 : GDALGeoTransform tmpGT;
3213 :
3214 : // Look for grid_mapping metadata.
3215 546 : const char *pszValue = pszGivenGM;
3216 546 : CPLString osTmpGridMapping; // let is in this outer scope as pszValue may
3217 : // point to it
3218 546 : if (pszValue == nullptr)
3219 : {
3220 503 : pszValue = FetchAttr(nGroupId, nVarId, CF_GRD_MAPPING);
3221 503 : if (pszValue && strchr(pszValue, ':') && strchr(pszValue, ' '))
3222 : {
3223 : // Expanded form of grid_mapping
3224 : // e.g. "crsOSGB: x y crsWGS84: lat lon"
3225 : // Pickup the grid_mapping whose coordinates are dimensions of the
3226 : // variable
3227 6 : const CPLStringList aosTokens(CSLTokenizeString2(pszValue, " ", 0));
3228 3 : if ((aosTokens.size() % 3) == 0)
3229 : {
3230 3 : for (int i = 0; i < aosTokens.size() / 3; i++)
3231 : {
3232 3 : if (CSLFindString(poDS->papszDimName,
3233 9 : aosTokens[3 * i + 1]) >= 0 &&
3234 3 : CSLFindString(poDS->papszDimName,
3235 3 : aosTokens[3 * i + 2]) >= 0)
3236 : {
3237 3 : osTmpGridMapping = aosTokens[3 * i];
3238 6 : if (!osTmpGridMapping.empty() &&
3239 3 : osTmpGridMapping.back() == ':')
3240 : {
3241 3 : osTmpGridMapping.resize(osTmpGridMapping.size() -
3242 : 1);
3243 : }
3244 3 : pszValue = osTmpGridMapping.c_str();
3245 3 : break;
3246 : }
3247 : }
3248 : }
3249 : }
3250 : }
3251 546 : char *pszGridMappingValue = CPLStrdup(pszValue ? pszValue : "");
3252 :
3253 546 : if (!EQUAL(pszGridMappingValue, ""))
3254 : {
3255 : // Read grid_mapping metadata.
3256 231 : int nProjGroupID = -1;
3257 231 : int nProjVarID = -1;
3258 231 : if (NCDFResolveVar(nGroupId, pszGridMappingValue, &nProjGroupID,
3259 231 : &nProjVarID) == CE_None)
3260 : {
3261 230 : poDS->ReadAttributes(nProjGroupID, nProjVarID);
3262 :
3263 : // Look for GDAL spatial_ref and GeoTransform within grid_mapping.
3264 230 : CPLFree(pszGridMappingValue);
3265 230 : pszGridMappingValue = nullptr;
3266 230 : NCDFGetVarFullName(nProjGroupID, nProjVarID, &pszGridMappingValue);
3267 230 : if (pszGridMappingValue)
3268 : {
3269 230 : CPLDebug("GDAL_netCDF", "got grid_mapping %s",
3270 : pszGridMappingValue);
3271 230 : pszWKT = FetchAttr(pszGridMappingValue, NCDF_SPATIAL_REF);
3272 230 : if (!pszWKT)
3273 : {
3274 35 : pszWKT = FetchAttr(pszGridMappingValue, NCDF_CRS_WKT);
3275 : }
3276 : else
3277 : {
3278 195 : bGotGdalSRS = true;
3279 195 : CPLDebug("GDAL_netCDF", "setting WKT from GDAL");
3280 : }
3281 230 : if (pszWKT)
3282 : {
3283 200 : if (!bGotGdalSRS)
3284 : {
3285 5 : bGotCfWktSRS = true;
3286 5 : CPLDebug("GDAL_netCDF", "setting WKT from CF");
3287 : }
3288 200 : if (returnProjStr != nullptr)
3289 : {
3290 41 : (*returnProjStr) = std::string(pszWKT);
3291 : }
3292 : else
3293 : {
3294 159 : m_bAddedProjectionVarsDefs = true;
3295 159 : m_bAddedProjectionVarsData = true;
3296 318 : OGRSpatialReference oSRSTmp;
3297 159 : oSRSTmp.SetAxisMappingStrategy(
3298 : OAMS_TRADITIONAL_GIS_ORDER);
3299 159 : oSRSTmp.importFromWkt(pszWKT);
3300 159 : SetSpatialRefNoUpdate(&oSRSTmp);
3301 : }
3302 : pszGeoTransform =
3303 200 : FetchAttr(pszGridMappingValue, NCDF_GEOTRANSFORM);
3304 : }
3305 : }
3306 : else
3307 : {
3308 0 : pszGridMappingValue = CPLStrdup("");
3309 : }
3310 : }
3311 : }
3312 :
3313 : // Get information about the file.
3314 : //
3315 : // Was this file created by the GDAL netcdf driver?
3316 : // Was this file created by the newer (CF-conformant) driver?
3317 : //
3318 : // 1) If GDAL netcdf metadata is set, and version >= 1.9,
3319 : // it was created with the new driver
3320 : // 2) Else, if spatial_ref and GeoTransform are present in the
3321 : // grid_mapping variable, it was created by the old driver
3322 546 : pszValue = FetchAttr("NC_GLOBAL", "GDAL");
3323 :
3324 546 : if (pszValue && NCDFIsGDALVersionGTE(pszValue, 1900))
3325 : {
3326 245 : bIsGdalFile = true;
3327 245 : bIsGdalCfFile = true;
3328 : }
3329 301 : else if (pszWKT != nullptr && pszGeoTransform != nullptr)
3330 : {
3331 24 : bIsGdalFile = true;
3332 24 : bIsGdalCfFile = false;
3333 : }
3334 :
3335 : // Set default bottom-up default value.
3336 : // Y axis dimension and absence of GT can modify this value.
3337 : // Override with Config option GDAL_NETCDF_BOTTOMUP.
3338 :
3339 : // New driver is bottom-up by default.
3340 546 : if ((bIsGdalFile && !bIsGdalCfFile) || bSwitchedXY)
3341 26 : poDS->bBottomUp = false;
3342 : else
3343 520 : poDS->bBottomUp = true;
3344 :
3345 546 : CPLDebug("GDAL_netCDF",
3346 : "bIsGdalFile=%d bIsGdalCfFile=%d bSwitchedXY=%d bBottomUp=%d",
3347 546 : static_cast<int>(bIsGdalFile), static_cast<int>(bIsGdalCfFile),
3348 546 : static_cast<int>(bSwitchedXY), static_cast<int>(bBottomUp));
3349 :
3350 : // Read projection coordinates.
3351 :
3352 546 : int nGroupDimXID = -1;
3353 546 : int nVarDimXID = -1;
3354 546 : int nGroupDimYID = -1;
3355 546 : int nVarDimYID = -1;
3356 546 : if (sg != nullptr)
3357 : {
3358 43 : nGroupDimXID = sg->get_ncID();
3359 43 : nGroupDimYID = sg->get_ncID();
3360 43 : nVarDimXID = sg->getNodeCoordVars()[0];
3361 43 : nVarDimYID = sg->getNodeCoordVars()[1];
3362 : }
3363 :
3364 546 : if (!bReadSRSOnly)
3365 : {
3366 356 : NCDFResolveVar(nGroupId, poDS->papszDimName[nXDimID], &nGroupDimXID,
3367 : &nVarDimXID);
3368 356 : NCDFResolveVar(nGroupId, poDS->papszDimName[nYDimID], &nGroupDimYID,
3369 : &nVarDimYID);
3370 : // TODO: if above resolving fails we should also search for coordinate
3371 : // variables without same name than dimension using the same resolving
3372 : // logic. This should handle for example NASA Ocean Color L2 products.
3373 :
3374 : const bool bIgnoreXYAxisNameChecks =
3375 712 : CPLTestBool(CSLFetchNameValueDef(
3376 356 : papszOpenOptions, "IGNORE_XY_AXIS_NAME_CHECKS",
3377 : CPLGetConfigOption("GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS",
3378 356 : "NO"))) ||
3379 : // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a res
3380 : // and transform attributes
3381 356 : (FetchAttr(nGroupId, nVarId, "res") != nullptr &&
3382 712 : FetchAttr(nGroupId, nVarId, "transform") != nullptr) ||
3383 355 : FetchAttr(nGroupId, NC_GLOBAL, "GMT_version") != nullptr;
3384 :
3385 : // Check that they are 1D or 2D variables
3386 356 : if (nVarDimXID >= 0)
3387 : {
3388 260 : int ndims = -1;
3389 260 : nc_inq_varndims(nGroupId, nVarDimXID, &ndims);
3390 260 : if (ndims == 0 || ndims > 2)
3391 0 : nVarDimXID = -1;
3392 260 : else if (!bIgnoreXYAxisNameChecks)
3393 : {
3394 258 : if (!NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
3395 168 : !NCDFIsVarProjectionX(nGroupId, nVarDimXID, nullptr) &&
3396 : // In case of inversion of X/Y
3397 458 : !NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr) &&
3398 32 : !NCDFIsVarProjectionY(nGroupId, nVarDimXID, nullptr))
3399 : {
3400 : char szVarNameX[NC_MAX_NAME + 1];
3401 32 : CPL_IGNORE_RET_VAL(
3402 32 : nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
3403 32 : if (!(ndims == 1 &&
3404 31 : (EQUAL(szVarNameX, CF_LONGITUDE_STD_NAME) ||
3405 30 : EQUAL(szVarNameX, CF_LONGITUDE_VAR_NAME))))
3406 : {
3407 31 : CPLDebug(
3408 : "netCDF",
3409 : "Georeferencing ignored due to non-specific "
3410 : "enough X axis name. "
3411 : "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
3412 : "as configuration option to bypass this check");
3413 31 : nVarDimXID = -1;
3414 : }
3415 : }
3416 : }
3417 : }
3418 :
3419 356 : if (nVarDimYID >= 0)
3420 : {
3421 262 : int ndims = -1;
3422 262 : nc_inq_varndims(nGroupId, nVarDimYID, &ndims);
3423 262 : if (ndims == 0 || ndims > 2)
3424 1 : nVarDimYID = -1;
3425 261 : else if (!bIgnoreXYAxisNameChecks)
3426 : {
3427 259 : if (!NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr) &&
3428 169 : !NCDFIsVarProjectionY(nGroupId, nVarDimYID, nullptr) &&
3429 : // In case of inversion of X/Y
3430 461 : !NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
3431 33 : !NCDFIsVarProjectionX(nGroupId, nVarDimYID, nullptr))
3432 : {
3433 : char szVarNameY[NC_MAX_NAME + 1];
3434 33 : CPL_IGNORE_RET_VAL(
3435 33 : nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
3436 33 : if (!(ndims == 1 &&
3437 33 : (EQUAL(szVarNameY, CF_LATITUDE_STD_NAME) ||
3438 32 : EQUAL(szVarNameY, CF_LATITUDE_VAR_NAME))))
3439 : {
3440 32 : CPLDebug(
3441 : "netCDF",
3442 : "Georeferencing ignored due to non-specific "
3443 : "enough Y axis name. "
3444 : "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
3445 : "as configuration option to bypass this check");
3446 32 : nVarDimYID = -1;
3447 : }
3448 : }
3449 : }
3450 : }
3451 :
3452 356 : if ((nVarDimXID >= 0 && xdim == 1) || (nVarDimXID >= 0 && ydim == 1))
3453 : {
3454 0 : CPLError(CE_Warning, CPLE_AppDefined,
3455 : "1-pixel width/height files not supported, "
3456 : "xdim: %ld ydim: %ld",
3457 : static_cast<long>(xdim), static_cast<long>(ydim));
3458 0 : nVarDimXID = -1;
3459 0 : nVarDimYID = -1;
3460 : }
3461 : }
3462 :
3463 546 : const char *pszUnits = nullptr;
3464 546 : if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
3465 : {
3466 272 : const char *pszUnitsX = FetchAttr(nGroupDimXID, nVarDimXID, "units");
3467 272 : const char *pszUnitsY = FetchAttr(nGroupDimYID, nVarDimYID, "units");
3468 : // Normalize degrees_east/degrees_north to degrees
3469 : // Cf https://github.com/OSGeo/gdal/issues/11009
3470 272 : if (pszUnitsX && EQUAL(pszUnitsX, "degrees_east"))
3471 79 : pszUnitsX = "degrees";
3472 272 : if (pszUnitsY && EQUAL(pszUnitsY, "degrees_north"))
3473 79 : pszUnitsY = "degrees";
3474 :
3475 272 : if (pszUnitsX && pszUnitsY)
3476 : {
3477 225 : if (EQUAL(pszUnitsX, pszUnitsY))
3478 222 : pszUnits = pszUnitsX;
3479 3 : else if (!pszWKT && !EQUAL(pszGridMappingValue, ""))
3480 : {
3481 0 : CPLError(CE_Failure, CPLE_AppDefined,
3482 : "X axis unit (%s) is different from Y axis "
3483 : "unit (%s). SRS will ignore axis unit and be "
3484 : "likely wrong.",
3485 : pszUnitsX, pszUnitsY);
3486 : }
3487 : }
3488 47 : else if (pszUnitsX && !pszWKT && !EQUAL(pszGridMappingValue, ""))
3489 : {
3490 0 : CPLError(CE_Failure, CPLE_AppDefined,
3491 : "X axis unit is defined, but not Y one ."
3492 : "SRS will ignore axis unit and be likely wrong.");
3493 : }
3494 47 : else if (pszUnitsY && !pszWKT && !EQUAL(pszGridMappingValue, ""))
3495 : {
3496 0 : CPLError(CE_Failure, CPLE_AppDefined,
3497 : "Y axis unit is defined, but not X one ."
3498 : "SRS will ignore axis unit and be likely wrong.");
3499 : }
3500 : }
3501 :
3502 546 : if (!pszWKT && !EQUAL(pszGridMappingValue, ""))
3503 : {
3504 31 : CPLStringList aosGridMappingKeyValues;
3505 31 : const size_t nLenGridMappingValue = strlen(pszGridMappingValue);
3506 789 : for (const char *const *papszIter = papszMetadata;
3507 789 : papszIter && *papszIter; ++papszIter)
3508 : {
3509 758 : if (STARTS_WITH(*papszIter, pszGridMappingValue) &&
3510 236 : (*papszIter)[nLenGridMappingValue] == '#')
3511 : {
3512 236 : char *pszKey = nullptr;
3513 472 : pszValue = CPLParseNameValue(
3514 236 : *papszIter + nLenGridMappingValue + 1, &pszKey);
3515 236 : if (pszKey && pszValue)
3516 236 : aosGridMappingKeyValues.SetNameValue(pszKey, pszValue);
3517 236 : CPLFree(pszKey);
3518 : }
3519 : }
3520 :
3521 31 : bGotGeogCS = aosGridMappingKeyValues.FetchNameValue(
3522 : CF_PP_SEMI_MAJOR_AXIS) != nullptr;
3523 :
3524 31 : oSRS.importFromCF1(aosGridMappingKeyValues.List(), pszUnits);
3525 31 : bGotCfSRS = oSRS.IsGeographic() || oSRS.IsProjected();
3526 : }
3527 : else
3528 : {
3529 : // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a "crs"
3530 : // attribute hold on the variable of interest that contains a PROJ.4
3531 : // string
3532 515 : pszValue = FetchAttr(nGroupId, nVarId, "crs");
3533 516 : if (pszValue &&
3534 1 : (strstr(pszValue, "+proj=") != nullptr ||
3535 0 : strstr(pszValue, "GEOGCS") != nullptr ||
3536 0 : strstr(pszValue, "PROJCS") != nullptr ||
3537 516 : strstr(pszValue, "EPSG:") != nullptr) &&
3538 1 : oSRS.SetFromUserInput(pszValue) == OGRERR_NONE)
3539 : {
3540 1 : bGotCfSRS = true;
3541 : }
3542 : }
3543 :
3544 : // Set Projection from CF.
3545 546 : double dfLinearUnitsConvFactor = 1.0;
3546 546 : if ((bGotGeogCS || bGotCfSRS))
3547 : {
3548 31 : if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
3549 : {
3550 : // Set SRS Units.
3551 :
3552 : // Check units for x and y.
3553 28 : if (oSRS.IsProjected())
3554 : {
3555 25 : dfLinearUnitsConvFactor = oSRS.GetLinearUnits(nullptr);
3556 :
3557 : // If the user doesn't ask to preserve the axis unit,
3558 : // then normalize to metre
3559 31 : if (dfLinearUnitsConvFactor != 1.0 &&
3560 6 : !CPLFetchBool(GetOpenOptions(), "PRESERVE_AXIS_UNIT_IN_CRS",
3561 : false))
3562 : {
3563 5 : oSRS.SetLinearUnits("metre", 1.0);
3564 5 : oSRS.SetAuthority("PROJCS|UNIT", "EPSG", 9001);
3565 : }
3566 : else
3567 : {
3568 20 : dfLinearUnitsConvFactor = 1.0;
3569 : }
3570 : }
3571 : }
3572 :
3573 : // Set projection.
3574 31 : char *pszTempProjection = nullptr;
3575 31 : oSRS.exportToWkt(&pszTempProjection);
3576 31 : if (pszTempProjection)
3577 : {
3578 31 : CPLDebug("GDAL_netCDF", "setting WKT from CF");
3579 31 : if (returnProjStr != nullptr)
3580 : {
3581 2 : (*returnProjStr) = std::string(pszTempProjection);
3582 : }
3583 : else
3584 : {
3585 29 : m_bAddedProjectionVarsDefs = true;
3586 29 : m_bAddedProjectionVarsData = true;
3587 29 : SetSpatialRefNoUpdate(&oSRS);
3588 : }
3589 : }
3590 31 : CPLFree(pszTempProjection);
3591 : }
3592 :
3593 546 : if (!bReadSRSOnly && (nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 &&
3594 : ydim > 0)
3595 : {
3596 : double *pdfXCoord =
3597 229 : static_cast<double *>(CPLCalloc(xdim, sizeof(double)));
3598 : double *pdfYCoord =
3599 229 : static_cast<double *>(CPLCalloc(ydim, sizeof(double)));
3600 :
3601 229 : size_t start[2] = {0, 0};
3602 229 : size_t edge[2] = {xdim, 0};
3603 229 : int status = nc_get_vara_double(nGroupDimXID, nVarDimXID, start, edge,
3604 : pdfXCoord);
3605 229 : NCDF_ERR(status);
3606 :
3607 229 : edge[0] = ydim;
3608 229 : status = nc_get_vara_double(nGroupDimYID, nVarDimYID, start, edge,
3609 : pdfYCoord);
3610 229 : NCDF_ERR(status);
3611 :
3612 229 : nc_type nc_var_dimx_datatype = NC_NAT;
3613 : status =
3614 229 : nc_inq_vartype(nGroupDimXID, nVarDimXID, &nc_var_dimx_datatype);
3615 229 : NCDF_ERR(status);
3616 :
3617 229 : nc_type nc_var_dimy_datatype = NC_NAT;
3618 : status =
3619 229 : nc_inq_vartype(nGroupDimYID, nVarDimYID, &nc_var_dimy_datatype);
3620 229 : NCDF_ERR(status);
3621 :
3622 229 : if (!poDS->bSwitchedXY)
3623 : {
3624 : // Convert ]180,540] longitude values to ]-180,0].
3625 317 : if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
3626 90 : CPLTestBool(
3627 : CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")))
3628 : {
3629 : // If minimum longitude is > 180, subtract 360 from all.
3630 : // Add a check on the maximum X value too, since
3631 : // NCDFIsVarLongitude() is not very specific by default (see
3632 : // https://github.com/OSGeo/gdal/issues/1440)
3633 97 : if (std::min(pdfXCoord[0], pdfXCoord[xdim - 1]) > 180.0 &&
3634 7 : std::max(pdfXCoord[0], pdfXCoord[xdim - 1]) <= 540)
3635 : {
3636 0 : CPLDebug(
3637 : "GDAL_netCDF",
3638 : "Offsetting longitudes from ]180,540] to ]-180,180]. "
3639 : "Can be disabled with GDAL_NETCDF_CENTERLONG_180=NO");
3640 0 : for (size_t i = 0; i < xdim; i++)
3641 0 : pdfXCoord[i] -= 360;
3642 : }
3643 : }
3644 : }
3645 :
3646 : // Is pixel spacing uniform across the map?
3647 :
3648 : // Check Longitude.
3649 :
3650 229 : bool bLonSpacingOK = false;
3651 229 : if (xdim == 2)
3652 : {
3653 29 : bLonSpacingOK = true;
3654 : }
3655 : else
3656 : {
3657 200 : bool bWestIsLeft = (pdfXCoord[0] < pdfXCoord[xdim - 1]);
3658 :
3659 : // fix longitudes if longitudes should increase from
3660 : // west to east, but west > east
3661 280 : if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
3662 80 : !bWestIsLeft)
3663 : {
3664 2 : size_t ndecreases = 0;
3665 :
3666 : // there is lon wrap if longitudes increase
3667 : // with one single decrease
3668 107 : for (size_t i = 1; i < xdim; i++)
3669 : {
3670 105 : if (pdfXCoord[i] < pdfXCoord[i - 1])
3671 1 : ndecreases++;
3672 : }
3673 :
3674 2 : if (ndecreases == 1)
3675 : {
3676 1 : CPLDebug("GDAL_netCDF", "longitude wrap detected");
3677 4 : for (size_t i = 0; i < xdim; i++)
3678 : {
3679 3 : if (pdfXCoord[i] > pdfXCoord[xdim - 1])
3680 1 : pdfXCoord[i] -= 360;
3681 : }
3682 : }
3683 : }
3684 :
3685 200 : const double dfSpacingBegin = pdfXCoord[1] - pdfXCoord[0];
3686 200 : const double dfSpacingMiddle =
3687 200 : pdfXCoord[xdim / 2 + 1] - pdfXCoord[xdim / 2];
3688 200 : const double dfSpacingLast =
3689 200 : pdfXCoord[xdim - 1] - pdfXCoord[xdim - 2];
3690 :
3691 200 : CPLDebug("GDAL_netCDF",
3692 : "xdim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
3693 : "dfSpacingLast: %f",
3694 : static_cast<long>(xdim), dfSpacingBegin, dfSpacingMiddle,
3695 : dfSpacingLast);
3696 : #ifdef NCDF_DEBUG
3697 : CPLDebug("GDAL_netCDF", "xcoords: %f %f %f %f %f %f", pdfXCoord[0],
3698 : pdfXCoord[1], pdfXCoord[xdim / 2],
3699 : pdfXCoord[(xdim / 2) + 1], pdfXCoord[xdim - 2],
3700 : pdfXCoord[xdim - 1]);
3701 : #endif
3702 :
3703 : // ftp://ftp.cdc.noaa.gov/Datasets/NARR/Dailies/monolevel/vwnd.10m.2015.nc
3704 : // requires a 0.02% tolerance, so let's settle for 0.05%
3705 :
3706 : // For float variables, increase to 0.2% (as seen in
3707 : // https://github.com/OSGeo/gdal/issues/3663)
3708 200 : const double dfEpsRel =
3709 200 : nc_var_dimx_datatype == NC_FLOAT ? 0.002 : 0.0005;
3710 :
3711 : const double dfEps =
3712 : dfEpsRel *
3713 400 : std::max(fabs(dfSpacingBegin),
3714 200 : std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
3715 394 : if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
3716 394 : IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
3717 194 : IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
3718 : {
3719 194 : bLonSpacingOK = true;
3720 : }
3721 6 : else if (CPLTestBool(CPLGetConfigOption(
3722 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
3723 : {
3724 0 : bLonSpacingOK = true;
3725 0 : CPLDebug(
3726 : "GDAL_netCDF",
3727 : "Longitude/X is not equally spaced, but will be considered "
3728 : "as such because of "
3729 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
3730 : }
3731 : }
3732 :
3733 229 : if (bLonSpacingOK == false)
3734 : {
3735 6 : CPLDebug(
3736 : "GDAL_netCDF", "%s",
3737 : "Longitude/X is not equally spaced (with a 0.05% tolerance). "
3738 : "You may set the "
3739 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
3740 : "option to YES to ignore this check");
3741 : }
3742 :
3743 : // Check Latitude.
3744 229 : bool bLatSpacingOK = false;
3745 :
3746 229 : if (ydim == 2)
3747 : {
3748 49 : bLatSpacingOK = true;
3749 : }
3750 : else
3751 : {
3752 180 : const double dfSpacingBegin = pdfYCoord[1] - pdfYCoord[0];
3753 180 : const double dfSpacingMiddle =
3754 180 : pdfYCoord[ydim / 2 + 1] - pdfYCoord[ydim / 2];
3755 :
3756 180 : const double dfSpacingLast =
3757 180 : pdfYCoord[ydim - 1] - pdfYCoord[ydim - 2];
3758 :
3759 180 : CPLDebug("GDAL_netCDF",
3760 : "ydim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
3761 : "dfSpacingLast: %f",
3762 : (long)ydim, dfSpacingBegin, dfSpacingMiddle,
3763 : dfSpacingLast);
3764 : #ifdef NCDF_DEBUG
3765 : CPLDebug("GDAL_netCDF", "ycoords: %f %f %f %f %f %f", pdfYCoord[0],
3766 : pdfYCoord[1], pdfYCoord[ydim / 2],
3767 : pdfYCoord[(ydim / 2) + 1], pdfYCoord[ydim - 2],
3768 : pdfYCoord[ydim - 1]);
3769 : #endif
3770 :
3771 180 : const double dfEpsRel =
3772 180 : nc_var_dimy_datatype == NC_FLOAT ? 0.002 : 0.0005;
3773 :
3774 : const double dfEps =
3775 : dfEpsRel *
3776 360 : std::max(fabs(dfSpacingBegin),
3777 180 : std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
3778 358 : if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
3779 358 : IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
3780 169 : IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
3781 : {
3782 169 : bLatSpacingOK = true;
3783 : }
3784 11 : else if (CPLTestBool(CPLGetConfigOption(
3785 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
3786 : {
3787 0 : bLatSpacingOK = true;
3788 0 : CPLDebug(
3789 : "GDAL_netCDF",
3790 : "Latitude/Y is not equally spaced, but will be considered "
3791 : "as such because of "
3792 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
3793 : }
3794 11 : else if (!oSRS.IsProjected() &&
3795 11 : fabs(dfSpacingBegin - dfSpacingLast) <= 0.1 &&
3796 30 : fabs(dfSpacingBegin - dfSpacingMiddle) <= 0.1 &&
3797 8 : fabs(dfSpacingMiddle - dfSpacingLast) <= 0.1)
3798 : {
3799 8 : bLatSpacingOK = true;
3800 8 : CPLError(CE_Warning, CPLE_AppDefined,
3801 : "Latitude grid not spaced evenly. "
3802 : "Setting projection for grid spacing is "
3803 : "within 0.1 degrees threshold.");
3804 :
3805 8 : CPLDebug("GDAL_netCDF",
3806 : "Latitude grid not spaced evenly, but within 0.1 "
3807 : "degree threshold (probably a Gaussian grid). "
3808 : "Saving original latitude values in Y_VALUES "
3809 : "geolocation metadata");
3810 8 : Set1DGeolocation(nGroupDimYID, nVarDimYID, "Y");
3811 : }
3812 :
3813 180 : if (bLatSpacingOK == false)
3814 : {
3815 3 : CPLDebug(
3816 : "GDAL_netCDF", "%s",
3817 : "Latitude/Y is not equally spaced (with a 0.05% "
3818 : "tolerance). "
3819 : "You may set the "
3820 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
3821 : "option to YES to ignore this check");
3822 : }
3823 : }
3824 :
3825 229 : if (bLonSpacingOK && bLatSpacingOK)
3826 : {
3827 : // We have gridded data so we can set the Georeferencing info.
3828 :
3829 : // Enable GeoTransform.
3830 :
3831 : // In the following "actual_range" and "node_offset"
3832 : // are attributes used by netCDF files created by GMT.
3833 : // If we find them we know how to proceed. Else, use
3834 : // the original algorithm.
3835 222 : bGotCfGT = true;
3836 :
3837 222 : int node_offset = 0;
3838 : const bool bUseActualRange =
3839 222 : NCDFResolveAttInt(nGroupId, NC_GLOBAL, "node_offset",
3840 222 : &node_offset) == CE_None;
3841 :
3842 222 : double adfActualRange[2] = {0.0, 0.0};
3843 222 : double xMinMax[2] = {0.0, 0.0};
3844 222 : double yMinMax[2] = {0.0, 0.0};
3845 :
3846 : const auto RoundMinMaxForFloatVals =
3847 60 : [](double &dfMin, double &dfMax, int nIntervals)
3848 : {
3849 : // Helps for a case where longitudes range from
3850 : // -179.99 to 180.0 with a 0.01 degree spacing.
3851 : // However as this is encoded in a float array,
3852 : // -179.99 is actually read as -179.99000549316406 as
3853 : // a double. Try to detect that and correct the rounding
3854 :
3855 88 : const auto IsAlmostInteger = [](double dfVal)
3856 : {
3857 88 : constexpr double THRESHOLD_INTEGER = 1e-3;
3858 88 : return std::fabs(dfVal - std::round(dfVal)) <=
3859 88 : THRESHOLD_INTEGER;
3860 : };
3861 :
3862 60 : const double dfSpacing = (dfMax - dfMin) / nIntervals;
3863 60 : if (dfSpacing > 0)
3864 : {
3865 48 : const double dfInvSpacing = 1.0 / dfSpacing;
3866 48 : if (IsAlmostInteger(dfInvSpacing))
3867 : {
3868 20 : const double dfRoundedSpacing =
3869 20 : 1.0 / std::round(dfInvSpacing);
3870 20 : const double dfMinDivRoundedSpacing =
3871 20 : dfMin / dfRoundedSpacing;
3872 20 : const double dfMaxDivRoundedSpacing =
3873 20 : dfMax / dfRoundedSpacing;
3874 40 : if (IsAlmostInteger(dfMinDivRoundedSpacing) &&
3875 20 : IsAlmostInteger(dfMaxDivRoundedSpacing))
3876 : {
3877 20 : const double dfRoundedMin =
3878 20 : std::round(dfMinDivRoundedSpacing) *
3879 : dfRoundedSpacing;
3880 20 : const double dfRoundedMax =
3881 20 : std::round(dfMaxDivRoundedSpacing) *
3882 : dfRoundedSpacing;
3883 20 : if (static_cast<float>(dfMin) ==
3884 20 : static_cast<float>(dfRoundedMin) &&
3885 8 : static_cast<float>(dfMax) ==
3886 8 : static_cast<float>(dfRoundedMax))
3887 : {
3888 7 : dfMin = dfRoundedMin;
3889 7 : dfMax = dfRoundedMax;
3890 : }
3891 : }
3892 : }
3893 : }
3894 60 : };
3895 :
3896 225 : if (bUseActualRange &&
3897 3 : !nc_get_att_double(nGroupDimXID, nVarDimXID, "actual_range",
3898 : adfActualRange))
3899 : {
3900 1 : xMinMax[0] = adfActualRange[0];
3901 1 : xMinMax[1] = adfActualRange[1];
3902 :
3903 : // Present xMinMax[] in the same order as padfXCoord
3904 1 : if ((xMinMax[0] - xMinMax[1]) *
3905 1 : (pdfXCoord[0] - pdfXCoord[xdim - 1]) <
3906 : 0)
3907 : {
3908 0 : std::swap(xMinMax[0], xMinMax[1]);
3909 : }
3910 : }
3911 : else
3912 : {
3913 221 : xMinMax[0] = pdfXCoord[0];
3914 221 : xMinMax[1] = pdfXCoord[xdim - 1];
3915 221 : node_offset = 0;
3916 :
3917 221 : if (nc_var_dimx_datatype == NC_FLOAT)
3918 : {
3919 30 : RoundMinMaxForFloatVals(xMinMax[0], xMinMax[1],
3920 30 : poDS->nRasterXSize - 1);
3921 : }
3922 : }
3923 :
3924 225 : if (bUseActualRange &&
3925 3 : !nc_get_att_double(nGroupDimYID, nVarDimYID, "actual_range",
3926 : adfActualRange))
3927 : {
3928 1 : yMinMax[0] = adfActualRange[0];
3929 1 : yMinMax[1] = adfActualRange[1];
3930 :
3931 : // Present yMinMax[] in the same order as pdfYCoord
3932 1 : if ((yMinMax[0] - yMinMax[1]) *
3933 1 : (pdfYCoord[0] - pdfYCoord[ydim - 1]) <
3934 : 0)
3935 : {
3936 0 : std::swap(yMinMax[0], yMinMax[1]);
3937 : }
3938 : }
3939 : else
3940 : {
3941 221 : yMinMax[0] = pdfYCoord[0];
3942 221 : yMinMax[1] = pdfYCoord[ydim - 1];
3943 221 : node_offset = 0;
3944 :
3945 221 : if (nc_var_dimy_datatype == NC_FLOAT)
3946 : {
3947 30 : RoundMinMaxForFloatVals(yMinMax[0], yMinMax[1],
3948 30 : poDS->nRasterYSize - 1);
3949 : }
3950 : }
3951 :
3952 222 : double dfCoordOffset = 0.0;
3953 222 : double dfCoordScale = 1.0;
3954 222 : if (!nc_get_att_double(nGroupId, nVarDimXID, CF_ADD_OFFSET,
3955 226 : &dfCoordOffset) &&
3956 4 : !nc_get_att_double(nGroupId, nVarDimXID, CF_SCALE_FACTOR,
3957 : &dfCoordScale))
3958 : {
3959 4 : xMinMax[0] = dfCoordOffset + xMinMax[0] * dfCoordScale;
3960 4 : xMinMax[1] = dfCoordOffset + xMinMax[1] * dfCoordScale;
3961 : }
3962 :
3963 222 : if (!nc_get_att_double(nGroupId, nVarDimYID, CF_ADD_OFFSET,
3964 226 : &dfCoordOffset) &&
3965 4 : !nc_get_att_double(nGroupId, nVarDimYID, CF_SCALE_FACTOR,
3966 : &dfCoordScale))
3967 : {
3968 4 : yMinMax[0] = dfCoordOffset + yMinMax[0] * dfCoordScale;
3969 4 : yMinMax[1] = dfCoordOffset + yMinMax[1] * dfCoordScale;
3970 : }
3971 :
3972 : // Check for reverse order of y-coordinate.
3973 222 : if (!bSwitchedXY)
3974 : {
3975 220 : poDS->bBottomUp = (yMinMax[0] <= yMinMax[1]);
3976 220 : if (!poDS->bBottomUp)
3977 : {
3978 32 : std::swap(yMinMax[0], yMinMax[1]);
3979 : }
3980 : }
3981 :
3982 : // Geostationary satellites can specify units in (micro)radians
3983 : // So we check if they do, and if so convert to linear units
3984 : // (meters)
3985 222 : const char *pszProjName = oSRS.GetAttrValue("PROJECTION");
3986 222 : if (pszProjName != nullptr)
3987 : {
3988 24 : if (EQUAL(pszProjName, SRS_PT_GEOSTATIONARY_SATELLITE))
3989 : {
3990 : double satelliteHeight =
3991 3 : oSRS.GetProjParm(SRS_PP_SATELLITE_HEIGHT, 1.0);
3992 3 : size_t nAttlen = 0;
3993 : char szUnits[NC_MAX_NAME + 1];
3994 3 : szUnits[0] = '\0';
3995 3 : nc_type nAttype = NC_NAT;
3996 3 : nc_inq_att(nGroupId, nVarDimXID, "units", &nAttype,
3997 : &nAttlen);
3998 6 : if (nAttlen < sizeof(szUnits) &&
3999 3 : nc_get_att_text(nGroupId, nVarDimXID, "units",
4000 : szUnits) == NC_NOERR)
4001 : {
4002 3 : szUnits[nAttlen] = '\0';
4003 3 : if (EQUAL(szUnits, "microradian"))
4004 : {
4005 1 : xMinMax[0] =
4006 1 : xMinMax[0] * satelliteHeight * 0.000001;
4007 1 : xMinMax[1] =
4008 1 : xMinMax[1] * satelliteHeight * 0.000001;
4009 : }
4010 2 : else if (EQUAL(szUnits, "rad") ||
4011 1 : EQUAL(szUnits, "radian"))
4012 : {
4013 2 : xMinMax[0] = xMinMax[0] * satelliteHeight;
4014 2 : xMinMax[1] = xMinMax[1] * satelliteHeight;
4015 : }
4016 : }
4017 3 : szUnits[0] = '\0';
4018 3 : nc_inq_att(nGroupId, nVarDimYID, "units", &nAttype,
4019 : &nAttlen);
4020 6 : if (nAttlen < sizeof(szUnits) &&
4021 3 : nc_get_att_text(nGroupId, nVarDimYID, "units",
4022 : szUnits) == NC_NOERR)
4023 : {
4024 3 : szUnits[nAttlen] = '\0';
4025 3 : if (EQUAL(szUnits, "microradian"))
4026 : {
4027 1 : yMinMax[0] =
4028 1 : yMinMax[0] * satelliteHeight * 0.000001;
4029 1 : yMinMax[1] =
4030 1 : yMinMax[1] * satelliteHeight * 0.000001;
4031 : }
4032 2 : else if (EQUAL(szUnits, "rad") ||
4033 1 : EQUAL(szUnits, "radian"))
4034 : {
4035 2 : yMinMax[0] = yMinMax[0] * satelliteHeight;
4036 2 : yMinMax[1] = yMinMax[1] * satelliteHeight;
4037 : }
4038 : }
4039 : }
4040 : }
4041 :
4042 222 : tmpGT[0] = xMinMax[0];
4043 444 : tmpGT[1] = (xMinMax[1] - xMinMax[0]) /
4044 222 : (poDS->nRasterXSize + (node_offset - 1));
4045 222 : tmpGT[2] = 0;
4046 222 : if (bSwitchedXY)
4047 : {
4048 2 : tmpGT[3] = yMinMax[0];
4049 2 : tmpGT[4] = 0;
4050 2 : tmpGT[5] = (yMinMax[1] - yMinMax[0]) /
4051 2 : (poDS->nRasterYSize + (node_offset - 1));
4052 : }
4053 : else
4054 : {
4055 220 : tmpGT[3] = yMinMax[1];
4056 220 : tmpGT[4] = 0;
4057 220 : tmpGT[5] = (yMinMax[0] - yMinMax[1]) /
4058 220 : (poDS->nRasterYSize + (node_offset - 1));
4059 : }
4060 :
4061 : // Compute the center of the pixel.
4062 222 : if (!node_offset)
4063 : {
4064 : // Otherwise its already the pixel center.
4065 222 : tmpGT[0] -= (tmpGT[1] / 2);
4066 222 : tmpGT[3] -= (tmpGT[5] / 2);
4067 : }
4068 : }
4069 :
4070 : const auto AreSRSEqualThroughProj4String =
4071 2 : [](const OGRSpatialReference &oSRS1,
4072 : const OGRSpatialReference &oSRS2)
4073 : {
4074 2 : char *pszProj4Str1 = nullptr;
4075 2 : oSRS1.exportToProj4(&pszProj4Str1);
4076 :
4077 2 : char *pszProj4Str2 = nullptr;
4078 2 : oSRS2.exportToProj4(&pszProj4Str2);
4079 :
4080 : {
4081 2 : char *pszTmp = strstr(pszProj4Str1, "+datum=");
4082 2 : if (pszTmp)
4083 0 : memcpy(pszTmp, "+ellps=", strlen("+ellps="));
4084 : }
4085 :
4086 : {
4087 2 : char *pszTmp = strstr(pszProj4Str2, "+datum=");
4088 2 : if (pszTmp)
4089 2 : memcpy(pszTmp, "+ellps=", strlen("+ellps="));
4090 : }
4091 :
4092 2 : bool bRet = false;
4093 2 : if (pszProj4Str1 && pszProj4Str2 &&
4094 2 : EQUAL(pszProj4Str1, pszProj4Str2))
4095 : {
4096 1 : bRet = true;
4097 : }
4098 :
4099 2 : CPLFree(pszProj4Str1);
4100 2 : CPLFree(pszProj4Str2);
4101 2 : return bRet;
4102 : };
4103 :
4104 229 : if (dfLinearUnitsConvFactor != 1.0)
4105 : {
4106 35 : for (int i = 0; i < 6; ++i)
4107 30 : tmpGT[i] *= dfLinearUnitsConvFactor;
4108 :
4109 5 : if (paosRemovedMDItems)
4110 : {
4111 : char szVarNameX[NC_MAX_NAME + 1];
4112 5 : CPL_IGNORE_RET_VAL(
4113 5 : nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
4114 :
4115 : char szVarNameY[NC_MAX_NAME + 1];
4116 5 : CPL_IGNORE_RET_VAL(
4117 5 : nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
4118 :
4119 5 : paosRemovedMDItems->push_back(
4120 : CPLSPrintf("%s#units", szVarNameX));
4121 5 : paosRemovedMDItems->push_back(
4122 : CPLSPrintf("%s#units", szVarNameY));
4123 : }
4124 : }
4125 :
4126 : // If there is a global "geospatial_bounds_crs" attribute, check that it
4127 : // is consistent with the SRS, and if so, use it as the SRS
4128 : const char *pszGBCRS =
4129 229 : FetchAttr(nGroupId, NC_GLOBAL, "geospatial_bounds_crs");
4130 229 : if (pszGBCRS && STARTS_WITH(pszGBCRS, "EPSG:"))
4131 : {
4132 4 : OGRSpatialReference oSRSFromGBCRS;
4133 2 : oSRSFromGBCRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
4134 2 : if (oSRSFromGBCRS.SetFromUserInput(
4135 : pszGBCRS,
4136 : OGRSpatialReference::
4137 4 : SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE &&
4138 2 : AreSRSEqualThroughProj4String(oSRS, oSRSFromGBCRS))
4139 : {
4140 1 : oSRS = std::move(oSRSFromGBCRS);
4141 1 : SetSpatialRefNoUpdate(&oSRS);
4142 : }
4143 : }
4144 :
4145 229 : CPLFree(pdfXCoord);
4146 229 : CPLFree(pdfYCoord);
4147 : } // end if(has dims)
4148 :
4149 : // Process custom GeoTransform GDAL value.
4150 546 : if (!EQUAL(pszGridMappingValue, ""))
4151 : {
4152 231 : if (pszGeoTransform != nullptr)
4153 : {
4154 : const CPLStringList aosGeoTransform(
4155 240 : CSLTokenizeString2(pszGeoTransform, " ", CSLT_HONOURSTRINGS));
4156 120 : if (aosGeoTransform.size() == 6)
4157 : {
4158 120 : bool bUseGeoTransformFromAttribute = true;
4159 :
4160 120 : GDALGeoTransform gtFromAttribute;
4161 840 : for (int i = 0; i < 6; i++)
4162 : {
4163 720 : gtFromAttribute[i] = CPLAtof(aosGeoTransform[i]);
4164 : }
4165 :
4166 : // When GDAL writes a raster that is north-up oriented, it
4167 : // writes the "GeoTransform" attribute unmodified, that is with
4168 : // gt.yscale < 0, but the first line is actually the southern-most
4169 : // one, consistently with the values of the "y" coordinate
4170 : // variable. This is wrong... but we have always done that, so
4171 : // this is hard to fix now.
4172 : // However there are datasets like
4173 : // https://public.hub.geosphere.at/datahub/resources/spartacus-v2-1d-1km/filelisting/TN/SPARTACUS2-DAILY_TN_2026.nc
4174 : // that correctly use a positive gt.yscale value. So make sure to not emit
4175 : // a warning when comparing against the geotransform derived from
4176 : // the x/y coordinates.
4177 120 : GDALGeoTransform gtFromAttributeNorthUp = gtFromAttribute;
4178 121 : if (gtFromAttributeNorthUp.yscale > 0 &&
4179 1 : gtFromAttributeNorthUp.IsAxisAligned())
4180 : {
4181 1 : gtFromAttributeNorthUp.yorig +=
4182 1 : poDS->nRasterYSize * gtFromAttributeNorthUp.yscale;
4183 1 : gtFromAttributeNorthUp.yscale =
4184 1 : -gtFromAttributeNorthUp.yscale;
4185 : }
4186 :
4187 120 : if (bGotCfGT)
4188 : {
4189 98 : constexpr double GT_RELERROR_WARN_THRESHOLD = 1e-6;
4190 98 : double dfMaxAbsoluteError = 0.0;
4191 686 : for (int i = 0; i < 6; i++)
4192 : {
4193 : double dfAbsoluteError =
4194 588 : std::abs(tmpGT[i] - gtFromAttributeNorthUp[i]);
4195 588 : if (dfAbsoluteError >
4196 588 : std::abs(gtFromAttributeNorthUp[i] *
4197 : GT_RELERROR_WARN_THRESHOLD))
4198 : {
4199 3 : dfMaxAbsoluteError =
4200 3 : std::max(dfMaxAbsoluteError, dfAbsoluteError);
4201 : }
4202 : }
4203 :
4204 98 : if (dfMaxAbsoluteError > 0)
4205 : {
4206 3 : bUseGeoTransformFromAttribute = false;
4207 3 : CPLError(CE_Warning, CPLE_AppDefined,
4208 : "GeoTransform read from attribute of %s "
4209 : "variable differs from value calculated from "
4210 : "dimension variables (max diff = %g). Using "
4211 : "value calculated from dimension variables.",
4212 : pszGridMappingValue, dfMaxAbsoluteError);
4213 : }
4214 : }
4215 :
4216 120 : if (bUseGeoTransformFromAttribute)
4217 : {
4218 117 : if (bGotCfGT)
4219 : {
4220 95 : tmpGT = gtFromAttributeNorthUp;
4221 95 : if (gtFromAttributeNorthUp.IsAxisAligned())
4222 : {
4223 95 : poDS->bBottomUp = true;
4224 : }
4225 : }
4226 : else
4227 : {
4228 22 : tmpGT = gtFromAttribute;
4229 : }
4230 117 : bGotGdalGT = true;
4231 : }
4232 : }
4233 : }
4234 : else
4235 : {
4236 : // Look for corner array values.
4237 : // CPLDebug("GDAL_netCDF",
4238 : // "looking for geotransform corners");
4239 111 : bool bGotNN = false;
4240 111 : double dfNN = FetchCopyParam(pszGridMappingValue,
4241 : "Northernmost_Northing", 0, &bGotNN);
4242 :
4243 111 : bool bGotSN = false;
4244 111 : double dfSN = FetchCopyParam(pszGridMappingValue,
4245 : "Southernmost_Northing", 0, &bGotSN);
4246 :
4247 111 : bool bGotEE = false;
4248 111 : double dfEE = FetchCopyParam(pszGridMappingValue,
4249 : "Easternmost_Easting", 0, &bGotEE);
4250 :
4251 111 : bool bGotWE = false;
4252 111 : double dfWE = FetchCopyParam(pszGridMappingValue,
4253 : "Westernmost_Easting", 0, &bGotWE);
4254 :
4255 : // Only set the GeoTransform if we got all the values.
4256 111 : if (bGotNN && bGotSN && bGotEE && bGotWE)
4257 : {
4258 0 : bGotGdalGT = true;
4259 :
4260 0 : tmpGT[0] = dfWE;
4261 0 : tmpGT[1] = (dfEE - dfWE) / (poDS->GetRasterXSize() - 1);
4262 0 : tmpGT[2] = 0.0;
4263 0 : tmpGT[3] = dfNN;
4264 0 : tmpGT[4] = 0.0;
4265 0 : tmpGT[5] = (dfSN - dfNN) / (poDS->GetRasterYSize() - 1);
4266 : // Compute the center of the pixel.
4267 0 : tmpGT[0] = dfWE - (tmpGT[1] / 2);
4268 0 : tmpGT[3] = dfNN - (tmpGT[5] / 2);
4269 : }
4270 : } // (pszGeoTransform != NULL)
4271 :
4272 231 : if (bGotGdalSRS && !bGotGdalGT)
4273 78 : CPLDebug("GDAL_netCDF", "Got SRS but no geotransform from GDAL!");
4274 : }
4275 :
4276 546 : if (bGotCfGT || bGotGdalGT)
4277 : {
4278 244 : CPLDebug("GDAL_netCDF", "set bBottomUp = %d from Y axis",
4279 244 : static_cast<int>(poDS->bBottomUp));
4280 : }
4281 :
4282 546 : if (!pszWKT && !bGotCfSRS)
4283 : {
4284 : // Some netCDF files have a srid attribute (#6613) like
4285 : // urn:ogc:def:crs:EPSG::6931
4286 315 : const char *pszSRID = FetchAttr(pszGridMappingValue, "srid");
4287 315 : if (pszSRID != nullptr)
4288 : {
4289 0 : oSRS.Clear();
4290 0 : if (oSRS.SetFromUserInput(
4291 : pszSRID,
4292 : OGRSpatialReference::
4293 0 : SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE)
4294 : {
4295 0 : char *pszWKTExport = nullptr;
4296 0 : CPLDebug("GDAL_netCDF", "Got SRS from %s", pszSRID);
4297 0 : oSRS.exportToWkt(&pszWKTExport);
4298 0 : if (returnProjStr != nullptr)
4299 : {
4300 0 : (*returnProjStr) = std::string(pszWKTExport);
4301 : }
4302 : else
4303 : {
4304 0 : m_bAddedProjectionVarsDefs = true;
4305 0 : m_bAddedProjectionVarsData = true;
4306 0 : SetSpatialRefNoUpdate(&oSRS);
4307 : }
4308 0 : CPLFree(pszWKTExport);
4309 : }
4310 : }
4311 : }
4312 :
4313 546 : CPLFree(pszGridMappingValue);
4314 :
4315 546 : if (bReadSRSOnly)
4316 190 : return;
4317 :
4318 : // Determines the SRS to be used by the geolocation array, if any
4319 712 : std::string osGeolocWKT = SRS_WKT_WGS84_LAT_LONG;
4320 356 : if (!m_oSRS.IsEmpty())
4321 : {
4322 274 : OGRSpatialReference oGeogCRS;
4323 137 : oGeogCRS.CopyGeogCSFrom(&m_oSRS);
4324 137 : char *pszWKTTmp = nullptr;
4325 137 : const char *const apszOptions[] = {"FORMAT=WKT2_2019", nullptr};
4326 137 : if (oGeogCRS.exportToWkt(&pszWKTTmp, apszOptions) == OGRERR_NONE)
4327 : {
4328 137 : osGeolocWKT = pszWKTTmp;
4329 : }
4330 137 : CPLFree(pszWKTTmp);
4331 : }
4332 :
4333 : // Process geolocation arrays from CF "coordinates" attribute.
4334 712 : std::string osGeolocXName, osGeolocYName;
4335 356 : if (ProcessCFGeolocation(nGroupId, nVarId, osGeolocWKT, osGeolocXName,
4336 356 : osGeolocYName))
4337 : {
4338 53 : bool bCanCancelGT = true;
4339 53 : if ((nVarDimXID != -1) && (nVarDimYID != -1))
4340 : {
4341 : char szVarNameX[NC_MAX_NAME + 1];
4342 44 : CPL_IGNORE_RET_VAL(
4343 44 : nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
4344 : char szVarNameY[NC_MAX_NAME + 1];
4345 44 : CPL_IGNORE_RET_VAL(
4346 44 : nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
4347 44 : bCanCancelGT =
4348 44 : !(osGeolocXName == szVarNameX && osGeolocYName == szVarNameY);
4349 : }
4350 88 : if (bCanCancelGT && !m_oSRS.IsGeographic() && !m_oSRS.IsProjected() &&
4351 35 : !bSwitchedXY)
4352 : {
4353 33 : bGotCfGT = false;
4354 : }
4355 : }
4356 121 : else if (!bGotCfGT && !bReadSRSOnly && (nVarDimXID != -1) &&
4357 427 : (nVarDimYID != -1) && xdim > 0 && ydim > 0 &&
4358 3 : ((!bSwitchedXY &&
4359 3 : NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
4360 1 : NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr)) ||
4361 2 : (bSwitchedXY &&
4362 0 : NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
4363 0 : NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr))))
4364 : {
4365 : // Case of autotest/gdrivers/data/netcdf/GLMELT_4X5.OCN.nc
4366 : // which is indexed by lat, lon variables, but lat has irregular
4367 : // spacing.
4368 1 : const char *pszGeolocXFullName = poDS->papszDimName[poDS->nXDimID];
4369 1 : const char *pszGeolocYFullName = poDS->papszDimName[poDS->nYDimID];
4370 1 : if (bSwitchedXY)
4371 : {
4372 0 : std::swap(pszGeolocXFullName, pszGeolocYFullName);
4373 0 : GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", "GEOLOCATION");
4374 : }
4375 :
4376 1 : CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
4377 : pszGeolocXFullName, pszGeolocYFullName);
4378 :
4379 1 : GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
4380 : "GEOLOCATION");
4381 :
4382 2 : CPLString osTMP;
4383 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4384 1 : pszGeolocXFullName);
4385 :
4386 1 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
4387 1 : GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
4388 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4389 1 : pszGeolocYFullName);
4390 :
4391 1 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
4392 1 : GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
4393 :
4394 1 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
4395 1 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
4396 :
4397 1 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
4398 1 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
4399 :
4400 1 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
4401 : "PIXEL_CENTER", "GEOLOCATION");
4402 : }
4403 :
4404 : // Set GeoTransform if we got a complete one - after projection has been set
4405 356 : if (bGotCfGT || bGotGdalGT)
4406 : {
4407 204 : m_bAddedProjectionVarsDefs = true;
4408 204 : m_bAddedProjectionVarsData = true;
4409 204 : SetGeoTransformNoUpdate(tmpGT);
4410 : }
4411 :
4412 : // Debugging reports.
4413 356 : CPLDebug("GDAL_netCDF",
4414 : "bGotGeogCS=%d bGotCfSRS=%d bGotCfGT=%d bGotCfWktSRS=%d "
4415 : "bGotGdalSRS=%d bGotGdalGT=%d",
4416 : static_cast<int>(bGotGeogCS), static_cast<int>(bGotCfSRS),
4417 : static_cast<int>(bGotCfGT), static_cast<int>(bGotCfWktSRS),
4418 : static_cast<int>(bGotGdalSRS), static_cast<int>(bGotGdalGT));
4419 :
4420 356 : if (!bGotCfGT && !bGotGdalGT)
4421 152 : CPLDebug("GDAL_netCDF", "did not get geotransform from CF nor GDAL!");
4422 :
4423 356 : if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfGT && !bGotCfWktSRS)
4424 152 : CPLDebug("GDAL_netCDF", "did not get projection from CF nor GDAL!");
4425 :
4426 : // wish of 6195
4427 : // we don't have CS/SRS, but we do have GT, and we live in -180,360 -90,90
4428 356 : if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfWktSRS)
4429 : {
4430 219 : if (bGotCfGT || bGotGdalGT)
4431 : {
4432 134 : bool bAssumedLongLat = CPLTestBool(CSLFetchNameValueDef(
4433 67 : papszOpenOptions, "ASSUME_LONGLAT",
4434 : CPLGetConfigOption("GDAL_NETCDF_ASSUME_LONGLAT", "NO")));
4435 :
4436 2 : if (bAssumedLongLat && tmpGT[0] >= -180 && tmpGT[0] < 360 &&
4437 2 : (tmpGT[0] + tmpGT[1] * poDS->GetRasterXSize()) <= 360 &&
4438 71 : tmpGT[3] <= 90 && tmpGT[3] > -90 &&
4439 2 : (tmpGT[3] + tmpGT[5] * poDS->GetRasterYSize()) >= -90)
4440 : {
4441 :
4442 2 : poDS->bIsGeographic = true;
4443 2 : char *pszTempProjection = nullptr;
4444 : // seems odd to use 4326 so OGC:CRS84
4445 2 : oSRS.SetFromUserInput("OGC:CRS84");
4446 2 : oSRS.exportToWkt(&pszTempProjection);
4447 2 : if (returnProjStr != nullptr)
4448 : {
4449 0 : (*returnProjStr) = std::string(pszTempProjection);
4450 : }
4451 : else
4452 : {
4453 2 : m_bAddedProjectionVarsDefs = true;
4454 2 : m_bAddedProjectionVarsData = true;
4455 2 : SetSpatialRefNoUpdate(&oSRS);
4456 : }
4457 2 : CPLFree(pszTempProjection);
4458 :
4459 2 : CPLDebug("netCDF",
4460 : "Assumed Longitude Latitude CRS 'OGC:CRS84' because "
4461 : "none otherwise available and geotransform within "
4462 : "suitable bounds. "
4463 : "Set GDAL_NETCDF_ASSUME_LONGLAT=NO as configuration "
4464 : "option or "
4465 : " ASSUME_LONGLAT=NO as open option to bypass this "
4466 : "assumption.");
4467 : }
4468 : }
4469 : }
4470 :
4471 : // Search for Well-known GeogCS if got only CF WKT
4472 : // Disabled for now, as a named datum also include control points
4473 : // (see mailing list and bug#4281
4474 : // For example, WGS84 vs. GDA94 (EPSG:3577) - AEA in netcdf_cf.py
4475 :
4476 : // Disabled for now, but could be set in a config option.
4477 : #if 0
4478 : bool bLookForWellKnownGCS = false; // This could be a Config Option.
4479 :
4480 : if( bLookForWellKnownGCS && bGotCfSRS && !bGotGdalSRS )
4481 : {
4482 : // ET - Could use a more exhaustive method by scanning all EPSG codes in
4483 : // data/gcs.csv as proposed by Even in the gdal-dev mailing list "help
4484 : // for comparing two WKT".
4485 : // This code could be contributed to a new function.
4486 : // OGRSpatialReference * OGRSpatialReference::FindMatchingGeogCS(
4487 : // const OGRSpatialReference *poOther) */
4488 : CPLDebug("GDAL_netCDF", "Searching for Well-known GeogCS");
4489 : const char *pszWKGCSList[] = { "WGS84", "WGS72", "NAD27", "NAD83" };
4490 : char *pszWKGCS = NULL;
4491 : oSRS.exportToPrettyWkt(&pszWKGCS);
4492 : for( size_t i = 0; i < sizeof(pszWKGCSList) / 8; i++ )
4493 : {
4494 : pszWKGCS = CPLStrdup(pszWKGCSList[i]);
4495 : OGRSpatialReference oSRSTmp;
4496 : oSRSTmp.SetWellKnownGeogCS(pszWKGCSList[i]);
4497 : // Set datum to unknown, bug #4281.
4498 : if( oSRSTmp.GetAttrNode("DATUM" ) )
4499 : oSRSTmp.GetAttrNode("DATUM")->GetChild(0)->SetValue("unknown");
4500 : // Could use OGRSpatialReference::StripCTParms(), but let's keep
4501 : // TOWGS84.
4502 : oSRSTmp.GetRoot()->StripNodes("AXIS");
4503 : oSRSTmp.GetRoot()->StripNodes("AUTHORITY");
4504 : oSRSTmp.GetRoot()->StripNodes("EXTENSION");
4505 :
4506 : oSRSTmp.exportToPrettyWkt(&pszWKGCS);
4507 : if( oSRS.IsSameGeogCS(&oSRSTmp) )
4508 : {
4509 : oSRS.SetWellKnownGeogCS(pszWKGCSList[i]);
4510 : oSRS.exportToWkt(&(pszTempProjection));
4511 : SetProjection(pszTempProjection);
4512 : CPLFree(pszTempProjection);
4513 : }
4514 : }
4515 : }
4516 : #endif
4517 : }
4518 :
4519 147 : void netCDFDataset::SetProjectionFromVar(int nGroupId, int nVarId,
4520 : bool bReadSRSOnly)
4521 : {
4522 147 : SetProjectionFromVar(nGroupId, nVarId, bReadSRSOnly, nullptr, nullptr,
4523 : nullptr, nullptr);
4524 147 : }
4525 :
4526 292 : bool netCDFDataset::ProcessNASAL2OceanGeoLocation(int nGroupId, int nVarId)
4527 : {
4528 : // Cf https://oceancolor.gsfc.nasa.gov/docs/format/l2nc/
4529 : // and https://github.com/OSGeo/gdal/issues/7605
4530 :
4531 : // Check for a structure like:
4532 : /* netcdf SNPP_VIIRS.20230406T024200.L2.OC.NRT {
4533 : dimensions:
4534 : number_of_lines = 3248 ;
4535 : pixels_per_line = 3200 ;
4536 : [...]
4537 : pixel_control_points = 3200 ;
4538 : [...]
4539 : group: geophysical_data {
4540 : variables:
4541 : short aot_862(number_of_lines, pixels_per_line) ; <-- nVarId
4542 : [...]
4543 : }
4544 : group: navigation_data {
4545 : variables:
4546 : float longitude(number_of_lines, pixel_control_points) ;
4547 : [...]
4548 : float latitude(number_of_lines, pixel_control_points) ;
4549 : [...]
4550 : }
4551 : }
4552 : */
4553 : // Note that the longitude and latitude arrays are not indexed by the
4554 : // same dimensions. Handle only the case where
4555 : // pixel_control_points == pixels_per_line
4556 : // If there was a subsampling of the geolocation arrays, we'd need to
4557 : // add more logic.
4558 :
4559 584 : std::string osGroupName;
4560 292 : osGroupName.resize(NC_MAX_NAME);
4561 292 : NCDF_ERR(nc_inq_grpname(nGroupId, &osGroupName[0]));
4562 292 : osGroupName.resize(strlen(osGroupName.data()));
4563 292 : if (osGroupName != "geophysical_data")
4564 291 : return false;
4565 :
4566 1 : int nVarDims = 0;
4567 1 : NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
4568 1 : if (nVarDims != 2)
4569 0 : return false;
4570 :
4571 1 : int nNavigationDataGrpId = 0;
4572 1 : if (nc_inq_grp_ncid(cdfid, "navigation_data", &nNavigationDataGrpId) !=
4573 : NC_NOERR)
4574 0 : return false;
4575 :
4576 : std::array<int, 2> anVarDimIds;
4577 1 : NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
4578 :
4579 1 : int nLongitudeId = 0;
4580 1 : int nLatitudeId = 0;
4581 1 : if (nc_inq_varid(nNavigationDataGrpId, "longitude", &nLongitudeId) !=
4582 2 : NC_NOERR ||
4583 1 : nc_inq_varid(nNavigationDataGrpId, "latitude", &nLatitudeId) !=
4584 : NC_NOERR)
4585 : {
4586 0 : return false;
4587 : }
4588 :
4589 1 : int nDimsLongitude = 0;
4590 1 : NCDF_ERR(
4591 : nc_inq_varndims(nNavigationDataGrpId, nLongitudeId, &nDimsLongitude));
4592 1 : int nDimsLatitude = 0;
4593 1 : NCDF_ERR(
4594 : nc_inq_varndims(nNavigationDataGrpId, nLatitudeId, &nDimsLatitude));
4595 1 : if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
4596 : {
4597 0 : return false;
4598 : }
4599 :
4600 : std::array<int, 2> anDimLongitudeIds;
4601 1 : NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLongitudeId,
4602 : anDimLongitudeIds.data()));
4603 : std::array<int, 2> anDimLatitudeIds;
4604 1 : NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLatitudeId,
4605 : anDimLatitudeIds.data()));
4606 1 : if (anDimLongitudeIds != anDimLatitudeIds)
4607 : {
4608 0 : return false;
4609 : }
4610 :
4611 : std::array<size_t, 2> anSizeVarDimIds;
4612 : std::array<size_t, 2> anSizeLongLatIds;
4613 2 : if (!(nc_inq_dimlen(cdfid, anVarDimIds[0], &anSizeVarDimIds[0]) ==
4614 1 : NC_NOERR &&
4615 1 : nc_inq_dimlen(cdfid, anVarDimIds[1], &anSizeVarDimIds[1]) ==
4616 1 : NC_NOERR &&
4617 1 : nc_inq_dimlen(cdfid, anDimLongitudeIds[0], &anSizeLongLatIds[0]) ==
4618 1 : NC_NOERR &&
4619 1 : nc_inq_dimlen(cdfid, anDimLongitudeIds[1], &anSizeLongLatIds[1]) ==
4620 : NC_NOERR &&
4621 1 : anSizeVarDimIds == anSizeLongLatIds))
4622 : {
4623 0 : return false;
4624 : }
4625 :
4626 1 : const char *pszGeolocXFullName = "/navigation_data/longitude";
4627 1 : const char *pszGeolocYFullName = "/navigation_data/latitude";
4628 :
4629 1 : if (bSwitchedXY)
4630 : {
4631 0 : std::swap(pszGeolocXFullName, pszGeolocYFullName);
4632 0 : GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", "GEOLOCATION");
4633 : }
4634 :
4635 1 : CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
4636 : pszGeolocXFullName, pszGeolocYFullName);
4637 :
4638 1 : GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
4639 : "GEOLOCATION");
4640 :
4641 1 : CPLString osTMP;
4642 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
4643 :
4644 1 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
4645 1 : GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
4646 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
4647 :
4648 1 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
4649 1 : GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
4650 :
4651 1 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
4652 1 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
4653 :
4654 1 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
4655 1 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
4656 :
4657 1 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
4658 : "GEOLOCATION");
4659 1 : return true;
4660 : }
4661 :
4662 291 : bool netCDFDataset::ProcessNASAEMITGeoLocation(int nGroupId, int nVarId)
4663 : {
4664 : // Cf https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/
4665 :
4666 : // Check for a structure like:
4667 : /* netcdf EMIT_L2A_RFL_001_20220903T163129_2224611_012 {
4668 : dimensions:
4669 : downtrack = 1280 ;
4670 : crosstrack = 1242 ;
4671 : bands = 285 ;
4672 : [...]
4673 :
4674 : variables:
4675 : float reflectance(downtrack, crosstrack, bands) ;
4676 :
4677 : group: location {
4678 : variables:
4679 : double lon(downtrack, crosstrack) ;
4680 : lon:_FillValue = -9999. ;
4681 : lon:long_name = "Longitude (WGS-84)" ;
4682 : lon:units = "degrees east" ;
4683 : double lat(downtrack, crosstrack) ;
4684 : lat:_FillValue = -9999. ;
4685 : lat:long_name = "Latitude (WGS-84)" ;
4686 : lat:units = "degrees north" ;
4687 : } // group location
4688 :
4689 : }
4690 : or
4691 : netcdf EMIT_L2B_MIN_001_20231024T055538_2329704_040 {
4692 : dimensions:
4693 : downtrack = 1664 ;
4694 : crosstrack = 1242 ;
4695 : [...]
4696 : variables:
4697 : float group_1_band_depth(downtrack, crosstrack) ;
4698 : group_1_band_depth:_FillValue = -9999.f ;
4699 : group_1_band_depth:long_name = "Group 1 Band Depth" ;
4700 : group_1_band_depth:units = "unitless" ;
4701 : [...]
4702 : group: location {
4703 : variables:
4704 : double lon(downtrack, crosstrack) ;
4705 : lon:_FillValue = -9999. ;
4706 : lon:long_name = "Longitude (WGS-84)" ;
4707 : lon:units = "degrees east" ;
4708 : double lat(downtrack, crosstrack) ;
4709 : lat:_FillValue = -9999. ;
4710 : lat:long_name = "Latitude (WGS-84)" ;
4711 : lat:units = "degrees north" ;
4712 : }
4713 : */
4714 :
4715 291 : int nVarDims = 0;
4716 291 : NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
4717 291 : if (nVarDims != 2 && nVarDims != 3)
4718 14 : return false;
4719 :
4720 277 : int nLocationGrpId = 0;
4721 277 : if (nc_inq_grp_ncid(cdfid, "location", &nLocationGrpId) != NC_NOERR)
4722 60 : return false;
4723 :
4724 : std::array<int, 3> anVarDimIds;
4725 217 : NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
4726 217 : if (nYDimID != anVarDimIds[0] || nXDimID != anVarDimIds[1])
4727 21 : return false;
4728 :
4729 196 : int nLongitudeId = 0;
4730 196 : int nLatitudeId = 0;
4731 234 : if (nc_inq_varid(nLocationGrpId, "lon", &nLongitudeId) != NC_NOERR ||
4732 38 : nc_inq_varid(nLocationGrpId, "lat", &nLatitudeId) != NC_NOERR)
4733 : {
4734 158 : return false;
4735 : }
4736 :
4737 38 : int nDimsLongitude = 0;
4738 38 : NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLongitudeId, &nDimsLongitude));
4739 38 : int nDimsLatitude = 0;
4740 38 : NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLatitudeId, &nDimsLatitude));
4741 38 : if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
4742 : {
4743 34 : return false;
4744 : }
4745 :
4746 : std::array<int, 2> anDimLongitudeIds;
4747 4 : NCDF_ERR(nc_inq_vardimid(nLocationGrpId, nLongitudeId,
4748 : anDimLongitudeIds.data()));
4749 : std::array<int, 2> anDimLatitudeIds;
4750 4 : NCDF_ERR(
4751 : nc_inq_vardimid(nLocationGrpId, nLatitudeId, anDimLatitudeIds.data()));
4752 4 : if (anDimLongitudeIds != anDimLatitudeIds)
4753 : {
4754 0 : return false;
4755 : }
4756 :
4757 8 : if (anDimLongitudeIds[0] != anVarDimIds[0] ||
4758 4 : anDimLongitudeIds[1] != anVarDimIds[1])
4759 : {
4760 0 : return false;
4761 : }
4762 :
4763 4 : const char *pszGeolocXFullName = "/location/lon";
4764 4 : const char *pszGeolocYFullName = "/location/lat";
4765 :
4766 4 : CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
4767 : pszGeolocXFullName, pszGeolocYFullName);
4768 :
4769 4 : GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
4770 : "GEOLOCATION");
4771 :
4772 4 : CPLString osTMP;
4773 4 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
4774 :
4775 4 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
4776 4 : GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
4777 4 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
4778 :
4779 4 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
4780 4 : GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
4781 :
4782 4 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
4783 4 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
4784 :
4785 4 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
4786 4 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
4787 :
4788 4 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
4789 : "GEOLOCATION");
4790 4 : return true;
4791 : }
4792 :
4793 356 : int netCDFDataset::ProcessCFGeolocation(int nGroupId, int nVarId,
4794 : const std::string &osGeolocWKT,
4795 : std::string &osGeolocXNameOut,
4796 : std::string &osGeolocYNameOut)
4797 : {
4798 356 : bool bAddGeoloc = false;
4799 356 : char *pszCoordinates = nullptr;
4800 :
4801 : // If there is no explicit "coordinates" attribute, check if there are
4802 : // "lon" and "lat" 2D variables whose dimensions are the last
4803 : // 2 ones of the variable of interest.
4804 356 : if (NCDFGetAttr(nGroupId, nVarId, "coordinates", &pszCoordinates) !=
4805 : CE_None)
4806 : {
4807 309 : CPLFree(pszCoordinates);
4808 309 : pszCoordinates = nullptr;
4809 :
4810 309 : int nVarDims = 0;
4811 309 : NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
4812 309 : if (nVarDims >= 2)
4813 : {
4814 618 : std::vector<int> anVarDimIds(nVarDims);
4815 309 : NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
4816 :
4817 309 : int nLongitudeId = 0;
4818 309 : int nLatitudeId = 0;
4819 377 : if (nc_inq_varid(nGroupId, "lon", &nLongitudeId) == NC_NOERR &&
4820 68 : nc_inq_varid(nGroupId, "lat", &nLatitudeId) == NC_NOERR)
4821 : {
4822 68 : int nDimsLongitude = 0;
4823 68 : NCDF_ERR(
4824 : nc_inq_varndims(nGroupId, nLongitudeId, &nDimsLongitude));
4825 68 : int nDimsLatitude = 0;
4826 68 : NCDF_ERR(
4827 : nc_inq_varndims(nGroupId, nLatitudeId, &nDimsLatitude));
4828 68 : if (nDimsLongitude == 2 && nDimsLatitude == 2)
4829 : {
4830 34 : std::vector<int> anDimLongitudeIds(2);
4831 17 : NCDF_ERR(nc_inq_vardimid(nGroupId, nLongitudeId,
4832 : anDimLongitudeIds.data()));
4833 34 : std::vector<int> anDimLatitudeIds(2);
4834 17 : NCDF_ERR(nc_inq_vardimid(nGroupId, nLatitudeId,
4835 : anDimLatitudeIds.data()));
4836 17 : if (anDimLongitudeIds == anDimLatitudeIds &&
4837 34 : anVarDimIds[anVarDimIds.size() - 2] ==
4838 51 : anDimLongitudeIds[0] &&
4839 34 : anVarDimIds[anVarDimIds.size() - 1] ==
4840 17 : anDimLongitudeIds[1])
4841 : {
4842 17 : pszCoordinates = CPLStrdup("lon lat");
4843 : }
4844 : }
4845 : }
4846 : }
4847 : }
4848 :
4849 356 : if (pszCoordinates)
4850 : {
4851 : // Get X and Y geolocation names from coordinates attribute.
4852 : const CPLStringList aosCoordinates(
4853 128 : NCDFTokenizeCoordinatesAttribute(pszCoordinates));
4854 64 : if (aosCoordinates.size() >= 2)
4855 : {
4856 : char szGeolocXName[NC_MAX_NAME + 1];
4857 : char szGeolocYName[NC_MAX_NAME + 1];
4858 61 : szGeolocXName[0] = '\0';
4859 61 : szGeolocYName[0] = '\0';
4860 :
4861 : // Test that each variable is longitude/latitude.
4862 196 : for (int i = 0; i < aosCoordinates.size(); i++)
4863 : {
4864 135 : if (NCDFIsVarLongitude(nGroupId, -1, aosCoordinates[i]))
4865 : {
4866 50 : int nOtherGroupId = -1;
4867 50 : int nOtherVarId = -1;
4868 : // Check that the variable actually exists
4869 : // Needed on Sentinel-3 products
4870 50 : if (NCDFResolveVar(nGroupId, aosCoordinates[i],
4871 50 : &nOtherGroupId, &nOtherVarId) == CE_None)
4872 : {
4873 48 : snprintf(szGeolocXName, sizeof(szGeolocXName), "%s",
4874 : aosCoordinates[i]);
4875 : }
4876 : }
4877 85 : else if (NCDFIsVarLatitude(nGroupId, -1, aosCoordinates[i]))
4878 : {
4879 50 : int nOtherGroupId = -1;
4880 50 : int nOtherVarId = -1;
4881 : // Check that the variable actually exists
4882 : // Needed on Sentinel-3 products
4883 50 : if (NCDFResolveVar(nGroupId, aosCoordinates[i],
4884 50 : &nOtherGroupId, &nOtherVarId) == CE_None)
4885 : {
4886 48 : snprintf(szGeolocYName, sizeof(szGeolocYName), "%s",
4887 : aosCoordinates[i]);
4888 : }
4889 : }
4890 : }
4891 : // Add GEOLOCATION metadata.
4892 61 : if (!EQUAL(szGeolocXName, "") && !EQUAL(szGeolocYName, ""))
4893 : {
4894 48 : osGeolocXNameOut = szGeolocXName;
4895 48 : osGeolocYNameOut = szGeolocYName;
4896 :
4897 48 : char *pszGeolocXFullName = nullptr;
4898 48 : char *pszGeolocYFullName = nullptr;
4899 48 : if (NCDFResolveVarFullName(nGroupId, szGeolocXName,
4900 96 : &pszGeolocXFullName) == CE_None &&
4901 48 : NCDFResolveVarFullName(nGroupId, szGeolocYName,
4902 : &pszGeolocYFullName) == CE_None)
4903 : {
4904 48 : if (bSwitchedXY)
4905 : {
4906 2 : std::swap(pszGeolocXFullName, pszGeolocYFullName);
4907 2 : GDALPamDataset::SetMetadataItem("SWAP_XY", "YES",
4908 : "GEOLOCATION");
4909 : }
4910 :
4911 48 : bAddGeoloc = true;
4912 48 : CPLDebug("GDAL_netCDF",
4913 : "using variables %s and %s for GEOLOCATION",
4914 : pszGeolocXFullName, pszGeolocYFullName);
4915 :
4916 48 : GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
4917 : "GEOLOCATION");
4918 :
4919 96 : CPLString osTMP;
4920 48 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4921 48 : pszGeolocXFullName);
4922 :
4923 48 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP,
4924 : "GEOLOCATION");
4925 48 : GDALPamDataset::SetMetadataItem("X_BAND", "1",
4926 : "GEOLOCATION");
4927 48 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4928 48 : pszGeolocYFullName);
4929 :
4930 48 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP,
4931 : "GEOLOCATION");
4932 48 : GDALPamDataset::SetMetadataItem("Y_BAND", "1",
4933 : "GEOLOCATION");
4934 :
4935 48 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0",
4936 : "GEOLOCATION");
4937 48 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1",
4938 : "GEOLOCATION");
4939 :
4940 48 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0",
4941 : "GEOLOCATION");
4942 48 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1",
4943 : "GEOLOCATION");
4944 :
4945 48 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
4946 : "PIXEL_CENTER",
4947 : "GEOLOCATION");
4948 : }
4949 : else
4950 : {
4951 0 : CPLDebug("GDAL_netCDF",
4952 : "cannot resolve location of "
4953 : "lat/lon variables specified by the coordinates "
4954 : "attribute [%s]",
4955 : pszCoordinates);
4956 : }
4957 48 : CPLFree(pszGeolocXFullName);
4958 48 : CPLFree(pszGeolocYFullName);
4959 : }
4960 : else
4961 : {
4962 13 : CPLDebug("GDAL_netCDF",
4963 : "coordinates attribute [%s] is unsupported",
4964 : pszCoordinates);
4965 : }
4966 : }
4967 : else
4968 : {
4969 3 : CPLDebug("GDAL_netCDF",
4970 : "coordinates attribute [%s] with %d element(s) is "
4971 : "unsupported",
4972 : pszCoordinates, aosCoordinates.size());
4973 : }
4974 : }
4975 :
4976 : else
4977 : {
4978 292 : bAddGeoloc = ProcessNASAL2OceanGeoLocation(nGroupId, nVarId);
4979 :
4980 292 : if (!bAddGeoloc)
4981 291 : bAddGeoloc = ProcessNASAEMITGeoLocation(nGroupId, nVarId);
4982 : }
4983 :
4984 356 : CPLFree(pszCoordinates);
4985 :
4986 356 : return bAddGeoloc;
4987 : }
4988 :
4989 8 : CPLErr netCDFDataset::Set1DGeolocation(int nGroupId, int nVarId,
4990 : const char *szDimName)
4991 : {
4992 : // Get values.
4993 8 : char *pszVarValues = nullptr;
4994 8 : CPLErr eErr = NCDFGet1DVar(nGroupId, nVarId, &pszVarValues);
4995 8 : if (eErr != CE_None)
4996 0 : return eErr;
4997 :
4998 : // Write metadata.
4999 8 : char szTemp[NC_MAX_NAME + 1 + 32] = {};
5000 8 : snprintf(szTemp, sizeof(szTemp), "%s_VALUES", szDimName);
5001 8 : GDALPamDataset::SetMetadataItem(szTemp, pszVarValues, "GEOLOCATION2");
5002 :
5003 8 : CPLFree(pszVarValues);
5004 :
5005 8 : return CE_None;
5006 : }
5007 :
5008 0 : double *netCDFDataset::Get1DGeolocation(CPL_UNUSED const char *szDimName,
5009 : int &nVarLen)
5010 : {
5011 0 : nVarLen = 0;
5012 :
5013 : // Get Y_VALUES as tokens.
5014 : const CPLStringList aosValues(
5015 0 : NCDFTokenizeArray(GetMetadataItem("Y_VALUES", "GEOLOCATION2")));
5016 0 : if (aosValues.empty())
5017 0 : return nullptr;
5018 :
5019 : // Initialize and fill array.
5020 0 : nVarLen = aosValues.size();
5021 : double *pdfVarValues =
5022 0 : static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
5023 :
5024 0 : for (int i = 0, j = 0; i < nVarLen; i++)
5025 : {
5026 0 : if (!bBottomUp)
5027 0 : j = nVarLen - 1 - i;
5028 : else
5029 0 : j = i; // Invert latitude values.
5030 0 : char *pszTemp = nullptr;
5031 0 : pdfVarValues[j] = CPLStrtod(aosValues[i], &pszTemp);
5032 : }
5033 :
5034 0 : return pdfVarValues;
5035 : }
5036 :
5037 : /************************************************************************/
5038 : /* SetSpatialRefNoUpdate() */
5039 : /************************************************************************/
5040 :
5041 269 : void netCDFDataset::SetSpatialRefNoUpdate(const OGRSpatialReference *poSRS)
5042 : {
5043 269 : m_oSRS.Clear();
5044 269 : if (poSRS)
5045 262 : m_oSRS = *poSRS;
5046 269 : m_bHasProjection = true;
5047 269 : }
5048 :
5049 : /************************************************************************/
5050 : /* SetSpatialRef() */
5051 : /************************************************************************/
5052 :
5053 78 : CPLErr netCDFDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
5054 : {
5055 156 : CPLMutexHolderD(&hNCMutex);
5056 :
5057 78 : if (GetAccess() != GA_Update || m_bHasProjection)
5058 : {
5059 0 : CPLError(CE_Failure, CPLE_AppDefined,
5060 : "netCDFDataset::_SetProjection() should only be called once "
5061 : "in update mode!");
5062 0 : return CE_Failure;
5063 : }
5064 :
5065 78 : if (m_bHasGeoTransform)
5066 : {
5067 32 : SetSpatialRefNoUpdate(poSRS);
5068 :
5069 : // For NC4/NC4C, writing both projection variables and data,
5070 : // followed by redefining nodata value, cancels the projection
5071 : // info from the Band variable, so for now only write the
5072 : // variable definitions, and write data at the end.
5073 : // See https://trac.osgeo.org/gdal/ticket/7245
5074 32 : return AddProjectionVars(true, nullptr, nullptr);
5075 : }
5076 :
5077 46 : SetSpatialRefNoUpdate(poSRS);
5078 :
5079 46 : return CE_None;
5080 : }
5081 :
5082 : /************************************************************************/
5083 : /* SetGeoTransformNoUpdate() */
5084 : /************************************************************************/
5085 :
5086 283 : void netCDFDataset::SetGeoTransformNoUpdate(const GDALGeoTransform >)
5087 : {
5088 283 : m_gt = gt;
5089 283 : m_bHasGeoTransform = true;
5090 283 : }
5091 :
5092 : /************************************************************************/
5093 : /* SetGeoTransform() */
5094 : /************************************************************************/
5095 :
5096 79 : CPLErr netCDFDataset::SetGeoTransform(const GDALGeoTransform >)
5097 : {
5098 158 : CPLMutexHolderD(&hNCMutex);
5099 :
5100 79 : if (GetAccess() != GA_Update || m_bHasGeoTransform)
5101 : {
5102 0 : CPLError(CE_Failure, CPLE_AppDefined,
5103 : "netCDFDataset::SetGeoTransform() should only be called once "
5104 : "in update mode!");
5105 0 : return CE_Failure;
5106 : }
5107 :
5108 79 : CPLDebug("GDAL_netCDF", "SetGeoTransform(%f,%f,%f,%f,%f,%f)", gt.xorig,
5109 79 : gt.xscale, gt.xrot, gt.yorig, gt.yrot, gt.yscale);
5110 :
5111 79 : SetGeoTransformNoUpdate(gt);
5112 :
5113 79 : if (m_bHasProjection)
5114 : {
5115 :
5116 : // For NC4/NC4C, writing both projection variables and data,
5117 : // followed by redefining nodata value, cancels the projection
5118 : // info from the Band variable, so for now only write the
5119 : // variable definitions, and write data at the end.
5120 : // See https://trac.osgeo.org/gdal/ticket/7245
5121 3 : return AddProjectionVars(true, nullptr, nullptr);
5122 : }
5123 :
5124 76 : return CE_None;
5125 : }
5126 :
5127 : /************************************************************************/
5128 : /* NCDFWriteSRSVariable() */
5129 : /************************************************************************/
5130 :
5131 132 : int NCDFWriteSRSVariable(int cdfid, const OGRSpatialReference *poSRS,
5132 : char **ppszCFProjection, bool bWriteGDALTags,
5133 : const std::string &srsVarName)
5134 : {
5135 132 : char *pszCFProjection = nullptr;
5136 132 : char **papszKeyValues = nullptr;
5137 132 : poSRS->exportToCF1(&pszCFProjection, &papszKeyValues, nullptr, nullptr);
5138 :
5139 132 : if (bWriteGDALTags)
5140 : {
5141 131 : const char *pszWKT = CSLFetchNameValue(papszKeyValues, NCDF_CRS_WKT);
5142 131 : if (pszWKT)
5143 : {
5144 : // SPATIAL_REF is deprecated. Will be removed in GDAL 4.
5145 131 : papszKeyValues =
5146 131 : CSLSetNameValue(papszKeyValues, NCDF_SPATIAL_REF, pszWKT);
5147 : }
5148 : }
5149 :
5150 132 : const int nValues = CSLCount(papszKeyValues);
5151 :
5152 : int NCDFVarID;
5153 264 : std::string varNameRadix(pszCFProjection);
5154 132 : int nCounter = 2;
5155 : while (true)
5156 : {
5157 134 : NCDFVarID = -1;
5158 134 : nc_inq_varid(cdfid, pszCFProjection, &NCDFVarID);
5159 134 : if (NCDFVarID < 0)
5160 129 : break;
5161 :
5162 5 : int nbAttr = 0;
5163 5 : NCDF_ERR(nc_inq_varnatts(cdfid, NCDFVarID, &nbAttr));
5164 5 : bool bSame = nbAttr == nValues;
5165 41 : for (int i = 0; bSame && (i < nbAttr); i++)
5166 : {
5167 : char szAttrName[NC_MAX_NAME + 1];
5168 38 : szAttrName[0] = 0;
5169 38 : NCDF_ERR(nc_inq_attname(cdfid, NCDFVarID, i, szAttrName));
5170 :
5171 : const char *pszValue =
5172 38 : CSLFetchNameValue(papszKeyValues, szAttrName);
5173 38 : if (!pszValue)
5174 : {
5175 0 : bSame = false;
5176 2 : break;
5177 : }
5178 :
5179 38 : nc_type atttype = NC_NAT;
5180 38 : size_t attlen = 0;
5181 38 : NCDF_ERR(
5182 : nc_inq_att(cdfid, NCDFVarID, szAttrName, &atttype, &attlen));
5183 38 : if (atttype != NC_CHAR && atttype != NC_DOUBLE)
5184 : {
5185 0 : bSame = false;
5186 0 : break;
5187 : }
5188 38 : if (atttype == NC_CHAR)
5189 : {
5190 15 : if (CPLGetValueType(pszValue) != CPL_VALUE_STRING)
5191 : {
5192 0 : bSame = false;
5193 0 : break;
5194 : }
5195 15 : std::string val;
5196 15 : val.resize(attlen);
5197 15 : nc_get_att_text(cdfid, NCDFVarID, szAttrName, &val[0]);
5198 15 : if (val != pszValue)
5199 : {
5200 0 : bSame = false;
5201 0 : break;
5202 : }
5203 : }
5204 : else
5205 : {
5206 : const CPLStringList aosTokens(
5207 23 : CSLTokenizeString2(pszValue, ",", 0));
5208 23 : if (static_cast<size_t>(aosTokens.size()) != attlen)
5209 : {
5210 0 : bSame = false;
5211 0 : break;
5212 : }
5213 : double vals[2];
5214 23 : nc_get_att_double(cdfid, NCDFVarID, szAttrName, vals);
5215 44 : if (vals[0] != CPLAtof(aosTokens[0]) ||
5216 21 : (attlen == 2 && vals[1] != CPLAtof(aosTokens[1])))
5217 : {
5218 2 : bSame = false;
5219 2 : break;
5220 : }
5221 : }
5222 : }
5223 5 : if (bSame)
5224 : {
5225 3 : *ppszCFProjection = pszCFProjection;
5226 3 : CSLDestroy(papszKeyValues);
5227 3 : return NCDFVarID;
5228 : }
5229 2 : CPLFree(pszCFProjection);
5230 2 : pszCFProjection =
5231 2 : CPLStrdup(CPLSPrintf("%s_%d", varNameRadix.c_str(), nCounter));
5232 2 : nCounter++;
5233 2 : }
5234 :
5235 129 : *ppszCFProjection = pszCFProjection;
5236 :
5237 : const char *pszVarName;
5238 :
5239 129 : if (srsVarName != "")
5240 : {
5241 38 : pszVarName = srsVarName.c_str();
5242 : }
5243 : else
5244 : {
5245 91 : pszVarName = pszCFProjection;
5246 : }
5247 :
5248 129 : int status = nc_def_var(cdfid, pszVarName, NC_CHAR, 0, nullptr, &NCDFVarID);
5249 129 : NCDF_ERR(status);
5250 1251 : for (int i = 0; i < nValues; ++i)
5251 : {
5252 1122 : char *pszKey = nullptr;
5253 1122 : const char *pszValue = CPLParseNameValue(papszKeyValues[i], &pszKey);
5254 1122 : if (pszKey && pszValue)
5255 : {
5256 2244 : const CPLStringList aosTokens(CSLTokenizeString2(pszValue, ",", 0));
5257 1122 : double adfValues[2] = {0, 0};
5258 1122 : const int nDoubleCount = std::min(2, aosTokens.size());
5259 1122 : if (!(aosTokens.size() == 2 &&
5260 2243 : CPLGetValueType(aosTokens[0]) != CPL_VALUE_STRING) &&
5261 1121 : CPLGetValueType(pszValue) == CPL_VALUE_STRING)
5262 : {
5263 515 : status = nc_put_att_text(cdfid, NCDFVarID, pszKey,
5264 : strlen(pszValue), pszValue);
5265 : }
5266 : else
5267 : {
5268 1215 : for (int j = 0; j < nDoubleCount; ++j)
5269 608 : adfValues[j] = CPLAtof(aosTokens[j]);
5270 607 : status = nc_put_att_double(cdfid, NCDFVarID, pszKey, NC_DOUBLE,
5271 : nDoubleCount, adfValues);
5272 : }
5273 1122 : NCDF_ERR(status);
5274 : }
5275 1122 : CPLFree(pszKey);
5276 : }
5277 :
5278 129 : CSLDestroy(papszKeyValues);
5279 129 : return NCDFVarID;
5280 : }
5281 :
5282 : /************************************************************************/
5283 : /* NCDFWriteLonLatVarsAttributes() */
5284 : /************************************************************************/
5285 :
5286 101 : void NCDFWriteLonLatVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarLonID,
5287 : int nVarLatID)
5288 : {
5289 :
5290 : try
5291 : {
5292 101 : vcdf.nc_put_vatt_text(nVarLatID, CF_STD_NAME, CF_LATITUDE_STD_NAME);
5293 101 : vcdf.nc_put_vatt_text(nVarLatID, CF_LNG_NAME, CF_LATITUDE_LNG_NAME);
5294 101 : vcdf.nc_put_vatt_text(nVarLatID, CF_UNITS, CF_DEGREES_NORTH);
5295 101 : vcdf.nc_put_vatt_text(nVarLonID, CF_STD_NAME, CF_LONGITUDE_STD_NAME);
5296 101 : vcdf.nc_put_vatt_text(nVarLonID, CF_LNG_NAME, CF_LONGITUDE_LNG_NAME);
5297 101 : vcdf.nc_put_vatt_text(nVarLonID, CF_UNITS, CF_DEGREES_EAST);
5298 : }
5299 0 : catch (nccfdriver::SG_Exception &e)
5300 : {
5301 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5302 : }
5303 101 : }
5304 :
5305 : /************************************************************************/
5306 : /* NCDFWriteRLonRLatVarsAttributes() */
5307 : /************************************************************************/
5308 :
5309 0 : void NCDFWriteRLonRLatVarsAttributes(nccfdriver::netCDFVID &vcdf,
5310 : int nVarRLonID, int nVarRLatID)
5311 : {
5312 : try
5313 : {
5314 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_STD_NAME, "grid_latitude");
5315 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_LNG_NAME,
5316 : "latitude in rotated pole grid");
5317 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_UNITS, "degrees");
5318 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_AXIS, "Y");
5319 :
5320 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_STD_NAME, "grid_longitude");
5321 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_LNG_NAME,
5322 : "longitude in rotated pole grid");
5323 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_UNITS, "degrees");
5324 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_AXIS, "X");
5325 : }
5326 0 : catch (nccfdriver::SG_Exception &e)
5327 : {
5328 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5329 : }
5330 0 : }
5331 :
5332 : /************************************************************************/
5333 : /* NCDFGetProjectedCFUnit() */
5334 : /************************************************************************/
5335 :
5336 44 : std::string NCDFGetProjectedCFUnit(const OGRSpatialReference *poSRS)
5337 : {
5338 44 : char *pszUnitsToWrite = nullptr;
5339 44 : poSRS->exportToCF1(nullptr, nullptr, &pszUnitsToWrite, nullptr);
5340 44 : std::string osRet = pszUnitsToWrite ? pszUnitsToWrite : std::string();
5341 44 : CPLFree(pszUnitsToWrite);
5342 88 : return osRet;
5343 : }
5344 :
5345 : /************************************************************************/
5346 : /* NCDFWriteXYVarsAttributes() */
5347 : /************************************************************************/
5348 :
5349 31 : void NCDFWriteXYVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarXID,
5350 : int nVarYID, const OGRSpatialReference *poSRS)
5351 : {
5352 62 : const std::string osUnitsToWrite = NCDFGetProjectedCFUnit(poSRS);
5353 :
5354 : try
5355 : {
5356 31 : vcdf.nc_put_vatt_text(nVarXID, CF_STD_NAME, CF_PROJ_X_COORD);
5357 31 : vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME, CF_PROJ_X_COORD_LONG_NAME);
5358 31 : if (!osUnitsToWrite.empty())
5359 31 : vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, osUnitsToWrite.c_str());
5360 31 : vcdf.nc_put_vatt_text(nVarYID, CF_STD_NAME, CF_PROJ_Y_COORD);
5361 31 : vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME, CF_PROJ_Y_COORD_LONG_NAME);
5362 31 : if (!osUnitsToWrite.empty())
5363 31 : vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, osUnitsToWrite.c_str());
5364 : }
5365 0 : catch (nccfdriver::SG_Exception &e)
5366 : {
5367 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5368 : }
5369 31 : }
5370 :
5371 : /************************************************************************/
5372 : /* AddProjectionVars() */
5373 : /************************************************************************/
5374 :
5375 168 : CPLErr netCDFDataset::AddProjectionVars(bool bDefsOnly,
5376 : GDALProgressFunc pfnProgress,
5377 : void *pProgressData)
5378 : {
5379 168 : if (nCFVersion >= 1.8)
5380 0 : return CE_None; // do nothing
5381 :
5382 168 : bool bWriteGridMapping = false;
5383 168 : bool bWriteLonLat = false;
5384 168 : bool bHasGeoloc = false;
5385 168 : bool bWriteGDALTags = false;
5386 168 : bool bWriteGeoTransform = false;
5387 :
5388 : // For GEOLOCATION information.
5389 168 : GDALDatasetUniquePtr poDS_X;
5390 168 : GDALDatasetUniquePtr poDS_Y;
5391 168 : GDALRasterBand *poBand_X = nullptr;
5392 168 : GDALRasterBand *poBand_Y = nullptr;
5393 :
5394 336 : OGRSpatialReference oSRS(m_oSRS);
5395 168 : if (!m_oSRS.IsEmpty())
5396 : {
5397 142 : if (oSRS.IsProjected())
5398 54 : bIsProjected = true;
5399 88 : else if (oSRS.IsGeographic())
5400 88 : bIsGeographic = true;
5401 : }
5402 :
5403 168 : if (bDefsOnly)
5404 : {
5405 84 : char *pszProjection = nullptr;
5406 84 : m_oSRS.exportToWkt(&pszProjection);
5407 84 : CPLDebug("GDAL_netCDF",
5408 : "SetProjection, WKT now = [%s]\nprojected: %d geographic: %d",
5409 84 : pszProjection ? pszProjection : "(null)",
5410 84 : static_cast<int>(bIsProjected),
5411 84 : static_cast<int>(bIsGeographic));
5412 84 : CPLFree(pszProjection);
5413 :
5414 84 : if (!m_bHasGeoTransform)
5415 5 : CPLDebug("GDAL_netCDF",
5416 : "netCDFDataset::AddProjectionVars() called, "
5417 : "but GeoTransform has not yet been defined!");
5418 :
5419 84 : if (!m_bHasProjection)
5420 6 : CPLDebug("GDAL_netCDF",
5421 : "netCDFDataset::AddProjectionVars() called, "
5422 : "but Projection has not yet been defined!");
5423 : }
5424 :
5425 : // Check GEOLOCATION information.
5426 : CSLConstList papszGeolocationInfo =
5427 168 : netCDFDataset::GetMetadata("GEOLOCATION");
5428 168 : if (papszGeolocationInfo != nullptr)
5429 : {
5430 : // Look for geolocation datasets.
5431 : const char *pszDSName =
5432 10 : CSLFetchNameValue(papszGeolocationInfo, "X_DATASET");
5433 10 : if (pszDSName != nullptr)
5434 10 : poDS_X.reset(GDALDataset::Open(
5435 : pszDSName,
5436 : GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR | GDAL_OF_SHARED));
5437 10 : pszDSName = CSLFetchNameValue(papszGeolocationInfo, "Y_DATASET");
5438 10 : if (pszDSName != nullptr)
5439 10 : poDS_Y.reset(GDALDataset::Open(
5440 : pszDSName,
5441 : GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR | GDAL_OF_SHARED));
5442 :
5443 10 : if (poDS_X != nullptr && poDS_Y != nullptr)
5444 : {
5445 10 : int nBand = std::max(1, atoi(CSLFetchNameValueDef(
5446 10 : papszGeolocationInfo, "X_BAND", "0")));
5447 10 : poBand_X = poDS_X->GetRasterBand(nBand);
5448 10 : nBand = std::max(1, atoi(CSLFetchNameValueDef(papszGeolocationInfo,
5449 10 : "Y_BAND", "0")));
5450 10 : poBand_Y = poDS_Y->GetRasterBand(nBand);
5451 :
5452 : // If geoloc bands are found, do basic validation based on their
5453 : // dimensions.
5454 10 : if (poBand_X != nullptr && poBand_Y != nullptr)
5455 : {
5456 10 : const int nXSize_XBand = poBand_X->GetXSize();
5457 10 : const int nYSize_XBand = poBand_X->GetYSize();
5458 10 : const int nXSize_YBand = poBand_Y->GetXSize();
5459 10 : const int nYSize_YBand = poBand_Y->GetYSize();
5460 :
5461 : // TODO 1D geolocation arrays not implemented.
5462 10 : if (nYSize_XBand == 1 && nYSize_YBand == 1)
5463 : {
5464 0 : bHasGeoloc = false;
5465 0 : CPLDebug("GDAL_netCDF",
5466 : "1D GEOLOCATION arrays not supported yet");
5467 : }
5468 : // 2D bands must have same sizes as the raster bands.
5469 10 : else if (nXSize_XBand != nRasterXSize ||
5470 10 : nYSize_XBand != nRasterYSize ||
5471 10 : nXSize_YBand != nRasterXSize ||
5472 10 : nYSize_YBand != nRasterYSize)
5473 : {
5474 0 : bHasGeoloc = false;
5475 0 : CPLDebug("GDAL_netCDF",
5476 : "GEOLOCATION array sizes (%dx%d %dx%d) differ "
5477 : "from raster (%dx%d), not supported",
5478 : nXSize_XBand, nYSize_XBand, nXSize_YBand,
5479 : nYSize_YBand, nRasterXSize, nRasterYSize);
5480 : }
5481 : else
5482 : {
5483 10 : bHasGeoloc = true;
5484 10 : CPLDebug("GDAL_netCDF",
5485 : "dataset has GEOLOCATION information, will try to "
5486 : "write it");
5487 : }
5488 : }
5489 : }
5490 : }
5491 :
5492 : // Process projection options.
5493 168 : if (bIsProjected)
5494 : {
5495 : bool bIsCfProjection =
5496 54 : oSRS.exportToCF1(nullptr, nullptr, nullptr, nullptr) == OGRERR_NONE;
5497 54 : bWriteGridMapping = true;
5498 54 : bWriteGDALTags = CPL_TO_BOOL(
5499 54 : CSLFetchBoolean(papszCreationOptions, "WRITE_GDAL_TAGS", TRUE));
5500 : // Force WRITE_GDAL_TAGS if is not a CF projection.
5501 54 : if (!bWriteGDALTags && !bIsCfProjection)
5502 0 : bWriteGDALTags = true;
5503 54 : if (bWriteGDALTags)
5504 54 : bWriteGeoTransform = true;
5505 :
5506 : // Write lon/lat: default is NO, except if has geolocation.
5507 : // With IF_NEEDED: write if has geoloc or is not CF projection.
5508 : const char *pszValue =
5509 54 : CSLFetchNameValue(papszCreationOptions, "WRITE_LONLAT");
5510 54 : if (pszValue)
5511 : {
5512 2 : if (EQUAL(pszValue, "IF_NEEDED"))
5513 : {
5514 0 : bWriteLonLat = bHasGeoloc || !bIsCfProjection;
5515 : }
5516 : else
5517 : {
5518 2 : bWriteLonLat = CPLTestBool(pszValue);
5519 : }
5520 : }
5521 : else
5522 : {
5523 52 : bWriteLonLat = bHasGeoloc;
5524 : }
5525 :
5526 : // Save value of pszCFCoordinates for later.
5527 54 : if (bWriteLonLat)
5528 : {
5529 4 : pszCFCoordinates = NCDF_LONLAT;
5530 : }
5531 : }
5532 : else
5533 : {
5534 : // Files without a Datum will not have a grid_mapping variable and
5535 : // geographic information.
5536 114 : bWriteGridMapping = bIsGeographic;
5537 :
5538 114 : if (bHasGeoloc)
5539 : {
5540 8 : bWriteLonLat = true;
5541 : }
5542 : else
5543 : {
5544 106 : bWriteGDALTags = CPL_TO_BOOL(CSLFetchBoolean(
5545 106 : papszCreationOptions, "WRITE_GDAL_TAGS", bWriteGridMapping));
5546 106 : if (bWriteGDALTags)
5547 88 : bWriteGeoTransform = true;
5548 :
5549 106 : const char *pszValue = CSLFetchNameValueDef(papszCreationOptions,
5550 : "WRITE_LONLAT", "YES");
5551 106 : if (EQUAL(pszValue, "IF_NEEDED"))
5552 0 : bWriteLonLat = true;
5553 : else
5554 106 : bWriteLonLat = CPLTestBool(pszValue);
5555 : // Don't write lon/lat if no source geotransform.
5556 106 : if (!m_bHasGeoTransform)
5557 0 : bWriteLonLat = false;
5558 : // If we don't write lon/lat, set dimnames to X/Y and write gdal
5559 : // tags.
5560 106 : if (!bWriteLonLat)
5561 : {
5562 0 : CPLError(CE_Warning, CPLE_AppDefined,
5563 : "creating geographic file without lon/lat values!");
5564 0 : if (m_bHasGeoTransform)
5565 : {
5566 0 : bWriteGDALTags = true; // Not desirable if no geotransform.
5567 0 : bWriteGeoTransform = true;
5568 : }
5569 : }
5570 : }
5571 : }
5572 :
5573 : // Make sure we write grid_mapping if we need to write GDAL tags.
5574 168 : if (bWriteGDALTags)
5575 142 : bWriteGridMapping = true;
5576 :
5577 : // bottom-up value: new driver is bottom-up by default.
5578 : // Override with WRITE_BOTTOMUP.
5579 168 : bBottomUp = CPL_TO_BOOL(
5580 168 : CSLFetchBoolean(papszCreationOptions, "WRITE_BOTTOMUP", TRUE));
5581 :
5582 168 : if (bDefsOnly)
5583 : {
5584 84 : CPLDebug(
5585 : "GDAL_netCDF",
5586 : "bIsProjected=%d bIsGeographic=%d bWriteGridMapping=%d "
5587 : "bWriteGDALTags=%d bWriteLonLat=%d bBottomUp=%d bHasGeoloc=%d",
5588 84 : static_cast<int>(bIsProjected), static_cast<int>(bIsGeographic),
5589 : static_cast<int>(bWriteGridMapping),
5590 : static_cast<int>(bWriteGDALTags), static_cast<int>(bWriteLonLat),
5591 84 : static_cast<int>(bBottomUp), static_cast<int>(bHasGeoloc));
5592 : }
5593 :
5594 : // Exit if nothing to do.
5595 168 : if (!bIsProjected && !bWriteLonLat)
5596 0 : return CE_None;
5597 :
5598 : // Define dimension names.
5599 :
5600 168 : constexpr const char *ROTATED_POLE_VAR_NAME = "rotated_pole";
5601 :
5602 168 : if (bDefsOnly)
5603 : {
5604 84 : int nVarLonID = -1;
5605 84 : int nVarLatID = -1;
5606 84 : int nVarXID = -1;
5607 84 : int nVarYID = -1;
5608 :
5609 84 : m_bAddedProjectionVarsDefs = true;
5610 :
5611 : // Make sure we are in define mode.
5612 84 : SetDefineMode(true);
5613 :
5614 : // Write projection attributes.
5615 84 : if (bWriteGridMapping)
5616 : {
5617 71 : const int NCDFVarID = NCDFWriteSRSVariable(
5618 : cdfid, &oSRS, &pszCFProjection, bWriteGDALTags);
5619 71 : if (NCDFVarID < 0)
5620 0 : return CE_Failure;
5621 :
5622 : // Optional GDAL custom projection tags.
5623 71 : if (bWriteGDALTags)
5624 : {
5625 142 : std::string osGeoTransform = m_gt.ToString(" ");
5626 71 : CPLDebug("GDAL_netCDF", "szGeoTransform = %s",
5627 : osGeoTransform.c_str());
5628 :
5629 : // if( strlen(pszProj4Defn) > 0 ) {
5630 : // nc_put_att_text(cdfid, NCDFVarID, "proj4",
5631 : // strlen(pszProj4Defn), pszProj4Defn);
5632 : // }
5633 :
5634 : // For now, write the geotransform for back-compat or else
5635 : // the old (1.8.1) driver overrides the CF geotransform with
5636 : // empty values from dfNN, dfSN, dfEE, dfWE;
5637 :
5638 : // TODO: fix this in 1.8 branch, and then remove this here.
5639 71 : if (bWriteGeoTransform && m_bHasGeoTransform)
5640 : {
5641 : {
5642 70 : const int status = nc_put_att_text(
5643 : cdfid, NCDFVarID, NCDF_GEOTRANSFORM,
5644 : osGeoTransform.size(), osGeoTransform.c_str());
5645 70 : NCDF_ERR(status);
5646 : }
5647 : }
5648 : }
5649 :
5650 : // Write projection variable to band variable.
5651 : // Need to call later if there are no bands.
5652 71 : AddGridMappingRef();
5653 : } // end if( bWriteGridMapping )
5654 :
5655 : // Write CF Projection vars.
5656 :
5657 84 : const bool bIsRotatedPole =
5658 155 : pszCFProjection != nullptr &&
5659 71 : EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
5660 84 : if (bIsRotatedPole)
5661 : {
5662 : // Rename dims to rlat/rlon.
5663 : papszDimName
5664 0 : .Clear(); // If we add other dims one day, this has to change
5665 0 : papszDimName.AddString(NCDF_DIMNAME_RLAT);
5666 0 : papszDimName.AddString(NCDF_DIMNAME_RLON);
5667 :
5668 0 : int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_RLAT);
5669 0 : NCDF_ERR(status);
5670 0 : status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_RLON);
5671 0 : NCDF_ERR(status);
5672 : }
5673 : // Rename dimensions if lon/lat.
5674 84 : else if (!bIsProjected && !bHasGeoloc)
5675 : {
5676 : // Rename dims to lat/lon.
5677 : papszDimName
5678 53 : .Clear(); // If we add other dims one day, this has to change
5679 53 : papszDimName.AddString(NCDF_DIMNAME_LAT);
5680 53 : papszDimName.AddString(NCDF_DIMNAME_LON);
5681 :
5682 53 : int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_LAT);
5683 53 : NCDF_ERR(status);
5684 53 : status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_LON);
5685 53 : NCDF_ERR(status);
5686 : }
5687 :
5688 : // Write X/Y attributes.
5689 : else /* if( bIsProjected || bHasGeoloc ) */
5690 : {
5691 : // X
5692 : int anXDims[1];
5693 31 : anXDims[0] = nXDimID;
5694 31 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
5695 : CF_PROJ_X_VAR_NAME, NC_DOUBLE);
5696 31 : int status = nc_def_var(cdfid, CF_PROJ_X_VAR_NAME, NC_DOUBLE, 1,
5697 : anXDims, &nVarXID);
5698 31 : NCDF_ERR(status);
5699 :
5700 : // Y
5701 : int anYDims[1];
5702 31 : anYDims[0] = nYDimID;
5703 31 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
5704 : CF_PROJ_Y_VAR_NAME, NC_DOUBLE);
5705 31 : status = nc_def_var(cdfid, CF_PROJ_Y_VAR_NAME, NC_DOUBLE, 1,
5706 : anYDims, &nVarYID);
5707 31 : NCDF_ERR(status);
5708 :
5709 31 : if (bIsProjected)
5710 : {
5711 27 : NCDFWriteXYVarsAttributes(this->vcdf, nVarXID, nVarYID, &oSRS);
5712 : }
5713 : else
5714 : {
5715 4 : CPLAssert(bHasGeoloc);
5716 : try
5717 : {
5718 4 : vcdf.nc_put_vatt_text(nVarXID, CF_AXIS, CF_SG_X_AXIS);
5719 4 : vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME,
5720 : "x-coordinate in Cartesian system");
5721 4 : vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, "m");
5722 4 : vcdf.nc_put_vatt_text(nVarYID, CF_AXIS, CF_SG_Y_AXIS);
5723 4 : vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME,
5724 : "y-coordinate in Cartesian system");
5725 4 : vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, "m");
5726 :
5727 4 : pszCFCoordinates = NCDF_LONLAT;
5728 : }
5729 0 : catch (nccfdriver::SG_Exception &e)
5730 : {
5731 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5732 0 : return CE_Failure;
5733 : }
5734 : }
5735 : }
5736 :
5737 : // Write lat/lon attributes if needed.
5738 84 : if (bWriteLonLat)
5739 : {
5740 59 : int anLatDims[2] = {0, 0};
5741 59 : int anLonDims[2] = {0, 0};
5742 59 : int nLatDims = -1;
5743 59 : int nLonDims = -1;
5744 :
5745 : // Get information.
5746 59 : if (bHasGeoloc)
5747 : {
5748 : // Geoloc
5749 5 : nLatDims = 2;
5750 5 : anLatDims[0] = nYDimID;
5751 5 : anLatDims[1] = nXDimID;
5752 5 : nLonDims = 2;
5753 5 : anLonDims[0] = nYDimID;
5754 5 : anLonDims[1] = nXDimID;
5755 : }
5756 54 : else if (bIsProjected)
5757 : {
5758 : // Projected
5759 1 : nLatDims = 2;
5760 1 : anLatDims[0] = nYDimID;
5761 1 : anLatDims[1] = nXDimID;
5762 1 : nLonDims = 2;
5763 1 : anLonDims[0] = nYDimID;
5764 1 : anLonDims[1] = nXDimID;
5765 : }
5766 : else
5767 : {
5768 : // Geographic
5769 53 : nLatDims = 1;
5770 53 : anLatDims[0] = nYDimID;
5771 53 : nLonDims = 1;
5772 53 : anLonDims[0] = nXDimID;
5773 : }
5774 :
5775 59 : nc_type eLonLatType = NC_NAT;
5776 59 : if (bIsProjected)
5777 : {
5778 2 : eLonLatType = NC_FLOAT;
5779 4 : const char *pszValue = CSLFetchNameValueDef(
5780 2 : papszCreationOptions, "TYPE_LONLAT", "FLOAT");
5781 2 : if (EQUAL(pszValue, "DOUBLE"))
5782 0 : eLonLatType = NC_DOUBLE;
5783 : }
5784 : else
5785 : {
5786 57 : eLonLatType = NC_DOUBLE;
5787 114 : const char *pszValue = CSLFetchNameValueDef(
5788 57 : papszCreationOptions, "TYPE_LONLAT", "DOUBLE");
5789 57 : if (EQUAL(pszValue, "FLOAT"))
5790 0 : eLonLatType = NC_FLOAT;
5791 : }
5792 :
5793 : // Def vars and attributes.
5794 : {
5795 59 : const char *pszVarName =
5796 59 : bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME;
5797 59 : int status = nc_def_var(cdfid, pszVarName, eLonLatType,
5798 : nLatDims, anLatDims, &nVarLatID);
5799 59 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
5800 : cdfid, pszVarName, eLonLatType, nLatDims, nVarLatID);
5801 59 : NCDF_ERR(status);
5802 59 : DefVarDeflate(nVarLatID, false); // Don't set chunking.
5803 : }
5804 :
5805 : {
5806 59 : const char *pszVarName =
5807 59 : bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME;
5808 59 : int status = nc_def_var(cdfid, pszVarName, eLonLatType,
5809 : nLonDims, anLonDims, &nVarLonID);
5810 59 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
5811 : cdfid, pszVarName, eLonLatType, nLatDims, nVarLonID);
5812 59 : NCDF_ERR(status);
5813 59 : DefVarDeflate(nVarLonID, false); // Don't set chunking.
5814 : }
5815 :
5816 59 : if (bIsRotatedPole)
5817 0 : NCDFWriteRLonRLatVarsAttributes(this->vcdf, nVarLonID,
5818 : nVarLatID);
5819 : else
5820 59 : NCDFWriteLonLatVarsAttributes(this->vcdf, nVarLonID, nVarLatID);
5821 : }
5822 : }
5823 :
5824 168 : if (!bDefsOnly)
5825 : {
5826 84 : m_bAddedProjectionVarsData = true;
5827 :
5828 84 : int nVarXID = -1;
5829 84 : int nVarYID = -1;
5830 :
5831 84 : nc_inq_varid(cdfid, CF_PROJ_X_VAR_NAME, &nVarXID);
5832 84 : nc_inq_varid(cdfid, CF_PROJ_Y_VAR_NAME, &nVarYID);
5833 :
5834 84 : int nVarLonID = -1;
5835 84 : int nVarLatID = -1;
5836 :
5837 84 : const bool bIsRotatedPole =
5838 155 : pszCFProjection != nullptr &&
5839 71 : EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
5840 84 : nc_inq_varid(cdfid,
5841 : bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME,
5842 : &nVarLonID);
5843 84 : nc_inq_varid(cdfid,
5844 : bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME,
5845 : &nVarLatID);
5846 :
5847 : // Get projection values.
5848 :
5849 84 : if (bIsProjected)
5850 : {
5851 0 : std::unique_ptr<OGRSpatialReference> poLatLonSRS;
5852 0 : std::unique_ptr<OGRCoordinateTransformation> poTransform;
5853 :
5854 : size_t startX[1];
5855 : size_t countX[1];
5856 : size_t startY[1];
5857 : size_t countY[1];
5858 :
5859 27 : CPLDebug("GDAL_netCDF", "Getting (X,Y) values");
5860 :
5861 : std::unique_ptr<double, decltype(&VSIFree)> adXValKeeper(
5862 : static_cast<double *>(
5863 54 : VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
5864 27 : VSIFree);
5865 : std::unique_ptr<double, decltype(&VSIFree)> adYValKeeper(
5866 : static_cast<double *>(
5867 54 : VSI_MALLOC2_VERBOSE(nRasterYSize, sizeof(double))),
5868 27 : VSIFree);
5869 27 : double *padXVal = adXValKeeper.get();
5870 27 : double *padYVal = adYValKeeper.get();
5871 27 : if (!padXVal || !padYVal)
5872 : {
5873 0 : return CE_Failure;
5874 : }
5875 :
5876 : // Get Y values.
5877 27 : const double dfY0 = (!bBottomUp) ? m_gt.yorig :
5878 : // Invert latitude values.
5879 27 : m_gt.yorig + (m_gt.yscale * nRasterYSize);
5880 27 : const double dfDY = m_gt.yscale;
5881 :
5882 1478 : for (int j = 0; j < nRasterYSize; j++)
5883 : {
5884 : // The data point is centered inside the pixel.
5885 1451 : if (!bBottomUp)
5886 0 : padYVal[j] = dfY0 + (j + 0.5) * dfDY;
5887 : else // Invert latitude values.
5888 1451 : padYVal[j] = dfY0 - (j + 0.5) * dfDY;
5889 : }
5890 27 : startX[0] = 0;
5891 27 : countX[0] = nRasterXSize;
5892 :
5893 : // Get X values.
5894 27 : const double dfX0 = m_gt.xorig;
5895 27 : const double dfDX = m_gt.xscale;
5896 :
5897 1519 : for (int i = 0; i < nRasterXSize; i++)
5898 : {
5899 : // The data point is centered inside the pixel.
5900 1492 : padXVal[i] = dfX0 + (i + 0.5) * dfDX;
5901 : }
5902 27 : startY[0] = 0;
5903 27 : countY[0] = nRasterYSize;
5904 :
5905 : // Write X/Y values.
5906 :
5907 : // Make sure we are in data mode.
5908 27 : SetDefineMode(false);
5909 :
5910 27 : CPLDebug("GDAL_netCDF", "Writing X values");
5911 : int status =
5912 27 : nc_put_vara_double(cdfid, nVarXID, startX, countX, padXVal);
5913 27 : NCDF_ERR(status);
5914 :
5915 27 : CPLDebug("GDAL_netCDF", "Writing Y values");
5916 : status =
5917 27 : nc_put_vara_double(cdfid, nVarYID, startY, countY, padYVal);
5918 27 : NCDF_ERR(status);
5919 :
5920 27 : if (pfnProgress)
5921 23 : pfnProgress(0.20, nullptr, pProgressData);
5922 :
5923 : // Write lon/lat arrays (CF coordinates) if requested.
5924 :
5925 : // Get OGR transform if GEOLOCATION is not available.
5926 27 : if (bWriteLonLat && !bHasGeoloc)
5927 : {
5928 1 : poLatLonSRS.reset(m_oSRS.CloneGeogCS());
5929 1 : if (poLatLonSRS != nullptr)
5930 : {
5931 1 : poLatLonSRS->SetAxisMappingStrategy(
5932 : OAMS_TRADITIONAL_GIS_ORDER);
5933 1 : poTransform.reset(OGRCreateCoordinateTransformation(
5934 1 : &m_oSRS, poLatLonSRS.get()));
5935 : }
5936 : // If no OGR transform, then don't write CF lon/lat.
5937 1 : if (poTransform == nullptr)
5938 : {
5939 0 : CPLError(CE_Failure, CPLE_AppDefined,
5940 : "Unable to get Coordinate Transform");
5941 0 : bWriteLonLat = false;
5942 : }
5943 : }
5944 :
5945 27 : if (bWriteLonLat)
5946 : {
5947 2 : if (!bHasGeoloc)
5948 1 : CPLDebug("GDAL_netCDF", "Transforming (X,Y)->(lon,lat)");
5949 : else
5950 1 : CPLDebug("GDAL_netCDF",
5951 : "Writing (lon,lat) from GEOLOCATION arrays");
5952 :
5953 2 : bool bOK = true;
5954 2 : double dfProgress = 0.2;
5955 :
5956 2 : size_t start[] = {0, 0};
5957 2 : size_t count[] = {1, (size_t)nRasterXSize};
5958 : std::unique_ptr<double, decltype(&VSIFree)> adLatValKeeper(
5959 : static_cast<double *>(
5960 4 : VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
5961 2 : VSIFree);
5962 : std::unique_ptr<double, decltype(&VSIFree)> adLonValKeeper(
5963 : static_cast<double *>(
5964 4 : VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
5965 2 : VSIFree);
5966 2 : double *padLonVal = adLonValKeeper.get();
5967 2 : double *padLatVal = adLatValKeeper.get();
5968 2 : if (!padLonVal || !padLatVal)
5969 : {
5970 0 : return CE_Failure;
5971 : }
5972 :
5973 61 : for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR;
5974 : j++)
5975 : {
5976 59 : start[0] = j;
5977 :
5978 : // Get values from geotransform.
5979 59 : if (!bHasGeoloc)
5980 : {
5981 : // Fill values to transform.
5982 420 : for (int i = 0; i < nRasterXSize; i++)
5983 : {
5984 400 : padLatVal[i] = padYVal[j];
5985 400 : padLonVal[i] = padXVal[i];
5986 : }
5987 :
5988 : // Do the transform.
5989 40 : bOK = CPL_TO_BOOL(poTransform->Transform(
5990 20 : nRasterXSize, padLonVal, padLatVal, nullptr));
5991 20 : if (!bOK)
5992 : {
5993 0 : CPLError(CE_Failure, CPLE_AppDefined,
5994 : "Unable to Transform (X,Y) to (lon,lat).");
5995 : }
5996 : }
5997 : // Get values from geoloc arrays.
5998 : else
5999 : {
6000 39 : CPLErr eErr = poBand_Y->RasterIO(
6001 : GF_Read, 0, j, nRasterXSize, 1, padLatVal,
6002 : nRasterXSize, 1, GDT_Float64, 0, 0, nullptr);
6003 39 : if (eErr == CE_None)
6004 : {
6005 39 : eErr = poBand_X->RasterIO(
6006 : GF_Read, 0, j, nRasterXSize, 1, padLonVal,
6007 : nRasterXSize, 1, GDT_Float64, 0, 0, nullptr);
6008 : }
6009 :
6010 39 : if (eErr == CE_None)
6011 : {
6012 39 : bOK = true;
6013 : }
6014 : else
6015 : {
6016 0 : bOK = false;
6017 0 : CPLError(CE_Failure, CPLE_AppDefined,
6018 : "Unable to get scanline %d", j);
6019 : }
6020 : }
6021 :
6022 : // Write data.
6023 59 : if (bOK)
6024 : {
6025 59 : status = nc_put_vara_double(cdfid, nVarLatID, start,
6026 : count, padLatVal);
6027 59 : NCDF_ERR(status);
6028 59 : status = nc_put_vara_double(cdfid, nVarLonID, start,
6029 : count, padLonVal);
6030 59 : NCDF_ERR(status);
6031 : }
6032 :
6033 59 : if (pfnProgress && (nRasterYSize / 10) > 0 &&
6034 59 : (j % (nRasterYSize / 10) == 0))
6035 : {
6036 23 : dfProgress += 0.08;
6037 23 : pfnProgress(dfProgress, nullptr, pProgressData);
6038 : }
6039 : }
6040 : }
6041 : } // Projected
6042 :
6043 : // If not projected/geographic and has geoloc
6044 57 : else if (!bIsGeographic && bHasGeoloc)
6045 : {
6046 : // Use
6047 : // https://cfconventions.org/Data/cf-conventions/cf-conventions-1.9/cf-conventions.html#_two_dimensional_latitude_longitude_coordinate_variables
6048 :
6049 4 : bool bOK = true;
6050 4 : double dfProgress = 0.2;
6051 :
6052 : // Make sure we are in data mode.
6053 4 : SetDefineMode(false);
6054 :
6055 : size_t startX[1];
6056 : size_t countX[1];
6057 : size_t startY[1];
6058 : size_t countY[1];
6059 4 : startX[0] = 0;
6060 4 : countX[0] = nRasterXSize;
6061 :
6062 4 : startY[0] = 0;
6063 4 : countY[0] = nRasterYSize;
6064 :
6065 4 : std::vector<double> adfXVal;
6066 4 : std::vector<double> adfYVal;
6067 : try
6068 : {
6069 4 : adfXVal.resize(nRasterXSize);
6070 4 : adfYVal.resize(nRasterYSize);
6071 : }
6072 0 : catch (const std::exception &)
6073 : {
6074 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
6075 : "Out of memory allocating temporary array");
6076 0 : return CE_Failure;
6077 : }
6078 16 : for (int i = 0; i < nRasterXSize; i++)
6079 12 : adfXVal[i] = i;
6080 12 : for (int i = 0; i < nRasterYSize; i++)
6081 8 : adfYVal[i] = bBottomUp ? nRasterYSize - 1 - i : i;
6082 :
6083 4 : CPLDebug("GDAL_netCDF", "Writing X values");
6084 4 : int status = nc_put_vara_double(cdfid, nVarXID, startX, countX,
6085 4 : adfXVal.data());
6086 4 : NCDF_ERR(status);
6087 :
6088 4 : CPLDebug("GDAL_netCDF", "Writing Y values");
6089 4 : status = nc_put_vara_double(cdfid, nVarYID, startY, countY,
6090 4 : adfYVal.data());
6091 4 : NCDF_ERR(status);
6092 :
6093 4 : if (pfnProgress)
6094 0 : pfnProgress(0.20, nullptr, pProgressData);
6095 :
6096 4 : size_t start[] = {0, 0};
6097 4 : size_t count[] = {1, (size_t)nRasterXSize};
6098 :
6099 : std::unique_ptr<double, decltype(&VSIFree)> adLatValKeeper(
6100 : static_cast<double *>(
6101 8 : VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
6102 4 : VSIFree);
6103 : std::unique_ptr<double, decltype(&VSIFree)> adLonValKeeper(
6104 : static_cast<double *>(
6105 8 : VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
6106 4 : VSIFree);
6107 4 : double *padLonVal = adLonValKeeper.get();
6108 4 : double *padLatVal = adLatValKeeper.get();
6109 4 : if (!padLonVal || !padLatVal)
6110 : {
6111 0 : return CE_Failure;
6112 : }
6113 :
6114 12 : for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR; j++)
6115 : {
6116 8 : start[0] = j;
6117 :
6118 8 : CPLErr eErr = poBand_Y->RasterIO(
6119 8 : GF_Read, 0, bBottomUp ? nRasterYSize - 1 - j : j,
6120 : nRasterXSize, 1, padLatVal, nRasterXSize, 1, GDT_Float64, 0,
6121 : 0, nullptr);
6122 8 : if (eErr == CE_None)
6123 : {
6124 8 : eErr = poBand_X->RasterIO(
6125 8 : GF_Read, 0, bBottomUp ? nRasterYSize - 1 - j : j,
6126 : nRasterXSize, 1, padLonVal, nRasterXSize, 1,
6127 : GDT_Float64, 0, 0, nullptr);
6128 : }
6129 :
6130 8 : if (eErr == CE_None)
6131 : {
6132 8 : bOK = true;
6133 : }
6134 : else
6135 : {
6136 0 : bOK = false;
6137 0 : CPLError(CE_Failure, CPLE_AppDefined,
6138 : "Unable to get scanline %d", j);
6139 : }
6140 :
6141 : // Write data.
6142 8 : if (bOK)
6143 : {
6144 8 : status = nc_put_vara_double(cdfid, nVarLatID, start, count,
6145 : padLatVal);
6146 8 : NCDF_ERR(status);
6147 8 : status = nc_put_vara_double(cdfid, nVarLonID, start, count,
6148 : padLonVal);
6149 8 : NCDF_ERR(status);
6150 : }
6151 :
6152 8 : if (pfnProgress && (nRasterYSize / 10) > 0 &&
6153 0 : (j % (nRasterYSize / 10) == 0))
6154 : {
6155 0 : dfProgress += 0.08;
6156 0 : pfnProgress(dfProgress, nullptr, pProgressData);
6157 : }
6158 4 : }
6159 : }
6160 :
6161 : // If not projected, assume geographic to catch grids without Datum.
6162 53 : else if (bWriteLonLat)
6163 : {
6164 : // Get latitude values.
6165 53 : const double dfY0 = (!bBottomUp) ? m_gt.yorig :
6166 : // Invert latitude values.
6167 53 : m_gt.yorig + (m_gt.yscale * nRasterYSize);
6168 53 : const double dfDY = m_gt.yscale;
6169 :
6170 : std::unique_ptr<double, decltype(&VSIFree)> adLatValKeeper(nullptr,
6171 53 : VSIFree);
6172 53 : double *padLatVal = nullptr;
6173 : // Override lat values with the ones in GEOLOCATION/Y_VALUES.
6174 53 : if (netCDFDataset::GetMetadataItem("Y_VALUES", "GEOLOCATION") !=
6175 : nullptr)
6176 : {
6177 0 : int nTemp = 0;
6178 0 : adLatValKeeper.reset(Get1DGeolocation("Y_VALUES", nTemp));
6179 0 : padLatVal = adLatValKeeper.get();
6180 : // Make sure we got the correct amount, if not fallback to GT */
6181 : // could add test fabs(fabs(padLatVal[0]) - fabs(dfY0)) <= 0.1))
6182 0 : if (nTemp == nRasterYSize)
6183 : {
6184 0 : CPLDebug(
6185 : "GDAL_netCDF",
6186 : "Using Y_VALUES geolocation metadata for lat values");
6187 : }
6188 : else
6189 : {
6190 0 : CPLDebug("GDAL_netCDF",
6191 : "Got %d elements from Y_VALUES geolocation "
6192 : "metadata, need %d",
6193 : nTemp, nRasterYSize);
6194 0 : padLatVal = nullptr;
6195 : }
6196 : }
6197 :
6198 53 : if (padLatVal == nullptr)
6199 : {
6200 53 : adLatValKeeper.reset(static_cast<double *>(
6201 53 : VSI_MALLOC2_VERBOSE(nRasterYSize, sizeof(double))));
6202 53 : padLatVal = adLatValKeeper.get();
6203 53 : if (!padLatVal)
6204 : {
6205 0 : return CE_Failure;
6206 : }
6207 7105 : for (int i = 0; i < nRasterYSize; i++)
6208 : {
6209 : // The data point is centered inside the pixel.
6210 7052 : if (!bBottomUp)
6211 0 : padLatVal[i] = dfY0 + (i + 0.5) * dfDY;
6212 : else // Invert latitude values.
6213 7052 : padLatVal[i] = dfY0 - (i + 0.5) * dfDY;
6214 : }
6215 : }
6216 :
6217 53 : size_t startLat[1] = {0};
6218 53 : size_t countLat[1] = {static_cast<size_t>(nRasterYSize)};
6219 :
6220 : // Get longitude values.
6221 53 : const double dfX0 = m_gt.xorig;
6222 53 : const double dfDX = m_gt.xscale;
6223 :
6224 : std::unique_ptr<double, decltype(&VSIFree)> adLonValKeeper(
6225 : static_cast<double *>(
6226 106 : VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
6227 53 : VSIFree);
6228 53 : double *padLonVal = adLonValKeeper.get();
6229 53 : if (!padLonVal)
6230 : {
6231 0 : return CE_Failure;
6232 : }
6233 7157 : for (int i = 0; i < nRasterXSize; i++)
6234 : {
6235 : // The data point is centered inside the pixel.
6236 7104 : padLonVal[i] = dfX0 + (i + 0.5) * dfDX;
6237 : }
6238 :
6239 53 : size_t startLon[1] = {0};
6240 53 : size_t countLon[1] = {static_cast<size_t>(nRasterXSize)};
6241 :
6242 : // Write latitude and longitude values.
6243 :
6244 : // Make sure we are in data mode.
6245 53 : SetDefineMode(false);
6246 :
6247 : // Write values.
6248 53 : CPLDebug("GDAL_netCDF", "Writing lat values");
6249 :
6250 53 : int status = nc_put_vara_double(cdfid, nVarLatID, startLat,
6251 : countLat, padLatVal);
6252 53 : NCDF_ERR(status);
6253 :
6254 53 : CPLDebug("GDAL_netCDF", "Writing lon values");
6255 53 : status = nc_put_vara_double(cdfid, nVarLonID, startLon, countLon,
6256 : padLonVal);
6257 53 : NCDF_ERR(status);
6258 :
6259 : } // Not projected.
6260 :
6261 84 : if (pfnProgress)
6262 43 : pfnProgress(1.00, nullptr, pProgressData);
6263 : }
6264 :
6265 168 : return CE_None;
6266 : }
6267 :
6268 : // Write Projection variable to band variable.
6269 : // Moved from AddProjectionVars() for cases when bands are added after
6270 : // projection.
6271 433 : bool netCDFDataset::AddGridMappingRef()
6272 : {
6273 433 : bool bRet = true;
6274 433 : bool bOldDefineMode = bDefineMode;
6275 :
6276 628 : if ((GetAccess() == GA_Update) && (nBands >= 1) && (GetRasterBand(1)) &&
6277 195 : ((pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, "")) ||
6278 189 : (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))))
6279 : {
6280 75 : bAddedGridMappingRef = true;
6281 :
6282 : // Make sure we are in define mode.
6283 75 : SetDefineMode(true);
6284 :
6285 196 : for (int i = 1; i <= nBands; i++)
6286 : {
6287 : const int nVarId =
6288 121 : cpl::down_cast<netCDFRasterBand *>(GetRasterBand(i))->nZId;
6289 :
6290 121 : if (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))
6291 : {
6292 : int status =
6293 234 : nc_put_att_text(cdfid, nVarId, CF_GRD_MAPPING,
6294 117 : strlen(pszCFProjection), pszCFProjection);
6295 117 : NCDF_ERR(status);
6296 117 : if (status != NC_NOERR)
6297 0 : bRet = false;
6298 : }
6299 121 : if (pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, ""))
6300 : {
6301 : int status =
6302 6 : nc_put_att_text(cdfid, nVarId, CF_COORDINATES,
6303 : strlen(pszCFCoordinates), pszCFCoordinates);
6304 6 : NCDF_ERR(status);
6305 6 : if (status != NC_NOERR)
6306 0 : bRet = false;
6307 : }
6308 : }
6309 :
6310 : // Go back to previous define mode.
6311 75 : SetDefineMode(bOldDefineMode);
6312 : }
6313 433 : return bRet;
6314 : }
6315 :
6316 : /************************************************************************/
6317 : /* GetGeoTransform() */
6318 : /************************************************************************/
6319 :
6320 121 : CPLErr netCDFDataset::GetGeoTransform(GDALGeoTransform >) const
6321 :
6322 : {
6323 121 : gt = m_gt;
6324 121 : if (m_bHasGeoTransform)
6325 89 : return CE_None;
6326 :
6327 32 : return GDALPamDataset::GetGeoTransform(gt);
6328 : }
6329 :
6330 : /************************************************************************/
6331 : /* rint() */
6332 : /************************************************************************/
6333 :
6334 0 : double netCDFDataset::rint(double dfX)
6335 : {
6336 0 : return std::round(dfX);
6337 : }
6338 :
6339 : /************************************************************************/
6340 : /* NCDFReadIsoMetadata() */
6341 : /************************************************************************/
6342 :
6343 16 : static void NCDFReadMetadataAsJson(int cdfid, CPLJSONObject &obj)
6344 : {
6345 16 : int nbAttr = 0;
6346 16 : NCDF_ERR(nc_inq_varnatts(cdfid, NC_GLOBAL, &nbAttr));
6347 :
6348 32 : std::map<std::string, CPLJSONArray> oMapNameToArray;
6349 40 : for (int l = 0; l < nbAttr; l++)
6350 : {
6351 : char szAttrName[NC_MAX_NAME + 1];
6352 24 : szAttrName[0] = 0;
6353 24 : NCDF_ERR(nc_inq_attname(cdfid, NC_GLOBAL, l, szAttrName));
6354 :
6355 24 : char *pszMetaValue = nullptr;
6356 24 : if (NCDFGetAttr(cdfid, NC_GLOBAL, szAttrName, &pszMetaValue) == CE_None)
6357 : {
6358 24 : nc_type nAttrType = NC_NAT;
6359 24 : size_t nAttrLen = 0;
6360 :
6361 24 : NCDF_ERR(nc_inq_att(cdfid, NC_GLOBAL, szAttrName, &nAttrType,
6362 : &nAttrLen));
6363 :
6364 24 : std::string osAttrName(szAttrName);
6365 24 : const auto sharpPos = osAttrName.find('#');
6366 24 : if (sharpPos == std::string::npos)
6367 : {
6368 16 : if (nAttrType == NC_DOUBLE || nAttrType == NC_FLOAT)
6369 4 : obj.Add(osAttrName, CPLAtof(pszMetaValue));
6370 : else
6371 12 : obj.Add(osAttrName, pszMetaValue);
6372 : }
6373 : else
6374 : {
6375 8 : osAttrName.resize(sharpPos);
6376 8 : auto iter = oMapNameToArray.find(osAttrName);
6377 8 : if (iter == oMapNameToArray.end())
6378 : {
6379 8 : CPLJSONArray array;
6380 4 : obj.Add(osAttrName, array);
6381 4 : oMapNameToArray[osAttrName] = array;
6382 4 : array.Add(pszMetaValue);
6383 : }
6384 : else
6385 : {
6386 4 : iter->second.Add(pszMetaValue);
6387 : }
6388 : }
6389 24 : CPLFree(pszMetaValue);
6390 24 : pszMetaValue = nullptr;
6391 : }
6392 : }
6393 :
6394 16 : int nSubGroups = 0;
6395 16 : int *panSubGroupIds = nullptr;
6396 16 : NCDFGetSubGroups(cdfid, &nSubGroups, &panSubGroupIds);
6397 16 : oMapNameToArray.clear();
6398 28 : for (int i = 0; i < nSubGroups; i++)
6399 : {
6400 24 : CPLJSONObject subObj;
6401 12 : NCDFReadMetadataAsJson(panSubGroupIds[i], subObj);
6402 :
6403 24 : std::string osGroupName;
6404 12 : osGroupName.resize(NC_MAX_NAME);
6405 12 : NCDF_ERR(nc_inq_grpname(panSubGroupIds[i], &osGroupName[0]));
6406 12 : osGroupName.resize(strlen(osGroupName.data()));
6407 12 : const auto sharpPos = osGroupName.find('#');
6408 12 : if (sharpPos == std::string::npos)
6409 : {
6410 4 : obj.Add(osGroupName, subObj);
6411 : }
6412 : else
6413 : {
6414 8 : osGroupName.resize(sharpPos);
6415 8 : auto iter = oMapNameToArray.find(osGroupName);
6416 8 : if (iter == oMapNameToArray.end())
6417 : {
6418 8 : CPLJSONArray array;
6419 4 : obj.Add(osGroupName, array);
6420 4 : oMapNameToArray[osGroupName] = array;
6421 4 : array.Add(subObj);
6422 : }
6423 : else
6424 : {
6425 4 : iter->second.Add(subObj);
6426 : }
6427 : }
6428 : }
6429 16 : CPLFree(panSubGroupIds);
6430 16 : }
6431 :
6432 4 : std::string NCDFReadMetadataAsJson(int cdfid)
6433 : {
6434 8 : CPLJSONDocument oDoc;
6435 8 : CPLJSONObject oRoot = oDoc.GetRoot();
6436 4 : NCDFReadMetadataAsJson(cdfid, oRoot);
6437 8 : return oDoc.SaveAsString();
6438 : }
6439 :
6440 : /************************************************************************/
6441 : /* ReadAttributes() */
6442 : /************************************************************************/
6443 1864 : CPLErr netCDFDataset::ReadAttributes(int cdfidIn, int var)
6444 :
6445 : {
6446 1864 : char *pszVarFullName = nullptr;
6447 1864 : ERR_RET(NCDFGetVarFullName(cdfidIn, var, &pszVarFullName));
6448 :
6449 : // For metadata in Sentinel 5
6450 1864 : if (STARTS_WITH(pszVarFullName, "/METADATA/"))
6451 : {
6452 6 : for (const char *key :
6453 : {"ISO_METADATA", "ESA_METADATA", "EOP_METADATA", "QA_STATISTICS",
6454 8 : "GRANULE_DESCRIPTION", "ALGORITHM_SETTINGS"})
6455 : {
6456 14 : if (var == NC_GLOBAL &&
6457 7 : strcmp(pszVarFullName,
6458 : CPLSPrintf("/METADATA/%s/NC_GLOBAL", key)) == 0)
6459 : {
6460 1 : CPLFree(pszVarFullName);
6461 1 : CPLStringList aosList;
6462 2 : aosList.AddString(CPLString(NCDFReadMetadataAsJson(cdfidIn))
6463 1 : .replaceAll("\\/", '/'));
6464 1 : m_oMapDomainToJSon[key] = std::move(aosList);
6465 1 : return CE_None;
6466 : }
6467 : }
6468 : }
6469 1863 : if (STARTS_WITH(pszVarFullName, "/PRODUCT/SUPPORT_DATA/"))
6470 : {
6471 0 : CPLFree(pszVarFullName);
6472 0 : CPLStringList aosList;
6473 : aosList.AddString(
6474 0 : CPLString(NCDFReadMetadataAsJson(cdfidIn)).replaceAll("\\/", '/'));
6475 0 : m_oMapDomainToJSon["SUPPORT_DATA"] = std::move(aosList);
6476 0 : return CE_None;
6477 : }
6478 :
6479 1863 : size_t nMetaNameSize =
6480 1863 : sizeof(char) * (strlen(pszVarFullName) + 1 + NC_MAX_NAME + 1);
6481 1863 : char *pszMetaName = static_cast<char *>(CPLMalloc(nMetaNameSize));
6482 :
6483 1863 : int nbAttr = 0;
6484 1863 : NCDF_ERR(nc_inq_varnatts(cdfidIn, var, &nbAttr));
6485 :
6486 9377 : for (int l = 0; l < nbAttr; l++)
6487 : {
6488 : char szAttrName[NC_MAX_NAME + 1];
6489 7514 : szAttrName[0] = 0;
6490 7514 : NCDF_ERR(nc_inq_attname(cdfidIn, var, l, szAttrName));
6491 7514 : snprintf(pszMetaName, nMetaNameSize, "%s#%s", pszVarFullName,
6492 : szAttrName);
6493 :
6494 7514 : char *pszMetaTemp = nullptr;
6495 7514 : if (NCDFGetAttr(cdfidIn, var, szAttrName, &pszMetaTemp) == CE_None)
6496 : {
6497 7513 : papszMetadata =
6498 7513 : CSLSetNameValue(papszMetadata, pszMetaName, pszMetaTemp);
6499 7513 : CPLFree(pszMetaTemp);
6500 7513 : pszMetaTemp = nullptr;
6501 : }
6502 : else
6503 : {
6504 1 : CPLDebug("GDAL_netCDF", "invalid metadata %s", pszMetaName);
6505 : }
6506 : }
6507 :
6508 1863 : CPLFree(pszVarFullName);
6509 1863 : CPLFree(pszMetaName);
6510 :
6511 1863 : if (var == NC_GLOBAL)
6512 : {
6513 : // Recurse on sub-groups.
6514 533 : int nSubGroups = 0;
6515 533 : int *panSubGroupIds = nullptr;
6516 533 : NCDFGetSubGroups(cdfidIn, &nSubGroups, &panSubGroupIds);
6517 565 : for (int i = 0; i < nSubGroups; i++)
6518 : {
6519 32 : ReadAttributes(panSubGroupIds[i], var);
6520 : }
6521 533 : CPLFree(panSubGroupIds);
6522 : }
6523 :
6524 1863 : return CE_None;
6525 : }
6526 :
6527 : /************************************************************************/
6528 : /* netCDFDataset::CreateSubDatasetList() */
6529 : /************************************************************************/
6530 55 : void netCDFDataset::CreateSubDatasetList(int nGroupId)
6531 : {
6532 : char szVarStdName[NC_MAX_NAME + 1];
6533 55 : int *ponDimIds = nullptr;
6534 : nc_type nAttype;
6535 : size_t nAttlen;
6536 :
6537 55 : netCDFDataset *poDS = this;
6538 :
6539 : int nVarCount;
6540 55 : nc_inq_nvars(nGroupId, &nVarCount);
6541 :
6542 55 : const bool bListAllArrays = CPLTestBool(
6543 55 : CSLFetchNameValueDef(papszOpenOptions, "LIST_ALL_ARRAYS", "NO"));
6544 :
6545 338 : for (int nVar = 0; nVar < nVarCount; nVar++)
6546 : {
6547 :
6548 : int nDims;
6549 283 : nc_inq_varndims(nGroupId, nVar, &nDims);
6550 :
6551 283 : if ((bListAllArrays && nDims > 0) || nDims >= 2)
6552 : {
6553 162 : ponDimIds = static_cast<int *>(CPLCalloc(nDims, sizeof(int)));
6554 162 : nc_inq_vardimid(nGroupId, nVar, ponDimIds);
6555 :
6556 : // Create Sub dataset list.
6557 162 : CPLString osDim;
6558 499 : for (int i = 0; i < nDims; i++)
6559 : {
6560 : size_t nDimLen;
6561 337 : nc_inq_dimlen(nGroupId, ponDimIds[i], &nDimLen);
6562 337 : if (!osDim.empty())
6563 175 : osDim += 'x';
6564 337 : osDim += CPLSPrintf("%d", (int)nDimLen);
6565 : }
6566 162 : CPLFree(ponDimIds);
6567 :
6568 : nc_type nVarType;
6569 162 : nc_inq_vartype(nGroupId, nVar, &nVarType);
6570 162 : const char *pszType = "";
6571 162 : switch (nVarType)
6572 : {
6573 38 : case NC_BYTE:
6574 38 : pszType = "8-bit integer";
6575 38 : break;
6576 2 : case NC_CHAR:
6577 2 : pszType = "8-bit character";
6578 2 : break;
6579 6 : case NC_SHORT:
6580 6 : pszType = "16-bit integer";
6581 6 : break;
6582 10 : case NC_INT:
6583 10 : pszType = "32-bit integer";
6584 10 : break;
6585 54 : case NC_FLOAT:
6586 54 : pszType = "32-bit floating-point";
6587 54 : break;
6588 34 : case NC_DOUBLE:
6589 34 : pszType = "64-bit floating-point";
6590 34 : break;
6591 4 : case NC_UBYTE:
6592 4 : pszType = "8-bit unsigned integer";
6593 4 : break;
6594 1 : case NC_USHORT:
6595 1 : pszType = "16-bit unsigned integer";
6596 1 : break;
6597 1 : case NC_UINT:
6598 1 : pszType = "32-bit unsigned integer";
6599 1 : break;
6600 1 : case NC_INT64:
6601 1 : pszType = "64-bit integer";
6602 1 : break;
6603 1 : case NC_UINT64:
6604 1 : pszType = "64-bit unsigned integer";
6605 1 : break;
6606 10 : default:
6607 10 : break;
6608 : }
6609 :
6610 162 : char *pszName = nullptr;
6611 162 : if (NCDFGetVarFullName(nGroupId, nVar, &pszName) != CE_None)
6612 0 : continue;
6613 :
6614 162 : nSubDatasets++;
6615 :
6616 162 : nAttlen = 0;
6617 162 : nc_inq_att(nGroupId, nVar, CF_STD_NAME, &nAttype, &nAttlen);
6618 324 : if (nAttlen < sizeof(szVarStdName) &&
6619 162 : nc_get_att_text(nGroupId, nVar, CF_STD_NAME, szVarStdName) ==
6620 : NC_NOERR)
6621 : {
6622 56 : szVarStdName[nAttlen] = '\0';
6623 : }
6624 : else
6625 : {
6626 106 : snprintf(szVarStdName, sizeof(szVarStdName), "%s", pszName);
6627 : }
6628 :
6629 : char szTemp[NC_MAX_NAME + 1];
6630 162 : snprintf(szTemp, sizeof(szTemp), "SUBDATASET_%d_NAME",
6631 : nSubDatasets);
6632 :
6633 162 : if (strchr(pszName, ' ') || strchr(pszName, ':'))
6634 : {
6635 1 : poDS->papszSubDatasets = CSLSetNameValue(
6636 : poDS->papszSubDatasets, szTemp,
6637 : CPLSPrintf("NETCDF:\"%s\":\"%s\"", poDS->osFilename.c_str(),
6638 : pszName));
6639 : }
6640 : else
6641 : {
6642 161 : poDS->papszSubDatasets = CSLSetNameValue(
6643 : poDS->papszSubDatasets, szTemp,
6644 : CPLSPrintf("NETCDF:\"%s\":%s", poDS->osFilename.c_str(),
6645 : pszName));
6646 : }
6647 :
6648 162 : CPLFree(pszName);
6649 :
6650 162 : snprintf(szTemp, sizeof(szTemp), "SUBDATASET_%d_DESC",
6651 : nSubDatasets);
6652 :
6653 162 : poDS->papszSubDatasets =
6654 162 : CSLSetNameValue(poDS->papszSubDatasets, szTemp,
6655 : CPLSPrintf("[%s] %s (%s)", osDim.c_str(),
6656 : szVarStdName, pszType));
6657 : }
6658 : }
6659 :
6660 : // Recurse on sub groups.
6661 55 : int nSubGroups = 0;
6662 55 : int *panSubGroupIds = nullptr;
6663 55 : NCDFGetSubGroups(nGroupId, &nSubGroups, &panSubGroupIds);
6664 62 : for (int i = 0; i < nSubGroups; i++)
6665 : {
6666 7 : CreateSubDatasetList(panSubGroupIds[i]);
6667 : }
6668 55 : CPLFree(panSubGroupIds);
6669 55 : }
6670 :
6671 : /************************************************************************/
6672 : /* TestCapability() */
6673 : /************************************************************************/
6674 :
6675 249 : int netCDFDataset::TestCapability(const char *pszCap) const
6676 : {
6677 249 : if (EQUAL(pszCap, ODsCCreateLayer))
6678 : {
6679 225 : return eAccess == GA_Update && nBands == 0 &&
6680 219 : (eMultipleLayerBehavior != SINGLE_LAYER ||
6681 230 : this->GetLayerCount() == 0 || bSGSupport);
6682 : }
6683 136 : else if (EQUAL(pszCap, ODsCZGeometries))
6684 2 : return true;
6685 :
6686 134 : return false;
6687 : }
6688 :
6689 : /************************************************************************/
6690 : /* GetLayer() */
6691 : /************************************************************************/
6692 :
6693 385 : const OGRLayer *netCDFDataset::GetLayer(int nIdx) const
6694 : {
6695 385 : if (nIdx < 0 || nIdx >= this->GetLayerCount())
6696 2 : return nullptr;
6697 383 : return papoLayers[nIdx].get();
6698 : }
6699 :
6700 : /************************************************************************/
6701 : /* ICreateLayer() */
6702 : /************************************************************************/
6703 :
6704 60 : OGRLayer *netCDFDataset::ICreateLayer(const char *pszName,
6705 : const OGRGeomFieldDefn *poGeomFieldDefn,
6706 : CSLConstList papszOptions)
6707 : {
6708 60 : int nLayerCDFId = cdfid;
6709 60 : if (!TestCapability(ODsCCreateLayer))
6710 0 : return nullptr;
6711 :
6712 60 : const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
6713 : const auto poSpatialRef =
6714 60 : poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
6715 :
6716 120 : CPLString osNetCDFLayerName(pszName);
6717 60 : const netCDFWriterConfigLayer *poLayerConfig = nullptr;
6718 60 : if (oWriterConfig.m_bIsValid)
6719 : {
6720 : std::map<CPLString, netCDFWriterConfigLayer>::const_iterator
6721 2 : oLayerIter = oWriterConfig.m_oLayers.find(pszName);
6722 2 : if (oLayerIter != oWriterConfig.m_oLayers.end())
6723 : {
6724 1 : poLayerConfig = &(oLayerIter->second);
6725 1 : osNetCDFLayerName = poLayerConfig->m_osNetCDFName;
6726 : }
6727 : }
6728 :
6729 60 : netCDFDataset *poLayerDataset = nullptr;
6730 60 : if (eMultipleLayerBehavior == SEPARATE_FILES)
6731 : {
6732 3 : if (CPLLaunderForFilenameSafe(osNetCDFLayerName.c_str(), nullptr) !=
6733 : osNetCDFLayerName)
6734 : {
6735 1 : CPLError(CE_Failure, CPLE_AppDefined,
6736 : "Illegal characters in '%s' to form a valid filename",
6737 : osNetCDFLayerName.c_str());
6738 1 : return nullptr;
6739 : }
6740 2 : CPLStringList aosDatasetOptions;
6741 : aosDatasetOptions.SetNameValue(
6742 : "CONFIG_FILE",
6743 2 : CSLFetchNameValue(papszCreationOptions, "CONFIG_FILE"));
6744 : aosDatasetOptions.SetNameValue(
6745 2 : "FORMAT", CSLFetchNameValue(papszCreationOptions, "FORMAT"));
6746 : aosDatasetOptions.SetNameValue(
6747 : "WRITE_GDAL_TAGS",
6748 2 : CSLFetchNameValue(papszCreationOptions, "WRITE_GDAL_TAGS"));
6749 : const CPLString osLayerFilename(
6750 2 : CPLFormFilenameSafe(osFilename, osNetCDFLayerName, "nc"));
6751 2 : CPLAcquireMutex(hNCMutex, 1000.0);
6752 2 : poLayerDataset =
6753 2 : CreateLL(osLayerFilename, 0, 0, 0, aosDatasetOptions.List());
6754 2 : CPLReleaseMutex(hNCMutex);
6755 2 : if (poLayerDataset == nullptr)
6756 0 : return nullptr;
6757 :
6758 2 : nLayerCDFId = poLayerDataset->cdfid;
6759 2 : NCDFAddGDALHistory(nLayerCDFId, osLayerFilename, bWriteGDALVersion,
6760 2 : bWriteGDALHistory, "", "Create",
6761 : NCDF_CONVENTIONS_CF_V1_6);
6762 : }
6763 57 : else if (eMultipleLayerBehavior == SEPARATE_GROUPS)
6764 : {
6765 2 : SetDefineMode(true);
6766 :
6767 2 : nLayerCDFId = -1;
6768 2 : int status = nc_def_grp(cdfid, osNetCDFLayerName, &nLayerCDFId);
6769 2 : NCDF_ERR(status);
6770 2 : if (status != NC_NOERR)
6771 0 : return nullptr;
6772 :
6773 2 : NCDFAddGDALHistory(nLayerCDFId, osFilename, bWriteGDALVersion,
6774 2 : bWriteGDALHistory, "", "Create",
6775 : NCDF_CONVENTIONS_CF_V1_6);
6776 : }
6777 :
6778 : // Make a clone to workaround a bug in released MapServer versions
6779 : // that destroys the passed SRS instead of releasing it .
6780 59 : OGRSpatialReference *poSRS = nullptr;
6781 59 : if (poSpatialRef)
6782 : {
6783 43 : poSRS = poSpatialRef->Clone();
6784 43 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
6785 : }
6786 : std::shared_ptr<netCDFLayer> poLayer(
6787 59 : new netCDFLayer(poLayerDataset ? poLayerDataset : this, nLayerCDFId,
6788 118 : osNetCDFLayerName, eGType, poSRS));
6789 59 : if (poSRS != nullptr)
6790 43 : poSRS->Release();
6791 :
6792 : // Fetch layer creation options coming from config file
6793 118 : CPLStringList aosNewOptions(CSLDuplicate(papszOptions));
6794 59 : if (oWriterConfig.m_bIsValid)
6795 : {
6796 2 : for (const auto &[osName, osValue] :
6797 4 : oWriterConfig.m_oLayerCreationOptions)
6798 : {
6799 1 : aosNewOptions.SetNameValue(osName, osValue);
6800 : }
6801 2 : if (poLayerConfig != nullptr)
6802 : {
6803 4 : for (const auto &[osName, osValue] :
6804 5 : poLayerConfig->m_oLayerCreationOptions)
6805 : {
6806 2 : aosNewOptions.SetNameValue(osName, osValue);
6807 : }
6808 : }
6809 : }
6810 :
6811 59 : const bool bRet = poLayer->Create(aosNewOptions.List(), poLayerConfig);
6812 :
6813 59 : if (!bRet)
6814 : {
6815 0 : return nullptr;
6816 : }
6817 :
6818 59 : if (poLayerDataset != nullptr)
6819 2 : apoVectorDatasets.push_back(poLayerDataset);
6820 :
6821 59 : papoLayers.push_back(poLayer);
6822 59 : return poLayer.get();
6823 : }
6824 :
6825 : /************************************************************************/
6826 : /* CloneAttributes() */
6827 : /************************************************************************/
6828 :
6829 137 : bool netCDFDataset::CloneAttributes(int old_cdfid, int new_cdfid, int nSrcVarId,
6830 : int nDstVarId)
6831 : {
6832 137 : int nAttCount = -1;
6833 137 : int status = nc_inq_varnatts(old_cdfid, nSrcVarId, &nAttCount);
6834 137 : NCDF_ERR(status);
6835 :
6836 693 : for (int i = 0; i < nAttCount; i++)
6837 : {
6838 : char szName[NC_MAX_NAME + 1];
6839 556 : szName[0] = 0;
6840 556 : status = nc_inq_attname(old_cdfid, nSrcVarId, i, szName);
6841 556 : NCDF_ERR(status);
6842 :
6843 : status =
6844 556 : nc_copy_att(old_cdfid, nSrcVarId, szName, new_cdfid, nDstVarId);
6845 556 : NCDF_ERR(status);
6846 556 : if (status != NC_NOERR)
6847 0 : return false;
6848 : }
6849 :
6850 137 : return true;
6851 : }
6852 :
6853 : /************************************************************************/
6854 : /* CloneVariableContent() */
6855 : /************************************************************************/
6856 :
6857 121 : bool netCDFDataset::CloneVariableContent(int old_cdfid, int new_cdfid,
6858 : int nSrcVarId, int nDstVarId)
6859 : {
6860 121 : int nVarDimCount = -1;
6861 121 : int status = nc_inq_varndims(old_cdfid, nSrcVarId, &nVarDimCount);
6862 121 : NCDF_ERR(status);
6863 121 : int anDimIds[] = {-1, 1};
6864 121 : status = nc_inq_vardimid(old_cdfid, nSrcVarId, anDimIds);
6865 121 : NCDF_ERR(status);
6866 121 : nc_type nc_datatype = NC_NAT;
6867 121 : status = nc_inq_vartype(old_cdfid, nSrcVarId, &nc_datatype);
6868 121 : NCDF_ERR(status);
6869 121 : size_t nTypeSize = 0;
6870 121 : switch (nc_datatype)
6871 : {
6872 35 : case NC_BYTE:
6873 : case NC_CHAR:
6874 35 : nTypeSize = 1;
6875 35 : break;
6876 4 : case NC_SHORT:
6877 4 : nTypeSize = 2;
6878 4 : break;
6879 24 : case NC_INT:
6880 24 : nTypeSize = 4;
6881 24 : break;
6882 4 : case NC_FLOAT:
6883 4 : nTypeSize = 4;
6884 4 : break;
6885 43 : case NC_DOUBLE:
6886 43 : nTypeSize = 8;
6887 43 : break;
6888 2 : case NC_UBYTE:
6889 2 : nTypeSize = 1;
6890 2 : break;
6891 2 : case NC_USHORT:
6892 2 : nTypeSize = 2;
6893 2 : break;
6894 2 : case NC_UINT:
6895 2 : nTypeSize = 4;
6896 2 : break;
6897 4 : case NC_INT64:
6898 : case NC_UINT64:
6899 4 : nTypeSize = 8;
6900 4 : break;
6901 1 : case NC_STRING:
6902 1 : nTypeSize = sizeof(char *);
6903 1 : break;
6904 0 : default:
6905 : {
6906 0 : CPLError(CE_Failure, CPLE_NotSupported, "Unsupported data type: %d",
6907 : nc_datatype);
6908 0 : return false;
6909 : }
6910 : }
6911 :
6912 121 : size_t nElems = 1;
6913 : size_t anStart[NC_MAX_DIMS];
6914 : size_t anCount[NC_MAX_DIMS];
6915 121 : size_t nRecords = 1;
6916 261 : for (int i = 0; i < nVarDimCount; i++)
6917 : {
6918 140 : anStart[i] = 0;
6919 140 : if (i == 0)
6920 : {
6921 116 : anCount[i] = 1;
6922 116 : status = nc_inq_dimlen(old_cdfid, anDimIds[i], &nRecords);
6923 116 : NCDF_ERR(status);
6924 : }
6925 : else
6926 : {
6927 24 : anCount[i] = 0;
6928 24 : status = nc_inq_dimlen(old_cdfid, anDimIds[i], &anCount[i]);
6929 24 : NCDF_ERR(status);
6930 24 : nElems *= anCount[i];
6931 : }
6932 : }
6933 :
6934 : /* Workaround in some cases a netCDF bug:
6935 : * https://github.com/Unidata/netcdf-c/pull/1442 */
6936 121 : if (nRecords > 0 && nRecords < 10 * 1000 * 1000 / (nElems * nTypeSize))
6937 : {
6938 119 : nElems *= nRecords;
6939 119 : anCount[0] = nRecords;
6940 119 : nRecords = 1;
6941 : }
6942 :
6943 121 : void *pBuffer = VSI_MALLOC2_VERBOSE(nElems, nTypeSize);
6944 121 : if (pBuffer == nullptr)
6945 0 : return false;
6946 :
6947 240 : for (size_t iRecord = 0; iRecord < nRecords; iRecord++)
6948 : {
6949 119 : anStart[0] = iRecord;
6950 :
6951 119 : switch (nc_datatype)
6952 : {
6953 5 : case NC_BYTE:
6954 : status =
6955 5 : nc_get_vara_schar(old_cdfid, nSrcVarId, anStart, anCount,
6956 : static_cast<signed char *>(pBuffer));
6957 5 : if (!status)
6958 5 : status = nc_put_vara_schar(
6959 : new_cdfid, nDstVarId, anStart, anCount,
6960 : static_cast<signed char *>(pBuffer));
6961 5 : break;
6962 28 : case NC_CHAR:
6963 : status =
6964 28 : nc_get_vara_text(old_cdfid, nSrcVarId, anStart, anCount,
6965 : static_cast<char *>(pBuffer));
6966 28 : if (!status)
6967 : status =
6968 28 : nc_put_vara_text(new_cdfid, nDstVarId, anStart, anCount,
6969 : static_cast<char *>(pBuffer));
6970 28 : break;
6971 4 : case NC_SHORT:
6972 : status =
6973 4 : nc_get_vara_short(old_cdfid, nSrcVarId, anStart, anCount,
6974 : static_cast<short *>(pBuffer));
6975 4 : if (!status)
6976 4 : status = nc_put_vara_short(new_cdfid, nDstVarId, anStart,
6977 : anCount,
6978 : static_cast<short *>(pBuffer));
6979 4 : break;
6980 24 : case NC_INT:
6981 24 : status = nc_get_vara_int(old_cdfid, nSrcVarId, anStart, anCount,
6982 : static_cast<int *>(pBuffer));
6983 24 : if (!status)
6984 : status =
6985 24 : nc_put_vara_int(new_cdfid, nDstVarId, anStart, anCount,
6986 : static_cast<int *>(pBuffer));
6987 24 : break;
6988 4 : case NC_FLOAT:
6989 : status =
6990 4 : nc_get_vara_float(old_cdfid, nSrcVarId, anStart, anCount,
6991 : static_cast<float *>(pBuffer));
6992 4 : if (!status)
6993 4 : status = nc_put_vara_float(new_cdfid, nDstVarId, anStart,
6994 : anCount,
6995 : static_cast<float *>(pBuffer));
6996 4 : break;
6997 43 : case NC_DOUBLE:
6998 : status =
6999 43 : nc_get_vara_double(old_cdfid, nSrcVarId, anStart, anCount,
7000 : static_cast<double *>(pBuffer));
7001 43 : if (!status)
7002 43 : status = nc_put_vara_double(new_cdfid, nDstVarId, anStart,
7003 : anCount,
7004 : static_cast<double *>(pBuffer));
7005 43 : break;
7006 1 : case NC_STRING:
7007 : status =
7008 1 : nc_get_vara_string(old_cdfid, nSrcVarId, anStart, anCount,
7009 : static_cast<char **>(pBuffer));
7010 1 : if (!status)
7011 : {
7012 1 : status = nc_put_vara_string(
7013 : new_cdfid, nDstVarId, anStart, anCount,
7014 : static_cast<const char **>(pBuffer));
7015 1 : nc_free_string(nElems, static_cast<char **>(pBuffer));
7016 : }
7017 1 : break;
7018 :
7019 2 : case NC_UBYTE:
7020 : status =
7021 2 : nc_get_vara_uchar(old_cdfid, nSrcVarId, anStart, anCount,
7022 : static_cast<unsigned char *>(pBuffer));
7023 2 : if (!status)
7024 2 : status = nc_put_vara_uchar(
7025 : new_cdfid, nDstVarId, anStart, anCount,
7026 : static_cast<unsigned char *>(pBuffer));
7027 2 : break;
7028 2 : case NC_USHORT:
7029 : status =
7030 2 : nc_get_vara_ushort(old_cdfid, nSrcVarId, anStart, anCount,
7031 : static_cast<unsigned short *>(pBuffer));
7032 2 : if (!status)
7033 2 : status = nc_put_vara_ushort(
7034 : new_cdfid, nDstVarId, anStart, anCount,
7035 : static_cast<unsigned short *>(pBuffer));
7036 2 : break;
7037 2 : case NC_UINT:
7038 : status =
7039 2 : nc_get_vara_uint(old_cdfid, nSrcVarId, anStart, anCount,
7040 : static_cast<unsigned int *>(pBuffer));
7041 2 : if (!status)
7042 : status =
7043 2 : nc_put_vara_uint(new_cdfid, nDstVarId, anStart, anCount,
7044 : static_cast<unsigned int *>(pBuffer));
7045 2 : break;
7046 2 : case NC_INT64:
7047 : status =
7048 2 : nc_get_vara_longlong(old_cdfid, nSrcVarId, anStart, anCount,
7049 : static_cast<long long *>(pBuffer));
7050 2 : if (!status)
7051 2 : status = nc_put_vara_longlong(
7052 : new_cdfid, nDstVarId, anStart, anCount,
7053 : static_cast<long long *>(pBuffer));
7054 2 : break;
7055 2 : case NC_UINT64:
7056 2 : status = nc_get_vara_ulonglong(
7057 : old_cdfid, nSrcVarId, anStart, anCount,
7058 : static_cast<unsigned long long *>(pBuffer));
7059 2 : if (!status)
7060 2 : status = nc_put_vara_ulonglong(
7061 : new_cdfid, nDstVarId, anStart, anCount,
7062 : static_cast<unsigned long long *>(pBuffer));
7063 2 : break;
7064 0 : default:
7065 0 : status = NC_EBADTYPE;
7066 : }
7067 :
7068 119 : NCDF_ERR(status);
7069 119 : if (status != NC_NOERR)
7070 : {
7071 0 : VSIFree(pBuffer);
7072 0 : return false;
7073 : }
7074 : }
7075 :
7076 121 : VSIFree(pBuffer);
7077 121 : return true;
7078 : }
7079 :
7080 : /************************************************************************/
7081 : /* NCDFIsUnlimitedDim() */
7082 : /************************************************************************/
7083 :
7084 58 : bool NCDFIsUnlimitedDim(bool bIsNC4, int cdfid, int nDimId)
7085 : {
7086 58 : if (bIsNC4)
7087 : {
7088 16 : int nUnlimitedDims = 0;
7089 16 : nc_inq_unlimdims(cdfid, &nUnlimitedDims, nullptr);
7090 16 : bool bFound = false;
7091 16 : if (nUnlimitedDims)
7092 : {
7093 : int *panUnlimitedDimIds =
7094 16 : static_cast<int *>(CPLMalloc(sizeof(int) * nUnlimitedDims));
7095 16 : nc_inq_unlimdims(cdfid, nullptr, panUnlimitedDimIds);
7096 30 : for (int i = 0; i < nUnlimitedDims; i++)
7097 : {
7098 22 : if (panUnlimitedDimIds[i] == nDimId)
7099 : {
7100 8 : bFound = true;
7101 8 : break;
7102 : }
7103 : }
7104 16 : CPLFree(panUnlimitedDimIds);
7105 : }
7106 16 : return bFound;
7107 : }
7108 : else
7109 : {
7110 42 : int nUnlimitedDimId = -1;
7111 42 : nc_inq(cdfid, nullptr, nullptr, nullptr, &nUnlimitedDimId);
7112 42 : return nDimId == nUnlimitedDimId;
7113 : }
7114 : }
7115 :
7116 : /************************************************************************/
7117 : /* CloneGrp() */
7118 : /************************************************************************/
7119 :
7120 16 : bool netCDFDataset::CloneGrp(int nOldGrpId, int nNewGrpId, bool bIsNC4,
7121 : int nLayerId, int nDimIdToGrow, size_t nNewSize)
7122 : {
7123 : // Clone dimensions
7124 16 : int nDimCount = -1;
7125 16 : int status = nc_inq_ndims(nOldGrpId, &nDimCount);
7126 16 : NCDF_ERR(status);
7127 16 : if (nDimCount < 0 || nDimCount > NC_MAX_DIMS)
7128 0 : return false;
7129 : int anDimIds[NC_MAX_DIMS];
7130 16 : int nUnlimiDimID = -1;
7131 16 : status = nc_inq_unlimdim(nOldGrpId, &nUnlimiDimID);
7132 16 : NCDF_ERR(status);
7133 16 : if (bIsNC4)
7134 : {
7135 : // In NC4, the dimension ids of a group are not necessarily in
7136 : // [0,nDimCount-1] range
7137 8 : int nDimCount2 = -1;
7138 8 : status = nc_inq_dimids(nOldGrpId, &nDimCount2, anDimIds, FALSE);
7139 8 : NCDF_ERR(status);
7140 8 : CPLAssert(nDimCount == nDimCount2);
7141 : }
7142 : else
7143 : {
7144 36 : for (int i = 0; i < nDimCount; i++)
7145 28 : anDimIds[i] = i;
7146 : }
7147 60 : for (int i = 0; i < nDimCount; i++)
7148 : {
7149 : char szDimName[NC_MAX_NAME + 1];
7150 44 : szDimName[0] = 0;
7151 44 : size_t nLen = 0;
7152 44 : const int nDimId = anDimIds[i];
7153 44 : status = nc_inq_dim(nOldGrpId, nDimId, szDimName, &nLen);
7154 44 : NCDF_ERR(status);
7155 44 : if (NCDFIsUnlimitedDim(bIsNC4, nOldGrpId, nDimId))
7156 16 : nLen = NC_UNLIMITED;
7157 28 : else if (nDimId == nDimIdToGrow && nOldGrpId == nLayerId)
7158 13 : nLen = nNewSize;
7159 44 : int nNewDimId = -1;
7160 44 : status = nc_def_dim(nNewGrpId, szDimName, nLen, &nNewDimId);
7161 44 : NCDF_ERR(status);
7162 44 : CPLAssert(nDimId == nNewDimId);
7163 44 : if (status != NC_NOERR)
7164 : {
7165 0 : return false;
7166 : }
7167 : }
7168 :
7169 : // Clone main attributes
7170 16 : if (!CloneAttributes(nOldGrpId, nNewGrpId, NC_GLOBAL, NC_GLOBAL))
7171 : {
7172 0 : return false;
7173 : }
7174 :
7175 : // Clone variable definitions
7176 16 : int nVarCount = -1;
7177 16 : status = nc_inq_nvars(nOldGrpId, &nVarCount);
7178 16 : NCDF_ERR(status);
7179 :
7180 137 : for (int i = 0; i < nVarCount; i++)
7181 : {
7182 : char szVarName[NC_MAX_NAME + 1];
7183 121 : szVarName[0] = 0;
7184 121 : status = nc_inq_varname(nOldGrpId, i, szVarName);
7185 121 : NCDF_ERR(status);
7186 121 : nc_type nc_datatype = NC_NAT;
7187 121 : status = nc_inq_vartype(nOldGrpId, i, &nc_datatype);
7188 121 : NCDF_ERR(status);
7189 121 : int nVarDimCount = -1;
7190 121 : status = nc_inq_varndims(nOldGrpId, i, &nVarDimCount);
7191 121 : NCDF_ERR(status);
7192 121 : status = nc_inq_vardimid(nOldGrpId, i, anDimIds);
7193 121 : NCDF_ERR(status);
7194 121 : int nNewVarId = -1;
7195 121 : status = nc_def_var(nNewGrpId, szVarName, nc_datatype, nVarDimCount,
7196 : anDimIds, &nNewVarId);
7197 121 : NCDF_ERR(status);
7198 121 : CPLAssert(i == nNewVarId);
7199 121 : if (status != NC_NOERR)
7200 : {
7201 0 : return false;
7202 : }
7203 :
7204 121 : if (!CloneAttributes(nOldGrpId, nNewGrpId, i, i))
7205 : {
7206 0 : return false;
7207 : }
7208 : }
7209 :
7210 16 : status = nc_enddef(nNewGrpId);
7211 16 : NCDF_ERR(status);
7212 16 : if (status != NC_NOERR)
7213 : {
7214 0 : return false;
7215 : }
7216 :
7217 : // Clone variable content
7218 137 : for (int i = 0; i < nVarCount; i++)
7219 : {
7220 121 : if (!CloneVariableContent(nOldGrpId, nNewGrpId, i, i))
7221 : {
7222 0 : return false;
7223 : }
7224 : }
7225 :
7226 16 : return true;
7227 : }
7228 :
7229 : /************************************************************************/
7230 : /* GrowDim() */
7231 : /************************************************************************/
7232 :
7233 13 : bool netCDFDataset::GrowDim(int nLayerId, int nDimIdToGrow, size_t nNewSize)
7234 : {
7235 : int nCreationMode;
7236 : // Set nCreationMode based on eFormat.
7237 13 : switch (eFormat)
7238 : {
7239 : #ifdef NETCDF_HAS_NC2
7240 0 : case NCDF_FORMAT_NC2:
7241 0 : nCreationMode = NC_CLOBBER | NC_64BIT_OFFSET;
7242 0 : break;
7243 : #endif
7244 5 : case NCDF_FORMAT_NC4:
7245 5 : nCreationMode = NC_CLOBBER | NC_NETCDF4;
7246 5 : break;
7247 0 : case NCDF_FORMAT_NC4C:
7248 0 : nCreationMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
7249 0 : break;
7250 8 : case NCDF_FORMAT_NC:
7251 : default:
7252 8 : nCreationMode = NC_CLOBBER;
7253 8 : break;
7254 : }
7255 :
7256 13 : int new_cdfid = -1;
7257 26 : CPLString osTmpFilename(osFilename + ".tmp");
7258 26 : CPLString osFilenameForNCCreate(osTmpFilename);
7259 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
7260 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
7261 : {
7262 : char *pszTemp =
7263 : CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
7264 : osFilenameForNCCreate = pszTemp;
7265 : CPLFree(pszTemp);
7266 : }
7267 : #endif
7268 13 : int status = nc_create(osFilenameForNCCreate, nCreationMode, &new_cdfid);
7269 13 : NCDF_ERR(status);
7270 13 : if (status != NC_NOERR)
7271 0 : return false;
7272 :
7273 13 : if (!CloneGrp(cdfid, new_cdfid, eFormat == NCDF_FORMAT_NC4, nLayerId,
7274 : nDimIdToGrow, nNewSize))
7275 : {
7276 0 : GDAL_nc_close(new_cdfid);
7277 0 : return false;
7278 : }
7279 :
7280 13 : int nGroupCount = 0;
7281 26 : std::vector<CPLString> oListGrpName;
7282 31 : if (eFormat == NCDF_FORMAT_NC4 &&
7283 18 : nc_inq_grps(cdfid, &nGroupCount, nullptr) == NC_NOERR &&
7284 5 : nGroupCount > 0)
7285 : {
7286 : int *panGroupIds =
7287 2 : static_cast<int *>(CPLMalloc(sizeof(int) * nGroupCount));
7288 2 : status = nc_inq_grps(cdfid, nullptr, panGroupIds);
7289 2 : NCDF_ERR(status);
7290 5 : for (int i = 0; i < nGroupCount; i++)
7291 : {
7292 : char szGroupName[NC_MAX_NAME + 1];
7293 3 : szGroupName[0] = 0;
7294 3 : NCDF_ERR(nc_inq_grpname(panGroupIds[i], szGroupName));
7295 3 : int nNewGrpId = -1;
7296 3 : status = nc_def_grp(new_cdfid, szGroupName, &nNewGrpId);
7297 3 : NCDF_ERR(status);
7298 3 : if (status != NC_NOERR)
7299 : {
7300 0 : CPLFree(panGroupIds);
7301 0 : GDAL_nc_close(new_cdfid);
7302 0 : return false;
7303 : }
7304 3 : if (!CloneGrp(panGroupIds[i], nNewGrpId, /*bIsNC4=*/true, nLayerId,
7305 : nDimIdToGrow, nNewSize))
7306 : {
7307 0 : CPLFree(panGroupIds);
7308 0 : GDAL_nc_close(new_cdfid);
7309 0 : return false;
7310 : }
7311 : }
7312 2 : CPLFree(panGroupIds);
7313 :
7314 5 : for (int i = 0; i < this->GetLayerCount(); i++)
7315 : {
7316 3 : auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
7317 3 : if (poLayer)
7318 : {
7319 : char szGroupName[NC_MAX_NAME + 1];
7320 3 : szGroupName[0] = 0;
7321 3 : status = nc_inq_grpname(poLayer->GetCDFID(), szGroupName);
7322 3 : NCDF_ERR(status);
7323 3 : oListGrpName.push_back(szGroupName);
7324 : }
7325 : }
7326 : }
7327 :
7328 13 : GDAL_nc_close(cdfid);
7329 13 : cdfid = -1;
7330 13 : GDAL_nc_close(new_cdfid);
7331 :
7332 26 : CPLString osOriFilename(osFilename + ".ori");
7333 26 : if (VSIRename(osFilename, osOriFilename) != 0 ||
7334 13 : VSIRename(osTmpFilename, osFilename) != 0)
7335 : {
7336 0 : CPLError(CE_Failure, CPLE_FileIO, "Renaming of files failed");
7337 0 : return false;
7338 : }
7339 13 : VSIUnlink(osOriFilename);
7340 :
7341 26 : CPLString osFilenameForNCOpen(osFilename);
7342 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
7343 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
7344 : {
7345 : char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
7346 : osFilenameForNCOpen = pszTemp;
7347 : CPLFree(pszTemp);
7348 : }
7349 : #endif
7350 13 : status = GDAL_nc_open(osFilenameForNCOpen, NC_WRITE, &cdfid);
7351 13 : NCDF_ERR(status);
7352 13 : if (status != NC_NOERR)
7353 0 : return false;
7354 13 : bDefineMode = false;
7355 :
7356 13 : if (!oListGrpName.empty())
7357 : {
7358 5 : for (int i = 0; i < this->GetLayerCount(); i++)
7359 : {
7360 3 : auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
7361 3 : if (poLayer)
7362 : {
7363 3 : int nNewLayerCDFID = -1;
7364 3 : status = nc_inq_ncid(cdfid, oListGrpName[i].c_str(),
7365 : &nNewLayerCDFID);
7366 3 : NCDF_ERR(status);
7367 3 : poLayer->SetCDFID(nNewLayerCDFID);
7368 : }
7369 : }
7370 : }
7371 : else
7372 : {
7373 22 : for (int i = 0; i < this->GetLayerCount(); i++)
7374 : {
7375 11 : auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
7376 11 : if (poLayer)
7377 11 : poLayer->SetCDFID(cdfid);
7378 : }
7379 : }
7380 :
7381 13 : return true;
7382 : }
7383 :
7384 : #ifdef ENABLE_NCDUMP
7385 :
7386 : /************************************************************************/
7387 : /* netCDFDatasetCreateTempFile() */
7388 : /************************************************************************/
7389 :
7390 : /* Create a netCDF file from a text dump (format of ncdump) */
7391 : /* Mostly to easy fuzzing of the driver, while still generating valid */
7392 : /* netCDF files. */
7393 : /* Note: not all data types are supported ! */
7394 4 : bool netCDFDatasetCreateTempFile(NetCDFFormatEnum eFormat,
7395 : const char *pszTmpFilename, VSILFILE *fpSrc)
7396 : {
7397 4 : CPL_IGNORE_RET_VAL(eFormat);
7398 4 : int nCreateMode = NC_CLOBBER;
7399 4 : if (eFormat == NCDF_FORMAT_NC4)
7400 1 : nCreateMode |= NC_NETCDF4;
7401 3 : else if (eFormat == NCDF_FORMAT_NC4C)
7402 0 : nCreateMode |= NC_NETCDF4 | NC_CLASSIC_MODEL;
7403 4 : int nCdfId = -1;
7404 4 : int status = nc_create(pszTmpFilename, nCreateMode, &nCdfId);
7405 4 : if (status != NC_NOERR)
7406 : {
7407 0 : return false;
7408 : }
7409 4 : VSIFSeekL(fpSrc, 0, SEEK_SET);
7410 : const char *pszLine;
7411 4 : constexpr int SECTION_NONE = 0;
7412 4 : constexpr int SECTION_DIMENSIONS = 1;
7413 4 : constexpr int SECTION_VARIABLES = 2;
7414 4 : constexpr int SECTION_DATA = 3;
7415 4 : int nActiveSection = SECTION_NONE;
7416 8 : std::map<CPLString, int> oMapDimToId;
7417 8 : std::map<int, int> oMapDimIdToDimLen;
7418 8 : std::map<CPLString, int> oMapVarToId;
7419 8 : std::map<int, std::vector<int>> oMapVarIdToVectorOfDimId;
7420 8 : std::map<int, int> oMapVarIdToType;
7421 4 : std::set<CPLString> oSetAttrDefined;
7422 4 : oMapVarToId[""] = -1;
7423 4 : size_t nTotalVarSize = 0;
7424 208 : while ((pszLine = CPLReadLineL(fpSrc)) != nullptr)
7425 : {
7426 204 : if (STARTS_WITH(pszLine, "dimensions:") &&
7427 : nActiveSection == SECTION_NONE)
7428 : {
7429 4 : nActiveSection = SECTION_DIMENSIONS;
7430 : }
7431 200 : else if (STARTS_WITH(pszLine, "variables:") &&
7432 : nActiveSection == SECTION_DIMENSIONS)
7433 : {
7434 4 : nActiveSection = SECTION_VARIABLES;
7435 : }
7436 196 : else if (STARTS_WITH(pszLine, "data:") &&
7437 : nActiveSection == SECTION_VARIABLES)
7438 : {
7439 4 : nActiveSection = SECTION_DATA;
7440 4 : status = nc_enddef(nCdfId);
7441 4 : if (status != NC_NOERR)
7442 : {
7443 0 : CPLDebug("netCDF", "nc_enddef() failed: %s",
7444 : nc_strerror(status));
7445 : }
7446 : }
7447 192 : else if (nActiveSection == SECTION_DIMENSIONS)
7448 : {
7449 : const CPLStringList aosTokens(
7450 9 : CSLTokenizeString2(pszLine, " \t=;", 0));
7451 9 : if (aosTokens.size() == 2)
7452 : {
7453 9 : const char *pszDimName = aosTokens[0];
7454 9 : bool bValidName = true;
7455 9 : if (STARTS_WITH(pszDimName, "_nc4_non_coord_"))
7456 : {
7457 : // This is an internal netcdf prefix. Using it may
7458 : // cause memory leaks.
7459 0 : bValidName = false;
7460 : }
7461 9 : if (!bValidName)
7462 : {
7463 0 : CPLDebug("netCDF",
7464 : "nc_def_dim(%s) failed: invalid name found",
7465 : pszDimName);
7466 0 : continue;
7467 : }
7468 :
7469 : const bool bIsASCII =
7470 9 : CPLIsASCII(pszDimName, static_cast<size_t>(-1));
7471 9 : if (!bIsASCII)
7472 : {
7473 : // Workaround https://github.com/Unidata/netcdf-c/pull/450
7474 0 : CPLDebug("netCDF",
7475 : "nc_def_dim(%s) failed: rejected because "
7476 : "of non-ASCII characters",
7477 : pszDimName);
7478 0 : continue;
7479 : }
7480 9 : int nDimSize = EQUAL(aosTokens[1], "UNLIMITED")
7481 : ? NC_UNLIMITED
7482 9 : : atoi(aosTokens[1]);
7483 9 : if (nDimSize >= 1000)
7484 1 : nDimSize = 1000; // to avoid very long processing
7485 9 : if (nDimSize >= 0)
7486 : {
7487 9 : int nDimId = -1;
7488 9 : status = nc_def_dim(nCdfId, pszDimName, nDimSize, &nDimId);
7489 9 : if (status != NC_NOERR)
7490 : {
7491 0 : CPLDebug("netCDF", "nc_def_dim(%s, %d) failed: %s",
7492 : pszDimName, nDimSize, nc_strerror(status));
7493 : }
7494 : else
7495 : {
7496 : #ifdef DEBUG_VERBOSE
7497 : CPLDebug("netCDF", "nc_def_dim(%s, %d) (%s) succeeded",
7498 : pszDimName, nDimSize, pszLine);
7499 : #endif
7500 9 : oMapDimToId[pszDimName] = nDimId;
7501 9 : oMapDimIdToDimLen[nDimId] = nDimSize;
7502 : }
7503 : }
7504 : }
7505 : }
7506 183 : else if (nActiveSection == SECTION_VARIABLES)
7507 : {
7508 390 : while (*pszLine == ' ' || *pszLine == '\t')
7509 249 : pszLine++;
7510 141 : const char *pszColumn = strchr(pszLine, ':');
7511 141 : const char *pszEqual = strchr(pszLine, '=');
7512 141 : if (pszColumn == nullptr)
7513 : {
7514 : const CPLStringList aosTokens(
7515 21 : CSLTokenizeString2(pszLine, " \t=(),;", 0));
7516 21 : if (aosTokens.size() >= 2)
7517 : {
7518 17 : const char *pszVarName = aosTokens[1];
7519 17 : bool bValidName = true;
7520 17 : if (STARTS_WITH(pszVarName, "_nc4_non_coord_"))
7521 : {
7522 : // This is an internal netcdf prefix. Using it may
7523 : // cause memory leaks.
7524 0 : bValidName = false;
7525 : }
7526 138 : for (int i = 0; pszVarName[i]; i++)
7527 : {
7528 121 : if (!((pszVarName[i] >= 'a' && pszVarName[i] <= 'z') ||
7529 28 : (pszVarName[i] >= 'A' && pszVarName[i] <= 'Z') ||
7530 9 : (pszVarName[i] >= '0' && pszVarName[i] <= '9') ||
7531 6 : pszVarName[i] == '_'))
7532 : {
7533 0 : bValidName = false;
7534 : }
7535 : }
7536 17 : if (!bValidName)
7537 : {
7538 0 : CPLDebug(
7539 : "netCDF",
7540 : "nc_def_var(%s) failed: illegal character found",
7541 : pszVarName);
7542 0 : continue;
7543 : }
7544 17 : if (oMapVarToId.find(pszVarName) != oMapVarToId.end())
7545 : {
7546 0 : CPLDebug("netCDF",
7547 : "nc_def_var(%s) failed: already defined",
7548 : pszVarName);
7549 0 : continue;
7550 : }
7551 17 : const char *pszVarType = aosTokens[0];
7552 17 : int nc_datatype = NC_BYTE;
7553 17 : size_t nDataTypeSize = 1;
7554 17 : if (EQUAL(pszVarType, "char"))
7555 : {
7556 6 : nc_datatype = NC_CHAR;
7557 6 : nDataTypeSize = 1;
7558 : }
7559 11 : else if (EQUAL(pszVarType, "byte"))
7560 : {
7561 3 : nc_datatype = NC_BYTE;
7562 3 : nDataTypeSize = 1;
7563 : }
7564 8 : else if (EQUAL(pszVarType, "short"))
7565 : {
7566 0 : nc_datatype = NC_SHORT;
7567 0 : nDataTypeSize = 2;
7568 : }
7569 8 : else if (EQUAL(pszVarType, "int"))
7570 : {
7571 0 : nc_datatype = NC_INT;
7572 0 : nDataTypeSize = 4;
7573 : }
7574 8 : else if (EQUAL(pszVarType, "float"))
7575 : {
7576 0 : nc_datatype = NC_FLOAT;
7577 0 : nDataTypeSize = 4;
7578 : }
7579 8 : else if (EQUAL(pszVarType, "double"))
7580 : {
7581 8 : nc_datatype = NC_DOUBLE;
7582 8 : nDataTypeSize = 8;
7583 : }
7584 0 : else if (EQUAL(pszVarType, "ubyte"))
7585 : {
7586 0 : nc_datatype = NC_UBYTE;
7587 0 : nDataTypeSize = 1;
7588 : }
7589 0 : else if (EQUAL(pszVarType, "ushort"))
7590 : {
7591 0 : nc_datatype = NC_USHORT;
7592 0 : nDataTypeSize = 2;
7593 : }
7594 0 : else if (EQUAL(pszVarType, "uint"))
7595 : {
7596 0 : nc_datatype = NC_UINT;
7597 0 : nDataTypeSize = 4;
7598 : }
7599 0 : else if (EQUAL(pszVarType, "int64"))
7600 : {
7601 0 : nc_datatype = NC_INT64;
7602 0 : nDataTypeSize = 8;
7603 : }
7604 0 : else if (EQUAL(pszVarType, "uint64"))
7605 : {
7606 0 : nc_datatype = NC_UINT64;
7607 0 : nDataTypeSize = 8;
7608 : }
7609 :
7610 17 : int nDims = aosTokens.size() - 2;
7611 17 : if (nDims >= 32)
7612 : {
7613 : // The number of dimensions in a netCDFv4 file is
7614 : // limited by #define H5S_MAX_RANK 32
7615 : // but libnetcdf doesn't check that...
7616 0 : CPLDebug("netCDF",
7617 : "nc_def_var(%s) failed: too many dimensions",
7618 : pszVarName);
7619 0 : continue;
7620 : }
7621 17 : std::vector<int> aoDimIds;
7622 17 : bool bFailed = false;
7623 17 : size_t nSize = 1;
7624 35 : for (int i = 0; i < nDims; i++)
7625 : {
7626 18 : const char *pszDimName = aosTokens[2 + i];
7627 18 : if (oMapDimToId.find(pszDimName) == oMapDimToId.end())
7628 : {
7629 0 : bFailed = true;
7630 0 : break;
7631 : }
7632 18 : const int nDimId = oMapDimToId[pszDimName];
7633 18 : aoDimIds.push_back(nDimId);
7634 :
7635 18 : const size_t nDimSize = oMapDimIdToDimLen[nDimId];
7636 18 : if (nDimSize != 0)
7637 : {
7638 18 : if (nSize >
7639 18 : std::numeric_limits<size_t>::max() / nDimSize)
7640 : {
7641 0 : bFailed = true;
7642 0 : break;
7643 : }
7644 : else
7645 : {
7646 18 : nSize *= nDimSize;
7647 : }
7648 : }
7649 : }
7650 17 : if (bFailed)
7651 : {
7652 0 : CPLDebug("netCDF",
7653 : "nc_def_var(%s) failed: unknown dimension(s)",
7654 : pszVarName);
7655 0 : continue;
7656 : }
7657 17 : if (nSize > 100U * 1024 * 1024 / nDataTypeSize)
7658 : {
7659 0 : CPLDebug("netCDF",
7660 : "nc_def_var(%s) failed: too large data",
7661 : pszVarName);
7662 0 : continue;
7663 : }
7664 17 : if (nTotalVarSize >
7665 34 : std::numeric_limits<size_t>::max() - nSize ||
7666 17 : nTotalVarSize + nSize > 100 * 1024 * 1024)
7667 : {
7668 0 : CPLDebug("netCDF",
7669 : "nc_def_var(%s) failed: too large data",
7670 : pszVarName);
7671 0 : continue;
7672 : }
7673 17 : nTotalVarSize += nSize;
7674 :
7675 17 : int nVarId = -1;
7676 : status =
7677 30 : nc_def_var(nCdfId, pszVarName, nc_datatype, nDims,
7678 13 : (nDims) ? &aoDimIds[0] : nullptr, &nVarId);
7679 17 : if (status != NC_NOERR)
7680 : {
7681 0 : CPLDebug("netCDF", "nc_def_var(%s) failed: %s",
7682 : pszVarName, nc_strerror(status));
7683 : }
7684 : else
7685 : {
7686 : #ifdef DEBUG_VERBOSE
7687 : CPLDebug("netCDF", "nc_def_var(%s) (%s) succeeded",
7688 : pszVarName, pszLine);
7689 : #endif
7690 17 : oMapVarToId[pszVarName] = nVarId;
7691 17 : oMapVarIdToType[nVarId] = nc_datatype;
7692 17 : oMapVarIdToVectorOfDimId[nVarId] = std::move(aoDimIds);
7693 : }
7694 : }
7695 : }
7696 120 : else if (pszEqual != nullptr && pszEqual - pszColumn > 0)
7697 : {
7698 116 : CPLString osVarName(pszLine, pszColumn - pszLine);
7699 116 : CPLString osAttrName(pszColumn + 1, pszEqual - pszColumn - 1);
7700 116 : osAttrName.Trim();
7701 116 : if (oMapVarToId.find(osVarName) == oMapVarToId.end())
7702 : {
7703 0 : CPLDebug("netCDF",
7704 : "nc_put_att(%s:%s) failed: "
7705 : "no corresponding variable",
7706 : osVarName.c_str(), osAttrName.c_str());
7707 0 : continue;
7708 : }
7709 116 : bool bValidName = true;
7710 1743 : for (size_t i = 0; i < osAttrName.size(); i++)
7711 : {
7712 1865 : if (!((osAttrName[i] >= 'a' && osAttrName[i] <= 'z') ||
7713 238 : (osAttrName[i] >= 'A' && osAttrName[i] <= 'Z') ||
7714 158 : (osAttrName[i] >= '0' && osAttrName[i] <= '9') ||
7715 158 : osAttrName[i] == '_'))
7716 : {
7717 0 : bValidName = false;
7718 : }
7719 : }
7720 116 : if (!bValidName)
7721 : {
7722 0 : CPLDebug(
7723 : "netCDF",
7724 : "nc_put_att(%s:%s) failed: illegal character found",
7725 : osVarName.c_str(), osAttrName.c_str());
7726 0 : continue;
7727 : }
7728 116 : if (oSetAttrDefined.find(osVarName + ":" + osAttrName) !=
7729 232 : oSetAttrDefined.end())
7730 : {
7731 0 : CPLDebug("netCDF",
7732 : "nc_put_att(%s:%s) failed: already defined",
7733 : osVarName.c_str(), osAttrName.c_str());
7734 0 : continue;
7735 : }
7736 :
7737 116 : const int nVarId = oMapVarToId[osVarName];
7738 116 : const char *pszValue = pszEqual + 1;
7739 232 : while (*pszValue == ' ')
7740 116 : pszValue++;
7741 :
7742 116 : status = NC_EBADTYPE;
7743 116 : if (*pszValue == '"')
7744 : {
7745 : // For _FillValue, the attribute type should match
7746 : // the variable type. Leaks memory with NC4 otherwise
7747 74 : if (osAttrName == "_FillValue")
7748 : {
7749 0 : CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
7750 : osVarName.c_str(), osAttrName.c_str(),
7751 : nc_strerror(status));
7752 0 : continue;
7753 : }
7754 :
7755 : // Unquote and unescape string value
7756 74 : CPLString osVal(pszValue + 1);
7757 222 : while (!osVal.empty())
7758 : {
7759 222 : if (osVal.back() == ';' || osVal.back() == ' ')
7760 : {
7761 148 : osVal.pop_back();
7762 : }
7763 74 : else if (osVal.back() == '"')
7764 : {
7765 74 : osVal.pop_back();
7766 74 : break;
7767 : }
7768 : else
7769 : {
7770 0 : break;
7771 : }
7772 : }
7773 74 : osVal.replaceAll("\\\"", '"');
7774 74 : status = nc_put_att_text(nCdfId, nVarId, osAttrName,
7775 : osVal.size(), osVal.c_str());
7776 : }
7777 : else
7778 : {
7779 84 : CPLString osVal(pszValue);
7780 126 : while (!osVal.empty())
7781 : {
7782 126 : if (osVal.back() == ';' || osVal.back() == ' ')
7783 : {
7784 84 : osVal.pop_back();
7785 : }
7786 : else
7787 : {
7788 42 : break;
7789 : }
7790 : }
7791 42 : int nc_datatype = -1;
7792 42 : if (!osVal.empty() && osVal.back() == 'b')
7793 : {
7794 3 : nc_datatype = NC_BYTE;
7795 3 : osVal.pop_back();
7796 : }
7797 39 : else if (!osVal.empty() && osVal.back() == 's')
7798 : {
7799 3 : nc_datatype = NC_SHORT;
7800 3 : osVal.pop_back();
7801 : }
7802 42 : if (CPLGetValueType(osVal) == CPL_VALUE_INTEGER)
7803 : {
7804 7 : if (nc_datatype < 0)
7805 4 : nc_datatype = NC_INT;
7806 : }
7807 35 : else if (CPLGetValueType(osVal) == CPL_VALUE_REAL)
7808 : {
7809 32 : nc_datatype = NC_DOUBLE;
7810 : }
7811 : else
7812 : {
7813 3 : nc_datatype = -1;
7814 : }
7815 :
7816 : // For _FillValue, check that the attribute type matches
7817 : // the variable type. Leaks memory with NC4 otherwise
7818 42 : if (osAttrName == "_FillValue")
7819 : {
7820 6 : if (nVarId < 0 ||
7821 3 : nc_datatype != oMapVarIdToType[nVarId])
7822 : {
7823 0 : nc_datatype = -1;
7824 : }
7825 : }
7826 :
7827 42 : if (nc_datatype == NC_BYTE)
7828 : {
7829 : signed char chVal =
7830 3 : static_cast<signed char>(atoi(osVal));
7831 3 : status = nc_put_att_schar(nCdfId, nVarId, osAttrName,
7832 : NC_BYTE, 1, &chVal);
7833 : }
7834 39 : else if (nc_datatype == NC_SHORT)
7835 : {
7836 0 : short nVal = static_cast<short>(atoi(osVal));
7837 0 : status = nc_put_att_short(nCdfId, nVarId, osAttrName,
7838 : NC_SHORT, 1, &nVal);
7839 : }
7840 39 : else if (nc_datatype == NC_INT)
7841 : {
7842 4 : int nVal = static_cast<int>(atoi(osVal));
7843 4 : status = nc_put_att_int(nCdfId, nVarId, osAttrName,
7844 : NC_INT, 1, &nVal);
7845 : }
7846 35 : else if (nc_datatype == NC_DOUBLE)
7847 : {
7848 32 : double dfVal = CPLAtof(osVal);
7849 32 : status = nc_put_att_double(nCdfId, nVarId, osAttrName,
7850 : NC_DOUBLE, 1, &dfVal);
7851 : }
7852 : }
7853 116 : if (status != NC_NOERR)
7854 : {
7855 3 : CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
7856 : osVarName.c_str(), osAttrName.c_str(),
7857 : nc_strerror(status));
7858 : }
7859 : else
7860 : {
7861 113 : oSetAttrDefined.insert(osVarName + ":" + osAttrName);
7862 : #ifdef DEBUG_VERBOSE
7863 : CPLDebug("netCDF", "nc_put_att_(%s:%s) (%s) succeeded",
7864 : osVarName.c_str(), osAttrName.c_str(), pszLine);
7865 : #endif
7866 : }
7867 : }
7868 : }
7869 42 : else if (nActiveSection == SECTION_DATA)
7870 : {
7871 55 : while (*pszLine == ' ' || *pszLine == '\t')
7872 17 : pszLine++;
7873 38 : const char *pszEqual = strchr(pszLine, '=');
7874 38 : if (pszEqual)
7875 : {
7876 17 : CPLString osVarName(pszLine, pszEqual - pszLine);
7877 17 : osVarName.Trim();
7878 17 : if (oMapVarToId.find(osVarName) == oMapVarToId.end())
7879 0 : continue;
7880 17 : const int nVarId = oMapVarToId[osVarName];
7881 17 : CPLString osAccVal(pszEqual + 1);
7882 17 : osAccVal.Trim();
7883 153 : while (osAccVal.empty() || osAccVal.back() != ';')
7884 : {
7885 136 : pszLine = CPLReadLineL(fpSrc);
7886 136 : if (pszLine == nullptr)
7887 0 : break;
7888 272 : CPLString osVal(pszLine);
7889 136 : osVal.Trim();
7890 136 : osAccVal += osVal;
7891 : }
7892 17 : if (pszLine == nullptr)
7893 0 : break;
7894 17 : osAccVal.pop_back();
7895 :
7896 : const std::vector<int> aoDimIds =
7897 34 : oMapVarIdToVectorOfDimId[nVarId];
7898 17 : size_t nSize = 1;
7899 34 : std::vector<size_t> aoStart, aoEdge;
7900 17 : aoStart.resize(aoDimIds.size());
7901 17 : aoEdge.resize(aoDimIds.size());
7902 35 : for (size_t i = 0; i < aoDimIds.size(); ++i)
7903 : {
7904 18 : const size_t nDimSize = oMapDimIdToDimLen[aoDimIds[i]];
7905 36 : if (nDimSize != 0 &&
7906 18 : nSize > std::numeric_limits<size_t>::max() / nDimSize)
7907 : {
7908 0 : nSize = 0;
7909 : }
7910 : else
7911 : {
7912 18 : nSize *= nDimSize;
7913 : }
7914 18 : aoStart[i] = 0;
7915 18 : aoEdge[i] = nDimSize;
7916 : }
7917 :
7918 17 : status = NC_EBADTYPE;
7919 17 : if (nSize == 0)
7920 : {
7921 : // Might happen with an unlimited dimension
7922 : }
7923 17 : else if (oMapVarIdToType[nVarId] == NC_DOUBLE)
7924 : {
7925 8 : if (!aoStart.empty())
7926 : {
7927 : const CPLStringList aosTokens(
7928 16 : CSLTokenizeString2(osAccVal, " ,;", 0));
7929 8 : size_t nTokens = aosTokens.size();
7930 8 : if (nTokens >= nSize)
7931 : {
7932 : double *padfVals = static_cast<double *>(
7933 8 : VSI_CALLOC_VERBOSE(nSize, sizeof(double)));
7934 8 : if (padfVals)
7935 : {
7936 132 : for (size_t i = 0; i < nSize; i++)
7937 : {
7938 124 : padfVals[i] = CPLAtof(aosTokens[i]);
7939 : }
7940 8 : status = nc_put_vara_double(
7941 8 : nCdfId, nVarId, &aoStart[0], &aoEdge[0],
7942 : padfVals);
7943 8 : VSIFree(padfVals);
7944 : }
7945 : }
7946 : }
7947 : }
7948 9 : else if (oMapVarIdToType[nVarId] == NC_BYTE)
7949 : {
7950 3 : if (!aoStart.empty())
7951 : {
7952 : const CPLStringList aosTokens(
7953 6 : CSLTokenizeString2(osAccVal, " ,;", 0));
7954 3 : size_t nTokens = aosTokens.size();
7955 3 : if (nTokens >= nSize)
7956 : {
7957 : signed char *panVals = static_cast<signed char *>(
7958 3 : VSI_CALLOC_VERBOSE(nSize, sizeof(signed char)));
7959 3 : if (panVals)
7960 : {
7961 1203 : for (size_t i = 0; i < nSize; i++)
7962 : {
7963 1200 : panVals[i] = static_cast<signed char>(
7964 1200 : atoi(aosTokens[i]));
7965 : }
7966 3 : status = nc_put_vara_schar(nCdfId, nVarId,
7967 3 : &aoStart[0],
7968 3 : &aoEdge[0], panVals);
7969 3 : VSIFree(panVals);
7970 : }
7971 : }
7972 : }
7973 : }
7974 6 : else if (oMapVarIdToType[nVarId] == NC_CHAR)
7975 : {
7976 6 : if (aoStart.size() == 2)
7977 : {
7978 4 : std::vector<CPLString> aoStrings;
7979 2 : bool bInString = false;
7980 4 : CPLString osCurString;
7981 935 : for (size_t i = 0; i < osAccVal.size();)
7982 : {
7983 933 : if (!bInString)
7984 : {
7985 8 : if (osAccVal[i] == '"')
7986 : {
7987 4 : bInString = true;
7988 4 : osCurString.clear();
7989 : }
7990 8 : i++;
7991 : }
7992 926 : else if (osAccVal[i] == '\\' &&
7993 926 : i + 1 < osAccVal.size() &&
7994 1 : osAccVal[i + 1] == '"')
7995 : {
7996 1 : osCurString += '"';
7997 1 : i += 2;
7998 : }
7999 924 : else if (osAccVal[i] == '"')
8000 : {
8001 4 : aoStrings.push_back(osCurString);
8002 4 : osCurString.clear();
8003 4 : bInString = false;
8004 4 : i++;
8005 : }
8006 : else
8007 : {
8008 920 : osCurString += osAccVal[i];
8009 920 : i++;
8010 : }
8011 : }
8012 2 : const size_t nRecords = oMapDimIdToDimLen[aoDimIds[0]];
8013 2 : const size_t nWidth = oMapDimIdToDimLen[aoDimIds[1]];
8014 2 : size_t nIters = aoStrings.size();
8015 2 : if (nIters > nRecords)
8016 0 : nIters = nRecords;
8017 6 : for (size_t i = 0; i < nIters; i++)
8018 : {
8019 : size_t anIndex[2];
8020 4 : anIndex[0] = i;
8021 4 : anIndex[1] = 0;
8022 : size_t anCount[2];
8023 4 : anCount[0] = 1;
8024 4 : anCount[1] = aoStrings[i].size();
8025 4 : if (anCount[1] > nWidth)
8026 0 : anCount[1] = nWidth;
8027 : status =
8028 4 : nc_put_vara_text(nCdfId, nVarId, anIndex,
8029 4 : anCount, aoStrings[i].c_str());
8030 4 : if (status != NC_NOERR)
8031 0 : break;
8032 : }
8033 : }
8034 : }
8035 17 : if (status != NC_NOERR)
8036 : {
8037 4 : CPLDebug("netCDF", "nc_put_var_(%s) failed: %s",
8038 : osVarName.c_str(), nc_strerror(status));
8039 : }
8040 : }
8041 : }
8042 : }
8043 :
8044 4 : GDAL_nc_close(nCdfId);
8045 4 : return true;
8046 : }
8047 :
8048 : #endif // ENABLE_NCDUMP
8049 :
8050 : /************************************************************************/
8051 : /* Open() */
8052 : /************************************************************************/
8053 :
8054 766 : GDALDataset *netCDFDataset::Open(GDALOpenInfo *poOpenInfo)
8055 :
8056 : {
8057 : #ifdef NCDF_DEBUG
8058 : CPLDebug("GDAL_netCDF", "\n=====\nOpen(), filename=[%s]",
8059 : poOpenInfo->pszFilename);
8060 : #endif
8061 :
8062 : // Does this appear to be a netcdf file?
8063 766 : NetCDFFormatEnum eTmpFormat = NCDF_FORMAT_NONE;
8064 766 : if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
8065 : {
8066 706 : eTmpFormat = netCDFIdentifyFormat(poOpenInfo, /* bCheckExt = */ true);
8067 : #ifdef NCDF_DEBUG
8068 : CPLDebug("GDAL_netCDF", "identified format %d", eTmpFormat);
8069 : #endif
8070 : // Note: not calling Identify() directly, because we want the file type.
8071 : // Only support NCDF_FORMAT* formats.
8072 706 : if (NCDF_FORMAT_NC == eTmpFormat || NCDF_FORMAT_NC2 == eTmpFormat ||
8073 2 : NCDF_FORMAT_NC4 == eTmpFormat || NCDF_FORMAT_NC4C == eTmpFormat)
8074 : {
8075 : // ok
8076 : }
8077 2 : else if (eTmpFormat == NCDF_FORMAT_HDF4 &&
8078 0 : poOpenInfo->IsSingleAllowedDriver("netCDF"))
8079 : {
8080 : // ok
8081 : }
8082 : else
8083 : {
8084 2 : return nullptr;
8085 : }
8086 : }
8087 : else
8088 : {
8089 : #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
8090 : // We don't necessarily want to catch bugs in libnetcdf ...
8091 : if (CPLGetConfigOption("DISABLE_OPEN_REAL_NETCDF_FILES", nullptr))
8092 : {
8093 : return nullptr;
8094 : }
8095 : #endif
8096 : }
8097 :
8098 764 : if (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER)
8099 : {
8100 262 : return OpenMultiDim(poOpenInfo);
8101 : }
8102 :
8103 1004 : CPLMutexHolderD(&hNCMutex);
8104 :
8105 502 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
8106 : // GDALDataset own mutex.
8107 502 : netCDFDataset *poDS = new netCDFDataset();
8108 502 : poDS->papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
8109 502 : CPLAcquireMutex(hNCMutex, 1000.0);
8110 :
8111 502 : poDS->SetDescription(poOpenInfo->pszFilename);
8112 :
8113 : // Check if filename start with NETCDF: tag.
8114 502 : bool bTreatAsSubdataset = false;
8115 1004 : CPLString osSubdatasetName;
8116 :
8117 : #ifdef ENABLE_NCDUMP
8118 502 : const char *pszHeader =
8119 : reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
8120 502 : if (poOpenInfo->fpL != nullptr && STARTS_WITH(pszHeader, "netcdf ") &&
8121 3 : strstr(pszHeader, "dimensions:") && strstr(pszHeader, "variables:"))
8122 : {
8123 : // By default create a temporary file that will be destroyed,
8124 : // unless NETCDF_TMP_FILE is defined. Can be useful to see which
8125 : // netCDF file has been generated from a potential fuzzed input.
8126 3 : poDS->osFilename = CPLGetConfigOption("NETCDF_TMP_FILE", "");
8127 3 : if (poDS->osFilename.empty())
8128 : {
8129 3 : poDS->bFileToDestroyAtClosing = true;
8130 3 : poDS->osFilename = CPLGenerateTempFilenameSafe("netcdf_tmp");
8131 : }
8132 3 : if (!netCDFDatasetCreateTempFile(eTmpFormat, poDS->osFilename,
8133 : poOpenInfo->fpL))
8134 : {
8135 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8136 : // deadlock with GDALDataset own mutex.
8137 0 : delete poDS;
8138 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8139 0 : return nullptr;
8140 : }
8141 3 : bTreatAsSubdataset = false;
8142 3 : poDS->eFormat = eTmpFormat;
8143 : }
8144 : else
8145 : #endif
8146 :
8147 499 : if (STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
8148 : {
8149 : GDALSubdatasetInfoH hInfo =
8150 60 : GDALGetSubdatasetInfo(poOpenInfo->pszFilename);
8151 60 : if (hInfo)
8152 : {
8153 60 : char *pszPath = GDALSubdatasetInfoGetPathComponent(hInfo);
8154 60 : poDS->osFilename = pszPath;
8155 60 : CPLFree(pszPath);
8156 :
8157 : char *pszSubdataset =
8158 60 : GDALSubdatasetInfoGetSubdatasetComponent(hInfo);
8159 60 : if (pszSubdataset && pszSubdataset[0] != '\0')
8160 : {
8161 60 : osSubdatasetName = pszSubdataset;
8162 60 : bTreatAsSubdataset = true;
8163 60 : CPLFree(pszSubdataset);
8164 : }
8165 : else
8166 : {
8167 0 : osSubdatasetName = "";
8168 0 : bTreatAsSubdataset = false;
8169 : }
8170 :
8171 60 : GDALDestroySubdatasetInfo(hInfo);
8172 : }
8173 :
8174 120 : if (!STARTS_WITH(poDS->osFilename, "http://") &&
8175 60 : !STARTS_WITH(poDS->osFilename, "https://"))
8176 : {
8177 : // Identify Format from real file, with bCheckExt=FALSE.
8178 : auto poOpenInfo2 = std::make_unique<GDALOpenInfo>(
8179 60 : poDS->osFilename.c_str(), GA_ReadOnly);
8180 60 : poDS->eFormat = netCDFIdentifyFormat(poOpenInfo2.get(),
8181 : /* bCheckExt = */ false);
8182 60 : if (NCDF_FORMAT_NONE == poDS->eFormat ||
8183 60 : NCDF_FORMAT_UNKNOWN == poDS->eFormat)
8184 : {
8185 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8186 : // deadlock with GDALDataset own mutex.
8187 0 : delete poDS;
8188 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8189 0 : return nullptr;
8190 : }
8191 : }
8192 : }
8193 : else
8194 : {
8195 439 : poDS->osFilename = poOpenInfo->pszFilename;
8196 439 : bTreatAsSubdataset = false;
8197 439 : poDS->eFormat = eTmpFormat;
8198 : }
8199 :
8200 : // Try opening the dataset.
8201 : #if defined(NCDF_DEBUG) && defined(ENABLE_UFFD)
8202 : CPLDebug("GDAL_netCDF", "calling nc_open_mem(%s)",
8203 : poDS->osFilename.c_str());
8204 : #elif defined(NCDF_DEBUG) && !defined(ENABLE_UFFD)
8205 : CPLDebug("GDAL_netCDF", "calling nc_open(%s)", poDS->osFilename.c_str());
8206 : #endif
8207 502 : int cdfid = -1;
8208 502 : const int nMode = ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE) != 0)
8209 : ? NC_WRITE
8210 : : NC_NOWRITE;
8211 1004 : CPLString osFilenameForNCOpen(poDS->osFilename);
8212 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
8213 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
8214 : {
8215 : char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
8216 : osFilenameForNCOpen = pszTemp;
8217 : CPLFree(pszTemp);
8218 : }
8219 : #endif
8220 502 : int status2 = -1;
8221 :
8222 : #ifdef ENABLE_UFFD
8223 502 : cpl_uffd_context *pCtx = nullptr;
8224 : #endif
8225 :
8226 517 : if (STARTS_WITH(osFilenameForNCOpen, "/vsimem/") &&
8227 15 : poOpenInfo->eAccess == GA_ReadOnly)
8228 : {
8229 15 : vsi_l_offset nLength = 0;
8230 15 : poDS->fpVSIMEM = VSIFOpenL(osFilenameForNCOpen, "rb");
8231 15 : if (poDS->fpVSIMEM)
8232 : {
8233 : // We assume that the file will not be modified. If it is, then
8234 : // pabyBuffer might become invalid.
8235 : GByte *pabyBuffer =
8236 15 : VSIGetMemFileBuffer(osFilenameForNCOpen, &nLength, false);
8237 15 : if (pabyBuffer)
8238 : {
8239 15 : status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen),
8240 : nMode, static_cast<size_t>(nLength),
8241 : pabyBuffer, &cdfid);
8242 : }
8243 : }
8244 : }
8245 : else
8246 : {
8247 : const bool bVsiFile =
8248 487 : !strncmp(osFilenameForNCOpen, "/vsi", strlen("/vsi"));
8249 : #ifdef ENABLE_UFFD
8250 487 : bool bReadOnly = (poOpenInfo->eAccess == GA_ReadOnly);
8251 487 : void *pVma = nullptr;
8252 487 : uint64_t nVmaSize = 0;
8253 :
8254 487 : if (bVsiFile)
8255 : {
8256 2 : if (bReadOnly)
8257 : {
8258 2 : if (CPLIsUserFaultMappingSupported())
8259 : {
8260 2 : pCtx = CPLCreateUserFaultMapping(osFilenameForNCOpen, &pVma,
8261 : &nVmaSize);
8262 : }
8263 : else
8264 : {
8265 0 : CPLError(CE_Failure, CPLE_AppDefined,
8266 : "Opening a /vsi file with the netCDF driver "
8267 : "requires Linux userfaultfd to be available. "
8268 : "If running from Docker, "
8269 : "--security-opt seccomp=unconfined might be "
8270 : "needed.%s",
8271 0 : ((poDS->eFormat == NCDF_FORMAT_NC4 ||
8272 0 : poDS->eFormat == NCDF_FORMAT_HDF5) &&
8273 0 : GDALGetDriverByName("HDF5"))
8274 : ? " Or you may set the GDAL_SKIP=netCDF "
8275 : "configuration option to force the use of "
8276 : "the HDF5 driver."
8277 : : "");
8278 : }
8279 : }
8280 : else
8281 : {
8282 0 : CPLError(CE_Failure, CPLE_AppDefined,
8283 : "Opening a /vsi file with the netCDF driver is only "
8284 : "supported in read-only mode");
8285 : }
8286 : }
8287 487 : if (pCtx != nullptr && pVma != nullptr && nVmaSize > 0)
8288 : {
8289 : // netCDF code, at least for netCDF 4.7.0, is confused by filenames
8290 : // like /vsicurl/http[s]://example.com/foo.nc, so just pass the
8291 : // final part
8292 2 : status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen), nMode,
8293 : static_cast<size_t>(nVmaSize), pVma, &cdfid);
8294 : }
8295 : else
8296 485 : status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
8297 : #else
8298 : if (bVsiFile)
8299 : {
8300 : CPLError(
8301 : CE_Failure, CPLE_AppDefined,
8302 : "Opening a /vsi file with the netCDF driver requires Linux "
8303 : "userfaultfd to be available.%s",
8304 : ((poDS->eFormat == NCDF_FORMAT_NC4 ||
8305 : poDS->eFormat == NCDF_FORMAT_HDF5) &&
8306 : GDALGetDriverByName("HDF5"))
8307 : ? " Or you may set the GDAL_SKIP=netCDF "
8308 : "configuration option to force the use of the HDF5 "
8309 : "driver."
8310 : : "");
8311 : status2 = NC_EIO;
8312 : }
8313 : else
8314 : {
8315 : status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
8316 : }
8317 : #endif
8318 : }
8319 502 : if (status2 != NC_NOERR)
8320 : {
8321 : #ifdef NCDF_DEBUG
8322 : CPLDebug("GDAL_netCDF", "error opening");
8323 : #endif
8324 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8325 : // with GDALDataset own mutex.
8326 0 : delete poDS;
8327 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8328 0 : return nullptr;
8329 : }
8330 : #ifdef NCDF_DEBUG
8331 : CPLDebug("GDAL_netCDF", "got cdfid=%d", cdfid);
8332 : #endif
8333 :
8334 : #if defined(ENABLE_NCDUMP) && !defined(_WIN32)
8335 : // Try to destroy the temporary file right now on Unix
8336 502 : if (poDS->bFileToDestroyAtClosing)
8337 : {
8338 3 : if (VSIUnlink(poDS->osFilename) == 0)
8339 : {
8340 3 : poDS->bFileToDestroyAtClosing = false;
8341 : }
8342 : }
8343 : #endif
8344 :
8345 : // Is this a real netCDF file?
8346 : int ndims;
8347 : int ngatts;
8348 : int nvars;
8349 : int unlimdimid;
8350 502 : int status = nc_inq(cdfid, &ndims, &nvars, &ngatts, &unlimdimid);
8351 502 : if (status != NC_NOERR)
8352 : {
8353 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8354 : // with GDALDataset own mutex.
8355 0 : delete poDS;
8356 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8357 0 : return nullptr;
8358 : }
8359 :
8360 : // Get file type from netcdf.
8361 502 : int nTmpFormat = NCDF_FORMAT_NONE;
8362 502 : status = nc_inq_format(cdfid, &nTmpFormat);
8363 502 : if (status != NC_NOERR)
8364 : {
8365 0 : NCDF_ERR(status);
8366 : }
8367 : else
8368 : {
8369 502 : CPLDebug("GDAL_netCDF",
8370 : "driver detected file type=%d, libnetcdf detected type=%d",
8371 502 : poDS->eFormat, nTmpFormat);
8372 502 : if (static_cast<NetCDFFormatEnum>(nTmpFormat) != poDS->eFormat)
8373 : {
8374 : // Warn if file detection conflicts with that from libnetcdf
8375 : // except for NC4C, which we have no way of detecting initially.
8376 26 : if (nTmpFormat != NCDF_FORMAT_NC4C &&
8377 13 : !STARTS_WITH(poDS->osFilename, "http://") &&
8378 0 : !STARTS_WITH(poDS->osFilename, "https://"))
8379 : {
8380 0 : CPLError(CE_Warning, CPLE_AppDefined,
8381 : "NetCDF driver detected file type=%d, but libnetcdf "
8382 : "detected type=%d",
8383 0 : poDS->eFormat, nTmpFormat);
8384 : }
8385 13 : CPLDebug("GDAL_netCDF", "setting file type to %d, was %d",
8386 13 : nTmpFormat, poDS->eFormat);
8387 13 : poDS->eFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
8388 : }
8389 : }
8390 :
8391 : // Does the request variable exist?
8392 502 : if (bTreatAsSubdataset)
8393 : {
8394 : int dummy;
8395 60 : if (NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &dummy,
8396 60 : &dummy) != CE_None)
8397 : {
8398 0 : CPLError(CE_Warning, CPLE_AppDefined,
8399 : "%s is a netCDF file, but %s is not a variable.",
8400 : poOpenInfo->pszFilename, osSubdatasetName.c_str());
8401 :
8402 0 : GDAL_nc_close(cdfid);
8403 : #ifdef ENABLE_UFFD
8404 0 : NETCDF_UFFD_UNMAP(pCtx);
8405 : #endif
8406 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8407 : // deadlock with GDALDataset own mutex.
8408 0 : delete poDS;
8409 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8410 0 : return nullptr;
8411 : }
8412 : }
8413 :
8414 : // Figure out whether or not the listed dataset has support for simple
8415 : // geometries (CF-1.8)
8416 502 : poDS->nCFVersion = nccfdriver::getCFVersion(cdfid);
8417 502 : bool bHasSimpleGeometries = false; // but not necessarily valid
8418 502 : if (poDS->nCFVersion >= 1.8)
8419 : {
8420 75 : bHasSimpleGeometries = poDS->DetectAndFillSGLayers(cdfid);
8421 75 : if (bHasSimpleGeometries)
8422 : {
8423 67 : poDS->bSGSupport = true;
8424 67 : poDS->vcdf.enableFullVirtualMode();
8425 : }
8426 : }
8427 :
8428 : char szConventions[NC_MAX_NAME + 1];
8429 502 : szConventions[0] = '\0';
8430 502 : nc_type nAttype = NC_NAT;
8431 502 : size_t nAttlen = 0;
8432 502 : nc_inq_att(cdfid, NC_GLOBAL, "Conventions", &nAttype, &nAttlen);
8433 1004 : if (nAttlen >= sizeof(szConventions) ||
8434 502 : nc_get_att_text(cdfid, NC_GLOBAL, "Conventions", szConventions) !=
8435 : NC_NOERR)
8436 : {
8437 59 : CPLDebug("GDAL_netCDF", "No UNIDATA NC_GLOBAL:Conventions attribute");
8438 : // Note that 'Conventions' is always capital 'C' in CF spec.
8439 : }
8440 : else
8441 : {
8442 443 : szConventions[nAttlen] = '\0';
8443 : }
8444 :
8445 : // Create band information objects.
8446 502 : CPLDebug("GDAL_netCDF", "var_count = %d", nvars);
8447 :
8448 : // Create a corresponding GDALDataset.
8449 : // Create Netcdf Subdataset if filename as NETCDF tag.
8450 502 : poDS->cdfid = cdfid;
8451 : #ifdef ENABLE_UFFD
8452 502 : poDS->pCtx = pCtx;
8453 : #endif
8454 502 : poDS->eAccess = poOpenInfo->eAccess;
8455 502 : poDS->bDefineMode = false;
8456 :
8457 502 : poDS->ReadAttributes(cdfid, NC_GLOBAL);
8458 :
8459 : // Identify coordinate and boundary variables that we should
8460 : // ignore as Raster Bands.
8461 502 : char **papszIgnoreVars = nullptr;
8462 502 : NCDFGetCoordAndBoundVarFullNames(cdfid, &papszIgnoreVars);
8463 : // Filter variables to keep only valid 2+D raster bands and vector fields.
8464 502 : int nRasterVars = 0;
8465 502 : int nIgnoredVars = 0;
8466 502 : int nGroupID = -1;
8467 502 : int nVarID = -1;
8468 :
8469 : std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>
8470 1004 : oMap2DDimsToGroupAndVar;
8471 1157 : if ((poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
8472 153 : STARTS_WITH(CSLFetchNameValueDef(poDS->papszMetadata,
8473 : "NC_GLOBAL#mission_name", ""),
8474 1 : "Sentinel 3") &&
8475 1 : EQUAL(CSLFetchNameValueDef(poDS->papszMetadata,
8476 : "NC_GLOBAL#altimeter_sensor_name", ""),
8477 655 : "SRAL") &&
8478 1 : EQUAL(CSLFetchNameValueDef(poDS->papszMetadata,
8479 : "NC_GLOBAL#radiometer_sensor_name", ""),
8480 : "MWR"))
8481 : {
8482 1 : if (poDS->eAccess == GA_Update)
8483 : {
8484 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8485 : // deadlock with GDALDataset own mutex.
8486 0 : delete poDS;
8487 0 : return nullptr;
8488 : }
8489 1 : poDS->ProcessSentinel3_SRAL_MWR();
8490 : }
8491 : else
8492 : {
8493 501 : poDS->FilterVars(cdfid, (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0,
8494 653 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
8495 152 : !bHasSimpleGeometries,
8496 : papszIgnoreVars, &nRasterVars, &nGroupID, &nVarID,
8497 : &nIgnoredVars, oMap2DDimsToGroupAndVar);
8498 : }
8499 502 : CSLDestroy(papszIgnoreVars);
8500 :
8501 502 : const bool bListAllArrays = CPLTestBool(
8502 502 : CSLFetchNameValueDef(poDS->papszOpenOptions, "LIST_ALL_ARRAYS", "NO"));
8503 :
8504 : // Case where there is no raster variable
8505 502 : if (!bListAllArrays && nRasterVars == 0 && !bTreatAsSubdataset)
8506 : {
8507 119 : poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
8508 119 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8509 : // with GDALDataset own mutex.
8510 119 : poDS->TryLoadXML();
8511 : // If the dataset has been opened in raster mode only, exit
8512 119 : if ((poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0 &&
8513 9 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) == 0)
8514 : {
8515 4 : delete poDS;
8516 4 : poDS = nullptr;
8517 : }
8518 : // Otherwise if the dataset is opened in vector mode, that there is
8519 : // no vector layer and we are in read-only, exit too.
8520 115 : else if (poDS->GetLayerCount() == 0 &&
8521 123 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
8522 8 : poOpenInfo->eAccess == GA_ReadOnly)
8523 : {
8524 8 : delete poDS;
8525 8 : poDS = nullptr;
8526 : }
8527 119 : CPLAcquireMutex(hNCMutex, 1000.0);
8528 119 : return poDS;
8529 : }
8530 :
8531 : // We have more than one variable with 2 dimensions in the
8532 : // file, then treat this as a subdataset container dataset.
8533 383 : bool bSeveralVariablesAsBands = false;
8534 383 : if (bListAllArrays || ((nRasterVars > 1) && !bTreatAsSubdataset))
8535 : {
8536 29 : if (CPLFetchBool(poOpenInfo->papszOpenOptions, "VARIABLES_AS_BANDS",
8537 35 : false) &&
8538 6 : oMap2DDimsToGroupAndVar.size() == 1)
8539 : {
8540 6 : std::tie(nGroupID, nVarID) =
8541 12 : oMap2DDimsToGroupAndVar.begin()->second.front();
8542 6 : bSeveralVariablesAsBands = true;
8543 : }
8544 : else
8545 : {
8546 23 : poDS->CreateSubDatasetList(cdfid);
8547 23 : poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
8548 23 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8549 : // deadlock with GDALDataset own mutex.
8550 23 : poDS->TryLoadXML();
8551 23 : CPLAcquireMutex(hNCMutex, 1000.0);
8552 23 : return poDS;
8553 : }
8554 : }
8555 :
8556 : // If we are not treating things as a subdataset, then capture
8557 : // the name of the single available variable as the subdataset.
8558 360 : if (!bTreatAsSubdataset)
8559 : {
8560 300 : char *pszVarName = nullptr;
8561 300 : NCDF_ERR(NCDFGetVarFullName(nGroupID, nVarID, &pszVarName));
8562 300 : osSubdatasetName = (pszVarName != nullptr ? pszVarName : "");
8563 300 : CPLFree(pszVarName);
8564 : }
8565 :
8566 : // We have ignored at least one variable, so we should report them
8567 : // as subdatasets for reference.
8568 360 : if (nIgnoredVars > 0 && !bTreatAsSubdataset)
8569 : {
8570 25 : CPLDebug("GDAL_netCDF",
8571 : "As %d variables were ignored, creating subdataset list "
8572 : "for reference. Variable #%d [%s] is the main variable",
8573 : nIgnoredVars, nVarID, osSubdatasetName.c_str());
8574 25 : poDS->CreateSubDatasetList(cdfid);
8575 : }
8576 :
8577 : // Open the NETCDF subdataset NETCDF:"filename":subdataset.
8578 360 : int var = -1;
8579 360 : NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &nGroupID, &var);
8580 : // Now we can forget the root cdfid and only use the selected group.
8581 360 : cdfid = nGroupID;
8582 360 : int nd = 0;
8583 360 : nc_inq_varndims(cdfid, var, &nd);
8584 :
8585 360 : poDS->m_anDimIds.resize(nd);
8586 :
8587 : // X, Y, Z position in array
8588 720 : std::vector<int> anBandDimPos(nd);
8589 :
8590 360 : nc_inq_vardimid(cdfid, var, poDS->m_anDimIds.data());
8591 :
8592 : // Check if somebody tried to pass a variable with less than 1D.
8593 360 : if (nd < 1)
8594 : {
8595 0 : CPLError(CE_Warning, CPLE_AppDefined,
8596 : "Variable has %d dimension(s) - not supported.", nd);
8597 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8598 : // with GDALDataset own mutex.
8599 0 : delete poDS;
8600 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8601 0 : return nullptr;
8602 : }
8603 :
8604 : // CF-1 Convention
8605 : //
8606 : // Dimensions to appear in the relative order T, then Z, then Y,
8607 : // then X to the file. All other dimensions should, whenever
8608 : // possible, be placed to the left of the spatiotemporal
8609 : // dimensions.
8610 :
8611 : // Verify that dimensions are in the {T,Z,Y,X} or {T,Z,Y,X} order
8612 : // Ideally we should detect for other ordering and act accordingly
8613 : // Only done if file has Conventions=CF-* and only prints warning
8614 : // To disable set GDAL_NETCDF_VERIFY_DIMS=NO and to use only
8615 : // attributes (not varnames) set GDAL_NETCDF_VERIFY_DIMS=STRICT
8616 : const bool bCheckDims =
8617 720 : CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES")) &&
8618 360 : STARTS_WITH_CI(szConventions, "CF");
8619 :
8620 360 : bool bYXBandOrder = false;
8621 360 : if (nd == 3)
8622 : {
8623 : // If there's a coordinates attributes, and the variable it points to
8624 : // are 2D variables indexed by the same first and second dimension than
8625 : // our variable of interest, then it is Y,X,Band order.
8626 46 : char *pszCoordinates = nullptr;
8627 46 : if (NCDFGetAttr(cdfid, var, "coordinates", &pszCoordinates) ==
8628 63 : CE_None &&
8629 17 : pszCoordinates)
8630 : {
8631 : const CPLStringList aosCoordinates(
8632 34 : NCDFTokenizeCoordinatesAttribute(pszCoordinates));
8633 17 : if (aosCoordinates.size() == 2)
8634 : {
8635 : // Test that each variable is longitude/latitude.
8636 13 : for (int i = 0; i < aosCoordinates.size(); i++)
8637 : {
8638 13 : if (NCDFIsVarLongitude(cdfid, -1, aosCoordinates[i]) ||
8639 4 : NCDFIsVarLatitude(cdfid, -1, aosCoordinates[i]))
8640 : {
8641 9 : int nOtherGroupId = -1;
8642 9 : int nOtherVarId = -1;
8643 9 : if (NCDFResolveVar(cdfid, aosCoordinates[i],
8644 : &nOtherGroupId,
8645 9 : &nOtherVarId) == CE_None)
8646 : {
8647 9 : int coordDimCount = 0;
8648 9 : nc_inq_varndims(nOtherGroupId, nOtherVarId,
8649 : &coordDimCount);
8650 9 : if (coordDimCount == 2)
8651 : {
8652 3 : int coordDimIds[2] = {0, 0};
8653 3 : nc_inq_vardimid(nOtherGroupId, nOtherVarId,
8654 : coordDimIds);
8655 4 : if (coordDimIds[0] == poDS->m_anDimIds[0] &&
8656 1 : coordDimIds[1] == poDS->m_anDimIds[1])
8657 : {
8658 1 : bYXBandOrder = true;
8659 1 : break;
8660 : }
8661 : }
8662 : }
8663 : }
8664 : }
8665 : }
8666 : }
8667 46 : CPLFree(pszCoordinates);
8668 :
8669 46 : if (!bYXBandOrder)
8670 : {
8671 45 : char szDim0Name[NC_MAX_NAME + 1] = {};
8672 45 : char szDim1Name[NC_MAX_NAME + 1] = {};
8673 45 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[0], szDim0Name);
8674 45 : NCDF_ERR(status);
8675 45 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[1], szDim1Name);
8676 45 : NCDF_ERR(status);
8677 :
8678 45 : if (strcmp(szDim0Name, "number_of_lines") == 0 &&
8679 1 : strcmp(szDim1Name, "pixels_per_line") == 0)
8680 : {
8681 : // Like in PACE OCI products
8682 1 : bYXBandOrder = true;
8683 : }
8684 : else
8685 : {
8686 : // For example for EMIT data (https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/),
8687 : // dimension order is downtrack, crosstrack, bands
8688 44 : char szDim2Name[NC_MAX_NAME + 1] = {};
8689 44 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[2], szDim2Name);
8690 44 : NCDF_ERR(status);
8691 86 : bYXBandOrder = strcmp(szDim2Name, "bands") == 0 ||
8692 42 : strcmp(szDim2Name, "band") == 0;
8693 : }
8694 : }
8695 : }
8696 :
8697 360 : if (nd >= 2 && bCheckDims && !bYXBandOrder)
8698 : {
8699 273 : char szDimName1[NC_MAX_NAME + 1] = {};
8700 273 : char szDimName2[NC_MAX_NAME + 1] = {};
8701 273 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 1], szDimName1);
8702 273 : NCDF_ERR(status);
8703 273 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 2], szDimName2);
8704 273 : NCDF_ERR(status);
8705 437 : if (NCDFIsVarLongitude(cdfid, -1, szDimName1) == false &&
8706 164 : NCDFIsVarProjectionX(cdfid, -1, szDimName1) == false)
8707 : {
8708 4 : CPLError(CE_Warning, CPLE_AppDefined,
8709 : "dimension #%d (%s) is not a Longitude/X dimension.",
8710 : nd - 1, szDimName1);
8711 : }
8712 437 : if (NCDFIsVarLatitude(cdfid, -1, szDimName2) == false &&
8713 164 : NCDFIsVarProjectionY(cdfid, -1, szDimName2) == false)
8714 : {
8715 4 : CPLError(CE_Warning, CPLE_AppDefined,
8716 : "dimension #%d (%s) is not a Latitude/Y dimension.",
8717 : nd - 2, szDimName2);
8718 : }
8719 273 : if ((NCDFIsVarLongitude(cdfid, -1, szDimName2) ||
8720 275 : NCDFIsVarProjectionX(cdfid, -1, szDimName2)) &&
8721 2 : (NCDFIsVarLatitude(cdfid, -1, szDimName1) ||
8722 0 : NCDFIsVarProjectionY(cdfid, -1, szDimName1)))
8723 : {
8724 2 : poDS->bSwitchedXY = true;
8725 : }
8726 273 : if (nd >= 3)
8727 : {
8728 52 : char szDimName3[NC_MAX_NAME + 1] = {};
8729 : status =
8730 52 : nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 3], szDimName3);
8731 52 : NCDF_ERR(status);
8732 52 : if (nd >= 4)
8733 : {
8734 13 : char szDimName4[NC_MAX_NAME + 1] = {};
8735 : status =
8736 13 : nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 4], szDimName4);
8737 13 : NCDF_ERR(status);
8738 13 : if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false)
8739 : {
8740 0 : CPLError(CE_Warning, CPLE_AppDefined,
8741 : "dimension #%d (%s) is not a Vertical dimension.",
8742 : nd - 3, szDimName3);
8743 : }
8744 13 : if (NCDFIsVarTimeCoord(cdfid, -1, szDimName4) == false)
8745 : {
8746 0 : CPLError(CE_Warning, CPLE_AppDefined,
8747 : "dimension #%d (%s) is not a Time dimension.",
8748 : nd - 4, szDimName4);
8749 : }
8750 : }
8751 : else
8752 : {
8753 75 : if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false &&
8754 36 : NCDFIsVarTimeCoord(cdfid, -1, szDimName3) == false)
8755 : {
8756 0 : CPLError(CE_Warning, CPLE_AppDefined,
8757 : "dimension #%d (%s) is not a "
8758 : "Time or Vertical dimension.",
8759 : nd - 3, szDimName3);
8760 : }
8761 : }
8762 : }
8763 : }
8764 :
8765 : // Get X dimensions information.
8766 : size_t xdim;
8767 360 : poDS->nXDimID = poDS->m_anDimIds[bYXBandOrder ? 1 : nd - 1];
8768 360 : nc_inq_dimlen(cdfid, poDS->nXDimID, &xdim);
8769 :
8770 : // Get Y dimension information.
8771 : size_t ydim;
8772 360 : if (nd >= 2)
8773 : {
8774 356 : poDS->nYDimID = poDS->m_anDimIds[bYXBandOrder ? 0 : nd - 2];
8775 356 : nc_inq_dimlen(cdfid, poDS->nYDimID, &ydim);
8776 : }
8777 : else
8778 : {
8779 4 : poDS->nYDimID = -1;
8780 4 : ydim = 1;
8781 : }
8782 :
8783 360 : if (xdim > INT_MAX || ydim > INT_MAX)
8784 : {
8785 0 : CPLError(CE_Failure, CPLE_AppDefined,
8786 : "Invalid raster dimensions: " CPL_FRMT_GUIB "x" CPL_FRMT_GUIB,
8787 : static_cast<GUIntBig>(xdim), static_cast<GUIntBig>(ydim));
8788 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8789 : // with GDALDataset own mutex.
8790 0 : delete poDS;
8791 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8792 0 : return nullptr;
8793 : }
8794 :
8795 360 : poDS->nRasterXSize = static_cast<int>(xdim);
8796 360 : poDS->nRasterYSize = static_cast<int>(ydim);
8797 :
8798 360 : unsigned int k = 0;
8799 1157 : for (int j = 0; j < nd; j++)
8800 : {
8801 797 : if (poDS->m_anDimIds[j] == poDS->nXDimID)
8802 : {
8803 360 : anBandDimPos[0] = j; // Save Position of XDim
8804 360 : k++;
8805 : }
8806 797 : if (poDS->m_anDimIds[j] == poDS->nYDimID)
8807 : {
8808 356 : anBandDimPos[1] = j; // Save Position of YDim
8809 356 : k++;
8810 : }
8811 : }
8812 : // X and Y Dimension Ids were not found!
8813 360 : if ((nd >= 2 && k != 2) || (nd == 1 && k != 1))
8814 : {
8815 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8816 : // with GDALDataset own mutex.
8817 0 : delete poDS;
8818 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8819 0 : return nullptr;
8820 : }
8821 :
8822 : // Read Metadata for this variable.
8823 :
8824 : // Should disable as is also done at band level, except driver needs the
8825 : // variables as metadata (e.g. projection).
8826 360 : poDS->ReadAttributes(cdfid, var);
8827 :
8828 : // Read Metadata for each dimension.
8829 360 : int *panDimIds = nullptr;
8830 360 : NCDFGetVisibleDims(cdfid, &ndims, &panDimIds);
8831 : // With NetCDF-4 groups panDimIds is not always [0..dim_count-1] like
8832 : // in NetCDF-3 because we see only the dimensions of the selected group
8833 : // and its parents.
8834 : // poDS->papszDimName is indexed by dim IDs, so it must contains all IDs
8835 : // [0..max(panDimIds)], but they are not all useful so we fill names
8836 : // of useless dims with empty string.
8837 360 : if (panDimIds)
8838 : {
8839 360 : const int nMaxDimId = *std::max_element(panDimIds, panDimIds + ndims);
8840 360 : std::set<int> oSetExistingDimIds;
8841 1197 : for (int i = 0; i < ndims; i++)
8842 : {
8843 837 : oSetExistingDimIds.insert(panDimIds[i]);
8844 : }
8845 360 : std::set<int> oSetDimIdsUsedByVar;
8846 1157 : for (int i = 0; i < nd; i++)
8847 : {
8848 797 : oSetDimIdsUsedByVar.insert(poDS->m_anDimIds[i]);
8849 : }
8850 1199 : for (int j = 0; j <= nMaxDimId; j++)
8851 : {
8852 : // Is j dim used?
8853 839 : if (oSetExistingDimIds.find(j) != oSetExistingDimIds.end())
8854 : {
8855 : // Useful dim.
8856 837 : char szTemp[NC_MAX_NAME + 1] = {};
8857 837 : status = nc_inq_dimname(cdfid, j, szTemp);
8858 837 : if (status != NC_NOERR)
8859 : {
8860 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8861 : // deadlock with GDALDataset own
8862 : // mutex.
8863 0 : delete poDS;
8864 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8865 0 : return nullptr;
8866 : }
8867 837 : poDS->papszDimName.AddString(szTemp);
8868 :
8869 837 : if (oSetDimIdsUsedByVar.find(j) != oSetDimIdsUsedByVar.end())
8870 : {
8871 797 : int nDimGroupId = -1;
8872 797 : int nDimVarId = -1;
8873 797 : if (NCDFResolveVar(cdfid, poDS->papszDimName[j],
8874 797 : &nDimGroupId, &nDimVarId) == CE_None)
8875 : {
8876 593 : poDS->ReadAttributes(nDimGroupId, nDimVarId);
8877 : }
8878 : }
8879 : }
8880 : else
8881 : {
8882 : // Useless dim.
8883 2 : poDS->papszDimName.AddString("");
8884 : }
8885 : }
8886 360 : CPLFree(panDimIds);
8887 : }
8888 :
8889 : // Set projection info.
8890 720 : std::vector<std::string> aosRemovedMDItems;
8891 360 : if (nd > 1)
8892 : {
8893 356 : poDS->SetProjectionFromVar(cdfid, var,
8894 : /*bReadSRSOnly=*/false,
8895 : /* pszGivenGM = */ nullptr,
8896 : /* returnProjStr = */ nullptr,
8897 : /* sg = */ nullptr, &aosRemovedMDItems);
8898 : }
8899 :
8900 : // Override bottom-up with GDAL_NETCDF_BOTTOMUP config option.
8901 360 : const char *pszValue = CPLGetConfigOption("GDAL_NETCDF_BOTTOMUP", nullptr);
8902 360 : if (pszValue)
8903 : {
8904 24 : poDS->bBottomUp = CPLTestBool(pszValue);
8905 24 : CPLDebug("GDAL_netCDF",
8906 : "set bBottomUp=%d because GDAL_NETCDF_BOTTOMUP=%s",
8907 24 : static_cast<int>(poDS->bBottomUp), pszValue);
8908 : }
8909 :
8910 : // Save non-spatial dimension info.
8911 :
8912 360 : int *panBandZLev = nullptr;
8913 360 : int nDim = (nd >= 2) ? 2 : 1;
8914 : size_t lev_count;
8915 360 : size_t nTotLevCount = 1;
8916 360 : nc_type nType = NC_NAT;
8917 :
8918 360 : if (nd > 2)
8919 : {
8920 62 : nDim = 2;
8921 62 : panBandZLev = static_cast<int *>(CPLCalloc(nd - 2, sizeof(int)));
8922 :
8923 62 : CPLString osExtraDimNames = "{";
8924 :
8925 62 : char szDimName[NC_MAX_NAME + 1] = {};
8926 :
8927 62 : bool bREPORT_EXTRA_DIM_VALUESWarningEmitted = false;
8928 267 : for (int j = 0; j < nd; j++)
8929 : {
8930 348 : if ((poDS->m_anDimIds[j] != poDS->nXDimID) &&
8931 143 : (poDS->m_anDimIds[j] != poDS->nYDimID))
8932 : {
8933 81 : nc_inq_dimlen(cdfid, poDS->m_anDimIds[j], &lev_count);
8934 81 : nTotLevCount *= lev_count;
8935 81 : panBandZLev[nDim - 2] = static_cast<int>(lev_count);
8936 81 : anBandDimPos[nDim] = j; // Save Position of ZDim
8937 : // Save non-spatial dimension names.
8938 81 : if (nc_inq_dimname(cdfid, poDS->m_anDimIds[j], szDimName) ==
8939 : NC_NOERR)
8940 : {
8941 81 : osExtraDimNames += szDimName;
8942 81 : if (j < nd - 3)
8943 : {
8944 19 : osExtraDimNames += ",";
8945 : }
8946 :
8947 81 : int nIdxGroupID = -1;
8948 81 : int nIdxVarID = Get1DVariableIndexedByDimension(
8949 81 : cdfid, poDS->m_anDimIds[j], szDimName, true,
8950 81 : &nIdxGroupID);
8951 81 : poDS->m_anExtraDimGroupIds.push_back(nIdxGroupID);
8952 81 : poDS->m_anExtraDimVarIds.push_back(nIdxVarID);
8953 :
8954 81 : if (nIdxVarID >= 0)
8955 : {
8956 72 : nc_inq_vartype(nIdxGroupID, nIdxVarID, &nType);
8957 : char szExtraDimDef[NC_MAX_NAME + 1];
8958 72 : snprintf(szExtraDimDef, sizeof(szExtraDimDef),
8959 : "{%ld,%d}", (long)lev_count, nType);
8960 : char szTemp[NC_MAX_NAME + 32 + 1];
8961 72 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
8962 : szDimName);
8963 72 : poDS->papszMetadata = CSLSetNameValue(
8964 : poDS->papszMetadata, szTemp, szExtraDimDef);
8965 :
8966 : // Retrieving data for unlimited dimensions might be
8967 : // costly on network storage, so don't do it.
8968 : // Each band will capture the value along the extra
8969 : // dimension in its NETCDF_DIM_xxxx band metadata item
8970 : // Addresses use case of
8971 : // https://lists.osgeo.org/pipermail/gdal-dev/2023-May/057209.html
8972 : const bool bIsLocal =
8973 72 : VSIIsLocal(osFilenameForNCOpen.c_str());
8974 : bool bListDimValues =
8975 73 : bIsLocal || lev_count == 1 ||
8976 1 : !NCDFIsUnlimitedDim(poDS->eFormat ==
8977 : NCDF_FORMAT_NC4,
8978 1 : cdfid, poDS->m_anDimIds[j]);
8979 : const char *pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES =
8980 72 : CPLGetConfigOption(
8981 : "GDAL_NETCDF_REPORT_EXTRA_DIM_VALUES", nullptr);
8982 72 : if (pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES)
8983 : {
8984 2 : bListDimValues = CPLTestBool(
8985 : pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES);
8986 : }
8987 70 : else if (!bListDimValues && !bIsLocal &&
8988 1 : !bREPORT_EXTRA_DIM_VALUESWarningEmitted)
8989 : {
8990 1 : bREPORT_EXTRA_DIM_VALUESWarningEmitted = true;
8991 1 : CPLDebug(
8992 : "GDAL_netCDF",
8993 : "Listing extra dimension values is skipped "
8994 : "because this dataset is hosted on a network "
8995 : "file system, and such an operation could be "
8996 : "slow. If you still want to proceed, set the "
8997 : "GDAL_NETCDF_REPORT_EXTRA_DIM_VALUES "
8998 : "configuration option to YES");
8999 : }
9000 72 : if (bListDimValues)
9001 : {
9002 70 : char *pszTemp = nullptr;
9003 70 : if (NCDFGet1DVar(nIdxGroupID, nIdxVarID,
9004 70 : &pszTemp) == CE_None)
9005 : {
9006 70 : snprintf(szTemp, sizeof(szTemp),
9007 : "NETCDF_DIM_%s_VALUES", szDimName);
9008 70 : poDS->papszMetadata = CSLSetNameValue(
9009 : poDS->papszMetadata, szTemp, pszTemp);
9010 70 : CPLFree(pszTemp);
9011 : }
9012 : }
9013 : }
9014 : }
9015 : else
9016 : {
9017 0 : poDS->m_anExtraDimGroupIds.push_back(-1);
9018 0 : poDS->m_anExtraDimVarIds.push_back(-1);
9019 : }
9020 :
9021 81 : nDim++;
9022 : }
9023 : }
9024 62 : osExtraDimNames += "}";
9025 62 : poDS->papszMetadata = CSLSetNameValue(
9026 : poDS->papszMetadata, "NETCDF_DIM_EXTRA", osExtraDimNames);
9027 : }
9028 :
9029 : // Store Metadata.
9030 370 : for (const auto &osStr : aosRemovedMDItems)
9031 10 : poDS->papszMetadata =
9032 10 : CSLSetNameValue(poDS->papszMetadata, osStr.c_str(), nullptr);
9033 :
9034 360 : poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
9035 :
9036 : // Create bands.
9037 :
9038 : // Arbitrary threshold.
9039 : int nMaxBandCount =
9040 360 : atoi(CPLGetConfigOption("GDAL_MAX_BAND_COUNT", "32768"));
9041 360 : if (nMaxBandCount <= 0)
9042 0 : nMaxBandCount = 32768;
9043 360 : if (nTotLevCount > static_cast<unsigned int>(nMaxBandCount))
9044 : {
9045 0 : CPLError(CE_Warning, CPLE_AppDefined,
9046 : "Limiting number of bands to %d instead of %u", nMaxBandCount,
9047 : static_cast<unsigned int>(nTotLevCount));
9048 0 : nTotLevCount = static_cast<unsigned int>(nMaxBandCount);
9049 : }
9050 360 : if (poDS->nRasterXSize == 0 || poDS->nRasterYSize == 0)
9051 : {
9052 0 : poDS->nRasterXSize = 0;
9053 0 : poDS->nRasterYSize = 0;
9054 0 : nTotLevCount = 0;
9055 0 : if (poDS->GetLayerCount() == 0)
9056 : {
9057 0 : CPLFree(panBandZLev);
9058 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9059 : // deadlock with GDALDataset own mutex.
9060 0 : delete poDS;
9061 0 : CPLAcquireMutex(hNCMutex, 1000.0);
9062 0 : return nullptr;
9063 : }
9064 : }
9065 360 : if (bSeveralVariablesAsBands)
9066 : {
9067 6 : const auto &listVariables = oMap2DDimsToGroupAndVar.begin()->second;
9068 24 : for (int iBand = 0; iBand < static_cast<int>(listVariables.size());
9069 : ++iBand)
9070 : {
9071 18 : int bandVarGroupId = listVariables[iBand].first;
9072 18 : int bandVarId = listVariables[iBand].second;
9073 : netCDFRasterBand *poBand = new netCDFRasterBand(
9074 0 : netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, bandVarGroupId,
9075 18 : bandVarId, nDim, 0, nullptr, anBandDimPos.data(), iBand + 1);
9076 18 : poDS->SetBand(iBand + 1, poBand);
9077 : }
9078 : }
9079 : else
9080 : {
9081 822 : for (unsigned int lev = 0; lev < nTotLevCount; lev++)
9082 : {
9083 : netCDFRasterBand *poBand = new netCDFRasterBand(
9084 0 : netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, cdfid, var, nDim,
9085 468 : lev, panBandZLev, anBandDimPos.data(), lev + 1);
9086 468 : poDS->SetBand(lev + 1, poBand);
9087 : }
9088 : }
9089 :
9090 360 : if (panBandZLev)
9091 62 : CPLFree(panBandZLev);
9092 : // Handle angular geographic coordinates here
9093 :
9094 : // Initialize any PAM information.
9095 360 : if (bTreatAsSubdataset)
9096 : {
9097 60 : poDS->SetPhysicalFilename(poDS->osFilename);
9098 60 : poDS->SetSubdatasetName(osSubdatasetName);
9099 : }
9100 :
9101 360 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
9102 : // GDALDataset own mutex.
9103 360 : poDS->TryLoadXML();
9104 :
9105 360 : if (bTreatAsSubdataset)
9106 60 : poDS->oOvManager.Initialize(poDS, ":::VIRTUAL:::");
9107 : else
9108 300 : poDS->oOvManager.Initialize(poDS, poDS->osFilename);
9109 :
9110 360 : CPLAcquireMutex(hNCMutex, 1000.0);
9111 :
9112 360 : return poDS;
9113 : }
9114 :
9115 : /************************************************************************/
9116 : /* CopyMetadata() */
9117 : /* */
9118 : /* Create a copy of metadata for NC_GLOBAL or a variable */
9119 : /************************************************************************/
9120 :
9121 161 : static void CopyMetadata(GDALDataset *poSrcDS, GDALRasterBand *poSrcBand,
9122 : GDALRasterBand *poDstBand, int nCdfId, int CDFVarID,
9123 : const char *pszPrefix)
9124 : {
9125 : // Remove the following band meta but set them later from band data.
9126 161 : const char *const papszIgnoreBand[] = {
9127 : CF_ADD_OFFSET, CF_SCALE_FACTOR, "valid_range", "_Unsigned",
9128 : NCDF_FillValue, "coordinates", nullptr};
9129 161 : const char *const papszIgnoreGlobal[] = {"NETCDF_DIM_EXTRA", nullptr};
9130 :
9131 161 : CSLConstList papszMetadata = nullptr;
9132 161 : if (poSrcDS)
9133 : {
9134 68 : papszMetadata = poSrcDS->GetMetadata();
9135 : }
9136 93 : else if (poSrcBand)
9137 : {
9138 93 : papszMetadata = poSrcBand->GetMetadata();
9139 : }
9140 :
9141 643 : for (const auto &[pszKey, pszValue] : cpl::IterateNameValue(papszMetadata))
9142 : {
9143 : #ifdef NCDF_DEBUG
9144 : CPLDebug("GDAL_netCDF", "copy metadata [%s]=[%s]", pszKey, pszValue);
9145 : #endif
9146 :
9147 482 : CPLString osMetaName(pszKey);
9148 :
9149 : // Check for items that match pszPrefix if applicable.
9150 482 : if (pszPrefix && !EQUAL(pszPrefix, ""))
9151 : {
9152 : // Remove prefix.
9153 115 : if (STARTS_WITH(osMetaName.c_str(), pszPrefix))
9154 : {
9155 17 : osMetaName = osMetaName.substr(strlen(pszPrefix));
9156 : }
9157 : // Only copy items that match prefix.
9158 : else
9159 : {
9160 98 : continue;
9161 : }
9162 : }
9163 :
9164 : // Fix various issues with metadata translation.
9165 384 : if (CDFVarID == NC_GLOBAL)
9166 : {
9167 : // Do not copy items in papszIgnoreGlobal and NETCDF_DIM_*.
9168 485 : if ((CSLFindString(papszIgnoreGlobal, osMetaName) != -1) ||
9169 240 : (STARTS_WITH(osMetaName, "NETCDF_DIM_")))
9170 21 : continue;
9171 : // Remove NC_GLOBAL prefix for netcdf global Metadata.
9172 224 : else if (STARTS_WITH(osMetaName, "NC_GLOBAL#"))
9173 : {
9174 33 : osMetaName = osMetaName.substr(strlen("NC_GLOBAL#"));
9175 : }
9176 : // GDAL Metadata renamed as GDAL-[meta].
9177 191 : else if (strstr(osMetaName, "#") == nullptr)
9178 : {
9179 18 : osMetaName = "GDAL_" + osMetaName;
9180 : }
9181 : // Keep time, lev and depth information for safe-keeping.
9182 : // Time and vertical coordinate handling need improvements.
9183 : /*
9184 : else if( STARTS_WITH(szMetaName, "time#") )
9185 : {
9186 : szMetaName[4] = '-';
9187 : }
9188 : else if( STARTS_WITH(szMetaName, "lev#") )
9189 : {
9190 : szMetaName[3] = '-';
9191 : }
9192 : else if( STARTS_WITH(szMetaName, "depth#") )
9193 : {
9194 : szMetaName[5] = '-';
9195 : }
9196 : */
9197 : // Only copy data without # (previously all data was copied).
9198 224 : if (strstr(osMetaName, "#") != nullptr)
9199 173 : continue;
9200 : // netCDF attributes do not like the '#' character.
9201 : // for( unsigned int h=0; h < strlen(szMetaName) -1 ; h++ ) {
9202 : // if( szMetaName[h] == '#') szMetaName[h] = '-';
9203 : // }
9204 : }
9205 : else
9206 : {
9207 : // Do not copy varname, stats, NETCDF_DIM_*, nodata
9208 : // and items in papszIgnoreBand.
9209 139 : if (STARTS_WITH(osMetaName, "NETCDF_VARNAME") ||
9210 107 : STARTS_WITH(osMetaName, "STATISTICS_") ||
9211 107 : STARTS_WITH(osMetaName, "NETCDF_DIM_") ||
9212 74 : STARTS_WITH(osMetaName, "missing_value") ||
9213 293 : STARTS_WITH(osMetaName, "_FillValue") ||
9214 47 : CSLFindString(papszIgnoreBand, osMetaName) != -1)
9215 97 : continue;
9216 : }
9217 :
9218 : #ifdef NCDF_DEBUG
9219 : CPLDebug("GDAL_netCDF", "copy name=[%s] value=[%s]", osMetaName.c_str(),
9220 : pszValue);
9221 : #endif
9222 93 : if (NCDFPutAttr(nCdfId, CDFVarID, osMetaName, pszValue) != CE_None)
9223 : {
9224 0 : CPLDebug("GDAL_netCDF", "NCDFPutAttr(%d, %d, %s, %s) failed",
9225 : nCdfId, CDFVarID, osMetaName.c_str(), pszValue);
9226 : }
9227 : }
9228 :
9229 : // Set add_offset and scale_factor here if present.
9230 161 : if (poSrcBand && poDstBand)
9231 : {
9232 :
9233 93 : int bGotAddOffset = FALSE;
9234 93 : const double dfAddOffset = poSrcBand->GetOffset(&bGotAddOffset);
9235 93 : int bGotScale = FALSE;
9236 93 : const double dfScale = poSrcBand->GetScale(&bGotScale);
9237 :
9238 93 : if (bGotAddOffset && dfAddOffset != 0.0)
9239 1 : poDstBand->SetOffset(dfAddOffset);
9240 93 : if (bGotScale && dfScale != 1.0)
9241 1 : poDstBand->SetScale(dfScale);
9242 : }
9243 161 : }
9244 :
9245 : /************************************************************************/
9246 : /* CreateLL() */
9247 : /* */
9248 : /* Shared functionality between netCDFDataset::Create() and */
9249 : /* netCDF::CreateCopy() for creating netcdf file based on a set of */
9250 : /* options and a configuration. */
9251 : /************************************************************************/
9252 :
9253 201 : netCDFDataset *netCDFDataset::CreateLL(const char *pszFilename, int nXSize,
9254 : int nYSize, int nBandsIn,
9255 : CSLConstList papszOptions)
9256 : {
9257 201 : if (!((nXSize == 0 && nYSize == 0 && nBandsIn == 0) ||
9258 128 : (nXSize > 0 && nYSize > 0 && nBandsIn > 0)))
9259 : {
9260 1 : return nullptr;
9261 : }
9262 :
9263 200 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
9264 : // GDALDataset own mutex.
9265 200 : netCDFDataset *poDS = new netCDFDataset();
9266 200 : CPLAcquireMutex(hNCMutex, 1000.0);
9267 :
9268 200 : poDS->nRasterXSize = nXSize;
9269 200 : poDS->nRasterYSize = nYSize;
9270 200 : poDS->eAccess = GA_Update;
9271 200 : poDS->osFilename = pszFilename;
9272 :
9273 : // From gtiff driver, is this ok?
9274 : /*
9275 : poDS->nBlockXSize = nXSize;
9276 : poDS->nBlockYSize = 1;
9277 : poDS->nBlocksPerBand =
9278 : DIV_ROUND_UP((nYSize, poDS->nBlockYSize))
9279 : * DIV_ROUND_UP((nXSize, poDS->nBlockXSize));
9280 : */
9281 :
9282 : // process options.
9283 200 : poDS->papszCreationOptions = CSLDuplicate(papszOptions);
9284 200 : poDS->ProcessCreationOptions();
9285 :
9286 200 : if (poDS->eMultipleLayerBehavior == SEPARATE_FILES)
9287 : {
9288 : VSIStatBuf sStat;
9289 3 : if (VSIStat(pszFilename, &sStat) == 0)
9290 : {
9291 0 : if (!VSI_ISDIR(sStat.st_mode))
9292 : {
9293 0 : CPLError(CE_Failure, CPLE_FileIO,
9294 : "%s is an existing file, but not a directory",
9295 : pszFilename);
9296 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9297 : // deadlock with GDALDataset own
9298 : // mutex.
9299 0 : delete poDS;
9300 0 : CPLAcquireMutex(hNCMutex, 1000.0);
9301 0 : return nullptr;
9302 : }
9303 : }
9304 3 : else if (VSIMkdir(pszFilename, 0755) != 0)
9305 : {
9306 1 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s directory",
9307 : pszFilename);
9308 1 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9309 : // deadlock with GDALDataset own mutex.
9310 1 : delete poDS;
9311 1 : CPLAcquireMutex(hNCMutex, 1000.0);
9312 1 : return nullptr;
9313 : }
9314 :
9315 2 : return poDS;
9316 : }
9317 : // Create the dataset.
9318 394 : CPLString osFilenameForNCCreate(pszFilename);
9319 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
9320 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
9321 : {
9322 : char *pszTemp =
9323 : CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
9324 : osFilenameForNCCreate = pszTemp;
9325 : CPLFree(pszTemp);
9326 : }
9327 : #endif
9328 :
9329 : #if defined(_WIN32)
9330 : {
9331 : // Works around bug of msys2 netCDF 4.9.0 package where nc_create()
9332 : // crashes
9333 : VSIStatBuf sStat;
9334 : const std::string osDirname =
9335 : CPLGetDirnameSafe(osFilenameForNCCreate.c_str());
9336 : if (VSIStat(osDirname.c_str(), &sStat) != 0)
9337 : {
9338 : CPLError(CE_Failure, CPLE_OpenFailed,
9339 : "Unable to create netCDF file %s: non existing output "
9340 : "directory",
9341 : pszFilename);
9342 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9343 : // deadlock with GDALDataset own mutex.
9344 : delete poDS;
9345 : CPLAcquireMutex(hNCMutex, 1000.0);
9346 : return nullptr;
9347 : }
9348 : }
9349 : #endif
9350 :
9351 : int status =
9352 197 : nc_create(osFilenameForNCCreate, poDS->nCreateMode, &(poDS->cdfid));
9353 :
9354 : // Put into define mode.
9355 197 : poDS->SetDefineMode(true);
9356 :
9357 197 : if (status != NC_NOERR)
9358 : {
9359 30 : CPLError(CE_Failure, CPLE_OpenFailed,
9360 : "Unable to create netCDF file %s (Error code %d): %s .",
9361 : pszFilename, status, nc_strerror(status));
9362 30 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
9363 : // with GDALDataset own mutex.
9364 30 : delete poDS;
9365 30 : CPLAcquireMutex(hNCMutex, 1000.0);
9366 30 : return nullptr;
9367 : }
9368 :
9369 : // Define dimensions.
9370 167 : if (nXSize > 0 && nYSize > 0)
9371 : {
9372 114 : poDS->papszDimName.AddString(NCDF_DIMNAME_X);
9373 : status =
9374 114 : nc_def_dim(poDS->cdfid, NCDF_DIMNAME_X, nXSize, &(poDS->nXDimID));
9375 114 : NCDF_ERR(status);
9376 114 : CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
9377 : poDS->cdfid, NCDF_DIMNAME_X, nXSize, poDS->nXDimID);
9378 :
9379 114 : poDS->papszDimName.AddString(NCDF_DIMNAME_Y);
9380 : status =
9381 114 : nc_def_dim(poDS->cdfid, NCDF_DIMNAME_Y, nYSize, &(poDS->nYDimID));
9382 114 : NCDF_ERR(status);
9383 114 : CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
9384 : poDS->cdfid, NCDF_DIMNAME_Y, nYSize, poDS->nYDimID);
9385 : }
9386 :
9387 167 : return poDS;
9388 : }
9389 :
9390 : /************************************************************************/
9391 : /* Create() */
9392 : /************************************************************************/
9393 :
9394 127 : GDALDataset *netCDFDataset::Create(const char *pszFilename, int nXSize,
9395 : int nYSize, int nBandsIn, GDALDataType eType,
9396 : CSLConstList papszOptions)
9397 : {
9398 127 : CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::Create(%s, ...)",
9399 : pszFilename);
9400 :
9401 : const char *legacyCreationOp =
9402 127 : CSLFetchNameValueDef(papszOptions, "GEOMETRY_ENCODING", "CF_1.8");
9403 254 : std::string legacyCreationOp_s = std::string(legacyCreationOp);
9404 :
9405 : // Check legacy creation op FIRST
9406 :
9407 127 : bool legacyCreateMode = false;
9408 :
9409 127 : if (nXSize != 0 || nYSize != 0 || nBandsIn != 0)
9410 : {
9411 56 : legacyCreateMode = true;
9412 : }
9413 71 : else if (legacyCreationOp_s == "CF_1.8")
9414 : {
9415 54 : legacyCreateMode = false;
9416 : }
9417 :
9418 17 : else if (legacyCreationOp_s == "WKT")
9419 : {
9420 17 : legacyCreateMode = true;
9421 : }
9422 :
9423 : else
9424 : {
9425 0 : CPLError(
9426 : CE_Failure, CPLE_NotSupported,
9427 : "Dataset creation option GEOMETRY_ENCODING=%s is not supported.",
9428 : legacyCreationOp_s.c_str());
9429 0 : return nullptr;
9430 : }
9431 :
9432 254 : CPLStringList aosOptions(CSLDuplicate(papszOptions));
9433 240 : if (aosOptions.FetchNameValue("FORMAT") == nullptr &&
9434 113 : (eType == GDT_UInt16 || eType == GDT_UInt32 || eType == GDT_UInt64 ||
9435 : eType == GDT_Int64))
9436 : {
9437 10 : CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
9438 10 : aosOptions.SetNameValue("FORMAT", "NC4");
9439 : }
9440 :
9441 254 : CPLStringList aosBandNames;
9442 127 : if (const char *pszBandNames = aosOptions.FetchNameValue("BAND_NAMES"))
9443 : {
9444 : aosBandNames =
9445 2 : CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);
9446 :
9447 2 : if (aosBandNames.Count() != nBandsIn)
9448 : {
9449 1 : CPLError(CE_Failure, CPLE_OpenFailed,
9450 : "Attempted to create netCDF with %d bands but %d names "
9451 : "provided in BAND_NAMES.",
9452 : nBandsIn, aosBandNames.Count());
9453 :
9454 1 : return nullptr;
9455 : }
9456 : }
9457 :
9458 252 : CPLMutexHolderD(&hNCMutex);
9459 :
9460 126 : auto poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize, nBandsIn,
9461 126 : aosOptions.List());
9462 :
9463 126 : if (!poDS)
9464 19 : return nullptr;
9465 :
9466 107 : if (!legacyCreateMode)
9467 : {
9468 37 : poDS->bSGSupport = true;
9469 37 : poDS->vcdf.enableFullVirtualMode();
9470 : }
9471 :
9472 : else
9473 : {
9474 70 : poDS->bSGSupport = false;
9475 : }
9476 :
9477 : // Should we write signed or unsigned byte?
9478 : // TODO should this only be done in Create()
9479 107 : poDS->bSignedData = true;
9480 107 : const char *pszValue = CSLFetchNameValueDef(papszOptions, "PIXELTYPE", "");
9481 107 : if (eType == GDT_UInt8 && !EQUAL(pszValue, "SIGNEDBYTE"))
9482 15 : poDS->bSignedData = false;
9483 :
9484 : // Add Conventions, GDAL info and history.
9485 107 : if (poDS->cdfid >= 0)
9486 : {
9487 : const char *CF_Vector_Conv =
9488 173 : poDS->bSGSupport ||
9489 : // Use of variable length strings require CF-1.8
9490 68 : EQUAL(aosOptions.FetchNameValueDef("FORMAT", ""), "NC4")
9491 : ? NCDF_CONVENTIONS_CF_V1_8
9492 173 : : NCDF_CONVENTIONS_CF_V1_6;
9493 105 : poDS->bWriteGDALVersion = CPLTestBool(
9494 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
9495 105 : poDS->bWriteGDALHistory = CPLTestBool(
9496 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
9497 105 : NCDFAddGDALHistory(poDS->cdfid, pszFilename, poDS->bWriteGDALVersion,
9498 105 : poDS->bWriteGDALHistory, "", "Create",
9499 : (nBandsIn == 0) ? CF_Vector_Conv
9500 : : GDAL_DEFAULT_NCDF_CONVENTIONS);
9501 : }
9502 :
9503 : // Define bands.
9504 198 : for (int iBand = 1; iBand <= nBandsIn; iBand++)
9505 : {
9506 : const char *pszBandName =
9507 91 : aosBandNames.empty() ? nullptr : aosBandNames[iBand - 1];
9508 :
9509 91 : poDS->SetBand(iBand, new netCDFRasterBand(
9510 91 : netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS,
9511 91 : eType, iBand, poDS->bSignedData, pszBandName));
9512 : }
9513 :
9514 107 : CPLDebug("GDAL_netCDF", "netCDFDataset::Create(%s, ...) done", pszFilename);
9515 : // Return same dataset.
9516 107 : return poDS;
9517 : }
9518 :
9519 : template <class T>
9520 93 : static CPLErr NCDFCopyBand(GDALRasterBand *poSrcBand, GDALRasterBand *poDstBand,
9521 : int nXSize, int nYSize, GDALProgressFunc pfnProgress,
9522 : void *pProgressData)
9523 : {
9524 93 : const GDALDataType eDT = poSrcBand->GetRasterDataType();
9525 93 : T *patScanline = static_cast<T *>(VSI_MALLOC2_VERBOSE(nXSize, sizeof(T)));
9526 93 : CPLErr eErr = patScanline ? CE_None : CE_Failure;
9527 :
9528 6330 : for (int iLine = 0; iLine < nYSize && eErr == CE_None; iLine++)
9529 : {
9530 6237 : eErr = poSrcBand->RasterIO(GF_Read, 0, iLine, nXSize, 1, patScanline,
9531 : nXSize, 1, eDT, 0, 0, nullptr);
9532 6237 : if (eErr != CE_None)
9533 : {
9534 0 : CPLDebug(
9535 : "GDAL_netCDF",
9536 : "NCDFCopyBand(), poSrcBand->RasterIO() returned error code %d",
9537 : eErr);
9538 : }
9539 : else
9540 : {
9541 6237 : eErr =
9542 : poDstBand->RasterIO(GF_Write, 0, iLine, nXSize, 1, patScanline,
9543 : nXSize, 1, eDT, 0, 0, nullptr);
9544 6237 : if (eErr != CE_None)
9545 0 : CPLDebug("GDAL_netCDF",
9546 : "NCDFCopyBand(), poDstBand->RasterIO() returned error "
9547 : "code %d",
9548 : eErr);
9549 : }
9550 :
9551 6237 : if (nYSize > 10 && (iLine % (nYSize / 10) == 1))
9552 : {
9553 277 : if (!pfnProgress(1.0 * iLine / nYSize, nullptr, pProgressData))
9554 : {
9555 0 : eErr = CE_Failure;
9556 0 : CPLError(CE_Failure, CPLE_UserInterrupt,
9557 : "User terminated CreateCopy()");
9558 : }
9559 : }
9560 : }
9561 :
9562 93 : CPLFree(patScanline);
9563 :
9564 93 : pfnProgress(1.0, nullptr, pProgressData);
9565 :
9566 93 : return eErr;
9567 : }
9568 :
9569 : /************************************************************************/
9570 : /* CreateCopy() */
9571 : /************************************************************************/
9572 :
9573 : GDALDataset *
9574 89 : netCDFDataset::CreateCopy(const char *pszFilename, GDALDataset *poSrcDS,
9575 : CPL_UNUSED int bStrict, CSLConstList papszOptions,
9576 : GDALProgressFunc pfnProgress, void *pProgressData)
9577 : {
9578 178 : CPLMutexHolderD(&hNCMutex);
9579 :
9580 89 : CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::CreateCopy(%s, ...)",
9581 : pszFilename);
9582 :
9583 89 : if (poSrcDS->GetRootGroup())
9584 : {
9585 10 : auto poDrv = GDALDriver::FromHandle(GDALGetDriverByName("netCDF"));
9586 10 : if (poDrv)
9587 : {
9588 10 : return poDrv->DefaultCreateCopy(pszFilename, poSrcDS, bStrict,
9589 : papszOptions, pfnProgress,
9590 10 : pProgressData);
9591 : }
9592 : }
9593 :
9594 79 : const int nBands = poSrcDS->GetRasterCount();
9595 79 : const int nXSize = poSrcDS->GetRasterXSize();
9596 79 : const int nYSize = poSrcDS->GetRasterYSize();
9597 79 : const char *pszWKT = poSrcDS->GetProjectionRef();
9598 :
9599 : // Check input bands for errors.
9600 79 : if (nBands == 0)
9601 : {
9602 1 : CPLError(CE_Failure, CPLE_NotSupported,
9603 : "NetCDF driver does not support "
9604 : "source dataset with zero band.");
9605 1 : return nullptr;
9606 : }
9607 :
9608 78 : GDALDataType eDT = GDT_Unknown;
9609 78 : GDALRasterBand *poSrcBand = nullptr;
9610 185 : for (int iBand = 1; iBand <= nBands; iBand++)
9611 : {
9612 111 : poSrcBand = poSrcDS->GetRasterBand(iBand);
9613 111 : eDT = poSrcBand->GetRasterDataType();
9614 111 : if (eDT == GDT_Unknown || GDALDataTypeIsComplex(eDT))
9615 : {
9616 4 : CPLError(CE_Failure, CPLE_NotSupported,
9617 : "NetCDF driver does not support source dataset with band "
9618 : "of complex type.");
9619 4 : return nullptr;
9620 : }
9621 : }
9622 :
9623 148 : CPLStringList aosBandNames;
9624 74 : if (const char *pszBandNames =
9625 74 : CSLFetchNameValue(papszOptions, "BAND_NAMES"))
9626 : {
9627 : aosBandNames =
9628 2 : CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);
9629 :
9630 2 : if (aosBandNames.Count() != nBands)
9631 : {
9632 1 : CPLError(CE_Failure, CPLE_OpenFailed,
9633 : "Attempted to create netCDF with %d bands but %d names "
9634 : "provided in BAND_NAMES.",
9635 : nBands, aosBandNames.Count());
9636 :
9637 1 : return nullptr;
9638 : }
9639 : }
9640 :
9641 73 : if (!pfnProgress(0.0, nullptr, pProgressData))
9642 0 : return nullptr;
9643 :
9644 : // Same as in Create().
9645 146 : CPLStringList aosOptions(CSLDuplicate(papszOptions));
9646 137 : if (aosOptions.FetchNameValue("FORMAT") == nullptr &&
9647 64 : (eDT == GDT_UInt16 || eDT == GDT_UInt32 || eDT == GDT_UInt64 ||
9648 : eDT == GDT_Int64))
9649 : {
9650 6 : CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
9651 6 : aosOptions.SetNameValue("FORMAT", "NC4");
9652 : }
9653 73 : netCDFDataset *poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize,
9654 73 : nBands, aosOptions.List());
9655 73 : if (!poDS)
9656 13 : return nullptr;
9657 :
9658 : // Copy global metadata.
9659 : // Add Conventions, GDAL info and history.
9660 60 : CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid, NC_GLOBAL, nullptr);
9661 60 : const bool bWriteGDALVersion = CPLTestBool(
9662 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
9663 60 : const bool bWriteGDALHistory = CPLTestBool(
9664 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
9665 60 : NCDFAddGDALHistory(
9666 : poDS->cdfid, pszFilename, bWriteGDALVersion, bWriteGDALHistory,
9667 60 : poSrcDS->GetMetadataItem("NC_GLOBAL#history"), "CreateCopy",
9668 60 : poSrcDS->GetMetadataItem("NC_GLOBAL#Conventions"));
9669 :
9670 60 : pfnProgress(0.1, nullptr, pProgressData);
9671 :
9672 : // Check for extra dimensions.
9673 60 : int nDim = 2;
9674 : CPLStringList aosExtraDimNames =
9675 120 : NCDFTokenizeArray(poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
9676 :
9677 60 : if (!aosExtraDimNames.empty())
9678 : {
9679 5 : size_t nDimSizeTot = 1;
9680 : // first make sure dimensions lengths compatible with band count
9681 : // for( int i=0; i<CSLCount(papszExtraDimNames ); i++ ) {
9682 13 : for (int i = aosExtraDimNames.size() - 1; i >= 0; i--)
9683 : {
9684 : char szTemp[NC_MAX_NAME + 32 + 1];
9685 8 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
9686 : aosExtraDimNames[i]);
9687 : const CPLStringList aosExtraDimValues =
9688 8 : NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
9689 8 : const size_t nDimSize = atol(aosExtraDimValues[0]);
9690 8 : nDimSizeTot *= nDimSize;
9691 : }
9692 5 : if (nDimSizeTot == (size_t)nBands)
9693 : {
9694 5 : nDim = 2 + aosExtraDimNames.size();
9695 : }
9696 : else
9697 : {
9698 : // if nBands != #bands computed raise a warning
9699 : // just issue a debug message, because it was probably intentional
9700 0 : CPLDebug("GDAL_netCDF",
9701 : "Warning: Number of bands (%d) is not compatible with "
9702 : "dimensions "
9703 : "(total=%ld names=%s)",
9704 : nBands, (long)nDimSizeTot,
9705 0 : poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
9706 0 : aosExtraDimNames.clear();
9707 : }
9708 : }
9709 :
9710 60 : int *panDimIds = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
9711 60 : int *panBandDimPos = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
9712 :
9713 : nc_type nVarType;
9714 60 : int *panBandZLev = nullptr;
9715 60 : int *panDimVarIds = nullptr;
9716 :
9717 60 : if (nDim > 2)
9718 : {
9719 5 : panBandZLev = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
9720 5 : panDimVarIds = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
9721 :
9722 : // Define all dims.
9723 13 : for (int i = aosExtraDimNames.size() - 1; i >= 0; i--)
9724 : {
9725 8 : poDS->papszDimName.AddString(aosExtraDimNames[i]);
9726 : char szTemp[NC_MAX_NAME + 32 + 1];
9727 8 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
9728 : aosExtraDimNames[i]);
9729 : const CPLStringList aosExtraDimValues =
9730 16 : NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
9731 : const int nDimSize =
9732 8 : aosExtraDimValues.empty() ? 0 : atoi(aosExtraDimValues[0]);
9733 : // nc_type is an enum in netcdf-3, needs casting.
9734 0 : nVarType = static_cast<nc_type>(
9735 8 : aosExtraDimValues.size() >= 2 ? atol(aosExtraDimValues[1]) : 0);
9736 8 : panBandZLev[i] = nDimSize;
9737 8 : panBandDimPos[i + 2] = i; // Save Position of ZDim.
9738 :
9739 : // Define dim.
9740 8 : int status = nc_def_dim(poDS->cdfid, aosExtraDimNames[i], nDimSize,
9741 8 : &(panDimIds[i]));
9742 8 : NCDF_ERR(status);
9743 :
9744 : // Define dim var.
9745 8 : int anDim[1] = {panDimIds[i]};
9746 8 : status = nc_def_var(poDS->cdfid, aosExtraDimNames[i], nVarType, 1,
9747 8 : anDim, &(panDimVarIds[i]));
9748 8 : NCDF_ERR(status);
9749 :
9750 : // Add dim metadata, using global var# items.
9751 8 : snprintf(szTemp, sizeof(szTemp), "%s#", aosExtraDimNames[i]);
9752 8 : CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid,
9753 8 : panDimVarIds[i], szTemp);
9754 : }
9755 : }
9756 :
9757 : // Copy GeoTransform and Projection.
9758 :
9759 : // Copy geolocation info.
9760 60 : CSLConstList papszGeolocationInfo = poSrcDS->GetMetadata("GEOLOCATION");
9761 60 : if (papszGeolocationInfo != nullptr)
9762 5 : poDS->GDALPamDataset::SetMetadata(papszGeolocationInfo, "GEOLOCATION");
9763 :
9764 : // Copy geotransform.
9765 60 : bool bGotGeoTransform = false;
9766 60 : GDALGeoTransform gt;
9767 60 : CPLErr eErr = poSrcDS->GetGeoTransform(gt);
9768 60 : if (eErr == CE_None)
9769 : {
9770 42 : poDS->SetGeoTransform(gt);
9771 : // Disable AddProjectionVars() from being called.
9772 42 : bGotGeoTransform = true;
9773 42 : poDS->m_bHasGeoTransform = false;
9774 : }
9775 :
9776 : // Copy projection.
9777 60 : void *pScaledProgress = nullptr;
9778 60 : if (bGotGeoTransform || (pszWKT && pszWKT[0] != 0))
9779 : {
9780 43 : poDS->SetProjection(pszWKT ? pszWKT : "");
9781 :
9782 : // Now we can call AddProjectionVars() directly.
9783 43 : poDS->m_bHasGeoTransform = bGotGeoTransform;
9784 43 : poDS->AddProjectionVars(true, nullptr, nullptr);
9785 : pScaledProgress =
9786 43 : GDALCreateScaledProgress(0.1, 0.25, pfnProgress, pProgressData);
9787 43 : poDS->AddProjectionVars(false, GDALScaledProgress, pScaledProgress);
9788 43 : GDALDestroyScaledProgress(pScaledProgress);
9789 : }
9790 : else
9791 : {
9792 17 : poDS->bBottomUp =
9793 17 : CPL_TO_BOOL(CSLFetchBoolean(papszOptions, "WRITE_BOTTOMUP", TRUE));
9794 17 : if (papszGeolocationInfo)
9795 : {
9796 4 : poDS->AddProjectionVars(true, nullptr, nullptr);
9797 4 : poDS->AddProjectionVars(false, nullptr, nullptr);
9798 : }
9799 : }
9800 :
9801 : // Save X,Y dim positions.
9802 60 : panDimIds[nDim - 1] = poDS->nXDimID;
9803 60 : panBandDimPos[0] = nDim - 1;
9804 60 : panDimIds[nDim - 2] = poDS->nYDimID;
9805 60 : panBandDimPos[1] = nDim - 2;
9806 :
9807 : // Write extra dim values - after projection for optimization.
9808 60 : if (nDim > 2)
9809 : {
9810 : // Make sure we are in data mode.
9811 5 : poDS->SetDefineMode(false);
9812 13 : for (int i = aosExtraDimNames.size() - 1; i >= 0; i--)
9813 : {
9814 : char szTemp[NC_MAX_NAME + 32 + 1];
9815 8 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_VALUES",
9816 : aosExtraDimNames[i]);
9817 8 : if (poSrcDS->GetMetadataItem(szTemp) != nullptr)
9818 : {
9819 8 : NCDFPut1DVar(poDS->cdfid, panDimVarIds[i],
9820 8 : poSrcDS->GetMetadataItem(szTemp));
9821 : }
9822 : }
9823 : }
9824 :
9825 60 : pfnProgress(0.25, nullptr, pProgressData);
9826 :
9827 : // Define Bands.
9828 60 : netCDFRasterBand *poBand = nullptr;
9829 60 : int nBandID = -1;
9830 :
9831 153 : for (int iBand = 1; iBand <= nBands; iBand++)
9832 : {
9833 93 : CPLDebug("GDAL_netCDF", "creating band # %d/%d nDim = %d", iBand,
9834 : nBands, nDim);
9835 :
9836 93 : poSrcBand = poSrcDS->GetRasterBand(iBand);
9837 93 : eDT = poSrcBand->GetRasterDataType();
9838 :
9839 : // Get var name from NETCDF_VARNAME.
9840 : const char *pszNETCDF_VARNAME =
9841 93 : poSrcBand->GetMetadataItem("NETCDF_VARNAME");
9842 : char szBandName[NC_MAX_NAME + 1];
9843 93 : if (!aosBandNames.empty())
9844 : {
9845 2 : snprintf(szBandName, sizeof(szBandName), "%s",
9846 : aosBandNames[iBand - 1]);
9847 : }
9848 91 : else if (pszNETCDF_VARNAME)
9849 : {
9850 32 : if (nBands > 1 && aosExtraDimNames.empty())
9851 0 : snprintf(szBandName, sizeof(szBandName), "%s%d",
9852 : pszNETCDF_VARNAME, iBand);
9853 : else
9854 32 : snprintf(szBandName, sizeof(szBandName), "%s",
9855 : pszNETCDF_VARNAME);
9856 : }
9857 : else
9858 : {
9859 59 : szBandName[0] = '\0';
9860 : }
9861 :
9862 : // Get long_name from <var>#long_name.
9863 93 : const char *pszLongName = "";
9864 93 : if (pszNETCDF_VARNAME)
9865 : {
9866 : pszLongName =
9867 64 : poSrcDS->GetMetadataItem(std::string(pszNETCDF_VARNAME)
9868 32 : .append("#")
9869 32 : .append(CF_LNG_NAME)
9870 32 : .c_str());
9871 32 : if (!pszLongName)
9872 25 : pszLongName = "";
9873 : }
9874 :
9875 93 : constexpr bool bSignedData = false;
9876 :
9877 93 : if (nDim > 2)
9878 27 : poBand = new netCDFRasterBand(
9879 27 : netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
9880 : bSignedData, szBandName, pszLongName, nBandID, nDim, iBand - 1,
9881 27 : panBandZLev, panBandDimPos, panDimIds);
9882 : else
9883 66 : poBand = new netCDFRasterBand(
9884 66 : netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
9885 66 : bSignedData, szBandName, pszLongName);
9886 :
9887 93 : poDS->SetBand(iBand, poBand);
9888 :
9889 : // Set nodata value, if any.
9890 93 : GDALCopyNoDataValue(poBand, poSrcBand);
9891 :
9892 : // Copy Metadata for band.
9893 93 : CopyMetadata(nullptr, poSrcDS->GetRasterBand(iBand), poBand,
9894 : poDS->cdfid, poBand->nZId);
9895 :
9896 : // If more than 2D pass the first band's netcdf var ID to subsequent
9897 : // bands.
9898 93 : if (nDim > 2)
9899 27 : nBandID = poBand->nZId;
9900 : }
9901 :
9902 : // Write projection variable to band variable.
9903 60 : poDS->AddGridMappingRef();
9904 :
9905 60 : pfnProgress(0.5, nullptr, pProgressData);
9906 :
9907 : // Write bands.
9908 :
9909 : // Make sure we are in data mode.
9910 60 : poDS->SetDefineMode(false);
9911 :
9912 60 : double dfTemp = 0.5;
9913 :
9914 60 : eErr = CE_None;
9915 :
9916 153 : for (int iBand = 1; iBand <= nBands && eErr == CE_None; iBand++)
9917 : {
9918 93 : const double dfTemp2 = dfTemp + 0.4 / nBands;
9919 93 : pScaledProgress = GDALCreateScaledProgress(dfTemp, dfTemp2, pfnProgress,
9920 : pProgressData);
9921 93 : dfTemp = dfTemp2;
9922 :
9923 93 : CPLDebug("GDAL_netCDF", "copying band data # %d/%d ", iBand, nBands);
9924 :
9925 93 : poSrcBand = poSrcDS->GetRasterBand(iBand);
9926 93 : eDT = poSrcBand->GetRasterDataType();
9927 :
9928 93 : GDALRasterBand *poDstBand = poDS->GetRasterBand(iBand);
9929 :
9930 : // Copy band data.
9931 93 : if (eDT == GDT_UInt8)
9932 : {
9933 53 : CPLDebug("GDAL_netCDF", "GByte Band#%d", iBand);
9934 53 : eErr = NCDFCopyBand<GByte>(poSrcBand, poDstBand, nXSize, nYSize,
9935 : GDALScaledProgress, pScaledProgress);
9936 : }
9937 40 : else if (eDT == GDT_Int8)
9938 : {
9939 1 : CPLDebug("GDAL_netCDF", "GInt8 Band#%d", iBand);
9940 1 : eErr = NCDFCopyBand<GInt8>(poSrcBand, poDstBand, nXSize, nYSize,
9941 : GDALScaledProgress, pScaledProgress);
9942 : }
9943 39 : else if (eDT == GDT_UInt16)
9944 : {
9945 2 : CPLDebug("GDAL_netCDF", "GUInt16 Band#%d", iBand);
9946 2 : eErr = NCDFCopyBand<GInt16>(poSrcBand, poDstBand, nXSize, nYSize,
9947 : GDALScaledProgress, pScaledProgress);
9948 : }
9949 37 : else if (eDT == GDT_Int16)
9950 : {
9951 5 : CPLDebug("GDAL_netCDF", "GInt16 Band#%d", iBand);
9952 5 : eErr = NCDFCopyBand<GUInt16>(poSrcBand, poDstBand, nXSize, nYSize,
9953 : GDALScaledProgress, pScaledProgress);
9954 : }
9955 32 : else if (eDT == GDT_UInt32)
9956 : {
9957 2 : CPLDebug("GDAL_netCDF", "GUInt32 Band#%d", iBand);
9958 2 : eErr = NCDFCopyBand<GUInt32>(poSrcBand, poDstBand, nXSize, nYSize,
9959 : GDALScaledProgress, pScaledProgress);
9960 : }
9961 30 : else if (eDT == GDT_Int32)
9962 : {
9963 18 : CPLDebug("GDAL_netCDF", "GInt32 Band#%d", iBand);
9964 18 : eErr = NCDFCopyBand<GInt32>(poSrcBand, poDstBand, nXSize, nYSize,
9965 : GDALScaledProgress, pScaledProgress);
9966 : }
9967 12 : else if (eDT == GDT_UInt64)
9968 : {
9969 2 : CPLDebug("GDAL_netCDF", "GUInt64 Band#%d", iBand);
9970 2 : eErr = NCDFCopyBand<std::uint64_t>(poSrcBand, poDstBand, nXSize,
9971 : nYSize, GDALScaledProgress,
9972 : pScaledProgress);
9973 : }
9974 10 : else if (eDT == GDT_Int64)
9975 : {
9976 2 : CPLDebug("GDAL_netCDF", "GInt64 Band#%d", iBand);
9977 : eErr =
9978 2 : NCDFCopyBand<std::int64_t>(poSrcBand, poDstBand, nXSize, nYSize,
9979 : GDALScaledProgress, pScaledProgress);
9980 : }
9981 8 : else if (eDT == GDT_Float32)
9982 : {
9983 6 : CPLDebug("GDAL_netCDF", "float Band#%d", iBand);
9984 6 : eErr = NCDFCopyBand<float>(poSrcBand, poDstBand, nXSize, nYSize,
9985 : GDALScaledProgress, pScaledProgress);
9986 : }
9987 2 : else if (eDT == GDT_Float64)
9988 : {
9989 2 : CPLDebug("GDAL_netCDF", "double Band#%d", iBand);
9990 2 : eErr = NCDFCopyBand<double>(poSrcBand, poDstBand, nXSize, nYSize,
9991 : GDALScaledProgress, pScaledProgress);
9992 : }
9993 : else
9994 : {
9995 0 : CPLError(CE_Failure, CPLE_NotSupported,
9996 : "The NetCDF driver does not support GDAL data type %d",
9997 : eDT);
9998 : }
9999 :
10000 93 : GDALDestroyScaledProgress(pScaledProgress);
10001 : }
10002 :
10003 60 : delete (poDS);
10004 :
10005 60 : CPLFree(panDimIds);
10006 60 : CPLFree(panBandDimPos);
10007 60 : CPLFree(panBandZLev);
10008 60 : CPLFree(panDimVarIds);
10009 :
10010 60 : if (eErr != CE_None)
10011 0 : return nullptr;
10012 :
10013 60 : pfnProgress(0.95, nullptr, pProgressData);
10014 :
10015 : // Re-open dataset so we can return it.
10016 120 : CPLStringList aosOpenOptions;
10017 60 : aosOpenOptions.AddString("VARIABLES_AS_BANDS=YES");
10018 60 : GDALOpenInfo oOpenInfo(pszFilename, GA_Update);
10019 60 : oOpenInfo.nOpenFlags = GDAL_OF_RASTER | GDAL_OF_UPDATE;
10020 60 : oOpenInfo.papszOpenOptions = aosOpenOptions.List();
10021 60 : auto poRetDS = Open(&oOpenInfo);
10022 :
10023 : // PAM cloning is disabled. See bug #4244.
10024 : // if( poDS )
10025 : // poDS->CloneInfo(poSrcDS, GCIF_PAM_DEFAULT);
10026 :
10027 60 : pfnProgress(1.0, nullptr, pProgressData);
10028 :
10029 60 : return poRetDS;
10030 : }
10031 :
10032 : // Note: some logic depends on bIsProjected and bIsGeoGraphic.
10033 : // May not be known when Create() is called, see AddProjectionVars().
10034 308 : void netCDFDataset::ProcessCreationOptions()
10035 : {
10036 : const char *pszConfig =
10037 308 : CSLFetchNameValue(papszCreationOptions, "CONFIG_FILE");
10038 308 : if (pszConfig != nullptr)
10039 : {
10040 4 : if (oWriterConfig.Parse(pszConfig))
10041 : {
10042 : // Override dataset creation options from the config file
10043 2 : for (const auto &[osName, osValue] :
10044 3 : oWriterConfig.m_oDatasetCreationOptions)
10045 : {
10046 1 : papszCreationOptions =
10047 1 : CSLSetNameValue(papszCreationOptions, osName, osValue);
10048 : }
10049 : }
10050 : }
10051 :
10052 : // File format.
10053 308 : eFormat = NCDF_FORMAT_NC;
10054 308 : const char *pszValue = CSLFetchNameValue(papszCreationOptions, "FORMAT");
10055 308 : if (pszValue != nullptr)
10056 : {
10057 144 : if (EQUAL(pszValue, "NC"))
10058 : {
10059 3 : eFormat = NCDF_FORMAT_NC;
10060 : }
10061 : #ifdef NETCDF_HAS_NC2
10062 141 : else if (EQUAL(pszValue, "NC2"))
10063 : {
10064 1 : eFormat = NCDF_FORMAT_NC2;
10065 : }
10066 : #endif
10067 140 : else if (EQUAL(pszValue, "NC4"))
10068 : {
10069 136 : eFormat = NCDF_FORMAT_NC4;
10070 : }
10071 4 : else if (EQUAL(pszValue, "NC4C"))
10072 : {
10073 4 : eFormat = NCDF_FORMAT_NC4C;
10074 : }
10075 : else
10076 : {
10077 0 : CPLError(CE_Failure, CPLE_NotSupported,
10078 : "FORMAT=%s in not supported, using the default NC format.",
10079 : pszValue);
10080 : }
10081 : }
10082 :
10083 : // COMPRESS option.
10084 308 : pszValue = CSLFetchNameValue(papszCreationOptions, "COMPRESS");
10085 308 : if (pszValue != nullptr)
10086 : {
10087 3 : if (EQUAL(pszValue, "NONE"))
10088 : {
10089 1 : eCompress = NCDF_COMPRESS_NONE;
10090 : }
10091 2 : else if (EQUAL(pszValue, "DEFLATE"))
10092 : {
10093 2 : eCompress = NCDF_COMPRESS_DEFLATE;
10094 2 : if (!((eFormat == NCDF_FORMAT_NC4) ||
10095 2 : (eFormat == NCDF_FORMAT_NC4C)))
10096 : {
10097 1 : CPLError(CE_Warning, CPLE_IllegalArg,
10098 : "NOTICE: Format set to NC4C because compression is "
10099 : "set to DEFLATE.");
10100 1 : eFormat = NCDF_FORMAT_NC4C;
10101 : }
10102 : }
10103 : else
10104 : {
10105 0 : CPLError(CE_Failure, CPLE_NotSupported,
10106 : "COMPRESS=%s is not supported.", pszValue);
10107 : }
10108 : }
10109 :
10110 : // ZLEVEL option.
10111 308 : pszValue = CSLFetchNameValue(papszCreationOptions, "ZLEVEL");
10112 308 : if (pszValue != nullptr)
10113 : {
10114 1 : nZLevel = atoi(pszValue);
10115 1 : if (!(nZLevel >= 1 && nZLevel <= 9))
10116 : {
10117 0 : CPLError(CE_Warning, CPLE_IllegalArg,
10118 : "ZLEVEL=%s value not recognised, ignoring.", pszValue);
10119 0 : nZLevel = NCDF_DEFLATE_LEVEL;
10120 : }
10121 : }
10122 :
10123 : // CHUNKING option.
10124 308 : bChunking =
10125 308 : CPL_TO_BOOL(CSLFetchBoolean(papszCreationOptions, "CHUNKING", TRUE));
10126 :
10127 : // MULTIPLE_LAYERS option.
10128 : const char *pszMultipleLayerBehavior =
10129 308 : CSLFetchNameValueDef(papszCreationOptions, "MULTIPLE_LAYERS", "NO");
10130 616 : const char *pszGeometryEnc = CSLFetchNameValueDef(
10131 308 : papszCreationOptions, "GEOMETRY_ENCODING", "CF_1.8");
10132 308 : if (EQUAL(pszMultipleLayerBehavior, "NO") ||
10133 4 : EQUAL(pszGeometryEnc, "CF_1.8"))
10134 : {
10135 304 : eMultipleLayerBehavior = SINGLE_LAYER;
10136 : }
10137 4 : else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_FILES"))
10138 : {
10139 3 : eMultipleLayerBehavior = SEPARATE_FILES;
10140 : }
10141 1 : else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_GROUPS"))
10142 : {
10143 1 : if (eFormat == NCDF_FORMAT_NC4)
10144 : {
10145 1 : eMultipleLayerBehavior = SEPARATE_GROUPS;
10146 : }
10147 : else
10148 : {
10149 0 : CPLError(CE_Warning, CPLE_IllegalArg,
10150 : "MULTIPLE_LAYERS=%s is recognised only with FORMAT=NC4",
10151 : pszMultipleLayerBehavior);
10152 : }
10153 : }
10154 : else
10155 : {
10156 0 : CPLError(CE_Warning, CPLE_IllegalArg,
10157 : "MULTIPLE_LAYERS=%s not recognised", pszMultipleLayerBehavior);
10158 : }
10159 :
10160 : // Set nCreateMode based on eFormat.
10161 308 : switch (eFormat)
10162 : {
10163 : #ifdef NETCDF_HAS_NC2
10164 1 : case NCDF_FORMAT_NC2:
10165 1 : nCreateMode = NC_CLOBBER | NC_64BIT_OFFSET;
10166 1 : break;
10167 : #endif
10168 136 : case NCDF_FORMAT_NC4:
10169 136 : nCreateMode = NC_CLOBBER | NC_NETCDF4;
10170 136 : break;
10171 5 : case NCDF_FORMAT_NC4C:
10172 5 : nCreateMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
10173 5 : break;
10174 166 : case NCDF_FORMAT_NC:
10175 : default:
10176 166 : nCreateMode = NC_CLOBBER;
10177 166 : break;
10178 : }
10179 :
10180 308 : CPLDebug("GDAL_netCDF", "file options: format=%d compress=%d zlevel=%d",
10181 308 : eFormat, eCompress, nZLevel);
10182 308 : }
10183 :
10184 280 : int netCDFDataset::DefVarDeflate(int nVarId, bool bChunkingArg) const
10185 : {
10186 280 : if (eCompress == NCDF_COMPRESS_DEFLATE)
10187 : {
10188 : // Must set chunk size to avoid huge performance hit (set
10189 : // bChunkingArg=TRUE)
10190 : // perhaps another solution it to change the chunk cache?
10191 : // http://www.unidata.ucar.edu/software/netcdf/docs/netcdf.html#Chunk-Cache
10192 : // TODO: make sure this is okay.
10193 2 : CPLDebug("GDAL_netCDF", "DefVarDeflate(%d, %d) nZlevel=%d", nVarId,
10194 2 : static_cast<int>(bChunkingArg), nZLevel);
10195 :
10196 2 : int status = nc_def_var_deflate(cdfid, nVarId, 1, 1, nZLevel);
10197 2 : NCDF_ERR(status);
10198 :
10199 2 : if (status == NC_NOERR && bChunkingArg && bChunking)
10200 : {
10201 : // set chunking to be 1 for all dims, except X dim
10202 : // size_t chunksize[] = { 1, (size_t)nRasterXSize };
10203 : size_t chunksize[MAX_NC_DIMS];
10204 : int nd;
10205 2 : nc_inq_varndims(cdfid, nVarId, &nd);
10206 2 : chunksize[0] = (size_t)1;
10207 2 : chunksize[1] = (size_t)1;
10208 2 : for (int i = 2; i < nd; i++)
10209 0 : chunksize[i] = (size_t)1;
10210 2 : chunksize[nd - 1] = (size_t)nRasterXSize;
10211 :
10212 : // Config options just for testing purposes
10213 : const char *pszBlockXSize =
10214 2 : CPLGetConfigOption("BLOCKXSIZE", nullptr);
10215 2 : if (pszBlockXSize)
10216 0 : chunksize[nd - 1] = (size_t)atoi(pszBlockXSize);
10217 :
10218 : const char *pszBlockYSize =
10219 2 : CPLGetConfigOption("BLOCKYSIZE", nullptr);
10220 2 : if (nd >= 2 && pszBlockYSize)
10221 0 : chunksize[nd - 2] = (size_t)atoi(pszBlockYSize);
10222 :
10223 2 : CPLDebug("GDAL_netCDF",
10224 : "DefVarDeflate() chunksize={%ld, %ld} chunkX=%ld nd=%d",
10225 2 : (long)chunksize[0], (long)chunksize[1],
10226 2 : (long)chunksize[nd - 1], nd);
10227 : #ifdef NCDF_DEBUG
10228 : for (int i = 0; i < nd; i++)
10229 : CPLDebug("GDAL_netCDF", "DefVarDeflate() chunk[%d]=%ld", i,
10230 : chunksize[i]);
10231 : #endif
10232 :
10233 2 : status = nc_def_var_chunking(cdfid, nVarId, NC_CHUNKED, chunksize);
10234 2 : NCDF_ERR(status);
10235 : }
10236 : else
10237 : {
10238 0 : CPLDebug("GDAL_netCDF", "chunksize not set");
10239 : }
10240 2 : return status;
10241 : }
10242 278 : return NC_NOERR;
10243 : }
10244 :
10245 : /************************************************************************/
10246 : /* NCDFUnloadDriver() */
10247 : /************************************************************************/
10248 :
10249 8 : static void NCDFUnloadDriver(CPL_UNUSED GDALDriver *poDriver)
10250 : {
10251 8 : if (hNCMutex != nullptr)
10252 4 : CPLDestroyMutex(hNCMutex);
10253 8 : hNCMutex = nullptr;
10254 8 : }
10255 :
10256 : /************************************************************************/
10257 : /* GDALRegister_netCDF() */
10258 : /************************************************************************/
10259 :
10260 : class GDALnetCDFDriver final : public GDALDriver
10261 : {
10262 : public:
10263 19 : GDALnetCDFDriver() = default;
10264 :
10265 : const char *GetMetadataItem(const char *pszName,
10266 : const char *pszDomain) override;
10267 :
10268 93 : CSLConstList GetMetadata(const char *pszDomain) override
10269 : {
10270 186 : std::lock_guard oLock(m_oMutex);
10271 93 : InitializeDCAPVirtualIO();
10272 186 : return GDALDriver::GetMetadata(pszDomain);
10273 : }
10274 :
10275 : private:
10276 : std::recursive_mutex m_oMutex{};
10277 : bool m_bInitialized = false;
10278 :
10279 106 : void InitializeDCAPVirtualIO()
10280 : {
10281 106 : if (!m_bInitialized)
10282 : {
10283 12 : m_bInitialized = true;
10284 :
10285 : #ifdef ENABLE_UFFD
10286 12 : if (CPLIsUserFaultMappingSupported())
10287 : {
10288 12 : SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
10289 : }
10290 : #endif
10291 : }
10292 106 : }
10293 : };
10294 :
10295 1412 : const char *GDALnetCDFDriver::GetMetadataItem(const char *pszName,
10296 : const char *pszDomain)
10297 : {
10298 2824 : std::lock_guard oLock(m_oMutex);
10299 1412 : if (EQUAL(pszName, GDAL_DCAP_VIRTUALIO))
10300 : {
10301 13 : InitializeDCAPVirtualIO();
10302 : }
10303 2824 : return GDALDriver::GetMetadataItem(pszName, pszDomain);
10304 : }
10305 :
10306 19 : void GDALRegister_netCDF()
10307 :
10308 : {
10309 19 : if (!GDAL_CHECK_VERSION("netCDF driver"))
10310 0 : return;
10311 :
10312 19 : if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
10313 0 : return;
10314 :
10315 19 : GDALDriver *poDriver = new GDALnetCDFDriver();
10316 19 : netCDFDriverSetCommonMetadata(poDriver);
10317 :
10318 19 : poDriver->SetMetadataItem("NETCDF_CONVENTIONS",
10319 19 : GDAL_DEFAULT_NCDF_CONVENTIONS);
10320 19 : poDriver->SetMetadataItem("NETCDF_VERSION", nc_inq_libvers());
10321 :
10322 : // Set pfns and register driver.
10323 19 : poDriver->pfnOpen = netCDFDataset::Open;
10324 19 : poDriver->pfnCreateCopy = netCDFDataset::CreateCopy;
10325 19 : poDriver->pfnCreate = netCDFDataset::Create;
10326 19 : poDriver->pfnCreateMultiDimensional = netCDFDataset::CreateMultiDimensional;
10327 19 : poDriver->pfnUnloadDriver = NCDFUnloadDriver;
10328 :
10329 19 : GetGDALDriverManager()->RegisterDriver(poDriver);
10330 : }
10331 :
10332 : /************************************************************************/
10333 : /* New functions */
10334 : /************************************************************************/
10335 :
10336 : /* Test for GDAL version string >= target */
10337 245 : static bool NCDFIsGDALVersionGTE(const char *pszVersion, int nTarget)
10338 : {
10339 :
10340 : // Valid strings are "GDAL 1.9dev, released 2011/01/18" and "GDAL 1.8.1 ".
10341 245 : if (pszVersion == nullptr || EQUAL(pszVersion, ""))
10342 0 : return false;
10343 245 : else if (!STARTS_WITH_CI(pszVersion, "GDAL "))
10344 0 : return false;
10345 : // 2.0dev of 2011/12/29 has been later renamed as 1.10dev.
10346 245 : else if (EQUAL("GDAL 2.0dev, released 2011/12/29", pszVersion))
10347 0 : return nTarget <= GDAL_COMPUTE_VERSION(1, 10, 0);
10348 245 : else if (STARTS_WITH_CI(pszVersion, "GDAL 1.9dev"))
10349 2 : return nTarget <= 1900;
10350 243 : else if (STARTS_WITH_CI(pszVersion, "GDAL 1.8dev"))
10351 0 : return nTarget <= 1800;
10352 :
10353 243 : const CPLStringList aosTokens(CSLTokenizeString2(pszVersion + 5, ".", 0));
10354 :
10355 243 : int nVersions[] = {0, 0, 0, 0};
10356 972 : for (int iToken = 0; iToken < std::min(4, aosTokens.size()); iToken++)
10357 : {
10358 729 : nVersions[iToken] = atoi(aosTokens[iToken]);
10359 729 : if (nVersions[iToken] < 0)
10360 0 : nVersions[iToken] = 0;
10361 729 : else if (nVersions[iToken] > 99)
10362 0 : nVersions[iToken] = 99;
10363 : }
10364 :
10365 243 : int nVersion = 0;
10366 243 : if (nVersions[0] > 1 || nVersions[1] >= 10)
10367 243 : nVersion =
10368 243 : GDAL_COMPUTE_VERSION(nVersions[0], nVersions[1], nVersions[2]);
10369 : else
10370 0 : nVersion = nVersions[0] * 1000 + nVersions[1] * 100 +
10371 0 : nVersions[2] * 10 + nVersions[3];
10372 :
10373 243 : return nTarget <= nVersion;
10374 : }
10375 :
10376 : // Add Conventions, GDAL version and history.
10377 169 : static void NCDFAddGDALHistory(int fpImage, const char *pszFilename,
10378 : bool bWriteGDALVersion, bool bWriteGDALHistory,
10379 : const char *pszOldHist,
10380 : const char *pszFunctionName,
10381 : const char *pszCFVersion)
10382 : {
10383 169 : if (pszCFVersion == nullptr)
10384 : {
10385 44 : pszCFVersion = GDAL_DEFAULT_NCDF_CONVENTIONS;
10386 : }
10387 169 : int status = nc_put_att_text(fpImage, NC_GLOBAL, "Conventions",
10388 : strlen(pszCFVersion), pszCFVersion);
10389 169 : NCDF_ERR(status);
10390 :
10391 169 : if (bWriteGDALVersion)
10392 : {
10393 167 : const char *pszNCDF_GDAL = GDALVersionInfo("--version");
10394 167 : status = nc_put_att_text(fpImage, NC_GLOBAL, "GDAL",
10395 : strlen(pszNCDF_GDAL), pszNCDF_GDAL);
10396 167 : NCDF_ERR(status);
10397 : }
10398 :
10399 169 : if (bWriteGDALHistory)
10400 : {
10401 : // Add history.
10402 334 : CPLString osTmp;
10403 : #ifdef GDAL_SET_CMD_LINE_DEFINED_TMP
10404 : if (!EQUAL(GDALGetCmdLine(), ""))
10405 : osTmp = GDALGetCmdLine();
10406 : else
10407 : osTmp =
10408 : CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
10409 : #else
10410 167 : osTmp = CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
10411 : #endif
10412 :
10413 167 : NCDFAddHistory(fpImage, osTmp.c_str(), pszOldHist);
10414 : }
10415 2 : else if (pszOldHist != nullptr)
10416 : {
10417 0 : status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
10418 : strlen(pszOldHist), pszOldHist);
10419 0 : NCDF_ERR(status);
10420 : }
10421 169 : }
10422 :
10423 : // Code taken from cdo and libcdi, used for writing the history attribute.
10424 :
10425 : // void cdoDefHistory(int fileID, char *histstring)
10426 167 : static void NCDFAddHistory(int fpImage, const char *pszAddHist,
10427 : const char *pszOldHist)
10428 : {
10429 : // Check pszOldHist - as if there was no previous history, it will be
10430 : // a null pointer - if so set as empty.
10431 167 : if (nullptr == pszOldHist)
10432 : {
10433 55 : pszOldHist = "";
10434 : }
10435 :
10436 : char strtime[32];
10437 167 : strtime[0] = '\0';
10438 :
10439 167 : time_t tp = time(nullptr);
10440 167 : if (tp != -1)
10441 : {
10442 : struct tm ltime;
10443 167 : VSILocalTime(&tp, <ime);
10444 167 : (void)strftime(strtime, sizeof(strtime),
10445 : "%a %b %d %H:%M:%S %Y: ", <ime);
10446 : }
10447 :
10448 : // status = nc_get_att_text(fpImage, NC_GLOBAL,
10449 : // "history", pszOldHist);
10450 : // printf("status: %d pszOldHist: [%s]\n",status,pszOldHist);
10451 :
10452 167 : size_t nNewHistSize =
10453 167 : strlen(pszOldHist) + strlen(strtime) + strlen(pszAddHist) + 1 + 1;
10454 : char *pszNewHist =
10455 167 : static_cast<char *>(CPLMalloc(nNewHistSize * sizeof(char)));
10456 :
10457 167 : strcpy(pszNewHist, strtime);
10458 167 : strcat(pszNewHist, pszAddHist);
10459 :
10460 : // int disableHistory = FALSE;
10461 : // if( !disableHistory )
10462 : {
10463 167 : if (!EQUAL(pszOldHist, ""))
10464 3 : strcat(pszNewHist, "\n");
10465 167 : strcat(pszNewHist, pszOldHist);
10466 : }
10467 :
10468 167 : const int status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
10469 : strlen(pszNewHist), pszNewHist);
10470 167 : NCDF_ERR(status);
10471 :
10472 167 : CPLFree(pszNewHist);
10473 167 : }
10474 :
10475 6476 : static CPLErr NCDFSafeStrcat(char **ppszDest, const char *pszSrc,
10476 : size_t *nDestSize)
10477 : {
10478 : /* Reallocate the data string until the content fits */
10479 6476 : while (*nDestSize < (strlen(*ppszDest) + strlen(pszSrc) + 1))
10480 : {
10481 411 : (*nDestSize) *= 2;
10482 411 : *ppszDest = static_cast<char *>(
10483 411 : CPLRealloc(reinterpret_cast<void *>(*ppszDest), *nDestSize));
10484 : #ifdef NCDF_DEBUG
10485 : CPLDebug("GDAL_netCDF", "NCDFSafeStrcat() resized str from %ld to %ld",
10486 : (*nDestSize) / 2, *nDestSize);
10487 : #endif
10488 : }
10489 6065 : strcat(*ppszDest, pszSrc);
10490 :
10491 6065 : return CE_None;
10492 : }
10493 :
10494 : /* helper function for NCDFGetAttr() */
10495 : /* if pdfValue != nullptr, sets *pdfValue to first value returned */
10496 : /* if ppszValue != nullptr, sets *ppszValue with all attribute values */
10497 : /* *ppszValue is the responsibility of the caller and must be freed */
10498 67877 : static CPLErr NCDFGetAttr1(int nCdfId, int nVarId, const char *pszAttrName,
10499 : double *pdfValue, char **ppszValue)
10500 : {
10501 67877 : nc_type nAttrType = NC_NAT;
10502 67877 : size_t nAttrLen = 0;
10503 :
10504 67877 : if (ppszValue)
10505 66719 : *ppszValue = nullptr;
10506 :
10507 67877 : int status = nc_inq_att(nCdfId, nVarId, pszAttrName, &nAttrType, &nAttrLen);
10508 67877 : if (status != NC_NOERR)
10509 36439 : return CE_Failure;
10510 :
10511 : #ifdef NCDF_DEBUG
10512 : CPLDebug("GDAL_netCDF", "NCDFGetAttr1(%s) len=%ld type=%d", pszAttrName,
10513 : nAttrLen, nAttrType);
10514 : #endif
10515 31438 : if (nAttrLen == 0 && nAttrType != NC_CHAR)
10516 1 : return CE_Failure;
10517 :
10518 : /* Allocate guaranteed minimum size (use 10 or 20 if not a string) */
10519 31437 : size_t nAttrValueSize = nAttrLen + 1;
10520 31437 : if (nAttrType != NC_CHAR && nAttrValueSize < 10)
10521 3484 : nAttrValueSize = 10;
10522 31437 : if (nAttrType == NC_DOUBLE && nAttrValueSize < 20)
10523 1658 : nAttrValueSize = 20;
10524 31437 : if (nAttrType == NC_INT64 && nAttrValueSize < 20)
10525 22 : nAttrValueSize = 22;
10526 : char *pszAttrValue =
10527 31437 : static_cast<char *>(CPLCalloc(nAttrValueSize, sizeof(char)));
10528 31437 : *pszAttrValue = '\0';
10529 :
10530 31437 : if (nAttrLen > 1 && nAttrType != NC_CHAR)
10531 614 : NCDFSafeStrcat(&pszAttrValue, "{", &nAttrValueSize);
10532 :
10533 31437 : double dfValue = 0.0;
10534 31437 : size_t m = 0;
10535 : char szTemp[256];
10536 31437 : bool bSetDoubleFromStr = false;
10537 :
10538 31437 : switch (nAttrType)
10539 : {
10540 27951 : case NC_CHAR:
10541 27951 : CPL_IGNORE_RET_VAL(
10542 27951 : nc_get_att_text(nCdfId, nVarId, pszAttrName, pszAttrValue));
10543 27951 : pszAttrValue[nAttrLen] = '\0';
10544 27951 : bSetDoubleFromStr = true;
10545 27951 : dfValue = 0.0;
10546 27951 : break;
10547 94 : case NC_BYTE:
10548 : {
10549 : signed char *pscTemp = static_cast<signed char *>(
10550 94 : CPLCalloc(nAttrLen, sizeof(signed char)));
10551 94 : nc_get_att_schar(nCdfId, nVarId, pszAttrName, pscTemp);
10552 94 : dfValue = static_cast<double>(pscTemp[0]);
10553 94 : if (nAttrLen > 1)
10554 : {
10555 24 : for (m = 0; m < nAttrLen - 1; m++)
10556 : {
10557 13 : snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
10558 13 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10559 : }
10560 : }
10561 94 : snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
10562 94 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10563 94 : CPLFree(pscTemp);
10564 94 : break;
10565 : }
10566 499 : case NC_SHORT:
10567 : {
10568 : short *psTemp =
10569 499 : static_cast<short *>(CPLCalloc(nAttrLen, sizeof(short)));
10570 499 : nc_get_att_short(nCdfId, nVarId, pszAttrName, psTemp);
10571 499 : dfValue = static_cast<double>(psTemp[0]);
10572 499 : if (nAttrLen > 1)
10573 : {
10574 792 : for (m = 0; m < nAttrLen - 1; m++)
10575 : {
10576 396 : snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
10577 396 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10578 : }
10579 : }
10580 499 : snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
10581 499 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10582 499 : CPLFree(psTemp);
10583 499 : break;
10584 : }
10585 528 : case NC_INT:
10586 : {
10587 528 : int *pnTemp = static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
10588 528 : nc_get_att_int(nCdfId, nVarId, pszAttrName, pnTemp);
10589 528 : dfValue = static_cast<double>(pnTemp[0]);
10590 528 : if (nAttrLen > 1)
10591 : {
10592 218 : for (m = 0; m < nAttrLen - 1; m++)
10593 : {
10594 139 : snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
10595 139 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10596 : }
10597 : }
10598 528 : snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
10599 528 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10600 528 : CPLFree(pnTemp);
10601 528 : break;
10602 : }
10603 395 : case NC_FLOAT:
10604 : {
10605 : float *pfTemp =
10606 395 : static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
10607 395 : nc_get_att_float(nCdfId, nVarId, pszAttrName, pfTemp);
10608 395 : dfValue = static_cast<double>(pfTemp[0]);
10609 395 : if (nAttrLen > 1)
10610 : {
10611 60 : for (m = 0; m < nAttrLen - 1; m++)
10612 : {
10613 30 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
10614 30 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10615 : }
10616 : }
10617 395 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
10618 395 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10619 395 : CPLFree(pfTemp);
10620 395 : break;
10621 : }
10622 1658 : case NC_DOUBLE:
10623 : {
10624 : double *pdfTemp =
10625 1658 : static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
10626 1658 : nc_get_att_double(nCdfId, nVarId, pszAttrName, pdfTemp);
10627 1658 : dfValue = pdfTemp[0];
10628 1658 : if (nAttrLen > 1)
10629 : {
10630 166 : for (m = 0; m < nAttrLen - 1; m++)
10631 : {
10632 90 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
10633 90 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10634 : }
10635 : }
10636 1658 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
10637 1658 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10638 1658 : CPLFree(pdfTemp);
10639 1658 : break;
10640 : }
10641 167 : case NC_STRING:
10642 : {
10643 : char **ppszTemp =
10644 167 : static_cast<char **>(CPLCalloc(nAttrLen, sizeof(char *)));
10645 167 : nc_get_att_string(nCdfId, nVarId, pszAttrName, ppszTemp);
10646 167 : bSetDoubleFromStr = true;
10647 167 : dfValue = 0.0;
10648 167 : if (nAttrLen > 1)
10649 : {
10650 19 : for (m = 0; m < nAttrLen - 1; m++)
10651 : {
10652 12 : NCDFSafeStrcat(&pszAttrValue,
10653 12 : ppszTemp[m] ? ppszTemp[m] : "{NULL}",
10654 : &nAttrValueSize);
10655 12 : NCDFSafeStrcat(&pszAttrValue, ",", &nAttrValueSize);
10656 : }
10657 : }
10658 167 : NCDFSafeStrcat(&pszAttrValue, ppszTemp[m] ? ppszTemp[m] : "{NULL}",
10659 : &nAttrValueSize);
10660 167 : nc_free_string(nAttrLen, ppszTemp);
10661 167 : CPLFree(ppszTemp);
10662 167 : break;
10663 : }
10664 28 : case NC_UBYTE:
10665 : {
10666 : unsigned char *pucTemp = static_cast<unsigned char *>(
10667 28 : CPLCalloc(nAttrLen, sizeof(unsigned char)));
10668 28 : nc_get_att_uchar(nCdfId, nVarId, pszAttrName, pucTemp);
10669 28 : dfValue = static_cast<double>(pucTemp[0]);
10670 28 : if (nAttrLen > 1)
10671 : {
10672 0 : for (m = 0; m < nAttrLen - 1; m++)
10673 : {
10674 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
10675 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10676 : }
10677 : }
10678 28 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
10679 28 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10680 28 : CPLFree(pucTemp);
10681 28 : break;
10682 : }
10683 26 : case NC_USHORT:
10684 : {
10685 : unsigned short *pusTemp = static_cast<unsigned short *>(
10686 26 : CPLCalloc(nAttrLen, sizeof(unsigned short)));
10687 26 : nc_get_att_ushort(nCdfId, nVarId, pszAttrName, pusTemp);
10688 26 : dfValue = static_cast<double>(pusTemp[0]);
10689 26 : if (nAttrLen > 1)
10690 : {
10691 10 : for (m = 0; m < nAttrLen - 1; m++)
10692 : {
10693 5 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
10694 5 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10695 : }
10696 : }
10697 26 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
10698 26 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10699 26 : CPLFree(pusTemp);
10700 26 : break;
10701 : }
10702 21 : case NC_UINT:
10703 : {
10704 : unsigned int *punTemp =
10705 21 : static_cast<unsigned int *>(CPLCalloc(nAttrLen, sizeof(int)));
10706 21 : nc_get_att_uint(nCdfId, nVarId, pszAttrName, punTemp);
10707 21 : dfValue = static_cast<double>(punTemp[0]);
10708 21 : if (nAttrLen > 1)
10709 : {
10710 0 : for (m = 0; m < nAttrLen - 1; m++)
10711 : {
10712 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
10713 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10714 : }
10715 : }
10716 21 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
10717 21 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10718 21 : CPLFree(punTemp);
10719 21 : break;
10720 : }
10721 22 : case NC_INT64:
10722 : {
10723 : GIntBig *panTemp =
10724 22 : static_cast<GIntBig *>(CPLCalloc(nAttrLen, sizeof(GIntBig)));
10725 22 : nc_get_att_longlong(nCdfId, nVarId, pszAttrName, panTemp);
10726 22 : dfValue = static_cast<double>(panTemp[0]);
10727 22 : if (nAttrLen > 1)
10728 : {
10729 0 : for (m = 0; m < nAttrLen - 1; m++)
10730 : {
10731 0 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",",
10732 0 : panTemp[m]);
10733 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10734 : }
10735 : }
10736 22 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, panTemp[m]);
10737 22 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10738 22 : CPLFree(panTemp);
10739 22 : break;
10740 : }
10741 22 : case NC_UINT64:
10742 : {
10743 : GUIntBig *panTemp =
10744 22 : static_cast<GUIntBig *>(CPLCalloc(nAttrLen, sizeof(GUIntBig)));
10745 22 : nc_get_att_ulonglong(nCdfId, nVarId, pszAttrName, panTemp);
10746 22 : dfValue = static_cast<double>(panTemp[0]);
10747 22 : if (nAttrLen > 1)
10748 : {
10749 0 : for (m = 0; m < nAttrLen - 1; m++)
10750 : {
10751 0 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",",
10752 0 : panTemp[m]);
10753 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10754 : }
10755 : }
10756 22 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, panTemp[m]);
10757 22 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10758 22 : CPLFree(panTemp);
10759 22 : break;
10760 : }
10761 26 : default:
10762 26 : CPLDebug("GDAL_netCDF",
10763 : "NCDFGetAttr unsupported type %d for attribute %s",
10764 : nAttrType, pszAttrName);
10765 26 : break;
10766 : }
10767 :
10768 31437 : if (nAttrLen > 1 && nAttrType != NC_CHAR)
10769 614 : NCDFSafeStrcat(&pszAttrValue, "}", &nAttrValueSize);
10770 :
10771 31437 : if (bSetDoubleFromStr)
10772 : {
10773 28118 : if (CPLGetValueType(pszAttrValue) == CPL_VALUE_STRING)
10774 : {
10775 27936 : if (ppszValue == nullptr && pdfValue != nullptr)
10776 : {
10777 1 : CPLFree(pszAttrValue);
10778 1 : return CE_Failure;
10779 : }
10780 : }
10781 28117 : dfValue = CPLAtof(pszAttrValue);
10782 : }
10783 :
10784 : /* set return values */
10785 31436 : if (ppszValue)
10786 31123 : *ppszValue = pszAttrValue;
10787 : else
10788 313 : CPLFree(pszAttrValue);
10789 :
10790 31436 : if (pdfValue)
10791 313 : *pdfValue = dfValue;
10792 :
10793 31436 : return CE_None;
10794 : }
10795 :
10796 : /* sets pdfValue to first value found */
10797 1158 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
10798 : double *pdfValue)
10799 : {
10800 1158 : return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, pdfValue, nullptr);
10801 : }
10802 :
10803 : /* pszValue is the responsibility of the caller and must be freed */
10804 66719 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
10805 : char **pszValue)
10806 : {
10807 66719 : return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, nullptr, pszValue);
10808 : }
10809 :
10810 : /* By default write NC_CHAR, but detect for int/float/double and */
10811 : /* NC4 string arrays */
10812 108 : static CPLErr NCDFPutAttr(int nCdfId, int nVarId, const char *pszAttrName,
10813 : const char *pszValue)
10814 : {
10815 108 : int status = 0;
10816 108 : char *pszTemp = nullptr;
10817 :
10818 : /* get the attribute values as tokens */
10819 216 : CPLStringList aosValues = NCDFTokenizeArray(pszValue);
10820 108 : if (aosValues.empty())
10821 0 : return CE_Failure;
10822 :
10823 108 : size_t nAttrLen = aosValues.size();
10824 :
10825 : /* first detect type */
10826 108 : nc_type nAttrType = NC_CHAR;
10827 108 : nc_type nTmpAttrType = NC_CHAR;
10828 229 : for (size_t i = 0; i < nAttrLen; i++)
10829 : {
10830 121 : nTmpAttrType = NC_CHAR;
10831 121 : bool bFoundType = false;
10832 121 : errno = 0;
10833 121 : int nValue = static_cast<int>(strtol(aosValues[i], &pszTemp, 10));
10834 : /* test for int */
10835 : /* TODO test for Byte and short - can this be done safely? */
10836 121 : if (errno == 0 && aosValues[i] != pszTemp && *pszTemp == 0)
10837 : {
10838 : char szTemp[256];
10839 19 : CPLsnprintf(szTemp, sizeof(szTemp), "%d", nValue);
10840 19 : if (EQUAL(szTemp, aosValues[i]))
10841 : {
10842 19 : bFoundType = true;
10843 19 : nTmpAttrType = NC_INT;
10844 : }
10845 : else
10846 : {
10847 : unsigned int unValue = static_cast<unsigned int>(
10848 0 : strtoul(aosValues[i], &pszTemp, 10));
10849 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", unValue);
10850 0 : if (EQUAL(szTemp, aosValues[i]))
10851 : {
10852 0 : bFoundType = true;
10853 0 : nTmpAttrType = NC_UINT;
10854 : }
10855 : }
10856 : }
10857 121 : if (!bFoundType)
10858 : {
10859 : /* test for double */
10860 102 : errno = 0;
10861 102 : double dfValue = CPLStrtod(aosValues[i], &pszTemp);
10862 102 : if ((errno == 0) && (aosValues[i] != pszTemp) && (*pszTemp == 0))
10863 : {
10864 : // Test for float instead of double.
10865 : // strtof() is C89, which is not available in MSVC.
10866 : // See if we lose precision if we cast to float and write to
10867 : // char*.
10868 14 : float fValue = float(dfValue);
10869 : char szTemp[256];
10870 14 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", fValue);
10871 14 : if (EQUAL(szTemp, aosValues[i]))
10872 8 : nTmpAttrType = NC_FLOAT;
10873 : else
10874 6 : nTmpAttrType = NC_DOUBLE;
10875 : }
10876 : }
10877 121 : if ((nTmpAttrType <= NC_DOUBLE && nAttrType <= NC_DOUBLE &&
10878 101 : nTmpAttrType > nAttrType) ||
10879 101 : (nTmpAttrType == NC_UINT && nAttrType < NC_FLOAT) ||
10880 5 : (nTmpAttrType >= NC_FLOAT && nAttrType == NC_UINT))
10881 20 : nAttrType = nTmpAttrType;
10882 : }
10883 :
10884 : #ifdef DEBUG
10885 108 : if (EQUAL(pszAttrName, "DEBUG_EMPTY_DOUBLE_ATTR"))
10886 : {
10887 0 : nAttrType = NC_DOUBLE;
10888 0 : nAttrLen = 0;
10889 : }
10890 : #endif
10891 :
10892 : /* now write the data */
10893 108 : if (nAttrType == NC_CHAR)
10894 : {
10895 88 : int nTmpFormat = 0;
10896 88 : if (nAttrLen > 1)
10897 : {
10898 0 : status = nc_inq_format(nCdfId, &nTmpFormat);
10899 0 : NCDF_ERR(status);
10900 : }
10901 88 : if (nAttrLen > 1 && nTmpFormat == NCDF_FORMAT_NC4)
10902 0 : status =
10903 0 : nc_put_att_string(nCdfId, nVarId, pszAttrName, nAttrLen,
10904 0 : const_cast<const char **>(aosValues.List()));
10905 : else
10906 88 : status = nc_put_att_text(nCdfId, nVarId, pszAttrName,
10907 : strlen(pszValue), pszValue);
10908 88 : NCDF_ERR(status);
10909 : }
10910 : else
10911 : {
10912 20 : switch (nAttrType)
10913 : {
10914 11 : case NC_INT:
10915 : {
10916 : int *pnTemp =
10917 11 : static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
10918 30 : for (size_t i = 0; i < nAttrLen; i++)
10919 : {
10920 19 : pnTemp[i] =
10921 19 : static_cast<int>(strtol(aosValues[i], &pszTemp, 10));
10922 : }
10923 11 : status = nc_put_att_int(nCdfId, nVarId, pszAttrName, NC_INT,
10924 : nAttrLen, pnTemp);
10925 11 : NCDF_ERR(status);
10926 11 : CPLFree(pnTemp);
10927 11 : break;
10928 : }
10929 0 : case NC_UINT:
10930 : {
10931 : unsigned int *punTemp = static_cast<unsigned int *>(
10932 0 : CPLCalloc(nAttrLen, sizeof(unsigned int)));
10933 0 : for (size_t i = 0; i < nAttrLen; i++)
10934 : {
10935 0 : punTemp[i] = static_cast<unsigned int>(
10936 0 : strtol(aosValues[i], &pszTemp, 10));
10937 : }
10938 0 : status = nc_put_att_uint(nCdfId, nVarId, pszAttrName, NC_UINT,
10939 : nAttrLen, punTemp);
10940 0 : NCDF_ERR(status);
10941 0 : CPLFree(punTemp);
10942 0 : break;
10943 : }
10944 6 : case NC_FLOAT:
10945 : {
10946 : float *pfTemp =
10947 6 : static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
10948 14 : for (size_t i = 0; i < nAttrLen; i++)
10949 : {
10950 8 : pfTemp[i] =
10951 8 : static_cast<float>(CPLStrtod(aosValues[i], &pszTemp));
10952 : }
10953 6 : status = nc_put_att_float(nCdfId, nVarId, pszAttrName, NC_FLOAT,
10954 : nAttrLen, pfTemp);
10955 6 : NCDF_ERR(status);
10956 6 : CPLFree(pfTemp);
10957 6 : break;
10958 : }
10959 3 : case NC_DOUBLE:
10960 : {
10961 : double *pdfTemp =
10962 3 : static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
10963 9 : for (size_t i = 0; i < nAttrLen; i++)
10964 : {
10965 6 : pdfTemp[i] = CPLStrtod(aosValues[i], &pszTemp);
10966 : }
10967 3 : status = nc_put_att_double(nCdfId, nVarId, pszAttrName,
10968 : NC_DOUBLE, nAttrLen, pdfTemp);
10969 3 : NCDF_ERR(status);
10970 3 : CPLFree(pdfTemp);
10971 3 : break;
10972 : }
10973 0 : default:
10974 0 : return CE_Failure;
10975 : }
10976 : }
10977 :
10978 108 : return CE_None;
10979 : }
10980 :
10981 78 : static CPLErr NCDFGet1DVar(int nCdfId, int nVarId, char **pszValue)
10982 : {
10983 : /* get var information */
10984 78 : int nVarDimId = -1;
10985 78 : int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
10986 78 : if (status != NC_NOERR || nVarDimId != 1)
10987 0 : return CE_Failure;
10988 :
10989 78 : status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
10990 78 : if (status != NC_NOERR)
10991 0 : return CE_Failure;
10992 :
10993 78 : nc_type nVarType = NC_NAT;
10994 78 : status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
10995 78 : if (status != NC_NOERR)
10996 0 : return CE_Failure;
10997 :
10998 78 : size_t nVarLen = 0;
10999 78 : status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
11000 78 : if (status != NC_NOERR)
11001 0 : return CE_Failure;
11002 :
11003 78 : size_t start[1] = {0};
11004 78 : size_t count[1] = {nVarLen};
11005 :
11006 : /* Allocate guaranteed minimum size */
11007 78 : size_t nVarValueSize = NCDF_MAX_STR_LEN;
11008 : char *pszVarValue =
11009 78 : static_cast<char *>(CPLCalloc(nVarValueSize, sizeof(char)));
11010 78 : *pszVarValue = '\0';
11011 :
11012 78 : if (nVarLen == 0)
11013 : {
11014 : /* set return values */
11015 1 : *pszValue = pszVarValue;
11016 :
11017 1 : return CE_None;
11018 : }
11019 :
11020 77 : if (nVarLen > 1 && nVarType != NC_CHAR)
11021 42 : NCDFSafeStrcat(&pszVarValue, "{", &nVarValueSize);
11022 :
11023 77 : switch (nVarType)
11024 : {
11025 0 : case NC_CHAR:
11026 0 : nc_get_vara_text(nCdfId, nVarId, start, count, pszVarValue);
11027 0 : pszVarValue[nVarLen] = '\0';
11028 0 : break;
11029 0 : case NC_BYTE:
11030 : {
11031 : signed char *pscTemp = static_cast<signed char *>(
11032 0 : CPLCalloc(nVarLen, sizeof(signed char)));
11033 0 : nc_get_vara_schar(nCdfId, nVarId, start, count, pscTemp);
11034 : char szTemp[256];
11035 0 : size_t m = 0;
11036 0 : for (; m < nVarLen - 1; m++)
11037 : {
11038 0 : snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
11039 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11040 : }
11041 0 : snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
11042 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11043 0 : CPLFree(pscTemp);
11044 0 : break;
11045 : }
11046 0 : case NC_SHORT:
11047 : {
11048 : short *psTemp =
11049 0 : static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
11050 0 : nc_get_vara_short(nCdfId, nVarId, start, count, psTemp);
11051 : char szTemp[256];
11052 0 : size_t m = 0;
11053 0 : for (; m < nVarLen - 1; m++)
11054 : {
11055 0 : snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
11056 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11057 : }
11058 0 : snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
11059 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11060 0 : CPLFree(psTemp);
11061 0 : break;
11062 : }
11063 21 : case NC_INT:
11064 : {
11065 21 : int *pnTemp = static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
11066 21 : nc_get_vara_int(nCdfId, nVarId, start, count, pnTemp);
11067 : char szTemp[256];
11068 21 : size_t m = 0;
11069 44 : for (; m < nVarLen - 1; m++)
11070 : {
11071 23 : snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
11072 23 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11073 : }
11074 21 : snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
11075 21 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11076 21 : CPLFree(pnTemp);
11077 21 : break;
11078 : }
11079 8 : case NC_FLOAT:
11080 : {
11081 : float *pfTemp =
11082 8 : static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
11083 8 : nc_get_vara_float(nCdfId, nVarId, start, count, pfTemp);
11084 : char szTemp[256];
11085 8 : size_t m = 0;
11086 325 : for (; m < nVarLen - 1; m++)
11087 : {
11088 317 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
11089 317 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11090 : }
11091 8 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
11092 8 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11093 8 : CPLFree(pfTemp);
11094 8 : break;
11095 : }
11096 47 : case NC_DOUBLE:
11097 : {
11098 : double *pdfTemp =
11099 47 : static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
11100 47 : nc_get_vara_double(nCdfId, nVarId, start, count, pdfTemp);
11101 : char szTemp[256];
11102 47 : size_t m = 0;
11103 225 : for (; m < nVarLen - 1; m++)
11104 : {
11105 178 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
11106 178 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11107 : }
11108 47 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
11109 47 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11110 47 : CPLFree(pdfTemp);
11111 47 : break;
11112 : }
11113 0 : case NC_STRING:
11114 : {
11115 : char **ppszTemp =
11116 0 : static_cast<char **>(CPLCalloc(nVarLen, sizeof(char *)));
11117 0 : nc_get_vara_string(nCdfId, nVarId, start, count, ppszTemp);
11118 0 : size_t m = 0;
11119 0 : for (; m < nVarLen - 1; m++)
11120 : {
11121 0 : NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
11122 0 : NCDFSafeStrcat(&pszVarValue, ",", &nVarValueSize);
11123 : }
11124 0 : NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
11125 0 : nc_free_string(nVarLen, ppszTemp);
11126 0 : CPLFree(ppszTemp);
11127 0 : break;
11128 : }
11129 0 : case NC_UBYTE:
11130 : {
11131 : unsigned char *pucTemp = static_cast<unsigned char *>(
11132 0 : CPLCalloc(nVarLen, sizeof(unsigned char)));
11133 0 : nc_get_vara_uchar(nCdfId, nVarId, start, count, pucTemp);
11134 : char szTemp[256];
11135 0 : size_t m = 0;
11136 0 : for (; m < nVarLen - 1; m++)
11137 : {
11138 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
11139 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11140 : }
11141 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
11142 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11143 0 : CPLFree(pucTemp);
11144 0 : break;
11145 : }
11146 0 : case NC_USHORT:
11147 : {
11148 : unsigned short *pusTemp = static_cast<unsigned short *>(
11149 0 : CPLCalloc(nVarLen, sizeof(unsigned short)));
11150 0 : nc_get_vara_ushort(nCdfId, nVarId, start, count, pusTemp);
11151 : char szTemp[256];
11152 0 : size_t m = 0;
11153 0 : for (; m < nVarLen - 1; m++)
11154 : {
11155 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
11156 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11157 : }
11158 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
11159 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11160 0 : CPLFree(pusTemp);
11161 0 : break;
11162 : }
11163 0 : case NC_UINT:
11164 : {
11165 : unsigned int *punTemp = static_cast<unsigned int *>(
11166 0 : CPLCalloc(nVarLen, sizeof(unsigned int)));
11167 0 : nc_get_vara_uint(nCdfId, nVarId, start, count, punTemp);
11168 : char szTemp[256];
11169 0 : size_t m = 0;
11170 0 : for (; m < nVarLen - 1; m++)
11171 : {
11172 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
11173 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11174 : }
11175 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
11176 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11177 0 : CPLFree(punTemp);
11178 0 : break;
11179 : }
11180 1 : case NC_INT64:
11181 : {
11182 : long long *pnTemp =
11183 1 : static_cast<long long *>(CPLCalloc(nVarLen, sizeof(long long)));
11184 1 : nc_get_vara_longlong(nCdfId, nVarId, start, count, pnTemp);
11185 : char szTemp[256];
11186 1 : size_t m = 0;
11187 2 : for (; m < nVarLen - 1; m++)
11188 : {
11189 1 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",", pnTemp[m]);
11190 1 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11191 : }
11192 1 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, pnTemp[m]);
11193 1 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11194 1 : CPLFree(pnTemp);
11195 1 : break;
11196 : }
11197 0 : case NC_UINT64:
11198 : {
11199 : unsigned long long *pnTemp = static_cast<unsigned long long *>(
11200 0 : CPLCalloc(nVarLen, sizeof(unsigned long long)));
11201 0 : nc_get_vara_ulonglong(nCdfId, nVarId, start, count, pnTemp);
11202 : char szTemp[256];
11203 0 : size_t m = 0;
11204 0 : for (; m < nVarLen - 1; m++)
11205 : {
11206 0 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",", pnTemp[m]);
11207 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11208 : }
11209 0 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, pnTemp[m]);
11210 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11211 0 : CPLFree(pnTemp);
11212 0 : break;
11213 : }
11214 0 : default:
11215 0 : CPLDebug("GDAL_netCDF", "NCDFGetVar1D unsupported type %d",
11216 : nVarType);
11217 0 : CPLFree(pszVarValue);
11218 0 : pszVarValue = nullptr;
11219 0 : break;
11220 : }
11221 :
11222 77 : if (pszVarValue != nullptr && nVarLen > 1 && nVarType != NC_CHAR)
11223 42 : NCDFSafeStrcat(&pszVarValue, "}", &nVarValueSize);
11224 :
11225 : /* set return values */
11226 77 : *pszValue = pszVarValue;
11227 :
11228 77 : return CE_None;
11229 : }
11230 :
11231 8 : static CPLErr NCDFPut1DVar(int nCdfId, int nVarId, const char *pszValue)
11232 : {
11233 8 : if (EQUAL(pszValue, ""))
11234 0 : return CE_Failure;
11235 :
11236 : /* get var information */
11237 8 : int nVarDimId = -1;
11238 8 : int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
11239 8 : if (status != NC_NOERR || nVarDimId != 1)
11240 0 : return CE_Failure;
11241 :
11242 8 : status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
11243 8 : if (status != NC_NOERR)
11244 0 : return CE_Failure;
11245 :
11246 8 : nc_type nVarType = NC_CHAR;
11247 8 : status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
11248 8 : if (status != NC_NOERR)
11249 0 : return CE_Failure;
11250 :
11251 8 : size_t nVarLen = 0;
11252 8 : status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
11253 8 : if (status != NC_NOERR)
11254 0 : return CE_Failure;
11255 :
11256 8 : size_t start[1] = {0};
11257 8 : size_t count[1] = {nVarLen};
11258 :
11259 : /* get the values as tokens */
11260 16 : CPLStringList aosValues = NCDFTokenizeArray(pszValue);
11261 8 : if (aosValues.empty())
11262 0 : return CE_Failure;
11263 :
11264 8 : nVarLen = aosValues.size();
11265 :
11266 : /* now write the data */
11267 8 : if (nVarType == NC_CHAR)
11268 : {
11269 0 : status = nc_put_vara_text(nCdfId, nVarId, start, count, pszValue);
11270 0 : NCDF_ERR(status);
11271 : }
11272 : else
11273 : {
11274 8 : switch (nVarType)
11275 : {
11276 0 : case NC_BYTE:
11277 : {
11278 : signed char *pscTemp = static_cast<signed char *>(
11279 0 : CPLCalloc(nVarLen, sizeof(signed char)));
11280 0 : for (size_t i = 0; i < nVarLen; i++)
11281 : {
11282 0 : char *pszTemp = nullptr;
11283 0 : pscTemp[i] = static_cast<signed char>(
11284 0 : strtol(aosValues[i], &pszTemp, 10));
11285 : }
11286 : status =
11287 0 : nc_put_vara_schar(nCdfId, nVarId, start, count, pscTemp);
11288 0 : NCDF_ERR(status);
11289 0 : CPLFree(pscTemp);
11290 0 : break;
11291 : }
11292 0 : case NC_SHORT:
11293 : {
11294 : short *psTemp =
11295 0 : static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
11296 0 : for (size_t i = 0; i < nVarLen; i++)
11297 : {
11298 0 : char *pszTemp = nullptr;
11299 0 : psTemp[i] =
11300 0 : static_cast<short>(strtol(aosValues[i], &pszTemp, 10));
11301 : }
11302 : status =
11303 0 : nc_put_vara_short(nCdfId, nVarId, start, count, psTemp);
11304 0 : NCDF_ERR(status);
11305 0 : CPLFree(psTemp);
11306 0 : break;
11307 : }
11308 3 : case NC_INT:
11309 : {
11310 : int *pnTemp =
11311 3 : static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
11312 11 : for (size_t i = 0; i < nVarLen; i++)
11313 : {
11314 8 : char *pszTemp = nullptr;
11315 8 : pnTemp[i] =
11316 8 : static_cast<int>(strtol(aosValues[i], &pszTemp, 10));
11317 : }
11318 3 : status = nc_put_vara_int(nCdfId, nVarId, start, count, pnTemp);
11319 3 : NCDF_ERR(status);
11320 3 : CPLFree(pnTemp);
11321 3 : break;
11322 : }
11323 0 : case NC_FLOAT:
11324 : {
11325 : float *pfTemp =
11326 0 : static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
11327 0 : for (size_t i = 0; i < nVarLen; i++)
11328 : {
11329 0 : char *pszTemp = nullptr;
11330 0 : pfTemp[i] =
11331 0 : static_cast<float>(CPLStrtod(aosValues[i], &pszTemp));
11332 : }
11333 : status =
11334 0 : nc_put_vara_float(nCdfId, nVarId, start, count, pfTemp);
11335 0 : NCDF_ERR(status);
11336 0 : CPLFree(pfTemp);
11337 0 : break;
11338 : }
11339 5 : case NC_DOUBLE:
11340 : {
11341 : double *pdfTemp =
11342 5 : static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
11343 19 : for (size_t i = 0; i < nVarLen; i++)
11344 : {
11345 14 : char *pszTemp = nullptr;
11346 14 : pdfTemp[i] = CPLStrtod(aosValues[i], &pszTemp);
11347 : }
11348 : status =
11349 5 : nc_put_vara_double(nCdfId, nVarId, start, count, pdfTemp);
11350 5 : NCDF_ERR(status);
11351 5 : CPLFree(pdfTemp);
11352 5 : break;
11353 : }
11354 0 : default:
11355 : {
11356 0 : int nTmpFormat = 0;
11357 0 : status = nc_inq_format(nCdfId, &nTmpFormat);
11358 0 : NCDF_ERR(status);
11359 0 : if (nTmpFormat == NCDF_FORMAT_NC4)
11360 : {
11361 0 : switch (nVarType)
11362 : {
11363 0 : case NC_STRING:
11364 : {
11365 0 : status = nc_put_vara_string(
11366 : nCdfId, nVarId, start, count,
11367 0 : const_cast<const char **>(aosValues.List()));
11368 0 : NCDF_ERR(status);
11369 0 : break;
11370 : }
11371 0 : case NC_UBYTE:
11372 : {
11373 : unsigned char *pucTemp =
11374 : static_cast<unsigned char *>(
11375 0 : CPLCalloc(nVarLen, sizeof(unsigned char)));
11376 0 : for (size_t i = 0; i < nVarLen; i++)
11377 : {
11378 0 : char *pszTemp = nullptr;
11379 0 : pucTemp[i] = static_cast<unsigned char>(
11380 0 : strtoul(aosValues[i], &pszTemp, 10));
11381 : }
11382 0 : status = nc_put_vara_uchar(nCdfId, nVarId, start,
11383 : count, pucTemp);
11384 0 : NCDF_ERR(status);
11385 0 : CPLFree(pucTemp);
11386 0 : break;
11387 : }
11388 0 : case NC_USHORT:
11389 : {
11390 : unsigned short *pusTemp =
11391 : static_cast<unsigned short *>(
11392 0 : CPLCalloc(nVarLen, sizeof(unsigned short)));
11393 0 : for (size_t i = 0; i < nVarLen; i++)
11394 : {
11395 0 : char *pszTemp = nullptr;
11396 0 : pusTemp[i] = static_cast<unsigned short>(
11397 0 : strtoul(aosValues[i], &pszTemp, 10));
11398 : }
11399 0 : status = nc_put_vara_ushort(nCdfId, nVarId, start,
11400 : count, pusTemp);
11401 0 : NCDF_ERR(status);
11402 0 : CPLFree(pusTemp);
11403 0 : break;
11404 : }
11405 0 : case NC_UINT:
11406 : {
11407 : unsigned int *punTemp = static_cast<unsigned int *>(
11408 0 : CPLCalloc(nVarLen, sizeof(unsigned int)));
11409 0 : for (size_t i = 0; i < nVarLen; i++)
11410 : {
11411 0 : char *pszTemp = nullptr;
11412 0 : punTemp[i] = static_cast<unsigned int>(
11413 0 : strtoul(aosValues[i], &pszTemp, 10));
11414 : }
11415 0 : status = nc_put_vara_uint(nCdfId, nVarId, start,
11416 : count, punTemp);
11417 0 : NCDF_ERR(status);
11418 0 : CPLFree(punTemp);
11419 0 : break;
11420 : }
11421 0 : default:
11422 0 : return CE_Failure;
11423 : }
11424 : }
11425 0 : break;
11426 : }
11427 : }
11428 : }
11429 :
11430 8 : return CE_None;
11431 : }
11432 :
11433 : /************************************************************************/
11434 : /* GetDefaultNoDataValue() */
11435 : /************************************************************************/
11436 :
11437 196 : double NCDFGetDefaultNoDataValue(int nCdfId, int nVarId, int nVarType,
11438 : bool &bGotNoData)
11439 :
11440 : {
11441 196 : int nNoFill = 0;
11442 196 : double dfNoData = 0.0;
11443 :
11444 196 : switch (nVarType)
11445 : {
11446 0 : case NC_CHAR:
11447 : case NC_BYTE:
11448 : case NC_UBYTE:
11449 : // Don't do default fill-values for bytes, too risky.
11450 : // This function should not be called in those cases.
11451 0 : CPLAssert(false);
11452 : break;
11453 24 : case NC_SHORT:
11454 : {
11455 24 : short nFillVal = 0;
11456 24 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11457 : NC_NOERR)
11458 : {
11459 24 : if (!nNoFill)
11460 : {
11461 23 : bGotNoData = true;
11462 23 : dfNoData = nFillVal;
11463 : }
11464 : }
11465 : else
11466 0 : dfNoData = NC_FILL_SHORT;
11467 24 : break;
11468 : }
11469 26 : case NC_INT:
11470 : {
11471 26 : int nFillVal = 0;
11472 26 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11473 : NC_NOERR)
11474 : {
11475 26 : if (!nNoFill)
11476 : {
11477 25 : bGotNoData = true;
11478 25 : dfNoData = nFillVal;
11479 : }
11480 : }
11481 : else
11482 0 : dfNoData = NC_FILL_INT;
11483 26 : break;
11484 : }
11485 79 : case NC_FLOAT:
11486 : {
11487 79 : float fFillVal = 0;
11488 79 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &fFillVal) ==
11489 : NC_NOERR)
11490 : {
11491 79 : if (!nNoFill)
11492 : {
11493 75 : bGotNoData = true;
11494 75 : dfNoData = fFillVal;
11495 : }
11496 : }
11497 : else
11498 0 : dfNoData = NC_FILL_FLOAT;
11499 79 : break;
11500 : }
11501 34 : case NC_DOUBLE:
11502 : {
11503 34 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &dfNoData) ==
11504 : NC_NOERR)
11505 : {
11506 34 : if (!nNoFill)
11507 : {
11508 34 : bGotNoData = true;
11509 : }
11510 : }
11511 : else
11512 0 : dfNoData = NC_FILL_DOUBLE;
11513 34 : break;
11514 : }
11515 7 : case NC_USHORT:
11516 : {
11517 7 : unsigned short nFillVal = 0;
11518 7 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11519 : NC_NOERR)
11520 : {
11521 7 : if (!nNoFill)
11522 : {
11523 7 : bGotNoData = true;
11524 7 : dfNoData = nFillVal;
11525 : }
11526 : }
11527 : else
11528 0 : dfNoData = NC_FILL_USHORT;
11529 7 : break;
11530 : }
11531 7 : case NC_UINT:
11532 : {
11533 7 : unsigned int nFillVal = 0;
11534 7 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11535 : NC_NOERR)
11536 : {
11537 7 : if (!nNoFill)
11538 : {
11539 7 : bGotNoData = true;
11540 7 : dfNoData = nFillVal;
11541 : }
11542 : }
11543 : else
11544 0 : dfNoData = NC_FILL_UINT;
11545 7 : break;
11546 : }
11547 19 : default:
11548 19 : dfNoData = 0.0;
11549 19 : break;
11550 : }
11551 :
11552 196 : return dfNoData;
11553 : }
11554 :
11555 : /************************************************************************/
11556 : /* NCDFGetDefaultNoDataValueAsInt64() */
11557 : /************************************************************************/
11558 :
11559 2 : int64_t NCDFGetDefaultNoDataValueAsInt64(int nCdfId, int nVarId,
11560 : bool &bGotNoData)
11561 :
11562 : {
11563 2 : int nNoFill = 0;
11564 2 : long long nFillVal = 0;
11565 2 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
11566 : {
11567 2 : if (!nNoFill)
11568 : {
11569 2 : bGotNoData = true;
11570 2 : return static_cast<int64_t>(nFillVal);
11571 : }
11572 : }
11573 : else
11574 0 : return static_cast<int64_t>(NC_FILL_INT64);
11575 0 : return 0;
11576 : }
11577 :
11578 : /************************************************************************/
11579 : /* NCDFGetDefaultNoDataValueAsUInt64() */
11580 : /************************************************************************/
11581 :
11582 1 : uint64_t NCDFGetDefaultNoDataValueAsUInt64(int nCdfId, int nVarId,
11583 : bool &bGotNoData)
11584 :
11585 : {
11586 1 : int nNoFill = 0;
11587 1 : unsigned long long nFillVal = 0;
11588 1 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
11589 : {
11590 1 : if (!nNoFill)
11591 : {
11592 1 : bGotNoData = true;
11593 1 : return static_cast<uint64_t>(nFillVal);
11594 : }
11595 : }
11596 : else
11597 0 : return static_cast<uint64_t>(NC_FILL_UINT64);
11598 0 : return 0;
11599 : }
11600 :
11601 12141 : static int NCDFDoesVarContainAttribVal(int nCdfId,
11602 : const char *const *papszAttribNames,
11603 : const char *const *papszAttribValues,
11604 : int nVarId, const char *pszVarName,
11605 : bool bStrict = true)
11606 : {
11607 12141 : if (nVarId == -1 && pszVarName != nullptr)
11608 8216 : NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
11609 :
11610 12141 : if (nVarId == -1)
11611 878 : return -1;
11612 :
11613 11263 : bool bFound = false;
11614 52496 : for (int i = 0; !bFound && papszAttribNames != nullptr &&
11615 50027 : papszAttribNames[i] != nullptr;
11616 : i++)
11617 : {
11618 41233 : char *pszTemp = nullptr;
11619 41233 : if (NCDFGetAttr(nCdfId, nVarId, papszAttribNames[i], &pszTemp) ==
11620 59217 : CE_None &&
11621 17984 : pszTemp != nullptr)
11622 : {
11623 17984 : if (bStrict)
11624 : {
11625 17984 : if (EQUAL(pszTemp, papszAttribValues[i]))
11626 2469 : bFound = true;
11627 : }
11628 : else
11629 : {
11630 0 : if (EQUALN(pszTemp, papszAttribValues[i],
11631 : strlen(papszAttribValues[i])))
11632 0 : bFound = true;
11633 : }
11634 17984 : CPLFree(pszTemp);
11635 : }
11636 : }
11637 11263 : return bFound;
11638 : }
11639 :
11640 2133 : static int NCDFDoesVarContainAttribVal2(int nCdfId, const char *papszAttribName,
11641 : const char *const *papszAttribValues,
11642 : int nVarId, const char *pszVarName,
11643 : int bStrict = true)
11644 : {
11645 2133 : if (nVarId == -1 && pszVarName != nullptr)
11646 1579 : NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
11647 :
11648 2133 : if (nVarId == -1)
11649 0 : return -1;
11650 :
11651 2133 : bool bFound = false;
11652 2133 : char *pszTemp = nullptr;
11653 2512 : if (NCDFGetAttr(nCdfId, nVarId, papszAttribName, &pszTemp) != CE_None ||
11654 379 : pszTemp == nullptr)
11655 1754 : return FALSE;
11656 :
11657 7703 : for (int i = 0; !bFound && i < CSLCount(papszAttribValues); i++)
11658 : {
11659 7324 : if (bStrict)
11660 : {
11661 7296 : if (EQUAL(pszTemp, papszAttribValues[i]))
11662 31 : bFound = true;
11663 : }
11664 : else
11665 : {
11666 28 : if (EQUALN(pszTemp, papszAttribValues[i],
11667 : strlen(papszAttribValues[i])))
11668 0 : bFound = true;
11669 : }
11670 : }
11671 :
11672 379 : CPLFree(pszTemp);
11673 :
11674 379 : return bFound;
11675 : }
11676 :
11677 876 : static bool NCDFEqual(const char *papszName, const char *const *papszValues)
11678 : {
11679 876 : if (papszName == nullptr || EQUAL(papszName, ""))
11680 0 : return false;
11681 :
11682 2392 : for (int i = 0; papszValues && papszValues[i]; ++i)
11683 : {
11684 1636 : if (EQUAL(papszName, papszValues[i]))
11685 120 : return true;
11686 : }
11687 :
11688 756 : return false;
11689 : }
11690 :
11691 : // Test that a variable is longitude/latitude coordinate,
11692 : // following CF 4.1 and 4.2.
11693 4071 : bool NCDFIsVarLongitude(int nCdfId, int nVarId, const char *pszVarName)
11694 : {
11695 : // Check for matching attributes.
11696 4071 : int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLongitudeAttribNames,
11697 : papszCFLongitudeAttribValues, nVarId,
11698 : pszVarName);
11699 : // If not found using attributes then check using var name
11700 : // unless GDAL_NETCDF_VERIFY_DIMS=STRICT.
11701 4071 : if (bVal == -1)
11702 : {
11703 280 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11704 : "STRICT"))
11705 280 : bVal = NCDFEqual(pszVarName, papszCFLongitudeVarNames);
11706 : else
11707 0 : bVal = FALSE;
11708 : }
11709 3791 : else if (bVal)
11710 : {
11711 : // Check that the units is not 'm' or '1'. See #6759
11712 795 : char *pszTemp = nullptr;
11713 1175 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11714 380 : pszTemp != nullptr)
11715 : {
11716 380 : if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
11717 97 : bVal = false;
11718 380 : CPLFree(pszTemp);
11719 : }
11720 : }
11721 :
11722 4071 : return CPL_TO_BOOL(bVal);
11723 : }
11724 :
11725 2348 : bool NCDFIsVarLatitude(int nCdfId, int nVarId, const char *pszVarName)
11726 : {
11727 2348 : int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLatitudeAttribNames,
11728 : papszCFLatitudeAttribValues, nVarId,
11729 : pszVarName);
11730 2348 : if (bVal == -1)
11731 : {
11732 163 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11733 : "STRICT"))
11734 163 : bVal = NCDFEqual(pszVarName, papszCFLatitudeVarNames);
11735 : else
11736 0 : bVal = FALSE;
11737 : }
11738 2185 : else if (bVal)
11739 : {
11740 : // Check that the units is not 'm' or '1'. See #6759
11741 548 : char *pszTemp = nullptr;
11742 690 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11743 142 : pszTemp != nullptr)
11744 : {
11745 142 : if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
11746 36 : bVal = false;
11747 142 : CPLFree(pszTemp);
11748 : }
11749 : }
11750 :
11751 2348 : return CPL_TO_BOOL(bVal);
11752 : }
11753 :
11754 2546 : bool NCDFIsVarProjectionX(int nCdfId, int nVarId, const char *pszVarName)
11755 : {
11756 2546 : int bVal = NCDFDoesVarContainAttribVal(
11757 : nCdfId, papszCFProjectionXAttribNames, papszCFProjectionXAttribValues,
11758 : nVarId, pszVarName);
11759 2546 : if (bVal == -1)
11760 : {
11761 274 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11762 : "STRICT"))
11763 274 : bVal = NCDFEqual(pszVarName, papszCFProjectionXVarNames);
11764 : else
11765 0 : bVal = FALSE;
11766 : }
11767 2272 : else if (bVal)
11768 : {
11769 : // Check that the units is not '1'
11770 457 : char *pszTemp = nullptr;
11771 684 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11772 227 : pszTemp != nullptr)
11773 : {
11774 227 : if (EQUAL(pszTemp, "1"))
11775 5 : bVal = false;
11776 227 : CPLFree(pszTemp);
11777 : }
11778 : }
11779 :
11780 2546 : return CPL_TO_BOOL(bVal);
11781 : }
11782 :
11783 1799 : bool NCDFIsVarProjectionY(int nCdfId, int nVarId, const char *pszVarName)
11784 : {
11785 1799 : int bVal = NCDFDoesVarContainAttribVal(
11786 : nCdfId, papszCFProjectionYAttribNames, papszCFProjectionYAttribValues,
11787 : nVarId, pszVarName);
11788 1799 : if (bVal == -1)
11789 : {
11790 159 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11791 : "STRICT"))
11792 159 : bVal = NCDFEqual(pszVarName, papszCFProjectionYVarNames);
11793 : else
11794 0 : bVal = FALSE;
11795 : }
11796 1640 : else if (bVal)
11797 : {
11798 : // Check that the units is not '1'
11799 451 : char *pszTemp = nullptr;
11800 673 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11801 222 : pszTemp != nullptr)
11802 : {
11803 222 : if (EQUAL(pszTemp, "1"))
11804 5 : bVal = false;
11805 222 : CPLFree(pszTemp);
11806 : }
11807 : }
11808 :
11809 1799 : return CPL_TO_BOOL(bVal);
11810 : }
11811 :
11812 : /* test that a variable is a vertical coordinate, following CF 4.3 */
11813 1118 : bool NCDFIsVarVerticalCoord(int nCdfId, int nVarId, const char *pszVarName)
11814 : {
11815 : /* check for matching attributes */
11816 1118 : if (NCDFDoesVarContainAttribVal(nCdfId, papszCFVerticalAttribNames,
11817 : papszCFVerticalAttribValues, nVarId,
11818 1118 : pszVarName))
11819 111 : return true;
11820 : /* check for matching units */
11821 1007 : else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
11822 : papszCFVerticalUnitsValues, nVarId,
11823 1007 : pszVarName))
11824 31 : return true;
11825 : /* check for matching standard name */
11826 976 : else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_STD_NAME,
11827 : papszCFVerticalStandardNameValues,
11828 976 : nVarId, pszVarName))
11829 0 : return true;
11830 : else
11831 976 : return false;
11832 : }
11833 :
11834 : /* test that a variable is a time coordinate, following CF 4.4 */
11835 259 : bool NCDFIsVarTimeCoord(int nCdfId, int nVarId, const char *pszVarName)
11836 : {
11837 : /* check for matching attributes */
11838 259 : if (NCDFDoesVarContainAttribVal(nCdfId, papszCFTimeAttribNames,
11839 : papszCFTimeAttribValues, nVarId,
11840 259 : pszVarName))
11841 109 : return true;
11842 : /* check for matching units */
11843 150 : else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
11844 : papszCFTimeUnitsValues, nVarId,
11845 150 : pszVarName, false))
11846 0 : return true;
11847 : else
11848 150 : return false;
11849 : }
11850 :
11851 : // Parse a string, and return as a string list.
11852 : // If it is an array of the form {a,b}, then tokenize it.
11853 : // Otherwise, return a copy.
11854 192 : static CPLStringList NCDFTokenizeArray(const char *pszValue)
11855 : {
11856 192 : if (pszValue == nullptr || EQUAL(pszValue, ""))
11857 55 : return CPLStringList();
11858 :
11859 274 : CPLStringList aosValues;
11860 137 : const int nLen = static_cast<int>(strlen(pszValue));
11861 :
11862 137 : if (pszValue[0] == '{' && nLen > 2 && pszValue[nLen - 1] == '}')
11863 : {
11864 41 : char *pszTemp = static_cast<char *>(CPLMalloc((nLen - 2) + 1));
11865 41 : strncpy(pszTemp, pszValue + 1, nLen - 2);
11866 41 : pszTemp[nLen - 2] = '\0';
11867 : aosValues.Assign(
11868 41 : CSLTokenizeString2(pszTemp, ",", CSLT_ALLOWEMPTYTOKENS));
11869 41 : CPLFree(pszTemp);
11870 : }
11871 : else
11872 : {
11873 96 : aosValues.AddString(pszValue);
11874 : }
11875 :
11876 137 : return aosValues;
11877 : }
11878 :
11879 : // Open a NetCDF subdataset from full path /group1/group2/.../groupn/var.
11880 : // Leading slash is optional.
11881 420 : static CPLErr NCDFOpenSubDataset(int nCdfId, const char *pszSubdatasetName,
11882 : int *pnGroupId, int *pnVarId)
11883 : {
11884 420 : *pnGroupId = -1;
11885 420 : *pnVarId = -1;
11886 :
11887 : // Open group.
11888 : char *pszGroupFullName =
11889 420 : CPLStrdup(CPLGetPathSafe(pszSubdatasetName).c_str());
11890 : // Add a leading slash if needed.
11891 420 : if (pszGroupFullName[0] != '/')
11892 : {
11893 403 : char *old = pszGroupFullName;
11894 403 : pszGroupFullName = CPLStrdup(CPLSPrintf("/%s", pszGroupFullName));
11895 403 : CPLFree(old);
11896 : }
11897 : // Detect root group.
11898 420 : if (EQUAL(pszGroupFullName, "/"))
11899 : {
11900 403 : *pnGroupId = nCdfId;
11901 403 : CPLFree(pszGroupFullName);
11902 : }
11903 : else
11904 : {
11905 17 : int status = nc_inq_grp_full_ncid(nCdfId, pszGroupFullName, pnGroupId);
11906 17 : CPLFree(pszGroupFullName);
11907 17 : NCDF_ERR_RET(status);
11908 : }
11909 :
11910 : // Open var.
11911 420 : const char *pszVarName = CPLGetFilename(pszSubdatasetName);
11912 420 : NCDF_ERR_RET(nc_inq_varid(*pnGroupId, pszVarName, pnVarId));
11913 :
11914 420 : return CE_None;
11915 : }
11916 :
11917 : // Get all dimensions visible from a given NetCDF (or group) ID and any of
11918 : // its parents.
11919 360 : static CPLErr NCDFGetVisibleDims(int nGroupId, int *pnDims, int **ppanDimIds)
11920 : {
11921 360 : int nDims = 0;
11922 360 : int *panDimIds = nullptr;
11923 360 : NCDF_ERR_RET(nc_inq_dimids(nGroupId, &nDims, nullptr, true));
11924 :
11925 360 : panDimIds = static_cast<int *>(CPLMalloc(nDims * sizeof(int)));
11926 :
11927 360 : int status = nc_inq_dimids(nGroupId, nullptr, panDimIds, true);
11928 360 : if (status != NC_NOERR)
11929 0 : CPLFree(panDimIds);
11930 360 : NCDF_ERR_RET(status);
11931 :
11932 360 : *pnDims = nDims;
11933 360 : *ppanDimIds = panDimIds;
11934 :
11935 360 : return CE_None;
11936 : }
11937 :
11938 : // Get direct sub-groups IDs of a given NetCDF (or group) ID.
11939 : // Consider only direct children, does not get children of children.
11940 3126 : static CPLErr NCDFGetSubGroups(int nGroupId, int *pnSubGroups,
11941 : int **ppanSubGroupIds)
11942 : {
11943 3126 : *pnSubGroups = 0;
11944 3126 : *ppanSubGroupIds = nullptr;
11945 :
11946 : int nSubGroups;
11947 3126 : NCDF_ERR_RET(nc_inq_grps(nGroupId, &nSubGroups, nullptr));
11948 : int *panSubGroupIds =
11949 3126 : static_cast<int *>(CPLMalloc(nSubGroups * sizeof(int)));
11950 3126 : NCDF_ERR_RET(nc_inq_grps(nGroupId, nullptr, panSubGroupIds));
11951 3126 : *pnSubGroups = nSubGroups;
11952 3126 : *ppanSubGroupIds = panSubGroupIds;
11953 :
11954 3126 : return CE_None;
11955 : }
11956 :
11957 : // Get the full name of a given NetCDF (or group) ID
11958 : // (e.g. /group1/group2/.../groupn).
11959 : // bNC3Compat remove the leading slash for top-level variables for
11960 : // backward compatibility (top-level variables are the ones in the root group).
11961 18276 : static CPLErr NCDFGetGroupFullName(int nGroupId, char **ppszFullName,
11962 : bool bNC3Compat)
11963 : {
11964 18276 : *ppszFullName = nullptr;
11965 :
11966 : size_t nFullNameLen;
11967 18276 : NCDF_ERR_RET(nc_inq_grpname_len(nGroupId, &nFullNameLen));
11968 18276 : *ppszFullName =
11969 18276 : static_cast<char *>(CPLMalloc((nFullNameLen + 1) * sizeof(char)));
11970 18276 : int status = nc_inq_grpname_full(nGroupId, &nFullNameLen, *ppszFullName);
11971 18276 : if (status != NC_NOERR)
11972 : {
11973 0 : CPLFree(*ppszFullName);
11974 0 : *ppszFullName = nullptr;
11975 0 : NCDF_ERR_RET(status);
11976 : }
11977 :
11978 18276 : if (bNC3Compat && EQUAL(*ppszFullName, "/"))
11979 8154 : (*ppszFullName)[0] = '\0';
11980 :
11981 18276 : return CE_None;
11982 : }
11983 :
11984 9914 : CPLString NCDFGetGroupFullName(int nGroupId)
11985 : {
11986 9914 : char *pszFullname = nullptr;
11987 9914 : NCDFGetGroupFullName(nGroupId, &pszFullname, false);
11988 9914 : CPLString osRet(pszFullname ? pszFullname : "");
11989 9914 : CPLFree(pszFullname);
11990 19828 : return osRet;
11991 : }
11992 :
11993 : // Get the full name of a given NetCDF variable ID
11994 : // (e.g. /group1/group2/.../groupn/var).
11995 : // Handle also NC_GLOBAL as nVarId.
11996 : // bNC3Compat remove the leading slash for top-level variables for
11997 : // backward compatibility (top-level variables are the ones in the root group).
11998 8313 : static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId, char **ppszFullName,
11999 : bool bNC3Compat)
12000 : {
12001 8313 : *ppszFullName = nullptr;
12002 8313 : char *pszGroupFullName = nullptr;
12003 8313 : ERR_RET(NCDFGetGroupFullName(nGroupId, &pszGroupFullName, bNC3Compat));
12004 : char szVarName[NC_MAX_NAME + 1];
12005 8313 : if (nVarId == NC_GLOBAL)
12006 : {
12007 1118 : strcpy(szVarName, "NC_GLOBAL");
12008 : }
12009 : else
12010 : {
12011 7195 : int status = nc_inq_varname(nGroupId, nVarId, szVarName);
12012 7195 : if (status != NC_NOERR)
12013 : {
12014 0 : CPLFree(pszGroupFullName);
12015 0 : NCDF_ERR_RET(status);
12016 : }
12017 : }
12018 8313 : const char *pszSep = "/";
12019 8313 : if (EQUAL(pszGroupFullName, "/") || EQUAL(pszGroupFullName, ""))
12020 8107 : pszSep = "";
12021 8313 : *ppszFullName =
12022 8313 : CPLStrdup(CPLSPrintf("%s%s%s", pszGroupFullName, pszSep, szVarName));
12023 8313 : CPLFree(pszGroupFullName);
12024 8313 : return CE_None;
12025 : }
12026 :
12027 : // Get the NetCDF root group ID of a given group ID.
12028 0 : static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId)
12029 : {
12030 0 : *pnRootGroupId = -1;
12031 : // Recurse on parent group.
12032 : int nParentGroupId;
12033 0 : int status = nc_inq_grp_parent(nStartGroupId, &nParentGroupId);
12034 0 : if (status == NC_NOERR)
12035 0 : return NCDFGetRootGroup(nParentGroupId, pnRootGroupId);
12036 0 : else if (status != NC_ENOGRP)
12037 0 : NCDF_ERR_RET(status);
12038 : else // No more parent group.
12039 : {
12040 0 : *pnRootGroupId = nStartGroupId;
12041 : }
12042 :
12043 0 : return CE_None;
12044 : }
12045 :
12046 : // Implementation of NCDFResolveVar/Att.
12047 13355 : static CPLErr NCDFResolveElem(int nStartGroupId, const char *pszVar,
12048 : const char *pszAtt, int *pnGroupId, int *pnId,
12049 : bool bMandatory)
12050 : {
12051 13355 : if (!pszVar && !pszAtt)
12052 : {
12053 0 : CPLError(CE_Failure, CPLE_IllegalArg,
12054 : "pszVar and pszAtt NCDFResolveElem() args are both null.");
12055 0 : return CE_Failure;
12056 : }
12057 :
12058 : enum
12059 : {
12060 : NCRM_PARENT,
12061 : NCRM_WIDTH_WISE
12062 13355 : } eNCResolveMode = NCRM_PARENT;
12063 :
12064 26710 : std::queue<int> aoQueueGroupIdsToVisit;
12065 13355 : aoQueueGroupIdsToVisit.push(nStartGroupId);
12066 :
12067 15085 : while (!aoQueueGroupIdsToVisit.empty())
12068 : {
12069 : // Get the first group of the FIFO queue.
12070 13549 : *pnGroupId = aoQueueGroupIdsToVisit.front();
12071 13549 : aoQueueGroupIdsToVisit.pop();
12072 :
12073 : // Look if this group contains the searched element.
12074 : int status;
12075 13549 : if (pszVar)
12076 13324 : status = nc_inq_varid(*pnGroupId, pszVar, pnId);
12077 : else // pszAtt != nullptr.
12078 225 : status = nc_inq_attid(*pnGroupId, NC_GLOBAL, pszAtt, pnId);
12079 :
12080 13549 : if (status == NC_NOERR)
12081 : {
12082 11819 : return CE_None;
12083 : }
12084 1730 : else if ((pszVar && status != NC_ENOTVAR) ||
12085 222 : (pszAtt && status != NC_ENOTATT))
12086 : {
12087 0 : NCDF_ERR(status);
12088 : }
12089 : // Element not found, in NC4 case we must search in other groups
12090 : // following the CF logic.
12091 :
12092 : // The first resolve mode consists to search on parent groups.
12093 1730 : if (eNCResolveMode == NCRM_PARENT)
12094 : {
12095 1611 : int nParentGroupId = -1;
12096 1611 : int status2 = nc_inq_grp_parent(*pnGroupId, &nParentGroupId);
12097 1611 : if (status2 == NC_NOERR)
12098 62 : aoQueueGroupIdsToVisit.push(nParentGroupId);
12099 1549 : else if (status2 != NC_ENOGRP)
12100 0 : NCDF_ERR(status2);
12101 1549 : else if (pszVar)
12102 : // When resolving a variable, if there is no more
12103 : // parent group then we switch to width-wise search mode
12104 : // starting from the latest found parent group.
12105 1330 : eNCResolveMode = NCRM_WIDTH_WISE;
12106 : }
12107 :
12108 : // The second resolve mode is a width-wise search.
12109 1730 : if (eNCResolveMode == NCRM_WIDTH_WISE)
12110 : {
12111 : // Enqueue all direct sub-groups.
12112 1449 : int nSubGroups = 0;
12113 1449 : int *panSubGroupIds = nullptr;
12114 1449 : NCDFGetSubGroups(*pnGroupId, &nSubGroups, &panSubGroupIds);
12115 1581 : for (int i = 0; i < nSubGroups; i++)
12116 132 : aoQueueGroupIdsToVisit.push(panSubGroupIds[i]);
12117 1449 : CPLFree(panSubGroupIds);
12118 : }
12119 : }
12120 :
12121 1536 : if (bMandatory)
12122 : {
12123 0 : char *pszStartGroupFullName = nullptr;
12124 0 : NCDFGetGroupFullName(nStartGroupId, &pszStartGroupFullName);
12125 0 : CPLError(CE_Failure, CPLE_AppDefined,
12126 : "Cannot resolve mandatory %s %s from group %s",
12127 : (pszVar ? pszVar : pszAtt),
12128 : (pszVar ? "variable" : "attribute"),
12129 0 : (pszStartGroupFullName ? pszStartGroupFullName : ""));
12130 0 : CPLFree(pszStartGroupFullName);
12131 : }
12132 :
12133 1536 : *pnGroupId = -1;
12134 1536 : *pnId = -1;
12135 1536 : return CE_Failure;
12136 : }
12137 :
12138 : // Resolve a variable name from a given starting group following the CF logic:
12139 : // - if var name is an absolute path then directly open it
12140 : // - first search in the starting group and its parent groups
12141 : // - then if there is no more parent group we switch to a width-wise search
12142 : // mode starting from the latest found parent group.
12143 : // The full CF logic is described here:
12144 : // https://github.com/diwg/cf2/blob/master/group/cf2-group.adoc#scope
12145 : // If bMandatory then print an error if resolving fails.
12146 : // TODO: implement support of relative paths.
12147 : // TODO: to follow strictly the CF logic, when searching for a coordinate
12148 : // variable, we must stop the parent search mode once the corresponding
12149 : // dimension is found and start the width-wise search from this group.
12150 : // TODO: to follow strictly the CF logic, when searching in width-wise mode
12151 : // we should skip every groups already visited during the parent
12152 : // search mode (but revisiting them should have no impact so we could
12153 : // let as it is if it is simpler...)
12154 : // TODO: CF specifies that the width-wise search order is "left-to-right" so
12155 : // maybe we must sort sibling groups alphabetically? but maybe not
12156 : // necessary if nc_inq_grps() already sort them?
12157 13133 : CPLErr NCDFResolveVar(int nStartGroupId, const char *pszVar, int *pnGroupId,
12158 : int *pnVarId, bool bMandatory)
12159 : {
12160 13133 : *pnGroupId = -1;
12161 13133 : *pnVarId = -1;
12162 13133 : int nGroupId = nStartGroupId, nVarId;
12163 13133 : if (pszVar[0] == '/')
12164 : {
12165 : // This is an absolute path: we can open the var directly.
12166 : int nRootGroupId;
12167 0 : ERR_RET(NCDFGetRootGroup(nStartGroupId, &nRootGroupId));
12168 0 : ERR_RET(NCDFOpenSubDataset(nRootGroupId, pszVar, &nGroupId, &nVarId));
12169 : }
12170 : else
12171 : {
12172 : // We have to search the variable following the CF logic.
12173 13133 : ERR_RET(NCDFResolveElem(nStartGroupId, pszVar, nullptr, &nGroupId,
12174 : &nVarId, bMandatory));
12175 : }
12176 11816 : *pnGroupId = nGroupId;
12177 11816 : *pnVarId = nVarId;
12178 11816 : return CE_None;
12179 : }
12180 :
12181 : // Like NCDFResolveVar but returns directly the var full name.
12182 1384 : static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,
12183 : char **ppszFullName, bool bMandatory)
12184 : {
12185 1384 : *ppszFullName = nullptr;
12186 : int nGroupId, nVarId;
12187 1384 : ERR_RET(
12188 : NCDFResolveVar(nStartGroupId, pszVar, &nGroupId, &nVarId, bMandatory));
12189 1358 : return NCDFGetVarFullName(nGroupId, nVarId, ppszFullName);
12190 : }
12191 :
12192 : // Like NCDFResolveVar but resolves an attribute instead a variable and
12193 : // returns its integer value.
12194 : // Only GLOBAL attributes are supported for the moment.
12195 222 : static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,
12196 : const char *pszAtt, int *pnAtt, bool bMandatory)
12197 : {
12198 222 : int nGroupId = nStartGroupId, nAttId = nStartVarId;
12199 222 : ERR_RET(NCDFResolveElem(nStartGroupId, nullptr, pszAtt, &nGroupId, &nAttId,
12200 : bMandatory));
12201 3 : NCDF_ERR_RET(nc_get_att_int(nGroupId, NC_GLOBAL, pszAtt, pnAtt));
12202 3 : return CE_None;
12203 : }
12204 :
12205 : // Filter variables to keep only valid 2+D raster bands and vector fields in
12206 : // a given a NetCDF (or group) ID and its sub-groups.
12207 : // Coordinate or boundary variables are ignored.
12208 : // It also creates corresponding vector layers.
12209 536 : CPLErr netCDFDataset::FilterVars(
12210 : int nCdfId, bool bKeepRasters, bool bKeepVectors, char **papszIgnoreVars,
12211 : int *pnRasterVars, int *pnGroupId, int *pnVarId, int *pnIgnoredVars,
12212 : std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>
12213 : &oMap2DDimsToGroupAndVar)
12214 : {
12215 536 : int nVars = 0;
12216 536 : int nRasterVars = 0;
12217 536 : NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
12218 :
12219 1072 : std::vector<int> anPotentialVectorVarID;
12220 : // oMapDimIdToCount[x] = number of times dim x is the first dimension of
12221 : // potential vector variables
12222 1072 : std::map<int, int> oMapDimIdToCount;
12223 536 : int nVarXId = -1;
12224 536 : int nVarYId = -1;
12225 536 : int nVarZId = -1;
12226 536 : int nVarTimeId = -1;
12227 536 : int nVarTimeDimId = -1;
12228 536 : bool bIsVectorOnly = true;
12229 536 : int nProfileDimId = -1;
12230 536 : int nParentIndexVarID = -1;
12231 :
12232 3259 : for (int v = 0; v < nVars; v++)
12233 : {
12234 : int nVarDims;
12235 2723 : NCDF_ERR_RET(nc_inq_varndims(nCdfId, v, &nVarDims));
12236 : // Should we ignore this variable?
12237 : char szTemp[NC_MAX_NAME + 1];
12238 2723 : szTemp[0] = '\0';
12239 2723 : NCDF_ERR_RET(nc_inq_varname(nCdfId, v, szTemp));
12240 :
12241 2723 : if (strstr(szTemp, "_node_coordinates") ||
12242 2723 : strstr(szTemp, "_node_count"))
12243 : {
12244 : // Ignore CF-1.8 Simple Geometries helper variables
12245 69 : continue;
12246 : }
12247 :
12248 3963 : if (nVarDims == 1 && (NCDFIsVarLongitude(nCdfId, -1, szTemp) ||
12249 1309 : NCDFIsVarProjectionX(nCdfId, -1, szTemp)))
12250 : {
12251 365 : nVarXId = v;
12252 : }
12253 3233 : else if (nVarDims == 1 && (NCDFIsVarLatitude(nCdfId, -1, szTemp) ||
12254 944 : NCDFIsVarProjectionY(nCdfId, -1, szTemp)))
12255 : {
12256 364 : nVarYId = v;
12257 : }
12258 1925 : else if (nVarDims == 1 && NCDFIsVarVerticalCoord(nCdfId, -1, szTemp))
12259 : {
12260 78 : nVarZId = v;
12261 : }
12262 : else
12263 : {
12264 1847 : char *pszVarFullName = nullptr;
12265 1847 : CPLErr eErr = NCDFGetVarFullName(nCdfId, v, &pszVarFullName);
12266 1847 : if (eErr != CE_None)
12267 : {
12268 0 : CPLFree(pszVarFullName);
12269 0 : continue;
12270 : }
12271 : bool bIgnoreVar =
12272 1847 : (CSLFindString(papszIgnoreVars, pszVarFullName) != -1);
12273 1847 : CPLFree(pszVarFullName);
12274 1847 : if (bIgnoreVar)
12275 : {
12276 104 : if (nVarDims == 1 && NCDFIsVarTimeCoord(nCdfId, -1, szTemp))
12277 : {
12278 11 : nVarTimeId = v;
12279 11 : nc_inq_vardimid(nCdfId, v, &nVarTimeDimId);
12280 : }
12281 93 : else if (nVarDims > 1)
12282 : {
12283 89 : (*pnIgnoredVars)++;
12284 89 : CPLDebug("GDAL_netCDF", "variable #%d [%s] was ignored", v,
12285 : szTemp);
12286 : }
12287 : }
12288 : // Only accept 2+D vars.
12289 1743 : else if (nVarDims >= 2)
12290 : {
12291 712 : bool bRasterCandidate = true;
12292 : // Identify variables that might be vector variables
12293 712 : if (nVarDims == 2)
12294 : {
12295 636 : int anDimIds[2] = {-1, -1};
12296 636 : nc_inq_vardimid(nCdfId, v, anDimIds);
12297 :
12298 636 : nc_type vartype = NC_NAT;
12299 636 : nc_inq_vartype(nCdfId, v, &vartype);
12300 :
12301 : char szDimNameFirst[NC_MAX_NAME + 1];
12302 : char szDimNameSecond[NC_MAX_NAME + 1];
12303 636 : szDimNameFirst[0] = '\0';
12304 636 : szDimNameSecond[0] = '\0';
12305 1429 : if (vartype == NC_CHAR &&
12306 157 : nc_inq_dimname(nCdfId, anDimIds[0], szDimNameFirst) ==
12307 157 : NC_NOERR &&
12308 157 : nc_inq_dimname(nCdfId, anDimIds[1], szDimNameSecond) ==
12309 157 : NC_NOERR &&
12310 157 : !NCDFIsVarLongitude(nCdfId, -1, szDimNameSecond) &&
12311 157 : !NCDFIsVarProjectionX(nCdfId, -1, szDimNameSecond) &&
12312 950 : !NCDFIsVarLatitude(nCdfId, -1, szDimNameFirst) &&
12313 157 : !NCDFIsVarProjectionY(nCdfId, -1, szDimNameFirst))
12314 : {
12315 157 : anPotentialVectorVarID.push_back(v);
12316 157 : oMapDimIdToCount[anDimIds[0]]++;
12317 157 : if (strstr(szDimNameSecond, "_max_width"))
12318 : {
12319 127 : bRasterCandidate = false;
12320 : }
12321 : else
12322 : {
12323 30 : std::array<int, 3> oKey{anDimIds[0], anDimIds[1],
12324 30 : vartype};
12325 30 : oMap2DDimsToGroupAndVar[oKey].emplace_back(
12326 30 : std::pair(nCdfId, v));
12327 : }
12328 : }
12329 : else
12330 : {
12331 479 : std::array<int, 3> oKey{anDimIds[0], anDimIds[1],
12332 479 : vartype};
12333 479 : oMap2DDimsToGroupAndVar[oKey].emplace_back(
12334 479 : std::pair(nCdfId, v));
12335 479 : bIsVectorOnly = false;
12336 : }
12337 : }
12338 : else
12339 : {
12340 76 : bIsVectorOnly = false;
12341 : }
12342 712 : if (bKeepRasters && bRasterCandidate)
12343 : {
12344 556 : *pnGroupId = nCdfId;
12345 556 : *pnVarId = v;
12346 556 : nRasterVars++;
12347 : }
12348 : }
12349 1031 : else if (nVarDims == 1)
12350 : {
12351 730 : nc_type atttype = NC_NAT;
12352 730 : size_t attlen = 0;
12353 730 : if (nc_inq_att(nCdfId, v, "instance_dimension", &atttype,
12354 14 : &attlen) == NC_NOERR &&
12355 730 : atttype == NC_CHAR && attlen < NC_MAX_NAME)
12356 : {
12357 : char szInstanceDimension[NC_MAX_NAME + 1];
12358 14 : if (nc_get_att_text(nCdfId, v, "instance_dimension",
12359 14 : szInstanceDimension) == NC_NOERR)
12360 : {
12361 14 : szInstanceDimension[attlen] = 0;
12362 14 : int status = nc_inq_dimid(nCdfId, szInstanceDimension,
12363 : &nProfileDimId);
12364 14 : if (status == NC_NOERR)
12365 14 : nParentIndexVarID = v;
12366 : else
12367 0 : nProfileDimId = -1;
12368 14 : if (status == NC_EBADDIM)
12369 0 : CPLError(CE_Warning, CPLE_AppDefined,
12370 : "Attribute instance_dimension='%s' refers "
12371 : "to a non existing dimension",
12372 : szInstanceDimension);
12373 : else
12374 14 : NCDF_ERR(status);
12375 : }
12376 : }
12377 730 : if (v != nParentIndexVarID)
12378 : {
12379 716 : anPotentialVectorVarID.push_back(v);
12380 716 : int nDimId = -1;
12381 716 : nc_inq_vardimid(nCdfId, v, &nDimId);
12382 716 : oMapDimIdToCount[nDimId]++;
12383 : }
12384 : }
12385 : }
12386 : }
12387 :
12388 : // If we are opened in raster-only mode and that there are only 1D or 2D
12389 : // variables and that the 2D variables have no X/Y dim, and all
12390 : // variables refer to the same main dimension (or 2 dimensions for
12391 : // featureType=profile), then it is a pure vector dataset
12392 : CPLString osFeatureType(
12393 536 : CSLFetchNameValueDef(papszMetadata, "NC_GLOBAL#featureType", ""));
12394 425 : if (bKeepRasters && !bKeepVectors && bIsVectorOnly && nRasterVars > 0 &&
12395 961 : !anPotentialVectorVarID.empty() &&
12396 0 : (oMapDimIdToCount.size() == 1 ||
12397 0 : (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2 &&
12398 0 : nProfileDimId >= 0)))
12399 : {
12400 0 : anPotentialVectorVarID.resize(0);
12401 : }
12402 : else
12403 : {
12404 536 : *pnRasterVars += nRasterVars;
12405 : }
12406 :
12407 536 : if (!anPotentialVectorVarID.empty() && bKeepVectors)
12408 : {
12409 : // Take the dimension that is referenced the most times.
12410 64 : if (!(oMapDimIdToCount.size() == 1 ||
12411 27 : (EQUAL(osFeatureType, "profile") &&
12412 26 : oMapDimIdToCount.size() == 2 && nProfileDimId >= 0)))
12413 : {
12414 1 : CPLError(CE_Warning, CPLE_AppDefined,
12415 : "The dataset has several variables that could be "
12416 : "identified as vector fields, but not all share the same "
12417 : "primary dimension. Consequently they will be ignored.");
12418 : }
12419 : else
12420 : {
12421 50 : if (nVarTimeId >= 0 &&
12422 50 : oMapDimIdToCount.find(nVarTimeDimId) != oMapDimIdToCount.end())
12423 : {
12424 1 : anPotentialVectorVarID.push_back(nVarTimeId);
12425 : }
12426 49 : CreateGrpVectorLayers(nCdfId, osFeatureType, anPotentialVectorVarID,
12427 : oMapDimIdToCount, nVarXId, nVarYId, nVarZId,
12428 : nProfileDimId, nParentIndexVarID,
12429 : bKeepRasters);
12430 : }
12431 : }
12432 :
12433 : // Recurse on sub-groups.
12434 536 : int nSubGroups = 0;
12435 536 : int *panSubGroupIds = nullptr;
12436 536 : NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
12437 571 : for (int i = 0; i < nSubGroups; i++)
12438 : {
12439 35 : FilterVars(panSubGroupIds[i], bKeepRasters, bKeepVectors,
12440 : papszIgnoreVars, pnRasterVars, pnGroupId, pnVarId,
12441 : pnIgnoredVars, oMap2DDimsToGroupAndVar);
12442 : }
12443 536 : CPLFree(panSubGroupIds);
12444 :
12445 536 : return CE_None;
12446 : }
12447 :
12448 : // Create vector layers from given potentially identified vector variables
12449 : // resulting from the scanning of a NetCDF (or group) ID.
12450 49 : CPLErr netCDFDataset::CreateGrpVectorLayers(
12451 : int nCdfId, const CPLString &osFeatureType,
12452 : const std::vector<int> &anPotentialVectorVarID,
12453 : const std::map<int, int> &oMapDimIdToCount, int nVarXId, int nVarYId,
12454 : int nVarZId, int nProfileDimId, int nParentIndexVarID, bool bKeepRasters)
12455 : {
12456 49 : char *pszGroupName = nullptr;
12457 49 : NCDFGetGroupFullName(nCdfId, &pszGroupName);
12458 49 : if (pszGroupName == nullptr || pszGroupName[0] == '\0')
12459 : {
12460 47 : CPLFree(pszGroupName);
12461 47 : pszGroupName = CPLStrdup(CPLGetBasenameSafe(osFilename).c_str());
12462 : }
12463 49 : OGRwkbGeometryType eGType = wkbUnknown;
12464 : CPLString osLayerName = CSLFetchNameValueDef(
12465 98 : papszMetadata, "NC_GLOBAL#ogr_layer_name", pszGroupName);
12466 49 : CPLFree(pszGroupName);
12467 49 : papszMetadata =
12468 49 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_name", nullptr);
12469 :
12470 49 : if (EQUAL(osFeatureType, "point") || EQUAL(osFeatureType, "profile"))
12471 : {
12472 33 : papszMetadata =
12473 33 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#featureType", nullptr);
12474 33 : eGType = wkbPoint;
12475 : }
12476 :
12477 : const char *pszLayerType =
12478 49 : CSLFetchNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_type");
12479 49 : if (pszLayerType != nullptr)
12480 : {
12481 9 : eGType = OGRFromOGCGeomType(pszLayerType);
12482 9 : papszMetadata =
12483 9 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_type", nullptr);
12484 : }
12485 :
12486 : CPLString osGeometryField =
12487 98 : CSLFetchNameValueDef(papszMetadata, "NC_GLOBAL#ogr_geometry_field", "");
12488 49 : papszMetadata =
12489 49 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_geometry_field", nullptr);
12490 :
12491 49 : int nFirstVarId = -1;
12492 49 : int nVectorDim = oMapDimIdToCount.rbegin()->first;
12493 49 : if (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2)
12494 : {
12495 13 : if (nVectorDim == nProfileDimId)
12496 0 : nVectorDim = oMapDimIdToCount.begin()->first;
12497 : }
12498 : else
12499 : {
12500 36 : nProfileDimId = -1;
12501 : }
12502 62 : for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)
12503 : {
12504 62 : int anDimIds[2] = {-1, -1};
12505 62 : nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);
12506 62 : if (nVectorDim == anDimIds[0])
12507 : {
12508 49 : nFirstVarId = anPotentialVectorVarID[j];
12509 49 : break;
12510 : }
12511 : }
12512 :
12513 : // In case where coordinates are explicitly specified for one of the
12514 : // field/variable, use them in priority over the ones that might have been
12515 : // identified above.
12516 49 : char *pszCoordinates = nullptr;
12517 49 : if (NCDFGetAttr(nCdfId, nFirstVarId, "coordinates", &pszCoordinates) ==
12518 : CE_None)
12519 : {
12520 : const CPLStringList aosTokens(
12521 68 : NCDFTokenizeCoordinatesAttribute(pszCFCoordinates));
12522 34 : for (int i = 0; i < aosTokens.size(); i++)
12523 : {
12524 0 : if (NCDFIsVarLongitude(nCdfId, -1, aosTokens[i]) ||
12525 0 : NCDFIsVarProjectionX(nCdfId, -1, aosTokens[i]))
12526 : {
12527 0 : nVarXId = -1;
12528 0 : CPL_IGNORE_RET_VAL(
12529 0 : nc_inq_varid(nCdfId, aosTokens[i], &nVarXId));
12530 : }
12531 0 : else if (NCDFIsVarLatitude(nCdfId, -1, aosTokens[i]) ||
12532 0 : NCDFIsVarProjectionY(nCdfId, -1, aosTokens[i]))
12533 : {
12534 0 : nVarYId = -1;
12535 0 : CPL_IGNORE_RET_VAL(
12536 0 : nc_inq_varid(nCdfId, aosTokens[i], &nVarYId));
12537 : }
12538 0 : else if (NCDFIsVarVerticalCoord(nCdfId, -1, aosTokens[i]))
12539 : {
12540 0 : nVarZId = -1;
12541 0 : CPL_IGNORE_RET_VAL(
12542 0 : nc_inq_varid(nCdfId, aosTokens[i], &nVarZId));
12543 : }
12544 : }
12545 : }
12546 49 : CPLFree(pszCoordinates);
12547 :
12548 : // Check that the X,Y,Z vars share 1D and share the same dimension as
12549 : // attribute variables.
12550 49 : if (nVarXId >= 0 && nVarYId >= 0)
12551 : {
12552 38 : int nVarDimCount = -1;
12553 38 : int nVarDimId = -1;
12554 38 : if (nc_inq_varndims(nCdfId, nVarXId, &nVarDimCount) != NC_NOERR ||
12555 38 : nVarDimCount != 1 ||
12556 38 : nc_inq_vardimid(nCdfId, nVarXId, &nVarDimId) != NC_NOERR ||
12557 38 : nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim) ||
12558 35 : nc_inq_varndims(nCdfId, nVarYId, &nVarDimCount) != NC_NOERR ||
12559 35 : nVarDimCount != 1 ||
12560 111 : nc_inq_vardimid(nCdfId, nVarYId, &nVarDimId) != NC_NOERR ||
12561 35 : nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim))
12562 : {
12563 3 : nVarXId = nVarYId = -1;
12564 : }
12565 69 : else if (nVarZId >= 0 &&
12566 34 : (nc_inq_varndims(nCdfId, nVarZId, &nVarDimCount) != NC_NOERR ||
12567 34 : nVarDimCount != 1 ||
12568 34 : nc_inq_vardimid(nCdfId, nVarZId, &nVarDimId) != NC_NOERR ||
12569 34 : nVarDimId != nVectorDim))
12570 : {
12571 0 : nVarZId = -1;
12572 : }
12573 : }
12574 :
12575 49 : if (eGType == wkbUnknown && nVarXId >= 0 && nVarYId >= 0)
12576 : {
12577 2 : eGType = wkbPoint;
12578 : }
12579 49 : if (eGType == wkbPoint && nVarXId >= 0 && nVarYId >= 0 && nVarZId >= 0)
12580 : {
12581 34 : eGType = wkbPoint25D;
12582 : }
12583 49 : if (eGType == wkbUnknown && osGeometryField.empty())
12584 : {
12585 5 : eGType = wkbNone;
12586 : }
12587 :
12588 : // Read projection info
12589 49 : char **papszMetadataBackup = CSLDuplicate(papszMetadata);
12590 49 : ReadAttributes(nCdfId, nFirstVarId);
12591 49 : if (!this->bSGSupport)
12592 49 : SetProjectionFromVar(nCdfId, nFirstVarId, true);
12593 49 : const char *pszValue = FetchAttr(nCdfId, nFirstVarId, CF_GRD_MAPPING);
12594 49 : char *pszGridMapping = (pszValue ? CPLStrdup(pszValue) : nullptr);
12595 49 : CSLDestroy(papszMetadata);
12596 49 : papszMetadata = papszMetadataBackup;
12597 :
12598 49 : OGRSpatialReference *poSRS = nullptr;
12599 49 : if (!m_oSRS.IsEmpty())
12600 : {
12601 21 : poSRS = m_oSRS.Clone();
12602 : }
12603 : // Reset if there's a 2D raster
12604 49 : m_bHasProjection = false;
12605 49 : m_bHasGeoTransform = false;
12606 :
12607 49 : if (!bKeepRasters)
12608 : {
12609 : // Strip out uninteresting metadata.
12610 45 : papszMetadata =
12611 45 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#Conventions", nullptr);
12612 45 : papszMetadata =
12613 45 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#GDAL", nullptr);
12614 45 : papszMetadata =
12615 45 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#history", nullptr);
12616 : }
12617 :
12618 : std::shared_ptr<netCDFLayer> poLayer(
12619 49 : new netCDFLayer(this, nCdfId, osLayerName, eGType, poSRS));
12620 49 : if (poSRS != nullptr)
12621 21 : poSRS->Release();
12622 49 : poLayer->SetRecordDimID(nVectorDim);
12623 49 : if (wkbFlatten(eGType) == wkbPoint && nVarXId >= 0 && nVarYId >= 0)
12624 : {
12625 35 : poLayer->SetXYZVars(nVarXId, nVarYId, nVarZId);
12626 : }
12627 14 : else if (!osGeometryField.empty())
12628 : {
12629 9 : poLayer->SetWKTGeometryField(osGeometryField);
12630 : }
12631 49 : if (pszGridMapping != nullptr)
12632 : {
12633 21 : poLayer->SetGridMapping(pszGridMapping);
12634 21 : CPLFree(pszGridMapping);
12635 : }
12636 49 : poLayer->SetProfile(nProfileDimId, nParentIndexVarID);
12637 :
12638 574 : for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)
12639 : {
12640 525 : int anDimIds[2] = {-1, -1};
12641 525 : nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);
12642 525 : if (anDimIds[0] == nVectorDim ||
12643 24 : (nProfileDimId >= 0 && anDimIds[0] == nProfileDimId))
12644 : {
12645 : #ifdef NCDF_DEBUG
12646 : char szTemp2[NC_MAX_NAME + 1] = {};
12647 : CPL_IGNORE_RET_VAL(
12648 : nc_inq_varname(nCdfId, anPotentialVectorVarID[j], szTemp2));
12649 : CPLDebug("GDAL_netCDF", "Variable %s is a vector field", szTemp2);
12650 : #endif
12651 525 : poLayer->AddField(anPotentialVectorVarID[j]);
12652 : }
12653 : }
12654 :
12655 49 : if (poLayer->GetLayerDefn()->GetFieldCount() != 0 ||
12656 0 : poLayer->GetGeomType() != wkbNone)
12657 : {
12658 49 : papoLayers.push_back(poLayer);
12659 : }
12660 :
12661 98 : return CE_None;
12662 : }
12663 :
12664 : // Get all coordinate and boundary variables full names referenced in
12665 : // a given a NetCDF (or group) ID and its sub-groups.
12666 : // These variables are identified in other variable's
12667 : // "coordinates" and "bounds" attribute.
12668 : // Searching coordinate and boundary variables may need to explore
12669 : // parents groups (or other groups in case of reference given in form of an
12670 : // absolute path).
12671 : // See CF sections 5.2, 5.6 and 7.1
12672 537 : static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId, char ***ppapszVars)
12673 : {
12674 537 : int nVars = 0;
12675 537 : NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
12676 :
12677 3274 : for (int v = 0; v < nVars; v++)
12678 : {
12679 2737 : char *pszTemp = nullptr;
12680 5474 : CPLStringList aosTokens;
12681 2737 : if (NCDFGetAttr(nCdfId, v, "coordinates", &pszTemp) == CE_None)
12682 446 : aosTokens.Assign(NCDFTokenizeCoordinatesAttribute(pszTemp));
12683 2737 : CPLFree(pszTemp);
12684 2737 : pszTemp = nullptr;
12685 2737 : if (NCDFGetAttr(nCdfId, v, "bounds", &pszTemp) == CE_None &&
12686 2737 : pszTemp != nullptr && !EQUAL(pszTemp, ""))
12687 17 : aosTokens.AddString(pszTemp);
12688 2737 : CPLFree(pszTemp);
12689 4025 : for (int i = 0; i < aosTokens.size(); i++)
12690 : {
12691 1288 : char *pszVarFullName = nullptr;
12692 1288 : if (NCDFResolveVarFullName(nCdfId, aosTokens[i], &pszVarFullName) ==
12693 : CE_None)
12694 1262 : *ppapszVars = CSLAddString(*ppapszVars, pszVarFullName);
12695 1288 : CPLFree(pszVarFullName);
12696 : }
12697 : }
12698 :
12699 : // Recurse on sub-groups.
12700 : int nSubGroups;
12701 537 : int *panSubGroupIds = nullptr;
12702 537 : NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
12703 572 : for (int i = 0; i < nSubGroups; i++)
12704 : {
12705 35 : NCDFGetCoordAndBoundVarFullNames(panSubGroupIds[i], ppapszVars);
12706 : }
12707 537 : CPLFree(panSubGroupIds);
12708 :
12709 537 : return CE_None;
12710 : }
12711 :
12712 : // Check if give type is user defined
12713 1903 : bool NCDFIsUserDefinedType(int /*ncid*/, int type)
12714 : {
12715 1903 : return type >= NC_FIRSTUSERTYPEID;
12716 : }
12717 :
12718 577 : char **NCDFTokenizeCoordinatesAttribute(const char *pszCoordinates)
12719 : {
12720 : // CF conventions use space as the separator for variable names in the
12721 : // coordinates attribute, but some products such as
12722 : // https://sentinel.esa.int/web/sentinel/technical-guides/sentinel-3-synergy/products-algorithms/level-2-aod-algorithms-and-products/level-2-aod-products-description
12723 : // use comma.
12724 577 : return CSLTokenizeString2(pszCoordinates, ", ", 0);
12725 : }
|