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, std::string &osFullName,
111 : bool bNC3Compat = true);
112 : static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId,
113 : std::string &osFullName,
114 : bool bNC3Compat = true);
115 : static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId);
116 :
117 : static std::pair<int, int> ReadExtraDimDef(GDALDataset *poSrcDS,
118 : const char *pszDimName);
119 :
120 : static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,
121 : std::string &osFullName,
122 : bool bMandatory = false);
123 : static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,
124 : const char *pszAtt, int *pnAtt,
125 : bool bMandatory = false);
126 : static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId,
127 : CPLStringList &aosVars);
128 :
129 : // Uncomment this for more debug output.
130 : // #define NCDF_DEBUG 1
131 :
132 : CPLMutex *hNCMutex = nullptr;
133 :
134 : // Workaround https://github.com/OSGeo/gdal/issues/6253
135 : // Having 2 netCDF handles on the same file doesn't work in a multi-threaded
136 : // way. Apparently having the same handle works better (this is OK since
137 : // we have a global mutex on the netCDF library)
138 : static std::map<std::string, int> goMapNameToNetCDFId;
139 : static std::map<int, std::pair<std::string, int>> goMapNetCDFIdToKeyAndCount;
140 :
141 864 : int GDAL_nc_open(const char *pszFilename, int nMode, int *pID)
142 : {
143 1728 : std::string osKey(pszFilename);
144 864 : osKey += "#####";
145 864 : osKey += std::to_string(nMode);
146 864 : auto oIter = goMapNameToNetCDFId.find(osKey);
147 864 : if (oIter == goMapNameToNetCDFId.end())
148 : {
149 798 : int ret = nc_open(pszFilename, nMode, pID);
150 798 : if (ret != NC_NOERR)
151 3 : return ret;
152 795 : goMapNameToNetCDFId[osKey] = *pID;
153 795 : goMapNetCDFIdToKeyAndCount[*pID] =
154 1590 : std::pair<std::string, int>(osKey, 1);
155 795 : return ret;
156 : }
157 : else
158 : {
159 66 : *pID = oIter->second;
160 66 : goMapNetCDFIdToKeyAndCount[oIter->second].second++;
161 66 : return NC_NOERR;
162 : }
163 : }
164 :
165 1188 : int GDAL_nc_close(int cdfid)
166 : {
167 1188 : int ret = NC_NOERR;
168 1188 : auto oIter = goMapNetCDFIdToKeyAndCount.find(cdfid);
169 1188 : if (oIter != goMapNetCDFIdToKeyAndCount.end())
170 : {
171 861 : if (--oIter->second.second == 0)
172 : {
173 795 : ret = nc_close(cdfid);
174 795 : goMapNameToNetCDFId.erase(oIter->second.first);
175 795 : goMapNetCDFIdToKeyAndCount.erase(oIter);
176 : }
177 : }
178 : else
179 : {
180 : // we can go here if file opened with nc_open_mem() or nc_create()
181 327 : ret = nc_close(cdfid);
182 : }
183 1188 : return ret;
184 : }
185 :
186 : /************************************************************************/
187 : /* ==================================================================== */
188 : /* netCDFRasterBand */
189 : /* ==================================================================== */
190 : /************************************************************************/
191 :
192 : class netCDFRasterBand final : public GDALPamRasterBand
193 : {
194 : friend class netCDFDataset;
195 :
196 : nc_type nc_datatype;
197 : int cdfid;
198 : int nZId;
199 : int nZDim;
200 : int nLevel;
201 : int nBandXPos;
202 : int nBandYPos;
203 : int *panBandZPos;
204 : int *panBandZLev;
205 : bool m_bNoDataSet = false;
206 : double m_dfNoDataValue = 0;
207 : bool m_bNoDataSetAsInt64 = false;
208 : int64_t m_nNodataValueInt64 = 0;
209 : bool m_bNoDataSetAsUInt64 = false;
210 : uint64_t m_nNodataValueUInt64 = 0;
211 : bool bValidRangeValid = false;
212 : double adfValidRange[2]{0, 0};
213 : bool m_bHaveScale = false;
214 : bool m_bHaveOffset = false;
215 : double m_dfScale = 1;
216 : double m_dfOffset = 0;
217 : CPLString m_osUnitType{};
218 : bool bSignedData;
219 : bool bCheckLongitude;
220 : bool m_bCreateMetadataFromOtherVarsDone = false;
221 :
222 : void CreateMetadataFromAttributes();
223 : void CreateMetadataFromOtherVars();
224 :
225 : template <class T>
226 : void CheckData(void *pImage, void *pImageNC, size_t nTmpBlockXSize,
227 : size_t nTmpBlockYSize, bool bCheckIsNan = false);
228 : template <class T>
229 : void CheckDataCpx(void *pImage, void *pImageNC, size_t nTmpBlockXSize,
230 : size_t nTmpBlockYSize, bool bCheckIsNan = false);
231 : void SetBlockSize();
232 :
233 : bool FetchNetcdfChunk(size_t xstart, size_t ystart, void *pImage);
234 :
235 : void SetNoDataValueNoUpdate(double dfNoData);
236 : void SetNoDataValueNoUpdate(int64_t nNoData);
237 : void SetNoDataValueNoUpdate(uint64_t nNoData);
238 :
239 : void SetOffsetNoUpdate(double dfVal);
240 : void SetScaleNoUpdate(double dfVal);
241 : void SetUnitTypeNoUpdate(const char *pszNewValue);
242 :
243 : protected:
244 : CPLXMLNode *SerializeToXML(const char *pszUnused) override;
245 :
246 : public:
247 : struct CONSTRUCTOR_OPEN
248 : {
249 : };
250 :
251 : struct CONSTRUCTOR_CREATE
252 : {
253 : };
254 :
255 : netCDFRasterBand(const CONSTRUCTOR_OPEN &, netCDFDataset *poDS,
256 : int nGroupId, int nZId, int nZDim, int nLevel,
257 : const int *panBandZLen, const int *panBandPos, int nBand);
258 : netCDFRasterBand(const CONSTRUCTOR_CREATE &, netCDFDataset *poDS,
259 : GDALDataType eType, int nBand, bool bSigned = true,
260 : const char *pszBandName = nullptr,
261 : const char *pszLongName = nullptr, int nZId = -1,
262 : int nZDim = 2, int nLevel = 0,
263 : const int *panBandZLev = nullptr,
264 : const int *panBandZPos = nullptr,
265 : const int *paDimIds = nullptr);
266 : ~netCDFRasterBand() override;
267 :
268 : double GetNoDataValue(int *) override;
269 : int64_t GetNoDataValueAsInt64(int *pbSuccess = nullptr) override;
270 : uint64_t GetNoDataValueAsUInt64(int *pbSuccess = nullptr) override;
271 : CPLErr SetNoDataValue(double) override;
272 : CPLErr SetNoDataValueAsInt64(int64_t nNoData) override;
273 : CPLErr SetNoDataValueAsUInt64(uint64_t nNoData) override;
274 : // virtual CPLErr DeleteNoDataValue();
275 : double GetOffset(int *) override;
276 : CPLErr SetOffset(double) override;
277 : double GetScale(int *) override;
278 : CPLErr SetScale(double) override;
279 : const char *GetUnitType() override;
280 : CPLErr SetUnitType(const char *) override;
281 : CPLErr IReadBlock(int, int, void *) override;
282 : CPLErr IWriteBlock(int, int, void *) override;
283 :
284 : CSLConstList GetMetadata(const char *pszDomain = "") override;
285 : const char *GetMetadataItem(const char *pszName,
286 : const char *pszDomain = "") override;
287 :
288 : CPLErr SetMetadataItem(const char *pszName, const char *pszValue,
289 : const char *pszDomain = "") override;
290 : CPLErr SetMetadata(CSLConstList papszMD,
291 : const char *pszDomain = "") override;
292 : };
293 :
294 : /************************************************************************/
295 : /* netCDFRasterBand() */
296 : /************************************************************************/
297 :
298 519 : netCDFRasterBand::netCDFRasterBand(const netCDFRasterBand::CONSTRUCTOR_OPEN &,
299 : netCDFDataset *poNCDFDS, int nGroupId,
300 : int nZIdIn, int nZDimIn, int nLevelIn,
301 : const int *panBandZLevIn,
302 519 : const int *panBandZPosIn, int nBandIn)
303 : : nc_datatype(NC_NAT), cdfid(nGroupId), nZId(nZIdIn), nZDim(nZDimIn),
304 519 : nLevel(nLevelIn), nBandXPos(panBandZPosIn[0]),
305 519 : nBandYPos(nZDim == 1 ? -1 : panBandZPosIn[1]), panBandZPos(nullptr),
306 : panBandZLev(nullptr),
307 : bSignedData(true), // Default signed, except for Byte.
308 1038 : bCheckLongitude(false)
309 : {
310 519 : poDS = poNCDFDS;
311 519 : nBand = nBandIn;
312 :
313 : // Take care of all other dimensions.
314 519 : if (nZDim > 2)
315 : {
316 182 : panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
317 182 : panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
318 :
319 490 : for (int i = 0; i < nZDim - 2; i++)
320 : {
321 308 : panBandZPos[i] = panBandZPosIn[i + 2];
322 308 : panBandZLev[i] = panBandZLevIn[i];
323 : }
324 : }
325 :
326 519 : nRasterXSize = poDS->GetRasterXSize();
327 519 : nRasterYSize = poDS->GetRasterYSize();
328 519 : nBlockXSize = poDS->GetRasterXSize();
329 519 : nBlockYSize = 1;
330 :
331 : // Get the type of the "z" variable, our target raster array.
332 519 : if (nc_inq_var(cdfid, nZId, nullptr, &nc_datatype, nullptr, nullptr,
333 519 : nullptr) != NC_NOERR)
334 : {
335 0 : CPLError(CE_Failure, CPLE_AppDefined, "Error in nc_var_inq() on 'z'.");
336 0 : return;
337 : }
338 :
339 519 : if (NCDFIsUserDefinedType(cdfid, nc_datatype))
340 : {
341 : // First enquire and check that the number of fields is 2
342 : size_t nfields, compoundsize;
343 5 : if (nc_inq_compound(cdfid, nc_datatype, nullptr, &compoundsize,
344 5 : &nfields) != NC_NOERR)
345 : {
346 0 : CPLError(CE_Failure, CPLE_AppDefined,
347 : "Error in nc_inq_compound() on 'z'.");
348 0 : return;
349 : }
350 :
351 5 : if (nfields != 2)
352 : {
353 0 : CPLError(CE_Failure, CPLE_AppDefined,
354 : "Unsupported data type encountered in nc_inq_compound() "
355 : "on 'z'.");
356 0 : return;
357 : }
358 :
359 : // Now check that that two types are the same in the struct.
360 : nc_type field_type1, field_type2;
361 : int field_dims1, field_dims2;
362 5 : if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
363 : &field_type1, &field_dims1,
364 5 : nullptr) != NC_NOERR)
365 : {
366 0 : CPLError(
367 : CE_Failure, CPLE_AppDefined,
368 : "Error in querying Field 1 in nc_inq_compound_field() on 'z'.");
369 0 : return;
370 : }
371 :
372 5 : if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
373 : &field_type2, &field_dims2,
374 5 : nullptr) != NC_NOERR)
375 : {
376 0 : CPLError(
377 : CE_Failure, CPLE_AppDefined,
378 : "Error in querying Field 2 in nc_inq_compound_field() on 'z'.");
379 0 : return;
380 : }
381 :
382 5 : if ((field_type1 != field_type2) || (field_dims1 != field_dims2) ||
383 5 : (field_dims1 != 0))
384 : {
385 0 : CPLError(CE_Failure, CPLE_AppDefined,
386 : "Error in interpreting compound data type on 'z'.");
387 0 : return;
388 : }
389 :
390 5 : if (field_type1 == NC_SHORT)
391 0 : eDataType = GDT_CInt16;
392 5 : else if (field_type1 == NC_INT)
393 0 : eDataType = GDT_CInt32;
394 5 : else if (field_type1 == NC_FLOAT)
395 4 : eDataType = GDT_CFloat32;
396 1 : else if (field_type1 == NC_DOUBLE)
397 1 : eDataType = GDT_CFloat64;
398 : else
399 : {
400 0 : CPLError(CE_Failure, CPLE_AppDefined,
401 : "Unsupported netCDF compound data type encountered.");
402 0 : return;
403 : }
404 : }
405 : else
406 : {
407 514 : if (nc_datatype == NC_BYTE)
408 166 : eDataType = GDT_UInt8;
409 348 : else if (nc_datatype == NC_CHAR)
410 0 : eDataType = GDT_UInt8;
411 348 : else if (nc_datatype == NC_SHORT)
412 44 : eDataType = GDT_Int16;
413 304 : else if (nc_datatype == NC_INT)
414 89 : eDataType = GDT_Int32;
415 215 : else if (nc_datatype == NC_FLOAT)
416 131 : eDataType = GDT_Float32;
417 84 : else if (nc_datatype == NC_DOUBLE)
418 45 : eDataType = GDT_Float64;
419 39 : else if (nc_datatype == NC_UBYTE)
420 16 : eDataType = GDT_UInt8;
421 23 : else if (nc_datatype == NC_USHORT)
422 4 : eDataType = GDT_UInt16;
423 19 : else if (nc_datatype == NC_UINT)
424 4 : eDataType = GDT_UInt32;
425 15 : else if (nc_datatype == NC_INT64)
426 8 : eDataType = GDT_Int64;
427 7 : else if (nc_datatype == NC_UINT64)
428 7 : eDataType = GDT_UInt64;
429 : else
430 : {
431 0 : if (nBand == 1)
432 0 : CPLError(CE_Warning, CPLE_AppDefined,
433 : "Unsupported netCDF datatype (%d), treat as Float32.",
434 0 : static_cast<int>(nc_datatype));
435 0 : eDataType = GDT_Float32;
436 0 : nc_datatype = NC_FLOAT;
437 : }
438 : }
439 :
440 : // Find and set No Data for this variable.
441 519 : nc_type atttype = NC_NAT;
442 519 : size_t attlen = 0;
443 519 : const char *pszNoValueName = nullptr;
444 :
445 : // Find attribute name, either _FillValue or missing_value.
446 519 : int status = nc_inq_att(cdfid, nZId, NCDF_FillValue, &atttype, &attlen);
447 519 : if (status == NC_NOERR)
448 : {
449 260 : pszNoValueName = NCDF_FillValue;
450 : }
451 : else
452 : {
453 259 : status = nc_inq_att(cdfid, nZId, "missing_value", &atttype, &attlen);
454 259 : if (status == NC_NOERR)
455 : {
456 12 : pszNoValueName = "missing_value";
457 : }
458 : }
459 :
460 : // Fetch missing value.
461 519 : double dfNoData = 0.0;
462 519 : bool bGotNoData = false;
463 519 : int64_t nNoDataAsInt64 = 0;
464 519 : bool bGotNoDataAsInt64 = false;
465 519 : uint64_t nNoDataAsUInt64 = 0;
466 519 : bool bGotNoDataAsUInt64 = false;
467 519 : if (status == NC_NOERR)
468 : {
469 272 : nc_type nAttrType = NC_NAT;
470 272 : size_t nAttrLen = 0;
471 272 : status = nc_inq_att(cdfid, nZId, pszNoValueName, &nAttrType, &nAttrLen);
472 272 : if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_INT64)
473 : {
474 : long long v;
475 7 : nc_get_att_longlong(cdfid, nZId, pszNoValueName, &v);
476 7 : bGotNoData = true;
477 7 : bGotNoDataAsInt64 = true;
478 7 : nNoDataAsInt64 = static_cast<int64_t>(v);
479 : }
480 265 : else if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_UINT64)
481 : {
482 : unsigned long long v;
483 7 : nc_get_att_ulonglong(cdfid, nZId, pszNoValueName, &v);
484 7 : bGotNoData = true;
485 7 : bGotNoDataAsUInt64 = true;
486 7 : nNoDataAsUInt64 = static_cast<uint64_t>(v);
487 : }
488 258 : else if (NCDFGetAttr(cdfid, nZId, pszNoValueName, &dfNoData) == CE_None)
489 : {
490 257 : bGotNoData = true;
491 : }
492 : }
493 :
494 : // If NoData was not found, use the default value, but for non-Byte types
495 : // as it is not recommended:
496 : // https://www.unidata.ucar.edu/software/netcdf/docs/attribute_conventions.html
497 519 : nc_type vartype = NC_NAT;
498 519 : if (!bGotNoData)
499 : {
500 248 : nc_inq_vartype(cdfid, nZId, &vartype);
501 248 : if (vartype == NC_INT64)
502 : {
503 : nNoDataAsInt64 =
504 1 : NCDFGetDefaultNoDataValueAsInt64(cdfid, nZId, bGotNoData);
505 1 : bGotNoDataAsInt64 = bGotNoData;
506 : }
507 247 : else if (vartype == NC_UINT64)
508 : {
509 : nNoDataAsUInt64 =
510 0 : NCDFGetDefaultNoDataValueAsUInt64(cdfid, nZId, bGotNoData);
511 0 : bGotNoDataAsUInt64 = bGotNoData;
512 : }
513 247 : else if (vartype != NC_CHAR && vartype != NC_BYTE &&
514 104 : vartype != NC_UBYTE)
515 : {
516 94 : dfNoData =
517 94 : NCDFGetDefaultNoDataValue(cdfid, nZId, vartype, bGotNoData);
518 94 : if (bGotNoData)
519 : {
520 83 : CPLDebug("GDAL_netCDF",
521 : "did not get nodata value for variable #%d, using "
522 : "default %f",
523 : nZId, dfNoData);
524 : }
525 : }
526 : }
527 :
528 519 : bool bHasUnderscoreUnsignedAttr = false;
529 519 : bool bUnderscoreUnsignedAttrVal = false;
530 : {
531 519 : char *pszTemp = nullptr;
532 519 : if (NCDFGetAttr(cdfid, nZId, "_Unsigned", &pszTemp) == CE_None)
533 : {
534 158 : if (EQUAL(pszTemp, "true"))
535 : {
536 150 : bHasUnderscoreUnsignedAttr = true;
537 150 : bUnderscoreUnsignedAttrVal = true;
538 : }
539 8 : else if (EQUAL(pszTemp, "false"))
540 : {
541 8 : bHasUnderscoreUnsignedAttr = true;
542 8 : bUnderscoreUnsignedAttrVal = false;
543 : }
544 158 : CPLFree(pszTemp);
545 : }
546 : }
547 :
548 : // Look for valid_range or valid_min/valid_max.
549 :
550 : // First look for valid_range.
551 519 : if (CPLFetchBool(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE", true))
552 : {
553 517 : char *pszValidRange = nullptr;
554 517 : if (NCDFGetAttr(cdfid, nZId, "valid_range", &pszValidRange) ==
555 151 : CE_None &&
556 668 : pszValidRange[0] == '{' &&
557 151 : pszValidRange[strlen(pszValidRange) - 1] == '}')
558 : {
559 : const std::string osValidRange =
560 453 : std::string(pszValidRange).substr(1, strlen(pszValidRange) - 2);
561 : const CPLStringList aosValidRange(
562 302 : CSLTokenizeString2(osValidRange.c_str(), ",", 0));
563 151 : if (aosValidRange.size() == 2 &&
564 302 : CPLGetValueType(aosValidRange[0]) != CPL_VALUE_STRING &&
565 151 : CPLGetValueType(aosValidRange[1]) != CPL_VALUE_STRING)
566 : {
567 151 : bValidRangeValid = true;
568 151 : adfValidRange[0] = CPLAtof(aosValidRange[0]);
569 151 : adfValidRange[1] = CPLAtof(aosValidRange[1]);
570 : }
571 : }
572 517 : CPLFree(pszValidRange);
573 :
574 : // If not found look for valid_min and valid_max.
575 517 : if (!bValidRangeValid)
576 : {
577 366 : double dfMin = 0;
578 366 : double dfMax = 0;
579 381 : if (NCDFGetAttr(cdfid, nZId, "valid_min", &dfMin) == CE_None &&
580 15 : NCDFGetAttr(cdfid, nZId, "valid_max", &dfMax) == CE_None)
581 : {
582 8 : adfValidRange[0] = dfMin;
583 8 : adfValidRange[1] = dfMax;
584 8 : bValidRangeValid = true;
585 : }
586 : }
587 :
588 517 : if (bValidRangeValid &&
589 159 : (adfValidRange[0] < 0 || adfValidRange[1] < 0) &&
590 17 : nc_datatype == NC_SHORT && bHasUnderscoreUnsignedAttr &&
591 : bUnderscoreUnsignedAttrVal)
592 : {
593 2 : if (adfValidRange[0] < 0)
594 0 : adfValidRange[0] += 65536;
595 2 : if (adfValidRange[1] < 0)
596 2 : adfValidRange[1] += 65536;
597 2 : if (adfValidRange[0] <= adfValidRange[1])
598 : {
599 : // Updating metadata item
600 2 : GDALPamRasterBand::SetMetadataItem(
601 : "valid_range",
602 2 : CPLSPrintf("{%d,%d}", static_cast<int>(adfValidRange[0]),
603 2 : static_cast<int>(adfValidRange[1])));
604 : }
605 : }
606 :
607 517 : if (bValidRangeValid && adfValidRange[0] > adfValidRange[1])
608 : {
609 0 : CPLError(CE_Warning, CPLE_AppDefined,
610 : "netCDFDataset::valid_range: min > max:\n"
611 : " min: %lf\n max: %lf\n",
612 : adfValidRange[0], adfValidRange[1]);
613 0 : bValidRangeValid = false;
614 0 : adfValidRange[0] = 0.0;
615 0 : adfValidRange[1] = 0.0;
616 : }
617 : }
618 :
619 : // Special For Byte Bands: check for signed/unsigned byte.
620 519 : if (nc_datatype == NC_BYTE)
621 : {
622 : // netcdf uses signed byte by default, but GDAL uses unsigned by default
623 : // This may cause unexpected results, but is needed for back-compat.
624 166 : if (poNCDFDS->bIsGdalFile)
625 144 : bSignedData = false;
626 : else
627 22 : bSignedData = true;
628 :
629 : // For NC4 format NC_BYTE is (normally) signed, NC_UBYTE is unsigned.
630 : // But in case a NC3 file was converted automatically and has hints
631 : // that it is unsigned, take them into account
632 166 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
633 : {
634 3 : bSignedData = true;
635 : }
636 :
637 : // If we got valid_range, test for signed/unsigned range.
638 : // https://docs.unidata.ucar.edu/netcdf-c/current/attribute_conventions.html
639 166 : if (bValidRangeValid)
640 : {
641 : // If we got valid_range={0,255}, treat as unsigned.
642 147 : if (adfValidRange[0] == 0 && adfValidRange[1] == 255)
643 : {
644 139 : bSignedData = false;
645 : // Reset valid_range.
646 139 : bValidRangeValid = false;
647 : }
648 : // If we got valid_range={-128,127}, treat as signed.
649 8 : else if (adfValidRange[0] == -128 && adfValidRange[1] == 127)
650 : {
651 8 : bSignedData = true;
652 : // Reset valid_range.
653 8 : bValidRangeValid = false;
654 : }
655 : }
656 : // Else test for _Unsigned.
657 : // https://docs.unidata.ucar.edu/nug/current/best_practices.html
658 : else
659 : {
660 19 : if (bHasUnderscoreUnsignedAttr)
661 7 : bSignedData = !bUnderscoreUnsignedAttrVal;
662 : }
663 :
664 166 : if (bSignedData)
665 : {
666 20 : eDataType = GDT_Int8;
667 : }
668 146 : else if (dfNoData < 0)
669 : {
670 : // Fix nodata value as it was stored signed.
671 6 : dfNoData += 256;
672 6 : if (pszNoValueName)
673 : {
674 : // Updating metadata item
675 6 : GDALPamRasterBand::SetMetadataItem(
676 : pszNoValueName,
677 : CPLSPrintf("%d", static_cast<int>(dfNoData)));
678 : }
679 : }
680 : }
681 353 : else if (nc_datatype == NC_SHORT)
682 : {
683 44 : if (bHasUnderscoreUnsignedAttr)
684 : {
685 4 : bSignedData = !bUnderscoreUnsignedAttrVal;
686 4 : if (!bSignedData)
687 4 : eDataType = GDT_UInt16;
688 : }
689 :
690 : // Fix nodata value as it was stored signed.
691 44 : if (!bSignedData && dfNoData < 0)
692 : {
693 4 : dfNoData += 65536;
694 4 : if (pszNoValueName)
695 : {
696 : // Updating metadata item
697 4 : GDALPamRasterBand::SetMetadataItem(
698 : pszNoValueName,
699 : CPLSPrintf("%d", static_cast<int>(dfNoData)));
700 : }
701 : }
702 : }
703 :
704 309 : else if (nc_datatype == NC_UBYTE || nc_datatype == NC_USHORT ||
705 289 : nc_datatype == NC_UINT || nc_datatype == NC_UINT64)
706 : {
707 31 : bSignedData = false;
708 : }
709 :
710 519 : CPLDebug("GDAL_netCDF", "netcdf type=%d gdal type=%d signedByte=%d",
711 519 : nc_datatype, eDataType, static_cast<int>(bSignedData));
712 :
713 519 : if (bGotNoData)
714 : {
715 : // Set nodata value.
716 355 : if (bGotNoDataAsInt64)
717 : {
718 8 : if (eDataType == GDT_Int64)
719 : {
720 8 : SetNoDataValueNoUpdate(nNoDataAsInt64);
721 : }
722 0 : else if (eDataType == GDT_UInt64 && nNoDataAsInt64 >= 0)
723 : {
724 0 : SetNoDataValueNoUpdate(static_cast<uint64_t>(nNoDataAsInt64));
725 : }
726 : else
727 : {
728 0 : SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsInt64));
729 : }
730 : }
731 347 : else if (bGotNoDataAsUInt64)
732 : {
733 7 : if (eDataType == GDT_UInt64)
734 : {
735 7 : SetNoDataValueNoUpdate(nNoDataAsUInt64);
736 : }
737 0 : else if (eDataType == GDT_Int64 &&
738 : nNoDataAsUInt64 <=
739 0 : static_cast<uint64_t>(
740 0 : std::numeric_limits<int64_t>::max()))
741 : {
742 0 : SetNoDataValueNoUpdate(static_cast<int64_t>(nNoDataAsUInt64));
743 : }
744 : else
745 : {
746 0 : SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsUInt64));
747 : }
748 : }
749 : else
750 : {
751 : #ifdef NCDF_DEBUG
752 : CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) read", dfNoData);
753 : #endif
754 340 : if (eDataType == GDT_Int64 && GDALIsValueExactAs<int64_t>(dfNoData))
755 : {
756 0 : SetNoDataValueNoUpdate(static_cast<int64_t>(dfNoData));
757 : }
758 340 : else if (eDataType == GDT_UInt64 &&
759 0 : GDALIsValueExactAs<uint64_t>(dfNoData))
760 : {
761 0 : SetNoDataValueNoUpdate(static_cast<uint64_t>(dfNoData));
762 : }
763 : else
764 : {
765 340 : SetNoDataValueNoUpdate(dfNoData);
766 : }
767 : }
768 : }
769 :
770 519 : CreateMetadataFromAttributes();
771 :
772 : // Attempt to fetch the scale_factor and add_offset attributes for the
773 : // variable and set them. If these values are not available, set
774 : // offset to 0 and scale to 1.
775 519 : if (nc_inq_attid(cdfid, nZId, CF_ADD_OFFSET, nullptr) == NC_NOERR)
776 : {
777 21 : double dfOffset = 0;
778 21 : status = nc_get_att_double(cdfid, nZId, CF_ADD_OFFSET, &dfOffset);
779 21 : CPLDebug("GDAL_netCDF", "got add_offset=%.16g, status=%d", dfOffset,
780 : status);
781 21 : SetOffsetNoUpdate(dfOffset);
782 : }
783 :
784 519 : bool bHasScale = false;
785 519 : if (nc_inq_attid(cdfid, nZId, CF_SCALE_FACTOR, nullptr) == NC_NOERR)
786 : {
787 23 : bHasScale = true;
788 23 : double dfScale = 1;
789 23 : status = nc_get_att_double(cdfid, nZId, CF_SCALE_FACTOR, &dfScale);
790 23 : CPLDebug("GDAL_netCDF", "got scale_factor=%.16g, status=%d", dfScale,
791 : status);
792 23 : SetScaleNoUpdate(dfScale);
793 : }
794 :
795 12 : if (bValidRangeValid && GDALDataTypeIsInteger(eDataType) &&
796 4 : eDataType != GDT_Int64 && eDataType != GDT_UInt64 &&
797 4 : (std::fabs(std::round(adfValidRange[0]) - adfValidRange[0]) > 1e-5 ||
798 531 : std::fabs(std::round(adfValidRange[1]) - adfValidRange[1]) > 1e-5) &&
799 1 : CSLFetchNameValue(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE") ==
800 : nullptr)
801 : {
802 1 : CPLError(CE_Warning, CPLE_AppDefined,
803 : "validity range = %f, %f contains floating-point values, "
804 : "whereas data type is integer. valid_range is thus likely "
805 : "wrong%s. Ignoring it.",
806 : adfValidRange[0], adfValidRange[1],
807 : bHasScale ? " (likely scaled using scale_factor/add_factor "
808 : "whereas it should be using the packed data type)"
809 : : "");
810 1 : bValidRangeValid = false;
811 1 : adfValidRange[0] = 0.0;
812 1 : adfValidRange[1] = 0.0;
813 : }
814 :
815 : // Should we check for longitude values > 360?
816 519 : bCheckLongitude =
817 1038 : CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")) &&
818 519 : NCDFIsVarLongitude(cdfid, nZId, nullptr);
819 :
820 : // Attempt to fetch the units attribute for the variable and set it.
821 519 : SetUnitTypeNoUpdate(netCDFRasterBand::GetMetadataItem(CF_UNITS));
822 :
823 519 : SetBlockSize();
824 : }
825 :
826 713 : void netCDFRasterBand::SetBlockSize()
827 : {
828 : // Check for variable chunking (netcdf-4 only).
829 : // GDAL block size should be set to hdf5 chunk size.
830 713 : int nTmpFormat = 0;
831 713 : int status = nc_inq_format(cdfid, &nTmpFormat);
832 713 : NetCDFFormatEnum eTmpFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
833 713 : if ((status == NC_NOERR) &&
834 603 : (eTmpFormat == NCDF_FORMAT_NC4 || eTmpFormat == NCDF_FORMAT_NC4C))
835 : {
836 126 : size_t chunksize[MAX_NC_DIMS] = {};
837 : // Check for chunksize and set it as the blocksize (optimizes read).
838 126 : status = nc_inq_var_chunking(cdfid, nZId, &nTmpFormat, chunksize);
839 126 : if ((status == NC_NOERR) && (nTmpFormat == NC_CHUNKED))
840 : {
841 14 : nBlockXSize = (int)chunksize[nZDim - 1];
842 14 : if (nZDim >= 2)
843 14 : nBlockYSize = (int)chunksize[nZDim - 2];
844 : else
845 0 : nBlockYSize = 1;
846 : }
847 : }
848 :
849 : // Deal with bottom-up datasets and nBlockYSize != 1.
850 713 : auto poGDS = cpl::down_cast<netCDFDataset *>(poDS);
851 713 : if (poGDS->bBottomUp && nBlockYSize != 1 && poGDS->poChunkCache == nullptr)
852 : {
853 6 : if (poGDS->eAccess == GA_ReadOnly)
854 : {
855 : // Try to cache 1 or 2 'rows' of netCDF chunks along the whole
856 : // width of the raster
857 6 : size_t nChunks =
858 6 : static_cast<size_t>(DIV_ROUND_UP(nRasterXSize, nBlockXSize));
859 6 : if ((nRasterYSize % nBlockYSize) != 0)
860 2 : nChunks *= 2;
861 : const size_t nChunkSize =
862 6 : static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
863 6 : nBlockXSize * nBlockYSize;
864 6 : constexpr size_t MAX_CACHE_SIZE = 100 * 1024 * 1024;
865 6 : nChunks = std::min(nChunks, MAX_CACHE_SIZE / nChunkSize);
866 6 : if (nChunks)
867 : {
868 6 : poGDS->poChunkCache.reset(
869 6 : new netCDFDataset::ChunkCacheType(nChunks));
870 : }
871 : }
872 : else
873 : {
874 0 : nBlockYSize = 1;
875 : }
876 : }
877 713 : }
878 :
879 : // Constructor in create mode.
880 : // If nZId and following variables are not passed, the band will have 2
881 : // dimensions.
882 : // TODO: Get metadata, missing val from band #1 if nZDim > 2.
883 194 : netCDFRasterBand::netCDFRasterBand(
884 : const netCDFRasterBand::CONSTRUCTOR_CREATE &, netCDFDataset *poNCDFDS,
885 : const GDALDataType eTypeIn, int nBandIn, bool bSigned,
886 : const char *pszBandName, const char *pszLongName, int nZIdIn, int nZDimIn,
887 : int nLevelIn, const int *panBandZLevIn, const int *panBandZPosIn,
888 194 : const int *paDimIds)
889 194 : : nc_datatype(NC_NAT), cdfid(poNCDFDS->GetCDFID()), nZId(nZIdIn),
890 : nZDim(nZDimIn), nLevel(nLevelIn), nBandXPos(1), nBandYPos(0),
891 : panBandZPos(nullptr), panBandZLev(nullptr), bSignedData(bSigned),
892 194 : bCheckLongitude(false), m_bCreateMetadataFromOtherVarsDone(true)
893 : {
894 194 : poDS = poNCDFDS;
895 194 : nBand = nBandIn;
896 :
897 194 : nRasterXSize = poDS->GetRasterXSize();
898 194 : nRasterYSize = poDS->GetRasterYSize();
899 194 : nBlockXSize = poDS->GetRasterXSize();
900 194 : nBlockYSize = 1;
901 :
902 194 : if (poDS->GetAccess() != GA_Update)
903 : {
904 0 : CPLError(CE_Failure, CPLE_NotSupported,
905 : "Dataset is not in update mode, "
906 : "wrong netCDFRasterBand constructor");
907 0 : return;
908 : }
909 :
910 : // Take care of all other dimensions.
911 194 : if (nZDim > 2 && paDimIds != nullptr)
912 : {
913 28 : nBandXPos = panBandZPosIn[0];
914 28 : nBandYPos = panBandZPosIn[1];
915 :
916 28 : panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
917 28 : panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
918 :
919 78 : for (int i = 0; i < nZDim - 2; i++)
920 : {
921 50 : panBandZPos[i] = panBandZPosIn[i + 2];
922 50 : panBandZLev[i] = panBandZLevIn[i];
923 : }
924 : }
925 :
926 : // Get the type of the "z" variable, our target raster array.
927 194 : eDataType = eTypeIn;
928 :
929 194 : switch (eDataType)
930 : {
931 87 : case GDT_UInt8:
932 87 : nc_datatype = NC_BYTE;
933 : // NC_UBYTE (unsigned byte) is only available for NC4.
934 87 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
935 3 : nc_datatype = NC_UBYTE;
936 87 : break;
937 7 : case GDT_Int8:
938 7 : nc_datatype = NC_BYTE;
939 7 : break;
940 11 : case GDT_Int16:
941 11 : nc_datatype = NC_SHORT;
942 11 : break;
943 24 : case GDT_Int32:
944 24 : nc_datatype = NC_INT;
945 24 : break;
946 15 : case GDT_Float32:
947 15 : nc_datatype = NC_FLOAT;
948 15 : break;
949 8 : case GDT_Float64:
950 8 : nc_datatype = NC_DOUBLE;
951 8 : break;
952 7 : case GDT_Int64:
953 7 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
954 : {
955 7 : nc_datatype = NC_INT64;
956 : }
957 : else
958 : {
959 0 : if (nBand == 1)
960 0 : CPLError(
961 : CE_Warning, CPLE_AppDefined,
962 : "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
963 : "Int64");
964 0 : nc_datatype = NC_DOUBLE;
965 0 : eDataType = GDT_Float64;
966 : }
967 7 : break;
968 7 : case GDT_UInt64:
969 7 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
970 : {
971 7 : nc_datatype = NC_UINT64;
972 : }
973 : else
974 : {
975 0 : if (nBand == 1)
976 0 : CPLError(
977 : CE_Warning, CPLE_AppDefined,
978 : "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
979 : "UInt64");
980 0 : nc_datatype = NC_DOUBLE;
981 0 : eDataType = GDT_Float64;
982 : }
983 7 : break;
984 6 : case GDT_UInt16:
985 6 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
986 : {
987 6 : nc_datatype = NC_USHORT;
988 6 : break;
989 : }
990 : [[fallthrough]];
991 : case GDT_UInt32:
992 6 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
993 : {
994 6 : nc_datatype = NC_UINT;
995 6 : break;
996 : }
997 : [[fallthrough]];
998 : default:
999 16 : if (nBand == 1)
1000 8 : CPLError(CE_Warning, CPLE_AppDefined,
1001 : "Unsupported GDAL datatype (%d), treat as NC_FLOAT.",
1002 8 : static_cast<int>(eDataType));
1003 16 : nc_datatype = NC_FLOAT;
1004 16 : eDataType = GDT_Float32;
1005 16 : break;
1006 : }
1007 :
1008 : // Define the variable if necessary (if nZId == -1).
1009 194 : bool bDefineVar = false;
1010 :
1011 194 : if (nZId == -1)
1012 : {
1013 172 : bDefineVar = true;
1014 :
1015 : // Make sure we are in define mode.
1016 172 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1017 :
1018 : char szTempPrivate[256 + 1];
1019 172 : const char *pszTemp = nullptr;
1020 172 : if (!pszBandName || EQUAL(pszBandName, ""))
1021 : {
1022 144 : snprintf(szTempPrivate, sizeof(szTempPrivate), "Band%d", nBand);
1023 144 : pszTemp = szTempPrivate;
1024 : }
1025 : else
1026 : {
1027 28 : pszTemp = pszBandName;
1028 : }
1029 :
1030 : int status;
1031 172 : if (nZDim > 2 && paDimIds != nullptr)
1032 : {
1033 6 : status =
1034 6 : nc_def_var(cdfid, pszTemp, nc_datatype, nZDim, paDimIds, &nZId);
1035 : }
1036 : else
1037 : {
1038 166 : int anBandDims[2] = {poNCDFDS->nYDimID, poNCDFDS->nXDimID};
1039 : status =
1040 166 : nc_def_var(cdfid, pszTemp, nc_datatype, 2, anBandDims, &nZId);
1041 : }
1042 172 : NCDF_ERR(status);
1043 172 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d) id=%d", cdfid, pszTemp,
1044 : nc_datatype, nZId);
1045 :
1046 172 : if (!pszLongName || EQUAL(pszLongName, ""))
1047 : {
1048 159 : snprintf(szTempPrivate, sizeof(szTempPrivate),
1049 : "GDAL Band Number %d", nBand);
1050 159 : pszTemp = szTempPrivate;
1051 : }
1052 : else
1053 : {
1054 13 : pszTemp = pszLongName;
1055 : }
1056 : status =
1057 172 : nc_put_att_text(cdfid, nZId, CF_LNG_NAME, strlen(pszTemp), pszTemp);
1058 172 : NCDF_ERR(status);
1059 :
1060 172 : poNCDFDS->DefVarDeflate(nZId, true);
1061 : }
1062 :
1063 : // For Byte data add signed/unsigned info.
1064 194 : if (eDataType == GDT_UInt8 || eDataType == GDT_Int8)
1065 : {
1066 94 : if (bDefineVar)
1067 : {
1068 : // Only add attributes if creating variable.
1069 : // For unsigned NC_BYTE (except NC4 format),
1070 : // add valid_range and _Unsigned ( defined in CF-1 and NUG ).
1071 86 : if (nc_datatype == NC_BYTE && poNCDFDS->eFormat != NCDF_FORMAT_NC4)
1072 : {
1073 83 : CPLDebug("GDAL_netCDF",
1074 : "adding valid_range attributes for Byte Band");
1075 83 : short l_adfValidRange[2] = {0, 0};
1076 : int status;
1077 83 : if (bSignedData || eDataType == GDT_Int8)
1078 : {
1079 7 : l_adfValidRange[0] = -128;
1080 7 : l_adfValidRange[1] = 127;
1081 7 : status =
1082 7 : nc_put_att_text(cdfid, nZId, "_Unsigned", 5, "false");
1083 : }
1084 : else
1085 : {
1086 76 : l_adfValidRange[0] = 0;
1087 76 : l_adfValidRange[1] = 255;
1088 : status =
1089 76 : nc_put_att_text(cdfid, nZId, "_Unsigned", 4, "true");
1090 : }
1091 83 : NCDF_ERR(status);
1092 83 : status = nc_put_att_short(cdfid, nZId, "valid_range", NC_SHORT,
1093 : 2, l_adfValidRange);
1094 83 : NCDF_ERR(status);
1095 : }
1096 : }
1097 : }
1098 :
1099 194 : if (nc_datatype != NC_BYTE && nc_datatype != NC_CHAR &&
1100 103 : nc_datatype != NC_UBYTE)
1101 : {
1102 : // Set default nodata.
1103 100 : bool bIgnored = false;
1104 : double dfNoData =
1105 100 : NCDFGetDefaultNoDataValue(cdfid, nZId, nc_datatype, bIgnored);
1106 : #ifdef NCDF_DEBUG
1107 : CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) default", dfNoData);
1108 : #endif
1109 100 : netCDFRasterBand::SetNoDataValue(dfNoData);
1110 : }
1111 :
1112 194 : SetBlockSize();
1113 : }
1114 :
1115 : /************************************************************************/
1116 : /* ~netCDFRasterBand() */
1117 : /************************************************************************/
1118 :
1119 1426 : netCDFRasterBand::~netCDFRasterBand()
1120 : {
1121 713 : netCDFRasterBand::FlushCache(true);
1122 713 : CPLFree(panBandZPos);
1123 713 : CPLFree(panBandZLev);
1124 1426 : }
1125 :
1126 : /************************************************************************/
1127 : /* GetMetadata() */
1128 : /************************************************************************/
1129 :
1130 60 : CSLConstList netCDFRasterBand::GetMetadata(const char *pszDomain)
1131 : {
1132 60 : if (!m_bCreateMetadataFromOtherVarsDone)
1133 58 : CreateMetadataFromOtherVars();
1134 60 : return GDALPamRasterBand::GetMetadata(pszDomain);
1135 : }
1136 :
1137 : /************************************************************************/
1138 : /* GetMetadataItem() */
1139 : /************************************************************************/
1140 :
1141 606 : const char *netCDFRasterBand::GetMetadataItem(const char *pszName,
1142 : const char *pszDomain)
1143 : {
1144 606 : if (!m_bCreateMetadataFromOtherVarsDone &&
1145 590 : STARTS_WITH(pszName, "NETCDF_DIM_") &&
1146 1 : (!pszDomain || pszDomain[0] == 0))
1147 1 : CreateMetadataFromOtherVars();
1148 606 : return GDALPamRasterBand::GetMetadataItem(pszName, pszDomain);
1149 : }
1150 :
1151 : /************************************************************************/
1152 : /* SetMetadataItem() */
1153 : /************************************************************************/
1154 :
1155 7 : CPLErr netCDFRasterBand::SetMetadataItem(const char *pszName,
1156 : const char *pszValue,
1157 : const char *pszDomain)
1158 : {
1159 9 : if (GetAccess() == GA_Update &&
1160 9 : (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)
1161 : {
1162 : // Same logic as in CopyMetadata()
1163 :
1164 2 : const char *const papszIgnoreBand[] = {
1165 : CF_ADD_OFFSET, CF_SCALE_FACTOR, "valid_range", "_Unsigned",
1166 : NCDF_FillValue, "coordinates", nullptr};
1167 : // Do not copy varname, stats, NETCDF_DIM_*, nodata
1168 : // and items in papszIgnoreBand.
1169 6 : if (STARTS_WITH(pszName, "NETCDF_VARNAME") ||
1170 2 : STARTS_WITH(pszName, "STATISTICS_") ||
1171 2 : STARTS_WITH(pszName, "NETCDF_DIM_") ||
1172 2 : STARTS_WITH(pszName, "missing_value") ||
1173 6 : STARTS_WITH(pszName, "_FillValue") ||
1174 2 : CSLFindString(papszIgnoreBand, pszName) != -1)
1175 : {
1176 : // do nothing
1177 : }
1178 : else
1179 : {
1180 2 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1181 :
1182 2 : if (!NCDFPutAttr(cdfid, nZId, pszName, pszValue))
1183 2 : return CE_Failure;
1184 : }
1185 : }
1186 :
1187 5 : return GDALPamRasterBand::SetMetadataItem(pszName, pszValue, pszDomain);
1188 : }
1189 :
1190 : /************************************************************************/
1191 : /* SetMetadata() */
1192 : /************************************************************************/
1193 :
1194 2 : CPLErr netCDFRasterBand::SetMetadata(CSLConstList papszMD,
1195 : const char *pszDomain)
1196 : {
1197 4 : if (GetAccess() == GA_Update &&
1198 2 : (pszDomain == nullptr || pszDomain[0] == '\0'))
1199 : {
1200 : // We don't handle metadata item removal for now
1201 4 : for (const char *const *papszIter = papszMD; papszIter && *papszIter;
1202 : ++papszIter)
1203 : {
1204 2 : char *pszName = nullptr;
1205 2 : const char *pszValue = CPLParseNameValue(*papszIter, &pszName);
1206 2 : if (pszName && pszValue)
1207 2 : SetMetadataItem(pszName, pszValue);
1208 2 : CPLFree(pszName);
1209 : }
1210 : }
1211 2 : return GDALPamRasterBand::SetMetadata(papszMD, pszDomain);
1212 : }
1213 :
1214 : /************************************************************************/
1215 : /* GetOffset() */
1216 : /************************************************************************/
1217 56 : double netCDFRasterBand::GetOffset(int *pbSuccess)
1218 : {
1219 56 : if (pbSuccess != nullptr)
1220 50 : *pbSuccess = static_cast<int>(m_bHaveOffset);
1221 :
1222 56 : return m_dfOffset;
1223 : }
1224 :
1225 : /************************************************************************/
1226 : /* SetOffset() */
1227 : /************************************************************************/
1228 6 : CPLErr netCDFRasterBand::SetOffset(double dfNewOffset)
1229 : {
1230 12 : CPLMutexHolderD(&hNCMutex);
1231 :
1232 : // Write value if in update mode.
1233 6 : if (poDS->GetAccess() == GA_Update)
1234 : {
1235 : // Make sure we are in define mode.
1236 6 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1237 :
1238 6 : const int status = nc_put_att_double(cdfid, nZId, CF_ADD_OFFSET,
1239 : NC_DOUBLE, 1, &dfNewOffset);
1240 :
1241 6 : NCDF_ERR(status);
1242 6 : if (status == NC_NOERR)
1243 : {
1244 6 : SetOffsetNoUpdate(dfNewOffset);
1245 6 : return CE_None;
1246 : }
1247 :
1248 0 : return CE_Failure;
1249 : }
1250 :
1251 0 : SetOffsetNoUpdate(dfNewOffset);
1252 0 : return CE_None;
1253 : }
1254 :
1255 : /************************************************************************/
1256 : /* SetOffsetNoUpdate() */
1257 : /************************************************************************/
1258 27 : void netCDFRasterBand::SetOffsetNoUpdate(double dfVal)
1259 : {
1260 27 : m_dfOffset = dfVal;
1261 27 : m_bHaveOffset = true;
1262 27 : }
1263 :
1264 : /************************************************************************/
1265 : /* GetScale() */
1266 : /************************************************************************/
1267 56 : double netCDFRasterBand::GetScale(int *pbSuccess)
1268 : {
1269 56 : if (pbSuccess != nullptr)
1270 50 : *pbSuccess = static_cast<int>(m_bHaveScale);
1271 :
1272 56 : return m_dfScale;
1273 : }
1274 :
1275 : /************************************************************************/
1276 : /* SetScale() */
1277 : /************************************************************************/
1278 1 : CPLErr netCDFRasterBand::SetScale(double dfNewScale)
1279 : {
1280 2 : CPLMutexHolderD(&hNCMutex);
1281 :
1282 : // Write value if in update mode.
1283 1 : if (poDS->GetAccess() == GA_Update)
1284 : {
1285 : // Make sure we are in define mode.
1286 1 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1287 :
1288 1 : const int status = nc_put_att_double(cdfid, nZId, CF_SCALE_FACTOR,
1289 : NC_DOUBLE, 1, &dfNewScale);
1290 :
1291 1 : NCDF_ERR(status);
1292 1 : if (status == NC_NOERR)
1293 : {
1294 1 : SetScaleNoUpdate(dfNewScale);
1295 1 : return CE_None;
1296 : }
1297 :
1298 0 : return CE_Failure;
1299 : }
1300 :
1301 0 : SetScaleNoUpdate(dfNewScale);
1302 0 : return CE_None;
1303 : }
1304 :
1305 : /************************************************************************/
1306 : /* SetScaleNoUpdate() */
1307 : /************************************************************************/
1308 24 : void netCDFRasterBand::SetScaleNoUpdate(double dfVal)
1309 : {
1310 24 : m_dfScale = dfVal;
1311 24 : m_bHaveScale = true;
1312 24 : }
1313 :
1314 : /************************************************************************/
1315 : /* GetUnitType() */
1316 : /************************************************************************/
1317 :
1318 27 : const char *netCDFRasterBand::GetUnitType()
1319 :
1320 : {
1321 27 : if (!m_osUnitType.empty())
1322 6 : return m_osUnitType;
1323 :
1324 21 : return GDALRasterBand::GetUnitType();
1325 : }
1326 :
1327 : /************************************************************************/
1328 : /* SetUnitType() */
1329 : /************************************************************************/
1330 :
1331 1 : CPLErr netCDFRasterBand::SetUnitType(const char *pszNewValue)
1332 :
1333 : {
1334 2 : CPLMutexHolderD(&hNCMutex);
1335 :
1336 2 : const std::string osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
1337 :
1338 1 : if (!osUnitType.empty())
1339 : {
1340 : // Write value if in update mode.
1341 1 : if (poDS->GetAccess() == GA_Update)
1342 : {
1343 : // Make sure we are in define mode.
1344 1 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(TRUE);
1345 :
1346 1 : const int status = nc_put_att_text(
1347 : cdfid, nZId, CF_UNITS, osUnitType.size(), osUnitType.c_str());
1348 :
1349 1 : NCDF_ERR(status);
1350 1 : if (status == NC_NOERR)
1351 : {
1352 1 : SetUnitTypeNoUpdate(pszNewValue);
1353 1 : return CE_None;
1354 : }
1355 :
1356 0 : return CE_Failure;
1357 : }
1358 : }
1359 :
1360 0 : SetUnitTypeNoUpdate(pszNewValue);
1361 :
1362 0 : return CE_None;
1363 : }
1364 :
1365 : /************************************************************************/
1366 : /* SetUnitTypeNoUpdate() */
1367 : /************************************************************************/
1368 :
1369 520 : void netCDFRasterBand::SetUnitTypeNoUpdate(const char *pszNewValue)
1370 : {
1371 520 : m_osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
1372 520 : }
1373 :
1374 : /************************************************************************/
1375 : /* GetNoDataValue() */
1376 : /************************************************************************/
1377 :
1378 208 : double netCDFRasterBand::GetNoDataValue(int *pbSuccess)
1379 :
1380 : {
1381 208 : if (m_bNoDataSetAsInt64)
1382 : {
1383 0 : if (pbSuccess)
1384 0 : *pbSuccess = TRUE;
1385 0 : return GDALGetNoDataValueCastToDouble(m_nNodataValueInt64);
1386 : }
1387 :
1388 208 : if (m_bNoDataSetAsUInt64)
1389 : {
1390 0 : if (pbSuccess)
1391 0 : *pbSuccess = TRUE;
1392 0 : return GDALGetNoDataValueCastToDouble(m_nNodataValueUInt64);
1393 : }
1394 :
1395 208 : if (m_bNoDataSet)
1396 : {
1397 145 : if (pbSuccess)
1398 128 : *pbSuccess = TRUE;
1399 145 : return m_dfNoDataValue;
1400 : }
1401 :
1402 63 : return GDALPamRasterBand::GetNoDataValue(pbSuccess);
1403 : }
1404 :
1405 : /************************************************************************/
1406 : /* GetNoDataValueAsInt64() */
1407 : /************************************************************************/
1408 :
1409 4 : int64_t netCDFRasterBand::GetNoDataValueAsInt64(int *pbSuccess)
1410 :
1411 : {
1412 4 : if (m_bNoDataSetAsInt64)
1413 : {
1414 4 : if (pbSuccess)
1415 4 : *pbSuccess = TRUE;
1416 :
1417 4 : return m_nNodataValueInt64;
1418 : }
1419 :
1420 0 : return GDALPamRasterBand::GetNoDataValueAsInt64(pbSuccess);
1421 : }
1422 :
1423 : /************************************************************************/
1424 : /* GetNoDataValueAsUInt64() */
1425 : /************************************************************************/
1426 :
1427 4 : uint64_t netCDFRasterBand::GetNoDataValueAsUInt64(int *pbSuccess)
1428 :
1429 : {
1430 4 : if (m_bNoDataSetAsUInt64)
1431 : {
1432 4 : if (pbSuccess)
1433 4 : *pbSuccess = TRUE;
1434 :
1435 4 : return m_nNodataValueUInt64;
1436 : }
1437 :
1438 0 : return GDALPamRasterBand::GetNoDataValueAsUInt64(pbSuccess);
1439 : }
1440 :
1441 : /************************************************************************/
1442 : /* SetNoDataValue() */
1443 : /************************************************************************/
1444 :
1445 137 : CPLErr netCDFRasterBand::SetNoDataValue(double dfNoData)
1446 :
1447 : {
1448 274 : CPLMutexHolderD(&hNCMutex);
1449 :
1450 : // If already set to new value, don't do anything.
1451 137 : if (m_bNoDataSet && CPLIsEqual(dfNoData, m_dfNoDataValue))
1452 19 : return CE_None;
1453 :
1454 : // Write value if in update mode.
1455 118 : if (poDS->GetAccess() == GA_Update)
1456 : {
1457 : // netcdf-4 does not allow to set _FillValue after leaving define mode,
1458 : // but it is ok if variable has not been written to, so only print
1459 : // debug. See bug #4484.
1460 129 : if (m_bNoDataSet &&
1461 11 : !cpl::down_cast<netCDFDataset *>(poDS)->GetDefineMode())
1462 : {
1463 0 : CPLDebug("GDAL_netCDF",
1464 : "Setting NoDataValue to %.17g (previously set to %.17g) "
1465 : "but file is no longer in define mode (id #%d, band #%d)",
1466 : dfNoData, m_dfNoDataValue, cdfid, nBand);
1467 : }
1468 : #ifdef NCDF_DEBUG
1469 : else
1470 : {
1471 : CPLDebug("GDAL_netCDF",
1472 : "Setting NoDataValue to %.17g (id #%d, band #%d)",
1473 : dfNoData, cdfid, nBand);
1474 : }
1475 : #endif
1476 : // Make sure we are in define mode.
1477 118 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1478 :
1479 : int status;
1480 118 : if (eDataType == GDT_UInt8)
1481 : {
1482 6 : if (bSignedData)
1483 : {
1484 0 : signed char cNoDataValue = static_cast<signed char>(dfNoData);
1485 0 : status = nc_put_att_schar(cdfid, nZId, NCDF_FillValue,
1486 : nc_datatype, 1, &cNoDataValue);
1487 : }
1488 : else
1489 : {
1490 6 : const unsigned char ucNoDataValue =
1491 6 : static_cast<unsigned char>(dfNoData);
1492 6 : status = nc_put_att_uchar(cdfid, nZId, NCDF_FillValue,
1493 : nc_datatype, 1, &ucNoDataValue);
1494 : }
1495 : }
1496 112 : else if (eDataType == GDT_Int16)
1497 : {
1498 14 : short nsNoDataValue = static_cast<short>(dfNoData);
1499 14 : status = nc_put_att_short(cdfid, nZId, NCDF_FillValue, nc_datatype,
1500 : 1, &nsNoDataValue);
1501 : }
1502 98 : else if (eDataType == GDT_Int32)
1503 : {
1504 27 : int nNoDataValue = static_cast<int>(dfNoData);
1505 27 : status = nc_put_att_int(cdfid, nZId, NCDF_FillValue, nc_datatype, 1,
1506 : &nNoDataValue);
1507 : }
1508 71 : else if (eDataType == GDT_Float32)
1509 : {
1510 34 : float fNoDataValue = static_cast<float>(dfNoData);
1511 34 : status = nc_put_att_float(cdfid, nZId, NCDF_FillValue, nc_datatype,
1512 : 1, &fNoDataValue);
1513 : }
1514 43 : else if (eDataType == GDT_UInt16 &&
1515 6 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat ==
1516 : NCDF_FORMAT_NC4)
1517 : {
1518 6 : unsigned short usNoDataValue =
1519 6 : static_cast<unsigned short>(dfNoData);
1520 6 : status = nc_put_att_ushort(cdfid, nZId, NCDF_FillValue, nc_datatype,
1521 : 1, &usNoDataValue);
1522 : }
1523 38 : else if (eDataType == GDT_UInt32 &&
1524 7 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat ==
1525 : NCDF_FORMAT_NC4)
1526 : {
1527 7 : unsigned int unNoDataValue = static_cast<unsigned int>(dfNoData);
1528 7 : status = nc_put_att_uint(cdfid, nZId, NCDF_FillValue, nc_datatype,
1529 : 1, &unNoDataValue);
1530 : }
1531 : else
1532 : {
1533 24 : status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
1534 : 1, &dfNoData);
1535 : }
1536 :
1537 118 : NCDF_ERR(status);
1538 :
1539 : // Update status if write worked.
1540 118 : if (status == NC_NOERR)
1541 : {
1542 118 : SetNoDataValueNoUpdate(dfNoData);
1543 118 : return CE_None;
1544 : }
1545 :
1546 0 : return CE_Failure;
1547 : }
1548 :
1549 0 : SetNoDataValueNoUpdate(dfNoData);
1550 0 : return CE_None;
1551 : }
1552 :
1553 : /************************************************************************/
1554 : /* SetNoDataValueNoUpdate() */
1555 : /************************************************************************/
1556 :
1557 458 : void netCDFRasterBand::SetNoDataValueNoUpdate(double dfNoData)
1558 : {
1559 458 : m_dfNoDataValue = dfNoData;
1560 458 : m_bNoDataSet = true;
1561 458 : m_bNoDataSetAsInt64 = false;
1562 458 : m_bNoDataSetAsUInt64 = false;
1563 458 : }
1564 :
1565 : /************************************************************************/
1566 : /* SetNoDataValueAsInt64() */
1567 : /************************************************************************/
1568 :
1569 3 : CPLErr netCDFRasterBand::SetNoDataValueAsInt64(int64_t nNoData)
1570 :
1571 : {
1572 6 : CPLMutexHolderD(&hNCMutex);
1573 :
1574 : // If already set to new value, don't do anything.
1575 3 : if (m_bNoDataSetAsInt64 && nNoData == m_nNodataValueInt64)
1576 0 : return CE_None;
1577 :
1578 : // Write value if in update mode.
1579 3 : if (poDS->GetAccess() == GA_Update)
1580 : {
1581 : // netcdf-4 does not allow to set NCDF_FillValue after leaving define mode,
1582 : // but it is ok if variable has not been written to, so only print
1583 : // debug. See bug #4484.
1584 3 : if (m_bNoDataSetAsInt64 &&
1585 0 : !cpl::down_cast<netCDFDataset *>(poDS)->GetDefineMode())
1586 : {
1587 0 : CPLDebug("GDAL_netCDF",
1588 : "Setting NoDataValue to " CPL_FRMT_GIB
1589 : " (previously set to " CPL_FRMT_GIB ") "
1590 : "but file is no longer in define mode (id #%d, band #%d)",
1591 : static_cast<GIntBig>(nNoData),
1592 0 : static_cast<GIntBig>(m_nNodataValueInt64), cdfid, nBand);
1593 : }
1594 : #ifdef NCDF_DEBUG
1595 : else
1596 : {
1597 : CPLDebug("GDAL_netCDF",
1598 : "Setting NoDataValue to " CPL_FRMT_GIB
1599 : " (id #%d, band #%d)",
1600 : static_cast<GIntBig>(nNoData), cdfid, nBand);
1601 : }
1602 : #endif
1603 : // Make sure we are in define mode.
1604 3 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1605 :
1606 : int status;
1607 6 : if (eDataType == GDT_Int64 &&
1608 3 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
1609 : {
1610 3 : long long tmp = static_cast<long long>(nNoData);
1611 3 : status = nc_put_att_longlong(cdfid, nZId, NCDF_FillValue,
1612 : nc_datatype, 1, &tmp);
1613 : }
1614 : else
1615 : {
1616 0 : double dfNoData = static_cast<double>(nNoData);
1617 0 : status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
1618 : 1, &dfNoData);
1619 : }
1620 :
1621 3 : NCDF_ERR(status);
1622 :
1623 : // Update status if write worked.
1624 3 : if (status == NC_NOERR)
1625 : {
1626 3 : SetNoDataValueNoUpdate(nNoData);
1627 3 : return CE_None;
1628 : }
1629 :
1630 0 : return CE_Failure;
1631 : }
1632 :
1633 0 : SetNoDataValueNoUpdate(nNoData);
1634 0 : return CE_None;
1635 : }
1636 :
1637 : /************************************************************************/
1638 : /* SetNoDataValueNoUpdate() */
1639 : /************************************************************************/
1640 :
1641 11 : void netCDFRasterBand::SetNoDataValueNoUpdate(int64_t nNoData)
1642 : {
1643 11 : m_nNodataValueInt64 = nNoData;
1644 11 : m_bNoDataSet = false;
1645 11 : m_bNoDataSetAsInt64 = true;
1646 11 : m_bNoDataSetAsUInt64 = false;
1647 11 : }
1648 :
1649 : /************************************************************************/
1650 : /* SetNoDataValueAsUInt64() */
1651 : /************************************************************************/
1652 :
1653 3 : CPLErr netCDFRasterBand::SetNoDataValueAsUInt64(uint64_t nNoData)
1654 :
1655 : {
1656 6 : CPLMutexHolderD(&hNCMutex);
1657 :
1658 : // If already set to new value, don't do anything.
1659 3 : if (m_bNoDataSetAsUInt64 && nNoData == m_nNodataValueUInt64)
1660 0 : return CE_None;
1661 :
1662 : // Write value if in update mode.
1663 3 : if (poDS->GetAccess() == GA_Update)
1664 : {
1665 : // netcdf-4 does not allow to set _FillValue after leaving define mode,
1666 : // but it is ok if variable has not been written to, so only print
1667 : // debug. See bug #4484.
1668 3 : if (m_bNoDataSetAsUInt64 &&
1669 0 : !cpl::down_cast<netCDFDataset *>(poDS)->GetDefineMode())
1670 : {
1671 0 : CPLDebug("GDAL_netCDF",
1672 : "Setting NoDataValue to " CPL_FRMT_GUIB
1673 : " (previously set to " CPL_FRMT_GUIB ") "
1674 : "but file is no longer in define mode (id #%d, band #%d)",
1675 : static_cast<GUIntBig>(nNoData),
1676 0 : static_cast<GUIntBig>(m_nNodataValueUInt64), cdfid, nBand);
1677 : }
1678 : #ifdef NCDF_DEBUG
1679 : else
1680 : {
1681 : CPLDebug("GDAL_netCDF",
1682 : "Setting NoDataValue to " CPL_FRMT_GUIB
1683 : " (id #%d, band #%d)",
1684 : static_cast<GUIntBig>(nNoData), cdfid, nBand);
1685 : }
1686 : #endif
1687 : // Make sure we are in define mode.
1688 3 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1689 :
1690 : int status;
1691 6 : if (eDataType == GDT_UInt64 &&
1692 3 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
1693 : {
1694 3 : unsigned long long tmp = static_cast<long long>(nNoData);
1695 3 : status = nc_put_att_ulonglong(cdfid, nZId, NCDF_FillValue,
1696 : nc_datatype, 1, &tmp);
1697 : }
1698 : else
1699 : {
1700 0 : double dfNoData = static_cast<double>(nNoData);
1701 0 : status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
1702 : 1, &dfNoData);
1703 : }
1704 :
1705 3 : NCDF_ERR(status);
1706 :
1707 : // Update status if write worked.
1708 3 : if (status == NC_NOERR)
1709 : {
1710 3 : SetNoDataValueNoUpdate(nNoData);
1711 3 : return CE_None;
1712 : }
1713 :
1714 0 : return CE_Failure;
1715 : }
1716 :
1717 0 : SetNoDataValueNoUpdate(nNoData);
1718 0 : return CE_None;
1719 : }
1720 :
1721 : /************************************************************************/
1722 : /* SetNoDataValueNoUpdate() */
1723 : /************************************************************************/
1724 :
1725 10 : void netCDFRasterBand::SetNoDataValueNoUpdate(uint64_t nNoData)
1726 : {
1727 10 : m_nNodataValueUInt64 = nNoData;
1728 10 : m_bNoDataSet = false;
1729 10 : m_bNoDataSetAsInt64 = false;
1730 10 : m_bNoDataSetAsUInt64 = true;
1731 10 : }
1732 :
1733 : /************************************************************************/
1734 : /* DeleteNoDataValue() */
1735 : /************************************************************************/
1736 :
1737 : #ifdef notdef
1738 : CPLErr netCDFRasterBand::DeleteNoDataValue()
1739 :
1740 : {
1741 : CPLMutexHolderD(&hNCMutex);
1742 :
1743 : if (!bNoDataSet)
1744 : return CE_None;
1745 :
1746 : // Write value if in update mode.
1747 : if (poDS->GetAccess() == GA_Update)
1748 : {
1749 : // Make sure we are in define mode.
1750 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1751 :
1752 : status = nc_del_att(cdfid, nZId, NCDF_FillValue);
1753 :
1754 : NCDF_ERR(status);
1755 :
1756 : // Update status if write worked.
1757 : if (status == NC_NOERR)
1758 : {
1759 : dfNoDataValue = 0.0;
1760 : bNoDataSet = false;
1761 : return CE_None;
1762 : }
1763 :
1764 : return CE_Failure;
1765 : }
1766 :
1767 : dfNoDataValue = 0.0;
1768 : bNoDataSet = false;
1769 : return CE_None;
1770 : }
1771 : #endif
1772 :
1773 : /************************************************************************/
1774 : /* SerializeToXML() */
1775 : /************************************************************************/
1776 :
1777 5 : CPLXMLNode *netCDFRasterBand::SerializeToXML(const char * /* pszUnused */)
1778 : {
1779 : // Overridden from GDALPamDataset to add only band histogram
1780 : // and statistics. See bug #4244.
1781 5 : if (psPam == nullptr)
1782 0 : return nullptr;
1783 :
1784 : // Setup root node and attributes.
1785 : CPLXMLNode *psTree =
1786 5 : CPLCreateXMLNode(nullptr, CXT_Element, "PAMRasterBand");
1787 :
1788 5 : if (GetBand() > 0)
1789 : {
1790 10 : CPLString oFmt;
1791 5 : CPLSetXMLValue(psTree, "#band", oFmt.Printf("%d", GetBand()));
1792 : }
1793 :
1794 : // Histograms.
1795 5 : if (psPam->psSavedHistograms != nullptr)
1796 1 : CPLAddXMLChild(psTree, CPLCloneXMLTree(psPam->psSavedHistograms));
1797 :
1798 : // Metadata (statistics only).
1799 5 : GDALMultiDomainMetadata oMDMDStats;
1800 5 : const char *papszMDStats[] = {"STATISTICS_MINIMUM", "STATISTICS_MAXIMUM",
1801 : "STATISTICS_MEAN", "STATISTICS_STDDEV",
1802 : nullptr};
1803 25 : for (int i = 0; i < CSLCount(papszMDStats); i++)
1804 : {
1805 20 : const char *pszMDI = GetMetadataItem(papszMDStats[i]);
1806 20 : if (pszMDI)
1807 4 : oMDMDStats.SetMetadataItem(papszMDStats[i], pszMDI);
1808 : }
1809 5 : CPLXMLNode *psMD = oMDMDStats.Serialize();
1810 :
1811 5 : if (psMD != nullptr)
1812 : {
1813 1 : if (psMD->psChild == nullptr)
1814 0 : CPLDestroyXMLNode(psMD);
1815 : else
1816 1 : CPLAddXMLChild(psTree, psMD);
1817 : }
1818 :
1819 : // We don't want to return anything if we had no metadata to attach.
1820 5 : if (psTree->psChild == nullptr || psTree->psChild->psNext == nullptr)
1821 : {
1822 3 : CPLDestroyXMLNode(psTree);
1823 3 : psTree = nullptr;
1824 : }
1825 :
1826 5 : return psTree;
1827 : }
1828 :
1829 : /************************************************************************/
1830 : /* Get1DVariableIndexedByDimension() */
1831 : /************************************************************************/
1832 :
1833 85 : static int Get1DVariableIndexedByDimension(int cdfid, int nDimId,
1834 : const char *pszDimName,
1835 : bool bVerboseError, int *pnGroupID)
1836 : {
1837 85 : *pnGroupID = -1;
1838 85 : int nVarID = -1;
1839 : // First try to find a variable whose name is identical to the dimension
1840 : // name, and check that it is indeed indexed by this dimension
1841 85 : if (NCDFResolveVar(cdfid, pszDimName, pnGroupID, &nVarID) == CE_None)
1842 : {
1843 71 : int nDimCountOfVariable = 0;
1844 71 : nc_inq_varndims(*pnGroupID, nVarID, &nDimCountOfVariable);
1845 71 : if (nDimCountOfVariable == 1)
1846 : {
1847 71 : int nDimIdOfVariable = -1;
1848 71 : nc_inq_vardimid(*pnGroupID, nVarID, &nDimIdOfVariable);
1849 71 : if (nDimIdOfVariable == nDimId)
1850 : {
1851 71 : return nVarID;
1852 : }
1853 : }
1854 : }
1855 :
1856 : // Otherwise iterate over the variables to find potential candidates
1857 : // TODO: should be modified to search also in other groups using the same
1858 : // logic than in NCDFResolveVar(), but maybe not needed if it's a
1859 : // very rare case? and I think this is not CF compliant.
1860 14 : int nvars = 0;
1861 14 : CPL_IGNORE_RET_VAL(nc_inq(cdfid, nullptr, &nvars, nullptr, nullptr));
1862 :
1863 14 : int nCountCandidateVars = 0;
1864 14 : int nCandidateVarID = -1;
1865 65 : for (int k = 0; k < nvars; k++)
1866 : {
1867 51 : int nDimCountOfVariable = 0;
1868 51 : nc_inq_varndims(cdfid, k, &nDimCountOfVariable);
1869 51 : if (nDimCountOfVariable == 1)
1870 : {
1871 27 : int nDimIdOfVariable = -1;
1872 27 : nc_inq_vardimid(cdfid, k, &nDimIdOfVariable);
1873 27 : if (nDimIdOfVariable == nDimId)
1874 : {
1875 7 : nCountCandidateVars++;
1876 7 : nCandidateVarID = k;
1877 : }
1878 : }
1879 : }
1880 14 : if (nCountCandidateVars > 1)
1881 : {
1882 1 : if (bVerboseError)
1883 : {
1884 1 : CPLError(CE_Warning, CPLE_AppDefined,
1885 : "Several 1D variables are indexed by dimension %s",
1886 : pszDimName);
1887 : }
1888 1 : *pnGroupID = -1;
1889 1 : return -1;
1890 : }
1891 13 : else if (nCandidateVarID < 0)
1892 : {
1893 8 : if (bVerboseError)
1894 : {
1895 8 : CPLError(CE_Warning, CPLE_AppDefined,
1896 : "No 1D variable is indexed by dimension %s", pszDimName);
1897 : }
1898 : }
1899 13 : *pnGroupID = cdfid;
1900 13 : return nCandidateVarID;
1901 : }
1902 :
1903 : /************************************************************************/
1904 : /* CreateMetadataFromAttributes() */
1905 : /************************************************************************/
1906 :
1907 519 : void netCDFRasterBand::CreateMetadataFromAttributes()
1908 : {
1909 519 : char szVarName[NC_MAX_NAME + 1] = {};
1910 519 : int status = nc_inq_varname(cdfid, nZId, szVarName);
1911 519 : NCDF_ERR(status);
1912 :
1913 519 : GDALPamRasterBand::SetMetadataItem("NETCDF_VARNAME", szVarName);
1914 :
1915 : // Get attribute metadata.
1916 519 : int nAtt = 0;
1917 519 : NCDF_ERR(nc_inq_varnatts(cdfid, nZId, &nAtt));
1918 :
1919 2291 : for (int i = 0; i < nAtt; i++)
1920 : {
1921 1772 : char szMetaName[NC_MAX_NAME + 1] = {};
1922 1772 : status = nc_inq_attname(cdfid, nZId, i, szMetaName);
1923 1772 : if (status != NC_NOERR)
1924 12 : continue;
1925 :
1926 1772 : if (GDALPamRasterBand::GetMetadataItem(szMetaName) != nullptr)
1927 : {
1928 12 : continue;
1929 : }
1930 :
1931 1760 : char *pszMetaValue = nullptr;
1932 1760 : if (NCDFGetAttr(cdfid, nZId, szMetaName, &pszMetaValue) == CE_None)
1933 : {
1934 1760 : GDALPamRasterBand::SetMetadataItem(szMetaName, pszMetaValue);
1935 : }
1936 : else
1937 : {
1938 0 : CPLDebug("GDAL_netCDF", "invalid Band metadata %s", szMetaName);
1939 : }
1940 :
1941 1760 : if (pszMetaValue)
1942 : {
1943 1760 : CPLFree(pszMetaValue);
1944 1760 : pszMetaValue = nullptr;
1945 : }
1946 : }
1947 519 : }
1948 :
1949 : /************************************************************************/
1950 : /* CreateMetadataFromOtherVars() */
1951 : /************************************************************************/
1952 :
1953 59 : void netCDFRasterBand::CreateMetadataFromOtherVars()
1954 :
1955 : {
1956 59 : CPLAssert(!m_bCreateMetadataFromOtherVarsDone);
1957 59 : m_bCreateMetadataFromOtherVarsDone = true;
1958 :
1959 59 : netCDFDataset *l_poDS = cpl::down_cast<netCDFDataset *>(poDS);
1960 59 : const int nPamFlagsBackup = l_poDS->nPamFlags;
1961 :
1962 : // Compute all dimensions from Band number and save in Metadata.
1963 59 : int nd = 0;
1964 59 : nc_inq_varndims(cdfid, nZId, &nd);
1965 : // Compute multidimention band position.
1966 : //
1967 : // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
1968 : // if Data[2,3,4,x,y]
1969 : //
1970 : // BandPos0 = (nBand) / (3*4)
1971 : // BandPos1 = (nBand - BandPos0*(3*4)) / (4)
1972 : // BandPos2 = (nBand - BandPos0*(3*4)) % (4)
1973 :
1974 59 : int Sum = 1;
1975 59 : if (nd == 3)
1976 : {
1977 6 : Sum *= panBandZLev[0];
1978 : }
1979 :
1980 : // Loop over non-spatial dimensions.
1981 59 : int Taken = 0;
1982 :
1983 100 : for (int i = 0; i < nd - 2; i++)
1984 : {
1985 : int result;
1986 41 : if (i != nd - 2 - 1)
1987 : {
1988 18 : Sum = 1;
1989 37 : for (int j = i + 1; j < nd - 2; j++)
1990 : {
1991 19 : Sum *= panBandZLev[j];
1992 : }
1993 18 : result = static_cast<int>((nLevel - Taken) / Sum);
1994 : }
1995 : else
1996 : {
1997 23 : result = static_cast<int>((nLevel - Taken) % Sum);
1998 : }
1999 :
2000 41 : char szName[NC_MAX_NAME + 1] = {};
2001 41 : snprintf(szName, sizeof(szName), "%s",
2002 41 : l_poDS->papszDimName[l_poDS->m_anDimIds[panBandZPos[i]]]);
2003 :
2004 : char szMetaName[NC_MAX_NAME + 1 + 32];
2005 41 : snprintf(szMetaName, sizeof(szMetaName), "NETCDF_DIM_%s", szName);
2006 :
2007 41 : const int nGroupID = l_poDS->m_anExtraDimGroupIds[i];
2008 41 : const int nVarID = l_poDS->m_anExtraDimVarIds[i];
2009 41 : if (nVarID < 0)
2010 : {
2011 2 : GDALPamRasterBand::SetMetadataItem(szMetaName,
2012 : CPLSPrintf("%d", result + 1));
2013 : }
2014 : else
2015 : {
2016 : // TODO: Make sure all the status checks make sense.
2017 :
2018 39 : nc_type nVarType = NC_NAT;
2019 39 : /* status = */ nc_inq_vartype(nGroupID, nVarID, &nVarType);
2020 :
2021 39 : int nDims = 0;
2022 39 : /* status = */ nc_inq_varndims(nGroupID, nVarID, &nDims);
2023 :
2024 39 : char szMetaTemp[256] = {};
2025 39 : if (nDims == 1)
2026 : {
2027 39 : size_t count[1] = {1};
2028 39 : size_t start[1] = {static_cast<size_t>(result)};
2029 :
2030 39 : switch (nVarType)
2031 : {
2032 0 : case NC_BYTE:
2033 : // TODO: Check for signed/unsigned byte.
2034 : signed char cData;
2035 0 : /* status = */ nc_get_vara_schar(nGroupID, nVarID,
2036 : start, count, &cData);
2037 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", cData);
2038 0 : break;
2039 0 : case NC_SHORT:
2040 : short sData;
2041 0 : /* status = */ nc_get_vara_short(nGroupID, nVarID,
2042 : start, count, &sData);
2043 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", sData);
2044 0 : break;
2045 19 : case NC_INT:
2046 : {
2047 : int nData;
2048 19 : /* status = */ nc_get_vara_int(nGroupID, nVarID, start,
2049 : count, &nData);
2050 19 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", nData);
2051 19 : break;
2052 : }
2053 0 : case NC_FLOAT:
2054 : float fData;
2055 0 : /* status = */ nc_get_vara_float(nGroupID, nVarID,
2056 : start, count, &fData);
2057 0 : CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.8g",
2058 : fData);
2059 0 : break;
2060 18 : case NC_DOUBLE:
2061 : double dfData;
2062 18 : /* status = */ nc_get_vara_double(
2063 : nGroupID, nVarID, start, count, &dfData);
2064 18 : CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.16g",
2065 : dfData);
2066 18 : break;
2067 0 : case NC_UBYTE:
2068 : unsigned char ucData;
2069 0 : /* status = */ nc_get_vara_uchar(nGroupID, nVarID,
2070 : start, count, &ucData);
2071 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", ucData);
2072 0 : break;
2073 0 : case NC_USHORT:
2074 : unsigned short usData;
2075 0 : /* status = */ nc_get_vara_ushort(
2076 : nGroupID, nVarID, start, count, &usData);
2077 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", usData);
2078 0 : break;
2079 0 : case NC_UINT:
2080 : {
2081 : unsigned int unData;
2082 0 : /* status = */ nc_get_vara_uint(nGroupID, nVarID, start,
2083 : count, &unData);
2084 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", unData);
2085 0 : break;
2086 : }
2087 2 : case NC_INT64:
2088 : {
2089 : long long nData;
2090 2 : /* status = */ nc_get_vara_longlong(
2091 : nGroupID, nVarID, start, count, &nData);
2092 2 : snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GIB,
2093 : nData);
2094 2 : break;
2095 : }
2096 0 : case NC_UINT64:
2097 : {
2098 : unsigned long long unData;
2099 0 : /* status = */ nc_get_vara_ulonglong(
2100 : nGroupID, nVarID, start, count, &unData);
2101 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GUIB,
2102 : unData);
2103 0 : break;
2104 : }
2105 0 : default:
2106 0 : CPLDebug("GDAL_netCDF", "invalid dim %s, type=%d",
2107 : szMetaTemp, nVarType);
2108 0 : break;
2109 : }
2110 : }
2111 : else
2112 : {
2113 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", result + 1);
2114 : }
2115 :
2116 : // Save dimension value.
2117 : // NOTE: removed #original_units as not part of CF-1.
2118 :
2119 39 : GDALPamRasterBand::SetMetadataItem(szMetaName, szMetaTemp);
2120 : }
2121 :
2122 : // Avoid int32 overflow. Perhaps something more sensible to do here ?
2123 41 : if (result > 0 && Sum > INT_MAX / result)
2124 0 : break;
2125 41 : if (Taken > INT_MAX - result * Sum)
2126 0 : break;
2127 :
2128 41 : Taken += result * Sum;
2129 : } // End loop non-spatial dimensions.
2130 :
2131 59 : l_poDS->nPamFlags = nPamFlagsBackup;
2132 59 : }
2133 :
2134 : /************************************************************************/
2135 : /* CheckData() */
2136 : /************************************************************************/
2137 : template <class T>
2138 6054 : void netCDFRasterBand::CheckData(void *pImage, void *pImageNC,
2139 : size_t nTmpBlockXSize, size_t nTmpBlockYSize,
2140 : bool bCheckIsNan)
2141 : {
2142 6054 : CPLAssert(pImage != nullptr && pImageNC != nullptr);
2143 :
2144 : // If this block is not a full block (in the x axis), we need to re-arrange
2145 : // the data this is because partial blocks are not arranged the same way in
2146 : // netcdf and gdal.
2147 6054 : if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
2148 : {
2149 6 : T *ptrWrite = static_cast<T *>(pImage);
2150 6 : T *ptrRead = static_cast<T *>(pImageNC);
2151 29 : for (size_t j = 0; j < nTmpBlockYSize;
2152 23 : j++, ptrWrite += nBlockXSize, ptrRead += nTmpBlockXSize)
2153 : {
2154 23 : memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T));
2155 : }
2156 : }
2157 :
2158 : // Is valid data checking needed or requested?
2159 6054 : if (bValidRangeValid || bCheckIsNan)
2160 : {
2161 1365 : T *ptrImage = static_cast<T *>(pImage);
2162 2784 : for (size_t j = 0; j < nTmpBlockYSize; j++)
2163 : {
2164 : // k moves along the gdal block, skipping the out-of-range pixels.
2165 1419 : size_t k = j * nBlockXSize;
2166 99038 : for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
2167 : {
2168 : // Check for nodata and nan.
2169 97619 : if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
2170 6301 : continue;
2171 91318 : if (bCheckIsNan && std::isnan((double)ptrImage[k]))
2172 : {
2173 5737 : ptrImage[k] = (T)m_dfNoDataValue;
2174 5737 : continue;
2175 : }
2176 : // Check for valid_range.
2177 85581 : if (bValidRangeValid)
2178 : {
2179 40986 : if (((adfValidRange[0] != m_dfNoDataValue) &&
2180 40986 : (ptrImage[k] < (T)adfValidRange[0])) ||
2181 40983 : ((adfValidRange[1] != m_dfNoDataValue) &&
2182 40983 : (ptrImage[k] > (T)adfValidRange[1])))
2183 : {
2184 4 : ptrImage[k] = (T)m_dfNoDataValue;
2185 : }
2186 : }
2187 : }
2188 : }
2189 : }
2190 :
2191 : // If minimum longitude is > 180, subtract 360 from all.
2192 : // If not, disable checking for further calls (check just once).
2193 : // Only check first and last block elements since lon must be monotonic.
2194 6054 : const bool bIsSigned = std::numeric_limits<T>::is_signed;
2195 5665 : if (bCheckLongitude && bIsSigned &&
2196 11 : !CPLIsEqual((double)((T *)pImage)[0], m_dfNoDataValue) &&
2197 10 : !CPLIsEqual((double)((T *)pImage)[nTmpBlockXSize - 1],
2198 2838 : m_dfNoDataValue) &&
2199 10 : std::min(((T *)pImage)[0], ((T *)pImage)[nTmpBlockXSize - 1]) > 180.0)
2200 : {
2201 0 : T *ptrImage = static_cast<T *>(pImage);
2202 0 : for (size_t j = 0; j < nTmpBlockYSize; j++)
2203 : {
2204 0 : size_t k = j * nBlockXSize;
2205 0 : for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
2206 : {
2207 0 : if (!CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
2208 0 : ptrImage[k] = static_cast<T>(ptrImage[k] - 360);
2209 : }
2210 : }
2211 : }
2212 : else
2213 : {
2214 6054 : bCheckLongitude = false;
2215 : }
2216 6054 : }
2217 :
2218 : /************************************************************************/
2219 : /* CheckDataCpx() */
2220 : /************************************************************************/
2221 : template <class T>
2222 25 : void netCDFRasterBand::CheckDataCpx(void *pImage, void *pImageNC,
2223 : size_t nTmpBlockXSize,
2224 : size_t nTmpBlockYSize, bool bCheckIsNan)
2225 : {
2226 25 : CPLAssert(pImage != nullptr && pImageNC != nullptr);
2227 :
2228 : // If this block is not a full block (in the x axis), we need to re-arrange
2229 : // the data this is because partial blocks are not arranged the same way in
2230 : // netcdf and gdal.
2231 25 : if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
2232 : {
2233 0 : T *ptrWrite = static_cast<T *>(pImage);
2234 0 : T *ptrRead = static_cast<T *>(pImageNC);
2235 0 : for (size_t j = 0; j < nTmpBlockYSize; j++,
2236 0 : ptrWrite += (2 * nBlockXSize),
2237 0 : ptrRead += (2 * nTmpBlockXSize))
2238 : {
2239 0 : memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T) * 2);
2240 : }
2241 : }
2242 :
2243 : // Is valid data checking needed or requested?
2244 25 : if (bValidRangeValid || bCheckIsNan)
2245 : {
2246 0 : T *ptrImage = static_cast<T *>(pImage);
2247 0 : for (size_t j = 0; j < nTmpBlockYSize; j++)
2248 : {
2249 : // k moves along the gdal block, skipping the out-of-range pixels.
2250 0 : size_t k = 2 * j * nBlockXSize;
2251 0 : for (size_t i = 0; i < (2 * nTmpBlockXSize); i++, k++)
2252 : {
2253 : // Check for nodata and nan.
2254 0 : if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
2255 0 : continue;
2256 0 : if (bCheckIsNan && std::isnan((double)ptrImage[k]))
2257 : {
2258 0 : ptrImage[k] = (T)m_dfNoDataValue;
2259 0 : continue;
2260 : }
2261 : // Check for valid_range.
2262 0 : if (bValidRangeValid)
2263 : {
2264 0 : if (((adfValidRange[0] != m_dfNoDataValue) &&
2265 0 : (ptrImage[k] < (T)adfValidRange[0])) ||
2266 0 : ((adfValidRange[1] != m_dfNoDataValue) &&
2267 0 : (ptrImage[k] > (T)adfValidRange[1])))
2268 : {
2269 0 : ptrImage[k] = (T)m_dfNoDataValue;
2270 : }
2271 : }
2272 : }
2273 : }
2274 : }
2275 25 : }
2276 :
2277 : /************************************************************************/
2278 : /* FetchNetcdfChunk() */
2279 : /************************************************************************/
2280 :
2281 6079 : bool netCDFRasterBand::FetchNetcdfChunk(size_t xstart, size_t ystart,
2282 : void *pImage)
2283 : {
2284 6079 : size_t start[MAX_NC_DIMS] = {};
2285 6079 : size_t edge[MAX_NC_DIMS] = {};
2286 :
2287 6079 : start[nBandXPos] = xstart;
2288 6079 : edge[nBandXPos] = nBlockXSize;
2289 6079 : if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
2290 6 : edge[nBandXPos] = nRasterXSize - start[nBandXPos];
2291 6079 : if (nBandYPos >= 0)
2292 : {
2293 6075 : start[nBandYPos] = ystart;
2294 6075 : edge[nBandYPos] = nBlockYSize;
2295 6075 : if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
2296 4 : edge[nBandYPos] = nRasterYSize - start[nBandYPos];
2297 : }
2298 6079 : const size_t nYChunkSize = nBandYPos < 0 ? 1 : edge[nBandYPos];
2299 :
2300 : #ifdef NCDF_DEBUG
2301 : CPLDebug("GDAL_netCDF", "start={%ld,%ld} edge={%ld,%ld} bBottomUp=%d",
2302 : start[nBandXPos], nBandYPos < 0 ? 0 : start[nBandYPos],
2303 : edge[nBandXPos], nYChunkSize, ((netCDFDataset *)poDS)->bBottomUp);
2304 : #endif
2305 :
2306 6079 : int nd = 0;
2307 6079 : nc_inq_varndims(cdfid, nZId, &nd);
2308 6079 : if (nd == 3)
2309 : {
2310 1120 : start[panBandZPos[0]] = nLevel; // z
2311 1120 : edge[panBandZPos[0]] = 1;
2312 : }
2313 :
2314 : // Compute multidimention band position.
2315 : //
2316 : // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
2317 : // if Data[2,3,4,x,y]
2318 : //
2319 : // BandPos0 = (nBand) / (3*4)
2320 : // BandPos1 = (nBand - (3*4)) / (4)
2321 : // BandPos2 = (nBand - (3*4)) % (4)
2322 6079 : if (nd > 3)
2323 : {
2324 160 : int Sum = -1;
2325 160 : int Taken = 0;
2326 480 : for (int i = 0; i < nd - 2; i++)
2327 : {
2328 320 : if (i != nd - 2 - 1)
2329 : {
2330 160 : Sum = 1;
2331 320 : for (int j = i + 1; j < nd - 2; j++)
2332 : {
2333 160 : Sum *= panBandZLev[j];
2334 : }
2335 160 : start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
2336 160 : edge[panBandZPos[i]] = 1;
2337 : }
2338 : else
2339 : {
2340 160 : start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
2341 160 : edge[panBandZPos[i]] = 1;
2342 : }
2343 320 : Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
2344 : }
2345 : }
2346 :
2347 : // Make sure we are in data mode.
2348 6079 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
2349 :
2350 : // If this block is not a full block in the x axis, we need to
2351 : // re-arrange the data because partial blocks are not arranged the
2352 : // same way in netcdf and gdal, so we first we read the netcdf data at
2353 : // the end of the gdal block buffer then re-arrange rows in CheckData().
2354 6079 : void *pImageNC = pImage;
2355 6079 : if (edge[nBandXPos] != static_cast<size_t>(nBlockXSize))
2356 : {
2357 6 : pImageNC = static_cast<GByte *>(pImage) +
2358 6 : ((static_cast<size_t>(nBlockXSize) * nBlockYSize -
2359 12 : edge[nBandXPos] * nYChunkSize) *
2360 6 : GDALGetDataTypeSizeBytes(eDataType));
2361 : }
2362 :
2363 : // Read data according to type.
2364 : int status;
2365 6079 : if (eDataType == GDT_UInt8)
2366 : {
2367 3205 : if (bSignedData)
2368 : {
2369 0 : status = nc_get_vara_schar(cdfid, nZId, start, edge,
2370 : static_cast<signed char *>(pImageNC));
2371 0 : if (status == NC_NOERR)
2372 0 : CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
2373 : nYChunkSize, false);
2374 : }
2375 : else
2376 : {
2377 3205 : status = nc_get_vara_uchar(cdfid, nZId, start, edge,
2378 : static_cast<unsigned char *>(pImageNC));
2379 3205 : if (status == NC_NOERR)
2380 3205 : CheckData<unsigned char>(pImage, pImageNC, edge[nBandXPos],
2381 : nYChunkSize, false);
2382 : }
2383 : }
2384 2874 : else if (eDataType == GDT_Int8)
2385 : {
2386 60 : status = nc_get_vara_schar(cdfid, nZId, start, edge,
2387 : static_cast<signed char *>(pImageNC));
2388 60 : if (status == NC_NOERR)
2389 60 : CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
2390 : nYChunkSize, false);
2391 : }
2392 2814 : else if (nc_datatype == NC_SHORT)
2393 : {
2394 487 : status = nc_get_vara_short(cdfid, nZId, start, edge,
2395 : static_cast<short *>(pImageNC));
2396 487 : if (status == NC_NOERR)
2397 : {
2398 487 : if (eDataType == GDT_Int16)
2399 : {
2400 484 : CheckData<GInt16>(pImage, pImageNC, edge[nBandXPos],
2401 : nYChunkSize, false);
2402 : }
2403 : else
2404 : {
2405 3 : CheckData<GUInt16>(pImage, pImageNC, edge[nBandXPos],
2406 : nYChunkSize, false);
2407 : }
2408 : }
2409 : }
2410 2327 : else if (eDataType == GDT_Int32)
2411 : {
2412 : #if SIZEOF_UNSIGNED_LONG == 4
2413 : status = nc_get_vara_long(cdfid, nZId, start, edge,
2414 : static_cast<long *>(pImageNC));
2415 : if (status == NC_NOERR)
2416 : CheckData<long>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2417 : false);
2418 : #else
2419 912 : status = nc_get_vara_int(cdfid, nZId, start, edge,
2420 : static_cast<int *>(pImageNC));
2421 912 : if (status == NC_NOERR)
2422 912 : CheckData<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2423 : false);
2424 : #endif
2425 : }
2426 1415 : else if (eDataType == GDT_Float32)
2427 : {
2428 1278 : status = nc_get_vara_float(cdfid, nZId, start, edge,
2429 : static_cast<float *>(pImageNC));
2430 1278 : if (status == NC_NOERR)
2431 1278 : CheckData<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2432 : true);
2433 : }
2434 137 : else if (eDataType == GDT_Float64)
2435 : {
2436 86 : status = nc_get_vara_double(cdfid, nZId, start, edge,
2437 : static_cast<double *>(pImageNC));
2438 86 : if (status == NC_NOERR)
2439 86 : CheckData<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2440 : true);
2441 : }
2442 51 : else if (eDataType == GDT_UInt16)
2443 : {
2444 6 : status = nc_get_vara_ushort(cdfid, nZId, start, edge,
2445 : static_cast<unsigned short *>(pImageNC));
2446 6 : if (status == NC_NOERR)
2447 6 : CheckData<unsigned short>(pImage, pImageNC, edge[nBandXPos],
2448 : nYChunkSize, false);
2449 : }
2450 45 : else if (eDataType == GDT_UInt32)
2451 : {
2452 6 : status = nc_get_vara_uint(cdfid, nZId, start, edge,
2453 : static_cast<unsigned int *>(pImageNC));
2454 6 : if (status == NC_NOERR)
2455 6 : CheckData<unsigned int>(pImage, pImageNC, edge[nBandXPos],
2456 : nYChunkSize, false);
2457 : }
2458 39 : else if (eDataType == GDT_Int64)
2459 : {
2460 7 : status = nc_get_vara_longlong(cdfid, nZId, start, edge,
2461 : static_cast<long long *>(pImageNC));
2462 7 : if (status == NC_NOERR)
2463 7 : CheckData<std::int64_t>(pImage, pImageNC, edge[nBandXPos],
2464 : nYChunkSize, false);
2465 : }
2466 32 : else if (eDataType == GDT_UInt64)
2467 : {
2468 : status =
2469 7 : nc_get_vara_ulonglong(cdfid, nZId, start, edge,
2470 : static_cast<unsigned long long *>(pImageNC));
2471 7 : if (status == NC_NOERR)
2472 7 : CheckData<std::uint64_t>(pImage, pImageNC, edge[nBandXPos],
2473 : nYChunkSize, false);
2474 : }
2475 25 : else if (eDataType == GDT_CInt16)
2476 : {
2477 0 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2478 0 : if (status == NC_NOERR)
2479 0 : CheckDataCpx<short>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2480 : false);
2481 : }
2482 25 : else if (eDataType == GDT_CInt32)
2483 : {
2484 0 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2485 0 : if (status == NC_NOERR)
2486 0 : CheckDataCpx<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2487 : false);
2488 : }
2489 25 : else if (eDataType == GDT_CFloat32)
2490 : {
2491 20 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2492 20 : if (status == NC_NOERR)
2493 20 : CheckDataCpx<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2494 : false);
2495 : }
2496 5 : else if (eDataType == GDT_CFloat64)
2497 : {
2498 5 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2499 5 : if (status == NC_NOERR)
2500 5 : CheckDataCpx<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2501 : false);
2502 : }
2503 :
2504 : else
2505 0 : status = NC_EBADTYPE;
2506 :
2507 6079 : if (status != NC_NOERR)
2508 : {
2509 0 : CPLError(CE_Failure, CPLE_AppDefined,
2510 : "netCDF chunk fetch failed: #%d (%s)", status,
2511 : nc_strerror(status));
2512 0 : return false;
2513 : }
2514 6079 : return true;
2515 : }
2516 :
2517 : /************************************************************************/
2518 : /* IReadBlock() */
2519 : /************************************************************************/
2520 :
2521 6079 : CPLErr netCDFRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
2522 : void *pImage)
2523 :
2524 : {
2525 12158 : CPLMutexHolderD(&hNCMutex);
2526 :
2527 : // Locate X, Y and Z position in the array.
2528 :
2529 6079 : size_t xstart = static_cast<size_t>(nBlockXOff) * nBlockXSize;
2530 6079 : size_t ystart = 0;
2531 :
2532 : // Check y order.
2533 6079 : if (nBandYPos >= 0)
2534 : {
2535 6075 : auto poGDS = cpl::down_cast<netCDFDataset *>(poDS);
2536 6075 : if (poGDS->bBottomUp)
2537 : {
2538 5118 : if (nBlockYSize == 1)
2539 : {
2540 5105 : ystart = nRasterYSize - 1 - nBlockYOff;
2541 : }
2542 : else
2543 : {
2544 : // in GDAL space
2545 13 : ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
2546 : const size_t yend =
2547 26 : std::min(ystart + nBlockYSize - 1,
2548 13 : static_cast<size_t>(nRasterYSize - 1));
2549 : // in netCDF space
2550 13 : const size_t nFirstChunkLine = nRasterYSize - 1 - yend;
2551 13 : const size_t nLastChunkLine = nRasterYSize - 1 - ystart;
2552 13 : const size_t nFirstChunkBlock = nFirstChunkLine / nBlockYSize;
2553 13 : const size_t nLastChunkBlock = nLastChunkLine / nBlockYSize;
2554 :
2555 : const auto firstKey = netCDFDataset::ChunkKey(
2556 13 : nBlockXOff, nFirstChunkBlock, nBand);
2557 : const auto secondKey =
2558 13 : netCDFDataset::ChunkKey(nBlockXOff, nLastChunkBlock, nBand);
2559 :
2560 : // Retrieve data from the one or 2 needed netCDF chunks
2561 13 : std::shared_ptr<std::vector<GByte>> firstChunk;
2562 13 : std::shared_ptr<std::vector<GByte>> secondChunk;
2563 13 : if (poGDS->poChunkCache)
2564 : {
2565 13 : poGDS->poChunkCache->tryGet(firstKey, firstChunk);
2566 13 : if (firstKey != secondKey)
2567 6 : poGDS->poChunkCache->tryGet(secondKey, secondChunk);
2568 : }
2569 : const size_t nChunkLineSize =
2570 13 : static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
2571 13 : nBlockXSize;
2572 13 : const size_t nChunkSize = nChunkLineSize * nBlockYSize;
2573 13 : if (!firstChunk)
2574 : {
2575 11 : firstChunk.reset(new std::vector<GByte>(nChunkSize));
2576 11 : if (!FetchNetcdfChunk(xstart,
2577 11 : nFirstChunkBlock * nBlockYSize,
2578 11 : firstChunk.get()->data()))
2579 0 : return CE_Failure;
2580 11 : if (poGDS->poChunkCache)
2581 11 : poGDS->poChunkCache->insert(firstKey, firstChunk);
2582 : }
2583 13 : if (!secondChunk && firstKey != secondKey)
2584 : {
2585 2 : secondChunk.reset(new std::vector<GByte>(nChunkSize));
2586 2 : if (!FetchNetcdfChunk(xstart, nLastChunkBlock * nBlockYSize,
2587 2 : secondChunk.get()->data()))
2588 0 : return CE_Failure;
2589 2 : if (poGDS->poChunkCache)
2590 2 : poGDS->poChunkCache->insert(secondKey, secondChunk);
2591 : }
2592 :
2593 : // Assemble netCDF chunks into GDAL block
2594 13 : GByte *pabyImage = static_cast<GByte *>(pImage);
2595 13 : const size_t nFirstChunkBlockLine =
2596 13 : nFirstChunkBlock * nBlockYSize;
2597 13 : const size_t nLastChunkBlockLine =
2598 13 : nLastChunkBlock * nBlockYSize;
2599 146 : for (size_t iLine = ystart; iLine <= yend; iLine++)
2600 : {
2601 133 : const size_t nLineFromBottom = nRasterYSize - 1 - iLine;
2602 133 : const size_t nChunkY = nLineFromBottom / nBlockYSize;
2603 133 : if (nChunkY == nFirstChunkBlock)
2604 : {
2605 121 : memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
2606 121 : firstChunk.get()->data() +
2607 121 : (nLineFromBottom - nFirstChunkBlockLine) *
2608 : nChunkLineSize,
2609 : nChunkLineSize);
2610 : }
2611 : else
2612 : {
2613 12 : CPLAssert(nChunkY == nLastChunkBlock);
2614 12 : assert(secondChunk);
2615 12 : memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
2616 12 : secondChunk.get()->data() +
2617 12 : (nLineFromBottom - nLastChunkBlockLine) *
2618 : nChunkLineSize,
2619 : nChunkLineSize);
2620 : }
2621 : }
2622 13 : return CE_None;
2623 : }
2624 : }
2625 : else
2626 : {
2627 957 : ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
2628 : }
2629 : }
2630 :
2631 6066 : return FetchNetcdfChunk(xstart, ystart, pImage) ? CE_None : CE_Failure;
2632 : }
2633 :
2634 : /************************************************************************/
2635 : /* IWriteBlock() */
2636 : /************************************************************************/
2637 :
2638 6621 : CPLErr netCDFRasterBand::IWriteBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
2639 : void *pImage)
2640 : {
2641 13242 : CPLMutexHolderD(&hNCMutex);
2642 :
2643 : #ifdef NCDF_DEBUG
2644 : if (nBlockYOff == 0 || (nBlockYOff == nRasterYSize - 1))
2645 : CPLDebug("GDAL_netCDF",
2646 : "netCDFRasterBand::IWriteBlock( %d, %d, ...) nBand=%d",
2647 : nBlockXOff, nBlockYOff, nBand);
2648 : #endif
2649 :
2650 6621 : int nd = 0;
2651 6621 : nc_inq_varndims(cdfid, nZId, &nd);
2652 :
2653 : // Locate X, Y and Z position in the array.
2654 :
2655 : size_t start[MAX_NC_DIMS];
2656 6621 : memset(start, 0, sizeof(start));
2657 6621 : start[nBandXPos] = static_cast<size_t>(nBlockXOff) * nBlockXSize;
2658 :
2659 : // check y order.
2660 6621 : if (cpl::down_cast<netCDFDataset *>(poDS)->bBottomUp)
2661 : {
2662 6557 : if (nBlockYSize == 1)
2663 : {
2664 6557 : start[nBandYPos] = nRasterYSize - 1 - nBlockYOff;
2665 : }
2666 : else
2667 : {
2668 0 : CPLError(CE_Failure, CPLE_AppDefined,
2669 : "nBlockYSize = %d, only 1 supported when "
2670 : "writing bottom-up dataset",
2671 : nBlockYSize);
2672 0 : return CE_Failure;
2673 : }
2674 : }
2675 : else
2676 : {
2677 64 : start[nBandYPos] = static_cast<size_t>(nBlockYOff) * nBlockYSize; // y
2678 : }
2679 :
2680 6621 : size_t edge[MAX_NC_DIMS] = {};
2681 :
2682 6621 : edge[nBandXPos] = nBlockXSize;
2683 6621 : if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
2684 0 : edge[nBandXPos] = nRasterXSize - start[nBandXPos];
2685 6621 : edge[nBandYPos] = nBlockYSize;
2686 6621 : if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
2687 0 : edge[nBandYPos] = nRasterYSize - start[nBandYPos];
2688 :
2689 6621 : if (nd == 3)
2690 : {
2691 630 : start[panBandZPos[0]] = nLevel; // z
2692 630 : edge[panBandZPos[0]] = 1;
2693 : }
2694 :
2695 : // Compute multidimention band position.
2696 : //
2697 : // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
2698 : // if Data[2,3,4,x,y]
2699 : //
2700 : // BandPos0 = (nBand) / (3*4)
2701 : // BandPos1 = (nBand - (3*4)) / (4)
2702 : // BandPos2 = (nBand - (3*4)) % (4)
2703 6621 : if (nd > 3)
2704 : {
2705 178 : int Sum = -1;
2706 178 : int Taken = 0;
2707 534 : for (int i = 0; i < nd - 2; i++)
2708 : {
2709 356 : if (i != nd - 2 - 1)
2710 : {
2711 178 : Sum = 1;
2712 356 : for (int j = i + 1; j < nd - 2; j++)
2713 : {
2714 178 : Sum *= panBandZLev[j];
2715 : }
2716 178 : start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
2717 178 : edge[panBandZPos[i]] = 1;
2718 : }
2719 : else
2720 : {
2721 178 : start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
2722 178 : edge[panBandZPos[i]] = 1;
2723 : }
2724 356 : Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
2725 : }
2726 : }
2727 :
2728 : // Make sure we are in data mode.
2729 6621 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
2730 :
2731 : // Copy data according to type.
2732 6621 : int status = 0;
2733 6621 : if (eDataType == GDT_UInt8)
2734 : {
2735 6022 : if (bSignedData)
2736 0 : status = nc_put_vara_schar(cdfid, nZId, start, edge,
2737 : static_cast<signed char *>(pImage));
2738 : else
2739 6022 : status = nc_put_vara_uchar(cdfid, nZId, start, edge,
2740 : static_cast<unsigned char *>(pImage));
2741 : }
2742 599 : else if (eDataType == GDT_Int8)
2743 : {
2744 40 : status = nc_put_vara_schar(cdfid, nZId, start, edge,
2745 : static_cast<signed char *>(pImage));
2746 : }
2747 559 : else if (nc_datatype == NC_SHORT)
2748 : {
2749 101 : status = nc_put_vara_short(cdfid, nZId, start, edge,
2750 : static_cast<short *>(pImage));
2751 : }
2752 458 : else if (eDataType == GDT_Int32)
2753 : {
2754 210 : status = nc_put_vara_int(cdfid, nZId, start, edge,
2755 : static_cast<int *>(pImage));
2756 : }
2757 248 : else if (eDataType == GDT_Float32)
2758 : {
2759 168 : status = nc_put_vara_float(cdfid, nZId, start, edge,
2760 : static_cast<float *>(pImage));
2761 : }
2762 80 : else if (eDataType == GDT_Float64)
2763 : {
2764 50 : status = nc_put_vara_double(cdfid, nZId, start, edge,
2765 : static_cast<double *>(pImage));
2766 : }
2767 42 : else if (eDataType == GDT_UInt16 &&
2768 12 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2769 : {
2770 12 : status = nc_put_vara_ushort(cdfid, nZId, start, edge,
2771 : static_cast<unsigned short *>(pImage));
2772 : }
2773 30 : else if (eDataType == GDT_UInt32 &&
2774 12 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2775 : {
2776 12 : status = nc_put_vara_uint(cdfid, nZId, start, edge,
2777 : static_cast<unsigned int *>(pImage));
2778 : }
2779 9 : else if (eDataType == GDT_UInt64 &&
2780 3 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2781 : {
2782 : status =
2783 3 : nc_put_vara_ulonglong(cdfid, nZId, start, edge,
2784 : static_cast<unsigned long long *>(pImage));
2785 : }
2786 6 : else if (eDataType == GDT_Int64 &&
2787 3 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2788 : {
2789 3 : status = nc_put_vara_longlong(cdfid, nZId, start, edge,
2790 : static_cast<long long *>(pImage));
2791 : }
2792 : else
2793 : {
2794 0 : CPLError(CE_Failure, CPLE_NotSupported,
2795 : "The NetCDF driver does not support GDAL data type %d",
2796 0 : eDataType);
2797 0 : status = NC_EBADTYPE;
2798 : }
2799 6621 : NCDF_ERR(status);
2800 :
2801 6621 : if (status != NC_NOERR)
2802 : {
2803 0 : CPLError(CE_Failure, CPLE_AppDefined,
2804 : "netCDF scanline write failed: %s", nc_strerror(status));
2805 0 : return CE_Failure;
2806 : }
2807 :
2808 6621 : return CE_None;
2809 : }
2810 :
2811 : /************************************************************************/
2812 : /* ==================================================================== */
2813 : /* netCDFDataset */
2814 : /* ==================================================================== */
2815 : /************************************************************************/
2816 :
2817 : /************************************************************************/
2818 : /* netCDFDataset() */
2819 : /************************************************************************/
2820 :
2821 1331 : netCDFDataset::netCDFDataset()
2822 : :
2823 : // Basic dataset vars.
2824 : #ifdef ENABLE_NCDUMP
2825 : bFileToDestroyAtClosing(false),
2826 : #endif
2827 : cdfid(-1), nSubDatasets(0), bBottomUp(true), eFormat(NCDF_FORMAT_NONE),
2828 : bIsGdalFile(false), bIsGdalCfFile(false), pszCFProjection(nullptr),
2829 : pszCFCoordinates(nullptr), bSGSupport(false),
2830 1331 : eMultipleLayerBehavior(SINGLE_LAYER), logCount(0), vcdf(this, cdfid),
2831 1331 : GeometryScribe(vcdf, this->generateLogName()),
2832 1331 : FieldScribe(vcdf, this->generateLogName()),
2833 2662 : bufManager(CPLGetUsablePhysicalRAM() / 5),
2834 :
2835 : // projection/GT.
2836 : nXDimID(-1), nYDimID(-1), bIsProjected(false),
2837 : bIsGeographic(false), // Can be not projected, and also not geographic
2838 : // State vars.
2839 : bDefineMode(true), bAddedGridMappingRef(false),
2840 :
2841 : // Create vars.
2842 : eCompress(NCDF_COMPRESS_NONE), nZLevel(NCDF_DEFLATE_LEVEL),
2843 3993 : bChunking(false), nCreateMode(NC_CLOBBER), bSignedData(true)
2844 : {
2845 1331 : m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2846 :
2847 : // Set buffers
2848 1331 : bufManager.addBuffer(&(GeometryScribe.getMemBuffer()));
2849 1331 : bufManager.addBuffer(&(FieldScribe.getMemBuffer()));
2850 1331 : }
2851 :
2852 : /************************************************************************/
2853 : /* ~netCDFDataset() */
2854 : /************************************************************************/
2855 :
2856 2549 : netCDFDataset::~netCDFDataset()
2857 :
2858 : {
2859 1331 : netCDFDataset::Close();
2860 2549 : }
2861 :
2862 : /************************************************************************/
2863 : /* Close() */
2864 : /************************************************************************/
2865 :
2866 2234 : CPLErr netCDFDataset::Close(GDALProgressFunc, void *)
2867 : {
2868 2234 : CPLErr eErr = CE_None;
2869 2234 : if (nOpenFlags != OPEN_FLAGS_CLOSED)
2870 : {
2871 2662 : CPLMutexHolderD(&hNCMutex);
2872 :
2873 : #ifdef NCDF_DEBUG
2874 : CPLDebug("GDAL_netCDF",
2875 : "netCDFDataset::~netCDFDataset(), cdfid=%d filename=%s", cdfid,
2876 : osFilename.c_str());
2877 : #endif
2878 :
2879 : // Write data related to geotransform
2880 1641 : if (GetAccess() == GA_Update && !m_bAddedProjectionVarsData &&
2881 310 : (m_bHasProjection || m_bHasGeoTransform))
2882 : {
2883 : // Ensure projection is written if GeoTransform OR Projection are
2884 : // missing.
2885 37 : if (!m_bAddedProjectionVarsDefs)
2886 : {
2887 2 : AddProjectionVars(true, nullptr, nullptr);
2888 : }
2889 37 : AddProjectionVars(false, nullptr, nullptr);
2890 : }
2891 :
2892 1331 : if (netCDFDataset::FlushCache(true) != CE_None)
2893 0 : eErr = CE_Failure;
2894 :
2895 1331 : if (GetAccess() == GA_Update && !SGCommitPendingTransaction())
2896 0 : eErr = CE_Failure;
2897 :
2898 1333 : for (size_t i = 0; i < apoVectorDatasets.size(); i++)
2899 2 : delete apoVectorDatasets[i];
2900 :
2901 : // Make sure projection variable is written to band variable.
2902 1331 : if (GetAccess() == GA_Update && !bAddedGridMappingRef)
2903 : {
2904 340 : if (!AddGridMappingRef())
2905 0 : eErr = CE_Failure;
2906 : }
2907 :
2908 1331 : CPLFree(pszCFProjection);
2909 :
2910 1331 : if (cdfid > 0)
2911 : {
2912 : #ifdef NCDF_DEBUG
2913 : CPLDebug("GDAL_netCDF", "calling nc_close( %d)", cdfid);
2914 : #endif
2915 729 : int status = GDAL_nc_close(cdfid);
2916 : #ifdef ENABLE_UFFD
2917 729 : NETCDF_UFFD_UNMAP(pCtx);
2918 : #endif
2919 729 : NCDF_ERR(status);
2920 729 : if (status != NC_NOERR)
2921 0 : eErr = CE_Failure;
2922 : }
2923 :
2924 1331 : if (fpVSIMEM)
2925 15 : VSIFCloseL(fpVSIMEM);
2926 :
2927 : #ifdef ENABLE_NCDUMP
2928 1331 : if (bFileToDestroyAtClosing)
2929 0 : VSIUnlink(osFilename);
2930 : #endif
2931 :
2932 1331 : if (GDALPamDataset::Close() != CE_None)
2933 0 : eErr = CE_Failure;
2934 : }
2935 2234 : return eErr;
2936 : }
2937 :
2938 : /************************************************************************/
2939 : /* SetDefineMode() */
2940 : /************************************************************************/
2941 14837 : bool netCDFDataset::SetDefineMode(bool bNewDefineMode)
2942 : {
2943 : // Do nothing if already in new define mode
2944 : // or if dataset is in read-only mode or if dataset is true NC4 dataset.
2945 15446 : if (bDefineMode == bNewDefineMode || GetAccess() == GA_ReadOnly ||
2946 609 : eFormat == NCDF_FORMAT_NC4)
2947 14398 : return true;
2948 :
2949 439 : CPLDebug("GDAL_netCDF", "SetDefineMode(%d) old=%d",
2950 439 : static_cast<int>(bNewDefineMode), static_cast<int>(bDefineMode));
2951 :
2952 439 : bDefineMode = bNewDefineMode;
2953 :
2954 : int status;
2955 439 : if (bDefineMode)
2956 152 : status = nc_redef(cdfid);
2957 : else
2958 287 : status = nc_enddef(cdfid);
2959 :
2960 439 : NCDF_ERR(status);
2961 439 : return status == NC_NOERR;
2962 : }
2963 :
2964 : /************************************************************************/
2965 : /* GetMetadataDomainList() */
2966 : /************************************************************************/
2967 :
2968 26 : char **netCDFDataset::GetMetadataDomainList()
2969 : {
2970 : char **papszDomains =
2971 26 : BuildMetadataDomainList(GDALDataset::GetMetadataDomainList(), TRUE,
2972 : GDAL_MDD_SUBDATASETS, nullptr);
2973 27 : for (const auto &kv : m_oMapDomainToJSon)
2974 1 : papszDomains = CSLAddString(papszDomains, ("json:" + kv.first).c_str());
2975 26 : return papszDomains;
2976 : }
2977 :
2978 : /************************************************************************/
2979 : /* GetMetadata() */
2980 : /************************************************************************/
2981 450 : CSLConstList netCDFDataset::GetMetadata(const char *pszDomain)
2982 : {
2983 450 : if (pszDomain != nullptr && STARTS_WITH_CI(pszDomain, GDAL_MDD_SUBDATASETS))
2984 47 : return aosSubDatasets.List();
2985 :
2986 403 : if (pszDomain != nullptr && STARTS_WITH(pszDomain, "json:"))
2987 : {
2988 6 : auto iter = m_oMapDomainToJSon.find(pszDomain + strlen("json:"));
2989 6 : if (iter != m_oMapDomainToJSon.end())
2990 1 : return iter->second.List();
2991 : }
2992 :
2993 402 : 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 (cpl::starts_with(osName, "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 (cpl::starts_with(osName, "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 252 : const OGRSpatialReference *netCDFDataset::GetSpatialRef() const
3061 : {
3062 252 : if (m_bHasProjection)
3063 121 : return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
3064 :
3065 131 : return GDALPamDataset::GetSpatialRef();
3066 : }
3067 :
3068 : /************************************************************************/
3069 : /* FetchCopyParam() */
3070 : /************************************************************************/
3071 :
3072 452 : double netCDFDataset::FetchCopyParam(const char *pszGridMappingValue,
3073 : const char *pszParam, double dfDefault,
3074 : bool *pbFound) const
3075 :
3076 : {
3077 904 : std::string osTemp = CPLOPrintf("%s#%s", pszGridMappingValue, pszParam);
3078 452 : const char *pszValue = aosMetadata.FetchNameValue(osTemp.c_str());
3079 :
3080 452 : if (pbFound)
3081 : {
3082 452 : *pbFound = (pszValue != nullptr);
3083 : }
3084 :
3085 452 : if (pszValue)
3086 : {
3087 0 : return CPLAtofM(pszValue);
3088 : }
3089 :
3090 452 : return dfDefault;
3091 : }
3092 :
3093 : /************************************************************************/
3094 : /* FetchStandardParallels() */
3095 : /************************************************************************/
3096 :
3097 : std::vector<std::string>
3098 0 : netCDFDataset::FetchStandardParallels(const char *pszGridMappingValue) const
3099 : {
3100 : // cf-1.0 tags
3101 0 : const char *pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL);
3102 :
3103 0 : std::vector<std::string> ret;
3104 0 : if (pszValue != nullptr)
3105 : {
3106 0 : CPLStringList aosValues;
3107 0 : if (pszValue[0] != '{' &&
3108 0 : CPLString(pszValue).Trim().find(' ') != std::string::npos)
3109 : {
3110 : // Some files like
3111 : // ftp://data.knmi.nl/download/KNW-NetCDF-3D/1.0/noversion/2013/11/14/KNW-1.0_H37-ERA_NL_20131114.nc
3112 : // do not use standard formatting for arrays, but just space
3113 : // separated syntax
3114 0 : aosValues = CSLTokenizeString2(pszValue, " ", 0);
3115 : }
3116 : else
3117 : {
3118 0 : aosValues = NCDFTokenizeArray(pszValue);
3119 : }
3120 0 : for (int i = 0; i < aosValues.size(); i++)
3121 : {
3122 0 : ret.push_back(aosValues[i]);
3123 : }
3124 : }
3125 : // Try gdal tags.
3126 : else
3127 : {
3128 0 : pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_1);
3129 :
3130 0 : if (pszValue != nullptr)
3131 0 : ret.push_back(pszValue);
3132 :
3133 0 : pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_2);
3134 :
3135 0 : if (pszValue != nullptr)
3136 0 : ret.push_back(pszValue);
3137 : }
3138 :
3139 0 : return ret;
3140 : }
3141 :
3142 : /************************************************************************/
3143 : /* FetchAttr() */
3144 : /************************************************************************/
3145 :
3146 4430 : const char *netCDFDataset::FetchAttr(const char *pszVarFullName,
3147 : const char *pszAttr) const
3148 :
3149 : {
3150 4430 : auto oKey = CPLOPrintf("%s#%s", pszVarFullName, pszAttr);
3151 4430 : const char *pszValue = aosMetadata.FetchNameValue(oKey.c_str());
3152 8860 : return pszValue;
3153 : }
3154 :
3155 2883 : const char *netCDFDataset::FetchAttr(int nGroupId, int nVarId,
3156 : const char *pszAttr) const
3157 :
3158 : {
3159 2883 : std::string osFullName;
3160 2883 : NCDFGetVarFullName(nGroupId, nVarId, osFullName);
3161 2883 : const char *pszValue = FetchAttr(osFullName.c_str(), pszAttr);
3162 5766 : return pszValue;
3163 : }
3164 :
3165 : /************************************************************************/
3166 : /* IsDifferenceBelow() */
3167 : /************************************************************************/
3168 :
3169 1199 : static bool IsDifferenceBelow(double dfA, double dfB, double dfError)
3170 : {
3171 1199 : const double dfAbsDiff = fabs(dfA - dfB);
3172 1199 : return dfAbsDiff <= dfError;
3173 : }
3174 :
3175 : /************************************************************************/
3176 : /* SetProjectionFromVar() */
3177 : /************************************************************************/
3178 639 : void netCDFDataset::SetProjectionFromVar(
3179 : int nGroupId, int nVarId, bool bReadSRSOnly, const char *pszGivenGM,
3180 : std::string *returnProjStr, nccfdriver::SGeometry_Reader *sg,
3181 : std::vector<std::string> *paosRemovedMDItems)
3182 : {
3183 639 : bool bGotGeogCS = false;
3184 639 : bool bGotCfSRS = false;
3185 639 : bool bGotCfWktSRS = false;
3186 639 : bool bGotGdalSRS = false;
3187 639 : bool bGotCfGT = false;
3188 639 : bool bGotGdalGT = false;
3189 :
3190 : // These values from CF metadata.
3191 639 : OGRSpatialReference oSRS;
3192 639 : oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3193 639 : size_t xdim = nRasterXSize;
3194 639 : size_t ydim = nRasterYSize;
3195 :
3196 : // These values from GDAL metadata.
3197 639 : const char *pszWKT = nullptr;
3198 639 : const char *pszGeoTransform = nullptr;
3199 :
3200 639 : netCDFDataset *poDS = this; // Perhaps this should be removed for clarity.
3201 :
3202 639 : CPLDebug("GDAL_netCDF", "\n=====\nSetProjectionFromVar( %d, %d)", nGroupId,
3203 : nVarId);
3204 :
3205 : // Get x/y range information.
3206 :
3207 : // Temp variables to use in SetGeoTransform() and SetProjection().
3208 639 : GDALGeoTransform tmpGT;
3209 :
3210 : // Look for grid_mapping metadata.
3211 639 : const char *pszValue = pszGivenGM;
3212 639 : CPLString osTmpGridMapping; // let is in this outer scope as pszValue may
3213 : // point to it
3214 639 : if (pszValue == nullptr)
3215 : {
3216 595 : pszValue = FetchAttr(nGroupId, nVarId, CF_GRD_MAPPING);
3217 595 : if (pszValue && strchr(pszValue, ':') && strchr(pszValue, ' '))
3218 : {
3219 : // Expanded form of grid_mapping
3220 : // e.g. "crsOSGB: x y crsWGS84: lat lon"
3221 : // Pickup the grid_mapping whose coordinates are dimensions of the
3222 : // variable
3223 6 : const CPLStringList aosTokens(CSLTokenizeString2(pszValue, " ", 0));
3224 3 : if ((aosTokens.size() % 3) == 0)
3225 : {
3226 3 : for (int i = 0; i < aosTokens.size() / 3; i++)
3227 : {
3228 3 : if (CSLFindString(poDS->papszDimName,
3229 9 : aosTokens[3 * i + 1]) >= 0 &&
3230 3 : CSLFindString(poDS->papszDimName,
3231 3 : aosTokens[3 * i + 2]) >= 0)
3232 : {
3233 3 : osTmpGridMapping = aosTokens[3 * i];
3234 6 : if (!osTmpGridMapping.empty() &&
3235 3 : osTmpGridMapping.back() == ':')
3236 : {
3237 3 : osTmpGridMapping.resize(osTmpGridMapping.size() -
3238 : 1);
3239 : }
3240 3 : pszValue = osTmpGridMapping.c_str();
3241 3 : break;
3242 : }
3243 : }
3244 : }
3245 : }
3246 : }
3247 639 : std::string osGridMappingValue = pszValue ? pszValue : "";
3248 :
3249 639 : if (!osGridMappingValue.empty())
3250 : {
3251 : // Read grid_mapping metadata.
3252 266 : int nProjGroupID = -1;
3253 266 : int nProjVarID = -1;
3254 266 : if (NCDFResolveVar(nGroupId, osGridMappingValue.c_str(), &nProjGroupID,
3255 266 : &nProjVarID) == CE_None)
3256 : {
3257 264 : poDS->ReadAttributes(nProjGroupID, nProjVarID);
3258 :
3259 : // Look for GDAL spatial_ref and GeoTransform within grid_mapping.
3260 264 : if (NCDFGetVarFullName(nProjGroupID, nProjVarID,
3261 264 : osGridMappingValue) == CE_None)
3262 : {
3263 264 : CPLDebug("GDAL_netCDF", "got grid_mapping %s",
3264 : osGridMappingValue.c_str());
3265 : pszWKT =
3266 264 : FetchAttr(osGridMappingValue.c_str(), NCDF_SPATIAL_REF);
3267 264 : if (!pszWKT)
3268 : {
3269 : pszWKT =
3270 36 : FetchAttr(osGridMappingValue.c_str(), NCDF_CRS_WKT);
3271 : }
3272 : else
3273 : {
3274 228 : bGotGdalSRS = true;
3275 228 : CPLDebug("GDAL_netCDF", "setting WKT from GDAL");
3276 : }
3277 264 : if (pszWKT)
3278 : {
3279 234 : if (!bGotGdalSRS)
3280 : {
3281 6 : bGotCfWktSRS = true;
3282 6 : CPLDebug("GDAL_netCDF", "setting WKT from CF");
3283 : }
3284 234 : if (returnProjStr != nullptr)
3285 : {
3286 42 : (*returnProjStr) = std::string(pszWKT);
3287 : }
3288 : else
3289 : {
3290 192 : m_bAddedProjectionVarsDefs = true;
3291 192 : m_bAddedProjectionVarsData = true;
3292 384 : OGRSpatialReference oSRSTmp;
3293 192 : oSRSTmp.SetAxisMappingStrategy(
3294 : OAMS_TRADITIONAL_GIS_ORDER);
3295 192 : oSRSTmp.importFromWkt(pszWKT);
3296 192 : SetSpatialRefNoUpdate(&oSRSTmp);
3297 : }
3298 234 : pszGeoTransform = FetchAttr(osGridMappingValue.c_str(),
3299 : NCDF_GEOTRANSFORM);
3300 : }
3301 : }
3302 : }
3303 : else
3304 : {
3305 4 : std::string osVarName = "unknown";
3306 2 : NCDFGetVarFullName(nGroupId, nVarId, osVarName);
3307 :
3308 2 : CPLError(CE_Warning, CPLE_AppDefined,
3309 : "'%s' attribute of variable '%s' references grid mapping "
3310 : "variable '%s', but no such variable exists. The spatial "
3311 : "referencing of this dataset may be incorrect.",
3312 : CF_GRD_MAPPING, osVarName.c_str(),
3313 : osGridMappingValue.c_str());
3314 : }
3315 : }
3316 :
3317 : // Get information about the file.
3318 : //
3319 : // Was this file created by the GDAL netcdf driver?
3320 : // Was this file created by the newer (CF-conformant) driver?
3321 : //
3322 : // 1) If GDAL netcdf metadata is set, and version >= 1.9,
3323 : // it was created with the new driver
3324 : // 2) Else, if spatial_ref and GeoTransform are present in the
3325 : // grid_mapping variable, it was created by the old driver
3326 639 : pszValue = FetchAttr("NC_GLOBAL", "GDAL");
3327 :
3328 639 : if (pszValue && NCDFIsGDALVersionGTE(pszValue, 1900))
3329 : {
3330 294 : bIsGdalFile = true;
3331 294 : bIsGdalCfFile = true;
3332 : }
3333 345 : else if (pszWKT != nullptr && pszGeoTransform != nullptr)
3334 : {
3335 39 : bIsGdalFile = true;
3336 39 : bIsGdalCfFile = false;
3337 : }
3338 :
3339 : // Set default bottom-up default value.
3340 : // Y axis dimension and absence of GT can modify this value.
3341 : // Override with Config option GDAL_NETCDF_BOTTOMUP.
3342 :
3343 : // New driver is bottom-up by default.
3344 639 : if ((bIsGdalFile && !bIsGdalCfFile) || bSwitchedXY)
3345 41 : poDS->bBottomUp = false;
3346 : else
3347 598 : poDS->bBottomUp = true;
3348 :
3349 639 : CPLDebug("GDAL_netCDF",
3350 : "bIsGdalFile=%d bIsGdalCfFile=%d bSwitchedXY=%d bBottomUp=%d",
3351 639 : static_cast<int>(bIsGdalFile), static_cast<int>(bIsGdalCfFile),
3352 639 : static_cast<int>(bSwitchedXY), static_cast<int>(bBottomUp));
3353 :
3354 : // Read projection coordinates.
3355 :
3356 639 : int nGroupDimXID = -1;
3357 639 : int nVarDimXID = -1;
3358 639 : int nGroupDimYID = -1;
3359 639 : int nVarDimYID = -1;
3360 639 : if (sg != nullptr)
3361 : {
3362 44 : nGroupDimXID = sg->get_ncID();
3363 44 : nGroupDimYID = sg->get_ncID();
3364 44 : nVarDimXID = sg->getNodeCoordVars()[0];
3365 44 : nVarDimYID = sg->getNodeCoordVars()[1];
3366 : }
3367 :
3368 639 : if (!bReadSRSOnly)
3369 : {
3370 382 : NCDFResolveVar(nGroupId, poDS->papszDimName[nXDimID], &nGroupDimXID,
3371 : &nVarDimXID);
3372 382 : NCDFResolveVar(nGroupId, poDS->papszDimName[nYDimID], &nGroupDimYID,
3373 : &nVarDimYID);
3374 : // TODO: if above resolving fails we should also search for coordinate
3375 : // variables without same name than dimension using the same resolving
3376 : // logic. This should handle for example NASA Ocean Color L2 products.
3377 :
3378 : const bool bIgnoreXYAxisNameChecks =
3379 764 : CPLTestBool(CSLFetchNameValueDef(
3380 382 : papszOpenOptions, "IGNORE_XY_AXIS_NAME_CHECKS",
3381 : CPLGetConfigOption("GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS",
3382 382 : "NO"))) ||
3383 : // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a res
3384 : // and transform attributes
3385 382 : (FetchAttr(nGroupId, nVarId, "res") != nullptr &&
3386 764 : FetchAttr(nGroupId, nVarId, "transform") != nullptr) ||
3387 381 : FetchAttr(nGroupId, NC_GLOBAL, "GMT_version") != nullptr;
3388 :
3389 : // Check that they are 1D or 2D variables
3390 382 : if (nVarDimXID >= 0)
3391 : {
3392 274 : int ndims = -1;
3393 274 : nc_inq_varndims(nGroupId, nVarDimXID, &ndims);
3394 274 : if (ndims == 0 || ndims > 2)
3395 0 : nVarDimXID = -1;
3396 274 : else if (!bIgnoreXYAxisNameChecks)
3397 : {
3398 272 : if (!NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
3399 179 : !NCDFIsVarProjectionX(nGroupId, nVarDimXID, nullptr) &&
3400 : // In case of inversion of X/Y
3401 483 : !NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr) &&
3402 32 : !NCDFIsVarProjectionY(nGroupId, nVarDimXID, nullptr))
3403 : {
3404 : char szVarNameX[NC_MAX_NAME + 1];
3405 32 : CPL_IGNORE_RET_VAL(
3406 32 : nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
3407 32 : if (!(ndims == 1 &&
3408 31 : (EQUAL(szVarNameX, CF_LONGITUDE_STD_NAME) ||
3409 30 : EQUAL(szVarNameX, CF_LONGITUDE_VAR_NAME))))
3410 : {
3411 31 : CPLDebug(
3412 : "netCDF",
3413 : "Georeferencing ignored due to non-specific "
3414 : "enough X axis name. "
3415 : "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
3416 : "as configuration option to bypass this check");
3417 31 : nVarDimXID = -1;
3418 : }
3419 : }
3420 : }
3421 : }
3422 :
3423 382 : if (nVarDimYID >= 0)
3424 : {
3425 276 : int ndims = -1;
3426 276 : nc_inq_varndims(nGroupId, nVarDimYID, &ndims);
3427 276 : if (ndims == 0 || ndims > 2)
3428 1 : nVarDimYID = -1;
3429 275 : else if (!bIgnoreXYAxisNameChecks)
3430 : {
3431 273 : if (!NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr) &&
3432 180 : !NCDFIsVarProjectionY(nGroupId, nVarDimYID, nullptr) &&
3433 : // In case of inversion of X/Y
3434 486 : !NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
3435 33 : !NCDFIsVarProjectionX(nGroupId, nVarDimYID, nullptr))
3436 : {
3437 : char szVarNameY[NC_MAX_NAME + 1];
3438 33 : CPL_IGNORE_RET_VAL(
3439 33 : nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
3440 33 : if (!(ndims == 1 &&
3441 33 : (EQUAL(szVarNameY, CF_LATITUDE_STD_NAME) ||
3442 32 : EQUAL(szVarNameY, CF_LATITUDE_VAR_NAME))))
3443 : {
3444 32 : CPLDebug(
3445 : "netCDF",
3446 : "Georeferencing ignored due to non-specific "
3447 : "enough Y axis name. "
3448 : "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
3449 : "as configuration option to bypass this check");
3450 32 : nVarDimYID = -1;
3451 : }
3452 : }
3453 : }
3454 : }
3455 :
3456 382 : if ((nVarDimXID >= 0 && xdim == 1) || (nVarDimXID >= 0 && ydim == 1))
3457 : {
3458 0 : CPLError(CE_Warning, CPLE_AppDefined,
3459 : "1-pixel width/height files not supported, "
3460 : "xdim: %ld ydim: %ld",
3461 : static_cast<long>(xdim), static_cast<long>(ydim));
3462 0 : nVarDimXID = -1;
3463 0 : nVarDimYID = -1;
3464 : }
3465 : }
3466 :
3467 639 : const char *pszUnits = nullptr;
3468 639 : if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
3469 : {
3470 287 : const char *pszUnitsX = FetchAttr(nGroupDimXID, nVarDimXID, "units");
3471 287 : const char *pszUnitsY = FetchAttr(nGroupDimYID, nVarDimYID, "units");
3472 : // Normalize degrees_east/degrees_north to degrees
3473 : // Cf https://github.com/OSGeo/gdal/issues/11009
3474 287 : if (pszUnitsX && EQUAL(pszUnitsX, "degrees_east"))
3475 81 : pszUnitsX = "degrees";
3476 287 : if (pszUnitsY && EQUAL(pszUnitsY, "degrees_north"))
3477 81 : pszUnitsY = "degrees";
3478 :
3479 287 : if (pszUnitsX && pszUnitsY)
3480 : {
3481 239 : if (EQUAL(pszUnitsX, pszUnitsY))
3482 236 : pszUnits = pszUnitsX;
3483 3 : else if (!pszWKT && !osGridMappingValue.empty())
3484 : {
3485 0 : CPLError(CE_Failure, CPLE_AppDefined,
3486 : "X axis unit (%s) is different from Y axis "
3487 : "unit (%s). SRS will ignore axis unit and be "
3488 : "likely wrong.",
3489 : pszUnitsX, pszUnitsY);
3490 : }
3491 : }
3492 48 : else if (pszUnitsX && !pszWKT && !osGridMappingValue.empty())
3493 : {
3494 0 : CPLError(CE_Failure, CPLE_AppDefined,
3495 : "X axis unit is defined, but not Y one ."
3496 : "SRS will ignore axis unit and be likely wrong.");
3497 : }
3498 48 : else if (pszUnitsY && !pszWKT && !osGridMappingValue.empty())
3499 : {
3500 0 : CPLError(CE_Failure, CPLE_AppDefined,
3501 : "Y axis unit is defined, but not X one ."
3502 : "SRS will ignore axis unit and be likely wrong.");
3503 : }
3504 : }
3505 :
3506 639 : if (!pszWKT && !osGridMappingValue.empty())
3507 : {
3508 32 : CPLStringList aosGridMappingKeyValues;
3509 32 : const size_t nLenGridMappingValue = osGridMappingValue.size();
3510 817 : for (const char *pszIter : aosMetadata)
3511 : {
3512 1021 : if (STARTS_WITH(pszIter, osGridMappingValue.c_str()) &&
3513 236 : pszIter[nLenGridMappingValue] == '#')
3514 : {
3515 236 : char *pszKey = nullptr;
3516 236 : pszValue = CPLParseNameValue(pszIter + nLenGridMappingValue + 1,
3517 : &pszKey);
3518 236 : if (pszKey && pszValue)
3519 236 : aosGridMappingKeyValues.SetNameValue(pszKey, pszValue);
3520 236 : CPLFree(pszKey);
3521 : }
3522 : }
3523 :
3524 32 : bGotGeogCS = aosGridMappingKeyValues.FetchNameValue(
3525 : CF_PP_SEMI_MAJOR_AXIS) != nullptr;
3526 :
3527 32 : oSRS.importFromCF1(aosGridMappingKeyValues.List(), pszUnits);
3528 32 : bGotCfSRS = oSRS.IsGeographic() || oSRS.IsProjected();
3529 : }
3530 : else
3531 : {
3532 : // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a "crs"
3533 : // attribute hold on the variable of interest that contains a PROJ.4
3534 : // string
3535 607 : pszValue = FetchAttr(nGroupId, nVarId, "crs");
3536 608 : if (pszValue &&
3537 1 : (strstr(pszValue, "+proj=") != nullptr ||
3538 0 : strstr(pszValue, "GEOGCS") != nullptr ||
3539 0 : strstr(pszValue, "PROJCS") != nullptr ||
3540 608 : strstr(pszValue, "EPSG:") != nullptr) &&
3541 1 : oSRS.SetFromUserInput(pszValue) == OGRERR_NONE)
3542 : {
3543 1 : bGotCfSRS = true;
3544 : }
3545 : }
3546 :
3547 : // Set Projection from CF.
3548 639 : double dfLinearUnitsConvFactor = 1.0;
3549 639 : if ((bGotGeogCS || bGotCfSRS))
3550 : {
3551 31 : if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
3552 : {
3553 : // Set SRS Units.
3554 :
3555 : // Check units for x and y.
3556 28 : if (oSRS.IsProjected())
3557 : {
3558 25 : dfLinearUnitsConvFactor = oSRS.GetLinearUnits(nullptr);
3559 :
3560 : // If the user doesn't ask to preserve the axis unit,
3561 : // then normalize to metre
3562 31 : if (dfLinearUnitsConvFactor != 1.0 &&
3563 6 : !CPLFetchBool(GetOpenOptions(), "PRESERVE_AXIS_UNIT_IN_CRS",
3564 : false))
3565 : {
3566 5 : oSRS.SetLinearUnits("metre", 1.0);
3567 5 : oSRS.SetAuthority("PROJCS|UNIT", "EPSG", 9001);
3568 : }
3569 : else
3570 : {
3571 20 : dfLinearUnitsConvFactor = 1.0;
3572 : }
3573 : }
3574 : }
3575 :
3576 : // Set projection.
3577 31 : char *pszTempProjection = nullptr;
3578 31 : oSRS.exportToWkt(&pszTempProjection);
3579 31 : if (pszTempProjection)
3580 : {
3581 31 : CPLDebug("GDAL_netCDF", "setting WKT from CF");
3582 31 : if (returnProjStr != nullptr)
3583 : {
3584 2 : (*returnProjStr) = std::string(pszTempProjection);
3585 : }
3586 : else
3587 : {
3588 29 : m_bAddedProjectionVarsDefs = true;
3589 29 : m_bAddedProjectionVarsData = true;
3590 29 : SetSpatialRefNoUpdate(&oSRS);
3591 : }
3592 : }
3593 31 : CPLFree(pszTempProjection);
3594 : }
3595 :
3596 639 : if (!bReadSRSOnly && (nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 &&
3597 : ydim > 0)
3598 : {
3599 : double *pdfXCoord =
3600 243 : static_cast<double *>(CPLCalloc(xdim, sizeof(double)));
3601 : double *pdfYCoord =
3602 243 : static_cast<double *>(CPLCalloc(ydim, sizeof(double)));
3603 :
3604 243 : size_t start[2] = {0, 0};
3605 243 : size_t edge[2] = {xdim, 0};
3606 243 : int status = nc_get_vara_double(nGroupDimXID, nVarDimXID, start, edge,
3607 : pdfXCoord);
3608 243 : NCDF_ERR(status);
3609 :
3610 243 : edge[0] = ydim;
3611 243 : status = nc_get_vara_double(nGroupDimYID, nVarDimYID, start, edge,
3612 : pdfYCoord);
3613 243 : NCDF_ERR(status);
3614 :
3615 243 : nc_type nc_var_dimx_datatype = NC_NAT;
3616 : status =
3617 243 : nc_inq_vartype(nGroupDimXID, nVarDimXID, &nc_var_dimx_datatype);
3618 243 : NCDF_ERR(status);
3619 :
3620 243 : nc_type nc_var_dimy_datatype = NC_NAT;
3621 : status =
3622 243 : nc_inq_vartype(nGroupDimYID, nVarDimYID, &nc_var_dimy_datatype);
3623 243 : NCDF_ERR(status);
3624 :
3625 243 : if (!poDS->bSwitchedXY)
3626 : {
3627 : // Convert ]180,540] longitude values to ]-180,0].
3628 334 : if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
3629 93 : CPLTestBool(
3630 : CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")))
3631 : {
3632 : // If minimum longitude is > 180, subtract 360 from all.
3633 : // Add a check on the maximum X value too, since
3634 : // NCDFIsVarLongitude() is not very specific by default (see
3635 : // https://github.com/OSGeo/gdal/issues/1440)
3636 100 : if (std::min(pdfXCoord[0], pdfXCoord[xdim - 1]) > 180.0 &&
3637 7 : std::max(pdfXCoord[0], pdfXCoord[xdim - 1]) <= 540)
3638 : {
3639 0 : CPLDebug(
3640 : "GDAL_netCDF",
3641 : "Offsetting longitudes from ]180,540] to ]-180,180]. "
3642 : "Can be disabled with GDAL_NETCDF_CENTERLONG_180=NO");
3643 0 : for (size_t i = 0; i < xdim; i++)
3644 0 : pdfXCoord[i] -= 360;
3645 : }
3646 : }
3647 : }
3648 :
3649 : // Is pixel spacing uniform across the map?
3650 :
3651 : // Check Longitude.
3652 :
3653 243 : bool bLonSpacingOK = false;
3654 243 : if (xdim == 2)
3655 : {
3656 29 : bLonSpacingOK = true;
3657 : }
3658 : else
3659 : {
3660 214 : bool bWestIsLeft = (pdfXCoord[0] < pdfXCoord[xdim - 1]);
3661 :
3662 : // fix longitudes if longitudes should increase from
3663 : // west to east, but west > east
3664 297 : if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
3665 83 : !bWestIsLeft)
3666 : {
3667 2 : size_t ndecreases = 0;
3668 :
3669 : // there is lon wrap if longitudes increase
3670 : // with one single decrease
3671 107 : for (size_t i = 1; i < xdim; i++)
3672 : {
3673 105 : if (pdfXCoord[i] < pdfXCoord[i - 1])
3674 1 : ndecreases++;
3675 : }
3676 :
3677 2 : if (ndecreases == 1)
3678 : {
3679 1 : CPLDebug("GDAL_netCDF", "longitude wrap detected");
3680 4 : for (size_t i = 0; i < xdim; i++)
3681 : {
3682 3 : if (pdfXCoord[i] > pdfXCoord[xdim - 1])
3683 1 : pdfXCoord[i] -= 360;
3684 : }
3685 : }
3686 : }
3687 :
3688 214 : const double dfSpacingBegin = pdfXCoord[1] - pdfXCoord[0];
3689 214 : const double dfSpacingMiddle =
3690 214 : pdfXCoord[xdim / 2 + 1] - pdfXCoord[xdim / 2];
3691 214 : const double dfSpacingLast =
3692 214 : pdfXCoord[xdim - 1] - pdfXCoord[xdim - 2];
3693 :
3694 214 : CPLDebug("GDAL_netCDF",
3695 : "xdim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
3696 : "dfSpacingLast: %f",
3697 : static_cast<long>(xdim), dfSpacingBegin, dfSpacingMiddle,
3698 : dfSpacingLast);
3699 : #ifdef NCDF_DEBUG
3700 : CPLDebug("GDAL_netCDF", "xcoords: %f %f %f %f %f %f", pdfXCoord[0],
3701 : pdfXCoord[1], pdfXCoord[xdim / 2],
3702 : pdfXCoord[(xdim / 2) + 1], pdfXCoord[xdim - 2],
3703 : pdfXCoord[xdim - 1]);
3704 : #endif
3705 :
3706 : // ftp://ftp.cdc.noaa.gov/Datasets/NARR/Dailies/monolevel/vwnd.10m.2015.nc
3707 : // requires a 0.02% tolerance, so let's settle for 0.05%
3708 :
3709 : // For float variables, increase to 0.2% (as seen in
3710 : // https://github.com/OSGeo/gdal/issues/3663)
3711 214 : const double dfEpsRel =
3712 214 : nc_var_dimx_datatype == NC_FLOAT ? 0.002 : 0.0005;
3713 :
3714 : const double dfEps =
3715 : dfEpsRel *
3716 428 : std::max(fabs(dfSpacingBegin),
3717 214 : std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
3718 422 : if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
3719 422 : IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
3720 208 : IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
3721 : {
3722 208 : bLonSpacingOK = true;
3723 : }
3724 6 : else if (CPLTestBool(CPLGetConfigOption(
3725 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
3726 : {
3727 0 : bLonSpacingOK = true;
3728 0 : CPLDebug(
3729 : "GDAL_netCDF",
3730 : "Longitude/X is not equally spaced, but will be considered "
3731 : "as such because of "
3732 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
3733 : }
3734 : }
3735 :
3736 243 : if (bLonSpacingOK == false)
3737 : {
3738 6 : CPLDebug(
3739 : "GDAL_netCDF", "%s",
3740 : "Longitude/X is not equally spaced (with a 0.05% tolerance). "
3741 : "You may set the "
3742 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
3743 : "option to YES to ignore this check");
3744 : }
3745 :
3746 : // Check Latitude.
3747 243 : bool bLatSpacingOK = false;
3748 :
3749 243 : if (ydim == 2)
3750 : {
3751 49 : bLatSpacingOK = true;
3752 : }
3753 : else
3754 : {
3755 194 : const double dfSpacingBegin = pdfYCoord[1] - pdfYCoord[0];
3756 194 : const double dfSpacingMiddle =
3757 194 : pdfYCoord[ydim / 2 + 1] - pdfYCoord[ydim / 2];
3758 :
3759 194 : const double dfSpacingLast =
3760 194 : pdfYCoord[ydim - 1] - pdfYCoord[ydim - 2];
3761 :
3762 194 : CPLDebug("GDAL_netCDF",
3763 : "ydim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
3764 : "dfSpacingLast: %f",
3765 : (long)ydim, dfSpacingBegin, dfSpacingMiddle,
3766 : dfSpacingLast);
3767 : #ifdef NCDF_DEBUG
3768 : CPLDebug("GDAL_netCDF", "ycoords: %f %f %f %f %f %f", pdfYCoord[0],
3769 : pdfYCoord[1], pdfYCoord[ydim / 2],
3770 : pdfYCoord[(ydim / 2) + 1], pdfYCoord[ydim - 2],
3771 : pdfYCoord[ydim - 1]);
3772 : #endif
3773 :
3774 194 : const double dfEpsRel =
3775 194 : nc_var_dimy_datatype == NC_FLOAT ? 0.002 : 0.0005;
3776 :
3777 : const double dfEps =
3778 : dfEpsRel *
3779 388 : std::max(fabs(dfSpacingBegin),
3780 194 : std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
3781 386 : if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
3782 386 : IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
3783 183 : IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
3784 : {
3785 183 : bLatSpacingOK = true;
3786 : }
3787 11 : else if (CPLTestBool(CPLGetConfigOption(
3788 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
3789 : {
3790 0 : bLatSpacingOK = true;
3791 0 : CPLDebug(
3792 : "GDAL_netCDF",
3793 : "Latitude/Y is not equally spaced, but will be considered "
3794 : "as such because of "
3795 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
3796 : }
3797 11 : else if (!oSRS.IsProjected() &&
3798 11 : fabs(dfSpacingBegin - dfSpacingLast) <= 0.1 &&
3799 30 : fabs(dfSpacingBegin - dfSpacingMiddle) <= 0.1 &&
3800 8 : fabs(dfSpacingMiddle - dfSpacingLast) <= 0.1)
3801 : {
3802 8 : bLatSpacingOK = true;
3803 8 : CPLError(CE_Warning, CPLE_AppDefined,
3804 : "Latitude grid not spaced evenly. "
3805 : "Setting projection for grid spacing is "
3806 : "within 0.1 degrees threshold.");
3807 :
3808 8 : CPLDebug("GDAL_netCDF",
3809 : "Latitude grid not spaced evenly, but within 0.1 "
3810 : "degree threshold (probably a Gaussian grid). "
3811 : "Saving original latitude values in Y_VALUES "
3812 : "geolocation metadata");
3813 8 : Set1DGeolocation(nGroupDimYID, nVarDimYID, "Y");
3814 : }
3815 :
3816 194 : if (bLatSpacingOK == false)
3817 : {
3818 3 : CPLDebug(
3819 : "GDAL_netCDF", "%s",
3820 : "Latitude/Y is not equally spaced (with a 0.05% "
3821 : "tolerance). "
3822 : "You may set the "
3823 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
3824 : "option to YES to ignore this check");
3825 : }
3826 : }
3827 :
3828 243 : if (bLonSpacingOK && bLatSpacingOK)
3829 : {
3830 : // We have gridded data so we can set the Georeferencing info.
3831 :
3832 : // Enable GeoTransform.
3833 :
3834 : // In the following "actual_range" and "node_offset"
3835 : // are attributes used by netCDF files created by GMT.
3836 : // If we find them we know how to proceed. Else, use
3837 : // the original algorithm.
3838 236 : bGotCfGT = true;
3839 :
3840 236 : int node_offset = 0;
3841 : const bool bUseActualRange =
3842 236 : NCDFResolveAttInt(nGroupId, NC_GLOBAL, "node_offset",
3843 236 : &node_offset) == CE_None;
3844 :
3845 236 : double adfActualRange[2] = {0.0, 0.0};
3846 236 : double xMinMax[2] = {0.0, 0.0};
3847 236 : double yMinMax[2] = {0.0, 0.0};
3848 :
3849 : const auto RoundMinMaxForFloatVals =
3850 60 : [](double &dfMin, double &dfMax, int nIntervals)
3851 : {
3852 : // Helps for a case where longitudes range from
3853 : // -179.99 to 180.0 with a 0.01 degree spacing.
3854 : // However as this is encoded in a float array,
3855 : // -179.99 is actually read as -179.99000549316406 as
3856 : // a double. Try to detect that and correct the rounding
3857 :
3858 88 : const auto IsAlmostInteger = [](double dfVal)
3859 : {
3860 88 : constexpr double THRESHOLD_INTEGER = 1e-3;
3861 88 : return std::fabs(dfVal - std::round(dfVal)) <=
3862 88 : THRESHOLD_INTEGER;
3863 : };
3864 :
3865 60 : const double dfSpacing = (dfMax - dfMin) / nIntervals;
3866 60 : if (dfSpacing > 0)
3867 : {
3868 48 : const double dfInvSpacing = 1.0 / dfSpacing;
3869 48 : if (IsAlmostInteger(dfInvSpacing))
3870 : {
3871 20 : const double dfRoundedSpacing =
3872 20 : 1.0 / std::round(dfInvSpacing);
3873 20 : const double dfMinDivRoundedSpacing =
3874 20 : dfMin / dfRoundedSpacing;
3875 20 : const double dfMaxDivRoundedSpacing =
3876 20 : dfMax / dfRoundedSpacing;
3877 40 : if (IsAlmostInteger(dfMinDivRoundedSpacing) &&
3878 20 : IsAlmostInteger(dfMaxDivRoundedSpacing))
3879 : {
3880 20 : const double dfRoundedMin =
3881 20 : std::round(dfMinDivRoundedSpacing) *
3882 : dfRoundedSpacing;
3883 20 : const double dfRoundedMax =
3884 20 : std::round(dfMaxDivRoundedSpacing) *
3885 : dfRoundedSpacing;
3886 20 : if (static_cast<float>(dfMin) ==
3887 20 : static_cast<float>(dfRoundedMin) &&
3888 8 : static_cast<float>(dfMax) ==
3889 8 : static_cast<float>(dfRoundedMax))
3890 : {
3891 7 : dfMin = dfRoundedMin;
3892 7 : dfMax = dfRoundedMax;
3893 : }
3894 : }
3895 : }
3896 : }
3897 60 : };
3898 :
3899 239 : if (bUseActualRange &&
3900 3 : !nc_get_att_double(nGroupDimXID, nVarDimXID, "actual_range",
3901 : adfActualRange))
3902 : {
3903 1 : xMinMax[0] = adfActualRange[0];
3904 1 : xMinMax[1] = adfActualRange[1];
3905 :
3906 : // Present xMinMax[] in the same order as padfXCoord
3907 1 : if ((xMinMax[0] - xMinMax[1]) *
3908 1 : (pdfXCoord[0] - pdfXCoord[xdim - 1]) <
3909 : 0)
3910 : {
3911 0 : std::swap(xMinMax[0], xMinMax[1]);
3912 : }
3913 : }
3914 : else
3915 : {
3916 235 : xMinMax[0] = pdfXCoord[0];
3917 235 : xMinMax[1] = pdfXCoord[xdim - 1];
3918 235 : node_offset = 0;
3919 :
3920 235 : if (nc_var_dimx_datatype == NC_FLOAT)
3921 : {
3922 30 : RoundMinMaxForFloatVals(xMinMax[0], xMinMax[1],
3923 30 : poDS->nRasterXSize - 1);
3924 : }
3925 : }
3926 :
3927 239 : if (bUseActualRange &&
3928 3 : !nc_get_att_double(nGroupDimYID, nVarDimYID, "actual_range",
3929 : adfActualRange))
3930 : {
3931 1 : yMinMax[0] = adfActualRange[0];
3932 1 : yMinMax[1] = adfActualRange[1];
3933 :
3934 : // Present yMinMax[] in the same order as pdfYCoord
3935 1 : if ((yMinMax[0] - yMinMax[1]) *
3936 1 : (pdfYCoord[0] - pdfYCoord[ydim - 1]) <
3937 : 0)
3938 : {
3939 0 : std::swap(yMinMax[0], yMinMax[1]);
3940 : }
3941 : }
3942 : else
3943 : {
3944 235 : yMinMax[0] = pdfYCoord[0];
3945 235 : yMinMax[1] = pdfYCoord[ydim - 1];
3946 235 : node_offset = 0;
3947 :
3948 235 : if (nc_var_dimy_datatype == NC_FLOAT)
3949 : {
3950 30 : RoundMinMaxForFloatVals(yMinMax[0], yMinMax[1],
3951 30 : poDS->nRasterYSize - 1);
3952 : }
3953 : }
3954 :
3955 236 : double dfCoordOffset = 0.0;
3956 236 : double dfCoordScale = 1.0;
3957 236 : if (!nc_get_att_double(nGroupId, nVarDimXID, CF_ADD_OFFSET,
3958 240 : &dfCoordOffset) &&
3959 4 : !nc_get_att_double(nGroupId, nVarDimXID, CF_SCALE_FACTOR,
3960 : &dfCoordScale))
3961 : {
3962 4 : xMinMax[0] = dfCoordOffset + xMinMax[0] * dfCoordScale;
3963 4 : xMinMax[1] = dfCoordOffset + xMinMax[1] * dfCoordScale;
3964 : }
3965 :
3966 236 : if (!nc_get_att_double(nGroupId, nVarDimYID, CF_ADD_OFFSET,
3967 240 : &dfCoordOffset) &&
3968 4 : !nc_get_att_double(nGroupId, nVarDimYID, CF_SCALE_FACTOR,
3969 : &dfCoordScale))
3970 : {
3971 4 : yMinMax[0] = dfCoordOffset + yMinMax[0] * dfCoordScale;
3972 4 : yMinMax[1] = dfCoordOffset + yMinMax[1] * dfCoordScale;
3973 : }
3974 :
3975 : // Check for reverse order of y-coordinate.
3976 236 : if (!bSwitchedXY)
3977 : {
3978 234 : poDS->bBottomUp = (yMinMax[0] <= yMinMax[1]);
3979 234 : if (!poDS->bBottomUp)
3980 : {
3981 34 : std::swap(yMinMax[0], yMinMax[1]);
3982 : }
3983 : }
3984 :
3985 : // Geostationary satellites can specify units in (micro)radians
3986 : // So we check if they do, and if so convert to linear units
3987 : // (meters)
3988 236 : const char *pszProjName = oSRS.GetAttrValue("PROJECTION");
3989 236 : if (pszProjName != nullptr)
3990 : {
3991 24 : if (EQUAL(pszProjName, SRS_PT_GEOSTATIONARY_SATELLITE))
3992 : {
3993 : const double satelliteHeight =
3994 3 : oSRS.GetProjParm(SRS_PP_SATELLITE_HEIGHT, 1.0);
3995 6 : std::string osUnits;
3996 3 : if (NCDFGetAttr(nGroupId, nVarDimXID, "units", osUnits) ==
3997 : CE_None)
3998 : {
3999 3 : if (EQUAL(osUnits.c_str(), "microradian"))
4000 : {
4001 1 : xMinMax[0] =
4002 1 : xMinMax[0] * satelliteHeight * 0.000001;
4003 1 : xMinMax[1] =
4004 1 : xMinMax[1] * satelliteHeight * 0.000001;
4005 : }
4006 3 : else if (EQUAL(osUnits.c_str(), "rad") ||
4007 1 : EQUAL(osUnits.c_str(), "radian"))
4008 : {
4009 2 : xMinMax[0] = xMinMax[0] * satelliteHeight;
4010 2 : xMinMax[1] = xMinMax[1] * satelliteHeight;
4011 : }
4012 : }
4013 3 : if (NCDFGetAttr(nGroupId, nVarDimYID, "units", osUnits) ==
4014 : CE_None)
4015 : {
4016 3 : if (EQUAL(osUnits.c_str(), "microradian"))
4017 : {
4018 1 : yMinMax[0] =
4019 1 : yMinMax[0] * satelliteHeight * 0.000001;
4020 1 : yMinMax[1] =
4021 1 : yMinMax[1] * satelliteHeight * 0.000001;
4022 : }
4023 3 : else if (EQUAL(osUnits.c_str(), "rad") ||
4024 1 : EQUAL(osUnits.c_str(), "radian"))
4025 : {
4026 2 : yMinMax[0] = yMinMax[0] * satelliteHeight;
4027 2 : yMinMax[1] = yMinMax[1] * satelliteHeight;
4028 : }
4029 : }
4030 : }
4031 : }
4032 :
4033 236 : tmpGT[0] = xMinMax[0];
4034 472 : tmpGT[1] = (xMinMax[1] - xMinMax[0]) /
4035 236 : (poDS->nRasterXSize + (node_offset - 1));
4036 236 : tmpGT[2] = 0;
4037 236 : if (bSwitchedXY)
4038 : {
4039 2 : tmpGT[3] = yMinMax[0];
4040 2 : tmpGT[4] = 0;
4041 2 : tmpGT[5] = (yMinMax[1] - yMinMax[0]) /
4042 2 : (poDS->nRasterYSize + (node_offset - 1));
4043 : }
4044 : else
4045 : {
4046 234 : tmpGT[3] = yMinMax[1];
4047 234 : tmpGT[4] = 0;
4048 234 : tmpGT[5] = (yMinMax[0] - yMinMax[1]) /
4049 234 : (poDS->nRasterYSize + (node_offset - 1));
4050 : }
4051 :
4052 : // Compute the center of the pixel.
4053 236 : if (!node_offset)
4054 : {
4055 : // Otherwise its already the pixel center.
4056 236 : tmpGT[0] -= (tmpGT[1] / 2);
4057 236 : tmpGT[3] -= (tmpGT[5] / 2);
4058 : }
4059 : }
4060 :
4061 : const auto AreSRSEqualThroughProj4String =
4062 2 : [](const OGRSpatialReference &oSRS1,
4063 : const OGRSpatialReference &oSRS2)
4064 : {
4065 2 : char *pszProj4Str1 = nullptr;
4066 2 : oSRS1.exportToProj4(&pszProj4Str1);
4067 :
4068 2 : char *pszProj4Str2 = nullptr;
4069 2 : oSRS2.exportToProj4(&pszProj4Str2);
4070 :
4071 : {
4072 2 : char *pszTmp = strstr(pszProj4Str1, "+datum=");
4073 2 : if (pszTmp)
4074 0 : memcpy(pszTmp, "+ellps=", strlen("+ellps="));
4075 : }
4076 :
4077 : {
4078 2 : char *pszTmp = strstr(pszProj4Str2, "+datum=");
4079 2 : if (pszTmp)
4080 2 : memcpy(pszTmp, "+ellps=", strlen("+ellps="));
4081 : }
4082 :
4083 2 : bool bRet = false;
4084 2 : if (pszProj4Str1 && pszProj4Str2 &&
4085 2 : EQUAL(pszProj4Str1, pszProj4Str2))
4086 : {
4087 1 : bRet = true;
4088 : }
4089 :
4090 2 : CPLFree(pszProj4Str1);
4091 2 : CPLFree(pszProj4Str2);
4092 2 : return bRet;
4093 : };
4094 :
4095 243 : if (dfLinearUnitsConvFactor != 1.0)
4096 : {
4097 35 : for (int i = 0; i < 6; ++i)
4098 30 : tmpGT[i] *= dfLinearUnitsConvFactor;
4099 :
4100 5 : if (paosRemovedMDItems)
4101 : {
4102 : char szVarNameX[NC_MAX_NAME + 1];
4103 5 : CPL_IGNORE_RET_VAL(
4104 5 : nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
4105 :
4106 : char szVarNameY[NC_MAX_NAME + 1];
4107 5 : CPL_IGNORE_RET_VAL(
4108 5 : nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
4109 :
4110 5 : paosRemovedMDItems->push_back(
4111 : CPLSPrintf("%s#units", szVarNameX));
4112 5 : paosRemovedMDItems->push_back(
4113 : CPLSPrintf("%s#units", szVarNameY));
4114 : }
4115 : }
4116 :
4117 : // If there is a global "geospatial_bounds_crs" attribute, check that it
4118 : // is consistent with the SRS, and if so, use it as the SRS
4119 : const char *pszGBCRS =
4120 243 : FetchAttr(nGroupId, NC_GLOBAL, "geospatial_bounds_crs");
4121 243 : if (pszGBCRS && STARTS_WITH(pszGBCRS, "EPSG:"))
4122 : {
4123 4 : OGRSpatialReference oSRSFromGBCRS;
4124 2 : oSRSFromGBCRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
4125 2 : if (oSRSFromGBCRS.SetFromUserInput(
4126 : pszGBCRS,
4127 : OGRSpatialReference::
4128 4 : SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE &&
4129 2 : AreSRSEqualThroughProj4String(oSRS, oSRSFromGBCRS))
4130 : {
4131 1 : oSRS = std::move(oSRSFromGBCRS);
4132 1 : SetSpatialRefNoUpdate(&oSRS);
4133 : }
4134 : }
4135 :
4136 243 : CPLFree(pdfXCoord);
4137 243 : CPLFree(pdfYCoord);
4138 : } // end if(has dims)
4139 :
4140 : // Process custom GeoTransform GDAL value.
4141 639 : if (!osGridMappingValue.empty())
4142 : {
4143 266 : if (pszGeoTransform != nullptr)
4144 : {
4145 : const CPLStringList aosGeoTransform(
4146 306 : CSLTokenizeString2(pszGeoTransform, " ", CSLT_HONOURSTRINGS));
4147 153 : if (aosGeoTransform.size() == 6)
4148 : {
4149 153 : bool bUseGeoTransformFromAttribute = true;
4150 :
4151 153 : GDALGeoTransform gtFromAttribute;
4152 1071 : for (int i = 0; i < 6; i++)
4153 : {
4154 918 : gtFromAttribute[i] = CPLAtof(aosGeoTransform[i]);
4155 : }
4156 :
4157 : // When GDAL writes a raster that is north-up oriented, it
4158 : // writes the "GeoTransform" attribute unmodified, that is with
4159 : // gt.yscale < 0, but the first line is actually the southern-most
4160 : // one, consistently with the values of the "y" coordinate
4161 : // variable. This is wrong... but we have always done that, so
4162 : // this is hard to fix now.
4163 : // However there are datasets like
4164 : // https://public.hub.geosphere.at/datahub/resources/spartacus-v2-1d-1km/filelisting/TN/SPARTACUS2-DAILY_TN_2026.nc
4165 : // that correctly use a positive gt.yscale value. So make sure to not emit
4166 : // a warning when comparing against the geotransform derived from
4167 : // the x/y coordinates.
4168 153 : GDALGeoTransform gtFromAttributeNorthUp = gtFromAttribute;
4169 158 : if (gtFromAttributeNorthUp.yscale > 0 &&
4170 5 : gtFromAttributeNorthUp.IsAxisAligned())
4171 : {
4172 1 : gtFromAttributeNorthUp.yorig +=
4173 1 : poDS->nRasterYSize * gtFromAttributeNorthUp.yscale;
4174 1 : gtFromAttributeNorthUp.yscale =
4175 1 : -gtFromAttributeNorthUp.yscale;
4176 : }
4177 :
4178 153 : if (bGotCfGT)
4179 : {
4180 109 : constexpr double GT_RELERROR_WARN_THRESHOLD = 1e-6;
4181 109 : double dfMaxAbsoluteError = 0.0;
4182 763 : for (int i = 0; i < 6; i++)
4183 : {
4184 : double dfAbsoluteError =
4185 654 : std::abs(tmpGT[i] - gtFromAttributeNorthUp[i]);
4186 654 : if (dfAbsoluteError >
4187 654 : std::abs(gtFromAttributeNorthUp[i] *
4188 : GT_RELERROR_WARN_THRESHOLD))
4189 : {
4190 3 : dfMaxAbsoluteError =
4191 3 : std::max(dfMaxAbsoluteError, dfAbsoluteError);
4192 : }
4193 : }
4194 :
4195 109 : if (dfMaxAbsoluteError > 0)
4196 : {
4197 3 : bUseGeoTransformFromAttribute = false;
4198 3 : CPLError(CE_Warning, CPLE_AppDefined,
4199 : "GeoTransform read from attribute of %s "
4200 : "variable differs from value calculated from "
4201 : "dimension variables (max diff = %g). Using "
4202 : "value calculated from dimension variables.",
4203 : osGridMappingValue.c_str(),
4204 : dfMaxAbsoluteError);
4205 : }
4206 : }
4207 :
4208 153 : if (bUseGeoTransformFromAttribute)
4209 : {
4210 150 : if (bGotCfGT)
4211 : {
4212 106 : tmpGT = gtFromAttributeNorthUp;
4213 106 : if (gtFromAttributeNorthUp.IsAxisAligned())
4214 : {
4215 : // Axis direction depends on whether it is GDAL-style CF file
4216 106 : poDS->bBottomUp = bIsGdalCfFile;
4217 : }
4218 : }
4219 : else
4220 : {
4221 44 : tmpGT = gtFromAttribute;
4222 : }
4223 150 : bGotGdalGT = true;
4224 : }
4225 : }
4226 : }
4227 : else
4228 : {
4229 : // Look for corner array values.
4230 : // CPLDebug("GDAL_netCDF",
4231 : // "looking for geotransform corners");
4232 113 : bool bGotNN = false;
4233 113 : double dfNN = FetchCopyParam(osGridMappingValue.c_str(),
4234 : "Northernmost_Northing", 0, &bGotNN);
4235 :
4236 113 : bool bGotSN = false;
4237 113 : double dfSN = FetchCopyParam(osGridMappingValue.c_str(),
4238 : "Southernmost_Northing", 0, &bGotSN);
4239 :
4240 113 : bool bGotEE = false;
4241 113 : double dfEE = FetchCopyParam(osGridMappingValue.c_str(),
4242 : "Easternmost_Easting", 0, &bGotEE);
4243 :
4244 113 : bool bGotWE = false;
4245 113 : double dfWE = FetchCopyParam(osGridMappingValue.c_str(),
4246 : "Westernmost_Easting", 0, &bGotWE);
4247 :
4248 : // Only set the GeoTransform if we got all the values.
4249 113 : if (bGotNN && bGotSN && bGotEE && bGotWE)
4250 : {
4251 0 : bGotGdalGT = true;
4252 :
4253 0 : tmpGT[0] = dfWE;
4254 0 : tmpGT[1] = (dfEE - dfWE) / (poDS->GetRasterXSize() - 1);
4255 0 : tmpGT[2] = 0.0;
4256 0 : tmpGT[3] = dfNN;
4257 0 : tmpGT[4] = 0.0;
4258 0 : tmpGT[5] = (dfSN - dfNN) / (poDS->GetRasterYSize() - 1);
4259 : // Compute the center of the pixel.
4260 0 : tmpGT[0] = dfWE - (tmpGT[1] / 2);
4261 0 : tmpGT[3] = dfNN - (tmpGT[5] / 2);
4262 : }
4263 : } // (pszGeoTransform != NULL)
4264 :
4265 266 : if (bGotGdalSRS && !bGotGdalGT)
4266 78 : CPLDebug("GDAL_netCDF", "Got SRS but no geotransform from GDAL!");
4267 : }
4268 :
4269 639 : if (bGotCfGT || bGotGdalGT)
4270 : {
4271 280 : CPLDebug("GDAL_netCDF", "set bBottomUp = %d from Y axis",
4272 280 : static_cast<int>(poDS->bBottomUp));
4273 : }
4274 :
4275 639 : if (!pszWKT && !bGotCfSRS)
4276 : {
4277 : // Some netCDF files have a srid attribute (#6613) like
4278 : // urn:ogc:def:crs:EPSG::6931
4279 374 : const char *pszSRID = FetchAttr(osGridMappingValue.c_str(), "srid");
4280 374 : if (pszSRID != nullptr)
4281 : {
4282 0 : oSRS.Clear();
4283 0 : if (oSRS.SetFromUserInput(
4284 : pszSRID,
4285 : OGRSpatialReference::
4286 0 : SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE)
4287 : {
4288 0 : CPLDebug("GDAL_netCDF", "Got SRS from %s", pszSRID);
4289 0 : std::string osWKTExport = oSRS.exportToWkt();
4290 0 : if (!osWKTExport.empty())
4291 : {
4292 0 : (*returnProjStr) = std::move(osWKTExport);
4293 : }
4294 : else
4295 : {
4296 0 : m_bAddedProjectionVarsDefs = true;
4297 0 : m_bAddedProjectionVarsData = true;
4298 0 : SetSpatialRefNoUpdate(&oSRS);
4299 : }
4300 : }
4301 : }
4302 : }
4303 :
4304 639 : if (bReadSRSOnly)
4305 257 : return;
4306 :
4307 : // Determines the SRS to be used by the geolocation array, if any
4308 764 : std::string osGeolocWKT = SRS_WKT_WGS84_LAT_LONG;
4309 382 : if (!m_oSRS.IsEmpty())
4310 : {
4311 312 : OGRSpatialReference oGeogCRS;
4312 156 : oGeogCRS.CopyGeogCSFrom(&m_oSRS, true);
4313 156 : const char *const apszOptions[] = {"FORMAT=WKT2_2019", nullptr};
4314 :
4315 312 : std::string osWKTTmp = oGeogCRS.exportToWkt(apszOptions);
4316 156 : if (!osWKTTmp.empty())
4317 : {
4318 156 : osGeolocWKT = std::move(osWKTTmp);
4319 : }
4320 : }
4321 :
4322 : // Process geolocation arrays from CF "coordinates" attribute.
4323 764 : std::string osGeolocXName, osGeolocYName;
4324 382 : if (ProcessCFGeolocation(nGroupId, nVarId, osGeolocWKT, osGeolocXName,
4325 382 : osGeolocYName))
4326 : {
4327 62 : bool bCanCancelGT = true;
4328 62 : if ((nVarDimXID != -1) && (nVarDimYID != -1))
4329 : {
4330 : char szVarNameX[NC_MAX_NAME + 1];
4331 45 : CPL_IGNORE_RET_VAL(
4332 45 : nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
4333 : char szVarNameY[NC_MAX_NAME + 1];
4334 45 : CPL_IGNORE_RET_VAL(
4335 45 : nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
4336 45 : bCanCancelGT =
4337 45 : !(osGeolocXName == szVarNameX && osGeolocYName == szVarNameY);
4338 : }
4339 101 : if (bCanCancelGT && !m_oSRS.IsGeographic() && !m_oSRS.IsProjected() &&
4340 39 : !bSwitchedXY)
4341 : {
4342 37 : bGotCfGT = false;
4343 : }
4344 : }
4345 125 : else if (!bGotCfGT && !bReadSRSOnly && (nVarDimXID != -1) &&
4346 448 : (nVarDimYID != -1) && xdim > 0 && ydim > 0 &&
4347 3 : ((!bSwitchedXY &&
4348 3 : NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
4349 1 : NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr)) ||
4350 2 : (bSwitchedXY &&
4351 0 : NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
4352 0 : NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr))))
4353 : {
4354 : // Case of autotest/gdrivers/data/netcdf/GLMELT_4X5.OCN.nc
4355 : // which is indexed by lat, lon variables, but lat has irregular
4356 : // spacing.
4357 1 : const char *pszGeolocXFullName = poDS->papszDimName[poDS->nXDimID];
4358 1 : const char *pszGeolocYFullName = poDS->papszDimName[poDS->nYDimID];
4359 1 : if (bSwitchedXY)
4360 : {
4361 0 : std::swap(pszGeolocXFullName, pszGeolocYFullName);
4362 0 : GDALPamDataset::SetMetadataItem("SWAP_XY", "YES",
4363 : GDAL_MDD_GEOLOCATION);
4364 : }
4365 :
4366 1 : CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
4367 : pszGeolocXFullName, pszGeolocYFullName);
4368 :
4369 1 : GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
4370 : GDAL_MDD_GEOLOCATION);
4371 :
4372 2 : CPLString osTMP;
4373 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4374 1 : pszGeolocXFullName);
4375 :
4376 1 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP,
4377 : GDAL_MDD_GEOLOCATION);
4378 1 : GDALPamDataset::SetMetadataItem("X_BAND", "1", GDAL_MDD_GEOLOCATION);
4379 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4380 1 : pszGeolocYFullName);
4381 :
4382 1 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP,
4383 : GDAL_MDD_GEOLOCATION);
4384 1 : GDALPamDataset::SetMetadataItem("Y_BAND", "1", GDAL_MDD_GEOLOCATION);
4385 :
4386 1 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0",
4387 : GDAL_MDD_GEOLOCATION);
4388 1 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1",
4389 : GDAL_MDD_GEOLOCATION);
4390 :
4391 1 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0",
4392 : GDAL_MDD_GEOLOCATION);
4393 1 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1", GDAL_MDD_GEOLOCATION);
4394 :
4395 1 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
4396 : "PIXEL_CENTER", GDAL_MDD_GEOLOCATION);
4397 : }
4398 :
4399 : // Set GeoTransform if we got a complete one - after projection has been set
4400 382 : if (bGotCfGT || bGotGdalGT)
4401 : {
4402 226 : m_bAddedProjectionVarsDefs = true;
4403 226 : m_bAddedProjectionVarsData = true;
4404 226 : SetGeoTransformNoUpdate(tmpGT);
4405 : }
4406 :
4407 : // Debugging reports.
4408 382 : CPLDebug("GDAL_netCDF",
4409 : "bGotGeogCS=%d bGotCfSRS=%d bGotCfGT=%d bGotCfWktSRS=%d "
4410 : "bGotGdalSRS=%d bGotGdalGT=%d",
4411 : static_cast<int>(bGotGeogCS), static_cast<int>(bGotCfSRS),
4412 : static_cast<int>(bGotCfGT), static_cast<int>(bGotCfWktSRS),
4413 : static_cast<int>(bGotGdalSRS), static_cast<int>(bGotGdalGT));
4414 :
4415 382 : if (!bGotCfGT && !bGotGdalGT)
4416 156 : CPLDebug("GDAL_netCDF", "did not get geotransform from CF nor GDAL!");
4417 :
4418 382 : if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfGT && !bGotCfWktSRS)
4419 156 : CPLDebug("GDAL_netCDF", "did not get projection from CF nor GDAL!");
4420 :
4421 : // wish of 6195
4422 : // we don't have CS/SRS, but we do have GT, and we live in -180,360 -90,90
4423 382 : if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfWktSRS)
4424 : {
4425 226 : if (bGotCfGT || bGotGdalGT)
4426 : {
4427 140 : bool bAssumedLongLat = CPLTestBool(CSLFetchNameValueDef(
4428 70 : papszOpenOptions, "ASSUME_LONGLAT",
4429 : CPLGetConfigOption("GDAL_NETCDF_ASSUME_LONGLAT", "NO")));
4430 :
4431 2 : if (bAssumedLongLat && tmpGT[0] >= -180 && tmpGT[0] < 360 &&
4432 2 : (tmpGT[0] + tmpGT[1] * poDS->GetRasterXSize()) <= 360 &&
4433 74 : tmpGT[3] <= 90 && tmpGT[3] > -90 &&
4434 2 : (tmpGT[3] + tmpGT[5] * poDS->GetRasterYSize()) >= -90)
4435 : {
4436 :
4437 2 : poDS->bIsGeographic = true;
4438 : // seems odd to use 4326 so OGC:CRS84
4439 2 : oSRS.SetFromUserInput("OGC:CRS84");
4440 2 : if (returnProjStr != nullptr)
4441 : {
4442 0 : *returnProjStr = oSRS.exportToWkt();
4443 : }
4444 : else
4445 : {
4446 2 : m_bAddedProjectionVarsDefs = true;
4447 2 : m_bAddedProjectionVarsData = true;
4448 2 : SetSpatialRefNoUpdate(&oSRS);
4449 : }
4450 :
4451 2 : CPLDebug("netCDF",
4452 : "Assumed Longitude Latitude CRS 'OGC:CRS84' because "
4453 : "none otherwise available and geotransform within "
4454 : "suitable bounds. "
4455 : "Set GDAL_NETCDF_ASSUME_LONGLAT=NO as configuration "
4456 : "option or "
4457 : " ASSUME_LONGLAT=NO as open option to bypass this "
4458 : "assumption.");
4459 : }
4460 : }
4461 : }
4462 :
4463 : // Search for Well-known GeogCS if got only CF WKT
4464 : // Disabled for now, as a named datum also include control points
4465 : // (see mailing list and bug#4281
4466 : // For example, WGS84 vs. GDA94 (EPSG:3577) - AEA in netcdf_cf.py
4467 :
4468 : // Disabled for now, but could be set in a config option.
4469 : #if 0
4470 : bool bLookForWellKnownGCS = false; // This could be a Config Option.
4471 :
4472 : if( bLookForWellKnownGCS && bGotCfSRS && !bGotGdalSRS )
4473 : {
4474 : // ET - Could use a more exhaustive method by scanning all EPSG codes in
4475 : // data/gcs.csv as proposed by Even in the gdal-dev mailing list "help
4476 : // for comparing two WKT".
4477 : // This code could be contributed to a new function.
4478 : // OGRSpatialReference * OGRSpatialReference::FindMatchingGeogCS(
4479 : // const OGRSpatialReference *poOther) */
4480 : CPLDebug("GDAL_netCDF", "Searching for Well-known GeogCS");
4481 : const char *pszWKGCSList[] = { "WGS84", "WGS72", "NAD27", "NAD83" };
4482 : char *pszWKGCS = NULL;
4483 : oSRS.exportToPrettyWkt(&pszWKGCS);
4484 : for( size_t i = 0; i < sizeof(pszWKGCSList) / 8; i++ )
4485 : {
4486 : pszWKGCS = CPLStrdup(pszWKGCSList[i]);
4487 : OGRSpatialReference oSRSTmp;
4488 : oSRSTmp.SetWellKnownGeogCS(pszWKGCSList[i]);
4489 : // Set datum to unknown, bug #4281.
4490 : if( oSRSTmp.GetAttrNode("DATUM" ) )
4491 : oSRSTmp.GetAttrNode("DATUM")->GetChild(0)->SetValue("unknown");
4492 : // Could use OGRSpatialReference::StripCTParms(), but let's keep
4493 : // TOWGS84.
4494 : oSRSTmp.GetRoot()->StripNodes("AXIS");
4495 : oSRSTmp.GetRoot()->StripNodes("AUTHORITY");
4496 : oSRSTmp.GetRoot()->StripNodes("EXTENSION");
4497 :
4498 : oSRSTmp.exportToPrettyWkt(&pszWKGCS);
4499 : if( oSRS.IsSameGeogCS(&oSRSTmp) )
4500 : {
4501 : oSRS.SetWellKnownGeogCS(pszWKGCSList[i]);
4502 : oSRS.exportToWkt(&(pszTempProjection));
4503 : SetProjection(pszTempProjection);
4504 : CPLFree(pszTempProjection);
4505 : }
4506 : }
4507 : }
4508 : #endif
4509 : }
4510 :
4511 213 : void netCDFDataset::SetProjectionFromVar(int nGroupId, int nVarId,
4512 : bool bReadSRSOnly)
4513 : {
4514 213 : SetProjectionFromVar(nGroupId, nVarId, bReadSRSOnly, nullptr, nullptr,
4515 : nullptr, nullptr);
4516 213 : }
4517 :
4518 308 : bool netCDFDataset::ProcessNASAL2OceanGeoLocation(int nGroupId, int nVarId)
4519 : {
4520 : // Cf https://oceancolor.gsfc.nasa.gov/docs/format/l2nc/
4521 : // and https://github.com/OSGeo/gdal/issues/7605
4522 :
4523 : // Check for a structure like:
4524 : /* netcdf SNPP_VIIRS.20230406T024200.L2.OC.NRT {
4525 : dimensions:
4526 : number_of_lines = 3248 ;
4527 : pixels_per_line = 3200 ;
4528 : [...]
4529 : pixel_control_points = 3200 ;
4530 : [...]
4531 : group: geophysical_data {
4532 : variables:
4533 : short aot_862(number_of_lines, pixels_per_line) ; <-- nVarId
4534 : [...]
4535 : }
4536 : group: navigation_data {
4537 : variables:
4538 : float longitude(number_of_lines, pixel_control_points) ;
4539 : [...]
4540 : float latitude(number_of_lines, pixel_control_points) ;
4541 : [...]
4542 : }
4543 : }
4544 : */
4545 : // Note that the longitude and latitude arrays are not indexed by the
4546 : // same dimensions. Handle only the case where
4547 : // pixel_control_points == pixels_per_line
4548 : // If there was a subsampling of the geolocation arrays, we'd need to
4549 : // add more logic.
4550 :
4551 616 : std::string osGroupName;
4552 308 : osGroupName.resize(NC_MAX_NAME);
4553 308 : NCDF_ERR(nc_inq_grpname(nGroupId, &osGroupName[0]));
4554 308 : osGroupName.resize(strlen(osGroupName.data()));
4555 308 : if (osGroupName != "geophysical_data")
4556 307 : return false;
4557 :
4558 1 : int nVarDims = 0;
4559 1 : NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
4560 1 : if (nVarDims != 2)
4561 0 : return false;
4562 :
4563 1 : int nNavigationDataGrpId = 0;
4564 1 : if (nc_inq_grp_ncid(cdfid, "navigation_data", &nNavigationDataGrpId) !=
4565 : NC_NOERR)
4566 0 : return false;
4567 :
4568 : std::array<int, 2> anVarDimIds;
4569 1 : NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
4570 :
4571 1 : int nLongitudeId = 0;
4572 1 : int nLatitudeId = 0;
4573 1 : if (nc_inq_varid(nNavigationDataGrpId, "longitude", &nLongitudeId) !=
4574 2 : NC_NOERR ||
4575 1 : nc_inq_varid(nNavigationDataGrpId, "latitude", &nLatitudeId) !=
4576 : NC_NOERR)
4577 : {
4578 0 : return false;
4579 : }
4580 :
4581 1 : int nDimsLongitude = 0;
4582 1 : NCDF_ERR(
4583 : nc_inq_varndims(nNavigationDataGrpId, nLongitudeId, &nDimsLongitude));
4584 1 : int nDimsLatitude = 0;
4585 1 : NCDF_ERR(
4586 : nc_inq_varndims(nNavigationDataGrpId, nLatitudeId, &nDimsLatitude));
4587 1 : if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
4588 : {
4589 0 : return false;
4590 : }
4591 :
4592 : std::array<int, 2> anDimLongitudeIds;
4593 1 : NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLongitudeId,
4594 : anDimLongitudeIds.data()));
4595 : std::array<int, 2> anDimLatitudeIds;
4596 1 : NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLatitudeId,
4597 : anDimLatitudeIds.data()));
4598 1 : if (anDimLongitudeIds != anDimLatitudeIds)
4599 : {
4600 0 : return false;
4601 : }
4602 :
4603 : std::array<size_t, 2> anSizeVarDimIds;
4604 : std::array<size_t, 2> anSizeLongLatIds;
4605 2 : if (!(nc_inq_dimlen(cdfid, anVarDimIds[0], &anSizeVarDimIds[0]) ==
4606 1 : NC_NOERR &&
4607 1 : nc_inq_dimlen(cdfid, anVarDimIds[1], &anSizeVarDimIds[1]) ==
4608 1 : NC_NOERR &&
4609 1 : nc_inq_dimlen(cdfid, anDimLongitudeIds[0], &anSizeLongLatIds[0]) ==
4610 1 : NC_NOERR &&
4611 1 : nc_inq_dimlen(cdfid, anDimLongitudeIds[1], &anSizeLongLatIds[1]) ==
4612 : NC_NOERR &&
4613 1 : anSizeVarDimIds == anSizeLongLatIds))
4614 : {
4615 0 : return false;
4616 : }
4617 :
4618 1 : const char *pszGeolocXFullName = "/navigation_data/longitude";
4619 1 : const char *pszGeolocYFullName = "/navigation_data/latitude";
4620 :
4621 1 : if (bSwitchedXY)
4622 : {
4623 0 : std::swap(pszGeolocXFullName, pszGeolocYFullName);
4624 0 : GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", GDAL_MDD_GEOLOCATION);
4625 : }
4626 :
4627 1 : CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
4628 : pszGeolocXFullName, pszGeolocYFullName);
4629 :
4630 1 : GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
4631 : GDAL_MDD_GEOLOCATION);
4632 :
4633 1 : CPLString osTMP;
4634 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
4635 :
4636 1 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, GDAL_MDD_GEOLOCATION);
4637 1 : GDALPamDataset::SetMetadataItem("X_BAND", "1", GDAL_MDD_GEOLOCATION);
4638 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
4639 :
4640 1 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, GDAL_MDD_GEOLOCATION);
4641 1 : GDALPamDataset::SetMetadataItem("Y_BAND", "1", GDAL_MDD_GEOLOCATION);
4642 :
4643 1 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", GDAL_MDD_GEOLOCATION);
4644 1 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", GDAL_MDD_GEOLOCATION);
4645 :
4646 1 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", GDAL_MDD_GEOLOCATION);
4647 1 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1", GDAL_MDD_GEOLOCATION);
4648 :
4649 1 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
4650 : GDAL_MDD_GEOLOCATION);
4651 1 : return true;
4652 : }
4653 :
4654 307 : bool netCDFDataset::ProcessNASAEMITGeoLocation(int nGroupId, int nVarId)
4655 : {
4656 : // Cf https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/
4657 :
4658 : // Check for a structure like:
4659 : /* netcdf EMIT_L2A_RFL_001_20220903T163129_2224611_012 {
4660 : dimensions:
4661 : downtrack = 1280 ;
4662 : crosstrack = 1242 ;
4663 : bands = 285 ;
4664 : [...]
4665 :
4666 : variables:
4667 : float reflectance(downtrack, crosstrack, bands) ;
4668 :
4669 : group: location {
4670 : variables:
4671 : double lon(downtrack, crosstrack) ;
4672 : lon:_FillValue = -9999. ;
4673 : lon:long_name = "Longitude (WGS-84)" ;
4674 : lon:units = "degrees east" ;
4675 : double lat(downtrack, crosstrack) ;
4676 : lat:_FillValue = -9999. ;
4677 : lat:long_name = "Latitude (WGS-84)" ;
4678 : lat:units = "degrees north" ;
4679 : } // group location
4680 :
4681 : }
4682 : or
4683 : netcdf EMIT_L2B_MIN_001_20231024T055538_2329704_040 {
4684 : dimensions:
4685 : downtrack = 1664 ;
4686 : crosstrack = 1242 ;
4687 : [...]
4688 : variables:
4689 : float group_1_band_depth(downtrack, crosstrack) ;
4690 : group_1_band_depth:_FillValue = -9999.f ;
4691 : group_1_band_depth:long_name = "Group 1 Band Depth" ;
4692 : group_1_band_depth:units = "unitless" ;
4693 : [...]
4694 : group: location {
4695 : variables:
4696 : double lon(downtrack, crosstrack) ;
4697 : lon:_FillValue = -9999. ;
4698 : lon:long_name = "Longitude (WGS-84)" ;
4699 : lon:units = "degrees east" ;
4700 : double lat(downtrack, crosstrack) ;
4701 : lat:_FillValue = -9999. ;
4702 : lat:long_name = "Latitude (WGS-84)" ;
4703 : lat:units = "degrees north" ;
4704 : }
4705 : */
4706 :
4707 307 : int nVarDims = 0;
4708 307 : NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
4709 307 : if (nVarDims != 2 && nVarDims != 3)
4710 14 : return false;
4711 :
4712 293 : int nLocationGrpId = 0;
4713 293 : if (nc_inq_grp_ncid(cdfid, "location", &nLocationGrpId) != NC_NOERR)
4714 62 : return false;
4715 :
4716 : std::array<int, 3> anVarDimIds;
4717 231 : NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
4718 231 : if (nYDimID != anVarDimIds[0] || nXDimID != anVarDimIds[1])
4719 21 : return false;
4720 :
4721 210 : int nLongitudeId = 0;
4722 210 : int nLatitudeId = 0;
4723 248 : if (nc_inq_varid(nLocationGrpId, "lon", &nLongitudeId) != NC_NOERR ||
4724 38 : nc_inq_varid(nLocationGrpId, "lat", &nLatitudeId) != NC_NOERR)
4725 : {
4726 172 : return false;
4727 : }
4728 :
4729 38 : int nDimsLongitude = 0;
4730 38 : NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLongitudeId, &nDimsLongitude));
4731 38 : int nDimsLatitude = 0;
4732 38 : NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLatitudeId, &nDimsLatitude));
4733 38 : if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
4734 : {
4735 34 : return false;
4736 : }
4737 :
4738 : std::array<int, 2> anDimLongitudeIds;
4739 4 : NCDF_ERR(nc_inq_vardimid(nLocationGrpId, nLongitudeId,
4740 : anDimLongitudeIds.data()));
4741 : std::array<int, 2> anDimLatitudeIds;
4742 4 : NCDF_ERR(
4743 : nc_inq_vardimid(nLocationGrpId, nLatitudeId, anDimLatitudeIds.data()));
4744 4 : if (anDimLongitudeIds != anDimLatitudeIds)
4745 : {
4746 0 : return false;
4747 : }
4748 :
4749 8 : if (anDimLongitudeIds[0] != anVarDimIds[0] ||
4750 4 : anDimLongitudeIds[1] != anVarDimIds[1])
4751 : {
4752 0 : return false;
4753 : }
4754 :
4755 4 : const char *pszGeolocXFullName = "/location/lon";
4756 4 : const char *pszGeolocYFullName = "/location/lat";
4757 :
4758 4 : CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
4759 : pszGeolocXFullName, pszGeolocYFullName);
4760 :
4761 4 : GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
4762 : GDAL_MDD_GEOLOCATION);
4763 :
4764 4 : CPLString osTMP;
4765 4 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
4766 :
4767 4 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, GDAL_MDD_GEOLOCATION);
4768 4 : GDALPamDataset::SetMetadataItem("X_BAND", "1", GDAL_MDD_GEOLOCATION);
4769 4 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
4770 :
4771 4 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, GDAL_MDD_GEOLOCATION);
4772 4 : GDALPamDataset::SetMetadataItem("Y_BAND", "1", GDAL_MDD_GEOLOCATION);
4773 :
4774 4 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", GDAL_MDD_GEOLOCATION);
4775 4 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", GDAL_MDD_GEOLOCATION);
4776 :
4777 4 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", GDAL_MDD_GEOLOCATION);
4778 4 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1", GDAL_MDD_GEOLOCATION);
4779 :
4780 4 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
4781 : GDAL_MDD_GEOLOCATION);
4782 4 : return true;
4783 : }
4784 :
4785 382 : int netCDFDataset::ProcessCFGeolocation(int nGroupId, int nVarId,
4786 : const std::string &osGeolocWKT,
4787 : std::string &osGeolocXNameOut,
4788 : std::string &osGeolocYNameOut)
4789 : {
4790 382 : bool bAddGeoloc = false;
4791 382 : char *pszCoordinates = nullptr;
4792 :
4793 : // If there is no explicit "coordinates" attribute, check if there are
4794 : // "lon" and "lat" 2D variables whose dimensions are the last
4795 : // 2 ones of the variable of interest.
4796 382 : if (NCDFGetAttr(nGroupId, nVarId, "coordinates", &pszCoordinates) !=
4797 : CE_None)
4798 : {
4799 329 : CPLFree(pszCoordinates);
4800 329 : pszCoordinates = nullptr;
4801 :
4802 329 : int nVarDims = 0;
4803 329 : NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
4804 329 : if (nVarDims >= 2)
4805 : {
4806 658 : std::vector<int> anVarDimIds(nVarDims);
4807 329 : NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
4808 :
4809 329 : int nLongitudeId = 0;
4810 329 : int nLatitudeId = 0;
4811 402 : if (nc_inq_varid(nGroupId, "lon", &nLongitudeId) == NC_NOERR &&
4812 73 : nc_inq_varid(nGroupId, "lat", &nLatitudeId) == NC_NOERR)
4813 : {
4814 73 : int nDimsLongitude = 0;
4815 73 : NCDF_ERR(
4816 : nc_inq_varndims(nGroupId, nLongitudeId, &nDimsLongitude));
4817 73 : int nDimsLatitude = 0;
4818 73 : NCDF_ERR(
4819 : nc_inq_varndims(nGroupId, nLatitudeId, &nDimsLatitude));
4820 73 : if (nDimsLongitude == 2 && nDimsLatitude == 2)
4821 : {
4822 42 : std::vector<int> anDimLongitudeIds(2);
4823 21 : NCDF_ERR(nc_inq_vardimid(nGroupId, nLongitudeId,
4824 : anDimLongitudeIds.data()));
4825 42 : std::vector<int> anDimLatitudeIds(2);
4826 21 : NCDF_ERR(nc_inq_vardimid(nGroupId, nLatitudeId,
4827 : anDimLatitudeIds.data()));
4828 21 : if (anDimLongitudeIds == anDimLatitudeIds &&
4829 42 : anVarDimIds[anVarDimIds.size() - 2] ==
4830 63 : anDimLongitudeIds[0] &&
4831 42 : anVarDimIds[anVarDimIds.size() - 1] ==
4832 21 : anDimLongitudeIds[1])
4833 : {
4834 21 : pszCoordinates = CPLStrdup("lon lat");
4835 : }
4836 : }
4837 : }
4838 : }
4839 : }
4840 :
4841 382 : if (pszCoordinates)
4842 : {
4843 : // Get X and Y geolocation names from coordinates attribute.
4844 : const CPLStringList aosCoordinates(
4845 148 : NCDFTokenizeCoordinatesAttribute(pszCoordinates));
4846 74 : if (aosCoordinates.size() >= 2)
4847 : {
4848 : char szGeolocXName[NC_MAX_NAME + 1];
4849 : char szGeolocYName[NC_MAX_NAME + 1];
4850 70 : szGeolocXName[0] = '\0';
4851 70 : szGeolocYName[0] = '\0';
4852 :
4853 : // Test that each variable is longitude/latitude.
4854 226 : for (int i = 0; i < aosCoordinates.size(); i++)
4855 : {
4856 156 : if (NCDFIsVarLongitude(nGroupId, -1, aosCoordinates[i]))
4857 : {
4858 59 : int nOtherGroupId = -1;
4859 59 : int nOtherVarId = -1;
4860 : // Check that the variable actually exists
4861 : // Needed on Sentinel-3 products
4862 59 : if (NCDFResolveVar(nGroupId, aosCoordinates[i],
4863 59 : &nOtherGroupId, &nOtherVarId) == CE_None)
4864 : {
4865 57 : snprintf(szGeolocXName, sizeof(szGeolocXName), "%s",
4866 : aosCoordinates[i]);
4867 : }
4868 : }
4869 97 : else if (NCDFIsVarLatitude(nGroupId, -1, aosCoordinates[i]))
4870 : {
4871 59 : int nOtherGroupId = -1;
4872 59 : int nOtherVarId = -1;
4873 : // Check that the variable actually exists
4874 : // Needed on Sentinel-3 products
4875 59 : if (NCDFResolveVar(nGroupId, aosCoordinates[i],
4876 59 : &nOtherGroupId, &nOtherVarId) == CE_None)
4877 : {
4878 57 : snprintf(szGeolocYName, sizeof(szGeolocYName), "%s",
4879 : aosCoordinates[i]);
4880 : }
4881 : }
4882 : }
4883 : // Add GEOLOCATION metadata.
4884 70 : if (!EQUAL(szGeolocXName, "") && !EQUAL(szGeolocYName, ""))
4885 : {
4886 57 : osGeolocXNameOut = szGeolocXName;
4887 57 : osGeolocYNameOut = szGeolocYName;
4888 :
4889 114 : std::string osGeolocXFullName;
4890 114 : std::string osGeolocYFullName;
4891 57 : if (NCDFResolveVarFullName(nGroupId, szGeolocXName,
4892 114 : osGeolocXFullName) == CE_None &&
4893 57 : NCDFResolveVarFullName(nGroupId, szGeolocYName,
4894 : osGeolocYFullName) == CE_None)
4895 : {
4896 57 : if (bSwitchedXY)
4897 : {
4898 2 : std::swap(osGeolocXFullName, osGeolocYFullName);
4899 2 : GDALPamDataset::SetMetadataItem("SWAP_XY", "YES",
4900 : GDAL_MDD_GEOLOCATION);
4901 : }
4902 :
4903 57 : bAddGeoloc = true;
4904 57 : CPLDebug("GDAL_netCDF",
4905 : "using variables %s and %s for GEOLOCATION",
4906 : osGeolocXFullName.c_str(),
4907 : osGeolocYFullName.c_str());
4908 :
4909 57 : GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
4910 : GDAL_MDD_GEOLOCATION);
4911 :
4912 114 : CPLString osTMP;
4913 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4914 57 : osGeolocXFullName.c_str());
4915 :
4916 57 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP,
4917 : GDAL_MDD_GEOLOCATION);
4918 57 : GDALPamDataset::SetMetadataItem("X_BAND", "1",
4919 : GDAL_MDD_GEOLOCATION);
4920 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4921 57 : osGeolocYFullName.c_str());
4922 :
4923 57 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP,
4924 : GDAL_MDD_GEOLOCATION);
4925 57 : GDALPamDataset::SetMetadataItem("Y_BAND", "1",
4926 : GDAL_MDD_GEOLOCATION);
4927 :
4928 57 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0",
4929 : GDAL_MDD_GEOLOCATION);
4930 57 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1",
4931 : GDAL_MDD_GEOLOCATION);
4932 :
4933 57 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0",
4934 : GDAL_MDD_GEOLOCATION);
4935 57 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1",
4936 : GDAL_MDD_GEOLOCATION);
4937 :
4938 57 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
4939 : "PIXEL_CENTER",
4940 : GDAL_MDD_GEOLOCATION);
4941 : }
4942 : else
4943 : {
4944 0 : CPLDebug("GDAL_netCDF",
4945 : "cannot resolve location of "
4946 : "lat/lon variables specified by the coordinates "
4947 : "attribute [%s]",
4948 : pszCoordinates);
4949 57 : }
4950 : }
4951 : else
4952 : {
4953 13 : CPLDebug("GDAL_netCDF",
4954 : "coordinates attribute [%s] is unsupported",
4955 : pszCoordinates);
4956 : }
4957 : }
4958 : else
4959 : {
4960 4 : CPLDebug("GDAL_netCDF",
4961 : "coordinates attribute [%s] with %d element(s) is "
4962 : "unsupported",
4963 : pszCoordinates, aosCoordinates.size());
4964 : }
4965 : }
4966 :
4967 : else
4968 : {
4969 308 : bAddGeoloc = ProcessNASAL2OceanGeoLocation(nGroupId, nVarId);
4970 :
4971 308 : if (!bAddGeoloc)
4972 307 : bAddGeoloc = ProcessNASAEMITGeoLocation(nGroupId, nVarId);
4973 : }
4974 :
4975 382 : CPLFree(pszCoordinates);
4976 :
4977 382 : return bAddGeoloc;
4978 : }
4979 :
4980 8 : CPLErr netCDFDataset::Set1DGeolocation(int nGroupId, int nVarId,
4981 : const char *szDimName)
4982 : {
4983 : // Get values.
4984 8 : char *pszVarValues = nullptr;
4985 8 : CPLErr eErr = NCDFGet1DVar(nGroupId, nVarId, &pszVarValues);
4986 8 : if (eErr != CE_None)
4987 0 : return eErr;
4988 :
4989 : // Write metadata.
4990 8 : char szTemp[NC_MAX_NAME + 1 + 32] = {};
4991 8 : snprintf(szTemp, sizeof(szTemp), "%s_VALUES", szDimName);
4992 8 : GDALPamDataset::SetMetadataItem(szTemp, pszVarValues, "GEOLOCATION2");
4993 :
4994 8 : CPLFree(pszVarValues);
4995 :
4996 8 : return CE_None;
4997 : }
4998 :
4999 0 : double *netCDFDataset::Get1DGeolocation(CPL_UNUSED const char *szDimName,
5000 : int &nVarLen)
5001 : {
5002 0 : nVarLen = 0;
5003 :
5004 : // Get Y_VALUES as tokens.
5005 : const CPLStringList aosValues(
5006 0 : NCDFTokenizeArray(GetMetadataItem("Y_VALUES", "GEOLOCATION2")));
5007 0 : if (aosValues.empty())
5008 0 : return nullptr;
5009 :
5010 : // Initialize and fill array.
5011 0 : nVarLen = aosValues.size();
5012 : double *pdfVarValues =
5013 0 : static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
5014 :
5015 0 : for (int i = 0, j = 0; i < nVarLen; i++)
5016 : {
5017 0 : if (!bBottomUp)
5018 0 : j = nVarLen - 1 - i;
5019 : else
5020 0 : j = i; // Invert latitude values.
5021 0 : char *pszTemp = nullptr;
5022 0 : pdfVarValues[j] = CPLStrtod(aosValues[i], &pszTemp);
5023 : }
5024 :
5025 0 : return pdfVarValues;
5026 : }
5027 :
5028 : /************************************************************************/
5029 : /* SetSpatialRefNoUpdate() */
5030 : /************************************************************************/
5031 :
5032 312 : void netCDFDataset::SetSpatialRefNoUpdate(const OGRSpatialReference *poSRS)
5033 : {
5034 312 : m_oSRS.Clear();
5035 312 : if (poSRS)
5036 304 : m_oSRS = *poSRS;
5037 312 : m_bHasProjection = true;
5038 312 : }
5039 :
5040 : /************************************************************************/
5041 : /* SetSpatialRef() */
5042 : /************************************************************************/
5043 :
5044 88 : CPLErr netCDFDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
5045 : {
5046 176 : CPLMutexHolderD(&hNCMutex);
5047 :
5048 88 : if (GetAccess() != GA_Update || m_bHasProjection)
5049 : {
5050 0 : CPLError(CE_Failure, CPLE_AppDefined,
5051 : "netCDFDataset::_SetProjection() should only be called once "
5052 : "in update mode!");
5053 0 : return CE_Failure;
5054 : }
5055 :
5056 88 : if (m_bHasGeoTransform)
5057 : {
5058 32 : SetSpatialRefNoUpdate(poSRS);
5059 :
5060 : // For NC4/NC4C, writing both projection variables and data,
5061 : // followed by redefining nodata value, cancels the projection
5062 : // info from the Band variable, so for now only write the
5063 : // variable definitions, and write data at the end.
5064 : // See https://trac.osgeo.org/gdal/ticket/7245
5065 32 : return AddProjectionVars(true, nullptr, nullptr);
5066 : }
5067 :
5068 56 : SetSpatialRefNoUpdate(poSRS);
5069 :
5070 56 : return CE_None;
5071 : }
5072 :
5073 : /************************************************************************/
5074 : /* SetGeoTransformNoUpdate() */
5075 : /************************************************************************/
5076 :
5077 315 : void netCDFDataset::SetGeoTransformNoUpdate(const GDALGeoTransform >)
5078 : {
5079 315 : m_gt = gt;
5080 315 : m_bHasGeoTransform = true;
5081 315 : }
5082 :
5083 : /************************************************************************/
5084 : /* SetGeoTransform() */
5085 : /************************************************************************/
5086 :
5087 89 : CPLErr netCDFDataset::SetGeoTransform(const GDALGeoTransform >)
5088 : {
5089 178 : CPLMutexHolderD(&hNCMutex);
5090 :
5091 89 : if (GetAccess() != GA_Update || m_bHasGeoTransform)
5092 : {
5093 0 : CPLError(CE_Failure, CPLE_AppDefined,
5094 : "netCDFDataset::SetGeoTransform() should only be called once "
5095 : "in update mode!");
5096 0 : return CE_Failure;
5097 : }
5098 :
5099 89 : CPLDebug("GDAL_netCDF", "SetGeoTransform(%f,%f,%f,%f,%f,%f)", gt.xorig,
5100 89 : gt.xscale, gt.xrot, gt.yorig, gt.yrot, gt.yscale);
5101 :
5102 89 : SetGeoTransformNoUpdate(gt);
5103 :
5104 89 : if (m_bHasProjection)
5105 : {
5106 :
5107 : // For NC4/NC4C, writing both projection variables and data,
5108 : // followed by redefining nodata value, cancels the projection
5109 : // info from the Band variable, so for now only write the
5110 : // variable definitions, and write data at the end.
5111 : // See https://trac.osgeo.org/gdal/ticket/7245
5112 3 : return AddProjectionVars(true, nullptr, nullptr);
5113 : }
5114 :
5115 86 : return CE_None;
5116 : }
5117 :
5118 : /************************************************************************/
5119 : /* NCDFWriteSRSVariable() */
5120 : /************************************************************************/
5121 :
5122 143 : int NCDFWriteSRSVariable(int cdfid, const OGRSpatialReference *poSRS,
5123 : char **ppszCFProjection, bool bWriteGDALTags,
5124 : const std::string &srsVarName)
5125 : {
5126 143 : char *pszCFProjection = nullptr;
5127 143 : char **papszKeyValues = nullptr;
5128 143 : poSRS->exportToCF1(&pszCFProjection, &papszKeyValues, nullptr, nullptr);
5129 :
5130 143 : if (bWriteGDALTags)
5131 : {
5132 142 : const char *pszWKT = CSLFetchNameValue(papszKeyValues, NCDF_CRS_WKT);
5133 142 : if (pszWKT)
5134 : {
5135 : // SPATIAL_REF is deprecated. Will be removed in GDAL 4.
5136 142 : papszKeyValues =
5137 142 : CSLSetNameValue(papszKeyValues, NCDF_SPATIAL_REF, pszWKT);
5138 : }
5139 : }
5140 :
5141 143 : const int nValues = CSLCount(papszKeyValues);
5142 :
5143 : int NCDFVarID;
5144 286 : std::string varNameRadix(pszCFProjection);
5145 143 : int nCounter = 2;
5146 : while (true)
5147 : {
5148 145 : NCDFVarID = -1;
5149 145 : nc_inq_varid(cdfid, pszCFProjection, &NCDFVarID);
5150 145 : if (NCDFVarID < 0)
5151 140 : break;
5152 :
5153 5 : int nbAttr = 0;
5154 5 : NCDF_ERR(nc_inq_varnatts(cdfid, NCDFVarID, &nbAttr));
5155 5 : bool bSame = nbAttr == nValues;
5156 41 : for (int i = 0; bSame && (i < nbAttr); i++)
5157 : {
5158 : char szAttrName[NC_MAX_NAME + 1];
5159 38 : szAttrName[0] = 0;
5160 38 : NCDF_ERR(nc_inq_attname(cdfid, NCDFVarID, i, szAttrName));
5161 :
5162 : const char *pszValue =
5163 38 : CSLFetchNameValue(papszKeyValues, szAttrName);
5164 38 : if (!pszValue)
5165 : {
5166 0 : bSame = false;
5167 2 : break;
5168 : }
5169 :
5170 38 : nc_type atttype = NC_NAT;
5171 38 : size_t attlen = 0;
5172 38 : NCDF_ERR(
5173 : nc_inq_att(cdfid, NCDFVarID, szAttrName, &atttype, &attlen));
5174 38 : if (atttype != NC_CHAR && atttype != NC_DOUBLE)
5175 : {
5176 0 : bSame = false;
5177 0 : break;
5178 : }
5179 38 : if (atttype == NC_CHAR)
5180 : {
5181 15 : if (CPLGetValueType(pszValue) != CPL_VALUE_STRING)
5182 : {
5183 0 : bSame = false;
5184 0 : break;
5185 : }
5186 15 : std::string val;
5187 15 : NCDFGetAttr(cdfid, NCDFVarID, szAttrName, val);
5188 15 : if (val != pszValue)
5189 : {
5190 0 : bSame = false;
5191 0 : break;
5192 : }
5193 : }
5194 : else
5195 : {
5196 : const CPLStringList aosTokens(
5197 23 : CSLTokenizeString2(pszValue, ",", 0));
5198 23 : if (static_cast<size_t>(aosTokens.size()) != attlen)
5199 : {
5200 0 : bSame = false;
5201 0 : break;
5202 : }
5203 : double vals[2];
5204 23 : nc_get_att_double(cdfid, NCDFVarID, szAttrName, vals);
5205 44 : if (vals[0] != CPLAtof(aosTokens[0]) ||
5206 21 : (attlen == 2 && vals[1] != CPLAtof(aosTokens[1])))
5207 : {
5208 2 : bSame = false;
5209 2 : break;
5210 : }
5211 : }
5212 : }
5213 5 : if (bSame)
5214 : {
5215 3 : *ppszCFProjection = pszCFProjection;
5216 3 : CSLDestroy(papszKeyValues);
5217 3 : return NCDFVarID;
5218 : }
5219 2 : CPLFree(pszCFProjection);
5220 2 : pszCFProjection =
5221 2 : CPLStrdup(CPLSPrintf("%s_%d", varNameRadix.c_str(), nCounter));
5222 2 : nCounter++;
5223 2 : }
5224 :
5225 140 : *ppszCFProjection = pszCFProjection;
5226 :
5227 : const char *pszVarName;
5228 :
5229 140 : if (srsVarName != "")
5230 : {
5231 38 : pszVarName = srsVarName.c_str();
5232 : }
5233 : else
5234 : {
5235 102 : pszVarName = pszCFProjection;
5236 : }
5237 :
5238 140 : int status = nc_def_var(cdfid, pszVarName, NC_CHAR, 0, nullptr, &NCDFVarID);
5239 140 : NCDF_ERR(status);
5240 1394 : for (int i = 0; i < nValues; ++i)
5241 : {
5242 1254 : char *pszKey = nullptr;
5243 1254 : const char *pszValue = CPLParseNameValue(papszKeyValues[i], &pszKey);
5244 1254 : if (pszKey && pszValue)
5245 : {
5246 2508 : const CPLStringList aosTokens(CSLTokenizeString2(pszValue, ",", 0));
5247 1254 : double adfValues[2] = {0, 0};
5248 1254 : const int nDoubleCount = std::min(2, aosTokens.size());
5249 1254 : if (!(aosTokens.size() == 2 &&
5250 2507 : CPLGetValueType(aosTokens[0]) != CPL_VALUE_STRING) &&
5251 1253 : CPLGetValueType(pszValue) == CPL_VALUE_STRING)
5252 : {
5253 559 : status = nc_put_att_text(cdfid, NCDFVarID, pszKey,
5254 : strlen(pszValue), pszValue);
5255 : }
5256 : else
5257 : {
5258 1391 : for (int j = 0; j < nDoubleCount; ++j)
5259 696 : adfValues[j] = CPLAtof(aosTokens[j]);
5260 695 : status = nc_put_att_double(cdfid, NCDFVarID, pszKey, NC_DOUBLE,
5261 : nDoubleCount, adfValues);
5262 : }
5263 1254 : NCDF_ERR(status);
5264 : }
5265 1254 : CPLFree(pszKey);
5266 : }
5267 :
5268 140 : CSLDestroy(papszKeyValues);
5269 140 : return NCDFVarID;
5270 : }
5271 :
5272 : /************************************************************************/
5273 : /* NCDFWriteLonLatVarsAttributes() */
5274 : /************************************************************************/
5275 :
5276 104 : void NCDFWriteLonLatVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarLonID,
5277 : int nVarLatID)
5278 : {
5279 :
5280 : try
5281 : {
5282 104 : vcdf.nc_put_vatt_text(nVarLatID, CF_STD_NAME, CF_LATITUDE_STD_NAME);
5283 104 : vcdf.nc_put_vatt_text(nVarLatID, CF_LNG_NAME, CF_LATITUDE_LNG_NAME);
5284 104 : vcdf.nc_put_vatt_text(nVarLatID, CF_UNITS, CF_DEGREES_NORTH);
5285 104 : vcdf.nc_put_vatt_text(nVarLonID, CF_STD_NAME, CF_LONGITUDE_STD_NAME);
5286 104 : vcdf.nc_put_vatt_text(nVarLonID, CF_LNG_NAME, CF_LONGITUDE_LNG_NAME);
5287 104 : vcdf.nc_put_vatt_text(nVarLonID, CF_UNITS, CF_DEGREES_EAST);
5288 : }
5289 0 : catch (nccfdriver::SG_Exception &e)
5290 : {
5291 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5292 : }
5293 104 : }
5294 :
5295 : /************************************************************************/
5296 : /* NCDFWriteRLonRLatVarsAttributes() */
5297 : /************************************************************************/
5298 :
5299 0 : void NCDFWriteRLonRLatVarsAttributes(nccfdriver::netCDFVID &vcdf,
5300 : int nVarRLonID, int nVarRLatID)
5301 : {
5302 : try
5303 : {
5304 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_STD_NAME, "grid_latitude");
5305 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_LNG_NAME,
5306 : "latitude in rotated pole grid");
5307 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_UNITS, "degrees");
5308 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_AXIS, "Y");
5309 :
5310 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_STD_NAME, "grid_longitude");
5311 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_LNG_NAME,
5312 : "longitude in rotated pole grid");
5313 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_UNITS, "degrees");
5314 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_AXIS, "X");
5315 : }
5316 0 : catch (nccfdriver::SG_Exception &e)
5317 : {
5318 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5319 : }
5320 0 : }
5321 :
5322 : /************************************************************************/
5323 : /* NCDFGetProjectedCFUnit() */
5324 : /************************************************************************/
5325 :
5326 51 : std::string NCDFGetProjectedCFUnit(const OGRSpatialReference *poSRS)
5327 : {
5328 51 : char *pszUnitsToWrite = nullptr;
5329 51 : poSRS->exportToCF1(nullptr, nullptr, &pszUnitsToWrite, nullptr);
5330 51 : std::string osRet = pszUnitsToWrite ? pszUnitsToWrite : std::string();
5331 51 : CPLFree(pszUnitsToWrite);
5332 102 : return osRet;
5333 : }
5334 :
5335 : /************************************************************************/
5336 : /* NCDFWriteXYVarsAttributes() */
5337 : /************************************************************************/
5338 :
5339 36 : void NCDFWriteXYVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarXID,
5340 : int nVarYID, const OGRSpatialReference *poSRS)
5341 : {
5342 72 : const std::string osUnitsToWrite = NCDFGetProjectedCFUnit(poSRS);
5343 :
5344 : try
5345 : {
5346 36 : vcdf.nc_put_vatt_text(nVarXID, CF_STD_NAME, CF_PROJ_X_COORD);
5347 36 : vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME, CF_PROJ_X_COORD_LONG_NAME);
5348 36 : if (!osUnitsToWrite.empty())
5349 36 : vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, osUnitsToWrite.c_str());
5350 36 : vcdf.nc_put_vatt_text(nVarYID, CF_STD_NAME, CF_PROJ_Y_COORD);
5351 36 : vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME, CF_PROJ_Y_COORD_LONG_NAME);
5352 36 : if (!osUnitsToWrite.empty())
5353 36 : vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, osUnitsToWrite.c_str());
5354 : }
5355 0 : catch (nccfdriver::SG_Exception &e)
5356 : {
5357 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5358 : }
5359 36 : }
5360 :
5361 : /************************************************************************/
5362 : /* AddProjectionVars() */
5363 : /************************************************************************/
5364 :
5365 188 : CPLErr netCDFDataset::AddProjectionVars(bool bDefsOnly,
5366 : GDALProgressFunc pfnProgress,
5367 : void *pProgressData)
5368 : {
5369 188 : if (nCFVersionMajor > 1 || (nCFVersionMajor == 1 && nCFVersionMinor >= 8))
5370 0 : return CE_None; // do nothing
5371 :
5372 188 : bool bWriteGridMapping = false;
5373 188 : bool bWriteLonLat = false;
5374 188 : bool bHasGeoloc = false;
5375 188 : bool bWriteGDALTags = false;
5376 188 : bool bWriteGeoTransform = false;
5377 :
5378 : // For GEOLOCATION information.
5379 188 : GDALDatasetUniquePtr poDS_X;
5380 188 : GDALDatasetUniquePtr poDS_Y;
5381 188 : GDALRasterBand *poBand_X = nullptr;
5382 188 : GDALRasterBand *poBand_Y = nullptr;
5383 :
5384 376 : OGRSpatialReference oSRS(m_oSRS);
5385 188 : if (!m_oSRS.IsEmpty())
5386 : {
5387 160 : if (oSRS.IsProjected())
5388 72 : bIsProjected = true;
5389 88 : else if (oSRS.IsGeographic())
5390 88 : bIsGeographic = true;
5391 : }
5392 :
5393 188 : if (bDefsOnly)
5394 : {
5395 188 : const std::string osProjection = m_oSRS.exportToWkt();
5396 174 : CPLDebug("GDAL_netCDF",
5397 : "SetProjection, WKT now = [%s]\nprojected: %d geographic: %d",
5398 80 : osProjection.empty() ? "(null)" : osProjection.c_str(),
5399 94 : static_cast<int>(bIsProjected),
5400 94 : static_cast<int>(bIsGeographic));
5401 :
5402 94 : if (!m_bHasGeoTransform)
5403 5 : CPLDebug("GDAL_netCDF",
5404 : "netCDFDataset::AddProjectionVars() called, "
5405 : "but GeoTransform has not yet been defined!");
5406 :
5407 94 : if (!m_bHasProjection)
5408 6 : CPLDebug("GDAL_netCDF",
5409 : "netCDFDataset::AddProjectionVars() called, "
5410 : "but Projection has not yet been defined!");
5411 : }
5412 :
5413 : // Check GEOLOCATION information.
5414 : CSLConstList papszGeolocationInfo =
5415 188 : netCDFDataset::GetMetadata(GDAL_MDD_GEOLOCATION);
5416 188 : if (papszGeolocationInfo != nullptr)
5417 : {
5418 : // Look for geolocation datasets.
5419 : const char *pszDSName =
5420 12 : CSLFetchNameValue(papszGeolocationInfo, "X_DATASET");
5421 12 : if (pszDSName != nullptr)
5422 12 : poDS_X.reset(GDALDataset::Open(
5423 : pszDSName,
5424 : GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR | GDAL_OF_SHARED));
5425 12 : pszDSName = CSLFetchNameValue(papszGeolocationInfo, "Y_DATASET");
5426 12 : if (pszDSName != nullptr)
5427 12 : poDS_Y.reset(GDALDataset::Open(
5428 : pszDSName,
5429 : GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR | GDAL_OF_SHARED));
5430 :
5431 12 : if (poDS_X != nullptr && poDS_Y != nullptr)
5432 : {
5433 12 : int nBand = std::max(1, atoi(CSLFetchNameValueDef(
5434 12 : papszGeolocationInfo, "X_BAND", "0")));
5435 12 : poBand_X = poDS_X->GetRasterBand(nBand);
5436 12 : nBand = std::max(1, atoi(CSLFetchNameValueDef(papszGeolocationInfo,
5437 12 : "Y_BAND", "0")));
5438 12 : poBand_Y = poDS_Y->GetRasterBand(nBand);
5439 :
5440 : // If geoloc bands are found, do basic validation based on their
5441 : // dimensions.
5442 12 : if (poBand_X != nullptr && poBand_Y != nullptr)
5443 : {
5444 12 : const int nXSize_XBand = poBand_X->GetXSize();
5445 12 : const int nYSize_XBand = poBand_X->GetYSize();
5446 12 : const int nXSize_YBand = poBand_Y->GetXSize();
5447 12 : const int nYSize_YBand = poBand_Y->GetYSize();
5448 :
5449 : // TODO 1D geolocation arrays not implemented.
5450 12 : if (nYSize_XBand == 1 && nYSize_YBand == 1)
5451 : {
5452 2 : bHasGeoloc = false;
5453 2 : CPLDebug("GDAL_netCDF",
5454 : "1D GEOLOCATION arrays not supported yet");
5455 : }
5456 : // 2D bands must have same sizes as the raster bands.
5457 10 : else if (nXSize_XBand != nRasterXSize ||
5458 10 : nYSize_XBand != nRasterYSize ||
5459 10 : nXSize_YBand != nRasterXSize ||
5460 10 : nYSize_YBand != nRasterYSize)
5461 : {
5462 0 : bHasGeoloc = false;
5463 0 : CPLDebug("GDAL_netCDF",
5464 : "GEOLOCATION array sizes (%dx%d %dx%d) differ "
5465 : "from raster (%dx%d), not supported",
5466 : nXSize_XBand, nYSize_XBand, nXSize_YBand,
5467 : nYSize_YBand, nRasterXSize, nRasterYSize);
5468 : }
5469 : else
5470 : {
5471 10 : bHasGeoloc = true;
5472 10 : CPLDebug("GDAL_netCDF",
5473 : "dataset has GEOLOCATION information, will try to "
5474 : "write it");
5475 : }
5476 : }
5477 : }
5478 : }
5479 :
5480 : // Process projection options.
5481 188 : if (bIsProjected)
5482 : {
5483 : bool bIsCfProjection =
5484 72 : oSRS.exportToCF1(nullptr, nullptr, nullptr, nullptr) == OGRERR_NONE;
5485 72 : bWriteGridMapping = true;
5486 72 : bWriteGDALTags = aosCreationOptions.FetchBool("WRITE_GDAL_TAGS", true);
5487 : // Force WRITE_GDAL_TAGS if is not a CF projection.
5488 72 : if (!bWriteGDALTags && !bIsCfProjection)
5489 0 : bWriteGDALTags = true;
5490 72 : if (bWriteGDALTags)
5491 72 : bWriteGeoTransform = true;
5492 :
5493 : // Write lon/lat: default is NO, except if has geolocation.
5494 : // With IF_NEEDED: write if has geoloc or is not CF projection.
5495 : const char *pszValue =
5496 72 : aosCreationOptions.FetchNameValue("WRITE_LONLAT");
5497 72 : if (pszValue)
5498 : {
5499 6 : if (EQUAL(pszValue, "IF_NEEDED"))
5500 : {
5501 0 : bWriteLonLat = bHasGeoloc || !bIsCfProjection;
5502 : }
5503 : else
5504 : {
5505 6 : bWriteLonLat = CPLTestBool(pszValue);
5506 : }
5507 : }
5508 : else
5509 : {
5510 66 : bWriteLonLat = bHasGeoloc;
5511 : }
5512 :
5513 : // Save value of pszCFCoordinates for later.
5514 72 : if (bWriteLonLat)
5515 : {
5516 8 : pszCFCoordinates = NCDF_LONLAT;
5517 : }
5518 : }
5519 : else
5520 : {
5521 : // Files without a Datum will not have a grid_mapping variable and
5522 : // geographic information.
5523 116 : bWriteGridMapping = bIsGeographic;
5524 :
5525 116 : if (bHasGeoloc)
5526 : {
5527 8 : bWriteLonLat = true;
5528 : }
5529 : else
5530 : {
5531 108 : bWriteGDALTags = aosCreationOptions.FetchBool("WRITE_GDAL_TAGS",
5532 : bWriteGridMapping);
5533 108 : if (bWriteGDALTags)
5534 88 : bWriteGeoTransform = true;
5535 :
5536 : const char *pszValue =
5537 108 : aosCreationOptions.FetchNameValueDef("WRITE_LONLAT", "YES");
5538 108 : if (EQUAL(pszValue, "IF_NEEDED"))
5539 0 : bWriteLonLat = true;
5540 : else
5541 108 : bWriteLonLat = CPLTestBool(pszValue);
5542 : // Don't write lon/lat if no source geotransform.
5543 108 : if (!m_bHasGeoTransform)
5544 0 : bWriteLonLat = false;
5545 : // If we don't write lon/lat, set dimnames to X/Y and write gdal
5546 : // tags.
5547 108 : if (!bWriteLonLat)
5548 : {
5549 0 : CPLError(CE_Warning, CPLE_AppDefined,
5550 : "creating geographic file without lon/lat values!");
5551 0 : if (m_bHasGeoTransform)
5552 : {
5553 0 : bWriteGDALTags = true; // Not desirable if no geotransform.
5554 0 : bWriteGeoTransform = true;
5555 : }
5556 : }
5557 : }
5558 : }
5559 :
5560 : // Make sure we write grid_mapping if we need to write GDAL tags.
5561 188 : if (bWriteGDALTags)
5562 160 : bWriteGridMapping = true;
5563 :
5564 : // bottom-up value: new driver is bottom-up by default.
5565 : // Override with WRITE_BOTTOMUP.
5566 188 : bBottomUp = aosCreationOptions.FetchBool("WRITE_BOTTOMUP", true);
5567 :
5568 188 : if (bDefsOnly)
5569 : {
5570 94 : CPLDebug(
5571 : "GDAL_netCDF",
5572 : "bIsProjected=%d bIsGeographic=%d bWriteGridMapping=%d "
5573 : "bWriteGDALTags=%d bWriteLonLat=%d bBottomUp=%d bHasGeoloc=%d",
5574 94 : static_cast<int>(bIsProjected), static_cast<int>(bIsGeographic),
5575 : static_cast<int>(bWriteGridMapping),
5576 : static_cast<int>(bWriteGDALTags), static_cast<int>(bWriteLonLat),
5577 94 : static_cast<int>(bBottomUp), static_cast<int>(bHasGeoloc));
5578 : }
5579 :
5580 : // Exit if nothing to do.
5581 188 : if (!bIsProjected && !bWriteLonLat)
5582 0 : return CE_None;
5583 :
5584 : // Define dimension names.
5585 :
5586 188 : constexpr const char *ROTATED_POLE_VAR_NAME = "rotated_pole";
5587 :
5588 188 : if (bDefsOnly)
5589 : {
5590 94 : int nVarLonID = -1;
5591 94 : int nVarLatID = -1;
5592 94 : int nVarXID = -1;
5593 94 : int nVarYID = -1;
5594 :
5595 94 : m_bAddedProjectionVarsDefs = true;
5596 :
5597 : // Make sure we are in define mode.
5598 94 : SetDefineMode(true);
5599 :
5600 : // Write projection attributes.
5601 94 : if (bWriteGridMapping)
5602 : {
5603 80 : const int NCDFVarID = NCDFWriteSRSVariable(
5604 : cdfid, &oSRS, &pszCFProjection, bWriteGDALTags);
5605 80 : if (NCDFVarID < 0)
5606 0 : return CE_Failure;
5607 :
5608 : // Optional GDAL custom projection tags.
5609 80 : if (bWriteGDALTags && bWriteGeoTransform && m_bHasGeoTransform)
5610 : {
5611 79 : GDALGeoTransform gt(m_gt);
5612 79 : if (!bBottomUp)
5613 : {
5614 : // Change origin from top to bottom and sign of coefficients
5615 : // indexed by row
5616 2 : gt.yorig += nRasterYSize * gt.yscale;
5617 2 : gt.xorig += nRasterYSize * gt.xrot;
5618 2 : gt.xrot = -gt.xrot;
5619 2 : gt.yscale = -gt.yscale;
5620 : }
5621 158 : std::string osGeoTransform = gt.ToString(" ");
5622 79 : CPLDebug("GDAL_netCDF", "szGeoTransform = %s",
5623 : osGeoTransform.c_str());
5624 :
5625 79 : const int status = nc_put_att_text(
5626 : cdfid, NCDFVarID, NCDF_GEOTRANSFORM, osGeoTransform.size(),
5627 : osGeoTransform.c_str());
5628 79 : NCDF_ERR(status);
5629 : }
5630 :
5631 : // Write projection variable to band variable.
5632 : // Need to call later if there are no bands.
5633 80 : AddGridMappingRef();
5634 : } // end if( bWriteGridMapping )
5635 :
5636 : // Write CF Projection vars.
5637 :
5638 94 : const bool bIsRotatedPole =
5639 174 : pszCFProjection != nullptr &&
5640 80 : EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
5641 :
5642 94 : if (m_bHasGeoTransform && !m_gt.IsAxisAligned())
5643 : {
5644 : // Do not write X/Y coordinate arrays
5645 : }
5646 :
5647 90 : else if (bIsRotatedPole)
5648 : {
5649 : // Rename dims to rlat/rlon.
5650 : papszDimName
5651 0 : .Clear(); // If we add other dims one day, this has to change
5652 0 : papszDimName.AddString(NCDF_DIMNAME_RLAT);
5653 0 : papszDimName.AddString(NCDF_DIMNAME_RLON);
5654 :
5655 0 : int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_RLAT);
5656 0 : NCDF_ERR(status);
5657 0 : status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_RLON);
5658 0 : NCDF_ERR(status);
5659 : }
5660 : // Rename dimensions if lon/lat.
5661 90 : else if (!bIsProjected && !bHasGeoloc)
5662 : {
5663 : // Rename dims to lat/lon.
5664 : papszDimName
5665 54 : .Clear(); // If we add other dims one day, this has to change
5666 54 : papszDimName.AddString(NCDF_DIMNAME_LAT);
5667 54 : papszDimName.AddString(NCDF_DIMNAME_LON);
5668 :
5669 54 : int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_LAT);
5670 54 : NCDF_ERR(status);
5671 54 : status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_LON);
5672 54 : NCDF_ERR(status);
5673 : }
5674 :
5675 : // Write X/Y attributes.
5676 : else /* if( bIsProjected || bHasGeoloc ) */
5677 : {
5678 : // X
5679 : int anXDims[1];
5680 36 : anXDims[0] = nXDimID;
5681 36 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
5682 : CF_PROJ_X_VAR_NAME, NC_DOUBLE);
5683 36 : int status = nc_def_var(cdfid, CF_PROJ_X_VAR_NAME, NC_DOUBLE, 1,
5684 : anXDims, &nVarXID);
5685 36 : NCDF_ERR(status);
5686 :
5687 : // Y
5688 : int anYDims[1];
5689 36 : anYDims[0] = nYDimID;
5690 36 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
5691 : CF_PROJ_Y_VAR_NAME, NC_DOUBLE);
5692 36 : status = nc_def_var(cdfid, CF_PROJ_Y_VAR_NAME, NC_DOUBLE, 1,
5693 : anYDims, &nVarYID);
5694 36 : NCDF_ERR(status);
5695 :
5696 36 : if (bIsProjected)
5697 : {
5698 32 : NCDFWriteXYVarsAttributes(this->vcdf, nVarXID, nVarYID, &oSRS);
5699 : }
5700 : else
5701 : {
5702 4 : CPLAssert(bHasGeoloc);
5703 : try
5704 : {
5705 4 : vcdf.nc_put_vatt_text(nVarXID, CF_AXIS, CF_SG_X_AXIS);
5706 4 : vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME,
5707 : "x-coordinate in Cartesian system");
5708 4 : vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, "m");
5709 4 : vcdf.nc_put_vatt_text(nVarYID, CF_AXIS, CF_SG_Y_AXIS);
5710 4 : vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME,
5711 : "y-coordinate in Cartesian system");
5712 4 : vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, "m");
5713 :
5714 4 : pszCFCoordinates = NCDF_LONLAT;
5715 : }
5716 0 : catch (nccfdriver::SG_Exception &e)
5717 : {
5718 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5719 0 : return CE_Failure;
5720 : }
5721 : }
5722 : }
5723 :
5724 : // Write lat/lon attributes if needed.
5725 94 : if (bWriteLonLat)
5726 : {
5727 62 : int anLatDims[2] = {0, 0};
5728 62 : int anLonDims[2] = {0, 0};
5729 62 : int nLatDims = -1;
5730 62 : int nLonDims = -1;
5731 :
5732 : // Get information.
5733 62 : if (bHasGeoloc)
5734 : {
5735 : // Geoloc
5736 5 : nLatDims = 2;
5737 5 : anLatDims[0] = nYDimID;
5738 5 : anLatDims[1] = nXDimID;
5739 5 : nLonDims = 2;
5740 5 : anLonDims[0] = nYDimID;
5741 5 : anLonDims[1] = nXDimID;
5742 : }
5743 57 : else if (bIsProjected)
5744 : {
5745 : // Projected
5746 3 : nLatDims = 2;
5747 3 : anLatDims[0] = nYDimID;
5748 3 : anLatDims[1] = nXDimID;
5749 3 : nLonDims = 2;
5750 3 : anLonDims[0] = nYDimID;
5751 3 : anLonDims[1] = nXDimID;
5752 : }
5753 : else
5754 : {
5755 : // Geographic
5756 54 : nLatDims = 1;
5757 54 : anLatDims[0] = nYDimID;
5758 54 : nLonDims = 1;
5759 54 : anLonDims[0] = nXDimID;
5760 : }
5761 :
5762 62 : nc_type eLonLatType = NC_NAT;
5763 62 : if (bIsProjected)
5764 : {
5765 4 : eLonLatType = NC_FLOAT;
5766 4 : const char *pszValue = aosCreationOptions.FetchNameValueDef(
5767 : "TYPE_LONLAT", "FLOAT");
5768 4 : if (EQUAL(pszValue, "DOUBLE"))
5769 0 : eLonLatType = NC_DOUBLE;
5770 : }
5771 : else
5772 : {
5773 58 : eLonLatType = NC_DOUBLE;
5774 58 : const char *pszValue = aosCreationOptions.FetchNameValueDef(
5775 : "TYPE_LONLAT", "DOUBLE");
5776 58 : if (EQUAL(pszValue, "FLOAT"))
5777 0 : eLonLatType = NC_FLOAT;
5778 : }
5779 :
5780 : // Def vars and attributes.
5781 : {
5782 62 : const char *pszVarName =
5783 62 : bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME;
5784 62 : int status = nc_def_var(cdfid, pszVarName, eLonLatType,
5785 : nLatDims, anLatDims, &nVarLatID);
5786 62 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
5787 : cdfid, pszVarName, eLonLatType, nLatDims, nVarLatID);
5788 62 : NCDF_ERR(status);
5789 62 : DefVarDeflate(nVarLatID, false); // Don't set chunking.
5790 : }
5791 :
5792 : {
5793 62 : const char *pszVarName =
5794 62 : bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME;
5795 62 : int status = nc_def_var(cdfid, pszVarName, eLonLatType,
5796 : nLonDims, anLonDims, &nVarLonID);
5797 62 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
5798 : cdfid, pszVarName, eLonLatType, nLatDims, nVarLonID);
5799 62 : NCDF_ERR(status);
5800 62 : DefVarDeflate(nVarLonID, false); // Don't set chunking.
5801 : }
5802 :
5803 62 : if (bIsRotatedPole)
5804 0 : NCDFWriteRLonRLatVarsAttributes(this->vcdf, nVarLonID,
5805 : nVarLatID);
5806 : else
5807 62 : NCDFWriteLonLatVarsAttributes(this->vcdf, nVarLonID, nVarLatID);
5808 : }
5809 : }
5810 :
5811 188 : if (!bDefsOnly)
5812 : {
5813 94 : m_bAddedProjectionVarsData = true;
5814 :
5815 94 : int nVarXID = -1;
5816 94 : int nVarYID = -1;
5817 :
5818 94 : nc_inq_varid(cdfid, CF_PROJ_X_VAR_NAME, &nVarXID);
5819 94 : nc_inq_varid(cdfid, CF_PROJ_Y_VAR_NAME, &nVarYID);
5820 :
5821 94 : int nVarLonID = -1;
5822 94 : int nVarLatID = -1;
5823 :
5824 94 : const bool bIsRotatedPole =
5825 174 : pszCFProjection != nullptr &&
5826 80 : EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
5827 94 : nc_inq_varid(cdfid,
5828 : bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME,
5829 : &nVarLonID);
5830 94 : nc_inq_varid(cdfid,
5831 : bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME,
5832 : &nVarLatID);
5833 :
5834 : // Get projection values.
5835 :
5836 94 : if (bIsProjected)
5837 : {
5838 0 : std::unique_ptr<OGRSpatialReference> poLatLonSRS;
5839 0 : std::unique_ptr<OGRCoordinateTransformation> poTransform;
5840 :
5841 : size_t startX[1];
5842 : size_t countX[1];
5843 : size_t startY[1];
5844 : size_t countY[1];
5845 :
5846 36 : CPLDebug("GDAL_netCDF", "Getting (X,Y) values");
5847 :
5848 : std::unique_ptr<double, decltype(&VSIFree)> adXValKeeper(
5849 : static_cast<double *>(
5850 72 : VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
5851 36 : VSIFree);
5852 : std::unique_ptr<double, decltype(&VSIFree)> adYValKeeper(
5853 : static_cast<double *>(
5854 72 : VSI_MALLOC2_VERBOSE(nRasterYSize, sizeof(double))),
5855 36 : VSIFree);
5856 36 : double *padXVal = adXValKeeper.get();
5857 36 : double *padYVal = adYValKeeper.get();
5858 36 : if (!padXVal || !padYVal)
5859 : {
5860 0 : return CE_Failure;
5861 : }
5862 :
5863 : // Make sure we are in data mode.
5864 36 : SetDefineMode(false);
5865 :
5866 36 : int status = NC_NOERR;
5867 :
5868 36 : if (m_gt.IsAxisAligned())
5869 : {
5870 : // Get Y values.
5871 32 : const double dfY0 =
5872 32 : (!bBottomUp) ? m_gt.yorig :
5873 : // Invert latitude values.
5874 32 : m_gt.yorig + (m_gt.yscale * nRasterYSize);
5875 32 : const double dfDY = m_gt.yscale;
5876 :
5877 1583 : for (int j = 0; j < nRasterYSize; j++)
5878 : {
5879 : // The data point is centered inside the pixel.
5880 1551 : if (!bBottomUp)
5881 0 : padYVal[j] = dfY0 + (j + 0.5) * dfDY;
5882 : else // Invert latitude values.
5883 1551 : padYVal[j] = dfY0 - (j + 0.5) * dfDY;
5884 : }
5885 32 : startX[0] = 0;
5886 32 : countX[0] = nRasterXSize;
5887 :
5888 : // Get X values.
5889 32 : const double dfX0 = m_gt.xorig;
5890 32 : const double dfDX = m_gt.xscale;
5891 :
5892 1624 : for (int i = 0; i < nRasterXSize; i++)
5893 : {
5894 : // The data point is centered inside the pixel.
5895 1592 : padXVal[i] = dfX0 + (i + 0.5) * dfDX;
5896 : }
5897 32 : startY[0] = 0;
5898 32 : countY[0] = nRasterYSize;
5899 :
5900 : // Write X/Y values.
5901 :
5902 32 : CPLDebug("GDAL_netCDF", "Writing X values");
5903 : status =
5904 32 : nc_put_vara_double(cdfid, nVarXID, startX, countX, padXVal);
5905 32 : NCDF_ERR(status);
5906 :
5907 32 : CPLDebug("GDAL_netCDF", "Writing Y values");
5908 : status =
5909 32 : nc_put_vara_double(cdfid, nVarYID, startY, countY, padYVal);
5910 32 : NCDF_ERR(status);
5911 : }
5912 :
5913 36 : if (pfnProgress)
5914 32 : pfnProgress(0.20, nullptr, pProgressData);
5915 :
5916 : // Write lon/lat arrays (CF coordinates) if requested.
5917 :
5918 : // Get OGR transform if GEOLOCATION is not available.
5919 36 : if (bWriteLonLat && !bHasGeoloc)
5920 : {
5921 3 : poLatLonSRS.reset(m_oSRS.CloneGeogCS());
5922 3 : if (poLatLonSRS != nullptr)
5923 : {
5924 3 : poLatLonSRS->SetAxisMappingStrategy(
5925 : OAMS_TRADITIONAL_GIS_ORDER);
5926 3 : poTransform.reset(OGRCreateCoordinateTransformation(
5927 3 : &m_oSRS, poLatLonSRS.get()));
5928 : }
5929 : // If no OGR transform, then don't write CF lon/lat.
5930 3 : if (poTransform == nullptr)
5931 : {
5932 0 : CPLError(CE_Failure, CPLE_AppDefined,
5933 : "Unable to get Coordinate Transform");
5934 0 : bWriteLonLat = false;
5935 : }
5936 : }
5937 :
5938 36 : if (bWriteLonLat)
5939 : {
5940 4 : if (!bHasGeoloc)
5941 3 : CPLDebug("GDAL_netCDF", "Transforming (X,Y)->(lon,lat)");
5942 : else
5943 1 : CPLDebug("GDAL_netCDF",
5944 : "Writing (lon,lat) from GEOLOCATION arrays");
5945 :
5946 4 : bool bOK = true;
5947 4 : double dfProgress = 0.2;
5948 :
5949 4 : size_t start[] = {0, 0};
5950 4 : size_t count[] = {1, (size_t)nRasterXSize};
5951 : std::unique_ptr<double, decltype(&VSIFree)> adLatValKeeper(
5952 : static_cast<double *>(
5953 8 : VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
5954 4 : VSIFree);
5955 : std::unique_ptr<double, decltype(&VSIFree)> adLonValKeeper(
5956 : static_cast<double *>(
5957 8 : VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
5958 4 : VSIFree);
5959 4 : double *padLonVal = adLonValKeeper.get();
5960 4 : double *padLatVal = adLatValKeeper.get();
5961 4 : if (!padLonVal || !padLatVal)
5962 : {
5963 0 : return CE_Failure;
5964 : }
5965 :
5966 103 : for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR;
5967 : j++)
5968 : {
5969 99 : start[0] = j;
5970 :
5971 : // Get values from geotransform.
5972 99 : if (!bHasGeoloc)
5973 : {
5974 : // Fill values to transform.
5975 60 : if (m_gt.IsAxisAligned())
5976 : {
5977 420 : for (int i = 0; i < nRasterXSize; i++)
5978 : {
5979 400 : padLatVal[i] = padYVal[j];
5980 400 : padLonVal[i] = padXVal[i];
5981 : }
5982 : }
5983 : else
5984 : {
5985 840 : for (int i = 0; i < nRasterXSize; i++)
5986 : {
5987 800 : if (!bBottomUp)
5988 : {
5989 400 : padLatVal[i] = m_gt.yorig +
5990 400 : (i + 0.5) * m_gt.yrot +
5991 400 : (j + 0.5) * m_gt.yscale;
5992 400 : padLonVal[i] = m_gt.xorig +
5993 400 : (i + 0.5) * m_gt.xscale +
5994 400 : (j + 0.5) * m_gt.xrot;
5995 : }
5996 : else
5997 : {
5998 400 : padLatVal[i] =
5999 400 : m_gt.yorig + (i + 0.5) * m_gt.yrot +
6000 400 : (nRasterYSize - j - 0.5) * m_gt.yscale;
6001 400 : padLonVal[i] =
6002 400 : m_gt.xorig + (i + 0.5) * m_gt.xscale +
6003 400 : (nRasterYSize - j - 0.5) * m_gt.xrot;
6004 : }
6005 : }
6006 : }
6007 :
6008 : // Do the transform.
6009 120 : bOK = CPL_TO_BOOL(poTransform->Transform(
6010 60 : nRasterXSize, padLonVal, padLatVal, nullptr));
6011 60 : if (!bOK)
6012 : {
6013 0 : CPLError(CE_Failure, CPLE_AppDefined,
6014 : "Unable to Transform (X,Y) to (lon,lat).");
6015 : }
6016 : }
6017 : // Get values from geoloc arrays.
6018 : else
6019 : {
6020 39 : CPLErr eErr = poBand_Y->RasterIO(
6021 : GF_Read, 0, j, nRasterXSize, 1, padLatVal,
6022 : nRasterXSize, 1, GDT_Float64, 0, 0, nullptr);
6023 39 : if (eErr == CE_None)
6024 : {
6025 39 : eErr = poBand_X->RasterIO(
6026 : GF_Read, 0, j, nRasterXSize, 1, padLonVal,
6027 : nRasterXSize, 1, GDT_Float64, 0, 0, nullptr);
6028 : }
6029 :
6030 39 : if (eErr == CE_None)
6031 : {
6032 39 : bOK = true;
6033 : }
6034 : else
6035 : {
6036 0 : bOK = false;
6037 0 : CPLError(CE_Failure, CPLE_AppDefined,
6038 : "Unable to get scanline %d", j);
6039 : }
6040 : }
6041 :
6042 : // Write data.
6043 99 : if (bOK)
6044 : {
6045 99 : status = nc_put_vara_double(cdfid, nVarLatID, start,
6046 : count, padLatVal);
6047 99 : NCDF_ERR(status);
6048 99 : status = nc_put_vara_double(cdfid, nVarLonID, start,
6049 : count, padLonVal);
6050 99 : NCDF_ERR(status);
6051 : }
6052 :
6053 99 : if (pfnProgress && (nRasterYSize / 10) > 0 &&
6054 99 : (j % (nRasterYSize / 10) == 0))
6055 : {
6056 43 : dfProgress += 0.08;
6057 43 : pfnProgress(dfProgress, nullptr, pProgressData);
6058 : }
6059 : }
6060 : }
6061 : } // Projected
6062 :
6063 : // If not projected/geographic and has geoloc
6064 58 : else if (!bIsGeographic && bHasGeoloc && m_gt.IsAxisAligned())
6065 : {
6066 : // Use
6067 : // https://cfconventions.org/Data/cf-conventions/cf-conventions-1.9/cf-conventions.html#_two_dimensional_latitude_longitude_coordinate_variables
6068 :
6069 4 : bool bOK = true;
6070 4 : double dfProgress = 0.2;
6071 :
6072 : // Make sure we are in data mode.
6073 4 : SetDefineMode(false);
6074 :
6075 : size_t startX[1];
6076 : size_t countX[1];
6077 : size_t startY[1];
6078 : size_t countY[1];
6079 4 : startX[0] = 0;
6080 4 : countX[0] = nRasterXSize;
6081 :
6082 4 : startY[0] = 0;
6083 4 : countY[0] = nRasterYSize;
6084 :
6085 4 : std::vector<double> adfXVal;
6086 4 : std::vector<double> adfYVal;
6087 : try
6088 : {
6089 4 : adfXVal.resize(nRasterXSize);
6090 4 : adfYVal.resize(nRasterYSize);
6091 : }
6092 0 : catch (const std::exception &)
6093 : {
6094 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
6095 : "Out of memory allocating temporary array");
6096 0 : return CE_Failure;
6097 : }
6098 16 : for (int i = 0; i < nRasterXSize; i++)
6099 12 : adfXVal[i] = i;
6100 12 : for (int i = 0; i < nRasterYSize; i++)
6101 8 : adfYVal[i] = bBottomUp ? nRasterYSize - 1 - i : i;
6102 :
6103 4 : CPLDebug("GDAL_netCDF", "Writing X values");
6104 4 : int status = nc_put_vara_double(cdfid, nVarXID, startX, countX,
6105 4 : adfXVal.data());
6106 4 : NCDF_ERR(status);
6107 :
6108 4 : CPLDebug("GDAL_netCDF", "Writing Y values");
6109 4 : status = nc_put_vara_double(cdfid, nVarYID, startY, countY,
6110 4 : adfYVal.data());
6111 4 : NCDF_ERR(status);
6112 :
6113 4 : if (pfnProgress)
6114 0 : pfnProgress(0.20, nullptr, pProgressData);
6115 :
6116 4 : size_t start[] = {0, 0};
6117 4 : size_t count[] = {1, (size_t)nRasterXSize};
6118 :
6119 : std::unique_ptr<double, decltype(&VSIFree)> adLatValKeeper(
6120 : static_cast<double *>(
6121 8 : VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
6122 4 : VSIFree);
6123 : std::unique_ptr<double, decltype(&VSIFree)> adLonValKeeper(
6124 : static_cast<double *>(
6125 8 : VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
6126 4 : VSIFree);
6127 4 : double *padLonVal = adLonValKeeper.get();
6128 4 : double *padLatVal = adLatValKeeper.get();
6129 4 : if (!padLonVal || !padLatVal)
6130 : {
6131 0 : return CE_Failure;
6132 : }
6133 :
6134 12 : for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR; j++)
6135 : {
6136 8 : start[0] = j;
6137 :
6138 8 : CPLErr eErr = poBand_Y->RasterIO(
6139 8 : GF_Read, 0, bBottomUp ? nRasterYSize - 1 - j : j,
6140 : nRasterXSize, 1, padLatVal, nRasterXSize, 1, GDT_Float64, 0,
6141 : 0, nullptr);
6142 8 : if (eErr == CE_None)
6143 : {
6144 8 : eErr = poBand_X->RasterIO(
6145 8 : GF_Read, 0, bBottomUp ? nRasterYSize - 1 - j : j,
6146 : nRasterXSize, 1, padLonVal, nRasterXSize, 1,
6147 : GDT_Float64, 0, 0, nullptr);
6148 : }
6149 :
6150 8 : if (eErr == CE_None)
6151 : {
6152 8 : bOK = true;
6153 : }
6154 : else
6155 : {
6156 0 : bOK = false;
6157 0 : CPLError(CE_Failure, CPLE_AppDefined,
6158 : "Unable to get scanline %d", j);
6159 : }
6160 :
6161 : // Write data.
6162 8 : if (bOK)
6163 : {
6164 8 : status = nc_put_vara_double(cdfid, nVarLatID, start, count,
6165 : padLatVal);
6166 8 : NCDF_ERR(status);
6167 8 : status = nc_put_vara_double(cdfid, nVarLonID, start, count,
6168 : padLonVal);
6169 8 : NCDF_ERR(status);
6170 : }
6171 :
6172 8 : if (pfnProgress && (nRasterYSize / 10) > 0 &&
6173 0 : (j % (nRasterYSize / 10) == 0))
6174 : {
6175 0 : dfProgress += 0.08;
6176 0 : pfnProgress(dfProgress, nullptr, pProgressData);
6177 : }
6178 : }
6179 : }
6180 :
6181 : // If not projected, assume geographic to catch grids without Datum.
6182 54 : else if (bWriteLonLat)
6183 : {
6184 : // Get latitude values.
6185 54 : const double dfY0 = (!bBottomUp) ? m_gt.yorig :
6186 : // Invert latitude values.
6187 54 : m_gt.yorig + (m_gt.yscale * nRasterYSize);
6188 54 : const double dfDY = m_gt.yscale;
6189 :
6190 : std::unique_ptr<double, decltype(&VSIFree)> adLatValKeeper(nullptr,
6191 54 : VSIFree);
6192 54 : double *padLatVal = nullptr;
6193 : // Override lat values with the ones in GEOLOCATION/Y_VALUES.
6194 54 : if (netCDFDataset::GetMetadataItem("Y_VALUES",
6195 54 : GDAL_MDD_GEOLOCATION) != nullptr)
6196 : {
6197 0 : int nTemp = 0;
6198 0 : adLatValKeeper.reset(Get1DGeolocation("Y_VALUES", nTemp));
6199 0 : padLatVal = adLatValKeeper.get();
6200 : // Make sure we got the correct amount, if not fallback to GT */
6201 : // could add test fabs(fabs(padLatVal[0]) - fabs(dfY0)) <= 0.1))
6202 0 : if (nTemp == nRasterYSize)
6203 : {
6204 0 : CPLDebug(
6205 : "GDAL_netCDF",
6206 : "Using Y_VALUES geolocation metadata for lat values");
6207 : }
6208 : else
6209 : {
6210 0 : CPLDebug("GDAL_netCDF",
6211 : "Got %d elements from Y_VALUES geolocation "
6212 : "metadata, need %d",
6213 : nTemp, nRasterYSize);
6214 0 : padLatVal = nullptr;
6215 : }
6216 : }
6217 :
6218 54 : if (padLatVal == nullptr)
6219 : {
6220 54 : adLatValKeeper.reset(static_cast<double *>(
6221 54 : VSI_MALLOC2_VERBOSE(nRasterYSize, sizeof(double))));
6222 54 : padLatVal = adLatValKeeper.get();
6223 54 : if (!padLatVal)
6224 : {
6225 0 : return CE_Failure;
6226 : }
6227 7126 : for (int i = 0; i < nRasterYSize; i++)
6228 : {
6229 : // The data point is centered inside the pixel.
6230 7072 : if (!bBottomUp)
6231 0 : padLatVal[i] = dfY0 + (i + 0.5) * dfDY;
6232 : else // Invert latitude values.
6233 7072 : padLatVal[i] = dfY0 - (i + 0.5) * dfDY;
6234 : }
6235 : }
6236 :
6237 54 : size_t startLat[1] = {0};
6238 54 : size_t countLat[1] = {static_cast<size_t>(nRasterYSize)};
6239 :
6240 : // Get longitude values.
6241 54 : const double dfX0 = m_gt.xorig;
6242 54 : const double dfDX = m_gt.xscale;
6243 :
6244 : std::unique_ptr<double, decltype(&VSIFree)> adLonValKeeper(
6245 : static_cast<double *>(
6246 108 : VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
6247 54 : VSIFree);
6248 54 : double *padLonVal = adLonValKeeper.get();
6249 54 : if (!padLonVal)
6250 : {
6251 0 : return CE_Failure;
6252 : }
6253 7178 : for (int i = 0; i < nRasterXSize; i++)
6254 : {
6255 : // The data point is centered inside the pixel.
6256 7124 : padLonVal[i] = dfX0 + (i + 0.5) * dfDX;
6257 : }
6258 :
6259 54 : size_t startLon[1] = {0};
6260 54 : size_t countLon[1] = {static_cast<size_t>(nRasterXSize)};
6261 :
6262 : // Write latitude and longitude values.
6263 :
6264 : // Make sure we are in data mode.
6265 54 : SetDefineMode(false);
6266 :
6267 : // Write values.
6268 54 : CPLDebug("GDAL_netCDF", "Writing lat values");
6269 :
6270 54 : int status = nc_put_vara_double(cdfid, nVarLatID, startLat,
6271 : countLat, padLatVal);
6272 54 : NCDF_ERR(status);
6273 :
6274 54 : CPLDebug("GDAL_netCDF", "Writing lon values");
6275 54 : status = nc_put_vara_double(cdfid, nVarLonID, startLon, countLon,
6276 : padLonVal);
6277 54 : NCDF_ERR(status);
6278 :
6279 : } // Not projected.
6280 :
6281 94 : if (pfnProgress)
6282 53 : pfnProgress(1.00, nullptr, pProgressData);
6283 : }
6284 :
6285 188 : return CE_None;
6286 : }
6287 :
6288 : // Write Projection variable to band variable.
6289 : // Moved from AddProjectionVars() for cases when bands are added after
6290 : // projection.
6291 490 : bool netCDFDataset::AddGridMappingRef()
6292 : {
6293 490 : bool bRet = true;
6294 490 : bool bOldDefineMode = bDefineMode;
6295 :
6296 706 : if ((GetAccess() == GA_Update) && (nBands >= 1) && (GetRasterBand(1)) &&
6297 216 : ((pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, "")) ||
6298 208 : (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))))
6299 : {
6300 84 : bAddedGridMappingRef = true;
6301 :
6302 : // Make sure we are in define mode.
6303 84 : SetDefineMode(true);
6304 :
6305 214 : for (int i = 1; i <= nBands; i++)
6306 : {
6307 : const int nVarId =
6308 130 : cpl::down_cast<netCDFRasterBand *>(GetRasterBand(i))->nZId;
6309 :
6310 130 : if (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))
6311 : {
6312 : int status =
6313 252 : nc_put_att_text(cdfid, nVarId, CF_GRD_MAPPING,
6314 126 : strlen(pszCFProjection), pszCFProjection);
6315 126 : NCDF_ERR(status);
6316 126 : if (status != NC_NOERR)
6317 0 : bRet = false;
6318 : }
6319 130 : if (pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, ""))
6320 : {
6321 : int status =
6322 8 : nc_put_att_text(cdfid, nVarId, CF_COORDINATES,
6323 : strlen(pszCFCoordinates), pszCFCoordinates);
6324 8 : NCDF_ERR(status);
6325 8 : if (status != NC_NOERR)
6326 0 : bRet = false;
6327 : }
6328 : }
6329 :
6330 : // Go back to previous define mode.
6331 84 : SetDefineMode(bOldDefineMode);
6332 : }
6333 490 : return bRet;
6334 : }
6335 :
6336 : /************************************************************************/
6337 : /* GetGeoTransform() */
6338 : /************************************************************************/
6339 :
6340 136 : CPLErr netCDFDataset::GetGeoTransform(GDALGeoTransform >) const
6341 :
6342 : {
6343 136 : gt = m_gt;
6344 136 : if (m_bHasGeoTransform)
6345 104 : return CE_None;
6346 :
6347 32 : return GDALPamDataset::GetGeoTransform(gt);
6348 : }
6349 :
6350 : /************************************************************************/
6351 : /* rint() */
6352 : /************************************************************************/
6353 :
6354 0 : double netCDFDataset::rint(double dfX)
6355 : {
6356 0 : return std::round(dfX);
6357 : }
6358 :
6359 : /************************************************************************/
6360 : /* NCDFReadIsoMetadata() */
6361 : /************************************************************************/
6362 :
6363 16 : static void NCDFReadMetadataAsJson(int cdfid, CPLJSONObject &obj)
6364 : {
6365 16 : int nbAttr = 0;
6366 16 : NCDF_ERR(nc_inq_varnatts(cdfid, NC_GLOBAL, &nbAttr));
6367 :
6368 32 : std::map<std::string, CPLJSONArray> oMapNameToArray;
6369 40 : for (int l = 0; l < nbAttr; l++)
6370 : {
6371 : char szAttrName[NC_MAX_NAME + 1];
6372 24 : szAttrName[0] = 0;
6373 24 : NCDF_ERR(nc_inq_attname(cdfid, NC_GLOBAL, l, szAttrName));
6374 :
6375 24 : char *pszMetaValue = nullptr;
6376 24 : if (NCDFGetAttr(cdfid, NC_GLOBAL, szAttrName, &pszMetaValue) == CE_None)
6377 : {
6378 24 : nc_type nAttrType = NC_NAT;
6379 24 : size_t nAttrLen = 0;
6380 :
6381 24 : NCDF_ERR(nc_inq_att(cdfid, NC_GLOBAL, szAttrName, &nAttrType,
6382 : &nAttrLen));
6383 :
6384 24 : std::string osAttrName(szAttrName);
6385 24 : const auto sharpPos = osAttrName.find('#');
6386 24 : if (sharpPos == std::string::npos)
6387 : {
6388 16 : if (nAttrType == NC_DOUBLE || nAttrType == NC_FLOAT)
6389 4 : obj.Add(osAttrName, CPLAtof(pszMetaValue));
6390 : else
6391 12 : obj.Add(osAttrName, pszMetaValue);
6392 : }
6393 : else
6394 : {
6395 8 : osAttrName.resize(sharpPos);
6396 8 : auto iter = oMapNameToArray.find(osAttrName);
6397 8 : if (iter == oMapNameToArray.end())
6398 : {
6399 8 : CPLJSONArray array;
6400 4 : obj.Add(osAttrName, array);
6401 4 : oMapNameToArray[osAttrName] = array;
6402 4 : array.Add(pszMetaValue);
6403 : }
6404 : else
6405 : {
6406 4 : iter->second.Add(pszMetaValue);
6407 : }
6408 : }
6409 24 : CPLFree(pszMetaValue);
6410 24 : pszMetaValue = nullptr;
6411 : }
6412 : }
6413 :
6414 16 : int nSubGroups = 0;
6415 16 : int *panSubGroupIds = nullptr;
6416 16 : NCDFGetSubGroups(cdfid, &nSubGroups, &panSubGroupIds);
6417 16 : oMapNameToArray.clear();
6418 28 : for (int i = 0; i < nSubGroups; i++)
6419 : {
6420 24 : CPLJSONObject subObj;
6421 12 : NCDFReadMetadataAsJson(panSubGroupIds[i], subObj);
6422 :
6423 24 : std::string osGroupName;
6424 12 : osGroupName.resize(NC_MAX_NAME);
6425 12 : NCDF_ERR(nc_inq_grpname(panSubGroupIds[i], &osGroupName[0]));
6426 12 : osGroupName.resize(strlen(osGroupName.data()));
6427 12 : const auto sharpPos = osGroupName.find('#');
6428 12 : if (sharpPos == std::string::npos)
6429 : {
6430 4 : obj.Add(osGroupName, subObj);
6431 : }
6432 : else
6433 : {
6434 8 : osGroupName.resize(sharpPos);
6435 8 : auto iter = oMapNameToArray.find(osGroupName);
6436 8 : if (iter == oMapNameToArray.end())
6437 : {
6438 8 : CPLJSONArray array;
6439 4 : obj.Add(osGroupName, array);
6440 4 : oMapNameToArray[osGroupName] = array;
6441 4 : array.Add(subObj);
6442 : }
6443 : else
6444 : {
6445 4 : iter->second.Add(subObj);
6446 : }
6447 : }
6448 : }
6449 16 : CPLFree(panSubGroupIds);
6450 16 : }
6451 :
6452 4 : std::string NCDFReadMetadataAsJson(int cdfid)
6453 : {
6454 8 : CPLJSONDocument oDoc;
6455 8 : CPLJSONObject oRoot = oDoc.GetRoot();
6456 4 : NCDFReadMetadataAsJson(cdfid, oRoot);
6457 8 : return oDoc.SaveAsString();
6458 : }
6459 :
6460 : /************************************************************************/
6461 : /* ReadAttributes() */
6462 : /************************************************************************/
6463 2084 : CPLErr netCDFDataset::ReadAttributes(int cdfidIn, int var)
6464 :
6465 : {
6466 4168 : std::string osVarFullName;
6467 2084 : ERR_RET(NCDFGetVarFullName(cdfidIn, var, osVarFullName));
6468 :
6469 : // For metadata in Sentinel 5
6470 2084 : if (cpl::starts_with(osVarFullName, "/METADATA/"))
6471 : {
6472 6 : for (const char *key :
6473 : {"ISO_METADATA", "ESA_METADATA", "EOP_METADATA", "QA_STATISTICS",
6474 8 : "GRANULE_DESCRIPTION", "ALGORITHM_SETTINGS"})
6475 : {
6476 14 : if (var == NC_GLOBAL &&
6477 14 : osVarFullName == CPLOPrintf("/METADATA/%s/NC_GLOBAL", key))
6478 : {
6479 1 : CPLStringList aosList;
6480 2 : aosList.AddString(CPLString(NCDFReadMetadataAsJson(cdfidIn))
6481 1 : .replaceAll("\\/", '/'));
6482 1 : m_oMapDomainToJSon[key] = std::move(aosList);
6483 1 : return CE_None;
6484 : }
6485 : }
6486 : }
6487 2083 : if (cpl::starts_with(osVarFullName, "/PRODUCT/SUPPORT_DATA/"))
6488 : {
6489 0 : CPLStringList aosList;
6490 : aosList.AddString(
6491 0 : CPLString(NCDFReadMetadataAsJson(cdfidIn)).replaceAll("\\/", '/'));
6492 0 : m_oMapDomainToJSon["SUPPORT_DATA"] = std::move(aosList);
6493 0 : return CE_None;
6494 : }
6495 :
6496 : size_t nMetaNameSize =
6497 2083 : sizeof(char) * (osVarFullName.size() + 1 + NC_MAX_NAME + 1);
6498 2083 : char *pszMetaName = static_cast<char *>(CPLMalloc(nMetaNameSize));
6499 :
6500 2083 : int nbAttr = 0;
6501 2083 : NCDF_ERR(nc_inq_varnatts(cdfidIn, var, &nbAttr));
6502 :
6503 10862 : for (int l = 0; l < nbAttr; l++)
6504 : {
6505 : char szAttrName[NC_MAX_NAME + 1];
6506 8779 : szAttrName[0] = 0;
6507 8779 : NCDF_ERR(nc_inq_attname(cdfidIn, var, l, szAttrName));
6508 8779 : snprintf(pszMetaName, nMetaNameSize, "%s#%s", osVarFullName.c_str(),
6509 : szAttrName);
6510 :
6511 8779 : char *pszMetaTemp = nullptr;
6512 8779 : if (NCDFGetAttr(cdfidIn, var, szAttrName, &pszMetaTemp) == CE_None)
6513 : {
6514 8778 : aosMetadata.SetNameValue(pszMetaName, pszMetaTemp);
6515 8778 : CPLFree(pszMetaTemp);
6516 8778 : pszMetaTemp = nullptr;
6517 : }
6518 : else
6519 : {
6520 1 : CPLDebug("GDAL_netCDF", "invalid metadata %s", pszMetaName);
6521 : }
6522 : }
6523 :
6524 2083 : CPLFree(pszMetaName);
6525 :
6526 2083 : if (var == NC_GLOBAL)
6527 : {
6528 : // Recurse on sub-groups.
6529 586 : int nSubGroups = 0;
6530 586 : int *panSubGroupIds = nullptr;
6531 586 : NCDFGetSubGroups(cdfidIn, &nSubGroups, &panSubGroupIds);
6532 620 : for (int i = 0; i < nSubGroups; i++)
6533 : {
6534 34 : ReadAttributes(panSubGroupIds[i], var);
6535 : }
6536 586 : CPLFree(panSubGroupIds);
6537 : }
6538 :
6539 2083 : return CE_None;
6540 : }
6541 :
6542 : /************************************************************************/
6543 : /* netCDFDataset::CreateSubDatasetList() */
6544 : /************************************************************************/
6545 61 : void netCDFDataset::CreateSubDatasetList(int nGroupId)
6546 : {
6547 122 : std::string osVarStdName;
6548 61 : int *ponDimIds = nullptr;
6549 :
6550 61 : netCDFDataset *poDS = this;
6551 :
6552 : int nVarCount;
6553 61 : nc_inq_nvars(nGroupId, &nVarCount);
6554 :
6555 61 : const bool bListAllArrays = CPLTestBool(
6556 61 : CSLFetchNameValueDef(papszOpenOptions, "LIST_ALL_ARRAYS", "NO"));
6557 :
6558 366 : for (int nVar = 0; nVar < nVarCount; nVar++)
6559 : {
6560 :
6561 : int nDims;
6562 305 : nc_inq_varndims(nGroupId, nVar, &nDims);
6563 :
6564 305 : if ((bListAllArrays && nDims > 0) || nDims >= 2)
6565 : {
6566 177 : ponDimIds = static_cast<int *>(CPLCalloc(nDims, sizeof(int)));
6567 177 : nc_inq_vardimid(nGroupId, nVar, ponDimIds);
6568 :
6569 : // Create Sub dataset list.
6570 177 : CPLString osDim;
6571 545 : for (int i = 0; i < nDims; i++)
6572 : {
6573 : size_t nDimLen;
6574 368 : nc_inq_dimlen(nGroupId, ponDimIds[i], &nDimLen);
6575 368 : if (!osDim.empty())
6576 191 : osDim += 'x';
6577 368 : osDim += CPLSPrintf("%d", (int)nDimLen);
6578 : }
6579 177 : CPLFree(ponDimIds);
6580 :
6581 : nc_type nVarType;
6582 177 : nc_inq_vartype(nGroupId, nVar, &nVarType);
6583 177 : const char *pszType = "";
6584 177 : switch (nVarType)
6585 : {
6586 42 : case NC_BYTE:
6587 42 : pszType = "8-bit integer";
6588 42 : break;
6589 2 : case NC_CHAR:
6590 2 : pszType = "8-bit character";
6591 2 : break;
6592 6 : case NC_SHORT:
6593 6 : pszType = "16-bit integer";
6594 6 : break;
6595 10 : case NC_INT:
6596 10 : pszType = "32-bit integer";
6597 10 : break;
6598 62 : case NC_FLOAT:
6599 62 : pszType = "32-bit floating-point";
6600 62 : break;
6601 34 : case NC_DOUBLE:
6602 34 : pszType = "64-bit floating-point";
6603 34 : break;
6604 4 : case NC_UBYTE:
6605 4 : pszType = "8-bit unsigned integer";
6606 4 : break;
6607 1 : case NC_USHORT:
6608 1 : pszType = "16-bit unsigned integer";
6609 1 : break;
6610 1 : case NC_UINT:
6611 1 : pszType = "32-bit unsigned integer";
6612 1 : break;
6613 1 : case NC_INT64:
6614 1 : pszType = "64-bit integer";
6615 1 : break;
6616 1 : case NC_UINT64:
6617 1 : pszType = "64-bit unsigned integer";
6618 1 : break;
6619 13 : default:
6620 13 : break;
6621 : }
6622 :
6623 177 : std::string osVarName;
6624 177 : if (NCDFGetVarFullName(nGroupId, nVar, osVarName) != CE_None)
6625 0 : continue;
6626 :
6627 177 : nSubDatasets++;
6628 :
6629 177 : if (NCDFGetAttr(nGroupId, nVar, CF_STD_NAME, osVarStdName) !=
6630 : CE_None)
6631 : {
6632 113 : osVarStdName = osVarName;
6633 : }
6634 :
6635 : const std::string osSubDatasetName =
6636 354 : CPLOPrintf("SUBDATASET_%d_NAME", nSubDatasets);
6637 :
6638 354 : if (osVarName.find(' ') != std::string::npos ||
6639 177 : osVarName.find(':') != std::string::npos)
6640 : {
6641 : poDS->aosSubDatasets.SetNameValue(
6642 : osSubDatasetName.c_str(),
6643 : CPLSPrintf("NETCDF:\"%s\":\"%s\"", poDS->osFilename.c_str(),
6644 1 : osVarName.c_str()));
6645 : }
6646 : else
6647 : {
6648 : poDS->aosSubDatasets.SetNameValue(
6649 : osSubDatasetName.c_str(),
6650 : CPLSPrintf("NETCDF:\"%s\":%s", poDS->osFilename.c_str(),
6651 176 : osVarName.c_str()));
6652 : }
6653 :
6654 : const std::string osSubDatasetDesc =
6655 354 : CPLOPrintf("SUBDATASET_%d_DESC", nSubDatasets);
6656 :
6657 : poDS->aosSubDatasets.SetNameValue(
6658 : osSubDatasetDesc.c_str(),
6659 : CPLSPrintf("[%s] %s (%s)", osDim.c_str(), osVarStdName.c_str(),
6660 177 : pszType));
6661 : }
6662 : }
6663 :
6664 : // Recurse on sub groups.
6665 61 : int nSubGroups = 0;
6666 61 : int *panSubGroupIds = nullptr;
6667 61 : NCDFGetSubGroups(nGroupId, &nSubGroups, &panSubGroupIds);
6668 69 : for (int i = 0; i < nSubGroups; i++)
6669 : {
6670 8 : CreateSubDatasetList(panSubGroupIds[i]);
6671 : }
6672 61 : CPLFree(panSubGroupIds);
6673 61 : }
6674 :
6675 : /************************************************************************/
6676 : /* TestCapability() */
6677 : /************************************************************************/
6678 :
6679 244 : bool netCDFDataset::TestCapability(const char *pszCap) const
6680 : {
6681 244 : if (EQUAL(pszCap, ODsCCreateLayer))
6682 : {
6683 221 : return eAccess == GA_Update && nBands == 0 &&
6684 215 : (eMultipleLayerBehavior != SINGLE_LAYER ||
6685 226 : this->GetLayerCount() == 0 || bSGSupport);
6686 : }
6687 133 : else if (EQUAL(pszCap, ODsCZGeometries))
6688 2 : return true;
6689 :
6690 131 : return false;
6691 : }
6692 :
6693 : /************************************************************************/
6694 : /* GetLayer() */
6695 : /************************************************************************/
6696 :
6697 443 : const OGRLayer *netCDFDataset::GetLayer(int nIdx) const
6698 : {
6699 443 : if (nIdx < 0 || nIdx >= this->GetLayerCount())
6700 2 : return nullptr;
6701 441 : return papoLayers[nIdx].get();
6702 : }
6703 :
6704 : /************************************************************************/
6705 : /* ICreateLayer() */
6706 : /************************************************************************/
6707 :
6708 59 : OGRLayer *netCDFDataset::ICreateLayer(const char *pszName,
6709 : const OGRGeomFieldDefn *poGeomFieldDefn,
6710 : CSLConstList papszOptions)
6711 : {
6712 59 : int nLayerCDFId = cdfid;
6713 59 : if (!TestCapability(ODsCCreateLayer))
6714 0 : return nullptr;
6715 :
6716 59 : const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
6717 : const auto poSpatialRef =
6718 59 : poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
6719 :
6720 118 : CPLString osNetCDFLayerName(pszName);
6721 59 : const netCDFWriterConfigLayer *poLayerConfig = nullptr;
6722 59 : if (oWriterConfig.m_bIsValid)
6723 : {
6724 : std::map<CPLString, netCDFWriterConfigLayer>::const_iterator
6725 3 : oLayerIter = oWriterConfig.m_oLayers.find(pszName);
6726 3 : if (oLayerIter != oWriterConfig.m_oLayers.end())
6727 : {
6728 1 : poLayerConfig = &(oLayerIter->second);
6729 1 : osNetCDFLayerName = poLayerConfig->m_osNetCDFName;
6730 : }
6731 : }
6732 :
6733 59 : netCDFDataset *poLayerDataset = nullptr;
6734 59 : if (eMultipleLayerBehavior == SEPARATE_FILES)
6735 : {
6736 3 : if (CPLLaunderForFilenameSafe(osNetCDFLayerName.c_str(), nullptr) !=
6737 : osNetCDFLayerName)
6738 : {
6739 1 : CPLError(CE_Failure, CPLE_AppDefined,
6740 : "Illegal characters in '%s' to form a valid filename",
6741 : osNetCDFLayerName.c_str());
6742 1 : return nullptr;
6743 : }
6744 2 : CPLStringList aosDatasetOptions;
6745 : aosDatasetOptions.SetNameValue(
6746 2 : "CONFIG_FILE", aosCreationOptions.FetchNameValue("CONFIG_FILE"));
6747 : aosDatasetOptions.SetNameValue(
6748 2 : "FORMAT", aosCreationOptions.FetchNameValue("FORMAT"));
6749 : aosDatasetOptions.SetNameValue(
6750 : "WRITE_GDAL_TAGS",
6751 2 : aosCreationOptions.FetchNameValue("WRITE_GDAL_TAGS"));
6752 : const CPLString osLayerFilename(
6753 2 : CPLFormFilenameSafe(osFilename, osNetCDFLayerName, "nc"));
6754 2 : CPLAcquireMutex(hNCMutex, 1000.0);
6755 2 : poLayerDataset =
6756 2 : CreateLL(osLayerFilename, 0, 0, 0, aosDatasetOptions.List());
6757 2 : CPLReleaseMutex(hNCMutex);
6758 2 : if (poLayerDataset == nullptr)
6759 0 : return nullptr;
6760 :
6761 2 : nLayerCDFId = poLayerDataset->cdfid;
6762 2 : NCDFAddGDALHistory(nLayerCDFId, osLayerFilename, bWriteGDALVersion,
6763 2 : bWriteGDALHistory, "", "Create",
6764 : NCDF_CONVENTIONS_CF_V1_6);
6765 : }
6766 56 : else if (eMultipleLayerBehavior == SEPARATE_GROUPS)
6767 : {
6768 2 : SetDefineMode(true);
6769 :
6770 2 : nLayerCDFId = -1;
6771 2 : int status = nc_def_grp(cdfid, osNetCDFLayerName, &nLayerCDFId);
6772 2 : NCDF_ERR(status);
6773 2 : if (status != NC_NOERR)
6774 0 : return nullptr;
6775 :
6776 2 : NCDFAddGDALHistory(nLayerCDFId, osFilename, bWriteGDALVersion,
6777 2 : bWriteGDALHistory, "", "Create",
6778 : NCDF_CONVENTIONS_CF_V1_6);
6779 : }
6780 :
6781 : // Make a clone to workaround a bug in released MapServer versions
6782 : // that destroys the passed SRS instead of releasing it .
6783 58 : OGRSpatialReference *poSRS = nullptr;
6784 58 : if (poSpatialRef)
6785 : {
6786 43 : poSRS = poSpatialRef->Clone();
6787 43 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
6788 : }
6789 : std::shared_ptr<netCDFLayer> poLayer(
6790 58 : new netCDFLayer(poLayerDataset ? poLayerDataset : this, nLayerCDFId,
6791 116 : osNetCDFLayerName, eGType, poSRS));
6792 58 : if (poSRS != nullptr)
6793 43 : poSRS->Release();
6794 :
6795 : // Fetch layer creation options coming from config file
6796 116 : CPLStringList aosNewOptions(CSLDuplicate(papszOptions));
6797 58 : if (oWriterConfig.m_bIsValid)
6798 : {
6799 2 : for (const auto &[osName, osValue] :
6800 5 : oWriterConfig.m_oLayerCreationOptions)
6801 : {
6802 1 : aosNewOptions.SetNameValue(osName, osValue);
6803 : }
6804 3 : if (poLayerConfig != nullptr)
6805 : {
6806 4 : for (const auto &[osName, osValue] :
6807 5 : poLayerConfig->m_oLayerCreationOptions)
6808 : {
6809 2 : aosNewOptions.SetNameValue(osName, osValue);
6810 : }
6811 : }
6812 : }
6813 :
6814 58 : const bool bRet = poLayer->Create(aosNewOptions.List(), poLayerConfig);
6815 :
6816 58 : if (!bRet)
6817 : {
6818 0 : return nullptr;
6819 : }
6820 :
6821 58 : if (poLayerDataset != nullptr)
6822 2 : apoVectorDatasets.push_back(poLayerDataset);
6823 :
6824 58 : papoLayers.push_back(poLayer);
6825 58 : return poLayer.get();
6826 : }
6827 :
6828 : /************************************************************************/
6829 : /* CloneAttributes() */
6830 : /************************************************************************/
6831 :
6832 137 : bool netCDFDataset::CloneAttributes(int old_cdfid, int new_cdfid, int nSrcVarId,
6833 : int nDstVarId)
6834 : {
6835 137 : int nAttCount = -1;
6836 137 : int status = nc_inq_varnatts(old_cdfid, nSrcVarId, &nAttCount);
6837 137 : NCDF_ERR(status);
6838 :
6839 693 : for (int i = 0; i < nAttCount; i++)
6840 : {
6841 : char szName[NC_MAX_NAME + 1];
6842 556 : szName[0] = 0;
6843 556 : status = nc_inq_attname(old_cdfid, nSrcVarId, i, szName);
6844 556 : NCDF_ERR(status);
6845 :
6846 : status =
6847 556 : nc_copy_att(old_cdfid, nSrcVarId, szName, new_cdfid, nDstVarId);
6848 556 : NCDF_ERR(status);
6849 556 : if (status != NC_NOERR)
6850 0 : return false;
6851 : }
6852 :
6853 137 : return true;
6854 : }
6855 :
6856 : /************************************************************************/
6857 : /* CloneVariableContent() */
6858 : /************************************************************************/
6859 :
6860 121 : bool netCDFDataset::CloneVariableContent(int old_cdfid, int new_cdfid,
6861 : int nSrcVarId, int nDstVarId)
6862 : {
6863 121 : int nVarDimCount = -1;
6864 121 : int status = nc_inq_varndims(old_cdfid, nSrcVarId, &nVarDimCount);
6865 121 : NCDF_ERR(status);
6866 121 : int anDimIds[] = {-1, 1};
6867 121 : status = nc_inq_vardimid(old_cdfid, nSrcVarId, anDimIds);
6868 121 : NCDF_ERR(status);
6869 121 : nc_type nc_datatype = NC_NAT;
6870 121 : status = nc_inq_vartype(old_cdfid, nSrcVarId, &nc_datatype);
6871 121 : NCDF_ERR(status);
6872 121 : size_t nTypeSize = 0;
6873 121 : switch (nc_datatype)
6874 : {
6875 35 : case NC_BYTE:
6876 : case NC_CHAR:
6877 35 : nTypeSize = 1;
6878 35 : break;
6879 4 : case NC_SHORT:
6880 4 : nTypeSize = 2;
6881 4 : break;
6882 24 : case NC_INT:
6883 24 : nTypeSize = 4;
6884 24 : break;
6885 4 : case NC_FLOAT:
6886 4 : nTypeSize = 4;
6887 4 : break;
6888 43 : case NC_DOUBLE:
6889 43 : nTypeSize = 8;
6890 43 : break;
6891 2 : case NC_UBYTE:
6892 2 : nTypeSize = 1;
6893 2 : break;
6894 2 : case NC_USHORT:
6895 2 : nTypeSize = 2;
6896 2 : break;
6897 2 : case NC_UINT:
6898 2 : nTypeSize = 4;
6899 2 : break;
6900 4 : case NC_INT64:
6901 : case NC_UINT64:
6902 4 : nTypeSize = 8;
6903 4 : break;
6904 1 : case NC_STRING:
6905 1 : nTypeSize = sizeof(char *);
6906 1 : break;
6907 0 : default:
6908 : {
6909 0 : CPLError(CE_Failure, CPLE_NotSupported, "Unsupported data type: %d",
6910 : nc_datatype);
6911 0 : return false;
6912 : }
6913 : }
6914 :
6915 121 : size_t nElems = 1;
6916 : size_t anStart[NC_MAX_DIMS];
6917 : size_t anCount[NC_MAX_DIMS];
6918 121 : size_t nRecords = 1;
6919 261 : for (int i = 0; i < nVarDimCount; i++)
6920 : {
6921 140 : anStart[i] = 0;
6922 140 : if (i == 0)
6923 : {
6924 116 : anCount[i] = 1;
6925 116 : status = nc_inq_dimlen(old_cdfid, anDimIds[i], &nRecords);
6926 116 : NCDF_ERR(status);
6927 : }
6928 : else
6929 : {
6930 24 : anCount[i] = 0;
6931 24 : status = nc_inq_dimlen(old_cdfid, anDimIds[i], &anCount[i]);
6932 24 : NCDF_ERR(status);
6933 24 : nElems *= anCount[i];
6934 : }
6935 : }
6936 :
6937 : /* Workaround in some cases a netCDF bug:
6938 : * https://github.com/Unidata/netcdf-c/pull/1442 */
6939 121 : if (nRecords > 0 && nRecords < 10 * 1000 * 1000 / (nElems * nTypeSize))
6940 : {
6941 119 : nElems *= nRecords;
6942 119 : anCount[0] = nRecords;
6943 119 : nRecords = 1;
6944 : }
6945 :
6946 121 : void *pBuffer = VSI_MALLOC2_VERBOSE(nElems, nTypeSize);
6947 121 : if (pBuffer == nullptr)
6948 0 : return false;
6949 :
6950 240 : for (size_t iRecord = 0; iRecord < nRecords; iRecord++)
6951 : {
6952 119 : anStart[0] = iRecord;
6953 :
6954 119 : switch (nc_datatype)
6955 : {
6956 5 : case NC_BYTE:
6957 : status =
6958 5 : nc_get_vara_schar(old_cdfid, nSrcVarId, anStart, anCount,
6959 : static_cast<signed char *>(pBuffer));
6960 5 : if (!status)
6961 5 : status = nc_put_vara_schar(
6962 : new_cdfid, nDstVarId, anStart, anCount,
6963 : static_cast<signed char *>(pBuffer));
6964 5 : break;
6965 28 : case NC_CHAR:
6966 : status =
6967 28 : nc_get_vara_text(old_cdfid, nSrcVarId, anStart, anCount,
6968 : static_cast<char *>(pBuffer));
6969 28 : if (!status)
6970 : status =
6971 28 : nc_put_vara_text(new_cdfid, nDstVarId, anStart, anCount,
6972 : static_cast<char *>(pBuffer));
6973 28 : break;
6974 4 : case NC_SHORT:
6975 : status =
6976 4 : nc_get_vara_short(old_cdfid, nSrcVarId, anStart, anCount,
6977 : static_cast<short *>(pBuffer));
6978 4 : if (!status)
6979 4 : status = nc_put_vara_short(new_cdfid, nDstVarId, anStart,
6980 : anCount,
6981 : static_cast<short *>(pBuffer));
6982 4 : break;
6983 24 : case NC_INT:
6984 24 : status = nc_get_vara_int(old_cdfid, nSrcVarId, anStart, anCount,
6985 : static_cast<int *>(pBuffer));
6986 24 : if (!status)
6987 : status =
6988 24 : nc_put_vara_int(new_cdfid, nDstVarId, anStart, anCount,
6989 : static_cast<int *>(pBuffer));
6990 24 : break;
6991 4 : case NC_FLOAT:
6992 : status =
6993 4 : nc_get_vara_float(old_cdfid, nSrcVarId, anStart, anCount,
6994 : static_cast<float *>(pBuffer));
6995 4 : if (!status)
6996 4 : status = nc_put_vara_float(new_cdfid, nDstVarId, anStart,
6997 : anCount,
6998 : static_cast<float *>(pBuffer));
6999 4 : break;
7000 43 : case NC_DOUBLE:
7001 : status =
7002 43 : nc_get_vara_double(old_cdfid, nSrcVarId, anStart, anCount,
7003 : static_cast<double *>(pBuffer));
7004 43 : if (!status)
7005 43 : status = nc_put_vara_double(new_cdfid, nDstVarId, anStart,
7006 : anCount,
7007 : static_cast<double *>(pBuffer));
7008 43 : break;
7009 1 : case NC_STRING:
7010 : status =
7011 1 : nc_get_vara_string(old_cdfid, nSrcVarId, anStart, anCount,
7012 : static_cast<char **>(pBuffer));
7013 1 : if (!status)
7014 : {
7015 1 : status = nc_put_vara_string(
7016 : new_cdfid, nDstVarId, anStart, anCount,
7017 : static_cast<const char **>(pBuffer));
7018 1 : nc_free_string(nElems, static_cast<char **>(pBuffer));
7019 : }
7020 1 : break;
7021 :
7022 2 : case NC_UBYTE:
7023 : status =
7024 2 : nc_get_vara_uchar(old_cdfid, nSrcVarId, anStart, anCount,
7025 : static_cast<unsigned char *>(pBuffer));
7026 2 : if (!status)
7027 2 : status = nc_put_vara_uchar(
7028 : new_cdfid, nDstVarId, anStart, anCount,
7029 : static_cast<unsigned char *>(pBuffer));
7030 2 : break;
7031 2 : case NC_USHORT:
7032 : status =
7033 2 : nc_get_vara_ushort(old_cdfid, nSrcVarId, anStart, anCount,
7034 : static_cast<unsigned short *>(pBuffer));
7035 2 : if (!status)
7036 2 : status = nc_put_vara_ushort(
7037 : new_cdfid, nDstVarId, anStart, anCount,
7038 : static_cast<unsigned short *>(pBuffer));
7039 2 : break;
7040 2 : case NC_UINT:
7041 : status =
7042 2 : nc_get_vara_uint(old_cdfid, nSrcVarId, anStart, anCount,
7043 : static_cast<unsigned int *>(pBuffer));
7044 2 : if (!status)
7045 : status =
7046 2 : nc_put_vara_uint(new_cdfid, nDstVarId, anStart, anCount,
7047 : static_cast<unsigned int *>(pBuffer));
7048 2 : break;
7049 2 : case NC_INT64:
7050 : status =
7051 2 : nc_get_vara_longlong(old_cdfid, nSrcVarId, anStart, anCount,
7052 : static_cast<long long *>(pBuffer));
7053 2 : if (!status)
7054 2 : status = nc_put_vara_longlong(
7055 : new_cdfid, nDstVarId, anStart, anCount,
7056 : static_cast<long long *>(pBuffer));
7057 2 : break;
7058 2 : case NC_UINT64:
7059 2 : status = nc_get_vara_ulonglong(
7060 : old_cdfid, nSrcVarId, anStart, anCount,
7061 : static_cast<unsigned long long *>(pBuffer));
7062 2 : if (!status)
7063 2 : status = nc_put_vara_ulonglong(
7064 : new_cdfid, nDstVarId, anStart, anCount,
7065 : static_cast<unsigned long long *>(pBuffer));
7066 2 : break;
7067 0 : default:
7068 0 : status = NC_EBADTYPE;
7069 : }
7070 :
7071 119 : NCDF_ERR(status);
7072 119 : if (status != NC_NOERR)
7073 : {
7074 0 : VSIFree(pBuffer);
7075 0 : return false;
7076 : }
7077 : }
7078 :
7079 121 : VSIFree(pBuffer);
7080 121 : return true;
7081 : }
7082 :
7083 : /************************************************************************/
7084 : /* NCDFIsUnlimitedDim() */
7085 : /************************************************************************/
7086 :
7087 80 : bool NCDFIsUnlimitedDim(bool bIsNC4, int cdfid, int nDimId)
7088 : {
7089 80 : if (bIsNC4)
7090 : {
7091 16 : int nUnlimitedDims = 0;
7092 16 : nc_inq_unlimdims(cdfid, &nUnlimitedDims, nullptr);
7093 16 : bool bFound = false;
7094 16 : if (nUnlimitedDims)
7095 : {
7096 : int *panUnlimitedDimIds =
7097 16 : static_cast<int *>(CPLMalloc(sizeof(int) * nUnlimitedDims));
7098 16 : nc_inq_unlimdims(cdfid, nullptr, panUnlimitedDimIds);
7099 30 : for (int i = 0; i < nUnlimitedDims; i++)
7100 : {
7101 22 : if (panUnlimitedDimIds[i] == nDimId)
7102 : {
7103 8 : bFound = true;
7104 8 : break;
7105 : }
7106 : }
7107 16 : CPLFree(panUnlimitedDimIds);
7108 : }
7109 16 : return bFound;
7110 : }
7111 : else
7112 : {
7113 64 : int nUnlimitedDimId = -1;
7114 64 : nc_inq(cdfid, nullptr, nullptr, nullptr, &nUnlimitedDimId);
7115 64 : return nDimId == nUnlimitedDimId;
7116 : }
7117 : }
7118 :
7119 : /************************************************************************/
7120 : /* CloneGrp() */
7121 : /************************************************************************/
7122 :
7123 16 : bool netCDFDataset::CloneGrp(int nOldGrpId, int nNewGrpId, bool bIsNC4,
7124 : int nLayerId, int nDimIdToGrow, size_t nNewSize)
7125 : {
7126 : // Clone dimensions
7127 16 : int nDimCount = -1;
7128 16 : int status = nc_inq_ndims(nOldGrpId, &nDimCount);
7129 16 : NCDF_ERR(status);
7130 16 : if (nDimCount < 0 || nDimCount > NC_MAX_DIMS)
7131 0 : return false;
7132 : int anDimIds[NC_MAX_DIMS];
7133 16 : int nUnlimiDimID = -1;
7134 16 : status = nc_inq_unlimdim(nOldGrpId, &nUnlimiDimID);
7135 16 : NCDF_ERR(status);
7136 16 : if (bIsNC4)
7137 : {
7138 : // In NC4, the dimension ids of a group are not necessarily in
7139 : // [0,nDimCount-1] range
7140 8 : int nDimCount2 = -1;
7141 8 : status = nc_inq_dimids(nOldGrpId, &nDimCount2, anDimIds, FALSE);
7142 8 : NCDF_ERR(status);
7143 8 : CPLAssert(nDimCount == nDimCount2);
7144 : }
7145 : else
7146 : {
7147 36 : for (int i = 0; i < nDimCount; i++)
7148 28 : anDimIds[i] = i;
7149 : }
7150 60 : for (int i = 0; i < nDimCount; i++)
7151 : {
7152 : char szDimName[NC_MAX_NAME + 1];
7153 44 : szDimName[0] = 0;
7154 44 : size_t nLen = 0;
7155 44 : const int nDimId = anDimIds[i];
7156 44 : status = nc_inq_dim(nOldGrpId, nDimId, szDimName, &nLen);
7157 44 : NCDF_ERR(status);
7158 44 : if (NCDFIsUnlimitedDim(bIsNC4, nOldGrpId, nDimId))
7159 16 : nLen = NC_UNLIMITED;
7160 28 : else if (nDimId == nDimIdToGrow && nOldGrpId == nLayerId)
7161 13 : nLen = nNewSize;
7162 44 : int nNewDimId = -1;
7163 44 : status = nc_def_dim(nNewGrpId, szDimName, nLen, &nNewDimId);
7164 44 : NCDF_ERR(status);
7165 44 : CPLAssert(nDimId == nNewDimId);
7166 44 : if (status != NC_NOERR)
7167 : {
7168 0 : return false;
7169 : }
7170 : }
7171 :
7172 : // Clone main attributes
7173 16 : if (!CloneAttributes(nOldGrpId, nNewGrpId, NC_GLOBAL, NC_GLOBAL))
7174 : {
7175 0 : return false;
7176 : }
7177 :
7178 : // Clone variable definitions
7179 16 : int nVarCount = -1;
7180 16 : status = nc_inq_nvars(nOldGrpId, &nVarCount);
7181 16 : NCDF_ERR(status);
7182 :
7183 137 : for (int i = 0; i < nVarCount; i++)
7184 : {
7185 : char szVarName[NC_MAX_NAME + 1];
7186 121 : szVarName[0] = 0;
7187 121 : status = nc_inq_varname(nOldGrpId, i, szVarName);
7188 121 : NCDF_ERR(status);
7189 121 : nc_type nc_datatype = NC_NAT;
7190 121 : status = nc_inq_vartype(nOldGrpId, i, &nc_datatype);
7191 121 : NCDF_ERR(status);
7192 121 : int nVarDimCount = -1;
7193 121 : status = nc_inq_varndims(nOldGrpId, i, &nVarDimCount);
7194 121 : NCDF_ERR(status);
7195 121 : status = nc_inq_vardimid(nOldGrpId, i, anDimIds);
7196 121 : NCDF_ERR(status);
7197 121 : int nNewVarId = -1;
7198 121 : status = nc_def_var(nNewGrpId, szVarName, nc_datatype, nVarDimCount,
7199 : anDimIds, &nNewVarId);
7200 121 : NCDF_ERR(status);
7201 121 : CPLAssert(i == nNewVarId);
7202 121 : if (status != NC_NOERR)
7203 : {
7204 0 : return false;
7205 : }
7206 :
7207 121 : if (!CloneAttributes(nOldGrpId, nNewGrpId, i, i))
7208 : {
7209 0 : return false;
7210 : }
7211 : }
7212 :
7213 16 : status = nc_enddef(nNewGrpId);
7214 16 : NCDF_ERR(status);
7215 16 : if (status != NC_NOERR)
7216 : {
7217 0 : return false;
7218 : }
7219 :
7220 : // Clone variable content
7221 137 : for (int i = 0; i < nVarCount; i++)
7222 : {
7223 121 : if (!CloneVariableContent(nOldGrpId, nNewGrpId, i, i))
7224 : {
7225 0 : return false;
7226 : }
7227 : }
7228 :
7229 16 : return true;
7230 : }
7231 :
7232 : /************************************************************************/
7233 : /* GrowDim() */
7234 : /************************************************************************/
7235 :
7236 13 : bool netCDFDataset::GrowDim(int nLayerId, int nDimIdToGrow, size_t nNewSize)
7237 : {
7238 : int nCreationMode;
7239 : // Set nCreationMode based on eFormat.
7240 13 : switch (eFormat)
7241 : {
7242 : #ifdef NETCDF_HAS_NC2
7243 0 : case NCDF_FORMAT_NC2:
7244 0 : nCreationMode = NC_CLOBBER | NC_64BIT_OFFSET;
7245 0 : break;
7246 : #endif
7247 5 : case NCDF_FORMAT_NC4:
7248 5 : nCreationMode = NC_CLOBBER | NC_NETCDF4;
7249 5 : break;
7250 0 : case NCDF_FORMAT_NC4C:
7251 0 : nCreationMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
7252 0 : break;
7253 8 : case NCDF_FORMAT_NC:
7254 : default:
7255 8 : nCreationMode = NC_CLOBBER;
7256 8 : break;
7257 : }
7258 :
7259 13 : int new_cdfid = -1;
7260 26 : CPLString osTmpFilename(osFilename + ".tmp");
7261 26 : CPLString osFilenameForNCCreate(osTmpFilename);
7262 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
7263 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
7264 : {
7265 : char *pszTemp =
7266 : CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
7267 : osFilenameForNCCreate = pszTemp;
7268 : CPLFree(pszTemp);
7269 : }
7270 : #endif
7271 13 : int status = nc_create(osFilenameForNCCreate, nCreationMode, &new_cdfid);
7272 13 : NCDF_ERR(status);
7273 13 : if (status != NC_NOERR)
7274 0 : return false;
7275 :
7276 13 : if (!CloneGrp(cdfid, new_cdfid, eFormat == NCDF_FORMAT_NC4, nLayerId,
7277 : nDimIdToGrow, nNewSize))
7278 : {
7279 0 : GDAL_nc_close(new_cdfid);
7280 0 : return false;
7281 : }
7282 :
7283 13 : int nGroupCount = 0;
7284 26 : std::vector<CPLString> oListGrpName;
7285 31 : if (eFormat == NCDF_FORMAT_NC4 &&
7286 18 : nc_inq_grps(cdfid, &nGroupCount, nullptr) == NC_NOERR &&
7287 5 : nGroupCount > 0)
7288 : {
7289 : int *panGroupIds =
7290 2 : static_cast<int *>(CPLMalloc(sizeof(int) * nGroupCount));
7291 2 : status = nc_inq_grps(cdfid, nullptr, panGroupIds);
7292 2 : NCDF_ERR(status);
7293 5 : for (int i = 0; i < nGroupCount; i++)
7294 : {
7295 : char szGroupName[NC_MAX_NAME + 1];
7296 3 : szGroupName[0] = 0;
7297 3 : NCDF_ERR(nc_inq_grpname(panGroupIds[i], szGroupName));
7298 3 : int nNewGrpId = -1;
7299 3 : status = nc_def_grp(new_cdfid, szGroupName, &nNewGrpId);
7300 3 : NCDF_ERR(status);
7301 3 : if (status != NC_NOERR)
7302 : {
7303 0 : CPLFree(panGroupIds);
7304 0 : GDAL_nc_close(new_cdfid);
7305 0 : return false;
7306 : }
7307 3 : if (!CloneGrp(panGroupIds[i], nNewGrpId, /*bIsNC4=*/true, nLayerId,
7308 : nDimIdToGrow, nNewSize))
7309 : {
7310 0 : CPLFree(panGroupIds);
7311 0 : GDAL_nc_close(new_cdfid);
7312 0 : return false;
7313 : }
7314 : }
7315 2 : CPLFree(panGroupIds);
7316 :
7317 5 : for (int i = 0; i < this->GetLayerCount(); i++)
7318 : {
7319 3 : auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
7320 3 : if (poLayer)
7321 : {
7322 : char szGroupName[NC_MAX_NAME + 1];
7323 3 : szGroupName[0] = 0;
7324 3 : status = nc_inq_grpname(poLayer->GetCDFID(), szGroupName);
7325 3 : NCDF_ERR(status);
7326 3 : oListGrpName.push_back(szGroupName);
7327 : }
7328 : }
7329 : }
7330 :
7331 13 : GDAL_nc_close(cdfid);
7332 13 : cdfid = -1;
7333 13 : GDAL_nc_close(new_cdfid);
7334 :
7335 26 : CPLString osOriFilename(osFilename + ".ori");
7336 26 : if (VSIRename(osFilename, osOriFilename) != 0 ||
7337 13 : VSIRename(osTmpFilename, osFilename) != 0)
7338 : {
7339 0 : CPLError(CE_Failure, CPLE_FileIO, "Renaming of files failed");
7340 0 : return false;
7341 : }
7342 13 : VSIUnlink(osOriFilename);
7343 :
7344 26 : CPLString osFilenameForNCOpen(osFilename);
7345 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
7346 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
7347 : {
7348 : char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
7349 : osFilenameForNCOpen = pszTemp;
7350 : CPLFree(pszTemp);
7351 : }
7352 : #endif
7353 13 : status = GDAL_nc_open(osFilenameForNCOpen, NC_WRITE, &cdfid);
7354 13 : NCDF_ERR(status);
7355 13 : if (status != NC_NOERR)
7356 0 : return false;
7357 13 : bDefineMode = false;
7358 :
7359 13 : if (!oListGrpName.empty())
7360 : {
7361 5 : for (int i = 0; i < this->GetLayerCount(); i++)
7362 : {
7363 3 : auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
7364 3 : if (poLayer)
7365 : {
7366 3 : int nNewLayerCDFID = -1;
7367 3 : status = nc_inq_ncid(cdfid, oListGrpName[i].c_str(),
7368 : &nNewLayerCDFID);
7369 3 : NCDF_ERR(status);
7370 3 : poLayer->SetCDFID(nNewLayerCDFID);
7371 : }
7372 : }
7373 : }
7374 : else
7375 : {
7376 22 : for (int i = 0; i < this->GetLayerCount(); i++)
7377 : {
7378 11 : auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
7379 11 : if (poLayer)
7380 11 : poLayer->SetCDFID(cdfid);
7381 : }
7382 : }
7383 :
7384 13 : return true;
7385 : }
7386 :
7387 : #ifdef ENABLE_NCDUMP
7388 :
7389 : /************************************************************************/
7390 : /* netCDFDatasetCreateTempFile() */
7391 : /************************************************************************/
7392 :
7393 : /* Create a netCDF file from a text dump (format of ncdump) */
7394 : /* Mostly to easy fuzzing of the driver, while still generating valid */
7395 : /* netCDF files. */
7396 : /* Note: not all data types are supported ! */
7397 4 : bool netCDFDatasetCreateTempFile(NetCDFFormatEnum eFormat,
7398 : const char *pszTmpFilename, VSILFILE *fpSrc)
7399 : {
7400 4 : CPL_IGNORE_RET_VAL(eFormat);
7401 4 : int nCreateMode = NC_CLOBBER;
7402 4 : if (eFormat == NCDF_FORMAT_NC4)
7403 1 : nCreateMode |= NC_NETCDF4;
7404 3 : else if (eFormat == NCDF_FORMAT_NC4C)
7405 0 : nCreateMode |= NC_NETCDF4 | NC_CLASSIC_MODEL;
7406 4 : int nCdfId = -1;
7407 4 : int status = nc_create(pszTmpFilename, nCreateMode, &nCdfId);
7408 4 : if (status != NC_NOERR)
7409 : {
7410 0 : return false;
7411 : }
7412 4 : VSIFSeekL(fpSrc, 0, SEEK_SET);
7413 : const char *pszLine;
7414 4 : constexpr int SECTION_NONE = 0;
7415 4 : constexpr int SECTION_DIMENSIONS = 1;
7416 4 : constexpr int SECTION_VARIABLES = 2;
7417 4 : constexpr int SECTION_DATA = 3;
7418 4 : int nActiveSection = SECTION_NONE;
7419 8 : std::map<CPLString, int> oMapDimToId;
7420 8 : std::map<int, int> oMapDimIdToDimLen;
7421 8 : std::map<CPLString, int> oMapVarToId;
7422 8 : std::map<int, std::vector<int>> oMapVarIdToVectorOfDimId;
7423 8 : std::map<int, int> oMapVarIdToType;
7424 4 : std::set<CPLString> oSetAttrDefined;
7425 4 : oMapVarToId[""] = -1;
7426 4 : size_t nTotalVarSize = 0;
7427 208 : while ((pszLine = CPLReadLineL(fpSrc)) != nullptr)
7428 : {
7429 204 : if (STARTS_WITH(pszLine, "dimensions:") &&
7430 : nActiveSection == SECTION_NONE)
7431 : {
7432 4 : nActiveSection = SECTION_DIMENSIONS;
7433 : }
7434 200 : else if (STARTS_WITH(pszLine, "variables:") &&
7435 : nActiveSection == SECTION_DIMENSIONS)
7436 : {
7437 4 : nActiveSection = SECTION_VARIABLES;
7438 : }
7439 196 : else if (STARTS_WITH(pszLine, "data:") &&
7440 : nActiveSection == SECTION_VARIABLES)
7441 : {
7442 4 : nActiveSection = SECTION_DATA;
7443 4 : status = nc_enddef(nCdfId);
7444 4 : if (status != NC_NOERR)
7445 : {
7446 0 : CPLDebug("netCDF", "nc_enddef() failed: %s",
7447 : nc_strerror(status));
7448 : }
7449 : }
7450 192 : else if (nActiveSection == SECTION_DIMENSIONS)
7451 : {
7452 : const CPLStringList aosTokens(
7453 9 : CSLTokenizeString2(pszLine, " \t=;", 0));
7454 9 : if (aosTokens.size() == 2)
7455 : {
7456 9 : const char *pszDimName = aosTokens[0];
7457 9 : bool bValidName = true;
7458 9 : if (STARTS_WITH(pszDimName, "_nc4_non_coord_"))
7459 : {
7460 : // This is an internal netcdf prefix. Using it may
7461 : // cause memory leaks.
7462 0 : bValidName = false;
7463 : }
7464 9 : if (!bValidName)
7465 : {
7466 0 : CPLDebug("netCDF",
7467 : "nc_def_dim(%s) failed: invalid name found",
7468 : pszDimName);
7469 0 : continue;
7470 : }
7471 :
7472 : const bool bIsASCII =
7473 9 : CPLIsASCII(pszDimName, static_cast<size_t>(-1));
7474 9 : if (!bIsASCII)
7475 : {
7476 : // Workaround https://github.com/Unidata/netcdf-c/pull/450
7477 0 : CPLDebug("netCDF",
7478 : "nc_def_dim(%s) failed: rejected because "
7479 : "of non-ASCII characters",
7480 : pszDimName);
7481 0 : continue;
7482 : }
7483 9 : int nDimSize = EQUAL(aosTokens[1], "UNLIMITED")
7484 : ? NC_UNLIMITED
7485 9 : : atoi(aosTokens[1]);
7486 9 : if (nDimSize >= 1000)
7487 1 : nDimSize = 1000; // to avoid very long processing
7488 9 : if (nDimSize >= 0)
7489 : {
7490 9 : int nDimId = -1;
7491 9 : status = nc_def_dim(nCdfId, pszDimName, nDimSize, &nDimId);
7492 9 : if (status != NC_NOERR)
7493 : {
7494 0 : CPLDebug("netCDF", "nc_def_dim(%s, %d) failed: %s",
7495 : pszDimName, nDimSize, nc_strerror(status));
7496 : }
7497 : else
7498 : {
7499 : #ifdef DEBUG_VERBOSE
7500 : CPLDebug("netCDF", "nc_def_dim(%s, %d) (%s) succeeded",
7501 : pszDimName, nDimSize, pszLine);
7502 : #endif
7503 9 : oMapDimToId[pszDimName] = nDimId;
7504 9 : oMapDimIdToDimLen[nDimId] = nDimSize;
7505 : }
7506 : }
7507 : }
7508 : }
7509 183 : else if (nActiveSection == SECTION_VARIABLES)
7510 : {
7511 390 : while (*pszLine == ' ' || *pszLine == '\t')
7512 249 : pszLine++;
7513 141 : const char *pszColumn = strchr(pszLine, ':');
7514 141 : const char *pszEqual = strchr(pszLine, '=');
7515 141 : if (pszColumn == nullptr)
7516 : {
7517 : const CPLStringList aosTokens(
7518 21 : CSLTokenizeString2(pszLine, " \t=(),;", 0));
7519 21 : if (aosTokens.size() >= 2)
7520 : {
7521 17 : const char *pszVarName = aosTokens[1];
7522 17 : bool bValidName = true;
7523 17 : if (STARTS_WITH(pszVarName, "_nc4_non_coord_"))
7524 : {
7525 : // This is an internal netcdf prefix. Using it may
7526 : // cause memory leaks.
7527 0 : bValidName = false;
7528 : }
7529 138 : for (int i = 0; pszVarName[i]; i++)
7530 : {
7531 121 : if (!((pszVarName[i] >= 'a' && pszVarName[i] <= 'z') ||
7532 28 : (pszVarName[i] >= 'A' && pszVarName[i] <= 'Z') ||
7533 9 : (pszVarName[i] >= '0' && pszVarName[i] <= '9') ||
7534 6 : pszVarName[i] == '_'))
7535 : {
7536 0 : bValidName = false;
7537 : }
7538 : }
7539 17 : if (!bValidName)
7540 : {
7541 0 : CPLDebug(
7542 : "netCDF",
7543 : "nc_def_var(%s) failed: illegal character found",
7544 : pszVarName);
7545 0 : continue;
7546 : }
7547 17 : if (oMapVarToId.find(pszVarName) != oMapVarToId.end())
7548 : {
7549 0 : CPLDebug("netCDF",
7550 : "nc_def_var(%s) failed: already defined",
7551 : pszVarName);
7552 0 : continue;
7553 : }
7554 17 : const char *pszVarType = aosTokens[0];
7555 17 : int nc_datatype = NC_BYTE;
7556 17 : size_t nDataTypeSize = 1;
7557 17 : if (EQUAL(pszVarType, "char"))
7558 : {
7559 6 : nc_datatype = NC_CHAR;
7560 6 : nDataTypeSize = 1;
7561 : }
7562 11 : else if (EQUAL(pszVarType, "byte"))
7563 : {
7564 3 : nc_datatype = NC_BYTE;
7565 3 : nDataTypeSize = 1;
7566 : }
7567 8 : else if (EQUAL(pszVarType, "short"))
7568 : {
7569 0 : nc_datatype = NC_SHORT;
7570 0 : nDataTypeSize = 2;
7571 : }
7572 8 : else if (EQUAL(pszVarType, "int"))
7573 : {
7574 0 : nc_datatype = NC_INT;
7575 0 : nDataTypeSize = 4;
7576 : }
7577 8 : else if (EQUAL(pszVarType, "float"))
7578 : {
7579 0 : nc_datatype = NC_FLOAT;
7580 0 : nDataTypeSize = 4;
7581 : }
7582 8 : else if (EQUAL(pszVarType, "double"))
7583 : {
7584 8 : nc_datatype = NC_DOUBLE;
7585 8 : nDataTypeSize = 8;
7586 : }
7587 0 : else if (EQUAL(pszVarType, "ubyte"))
7588 : {
7589 0 : nc_datatype = NC_UBYTE;
7590 0 : nDataTypeSize = 1;
7591 : }
7592 0 : else if (EQUAL(pszVarType, "ushort"))
7593 : {
7594 0 : nc_datatype = NC_USHORT;
7595 0 : nDataTypeSize = 2;
7596 : }
7597 0 : else if (EQUAL(pszVarType, "uint"))
7598 : {
7599 0 : nc_datatype = NC_UINT;
7600 0 : nDataTypeSize = 4;
7601 : }
7602 0 : else if (EQUAL(pszVarType, "int64"))
7603 : {
7604 0 : nc_datatype = NC_INT64;
7605 0 : nDataTypeSize = 8;
7606 : }
7607 0 : else if (EQUAL(pszVarType, "uint64"))
7608 : {
7609 0 : nc_datatype = NC_UINT64;
7610 0 : nDataTypeSize = 8;
7611 : }
7612 :
7613 17 : int nDims = aosTokens.size() - 2;
7614 17 : if (nDims >= 32)
7615 : {
7616 : // The number of dimensions in a netCDFv4 file is
7617 : // limited by #define H5S_MAX_RANK 32
7618 : // but libnetcdf doesn't check that...
7619 0 : CPLDebug("netCDF",
7620 : "nc_def_var(%s) failed: too many dimensions",
7621 : pszVarName);
7622 0 : continue;
7623 : }
7624 17 : std::vector<int> aoDimIds;
7625 17 : bool bFailed = false;
7626 17 : size_t nSize = 1;
7627 35 : for (int i = 0; i < nDims; i++)
7628 : {
7629 18 : const char *pszDimName = aosTokens[2 + i];
7630 18 : if (oMapDimToId.find(pszDimName) == oMapDimToId.end())
7631 : {
7632 0 : bFailed = true;
7633 0 : break;
7634 : }
7635 18 : const int nDimId = oMapDimToId[pszDimName];
7636 18 : aoDimIds.push_back(nDimId);
7637 :
7638 18 : const size_t nDimSize = oMapDimIdToDimLen[nDimId];
7639 18 : if (nDimSize != 0)
7640 : {
7641 18 : if (nSize >
7642 18 : std::numeric_limits<size_t>::max() / nDimSize)
7643 : {
7644 0 : bFailed = true;
7645 0 : break;
7646 : }
7647 : else
7648 : {
7649 18 : nSize *= nDimSize;
7650 : }
7651 : }
7652 : }
7653 17 : if (bFailed)
7654 : {
7655 0 : CPLDebug("netCDF",
7656 : "nc_def_var(%s) failed: unknown dimension(s)",
7657 : pszVarName);
7658 0 : continue;
7659 : }
7660 17 : if (nSize > 100U * 1024 * 1024 / nDataTypeSize)
7661 : {
7662 0 : CPLDebug("netCDF",
7663 : "nc_def_var(%s) failed: too large data",
7664 : pszVarName);
7665 0 : continue;
7666 : }
7667 17 : if (nTotalVarSize >
7668 34 : std::numeric_limits<size_t>::max() - nSize ||
7669 17 : nTotalVarSize + nSize > 100 * 1024 * 1024)
7670 : {
7671 0 : CPLDebug("netCDF",
7672 : "nc_def_var(%s) failed: too large data",
7673 : pszVarName);
7674 0 : continue;
7675 : }
7676 17 : nTotalVarSize += nSize;
7677 :
7678 17 : int nVarId = -1;
7679 : status =
7680 30 : nc_def_var(nCdfId, pszVarName, nc_datatype, nDims,
7681 13 : (nDims) ? &aoDimIds[0] : nullptr, &nVarId);
7682 17 : if (status != NC_NOERR)
7683 : {
7684 0 : CPLDebug("netCDF", "nc_def_var(%s) failed: %s",
7685 : pszVarName, nc_strerror(status));
7686 : }
7687 : else
7688 : {
7689 : #ifdef DEBUG_VERBOSE
7690 : CPLDebug("netCDF", "nc_def_var(%s) (%s) succeeded",
7691 : pszVarName, pszLine);
7692 : #endif
7693 17 : oMapVarToId[pszVarName] = nVarId;
7694 17 : oMapVarIdToType[nVarId] = nc_datatype;
7695 17 : oMapVarIdToVectorOfDimId[nVarId] = std::move(aoDimIds);
7696 : }
7697 : }
7698 : }
7699 120 : else if (pszEqual != nullptr && pszEqual - pszColumn > 0)
7700 : {
7701 116 : CPLString osVarName(pszLine, pszColumn - pszLine);
7702 116 : CPLString osAttrName(pszColumn + 1, pszEqual - pszColumn - 1);
7703 116 : osAttrName.Trim();
7704 116 : if (oMapVarToId.find(osVarName) == oMapVarToId.end())
7705 : {
7706 0 : CPLDebug("netCDF",
7707 : "nc_put_att(%s:%s) failed: "
7708 : "no corresponding variable",
7709 : osVarName.c_str(), osAttrName.c_str());
7710 0 : continue;
7711 : }
7712 116 : bool bValidName = true;
7713 1743 : for (size_t i = 0; i < osAttrName.size(); i++)
7714 : {
7715 1865 : if (!((osAttrName[i] >= 'a' && osAttrName[i] <= 'z') ||
7716 238 : (osAttrName[i] >= 'A' && osAttrName[i] <= 'Z') ||
7717 158 : (osAttrName[i] >= '0' && osAttrName[i] <= '9') ||
7718 158 : osAttrName[i] == '_'))
7719 : {
7720 0 : bValidName = false;
7721 : }
7722 : }
7723 116 : if (!bValidName)
7724 : {
7725 0 : CPLDebug(
7726 : "netCDF",
7727 : "nc_put_att(%s:%s) failed: illegal character found",
7728 : osVarName.c_str(), osAttrName.c_str());
7729 0 : continue;
7730 : }
7731 116 : if (oSetAttrDefined.find(osVarName + ":" + osAttrName) !=
7732 232 : oSetAttrDefined.end())
7733 : {
7734 0 : CPLDebug("netCDF",
7735 : "nc_put_att(%s:%s) failed: already defined",
7736 : osVarName.c_str(), osAttrName.c_str());
7737 0 : continue;
7738 : }
7739 :
7740 116 : const int nVarId = oMapVarToId[osVarName];
7741 116 : const char *pszValue = pszEqual + 1;
7742 232 : while (*pszValue == ' ')
7743 116 : pszValue++;
7744 :
7745 116 : status = NC_EBADTYPE;
7746 116 : if (*pszValue == '"')
7747 : {
7748 : // For _FillValue, the attribute type should match
7749 : // the variable type. Leaks memory with NC4 otherwise
7750 74 : if (osAttrName == "_FillValue")
7751 : {
7752 0 : CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
7753 : osVarName.c_str(), osAttrName.c_str(),
7754 : nc_strerror(status));
7755 0 : continue;
7756 : }
7757 :
7758 : // Unquote and unescape string value
7759 74 : CPLString osVal(pszValue + 1);
7760 222 : while (!osVal.empty())
7761 : {
7762 222 : if (osVal.back() == ';' || osVal.back() == ' ')
7763 : {
7764 148 : osVal.pop_back();
7765 : }
7766 74 : else if (osVal.back() == '"')
7767 : {
7768 74 : osVal.pop_back();
7769 74 : break;
7770 : }
7771 : else
7772 : {
7773 0 : break;
7774 : }
7775 : }
7776 74 : osVal.replaceAll("\\\"", '"');
7777 74 : status = nc_put_att_text(nCdfId, nVarId, osAttrName,
7778 : osVal.size(), osVal.c_str());
7779 : }
7780 : else
7781 : {
7782 84 : CPLString osVal(pszValue);
7783 126 : while (!osVal.empty())
7784 : {
7785 126 : if (osVal.back() == ';' || osVal.back() == ' ')
7786 : {
7787 84 : osVal.pop_back();
7788 : }
7789 : else
7790 : {
7791 42 : break;
7792 : }
7793 : }
7794 42 : int nc_datatype = -1;
7795 42 : if (!osVal.empty() && osVal.back() == 'b')
7796 : {
7797 3 : nc_datatype = NC_BYTE;
7798 3 : osVal.pop_back();
7799 : }
7800 39 : else if (!osVal.empty() && osVal.back() == 's')
7801 : {
7802 3 : nc_datatype = NC_SHORT;
7803 3 : osVal.pop_back();
7804 : }
7805 42 : if (CPLGetValueType(osVal) == CPL_VALUE_INTEGER)
7806 : {
7807 7 : if (nc_datatype < 0)
7808 4 : nc_datatype = NC_INT;
7809 : }
7810 35 : else if (CPLGetValueType(osVal) == CPL_VALUE_REAL)
7811 : {
7812 32 : nc_datatype = NC_DOUBLE;
7813 : }
7814 : else
7815 : {
7816 3 : nc_datatype = -1;
7817 : }
7818 :
7819 : // For _FillValue, check that the attribute type matches
7820 : // the variable type. Leaks memory with NC4 otherwise
7821 42 : if (osAttrName == "_FillValue")
7822 : {
7823 6 : if (nVarId < 0 ||
7824 3 : nc_datatype != oMapVarIdToType[nVarId])
7825 : {
7826 0 : nc_datatype = -1;
7827 : }
7828 : }
7829 :
7830 42 : if (nc_datatype == NC_BYTE)
7831 : {
7832 : signed char chVal =
7833 3 : static_cast<signed char>(atoi(osVal));
7834 3 : status = nc_put_att_schar(nCdfId, nVarId, osAttrName,
7835 : NC_BYTE, 1, &chVal);
7836 : }
7837 39 : else if (nc_datatype == NC_SHORT)
7838 : {
7839 0 : short nVal = static_cast<short>(atoi(osVal));
7840 0 : status = nc_put_att_short(nCdfId, nVarId, osAttrName,
7841 : NC_SHORT, 1, &nVal);
7842 : }
7843 39 : else if (nc_datatype == NC_INT)
7844 : {
7845 4 : int nVal = static_cast<int>(atoi(osVal));
7846 4 : status = nc_put_att_int(nCdfId, nVarId, osAttrName,
7847 : NC_INT, 1, &nVal);
7848 : }
7849 35 : else if (nc_datatype == NC_DOUBLE)
7850 : {
7851 32 : double dfVal = CPLAtof(osVal);
7852 32 : status = nc_put_att_double(nCdfId, nVarId, osAttrName,
7853 : NC_DOUBLE, 1, &dfVal);
7854 : }
7855 : }
7856 116 : if (status != NC_NOERR)
7857 : {
7858 3 : CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
7859 : osVarName.c_str(), osAttrName.c_str(),
7860 : nc_strerror(status));
7861 : }
7862 : else
7863 : {
7864 113 : oSetAttrDefined.insert(osVarName + ":" + osAttrName);
7865 : #ifdef DEBUG_VERBOSE
7866 : CPLDebug("netCDF", "nc_put_att_(%s:%s) (%s) succeeded",
7867 : osVarName.c_str(), osAttrName.c_str(), pszLine);
7868 : #endif
7869 : }
7870 : }
7871 : }
7872 42 : else if (nActiveSection == SECTION_DATA)
7873 : {
7874 55 : while (*pszLine == ' ' || *pszLine == '\t')
7875 17 : pszLine++;
7876 38 : const char *pszEqual = strchr(pszLine, '=');
7877 38 : if (pszEqual)
7878 : {
7879 17 : CPLString osVarName(pszLine, pszEqual - pszLine);
7880 17 : osVarName.Trim();
7881 17 : if (oMapVarToId.find(osVarName) == oMapVarToId.end())
7882 0 : continue;
7883 17 : const int nVarId = oMapVarToId[osVarName];
7884 17 : CPLString osAccVal(pszEqual + 1);
7885 17 : osAccVal.Trim();
7886 153 : while (osAccVal.empty() || osAccVal.back() != ';')
7887 : {
7888 136 : pszLine = CPLReadLineL(fpSrc);
7889 136 : if (pszLine == nullptr)
7890 0 : break;
7891 272 : CPLString osVal(pszLine);
7892 136 : osVal.Trim();
7893 136 : osAccVal += osVal;
7894 : }
7895 17 : if (pszLine == nullptr)
7896 0 : break;
7897 17 : osAccVal.pop_back();
7898 :
7899 : const std::vector<int> aoDimIds =
7900 34 : oMapVarIdToVectorOfDimId[nVarId];
7901 17 : size_t nSize = 1;
7902 34 : std::vector<size_t> aoStart, aoEdge;
7903 17 : aoStart.resize(aoDimIds.size());
7904 17 : aoEdge.resize(aoDimIds.size());
7905 35 : for (size_t i = 0; i < aoDimIds.size(); ++i)
7906 : {
7907 18 : const size_t nDimSize = oMapDimIdToDimLen[aoDimIds[i]];
7908 36 : if (nDimSize != 0 &&
7909 18 : nSize > std::numeric_limits<size_t>::max() / nDimSize)
7910 : {
7911 0 : nSize = 0;
7912 : }
7913 : else
7914 : {
7915 18 : nSize *= nDimSize;
7916 : }
7917 18 : aoStart[i] = 0;
7918 18 : aoEdge[i] = nDimSize;
7919 : }
7920 :
7921 17 : status = NC_EBADTYPE;
7922 17 : if (nSize == 0)
7923 : {
7924 : // Might happen with an unlimited dimension
7925 : }
7926 17 : else if (oMapVarIdToType[nVarId] == NC_DOUBLE)
7927 : {
7928 8 : if (!aoStart.empty())
7929 : {
7930 : const CPLStringList aosTokens(
7931 16 : CSLTokenizeString2(osAccVal, " ,;", 0));
7932 8 : size_t nTokens = aosTokens.size();
7933 8 : if (nTokens >= nSize)
7934 : {
7935 : double *padfVals = static_cast<double *>(
7936 8 : VSI_CALLOC_VERBOSE(nSize, sizeof(double)));
7937 8 : if (padfVals)
7938 : {
7939 132 : for (size_t i = 0; i < nSize; i++)
7940 : {
7941 124 : padfVals[i] = CPLAtof(aosTokens[i]);
7942 : }
7943 8 : status = nc_put_vara_double(
7944 8 : nCdfId, nVarId, &aoStart[0], &aoEdge[0],
7945 : padfVals);
7946 8 : VSIFree(padfVals);
7947 : }
7948 : }
7949 : }
7950 : }
7951 9 : else if (oMapVarIdToType[nVarId] == NC_BYTE)
7952 : {
7953 3 : if (!aoStart.empty())
7954 : {
7955 : const CPLStringList aosTokens(
7956 6 : CSLTokenizeString2(osAccVal, " ,;", 0));
7957 3 : size_t nTokens = aosTokens.size();
7958 3 : if (nTokens >= nSize)
7959 : {
7960 : signed char *panVals = static_cast<signed char *>(
7961 3 : VSI_CALLOC_VERBOSE(nSize, sizeof(signed char)));
7962 3 : if (panVals)
7963 : {
7964 1203 : for (size_t i = 0; i < nSize; i++)
7965 : {
7966 1200 : panVals[i] = static_cast<signed char>(
7967 1200 : atoi(aosTokens[i]));
7968 : }
7969 3 : status = nc_put_vara_schar(nCdfId, nVarId,
7970 3 : &aoStart[0],
7971 3 : &aoEdge[0], panVals);
7972 3 : VSIFree(panVals);
7973 : }
7974 : }
7975 : }
7976 : }
7977 6 : else if (oMapVarIdToType[nVarId] == NC_CHAR)
7978 : {
7979 6 : if (aoStart.size() == 2)
7980 : {
7981 4 : std::vector<CPLString> aoStrings;
7982 2 : bool bInString = false;
7983 4 : CPLString osCurString;
7984 935 : for (size_t i = 0; i < osAccVal.size();)
7985 : {
7986 933 : if (!bInString)
7987 : {
7988 8 : if (osAccVal[i] == '"')
7989 : {
7990 4 : bInString = true;
7991 4 : osCurString.clear();
7992 : }
7993 8 : i++;
7994 : }
7995 926 : else if (osAccVal[i] == '\\' &&
7996 926 : i + 1 < osAccVal.size() &&
7997 1 : osAccVal[i + 1] == '"')
7998 : {
7999 1 : osCurString += '"';
8000 1 : i += 2;
8001 : }
8002 924 : else if (osAccVal[i] == '"')
8003 : {
8004 4 : aoStrings.push_back(osCurString);
8005 4 : osCurString.clear();
8006 4 : bInString = false;
8007 4 : i++;
8008 : }
8009 : else
8010 : {
8011 920 : osCurString += osAccVal[i];
8012 920 : i++;
8013 : }
8014 : }
8015 2 : const size_t nRecords = oMapDimIdToDimLen[aoDimIds[0]];
8016 2 : const size_t nWidth = oMapDimIdToDimLen[aoDimIds[1]];
8017 2 : size_t nIters = aoStrings.size();
8018 2 : if (nIters > nRecords)
8019 0 : nIters = nRecords;
8020 6 : for (size_t i = 0; i < nIters; i++)
8021 : {
8022 : size_t anIndex[2];
8023 4 : anIndex[0] = i;
8024 4 : anIndex[1] = 0;
8025 : size_t anCount[2];
8026 4 : anCount[0] = 1;
8027 4 : anCount[1] = aoStrings[i].size();
8028 4 : if (anCount[1] > nWidth)
8029 0 : anCount[1] = nWidth;
8030 : status =
8031 4 : nc_put_vara_text(nCdfId, nVarId, anIndex,
8032 4 : anCount, aoStrings[i].c_str());
8033 4 : if (status != NC_NOERR)
8034 0 : break;
8035 : }
8036 : }
8037 : }
8038 17 : if (status != NC_NOERR)
8039 : {
8040 4 : CPLDebug("netCDF", "nc_put_var_(%s) failed: %s",
8041 : osVarName.c_str(), nc_strerror(status));
8042 : }
8043 : }
8044 : }
8045 : }
8046 :
8047 4 : GDAL_nc_close(nCdfId);
8048 4 : return true;
8049 : }
8050 :
8051 : #endif // ENABLE_NCDUMP
8052 :
8053 : /************************************************************************/
8054 : /* Open() */
8055 : /************************************************************************/
8056 :
8057 875 : GDALDataset *netCDFDataset::Open(GDALOpenInfo *poOpenInfo)
8058 :
8059 : {
8060 : #ifdef NCDF_DEBUG
8061 : CPLDebug("GDAL_netCDF", "\n=====\nOpen(), filename=[%s]",
8062 : poOpenInfo->pszFilename);
8063 : #endif
8064 :
8065 : // Does this appear to be a netcdf file?
8066 875 : NetCDFFormatEnum eTmpFormat = NCDF_FORMAT_NONE;
8067 875 : if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
8068 : {
8069 806 : eTmpFormat = netCDFIdentifyFormat(poOpenInfo, /* bCheckExt = */ true);
8070 : #ifdef NCDF_DEBUG
8071 : CPLDebug("GDAL_netCDF", "identified format %d", eTmpFormat);
8072 : #endif
8073 : // Note: not calling Identify() directly, because we want the file type.
8074 : // Only support NCDF_FORMAT* formats.
8075 806 : if (NCDF_FORMAT_NC == eTmpFormat || NCDF_FORMAT_NC2 == eTmpFormat ||
8076 2 : NCDF_FORMAT_NC4 == eTmpFormat || NCDF_FORMAT_NC4C == eTmpFormat)
8077 : {
8078 : // ok
8079 : }
8080 2 : else if (eTmpFormat == NCDF_FORMAT_HDF4 &&
8081 0 : poOpenInfo->IsSingleAllowedDriver("netCDF"))
8082 : {
8083 : // ok
8084 : }
8085 : else
8086 : {
8087 2 : return nullptr;
8088 : }
8089 : }
8090 : else
8091 : {
8092 : #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
8093 : // We don't necessarily want to catch bugs in libnetcdf ...
8094 : if (CPLGetConfigOption("DISABLE_OPEN_REAL_NETCDF_FILES", nullptr))
8095 : {
8096 : return nullptr;
8097 : }
8098 : #endif
8099 : }
8100 :
8101 873 : if (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER)
8102 : {
8103 320 : return OpenMultiDim(poOpenInfo);
8104 : }
8105 :
8106 1106 : CPLMutexHolderD(&hNCMutex);
8107 :
8108 553 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
8109 : // GDALDataset own mutex.
8110 553 : netCDFDataset *poDS = new netCDFDataset();
8111 553 : poDS->papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
8112 553 : CPLAcquireMutex(hNCMutex, 1000.0);
8113 :
8114 553 : poDS->SetDescription(poOpenInfo->pszFilename);
8115 :
8116 : // Check if filename start with NETCDF: tag.
8117 553 : bool bTreatAsSubdataset = false;
8118 1106 : CPLString osSubdatasetName;
8119 :
8120 : #ifdef ENABLE_NCDUMP
8121 553 : const char *pszHeader =
8122 : reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
8123 553 : if (poOpenInfo->fpL != nullptr && STARTS_WITH(pszHeader, "netcdf ") &&
8124 3 : strstr(pszHeader, "dimensions:") && strstr(pszHeader, "variables:"))
8125 : {
8126 : // By default create a temporary file that will be destroyed,
8127 : // unless NETCDF_TMP_FILE is defined. Can be useful to see which
8128 : // netCDF file has been generated from a potential fuzzed input.
8129 3 : poDS->osFilename = CPLGetConfigOption("NETCDF_TMP_FILE", "");
8130 3 : if (poDS->osFilename.empty())
8131 : {
8132 3 : poDS->bFileToDestroyAtClosing = true;
8133 3 : poDS->osFilename = CPLGenerateTempFilenameSafe("netcdf_tmp");
8134 : }
8135 3 : if (!netCDFDatasetCreateTempFile(eTmpFormat, poDS->osFilename,
8136 : poOpenInfo->fpL))
8137 : {
8138 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8139 : // deadlock with GDALDataset own mutex.
8140 0 : delete poDS;
8141 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8142 0 : return nullptr;
8143 : }
8144 3 : bTreatAsSubdataset = false;
8145 3 : poDS->eFormat = eTmpFormat;
8146 : }
8147 : else
8148 : #endif
8149 :
8150 550 : if (STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
8151 : {
8152 : GDALSubdatasetInfoH hInfo =
8153 69 : GDALGetSubdatasetInfo(poOpenInfo->pszFilename);
8154 69 : if (hInfo)
8155 : {
8156 69 : char *pszPath = GDALSubdatasetInfoGetPathComponent(hInfo);
8157 69 : poDS->osFilename = pszPath;
8158 69 : CPLFree(pszPath);
8159 :
8160 : char *pszSubdataset =
8161 69 : GDALSubdatasetInfoGetSubdatasetComponent(hInfo);
8162 69 : if (pszSubdataset && pszSubdataset[0] != '\0')
8163 : {
8164 69 : osSubdatasetName = pszSubdataset;
8165 69 : bTreatAsSubdataset = true;
8166 : }
8167 : else
8168 : {
8169 0 : osSubdatasetName = "";
8170 0 : bTreatAsSubdataset = false;
8171 : }
8172 69 : CPLFree(pszSubdataset);
8173 :
8174 69 : GDALDestroySubdatasetInfo(hInfo);
8175 : }
8176 :
8177 138 : if (!STARTS_WITH(poDS->osFilename, "http://") &&
8178 69 : !STARTS_WITH(poDS->osFilename, "https://"))
8179 : {
8180 : // Identify Format from real file, with bCheckExt=FALSE.
8181 : auto poOpenInfo2 = std::make_unique<GDALOpenInfo>(
8182 69 : poDS->osFilename.c_str(), GA_ReadOnly);
8183 69 : poDS->eFormat = netCDFIdentifyFormat(poOpenInfo2.get(),
8184 : /* bCheckExt = */ false);
8185 69 : if (NCDF_FORMAT_NONE == poDS->eFormat ||
8186 69 : NCDF_FORMAT_UNKNOWN == poDS->eFormat)
8187 : {
8188 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8189 : // deadlock with GDALDataset own mutex.
8190 0 : delete poDS;
8191 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8192 0 : return nullptr;
8193 : }
8194 : }
8195 : }
8196 : else
8197 : {
8198 481 : poDS->osFilename = poOpenInfo->pszFilename;
8199 481 : bTreatAsSubdataset = false;
8200 481 : poDS->eFormat = eTmpFormat;
8201 : }
8202 :
8203 : // Try opening the dataset.
8204 : #if defined(NCDF_DEBUG) && defined(ENABLE_UFFD)
8205 : CPLDebug("GDAL_netCDF", "calling nc_open_mem(%s)",
8206 : poDS->osFilename.c_str());
8207 : #elif defined(NCDF_DEBUG) && !defined(ENABLE_UFFD)
8208 : CPLDebug("GDAL_netCDF", "calling nc_open(%s)", poDS->osFilename.c_str());
8209 : #endif
8210 553 : int cdfid = -1;
8211 553 : const int nMode = ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE) != 0)
8212 : ? NC_WRITE
8213 : : NC_NOWRITE;
8214 1106 : CPLString osFilenameForNCOpen(poDS->osFilename);
8215 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
8216 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
8217 : {
8218 : char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
8219 : osFilenameForNCOpen = pszTemp;
8220 : CPLFree(pszTemp);
8221 : }
8222 : #endif
8223 553 : int status2 = -1;
8224 :
8225 : #ifdef ENABLE_UFFD
8226 553 : cpl_uffd_context *pCtx = nullptr;
8227 : #endif
8228 :
8229 568 : if (STARTS_WITH(osFilenameForNCOpen, "/vsimem/") &&
8230 15 : poOpenInfo->eAccess == GA_ReadOnly)
8231 : {
8232 15 : vsi_l_offset nLength = 0;
8233 15 : poDS->fpVSIMEM = VSIFOpenL(osFilenameForNCOpen, "rb");
8234 15 : if (poDS->fpVSIMEM)
8235 : {
8236 : // We assume that the file will not be modified. If it is, then
8237 : // pabyBuffer might become invalid.
8238 : GByte *pabyBuffer =
8239 15 : VSIGetMemFileBuffer(osFilenameForNCOpen, &nLength, false);
8240 15 : if (pabyBuffer)
8241 : {
8242 15 : status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen),
8243 : nMode, static_cast<size_t>(nLength),
8244 : pabyBuffer, &cdfid);
8245 : }
8246 : }
8247 : }
8248 : else
8249 : {
8250 : const bool bVsiFile =
8251 538 : !strncmp(osFilenameForNCOpen, "/vsi", strlen("/vsi"));
8252 : #ifdef ENABLE_UFFD
8253 538 : bool bReadOnly = (poOpenInfo->eAccess == GA_ReadOnly);
8254 538 : void *pVma = nullptr;
8255 538 : uint64_t nVmaSize = 0;
8256 :
8257 538 : if (bVsiFile)
8258 : {
8259 2 : if (bReadOnly)
8260 : {
8261 2 : if (CPLIsUserFaultMappingSupported())
8262 : {
8263 2 : pCtx = CPLCreateUserFaultMapping(osFilenameForNCOpen, &pVma,
8264 : &nVmaSize);
8265 : }
8266 : else
8267 : {
8268 0 : CPLError(CE_Failure, CPLE_AppDefined,
8269 : "Opening a /vsi file with the netCDF driver "
8270 : "requires Linux userfaultfd to be available. "
8271 : "If running from Docker, "
8272 : "--security-opt seccomp=unconfined might be "
8273 : "needed.%s",
8274 0 : ((poDS->eFormat == NCDF_FORMAT_NC4 ||
8275 0 : poDS->eFormat == NCDF_FORMAT_HDF5) &&
8276 0 : GDALGetDriverByName("HDF5"))
8277 : ? " Or you may set the GDAL_SKIP=netCDF "
8278 : "configuration option to force the use of "
8279 : "the HDF5 driver."
8280 : : "");
8281 : }
8282 : }
8283 : else
8284 : {
8285 0 : CPLError(CE_Failure, CPLE_AppDefined,
8286 : "Opening a /vsi file with the netCDF driver is only "
8287 : "supported in read-only mode");
8288 : }
8289 : }
8290 538 : if (pCtx != nullptr && pVma != nullptr && nVmaSize > 0)
8291 : {
8292 : // netCDF code, at least for netCDF 4.7.0, is confused by filenames
8293 : // like /vsicurl/http[s]://example.com/foo.nc, so just pass the
8294 : // final part
8295 2 : status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen), nMode,
8296 : static_cast<size_t>(nVmaSize), pVma, &cdfid);
8297 : }
8298 : else
8299 536 : status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
8300 : #else
8301 : if (bVsiFile)
8302 : {
8303 : CPLError(
8304 : CE_Failure, CPLE_AppDefined,
8305 : "Opening a /vsi file with the netCDF driver requires Linux "
8306 : "userfaultfd to be available.%s",
8307 : ((poDS->eFormat == NCDF_FORMAT_NC4 ||
8308 : poDS->eFormat == NCDF_FORMAT_HDF5) &&
8309 : GDALGetDriverByName("HDF5"))
8310 : ? " Or you may set the GDAL_SKIP=netCDF "
8311 : "configuration option to force the use of the HDF5 "
8312 : "driver."
8313 : : "");
8314 : status2 = NC_EIO;
8315 : }
8316 : else
8317 : {
8318 : status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
8319 : }
8320 : #endif
8321 : }
8322 553 : if (status2 != NC_NOERR)
8323 : {
8324 : #ifdef NCDF_DEBUG
8325 : CPLDebug("GDAL_netCDF", "error opening");
8326 : #endif
8327 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8328 : // with GDALDataset own mutex.
8329 0 : delete poDS;
8330 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8331 0 : return nullptr;
8332 : }
8333 : #ifdef NCDF_DEBUG
8334 : CPLDebug("GDAL_netCDF", "got cdfid=%d", cdfid);
8335 : #endif
8336 :
8337 : #if defined(ENABLE_NCDUMP) && !defined(_WIN32)
8338 : // Try to destroy the temporary file right now on Unix
8339 553 : if (poDS->bFileToDestroyAtClosing)
8340 : {
8341 3 : if (VSIUnlink(poDS->osFilename) == 0)
8342 : {
8343 3 : poDS->bFileToDestroyAtClosing = false;
8344 : }
8345 : }
8346 : #endif
8347 :
8348 : // Is this a real netCDF file?
8349 : int ndims;
8350 : int ngatts;
8351 : int nvars;
8352 : int unlimdimid;
8353 553 : int status = nc_inq(cdfid, &ndims, &nvars, &ngatts, &unlimdimid);
8354 553 : if (status != NC_NOERR)
8355 : {
8356 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8357 : // with GDALDataset own mutex.
8358 0 : delete poDS;
8359 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8360 0 : return nullptr;
8361 : }
8362 :
8363 : // Get file type from netcdf.
8364 553 : int nTmpFormat = NCDF_FORMAT_NONE;
8365 553 : status = nc_inq_format(cdfid, &nTmpFormat);
8366 553 : if (status != NC_NOERR)
8367 : {
8368 0 : NCDF_ERR(status);
8369 : }
8370 : else
8371 : {
8372 553 : CPLDebug("GDAL_netCDF",
8373 : "driver detected file type=%d, libnetcdf detected type=%d",
8374 553 : poDS->eFormat, nTmpFormat);
8375 553 : if (static_cast<NetCDFFormatEnum>(nTmpFormat) != poDS->eFormat)
8376 : {
8377 : // Warn if file detection conflicts with that from libnetcdf
8378 : // except for NC4C, which we have no way of detecting initially.
8379 26 : if (nTmpFormat != NCDF_FORMAT_NC4C &&
8380 13 : !STARTS_WITH(poDS->osFilename, "http://") &&
8381 0 : !STARTS_WITH(poDS->osFilename, "https://"))
8382 : {
8383 0 : CPLError(CE_Warning, CPLE_AppDefined,
8384 : "NetCDF driver detected file type=%d, but libnetcdf "
8385 : "detected type=%d",
8386 0 : poDS->eFormat, nTmpFormat);
8387 : }
8388 13 : CPLDebug("GDAL_netCDF", "setting file type to %d, was %d",
8389 13 : nTmpFormat, poDS->eFormat);
8390 13 : poDS->eFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
8391 : }
8392 : }
8393 :
8394 : // Does the request variable exist?
8395 553 : if (bTreatAsSubdataset)
8396 : {
8397 : int dummy;
8398 69 : if (NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &dummy,
8399 69 : &dummy) != CE_None)
8400 : {
8401 0 : CPLError(CE_Warning, CPLE_AppDefined,
8402 : "%s is a netCDF file, but %s is not a variable.",
8403 : poOpenInfo->pszFilename, osSubdatasetName.c_str());
8404 :
8405 0 : GDAL_nc_close(cdfid);
8406 : #ifdef ENABLE_UFFD
8407 0 : NETCDF_UFFD_UNMAP(pCtx);
8408 : #endif
8409 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8410 : // deadlock with GDALDataset own mutex.
8411 0 : delete poDS;
8412 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8413 0 : return nullptr;
8414 : }
8415 : }
8416 :
8417 : // Figure out whether or not the listed dataset has support for simple
8418 : // geometries (CF-1.8)
8419 553 : nccfdriver::getCFVersion(cdfid, poDS->nCFVersionMajor,
8420 553 : poDS->nCFVersionMinor);
8421 553 : bool bHasSimpleGeometries = false; // but not necessarily valid
8422 553 : if (poDS->nCFVersionMajor > 1 ||
8423 553 : (poDS->nCFVersionMajor == 1 && poDS->nCFVersionMinor >= 8))
8424 : {
8425 74 : bHasSimpleGeometries = poDS->DetectAndFillSGLayers(cdfid);
8426 74 : if (bHasSimpleGeometries)
8427 : {
8428 66 : poDS->bSGSupport = true;
8429 66 : poDS->vcdf.enableFullVirtualMode();
8430 : }
8431 : }
8432 :
8433 1106 : std::string osConventions;
8434 553 : if (NCDFGetAttr(cdfid, NC_GLOBAL, "Conventions", osConventions) != CE_None)
8435 : {
8436 60 : CPLDebug("GDAL_netCDF", "No UNIDATA NC_GLOBAL:Conventions attribute");
8437 : // Note that 'Conventions' is always capital 'C' in CF spec.
8438 : }
8439 :
8440 : // Create band information objects.
8441 553 : CPLDebug("GDAL_netCDF", "var_count = %d", nvars);
8442 :
8443 : // Create a corresponding GDALDataset.
8444 : // Create Netcdf Subdataset if filename as NETCDF tag.
8445 553 : poDS->cdfid = cdfid;
8446 : #ifdef ENABLE_UFFD
8447 553 : poDS->pCtx = pCtx;
8448 : #endif
8449 553 : poDS->eAccess = poOpenInfo->eAccess;
8450 553 : poDS->bDefineMode = false;
8451 :
8452 553 : poDS->ReadAttributes(cdfid, NC_GLOBAL);
8453 :
8454 : // Identify coordinate and boundary variables that we should
8455 : // ignore as Raster Bands.
8456 1106 : CPLStringList aosIgnoreVars;
8457 553 : NCDFGetCoordAndBoundVarFullNames(cdfid, aosIgnoreVars);
8458 : // Filter variables to keep only valid 2+D raster bands and vector fields.
8459 553 : int nRasterVars = 0;
8460 553 : int nIgnoredVars = 0;
8461 553 : int nGroupID = -1;
8462 553 : int nVarID = -1;
8463 :
8464 : std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>
8465 1106 : oMap2DDimsToGroupAndVar;
8466 1462 : if ((poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
8467 356 : STARTS_WITH(
8468 : poDS->aosMetadata.FetchNameValueDef("NC_GLOBAL#mission_name", ""),
8469 1 : "Sentinel 3") &&
8470 1 : EQUAL(poDS->aosMetadata.FetchNameValueDef(
8471 : "NC_GLOBAL#altimeter_sensor_name", ""),
8472 909 : "SRAL") &&
8473 1 : EQUAL(poDS->aosMetadata.FetchNameValueDef(
8474 : "NC_GLOBAL#radiometer_sensor_name", ""),
8475 : "MWR"))
8476 : {
8477 1 : if (poDS->eAccess == GA_Update)
8478 : {
8479 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8480 : // deadlock with GDALDataset own mutex.
8481 0 : delete poDS;
8482 0 : return nullptr;
8483 : }
8484 1 : poDS->ProcessSentinel3_SRAL_MWR();
8485 : }
8486 : else
8487 : {
8488 552 : poDS->FilterVars(cdfid, (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0,
8489 907 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
8490 355 : !bHasSimpleGeometries,
8491 : aosIgnoreVars, &nRasterVars, &nGroupID, &nVarID,
8492 : &nIgnoredVars, oMap2DDimsToGroupAndVar);
8493 : }
8494 :
8495 553 : const bool bListAllArrays = CPLTestBool(
8496 553 : CSLFetchNameValueDef(poDS->papszOpenOptions, "LIST_ALL_ARRAYS", "NO"));
8497 :
8498 : // Case where there is no raster variable
8499 553 : if (!bListAllArrays && nRasterVars == 0 && !bTreatAsSubdataset)
8500 : {
8501 138 : poDS->GDALPamDataset::SetMetadata(poDS->aosMetadata);
8502 138 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8503 : // with GDALDataset own mutex.
8504 138 : poDS->TryLoadXML();
8505 : // If the dataset has been opened in raster mode only, exit
8506 138 : if ((poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0 &&
8507 6 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) == 0)
8508 : {
8509 4 : delete poDS;
8510 4 : poDS = nullptr;
8511 : }
8512 : // Otherwise if the dataset is opened in vector mode, that there is
8513 : // no vector layer and we are in read-only, exit too.
8514 134 : else if (poDS->GetLayerCount() == 0 &&
8515 142 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
8516 8 : poOpenInfo->eAccess == GA_ReadOnly)
8517 : {
8518 8 : delete poDS;
8519 8 : poDS = nullptr;
8520 : }
8521 138 : CPLAcquireMutex(hNCMutex, 1000.0);
8522 138 : return poDS;
8523 : }
8524 :
8525 : // We have more than one variable with 2 dimensions in the
8526 : // file, then treat this as a subdataset container dataset.
8527 415 : bool bSeveralVariablesAsBands = false;
8528 415 : if (bListAllArrays || ((nRasterVars > 1) && !bTreatAsSubdataset))
8529 : {
8530 30 : if (CPLFetchBool(poOpenInfo->papszOpenOptions, "VARIABLES_AS_BANDS",
8531 36 : false) &&
8532 6 : oMap2DDimsToGroupAndVar.size() == 1)
8533 : {
8534 6 : std::tie(nGroupID, nVarID) =
8535 12 : oMap2DDimsToGroupAndVar.begin()->second.front();
8536 6 : bSeveralVariablesAsBands = true;
8537 : }
8538 : else
8539 : {
8540 24 : poDS->CreateSubDatasetList(cdfid);
8541 24 : poDS->GDALPamDataset::SetMetadata(poDS->aosMetadata);
8542 24 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8543 : // deadlock with GDALDataset own mutex.
8544 24 : poDS->TryLoadXML();
8545 24 : CPLAcquireMutex(hNCMutex, 1000.0);
8546 24 : return poDS;
8547 : }
8548 : }
8549 :
8550 : // If we are not treating things as a subdataset, then capture
8551 : // the name of the single available variable as the subdataset.
8552 391 : if (!bTreatAsSubdataset)
8553 : {
8554 322 : NCDF_ERR(NCDFGetVarFullName(nGroupID, nVarID, osSubdatasetName));
8555 : }
8556 :
8557 : // We have ignored at least one variable, so we should report them
8558 : // as subdatasets for reference.
8559 391 : if (nIgnoredVars > 0 && !bTreatAsSubdataset)
8560 : {
8561 29 : CPLDebug("GDAL_netCDF",
8562 : "As %d variables were ignored, creating subdataset list "
8563 : "for reference. Variable #%d [%s] is the main variable",
8564 : nIgnoredVars, nVarID, osSubdatasetName.c_str());
8565 29 : poDS->CreateSubDatasetList(cdfid);
8566 : }
8567 :
8568 : // Open the NETCDF subdataset NETCDF:"filename":subdataset.
8569 391 : int var = -1;
8570 391 : NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &nGroupID, &var);
8571 : // Now we can forget the root cdfid and only use the selected group.
8572 391 : cdfid = nGroupID;
8573 391 : int nd = 0;
8574 391 : nc_inq_varndims(cdfid, var, &nd);
8575 :
8576 391 : poDS->m_anDimIds.resize(nd);
8577 :
8578 : // X, Y, Z position in array
8579 782 : std::vector<int> anBandDimPos(nd);
8580 :
8581 391 : nc_inq_vardimid(cdfid, var, poDS->m_anDimIds.data());
8582 :
8583 : // Check if somebody tried to pass a variable with less than 1D.
8584 391 : if (nd < 1)
8585 : {
8586 0 : CPLError(CE_Warning, CPLE_AppDefined,
8587 : "Variable has %d dimension(s) - not supported.", nd);
8588 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8589 : // with GDALDataset own mutex.
8590 0 : delete poDS;
8591 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8592 0 : return nullptr;
8593 : }
8594 :
8595 : // CF-1 Convention
8596 : //
8597 : // Dimensions to appear in the relative order T, then Z, then Y,
8598 : // then X to the file. All other dimensions should, whenever
8599 : // possible, be placed to the left of the spatiotemporal
8600 : // dimensions.
8601 :
8602 : // Verify that dimensions are in the {T,Z,Y,X} or {T,Z,Y,X} order
8603 : // Ideally we should detect for other ordering and act accordingly
8604 : // Only done if file has Conventions=CF-* and only prints warning
8605 : // To disable set GDAL_NETCDF_VERIFY_DIMS=NO and to use only
8606 : // attributes (not varnames) set GDAL_NETCDF_VERIFY_DIMS=STRICT
8607 : const bool bCheckDims =
8608 782 : CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES")) &&
8609 391 : STARTS_WITH_CI(osConventions.c_str(), "CF");
8610 :
8611 391 : bool bYXBandOrder = false;
8612 391 : if (nd == 3)
8613 : {
8614 : // If there's a coordinates attributes, and the variable it points to
8615 : // are 2D variables indexed by the same first and second dimension than
8616 : // our variable of interest, then it is Y,X,Band order.
8617 50 : char *pszCoordinates = nullptr;
8618 50 : if (NCDFGetAttr(cdfid, var, "coordinates", &pszCoordinates) ==
8619 69 : CE_None &&
8620 19 : pszCoordinates)
8621 : {
8622 : const CPLStringList aosCoordinates(
8623 38 : NCDFTokenizeCoordinatesAttribute(pszCoordinates));
8624 19 : if (aosCoordinates.size() == 2)
8625 : {
8626 : // Test that each variable is longitude/latitude.
8627 13 : for (int i = 0; i < aosCoordinates.size(); i++)
8628 : {
8629 13 : if (NCDFIsVarLongitude(cdfid, -1, aosCoordinates[i]) ||
8630 4 : NCDFIsVarLatitude(cdfid, -1, aosCoordinates[i]))
8631 : {
8632 9 : int nOtherGroupId = -1;
8633 9 : int nOtherVarId = -1;
8634 9 : if (NCDFResolveVar(cdfid, aosCoordinates[i],
8635 : &nOtherGroupId,
8636 9 : &nOtherVarId) == CE_None)
8637 : {
8638 9 : int coordDimCount = 0;
8639 9 : nc_inq_varndims(nOtherGroupId, nOtherVarId,
8640 : &coordDimCount);
8641 9 : if (coordDimCount == 2)
8642 : {
8643 3 : int coordDimIds[2] = {0, 0};
8644 3 : nc_inq_vardimid(nOtherGroupId, nOtherVarId,
8645 : coordDimIds);
8646 4 : if (coordDimIds[0] == poDS->m_anDimIds[0] &&
8647 1 : coordDimIds[1] == poDS->m_anDimIds[1])
8648 : {
8649 1 : bYXBandOrder = true;
8650 1 : break;
8651 : }
8652 : }
8653 : }
8654 : }
8655 : }
8656 : }
8657 : }
8658 50 : CPLFree(pszCoordinates);
8659 :
8660 50 : if (!bYXBandOrder)
8661 : {
8662 49 : char szDim0Name[NC_MAX_NAME + 1] = {};
8663 49 : char szDim1Name[NC_MAX_NAME + 1] = {};
8664 49 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[0], szDim0Name);
8665 49 : NCDF_ERR(status);
8666 49 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[1], szDim1Name);
8667 49 : NCDF_ERR(status);
8668 :
8669 49 : if (strcmp(szDim0Name, "number_of_lines") == 0 &&
8670 1 : strcmp(szDim1Name, "pixels_per_line") == 0)
8671 : {
8672 : // Like in PACE OCI products
8673 1 : bYXBandOrder = true;
8674 : }
8675 : else
8676 : {
8677 : // For example for EMIT data (https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/),
8678 : // dimension order is downtrack, crosstrack, bands
8679 48 : char szDim2Name[NC_MAX_NAME + 1] = {};
8680 48 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[2], szDim2Name);
8681 48 : NCDF_ERR(status);
8682 94 : bYXBandOrder = strcmp(szDim2Name, "bands") == 0 ||
8683 46 : strcmp(szDim2Name, "band") == 0;
8684 : }
8685 : }
8686 : }
8687 :
8688 391 : if (nd >= 2 && bCheckDims && !bYXBandOrder)
8689 : {
8690 298 : char szDimName1[NC_MAX_NAME + 1] = {};
8691 298 : char szDimName2[NC_MAX_NAME + 1] = {};
8692 298 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 1], szDimName1);
8693 298 : NCDF_ERR(status);
8694 298 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 2], szDimName2);
8695 298 : NCDF_ERR(status);
8696 484 : if (NCDFIsVarLongitude(cdfid, -1, szDimName1) == false &&
8697 186 : NCDFIsVarProjectionX(cdfid, -1, szDimName1) == false)
8698 : {
8699 4 : CPLError(CE_Warning, CPLE_AppDefined,
8700 : "dimension #%d (%s) is not a Longitude/X dimension.",
8701 : nd - 1, szDimName1);
8702 : }
8703 484 : if (NCDFIsVarLatitude(cdfid, -1, szDimName2) == false &&
8704 186 : NCDFIsVarProjectionY(cdfid, -1, szDimName2) == false)
8705 : {
8706 4 : CPLError(CE_Warning, CPLE_AppDefined,
8707 : "dimension #%d (%s) is not a Latitude/Y dimension.",
8708 : nd - 2, szDimName2);
8709 : }
8710 298 : if ((NCDFIsVarLongitude(cdfid, -1, szDimName2) ||
8711 300 : NCDFIsVarProjectionX(cdfid, -1, szDimName2)) &&
8712 2 : (NCDFIsVarLatitude(cdfid, -1, szDimName1) ||
8713 0 : NCDFIsVarProjectionY(cdfid, -1, szDimName1)))
8714 : {
8715 2 : poDS->bSwitchedXY = true;
8716 : }
8717 298 : if (nd >= 3)
8718 : {
8719 55 : char szDimName3[NC_MAX_NAME + 1] = {};
8720 : status =
8721 55 : nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 3], szDimName3);
8722 55 : NCDF_ERR(status);
8723 55 : if (nd >= 4)
8724 : {
8725 13 : char szDimName4[NC_MAX_NAME + 1] = {};
8726 : status =
8727 13 : nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 4], szDimName4);
8728 13 : NCDF_ERR(status);
8729 13 : if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false)
8730 : {
8731 0 : CPLError(CE_Warning, CPLE_AppDefined,
8732 : "dimension #%d (%s) is not a Vertical dimension.",
8733 : nd - 3, szDimName3);
8734 : }
8735 13 : if (NCDFIsVarTimeCoord(cdfid, -1, szDimName4) == false)
8736 : {
8737 0 : CPLError(CE_Warning, CPLE_AppDefined,
8738 : "dimension #%d (%s) is not a Time dimension.",
8739 : nd - 4, szDimName4);
8740 : }
8741 : }
8742 : else
8743 : {
8744 81 : if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false &&
8745 39 : NCDFIsVarTimeCoord(cdfid, -1, szDimName3) == false)
8746 : {
8747 0 : CPLError(CE_Warning, CPLE_AppDefined,
8748 : "dimension #%d (%s) is not a "
8749 : "Time or Vertical dimension.",
8750 : nd - 3, szDimName3);
8751 : }
8752 : }
8753 : }
8754 : }
8755 :
8756 : // Get X dimensions information.
8757 : size_t xdim;
8758 391 : poDS->nXDimID = poDS->m_anDimIds[bYXBandOrder ? 1 : nd - 1];
8759 391 : nc_inq_dimlen(cdfid, poDS->nXDimID, &xdim);
8760 :
8761 : // Get Y dimension information.
8762 : size_t ydim;
8763 391 : if (nd >= 2)
8764 : {
8765 382 : poDS->nYDimID = poDS->m_anDimIds[bYXBandOrder ? 0 : nd - 2];
8766 382 : nc_inq_dimlen(cdfid, poDS->nYDimID, &ydim);
8767 : }
8768 : else
8769 : {
8770 9 : poDS->nYDimID = -1;
8771 9 : ydim = 1;
8772 : }
8773 :
8774 391 : if (xdim > INT_MAX || ydim > INT_MAX)
8775 : {
8776 0 : CPLError(CE_Failure, CPLE_AppDefined,
8777 : "Invalid raster dimensions: " CPL_FRMT_GUIB "x" CPL_FRMT_GUIB,
8778 : static_cast<GUIntBig>(xdim), static_cast<GUIntBig>(ydim));
8779 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8780 : // with GDALDataset own mutex.
8781 0 : delete poDS;
8782 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8783 0 : return nullptr;
8784 : }
8785 :
8786 391 : poDS->nRasterXSize = static_cast<int>(xdim);
8787 391 : poDS->nRasterYSize = static_cast<int>(ydim);
8788 :
8789 391 : unsigned int k = 0;
8790 1249 : for (int j = 0; j < nd; j++)
8791 : {
8792 858 : if (poDS->m_anDimIds[j] == poDS->nXDimID)
8793 : {
8794 391 : anBandDimPos[0] = j; // Save Position of XDim
8795 391 : k++;
8796 : }
8797 858 : if (poDS->m_anDimIds[j] == poDS->nYDimID)
8798 : {
8799 382 : anBandDimPos[1] = j; // Save Position of YDim
8800 382 : k++;
8801 : }
8802 : }
8803 : // X and Y Dimension Ids were not found!
8804 391 : if ((nd >= 2 && k != 2) || (nd == 1 && k != 1))
8805 : {
8806 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8807 : // with GDALDataset own mutex.
8808 0 : delete poDS;
8809 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8810 0 : return nullptr;
8811 : }
8812 :
8813 : // Read Metadata for this variable.
8814 :
8815 : // Should disable as is also done at band level, except driver needs the
8816 : // variables as metadata (e.g. projection).
8817 391 : poDS->ReadAttributes(cdfid, var);
8818 :
8819 : // Read Metadata for each dimension.
8820 391 : int *panDimIds = nullptr;
8821 391 : NCDFGetVisibleDims(cdfid, &ndims, &panDimIds);
8822 : // With NetCDF-4 groups panDimIds is not always [0..dim_count-1] like
8823 : // in NetCDF-3 because we see only the dimensions of the selected group
8824 : // and its parents.
8825 : // poDS->papszDimName is indexed by dim IDs, so it must contains all IDs
8826 : // [0..max(panDimIds)], but they are not all useful so we fill names
8827 : // of useless dims with empty string.
8828 391 : if (panDimIds)
8829 : {
8830 391 : const int nMaxDimId = *std::max_element(panDimIds, panDimIds + ndims);
8831 391 : std::set<int> oSetExistingDimIds;
8832 1297 : for (int i = 0; i < ndims; i++)
8833 : {
8834 906 : oSetExistingDimIds.insert(panDimIds[i]);
8835 : }
8836 391 : std::set<int> oSetDimIdsUsedByVar;
8837 1249 : for (int i = 0; i < nd; i++)
8838 : {
8839 858 : oSetDimIdsUsedByVar.insert(poDS->m_anDimIds[i]);
8840 : }
8841 1299 : for (int j = 0; j <= nMaxDimId; j++)
8842 : {
8843 : // Is j dim used?
8844 908 : if (oSetExistingDimIds.find(j) != oSetExistingDimIds.end())
8845 : {
8846 : // Useful dim.
8847 906 : char szTemp[NC_MAX_NAME + 1] = {};
8848 906 : status = nc_inq_dimname(cdfid, j, szTemp);
8849 906 : if (status != NC_NOERR)
8850 : {
8851 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8852 : // deadlock with GDALDataset own
8853 : // mutex.
8854 0 : delete poDS;
8855 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8856 0 : return nullptr;
8857 : }
8858 906 : poDS->papszDimName.AddString(szTemp);
8859 :
8860 906 : if (oSetDimIdsUsedByVar.find(j) != oSetDimIdsUsedByVar.end())
8861 : {
8862 858 : int nDimGroupId = -1;
8863 858 : int nDimVarId = -1;
8864 858 : if (NCDFResolveVar(cdfid, poDS->papszDimName[j],
8865 858 : &nDimGroupId, &nDimVarId) == CE_None)
8866 : {
8867 629 : poDS->ReadAttributes(nDimGroupId, nDimVarId);
8868 : }
8869 : }
8870 : }
8871 : else
8872 : {
8873 : // Useless dim.
8874 2 : poDS->papszDimName.AddString("");
8875 : }
8876 : }
8877 391 : CPLFree(panDimIds);
8878 : }
8879 :
8880 : // Set projection info.
8881 782 : std::vector<std::string> aosRemovedMDItems;
8882 391 : if (nd > 1)
8883 : {
8884 382 : poDS->SetProjectionFromVar(cdfid, var,
8885 : /*bReadSRSOnly=*/false,
8886 : /* pszGivenGM = */ nullptr,
8887 : /* returnProjStr = */ nullptr,
8888 : /* sg = */ nullptr, &aosRemovedMDItems);
8889 : }
8890 :
8891 : // Override bottom-up with GDAL_NETCDF_BOTTOMUP config option.
8892 391 : const char *pszValue = CPLGetConfigOption("GDAL_NETCDF_BOTTOMUP", nullptr);
8893 391 : if (pszValue)
8894 : {
8895 24 : poDS->bBottomUp = CPLTestBool(pszValue);
8896 24 : CPLDebug("GDAL_netCDF",
8897 : "set bBottomUp=%d because GDAL_NETCDF_BOTTOMUP=%s",
8898 24 : static_cast<int>(poDS->bBottomUp), pszValue);
8899 : }
8900 :
8901 : // Save non-spatial dimension info.
8902 :
8903 391 : int *panBandZLev = nullptr;
8904 391 : int nDim = (nd >= 2) ? 2 : 1;
8905 : size_t lev_count;
8906 391 : size_t nTotLevCount = 1;
8907 391 : nc_type nType = NC_NAT;
8908 :
8909 391 : if (nd > 2)
8910 : {
8911 66 : nDim = 2;
8912 66 : panBandZLev = static_cast<int *>(CPLCalloc(nd - 2, sizeof(int)));
8913 :
8914 132 : CPLString osExtraDimNames = "{";
8915 :
8916 66 : char szDimName[NC_MAX_NAME + 1] = {};
8917 :
8918 66 : bool bREPORT_EXTRA_DIM_VALUESWarningEmitted = false;
8919 283 : for (int j = 0; j < nd; j++)
8920 : {
8921 368 : if ((poDS->m_anDimIds[j] != poDS->nXDimID) &&
8922 151 : (poDS->m_anDimIds[j] != poDS->nYDimID))
8923 : {
8924 85 : nc_inq_dimlen(cdfid, poDS->m_anDimIds[j], &lev_count);
8925 85 : nTotLevCount *= lev_count;
8926 85 : panBandZLev[nDim - 2] = static_cast<int>(lev_count);
8927 85 : anBandDimPos[nDim] = j; // Save Position of ZDim
8928 : // Save non-spatial dimension names.
8929 85 : if (nc_inq_dimname(cdfid, poDS->m_anDimIds[j], szDimName) ==
8930 : NC_NOERR)
8931 : {
8932 85 : osExtraDimNames += szDimName;
8933 85 : if (j < nd - 3)
8934 : {
8935 19 : osExtraDimNames += ",";
8936 : }
8937 :
8938 85 : int nIdxGroupID = -1;
8939 85 : int nIdxVarID = Get1DVariableIndexedByDimension(
8940 85 : cdfid, poDS->m_anDimIds[j], szDimName, true,
8941 85 : &nIdxGroupID);
8942 85 : poDS->m_anExtraDimGroupIds.push_back(nIdxGroupID);
8943 85 : poDS->m_anExtraDimVarIds.push_back(nIdxVarID);
8944 :
8945 85 : if (nIdxVarID >= 0)
8946 : {
8947 76 : nc_inq_vartype(nIdxGroupID, nIdxVarID, &nType);
8948 : char szExtraDimDef[NC_MAX_NAME + 1];
8949 76 : snprintf(szExtraDimDef, sizeof(szExtraDimDef),
8950 : "{%ld,%d}", (long)lev_count, nType);
8951 : char szTemp[NC_MAX_NAME + 32 + 1];
8952 76 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
8953 : szDimName);
8954 76 : poDS->aosMetadata.SetNameValue(szTemp, szExtraDimDef);
8955 :
8956 : // Retrieving data for unlimited dimensions might be
8957 : // costly on network storage, so don't do it.
8958 : // Each band will capture the value along the extra
8959 : // dimension in its NETCDF_DIM_xxxx band metadata item
8960 : // Addresses use case of
8961 : // https://lists.osgeo.org/pipermail/gdal-dev/2023-May/057209.html
8962 : const bool bIsLocal =
8963 76 : VSIIsLocal(osFilenameForNCOpen.c_str());
8964 : bool bListDimValues =
8965 77 : bIsLocal || lev_count == 1 ||
8966 1 : !NCDFIsUnlimitedDim(poDS->eFormat ==
8967 : NCDF_FORMAT_NC4,
8968 1 : cdfid, poDS->m_anDimIds[j]);
8969 : const char *pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES =
8970 76 : CPLGetConfigOption(
8971 : "GDAL_NETCDF_REPORT_EXTRA_DIM_VALUES", nullptr);
8972 76 : if (pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES)
8973 : {
8974 2 : bListDimValues = CPLTestBool(
8975 : pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES);
8976 : }
8977 74 : else if (!bListDimValues && !bIsLocal &&
8978 1 : !bREPORT_EXTRA_DIM_VALUESWarningEmitted)
8979 : {
8980 1 : bREPORT_EXTRA_DIM_VALUESWarningEmitted = true;
8981 1 : CPLDebug(
8982 : "GDAL_netCDF",
8983 : "Listing extra dimension values is skipped "
8984 : "because this dataset is hosted on a network "
8985 : "file system, and such an operation could be "
8986 : "slow. If you still want to proceed, set the "
8987 : "GDAL_NETCDF_REPORT_EXTRA_DIM_VALUES "
8988 : "configuration option to YES");
8989 : }
8990 76 : if (bListDimValues)
8991 : {
8992 74 : char *pszTemp = nullptr;
8993 74 : if (NCDFGet1DVar(nIdxGroupID, nIdxVarID,
8994 74 : &pszTemp) == CE_None)
8995 : {
8996 74 : snprintf(szTemp, sizeof(szTemp),
8997 : "NETCDF_DIM_%s_VALUES", szDimName);
8998 74 : poDS->aosMetadata.SetNameValue(szTemp, pszTemp);
8999 74 : CPLFree(pszTemp);
9000 : }
9001 : }
9002 : }
9003 : }
9004 : else
9005 : {
9006 0 : poDS->m_anExtraDimGroupIds.push_back(-1);
9007 0 : poDS->m_anExtraDimVarIds.push_back(-1);
9008 : }
9009 :
9010 85 : nDim++;
9011 : }
9012 : }
9013 66 : osExtraDimNames += "}";
9014 66 : poDS->aosMetadata.SetNameValue("NETCDF_DIM_EXTRA", osExtraDimNames);
9015 : }
9016 :
9017 : // Store Metadata.
9018 401 : for (const auto &osStr : aosRemovedMDItems)
9019 10 : poDS->aosMetadata.SetNameValue(osStr.c_str(), nullptr);
9020 :
9021 391 : poDS->GDALPamDataset::SetMetadata(poDS->aosMetadata);
9022 :
9023 : // Create bands.
9024 :
9025 : // Arbitrary threshold.
9026 : int nMaxBandCount =
9027 391 : atoi(CPLGetConfigOption("GDAL_MAX_BAND_COUNT", "32768"));
9028 391 : if (nMaxBandCount <= 0)
9029 0 : nMaxBandCount = 32768;
9030 391 : if (nTotLevCount > static_cast<unsigned int>(nMaxBandCount))
9031 : {
9032 0 : CPLError(CE_Warning, CPLE_AppDefined,
9033 : "Limiting number of bands to %d instead of %u", nMaxBandCount,
9034 : static_cast<unsigned int>(nTotLevCount));
9035 0 : nTotLevCount = static_cast<unsigned int>(nMaxBandCount);
9036 : }
9037 391 : if (poDS->nRasterXSize == 0 || poDS->nRasterYSize == 0)
9038 : {
9039 0 : poDS->nRasterXSize = 0;
9040 0 : poDS->nRasterYSize = 0;
9041 0 : nTotLevCount = 0;
9042 0 : if (poDS->GetLayerCount() == 0)
9043 : {
9044 0 : CPLFree(panBandZLev);
9045 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9046 : // deadlock with GDALDataset own mutex.
9047 0 : delete poDS;
9048 0 : CPLAcquireMutex(hNCMutex, 1000.0);
9049 0 : return nullptr;
9050 : }
9051 : }
9052 391 : if (bSeveralVariablesAsBands)
9053 : {
9054 6 : const auto &listVariables = oMap2DDimsToGroupAndVar.begin()->second;
9055 24 : for (int iBand = 0; iBand < static_cast<int>(listVariables.size());
9056 : ++iBand)
9057 : {
9058 18 : int bandVarGroupId = listVariables[iBand].first;
9059 18 : int bandVarId = listVariables[iBand].second;
9060 : netCDFRasterBand *poBand = new netCDFRasterBand(
9061 0 : netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, bandVarGroupId,
9062 18 : bandVarId, nDim, 0, nullptr, anBandDimPos.data(), iBand + 1);
9063 18 : poDS->SetBand(iBand + 1, poBand);
9064 : }
9065 : }
9066 : else
9067 : {
9068 886 : for (unsigned int lev = 0; lev < nTotLevCount; lev++)
9069 : {
9070 : netCDFRasterBand *poBand = new netCDFRasterBand(
9071 0 : netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, cdfid, var, nDim,
9072 501 : lev, panBandZLev, anBandDimPos.data(), lev + 1);
9073 501 : poDS->SetBand(lev + 1, poBand);
9074 : }
9075 : }
9076 :
9077 391 : if (panBandZLev)
9078 66 : CPLFree(panBandZLev);
9079 : // Handle angular geographic coordinates here
9080 :
9081 : // Initialize any PAM information.
9082 391 : if (bTreatAsSubdataset)
9083 : {
9084 69 : poDS->SetPhysicalFilename(poDS->osFilename);
9085 69 : poDS->SetSubdatasetName(osSubdatasetName);
9086 : }
9087 :
9088 391 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
9089 : // GDALDataset own mutex.
9090 391 : poDS->TryLoadXML();
9091 :
9092 391 : if (bTreatAsSubdataset)
9093 69 : poDS->oOvManager.Initialize(poDS, ":::VIRTUAL:::");
9094 : else
9095 322 : poDS->oOvManager.Initialize(poDS, poDS->osFilename);
9096 :
9097 391 : CPLAcquireMutex(hNCMutex, 1000.0);
9098 :
9099 391 : return poDS;
9100 : }
9101 :
9102 : /************************************************************************/
9103 : /* CopyMetadata() */
9104 : /* */
9105 : /* Create a copy of metadata for NC_GLOBAL or a variable */
9106 : /************************************************************************/
9107 :
9108 182 : static void CopyMetadata(GDALDataset *poSrcDS, GDALRasterBand *poSrcBand,
9109 : GDALRasterBand *poDstBand, int nCdfId, int CDFVarID,
9110 : const char *pszPrefix)
9111 : {
9112 : // Remove the following band meta but set them later from band data.
9113 182 : const char *const papszIgnoreBand[] = {
9114 : CF_ADD_OFFSET, CF_SCALE_FACTOR, "valid_range", "_Unsigned",
9115 : NCDF_FillValue, "coordinates", nullptr};
9116 182 : const char *const papszIgnoreGlobal[] = {"NETCDF_DIM_EXTRA", nullptr};
9117 :
9118 182 : CSLConstList papszMetadata = nullptr;
9119 182 : if (poSrcDS)
9120 : {
9121 79 : papszMetadata = poSrcDS->GetMetadata();
9122 : }
9123 103 : else if (poSrcBand)
9124 : {
9125 103 : papszMetadata = poSrcBand->GetMetadata();
9126 : }
9127 :
9128 976 : for (const auto &[pszKey, pszValue] : cpl::IterateNameValue(papszMetadata))
9129 : {
9130 : #ifdef NCDF_DEBUG
9131 : CPLDebug("GDAL_netCDF", "copy metadata [%s]=[%s]", pszKey, pszValue);
9132 : #endif
9133 :
9134 794 : CPLString osMetaName(pszKey);
9135 :
9136 : // Check for items that match pszPrefix if applicable.
9137 794 : if (pszPrefix && !EQUAL(pszPrefix, ""))
9138 : {
9139 : // Remove prefix.
9140 171 : if (STARTS_WITH(osMetaName.c_str(), pszPrefix))
9141 : {
9142 21 : osMetaName = osMetaName.substr(strlen(pszPrefix));
9143 : }
9144 : // Only copy items that match prefix.
9145 : else
9146 : {
9147 150 : continue;
9148 : }
9149 : }
9150 :
9151 : // Fix various issues with metadata translation.
9152 644 : if (CDFVarID == NC_GLOBAL)
9153 : {
9154 : // Do not copy items in papszIgnoreGlobal and NETCDF_DIM_*.
9155 874 : if ((CSLFindString(papszIgnoreGlobal, osMetaName) != -1) ||
9156 434 : (STARTS_WITH(osMetaName, "NETCDF_DIM_")))
9157 24 : continue;
9158 : // Remove NC_GLOBAL prefix for netcdf global Metadata.
9159 416 : else if (STARTS_WITH(osMetaName, "NC_GLOBAL#"))
9160 : {
9161 59 : osMetaName = osMetaName.substr(strlen("NC_GLOBAL#"));
9162 : }
9163 : // GDAL Metadata renamed as GDAL-[meta].
9164 357 : else if (strstr(osMetaName, "#") == nullptr)
9165 : {
9166 22 : osMetaName = "GDAL_" + osMetaName;
9167 : }
9168 : // Keep time, lev and depth information for safe-keeping.
9169 : // Time and vertical coordinate handling need improvements.
9170 : /*
9171 : else if( STARTS_WITH(szMetaName, "time#") )
9172 : {
9173 : szMetaName[4] = '-';
9174 : }
9175 : else if( STARTS_WITH(szMetaName, "lev#") )
9176 : {
9177 : szMetaName[3] = '-';
9178 : }
9179 : else if( STARTS_WITH(szMetaName, "depth#") )
9180 : {
9181 : szMetaName[5] = '-';
9182 : }
9183 : */
9184 : // Only copy data without # (previously all data was copied).
9185 416 : if (strstr(osMetaName, "#") != nullptr)
9186 335 : continue;
9187 : // netCDF attributes do not like the '#' character.
9188 : // for( unsigned int h=0; h < strlen(szMetaName) -1 ; h++ ) {
9189 : // if( szMetaName[h] == '#') szMetaName[h] = '-';
9190 : // }
9191 : }
9192 : else
9193 : {
9194 : // Do not copy varname, stats, NETCDF_DIM_*, nodata
9195 : // and items in papszIgnoreBand.
9196 204 : if (STARTS_WITH(osMetaName, "NETCDF_VARNAME") ||
9197 166 : STARTS_WITH(osMetaName, "STATISTICS_") ||
9198 166 : STARTS_WITH(osMetaName, "NETCDF_DIM_") ||
9199 132 : STARTS_WITH(osMetaName, "missing_value") ||
9200 474 : STARTS_WITH(osMetaName, "_FillValue") ||
9201 104 : CSLFindString(papszIgnoreBand, osMetaName) != -1)
9202 116 : continue;
9203 : }
9204 :
9205 : #ifdef NCDF_DEBUG
9206 : CPLDebug("GDAL_netCDF", "copy name=[%s] value=[%s]", osMetaName.c_str(),
9207 : pszValue);
9208 : #endif
9209 169 : if (NCDFPutAttr(nCdfId, CDFVarID, osMetaName, pszValue) != CE_None)
9210 : {
9211 0 : CPLDebug("GDAL_netCDF", "NCDFPutAttr(%d, %d, %s, %s) failed",
9212 : nCdfId, CDFVarID, osMetaName.c_str(), pszValue);
9213 : }
9214 : }
9215 :
9216 : // Set add_offset and scale_factor here if present.
9217 182 : if (poSrcBand && poDstBand)
9218 : {
9219 :
9220 103 : int bGotAddOffset = FALSE;
9221 103 : const double dfAddOffset = poSrcBand->GetOffset(&bGotAddOffset);
9222 103 : int bGotScale = FALSE;
9223 103 : const double dfScale = poSrcBand->GetScale(&bGotScale);
9224 :
9225 103 : if (bGotAddOffset && dfAddOffset != 0.0)
9226 6 : poDstBand->SetOffset(dfAddOffset);
9227 103 : if (bGotScale && dfScale != 1.0)
9228 1 : poDstBand->SetScale(dfScale);
9229 : }
9230 182 : }
9231 :
9232 : /************************************************************************/
9233 : /* CreateLL() */
9234 : /* */
9235 : /* Shared functionality between netCDFDataset::Create() and */
9236 : /* netCDF::CreateCopy() for creating netcdf file based on a set of */
9237 : /* options and a configuration. */
9238 : /************************************************************************/
9239 :
9240 232 : netCDFDataset *netCDFDataset::CreateLL(const char *pszFilename, int nXSize,
9241 : int nYSize, int nBandsIn,
9242 : CSLConstList papszOptions)
9243 : {
9244 232 : if (!((nXSize == 0 && nYSize == 0 && nBandsIn == 0) ||
9245 137 : (nXSize > 0 && nYSize > 0 && nBandsIn > 0)))
9246 : {
9247 1 : return nullptr;
9248 : }
9249 :
9250 231 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
9251 : // GDALDataset own mutex.
9252 231 : netCDFDataset *poDS = new netCDFDataset();
9253 231 : CPLAcquireMutex(hNCMutex, 1000.0);
9254 :
9255 231 : poDS->nRasterXSize = nXSize;
9256 231 : poDS->nRasterYSize = nYSize;
9257 231 : poDS->eAccess = GA_Update;
9258 231 : poDS->osFilename = pszFilename;
9259 :
9260 : // From gtiff driver, is this ok?
9261 : /*
9262 : poDS->nBlockXSize = nXSize;
9263 : poDS->nBlockYSize = 1;
9264 : poDS->nBlocksPerBand =
9265 : DIV_ROUND_UP((nYSize, poDS->nBlockYSize))
9266 : * DIV_ROUND_UP((nXSize, poDS->nBlockXSize));
9267 : */
9268 :
9269 : // process options.
9270 231 : poDS->aosCreationOptions = CSLDuplicate(papszOptions);
9271 231 : if (!poDS->ProcessCreationOptions())
9272 : {
9273 23 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9274 : // deadlock with GDALDataset own
9275 : // mutex.
9276 23 : delete poDS;
9277 23 : CPLAcquireMutex(hNCMutex, 1000.0);
9278 23 : return nullptr;
9279 : }
9280 :
9281 208 : if (poDS->eMultipleLayerBehavior == SEPARATE_FILES)
9282 : {
9283 : VSIStatBuf sStat;
9284 3 : if (VSIStat(pszFilename, &sStat) == 0)
9285 : {
9286 0 : if (!VSI_ISDIR(sStat.st_mode))
9287 : {
9288 0 : CPLError(CE_Failure, CPLE_FileIO,
9289 : "%s is an existing file, but not a directory",
9290 : pszFilename);
9291 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9292 : // deadlock with GDALDataset own
9293 : // mutex.
9294 0 : delete poDS;
9295 0 : CPLAcquireMutex(hNCMutex, 1000.0);
9296 0 : return nullptr;
9297 : }
9298 : }
9299 3 : else if (VSIMkdir(pszFilename, 0755) != 0)
9300 : {
9301 1 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s directory",
9302 : pszFilename);
9303 1 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9304 : // deadlock with GDALDataset own mutex.
9305 1 : delete poDS;
9306 1 : CPLAcquireMutex(hNCMutex, 1000.0);
9307 1 : return nullptr;
9308 : }
9309 :
9310 2 : return poDS;
9311 : }
9312 : // Create the dataset.
9313 410 : CPLString osFilenameForNCCreate(pszFilename);
9314 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
9315 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
9316 : {
9317 : char *pszTemp =
9318 : CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
9319 : osFilenameForNCCreate = pszTemp;
9320 : CPLFree(pszTemp);
9321 : }
9322 : #endif
9323 :
9324 : #if defined(_WIN32)
9325 : {
9326 : // Works around bug of msys2 netCDF 4.9.0 package where nc_create()
9327 : // crashes
9328 : VSIStatBuf sStat;
9329 : const std::string osDirname =
9330 : CPLGetDirnameSafe(osFilenameForNCCreate.c_str());
9331 : if (VSIStat(osDirname.c_str(), &sStat) != 0)
9332 : {
9333 : CPLError(CE_Failure, CPLE_OpenFailed,
9334 : "Unable to create netCDF file %s: non existing output "
9335 : "directory",
9336 : pszFilename);
9337 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9338 : // deadlock with GDALDataset own mutex.
9339 : delete poDS;
9340 : CPLAcquireMutex(hNCMutex, 1000.0);
9341 : return nullptr;
9342 : }
9343 : }
9344 : #endif
9345 :
9346 : int status =
9347 205 : nc_create(osFilenameForNCCreate, poDS->nCreateMode, &(poDS->cdfid));
9348 :
9349 : // Put into define mode.
9350 205 : poDS->SetDefineMode(true);
9351 :
9352 205 : if (status != NC_NOERR)
9353 : {
9354 29 : CPLError(CE_Failure, CPLE_OpenFailed,
9355 : "Unable to create netCDF file %s (Error code %d): %s .",
9356 : pszFilename, status, nc_strerror(status));
9357 29 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
9358 : // with GDALDataset own mutex.
9359 29 : delete poDS;
9360 29 : CPLAcquireMutex(hNCMutex, 1000.0);
9361 29 : return nullptr;
9362 : }
9363 :
9364 : // Define dimensions.
9365 176 : if (nXSize > 0 && nYSize > 0)
9366 : {
9367 124 : poDS->papszDimName.AddString(NCDF_DIMNAME_X);
9368 : status =
9369 124 : nc_def_dim(poDS->cdfid, NCDF_DIMNAME_X, nXSize, &(poDS->nXDimID));
9370 124 : NCDF_ERR(status);
9371 124 : CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
9372 : poDS->cdfid, NCDF_DIMNAME_X, nXSize, poDS->nXDimID);
9373 :
9374 124 : poDS->papszDimName.AddString(NCDF_DIMNAME_Y);
9375 : status =
9376 124 : nc_def_dim(poDS->cdfid, NCDF_DIMNAME_Y, nYSize, &(poDS->nYDimID));
9377 124 : NCDF_ERR(status);
9378 124 : CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
9379 : poDS->cdfid, NCDF_DIMNAME_Y, nYSize, poDS->nYDimID);
9380 : }
9381 :
9382 176 : return poDS;
9383 : }
9384 :
9385 : /************************************************************************/
9386 : /* Create() */
9387 : /************************************************************************/
9388 :
9389 149 : GDALDataset *netCDFDataset::Create(const char *pszFilename, int nXSize,
9390 : int nYSize, int nBandsIn, GDALDataType eType,
9391 : CSLConstList papszOptions)
9392 : {
9393 149 : CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::Create(%s, ...)",
9394 : pszFilename);
9395 :
9396 : const char *legacyCreationOp =
9397 149 : CSLFetchNameValueDef(papszOptions, "GEOMETRY_ENCODING", "CF_1.8");
9398 298 : std::string legacyCreationOp_s = std::string(legacyCreationOp);
9399 :
9400 : // Check legacy creation op FIRST
9401 :
9402 149 : bool legacyCreateMode = false;
9403 :
9404 149 : if (nXSize != 0 || nYSize != 0 || nBandsIn != 0)
9405 : {
9406 56 : legacyCreateMode = true;
9407 : }
9408 93 : else if (legacyCreationOp_s == "CF_1.8")
9409 : {
9410 76 : legacyCreateMode = false;
9411 : }
9412 :
9413 17 : else if (legacyCreationOp_s == "WKT")
9414 : {
9415 17 : legacyCreateMode = true;
9416 : }
9417 :
9418 : else
9419 : {
9420 0 : CPLError(
9421 : CE_Failure, CPLE_NotSupported,
9422 : "Dataset creation option GEOMETRY_ENCODING=%s is not supported.",
9423 : legacyCreationOp_s.c_str());
9424 0 : return nullptr;
9425 : }
9426 :
9427 298 : CPLStringList aosOptions(CSLDuplicate(papszOptions));
9428 284 : if (aosOptions.FetchNameValue("FORMAT") == nullptr &&
9429 135 : (eType == GDT_UInt16 || eType == GDT_UInt32 || eType == GDT_UInt64 ||
9430 : eType == GDT_Int64))
9431 : {
9432 10 : CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
9433 10 : aosOptions.SetNameValue("FORMAT", "NC4");
9434 : }
9435 :
9436 298 : CPLStringList aosBandNames;
9437 149 : if (const char *pszBandNames = aosOptions.FetchNameValue("BAND_NAMES"))
9438 : {
9439 : aosBandNames =
9440 2 : CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);
9441 :
9442 2 : if (aosBandNames.Count() != nBandsIn)
9443 : {
9444 1 : CPLError(CE_Failure, CPLE_OpenFailed,
9445 : "Attempted to create netCDF with %d bands but %d names "
9446 : "provided in BAND_NAMES.",
9447 : nBandsIn, aosBandNames.Count());
9448 :
9449 1 : return nullptr;
9450 : }
9451 : }
9452 :
9453 296 : CPLMutexHolderD(&hNCMutex);
9454 :
9455 148 : auto poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize, nBandsIn,
9456 148 : aosOptions.List());
9457 :
9458 148 : if (!poDS)
9459 42 : return nullptr;
9460 :
9461 106 : if (!legacyCreateMode)
9462 : {
9463 36 : poDS->bSGSupport = true;
9464 36 : poDS->vcdf.enableFullVirtualMode();
9465 : }
9466 :
9467 : else
9468 : {
9469 70 : poDS->bSGSupport = false;
9470 : }
9471 :
9472 : // Should we write signed or unsigned byte?
9473 : // TODO should this only be done in Create()
9474 106 : poDS->bSignedData = true;
9475 106 : const char *pszValue = CSLFetchNameValueDef(papszOptions, "PIXELTYPE", "");
9476 106 : if (eType == GDT_UInt8 && !EQUAL(pszValue, "SIGNEDBYTE"))
9477 15 : poDS->bSignedData = false;
9478 :
9479 : // Add Conventions, GDAL info and history.
9480 106 : if (poDS->cdfid >= 0)
9481 : {
9482 : const char *CF_Vector_Conv =
9483 172 : poDS->bSGSupport ||
9484 : // Use of variable length strings require CF-1.8
9485 68 : EQUAL(aosOptions.FetchNameValueDef("FORMAT", ""), "NC4")
9486 : ? NCDF_CONVENTIONS_CF_V1_8
9487 172 : : NCDF_CONVENTIONS_CF_V1_6;
9488 104 : poDS->bWriteGDALVersion = CPLTestBool(
9489 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
9490 104 : poDS->bWriteGDALHistory = CPLTestBool(
9491 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
9492 104 : NCDFAddGDALHistory(poDS->cdfid, pszFilename, poDS->bWriteGDALVersion,
9493 104 : poDS->bWriteGDALHistory, "", "Create",
9494 : (nBandsIn == 0) ? CF_Vector_Conv
9495 : : GDAL_DEFAULT_NCDF_CONVENTIONS);
9496 : }
9497 :
9498 : // Define bands.
9499 197 : for (int iBand = 1; iBand <= nBandsIn; iBand++)
9500 : {
9501 : const char *pszBandName =
9502 91 : aosBandNames.empty() ? nullptr : aosBandNames[iBand - 1];
9503 :
9504 91 : poDS->SetBand(iBand, new netCDFRasterBand(
9505 91 : netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS,
9506 91 : eType, iBand, poDS->bSignedData, pszBandName));
9507 : }
9508 :
9509 106 : CPLDebug("GDAL_netCDF", "netCDFDataset::Create(%s, ...) done", pszFilename);
9510 : // Return same dataset.
9511 106 : return poDS;
9512 : }
9513 :
9514 : template <class T>
9515 103 : static CPLErr NCDFCopyBand(GDALRasterBand *poSrcBand, GDALRasterBand *poDstBand,
9516 : int nXSize, int nYSize, GDALProgressFunc pfnProgress,
9517 : void *pProgressData)
9518 : {
9519 103 : const GDALDataType eDT = poSrcBand->GetRasterDataType();
9520 103 : T *patScanline = static_cast<T *>(VSI_MALLOC2_VERBOSE(nXSize, sizeof(T)));
9521 103 : CPLErr eErr = patScanline ? CE_None : CE_Failure;
9522 :
9523 6540 : for (int iLine = 0; iLine < nYSize && eErr == CE_None; iLine++)
9524 : {
9525 6437 : eErr = poSrcBand->RasterIO(GF_Read, 0, iLine, nXSize, 1, patScanline,
9526 : nXSize, 1, eDT, 0, 0, nullptr);
9527 6437 : if (eErr != CE_None)
9528 : {
9529 0 : CPLDebug(
9530 : "GDAL_netCDF",
9531 : "NCDFCopyBand(), poSrcBand->RasterIO() returned error code %d",
9532 : eErr);
9533 : }
9534 : else
9535 : {
9536 6437 : eErr =
9537 : poDstBand->RasterIO(GF_Write, 0, iLine, nXSize, 1, patScanline,
9538 : nXSize, 1, eDT, 0, 0, nullptr);
9539 6437 : if (eErr != CE_None)
9540 0 : CPLDebug("GDAL_netCDF",
9541 : "NCDFCopyBand(), poDstBand->RasterIO() returned error "
9542 : "code %d",
9543 : eErr);
9544 : }
9545 :
9546 6437 : if (nYSize > 10 && (iLine % (nYSize / 10) == 1))
9547 : {
9548 377 : if (!pfnProgress(1.0 * iLine / nYSize, nullptr, pProgressData))
9549 : {
9550 0 : eErr = CE_Failure;
9551 0 : CPLError(CE_Failure, CPLE_UserInterrupt,
9552 : "User terminated CreateCopy()");
9553 : }
9554 : }
9555 : }
9556 :
9557 103 : CPLFree(patScanline);
9558 :
9559 103 : pfnProgress(1.0, nullptr, pProgressData);
9560 :
9561 103 : return eErr;
9562 : }
9563 :
9564 : /************************************************************************/
9565 : /* CreateCopy() */
9566 : /************************************************************************/
9567 :
9568 : GDALDataset *
9569 100 : netCDFDataset::CreateCopy(const char *pszFilename, GDALDataset *poSrcDS,
9570 : CPL_UNUSED int bStrict, CSLConstList papszOptions,
9571 : GDALProgressFunc pfnProgress, void *pProgressData)
9572 : {
9573 200 : CPLMutexHolderD(&hNCMutex);
9574 :
9575 100 : CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::CreateCopy(%s, ...)",
9576 : pszFilename);
9577 :
9578 100 : if (poSrcDS->GetRootGroup())
9579 : {
9580 12 : auto poDrv = GDALDriver::FromHandle(GDALGetDriverByName("netCDF"));
9581 12 : if (poDrv)
9582 : {
9583 12 : return poDrv->DefaultCreateCopy(pszFilename, poSrcDS, bStrict,
9584 : papszOptions, pfnProgress,
9585 12 : pProgressData);
9586 : }
9587 : }
9588 :
9589 88 : const int nBands = poSrcDS->GetRasterCount();
9590 88 : const int nXSize = poSrcDS->GetRasterXSize();
9591 88 : const int nYSize = poSrcDS->GetRasterYSize();
9592 88 : const char *pszWKT = poSrcDS->GetProjectionRef();
9593 :
9594 : // Check input bands for errors.
9595 88 : if (nBands == 0)
9596 : {
9597 1 : CPLError(CE_Failure, CPLE_NotSupported,
9598 : "NetCDF driver does not support "
9599 : "source datasets with zero bands.");
9600 1 : return nullptr;
9601 : }
9602 :
9603 87 : GDALDataType eDT = GDT_Unknown;
9604 87 : GDALRasterBand *poSrcBand = nullptr;
9605 203 : for (int iBand = 1; iBand <= nBands; iBand++)
9606 : {
9607 120 : poSrcBand = poSrcDS->GetRasterBand(iBand);
9608 120 : eDT = poSrcBand->GetRasterDataType();
9609 120 : if (eDT == GDT_Unknown || GDALDataTypeIsComplex(eDT))
9610 : {
9611 4 : CPLError(CE_Failure, CPLE_NotSupported,
9612 : "NetCDF driver does not support source dataset with band "
9613 : "of complex type.");
9614 4 : return nullptr;
9615 : }
9616 : }
9617 :
9618 166 : CPLStringList aosBandNames;
9619 83 : if (const char *pszBandNames =
9620 83 : CSLFetchNameValue(papszOptions, "BAND_NAMES"))
9621 : {
9622 : aosBandNames =
9623 2 : CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);
9624 :
9625 2 : if (aosBandNames.Count() != nBands)
9626 : {
9627 1 : CPLError(CE_Failure, CPLE_OpenFailed,
9628 : "Attempted to create netCDF with %d bands but %d names "
9629 : "provided in BAND_NAMES.",
9630 : nBands, aosBandNames.Count());
9631 :
9632 1 : return nullptr;
9633 : }
9634 : }
9635 :
9636 82 : if (!pfnProgress(0.0, nullptr, pProgressData))
9637 0 : return nullptr;
9638 :
9639 : // Check for extra dimensions.
9640 82 : int nDim = 2;
9641 : CPLStringList aosExtraDimNames =
9642 164 : NCDFTokenizeArray(poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
9643 :
9644 : // Same as in Create().
9645 164 : CPLStringList aosOptions(CSLDuplicate(papszOptions));
9646 82 : if (aosOptions.FetchNameValue("FORMAT") == nullptr)
9647 : {
9648 74 : if (eDT == GDT_UInt16 || eDT == GDT_UInt32 || eDT == GDT_UInt64 ||
9649 : eDT == GDT_Int64)
9650 : {
9651 6 : CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
9652 6 : aosOptions.SetNameValue("FORMAT", "NC4");
9653 : }
9654 68 : else if (!aosExtraDimNames.empty())
9655 : {
9656 14 : for (const auto &pszDimName : aosExtraDimNames)
9657 : {
9658 9 : const auto [nDimSize, nDimType] =
9659 9 : ReadExtraDimDef(poSrcDS, pszDimName);
9660 : {
9661 9 : if (nDimType > NC_DOUBLE)
9662 : {
9663 1 : CPLDebug("netCDF",
9664 : "Selecting FORMAT=NC4 due to data type of "
9665 : "extra dimension '%s'",
9666 : pszDimName);
9667 1 : aosOptions.SetNameValue("FORMAT", "NC4");
9668 1 : break;
9669 : }
9670 : }
9671 : }
9672 : }
9673 : }
9674 :
9675 82 : netCDFDataset *poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize,
9676 82 : nBands, aosOptions.List());
9677 82 : if (!poDS)
9678 12 : return nullptr;
9679 :
9680 : // Copy global metadata.
9681 : // Add Conventions, GDAL info and history.
9682 70 : CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid, NC_GLOBAL, nullptr);
9683 70 : const bool bWriteGDALVersion = CPLTestBool(
9684 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
9685 70 : const bool bWriteGDALHistory = CPLTestBool(
9686 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
9687 70 : NCDFAddGDALHistory(
9688 : poDS->cdfid, pszFilename, bWriteGDALVersion, bWriteGDALHistory,
9689 70 : poSrcDS->GetMetadataItem("NC_GLOBAL#history"), "CreateCopy",
9690 70 : poSrcDS->GetMetadataItem("NC_GLOBAL#Conventions"));
9691 :
9692 70 : pfnProgress(0.1, nullptr, pProgressData);
9693 :
9694 70 : if (!aosExtraDimNames.empty())
9695 : {
9696 6 : size_t nDimSizeTot = 1;
9697 : // first make sure dimensions lengths compatible with band count
9698 : // for( int i=0; i<CSLCount(papszExtraDimNames ); i++ ) {
9699 15 : for (int i = aosExtraDimNames.size() - 1; i >= 0; i--)
9700 : {
9701 9 : const auto [nDimSize, _] =
9702 9 : ReadExtraDimDef(poSrcDS, aosExtraDimNames[i]);
9703 9 : nDimSizeTot *= nDimSize;
9704 : }
9705 6 : if (nDimSizeTot == (size_t)nBands)
9706 : {
9707 6 : nDim = 2 + aosExtraDimNames.size();
9708 : }
9709 : else
9710 : {
9711 : // if nBands != #bands computed raise a warning
9712 : // just issue a debug message, because it was probably intentional
9713 0 : CPLDebug("GDAL_netCDF",
9714 : "Warning: Number of bands (%d) is not compatible with "
9715 : "dimensions "
9716 : "(total=%ld names=%s)",
9717 : nBands, (long)nDimSizeTot,
9718 0 : poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
9719 0 : aosExtraDimNames.clear();
9720 : }
9721 : }
9722 :
9723 70 : int *panDimIds = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
9724 70 : int *panBandDimPos = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
9725 :
9726 : nc_type nVarType;
9727 70 : int *panBandZLev = nullptr;
9728 70 : int *panDimVarIds = nullptr;
9729 :
9730 70 : if (nDim > 2)
9731 : {
9732 6 : panBandZLev = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
9733 6 : panDimVarIds = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
9734 :
9735 : // Define all dims.
9736 15 : for (int i = aosExtraDimNames.size() - 1; i >= 0; i--)
9737 : {
9738 9 : poDS->papszDimName.AddString(aosExtraDimNames[i]);
9739 : char szTemp[NC_MAX_NAME + 32 + 1];
9740 9 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
9741 : aosExtraDimNames[i]);
9742 : const CPLStringList aosExtraDimValues =
9743 18 : NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
9744 : const int nDimSize =
9745 9 : aosExtraDimValues.empty() ? 0 : atoi(aosExtraDimValues[0]);
9746 : // nc_type is an enum in netcdf-3, needs casting.
9747 0 : nVarType = static_cast<nc_type>(
9748 9 : aosExtraDimValues.size() >= 2 ? atol(aosExtraDimValues[1]) : 0);
9749 9 : panBandZLev[i] = nDimSize;
9750 9 : panBandDimPos[i + 2] = i; // Save Position of ZDim.
9751 :
9752 : // Define dim.
9753 9 : int status = nc_def_dim(poDS->cdfid, aosExtraDimNames[i], nDimSize,
9754 9 : &(panDimIds[i]));
9755 9 : NCDF_ERR(status);
9756 :
9757 : // Define dim var.
9758 9 : int anDim[1] = {panDimIds[i]};
9759 9 : status = nc_def_var(poDS->cdfid, aosExtraDimNames[i], nVarType, 1,
9760 9 : anDim, &(panDimVarIds[i]));
9761 9 : NCDF_ERR(status);
9762 :
9763 : // Add dim metadata, using global var# items.
9764 9 : snprintf(szTemp, sizeof(szTemp), "%s#", aosExtraDimNames[i]);
9765 9 : CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid,
9766 9 : panDimVarIds[i], szTemp);
9767 : }
9768 : }
9769 :
9770 : // Copy GeoTransform and Projection.
9771 :
9772 : // Copy geolocation info.
9773 : CSLConstList papszGeolocationInfo =
9774 70 : poSrcDS->GetMetadata(GDAL_MDD_GEOLOCATION);
9775 70 : if (papszGeolocationInfo != nullptr)
9776 6 : poDS->GDALPamDataset::SetMetadata(papszGeolocationInfo,
9777 : GDAL_MDD_GEOLOCATION);
9778 :
9779 : // Copy geotransform.
9780 70 : bool bGotGeoTransform = false;
9781 70 : GDALGeoTransform gt;
9782 70 : CPLErr eErr = poSrcDS->GetGeoTransform(gt);
9783 70 : if (eErr == CE_None)
9784 : {
9785 52 : poDS->SetGeoTransform(gt);
9786 : // Disable AddProjectionVars() from being called.
9787 52 : bGotGeoTransform = true;
9788 52 : poDS->m_bHasGeoTransform = false;
9789 : }
9790 :
9791 : // Copy projection.
9792 70 : void *pScaledProgress = nullptr;
9793 70 : if (bGotGeoTransform || (pszWKT && pszWKT[0] != 0))
9794 : {
9795 53 : poDS->SetProjection(pszWKT ? pszWKT : "");
9796 :
9797 : // Now we can call AddProjectionVars() directly.
9798 53 : poDS->m_bHasGeoTransform = bGotGeoTransform;
9799 53 : poDS->AddProjectionVars(true, nullptr, nullptr);
9800 : pScaledProgress =
9801 53 : GDALCreateScaledProgress(0.1, 0.25, pfnProgress, pProgressData);
9802 53 : poDS->AddProjectionVars(false, GDALScaledProgress, pScaledProgress);
9803 53 : GDALDestroyScaledProgress(pScaledProgress);
9804 : }
9805 : else
9806 : {
9807 17 : poDS->bBottomUp =
9808 17 : CPL_TO_BOOL(CSLFetchBoolean(papszOptions, "WRITE_BOTTOMUP", TRUE));
9809 17 : if (papszGeolocationInfo)
9810 : {
9811 4 : poDS->AddProjectionVars(true, nullptr, nullptr);
9812 4 : poDS->AddProjectionVars(false, nullptr, nullptr);
9813 : }
9814 : }
9815 :
9816 : // Save X,Y dim positions.
9817 70 : panDimIds[nDim - 1] = poDS->nXDimID;
9818 70 : panBandDimPos[0] = nDim - 1;
9819 70 : panDimIds[nDim - 2] = poDS->nYDimID;
9820 70 : panBandDimPos[1] = nDim - 2;
9821 :
9822 : // Write extra dim values - after projection for optimization.
9823 70 : if (nDim > 2)
9824 : {
9825 : // Make sure we are in data mode.
9826 6 : poDS->SetDefineMode(false);
9827 15 : for (int i = aosExtraDimNames.size() - 1; i >= 0; i--)
9828 : {
9829 : char szTemp[NC_MAX_NAME + 32 + 1];
9830 9 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_VALUES",
9831 : aosExtraDimNames[i]);
9832 9 : if (poSrcDS->GetMetadataItem(szTemp) != nullptr)
9833 : {
9834 9 : NCDFPut1DVar(poDS->cdfid, panDimVarIds[i],
9835 9 : poSrcDS->GetMetadataItem(szTemp));
9836 : }
9837 : }
9838 : }
9839 :
9840 70 : pfnProgress(0.25, nullptr, pProgressData);
9841 :
9842 : // Define Bands.
9843 70 : netCDFRasterBand *poBand = nullptr;
9844 70 : int nBandID = -1;
9845 :
9846 173 : for (int iBand = 1; iBand <= nBands; iBand++)
9847 : {
9848 103 : CPLDebug("GDAL_netCDF", "creating band # %d/%d nDim = %d", iBand,
9849 : nBands, nDim);
9850 :
9851 103 : poSrcBand = poSrcDS->GetRasterBand(iBand);
9852 103 : eDT = poSrcBand->GetRasterDataType();
9853 :
9854 : // Get var name from NETCDF_VARNAME.
9855 : const char *pszNETCDF_VARNAME =
9856 103 : poSrcBand->GetMetadataItem("NETCDF_VARNAME");
9857 : char szBandName[NC_MAX_NAME + 1];
9858 103 : if (!aosBandNames.empty())
9859 : {
9860 2 : snprintf(szBandName, sizeof(szBandName), "%s",
9861 : aosBandNames[iBand - 1]);
9862 : }
9863 101 : else if (pszNETCDF_VARNAME)
9864 : {
9865 38 : if (nBands > 1 && aosExtraDimNames.empty())
9866 0 : snprintf(szBandName, sizeof(szBandName), "%s%d",
9867 : pszNETCDF_VARNAME, iBand);
9868 : else
9869 38 : snprintf(szBandName, sizeof(szBandName), "%s",
9870 : pszNETCDF_VARNAME);
9871 : }
9872 : else
9873 : {
9874 63 : szBandName[0] = '\0';
9875 : }
9876 :
9877 : // Get long_name from <var>#long_name.
9878 103 : const char *pszLongName = "";
9879 103 : if (pszNETCDF_VARNAME)
9880 : {
9881 : pszLongName =
9882 76 : poSrcDS->GetMetadataItem(std::string(pszNETCDF_VARNAME)
9883 38 : .append("#")
9884 38 : .append(CF_LNG_NAME)
9885 38 : .c_str());
9886 38 : if (!pszLongName)
9887 25 : pszLongName = "";
9888 : }
9889 :
9890 103 : constexpr bool bSignedData = false;
9891 :
9892 103 : if (nDim > 2)
9893 28 : poBand = new netCDFRasterBand(
9894 28 : netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
9895 : bSignedData, szBandName, pszLongName, nBandID, nDim, iBand - 1,
9896 28 : panBandZLev, panBandDimPos, panDimIds);
9897 : else
9898 75 : poBand = new netCDFRasterBand(
9899 75 : netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
9900 75 : bSignedData, szBandName, pszLongName);
9901 :
9902 103 : poDS->SetBand(iBand, poBand);
9903 :
9904 : // Set nodata value, if any.
9905 103 : GDALCopyNoDataValue(poBand, poSrcBand);
9906 :
9907 : // Copy Metadata for band.
9908 103 : CopyMetadata(nullptr, poSrcDS->GetRasterBand(iBand), poBand,
9909 : poDS->cdfid, poBand->nZId);
9910 :
9911 : // If more than 2D pass the first band's netcdf var ID to subsequent
9912 : // bands.
9913 103 : if (nDim > 2)
9914 28 : nBandID = poBand->nZId;
9915 : }
9916 :
9917 : // Write projection variable to band variable.
9918 70 : poDS->AddGridMappingRef();
9919 :
9920 70 : pfnProgress(0.5, nullptr, pProgressData);
9921 :
9922 : // Write bands.
9923 :
9924 : // Make sure we are in data mode.
9925 70 : poDS->SetDefineMode(false);
9926 :
9927 70 : double dfTemp = 0.5;
9928 :
9929 70 : eErr = CE_None;
9930 :
9931 173 : for (int iBand = 1; iBand <= nBands && eErr == CE_None; iBand++)
9932 : {
9933 103 : const double dfTemp2 = dfTemp + 0.4 / nBands;
9934 103 : pScaledProgress = GDALCreateScaledProgress(dfTemp, dfTemp2, pfnProgress,
9935 : pProgressData);
9936 103 : dfTemp = dfTemp2;
9937 :
9938 103 : CPLDebug("GDAL_netCDF", "copying band data # %d/%d ", iBand, nBands);
9939 :
9940 103 : poSrcBand = poSrcDS->GetRasterBand(iBand);
9941 103 : eDT = poSrcBand->GetRasterDataType();
9942 :
9943 103 : GDALRasterBand *poDstBand = poDS->GetRasterBand(iBand);
9944 :
9945 : // Copy band data.
9946 103 : if (eDT == GDT_UInt8)
9947 : {
9948 61 : CPLDebug("GDAL_netCDF", "GByte Band#%d", iBand);
9949 61 : eErr = NCDFCopyBand<GByte>(poSrcBand, poDstBand, nXSize, nYSize,
9950 : GDALScaledProgress, pScaledProgress);
9951 : }
9952 42 : else if (eDT == GDT_Int8)
9953 : {
9954 1 : CPLDebug("GDAL_netCDF", "GInt8 Band#%d", iBand);
9955 1 : eErr = NCDFCopyBand<GInt8>(poSrcBand, poDstBand, nXSize, nYSize,
9956 : GDALScaledProgress, pScaledProgress);
9957 : }
9958 41 : else if (eDT == GDT_UInt16)
9959 : {
9960 2 : CPLDebug("GDAL_netCDF", "GUInt16 Band#%d", iBand);
9961 2 : eErr = NCDFCopyBand<GInt16>(poSrcBand, poDstBand, nXSize, nYSize,
9962 : GDALScaledProgress, pScaledProgress);
9963 : }
9964 39 : else if (eDT == GDT_Int16)
9965 : {
9966 5 : CPLDebug("GDAL_netCDF", "GInt16 Band#%d", iBand);
9967 5 : eErr = NCDFCopyBand<GUInt16>(poSrcBand, poDstBand, nXSize, nYSize,
9968 : GDALScaledProgress, pScaledProgress);
9969 : }
9970 34 : else if (eDT == GDT_UInt32)
9971 : {
9972 2 : CPLDebug("GDAL_netCDF", "GUInt32 Band#%d", iBand);
9973 2 : eErr = NCDFCopyBand<GUInt32>(poSrcBand, poDstBand, nXSize, nYSize,
9974 : GDALScaledProgress, pScaledProgress);
9975 : }
9976 32 : else if (eDT == GDT_Int32)
9977 : {
9978 18 : CPLDebug("GDAL_netCDF", "GInt32 Band#%d", iBand);
9979 18 : eErr = NCDFCopyBand<GInt32>(poSrcBand, poDstBand, nXSize, nYSize,
9980 : GDALScaledProgress, pScaledProgress);
9981 : }
9982 14 : else if (eDT == GDT_UInt64)
9983 : {
9984 2 : CPLDebug("GDAL_netCDF", "GUInt64 Band#%d", iBand);
9985 2 : eErr = NCDFCopyBand<std::uint64_t>(poSrcBand, poDstBand, nXSize,
9986 : nYSize, GDALScaledProgress,
9987 : pScaledProgress);
9988 : }
9989 12 : else if (eDT == GDT_Int64)
9990 : {
9991 2 : CPLDebug("GDAL_netCDF", "GInt64 Band#%d", iBand);
9992 : eErr =
9993 2 : NCDFCopyBand<std::int64_t>(poSrcBand, poDstBand, nXSize, nYSize,
9994 : GDALScaledProgress, pScaledProgress);
9995 : }
9996 10 : else if (eDT == GDT_Float32)
9997 : {
9998 8 : CPLDebug("GDAL_netCDF", "float Band#%d", iBand);
9999 8 : eErr = NCDFCopyBand<float>(poSrcBand, poDstBand, nXSize, nYSize,
10000 : GDALScaledProgress, pScaledProgress);
10001 : }
10002 2 : else if (eDT == GDT_Float64)
10003 : {
10004 2 : CPLDebug("GDAL_netCDF", "double Band#%d", iBand);
10005 2 : eErr = NCDFCopyBand<double>(poSrcBand, poDstBand, nXSize, nYSize,
10006 : GDALScaledProgress, pScaledProgress);
10007 : }
10008 : else
10009 : {
10010 0 : CPLError(CE_Failure, CPLE_NotSupported,
10011 : "The NetCDF driver does not support GDAL data type %d",
10012 : eDT);
10013 : }
10014 :
10015 103 : GDALDestroyScaledProgress(pScaledProgress);
10016 : }
10017 :
10018 70 : delete (poDS);
10019 :
10020 70 : CPLFree(panDimIds);
10021 70 : CPLFree(panBandDimPos);
10022 70 : CPLFree(panBandZLev);
10023 70 : CPLFree(panDimVarIds);
10024 :
10025 70 : if (eErr != CE_None)
10026 0 : return nullptr;
10027 :
10028 70 : pfnProgress(0.95, nullptr, pProgressData);
10029 :
10030 : // Re-open dataset so we can return it.
10031 140 : CPLStringList aosOpenOptions;
10032 70 : aosOpenOptions.AddString("VARIABLES_AS_BANDS=YES");
10033 70 : GDALOpenInfo oOpenInfo(pszFilename, GA_Update);
10034 70 : oOpenInfo.nOpenFlags = GDAL_OF_RASTER | GDAL_OF_UPDATE;
10035 70 : oOpenInfo.papszOpenOptions = aosOpenOptions.List();
10036 70 : auto poRetDS = Open(&oOpenInfo);
10037 :
10038 : // PAM cloning is disabled. See bug #4244.
10039 : // if( poDS )
10040 : // poDS->CloneInfo(poSrcDS, GCIF_PAM_DEFAULT);
10041 :
10042 70 : pfnProgress(1.0, nullptr, pProgressData);
10043 :
10044 70 : return poRetDS;
10045 : }
10046 :
10047 : // Note: some logic depends on bIsProjected and bIsGeoGraphic.
10048 : // May not be known when Create() is called, see AddProjectionVars().
10049 345 : bool netCDFDataset::ProcessCreationOptions()
10050 : {
10051 345 : const char *pszConfig = aosCreationOptions.FetchNameValue("CONFIG_FILE");
10052 345 : if (pszConfig != nullptr)
10053 : {
10054 26 : if (oWriterConfig.Parse(pszConfig))
10055 : {
10056 : // Override dataset creation options from the config file
10057 2 : for (const auto &[osName, osValue] :
10058 5 : oWriterConfig.m_oDatasetCreationOptions)
10059 : {
10060 1 : aosCreationOptions.SetNameValue(osName, osValue);
10061 : }
10062 : }
10063 : else
10064 : {
10065 23 : return false;
10066 : }
10067 : }
10068 :
10069 : // File format.
10070 322 : eFormat = NCDF_FORMAT_NC;
10071 322 : const char *pszValue = aosCreationOptions.FetchNameValue("FORMAT");
10072 322 : if (pszValue != nullptr)
10073 : {
10074 151 : if (EQUAL(pszValue, "NC"))
10075 : {
10076 3 : eFormat = NCDF_FORMAT_NC;
10077 : }
10078 : #ifdef NETCDF_HAS_NC2
10079 148 : else if (EQUAL(pszValue, "NC2"))
10080 : {
10081 1 : eFormat = NCDF_FORMAT_NC2;
10082 : }
10083 : #endif
10084 147 : else if (EQUAL(pszValue, "NC4"))
10085 : {
10086 143 : eFormat = NCDF_FORMAT_NC4;
10087 : }
10088 4 : else if (EQUAL(pszValue, "NC4C"))
10089 : {
10090 4 : eFormat = NCDF_FORMAT_NC4C;
10091 : }
10092 : else
10093 : {
10094 0 : CPLError(CE_Warning, CPLE_NotSupported,
10095 : "FORMAT=%s in not supported, using the default NC format.",
10096 : pszValue);
10097 : }
10098 : }
10099 :
10100 : // COMPRESS option.
10101 322 : pszValue = aosCreationOptions.FetchNameValue("COMPRESS");
10102 322 : if (pszValue != nullptr)
10103 : {
10104 3 : if (EQUAL(pszValue, "NONE"))
10105 : {
10106 1 : eCompress = NCDF_COMPRESS_NONE;
10107 : }
10108 2 : else if (EQUAL(pszValue, "DEFLATE"))
10109 : {
10110 2 : eCompress = NCDF_COMPRESS_DEFLATE;
10111 2 : if (!((eFormat == NCDF_FORMAT_NC4) ||
10112 2 : (eFormat == NCDF_FORMAT_NC4C)))
10113 : {
10114 1 : CPLError(CE_Warning, CPLE_IllegalArg,
10115 : "NOTICE: Format set to NC4C because compression is "
10116 : "set to DEFLATE.");
10117 1 : eFormat = NCDF_FORMAT_NC4C;
10118 : }
10119 : }
10120 : else
10121 : {
10122 0 : CPLError(CE_Warning, CPLE_NotSupported,
10123 : "COMPRESS=%s is not supported.", pszValue);
10124 : }
10125 : }
10126 :
10127 : // ZLEVEL option.
10128 322 : pszValue = aosCreationOptions.FetchNameValue("ZLEVEL");
10129 322 : if (pszValue != nullptr)
10130 : {
10131 1 : nZLevel = atoi(pszValue);
10132 1 : if (!(nZLevel >= 1 && nZLevel <= 9))
10133 : {
10134 0 : CPLError(CE_Warning, CPLE_IllegalArg,
10135 : "ZLEVEL=%s value not recognised, ignoring.", pszValue);
10136 0 : nZLevel = NCDF_DEFLATE_LEVEL;
10137 : }
10138 : }
10139 :
10140 : // CHUNKING option.
10141 322 : bChunking = aosCreationOptions.FetchBool("CHUNKING", true);
10142 :
10143 : // MULTIPLE_LAYERS option.
10144 : const char *pszMultipleLayerBehavior =
10145 322 : aosCreationOptions.FetchNameValueDef("MULTIPLE_LAYERS", "NO");
10146 : const char *pszGeometryEnc =
10147 322 : aosCreationOptions.FetchNameValueDef("GEOMETRY_ENCODING", "CF_1.8");
10148 322 : if (EQUAL(pszMultipleLayerBehavior, "NO") ||
10149 4 : EQUAL(pszGeometryEnc, "CF_1.8"))
10150 : {
10151 318 : eMultipleLayerBehavior = SINGLE_LAYER;
10152 : }
10153 4 : else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_FILES"))
10154 : {
10155 3 : eMultipleLayerBehavior = SEPARATE_FILES;
10156 : }
10157 1 : else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_GROUPS"))
10158 : {
10159 1 : if (eFormat == NCDF_FORMAT_NC4)
10160 : {
10161 1 : eMultipleLayerBehavior = SEPARATE_GROUPS;
10162 : }
10163 : else
10164 : {
10165 0 : CPLError(CE_Warning, CPLE_IllegalArg,
10166 : "MULTIPLE_LAYERS=%s is recognised only with FORMAT=NC4",
10167 : pszMultipleLayerBehavior);
10168 : }
10169 : }
10170 : else
10171 : {
10172 0 : CPLError(CE_Warning, CPLE_IllegalArg,
10173 : "MULTIPLE_LAYERS=%s not recognised", pszMultipleLayerBehavior);
10174 : }
10175 :
10176 : // Set nCreateMode based on eFormat.
10177 322 : switch (eFormat)
10178 : {
10179 : #ifdef NETCDF_HAS_NC2
10180 1 : case NCDF_FORMAT_NC2:
10181 1 : nCreateMode = NC_CLOBBER | NC_64BIT_OFFSET;
10182 1 : break;
10183 : #endif
10184 143 : case NCDF_FORMAT_NC4:
10185 143 : nCreateMode = NC_CLOBBER | NC_NETCDF4;
10186 143 : break;
10187 5 : case NCDF_FORMAT_NC4C:
10188 5 : nCreateMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
10189 5 : break;
10190 173 : case NCDF_FORMAT_NC:
10191 : default:
10192 173 : nCreateMode = NC_CLOBBER;
10193 173 : break;
10194 : }
10195 :
10196 322 : CPLDebug("GDAL_netCDF", "file options: format=%d compress=%d zlevel=%d",
10197 322 : eFormat, eCompress, nZLevel);
10198 :
10199 322 : return true;
10200 : }
10201 :
10202 296 : int netCDFDataset::DefVarDeflate(int nVarId, bool bChunkingArg) const
10203 : {
10204 296 : if (eCompress == NCDF_COMPRESS_DEFLATE)
10205 : {
10206 : // Must set chunk size to avoid huge performance hit (set
10207 : // bChunkingArg=TRUE)
10208 : // perhaps another solution it to change the chunk cache?
10209 : // http://www.unidata.ucar.edu/software/netcdf/docs/netcdf.html#Chunk-Cache
10210 : // TODO: make sure this is okay.
10211 2 : CPLDebug("GDAL_netCDF", "DefVarDeflate(%d, %d) nZlevel=%d", nVarId,
10212 2 : static_cast<int>(bChunkingArg), nZLevel);
10213 :
10214 2 : int status = nc_def_var_deflate(cdfid, nVarId, 1, 1, nZLevel);
10215 2 : NCDF_ERR(status);
10216 :
10217 2 : if (status == NC_NOERR && bChunkingArg && bChunking)
10218 : {
10219 : // set chunking to be 1 for all dims, except X dim
10220 : // size_t chunksize[] = { 1, (size_t)nRasterXSize };
10221 : size_t chunksize[MAX_NC_DIMS];
10222 : int nd;
10223 2 : nc_inq_varndims(cdfid, nVarId, &nd);
10224 2 : chunksize[0] = (size_t)1;
10225 2 : chunksize[1] = (size_t)1;
10226 2 : for (int i = 2; i < nd; i++)
10227 0 : chunksize[i] = (size_t)1;
10228 2 : chunksize[nd - 1] = (size_t)nRasterXSize;
10229 :
10230 : // Config options just for testing purposes
10231 : const char *pszBlockXSize =
10232 2 : CPLGetConfigOption("BLOCKXSIZE", nullptr);
10233 2 : if (pszBlockXSize)
10234 0 : chunksize[nd - 1] = (size_t)atoi(pszBlockXSize);
10235 :
10236 : const char *pszBlockYSize =
10237 2 : CPLGetConfigOption("BLOCKYSIZE", nullptr);
10238 2 : if (nd >= 2 && pszBlockYSize)
10239 0 : chunksize[nd - 2] = (size_t)atoi(pszBlockYSize);
10240 :
10241 2 : CPLDebug("GDAL_netCDF",
10242 : "DefVarDeflate() chunksize={%ld, %ld} chunkX=%ld nd=%d",
10243 2 : (long)chunksize[0], (long)chunksize[1],
10244 2 : (long)chunksize[nd - 1], nd);
10245 : #ifdef NCDF_DEBUG
10246 : for (int i = 0; i < nd; i++)
10247 : CPLDebug("GDAL_netCDF", "DefVarDeflate() chunk[%d]=%ld", i,
10248 : chunksize[i]);
10249 : #endif
10250 :
10251 2 : status = nc_def_var_chunking(cdfid, nVarId, NC_CHUNKED, chunksize);
10252 2 : NCDF_ERR(status);
10253 : }
10254 : else
10255 : {
10256 0 : CPLDebug("GDAL_netCDF", "chunksize not set");
10257 : }
10258 2 : return status;
10259 : }
10260 294 : return NC_NOERR;
10261 : }
10262 :
10263 : /************************************************************************/
10264 : /* NCDFUnloadDriver() */
10265 : /************************************************************************/
10266 :
10267 10 : static void NCDFUnloadDriver(CPL_UNUSED GDALDriver *poDriver)
10268 : {
10269 10 : if (hNCMutex != nullptr)
10270 6 : CPLDestroyMutex(hNCMutex);
10271 10 : hNCMutex = nullptr;
10272 10 : }
10273 :
10274 : /************************************************************************/
10275 : /* GDALRegister_netCDF() */
10276 : /************************************************************************/
10277 :
10278 : class GDALnetCDFDriver final : public GDALDriver
10279 : {
10280 : public:
10281 20 : GDALnetCDFDriver() = default;
10282 :
10283 : const char *GetMetadataItem(const char *pszName,
10284 : const char *pszDomain) override;
10285 :
10286 122 : CSLConstList GetMetadata(const char *pszDomain) override
10287 : {
10288 244 : std::lock_guard oLock(m_oMutex);
10289 122 : InitializeDCAPVirtualIO();
10290 244 : return GDALDriver::GetMetadata(pszDomain);
10291 : }
10292 :
10293 : private:
10294 : std::recursive_mutex m_oMutex{};
10295 : bool m_bInitialized = false;
10296 :
10297 135 : void InitializeDCAPVirtualIO()
10298 : {
10299 135 : if (!m_bInitialized)
10300 : {
10301 11 : m_bInitialized = true;
10302 :
10303 : #ifdef ENABLE_UFFD
10304 11 : if (CPLIsUserFaultMappingSupported())
10305 : {
10306 11 : SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
10307 : }
10308 : #endif
10309 : }
10310 135 : }
10311 : };
10312 :
10313 1454 : const char *GDALnetCDFDriver::GetMetadataItem(const char *pszName,
10314 : const char *pszDomain)
10315 : {
10316 2908 : std::lock_guard oLock(m_oMutex);
10317 1454 : if (EQUAL(pszName, GDAL_DCAP_VIRTUALIO))
10318 : {
10319 13 : InitializeDCAPVirtualIO();
10320 : }
10321 2908 : return GDALDriver::GetMetadataItem(pszName, pszDomain);
10322 : }
10323 :
10324 20 : void GDALRegister_netCDF()
10325 :
10326 : {
10327 20 : if (!GDAL_CHECK_VERSION("netCDF driver"))
10328 0 : return;
10329 :
10330 20 : if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
10331 0 : return;
10332 :
10333 20 : GDALDriver *poDriver = new GDALnetCDFDriver();
10334 20 : netCDFDriverSetCommonMetadata(poDriver);
10335 :
10336 20 : poDriver->SetMetadataItem("NETCDF_CONVENTIONS",
10337 20 : GDAL_DEFAULT_NCDF_CONVENTIONS);
10338 20 : poDriver->SetMetadataItem("NETCDF_VERSION", nc_inq_libvers());
10339 :
10340 : // Set pfns and register driver.
10341 20 : poDriver->pfnOpen = netCDFDataset::Open;
10342 20 : poDriver->pfnCreateCopy = netCDFDataset::CreateCopy;
10343 20 : poDriver->pfnCreate = netCDFDataset::Create;
10344 20 : poDriver->pfnCreateMultiDimensional = netCDFDataset::CreateMultiDimensional;
10345 20 : poDriver->pfnUnloadDriver = NCDFUnloadDriver;
10346 :
10347 20 : GetGDALDriverManager()->RegisterDriver(poDriver);
10348 : }
10349 :
10350 : /************************************************************************/
10351 : /* New functions */
10352 : /************************************************************************/
10353 :
10354 : /* Test for GDAL version string >= target */
10355 294 : static bool NCDFIsGDALVersionGTE(const char *pszVersion, int nTarget)
10356 : {
10357 :
10358 : // Valid strings are "GDAL 1.9dev, released 2011/01/18" and "GDAL 1.8.1 ".
10359 294 : if (pszVersion == nullptr || EQUAL(pszVersion, ""))
10360 0 : return false;
10361 294 : else if (!STARTS_WITH_CI(pszVersion, "GDAL "))
10362 0 : return false;
10363 : // 2.0dev of 2011/12/29 has been later renamed as 1.10dev.
10364 294 : else if (EQUAL("GDAL 2.0dev, released 2011/12/29", pszVersion))
10365 0 : return nTarget <= GDAL_COMPUTE_VERSION(1, 10, 0);
10366 294 : else if (STARTS_WITH_CI(pszVersion, "GDAL 1.9dev"))
10367 2 : return nTarget <= 1900;
10368 292 : else if (STARTS_WITH_CI(pszVersion, "GDAL 1.8dev"))
10369 0 : return nTarget <= 1800;
10370 :
10371 292 : const CPLStringList aosTokens(CSLTokenizeString2(pszVersion + 5, ".", 0));
10372 :
10373 292 : int nVersions[] = {0, 0, 0, 0};
10374 1168 : for (int iToken = 0; iToken < std::min(4, aosTokens.size()); iToken++)
10375 : {
10376 876 : nVersions[iToken] = atoi(aosTokens[iToken]);
10377 876 : if (nVersions[iToken] < 0)
10378 0 : nVersions[iToken] = 0;
10379 876 : else if (nVersions[iToken] > 99)
10380 0 : nVersions[iToken] = 99;
10381 : }
10382 :
10383 292 : int nVersion = 0;
10384 292 : if (nVersions[0] > 1 || nVersions[1] >= 10)
10385 292 : nVersion =
10386 292 : GDAL_COMPUTE_VERSION(nVersions[0], nVersions[1], nVersions[2]);
10387 : else
10388 0 : nVersion = nVersions[0] * 1000 + nVersions[1] * 100 +
10389 0 : nVersions[2] * 10 + nVersions[3];
10390 :
10391 292 : return nTarget <= nVersion;
10392 : }
10393 :
10394 : // Add Conventions, GDAL version and history.
10395 178 : static void NCDFAddGDALHistory(int fpImage, const char *pszFilename,
10396 : bool bWriteGDALVersion, bool bWriteGDALHistory,
10397 : const char *pszOldHist,
10398 : const char *pszFunctionName,
10399 : const char *pszCFVersion)
10400 : {
10401 178 : if (pszCFVersion == nullptr)
10402 : {
10403 48 : pszCFVersion = GDAL_DEFAULT_NCDF_CONVENTIONS;
10404 : }
10405 178 : int status = nc_put_att_text(fpImage, NC_GLOBAL, "Conventions",
10406 : strlen(pszCFVersion), pszCFVersion);
10407 178 : NCDF_ERR(status);
10408 :
10409 178 : if (bWriteGDALVersion)
10410 : {
10411 176 : const char *pszNCDF_GDAL = GDALVersionInfo("--version");
10412 176 : status = nc_put_att_text(fpImage, NC_GLOBAL, "GDAL",
10413 : strlen(pszNCDF_GDAL), pszNCDF_GDAL);
10414 176 : NCDF_ERR(status);
10415 : }
10416 :
10417 178 : if (bWriteGDALHistory)
10418 : {
10419 : // Add history.
10420 352 : CPLString osTmp;
10421 : #ifdef GDAL_SET_CMD_LINE_DEFINED_TMP
10422 : if (!EQUAL(GDALGetCmdLine(), ""))
10423 : osTmp = GDALGetCmdLine();
10424 : else
10425 : osTmp =
10426 : CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
10427 : #else
10428 176 : osTmp = CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
10429 : #endif
10430 :
10431 176 : NCDFAddHistory(fpImage, osTmp.c_str(), pszOldHist);
10432 : }
10433 2 : else if (pszOldHist != nullptr)
10434 : {
10435 0 : status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
10436 : strlen(pszOldHist), pszOldHist);
10437 0 : NCDF_ERR(status);
10438 : }
10439 178 : }
10440 :
10441 : // Code taken from cdo and libcdi, used for writing the history attribute.
10442 :
10443 : // void cdoDefHistory(int fileID, char *histstring)
10444 176 : static void NCDFAddHistory(int fpImage, const char *pszAddHist,
10445 : const char *pszOldHist)
10446 : {
10447 : // Check pszOldHist - as if there was no previous history, it will be
10448 : // a null pointer - if so set as empty.
10449 176 : if (nullptr == pszOldHist)
10450 : {
10451 59 : pszOldHist = "";
10452 : }
10453 :
10454 : char strtime[32];
10455 176 : strtime[0] = '\0';
10456 :
10457 176 : time_t tp = time(nullptr);
10458 176 : if (tp != -1)
10459 : {
10460 : struct tm ltime;
10461 176 : VSILocalTime(&tp, <ime);
10462 176 : (void)strftime(strtime, sizeof(strtime),
10463 : "%a %b %d %H:%M:%S %Y: ", <ime);
10464 : }
10465 :
10466 : // status = nc_get_att_text(fpImage, NC_GLOBAL,
10467 : // "history", pszOldHist);
10468 : // printf("status: %d pszOldHist: [%s]\n",status,pszOldHist);
10469 :
10470 176 : size_t nNewHistSize =
10471 176 : strlen(pszOldHist) + strlen(strtime) + strlen(pszAddHist) + 1 + 1;
10472 : char *pszNewHist =
10473 176 : static_cast<char *>(CPLMalloc(nNewHistSize * sizeof(char)));
10474 :
10475 176 : strcpy(pszNewHist, strtime);
10476 176 : strcat(pszNewHist, pszAddHist);
10477 :
10478 : // int disableHistory = FALSE;
10479 : // if( !disableHistory )
10480 : {
10481 176 : if (!EQUAL(pszOldHist, ""))
10482 9 : strcat(pszNewHist, "\n");
10483 176 : strcat(pszNewHist, pszOldHist);
10484 : }
10485 :
10486 176 : const int status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
10487 : strlen(pszNewHist), pszNewHist);
10488 176 : NCDF_ERR(status);
10489 :
10490 176 : CPLFree(pszNewHist);
10491 176 : }
10492 :
10493 7401 : static CPLErr NCDFSafeStrcat(char **ppszDest, const char *pszSrc,
10494 : size_t *nDestSize)
10495 : {
10496 : /* Reallocate the data string until the content fits */
10497 7401 : while (*nDestSize < (strlen(*ppszDest) + strlen(pszSrc) + 1))
10498 : {
10499 495 : (*nDestSize) *= 2;
10500 495 : *ppszDest = static_cast<char *>(
10501 495 : CPLRealloc(reinterpret_cast<void *>(*ppszDest), *nDestSize));
10502 : #ifdef NCDF_DEBUG
10503 : CPLDebug("GDAL_netCDF", "NCDFSafeStrcat() resized str from %ld to %ld",
10504 : (*nDestSize) / 2, *nDestSize);
10505 : #endif
10506 : }
10507 6906 : strcat(*ppszDest, pszSrc);
10508 :
10509 6906 : return CE_None;
10510 : }
10511 :
10512 : /* helper function for NCDFGetAttr() */
10513 : /* if pdfValue != nullptr, sets *pdfValue to first value returned */
10514 : /* if ppszValue != nullptr, sets *ppszValue with all attribute values */
10515 : /* *ppszValue is the responsibility of the caller and must be freed */
10516 79168 : static CPLErr NCDFGetAttr1(int nCdfId, int nVarId, const char *pszAttrName,
10517 : double *pdfValue, char **ppszValue)
10518 : {
10519 79168 : nc_type nAttrType = NC_NAT;
10520 79168 : size_t nAttrLen = 0;
10521 :
10522 79168 : if (ppszValue)
10523 77811 : *ppszValue = nullptr;
10524 :
10525 79168 : int status = nc_inq_att(nCdfId, nVarId, pszAttrName, &nAttrType, &nAttrLen);
10526 79168 : if (status != NC_NOERR)
10527 42147 : return CE_Failure;
10528 :
10529 : #ifdef NCDF_DEBUG
10530 : CPLDebug("GDAL_netCDF", "NCDFGetAttr1(%s) len=%ld type=%d", pszAttrName,
10531 : nAttrLen, nAttrType);
10532 : #endif
10533 37021 : if (nAttrLen == 0 && nAttrType != NC_CHAR)
10534 1 : return CE_Failure;
10535 :
10536 : /* Allocate guaranteed minimum size (use 10 or 20 if not a string) */
10537 37020 : size_t nAttrValueSize = nAttrLen + 1;
10538 37020 : if (nAttrType != NC_CHAR && nAttrValueSize < 10)
10539 4119 : nAttrValueSize = 10;
10540 37020 : if (nAttrType == NC_DOUBLE && nAttrValueSize < 20)
10541 1981 : nAttrValueSize = 20;
10542 37020 : if (nAttrType == NC_INT64 && nAttrValueSize < 20)
10543 49 : nAttrValueSize = 22;
10544 : char *pszAttrValue =
10545 37020 : static_cast<char *>(CPLCalloc(nAttrValueSize, sizeof(char)));
10546 37020 : *pszAttrValue = '\0';
10547 :
10548 37020 : if (nAttrLen > 1 && nAttrType != NC_CHAR)
10549 680 : NCDFSafeStrcat(&pszAttrValue, "{", &nAttrValueSize);
10550 :
10551 37020 : double dfValue = 0.0;
10552 37020 : size_t m = 0;
10553 : char szTemp[256];
10554 37020 : bool bSetDoubleFromStr = false;
10555 :
10556 37020 : switch (nAttrType)
10557 : {
10558 32899 : case NC_CHAR:
10559 32899 : CPL_IGNORE_RET_VAL(
10560 32899 : nc_get_att_text(nCdfId, nVarId, pszAttrName, pszAttrValue));
10561 32899 : pszAttrValue[nAttrLen] = '\0';
10562 32899 : bSetDoubleFromStr = true;
10563 32899 : dfValue = 0.0;
10564 32899 : break;
10565 94 : case NC_BYTE:
10566 : {
10567 : signed char *pscTemp = static_cast<signed char *>(
10568 94 : CPLCalloc(nAttrLen, sizeof(signed char)));
10569 94 : nc_get_att_schar(nCdfId, nVarId, pszAttrName, pscTemp);
10570 94 : dfValue = static_cast<double>(pscTemp[0]);
10571 94 : if (nAttrLen > 1)
10572 : {
10573 24 : for (m = 0; m < nAttrLen - 1; m++)
10574 : {
10575 13 : snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
10576 13 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10577 : }
10578 : }
10579 94 : snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
10580 94 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10581 94 : CPLFree(pscTemp);
10582 94 : break;
10583 : }
10584 571 : case NC_SHORT:
10585 : {
10586 : short *psTemp =
10587 571 : static_cast<short *>(CPLCalloc(nAttrLen, sizeof(short)));
10588 571 : nc_get_att_short(nCdfId, nVarId, pszAttrName, psTemp);
10589 571 : dfValue = static_cast<double>(psTemp[0]);
10590 571 : if (nAttrLen > 1)
10591 : {
10592 922 : for (m = 0; m < nAttrLen - 1; m++)
10593 : {
10594 461 : snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
10595 461 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10596 : }
10597 : }
10598 571 : snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
10599 571 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10600 571 : CPLFree(psTemp);
10601 571 : break;
10602 : }
10603 620 : case NC_INT:
10604 : {
10605 620 : int *pnTemp = static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
10606 620 : nc_get_att_int(nCdfId, nVarId, pszAttrName, pnTemp);
10607 620 : dfValue = static_cast<double>(pnTemp[0]);
10608 620 : if (nAttrLen > 1)
10609 : {
10610 218 : for (m = 0; m < nAttrLen - 1; m++)
10611 : {
10612 139 : snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
10613 139 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10614 : }
10615 : }
10616 620 : snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
10617 620 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10618 620 : CPLFree(pnTemp);
10619 620 : break;
10620 : }
10621 419 : case NC_FLOAT:
10622 : {
10623 : float *pfTemp =
10624 419 : static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
10625 419 : nc_get_att_float(nCdfId, nVarId, pszAttrName, pfTemp);
10626 419 : dfValue = static_cast<double>(pfTemp[0]);
10627 419 : if (nAttrLen > 1)
10628 : {
10629 60 : for (m = 0; m < nAttrLen - 1; m++)
10630 : {
10631 30 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
10632 30 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10633 : }
10634 : }
10635 419 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
10636 419 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10637 419 : CPLFree(pfTemp);
10638 419 : break;
10639 : }
10640 1981 : case NC_DOUBLE:
10641 : {
10642 : double *pdfTemp =
10643 1981 : static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
10644 1981 : nc_get_att_double(nCdfId, nVarId, pszAttrName, pdfTemp);
10645 1981 : dfValue = pdfTemp[0];
10646 1981 : if (nAttrLen > 1)
10647 : {
10648 168 : for (m = 0; m < nAttrLen - 1; m++)
10649 : {
10650 91 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
10651 91 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10652 : }
10653 : }
10654 1981 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
10655 1981 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10656 1981 : CPLFree(pdfTemp);
10657 1981 : break;
10658 : }
10659 264 : case NC_STRING:
10660 : {
10661 : char **ppszTemp =
10662 264 : static_cast<char **>(CPLCalloc(nAttrLen, sizeof(char *)));
10663 264 : nc_get_att_string(nCdfId, nVarId, pszAttrName, ppszTemp);
10664 264 : bSetDoubleFromStr = true;
10665 264 : dfValue = 0.0;
10666 264 : if (nAttrLen > 1)
10667 : {
10668 19 : for (m = 0; m < nAttrLen - 1; m++)
10669 : {
10670 12 : NCDFSafeStrcat(&pszAttrValue,
10671 12 : ppszTemp[m] ? ppszTemp[m] : "{NULL}",
10672 : &nAttrValueSize);
10673 12 : NCDFSafeStrcat(&pszAttrValue, ",", &nAttrValueSize);
10674 : }
10675 : }
10676 264 : NCDFSafeStrcat(&pszAttrValue, ppszTemp[m] ? ppszTemp[m] : "{NULL}",
10677 : &nAttrValueSize);
10678 264 : nc_free_string(nAttrLen, ppszTemp);
10679 264 : CPLFree(ppszTemp);
10680 264 : break;
10681 : }
10682 28 : case NC_UBYTE:
10683 : {
10684 : unsigned char *pucTemp = static_cast<unsigned char *>(
10685 28 : CPLCalloc(nAttrLen, sizeof(unsigned char)));
10686 28 : nc_get_att_uchar(nCdfId, nVarId, pszAttrName, pucTemp);
10687 28 : dfValue = static_cast<double>(pucTemp[0]);
10688 28 : if (nAttrLen > 1)
10689 : {
10690 0 : for (m = 0; m < nAttrLen - 1; m++)
10691 : {
10692 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
10693 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10694 : }
10695 : }
10696 28 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
10697 28 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10698 28 : CPLFree(pucTemp);
10699 28 : break;
10700 : }
10701 26 : case NC_USHORT:
10702 : {
10703 : unsigned short *pusTemp = static_cast<unsigned short *>(
10704 26 : CPLCalloc(nAttrLen, sizeof(unsigned short)));
10705 26 : nc_get_att_ushort(nCdfId, nVarId, pszAttrName, pusTemp);
10706 26 : dfValue = static_cast<double>(pusTemp[0]);
10707 26 : if (nAttrLen > 1)
10708 : {
10709 10 : for (m = 0; m < nAttrLen - 1; m++)
10710 : {
10711 5 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
10712 5 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10713 : }
10714 : }
10715 26 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
10716 26 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10717 26 : CPLFree(pusTemp);
10718 26 : break;
10719 : }
10720 21 : case NC_UINT:
10721 : {
10722 : unsigned int *punTemp =
10723 21 : static_cast<unsigned int *>(CPLCalloc(nAttrLen, sizeof(int)));
10724 21 : nc_get_att_uint(nCdfId, nVarId, pszAttrName, punTemp);
10725 21 : dfValue = static_cast<double>(punTemp[0]);
10726 21 : if (nAttrLen > 1)
10727 : {
10728 0 : for (m = 0; m < nAttrLen - 1; m++)
10729 : {
10730 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
10731 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10732 : }
10733 : }
10734 21 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
10735 21 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10736 21 : CPLFree(punTemp);
10737 21 : break;
10738 : }
10739 49 : case NC_INT64:
10740 : {
10741 : GIntBig *panTemp =
10742 49 : static_cast<GIntBig *>(CPLCalloc(nAttrLen, sizeof(GIntBig)));
10743 49 : nc_get_att_longlong(nCdfId, nVarId, pszAttrName, panTemp);
10744 49 : dfValue = static_cast<double>(panTemp[0]);
10745 49 : if (nAttrLen > 1)
10746 : {
10747 0 : for (m = 0; m < nAttrLen - 1; m++)
10748 : {
10749 0 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",",
10750 0 : panTemp[m]);
10751 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10752 : }
10753 : }
10754 49 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, panTemp[m]);
10755 49 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10756 49 : CPLFree(panTemp);
10757 49 : break;
10758 : }
10759 22 : case NC_UINT64:
10760 : {
10761 : GUIntBig *panTemp =
10762 22 : static_cast<GUIntBig *>(CPLCalloc(nAttrLen, sizeof(GUIntBig)));
10763 22 : nc_get_att_ulonglong(nCdfId, nVarId, pszAttrName, panTemp);
10764 22 : dfValue = static_cast<double>(panTemp[0]);
10765 22 : if (nAttrLen > 1)
10766 : {
10767 0 : for (m = 0; m < nAttrLen - 1; m++)
10768 : {
10769 0 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",",
10770 0 : panTemp[m]);
10771 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10772 : }
10773 : }
10774 22 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, panTemp[m]);
10775 22 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10776 22 : CPLFree(panTemp);
10777 22 : break;
10778 : }
10779 26 : default:
10780 26 : CPLDebug("GDAL_netCDF",
10781 : "NCDFGetAttr unsupported type %d for attribute %s",
10782 : nAttrType, pszAttrName);
10783 26 : break;
10784 : }
10785 :
10786 37020 : if (nAttrLen > 1 && nAttrType != NC_CHAR)
10787 680 : NCDFSafeStrcat(&pszAttrValue, "}", &nAttrValueSize);
10788 :
10789 37020 : if (bSetDoubleFromStr)
10790 : {
10791 33163 : if (CPLGetValueType(pszAttrValue) == CPL_VALUE_STRING)
10792 : {
10793 32975 : if (ppszValue == nullptr && pdfValue != nullptr)
10794 : {
10795 1 : CPLFree(pszAttrValue);
10796 1 : return CE_Failure;
10797 : }
10798 : }
10799 33162 : dfValue = CPLAtof(pszAttrValue);
10800 : }
10801 :
10802 : /* set return values */
10803 37019 : if (ppszValue)
10804 36695 : *ppszValue = pszAttrValue;
10805 : else
10806 324 : CPLFree(pszAttrValue);
10807 :
10808 37019 : if (pdfValue)
10809 324 : *pdfValue = dfValue;
10810 :
10811 37019 : return CE_None;
10812 : }
10813 :
10814 : /* sets pdfValue to first value found */
10815 1357 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
10816 : double *pdfValue)
10817 : {
10818 1357 : return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, pdfValue, nullptr);
10819 : }
10820 :
10821 : /* pszValue is the responsibility of the caller and must be freed */
10822 77811 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
10823 : char **pszValue)
10824 : {
10825 77811 : return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, nullptr, pszValue);
10826 : }
10827 :
10828 3172 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
10829 : std::string &osValue)
10830 : {
10831 3172 : nc_type nAttrType = NC_NAT;
10832 3172 : size_t nAttrLen = 0;
10833 :
10834 3172 : int status = nc_inq_att(nCdfId, nVarId, pszAttrName, &nAttrType, &nAttrLen);
10835 3172 : if (status != NC_NOERR)
10836 1391 : return CE_Failure;
10837 :
10838 1781 : if (nAttrType != NC_CHAR)
10839 2 : return CE_Failure;
10840 :
10841 : try
10842 : {
10843 1779 : osValue.resize(nAttrLen, 0);
10844 : }
10845 0 : catch (const std::exception &)
10846 : {
10847 0 : return CE_Failure;
10848 : }
10849 :
10850 1779 : const auto nErr = nc_get_att_text(nCdfId, nVarId, pszAttrName,
10851 1779 : osValue.data()) != NC_NOERR;
10852 1779 : NCDF_ERR_RET(nErr);
10853 :
10854 1779 : return CE_None;
10855 : }
10856 :
10857 : /* By default write NC_CHAR, but detect for int/float/double and */
10858 : /* NC4 string arrays */
10859 184 : static CPLErr NCDFPutAttr(int nCdfId, int nVarId, const char *pszAttrName,
10860 : const char *pszValue)
10861 : {
10862 184 : int status = 0;
10863 184 : char *pszTemp = nullptr;
10864 :
10865 : /* get the attribute values as tokens */
10866 368 : CPLStringList aosValues = NCDFTokenizeArray(pszValue);
10867 184 : if (aosValues.empty())
10868 0 : return CE_Failure;
10869 :
10870 184 : size_t nAttrLen = aosValues.size();
10871 :
10872 : /* first detect type */
10873 184 : nc_type nAttrType = NC_CHAR;
10874 184 : nc_type nTmpAttrType = NC_CHAR;
10875 381 : for (size_t i = 0; i < nAttrLen; i++)
10876 : {
10877 197 : nTmpAttrType = NC_CHAR;
10878 197 : bool bFoundType = false;
10879 197 : errno = 0;
10880 197 : int nValue = static_cast<int>(strtol(aosValues[i], &pszTemp, 10));
10881 : /* test for int */
10882 : /* TODO test for Byte and short - can this be done safely? */
10883 197 : if (errno == 0 && aosValues[i] != pszTemp && *pszTemp == 0)
10884 : {
10885 : char szTemp[256];
10886 35 : CPLsnprintf(szTemp, sizeof(szTemp), "%d", nValue);
10887 35 : if (EQUAL(szTemp, aosValues[i]))
10888 : {
10889 35 : bFoundType = true;
10890 35 : nTmpAttrType = NC_INT;
10891 : }
10892 : else
10893 : {
10894 : unsigned int unValue = static_cast<unsigned int>(
10895 0 : strtoul(aosValues[i], &pszTemp, 10));
10896 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", unValue);
10897 0 : if (EQUAL(szTemp, aosValues[i]))
10898 : {
10899 0 : bFoundType = true;
10900 0 : nTmpAttrType = NC_UINT;
10901 : }
10902 : }
10903 : }
10904 197 : if (!bFoundType)
10905 : {
10906 : /* test for double */
10907 162 : errno = 0;
10908 162 : double dfValue = CPLStrtod(aosValues[i], &pszTemp);
10909 162 : if ((errno == 0) && (aosValues[i] != pszTemp) && (*pszTemp == 0))
10910 : {
10911 : // Test for float instead of double.
10912 : // strtof() is C89, which is not available in MSVC.
10913 : // See if we lose precision if we cast to float and write to
10914 : // char*.
10915 18 : float fValue = float(dfValue);
10916 : char szTemp[256];
10917 18 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", fValue);
10918 18 : if (EQUAL(szTemp, aosValues[i]))
10919 11 : nTmpAttrType = NC_FLOAT;
10920 : else
10921 7 : nTmpAttrType = NC_DOUBLE;
10922 : }
10923 : }
10924 197 : if ((nTmpAttrType <= NC_DOUBLE && nAttrType <= NC_DOUBLE &&
10925 157 : nTmpAttrType > nAttrType) ||
10926 157 : (nTmpAttrType == NC_UINT && nAttrType < NC_FLOAT) ||
10927 5 : (nTmpAttrType >= NC_FLOAT && nAttrType == NC_UINT))
10928 40 : nAttrType = nTmpAttrType;
10929 : }
10930 :
10931 : #ifdef DEBUG
10932 184 : if (EQUAL(pszAttrName, "DEBUG_EMPTY_DOUBLE_ATTR"))
10933 : {
10934 0 : nAttrType = NC_DOUBLE;
10935 0 : nAttrLen = 0;
10936 : }
10937 : #endif
10938 :
10939 : /* now write the data */
10940 184 : if (nAttrType == NC_CHAR)
10941 : {
10942 144 : int nTmpFormat = 0;
10943 144 : if (nAttrLen > 1)
10944 : {
10945 0 : status = nc_inq_format(nCdfId, &nTmpFormat);
10946 0 : NCDF_ERR(status);
10947 : }
10948 144 : if (nAttrLen > 1 && nTmpFormat == NCDF_FORMAT_NC4)
10949 0 : status =
10950 0 : nc_put_att_string(nCdfId, nVarId, pszAttrName, nAttrLen,
10951 0 : const_cast<const char **>(aosValues.List()));
10952 : else
10953 144 : status = nc_put_att_text(nCdfId, nVarId, pszAttrName,
10954 : strlen(pszValue), pszValue);
10955 144 : NCDF_ERR(status);
10956 : }
10957 : else
10958 : {
10959 40 : switch (nAttrType)
10960 : {
10961 27 : case NC_INT:
10962 : {
10963 : int *pnTemp =
10964 27 : static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
10965 62 : for (size_t i = 0; i < nAttrLen; i++)
10966 : {
10967 35 : pnTemp[i] =
10968 35 : static_cast<int>(strtol(aosValues[i], &pszTemp, 10));
10969 : }
10970 27 : status = nc_put_att_int(nCdfId, nVarId, pszAttrName, NC_INT,
10971 : nAttrLen, pnTemp);
10972 27 : NCDF_ERR(status);
10973 27 : CPLFree(pnTemp);
10974 27 : break;
10975 : }
10976 0 : case NC_UINT:
10977 : {
10978 : unsigned int *punTemp = static_cast<unsigned int *>(
10979 0 : CPLCalloc(nAttrLen, sizeof(unsigned int)));
10980 0 : for (size_t i = 0; i < nAttrLen; i++)
10981 : {
10982 0 : punTemp[i] = static_cast<unsigned int>(
10983 0 : strtol(aosValues[i], &pszTemp, 10));
10984 : }
10985 0 : status = nc_put_att_uint(nCdfId, nVarId, pszAttrName, NC_UINT,
10986 : nAttrLen, punTemp);
10987 0 : NCDF_ERR(status);
10988 0 : CPLFree(punTemp);
10989 0 : break;
10990 : }
10991 9 : case NC_FLOAT:
10992 : {
10993 : float *pfTemp =
10994 9 : static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
10995 20 : for (size_t i = 0; i < nAttrLen; i++)
10996 : {
10997 11 : pfTemp[i] =
10998 11 : static_cast<float>(CPLStrtod(aosValues[i], &pszTemp));
10999 : }
11000 9 : status = nc_put_att_float(nCdfId, nVarId, pszAttrName, NC_FLOAT,
11001 : nAttrLen, pfTemp);
11002 9 : NCDF_ERR(status);
11003 9 : CPLFree(pfTemp);
11004 9 : break;
11005 : }
11006 4 : case NC_DOUBLE:
11007 : {
11008 : double *pdfTemp =
11009 4 : static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
11010 11 : for (size_t i = 0; i < nAttrLen; i++)
11011 : {
11012 7 : pdfTemp[i] = CPLStrtod(aosValues[i], &pszTemp);
11013 : }
11014 4 : status = nc_put_att_double(nCdfId, nVarId, pszAttrName,
11015 : NC_DOUBLE, nAttrLen, pdfTemp);
11016 4 : NCDF_ERR(status);
11017 4 : CPLFree(pdfTemp);
11018 4 : break;
11019 : }
11020 0 : default:
11021 0 : return CE_Failure;
11022 : }
11023 : }
11024 :
11025 184 : return CE_None;
11026 : }
11027 :
11028 82 : static CPLErr NCDFGet1DVar(int nCdfId, int nVarId, char **pszValue)
11029 : {
11030 : /* get var information */
11031 82 : int nVarDimId = -1;
11032 82 : int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
11033 82 : if (status != NC_NOERR || nVarDimId != 1)
11034 0 : return CE_Failure;
11035 :
11036 82 : status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
11037 82 : if (status != NC_NOERR)
11038 0 : return CE_Failure;
11039 :
11040 82 : nc_type nVarType = NC_NAT;
11041 82 : status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
11042 82 : if (status != NC_NOERR)
11043 0 : return CE_Failure;
11044 :
11045 82 : size_t nVarLen = 0;
11046 82 : status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
11047 82 : if (status != NC_NOERR)
11048 0 : return CE_Failure;
11049 :
11050 82 : size_t start[1] = {0};
11051 82 : size_t count[1] = {nVarLen};
11052 :
11053 : /* Allocate guaranteed minimum size */
11054 82 : size_t nVarValueSize = NCDF_MAX_STR_LEN;
11055 : char *pszVarValue =
11056 82 : static_cast<char *>(CPLCalloc(nVarValueSize, sizeof(char)));
11057 82 : *pszVarValue = '\0';
11058 :
11059 82 : if (nVarLen == 0)
11060 : {
11061 : /* set return values */
11062 1 : *pszValue = pszVarValue;
11063 :
11064 1 : return CE_None;
11065 : }
11066 :
11067 81 : if (nVarLen > 1 && nVarType != NC_CHAR)
11068 43 : NCDFSafeStrcat(&pszVarValue, "{", &nVarValueSize);
11069 :
11070 81 : switch (nVarType)
11071 : {
11072 0 : case NC_CHAR:
11073 0 : nc_get_vara_text(nCdfId, nVarId, start, count, pszVarValue);
11074 0 : pszVarValue[nVarLen] = '\0';
11075 0 : break;
11076 0 : case NC_BYTE:
11077 : {
11078 : signed char *pscTemp = static_cast<signed char *>(
11079 0 : CPLCalloc(nVarLen, sizeof(signed char)));
11080 0 : nc_get_vara_schar(nCdfId, nVarId, start, count, pscTemp);
11081 : char szTemp[256];
11082 0 : size_t m = 0;
11083 0 : for (; m < nVarLen - 1; m++)
11084 : {
11085 0 : snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
11086 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11087 : }
11088 0 : snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
11089 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11090 0 : CPLFree(pscTemp);
11091 0 : break;
11092 : }
11093 0 : case NC_SHORT:
11094 : {
11095 : short *psTemp =
11096 0 : static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
11097 0 : nc_get_vara_short(nCdfId, nVarId, start, count, psTemp);
11098 : char szTemp[256];
11099 0 : size_t m = 0;
11100 0 : for (; m < nVarLen - 1; m++)
11101 : {
11102 0 : snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
11103 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11104 : }
11105 0 : snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
11106 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11107 0 : CPLFree(psTemp);
11108 0 : break;
11109 : }
11110 22 : case NC_INT:
11111 : {
11112 22 : int *pnTemp = static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
11113 22 : nc_get_vara_int(nCdfId, nVarId, start, count, pnTemp);
11114 : char szTemp[256];
11115 22 : size_t m = 0;
11116 47 : for (; m < nVarLen - 1; m++)
11117 : {
11118 25 : snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
11119 25 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11120 : }
11121 22 : snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
11122 22 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11123 22 : CPLFree(pnTemp);
11124 22 : break;
11125 : }
11126 8 : case NC_FLOAT:
11127 : {
11128 : float *pfTemp =
11129 8 : static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
11130 8 : nc_get_vara_float(nCdfId, nVarId, start, count, pfTemp);
11131 : char szTemp[256];
11132 8 : size_t m = 0;
11133 325 : for (; m < nVarLen - 1; m++)
11134 : {
11135 317 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
11136 317 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11137 : }
11138 8 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
11139 8 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11140 8 : CPLFree(pfTemp);
11141 8 : break;
11142 : }
11143 48 : case NC_DOUBLE:
11144 : {
11145 : double *pdfTemp =
11146 48 : static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
11147 48 : nc_get_vara_double(nCdfId, nVarId, start, count, pdfTemp);
11148 : char szTemp[256];
11149 48 : size_t m = 0;
11150 226 : for (; m < nVarLen - 1; m++)
11151 : {
11152 178 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
11153 178 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11154 : }
11155 48 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
11156 48 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11157 48 : CPLFree(pdfTemp);
11158 48 : break;
11159 : }
11160 0 : case NC_STRING:
11161 : {
11162 : char **ppszTemp =
11163 0 : static_cast<char **>(CPLCalloc(nVarLen, sizeof(char *)));
11164 0 : nc_get_vara_string(nCdfId, nVarId, start, count, ppszTemp);
11165 0 : size_t m = 0;
11166 0 : for (; m < nVarLen - 1; m++)
11167 : {
11168 0 : NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
11169 0 : NCDFSafeStrcat(&pszVarValue, ",", &nVarValueSize);
11170 : }
11171 0 : NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
11172 0 : nc_free_string(nVarLen, ppszTemp);
11173 0 : CPLFree(ppszTemp);
11174 0 : break;
11175 : }
11176 0 : case NC_UBYTE:
11177 : {
11178 : unsigned char *pucTemp = static_cast<unsigned char *>(
11179 0 : CPLCalloc(nVarLen, sizeof(unsigned char)));
11180 0 : nc_get_vara_uchar(nCdfId, nVarId, start, count, pucTemp);
11181 : char szTemp[256];
11182 0 : size_t m = 0;
11183 0 : for (; m < nVarLen - 1; m++)
11184 : {
11185 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
11186 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11187 : }
11188 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
11189 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11190 0 : CPLFree(pucTemp);
11191 0 : break;
11192 : }
11193 0 : case NC_USHORT:
11194 : {
11195 : unsigned short *pusTemp = static_cast<unsigned short *>(
11196 0 : CPLCalloc(nVarLen, sizeof(unsigned short)));
11197 0 : nc_get_vara_ushort(nCdfId, nVarId, start, count, pusTemp);
11198 : char szTemp[256];
11199 0 : size_t m = 0;
11200 0 : for (; m < nVarLen - 1; m++)
11201 : {
11202 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
11203 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11204 : }
11205 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
11206 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11207 0 : CPLFree(pusTemp);
11208 0 : break;
11209 : }
11210 0 : case NC_UINT:
11211 : {
11212 : unsigned int *punTemp = static_cast<unsigned int *>(
11213 0 : CPLCalloc(nVarLen, sizeof(unsigned int)));
11214 0 : nc_get_vara_uint(nCdfId, nVarId, start, count, punTemp);
11215 : char szTemp[256];
11216 0 : size_t m = 0;
11217 0 : for (; m < nVarLen - 1; m++)
11218 : {
11219 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
11220 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11221 : }
11222 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
11223 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11224 0 : CPLFree(punTemp);
11225 0 : break;
11226 : }
11227 3 : case NC_INT64:
11228 : {
11229 : long long *pnTemp =
11230 3 : static_cast<long long *>(CPLCalloc(nVarLen, sizeof(long long)));
11231 3 : nc_get_vara_longlong(nCdfId, nVarId, start, count, pnTemp);
11232 : char szTemp[256];
11233 3 : size_t m = 0;
11234 4 : for (; m < nVarLen - 1; m++)
11235 : {
11236 1 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",", pnTemp[m]);
11237 1 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11238 : }
11239 3 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, pnTemp[m]);
11240 3 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11241 3 : CPLFree(pnTemp);
11242 3 : break;
11243 : }
11244 0 : case NC_UINT64:
11245 : {
11246 : unsigned long long *pnTemp = static_cast<unsigned long long *>(
11247 0 : CPLCalloc(nVarLen, sizeof(unsigned long long)));
11248 0 : nc_get_vara_ulonglong(nCdfId, nVarId, start, count, pnTemp);
11249 : char szTemp[256];
11250 0 : size_t m = 0;
11251 0 : for (; m < nVarLen - 1; m++)
11252 : {
11253 0 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",", pnTemp[m]);
11254 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11255 : }
11256 0 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, pnTemp[m]);
11257 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11258 0 : CPLFree(pnTemp);
11259 0 : break;
11260 : }
11261 0 : default:
11262 0 : CPLDebug("GDAL_netCDF", "NCDFGetVar1D unsupported type %d",
11263 : nVarType);
11264 0 : CPLFree(pszVarValue);
11265 0 : pszVarValue = nullptr;
11266 0 : break;
11267 : }
11268 :
11269 81 : if (pszVarValue != nullptr && nVarLen > 1 && nVarType != NC_CHAR)
11270 43 : NCDFSafeStrcat(&pszVarValue, "}", &nVarValueSize);
11271 :
11272 : /* set return values */
11273 81 : *pszValue = pszVarValue;
11274 :
11275 81 : return CE_None;
11276 : }
11277 :
11278 9 : static CPLErr NCDFPut1DVar(int nCdfId, int nVarId, const char *pszValue)
11279 : {
11280 9 : if (EQUAL(pszValue, ""))
11281 0 : return CE_Failure;
11282 :
11283 : /* get var information */
11284 9 : int nVarDimId = -1;
11285 9 : int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
11286 9 : if (status != NC_NOERR || nVarDimId != 1)
11287 0 : return CE_Failure;
11288 :
11289 9 : status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
11290 9 : if (status != NC_NOERR)
11291 0 : return CE_Failure;
11292 :
11293 9 : nc_type nVarType = NC_CHAR;
11294 9 : status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
11295 9 : if (status != NC_NOERR)
11296 0 : return CE_Failure;
11297 :
11298 9 : size_t nVarLen = 0;
11299 9 : status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
11300 9 : if (status != NC_NOERR)
11301 0 : return CE_Failure;
11302 :
11303 9 : size_t start[1] = {0};
11304 9 : size_t count[1] = {nVarLen};
11305 :
11306 : /* get the values as tokens */
11307 18 : CPLStringList aosValues = NCDFTokenizeArray(pszValue);
11308 9 : if (aosValues.empty())
11309 0 : return CE_Failure;
11310 :
11311 9 : nVarLen = aosValues.size();
11312 :
11313 : /* now write the data */
11314 9 : if (nVarType == NC_CHAR)
11315 : {
11316 0 : status = nc_put_vara_text(nCdfId, nVarId, start, count, pszValue);
11317 0 : NCDF_ERR(status);
11318 : }
11319 : else
11320 : {
11321 9 : switch (nVarType)
11322 : {
11323 0 : case NC_BYTE:
11324 : {
11325 : signed char *pscTemp = static_cast<signed char *>(
11326 0 : CPLCalloc(nVarLen, sizeof(signed char)));
11327 0 : for (size_t i = 0; i < nVarLen; i++)
11328 : {
11329 0 : char *pszTemp = nullptr;
11330 0 : pscTemp[i] = static_cast<signed char>(
11331 0 : strtol(aosValues[i], &pszTemp, 10));
11332 : }
11333 : status =
11334 0 : nc_put_vara_schar(nCdfId, nVarId, start, count, pscTemp);
11335 0 : NCDF_ERR(status);
11336 0 : CPLFree(pscTemp);
11337 0 : break;
11338 : }
11339 0 : case NC_SHORT:
11340 : {
11341 : short *psTemp =
11342 0 : static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
11343 0 : for (size_t i = 0; i < nVarLen; i++)
11344 : {
11345 0 : char *pszTemp = nullptr;
11346 0 : psTemp[i] =
11347 0 : static_cast<short>(strtol(aosValues[i], &pszTemp, 10));
11348 : }
11349 : status =
11350 0 : nc_put_vara_short(nCdfId, nVarId, start, count, psTemp);
11351 0 : NCDF_ERR(status);
11352 0 : CPLFree(psTemp);
11353 0 : break;
11354 : }
11355 3 : case NC_INT:
11356 : {
11357 : int *pnTemp =
11358 3 : static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
11359 11 : for (size_t i = 0; i < nVarLen; i++)
11360 : {
11361 8 : char *pszTemp = nullptr;
11362 8 : pnTemp[i] =
11363 8 : static_cast<int>(strtol(aosValues[i], &pszTemp, 10));
11364 : }
11365 3 : status = nc_put_vara_int(nCdfId, nVarId, start, count, pnTemp);
11366 3 : NCDF_ERR(status);
11367 3 : CPLFree(pnTemp);
11368 3 : break;
11369 : }
11370 0 : case NC_FLOAT:
11371 : {
11372 : float *pfTemp =
11373 0 : static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
11374 0 : for (size_t i = 0; i < nVarLen; i++)
11375 : {
11376 0 : char *pszTemp = nullptr;
11377 0 : pfTemp[i] =
11378 0 : static_cast<float>(CPLStrtod(aosValues[i], &pszTemp));
11379 : }
11380 : status =
11381 0 : nc_put_vara_float(nCdfId, nVarId, start, count, pfTemp);
11382 0 : NCDF_ERR(status);
11383 0 : CPLFree(pfTemp);
11384 0 : break;
11385 : }
11386 5 : case NC_DOUBLE:
11387 : {
11388 : double *pdfTemp =
11389 5 : static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
11390 19 : for (size_t i = 0; i < nVarLen; i++)
11391 : {
11392 14 : char *pszTemp = nullptr;
11393 14 : pdfTemp[i] = CPLStrtod(aosValues[i], &pszTemp);
11394 : }
11395 : status =
11396 5 : nc_put_vara_double(nCdfId, nVarId, start, count, pdfTemp);
11397 5 : NCDF_ERR(status);
11398 5 : CPLFree(pdfTemp);
11399 5 : break;
11400 : }
11401 1 : default:
11402 : {
11403 1 : int nTmpFormat = 0;
11404 1 : status = nc_inq_format(nCdfId, &nTmpFormat);
11405 1 : NCDF_ERR(status);
11406 1 : if (nTmpFormat == NCDF_FORMAT_NC4)
11407 : {
11408 1 : switch (nVarType)
11409 : {
11410 0 : case NC_STRING:
11411 : {
11412 0 : status = nc_put_vara_string(
11413 : nCdfId, nVarId, start, count,
11414 0 : const_cast<const char **>(aosValues.List()));
11415 0 : NCDF_ERR(status);
11416 0 : break;
11417 : }
11418 0 : case NC_UBYTE:
11419 : {
11420 : unsigned char *pucTemp =
11421 : static_cast<unsigned char *>(
11422 0 : CPLCalloc(nVarLen, sizeof(unsigned char)));
11423 0 : for (size_t i = 0; i < nVarLen; i++)
11424 : {
11425 0 : char *pszTemp = nullptr;
11426 0 : pucTemp[i] = static_cast<unsigned char>(
11427 0 : strtoul(aosValues[i], &pszTemp, 10));
11428 : }
11429 0 : status = nc_put_vara_uchar(nCdfId, nVarId, start,
11430 : count, pucTemp);
11431 0 : NCDF_ERR(status);
11432 0 : CPLFree(pucTemp);
11433 0 : break;
11434 : }
11435 0 : case NC_USHORT:
11436 : {
11437 : unsigned short *pusTemp =
11438 : static_cast<unsigned short *>(
11439 0 : CPLCalloc(nVarLen, sizeof(unsigned short)));
11440 0 : for (size_t i = 0; i < nVarLen; i++)
11441 : {
11442 0 : char *pszTemp = nullptr;
11443 0 : pusTemp[i] = static_cast<unsigned short>(
11444 0 : strtoul(aosValues[i], &pszTemp, 10));
11445 : }
11446 0 : status = nc_put_vara_ushort(nCdfId, nVarId, start,
11447 : count, pusTemp);
11448 0 : NCDF_ERR(status);
11449 0 : CPLFree(pusTemp);
11450 0 : break;
11451 : }
11452 0 : case NC_UINT:
11453 : {
11454 : unsigned int *punTemp = static_cast<unsigned int *>(
11455 0 : CPLCalloc(nVarLen, sizeof(unsigned int)));
11456 0 : for (size_t i = 0; i < nVarLen; i++)
11457 : {
11458 0 : char *pszTemp = nullptr;
11459 0 : punTemp[i] = static_cast<unsigned int>(
11460 0 : strtoul(aosValues[i], &pszTemp, 10));
11461 : }
11462 0 : status = nc_put_vara_uint(nCdfId, nVarId, start,
11463 : count, punTemp);
11464 0 : NCDF_ERR(status);
11465 0 : CPLFree(punTemp);
11466 0 : break;
11467 : }
11468 1 : default:
11469 1 : return CE_Failure;
11470 : }
11471 : }
11472 0 : break;
11473 : }
11474 : }
11475 : }
11476 :
11477 8 : return CE_None;
11478 : }
11479 :
11480 : /************************************************************************/
11481 : /* GetDefaultNoDataValue() */
11482 : /************************************************************************/
11483 :
11484 203 : double NCDFGetDefaultNoDataValue(int nCdfId, int nVarId, int nVarType,
11485 : bool &bGotNoData)
11486 :
11487 : {
11488 203 : int nNoFill = 0;
11489 203 : double dfNoData = 0.0;
11490 :
11491 203 : switch (nVarType)
11492 : {
11493 0 : case NC_CHAR:
11494 : case NC_BYTE:
11495 : case NC_UBYTE:
11496 : // Don't do default fill-values for bytes, too risky.
11497 : // This function should not be called in those cases.
11498 0 : CPLAssert(false);
11499 : break;
11500 24 : case NC_SHORT:
11501 : {
11502 24 : short nFillVal = 0;
11503 24 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11504 : NC_NOERR)
11505 : {
11506 24 : if (!nNoFill)
11507 : {
11508 23 : bGotNoData = true;
11509 23 : dfNoData = nFillVal;
11510 : }
11511 : }
11512 : else
11513 0 : dfNoData = NC_FILL_SHORT;
11514 24 : break;
11515 : }
11516 26 : case NC_INT:
11517 : {
11518 26 : int nFillVal = 0;
11519 26 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11520 : NC_NOERR)
11521 : {
11522 26 : if (!nNoFill)
11523 : {
11524 25 : bGotNoData = true;
11525 25 : dfNoData = nFillVal;
11526 : }
11527 : }
11528 : else
11529 0 : dfNoData = NC_FILL_INT;
11530 26 : break;
11531 : }
11532 85 : case NC_FLOAT:
11533 : {
11534 85 : float fFillVal = 0;
11535 85 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &fFillVal) ==
11536 : NC_NOERR)
11537 : {
11538 85 : if (!nNoFill)
11539 : {
11540 81 : bGotNoData = true;
11541 81 : dfNoData = fFillVal;
11542 : }
11543 : }
11544 : else
11545 0 : dfNoData = NC_FILL_FLOAT;
11546 85 : break;
11547 : }
11548 35 : case NC_DOUBLE:
11549 : {
11550 35 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &dfNoData) ==
11551 : NC_NOERR)
11552 : {
11553 35 : if (!nNoFill)
11554 : {
11555 35 : bGotNoData = true;
11556 : }
11557 : }
11558 : else
11559 0 : dfNoData = NC_FILL_DOUBLE;
11560 35 : break;
11561 : }
11562 7 : case NC_USHORT:
11563 : {
11564 7 : unsigned short nFillVal = 0;
11565 7 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11566 : NC_NOERR)
11567 : {
11568 7 : if (!nNoFill)
11569 : {
11570 7 : bGotNoData = true;
11571 7 : dfNoData = nFillVal;
11572 : }
11573 : }
11574 : else
11575 0 : dfNoData = NC_FILL_USHORT;
11576 7 : break;
11577 : }
11578 7 : case NC_UINT:
11579 : {
11580 7 : unsigned int nFillVal = 0;
11581 7 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11582 : NC_NOERR)
11583 : {
11584 7 : if (!nNoFill)
11585 : {
11586 7 : bGotNoData = true;
11587 7 : dfNoData = nFillVal;
11588 : }
11589 : }
11590 : else
11591 0 : dfNoData = NC_FILL_UINT;
11592 7 : break;
11593 : }
11594 19 : default:
11595 19 : dfNoData = 0.0;
11596 19 : break;
11597 : }
11598 :
11599 203 : return dfNoData;
11600 : }
11601 :
11602 : /************************************************************************/
11603 : /* NCDFGetDefaultNoDataValueAsInt64() */
11604 : /************************************************************************/
11605 :
11606 2 : int64_t NCDFGetDefaultNoDataValueAsInt64(int nCdfId, int nVarId,
11607 : bool &bGotNoData)
11608 :
11609 : {
11610 2 : int nNoFill = 0;
11611 2 : long long nFillVal = 0;
11612 2 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
11613 : {
11614 2 : if (!nNoFill)
11615 : {
11616 2 : bGotNoData = true;
11617 2 : return static_cast<int64_t>(nFillVal);
11618 : }
11619 : }
11620 : else
11621 0 : return static_cast<int64_t>(NC_FILL_INT64);
11622 0 : return 0;
11623 : }
11624 :
11625 : /************************************************************************/
11626 : /* NCDFGetDefaultNoDataValueAsUInt64() */
11627 : /************************************************************************/
11628 :
11629 1 : uint64_t NCDFGetDefaultNoDataValueAsUInt64(int nCdfId, int nVarId,
11630 : bool &bGotNoData)
11631 :
11632 : {
11633 1 : int nNoFill = 0;
11634 1 : unsigned long long nFillVal = 0;
11635 1 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
11636 : {
11637 1 : if (!nNoFill)
11638 : {
11639 1 : bGotNoData = true;
11640 1 : return static_cast<uint64_t>(nFillVal);
11641 : }
11642 : }
11643 : else
11644 0 : return static_cast<uint64_t>(NC_FILL_UINT64);
11645 0 : return 0;
11646 : }
11647 :
11648 13503 : static int NCDFDoesVarContainAttribVal(int nCdfId,
11649 : const char *const *papszAttribNames,
11650 : const char *const *papszAttribValues,
11651 : int nVarId, const char *pszVarName,
11652 : bool bStrict = true)
11653 : {
11654 13503 : if (nVarId == -1 && pszVarName != nullptr)
11655 9183 : NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
11656 :
11657 13503 : if (nVarId == -1)
11658 1058 : return -1;
11659 :
11660 12445 : bool bFound = false;
11661 61273 : for (int i = 0; !bFound && papszAttribNames != nullptr &&
11662 58524 : papszAttribNames[i] != nullptr;
11663 : i++)
11664 : {
11665 48828 : char *pszTemp = nullptr;
11666 48828 : if (NCDFGetAttr(nCdfId, nVarId, papszAttribNames[i], &pszTemp) ==
11667 70319 : CE_None &&
11668 21491 : pszTemp != nullptr)
11669 : {
11670 21491 : if (bStrict)
11671 : {
11672 21491 : if (EQUAL(pszTemp, papszAttribValues[i]))
11673 2749 : bFound = true;
11674 : }
11675 : else
11676 : {
11677 0 : if (EQUALN(pszTemp, papszAttribValues[i],
11678 : strlen(papszAttribValues[i])))
11679 0 : bFound = true;
11680 : }
11681 21491 : CPLFree(pszTemp);
11682 : }
11683 : }
11684 12445 : return bFound;
11685 : }
11686 :
11687 2291 : static int NCDFDoesVarContainAttribVal2(int nCdfId, const char *papszAttribName,
11688 : const char *const *papszAttribValues,
11689 : int nVarId, const char *pszVarName,
11690 : int bStrict = true)
11691 : {
11692 2291 : if (nVarId == -1 && pszVarName != nullptr)
11693 1729 : NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
11694 :
11695 2291 : if (nVarId == -1)
11696 0 : return -1;
11697 :
11698 2291 : bool bFound = false;
11699 2291 : char *pszTemp = nullptr;
11700 2698 : if (NCDFGetAttr(nCdfId, nVarId, papszAttribName, &pszTemp) != CE_None ||
11701 407 : pszTemp == nullptr)
11702 1884 : return FALSE;
11703 :
11704 8257 : for (int i = 0; !bFound && i < CSLCount(papszAttribValues); i++)
11705 : {
11706 7850 : if (bStrict)
11707 : {
11708 7820 : if (EQUAL(pszTemp, papszAttribValues[i]))
11709 31 : bFound = true;
11710 : }
11711 : else
11712 : {
11713 30 : if (EQUALN(pszTemp, papszAttribValues[i],
11714 : strlen(papszAttribValues[i])))
11715 2 : bFound = true;
11716 : }
11717 : }
11718 :
11719 407 : CPLFree(pszTemp);
11720 :
11721 407 : return bFound;
11722 : }
11723 :
11724 1056 : static bool NCDFEqual(const char *papszName, const char *const *papszValues)
11725 : {
11726 1056 : if (papszName == nullptr || EQUAL(papszName, ""))
11727 0 : return false;
11728 :
11729 3267 : for (int i = 0; papszValues && papszValues[i]; ++i)
11730 : {
11731 2355 : if (EQUAL(papszName, papszValues[i]))
11732 144 : return true;
11733 : }
11734 :
11735 912 : return false;
11736 : }
11737 :
11738 : // Test that a variable is longitude/latitude coordinate,
11739 : // following CF 4.1 and 4.2.
11740 4515 : bool NCDFIsVarLongitude(int nCdfId, int nVarId, const char *pszVarName)
11741 : {
11742 : // Check for matching attributes.
11743 4515 : int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLongitudeAttribNames,
11744 : papszCFLongitudeAttribValues, nVarId,
11745 : pszVarName);
11746 : // If not found using attributes then check using var name
11747 : // unless GDAL_NETCDF_VERIFY_DIMS=STRICT.
11748 4515 : if (bVal == -1)
11749 : {
11750 342 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11751 : "STRICT"))
11752 342 : bVal = NCDFEqual(pszVarName, papszCFLongitudeVarNames);
11753 : else
11754 0 : bVal = FALSE;
11755 : }
11756 4173 : else if (bVal)
11757 : {
11758 : // Check that the units is not 'm' or '1'. See #6759
11759 852 : char *pszTemp = nullptr;
11760 1249 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11761 397 : pszTemp != nullptr)
11762 : {
11763 397 : if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
11764 100 : bVal = false;
11765 397 : CPLFree(pszTemp);
11766 : }
11767 : }
11768 :
11769 4515 : return CPL_TO_BOOL(bVal);
11770 : }
11771 :
11772 2613 : bool NCDFIsVarLatitude(int nCdfId, int nVarId, const char *pszVarName)
11773 : {
11774 2613 : int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLatitudeAttribNames,
11775 : papszCFLatitudeAttribValues, nVarId,
11776 : pszVarName);
11777 2613 : if (bVal == -1)
11778 : {
11779 191 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11780 : "STRICT"))
11781 191 : bVal = NCDFEqual(pszVarName, papszCFLatitudeVarNames);
11782 : else
11783 0 : bVal = FALSE;
11784 : }
11785 2422 : else if (bVal)
11786 : {
11787 : // Check that the units is not 'm' or '1'. See #6759
11788 593 : char *pszTemp = nullptr;
11789 740 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11790 147 : pszTemp != nullptr)
11791 : {
11792 147 : if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
11793 37 : bVal = false;
11794 147 : CPLFree(pszTemp);
11795 : }
11796 : }
11797 :
11798 2613 : return CPL_TO_BOOL(bVal);
11799 : }
11800 :
11801 2873 : bool NCDFIsVarProjectionX(int nCdfId, int nVarId, const char *pszVarName)
11802 : {
11803 2873 : int bVal = NCDFDoesVarContainAttribVal(
11804 : nCdfId, papszCFProjectionXAttribNames, papszCFProjectionXAttribValues,
11805 : nVarId, pszVarName);
11806 2873 : if (bVal == -1)
11807 : {
11808 336 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11809 : "STRICT"))
11810 336 : bVal = NCDFEqual(pszVarName, papszCFProjectionXVarNames);
11811 : else
11812 0 : bVal = FALSE;
11813 : }
11814 2537 : else if (bVal)
11815 : {
11816 : // Check that the units is not '1'
11817 533 : char *pszTemp = nullptr;
11818 818 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11819 285 : pszTemp != nullptr)
11820 : {
11821 285 : if (EQUAL(pszTemp, "1"))
11822 5 : bVal = false;
11823 285 : CPLFree(pszTemp);
11824 : }
11825 : }
11826 :
11827 2873 : return CPL_TO_BOOL(bVal);
11828 : }
11829 :
11830 2017 : bool NCDFIsVarProjectionY(int nCdfId, int nVarId, const char *pszVarName)
11831 : {
11832 2017 : int bVal = NCDFDoesVarContainAttribVal(
11833 : nCdfId, papszCFProjectionYAttribNames, papszCFProjectionYAttribValues,
11834 : nVarId, pszVarName);
11835 2017 : if (bVal == -1)
11836 : {
11837 187 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11838 : "STRICT"))
11839 187 : bVal = NCDFEqual(pszVarName, papszCFProjectionYVarNames);
11840 : else
11841 0 : bVal = FALSE;
11842 : }
11843 1830 : else if (bVal)
11844 : {
11845 : // Check that the units is not '1'
11846 525 : char *pszTemp = nullptr;
11847 803 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11848 278 : pszTemp != nullptr)
11849 : {
11850 278 : if (EQUAL(pszTemp, "1"))
11851 5 : bVal = false;
11852 278 : CPLFree(pszTemp);
11853 : }
11854 : }
11855 :
11856 2017 : return CPL_TO_BOOL(bVal);
11857 : }
11858 :
11859 : /* test that a variable is a vertical coordinate, following CF 4.3 */
11860 1215 : bool NCDFIsVarVerticalCoord(int nCdfId, int nVarId, const char *pszVarName)
11861 : {
11862 : /* check for matching attributes */
11863 1215 : if (NCDFDoesVarContainAttribVal(nCdfId, papszCFVerticalAttribNames,
11864 : papszCFVerticalAttribValues, nVarId,
11865 1215 : pszVarName))
11866 130 : return true;
11867 : /* check for matching units */
11868 1085 : else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
11869 : papszCFVerticalUnitsValues, nVarId,
11870 1085 : pszVarName))
11871 31 : return true;
11872 : /* check for matching standard name */
11873 1054 : else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_STD_NAME,
11874 : papszCFVerticalStandardNameValues,
11875 1054 : nVarId, pszVarName))
11876 0 : return true;
11877 : else
11878 1054 : return false;
11879 : }
11880 :
11881 : /* test that a variable is a time coordinate, following CF 4.4 */
11882 270 : bool NCDFIsVarTimeCoord(int nCdfId, int nVarId, const char *pszVarName)
11883 : {
11884 : /* check for matching attributes */
11885 270 : if (NCDFDoesVarContainAttribVal(nCdfId, papszCFTimeAttribNames,
11886 : papszCFTimeAttribValues, nVarId,
11887 270 : pszVarName))
11888 118 : return true;
11889 : /* check for matching units */
11890 152 : else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
11891 : papszCFTimeUnitsValues, nVarId,
11892 152 : pszVarName, false))
11893 2 : return true;
11894 : else
11895 150 : return false;
11896 : }
11897 :
11898 : // Parse a string, and return as a string list.
11899 : // If it is an array of the form {a,b}, then tokenize it.
11900 : // Otherwise, return a copy.
11901 302 : static CPLStringList NCDFTokenizeArray(const char *pszValue)
11902 : {
11903 302 : if (pszValue == nullptr || EQUAL(pszValue, ""))
11904 76 : return CPLStringList();
11905 :
11906 452 : CPLStringList aosValues;
11907 226 : const int nLen = static_cast<int>(strlen(pszValue));
11908 :
11909 226 : if (pszValue[0] == '{' && nLen > 2 && pszValue[nLen - 1] == '}')
11910 : {
11911 53 : char *pszTemp = static_cast<char *>(CPLMalloc((nLen - 2) + 1));
11912 53 : strncpy(pszTemp, pszValue + 1, nLen - 2);
11913 53 : pszTemp[nLen - 2] = '\0';
11914 : aosValues.Assign(
11915 53 : CSLTokenizeString2(pszTemp, ",", CSLT_ALLOWEMPTYTOKENS));
11916 53 : CPLFree(pszTemp);
11917 : }
11918 : else
11919 : {
11920 173 : aosValues.AddString(pszValue);
11921 : }
11922 :
11923 226 : return aosValues;
11924 : }
11925 :
11926 : // Open a NetCDF subdataset from full path /group1/group2/.../groupn/var.
11927 : // Leading slash is optional.
11928 462 : static CPLErr NCDFOpenSubDataset(int nCdfId, const char *pszSubdatasetName,
11929 : int *pnGroupId, int *pnVarId)
11930 : {
11931 462 : *pnGroupId = -1;
11932 462 : *pnVarId = -1;
11933 :
11934 : // Open group.
11935 924 : std::string osGroupFullName = CPLGetPathSafe(pszSubdatasetName);
11936 : // Add a leading slash if needed.
11937 462 : if (osGroupFullName.empty() || osGroupFullName[0] != '/')
11938 : {
11939 441 : osGroupFullName = "/" + osGroupFullName;
11940 : }
11941 : // Detect root group.
11942 462 : if (osGroupFullName == "/")
11943 : {
11944 443 : *pnGroupId = nCdfId;
11945 : }
11946 : else
11947 : {
11948 : int status =
11949 19 : nc_inq_grp_full_ncid(nCdfId, osGroupFullName.c_str(), pnGroupId);
11950 19 : NCDF_ERR_RET(status);
11951 : }
11952 :
11953 : // Open var.
11954 462 : const char *pszVarName = CPLGetFilename(pszSubdatasetName);
11955 462 : NCDF_ERR_RET(nc_inq_varid(*pnGroupId, pszVarName, pnVarId));
11956 :
11957 462 : return CE_None;
11958 : }
11959 :
11960 : // Get all dimensions visible from a given NetCDF (or group) ID and any of
11961 : // its parents.
11962 391 : static CPLErr NCDFGetVisibleDims(int nGroupId, int *pnDims, int **ppanDimIds)
11963 : {
11964 391 : int nDims = 0;
11965 391 : int *panDimIds = nullptr;
11966 391 : NCDF_ERR_RET(nc_inq_dimids(nGroupId, &nDims, nullptr, true));
11967 :
11968 391 : panDimIds = static_cast<int *>(CPLMalloc(nDims * sizeof(int)));
11969 :
11970 391 : int status = nc_inq_dimids(nGroupId, nullptr, panDimIds, true);
11971 391 : if (status != NC_NOERR)
11972 0 : CPLFree(panDimIds);
11973 391 : NCDF_ERR_RET(status);
11974 :
11975 391 : *pnDims = nDims;
11976 391 : *ppanDimIds = panDimIds;
11977 :
11978 391 : return CE_None;
11979 : }
11980 :
11981 : // Get direct sub-groups IDs of a given NetCDF (or group) ID.
11982 : // Consider only direct children, does not get children of children.
11983 3526 : static CPLErr NCDFGetSubGroups(int nGroupId, int *pnSubGroups,
11984 : int **ppanSubGroupIds)
11985 : {
11986 3526 : *pnSubGroups = 0;
11987 3526 : *ppanSubGroupIds = nullptr;
11988 :
11989 : int nSubGroups;
11990 3526 : NCDF_ERR_RET(nc_inq_grps(nGroupId, &nSubGroups, nullptr));
11991 : int *panSubGroupIds =
11992 3526 : static_cast<int *>(CPLMalloc(nSubGroups * sizeof(int)));
11993 3526 : NCDF_ERR_RET(nc_inq_grps(nGroupId, nullptr, panSubGroupIds));
11994 3526 : *pnSubGroups = nSubGroups;
11995 3526 : *ppanSubGroupIds = panSubGroupIds;
11996 :
11997 3526 : return CE_None;
11998 : }
11999 :
12000 : // Get the full name of a given NetCDF (or group) ID
12001 : // (e.g. /group1/group2/.../groupn).
12002 : // bNC3Compat remove the leading slash for top-level variables for
12003 : // backward compatibility (top-level variables are the ones in the root group).
12004 21117 : static CPLErr NCDFGetGroupFullName(int nGroupId, std::string &osFullName,
12005 : bool bNC3Compat)
12006 : {
12007 21117 : osFullName = "";
12008 :
12009 : size_t nFullNameLen;
12010 21117 : NCDF_ERR_RET(nc_inq_grpname_len(nGroupId, &nFullNameLen));
12011 21117 : osFullName.resize(nFullNameLen);
12012 :
12013 : const int status =
12014 21117 : nc_inq_grpname_full(nGroupId, &nFullNameLen, osFullName.data());
12015 21117 : if (status != NC_NOERR)
12016 : {
12017 0 : osFullName = "";
12018 0 : NCDF_ERR_RET(status);
12019 : }
12020 :
12021 21117 : if (bNC3Compat && osFullName == "/")
12022 9194 : osFullName = "";
12023 :
12024 21117 : return CE_None;
12025 : }
12026 :
12027 11708 : CPLString NCDFGetGroupFullName(int nGroupId)
12028 : {
12029 11708 : CPLString osFullName;
12030 11708 : NCDFGetGroupFullName(nGroupId, osFullName, false);
12031 :
12032 11708 : return osFullName;
12033 : }
12034 :
12035 : // Get the full name of a given NetCDF variable ID
12036 : // (e.g. /group1/group2/.../groupn/var).
12037 : // Handle also NC_GLOBAL as nVarId.
12038 : // bNC3Compat remove the leading slash for top-level variables for
12039 : // backward compatibility (top-level variables are the ones in the root group).
12040 9309 : static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId,
12041 : std::string &osFullName, bool bNC3Compat)
12042 : {
12043 9309 : osFullName = "";
12044 18618 : std::string osGroupFullName;
12045 9309 : ERR_RET(NCDFGetGroupFullName(nGroupId, osGroupFullName, bNC3Compat));
12046 : char szVarName[NC_MAX_NAME + 1];
12047 9309 : if (nVarId == NC_GLOBAL)
12048 : {
12049 1211 : strcpy(szVarName, "NC_GLOBAL");
12050 : }
12051 : else
12052 : {
12053 8098 : int status = nc_inq_varname(nGroupId, nVarId, szVarName);
12054 8098 : if (status != NC_NOERR)
12055 : {
12056 0 : NCDF_ERR_RET(status);
12057 : }
12058 : }
12059 9309 : const char *pszSep = "/";
12060 9309 : if (osGroupFullName.empty() || osGroupFullName == "/")
12061 9096 : pszSep = "";
12062 : osFullName =
12063 9309 : CPLOPrintf("%s%s%s", osGroupFullName.c_str(), pszSep, szVarName);
12064 :
12065 9309 : return CE_None;
12066 : }
12067 :
12068 : // Get the NetCDF root group ID of a given group ID.
12069 2 : static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId)
12070 : {
12071 2 : *pnRootGroupId = -1;
12072 : // Recurse on parent group.
12073 : int nParentGroupId;
12074 2 : int status = nc_inq_grp_parent(nStartGroupId, &nParentGroupId);
12075 2 : if (status == NC_NOERR)
12076 0 : return NCDFGetRootGroup(nParentGroupId, pnRootGroupId);
12077 2 : else if (status != NC_ENOGRP)
12078 0 : NCDF_ERR_RET(status);
12079 : else // No more parent group.
12080 : {
12081 2 : *pnRootGroupId = nStartGroupId;
12082 : }
12083 :
12084 2 : return CE_None;
12085 : }
12086 :
12087 : // Read the size and type of an extra dimension
12088 18 : static std::pair<int, int> ReadExtraDimDef(GDALDataset *poSrcDS,
12089 : const char *pszDimName)
12090 : {
12091 : static char szTemp[NC_MAX_NAME + 32 + 1];
12092 18 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF", pszDimName);
12093 : const CPLStringList aosExtraDimValues =
12094 18 : NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
12095 : const int nDimSize =
12096 18 : aosExtraDimValues.empty() ? 0 : atoi(aosExtraDimValues[0]);
12097 :
12098 : // nc_type is an enum in netcdf-3, needs casting.
12099 : const int nVarType = static_cast<nc_type>(
12100 18 : aosExtraDimValues.size() >= 2 ? atol(aosExtraDimValues[1]) : 0);
12101 :
12102 36 : return {nDimSize, nVarType};
12103 : }
12104 :
12105 : // Implementation of NCDFResolveVar/Att.
12106 14884 : static CPLErr NCDFResolveElem(int nStartGroupId, const char *pszVar,
12107 : const char *pszAtt, int *pnGroupId, int *pnId,
12108 : bool bMandatory)
12109 : {
12110 14884 : if (!pszVar && !pszAtt)
12111 : {
12112 0 : CPLError(CE_Failure, CPLE_IllegalArg,
12113 : "pszVar and pszAtt NCDFResolveElem() args are both null.");
12114 0 : return CE_Failure;
12115 : }
12116 :
12117 : enum
12118 : {
12119 : NCRM_PARENT,
12120 : NCRM_WIDTH_WISE
12121 14884 : } eNCResolveMode = NCRM_PARENT;
12122 :
12123 29768 : std::queue<int> aoQueueGroupIdsToVisit;
12124 14884 : aoQueueGroupIdsToVisit.push(nStartGroupId);
12125 :
12126 16863 : while (!aoQueueGroupIdsToVisit.empty())
12127 : {
12128 : // Get the first group of the FIFO queue.
12129 15079 : *pnGroupId = aoQueueGroupIdsToVisit.front();
12130 15079 : aoQueueGroupIdsToVisit.pop();
12131 :
12132 : // Look if this group contains the searched element.
12133 : int status;
12134 15079 : if (pszVar)
12135 14840 : status = nc_inq_varid(*pnGroupId, pszVar, pnId);
12136 : else // pszAtt != nullptr.
12137 239 : status = nc_inq_attid(*pnGroupId, NC_GLOBAL, pszAtt, pnId);
12138 :
12139 15079 : if (status == NC_NOERR)
12140 : {
12141 13100 : return CE_None;
12142 : }
12143 1979 : else if ((pszVar && status != NC_ENOTVAR) ||
12144 236 : (pszAtt && status != NC_ENOTATT))
12145 : {
12146 0 : NCDF_ERR(status);
12147 : }
12148 : // Element not found, in NC4 case we must search in other groups
12149 : // following the CF logic.
12150 :
12151 : // The first resolve mode consists to search on parent groups.
12152 1979 : if (eNCResolveMode == NCRM_PARENT)
12153 : {
12154 1859 : int nParentGroupId = -1;
12155 1859 : int status2 = nc_inq_grp_parent(*pnGroupId, &nParentGroupId);
12156 1859 : if (status2 == NC_NOERR)
12157 62 : aoQueueGroupIdsToVisit.push(nParentGroupId);
12158 1797 : else if (status2 != NC_ENOGRP)
12159 0 : NCDF_ERR(status2);
12160 1797 : else if (pszVar)
12161 : // When resolving a variable, if there is no more
12162 : // parent group then we switch to width-wise search mode
12163 : // starting from the latest found parent group.
12164 1564 : eNCResolveMode = NCRM_WIDTH_WISE;
12165 : }
12166 :
12167 : // The second resolve mode is a width-wise search.
12168 1979 : if (eNCResolveMode == NCRM_WIDTH_WISE)
12169 : {
12170 : // Enqueue all direct sub-groups.
12171 1684 : int nSubGroups = 0;
12172 1684 : int *panSubGroupIds = nullptr;
12173 1684 : NCDFGetSubGroups(*pnGroupId, &nSubGroups, &panSubGroupIds);
12174 1817 : for (int i = 0; i < nSubGroups; i++)
12175 133 : aoQueueGroupIdsToVisit.push(panSubGroupIds[i]);
12176 1684 : CPLFree(panSubGroupIds);
12177 : }
12178 : }
12179 :
12180 1784 : if (bMandatory)
12181 : {
12182 0 : std::string osStartGroupFullName;
12183 0 : NCDFGetGroupFullName(nStartGroupId, osStartGroupFullName);
12184 0 : CPLError(CE_Failure, CPLE_AppDefined,
12185 : "Cannot resolve mandatory %s %s from group %s",
12186 : (pszVar ? pszVar : pszAtt),
12187 : (pszVar ? "variable" : "attribute"),
12188 : osStartGroupFullName.c_str());
12189 : }
12190 :
12191 1784 : *pnGroupId = -1;
12192 1784 : *pnId = -1;
12193 1784 : return CE_Failure;
12194 : }
12195 :
12196 : // Resolve a variable name from a given starting group following the CF logic:
12197 : // - if var name is an absolute path then directly open it
12198 : // - first search in the starting group and its parent groups
12199 : // - then if there is no more parent group we switch to a width-wise search
12200 : // mode starting from the latest found parent group.
12201 : // The full CF logic is described here:
12202 : // https://github.com/diwg/cf2/blob/master/group/cf2-group.adoc#scope
12203 : // If bMandatory then print an error if resolving fails.
12204 : // TODO: implement support of relative paths.
12205 : // TODO: to follow strictly the CF logic, when searching for a coordinate
12206 : // variable, we must stop the parent search mode once the corresponding
12207 : // dimension is found and start the width-wise search from this group.
12208 : // TODO: to follow strictly the CF logic, when searching in width-wise mode
12209 : // we should skip every groups already visited during the parent
12210 : // search mode (but revisiting them should have no impact so we could
12211 : // let as it is if it is simpler...)
12212 : // TODO: CF specifies that the width-wise search order is "left-to-right" so
12213 : // maybe we must sort sibling groups alphabetically? but maybe not
12214 : // necessary if nc_inq_grps() already sort them?
12215 14650 : CPLErr NCDFResolveVar(int nStartGroupId, const char *pszVar, int *pnGroupId,
12216 : int *pnVarId, bool bMandatory)
12217 : {
12218 14650 : *pnGroupId = -1;
12219 14650 : *pnVarId = -1;
12220 14650 : int nGroupId = nStartGroupId, nVarId;
12221 14650 : if (pszVar[0] == '/')
12222 : {
12223 : // This is an absolute path: we can open the var directly.
12224 : int nRootGroupId;
12225 2 : ERR_RET(NCDFGetRootGroup(nStartGroupId, &nRootGroupId));
12226 2 : ERR_RET(NCDFOpenSubDataset(nRootGroupId, pszVar, &nGroupId, &nVarId));
12227 : }
12228 : else
12229 : {
12230 : // We have to search the variable following the CF logic.
12231 14648 : ERR_RET(NCDFResolveElem(nStartGroupId, pszVar, nullptr, &nGroupId,
12232 : &nVarId, bMandatory));
12233 : }
12234 13099 : *pnGroupId = nGroupId;
12235 13099 : *pnVarId = nVarId;
12236 13099 : return CE_None;
12237 : }
12238 :
12239 : // Like NCDFResolveVar but returns directly the var full name.
12240 1567 : static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,
12241 : std::string &osFullName, bool bMandatory)
12242 : {
12243 : int nGroupId, nVarId;
12244 1567 : osFullName = "";
12245 1567 : ERR_RET(
12246 : NCDFResolveVar(nStartGroupId, pszVar, &nGroupId, &nVarId, bMandatory));
12247 1537 : return NCDFGetVarFullName(nGroupId, nVarId, osFullName);
12248 : }
12249 :
12250 : // Like NCDFResolveVar but resolves an attribute instead a variable and
12251 : // returns its integer value.
12252 : // Only GLOBAL attributes are supported for the moment.
12253 236 : static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,
12254 : const char *pszAtt, int *pnAtt, bool bMandatory)
12255 : {
12256 236 : int nGroupId = nStartGroupId, nAttId = nStartVarId;
12257 236 : ERR_RET(NCDFResolveElem(nStartGroupId, nullptr, pszAtt, &nGroupId, &nAttId,
12258 : bMandatory));
12259 3 : NCDF_ERR_RET(nc_get_att_int(nGroupId, NC_GLOBAL, pszAtt, pnAtt));
12260 3 : return CE_None;
12261 : }
12262 :
12263 : // Filter variables to keep only valid 2+D raster bands and vector fields in
12264 : // a given a NetCDF (or group) ID and its sub-groups.
12265 : // Coordinate or boundary variables are ignored.
12266 : // It also creates corresponding vector layers.
12267 589 : CPLErr netCDFDataset::FilterVars(
12268 : int nCdfId, bool bKeepRasters, bool bKeepVectors,
12269 : const CPLStringList &aosIgnoreVars, int *pnRasterVars, int *pnGroupId,
12270 : int *pnVarId, int *pnIgnoredVars,
12271 : std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>
12272 : &oMap2DDimsToGroupAndVar)
12273 : {
12274 589 : int nVars = 0;
12275 589 : int nRasterVars = 0;
12276 589 : NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
12277 :
12278 1178 : std::vector<int> anPotentialVectorVarID;
12279 : // oMapDimIdToCount[x] = number of times dim x is the first dimension of
12280 : // potential vector variables
12281 1178 : std::map<int, int> oMapDimIdToCount;
12282 589 : int nVarXId = -1;
12283 589 : int nVarYId = -1;
12284 589 : int nVarZId = -1;
12285 589 : int nVarTimeId = -1;
12286 589 : int nVarTimeDimId = -1;
12287 589 : bool bIsVectorOnly = true;
12288 589 : int nProfileDimId = -1;
12289 589 : int nParentIndexVarID = -1;
12290 :
12291 3601 : for (int v = 0; v < nVars; v++)
12292 : {
12293 : int nVarDims;
12294 3012 : NCDF_ERR_RET(nc_inq_varndims(nCdfId, v, &nVarDims));
12295 : // Should we ignore this variable?
12296 : char szTemp[NC_MAX_NAME + 1];
12297 3012 : szTemp[0] = '\0';
12298 3012 : NCDF_ERR_RET(nc_inq_varname(nCdfId, v, szTemp));
12299 :
12300 3012 : if (strstr(szTemp, "_node_coordinates") ||
12301 3012 : strstr(szTemp, "_node_count"))
12302 : {
12303 : // Ignore CF-1.8 Simple Geometries helper variables
12304 70 : continue;
12305 : }
12306 :
12307 4390 : if (nVarDims == 1 && (NCDFIsVarLongitude(nCdfId, -1, szTemp) ||
12308 1448 : NCDFIsVarProjectionX(nCdfId, -1, szTemp)))
12309 : {
12310 403 : nVarXId = v;
12311 : }
12312 3584 : else if (nVarDims == 1 && (NCDFIsVarLatitude(nCdfId, -1, szTemp) ||
12313 1045 : NCDFIsVarProjectionY(nCdfId, -1, szTemp)))
12314 : {
12315 402 : nVarYId = v;
12316 : }
12317 2137 : else if (nVarDims == 1 && NCDFIsVarVerticalCoord(nCdfId, -1, szTemp))
12318 : {
12319 97 : nVarZId = v;
12320 : }
12321 : else
12322 : {
12323 2040 : std::string osFullName;
12324 2040 : CPLErr eErr = NCDFGetVarFullName(nCdfId, v, osFullName);
12325 2040 : if (eErr != CE_None)
12326 : {
12327 0 : continue;
12328 : }
12329 : const bool bIgnoreVar =
12330 2040 : aosIgnoreVars.FindString(osFullName.c_str()) != -1;
12331 2040 : if (bIgnoreVar)
12332 : {
12333 135 : if (nVarDims == 1 && NCDFIsVarTimeCoord(nCdfId, -1, szTemp))
12334 : {
12335 16 : nVarTimeId = v;
12336 16 : nc_inq_vardimid(nCdfId, v, &nVarTimeDimId);
12337 : }
12338 119 : else if (nVarDims > 1)
12339 : {
12340 105 : (*pnIgnoredVars)++;
12341 105 : CPLDebug("GDAL_netCDF", "variable #%d [%s] was ignored", v,
12342 : szTemp);
12343 : }
12344 : }
12345 : // Only accept 2+D vars.
12346 1905 : else if (nVarDims >= 2)
12347 : {
12348 783 : bool bRasterCandidate = true;
12349 : // Identify variables that might be vector variables
12350 783 : if (nVarDims == 2)
12351 : {
12352 698 : int anDimIds[2] = {-1, -1};
12353 698 : nc_inq_vardimid(nCdfId, v, anDimIds);
12354 :
12355 698 : nc_type vartype = NC_NAT;
12356 698 : nc_inq_vartype(nCdfId, v, &vartype);
12357 :
12358 : char szDimNameFirst[NC_MAX_NAME + 1];
12359 : char szDimNameSecond[NC_MAX_NAME + 1];
12360 698 : szDimNameFirst[0] = '\0';
12361 698 : szDimNameSecond[0] = '\0';
12362 1591 : if (vartype == NC_CHAR &&
12363 195 : nc_inq_dimname(nCdfId, anDimIds[0], szDimNameFirst) ==
12364 195 : NC_NOERR &&
12365 195 : nc_inq_dimname(nCdfId, anDimIds[1], szDimNameSecond) ==
12366 195 : NC_NOERR &&
12367 195 : !NCDFIsVarLongitude(nCdfId, -1, szDimNameSecond) &&
12368 195 : !NCDFIsVarProjectionX(nCdfId, -1, szDimNameSecond) &&
12369 1088 : !NCDFIsVarLatitude(nCdfId, -1, szDimNameFirst) &&
12370 195 : !NCDFIsVarProjectionY(nCdfId, -1, szDimNameFirst))
12371 : {
12372 195 : anPotentialVectorVarID.push_back(v);
12373 195 : oMapDimIdToCount[anDimIds[0]]++;
12374 195 : if (strstr(szDimNameSecond, "_max_width"))
12375 : {
12376 165 : bRasterCandidate = false;
12377 : }
12378 : else
12379 : {
12380 30 : std::array<int, 3> oKey{anDimIds[0], anDimIds[1],
12381 30 : vartype};
12382 30 : oMap2DDimsToGroupAndVar[oKey].emplace_back(
12383 30 : std::pair(nCdfId, v));
12384 : }
12385 : }
12386 : else
12387 : {
12388 503 : std::array<int, 3> oKey{anDimIds[0], anDimIds[1],
12389 503 : vartype};
12390 503 : oMap2DDimsToGroupAndVar[oKey].emplace_back(
12391 503 : std::pair(nCdfId, v));
12392 503 : bIsVectorOnly = false;
12393 : }
12394 : }
12395 : else
12396 : {
12397 85 : bIsVectorOnly = false;
12398 : }
12399 783 : if (bKeepRasters && bRasterCandidate)
12400 : {
12401 589 : *pnGroupId = nCdfId;
12402 589 : *pnVarId = v;
12403 589 : nRasterVars++;
12404 : }
12405 : }
12406 1122 : else if (nVarDims == 1)
12407 : {
12408 797 : nc_type atttype = NC_NAT;
12409 797 : size_t attlen = 0;
12410 797 : if (nc_inq_att(nCdfId, v, "instance_dimension", &atttype,
12411 36 : &attlen) == NC_NOERR &&
12412 797 : atttype == NC_CHAR && attlen < NC_MAX_NAME)
12413 : {
12414 72 : std::string osInstanceDimension;
12415 36 : if (NCDFGetAttr(nCdfId, v, "instance_dimension",
12416 36 : osInstanceDimension) == CE_None)
12417 : {
12418 : const int status =
12419 36 : nc_inq_dimid(nCdfId, osInstanceDimension.c_str(),
12420 : &nProfileDimId);
12421 36 : if (status == NC_NOERR)
12422 36 : nParentIndexVarID = v;
12423 : else
12424 0 : nProfileDimId = -1;
12425 36 : if (status == NC_EBADDIM)
12426 0 : CPLError(CE_Warning, CPLE_AppDefined,
12427 : "Attribute instance_dimension='%s' refers "
12428 : "to a non existing dimension",
12429 : osInstanceDimension.c_str());
12430 : else
12431 36 : NCDF_ERR(status);
12432 : }
12433 : }
12434 797 : if (v != nParentIndexVarID)
12435 : {
12436 761 : anPotentialVectorVarID.push_back(v);
12437 761 : int nDimId = -1;
12438 761 : nc_inq_vardimid(nCdfId, v, &nDimId);
12439 761 : oMapDimIdToCount[nDimId]++;
12440 : }
12441 : }
12442 : }
12443 : }
12444 :
12445 : // If we are opened in raster-only mode and that there are only 1D or 2D
12446 : // variables and that the 2D variables have no X/Y dim, and all
12447 : // variables refer to the same main dimension (or 2 dimensions for
12448 : // featureType=profile), then it is a pure vector dataset
12449 : CPLString osFeatureType(
12450 589 : aosMetadata.FetchNameValueDef("NC_GLOBAL#featureType", ""));
12451 456 : if (bKeepRasters && !bKeepVectors && bIsVectorOnly && nRasterVars > 0 &&
12452 1045 : !anPotentialVectorVarID.empty() &&
12453 0 : (oMapDimIdToCount.size() == 1 ||
12454 0 : (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2 &&
12455 0 : nProfileDimId >= 0)))
12456 : {
12457 0 : anPotentialVectorVarID.resize(0);
12458 : }
12459 : else
12460 : {
12461 589 : *pnRasterVars += nRasterVars;
12462 : }
12463 :
12464 589 : if (!anPotentialVectorVarID.empty() && bKeepVectors)
12465 : {
12466 : // Take the dimension that is referenced the most times.
12467 135 : if (!(oMapDimIdToCount.size() == 1 ||
12468 70 : (EQUAL(osFeatureType, "profile") &&
12469 70 : oMapDimIdToCount.size() == 2 && nProfileDimId >= 0)))
12470 : {
12471 0 : CPLError(CE_Warning, CPLE_AppDefined,
12472 : "The dataset has several variables that could be "
12473 : "identified as vector fields, but not all share the same "
12474 : "primary dimension. Consequently they will be ignored.");
12475 : }
12476 : else
12477 : {
12478 101 : if (nVarTimeId >= 0 &&
12479 101 : oMapDimIdToCount.find(nVarTimeDimId) != oMapDimIdToCount.end())
12480 : {
12481 1 : anPotentialVectorVarID.push_back(nVarTimeId);
12482 : }
12483 100 : CreateGrpVectorLayers(nCdfId, osFeatureType, anPotentialVectorVarID,
12484 : oMapDimIdToCount, nVarXId, nVarYId, nVarZId,
12485 : nProfileDimId, nParentIndexVarID,
12486 : bKeepRasters);
12487 : }
12488 : }
12489 :
12490 : // Recurse on sub-groups.
12491 589 : int nSubGroups = 0;
12492 589 : int *panSubGroupIds = nullptr;
12493 589 : NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
12494 626 : for (int i = 0; i < nSubGroups; i++)
12495 : {
12496 37 : FilterVars(panSubGroupIds[i], bKeepRasters, bKeepVectors, aosIgnoreVars,
12497 : pnRasterVars, pnGroupId, pnVarId, pnIgnoredVars,
12498 : oMap2DDimsToGroupAndVar);
12499 : }
12500 589 : CPLFree(panSubGroupIds);
12501 :
12502 589 : return CE_None;
12503 : }
12504 :
12505 : // Create vector layers from given potentially identified vector variables
12506 : // resulting from the scanning of a NetCDF (or group) ID.
12507 100 : CPLErr netCDFDataset::CreateGrpVectorLayers(
12508 : int nCdfId, const CPLString &osFeatureType,
12509 : const std::vector<int> &anPotentialVectorVarID,
12510 : const std::map<int, int> &oMapDimIdToCount, int nVarXId, int nVarYId,
12511 : int nVarZId, int nProfileDimId, int nParentIndexVarID, bool bKeepRasters)
12512 : {
12513 200 : std::string osGroupName;
12514 100 : NCDFGetGroupFullName(nCdfId, osGroupName);
12515 100 : if (osGroupName.empty())
12516 : {
12517 98 : osGroupName = CPLGetBasenameSafe(osFilename);
12518 : }
12519 100 : OGRwkbGeometryType eGType = wkbUnknown;
12520 : CPLString osLayerName = aosMetadata.FetchNameValueDef(
12521 200 : "NC_GLOBAL#ogr_layer_name", osGroupName.c_str());
12522 100 : aosMetadata.SetNameValue("NC_GLOBAL#ogr_layer_name", nullptr);
12523 :
12524 100 : if (EQUAL(osFeatureType, "point") || EQUAL(osFeatureType, "profile"))
12525 : {
12526 54 : aosMetadata.SetNameValue("NC_GLOBAL#featureType", nullptr);
12527 54 : eGType = wkbPoint;
12528 : }
12529 :
12530 : const char *pszLayerType =
12531 100 : aosMetadata.FetchNameValue("NC_GLOBAL#ogr_layer_type");
12532 100 : if (pszLayerType != nullptr)
12533 : {
12534 10 : eGType = OGRFromOGCGeomType(pszLayerType);
12535 10 : aosMetadata.SetNameValue("NC_GLOBAL#ogr_layer_type", nullptr);
12536 : }
12537 :
12538 : CPLString osGeometryField =
12539 200 : aosMetadata.FetchNameValueDef("NC_GLOBAL#ogr_geometry_field", "");
12540 100 : aosMetadata.SetNameValue("NC_GLOBAL#ogr_geometry_field", nullptr);
12541 :
12542 100 : int nFirstVarId = -1;
12543 100 : int nVectorDim = oMapDimIdToCount.rbegin()->first;
12544 100 : if (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2)
12545 : {
12546 35 : if (nVectorDim == nProfileDimId)
12547 0 : nVectorDim = oMapDimIdToCount.begin()->first;
12548 : }
12549 : else
12550 : {
12551 65 : nProfileDimId = -1;
12552 : }
12553 135 : for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)
12554 : {
12555 135 : int anDimIds[2] = {-1, -1};
12556 135 : nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);
12557 135 : if (nVectorDim == anDimIds[0])
12558 : {
12559 100 : nFirstVarId = anPotentialVectorVarID[j];
12560 100 : break;
12561 : }
12562 : }
12563 :
12564 : // In case where coordinates are explicitly specified for one of the
12565 : // field/variable, use them in priority over the ones that might have been
12566 : // identified above.
12567 100 : char *pszCoordinates = nullptr;
12568 100 : if (NCDFGetAttr(nCdfId, nFirstVarId, "coordinates", &pszCoordinates) ==
12569 : CE_None)
12570 : {
12571 : const CPLStringList aosTokens(
12572 110 : NCDFTokenizeCoordinatesAttribute(pszCFCoordinates));
12573 55 : for (int i = 0; i < aosTokens.size(); i++)
12574 : {
12575 0 : if (NCDFIsVarLongitude(nCdfId, -1, aosTokens[i]) ||
12576 0 : NCDFIsVarProjectionX(nCdfId, -1, aosTokens[i]))
12577 : {
12578 0 : nVarXId = -1;
12579 0 : CPL_IGNORE_RET_VAL(
12580 0 : nc_inq_varid(nCdfId, aosTokens[i], &nVarXId));
12581 : }
12582 0 : else if (NCDFIsVarLatitude(nCdfId, -1, aosTokens[i]) ||
12583 0 : NCDFIsVarProjectionY(nCdfId, -1, aosTokens[i]))
12584 : {
12585 0 : nVarYId = -1;
12586 0 : CPL_IGNORE_RET_VAL(
12587 0 : nc_inq_varid(nCdfId, aosTokens[i], &nVarYId));
12588 : }
12589 0 : else if (NCDFIsVarVerticalCoord(nCdfId, -1, aosTokens[i]))
12590 : {
12591 0 : nVarZId = -1;
12592 0 : CPL_IGNORE_RET_VAL(
12593 0 : nc_inq_varid(nCdfId, aosTokens[i], &nVarZId));
12594 : }
12595 : }
12596 : }
12597 100 : CPLFree(pszCoordinates);
12598 :
12599 : // Check that the X,Y,Z vars share 1D and share the same dimension as
12600 : // attribute variables.
12601 100 : if (nVarXId >= 0 && nVarYId >= 0)
12602 : {
12603 84 : int nVarDimCount = -1;
12604 84 : int nVarDimId = -1;
12605 84 : if (nc_inq_varndims(nCdfId, nVarXId, &nVarDimCount) != NC_NOERR ||
12606 84 : nVarDimCount != 1 ||
12607 84 : nc_inq_vardimid(nCdfId, nVarXId, &nVarDimId) != NC_NOERR ||
12608 84 : nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim) ||
12609 56 : nc_inq_varndims(nCdfId, nVarYId, &nVarDimCount) != NC_NOERR ||
12610 56 : nVarDimCount != 1 ||
12611 224 : nc_inq_vardimid(nCdfId, nVarYId, &nVarDimId) != NC_NOERR ||
12612 56 : nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim))
12613 : {
12614 28 : nVarXId = nVarYId = -1;
12615 : }
12616 111 : else if (nVarZId >= 0 &&
12617 55 : (nc_inq_varndims(nCdfId, nVarZId, &nVarDimCount) != NC_NOERR ||
12618 55 : nVarDimCount != 1 ||
12619 55 : nc_inq_vardimid(nCdfId, nVarZId, &nVarDimId) != NC_NOERR ||
12620 55 : nVarDimId != nVectorDim))
12621 : {
12622 0 : nVarZId = -1;
12623 : }
12624 : }
12625 :
12626 100 : if (eGType == wkbUnknown && nVarXId >= 0 && nVarYId >= 0)
12627 : {
12628 2 : eGType = wkbPoint;
12629 : }
12630 100 : if (eGType == wkbPoint && nVarXId >= 0 && nVarYId >= 0 && nVarZId >= 0)
12631 : {
12632 55 : eGType = wkbPoint25D;
12633 : }
12634 100 : if (eGType == wkbUnknown && osGeometryField.empty())
12635 : {
12636 34 : eGType = wkbNone;
12637 : }
12638 :
12639 : // Read projection info
12640 200 : CPLStringList aosMetadataBackup = aosMetadata;
12641 100 : ReadAttributes(nCdfId, nFirstVarId);
12642 100 : if (!this->bSGSupport)
12643 100 : SetProjectionFromVar(nCdfId, nFirstVarId, true);
12644 100 : const char *pszValue = FetchAttr(nCdfId, nFirstVarId, CF_GRD_MAPPING);
12645 200 : std::string osGridMapping = pszValue ? pszValue : "";
12646 100 : aosMetadata = std::move(aosMetadataBackup);
12647 :
12648 100 : OGRSpatialReference *poSRS = nullptr;
12649 100 : if (!m_oSRS.IsEmpty())
12650 : {
12651 21 : poSRS = m_oSRS.Clone();
12652 : }
12653 : // Reset if there's a 2D raster
12654 100 : m_bHasProjection = false;
12655 100 : m_bHasGeoTransform = false;
12656 :
12657 100 : if (!bKeepRasters)
12658 : {
12659 : // Strip out uninteresting metadata.
12660 67 : aosMetadata.SetNameValue("NC_GLOBAL#Conventions", nullptr);
12661 67 : aosMetadata.SetNameValue("NC_GLOBAL#GDAL", nullptr);
12662 67 : aosMetadata.SetNameValue("NC_GLOBAL#history", nullptr);
12663 : }
12664 :
12665 : std::shared_ptr<netCDFLayer> poLayer(
12666 100 : new netCDFLayer(this, nCdfId, osLayerName, eGType, poSRS));
12667 100 : if (poSRS != nullptr)
12668 21 : poSRS->Release();
12669 100 : poLayer->SetRecordDimID(nVectorDim);
12670 100 : if (wkbFlatten(eGType) == wkbPoint && nVarXId >= 0 && nVarYId >= 0)
12671 : {
12672 56 : poLayer->SetXYZVars(nVarXId, nVarYId, nVarZId);
12673 : }
12674 44 : else if (!osGeometryField.empty())
12675 : {
12676 10 : poLayer->SetWKTGeometryField(osGeometryField);
12677 : }
12678 100 : if (!osGridMapping.empty())
12679 : {
12680 21 : poLayer->SetGridMapping(osGridMapping.c_str());
12681 : }
12682 100 : poLayer->SetProfile(nProfileDimId, nParentIndexVarID);
12683 :
12684 742 : for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)
12685 : {
12686 642 : int anDimIds[2] = {-1, -1};
12687 642 : nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);
12688 642 : if (anDimIds[0] == nVectorDim ||
12689 68 : (nProfileDimId >= 0 && anDimIds[0] == nProfileDimId))
12690 : {
12691 : #ifdef NCDF_DEBUG
12692 : char szTemp2[NC_MAX_NAME + 1] = {};
12693 : CPL_IGNORE_RET_VAL(
12694 : nc_inq_varname(nCdfId, anPotentialVectorVarID[j], szTemp2));
12695 : CPLDebug("GDAL_netCDF", "Variable %s is a vector field", szTemp2);
12696 : #endif
12697 642 : poLayer->AddField(anPotentialVectorVarID[j]);
12698 : }
12699 : }
12700 :
12701 100 : if (poLayer->GetLayerDefn()->GetFieldCount() != 0 ||
12702 0 : poLayer->GetGeomType() != wkbNone)
12703 : {
12704 100 : papoLayers.push_back(poLayer);
12705 : }
12706 :
12707 200 : return CE_None;
12708 : }
12709 :
12710 : // Get all coordinate and boundary variables full names referenced in
12711 : // a given a NetCDF (or group) ID and its sub-groups.
12712 : // These variables are identified in other variable's
12713 : // "coordinates" and "bounds" attribute.
12714 : // Searching coordinate and boundary variables may need to explore
12715 : // parents groups (or other groups in case of reference given in form of an
12716 : // absolute path).
12717 : // See CF sections 5.2, 5.6 and 7.1
12718 590 : static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId,
12719 : CPLStringList &aosVars)
12720 : {
12721 590 : int nVars = 0;
12722 590 : NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
12723 :
12724 3616 : for (int v = 0; v < nVars; v++)
12725 : {
12726 3026 : char *pszTemp = nullptr;
12727 6052 : CPLStringList aosTokens;
12728 3026 : if (NCDFGetAttr(nCdfId, v, "coordinates", &pszTemp) == CE_None)
12729 500 : aosTokens.Assign(NCDFTokenizeCoordinatesAttribute(pszTemp));
12730 3026 : CPLFree(pszTemp);
12731 3026 : pszTemp = nullptr;
12732 3026 : if (NCDFGetAttr(nCdfId, v, "bounds", &pszTemp) == CE_None &&
12733 3026 : pszTemp != nullptr && !EQUAL(pszTemp, ""))
12734 20 : aosTokens.AddString(pszTemp);
12735 3026 : CPLFree(pszTemp);
12736 4479 : for (int i = 0; i < aosTokens.size(); i++)
12737 : {
12738 2906 : std::string osVarFullName;
12739 1453 : if (NCDFResolveVarFullName(nCdfId, aosTokens[i], osVarFullName) ==
12740 : CE_None)
12741 1423 : aosVars.AddString(osVarFullName);
12742 : }
12743 : }
12744 :
12745 : // Recurse on sub-groups.
12746 : int nSubGroups;
12747 590 : int *panSubGroupIds = nullptr;
12748 590 : NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
12749 627 : for (int i = 0; i < nSubGroups; i++)
12750 : {
12751 37 : NCDFGetCoordAndBoundVarFullNames(panSubGroupIds[i], aosVars);
12752 : }
12753 590 : CPLFree(panSubGroupIds);
12754 :
12755 590 : return CE_None;
12756 : }
12757 :
12758 : // Check if give type is user defined
12759 2073 : bool NCDFIsUserDefinedType(int /*ncid*/, int type)
12760 : {
12761 2073 : return type >= NC_FIRSTUSERTYPEID;
12762 : }
12763 :
12764 664 : char **NCDFTokenizeCoordinatesAttribute(const char *pszCoordinates)
12765 : {
12766 : // CF conventions use space as the separator for variable names in the
12767 : // coordinates attribute, but some products such as
12768 : // https://sentinel.esa.int/web/sentinel/technical-guides/sentinel-3-synergy/products-algorithms/level-2-aod-algorithms-and-products/level-2-aod-products-description
12769 : // use comma.
12770 664 : return CSLTokenizeString2(pszCoordinates, ", ", 0);
12771 : }
|