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 CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,
118 : std::string &osFullName,
119 : bool bMandatory = false);
120 : static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,
121 : const char *pszAtt, int *pnAtt,
122 : bool bMandatory = false);
123 : static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId,
124 : CPLStringList &aosVars);
125 :
126 : // Uncomment this for more debug output.
127 : // #define NCDF_DEBUG 1
128 :
129 : CPLMutex *hNCMutex = nullptr;
130 :
131 : // Workaround https://github.com/OSGeo/gdal/issues/6253
132 : // Having 2 netCDF handles on the same file doesn't work in a multi-threaded
133 : // way. Apparently having the same handle works better (this is OK since
134 : // we have a global mutex on the netCDF library)
135 : static std::map<std::string, int> goMapNameToNetCDFId;
136 : static std::map<int, std::pair<std::string, int>> goMapNetCDFIdToKeyAndCount;
137 :
138 854 : int GDAL_nc_open(const char *pszFilename, int nMode, int *pID)
139 : {
140 1708 : std::string osKey(pszFilename);
141 854 : osKey += "#####";
142 854 : osKey += std::to_string(nMode);
143 854 : auto oIter = goMapNameToNetCDFId.find(osKey);
144 854 : if (oIter == goMapNameToNetCDFId.end())
145 : {
146 792 : int ret = nc_open(pszFilename, nMode, pID);
147 792 : if (ret != NC_NOERR)
148 3 : return ret;
149 789 : goMapNameToNetCDFId[osKey] = *pID;
150 789 : goMapNetCDFIdToKeyAndCount[*pID] =
151 1578 : std::pair<std::string, int>(osKey, 1);
152 789 : return ret;
153 : }
154 : else
155 : {
156 62 : *pID = oIter->second;
157 62 : goMapNetCDFIdToKeyAndCount[oIter->second].second++;
158 62 : return NC_NOERR;
159 : }
160 : }
161 :
162 1177 : int GDAL_nc_close(int cdfid)
163 : {
164 1177 : int ret = NC_NOERR;
165 1177 : auto oIter = goMapNetCDFIdToKeyAndCount.find(cdfid);
166 1177 : if (oIter != goMapNetCDFIdToKeyAndCount.end())
167 : {
168 851 : if (--oIter->second.second == 0)
169 : {
170 789 : ret = nc_close(cdfid);
171 789 : goMapNameToNetCDFId.erase(oIter->second.first);
172 789 : goMapNetCDFIdToKeyAndCount.erase(oIter);
173 : }
174 : }
175 : else
176 : {
177 : // we can go here if file opened with nc_open_mem() or nc_create()
178 326 : ret = nc_close(cdfid);
179 : }
180 1177 : return ret;
181 : }
182 :
183 : /************************************************************************/
184 : /* ==================================================================== */
185 : /* netCDFRasterBand */
186 : /* ==================================================================== */
187 : /************************************************************************/
188 :
189 : class netCDFRasterBand final : public GDALPamRasterBand
190 : {
191 : friend class netCDFDataset;
192 :
193 : nc_type nc_datatype;
194 : int cdfid;
195 : int nZId;
196 : int nZDim;
197 : int nLevel;
198 : int nBandXPos;
199 : int nBandYPos;
200 : int *panBandZPos;
201 : int *panBandZLev;
202 : bool m_bNoDataSet = false;
203 : double m_dfNoDataValue = 0;
204 : bool m_bNoDataSetAsInt64 = false;
205 : int64_t m_nNodataValueInt64 = 0;
206 : bool m_bNoDataSetAsUInt64 = false;
207 : uint64_t m_nNodataValueUInt64 = 0;
208 : bool bValidRangeValid = false;
209 : double adfValidRange[2]{0, 0};
210 : bool m_bHaveScale = false;
211 : bool m_bHaveOffset = false;
212 : double m_dfScale = 1;
213 : double m_dfOffset = 0;
214 : CPLString m_osUnitType{};
215 : bool bSignedData;
216 : bool bCheckLongitude;
217 : bool m_bCreateMetadataFromOtherVarsDone = false;
218 :
219 : void CreateMetadataFromAttributes();
220 : void CreateMetadataFromOtherVars();
221 :
222 : template <class T>
223 : void CheckData(void *pImage, void *pImageNC, size_t nTmpBlockXSize,
224 : size_t nTmpBlockYSize, bool bCheckIsNan = false);
225 : template <class T>
226 : void CheckDataCpx(void *pImage, void *pImageNC, size_t nTmpBlockXSize,
227 : size_t nTmpBlockYSize, bool bCheckIsNan = false);
228 : void SetBlockSize();
229 :
230 : bool FetchNetcdfChunk(size_t xstart, size_t ystart, void *pImage);
231 :
232 : void SetNoDataValueNoUpdate(double dfNoData);
233 : void SetNoDataValueNoUpdate(int64_t nNoData);
234 : void SetNoDataValueNoUpdate(uint64_t nNoData);
235 :
236 : void SetOffsetNoUpdate(double dfVal);
237 : void SetScaleNoUpdate(double dfVal);
238 : void SetUnitTypeNoUpdate(const char *pszNewValue);
239 :
240 : protected:
241 : CPLXMLNode *SerializeToXML(const char *pszUnused) override;
242 :
243 : public:
244 : struct CONSTRUCTOR_OPEN
245 : {
246 : };
247 :
248 : struct CONSTRUCTOR_CREATE
249 : {
250 : };
251 :
252 : netCDFRasterBand(const CONSTRUCTOR_OPEN &, netCDFDataset *poDS,
253 : int nGroupId, int nZId, int nZDim, int nLevel,
254 : const int *panBandZLen, const int *panBandPos, int nBand);
255 : netCDFRasterBand(const CONSTRUCTOR_CREATE &, netCDFDataset *poDS,
256 : GDALDataType eType, int nBand, bool bSigned = true,
257 : const char *pszBandName = nullptr,
258 : const char *pszLongName = nullptr, int nZId = -1,
259 : int nZDim = 2, int nLevel = 0,
260 : const int *panBandZLev = nullptr,
261 : const int *panBandZPos = nullptr,
262 : const int *paDimIds = nullptr);
263 : ~netCDFRasterBand() override;
264 :
265 : double GetNoDataValue(int *) override;
266 : int64_t GetNoDataValueAsInt64(int *pbSuccess = nullptr) override;
267 : uint64_t GetNoDataValueAsUInt64(int *pbSuccess = nullptr) override;
268 : CPLErr SetNoDataValue(double) override;
269 : CPLErr SetNoDataValueAsInt64(int64_t nNoData) override;
270 : CPLErr SetNoDataValueAsUInt64(uint64_t nNoData) override;
271 : // virtual CPLErr DeleteNoDataValue();
272 : double GetOffset(int *) override;
273 : CPLErr SetOffset(double) override;
274 : double GetScale(int *) override;
275 : CPLErr SetScale(double) override;
276 : const char *GetUnitType() override;
277 : CPLErr SetUnitType(const char *) override;
278 : CPLErr IReadBlock(int, int, void *) override;
279 : CPLErr IWriteBlock(int, int, void *) override;
280 :
281 : CSLConstList GetMetadata(const char *pszDomain = "") override;
282 : const char *GetMetadataItem(const char *pszName,
283 : const char *pszDomain = "") override;
284 :
285 : CPLErr SetMetadataItem(const char *pszName, const char *pszValue,
286 : const char *pszDomain = "") override;
287 : CPLErr SetMetadata(CSLConstList papszMD,
288 : const char *pszDomain = "") override;
289 : };
290 :
291 : /************************************************************************/
292 : /* netCDFRasterBand() */
293 : /************************************************************************/
294 :
295 512 : netCDFRasterBand::netCDFRasterBand(const netCDFRasterBand::CONSTRUCTOR_OPEN &,
296 : netCDFDataset *poNCDFDS, int nGroupId,
297 : int nZIdIn, int nZDimIn, int nLevelIn,
298 : const int *panBandZLevIn,
299 512 : const int *panBandZPosIn, int nBandIn)
300 : : nc_datatype(NC_NAT), cdfid(nGroupId), nZId(nZIdIn), nZDim(nZDimIn),
301 512 : nLevel(nLevelIn), nBandXPos(panBandZPosIn[0]),
302 512 : nBandYPos(nZDim == 1 ? -1 : panBandZPosIn[1]), panBandZPos(nullptr),
303 : panBandZLev(nullptr),
304 : bSignedData(true), // Default signed, except for Byte.
305 1024 : bCheckLongitude(false)
306 : {
307 512 : poDS = poNCDFDS;
308 512 : nBand = nBandIn;
309 :
310 : // Take care of all other dimensions.
311 512 : if (nZDim > 2)
312 : {
313 180 : panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
314 180 : panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
315 :
316 486 : for (int i = 0; i < nZDim - 2; i++)
317 : {
318 306 : panBandZPos[i] = panBandZPosIn[i + 2];
319 306 : panBandZLev[i] = panBandZLevIn[i];
320 : }
321 : }
322 :
323 512 : nRasterXSize = poDS->GetRasterXSize();
324 512 : nRasterYSize = poDS->GetRasterYSize();
325 512 : nBlockXSize = poDS->GetRasterXSize();
326 512 : nBlockYSize = 1;
327 :
328 : // Get the type of the "z" variable, our target raster array.
329 512 : if (nc_inq_var(cdfid, nZId, nullptr, &nc_datatype, nullptr, nullptr,
330 512 : nullptr) != NC_NOERR)
331 : {
332 0 : CPLError(CE_Failure, CPLE_AppDefined, "Error in nc_var_inq() on 'z'.");
333 0 : return;
334 : }
335 :
336 512 : if (NCDFIsUserDefinedType(cdfid, nc_datatype))
337 : {
338 : // First enquire and check that the number of fields is 2
339 : size_t nfields, compoundsize;
340 5 : if (nc_inq_compound(cdfid, nc_datatype, nullptr, &compoundsize,
341 5 : &nfields) != NC_NOERR)
342 : {
343 0 : CPLError(CE_Failure, CPLE_AppDefined,
344 : "Error in nc_inq_compound() on 'z'.");
345 0 : return;
346 : }
347 :
348 5 : if (nfields != 2)
349 : {
350 0 : CPLError(CE_Failure, CPLE_AppDefined,
351 : "Unsupported data type encountered in nc_inq_compound() "
352 : "on 'z'.");
353 0 : return;
354 : }
355 :
356 : // Now check that that two types are the same in the struct.
357 : nc_type field_type1, field_type2;
358 : int field_dims1, field_dims2;
359 5 : if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
360 : &field_type1, &field_dims1,
361 5 : nullptr) != NC_NOERR)
362 : {
363 0 : CPLError(
364 : CE_Failure, CPLE_AppDefined,
365 : "Error in querying Field 1 in nc_inq_compound_field() on 'z'.");
366 0 : return;
367 : }
368 :
369 5 : if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
370 : &field_type2, &field_dims2,
371 5 : nullptr) != NC_NOERR)
372 : {
373 0 : CPLError(
374 : CE_Failure, CPLE_AppDefined,
375 : "Error in querying Field 2 in nc_inq_compound_field() on 'z'.");
376 0 : return;
377 : }
378 :
379 5 : if ((field_type1 != field_type2) || (field_dims1 != field_dims2) ||
380 5 : (field_dims1 != 0))
381 : {
382 0 : CPLError(CE_Failure, CPLE_AppDefined,
383 : "Error in interpreting compound data type on 'z'.");
384 0 : return;
385 : }
386 :
387 5 : if (field_type1 == NC_SHORT)
388 0 : eDataType = GDT_CInt16;
389 5 : else if (field_type1 == NC_INT)
390 0 : eDataType = GDT_CInt32;
391 5 : else if (field_type1 == NC_FLOAT)
392 4 : eDataType = GDT_CFloat32;
393 1 : else if (field_type1 == NC_DOUBLE)
394 1 : eDataType = GDT_CFloat64;
395 : else
396 : {
397 0 : CPLError(CE_Failure, CPLE_AppDefined,
398 : "Unsupported netCDF compound data type encountered.");
399 0 : return;
400 : }
401 : }
402 : else
403 : {
404 507 : if (nc_datatype == NC_BYTE)
405 166 : eDataType = GDT_UInt8;
406 341 : else if (nc_datatype == NC_CHAR)
407 0 : eDataType = GDT_UInt8;
408 341 : else if (nc_datatype == NC_SHORT)
409 44 : eDataType = GDT_Int16;
410 297 : else if (nc_datatype == NC_INT)
411 89 : eDataType = GDT_Int32;
412 208 : else if (nc_datatype == NC_FLOAT)
413 129 : eDataType = GDT_Float32;
414 79 : else if (nc_datatype == NC_DOUBLE)
415 40 : eDataType = GDT_Float64;
416 39 : else if (nc_datatype == NC_UBYTE)
417 16 : eDataType = GDT_UInt8;
418 23 : else if (nc_datatype == NC_USHORT)
419 4 : eDataType = GDT_UInt16;
420 19 : else if (nc_datatype == NC_UINT)
421 4 : eDataType = GDT_UInt32;
422 15 : else if (nc_datatype == NC_INT64)
423 8 : eDataType = GDT_Int64;
424 7 : else if (nc_datatype == NC_UINT64)
425 7 : eDataType = GDT_UInt64;
426 : else
427 : {
428 0 : if (nBand == 1)
429 0 : CPLError(CE_Warning, CPLE_AppDefined,
430 : "Unsupported netCDF datatype (%d), treat as Float32.",
431 0 : static_cast<int>(nc_datatype));
432 0 : eDataType = GDT_Float32;
433 0 : nc_datatype = NC_FLOAT;
434 : }
435 : }
436 :
437 : // Find and set No Data for this variable.
438 512 : nc_type atttype = NC_NAT;
439 512 : size_t attlen = 0;
440 512 : const char *pszNoValueName = nullptr;
441 :
442 : // Find attribute name, either _FillValue or missing_value.
443 512 : int status = nc_inq_att(cdfid, nZId, NCDF_FillValue, &atttype, &attlen);
444 512 : if (status == NC_NOERR)
445 : {
446 254 : pszNoValueName = NCDF_FillValue;
447 : }
448 : else
449 : {
450 258 : status = nc_inq_att(cdfid, nZId, "missing_value", &atttype, &attlen);
451 258 : if (status == NC_NOERR)
452 : {
453 12 : pszNoValueName = "missing_value";
454 : }
455 : }
456 :
457 : // Fetch missing value.
458 512 : double dfNoData = 0.0;
459 512 : bool bGotNoData = false;
460 512 : int64_t nNoDataAsInt64 = 0;
461 512 : bool bGotNoDataAsInt64 = false;
462 512 : uint64_t nNoDataAsUInt64 = 0;
463 512 : bool bGotNoDataAsUInt64 = false;
464 512 : if (status == NC_NOERR)
465 : {
466 266 : nc_type nAttrType = NC_NAT;
467 266 : size_t nAttrLen = 0;
468 266 : status = nc_inq_att(cdfid, nZId, pszNoValueName, &nAttrType, &nAttrLen);
469 266 : if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_INT64)
470 : {
471 : long long v;
472 7 : nc_get_att_longlong(cdfid, nZId, pszNoValueName, &v);
473 7 : bGotNoData = true;
474 7 : bGotNoDataAsInt64 = true;
475 7 : nNoDataAsInt64 = static_cast<int64_t>(v);
476 : }
477 259 : else if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_UINT64)
478 : {
479 : unsigned long long v;
480 7 : nc_get_att_ulonglong(cdfid, nZId, pszNoValueName, &v);
481 7 : bGotNoData = true;
482 7 : bGotNoDataAsUInt64 = true;
483 7 : nNoDataAsUInt64 = static_cast<uint64_t>(v);
484 : }
485 252 : else if (NCDFGetAttr(cdfid, nZId, pszNoValueName, &dfNoData) == CE_None)
486 : {
487 251 : bGotNoData = true;
488 : }
489 : }
490 :
491 : // If NoData was not found, use the default value, but for non-Byte types
492 : // as it is not recommended:
493 : // https://www.unidata.ucar.edu/software/netcdf/docs/attribute_conventions.html
494 512 : nc_type vartype = NC_NAT;
495 512 : if (!bGotNoData)
496 : {
497 247 : nc_inq_vartype(cdfid, nZId, &vartype);
498 247 : if (vartype == NC_INT64)
499 : {
500 : nNoDataAsInt64 =
501 1 : NCDFGetDefaultNoDataValueAsInt64(cdfid, nZId, bGotNoData);
502 1 : bGotNoDataAsInt64 = bGotNoData;
503 : }
504 246 : else if (vartype == NC_UINT64)
505 : {
506 : nNoDataAsUInt64 =
507 0 : NCDFGetDefaultNoDataValueAsUInt64(cdfid, nZId, bGotNoData);
508 0 : bGotNoDataAsUInt64 = bGotNoData;
509 : }
510 246 : else if (vartype != NC_CHAR && vartype != NC_BYTE &&
511 103 : vartype != NC_UBYTE)
512 : {
513 93 : dfNoData =
514 93 : NCDFGetDefaultNoDataValue(cdfid, nZId, vartype, bGotNoData);
515 93 : if (bGotNoData)
516 : {
517 82 : CPLDebug("GDAL_netCDF",
518 : "did not get nodata value for variable #%d, using "
519 : "default %f",
520 : nZId, dfNoData);
521 : }
522 : }
523 : }
524 :
525 512 : bool bHasUnderscoreUnsignedAttr = false;
526 512 : bool bUnderscoreUnsignedAttrVal = false;
527 : {
528 512 : char *pszTemp = nullptr;
529 512 : if (NCDFGetAttr(cdfid, nZId, "_Unsigned", &pszTemp) == CE_None)
530 : {
531 158 : if (EQUAL(pszTemp, "true"))
532 : {
533 150 : bHasUnderscoreUnsignedAttr = true;
534 150 : bUnderscoreUnsignedAttrVal = true;
535 : }
536 8 : else if (EQUAL(pszTemp, "false"))
537 : {
538 8 : bHasUnderscoreUnsignedAttr = true;
539 8 : bUnderscoreUnsignedAttrVal = false;
540 : }
541 158 : CPLFree(pszTemp);
542 : }
543 : }
544 :
545 : // Look for valid_range or valid_min/valid_max.
546 :
547 : // First look for valid_range.
548 512 : if (CPLFetchBool(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE", true))
549 : {
550 510 : char *pszValidRange = nullptr;
551 510 : if (NCDFGetAttr(cdfid, nZId, "valid_range", &pszValidRange) ==
552 151 : CE_None &&
553 661 : pszValidRange[0] == '{' &&
554 151 : pszValidRange[strlen(pszValidRange) - 1] == '}')
555 : {
556 : const std::string osValidRange =
557 453 : std::string(pszValidRange).substr(1, strlen(pszValidRange) - 2);
558 : const CPLStringList aosValidRange(
559 302 : CSLTokenizeString2(osValidRange.c_str(), ",", 0));
560 151 : if (aosValidRange.size() == 2 &&
561 302 : CPLGetValueType(aosValidRange[0]) != CPL_VALUE_STRING &&
562 151 : CPLGetValueType(aosValidRange[1]) != CPL_VALUE_STRING)
563 : {
564 151 : bValidRangeValid = true;
565 151 : adfValidRange[0] = CPLAtof(aosValidRange[0]);
566 151 : adfValidRange[1] = CPLAtof(aosValidRange[1]);
567 : }
568 : }
569 510 : CPLFree(pszValidRange);
570 :
571 : // If not found look for valid_min and valid_max.
572 510 : if (!bValidRangeValid)
573 : {
574 359 : double dfMin = 0;
575 359 : double dfMax = 0;
576 374 : if (NCDFGetAttr(cdfid, nZId, "valid_min", &dfMin) == CE_None &&
577 15 : NCDFGetAttr(cdfid, nZId, "valid_max", &dfMax) == CE_None)
578 : {
579 8 : adfValidRange[0] = dfMin;
580 8 : adfValidRange[1] = dfMax;
581 8 : bValidRangeValid = true;
582 : }
583 : }
584 :
585 510 : if (bValidRangeValid &&
586 159 : (adfValidRange[0] < 0 || adfValidRange[1] < 0) &&
587 17 : nc_datatype == NC_SHORT && bHasUnderscoreUnsignedAttr &&
588 : bUnderscoreUnsignedAttrVal)
589 : {
590 2 : if (adfValidRange[0] < 0)
591 0 : adfValidRange[0] += 65536;
592 2 : if (adfValidRange[1] < 0)
593 2 : adfValidRange[1] += 65536;
594 2 : if (adfValidRange[0] <= adfValidRange[1])
595 : {
596 : // Updating metadata item
597 2 : GDALPamRasterBand::SetMetadataItem(
598 : "valid_range",
599 2 : CPLSPrintf("{%d,%d}", static_cast<int>(adfValidRange[0]),
600 2 : static_cast<int>(adfValidRange[1])));
601 : }
602 : }
603 :
604 510 : if (bValidRangeValid && adfValidRange[0] > adfValidRange[1])
605 : {
606 0 : CPLError(CE_Warning, CPLE_AppDefined,
607 : "netCDFDataset::valid_range: min > max:\n"
608 : " min: %lf\n max: %lf\n",
609 : adfValidRange[0], adfValidRange[1]);
610 0 : bValidRangeValid = false;
611 0 : adfValidRange[0] = 0.0;
612 0 : adfValidRange[1] = 0.0;
613 : }
614 : }
615 :
616 : // Special For Byte Bands: check for signed/unsigned byte.
617 512 : if (nc_datatype == NC_BYTE)
618 : {
619 : // netcdf uses signed byte by default, but GDAL uses unsigned by default
620 : // This may cause unexpected results, but is needed for back-compat.
621 166 : if (poNCDFDS->bIsGdalFile)
622 144 : bSignedData = false;
623 : else
624 22 : bSignedData = true;
625 :
626 : // For NC4 format NC_BYTE is (normally) signed, NC_UBYTE is unsigned.
627 : // But in case a NC3 file was converted automatically and has hints
628 : // that it is unsigned, take them into account
629 166 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
630 : {
631 3 : bSignedData = true;
632 : }
633 :
634 : // If we got valid_range, test for signed/unsigned range.
635 : // https://docs.unidata.ucar.edu/netcdf-c/current/attribute_conventions.html
636 166 : if (bValidRangeValid)
637 : {
638 : // If we got valid_range={0,255}, treat as unsigned.
639 147 : if (adfValidRange[0] == 0 && adfValidRange[1] == 255)
640 : {
641 139 : bSignedData = false;
642 : // Reset valid_range.
643 139 : bValidRangeValid = false;
644 : }
645 : // If we got valid_range={-128,127}, treat as signed.
646 8 : else if (adfValidRange[0] == -128 && adfValidRange[1] == 127)
647 : {
648 8 : bSignedData = true;
649 : // Reset valid_range.
650 8 : bValidRangeValid = false;
651 : }
652 : }
653 : // Else test for _Unsigned.
654 : // https://docs.unidata.ucar.edu/nug/current/best_practices.html
655 : else
656 : {
657 19 : if (bHasUnderscoreUnsignedAttr)
658 7 : bSignedData = !bUnderscoreUnsignedAttrVal;
659 : }
660 :
661 166 : if (bSignedData)
662 : {
663 20 : eDataType = GDT_Int8;
664 : }
665 146 : else if (dfNoData < 0)
666 : {
667 : // Fix nodata value as it was stored signed.
668 6 : dfNoData += 256;
669 6 : if (pszNoValueName)
670 : {
671 : // Updating metadata item
672 6 : GDALPamRasterBand::SetMetadataItem(
673 : pszNoValueName,
674 : CPLSPrintf("%d", static_cast<int>(dfNoData)));
675 : }
676 : }
677 : }
678 346 : else if (nc_datatype == NC_SHORT)
679 : {
680 44 : if (bHasUnderscoreUnsignedAttr)
681 : {
682 4 : bSignedData = !bUnderscoreUnsignedAttrVal;
683 4 : if (!bSignedData)
684 4 : eDataType = GDT_UInt16;
685 : }
686 :
687 : // Fix nodata value as it was stored signed.
688 44 : if (!bSignedData && dfNoData < 0)
689 : {
690 4 : dfNoData += 65536;
691 4 : if (pszNoValueName)
692 : {
693 : // Updating metadata item
694 4 : GDALPamRasterBand::SetMetadataItem(
695 : pszNoValueName,
696 : CPLSPrintf("%d", static_cast<int>(dfNoData)));
697 : }
698 : }
699 : }
700 :
701 302 : else if (nc_datatype == NC_UBYTE || nc_datatype == NC_USHORT ||
702 282 : nc_datatype == NC_UINT || nc_datatype == NC_UINT64)
703 : {
704 31 : bSignedData = false;
705 : }
706 :
707 512 : CPLDebug("GDAL_netCDF", "netcdf type=%d gdal type=%d signedByte=%d",
708 512 : nc_datatype, eDataType, static_cast<int>(bSignedData));
709 :
710 512 : if (bGotNoData)
711 : {
712 : // Set nodata value.
713 348 : if (bGotNoDataAsInt64)
714 : {
715 8 : if (eDataType == GDT_Int64)
716 : {
717 8 : SetNoDataValueNoUpdate(nNoDataAsInt64);
718 : }
719 0 : else if (eDataType == GDT_UInt64 && nNoDataAsInt64 >= 0)
720 : {
721 0 : SetNoDataValueNoUpdate(static_cast<uint64_t>(nNoDataAsInt64));
722 : }
723 : else
724 : {
725 0 : SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsInt64));
726 : }
727 : }
728 340 : else if (bGotNoDataAsUInt64)
729 : {
730 7 : if (eDataType == GDT_UInt64)
731 : {
732 7 : SetNoDataValueNoUpdate(nNoDataAsUInt64);
733 : }
734 0 : else if (eDataType == GDT_Int64 &&
735 : nNoDataAsUInt64 <=
736 0 : static_cast<uint64_t>(
737 0 : std::numeric_limits<int64_t>::max()))
738 : {
739 0 : SetNoDataValueNoUpdate(static_cast<int64_t>(nNoDataAsUInt64));
740 : }
741 : else
742 : {
743 0 : SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsUInt64));
744 : }
745 : }
746 : else
747 : {
748 : #ifdef NCDF_DEBUG
749 : CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) read", dfNoData);
750 : #endif
751 333 : if (eDataType == GDT_Int64 && GDALIsValueExactAs<int64_t>(dfNoData))
752 : {
753 0 : SetNoDataValueNoUpdate(static_cast<int64_t>(dfNoData));
754 : }
755 333 : else if (eDataType == GDT_UInt64 &&
756 0 : GDALIsValueExactAs<uint64_t>(dfNoData))
757 : {
758 0 : SetNoDataValueNoUpdate(static_cast<uint64_t>(dfNoData));
759 : }
760 : else
761 : {
762 333 : SetNoDataValueNoUpdate(dfNoData);
763 : }
764 : }
765 : }
766 :
767 512 : CreateMetadataFromAttributes();
768 :
769 : // Attempt to fetch the scale_factor and add_offset attributes for the
770 : // variable and set them. If these values are not available, set
771 : // offset to 0 and scale to 1.
772 512 : if (nc_inq_attid(cdfid, nZId, CF_ADD_OFFSET, nullptr) == NC_NOERR)
773 : {
774 16 : double dfOffset = 0;
775 16 : status = nc_get_att_double(cdfid, nZId, CF_ADD_OFFSET, &dfOffset);
776 16 : CPLDebug("GDAL_netCDF", "got add_offset=%.16g, status=%d", dfOffset,
777 : status);
778 16 : SetOffsetNoUpdate(dfOffset);
779 : }
780 :
781 512 : bool bHasScale = false;
782 512 : if (nc_inq_attid(cdfid, nZId, CF_SCALE_FACTOR, nullptr) == NC_NOERR)
783 : {
784 23 : bHasScale = true;
785 23 : double dfScale = 1;
786 23 : status = nc_get_att_double(cdfid, nZId, CF_SCALE_FACTOR, &dfScale);
787 23 : CPLDebug("GDAL_netCDF", "got scale_factor=%.16g, status=%d", dfScale,
788 : status);
789 23 : SetScaleNoUpdate(dfScale);
790 : }
791 :
792 12 : if (bValidRangeValid && GDALDataTypeIsInteger(eDataType) &&
793 4 : eDataType != GDT_Int64 && eDataType != GDT_UInt64 &&
794 4 : (std::fabs(std::round(adfValidRange[0]) - adfValidRange[0]) > 1e-5 ||
795 524 : std::fabs(std::round(adfValidRange[1]) - adfValidRange[1]) > 1e-5) &&
796 1 : CSLFetchNameValue(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE") ==
797 : nullptr)
798 : {
799 1 : CPLError(CE_Warning, CPLE_AppDefined,
800 : "validity range = %f, %f contains floating-point values, "
801 : "whereas data type is integer. valid_range is thus likely "
802 : "wrong%s. Ignoring it.",
803 : adfValidRange[0], adfValidRange[1],
804 : bHasScale ? " (likely scaled using scale_factor/add_factor "
805 : "whereas it should be using the packed data type)"
806 : : "");
807 1 : bValidRangeValid = false;
808 1 : adfValidRange[0] = 0.0;
809 1 : adfValidRange[1] = 0.0;
810 : }
811 :
812 : // Should we check for longitude values > 360?
813 512 : bCheckLongitude =
814 1024 : CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")) &&
815 512 : NCDFIsVarLongitude(cdfid, nZId, nullptr);
816 :
817 : // Attempt to fetch the units attribute for the variable and set it.
818 512 : SetUnitTypeNoUpdate(netCDFRasterBand::GetMetadataItem(CF_UNITS));
819 :
820 512 : SetBlockSize();
821 : }
822 :
823 705 : void netCDFRasterBand::SetBlockSize()
824 : {
825 : // Check for variable chunking (netcdf-4 only).
826 : // GDAL block size should be set to hdf5 chunk size.
827 705 : int nTmpFormat = 0;
828 705 : int status = nc_inq_format(cdfid, &nTmpFormat);
829 705 : NetCDFFormatEnum eTmpFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
830 705 : if ((status == NC_NOERR) &&
831 603 : (eTmpFormat == NCDF_FORMAT_NC4 || eTmpFormat == NCDF_FORMAT_NC4C))
832 : {
833 118 : size_t chunksize[MAX_NC_DIMS] = {};
834 : // Check for chunksize and set it as the blocksize (optimizes read).
835 118 : status = nc_inq_var_chunking(cdfid, nZId, &nTmpFormat, chunksize);
836 118 : if ((status == NC_NOERR) && (nTmpFormat == NC_CHUNKED))
837 : {
838 14 : nBlockXSize = (int)chunksize[nZDim - 1];
839 14 : if (nZDim >= 2)
840 14 : nBlockYSize = (int)chunksize[nZDim - 2];
841 : else
842 0 : nBlockYSize = 1;
843 : }
844 : }
845 :
846 : // Deal with bottom-up datasets and nBlockYSize != 1.
847 705 : auto poGDS = cpl::down_cast<netCDFDataset *>(poDS);
848 705 : if (poGDS->bBottomUp && nBlockYSize != 1 && poGDS->poChunkCache == nullptr)
849 : {
850 6 : if (poGDS->eAccess == GA_ReadOnly)
851 : {
852 : // Try to cache 1 or 2 'rows' of netCDF chunks along the whole
853 : // width of the raster
854 6 : size_t nChunks =
855 6 : static_cast<size_t>(DIV_ROUND_UP(nRasterXSize, nBlockXSize));
856 6 : if ((nRasterYSize % nBlockYSize) != 0)
857 2 : nChunks *= 2;
858 : const size_t nChunkSize =
859 6 : static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
860 6 : nBlockXSize * nBlockYSize;
861 6 : constexpr size_t MAX_CACHE_SIZE = 100 * 1024 * 1024;
862 6 : nChunks = std::min(nChunks, MAX_CACHE_SIZE / nChunkSize);
863 6 : if (nChunks)
864 : {
865 6 : poGDS->poChunkCache.reset(
866 6 : new netCDFDataset::ChunkCacheType(nChunks));
867 : }
868 : }
869 : else
870 : {
871 0 : nBlockYSize = 1;
872 : }
873 : }
874 705 : }
875 :
876 : // Constructor in create mode.
877 : // If nZId and following variables are not passed, the band will have 2
878 : // dimensions.
879 : // TODO: Get metadata, missing val from band #1 if nZDim > 2.
880 193 : netCDFRasterBand::netCDFRasterBand(
881 : const netCDFRasterBand::CONSTRUCTOR_CREATE &, netCDFDataset *poNCDFDS,
882 : const GDALDataType eTypeIn, int nBandIn, bool bSigned,
883 : const char *pszBandName, const char *pszLongName, int nZIdIn, int nZDimIn,
884 : int nLevelIn, const int *panBandZLevIn, const int *panBandZPosIn,
885 193 : const int *paDimIds)
886 193 : : nc_datatype(NC_NAT), cdfid(poNCDFDS->GetCDFID()), nZId(nZIdIn),
887 : nZDim(nZDimIn), nLevel(nLevelIn), nBandXPos(1), nBandYPos(0),
888 : panBandZPos(nullptr), panBandZLev(nullptr), bSignedData(bSigned),
889 193 : bCheckLongitude(false), m_bCreateMetadataFromOtherVarsDone(true)
890 : {
891 193 : poDS = poNCDFDS;
892 193 : nBand = nBandIn;
893 :
894 193 : nRasterXSize = poDS->GetRasterXSize();
895 193 : nRasterYSize = poDS->GetRasterYSize();
896 193 : nBlockXSize = poDS->GetRasterXSize();
897 193 : nBlockYSize = 1;
898 :
899 193 : if (poDS->GetAccess() != GA_Update)
900 : {
901 0 : CPLError(CE_Failure, CPLE_NotSupported,
902 : "Dataset is not in update mode, "
903 : "wrong netCDFRasterBand constructor");
904 0 : return;
905 : }
906 :
907 : // Take care of all other dimensions.
908 193 : if (nZDim > 2 && paDimIds != nullptr)
909 : {
910 27 : nBandXPos = panBandZPosIn[0];
911 27 : nBandYPos = panBandZPosIn[1];
912 :
913 27 : panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
914 27 : panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
915 :
916 76 : for (int i = 0; i < nZDim - 2; i++)
917 : {
918 49 : panBandZPos[i] = panBandZPosIn[i + 2];
919 49 : panBandZLev[i] = panBandZLevIn[i];
920 : }
921 : }
922 :
923 : // Get the type of the "z" variable, our target raster array.
924 193 : eDataType = eTypeIn;
925 :
926 193 : switch (eDataType)
927 : {
928 87 : case GDT_UInt8:
929 87 : nc_datatype = NC_BYTE;
930 : // NC_UBYTE (unsigned byte) is only available for NC4.
931 87 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
932 3 : nc_datatype = NC_UBYTE;
933 87 : break;
934 7 : case GDT_Int8:
935 7 : nc_datatype = NC_BYTE;
936 7 : break;
937 11 : case GDT_Int16:
938 11 : nc_datatype = NC_SHORT;
939 11 : break;
940 24 : case GDT_Int32:
941 24 : nc_datatype = NC_INT;
942 24 : break;
943 14 : case GDT_Float32:
944 14 : nc_datatype = NC_FLOAT;
945 14 : break;
946 8 : case GDT_Float64:
947 8 : nc_datatype = NC_DOUBLE;
948 8 : break;
949 7 : case GDT_Int64:
950 7 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
951 : {
952 7 : nc_datatype = NC_INT64;
953 : }
954 : else
955 : {
956 0 : if (nBand == 1)
957 0 : CPLError(
958 : CE_Warning, CPLE_AppDefined,
959 : "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
960 : "Int64");
961 0 : nc_datatype = NC_DOUBLE;
962 0 : eDataType = GDT_Float64;
963 : }
964 7 : break;
965 7 : case GDT_UInt64:
966 7 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
967 : {
968 7 : nc_datatype = NC_UINT64;
969 : }
970 : else
971 : {
972 0 : if (nBand == 1)
973 0 : CPLError(
974 : CE_Warning, CPLE_AppDefined,
975 : "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
976 : "UInt64");
977 0 : nc_datatype = NC_DOUBLE;
978 0 : eDataType = GDT_Float64;
979 : }
980 7 : break;
981 6 : case GDT_UInt16:
982 6 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
983 : {
984 6 : nc_datatype = NC_USHORT;
985 6 : break;
986 : }
987 : [[fallthrough]];
988 : case GDT_UInt32:
989 6 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
990 : {
991 6 : nc_datatype = NC_UINT;
992 6 : break;
993 : }
994 : [[fallthrough]];
995 : default:
996 16 : if (nBand == 1)
997 8 : CPLError(CE_Warning, CPLE_AppDefined,
998 : "Unsupported GDAL datatype (%d), treat as NC_FLOAT.",
999 8 : static_cast<int>(eDataType));
1000 16 : nc_datatype = NC_FLOAT;
1001 16 : eDataType = GDT_Float32;
1002 16 : break;
1003 : }
1004 :
1005 : // Define the variable if necessary (if nZId == -1).
1006 193 : bool bDefineVar = false;
1007 :
1008 193 : if (nZId == -1)
1009 : {
1010 171 : bDefineVar = true;
1011 :
1012 : // Make sure we are in define mode.
1013 171 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1014 :
1015 : char szTempPrivate[256 + 1];
1016 171 : const char *pszTemp = nullptr;
1017 171 : if (!pszBandName || EQUAL(pszBandName, ""))
1018 : {
1019 144 : snprintf(szTempPrivate, sizeof(szTempPrivate), "Band%d", nBand);
1020 144 : pszTemp = szTempPrivate;
1021 : }
1022 : else
1023 : {
1024 27 : pszTemp = pszBandName;
1025 : }
1026 :
1027 : int status;
1028 171 : if (nZDim > 2 && paDimIds != nullptr)
1029 : {
1030 5 : status =
1031 5 : nc_def_var(cdfid, pszTemp, nc_datatype, nZDim, paDimIds, &nZId);
1032 : }
1033 : else
1034 : {
1035 166 : int anBandDims[2] = {poNCDFDS->nYDimID, poNCDFDS->nXDimID};
1036 : status =
1037 166 : nc_def_var(cdfid, pszTemp, nc_datatype, 2, anBandDims, &nZId);
1038 : }
1039 171 : NCDF_ERR(status);
1040 171 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d) id=%d", cdfid, pszTemp,
1041 : nc_datatype, nZId);
1042 :
1043 171 : if (!pszLongName || EQUAL(pszLongName, ""))
1044 : {
1045 159 : snprintf(szTempPrivate, sizeof(szTempPrivate),
1046 : "GDAL Band Number %d", nBand);
1047 159 : pszTemp = szTempPrivate;
1048 : }
1049 : else
1050 : {
1051 12 : pszTemp = pszLongName;
1052 : }
1053 : status =
1054 171 : nc_put_att_text(cdfid, nZId, CF_LNG_NAME, strlen(pszTemp), pszTemp);
1055 171 : NCDF_ERR(status);
1056 :
1057 171 : poNCDFDS->DefVarDeflate(nZId, true);
1058 : }
1059 :
1060 : // For Byte data add signed/unsigned info.
1061 193 : if (eDataType == GDT_UInt8 || eDataType == GDT_Int8)
1062 : {
1063 94 : if (bDefineVar)
1064 : {
1065 : // Only add attributes if creating variable.
1066 : // For unsigned NC_BYTE (except NC4 format),
1067 : // add valid_range and _Unsigned ( defined in CF-1 and NUG ).
1068 86 : if (nc_datatype == NC_BYTE && poNCDFDS->eFormat != NCDF_FORMAT_NC4)
1069 : {
1070 83 : CPLDebug("GDAL_netCDF",
1071 : "adding valid_range attributes for Byte Band");
1072 83 : short l_adfValidRange[2] = {0, 0};
1073 : int status;
1074 83 : if (bSignedData || eDataType == GDT_Int8)
1075 : {
1076 7 : l_adfValidRange[0] = -128;
1077 7 : l_adfValidRange[1] = 127;
1078 7 : status =
1079 7 : nc_put_att_text(cdfid, nZId, "_Unsigned", 5, "false");
1080 : }
1081 : else
1082 : {
1083 76 : l_adfValidRange[0] = 0;
1084 76 : l_adfValidRange[1] = 255;
1085 : status =
1086 76 : nc_put_att_text(cdfid, nZId, "_Unsigned", 4, "true");
1087 : }
1088 83 : NCDF_ERR(status);
1089 83 : status = nc_put_att_short(cdfid, nZId, "valid_range", NC_SHORT,
1090 : 2, l_adfValidRange);
1091 83 : NCDF_ERR(status);
1092 : }
1093 : }
1094 : }
1095 :
1096 193 : if (nc_datatype != NC_BYTE && nc_datatype != NC_CHAR &&
1097 102 : nc_datatype != NC_UBYTE)
1098 : {
1099 : // Set default nodata.
1100 99 : bool bIgnored = false;
1101 : double dfNoData =
1102 99 : NCDFGetDefaultNoDataValue(cdfid, nZId, nc_datatype, bIgnored);
1103 : #ifdef NCDF_DEBUG
1104 : CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) default", dfNoData);
1105 : #endif
1106 99 : netCDFRasterBand::SetNoDataValue(dfNoData);
1107 : }
1108 :
1109 193 : SetBlockSize();
1110 : }
1111 :
1112 : /************************************************************************/
1113 : /* ~netCDFRasterBand() */
1114 : /************************************************************************/
1115 :
1116 1410 : netCDFRasterBand::~netCDFRasterBand()
1117 : {
1118 705 : netCDFRasterBand::FlushCache(true);
1119 705 : CPLFree(panBandZPos);
1120 705 : CPLFree(panBandZLev);
1121 1410 : }
1122 :
1123 : /************************************************************************/
1124 : /* GetMetadata() */
1125 : /************************************************************************/
1126 :
1127 59 : CSLConstList netCDFRasterBand::GetMetadata(const char *pszDomain)
1128 : {
1129 59 : if (!m_bCreateMetadataFromOtherVarsDone)
1130 57 : CreateMetadataFromOtherVars();
1131 59 : return GDALPamRasterBand::GetMetadata(pszDomain);
1132 : }
1133 :
1134 : /************************************************************************/
1135 : /* GetMetadataItem() */
1136 : /************************************************************************/
1137 :
1138 598 : const char *netCDFRasterBand::GetMetadataItem(const char *pszName,
1139 : const char *pszDomain)
1140 : {
1141 598 : if (!m_bCreateMetadataFromOtherVarsDone &&
1142 582 : STARTS_WITH(pszName, "NETCDF_DIM_") &&
1143 1 : (!pszDomain || pszDomain[0] == 0))
1144 1 : CreateMetadataFromOtherVars();
1145 598 : return GDALPamRasterBand::GetMetadataItem(pszName, pszDomain);
1146 : }
1147 :
1148 : /************************************************************************/
1149 : /* SetMetadataItem() */
1150 : /************************************************************************/
1151 :
1152 7 : CPLErr netCDFRasterBand::SetMetadataItem(const char *pszName,
1153 : const char *pszValue,
1154 : const char *pszDomain)
1155 : {
1156 9 : if (GetAccess() == GA_Update &&
1157 9 : (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)
1158 : {
1159 : // Same logic as in CopyMetadata()
1160 :
1161 2 : const char *const papszIgnoreBand[] = {
1162 : CF_ADD_OFFSET, CF_SCALE_FACTOR, "valid_range", "_Unsigned",
1163 : NCDF_FillValue, "coordinates", nullptr};
1164 : // Do not copy varname, stats, NETCDF_DIM_*, nodata
1165 : // and items in papszIgnoreBand.
1166 6 : if (STARTS_WITH(pszName, "NETCDF_VARNAME") ||
1167 2 : STARTS_WITH(pszName, "STATISTICS_") ||
1168 2 : STARTS_WITH(pszName, "NETCDF_DIM_") ||
1169 2 : STARTS_WITH(pszName, "missing_value") ||
1170 6 : STARTS_WITH(pszName, "_FillValue") ||
1171 2 : CSLFindString(papszIgnoreBand, pszName) != -1)
1172 : {
1173 : // do nothing
1174 : }
1175 : else
1176 : {
1177 2 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1178 :
1179 2 : if (!NCDFPutAttr(cdfid, nZId, pszName, pszValue))
1180 2 : return CE_Failure;
1181 : }
1182 : }
1183 :
1184 5 : return GDALPamRasterBand::SetMetadataItem(pszName, pszValue, pszDomain);
1185 : }
1186 :
1187 : /************************************************************************/
1188 : /* SetMetadata() */
1189 : /************************************************************************/
1190 :
1191 2 : CPLErr netCDFRasterBand::SetMetadata(CSLConstList papszMD,
1192 : const char *pszDomain)
1193 : {
1194 4 : if (GetAccess() == GA_Update &&
1195 2 : (pszDomain == nullptr || pszDomain[0] == '\0'))
1196 : {
1197 : // We don't handle metadata item removal for now
1198 4 : for (const char *const *papszIter = papszMD; papszIter && *papszIter;
1199 : ++papszIter)
1200 : {
1201 2 : char *pszName = nullptr;
1202 2 : const char *pszValue = CPLParseNameValue(*papszIter, &pszName);
1203 2 : if (pszName && pszValue)
1204 2 : SetMetadataItem(pszName, pszValue);
1205 2 : CPLFree(pszName);
1206 : }
1207 : }
1208 2 : return GDALPamRasterBand::SetMetadata(papszMD, pszDomain);
1209 : }
1210 :
1211 : /************************************************************************/
1212 : /* GetOffset() */
1213 : /************************************************************************/
1214 59 : double netCDFRasterBand::GetOffset(int *pbSuccess)
1215 : {
1216 59 : if (pbSuccess != nullptr)
1217 49 : *pbSuccess = static_cast<int>(m_bHaveOffset);
1218 :
1219 59 : return m_dfOffset;
1220 : }
1221 :
1222 : /************************************************************************/
1223 : /* SetOffset() */
1224 : /************************************************************************/
1225 1 : CPLErr netCDFRasterBand::SetOffset(double dfNewOffset)
1226 : {
1227 2 : CPLMutexHolderD(&hNCMutex);
1228 :
1229 : // Write value if in update mode.
1230 1 : if (poDS->GetAccess() == GA_Update)
1231 : {
1232 : // Make sure we are in define mode.
1233 1 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1234 :
1235 1 : const int status = nc_put_att_double(cdfid, nZId, CF_ADD_OFFSET,
1236 : NC_DOUBLE, 1, &dfNewOffset);
1237 :
1238 1 : NCDF_ERR(status);
1239 1 : if (status == NC_NOERR)
1240 : {
1241 1 : SetOffsetNoUpdate(dfNewOffset);
1242 1 : return CE_None;
1243 : }
1244 :
1245 0 : return CE_Failure;
1246 : }
1247 :
1248 0 : SetOffsetNoUpdate(dfNewOffset);
1249 0 : return CE_None;
1250 : }
1251 :
1252 : /************************************************************************/
1253 : /* SetOffsetNoUpdate() */
1254 : /************************************************************************/
1255 17 : void netCDFRasterBand::SetOffsetNoUpdate(double dfVal)
1256 : {
1257 17 : m_dfOffset = dfVal;
1258 17 : m_bHaveOffset = true;
1259 17 : }
1260 :
1261 : /************************************************************************/
1262 : /* GetScale() */
1263 : /************************************************************************/
1264 59 : double netCDFRasterBand::GetScale(int *pbSuccess)
1265 : {
1266 59 : if (pbSuccess != nullptr)
1267 49 : *pbSuccess = static_cast<int>(m_bHaveScale);
1268 :
1269 59 : return m_dfScale;
1270 : }
1271 :
1272 : /************************************************************************/
1273 : /* SetScale() */
1274 : /************************************************************************/
1275 1 : CPLErr netCDFRasterBand::SetScale(double dfNewScale)
1276 : {
1277 2 : CPLMutexHolderD(&hNCMutex);
1278 :
1279 : // Write value if in update mode.
1280 1 : if (poDS->GetAccess() == GA_Update)
1281 : {
1282 : // Make sure we are in define mode.
1283 1 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1284 :
1285 1 : const int status = nc_put_att_double(cdfid, nZId, CF_SCALE_FACTOR,
1286 : NC_DOUBLE, 1, &dfNewScale);
1287 :
1288 1 : NCDF_ERR(status);
1289 1 : if (status == NC_NOERR)
1290 : {
1291 1 : SetScaleNoUpdate(dfNewScale);
1292 1 : return CE_None;
1293 : }
1294 :
1295 0 : return CE_Failure;
1296 : }
1297 :
1298 0 : SetScaleNoUpdate(dfNewScale);
1299 0 : return CE_None;
1300 : }
1301 :
1302 : /************************************************************************/
1303 : /* SetScaleNoUpdate() */
1304 : /************************************************************************/
1305 24 : void netCDFRasterBand::SetScaleNoUpdate(double dfVal)
1306 : {
1307 24 : m_dfScale = dfVal;
1308 24 : m_bHaveScale = true;
1309 24 : }
1310 :
1311 : /************************************************************************/
1312 : /* GetUnitType() */
1313 : /************************************************************************/
1314 :
1315 27 : const char *netCDFRasterBand::GetUnitType()
1316 :
1317 : {
1318 27 : if (!m_osUnitType.empty())
1319 6 : return m_osUnitType;
1320 :
1321 21 : return GDALRasterBand::GetUnitType();
1322 : }
1323 :
1324 : /************************************************************************/
1325 : /* SetUnitType() */
1326 : /************************************************************************/
1327 :
1328 1 : CPLErr netCDFRasterBand::SetUnitType(const char *pszNewValue)
1329 :
1330 : {
1331 2 : CPLMutexHolderD(&hNCMutex);
1332 :
1333 2 : const std::string osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
1334 :
1335 1 : if (!osUnitType.empty())
1336 : {
1337 : // Write value if in update mode.
1338 1 : if (poDS->GetAccess() == GA_Update)
1339 : {
1340 : // Make sure we are in define mode.
1341 1 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(TRUE);
1342 :
1343 1 : const int status = nc_put_att_text(
1344 : cdfid, nZId, CF_UNITS, osUnitType.size(), osUnitType.c_str());
1345 :
1346 1 : NCDF_ERR(status);
1347 1 : if (status == NC_NOERR)
1348 : {
1349 1 : SetUnitTypeNoUpdate(pszNewValue);
1350 1 : return CE_None;
1351 : }
1352 :
1353 0 : return CE_Failure;
1354 : }
1355 : }
1356 :
1357 0 : SetUnitTypeNoUpdate(pszNewValue);
1358 :
1359 0 : return CE_None;
1360 : }
1361 :
1362 : /************************************************************************/
1363 : /* SetUnitTypeNoUpdate() */
1364 : /************************************************************************/
1365 :
1366 513 : void netCDFRasterBand::SetUnitTypeNoUpdate(const char *pszNewValue)
1367 : {
1368 513 : m_osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
1369 513 : }
1370 :
1371 : /************************************************************************/
1372 : /* GetNoDataValue() */
1373 : /************************************************************************/
1374 :
1375 207 : double netCDFRasterBand::GetNoDataValue(int *pbSuccess)
1376 :
1377 : {
1378 207 : if (m_bNoDataSetAsInt64)
1379 : {
1380 0 : if (pbSuccess)
1381 0 : *pbSuccess = TRUE;
1382 0 : return GDALGetNoDataValueCastToDouble(m_nNodataValueInt64);
1383 : }
1384 :
1385 207 : if (m_bNoDataSetAsUInt64)
1386 : {
1387 0 : if (pbSuccess)
1388 0 : *pbSuccess = TRUE;
1389 0 : return GDALGetNoDataValueCastToDouble(m_nNodataValueUInt64);
1390 : }
1391 :
1392 207 : if (m_bNoDataSet)
1393 : {
1394 144 : if (pbSuccess)
1395 127 : *pbSuccess = TRUE;
1396 144 : return m_dfNoDataValue;
1397 : }
1398 :
1399 63 : return GDALPamRasterBand::GetNoDataValue(pbSuccess);
1400 : }
1401 :
1402 : /************************************************************************/
1403 : /* GetNoDataValueAsInt64() */
1404 : /************************************************************************/
1405 :
1406 4 : int64_t netCDFRasterBand::GetNoDataValueAsInt64(int *pbSuccess)
1407 :
1408 : {
1409 4 : if (m_bNoDataSetAsInt64)
1410 : {
1411 4 : if (pbSuccess)
1412 4 : *pbSuccess = TRUE;
1413 :
1414 4 : return m_nNodataValueInt64;
1415 : }
1416 :
1417 0 : return GDALPamRasterBand::GetNoDataValueAsInt64(pbSuccess);
1418 : }
1419 :
1420 : /************************************************************************/
1421 : /* GetNoDataValueAsUInt64() */
1422 : /************************************************************************/
1423 :
1424 4 : uint64_t netCDFRasterBand::GetNoDataValueAsUInt64(int *pbSuccess)
1425 :
1426 : {
1427 4 : if (m_bNoDataSetAsUInt64)
1428 : {
1429 4 : if (pbSuccess)
1430 4 : *pbSuccess = TRUE;
1431 :
1432 4 : return m_nNodataValueUInt64;
1433 : }
1434 :
1435 0 : return GDALPamRasterBand::GetNoDataValueAsUInt64(pbSuccess);
1436 : }
1437 :
1438 : /************************************************************************/
1439 : /* SetNoDataValue() */
1440 : /************************************************************************/
1441 :
1442 135 : CPLErr netCDFRasterBand::SetNoDataValue(double dfNoData)
1443 :
1444 : {
1445 270 : CPLMutexHolderD(&hNCMutex);
1446 :
1447 : // If already set to new value, don't do anything.
1448 135 : if (m_bNoDataSet && CPLIsEqual(dfNoData, m_dfNoDataValue))
1449 19 : return CE_None;
1450 :
1451 : // Write value if in update mode.
1452 116 : if (poDS->GetAccess() == GA_Update)
1453 : {
1454 : // netcdf-4 does not allow to set _FillValue after leaving define mode,
1455 : // but it is ok if variable has not been written to, so only print
1456 : // debug. See bug #4484.
1457 126 : if (m_bNoDataSet &&
1458 10 : !cpl::down_cast<netCDFDataset *>(poDS)->GetDefineMode())
1459 : {
1460 0 : CPLDebug("GDAL_netCDF",
1461 : "Setting NoDataValue to %.17g (previously set to %.17g) "
1462 : "but file is no longer in define mode (id #%d, band #%d)",
1463 : dfNoData, m_dfNoDataValue, cdfid, nBand);
1464 : }
1465 : #ifdef NCDF_DEBUG
1466 : else
1467 : {
1468 : CPLDebug("GDAL_netCDF",
1469 : "Setting NoDataValue to %.17g (id #%d, band #%d)",
1470 : dfNoData, cdfid, nBand);
1471 : }
1472 : #endif
1473 : // Make sure we are in define mode.
1474 116 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1475 :
1476 : int status;
1477 116 : if (eDataType == GDT_UInt8)
1478 : {
1479 6 : if (bSignedData)
1480 : {
1481 0 : signed char cNoDataValue = static_cast<signed char>(dfNoData);
1482 0 : status = nc_put_att_schar(cdfid, nZId, NCDF_FillValue,
1483 : nc_datatype, 1, &cNoDataValue);
1484 : }
1485 : else
1486 : {
1487 6 : const unsigned char ucNoDataValue =
1488 6 : static_cast<unsigned char>(dfNoData);
1489 6 : status = nc_put_att_uchar(cdfid, nZId, NCDF_FillValue,
1490 : nc_datatype, 1, &ucNoDataValue);
1491 : }
1492 : }
1493 110 : else if (eDataType == GDT_Int16)
1494 : {
1495 14 : short nsNoDataValue = static_cast<short>(dfNoData);
1496 14 : status = nc_put_att_short(cdfid, nZId, NCDF_FillValue, nc_datatype,
1497 : 1, &nsNoDataValue);
1498 : }
1499 96 : else if (eDataType == GDT_Int32)
1500 : {
1501 27 : int nNoDataValue = static_cast<int>(dfNoData);
1502 27 : status = nc_put_att_int(cdfid, nZId, NCDF_FillValue, nc_datatype, 1,
1503 : &nNoDataValue);
1504 : }
1505 69 : else if (eDataType == GDT_Float32)
1506 : {
1507 32 : float fNoDataValue = static_cast<float>(dfNoData);
1508 32 : status = nc_put_att_float(cdfid, nZId, NCDF_FillValue, nc_datatype,
1509 : 1, &fNoDataValue);
1510 : }
1511 43 : else if (eDataType == GDT_UInt16 &&
1512 6 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat ==
1513 : NCDF_FORMAT_NC4)
1514 : {
1515 6 : unsigned short usNoDataValue =
1516 6 : static_cast<unsigned short>(dfNoData);
1517 6 : status = nc_put_att_ushort(cdfid, nZId, NCDF_FillValue, nc_datatype,
1518 : 1, &usNoDataValue);
1519 : }
1520 38 : else if (eDataType == GDT_UInt32 &&
1521 7 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat ==
1522 : NCDF_FORMAT_NC4)
1523 : {
1524 7 : unsigned int unNoDataValue = static_cast<unsigned int>(dfNoData);
1525 7 : status = nc_put_att_uint(cdfid, nZId, NCDF_FillValue, nc_datatype,
1526 : 1, &unNoDataValue);
1527 : }
1528 : else
1529 : {
1530 24 : status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
1531 : 1, &dfNoData);
1532 : }
1533 :
1534 116 : NCDF_ERR(status);
1535 :
1536 : // Update status if write worked.
1537 116 : if (status == NC_NOERR)
1538 : {
1539 116 : SetNoDataValueNoUpdate(dfNoData);
1540 116 : return CE_None;
1541 : }
1542 :
1543 0 : return CE_Failure;
1544 : }
1545 :
1546 0 : SetNoDataValueNoUpdate(dfNoData);
1547 0 : return CE_None;
1548 : }
1549 :
1550 : /************************************************************************/
1551 : /* SetNoDataValueNoUpdate() */
1552 : /************************************************************************/
1553 :
1554 449 : void netCDFRasterBand::SetNoDataValueNoUpdate(double dfNoData)
1555 : {
1556 449 : m_dfNoDataValue = dfNoData;
1557 449 : m_bNoDataSet = true;
1558 449 : m_bNoDataSetAsInt64 = false;
1559 449 : m_bNoDataSetAsUInt64 = false;
1560 449 : }
1561 :
1562 : /************************************************************************/
1563 : /* SetNoDataValueAsInt64() */
1564 : /************************************************************************/
1565 :
1566 3 : CPLErr netCDFRasterBand::SetNoDataValueAsInt64(int64_t nNoData)
1567 :
1568 : {
1569 6 : CPLMutexHolderD(&hNCMutex);
1570 :
1571 : // If already set to new value, don't do anything.
1572 3 : if (m_bNoDataSetAsInt64 && nNoData == m_nNodataValueInt64)
1573 0 : return CE_None;
1574 :
1575 : // Write value if in update mode.
1576 3 : if (poDS->GetAccess() == GA_Update)
1577 : {
1578 : // netcdf-4 does not allow to set NCDF_FillValue after leaving define mode,
1579 : // but it is ok if variable has not been written to, so only print
1580 : // debug. See bug #4484.
1581 3 : if (m_bNoDataSetAsInt64 &&
1582 0 : !cpl::down_cast<netCDFDataset *>(poDS)->GetDefineMode())
1583 : {
1584 0 : CPLDebug("GDAL_netCDF",
1585 : "Setting NoDataValue to " CPL_FRMT_GIB
1586 : " (previously set to " CPL_FRMT_GIB ") "
1587 : "but file is no longer in define mode (id #%d, band #%d)",
1588 : static_cast<GIntBig>(nNoData),
1589 0 : static_cast<GIntBig>(m_nNodataValueInt64), cdfid, nBand);
1590 : }
1591 : #ifdef NCDF_DEBUG
1592 : else
1593 : {
1594 : CPLDebug("GDAL_netCDF",
1595 : "Setting NoDataValue to " CPL_FRMT_GIB
1596 : " (id #%d, band #%d)",
1597 : static_cast<GIntBig>(nNoData), cdfid, nBand);
1598 : }
1599 : #endif
1600 : // Make sure we are in define mode.
1601 3 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1602 :
1603 : int status;
1604 6 : if (eDataType == GDT_Int64 &&
1605 3 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
1606 : {
1607 3 : long long tmp = static_cast<long long>(nNoData);
1608 3 : status = nc_put_att_longlong(cdfid, nZId, NCDF_FillValue,
1609 : nc_datatype, 1, &tmp);
1610 : }
1611 : else
1612 : {
1613 0 : double dfNoData = static_cast<double>(nNoData);
1614 0 : status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
1615 : 1, &dfNoData);
1616 : }
1617 :
1618 3 : NCDF_ERR(status);
1619 :
1620 : // Update status if write worked.
1621 3 : if (status == NC_NOERR)
1622 : {
1623 3 : SetNoDataValueNoUpdate(nNoData);
1624 3 : return CE_None;
1625 : }
1626 :
1627 0 : return CE_Failure;
1628 : }
1629 :
1630 0 : SetNoDataValueNoUpdate(nNoData);
1631 0 : return CE_None;
1632 : }
1633 :
1634 : /************************************************************************/
1635 : /* SetNoDataValueNoUpdate() */
1636 : /************************************************************************/
1637 :
1638 11 : void netCDFRasterBand::SetNoDataValueNoUpdate(int64_t nNoData)
1639 : {
1640 11 : m_nNodataValueInt64 = nNoData;
1641 11 : m_bNoDataSet = false;
1642 11 : m_bNoDataSetAsInt64 = true;
1643 11 : m_bNoDataSetAsUInt64 = false;
1644 11 : }
1645 :
1646 : /************************************************************************/
1647 : /* SetNoDataValueAsUInt64() */
1648 : /************************************************************************/
1649 :
1650 3 : CPLErr netCDFRasterBand::SetNoDataValueAsUInt64(uint64_t nNoData)
1651 :
1652 : {
1653 6 : CPLMutexHolderD(&hNCMutex);
1654 :
1655 : // If already set to new value, don't do anything.
1656 3 : if (m_bNoDataSetAsUInt64 && nNoData == m_nNodataValueUInt64)
1657 0 : return CE_None;
1658 :
1659 : // Write value if in update mode.
1660 3 : if (poDS->GetAccess() == GA_Update)
1661 : {
1662 : // netcdf-4 does not allow to set _FillValue after leaving define mode,
1663 : // but it is ok if variable has not been written to, so only print
1664 : // debug. See bug #4484.
1665 3 : if (m_bNoDataSetAsUInt64 &&
1666 0 : !cpl::down_cast<netCDFDataset *>(poDS)->GetDefineMode())
1667 : {
1668 0 : CPLDebug("GDAL_netCDF",
1669 : "Setting NoDataValue to " CPL_FRMT_GUIB
1670 : " (previously set to " CPL_FRMT_GUIB ") "
1671 : "but file is no longer in define mode (id #%d, band #%d)",
1672 : static_cast<GUIntBig>(nNoData),
1673 0 : static_cast<GUIntBig>(m_nNodataValueUInt64), cdfid, nBand);
1674 : }
1675 : #ifdef NCDF_DEBUG
1676 : else
1677 : {
1678 : CPLDebug("GDAL_netCDF",
1679 : "Setting NoDataValue to " CPL_FRMT_GUIB
1680 : " (id #%d, band #%d)",
1681 : static_cast<GUIntBig>(nNoData), cdfid, nBand);
1682 : }
1683 : #endif
1684 : // Make sure we are in define mode.
1685 3 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1686 :
1687 : int status;
1688 6 : if (eDataType == GDT_UInt64 &&
1689 3 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
1690 : {
1691 3 : unsigned long long tmp = static_cast<long long>(nNoData);
1692 3 : status = nc_put_att_ulonglong(cdfid, nZId, NCDF_FillValue,
1693 : nc_datatype, 1, &tmp);
1694 : }
1695 : else
1696 : {
1697 0 : double dfNoData = static_cast<double>(nNoData);
1698 0 : status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
1699 : 1, &dfNoData);
1700 : }
1701 :
1702 3 : NCDF_ERR(status);
1703 :
1704 : // Update status if write worked.
1705 3 : if (status == NC_NOERR)
1706 : {
1707 3 : SetNoDataValueNoUpdate(nNoData);
1708 3 : return CE_None;
1709 : }
1710 :
1711 0 : return CE_Failure;
1712 : }
1713 :
1714 0 : SetNoDataValueNoUpdate(nNoData);
1715 0 : return CE_None;
1716 : }
1717 :
1718 : /************************************************************************/
1719 : /* SetNoDataValueNoUpdate() */
1720 : /************************************************************************/
1721 :
1722 10 : void netCDFRasterBand::SetNoDataValueNoUpdate(uint64_t nNoData)
1723 : {
1724 10 : m_nNodataValueUInt64 = nNoData;
1725 10 : m_bNoDataSet = false;
1726 10 : m_bNoDataSetAsInt64 = false;
1727 10 : m_bNoDataSetAsUInt64 = true;
1728 10 : }
1729 :
1730 : /************************************************************************/
1731 : /* DeleteNoDataValue() */
1732 : /************************************************************************/
1733 :
1734 : #ifdef notdef
1735 : CPLErr netCDFRasterBand::DeleteNoDataValue()
1736 :
1737 : {
1738 : CPLMutexHolderD(&hNCMutex);
1739 :
1740 : if (!bNoDataSet)
1741 : return CE_None;
1742 :
1743 : // Write value if in update mode.
1744 : if (poDS->GetAccess() == GA_Update)
1745 : {
1746 : // Make sure we are in define mode.
1747 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1748 :
1749 : status = nc_del_att(cdfid, nZId, NCDF_FillValue);
1750 :
1751 : NCDF_ERR(status);
1752 :
1753 : // Update status if write worked.
1754 : if (status == NC_NOERR)
1755 : {
1756 : dfNoDataValue = 0.0;
1757 : bNoDataSet = false;
1758 : return CE_None;
1759 : }
1760 :
1761 : return CE_Failure;
1762 : }
1763 :
1764 : dfNoDataValue = 0.0;
1765 : bNoDataSet = false;
1766 : return CE_None;
1767 : }
1768 : #endif
1769 :
1770 : /************************************************************************/
1771 : /* SerializeToXML() */
1772 : /************************************************************************/
1773 :
1774 5 : CPLXMLNode *netCDFRasterBand::SerializeToXML(const char * /* pszUnused */)
1775 : {
1776 : // Overridden from GDALPamDataset to add only band histogram
1777 : // and statistics. See bug #4244.
1778 5 : if (psPam == nullptr)
1779 0 : return nullptr;
1780 :
1781 : // Setup root node and attributes.
1782 : CPLXMLNode *psTree =
1783 5 : CPLCreateXMLNode(nullptr, CXT_Element, "PAMRasterBand");
1784 :
1785 5 : if (GetBand() > 0)
1786 : {
1787 10 : CPLString oFmt;
1788 5 : CPLSetXMLValue(psTree, "#band", oFmt.Printf("%d", GetBand()));
1789 : }
1790 :
1791 : // Histograms.
1792 5 : if (psPam->psSavedHistograms != nullptr)
1793 1 : CPLAddXMLChild(psTree, CPLCloneXMLTree(psPam->psSavedHistograms));
1794 :
1795 : // Metadata (statistics only).
1796 5 : GDALMultiDomainMetadata oMDMDStats;
1797 5 : const char *papszMDStats[] = {"STATISTICS_MINIMUM", "STATISTICS_MAXIMUM",
1798 : "STATISTICS_MEAN", "STATISTICS_STDDEV",
1799 : nullptr};
1800 25 : for (int i = 0; i < CSLCount(papszMDStats); i++)
1801 : {
1802 20 : const char *pszMDI = GetMetadataItem(papszMDStats[i]);
1803 20 : if (pszMDI)
1804 4 : oMDMDStats.SetMetadataItem(papszMDStats[i], pszMDI);
1805 : }
1806 5 : CPLXMLNode *psMD = oMDMDStats.Serialize();
1807 :
1808 5 : if (psMD != nullptr)
1809 : {
1810 1 : if (psMD->psChild == nullptr)
1811 0 : CPLDestroyXMLNode(psMD);
1812 : else
1813 1 : CPLAddXMLChild(psTree, psMD);
1814 : }
1815 :
1816 : // We don't want to return anything if we had no metadata to attach.
1817 5 : if (psTree->psChild == nullptr || psTree->psChild->psNext == nullptr)
1818 : {
1819 3 : CPLDestroyXMLNode(psTree);
1820 3 : psTree = nullptr;
1821 : }
1822 :
1823 5 : return psTree;
1824 : }
1825 :
1826 : /************************************************************************/
1827 : /* Get1DVariableIndexedByDimension() */
1828 : /************************************************************************/
1829 :
1830 83 : static int Get1DVariableIndexedByDimension(int cdfid, int nDimId,
1831 : const char *pszDimName,
1832 : bool bVerboseError, int *pnGroupID)
1833 : {
1834 83 : *pnGroupID = -1;
1835 83 : int nVarID = -1;
1836 : // First try to find a variable whose name is identical to the dimension
1837 : // name, and check that it is indeed indexed by this dimension
1838 83 : if (NCDFResolveVar(cdfid, pszDimName, pnGroupID, &nVarID) == CE_None)
1839 : {
1840 69 : int nDimCountOfVariable = 0;
1841 69 : nc_inq_varndims(*pnGroupID, nVarID, &nDimCountOfVariable);
1842 69 : if (nDimCountOfVariable == 1)
1843 : {
1844 69 : int nDimIdOfVariable = -1;
1845 69 : nc_inq_vardimid(*pnGroupID, nVarID, &nDimIdOfVariable);
1846 69 : if (nDimIdOfVariable == nDimId)
1847 : {
1848 69 : return nVarID;
1849 : }
1850 : }
1851 : }
1852 :
1853 : // Otherwise iterate over the variables to find potential candidates
1854 : // TODO: should be modified to search also in other groups using the same
1855 : // logic than in NCDFResolveVar(), but maybe not needed if it's a
1856 : // very rare case? and I think this is not CF compliant.
1857 14 : int nvars = 0;
1858 14 : CPL_IGNORE_RET_VAL(nc_inq(cdfid, nullptr, &nvars, nullptr, nullptr));
1859 :
1860 14 : int nCountCandidateVars = 0;
1861 14 : int nCandidateVarID = -1;
1862 65 : for (int k = 0; k < nvars; k++)
1863 : {
1864 51 : int nDimCountOfVariable = 0;
1865 51 : nc_inq_varndims(cdfid, k, &nDimCountOfVariable);
1866 51 : if (nDimCountOfVariable == 1)
1867 : {
1868 27 : int nDimIdOfVariable = -1;
1869 27 : nc_inq_vardimid(cdfid, k, &nDimIdOfVariable);
1870 27 : if (nDimIdOfVariable == nDimId)
1871 : {
1872 7 : nCountCandidateVars++;
1873 7 : nCandidateVarID = k;
1874 : }
1875 : }
1876 : }
1877 14 : if (nCountCandidateVars > 1)
1878 : {
1879 1 : if (bVerboseError)
1880 : {
1881 1 : CPLError(CE_Warning, CPLE_AppDefined,
1882 : "Several 1D variables are indexed by dimension %s",
1883 : pszDimName);
1884 : }
1885 1 : *pnGroupID = -1;
1886 1 : return -1;
1887 : }
1888 13 : else if (nCandidateVarID < 0)
1889 : {
1890 8 : if (bVerboseError)
1891 : {
1892 8 : CPLError(CE_Warning, CPLE_AppDefined,
1893 : "No 1D variable is indexed by dimension %s", pszDimName);
1894 : }
1895 : }
1896 13 : *pnGroupID = cdfid;
1897 13 : return nCandidateVarID;
1898 : }
1899 :
1900 : /************************************************************************/
1901 : /* CreateMetadataFromAttributes() */
1902 : /************************************************************************/
1903 :
1904 512 : void netCDFRasterBand::CreateMetadataFromAttributes()
1905 : {
1906 512 : char szVarName[NC_MAX_NAME + 1] = {};
1907 512 : int status = nc_inq_varname(cdfid, nZId, szVarName);
1908 512 : NCDF_ERR(status);
1909 :
1910 512 : GDALPamRasterBand::SetMetadataItem("NETCDF_VARNAME", szVarName);
1911 :
1912 : // Get attribute metadata.
1913 512 : int nAtt = 0;
1914 512 : NCDF_ERR(nc_inq_varnatts(cdfid, nZId, &nAtt));
1915 :
1916 2193 : for (int i = 0; i < nAtt; i++)
1917 : {
1918 1681 : char szMetaName[NC_MAX_NAME + 1] = {};
1919 1681 : status = nc_inq_attname(cdfid, nZId, i, szMetaName);
1920 1681 : if (status != NC_NOERR)
1921 12 : continue;
1922 :
1923 1681 : if (GDALPamRasterBand::GetMetadataItem(szMetaName) != nullptr)
1924 : {
1925 12 : continue;
1926 : }
1927 :
1928 1669 : char *pszMetaValue = nullptr;
1929 1669 : if (NCDFGetAttr(cdfid, nZId, szMetaName, &pszMetaValue) == CE_None)
1930 : {
1931 1669 : GDALPamRasterBand::SetMetadataItem(szMetaName, pszMetaValue);
1932 : }
1933 : else
1934 : {
1935 0 : CPLDebug("GDAL_netCDF", "invalid Band metadata %s", szMetaName);
1936 : }
1937 :
1938 1669 : if (pszMetaValue)
1939 : {
1940 1669 : CPLFree(pszMetaValue);
1941 1669 : pszMetaValue = nullptr;
1942 : }
1943 : }
1944 512 : }
1945 :
1946 : /************************************************************************/
1947 : /* CreateMetadataFromOtherVars() */
1948 : /************************************************************************/
1949 :
1950 58 : void netCDFRasterBand::CreateMetadataFromOtherVars()
1951 :
1952 : {
1953 58 : CPLAssert(!m_bCreateMetadataFromOtherVarsDone);
1954 58 : m_bCreateMetadataFromOtherVarsDone = true;
1955 :
1956 58 : netCDFDataset *l_poDS = cpl::down_cast<netCDFDataset *>(poDS);
1957 58 : const int nPamFlagsBackup = l_poDS->nPamFlags;
1958 :
1959 : // Compute all dimensions from Band number and save in Metadata.
1960 58 : int nd = 0;
1961 58 : nc_inq_varndims(cdfid, nZId, &nd);
1962 : // Compute multidimention band position.
1963 : //
1964 : // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
1965 : // if Data[2,3,4,x,y]
1966 : //
1967 : // BandPos0 = (nBand) / (3*4)
1968 : // BandPos1 = (nBand - BandPos0*(3*4)) / (4)
1969 : // BandPos2 = (nBand - BandPos0*(3*4)) % (4)
1970 :
1971 58 : int Sum = 1;
1972 58 : if (nd == 3)
1973 : {
1974 5 : Sum *= panBandZLev[0];
1975 : }
1976 :
1977 : // Loop over non-spatial dimensions.
1978 58 : int Taken = 0;
1979 :
1980 98 : for (int i = 0; i < nd - 2; i++)
1981 : {
1982 : int result;
1983 40 : if (i != nd - 2 - 1)
1984 : {
1985 18 : Sum = 1;
1986 37 : for (int j = i + 1; j < nd - 2; j++)
1987 : {
1988 19 : Sum *= panBandZLev[j];
1989 : }
1990 18 : result = static_cast<int>((nLevel - Taken) / Sum);
1991 : }
1992 : else
1993 : {
1994 22 : result = static_cast<int>((nLevel - Taken) % Sum);
1995 : }
1996 :
1997 40 : char szName[NC_MAX_NAME + 1] = {};
1998 40 : snprintf(szName, sizeof(szName), "%s",
1999 40 : l_poDS->papszDimName[l_poDS->m_anDimIds[panBandZPos[i]]]);
2000 :
2001 : char szMetaName[NC_MAX_NAME + 1 + 32];
2002 40 : snprintf(szMetaName, sizeof(szMetaName), "NETCDF_DIM_%s", szName);
2003 :
2004 40 : const int nGroupID = l_poDS->m_anExtraDimGroupIds[i];
2005 40 : const int nVarID = l_poDS->m_anExtraDimVarIds[i];
2006 40 : if (nVarID < 0)
2007 : {
2008 2 : GDALPamRasterBand::SetMetadataItem(szMetaName,
2009 : CPLSPrintf("%d", result + 1));
2010 : }
2011 : else
2012 : {
2013 : // TODO: Make sure all the status checks make sense.
2014 :
2015 38 : nc_type nVarType = NC_NAT;
2016 38 : /* status = */ nc_inq_vartype(nGroupID, nVarID, &nVarType);
2017 :
2018 38 : int nDims = 0;
2019 38 : /* status = */ nc_inq_varndims(nGroupID, nVarID, &nDims);
2020 :
2021 38 : char szMetaTemp[256] = {};
2022 38 : if (nDims == 1)
2023 : {
2024 38 : size_t count[1] = {1};
2025 38 : size_t start[1] = {static_cast<size_t>(result)};
2026 :
2027 38 : switch (nVarType)
2028 : {
2029 0 : case NC_BYTE:
2030 : // TODO: Check for signed/unsigned byte.
2031 : signed char cData;
2032 0 : /* status = */ nc_get_vara_schar(nGroupID, nVarID,
2033 : start, count, &cData);
2034 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", cData);
2035 0 : break;
2036 0 : case NC_SHORT:
2037 : short sData;
2038 0 : /* status = */ nc_get_vara_short(nGroupID, nVarID,
2039 : start, count, &sData);
2040 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", sData);
2041 0 : break;
2042 19 : case NC_INT:
2043 : {
2044 : int nData;
2045 19 : /* status = */ nc_get_vara_int(nGroupID, nVarID, start,
2046 : count, &nData);
2047 19 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", nData);
2048 19 : break;
2049 : }
2050 0 : case NC_FLOAT:
2051 : float fData;
2052 0 : /* status = */ nc_get_vara_float(nGroupID, nVarID,
2053 : start, count, &fData);
2054 0 : CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.8g",
2055 : fData);
2056 0 : break;
2057 18 : case NC_DOUBLE:
2058 : double dfData;
2059 18 : /* status = */ nc_get_vara_double(
2060 : nGroupID, nVarID, start, count, &dfData);
2061 18 : CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.16g",
2062 : dfData);
2063 18 : break;
2064 0 : case NC_UBYTE:
2065 : unsigned char ucData;
2066 0 : /* status = */ nc_get_vara_uchar(nGroupID, nVarID,
2067 : start, count, &ucData);
2068 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", ucData);
2069 0 : break;
2070 0 : case NC_USHORT:
2071 : unsigned short usData;
2072 0 : /* status = */ nc_get_vara_ushort(
2073 : nGroupID, nVarID, start, count, &usData);
2074 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", usData);
2075 0 : break;
2076 0 : case NC_UINT:
2077 : {
2078 : unsigned int unData;
2079 0 : /* status = */ nc_get_vara_uint(nGroupID, nVarID, start,
2080 : count, &unData);
2081 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", unData);
2082 0 : break;
2083 : }
2084 1 : case NC_INT64:
2085 : {
2086 : long long nData;
2087 1 : /* status = */ nc_get_vara_longlong(
2088 : nGroupID, nVarID, start, count, &nData);
2089 1 : snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GIB,
2090 : nData);
2091 1 : break;
2092 : }
2093 0 : case NC_UINT64:
2094 : {
2095 : unsigned long long unData;
2096 0 : /* status = */ nc_get_vara_ulonglong(
2097 : nGroupID, nVarID, start, count, &unData);
2098 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GUIB,
2099 : unData);
2100 0 : break;
2101 : }
2102 0 : default:
2103 0 : CPLDebug("GDAL_netCDF", "invalid dim %s, type=%d",
2104 : szMetaTemp, nVarType);
2105 0 : break;
2106 : }
2107 : }
2108 : else
2109 : {
2110 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", result + 1);
2111 : }
2112 :
2113 : // Save dimension value.
2114 : // NOTE: removed #original_units as not part of CF-1.
2115 :
2116 38 : GDALPamRasterBand::SetMetadataItem(szMetaName, szMetaTemp);
2117 : }
2118 :
2119 : // Avoid int32 overflow. Perhaps something more sensible to do here ?
2120 40 : if (result > 0 && Sum > INT_MAX / result)
2121 0 : break;
2122 40 : if (Taken > INT_MAX - result * Sum)
2123 0 : break;
2124 :
2125 40 : Taken += result * Sum;
2126 : } // End loop non-spatial dimensions.
2127 :
2128 58 : l_poDS->nPamFlags = nPamFlagsBackup;
2129 58 : }
2130 :
2131 : /************************************************************************/
2132 : /* CheckData() */
2133 : /************************************************************************/
2134 : template <class T>
2135 6034 : void netCDFRasterBand::CheckData(void *pImage, void *pImageNC,
2136 : size_t nTmpBlockXSize, size_t nTmpBlockYSize,
2137 : bool bCheckIsNan)
2138 : {
2139 6034 : CPLAssert(pImage != nullptr && pImageNC != nullptr);
2140 :
2141 : // If this block is not a full block (in the x axis), we need to re-arrange
2142 : // the data this is because partial blocks are not arranged the same way in
2143 : // netcdf and gdal.
2144 6034 : if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
2145 : {
2146 6 : T *ptrWrite = static_cast<T *>(pImage);
2147 6 : T *ptrRead = static_cast<T *>(pImageNC);
2148 29 : for (size_t j = 0; j < nTmpBlockYSize;
2149 23 : j++, ptrWrite += nBlockXSize, ptrRead += nTmpBlockXSize)
2150 : {
2151 23 : memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T));
2152 : }
2153 : }
2154 :
2155 : // Is valid data checking needed or requested?
2156 6034 : if (bValidRangeValid || bCheckIsNan)
2157 : {
2158 1345 : T *ptrImage = static_cast<T *>(pImage);
2159 2744 : for (size_t j = 0; j < nTmpBlockYSize; j++)
2160 : {
2161 : // k moves along the gdal block, skipping the out-of-range pixels.
2162 1399 : size_t k = j * nBlockXSize;
2163 98618 : for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
2164 : {
2165 : // Check for nodata and nan.
2166 97219 : if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
2167 6301 : continue;
2168 90918 : if (bCheckIsNan && std::isnan((double)ptrImage[k]))
2169 : {
2170 5737 : ptrImage[k] = (T)m_dfNoDataValue;
2171 5737 : continue;
2172 : }
2173 : // Check for valid_range.
2174 85181 : if (bValidRangeValid)
2175 : {
2176 40986 : if (((adfValidRange[0] != m_dfNoDataValue) &&
2177 40986 : (ptrImage[k] < (T)adfValidRange[0])) ||
2178 40983 : ((adfValidRange[1] != m_dfNoDataValue) &&
2179 40983 : (ptrImage[k] > (T)adfValidRange[1])))
2180 : {
2181 4 : ptrImage[k] = (T)m_dfNoDataValue;
2182 : }
2183 : }
2184 : }
2185 : }
2186 : }
2187 :
2188 : // If minimum longitude is > 180, subtract 360 from all.
2189 : // If not, disable checking for further calls (check just once).
2190 : // Only check first and last block elements since lon must be monotonic.
2191 6034 : const bool bIsSigned = std::numeric_limits<T>::is_signed;
2192 5625 : if (bCheckLongitude && bIsSigned &&
2193 11 : !CPLIsEqual((double)((T *)pImage)[0], m_dfNoDataValue) &&
2194 10 : !CPLIsEqual((double)((T *)pImage)[nTmpBlockXSize - 1],
2195 2818 : m_dfNoDataValue) &&
2196 10 : std::min(((T *)pImage)[0], ((T *)pImage)[nTmpBlockXSize - 1]) > 180.0)
2197 : {
2198 0 : T *ptrImage = static_cast<T *>(pImage);
2199 0 : for (size_t j = 0; j < nTmpBlockYSize; j++)
2200 : {
2201 0 : size_t k = j * nBlockXSize;
2202 0 : for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
2203 : {
2204 0 : if (!CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
2205 0 : ptrImage[k] = static_cast<T>(ptrImage[k] - 360);
2206 : }
2207 : }
2208 : }
2209 : else
2210 : {
2211 6034 : bCheckLongitude = false;
2212 : }
2213 6034 : }
2214 :
2215 : /************************************************************************/
2216 : /* CheckDataCpx() */
2217 : /************************************************************************/
2218 : template <class T>
2219 25 : void netCDFRasterBand::CheckDataCpx(void *pImage, void *pImageNC,
2220 : size_t nTmpBlockXSize,
2221 : size_t nTmpBlockYSize, bool bCheckIsNan)
2222 : {
2223 25 : CPLAssert(pImage != nullptr && pImageNC != nullptr);
2224 :
2225 : // If this block is not a full block (in the x axis), we need to re-arrange
2226 : // the data this is because partial blocks are not arranged the same way in
2227 : // netcdf and gdal.
2228 25 : if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
2229 : {
2230 0 : T *ptrWrite = static_cast<T *>(pImage);
2231 0 : T *ptrRead = static_cast<T *>(pImageNC);
2232 0 : for (size_t j = 0; j < nTmpBlockYSize; j++,
2233 0 : ptrWrite += (2 * nBlockXSize),
2234 0 : ptrRead += (2 * nTmpBlockXSize))
2235 : {
2236 0 : memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T) * 2);
2237 : }
2238 : }
2239 :
2240 : // Is valid data checking needed or requested?
2241 25 : if (bValidRangeValid || bCheckIsNan)
2242 : {
2243 0 : T *ptrImage = static_cast<T *>(pImage);
2244 0 : for (size_t j = 0; j < nTmpBlockYSize; j++)
2245 : {
2246 : // k moves along the gdal block, skipping the out-of-range pixels.
2247 0 : size_t k = 2 * j * nBlockXSize;
2248 0 : for (size_t i = 0; i < (2 * nTmpBlockXSize); i++, k++)
2249 : {
2250 : // Check for nodata and nan.
2251 0 : if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
2252 0 : continue;
2253 0 : if (bCheckIsNan && std::isnan((double)ptrImage[k]))
2254 : {
2255 0 : ptrImage[k] = (T)m_dfNoDataValue;
2256 0 : continue;
2257 : }
2258 : // Check for valid_range.
2259 0 : if (bValidRangeValid)
2260 : {
2261 0 : if (((adfValidRange[0] != m_dfNoDataValue) &&
2262 0 : (ptrImage[k] < (T)adfValidRange[0])) ||
2263 0 : ((adfValidRange[1] != m_dfNoDataValue) &&
2264 0 : (ptrImage[k] > (T)adfValidRange[1])))
2265 : {
2266 0 : ptrImage[k] = (T)m_dfNoDataValue;
2267 : }
2268 : }
2269 : }
2270 : }
2271 : }
2272 25 : }
2273 :
2274 : /************************************************************************/
2275 : /* FetchNetcdfChunk() */
2276 : /************************************************************************/
2277 :
2278 6059 : bool netCDFRasterBand::FetchNetcdfChunk(size_t xstart, size_t ystart,
2279 : void *pImage)
2280 : {
2281 6059 : size_t start[MAX_NC_DIMS] = {};
2282 6059 : size_t edge[MAX_NC_DIMS] = {};
2283 :
2284 6059 : start[nBandXPos] = xstart;
2285 6059 : edge[nBandXPos] = nBlockXSize;
2286 6059 : if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
2287 6 : edge[nBandXPos] = nRasterXSize - start[nBandXPos];
2288 6059 : if (nBandYPos >= 0)
2289 : {
2290 6055 : start[nBandYPos] = ystart;
2291 6055 : edge[nBandYPos] = nBlockYSize;
2292 6055 : if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
2293 4 : edge[nBandYPos] = nRasterYSize - start[nBandYPos];
2294 : }
2295 6059 : const size_t nYChunkSize = nBandYPos < 0 ? 1 : edge[nBandYPos];
2296 :
2297 : #ifdef NCDF_DEBUG
2298 : CPLDebug("GDAL_netCDF", "start={%ld,%ld} edge={%ld,%ld} bBottomUp=%d",
2299 : start[nBandXPos], nBandYPos < 0 ? 0 : start[nBandYPos],
2300 : edge[nBandXPos], nYChunkSize, ((netCDFDataset *)poDS)->bBottomUp);
2301 : #endif
2302 :
2303 6059 : int nd = 0;
2304 6059 : nc_inq_varndims(cdfid, nZId, &nd);
2305 6059 : if (nd == 3)
2306 : {
2307 1100 : start[panBandZPos[0]] = nLevel; // z
2308 1100 : edge[panBandZPos[0]] = 1;
2309 : }
2310 :
2311 : // Compute multidimention band position.
2312 : //
2313 : // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
2314 : // if Data[2,3,4,x,y]
2315 : //
2316 : // BandPos0 = (nBand) / (3*4)
2317 : // BandPos1 = (nBand - (3*4)) / (4)
2318 : // BandPos2 = (nBand - (3*4)) % (4)
2319 6059 : if (nd > 3)
2320 : {
2321 160 : int Sum = -1;
2322 160 : int Taken = 0;
2323 480 : for (int i = 0; i < nd - 2; i++)
2324 : {
2325 320 : if (i != nd - 2 - 1)
2326 : {
2327 160 : Sum = 1;
2328 320 : for (int j = i + 1; j < nd - 2; j++)
2329 : {
2330 160 : Sum *= panBandZLev[j];
2331 : }
2332 160 : start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
2333 160 : edge[panBandZPos[i]] = 1;
2334 : }
2335 : else
2336 : {
2337 160 : start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
2338 160 : edge[panBandZPos[i]] = 1;
2339 : }
2340 320 : Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
2341 : }
2342 : }
2343 :
2344 : // Make sure we are in data mode.
2345 6059 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
2346 :
2347 : // If this block is not a full block in the x axis, we need to
2348 : // re-arrange the data because partial blocks are not arranged the
2349 : // same way in netcdf and gdal, so we first we read the netcdf data at
2350 : // the end of the gdal block buffer then re-arrange rows in CheckData().
2351 6059 : void *pImageNC = pImage;
2352 6059 : if (edge[nBandXPos] != static_cast<size_t>(nBlockXSize))
2353 : {
2354 6 : pImageNC = static_cast<GByte *>(pImage) +
2355 6 : ((static_cast<size_t>(nBlockXSize) * nBlockYSize -
2356 12 : edge[nBandXPos] * nYChunkSize) *
2357 6 : GDALGetDataTypeSizeBytes(eDataType));
2358 : }
2359 :
2360 : // Read data according to type.
2361 : int status;
2362 6059 : if (eDataType == GDT_UInt8)
2363 : {
2364 3205 : if (bSignedData)
2365 : {
2366 0 : status = nc_get_vara_schar(cdfid, nZId, start, edge,
2367 : static_cast<signed char *>(pImageNC));
2368 0 : if (status == NC_NOERR)
2369 0 : CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
2370 : nYChunkSize, false);
2371 : }
2372 : else
2373 : {
2374 3205 : status = nc_get_vara_uchar(cdfid, nZId, start, edge,
2375 : static_cast<unsigned char *>(pImageNC));
2376 3205 : if (status == NC_NOERR)
2377 3205 : CheckData<unsigned char>(pImage, pImageNC, edge[nBandXPos],
2378 : nYChunkSize, false);
2379 : }
2380 : }
2381 2854 : else if (eDataType == GDT_Int8)
2382 : {
2383 60 : status = nc_get_vara_schar(cdfid, nZId, start, edge,
2384 : static_cast<signed char *>(pImageNC));
2385 60 : if (status == NC_NOERR)
2386 60 : CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
2387 : nYChunkSize, false);
2388 : }
2389 2794 : else if (nc_datatype == NC_SHORT)
2390 : {
2391 487 : status = nc_get_vara_short(cdfid, nZId, start, edge,
2392 : static_cast<short *>(pImageNC));
2393 487 : if (status == NC_NOERR)
2394 : {
2395 487 : if (eDataType == GDT_Int16)
2396 : {
2397 484 : CheckData<GInt16>(pImage, pImageNC, edge[nBandXPos],
2398 : nYChunkSize, false);
2399 : }
2400 : else
2401 : {
2402 3 : CheckData<GUInt16>(pImage, pImageNC, edge[nBandXPos],
2403 : nYChunkSize, false);
2404 : }
2405 : }
2406 : }
2407 2307 : else if (eDataType == GDT_Int32)
2408 : {
2409 : #if SIZEOF_UNSIGNED_LONG == 4
2410 : status = nc_get_vara_long(cdfid, nZId, start, edge,
2411 : static_cast<long *>(pImageNC));
2412 : if (status == NC_NOERR)
2413 : CheckData<long>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2414 : false);
2415 : #else
2416 912 : status = nc_get_vara_int(cdfid, nZId, start, edge,
2417 : static_cast<int *>(pImageNC));
2418 912 : if (status == NC_NOERR)
2419 912 : CheckData<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2420 : false);
2421 : #endif
2422 : }
2423 1395 : else if (eDataType == GDT_Float32)
2424 : {
2425 1258 : status = nc_get_vara_float(cdfid, nZId, start, edge,
2426 : static_cast<float *>(pImageNC));
2427 1258 : if (status == NC_NOERR)
2428 1258 : CheckData<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2429 : true);
2430 : }
2431 137 : else if (eDataType == GDT_Float64)
2432 : {
2433 86 : status = nc_get_vara_double(cdfid, nZId, start, edge,
2434 : static_cast<double *>(pImageNC));
2435 86 : if (status == NC_NOERR)
2436 86 : CheckData<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2437 : true);
2438 : }
2439 51 : else if (eDataType == GDT_UInt16)
2440 : {
2441 6 : status = nc_get_vara_ushort(cdfid, nZId, start, edge,
2442 : static_cast<unsigned short *>(pImageNC));
2443 6 : if (status == NC_NOERR)
2444 6 : CheckData<unsigned short>(pImage, pImageNC, edge[nBandXPos],
2445 : nYChunkSize, false);
2446 : }
2447 45 : else if (eDataType == GDT_UInt32)
2448 : {
2449 6 : status = nc_get_vara_uint(cdfid, nZId, start, edge,
2450 : static_cast<unsigned int *>(pImageNC));
2451 6 : if (status == NC_NOERR)
2452 6 : CheckData<unsigned int>(pImage, pImageNC, edge[nBandXPos],
2453 : nYChunkSize, false);
2454 : }
2455 39 : else if (eDataType == GDT_Int64)
2456 : {
2457 7 : status = nc_get_vara_longlong(cdfid, nZId, start, edge,
2458 : static_cast<long long *>(pImageNC));
2459 7 : if (status == NC_NOERR)
2460 7 : CheckData<std::int64_t>(pImage, pImageNC, edge[nBandXPos],
2461 : nYChunkSize, false);
2462 : }
2463 32 : else if (eDataType == GDT_UInt64)
2464 : {
2465 : status =
2466 7 : nc_get_vara_ulonglong(cdfid, nZId, start, edge,
2467 : static_cast<unsigned long long *>(pImageNC));
2468 7 : if (status == NC_NOERR)
2469 7 : CheckData<std::uint64_t>(pImage, pImageNC, edge[nBandXPos],
2470 : nYChunkSize, false);
2471 : }
2472 25 : else if (eDataType == GDT_CInt16)
2473 : {
2474 0 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2475 0 : if (status == NC_NOERR)
2476 0 : CheckDataCpx<short>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2477 : false);
2478 : }
2479 25 : else if (eDataType == GDT_CInt32)
2480 : {
2481 0 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2482 0 : if (status == NC_NOERR)
2483 0 : CheckDataCpx<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2484 : false);
2485 : }
2486 25 : else if (eDataType == GDT_CFloat32)
2487 : {
2488 20 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2489 20 : if (status == NC_NOERR)
2490 20 : CheckDataCpx<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2491 : false);
2492 : }
2493 5 : else if (eDataType == GDT_CFloat64)
2494 : {
2495 5 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2496 5 : if (status == NC_NOERR)
2497 5 : CheckDataCpx<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2498 : false);
2499 : }
2500 :
2501 : else
2502 0 : status = NC_EBADTYPE;
2503 :
2504 6059 : if (status != NC_NOERR)
2505 : {
2506 0 : CPLError(CE_Failure, CPLE_AppDefined,
2507 : "netCDF chunk fetch failed: #%d (%s)", status,
2508 : nc_strerror(status));
2509 0 : return false;
2510 : }
2511 6059 : return true;
2512 : }
2513 :
2514 : /************************************************************************/
2515 : /* IReadBlock() */
2516 : /************************************************************************/
2517 :
2518 6059 : CPLErr netCDFRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
2519 : void *pImage)
2520 :
2521 : {
2522 12118 : CPLMutexHolderD(&hNCMutex);
2523 :
2524 : // Locate X, Y and Z position in the array.
2525 :
2526 6059 : size_t xstart = static_cast<size_t>(nBlockXOff) * nBlockXSize;
2527 6059 : size_t ystart = 0;
2528 :
2529 : // Check y order.
2530 6059 : if (nBandYPos >= 0)
2531 : {
2532 6055 : auto poGDS = cpl::down_cast<netCDFDataset *>(poDS);
2533 6055 : if (poGDS->bBottomUp)
2534 : {
2535 5118 : if (nBlockYSize == 1)
2536 : {
2537 5105 : ystart = nRasterYSize - 1 - nBlockYOff;
2538 : }
2539 : else
2540 : {
2541 : // in GDAL space
2542 13 : ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
2543 : const size_t yend =
2544 26 : std::min(ystart + nBlockYSize - 1,
2545 13 : static_cast<size_t>(nRasterYSize - 1));
2546 : // in netCDF space
2547 13 : const size_t nFirstChunkLine = nRasterYSize - 1 - yend;
2548 13 : const size_t nLastChunkLine = nRasterYSize - 1 - ystart;
2549 13 : const size_t nFirstChunkBlock = nFirstChunkLine / nBlockYSize;
2550 13 : const size_t nLastChunkBlock = nLastChunkLine / nBlockYSize;
2551 :
2552 : const auto firstKey = netCDFDataset::ChunkKey(
2553 13 : nBlockXOff, nFirstChunkBlock, nBand);
2554 : const auto secondKey =
2555 13 : netCDFDataset::ChunkKey(nBlockXOff, nLastChunkBlock, nBand);
2556 :
2557 : // Retrieve data from the one or 2 needed netCDF chunks
2558 13 : std::shared_ptr<std::vector<GByte>> firstChunk;
2559 13 : std::shared_ptr<std::vector<GByte>> secondChunk;
2560 13 : if (poGDS->poChunkCache)
2561 : {
2562 13 : poGDS->poChunkCache->tryGet(firstKey, firstChunk);
2563 13 : if (firstKey != secondKey)
2564 6 : poGDS->poChunkCache->tryGet(secondKey, secondChunk);
2565 : }
2566 : const size_t nChunkLineSize =
2567 13 : static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
2568 13 : nBlockXSize;
2569 13 : const size_t nChunkSize = nChunkLineSize * nBlockYSize;
2570 13 : if (!firstChunk)
2571 : {
2572 11 : firstChunk.reset(new std::vector<GByte>(nChunkSize));
2573 11 : if (!FetchNetcdfChunk(xstart,
2574 11 : nFirstChunkBlock * nBlockYSize,
2575 11 : firstChunk.get()->data()))
2576 0 : return CE_Failure;
2577 11 : if (poGDS->poChunkCache)
2578 11 : poGDS->poChunkCache->insert(firstKey, firstChunk);
2579 : }
2580 13 : if (!secondChunk && firstKey != secondKey)
2581 : {
2582 2 : secondChunk.reset(new std::vector<GByte>(nChunkSize));
2583 2 : if (!FetchNetcdfChunk(xstart, nLastChunkBlock * nBlockYSize,
2584 2 : secondChunk.get()->data()))
2585 0 : return CE_Failure;
2586 2 : if (poGDS->poChunkCache)
2587 2 : poGDS->poChunkCache->insert(secondKey, secondChunk);
2588 : }
2589 :
2590 : // Assemble netCDF chunks into GDAL block
2591 13 : GByte *pabyImage = static_cast<GByte *>(pImage);
2592 13 : const size_t nFirstChunkBlockLine =
2593 13 : nFirstChunkBlock * nBlockYSize;
2594 13 : const size_t nLastChunkBlockLine =
2595 13 : nLastChunkBlock * nBlockYSize;
2596 146 : for (size_t iLine = ystart; iLine <= yend; iLine++)
2597 : {
2598 133 : const size_t nLineFromBottom = nRasterYSize - 1 - iLine;
2599 133 : const size_t nChunkY = nLineFromBottom / nBlockYSize;
2600 133 : if (nChunkY == nFirstChunkBlock)
2601 : {
2602 121 : memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
2603 121 : firstChunk.get()->data() +
2604 121 : (nLineFromBottom - nFirstChunkBlockLine) *
2605 : nChunkLineSize,
2606 : nChunkLineSize);
2607 : }
2608 : else
2609 : {
2610 12 : CPLAssert(nChunkY == nLastChunkBlock);
2611 12 : assert(secondChunk);
2612 12 : memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
2613 12 : secondChunk.get()->data() +
2614 12 : (nLineFromBottom - nLastChunkBlockLine) *
2615 : nChunkLineSize,
2616 : nChunkLineSize);
2617 : }
2618 : }
2619 13 : return CE_None;
2620 : }
2621 : }
2622 : else
2623 : {
2624 937 : ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
2625 : }
2626 : }
2627 :
2628 6046 : return FetchNetcdfChunk(xstart, ystart, pImage) ? CE_None : CE_Failure;
2629 : }
2630 :
2631 : /************************************************************************/
2632 : /* IWriteBlock() */
2633 : /************************************************************************/
2634 :
2635 6601 : CPLErr netCDFRasterBand::IWriteBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
2636 : void *pImage)
2637 : {
2638 13202 : CPLMutexHolderD(&hNCMutex);
2639 :
2640 : #ifdef NCDF_DEBUG
2641 : if (nBlockYOff == 0 || (nBlockYOff == nRasterYSize - 1))
2642 : CPLDebug("GDAL_netCDF",
2643 : "netCDFRasterBand::IWriteBlock( %d, %d, ...) nBand=%d",
2644 : nBlockXOff, nBlockYOff, nBand);
2645 : #endif
2646 :
2647 6601 : int nd = 0;
2648 6601 : nc_inq_varndims(cdfid, nZId, &nd);
2649 :
2650 : // Locate X, Y and Z position in the array.
2651 :
2652 : size_t start[MAX_NC_DIMS];
2653 6601 : memset(start, 0, sizeof(start));
2654 6601 : start[nBandXPos] = static_cast<size_t>(nBlockXOff) * nBlockXSize;
2655 :
2656 : // check y order.
2657 6601 : if (cpl::down_cast<netCDFDataset *>(poDS)->bBottomUp)
2658 : {
2659 6537 : if (nBlockYSize == 1)
2660 : {
2661 6537 : start[nBandYPos] = nRasterYSize - 1 - nBlockYOff;
2662 : }
2663 : else
2664 : {
2665 0 : CPLError(CE_Failure, CPLE_AppDefined,
2666 : "nBlockYSize = %d, only 1 supported when "
2667 : "writing bottom-up dataset",
2668 : nBlockYSize);
2669 0 : return CE_Failure;
2670 : }
2671 : }
2672 : else
2673 : {
2674 64 : start[nBandYPos] = static_cast<size_t>(nBlockYOff) * nBlockYSize; // y
2675 : }
2676 :
2677 6601 : size_t edge[MAX_NC_DIMS] = {};
2678 :
2679 6601 : edge[nBandXPos] = nBlockXSize;
2680 6601 : if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
2681 0 : edge[nBandXPos] = nRasterXSize - start[nBandXPos];
2682 6601 : edge[nBandYPos] = nBlockYSize;
2683 6601 : if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
2684 0 : edge[nBandYPos] = nRasterYSize - start[nBandYPos];
2685 :
2686 6601 : if (nd == 3)
2687 : {
2688 610 : start[panBandZPos[0]] = nLevel; // z
2689 610 : edge[panBandZPos[0]] = 1;
2690 : }
2691 :
2692 : // Compute multidimention band position.
2693 : //
2694 : // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
2695 : // if Data[2,3,4,x,y]
2696 : //
2697 : // BandPos0 = (nBand) / (3*4)
2698 : // BandPos1 = (nBand - (3*4)) / (4)
2699 : // BandPos2 = (nBand - (3*4)) % (4)
2700 6601 : if (nd > 3)
2701 : {
2702 178 : int Sum = -1;
2703 178 : int Taken = 0;
2704 534 : for (int i = 0; i < nd - 2; i++)
2705 : {
2706 356 : if (i != nd - 2 - 1)
2707 : {
2708 178 : Sum = 1;
2709 356 : for (int j = i + 1; j < nd - 2; j++)
2710 : {
2711 178 : Sum *= panBandZLev[j];
2712 : }
2713 178 : start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
2714 178 : edge[panBandZPos[i]] = 1;
2715 : }
2716 : else
2717 : {
2718 178 : start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
2719 178 : edge[panBandZPos[i]] = 1;
2720 : }
2721 356 : Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
2722 : }
2723 : }
2724 :
2725 : // Make sure we are in data mode.
2726 6601 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
2727 :
2728 : // Copy data according to type.
2729 6601 : int status = 0;
2730 6601 : if (eDataType == GDT_UInt8)
2731 : {
2732 6022 : if (bSignedData)
2733 0 : status = nc_put_vara_schar(cdfid, nZId, start, edge,
2734 : static_cast<signed char *>(pImage));
2735 : else
2736 6022 : status = nc_put_vara_uchar(cdfid, nZId, start, edge,
2737 : static_cast<unsigned char *>(pImage));
2738 : }
2739 579 : else if (eDataType == GDT_Int8)
2740 : {
2741 40 : status = nc_put_vara_schar(cdfid, nZId, start, edge,
2742 : static_cast<signed char *>(pImage));
2743 : }
2744 539 : else if (nc_datatype == NC_SHORT)
2745 : {
2746 101 : status = nc_put_vara_short(cdfid, nZId, start, edge,
2747 : static_cast<short *>(pImage));
2748 : }
2749 438 : else if (eDataType == GDT_Int32)
2750 : {
2751 210 : status = nc_put_vara_int(cdfid, nZId, start, edge,
2752 : static_cast<int *>(pImage));
2753 : }
2754 228 : else if (eDataType == GDT_Float32)
2755 : {
2756 148 : status = nc_put_vara_float(cdfid, nZId, start, edge,
2757 : static_cast<float *>(pImage));
2758 : }
2759 80 : else if (eDataType == GDT_Float64)
2760 : {
2761 50 : status = nc_put_vara_double(cdfid, nZId, start, edge,
2762 : static_cast<double *>(pImage));
2763 : }
2764 42 : else if (eDataType == GDT_UInt16 &&
2765 12 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2766 : {
2767 12 : status = nc_put_vara_ushort(cdfid, nZId, start, edge,
2768 : static_cast<unsigned short *>(pImage));
2769 : }
2770 30 : else if (eDataType == GDT_UInt32 &&
2771 12 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2772 : {
2773 12 : status = nc_put_vara_uint(cdfid, nZId, start, edge,
2774 : static_cast<unsigned int *>(pImage));
2775 : }
2776 9 : else if (eDataType == GDT_UInt64 &&
2777 3 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2778 : {
2779 : status =
2780 3 : nc_put_vara_ulonglong(cdfid, nZId, start, edge,
2781 : static_cast<unsigned long long *>(pImage));
2782 : }
2783 6 : else if (eDataType == GDT_Int64 &&
2784 3 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2785 : {
2786 3 : status = nc_put_vara_longlong(cdfid, nZId, start, edge,
2787 : static_cast<long long *>(pImage));
2788 : }
2789 : else
2790 : {
2791 0 : CPLError(CE_Failure, CPLE_NotSupported,
2792 : "The NetCDF driver does not support GDAL data type %d",
2793 0 : eDataType);
2794 0 : status = NC_EBADTYPE;
2795 : }
2796 6601 : NCDF_ERR(status);
2797 :
2798 6601 : if (status != NC_NOERR)
2799 : {
2800 0 : CPLError(CE_Failure, CPLE_AppDefined,
2801 : "netCDF scanline write failed: %s", nc_strerror(status));
2802 0 : return CE_Failure;
2803 : }
2804 :
2805 6601 : return CE_None;
2806 : }
2807 :
2808 : /************************************************************************/
2809 : /* ==================================================================== */
2810 : /* netCDFDataset */
2811 : /* ==================================================================== */
2812 : /************************************************************************/
2813 :
2814 : /************************************************************************/
2815 : /* netCDFDataset() */
2816 : /************************************************************************/
2817 :
2818 1315 : netCDFDataset::netCDFDataset()
2819 : :
2820 : // Basic dataset vars.
2821 : #ifdef ENABLE_NCDUMP
2822 : bFileToDestroyAtClosing(false),
2823 : #endif
2824 : cdfid(-1), nSubDatasets(0), bBottomUp(true), eFormat(NCDF_FORMAT_NONE),
2825 : bIsGdalFile(false), bIsGdalCfFile(false), pszCFProjection(nullptr),
2826 : pszCFCoordinates(nullptr), nCFVersion(1.6), bSGSupport(false),
2827 1315 : eMultipleLayerBehavior(SINGLE_LAYER), logCount(0), vcdf(this, cdfid),
2828 1315 : GeometryScribe(vcdf, this->generateLogName()),
2829 1315 : FieldScribe(vcdf, this->generateLogName()),
2830 2630 : bufManager(CPLGetUsablePhysicalRAM() / 5),
2831 :
2832 : // projection/GT.
2833 : nXDimID(-1), nYDimID(-1), bIsProjected(false),
2834 : bIsGeographic(false), // Can be not projected, and also not geographic
2835 : // State vars.
2836 : bDefineMode(true), bAddedGridMappingRef(false),
2837 :
2838 : // Create vars.
2839 : eCompress(NCDF_COMPRESS_NONE), nZLevel(NCDF_DEFLATE_LEVEL),
2840 3945 : bChunking(false), nCreateMode(NC_CLOBBER), bSignedData(true)
2841 : {
2842 1315 : m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2843 :
2844 : // Set buffers
2845 1315 : bufManager.addBuffer(&(GeometryScribe.getMemBuffer()));
2846 1315 : bufManager.addBuffer(&(FieldScribe.getMemBuffer()));
2847 1315 : }
2848 :
2849 : /************************************************************************/
2850 : /* ~netCDFDataset() */
2851 : /************************************************************************/
2852 :
2853 2523 : netCDFDataset::~netCDFDataset()
2854 :
2855 : {
2856 1315 : netCDFDataset::Close();
2857 2523 : }
2858 :
2859 : /************************************************************************/
2860 : /* Close() */
2861 : /************************************************************************/
2862 :
2863 2208 : CPLErr netCDFDataset::Close(GDALProgressFunc, void *)
2864 : {
2865 2208 : CPLErr eErr = CE_None;
2866 2208 : if (nOpenFlags != OPEN_FLAGS_CLOSED)
2867 : {
2868 2630 : CPLMutexHolderD(&hNCMutex);
2869 :
2870 : #ifdef NCDF_DEBUG
2871 : CPLDebug("GDAL_netCDF",
2872 : "netCDFDataset::~netCDFDataset(), cdfid=%d filename=%s", cdfid,
2873 : osFilename.c_str());
2874 : #endif
2875 :
2876 : // Write data related to geotransform
2877 1626 : if (GetAccess() == GA_Update && !m_bAddedProjectionVarsData &&
2878 311 : (m_bHasProjection || m_bHasGeoTransform))
2879 : {
2880 : // Ensure projection is written if GeoTransform OR Projection are
2881 : // missing.
2882 37 : if (!m_bAddedProjectionVarsDefs)
2883 : {
2884 2 : AddProjectionVars(true, nullptr, nullptr);
2885 : }
2886 37 : AddProjectionVars(false, nullptr, nullptr);
2887 : }
2888 :
2889 1315 : if (netCDFDataset::FlushCache(true) != CE_None)
2890 0 : eErr = CE_Failure;
2891 :
2892 1315 : if (GetAccess() == GA_Update && !SGCommitPendingTransaction())
2893 0 : eErr = CE_Failure;
2894 :
2895 1317 : for (size_t i = 0; i < apoVectorDatasets.size(); i++)
2896 2 : delete apoVectorDatasets[i];
2897 :
2898 : // Make sure projection variable is written to band variable.
2899 1315 : if (GetAccess() == GA_Update && !bAddedGridMappingRef)
2900 : {
2901 339 : if (!AddGridMappingRef())
2902 0 : eErr = CE_Failure;
2903 : }
2904 :
2905 1315 : CPLFree(pszCFProjection);
2906 :
2907 1315 : if (cdfid > 0)
2908 : {
2909 : #ifdef NCDF_DEBUG
2910 : CPLDebug("GDAL_netCDF", "calling nc_close( %d)", cdfid);
2911 : #endif
2912 721 : int status = GDAL_nc_close(cdfid);
2913 : #ifdef ENABLE_UFFD
2914 721 : NETCDF_UFFD_UNMAP(pCtx);
2915 : #endif
2916 721 : NCDF_ERR(status);
2917 721 : if (status != NC_NOERR)
2918 0 : eErr = CE_Failure;
2919 : }
2920 :
2921 1315 : if (fpVSIMEM)
2922 15 : VSIFCloseL(fpVSIMEM);
2923 :
2924 : #ifdef ENABLE_NCDUMP
2925 1315 : if (bFileToDestroyAtClosing)
2926 0 : VSIUnlink(osFilename);
2927 : #endif
2928 :
2929 1315 : if (GDALPamDataset::Close() != CE_None)
2930 0 : eErr = CE_Failure;
2931 : }
2932 2208 : return eErr;
2933 : }
2934 :
2935 : /************************************************************************/
2936 : /* SetDefineMode() */
2937 : /************************************************************************/
2938 14785 : bool netCDFDataset::SetDefineMode(bool bNewDefineMode)
2939 : {
2940 : // Do nothing if already in new define mode
2941 : // or if dataset is in read-only mode or if dataset is true NC4 dataset.
2942 15371 : if (bDefineMode == bNewDefineMode || GetAccess() == GA_ReadOnly ||
2943 586 : eFormat == NCDF_FORMAT_NC4)
2944 14346 : return true;
2945 :
2946 439 : CPLDebug("GDAL_netCDF", "SetDefineMode(%d) old=%d",
2947 439 : static_cast<int>(bNewDefineMode), static_cast<int>(bDefineMode));
2948 :
2949 439 : bDefineMode = bNewDefineMode;
2950 :
2951 : int status;
2952 439 : if (bDefineMode)
2953 152 : status = nc_redef(cdfid);
2954 : else
2955 287 : status = nc_enddef(cdfid);
2956 :
2957 439 : NCDF_ERR(status);
2958 439 : return status == NC_NOERR;
2959 : }
2960 :
2961 : /************************************************************************/
2962 : /* GetMetadataDomainList() */
2963 : /************************************************************************/
2964 :
2965 26 : char **netCDFDataset::GetMetadataDomainList()
2966 : {
2967 : char **papszDomains =
2968 26 : BuildMetadataDomainList(GDALDataset::GetMetadataDomainList(), TRUE,
2969 : GDAL_MDD_SUBDATASETS, nullptr);
2970 27 : for (const auto &kv : m_oMapDomainToJSon)
2971 1 : papszDomains = CSLAddString(papszDomains, ("json:" + kv.first).c_str());
2972 26 : return papszDomains;
2973 : }
2974 :
2975 : /************************************************************************/
2976 : /* GetMetadata() */
2977 : /************************************************************************/
2978 444 : CSLConstList netCDFDataset::GetMetadata(const char *pszDomain)
2979 : {
2980 444 : if (pszDomain != nullptr && STARTS_WITH_CI(pszDomain, GDAL_MDD_SUBDATASETS))
2981 46 : return aosSubDatasets.List();
2982 :
2983 398 : if (pszDomain != nullptr && STARTS_WITH(pszDomain, "json:"))
2984 : {
2985 6 : auto iter = m_oMapDomainToJSon.find(pszDomain + strlen("json:"));
2986 6 : if (iter != m_oMapDomainToJSon.end())
2987 1 : return iter->second.List();
2988 : }
2989 :
2990 397 : return GDALDataset::GetMetadata(pszDomain);
2991 : }
2992 :
2993 : /************************************************************************/
2994 : /* SetMetadataItem() */
2995 : /************************************************************************/
2996 :
2997 43 : CPLErr netCDFDataset::SetMetadataItem(const char *pszName, const char *pszValue,
2998 : const char *pszDomain)
2999 : {
3000 85 : if (GetAccess() == GA_Update &&
3001 85 : (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)
3002 : {
3003 42 : std::string osName(pszName);
3004 :
3005 : // Same logic as in CopyMetadata()
3006 42 : if (cpl::starts_with(osName, "NC_GLOBAL#"))
3007 8 : osName = osName.substr(strlen("NC_GLOBAL#"));
3008 34 : else if (strchr(osName.c_str(), '#') == nullptr)
3009 5 : osName = "GDAL_" + osName;
3010 :
3011 84 : if (cpl::starts_with(osName, "NETCDF_DIM_") ||
3012 42 : strchr(osName.c_str(), '#') != nullptr)
3013 : {
3014 : // do nothing
3015 29 : return CE_None;
3016 : }
3017 : else
3018 : {
3019 13 : SetDefineMode(true);
3020 :
3021 13 : if (!NCDFPutAttr(cdfid, NC_GLOBAL, osName.c_str(), pszValue))
3022 13 : return CE_Failure;
3023 : }
3024 : }
3025 :
3026 1 : return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);
3027 : }
3028 :
3029 : /************************************************************************/
3030 : /* SetMetadata() */
3031 : /************************************************************************/
3032 :
3033 8 : CPLErr netCDFDataset::SetMetadata(CSLConstList papszMD, const char *pszDomain)
3034 : {
3035 13 : if (GetAccess() == GA_Update &&
3036 5 : (pszDomain == nullptr || pszDomain[0] == '\0'))
3037 : {
3038 : // We don't handle metadata item removal for now
3039 50 : for (const char *const *papszIter = papszMD; papszIter && *papszIter;
3040 : ++papszIter)
3041 : {
3042 42 : char *pszName = nullptr;
3043 42 : const char *pszValue = CPLParseNameValue(*papszIter, &pszName);
3044 42 : if (pszName && pszValue)
3045 42 : SetMetadataItem(pszName, pszValue);
3046 42 : CPLFree(pszName);
3047 : }
3048 8 : return CE_None;
3049 : }
3050 0 : return GDALPamDataset::SetMetadata(papszMD, pszDomain);
3051 : }
3052 :
3053 : /************************************************************************/
3054 : /* GetSpatialRef() */
3055 : /************************************************************************/
3056 :
3057 244 : const OGRSpatialReference *netCDFDataset::GetSpatialRef() const
3058 : {
3059 244 : if (m_bHasProjection)
3060 119 : return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
3061 :
3062 125 : return GDALPamDataset::GetSpatialRef();
3063 : }
3064 :
3065 : /************************************************************************/
3066 : /* FetchCopyParam() */
3067 : /************************************************************************/
3068 :
3069 448 : double netCDFDataset::FetchCopyParam(const char *pszGridMappingValue,
3070 : const char *pszParam, double dfDefault,
3071 : bool *pbFound) const
3072 :
3073 : {
3074 896 : std::string osTemp = CPLOPrintf("%s#%s", pszGridMappingValue, pszParam);
3075 448 : const char *pszValue = aosMetadata.FetchNameValue(osTemp.c_str());
3076 :
3077 448 : if (pbFound)
3078 : {
3079 448 : *pbFound = (pszValue != nullptr);
3080 : }
3081 :
3082 448 : if (pszValue)
3083 : {
3084 0 : return CPLAtofM(pszValue);
3085 : }
3086 :
3087 448 : return dfDefault;
3088 : }
3089 :
3090 : /************************************************************************/
3091 : /* FetchStandardParallels() */
3092 : /************************************************************************/
3093 :
3094 : std::vector<std::string>
3095 0 : netCDFDataset::FetchStandardParallels(const char *pszGridMappingValue) const
3096 : {
3097 : // cf-1.0 tags
3098 0 : const char *pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL);
3099 :
3100 0 : std::vector<std::string> ret;
3101 0 : if (pszValue != nullptr)
3102 : {
3103 0 : CPLStringList aosValues;
3104 0 : if (pszValue[0] != '{' &&
3105 0 : CPLString(pszValue).Trim().find(' ') != std::string::npos)
3106 : {
3107 : // Some files like
3108 : // ftp://data.knmi.nl/download/KNW-NetCDF-3D/1.0/noversion/2013/11/14/KNW-1.0_H37-ERA_NL_20131114.nc
3109 : // do not use standard formatting for arrays, but just space
3110 : // separated syntax
3111 0 : aosValues = CSLTokenizeString2(pszValue, " ", 0);
3112 : }
3113 : else
3114 : {
3115 0 : aosValues = NCDFTokenizeArray(pszValue);
3116 : }
3117 0 : for (int i = 0; i < aosValues.size(); i++)
3118 : {
3119 0 : ret.push_back(aosValues[i]);
3120 : }
3121 : }
3122 : // Try gdal tags.
3123 : else
3124 : {
3125 0 : pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_1);
3126 :
3127 0 : if (pszValue != nullptr)
3128 0 : ret.push_back(pszValue);
3129 :
3130 0 : pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_2);
3131 :
3132 0 : if (pszValue != nullptr)
3133 0 : ret.push_back(pszValue);
3134 : }
3135 :
3136 0 : return ret;
3137 : }
3138 :
3139 : /************************************************************************/
3140 : /* FetchAttr() */
3141 : /************************************************************************/
3142 :
3143 4379 : const char *netCDFDataset::FetchAttr(const char *pszVarFullName,
3144 : const char *pszAttr) const
3145 :
3146 : {
3147 4379 : auto oKey = CPLOPrintf("%s#%s", pszVarFullName, pszAttr);
3148 4379 : const char *pszValue = aosMetadata.FetchNameValue(oKey.c_str());
3149 8758 : return pszValue;
3150 : }
3151 :
3152 2854 : const char *netCDFDataset::FetchAttr(int nGroupId, int nVarId,
3153 : const char *pszAttr) const
3154 :
3155 : {
3156 2854 : std::string osFullName;
3157 2854 : NCDFGetVarFullName(nGroupId, nVarId, osFullName);
3158 2854 : const char *pszValue = FetchAttr(osFullName.c_str(), pszAttr);
3159 5708 : return pszValue;
3160 : }
3161 :
3162 : /************************************************************************/
3163 : /* IsDifferenceBelow() */
3164 : /************************************************************************/
3165 :
3166 1187 : static bool IsDifferenceBelow(double dfA, double dfB, double dfError)
3167 : {
3168 1187 : const double dfAbsDiff = fabs(dfA - dfB);
3169 1187 : return dfAbsDiff <= dfError;
3170 : }
3171 :
3172 : /************************************************************************/
3173 : /* SetProjectionFromVar() */
3174 : /************************************************************************/
3175 630 : void netCDFDataset::SetProjectionFromVar(
3176 : int nGroupId, int nVarId, bool bReadSRSOnly, const char *pszGivenGM,
3177 : std::string *returnProjStr, nccfdriver::SGeometry_Reader *sg,
3178 : std::vector<std::string> *paosRemovedMDItems)
3179 : {
3180 630 : bool bGotGeogCS = false;
3181 630 : bool bGotCfSRS = false;
3182 630 : bool bGotCfWktSRS = false;
3183 630 : bool bGotGdalSRS = false;
3184 630 : bool bGotCfGT = false;
3185 630 : bool bGotGdalGT = false;
3186 :
3187 : // These values from CF metadata.
3188 630 : OGRSpatialReference oSRS;
3189 630 : oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3190 630 : size_t xdim = nRasterXSize;
3191 630 : size_t ydim = nRasterYSize;
3192 :
3193 : // These values from GDAL metadata.
3194 630 : const char *pszWKT = nullptr;
3195 630 : const char *pszGeoTransform = nullptr;
3196 :
3197 630 : netCDFDataset *poDS = this; // Perhaps this should be removed for clarity.
3198 :
3199 630 : CPLDebug("GDAL_netCDF", "\n=====\nSetProjectionFromVar( %d, %d)", nGroupId,
3200 : nVarId);
3201 :
3202 : // Get x/y range information.
3203 :
3204 : // Temp variables to use in SetGeoTransform() and SetProjection().
3205 630 : GDALGeoTransform tmpGT;
3206 :
3207 : // Look for grid_mapping metadata.
3208 630 : const char *pszValue = pszGivenGM;
3209 630 : CPLString osTmpGridMapping; // let is in this outer scope as pszValue may
3210 : // point to it
3211 630 : if (pszValue == nullptr)
3212 : {
3213 587 : pszValue = FetchAttr(nGroupId, nVarId, CF_GRD_MAPPING);
3214 587 : if (pszValue && strchr(pszValue, ':') && strchr(pszValue, ' '))
3215 : {
3216 : // Expanded form of grid_mapping
3217 : // e.g. "crsOSGB: x y crsWGS84: lat lon"
3218 : // Pickup the grid_mapping whose coordinates are dimensions of the
3219 : // variable
3220 6 : const CPLStringList aosTokens(CSLTokenizeString2(pszValue, " ", 0));
3221 3 : if ((aosTokens.size() % 3) == 0)
3222 : {
3223 3 : for (int i = 0; i < aosTokens.size() / 3; i++)
3224 : {
3225 3 : if (CSLFindString(poDS->papszDimName,
3226 9 : aosTokens[3 * i + 1]) >= 0 &&
3227 3 : CSLFindString(poDS->papszDimName,
3228 3 : aosTokens[3 * i + 2]) >= 0)
3229 : {
3230 3 : osTmpGridMapping = aosTokens[3 * i];
3231 6 : if (!osTmpGridMapping.empty() &&
3232 3 : osTmpGridMapping.back() == ':')
3233 : {
3234 3 : osTmpGridMapping.resize(osTmpGridMapping.size() -
3235 : 1);
3236 : }
3237 3 : pszValue = osTmpGridMapping.c_str();
3238 3 : break;
3239 : }
3240 : }
3241 : }
3242 : }
3243 : }
3244 630 : std::string osGridMappingValue = pszValue ? pszValue : "";
3245 :
3246 630 : if (!osGridMappingValue.empty())
3247 : {
3248 : // Read grid_mapping metadata.
3249 263 : int nProjGroupID = -1;
3250 263 : int nProjVarID = -1;
3251 263 : if (NCDFResolveVar(nGroupId, osGridMappingValue.c_str(), &nProjGroupID,
3252 263 : &nProjVarID) == CE_None)
3253 : {
3254 261 : poDS->ReadAttributes(nProjGroupID, nProjVarID);
3255 :
3256 : // Look for GDAL spatial_ref and GeoTransform within grid_mapping.
3257 261 : if (NCDFGetVarFullName(nProjGroupID, nProjVarID,
3258 261 : osGridMappingValue) == CE_None)
3259 : {
3260 261 : CPLDebug("GDAL_netCDF", "got grid_mapping %s",
3261 : osGridMappingValue.c_str());
3262 : pszWKT =
3263 261 : FetchAttr(osGridMappingValue.c_str(), NCDF_SPATIAL_REF);
3264 261 : if (!pszWKT)
3265 : {
3266 : pszWKT =
3267 35 : FetchAttr(osGridMappingValue.c_str(), NCDF_CRS_WKT);
3268 : }
3269 : else
3270 : {
3271 226 : bGotGdalSRS = true;
3272 226 : CPLDebug("GDAL_netCDF", "setting WKT from GDAL");
3273 : }
3274 261 : if (pszWKT)
3275 : {
3276 231 : if (!bGotGdalSRS)
3277 : {
3278 5 : bGotCfWktSRS = true;
3279 5 : CPLDebug("GDAL_netCDF", "setting WKT from CF");
3280 : }
3281 231 : if (returnProjStr != nullptr)
3282 : {
3283 41 : (*returnProjStr) = std::string(pszWKT);
3284 : }
3285 : else
3286 : {
3287 190 : m_bAddedProjectionVarsDefs = true;
3288 190 : m_bAddedProjectionVarsData = true;
3289 380 : OGRSpatialReference oSRSTmp;
3290 190 : oSRSTmp.SetAxisMappingStrategy(
3291 : OAMS_TRADITIONAL_GIS_ORDER);
3292 190 : oSRSTmp.importFromWkt(pszWKT);
3293 190 : SetSpatialRefNoUpdate(&oSRSTmp);
3294 : }
3295 231 : pszGeoTransform = FetchAttr(osGridMappingValue.c_str(),
3296 : NCDF_GEOTRANSFORM);
3297 : }
3298 : }
3299 : }
3300 : else
3301 : {
3302 4 : std::string osVarName = "unknown";
3303 2 : NCDFGetVarFullName(nGroupId, nVarId, osVarName);
3304 :
3305 2 : CPLError(CE_Warning, CPLE_AppDefined,
3306 : "'%s' attribute of variable '%s' references grid mapping "
3307 : "variable '%s', but no such variable exists. The spatial "
3308 : "referencing of this dataset may be incorrect.",
3309 : CF_GRD_MAPPING, osVarName.c_str(),
3310 : osGridMappingValue.c_str());
3311 : }
3312 : }
3313 :
3314 : // Get information about the file.
3315 : //
3316 : // Was this file created by the GDAL netcdf driver?
3317 : // Was this file created by the newer (CF-conformant) driver?
3318 : //
3319 : // 1) If GDAL netcdf metadata is set, and version >= 1.9,
3320 : // it was created with the new driver
3321 : // 2) Else, if spatial_ref and GeoTransform are present in the
3322 : // grid_mapping variable, it was created by the old driver
3323 630 : pszValue = FetchAttr("NC_GLOBAL", "GDAL");
3324 :
3325 630 : if (pszValue && NCDFIsGDALVersionGTE(pszValue, 1900))
3326 : {
3327 293 : bIsGdalFile = true;
3328 293 : bIsGdalCfFile = true;
3329 : }
3330 337 : else if (pszWKT != nullptr && pszGeoTransform != nullptr)
3331 : {
3332 37 : bIsGdalFile = true;
3333 37 : bIsGdalCfFile = false;
3334 : }
3335 :
3336 : // Set default bottom-up default value.
3337 : // Y axis dimension and absence of GT can modify this value.
3338 : // Override with Config option GDAL_NETCDF_BOTTOMUP.
3339 :
3340 : // New driver is bottom-up by default.
3341 630 : if ((bIsGdalFile && !bIsGdalCfFile) || bSwitchedXY)
3342 39 : poDS->bBottomUp = false;
3343 : else
3344 591 : poDS->bBottomUp = true;
3345 :
3346 630 : CPLDebug("GDAL_netCDF",
3347 : "bIsGdalFile=%d bIsGdalCfFile=%d bSwitchedXY=%d bBottomUp=%d",
3348 630 : static_cast<int>(bIsGdalFile), static_cast<int>(bIsGdalCfFile),
3349 630 : static_cast<int>(bSwitchedXY), static_cast<int>(bBottomUp));
3350 :
3351 : // Read projection coordinates.
3352 :
3353 630 : int nGroupDimXID = -1;
3354 630 : int nVarDimXID = -1;
3355 630 : int nGroupDimYID = -1;
3356 630 : int nVarDimYID = -1;
3357 630 : if (sg != nullptr)
3358 : {
3359 43 : nGroupDimXID = sg->get_ncID();
3360 43 : nGroupDimYID = sg->get_ncID();
3361 43 : nVarDimXID = sg->getNodeCoordVars()[0];
3362 43 : nVarDimYID = sg->getNodeCoordVars()[1];
3363 : }
3364 :
3365 630 : if (!bReadSRSOnly)
3366 : {
3367 380 : NCDFResolveVar(nGroupId, poDS->papszDimName[nXDimID], &nGroupDimXID,
3368 : &nVarDimXID);
3369 380 : NCDFResolveVar(nGroupId, poDS->papszDimName[nYDimID], &nGroupDimYID,
3370 : &nVarDimYID);
3371 : // TODO: if above resolving fails we should also search for coordinate
3372 : // variables without same name than dimension using the same resolving
3373 : // logic. This should handle for example NASA Ocean Color L2 products.
3374 :
3375 : const bool bIgnoreXYAxisNameChecks =
3376 760 : CPLTestBool(CSLFetchNameValueDef(
3377 380 : papszOpenOptions, "IGNORE_XY_AXIS_NAME_CHECKS",
3378 : CPLGetConfigOption("GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS",
3379 380 : "NO"))) ||
3380 : // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a res
3381 : // and transform attributes
3382 380 : (FetchAttr(nGroupId, nVarId, "res") != nullptr &&
3383 760 : FetchAttr(nGroupId, nVarId, "transform") != nullptr) ||
3384 379 : FetchAttr(nGroupId, NC_GLOBAL, "GMT_version") != nullptr;
3385 :
3386 : // Check that they are 1D or 2D variables
3387 380 : if (nVarDimXID >= 0)
3388 : {
3389 272 : int ndims = -1;
3390 272 : nc_inq_varndims(nGroupId, nVarDimXID, &ndims);
3391 272 : if (ndims == 0 || ndims > 2)
3392 0 : nVarDimXID = -1;
3393 272 : else if (!bIgnoreXYAxisNameChecks)
3394 : {
3395 270 : if (!NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
3396 179 : !NCDFIsVarProjectionX(nGroupId, nVarDimXID, nullptr) &&
3397 : // In case of inversion of X/Y
3398 481 : !NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr) &&
3399 32 : !NCDFIsVarProjectionY(nGroupId, nVarDimXID, nullptr))
3400 : {
3401 : char szVarNameX[NC_MAX_NAME + 1];
3402 32 : CPL_IGNORE_RET_VAL(
3403 32 : nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
3404 32 : if (!(ndims == 1 &&
3405 31 : (EQUAL(szVarNameX, CF_LONGITUDE_STD_NAME) ||
3406 30 : EQUAL(szVarNameX, CF_LONGITUDE_VAR_NAME))))
3407 : {
3408 31 : CPLDebug(
3409 : "netCDF",
3410 : "Georeferencing ignored due to non-specific "
3411 : "enough X axis name. "
3412 : "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
3413 : "as configuration option to bypass this check");
3414 31 : nVarDimXID = -1;
3415 : }
3416 : }
3417 : }
3418 : }
3419 :
3420 380 : if (nVarDimYID >= 0)
3421 : {
3422 274 : int ndims = -1;
3423 274 : nc_inq_varndims(nGroupId, nVarDimYID, &ndims);
3424 274 : if (ndims == 0 || ndims > 2)
3425 1 : nVarDimYID = -1;
3426 273 : else if (!bIgnoreXYAxisNameChecks)
3427 : {
3428 271 : if (!NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr) &&
3429 180 : !NCDFIsVarProjectionY(nGroupId, nVarDimYID, nullptr) &&
3430 : // In case of inversion of X/Y
3431 484 : !NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
3432 33 : !NCDFIsVarProjectionX(nGroupId, nVarDimYID, nullptr))
3433 : {
3434 : char szVarNameY[NC_MAX_NAME + 1];
3435 33 : CPL_IGNORE_RET_VAL(
3436 33 : nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
3437 33 : if (!(ndims == 1 &&
3438 33 : (EQUAL(szVarNameY, CF_LATITUDE_STD_NAME) ||
3439 32 : EQUAL(szVarNameY, CF_LATITUDE_VAR_NAME))))
3440 : {
3441 32 : CPLDebug(
3442 : "netCDF",
3443 : "Georeferencing ignored due to non-specific "
3444 : "enough Y axis name. "
3445 : "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
3446 : "as configuration option to bypass this check");
3447 32 : nVarDimYID = -1;
3448 : }
3449 : }
3450 : }
3451 : }
3452 :
3453 380 : if ((nVarDimXID >= 0 && xdim == 1) || (nVarDimXID >= 0 && ydim == 1))
3454 : {
3455 0 : CPLError(CE_Warning, CPLE_AppDefined,
3456 : "1-pixel width/height files not supported, "
3457 : "xdim: %ld ydim: %ld",
3458 : static_cast<long>(xdim), static_cast<long>(ydim));
3459 0 : nVarDimXID = -1;
3460 0 : nVarDimYID = -1;
3461 : }
3462 : }
3463 :
3464 630 : const char *pszUnits = nullptr;
3465 630 : if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
3466 : {
3467 284 : const char *pszUnitsX = FetchAttr(nGroupDimXID, nVarDimXID, "units");
3468 284 : const char *pszUnitsY = FetchAttr(nGroupDimYID, nVarDimYID, "units");
3469 : // Normalize degrees_east/degrees_north to degrees
3470 : // Cf https://github.com/OSGeo/gdal/issues/11009
3471 284 : if (pszUnitsX && EQUAL(pszUnitsX, "degrees_east"))
3472 79 : pszUnitsX = "degrees";
3473 284 : if (pszUnitsY && EQUAL(pszUnitsY, "degrees_north"))
3474 79 : pszUnitsY = "degrees";
3475 :
3476 284 : if (pszUnitsX && pszUnitsY)
3477 : {
3478 237 : if (EQUAL(pszUnitsX, pszUnitsY))
3479 234 : pszUnits = pszUnitsX;
3480 3 : else if (!pszWKT && !osGridMappingValue.empty())
3481 : {
3482 0 : CPLError(CE_Failure, CPLE_AppDefined,
3483 : "X axis unit (%s) is different from Y axis "
3484 : "unit (%s). SRS will ignore axis unit and be "
3485 : "likely wrong.",
3486 : pszUnitsX, pszUnitsY);
3487 : }
3488 : }
3489 47 : else if (pszUnitsX && !pszWKT && !osGridMappingValue.empty())
3490 : {
3491 0 : CPLError(CE_Failure, CPLE_AppDefined,
3492 : "X axis unit is defined, but not Y one ."
3493 : "SRS will ignore axis unit and be likely wrong.");
3494 : }
3495 47 : else if (pszUnitsY && !pszWKT && !osGridMappingValue.empty())
3496 : {
3497 0 : CPLError(CE_Failure, CPLE_AppDefined,
3498 : "Y axis unit is defined, but not X one ."
3499 : "SRS will ignore axis unit and be likely wrong.");
3500 : }
3501 : }
3502 :
3503 630 : if (!pszWKT && !osGridMappingValue.empty())
3504 : {
3505 32 : CPLStringList aosGridMappingKeyValues;
3506 32 : const size_t nLenGridMappingValue = osGridMappingValue.size();
3507 817 : for (const char *pszIter : aosMetadata)
3508 : {
3509 1021 : if (STARTS_WITH(pszIter, osGridMappingValue.c_str()) &&
3510 236 : pszIter[nLenGridMappingValue] == '#')
3511 : {
3512 236 : char *pszKey = nullptr;
3513 236 : pszValue = CPLParseNameValue(pszIter + nLenGridMappingValue + 1,
3514 : &pszKey);
3515 236 : if (pszKey && pszValue)
3516 236 : aosGridMappingKeyValues.SetNameValue(pszKey, pszValue);
3517 236 : CPLFree(pszKey);
3518 : }
3519 : }
3520 :
3521 32 : bGotGeogCS = aosGridMappingKeyValues.FetchNameValue(
3522 : CF_PP_SEMI_MAJOR_AXIS) != nullptr;
3523 :
3524 32 : oSRS.importFromCF1(aosGridMappingKeyValues.List(), pszUnits);
3525 32 : bGotCfSRS = oSRS.IsGeographic() || oSRS.IsProjected();
3526 : }
3527 : else
3528 : {
3529 : // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a "crs"
3530 : // attribute hold on the variable of interest that contains a PROJ.4
3531 : // string
3532 598 : pszValue = FetchAttr(nGroupId, nVarId, "crs");
3533 599 : if (pszValue &&
3534 1 : (strstr(pszValue, "+proj=") != nullptr ||
3535 0 : strstr(pszValue, "GEOGCS") != nullptr ||
3536 0 : strstr(pszValue, "PROJCS") != nullptr ||
3537 599 : strstr(pszValue, "EPSG:") != nullptr) &&
3538 1 : oSRS.SetFromUserInput(pszValue) == OGRERR_NONE)
3539 : {
3540 1 : bGotCfSRS = true;
3541 : }
3542 : }
3543 :
3544 : // Set Projection from CF.
3545 630 : double dfLinearUnitsConvFactor = 1.0;
3546 630 : if ((bGotGeogCS || bGotCfSRS))
3547 : {
3548 31 : if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
3549 : {
3550 : // Set SRS Units.
3551 :
3552 : // Check units for x and y.
3553 28 : if (oSRS.IsProjected())
3554 : {
3555 25 : dfLinearUnitsConvFactor = oSRS.GetLinearUnits(nullptr);
3556 :
3557 : // If the user doesn't ask to preserve the axis unit,
3558 : // then normalize to metre
3559 31 : if (dfLinearUnitsConvFactor != 1.0 &&
3560 6 : !CPLFetchBool(GetOpenOptions(), "PRESERVE_AXIS_UNIT_IN_CRS",
3561 : false))
3562 : {
3563 5 : oSRS.SetLinearUnits("metre", 1.0);
3564 5 : oSRS.SetAuthority("PROJCS|UNIT", "EPSG", 9001);
3565 : }
3566 : else
3567 : {
3568 20 : dfLinearUnitsConvFactor = 1.0;
3569 : }
3570 : }
3571 : }
3572 :
3573 : // Set projection.
3574 31 : char *pszTempProjection = nullptr;
3575 31 : oSRS.exportToWkt(&pszTempProjection);
3576 31 : if (pszTempProjection)
3577 : {
3578 31 : CPLDebug("GDAL_netCDF", "setting WKT from CF");
3579 31 : if (returnProjStr != nullptr)
3580 : {
3581 2 : (*returnProjStr) = std::string(pszTempProjection);
3582 : }
3583 : else
3584 : {
3585 29 : m_bAddedProjectionVarsDefs = true;
3586 29 : m_bAddedProjectionVarsData = true;
3587 29 : SetSpatialRefNoUpdate(&oSRS);
3588 : }
3589 : }
3590 31 : CPLFree(pszTempProjection);
3591 : }
3592 :
3593 630 : if (!bReadSRSOnly && (nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 &&
3594 : ydim > 0)
3595 : {
3596 : double *pdfXCoord =
3597 241 : static_cast<double *>(CPLCalloc(xdim, sizeof(double)));
3598 : double *pdfYCoord =
3599 241 : static_cast<double *>(CPLCalloc(ydim, sizeof(double)));
3600 :
3601 241 : size_t start[2] = {0, 0};
3602 241 : size_t edge[2] = {xdim, 0};
3603 241 : int status = nc_get_vara_double(nGroupDimXID, nVarDimXID, start, edge,
3604 : pdfXCoord);
3605 241 : NCDF_ERR(status);
3606 :
3607 241 : edge[0] = ydim;
3608 241 : status = nc_get_vara_double(nGroupDimYID, nVarDimYID, start, edge,
3609 : pdfYCoord);
3610 241 : NCDF_ERR(status);
3611 :
3612 241 : nc_type nc_var_dimx_datatype = NC_NAT;
3613 : status =
3614 241 : nc_inq_vartype(nGroupDimXID, nVarDimXID, &nc_var_dimx_datatype);
3615 241 : NCDF_ERR(status);
3616 :
3617 241 : nc_type nc_var_dimy_datatype = NC_NAT;
3618 : status =
3619 241 : nc_inq_vartype(nGroupDimYID, nVarDimYID, &nc_var_dimy_datatype);
3620 241 : NCDF_ERR(status);
3621 :
3622 241 : if (!poDS->bSwitchedXY)
3623 : {
3624 : // Convert ]180,540] longitude values to ]-180,0].
3625 330 : if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
3626 91 : CPLTestBool(
3627 : CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")))
3628 : {
3629 : // If minimum longitude is > 180, subtract 360 from all.
3630 : // Add a check on the maximum X value too, since
3631 : // NCDFIsVarLongitude() is not very specific by default (see
3632 : // https://github.com/OSGeo/gdal/issues/1440)
3633 98 : if (std::min(pdfXCoord[0], pdfXCoord[xdim - 1]) > 180.0 &&
3634 7 : std::max(pdfXCoord[0], pdfXCoord[xdim - 1]) <= 540)
3635 : {
3636 0 : CPLDebug(
3637 : "GDAL_netCDF",
3638 : "Offsetting longitudes from ]180,540] to ]-180,180]. "
3639 : "Can be disabled with GDAL_NETCDF_CENTERLONG_180=NO");
3640 0 : for (size_t i = 0; i < xdim; i++)
3641 0 : pdfXCoord[i] -= 360;
3642 : }
3643 : }
3644 : }
3645 :
3646 : // Is pixel spacing uniform across the map?
3647 :
3648 : // Check Longitude.
3649 :
3650 241 : bool bLonSpacingOK = false;
3651 241 : if (xdim == 2)
3652 : {
3653 29 : bLonSpacingOK = true;
3654 : }
3655 : else
3656 : {
3657 212 : bool bWestIsLeft = (pdfXCoord[0] < pdfXCoord[xdim - 1]);
3658 :
3659 : // fix longitudes if longitudes should increase from
3660 : // west to east, but west > east
3661 293 : if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
3662 81 : !bWestIsLeft)
3663 : {
3664 2 : size_t ndecreases = 0;
3665 :
3666 : // there is lon wrap if longitudes increase
3667 : // with one single decrease
3668 107 : for (size_t i = 1; i < xdim; i++)
3669 : {
3670 105 : if (pdfXCoord[i] < pdfXCoord[i - 1])
3671 1 : ndecreases++;
3672 : }
3673 :
3674 2 : if (ndecreases == 1)
3675 : {
3676 1 : CPLDebug("GDAL_netCDF", "longitude wrap detected");
3677 4 : for (size_t i = 0; i < xdim; i++)
3678 : {
3679 3 : if (pdfXCoord[i] > pdfXCoord[xdim - 1])
3680 1 : pdfXCoord[i] -= 360;
3681 : }
3682 : }
3683 : }
3684 :
3685 212 : const double dfSpacingBegin = pdfXCoord[1] - pdfXCoord[0];
3686 212 : const double dfSpacingMiddle =
3687 212 : pdfXCoord[xdim / 2 + 1] - pdfXCoord[xdim / 2];
3688 212 : const double dfSpacingLast =
3689 212 : pdfXCoord[xdim - 1] - pdfXCoord[xdim - 2];
3690 :
3691 212 : CPLDebug("GDAL_netCDF",
3692 : "xdim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
3693 : "dfSpacingLast: %f",
3694 : static_cast<long>(xdim), dfSpacingBegin, dfSpacingMiddle,
3695 : dfSpacingLast);
3696 : #ifdef NCDF_DEBUG
3697 : CPLDebug("GDAL_netCDF", "xcoords: %f %f %f %f %f %f", pdfXCoord[0],
3698 : pdfXCoord[1], pdfXCoord[xdim / 2],
3699 : pdfXCoord[(xdim / 2) + 1], pdfXCoord[xdim - 2],
3700 : pdfXCoord[xdim - 1]);
3701 : #endif
3702 :
3703 : // ftp://ftp.cdc.noaa.gov/Datasets/NARR/Dailies/monolevel/vwnd.10m.2015.nc
3704 : // requires a 0.02% tolerance, so let's settle for 0.05%
3705 :
3706 : // For float variables, increase to 0.2% (as seen in
3707 : // https://github.com/OSGeo/gdal/issues/3663)
3708 212 : const double dfEpsRel =
3709 212 : nc_var_dimx_datatype == NC_FLOAT ? 0.002 : 0.0005;
3710 :
3711 : const double dfEps =
3712 : dfEpsRel *
3713 424 : std::max(fabs(dfSpacingBegin),
3714 212 : std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
3715 418 : if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
3716 418 : IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
3717 206 : IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
3718 : {
3719 206 : bLonSpacingOK = true;
3720 : }
3721 6 : else if (CPLTestBool(CPLGetConfigOption(
3722 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
3723 : {
3724 0 : bLonSpacingOK = true;
3725 0 : CPLDebug(
3726 : "GDAL_netCDF",
3727 : "Longitude/X is not equally spaced, but will be considered "
3728 : "as such because of "
3729 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
3730 : }
3731 : }
3732 :
3733 241 : if (bLonSpacingOK == false)
3734 : {
3735 6 : CPLDebug(
3736 : "GDAL_netCDF", "%s",
3737 : "Longitude/X is not equally spaced (with a 0.05% tolerance). "
3738 : "You may set the "
3739 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
3740 : "option to YES to ignore this check");
3741 : }
3742 :
3743 : // Check Latitude.
3744 241 : bool bLatSpacingOK = false;
3745 :
3746 241 : if (ydim == 2)
3747 : {
3748 49 : bLatSpacingOK = true;
3749 : }
3750 : else
3751 : {
3752 192 : const double dfSpacingBegin = pdfYCoord[1] - pdfYCoord[0];
3753 192 : const double dfSpacingMiddle =
3754 192 : pdfYCoord[ydim / 2 + 1] - pdfYCoord[ydim / 2];
3755 :
3756 192 : const double dfSpacingLast =
3757 192 : pdfYCoord[ydim - 1] - pdfYCoord[ydim - 2];
3758 :
3759 192 : CPLDebug("GDAL_netCDF",
3760 : "ydim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
3761 : "dfSpacingLast: %f",
3762 : (long)ydim, dfSpacingBegin, dfSpacingMiddle,
3763 : dfSpacingLast);
3764 : #ifdef NCDF_DEBUG
3765 : CPLDebug("GDAL_netCDF", "ycoords: %f %f %f %f %f %f", pdfYCoord[0],
3766 : pdfYCoord[1], pdfYCoord[ydim / 2],
3767 : pdfYCoord[(ydim / 2) + 1], pdfYCoord[ydim - 2],
3768 : pdfYCoord[ydim - 1]);
3769 : #endif
3770 :
3771 192 : const double dfEpsRel =
3772 192 : nc_var_dimy_datatype == NC_FLOAT ? 0.002 : 0.0005;
3773 :
3774 : const double dfEps =
3775 : dfEpsRel *
3776 384 : std::max(fabs(dfSpacingBegin),
3777 192 : std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
3778 382 : if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
3779 382 : IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
3780 181 : IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
3781 : {
3782 181 : bLatSpacingOK = true;
3783 : }
3784 11 : else if (CPLTestBool(CPLGetConfigOption(
3785 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
3786 : {
3787 0 : bLatSpacingOK = true;
3788 0 : CPLDebug(
3789 : "GDAL_netCDF",
3790 : "Latitude/Y is not equally spaced, but will be considered "
3791 : "as such because of "
3792 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
3793 : }
3794 11 : else if (!oSRS.IsProjected() &&
3795 11 : fabs(dfSpacingBegin - dfSpacingLast) <= 0.1 &&
3796 30 : fabs(dfSpacingBegin - dfSpacingMiddle) <= 0.1 &&
3797 8 : fabs(dfSpacingMiddle - dfSpacingLast) <= 0.1)
3798 : {
3799 8 : bLatSpacingOK = true;
3800 8 : CPLError(CE_Warning, CPLE_AppDefined,
3801 : "Latitude grid not spaced evenly. "
3802 : "Setting projection for grid spacing is "
3803 : "within 0.1 degrees threshold.");
3804 :
3805 8 : CPLDebug("GDAL_netCDF",
3806 : "Latitude grid not spaced evenly, but within 0.1 "
3807 : "degree threshold (probably a Gaussian grid). "
3808 : "Saving original latitude values in Y_VALUES "
3809 : "geolocation metadata");
3810 8 : Set1DGeolocation(nGroupDimYID, nVarDimYID, "Y");
3811 : }
3812 :
3813 192 : if (bLatSpacingOK == false)
3814 : {
3815 3 : CPLDebug(
3816 : "GDAL_netCDF", "%s",
3817 : "Latitude/Y is not equally spaced (with a 0.05% "
3818 : "tolerance). "
3819 : "You may set the "
3820 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
3821 : "option to YES to ignore this check");
3822 : }
3823 : }
3824 :
3825 241 : if (bLonSpacingOK && bLatSpacingOK)
3826 : {
3827 : // We have gridded data so we can set the Georeferencing info.
3828 :
3829 : // Enable GeoTransform.
3830 :
3831 : // In the following "actual_range" and "node_offset"
3832 : // are attributes used by netCDF files created by GMT.
3833 : // If we find them we know how to proceed. Else, use
3834 : // the original algorithm.
3835 234 : bGotCfGT = true;
3836 :
3837 234 : int node_offset = 0;
3838 : const bool bUseActualRange =
3839 234 : NCDFResolveAttInt(nGroupId, NC_GLOBAL, "node_offset",
3840 234 : &node_offset) == CE_None;
3841 :
3842 234 : double adfActualRange[2] = {0.0, 0.0};
3843 234 : double xMinMax[2] = {0.0, 0.0};
3844 234 : double yMinMax[2] = {0.0, 0.0};
3845 :
3846 : const auto RoundMinMaxForFloatVals =
3847 60 : [](double &dfMin, double &dfMax, int nIntervals)
3848 : {
3849 : // Helps for a case where longitudes range from
3850 : // -179.99 to 180.0 with a 0.01 degree spacing.
3851 : // However as this is encoded in a float array,
3852 : // -179.99 is actually read as -179.99000549316406 as
3853 : // a double. Try to detect that and correct the rounding
3854 :
3855 88 : const auto IsAlmostInteger = [](double dfVal)
3856 : {
3857 88 : constexpr double THRESHOLD_INTEGER = 1e-3;
3858 88 : return std::fabs(dfVal - std::round(dfVal)) <=
3859 88 : THRESHOLD_INTEGER;
3860 : };
3861 :
3862 60 : const double dfSpacing = (dfMax - dfMin) / nIntervals;
3863 60 : if (dfSpacing > 0)
3864 : {
3865 48 : const double dfInvSpacing = 1.0 / dfSpacing;
3866 48 : if (IsAlmostInteger(dfInvSpacing))
3867 : {
3868 20 : const double dfRoundedSpacing =
3869 20 : 1.0 / std::round(dfInvSpacing);
3870 20 : const double dfMinDivRoundedSpacing =
3871 20 : dfMin / dfRoundedSpacing;
3872 20 : const double dfMaxDivRoundedSpacing =
3873 20 : dfMax / dfRoundedSpacing;
3874 40 : if (IsAlmostInteger(dfMinDivRoundedSpacing) &&
3875 20 : IsAlmostInteger(dfMaxDivRoundedSpacing))
3876 : {
3877 20 : const double dfRoundedMin =
3878 20 : std::round(dfMinDivRoundedSpacing) *
3879 : dfRoundedSpacing;
3880 20 : const double dfRoundedMax =
3881 20 : std::round(dfMaxDivRoundedSpacing) *
3882 : dfRoundedSpacing;
3883 20 : if (static_cast<float>(dfMin) ==
3884 20 : static_cast<float>(dfRoundedMin) &&
3885 8 : static_cast<float>(dfMax) ==
3886 8 : static_cast<float>(dfRoundedMax))
3887 : {
3888 7 : dfMin = dfRoundedMin;
3889 7 : dfMax = dfRoundedMax;
3890 : }
3891 : }
3892 : }
3893 : }
3894 60 : };
3895 :
3896 237 : if (bUseActualRange &&
3897 3 : !nc_get_att_double(nGroupDimXID, nVarDimXID, "actual_range",
3898 : adfActualRange))
3899 : {
3900 1 : xMinMax[0] = adfActualRange[0];
3901 1 : xMinMax[1] = adfActualRange[1];
3902 :
3903 : // Present xMinMax[] in the same order as padfXCoord
3904 1 : if ((xMinMax[0] - xMinMax[1]) *
3905 1 : (pdfXCoord[0] - pdfXCoord[xdim - 1]) <
3906 : 0)
3907 : {
3908 0 : std::swap(xMinMax[0], xMinMax[1]);
3909 : }
3910 : }
3911 : else
3912 : {
3913 233 : xMinMax[0] = pdfXCoord[0];
3914 233 : xMinMax[1] = pdfXCoord[xdim - 1];
3915 233 : node_offset = 0;
3916 :
3917 233 : if (nc_var_dimx_datatype == NC_FLOAT)
3918 : {
3919 30 : RoundMinMaxForFloatVals(xMinMax[0], xMinMax[1],
3920 30 : poDS->nRasterXSize - 1);
3921 : }
3922 : }
3923 :
3924 237 : if (bUseActualRange &&
3925 3 : !nc_get_att_double(nGroupDimYID, nVarDimYID, "actual_range",
3926 : adfActualRange))
3927 : {
3928 1 : yMinMax[0] = adfActualRange[0];
3929 1 : yMinMax[1] = adfActualRange[1];
3930 :
3931 : // Present yMinMax[] in the same order as pdfYCoord
3932 1 : if ((yMinMax[0] - yMinMax[1]) *
3933 1 : (pdfYCoord[0] - pdfYCoord[ydim - 1]) <
3934 : 0)
3935 : {
3936 0 : std::swap(yMinMax[0], yMinMax[1]);
3937 : }
3938 : }
3939 : else
3940 : {
3941 233 : yMinMax[0] = pdfYCoord[0];
3942 233 : yMinMax[1] = pdfYCoord[ydim - 1];
3943 233 : node_offset = 0;
3944 :
3945 233 : if (nc_var_dimy_datatype == NC_FLOAT)
3946 : {
3947 30 : RoundMinMaxForFloatVals(yMinMax[0], yMinMax[1],
3948 30 : poDS->nRasterYSize - 1);
3949 : }
3950 : }
3951 :
3952 234 : double dfCoordOffset = 0.0;
3953 234 : double dfCoordScale = 1.0;
3954 234 : if (!nc_get_att_double(nGroupId, nVarDimXID, CF_ADD_OFFSET,
3955 238 : &dfCoordOffset) &&
3956 4 : !nc_get_att_double(nGroupId, nVarDimXID, CF_SCALE_FACTOR,
3957 : &dfCoordScale))
3958 : {
3959 4 : xMinMax[0] = dfCoordOffset + xMinMax[0] * dfCoordScale;
3960 4 : xMinMax[1] = dfCoordOffset + xMinMax[1] * dfCoordScale;
3961 : }
3962 :
3963 234 : if (!nc_get_att_double(nGroupId, nVarDimYID, CF_ADD_OFFSET,
3964 238 : &dfCoordOffset) &&
3965 4 : !nc_get_att_double(nGroupId, nVarDimYID, CF_SCALE_FACTOR,
3966 : &dfCoordScale))
3967 : {
3968 4 : yMinMax[0] = dfCoordOffset + yMinMax[0] * dfCoordScale;
3969 4 : yMinMax[1] = dfCoordOffset + yMinMax[1] * dfCoordScale;
3970 : }
3971 :
3972 : // Check for reverse order of y-coordinate.
3973 234 : if (!bSwitchedXY)
3974 : {
3975 232 : poDS->bBottomUp = (yMinMax[0] <= yMinMax[1]);
3976 232 : if (!poDS->bBottomUp)
3977 : {
3978 33 : std::swap(yMinMax[0], yMinMax[1]);
3979 : }
3980 : }
3981 :
3982 : // Geostationary satellites can specify units in (micro)radians
3983 : // So we check if they do, and if so convert to linear units
3984 : // (meters)
3985 234 : const char *pszProjName = oSRS.GetAttrValue("PROJECTION");
3986 234 : if (pszProjName != nullptr)
3987 : {
3988 24 : if (EQUAL(pszProjName, SRS_PT_GEOSTATIONARY_SATELLITE))
3989 : {
3990 : const double satelliteHeight =
3991 3 : oSRS.GetProjParm(SRS_PP_SATELLITE_HEIGHT, 1.0);
3992 6 : std::string osUnits;
3993 3 : if (NCDFGetAttr(nGroupId, nVarDimXID, "units", osUnits) ==
3994 : CE_None)
3995 : {
3996 3 : if (EQUAL(osUnits.c_str(), "microradian"))
3997 : {
3998 1 : xMinMax[0] =
3999 1 : xMinMax[0] * satelliteHeight * 0.000001;
4000 1 : xMinMax[1] =
4001 1 : xMinMax[1] * satelliteHeight * 0.000001;
4002 : }
4003 3 : else if (EQUAL(osUnits.c_str(), "rad") ||
4004 1 : EQUAL(osUnits.c_str(), "radian"))
4005 : {
4006 2 : xMinMax[0] = xMinMax[0] * satelliteHeight;
4007 2 : xMinMax[1] = xMinMax[1] * satelliteHeight;
4008 : }
4009 : }
4010 3 : if (NCDFGetAttr(nGroupId, nVarDimYID, "units", osUnits) ==
4011 : CE_None)
4012 : {
4013 3 : if (EQUAL(osUnits.c_str(), "microradian"))
4014 : {
4015 1 : yMinMax[0] =
4016 1 : yMinMax[0] * satelliteHeight * 0.000001;
4017 1 : yMinMax[1] =
4018 1 : yMinMax[1] * satelliteHeight * 0.000001;
4019 : }
4020 3 : else if (EQUAL(osUnits.c_str(), "rad") ||
4021 1 : EQUAL(osUnits.c_str(), "radian"))
4022 : {
4023 2 : yMinMax[0] = yMinMax[0] * satelliteHeight;
4024 2 : yMinMax[1] = yMinMax[1] * satelliteHeight;
4025 : }
4026 : }
4027 : }
4028 : }
4029 :
4030 234 : tmpGT[0] = xMinMax[0];
4031 468 : tmpGT[1] = (xMinMax[1] - xMinMax[0]) /
4032 234 : (poDS->nRasterXSize + (node_offset - 1));
4033 234 : tmpGT[2] = 0;
4034 234 : if (bSwitchedXY)
4035 : {
4036 2 : tmpGT[3] = yMinMax[0];
4037 2 : tmpGT[4] = 0;
4038 2 : tmpGT[5] = (yMinMax[1] - yMinMax[0]) /
4039 2 : (poDS->nRasterYSize + (node_offset - 1));
4040 : }
4041 : else
4042 : {
4043 232 : tmpGT[3] = yMinMax[1];
4044 232 : tmpGT[4] = 0;
4045 232 : tmpGT[5] = (yMinMax[0] - yMinMax[1]) /
4046 232 : (poDS->nRasterYSize + (node_offset - 1));
4047 : }
4048 :
4049 : // Compute the center of the pixel.
4050 234 : if (!node_offset)
4051 : {
4052 : // Otherwise its already the pixel center.
4053 234 : tmpGT[0] -= (tmpGT[1] / 2);
4054 234 : tmpGT[3] -= (tmpGT[5] / 2);
4055 : }
4056 : }
4057 :
4058 : const auto AreSRSEqualThroughProj4String =
4059 2 : [](const OGRSpatialReference &oSRS1,
4060 : const OGRSpatialReference &oSRS2)
4061 : {
4062 2 : char *pszProj4Str1 = nullptr;
4063 2 : oSRS1.exportToProj4(&pszProj4Str1);
4064 :
4065 2 : char *pszProj4Str2 = nullptr;
4066 2 : oSRS2.exportToProj4(&pszProj4Str2);
4067 :
4068 : {
4069 2 : char *pszTmp = strstr(pszProj4Str1, "+datum=");
4070 2 : if (pszTmp)
4071 0 : memcpy(pszTmp, "+ellps=", strlen("+ellps="));
4072 : }
4073 :
4074 : {
4075 2 : char *pszTmp = strstr(pszProj4Str2, "+datum=");
4076 2 : if (pszTmp)
4077 2 : memcpy(pszTmp, "+ellps=", strlen("+ellps="));
4078 : }
4079 :
4080 2 : bool bRet = false;
4081 2 : if (pszProj4Str1 && pszProj4Str2 &&
4082 2 : EQUAL(pszProj4Str1, pszProj4Str2))
4083 : {
4084 1 : bRet = true;
4085 : }
4086 :
4087 2 : CPLFree(pszProj4Str1);
4088 2 : CPLFree(pszProj4Str2);
4089 2 : return bRet;
4090 : };
4091 :
4092 241 : if (dfLinearUnitsConvFactor != 1.0)
4093 : {
4094 35 : for (int i = 0; i < 6; ++i)
4095 30 : tmpGT[i] *= dfLinearUnitsConvFactor;
4096 :
4097 5 : if (paosRemovedMDItems)
4098 : {
4099 : char szVarNameX[NC_MAX_NAME + 1];
4100 5 : CPL_IGNORE_RET_VAL(
4101 5 : nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
4102 :
4103 : char szVarNameY[NC_MAX_NAME + 1];
4104 5 : CPL_IGNORE_RET_VAL(
4105 5 : nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
4106 :
4107 5 : paosRemovedMDItems->push_back(
4108 : CPLSPrintf("%s#units", szVarNameX));
4109 5 : paosRemovedMDItems->push_back(
4110 : CPLSPrintf("%s#units", szVarNameY));
4111 : }
4112 : }
4113 :
4114 : // If there is a global "geospatial_bounds_crs" attribute, check that it
4115 : // is consistent with the SRS, and if so, use it as the SRS
4116 : const char *pszGBCRS =
4117 241 : FetchAttr(nGroupId, NC_GLOBAL, "geospatial_bounds_crs");
4118 241 : if (pszGBCRS && STARTS_WITH(pszGBCRS, "EPSG:"))
4119 : {
4120 4 : OGRSpatialReference oSRSFromGBCRS;
4121 2 : oSRSFromGBCRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
4122 2 : if (oSRSFromGBCRS.SetFromUserInput(
4123 : pszGBCRS,
4124 : OGRSpatialReference::
4125 4 : SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE &&
4126 2 : AreSRSEqualThroughProj4String(oSRS, oSRSFromGBCRS))
4127 : {
4128 1 : oSRS = std::move(oSRSFromGBCRS);
4129 1 : SetSpatialRefNoUpdate(&oSRS);
4130 : }
4131 : }
4132 :
4133 241 : CPLFree(pdfXCoord);
4134 241 : CPLFree(pdfYCoord);
4135 : } // end if(has dims)
4136 :
4137 : // Process custom GeoTransform GDAL value.
4138 630 : if (!osGridMappingValue.empty())
4139 : {
4140 263 : if (pszGeoTransform != nullptr)
4141 : {
4142 : const CPLStringList aosGeoTransform(
4143 302 : CSLTokenizeString2(pszGeoTransform, " ", CSLT_HONOURSTRINGS));
4144 151 : if (aosGeoTransform.size() == 6)
4145 : {
4146 151 : bool bUseGeoTransformFromAttribute = true;
4147 :
4148 151 : GDALGeoTransform gtFromAttribute;
4149 1057 : for (int i = 0; i < 6; i++)
4150 : {
4151 906 : gtFromAttribute[i] = CPLAtof(aosGeoTransform[i]);
4152 : }
4153 :
4154 : // When GDAL writes a raster that is north-up oriented, it
4155 : // writes the "GeoTransform" attribute unmodified, that is with
4156 : // gt.yscale < 0, but the first line is actually the southern-most
4157 : // one, consistently with the values of the "y" coordinate
4158 : // variable. This is wrong... but we have always done that, so
4159 : // this is hard to fix now.
4160 : // However there are datasets like
4161 : // https://public.hub.geosphere.at/datahub/resources/spartacus-v2-1d-1km/filelisting/TN/SPARTACUS2-DAILY_TN_2026.nc
4162 : // that correctly use a positive gt.yscale value. So make sure to not emit
4163 : // a warning when comparing against the geotransform derived from
4164 : // the x/y coordinates.
4165 151 : GDALGeoTransform gtFromAttributeNorthUp = gtFromAttribute;
4166 156 : if (gtFromAttributeNorthUp.yscale > 0 &&
4167 5 : gtFromAttributeNorthUp.IsAxisAligned())
4168 : {
4169 1 : gtFromAttributeNorthUp.yorig +=
4170 1 : poDS->nRasterYSize * gtFromAttributeNorthUp.yscale;
4171 1 : gtFromAttributeNorthUp.yscale =
4172 1 : -gtFromAttributeNorthUp.yscale;
4173 : }
4174 :
4175 151 : if (bGotCfGT)
4176 : {
4177 109 : constexpr double GT_RELERROR_WARN_THRESHOLD = 1e-6;
4178 109 : double dfMaxAbsoluteError = 0.0;
4179 763 : for (int i = 0; i < 6; i++)
4180 : {
4181 : double dfAbsoluteError =
4182 654 : std::abs(tmpGT[i] - gtFromAttributeNorthUp[i]);
4183 654 : if (dfAbsoluteError >
4184 654 : std::abs(gtFromAttributeNorthUp[i] *
4185 : GT_RELERROR_WARN_THRESHOLD))
4186 : {
4187 3 : dfMaxAbsoluteError =
4188 3 : std::max(dfMaxAbsoluteError, dfAbsoluteError);
4189 : }
4190 : }
4191 :
4192 109 : if (dfMaxAbsoluteError > 0)
4193 : {
4194 3 : bUseGeoTransformFromAttribute = false;
4195 3 : CPLError(CE_Warning, CPLE_AppDefined,
4196 : "GeoTransform read from attribute of %s "
4197 : "variable differs from value calculated from "
4198 : "dimension variables (max diff = %g). Using "
4199 : "value calculated from dimension variables.",
4200 : osGridMappingValue.c_str(),
4201 : dfMaxAbsoluteError);
4202 : }
4203 : }
4204 :
4205 151 : if (bUseGeoTransformFromAttribute)
4206 : {
4207 148 : if (bGotCfGT)
4208 : {
4209 106 : tmpGT = gtFromAttributeNorthUp;
4210 106 : if (gtFromAttributeNorthUp.IsAxisAligned())
4211 : {
4212 : // Axis direction depends on whether it is GDAL-style CF file
4213 106 : poDS->bBottomUp = bIsGdalCfFile;
4214 : }
4215 : }
4216 : else
4217 : {
4218 42 : tmpGT = gtFromAttribute;
4219 : }
4220 148 : bGotGdalGT = true;
4221 : }
4222 : }
4223 : }
4224 : else
4225 : {
4226 : // Look for corner array values.
4227 : // CPLDebug("GDAL_netCDF",
4228 : // "looking for geotransform corners");
4229 112 : bool bGotNN = false;
4230 112 : double dfNN = FetchCopyParam(osGridMappingValue.c_str(),
4231 : "Northernmost_Northing", 0, &bGotNN);
4232 :
4233 112 : bool bGotSN = false;
4234 112 : double dfSN = FetchCopyParam(osGridMappingValue.c_str(),
4235 : "Southernmost_Northing", 0, &bGotSN);
4236 :
4237 112 : bool bGotEE = false;
4238 112 : double dfEE = FetchCopyParam(osGridMappingValue.c_str(),
4239 : "Easternmost_Easting", 0, &bGotEE);
4240 :
4241 112 : bool bGotWE = false;
4242 112 : double dfWE = FetchCopyParam(osGridMappingValue.c_str(),
4243 : "Westernmost_Easting", 0, &bGotWE);
4244 :
4245 : // Only set the GeoTransform if we got all the values.
4246 112 : if (bGotNN && bGotSN && bGotEE && bGotWE)
4247 : {
4248 0 : bGotGdalGT = true;
4249 :
4250 0 : tmpGT[0] = dfWE;
4251 0 : tmpGT[1] = (dfEE - dfWE) / (poDS->GetRasterXSize() - 1);
4252 0 : tmpGT[2] = 0.0;
4253 0 : tmpGT[3] = dfNN;
4254 0 : tmpGT[4] = 0.0;
4255 0 : tmpGT[5] = (dfSN - dfNN) / (poDS->GetRasterYSize() - 1);
4256 : // Compute the center of the pixel.
4257 0 : tmpGT[0] = dfWE - (tmpGT[1] / 2);
4258 0 : tmpGT[3] = dfNN - (tmpGT[5] / 2);
4259 : }
4260 : } // (pszGeoTransform != NULL)
4261 :
4262 263 : if (bGotGdalSRS && !bGotGdalGT)
4263 78 : CPLDebug("GDAL_netCDF", "Got SRS but no geotransform from GDAL!");
4264 : }
4265 :
4266 630 : if (bGotCfGT || bGotGdalGT)
4267 : {
4268 276 : CPLDebug("GDAL_netCDF", "set bBottomUp = %d from Y axis",
4269 276 : static_cast<int>(poDS->bBottomUp));
4270 : }
4271 :
4272 630 : if (!pszWKT && !bGotCfSRS)
4273 : {
4274 : // Some netCDF files have a srid attribute (#6613) like
4275 : // urn:ogc:def:crs:EPSG::6931
4276 368 : const char *pszSRID = FetchAttr(osGridMappingValue.c_str(), "srid");
4277 368 : if (pszSRID != nullptr)
4278 : {
4279 0 : oSRS.Clear();
4280 0 : if (oSRS.SetFromUserInput(
4281 : pszSRID,
4282 : OGRSpatialReference::
4283 0 : SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE)
4284 : {
4285 0 : CPLDebug("GDAL_netCDF", "Got SRS from %s", pszSRID);
4286 0 : std::string osWKTExport = oSRS.exportToWkt();
4287 0 : if (!osWKTExport.empty())
4288 : {
4289 0 : (*returnProjStr) = std::move(osWKTExport);
4290 : }
4291 : else
4292 : {
4293 0 : m_bAddedProjectionVarsDefs = true;
4294 0 : m_bAddedProjectionVarsData = true;
4295 0 : SetSpatialRefNoUpdate(&oSRS);
4296 : }
4297 : }
4298 : }
4299 : }
4300 :
4301 630 : if (bReadSRSOnly)
4302 250 : return;
4303 :
4304 : // Determines the SRS to be used by the geolocation array, if any
4305 760 : std::string osGeolocWKT = SRS_WKT_WGS84_LAT_LONG;
4306 380 : if (!m_oSRS.IsEmpty())
4307 : {
4308 312 : OGRSpatialReference oGeogCRS;
4309 156 : oGeogCRS.CopyGeogCSFrom(&m_oSRS, true);
4310 156 : const char *const apszOptions[] = {"FORMAT=WKT2_2019", nullptr};
4311 :
4312 312 : std::string osWKTTmp = oGeogCRS.exportToWkt(apszOptions);
4313 156 : if (!osWKTTmp.empty())
4314 : {
4315 156 : osGeolocWKT = std::move(osWKTTmp);
4316 : }
4317 : }
4318 :
4319 : // Process geolocation arrays from CF "coordinates" attribute.
4320 760 : std::string osGeolocXName, osGeolocYName;
4321 380 : if (ProcessCFGeolocation(nGroupId, nVarId, osGeolocWKT, osGeolocXName,
4322 380 : osGeolocYName))
4323 : {
4324 61 : bool bCanCancelGT = true;
4325 61 : if ((nVarDimXID != -1) && (nVarDimYID != -1))
4326 : {
4327 : char szVarNameX[NC_MAX_NAME + 1];
4328 44 : CPL_IGNORE_RET_VAL(
4329 44 : nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
4330 : char szVarNameY[NC_MAX_NAME + 1];
4331 44 : CPL_IGNORE_RET_VAL(
4332 44 : nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
4333 44 : bCanCancelGT =
4334 44 : !(osGeolocXName == szVarNameX && osGeolocYName == szVarNameY);
4335 : }
4336 100 : if (bCanCancelGT && !m_oSRS.IsGeographic() && !m_oSRS.IsProjected() &&
4337 39 : !bSwitchedXY)
4338 : {
4339 37 : bGotCfGT = false;
4340 : }
4341 : }
4342 125 : else if (!bGotCfGT && !bReadSRSOnly && (nVarDimXID != -1) &&
4343 447 : (nVarDimYID != -1) && xdim > 0 && ydim > 0 &&
4344 3 : ((!bSwitchedXY &&
4345 3 : NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
4346 1 : NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr)) ||
4347 2 : (bSwitchedXY &&
4348 0 : NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
4349 0 : NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr))))
4350 : {
4351 : // Case of autotest/gdrivers/data/netcdf/GLMELT_4X5.OCN.nc
4352 : // which is indexed by lat, lon variables, but lat has irregular
4353 : // spacing.
4354 1 : const char *pszGeolocXFullName = poDS->papszDimName[poDS->nXDimID];
4355 1 : const char *pszGeolocYFullName = poDS->papszDimName[poDS->nYDimID];
4356 1 : if (bSwitchedXY)
4357 : {
4358 0 : std::swap(pszGeolocXFullName, pszGeolocYFullName);
4359 0 : GDALPamDataset::SetMetadataItem("SWAP_XY", "YES",
4360 : GDAL_MDD_GEOLOCATION);
4361 : }
4362 :
4363 1 : CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
4364 : pszGeolocXFullName, pszGeolocYFullName);
4365 :
4366 1 : GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
4367 : GDAL_MDD_GEOLOCATION);
4368 :
4369 2 : CPLString osTMP;
4370 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4371 1 : pszGeolocXFullName);
4372 :
4373 1 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP,
4374 : GDAL_MDD_GEOLOCATION);
4375 1 : GDALPamDataset::SetMetadataItem("X_BAND", "1", GDAL_MDD_GEOLOCATION);
4376 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4377 1 : pszGeolocYFullName);
4378 :
4379 1 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP,
4380 : GDAL_MDD_GEOLOCATION);
4381 1 : GDALPamDataset::SetMetadataItem("Y_BAND", "1", GDAL_MDD_GEOLOCATION);
4382 :
4383 1 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0",
4384 : GDAL_MDD_GEOLOCATION);
4385 1 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1",
4386 : GDAL_MDD_GEOLOCATION);
4387 :
4388 1 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0",
4389 : GDAL_MDD_GEOLOCATION);
4390 1 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1", GDAL_MDD_GEOLOCATION);
4391 :
4392 1 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
4393 : "PIXEL_CENTER", GDAL_MDD_GEOLOCATION);
4394 : }
4395 :
4396 : // Set GeoTransform if we got a complete one - after projection has been set
4397 380 : if (bGotCfGT || bGotGdalGT)
4398 : {
4399 224 : m_bAddedProjectionVarsDefs = true;
4400 224 : m_bAddedProjectionVarsData = true;
4401 224 : SetGeoTransformNoUpdate(tmpGT);
4402 : }
4403 :
4404 : // Debugging reports.
4405 380 : CPLDebug("GDAL_netCDF",
4406 : "bGotGeogCS=%d bGotCfSRS=%d bGotCfGT=%d bGotCfWktSRS=%d "
4407 : "bGotGdalSRS=%d bGotGdalGT=%d",
4408 : static_cast<int>(bGotGeogCS), static_cast<int>(bGotCfSRS),
4409 : static_cast<int>(bGotCfGT), static_cast<int>(bGotCfWktSRS),
4410 : static_cast<int>(bGotGdalSRS), static_cast<int>(bGotGdalGT));
4411 :
4412 380 : if (!bGotCfGT && !bGotGdalGT)
4413 156 : CPLDebug("GDAL_netCDF", "did not get geotransform from CF nor GDAL!");
4414 :
4415 380 : if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfGT && !bGotCfWktSRS)
4416 156 : CPLDebug("GDAL_netCDF", "did not get projection from CF nor GDAL!");
4417 :
4418 : // wish of 6195
4419 : // we don't have CS/SRS, but we do have GT, and we live in -180,360 -90,90
4420 380 : if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfWktSRS)
4421 : {
4422 224 : if (bGotCfGT || bGotGdalGT)
4423 : {
4424 136 : bool bAssumedLongLat = CPLTestBool(CSLFetchNameValueDef(
4425 68 : papszOpenOptions, "ASSUME_LONGLAT",
4426 : CPLGetConfigOption("GDAL_NETCDF_ASSUME_LONGLAT", "NO")));
4427 :
4428 2 : if (bAssumedLongLat && tmpGT[0] >= -180 && tmpGT[0] < 360 &&
4429 2 : (tmpGT[0] + tmpGT[1] * poDS->GetRasterXSize()) <= 360 &&
4430 72 : tmpGT[3] <= 90 && tmpGT[3] > -90 &&
4431 2 : (tmpGT[3] + tmpGT[5] * poDS->GetRasterYSize()) >= -90)
4432 : {
4433 :
4434 2 : poDS->bIsGeographic = true;
4435 : // seems odd to use 4326 so OGC:CRS84
4436 2 : oSRS.SetFromUserInput("OGC:CRS84");
4437 2 : if (returnProjStr != nullptr)
4438 : {
4439 0 : *returnProjStr = oSRS.exportToWkt();
4440 : }
4441 : else
4442 : {
4443 2 : m_bAddedProjectionVarsDefs = true;
4444 2 : m_bAddedProjectionVarsData = true;
4445 2 : SetSpatialRefNoUpdate(&oSRS);
4446 : }
4447 :
4448 2 : CPLDebug("netCDF",
4449 : "Assumed Longitude Latitude CRS 'OGC:CRS84' because "
4450 : "none otherwise available and geotransform within "
4451 : "suitable bounds. "
4452 : "Set GDAL_NETCDF_ASSUME_LONGLAT=NO as configuration "
4453 : "option or "
4454 : " ASSUME_LONGLAT=NO as open option to bypass this "
4455 : "assumption.");
4456 : }
4457 : }
4458 : }
4459 :
4460 : // Search for Well-known GeogCS if got only CF WKT
4461 : // Disabled for now, as a named datum also include control points
4462 : // (see mailing list and bug#4281
4463 : // For example, WGS84 vs. GDA94 (EPSG:3577) - AEA in netcdf_cf.py
4464 :
4465 : // Disabled for now, but could be set in a config option.
4466 : #if 0
4467 : bool bLookForWellKnownGCS = false; // This could be a Config Option.
4468 :
4469 : if( bLookForWellKnownGCS && bGotCfSRS && !bGotGdalSRS )
4470 : {
4471 : // ET - Could use a more exhaustive method by scanning all EPSG codes in
4472 : // data/gcs.csv as proposed by Even in the gdal-dev mailing list "help
4473 : // for comparing two WKT".
4474 : // This code could be contributed to a new function.
4475 : // OGRSpatialReference * OGRSpatialReference::FindMatchingGeogCS(
4476 : // const OGRSpatialReference *poOther) */
4477 : CPLDebug("GDAL_netCDF", "Searching for Well-known GeogCS");
4478 : const char *pszWKGCSList[] = { "WGS84", "WGS72", "NAD27", "NAD83" };
4479 : char *pszWKGCS = NULL;
4480 : oSRS.exportToPrettyWkt(&pszWKGCS);
4481 : for( size_t i = 0; i < sizeof(pszWKGCSList) / 8; i++ )
4482 : {
4483 : pszWKGCS = CPLStrdup(pszWKGCSList[i]);
4484 : OGRSpatialReference oSRSTmp;
4485 : oSRSTmp.SetWellKnownGeogCS(pszWKGCSList[i]);
4486 : // Set datum to unknown, bug #4281.
4487 : if( oSRSTmp.GetAttrNode("DATUM" ) )
4488 : oSRSTmp.GetAttrNode("DATUM")->GetChild(0)->SetValue("unknown");
4489 : // Could use OGRSpatialReference::StripCTParms(), but let's keep
4490 : // TOWGS84.
4491 : oSRSTmp.GetRoot()->StripNodes("AXIS");
4492 : oSRSTmp.GetRoot()->StripNodes("AUTHORITY");
4493 : oSRSTmp.GetRoot()->StripNodes("EXTENSION");
4494 :
4495 : oSRSTmp.exportToPrettyWkt(&pszWKGCS);
4496 : if( oSRS.IsSameGeogCS(&oSRSTmp) )
4497 : {
4498 : oSRS.SetWellKnownGeogCS(pszWKGCSList[i]);
4499 : oSRS.exportToWkt(&(pszTempProjection));
4500 : SetProjection(pszTempProjection);
4501 : CPLFree(pszTempProjection);
4502 : }
4503 : }
4504 : }
4505 : #endif
4506 : }
4507 :
4508 207 : void netCDFDataset::SetProjectionFromVar(int nGroupId, int nVarId,
4509 : bool bReadSRSOnly)
4510 : {
4511 207 : SetProjectionFromVar(nGroupId, nVarId, bReadSRSOnly, nullptr, nullptr,
4512 : nullptr, nullptr);
4513 207 : }
4514 :
4515 307 : bool netCDFDataset::ProcessNASAL2OceanGeoLocation(int nGroupId, int nVarId)
4516 : {
4517 : // Cf https://oceancolor.gsfc.nasa.gov/docs/format/l2nc/
4518 : // and https://github.com/OSGeo/gdal/issues/7605
4519 :
4520 : // Check for a structure like:
4521 : /* netcdf SNPP_VIIRS.20230406T024200.L2.OC.NRT {
4522 : dimensions:
4523 : number_of_lines = 3248 ;
4524 : pixels_per_line = 3200 ;
4525 : [...]
4526 : pixel_control_points = 3200 ;
4527 : [...]
4528 : group: geophysical_data {
4529 : variables:
4530 : short aot_862(number_of_lines, pixels_per_line) ; <-- nVarId
4531 : [...]
4532 : }
4533 : group: navigation_data {
4534 : variables:
4535 : float longitude(number_of_lines, pixel_control_points) ;
4536 : [...]
4537 : float latitude(number_of_lines, pixel_control_points) ;
4538 : [...]
4539 : }
4540 : }
4541 : */
4542 : // Note that the longitude and latitude arrays are not indexed by the
4543 : // same dimensions. Handle only the case where
4544 : // pixel_control_points == pixels_per_line
4545 : // If there was a subsampling of the geolocation arrays, we'd need to
4546 : // add more logic.
4547 :
4548 614 : std::string osGroupName;
4549 307 : osGroupName.resize(NC_MAX_NAME);
4550 307 : NCDF_ERR(nc_inq_grpname(nGroupId, &osGroupName[0]));
4551 307 : osGroupName.resize(strlen(osGroupName.data()));
4552 307 : if (osGroupName != "geophysical_data")
4553 306 : return false;
4554 :
4555 1 : int nVarDims = 0;
4556 1 : NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
4557 1 : if (nVarDims != 2)
4558 0 : return false;
4559 :
4560 1 : int nNavigationDataGrpId = 0;
4561 1 : if (nc_inq_grp_ncid(cdfid, "navigation_data", &nNavigationDataGrpId) !=
4562 : NC_NOERR)
4563 0 : return false;
4564 :
4565 : std::array<int, 2> anVarDimIds;
4566 1 : NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
4567 :
4568 1 : int nLongitudeId = 0;
4569 1 : int nLatitudeId = 0;
4570 1 : if (nc_inq_varid(nNavigationDataGrpId, "longitude", &nLongitudeId) !=
4571 2 : NC_NOERR ||
4572 1 : nc_inq_varid(nNavigationDataGrpId, "latitude", &nLatitudeId) !=
4573 : NC_NOERR)
4574 : {
4575 0 : return false;
4576 : }
4577 :
4578 1 : int nDimsLongitude = 0;
4579 1 : NCDF_ERR(
4580 : nc_inq_varndims(nNavigationDataGrpId, nLongitudeId, &nDimsLongitude));
4581 1 : int nDimsLatitude = 0;
4582 1 : NCDF_ERR(
4583 : nc_inq_varndims(nNavigationDataGrpId, nLatitudeId, &nDimsLatitude));
4584 1 : if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
4585 : {
4586 0 : return false;
4587 : }
4588 :
4589 : std::array<int, 2> anDimLongitudeIds;
4590 1 : NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLongitudeId,
4591 : anDimLongitudeIds.data()));
4592 : std::array<int, 2> anDimLatitudeIds;
4593 1 : NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLatitudeId,
4594 : anDimLatitudeIds.data()));
4595 1 : if (anDimLongitudeIds != anDimLatitudeIds)
4596 : {
4597 0 : return false;
4598 : }
4599 :
4600 : std::array<size_t, 2> anSizeVarDimIds;
4601 : std::array<size_t, 2> anSizeLongLatIds;
4602 2 : if (!(nc_inq_dimlen(cdfid, anVarDimIds[0], &anSizeVarDimIds[0]) ==
4603 1 : NC_NOERR &&
4604 1 : nc_inq_dimlen(cdfid, anVarDimIds[1], &anSizeVarDimIds[1]) ==
4605 1 : NC_NOERR &&
4606 1 : nc_inq_dimlen(cdfid, anDimLongitudeIds[0], &anSizeLongLatIds[0]) ==
4607 1 : NC_NOERR &&
4608 1 : nc_inq_dimlen(cdfid, anDimLongitudeIds[1], &anSizeLongLatIds[1]) ==
4609 : NC_NOERR &&
4610 1 : anSizeVarDimIds == anSizeLongLatIds))
4611 : {
4612 0 : return false;
4613 : }
4614 :
4615 1 : const char *pszGeolocXFullName = "/navigation_data/longitude";
4616 1 : const char *pszGeolocYFullName = "/navigation_data/latitude";
4617 :
4618 1 : if (bSwitchedXY)
4619 : {
4620 0 : std::swap(pszGeolocXFullName, pszGeolocYFullName);
4621 0 : GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", GDAL_MDD_GEOLOCATION);
4622 : }
4623 :
4624 1 : CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
4625 : pszGeolocXFullName, pszGeolocYFullName);
4626 :
4627 1 : GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
4628 : GDAL_MDD_GEOLOCATION);
4629 :
4630 1 : CPLString osTMP;
4631 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
4632 :
4633 1 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, GDAL_MDD_GEOLOCATION);
4634 1 : GDALPamDataset::SetMetadataItem("X_BAND", "1", GDAL_MDD_GEOLOCATION);
4635 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
4636 :
4637 1 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, GDAL_MDD_GEOLOCATION);
4638 1 : GDALPamDataset::SetMetadataItem("Y_BAND", "1", GDAL_MDD_GEOLOCATION);
4639 :
4640 1 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", GDAL_MDD_GEOLOCATION);
4641 1 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", GDAL_MDD_GEOLOCATION);
4642 :
4643 1 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", GDAL_MDD_GEOLOCATION);
4644 1 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1", GDAL_MDD_GEOLOCATION);
4645 :
4646 1 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
4647 : GDAL_MDD_GEOLOCATION);
4648 1 : return true;
4649 : }
4650 :
4651 306 : bool netCDFDataset::ProcessNASAEMITGeoLocation(int nGroupId, int nVarId)
4652 : {
4653 : // Cf https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/
4654 :
4655 : // Check for a structure like:
4656 : /* netcdf EMIT_L2A_RFL_001_20220903T163129_2224611_012 {
4657 : dimensions:
4658 : downtrack = 1280 ;
4659 : crosstrack = 1242 ;
4660 : bands = 285 ;
4661 : [...]
4662 :
4663 : variables:
4664 : float reflectance(downtrack, crosstrack, bands) ;
4665 :
4666 : group: location {
4667 : variables:
4668 : double lon(downtrack, crosstrack) ;
4669 : lon:_FillValue = -9999. ;
4670 : lon:long_name = "Longitude (WGS-84)" ;
4671 : lon:units = "degrees east" ;
4672 : double lat(downtrack, crosstrack) ;
4673 : lat:_FillValue = -9999. ;
4674 : lat:long_name = "Latitude (WGS-84)" ;
4675 : lat:units = "degrees north" ;
4676 : } // group location
4677 :
4678 : }
4679 : or
4680 : netcdf EMIT_L2B_MIN_001_20231024T055538_2329704_040 {
4681 : dimensions:
4682 : downtrack = 1664 ;
4683 : crosstrack = 1242 ;
4684 : [...]
4685 : variables:
4686 : float group_1_band_depth(downtrack, crosstrack) ;
4687 : group_1_band_depth:_FillValue = -9999.f ;
4688 : group_1_band_depth:long_name = "Group 1 Band Depth" ;
4689 : group_1_band_depth:units = "unitless" ;
4690 : [...]
4691 : group: location {
4692 : variables:
4693 : double lon(downtrack, crosstrack) ;
4694 : lon:_FillValue = -9999. ;
4695 : lon:long_name = "Longitude (WGS-84)" ;
4696 : lon:units = "degrees east" ;
4697 : double lat(downtrack, crosstrack) ;
4698 : lat:_FillValue = -9999. ;
4699 : lat:long_name = "Latitude (WGS-84)" ;
4700 : lat:units = "degrees north" ;
4701 : }
4702 : */
4703 :
4704 306 : int nVarDims = 0;
4705 306 : NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
4706 306 : if (nVarDims != 2 && nVarDims != 3)
4707 14 : return false;
4708 :
4709 292 : int nLocationGrpId = 0;
4710 292 : if (nc_inq_grp_ncid(cdfid, "location", &nLocationGrpId) != NC_NOERR)
4711 61 : return false;
4712 :
4713 : std::array<int, 3> anVarDimIds;
4714 231 : NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
4715 231 : if (nYDimID != anVarDimIds[0] || nXDimID != anVarDimIds[1])
4716 21 : return false;
4717 :
4718 210 : int nLongitudeId = 0;
4719 210 : int nLatitudeId = 0;
4720 248 : if (nc_inq_varid(nLocationGrpId, "lon", &nLongitudeId) != NC_NOERR ||
4721 38 : nc_inq_varid(nLocationGrpId, "lat", &nLatitudeId) != NC_NOERR)
4722 : {
4723 172 : return false;
4724 : }
4725 :
4726 38 : int nDimsLongitude = 0;
4727 38 : NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLongitudeId, &nDimsLongitude));
4728 38 : int nDimsLatitude = 0;
4729 38 : NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLatitudeId, &nDimsLatitude));
4730 38 : if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
4731 : {
4732 34 : return false;
4733 : }
4734 :
4735 : std::array<int, 2> anDimLongitudeIds;
4736 4 : NCDF_ERR(nc_inq_vardimid(nLocationGrpId, nLongitudeId,
4737 : anDimLongitudeIds.data()));
4738 : std::array<int, 2> anDimLatitudeIds;
4739 4 : NCDF_ERR(
4740 : nc_inq_vardimid(nLocationGrpId, nLatitudeId, anDimLatitudeIds.data()));
4741 4 : if (anDimLongitudeIds != anDimLatitudeIds)
4742 : {
4743 0 : return false;
4744 : }
4745 :
4746 8 : if (anDimLongitudeIds[0] != anVarDimIds[0] ||
4747 4 : anDimLongitudeIds[1] != anVarDimIds[1])
4748 : {
4749 0 : return false;
4750 : }
4751 :
4752 4 : const char *pszGeolocXFullName = "/location/lon";
4753 4 : const char *pszGeolocYFullName = "/location/lat";
4754 :
4755 4 : CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
4756 : pszGeolocXFullName, pszGeolocYFullName);
4757 :
4758 4 : GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
4759 : GDAL_MDD_GEOLOCATION);
4760 :
4761 4 : CPLString osTMP;
4762 4 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
4763 :
4764 4 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, GDAL_MDD_GEOLOCATION);
4765 4 : GDALPamDataset::SetMetadataItem("X_BAND", "1", GDAL_MDD_GEOLOCATION);
4766 4 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
4767 :
4768 4 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, GDAL_MDD_GEOLOCATION);
4769 4 : GDALPamDataset::SetMetadataItem("Y_BAND", "1", GDAL_MDD_GEOLOCATION);
4770 :
4771 4 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", GDAL_MDD_GEOLOCATION);
4772 4 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", GDAL_MDD_GEOLOCATION);
4773 :
4774 4 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", GDAL_MDD_GEOLOCATION);
4775 4 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1", GDAL_MDD_GEOLOCATION);
4776 :
4777 4 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
4778 : GDAL_MDD_GEOLOCATION);
4779 4 : return true;
4780 : }
4781 :
4782 380 : int netCDFDataset::ProcessCFGeolocation(int nGroupId, int nVarId,
4783 : const std::string &osGeolocWKT,
4784 : std::string &osGeolocXNameOut,
4785 : std::string &osGeolocYNameOut)
4786 : {
4787 380 : bool bAddGeoloc = false;
4788 380 : char *pszCoordinates = nullptr;
4789 :
4790 : // If there is no explicit "coordinates" attribute, check if there are
4791 : // "lon" and "lat" 2D variables whose dimensions are the last
4792 : // 2 ones of the variable of interest.
4793 380 : if (NCDFGetAttr(nGroupId, nVarId, "coordinates", &pszCoordinates) !=
4794 : CE_None)
4795 : {
4796 328 : CPLFree(pszCoordinates);
4797 328 : pszCoordinates = nullptr;
4798 :
4799 328 : int nVarDims = 0;
4800 328 : NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
4801 328 : if (nVarDims >= 2)
4802 : {
4803 656 : std::vector<int> anVarDimIds(nVarDims);
4804 328 : NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
4805 :
4806 328 : int nLongitudeId = 0;
4807 328 : int nLatitudeId = 0;
4808 400 : if (nc_inq_varid(nGroupId, "lon", &nLongitudeId) == NC_NOERR &&
4809 72 : nc_inq_varid(nGroupId, "lat", &nLatitudeId) == NC_NOERR)
4810 : {
4811 72 : int nDimsLongitude = 0;
4812 72 : NCDF_ERR(
4813 : nc_inq_varndims(nGroupId, nLongitudeId, &nDimsLongitude));
4814 72 : int nDimsLatitude = 0;
4815 72 : NCDF_ERR(
4816 : nc_inq_varndims(nGroupId, nLatitudeId, &nDimsLatitude));
4817 72 : if (nDimsLongitude == 2 && nDimsLatitude == 2)
4818 : {
4819 42 : std::vector<int> anDimLongitudeIds(2);
4820 21 : NCDF_ERR(nc_inq_vardimid(nGroupId, nLongitudeId,
4821 : anDimLongitudeIds.data()));
4822 42 : std::vector<int> anDimLatitudeIds(2);
4823 21 : NCDF_ERR(nc_inq_vardimid(nGroupId, nLatitudeId,
4824 : anDimLatitudeIds.data()));
4825 21 : if (anDimLongitudeIds == anDimLatitudeIds &&
4826 42 : anVarDimIds[anVarDimIds.size() - 2] ==
4827 63 : anDimLongitudeIds[0] &&
4828 42 : anVarDimIds[anVarDimIds.size() - 1] ==
4829 21 : anDimLongitudeIds[1])
4830 : {
4831 21 : pszCoordinates = CPLStrdup("lon lat");
4832 : }
4833 : }
4834 : }
4835 : }
4836 : }
4837 :
4838 380 : if (pszCoordinates)
4839 : {
4840 : // Get X and Y geolocation names from coordinates attribute.
4841 : const CPLStringList aosCoordinates(
4842 146 : NCDFTokenizeCoordinatesAttribute(pszCoordinates));
4843 73 : if (aosCoordinates.size() >= 2)
4844 : {
4845 : char szGeolocXName[NC_MAX_NAME + 1];
4846 : char szGeolocYName[NC_MAX_NAME + 1];
4847 69 : szGeolocXName[0] = '\0';
4848 69 : szGeolocYName[0] = '\0';
4849 :
4850 : // Test that each variable is longitude/latitude.
4851 220 : for (int i = 0; i < aosCoordinates.size(); i++)
4852 : {
4853 151 : if (NCDFIsVarLongitude(nGroupId, -1, aosCoordinates[i]))
4854 : {
4855 58 : int nOtherGroupId = -1;
4856 58 : int nOtherVarId = -1;
4857 : // Check that the variable actually exists
4858 : // Needed on Sentinel-3 products
4859 58 : if (NCDFResolveVar(nGroupId, aosCoordinates[i],
4860 58 : &nOtherGroupId, &nOtherVarId) == CE_None)
4861 : {
4862 56 : snprintf(szGeolocXName, sizeof(szGeolocXName), "%s",
4863 : aosCoordinates[i]);
4864 : }
4865 : }
4866 93 : else if (NCDFIsVarLatitude(nGroupId, -1, aosCoordinates[i]))
4867 : {
4868 58 : int nOtherGroupId = -1;
4869 58 : int nOtherVarId = -1;
4870 : // Check that the variable actually exists
4871 : // Needed on Sentinel-3 products
4872 58 : if (NCDFResolveVar(nGroupId, aosCoordinates[i],
4873 58 : &nOtherGroupId, &nOtherVarId) == CE_None)
4874 : {
4875 56 : snprintf(szGeolocYName, sizeof(szGeolocYName), "%s",
4876 : aosCoordinates[i]);
4877 : }
4878 : }
4879 : }
4880 : // Add GEOLOCATION metadata.
4881 69 : if (!EQUAL(szGeolocXName, "") && !EQUAL(szGeolocYName, ""))
4882 : {
4883 56 : osGeolocXNameOut = szGeolocXName;
4884 56 : osGeolocYNameOut = szGeolocYName;
4885 :
4886 112 : std::string osGeolocXFullName;
4887 112 : std::string osGeolocYFullName;
4888 56 : if (NCDFResolveVarFullName(nGroupId, szGeolocXName,
4889 112 : osGeolocXFullName) == CE_None &&
4890 56 : NCDFResolveVarFullName(nGroupId, szGeolocYName,
4891 : osGeolocYFullName) == CE_None)
4892 : {
4893 56 : if (bSwitchedXY)
4894 : {
4895 2 : std::swap(osGeolocXFullName, osGeolocYFullName);
4896 2 : GDALPamDataset::SetMetadataItem("SWAP_XY", "YES",
4897 : GDAL_MDD_GEOLOCATION);
4898 : }
4899 :
4900 56 : bAddGeoloc = true;
4901 56 : CPLDebug("GDAL_netCDF",
4902 : "using variables %s and %s for GEOLOCATION",
4903 : osGeolocXFullName.c_str(),
4904 : osGeolocYFullName.c_str());
4905 :
4906 56 : GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
4907 : GDAL_MDD_GEOLOCATION);
4908 :
4909 112 : CPLString osTMP;
4910 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4911 56 : osGeolocXFullName.c_str());
4912 :
4913 56 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP,
4914 : GDAL_MDD_GEOLOCATION);
4915 56 : GDALPamDataset::SetMetadataItem("X_BAND", "1",
4916 : GDAL_MDD_GEOLOCATION);
4917 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4918 56 : osGeolocYFullName.c_str());
4919 :
4920 56 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP,
4921 : GDAL_MDD_GEOLOCATION);
4922 56 : GDALPamDataset::SetMetadataItem("Y_BAND", "1",
4923 : GDAL_MDD_GEOLOCATION);
4924 :
4925 56 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0",
4926 : GDAL_MDD_GEOLOCATION);
4927 56 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1",
4928 : GDAL_MDD_GEOLOCATION);
4929 :
4930 56 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0",
4931 : GDAL_MDD_GEOLOCATION);
4932 56 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1",
4933 : GDAL_MDD_GEOLOCATION);
4934 :
4935 56 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
4936 : "PIXEL_CENTER",
4937 : GDAL_MDD_GEOLOCATION);
4938 : }
4939 : else
4940 : {
4941 0 : CPLDebug("GDAL_netCDF",
4942 : "cannot resolve location of "
4943 : "lat/lon variables specified by the coordinates "
4944 : "attribute [%s]",
4945 : pszCoordinates);
4946 56 : }
4947 : }
4948 : else
4949 : {
4950 13 : CPLDebug("GDAL_netCDF",
4951 : "coordinates attribute [%s] is unsupported",
4952 : pszCoordinates);
4953 : }
4954 : }
4955 : else
4956 : {
4957 4 : CPLDebug("GDAL_netCDF",
4958 : "coordinates attribute [%s] with %d element(s) is "
4959 : "unsupported",
4960 : pszCoordinates, aosCoordinates.size());
4961 : }
4962 : }
4963 :
4964 : else
4965 : {
4966 307 : bAddGeoloc = ProcessNASAL2OceanGeoLocation(nGroupId, nVarId);
4967 :
4968 307 : if (!bAddGeoloc)
4969 306 : bAddGeoloc = ProcessNASAEMITGeoLocation(nGroupId, nVarId);
4970 : }
4971 :
4972 380 : CPLFree(pszCoordinates);
4973 :
4974 380 : return bAddGeoloc;
4975 : }
4976 :
4977 8 : CPLErr netCDFDataset::Set1DGeolocation(int nGroupId, int nVarId,
4978 : const char *szDimName)
4979 : {
4980 : // Get values.
4981 8 : char *pszVarValues = nullptr;
4982 8 : CPLErr eErr = NCDFGet1DVar(nGroupId, nVarId, &pszVarValues);
4983 8 : if (eErr != CE_None)
4984 0 : return eErr;
4985 :
4986 : // Write metadata.
4987 8 : char szTemp[NC_MAX_NAME + 1 + 32] = {};
4988 8 : snprintf(szTemp, sizeof(szTemp), "%s_VALUES", szDimName);
4989 8 : GDALPamDataset::SetMetadataItem(szTemp, pszVarValues, "GEOLOCATION2");
4990 :
4991 8 : CPLFree(pszVarValues);
4992 :
4993 8 : return CE_None;
4994 : }
4995 :
4996 0 : double *netCDFDataset::Get1DGeolocation(CPL_UNUSED const char *szDimName,
4997 : int &nVarLen)
4998 : {
4999 0 : nVarLen = 0;
5000 :
5001 : // Get Y_VALUES as tokens.
5002 : const CPLStringList aosValues(
5003 0 : NCDFTokenizeArray(GetMetadataItem("Y_VALUES", "GEOLOCATION2")));
5004 0 : if (aosValues.empty())
5005 0 : return nullptr;
5006 :
5007 : // Initialize and fill array.
5008 0 : nVarLen = aosValues.size();
5009 : double *pdfVarValues =
5010 0 : static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
5011 :
5012 0 : for (int i = 0, j = 0; i < nVarLen; i++)
5013 : {
5014 0 : if (!bBottomUp)
5015 0 : j = nVarLen - 1 - i;
5016 : else
5017 0 : j = i; // Invert latitude values.
5018 0 : char *pszTemp = nullptr;
5019 0 : pdfVarValues[j] = CPLStrtod(aosValues[i], &pszTemp);
5020 : }
5021 :
5022 0 : return pdfVarValues;
5023 : }
5024 :
5025 : /************************************************************************/
5026 : /* SetSpatialRefNoUpdate() */
5027 : /************************************************************************/
5028 :
5029 309 : void netCDFDataset::SetSpatialRefNoUpdate(const OGRSpatialReference *poSRS)
5030 : {
5031 309 : m_oSRS.Clear();
5032 309 : if (poSRS)
5033 302 : m_oSRS = *poSRS;
5034 309 : m_bHasProjection = true;
5035 309 : }
5036 :
5037 : /************************************************************************/
5038 : /* SetSpatialRef() */
5039 : /************************************************************************/
5040 :
5041 87 : CPLErr netCDFDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
5042 : {
5043 174 : CPLMutexHolderD(&hNCMutex);
5044 :
5045 87 : if (GetAccess() != GA_Update || m_bHasProjection)
5046 : {
5047 0 : CPLError(CE_Failure, CPLE_AppDefined,
5048 : "netCDFDataset::_SetProjection() should only be called once "
5049 : "in update mode!");
5050 0 : return CE_Failure;
5051 : }
5052 :
5053 87 : if (m_bHasGeoTransform)
5054 : {
5055 32 : SetSpatialRefNoUpdate(poSRS);
5056 :
5057 : // For NC4/NC4C, writing both projection variables and data,
5058 : // followed by redefining nodata value, cancels the projection
5059 : // info from the Band variable, so for now only write the
5060 : // variable definitions, and write data at the end.
5061 : // See https://trac.osgeo.org/gdal/ticket/7245
5062 32 : return AddProjectionVars(true, nullptr, nullptr);
5063 : }
5064 :
5065 55 : SetSpatialRefNoUpdate(poSRS);
5066 :
5067 55 : return CE_None;
5068 : }
5069 :
5070 : /************************************************************************/
5071 : /* SetGeoTransformNoUpdate() */
5072 : /************************************************************************/
5073 :
5074 312 : void netCDFDataset::SetGeoTransformNoUpdate(const GDALGeoTransform >)
5075 : {
5076 312 : m_gt = gt;
5077 312 : m_bHasGeoTransform = true;
5078 312 : }
5079 :
5080 : /************************************************************************/
5081 : /* SetGeoTransform() */
5082 : /************************************************************************/
5083 :
5084 88 : CPLErr netCDFDataset::SetGeoTransform(const GDALGeoTransform >)
5085 : {
5086 176 : CPLMutexHolderD(&hNCMutex);
5087 :
5088 88 : if (GetAccess() != GA_Update || m_bHasGeoTransform)
5089 : {
5090 0 : CPLError(CE_Failure, CPLE_AppDefined,
5091 : "netCDFDataset::SetGeoTransform() should only be called once "
5092 : "in update mode!");
5093 0 : return CE_Failure;
5094 : }
5095 :
5096 88 : CPLDebug("GDAL_netCDF", "SetGeoTransform(%f,%f,%f,%f,%f,%f)", gt.xorig,
5097 88 : gt.xscale, gt.xrot, gt.yorig, gt.yrot, gt.yscale);
5098 :
5099 88 : SetGeoTransformNoUpdate(gt);
5100 :
5101 88 : if (m_bHasProjection)
5102 : {
5103 :
5104 : // For NC4/NC4C, writing both projection variables and data,
5105 : // followed by redefining nodata value, cancels the projection
5106 : // info from the Band variable, so for now only write the
5107 : // variable definitions, and write data at the end.
5108 : // See https://trac.osgeo.org/gdal/ticket/7245
5109 3 : return AddProjectionVars(true, nullptr, nullptr);
5110 : }
5111 :
5112 85 : return CE_None;
5113 : }
5114 :
5115 : /************************************************************************/
5116 : /* NCDFWriteSRSVariable() */
5117 : /************************************************************************/
5118 :
5119 143 : int NCDFWriteSRSVariable(int cdfid, const OGRSpatialReference *poSRS,
5120 : char **ppszCFProjection, bool bWriteGDALTags,
5121 : const std::string &srsVarName)
5122 : {
5123 143 : char *pszCFProjection = nullptr;
5124 143 : char **papszKeyValues = nullptr;
5125 143 : poSRS->exportToCF1(&pszCFProjection, &papszKeyValues, nullptr, nullptr);
5126 :
5127 143 : if (bWriteGDALTags)
5128 : {
5129 142 : const char *pszWKT = CSLFetchNameValue(papszKeyValues, NCDF_CRS_WKT);
5130 142 : if (pszWKT)
5131 : {
5132 : // SPATIAL_REF is deprecated. Will be removed in GDAL 4.
5133 142 : papszKeyValues =
5134 142 : CSLSetNameValue(papszKeyValues, NCDF_SPATIAL_REF, pszWKT);
5135 : }
5136 : }
5137 :
5138 143 : const int nValues = CSLCount(papszKeyValues);
5139 :
5140 : int NCDFVarID;
5141 286 : std::string varNameRadix(pszCFProjection);
5142 143 : int nCounter = 2;
5143 : while (true)
5144 : {
5145 145 : NCDFVarID = -1;
5146 145 : nc_inq_varid(cdfid, pszCFProjection, &NCDFVarID);
5147 145 : if (NCDFVarID < 0)
5148 140 : break;
5149 :
5150 5 : int nbAttr = 0;
5151 5 : NCDF_ERR(nc_inq_varnatts(cdfid, NCDFVarID, &nbAttr));
5152 5 : bool bSame = nbAttr == nValues;
5153 41 : for (int i = 0; bSame && (i < nbAttr); i++)
5154 : {
5155 : char szAttrName[NC_MAX_NAME + 1];
5156 38 : szAttrName[0] = 0;
5157 38 : NCDF_ERR(nc_inq_attname(cdfid, NCDFVarID, i, szAttrName));
5158 :
5159 : const char *pszValue =
5160 38 : CSLFetchNameValue(papszKeyValues, szAttrName);
5161 38 : if (!pszValue)
5162 : {
5163 0 : bSame = false;
5164 2 : break;
5165 : }
5166 :
5167 38 : nc_type atttype = NC_NAT;
5168 38 : size_t attlen = 0;
5169 38 : NCDF_ERR(
5170 : nc_inq_att(cdfid, NCDFVarID, szAttrName, &atttype, &attlen));
5171 38 : if (atttype != NC_CHAR && atttype != NC_DOUBLE)
5172 : {
5173 0 : bSame = false;
5174 0 : break;
5175 : }
5176 38 : if (atttype == NC_CHAR)
5177 : {
5178 15 : if (CPLGetValueType(pszValue) != CPL_VALUE_STRING)
5179 : {
5180 0 : bSame = false;
5181 0 : break;
5182 : }
5183 15 : std::string val;
5184 15 : NCDFGetAttr(cdfid, NCDFVarID, szAttrName, val);
5185 15 : if (val != pszValue)
5186 : {
5187 0 : bSame = false;
5188 0 : break;
5189 : }
5190 : }
5191 : else
5192 : {
5193 : const CPLStringList aosTokens(
5194 23 : CSLTokenizeString2(pszValue, ",", 0));
5195 23 : if (static_cast<size_t>(aosTokens.size()) != attlen)
5196 : {
5197 0 : bSame = false;
5198 0 : break;
5199 : }
5200 : double vals[2];
5201 23 : nc_get_att_double(cdfid, NCDFVarID, szAttrName, vals);
5202 44 : if (vals[0] != CPLAtof(aosTokens[0]) ||
5203 21 : (attlen == 2 && vals[1] != CPLAtof(aosTokens[1])))
5204 : {
5205 2 : bSame = false;
5206 2 : break;
5207 : }
5208 : }
5209 : }
5210 5 : if (bSame)
5211 : {
5212 3 : *ppszCFProjection = pszCFProjection;
5213 3 : CSLDestroy(papszKeyValues);
5214 3 : return NCDFVarID;
5215 : }
5216 2 : CPLFree(pszCFProjection);
5217 2 : pszCFProjection =
5218 2 : CPLStrdup(CPLSPrintf("%s_%d", varNameRadix.c_str(), nCounter));
5219 2 : nCounter++;
5220 2 : }
5221 :
5222 140 : *ppszCFProjection = pszCFProjection;
5223 :
5224 : const char *pszVarName;
5225 :
5226 140 : if (srsVarName != "")
5227 : {
5228 38 : pszVarName = srsVarName.c_str();
5229 : }
5230 : else
5231 : {
5232 102 : pszVarName = pszCFProjection;
5233 : }
5234 :
5235 140 : int status = nc_def_var(cdfid, pszVarName, NC_CHAR, 0, nullptr, &NCDFVarID);
5236 140 : NCDF_ERR(status);
5237 1394 : for (int i = 0; i < nValues; ++i)
5238 : {
5239 1254 : char *pszKey = nullptr;
5240 1254 : const char *pszValue = CPLParseNameValue(papszKeyValues[i], &pszKey);
5241 1254 : if (pszKey && pszValue)
5242 : {
5243 2508 : const CPLStringList aosTokens(CSLTokenizeString2(pszValue, ",", 0));
5244 1254 : double adfValues[2] = {0, 0};
5245 1254 : const int nDoubleCount = std::min(2, aosTokens.size());
5246 1254 : if (!(aosTokens.size() == 2 &&
5247 2507 : CPLGetValueType(aosTokens[0]) != CPL_VALUE_STRING) &&
5248 1253 : CPLGetValueType(pszValue) == CPL_VALUE_STRING)
5249 : {
5250 559 : status = nc_put_att_text(cdfid, NCDFVarID, pszKey,
5251 : strlen(pszValue), pszValue);
5252 : }
5253 : else
5254 : {
5255 1391 : for (int j = 0; j < nDoubleCount; ++j)
5256 696 : adfValues[j] = CPLAtof(aosTokens[j]);
5257 695 : status = nc_put_att_double(cdfid, NCDFVarID, pszKey, NC_DOUBLE,
5258 : nDoubleCount, adfValues);
5259 : }
5260 1254 : NCDF_ERR(status);
5261 : }
5262 1254 : CPLFree(pszKey);
5263 : }
5264 :
5265 140 : CSLDestroy(papszKeyValues);
5266 140 : return NCDFVarID;
5267 : }
5268 :
5269 : /************************************************************************/
5270 : /* NCDFWriteLonLatVarsAttributes() */
5271 : /************************************************************************/
5272 :
5273 103 : void NCDFWriteLonLatVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarLonID,
5274 : int nVarLatID)
5275 : {
5276 :
5277 : try
5278 : {
5279 103 : vcdf.nc_put_vatt_text(nVarLatID, CF_STD_NAME, CF_LATITUDE_STD_NAME);
5280 103 : vcdf.nc_put_vatt_text(nVarLatID, CF_LNG_NAME, CF_LATITUDE_LNG_NAME);
5281 103 : vcdf.nc_put_vatt_text(nVarLatID, CF_UNITS, CF_DEGREES_NORTH);
5282 103 : vcdf.nc_put_vatt_text(nVarLonID, CF_STD_NAME, CF_LONGITUDE_STD_NAME);
5283 103 : vcdf.nc_put_vatt_text(nVarLonID, CF_LNG_NAME, CF_LONGITUDE_LNG_NAME);
5284 103 : vcdf.nc_put_vatt_text(nVarLonID, CF_UNITS, CF_DEGREES_EAST);
5285 : }
5286 0 : catch (nccfdriver::SG_Exception &e)
5287 : {
5288 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5289 : }
5290 103 : }
5291 :
5292 : /************************************************************************/
5293 : /* NCDFWriteRLonRLatVarsAttributes() */
5294 : /************************************************************************/
5295 :
5296 0 : void NCDFWriteRLonRLatVarsAttributes(nccfdriver::netCDFVID &vcdf,
5297 : int nVarRLonID, int nVarRLatID)
5298 : {
5299 : try
5300 : {
5301 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_STD_NAME, "grid_latitude");
5302 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_LNG_NAME,
5303 : "latitude in rotated pole grid");
5304 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_UNITS, "degrees");
5305 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_AXIS, "Y");
5306 :
5307 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_STD_NAME, "grid_longitude");
5308 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_LNG_NAME,
5309 : "longitude in rotated pole grid");
5310 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_UNITS, "degrees");
5311 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_AXIS, "X");
5312 : }
5313 0 : catch (nccfdriver::SG_Exception &e)
5314 : {
5315 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5316 : }
5317 0 : }
5318 :
5319 : /************************************************************************/
5320 : /* NCDFGetProjectedCFUnit() */
5321 : /************************************************************************/
5322 :
5323 51 : std::string NCDFGetProjectedCFUnit(const OGRSpatialReference *poSRS)
5324 : {
5325 51 : char *pszUnitsToWrite = nullptr;
5326 51 : poSRS->exportToCF1(nullptr, nullptr, &pszUnitsToWrite, nullptr);
5327 51 : std::string osRet = pszUnitsToWrite ? pszUnitsToWrite : std::string();
5328 51 : CPLFree(pszUnitsToWrite);
5329 102 : return osRet;
5330 : }
5331 :
5332 : /************************************************************************/
5333 : /* NCDFWriteXYVarsAttributes() */
5334 : /************************************************************************/
5335 :
5336 36 : void NCDFWriteXYVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarXID,
5337 : int nVarYID, const OGRSpatialReference *poSRS)
5338 : {
5339 72 : const std::string osUnitsToWrite = NCDFGetProjectedCFUnit(poSRS);
5340 :
5341 : try
5342 : {
5343 36 : vcdf.nc_put_vatt_text(nVarXID, CF_STD_NAME, CF_PROJ_X_COORD);
5344 36 : vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME, CF_PROJ_X_COORD_LONG_NAME);
5345 36 : if (!osUnitsToWrite.empty())
5346 36 : vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, osUnitsToWrite.c_str());
5347 36 : vcdf.nc_put_vatt_text(nVarYID, CF_STD_NAME, CF_PROJ_Y_COORD);
5348 36 : vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME, CF_PROJ_Y_COORD_LONG_NAME);
5349 36 : if (!osUnitsToWrite.empty())
5350 36 : vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, osUnitsToWrite.c_str());
5351 : }
5352 0 : catch (nccfdriver::SG_Exception &e)
5353 : {
5354 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5355 : }
5356 36 : }
5357 :
5358 : /************************************************************************/
5359 : /* AddProjectionVars() */
5360 : /************************************************************************/
5361 :
5362 186 : CPLErr netCDFDataset::AddProjectionVars(bool bDefsOnly,
5363 : GDALProgressFunc pfnProgress,
5364 : void *pProgressData)
5365 : {
5366 186 : if (nCFVersion >= 1.8)
5367 0 : return CE_None; // do nothing
5368 :
5369 186 : bool bWriteGridMapping = false;
5370 186 : bool bWriteLonLat = false;
5371 186 : bool bHasGeoloc = false;
5372 186 : bool bWriteGDALTags = false;
5373 186 : bool bWriteGeoTransform = false;
5374 :
5375 : // For GEOLOCATION information.
5376 186 : GDALDatasetUniquePtr poDS_X;
5377 186 : GDALDatasetUniquePtr poDS_Y;
5378 186 : GDALRasterBand *poBand_X = nullptr;
5379 186 : GDALRasterBand *poBand_Y = nullptr;
5380 :
5381 372 : OGRSpatialReference oSRS(m_oSRS);
5382 186 : if (!m_oSRS.IsEmpty())
5383 : {
5384 160 : if (oSRS.IsProjected())
5385 72 : bIsProjected = true;
5386 88 : else if (oSRS.IsGeographic())
5387 88 : bIsGeographic = true;
5388 : }
5389 :
5390 186 : if (bDefsOnly)
5391 : {
5392 186 : const std::string osProjection = m_oSRS.exportToWkt();
5393 173 : CPLDebug("GDAL_netCDF",
5394 : "SetProjection, WKT now = [%s]\nprojected: %d geographic: %d",
5395 80 : osProjection.empty() ? "(null)" : osProjection.c_str(),
5396 93 : static_cast<int>(bIsProjected),
5397 93 : static_cast<int>(bIsGeographic));
5398 :
5399 93 : if (!m_bHasGeoTransform)
5400 5 : CPLDebug("GDAL_netCDF",
5401 : "netCDFDataset::AddProjectionVars() called, "
5402 : "but GeoTransform has not yet been defined!");
5403 :
5404 93 : if (!m_bHasProjection)
5405 6 : CPLDebug("GDAL_netCDF",
5406 : "netCDFDataset::AddProjectionVars() called, "
5407 : "but Projection has not yet been defined!");
5408 : }
5409 :
5410 : // Check GEOLOCATION information.
5411 : CSLConstList papszGeolocationInfo =
5412 186 : netCDFDataset::GetMetadata(GDAL_MDD_GEOLOCATION);
5413 186 : if (papszGeolocationInfo != nullptr)
5414 : {
5415 : // Look for geolocation datasets.
5416 : const char *pszDSName =
5417 10 : CSLFetchNameValue(papszGeolocationInfo, "X_DATASET");
5418 10 : if (pszDSName != nullptr)
5419 10 : poDS_X.reset(GDALDataset::Open(
5420 : pszDSName,
5421 : GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR | GDAL_OF_SHARED));
5422 10 : pszDSName = CSLFetchNameValue(papszGeolocationInfo, "Y_DATASET");
5423 10 : if (pszDSName != nullptr)
5424 10 : poDS_Y.reset(GDALDataset::Open(
5425 : pszDSName,
5426 : GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR | GDAL_OF_SHARED));
5427 :
5428 10 : if (poDS_X != nullptr && poDS_Y != nullptr)
5429 : {
5430 10 : int nBand = std::max(1, atoi(CSLFetchNameValueDef(
5431 10 : papszGeolocationInfo, "X_BAND", "0")));
5432 10 : poBand_X = poDS_X->GetRasterBand(nBand);
5433 10 : nBand = std::max(1, atoi(CSLFetchNameValueDef(papszGeolocationInfo,
5434 10 : "Y_BAND", "0")));
5435 10 : poBand_Y = poDS_Y->GetRasterBand(nBand);
5436 :
5437 : // If geoloc bands are found, do basic validation based on their
5438 : // dimensions.
5439 10 : if (poBand_X != nullptr && poBand_Y != nullptr)
5440 : {
5441 10 : const int nXSize_XBand = poBand_X->GetXSize();
5442 10 : const int nYSize_XBand = poBand_X->GetYSize();
5443 10 : const int nXSize_YBand = poBand_Y->GetXSize();
5444 10 : const int nYSize_YBand = poBand_Y->GetYSize();
5445 :
5446 : // TODO 1D geolocation arrays not implemented.
5447 10 : if (nYSize_XBand == 1 && nYSize_YBand == 1)
5448 : {
5449 0 : bHasGeoloc = false;
5450 0 : CPLDebug("GDAL_netCDF",
5451 : "1D GEOLOCATION arrays not supported yet");
5452 : }
5453 : // 2D bands must have same sizes as the raster bands.
5454 10 : else if (nXSize_XBand != nRasterXSize ||
5455 10 : nYSize_XBand != nRasterYSize ||
5456 10 : nXSize_YBand != nRasterXSize ||
5457 10 : nYSize_YBand != nRasterYSize)
5458 : {
5459 0 : bHasGeoloc = false;
5460 0 : CPLDebug("GDAL_netCDF",
5461 : "GEOLOCATION array sizes (%dx%d %dx%d) differ "
5462 : "from raster (%dx%d), not supported",
5463 : nXSize_XBand, nYSize_XBand, nXSize_YBand,
5464 : nYSize_YBand, nRasterXSize, nRasterYSize);
5465 : }
5466 : else
5467 : {
5468 10 : bHasGeoloc = true;
5469 10 : CPLDebug("GDAL_netCDF",
5470 : "dataset has GEOLOCATION information, will try to "
5471 : "write it");
5472 : }
5473 : }
5474 : }
5475 : }
5476 :
5477 : // Process projection options.
5478 186 : if (bIsProjected)
5479 : {
5480 : bool bIsCfProjection =
5481 72 : oSRS.exportToCF1(nullptr, nullptr, nullptr, nullptr) == OGRERR_NONE;
5482 72 : bWriteGridMapping = true;
5483 72 : bWriteGDALTags = aosCreationOptions.FetchBool("WRITE_GDAL_TAGS", true);
5484 : // Force WRITE_GDAL_TAGS if is not a CF projection.
5485 72 : if (!bWriteGDALTags && !bIsCfProjection)
5486 0 : bWriteGDALTags = true;
5487 72 : if (bWriteGDALTags)
5488 72 : bWriteGeoTransform = true;
5489 :
5490 : // Write lon/lat: default is NO, except if has geolocation.
5491 : // With IF_NEEDED: write if has geoloc or is not CF projection.
5492 : const char *pszValue =
5493 72 : aosCreationOptions.FetchNameValue("WRITE_LONLAT");
5494 72 : if (pszValue)
5495 : {
5496 6 : if (EQUAL(pszValue, "IF_NEEDED"))
5497 : {
5498 0 : bWriteLonLat = bHasGeoloc || !bIsCfProjection;
5499 : }
5500 : else
5501 : {
5502 6 : bWriteLonLat = CPLTestBool(pszValue);
5503 : }
5504 : }
5505 : else
5506 : {
5507 66 : bWriteLonLat = bHasGeoloc;
5508 : }
5509 :
5510 : // Save value of pszCFCoordinates for later.
5511 72 : if (bWriteLonLat)
5512 : {
5513 8 : pszCFCoordinates = NCDF_LONLAT;
5514 : }
5515 : }
5516 : else
5517 : {
5518 : // Files without a Datum will not have a grid_mapping variable and
5519 : // geographic information.
5520 114 : bWriteGridMapping = bIsGeographic;
5521 :
5522 114 : if (bHasGeoloc)
5523 : {
5524 8 : bWriteLonLat = true;
5525 : }
5526 : else
5527 : {
5528 106 : bWriteGDALTags = aosCreationOptions.FetchBool("WRITE_GDAL_TAGS",
5529 : bWriteGridMapping);
5530 106 : if (bWriteGDALTags)
5531 88 : bWriteGeoTransform = true;
5532 :
5533 : const char *pszValue =
5534 106 : aosCreationOptions.FetchNameValueDef("WRITE_LONLAT", "YES");
5535 106 : if (EQUAL(pszValue, "IF_NEEDED"))
5536 0 : bWriteLonLat = true;
5537 : else
5538 106 : bWriteLonLat = CPLTestBool(pszValue);
5539 : // Don't write lon/lat if no source geotransform.
5540 106 : if (!m_bHasGeoTransform)
5541 0 : bWriteLonLat = false;
5542 : // If we don't write lon/lat, set dimnames to X/Y and write gdal
5543 : // tags.
5544 106 : if (!bWriteLonLat)
5545 : {
5546 0 : CPLError(CE_Warning, CPLE_AppDefined,
5547 : "creating geographic file without lon/lat values!");
5548 0 : if (m_bHasGeoTransform)
5549 : {
5550 0 : bWriteGDALTags = true; // Not desirable if no geotransform.
5551 0 : bWriteGeoTransform = true;
5552 : }
5553 : }
5554 : }
5555 : }
5556 :
5557 : // Make sure we write grid_mapping if we need to write GDAL tags.
5558 186 : if (bWriteGDALTags)
5559 160 : bWriteGridMapping = true;
5560 :
5561 : // bottom-up value: new driver is bottom-up by default.
5562 : // Override with WRITE_BOTTOMUP.
5563 186 : bBottomUp = aosCreationOptions.FetchBool("WRITE_BOTTOMUP", true);
5564 :
5565 186 : if (bDefsOnly)
5566 : {
5567 93 : CPLDebug(
5568 : "GDAL_netCDF",
5569 : "bIsProjected=%d bIsGeographic=%d bWriteGridMapping=%d "
5570 : "bWriteGDALTags=%d bWriteLonLat=%d bBottomUp=%d bHasGeoloc=%d",
5571 93 : static_cast<int>(bIsProjected), static_cast<int>(bIsGeographic),
5572 : static_cast<int>(bWriteGridMapping),
5573 : static_cast<int>(bWriteGDALTags), static_cast<int>(bWriteLonLat),
5574 93 : static_cast<int>(bBottomUp), static_cast<int>(bHasGeoloc));
5575 : }
5576 :
5577 : // Exit if nothing to do.
5578 186 : if (!bIsProjected && !bWriteLonLat)
5579 0 : return CE_None;
5580 :
5581 : // Define dimension names.
5582 :
5583 186 : constexpr const char *ROTATED_POLE_VAR_NAME = "rotated_pole";
5584 :
5585 186 : if (bDefsOnly)
5586 : {
5587 93 : int nVarLonID = -1;
5588 93 : int nVarLatID = -1;
5589 93 : int nVarXID = -1;
5590 93 : int nVarYID = -1;
5591 :
5592 93 : m_bAddedProjectionVarsDefs = true;
5593 :
5594 : // Make sure we are in define mode.
5595 93 : SetDefineMode(true);
5596 :
5597 : // Write projection attributes.
5598 93 : if (bWriteGridMapping)
5599 : {
5600 80 : const int NCDFVarID = NCDFWriteSRSVariable(
5601 : cdfid, &oSRS, &pszCFProjection, bWriteGDALTags);
5602 80 : if (NCDFVarID < 0)
5603 0 : return CE_Failure;
5604 :
5605 : // Optional GDAL custom projection tags.
5606 80 : if (bWriteGDALTags && bWriteGeoTransform && m_bHasGeoTransform)
5607 : {
5608 79 : GDALGeoTransform gt(m_gt);
5609 79 : if (!bBottomUp)
5610 : {
5611 : // Change origin from top to bottom and sign of coefficients
5612 : // indexed by row
5613 2 : gt.yorig += nRasterYSize * gt.yscale;
5614 2 : gt.xorig += nRasterYSize * gt.xrot;
5615 2 : gt.xrot = -gt.xrot;
5616 2 : gt.yscale = -gt.yscale;
5617 : }
5618 158 : std::string osGeoTransform = gt.ToString(" ");
5619 79 : CPLDebug("GDAL_netCDF", "szGeoTransform = %s",
5620 : osGeoTransform.c_str());
5621 :
5622 79 : const int status = nc_put_att_text(
5623 : cdfid, NCDFVarID, NCDF_GEOTRANSFORM, osGeoTransform.size(),
5624 : osGeoTransform.c_str());
5625 79 : NCDF_ERR(status);
5626 : }
5627 :
5628 : // Write projection variable to band variable.
5629 : // Need to call later if there are no bands.
5630 80 : AddGridMappingRef();
5631 : } // end if( bWriteGridMapping )
5632 :
5633 : // Write CF Projection vars.
5634 :
5635 93 : const bool bIsRotatedPole =
5636 173 : pszCFProjection != nullptr &&
5637 80 : EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
5638 :
5639 93 : if (m_bHasGeoTransform && !m_gt.IsAxisAligned())
5640 : {
5641 : // Do not write X/Y coordinate arrays
5642 : }
5643 :
5644 89 : else if (bIsRotatedPole)
5645 : {
5646 : // Rename dims to rlat/rlon.
5647 : papszDimName
5648 0 : .Clear(); // If we add other dims one day, this has to change
5649 0 : papszDimName.AddString(NCDF_DIMNAME_RLAT);
5650 0 : papszDimName.AddString(NCDF_DIMNAME_RLON);
5651 :
5652 0 : int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_RLAT);
5653 0 : NCDF_ERR(status);
5654 0 : status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_RLON);
5655 0 : NCDF_ERR(status);
5656 : }
5657 : // Rename dimensions if lon/lat.
5658 89 : else if (!bIsProjected && !bHasGeoloc)
5659 : {
5660 : // Rename dims to lat/lon.
5661 : papszDimName
5662 53 : .Clear(); // If we add other dims one day, this has to change
5663 53 : papszDimName.AddString(NCDF_DIMNAME_LAT);
5664 53 : papszDimName.AddString(NCDF_DIMNAME_LON);
5665 :
5666 53 : int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_LAT);
5667 53 : NCDF_ERR(status);
5668 53 : status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_LON);
5669 53 : NCDF_ERR(status);
5670 : }
5671 :
5672 : // Write X/Y attributes.
5673 : else /* if( bIsProjected || bHasGeoloc ) */
5674 : {
5675 : // X
5676 : int anXDims[1];
5677 36 : anXDims[0] = nXDimID;
5678 36 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
5679 : CF_PROJ_X_VAR_NAME, NC_DOUBLE);
5680 36 : int status = nc_def_var(cdfid, CF_PROJ_X_VAR_NAME, NC_DOUBLE, 1,
5681 : anXDims, &nVarXID);
5682 36 : NCDF_ERR(status);
5683 :
5684 : // Y
5685 : int anYDims[1];
5686 36 : anYDims[0] = nYDimID;
5687 36 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
5688 : CF_PROJ_Y_VAR_NAME, NC_DOUBLE);
5689 36 : status = nc_def_var(cdfid, CF_PROJ_Y_VAR_NAME, NC_DOUBLE, 1,
5690 : anYDims, &nVarYID);
5691 36 : NCDF_ERR(status);
5692 :
5693 36 : if (bIsProjected)
5694 : {
5695 32 : NCDFWriteXYVarsAttributes(this->vcdf, nVarXID, nVarYID, &oSRS);
5696 : }
5697 : else
5698 : {
5699 4 : CPLAssert(bHasGeoloc);
5700 : try
5701 : {
5702 4 : vcdf.nc_put_vatt_text(nVarXID, CF_AXIS, CF_SG_X_AXIS);
5703 4 : vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME,
5704 : "x-coordinate in Cartesian system");
5705 4 : vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, "m");
5706 4 : vcdf.nc_put_vatt_text(nVarYID, CF_AXIS, CF_SG_Y_AXIS);
5707 4 : vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME,
5708 : "y-coordinate in Cartesian system");
5709 4 : vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, "m");
5710 :
5711 4 : pszCFCoordinates = NCDF_LONLAT;
5712 : }
5713 0 : catch (nccfdriver::SG_Exception &e)
5714 : {
5715 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5716 0 : return CE_Failure;
5717 : }
5718 : }
5719 : }
5720 :
5721 : // Write lat/lon attributes if needed.
5722 93 : if (bWriteLonLat)
5723 : {
5724 61 : int anLatDims[2] = {0, 0};
5725 61 : int anLonDims[2] = {0, 0};
5726 61 : int nLatDims = -1;
5727 61 : int nLonDims = -1;
5728 :
5729 : // Get information.
5730 61 : if (bHasGeoloc)
5731 : {
5732 : // Geoloc
5733 5 : nLatDims = 2;
5734 5 : anLatDims[0] = nYDimID;
5735 5 : anLatDims[1] = nXDimID;
5736 5 : nLonDims = 2;
5737 5 : anLonDims[0] = nYDimID;
5738 5 : anLonDims[1] = nXDimID;
5739 : }
5740 56 : else if (bIsProjected)
5741 : {
5742 : // Projected
5743 3 : nLatDims = 2;
5744 3 : anLatDims[0] = nYDimID;
5745 3 : anLatDims[1] = nXDimID;
5746 3 : nLonDims = 2;
5747 3 : anLonDims[0] = nYDimID;
5748 3 : anLonDims[1] = nXDimID;
5749 : }
5750 : else
5751 : {
5752 : // Geographic
5753 53 : nLatDims = 1;
5754 53 : anLatDims[0] = nYDimID;
5755 53 : nLonDims = 1;
5756 53 : anLonDims[0] = nXDimID;
5757 : }
5758 :
5759 61 : nc_type eLonLatType = NC_NAT;
5760 61 : if (bIsProjected)
5761 : {
5762 4 : eLonLatType = NC_FLOAT;
5763 4 : const char *pszValue = aosCreationOptions.FetchNameValueDef(
5764 : "TYPE_LONLAT", "FLOAT");
5765 4 : if (EQUAL(pszValue, "DOUBLE"))
5766 0 : eLonLatType = NC_DOUBLE;
5767 : }
5768 : else
5769 : {
5770 57 : eLonLatType = NC_DOUBLE;
5771 57 : const char *pszValue = aosCreationOptions.FetchNameValueDef(
5772 : "TYPE_LONLAT", "DOUBLE");
5773 57 : if (EQUAL(pszValue, "FLOAT"))
5774 0 : eLonLatType = NC_FLOAT;
5775 : }
5776 :
5777 : // Def vars and attributes.
5778 : {
5779 61 : const char *pszVarName =
5780 61 : bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME;
5781 61 : int status = nc_def_var(cdfid, pszVarName, eLonLatType,
5782 : nLatDims, anLatDims, &nVarLatID);
5783 61 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
5784 : cdfid, pszVarName, eLonLatType, nLatDims, nVarLatID);
5785 61 : NCDF_ERR(status);
5786 61 : DefVarDeflate(nVarLatID, false); // Don't set chunking.
5787 : }
5788 :
5789 : {
5790 61 : const char *pszVarName =
5791 61 : bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME;
5792 61 : int status = nc_def_var(cdfid, pszVarName, eLonLatType,
5793 : nLonDims, anLonDims, &nVarLonID);
5794 61 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
5795 : cdfid, pszVarName, eLonLatType, nLatDims, nVarLonID);
5796 61 : NCDF_ERR(status);
5797 61 : DefVarDeflate(nVarLonID, false); // Don't set chunking.
5798 : }
5799 :
5800 61 : if (bIsRotatedPole)
5801 0 : NCDFWriteRLonRLatVarsAttributes(this->vcdf, nVarLonID,
5802 : nVarLatID);
5803 : else
5804 61 : NCDFWriteLonLatVarsAttributes(this->vcdf, nVarLonID, nVarLatID);
5805 : }
5806 : }
5807 :
5808 186 : if (!bDefsOnly)
5809 : {
5810 93 : m_bAddedProjectionVarsData = true;
5811 :
5812 93 : int nVarXID = -1;
5813 93 : int nVarYID = -1;
5814 :
5815 93 : nc_inq_varid(cdfid, CF_PROJ_X_VAR_NAME, &nVarXID);
5816 93 : nc_inq_varid(cdfid, CF_PROJ_Y_VAR_NAME, &nVarYID);
5817 :
5818 93 : int nVarLonID = -1;
5819 93 : int nVarLatID = -1;
5820 :
5821 93 : const bool bIsRotatedPole =
5822 173 : pszCFProjection != nullptr &&
5823 80 : EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
5824 93 : nc_inq_varid(cdfid,
5825 : bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME,
5826 : &nVarLonID);
5827 93 : nc_inq_varid(cdfid,
5828 : bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME,
5829 : &nVarLatID);
5830 :
5831 : // Get projection values.
5832 :
5833 93 : if (bIsProjected)
5834 : {
5835 0 : std::unique_ptr<OGRSpatialReference> poLatLonSRS;
5836 0 : std::unique_ptr<OGRCoordinateTransformation> poTransform;
5837 :
5838 : size_t startX[1];
5839 : size_t countX[1];
5840 : size_t startY[1];
5841 : size_t countY[1];
5842 :
5843 36 : CPLDebug("GDAL_netCDF", "Getting (X,Y) values");
5844 :
5845 : std::unique_ptr<double, decltype(&VSIFree)> adXValKeeper(
5846 : static_cast<double *>(
5847 72 : VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
5848 36 : VSIFree);
5849 : std::unique_ptr<double, decltype(&VSIFree)> adYValKeeper(
5850 : static_cast<double *>(
5851 72 : VSI_MALLOC2_VERBOSE(nRasterYSize, sizeof(double))),
5852 36 : VSIFree);
5853 36 : double *padXVal = adXValKeeper.get();
5854 36 : double *padYVal = adYValKeeper.get();
5855 36 : if (!padXVal || !padYVal)
5856 : {
5857 0 : return CE_Failure;
5858 : }
5859 :
5860 : // Make sure we are in data mode.
5861 36 : SetDefineMode(false);
5862 :
5863 36 : int status = NC_NOERR;
5864 :
5865 36 : if (m_gt.IsAxisAligned())
5866 : {
5867 : // Get Y values.
5868 32 : const double dfY0 =
5869 32 : (!bBottomUp) ? m_gt.yorig :
5870 : // Invert latitude values.
5871 32 : m_gt.yorig + (m_gt.yscale * nRasterYSize);
5872 32 : const double dfDY = m_gt.yscale;
5873 :
5874 1583 : for (int j = 0; j < nRasterYSize; j++)
5875 : {
5876 : // The data point is centered inside the pixel.
5877 1551 : if (!bBottomUp)
5878 0 : padYVal[j] = dfY0 + (j + 0.5) * dfDY;
5879 : else // Invert latitude values.
5880 1551 : padYVal[j] = dfY0 - (j + 0.5) * dfDY;
5881 : }
5882 32 : startX[0] = 0;
5883 32 : countX[0] = nRasterXSize;
5884 :
5885 : // Get X values.
5886 32 : const double dfX0 = m_gt.xorig;
5887 32 : const double dfDX = m_gt.xscale;
5888 :
5889 1624 : for (int i = 0; i < nRasterXSize; i++)
5890 : {
5891 : // The data point is centered inside the pixel.
5892 1592 : padXVal[i] = dfX0 + (i + 0.5) * dfDX;
5893 : }
5894 32 : startY[0] = 0;
5895 32 : countY[0] = nRasterYSize;
5896 :
5897 : // Write X/Y values.
5898 :
5899 32 : CPLDebug("GDAL_netCDF", "Writing X values");
5900 : status =
5901 32 : nc_put_vara_double(cdfid, nVarXID, startX, countX, padXVal);
5902 32 : NCDF_ERR(status);
5903 :
5904 32 : CPLDebug("GDAL_netCDF", "Writing Y values");
5905 : status =
5906 32 : nc_put_vara_double(cdfid, nVarYID, startY, countY, padYVal);
5907 32 : NCDF_ERR(status);
5908 : }
5909 :
5910 36 : if (pfnProgress)
5911 32 : pfnProgress(0.20, nullptr, pProgressData);
5912 :
5913 : // Write lon/lat arrays (CF coordinates) if requested.
5914 :
5915 : // Get OGR transform if GEOLOCATION is not available.
5916 36 : if (bWriteLonLat && !bHasGeoloc)
5917 : {
5918 3 : poLatLonSRS.reset(m_oSRS.CloneGeogCS());
5919 3 : if (poLatLonSRS != nullptr)
5920 : {
5921 3 : poLatLonSRS->SetAxisMappingStrategy(
5922 : OAMS_TRADITIONAL_GIS_ORDER);
5923 3 : poTransform.reset(OGRCreateCoordinateTransformation(
5924 3 : &m_oSRS, poLatLonSRS.get()));
5925 : }
5926 : // If no OGR transform, then don't write CF lon/lat.
5927 3 : if (poTransform == nullptr)
5928 : {
5929 0 : CPLError(CE_Failure, CPLE_AppDefined,
5930 : "Unable to get Coordinate Transform");
5931 0 : bWriteLonLat = false;
5932 : }
5933 : }
5934 :
5935 36 : if (bWriteLonLat)
5936 : {
5937 4 : if (!bHasGeoloc)
5938 3 : CPLDebug("GDAL_netCDF", "Transforming (X,Y)->(lon,lat)");
5939 : else
5940 1 : CPLDebug("GDAL_netCDF",
5941 : "Writing (lon,lat) from GEOLOCATION arrays");
5942 :
5943 4 : bool bOK = true;
5944 4 : double dfProgress = 0.2;
5945 :
5946 4 : size_t start[] = {0, 0};
5947 4 : size_t count[] = {1, (size_t)nRasterXSize};
5948 : std::unique_ptr<double, decltype(&VSIFree)> adLatValKeeper(
5949 : static_cast<double *>(
5950 8 : VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
5951 4 : VSIFree);
5952 : std::unique_ptr<double, decltype(&VSIFree)> adLonValKeeper(
5953 : static_cast<double *>(
5954 8 : VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
5955 4 : VSIFree);
5956 4 : double *padLonVal = adLonValKeeper.get();
5957 4 : double *padLatVal = adLatValKeeper.get();
5958 4 : if (!padLonVal || !padLatVal)
5959 : {
5960 0 : return CE_Failure;
5961 : }
5962 :
5963 103 : for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR;
5964 : j++)
5965 : {
5966 99 : start[0] = j;
5967 :
5968 : // Get values from geotransform.
5969 99 : if (!bHasGeoloc)
5970 : {
5971 : // Fill values to transform.
5972 60 : if (m_gt.IsAxisAligned())
5973 : {
5974 420 : for (int i = 0; i < nRasterXSize; i++)
5975 : {
5976 400 : padLatVal[i] = padYVal[j];
5977 400 : padLonVal[i] = padXVal[i];
5978 : }
5979 : }
5980 : else
5981 : {
5982 840 : for (int i = 0; i < nRasterXSize; i++)
5983 : {
5984 800 : if (!bBottomUp)
5985 : {
5986 400 : padLatVal[i] = m_gt.yorig +
5987 400 : (i + 0.5) * m_gt.yrot +
5988 400 : (j + 0.5) * m_gt.yscale;
5989 400 : padLonVal[i] = m_gt.xorig +
5990 400 : (i + 0.5) * m_gt.xscale +
5991 400 : (j + 0.5) * m_gt.xrot;
5992 : }
5993 : else
5994 : {
5995 400 : padLatVal[i] =
5996 400 : m_gt.yorig + (i + 0.5) * m_gt.yrot +
5997 400 : (nRasterYSize - j - 0.5) * m_gt.yscale;
5998 400 : padLonVal[i] =
5999 400 : m_gt.xorig + (i + 0.5) * m_gt.xscale +
6000 400 : (nRasterYSize - j - 0.5) * m_gt.xrot;
6001 : }
6002 : }
6003 : }
6004 :
6005 : // Do the transform.
6006 120 : bOK = CPL_TO_BOOL(poTransform->Transform(
6007 60 : nRasterXSize, padLonVal, padLatVal, nullptr));
6008 60 : if (!bOK)
6009 : {
6010 0 : CPLError(CE_Failure, CPLE_AppDefined,
6011 : "Unable to Transform (X,Y) to (lon,lat).");
6012 : }
6013 : }
6014 : // Get values from geoloc arrays.
6015 : else
6016 : {
6017 39 : CPLErr eErr = poBand_Y->RasterIO(
6018 : GF_Read, 0, j, nRasterXSize, 1, padLatVal,
6019 : nRasterXSize, 1, GDT_Float64, 0, 0, nullptr);
6020 39 : if (eErr == CE_None)
6021 : {
6022 39 : eErr = poBand_X->RasterIO(
6023 : GF_Read, 0, j, nRasterXSize, 1, padLonVal,
6024 : nRasterXSize, 1, GDT_Float64, 0, 0, nullptr);
6025 : }
6026 :
6027 39 : if (eErr == CE_None)
6028 : {
6029 39 : bOK = true;
6030 : }
6031 : else
6032 : {
6033 0 : bOK = false;
6034 0 : CPLError(CE_Failure, CPLE_AppDefined,
6035 : "Unable to get scanline %d", j);
6036 : }
6037 : }
6038 :
6039 : // Write data.
6040 99 : if (bOK)
6041 : {
6042 99 : status = nc_put_vara_double(cdfid, nVarLatID, start,
6043 : count, padLatVal);
6044 99 : NCDF_ERR(status);
6045 99 : status = nc_put_vara_double(cdfid, nVarLonID, start,
6046 : count, padLonVal);
6047 99 : NCDF_ERR(status);
6048 : }
6049 :
6050 99 : if (pfnProgress && (nRasterYSize / 10) > 0 &&
6051 99 : (j % (nRasterYSize / 10) == 0))
6052 : {
6053 43 : dfProgress += 0.08;
6054 43 : pfnProgress(dfProgress, nullptr, pProgressData);
6055 : }
6056 : }
6057 : }
6058 : } // Projected
6059 :
6060 : // If not projected/geographic and has geoloc
6061 57 : else if (!bIsGeographic && bHasGeoloc && m_gt.IsAxisAligned())
6062 : {
6063 : // Use
6064 : // https://cfconventions.org/Data/cf-conventions/cf-conventions-1.9/cf-conventions.html#_two_dimensional_latitude_longitude_coordinate_variables
6065 :
6066 4 : bool bOK = true;
6067 4 : double dfProgress = 0.2;
6068 :
6069 : // Make sure we are in data mode.
6070 4 : SetDefineMode(false);
6071 :
6072 : size_t startX[1];
6073 : size_t countX[1];
6074 : size_t startY[1];
6075 : size_t countY[1];
6076 4 : startX[0] = 0;
6077 4 : countX[0] = nRasterXSize;
6078 :
6079 4 : startY[0] = 0;
6080 4 : countY[0] = nRasterYSize;
6081 :
6082 4 : std::vector<double> adfXVal;
6083 4 : std::vector<double> adfYVal;
6084 : try
6085 : {
6086 4 : adfXVal.resize(nRasterXSize);
6087 4 : adfYVal.resize(nRasterYSize);
6088 : }
6089 0 : catch (const std::exception &)
6090 : {
6091 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
6092 : "Out of memory allocating temporary array");
6093 0 : return CE_Failure;
6094 : }
6095 16 : for (int i = 0; i < nRasterXSize; i++)
6096 12 : adfXVal[i] = i;
6097 12 : for (int i = 0; i < nRasterYSize; i++)
6098 8 : adfYVal[i] = bBottomUp ? nRasterYSize - 1 - i : i;
6099 :
6100 4 : CPLDebug("GDAL_netCDF", "Writing X values");
6101 4 : int status = nc_put_vara_double(cdfid, nVarXID, startX, countX,
6102 4 : adfXVal.data());
6103 4 : NCDF_ERR(status);
6104 :
6105 4 : CPLDebug("GDAL_netCDF", "Writing Y values");
6106 4 : status = nc_put_vara_double(cdfid, nVarYID, startY, countY,
6107 4 : adfYVal.data());
6108 4 : NCDF_ERR(status);
6109 :
6110 4 : if (pfnProgress)
6111 0 : pfnProgress(0.20, nullptr, pProgressData);
6112 :
6113 4 : size_t start[] = {0, 0};
6114 4 : size_t count[] = {1, (size_t)nRasterXSize};
6115 :
6116 : std::unique_ptr<double, decltype(&VSIFree)> adLatValKeeper(
6117 : static_cast<double *>(
6118 8 : VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
6119 4 : VSIFree);
6120 : std::unique_ptr<double, decltype(&VSIFree)> adLonValKeeper(
6121 : static_cast<double *>(
6122 8 : VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
6123 4 : VSIFree);
6124 4 : double *padLonVal = adLonValKeeper.get();
6125 4 : double *padLatVal = adLatValKeeper.get();
6126 4 : if (!padLonVal || !padLatVal)
6127 : {
6128 0 : return CE_Failure;
6129 : }
6130 :
6131 12 : for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR; j++)
6132 : {
6133 8 : start[0] = j;
6134 :
6135 8 : CPLErr eErr = poBand_Y->RasterIO(
6136 8 : GF_Read, 0, bBottomUp ? nRasterYSize - 1 - j : j,
6137 : nRasterXSize, 1, padLatVal, nRasterXSize, 1, GDT_Float64, 0,
6138 : 0, nullptr);
6139 8 : if (eErr == CE_None)
6140 : {
6141 8 : eErr = poBand_X->RasterIO(
6142 8 : GF_Read, 0, bBottomUp ? nRasterYSize - 1 - j : j,
6143 : nRasterXSize, 1, padLonVal, nRasterXSize, 1,
6144 : GDT_Float64, 0, 0, nullptr);
6145 : }
6146 :
6147 8 : if (eErr == CE_None)
6148 : {
6149 8 : bOK = true;
6150 : }
6151 : else
6152 : {
6153 0 : bOK = false;
6154 0 : CPLError(CE_Failure, CPLE_AppDefined,
6155 : "Unable to get scanline %d", j);
6156 : }
6157 :
6158 : // Write data.
6159 8 : if (bOK)
6160 : {
6161 8 : status = nc_put_vara_double(cdfid, nVarLatID, start, count,
6162 : padLatVal);
6163 8 : NCDF_ERR(status);
6164 8 : status = nc_put_vara_double(cdfid, nVarLonID, start, count,
6165 : padLonVal);
6166 8 : NCDF_ERR(status);
6167 : }
6168 :
6169 8 : if (pfnProgress && (nRasterYSize / 10) > 0 &&
6170 0 : (j % (nRasterYSize / 10) == 0))
6171 : {
6172 0 : dfProgress += 0.08;
6173 0 : pfnProgress(dfProgress, nullptr, pProgressData);
6174 : }
6175 : }
6176 : }
6177 :
6178 : // If not projected, assume geographic to catch grids without Datum.
6179 53 : else if (bWriteLonLat)
6180 : {
6181 : // Get latitude values.
6182 53 : const double dfY0 = (!bBottomUp) ? m_gt.yorig :
6183 : // Invert latitude values.
6184 53 : m_gt.yorig + (m_gt.yscale * nRasterYSize);
6185 53 : const double dfDY = m_gt.yscale;
6186 :
6187 : std::unique_ptr<double, decltype(&VSIFree)> adLatValKeeper(nullptr,
6188 53 : VSIFree);
6189 53 : double *padLatVal = nullptr;
6190 : // Override lat values with the ones in GEOLOCATION/Y_VALUES.
6191 53 : if (netCDFDataset::GetMetadataItem("Y_VALUES",
6192 53 : GDAL_MDD_GEOLOCATION) != nullptr)
6193 : {
6194 0 : int nTemp = 0;
6195 0 : adLatValKeeper.reset(Get1DGeolocation("Y_VALUES", nTemp));
6196 0 : padLatVal = adLatValKeeper.get();
6197 : // Make sure we got the correct amount, if not fallback to GT */
6198 : // could add test fabs(fabs(padLatVal[0]) - fabs(dfY0)) <= 0.1))
6199 0 : if (nTemp == nRasterYSize)
6200 : {
6201 0 : CPLDebug(
6202 : "GDAL_netCDF",
6203 : "Using Y_VALUES geolocation metadata for lat values");
6204 : }
6205 : else
6206 : {
6207 0 : CPLDebug("GDAL_netCDF",
6208 : "Got %d elements from Y_VALUES geolocation "
6209 : "metadata, need %d",
6210 : nTemp, nRasterYSize);
6211 0 : padLatVal = nullptr;
6212 : }
6213 : }
6214 :
6215 53 : if (padLatVal == nullptr)
6216 : {
6217 53 : adLatValKeeper.reset(static_cast<double *>(
6218 53 : VSI_MALLOC2_VERBOSE(nRasterYSize, sizeof(double))));
6219 53 : padLatVal = adLatValKeeper.get();
6220 53 : if (!padLatVal)
6221 : {
6222 0 : return CE_Failure;
6223 : }
6224 7105 : for (int i = 0; i < nRasterYSize; i++)
6225 : {
6226 : // The data point is centered inside the pixel.
6227 7052 : if (!bBottomUp)
6228 0 : padLatVal[i] = dfY0 + (i + 0.5) * dfDY;
6229 : else // Invert latitude values.
6230 7052 : padLatVal[i] = dfY0 - (i + 0.5) * dfDY;
6231 : }
6232 : }
6233 :
6234 53 : size_t startLat[1] = {0};
6235 53 : size_t countLat[1] = {static_cast<size_t>(nRasterYSize)};
6236 :
6237 : // Get longitude values.
6238 53 : const double dfX0 = m_gt.xorig;
6239 53 : const double dfDX = m_gt.xscale;
6240 :
6241 : std::unique_ptr<double, decltype(&VSIFree)> adLonValKeeper(
6242 : static_cast<double *>(
6243 106 : VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
6244 53 : VSIFree);
6245 53 : double *padLonVal = adLonValKeeper.get();
6246 53 : if (!padLonVal)
6247 : {
6248 0 : return CE_Failure;
6249 : }
6250 7157 : for (int i = 0; i < nRasterXSize; i++)
6251 : {
6252 : // The data point is centered inside the pixel.
6253 7104 : padLonVal[i] = dfX0 + (i + 0.5) * dfDX;
6254 : }
6255 :
6256 53 : size_t startLon[1] = {0};
6257 53 : size_t countLon[1] = {static_cast<size_t>(nRasterXSize)};
6258 :
6259 : // Write latitude and longitude values.
6260 :
6261 : // Make sure we are in data mode.
6262 53 : SetDefineMode(false);
6263 :
6264 : // Write values.
6265 53 : CPLDebug("GDAL_netCDF", "Writing lat values");
6266 :
6267 53 : int status = nc_put_vara_double(cdfid, nVarLatID, startLat,
6268 : countLat, padLatVal);
6269 53 : NCDF_ERR(status);
6270 :
6271 53 : CPLDebug("GDAL_netCDF", "Writing lon values");
6272 53 : status = nc_put_vara_double(cdfid, nVarLonID, startLon, countLon,
6273 : padLonVal);
6274 53 : NCDF_ERR(status);
6275 :
6276 : } // Not projected.
6277 :
6278 93 : if (pfnProgress)
6279 52 : pfnProgress(1.00, nullptr, pProgressData);
6280 : }
6281 :
6282 186 : return CE_None;
6283 : }
6284 :
6285 : // Write Projection variable to band variable.
6286 : // Moved from AddProjectionVars() for cases when bands are added after
6287 : // projection.
6288 488 : bool netCDFDataset::AddGridMappingRef()
6289 : {
6290 488 : bool bRet = true;
6291 488 : bool bOldDefineMode = bDefineMode;
6292 :
6293 701 : if ((GetAccess() == GA_Update) && (nBands >= 1) && (GetRasterBand(1)) &&
6294 213 : ((pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, "")) ||
6295 205 : (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))))
6296 : {
6297 84 : bAddedGridMappingRef = true;
6298 :
6299 : // Make sure we are in define mode.
6300 84 : SetDefineMode(true);
6301 :
6302 214 : for (int i = 1; i <= nBands; i++)
6303 : {
6304 : const int nVarId =
6305 130 : cpl::down_cast<netCDFRasterBand *>(GetRasterBand(i))->nZId;
6306 :
6307 130 : if (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))
6308 : {
6309 : int status =
6310 252 : nc_put_att_text(cdfid, nVarId, CF_GRD_MAPPING,
6311 126 : strlen(pszCFProjection), pszCFProjection);
6312 126 : NCDF_ERR(status);
6313 126 : if (status != NC_NOERR)
6314 0 : bRet = false;
6315 : }
6316 130 : if (pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, ""))
6317 : {
6318 : int status =
6319 8 : nc_put_att_text(cdfid, nVarId, CF_COORDINATES,
6320 : strlen(pszCFCoordinates), pszCFCoordinates);
6321 8 : NCDF_ERR(status);
6322 8 : if (status != NC_NOERR)
6323 0 : bRet = false;
6324 : }
6325 : }
6326 :
6327 : // Go back to previous define mode.
6328 84 : SetDefineMode(bOldDefineMode);
6329 : }
6330 488 : return bRet;
6331 : }
6332 :
6333 : /************************************************************************/
6334 : /* GetGeoTransform() */
6335 : /************************************************************************/
6336 :
6337 135 : CPLErr netCDFDataset::GetGeoTransform(GDALGeoTransform >) const
6338 :
6339 : {
6340 135 : gt = m_gt;
6341 135 : if (m_bHasGeoTransform)
6342 103 : return CE_None;
6343 :
6344 32 : return GDALPamDataset::GetGeoTransform(gt);
6345 : }
6346 :
6347 : /************************************************************************/
6348 : /* rint() */
6349 : /************************************************************************/
6350 :
6351 0 : double netCDFDataset::rint(double dfX)
6352 : {
6353 0 : return std::round(dfX);
6354 : }
6355 :
6356 : /************************************************************************/
6357 : /* NCDFReadIsoMetadata() */
6358 : /************************************************************************/
6359 :
6360 16 : static void NCDFReadMetadataAsJson(int cdfid, CPLJSONObject &obj)
6361 : {
6362 16 : int nbAttr = 0;
6363 16 : NCDF_ERR(nc_inq_varnatts(cdfid, NC_GLOBAL, &nbAttr));
6364 :
6365 32 : std::map<std::string, CPLJSONArray> oMapNameToArray;
6366 40 : for (int l = 0; l < nbAttr; l++)
6367 : {
6368 : char szAttrName[NC_MAX_NAME + 1];
6369 24 : szAttrName[0] = 0;
6370 24 : NCDF_ERR(nc_inq_attname(cdfid, NC_GLOBAL, l, szAttrName));
6371 :
6372 24 : char *pszMetaValue = nullptr;
6373 24 : if (NCDFGetAttr(cdfid, NC_GLOBAL, szAttrName, &pszMetaValue) == CE_None)
6374 : {
6375 24 : nc_type nAttrType = NC_NAT;
6376 24 : size_t nAttrLen = 0;
6377 :
6378 24 : NCDF_ERR(nc_inq_att(cdfid, NC_GLOBAL, szAttrName, &nAttrType,
6379 : &nAttrLen));
6380 :
6381 24 : std::string osAttrName(szAttrName);
6382 24 : const auto sharpPos = osAttrName.find('#');
6383 24 : if (sharpPos == std::string::npos)
6384 : {
6385 16 : if (nAttrType == NC_DOUBLE || nAttrType == NC_FLOAT)
6386 4 : obj.Add(osAttrName, CPLAtof(pszMetaValue));
6387 : else
6388 12 : obj.Add(osAttrName, pszMetaValue);
6389 : }
6390 : else
6391 : {
6392 8 : osAttrName.resize(sharpPos);
6393 8 : auto iter = oMapNameToArray.find(osAttrName);
6394 8 : if (iter == oMapNameToArray.end())
6395 : {
6396 8 : CPLJSONArray array;
6397 4 : obj.Add(osAttrName, array);
6398 4 : oMapNameToArray[osAttrName] = array;
6399 4 : array.Add(pszMetaValue);
6400 : }
6401 : else
6402 : {
6403 4 : iter->second.Add(pszMetaValue);
6404 : }
6405 : }
6406 24 : CPLFree(pszMetaValue);
6407 24 : pszMetaValue = nullptr;
6408 : }
6409 : }
6410 :
6411 16 : int nSubGroups = 0;
6412 16 : int *panSubGroupIds = nullptr;
6413 16 : NCDFGetSubGroups(cdfid, &nSubGroups, &panSubGroupIds);
6414 16 : oMapNameToArray.clear();
6415 28 : for (int i = 0; i < nSubGroups; i++)
6416 : {
6417 24 : CPLJSONObject subObj;
6418 12 : NCDFReadMetadataAsJson(panSubGroupIds[i], subObj);
6419 :
6420 24 : std::string osGroupName;
6421 12 : osGroupName.resize(NC_MAX_NAME);
6422 12 : NCDF_ERR(nc_inq_grpname(panSubGroupIds[i], &osGroupName[0]));
6423 12 : osGroupName.resize(strlen(osGroupName.data()));
6424 12 : const auto sharpPos = osGroupName.find('#');
6425 12 : if (sharpPos == std::string::npos)
6426 : {
6427 4 : obj.Add(osGroupName, subObj);
6428 : }
6429 : else
6430 : {
6431 8 : osGroupName.resize(sharpPos);
6432 8 : auto iter = oMapNameToArray.find(osGroupName);
6433 8 : if (iter == oMapNameToArray.end())
6434 : {
6435 8 : CPLJSONArray array;
6436 4 : obj.Add(osGroupName, array);
6437 4 : oMapNameToArray[osGroupName] = array;
6438 4 : array.Add(subObj);
6439 : }
6440 : else
6441 : {
6442 4 : iter->second.Add(subObj);
6443 : }
6444 : }
6445 : }
6446 16 : CPLFree(panSubGroupIds);
6447 16 : }
6448 :
6449 4 : std::string NCDFReadMetadataAsJson(int cdfid)
6450 : {
6451 8 : CPLJSONDocument oDoc;
6452 8 : CPLJSONObject oRoot = oDoc.GetRoot();
6453 4 : NCDFReadMetadataAsJson(cdfid, oRoot);
6454 8 : return oDoc.SaveAsString();
6455 : }
6456 :
6457 : /************************************************************************/
6458 : /* ReadAttributes() */
6459 : /************************************************************************/
6460 2050 : CPLErr netCDFDataset::ReadAttributes(int cdfidIn, int var)
6461 :
6462 : {
6463 4100 : std::string osVarFullName;
6464 2050 : ERR_RET(NCDFGetVarFullName(cdfidIn, var, osVarFullName));
6465 :
6466 : // For metadata in Sentinel 5
6467 2050 : if (cpl::starts_with(osVarFullName, "/METADATA/"))
6468 : {
6469 6 : for (const char *key :
6470 : {"ISO_METADATA", "ESA_METADATA", "EOP_METADATA", "QA_STATISTICS",
6471 8 : "GRANULE_DESCRIPTION", "ALGORITHM_SETTINGS"})
6472 : {
6473 14 : if (var == NC_GLOBAL &&
6474 14 : osVarFullName == CPLOPrintf("/METADATA/%s/NC_GLOBAL", key))
6475 : {
6476 1 : CPLStringList aosList;
6477 2 : aosList.AddString(CPLString(NCDFReadMetadataAsJson(cdfidIn))
6478 1 : .replaceAll("\\/", '/'));
6479 1 : m_oMapDomainToJSon[key] = std::move(aosList);
6480 1 : return CE_None;
6481 : }
6482 : }
6483 : }
6484 2049 : if (cpl::starts_with(osVarFullName, "/PRODUCT/SUPPORT_DATA/"))
6485 : {
6486 0 : CPLStringList aosList;
6487 : aosList.AddString(
6488 0 : CPLString(NCDFReadMetadataAsJson(cdfidIn)).replaceAll("\\/", '/'));
6489 0 : m_oMapDomainToJSon["SUPPORT_DATA"] = std::move(aosList);
6490 0 : return CE_None;
6491 : }
6492 :
6493 : size_t nMetaNameSize =
6494 2049 : sizeof(char) * (osVarFullName.size() + 1 + NC_MAX_NAME + 1);
6495 2049 : char *pszMetaName = static_cast<char *>(CPLMalloc(nMetaNameSize));
6496 :
6497 2049 : int nbAttr = 0;
6498 2049 : NCDF_ERR(nc_inq_varnatts(cdfidIn, var, &nbAttr));
6499 :
6500 10570 : for (int l = 0; l < nbAttr; l++)
6501 : {
6502 : char szAttrName[NC_MAX_NAME + 1];
6503 8521 : szAttrName[0] = 0;
6504 8521 : NCDF_ERR(nc_inq_attname(cdfidIn, var, l, szAttrName));
6505 8521 : snprintf(pszMetaName, nMetaNameSize, "%s#%s", osVarFullName.c_str(),
6506 : szAttrName);
6507 :
6508 8521 : char *pszMetaTemp = nullptr;
6509 8521 : if (NCDFGetAttr(cdfidIn, var, szAttrName, &pszMetaTemp) == CE_None)
6510 : {
6511 8520 : aosMetadata.SetNameValue(pszMetaName, pszMetaTemp);
6512 8520 : CPLFree(pszMetaTemp);
6513 8520 : pszMetaTemp = nullptr;
6514 : }
6515 : else
6516 : {
6517 1 : CPLDebug("GDAL_netCDF", "invalid metadata %s", pszMetaName);
6518 : }
6519 : }
6520 :
6521 2049 : CPLFree(pszMetaName);
6522 :
6523 2049 : if (var == NC_GLOBAL)
6524 : {
6525 : // Recurse on sub-groups.
6526 578 : int nSubGroups = 0;
6527 578 : int *panSubGroupIds = nullptr;
6528 578 : NCDFGetSubGroups(cdfidIn, &nSubGroups, &panSubGroupIds);
6529 611 : for (int i = 0; i < nSubGroups; i++)
6530 : {
6531 33 : ReadAttributes(panSubGroupIds[i], var);
6532 : }
6533 578 : CPLFree(panSubGroupIds);
6534 : }
6535 :
6536 2049 : return CE_None;
6537 : }
6538 :
6539 : /************************************************************************/
6540 : /* netCDFDataset::CreateSubDatasetList() */
6541 : /************************************************************************/
6542 61 : void netCDFDataset::CreateSubDatasetList(int nGroupId)
6543 : {
6544 122 : std::string osVarStdName;
6545 61 : int *ponDimIds = nullptr;
6546 :
6547 61 : netCDFDataset *poDS = this;
6548 :
6549 : int nVarCount;
6550 61 : nc_inq_nvars(nGroupId, &nVarCount);
6551 :
6552 61 : const bool bListAllArrays = CPLTestBool(
6553 61 : CSLFetchNameValueDef(papszOpenOptions, "LIST_ALL_ARRAYS", "NO"));
6554 :
6555 366 : for (int nVar = 0; nVar < nVarCount; nVar++)
6556 : {
6557 :
6558 : int nDims;
6559 305 : nc_inq_varndims(nGroupId, nVar, &nDims);
6560 :
6561 305 : if ((bListAllArrays && nDims > 0) || nDims >= 2)
6562 : {
6563 177 : ponDimIds = static_cast<int *>(CPLCalloc(nDims, sizeof(int)));
6564 177 : nc_inq_vardimid(nGroupId, nVar, ponDimIds);
6565 :
6566 : // Create Sub dataset list.
6567 177 : CPLString osDim;
6568 545 : for (int i = 0; i < nDims; i++)
6569 : {
6570 : size_t nDimLen;
6571 368 : nc_inq_dimlen(nGroupId, ponDimIds[i], &nDimLen);
6572 368 : if (!osDim.empty())
6573 191 : osDim += 'x';
6574 368 : osDim += CPLSPrintf("%d", (int)nDimLen);
6575 : }
6576 177 : CPLFree(ponDimIds);
6577 :
6578 : nc_type nVarType;
6579 177 : nc_inq_vartype(nGroupId, nVar, &nVarType);
6580 177 : const char *pszType = "";
6581 177 : switch (nVarType)
6582 : {
6583 42 : case NC_BYTE:
6584 42 : pszType = "8-bit integer";
6585 42 : break;
6586 2 : case NC_CHAR:
6587 2 : pszType = "8-bit character";
6588 2 : break;
6589 6 : case NC_SHORT:
6590 6 : pszType = "16-bit integer";
6591 6 : break;
6592 10 : case NC_INT:
6593 10 : pszType = "32-bit integer";
6594 10 : break;
6595 62 : case NC_FLOAT:
6596 62 : pszType = "32-bit floating-point";
6597 62 : break;
6598 34 : case NC_DOUBLE:
6599 34 : pszType = "64-bit floating-point";
6600 34 : break;
6601 4 : case NC_UBYTE:
6602 4 : pszType = "8-bit unsigned integer";
6603 4 : break;
6604 1 : case NC_USHORT:
6605 1 : pszType = "16-bit unsigned integer";
6606 1 : break;
6607 1 : case NC_UINT:
6608 1 : pszType = "32-bit unsigned integer";
6609 1 : break;
6610 1 : case NC_INT64:
6611 1 : pszType = "64-bit integer";
6612 1 : break;
6613 1 : case NC_UINT64:
6614 1 : pszType = "64-bit unsigned integer";
6615 1 : break;
6616 13 : default:
6617 13 : break;
6618 : }
6619 :
6620 177 : std::string osVarName;
6621 177 : if (NCDFGetVarFullName(nGroupId, nVar, osVarName) != CE_None)
6622 0 : continue;
6623 :
6624 177 : nSubDatasets++;
6625 :
6626 177 : if (NCDFGetAttr(nGroupId, nVar, CF_STD_NAME, osVarStdName) !=
6627 : CE_None)
6628 : {
6629 113 : osVarStdName = osVarName;
6630 : }
6631 :
6632 : const std::string osSubDatasetName =
6633 354 : CPLOPrintf("SUBDATASET_%d_NAME", nSubDatasets);
6634 :
6635 354 : if (osVarName.find(' ') != std::string::npos ||
6636 177 : osVarName.find(':') != std::string::npos)
6637 : {
6638 : poDS->aosSubDatasets.SetNameValue(
6639 : osSubDatasetName.c_str(),
6640 : CPLSPrintf("NETCDF:\"%s\":\"%s\"", poDS->osFilename.c_str(),
6641 1 : osVarName.c_str()));
6642 : }
6643 : else
6644 : {
6645 : poDS->aosSubDatasets.SetNameValue(
6646 : osSubDatasetName.c_str(),
6647 : CPLSPrintf("NETCDF:\"%s\":%s", poDS->osFilename.c_str(),
6648 176 : osVarName.c_str()));
6649 : }
6650 :
6651 : const std::string osSubDatasetDesc =
6652 354 : CPLOPrintf("SUBDATASET_%d_DESC", nSubDatasets);
6653 :
6654 : poDS->aosSubDatasets.SetNameValue(
6655 : osSubDatasetDesc.c_str(),
6656 : CPLSPrintf("[%s] %s (%s)", osDim.c_str(), osVarStdName.c_str(),
6657 177 : pszType));
6658 : }
6659 : }
6660 :
6661 : // Recurse on sub groups.
6662 61 : int nSubGroups = 0;
6663 61 : int *panSubGroupIds = nullptr;
6664 61 : NCDFGetSubGroups(nGroupId, &nSubGroups, &panSubGroupIds);
6665 69 : for (int i = 0; i < nSubGroups; i++)
6666 : {
6667 8 : CreateSubDatasetList(panSubGroupIds[i]);
6668 : }
6669 61 : CPLFree(panSubGroupIds);
6670 61 : }
6671 :
6672 : /************************************************************************/
6673 : /* TestCapability() */
6674 : /************************************************************************/
6675 :
6676 244 : int netCDFDataset::TestCapability(const char *pszCap) const
6677 : {
6678 244 : if (EQUAL(pszCap, ODsCCreateLayer))
6679 : {
6680 221 : return eAccess == GA_Update && nBands == 0 &&
6681 215 : (eMultipleLayerBehavior != SINGLE_LAYER ||
6682 226 : this->GetLayerCount() == 0 || bSGSupport);
6683 : }
6684 133 : else if (EQUAL(pszCap, ODsCZGeometries))
6685 2 : return true;
6686 :
6687 131 : return false;
6688 : }
6689 :
6690 : /************************************************************************/
6691 : /* GetLayer() */
6692 : /************************************************************************/
6693 :
6694 441 : const OGRLayer *netCDFDataset::GetLayer(int nIdx) const
6695 : {
6696 441 : if (nIdx < 0 || nIdx >= this->GetLayerCount())
6697 2 : return nullptr;
6698 439 : return papoLayers[nIdx].get();
6699 : }
6700 :
6701 : /************************************************************************/
6702 : /* ICreateLayer() */
6703 : /************************************************************************/
6704 :
6705 59 : OGRLayer *netCDFDataset::ICreateLayer(const char *pszName,
6706 : const OGRGeomFieldDefn *poGeomFieldDefn,
6707 : CSLConstList papszOptions)
6708 : {
6709 59 : int nLayerCDFId = cdfid;
6710 59 : if (!TestCapability(ODsCCreateLayer))
6711 0 : return nullptr;
6712 :
6713 59 : const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
6714 : const auto poSpatialRef =
6715 59 : poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
6716 :
6717 118 : CPLString osNetCDFLayerName(pszName);
6718 59 : const netCDFWriterConfigLayer *poLayerConfig = nullptr;
6719 59 : if (oWriterConfig.m_bIsValid)
6720 : {
6721 : std::map<CPLString, netCDFWriterConfigLayer>::const_iterator
6722 3 : oLayerIter = oWriterConfig.m_oLayers.find(pszName);
6723 3 : if (oLayerIter != oWriterConfig.m_oLayers.end())
6724 : {
6725 1 : poLayerConfig = &(oLayerIter->second);
6726 1 : osNetCDFLayerName = poLayerConfig->m_osNetCDFName;
6727 : }
6728 : }
6729 :
6730 59 : netCDFDataset *poLayerDataset = nullptr;
6731 59 : if (eMultipleLayerBehavior == SEPARATE_FILES)
6732 : {
6733 3 : if (CPLLaunderForFilenameSafe(osNetCDFLayerName.c_str(), nullptr) !=
6734 : osNetCDFLayerName)
6735 : {
6736 1 : CPLError(CE_Failure, CPLE_AppDefined,
6737 : "Illegal characters in '%s' to form a valid filename",
6738 : osNetCDFLayerName.c_str());
6739 1 : return nullptr;
6740 : }
6741 2 : CPLStringList aosDatasetOptions;
6742 : aosDatasetOptions.SetNameValue(
6743 2 : "CONFIG_FILE", aosCreationOptions.FetchNameValue("CONFIG_FILE"));
6744 : aosDatasetOptions.SetNameValue(
6745 2 : "FORMAT", aosCreationOptions.FetchNameValue("FORMAT"));
6746 : aosDatasetOptions.SetNameValue(
6747 : "WRITE_GDAL_TAGS",
6748 2 : aosCreationOptions.FetchNameValue("WRITE_GDAL_TAGS"));
6749 : const CPLString osLayerFilename(
6750 2 : CPLFormFilenameSafe(osFilename, osNetCDFLayerName, "nc"));
6751 2 : CPLAcquireMutex(hNCMutex, 1000.0);
6752 2 : poLayerDataset =
6753 2 : CreateLL(osLayerFilename, 0, 0, 0, aosDatasetOptions.List());
6754 2 : CPLReleaseMutex(hNCMutex);
6755 2 : if (poLayerDataset == nullptr)
6756 0 : return nullptr;
6757 :
6758 2 : nLayerCDFId = poLayerDataset->cdfid;
6759 2 : NCDFAddGDALHistory(nLayerCDFId, osLayerFilename, bWriteGDALVersion,
6760 2 : bWriteGDALHistory, "", "Create",
6761 : NCDF_CONVENTIONS_CF_V1_6);
6762 : }
6763 56 : else if (eMultipleLayerBehavior == SEPARATE_GROUPS)
6764 : {
6765 2 : SetDefineMode(true);
6766 :
6767 2 : nLayerCDFId = -1;
6768 2 : int status = nc_def_grp(cdfid, osNetCDFLayerName, &nLayerCDFId);
6769 2 : NCDF_ERR(status);
6770 2 : if (status != NC_NOERR)
6771 0 : return nullptr;
6772 :
6773 2 : NCDFAddGDALHistory(nLayerCDFId, osFilename, bWriteGDALVersion,
6774 2 : bWriteGDALHistory, "", "Create",
6775 : NCDF_CONVENTIONS_CF_V1_6);
6776 : }
6777 :
6778 : // Make a clone to workaround a bug in released MapServer versions
6779 : // that destroys the passed SRS instead of releasing it .
6780 58 : OGRSpatialReference *poSRS = nullptr;
6781 58 : if (poSpatialRef)
6782 : {
6783 43 : poSRS = poSpatialRef->Clone();
6784 43 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
6785 : }
6786 : std::shared_ptr<netCDFLayer> poLayer(
6787 58 : new netCDFLayer(poLayerDataset ? poLayerDataset : this, nLayerCDFId,
6788 116 : osNetCDFLayerName, eGType, poSRS));
6789 58 : if (poSRS != nullptr)
6790 43 : poSRS->Release();
6791 :
6792 : // Fetch layer creation options coming from config file
6793 116 : CPLStringList aosNewOptions(CSLDuplicate(papszOptions));
6794 58 : if (oWriterConfig.m_bIsValid)
6795 : {
6796 2 : for (const auto &[osName, osValue] :
6797 5 : oWriterConfig.m_oLayerCreationOptions)
6798 : {
6799 1 : aosNewOptions.SetNameValue(osName, osValue);
6800 : }
6801 3 : if (poLayerConfig != nullptr)
6802 : {
6803 4 : for (const auto &[osName, osValue] :
6804 5 : poLayerConfig->m_oLayerCreationOptions)
6805 : {
6806 2 : aosNewOptions.SetNameValue(osName, osValue);
6807 : }
6808 : }
6809 : }
6810 :
6811 58 : const bool bRet = poLayer->Create(aosNewOptions.List(), poLayerConfig);
6812 :
6813 58 : if (!bRet)
6814 : {
6815 0 : return nullptr;
6816 : }
6817 :
6818 58 : if (poLayerDataset != nullptr)
6819 2 : apoVectorDatasets.push_back(poLayerDataset);
6820 :
6821 58 : papoLayers.push_back(poLayer);
6822 58 : return poLayer.get();
6823 : }
6824 :
6825 : /************************************************************************/
6826 : /* CloneAttributes() */
6827 : /************************************************************************/
6828 :
6829 137 : bool netCDFDataset::CloneAttributes(int old_cdfid, int new_cdfid, int nSrcVarId,
6830 : int nDstVarId)
6831 : {
6832 137 : int nAttCount = -1;
6833 137 : int status = nc_inq_varnatts(old_cdfid, nSrcVarId, &nAttCount);
6834 137 : NCDF_ERR(status);
6835 :
6836 693 : for (int i = 0; i < nAttCount; i++)
6837 : {
6838 : char szName[NC_MAX_NAME + 1];
6839 556 : szName[0] = 0;
6840 556 : status = nc_inq_attname(old_cdfid, nSrcVarId, i, szName);
6841 556 : NCDF_ERR(status);
6842 :
6843 : status =
6844 556 : nc_copy_att(old_cdfid, nSrcVarId, szName, new_cdfid, nDstVarId);
6845 556 : NCDF_ERR(status);
6846 556 : if (status != NC_NOERR)
6847 0 : return false;
6848 : }
6849 :
6850 137 : return true;
6851 : }
6852 :
6853 : /************************************************************************/
6854 : /* CloneVariableContent() */
6855 : /************************************************************************/
6856 :
6857 121 : bool netCDFDataset::CloneVariableContent(int old_cdfid, int new_cdfid,
6858 : int nSrcVarId, int nDstVarId)
6859 : {
6860 121 : int nVarDimCount = -1;
6861 121 : int status = nc_inq_varndims(old_cdfid, nSrcVarId, &nVarDimCount);
6862 121 : NCDF_ERR(status);
6863 121 : int anDimIds[] = {-1, 1};
6864 121 : status = nc_inq_vardimid(old_cdfid, nSrcVarId, anDimIds);
6865 121 : NCDF_ERR(status);
6866 121 : nc_type nc_datatype = NC_NAT;
6867 121 : status = nc_inq_vartype(old_cdfid, nSrcVarId, &nc_datatype);
6868 121 : NCDF_ERR(status);
6869 121 : size_t nTypeSize = 0;
6870 121 : switch (nc_datatype)
6871 : {
6872 35 : case NC_BYTE:
6873 : case NC_CHAR:
6874 35 : nTypeSize = 1;
6875 35 : break;
6876 4 : case NC_SHORT:
6877 4 : nTypeSize = 2;
6878 4 : break;
6879 24 : case NC_INT:
6880 24 : nTypeSize = 4;
6881 24 : break;
6882 4 : case NC_FLOAT:
6883 4 : nTypeSize = 4;
6884 4 : break;
6885 43 : case NC_DOUBLE:
6886 43 : nTypeSize = 8;
6887 43 : break;
6888 2 : case NC_UBYTE:
6889 2 : nTypeSize = 1;
6890 2 : break;
6891 2 : case NC_USHORT:
6892 2 : nTypeSize = 2;
6893 2 : break;
6894 2 : case NC_UINT:
6895 2 : nTypeSize = 4;
6896 2 : break;
6897 4 : case NC_INT64:
6898 : case NC_UINT64:
6899 4 : nTypeSize = 8;
6900 4 : break;
6901 1 : case NC_STRING:
6902 1 : nTypeSize = sizeof(char *);
6903 1 : break;
6904 0 : default:
6905 : {
6906 0 : CPLError(CE_Failure, CPLE_NotSupported, "Unsupported data type: %d",
6907 : nc_datatype);
6908 0 : return false;
6909 : }
6910 : }
6911 :
6912 121 : size_t nElems = 1;
6913 : size_t anStart[NC_MAX_DIMS];
6914 : size_t anCount[NC_MAX_DIMS];
6915 121 : size_t nRecords = 1;
6916 261 : for (int i = 0; i < nVarDimCount; i++)
6917 : {
6918 140 : anStart[i] = 0;
6919 140 : if (i == 0)
6920 : {
6921 116 : anCount[i] = 1;
6922 116 : status = nc_inq_dimlen(old_cdfid, anDimIds[i], &nRecords);
6923 116 : NCDF_ERR(status);
6924 : }
6925 : else
6926 : {
6927 24 : anCount[i] = 0;
6928 24 : status = nc_inq_dimlen(old_cdfid, anDimIds[i], &anCount[i]);
6929 24 : NCDF_ERR(status);
6930 24 : nElems *= anCount[i];
6931 : }
6932 : }
6933 :
6934 : /* Workaround in some cases a netCDF bug:
6935 : * https://github.com/Unidata/netcdf-c/pull/1442 */
6936 121 : if (nRecords > 0 && nRecords < 10 * 1000 * 1000 / (nElems * nTypeSize))
6937 : {
6938 119 : nElems *= nRecords;
6939 119 : anCount[0] = nRecords;
6940 119 : nRecords = 1;
6941 : }
6942 :
6943 121 : void *pBuffer = VSI_MALLOC2_VERBOSE(nElems, nTypeSize);
6944 121 : if (pBuffer == nullptr)
6945 0 : return false;
6946 :
6947 240 : for (size_t iRecord = 0; iRecord < nRecords; iRecord++)
6948 : {
6949 119 : anStart[0] = iRecord;
6950 :
6951 119 : switch (nc_datatype)
6952 : {
6953 5 : case NC_BYTE:
6954 : status =
6955 5 : nc_get_vara_schar(old_cdfid, nSrcVarId, anStart, anCount,
6956 : static_cast<signed char *>(pBuffer));
6957 5 : if (!status)
6958 5 : status = nc_put_vara_schar(
6959 : new_cdfid, nDstVarId, anStart, anCount,
6960 : static_cast<signed char *>(pBuffer));
6961 5 : break;
6962 28 : case NC_CHAR:
6963 : status =
6964 28 : nc_get_vara_text(old_cdfid, nSrcVarId, anStart, anCount,
6965 : static_cast<char *>(pBuffer));
6966 28 : if (!status)
6967 : status =
6968 28 : nc_put_vara_text(new_cdfid, nDstVarId, anStart, anCount,
6969 : static_cast<char *>(pBuffer));
6970 28 : break;
6971 4 : case NC_SHORT:
6972 : status =
6973 4 : nc_get_vara_short(old_cdfid, nSrcVarId, anStart, anCount,
6974 : static_cast<short *>(pBuffer));
6975 4 : if (!status)
6976 4 : status = nc_put_vara_short(new_cdfid, nDstVarId, anStart,
6977 : anCount,
6978 : static_cast<short *>(pBuffer));
6979 4 : break;
6980 24 : case NC_INT:
6981 24 : status = nc_get_vara_int(old_cdfid, nSrcVarId, anStart, anCount,
6982 : static_cast<int *>(pBuffer));
6983 24 : if (!status)
6984 : status =
6985 24 : nc_put_vara_int(new_cdfid, nDstVarId, anStart, anCount,
6986 : static_cast<int *>(pBuffer));
6987 24 : break;
6988 4 : case NC_FLOAT:
6989 : status =
6990 4 : nc_get_vara_float(old_cdfid, nSrcVarId, anStart, anCount,
6991 : static_cast<float *>(pBuffer));
6992 4 : if (!status)
6993 4 : status = nc_put_vara_float(new_cdfid, nDstVarId, anStart,
6994 : anCount,
6995 : static_cast<float *>(pBuffer));
6996 4 : break;
6997 43 : case NC_DOUBLE:
6998 : status =
6999 43 : nc_get_vara_double(old_cdfid, nSrcVarId, anStart, anCount,
7000 : static_cast<double *>(pBuffer));
7001 43 : if (!status)
7002 43 : status = nc_put_vara_double(new_cdfid, nDstVarId, anStart,
7003 : anCount,
7004 : static_cast<double *>(pBuffer));
7005 43 : break;
7006 1 : case NC_STRING:
7007 : status =
7008 1 : nc_get_vara_string(old_cdfid, nSrcVarId, anStart, anCount,
7009 : static_cast<char **>(pBuffer));
7010 1 : if (!status)
7011 : {
7012 1 : status = nc_put_vara_string(
7013 : new_cdfid, nDstVarId, anStart, anCount,
7014 : static_cast<const char **>(pBuffer));
7015 1 : nc_free_string(nElems, static_cast<char **>(pBuffer));
7016 : }
7017 1 : break;
7018 :
7019 2 : case NC_UBYTE:
7020 : status =
7021 2 : nc_get_vara_uchar(old_cdfid, nSrcVarId, anStart, anCount,
7022 : static_cast<unsigned char *>(pBuffer));
7023 2 : if (!status)
7024 2 : status = nc_put_vara_uchar(
7025 : new_cdfid, nDstVarId, anStart, anCount,
7026 : static_cast<unsigned char *>(pBuffer));
7027 2 : break;
7028 2 : case NC_USHORT:
7029 : status =
7030 2 : nc_get_vara_ushort(old_cdfid, nSrcVarId, anStart, anCount,
7031 : static_cast<unsigned short *>(pBuffer));
7032 2 : if (!status)
7033 2 : status = nc_put_vara_ushort(
7034 : new_cdfid, nDstVarId, anStart, anCount,
7035 : static_cast<unsigned short *>(pBuffer));
7036 2 : break;
7037 2 : case NC_UINT:
7038 : status =
7039 2 : nc_get_vara_uint(old_cdfid, nSrcVarId, anStart, anCount,
7040 : static_cast<unsigned int *>(pBuffer));
7041 2 : if (!status)
7042 : status =
7043 2 : nc_put_vara_uint(new_cdfid, nDstVarId, anStart, anCount,
7044 : static_cast<unsigned int *>(pBuffer));
7045 2 : break;
7046 2 : case NC_INT64:
7047 : status =
7048 2 : nc_get_vara_longlong(old_cdfid, nSrcVarId, anStart, anCount,
7049 : static_cast<long long *>(pBuffer));
7050 2 : if (!status)
7051 2 : status = nc_put_vara_longlong(
7052 : new_cdfid, nDstVarId, anStart, anCount,
7053 : static_cast<long long *>(pBuffer));
7054 2 : break;
7055 2 : case NC_UINT64:
7056 2 : status = nc_get_vara_ulonglong(
7057 : old_cdfid, nSrcVarId, anStart, anCount,
7058 : static_cast<unsigned long long *>(pBuffer));
7059 2 : if (!status)
7060 2 : status = nc_put_vara_ulonglong(
7061 : new_cdfid, nDstVarId, anStart, anCount,
7062 : static_cast<unsigned long long *>(pBuffer));
7063 2 : break;
7064 0 : default:
7065 0 : status = NC_EBADTYPE;
7066 : }
7067 :
7068 119 : NCDF_ERR(status);
7069 119 : if (status != NC_NOERR)
7070 : {
7071 0 : VSIFree(pBuffer);
7072 0 : return false;
7073 : }
7074 : }
7075 :
7076 121 : VSIFree(pBuffer);
7077 121 : return true;
7078 : }
7079 :
7080 : /************************************************************************/
7081 : /* NCDFIsUnlimitedDim() */
7082 : /************************************************************************/
7083 :
7084 80 : bool NCDFIsUnlimitedDim(bool bIsNC4, int cdfid, int nDimId)
7085 : {
7086 80 : if (bIsNC4)
7087 : {
7088 16 : int nUnlimitedDims = 0;
7089 16 : nc_inq_unlimdims(cdfid, &nUnlimitedDims, nullptr);
7090 16 : bool bFound = false;
7091 16 : if (nUnlimitedDims)
7092 : {
7093 : int *panUnlimitedDimIds =
7094 16 : static_cast<int *>(CPLMalloc(sizeof(int) * nUnlimitedDims));
7095 16 : nc_inq_unlimdims(cdfid, nullptr, panUnlimitedDimIds);
7096 30 : for (int i = 0; i < nUnlimitedDims; i++)
7097 : {
7098 22 : if (panUnlimitedDimIds[i] == nDimId)
7099 : {
7100 8 : bFound = true;
7101 8 : break;
7102 : }
7103 : }
7104 16 : CPLFree(panUnlimitedDimIds);
7105 : }
7106 16 : return bFound;
7107 : }
7108 : else
7109 : {
7110 64 : int nUnlimitedDimId = -1;
7111 64 : nc_inq(cdfid, nullptr, nullptr, nullptr, &nUnlimitedDimId);
7112 64 : return nDimId == nUnlimitedDimId;
7113 : }
7114 : }
7115 :
7116 : /************************************************************************/
7117 : /* CloneGrp() */
7118 : /************************************************************************/
7119 :
7120 16 : bool netCDFDataset::CloneGrp(int nOldGrpId, int nNewGrpId, bool bIsNC4,
7121 : int nLayerId, int nDimIdToGrow, size_t nNewSize)
7122 : {
7123 : // Clone dimensions
7124 16 : int nDimCount = -1;
7125 16 : int status = nc_inq_ndims(nOldGrpId, &nDimCount);
7126 16 : NCDF_ERR(status);
7127 16 : if (nDimCount < 0 || nDimCount > NC_MAX_DIMS)
7128 0 : return false;
7129 : int anDimIds[NC_MAX_DIMS];
7130 16 : int nUnlimiDimID = -1;
7131 16 : status = nc_inq_unlimdim(nOldGrpId, &nUnlimiDimID);
7132 16 : NCDF_ERR(status);
7133 16 : if (bIsNC4)
7134 : {
7135 : // In NC4, the dimension ids of a group are not necessarily in
7136 : // [0,nDimCount-1] range
7137 8 : int nDimCount2 = -1;
7138 8 : status = nc_inq_dimids(nOldGrpId, &nDimCount2, anDimIds, FALSE);
7139 8 : NCDF_ERR(status);
7140 8 : CPLAssert(nDimCount == nDimCount2);
7141 : }
7142 : else
7143 : {
7144 36 : for (int i = 0; i < nDimCount; i++)
7145 28 : anDimIds[i] = i;
7146 : }
7147 60 : for (int i = 0; i < nDimCount; i++)
7148 : {
7149 : char szDimName[NC_MAX_NAME + 1];
7150 44 : szDimName[0] = 0;
7151 44 : size_t nLen = 0;
7152 44 : const int nDimId = anDimIds[i];
7153 44 : status = nc_inq_dim(nOldGrpId, nDimId, szDimName, &nLen);
7154 44 : NCDF_ERR(status);
7155 44 : if (NCDFIsUnlimitedDim(bIsNC4, nOldGrpId, nDimId))
7156 16 : nLen = NC_UNLIMITED;
7157 28 : else if (nDimId == nDimIdToGrow && nOldGrpId == nLayerId)
7158 13 : nLen = nNewSize;
7159 44 : int nNewDimId = -1;
7160 44 : status = nc_def_dim(nNewGrpId, szDimName, nLen, &nNewDimId);
7161 44 : NCDF_ERR(status);
7162 44 : CPLAssert(nDimId == nNewDimId);
7163 44 : if (status != NC_NOERR)
7164 : {
7165 0 : return false;
7166 : }
7167 : }
7168 :
7169 : // Clone main attributes
7170 16 : if (!CloneAttributes(nOldGrpId, nNewGrpId, NC_GLOBAL, NC_GLOBAL))
7171 : {
7172 0 : return false;
7173 : }
7174 :
7175 : // Clone variable definitions
7176 16 : int nVarCount = -1;
7177 16 : status = nc_inq_nvars(nOldGrpId, &nVarCount);
7178 16 : NCDF_ERR(status);
7179 :
7180 137 : for (int i = 0; i < nVarCount; i++)
7181 : {
7182 : char szVarName[NC_MAX_NAME + 1];
7183 121 : szVarName[0] = 0;
7184 121 : status = nc_inq_varname(nOldGrpId, i, szVarName);
7185 121 : NCDF_ERR(status);
7186 121 : nc_type nc_datatype = NC_NAT;
7187 121 : status = nc_inq_vartype(nOldGrpId, i, &nc_datatype);
7188 121 : NCDF_ERR(status);
7189 121 : int nVarDimCount = -1;
7190 121 : status = nc_inq_varndims(nOldGrpId, i, &nVarDimCount);
7191 121 : NCDF_ERR(status);
7192 121 : status = nc_inq_vardimid(nOldGrpId, i, anDimIds);
7193 121 : NCDF_ERR(status);
7194 121 : int nNewVarId = -1;
7195 121 : status = nc_def_var(nNewGrpId, szVarName, nc_datatype, nVarDimCount,
7196 : anDimIds, &nNewVarId);
7197 121 : NCDF_ERR(status);
7198 121 : CPLAssert(i == nNewVarId);
7199 121 : if (status != NC_NOERR)
7200 : {
7201 0 : return false;
7202 : }
7203 :
7204 121 : if (!CloneAttributes(nOldGrpId, nNewGrpId, i, i))
7205 : {
7206 0 : return false;
7207 : }
7208 : }
7209 :
7210 16 : status = nc_enddef(nNewGrpId);
7211 16 : NCDF_ERR(status);
7212 16 : if (status != NC_NOERR)
7213 : {
7214 0 : return false;
7215 : }
7216 :
7217 : // Clone variable content
7218 137 : for (int i = 0; i < nVarCount; i++)
7219 : {
7220 121 : if (!CloneVariableContent(nOldGrpId, nNewGrpId, i, i))
7221 : {
7222 0 : return false;
7223 : }
7224 : }
7225 :
7226 16 : return true;
7227 : }
7228 :
7229 : /************************************************************************/
7230 : /* GrowDim() */
7231 : /************************************************************************/
7232 :
7233 13 : bool netCDFDataset::GrowDim(int nLayerId, int nDimIdToGrow, size_t nNewSize)
7234 : {
7235 : int nCreationMode;
7236 : // Set nCreationMode based on eFormat.
7237 13 : switch (eFormat)
7238 : {
7239 : #ifdef NETCDF_HAS_NC2
7240 0 : case NCDF_FORMAT_NC2:
7241 0 : nCreationMode = NC_CLOBBER | NC_64BIT_OFFSET;
7242 0 : break;
7243 : #endif
7244 5 : case NCDF_FORMAT_NC4:
7245 5 : nCreationMode = NC_CLOBBER | NC_NETCDF4;
7246 5 : break;
7247 0 : case NCDF_FORMAT_NC4C:
7248 0 : nCreationMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
7249 0 : break;
7250 8 : case NCDF_FORMAT_NC:
7251 : default:
7252 8 : nCreationMode = NC_CLOBBER;
7253 8 : break;
7254 : }
7255 :
7256 13 : int new_cdfid = -1;
7257 26 : CPLString osTmpFilename(osFilename + ".tmp");
7258 26 : CPLString osFilenameForNCCreate(osTmpFilename);
7259 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
7260 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
7261 : {
7262 : char *pszTemp =
7263 : CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
7264 : osFilenameForNCCreate = pszTemp;
7265 : CPLFree(pszTemp);
7266 : }
7267 : #endif
7268 13 : int status = nc_create(osFilenameForNCCreate, nCreationMode, &new_cdfid);
7269 13 : NCDF_ERR(status);
7270 13 : if (status != NC_NOERR)
7271 0 : return false;
7272 :
7273 13 : if (!CloneGrp(cdfid, new_cdfid, eFormat == NCDF_FORMAT_NC4, nLayerId,
7274 : nDimIdToGrow, nNewSize))
7275 : {
7276 0 : GDAL_nc_close(new_cdfid);
7277 0 : return false;
7278 : }
7279 :
7280 13 : int nGroupCount = 0;
7281 26 : std::vector<CPLString> oListGrpName;
7282 31 : if (eFormat == NCDF_FORMAT_NC4 &&
7283 18 : nc_inq_grps(cdfid, &nGroupCount, nullptr) == NC_NOERR &&
7284 5 : nGroupCount > 0)
7285 : {
7286 : int *panGroupIds =
7287 2 : static_cast<int *>(CPLMalloc(sizeof(int) * nGroupCount));
7288 2 : status = nc_inq_grps(cdfid, nullptr, panGroupIds);
7289 2 : NCDF_ERR(status);
7290 5 : for (int i = 0; i < nGroupCount; i++)
7291 : {
7292 : char szGroupName[NC_MAX_NAME + 1];
7293 3 : szGroupName[0] = 0;
7294 3 : NCDF_ERR(nc_inq_grpname(panGroupIds[i], szGroupName));
7295 3 : int nNewGrpId = -1;
7296 3 : status = nc_def_grp(new_cdfid, szGroupName, &nNewGrpId);
7297 3 : NCDF_ERR(status);
7298 3 : if (status != NC_NOERR)
7299 : {
7300 0 : CPLFree(panGroupIds);
7301 0 : GDAL_nc_close(new_cdfid);
7302 0 : return false;
7303 : }
7304 3 : if (!CloneGrp(panGroupIds[i], nNewGrpId, /*bIsNC4=*/true, nLayerId,
7305 : nDimIdToGrow, nNewSize))
7306 : {
7307 0 : CPLFree(panGroupIds);
7308 0 : GDAL_nc_close(new_cdfid);
7309 0 : return false;
7310 : }
7311 : }
7312 2 : CPLFree(panGroupIds);
7313 :
7314 5 : for (int i = 0; i < this->GetLayerCount(); i++)
7315 : {
7316 3 : auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
7317 3 : if (poLayer)
7318 : {
7319 : char szGroupName[NC_MAX_NAME + 1];
7320 3 : szGroupName[0] = 0;
7321 3 : status = nc_inq_grpname(poLayer->GetCDFID(), szGroupName);
7322 3 : NCDF_ERR(status);
7323 3 : oListGrpName.push_back(szGroupName);
7324 : }
7325 : }
7326 : }
7327 :
7328 13 : GDAL_nc_close(cdfid);
7329 13 : cdfid = -1;
7330 13 : GDAL_nc_close(new_cdfid);
7331 :
7332 26 : CPLString osOriFilename(osFilename + ".ori");
7333 26 : if (VSIRename(osFilename, osOriFilename) != 0 ||
7334 13 : VSIRename(osTmpFilename, osFilename) != 0)
7335 : {
7336 0 : CPLError(CE_Failure, CPLE_FileIO, "Renaming of files failed");
7337 0 : return false;
7338 : }
7339 13 : VSIUnlink(osOriFilename);
7340 :
7341 26 : CPLString osFilenameForNCOpen(osFilename);
7342 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
7343 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
7344 : {
7345 : char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
7346 : osFilenameForNCOpen = pszTemp;
7347 : CPLFree(pszTemp);
7348 : }
7349 : #endif
7350 13 : status = GDAL_nc_open(osFilenameForNCOpen, NC_WRITE, &cdfid);
7351 13 : NCDF_ERR(status);
7352 13 : if (status != NC_NOERR)
7353 0 : return false;
7354 13 : bDefineMode = false;
7355 :
7356 13 : if (!oListGrpName.empty())
7357 : {
7358 5 : for (int i = 0; i < this->GetLayerCount(); i++)
7359 : {
7360 3 : auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
7361 3 : if (poLayer)
7362 : {
7363 3 : int nNewLayerCDFID = -1;
7364 3 : status = nc_inq_ncid(cdfid, oListGrpName[i].c_str(),
7365 : &nNewLayerCDFID);
7366 3 : NCDF_ERR(status);
7367 3 : poLayer->SetCDFID(nNewLayerCDFID);
7368 : }
7369 : }
7370 : }
7371 : else
7372 : {
7373 22 : for (int i = 0; i < this->GetLayerCount(); i++)
7374 : {
7375 11 : auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
7376 11 : if (poLayer)
7377 11 : poLayer->SetCDFID(cdfid);
7378 : }
7379 : }
7380 :
7381 13 : return true;
7382 : }
7383 :
7384 : #ifdef ENABLE_NCDUMP
7385 :
7386 : /************************************************************************/
7387 : /* netCDFDatasetCreateTempFile() */
7388 : /************************************************************************/
7389 :
7390 : /* Create a netCDF file from a text dump (format of ncdump) */
7391 : /* Mostly to easy fuzzing of the driver, while still generating valid */
7392 : /* netCDF files. */
7393 : /* Note: not all data types are supported ! */
7394 4 : bool netCDFDatasetCreateTempFile(NetCDFFormatEnum eFormat,
7395 : const char *pszTmpFilename, VSILFILE *fpSrc)
7396 : {
7397 4 : CPL_IGNORE_RET_VAL(eFormat);
7398 4 : int nCreateMode = NC_CLOBBER;
7399 4 : if (eFormat == NCDF_FORMAT_NC4)
7400 1 : nCreateMode |= NC_NETCDF4;
7401 3 : else if (eFormat == NCDF_FORMAT_NC4C)
7402 0 : nCreateMode |= NC_NETCDF4 | NC_CLASSIC_MODEL;
7403 4 : int nCdfId = -1;
7404 4 : int status = nc_create(pszTmpFilename, nCreateMode, &nCdfId);
7405 4 : if (status != NC_NOERR)
7406 : {
7407 0 : return false;
7408 : }
7409 4 : VSIFSeekL(fpSrc, 0, SEEK_SET);
7410 : const char *pszLine;
7411 4 : constexpr int SECTION_NONE = 0;
7412 4 : constexpr int SECTION_DIMENSIONS = 1;
7413 4 : constexpr int SECTION_VARIABLES = 2;
7414 4 : constexpr int SECTION_DATA = 3;
7415 4 : int nActiveSection = SECTION_NONE;
7416 8 : std::map<CPLString, int> oMapDimToId;
7417 8 : std::map<int, int> oMapDimIdToDimLen;
7418 8 : std::map<CPLString, int> oMapVarToId;
7419 8 : std::map<int, std::vector<int>> oMapVarIdToVectorOfDimId;
7420 8 : std::map<int, int> oMapVarIdToType;
7421 4 : std::set<CPLString> oSetAttrDefined;
7422 4 : oMapVarToId[""] = -1;
7423 4 : size_t nTotalVarSize = 0;
7424 208 : while ((pszLine = CPLReadLineL(fpSrc)) != nullptr)
7425 : {
7426 204 : if (STARTS_WITH(pszLine, "dimensions:") &&
7427 : nActiveSection == SECTION_NONE)
7428 : {
7429 4 : nActiveSection = SECTION_DIMENSIONS;
7430 : }
7431 200 : else if (STARTS_WITH(pszLine, "variables:") &&
7432 : nActiveSection == SECTION_DIMENSIONS)
7433 : {
7434 4 : nActiveSection = SECTION_VARIABLES;
7435 : }
7436 196 : else if (STARTS_WITH(pszLine, "data:") &&
7437 : nActiveSection == SECTION_VARIABLES)
7438 : {
7439 4 : nActiveSection = SECTION_DATA;
7440 4 : status = nc_enddef(nCdfId);
7441 4 : if (status != NC_NOERR)
7442 : {
7443 0 : CPLDebug("netCDF", "nc_enddef() failed: %s",
7444 : nc_strerror(status));
7445 : }
7446 : }
7447 192 : else if (nActiveSection == SECTION_DIMENSIONS)
7448 : {
7449 : const CPLStringList aosTokens(
7450 9 : CSLTokenizeString2(pszLine, " \t=;", 0));
7451 9 : if (aosTokens.size() == 2)
7452 : {
7453 9 : const char *pszDimName = aosTokens[0];
7454 9 : bool bValidName = true;
7455 9 : if (STARTS_WITH(pszDimName, "_nc4_non_coord_"))
7456 : {
7457 : // This is an internal netcdf prefix. Using it may
7458 : // cause memory leaks.
7459 0 : bValidName = false;
7460 : }
7461 9 : if (!bValidName)
7462 : {
7463 0 : CPLDebug("netCDF",
7464 : "nc_def_dim(%s) failed: invalid name found",
7465 : pszDimName);
7466 0 : continue;
7467 : }
7468 :
7469 : const bool bIsASCII =
7470 9 : CPLIsASCII(pszDimName, static_cast<size_t>(-1));
7471 9 : if (!bIsASCII)
7472 : {
7473 : // Workaround https://github.com/Unidata/netcdf-c/pull/450
7474 0 : CPLDebug("netCDF",
7475 : "nc_def_dim(%s) failed: rejected because "
7476 : "of non-ASCII characters",
7477 : pszDimName);
7478 0 : continue;
7479 : }
7480 9 : int nDimSize = EQUAL(aosTokens[1], "UNLIMITED")
7481 : ? NC_UNLIMITED
7482 9 : : atoi(aosTokens[1]);
7483 9 : if (nDimSize >= 1000)
7484 1 : nDimSize = 1000; // to avoid very long processing
7485 9 : if (nDimSize >= 0)
7486 : {
7487 9 : int nDimId = -1;
7488 9 : status = nc_def_dim(nCdfId, pszDimName, nDimSize, &nDimId);
7489 9 : if (status != NC_NOERR)
7490 : {
7491 0 : CPLDebug("netCDF", "nc_def_dim(%s, %d) failed: %s",
7492 : pszDimName, nDimSize, nc_strerror(status));
7493 : }
7494 : else
7495 : {
7496 : #ifdef DEBUG_VERBOSE
7497 : CPLDebug("netCDF", "nc_def_dim(%s, %d) (%s) succeeded",
7498 : pszDimName, nDimSize, pszLine);
7499 : #endif
7500 9 : oMapDimToId[pszDimName] = nDimId;
7501 9 : oMapDimIdToDimLen[nDimId] = nDimSize;
7502 : }
7503 : }
7504 : }
7505 : }
7506 183 : else if (nActiveSection == SECTION_VARIABLES)
7507 : {
7508 390 : while (*pszLine == ' ' || *pszLine == '\t')
7509 249 : pszLine++;
7510 141 : const char *pszColumn = strchr(pszLine, ':');
7511 141 : const char *pszEqual = strchr(pszLine, '=');
7512 141 : if (pszColumn == nullptr)
7513 : {
7514 : const CPLStringList aosTokens(
7515 21 : CSLTokenizeString2(pszLine, " \t=(),;", 0));
7516 21 : if (aosTokens.size() >= 2)
7517 : {
7518 17 : const char *pszVarName = aosTokens[1];
7519 17 : bool bValidName = true;
7520 17 : if (STARTS_WITH(pszVarName, "_nc4_non_coord_"))
7521 : {
7522 : // This is an internal netcdf prefix. Using it may
7523 : // cause memory leaks.
7524 0 : bValidName = false;
7525 : }
7526 138 : for (int i = 0; pszVarName[i]; i++)
7527 : {
7528 121 : if (!((pszVarName[i] >= 'a' && pszVarName[i] <= 'z') ||
7529 28 : (pszVarName[i] >= 'A' && pszVarName[i] <= 'Z') ||
7530 9 : (pszVarName[i] >= '0' && pszVarName[i] <= '9') ||
7531 6 : pszVarName[i] == '_'))
7532 : {
7533 0 : bValidName = false;
7534 : }
7535 : }
7536 17 : if (!bValidName)
7537 : {
7538 0 : CPLDebug(
7539 : "netCDF",
7540 : "nc_def_var(%s) failed: illegal character found",
7541 : pszVarName);
7542 0 : continue;
7543 : }
7544 17 : if (oMapVarToId.find(pszVarName) != oMapVarToId.end())
7545 : {
7546 0 : CPLDebug("netCDF",
7547 : "nc_def_var(%s) failed: already defined",
7548 : pszVarName);
7549 0 : continue;
7550 : }
7551 17 : const char *pszVarType = aosTokens[0];
7552 17 : int nc_datatype = NC_BYTE;
7553 17 : size_t nDataTypeSize = 1;
7554 17 : if (EQUAL(pszVarType, "char"))
7555 : {
7556 6 : nc_datatype = NC_CHAR;
7557 6 : nDataTypeSize = 1;
7558 : }
7559 11 : else if (EQUAL(pszVarType, "byte"))
7560 : {
7561 3 : nc_datatype = NC_BYTE;
7562 3 : nDataTypeSize = 1;
7563 : }
7564 8 : else if (EQUAL(pszVarType, "short"))
7565 : {
7566 0 : nc_datatype = NC_SHORT;
7567 0 : nDataTypeSize = 2;
7568 : }
7569 8 : else if (EQUAL(pszVarType, "int"))
7570 : {
7571 0 : nc_datatype = NC_INT;
7572 0 : nDataTypeSize = 4;
7573 : }
7574 8 : else if (EQUAL(pszVarType, "float"))
7575 : {
7576 0 : nc_datatype = NC_FLOAT;
7577 0 : nDataTypeSize = 4;
7578 : }
7579 8 : else if (EQUAL(pszVarType, "double"))
7580 : {
7581 8 : nc_datatype = NC_DOUBLE;
7582 8 : nDataTypeSize = 8;
7583 : }
7584 0 : else if (EQUAL(pszVarType, "ubyte"))
7585 : {
7586 0 : nc_datatype = NC_UBYTE;
7587 0 : nDataTypeSize = 1;
7588 : }
7589 0 : else if (EQUAL(pszVarType, "ushort"))
7590 : {
7591 0 : nc_datatype = NC_USHORT;
7592 0 : nDataTypeSize = 2;
7593 : }
7594 0 : else if (EQUAL(pszVarType, "uint"))
7595 : {
7596 0 : nc_datatype = NC_UINT;
7597 0 : nDataTypeSize = 4;
7598 : }
7599 0 : else if (EQUAL(pszVarType, "int64"))
7600 : {
7601 0 : nc_datatype = NC_INT64;
7602 0 : nDataTypeSize = 8;
7603 : }
7604 0 : else if (EQUAL(pszVarType, "uint64"))
7605 : {
7606 0 : nc_datatype = NC_UINT64;
7607 0 : nDataTypeSize = 8;
7608 : }
7609 :
7610 17 : int nDims = aosTokens.size() - 2;
7611 17 : if (nDims >= 32)
7612 : {
7613 : // The number of dimensions in a netCDFv4 file is
7614 : // limited by #define H5S_MAX_RANK 32
7615 : // but libnetcdf doesn't check that...
7616 0 : CPLDebug("netCDF",
7617 : "nc_def_var(%s) failed: too many dimensions",
7618 : pszVarName);
7619 0 : continue;
7620 : }
7621 17 : std::vector<int> aoDimIds;
7622 17 : bool bFailed = false;
7623 17 : size_t nSize = 1;
7624 35 : for (int i = 0; i < nDims; i++)
7625 : {
7626 18 : const char *pszDimName = aosTokens[2 + i];
7627 18 : if (oMapDimToId.find(pszDimName) == oMapDimToId.end())
7628 : {
7629 0 : bFailed = true;
7630 0 : break;
7631 : }
7632 18 : const int nDimId = oMapDimToId[pszDimName];
7633 18 : aoDimIds.push_back(nDimId);
7634 :
7635 18 : const size_t nDimSize = oMapDimIdToDimLen[nDimId];
7636 18 : if (nDimSize != 0)
7637 : {
7638 18 : if (nSize >
7639 18 : std::numeric_limits<size_t>::max() / nDimSize)
7640 : {
7641 0 : bFailed = true;
7642 0 : break;
7643 : }
7644 : else
7645 : {
7646 18 : nSize *= nDimSize;
7647 : }
7648 : }
7649 : }
7650 17 : if (bFailed)
7651 : {
7652 0 : CPLDebug("netCDF",
7653 : "nc_def_var(%s) failed: unknown dimension(s)",
7654 : pszVarName);
7655 0 : continue;
7656 : }
7657 17 : if (nSize > 100U * 1024 * 1024 / nDataTypeSize)
7658 : {
7659 0 : CPLDebug("netCDF",
7660 : "nc_def_var(%s) failed: too large data",
7661 : pszVarName);
7662 0 : continue;
7663 : }
7664 17 : if (nTotalVarSize >
7665 34 : std::numeric_limits<size_t>::max() - nSize ||
7666 17 : nTotalVarSize + nSize > 100 * 1024 * 1024)
7667 : {
7668 0 : CPLDebug("netCDF",
7669 : "nc_def_var(%s) failed: too large data",
7670 : pszVarName);
7671 0 : continue;
7672 : }
7673 17 : nTotalVarSize += nSize;
7674 :
7675 17 : int nVarId = -1;
7676 : status =
7677 30 : nc_def_var(nCdfId, pszVarName, nc_datatype, nDims,
7678 13 : (nDims) ? &aoDimIds[0] : nullptr, &nVarId);
7679 17 : if (status != NC_NOERR)
7680 : {
7681 0 : CPLDebug("netCDF", "nc_def_var(%s) failed: %s",
7682 : pszVarName, nc_strerror(status));
7683 : }
7684 : else
7685 : {
7686 : #ifdef DEBUG_VERBOSE
7687 : CPLDebug("netCDF", "nc_def_var(%s) (%s) succeeded",
7688 : pszVarName, pszLine);
7689 : #endif
7690 17 : oMapVarToId[pszVarName] = nVarId;
7691 17 : oMapVarIdToType[nVarId] = nc_datatype;
7692 17 : oMapVarIdToVectorOfDimId[nVarId] = std::move(aoDimIds);
7693 : }
7694 : }
7695 : }
7696 120 : else if (pszEqual != nullptr && pszEqual - pszColumn > 0)
7697 : {
7698 116 : CPLString osVarName(pszLine, pszColumn - pszLine);
7699 116 : CPLString osAttrName(pszColumn + 1, pszEqual - pszColumn - 1);
7700 116 : osAttrName.Trim();
7701 116 : if (oMapVarToId.find(osVarName) == oMapVarToId.end())
7702 : {
7703 0 : CPLDebug("netCDF",
7704 : "nc_put_att(%s:%s) failed: "
7705 : "no corresponding variable",
7706 : osVarName.c_str(), osAttrName.c_str());
7707 0 : continue;
7708 : }
7709 116 : bool bValidName = true;
7710 1743 : for (size_t i = 0; i < osAttrName.size(); i++)
7711 : {
7712 1865 : if (!((osAttrName[i] >= 'a' && osAttrName[i] <= 'z') ||
7713 238 : (osAttrName[i] >= 'A' && osAttrName[i] <= 'Z') ||
7714 158 : (osAttrName[i] >= '0' && osAttrName[i] <= '9') ||
7715 158 : osAttrName[i] == '_'))
7716 : {
7717 0 : bValidName = false;
7718 : }
7719 : }
7720 116 : if (!bValidName)
7721 : {
7722 0 : CPLDebug(
7723 : "netCDF",
7724 : "nc_put_att(%s:%s) failed: illegal character found",
7725 : osVarName.c_str(), osAttrName.c_str());
7726 0 : continue;
7727 : }
7728 116 : if (oSetAttrDefined.find(osVarName + ":" + osAttrName) !=
7729 232 : oSetAttrDefined.end())
7730 : {
7731 0 : CPLDebug("netCDF",
7732 : "nc_put_att(%s:%s) failed: already defined",
7733 : osVarName.c_str(), osAttrName.c_str());
7734 0 : continue;
7735 : }
7736 :
7737 116 : const int nVarId = oMapVarToId[osVarName];
7738 116 : const char *pszValue = pszEqual + 1;
7739 232 : while (*pszValue == ' ')
7740 116 : pszValue++;
7741 :
7742 116 : status = NC_EBADTYPE;
7743 116 : if (*pszValue == '"')
7744 : {
7745 : // For _FillValue, the attribute type should match
7746 : // the variable type. Leaks memory with NC4 otherwise
7747 74 : if (osAttrName == "_FillValue")
7748 : {
7749 0 : CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
7750 : osVarName.c_str(), osAttrName.c_str(),
7751 : nc_strerror(status));
7752 0 : continue;
7753 : }
7754 :
7755 : // Unquote and unescape string value
7756 74 : CPLString osVal(pszValue + 1);
7757 222 : while (!osVal.empty())
7758 : {
7759 222 : if (osVal.back() == ';' || osVal.back() == ' ')
7760 : {
7761 148 : osVal.pop_back();
7762 : }
7763 74 : else if (osVal.back() == '"')
7764 : {
7765 74 : osVal.pop_back();
7766 74 : break;
7767 : }
7768 : else
7769 : {
7770 0 : break;
7771 : }
7772 : }
7773 74 : osVal.replaceAll("\\\"", '"');
7774 74 : status = nc_put_att_text(nCdfId, nVarId, osAttrName,
7775 : osVal.size(), osVal.c_str());
7776 : }
7777 : else
7778 : {
7779 84 : CPLString osVal(pszValue);
7780 126 : while (!osVal.empty())
7781 : {
7782 126 : if (osVal.back() == ';' || osVal.back() == ' ')
7783 : {
7784 84 : osVal.pop_back();
7785 : }
7786 : else
7787 : {
7788 42 : break;
7789 : }
7790 : }
7791 42 : int nc_datatype = -1;
7792 42 : if (!osVal.empty() && osVal.back() == 'b')
7793 : {
7794 3 : nc_datatype = NC_BYTE;
7795 3 : osVal.pop_back();
7796 : }
7797 39 : else if (!osVal.empty() && osVal.back() == 's')
7798 : {
7799 3 : nc_datatype = NC_SHORT;
7800 3 : osVal.pop_back();
7801 : }
7802 42 : if (CPLGetValueType(osVal) == CPL_VALUE_INTEGER)
7803 : {
7804 7 : if (nc_datatype < 0)
7805 4 : nc_datatype = NC_INT;
7806 : }
7807 35 : else if (CPLGetValueType(osVal) == CPL_VALUE_REAL)
7808 : {
7809 32 : nc_datatype = NC_DOUBLE;
7810 : }
7811 : else
7812 : {
7813 3 : nc_datatype = -1;
7814 : }
7815 :
7816 : // For _FillValue, check that the attribute type matches
7817 : // the variable type. Leaks memory with NC4 otherwise
7818 42 : if (osAttrName == "_FillValue")
7819 : {
7820 6 : if (nVarId < 0 ||
7821 3 : nc_datatype != oMapVarIdToType[nVarId])
7822 : {
7823 0 : nc_datatype = -1;
7824 : }
7825 : }
7826 :
7827 42 : if (nc_datatype == NC_BYTE)
7828 : {
7829 : signed char chVal =
7830 3 : static_cast<signed char>(atoi(osVal));
7831 3 : status = nc_put_att_schar(nCdfId, nVarId, osAttrName,
7832 : NC_BYTE, 1, &chVal);
7833 : }
7834 39 : else if (nc_datatype == NC_SHORT)
7835 : {
7836 0 : short nVal = static_cast<short>(atoi(osVal));
7837 0 : status = nc_put_att_short(nCdfId, nVarId, osAttrName,
7838 : NC_SHORT, 1, &nVal);
7839 : }
7840 39 : else if (nc_datatype == NC_INT)
7841 : {
7842 4 : int nVal = static_cast<int>(atoi(osVal));
7843 4 : status = nc_put_att_int(nCdfId, nVarId, osAttrName,
7844 : NC_INT, 1, &nVal);
7845 : }
7846 35 : else if (nc_datatype == NC_DOUBLE)
7847 : {
7848 32 : double dfVal = CPLAtof(osVal);
7849 32 : status = nc_put_att_double(nCdfId, nVarId, osAttrName,
7850 : NC_DOUBLE, 1, &dfVal);
7851 : }
7852 : }
7853 116 : if (status != NC_NOERR)
7854 : {
7855 3 : CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
7856 : osVarName.c_str(), osAttrName.c_str(),
7857 : nc_strerror(status));
7858 : }
7859 : else
7860 : {
7861 113 : oSetAttrDefined.insert(osVarName + ":" + osAttrName);
7862 : #ifdef DEBUG_VERBOSE
7863 : CPLDebug("netCDF", "nc_put_att_(%s:%s) (%s) succeeded",
7864 : osVarName.c_str(), osAttrName.c_str(), pszLine);
7865 : #endif
7866 : }
7867 : }
7868 : }
7869 42 : else if (nActiveSection == SECTION_DATA)
7870 : {
7871 55 : while (*pszLine == ' ' || *pszLine == '\t')
7872 17 : pszLine++;
7873 38 : const char *pszEqual = strchr(pszLine, '=');
7874 38 : if (pszEqual)
7875 : {
7876 17 : CPLString osVarName(pszLine, pszEqual - pszLine);
7877 17 : osVarName.Trim();
7878 17 : if (oMapVarToId.find(osVarName) == oMapVarToId.end())
7879 0 : continue;
7880 17 : const int nVarId = oMapVarToId[osVarName];
7881 17 : CPLString osAccVal(pszEqual + 1);
7882 17 : osAccVal.Trim();
7883 153 : while (osAccVal.empty() || osAccVal.back() != ';')
7884 : {
7885 136 : pszLine = CPLReadLineL(fpSrc);
7886 136 : if (pszLine == nullptr)
7887 0 : break;
7888 272 : CPLString osVal(pszLine);
7889 136 : osVal.Trim();
7890 136 : osAccVal += osVal;
7891 : }
7892 17 : if (pszLine == nullptr)
7893 0 : break;
7894 17 : osAccVal.pop_back();
7895 :
7896 : const std::vector<int> aoDimIds =
7897 34 : oMapVarIdToVectorOfDimId[nVarId];
7898 17 : size_t nSize = 1;
7899 34 : std::vector<size_t> aoStart, aoEdge;
7900 17 : aoStart.resize(aoDimIds.size());
7901 17 : aoEdge.resize(aoDimIds.size());
7902 35 : for (size_t i = 0; i < aoDimIds.size(); ++i)
7903 : {
7904 18 : const size_t nDimSize = oMapDimIdToDimLen[aoDimIds[i]];
7905 36 : if (nDimSize != 0 &&
7906 18 : nSize > std::numeric_limits<size_t>::max() / nDimSize)
7907 : {
7908 0 : nSize = 0;
7909 : }
7910 : else
7911 : {
7912 18 : nSize *= nDimSize;
7913 : }
7914 18 : aoStart[i] = 0;
7915 18 : aoEdge[i] = nDimSize;
7916 : }
7917 :
7918 17 : status = NC_EBADTYPE;
7919 17 : if (nSize == 0)
7920 : {
7921 : // Might happen with an unlimited dimension
7922 : }
7923 17 : else if (oMapVarIdToType[nVarId] == NC_DOUBLE)
7924 : {
7925 8 : if (!aoStart.empty())
7926 : {
7927 : const CPLStringList aosTokens(
7928 16 : CSLTokenizeString2(osAccVal, " ,;", 0));
7929 8 : size_t nTokens = aosTokens.size();
7930 8 : if (nTokens >= nSize)
7931 : {
7932 : double *padfVals = static_cast<double *>(
7933 8 : VSI_CALLOC_VERBOSE(nSize, sizeof(double)));
7934 8 : if (padfVals)
7935 : {
7936 132 : for (size_t i = 0; i < nSize; i++)
7937 : {
7938 124 : padfVals[i] = CPLAtof(aosTokens[i]);
7939 : }
7940 8 : status = nc_put_vara_double(
7941 8 : nCdfId, nVarId, &aoStart[0], &aoEdge[0],
7942 : padfVals);
7943 8 : VSIFree(padfVals);
7944 : }
7945 : }
7946 : }
7947 : }
7948 9 : else if (oMapVarIdToType[nVarId] == NC_BYTE)
7949 : {
7950 3 : if (!aoStart.empty())
7951 : {
7952 : const CPLStringList aosTokens(
7953 6 : CSLTokenizeString2(osAccVal, " ,;", 0));
7954 3 : size_t nTokens = aosTokens.size();
7955 3 : if (nTokens >= nSize)
7956 : {
7957 : signed char *panVals = static_cast<signed char *>(
7958 3 : VSI_CALLOC_VERBOSE(nSize, sizeof(signed char)));
7959 3 : if (panVals)
7960 : {
7961 1203 : for (size_t i = 0; i < nSize; i++)
7962 : {
7963 1200 : panVals[i] = static_cast<signed char>(
7964 1200 : atoi(aosTokens[i]));
7965 : }
7966 3 : status = nc_put_vara_schar(nCdfId, nVarId,
7967 3 : &aoStart[0],
7968 3 : &aoEdge[0], panVals);
7969 3 : VSIFree(panVals);
7970 : }
7971 : }
7972 : }
7973 : }
7974 6 : else if (oMapVarIdToType[nVarId] == NC_CHAR)
7975 : {
7976 6 : if (aoStart.size() == 2)
7977 : {
7978 4 : std::vector<CPLString> aoStrings;
7979 2 : bool bInString = false;
7980 4 : CPLString osCurString;
7981 935 : for (size_t i = 0; i < osAccVal.size();)
7982 : {
7983 933 : if (!bInString)
7984 : {
7985 8 : if (osAccVal[i] == '"')
7986 : {
7987 4 : bInString = true;
7988 4 : osCurString.clear();
7989 : }
7990 8 : i++;
7991 : }
7992 926 : else if (osAccVal[i] == '\\' &&
7993 926 : i + 1 < osAccVal.size() &&
7994 1 : osAccVal[i + 1] == '"')
7995 : {
7996 1 : osCurString += '"';
7997 1 : i += 2;
7998 : }
7999 924 : else if (osAccVal[i] == '"')
8000 : {
8001 4 : aoStrings.push_back(osCurString);
8002 4 : osCurString.clear();
8003 4 : bInString = false;
8004 4 : i++;
8005 : }
8006 : else
8007 : {
8008 920 : osCurString += osAccVal[i];
8009 920 : i++;
8010 : }
8011 : }
8012 2 : const size_t nRecords = oMapDimIdToDimLen[aoDimIds[0]];
8013 2 : const size_t nWidth = oMapDimIdToDimLen[aoDimIds[1]];
8014 2 : size_t nIters = aoStrings.size();
8015 2 : if (nIters > nRecords)
8016 0 : nIters = nRecords;
8017 6 : for (size_t i = 0; i < nIters; i++)
8018 : {
8019 : size_t anIndex[2];
8020 4 : anIndex[0] = i;
8021 4 : anIndex[1] = 0;
8022 : size_t anCount[2];
8023 4 : anCount[0] = 1;
8024 4 : anCount[1] = aoStrings[i].size();
8025 4 : if (anCount[1] > nWidth)
8026 0 : anCount[1] = nWidth;
8027 : status =
8028 4 : nc_put_vara_text(nCdfId, nVarId, anIndex,
8029 4 : anCount, aoStrings[i].c_str());
8030 4 : if (status != NC_NOERR)
8031 0 : break;
8032 : }
8033 : }
8034 : }
8035 17 : if (status != NC_NOERR)
8036 : {
8037 4 : CPLDebug("netCDF", "nc_put_var_(%s) failed: %s",
8038 : osVarName.c_str(), nc_strerror(status));
8039 : }
8040 : }
8041 : }
8042 : }
8043 :
8044 4 : GDAL_nc_close(nCdfId);
8045 4 : return true;
8046 : }
8047 :
8048 : #endif // ENABLE_NCDUMP
8049 :
8050 : /************************************************************************/
8051 : /* Open() */
8052 : /************************************************************************/
8053 :
8054 865 : GDALDataset *netCDFDataset::Open(GDALOpenInfo *poOpenInfo)
8055 :
8056 : {
8057 : #ifdef NCDF_DEBUG
8058 : CPLDebug("GDAL_netCDF", "\n=====\nOpen(), filename=[%s]",
8059 : poOpenInfo->pszFilename);
8060 : #endif
8061 :
8062 : // Does this appear to be a netcdf file?
8063 865 : NetCDFFormatEnum eTmpFormat = NCDF_FORMAT_NONE;
8064 865 : if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
8065 : {
8066 801 : eTmpFormat = netCDFIdentifyFormat(poOpenInfo, /* bCheckExt = */ true);
8067 : #ifdef NCDF_DEBUG
8068 : CPLDebug("GDAL_netCDF", "identified format %d", eTmpFormat);
8069 : #endif
8070 : // Note: not calling Identify() directly, because we want the file type.
8071 : // Only support NCDF_FORMAT* formats.
8072 801 : if (NCDF_FORMAT_NC == eTmpFormat || NCDF_FORMAT_NC2 == eTmpFormat ||
8073 2 : NCDF_FORMAT_NC4 == eTmpFormat || NCDF_FORMAT_NC4C == eTmpFormat)
8074 : {
8075 : // ok
8076 : }
8077 2 : else if (eTmpFormat == NCDF_FORMAT_HDF4 &&
8078 0 : poOpenInfo->IsSingleAllowedDriver("netCDF"))
8079 : {
8080 : // ok
8081 : }
8082 : else
8083 : {
8084 2 : return nullptr;
8085 : }
8086 : }
8087 : else
8088 : {
8089 : #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
8090 : // We don't necessarily want to catch bugs in libnetcdf ...
8091 : if (CPLGetConfigOption("DISABLE_OPEN_REAL_NETCDF_FILES", nullptr))
8092 : {
8093 : return nullptr;
8094 : }
8095 : #endif
8096 : }
8097 :
8098 863 : if (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER)
8099 : {
8100 317 : return OpenMultiDim(poOpenInfo);
8101 : }
8102 :
8103 1092 : CPLMutexHolderD(&hNCMutex);
8104 :
8105 546 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
8106 : // GDALDataset own mutex.
8107 546 : netCDFDataset *poDS = new netCDFDataset();
8108 546 : poDS->papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
8109 546 : CPLAcquireMutex(hNCMutex, 1000.0);
8110 :
8111 546 : poDS->SetDescription(poOpenInfo->pszFilename);
8112 :
8113 : // Check if filename start with NETCDF: tag.
8114 546 : bool bTreatAsSubdataset = false;
8115 1092 : CPLString osSubdatasetName;
8116 :
8117 : #ifdef ENABLE_NCDUMP
8118 546 : const char *pszHeader =
8119 : reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
8120 546 : if (poOpenInfo->fpL != nullptr && STARTS_WITH(pszHeader, "netcdf ") &&
8121 3 : strstr(pszHeader, "dimensions:") && strstr(pszHeader, "variables:"))
8122 : {
8123 : // By default create a temporary file that will be destroyed,
8124 : // unless NETCDF_TMP_FILE is defined. Can be useful to see which
8125 : // netCDF file has been generated from a potential fuzzed input.
8126 3 : poDS->osFilename = CPLGetConfigOption("NETCDF_TMP_FILE", "");
8127 3 : if (poDS->osFilename.empty())
8128 : {
8129 3 : poDS->bFileToDestroyAtClosing = true;
8130 3 : poDS->osFilename = CPLGenerateTempFilenameSafe("netcdf_tmp");
8131 : }
8132 3 : if (!netCDFDatasetCreateTempFile(eTmpFormat, poDS->osFilename,
8133 : poOpenInfo->fpL))
8134 : {
8135 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8136 : // deadlock with GDALDataset own mutex.
8137 0 : delete poDS;
8138 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8139 0 : return nullptr;
8140 : }
8141 3 : bTreatAsSubdataset = false;
8142 3 : poDS->eFormat = eTmpFormat;
8143 : }
8144 : else
8145 : #endif
8146 :
8147 543 : if (STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
8148 : {
8149 : GDALSubdatasetInfoH hInfo =
8150 64 : GDALGetSubdatasetInfo(poOpenInfo->pszFilename);
8151 64 : if (hInfo)
8152 : {
8153 64 : char *pszPath = GDALSubdatasetInfoGetPathComponent(hInfo);
8154 64 : poDS->osFilename = pszPath;
8155 64 : CPLFree(pszPath);
8156 :
8157 : char *pszSubdataset =
8158 64 : GDALSubdatasetInfoGetSubdatasetComponent(hInfo);
8159 64 : if (pszSubdataset && pszSubdataset[0] != '\0')
8160 : {
8161 64 : osSubdatasetName = pszSubdataset;
8162 64 : bTreatAsSubdataset = true;
8163 : }
8164 : else
8165 : {
8166 0 : osSubdatasetName = "";
8167 0 : bTreatAsSubdataset = false;
8168 : }
8169 64 : CPLFree(pszSubdataset);
8170 :
8171 64 : GDALDestroySubdatasetInfo(hInfo);
8172 : }
8173 :
8174 128 : if (!STARTS_WITH(poDS->osFilename, "http://") &&
8175 64 : !STARTS_WITH(poDS->osFilename, "https://"))
8176 : {
8177 : // Identify Format from real file, with bCheckExt=FALSE.
8178 : auto poOpenInfo2 = std::make_unique<GDALOpenInfo>(
8179 64 : poDS->osFilename.c_str(), GA_ReadOnly);
8180 64 : poDS->eFormat = netCDFIdentifyFormat(poOpenInfo2.get(),
8181 : /* bCheckExt = */ false);
8182 64 : if (NCDF_FORMAT_NONE == poDS->eFormat ||
8183 64 : NCDF_FORMAT_UNKNOWN == poDS->eFormat)
8184 : {
8185 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8186 : // deadlock with GDALDataset own mutex.
8187 0 : delete poDS;
8188 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8189 0 : return nullptr;
8190 : }
8191 : }
8192 : }
8193 : else
8194 : {
8195 479 : poDS->osFilename = poOpenInfo->pszFilename;
8196 479 : bTreatAsSubdataset = false;
8197 479 : poDS->eFormat = eTmpFormat;
8198 : }
8199 :
8200 : // Try opening the dataset.
8201 : #if defined(NCDF_DEBUG) && defined(ENABLE_UFFD)
8202 : CPLDebug("GDAL_netCDF", "calling nc_open_mem(%s)",
8203 : poDS->osFilename.c_str());
8204 : #elif defined(NCDF_DEBUG) && !defined(ENABLE_UFFD)
8205 : CPLDebug("GDAL_netCDF", "calling nc_open(%s)", poDS->osFilename.c_str());
8206 : #endif
8207 546 : int cdfid = -1;
8208 546 : const int nMode = ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE) != 0)
8209 : ? NC_WRITE
8210 : : NC_NOWRITE;
8211 1092 : CPLString osFilenameForNCOpen(poDS->osFilename);
8212 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
8213 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
8214 : {
8215 : char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
8216 : osFilenameForNCOpen = pszTemp;
8217 : CPLFree(pszTemp);
8218 : }
8219 : #endif
8220 546 : int status2 = -1;
8221 :
8222 : #ifdef ENABLE_UFFD
8223 546 : cpl_uffd_context *pCtx = nullptr;
8224 : #endif
8225 :
8226 561 : if (STARTS_WITH(osFilenameForNCOpen, "/vsimem/") &&
8227 15 : poOpenInfo->eAccess == GA_ReadOnly)
8228 : {
8229 15 : vsi_l_offset nLength = 0;
8230 15 : poDS->fpVSIMEM = VSIFOpenL(osFilenameForNCOpen, "rb");
8231 15 : if (poDS->fpVSIMEM)
8232 : {
8233 : // We assume that the file will not be modified. If it is, then
8234 : // pabyBuffer might become invalid.
8235 : GByte *pabyBuffer =
8236 15 : VSIGetMemFileBuffer(osFilenameForNCOpen, &nLength, false);
8237 15 : if (pabyBuffer)
8238 : {
8239 15 : status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen),
8240 : nMode, static_cast<size_t>(nLength),
8241 : pabyBuffer, &cdfid);
8242 : }
8243 : }
8244 : }
8245 : else
8246 : {
8247 : const bool bVsiFile =
8248 531 : !strncmp(osFilenameForNCOpen, "/vsi", strlen("/vsi"));
8249 : #ifdef ENABLE_UFFD
8250 531 : bool bReadOnly = (poOpenInfo->eAccess == GA_ReadOnly);
8251 531 : void *pVma = nullptr;
8252 531 : uint64_t nVmaSize = 0;
8253 :
8254 531 : if (bVsiFile)
8255 : {
8256 2 : if (bReadOnly)
8257 : {
8258 2 : if (CPLIsUserFaultMappingSupported())
8259 : {
8260 2 : pCtx = CPLCreateUserFaultMapping(osFilenameForNCOpen, &pVma,
8261 : &nVmaSize);
8262 : }
8263 : else
8264 : {
8265 0 : CPLError(CE_Failure, CPLE_AppDefined,
8266 : "Opening a /vsi file with the netCDF driver "
8267 : "requires Linux userfaultfd to be available. "
8268 : "If running from Docker, "
8269 : "--security-opt seccomp=unconfined might be "
8270 : "needed.%s",
8271 0 : ((poDS->eFormat == NCDF_FORMAT_NC4 ||
8272 0 : poDS->eFormat == NCDF_FORMAT_HDF5) &&
8273 0 : GDALGetDriverByName("HDF5"))
8274 : ? " Or you may set the GDAL_SKIP=netCDF "
8275 : "configuration option to force the use of "
8276 : "the HDF5 driver."
8277 : : "");
8278 : }
8279 : }
8280 : else
8281 : {
8282 0 : CPLError(CE_Failure, CPLE_AppDefined,
8283 : "Opening a /vsi file with the netCDF driver is only "
8284 : "supported in read-only mode");
8285 : }
8286 : }
8287 531 : if (pCtx != nullptr && pVma != nullptr && nVmaSize > 0)
8288 : {
8289 : // netCDF code, at least for netCDF 4.7.0, is confused by filenames
8290 : // like /vsicurl/http[s]://example.com/foo.nc, so just pass the
8291 : // final part
8292 2 : status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen), nMode,
8293 : static_cast<size_t>(nVmaSize), pVma, &cdfid);
8294 : }
8295 : else
8296 529 : status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
8297 : #else
8298 : if (bVsiFile)
8299 : {
8300 : CPLError(
8301 : CE_Failure, CPLE_AppDefined,
8302 : "Opening a /vsi file with the netCDF driver requires Linux "
8303 : "userfaultfd to be available.%s",
8304 : ((poDS->eFormat == NCDF_FORMAT_NC4 ||
8305 : poDS->eFormat == NCDF_FORMAT_HDF5) &&
8306 : GDALGetDriverByName("HDF5"))
8307 : ? " Or you may set the GDAL_SKIP=netCDF "
8308 : "configuration option to force the use of the HDF5 "
8309 : "driver."
8310 : : "");
8311 : status2 = NC_EIO;
8312 : }
8313 : else
8314 : {
8315 : status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
8316 : }
8317 : #endif
8318 : }
8319 546 : if (status2 != NC_NOERR)
8320 : {
8321 : #ifdef NCDF_DEBUG
8322 : CPLDebug("GDAL_netCDF", "error opening");
8323 : #endif
8324 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8325 : // with GDALDataset own mutex.
8326 0 : delete poDS;
8327 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8328 0 : return nullptr;
8329 : }
8330 : #ifdef NCDF_DEBUG
8331 : CPLDebug("GDAL_netCDF", "got cdfid=%d", cdfid);
8332 : #endif
8333 :
8334 : #if defined(ENABLE_NCDUMP) && !defined(_WIN32)
8335 : // Try to destroy the temporary file right now on Unix
8336 546 : if (poDS->bFileToDestroyAtClosing)
8337 : {
8338 3 : if (VSIUnlink(poDS->osFilename) == 0)
8339 : {
8340 3 : poDS->bFileToDestroyAtClosing = false;
8341 : }
8342 : }
8343 : #endif
8344 :
8345 : // Is this a real netCDF file?
8346 : int ndims;
8347 : int ngatts;
8348 : int nvars;
8349 : int unlimdimid;
8350 546 : int status = nc_inq(cdfid, &ndims, &nvars, &ngatts, &unlimdimid);
8351 546 : if (status != NC_NOERR)
8352 : {
8353 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8354 : // with GDALDataset own mutex.
8355 0 : delete poDS;
8356 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8357 0 : return nullptr;
8358 : }
8359 :
8360 : // Get file type from netcdf.
8361 546 : int nTmpFormat = NCDF_FORMAT_NONE;
8362 546 : status = nc_inq_format(cdfid, &nTmpFormat);
8363 546 : if (status != NC_NOERR)
8364 : {
8365 0 : NCDF_ERR(status);
8366 : }
8367 : else
8368 : {
8369 546 : CPLDebug("GDAL_netCDF",
8370 : "driver detected file type=%d, libnetcdf detected type=%d",
8371 546 : poDS->eFormat, nTmpFormat);
8372 546 : if (static_cast<NetCDFFormatEnum>(nTmpFormat) != poDS->eFormat)
8373 : {
8374 : // Warn if file detection conflicts with that from libnetcdf
8375 : // except for NC4C, which we have no way of detecting initially.
8376 26 : if (nTmpFormat != NCDF_FORMAT_NC4C &&
8377 13 : !STARTS_WITH(poDS->osFilename, "http://") &&
8378 0 : !STARTS_WITH(poDS->osFilename, "https://"))
8379 : {
8380 0 : CPLError(CE_Warning, CPLE_AppDefined,
8381 : "NetCDF driver detected file type=%d, but libnetcdf "
8382 : "detected type=%d",
8383 0 : poDS->eFormat, nTmpFormat);
8384 : }
8385 13 : CPLDebug("GDAL_netCDF", "setting file type to %d, was %d",
8386 13 : nTmpFormat, poDS->eFormat);
8387 13 : poDS->eFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
8388 : }
8389 : }
8390 :
8391 : // Does the request variable exist?
8392 546 : if (bTreatAsSubdataset)
8393 : {
8394 : int dummy;
8395 64 : if (NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &dummy,
8396 64 : &dummy) != CE_None)
8397 : {
8398 0 : CPLError(CE_Warning, CPLE_AppDefined,
8399 : "%s is a netCDF file, but %s is not a variable.",
8400 : poOpenInfo->pszFilename, osSubdatasetName.c_str());
8401 :
8402 0 : GDAL_nc_close(cdfid);
8403 : #ifdef ENABLE_UFFD
8404 0 : NETCDF_UFFD_UNMAP(pCtx);
8405 : #endif
8406 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8407 : // deadlock with GDALDataset own mutex.
8408 0 : delete poDS;
8409 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8410 0 : return nullptr;
8411 : }
8412 : }
8413 :
8414 : // Figure out whether or not the listed dataset has support for simple
8415 : // geometries (CF-1.8)
8416 546 : poDS->nCFVersion = nccfdriver::getCFVersion(cdfid);
8417 546 : bool bHasSimpleGeometries = false; // but not necessarily valid
8418 546 : if (poDS->nCFVersion >= 1.8)
8419 : {
8420 73 : bHasSimpleGeometries = poDS->DetectAndFillSGLayers(cdfid);
8421 73 : if (bHasSimpleGeometries)
8422 : {
8423 65 : poDS->bSGSupport = true;
8424 65 : poDS->vcdf.enableFullVirtualMode();
8425 : }
8426 : }
8427 :
8428 1092 : std::string osConventions;
8429 546 : if (NCDFGetAttr(cdfid, NC_GLOBAL, "Conventions", osConventions) != CE_None)
8430 : {
8431 60 : CPLDebug("GDAL_netCDF", "No UNIDATA NC_GLOBAL:Conventions attribute");
8432 : // Note that 'Conventions' is always capital 'C' in CF spec.
8433 : }
8434 :
8435 : // Create band information objects.
8436 546 : CPLDebug("GDAL_netCDF", "var_count = %d", nvars);
8437 :
8438 : // Create a corresponding GDALDataset.
8439 : // Create Netcdf Subdataset if filename as NETCDF tag.
8440 546 : poDS->cdfid = cdfid;
8441 : #ifdef ENABLE_UFFD
8442 546 : poDS->pCtx = pCtx;
8443 : #endif
8444 546 : poDS->eAccess = poOpenInfo->eAccess;
8445 546 : poDS->bDefineMode = false;
8446 :
8447 546 : poDS->ReadAttributes(cdfid, NC_GLOBAL);
8448 :
8449 : // Identify coordinate and boundary variables that we should
8450 : // ignore as Raster Bands.
8451 1092 : CPLStringList aosIgnoreVars;
8452 546 : NCDFGetCoordAndBoundVarFullNames(cdfid, aosIgnoreVars);
8453 : // Filter variables to keep only valid 2+D raster bands and vector fields.
8454 546 : int nRasterVars = 0;
8455 546 : int nIgnoredVars = 0;
8456 546 : int nGroupID = -1;
8457 546 : int nVarID = -1;
8458 :
8459 : std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>
8460 1092 : oMap2DDimsToGroupAndVar;
8461 1446 : if ((poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
8462 354 : STARTS_WITH(
8463 : poDS->aosMetadata.FetchNameValueDef("NC_GLOBAL#mission_name", ""),
8464 1 : "Sentinel 3") &&
8465 1 : EQUAL(poDS->aosMetadata.FetchNameValueDef(
8466 : "NC_GLOBAL#altimeter_sensor_name", ""),
8467 900 : "SRAL") &&
8468 1 : EQUAL(poDS->aosMetadata.FetchNameValueDef(
8469 : "NC_GLOBAL#radiometer_sensor_name", ""),
8470 : "MWR"))
8471 : {
8472 1 : if (poDS->eAccess == GA_Update)
8473 : {
8474 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8475 : // deadlock with GDALDataset own mutex.
8476 0 : delete poDS;
8477 0 : return nullptr;
8478 : }
8479 1 : poDS->ProcessSentinel3_SRAL_MWR();
8480 : }
8481 : else
8482 : {
8483 545 : poDS->FilterVars(cdfid, (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0,
8484 898 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
8485 353 : !bHasSimpleGeometries,
8486 : aosIgnoreVars, &nRasterVars, &nGroupID, &nVarID,
8487 : &nIgnoredVars, oMap2DDimsToGroupAndVar);
8488 : }
8489 :
8490 546 : const bool bListAllArrays = CPLTestBool(
8491 546 : CSLFetchNameValueDef(poDS->papszOpenOptions, "LIST_ALL_ARRAYS", "NO"));
8492 :
8493 : // Case where there is no raster variable
8494 546 : if (!bListAllArrays && nRasterVars == 0 && !bTreatAsSubdataset)
8495 : {
8496 138 : poDS->GDALPamDataset::SetMetadata(poDS->aosMetadata);
8497 138 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8498 : // with GDALDataset own mutex.
8499 138 : poDS->TryLoadXML();
8500 : // If the dataset has been opened in raster mode only, exit
8501 138 : if ((poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0 &&
8502 6 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) == 0)
8503 : {
8504 4 : delete poDS;
8505 4 : poDS = nullptr;
8506 : }
8507 : // Otherwise if the dataset is opened in vector mode, that there is
8508 : // no vector layer and we are in read-only, exit too.
8509 134 : else if (poDS->GetLayerCount() == 0 &&
8510 142 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
8511 8 : poOpenInfo->eAccess == GA_ReadOnly)
8512 : {
8513 8 : delete poDS;
8514 8 : poDS = nullptr;
8515 : }
8516 138 : CPLAcquireMutex(hNCMutex, 1000.0);
8517 138 : return poDS;
8518 : }
8519 :
8520 : // We have more than one variable with 2 dimensions in the
8521 : // file, then treat this as a subdataset container dataset.
8522 408 : bool bSeveralVariablesAsBands = false;
8523 408 : if (bListAllArrays || ((nRasterVars > 1) && !bTreatAsSubdataset))
8524 : {
8525 30 : if (CPLFetchBool(poOpenInfo->papszOpenOptions, "VARIABLES_AS_BANDS",
8526 36 : false) &&
8527 6 : oMap2DDimsToGroupAndVar.size() == 1)
8528 : {
8529 6 : std::tie(nGroupID, nVarID) =
8530 12 : oMap2DDimsToGroupAndVar.begin()->second.front();
8531 6 : bSeveralVariablesAsBands = true;
8532 : }
8533 : else
8534 : {
8535 24 : poDS->CreateSubDatasetList(cdfid);
8536 24 : poDS->GDALPamDataset::SetMetadata(poDS->aosMetadata);
8537 24 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8538 : // deadlock with GDALDataset own mutex.
8539 24 : poDS->TryLoadXML();
8540 24 : CPLAcquireMutex(hNCMutex, 1000.0);
8541 24 : return poDS;
8542 : }
8543 : }
8544 :
8545 : // If we are not treating things as a subdataset, then capture
8546 : // the name of the single available variable as the subdataset.
8547 384 : if (!bTreatAsSubdataset)
8548 : {
8549 320 : NCDF_ERR(NCDFGetVarFullName(nGroupID, nVarID, osSubdatasetName));
8550 : }
8551 :
8552 : // We have ignored at least one variable, so we should report them
8553 : // as subdatasets for reference.
8554 384 : if (nIgnoredVars > 0 && !bTreatAsSubdataset)
8555 : {
8556 29 : CPLDebug("GDAL_netCDF",
8557 : "As %d variables were ignored, creating subdataset list "
8558 : "for reference. Variable #%d [%s] is the main variable",
8559 : nIgnoredVars, nVarID, osSubdatasetName.c_str());
8560 29 : poDS->CreateSubDatasetList(cdfid);
8561 : }
8562 :
8563 : // Open the NETCDF subdataset NETCDF:"filename":subdataset.
8564 384 : int var = -1;
8565 384 : NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &nGroupID, &var);
8566 : // Now we can forget the root cdfid and only use the selected group.
8567 384 : cdfid = nGroupID;
8568 384 : int nd = 0;
8569 384 : nc_inq_varndims(cdfid, var, &nd);
8570 :
8571 384 : poDS->m_anDimIds.resize(nd);
8572 :
8573 : // X, Y, Z position in array
8574 768 : std::vector<int> anBandDimPos(nd);
8575 :
8576 384 : nc_inq_vardimid(cdfid, var, poDS->m_anDimIds.data());
8577 :
8578 : // Check if somebody tried to pass a variable with less than 1D.
8579 384 : if (nd < 1)
8580 : {
8581 0 : CPLError(CE_Warning, CPLE_AppDefined,
8582 : "Variable has %d dimension(s) - not supported.", nd);
8583 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8584 : // with GDALDataset own mutex.
8585 0 : delete poDS;
8586 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8587 0 : return nullptr;
8588 : }
8589 :
8590 : // CF-1 Convention
8591 : //
8592 : // Dimensions to appear in the relative order T, then Z, then Y,
8593 : // then X to the file. All other dimensions should, whenever
8594 : // possible, be placed to the left of the spatiotemporal
8595 : // dimensions.
8596 :
8597 : // Verify that dimensions are in the {T,Z,Y,X} or {T,Z,Y,X} order
8598 : // Ideally we should detect for other ordering and act accordingly
8599 : // Only done if file has Conventions=CF-* and only prints warning
8600 : // To disable set GDAL_NETCDF_VERIFY_DIMS=NO and to use only
8601 : // attributes (not varnames) set GDAL_NETCDF_VERIFY_DIMS=STRICT
8602 : const bool bCheckDims =
8603 768 : CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES")) &&
8604 384 : STARTS_WITH_CI(osConventions.c_str(), "CF");
8605 :
8606 384 : bool bYXBandOrder = false;
8607 384 : if (nd == 3)
8608 : {
8609 : // If there's a coordinates attributes, and the variable it points to
8610 : // are 2D variables indexed by the same first and second dimension than
8611 : // our variable of interest, then it is Y,X,Band order.
8612 48 : char *pszCoordinates = nullptr;
8613 48 : if (NCDFGetAttr(cdfid, var, "coordinates", &pszCoordinates) ==
8614 66 : CE_None &&
8615 18 : pszCoordinates)
8616 : {
8617 : const CPLStringList aosCoordinates(
8618 36 : NCDFTokenizeCoordinatesAttribute(pszCoordinates));
8619 18 : if (aosCoordinates.size() == 2)
8620 : {
8621 : // Test that each variable is longitude/latitude.
8622 13 : for (int i = 0; i < aosCoordinates.size(); i++)
8623 : {
8624 13 : if (NCDFIsVarLongitude(cdfid, -1, aosCoordinates[i]) ||
8625 4 : NCDFIsVarLatitude(cdfid, -1, aosCoordinates[i]))
8626 : {
8627 9 : int nOtherGroupId = -1;
8628 9 : int nOtherVarId = -1;
8629 9 : if (NCDFResolveVar(cdfid, aosCoordinates[i],
8630 : &nOtherGroupId,
8631 9 : &nOtherVarId) == CE_None)
8632 : {
8633 9 : int coordDimCount = 0;
8634 9 : nc_inq_varndims(nOtherGroupId, nOtherVarId,
8635 : &coordDimCount);
8636 9 : if (coordDimCount == 2)
8637 : {
8638 3 : int coordDimIds[2] = {0, 0};
8639 3 : nc_inq_vardimid(nOtherGroupId, nOtherVarId,
8640 : coordDimIds);
8641 4 : if (coordDimIds[0] == poDS->m_anDimIds[0] &&
8642 1 : coordDimIds[1] == poDS->m_anDimIds[1])
8643 : {
8644 1 : bYXBandOrder = true;
8645 1 : break;
8646 : }
8647 : }
8648 : }
8649 : }
8650 : }
8651 : }
8652 : }
8653 48 : CPLFree(pszCoordinates);
8654 :
8655 48 : if (!bYXBandOrder)
8656 : {
8657 47 : char szDim0Name[NC_MAX_NAME + 1] = {};
8658 47 : char szDim1Name[NC_MAX_NAME + 1] = {};
8659 47 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[0], szDim0Name);
8660 47 : NCDF_ERR(status);
8661 47 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[1], szDim1Name);
8662 47 : NCDF_ERR(status);
8663 :
8664 47 : if (strcmp(szDim0Name, "number_of_lines") == 0 &&
8665 1 : strcmp(szDim1Name, "pixels_per_line") == 0)
8666 : {
8667 : // Like in PACE OCI products
8668 1 : bYXBandOrder = true;
8669 : }
8670 : else
8671 : {
8672 : // For example for EMIT data (https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/),
8673 : // dimension order is downtrack, crosstrack, bands
8674 46 : char szDim2Name[NC_MAX_NAME + 1] = {};
8675 46 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[2], szDim2Name);
8676 46 : NCDF_ERR(status);
8677 90 : bYXBandOrder = strcmp(szDim2Name, "bands") == 0 ||
8678 44 : strcmp(szDim2Name, "band") == 0;
8679 : }
8680 : }
8681 : }
8682 :
8683 384 : if (nd >= 2 && bCheckDims && !bYXBandOrder)
8684 : {
8685 296 : char szDimName1[NC_MAX_NAME + 1] = {};
8686 296 : char szDimName2[NC_MAX_NAME + 1] = {};
8687 296 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 1], szDimName1);
8688 296 : NCDF_ERR(status);
8689 296 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 2], szDimName2);
8690 296 : NCDF_ERR(status);
8691 482 : if (NCDFIsVarLongitude(cdfid, -1, szDimName1) == false &&
8692 186 : NCDFIsVarProjectionX(cdfid, -1, szDimName1) == false)
8693 : {
8694 4 : CPLError(CE_Warning, CPLE_AppDefined,
8695 : "dimension #%d (%s) is not a Longitude/X dimension.",
8696 : nd - 1, szDimName1);
8697 : }
8698 482 : if (NCDFIsVarLatitude(cdfid, -1, szDimName2) == false &&
8699 186 : NCDFIsVarProjectionY(cdfid, -1, szDimName2) == false)
8700 : {
8701 4 : CPLError(CE_Warning, CPLE_AppDefined,
8702 : "dimension #%d (%s) is not a Latitude/Y dimension.",
8703 : nd - 2, szDimName2);
8704 : }
8705 296 : if ((NCDFIsVarLongitude(cdfid, -1, szDimName2) ||
8706 298 : NCDFIsVarProjectionX(cdfid, -1, szDimName2)) &&
8707 2 : (NCDFIsVarLatitude(cdfid, -1, szDimName1) ||
8708 0 : NCDFIsVarProjectionY(cdfid, -1, szDimName1)))
8709 : {
8710 2 : poDS->bSwitchedXY = true;
8711 : }
8712 296 : if (nd >= 3)
8713 : {
8714 53 : char szDimName3[NC_MAX_NAME + 1] = {};
8715 : status =
8716 53 : nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 3], szDimName3);
8717 53 : NCDF_ERR(status);
8718 53 : if (nd >= 4)
8719 : {
8720 13 : char szDimName4[NC_MAX_NAME + 1] = {};
8721 : status =
8722 13 : nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 4], szDimName4);
8723 13 : NCDF_ERR(status);
8724 13 : if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false)
8725 : {
8726 0 : CPLError(CE_Warning, CPLE_AppDefined,
8727 : "dimension #%d (%s) is not a Vertical dimension.",
8728 : nd - 3, szDimName3);
8729 : }
8730 13 : if (NCDFIsVarTimeCoord(cdfid, -1, szDimName4) == false)
8731 : {
8732 0 : CPLError(CE_Warning, CPLE_AppDefined,
8733 : "dimension #%d (%s) is not a Time dimension.",
8734 : nd - 4, szDimName4);
8735 : }
8736 : }
8737 : else
8738 : {
8739 77 : if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false &&
8740 37 : NCDFIsVarTimeCoord(cdfid, -1, szDimName3) == false)
8741 : {
8742 0 : CPLError(CE_Warning, CPLE_AppDefined,
8743 : "dimension #%d (%s) is not a "
8744 : "Time or Vertical dimension.",
8745 : nd - 3, szDimName3);
8746 : }
8747 : }
8748 : }
8749 : }
8750 :
8751 : // Get X dimensions information.
8752 : size_t xdim;
8753 384 : poDS->nXDimID = poDS->m_anDimIds[bYXBandOrder ? 1 : nd - 1];
8754 384 : nc_inq_dimlen(cdfid, poDS->nXDimID, &xdim);
8755 :
8756 : // Get Y dimension information.
8757 : size_t ydim;
8758 384 : if (nd >= 2)
8759 : {
8760 380 : poDS->nYDimID = poDS->m_anDimIds[bYXBandOrder ? 0 : nd - 2];
8761 380 : nc_inq_dimlen(cdfid, poDS->nYDimID, &ydim);
8762 : }
8763 : else
8764 : {
8765 4 : poDS->nYDimID = -1;
8766 4 : ydim = 1;
8767 : }
8768 :
8769 384 : if (xdim > INT_MAX || ydim > INT_MAX)
8770 : {
8771 0 : CPLError(CE_Failure, CPLE_AppDefined,
8772 : "Invalid raster dimensions: " CPL_FRMT_GUIB "x" CPL_FRMT_GUIB,
8773 : static_cast<GUIntBig>(xdim), static_cast<GUIntBig>(ydim));
8774 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8775 : // with GDALDataset own mutex.
8776 0 : delete poDS;
8777 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8778 0 : return nullptr;
8779 : }
8780 :
8781 384 : poDS->nRasterXSize = static_cast<int>(xdim);
8782 384 : poDS->nRasterYSize = static_cast<int>(ydim);
8783 :
8784 384 : unsigned int k = 0;
8785 1231 : for (int j = 0; j < nd; j++)
8786 : {
8787 847 : if (poDS->m_anDimIds[j] == poDS->nXDimID)
8788 : {
8789 384 : anBandDimPos[0] = j; // Save Position of XDim
8790 384 : k++;
8791 : }
8792 847 : if (poDS->m_anDimIds[j] == poDS->nYDimID)
8793 : {
8794 380 : anBandDimPos[1] = j; // Save Position of YDim
8795 380 : k++;
8796 : }
8797 : }
8798 : // X and Y Dimension Ids were not found!
8799 384 : if ((nd >= 2 && k != 2) || (nd == 1 && k != 1))
8800 : {
8801 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8802 : // with GDALDataset own mutex.
8803 0 : delete poDS;
8804 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8805 0 : return nullptr;
8806 : }
8807 :
8808 : // Read Metadata for this variable.
8809 :
8810 : // Should disable as is also done at band level, except driver needs the
8811 : // variables as metadata (e.g. projection).
8812 384 : poDS->ReadAttributes(cdfid, var);
8813 :
8814 : // Read Metadata for each dimension.
8815 384 : int *panDimIds = nullptr;
8816 384 : NCDFGetVisibleDims(cdfid, &ndims, &panDimIds);
8817 : // With NetCDF-4 groups panDimIds is not always [0..dim_count-1] like
8818 : // in NetCDF-3 because we see only the dimensions of the selected group
8819 : // and its parents.
8820 : // poDS->papszDimName is indexed by dim IDs, so it must contains all IDs
8821 : // [0..max(panDimIds)], but they are not all useful so we fill names
8822 : // of useless dims with empty string.
8823 384 : if (panDimIds)
8824 : {
8825 384 : const int nMaxDimId = *std::max_element(panDimIds, panDimIds + ndims);
8826 384 : std::set<int> oSetExistingDimIds;
8827 1271 : for (int i = 0; i < ndims; i++)
8828 : {
8829 887 : oSetExistingDimIds.insert(panDimIds[i]);
8830 : }
8831 384 : std::set<int> oSetDimIdsUsedByVar;
8832 1231 : for (int i = 0; i < nd; i++)
8833 : {
8834 847 : oSetDimIdsUsedByVar.insert(poDS->m_anDimIds[i]);
8835 : }
8836 1273 : for (int j = 0; j <= nMaxDimId; j++)
8837 : {
8838 : // Is j dim used?
8839 889 : if (oSetExistingDimIds.find(j) != oSetExistingDimIds.end())
8840 : {
8841 : // Useful dim.
8842 887 : char szTemp[NC_MAX_NAME + 1] = {};
8843 887 : status = nc_inq_dimname(cdfid, j, szTemp);
8844 887 : if (status != NC_NOERR)
8845 : {
8846 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8847 : // deadlock with GDALDataset own
8848 : // mutex.
8849 0 : delete poDS;
8850 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8851 0 : return nullptr;
8852 : }
8853 887 : poDS->papszDimName.AddString(szTemp);
8854 :
8855 887 : if (oSetDimIdsUsedByVar.find(j) != oSetDimIdsUsedByVar.end())
8856 : {
8857 847 : int nDimGroupId = -1;
8858 847 : int nDimVarId = -1;
8859 847 : if (NCDFResolveVar(cdfid, poDS->papszDimName[j],
8860 847 : &nDimGroupId, &nDimVarId) == CE_None)
8861 : {
8862 619 : poDS->ReadAttributes(nDimGroupId, nDimVarId);
8863 : }
8864 : }
8865 : }
8866 : else
8867 : {
8868 : // Useless dim.
8869 2 : poDS->papszDimName.AddString("");
8870 : }
8871 : }
8872 384 : CPLFree(panDimIds);
8873 : }
8874 :
8875 : // Set projection info.
8876 768 : std::vector<std::string> aosRemovedMDItems;
8877 384 : if (nd > 1)
8878 : {
8879 380 : poDS->SetProjectionFromVar(cdfid, var,
8880 : /*bReadSRSOnly=*/false,
8881 : /* pszGivenGM = */ nullptr,
8882 : /* returnProjStr = */ nullptr,
8883 : /* sg = */ nullptr, &aosRemovedMDItems);
8884 : }
8885 :
8886 : // Override bottom-up with GDAL_NETCDF_BOTTOMUP config option.
8887 384 : const char *pszValue = CPLGetConfigOption("GDAL_NETCDF_BOTTOMUP", nullptr);
8888 384 : if (pszValue)
8889 : {
8890 24 : poDS->bBottomUp = CPLTestBool(pszValue);
8891 24 : CPLDebug("GDAL_netCDF",
8892 : "set bBottomUp=%d because GDAL_NETCDF_BOTTOMUP=%s",
8893 24 : static_cast<int>(poDS->bBottomUp), pszValue);
8894 : }
8895 :
8896 : // Save non-spatial dimension info.
8897 :
8898 384 : int *panBandZLev = nullptr;
8899 384 : int nDim = (nd >= 2) ? 2 : 1;
8900 : size_t lev_count;
8901 384 : size_t nTotLevCount = 1;
8902 384 : nc_type nType = NC_NAT;
8903 :
8904 384 : if (nd > 2)
8905 : {
8906 64 : nDim = 2;
8907 64 : panBandZLev = static_cast<int *>(CPLCalloc(nd - 2, sizeof(int)));
8908 :
8909 128 : CPLString osExtraDimNames = "{";
8910 :
8911 64 : char szDimName[NC_MAX_NAME + 1] = {};
8912 :
8913 64 : bool bREPORT_EXTRA_DIM_VALUESWarningEmitted = false;
8914 275 : for (int j = 0; j < nd; j++)
8915 : {
8916 358 : if ((poDS->m_anDimIds[j] != poDS->nXDimID) &&
8917 147 : (poDS->m_anDimIds[j] != poDS->nYDimID))
8918 : {
8919 83 : nc_inq_dimlen(cdfid, poDS->m_anDimIds[j], &lev_count);
8920 83 : nTotLevCount *= lev_count;
8921 83 : panBandZLev[nDim - 2] = static_cast<int>(lev_count);
8922 83 : anBandDimPos[nDim] = j; // Save Position of ZDim
8923 : // Save non-spatial dimension names.
8924 83 : if (nc_inq_dimname(cdfid, poDS->m_anDimIds[j], szDimName) ==
8925 : NC_NOERR)
8926 : {
8927 83 : osExtraDimNames += szDimName;
8928 83 : if (j < nd - 3)
8929 : {
8930 19 : osExtraDimNames += ",";
8931 : }
8932 :
8933 83 : int nIdxGroupID = -1;
8934 83 : int nIdxVarID = Get1DVariableIndexedByDimension(
8935 83 : cdfid, poDS->m_anDimIds[j], szDimName, true,
8936 83 : &nIdxGroupID);
8937 83 : poDS->m_anExtraDimGroupIds.push_back(nIdxGroupID);
8938 83 : poDS->m_anExtraDimVarIds.push_back(nIdxVarID);
8939 :
8940 83 : if (nIdxVarID >= 0)
8941 : {
8942 74 : nc_inq_vartype(nIdxGroupID, nIdxVarID, &nType);
8943 : char szExtraDimDef[NC_MAX_NAME + 1];
8944 74 : snprintf(szExtraDimDef, sizeof(szExtraDimDef),
8945 : "{%ld,%d}", (long)lev_count, nType);
8946 : char szTemp[NC_MAX_NAME + 32 + 1];
8947 74 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
8948 : szDimName);
8949 74 : poDS->aosMetadata.SetNameValue(szTemp, szExtraDimDef);
8950 :
8951 : // Retrieving data for unlimited dimensions might be
8952 : // costly on network storage, so don't do it.
8953 : // Each band will capture the value along the extra
8954 : // dimension in its NETCDF_DIM_xxxx band metadata item
8955 : // Addresses use case of
8956 : // https://lists.osgeo.org/pipermail/gdal-dev/2023-May/057209.html
8957 : const bool bIsLocal =
8958 74 : VSIIsLocal(osFilenameForNCOpen.c_str());
8959 : bool bListDimValues =
8960 75 : bIsLocal || lev_count == 1 ||
8961 1 : !NCDFIsUnlimitedDim(poDS->eFormat ==
8962 : NCDF_FORMAT_NC4,
8963 1 : cdfid, poDS->m_anDimIds[j]);
8964 : const char *pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES =
8965 74 : CPLGetConfigOption(
8966 : "GDAL_NETCDF_REPORT_EXTRA_DIM_VALUES", nullptr);
8967 74 : if (pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES)
8968 : {
8969 2 : bListDimValues = CPLTestBool(
8970 : pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES);
8971 : }
8972 72 : else if (!bListDimValues && !bIsLocal &&
8973 1 : !bREPORT_EXTRA_DIM_VALUESWarningEmitted)
8974 : {
8975 1 : bREPORT_EXTRA_DIM_VALUESWarningEmitted = true;
8976 1 : CPLDebug(
8977 : "GDAL_netCDF",
8978 : "Listing extra dimension values is skipped "
8979 : "because this dataset is hosted on a network "
8980 : "file system, and such an operation could be "
8981 : "slow. If you still want to proceed, set the "
8982 : "GDAL_NETCDF_REPORT_EXTRA_DIM_VALUES "
8983 : "configuration option to YES");
8984 : }
8985 74 : if (bListDimValues)
8986 : {
8987 72 : char *pszTemp = nullptr;
8988 72 : if (NCDFGet1DVar(nIdxGroupID, nIdxVarID,
8989 72 : &pszTemp) == CE_None)
8990 : {
8991 72 : snprintf(szTemp, sizeof(szTemp),
8992 : "NETCDF_DIM_%s_VALUES", szDimName);
8993 72 : poDS->aosMetadata.SetNameValue(szTemp, pszTemp);
8994 72 : CPLFree(pszTemp);
8995 : }
8996 : }
8997 : }
8998 : }
8999 : else
9000 : {
9001 0 : poDS->m_anExtraDimGroupIds.push_back(-1);
9002 0 : poDS->m_anExtraDimVarIds.push_back(-1);
9003 : }
9004 :
9005 83 : nDim++;
9006 : }
9007 : }
9008 64 : osExtraDimNames += "}";
9009 64 : poDS->aosMetadata.SetNameValue("NETCDF_DIM_EXTRA", osExtraDimNames);
9010 : }
9011 :
9012 : // Store Metadata.
9013 394 : for (const auto &osStr : aosRemovedMDItems)
9014 10 : poDS->aosMetadata.SetNameValue(osStr.c_str(), nullptr);
9015 :
9016 384 : poDS->GDALPamDataset::SetMetadata(poDS->aosMetadata);
9017 :
9018 : // Create bands.
9019 :
9020 : // Arbitrary threshold.
9021 : int nMaxBandCount =
9022 384 : atoi(CPLGetConfigOption("GDAL_MAX_BAND_COUNT", "32768"));
9023 384 : if (nMaxBandCount <= 0)
9024 0 : nMaxBandCount = 32768;
9025 384 : if (nTotLevCount > static_cast<unsigned int>(nMaxBandCount))
9026 : {
9027 0 : CPLError(CE_Warning, CPLE_AppDefined,
9028 : "Limiting number of bands to %d instead of %u", nMaxBandCount,
9029 : static_cast<unsigned int>(nTotLevCount));
9030 0 : nTotLevCount = static_cast<unsigned int>(nMaxBandCount);
9031 : }
9032 384 : if (poDS->nRasterXSize == 0 || poDS->nRasterYSize == 0)
9033 : {
9034 0 : poDS->nRasterXSize = 0;
9035 0 : poDS->nRasterYSize = 0;
9036 0 : nTotLevCount = 0;
9037 0 : if (poDS->GetLayerCount() == 0)
9038 : {
9039 0 : CPLFree(panBandZLev);
9040 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9041 : // deadlock with GDALDataset own mutex.
9042 0 : delete poDS;
9043 0 : CPLAcquireMutex(hNCMutex, 1000.0);
9044 0 : return nullptr;
9045 : }
9046 : }
9047 384 : if (bSeveralVariablesAsBands)
9048 : {
9049 6 : const auto &listVariables = oMap2DDimsToGroupAndVar.begin()->second;
9050 24 : for (int iBand = 0; iBand < static_cast<int>(listVariables.size());
9051 : ++iBand)
9052 : {
9053 18 : int bandVarGroupId = listVariables[iBand].first;
9054 18 : int bandVarId = listVariables[iBand].second;
9055 : netCDFRasterBand *poBand = new netCDFRasterBand(
9056 0 : netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, bandVarGroupId,
9057 18 : bandVarId, nDim, 0, nullptr, anBandDimPos.data(), iBand + 1);
9058 18 : poDS->SetBand(iBand + 1, poBand);
9059 : }
9060 : }
9061 : else
9062 : {
9063 872 : for (unsigned int lev = 0; lev < nTotLevCount; lev++)
9064 : {
9065 : netCDFRasterBand *poBand = new netCDFRasterBand(
9066 0 : netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, cdfid, var, nDim,
9067 494 : lev, panBandZLev, anBandDimPos.data(), lev + 1);
9068 494 : poDS->SetBand(lev + 1, poBand);
9069 : }
9070 : }
9071 :
9072 384 : if (panBandZLev)
9073 64 : CPLFree(panBandZLev);
9074 : // Handle angular geographic coordinates here
9075 :
9076 : // Initialize any PAM information.
9077 384 : if (bTreatAsSubdataset)
9078 : {
9079 64 : poDS->SetPhysicalFilename(poDS->osFilename);
9080 64 : poDS->SetSubdatasetName(osSubdatasetName);
9081 : }
9082 :
9083 384 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
9084 : // GDALDataset own mutex.
9085 384 : poDS->TryLoadXML();
9086 :
9087 384 : if (bTreatAsSubdataset)
9088 64 : poDS->oOvManager.Initialize(poDS, ":::VIRTUAL:::");
9089 : else
9090 320 : poDS->oOvManager.Initialize(poDS, poDS->osFilename);
9091 :
9092 384 : CPLAcquireMutex(hNCMutex, 1000.0);
9093 :
9094 384 : return poDS;
9095 : }
9096 :
9097 : /************************************************************************/
9098 : /* CopyMetadata() */
9099 : /* */
9100 : /* Create a copy of metadata for NC_GLOBAL or a variable */
9101 : /************************************************************************/
9102 :
9103 179 : static void CopyMetadata(GDALDataset *poSrcDS, GDALRasterBand *poSrcBand,
9104 : GDALRasterBand *poDstBand, int nCdfId, int CDFVarID,
9105 : const char *pszPrefix)
9106 : {
9107 : // Remove the following band meta but set them later from band data.
9108 179 : const char *const papszIgnoreBand[] = {
9109 : CF_ADD_OFFSET, CF_SCALE_FACTOR, "valid_range", "_Unsigned",
9110 : NCDF_FillValue, "coordinates", nullptr};
9111 179 : const char *const papszIgnoreGlobal[] = {"NETCDF_DIM_EXTRA", nullptr};
9112 :
9113 179 : CSLConstList papszMetadata = nullptr;
9114 179 : if (poSrcDS)
9115 : {
9116 77 : papszMetadata = poSrcDS->GetMetadata();
9117 : }
9118 102 : else if (poSrcBand)
9119 : {
9120 102 : papszMetadata = poSrcBand->GetMetadata();
9121 : }
9122 :
9123 825 : for (const auto &[pszKey, pszValue] : cpl::IterateNameValue(papszMetadata))
9124 : {
9125 : #ifdef NCDF_DEBUG
9126 : CPLDebug("GDAL_netCDF", "copy metadata [%s]=[%s]", pszKey, pszValue);
9127 : #endif
9128 :
9129 646 : CPLString osMetaName(pszKey);
9130 :
9131 : // Check for items that match pszPrefix if applicable.
9132 646 : if (pszPrefix && !EQUAL(pszPrefix, ""))
9133 : {
9134 : // Remove prefix.
9135 115 : if (STARTS_WITH(osMetaName.c_str(), pszPrefix))
9136 : {
9137 17 : osMetaName = osMetaName.substr(strlen(pszPrefix));
9138 : }
9139 : // Only copy items that match prefix.
9140 : else
9141 : {
9142 98 : continue;
9143 : }
9144 : }
9145 :
9146 : // Fix various issues with metadata translation.
9147 548 : if (CDFVarID == NC_GLOBAL)
9148 : {
9149 : // Do not copy items in papszIgnoreGlobal and NETCDF_DIM_*.
9150 763 : if ((CSLFindString(papszIgnoreGlobal, osMetaName) != -1) ||
9151 379 : (STARTS_WITH(osMetaName, "NETCDF_DIM_")))
9152 21 : continue;
9153 : // Remove NC_GLOBAL prefix for netcdf global Metadata.
9154 363 : else if (STARTS_WITH(osMetaName, "NC_GLOBAL#"))
9155 : {
9156 53 : osMetaName = osMetaName.substr(strlen("NC_GLOBAL#"));
9157 : }
9158 : // GDAL Metadata renamed as GDAL-[meta].
9159 310 : else if (strstr(osMetaName, "#") == nullptr)
9160 : {
9161 22 : osMetaName = "GDAL_" + osMetaName;
9162 : }
9163 : // Keep time, lev and depth information for safe-keeping.
9164 : // Time and vertical coordinate handling need improvements.
9165 : /*
9166 : else if( STARTS_WITH(szMetaName, "time#") )
9167 : {
9168 : szMetaName[4] = '-';
9169 : }
9170 : else if( STARTS_WITH(szMetaName, "lev#") )
9171 : {
9172 : szMetaName[3] = '-';
9173 : }
9174 : else if( STARTS_WITH(szMetaName, "depth#") )
9175 : {
9176 : szMetaName[5] = '-';
9177 : }
9178 : */
9179 : // Only copy data without # (previously all data was copied).
9180 363 : if (strstr(osMetaName, "#") != nullptr)
9181 288 : continue;
9182 : // netCDF attributes do not like the '#' character.
9183 : // for( unsigned int h=0; h < strlen(szMetaName) -1 ; h++ ) {
9184 : // if( szMetaName[h] == '#') szMetaName[h] = '-';
9185 : // }
9186 : }
9187 : else
9188 : {
9189 : // Do not copy varname, stats, NETCDF_DIM_*, nodata
9190 : // and items in papszIgnoreBand.
9191 164 : if (STARTS_WITH(osMetaName, "NETCDF_VARNAME") ||
9192 127 : STARTS_WITH(osMetaName, "STATISTICS_") ||
9193 127 : STARTS_WITH(osMetaName, "NETCDF_DIM_") ||
9194 94 : STARTS_WITH(osMetaName, "missing_value") ||
9195 358 : STARTS_WITH(osMetaName, "_FillValue") ||
9196 67 : CSLFindString(papszIgnoreBand, osMetaName) != -1)
9197 112 : continue;
9198 : }
9199 :
9200 : #ifdef NCDF_DEBUG
9201 : CPLDebug("GDAL_netCDF", "copy name=[%s] value=[%s]", osMetaName.c_str(),
9202 : pszValue);
9203 : #endif
9204 127 : if (NCDFPutAttr(nCdfId, CDFVarID, osMetaName, pszValue) != CE_None)
9205 : {
9206 0 : CPLDebug("GDAL_netCDF", "NCDFPutAttr(%d, %d, %s, %s) failed",
9207 : nCdfId, CDFVarID, osMetaName.c_str(), pszValue);
9208 : }
9209 : }
9210 :
9211 : // Set add_offset and scale_factor here if present.
9212 179 : if (poSrcBand && poDstBand)
9213 : {
9214 :
9215 102 : int bGotAddOffset = FALSE;
9216 102 : const double dfAddOffset = poSrcBand->GetOffset(&bGotAddOffset);
9217 102 : int bGotScale = FALSE;
9218 102 : const double dfScale = poSrcBand->GetScale(&bGotScale);
9219 :
9220 102 : if (bGotAddOffset && dfAddOffset != 0.0)
9221 1 : poDstBand->SetOffset(dfAddOffset);
9222 102 : if (bGotScale && dfScale != 1.0)
9223 1 : poDstBand->SetScale(dfScale);
9224 : }
9225 179 : }
9226 :
9227 : /************************************************************************/
9228 : /* CreateLL() */
9229 : /* */
9230 : /* Shared functionality between netCDFDataset::Create() and */
9231 : /* netCDF::CreateCopy() for creating netcdf file based on a set of */
9232 : /* options and a configuration. */
9233 : /************************************************************************/
9234 :
9235 232 : netCDFDataset *netCDFDataset::CreateLL(const char *pszFilename, int nXSize,
9236 : int nYSize, int nBandsIn,
9237 : CSLConstList papszOptions)
9238 : {
9239 232 : if (!((nXSize == 0 && nYSize == 0 && nBandsIn == 0) ||
9240 137 : (nXSize > 0 && nYSize > 0 && nBandsIn > 0)))
9241 : {
9242 1 : return nullptr;
9243 : }
9244 :
9245 231 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
9246 : // GDALDataset own mutex.
9247 231 : netCDFDataset *poDS = new netCDFDataset();
9248 231 : CPLAcquireMutex(hNCMutex, 1000.0);
9249 :
9250 231 : poDS->nRasterXSize = nXSize;
9251 231 : poDS->nRasterYSize = nYSize;
9252 231 : poDS->eAccess = GA_Update;
9253 231 : poDS->osFilename = pszFilename;
9254 :
9255 : // From gtiff driver, is this ok?
9256 : /*
9257 : poDS->nBlockXSize = nXSize;
9258 : poDS->nBlockYSize = 1;
9259 : poDS->nBlocksPerBand =
9260 : DIV_ROUND_UP((nYSize, poDS->nBlockYSize))
9261 : * DIV_ROUND_UP((nXSize, poDS->nBlockXSize));
9262 : */
9263 :
9264 : // process options.
9265 231 : poDS->aosCreationOptions = CSLDuplicate(papszOptions);
9266 231 : if (!poDS->ProcessCreationOptions())
9267 : {
9268 23 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9269 : // deadlock with GDALDataset own
9270 : // mutex.
9271 23 : delete poDS;
9272 23 : CPLAcquireMutex(hNCMutex, 1000.0);
9273 23 : return nullptr;
9274 : }
9275 :
9276 208 : if (poDS->eMultipleLayerBehavior == SEPARATE_FILES)
9277 : {
9278 : VSIStatBuf sStat;
9279 3 : if (VSIStat(pszFilename, &sStat) == 0)
9280 : {
9281 0 : if (!VSI_ISDIR(sStat.st_mode))
9282 : {
9283 0 : CPLError(CE_Failure, CPLE_FileIO,
9284 : "%s is an existing file, but not a directory",
9285 : pszFilename);
9286 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9287 : // deadlock with GDALDataset own
9288 : // mutex.
9289 0 : delete poDS;
9290 0 : CPLAcquireMutex(hNCMutex, 1000.0);
9291 0 : return nullptr;
9292 : }
9293 : }
9294 3 : else if (VSIMkdir(pszFilename, 0755) != 0)
9295 : {
9296 1 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s directory",
9297 : pszFilename);
9298 1 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9299 : // deadlock with GDALDataset own mutex.
9300 1 : delete poDS;
9301 1 : CPLAcquireMutex(hNCMutex, 1000.0);
9302 1 : return nullptr;
9303 : }
9304 :
9305 2 : return poDS;
9306 : }
9307 : // Create the dataset.
9308 410 : CPLString osFilenameForNCCreate(pszFilename);
9309 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
9310 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
9311 : {
9312 : char *pszTemp =
9313 : CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
9314 : osFilenameForNCCreate = pszTemp;
9315 : CPLFree(pszTemp);
9316 : }
9317 : #endif
9318 :
9319 : #if defined(_WIN32)
9320 : {
9321 : // Works around bug of msys2 netCDF 4.9.0 package where nc_create()
9322 : // crashes
9323 : VSIStatBuf sStat;
9324 : const std::string osDirname =
9325 : CPLGetDirnameSafe(osFilenameForNCCreate.c_str());
9326 : if (VSIStat(osDirname.c_str(), &sStat) != 0)
9327 : {
9328 : CPLError(CE_Failure, CPLE_OpenFailed,
9329 : "Unable to create netCDF file %s: non existing output "
9330 : "directory",
9331 : pszFilename);
9332 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9333 : // deadlock with GDALDataset own mutex.
9334 : delete poDS;
9335 : CPLAcquireMutex(hNCMutex, 1000.0);
9336 : return nullptr;
9337 : }
9338 : }
9339 : #endif
9340 :
9341 : int status =
9342 205 : nc_create(osFilenameForNCCreate, poDS->nCreateMode, &(poDS->cdfid));
9343 :
9344 : // Put into define mode.
9345 205 : poDS->SetDefineMode(true);
9346 :
9347 205 : if (status != NC_NOERR)
9348 : {
9349 30 : CPLError(CE_Failure, CPLE_OpenFailed,
9350 : "Unable to create netCDF file %s (Error code %d): %s .",
9351 : pszFilename, status, nc_strerror(status));
9352 30 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
9353 : // with GDALDataset own mutex.
9354 30 : delete poDS;
9355 30 : CPLAcquireMutex(hNCMutex, 1000.0);
9356 30 : return nullptr;
9357 : }
9358 :
9359 : // Define dimensions.
9360 175 : if (nXSize > 0 && nYSize > 0)
9361 : {
9362 123 : poDS->papszDimName.AddString(NCDF_DIMNAME_X);
9363 : status =
9364 123 : nc_def_dim(poDS->cdfid, NCDF_DIMNAME_X, nXSize, &(poDS->nXDimID));
9365 123 : NCDF_ERR(status);
9366 123 : CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
9367 : poDS->cdfid, NCDF_DIMNAME_X, nXSize, poDS->nXDimID);
9368 :
9369 123 : poDS->papszDimName.AddString(NCDF_DIMNAME_Y);
9370 : status =
9371 123 : nc_def_dim(poDS->cdfid, NCDF_DIMNAME_Y, nYSize, &(poDS->nYDimID));
9372 123 : NCDF_ERR(status);
9373 123 : CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
9374 : poDS->cdfid, NCDF_DIMNAME_Y, nYSize, poDS->nYDimID);
9375 : }
9376 :
9377 175 : return poDS;
9378 : }
9379 :
9380 : /************************************************************************/
9381 : /* Create() */
9382 : /************************************************************************/
9383 :
9384 149 : GDALDataset *netCDFDataset::Create(const char *pszFilename, int nXSize,
9385 : int nYSize, int nBandsIn, GDALDataType eType,
9386 : CSLConstList papszOptions)
9387 : {
9388 149 : CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::Create(%s, ...)",
9389 : pszFilename);
9390 :
9391 : const char *legacyCreationOp =
9392 149 : CSLFetchNameValueDef(papszOptions, "GEOMETRY_ENCODING", "CF_1.8");
9393 298 : std::string legacyCreationOp_s = std::string(legacyCreationOp);
9394 :
9395 : // Check legacy creation op FIRST
9396 :
9397 149 : bool legacyCreateMode = false;
9398 :
9399 149 : if (nXSize != 0 || nYSize != 0 || nBandsIn != 0)
9400 : {
9401 56 : legacyCreateMode = true;
9402 : }
9403 93 : else if (legacyCreationOp_s == "CF_1.8")
9404 : {
9405 76 : legacyCreateMode = false;
9406 : }
9407 :
9408 17 : else if (legacyCreationOp_s == "WKT")
9409 : {
9410 17 : legacyCreateMode = true;
9411 : }
9412 :
9413 : else
9414 : {
9415 0 : CPLError(
9416 : CE_Failure, CPLE_NotSupported,
9417 : "Dataset creation option GEOMETRY_ENCODING=%s is not supported.",
9418 : legacyCreationOp_s.c_str());
9419 0 : return nullptr;
9420 : }
9421 :
9422 298 : CPLStringList aosOptions(CSLDuplicate(papszOptions));
9423 284 : if (aosOptions.FetchNameValue("FORMAT") == nullptr &&
9424 135 : (eType == GDT_UInt16 || eType == GDT_UInt32 || eType == GDT_UInt64 ||
9425 : eType == GDT_Int64))
9426 : {
9427 10 : CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
9428 10 : aosOptions.SetNameValue("FORMAT", "NC4");
9429 : }
9430 :
9431 298 : CPLStringList aosBandNames;
9432 149 : if (const char *pszBandNames = aosOptions.FetchNameValue("BAND_NAMES"))
9433 : {
9434 : aosBandNames =
9435 2 : CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);
9436 :
9437 2 : if (aosBandNames.Count() != nBandsIn)
9438 : {
9439 1 : CPLError(CE_Failure, CPLE_OpenFailed,
9440 : "Attempted to create netCDF with %d bands but %d names "
9441 : "provided in BAND_NAMES.",
9442 : nBandsIn, aosBandNames.Count());
9443 :
9444 1 : return nullptr;
9445 : }
9446 : }
9447 :
9448 296 : CPLMutexHolderD(&hNCMutex);
9449 :
9450 148 : auto poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize, nBandsIn,
9451 148 : aosOptions.List());
9452 :
9453 148 : if (!poDS)
9454 42 : return nullptr;
9455 :
9456 106 : if (!legacyCreateMode)
9457 : {
9458 36 : poDS->bSGSupport = true;
9459 36 : poDS->vcdf.enableFullVirtualMode();
9460 : }
9461 :
9462 : else
9463 : {
9464 70 : poDS->bSGSupport = false;
9465 : }
9466 :
9467 : // Should we write signed or unsigned byte?
9468 : // TODO should this only be done in Create()
9469 106 : poDS->bSignedData = true;
9470 106 : const char *pszValue = CSLFetchNameValueDef(papszOptions, "PIXELTYPE", "");
9471 106 : if (eType == GDT_UInt8 && !EQUAL(pszValue, "SIGNEDBYTE"))
9472 15 : poDS->bSignedData = false;
9473 :
9474 : // Add Conventions, GDAL info and history.
9475 106 : if (poDS->cdfid >= 0)
9476 : {
9477 : const char *CF_Vector_Conv =
9478 172 : poDS->bSGSupport ||
9479 : // Use of variable length strings require CF-1.8
9480 68 : EQUAL(aosOptions.FetchNameValueDef("FORMAT", ""), "NC4")
9481 : ? NCDF_CONVENTIONS_CF_V1_8
9482 172 : : NCDF_CONVENTIONS_CF_V1_6;
9483 104 : poDS->bWriteGDALVersion = CPLTestBool(
9484 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
9485 104 : poDS->bWriteGDALHistory = CPLTestBool(
9486 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
9487 104 : NCDFAddGDALHistory(poDS->cdfid, pszFilename, poDS->bWriteGDALVersion,
9488 104 : poDS->bWriteGDALHistory, "", "Create",
9489 : (nBandsIn == 0) ? CF_Vector_Conv
9490 : : GDAL_DEFAULT_NCDF_CONVENTIONS);
9491 : }
9492 :
9493 : // Define bands.
9494 197 : for (int iBand = 1; iBand <= nBandsIn; iBand++)
9495 : {
9496 : const char *pszBandName =
9497 91 : aosBandNames.empty() ? nullptr : aosBandNames[iBand - 1];
9498 :
9499 91 : poDS->SetBand(iBand, new netCDFRasterBand(
9500 91 : netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS,
9501 91 : eType, iBand, poDS->bSignedData, pszBandName));
9502 : }
9503 :
9504 106 : CPLDebug("GDAL_netCDF", "netCDFDataset::Create(%s, ...) done", pszFilename);
9505 : // Return same dataset.
9506 106 : return poDS;
9507 : }
9508 :
9509 : template <class T>
9510 102 : static CPLErr NCDFCopyBand(GDALRasterBand *poSrcBand, GDALRasterBand *poDstBand,
9511 : int nXSize, int nYSize, GDALProgressFunc pfnProgress,
9512 : void *pProgressData)
9513 : {
9514 102 : const GDALDataType eDT = poSrcBand->GetRasterDataType();
9515 102 : T *patScanline = static_cast<T *>(VSI_MALLOC2_VERBOSE(nXSize, sizeof(T)));
9516 102 : CPLErr eErr = patScanline ? CE_None : CE_Failure;
9517 :
9518 6519 : for (int iLine = 0; iLine < nYSize && eErr == CE_None; iLine++)
9519 : {
9520 6417 : eErr = poSrcBand->RasterIO(GF_Read, 0, iLine, nXSize, 1, patScanline,
9521 : nXSize, 1, eDT, 0, 0, nullptr);
9522 6417 : if (eErr != CE_None)
9523 : {
9524 0 : CPLDebug(
9525 : "GDAL_netCDF",
9526 : "NCDFCopyBand(), poSrcBand->RasterIO() returned error code %d",
9527 : eErr);
9528 : }
9529 : else
9530 : {
9531 6417 : eErr =
9532 : poDstBand->RasterIO(GF_Write, 0, iLine, nXSize, 1, patScanline,
9533 : nXSize, 1, eDT, 0, 0, nullptr);
9534 6417 : if (eErr != CE_None)
9535 0 : CPLDebug("GDAL_netCDF",
9536 : "NCDFCopyBand(), poDstBand->RasterIO() returned error "
9537 : "code %d",
9538 : eErr);
9539 : }
9540 :
9541 6417 : if (nYSize > 10 && (iLine % (nYSize / 10) == 1))
9542 : {
9543 367 : if (!pfnProgress(1.0 * iLine / nYSize, nullptr, pProgressData))
9544 : {
9545 0 : eErr = CE_Failure;
9546 0 : CPLError(CE_Failure, CPLE_UserInterrupt,
9547 : "User terminated CreateCopy()");
9548 : }
9549 : }
9550 : }
9551 :
9552 102 : CPLFree(patScanline);
9553 :
9554 102 : pfnProgress(1.0, nullptr, pProgressData);
9555 :
9556 102 : return eErr;
9557 : }
9558 :
9559 : /************************************************************************/
9560 : /* CreateCopy() */
9561 : /************************************************************************/
9562 :
9563 : GDALDataset *
9564 100 : netCDFDataset::CreateCopy(const char *pszFilename, GDALDataset *poSrcDS,
9565 : CPL_UNUSED int bStrict, CSLConstList papszOptions,
9566 : GDALProgressFunc pfnProgress, void *pProgressData)
9567 : {
9568 200 : CPLMutexHolderD(&hNCMutex);
9569 :
9570 100 : CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::CreateCopy(%s, ...)",
9571 : pszFilename);
9572 :
9573 100 : if (poSrcDS->GetRootGroup())
9574 : {
9575 12 : auto poDrv = GDALDriver::FromHandle(GDALGetDriverByName("netCDF"));
9576 12 : if (poDrv)
9577 : {
9578 12 : return poDrv->DefaultCreateCopy(pszFilename, poSrcDS, bStrict,
9579 : papszOptions, pfnProgress,
9580 12 : pProgressData);
9581 : }
9582 : }
9583 :
9584 88 : const int nBands = poSrcDS->GetRasterCount();
9585 88 : const int nXSize = poSrcDS->GetRasterXSize();
9586 88 : const int nYSize = poSrcDS->GetRasterYSize();
9587 88 : const char *pszWKT = poSrcDS->GetProjectionRef();
9588 :
9589 : // Check input bands for errors.
9590 88 : if (nBands == 0)
9591 : {
9592 1 : CPLError(CE_Failure, CPLE_NotSupported,
9593 : "NetCDF driver does not support "
9594 : "source datasets with zero bands.");
9595 1 : return nullptr;
9596 : }
9597 :
9598 87 : GDALDataType eDT = GDT_Unknown;
9599 87 : GDALRasterBand *poSrcBand = nullptr;
9600 203 : for (int iBand = 1; iBand <= nBands; iBand++)
9601 : {
9602 120 : poSrcBand = poSrcDS->GetRasterBand(iBand);
9603 120 : eDT = poSrcBand->GetRasterDataType();
9604 120 : if (eDT == GDT_Unknown || GDALDataTypeIsComplex(eDT))
9605 : {
9606 4 : CPLError(CE_Failure, CPLE_NotSupported,
9607 : "NetCDF driver does not support source dataset with band "
9608 : "of complex type.");
9609 4 : return nullptr;
9610 : }
9611 : }
9612 :
9613 166 : CPLStringList aosBandNames;
9614 83 : if (const char *pszBandNames =
9615 83 : CSLFetchNameValue(papszOptions, "BAND_NAMES"))
9616 : {
9617 : aosBandNames =
9618 2 : CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);
9619 :
9620 2 : if (aosBandNames.Count() != nBands)
9621 : {
9622 1 : CPLError(CE_Failure, CPLE_OpenFailed,
9623 : "Attempted to create netCDF with %d bands but %d names "
9624 : "provided in BAND_NAMES.",
9625 : nBands, aosBandNames.Count());
9626 :
9627 1 : return nullptr;
9628 : }
9629 : }
9630 :
9631 82 : if (!pfnProgress(0.0, nullptr, pProgressData))
9632 0 : return nullptr;
9633 :
9634 : // Same as in Create().
9635 164 : CPLStringList aosOptions(CSLDuplicate(papszOptions));
9636 155 : if (aosOptions.FetchNameValue("FORMAT") == nullptr &&
9637 73 : (eDT == GDT_UInt16 || eDT == GDT_UInt32 || eDT == GDT_UInt64 ||
9638 : eDT == GDT_Int64))
9639 : {
9640 6 : CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
9641 6 : aosOptions.SetNameValue("FORMAT", "NC4");
9642 : }
9643 82 : netCDFDataset *poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize,
9644 82 : nBands, aosOptions.List());
9645 82 : if (!poDS)
9646 13 : return nullptr;
9647 :
9648 : // Copy global metadata.
9649 : // Add Conventions, GDAL info and history.
9650 69 : CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid, NC_GLOBAL, nullptr);
9651 69 : const bool bWriteGDALVersion = CPLTestBool(
9652 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
9653 69 : const bool bWriteGDALHistory = CPLTestBool(
9654 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
9655 69 : NCDFAddGDALHistory(
9656 : poDS->cdfid, pszFilename, bWriteGDALVersion, bWriteGDALHistory,
9657 69 : poSrcDS->GetMetadataItem("NC_GLOBAL#history"), "CreateCopy",
9658 69 : poSrcDS->GetMetadataItem("NC_GLOBAL#Conventions"));
9659 :
9660 69 : pfnProgress(0.1, nullptr, pProgressData);
9661 :
9662 : // Check for extra dimensions.
9663 69 : int nDim = 2;
9664 : CPLStringList aosExtraDimNames =
9665 138 : NCDFTokenizeArray(poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
9666 :
9667 69 : if (!aosExtraDimNames.empty())
9668 : {
9669 5 : size_t nDimSizeTot = 1;
9670 : // first make sure dimensions lengths compatible with band count
9671 : // for( int i=0; i<CSLCount(papszExtraDimNames ); i++ ) {
9672 13 : for (int i = aosExtraDimNames.size() - 1; i >= 0; i--)
9673 : {
9674 : char szTemp[NC_MAX_NAME + 32 + 1];
9675 8 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
9676 : aosExtraDimNames[i]);
9677 : const CPLStringList aosExtraDimValues =
9678 8 : NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
9679 8 : const size_t nDimSize = atol(aosExtraDimValues[0]);
9680 8 : nDimSizeTot *= nDimSize;
9681 : }
9682 5 : if (nDimSizeTot == (size_t)nBands)
9683 : {
9684 5 : nDim = 2 + aosExtraDimNames.size();
9685 : }
9686 : else
9687 : {
9688 : // if nBands != #bands computed raise a warning
9689 : // just issue a debug message, because it was probably intentional
9690 0 : CPLDebug("GDAL_netCDF",
9691 : "Warning: Number of bands (%d) is not compatible with "
9692 : "dimensions "
9693 : "(total=%ld names=%s)",
9694 : nBands, (long)nDimSizeTot,
9695 0 : poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
9696 0 : aosExtraDimNames.clear();
9697 : }
9698 : }
9699 :
9700 69 : int *panDimIds = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
9701 69 : int *panBandDimPos = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
9702 :
9703 : nc_type nVarType;
9704 69 : int *panBandZLev = nullptr;
9705 69 : int *panDimVarIds = nullptr;
9706 :
9707 69 : if (nDim > 2)
9708 : {
9709 5 : panBandZLev = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
9710 5 : panDimVarIds = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
9711 :
9712 : // Define all dims.
9713 13 : for (int i = aosExtraDimNames.size() - 1; i >= 0; i--)
9714 : {
9715 8 : poDS->papszDimName.AddString(aosExtraDimNames[i]);
9716 : char szTemp[NC_MAX_NAME + 32 + 1];
9717 8 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
9718 : aosExtraDimNames[i]);
9719 : const CPLStringList aosExtraDimValues =
9720 16 : NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
9721 : const int nDimSize =
9722 8 : aosExtraDimValues.empty() ? 0 : atoi(aosExtraDimValues[0]);
9723 : // nc_type is an enum in netcdf-3, needs casting.
9724 0 : nVarType = static_cast<nc_type>(
9725 8 : aosExtraDimValues.size() >= 2 ? atol(aosExtraDimValues[1]) : 0);
9726 8 : panBandZLev[i] = nDimSize;
9727 8 : panBandDimPos[i + 2] = i; // Save Position of ZDim.
9728 :
9729 : // Define dim.
9730 8 : int status = nc_def_dim(poDS->cdfid, aosExtraDimNames[i], nDimSize,
9731 8 : &(panDimIds[i]));
9732 8 : NCDF_ERR(status);
9733 :
9734 : // Define dim var.
9735 8 : int anDim[1] = {panDimIds[i]};
9736 8 : status = nc_def_var(poDS->cdfid, aosExtraDimNames[i], nVarType, 1,
9737 8 : anDim, &(panDimVarIds[i]));
9738 8 : NCDF_ERR(status);
9739 :
9740 : // Add dim metadata, using global var# items.
9741 8 : snprintf(szTemp, sizeof(szTemp), "%s#", aosExtraDimNames[i]);
9742 8 : CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid,
9743 8 : panDimVarIds[i], szTemp);
9744 : }
9745 : }
9746 :
9747 : // Copy GeoTransform and Projection.
9748 :
9749 : // Copy geolocation info.
9750 : CSLConstList papszGeolocationInfo =
9751 69 : poSrcDS->GetMetadata(GDAL_MDD_GEOLOCATION);
9752 69 : if (papszGeolocationInfo != nullptr)
9753 5 : poDS->GDALPamDataset::SetMetadata(papszGeolocationInfo,
9754 : GDAL_MDD_GEOLOCATION);
9755 :
9756 : // Copy geotransform.
9757 69 : bool bGotGeoTransform = false;
9758 69 : GDALGeoTransform gt;
9759 69 : CPLErr eErr = poSrcDS->GetGeoTransform(gt);
9760 69 : if (eErr == CE_None)
9761 : {
9762 51 : poDS->SetGeoTransform(gt);
9763 : // Disable AddProjectionVars() from being called.
9764 51 : bGotGeoTransform = true;
9765 51 : poDS->m_bHasGeoTransform = false;
9766 : }
9767 :
9768 : // Copy projection.
9769 69 : void *pScaledProgress = nullptr;
9770 69 : if (bGotGeoTransform || (pszWKT && pszWKT[0] != 0))
9771 : {
9772 52 : poDS->SetProjection(pszWKT ? pszWKT : "");
9773 :
9774 : // Now we can call AddProjectionVars() directly.
9775 52 : poDS->m_bHasGeoTransform = bGotGeoTransform;
9776 52 : poDS->AddProjectionVars(true, nullptr, nullptr);
9777 : pScaledProgress =
9778 52 : GDALCreateScaledProgress(0.1, 0.25, pfnProgress, pProgressData);
9779 52 : poDS->AddProjectionVars(false, GDALScaledProgress, pScaledProgress);
9780 52 : GDALDestroyScaledProgress(pScaledProgress);
9781 : }
9782 : else
9783 : {
9784 17 : poDS->bBottomUp =
9785 17 : CPL_TO_BOOL(CSLFetchBoolean(papszOptions, "WRITE_BOTTOMUP", TRUE));
9786 17 : if (papszGeolocationInfo)
9787 : {
9788 4 : poDS->AddProjectionVars(true, nullptr, nullptr);
9789 4 : poDS->AddProjectionVars(false, nullptr, nullptr);
9790 : }
9791 : }
9792 :
9793 : // Save X,Y dim positions.
9794 69 : panDimIds[nDim - 1] = poDS->nXDimID;
9795 69 : panBandDimPos[0] = nDim - 1;
9796 69 : panDimIds[nDim - 2] = poDS->nYDimID;
9797 69 : panBandDimPos[1] = nDim - 2;
9798 :
9799 : // Write extra dim values - after projection for optimization.
9800 69 : if (nDim > 2)
9801 : {
9802 : // Make sure we are in data mode.
9803 5 : poDS->SetDefineMode(false);
9804 13 : for (int i = aosExtraDimNames.size() - 1; i >= 0; i--)
9805 : {
9806 : char szTemp[NC_MAX_NAME + 32 + 1];
9807 8 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_VALUES",
9808 : aosExtraDimNames[i]);
9809 8 : if (poSrcDS->GetMetadataItem(szTemp) != nullptr)
9810 : {
9811 8 : NCDFPut1DVar(poDS->cdfid, panDimVarIds[i],
9812 8 : poSrcDS->GetMetadataItem(szTemp));
9813 : }
9814 : }
9815 : }
9816 :
9817 69 : pfnProgress(0.25, nullptr, pProgressData);
9818 :
9819 : // Define Bands.
9820 69 : netCDFRasterBand *poBand = nullptr;
9821 69 : int nBandID = -1;
9822 :
9823 171 : for (int iBand = 1; iBand <= nBands; iBand++)
9824 : {
9825 102 : CPLDebug("GDAL_netCDF", "creating band # %d/%d nDim = %d", iBand,
9826 : nBands, nDim);
9827 :
9828 102 : poSrcBand = poSrcDS->GetRasterBand(iBand);
9829 102 : eDT = poSrcBand->GetRasterDataType();
9830 :
9831 : // Get var name from NETCDF_VARNAME.
9832 : const char *pszNETCDF_VARNAME =
9833 102 : poSrcBand->GetMetadataItem("NETCDF_VARNAME");
9834 : char szBandName[NC_MAX_NAME + 1];
9835 102 : if (!aosBandNames.empty())
9836 : {
9837 2 : snprintf(szBandName, sizeof(szBandName), "%s",
9838 : aosBandNames[iBand - 1]);
9839 : }
9840 100 : else if (pszNETCDF_VARNAME)
9841 : {
9842 37 : if (nBands > 1 && aosExtraDimNames.empty())
9843 0 : snprintf(szBandName, sizeof(szBandName), "%s%d",
9844 : pszNETCDF_VARNAME, iBand);
9845 : else
9846 37 : snprintf(szBandName, sizeof(szBandName), "%s",
9847 : pszNETCDF_VARNAME);
9848 : }
9849 : else
9850 : {
9851 63 : szBandName[0] = '\0';
9852 : }
9853 :
9854 : // Get long_name from <var>#long_name.
9855 102 : const char *pszLongName = "";
9856 102 : if (pszNETCDF_VARNAME)
9857 : {
9858 : pszLongName =
9859 74 : poSrcDS->GetMetadataItem(std::string(pszNETCDF_VARNAME)
9860 37 : .append("#")
9861 37 : .append(CF_LNG_NAME)
9862 37 : .c_str());
9863 37 : if (!pszLongName)
9864 25 : pszLongName = "";
9865 : }
9866 :
9867 102 : constexpr bool bSignedData = false;
9868 :
9869 102 : if (nDim > 2)
9870 27 : poBand = new netCDFRasterBand(
9871 27 : netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
9872 : bSignedData, szBandName, pszLongName, nBandID, nDim, iBand - 1,
9873 27 : panBandZLev, panBandDimPos, panDimIds);
9874 : else
9875 75 : poBand = new netCDFRasterBand(
9876 75 : netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
9877 75 : bSignedData, szBandName, pszLongName);
9878 :
9879 102 : poDS->SetBand(iBand, poBand);
9880 :
9881 : // Set nodata value, if any.
9882 102 : GDALCopyNoDataValue(poBand, poSrcBand);
9883 :
9884 : // Copy Metadata for band.
9885 102 : CopyMetadata(nullptr, poSrcDS->GetRasterBand(iBand), poBand,
9886 : poDS->cdfid, poBand->nZId);
9887 :
9888 : // If more than 2D pass the first band's netcdf var ID to subsequent
9889 : // bands.
9890 102 : if (nDim > 2)
9891 27 : nBandID = poBand->nZId;
9892 : }
9893 :
9894 : // Write projection variable to band variable.
9895 69 : poDS->AddGridMappingRef();
9896 :
9897 69 : pfnProgress(0.5, nullptr, pProgressData);
9898 :
9899 : // Write bands.
9900 :
9901 : // Make sure we are in data mode.
9902 69 : poDS->SetDefineMode(false);
9903 :
9904 69 : double dfTemp = 0.5;
9905 :
9906 69 : eErr = CE_None;
9907 :
9908 171 : for (int iBand = 1; iBand <= nBands && eErr == CE_None; iBand++)
9909 : {
9910 102 : const double dfTemp2 = dfTemp + 0.4 / nBands;
9911 102 : pScaledProgress = GDALCreateScaledProgress(dfTemp, dfTemp2, pfnProgress,
9912 : pProgressData);
9913 102 : dfTemp = dfTemp2;
9914 :
9915 102 : CPLDebug("GDAL_netCDF", "copying band data # %d/%d ", iBand, nBands);
9916 :
9917 102 : poSrcBand = poSrcDS->GetRasterBand(iBand);
9918 102 : eDT = poSrcBand->GetRasterDataType();
9919 :
9920 102 : GDALRasterBand *poDstBand = poDS->GetRasterBand(iBand);
9921 :
9922 : // Copy band data.
9923 102 : if (eDT == GDT_UInt8)
9924 : {
9925 61 : CPLDebug("GDAL_netCDF", "GByte Band#%d", iBand);
9926 61 : eErr = NCDFCopyBand<GByte>(poSrcBand, poDstBand, nXSize, nYSize,
9927 : GDALScaledProgress, pScaledProgress);
9928 : }
9929 41 : else if (eDT == GDT_Int8)
9930 : {
9931 1 : CPLDebug("GDAL_netCDF", "GInt8 Band#%d", iBand);
9932 1 : eErr = NCDFCopyBand<GInt8>(poSrcBand, poDstBand, nXSize, nYSize,
9933 : GDALScaledProgress, pScaledProgress);
9934 : }
9935 40 : else if (eDT == GDT_UInt16)
9936 : {
9937 2 : CPLDebug("GDAL_netCDF", "GUInt16 Band#%d", iBand);
9938 2 : eErr = NCDFCopyBand<GInt16>(poSrcBand, poDstBand, nXSize, nYSize,
9939 : GDALScaledProgress, pScaledProgress);
9940 : }
9941 38 : else if (eDT == GDT_Int16)
9942 : {
9943 5 : CPLDebug("GDAL_netCDF", "GInt16 Band#%d", iBand);
9944 5 : eErr = NCDFCopyBand<GUInt16>(poSrcBand, poDstBand, nXSize, nYSize,
9945 : GDALScaledProgress, pScaledProgress);
9946 : }
9947 33 : else if (eDT == GDT_UInt32)
9948 : {
9949 2 : CPLDebug("GDAL_netCDF", "GUInt32 Band#%d", iBand);
9950 2 : eErr = NCDFCopyBand<GUInt32>(poSrcBand, poDstBand, nXSize, nYSize,
9951 : GDALScaledProgress, pScaledProgress);
9952 : }
9953 31 : else if (eDT == GDT_Int32)
9954 : {
9955 18 : CPLDebug("GDAL_netCDF", "GInt32 Band#%d", iBand);
9956 18 : eErr = NCDFCopyBand<GInt32>(poSrcBand, poDstBand, nXSize, nYSize,
9957 : GDALScaledProgress, pScaledProgress);
9958 : }
9959 13 : else if (eDT == GDT_UInt64)
9960 : {
9961 2 : CPLDebug("GDAL_netCDF", "GUInt64 Band#%d", iBand);
9962 2 : eErr = NCDFCopyBand<std::uint64_t>(poSrcBand, poDstBand, nXSize,
9963 : nYSize, GDALScaledProgress,
9964 : pScaledProgress);
9965 : }
9966 11 : else if (eDT == GDT_Int64)
9967 : {
9968 2 : CPLDebug("GDAL_netCDF", "GInt64 Band#%d", iBand);
9969 : eErr =
9970 2 : NCDFCopyBand<std::int64_t>(poSrcBand, poDstBand, nXSize, nYSize,
9971 : GDALScaledProgress, pScaledProgress);
9972 : }
9973 9 : else if (eDT == GDT_Float32)
9974 : {
9975 7 : CPLDebug("GDAL_netCDF", "float Band#%d", iBand);
9976 7 : eErr = NCDFCopyBand<float>(poSrcBand, poDstBand, nXSize, nYSize,
9977 : GDALScaledProgress, pScaledProgress);
9978 : }
9979 2 : else if (eDT == GDT_Float64)
9980 : {
9981 2 : CPLDebug("GDAL_netCDF", "double Band#%d", iBand);
9982 2 : eErr = NCDFCopyBand<double>(poSrcBand, poDstBand, nXSize, nYSize,
9983 : GDALScaledProgress, pScaledProgress);
9984 : }
9985 : else
9986 : {
9987 0 : CPLError(CE_Failure, CPLE_NotSupported,
9988 : "The NetCDF driver does not support GDAL data type %d",
9989 : eDT);
9990 : }
9991 :
9992 102 : GDALDestroyScaledProgress(pScaledProgress);
9993 : }
9994 :
9995 69 : delete (poDS);
9996 :
9997 69 : CPLFree(panDimIds);
9998 69 : CPLFree(panBandDimPos);
9999 69 : CPLFree(panBandZLev);
10000 69 : CPLFree(panDimVarIds);
10001 :
10002 69 : if (eErr != CE_None)
10003 0 : return nullptr;
10004 :
10005 69 : pfnProgress(0.95, nullptr, pProgressData);
10006 :
10007 : // Re-open dataset so we can return it.
10008 138 : CPLStringList aosOpenOptions;
10009 69 : aosOpenOptions.AddString("VARIABLES_AS_BANDS=YES");
10010 69 : GDALOpenInfo oOpenInfo(pszFilename, GA_Update);
10011 69 : oOpenInfo.nOpenFlags = GDAL_OF_RASTER | GDAL_OF_UPDATE;
10012 69 : oOpenInfo.papszOpenOptions = aosOpenOptions.List();
10013 69 : auto poRetDS = Open(&oOpenInfo);
10014 :
10015 : // PAM cloning is disabled. See bug #4244.
10016 : // if( poDS )
10017 : // poDS->CloneInfo(poSrcDS, GCIF_PAM_DEFAULT);
10018 :
10019 69 : pfnProgress(1.0, nullptr, pProgressData);
10020 :
10021 69 : return poRetDS;
10022 : }
10023 :
10024 : // Note: some logic depends on bIsProjected and bIsGeoGraphic.
10025 : // May not be known when Create() is called, see AddProjectionVars().
10026 345 : bool netCDFDataset::ProcessCreationOptions()
10027 : {
10028 345 : const char *pszConfig = aosCreationOptions.FetchNameValue("CONFIG_FILE");
10029 345 : if (pszConfig != nullptr)
10030 : {
10031 26 : if (oWriterConfig.Parse(pszConfig))
10032 : {
10033 : // Override dataset creation options from the config file
10034 2 : for (const auto &[osName, osValue] :
10035 5 : oWriterConfig.m_oDatasetCreationOptions)
10036 : {
10037 1 : aosCreationOptions.SetNameValue(osName, osValue);
10038 : }
10039 : }
10040 : else
10041 : {
10042 23 : return false;
10043 : }
10044 : }
10045 :
10046 : // File format.
10047 322 : eFormat = NCDF_FORMAT_NC;
10048 322 : const char *pszValue = aosCreationOptions.FetchNameValue("FORMAT");
10049 322 : if (pszValue != nullptr)
10050 : {
10051 150 : if (EQUAL(pszValue, "NC"))
10052 : {
10053 3 : eFormat = NCDF_FORMAT_NC;
10054 : }
10055 : #ifdef NETCDF_HAS_NC2
10056 147 : else if (EQUAL(pszValue, "NC2"))
10057 : {
10058 1 : eFormat = NCDF_FORMAT_NC2;
10059 : }
10060 : #endif
10061 146 : else if (EQUAL(pszValue, "NC4"))
10062 : {
10063 142 : eFormat = NCDF_FORMAT_NC4;
10064 : }
10065 4 : else if (EQUAL(pszValue, "NC4C"))
10066 : {
10067 4 : eFormat = NCDF_FORMAT_NC4C;
10068 : }
10069 : else
10070 : {
10071 0 : CPLError(CE_Warning, CPLE_NotSupported,
10072 : "FORMAT=%s in not supported, using the default NC format.",
10073 : pszValue);
10074 : }
10075 : }
10076 :
10077 : // COMPRESS option.
10078 322 : pszValue = aosCreationOptions.FetchNameValue("COMPRESS");
10079 322 : if (pszValue != nullptr)
10080 : {
10081 3 : if (EQUAL(pszValue, "NONE"))
10082 : {
10083 1 : eCompress = NCDF_COMPRESS_NONE;
10084 : }
10085 2 : else if (EQUAL(pszValue, "DEFLATE"))
10086 : {
10087 2 : eCompress = NCDF_COMPRESS_DEFLATE;
10088 2 : if (!((eFormat == NCDF_FORMAT_NC4) ||
10089 2 : (eFormat == NCDF_FORMAT_NC4C)))
10090 : {
10091 1 : CPLError(CE_Warning, CPLE_IllegalArg,
10092 : "NOTICE: Format set to NC4C because compression is "
10093 : "set to DEFLATE.");
10094 1 : eFormat = NCDF_FORMAT_NC4C;
10095 : }
10096 : }
10097 : else
10098 : {
10099 0 : CPLError(CE_Warning, CPLE_NotSupported,
10100 : "COMPRESS=%s is not supported.", pszValue);
10101 : }
10102 : }
10103 :
10104 : // ZLEVEL option.
10105 322 : pszValue = aosCreationOptions.FetchNameValue("ZLEVEL");
10106 322 : if (pszValue != nullptr)
10107 : {
10108 1 : nZLevel = atoi(pszValue);
10109 1 : if (!(nZLevel >= 1 && nZLevel <= 9))
10110 : {
10111 0 : CPLError(CE_Warning, CPLE_IllegalArg,
10112 : "ZLEVEL=%s value not recognised, ignoring.", pszValue);
10113 0 : nZLevel = NCDF_DEFLATE_LEVEL;
10114 : }
10115 : }
10116 :
10117 : // CHUNKING option.
10118 322 : bChunking = aosCreationOptions.FetchBool("CHUNKING", true);
10119 :
10120 : // MULTIPLE_LAYERS option.
10121 : const char *pszMultipleLayerBehavior =
10122 322 : aosCreationOptions.FetchNameValueDef("MULTIPLE_LAYERS", "NO");
10123 : const char *pszGeometryEnc =
10124 322 : aosCreationOptions.FetchNameValueDef("GEOMETRY_ENCODING", "CF_1.8");
10125 322 : if (EQUAL(pszMultipleLayerBehavior, "NO") ||
10126 4 : EQUAL(pszGeometryEnc, "CF_1.8"))
10127 : {
10128 318 : eMultipleLayerBehavior = SINGLE_LAYER;
10129 : }
10130 4 : else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_FILES"))
10131 : {
10132 3 : eMultipleLayerBehavior = SEPARATE_FILES;
10133 : }
10134 1 : else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_GROUPS"))
10135 : {
10136 1 : if (eFormat == NCDF_FORMAT_NC4)
10137 : {
10138 1 : eMultipleLayerBehavior = SEPARATE_GROUPS;
10139 : }
10140 : else
10141 : {
10142 0 : CPLError(CE_Warning, CPLE_IllegalArg,
10143 : "MULTIPLE_LAYERS=%s is recognised only with FORMAT=NC4",
10144 : pszMultipleLayerBehavior);
10145 : }
10146 : }
10147 : else
10148 : {
10149 0 : CPLError(CE_Warning, CPLE_IllegalArg,
10150 : "MULTIPLE_LAYERS=%s not recognised", pszMultipleLayerBehavior);
10151 : }
10152 :
10153 : // Set nCreateMode based on eFormat.
10154 322 : switch (eFormat)
10155 : {
10156 : #ifdef NETCDF_HAS_NC2
10157 1 : case NCDF_FORMAT_NC2:
10158 1 : nCreateMode = NC_CLOBBER | NC_64BIT_OFFSET;
10159 1 : break;
10160 : #endif
10161 142 : case NCDF_FORMAT_NC4:
10162 142 : nCreateMode = NC_CLOBBER | NC_NETCDF4;
10163 142 : break;
10164 5 : case NCDF_FORMAT_NC4C:
10165 5 : nCreateMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
10166 5 : break;
10167 174 : case NCDF_FORMAT_NC:
10168 : default:
10169 174 : nCreateMode = NC_CLOBBER;
10170 174 : break;
10171 : }
10172 :
10173 322 : CPLDebug("GDAL_netCDF", "file options: format=%d compress=%d zlevel=%d",
10174 322 : eFormat, eCompress, nZLevel);
10175 :
10176 322 : return true;
10177 : }
10178 :
10179 293 : int netCDFDataset::DefVarDeflate(int nVarId, bool bChunkingArg) const
10180 : {
10181 293 : if (eCompress == NCDF_COMPRESS_DEFLATE)
10182 : {
10183 : // Must set chunk size to avoid huge performance hit (set
10184 : // bChunkingArg=TRUE)
10185 : // perhaps another solution it to change the chunk cache?
10186 : // http://www.unidata.ucar.edu/software/netcdf/docs/netcdf.html#Chunk-Cache
10187 : // TODO: make sure this is okay.
10188 2 : CPLDebug("GDAL_netCDF", "DefVarDeflate(%d, %d) nZlevel=%d", nVarId,
10189 2 : static_cast<int>(bChunkingArg), nZLevel);
10190 :
10191 2 : int status = nc_def_var_deflate(cdfid, nVarId, 1, 1, nZLevel);
10192 2 : NCDF_ERR(status);
10193 :
10194 2 : if (status == NC_NOERR && bChunkingArg && bChunking)
10195 : {
10196 : // set chunking to be 1 for all dims, except X dim
10197 : // size_t chunksize[] = { 1, (size_t)nRasterXSize };
10198 : size_t chunksize[MAX_NC_DIMS];
10199 : int nd;
10200 2 : nc_inq_varndims(cdfid, nVarId, &nd);
10201 2 : chunksize[0] = (size_t)1;
10202 2 : chunksize[1] = (size_t)1;
10203 2 : for (int i = 2; i < nd; i++)
10204 0 : chunksize[i] = (size_t)1;
10205 2 : chunksize[nd - 1] = (size_t)nRasterXSize;
10206 :
10207 : // Config options just for testing purposes
10208 : const char *pszBlockXSize =
10209 2 : CPLGetConfigOption("BLOCKXSIZE", nullptr);
10210 2 : if (pszBlockXSize)
10211 0 : chunksize[nd - 1] = (size_t)atoi(pszBlockXSize);
10212 :
10213 : const char *pszBlockYSize =
10214 2 : CPLGetConfigOption("BLOCKYSIZE", nullptr);
10215 2 : if (nd >= 2 && pszBlockYSize)
10216 0 : chunksize[nd - 2] = (size_t)atoi(pszBlockYSize);
10217 :
10218 2 : CPLDebug("GDAL_netCDF",
10219 : "DefVarDeflate() chunksize={%ld, %ld} chunkX=%ld nd=%d",
10220 2 : (long)chunksize[0], (long)chunksize[1],
10221 2 : (long)chunksize[nd - 1], nd);
10222 : #ifdef NCDF_DEBUG
10223 : for (int i = 0; i < nd; i++)
10224 : CPLDebug("GDAL_netCDF", "DefVarDeflate() chunk[%d]=%ld", i,
10225 : chunksize[i]);
10226 : #endif
10227 :
10228 2 : status = nc_def_var_chunking(cdfid, nVarId, NC_CHUNKED, chunksize);
10229 2 : NCDF_ERR(status);
10230 : }
10231 : else
10232 : {
10233 0 : CPLDebug("GDAL_netCDF", "chunksize not set");
10234 : }
10235 2 : return status;
10236 : }
10237 291 : return NC_NOERR;
10238 : }
10239 :
10240 : /************************************************************************/
10241 : /* NCDFUnloadDriver() */
10242 : /************************************************************************/
10243 :
10244 11 : static void NCDFUnloadDriver(CPL_UNUSED GDALDriver *poDriver)
10245 : {
10246 11 : if (hNCMutex != nullptr)
10247 7 : CPLDestroyMutex(hNCMutex);
10248 11 : hNCMutex = nullptr;
10249 11 : }
10250 :
10251 : /************************************************************************/
10252 : /* GDALRegister_netCDF() */
10253 : /************************************************************************/
10254 :
10255 : class GDALnetCDFDriver final : public GDALDriver
10256 : {
10257 : public:
10258 21 : GDALnetCDFDriver() = default;
10259 :
10260 : const char *GetMetadataItem(const char *pszName,
10261 : const char *pszDomain) override;
10262 :
10263 122 : CSLConstList GetMetadata(const char *pszDomain) override
10264 : {
10265 244 : std::lock_guard oLock(m_oMutex);
10266 122 : InitializeDCAPVirtualIO();
10267 244 : return GDALDriver::GetMetadata(pszDomain);
10268 : }
10269 :
10270 : private:
10271 : std::recursive_mutex m_oMutex{};
10272 : bool m_bInitialized = false;
10273 :
10274 135 : void InitializeDCAPVirtualIO()
10275 : {
10276 135 : if (!m_bInitialized)
10277 : {
10278 12 : m_bInitialized = true;
10279 :
10280 : #ifdef ENABLE_UFFD
10281 12 : if (CPLIsUserFaultMappingSupported())
10282 : {
10283 12 : SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
10284 : }
10285 : #endif
10286 : }
10287 135 : }
10288 : };
10289 :
10290 1508 : const char *GDALnetCDFDriver::GetMetadataItem(const char *pszName,
10291 : const char *pszDomain)
10292 : {
10293 3016 : std::lock_guard oLock(m_oMutex);
10294 1508 : if (EQUAL(pszName, GDAL_DCAP_VIRTUALIO))
10295 : {
10296 13 : InitializeDCAPVirtualIO();
10297 : }
10298 3016 : return GDALDriver::GetMetadataItem(pszName, pszDomain);
10299 : }
10300 :
10301 21 : void GDALRegister_netCDF()
10302 :
10303 : {
10304 21 : if (!GDAL_CHECK_VERSION("netCDF driver"))
10305 0 : return;
10306 :
10307 21 : if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
10308 0 : return;
10309 :
10310 21 : GDALDriver *poDriver = new GDALnetCDFDriver();
10311 21 : netCDFDriverSetCommonMetadata(poDriver);
10312 :
10313 21 : poDriver->SetMetadataItem("NETCDF_CONVENTIONS",
10314 21 : GDAL_DEFAULT_NCDF_CONVENTIONS);
10315 21 : poDriver->SetMetadataItem("NETCDF_VERSION", nc_inq_libvers());
10316 :
10317 : // Set pfns and register driver.
10318 21 : poDriver->pfnOpen = netCDFDataset::Open;
10319 21 : poDriver->pfnCreateCopy = netCDFDataset::CreateCopy;
10320 21 : poDriver->pfnCreate = netCDFDataset::Create;
10321 21 : poDriver->pfnCreateMultiDimensional = netCDFDataset::CreateMultiDimensional;
10322 21 : poDriver->pfnUnloadDriver = NCDFUnloadDriver;
10323 :
10324 21 : GetGDALDriverManager()->RegisterDriver(poDriver);
10325 : }
10326 :
10327 : /************************************************************************/
10328 : /* New functions */
10329 : /************************************************************************/
10330 :
10331 : /* Test for GDAL version string >= target */
10332 293 : static bool NCDFIsGDALVersionGTE(const char *pszVersion, int nTarget)
10333 : {
10334 :
10335 : // Valid strings are "GDAL 1.9dev, released 2011/01/18" and "GDAL 1.8.1 ".
10336 293 : if (pszVersion == nullptr || EQUAL(pszVersion, ""))
10337 0 : return false;
10338 293 : else if (!STARTS_WITH_CI(pszVersion, "GDAL "))
10339 0 : return false;
10340 : // 2.0dev of 2011/12/29 has been later renamed as 1.10dev.
10341 293 : else if (EQUAL("GDAL 2.0dev, released 2011/12/29", pszVersion))
10342 0 : return nTarget <= GDAL_COMPUTE_VERSION(1, 10, 0);
10343 293 : else if (STARTS_WITH_CI(pszVersion, "GDAL 1.9dev"))
10344 2 : return nTarget <= 1900;
10345 291 : else if (STARTS_WITH_CI(pszVersion, "GDAL 1.8dev"))
10346 0 : return nTarget <= 1800;
10347 :
10348 291 : const CPLStringList aosTokens(CSLTokenizeString2(pszVersion + 5, ".", 0));
10349 :
10350 291 : int nVersions[] = {0, 0, 0, 0};
10351 1164 : for (int iToken = 0; iToken < std::min(4, aosTokens.size()); iToken++)
10352 : {
10353 873 : nVersions[iToken] = atoi(aosTokens[iToken]);
10354 873 : if (nVersions[iToken] < 0)
10355 0 : nVersions[iToken] = 0;
10356 873 : else if (nVersions[iToken] > 99)
10357 0 : nVersions[iToken] = 99;
10358 : }
10359 :
10360 291 : int nVersion = 0;
10361 291 : if (nVersions[0] > 1 || nVersions[1] >= 10)
10362 291 : nVersion =
10363 291 : GDAL_COMPUTE_VERSION(nVersions[0], nVersions[1], nVersions[2]);
10364 : else
10365 0 : nVersion = nVersions[0] * 1000 + nVersions[1] * 100 +
10366 0 : nVersions[2] * 10 + nVersions[3];
10367 :
10368 291 : return nTarget <= nVersion;
10369 : }
10370 :
10371 : // Add Conventions, GDAL version and history.
10372 177 : static void NCDFAddGDALHistory(int fpImage, const char *pszFilename,
10373 : bool bWriteGDALVersion, bool bWriteGDALHistory,
10374 : const char *pszOldHist,
10375 : const char *pszFunctionName,
10376 : const char *pszCFVersion)
10377 : {
10378 177 : if (pszCFVersion == nullptr)
10379 : {
10380 48 : pszCFVersion = GDAL_DEFAULT_NCDF_CONVENTIONS;
10381 : }
10382 177 : int status = nc_put_att_text(fpImage, NC_GLOBAL, "Conventions",
10383 : strlen(pszCFVersion), pszCFVersion);
10384 177 : NCDF_ERR(status);
10385 :
10386 177 : if (bWriteGDALVersion)
10387 : {
10388 175 : const char *pszNCDF_GDAL = GDALVersionInfo("--version");
10389 175 : status = nc_put_att_text(fpImage, NC_GLOBAL, "GDAL",
10390 : strlen(pszNCDF_GDAL), pszNCDF_GDAL);
10391 175 : NCDF_ERR(status);
10392 : }
10393 :
10394 177 : if (bWriteGDALHistory)
10395 : {
10396 : // Add history.
10397 350 : CPLString osTmp;
10398 : #ifdef GDAL_SET_CMD_LINE_DEFINED_TMP
10399 : if (!EQUAL(GDALGetCmdLine(), ""))
10400 : osTmp = GDALGetCmdLine();
10401 : else
10402 : osTmp =
10403 : CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
10404 : #else
10405 175 : osTmp = CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
10406 : #endif
10407 :
10408 175 : NCDFAddHistory(fpImage, osTmp.c_str(), pszOldHist);
10409 : }
10410 2 : else if (pszOldHist != nullptr)
10411 : {
10412 0 : status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
10413 : strlen(pszOldHist), pszOldHist);
10414 0 : NCDF_ERR(status);
10415 : }
10416 177 : }
10417 :
10418 : // Code taken from cdo and libcdi, used for writing the history attribute.
10419 :
10420 : // void cdoDefHistory(int fileID, char *histstring)
10421 175 : static void NCDFAddHistory(int fpImage, const char *pszAddHist,
10422 : const char *pszOldHist)
10423 : {
10424 : // Check pszOldHist - as if there was no previous history, it will be
10425 : // a null pointer - if so set as empty.
10426 175 : if (nullptr == pszOldHist)
10427 : {
10428 59 : pszOldHist = "";
10429 : }
10430 :
10431 : char strtime[32];
10432 175 : strtime[0] = '\0';
10433 :
10434 175 : time_t tp = time(nullptr);
10435 175 : if (tp != -1)
10436 : {
10437 : struct tm ltime;
10438 175 : VSILocalTime(&tp, <ime);
10439 175 : (void)strftime(strtime, sizeof(strtime),
10440 : "%a %b %d %H:%M:%S %Y: ", <ime);
10441 : }
10442 :
10443 : // status = nc_get_att_text(fpImage, NC_GLOBAL,
10444 : // "history", pszOldHist);
10445 : // printf("status: %d pszOldHist: [%s]\n",status,pszOldHist);
10446 :
10447 175 : size_t nNewHistSize =
10448 175 : strlen(pszOldHist) + strlen(strtime) + strlen(pszAddHist) + 1 + 1;
10449 : char *pszNewHist =
10450 175 : static_cast<char *>(CPLMalloc(nNewHistSize * sizeof(char)));
10451 :
10452 175 : strcpy(pszNewHist, strtime);
10453 175 : strcat(pszNewHist, pszAddHist);
10454 :
10455 : // int disableHistory = FALSE;
10456 : // if( !disableHistory )
10457 : {
10458 175 : if (!EQUAL(pszOldHist, ""))
10459 8 : strcat(pszNewHist, "\n");
10460 175 : strcat(pszNewHist, pszOldHist);
10461 : }
10462 :
10463 175 : const int status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
10464 : strlen(pszNewHist), pszNewHist);
10465 175 : NCDF_ERR(status);
10466 :
10467 175 : CPLFree(pszNewHist);
10468 175 : }
10469 :
10470 7217 : static CPLErr NCDFSafeStrcat(char **ppszDest, const char *pszSrc,
10471 : size_t *nDestSize)
10472 : {
10473 : /* Reallocate the data string until the content fits */
10474 7217 : while (*nDestSize < (strlen(*ppszDest) + strlen(pszSrc) + 1))
10475 : {
10476 482 : (*nDestSize) *= 2;
10477 482 : *ppszDest = static_cast<char *>(
10478 482 : CPLRealloc(reinterpret_cast<void *>(*ppszDest), *nDestSize));
10479 : #ifdef NCDF_DEBUG
10480 : CPLDebug("GDAL_netCDF", "NCDFSafeStrcat() resized str from %ld to %ld",
10481 : (*nDestSize) / 2, *nDestSize);
10482 : #endif
10483 : }
10484 6735 : strcat(*ppszDest, pszSrc);
10485 :
10486 6735 : return CE_None;
10487 : }
10488 :
10489 : /* helper function for NCDFGetAttr() */
10490 : /* if pdfValue != nullptr, sets *pdfValue to first value returned */
10491 : /* if ppszValue != nullptr, sets *ppszValue with all attribute values */
10492 : /* *ppszValue is the responsibility of the caller and must be freed */
10493 78177 : static CPLErr NCDFGetAttr1(int nCdfId, int nVarId, const char *pszAttrName,
10494 : double *pdfValue, char **ppszValue)
10495 : {
10496 78177 : nc_type nAttrType = NC_NAT;
10497 78177 : size_t nAttrLen = 0;
10498 :
10499 78177 : if (ppszValue)
10500 76835 : *ppszValue = nullptr;
10501 :
10502 78177 : int status = nc_inq_att(nCdfId, nVarId, pszAttrName, &nAttrType, &nAttrLen);
10503 78177 : if (status != NC_NOERR)
10504 41879 : return CE_Failure;
10505 :
10506 : #ifdef NCDF_DEBUG
10507 : CPLDebug("GDAL_netCDF", "NCDFGetAttr1(%s) len=%ld type=%d", pszAttrName,
10508 : nAttrLen, nAttrType);
10509 : #endif
10510 36298 : if (nAttrLen == 0 && nAttrType != NC_CHAR)
10511 1 : return CE_Failure;
10512 :
10513 : /* Allocate guaranteed minimum size (use 10 or 20 if not a string) */
10514 36297 : size_t nAttrValueSize = nAttrLen + 1;
10515 36297 : if (nAttrType != NC_CHAR && nAttrValueSize < 10)
10516 3956 : nAttrValueSize = 10;
10517 36297 : if (nAttrType == NC_DOUBLE && nAttrValueSize < 20)
10518 1910 : nAttrValueSize = 20;
10519 36297 : if (nAttrType == NC_INT64 && nAttrValueSize < 20)
10520 22 : nAttrValueSize = 22;
10521 : char *pszAttrValue =
10522 36297 : static_cast<char *>(CPLCalloc(nAttrValueSize, sizeof(char)));
10523 36297 : *pszAttrValue = '\0';
10524 :
10525 36297 : if (nAttrLen > 1 && nAttrType != NC_CHAR)
10526 678 : NCDFSafeStrcat(&pszAttrValue, "{", &nAttrValueSize);
10527 :
10528 36297 : double dfValue = 0.0;
10529 36297 : size_t m = 0;
10530 : char szTemp[256];
10531 36297 : bool bSetDoubleFromStr = false;
10532 :
10533 36297 : switch (nAttrType)
10534 : {
10535 32339 : case NC_CHAR:
10536 32339 : CPL_IGNORE_RET_VAL(
10537 32339 : nc_get_att_text(nCdfId, nVarId, pszAttrName, pszAttrValue));
10538 32339 : pszAttrValue[nAttrLen] = '\0';
10539 32339 : bSetDoubleFromStr = true;
10540 32339 : dfValue = 0.0;
10541 32339 : break;
10542 94 : case NC_BYTE:
10543 : {
10544 : signed char *pscTemp = static_cast<signed char *>(
10545 94 : CPLCalloc(nAttrLen, sizeof(signed char)));
10546 94 : nc_get_att_schar(nCdfId, nVarId, pszAttrName, pscTemp);
10547 94 : dfValue = static_cast<double>(pscTemp[0]);
10548 94 : if (nAttrLen > 1)
10549 : {
10550 24 : for (m = 0; m < nAttrLen - 1; m++)
10551 : {
10552 13 : snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
10553 13 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10554 : }
10555 : }
10556 94 : snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
10557 94 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10558 94 : CPLFree(pscTemp);
10559 94 : break;
10560 : }
10561 569 : case NC_SHORT:
10562 : {
10563 : short *psTemp =
10564 569 : static_cast<short *>(CPLCalloc(nAttrLen, sizeof(short)));
10565 569 : nc_get_att_short(nCdfId, nVarId, pszAttrName, psTemp);
10566 569 : dfValue = static_cast<double>(psTemp[0]);
10567 569 : if (nAttrLen > 1)
10568 : {
10569 918 : for (m = 0; m < nAttrLen - 1; m++)
10570 : {
10571 459 : snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
10572 459 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10573 : }
10574 : }
10575 569 : snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
10576 569 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10577 569 : CPLFree(psTemp);
10578 569 : break;
10579 : }
10580 574 : case NC_INT:
10581 : {
10582 574 : int *pnTemp = static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
10583 574 : nc_get_att_int(nCdfId, nVarId, pszAttrName, pnTemp);
10584 574 : dfValue = static_cast<double>(pnTemp[0]);
10585 574 : if (nAttrLen > 1)
10586 : {
10587 218 : for (m = 0; m < nAttrLen - 1; m++)
10588 : {
10589 139 : snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
10590 139 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10591 : }
10592 : }
10593 574 : snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
10594 574 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10595 574 : CPLFree(pnTemp);
10596 574 : break;
10597 : }
10598 403 : case NC_FLOAT:
10599 : {
10600 : float *pfTemp =
10601 403 : static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
10602 403 : nc_get_att_float(nCdfId, nVarId, pszAttrName, pfTemp);
10603 403 : dfValue = static_cast<double>(pfTemp[0]);
10604 403 : if (nAttrLen > 1)
10605 : {
10606 60 : for (m = 0; m < nAttrLen - 1; m++)
10607 : {
10608 30 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
10609 30 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10610 : }
10611 : }
10612 403 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
10613 403 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10614 403 : CPLFree(pfTemp);
10615 403 : break;
10616 : }
10617 1910 : case NC_DOUBLE:
10618 : {
10619 : double *pdfTemp =
10620 1910 : static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
10621 1910 : nc_get_att_double(nCdfId, nVarId, pszAttrName, pdfTemp);
10622 1910 : dfValue = pdfTemp[0];
10623 1910 : if (nAttrLen > 1)
10624 : {
10625 168 : for (m = 0; m < nAttrLen - 1; m++)
10626 : {
10627 91 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
10628 91 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10629 : }
10630 : }
10631 1910 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
10632 1910 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10633 1910 : CPLFree(pdfTemp);
10634 1910 : break;
10635 : }
10636 263 : case NC_STRING:
10637 : {
10638 : char **ppszTemp =
10639 263 : static_cast<char **>(CPLCalloc(nAttrLen, sizeof(char *)));
10640 263 : nc_get_att_string(nCdfId, nVarId, pszAttrName, ppszTemp);
10641 263 : bSetDoubleFromStr = true;
10642 263 : dfValue = 0.0;
10643 263 : if (nAttrLen > 1)
10644 : {
10645 19 : for (m = 0; m < nAttrLen - 1; m++)
10646 : {
10647 12 : NCDFSafeStrcat(&pszAttrValue,
10648 12 : ppszTemp[m] ? ppszTemp[m] : "{NULL}",
10649 : &nAttrValueSize);
10650 12 : NCDFSafeStrcat(&pszAttrValue, ",", &nAttrValueSize);
10651 : }
10652 : }
10653 263 : NCDFSafeStrcat(&pszAttrValue, ppszTemp[m] ? ppszTemp[m] : "{NULL}",
10654 : &nAttrValueSize);
10655 263 : nc_free_string(nAttrLen, ppszTemp);
10656 263 : CPLFree(ppszTemp);
10657 263 : break;
10658 : }
10659 28 : case NC_UBYTE:
10660 : {
10661 : unsigned char *pucTemp = static_cast<unsigned char *>(
10662 28 : CPLCalloc(nAttrLen, sizeof(unsigned char)));
10663 28 : nc_get_att_uchar(nCdfId, nVarId, pszAttrName, pucTemp);
10664 28 : dfValue = static_cast<double>(pucTemp[0]);
10665 28 : if (nAttrLen > 1)
10666 : {
10667 0 : for (m = 0; m < nAttrLen - 1; m++)
10668 : {
10669 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
10670 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10671 : }
10672 : }
10673 28 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
10674 28 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10675 28 : CPLFree(pucTemp);
10676 28 : break;
10677 : }
10678 26 : case NC_USHORT:
10679 : {
10680 : unsigned short *pusTemp = static_cast<unsigned short *>(
10681 26 : CPLCalloc(nAttrLen, sizeof(unsigned short)));
10682 26 : nc_get_att_ushort(nCdfId, nVarId, pszAttrName, pusTemp);
10683 26 : dfValue = static_cast<double>(pusTemp[0]);
10684 26 : if (nAttrLen > 1)
10685 : {
10686 10 : for (m = 0; m < nAttrLen - 1; m++)
10687 : {
10688 5 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
10689 5 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10690 : }
10691 : }
10692 26 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
10693 26 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10694 26 : CPLFree(pusTemp);
10695 26 : break;
10696 : }
10697 21 : case NC_UINT:
10698 : {
10699 : unsigned int *punTemp =
10700 21 : static_cast<unsigned int *>(CPLCalloc(nAttrLen, sizeof(int)));
10701 21 : nc_get_att_uint(nCdfId, nVarId, pszAttrName, punTemp);
10702 21 : dfValue = static_cast<double>(punTemp[0]);
10703 21 : if (nAttrLen > 1)
10704 : {
10705 0 : for (m = 0; m < nAttrLen - 1; m++)
10706 : {
10707 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
10708 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10709 : }
10710 : }
10711 21 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
10712 21 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10713 21 : CPLFree(punTemp);
10714 21 : break;
10715 : }
10716 22 : case NC_INT64:
10717 : {
10718 : GIntBig *panTemp =
10719 22 : static_cast<GIntBig *>(CPLCalloc(nAttrLen, sizeof(GIntBig)));
10720 22 : nc_get_att_longlong(nCdfId, nVarId, pszAttrName, panTemp);
10721 22 : dfValue = static_cast<double>(panTemp[0]);
10722 22 : if (nAttrLen > 1)
10723 : {
10724 0 : for (m = 0; m < nAttrLen - 1; m++)
10725 : {
10726 0 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",",
10727 0 : panTemp[m]);
10728 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10729 : }
10730 : }
10731 22 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, panTemp[m]);
10732 22 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10733 22 : CPLFree(panTemp);
10734 22 : break;
10735 : }
10736 22 : case NC_UINT64:
10737 : {
10738 : GUIntBig *panTemp =
10739 22 : static_cast<GUIntBig *>(CPLCalloc(nAttrLen, sizeof(GUIntBig)));
10740 22 : nc_get_att_ulonglong(nCdfId, nVarId, pszAttrName, panTemp);
10741 22 : dfValue = static_cast<double>(panTemp[0]);
10742 22 : if (nAttrLen > 1)
10743 : {
10744 0 : for (m = 0; m < nAttrLen - 1; m++)
10745 : {
10746 0 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",",
10747 0 : panTemp[m]);
10748 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10749 : }
10750 : }
10751 22 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, panTemp[m]);
10752 22 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10753 22 : CPLFree(panTemp);
10754 22 : break;
10755 : }
10756 26 : default:
10757 26 : CPLDebug("GDAL_netCDF",
10758 : "NCDFGetAttr unsupported type %d for attribute %s",
10759 : nAttrType, pszAttrName);
10760 26 : break;
10761 : }
10762 :
10763 36297 : if (nAttrLen > 1 && nAttrType != NC_CHAR)
10764 678 : NCDFSafeStrcat(&pszAttrValue, "}", &nAttrValueSize);
10765 :
10766 36297 : if (bSetDoubleFromStr)
10767 : {
10768 32602 : if (CPLGetValueType(pszAttrValue) == CPL_VALUE_STRING)
10769 : {
10770 32420 : if (ppszValue == nullptr && pdfValue != nullptr)
10771 : {
10772 1 : CPLFree(pszAttrValue);
10773 1 : return CE_Failure;
10774 : }
10775 : }
10776 32601 : dfValue = CPLAtof(pszAttrValue);
10777 : }
10778 :
10779 : /* set return values */
10780 36296 : if (ppszValue)
10781 35978 : *ppszValue = pszAttrValue;
10782 : else
10783 318 : CPLFree(pszAttrValue);
10784 :
10785 36296 : if (pdfValue)
10786 318 : *pdfValue = dfValue;
10787 :
10788 36296 : return CE_None;
10789 : }
10790 :
10791 : /* sets pdfValue to first value found */
10792 1342 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
10793 : double *pdfValue)
10794 : {
10795 1342 : return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, pdfValue, nullptr);
10796 : }
10797 :
10798 : /* pszValue is the responsibility of the caller and must be freed */
10799 76835 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
10800 : char **pszValue)
10801 : {
10802 76835 : return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, nullptr, pszValue);
10803 : }
10804 :
10805 3141 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
10806 : std::string &osValue)
10807 : {
10808 3141 : nc_type nAttrType = NC_NAT;
10809 3141 : size_t nAttrLen = 0;
10810 :
10811 3141 : int status = nc_inq_att(nCdfId, nVarId, pszAttrName, &nAttrType, &nAttrLen);
10812 3141 : if (status != NC_NOERR)
10813 1381 : return CE_Failure;
10814 :
10815 1760 : if (nAttrType != NC_CHAR)
10816 2 : return CE_Failure;
10817 :
10818 : try
10819 : {
10820 1758 : osValue.resize(nAttrLen, 0);
10821 : }
10822 0 : catch (const std::exception &)
10823 : {
10824 0 : return CE_Failure;
10825 : }
10826 :
10827 1758 : const auto nErr = nc_get_att_text(nCdfId, nVarId, pszAttrName,
10828 1758 : osValue.data()) != NC_NOERR;
10829 1758 : NCDF_ERR_RET(nErr);
10830 :
10831 1758 : return CE_None;
10832 : }
10833 :
10834 : /* By default write NC_CHAR, but detect for int/float/double and */
10835 : /* NC4 string arrays */
10836 142 : static CPLErr NCDFPutAttr(int nCdfId, int nVarId, const char *pszAttrName,
10837 : const char *pszValue)
10838 : {
10839 142 : int status = 0;
10840 142 : char *pszTemp = nullptr;
10841 :
10842 : /* get the attribute values as tokens */
10843 284 : CPLStringList aosValues = NCDFTokenizeArray(pszValue);
10844 142 : if (aosValues.empty())
10845 0 : return CE_Failure;
10846 :
10847 142 : size_t nAttrLen = aosValues.size();
10848 :
10849 : /* first detect type */
10850 142 : nc_type nAttrType = NC_CHAR;
10851 142 : nc_type nTmpAttrType = NC_CHAR;
10852 297 : for (size_t i = 0; i < nAttrLen; i++)
10853 : {
10854 155 : nTmpAttrType = NC_CHAR;
10855 155 : bool bFoundType = false;
10856 155 : errno = 0;
10857 155 : int nValue = static_cast<int>(strtol(aosValues[i], &pszTemp, 10));
10858 : /* test for int */
10859 : /* TODO test for Byte and short - can this be done safely? */
10860 155 : if (errno == 0 && aosValues[i] != pszTemp && *pszTemp == 0)
10861 : {
10862 : char szTemp[256];
10863 19 : CPLsnprintf(szTemp, sizeof(szTemp), "%d", nValue);
10864 19 : if (EQUAL(szTemp, aosValues[i]))
10865 : {
10866 19 : bFoundType = true;
10867 19 : nTmpAttrType = NC_INT;
10868 : }
10869 : else
10870 : {
10871 : unsigned int unValue = static_cast<unsigned int>(
10872 0 : strtoul(aosValues[i], &pszTemp, 10));
10873 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", unValue);
10874 0 : if (EQUAL(szTemp, aosValues[i]))
10875 : {
10876 0 : bFoundType = true;
10877 0 : nTmpAttrType = NC_UINT;
10878 : }
10879 : }
10880 : }
10881 155 : if (!bFoundType)
10882 : {
10883 : /* test for double */
10884 136 : errno = 0;
10885 136 : double dfValue = CPLStrtod(aosValues[i], &pszTemp);
10886 136 : if ((errno == 0) && (aosValues[i] != pszTemp) && (*pszTemp == 0))
10887 : {
10888 : // Test for float instead of double.
10889 : // strtof() is C89, which is not available in MSVC.
10890 : // See if we lose precision if we cast to float and write to
10891 : // char*.
10892 14 : float fValue = float(dfValue);
10893 : char szTemp[256];
10894 14 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", fValue);
10895 14 : if (EQUAL(szTemp, aosValues[i]))
10896 8 : nTmpAttrType = NC_FLOAT;
10897 : else
10898 6 : nTmpAttrType = NC_DOUBLE;
10899 : }
10900 : }
10901 155 : if ((nTmpAttrType <= NC_DOUBLE && nAttrType <= NC_DOUBLE &&
10902 135 : nTmpAttrType > nAttrType) ||
10903 135 : (nTmpAttrType == NC_UINT && nAttrType < NC_FLOAT) ||
10904 5 : (nTmpAttrType >= NC_FLOAT && nAttrType == NC_UINT))
10905 20 : nAttrType = nTmpAttrType;
10906 : }
10907 :
10908 : #ifdef DEBUG
10909 142 : if (EQUAL(pszAttrName, "DEBUG_EMPTY_DOUBLE_ATTR"))
10910 : {
10911 0 : nAttrType = NC_DOUBLE;
10912 0 : nAttrLen = 0;
10913 : }
10914 : #endif
10915 :
10916 : /* now write the data */
10917 142 : if (nAttrType == NC_CHAR)
10918 : {
10919 122 : int nTmpFormat = 0;
10920 122 : if (nAttrLen > 1)
10921 : {
10922 0 : status = nc_inq_format(nCdfId, &nTmpFormat);
10923 0 : NCDF_ERR(status);
10924 : }
10925 122 : if (nAttrLen > 1 && nTmpFormat == NCDF_FORMAT_NC4)
10926 0 : status =
10927 0 : nc_put_att_string(nCdfId, nVarId, pszAttrName, nAttrLen,
10928 0 : const_cast<const char **>(aosValues.List()));
10929 : else
10930 122 : status = nc_put_att_text(nCdfId, nVarId, pszAttrName,
10931 : strlen(pszValue), pszValue);
10932 122 : NCDF_ERR(status);
10933 : }
10934 : else
10935 : {
10936 20 : switch (nAttrType)
10937 : {
10938 11 : case NC_INT:
10939 : {
10940 : int *pnTemp =
10941 11 : static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
10942 30 : for (size_t i = 0; i < nAttrLen; i++)
10943 : {
10944 19 : pnTemp[i] =
10945 19 : static_cast<int>(strtol(aosValues[i], &pszTemp, 10));
10946 : }
10947 11 : status = nc_put_att_int(nCdfId, nVarId, pszAttrName, NC_INT,
10948 : nAttrLen, pnTemp);
10949 11 : NCDF_ERR(status);
10950 11 : CPLFree(pnTemp);
10951 11 : break;
10952 : }
10953 0 : case NC_UINT:
10954 : {
10955 : unsigned int *punTemp = static_cast<unsigned int *>(
10956 0 : CPLCalloc(nAttrLen, sizeof(unsigned int)));
10957 0 : for (size_t i = 0; i < nAttrLen; i++)
10958 : {
10959 0 : punTemp[i] = static_cast<unsigned int>(
10960 0 : strtol(aosValues[i], &pszTemp, 10));
10961 : }
10962 0 : status = nc_put_att_uint(nCdfId, nVarId, pszAttrName, NC_UINT,
10963 : nAttrLen, punTemp);
10964 0 : NCDF_ERR(status);
10965 0 : CPLFree(punTemp);
10966 0 : break;
10967 : }
10968 6 : case NC_FLOAT:
10969 : {
10970 : float *pfTemp =
10971 6 : static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
10972 14 : for (size_t i = 0; i < nAttrLen; i++)
10973 : {
10974 8 : pfTemp[i] =
10975 8 : static_cast<float>(CPLStrtod(aosValues[i], &pszTemp));
10976 : }
10977 6 : status = nc_put_att_float(nCdfId, nVarId, pszAttrName, NC_FLOAT,
10978 : nAttrLen, pfTemp);
10979 6 : NCDF_ERR(status);
10980 6 : CPLFree(pfTemp);
10981 6 : break;
10982 : }
10983 3 : case NC_DOUBLE:
10984 : {
10985 : double *pdfTemp =
10986 3 : static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
10987 9 : for (size_t i = 0; i < nAttrLen; i++)
10988 : {
10989 6 : pdfTemp[i] = CPLStrtod(aosValues[i], &pszTemp);
10990 : }
10991 3 : status = nc_put_att_double(nCdfId, nVarId, pszAttrName,
10992 : NC_DOUBLE, nAttrLen, pdfTemp);
10993 3 : NCDF_ERR(status);
10994 3 : CPLFree(pdfTemp);
10995 3 : break;
10996 : }
10997 0 : default:
10998 0 : return CE_Failure;
10999 : }
11000 : }
11001 :
11002 142 : return CE_None;
11003 : }
11004 :
11005 80 : static CPLErr NCDFGet1DVar(int nCdfId, int nVarId, char **pszValue)
11006 : {
11007 : /* get var information */
11008 80 : int nVarDimId = -1;
11009 80 : int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
11010 80 : if (status != NC_NOERR || nVarDimId != 1)
11011 0 : return CE_Failure;
11012 :
11013 80 : status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
11014 80 : if (status != NC_NOERR)
11015 0 : return CE_Failure;
11016 :
11017 80 : nc_type nVarType = NC_NAT;
11018 80 : status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
11019 80 : if (status != NC_NOERR)
11020 0 : return CE_Failure;
11021 :
11022 80 : size_t nVarLen = 0;
11023 80 : status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
11024 80 : if (status != NC_NOERR)
11025 0 : return CE_Failure;
11026 :
11027 80 : size_t start[1] = {0};
11028 80 : size_t count[1] = {nVarLen};
11029 :
11030 : /* Allocate guaranteed minimum size */
11031 80 : size_t nVarValueSize = NCDF_MAX_STR_LEN;
11032 : char *pszVarValue =
11033 80 : static_cast<char *>(CPLCalloc(nVarValueSize, sizeof(char)));
11034 80 : *pszVarValue = '\0';
11035 :
11036 80 : if (nVarLen == 0)
11037 : {
11038 : /* set return values */
11039 1 : *pszValue = pszVarValue;
11040 :
11041 1 : return CE_None;
11042 : }
11043 :
11044 79 : if (nVarLen > 1 && nVarType != NC_CHAR)
11045 43 : NCDFSafeStrcat(&pszVarValue, "{", &nVarValueSize);
11046 :
11047 79 : switch (nVarType)
11048 : {
11049 0 : case NC_CHAR:
11050 0 : nc_get_vara_text(nCdfId, nVarId, start, count, pszVarValue);
11051 0 : pszVarValue[nVarLen] = '\0';
11052 0 : break;
11053 0 : case NC_BYTE:
11054 : {
11055 : signed char *pscTemp = static_cast<signed char *>(
11056 0 : CPLCalloc(nVarLen, sizeof(signed char)));
11057 0 : nc_get_vara_schar(nCdfId, nVarId, start, count, pscTemp);
11058 : char szTemp[256];
11059 0 : size_t m = 0;
11060 0 : for (; m < nVarLen - 1; m++)
11061 : {
11062 0 : snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
11063 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11064 : }
11065 0 : snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
11066 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11067 0 : CPLFree(pscTemp);
11068 0 : break;
11069 : }
11070 0 : case NC_SHORT:
11071 : {
11072 : short *psTemp =
11073 0 : static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
11074 0 : nc_get_vara_short(nCdfId, nVarId, start, count, psTemp);
11075 : char szTemp[256];
11076 0 : size_t m = 0;
11077 0 : for (; m < nVarLen - 1; m++)
11078 : {
11079 0 : snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
11080 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11081 : }
11082 0 : snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
11083 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11084 0 : CPLFree(psTemp);
11085 0 : break;
11086 : }
11087 22 : case NC_INT:
11088 : {
11089 22 : int *pnTemp = static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
11090 22 : nc_get_vara_int(nCdfId, nVarId, start, count, pnTemp);
11091 : char szTemp[256];
11092 22 : size_t m = 0;
11093 47 : for (; m < nVarLen - 1; m++)
11094 : {
11095 25 : snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
11096 25 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11097 : }
11098 22 : snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
11099 22 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11100 22 : CPLFree(pnTemp);
11101 22 : break;
11102 : }
11103 8 : case NC_FLOAT:
11104 : {
11105 : float *pfTemp =
11106 8 : static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
11107 8 : nc_get_vara_float(nCdfId, nVarId, start, count, pfTemp);
11108 : char szTemp[256];
11109 8 : size_t m = 0;
11110 325 : for (; m < nVarLen - 1; m++)
11111 : {
11112 317 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
11113 317 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11114 : }
11115 8 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
11116 8 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11117 8 : CPLFree(pfTemp);
11118 8 : break;
11119 : }
11120 48 : case NC_DOUBLE:
11121 : {
11122 : double *pdfTemp =
11123 48 : static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
11124 48 : nc_get_vara_double(nCdfId, nVarId, start, count, pdfTemp);
11125 : char szTemp[256];
11126 48 : size_t m = 0;
11127 226 : for (; m < nVarLen - 1; m++)
11128 : {
11129 178 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
11130 178 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11131 : }
11132 48 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
11133 48 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11134 48 : CPLFree(pdfTemp);
11135 48 : break;
11136 : }
11137 0 : case NC_STRING:
11138 : {
11139 : char **ppszTemp =
11140 0 : static_cast<char **>(CPLCalloc(nVarLen, sizeof(char *)));
11141 0 : nc_get_vara_string(nCdfId, nVarId, start, count, ppszTemp);
11142 0 : size_t m = 0;
11143 0 : for (; m < nVarLen - 1; m++)
11144 : {
11145 0 : NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
11146 0 : NCDFSafeStrcat(&pszVarValue, ",", &nVarValueSize);
11147 : }
11148 0 : NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
11149 0 : nc_free_string(nVarLen, ppszTemp);
11150 0 : CPLFree(ppszTemp);
11151 0 : break;
11152 : }
11153 0 : case NC_UBYTE:
11154 : {
11155 : unsigned char *pucTemp = static_cast<unsigned char *>(
11156 0 : CPLCalloc(nVarLen, sizeof(unsigned char)));
11157 0 : nc_get_vara_uchar(nCdfId, nVarId, start, count, pucTemp);
11158 : char szTemp[256];
11159 0 : size_t m = 0;
11160 0 : for (; m < nVarLen - 1; m++)
11161 : {
11162 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
11163 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11164 : }
11165 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
11166 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11167 0 : CPLFree(pucTemp);
11168 0 : break;
11169 : }
11170 0 : case NC_USHORT:
11171 : {
11172 : unsigned short *pusTemp = static_cast<unsigned short *>(
11173 0 : CPLCalloc(nVarLen, sizeof(unsigned short)));
11174 0 : nc_get_vara_ushort(nCdfId, nVarId, start, count, pusTemp);
11175 : char szTemp[256];
11176 0 : size_t m = 0;
11177 0 : for (; m < nVarLen - 1; m++)
11178 : {
11179 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
11180 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11181 : }
11182 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
11183 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11184 0 : CPLFree(pusTemp);
11185 0 : break;
11186 : }
11187 0 : case NC_UINT:
11188 : {
11189 : unsigned int *punTemp = static_cast<unsigned int *>(
11190 0 : CPLCalloc(nVarLen, sizeof(unsigned int)));
11191 0 : nc_get_vara_uint(nCdfId, nVarId, start, count, punTemp);
11192 : char szTemp[256];
11193 0 : size_t m = 0;
11194 0 : for (; m < nVarLen - 1; m++)
11195 : {
11196 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
11197 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11198 : }
11199 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
11200 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11201 0 : CPLFree(punTemp);
11202 0 : break;
11203 : }
11204 1 : case NC_INT64:
11205 : {
11206 : long long *pnTemp =
11207 1 : static_cast<long long *>(CPLCalloc(nVarLen, sizeof(long long)));
11208 1 : nc_get_vara_longlong(nCdfId, nVarId, start, count, pnTemp);
11209 : char szTemp[256];
11210 1 : size_t m = 0;
11211 2 : for (; m < nVarLen - 1; m++)
11212 : {
11213 1 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",", pnTemp[m]);
11214 1 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11215 : }
11216 1 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, pnTemp[m]);
11217 1 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11218 1 : CPLFree(pnTemp);
11219 1 : break;
11220 : }
11221 0 : case NC_UINT64:
11222 : {
11223 : unsigned long long *pnTemp = static_cast<unsigned long long *>(
11224 0 : CPLCalloc(nVarLen, sizeof(unsigned long long)));
11225 0 : nc_get_vara_ulonglong(nCdfId, nVarId, start, count, pnTemp);
11226 : char szTemp[256];
11227 0 : size_t m = 0;
11228 0 : for (; m < nVarLen - 1; m++)
11229 : {
11230 0 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",", pnTemp[m]);
11231 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11232 : }
11233 0 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, pnTemp[m]);
11234 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11235 0 : CPLFree(pnTemp);
11236 0 : break;
11237 : }
11238 0 : default:
11239 0 : CPLDebug("GDAL_netCDF", "NCDFGetVar1D unsupported type %d",
11240 : nVarType);
11241 0 : CPLFree(pszVarValue);
11242 0 : pszVarValue = nullptr;
11243 0 : break;
11244 : }
11245 :
11246 79 : if (pszVarValue != nullptr && nVarLen > 1 && nVarType != NC_CHAR)
11247 43 : NCDFSafeStrcat(&pszVarValue, "}", &nVarValueSize);
11248 :
11249 : /* set return values */
11250 79 : *pszValue = pszVarValue;
11251 :
11252 79 : return CE_None;
11253 : }
11254 :
11255 8 : static CPLErr NCDFPut1DVar(int nCdfId, int nVarId, const char *pszValue)
11256 : {
11257 8 : if (EQUAL(pszValue, ""))
11258 0 : return CE_Failure;
11259 :
11260 : /* get var information */
11261 8 : int nVarDimId = -1;
11262 8 : int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
11263 8 : if (status != NC_NOERR || nVarDimId != 1)
11264 0 : return CE_Failure;
11265 :
11266 8 : status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
11267 8 : if (status != NC_NOERR)
11268 0 : return CE_Failure;
11269 :
11270 8 : nc_type nVarType = NC_CHAR;
11271 8 : status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
11272 8 : if (status != NC_NOERR)
11273 0 : return CE_Failure;
11274 :
11275 8 : size_t nVarLen = 0;
11276 8 : status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
11277 8 : if (status != NC_NOERR)
11278 0 : return CE_Failure;
11279 :
11280 8 : size_t start[1] = {0};
11281 8 : size_t count[1] = {nVarLen};
11282 :
11283 : /* get the values as tokens */
11284 16 : CPLStringList aosValues = NCDFTokenizeArray(pszValue);
11285 8 : if (aosValues.empty())
11286 0 : return CE_Failure;
11287 :
11288 8 : nVarLen = aosValues.size();
11289 :
11290 : /* now write the data */
11291 8 : if (nVarType == NC_CHAR)
11292 : {
11293 0 : status = nc_put_vara_text(nCdfId, nVarId, start, count, pszValue);
11294 0 : NCDF_ERR(status);
11295 : }
11296 : else
11297 : {
11298 8 : switch (nVarType)
11299 : {
11300 0 : case NC_BYTE:
11301 : {
11302 : signed char *pscTemp = static_cast<signed char *>(
11303 0 : CPLCalloc(nVarLen, sizeof(signed char)));
11304 0 : for (size_t i = 0; i < nVarLen; i++)
11305 : {
11306 0 : char *pszTemp = nullptr;
11307 0 : pscTemp[i] = static_cast<signed char>(
11308 0 : strtol(aosValues[i], &pszTemp, 10));
11309 : }
11310 : status =
11311 0 : nc_put_vara_schar(nCdfId, nVarId, start, count, pscTemp);
11312 0 : NCDF_ERR(status);
11313 0 : CPLFree(pscTemp);
11314 0 : break;
11315 : }
11316 0 : case NC_SHORT:
11317 : {
11318 : short *psTemp =
11319 0 : static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
11320 0 : for (size_t i = 0; i < nVarLen; i++)
11321 : {
11322 0 : char *pszTemp = nullptr;
11323 0 : psTemp[i] =
11324 0 : static_cast<short>(strtol(aosValues[i], &pszTemp, 10));
11325 : }
11326 : status =
11327 0 : nc_put_vara_short(nCdfId, nVarId, start, count, psTemp);
11328 0 : NCDF_ERR(status);
11329 0 : CPLFree(psTemp);
11330 0 : break;
11331 : }
11332 3 : case NC_INT:
11333 : {
11334 : int *pnTemp =
11335 3 : static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
11336 11 : for (size_t i = 0; i < nVarLen; i++)
11337 : {
11338 8 : char *pszTemp = nullptr;
11339 8 : pnTemp[i] =
11340 8 : static_cast<int>(strtol(aosValues[i], &pszTemp, 10));
11341 : }
11342 3 : status = nc_put_vara_int(nCdfId, nVarId, start, count, pnTemp);
11343 3 : NCDF_ERR(status);
11344 3 : CPLFree(pnTemp);
11345 3 : break;
11346 : }
11347 0 : case NC_FLOAT:
11348 : {
11349 : float *pfTemp =
11350 0 : static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
11351 0 : for (size_t i = 0; i < nVarLen; i++)
11352 : {
11353 0 : char *pszTemp = nullptr;
11354 0 : pfTemp[i] =
11355 0 : static_cast<float>(CPLStrtod(aosValues[i], &pszTemp));
11356 : }
11357 : status =
11358 0 : nc_put_vara_float(nCdfId, nVarId, start, count, pfTemp);
11359 0 : NCDF_ERR(status);
11360 0 : CPLFree(pfTemp);
11361 0 : break;
11362 : }
11363 5 : case NC_DOUBLE:
11364 : {
11365 : double *pdfTemp =
11366 5 : static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
11367 19 : for (size_t i = 0; i < nVarLen; i++)
11368 : {
11369 14 : char *pszTemp = nullptr;
11370 14 : pdfTemp[i] = CPLStrtod(aosValues[i], &pszTemp);
11371 : }
11372 : status =
11373 5 : nc_put_vara_double(nCdfId, nVarId, start, count, pdfTemp);
11374 5 : NCDF_ERR(status);
11375 5 : CPLFree(pdfTemp);
11376 5 : break;
11377 : }
11378 0 : default:
11379 : {
11380 0 : int nTmpFormat = 0;
11381 0 : status = nc_inq_format(nCdfId, &nTmpFormat);
11382 0 : NCDF_ERR(status);
11383 0 : if (nTmpFormat == NCDF_FORMAT_NC4)
11384 : {
11385 0 : switch (nVarType)
11386 : {
11387 0 : case NC_STRING:
11388 : {
11389 0 : status = nc_put_vara_string(
11390 : nCdfId, nVarId, start, count,
11391 0 : const_cast<const char **>(aosValues.List()));
11392 0 : NCDF_ERR(status);
11393 0 : break;
11394 : }
11395 0 : case NC_UBYTE:
11396 : {
11397 : unsigned char *pucTemp =
11398 : static_cast<unsigned char *>(
11399 0 : CPLCalloc(nVarLen, sizeof(unsigned char)));
11400 0 : for (size_t i = 0; i < nVarLen; i++)
11401 : {
11402 0 : char *pszTemp = nullptr;
11403 0 : pucTemp[i] = static_cast<unsigned char>(
11404 0 : strtoul(aosValues[i], &pszTemp, 10));
11405 : }
11406 0 : status = nc_put_vara_uchar(nCdfId, nVarId, start,
11407 : count, pucTemp);
11408 0 : NCDF_ERR(status);
11409 0 : CPLFree(pucTemp);
11410 0 : break;
11411 : }
11412 0 : case NC_USHORT:
11413 : {
11414 : unsigned short *pusTemp =
11415 : static_cast<unsigned short *>(
11416 0 : CPLCalloc(nVarLen, sizeof(unsigned short)));
11417 0 : for (size_t i = 0; i < nVarLen; i++)
11418 : {
11419 0 : char *pszTemp = nullptr;
11420 0 : pusTemp[i] = static_cast<unsigned short>(
11421 0 : strtoul(aosValues[i], &pszTemp, 10));
11422 : }
11423 0 : status = nc_put_vara_ushort(nCdfId, nVarId, start,
11424 : count, pusTemp);
11425 0 : NCDF_ERR(status);
11426 0 : CPLFree(pusTemp);
11427 0 : break;
11428 : }
11429 0 : case NC_UINT:
11430 : {
11431 : unsigned int *punTemp = static_cast<unsigned int *>(
11432 0 : CPLCalloc(nVarLen, sizeof(unsigned int)));
11433 0 : for (size_t i = 0; i < nVarLen; i++)
11434 : {
11435 0 : char *pszTemp = nullptr;
11436 0 : punTemp[i] = static_cast<unsigned int>(
11437 0 : strtoul(aosValues[i], &pszTemp, 10));
11438 : }
11439 0 : status = nc_put_vara_uint(nCdfId, nVarId, start,
11440 : count, punTemp);
11441 0 : NCDF_ERR(status);
11442 0 : CPLFree(punTemp);
11443 0 : break;
11444 : }
11445 0 : default:
11446 0 : return CE_Failure;
11447 : }
11448 : }
11449 0 : break;
11450 : }
11451 : }
11452 : }
11453 :
11454 8 : return CE_None;
11455 : }
11456 :
11457 : /************************************************************************/
11458 : /* GetDefaultNoDataValue() */
11459 : /************************************************************************/
11460 :
11461 201 : double NCDFGetDefaultNoDataValue(int nCdfId, int nVarId, int nVarType,
11462 : bool &bGotNoData)
11463 :
11464 : {
11465 201 : int nNoFill = 0;
11466 201 : double dfNoData = 0.0;
11467 :
11468 201 : switch (nVarType)
11469 : {
11470 0 : case NC_CHAR:
11471 : case NC_BYTE:
11472 : case NC_UBYTE:
11473 : // Don't do default fill-values for bytes, too risky.
11474 : // This function should not be called in those cases.
11475 0 : CPLAssert(false);
11476 : break;
11477 24 : case NC_SHORT:
11478 : {
11479 24 : short nFillVal = 0;
11480 24 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11481 : NC_NOERR)
11482 : {
11483 24 : if (!nNoFill)
11484 : {
11485 23 : bGotNoData = true;
11486 23 : dfNoData = nFillVal;
11487 : }
11488 : }
11489 : else
11490 0 : dfNoData = NC_FILL_SHORT;
11491 24 : break;
11492 : }
11493 26 : case NC_INT:
11494 : {
11495 26 : int nFillVal = 0;
11496 26 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11497 : NC_NOERR)
11498 : {
11499 26 : if (!nNoFill)
11500 : {
11501 25 : bGotNoData = true;
11502 25 : dfNoData = nFillVal;
11503 : }
11504 : }
11505 : else
11506 0 : dfNoData = NC_FILL_INT;
11507 26 : break;
11508 : }
11509 84 : case NC_FLOAT:
11510 : {
11511 84 : float fFillVal = 0;
11512 84 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &fFillVal) ==
11513 : NC_NOERR)
11514 : {
11515 84 : if (!nNoFill)
11516 : {
11517 80 : bGotNoData = true;
11518 80 : dfNoData = fFillVal;
11519 : }
11520 : }
11521 : else
11522 0 : dfNoData = NC_FILL_FLOAT;
11523 84 : break;
11524 : }
11525 34 : case NC_DOUBLE:
11526 : {
11527 34 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &dfNoData) ==
11528 : NC_NOERR)
11529 : {
11530 34 : if (!nNoFill)
11531 : {
11532 34 : bGotNoData = true;
11533 : }
11534 : }
11535 : else
11536 0 : dfNoData = NC_FILL_DOUBLE;
11537 34 : break;
11538 : }
11539 7 : case NC_USHORT:
11540 : {
11541 7 : unsigned short nFillVal = 0;
11542 7 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11543 : NC_NOERR)
11544 : {
11545 7 : if (!nNoFill)
11546 : {
11547 7 : bGotNoData = true;
11548 7 : dfNoData = nFillVal;
11549 : }
11550 : }
11551 : else
11552 0 : dfNoData = NC_FILL_USHORT;
11553 7 : break;
11554 : }
11555 7 : case NC_UINT:
11556 : {
11557 7 : unsigned int nFillVal = 0;
11558 7 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11559 : NC_NOERR)
11560 : {
11561 7 : if (!nNoFill)
11562 : {
11563 7 : bGotNoData = true;
11564 7 : dfNoData = nFillVal;
11565 : }
11566 : }
11567 : else
11568 0 : dfNoData = NC_FILL_UINT;
11569 7 : break;
11570 : }
11571 19 : default:
11572 19 : dfNoData = 0.0;
11573 19 : break;
11574 : }
11575 :
11576 201 : return dfNoData;
11577 : }
11578 :
11579 : /************************************************************************/
11580 : /* NCDFGetDefaultNoDataValueAsInt64() */
11581 : /************************************************************************/
11582 :
11583 2 : int64_t NCDFGetDefaultNoDataValueAsInt64(int nCdfId, int nVarId,
11584 : bool &bGotNoData)
11585 :
11586 : {
11587 2 : int nNoFill = 0;
11588 2 : long long nFillVal = 0;
11589 2 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
11590 : {
11591 2 : if (!nNoFill)
11592 : {
11593 2 : bGotNoData = true;
11594 2 : return static_cast<int64_t>(nFillVal);
11595 : }
11596 : }
11597 : else
11598 0 : return static_cast<int64_t>(NC_FILL_INT64);
11599 0 : return 0;
11600 : }
11601 :
11602 : /************************************************************************/
11603 : /* NCDFGetDefaultNoDataValueAsUInt64() */
11604 : /************************************************************************/
11605 :
11606 1 : uint64_t NCDFGetDefaultNoDataValueAsUInt64(int nCdfId, int nVarId,
11607 : bool &bGotNoData)
11608 :
11609 : {
11610 1 : int nNoFill = 0;
11611 1 : unsigned long long nFillVal = 0;
11612 1 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
11613 : {
11614 1 : if (!nNoFill)
11615 : {
11616 1 : bGotNoData = true;
11617 1 : return static_cast<uint64_t>(nFillVal);
11618 : }
11619 : }
11620 : else
11621 0 : return static_cast<uint64_t>(NC_FILL_UINT64);
11622 0 : return 0;
11623 : }
11624 :
11625 13371 : static int NCDFDoesVarContainAttribVal(int nCdfId,
11626 : const char *const *papszAttribNames,
11627 : const char *const *papszAttribValues,
11628 : int nVarId, const char *pszVarName,
11629 : bool bStrict = true)
11630 : {
11631 13371 : if (nVarId == -1 && pszVarName != nullptr)
11632 9094 : NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
11633 :
11634 13371 : if (nVarId == -1)
11635 1058 : return -1;
11636 :
11637 12313 : bool bFound = false;
11638 60680 : for (int i = 0; !bFound && papszAttribNames != nullptr &&
11639 57977 : papszAttribNames[i] != nullptr;
11640 : i++)
11641 : {
11642 48367 : char *pszTemp = nullptr;
11643 48367 : if (NCDFGetAttr(nCdfId, nVarId, papszAttribNames[i], &pszTemp) ==
11644 69538 : CE_None &&
11645 21171 : pszTemp != nullptr)
11646 : {
11647 21171 : if (bStrict)
11648 : {
11649 21171 : if (EQUAL(pszTemp, papszAttribValues[i]))
11650 2703 : bFound = true;
11651 : }
11652 : else
11653 : {
11654 0 : if (EQUALN(pszTemp, papszAttribValues[i],
11655 : strlen(papszAttribValues[i])))
11656 0 : bFound = true;
11657 : }
11658 21171 : CPLFree(pszTemp);
11659 : }
11660 : }
11661 12313 : return bFound;
11662 : }
11663 :
11664 2271 : static int NCDFDoesVarContainAttribVal2(int nCdfId, const char *papszAttribName,
11665 : const char *const *papszAttribValues,
11666 : int nVarId, const char *pszVarName,
11667 : int bStrict = true)
11668 : {
11669 2271 : if (nVarId == -1 && pszVarName != nullptr)
11670 1711 : NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
11671 :
11672 2271 : if (nVarId == -1)
11673 0 : return -1;
11674 :
11675 2271 : bool bFound = false;
11676 2271 : char *pszTemp = nullptr;
11677 2660 : if (NCDFGetAttr(nCdfId, nVarId, papszAttribName, &pszTemp) != CE_None ||
11678 389 : pszTemp == nullptr)
11679 1882 : return FALSE;
11680 :
11681 7879 : for (int i = 0; !bFound && i < CSLCount(papszAttribValues); i++)
11682 : {
11683 7490 : if (bStrict)
11684 : {
11685 7460 : if (EQUAL(pszTemp, papszAttribValues[i]))
11686 31 : bFound = true;
11687 : }
11688 : else
11689 : {
11690 30 : if (EQUALN(pszTemp, papszAttribValues[i],
11691 : strlen(papszAttribValues[i])))
11692 2 : bFound = true;
11693 : }
11694 : }
11695 :
11696 389 : CPLFree(pszTemp);
11697 :
11698 389 : return bFound;
11699 : }
11700 :
11701 1056 : static bool NCDFEqual(const char *papszName, const char *const *papszValues)
11702 : {
11703 1056 : if (papszName == nullptr || EQUAL(papszName, ""))
11704 0 : return false;
11705 :
11706 3267 : for (int i = 0; papszValues && papszValues[i]; ++i)
11707 : {
11708 2355 : if (EQUAL(papszName, papszValues[i]))
11709 144 : return true;
11710 : }
11711 :
11712 912 : return false;
11713 : }
11714 :
11715 : // Test that a variable is longitude/latitude coordinate,
11716 : // following CF 4.1 and 4.2.
11717 4463 : bool NCDFIsVarLongitude(int nCdfId, int nVarId, const char *pszVarName)
11718 : {
11719 : // Check for matching attributes.
11720 4463 : int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLongitudeAttribNames,
11721 : papszCFLongitudeAttribValues, nVarId,
11722 : pszVarName);
11723 : // If not found using attributes then check using var name
11724 : // unless GDAL_NETCDF_VERIFY_DIMS=STRICT.
11725 4463 : if (bVal == -1)
11726 : {
11727 342 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11728 : "STRICT"))
11729 342 : bVal = NCDFEqual(pszVarName, papszCFLongitudeVarNames);
11730 : else
11731 0 : bVal = FALSE;
11732 : }
11733 4121 : else if (bVal)
11734 : {
11735 : // Check that the units is not 'm' or '1'. See #6759
11736 833 : char *pszTemp = nullptr;
11737 1221 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11738 388 : pszTemp != nullptr)
11739 : {
11740 388 : if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
11741 100 : bVal = false;
11742 388 : CPLFree(pszTemp);
11743 : }
11744 : }
11745 :
11746 4463 : return CPL_TO_BOOL(bVal);
11747 : }
11748 :
11749 2586 : bool NCDFIsVarLatitude(int nCdfId, int nVarId, const char *pszVarName)
11750 : {
11751 2586 : int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLatitudeAttribNames,
11752 : papszCFLatitudeAttribValues, nVarId,
11753 : pszVarName);
11754 2586 : if (bVal == -1)
11755 : {
11756 191 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11757 : "STRICT"))
11758 191 : bVal = NCDFEqual(pszVarName, papszCFLatitudeVarNames);
11759 : else
11760 0 : bVal = FALSE;
11761 : }
11762 2395 : else if (bVal)
11763 : {
11764 : // Check that the units is not 'm' or '1'. See #6759
11765 580 : char *pszTemp = nullptr;
11766 724 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11767 144 : pszTemp != nullptr)
11768 : {
11769 144 : if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
11770 37 : bVal = false;
11771 144 : CPLFree(pszTemp);
11772 : }
11773 : }
11774 :
11775 2586 : return CPL_TO_BOOL(bVal);
11776 : }
11777 :
11778 2849 : bool NCDFIsVarProjectionX(int nCdfId, int nVarId, const char *pszVarName)
11779 : {
11780 2849 : int bVal = NCDFDoesVarContainAttribVal(
11781 : nCdfId, papszCFProjectionXAttribNames, papszCFProjectionXAttribValues,
11782 : nVarId, pszVarName);
11783 2849 : if (bVal == -1)
11784 : {
11785 336 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11786 : "STRICT"))
11787 336 : bVal = NCDFEqual(pszVarName, papszCFProjectionXVarNames);
11788 : else
11789 0 : bVal = FALSE;
11790 : }
11791 2513 : else if (bVal)
11792 : {
11793 : // Check that the units is not '1'
11794 530 : char *pszTemp = nullptr;
11795 812 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11796 282 : pszTemp != nullptr)
11797 : {
11798 282 : if (EQUAL(pszTemp, "1"))
11799 5 : bVal = false;
11800 282 : CPLFree(pszTemp);
11801 : }
11802 : }
11803 :
11804 2849 : return CPL_TO_BOOL(bVal);
11805 : }
11806 :
11807 2006 : bool NCDFIsVarProjectionY(int nCdfId, int nVarId, const char *pszVarName)
11808 : {
11809 2006 : int bVal = NCDFDoesVarContainAttribVal(
11810 : nCdfId, papszCFProjectionYAttribNames, papszCFProjectionYAttribValues,
11811 : nVarId, pszVarName);
11812 2006 : if (bVal == -1)
11813 : {
11814 187 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11815 : "STRICT"))
11816 187 : bVal = NCDFEqual(pszVarName, papszCFProjectionYVarNames);
11817 : else
11818 0 : bVal = FALSE;
11819 : }
11820 1819 : else if (bVal)
11821 : {
11822 : // Check that the units is not '1'
11823 522 : char *pszTemp = nullptr;
11824 797 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11825 275 : pszTemp != nullptr)
11826 : {
11827 275 : if (EQUAL(pszTemp, "1"))
11828 5 : bVal = false;
11829 275 : CPLFree(pszTemp);
11830 : }
11831 : }
11832 :
11833 2006 : return CPL_TO_BOOL(bVal);
11834 : }
11835 :
11836 : /* test that a variable is a vertical coordinate, following CF 4.3 */
11837 1205 : bool NCDFIsVarVerticalCoord(int nCdfId, int nVarId, const char *pszVarName)
11838 : {
11839 : /* check for matching attributes */
11840 1205 : if (NCDFDoesVarContainAttribVal(nCdfId, papszCFVerticalAttribNames,
11841 : papszCFVerticalAttribValues, nVarId,
11842 1205 : pszVarName))
11843 130 : return true;
11844 : /* check for matching units */
11845 1075 : else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
11846 : papszCFVerticalUnitsValues, nVarId,
11847 1075 : pszVarName))
11848 31 : return true;
11849 : /* check for matching standard name */
11850 1044 : else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_STD_NAME,
11851 : papszCFVerticalStandardNameValues,
11852 1044 : nVarId, pszVarName))
11853 0 : return true;
11854 : else
11855 1044 : return false;
11856 : }
11857 :
11858 : /* test that a variable is a time coordinate, following CF 4.4 */
11859 262 : bool NCDFIsVarTimeCoord(int nCdfId, int nVarId, const char *pszVarName)
11860 : {
11861 : /* check for matching attributes */
11862 262 : if (NCDFDoesVarContainAttribVal(nCdfId, papszCFTimeAttribNames,
11863 : papszCFTimeAttribValues, nVarId,
11864 262 : pszVarName))
11865 110 : return true;
11866 : /* check for matching units */
11867 152 : else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
11868 : papszCFTimeUnitsValues, nVarId,
11869 152 : pszVarName, false))
11870 2 : return true;
11871 : else
11872 150 : return false;
11873 : }
11874 :
11875 : // Parse a string, and return as a string list.
11876 : // If it is an array of the form {a,b}, then tokenize it.
11877 : // Otherwise, return a copy.
11878 235 : static CPLStringList NCDFTokenizeArray(const char *pszValue)
11879 : {
11880 235 : if (pszValue == nullptr || EQUAL(pszValue, ""))
11881 64 : return CPLStringList();
11882 :
11883 342 : CPLStringList aosValues;
11884 171 : const int nLen = static_cast<int>(strlen(pszValue));
11885 :
11886 171 : if (pszValue[0] == '{' && nLen > 2 && pszValue[nLen - 1] == '}')
11887 : {
11888 41 : char *pszTemp = static_cast<char *>(CPLMalloc((nLen - 2) + 1));
11889 41 : strncpy(pszTemp, pszValue + 1, nLen - 2);
11890 41 : pszTemp[nLen - 2] = '\0';
11891 : aosValues.Assign(
11892 41 : CSLTokenizeString2(pszTemp, ",", CSLT_ALLOWEMPTYTOKENS));
11893 41 : CPLFree(pszTemp);
11894 : }
11895 : else
11896 : {
11897 130 : aosValues.AddString(pszValue);
11898 : }
11899 :
11900 171 : return aosValues;
11901 : }
11902 :
11903 : // Open a NetCDF subdataset from full path /group1/group2/.../groupn/var.
11904 : // Leading slash is optional.
11905 448 : static CPLErr NCDFOpenSubDataset(int nCdfId, const char *pszSubdatasetName,
11906 : int *pnGroupId, int *pnVarId)
11907 : {
11908 448 : *pnGroupId = -1;
11909 448 : *pnVarId = -1;
11910 :
11911 : // Open group.
11912 896 : std::string osGroupFullName = CPLGetPathSafe(pszSubdatasetName);
11913 : // Add a leading slash if needed.
11914 448 : if (osGroupFullName.empty() || osGroupFullName[0] != '/')
11915 : {
11916 431 : osGroupFullName = "/" + osGroupFullName;
11917 : }
11918 : // Detect root group.
11919 448 : if (osGroupFullName == "/")
11920 : {
11921 431 : *pnGroupId = nCdfId;
11922 : }
11923 : else
11924 : {
11925 : int status =
11926 17 : nc_inq_grp_full_ncid(nCdfId, osGroupFullName.c_str(), pnGroupId);
11927 17 : NCDF_ERR_RET(status);
11928 : }
11929 :
11930 : // Open var.
11931 448 : const char *pszVarName = CPLGetFilename(pszSubdatasetName);
11932 448 : NCDF_ERR_RET(nc_inq_varid(*pnGroupId, pszVarName, pnVarId));
11933 :
11934 448 : return CE_None;
11935 : }
11936 :
11937 : // Get all dimensions visible from a given NetCDF (or group) ID and any of
11938 : // its parents.
11939 384 : static CPLErr NCDFGetVisibleDims(int nGroupId, int *pnDims, int **ppanDimIds)
11940 : {
11941 384 : int nDims = 0;
11942 384 : int *panDimIds = nullptr;
11943 384 : NCDF_ERR_RET(nc_inq_dimids(nGroupId, &nDims, nullptr, true));
11944 :
11945 384 : panDimIds = static_cast<int *>(CPLMalloc(nDims * sizeof(int)));
11946 :
11947 384 : int status = nc_inq_dimids(nGroupId, nullptr, panDimIds, true);
11948 384 : if (status != NC_NOERR)
11949 0 : CPLFree(panDimIds);
11950 384 : NCDF_ERR_RET(status);
11951 :
11952 384 : *pnDims = nDims;
11953 384 : *ppanDimIds = panDimIds;
11954 :
11955 384 : return CE_None;
11956 : }
11957 :
11958 : // Get direct sub-groups IDs of a given NetCDF (or group) ID.
11959 : // Consider only direct children, does not get children of children.
11960 3500 : static CPLErr NCDFGetSubGroups(int nGroupId, int *pnSubGroups,
11961 : int **ppanSubGroupIds)
11962 : {
11963 3500 : *pnSubGroups = 0;
11964 3500 : *ppanSubGroupIds = nullptr;
11965 :
11966 : int nSubGroups;
11967 3500 : NCDF_ERR_RET(nc_inq_grps(nGroupId, &nSubGroups, nullptr));
11968 : int *panSubGroupIds =
11969 3500 : static_cast<int *>(CPLMalloc(nSubGroups * sizeof(int)));
11970 3500 : NCDF_ERR_RET(nc_inq_grps(nGroupId, nullptr, panSubGroupIds));
11971 3500 : *pnSubGroups = nSubGroups;
11972 3500 : *ppanSubGroupIds = panSubGroupIds;
11973 :
11974 3500 : return CE_None;
11975 : }
11976 :
11977 : // Get the full name of a given NetCDF (or group) ID
11978 : // (e.g. /group1/group2/.../groupn).
11979 : // bNC3Compat remove the leading slash for top-level variables for
11980 : // backward compatibility (top-level variables are the ones in the root group).
11981 20722 : static CPLErr NCDFGetGroupFullName(int nGroupId, std::string &osFullName,
11982 : bool bNC3Compat)
11983 : {
11984 20722 : osFullName = "";
11985 :
11986 : size_t nFullNameLen;
11987 20722 : NCDF_ERR_RET(nc_inq_grpname_len(nGroupId, &nFullNameLen));
11988 20722 : osFullName.resize(nFullNameLen);
11989 :
11990 : const int status =
11991 20722 : nc_inq_grpname_full(nGroupId, &nFullNameLen, osFullName.data());
11992 20722 : if (status != NC_NOERR)
11993 : {
11994 0 : osFullName = "";
11995 0 : NCDF_ERR_RET(status);
11996 : }
11997 :
11998 20722 : if (bNC3Compat && osFullName == "/")
11999 9078 : osFullName = "";
12000 :
12001 20722 : return CE_None;
12002 : }
12003 :
12004 11433 : CPLString NCDFGetGroupFullName(int nGroupId)
12005 : {
12006 11433 : CPLString osFullName;
12007 11433 : NCDFGetGroupFullName(nGroupId, osFullName, false);
12008 :
12009 11433 : return osFullName;
12010 : }
12011 :
12012 : // Get the full name of a given NetCDF variable ID
12013 : // (e.g. /group1/group2/.../groupn/var).
12014 : // Handle also NC_GLOBAL as nVarId.
12015 : // bNC3Compat remove the leading slash for top-level variables for
12016 : // backward compatibility (top-level variables are the ones in the root group).
12017 9189 : static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId,
12018 : std::string &osFullName, bool bNC3Compat)
12019 : {
12020 9189 : osFullName = "";
12021 18378 : std::string osGroupFullName;
12022 9189 : ERR_RET(NCDFGetGroupFullName(nGroupId, osGroupFullName, bNC3Compat));
12023 : char szVarName[NC_MAX_NAME + 1];
12024 9189 : if (nVarId == NC_GLOBAL)
12025 : {
12026 1199 : strcpy(szVarName, "NC_GLOBAL");
12027 : }
12028 : else
12029 : {
12030 7990 : int status = nc_inq_varname(nGroupId, nVarId, szVarName);
12031 7990 : if (status != NC_NOERR)
12032 : {
12033 0 : NCDF_ERR_RET(status);
12034 : }
12035 : }
12036 9189 : const char *pszSep = "/";
12037 9189 : if (osGroupFullName.empty() || osGroupFullName == "/")
12038 8980 : pszSep = "";
12039 : osFullName =
12040 9189 : CPLOPrintf("%s%s%s", osGroupFullName.c_str(), pszSep, szVarName);
12041 :
12042 9189 : return CE_None;
12043 : }
12044 :
12045 : // Get the NetCDF root group ID of a given group ID.
12046 0 : static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId)
12047 : {
12048 0 : *pnRootGroupId = -1;
12049 : // Recurse on parent group.
12050 : int nParentGroupId;
12051 0 : int status = nc_inq_grp_parent(nStartGroupId, &nParentGroupId);
12052 0 : if (status == NC_NOERR)
12053 0 : return NCDFGetRootGroup(nParentGroupId, pnRootGroupId);
12054 0 : else if (status != NC_ENOGRP)
12055 0 : NCDF_ERR_RET(status);
12056 : else // No more parent group.
12057 : {
12058 0 : *pnRootGroupId = nStartGroupId;
12059 : }
12060 :
12061 0 : return CE_None;
12062 : }
12063 :
12064 : // Implementation of NCDFResolveVar/Att.
12065 14681 : static CPLErr NCDFResolveElem(int nStartGroupId, const char *pszVar,
12066 : const char *pszAtt, int *pnGroupId, int *pnId,
12067 : bool bMandatory)
12068 : {
12069 14681 : if (!pszVar && !pszAtt)
12070 : {
12071 0 : CPLError(CE_Failure, CPLE_IllegalArg,
12072 : "pszVar and pszAtt NCDFResolveElem() args are both null.");
12073 0 : return CE_Failure;
12074 : }
12075 :
12076 : enum
12077 : {
12078 : NCRM_PARENT,
12079 : NCRM_WIDTH_WISE
12080 14681 : } eNCResolveMode = NCRM_PARENT;
12081 :
12082 29362 : std::queue<int> aoQueueGroupIdsToVisit;
12083 14681 : aoQueueGroupIdsToVisit.push(nStartGroupId);
12084 :
12085 16656 : while (!aoQueueGroupIdsToVisit.empty())
12086 : {
12087 : // Get the first group of the FIFO queue.
12088 14875 : *pnGroupId = aoQueueGroupIdsToVisit.front();
12089 14875 : aoQueueGroupIdsToVisit.pop();
12090 :
12091 : // Look if this group contains the searched element.
12092 : int status;
12093 14875 : if (pszVar)
12094 14638 : status = nc_inq_varid(*pnGroupId, pszVar, pnId);
12095 : else // pszAtt != nullptr.
12096 237 : status = nc_inq_attid(*pnGroupId, NC_GLOBAL, pszAtt, pnId);
12097 :
12098 14875 : if (status == NC_NOERR)
12099 : {
12100 12900 : return CE_None;
12101 : }
12102 1975 : else if ((pszVar && status != NC_ENOTVAR) ||
12103 234 : (pszAtt && status != NC_ENOTATT))
12104 : {
12105 0 : NCDF_ERR(status);
12106 : }
12107 : // Element not found, in NC4 case we must search in other groups
12108 : // following the CF logic.
12109 :
12110 : // The first resolve mode consists to search on parent groups.
12111 1975 : if (eNCResolveMode == NCRM_PARENT)
12112 : {
12113 1856 : int nParentGroupId = -1;
12114 1856 : int status2 = nc_inq_grp_parent(*pnGroupId, &nParentGroupId);
12115 1856 : if (status2 == NC_NOERR)
12116 62 : aoQueueGroupIdsToVisit.push(nParentGroupId);
12117 1794 : else if (status2 != NC_ENOGRP)
12118 0 : NCDF_ERR(status2);
12119 1794 : else if (pszVar)
12120 : // When resolving a variable, if there is no more
12121 : // parent group then we switch to width-wise search mode
12122 : // starting from the latest found parent group.
12123 1563 : eNCResolveMode = NCRM_WIDTH_WISE;
12124 : }
12125 :
12126 : // The second resolve mode is a width-wise search.
12127 1975 : if (eNCResolveMode == NCRM_WIDTH_WISE)
12128 : {
12129 : // Enqueue all direct sub-groups.
12130 1682 : int nSubGroups = 0;
12131 1682 : int *panSubGroupIds = nullptr;
12132 1682 : NCDFGetSubGroups(*pnGroupId, &nSubGroups, &panSubGroupIds);
12133 1814 : for (int i = 0; i < nSubGroups; i++)
12134 132 : aoQueueGroupIdsToVisit.push(panSubGroupIds[i]);
12135 1682 : CPLFree(panSubGroupIds);
12136 : }
12137 : }
12138 :
12139 1781 : if (bMandatory)
12140 : {
12141 0 : std::string osStartGroupFullName;
12142 0 : NCDFGetGroupFullName(nStartGroupId, osStartGroupFullName);
12143 0 : CPLError(CE_Failure, CPLE_AppDefined,
12144 : "Cannot resolve mandatory %s %s from group %s",
12145 : (pszVar ? pszVar : pszAtt),
12146 : (pszVar ? "variable" : "attribute"),
12147 : osStartGroupFullName.c_str());
12148 : }
12149 :
12150 1781 : *pnGroupId = -1;
12151 1781 : *pnId = -1;
12152 1781 : return CE_Failure;
12153 : }
12154 :
12155 : // Resolve a variable name from a given starting group following the CF logic:
12156 : // - if var name is an absolute path then directly open it
12157 : // - first search in the starting group and its parent groups
12158 : // - then if there is no more parent group we switch to a width-wise search
12159 : // mode starting from the latest found parent group.
12160 : // The full CF logic is described here:
12161 : // https://github.com/diwg/cf2/blob/master/group/cf2-group.adoc#scope
12162 : // If bMandatory then print an error if resolving fails.
12163 : // TODO: implement support of relative paths.
12164 : // TODO: to follow strictly the CF logic, when searching for a coordinate
12165 : // variable, we must stop the parent search mode once the corresponding
12166 : // dimension is found and start the width-wise search from this group.
12167 : // TODO: to follow strictly the CF logic, when searching in width-wise mode
12168 : // we should skip every groups already visited during the parent
12169 : // search mode (but revisiting them should have no impact so we could
12170 : // let as it is if it is simpler...)
12171 : // TODO: CF specifies that the width-wise search order is "left-to-right" so
12172 : // maybe we must sort sibling groups alphabetically? but maybe not
12173 : // necessary if nc_inq_grps() already sort them?
12174 14447 : CPLErr NCDFResolveVar(int nStartGroupId, const char *pszVar, int *pnGroupId,
12175 : int *pnVarId, bool bMandatory)
12176 : {
12177 14447 : *pnGroupId = -1;
12178 14447 : *pnVarId = -1;
12179 14447 : int nGroupId = nStartGroupId, nVarId;
12180 14447 : if (pszVar[0] == '/')
12181 : {
12182 : // This is an absolute path: we can open the var directly.
12183 : int nRootGroupId;
12184 0 : ERR_RET(NCDFGetRootGroup(nStartGroupId, &nRootGroupId));
12185 0 : ERR_RET(NCDFOpenSubDataset(nRootGroupId, pszVar, &nGroupId, &nVarId));
12186 : }
12187 : else
12188 : {
12189 : // We have to search the variable following the CF logic.
12190 14447 : ERR_RET(NCDFResolveElem(nStartGroupId, pszVar, nullptr, &nGroupId,
12191 : &nVarId, bMandatory));
12192 : }
12193 12897 : *pnGroupId = nGroupId;
12194 12897 : *pnVarId = nVarId;
12195 12897 : return CE_None;
12196 : }
12197 :
12198 : // Like NCDFResolveVar but returns directly the var full name.
12199 1540 : static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,
12200 : std::string &osFullName, bool bMandatory)
12201 : {
12202 : int nGroupId, nVarId;
12203 1540 : osFullName = "";
12204 1540 : ERR_RET(
12205 : NCDFResolveVar(nStartGroupId, pszVar, &nGroupId, &nVarId, bMandatory));
12206 1510 : return NCDFGetVarFullName(nGroupId, nVarId, osFullName);
12207 : }
12208 :
12209 : // Like NCDFResolveVar but resolves an attribute instead a variable and
12210 : // returns its integer value.
12211 : // Only GLOBAL attributes are supported for the moment.
12212 234 : static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,
12213 : const char *pszAtt, int *pnAtt, bool bMandatory)
12214 : {
12215 234 : int nGroupId = nStartGroupId, nAttId = nStartVarId;
12216 234 : ERR_RET(NCDFResolveElem(nStartGroupId, nullptr, pszAtt, &nGroupId, &nAttId,
12217 : bMandatory));
12218 3 : NCDF_ERR_RET(nc_get_att_int(nGroupId, NC_GLOBAL, pszAtt, pnAtt));
12219 3 : return CE_None;
12220 : }
12221 :
12222 : // Filter variables to keep only valid 2+D raster bands and vector fields in
12223 : // a given a NetCDF (or group) ID and its sub-groups.
12224 : // Coordinate or boundary variables are ignored.
12225 : // It also creates corresponding vector layers.
12226 581 : CPLErr netCDFDataset::FilterVars(
12227 : int nCdfId, bool bKeepRasters, bool bKeepVectors,
12228 : const CPLStringList &aosIgnoreVars, int *pnRasterVars, int *pnGroupId,
12229 : int *pnVarId, int *pnIgnoredVars,
12230 : std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>
12231 : &oMap2DDimsToGroupAndVar)
12232 : {
12233 581 : int nVars = 0;
12234 581 : int nRasterVars = 0;
12235 581 : NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
12236 :
12237 1162 : std::vector<int> anPotentialVectorVarID;
12238 : // oMapDimIdToCount[x] = number of times dim x is the first dimension of
12239 : // potential vector variables
12240 1162 : std::map<int, int> oMapDimIdToCount;
12241 581 : int nVarXId = -1;
12242 581 : int nVarYId = -1;
12243 581 : int nVarZId = -1;
12244 581 : int nVarTimeId = -1;
12245 581 : int nVarTimeDimId = -1;
12246 581 : bool bIsVectorOnly = true;
12247 581 : int nProfileDimId = -1;
12248 581 : int nParentIndexVarID = -1;
12249 :
12250 3554 : for (int v = 0; v < nVars; v++)
12251 : {
12252 : int nVarDims;
12253 2973 : NCDF_ERR_RET(nc_inq_varndims(nCdfId, v, &nVarDims));
12254 : // Should we ignore this variable?
12255 : char szTemp[NC_MAX_NAME + 1];
12256 2973 : szTemp[0] = '\0';
12257 2973 : NCDF_ERR_RET(nc_inq_varname(nCdfId, v, szTemp));
12258 :
12259 2973 : if (strstr(szTemp, "_node_coordinates") ||
12260 2973 : strstr(szTemp, "_node_count"))
12261 : {
12262 : // Ignore CF-1.8 Simple Geometries helper variables
12263 70 : continue;
12264 : }
12265 :
12266 4337 : if (nVarDims == 1 && (NCDFIsVarLongitude(nCdfId, -1, szTemp) ||
12267 1434 : NCDFIsVarProjectionX(nCdfId, -1, szTemp)))
12268 : {
12269 396 : nVarXId = v;
12270 : }
12271 3545 : else if (nVarDims == 1 && (NCDFIsVarLatitude(nCdfId, -1, szTemp) ||
12272 1038 : NCDFIsVarProjectionY(nCdfId, -1, szTemp)))
12273 : {
12274 395 : nVarYId = v;
12275 : }
12276 2112 : else if (nVarDims == 1 && NCDFIsVarVerticalCoord(nCdfId, -1, szTemp))
12277 : {
12278 97 : nVarZId = v;
12279 : }
12280 : else
12281 : {
12282 2015 : std::string osFullName;
12283 2015 : CPLErr eErr = NCDFGetVarFullName(nCdfId, v, osFullName);
12284 2015 : if (eErr != CE_None)
12285 : {
12286 0 : continue;
12287 : }
12288 : const bool bIgnoreVar =
12289 2015 : aosIgnoreVars.FindString(osFullName.c_str()) != -1;
12290 2015 : if (bIgnoreVar)
12291 : {
12292 120 : if (nVarDims == 1 && NCDFIsVarTimeCoord(nCdfId, -1, szTemp))
12293 : {
12294 11 : nVarTimeId = v;
12295 11 : nc_inq_vardimid(nCdfId, v, &nVarTimeDimId);
12296 : }
12297 109 : else if (nVarDims > 1)
12298 : {
12299 105 : (*pnIgnoredVars)++;
12300 105 : CPLDebug("GDAL_netCDF", "variable #%d [%s] was ignored", v,
12301 : szTemp);
12302 : }
12303 : }
12304 : // Only accept 2+D vars.
12305 1895 : else if (nVarDims >= 2)
12306 : {
12307 777 : bool bRasterCandidate = true;
12308 : // Identify variables that might be vector variables
12309 777 : if (nVarDims == 2)
12310 : {
12311 698 : int anDimIds[2] = {-1, -1};
12312 698 : nc_inq_vardimid(nCdfId, v, anDimIds);
12313 :
12314 698 : nc_type vartype = NC_NAT;
12315 698 : nc_inq_vartype(nCdfId, v, &vartype);
12316 :
12317 : char szDimNameFirst[NC_MAX_NAME + 1];
12318 : char szDimNameSecond[NC_MAX_NAME + 1];
12319 698 : szDimNameFirst[0] = '\0';
12320 698 : szDimNameSecond[0] = '\0';
12321 1591 : if (vartype == NC_CHAR &&
12322 195 : nc_inq_dimname(nCdfId, anDimIds[0], szDimNameFirst) ==
12323 195 : NC_NOERR &&
12324 195 : nc_inq_dimname(nCdfId, anDimIds[1], szDimNameSecond) ==
12325 195 : NC_NOERR &&
12326 195 : !NCDFIsVarLongitude(nCdfId, -1, szDimNameSecond) &&
12327 195 : !NCDFIsVarProjectionX(nCdfId, -1, szDimNameSecond) &&
12328 1088 : !NCDFIsVarLatitude(nCdfId, -1, szDimNameFirst) &&
12329 195 : !NCDFIsVarProjectionY(nCdfId, -1, szDimNameFirst))
12330 : {
12331 195 : anPotentialVectorVarID.push_back(v);
12332 195 : oMapDimIdToCount[anDimIds[0]]++;
12333 195 : if (strstr(szDimNameSecond, "_max_width"))
12334 : {
12335 165 : bRasterCandidate = false;
12336 : }
12337 : else
12338 : {
12339 30 : std::array<int, 3> oKey{anDimIds[0], anDimIds[1],
12340 30 : vartype};
12341 30 : oMap2DDimsToGroupAndVar[oKey].emplace_back(
12342 30 : std::pair(nCdfId, v));
12343 : }
12344 : }
12345 : else
12346 : {
12347 503 : std::array<int, 3> oKey{anDimIds[0], anDimIds[1],
12348 503 : vartype};
12349 503 : oMap2DDimsToGroupAndVar[oKey].emplace_back(
12350 503 : std::pair(nCdfId, v));
12351 503 : bIsVectorOnly = false;
12352 : }
12353 : }
12354 : else
12355 : {
12356 79 : bIsVectorOnly = false;
12357 : }
12358 777 : if (bKeepRasters && bRasterCandidate)
12359 : {
12360 583 : *pnGroupId = nCdfId;
12361 583 : *pnVarId = v;
12362 583 : nRasterVars++;
12363 : }
12364 : }
12365 1118 : else if (nVarDims == 1)
12366 : {
12367 795 : nc_type atttype = NC_NAT;
12368 795 : size_t attlen = 0;
12369 795 : if (nc_inq_att(nCdfId, v, "instance_dimension", &atttype,
12370 36 : &attlen) == NC_NOERR &&
12371 795 : atttype == NC_CHAR && attlen < NC_MAX_NAME)
12372 : {
12373 72 : std::string osInstanceDimension;
12374 36 : if (NCDFGetAttr(nCdfId, v, "instance_dimension",
12375 36 : osInstanceDimension) == CE_None)
12376 : {
12377 : const int status =
12378 36 : nc_inq_dimid(nCdfId, osInstanceDimension.c_str(),
12379 : &nProfileDimId);
12380 36 : if (status == NC_NOERR)
12381 36 : nParentIndexVarID = v;
12382 : else
12383 0 : nProfileDimId = -1;
12384 36 : if (status == NC_EBADDIM)
12385 0 : CPLError(CE_Warning, CPLE_AppDefined,
12386 : "Attribute instance_dimension='%s' refers "
12387 : "to a non existing dimension",
12388 : osInstanceDimension.c_str());
12389 : else
12390 36 : NCDF_ERR(status);
12391 : }
12392 : }
12393 795 : if (v != nParentIndexVarID)
12394 : {
12395 759 : anPotentialVectorVarID.push_back(v);
12396 759 : int nDimId = -1;
12397 759 : nc_inq_vardimid(nCdfId, v, &nDimId);
12398 759 : oMapDimIdToCount[nDimId]++;
12399 : }
12400 : }
12401 : }
12402 : }
12403 :
12404 : // If we are opened in raster-only mode and that there are only 1D or 2D
12405 : // variables and that the 2D variables have no X/Y dim, and all
12406 : // variables refer to the same main dimension (or 2 dimensions for
12407 : // featureType=profile), then it is a pure vector dataset
12408 : CPLString osFeatureType(
12409 581 : aosMetadata.FetchNameValueDef("NC_GLOBAL#featureType", ""));
12410 448 : if (bKeepRasters && !bKeepVectors && bIsVectorOnly && nRasterVars > 0 &&
12411 1029 : !anPotentialVectorVarID.empty() &&
12412 0 : (oMapDimIdToCount.size() == 1 ||
12413 0 : (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2 &&
12414 0 : nProfileDimId >= 0)))
12415 : {
12416 0 : anPotentialVectorVarID.resize(0);
12417 : }
12418 : else
12419 : {
12420 581 : *pnRasterVars += nRasterVars;
12421 : }
12422 :
12423 581 : if (!anPotentialVectorVarID.empty() && bKeepVectors)
12424 : {
12425 : // Take the dimension that is referenced the most times.
12426 135 : if (!(oMapDimIdToCount.size() == 1 ||
12427 70 : (EQUAL(osFeatureType, "profile") &&
12428 70 : oMapDimIdToCount.size() == 2 && nProfileDimId >= 0)))
12429 : {
12430 0 : CPLError(CE_Warning, CPLE_AppDefined,
12431 : "The dataset has several variables that could be "
12432 : "identified as vector fields, but not all share the same "
12433 : "primary dimension. Consequently they will be ignored.");
12434 : }
12435 : else
12436 : {
12437 101 : if (nVarTimeId >= 0 &&
12438 101 : oMapDimIdToCount.find(nVarTimeDimId) != oMapDimIdToCount.end())
12439 : {
12440 1 : anPotentialVectorVarID.push_back(nVarTimeId);
12441 : }
12442 100 : CreateGrpVectorLayers(nCdfId, osFeatureType, anPotentialVectorVarID,
12443 : oMapDimIdToCount, nVarXId, nVarYId, nVarZId,
12444 : nProfileDimId, nParentIndexVarID,
12445 : bKeepRasters);
12446 : }
12447 : }
12448 :
12449 : // Recurse on sub-groups.
12450 581 : int nSubGroups = 0;
12451 581 : int *panSubGroupIds = nullptr;
12452 581 : NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
12453 617 : for (int i = 0; i < nSubGroups; i++)
12454 : {
12455 36 : FilterVars(panSubGroupIds[i], bKeepRasters, bKeepVectors, aosIgnoreVars,
12456 : pnRasterVars, pnGroupId, pnVarId, pnIgnoredVars,
12457 : oMap2DDimsToGroupAndVar);
12458 : }
12459 581 : CPLFree(panSubGroupIds);
12460 :
12461 581 : return CE_None;
12462 : }
12463 :
12464 : // Create vector layers from given potentially identified vector variables
12465 : // resulting from the scanning of a NetCDF (or group) ID.
12466 100 : CPLErr netCDFDataset::CreateGrpVectorLayers(
12467 : int nCdfId, const CPLString &osFeatureType,
12468 : const std::vector<int> &anPotentialVectorVarID,
12469 : const std::map<int, int> &oMapDimIdToCount, int nVarXId, int nVarYId,
12470 : int nVarZId, int nProfileDimId, int nParentIndexVarID, bool bKeepRasters)
12471 : {
12472 200 : std::string osGroupName;
12473 100 : NCDFGetGroupFullName(nCdfId, osGroupName);
12474 100 : if (osGroupName.empty())
12475 : {
12476 98 : osGroupName = CPLGetBasenameSafe(osFilename);
12477 : }
12478 100 : OGRwkbGeometryType eGType = wkbUnknown;
12479 : CPLString osLayerName = aosMetadata.FetchNameValueDef(
12480 200 : "NC_GLOBAL#ogr_layer_name", osGroupName.c_str());
12481 100 : aosMetadata.SetNameValue("NC_GLOBAL#ogr_layer_name", nullptr);
12482 :
12483 100 : if (EQUAL(osFeatureType, "point") || EQUAL(osFeatureType, "profile"))
12484 : {
12485 54 : aosMetadata.SetNameValue("NC_GLOBAL#featureType", nullptr);
12486 54 : eGType = wkbPoint;
12487 : }
12488 :
12489 : const char *pszLayerType =
12490 100 : aosMetadata.FetchNameValue("NC_GLOBAL#ogr_layer_type");
12491 100 : if (pszLayerType != nullptr)
12492 : {
12493 10 : eGType = OGRFromOGCGeomType(pszLayerType);
12494 10 : aosMetadata.SetNameValue("NC_GLOBAL#ogr_layer_type", nullptr);
12495 : }
12496 :
12497 : CPLString osGeometryField =
12498 200 : aosMetadata.FetchNameValueDef("NC_GLOBAL#ogr_geometry_field", "");
12499 100 : aosMetadata.SetNameValue("NC_GLOBAL#ogr_geometry_field", nullptr);
12500 :
12501 100 : int nFirstVarId = -1;
12502 100 : int nVectorDim = oMapDimIdToCount.rbegin()->first;
12503 100 : if (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2)
12504 : {
12505 35 : if (nVectorDim == nProfileDimId)
12506 0 : nVectorDim = oMapDimIdToCount.begin()->first;
12507 : }
12508 : else
12509 : {
12510 65 : nProfileDimId = -1;
12511 : }
12512 135 : for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)
12513 : {
12514 135 : int anDimIds[2] = {-1, -1};
12515 135 : nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);
12516 135 : if (nVectorDim == anDimIds[0])
12517 : {
12518 100 : nFirstVarId = anPotentialVectorVarID[j];
12519 100 : break;
12520 : }
12521 : }
12522 :
12523 : // In case where coordinates are explicitly specified for one of the
12524 : // field/variable, use them in priority over the ones that might have been
12525 : // identified above.
12526 100 : char *pszCoordinates = nullptr;
12527 100 : if (NCDFGetAttr(nCdfId, nFirstVarId, "coordinates", &pszCoordinates) ==
12528 : CE_None)
12529 : {
12530 : const CPLStringList aosTokens(
12531 110 : NCDFTokenizeCoordinatesAttribute(pszCFCoordinates));
12532 55 : for (int i = 0; i < aosTokens.size(); i++)
12533 : {
12534 0 : if (NCDFIsVarLongitude(nCdfId, -1, aosTokens[i]) ||
12535 0 : NCDFIsVarProjectionX(nCdfId, -1, aosTokens[i]))
12536 : {
12537 0 : nVarXId = -1;
12538 0 : CPL_IGNORE_RET_VAL(
12539 0 : nc_inq_varid(nCdfId, aosTokens[i], &nVarXId));
12540 : }
12541 0 : else if (NCDFIsVarLatitude(nCdfId, -1, aosTokens[i]) ||
12542 0 : NCDFIsVarProjectionY(nCdfId, -1, aosTokens[i]))
12543 : {
12544 0 : nVarYId = -1;
12545 0 : CPL_IGNORE_RET_VAL(
12546 0 : nc_inq_varid(nCdfId, aosTokens[i], &nVarYId));
12547 : }
12548 0 : else if (NCDFIsVarVerticalCoord(nCdfId, -1, aosTokens[i]))
12549 : {
12550 0 : nVarZId = -1;
12551 0 : CPL_IGNORE_RET_VAL(
12552 0 : nc_inq_varid(nCdfId, aosTokens[i], &nVarZId));
12553 : }
12554 : }
12555 : }
12556 100 : CPLFree(pszCoordinates);
12557 :
12558 : // Check that the X,Y,Z vars share 1D and share the same dimension as
12559 : // attribute variables.
12560 100 : if (nVarXId >= 0 && nVarYId >= 0)
12561 : {
12562 84 : int nVarDimCount = -1;
12563 84 : int nVarDimId = -1;
12564 84 : if (nc_inq_varndims(nCdfId, nVarXId, &nVarDimCount) != NC_NOERR ||
12565 84 : nVarDimCount != 1 ||
12566 84 : nc_inq_vardimid(nCdfId, nVarXId, &nVarDimId) != NC_NOERR ||
12567 84 : nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim) ||
12568 56 : nc_inq_varndims(nCdfId, nVarYId, &nVarDimCount) != NC_NOERR ||
12569 56 : nVarDimCount != 1 ||
12570 224 : nc_inq_vardimid(nCdfId, nVarYId, &nVarDimId) != NC_NOERR ||
12571 56 : nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim))
12572 : {
12573 28 : nVarXId = nVarYId = -1;
12574 : }
12575 111 : else if (nVarZId >= 0 &&
12576 55 : (nc_inq_varndims(nCdfId, nVarZId, &nVarDimCount) != NC_NOERR ||
12577 55 : nVarDimCount != 1 ||
12578 55 : nc_inq_vardimid(nCdfId, nVarZId, &nVarDimId) != NC_NOERR ||
12579 55 : nVarDimId != nVectorDim))
12580 : {
12581 0 : nVarZId = -1;
12582 : }
12583 : }
12584 :
12585 100 : if (eGType == wkbUnknown && nVarXId >= 0 && nVarYId >= 0)
12586 : {
12587 2 : eGType = wkbPoint;
12588 : }
12589 100 : if (eGType == wkbPoint && nVarXId >= 0 && nVarYId >= 0 && nVarZId >= 0)
12590 : {
12591 55 : eGType = wkbPoint25D;
12592 : }
12593 100 : if (eGType == wkbUnknown && osGeometryField.empty())
12594 : {
12595 34 : eGType = wkbNone;
12596 : }
12597 :
12598 : // Read projection info
12599 200 : CPLStringList aosMetadataBackup = aosMetadata;
12600 100 : ReadAttributes(nCdfId, nFirstVarId);
12601 100 : if (!this->bSGSupport)
12602 100 : SetProjectionFromVar(nCdfId, nFirstVarId, true);
12603 100 : const char *pszValue = FetchAttr(nCdfId, nFirstVarId, CF_GRD_MAPPING);
12604 200 : std::string osGridMapping = pszValue ? pszValue : "";
12605 100 : aosMetadata = std::move(aosMetadataBackup);
12606 :
12607 100 : OGRSpatialReference *poSRS = nullptr;
12608 100 : if (!m_oSRS.IsEmpty())
12609 : {
12610 21 : poSRS = m_oSRS.Clone();
12611 : }
12612 : // Reset if there's a 2D raster
12613 100 : m_bHasProjection = false;
12614 100 : m_bHasGeoTransform = false;
12615 :
12616 100 : if (!bKeepRasters)
12617 : {
12618 : // Strip out uninteresting metadata.
12619 67 : aosMetadata.SetNameValue("NC_GLOBAL#Conventions", nullptr);
12620 67 : aosMetadata.SetNameValue("NC_GLOBAL#GDAL", nullptr);
12621 67 : aosMetadata.SetNameValue("NC_GLOBAL#history", nullptr);
12622 : }
12623 :
12624 : std::shared_ptr<netCDFLayer> poLayer(
12625 100 : new netCDFLayer(this, nCdfId, osLayerName, eGType, poSRS));
12626 100 : if (poSRS != nullptr)
12627 21 : poSRS->Release();
12628 100 : poLayer->SetRecordDimID(nVectorDim);
12629 100 : if (wkbFlatten(eGType) == wkbPoint && nVarXId >= 0 && nVarYId >= 0)
12630 : {
12631 56 : poLayer->SetXYZVars(nVarXId, nVarYId, nVarZId);
12632 : }
12633 44 : else if (!osGeometryField.empty())
12634 : {
12635 10 : poLayer->SetWKTGeometryField(osGeometryField);
12636 : }
12637 100 : if (!osGridMapping.empty())
12638 : {
12639 21 : poLayer->SetGridMapping(osGridMapping.c_str());
12640 : }
12641 100 : poLayer->SetProfile(nProfileDimId, nParentIndexVarID);
12642 :
12643 742 : for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)
12644 : {
12645 642 : int anDimIds[2] = {-1, -1};
12646 642 : nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);
12647 642 : if (anDimIds[0] == nVectorDim ||
12648 68 : (nProfileDimId >= 0 && anDimIds[0] == nProfileDimId))
12649 : {
12650 : #ifdef NCDF_DEBUG
12651 : char szTemp2[NC_MAX_NAME + 1] = {};
12652 : CPL_IGNORE_RET_VAL(
12653 : nc_inq_varname(nCdfId, anPotentialVectorVarID[j], szTemp2));
12654 : CPLDebug("GDAL_netCDF", "Variable %s is a vector field", szTemp2);
12655 : #endif
12656 642 : poLayer->AddField(anPotentialVectorVarID[j]);
12657 : }
12658 : }
12659 :
12660 100 : if (poLayer->GetLayerDefn()->GetFieldCount() != 0 ||
12661 0 : poLayer->GetGeomType() != wkbNone)
12662 : {
12663 100 : papoLayers.push_back(poLayer);
12664 : }
12665 :
12666 200 : return CE_None;
12667 : }
12668 :
12669 : // Get all coordinate and boundary variables full names referenced in
12670 : // a given a NetCDF (or group) ID and its sub-groups.
12671 : // These variables are identified in other variable's
12672 : // "coordinates" and "bounds" attribute.
12673 : // Searching coordinate and boundary variables may need to explore
12674 : // parents groups (or other groups in case of reference given in form of an
12675 : // absolute path).
12676 : // See CF sections 5.2, 5.6 and 7.1
12677 582 : static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId,
12678 : CPLStringList &aosVars)
12679 : {
12680 582 : int nVars = 0;
12681 582 : NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
12682 :
12683 3569 : for (int v = 0; v < nVars; v++)
12684 : {
12685 2987 : char *pszTemp = nullptr;
12686 5974 : CPLStringList aosTokens;
12687 2987 : if (NCDFGetAttr(nCdfId, v, "coordinates", &pszTemp) == CE_None)
12688 495 : aosTokens.Assign(NCDFTokenizeCoordinatesAttribute(pszTemp));
12689 2987 : CPLFree(pszTemp);
12690 2987 : pszTemp = nullptr;
12691 2987 : if (NCDFGetAttr(nCdfId, v, "bounds", &pszTemp) == CE_None &&
12692 2987 : pszTemp != nullptr && !EQUAL(pszTemp, ""))
12693 20 : aosTokens.AddString(pszTemp);
12694 2987 : CPLFree(pszTemp);
12695 4415 : for (int i = 0; i < aosTokens.size(); i++)
12696 : {
12697 2856 : std::string osVarFullName;
12698 1428 : if (NCDFResolveVarFullName(nCdfId, aosTokens[i], osVarFullName) ==
12699 : CE_None)
12700 1398 : aosVars.AddString(osVarFullName);
12701 : }
12702 : }
12703 :
12704 : // Recurse on sub-groups.
12705 : int nSubGroups;
12706 582 : int *panSubGroupIds = nullptr;
12707 582 : NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
12708 618 : for (int i = 0; i < nSubGroups; i++)
12709 : {
12710 36 : NCDFGetCoordAndBoundVarFullNames(panSubGroupIds[i], aosVars);
12711 : }
12712 582 : CPLFree(panSubGroupIds);
12713 :
12714 582 : return CE_None;
12715 : }
12716 :
12717 : // Check if give type is user defined
12718 2032 : bool NCDFIsUserDefinedType(int /*ncid*/, int type)
12719 : {
12720 2032 : return type >= NC_FIRSTUSERTYPEID;
12721 : }
12722 :
12723 657 : char **NCDFTokenizeCoordinatesAttribute(const char *pszCoordinates)
12724 : {
12725 : // CF conventions use space as the separator for variable names in the
12726 : // coordinates attribute, but some products such as
12727 : // https://sentinel.esa.int/web/sentinel/technical-guides/sentinel-3-synergy/products-algorithms/level-2-aod-algorithms-and-products/level-2-aod-products-description
12728 : // use comma.
12729 657 : return CSLTokenizeString2(pszCoordinates, ", ", 0);
12730 : }
|