Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: netCDF read/write Driver
4 : * Purpose: GDAL bindings over netCDF library.
5 : * Author: Frank Warmerdam, warmerdam@pobox.com
6 : * Even Rouault <even.rouault at spatialys.com>
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2004, Frank Warmerdam
10 : * Copyright (c) 2007-2016, Even Rouault <even.rouault at spatialys.com>
11 : * Copyright (c) 2010, Kyle Shannon <kyle at pobox dot com>
12 : * Copyright (c) 2021, CLS
13 : *
14 : * SPDX-License-Identifier: MIT
15 : ****************************************************************************/
16 :
17 : #include "cpl_port.h"
18 :
19 : #include <array>
20 : #include <cassert>
21 : #include <cctype>
22 : #include <cerrno>
23 : #include <climits>
24 : #include <cmath>
25 : #include <cstdio>
26 : #include <cstdlib>
27 : #include <cstring>
28 : #include <ctime>
29 : #include <algorithm>
30 : #include <limits>
31 : #include <map>
32 : #include <set>
33 : #include <queue>
34 : #include <string>
35 : #include <tuple>
36 : #include <utility>
37 : #include <vector>
38 :
39 : // Must be included after standard includes, otherwise VS2015 fails when
40 : // including <ctime>
41 : #include "netcdfdataset.h"
42 : #include "netcdfdrivercore.h"
43 : #include "netcdfsg.h"
44 : #include "netcdfuffd.h"
45 :
46 : #include "netcdf_mem.h"
47 :
48 : #include "cpl_conv.h"
49 : #include "cpl_error.h"
50 : #include "cpl_float.h"
51 : #include "cpl_json.h"
52 : #include "cpl_minixml.h"
53 : #include "cpl_multiproc.h"
54 : #include "cpl_progress.h"
55 : #include "cpl_time.h"
56 : #include "gdal.h"
57 : #include "gdal_frmts.h"
58 : #include "gdal_priv_templates.hpp"
59 : #include "ogr_core.h"
60 : #include "ogr_srs_api.h"
61 :
62 : // netCDF 4.8 switched to expecting filenames in UTF-8 on Windows
63 : // But with netCDF 4.9 and https://github.com/Unidata/netcdf-c/pull/2277/,
64 : // this is apparently back to expecting filenames in current codepage...
65 : // Detect netCDF 4.8 with NC_ENCZARR
66 : // Detect netCDF 4.9 with NC_NOATTCREORD
67 : #if defined(NC_ENCZARR) && !defined(NC_NOATTCREORD)
68 : #define NETCDF_USES_UTF8
69 : #endif
70 :
71 : // Internal function declarations.
72 :
73 : static bool NCDFIsGDALVersionGTE(const char *pszVersion, int nTarget);
74 :
75 : static void
76 : NCDFAddGDALHistory(int fpImage, const char *pszFilename, bool bWriteGDALVersion,
77 : bool bWriteGDALHistory, const char *pszOldHist,
78 : const char *pszFunctionName,
79 : const char *pszCFVersion = GDAL_DEFAULT_NCDF_CONVENTIONS);
80 :
81 : static void NCDFAddHistory(int fpImage, const char *pszAddHist,
82 : const char *pszOldHist);
83 :
84 : static CPLErr NCDFSafeStrcat(char **ppszDest, const char *pszSrc,
85 : size_t *nDestSize);
86 :
87 : // Var / attribute helper functions.
88 : static CPLErr NCDFPutAttr(int nCdfId, int nVarId, const char *pszAttrName,
89 : const char *pszValue);
90 :
91 : // Replace this where used.
92 : static CPLErr NCDFGet1DVar(int nCdfId, int nVarId, char **pszValue);
93 : static CPLErr NCDFPut1DVar(int nCdfId, int nVarId, const char *pszValue);
94 :
95 : // Replace this where used.
96 : static char **NCDFTokenizeArray(const char *pszValue);
97 : static void CopyMetadata(GDALDataset *poSrcDS, GDALRasterBand *poSrcBand,
98 : GDALRasterBand *poDstBand, int fpImage, int CDFVarID,
99 : const char *pszMatchPrefix = nullptr);
100 :
101 : // NetCDF-4 groups helper functions.
102 : // They all work also for NetCDF-3 files which are considered as
103 : // NetCDF-4 file with only one group.
104 : static CPLErr NCDFOpenSubDataset(int nCdfId, const char *pszSubdatasetName,
105 : int *pnGroupId, int *pnVarId);
106 : static CPLErr NCDFGetVisibleDims(int nGroupId, int *pnDims, int **ppanDimIds);
107 : static CPLErr NCDFGetSubGroups(int nGroupId, int *pnSubGroups,
108 : int **ppanSubGroupIds);
109 : static CPLErr NCDFGetGroupFullName(int nGroupId, char **ppszFullName,
110 : bool bNC3Compat = true);
111 : static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId, char **ppszFullName,
112 : bool bNC3Compat = true);
113 : static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId);
114 :
115 : static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,
116 : char **ppszFullName,
117 : bool bMandatory = false);
118 : static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,
119 : const char *pszAtt, int *pnAtt,
120 : bool bMandatory = false);
121 : static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId, char ***ppapszVars);
122 :
123 : // Uncomment this for more debug output.
124 : // #define NCDF_DEBUG 1
125 :
126 : CPLMutex *hNCMutex = nullptr;
127 :
128 : // Workaround https://github.com/OSGeo/gdal/issues/6253
129 : // Having 2 netCDF handles on the same file doesn't work in a multi-threaded
130 : // way. Apparently having the same handle works better (this is OK since
131 : // we have a global mutex on the netCDF library)
132 : static std::map<std::string, int> goMapNameToNetCDFId;
133 : static std::map<int, std::pair<std::string, int>> goMapNetCDFIdToKeyAndCount;
134 :
135 665 : int GDAL_nc_open(const char *pszFilename, int nMode, int *pID)
136 : {
137 1330 : std::string osKey(pszFilename);
138 665 : osKey += "#####";
139 665 : osKey += std::to_string(nMode);
140 665 : auto oIter = goMapNameToNetCDFId.find(osKey);
141 665 : if (oIter == goMapNameToNetCDFId.end())
142 : {
143 616 : int ret = nc_open(pszFilename, nMode, pID);
144 616 : if (ret != NC_NOERR)
145 3 : return ret;
146 613 : goMapNameToNetCDFId[osKey] = *pID;
147 613 : goMapNetCDFIdToKeyAndCount[*pID] =
148 1226 : std::pair<std::string, int>(osKey, 1);
149 613 : return ret;
150 : }
151 : else
152 : {
153 49 : *pID = oIter->second;
154 49 : goMapNetCDFIdToKeyAndCount[oIter->second].second++;
155 49 : return NC_NOERR;
156 : }
157 : }
158 :
159 921 : int GDAL_nc_close(int cdfid)
160 : {
161 921 : int ret = NC_NOERR;
162 921 : auto oIter = goMapNetCDFIdToKeyAndCount.find(cdfid);
163 921 : if (oIter != goMapNetCDFIdToKeyAndCount.end())
164 : {
165 662 : if (--oIter->second.second == 0)
166 : {
167 613 : ret = nc_close(cdfid);
168 613 : goMapNameToNetCDFId.erase(oIter->second.first);
169 613 : goMapNetCDFIdToKeyAndCount.erase(oIter);
170 : }
171 : }
172 : else
173 : {
174 : // we can go here if file opened with nc_open_mem() or nc_create()
175 259 : ret = nc_close(cdfid);
176 : }
177 921 : return ret;
178 : }
179 :
180 : /************************************************************************/
181 : /* ==================================================================== */
182 : /* netCDFRasterBand */
183 : /* ==================================================================== */
184 : /************************************************************************/
185 :
186 : class netCDFRasterBand final : public GDALPamRasterBand
187 : {
188 : friend class netCDFDataset;
189 :
190 : nc_type nc_datatype;
191 : int cdfid;
192 : int nZId;
193 : int nZDim;
194 : int nLevel;
195 : int nBandXPos;
196 : int nBandYPos;
197 : int *panBandZPos;
198 : int *panBandZLev;
199 : bool m_bNoDataSet = false;
200 : double m_dfNoDataValue = 0;
201 : bool m_bNoDataSetAsInt64 = false;
202 : int64_t m_nNodataValueInt64 = 0;
203 : bool m_bNoDataSetAsUInt64 = false;
204 : uint64_t m_nNodataValueUInt64 = 0;
205 : bool bValidRangeValid = false;
206 : double adfValidRange[2]{0, 0};
207 : bool m_bHaveScale = false;
208 : bool m_bHaveOffset = false;
209 : double m_dfScale = 1;
210 : double m_dfOffset = 0;
211 : CPLString m_osUnitType{};
212 : bool bSignedData;
213 : bool bCheckLongitude;
214 : bool m_bCreateMetadataFromOtherVarsDone = false;
215 :
216 : void CreateMetadataFromAttributes();
217 : void CreateMetadataFromOtherVars();
218 :
219 : template <class T>
220 : void CheckData(void *pImage, void *pImageNC, size_t nTmpBlockXSize,
221 : size_t nTmpBlockYSize, bool bCheckIsNan = false);
222 : template <class T>
223 : void CheckDataCpx(void *pImage, void *pImageNC, size_t nTmpBlockXSize,
224 : size_t nTmpBlockYSize, bool bCheckIsNan = false);
225 : void SetBlockSize();
226 :
227 : bool FetchNetcdfChunk(size_t xstart, size_t ystart, void *pImage);
228 :
229 : void SetNoDataValueNoUpdate(double dfNoData);
230 : void SetNoDataValueNoUpdate(int64_t nNoData);
231 : void SetNoDataValueNoUpdate(uint64_t nNoData);
232 :
233 : void SetOffsetNoUpdate(double dfVal);
234 : void SetScaleNoUpdate(double dfVal);
235 : void SetUnitTypeNoUpdate(const char *pszNewValue);
236 :
237 : protected:
238 : CPLXMLNode *SerializeToXML(const char *pszUnused) override;
239 :
240 : public:
241 : struct CONSTRUCTOR_OPEN
242 : {
243 : };
244 :
245 : struct CONSTRUCTOR_CREATE
246 : {
247 : };
248 :
249 : netCDFRasterBand(const CONSTRUCTOR_OPEN &, netCDFDataset *poDS,
250 : int nGroupId, int nZId, int nZDim, int nLevel,
251 : const int *panBandZLen, const int *panBandPos, int nBand);
252 : netCDFRasterBand(const CONSTRUCTOR_CREATE &, netCDFDataset *poDS,
253 : GDALDataType eType, int nBand, bool bSigned = true,
254 : const char *pszBandName = nullptr,
255 : const char *pszLongName = nullptr, int nZId = -1,
256 : int nZDim = 2, int nLevel = 0,
257 : const int *panBandZLev = nullptr,
258 : const int *panBandZPos = nullptr,
259 : const int *paDimIds = nullptr);
260 : virtual ~netCDFRasterBand();
261 :
262 : virtual double GetNoDataValue(int *) override;
263 : virtual int64_t GetNoDataValueAsInt64(int *pbSuccess = nullptr) override;
264 : virtual uint64_t GetNoDataValueAsUInt64(int *pbSuccess = nullptr) override;
265 : virtual CPLErr SetNoDataValue(double) override;
266 : virtual CPLErr SetNoDataValueAsInt64(int64_t nNoData) override;
267 : virtual CPLErr SetNoDataValueAsUInt64(uint64_t nNoData) override;
268 : // virtual CPLErr DeleteNoDataValue();
269 : virtual double GetOffset(int *) override;
270 : virtual CPLErr SetOffset(double) override;
271 : virtual double GetScale(int *) override;
272 : virtual CPLErr SetScale(double) override;
273 : virtual const char *GetUnitType() override;
274 : virtual CPLErr SetUnitType(const char *) override;
275 : virtual CPLErr IReadBlock(int, int, void *) override;
276 : virtual CPLErr IWriteBlock(int, int, void *) override;
277 :
278 : char **GetMetadata(const char *pszDomain = "") override;
279 : const char *GetMetadataItem(const char *pszName,
280 : const char *pszDomain = "") override;
281 :
282 : virtual CPLErr SetMetadataItem(const char *pszName, const char *pszValue,
283 : const char *pszDomain = "") override;
284 : virtual CPLErr SetMetadata(char **papszMD,
285 : const char *pszDomain = "") override;
286 : };
287 :
288 : /************************************************************************/
289 : /* netCDFRasterBand() */
290 : /************************************************************************/
291 :
292 471 : netCDFRasterBand::netCDFRasterBand(const netCDFRasterBand::CONSTRUCTOR_OPEN &,
293 : netCDFDataset *poNCDFDS, int nGroupId,
294 : int nZIdIn, int nZDimIn, int nLevelIn,
295 : const int *panBandZLevIn,
296 471 : const int *panBandZPosIn, int nBandIn)
297 : : nc_datatype(NC_NAT), cdfid(nGroupId), nZId(nZIdIn), nZDim(nZDimIn),
298 471 : nLevel(nLevelIn), nBandXPos(panBandZPosIn[0]),
299 471 : nBandYPos(nZDim == 1 ? -1 : panBandZPosIn[1]), panBandZPos(nullptr),
300 : panBandZLev(nullptr),
301 : bSignedData(true), // Default signed, except for Byte.
302 942 : bCheckLongitude(false)
303 : {
304 471 : poDS = poNCDFDS;
305 471 : nBand = nBandIn;
306 :
307 : // Take care of all other dimensions.
308 471 : if (nZDim > 2)
309 : {
310 168 : panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
311 168 : panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
312 :
313 462 : for (int i = 0; i < nZDim - 2; i++)
314 : {
315 294 : panBandZPos[i] = panBandZPosIn[i + 2];
316 294 : panBandZLev[i] = panBandZLevIn[i];
317 : }
318 : }
319 :
320 471 : nRasterXSize = poDS->GetRasterXSize();
321 471 : nRasterYSize = poDS->GetRasterYSize();
322 471 : nBlockXSize = poDS->GetRasterXSize();
323 471 : nBlockYSize = 1;
324 :
325 : // Get the type of the "z" variable, our target raster array.
326 471 : if (nc_inq_var(cdfid, nZId, nullptr, &nc_datatype, nullptr, nullptr,
327 471 : nullptr) != NC_NOERR)
328 : {
329 0 : CPLError(CE_Failure, CPLE_AppDefined, "Error in nc_var_inq() on 'z'.");
330 0 : return;
331 : }
332 :
333 471 : if (NCDFIsUserDefinedType(cdfid, nc_datatype))
334 : {
335 : // First enquire and check that the number of fields is 2
336 : size_t nfields, compoundsize;
337 5 : if (nc_inq_compound(cdfid, nc_datatype, nullptr, &compoundsize,
338 5 : &nfields) != NC_NOERR)
339 : {
340 0 : CPLError(CE_Failure, CPLE_AppDefined,
341 : "Error in nc_inq_compound() on 'z'.");
342 0 : return;
343 : }
344 :
345 5 : if (nfields != 2)
346 : {
347 0 : CPLError(CE_Failure, CPLE_AppDefined,
348 : "Unsupported data type encountered in nc_inq_compound() "
349 : "on 'z'.");
350 0 : return;
351 : }
352 :
353 : // Now check that that two types are the same in the struct.
354 : nc_type field_type1, field_type2;
355 : int field_dims1, field_dims2;
356 5 : if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
357 : &field_type1, &field_dims1,
358 5 : nullptr) != NC_NOERR)
359 : {
360 0 : CPLError(
361 : CE_Failure, CPLE_AppDefined,
362 : "Error in querying Field 1 in nc_inq_compound_field() on 'z'.");
363 0 : return;
364 : }
365 :
366 5 : if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
367 : &field_type2, &field_dims2,
368 5 : nullptr) != NC_NOERR)
369 : {
370 0 : CPLError(
371 : CE_Failure, CPLE_AppDefined,
372 : "Error in querying Field 2 in nc_inq_compound_field() on 'z'.");
373 0 : return;
374 : }
375 :
376 5 : if ((field_type1 != field_type2) || (field_dims1 != field_dims2) ||
377 5 : (field_dims1 != 0))
378 : {
379 0 : CPLError(CE_Failure, CPLE_AppDefined,
380 : "Error in interpreting compound data type on 'z'.");
381 0 : return;
382 : }
383 :
384 5 : if (field_type1 == NC_SHORT)
385 0 : eDataType = GDT_CInt16;
386 5 : else if (field_type1 == NC_INT)
387 0 : eDataType = GDT_CInt32;
388 5 : else if (field_type1 == NC_FLOAT)
389 4 : eDataType = GDT_CFloat32;
390 1 : else if (field_type1 == NC_DOUBLE)
391 1 : eDataType = GDT_CFloat64;
392 : else
393 : {
394 0 : CPLError(CE_Failure, CPLE_AppDefined,
395 : "Unsupported netCDF compound data type encountered.");
396 0 : return;
397 : }
398 : }
399 : else
400 : {
401 466 : if (nc_datatype == NC_BYTE)
402 145 : eDataType = GDT_Byte;
403 321 : else if (nc_datatype == NC_CHAR)
404 0 : eDataType = GDT_Byte;
405 321 : else if (nc_datatype == NC_SHORT)
406 41 : eDataType = GDT_Int16;
407 280 : else if (nc_datatype == NC_INT)
408 89 : eDataType = GDT_Int32;
409 191 : else if (nc_datatype == NC_FLOAT)
410 115 : eDataType = GDT_Float32;
411 76 : else if (nc_datatype == NC_DOUBLE)
412 40 : eDataType = GDT_Float64;
413 36 : else if (nc_datatype == NC_UBYTE)
414 14 : eDataType = GDT_Byte;
415 22 : else if (nc_datatype == NC_USHORT)
416 4 : eDataType = GDT_UInt16;
417 18 : else if (nc_datatype == NC_UINT)
418 3 : eDataType = GDT_UInt32;
419 15 : else if (nc_datatype == NC_INT64)
420 8 : eDataType = GDT_Int64;
421 7 : else if (nc_datatype == NC_UINT64)
422 7 : eDataType = GDT_UInt64;
423 : else
424 : {
425 0 : if (nBand == 1)
426 0 : CPLError(CE_Warning, CPLE_AppDefined,
427 : "Unsupported netCDF datatype (%d), treat as Float32.",
428 0 : static_cast<int>(nc_datatype));
429 0 : eDataType = GDT_Float32;
430 0 : nc_datatype = NC_FLOAT;
431 : }
432 : }
433 :
434 : // Find and set No Data for this variable.
435 471 : nc_type atttype = NC_NAT;
436 471 : size_t attlen = 0;
437 471 : const char *pszNoValueName = nullptr;
438 :
439 : // Find attribute name, either _FillValue or missing_value.
440 471 : int status = nc_inq_att(cdfid, nZId, NCDF_FillValue, &atttype, &attlen);
441 471 : if (status == NC_NOERR)
442 : {
443 248 : pszNoValueName = NCDF_FillValue;
444 : }
445 : else
446 : {
447 223 : status = nc_inq_att(cdfid, nZId, "missing_value", &atttype, &attlen);
448 223 : if (status == NC_NOERR)
449 : {
450 12 : pszNoValueName = "missing_value";
451 : }
452 : }
453 :
454 : // Fetch missing value.
455 471 : double dfNoData = 0.0;
456 471 : bool bGotNoData = false;
457 471 : int64_t nNoDataAsInt64 = 0;
458 471 : bool bGotNoDataAsInt64 = false;
459 471 : uint64_t nNoDataAsUInt64 = 0;
460 471 : bool bGotNoDataAsUInt64 = false;
461 471 : if (status == NC_NOERR)
462 : {
463 260 : nc_type nAttrType = NC_NAT;
464 260 : size_t nAttrLen = 0;
465 260 : status = nc_inq_att(cdfid, nZId, pszNoValueName, &nAttrType, &nAttrLen);
466 260 : if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_INT64)
467 : {
468 : long long v;
469 7 : nc_get_att_longlong(cdfid, nZId, pszNoValueName, &v);
470 7 : bGotNoData = true;
471 7 : bGotNoDataAsInt64 = true;
472 7 : nNoDataAsInt64 = static_cast<int64_t>(v);
473 : }
474 253 : else if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_UINT64)
475 : {
476 : unsigned long long v;
477 7 : nc_get_att_ulonglong(cdfid, nZId, pszNoValueName, &v);
478 7 : bGotNoData = true;
479 7 : bGotNoDataAsUInt64 = true;
480 7 : nNoDataAsUInt64 = static_cast<uint64_t>(v);
481 : }
482 246 : else if (NCDFGetAttr(cdfid, nZId, pszNoValueName, &dfNoData) == CE_None)
483 : {
484 245 : bGotNoData = true;
485 : }
486 : }
487 :
488 : // If NoData was not found, use the default value, but for non-Byte types
489 : // as it is not recommended:
490 : // https://www.unidata.ucar.edu/software/netcdf/docs/attribute_conventions.html
491 471 : nc_type vartype = NC_NAT;
492 471 : if (!bGotNoData)
493 : {
494 212 : nc_inq_vartype(cdfid, nZId, &vartype);
495 212 : if (vartype == NC_INT64)
496 : {
497 : nNoDataAsInt64 =
498 1 : NCDFGetDefaultNoDataValueAsInt64(cdfid, nZId, bGotNoData);
499 1 : bGotNoDataAsInt64 = bGotNoData;
500 : }
501 211 : else if (vartype == NC_UINT64)
502 : {
503 : nNoDataAsUInt64 =
504 0 : NCDFGetDefaultNoDataValueAsUInt64(cdfid, nZId, bGotNoData);
505 0 : bGotNoDataAsUInt64 = bGotNoData;
506 : }
507 211 : else if (vartype != NC_CHAR && vartype != NC_BYTE &&
508 89 : vartype != NC_UBYTE)
509 : {
510 81 : dfNoData =
511 81 : NCDFGetDefaultNoDataValue(cdfid, nZId, vartype, bGotNoData);
512 81 : if (bGotNoData)
513 : {
514 70 : CPLDebug("GDAL_netCDF",
515 : "did not get nodata value for variable #%d, using "
516 : "default %f",
517 : nZId, dfNoData);
518 : }
519 : }
520 : }
521 :
522 471 : bool bHasUnderscoreUnsignedAttr = false;
523 471 : bool bUnderscoreUnsignedAttrVal = false;
524 : {
525 471 : char *pszTemp = nullptr;
526 471 : if (NCDFGetAttr(cdfid, nZId, "_Unsigned", &pszTemp) == CE_None)
527 : {
528 137 : if (EQUAL(pszTemp, "true"))
529 : {
530 129 : bHasUnderscoreUnsignedAttr = true;
531 129 : bUnderscoreUnsignedAttrVal = true;
532 : }
533 8 : else if (EQUAL(pszTemp, "false"))
534 : {
535 8 : bHasUnderscoreUnsignedAttr = true;
536 8 : bUnderscoreUnsignedAttrVal = false;
537 : }
538 137 : CPLFree(pszTemp);
539 : }
540 : }
541 :
542 : // Look for valid_range or valid_min/valid_max.
543 :
544 : // First look for valid_range.
545 471 : if (CPLFetchBool(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE", true))
546 : {
547 469 : char *pszValidRange = nullptr;
548 469 : if (NCDFGetAttr(cdfid, nZId, "valid_range", &pszValidRange) ==
549 130 : CE_None &&
550 599 : pszValidRange[0] == '{' &&
551 130 : pszValidRange[strlen(pszValidRange) - 1] == '}')
552 : {
553 : const std::string osValidRange =
554 390 : std::string(pszValidRange).substr(1, strlen(pszValidRange) - 2);
555 : const CPLStringList aosValidRange(
556 260 : CSLTokenizeString2(osValidRange.c_str(), ",", 0));
557 130 : if (aosValidRange.size() == 2 &&
558 260 : CPLGetValueType(aosValidRange[0]) != CPL_VALUE_STRING &&
559 130 : CPLGetValueType(aosValidRange[1]) != CPL_VALUE_STRING)
560 : {
561 130 : bValidRangeValid = true;
562 130 : adfValidRange[0] = CPLAtof(aosValidRange[0]);
563 130 : adfValidRange[1] = CPLAtof(aosValidRange[1]);
564 : }
565 : }
566 469 : CPLFree(pszValidRange);
567 :
568 : // If not found look for valid_min and valid_max.
569 469 : if (!bValidRangeValid)
570 : {
571 339 : double dfMin = 0;
572 339 : double dfMax = 0;
573 354 : if (NCDFGetAttr(cdfid, nZId, "valid_min", &dfMin) == CE_None &&
574 15 : NCDFGetAttr(cdfid, nZId, "valid_max", &dfMax) == CE_None)
575 : {
576 8 : adfValidRange[0] = dfMin;
577 8 : adfValidRange[1] = dfMax;
578 8 : bValidRangeValid = true;
579 : }
580 : }
581 :
582 469 : if (bValidRangeValid &&
583 138 : (adfValidRange[0] < 0 || adfValidRange[1] < 0) &&
584 17 : nc_datatype == NC_SHORT && bHasUnderscoreUnsignedAttr &&
585 : bUnderscoreUnsignedAttrVal)
586 : {
587 2 : if (adfValidRange[0] < 0)
588 0 : adfValidRange[0] += 65536;
589 2 : if (adfValidRange[1] < 0)
590 2 : adfValidRange[1] += 65536;
591 2 : if (adfValidRange[0] <= adfValidRange[1])
592 : {
593 : // Updating metadata item
594 2 : GDALPamRasterBand::SetMetadataItem(
595 : "valid_range",
596 2 : CPLSPrintf("{%d,%d}", static_cast<int>(adfValidRange[0]),
597 2 : static_cast<int>(adfValidRange[1])));
598 : }
599 : }
600 :
601 469 : if (bValidRangeValid && adfValidRange[0] > adfValidRange[1])
602 : {
603 0 : CPLError(CE_Warning, CPLE_AppDefined,
604 : "netCDFDataset::valid_range: min > max:\n"
605 : " min: %lf\n max: %lf\n",
606 : adfValidRange[0], adfValidRange[1]);
607 0 : bValidRangeValid = false;
608 0 : adfValidRange[0] = 0.0;
609 0 : adfValidRange[1] = 0.0;
610 : }
611 : }
612 :
613 : // Special For Byte Bands: check for signed/unsigned byte.
614 471 : if (nc_datatype == NC_BYTE)
615 : {
616 : // netcdf uses signed byte by default, but GDAL uses unsigned by default
617 : // This may cause unexpected results, but is needed for back-compat.
618 145 : if (poNCDFDS->bIsGdalFile)
619 124 : bSignedData = false;
620 : else
621 21 : bSignedData = true;
622 :
623 : // For NC4 format NC_BYTE is (normally) signed, NC_UBYTE is unsigned.
624 : // But in case a NC3 file was converted automatically and has hints
625 : // that it is unsigned, take them into account
626 145 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
627 : {
628 3 : bSignedData = true;
629 : }
630 :
631 : // If we got valid_range, test for signed/unsigned range.
632 : // https://docs.unidata.ucar.edu/netcdf-c/current/attribute_conventions.html
633 145 : if (bValidRangeValid)
634 : {
635 : // If we got valid_range={0,255}, treat as unsigned.
636 126 : if (adfValidRange[0] == 0 && adfValidRange[1] == 255)
637 : {
638 118 : bSignedData = false;
639 : // Reset valid_range.
640 118 : bValidRangeValid = false;
641 : }
642 : // If we got valid_range={-128,127}, treat as signed.
643 8 : else if (adfValidRange[0] == -128 && adfValidRange[1] == 127)
644 : {
645 8 : bSignedData = true;
646 : // Reset valid_range.
647 8 : bValidRangeValid = false;
648 : }
649 : }
650 : // Else test for _Unsigned.
651 : // https://docs.unidata.ucar.edu/nug/current/best_practices.html
652 : else
653 : {
654 19 : if (bHasUnderscoreUnsignedAttr)
655 7 : bSignedData = !bUnderscoreUnsignedAttrVal;
656 : }
657 :
658 145 : if (bSignedData)
659 : {
660 20 : eDataType = GDT_Int8;
661 : }
662 125 : else if (dfNoData < 0)
663 : {
664 : // Fix nodata value as it was stored signed.
665 6 : dfNoData += 256;
666 6 : if (pszNoValueName)
667 : {
668 : // Updating metadata item
669 6 : GDALPamRasterBand::SetMetadataItem(
670 : pszNoValueName,
671 : CPLSPrintf("%d", static_cast<int>(dfNoData)));
672 : }
673 : }
674 : }
675 326 : else if (nc_datatype == NC_SHORT)
676 : {
677 41 : if (bHasUnderscoreUnsignedAttr)
678 : {
679 4 : bSignedData = !bUnderscoreUnsignedAttrVal;
680 4 : if (!bSignedData)
681 4 : eDataType = GDT_UInt16;
682 : }
683 :
684 : // Fix nodata value as it was stored signed.
685 41 : if (!bSignedData && dfNoData < 0)
686 : {
687 4 : dfNoData += 65536;
688 4 : if (pszNoValueName)
689 : {
690 : // Updating metadata item
691 4 : GDALPamRasterBand::SetMetadataItem(
692 : pszNoValueName,
693 : CPLSPrintf("%d", static_cast<int>(dfNoData)));
694 : }
695 : }
696 : }
697 :
698 285 : else if (nc_datatype == NC_UBYTE || nc_datatype == NC_USHORT ||
699 267 : nc_datatype == NC_UINT || nc_datatype == NC_UINT64)
700 : {
701 28 : bSignedData = false;
702 : }
703 :
704 471 : CPLDebug("GDAL_netCDF", "netcdf type=%d gdal type=%d signedByte=%d",
705 471 : nc_datatype, eDataType, static_cast<int>(bSignedData));
706 :
707 471 : if (bGotNoData)
708 : {
709 : // Set nodata value.
710 330 : if (bGotNoDataAsInt64)
711 : {
712 8 : if (eDataType == GDT_Int64)
713 : {
714 8 : SetNoDataValueNoUpdate(nNoDataAsInt64);
715 : }
716 0 : else if (eDataType == GDT_UInt64 && nNoDataAsInt64 >= 0)
717 : {
718 0 : SetNoDataValueNoUpdate(static_cast<uint64_t>(nNoDataAsInt64));
719 : }
720 : else
721 : {
722 0 : SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsInt64));
723 : }
724 : }
725 322 : else if (bGotNoDataAsUInt64)
726 : {
727 7 : if (eDataType == GDT_UInt64)
728 : {
729 7 : SetNoDataValueNoUpdate(nNoDataAsUInt64);
730 : }
731 0 : else if (eDataType == GDT_Int64 &&
732 : nNoDataAsUInt64 <=
733 0 : static_cast<uint64_t>(
734 0 : std::numeric_limits<int64_t>::max()))
735 : {
736 0 : SetNoDataValueNoUpdate(static_cast<int64_t>(nNoDataAsUInt64));
737 : }
738 : else
739 : {
740 0 : SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsUInt64));
741 : }
742 : }
743 : else
744 : {
745 : #ifdef NCDF_DEBUG
746 : CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) read", dfNoData);
747 : #endif
748 315 : if (eDataType == GDT_Int64 && GDALIsValueExactAs<int64_t>(dfNoData))
749 : {
750 0 : SetNoDataValueNoUpdate(static_cast<int64_t>(dfNoData));
751 : }
752 315 : else if (eDataType == GDT_UInt64 &&
753 0 : GDALIsValueExactAs<uint64_t>(dfNoData))
754 : {
755 0 : SetNoDataValueNoUpdate(static_cast<uint64_t>(dfNoData));
756 : }
757 : else
758 : {
759 315 : SetNoDataValueNoUpdate(dfNoData);
760 : }
761 : }
762 : }
763 :
764 471 : CreateMetadataFromAttributes();
765 :
766 : // Attempt to fetch the scale_factor and add_offset attributes for the
767 : // variable and set them. If these values are not available, set
768 : // offset to 0 and scale to 1.
769 471 : if (nc_inq_attid(cdfid, nZId, CF_ADD_OFFSET, nullptr) == NC_NOERR)
770 : {
771 16 : double dfOffset = 0;
772 16 : status = nc_get_att_double(cdfid, nZId, CF_ADD_OFFSET, &dfOffset);
773 16 : CPLDebug("GDAL_netCDF", "got add_offset=%.16g, status=%d", dfOffset,
774 : status);
775 16 : SetOffsetNoUpdate(dfOffset);
776 : }
777 :
778 471 : bool bHasScale = false;
779 471 : if (nc_inq_attid(cdfid, nZId, CF_SCALE_FACTOR, nullptr) == NC_NOERR)
780 : {
781 20 : bHasScale = true;
782 20 : double dfScale = 1;
783 20 : status = nc_get_att_double(cdfid, nZId, CF_SCALE_FACTOR, &dfScale);
784 20 : CPLDebug("GDAL_netCDF", "got scale_factor=%.16g, status=%d", dfScale,
785 : status);
786 20 : SetScaleNoUpdate(dfScale);
787 : }
788 :
789 12 : if (bValidRangeValid && GDALDataTypeIsInteger(eDataType) &&
790 4 : eDataType != GDT_Int64 && eDataType != GDT_UInt64 &&
791 4 : (std::fabs(std::round(adfValidRange[0]) - adfValidRange[0]) > 1e-5 ||
792 483 : std::fabs(std::round(adfValidRange[1]) - adfValidRange[1]) > 1e-5) &&
793 1 : CSLFetchNameValue(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE") ==
794 : nullptr)
795 : {
796 1 : CPLError(CE_Warning, CPLE_AppDefined,
797 : "validity range = %f, %f contains floating-point values, "
798 : "whereas data type is integer. valid_range is thus likely "
799 : "wrong%s. Ignoring it.",
800 : adfValidRange[0], adfValidRange[1],
801 : bHasScale ? " (likely scaled using scale_factor/add_factor "
802 : "whereas it should be using the packed data type)"
803 : : "");
804 1 : bValidRangeValid = false;
805 1 : adfValidRange[0] = 0.0;
806 1 : adfValidRange[1] = 0.0;
807 : }
808 :
809 : // Should we check for longitude values > 360?
810 471 : bCheckLongitude =
811 942 : CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")) &&
812 471 : NCDFIsVarLongitude(cdfid, nZId, nullptr);
813 :
814 : // Attempt to fetch the units attribute for the variable and set it.
815 471 : SetUnitTypeNoUpdate(netCDFRasterBand::GetMetadataItem(CF_UNITS));
816 :
817 471 : SetBlockSize();
818 : }
819 :
820 653 : void netCDFRasterBand::SetBlockSize()
821 : {
822 : // Check for variable chunking (netcdf-4 only).
823 : // GDAL block size should be set to hdf5 chunk size.
824 653 : int nTmpFormat = 0;
825 653 : int status = nc_inq_format(cdfid, &nTmpFormat);
826 653 : NetCDFFormatEnum eTmpFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
827 653 : if ((status == NC_NOERR) &&
828 561 : (eTmpFormat == NCDF_FORMAT_NC4 || eTmpFormat == NCDF_FORMAT_NC4C))
829 : {
830 108 : size_t chunksize[MAX_NC_DIMS] = {};
831 : // Check for chunksize and set it as the blocksize (optimizes read).
832 108 : status = nc_inq_var_chunking(cdfid, nZId, &nTmpFormat, chunksize);
833 108 : if ((status == NC_NOERR) && (nTmpFormat == NC_CHUNKED))
834 : {
835 13 : nBlockXSize = (int)chunksize[nZDim - 1];
836 13 : if (nZDim >= 2)
837 13 : nBlockYSize = (int)chunksize[nZDim - 2];
838 : else
839 0 : nBlockYSize = 1;
840 : }
841 : }
842 :
843 : // Deal with bottom-up datasets and nBlockYSize != 1.
844 653 : auto poGDS = static_cast<netCDFDataset *>(poDS);
845 653 : if (poGDS->bBottomUp && nBlockYSize != 1 && poGDS->poChunkCache == nullptr)
846 : {
847 5 : if (poGDS->eAccess == GA_ReadOnly)
848 : {
849 : // Try to cache 1 or 2 'rows' of netCDF chunks along the whole
850 : // width of the raster
851 5 : size_t nChunks =
852 5 : static_cast<size_t>(DIV_ROUND_UP(nRasterXSize, nBlockXSize));
853 5 : if ((nRasterYSize % nBlockYSize) != 0)
854 1 : nChunks *= 2;
855 : const size_t nChunkSize =
856 5 : static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
857 5 : nBlockXSize * nBlockYSize;
858 5 : constexpr size_t MAX_CACHE_SIZE = 100 * 1024 * 1024;
859 5 : nChunks = std::min(nChunks, MAX_CACHE_SIZE / nChunkSize);
860 5 : if (nChunks)
861 : {
862 5 : poGDS->poChunkCache.reset(
863 5 : new netCDFDataset::ChunkCacheType(nChunks));
864 : }
865 : }
866 : else
867 : {
868 0 : nBlockYSize = 1;
869 : }
870 : }
871 653 : }
872 :
873 : // Constructor in create mode.
874 : // If nZId and following variables are not passed, the band will have 2
875 : // dimensions.
876 : // TODO: Get metadata, missing val from band #1 if nZDim > 2.
877 182 : netCDFRasterBand::netCDFRasterBand(
878 : const netCDFRasterBand::CONSTRUCTOR_CREATE &, netCDFDataset *poNCDFDS,
879 : const GDALDataType eTypeIn, int nBandIn, bool bSigned,
880 : const char *pszBandName, const char *pszLongName, int nZIdIn, int nZDimIn,
881 : int nLevelIn, const int *panBandZLevIn, const int *panBandZPosIn,
882 182 : const int *paDimIds)
883 182 : : nc_datatype(NC_NAT), cdfid(poNCDFDS->GetCDFID()), nZId(nZIdIn),
884 : nZDim(nZDimIn), nLevel(nLevelIn), nBandXPos(1), nBandYPos(0),
885 : panBandZPos(nullptr), panBandZLev(nullptr), bSignedData(bSigned),
886 182 : bCheckLongitude(false), m_bCreateMetadataFromOtherVarsDone(true)
887 : {
888 182 : poDS = poNCDFDS;
889 182 : nBand = nBandIn;
890 :
891 182 : nRasterXSize = poDS->GetRasterXSize();
892 182 : nRasterYSize = poDS->GetRasterYSize();
893 182 : nBlockXSize = poDS->GetRasterXSize();
894 182 : nBlockYSize = 1;
895 :
896 182 : if (poDS->GetAccess() != GA_Update)
897 : {
898 0 : CPLError(CE_Failure, CPLE_NotSupported,
899 : "Dataset is not in update mode, "
900 : "wrong netCDFRasterBand constructor");
901 0 : return;
902 : }
903 :
904 : // Take care of all other dimensions.
905 182 : if (nZDim > 2 && paDimIds != nullptr)
906 : {
907 27 : nBandXPos = panBandZPosIn[0];
908 27 : nBandYPos = panBandZPosIn[1];
909 :
910 27 : panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
911 27 : panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
912 :
913 76 : for (int i = 0; i < nZDim - 2; i++)
914 : {
915 49 : panBandZPos[i] = panBandZPosIn[i + 2];
916 49 : panBandZLev[i] = panBandZLevIn[i];
917 : }
918 : }
919 :
920 : // Get the type of the "z" variable, our target raster array.
921 182 : eDataType = eTypeIn;
922 :
923 182 : switch (eDataType)
924 : {
925 77 : case GDT_Byte:
926 77 : nc_datatype = NC_BYTE;
927 : // NC_UBYTE (unsigned byte) is only available for NC4.
928 77 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
929 3 : nc_datatype = NC_UBYTE;
930 77 : break;
931 7 : case GDT_Int8:
932 7 : nc_datatype = NC_BYTE;
933 7 : break;
934 11 : case GDT_Int16:
935 11 : nc_datatype = NC_SHORT;
936 11 : break;
937 24 : case GDT_Int32:
938 24 : nc_datatype = NC_INT;
939 24 : break;
940 13 : case GDT_Float32:
941 13 : nc_datatype = NC_FLOAT;
942 13 : break;
943 8 : case GDT_Float64:
944 8 : nc_datatype = NC_DOUBLE;
945 8 : break;
946 7 : case GDT_Int64:
947 7 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
948 : {
949 7 : nc_datatype = NC_INT64;
950 : }
951 : else
952 : {
953 0 : if (nBand == 1)
954 0 : CPLError(
955 : CE_Warning, CPLE_AppDefined,
956 : "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
957 : "Int64");
958 0 : nc_datatype = NC_DOUBLE;
959 0 : eDataType = GDT_Float64;
960 : }
961 7 : break;
962 7 : case GDT_UInt64:
963 7 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
964 : {
965 7 : nc_datatype = NC_UINT64;
966 : }
967 : else
968 : {
969 0 : if (nBand == 1)
970 0 : CPLError(
971 : CE_Warning, CPLE_AppDefined,
972 : "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
973 : "UInt64");
974 0 : nc_datatype = NC_DOUBLE;
975 0 : eDataType = GDT_Float64;
976 : }
977 7 : break;
978 6 : case GDT_UInt16:
979 6 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
980 : {
981 6 : nc_datatype = NC_USHORT;
982 6 : break;
983 : }
984 : [[fallthrough]];
985 : case GDT_UInt32:
986 6 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
987 : {
988 6 : nc_datatype = NC_UINT;
989 6 : break;
990 : }
991 : [[fallthrough]];
992 : default:
993 16 : if (nBand == 1)
994 8 : CPLError(CE_Warning, CPLE_AppDefined,
995 : "Unsupported GDAL datatype (%d), treat as NC_FLOAT.",
996 8 : static_cast<int>(eDataType));
997 16 : nc_datatype = NC_FLOAT;
998 16 : eDataType = GDT_Float32;
999 16 : break;
1000 : }
1001 :
1002 : // Define the variable if necessary (if nZId == -1).
1003 182 : bool bDefineVar = false;
1004 :
1005 182 : if (nZId == -1)
1006 : {
1007 160 : bDefineVar = true;
1008 :
1009 : // Make sure we are in define mode.
1010 160 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1011 :
1012 : char szTempPrivate[256 + 1];
1013 160 : const char *pszTemp = nullptr;
1014 160 : if (!pszBandName || EQUAL(pszBandName, ""))
1015 : {
1016 138 : snprintf(szTempPrivate, sizeof(szTempPrivate), "Band%d", nBand);
1017 138 : pszTemp = szTempPrivate;
1018 : }
1019 : else
1020 : {
1021 22 : pszTemp = pszBandName;
1022 : }
1023 :
1024 : int status;
1025 160 : if (nZDim > 2 && paDimIds != nullptr)
1026 : {
1027 5 : status =
1028 5 : nc_def_var(cdfid, pszTemp, nc_datatype, nZDim, paDimIds, &nZId);
1029 : }
1030 : else
1031 : {
1032 155 : int anBandDims[2] = {poNCDFDS->nYDimID, poNCDFDS->nXDimID};
1033 : status =
1034 155 : nc_def_var(cdfid, pszTemp, nc_datatype, 2, anBandDims, &nZId);
1035 : }
1036 160 : NCDF_ERR(status);
1037 160 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d) id=%d", cdfid, pszTemp,
1038 : nc_datatype, nZId);
1039 :
1040 160 : if (!pszLongName || EQUAL(pszLongName, ""))
1041 : {
1042 153 : snprintf(szTempPrivate, sizeof(szTempPrivate),
1043 : "GDAL Band Number %d", nBand);
1044 153 : pszTemp = szTempPrivate;
1045 : }
1046 : else
1047 : {
1048 7 : pszTemp = pszLongName;
1049 : }
1050 : status =
1051 160 : nc_put_att_text(cdfid, nZId, CF_LNG_NAME, strlen(pszTemp), pszTemp);
1052 160 : NCDF_ERR(status);
1053 :
1054 160 : poNCDFDS->DefVarDeflate(nZId, true);
1055 : }
1056 :
1057 : // For Byte data add signed/unsigned info.
1058 182 : if (eDataType == GDT_Byte || eDataType == GDT_Int8)
1059 : {
1060 84 : if (bDefineVar)
1061 : {
1062 : // Only add attributes if creating variable.
1063 : // For unsigned NC_BYTE (except NC4 format),
1064 : // add valid_range and _Unsigned ( defined in CF-1 and NUG ).
1065 76 : if (nc_datatype == NC_BYTE && poNCDFDS->eFormat != NCDF_FORMAT_NC4)
1066 : {
1067 73 : CPLDebug("GDAL_netCDF",
1068 : "adding valid_range attributes for Byte Band");
1069 73 : short l_adfValidRange[2] = {0, 0};
1070 : int status;
1071 73 : if (bSignedData || eDataType == GDT_Int8)
1072 : {
1073 7 : l_adfValidRange[0] = -128;
1074 7 : l_adfValidRange[1] = 127;
1075 7 : status =
1076 7 : nc_put_att_text(cdfid, nZId, "_Unsigned", 5, "false");
1077 : }
1078 : else
1079 : {
1080 66 : l_adfValidRange[0] = 0;
1081 66 : l_adfValidRange[1] = 255;
1082 : status =
1083 66 : nc_put_att_text(cdfid, nZId, "_Unsigned", 4, "true");
1084 : }
1085 73 : NCDF_ERR(status);
1086 73 : status = nc_put_att_short(cdfid, nZId, "valid_range", NC_SHORT,
1087 : 2, l_adfValidRange);
1088 73 : NCDF_ERR(status);
1089 : }
1090 : }
1091 : }
1092 :
1093 182 : if (nc_datatype != NC_BYTE && nc_datatype != NC_CHAR &&
1094 101 : nc_datatype != NC_UBYTE)
1095 : {
1096 : // Set default nodata.
1097 98 : bool bIgnored = false;
1098 : double dfNoData =
1099 98 : NCDFGetDefaultNoDataValue(cdfid, nZId, nc_datatype, bIgnored);
1100 : #ifdef NCDF_DEBUG
1101 : CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) default", dfNoData);
1102 : #endif
1103 98 : netCDFRasterBand::SetNoDataValue(dfNoData);
1104 : }
1105 :
1106 182 : SetBlockSize();
1107 : }
1108 :
1109 : /************************************************************************/
1110 : /* ~netCDFRasterBand() */
1111 : /************************************************************************/
1112 :
1113 1306 : netCDFRasterBand::~netCDFRasterBand()
1114 : {
1115 653 : netCDFRasterBand::FlushCache(true);
1116 653 : CPLFree(panBandZPos);
1117 653 : CPLFree(panBandZLev);
1118 1306 : }
1119 :
1120 : /************************************************************************/
1121 : /* GetMetadata() */
1122 : /************************************************************************/
1123 :
1124 50 : char **netCDFRasterBand::GetMetadata(const char *pszDomain)
1125 : {
1126 50 : if (!m_bCreateMetadataFromOtherVarsDone)
1127 48 : CreateMetadataFromOtherVars();
1128 50 : return GDALPamRasterBand::GetMetadata(pszDomain);
1129 : }
1130 :
1131 : /************************************************************************/
1132 : /* GetMetadataItem() */
1133 : /************************************************************************/
1134 :
1135 545 : const char *netCDFRasterBand::GetMetadataItem(const char *pszName,
1136 : const char *pszDomain)
1137 : {
1138 545 : if (!m_bCreateMetadataFromOtherVarsDone &&
1139 529 : STARTS_WITH(pszName, "NETCDF_DIM_") &&
1140 1 : (!pszDomain || pszDomain[0] == 0))
1141 1 : CreateMetadataFromOtherVars();
1142 545 : return GDALPamRasterBand::GetMetadataItem(pszName, pszDomain);
1143 : }
1144 :
1145 : /************************************************************************/
1146 : /* SetMetadataItem() */
1147 : /************************************************************************/
1148 :
1149 7 : CPLErr netCDFRasterBand::SetMetadataItem(const char *pszName,
1150 : const char *pszValue,
1151 : const char *pszDomain)
1152 : {
1153 9 : if (GetAccess() == GA_Update &&
1154 9 : (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)
1155 : {
1156 : // Same logic as in CopyMetadata()
1157 :
1158 2 : const char *const papszIgnoreBand[] = {
1159 : CF_ADD_OFFSET, CF_SCALE_FACTOR, "valid_range", "_Unsigned",
1160 : NCDF_FillValue, "coordinates", nullptr};
1161 : // Do not copy varname, stats, NETCDF_DIM_*, nodata
1162 : // and items in papszIgnoreBand.
1163 6 : if (STARTS_WITH(pszName, "NETCDF_VARNAME") ||
1164 2 : STARTS_WITH(pszName, "STATISTICS_") ||
1165 2 : STARTS_WITH(pszName, "NETCDF_DIM_") ||
1166 2 : STARTS_WITH(pszName, "missing_value") ||
1167 6 : STARTS_WITH(pszName, "_FillValue") ||
1168 2 : CSLFindString(papszIgnoreBand, pszName) != -1)
1169 : {
1170 : // do nothing
1171 : }
1172 : else
1173 : {
1174 2 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1175 :
1176 2 : if (!NCDFPutAttr(cdfid, nZId, pszName, pszValue))
1177 2 : return CE_Failure;
1178 : }
1179 : }
1180 :
1181 5 : return GDALPamRasterBand::SetMetadataItem(pszName, pszValue, pszDomain);
1182 : }
1183 :
1184 : /************************************************************************/
1185 : /* SetMetadata() */
1186 : /************************************************************************/
1187 :
1188 2 : CPLErr netCDFRasterBand::SetMetadata(char **papszMD, const char *pszDomain)
1189 : {
1190 4 : if (GetAccess() == GA_Update &&
1191 2 : (pszDomain == nullptr || pszDomain[0] == '\0'))
1192 : {
1193 : // We don't handle metadata item removal for now
1194 4 : for (const char *const *papszIter = papszMD; papszIter && *papszIter;
1195 : ++papszIter)
1196 : {
1197 2 : char *pszName = nullptr;
1198 2 : const char *pszValue = CPLParseNameValue(*papszIter, &pszName);
1199 2 : if (pszName && pszValue)
1200 2 : SetMetadataItem(pszName, pszValue);
1201 2 : CPLFree(pszName);
1202 : }
1203 : }
1204 2 : return GDALPamRasterBand::SetMetadata(papszMD, pszDomain);
1205 : }
1206 :
1207 : /************************************************************************/
1208 : /* GetOffset() */
1209 : /************************************************************************/
1210 50 : double netCDFRasterBand::GetOffset(int *pbSuccess)
1211 : {
1212 50 : if (pbSuccess != nullptr)
1213 45 : *pbSuccess = static_cast<int>(m_bHaveOffset);
1214 :
1215 50 : return m_dfOffset;
1216 : }
1217 :
1218 : /************************************************************************/
1219 : /* SetOffset() */
1220 : /************************************************************************/
1221 1 : CPLErr netCDFRasterBand::SetOffset(double dfNewOffset)
1222 : {
1223 2 : CPLMutexHolderD(&hNCMutex);
1224 :
1225 : // Write value if in update mode.
1226 1 : if (poDS->GetAccess() == GA_Update)
1227 : {
1228 : // Make sure we are in define mode.
1229 1 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1230 :
1231 1 : const int status = nc_put_att_double(cdfid, nZId, CF_ADD_OFFSET,
1232 : NC_DOUBLE, 1, &dfNewOffset);
1233 :
1234 1 : NCDF_ERR(status);
1235 1 : if (status == NC_NOERR)
1236 : {
1237 1 : SetOffsetNoUpdate(dfNewOffset);
1238 1 : return CE_None;
1239 : }
1240 :
1241 0 : return CE_Failure;
1242 : }
1243 :
1244 0 : SetOffsetNoUpdate(dfNewOffset);
1245 0 : return CE_None;
1246 : }
1247 :
1248 : /************************************************************************/
1249 : /* SetOffsetNoUpdate() */
1250 : /************************************************************************/
1251 17 : void netCDFRasterBand::SetOffsetNoUpdate(double dfVal)
1252 : {
1253 17 : m_dfOffset = dfVal;
1254 17 : m_bHaveOffset = true;
1255 17 : }
1256 :
1257 : /************************************************************************/
1258 : /* GetScale() */
1259 : /************************************************************************/
1260 50 : double netCDFRasterBand::GetScale(int *pbSuccess)
1261 : {
1262 50 : if (pbSuccess != nullptr)
1263 45 : *pbSuccess = static_cast<int>(m_bHaveScale);
1264 :
1265 50 : return m_dfScale;
1266 : }
1267 :
1268 : /************************************************************************/
1269 : /* SetScale() */
1270 : /************************************************************************/
1271 1 : CPLErr netCDFRasterBand::SetScale(double dfNewScale)
1272 : {
1273 2 : CPLMutexHolderD(&hNCMutex);
1274 :
1275 : // Write value if in update mode.
1276 1 : if (poDS->GetAccess() == GA_Update)
1277 : {
1278 : // Make sure we are in define mode.
1279 1 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1280 :
1281 1 : const int status = nc_put_att_double(cdfid, nZId, CF_SCALE_FACTOR,
1282 : NC_DOUBLE, 1, &dfNewScale);
1283 :
1284 1 : NCDF_ERR(status);
1285 1 : if (status == NC_NOERR)
1286 : {
1287 1 : SetScaleNoUpdate(dfNewScale);
1288 1 : return CE_None;
1289 : }
1290 :
1291 0 : return CE_Failure;
1292 : }
1293 :
1294 0 : SetScaleNoUpdate(dfNewScale);
1295 0 : return CE_None;
1296 : }
1297 :
1298 : /************************************************************************/
1299 : /* SetScaleNoUpdate() */
1300 : /************************************************************************/
1301 21 : void netCDFRasterBand::SetScaleNoUpdate(double dfVal)
1302 : {
1303 21 : m_dfScale = dfVal;
1304 21 : m_bHaveScale = true;
1305 21 : }
1306 :
1307 : /************************************************************************/
1308 : /* GetUnitType() */
1309 : /************************************************************************/
1310 :
1311 22 : const char *netCDFRasterBand::GetUnitType()
1312 :
1313 : {
1314 22 : if (!m_osUnitType.empty())
1315 6 : return m_osUnitType;
1316 :
1317 16 : return GDALRasterBand::GetUnitType();
1318 : }
1319 :
1320 : /************************************************************************/
1321 : /* SetUnitType() */
1322 : /************************************************************************/
1323 :
1324 1 : CPLErr netCDFRasterBand::SetUnitType(const char *pszNewValue)
1325 :
1326 : {
1327 2 : CPLMutexHolderD(&hNCMutex);
1328 :
1329 2 : const std::string osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
1330 :
1331 1 : if (!osUnitType.empty())
1332 : {
1333 : // Write value if in update mode.
1334 1 : if (poDS->GetAccess() == GA_Update)
1335 : {
1336 : // Make sure we are in define mode.
1337 1 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(TRUE);
1338 :
1339 1 : const int status = nc_put_att_text(
1340 : cdfid, nZId, CF_UNITS, osUnitType.size(), osUnitType.c_str());
1341 :
1342 1 : NCDF_ERR(status);
1343 1 : if (status == NC_NOERR)
1344 : {
1345 1 : SetUnitTypeNoUpdate(pszNewValue);
1346 1 : return CE_None;
1347 : }
1348 :
1349 0 : return CE_Failure;
1350 : }
1351 : }
1352 :
1353 0 : SetUnitTypeNoUpdate(pszNewValue);
1354 :
1355 0 : return CE_None;
1356 : }
1357 :
1358 : /************************************************************************/
1359 : /* SetUnitTypeNoUpdate() */
1360 : /************************************************************************/
1361 :
1362 472 : void netCDFRasterBand::SetUnitTypeNoUpdate(const char *pszNewValue)
1363 : {
1364 472 : m_osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
1365 472 : }
1366 :
1367 : /************************************************************************/
1368 : /* GetNoDataValue() */
1369 : /************************************************************************/
1370 :
1371 155 : double netCDFRasterBand::GetNoDataValue(int *pbSuccess)
1372 :
1373 : {
1374 155 : if (m_bNoDataSetAsInt64)
1375 : {
1376 0 : if (pbSuccess)
1377 0 : *pbSuccess = TRUE;
1378 0 : return GDALGetNoDataValueCastToDouble(m_nNodataValueInt64);
1379 : }
1380 :
1381 155 : if (m_bNoDataSetAsUInt64)
1382 : {
1383 0 : if (pbSuccess)
1384 0 : *pbSuccess = TRUE;
1385 0 : return GDALGetNoDataValueCastToDouble(m_nNodataValueUInt64);
1386 : }
1387 :
1388 155 : if (m_bNoDataSet)
1389 : {
1390 118 : if (pbSuccess)
1391 102 : *pbSuccess = TRUE;
1392 118 : return m_dfNoDataValue;
1393 : }
1394 :
1395 37 : return GDALPamRasterBand::GetNoDataValue(pbSuccess);
1396 : }
1397 :
1398 : /************************************************************************/
1399 : /* GetNoDataValueAsInt64() */
1400 : /************************************************************************/
1401 :
1402 4 : int64_t netCDFRasterBand::GetNoDataValueAsInt64(int *pbSuccess)
1403 :
1404 : {
1405 4 : if (m_bNoDataSetAsInt64)
1406 : {
1407 4 : if (pbSuccess)
1408 4 : *pbSuccess = TRUE;
1409 :
1410 4 : return m_nNodataValueInt64;
1411 : }
1412 :
1413 0 : return GDALPamRasterBand::GetNoDataValueAsInt64(pbSuccess);
1414 : }
1415 :
1416 : /************************************************************************/
1417 : /* GetNoDataValueAsUInt64() */
1418 : /************************************************************************/
1419 :
1420 4 : uint64_t netCDFRasterBand::GetNoDataValueAsUInt64(int *pbSuccess)
1421 :
1422 : {
1423 4 : if (m_bNoDataSetAsUInt64)
1424 : {
1425 4 : if (pbSuccess)
1426 4 : *pbSuccess = TRUE;
1427 :
1428 4 : return m_nNodataValueUInt64;
1429 : }
1430 :
1431 0 : return GDALPamRasterBand::GetNoDataValueAsUInt64(pbSuccess);
1432 : }
1433 :
1434 : /************************************************************************/
1435 : /* SetNoDataValue() */
1436 : /************************************************************************/
1437 :
1438 134 : CPLErr netCDFRasterBand::SetNoDataValue(double dfNoData)
1439 :
1440 : {
1441 268 : CPLMutexHolderD(&hNCMutex);
1442 :
1443 : // If already set to new value, don't do anything.
1444 134 : if (m_bNoDataSet && CPLIsEqual(dfNoData, m_dfNoDataValue))
1445 19 : return CE_None;
1446 :
1447 : // Write value if in update mode.
1448 115 : if (poDS->GetAccess() == GA_Update)
1449 : {
1450 : // netcdf-4 does not allow to set _FillValue after leaving define mode,
1451 : // but it is ok if variable has not been written to, so only print
1452 : // debug. See bug #4484.
1453 125 : if (m_bNoDataSet &&
1454 10 : !reinterpret_cast<netCDFDataset *>(poDS)->GetDefineMode())
1455 : {
1456 0 : CPLDebug("GDAL_netCDF",
1457 : "Setting NoDataValue to %.17g (previously set to %.17g) "
1458 : "but file is no longer in define mode (id #%d, band #%d)",
1459 : dfNoData, m_dfNoDataValue, cdfid, nBand);
1460 : }
1461 : #ifdef NCDF_DEBUG
1462 : else
1463 : {
1464 : CPLDebug("GDAL_netCDF",
1465 : "Setting NoDataValue to %.17g (id #%d, band #%d)",
1466 : dfNoData, cdfid, nBand);
1467 : }
1468 : #endif
1469 : // Make sure we are in define mode.
1470 115 : reinterpret_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1471 :
1472 : int status;
1473 115 : if (eDataType == GDT_Byte)
1474 : {
1475 6 : if (bSignedData)
1476 : {
1477 0 : signed char cNoDataValue = static_cast<signed char>(dfNoData);
1478 0 : status = nc_put_att_schar(cdfid, nZId, NCDF_FillValue,
1479 : nc_datatype, 1, &cNoDataValue);
1480 : }
1481 : else
1482 : {
1483 6 : const unsigned char ucNoDataValue =
1484 6 : static_cast<unsigned char>(dfNoData);
1485 6 : status = nc_put_att_uchar(cdfid, nZId, NCDF_FillValue,
1486 : nc_datatype, 1, &ucNoDataValue);
1487 : }
1488 : }
1489 109 : else if (eDataType == GDT_Int16)
1490 : {
1491 14 : short nsNoDataValue = static_cast<short>(dfNoData);
1492 14 : status = nc_put_att_short(cdfid, nZId, NCDF_FillValue, nc_datatype,
1493 : 1, &nsNoDataValue);
1494 : }
1495 95 : else if (eDataType == GDT_Int32)
1496 : {
1497 27 : int nNoDataValue = static_cast<int>(dfNoData);
1498 27 : status = nc_put_att_int(cdfid, nZId, NCDF_FillValue, nc_datatype, 1,
1499 : &nNoDataValue);
1500 : }
1501 68 : else if (eDataType == GDT_Float32)
1502 : {
1503 31 : float fNoDataValue = static_cast<float>(dfNoData);
1504 31 : status = nc_put_att_float(cdfid, nZId, NCDF_FillValue, nc_datatype,
1505 : 1, &fNoDataValue);
1506 : }
1507 37 : else if (eDataType == GDT_UInt16 &&
1508 6 : reinterpret_cast<netCDFDataset *>(poDS)->eFormat ==
1509 : NCDF_FORMAT_NC4)
1510 : {
1511 6 : unsigned short usNoDataValue =
1512 6 : static_cast<unsigned short>(dfNoData);
1513 6 : status = nc_put_att_ushort(cdfid, nZId, NCDF_FillValue, nc_datatype,
1514 6 : 1, &usNoDataValue);
1515 : }
1516 31 : else if (eDataType == GDT_UInt32 &&
1517 7 : reinterpret_cast<netCDFDataset *>(poDS)->eFormat ==
1518 : NCDF_FORMAT_NC4)
1519 : {
1520 7 : unsigned int unNoDataValue = static_cast<unsigned int>(dfNoData);
1521 7 : status = nc_put_att_uint(cdfid, nZId, NCDF_FillValue, nc_datatype,
1522 7 : 1, &unNoDataValue);
1523 : }
1524 : else
1525 : {
1526 24 : status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
1527 : 1, &dfNoData);
1528 : }
1529 :
1530 115 : NCDF_ERR(status);
1531 :
1532 : // Update status if write worked.
1533 115 : if (status == NC_NOERR)
1534 : {
1535 115 : SetNoDataValueNoUpdate(dfNoData);
1536 115 : return CE_None;
1537 : }
1538 :
1539 0 : return CE_Failure;
1540 : }
1541 :
1542 0 : SetNoDataValueNoUpdate(dfNoData);
1543 0 : return CE_None;
1544 : }
1545 :
1546 : /************************************************************************/
1547 : /* SetNoDataValueNoUpdate() */
1548 : /************************************************************************/
1549 :
1550 430 : void netCDFRasterBand::SetNoDataValueNoUpdate(double dfNoData)
1551 : {
1552 430 : m_dfNoDataValue = dfNoData;
1553 430 : m_bNoDataSet = true;
1554 430 : m_bNoDataSetAsInt64 = false;
1555 430 : m_bNoDataSetAsUInt64 = false;
1556 430 : }
1557 :
1558 : /************************************************************************/
1559 : /* SetNoDataValueAsInt64() */
1560 : /************************************************************************/
1561 :
1562 3 : CPLErr netCDFRasterBand::SetNoDataValueAsInt64(int64_t nNoData)
1563 :
1564 : {
1565 6 : CPLMutexHolderD(&hNCMutex);
1566 :
1567 : // If already set to new value, don't do anything.
1568 3 : if (m_bNoDataSetAsInt64 && nNoData == m_nNodataValueInt64)
1569 0 : return CE_None;
1570 :
1571 : // Write value if in update mode.
1572 3 : if (poDS->GetAccess() == GA_Update)
1573 : {
1574 : // netcdf-4 does not allow to set NCDF_FillValue after leaving define mode,
1575 : // but it is ok if variable has not been written to, so only print
1576 : // debug. See bug #4484.
1577 3 : if (m_bNoDataSetAsInt64 &&
1578 0 : !reinterpret_cast<netCDFDataset *>(poDS)->GetDefineMode())
1579 : {
1580 0 : CPLDebug("GDAL_netCDF",
1581 : "Setting NoDataValue to " CPL_FRMT_GIB
1582 : " (previously set to " CPL_FRMT_GIB ") "
1583 : "but file is no longer in define mode (id #%d, band #%d)",
1584 : static_cast<GIntBig>(nNoData),
1585 0 : static_cast<GIntBig>(m_nNodataValueInt64), cdfid, nBand);
1586 : }
1587 : #ifdef NCDF_DEBUG
1588 : else
1589 : {
1590 : CPLDebug("GDAL_netCDF",
1591 : "Setting NoDataValue to " CPL_FRMT_GIB
1592 : " (id #%d, band #%d)",
1593 : static_cast<GIntBig>(nNoData), cdfid, nBand);
1594 : }
1595 : #endif
1596 : // Make sure we are in define mode.
1597 3 : reinterpret_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1598 :
1599 : int status;
1600 3 : if (eDataType == GDT_Int64 &&
1601 3 : reinterpret_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
1602 : {
1603 3 : long long tmp = static_cast<long long>(nNoData);
1604 3 : status = nc_put_att_longlong(cdfid, nZId, NCDF_FillValue,
1605 3 : nc_datatype, 1, &tmp);
1606 : }
1607 : else
1608 : {
1609 0 : double dfNoData = static_cast<double>(nNoData);
1610 0 : status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
1611 : 1, &dfNoData);
1612 : }
1613 :
1614 3 : NCDF_ERR(status);
1615 :
1616 : // Update status if write worked.
1617 3 : if (status == NC_NOERR)
1618 : {
1619 3 : SetNoDataValueNoUpdate(nNoData);
1620 3 : return CE_None;
1621 : }
1622 :
1623 0 : return CE_Failure;
1624 : }
1625 :
1626 0 : SetNoDataValueNoUpdate(nNoData);
1627 0 : return CE_None;
1628 : }
1629 :
1630 : /************************************************************************/
1631 : /* SetNoDataValueNoUpdate() */
1632 : /************************************************************************/
1633 :
1634 11 : void netCDFRasterBand::SetNoDataValueNoUpdate(int64_t nNoData)
1635 : {
1636 11 : m_nNodataValueInt64 = nNoData;
1637 11 : m_bNoDataSet = false;
1638 11 : m_bNoDataSetAsInt64 = true;
1639 11 : m_bNoDataSetAsUInt64 = false;
1640 11 : }
1641 :
1642 : /************************************************************************/
1643 : /* SetNoDataValueAsUInt64() */
1644 : /************************************************************************/
1645 :
1646 3 : CPLErr netCDFRasterBand::SetNoDataValueAsUInt64(uint64_t nNoData)
1647 :
1648 : {
1649 6 : CPLMutexHolderD(&hNCMutex);
1650 :
1651 : // If already set to new value, don't do anything.
1652 3 : if (m_bNoDataSetAsUInt64 && nNoData == m_nNodataValueUInt64)
1653 0 : return CE_None;
1654 :
1655 : // Write value if in update mode.
1656 3 : if (poDS->GetAccess() == GA_Update)
1657 : {
1658 : // netcdf-4 does not allow to set _FillValue after leaving define mode,
1659 : // but it is ok if variable has not been written to, so only print
1660 : // debug. See bug #4484.
1661 3 : if (m_bNoDataSetAsUInt64 &&
1662 0 : !reinterpret_cast<netCDFDataset *>(poDS)->GetDefineMode())
1663 : {
1664 0 : CPLDebug("GDAL_netCDF",
1665 : "Setting NoDataValue to " CPL_FRMT_GUIB
1666 : " (previously set to " CPL_FRMT_GUIB ") "
1667 : "but file is no longer in define mode (id #%d, band #%d)",
1668 : static_cast<GUIntBig>(nNoData),
1669 0 : static_cast<GUIntBig>(m_nNodataValueUInt64), cdfid, nBand);
1670 : }
1671 : #ifdef NCDF_DEBUG
1672 : else
1673 : {
1674 : CPLDebug("GDAL_netCDF",
1675 : "Setting NoDataValue to " CPL_FRMT_GUIB
1676 : " (id #%d, band #%d)",
1677 : static_cast<GUIntBig>(nNoData), cdfid, nBand);
1678 : }
1679 : #endif
1680 : // Make sure we are in define mode.
1681 3 : reinterpret_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1682 :
1683 : int status;
1684 3 : if (eDataType == GDT_UInt64 &&
1685 3 : reinterpret_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
1686 : {
1687 3 : unsigned long long tmp = static_cast<long long>(nNoData);
1688 3 : status = nc_put_att_ulonglong(cdfid, nZId, NCDF_FillValue,
1689 3 : nc_datatype, 1, &tmp);
1690 : }
1691 : else
1692 : {
1693 0 : double dfNoData = static_cast<double>(nNoData);
1694 0 : status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
1695 : 1, &dfNoData);
1696 : }
1697 :
1698 3 : NCDF_ERR(status);
1699 :
1700 : // Update status if write worked.
1701 3 : if (status == NC_NOERR)
1702 : {
1703 3 : SetNoDataValueNoUpdate(nNoData);
1704 3 : return CE_None;
1705 : }
1706 :
1707 0 : return CE_Failure;
1708 : }
1709 :
1710 0 : SetNoDataValueNoUpdate(nNoData);
1711 0 : return CE_None;
1712 : }
1713 :
1714 : /************************************************************************/
1715 : /* SetNoDataValueNoUpdate() */
1716 : /************************************************************************/
1717 :
1718 10 : void netCDFRasterBand::SetNoDataValueNoUpdate(uint64_t nNoData)
1719 : {
1720 10 : m_nNodataValueUInt64 = nNoData;
1721 10 : m_bNoDataSet = false;
1722 10 : m_bNoDataSetAsInt64 = false;
1723 10 : m_bNoDataSetAsUInt64 = true;
1724 10 : }
1725 :
1726 : /************************************************************************/
1727 : /* DeleteNoDataValue() */
1728 : /************************************************************************/
1729 :
1730 : #ifdef notdef
1731 : CPLErr netCDFRasterBand::DeleteNoDataValue()
1732 :
1733 : {
1734 : CPLMutexHolderD(&hNCMutex);
1735 :
1736 : if (!bNoDataSet)
1737 : return CE_None;
1738 :
1739 : // Write value if in update mode.
1740 : if (poDS->GetAccess() == GA_Update)
1741 : {
1742 : // Make sure we are in define mode.
1743 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1744 :
1745 : status = nc_del_att(cdfid, nZId, NCDF_FillValue);
1746 :
1747 : NCDF_ERR(status);
1748 :
1749 : // Update status if write worked.
1750 : if (status == NC_NOERR)
1751 : {
1752 : dfNoDataValue = 0.0;
1753 : bNoDataSet = false;
1754 : return CE_None;
1755 : }
1756 :
1757 : return CE_Failure;
1758 : }
1759 :
1760 : dfNoDataValue = 0.0;
1761 : bNoDataSet = false;
1762 : return CE_None;
1763 : }
1764 : #endif
1765 :
1766 : /************************************************************************/
1767 : /* SerializeToXML() */
1768 : /************************************************************************/
1769 :
1770 5 : CPLXMLNode *netCDFRasterBand::SerializeToXML(const char * /* pszUnused */)
1771 : {
1772 : // Overridden from GDALPamDataset to add only band histogram
1773 : // and statistics. See bug #4244.
1774 5 : if (psPam == nullptr)
1775 0 : return nullptr;
1776 :
1777 : // Setup root node and attributes.
1778 : CPLXMLNode *psTree =
1779 5 : CPLCreateXMLNode(nullptr, CXT_Element, "PAMRasterBand");
1780 :
1781 5 : if (GetBand() > 0)
1782 : {
1783 10 : CPLString oFmt;
1784 5 : CPLSetXMLValue(psTree, "#band", oFmt.Printf("%d", GetBand()));
1785 : }
1786 :
1787 : // Histograms.
1788 5 : if (psPam->psSavedHistograms != nullptr)
1789 1 : CPLAddXMLChild(psTree, CPLCloneXMLTree(psPam->psSavedHistograms));
1790 :
1791 : // Metadata (statistics only).
1792 5 : GDALMultiDomainMetadata oMDMDStats;
1793 5 : const char *papszMDStats[] = {"STATISTICS_MINIMUM", "STATISTICS_MAXIMUM",
1794 : "STATISTICS_MEAN", "STATISTICS_STDDEV",
1795 : nullptr};
1796 25 : for (int i = 0; i < CSLCount(papszMDStats); i++)
1797 : {
1798 20 : const char *pszMDI = GetMetadataItem(papszMDStats[i]);
1799 20 : if (pszMDI)
1800 4 : oMDMDStats.SetMetadataItem(papszMDStats[i], pszMDI);
1801 : }
1802 5 : CPLXMLNode *psMD = oMDMDStats.Serialize();
1803 :
1804 5 : if (psMD != nullptr)
1805 : {
1806 1 : if (psMD->psChild == nullptr)
1807 0 : CPLDestroyXMLNode(psMD);
1808 : else
1809 1 : CPLAddXMLChild(psTree, psMD);
1810 : }
1811 :
1812 : // We don't want to return anything if we had no metadata to attach.
1813 5 : if (psTree->psChild == nullptr || psTree->psChild->psNext == nullptr)
1814 : {
1815 3 : CPLDestroyXMLNode(psTree);
1816 3 : psTree = nullptr;
1817 : }
1818 :
1819 5 : return psTree;
1820 : }
1821 :
1822 : /************************************************************************/
1823 : /* Get1DVariableIndexedByDimension() */
1824 : /************************************************************************/
1825 :
1826 79 : static int Get1DVariableIndexedByDimension(int cdfid, int nDimId,
1827 : const char *pszDimName,
1828 : bool bVerboseError, int *pnGroupID)
1829 : {
1830 79 : *pnGroupID = -1;
1831 79 : int nVarID = -1;
1832 : // First try to find a variable whose name is identical to the dimension
1833 : // name, and check that it is indeed indexed by this dimension
1834 79 : if (NCDFResolveVar(cdfid, pszDimName, pnGroupID, &nVarID) == CE_None)
1835 : {
1836 65 : int nDimCountOfVariable = 0;
1837 65 : nc_inq_varndims(*pnGroupID, nVarID, &nDimCountOfVariable);
1838 65 : if (nDimCountOfVariable == 1)
1839 : {
1840 65 : int nDimIdOfVariable = -1;
1841 65 : nc_inq_vardimid(*pnGroupID, nVarID, &nDimIdOfVariable);
1842 65 : if (nDimIdOfVariable == nDimId)
1843 : {
1844 65 : return nVarID;
1845 : }
1846 : }
1847 : }
1848 :
1849 : // Otherwise iterate over the variables to find potential candidates
1850 : // TODO: should be modified to search also in other groups using the same
1851 : // logic than in NCDFResolveVar(), but maybe not needed if it's a
1852 : // very rare case? and I think this is not CF compliant.
1853 14 : int nvars = 0;
1854 14 : CPL_IGNORE_RET_VAL(nc_inq(cdfid, nullptr, &nvars, nullptr, nullptr));
1855 :
1856 14 : int nCountCandidateVars = 0;
1857 14 : int nCandidateVarID = -1;
1858 65 : for (int k = 0; k < nvars; k++)
1859 : {
1860 51 : int nDimCountOfVariable = 0;
1861 51 : nc_inq_varndims(cdfid, k, &nDimCountOfVariable);
1862 51 : if (nDimCountOfVariable == 1)
1863 : {
1864 27 : int nDimIdOfVariable = -1;
1865 27 : nc_inq_vardimid(cdfid, k, &nDimIdOfVariable);
1866 27 : if (nDimIdOfVariable == nDimId)
1867 : {
1868 7 : nCountCandidateVars++;
1869 7 : nCandidateVarID = k;
1870 : }
1871 : }
1872 : }
1873 14 : if (nCountCandidateVars > 1)
1874 : {
1875 1 : if (bVerboseError)
1876 : {
1877 1 : CPLError(CE_Warning, CPLE_AppDefined,
1878 : "Several 1D variables are indexed by dimension %s",
1879 : pszDimName);
1880 : }
1881 1 : *pnGroupID = -1;
1882 1 : return -1;
1883 : }
1884 13 : else if (nCandidateVarID < 0)
1885 : {
1886 8 : if (bVerboseError)
1887 : {
1888 8 : CPLError(CE_Warning, CPLE_AppDefined,
1889 : "No 1D variable is indexed by dimension %s", pszDimName);
1890 : }
1891 : }
1892 13 : *pnGroupID = cdfid;
1893 13 : return nCandidateVarID;
1894 : }
1895 :
1896 : /************************************************************************/
1897 : /* CreateMetadataFromAttributes() */
1898 : /************************************************************************/
1899 :
1900 471 : void netCDFRasterBand::CreateMetadataFromAttributes()
1901 : {
1902 471 : char szVarName[NC_MAX_NAME + 1] = {};
1903 471 : int status = nc_inq_varname(cdfid, nZId, szVarName);
1904 471 : NCDF_ERR(status);
1905 :
1906 471 : GDALPamRasterBand::SetMetadataItem("NETCDF_VARNAME", szVarName);
1907 :
1908 : // Get attribute metadata.
1909 471 : int nAtt = 0;
1910 471 : NCDF_ERR(nc_inq_varnatts(cdfid, nZId, &nAtt));
1911 :
1912 1987 : for (int i = 0; i < nAtt; i++)
1913 : {
1914 1516 : char szMetaName[NC_MAX_NAME + 1] = {};
1915 1516 : status = nc_inq_attname(cdfid, nZId, i, szMetaName);
1916 1516 : if (status != NC_NOERR)
1917 12 : continue;
1918 :
1919 1516 : if (GDALPamRasterBand::GetMetadataItem(szMetaName) != nullptr)
1920 : {
1921 12 : continue;
1922 : }
1923 :
1924 1504 : char *pszMetaValue = nullptr;
1925 1504 : if (NCDFGetAttr(cdfid, nZId, szMetaName, &pszMetaValue) == CE_None)
1926 : {
1927 1504 : GDALPamRasterBand::SetMetadataItem(szMetaName, pszMetaValue);
1928 : }
1929 : else
1930 : {
1931 0 : CPLDebug("GDAL_netCDF", "invalid Band metadata %s", szMetaName);
1932 : }
1933 :
1934 1504 : if (pszMetaValue)
1935 : {
1936 1504 : CPLFree(pszMetaValue);
1937 1504 : pszMetaValue = nullptr;
1938 : }
1939 : }
1940 471 : }
1941 :
1942 : /************************************************************************/
1943 : /* CreateMetadataFromOtherVars() */
1944 : /************************************************************************/
1945 :
1946 49 : void netCDFRasterBand::CreateMetadataFromOtherVars()
1947 :
1948 : {
1949 49 : CPLAssert(!m_bCreateMetadataFromOtherVarsDone);
1950 49 : m_bCreateMetadataFromOtherVarsDone = true;
1951 :
1952 49 : netCDFDataset *l_poDS = reinterpret_cast<netCDFDataset *>(poDS);
1953 49 : const int nPamFlagsBackup = l_poDS->nPamFlags;
1954 :
1955 : // Compute all dimensions from Band number and save in Metadata.
1956 49 : int nd = 0;
1957 49 : nc_inq_varndims(cdfid, nZId, &nd);
1958 : // Compute multidimention band position.
1959 : //
1960 : // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
1961 : // if Data[2,3,4,x,y]
1962 : //
1963 : // BandPos0 = (nBand) / (3*4)
1964 : // BandPos1 = (nBand - BandPos0*(3*4)) / (4)
1965 : // BandPos2 = (nBand - BandPos0*(3*4)) % (4)
1966 :
1967 49 : int Sum = 1;
1968 49 : if (nd == 3)
1969 : {
1970 5 : Sum *= panBandZLev[0];
1971 : }
1972 :
1973 : // Loop over non-spatial dimensions.
1974 49 : int Taken = 0;
1975 :
1976 89 : for (int i = 0; i < nd - 2; i++)
1977 : {
1978 : int result;
1979 40 : if (i != nd - 2 - 1)
1980 : {
1981 18 : Sum = 1;
1982 37 : for (int j = i + 1; j < nd - 2; j++)
1983 : {
1984 19 : Sum *= panBandZLev[j];
1985 : }
1986 18 : result = static_cast<int>((nLevel - Taken) / Sum);
1987 : }
1988 : else
1989 : {
1990 22 : result = static_cast<int>((nLevel - Taken) % Sum);
1991 : }
1992 :
1993 40 : char szName[NC_MAX_NAME + 1] = {};
1994 40 : snprintf(szName, sizeof(szName), "%s",
1995 40 : l_poDS->papszDimName[l_poDS->m_anDimIds[panBandZPos[i]]]);
1996 :
1997 : char szMetaName[NC_MAX_NAME + 1 + 32];
1998 40 : snprintf(szMetaName, sizeof(szMetaName), "NETCDF_DIM_%s", szName);
1999 :
2000 40 : const int nGroupID = l_poDS->m_anExtraDimGroupIds[i];
2001 40 : const int nVarID = l_poDS->m_anExtraDimVarIds[i];
2002 40 : if (nVarID < 0)
2003 : {
2004 2 : GDALPamRasterBand::SetMetadataItem(szMetaName,
2005 : CPLSPrintf("%d", result + 1));
2006 : }
2007 : else
2008 : {
2009 : // TODO: Make sure all the status checks make sense.
2010 :
2011 38 : nc_type nVarType = NC_NAT;
2012 38 : /* status = */ nc_inq_vartype(nGroupID, nVarID, &nVarType);
2013 :
2014 38 : int nDims = 0;
2015 38 : /* status = */ nc_inq_varndims(nGroupID, nVarID, &nDims);
2016 :
2017 38 : char szMetaTemp[256] = {};
2018 38 : if (nDims == 1)
2019 : {
2020 38 : size_t count[1] = {1};
2021 38 : size_t start[1] = {static_cast<size_t>(result)};
2022 :
2023 38 : switch (nVarType)
2024 : {
2025 0 : case NC_BYTE:
2026 : // TODO: Check for signed/unsigned byte.
2027 : signed char cData;
2028 0 : /* status = */ nc_get_vara_schar(nGroupID, nVarID,
2029 : start, count, &cData);
2030 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", cData);
2031 0 : break;
2032 0 : case NC_SHORT:
2033 : short sData;
2034 0 : /* status = */ nc_get_vara_short(nGroupID, nVarID,
2035 : start, count, &sData);
2036 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", sData);
2037 0 : break;
2038 19 : case NC_INT:
2039 : {
2040 : int nData;
2041 19 : /* status = */ nc_get_vara_int(nGroupID, nVarID, start,
2042 : count, &nData);
2043 19 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", nData);
2044 19 : break;
2045 : }
2046 0 : case NC_FLOAT:
2047 : float fData;
2048 0 : /* status = */ nc_get_vara_float(nGroupID, nVarID,
2049 : start, count, &fData);
2050 0 : CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.8g",
2051 : fData);
2052 0 : break;
2053 18 : case NC_DOUBLE:
2054 : double dfData;
2055 18 : /* status = */ nc_get_vara_double(
2056 : nGroupID, nVarID, start, count, &dfData);
2057 18 : CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.16g",
2058 : dfData);
2059 18 : break;
2060 0 : case NC_UBYTE:
2061 : unsigned char ucData;
2062 0 : /* status = */ nc_get_vara_uchar(nGroupID, nVarID,
2063 : start, count, &ucData);
2064 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", ucData);
2065 0 : break;
2066 0 : case NC_USHORT:
2067 : unsigned short usData;
2068 0 : /* status = */ nc_get_vara_ushort(
2069 : nGroupID, nVarID, start, count, &usData);
2070 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", usData);
2071 0 : break;
2072 0 : case NC_UINT:
2073 : {
2074 : unsigned int unData;
2075 0 : /* status = */ nc_get_vara_uint(nGroupID, nVarID, start,
2076 : count, &unData);
2077 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", unData);
2078 0 : break;
2079 : }
2080 1 : case NC_INT64:
2081 : {
2082 : long long nData;
2083 1 : /* status = */ nc_get_vara_longlong(
2084 : nGroupID, nVarID, start, count, &nData);
2085 1 : snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GIB,
2086 : nData);
2087 1 : break;
2088 : }
2089 0 : case NC_UINT64:
2090 : {
2091 : unsigned long long unData;
2092 0 : /* status = */ nc_get_vara_ulonglong(
2093 : nGroupID, nVarID, start, count, &unData);
2094 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GUIB,
2095 : unData);
2096 0 : break;
2097 : }
2098 0 : default:
2099 0 : CPLDebug("GDAL_netCDF", "invalid dim %s, type=%d",
2100 : szMetaTemp, nVarType);
2101 0 : break;
2102 : }
2103 : }
2104 : else
2105 : {
2106 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", result + 1);
2107 : }
2108 :
2109 : // Save dimension value.
2110 : // NOTE: removed #original_units as not part of CF-1.
2111 :
2112 38 : GDALPamRasterBand::SetMetadataItem(szMetaName, szMetaTemp);
2113 : }
2114 :
2115 : // Avoid int32 overflow. Perhaps something more sensible to do here ?
2116 40 : if (result > 0 && Sum > INT_MAX / result)
2117 0 : break;
2118 40 : if (Taken > INT_MAX - result * Sum)
2119 0 : break;
2120 :
2121 40 : Taken += result * Sum;
2122 : } // End loop non-spatial dimensions.
2123 :
2124 49 : l_poDS->nPamFlags = nPamFlagsBackup;
2125 49 : }
2126 :
2127 : /************************************************************************/
2128 : /* CheckData() */
2129 : /************************************************************************/
2130 : template <class T>
2131 5731 : void netCDFRasterBand::CheckData(void *pImage, void *pImageNC,
2132 : size_t nTmpBlockXSize, size_t nTmpBlockYSize,
2133 : bool bCheckIsNan)
2134 : {
2135 5731 : CPLAssert(pImage != nullptr && pImageNC != nullptr);
2136 :
2137 : // If this block is not a full block (in the x axis), we need to re-arrange
2138 : // the data this is because partial blocks are not arranged the same way in
2139 : // netcdf and gdal.
2140 5731 : if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
2141 : {
2142 6 : T *ptrWrite = static_cast<T *>(pImage);
2143 6 : T *ptrRead = static_cast<T *>(pImageNC);
2144 29 : for (size_t j = 0; j < nTmpBlockYSize;
2145 23 : j++, ptrWrite += nBlockXSize, ptrRead += nTmpBlockXSize)
2146 : {
2147 23 : memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T));
2148 : }
2149 : }
2150 :
2151 : // Is valid data checking needed or requested?
2152 5731 : if (bValidRangeValid || bCheckIsNan)
2153 : {
2154 1265 : T *ptrImage = static_cast<T *>(pImage);
2155 2584 : for (size_t j = 0; j < nTmpBlockYSize; j++)
2156 : {
2157 : // k moves along the gdal block, skipping the out-of-range pixels.
2158 1319 : size_t k = j * nBlockXSize;
2159 96938 : for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
2160 : {
2161 : // Check for nodata and nan.
2162 95619 : if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
2163 6301 : continue;
2164 89318 : if (bCheckIsNan && std::isnan((double)ptrImage[k]))
2165 : {
2166 5737 : ptrImage[k] = (T)m_dfNoDataValue;
2167 5737 : continue;
2168 : }
2169 : // Check for valid_range.
2170 83581 : if (bValidRangeValid)
2171 : {
2172 40986 : if (((adfValidRange[0] != m_dfNoDataValue) &&
2173 40986 : (ptrImage[k] < (T)adfValidRange[0])) ||
2174 40983 : ((adfValidRange[1] != m_dfNoDataValue) &&
2175 40983 : (ptrImage[k] > (T)adfValidRange[1])))
2176 : {
2177 4 : ptrImage[k] = (T)m_dfNoDataValue;
2178 : }
2179 : }
2180 : }
2181 : }
2182 : }
2183 :
2184 : // If minimum longitude is > 180, subtract 360 from all.
2185 : // If not, disable checking for further calls (check just once).
2186 : // Only check first and last block elements since lon must be monotonic.
2187 5731 : const bool bIsSigned = std::numeric_limits<T>::is_signed;
2188 5419 : if (bCheckLongitude && bIsSigned &&
2189 9 : !CPLIsEqual((double)((T *)pImage)[0], m_dfNoDataValue) &&
2190 8 : !CPLIsEqual((double)((T *)pImage)[nTmpBlockXSize - 1],
2191 2714 : m_dfNoDataValue) &&
2192 8 : std::min(((T *)pImage)[0], ((T *)pImage)[nTmpBlockXSize - 1]) > 180.0)
2193 : {
2194 0 : T *ptrImage = static_cast<T *>(pImage);
2195 0 : for (size_t j = 0; j < nTmpBlockYSize; j++)
2196 : {
2197 0 : size_t k = j * nBlockXSize;
2198 0 : for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
2199 : {
2200 0 : if (!CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
2201 0 : ptrImage[k] = static_cast<T>(ptrImage[k] - 360);
2202 : }
2203 : }
2204 : }
2205 : else
2206 : {
2207 5731 : bCheckLongitude = false;
2208 : }
2209 5731 : }
2210 :
2211 : /************************************************************************/
2212 : /* CheckDataCpx() */
2213 : /************************************************************************/
2214 : template <class T>
2215 25 : void netCDFRasterBand::CheckDataCpx(void *pImage, void *pImageNC,
2216 : size_t nTmpBlockXSize,
2217 : size_t nTmpBlockYSize, bool bCheckIsNan)
2218 : {
2219 25 : CPLAssert(pImage != nullptr && pImageNC != nullptr);
2220 :
2221 : // If this block is not a full block (in the x axis), we need to re-arrange
2222 : // the data this is because partial blocks are not arranged the same way in
2223 : // netcdf and gdal.
2224 25 : if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
2225 : {
2226 0 : T *ptrWrite = static_cast<T *>(pImage);
2227 0 : T *ptrRead = static_cast<T *>(pImageNC);
2228 0 : for (size_t j = 0; j < nTmpBlockYSize; j++,
2229 0 : ptrWrite += (2 * nBlockXSize),
2230 0 : ptrRead += (2 * nTmpBlockXSize))
2231 : {
2232 0 : memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T) * 2);
2233 : }
2234 : }
2235 :
2236 : // Is valid data checking needed or requested?
2237 25 : if (bValidRangeValid || bCheckIsNan)
2238 : {
2239 0 : T *ptrImage = static_cast<T *>(pImage);
2240 0 : for (size_t j = 0; j < nTmpBlockYSize; j++)
2241 : {
2242 : // k moves along the gdal block, skipping the out-of-range pixels.
2243 0 : size_t k = 2 * j * nBlockXSize;
2244 0 : for (size_t i = 0; i < (2 * nTmpBlockXSize); i++, k++)
2245 : {
2246 : // Check for nodata and nan.
2247 0 : if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
2248 0 : continue;
2249 0 : if (bCheckIsNan && std::isnan((double)ptrImage[k]))
2250 : {
2251 0 : ptrImage[k] = (T)m_dfNoDataValue;
2252 0 : continue;
2253 : }
2254 : // Check for valid_range.
2255 0 : if (bValidRangeValid)
2256 : {
2257 0 : if (((adfValidRange[0] != m_dfNoDataValue) &&
2258 0 : (ptrImage[k] < (T)adfValidRange[0])) ||
2259 0 : ((adfValidRange[1] != m_dfNoDataValue) &&
2260 0 : (ptrImage[k] > (T)adfValidRange[1])))
2261 : {
2262 0 : ptrImage[k] = (T)m_dfNoDataValue;
2263 : }
2264 : }
2265 : }
2266 : }
2267 : }
2268 25 : }
2269 :
2270 : /************************************************************************/
2271 : /* FetchNetcdfChunk() */
2272 : /************************************************************************/
2273 :
2274 5756 : bool netCDFRasterBand::FetchNetcdfChunk(size_t xstart, size_t ystart,
2275 : void *pImage)
2276 : {
2277 5756 : size_t start[MAX_NC_DIMS] = {};
2278 5756 : size_t edge[MAX_NC_DIMS] = {};
2279 :
2280 5756 : start[nBandXPos] = xstart;
2281 5756 : edge[nBandXPos] = nBlockXSize;
2282 5756 : if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
2283 6 : edge[nBandXPos] = nRasterXSize - start[nBandXPos];
2284 5756 : if (nBandYPos >= 0)
2285 : {
2286 5752 : start[nBandYPos] = ystart;
2287 5752 : edge[nBandYPos] = nBlockYSize;
2288 5752 : if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
2289 4 : edge[nBandYPos] = nRasterYSize - start[nBandYPos];
2290 : }
2291 5756 : const size_t nYChunkSize = nBandYPos < 0 ? 1 : edge[nBandYPos];
2292 :
2293 : #ifdef NCDF_DEBUG
2294 : CPLDebug("GDAL_netCDF", "start={%ld,%ld} edge={%ld,%ld} bBottomUp=%d",
2295 : start[nBandXPos], nBandYPos < 0 ? 0 : start[nBandYPos],
2296 : edge[nBandXPos], nYChunkSize, ((netCDFDataset *)poDS)->bBottomUp);
2297 : #endif
2298 :
2299 5756 : int nd = 0;
2300 5756 : nc_inq_varndims(cdfid, nZId, &nd);
2301 5756 : if (nd == 3)
2302 : {
2303 1078 : start[panBandZPos[0]] = nLevel; // z
2304 1078 : edge[panBandZPos[0]] = 1;
2305 : }
2306 :
2307 : // Compute multidimention band position.
2308 : //
2309 : // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
2310 : // if Data[2,3,4,x,y]
2311 : //
2312 : // BandPos0 = (nBand) / (3*4)
2313 : // BandPos1 = (nBand - (3*4)) / (4)
2314 : // BandPos2 = (nBand - (3*4)) % (4)
2315 5756 : if (nd > 3)
2316 : {
2317 160 : int Sum = -1;
2318 160 : int Taken = 0;
2319 480 : for (int i = 0; i < nd - 2; i++)
2320 : {
2321 320 : if (i != nd - 2 - 1)
2322 : {
2323 160 : Sum = 1;
2324 320 : for (int j = i + 1; j < nd - 2; j++)
2325 : {
2326 160 : Sum *= panBandZLev[j];
2327 : }
2328 160 : start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
2329 160 : edge[panBandZPos[i]] = 1;
2330 : }
2331 : else
2332 : {
2333 160 : start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
2334 160 : edge[panBandZPos[i]] = 1;
2335 : }
2336 320 : Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
2337 : }
2338 : }
2339 :
2340 : // Make sure we are in data mode.
2341 5756 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
2342 :
2343 : // If this block is not a full block in the x axis, we need to
2344 : // re-arrange the data because partial blocks are not arranged the
2345 : // same way in netcdf and gdal, so we first we read the netcdf data at
2346 : // the end of the gdal block buffer then re-arrange rows in CheckData().
2347 5756 : void *pImageNC = pImage;
2348 5756 : if (edge[nBandXPos] != static_cast<size_t>(nBlockXSize))
2349 : {
2350 6 : pImageNC = static_cast<GByte *>(pImage) +
2351 6 : ((static_cast<size_t>(nBlockXSize) * nBlockYSize -
2352 12 : edge[nBandXPos] * nYChunkSize) *
2353 6 : (GDALGetDataTypeSize(eDataType) / 8));
2354 : }
2355 :
2356 : // Read data according to type.
2357 : int status;
2358 5756 : if (eDataType == GDT_Byte)
2359 : {
2360 3004 : if (bSignedData)
2361 : {
2362 0 : status = nc_get_vara_schar(cdfid, nZId, start, edge,
2363 : static_cast<signed char *>(pImageNC));
2364 0 : if (status == NC_NOERR)
2365 0 : CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
2366 : nYChunkSize, false);
2367 : }
2368 : else
2369 : {
2370 3004 : status = nc_get_vara_uchar(cdfid, nZId, start, edge,
2371 : static_cast<unsigned char *>(pImageNC));
2372 3004 : if (status == NC_NOERR)
2373 3004 : CheckData<unsigned char>(pImage, pImageNC, edge[nBandXPos],
2374 : nYChunkSize, false);
2375 : }
2376 : }
2377 2752 : else if (eDataType == GDT_Int8)
2378 : {
2379 60 : status = nc_get_vara_schar(cdfid, nZId, start, edge,
2380 : static_cast<signed char *>(pImageNC));
2381 60 : if (status == NC_NOERR)
2382 60 : CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
2383 : nYChunkSize, false);
2384 : }
2385 2692 : else if (nc_datatype == NC_SHORT)
2386 : {
2387 465 : status = nc_get_vara_short(cdfid, nZId, start, edge,
2388 : static_cast<short *>(pImageNC));
2389 465 : if (status == NC_NOERR)
2390 : {
2391 465 : if (eDataType == GDT_Int16)
2392 : {
2393 462 : CheckData<GInt16>(pImage, pImageNC, edge[nBandXPos],
2394 : nYChunkSize, false);
2395 : }
2396 : else
2397 : {
2398 3 : CheckData<GUInt16>(pImage, pImageNC, edge[nBandXPos],
2399 : nYChunkSize, false);
2400 : }
2401 : }
2402 : }
2403 2227 : else if (eDataType == GDT_Int32)
2404 : {
2405 : #if SIZEOF_UNSIGNED_LONG == 4
2406 : status = nc_get_vara_long(cdfid, nZId, start, edge,
2407 : static_cast<long *>(pImageNC));
2408 : if (status == NC_NOERR)
2409 : CheckData<long>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2410 : false);
2411 : #else
2412 912 : status = nc_get_vara_int(cdfid, nZId, start, edge,
2413 : static_cast<int *>(pImageNC));
2414 912 : if (status == NC_NOERR)
2415 912 : CheckData<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2416 : false);
2417 : #endif
2418 : }
2419 1315 : else if (eDataType == GDT_Float32)
2420 : {
2421 1178 : status = nc_get_vara_float(cdfid, nZId, start, edge,
2422 : static_cast<float *>(pImageNC));
2423 1178 : if (status == NC_NOERR)
2424 1178 : CheckData<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2425 : true);
2426 : }
2427 137 : else if (eDataType == GDT_Float64)
2428 : {
2429 86 : status = nc_get_vara_double(cdfid, nZId, start, edge,
2430 : static_cast<double *>(pImageNC));
2431 86 : if (status == NC_NOERR)
2432 86 : CheckData<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2433 : true);
2434 : }
2435 51 : else if (eDataType == GDT_UInt16)
2436 : {
2437 6 : status = nc_get_vara_ushort(cdfid, nZId, start, edge,
2438 : static_cast<unsigned short *>(pImageNC));
2439 6 : if (status == NC_NOERR)
2440 6 : CheckData<unsigned short>(pImage, pImageNC, edge[nBandXPos],
2441 : nYChunkSize, false);
2442 : }
2443 45 : else if (eDataType == GDT_UInt32)
2444 : {
2445 6 : status = nc_get_vara_uint(cdfid, nZId, start, edge,
2446 : static_cast<unsigned int *>(pImageNC));
2447 6 : if (status == NC_NOERR)
2448 6 : CheckData<unsigned int>(pImage, pImageNC, edge[nBandXPos],
2449 : nYChunkSize, false);
2450 : }
2451 39 : else if (eDataType == GDT_Int64)
2452 : {
2453 7 : status = nc_get_vara_longlong(cdfid, nZId, start, edge,
2454 : static_cast<long long *>(pImageNC));
2455 7 : if (status == NC_NOERR)
2456 7 : CheckData<std::int64_t>(pImage, pImageNC, edge[nBandXPos],
2457 : nYChunkSize, false);
2458 : }
2459 32 : else if (eDataType == GDT_UInt64)
2460 : {
2461 : status =
2462 7 : nc_get_vara_ulonglong(cdfid, nZId, start, edge,
2463 : static_cast<unsigned long long *>(pImageNC));
2464 7 : if (status == NC_NOERR)
2465 7 : CheckData<std::uint64_t>(pImage, pImageNC, edge[nBandXPos],
2466 : nYChunkSize, false);
2467 : }
2468 25 : else if (eDataType == GDT_CInt16)
2469 : {
2470 0 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2471 0 : if (status == NC_NOERR)
2472 0 : CheckDataCpx<short>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2473 : false);
2474 : }
2475 25 : else if (eDataType == GDT_CInt32)
2476 : {
2477 0 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2478 0 : if (status == NC_NOERR)
2479 0 : CheckDataCpx<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2480 : false);
2481 : }
2482 25 : else if (eDataType == GDT_CFloat32)
2483 : {
2484 20 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2485 20 : if (status == NC_NOERR)
2486 20 : CheckDataCpx<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2487 : false);
2488 : }
2489 5 : else if (eDataType == GDT_CFloat64)
2490 : {
2491 5 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2492 5 : if (status == NC_NOERR)
2493 5 : CheckDataCpx<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2494 : false);
2495 : }
2496 :
2497 : else
2498 0 : status = NC_EBADTYPE;
2499 :
2500 5756 : if (status != NC_NOERR)
2501 : {
2502 0 : CPLError(CE_Failure, CPLE_AppDefined,
2503 : "netCDF chunk fetch failed: #%d (%s)", status,
2504 : nc_strerror(status));
2505 0 : return false;
2506 : }
2507 5756 : return true;
2508 : }
2509 :
2510 : /************************************************************************/
2511 : /* IReadBlock() */
2512 : /************************************************************************/
2513 :
2514 5756 : CPLErr netCDFRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
2515 : void *pImage)
2516 :
2517 : {
2518 11512 : CPLMutexHolderD(&hNCMutex);
2519 :
2520 : // Locate X, Y and Z position in the array.
2521 :
2522 5756 : size_t xstart = static_cast<size_t>(nBlockXOff) * nBlockXSize;
2523 5756 : size_t ystart = 0;
2524 :
2525 : // Check y order.
2526 5756 : if (nBandYPos >= 0)
2527 : {
2528 5752 : auto poGDS = static_cast<netCDFDataset *>(poDS);
2529 5752 : if (poGDS->bBottomUp)
2530 : {
2531 4837 : if (nBlockYSize == 1)
2532 : {
2533 4824 : ystart = nRasterYSize - 1 - nBlockYOff;
2534 : }
2535 : else
2536 : {
2537 : // in GDAL space
2538 13 : ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
2539 : const size_t yend =
2540 26 : std::min(ystart + nBlockYSize - 1,
2541 13 : static_cast<size_t>(nRasterYSize - 1));
2542 : // in netCDF space
2543 13 : const size_t nFirstChunkLine = nRasterYSize - 1 - yend;
2544 13 : const size_t nLastChunkLine = nRasterYSize - 1 - ystart;
2545 13 : const size_t nFirstChunkBlock = nFirstChunkLine / nBlockYSize;
2546 13 : const size_t nLastChunkBlock = nLastChunkLine / nBlockYSize;
2547 :
2548 : const auto firstKey = netCDFDataset::ChunkKey(
2549 13 : nBlockXOff, nFirstChunkBlock, nBand);
2550 : const auto secondKey =
2551 13 : netCDFDataset::ChunkKey(nBlockXOff, nLastChunkBlock, nBand);
2552 :
2553 : // Retrieve data from the one or 2 needed netCDF chunks
2554 13 : std::shared_ptr<std::vector<GByte>> firstChunk;
2555 13 : std::shared_ptr<std::vector<GByte>> secondChunk;
2556 13 : if (poGDS->poChunkCache)
2557 : {
2558 13 : poGDS->poChunkCache->tryGet(firstKey, firstChunk);
2559 13 : if (firstKey != secondKey)
2560 6 : poGDS->poChunkCache->tryGet(secondKey, secondChunk);
2561 : }
2562 : const size_t nChunkLineSize =
2563 13 : static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
2564 13 : nBlockXSize;
2565 13 : const size_t nChunkSize = nChunkLineSize * nBlockYSize;
2566 13 : if (!firstChunk)
2567 : {
2568 11 : firstChunk.reset(new std::vector<GByte>(nChunkSize));
2569 11 : if (!FetchNetcdfChunk(xstart,
2570 11 : nFirstChunkBlock * nBlockYSize,
2571 11 : firstChunk.get()->data()))
2572 0 : return CE_Failure;
2573 11 : if (poGDS->poChunkCache)
2574 11 : poGDS->poChunkCache->insert(firstKey, firstChunk);
2575 : }
2576 13 : if (!secondChunk && firstKey != secondKey)
2577 : {
2578 2 : secondChunk.reset(new std::vector<GByte>(nChunkSize));
2579 2 : if (!FetchNetcdfChunk(xstart, nLastChunkBlock * nBlockYSize,
2580 2 : secondChunk.get()->data()))
2581 0 : return CE_Failure;
2582 2 : if (poGDS->poChunkCache)
2583 2 : poGDS->poChunkCache->insert(secondKey, secondChunk);
2584 : }
2585 :
2586 : // Assemble netCDF chunks into GDAL block
2587 13 : GByte *pabyImage = static_cast<GByte *>(pImage);
2588 13 : const size_t nFirstChunkBlockLine =
2589 13 : nFirstChunkBlock * nBlockYSize;
2590 13 : const size_t nLastChunkBlockLine =
2591 13 : nLastChunkBlock * nBlockYSize;
2592 146 : for (size_t iLine = ystart; iLine <= yend; iLine++)
2593 : {
2594 133 : const size_t nLineFromBottom = nRasterYSize - 1 - iLine;
2595 133 : const size_t nChunkY = nLineFromBottom / nBlockYSize;
2596 133 : if (nChunkY == nFirstChunkBlock)
2597 : {
2598 121 : memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
2599 121 : firstChunk.get()->data() +
2600 121 : (nLineFromBottom - nFirstChunkBlockLine) *
2601 : nChunkLineSize,
2602 : nChunkLineSize);
2603 : }
2604 : else
2605 : {
2606 12 : CPLAssert(nChunkY == nLastChunkBlock);
2607 12 : assert(secondChunk);
2608 12 : memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
2609 12 : secondChunk.get()->data() +
2610 12 : (nLineFromBottom - nLastChunkBlockLine) *
2611 : nChunkLineSize,
2612 : nChunkLineSize);
2613 : }
2614 : }
2615 13 : return CE_None;
2616 : }
2617 : }
2618 : else
2619 : {
2620 915 : ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
2621 : }
2622 : }
2623 :
2624 5743 : return FetchNetcdfChunk(xstart, ystart, pImage) ? CE_None : CE_Failure;
2625 : }
2626 :
2627 : /************************************************************************/
2628 : /* IWriteBlock() */
2629 : /************************************************************************/
2630 :
2631 6401 : CPLErr netCDFRasterBand::IWriteBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
2632 : void *pImage)
2633 : {
2634 12802 : CPLMutexHolderD(&hNCMutex);
2635 :
2636 : #ifdef NCDF_DEBUG
2637 : if (nBlockYOff == 0 || (nBlockYOff == nRasterYSize - 1))
2638 : CPLDebug("GDAL_netCDF",
2639 : "netCDFRasterBand::IWriteBlock( %d, %d, ...) nBand=%d",
2640 : nBlockXOff, nBlockYOff, nBand);
2641 : #endif
2642 :
2643 6401 : int nd = 0;
2644 6401 : nc_inq_varndims(cdfid, nZId, &nd);
2645 :
2646 : // Locate X, Y and Z position in the array.
2647 :
2648 : size_t start[MAX_NC_DIMS];
2649 6401 : memset(start, 0, sizeof(start));
2650 6401 : start[nBandXPos] = static_cast<size_t>(nBlockXOff) * nBlockXSize;
2651 :
2652 : // check y order.
2653 6401 : if (static_cast<netCDFDataset *>(poDS)->bBottomUp)
2654 : {
2655 6377 : if (nBlockYSize == 1)
2656 : {
2657 6377 : start[nBandYPos] = nRasterYSize - 1 - nBlockYOff;
2658 : }
2659 : else
2660 : {
2661 0 : CPLError(CE_Failure, CPLE_AppDefined,
2662 : "nBlockYSize = %d, only 1 supported when "
2663 : "writing bottom-up dataset",
2664 : nBlockYSize);
2665 0 : return CE_Failure;
2666 : }
2667 : }
2668 : else
2669 : {
2670 24 : start[nBandYPos] = static_cast<size_t>(nBlockYOff) * nBlockYSize; // y
2671 : }
2672 :
2673 6401 : size_t edge[MAX_NC_DIMS] = {};
2674 :
2675 6401 : edge[nBandXPos] = nBlockXSize;
2676 6401 : if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
2677 0 : edge[nBandXPos] = nRasterXSize - start[nBandXPos];
2678 6401 : edge[nBandYPos] = nBlockYSize;
2679 6401 : if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
2680 0 : edge[nBandYPos] = nRasterYSize - start[nBandYPos];
2681 :
2682 6401 : if (nd == 3)
2683 : {
2684 610 : start[panBandZPos[0]] = nLevel; // z
2685 610 : edge[panBandZPos[0]] = 1;
2686 : }
2687 :
2688 : // Compute multidimention band position.
2689 : //
2690 : // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
2691 : // if Data[2,3,4,x,y]
2692 : //
2693 : // BandPos0 = (nBand) / (3*4)
2694 : // BandPos1 = (nBand - (3*4)) / (4)
2695 : // BandPos2 = (nBand - (3*4)) % (4)
2696 6401 : if (nd > 3)
2697 : {
2698 178 : int Sum = -1;
2699 178 : int Taken = 0;
2700 534 : for (int i = 0; i < nd - 2; i++)
2701 : {
2702 356 : if (i != nd - 2 - 1)
2703 : {
2704 178 : Sum = 1;
2705 356 : for (int j = i + 1; j < nd - 2; j++)
2706 : {
2707 178 : Sum *= panBandZLev[j];
2708 : }
2709 178 : start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
2710 178 : edge[panBandZPos[i]] = 1;
2711 : }
2712 : else
2713 : {
2714 178 : start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
2715 178 : edge[panBandZPos[i]] = 1;
2716 : }
2717 356 : Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
2718 : }
2719 : }
2720 :
2721 : // Make sure we are in data mode.
2722 6401 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
2723 :
2724 : // Copy data according to type.
2725 6401 : int status = 0;
2726 6401 : if (eDataType == GDT_Byte)
2727 : {
2728 5842 : if (bSignedData)
2729 0 : status = nc_put_vara_schar(cdfid, nZId, start, edge,
2730 : static_cast<signed char *>(pImage));
2731 : else
2732 5842 : status = nc_put_vara_uchar(cdfid, nZId, start, edge,
2733 : static_cast<unsigned char *>(pImage));
2734 : }
2735 559 : else if (eDataType == GDT_Int8)
2736 : {
2737 40 : status = nc_put_vara_schar(cdfid, nZId, start, edge,
2738 : static_cast<signed char *>(pImage));
2739 : }
2740 519 : else if (nc_datatype == NC_SHORT)
2741 : {
2742 101 : status = nc_put_vara_short(cdfid, nZId, start, edge,
2743 : static_cast<short *>(pImage));
2744 : }
2745 418 : else if (eDataType == GDT_Int32)
2746 : {
2747 210 : status = nc_put_vara_int(cdfid, nZId, start, edge,
2748 : static_cast<int *>(pImage));
2749 : }
2750 208 : else if (eDataType == GDT_Float32)
2751 : {
2752 128 : status = nc_put_vara_float(cdfid, nZId, start, edge,
2753 : static_cast<float *>(pImage));
2754 : }
2755 80 : else if (eDataType == GDT_Float64)
2756 : {
2757 50 : status = nc_put_vara_double(cdfid, nZId, start, edge,
2758 : static_cast<double *>(pImage));
2759 : }
2760 30 : else if (eDataType == GDT_UInt16 &&
2761 12 : static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2762 : {
2763 12 : status = nc_put_vara_ushort(cdfid, nZId, start, edge,
2764 : static_cast<unsigned short *>(pImage));
2765 : }
2766 18 : else if (eDataType == GDT_UInt32 &&
2767 12 : static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2768 : {
2769 12 : status = nc_put_vara_uint(cdfid, nZId, start, edge,
2770 : static_cast<unsigned int *>(pImage));
2771 : }
2772 6 : else if (eDataType == GDT_UInt64 &&
2773 3 : static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2774 : {
2775 3 : status =
2776 3 : nc_put_vara_ulonglong(cdfid, nZId, start, edge,
2777 : static_cast<unsigned long long *>(pImage));
2778 : }
2779 3 : else if (eDataType == GDT_Int64 &&
2780 3 : static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2781 : {
2782 3 : status = nc_put_vara_longlong(cdfid, nZId, start, edge,
2783 : static_cast<long long *>(pImage));
2784 : }
2785 : else
2786 : {
2787 0 : CPLError(CE_Failure, CPLE_NotSupported,
2788 : "The NetCDF driver does not support GDAL data type %d",
2789 0 : eDataType);
2790 0 : status = NC_EBADTYPE;
2791 : }
2792 6401 : NCDF_ERR(status);
2793 :
2794 6401 : if (status != NC_NOERR)
2795 : {
2796 0 : CPLError(CE_Failure, CPLE_AppDefined,
2797 : "netCDF scanline write failed: %s", nc_strerror(status));
2798 0 : return CE_Failure;
2799 : }
2800 :
2801 6401 : return CE_None;
2802 : }
2803 :
2804 : /************************************************************************/
2805 : /* ==================================================================== */
2806 : /* netCDFDataset */
2807 : /* ==================================================================== */
2808 : /************************************************************************/
2809 :
2810 : /************************************************************************/
2811 : /* netCDFDataset() */
2812 : /************************************************************************/
2813 :
2814 1002 : netCDFDataset::netCDFDataset()
2815 : :
2816 : // Basic dataset vars.
2817 : #ifdef ENABLE_NCDUMP
2818 : bFileToDestroyAtClosing(false),
2819 : #endif
2820 : cdfid(-1), nSubDatasets(0), papszSubDatasets(nullptr),
2821 : papszMetadata(nullptr), bBottomUp(true), eFormat(NCDF_FORMAT_NONE),
2822 : bIsGdalFile(false), bIsGdalCfFile(false), pszCFProjection(nullptr),
2823 : pszCFCoordinates(nullptr), nCFVersion(1.6), bSGSupport(false),
2824 1002 : eMultipleLayerBehavior(SINGLE_LAYER), logCount(0), vcdf(this, cdfid),
2825 1002 : GeometryScribe(vcdf, this->generateLogName()),
2826 1002 : FieldScribe(vcdf, this->generateLogName()),
2827 2004 : bufManager(CPLGetUsablePhysicalRAM() / 5),
2828 :
2829 : // projection/GT.
2830 : nXDimID(-1), nYDimID(-1), bIsProjected(false),
2831 : bIsGeographic(false), // Can be not projected, and also not geographic
2832 : // State vars.
2833 : bDefineMode(true), bAddedGridMappingRef(false),
2834 :
2835 : // Create vars.
2836 : papszCreationOptions(nullptr), eCompress(NCDF_COMPRESS_NONE),
2837 : nZLevel(NCDF_DEFLATE_LEVEL), bChunking(false), nCreateMode(NC_CLOBBER),
2838 3006 : bSignedData(true)
2839 : {
2840 1002 : m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2841 :
2842 : // Projection/GT.
2843 1002 : m_adfGeoTransform[0] = 0.0;
2844 1002 : m_adfGeoTransform[1] = 1.0;
2845 1002 : m_adfGeoTransform[2] = 0.0;
2846 1002 : m_adfGeoTransform[3] = 0.0;
2847 1002 : m_adfGeoTransform[4] = 0.0;
2848 1002 : m_adfGeoTransform[5] = 1.0;
2849 :
2850 : // Set buffers
2851 1002 : bufManager.addBuffer(&(GeometryScribe.getMemBuffer()));
2852 1002 : bufManager.addBuffer(&(FieldScribe.getMemBuffer()));
2853 1002 : }
2854 :
2855 : /************************************************************************/
2856 : /* ~netCDFDataset() */
2857 : /************************************************************************/
2858 :
2859 1930 : netCDFDataset::~netCDFDataset()
2860 :
2861 : {
2862 1002 : netCDFDataset::Close();
2863 1930 : }
2864 :
2865 : /************************************************************************/
2866 : /* Close() */
2867 : /************************************************************************/
2868 :
2869 1746 : CPLErr netCDFDataset::Close()
2870 : {
2871 1746 : CPLErr eErr = CE_None;
2872 1746 : if (nOpenFlags != OPEN_FLAGS_CLOSED)
2873 : {
2874 2004 : CPLMutexHolderD(&hNCMutex);
2875 :
2876 : #ifdef NCDF_DEBUG
2877 : CPLDebug("GDAL_netCDF",
2878 : "netCDFDataset::~netCDFDataset(), cdfid=%d filename=%s", cdfid,
2879 : osFilename.c_str());
2880 : #endif
2881 :
2882 : // Write data related to geotransform
2883 1233 : if (GetAccess() == GA_Update && !m_bAddedProjectionVarsData &&
2884 231 : (m_bHasProjection || m_bHasGeoTransform))
2885 : {
2886 : // Ensure projection is written if GeoTransform OR Projection are
2887 : // missing.
2888 37 : if (!m_bAddedProjectionVarsDefs)
2889 : {
2890 2 : AddProjectionVars(true, nullptr, nullptr);
2891 : }
2892 37 : AddProjectionVars(false, nullptr, nullptr);
2893 : }
2894 :
2895 1002 : if (netCDFDataset::FlushCache(true) != CE_None)
2896 0 : eErr = CE_Failure;
2897 :
2898 1002 : if (GetAccess() == GA_Update && !SGCommitPendingTransaction())
2899 0 : eErr = CE_Failure;
2900 :
2901 1004 : for (size_t i = 0; i < apoVectorDatasets.size(); i++)
2902 2 : delete apoVectorDatasets[i];
2903 :
2904 : // Make sure projection variable is written to band variable.
2905 1002 : if (GetAccess() == GA_Update && !bAddedGridMappingRef)
2906 : {
2907 248 : if (!AddGridMappingRef())
2908 0 : eErr = CE_Failure;
2909 : }
2910 :
2911 1002 : CSLDestroy(papszMetadata);
2912 1002 : CSLDestroy(papszSubDatasets);
2913 1002 : CSLDestroy(papszCreationOptions);
2914 :
2915 1002 : CPLFree(pszCFProjection);
2916 :
2917 1002 : if (cdfid > 0)
2918 : {
2919 : #ifdef NCDF_DEBUG
2920 : CPLDebug("GDAL_netCDF", "calling nc_close( %d)", cdfid);
2921 : #endif
2922 657 : int status = GDAL_nc_close(cdfid);
2923 : #ifdef ENABLE_UFFD
2924 657 : NETCDF_UFFD_UNMAP(pCtx);
2925 : #endif
2926 657 : NCDF_ERR(status);
2927 657 : if (status != NC_NOERR)
2928 0 : eErr = CE_Failure;
2929 : }
2930 :
2931 1002 : if (fpVSIMEM)
2932 15 : VSIFCloseL(fpVSIMEM);
2933 :
2934 : #ifdef ENABLE_NCDUMP
2935 1002 : if (bFileToDestroyAtClosing)
2936 0 : VSIUnlink(osFilename);
2937 : #endif
2938 :
2939 1002 : if (GDALPamDataset::Close() != CE_None)
2940 0 : eErr = CE_Failure;
2941 : }
2942 1746 : return eErr;
2943 : }
2944 :
2945 : /************************************************************************/
2946 : /* SetDefineMode() */
2947 : /************************************************************************/
2948 14225 : bool netCDFDataset::SetDefineMode(bool bNewDefineMode)
2949 : {
2950 : // Do nothing if already in new define mode
2951 : // or if dataset is in read-only mode or if dataset is true NC4 dataset.
2952 14784 : if (bDefineMode == bNewDefineMode || GetAccess() == GA_ReadOnly ||
2953 559 : eFormat == NCDF_FORMAT_NC4)
2954 13813 : return true;
2955 :
2956 412 : CPLDebug("GDAL_netCDF", "SetDefineMode(%d) old=%d",
2957 412 : static_cast<int>(bNewDefineMode), static_cast<int>(bDefineMode));
2958 :
2959 412 : bDefineMode = bNewDefineMode;
2960 :
2961 : int status;
2962 412 : if (bDefineMode)
2963 143 : status = nc_redef(cdfid);
2964 : else
2965 269 : status = nc_enddef(cdfid);
2966 :
2967 412 : NCDF_ERR(status);
2968 412 : return status == NC_NOERR;
2969 : }
2970 :
2971 : /************************************************************************/
2972 : /* GetMetadataDomainList() */
2973 : /************************************************************************/
2974 :
2975 27 : char **netCDFDataset::GetMetadataDomainList()
2976 : {
2977 27 : char **papszDomains = BuildMetadataDomainList(
2978 : GDALDataset::GetMetadataDomainList(), TRUE, "SUBDATASETS", nullptr);
2979 28 : for (const auto &kv : m_oMapDomainToJSon)
2980 1 : papszDomains = CSLAddString(papszDomains, ("json:" + kv.first).c_str());
2981 27 : return papszDomains;
2982 : }
2983 :
2984 : /************************************************************************/
2985 : /* GetMetadata() */
2986 : /************************************************************************/
2987 368 : char **netCDFDataset::GetMetadata(const char *pszDomain)
2988 : {
2989 368 : if (pszDomain != nullptr && STARTS_WITH_CI(pszDomain, "SUBDATASETS"))
2990 38 : return papszSubDatasets;
2991 :
2992 330 : if (pszDomain != nullptr && STARTS_WITH(pszDomain, "json:"))
2993 : {
2994 1 : auto iter = m_oMapDomainToJSon.find(pszDomain + strlen("json:"));
2995 1 : if (iter != m_oMapDomainToJSon.end())
2996 1 : return iter->second.List();
2997 : }
2998 :
2999 329 : return GDALDataset::GetMetadata(pszDomain);
3000 : }
3001 :
3002 : /************************************************************************/
3003 : /* SetMetadataItem() */
3004 : /************************************************************************/
3005 :
3006 43 : CPLErr netCDFDataset::SetMetadataItem(const char *pszName, const char *pszValue,
3007 : const char *pszDomain)
3008 : {
3009 85 : if (GetAccess() == GA_Update &&
3010 85 : (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)
3011 : {
3012 42 : std::string osName(pszName);
3013 :
3014 : // Same logic as in CopyMetadata()
3015 42 : if (STARTS_WITH(osName.c_str(), "NC_GLOBAL#"))
3016 8 : osName = osName.substr(strlen("NC_GLOBAL#"));
3017 34 : else if (strchr(osName.c_str(), '#') == nullptr)
3018 5 : osName = "GDAL_" + osName;
3019 :
3020 84 : if (STARTS_WITH(osName.c_str(), "NETCDF_DIM_") ||
3021 42 : strchr(osName.c_str(), '#') != nullptr)
3022 : {
3023 : // do nothing
3024 29 : return CE_None;
3025 : }
3026 : else
3027 : {
3028 13 : SetDefineMode(true);
3029 :
3030 13 : if (!NCDFPutAttr(cdfid, NC_GLOBAL, osName.c_str(), pszValue))
3031 13 : return CE_Failure;
3032 : }
3033 : }
3034 :
3035 1 : return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);
3036 : }
3037 :
3038 : /************************************************************************/
3039 : /* SetMetadata() */
3040 : /************************************************************************/
3041 :
3042 8 : CPLErr netCDFDataset::SetMetadata(char **papszMD, const char *pszDomain)
3043 : {
3044 13 : if (GetAccess() == GA_Update &&
3045 5 : (pszDomain == nullptr || pszDomain[0] == '\0'))
3046 : {
3047 : // We don't handle metadata item removal for now
3048 50 : for (const char *const *papszIter = papszMD; papszIter && *papszIter;
3049 : ++papszIter)
3050 : {
3051 42 : char *pszName = nullptr;
3052 42 : const char *pszValue = CPLParseNameValue(*papszIter, &pszName);
3053 42 : if (pszName && pszValue)
3054 42 : SetMetadataItem(pszName, pszValue);
3055 42 : CPLFree(pszName);
3056 : }
3057 8 : return CE_None;
3058 : }
3059 0 : return GDALPamDataset::SetMetadata(papszMD, pszDomain);
3060 : }
3061 :
3062 : /************************************************************************/
3063 : /* GetSpatialRef() */
3064 : /************************************************************************/
3065 :
3066 183 : const OGRSpatialReference *netCDFDataset::GetSpatialRef() const
3067 : {
3068 183 : if (m_bHasProjection)
3069 74 : return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
3070 :
3071 109 : return GDALPamDataset::GetSpatialRef();
3072 : }
3073 :
3074 : /************************************************************************/
3075 : /* FetchCopyParam() */
3076 : /************************************************************************/
3077 :
3078 436 : double netCDFDataset::FetchCopyParam(const char *pszGridMappingValue,
3079 : const char *pszParam, double dfDefault,
3080 : bool *pbFound)
3081 :
3082 : {
3083 : char *pszTemp =
3084 436 : CPLStrdup(CPLSPrintf("%s#%s", pszGridMappingValue, pszParam));
3085 436 : const char *pszValue = CSLFetchNameValue(papszMetadata, pszTemp);
3086 436 : CPLFree(pszTemp);
3087 :
3088 436 : if (pbFound)
3089 : {
3090 436 : *pbFound = (pszValue != nullptr);
3091 : }
3092 :
3093 436 : if (pszValue)
3094 : {
3095 0 : return CPLAtofM(pszValue);
3096 : }
3097 :
3098 436 : return dfDefault;
3099 : }
3100 :
3101 : /************************************************************************/
3102 : /* FetchStandardParallels() */
3103 : /************************************************************************/
3104 :
3105 : std::vector<std::string>
3106 0 : netCDFDataset::FetchStandardParallels(const char *pszGridMappingValue)
3107 : {
3108 : // cf-1.0 tags
3109 0 : const char *pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL);
3110 :
3111 0 : std::vector<std::string> ret;
3112 0 : if (pszValue != nullptr)
3113 : {
3114 0 : CPLStringList aosValues;
3115 0 : if (pszValue[0] != '{' &&
3116 0 : CPLString(pszValue).Trim().find(' ') != std::string::npos)
3117 : {
3118 : // Some files like
3119 : // ftp://data.knmi.nl/download/KNW-NetCDF-3D/1.0/noversion/2013/11/14/KNW-1.0_H37-ERA_NL_20131114.nc
3120 : // do not use standard formatting for arrays, but just space
3121 : // separated syntax
3122 0 : aosValues = CSLTokenizeString2(pszValue, " ", 0);
3123 : }
3124 : else
3125 : {
3126 0 : aosValues = NCDFTokenizeArray(pszValue);
3127 : }
3128 0 : for (int i = 0; i < aosValues.size(); i++)
3129 : {
3130 0 : ret.push_back(aosValues[i]);
3131 : }
3132 : }
3133 : // Try gdal tags.
3134 : else
3135 : {
3136 0 : pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_1);
3137 :
3138 0 : if (pszValue != nullptr)
3139 0 : ret.push_back(pszValue);
3140 :
3141 0 : pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_2);
3142 :
3143 0 : if (pszValue != nullptr)
3144 0 : ret.push_back(pszValue);
3145 : }
3146 :
3147 0 : return ret;
3148 : }
3149 :
3150 : /************************************************************************/
3151 : /* FetchAttr() */
3152 : /************************************************************************/
3153 :
3154 3697 : const char *netCDFDataset::FetchAttr(const char *pszVarFullName,
3155 : const char *pszAttr)
3156 :
3157 : {
3158 3697 : char *pszKey = CPLStrdup(CPLSPrintf("%s#%s", pszVarFullName, pszAttr));
3159 3697 : const char *pszValue = CSLFetchNameValue(papszMetadata, pszKey);
3160 3697 : CPLFree(pszKey);
3161 3697 : return pszValue;
3162 : }
3163 :
3164 2450 : const char *netCDFDataset::FetchAttr(int nGroupId, int nVarId,
3165 : const char *pszAttr)
3166 :
3167 : {
3168 2450 : char *pszVarFullName = nullptr;
3169 2450 : NCDFGetVarFullName(nGroupId, nVarId, &pszVarFullName);
3170 2450 : const char *pszValue = FetchAttr(pszVarFullName, pszAttr);
3171 2450 : CPLFree(pszVarFullName);
3172 2450 : return pszValue;
3173 : }
3174 :
3175 : /************************************************************************/
3176 : /* IsDifferenceBelow() */
3177 : /************************************************************************/
3178 :
3179 1085 : static bool IsDifferenceBelow(double dfA, double dfB, double dfError)
3180 : {
3181 1085 : const double dfAbsDiff = fabs(dfA - dfB);
3182 1085 : return dfAbsDiff <= dfError;
3183 : }
3184 :
3185 : /************************************************************************/
3186 : /* SetProjectionFromVar() */
3187 : /************************************************************************/
3188 513 : void netCDFDataset::SetProjectionFromVar(
3189 : int nGroupId, int nVarId, bool bReadSRSOnly, const char *pszGivenGM,
3190 : std::string *returnProjStr, nccfdriver::SGeometry_Reader *sg,
3191 : std::vector<std::string> *paosRemovedMDItems)
3192 : {
3193 513 : bool bGotGeogCS = false;
3194 513 : bool bGotCfSRS = false;
3195 513 : bool bGotCfWktSRS = false;
3196 513 : bool bGotGdalSRS = false;
3197 513 : bool bGotCfGT = false;
3198 513 : bool bGotGdalGT = false;
3199 :
3200 : // These values from CF metadata.
3201 513 : OGRSpatialReference oSRS;
3202 513 : oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3203 513 : size_t xdim = nRasterXSize;
3204 513 : size_t ydim = nRasterYSize;
3205 :
3206 : // These values from GDAL metadata.
3207 513 : const char *pszWKT = nullptr;
3208 513 : const char *pszGeoTransform = nullptr;
3209 :
3210 513 : netCDFDataset *poDS = this; // Perhaps this should be removed for clarity.
3211 :
3212 513 : CPLDebug("GDAL_netCDF", "\n=====\nSetProjectionFromVar( %d, %d)", nGroupId,
3213 : nVarId);
3214 :
3215 : // Get x/y range information.
3216 :
3217 : // Temp variables to use in SetGeoTransform() and SetProjection().
3218 513 : double adfTempGeoTransform[6] = {0.0, 1.0, 0.0, 0.0, 0.0, 1.0};
3219 :
3220 : // Look for grid_mapping metadata.
3221 513 : const char *pszValue = pszGivenGM;
3222 513 : CPLString osTmpGridMapping; // let is in this outer scope as pszValue may
3223 : // point to it
3224 513 : if (pszValue == nullptr)
3225 : {
3226 470 : pszValue = FetchAttr(nGroupId, nVarId, CF_GRD_MAPPING);
3227 470 : if (pszValue && strchr(pszValue, ':') && strchr(pszValue, ' '))
3228 : {
3229 : // Expanded form of grid_mapping
3230 : // e.g. "crsOSGB: x y crsWGS84: lat lon"
3231 : // Pickup the grid_mapping whose coordinates are dimensions of the
3232 : // variable
3233 6 : CPLStringList aosTokens(CSLTokenizeString2(pszValue, " ", 0));
3234 3 : if ((aosTokens.size() % 3) == 0)
3235 : {
3236 3 : for (int i = 0; i < aosTokens.size() / 3; i++)
3237 : {
3238 3 : if (CSLFindString(poDS->papszDimName,
3239 9 : aosTokens[3 * i + 1]) >= 0 &&
3240 3 : CSLFindString(poDS->papszDimName,
3241 3 : aosTokens[3 * i + 2]) >= 0)
3242 : {
3243 3 : osTmpGridMapping = aosTokens[3 * i];
3244 6 : if (!osTmpGridMapping.empty() &&
3245 3 : osTmpGridMapping.back() == ':')
3246 : {
3247 3 : osTmpGridMapping.resize(osTmpGridMapping.size() -
3248 : 1);
3249 : }
3250 3 : pszValue = osTmpGridMapping.c_str();
3251 3 : break;
3252 : }
3253 : }
3254 : }
3255 : }
3256 : }
3257 513 : char *pszGridMappingValue = CPLStrdup(pszValue ? pszValue : "");
3258 :
3259 513 : if (!EQUAL(pszGridMappingValue, ""))
3260 : {
3261 : // Read grid_mapping metadata.
3262 219 : int nProjGroupID = -1;
3263 219 : int nProjVarID = -1;
3264 219 : if (NCDFResolveVar(nGroupId, pszGridMappingValue, &nProjGroupID,
3265 219 : &nProjVarID) == CE_None)
3266 : {
3267 218 : poDS->ReadAttributes(nProjGroupID, nProjVarID);
3268 :
3269 : // Look for GDAL spatial_ref and GeoTransform within grid_mapping.
3270 218 : CPLFree(pszGridMappingValue);
3271 218 : pszGridMappingValue = nullptr;
3272 218 : NCDFGetVarFullName(nProjGroupID, nProjVarID, &pszGridMappingValue);
3273 218 : if (pszGridMappingValue)
3274 : {
3275 218 : CPLDebug("GDAL_netCDF", "got grid_mapping %s",
3276 : pszGridMappingValue);
3277 218 : pszWKT = FetchAttr(pszGridMappingValue, NCDF_SPATIAL_REF);
3278 218 : if (!pszWKT)
3279 : {
3280 34 : pszWKT = FetchAttr(pszGridMappingValue, NCDF_CRS_WKT);
3281 : }
3282 : else
3283 : {
3284 184 : bGotGdalSRS = true;
3285 184 : CPLDebug("GDAL_netCDF", "setting WKT from GDAL");
3286 : }
3287 218 : if (pszWKT)
3288 : {
3289 188 : if (!bGotGdalSRS)
3290 : {
3291 4 : bGotCfWktSRS = true;
3292 4 : CPLDebug("GDAL_netCDF", "setting WKT from CF");
3293 : }
3294 188 : if (returnProjStr != nullptr)
3295 : {
3296 41 : (*returnProjStr) = std::string(pszWKT);
3297 : }
3298 : else
3299 : {
3300 147 : m_bAddedProjectionVarsDefs = true;
3301 147 : m_bAddedProjectionVarsData = true;
3302 294 : OGRSpatialReference oSRSTmp;
3303 147 : oSRSTmp.SetAxisMappingStrategy(
3304 : OAMS_TRADITIONAL_GIS_ORDER);
3305 147 : oSRSTmp.importFromWkt(pszWKT);
3306 147 : SetSpatialRefNoUpdate(&oSRSTmp);
3307 : }
3308 : pszGeoTransform =
3309 188 : FetchAttr(pszGridMappingValue, NCDF_GEOTRANSFORM);
3310 : }
3311 : }
3312 : else
3313 : {
3314 0 : pszGridMappingValue = CPLStrdup("");
3315 : }
3316 : }
3317 : }
3318 :
3319 : // Get information about the file.
3320 : //
3321 : // Was this file created by the GDAL netcdf driver?
3322 : // Was this file created by the newer (CF-conformant) driver?
3323 : //
3324 : // 1) If GDAL netcdf metadata is set, and version >= 1.9,
3325 : // it was created with the new driver
3326 : // 2) Else, if spatial_ref and GeoTransform are present in the
3327 : // grid_mapping variable, it was created by the old driver
3328 513 : pszValue = FetchAttr("NC_GLOBAL", "GDAL");
3329 :
3330 513 : if (pszValue && NCDFIsGDALVersionGTE(pszValue, 1900))
3331 : {
3332 241 : bIsGdalFile = true;
3333 241 : bIsGdalCfFile = true;
3334 : }
3335 272 : else if (pszWKT != nullptr && pszGeoTransform != nullptr)
3336 : {
3337 17 : bIsGdalFile = true;
3338 17 : bIsGdalCfFile = false;
3339 : }
3340 :
3341 : // Set default bottom-up default value.
3342 : // Y axis dimension and absence of GT can modify this value.
3343 : // Override with Config option GDAL_NETCDF_BOTTOMUP.
3344 :
3345 : // New driver is bottom-up by default.
3346 513 : if ((bIsGdalFile && !bIsGdalCfFile) || bSwitchedXY)
3347 19 : poDS->bBottomUp = false;
3348 : else
3349 494 : poDS->bBottomUp = true;
3350 :
3351 513 : CPLDebug("GDAL_netCDF",
3352 : "bIsGdalFile=%d bIsGdalCfFile=%d bSwitchedXY=%d bBottomUp=%d",
3353 513 : static_cast<int>(bIsGdalFile), static_cast<int>(bIsGdalCfFile),
3354 513 : static_cast<int>(bSwitchedXY), static_cast<int>(bBottomUp));
3355 :
3356 : // Read projection coordinates.
3357 :
3358 513 : int nGroupDimXID = -1;
3359 513 : int nVarDimXID = -1;
3360 513 : int nGroupDimYID = -1;
3361 513 : int nVarDimYID = -1;
3362 513 : if (sg != nullptr)
3363 : {
3364 43 : nGroupDimXID = sg->get_ncID();
3365 43 : nGroupDimYID = sg->get_ncID();
3366 43 : nVarDimXID = sg->getNodeCoordVars()[0];
3367 43 : nVarDimYID = sg->getNodeCoordVars()[1];
3368 : }
3369 :
3370 513 : if (!bReadSRSOnly)
3371 : {
3372 347 : NCDFResolveVar(nGroupId, poDS->papszDimName[nXDimID], &nGroupDimXID,
3373 : &nVarDimXID);
3374 347 : NCDFResolveVar(nGroupId, poDS->papszDimName[nYDimID], &nGroupDimYID,
3375 : &nVarDimYID);
3376 : // TODO: if above resolving fails we should also search for coordinate
3377 : // variables without same name than dimension using the same resolving
3378 : // logic. This should handle for example NASA Ocean Color L2 products.
3379 :
3380 : const bool bIgnoreXYAxisNameChecks =
3381 694 : CPLTestBool(CSLFetchNameValueDef(
3382 347 : papszOpenOptions, "IGNORE_XY_AXIS_NAME_CHECKS",
3383 : CPLGetConfigOption("GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS",
3384 347 : "NO"))) ||
3385 : // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a res
3386 : // and transform attributes
3387 347 : (FetchAttr(nGroupId, nVarId, "res") != nullptr &&
3388 694 : FetchAttr(nGroupId, nVarId, "transform") != nullptr) ||
3389 346 : FetchAttr(nGroupId, NC_GLOBAL, "GMT_version") != nullptr;
3390 :
3391 : // Check that they are 1D or 2D variables
3392 347 : if (nVarDimXID >= 0)
3393 : {
3394 253 : int ndims = -1;
3395 253 : nc_inq_varndims(nGroupId, nVarDimXID, &ndims);
3396 253 : if (ndims == 0 || ndims > 2)
3397 0 : nVarDimXID = -1;
3398 253 : else if (!bIgnoreXYAxisNameChecks)
3399 : {
3400 251 : if (!NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
3401 161 : !NCDFIsVarProjectionX(nGroupId, nVarDimXID, nullptr) &&
3402 : // In case of inversion of X/Y
3403 443 : !NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr) &&
3404 31 : !NCDFIsVarProjectionY(nGroupId, nVarDimXID, nullptr))
3405 : {
3406 : char szVarNameX[NC_MAX_NAME + 1];
3407 31 : CPL_IGNORE_RET_VAL(
3408 31 : nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
3409 31 : if (!(ndims == 1 &&
3410 30 : (EQUAL(szVarNameX, CF_LONGITUDE_STD_NAME) ||
3411 29 : EQUAL(szVarNameX, CF_LONGITUDE_VAR_NAME))))
3412 : {
3413 30 : CPLDebug(
3414 : "netCDF",
3415 : "Georeferencing ignored due to non-specific "
3416 : "enough X axis name. "
3417 : "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
3418 : "as configuration option to bypass this check");
3419 30 : nVarDimXID = -1;
3420 : }
3421 : }
3422 : }
3423 : }
3424 :
3425 347 : if (nVarDimYID >= 0)
3426 : {
3427 255 : int ndims = -1;
3428 255 : nc_inq_varndims(nGroupId, nVarDimYID, &ndims);
3429 255 : if (ndims == 0 || ndims > 2)
3430 1 : nVarDimYID = -1;
3431 254 : else if (!bIgnoreXYAxisNameChecks)
3432 : {
3433 252 : if (!NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr) &&
3434 162 : !NCDFIsVarProjectionY(nGroupId, nVarDimYID, nullptr) &&
3435 : // In case of inversion of X/Y
3436 446 : !NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
3437 32 : !NCDFIsVarProjectionX(nGroupId, nVarDimYID, nullptr))
3438 : {
3439 : char szVarNameY[NC_MAX_NAME + 1];
3440 32 : CPL_IGNORE_RET_VAL(
3441 32 : nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
3442 32 : if (!(ndims == 1 &&
3443 32 : (EQUAL(szVarNameY, CF_LATITUDE_STD_NAME) ||
3444 31 : EQUAL(szVarNameY, CF_LATITUDE_VAR_NAME))))
3445 : {
3446 31 : CPLDebug(
3447 : "netCDF",
3448 : "Georeferencing ignored due to non-specific "
3449 : "enough Y axis name. "
3450 : "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
3451 : "as configuration option to bypass this check");
3452 31 : nVarDimYID = -1;
3453 : }
3454 : }
3455 : }
3456 : }
3457 :
3458 347 : if ((nVarDimXID >= 0 && xdim == 1) || (nVarDimXID >= 0 && ydim == 1))
3459 : {
3460 0 : CPLError(CE_Warning, CPLE_AppDefined,
3461 : "1-pixel width/height files not supported, "
3462 : "xdim: %ld ydim: %ld",
3463 : static_cast<long>(xdim), static_cast<long>(ydim));
3464 0 : nVarDimXID = -1;
3465 0 : nVarDimYID = -1;
3466 : }
3467 : }
3468 :
3469 513 : const char *pszUnits = nullptr;
3470 513 : if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
3471 : {
3472 266 : const char *pszUnitsX = FetchAttr(nGroupDimXID, nVarDimXID, "units");
3473 266 : const char *pszUnitsY = FetchAttr(nGroupDimYID, nVarDimYID, "units");
3474 : // Normalize degrees_east/degrees_north to degrees
3475 : // Cf https://github.com/OSGeo/gdal/issues/11009
3476 266 : if (pszUnitsX && EQUAL(pszUnitsX, "degrees_east"))
3477 79 : pszUnitsX = "degrees";
3478 266 : if (pszUnitsY && EQUAL(pszUnitsY, "degrees_north"))
3479 79 : pszUnitsY = "degrees";
3480 :
3481 266 : if (pszUnitsX && pszUnitsY)
3482 : {
3483 219 : if (EQUAL(pszUnitsX, pszUnitsY))
3484 216 : pszUnits = pszUnitsX;
3485 3 : else if (!pszWKT && !EQUAL(pszGridMappingValue, ""))
3486 : {
3487 0 : CPLError(CE_Failure, CPLE_AppDefined,
3488 : "X axis unit (%s) is different from Y axis "
3489 : "unit (%s). SRS will ignore axis unit and be "
3490 : "likely wrong.",
3491 : pszUnitsX, pszUnitsY);
3492 : }
3493 : }
3494 47 : else if (pszUnitsX && !pszWKT && !EQUAL(pszGridMappingValue, ""))
3495 : {
3496 0 : CPLError(CE_Failure, CPLE_AppDefined,
3497 : "X axis unit is defined, but not Y one ."
3498 : "SRS will ignore axis unit and be likely wrong.");
3499 : }
3500 47 : else if (pszUnitsY && !pszWKT && !EQUAL(pszGridMappingValue, ""))
3501 : {
3502 0 : CPLError(CE_Failure, CPLE_AppDefined,
3503 : "Y axis unit is defined, but not X one ."
3504 : "SRS will ignore axis unit and be likely wrong.");
3505 : }
3506 : }
3507 :
3508 513 : if (!pszWKT && !EQUAL(pszGridMappingValue, ""))
3509 : {
3510 31 : CPLStringList aosGridMappingKeyValues;
3511 31 : const size_t nLenGridMappingValue = strlen(pszGridMappingValue);
3512 789 : for (const char *const *papszIter = papszMetadata;
3513 789 : papszIter && *papszIter; ++papszIter)
3514 : {
3515 758 : if (STARTS_WITH(*papszIter, pszGridMappingValue) &&
3516 236 : (*papszIter)[nLenGridMappingValue] == '#')
3517 : {
3518 236 : char *pszKey = nullptr;
3519 472 : pszValue = CPLParseNameValue(
3520 236 : *papszIter + nLenGridMappingValue + 1, &pszKey);
3521 236 : if (pszKey && pszValue)
3522 236 : aosGridMappingKeyValues.SetNameValue(pszKey, pszValue);
3523 236 : CPLFree(pszKey);
3524 : }
3525 : }
3526 :
3527 31 : bGotGeogCS = aosGridMappingKeyValues.FetchNameValue(
3528 : CF_PP_SEMI_MAJOR_AXIS) != nullptr;
3529 :
3530 31 : oSRS.importFromCF1(aosGridMappingKeyValues.List(), pszUnits);
3531 31 : bGotCfSRS = oSRS.IsGeographic() || oSRS.IsProjected();
3532 : }
3533 : else
3534 : {
3535 : // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a "crs"
3536 : // attribute hold on the variable of interest that contains a PROJ.4
3537 : // string
3538 482 : pszValue = FetchAttr(nGroupId, nVarId, "crs");
3539 483 : if (pszValue &&
3540 1 : (strstr(pszValue, "+proj=") != nullptr ||
3541 0 : strstr(pszValue, "GEOGCS") != nullptr ||
3542 0 : strstr(pszValue, "PROJCS") != nullptr ||
3543 483 : strstr(pszValue, "EPSG:") != nullptr) &&
3544 1 : oSRS.SetFromUserInput(pszValue) == OGRERR_NONE)
3545 : {
3546 1 : bGotCfSRS = true;
3547 : }
3548 : }
3549 :
3550 : // Set Projection from CF.
3551 513 : double dfLinearUnitsConvFactor = 1.0;
3552 513 : if ((bGotGeogCS || bGotCfSRS))
3553 : {
3554 31 : if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
3555 : {
3556 : // Set SRS Units.
3557 :
3558 : // Check units for x and y.
3559 28 : if (oSRS.IsProjected())
3560 : {
3561 25 : dfLinearUnitsConvFactor = oSRS.GetLinearUnits(nullptr);
3562 :
3563 : // If the user doesn't ask to preserve the axis unit,
3564 : // then normalize to metre
3565 31 : if (dfLinearUnitsConvFactor != 1.0 &&
3566 6 : !CPLFetchBool(GetOpenOptions(), "PRESERVE_AXIS_UNIT_IN_CRS",
3567 : false))
3568 : {
3569 5 : oSRS.SetLinearUnits("metre", 1.0);
3570 5 : oSRS.SetAuthority("PROJCS|UNIT", "EPSG", 9001);
3571 : }
3572 : else
3573 : {
3574 20 : dfLinearUnitsConvFactor = 1.0;
3575 : }
3576 : }
3577 : }
3578 :
3579 : // Set projection.
3580 31 : char *pszTempProjection = nullptr;
3581 31 : oSRS.exportToWkt(&pszTempProjection);
3582 31 : if (pszTempProjection)
3583 : {
3584 31 : CPLDebug("GDAL_netCDF", "setting WKT from CF");
3585 31 : if (returnProjStr != nullptr)
3586 : {
3587 2 : (*returnProjStr) = std::string(pszTempProjection);
3588 : }
3589 : else
3590 : {
3591 29 : m_bAddedProjectionVarsDefs = true;
3592 29 : m_bAddedProjectionVarsData = true;
3593 29 : SetSpatialRefNoUpdate(&oSRS);
3594 : }
3595 : }
3596 31 : CPLFree(pszTempProjection);
3597 : }
3598 :
3599 513 : if (!bReadSRSOnly && (nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 &&
3600 : ydim > 0)
3601 : {
3602 : double *pdfXCoord =
3603 223 : static_cast<double *>(CPLCalloc(xdim, sizeof(double)));
3604 : double *pdfYCoord =
3605 223 : static_cast<double *>(CPLCalloc(ydim, sizeof(double)));
3606 :
3607 223 : size_t start[2] = {0, 0};
3608 223 : size_t edge[2] = {xdim, 0};
3609 223 : int status = nc_get_vara_double(nGroupDimXID, nVarDimXID, start, edge,
3610 : pdfXCoord);
3611 223 : NCDF_ERR(status);
3612 :
3613 223 : edge[0] = ydim;
3614 223 : status = nc_get_vara_double(nGroupDimYID, nVarDimYID, start, edge,
3615 : pdfYCoord);
3616 223 : NCDF_ERR(status);
3617 :
3618 223 : nc_type nc_var_dimx_datatype = NC_NAT;
3619 : status =
3620 223 : nc_inq_vartype(nGroupDimXID, nVarDimXID, &nc_var_dimx_datatype);
3621 223 : NCDF_ERR(status);
3622 :
3623 223 : nc_type nc_var_dimy_datatype = NC_NAT;
3624 : status =
3625 223 : nc_inq_vartype(nGroupDimYID, nVarDimYID, &nc_var_dimy_datatype);
3626 223 : NCDF_ERR(status);
3627 :
3628 223 : if (!poDS->bSwitchedXY)
3629 : {
3630 : // Convert ]180,540] longitude values to ]-180,0].
3631 311 : if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
3632 90 : CPLTestBool(
3633 : CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")))
3634 : {
3635 : // If minimum longitude is > 180, subtract 360 from all.
3636 : // Add a check on the maximum X value too, since
3637 : // NCDFIsVarLongitude() is not very specific by default (see
3638 : // https://github.com/OSGeo/gdal/issues/1440)
3639 97 : if (std::min(pdfXCoord[0], pdfXCoord[xdim - 1]) > 180.0 &&
3640 7 : std::max(pdfXCoord[0], pdfXCoord[xdim - 1]) <= 540)
3641 : {
3642 0 : CPLDebug(
3643 : "GDAL_netCDF",
3644 : "Offsetting longitudes from ]180,540] to ]-180,180]. "
3645 : "Can be disabled with GDAL_NETCDF_CENTERLONG_180=NO");
3646 0 : for (size_t i = 0; i < xdim; i++)
3647 0 : pdfXCoord[i] -= 360;
3648 : }
3649 : }
3650 : }
3651 :
3652 : // Is pixel spacing uniform across the map?
3653 :
3654 : // Check Longitude.
3655 :
3656 223 : bool bLonSpacingOK = false;
3657 223 : if (xdim == 2)
3658 : {
3659 28 : bLonSpacingOK = true;
3660 : }
3661 : else
3662 : {
3663 195 : bool bWestIsLeft = (pdfXCoord[0] < pdfXCoord[xdim - 1]);
3664 :
3665 : // fix longitudes if longitudes should increase from
3666 : // west to east, but west > east
3667 275 : if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
3668 80 : !bWestIsLeft)
3669 : {
3670 2 : size_t ndecreases = 0;
3671 :
3672 : // there is lon wrap if longitudes increase
3673 : // with one single decrease
3674 107 : for (size_t i = 1; i < xdim; i++)
3675 : {
3676 105 : if (pdfXCoord[i] < pdfXCoord[i - 1])
3677 1 : ndecreases++;
3678 : }
3679 :
3680 2 : if (ndecreases == 1)
3681 : {
3682 1 : CPLDebug("GDAL_netCDF", "longitude wrap detected");
3683 4 : for (size_t i = 0; i < xdim; i++)
3684 : {
3685 3 : if (pdfXCoord[i] > pdfXCoord[xdim - 1])
3686 1 : pdfXCoord[i] -= 360;
3687 : }
3688 : }
3689 : }
3690 :
3691 195 : const double dfSpacingBegin = pdfXCoord[1] - pdfXCoord[0];
3692 195 : const double dfSpacingMiddle =
3693 195 : pdfXCoord[xdim / 2 + 1] - pdfXCoord[xdim / 2];
3694 195 : const double dfSpacingLast =
3695 195 : pdfXCoord[xdim - 1] - pdfXCoord[xdim - 2];
3696 :
3697 195 : CPLDebug("GDAL_netCDF",
3698 : "xdim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
3699 : "dfSpacingLast: %f",
3700 : static_cast<long>(xdim), dfSpacingBegin, dfSpacingMiddle,
3701 : dfSpacingLast);
3702 : #ifdef NCDF_DEBUG
3703 : CPLDebug("GDAL_netCDF", "xcoords: %f %f %f %f %f %f", pdfXCoord[0],
3704 : pdfXCoord[1], pdfXCoord[xdim / 2],
3705 : pdfXCoord[(xdim / 2) + 1], pdfXCoord[xdim - 2],
3706 : pdfXCoord[xdim - 1]);
3707 : #endif
3708 :
3709 : // ftp://ftp.cdc.noaa.gov/Datasets/NARR/Dailies/monolevel/vwnd.10m.2015.nc
3710 : // requires a 0.02% tolerance, so let's settle for 0.05%
3711 :
3712 : // For float variables, increase to 0.2% (as seen in
3713 : // https://github.com/OSGeo/gdal/issues/3663)
3714 195 : const double dfEpsRel =
3715 195 : nc_var_dimx_datatype == NC_FLOAT ? 0.002 : 0.0005;
3716 :
3717 : const double dfEps =
3718 : dfEpsRel *
3719 390 : std::max(fabs(dfSpacingBegin),
3720 195 : std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
3721 384 : if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
3722 384 : IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
3723 189 : IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
3724 : {
3725 189 : bLonSpacingOK = true;
3726 : }
3727 6 : else if (CPLTestBool(CPLGetConfigOption(
3728 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
3729 : {
3730 0 : bLonSpacingOK = true;
3731 0 : CPLDebug(
3732 : "GDAL_netCDF",
3733 : "Longitude/X is not equally spaced, but will be considered "
3734 : "as such because of "
3735 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
3736 : }
3737 : }
3738 :
3739 223 : if (bLonSpacingOK == false)
3740 : {
3741 6 : CPLDebug(
3742 : "GDAL_netCDF", "%s",
3743 : "Longitude/X is not equally spaced (with a 0.05% tolerance). "
3744 : "You may set the "
3745 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
3746 : "option to YES to ignore this check");
3747 : }
3748 :
3749 : // Check Latitude.
3750 223 : bool bLatSpacingOK = false;
3751 :
3752 223 : if (ydim == 2)
3753 : {
3754 48 : bLatSpacingOK = true;
3755 : }
3756 : else
3757 : {
3758 175 : const double dfSpacingBegin = pdfYCoord[1] - pdfYCoord[0];
3759 175 : const double dfSpacingMiddle =
3760 175 : pdfYCoord[ydim / 2 + 1] - pdfYCoord[ydim / 2];
3761 :
3762 175 : const double dfSpacingLast =
3763 175 : pdfYCoord[ydim - 1] - pdfYCoord[ydim - 2];
3764 :
3765 175 : CPLDebug("GDAL_netCDF",
3766 : "ydim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
3767 : "dfSpacingLast: %f",
3768 : (long)ydim, dfSpacingBegin, dfSpacingMiddle,
3769 : dfSpacingLast);
3770 : #ifdef NCDF_DEBUG
3771 : CPLDebug("GDAL_netCDF", "ycoords: %f %f %f %f %f %f", pdfYCoord[0],
3772 : pdfYCoord[1], pdfYCoord[ydim / 2],
3773 : pdfYCoord[(ydim / 2) + 1], pdfYCoord[ydim - 2],
3774 : pdfYCoord[ydim - 1]);
3775 : #endif
3776 :
3777 175 : const double dfEpsRel =
3778 175 : nc_var_dimy_datatype == NC_FLOAT ? 0.002 : 0.0005;
3779 :
3780 : const double dfEps =
3781 : dfEpsRel *
3782 350 : std::max(fabs(dfSpacingBegin),
3783 175 : std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
3784 348 : if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
3785 348 : IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
3786 164 : IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
3787 : {
3788 164 : bLatSpacingOK = true;
3789 : }
3790 11 : else if (CPLTestBool(CPLGetConfigOption(
3791 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
3792 : {
3793 0 : bLatSpacingOK = true;
3794 0 : CPLDebug(
3795 : "GDAL_netCDF",
3796 : "Latitude/Y is not equally spaced, but will be considered "
3797 : "as such because of "
3798 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
3799 : }
3800 11 : else if (!oSRS.IsProjected() &&
3801 11 : fabs(dfSpacingBegin - dfSpacingLast) <= 0.1 &&
3802 30 : fabs(dfSpacingBegin - dfSpacingMiddle) <= 0.1 &&
3803 8 : fabs(dfSpacingMiddle - dfSpacingLast) <= 0.1)
3804 : {
3805 8 : bLatSpacingOK = true;
3806 8 : CPLError(CE_Warning, CPLE_AppDefined,
3807 : "Latitude grid not spaced evenly. "
3808 : "Setting projection for grid spacing is "
3809 : "within 0.1 degrees threshold.");
3810 :
3811 8 : CPLDebug("GDAL_netCDF",
3812 : "Latitude grid not spaced evenly, but within 0.1 "
3813 : "degree threshold (probably a Gaussian grid). "
3814 : "Saving original latitude values in Y_VALUES "
3815 : "geolocation metadata");
3816 8 : Set1DGeolocation(nGroupDimYID, nVarDimYID, "Y");
3817 : }
3818 :
3819 175 : if (bLatSpacingOK == false)
3820 : {
3821 3 : CPLDebug(
3822 : "GDAL_netCDF", "%s",
3823 : "Latitude/Y is not equally spaced (with a 0.05% "
3824 : "tolerance). "
3825 : "You may set the "
3826 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
3827 : "option to YES to ignore this check");
3828 : }
3829 : }
3830 :
3831 223 : if (bLonSpacingOK && bLatSpacingOK)
3832 : {
3833 : // We have gridded data so we can set the Georeferencing info.
3834 :
3835 : // Enable GeoTransform.
3836 :
3837 : // In the following "actual_range" and "node_offset"
3838 : // are attributes used by netCDF files created by GMT.
3839 : // If we find them we know how to proceed. Else, use
3840 : // the original algorithm.
3841 216 : bGotCfGT = true;
3842 :
3843 216 : int node_offset = 0;
3844 216 : NCDFResolveAttInt(nGroupId, NC_GLOBAL, "node_offset", &node_offset);
3845 :
3846 216 : double adfActualRange[2] = {0.0, 0.0};
3847 216 : double xMinMax[2] = {0.0, 0.0};
3848 216 : double yMinMax[2] = {0.0, 0.0};
3849 :
3850 : const auto RoundMinMaxForFloatVals =
3851 58 : [](double &dfMin, double &dfMax, int nIntervals)
3852 : {
3853 : // Helps for a case where longitudes range from
3854 : // -179.99 to 180.0 with a 0.01 degree spacing.
3855 : // However as this is encoded in a float array,
3856 : // -179.99 is actually read as -179.99000549316406 as
3857 : // a double. Try to detect that and correct the rounding
3858 :
3859 87 : const auto IsAlmostInteger = [](double dfVal)
3860 : {
3861 87 : constexpr double THRESHOLD_INTEGER = 1e-3;
3862 87 : return std::fabs(dfVal - std::round(dfVal)) <=
3863 87 : THRESHOLD_INTEGER;
3864 : };
3865 :
3866 58 : const double dfSpacing = (dfMax - dfMin) / nIntervals;
3867 58 : if (dfSpacing > 0)
3868 : {
3869 47 : const double dfInvSpacing = 1.0 / dfSpacing;
3870 47 : if (IsAlmostInteger(dfInvSpacing))
3871 : {
3872 20 : const double dfRoundedSpacing =
3873 20 : 1.0 / std::round(dfInvSpacing);
3874 20 : const double dfMinDivRoundedSpacing =
3875 20 : dfMin / dfRoundedSpacing;
3876 20 : const double dfMaxDivRoundedSpacing =
3877 20 : dfMax / dfRoundedSpacing;
3878 40 : if (IsAlmostInteger(dfMinDivRoundedSpacing) &&
3879 20 : IsAlmostInteger(dfMaxDivRoundedSpacing))
3880 : {
3881 20 : const double dfRoundedMin =
3882 20 : std::round(dfMinDivRoundedSpacing) *
3883 : dfRoundedSpacing;
3884 20 : const double dfRoundedMax =
3885 20 : std::round(dfMaxDivRoundedSpacing) *
3886 : dfRoundedSpacing;
3887 20 : if (static_cast<float>(dfMin) ==
3888 20 : static_cast<float>(dfRoundedMin) &&
3889 8 : static_cast<float>(dfMax) ==
3890 8 : static_cast<float>(dfRoundedMax))
3891 : {
3892 7 : dfMin = dfRoundedMin;
3893 7 : dfMax = dfRoundedMax;
3894 : }
3895 : }
3896 : }
3897 : }
3898 58 : };
3899 :
3900 216 : if (!nc_get_att_double(nGroupDimXID, nVarDimXID, "actual_range",
3901 : adfActualRange))
3902 : {
3903 3 : xMinMax[0] = adfActualRange[0];
3904 3 : xMinMax[1] = adfActualRange[1];
3905 :
3906 : // Present xMinMax[] in the same order as padfXCoord
3907 3 : if ((xMinMax[0] - xMinMax[1]) *
3908 3 : (pdfXCoord[0] - pdfXCoord[xdim - 1]) <
3909 : 0)
3910 : {
3911 0 : std::swap(xMinMax[0], xMinMax[1]);
3912 : }
3913 : }
3914 : else
3915 : {
3916 213 : xMinMax[0] = pdfXCoord[0];
3917 213 : xMinMax[1] = pdfXCoord[xdim - 1];
3918 213 : node_offset = 0;
3919 :
3920 213 : if (nc_var_dimx_datatype == NC_FLOAT)
3921 : {
3922 29 : RoundMinMaxForFloatVals(xMinMax[0], xMinMax[1],
3923 29 : poDS->nRasterXSize - 1);
3924 : }
3925 : }
3926 :
3927 216 : if (!nc_get_att_double(nGroupDimYID, nVarDimYID, "actual_range",
3928 : adfActualRange))
3929 : {
3930 3 : yMinMax[0] = adfActualRange[0];
3931 3 : yMinMax[1] = adfActualRange[1];
3932 :
3933 : // Present yMinMax[] in the same order as pdfYCoord
3934 3 : if ((yMinMax[0] - yMinMax[1]) *
3935 3 : (pdfYCoord[0] - pdfYCoord[ydim - 1]) <
3936 : 0)
3937 : {
3938 1 : std::swap(yMinMax[0], yMinMax[1]);
3939 : }
3940 : }
3941 : else
3942 : {
3943 213 : yMinMax[0] = pdfYCoord[0];
3944 213 : yMinMax[1] = pdfYCoord[ydim - 1];
3945 213 : node_offset = 0;
3946 :
3947 213 : if (nc_var_dimy_datatype == NC_FLOAT)
3948 : {
3949 29 : RoundMinMaxForFloatVals(yMinMax[0], yMinMax[1],
3950 29 : poDS->nRasterYSize - 1);
3951 : }
3952 : }
3953 :
3954 216 : double dfCoordOffset = 0.0;
3955 216 : double dfCoordScale = 1.0;
3956 216 : if (!nc_get_att_double(nGroupId, nVarDimXID, CF_ADD_OFFSET,
3957 220 : &dfCoordOffset) &&
3958 4 : !nc_get_att_double(nGroupId, nVarDimXID, CF_SCALE_FACTOR,
3959 : &dfCoordScale))
3960 : {
3961 4 : xMinMax[0] = dfCoordOffset + xMinMax[0] * dfCoordScale;
3962 4 : xMinMax[1] = dfCoordOffset + xMinMax[1] * dfCoordScale;
3963 : }
3964 :
3965 216 : if (!nc_get_att_double(nGroupId, nVarDimYID, CF_ADD_OFFSET,
3966 220 : &dfCoordOffset) &&
3967 4 : !nc_get_att_double(nGroupId, nVarDimYID, CF_SCALE_FACTOR,
3968 : &dfCoordScale))
3969 : {
3970 4 : yMinMax[0] = dfCoordOffset + yMinMax[0] * dfCoordScale;
3971 4 : yMinMax[1] = dfCoordOffset + yMinMax[1] * dfCoordScale;
3972 : }
3973 :
3974 : // Check for reverse order of y-coordinate.
3975 216 : if (!bSwitchedXY)
3976 : {
3977 214 : poDS->bBottomUp = (yMinMax[0] <= yMinMax[1]);
3978 214 : CPLDebug("GDAL_netCDF", "set bBottomUp = %d from Y axis",
3979 214 : static_cast<int>(poDS->bBottomUp));
3980 214 : if (!poDS->bBottomUp)
3981 : {
3982 32 : std::swap(yMinMax[0], yMinMax[1]);
3983 : }
3984 : }
3985 :
3986 : // Geostationary satellites can specify units in (micro)radians
3987 : // So we check if they do, and if so convert to linear units
3988 : // (meters)
3989 216 : const char *pszProjName = oSRS.GetAttrValue("PROJECTION");
3990 216 : if (pszProjName != nullptr)
3991 : {
3992 24 : if (EQUAL(pszProjName, SRS_PT_GEOSTATIONARY_SATELLITE))
3993 : {
3994 : double satelliteHeight =
3995 3 : oSRS.GetProjParm(SRS_PP_SATELLITE_HEIGHT, 1.0);
3996 3 : size_t nAttlen = 0;
3997 : char szUnits[NC_MAX_NAME + 1];
3998 3 : szUnits[0] = '\0';
3999 3 : nc_type nAttype = NC_NAT;
4000 3 : nc_inq_att(nGroupId, nVarDimXID, "units", &nAttype,
4001 : &nAttlen);
4002 6 : if (nAttlen < sizeof(szUnits) &&
4003 3 : nc_get_att_text(nGroupId, nVarDimXID, "units",
4004 : szUnits) == NC_NOERR)
4005 : {
4006 3 : szUnits[nAttlen] = '\0';
4007 3 : if (EQUAL(szUnits, "microradian"))
4008 : {
4009 1 : xMinMax[0] =
4010 1 : xMinMax[0] * satelliteHeight * 0.000001;
4011 1 : xMinMax[1] =
4012 1 : xMinMax[1] * satelliteHeight * 0.000001;
4013 : }
4014 2 : else if (EQUAL(szUnits, "rad") ||
4015 1 : EQUAL(szUnits, "radian"))
4016 : {
4017 2 : xMinMax[0] = xMinMax[0] * satelliteHeight;
4018 2 : xMinMax[1] = xMinMax[1] * satelliteHeight;
4019 : }
4020 : }
4021 3 : szUnits[0] = '\0';
4022 3 : nc_inq_att(nGroupId, nVarDimYID, "units", &nAttype,
4023 : &nAttlen);
4024 6 : if (nAttlen < sizeof(szUnits) &&
4025 3 : nc_get_att_text(nGroupId, nVarDimYID, "units",
4026 : szUnits) == NC_NOERR)
4027 : {
4028 3 : szUnits[nAttlen] = '\0';
4029 3 : if (EQUAL(szUnits, "microradian"))
4030 : {
4031 1 : yMinMax[0] =
4032 1 : yMinMax[0] * satelliteHeight * 0.000001;
4033 1 : yMinMax[1] =
4034 1 : yMinMax[1] * satelliteHeight * 0.000001;
4035 : }
4036 2 : else if (EQUAL(szUnits, "rad") ||
4037 1 : EQUAL(szUnits, "radian"))
4038 : {
4039 2 : yMinMax[0] = yMinMax[0] * satelliteHeight;
4040 2 : yMinMax[1] = yMinMax[1] * satelliteHeight;
4041 : }
4042 : }
4043 : }
4044 : }
4045 :
4046 216 : adfTempGeoTransform[0] = xMinMax[0];
4047 216 : adfTempGeoTransform[1] = (xMinMax[1] - xMinMax[0]) /
4048 216 : (poDS->nRasterXSize + (node_offset - 1));
4049 216 : adfTempGeoTransform[2] = 0;
4050 216 : if (bSwitchedXY)
4051 : {
4052 2 : adfTempGeoTransform[3] = yMinMax[0];
4053 2 : adfTempGeoTransform[4] = 0;
4054 2 : adfTempGeoTransform[5] =
4055 2 : (yMinMax[1] - yMinMax[0]) /
4056 2 : (poDS->nRasterYSize + (node_offset - 1));
4057 : }
4058 : else
4059 : {
4060 214 : adfTempGeoTransform[3] = yMinMax[1];
4061 214 : adfTempGeoTransform[4] = 0;
4062 214 : adfTempGeoTransform[5] =
4063 214 : (yMinMax[0] - yMinMax[1]) /
4064 214 : (poDS->nRasterYSize + (node_offset - 1));
4065 : }
4066 :
4067 : // Compute the center of the pixel.
4068 216 : if (!node_offset)
4069 : {
4070 : // Otherwise its already the pixel center.
4071 216 : adfTempGeoTransform[0] -= (adfTempGeoTransform[1] / 2);
4072 216 : adfTempGeoTransform[3] -= (adfTempGeoTransform[5] / 2);
4073 : }
4074 : }
4075 :
4076 : const auto AreSRSEqualThroughProj4String =
4077 2 : [](const OGRSpatialReference &oSRS1,
4078 : const OGRSpatialReference &oSRS2)
4079 : {
4080 2 : char *pszProj4Str1 = nullptr;
4081 2 : oSRS1.exportToProj4(&pszProj4Str1);
4082 :
4083 2 : char *pszProj4Str2 = nullptr;
4084 2 : oSRS2.exportToProj4(&pszProj4Str2);
4085 :
4086 : {
4087 2 : char *pszTmp = strstr(pszProj4Str1, "+datum=");
4088 2 : if (pszTmp)
4089 0 : memcpy(pszTmp, "+ellps=", strlen("+ellps="));
4090 : }
4091 :
4092 : {
4093 2 : char *pszTmp = strstr(pszProj4Str2, "+datum=");
4094 2 : if (pszTmp)
4095 2 : memcpy(pszTmp, "+ellps=", strlen("+ellps="));
4096 : }
4097 :
4098 2 : bool bRet = false;
4099 2 : if (pszProj4Str1 && pszProj4Str2 &&
4100 2 : EQUAL(pszProj4Str1, pszProj4Str2))
4101 : {
4102 1 : bRet = true;
4103 : }
4104 :
4105 2 : CPLFree(pszProj4Str1);
4106 2 : CPLFree(pszProj4Str2);
4107 2 : return bRet;
4108 : };
4109 :
4110 223 : if (dfLinearUnitsConvFactor != 1.0)
4111 : {
4112 35 : for (int i = 0; i < 6; ++i)
4113 30 : adfTempGeoTransform[i] *= dfLinearUnitsConvFactor;
4114 :
4115 5 : if (paosRemovedMDItems)
4116 : {
4117 : char szVarNameX[NC_MAX_NAME + 1];
4118 5 : CPL_IGNORE_RET_VAL(
4119 5 : nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
4120 :
4121 : char szVarNameY[NC_MAX_NAME + 1];
4122 5 : CPL_IGNORE_RET_VAL(
4123 5 : nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
4124 :
4125 5 : paosRemovedMDItems->push_back(
4126 : CPLSPrintf("%s#units", szVarNameX));
4127 5 : paosRemovedMDItems->push_back(
4128 : CPLSPrintf("%s#units", szVarNameY));
4129 : }
4130 : }
4131 :
4132 : // If there is a global "geospatial_bounds_crs" attribute, check that it
4133 : // is consistent with the SRS, and if so, use it as the SRS
4134 : const char *pszGBCRS =
4135 223 : FetchAttr(nGroupId, NC_GLOBAL, "geospatial_bounds_crs");
4136 223 : if (pszGBCRS && STARTS_WITH(pszGBCRS, "EPSG:"))
4137 : {
4138 4 : OGRSpatialReference oSRSFromGBCRS;
4139 2 : oSRSFromGBCRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
4140 2 : if (oSRSFromGBCRS.SetFromUserInput(
4141 : pszGBCRS,
4142 : OGRSpatialReference::
4143 4 : SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE &&
4144 2 : AreSRSEqualThroughProj4String(oSRS, oSRSFromGBCRS))
4145 : {
4146 1 : oSRS = std::move(oSRSFromGBCRS);
4147 1 : SetSpatialRefNoUpdate(&oSRS);
4148 : }
4149 : }
4150 :
4151 223 : CPLFree(pdfXCoord);
4152 223 : CPLFree(pdfYCoord);
4153 : } // end if(has dims)
4154 :
4155 : // Process custom GeoTransform GDAL value.
4156 513 : if (!EQUAL(pszGridMappingValue, ""))
4157 : {
4158 219 : if (pszGeoTransform != nullptr)
4159 : {
4160 : CPLStringList aosGeoTransform(
4161 220 : CSLTokenizeString2(pszGeoTransform, " ", CSLT_HONOURSTRINGS));
4162 110 : if (aosGeoTransform.size() == 6)
4163 : {
4164 : std::array<double, 6> adfGeoTransformFromAttribute;
4165 770 : for (int i = 0; i < 6; i++)
4166 : {
4167 660 : adfGeoTransformFromAttribute[i] =
4168 660 : CPLAtof(aosGeoTransform[i]);
4169 : }
4170 :
4171 110 : if (bGotCfGT)
4172 : {
4173 94 : constexpr double GT_RELERROR_WARN_THRESHOLD = 1e-6;
4174 94 : double dfMaxAbsoluteError = 0.0;
4175 658 : for (int i = 0; i < 6; i++)
4176 : {
4177 : double dfAbsoluteError =
4178 564 : std::abs(adfTempGeoTransform[i] -
4179 564 : adfGeoTransformFromAttribute[i]);
4180 564 : if (dfAbsoluteError >
4181 564 : std::abs(adfGeoTransformFromAttribute[i] *
4182 : GT_RELERROR_WARN_THRESHOLD))
4183 : {
4184 2 : dfMaxAbsoluteError =
4185 2 : std::max(dfMaxAbsoluteError, dfAbsoluteError);
4186 : }
4187 : }
4188 :
4189 94 : if (dfMaxAbsoluteError > 0)
4190 : {
4191 2 : CPLError(CE_Warning, CPLE_AppDefined,
4192 : "GeoTransform read from attribute of %s "
4193 : "variable differs from value calculated from "
4194 : "dimension variables (max diff = %g). Using "
4195 : "value from attribute.",
4196 : pszGridMappingValue, dfMaxAbsoluteError);
4197 : }
4198 : }
4199 :
4200 110 : std::copy(adfGeoTransformFromAttribute.begin(),
4201 : adfGeoTransformFromAttribute.end(),
4202 : adfTempGeoTransform);
4203 110 : bGotGdalGT = true;
4204 : }
4205 : }
4206 : else
4207 : {
4208 : // Look for corner array values.
4209 : // CPLDebug("GDAL_netCDF",
4210 : // "looking for geotransform corners");
4211 109 : bool bGotNN = false;
4212 109 : double dfNN = FetchCopyParam(pszGridMappingValue,
4213 : "Northernmost_Northing", 0, &bGotNN);
4214 :
4215 109 : bool bGotSN = false;
4216 109 : double dfSN = FetchCopyParam(pszGridMappingValue,
4217 : "Southernmost_Northing", 0, &bGotSN);
4218 :
4219 109 : bool bGotEE = false;
4220 109 : double dfEE = FetchCopyParam(pszGridMappingValue,
4221 : "Easternmost_Easting", 0, &bGotEE);
4222 :
4223 109 : bool bGotWE = false;
4224 109 : double dfWE = FetchCopyParam(pszGridMappingValue,
4225 : "Westernmost_Easting", 0, &bGotWE);
4226 :
4227 : // Only set the GeoTransform if we got all the values.
4228 109 : if (bGotNN && bGotSN && bGotEE && bGotWE)
4229 : {
4230 0 : bGotGdalGT = true;
4231 :
4232 0 : adfTempGeoTransform[0] = dfWE;
4233 0 : adfTempGeoTransform[1] =
4234 0 : (dfEE - dfWE) / (poDS->GetRasterXSize() - 1);
4235 0 : adfTempGeoTransform[2] = 0.0;
4236 0 : adfTempGeoTransform[3] = dfNN;
4237 0 : adfTempGeoTransform[4] = 0.0;
4238 0 : adfTempGeoTransform[5] =
4239 0 : (dfSN - dfNN) / (poDS->GetRasterYSize() - 1);
4240 : // Compute the center of the pixel.
4241 0 : adfTempGeoTransform[0] = dfWE - (adfTempGeoTransform[1] / 2);
4242 0 : adfTempGeoTransform[3] = dfNN - (adfTempGeoTransform[5] / 2);
4243 : }
4244 : } // (pszGeoTransform != NULL)
4245 :
4246 219 : if (bGotGdalSRS && !bGotGdalGT)
4247 74 : CPLDebug("GDAL_netCDF", "Got SRS but no geotransform from GDAL!");
4248 : }
4249 :
4250 513 : if (!pszWKT && !bGotCfSRS)
4251 : {
4252 : // Some netCDF files have a srid attribute (#6613) like
4253 : // urn:ogc:def:crs:EPSG::6931
4254 294 : const char *pszSRID = FetchAttr(pszGridMappingValue, "srid");
4255 294 : if (pszSRID != nullptr)
4256 : {
4257 0 : oSRS.Clear();
4258 0 : if (oSRS.SetFromUserInput(
4259 : pszSRID,
4260 : OGRSpatialReference::
4261 0 : SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE)
4262 : {
4263 0 : char *pszWKTExport = nullptr;
4264 0 : CPLDebug("GDAL_netCDF", "Got SRS from %s", pszSRID);
4265 0 : oSRS.exportToWkt(&pszWKTExport);
4266 0 : if (returnProjStr != nullptr)
4267 : {
4268 0 : (*returnProjStr) = std::string(pszWKTExport);
4269 : }
4270 : else
4271 : {
4272 0 : m_bAddedProjectionVarsDefs = true;
4273 0 : m_bAddedProjectionVarsData = true;
4274 0 : SetSpatialRefNoUpdate(&oSRS);
4275 : }
4276 0 : CPLFree(pszWKTExport);
4277 : }
4278 : }
4279 : }
4280 :
4281 513 : CPLFree(pszGridMappingValue);
4282 :
4283 513 : if (bReadSRSOnly)
4284 166 : return;
4285 :
4286 : // Determines the SRS to be used by the geolocation array, if any
4287 694 : std::string osGeolocWKT = SRS_WKT_WGS84_LAT_LONG;
4288 347 : if (!m_oSRS.IsEmpty())
4289 : {
4290 262 : OGRSpatialReference oGeogCRS;
4291 131 : oGeogCRS.CopyGeogCSFrom(&m_oSRS);
4292 131 : char *pszWKTTmp = nullptr;
4293 131 : const char *const apszOptions[] = {"FORMAT=WKT2_2019", nullptr};
4294 131 : if (oGeogCRS.exportToWkt(&pszWKTTmp, apszOptions) == OGRERR_NONE)
4295 : {
4296 131 : osGeolocWKT = pszWKTTmp;
4297 : }
4298 131 : CPLFree(pszWKTTmp);
4299 : }
4300 :
4301 : // Process geolocation arrays from CF "coordinates" attribute.
4302 694 : std::string osGeolocXName, osGeolocYName;
4303 347 : if (ProcessCFGeolocation(nGroupId, nVarId, osGeolocWKT, osGeolocXName,
4304 347 : osGeolocYName))
4305 : {
4306 52 : bool bCanCancelGT = true;
4307 52 : if ((nVarDimXID != -1) && (nVarDimYID != -1))
4308 : {
4309 : char szVarNameX[NC_MAX_NAME + 1];
4310 44 : CPL_IGNORE_RET_VAL(
4311 44 : nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
4312 : char szVarNameY[NC_MAX_NAME + 1];
4313 44 : CPL_IGNORE_RET_VAL(
4314 44 : nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
4315 44 : bCanCancelGT =
4316 44 : !(osGeolocXName == szVarNameX && osGeolocYName == szVarNameY);
4317 : }
4318 86 : if (bCanCancelGT && !m_oSRS.IsGeographic() && !m_oSRS.IsProjected() &&
4319 34 : !bSwitchedXY)
4320 : {
4321 32 : bGotCfGT = false;
4322 : }
4323 : }
4324 119 : else if (!bGotCfGT && !bReadSRSOnly && (nVarDimXID != -1) &&
4325 417 : (nVarDimYID != -1) && xdim > 0 && ydim > 0 &&
4326 3 : ((!bSwitchedXY &&
4327 3 : NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
4328 1 : NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr)) ||
4329 2 : (bSwitchedXY &&
4330 0 : NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
4331 0 : NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr))))
4332 : {
4333 : // Case of autotest/gdrivers/data/netcdf/GLMELT_4X5.OCN.nc
4334 : // which is indexed by lat, lon variables, but lat has irregular
4335 : // spacing.
4336 1 : const char *pszGeolocXFullName = poDS->papszDimName[poDS->nXDimID];
4337 1 : const char *pszGeolocYFullName = poDS->papszDimName[poDS->nYDimID];
4338 1 : if (bSwitchedXY)
4339 : {
4340 0 : std::swap(pszGeolocXFullName, pszGeolocYFullName);
4341 0 : GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", "GEOLOCATION");
4342 : }
4343 :
4344 1 : CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
4345 : pszGeolocXFullName, pszGeolocYFullName);
4346 :
4347 1 : GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
4348 : "GEOLOCATION");
4349 :
4350 2 : CPLString osTMP;
4351 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4352 1 : pszGeolocXFullName);
4353 :
4354 1 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
4355 1 : GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
4356 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4357 1 : pszGeolocYFullName);
4358 :
4359 1 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
4360 1 : GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
4361 :
4362 1 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
4363 1 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
4364 :
4365 1 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
4366 1 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
4367 :
4368 1 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
4369 : "PIXEL_CENTER", "GEOLOCATION");
4370 : }
4371 :
4372 : // Set GeoTransform if we got a complete one - after projection has been set
4373 347 : if (bGotCfGT || bGotGdalGT)
4374 : {
4375 198 : m_bAddedProjectionVarsDefs = true;
4376 198 : m_bAddedProjectionVarsData = true;
4377 198 : SetGeoTransformNoUpdate(adfTempGeoTransform);
4378 : }
4379 :
4380 : // Debugging reports.
4381 347 : CPLDebug("GDAL_netCDF",
4382 : "bGotGeogCS=%d bGotCfSRS=%d bGotCfGT=%d bGotCfWktSRS=%d "
4383 : "bGotGdalSRS=%d bGotGdalGT=%d",
4384 : static_cast<int>(bGotGeogCS), static_cast<int>(bGotCfSRS),
4385 : static_cast<int>(bGotCfGT), static_cast<int>(bGotCfWktSRS),
4386 : static_cast<int>(bGotGdalSRS), static_cast<int>(bGotGdalGT));
4387 :
4388 347 : if (!bGotCfGT && !bGotGdalGT)
4389 149 : CPLDebug("GDAL_netCDF", "did not get geotransform from CF nor GDAL!");
4390 :
4391 347 : if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfGT && !bGotCfWktSRS)
4392 149 : CPLDebug("GDAL_netCDF", "did not get projection from CF nor GDAL!");
4393 :
4394 : // wish of 6195
4395 : // we don't have CS/SRS, but we do have GT, and we live in -180,360 -90,90
4396 347 : if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfWktSRS)
4397 : {
4398 216 : if (bGotCfGT || bGotGdalGT)
4399 : {
4400 134 : bool bAssumedLongLat = CPLTestBool(CSLFetchNameValueDef(
4401 67 : papszOpenOptions, "ASSUME_LONGLAT",
4402 : CPLGetConfigOption("GDAL_NETCDF_ASSUME_LONGLAT", "NO")));
4403 :
4404 2 : if (bAssumedLongLat && adfTempGeoTransform[0] >= -180 &&
4405 2 : adfTempGeoTransform[0] < 360 &&
4406 4 : (adfTempGeoTransform[0] +
4407 2 : adfTempGeoTransform[1] * poDS->GetRasterXSize()) <= 360 &&
4408 71 : adfTempGeoTransform[3] <= 90 && adfTempGeoTransform[3] > -90 &&
4409 4 : (adfTempGeoTransform[3] +
4410 2 : adfTempGeoTransform[5] * poDS->GetRasterYSize()) >= -90)
4411 : {
4412 :
4413 2 : poDS->bIsGeographic = true;
4414 2 : char *pszTempProjection = nullptr;
4415 : // seems odd to use 4326 so OGC:CRS84
4416 2 : oSRS.SetFromUserInput("OGC:CRS84");
4417 2 : oSRS.exportToWkt(&pszTempProjection);
4418 2 : if (returnProjStr != nullptr)
4419 : {
4420 0 : (*returnProjStr) = std::string(pszTempProjection);
4421 : }
4422 : else
4423 : {
4424 2 : m_bAddedProjectionVarsDefs = true;
4425 2 : m_bAddedProjectionVarsData = true;
4426 2 : SetSpatialRefNoUpdate(&oSRS);
4427 : }
4428 2 : CPLFree(pszTempProjection);
4429 :
4430 2 : CPLDebug("netCDF",
4431 : "Assumed Longitude Latitude CRS 'OGC:CRS84' because "
4432 : "none otherwise available and geotransform within "
4433 : "suitable bounds. "
4434 : "Set GDAL_NETCDF_ASSUME_LONGLAT=NO as configuration "
4435 : "option or "
4436 : " ASSUME_LONGLAT=NO as open option to bypass this "
4437 : "assumption.");
4438 : }
4439 : }
4440 : }
4441 :
4442 : // Search for Well-known GeogCS if got only CF WKT
4443 : // Disabled for now, as a named datum also include control points
4444 : // (see mailing list and bug#4281
4445 : // For example, WGS84 vs. GDA94 (EPSG:3577) - AEA in netcdf_cf.py
4446 :
4447 : // Disabled for now, but could be set in a config option.
4448 : #if 0
4449 : bool bLookForWellKnownGCS = false; // This could be a Config Option.
4450 :
4451 : if( bLookForWellKnownGCS && bGotCfSRS && !bGotGdalSRS )
4452 : {
4453 : // ET - Could use a more exhaustive method by scanning all EPSG codes in
4454 : // data/gcs.csv as proposed by Even in the gdal-dev mailing list "help
4455 : // for comparing two WKT".
4456 : // This code could be contributed to a new function.
4457 : // OGRSpatialReference * OGRSpatialReference::FindMatchingGeogCS(
4458 : // const OGRSpatialReference *poOther) */
4459 : CPLDebug("GDAL_netCDF", "Searching for Well-known GeogCS");
4460 : const char *pszWKGCSList[] = { "WGS84", "WGS72", "NAD27", "NAD83" };
4461 : char *pszWKGCS = NULL;
4462 : oSRS.exportToPrettyWkt(&pszWKGCS);
4463 : for( size_t i = 0; i < sizeof(pszWKGCSList) / 8; i++ )
4464 : {
4465 : pszWKGCS = CPLStrdup(pszWKGCSList[i]);
4466 : OGRSpatialReference oSRSTmp;
4467 : oSRSTmp.SetWellKnownGeogCS(pszWKGCSList[i]);
4468 : // Set datum to unknown, bug #4281.
4469 : if( oSRSTmp.GetAttrNode("DATUM" ) )
4470 : oSRSTmp.GetAttrNode("DATUM")->GetChild(0)->SetValue("unknown");
4471 : // Could use OGRSpatialReference::StripCTParms(), but let's keep
4472 : // TOWGS84.
4473 : oSRSTmp.GetRoot()->StripNodes("AXIS");
4474 : oSRSTmp.GetRoot()->StripNodes("AUTHORITY");
4475 : oSRSTmp.GetRoot()->StripNodes("EXTENSION");
4476 :
4477 : oSRSTmp.exportToPrettyWkt(&pszWKGCS);
4478 : if( oSRS.IsSameGeogCS(&oSRSTmp) )
4479 : {
4480 : oSRS.SetWellKnownGeogCS(pszWKGCSList[i]);
4481 : oSRS.exportToWkt(&(pszTempProjection));
4482 : SetProjection(pszTempProjection);
4483 : CPLFree(pszTempProjection);
4484 : }
4485 : }
4486 : }
4487 : #endif
4488 : }
4489 :
4490 123 : void netCDFDataset::SetProjectionFromVar(int nGroupId, int nVarId,
4491 : bool bReadSRSOnly)
4492 : {
4493 123 : SetProjectionFromVar(nGroupId, nVarId, bReadSRSOnly, nullptr, nullptr,
4494 : nullptr, nullptr);
4495 123 : }
4496 :
4497 284 : bool netCDFDataset::ProcessNASAL2OceanGeoLocation(int nGroupId, int nVarId)
4498 : {
4499 : // Cf https://oceancolor.gsfc.nasa.gov/docs/format/l2nc/
4500 : // and https://github.com/OSGeo/gdal/issues/7605
4501 :
4502 : // Check for a structure like:
4503 : /* netcdf SNPP_VIIRS.20230406T024200.L2.OC.NRT {
4504 : dimensions:
4505 : number_of_lines = 3248 ;
4506 : pixels_per_line = 3200 ;
4507 : [...]
4508 : pixel_control_points = 3200 ;
4509 : [...]
4510 : group: geophysical_data {
4511 : variables:
4512 : short aot_862(number_of_lines, pixels_per_line) ; <-- nVarId
4513 : [...]
4514 : }
4515 : group: navigation_data {
4516 : variables:
4517 : float longitude(number_of_lines, pixel_control_points) ;
4518 : [...]
4519 : float latitude(number_of_lines, pixel_control_points) ;
4520 : [...]
4521 : }
4522 : }
4523 : */
4524 : // Note that the longitude and latitude arrays are not indexed by the
4525 : // same dimensions. Handle only the case where
4526 : // pixel_control_points == pixels_per_line
4527 : // If there was a subsampling of the geolocation arrays, we'd need to
4528 : // add more logic.
4529 :
4530 568 : std::string osGroupName;
4531 284 : osGroupName.resize(NC_MAX_NAME);
4532 284 : NCDF_ERR(nc_inq_grpname(nGroupId, &osGroupName[0]));
4533 284 : osGroupName.resize(strlen(osGroupName.data()));
4534 284 : if (osGroupName != "geophysical_data")
4535 283 : return false;
4536 :
4537 1 : int nVarDims = 0;
4538 1 : NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
4539 1 : if (nVarDims != 2)
4540 0 : return false;
4541 :
4542 1 : int nNavigationDataGrpId = 0;
4543 1 : if (nc_inq_grp_ncid(cdfid, "navigation_data", &nNavigationDataGrpId) !=
4544 : NC_NOERR)
4545 0 : return false;
4546 :
4547 : std::array<int, 2> anVarDimIds;
4548 1 : NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
4549 :
4550 1 : int nLongitudeId = 0;
4551 1 : int nLatitudeId = 0;
4552 1 : if (nc_inq_varid(nNavigationDataGrpId, "longitude", &nLongitudeId) !=
4553 2 : NC_NOERR ||
4554 1 : nc_inq_varid(nNavigationDataGrpId, "latitude", &nLatitudeId) !=
4555 : NC_NOERR)
4556 : {
4557 0 : return false;
4558 : }
4559 :
4560 1 : int nDimsLongitude = 0;
4561 1 : NCDF_ERR(
4562 : nc_inq_varndims(nNavigationDataGrpId, nLongitudeId, &nDimsLongitude));
4563 1 : int nDimsLatitude = 0;
4564 1 : NCDF_ERR(
4565 : nc_inq_varndims(nNavigationDataGrpId, nLatitudeId, &nDimsLatitude));
4566 1 : if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
4567 : {
4568 0 : return false;
4569 : }
4570 :
4571 : std::array<int, 2> anDimLongitudeIds;
4572 1 : NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLongitudeId,
4573 : anDimLongitudeIds.data()));
4574 : std::array<int, 2> anDimLatitudeIds;
4575 1 : NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLatitudeId,
4576 : anDimLatitudeIds.data()));
4577 1 : if (anDimLongitudeIds != anDimLatitudeIds)
4578 : {
4579 0 : return false;
4580 : }
4581 :
4582 : std::array<size_t, 2> anSizeVarDimIds;
4583 : std::array<size_t, 2> anSizeLongLatIds;
4584 2 : if (!(nc_inq_dimlen(cdfid, anVarDimIds[0], &anSizeVarDimIds[0]) ==
4585 1 : NC_NOERR &&
4586 1 : nc_inq_dimlen(cdfid, anVarDimIds[1], &anSizeVarDimIds[1]) ==
4587 1 : NC_NOERR &&
4588 1 : nc_inq_dimlen(cdfid, anDimLongitudeIds[0], &anSizeLongLatIds[0]) ==
4589 1 : NC_NOERR &&
4590 1 : nc_inq_dimlen(cdfid, anDimLongitudeIds[1], &anSizeLongLatIds[1]) ==
4591 : NC_NOERR &&
4592 1 : anSizeVarDimIds == anSizeLongLatIds))
4593 : {
4594 0 : return false;
4595 : }
4596 :
4597 1 : const char *pszGeolocXFullName = "/navigation_data/longitude";
4598 1 : const char *pszGeolocYFullName = "/navigation_data/latitude";
4599 :
4600 1 : if (bSwitchedXY)
4601 : {
4602 0 : std::swap(pszGeolocXFullName, pszGeolocYFullName);
4603 0 : GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", "GEOLOCATION");
4604 : }
4605 :
4606 1 : CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
4607 : pszGeolocXFullName, pszGeolocYFullName);
4608 :
4609 1 : GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
4610 : "GEOLOCATION");
4611 :
4612 1 : CPLString osTMP;
4613 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
4614 :
4615 1 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
4616 1 : GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
4617 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
4618 :
4619 1 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
4620 1 : GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
4621 :
4622 1 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
4623 1 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
4624 :
4625 1 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
4626 1 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
4627 :
4628 1 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
4629 : "GEOLOCATION");
4630 1 : return true;
4631 : }
4632 :
4633 283 : bool netCDFDataset::ProcessNASAEMITGeoLocation(int nGroupId, int nVarId)
4634 : {
4635 : // Cf https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/
4636 :
4637 : // Check for a structure like:
4638 : /* netcdf EMIT_L2A_RFL_001_20220903T163129_2224611_012 {
4639 : dimensions:
4640 : downtrack = 1280 ;
4641 : crosstrack = 1242 ;
4642 : bands = 285 ;
4643 : [...]
4644 :
4645 : variables:
4646 : float reflectance(downtrack, crosstrack, bands) ;
4647 :
4648 : group: location {
4649 : variables:
4650 : double lon(downtrack, crosstrack) ;
4651 : lon:_FillValue = -9999. ;
4652 : lon:long_name = "Longitude (WGS-84)" ;
4653 : lon:units = "degrees east" ;
4654 : double lat(downtrack, crosstrack) ;
4655 : lat:_FillValue = -9999. ;
4656 : lat:long_name = "Latitude (WGS-84)" ;
4657 : lat:units = "degrees north" ;
4658 : } // group location
4659 :
4660 : }
4661 : or
4662 : netcdf EMIT_L2B_MIN_001_20231024T055538_2329704_040 {
4663 : dimensions:
4664 : downtrack = 1664 ;
4665 : crosstrack = 1242 ;
4666 : [...]
4667 : variables:
4668 : float group_1_band_depth(downtrack, crosstrack) ;
4669 : group_1_band_depth:_FillValue = -9999.f ;
4670 : group_1_band_depth:long_name = "Group 1 Band Depth" ;
4671 : group_1_band_depth:units = "unitless" ;
4672 : [...]
4673 : group: location {
4674 : variables:
4675 : double lon(downtrack, crosstrack) ;
4676 : lon:_FillValue = -9999. ;
4677 : lon:long_name = "Longitude (WGS-84)" ;
4678 : lon:units = "degrees east" ;
4679 : double lat(downtrack, crosstrack) ;
4680 : lat:_FillValue = -9999. ;
4681 : lat:long_name = "Latitude (WGS-84)" ;
4682 : lat:units = "degrees north" ;
4683 : }
4684 : */
4685 :
4686 283 : int nVarDims = 0;
4687 283 : NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
4688 283 : if (nVarDims != 2 && nVarDims != 3)
4689 14 : return false;
4690 :
4691 269 : int nLocationGrpId = 0;
4692 269 : if (nc_inq_grp_ncid(cdfid, "location", &nLocationGrpId) != NC_NOERR)
4693 57 : return false;
4694 :
4695 : std::array<int, 3> anVarDimIds;
4696 212 : NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
4697 212 : if (nYDimID != anVarDimIds[0] || nXDimID != anVarDimIds[1])
4698 21 : return false;
4699 :
4700 191 : int nLongitudeId = 0;
4701 191 : int nLatitudeId = 0;
4702 229 : if (nc_inq_varid(nLocationGrpId, "lon", &nLongitudeId) != NC_NOERR ||
4703 38 : nc_inq_varid(nLocationGrpId, "lat", &nLatitudeId) != NC_NOERR)
4704 : {
4705 153 : return false;
4706 : }
4707 :
4708 38 : int nDimsLongitude = 0;
4709 38 : NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLongitudeId, &nDimsLongitude));
4710 38 : int nDimsLatitude = 0;
4711 38 : NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLatitudeId, &nDimsLatitude));
4712 38 : if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
4713 : {
4714 34 : return false;
4715 : }
4716 :
4717 : std::array<int, 2> anDimLongitudeIds;
4718 4 : NCDF_ERR(nc_inq_vardimid(nLocationGrpId, nLongitudeId,
4719 : anDimLongitudeIds.data()));
4720 : std::array<int, 2> anDimLatitudeIds;
4721 4 : NCDF_ERR(
4722 : nc_inq_vardimid(nLocationGrpId, nLatitudeId, anDimLatitudeIds.data()));
4723 4 : if (anDimLongitudeIds != anDimLatitudeIds)
4724 : {
4725 0 : return false;
4726 : }
4727 :
4728 8 : if (anDimLongitudeIds[0] != anVarDimIds[0] ||
4729 4 : anDimLongitudeIds[1] != anVarDimIds[1])
4730 : {
4731 0 : return false;
4732 : }
4733 :
4734 4 : const char *pszGeolocXFullName = "/location/lon";
4735 4 : const char *pszGeolocYFullName = "/location/lat";
4736 :
4737 4 : CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
4738 : pszGeolocXFullName, pszGeolocYFullName);
4739 :
4740 4 : GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
4741 : "GEOLOCATION");
4742 :
4743 4 : CPLString osTMP;
4744 4 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
4745 :
4746 4 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
4747 4 : GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
4748 4 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
4749 :
4750 4 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
4751 4 : GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
4752 :
4753 4 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
4754 4 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
4755 :
4756 4 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
4757 4 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
4758 :
4759 4 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
4760 : "GEOLOCATION");
4761 4 : return true;
4762 : }
4763 :
4764 347 : int netCDFDataset::ProcessCFGeolocation(int nGroupId, int nVarId,
4765 : const std::string &osGeolocWKT,
4766 : std::string &osGeolocXNameOut,
4767 : std::string &osGeolocYNameOut)
4768 : {
4769 347 : bool bAddGeoloc = false;
4770 347 : char *pszCoordinates = nullptr;
4771 :
4772 : // If there is no explicit "coordinates" attribute, check if there are
4773 : // "lon" and "lat" 2D variables whose dimensions are the last
4774 : // 2 ones of the variable of interest.
4775 347 : if (NCDFGetAttr(nGroupId, nVarId, "coordinates", &pszCoordinates) !=
4776 : CE_None)
4777 : {
4778 301 : CPLFree(pszCoordinates);
4779 301 : pszCoordinates = nullptr;
4780 :
4781 301 : int nVarDims = 0;
4782 301 : NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
4783 301 : if (nVarDims >= 2)
4784 : {
4785 602 : std::vector<int> anVarDimIds(nVarDims);
4786 301 : NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
4787 :
4788 301 : int nLongitudeId = 0;
4789 301 : int nLatitudeId = 0;
4790 369 : if (nc_inq_varid(nGroupId, "lon", &nLongitudeId) == NC_NOERR &&
4791 68 : nc_inq_varid(nGroupId, "lat", &nLatitudeId) == NC_NOERR)
4792 : {
4793 68 : int nDimsLongitude = 0;
4794 68 : NCDF_ERR(
4795 : nc_inq_varndims(nGroupId, nLongitudeId, &nDimsLongitude));
4796 68 : int nDimsLatitude = 0;
4797 68 : NCDF_ERR(
4798 : nc_inq_varndims(nGroupId, nLatitudeId, &nDimsLatitude));
4799 68 : if (nDimsLongitude == 2 && nDimsLatitude == 2)
4800 : {
4801 34 : std::vector<int> anDimLongitudeIds(2);
4802 17 : NCDF_ERR(nc_inq_vardimid(nGroupId, nLongitudeId,
4803 : anDimLongitudeIds.data()));
4804 34 : std::vector<int> anDimLatitudeIds(2);
4805 17 : NCDF_ERR(nc_inq_vardimid(nGroupId, nLatitudeId,
4806 : anDimLatitudeIds.data()));
4807 17 : if (anDimLongitudeIds == anDimLatitudeIds &&
4808 34 : anVarDimIds[anVarDimIds.size() - 2] ==
4809 51 : anDimLongitudeIds[0] &&
4810 34 : anVarDimIds[anVarDimIds.size() - 1] ==
4811 17 : anDimLongitudeIds[1])
4812 : {
4813 17 : pszCoordinates = CPLStrdup("lon lat");
4814 : }
4815 : }
4816 : }
4817 : }
4818 : }
4819 :
4820 347 : if (pszCoordinates)
4821 : {
4822 : // Get X and Y geolocation names from coordinates attribute.
4823 : const CPLStringList aosCoordinates(
4824 126 : NCDFTokenizeCoordinatesAttribute(pszCoordinates));
4825 63 : if (aosCoordinates.size() >= 2)
4826 : {
4827 : char szGeolocXName[NC_MAX_NAME + 1];
4828 : char szGeolocYName[NC_MAX_NAME + 1];
4829 60 : szGeolocXName[0] = '\0';
4830 60 : szGeolocYName[0] = '\0';
4831 :
4832 : // Test that each variable is longitude/latitude.
4833 193 : for (int i = 0; i < aosCoordinates.size(); i++)
4834 : {
4835 133 : if (NCDFIsVarLongitude(nGroupId, -1, aosCoordinates[i]))
4836 : {
4837 49 : int nOtherGroupId = -1;
4838 49 : int nOtherVarId = -1;
4839 : // Check that the variable actually exists
4840 : // Needed on Sentinel-3 products
4841 49 : if (NCDFResolveVar(nGroupId, aosCoordinates[i],
4842 49 : &nOtherGroupId, &nOtherVarId) == CE_None)
4843 : {
4844 47 : snprintf(szGeolocXName, sizeof(szGeolocXName), "%s",
4845 : aosCoordinates[i]);
4846 : }
4847 : }
4848 84 : else if (NCDFIsVarLatitude(nGroupId, -1, aosCoordinates[i]))
4849 : {
4850 49 : int nOtherGroupId = -1;
4851 49 : int nOtherVarId = -1;
4852 : // Check that the variable actually exists
4853 : // Needed on Sentinel-3 products
4854 49 : if (NCDFResolveVar(nGroupId, aosCoordinates[i],
4855 49 : &nOtherGroupId, &nOtherVarId) == CE_None)
4856 : {
4857 47 : snprintf(szGeolocYName, sizeof(szGeolocYName), "%s",
4858 : aosCoordinates[i]);
4859 : }
4860 : }
4861 : }
4862 : // Add GEOLOCATION metadata.
4863 60 : if (!EQUAL(szGeolocXName, "") && !EQUAL(szGeolocYName, ""))
4864 : {
4865 47 : osGeolocXNameOut = szGeolocXName;
4866 47 : osGeolocYNameOut = szGeolocYName;
4867 :
4868 47 : char *pszGeolocXFullName = nullptr;
4869 47 : char *pszGeolocYFullName = nullptr;
4870 47 : if (NCDFResolveVarFullName(nGroupId, szGeolocXName,
4871 94 : &pszGeolocXFullName) == CE_None &&
4872 47 : NCDFResolveVarFullName(nGroupId, szGeolocYName,
4873 : &pszGeolocYFullName) == CE_None)
4874 : {
4875 47 : if (bSwitchedXY)
4876 : {
4877 2 : std::swap(pszGeolocXFullName, pszGeolocYFullName);
4878 2 : GDALPamDataset::SetMetadataItem("SWAP_XY", "YES",
4879 : "GEOLOCATION");
4880 : }
4881 :
4882 47 : bAddGeoloc = true;
4883 47 : CPLDebug("GDAL_netCDF",
4884 : "using variables %s and %s for GEOLOCATION",
4885 : pszGeolocXFullName, pszGeolocYFullName);
4886 :
4887 47 : GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
4888 : "GEOLOCATION");
4889 :
4890 94 : CPLString osTMP;
4891 47 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4892 47 : pszGeolocXFullName);
4893 :
4894 47 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP,
4895 : "GEOLOCATION");
4896 47 : GDALPamDataset::SetMetadataItem("X_BAND", "1",
4897 : "GEOLOCATION");
4898 47 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4899 47 : pszGeolocYFullName);
4900 :
4901 47 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP,
4902 : "GEOLOCATION");
4903 47 : GDALPamDataset::SetMetadataItem("Y_BAND", "1",
4904 : "GEOLOCATION");
4905 :
4906 47 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0",
4907 : "GEOLOCATION");
4908 47 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1",
4909 : "GEOLOCATION");
4910 :
4911 47 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0",
4912 : "GEOLOCATION");
4913 47 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1",
4914 : "GEOLOCATION");
4915 :
4916 47 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
4917 : "PIXEL_CENTER",
4918 : "GEOLOCATION");
4919 : }
4920 : else
4921 : {
4922 0 : CPLDebug("GDAL_netCDF",
4923 : "cannot resolve location of "
4924 : "lat/lon variables specified by the coordinates "
4925 : "attribute [%s]",
4926 : pszCoordinates);
4927 : }
4928 47 : CPLFree(pszGeolocXFullName);
4929 47 : CPLFree(pszGeolocYFullName);
4930 : }
4931 : else
4932 : {
4933 13 : CPLDebug("GDAL_netCDF",
4934 : "coordinates attribute [%s] is unsupported",
4935 : pszCoordinates);
4936 : }
4937 : }
4938 : else
4939 : {
4940 3 : CPLDebug("GDAL_netCDF",
4941 : "coordinates attribute [%s] with %d element(s) is "
4942 : "unsupported",
4943 : pszCoordinates, aosCoordinates.size());
4944 : }
4945 : }
4946 :
4947 : else
4948 : {
4949 284 : bAddGeoloc = ProcessNASAL2OceanGeoLocation(nGroupId, nVarId);
4950 :
4951 284 : if (!bAddGeoloc)
4952 283 : bAddGeoloc = ProcessNASAEMITGeoLocation(nGroupId, nVarId);
4953 : }
4954 :
4955 347 : CPLFree(pszCoordinates);
4956 :
4957 347 : return bAddGeoloc;
4958 : }
4959 :
4960 8 : CPLErr netCDFDataset::Set1DGeolocation(int nGroupId, int nVarId,
4961 : const char *szDimName)
4962 : {
4963 : // Get values.
4964 8 : char *pszVarValues = nullptr;
4965 8 : CPLErr eErr = NCDFGet1DVar(nGroupId, nVarId, &pszVarValues);
4966 8 : if (eErr != CE_None)
4967 0 : return eErr;
4968 :
4969 : // Write metadata.
4970 8 : char szTemp[NC_MAX_NAME + 1 + 32] = {};
4971 8 : snprintf(szTemp, sizeof(szTemp), "%s_VALUES", szDimName);
4972 8 : GDALPamDataset::SetMetadataItem(szTemp, pszVarValues, "GEOLOCATION2");
4973 :
4974 8 : CPLFree(pszVarValues);
4975 :
4976 8 : return CE_None;
4977 : }
4978 :
4979 0 : double *netCDFDataset::Get1DGeolocation(CPL_UNUSED const char *szDimName,
4980 : int &nVarLen)
4981 : {
4982 0 : nVarLen = 0;
4983 :
4984 : // Get Y_VALUES as tokens.
4985 : char **papszValues =
4986 0 : NCDFTokenizeArray(GetMetadataItem("Y_VALUES", "GEOLOCATION2"));
4987 0 : if (papszValues == nullptr)
4988 0 : return nullptr;
4989 :
4990 : // Initialize and fill array.
4991 0 : nVarLen = CSLCount(papszValues);
4992 : double *pdfVarValues =
4993 0 : static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
4994 :
4995 0 : for (int i = 0, j = 0; i < nVarLen; i++)
4996 : {
4997 0 : if (!bBottomUp)
4998 0 : j = nVarLen - 1 - i;
4999 : else
5000 0 : j = i; // Invert latitude values.
5001 0 : char *pszTemp = nullptr;
5002 0 : pdfVarValues[j] = CPLStrtod(papszValues[i], &pszTemp);
5003 : }
5004 0 : CSLDestroy(papszValues);
5005 :
5006 0 : return pdfVarValues;
5007 : }
5008 :
5009 : /************************************************************************/
5010 : /* SetSpatialRefNoUpdate() */
5011 : /************************************************************************/
5012 :
5013 255 : void netCDFDataset::SetSpatialRefNoUpdate(const OGRSpatialReference *poSRS)
5014 : {
5015 255 : m_oSRS.Clear();
5016 255 : if (poSRS)
5017 248 : m_oSRS = *poSRS;
5018 255 : m_bHasProjection = true;
5019 255 : }
5020 :
5021 : /************************************************************************/
5022 : /* SetSpatialRef() */
5023 : /************************************************************************/
5024 :
5025 76 : CPLErr netCDFDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
5026 : {
5027 152 : CPLMutexHolderD(&hNCMutex);
5028 :
5029 76 : if (GetAccess() != GA_Update || m_bHasProjection)
5030 : {
5031 0 : CPLError(CE_Failure, CPLE_AppDefined,
5032 : "netCDFDataset::_SetProjection() should only be called once "
5033 : "in update mode!");
5034 0 : return CE_Failure;
5035 : }
5036 :
5037 76 : if (m_bHasGeoTransform)
5038 : {
5039 32 : SetSpatialRefNoUpdate(poSRS);
5040 :
5041 : // For NC4/NC4C, writing both projection variables and data,
5042 : // followed by redefining nodata value, cancels the projection
5043 : // info from the Band variable, so for now only write the
5044 : // variable definitions, and write data at the end.
5045 : // See https://trac.osgeo.org/gdal/ticket/7245
5046 32 : return AddProjectionVars(true, nullptr, nullptr);
5047 : }
5048 :
5049 44 : SetSpatialRefNoUpdate(poSRS);
5050 :
5051 44 : return CE_None;
5052 : }
5053 :
5054 : /************************************************************************/
5055 : /* SetGeoTransformNoUpdate() */
5056 : /************************************************************************/
5057 :
5058 275 : void netCDFDataset::SetGeoTransformNoUpdate(double *padfTransform)
5059 : {
5060 275 : memcpy(m_adfGeoTransform, padfTransform, sizeof(double) * 6);
5061 275 : m_bHasGeoTransform = true;
5062 275 : }
5063 :
5064 : /************************************************************************/
5065 : /* SetGeoTransform() */
5066 : /************************************************************************/
5067 :
5068 77 : CPLErr netCDFDataset::SetGeoTransform(double *padfTransform)
5069 : {
5070 154 : CPLMutexHolderD(&hNCMutex);
5071 :
5072 77 : if (GetAccess() != GA_Update || m_bHasGeoTransform)
5073 : {
5074 0 : CPLError(CE_Failure, CPLE_AppDefined,
5075 : "netCDFDataset::SetGeoTransform() should only be called once "
5076 : "in update mode!");
5077 0 : return CE_Failure;
5078 : }
5079 :
5080 77 : CPLDebug("GDAL_netCDF", "SetGeoTransform(%f,%f,%f,%f,%f,%f)",
5081 77 : padfTransform[0], padfTransform[1], padfTransform[2],
5082 77 : padfTransform[3], padfTransform[4], padfTransform[5]);
5083 :
5084 77 : if (m_bHasProjection)
5085 : {
5086 3 : SetGeoTransformNoUpdate(padfTransform);
5087 :
5088 : // For NC4/NC4C, writing both projection variables and data,
5089 : // followed by redefining nodata value, cancels the projection
5090 : // info from the Band variable, so for now only write the
5091 : // variable definitions, and write data at the end.
5092 : // See https://trac.osgeo.org/gdal/ticket/7245
5093 3 : return AddProjectionVars(true, nullptr, nullptr);
5094 : }
5095 :
5096 74 : SetGeoTransformNoUpdate(padfTransform);
5097 74 : return CE_None;
5098 : }
5099 :
5100 : /************************************************************************/
5101 : /* NCDFWriteSRSVariable() */
5102 : /************************************************************************/
5103 :
5104 128 : int NCDFWriteSRSVariable(int cdfid, const OGRSpatialReference *poSRS,
5105 : char **ppszCFProjection, bool bWriteGDALTags,
5106 : const std::string &srsVarName)
5107 : {
5108 128 : char *pszCFProjection = nullptr;
5109 128 : char **papszKeyValues = nullptr;
5110 128 : poSRS->exportToCF1(&pszCFProjection, &papszKeyValues, nullptr, nullptr);
5111 :
5112 128 : if (bWriteGDALTags)
5113 : {
5114 127 : const char *pszWKT = CSLFetchNameValue(papszKeyValues, NCDF_CRS_WKT);
5115 127 : if (pszWKT)
5116 : {
5117 : // SPATIAL_REF is deprecated. Will be removed in GDAL 4.
5118 127 : papszKeyValues =
5119 127 : CSLSetNameValue(papszKeyValues, NCDF_SPATIAL_REF, pszWKT);
5120 : }
5121 : }
5122 :
5123 128 : const int nValues = CSLCount(papszKeyValues);
5124 :
5125 : int NCDFVarID;
5126 256 : std::string varNameRadix(pszCFProjection);
5127 128 : int nCounter = 2;
5128 : while (true)
5129 : {
5130 130 : NCDFVarID = -1;
5131 130 : nc_inq_varid(cdfid, pszCFProjection, &NCDFVarID);
5132 130 : if (NCDFVarID < 0)
5133 125 : break;
5134 :
5135 5 : int nbAttr = 0;
5136 5 : NCDF_ERR(nc_inq_varnatts(cdfid, NCDFVarID, &nbAttr));
5137 5 : bool bSame = nbAttr == nValues;
5138 41 : for (int i = 0; bSame && (i < nbAttr); i++)
5139 : {
5140 : char szAttrName[NC_MAX_NAME + 1];
5141 38 : szAttrName[0] = 0;
5142 38 : NCDF_ERR(nc_inq_attname(cdfid, NCDFVarID, i, szAttrName));
5143 :
5144 : const char *pszValue =
5145 38 : CSLFetchNameValue(papszKeyValues, szAttrName);
5146 38 : if (!pszValue)
5147 : {
5148 0 : bSame = false;
5149 2 : break;
5150 : }
5151 :
5152 38 : nc_type atttype = NC_NAT;
5153 38 : size_t attlen = 0;
5154 38 : NCDF_ERR(
5155 : nc_inq_att(cdfid, NCDFVarID, szAttrName, &atttype, &attlen));
5156 38 : if (atttype != NC_CHAR && atttype != NC_DOUBLE)
5157 : {
5158 0 : bSame = false;
5159 0 : break;
5160 : }
5161 38 : if (atttype == NC_CHAR)
5162 : {
5163 15 : if (CPLGetValueType(pszValue) != CPL_VALUE_STRING)
5164 : {
5165 0 : bSame = false;
5166 0 : break;
5167 : }
5168 15 : std::string val;
5169 15 : val.resize(attlen);
5170 15 : nc_get_att_text(cdfid, NCDFVarID, szAttrName, &val[0]);
5171 15 : if (val != pszValue)
5172 : {
5173 0 : bSame = false;
5174 0 : break;
5175 : }
5176 : }
5177 : else
5178 : {
5179 : const CPLStringList aosTokens(
5180 23 : CSLTokenizeString2(pszValue, ",", 0));
5181 23 : if (static_cast<size_t>(aosTokens.size()) != attlen)
5182 : {
5183 0 : bSame = false;
5184 0 : break;
5185 : }
5186 : double vals[2];
5187 23 : nc_get_att_double(cdfid, NCDFVarID, szAttrName, vals);
5188 44 : if (vals[0] != CPLAtof(aosTokens[0]) ||
5189 21 : (attlen == 2 && vals[1] != CPLAtof(aosTokens[1])))
5190 : {
5191 2 : bSame = false;
5192 2 : break;
5193 : }
5194 : }
5195 : }
5196 5 : if (bSame)
5197 : {
5198 3 : *ppszCFProjection = pszCFProjection;
5199 3 : CSLDestroy(papszKeyValues);
5200 3 : return NCDFVarID;
5201 : }
5202 2 : CPLFree(pszCFProjection);
5203 2 : pszCFProjection =
5204 2 : CPLStrdup(CPLSPrintf("%s_%d", varNameRadix.c_str(), nCounter));
5205 2 : nCounter++;
5206 2 : }
5207 :
5208 125 : *ppszCFProjection = pszCFProjection;
5209 :
5210 : const char *pszVarName;
5211 :
5212 125 : if (srsVarName != "")
5213 : {
5214 38 : pszVarName = srsVarName.c_str();
5215 : }
5216 : else
5217 : {
5218 87 : pszVarName = pszCFProjection;
5219 : }
5220 :
5221 125 : int status = nc_def_var(cdfid, pszVarName, NC_CHAR, 0, nullptr, &NCDFVarID);
5222 125 : NCDF_ERR(status);
5223 1199 : for (int i = 0; i < nValues; ++i)
5224 : {
5225 1074 : char *pszKey = nullptr;
5226 1074 : const char *pszValue = CPLParseNameValue(papszKeyValues[i], &pszKey);
5227 1074 : if (pszKey && pszValue)
5228 : {
5229 2148 : const CPLStringList aosTokens(CSLTokenizeString2(pszValue, ",", 0));
5230 1074 : double adfValues[2] = {0, 0};
5231 1074 : const int nDoubleCount = std::min(2, aosTokens.size());
5232 1074 : if (!(aosTokens.size() == 2 &&
5233 2147 : CPLGetValueType(aosTokens[0]) != CPL_VALUE_STRING) &&
5234 1073 : CPLGetValueType(pszValue) == CPL_VALUE_STRING)
5235 : {
5236 499 : status = nc_put_att_text(cdfid, NCDFVarID, pszKey,
5237 : strlen(pszValue), pszValue);
5238 : }
5239 : else
5240 : {
5241 1151 : for (int j = 0; j < nDoubleCount; ++j)
5242 576 : adfValues[j] = CPLAtof(aosTokens[j]);
5243 575 : status = nc_put_att_double(cdfid, NCDFVarID, pszKey, NC_DOUBLE,
5244 : nDoubleCount, adfValues);
5245 : }
5246 1074 : NCDF_ERR(status);
5247 : }
5248 1074 : CPLFree(pszKey);
5249 : }
5250 :
5251 125 : CSLDestroy(papszKeyValues);
5252 125 : return NCDFVarID;
5253 : }
5254 :
5255 : /************************************************************************/
5256 : /* NCDFWriteLonLatVarsAttributes() */
5257 : /************************************************************************/
5258 :
5259 101 : void NCDFWriteLonLatVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarLonID,
5260 : int nVarLatID)
5261 : {
5262 :
5263 : try
5264 : {
5265 101 : vcdf.nc_put_vatt_text(nVarLatID, CF_STD_NAME, CF_LATITUDE_STD_NAME);
5266 101 : vcdf.nc_put_vatt_text(nVarLatID, CF_LNG_NAME, CF_LATITUDE_LNG_NAME);
5267 101 : vcdf.nc_put_vatt_text(nVarLatID, CF_UNITS, CF_DEGREES_NORTH);
5268 101 : vcdf.nc_put_vatt_text(nVarLonID, CF_STD_NAME, CF_LONGITUDE_STD_NAME);
5269 101 : vcdf.nc_put_vatt_text(nVarLonID, CF_LNG_NAME, CF_LONGITUDE_LNG_NAME);
5270 101 : vcdf.nc_put_vatt_text(nVarLonID, CF_UNITS, CF_DEGREES_EAST);
5271 : }
5272 0 : catch (nccfdriver::SG_Exception &e)
5273 : {
5274 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5275 : }
5276 101 : }
5277 :
5278 : /************************************************************************/
5279 : /* NCDFWriteRLonRLatVarsAttributes() */
5280 : /************************************************************************/
5281 :
5282 0 : void NCDFWriteRLonRLatVarsAttributes(nccfdriver::netCDFVID &vcdf,
5283 : int nVarRLonID, int nVarRLatID)
5284 : {
5285 : try
5286 : {
5287 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_STD_NAME, "grid_latitude");
5288 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_LNG_NAME,
5289 : "latitude in rotated pole grid");
5290 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_UNITS, "degrees");
5291 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_AXIS, "Y");
5292 :
5293 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_STD_NAME, "grid_longitude");
5294 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_LNG_NAME,
5295 : "longitude in rotated pole grid");
5296 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_UNITS, "degrees");
5297 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_AXIS, "X");
5298 : }
5299 0 : catch (nccfdriver::SG_Exception &e)
5300 : {
5301 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5302 : }
5303 0 : }
5304 :
5305 : /************************************************************************/
5306 : /* NCDFGetProjectedCFUnit() */
5307 : /************************************************************************/
5308 :
5309 40 : std::string NCDFGetProjectedCFUnit(const OGRSpatialReference *poSRS)
5310 : {
5311 40 : char *pszUnitsToWrite = nullptr;
5312 40 : poSRS->exportToCF1(nullptr, nullptr, &pszUnitsToWrite, nullptr);
5313 40 : const std::string osRet = pszUnitsToWrite ? pszUnitsToWrite : std::string();
5314 40 : CPLFree(pszUnitsToWrite);
5315 80 : return osRet;
5316 : }
5317 :
5318 : /************************************************************************/
5319 : /* NCDFWriteXYVarsAttributes() */
5320 : /************************************************************************/
5321 :
5322 29 : void NCDFWriteXYVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarXID,
5323 : int nVarYID, const OGRSpatialReference *poSRS)
5324 : {
5325 58 : const std::string osUnitsToWrite = NCDFGetProjectedCFUnit(poSRS);
5326 :
5327 : try
5328 : {
5329 29 : vcdf.nc_put_vatt_text(nVarXID, CF_STD_NAME, CF_PROJ_X_COORD);
5330 29 : vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME, CF_PROJ_X_COORD_LONG_NAME);
5331 29 : if (!osUnitsToWrite.empty())
5332 29 : vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, osUnitsToWrite.c_str());
5333 29 : vcdf.nc_put_vatt_text(nVarYID, CF_STD_NAME, CF_PROJ_Y_COORD);
5334 29 : vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME, CF_PROJ_Y_COORD_LONG_NAME);
5335 29 : if (!osUnitsToWrite.empty())
5336 29 : vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, osUnitsToWrite.c_str());
5337 : }
5338 0 : catch (nccfdriver::SG_Exception &e)
5339 : {
5340 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5341 : }
5342 29 : }
5343 :
5344 : /************************************************************************/
5345 : /* AddProjectionVars() */
5346 : /************************************************************************/
5347 :
5348 164 : CPLErr netCDFDataset::AddProjectionVars(bool bDefsOnly,
5349 : GDALProgressFunc pfnProgress,
5350 : void *pProgressData)
5351 : {
5352 164 : if (nCFVersion >= 1.8)
5353 0 : return CE_None; // do nothing
5354 :
5355 164 : bool bWriteGridMapping = false;
5356 164 : bool bWriteLonLat = false;
5357 164 : bool bHasGeoloc = false;
5358 164 : bool bWriteGDALTags = false;
5359 164 : bool bWriteGeoTransform = false;
5360 :
5361 : // For GEOLOCATION information.
5362 164 : GDALDatasetH hDS_X = nullptr;
5363 164 : GDALRasterBandH hBand_X = nullptr;
5364 164 : GDALDatasetH hDS_Y = nullptr;
5365 164 : GDALRasterBandH hBand_Y = nullptr;
5366 :
5367 328 : OGRSpatialReference oSRS(m_oSRS);
5368 164 : if (!m_oSRS.IsEmpty())
5369 : {
5370 138 : if (oSRS.IsProjected())
5371 50 : bIsProjected = true;
5372 88 : else if (oSRS.IsGeographic())
5373 88 : bIsGeographic = true;
5374 : }
5375 :
5376 164 : if (bDefsOnly)
5377 : {
5378 82 : char *pszProjection = nullptr;
5379 82 : m_oSRS.exportToWkt(&pszProjection);
5380 82 : CPLDebug("GDAL_netCDF",
5381 : "SetProjection, WKT now = [%s]\nprojected: %d geographic: %d",
5382 82 : pszProjection ? pszProjection : "(null)",
5383 82 : static_cast<int>(bIsProjected),
5384 82 : static_cast<int>(bIsGeographic));
5385 82 : CPLFree(pszProjection);
5386 :
5387 82 : if (!m_bHasGeoTransform)
5388 5 : CPLDebug("GDAL_netCDF",
5389 : "netCDFDataset::AddProjectionVars() called, "
5390 : "but GeoTransform has not yet been defined!");
5391 :
5392 82 : if (!m_bHasProjection)
5393 6 : CPLDebug("GDAL_netCDF",
5394 : "netCDFDataset::AddProjectionVars() called, "
5395 : "but Projection has not yet been defined!");
5396 : }
5397 :
5398 : // Check GEOLOCATION information.
5399 164 : char **papszGeolocationInfo = netCDFDataset::GetMetadata("GEOLOCATION");
5400 164 : if (papszGeolocationInfo != nullptr)
5401 : {
5402 : // Look for geolocation datasets.
5403 : const char *pszDSName =
5404 10 : CSLFetchNameValue(papszGeolocationInfo, "X_DATASET");
5405 10 : if (pszDSName != nullptr)
5406 10 : hDS_X = GDALOpenShared(pszDSName, GA_ReadOnly);
5407 10 : pszDSName = CSLFetchNameValue(papszGeolocationInfo, "Y_DATASET");
5408 10 : if (pszDSName != nullptr)
5409 10 : hDS_Y = GDALOpenShared(pszDSName, GA_ReadOnly);
5410 :
5411 10 : if (hDS_X != nullptr && hDS_Y != nullptr)
5412 : {
5413 10 : int nBand = std::max(1, atoi(CSLFetchNameValueDef(
5414 10 : papszGeolocationInfo, "X_BAND", "0")));
5415 10 : hBand_X = GDALGetRasterBand(hDS_X, nBand);
5416 10 : nBand = std::max(1, atoi(CSLFetchNameValueDef(papszGeolocationInfo,
5417 10 : "Y_BAND", "0")));
5418 10 : hBand_Y = GDALGetRasterBand(hDS_Y, nBand);
5419 :
5420 : // If geoloc bands are found, do basic validation based on their
5421 : // dimensions.
5422 10 : if (hBand_X != nullptr && hBand_Y != nullptr)
5423 : {
5424 10 : int nXSize_XBand = GDALGetRasterXSize(hDS_X);
5425 10 : int nYSize_XBand = GDALGetRasterYSize(hDS_X);
5426 10 : int nXSize_YBand = GDALGetRasterXSize(hDS_Y);
5427 10 : int nYSize_YBand = GDALGetRasterYSize(hDS_Y);
5428 :
5429 : // TODO 1D geolocation arrays not implemented.
5430 10 : if (nYSize_XBand == 1 && nYSize_YBand == 1)
5431 : {
5432 0 : bHasGeoloc = false;
5433 0 : CPLDebug("GDAL_netCDF",
5434 : "1D GEOLOCATION arrays not supported yet");
5435 : }
5436 : // 2D bands must have same sizes as the raster bands.
5437 10 : else if (nXSize_XBand != nRasterXSize ||
5438 10 : nYSize_XBand != nRasterYSize ||
5439 10 : nXSize_YBand != nRasterXSize ||
5440 10 : nYSize_YBand != nRasterYSize)
5441 : {
5442 0 : bHasGeoloc = false;
5443 0 : CPLDebug("GDAL_netCDF",
5444 : "GEOLOCATION array sizes (%dx%d %dx%d) differ "
5445 : "from raster (%dx%d), not supported",
5446 : nXSize_XBand, nYSize_XBand, nXSize_YBand,
5447 : nYSize_YBand, nRasterXSize, nRasterYSize);
5448 : }
5449 : else
5450 : {
5451 10 : bHasGeoloc = true;
5452 10 : CPLDebug("GDAL_netCDF",
5453 : "dataset has GEOLOCATION information, will try to "
5454 : "write it");
5455 : }
5456 : }
5457 : }
5458 : }
5459 :
5460 : // Process projection options.
5461 164 : if (bIsProjected)
5462 : {
5463 : bool bIsCfProjection =
5464 50 : oSRS.exportToCF1(nullptr, nullptr, nullptr, nullptr) == OGRERR_NONE;
5465 50 : bWriteGridMapping = true;
5466 50 : bWriteGDALTags = CPL_TO_BOOL(
5467 50 : CSLFetchBoolean(papszCreationOptions, "WRITE_GDAL_TAGS", TRUE));
5468 : // Force WRITE_GDAL_TAGS if is not a CF projection.
5469 50 : if (!bWriteGDALTags && !bIsCfProjection)
5470 0 : bWriteGDALTags = true;
5471 50 : if (bWriteGDALTags)
5472 50 : bWriteGeoTransform = true;
5473 :
5474 : // Write lon/lat: default is NO, except if has geolocation.
5475 : // With IF_NEEDED: write if has geoloc or is not CF projection.
5476 : const char *pszValue =
5477 50 : CSLFetchNameValue(papszCreationOptions, "WRITE_LONLAT");
5478 50 : if (pszValue)
5479 : {
5480 2 : if (EQUAL(pszValue, "IF_NEEDED"))
5481 : {
5482 0 : bWriteLonLat = bHasGeoloc || !bIsCfProjection;
5483 : }
5484 : else
5485 : {
5486 2 : bWriteLonLat = CPLTestBool(pszValue);
5487 : }
5488 : }
5489 : else
5490 : {
5491 48 : bWriteLonLat = bHasGeoloc;
5492 : }
5493 :
5494 : // Save value of pszCFCoordinates for later.
5495 50 : if (bWriteLonLat)
5496 : {
5497 4 : pszCFCoordinates = NCDF_LONLAT;
5498 : }
5499 : }
5500 : else
5501 : {
5502 : // Files without a Datum will not have a grid_mapping variable and
5503 : // geographic information.
5504 114 : bWriteGridMapping = bIsGeographic;
5505 :
5506 114 : if (bHasGeoloc)
5507 : {
5508 8 : bWriteLonLat = true;
5509 : }
5510 : else
5511 : {
5512 106 : bWriteGDALTags = CPL_TO_BOOL(CSLFetchBoolean(
5513 106 : papszCreationOptions, "WRITE_GDAL_TAGS", bWriteGridMapping));
5514 106 : if (bWriteGDALTags)
5515 88 : bWriteGeoTransform = true;
5516 :
5517 106 : const char *pszValue = CSLFetchNameValueDef(papszCreationOptions,
5518 : "WRITE_LONLAT", "YES");
5519 106 : if (EQUAL(pszValue, "IF_NEEDED"))
5520 0 : bWriteLonLat = true;
5521 : else
5522 106 : bWriteLonLat = CPLTestBool(pszValue);
5523 : // Don't write lon/lat if no source geotransform.
5524 106 : if (!m_bHasGeoTransform)
5525 0 : bWriteLonLat = false;
5526 : // If we don't write lon/lat, set dimnames to X/Y and write gdal
5527 : // tags.
5528 106 : if (!bWriteLonLat)
5529 : {
5530 0 : CPLError(CE_Warning, CPLE_AppDefined,
5531 : "creating geographic file without lon/lat values!");
5532 0 : if (m_bHasGeoTransform)
5533 : {
5534 0 : bWriteGDALTags = true; // Not desirable if no geotransform.
5535 0 : bWriteGeoTransform = true;
5536 : }
5537 : }
5538 : }
5539 : }
5540 :
5541 : // Make sure we write grid_mapping if we need to write GDAL tags.
5542 164 : if (bWriteGDALTags)
5543 138 : bWriteGridMapping = true;
5544 :
5545 : // bottom-up value: new driver is bottom-up by default.
5546 : // Override with WRITE_BOTTOMUP.
5547 164 : bBottomUp = CPL_TO_BOOL(
5548 164 : CSLFetchBoolean(papszCreationOptions, "WRITE_BOTTOMUP", TRUE));
5549 :
5550 164 : if (bDefsOnly)
5551 : {
5552 82 : CPLDebug(
5553 : "GDAL_netCDF",
5554 : "bIsProjected=%d bIsGeographic=%d bWriteGridMapping=%d "
5555 : "bWriteGDALTags=%d bWriteLonLat=%d bBottomUp=%d bHasGeoloc=%d",
5556 82 : static_cast<int>(bIsProjected), static_cast<int>(bIsGeographic),
5557 : static_cast<int>(bWriteGridMapping),
5558 : static_cast<int>(bWriteGDALTags), static_cast<int>(bWriteLonLat),
5559 82 : static_cast<int>(bBottomUp), static_cast<int>(bHasGeoloc));
5560 : }
5561 :
5562 : // Exit if nothing to do.
5563 164 : if (!bIsProjected && !bWriteLonLat)
5564 0 : return CE_None;
5565 :
5566 : // Define dimension names.
5567 :
5568 164 : constexpr const char *ROTATED_POLE_VAR_NAME = "rotated_pole";
5569 :
5570 164 : if (bDefsOnly)
5571 : {
5572 82 : int nVarLonID = -1;
5573 82 : int nVarLatID = -1;
5574 82 : int nVarXID = -1;
5575 82 : int nVarYID = -1;
5576 :
5577 82 : m_bAddedProjectionVarsDefs = true;
5578 :
5579 : // Make sure we are in define mode.
5580 82 : SetDefineMode(true);
5581 :
5582 : // Write projection attributes.
5583 82 : if (bWriteGridMapping)
5584 : {
5585 69 : const int NCDFVarID = NCDFWriteSRSVariable(
5586 : cdfid, &oSRS, &pszCFProjection, bWriteGDALTags);
5587 69 : if (NCDFVarID < 0)
5588 0 : return CE_Failure;
5589 :
5590 : // Optional GDAL custom projection tags.
5591 69 : if (bWriteGDALTags)
5592 : {
5593 138 : CPLString osGeoTransform;
5594 483 : for (int i = 0; i < 6; i++)
5595 : {
5596 : osGeoTransform +=
5597 414 : CPLSPrintf("%.17g ", m_adfGeoTransform[i]);
5598 : }
5599 69 : CPLDebug("GDAL_netCDF", "szGeoTransform = %s",
5600 : osGeoTransform.c_str());
5601 :
5602 : // if( strlen(pszProj4Defn) > 0 ) {
5603 : // nc_put_att_text(cdfid, NCDFVarID, "proj4",
5604 : // strlen(pszProj4Defn), pszProj4Defn);
5605 : // }
5606 :
5607 : // For now, write the geotransform for back-compat or else
5608 : // the old (1.8.1) driver overrides the CF geotransform with
5609 : // empty values from dfNN, dfSN, dfEE, dfWE;
5610 :
5611 : // TODO: fix this in 1.8 branch, and then remove this here.
5612 69 : if (bWriteGeoTransform && m_bHasGeoTransform)
5613 : {
5614 : {
5615 68 : const int status = nc_put_att_text(
5616 : cdfid, NCDFVarID, NCDF_GEOTRANSFORM,
5617 : osGeoTransform.size(), osGeoTransform.c_str());
5618 68 : NCDF_ERR(status);
5619 : }
5620 : }
5621 : }
5622 :
5623 : // Write projection variable to band variable.
5624 : // Need to call later if there are no bands.
5625 69 : AddGridMappingRef();
5626 : } // end if( bWriteGridMapping )
5627 :
5628 : // Write CF Projection vars.
5629 :
5630 82 : const bool bIsRotatedPole =
5631 151 : pszCFProjection != nullptr &&
5632 69 : EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
5633 82 : if (bIsRotatedPole)
5634 : {
5635 : // Rename dims to rlat/rlon.
5636 : papszDimName
5637 0 : .Clear(); // If we add other dims one day, this has to change
5638 0 : papszDimName.AddString(NCDF_DIMNAME_RLAT);
5639 0 : papszDimName.AddString(NCDF_DIMNAME_RLON);
5640 :
5641 0 : int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_RLAT);
5642 0 : NCDF_ERR(status);
5643 0 : status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_RLON);
5644 0 : NCDF_ERR(status);
5645 : }
5646 : // Rename dimensions if lon/lat.
5647 82 : else if (!bIsProjected && !bHasGeoloc)
5648 : {
5649 : // Rename dims to lat/lon.
5650 : papszDimName
5651 53 : .Clear(); // If we add other dims one day, this has to change
5652 53 : papszDimName.AddString(NCDF_DIMNAME_LAT);
5653 53 : papszDimName.AddString(NCDF_DIMNAME_LON);
5654 :
5655 53 : int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_LAT);
5656 53 : NCDF_ERR(status);
5657 53 : status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_LON);
5658 53 : NCDF_ERR(status);
5659 : }
5660 :
5661 : // Write X/Y attributes.
5662 : else /* if( bIsProjected || bHasGeoloc ) */
5663 : {
5664 : // X
5665 : int anXDims[1];
5666 29 : anXDims[0] = nXDimID;
5667 29 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
5668 : CF_PROJ_X_VAR_NAME, NC_DOUBLE);
5669 29 : int status = nc_def_var(cdfid, CF_PROJ_X_VAR_NAME, NC_DOUBLE, 1,
5670 : anXDims, &nVarXID);
5671 29 : NCDF_ERR(status);
5672 :
5673 : // Y
5674 : int anYDims[1];
5675 29 : anYDims[0] = nYDimID;
5676 29 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
5677 : CF_PROJ_Y_VAR_NAME, NC_DOUBLE);
5678 29 : status = nc_def_var(cdfid, CF_PROJ_Y_VAR_NAME, NC_DOUBLE, 1,
5679 : anYDims, &nVarYID);
5680 29 : NCDF_ERR(status);
5681 :
5682 29 : if (bIsProjected)
5683 : {
5684 25 : NCDFWriteXYVarsAttributes(this->vcdf, nVarXID, nVarYID, &oSRS);
5685 : }
5686 : else
5687 : {
5688 4 : CPLAssert(bHasGeoloc);
5689 : try
5690 : {
5691 4 : vcdf.nc_put_vatt_text(nVarXID, CF_AXIS, CF_SG_X_AXIS);
5692 4 : vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME,
5693 : "x-coordinate in Cartesian system");
5694 4 : vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, "m");
5695 4 : vcdf.nc_put_vatt_text(nVarYID, CF_AXIS, CF_SG_Y_AXIS);
5696 4 : vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME,
5697 : "y-coordinate in Cartesian system");
5698 4 : vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, "m");
5699 :
5700 4 : pszCFCoordinates = NCDF_LONLAT;
5701 : }
5702 0 : catch (nccfdriver::SG_Exception &e)
5703 : {
5704 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5705 0 : return CE_Failure;
5706 : }
5707 : }
5708 : }
5709 :
5710 : // Write lat/lon attributes if needed.
5711 82 : if (bWriteLonLat)
5712 : {
5713 59 : int *panLatDims = nullptr;
5714 59 : int *panLonDims = nullptr;
5715 59 : int nLatDims = -1;
5716 59 : int nLonDims = -1;
5717 :
5718 : // Get information.
5719 59 : if (bHasGeoloc)
5720 : {
5721 : // Geoloc
5722 5 : nLatDims = 2;
5723 : panLatDims =
5724 5 : static_cast<int *>(CPLCalloc(nLatDims, sizeof(int)));
5725 5 : panLatDims[0] = nYDimID;
5726 5 : panLatDims[1] = nXDimID;
5727 5 : nLonDims = 2;
5728 : panLonDims =
5729 5 : static_cast<int *>(CPLCalloc(nLonDims, sizeof(int)));
5730 5 : panLonDims[0] = nYDimID;
5731 5 : panLonDims[1] = nXDimID;
5732 : }
5733 54 : else if (bIsProjected)
5734 : {
5735 : // Projected
5736 1 : nLatDims = 2;
5737 : panLatDims =
5738 1 : static_cast<int *>(CPLCalloc(nLatDims, sizeof(int)));
5739 1 : panLatDims[0] = nYDimID;
5740 1 : panLatDims[1] = nXDimID;
5741 1 : nLonDims = 2;
5742 : panLonDims =
5743 1 : static_cast<int *>(CPLCalloc(nLonDims, sizeof(int)));
5744 1 : panLonDims[0] = nYDimID;
5745 1 : panLonDims[1] = nXDimID;
5746 : }
5747 : else
5748 : {
5749 : // Geographic
5750 53 : nLatDims = 1;
5751 : panLatDims =
5752 53 : static_cast<int *>(CPLCalloc(nLatDims, sizeof(int)));
5753 53 : panLatDims[0] = nYDimID;
5754 53 : nLonDims = 1;
5755 : panLonDims =
5756 53 : static_cast<int *>(CPLCalloc(nLonDims, sizeof(int)));
5757 53 : panLonDims[0] = nXDimID;
5758 : }
5759 :
5760 59 : nc_type eLonLatType = NC_NAT;
5761 59 : if (bIsProjected)
5762 : {
5763 2 : eLonLatType = NC_FLOAT;
5764 4 : const char *pszValue = CSLFetchNameValueDef(
5765 2 : papszCreationOptions, "TYPE_LONLAT", "FLOAT");
5766 2 : if (EQUAL(pszValue, "DOUBLE"))
5767 0 : eLonLatType = NC_DOUBLE;
5768 : }
5769 : else
5770 : {
5771 57 : eLonLatType = NC_DOUBLE;
5772 114 : const char *pszValue = CSLFetchNameValueDef(
5773 57 : papszCreationOptions, "TYPE_LONLAT", "DOUBLE");
5774 57 : if (EQUAL(pszValue, "FLOAT"))
5775 0 : eLonLatType = NC_FLOAT;
5776 : }
5777 :
5778 : // Def vars and attributes.
5779 : {
5780 59 : const char *pszVarName =
5781 59 : bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME;
5782 59 : int status = nc_def_var(cdfid, pszVarName, eLonLatType,
5783 : nLatDims, panLatDims, &nVarLatID);
5784 59 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
5785 : cdfid, pszVarName, eLonLatType, nLatDims, nVarLatID);
5786 59 : NCDF_ERR(status);
5787 59 : DefVarDeflate(nVarLatID, false); // Don't set chunking.
5788 : }
5789 :
5790 : {
5791 59 : const char *pszVarName =
5792 59 : bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME;
5793 59 : int status = nc_def_var(cdfid, pszVarName, eLonLatType,
5794 : nLonDims, panLonDims, &nVarLonID);
5795 59 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
5796 : cdfid, pszVarName, eLonLatType, nLatDims, nVarLonID);
5797 59 : NCDF_ERR(status);
5798 59 : DefVarDeflate(nVarLonID, false); // Don't set chunking.
5799 : }
5800 :
5801 59 : if (bIsRotatedPole)
5802 0 : NCDFWriteRLonRLatVarsAttributes(this->vcdf, nVarLonID,
5803 : nVarLatID);
5804 : else
5805 59 : NCDFWriteLonLatVarsAttributes(this->vcdf, nVarLonID, nVarLatID);
5806 :
5807 59 : CPLFree(panLatDims);
5808 59 : CPLFree(panLonDims);
5809 : }
5810 : }
5811 :
5812 164 : if (!bDefsOnly)
5813 : {
5814 82 : m_bAddedProjectionVarsData = true;
5815 :
5816 82 : int nVarXID = -1;
5817 82 : int nVarYID = -1;
5818 :
5819 82 : nc_inq_varid(cdfid, CF_PROJ_X_VAR_NAME, &nVarXID);
5820 82 : nc_inq_varid(cdfid, CF_PROJ_Y_VAR_NAME, &nVarYID);
5821 :
5822 82 : int nVarLonID = -1;
5823 82 : int nVarLatID = -1;
5824 :
5825 82 : const bool bIsRotatedPole =
5826 151 : pszCFProjection != nullptr &&
5827 69 : EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
5828 82 : nc_inq_varid(cdfid,
5829 : bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME,
5830 : &nVarLonID);
5831 82 : nc_inq_varid(cdfid,
5832 : bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME,
5833 : &nVarLatID);
5834 :
5835 : // Get projection values.
5836 :
5837 82 : double *padLonVal = nullptr;
5838 82 : double *padLatVal = nullptr;
5839 :
5840 82 : if (bIsProjected)
5841 : {
5842 25 : OGRSpatialReference *poLatLonSRS = nullptr;
5843 25 : OGRCoordinateTransformation *poTransform = nullptr;
5844 :
5845 : size_t startX[1];
5846 : size_t countX[1];
5847 : size_t startY[1];
5848 : size_t countY[1];
5849 :
5850 25 : CPLDebug("GDAL_netCDF", "Getting (X,Y) values");
5851 :
5852 : double *padXVal =
5853 25 : static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
5854 : double *padYVal =
5855 25 : static_cast<double *>(CPLMalloc(nRasterYSize * sizeof(double)));
5856 :
5857 : // Get Y values.
5858 25 : const double dfY0 = (!bBottomUp) ? m_adfGeoTransform[3] :
5859 : // Invert latitude values.
5860 25 : m_adfGeoTransform[3] +
5861 25 : (m_adfGeoTransform[5] * nRasterYSize);
5862 25 : const double dfDY = m_adfGeoTransform[5];
5863 :
5864 1456 : for (int j = 0; j < nRasterYSize; j++)
5865 : {
5866 : // The data point is centered inside the pixel.
5867 1431 : if (!bBottomUp)
5868 0 : padYVal[j] = dfY0 + (j + 0.5) * dfDY;
5869 : else // Invert latitude values.
5870 1431 : padYVal[j] = dfY0 - (j + 0.5) * dfDY;
5871 : }
5872 25 : startX[0] = 0;
5873 25 : countX[0] = nRasterXSize;
5874 :
5875 : // Get X values.
5876 25 : const double dfX0 = m_adfGeoTransform[0];
5877 25 : const double dfDX = m_adfGeoTransform[1];
5878 :
5879 1477 : for (int i = 0; i < nRasterXSize; i++)
5880 : {
5881 : // The data point is centered inside the pixel.
5882 1452 : padXVal[i] = dfX0 + (i + 0.5) * dfDX;
5883 : }
5884 25 : startY[0] = 0;
5885 25 : countY[0] = nRasterYSize;
5886 :
5887 : // Write X/Y values.
5888 :
5889 : // Make sure we are in data mode.
5890 25 : SetDefineMode(false);
5891 :
5892 25 : CPLDebug("GDAL_netCDF", "Writing X values");
5893 : int status =
5894 25 : nc_put_vara_double(cdfid, nVarXID, startX, countX, padXVal);
5895 25 : NCDF_ERR(status);
5896 :
5897 25 : CPLDebug("GDAL_netCDF", "Writing Y values");
5898 : status =
5899 25 : nc_put_vara_double(cdfid, nVarYID, startY, countY, padYVal);
5900 25 : NCDF_ERR(status);
5901 :
5902 25 : if (pfnProgress)
5903 21 : pfnProgress(0.20, nullptr, pProgressData);
5904 :
5905 : // Write lon/lat arrays (CF coordinates) if requested.
5906 :
5907 : // Get OGR transform if GEOLOCATION is not available.
5908 25 : if (bWriteLonLat && !bHasGeoloc)
5909 : {
5910 1 : poLatLonSRS = m_oSRS.CloneGeogCS();
5911 1 : if (poLatLonSRS != nullptr)
5912 : {
5913 1 : poLatLonSRS->SetAxisMappingStrategy(
5914 : OAMS_TRADITIONAL_GIS_ORDER);
5915 : poTransform =
5916 1 : OGRCreateCoordinateTransformation(&m_oSRS, poLatLonSRS);
5917 : }
5918 : // If no OGR transform, then don't write CF lon/lat.
5919 1 : if (poTransform == nullptr)
5920 : {
5921 0 : CPLError(CE_Failure, CPLE_AppDefined,
5922 : "Unable to get Coordinate Transform");
5923 0 : bWriteLonLat = false;
5924 : }
5925 : }
5926 :
5927 25 : if (bWriteLonLat)
5928 : {
5929 2 : if (!bHasGeoloc)
5930 1 : CPLDebug("GDAL_netCDF", "Transforming (X,Y)->(lon,lat)");
5931 : else
5932 1 : CPLDebug("GDAL_netCDF",
5933 : "Writing (lon,lat) from GEOLOCATION arrays");
5934 :
5935 2 : bool bOK = true;
5936 2 : double dfProgress = 0.2;
5937 :
5938 2 : size_t start[] = {0, 0};
5939 2 : size_t count[] = {1, (size_t)nRasterXSize};
5940 : padLatVal = static_cast<double *>(
5941 2 : CPLMalloc(nRasterXSize * sizeof(double)));
5942 : padLonVal = static_cast<double *>(
5943 2 : CPLMalloc(nRasterXSize * sizeof(double)));
5944 :
5945 61 : for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR;
5946 : j++)
5947 : {
5948 59 : start[0] = j;
5949 :
5950 : // Get values from geotransform.
5951 59 : if (!bHasGeoloc)
5952 : {
5953 : // Fill values to transform.
5954 420 : for (int i = 0; i < nRasterXSize; i++)
5955 : {
5956 400 : padLatVal[i] = padYVal[j];
5957 400 : padLonVal[i] = padXVal[i];
5958 : }
5959 :
5960 : // Do the transform.
5961 20 : bOK = CPL_TO_BOOL(poTransform->Transform(
5962 20 : nRasterXSize, padLonVal, padLatVal, nullptr));
5963 20 : if (!bOK)
5964 : {
5965 0 : CPLError(CE_Failure, CPLE_AppDefined,
5966 : "Unable to Transform (X,Y) to (lon,lat).");
5967 : }
5968 : }
5969 : // Get values from geoloc arrays.
5970 : else
5971 : {
5972 39 : CPLErr eErr = GDALRasterIO(
5973 : hBand_Y, GF_Read, 0, j, nRasterXSize, 1, padLatVal,
5974 : nRasterXSize, 1, GDT_Float64, 0, 0);
5975 39 : if (eErr == CE_None)
5976 : {
5977 39 : eErr = GDALRasterIO(
5978 : hBand_X, GF_Read, 0, j, nRasterXSize, 1,
5979 : padLonVal, nRasterXSize, 1, GDT_Float64, 0, 0);
5980 : }
5981 :
5982 39 : if (eErr == CE_None)
5983 : {
5984 39 : bOK = true;
5985 : }
5986 : else
5987 : {
5988 0 : bOK = false;
5989 0 : CPLError(CE_Failure, CPLE_AppDefined,
5990 : "Unable to get scanline %d", j);
5991 : }
5992 : }
5993 :
5994 : // Write data.
5995 59 : if (bOK)
5996 : {
5997 59 : status = nc_put_vara_double(cdfid, nVarLatID, start,
5998 : count, padLatVal);
5999 59 : NCDF_ERR(status);
6000 59 : status = nc_put_vara_double(cdfid, nVarLonID, start,
6001 : count, padLonVal);
6002 59 : NCDF_ERR(status);
6003 : }
6004 :
6005 59 : if (pfnProgress && (nRasterYSize / 10) > 0 &&
6006 59 : (j % (nRasterYSize / 10) == 0))
6007 : {
6008 23 : dfProgress += 0.08;
6009 23 : pfnProgress(dfProgress, nullptr, pProgressData);
6010 : }
6011 : }
6012 : }
6013 :
6014 25 : if (poLatLonSRS != nullptr)
6015 1 : delete poLatLonSRS;
6016 25 : if (poTransform != nullptr)
6017 1 : delete poTransform;
6018 :
6019 25 : CPLFree(padXVal);
6020 25 : CPLFree(padYVal);
6021 : } // Projected
6022 :
6023 : // If not projected/geographic and has geoloc
6024 57 : else if (!bIsGeographic && bHasGeoloc)
6025 : {
6026 : // Use
6027 : // https://cfconventions.org/Data/cf-conventions/cf-conventions-1.9/cf-conventions.html#_two_dimensional_latitude_longitude_coordinate_variables
6028 :
6029 4 : bool bOK = true;
6030 4 : double dfProgress = 0.2;
6031 :
6032 : // Make sure we are in data mode.
6033 4 : SetDefineMode(false);
6034 :
6035 : size_t startX[1];
6036 : size_t countX[1];
6037 : size_t startY[1];
6038 : size_t countY[1];
6039 4 : startX[0] = 0;
6040 4 : countX[0] = nRasterXSize;
6041 :
6042 4 : startY[0] = 0;
6043 4 : countY[0] = nRasterYSize;
6044 :
6045 8 : std::vector<double> adfXVal(nRasterXSize);
6046 16 : for (int i = 0; i < nRasterXSize; i++)
6047 12 : adfXVal[i] = i;
6048 :
6049 8 : std::vector<double> adfYVal(nRasterYSize);
6050 12 : for (int i = 0; i < nRasterYSize; i++)
6051 8 : adfYVal[i] = bBottomUp ? nRasterYSize - 1 - i : i;
6052 :
6053 4 : CPLDebug("GDAL_netCDF", "Writing X values");
6054 4 : int status = nc_put_vara_double(cdfid, nVarXID, startX, countX,
6055 4 : adfXVal.data());
6056 4 : NCDF_ERR(status);
6057 :
6058 4 : CPLDebug("GDAL_netCDF", "Writing Y values");
6059 4 : status = nc_put_vara_double(cdfid, nVarYID, startY, countY,
6060 4 : adfYVal.data());
6061 4 : NCDF_ERR(status);
6062 :
6063 4 : if (pfnProgress)
6064 0 : pfnProgress(0.20, nullptr, pProgressData);
6065 :
6066 4 : size_t start[] = {0, 0};
6067 4 : size_t count[] = {1, (size_t)nRasterXSize};
6068 : padLatVal =
6069 4 : static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
6070 : padLonVal =
6071 4 : static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
6072 :
6073 12 : for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR; j++)
6074 : {
6075 8 : start[0] = j;
6076 :
6077 8 : CPLErr eErr = GDALRasterIO(hBand_Y, GF_Read, 0,
6078 8 : bBottomUp ? nRasterYSize - 1 - j : j,
6079 : nRasterXSize, 1, padLatVal,
6080 : nRasterXSize, 1, GDT_Float64, 0, 0);
6081 8 : if (eErr == CE_None)
6082 : {
6083 8 : eErr = GDALRasterIO(hBand_X, GF_Read, 0,
6084 8 : bBottomUp ? nRasterYSize - 1 - j : j,
6085 : nRasterXSize, 1, padLonVal,
6086 : nRasterXSize, 1, GDT_Float64, 0, 0);
6087 : }
6088 :
6089 8 : if (eErr == CE_None)
6090 : {
6091 8 : bOK = true;
6092 : }
6093 : else
6094 : {
6095 0 : bOK = false;
6096 0 : CPLError(CE_Failure, CPLE_AppDefined,
6097 : "Unable to get scanline %d", j);
6098 : }
6099 :
6100 : // Write data.
6101 8 : if (bOK)
6102 : {
6103 8 : status = nc_put_vara_double(cdfid, nVarLatID, start, count,
6104 : padLatVal);
6105 8 : NCDF_ERR(status);
6106 8 : status = nc_put_vara_double(cdfid, nVarLonID, start, count,
6107 : padLonVal);
6108 8 : NCDF_ERR(status);
6109 : }
6110 :
6111 8 : if (pfnProgress && (nRasterYSize / 10) > 0 &&
6112 0 : (j % (nRasterYSize / 10) == 0))
6113 : {
6114 0 : dfProgress += 0.08;
6115 0 : pfnProgress(dfProgress, nullptr, pProgressData);
6116 : }
6117 4 : }
6118 : }
6119 :
6120 : // If not projected, assume geographic to catch grids without Datum.
6121 53 : else if (bWriteLonLat)
6122 : {
6123 : // Get latitude values.
6124 53 : const double dfY0 = (!bBottomUp) ? m_adfGeoTransform[3] :
6125 : // Invert latitude values.
6126 53 : m_adfGeoTransform[3] +
6127 53 : (m_adfGeoTransform[5] * nRasterYSize);
6128 53 : const double dfDY = m_adfGeoTransform[5];
6129 :
6130 : // Override lat values with the ones in GEOLOCATION/Y_VALUES.
6131 53 : if (netCDFDataset::GetMetadataItem("Y_VALUES", "GEOLOCATION") !=
6132 : nullptr)
6133 : {
6134 0 : int nTemp = 0;
6135 0 : padLatVal = Get1DGeolocation("Y_VALUES", nTemp);
6136 : // Make sure we got the correct amount, if not fallback to GT */
6137 : // could add test fabs(fabs(padLatVal[0]) - fabs(dfY0)) <= 0.1))
6138 0 : if (nTemp == nRasterYSize)
6139 : {
6140 0 : CPLDebug(
6141 : "GDAL_netCDF",
6142 : "Using Y_VALUES geolocation metadata for lat values");
6143 : }
6144 : else
6145 : {
6146 0 : CPLDebug("GDAL_netCDF",
6147 : "Got %d elements from Y_VALUES geolocation "
6148 : "metadata, need %d",
6149 : nTemp, nRasterYSize);
6150 0 : if (padLatVal)
6151 : {
6152 0 : CPLFree(padLatVal);
6153 0 : padLatVal = nullptr;
6154 : }
6155 : }
6156 : }
6157 :
6158 53 : if (padLatVal == nullptr)
6159 : {
6160 : padLatVal = static_cast<double *>(
6161 53 : CPLMalloc(nRasterYSize * sizeof(double)));
6162 7105 : for (int i = 0; i < nRasterYSize; i++)
6163 : {
6164 : // The data point is centered inside the pixel.
6165 7052 : if (!bBottomUp)
6166 0 : padLatVal[i] = dfY0 + (i + 0.5) * dfDY;
6167 : else // Invert latitude values.
6168 7052 : padLatVal[i] = dfY0 - (i + 0.5) * dfDY;
6169 : }
6170 : }
6171 :
6172 53 : size_t startLat[1] = {0};
6173 53 : size_t countLat[1] = {static_cast<size_t>(nRasterYSize)};
6174 :
6175 : // Get longitude values.
6176 53 : const double dfX0 = m_adfGeoTransform[0];
6177 53 : const double dfDX = m_adfGeoTransform[1];
6178 :
6179 : padLonVal =
6180 53 : static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
6181 7157 : for (int i = 0; i < nRasterXSize; i++)
6182 : {
6183 : // The data point is centered inside the pixel.
6184 7104 : padLonVal[i] = dfX0 + (i + 0.5) * dfDX;
6185 : }
6186 :
6187 53 : size_t startLon[1] = {0};
6188 53 : size_t countLon[1] = {static_cast<size_t>(nRasterXSize)};
6189 :
6190 : // Write latitude and longitude values.
6191 :
6192 : // Make sure we are in data mode.
6193 53 : SetDefineMode(false);
6194 :
6195 : // Write values.
6196 53 : CPLDebug("GDAL_netCDF", "Writing lat values");
6197 :
6198 53 : int status = nc_put_vara_double(cdfid, nVarLatID, startLat,
6199 : countLat, padLatVal);
6200 53 : NCDF_ERR(status);
6201 :
6202 53 : CPLDebug("GDAL_netCDF", "Writing lon values");
6203 53 : status = nc_put_vara_double(cdfid, nVarLonID, startLon, countLon,
6204 : padLonVal);
6205 53 : NCDF_ERR(status);
6206 :
6207 : } // Not projected.
6208 :
6209 82 : CPLFree(padLatVal);
6210 82 : CPLFree(padLonVal);
6211 :
6212 82 : if (pfnProgress)
6213 41 : pfnProgress(1.00, nullptr, pProgressData);
6214 : }
6215 :
6216 164 : if (hDS_X != nullptr)
6217 : {
6218 10 : GDALClose(hDS_X);
6219 : }
6220 164 : if (hDS_Y != nullptr)
6221 : {
6222 10 : GDALClose(hDS_Y);
6223 : }
6224 :
6225 164 : return CE_None;
6226 : }
6227 :
6228 : // Write Projection variable to band variable.
6229 : // Moved from AddProjectionVars() for cases when bands are added after
6230 : // projection.
6231 375 : bool netCDFDataset::AddGridMappingRef()
6232 : {
6233 375 : bool bRet = true;
6234 375 : bool bOldDefineMode = bDefineMode;
6235 :
6236 566 : if ((GetAccess() == GA_Update) && (nBands >= 1) && (GetRasterBand(1)) &&
6237 191 : ((pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, "")) ||
6238 185 : (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))))
6239 : {
6240 73 : bAddedGridMappingRef = true;
6241 :
6242 : // Make sure we are in define mode.
6243 73 : SetDefineMode(true);
6244 :
6245 192 : for (int i = 1; i <= nBands; i++)
6246 : {
6247 : const int nVarId =
6248 119 : static_cast<netCDFRasterBand *>(GetRasterBand(i))->nZId;
6249 :
6250 119 : if (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))
6251 : {
6252 : int status =
6253 230 : nc_put_att_text(cdfid, nVarId, CF_GRD_MAPPING,
6254 115 : strlen(pszCFProjection), pszCFProjection);
6255 115 : NCDF_ERR(status);
6256 115 : if (status != NC_NOERR)
6257 0 : bRet = false;
6258 : }
6259 119 : if (pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, ""))
6260 : {
6261 : int status =
6262 6 : nc_put_att_text(cdfid, nVarId, CF_COORDINATES,
6263 : strlen(pszCFCoordinates), pszCFCoordinates);
6264 6 : NCDF_ERR(status);
6265 6 : if (status != NC_NOERR)
6266 0 : bRet = false;
6267 : }
6268 : }
6269 :
6270 : // Go back to previous define mode.
6271 73 : SetDefineMode(bOldDefineMode);
6272 : }
6273 375 : return bRet;
6274 : }
6275 :
6276 : /************************************************************************/
6277 : /* GetGeoTransform() */
6278 : /************************************************************************/
6279 :
6280 116 : CPLErr netCDFDataset::GetGeoTransform(double *padfTransform)
6281 :
6282 : {
6283 116 : memcpy(padfTransform, m_adfGeoTransform, sizeof(double) * 6);
6284 116 : if (m_bHasGeoTransform)
6285 86 : return CE_None;
6286 :
6287 30 : return GDALPamDataset::GetGeoTransform(padfTransform);
6288 : }
6289 :
6290 : /************************************************************************/
6291 : /* rint() */
6292 : /************************************************************************/
6293 :
6294 0 : double netCDFDataset::rint(double dfX)
6295 : {
6296 0 : return std::round(dfX);
6297 : }
6298 :
6299 : /************************************************************************/
6300 : /* NCDFReadIsoMetadata() */
6301 : /************************************************************************/
6302 :
6303 16 : static void NCDFReadMetadataAsJson(int cdfid, CPLJSONObject &obj)
6304 : {
6305 16 : int nbAttr = 0;
6306 16 : NCDF_ERR(nc_inq_varnatts(cdfid, NC_GLOBAL, &nbAttr));
6307 :
6308 32 : std::map<std::string, CPLJSONArray> oMapNameToArray;
6309 40 : for (int l = 0; l < nbAttr; l++)
6310 : {
6311 : char szAttrName[NC_MAX_NAME + 1];
6312 24 : szAttrName[0] = 0;
6313 24 : NCDF_ERR(nc_inq_attname(cdfid, NC_GLOBAL, l, szAttrName));
6314 :
6315 24 : char *pszMetaValue = nullptr;
6316 24 : if (NCDFGetAttr(cdfid, NC_GLOBAL, szAttrName, &pszMetaValue) == CE_None)
6317 : {
6318 24 : nc_type nAttrType = NC_NAT;
6319 24 : size_t nAttrLen = 0;
6320 :
6321 24 : NCDF_ERR(nc_inq_att(cdfid, NC_GLOBAL, szAttrName, &nAttrType,
6322 : &nAttrLen));
6323 :
6324 24 : std::string osAttrName(szAttrName);
6325 24 : const auto sharpPos = osAttrName.find('#');
6326 24 : if (sharpPos == std::string::npos)
6327 : {
6328 16 : if (nAttrType == NC_DOUBLE || nAttrType == NC_FLOAT)
6329 4 : obj.Add(osAttrName, CPLAtof(pszMetaValue));
6330 : else
6331 12 : obj.Add(osAttrName, pszMetaValue);
6332 : }
6333 : else
6334 : {
6335 8 : osAttrName.resize(sharpPos);
6336 8 : auto iter = oMapNameToArray.find(osAttrName);
6337 8 : if (iter == oMapNameToArray.end())
6338 : {
6339 8 : CPLJSONArray array;
6340 4 : obj.Add(osAttrName, array);
6341 4 : oMapNameToArray[osAttrName] = array;
6342 4 : array.Add(pszMetaValue);
6343 : }
6344 : else
6345 : {
6346 4 : iter->second.Add(pszMetaValue);
6347 : }
6348 : }
6349 24 : CPLFree(pszMetaValue);
6350 24 : pszMetaValue = nullptr;
6351 : }
6352 : }
6353 :
6354 16 : int nSubGroups = 0;
6355 16 : int *panSubGroupIds = nullptr;
6356 16 : NCDFGetSubGroups(cdfid, &nSubGroups, &panSubGroupIds);
6357 16 : oMapNameToArray.clear();
6358 28 : for (int i = 0; i < nSubGroups; i++)
6359 : {
6360 24 : CPLJSONObject subObj;
6361 12 : NCDFReadMetadataAsJson(panSubGroupIds[i], subObj);
6362 :
6363 24 : std::string osGroupName;
6364 12 : osGroupName.resize(NC_MAX_NAME);
6365 12 : NCDF_ERR(nc_inq_grpname(panSubGroupIds[i], &osGroupName[0]));
6366 12 : osGroupName.resize(strlen(osGroupName.data()));
6367 12 : const auto sharpPos = osGroupName.find('#');
6368 12 : if (sharpPos == std::string::npos)
6369 : {
6370 4 : obj.Add(osGroupName, subObj);
6371 : }
6372 : else
6373 : {
6374 8 : osGroupName.resize(sharpPos);
6375 8 : auto iter = oMapNameToArray.find(osGroupName);
6376 8 : if (iter == oMapNameToArray.end())
6377 : {
6378 8 : CPLJSONArray array;
6379 4 : obj.Add(osGroupName, array);
6380 4 : oMapNameToArray[osGroupName] = array;
6381 4 : array.Add(subObj);
6382 : }
6383 : else
6384 : {
6385 4 : iter->second.Add(subObj);
6386 : }
6387 : }
6388 : }
6389 16 : CPLFree(panSubGroupIds);
6390 16 : }
6391 :
6392 4 : std::string NCDFReadMetadataAsJson(int cdfid)
6393 : {
6394 8 : CPLJSONDocument oDoc;
6395 8 : CPLJSONObject oRoot = oDoc.GetRoot();
6396 4 : NCDFReadMetadataAsJson(cdfid, oRoot);
6397 8 : return oDoc.SaveAsString();
6398 : }
6399 :
6400 : /************************************************************************/
6401 : /* ReadAttributes() */
6402 : /************************************************************************/
6403 1790 : CPLErr netCDFDataset::ReadAttributes(int cdfidIn, int var)
6404 :
6405 : {
6406 1790 : char *pszVarFullName = nullptr;
6407 1790 : ERR_RET(NCDFGetVarFullName(cdfidIn, var, &pszVarFullName));
6408 :
6409 : // For metadata in Sentinel 5
6410 1790 : if (STARTS_WITH(pszVarFullName, "/METADATA/"))
6411 : {
6412 6 : for (const char *key :
6413 : {"ISO_METADATA", "ESA_METADATA", "EOP_METADATA", "QA_STATISTICS",
6414 8 : "GRANULE_DESCRIPTION", "ALGORITHM_SETTINGS"})
6415 : {
6416 14 : if (var == NC_GLOBAL &&
6417 7 : strcmp(pszVarFullName,
6418 : CPLSPrintf("/METADATA/%s/NC_GLOBAL", key)) == 0)
6419 : {
6420 1 : CPLFree(pszVarFullName);
6421 1 : CPLStringList aosList;
6422 2 : aosList.AddString(CPLString(NCDFReadMetadataAsJson(cdfidIn))
6423 1 : .replaceAll("\\/", '/'));
6424 1 : m_oMapDomainToJSon[key] = std::move(aosList);
6425 1 : return CE_None;
6426 : }
6427 : }
6428 : }
6429 1789 : if (STARTS_WITH(pszVarFullName, "/PRODUCT/SUPPORT_DATA/"))
6430 : {
6431 0 : CPLFree(pszVarFullName);
6432 0 : CPLStringList aosList;
6433 : aosList.AddString(
6434 0 : CPLString(NCDFReadMetadataAsJson(cdfidIn)).replaceAll("\\/", '/'));
6435 0 : m_oMapDomainToJSon["SUPPORT_DATA"] = std::move(aosList);
6436 0 : return CE_None;
6437 : }
6438 :
6439 1789 : size_t nMetaNameSize =
6440 1789 : sizeof(char) * (strlen(pszVarFullName) + 1 + NC_MAX_NAME + 1);
6441 1789 : char *pszMetaName = static_cast<char *>(CPLMalloc(nMetaNameSize));
6442 :
6443 1789 : int nbAttr = 0;
6444 1789 : NCDF_ERR(nc_inq_varnatts(cdfidIn, var, &nbAttr));
6445 :
6446 8992 : for (int l = 0; l < nbAttr; l++)
6447 : {
6448 : char szAttrName[NC_MAX_NAME + 1];
6449 7203 : szAttrName[0] = 0;
6450 7203 : NCDF_ERR(nc_inq_attname(cdfidIn, var, l, szAttrName));
6451 7203 : snprintf(pszMetaName, nMetaNameSize, "%s#%s", pszVarFullName,
6452 : szAttrName);
6453 :
6454 7203 : char *pszMetaTemp = nullptr;
6455 7203 : if (NCDFGetAttr(cdfidIn, var, szAttrName, &pszMetaTemp) == CE_None)
6456 : {
6457 7202 : papszMetadata =
6458 7202 : CSLSetNameValue(papszMetadata, pszMetaName, pszMetaTemp);
6459 7202 : CPLFree(pszMetaTemp);
6460 7202 : pszMetaTemp = nullptr;
6461 : }
6462 : else
6463 : {
6464 1 : CPLDebug("GDAL_netCDF", "invalid metadata %s", pszMetaName);
6465 : }
6466 : }
6467 :
6468 1789 : CPLFree(pszVarFullName);
6469 1789 : CPLFree(pszMetaName);
6470 :
6471 1789 : if (var == NC_GLOBAL)
6472 : {
6473 : // Recurse on sub-groups.
6474 520 : int nSubGroups = 0;
6475 520 : int *panSubGroupIds = nullptr;
6476 520 : NCDFGetSubGroups(cdfidIn, &nSubGroups, &panSubGroupIds);
6477 549 : for (int i = 0; i < nSubGroups; i++)
6478 : {
6479 29 : ReadAttributes(panSubGroupIds[i], var);
6480 : }
6481 520 : CPLFree(panSubGroupIds);
6482 : }
6483 :
6484 1789 : return CE_None;
6485 : }
6486 :
6487 : /************************************************************************/
6488 : /* netCDFDataset::CreateSubDatasetList() */
6489 : /************************************************************************/
6490 51 : void netCDFDataset::CreateSubDatasetList(int nGroupId)
6491 : {
6492 : char szVarStdName[NC_MAX_NAME + 1];
6493 51 : int *ponDimIds = nullptr;
6494 : nc_type nAttype;
6495 : size_t nAttlen;
6496 :
6497 51 : netCDFDataset *poDS = this;
6498 :
6499 : int nVarCount;
6500 51 : nc_inq_nvars(nGroupId, &nVarCount);
6501 :
6502 51 : const bool bListAllArrays = CPLTestBool(
6503 51 : CSLFetchNameValueDef(papszOpenOptions, "LIST_ALL_ARRAYS", "NO"));
6504 :
6505 329 : for (int nVar = 0; nVar < nVarCount; nVar++)
6506 : {
6507 :
6508 : int nDims;
6509 278 : nc_inq_varndims(nGroupId, nVar, &nDims);
6510 :
6511 278 : if ((bListAllArrays && nDims > 0) || nDims >= 2)
6512 : {
6513 158 : ponDimIds = static_cast<int *>(CPLCalloc(nDims, sizeof(int)));
6514 158 : nc_inq_vardimid(nGroupId, nVar, ponDimIds);
6515 :
6516 : // Create Sub dataset list.
6517 158 : CPLString osDim;
6518 487 : for (int i = 0; i < nDims; i++)
6519 : {
6520 : size_t nDimLen;
6521 329 : nc_inq_dimlen(nGroupId, ponDimIds[i], &nDimLen);
6522 329 : if (!osDim.empty())
6523 171 : osDim += 'x';
6524 329 : osDim += CPLSPrintf("%d", (int)nDimLen);
6525 : }
6526 158 : CPLFree(ponDimIds);
6527 :
6528 : nc_type nVarType;
6529 158 : nc_inq_vartype(nGroupId, nVar, &nVarType);
6530 158 : const char *pszType = "";
6531 158 : switch (nVarType)
6532 : {
6533 38 : case NC_BYTE:
6534 38 : pszType = "8-bit integer";
6535 38 : break;
6536 2 : case NC_CHAR:
6537 2 : pszType = "8-bit character";
6538 2 : break;
6539 6 : case NC_SHORT:
6540 6 : pszType = "16-bit integer";
6541 6 : break;
6542 10 : case NC_INT:
6543 10 : pszType = "32-bit integer";
6544 10 : break;
6545 51 : case NC_FLOAT:
6546 51 : pszType = "32-bit floating-point";
6547 51 : break;
6548 33 : case NC_DOUBLE:
6549 33 : pszType = "64-bit floating-point";
6550 33 : break;
6551 4 : case NC_UBYTE:
6552 4 : pszType = "8-bit unsigned integer";
6553 4 : break;
6554 1 : case NC_USHORT:
6555 1 : pszType = "16-bit unsigned integer";
6556 1 : break;
6557 1 : case NC_UINT:
6558 1 : pszType = "32-bit unsigned integer";
6559 1 : break;
6560 1 : case NC_INT64:
6561 1 : pszType = "64-bit integer";
6562 1 : break;
6563 1 : case NC_UINT64:
6564 1 : pszType = "64-bit unsigned integer";
6565 1 : break;
6566 10 : default:
6567 10 : break;
6568 : }
6569 :
6570 158 : char *pszName = nullptr;
6571 158 : if (NCDFGetVarFullName(nGroupId, nVar, &pszName) != CE_None)
6572 0 : continue;
6573 :
6574 158 : nSubDatasets++;
6575 :
6576 158 : nAttlen = 0;
6577 158 : nc_inq_att(nGroupId, nVar, CF_STD_NAME, &nAttype, &nAttlen);
6578 316 : if (nAttlen < sizeof(szVarStdName) &&
6579 158 : nc_get_att_text(nGroupId, nVar, CF_STD_NAME, szVarStdName) ==
6580 : NC_NOERR)
6581 : {
6582 53 : szVarStdName[nAttlen] = '\0';
6583 : }
6584 : else
6585 : {
6586 105 : snprintf(szVarStdName, sizeof(szVarStdName), "%s", pszName);
6587 : }
6588 :
6589 : char szTemp[NC_MAX_NAME + 1];
6590 158 : snprintf(szTemp, sizeof(szTemp), "SUBDATASET_%d_NAME",
6591 : nSubDatasets);
6592 :
6593 158 : if (strchr(pszName, ' ') || strchr(pszName, ':'))
6594 : {
6595 1 : poDS->papszSubDatasets = CSLSetNameValue(
6596 : poDS->papszSubDatasets, szTemp,
6597 : CPLSPrintf("NETCDF:\"%s\":\"%s\"", poDS->osFilename.c_str(),
6598 : pszName));
6599 : }
6600 : else
6601 : {
6602 157 : poDS->papszSubDatasets = CSLSetNameValue(
6603 : poDS->papszSubDatasets, szTemp,
6604 : CPLSPrintf("NETCDF:\"%s\":%s", poDS->osFilename.c_str(),
6605 : pszName));
6606 : }
6607 :
6608 158 : CPLFree(pszName);
6609 :
6610 158 : snprintf(szTemp, sizeof(szTemp), "SUBDATASET_%d_DESC",
6611 : nSubDatasets);
6612 :
6613 158 : poDS->papszSubDatasets =
6614 158 : CSLSetNameValue(poDS->papszSubDatasets, szTemp,
6615 : CPLSPrintf("[%s] %s (%s)", osDim.c_str(),
6616 : szVarStdName, pszType));
6617 : }
6618 : }
6619 :
6620 : // Recurse on sub groups.
6621 51 : int nSubGroups = 0;
6622 51 : int *panSubGroupIds = nullptr;
6623 51 : NCDFGetSubGroups(nGroupId, &nSubGroups, &panSubGroupIds);
6624 56 : for (int i = 0; i < nSubGroups; i++)
6625 : {
6626 5 : CreateSubDatasetList(panSubGroupIds[i]);
6627 : }
6628 51 : CPLFree(panSubGroupIds);
6629 51 : }
6630 :
6631 : /************************************************************************/
6632 : /* TestCapability() */
6633 : /************************************************************************/
6634 :
6635 248 : int netCDFDataset::TestCapability(const char *pszCap)
6636 : {
6637 248 : if (EQUAL(pszCap, ODsCCreateLayer))
6638 : {
6639 223 : return eAccess == GA_Update && nBands == 0 &&
6640 218 : (eMultipleLayerBehavior != SINGLE_LAYER ||
6641 229 : this->GetLayerCount() == 0 || bSGSupport);
6642 : }
6643 136 : else if (EQUAL(pszCap, ODsCZGeometries))
6644 2 : return true;
6645 :
6646 134 : return false;
6647 : }
6648 :
6649 : /************************************************************************/
6650 : /* GetLayer() */
6651 : /************************************************************************/
6652 :
6653 384 : OGRLayer *netCDFDataset::GetLayer(int nIdx)
6654 : {
6655 384 : if (nIdx < 0 || nIdx >= this->GetLayerCount())
6656 2 : return nullptr;
6657 382 : return papoLayers[nIdx].get();
6658 : }
6659 :
6660 : /************************************************************************/
6661 : /* ICreateLayer() */
6662 : /************************************************************************/
6663 :
6664 59 : OGRLayer *netCDFDataset::ICreateLayer(const char *pszName,
6665 : const OGRGeomFieldDefn *poGeomFieldDefn,
6666 : CSLConstList papszOptions)
6667 : {
6668 59 : int nLayerCDFId = cdfid;
6669 59 : if (!TestCapability(ODsCCreateLayer))
6670 0 : return nullptr;
6671 :
6672 59 : const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
6673 : const auto poSpatialRef =
6674 59 : poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
6675 :
6676 118 : CPLString osNetCDFLayerName(pszName);
6677 59 : const netCDFWriterConfigLayer *poLayerConfig = nullptr;
6678 59 : if (oWriterConfig.m_bIsValid)
6679 : {
6680 : std::map<CPLString, netCDFWriterConfigLayer>::const_iterator
6681 2 : oLayerIter = oWriterConfig.m_oLayers.find(pszName);
6682 2 : if (oLayerIter != oWriterConfig.m_oLayers.end())
6683 : {
6684 1 : poLayerConfig = &(oLayerIter->second);
6685 1 : osNetCDFLayerName = poLayerConfig->m_osNetCDFName;
6686 : }
6687 : }
6688 :
6689 59 : netCDFDataset *poLayerDataset = nullptr;
6690 59 : if (eMultipleLayerBehavior == SEPARATE_FILES)
6691 : {
6692 2 : char **papszDatasetOptions = nullptr;
6693 2 : papszDatasetOptions = CSLSetNameValue(
6694 : papszDatasetOptions, "CONFIG_FILE",
6695 2 : CSLFetchNameValue(papszCreationOptions, "CONFIG_FILE"));
6696 : papszDatasetOptions =
6697 2 : CSLSetNameValue(papszDatasetOptions, "FORMAT",
6698 2 : CSLFetchNameValue(papszCreationOptions, "FORMAT"));
6699 2 : papszDatasetOptions = CSLSetNameValue(
6700 : papszDatasetOptions, "WRITE_GDAL_TAGS",
6701 2 : CSLFetchNameValue(papszCreationOptions, "WRITE_GDAL_TAGS"));
6702 : const CPLString osLayerFilename(
6703 2 : CPLFormFilenameSafe(osFilename, osNetCDFLayerName, "nc"));
6704 2 : CPLAcquireMutex(hNCMutex, 1000.0);
6705 2 : poLayerDataset =
6706 2 : CreateLL(osLayerFilename, 0, 0, 0, papszDatasetOptions);
6707 2 : CPLReleaseMutex(hNCMutex);
6708 2 : CSLDestroy(papszDatasetOptions);
6709 2 : if (poLayerDataset == nullptr)
6710 0 : return nullptr;
6711 :
6712 2 : nLayerCDFId = poLayerDataset->cdfid;
6713 2 : NCDFAddGDALHistory(nLayerCDFId, osLayerFilename, bWriteGDALVersion,
6714 2 : bWriteGDALHistory, "", "Create",
6715 : NCDF_CONVENTIONS_CF_V1_6);
6716 : }
6717 57 : else if (eMultipleLayerBehavior == SEPARATE_GROUPS)
6718 : {
6719 2 : SetDefineMode(true);
6720 :
6721 2 : nLayerCDFId = -1;
6722 2 : int status = nc_def_grp(cdfid, osNetCDFLayerName, &nLayerCDFId);
6723 2 : NCDF_ERR(status);
6724 2 : if (status != NC_NOERR)
6725 0 : return nullptr;
6726 :
6727 2 : NCDFAddGDALHistory(nLayerCDFId, osFilename, bWriteGDALVersion,
6728 2 : bWriteGDALHistory, "", "Create",
6729 : NCDF_CONVENTIONS_CF_V1_6);
6730 : }
6731 :
6732 : // Make a clone to workaround a bug in released MapServer versions
6733 : // that destroys the passed SRS instead of releasing it .
6734 59 : OGRSpatialReference *poSRS = nullptr;
6735 59 : if (poSpatialRef)
6736 : {
6737 43 : poSRS = poSpatialRef->Clone();
6738 43 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
6739 : }
6740 : std::shared_ptr<netCDFLayer> poLayer(
6741 59 : new netCDFLayer(poLayerDataset ? poLayerDataset : this, nLayerCDFId,
6742 118 : osNetCDFLayerName, eGType, poSRS));
6743 59 : if (poSRS != nullptr)
6744 43 : poSRS->Release();
6745 :
6746 : // Fetch layer creation options coming from config file
6747 59 : char **papszNewOptions = CSLDuplicate(papszOptions);
6748 59 : if (oWriterConfig.m_bIsValid)
6749 : {
6750 2 : std::map<CPLString, CPLString>::const_iterator oIter;
6751 3 : for (oIter = oWriterConfig.m_oLayerCreationOptions.begin();
6752 3 : oIter != oWriterConfig.m_oLayerCreationOptions.end(); ++oIter)
6753 : {
6754 : papszNewOptions =
6755 1 : CSLSetNameValue(papszNewOptions, oIter->first, oIter->second);
6756 : }
6757 2 : if (poLayerConfig != nullptr)
6758 : {
6759 3 : for (oIter = poLayerConfig->m_oLayerCreationOptions.begin();
6760 3 : oIter != poLayerConfig->m_oLayerCreationOptions.end(); ++oIter)
6761 : {
6762 2 : papszNewOptions = CSLSetNameValue(papszNewOptions, oIter->first,
6763 2 : oIter->second);
6764 : }
6765 : }
6766 : }
6767 :
6768 59 : const bool bRet = poLayer->Create(papszNewOptions, poLayerConfig);
6769 59 : CSLDestroy(papszNewOptions);
6770 :
6771 59 : if (!bRet)
6772 : {
6773 0 : return nullptr;
6774 : }
6775 :
6776 59 : if (poLayerDataset != nullptr)
6777 2 : apoVectorDatasets.push_back(poLayerDataset);
6778 :
6779 59 : papoLayers.push_back(poLayer);
6780 59 : return poLayer.get();
6781 : }
6782 :
6783 : /************************************************************************/
6784 : /* CloneAttributes() */
6785 : /************************************************************************/
6786 :
6787 137 : bool netCDFDataset::CloneAttributes(int old_cdfid, int new_cdfid, int nSrcVarId,
6788 : int nDstVarId)
6789 : {
6790 137 : int nAttCount = -1;
6791 137 : int status = nc_inq_varnatts(old_cdfid, nSrcVarId, &nAttCount);
6792 137 : NCDF_ERR(status);
6793 :
6794 693 : for (int i = 0; i < nAttCount; i++)
6795 : {
6796 : char szName[NC_MAX_NAME + 1];
6797 556 : szName[0] = 0;
6798 556 : status = nc_inq_attname(old_cdfid, nSrcVarId, i, szName);
6799 556 : NCDF_ERR(status);
6800 :
6801 : status =
6802 556 : nc_copy_att(old_cdfid, nSrcVarId, szName, new_cdfid, nDstVarId);
6803 556 : NCDF_ERR(status);
6804 556 : if (status != NC_NOERR)
6805 0 : return false;
6806 : }
6807 :
6808 137 : return true;
6809 : }
6810 :
6811 : /************************************************************************/
6812 : /* CloneVariableContent() */
6813 : /************************************************************************/
6814 :
6815 121 : bool netCDFDataset::CloneVariableContent(int old_cdfid, int new_cdfid,
6816 : int nSrcVarId, int nDstVarId)
6817 : {
6818 121 : int nVarDimCount = -1;
6819 121 : int status = nc_inq_varndims(old_cdfid, nSrcVarId, &nVarDimCount);
6820 121 : NCDF_ERR(status);
6821 121 : int anDimIds[] = {-1, 1};
6822 121 : status = nc_inq_vardimid(old_cdfid, nSrcVarId, anDimIds);
6823 121 : NCDF_ERR(status);
6824 121 : nc_type nc_datatype = NC_NAT;
6825 121 : status = nc_inq_vartype(old_cdfid, nSrcVarId, &nc_datatype);
6826 121 : NCDF_ERR(status);
6827 121 : size_t nTypeSize = 0;
6828 121 : switch (nc_datatype)
6829 : {
6830 35 : case NC_BYTE:
6831 : case NC_CHAR:
6832 35 : nTypeSize = 1;
6833 35 : break;
6834 4 : case NC_SHORT:
6835 4 : nTypeSize = 2;
6836 4 : break;
6837 24 : case NC_INT:
6838 24 : nTypeSize = 4;
6839 24 : break;
6840 4 : case NC_FLOAT:
6841 4 : nTypeSize = 4;
6842 4 : break;
6843 43 : case NC_DOUBLE:
6844 43 : nTypeSize = 8;
6845 43 : break;
6846 2 : case NC_UBYTE:
6847 2 : nTypeSize = 1;
6848 2 : break;
6849 2 : case NC_USHORT:
6850 2 : nTypeSize = 2;
6851 2 : break;
6852 2 : case NC_UINT:
6853 2 : nTypeSize = 4;
6854 2 : break;
6855 4 : case NC_INT64:
6856 : case NC_UINT64:
6857 4 : nTypeSize = 8;
6858 4 : break;
6859 1 : case NC_STRING:
6860 1 : nTypeSize = sizeof(char *);
6861 1 : break;
6862 0 : default:
6863 : {
6864 0 : CPLError(CE_Failure, CPLE_NotSupported, "Unsupported data type: %d",
6865 : nc_datatype);
6866 0 : return false;
6867 : }
6868 : }
6869 :
6870 121 : size_t nElems = 1;
6871 : size_t anStart[NC_MAX_DIMS];
6872 : size_t anCount[NC_MAX_DIMS];
6873 121 : size_t nRecords = 1;
6874 261 : for (int i = 0; i < nVarDimCount; i++)
6875 : {
6876 140 : anStart[i] = 0;
6877 140 : if (i == 0)
6878 : {
6879 116 : anCount[i] = 1;
6880 116 : status = nc_inq_dimlen(old_cdfid, anDimIds[i], &nRecords);
6881 116 : NCDF_ERR(status);
6882 : }
6883 : else
6884 : {
6885 24 : anCount[i] = 0;
6886 24 : status = nc_inq_dimlen(old_cdfid, anDimIds[i], &anCount[i]);
6887 24 : NCDF_ERR(status);
6888 24 : nElems *= anCount[i];
6889 : }
6890 : }
6891 :
6892 : /* Workaround in some cases a netCDF bug:
6893 : * https://github.com/Unidata/netcdf-c/pull/1442 */
6894 121 : if (nRecords > 0 && nRecords < 10 * 1000 * 1000 / (nElems * nTypeSize))
6895 : {
6896 119 : nElems *= nRecords;
6897 119 : anCount[0] = nRecords;
6898 119 : nRecords = 1;
6899 : }
6900 :
6901 121 : void *pBuffer = VSI_MALLOC2_VERBOSE(nElems, nTypeSize);
6902 121 : if (pBuffer == nullptr)
6903 0 : return false;
6904 :
6905 240 : for (size_t iRecord = 0; iRecord < nRecords; iRecord++)
6906 : {
6907 119 : anStart[0] = iRecord;
6908 :
6909 119 : switch (nc_datatype)
6910 : {
6911 5 : case NC_BYTE:
6912 : status =
6913 5 : nc_get_vara_schar(old_cdfid, nSrcVarId, anStart, anCount,
6914 : static_cast<signed char *>(pBuffer));
6915 5 : if (!status)
6916 5 : status = nc_put_vara_schar(
6917 : new_cdfid, nDstVarId, anStart, anCount,
6918 : static_cast<signed char *>(pBuffer));
6919 5 : break;
6920 28 : case NC_CHAR:
6921 : status =
6922 28 : nc_get_vara_text(old_cdfid, nSrcVarId, anStart, anCount,
6923 : static_cast<char *>(pBuffer));
6924 28 : if (!status)
6925 : status =
6926 28 : nc_put_vara_text(new_cdfid, nDstVarId, anStart, anCount,
6927 : static_cast<char *>(pBuffer));
6928 28 : break;
6929 4 : case NC_SHORT:
6930 : status =
6931 4 : nc_get_vara_short(old_cdfid, nSrcVarId, anStart, anCount,
6932 : static_cast<short *>(pBuffer));
6933 4 : if (!status)
6934 4 : status = nc_put_vara_short(new_cdfid, nDstVarId, anStart,
6935 : anCount,
6936 : static_cast<short *>(pBuffer));
6937 4 : break;
6938 24 : case NC_INT:
6939 24 : status = nc_get_vara_int(old_cdfid, nSrcVarId, anStart, anCount,
6940 : static_cast<int *>(pBuffer));
6941 24 : if (!status)
6942 : status =
6943 24 : nc_put_vara_int(new_cdfid, nDstVarId, anStart, anCount,
6944 : static_cast<int *>(pBuffer));
6945 24 : break;
6946 4 : case NC_FLOAT:
6947 : status =
6948 4 : nc_get_vara_float(old_cdfid, nSrcVarId, anStart, anCount,
6949 : static_cast<float *>(pBuffer));
6950 4 : if (!status)
6951 4 : status = nc_put_vara_float(new_cdfid, nDstVarId, anStart,
6952 : anCount,
6953 : static_cast<float *>(pBuffer));
6954 4 : break;
6955 43 : case NC_DOUBLE:
6956 : status =
6957 43 : nc_get_vara_double(old_cdfid, nSrcVarId, anStart, anCount,
6958 : static_cast<double *>(pBuffer));
6959 43 : if (!status)
6960 43 : status = nc_put_vara_double(new_cdfid, nDstVarId, anStart,
6961 : anCount,
6962 : static_cast<double *>(pBuffer));
6963 43 : break;
6964 1 : case NC_STRING:
6965 : status =
6966 1 : nc_get_vara_string(old_cdfid, nSrcVarId, anStart, anCount,
6967 : static_cast<char **>(pBuffer));
6968 1 : if (!status)
6969 : {
6970 1 : status = nc_put_vara_string(
6971 : new_cdfid, nDstVarId, anStart, anCount,
6972 : static_cast<const char **>(pBuffer));
6973 1 : nc_free_string(nElems, static_cast<char **>(pBuffer));
6974 : }
6975 1 : break;
6976 :
6977 2 : case NC_UBYTE:
6978 : status =
6979 2 : nc_get_vara_uchar(old_cdfid, nSrcVarId, anStart, anCount,
6980 : static_cast<unsigned char *>(pBuffer));
6981 2 : if (!status)
6982 2 : status = nc_put_vara_uchar(
6983 : new_cdfid, nDstVarId, anStart, anCount,
6984 : static_cast<unsigned char *>(pBuffer));
6985 2 : break;
6986 2 : case NC_USHORT:
6987 : status =
6988 2 : nc_get_vara_ushort(old_cdfid, nSrcVarId, anStart, anCount,
6989 : static_cast<unsigned short *>(pBuffer));
6990 2 : if (!status)
6991 2 : status = nc_put_vara_ushort(
6992 : new_cdfid, nDstVarId, anStart, anCount,
6993 : static_cast<unsigned short *>(pBuffer));
6994 2 : break;
6995 2 : case NC_UINT:
6996 : status =
6997 2 : nc_get_vara_uint(old_cdfid, nSrcVarId, anStart, anCount,
6998 : static_cast<unsigned int *>(pBuffer));
6999 2 : if (!status)
7000 : status =
7001 2 : nc_put_vara_uint(new_cdfid, nDstVarId, anStart, anCount,
7002 : static_cast<unsigned int *>(pBuffer));
7003 2 : break;
7004 2 : case NC_INT64:
7005 : status =
7006 2 : nc_get_vara_longlong(old_cdfid, nSrcVarId, anStart, anCount,
7007 : static_cast<long long *>(pBuffer));
7008 2 : if (!status)
7009 2 : status = nc_put_vara_longlong(
7010 : new_cdfid, nDstVarId, anStart, anCount,
7011 : static_cast<long long *>(pBuffer));
7012 2 : break;
7013 2 : case NC_UINT64:
7014 2 : status = nc_get_vara_ulonglong(
7015 : old_cdfid, nSrcVarId, anStart, anCount,
7016 : static_cast<unsigned long long *>(pBuffer));
7017 2 : if (!status)
7018 2 : status = nc_put_vara_ulonglong(
7019 : new_cdfid, nDstVarId, anStart, anCount,
7020 : static_cast<unsigned long long *>(pBuffer));
7021 2 : break;
7022 0 : default:
7023 0 : status = NC_EBADTYPE;
7024 : }
7025 :
7026 119 : NCDF_ERR(status);
7027 119 : if (status != NC_NOERR)
7028 : {
7029 0 : VSIFree(pBuffer);
7030 0 : return false;
7031 : }
7032 : }
7033 :
7034 121 : VSIFree(pBuffer);
7035 121 : return true;
7036 : }
7037 :
7038 : /************************************************************************/
7039 : /* NCDFIsUnlimitedDim() */
7040 : /************************************************************************/
7041 :
7042 58 : bool NCDFIsUnlimitedDim(bool bIsNC4, int cdfid, int nDimId)
7043 : {
7044 58 : if (bIsNC4)
7045 : {
7046 16 : int nUnlimitedDims = 0;
7047 16 : nc_inq_unlimdims(cdfid, &nUnlimitedDims, nullptr);
7048 16 : bool bFound = false;
7049 16 : if (nUnlimitedDims)
7050 : {
7051 : int *panUnlimitedDimIds =
7052 16 : static_cast<int *>(CPLMalloc(sizeof(int) * nUnlimitedDims));
7053 16 : nc_inq_unlimdims(cdfid, nullptr, panUnlimitedDimIds);
7054 30 : for (int i = 0; i < nUnlimitedDims; i++)
7055 : {
7056 22 : if (panUnlimitedDimIds[i] == nDimId)
7057 : {
7058 8 : bFound = true;
7059 8 : break;
7060 : }
7061 : }
7062 16 : CPLFree(panUnlimitedDimIds);
7063 : }
7064 16 : return bFound;
7065 : }
7066 : else
7067 : {
7068 42 : int nUnlimitedDimId = -1;
7069 42 : nc_inq(cdfid, nullptr, nullptr, nullptr, &nUnlimitedDimId);
7070 42 : return nDimId == nUnlimitedDimId;
7071 : }
7072 : }
7073 :
7074 : /************************************************************************/
7075 : /* CloneGrp() */
7076 : /************************************************************************/
7077 :
7078 16 : bool netCDFDataset::CloneGrp(int nOldGrpId, int nNewGrpId, bool bIsNC4,
7079 : int nLayerId, int nDimIdToGrow, size_t nNewSize)
7080 : {
7081 : // Clone dimensions
7082 16 : int nDimCount = -1;
7083 16 : int status = nc_inq_ndims(nOldGrpId, &nDimCount);
7084 16 : NCDF_ERR(status);
7085 16 : if (nDimCount < 0 || nDimCount > NC_MAX_DIMS)
7086 0 : return false;
7087 : int anDimIds[NC_MAX_DIMS];
7088 16 : int nUnlimiDimID = -1;
7089 16 : status = nc_inq_unlimdim(nOldGrpId, &nUnlimiDimID);
7090 16 : NCDF_ERR(status);
7091 16 : if (bIsNC4)
7092 : {
7093 : // In NC4, the dimension ids of a group are not necessarily in
7094 : // [0,nDimCount-1] range
7095 8 : int nDimCount2 = -1;
7096 8 : status = nc_inq_dimids(nOldGrpId, &nDimCount2, anDimIds, FALSE);
7097 8 : NCDF_ERR(status);
7098 8 : CPLAssert(nDimCount == nDimCount2);
7099 : }
7100 : else
7101 : {
7102 36 : for (int i = 0; i < nDimCount; i++)
7103 28 : anDimIds[i] = i;
7104 : }
7105 60 : for (int i = 0; i < nDimCount; i++)
7106 : {
7107 : char szDimName[NC_MAX_NAME + 1];
7108 44 : szDimName[0] = 0;
7109 44 : size_t nLen = 0;
7110 44 : const int nDimId = anDimIds[i];
7111 44 : status = nc_inq_dim(nOldGrpId, nDimId, szDimName, &nLen);
7112 44 : NCDF_ERR(status);
7113 44 : if (NCDFIsUnlimitedDim(bIsNC4, nOldGrpId, nDimId))
7114 16 : nLen = NC_UNLIMITED;
7115 28 : else if (nDimId == nDimIdToGrow && nOldGrpId == nLayerId)
7116 13 : nLen = nNewSize;
7117 44 : int nNewDimId = -1;
7118 44 : status = nc_def_dim(nNewGrpId, szDimName, nLen, &nNewDimId);
7119 44 : NCDF_ERR(status);
7120 44 : CPLAssert(nDimId == nNewDimId);
7121 44 : if (status != NC_NOERR)
7122 : {
7123 0 : return false;
7124 : }
7125 : }
7126 :
7127 : // Clone main attributes
7128 16 : if (!CloneAttributes(nOldGrpId, nNewGrpId, NC_GLOBAL, NC_GLOBAL))
7129 : {
7130 0 : return false;
7131 : }
7132 :
7133 : // Clone variable definitions
7134 16 : int nVarCount = -1;
7135 16 : status = nc_inq_nvars(nOldGrpId, &nVarCount);
7136 16 : NCDF_ERR(status);
7137 :
7138 137 : for (int i = 0; i < nVarCount; i++)
7139 : {
7140 : char szVarName[NC_MAX_NAME + 1];
7141 121 : szVarName[0] = 0;
7142 121 : status = nc_inq_varname(nOldGrpId, i, szVarName);
7143 121 : NCDF_ERR(status);
7144 121 : nc_type nc_datatype = NC_NAT;
7145 121 : status = nc_inq_vartype(nOldGrpId, i, &nc_datatype);
7146 121 : NCDF_ERR(status);
7147 121 : int nVarDimCount = -1;
7148 121 : status = nc_inq_varndims(nOldGrpId, i, &nVarDimCount);
7149 121 : NCDF_ERR(status);
7150 121 : status = nc_inq_vardimid(nOldGrpId, i, anDimIds);
7151 121 : NCDF_ERR(status);
7152 121 : int nNewVarId = -1;
7153 121 : status = nc_def_var(nNewGrpId, szVarName, nc_datatype, nVarDimCount,
7154 : anDimIds, &nNewVarId);
7155 121 : NCDF_ERR(status);
7156 121 : CPLAssert(i == nNewVarId);
7157 121 : if (status != NC_NOERR)
7158 : {
7159 0 : return false;
7160 : }
7161 :
7162 121 : if (!CloneAttributes(nOldGrpId, nNewGrpId, i, i))
7163 : {
7164 0 : return false;
7165 : }
7166 : }
7167 :
7168 16 : status = nc_enddef(nNewGrpId);
7169 16 : NCDF_ERR(status);
7170 16 : if (status != NC_NOERR)
7171 : {
7172 0 : return false;
7173 : }
7174 :
7175 : // Clone variable content
7176 137 : for (int i = 0; i < nVarCount; i++)
7177 : {
7178 121 : if (!CloneVariableContent(nOldGrpId, nNewGrpId, i, i))
7179 : {
7180 0 : return false;
7181 : }
7182 : }
7183 :
7184 16 : return true;
7185 : }
7186 :
7187 : /************************************************************************/
7188 : /* GrowDim() */
7189 : /************************************************************************/
7190 :
7191 13 : bool netCDFDataset::GrowDim(int nLayerId, int nDimIdToGrow, size_t nNewSize)
7192 : {
7193 : int nCreationMode;
7194 : // Set nCreationMode based on eFormat.
7195 13 : switch (eFormat)
7196 : {
7197 : #ifdef NETCDF_HAS_NC2
7198 0 : case NCDF_FORMAT_NC2:
7199 0 : nCreationMode = NC_CLOBBER | NC_64BIT_OFFSET;
7200 0 : break;
7201 : #endif
7202 5 : case NCDF_FORMAT_NC4:
7203 5 : nCreationMode = NC_CLOBBER | NC_NETCDF4;
7204 5 : break;
7205 0 : case NCDF_FORMAT_NC4C:
7206 0 : nCreationMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
7207 0 : break;
7208 8 : case NCDF_FORMAT_NC:
7209 : default:
7210 8 : nCreationMode = NC_CLOBBER;
7211 8 : break;
7212 : }
7213 :
7214 13 : int new_cdfid = -1;
7215 26 : CPLString osTmpFilename(osFilename + ".tmp");
7216 26 : CPLString osFilenameForNCCreate(osTmpFilename);
7217 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
7218 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
7219 : {
7220 : char *pszTemp =
7221 : CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
7222 : osFilenameForNCCreate = pszTemp;
7223 : CPLFree(pszTemp);
7224 : }
7225 : #endif
7226 13 : int status = nc_create(osFilenameForNCCreate, nCreationMode, &new_cdfid);
7227 13 : NCDF_ERR(status);
7228 13 : if (status != NC_NOERR)
7229 0 : return false;
7230 :
7231 13 : if (!CloneGrp(cdfid, new_cdfid, eFormat == NCDF_FORMAT_NC4, nLayerId,
7232 : nDimIdToGrow, nNewSize))
7233 : {
7234 0 : GDAL_nc_close(new_cdfid);
7235 0 : return false;
7236 : }
7237 :
7238 13 : int nGroupCount = 0;
7239 26 : std::vector<CPLString> oListGrpName;
7240 31 : if (eFormat == NCDF_FORMAT_NC4 &&
7241 18 : nc_inq_grps(cdfid, &nGroupCount, nullptr) == NC_NOERR &&
7242 5 : nGroupCount > 0)
7243 : {
7244 : int *panGroupIds =
7245 2 : static_cast<int *>(CPLMalloc(sizeof(int) * nGroupCount));
7246 2 : status = nc_inq_grps(cdfid, nullptr, panGroupIds);
7247 2 : NCDF_ERR(status);
7248 5 : for (int i = 0; i < nGroupCount; i++)
7249 : {
7250 : char szGroupName[NC_MAX_NAME + 1];
7251 3 : szGroupName[0] = 0;
7252 3 : NCDF_ERR(nc_inq_grpname(panGroupIds[i], szGroupName));
7253 3 : int nNewGrpId = -1;
7254 3 : status = nc_def_grp(new_cdfid, szGroupName, &nNewGrpId);
7255 3 : NCDF_ERR(status);
7256 3 : if (status != NC_NOERR)
7257 : {
7258 0 : CPLFree(panGroupIds);
7259 0 : GDAL_nc_close(new_cdfid);
7260 0 : return false;
7261 : }
7262 3 : if (!CloneGrp(panGroupIds[i], nNewGrpId, /*bIsNC4=*/true, nLayerId,
7263 : nDimIdToGrow, nNewSize))
7264 : {
7265 0 : CPLFree(panGroupIds);
7266 0 : GDAL_nc_close(new_cdfid);
7267 0 : return false;
7268 : }
7269 : }
7270 2 : CPLFree(panGroupIds);
7271 :
7272 5 : for (int i = 0; i < this->GetLayerCount(); i++)
7273 : {
7274 3 : auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
7275 3 : if (poLayer)
7276 : {
7277 : char szGroupName[NC_MAX_NAME + 1];
7278 3 : szGroupName[0] = 0;
7279 3 : status = nc_inq_grpname(poLayer->GetCDFID(), szGroupName);
7280 3 : NCDF_ERR(status);
7281 3 : oListGrpName.push_back(szGroupName);
7282 : }
7283 : }
7284 : }
7285 :
7286 13 : GDAL_nc_close(cdfid);
7287 13 : cdfid = -1;
7288 13 : GDAL_nc_close(new_cdfid);
7289 :
7290 26 : CPLString osOriFilename(osFilename + ".ori");
7291 26 : if (VSIRename(osFilename, osOriFilename) != 0 ||
7292 13 : VSIRename(osTmpFilename, osFilename) != 0)
7293 : {
7294 0 : CPLError(CE_Failure, CPLE_FileIO, "Renaming of files failed");
7295 0 : return false;
7296 : }
7297 13 : VSIUnlink(osOriFilename);
7298 :
7299 26 : CPLString osFilenameForNCOpen(osFilename);
7300 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
7301 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
7302 : {
7303 : char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
7304 : osFilenameForNCOpen = pszTemp;
7305 : CPLFree(pszTemp);
7306 : }
7307 : #endif
7308 13 : status = GDAL_nc_open(osFilenameForNCOpen, NC_WRITE, &cdfid);
7309 13 : NCDF_ERR(status);
7310 13 : if (status != NC_NOERR)
7311 0 : return false;
7312 13 : bDefineMode = false;
7313 :
7314 13 : if (!oListGrpName.empty())
7315 : {
7316 5 : for (int i = 0; i < this->GetLayerCount(); i++)
7317 : {
7318 3 : auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
7319 3 : if (poLayer)
7320 : {
7321 3 : int nNewLayerCDFID = -1;
7322 3 : status = nc_inq_ncid(cdfid, oListGrpName[i].c_str(),
7323 : &nNewLayerCDFID);
7324 3 : NCDF_ERR(status);
7325 3 : poLayer->SetCDFID(nNewLayerCDFID);
7326 : }
7327 : }
7328 : }
7329 : else
7330 : {
7331 22 : for (int i = 0; i < this->GetLayerCount(); i++)
7332 : {
7333 11 : auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
7334 11 : if (poLayer)
7335 11 : poLayer->SetCDFID(cdfid);
7336 : }
7337 : }
7338 :
7339 13 : return true;
7340 : }
7341 :
7342 : #ifdef ENABLE_NCDUMP
7343 :
7344 : /************************************************************************/
7345 : /* netCDFDatasetCreateTempFile() */
7346 : /************************************************************************/
7347 :
7348 : /* Create a netCDF file from a text dump (format of ncdump) */
7349 : /* Mostly to easy fuzzing of the driver, while still generating valid */
7350 : /* netCDF files. */
7351 : /* Note: not all data types are supported ! */
7352 4 : bool netCDFDatasetCreateTempFile(NetCDFFormatEnum eFormat,
7353 : const char *pszTmpFilename, VSILFILE *fpSrc)
7354 : {
7355 4 : CPL_IGNORE_RET_VAL(eFormat);
7356 4 : int nCreateMode = NC_CLOBBER;
7357 4 : if (eFormat == NCDF_FORMAT_NC4)
7358 1 : nCreateMode |= NC_NETCDF4;
7359 3 : else if (eFormat == NCDF_FORMAT_NC4C)
7360 0 : nCreateMode |= NC_NETCDF4 | NC_CLASSIC_MODEL;
7361 4 : int nCdfId = -1;
7362 4 : int status = nc_create(pszTmpFilename, nCreateMode, &nCdfId);
7363 4 : if (status != NC_NOERR)
7364 : {
7365 0 : return false;
7366 : }
7367 4 : VSIFSeekL(fpSrc, 0, SEEK_SET);
7368 : const char *pszLine;
7369 4 : constexpr int SECTION_NONE = 0;
7370 4 : constexpr int SECTION_DIMENSIONS = 1;
7371 4 : constexpr int SECTION_VARIABLES = 2;
7372 4 : constexpr int SECTION_DATA = 3;
7373 4 : int nActiveSection = SECTION_NONE;
7374 8 : std::map<CPLString, int> oMapDimToId;
7375 8 : std::map<int, int> oMapDimIdToDimLen;
7376 8 : std::map<CPLString, int> oMapVarToId;
7377 8 : std::map<int, std::vector<int>> oMapVarIdToVectorOfDimId;
7378 8 : std::map<int, int> oMapVarIdToType;
7379 4 : std::set<CPLString> oSetAttrDefined;
7380 4 : oMapVarToId[""] = -1;
7381 4 : size_t nTotalVarSize = 0;
7382 208 : while ((pszLine = CPLReadLineL(fpSrc)) != nullptr)
7383 : {
7384 204 : if (STARTS_WITH(pszLine, "dimensions:") &&
7385 : nActiveSection == SECTION_NONE)
7386 : {
7387 4 : nActiveSection = SECTION_DIMENSIONS;
7388 : }
7389 200 : else if (STARTS_WITH(pszLine, "variables:") &&
7390 : nActiveSection == SECTION_DIMENSIONS)
7391 : {
7392 4 : nActiveSection = SECTION_VARIABLES;
7393 : }
7394 196 : else if (STARTS_WITH(pszLine, "data:") &&
7395 : nActiveSection == SECTION_VARIABLES)
7396 : {
7397 4 : nActiveSection = SECTION_DATA;
7398 4 : status = nc_enddef(nCdfId);
7399 4 : if (status != NC_NOERR)
7400 : {
7401 0 : CPLDebug("netCDF", "nc_enddef() failed: %s",
7402 : nc_strerror(status));
7403 : }
7404 : }
7405 192 : else if (nActiveSection == SECTION_DIMENSIONS)
7406 : {
7407 9 : char **papszTokens = CSLTokenizeString2(pszLine, " \t=;", 0);
7408 9 : if (CSLCount(papszTokens) == 2)
7409 : {
7410 9 : const char *pszDimName = papszTokens[0];
7411 9 : bool bValidName = true;
7412 9 : if (STARTS_WITH(pszDimName, "_nc4_non_coord_"))
7413 : {
7414 : // This is an internal netcdf prefix. Using it may
7415 : // cause memory leaks.
7416 0 : bValidName = false;
7417 : }
7418 9 : if (!bValidName)
7419 : {
7420 0 : CPLDebug("netCDF",
7421 : "nc_def_dim(%s) failed: invalid name found",
7422 : pszDimName);
7423 0 : CSLDestroy(papszTokens);
7424 0 : continue;
7425 : }
7426 :
7427 : const bool bIsASCII =
7428 9 : CPLIsASCII(pszDimName, static_cast<size_t>(-1));
7429 9 : if (!bIsASCII)
7430 : {
7431 : // Workaround https://github.com/Unidata/netcdf-c/pull/450
7432 0 : CPLDebug("netCDF",
7433 : "nc_def_dim(%s) failed: rejected because "
7434 : "of non-ASCII characters",
7435 : pszDimName);
7436 0 : CSLDestroy(papszTokens);
7437 0 : continue;
7438 : }
7439 9 : int nDimSize = EQUAL(papszTokens[1], "UNLIMITED")
7440 : ? NC_UNLIMITED
7441 9 : : atoi(papszTokens[1]);
7442 9 : if (nDimSize >= 1000)
7443 1 : nDimSize = 1000; // to avoid very long processing
7444 9 : if (nDimSize >= 0)
7445 : {
7446 9 : int nDimId = -1;
7447 9 : status = nc_def_dim(nCdfId, pszDimName, nDimSize, &nDimId);
7448 9 : if (status != NC_NOERR)
7449 : {
7450 0 : CPLDebug("netCDF", "nc_def_dim(%s, %d) failed: %s",
7451 : pszDimName, nDimSize, nc_strerror(status));
7452 : }
7453 : else
7454 : {
7455 : #ifdef DEBUG_VERBOSE
7456 : CPLDebug("netCDF", "nc_def_dim(%s, %d) (%s) succeeded",
7457 : pszDimName, nDimSize, pszLine);
7458 : #endif
7459 9 : oMapDimToId[pszDimName] = nDimId;
7460 9 : oMapDimIdToDimLen[nDimId] = nDimSize;
7461 : }
7462 : }
7463 : }
7464 9 : CSLDestroy(papszTokens);
7465 : }
7466 183 : else if (nActiveSection == SECTION_VARIABLES)
7467 : {
7468 390 : while (*pszLine == ' ' || *pszLine == '\t')
7469 249 : pszLine++;
7470 141 : const char *pszColumn = strchr(pszLine, ':');
7471 141 : const char *pszEqual = strchr(pszLine, '=');
7472 141 : if (pszColumn == nullptr)
7473 : {
7474 21 : char **papszTokens = CSLTokenizeString2(pszLine, " \t=(),;", 0);
7475 21 : if (CSLCount(papszTokens) >= 2)
7476 : {
7477 17 : const char *pszVarName = papszTokens[1];
7478 17 : bool bValidName = true;
7479 17 : if (STARTS_WITH(pszVarName, "_nc4_non_coord_"))
7480 : {
7481 : // This is an internal netcdf prefix. Using it may
7482 : // cause memory leaks.
7483 0 : bValidName = false;
7484 : }
7485 138 : for (int i = 0; pszVarName[i]; i++)
7486 : {
7487 121 : if (!((pszVarName[i] >= 'a' && pszVarName[i] <= 'z') ||
7488 28 : (pszVarName[i] >= 'A' && pszVarName[i] <= 'Z') ||
7489 9 : (pszVarName[i] >= '0' && pszVarName[i] <= '9') ||
7490 6 : pszVarName[i] == '_'))
7491 : {
7492 0 : bValidName = false;
7493 : }
7494 : }
7495 17 : if (!bValidName)
7496 : {
7497 0 : CPLDebug(
7498 : "netCDF",
7499 : "nc_def_var(%s) failed: illegal character found",
7500 : pszVarName);
7501 0 : CSLDestroy(papszTokens);
7502 0 : continue;
7503 : }
7504 17 : if (oMapVarToId.find(pszVarName) != oMapVarToId.end())
7505 : {
7506 0 : CPLDebug("netCDF",
7507 : "nc_def_var(%s) failed: already defined",
7508 : pszVarName);
7509 0 : CSLDestroy(papszTokens);
7510 0 : continue;
7511 : }
7512 17 : const char *pszVarType = papszTokens[0];
7513 17 : int nc_datatype = NC_BYTE;
7514 17 : size_t nDataTypeSize = 1;
7515 17 : if (EQUAL(pszVarType, "char"))
7516 : {
7517 6 : nc_datatype = NC_CHAR;
7518 6 : nDataTypeSize = 1;
7519 : }
7520 11 : else if (EQUAL(pszVarType, "byte"))
7521 : {
7522 3 : nc_datatype = NC_BYTE;
7523 3 : nDataTypeSize = 1;
7524 : }
7525 8 : else if (EQUAL(pszVarType, "short"))
7526 : {
7527 0 : nc_datatype = NC_SHORT;
7528 0 : nDataTypeSize = 2;
7529 : }
7530 8 : else if (EQUAL(pszVarType, "int"))
7531 : {
7532 0 : nc_datatype = NC_INT;
7533 0 : nDataTypeSize = 4;
7534 : }
7535 8 : else if (EQUAL(pszVarType, "float"))
7536 : {
7537 0 : nc_datatype = NC_FLOAT;
7538 0 : nDataTypeSize = 4;
7539 : }
7540 8 : else if (EQUAL(pszVarType, "double"))
7541 : {
7542 8 : nc_datatype = NC_DOUBLE;
7543 8 : nDataTypeSize = 8;
7544 : }
7545 0 : else if (EQUAL(pszVarType, "ubyte"))
7546 : {
7547 0 : nc_datatype = NC_UBYTE;
7548 0 : nDataTypeSize = 1;
7549 : }
7550 0 : else if (EQUAL(pszVarType, "ushort"))
7551 : {
7552 0 : nc_datatype = NC_USHORT;
7553 0 : nDataTypeSize = 2;
7554 : }
7555 0 : else if (EQUAL(pszVarType, "uint"))
7556 : {
7557 0 : nc_datatype = NC_UINT;
7558 0 : nDataTypeSize = 4;
7559 : }
7560 0 : else if (EQUAL(pszVarType, "int64"))
7561 : {
7562 0 : nc_datatype = NC_INT64;
7563 0 : nDataTypeSize = 8;
7564 : }
7565 0 : else if (EQUAL(pszVarType, "uint64"))
7566 : {
7567 0 : nc_datatype = NC_UINT64;
7568 0 : nDataTypeSize = 8;
7569 : }
7570 :
7571 17 : int nDims = CSLCount(papszTokens) - 2;
7572 17 : if (nDims >= 32)
7573 : {
7574 : // The number of dimensions in a netCDFv4 file is
7575 : // limited by #define H5S_MAX_RANK 32
7576 : // but libnetcdf doesn't check that...
7577 0 : CPLDebug("netCDF",
7578 : "nc_def_var(%s) failed: too many dimensions",
7579 : pszVarName);
7580 0 : CSLDestroy(papszTokens);
7581 0 : continue;
7582 : }
7583 17 : std::vector<int> aoDimIds;
7584 17 : bool bFailed = false;
7585 17 : size_t nSize = 1;
7586 35 : for (int i = 0; i < nDims; i++)
7587 : {
7588 18 : const char *pszDimName = papszTokens[2 + i];
7589 18 : if (oMapDimToId.find(pszDimName) == oMapDimToId.end())
7590 : {
7591 0 : bFailed = true;
7592 0 : break;
7593 : }
7594 18 : const int nDimId = oMapDimToId[pszDimName];
7595 18 : aoDimIds.push_back(nDimId);
7596 :
7597 18 : const size_t nDimSize = oMapDimIdToDimLen[nDimId];
7598 18 : if (nDimSize != 0)
7599 : {
7600 18 : if (nSize >
7601 18 : std::numeric_limits<size_t>::max() / nDimSize)
7602 : {
7603 0 : bFailed = true;
7604 0 : break;
7605 : }
7606 : else
7607 : {
7608 18 : nSize *= nDimSize;
7609 : }
7610 : }
7611 : }
7612 17 : if (bFailed)
7613 : {
7614 0 : CPLDebug("netCDF",
7615 : "nc_def_var(%s) failed: unknown dimension(s)",
7616 : pszVarName);
7617 0 : CSLDestroy(papszTokens);
7618 0 : continue;
7619 : }
7620 17 : if (nSize > 100U * 1024 * 1024 / nDataTypeSize)
7621 : {
7622 0 : CPLDebug("netCDF",
7623 : "nc_def_var(%s) failed: too large data",
7624 : pszVarName);
7625 0 : CSLDestroy(papszTokens);
7626 0 : continue;
7627 : }
7628 17 : if (nTotalVarSize >
7629 34 : std::numeric_limits<size_t>::max() - nSize ||
7630 17 : nTotalVarSize + nSize > 100 * 1024 * 1024)
7631 : {
7632 0 : CPLDebug("netCDF",
7633 : "nc_def_var(%s) failed: too large data",
7634 : pszVarName);
7635 0 : CSLDestroy(papszTokens);
7636 0 : continue;
7637 : }
7638 17 : nTotalVarSize += nSize;
7639 :
7640 17 : int nVarId = -1;
7641 : status =
7642 30 : nc_def_var(nCdfId, pszVarName, nc_datatype, nDims,
7643 13 : (nDims) ? &aoDimIds[0] : nullptr, &nVarId);
7644 17 : if (status != NC_NOERR)
7645 : {
7646 0 : CPLDebug("netCDF", "nc_def_var(%s) failed: %s",
7647 : pszVarName, nc_strerror(status));
7648 : }
7649 : else
7650 : {
7651 : #ifdef DEBUG_VERBOSE
7652 : CPLDebug("netCDF", "nc_def_var(%s) (%s) succeeded",
7653 : pszVarName, pszLine);
7654 : #endif
7655 17 : oMapVarToId[pszVarName] = nVarId;
7656 17 : oMapVarIdToType[nVarId] = nc_datatype;
7657 17 : oMapVarIdToVectorOfDimId[nVarId] = std::move(aoDimIds);
7658 : }
7659 : }
7660 21 : CSLDestroy(papszTokens);
7661 : }
7662 120 : else if (pszEqual != nullptr && pszEqual - pszColumn > 0)
7663 : {
7664 116 : CPLString osVarName(pszLine, pszColumn - pszLine);
7665 116 : CPLString osAttrName(pszColumn + 1, pszEqual - pszColumn - 1);
7666 116 : osAttrName.Trim();
7667 116 : if (oMapVarToId.find(osVarName) == oMapVarToId.end())
7668 : {
7669 0 : CPLDebug("netCDF",
7670 : "nc_put_att(%s:%s) failed: "
7671 : "no corresponding variable",
7672 : osVarName.c_str(), osAttrName.c_str());
7673 0 : continue;
7674 : }
7675 116 : bool bValidName = true;
7676 1743 : for (size_t i = 0; i < osAttrName.size(); i++)
7677 : {
7678 1865 : if (!((osAttrName[i] >= 'a' && osAttrName[i] <= 'z') ||
7679 238 : (osAttrName[i] >= 'A' && osAttrName[i] <= 'Z') ||
7680 158 : (osAttrName[i] >= '0' && osAttrName[i] <= '9') ||
7681 158 : osAttrName[i] == '_'))
7682 : {
7683 0 : bValidName = false;
7684 : }
7685 : }
7686 116 : if (!bValidName)
7687 : {
7688 0 : CPLDebug(
7689 : "netCDF",
7690 : "nc_put_att(%s:%s) failed: illegal character found",
7691 : osVarName.c_str(), osAttrName.c_str());
7692 0 : continue;
7693 : }
7694 116 : if (oSetAttrDefined.find(osVarName + ":" + osAttrName) !=
7695 232 : oSetAttrDefined.end())
7696 : {
7697 0 : CPLDebug("netCDF",
7698 : "nc_put_att(%s:%s) failed: already defined",
7699 : osVarName.c_str(), osAttrName.c_str());
7700 0 : continue;
7701 : }
7702 :
7703 116 : const int nVarId = oMapVarToId[osVarName];
7704 116 : const char *pszValue = pszEqual + 1;
7705 232 : while (*pszValue == ' ')
7706 116 : pszValue++;
7707 :
7708 116 : status = NC_EBADTYPE;
7709 116 : if (*pszValue == '"')
7710 : {
7711 : // For _FillValue, the attribute type should match
7712 : // the variable type. Leaks memory with NC4 otherwise
7713 74 : if (osAttrName == "_FillValue")
7714 : {
7715 0 : CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
7716 : osVarName.c_str(), osAttrName.c_str(),
7717 : nc_strerror(status));
7718 0 : continue;
7719 : }
7720 :
7721 : // Unquote and unescape string value
7722 74 : CPLString osVal(pszValue + 1);
7723 222 : while (!osVal.empty())
7724 : {
7725 222 : if (osVal.back() == ';' || osVal.back() == ' ')
7726 : {
7727 148 : osVal.pop_back();
7728 : }
7729 74 : else if (osVal.back() == '"')
7730 : {
7731 74 : osVal.pop_back();
7732 74 : break;
7733 : }
7734 : else
7735 : {
7736 0 : break;
7737 : }
7738 : }
7739 74 : osVal.replaceAll("\\\"", '"');
7740 74 : status = nc_put_att_text(nCdfId, nVarId, osAttrName,
7741 : osVal.size(), osVal.c_str());
7742 : }
7743 : else
7744 : {
7745 84 : CPLString osVal(pszValue);
7746 126 : while (!osVal.empty())
7747 : {
7748 126 : if (osVal.back() == ';' || osVal.back() == ' ')
7749 : {
7750 84 : osVal.pop_back();
7751 : }
7752 : else
7753 : {
7754 42 : break;
7755 : }
7756 : }
7757 42 : int nc_datatype = -1;
7758 42 : if (!osVal.empty() && osVal.back() == 'b')
7759 : {
7760 3 : nc_datatype = NC_BYTE;
7761 3 : osVal.pop_back();
7762 : }
7763 39 : else if (!osVal.empty() && osVal.back() == 's')
7764 : {
7765 3 : nc_datatype = NC_SHORT;
7766 3 : osVal.pop_back();
7767 : }
7768 42 : if (CPLGetValueType(osVal) == CPL_VALUE_INTEGER)
7769 : {
7770 7 : if (nc_datatype < 0)
7771 4 : nc_datatype = NC_INT;
7772 : }
7773 35 : else if (CPLGetValueType(osVal) == CPL_VALUE_REAL)
7774 : {
7775 32 : nc_datatype = NC_DOUBLE;
7776 : }
7777 : else
7778 : {
7779 3 : nc_datatype = -1;
7780 : }
7781 :
7782 : // For _FillValue, check that the attribute type matches
7783 : // the variable type. Leaks memory with NC4 otherwise
7784 42 : if (osAttrName == "_FillValue")
7785 : {
7786 6 : if (nVarId < 0 ||
7787 3 : nc_datatype != oMapVarIdToType[nVarId])
7788 : {
7789 0 : nc_datatype = -1;
7790 : }
7791 : }
7792 :
7793 42 : if (nc_datatype == NC_BYTE)
7794 : {
7795 : signed char chVal =
7796 3 : static_cast<signed char>(atoi(osVal));
7797 3 : status = nc_put_att_schar(nCdfId, nVarId, osAttrName,
7798 : NC_BYTE, 1, &chVal);
7799 : }
7800 39 : else if (nc_datatype == NC_SHORT)
7801 : {
7802 0 : short nVal = static_cast<short>(atoi(osVal));
7803 0 : status = nc_put_att_short(nCdfId, nVarId, osAttrName,
7804 : NC_SHORT, 1, &nVal);
7805 : }
7806 39 : else if (nc_datatype == NC_INT)
7807 : {
7808 4 : int nVal = static_cast<int>(atoi(osVal));
7809 4 : status = nc_put_att_int(nCdfId, nVarId, osAttrName,
7810 : NC_INT, 1, &nVal);
7811 : }
7812 35 : else if (nc_datatype == NC_DOUBLE)
7813 : {
7814 32 : double dfVal = CPLAtof(osVal);
7815 32 : status = nc_put_att_double(nCdfId, nVarId, osAttrName,
7816 : NC_DOUBLE, 1, &dfVal);
7817 : }
7818 : }
7819 116 : if (status != NC_NOERR)
7820 : {
7821 3 : CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
7822 : osVarName.c_str(), osAttrName.c_str(),
7823 : nc_strerror(status));
7824 : }
7825 : else
7826 : {
7827 113 : oSetAttrDefined.insert(osVarName + ":" + osAttrName);
7828 : #ifdef DEBUG_VERBOSE
7829 : CPLDebug("netCDF", "nc_put_att_(%s:%s) (%s) succeeded",
7830 : osVarName.c_str(), osAttrName.c_str(), pszLine);
7831 : #endif
7832 : }
7833 : }
7834 : }
7835 42 : else if (nActiveSection == SECTION_DATA)
7836 : {
7837 55 : while (*pszLine == ' ' || *pszLine == '\t')
7838 17 : pszLine++;
7839 38 : const char *pszEqual = strchr(pszLine, '=');
7840 38 : if (pszEqual)
7841 : {
7842 17 : CPLString osVarName(pszLine, pszEqual - pszLine);
7843 17 : osVarName.Trim();
7844 17 : if (oMapVarToId.find(osVarName) == oMapVarToId.end())
7845 0 : continue;
7846 17 : const int nVarId = oMapVarToId[osVarName];
7847 17 : CPLString osAccVal(pszEqual + 1);
7848 17 : osAccVal.Trim();
7849 153 : while (osAccVal.empty() || osAccVal.back() != ';')
7850 : {
7851 136 : pszLine = CPLReadLineL(fpSrc);
7852 136 : if (pszLine == nullptr)
7853 0 : break;
7854 272 : CPLString osVal(pszLine);
7855 136 : osVal.Trim();
7856 136 : osAccVal += osVal;
7857 : }
7858 17 : if (pszLine == nullptr)
7859 0 : break;
7860 17 : osAccVal.pop_back();
7861 :
7862 : const std::vector<int> aoDimIds =
7863 34 : oMapVarIdToVectorOfDimId[nVarId];
7864 17 : size_t nSize = 1;
7865 34 : std::vector<size_t> aoStart, aoEdge;
7866 17 : aoStart.resize(aoDimIds.size());
7867 17 : aoEdge.resize(aoDimIds.size());
7868 35 : for (size_t i = 0; i < aoDimIds.size(); ++i)
7869 : {
7870 18 : const size_t nDimSize = oMapDimIdToDimLen[aoDimIds[i]];
7871 36 : if (nDimSize != 0 &&
7872 18 : nSize > std::numeric_limits<size_t>::max() / nDimSize)
7873 : {
7874 0 : nSize = 0;
7875 : }
7876 : else
7877 : {
7878 18 : nSize *= nDimSize;
7879 : }
7880 18 : aoStart[i] = 0;
7881 18 : aoEdge[i] = nDimSize;
7882 : }
7883 :
7884 17 : status = NC_EBADTYPE;
7885 17 : if (nSize == 0)
7886 : {
7887 : // Might happen with a unlimited dimension
7888 : }
7889 17 : else if (oMapVarIdToType[nVarId] == NC_DOUBLE)
7890 : {
7891 8 : if (!aoStart.empty())
7892 : {
7893 : char **papszTokens =
7894 8 : CSLTokenizeString2(osAccVal, " ,;", 0);
7895 8 : size_t nTokens = CSLCount(papszTokens);
7896 8 : if (nTokens >= nSize)
7897 : {
7898 : double *padfVals = static_cast<double *>(
7899 8 : VSI_CALLOC_VERBOSE(nSize, sizeof(double)));
7900 8 : if (padfVals)
7901 : {
7902 132 : for (size_t i = 0; i < nSize; i++)
7903 : {
7904 124 : padfVals[i] = CPLAtof(papszTokens[i]);
7905 : }
7906 8 : status = nc_put_vara_double(
7907 8 : nCdfId, nVarId, &aoStart[0], &aoEdge[0],
7908 : padfVals);
7909 8 : VSIFree(padfVals);
7910 : }
7911 : }
7912 8 : CSLDestroy(papszTokens);
7913 : }
7914 : }
7915 9 : else if (oMapVarIdToType[nVarId] == NC_BYTE)
7916 : {
7917 3 : if (!aoStart.empty())
7918 : {
7919 : char **papszTokens =
7920 3 : CSLTokenizeString2(osAccVal, " ,;", 0);
7921 3 : size_t nTokens = CSLCount(papszTokens);
7922 3 : if (nTokens >= nSize)
7923 : {
7924 : signed char *panVals = static_cast<signed char *>(
7925 3 : VSI_CALLOC_VERBOSE(nSize, sizeof(signed char)));
7926 3 : if (panVals)
7927 : {
7928 1203 : for (size_t i = 0; i < nSize; i++)
7929 : {
7930 1200 : panVals[i] = static_cast<signed char>(
7931 1200 : atoi(papszTokens[i]));
7932 : }
7933 3 : status = nc_put_vara_schar(nCdfId, nVarId,
7934 3 : &aoStart[0],
7935 3 : &aoEdge[0], panVals);
7936 3 : VSIFree(panVals);
7937 : }
7938 : }
7939 3 : CSLDestroy(papszTokens);
7940 : }
7941 : }
7942 6 : else if (oMapVarIdToType[nVarId] == NC_CHAR)
7943 : {
7944 6 : if (aoStart.size() == 2)
7945 : {
7946 4 : std::vector<CPLString> aoStrings;
7947 2 : bool bInString = false;
7948 4 : CPLString osCurString;
7949 935 : for (size_t i = 0; i < osAccVal.size();)
7950 : {
7951 933 : if (!bInString)
7952 : {
7953 8 : if (osAccVal[i] == '"')
7954 : {
7955 4 : bInString = true;
7956 4 : osCurString.clear();
7957 : }
7958 8 : i++;
7959 : }
7960 926 : else if (osAccVal[i] == '\\' &&
7961 926 : i + 1 < osAccVal.size() &&
7962 1 : osAccVal[i + 1] == '"')
7963 : {
7964 1 : osCurString += '"';
7965 1 : i += 2;
7966 : }
7967 924 : else if (osAccVal[i] == '"')
7968 : {
7969 4 : aoStrings.push_back(osCurString);
7970 4 : osCurString.clear();
7971 4 : bInString = false;
7972 4 : i++;
7973 : }
7974 : else
7975 : {
7976 920 : osCurString += osAccVal[i];
7977 920 : i++;
7978 : }
7979 : }
7980 2 : const size_t nRecords = oMapDimIdToDimLen[aoDimIds[0]];
7981 2 : const size_t nWidth = oMapDimIdToDimLen[aoDimIds[1]];
7982 2 : size_t nIters = aoStrings.size();
7983 2 : if (nIters > nRecords)
7984 0 : nIters = nRecords;
7985 6 : for (size_t i = 0; i < nIters; i++)
7986 : {
7987 : size_t anIndex[2];
7988 4 : anIndex[0] = i;
7989 4 : anIndex[1] = 0;
7990 : size_t anCount[2];
7991 4 : anCount[0] = 1;
7992 4 : anCount[1] = aoStrings[i].size();
7993 4 : if (anCount[1] > nWidth)
7994 0 : anCount[1] = nWidth;
7995 : status =
7996 4 : nc_put_vara_text(nCdfId, nVarId, anIndex,
7997 4 : anCount, aoStrings[i].c_str());
7998 4 : if (status != NC_NOERR)
7999 0 : break;
8000 : }
8001 : }
8002 : }
8003 17 : if (status != NC_NOERR)
8004 : {
8005 4 : CPLDebug("netCDF", "nc_put_var_(%s) failed: %s",
8006 : osVarName.c_str(), nc_strerror(status));
8007 : }
8008 : }
8009 : }
8010 : }
8011 :
8012 4 : GDAL_nc_close(nCdfId);
8013 4 : return true;
8014 : }
8015 :
8016 : #endif // ENABLE_NCDUMP
8017 :
8018 : /************************************************************************/
8019 : /* Open() */
8020 : /************************************************************************/
8021 :
8022 676 : GDALDataset *netCDFDataset::Open(GDALOpenInfo *poOpenInfo)
8023 :
8024 : {
8025 : #ifdef NCDF_DEBUG
8026 : CPLDebug("GDAL_netCDF", "\n=====\nOpen(), filename=[%s]",
8027 : poOpenInfo->pszFilename);
8028 : #endif
8029 :
8030 : // Does this appear to be a netcdf file?
8031 676 : NetCDFFormatEnum eTmpFormat = NCDF_FORMAT_NONE;
8032 676 : if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
8033 : {
8034 617 : eTmpFormat = netCDFIdentifyFormat(poOpenInfo, /* bCheckExt = */ true);
8035 : #ifdef NCDF_DEBUG
8036 : CPLDebug("GDAL_netCDF", "identified format %d", eTmpFormat);
8037 : #endif
8038 : // Note: not calling Identify() directly, because we want the file type.
8039 : // Only support NCDF_FORMAT* formats.
8040 617 : if (NCDF_FORMAT_NC == eTmpFormat || NCDF_FORMAT_NC2 == eTmpFormat ||
8041 2 : NCDF_FORMAT_NC4 == eTmpFormat || NCDF_FORMAT_NC4C == eTmpFormat)
8042 : {
8043 : // ok
8044 : }
8045 2 : else if (eTmpFormat == NCDF_FORMAT_HDF4 &&
8046 0 : poOpenInfo->IsSingleAllowedDriver("netCDF"))
8047 : {
8048 : // ok
8049 : }
8050 : else
8051 : {
8052 2 : return nullptr;
8053 : }
8054 : }
8055 : else
8056 : {
8057 : #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
8058 : // We don't necessarily want to catch bugs in libnetcdf ...
8059 : if (CPLGetConfigOption("DISABLE_OPEN_REAL_NETCDF_FILES", nullptr))
8060 : {
8061 : return nullptr;
8062 : }
8063 : #endif
8064 : }
8065 :
8066 674 : if (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER)
8067 : {
8068 182 : return OpenMultiDim(poOpenInfo);
8069 : }
8070 :
8071 984 : CPLMutexHolderD(&hNCMutex);
8072 :
8073 492 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
8074 : // GDALDataset own mutex.
8075 492 : netCDFDataset *poDS = new netCDFDataset();
8076 492 : poDS->papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
8077 492 : CPLAcquireMutex(hNCMutex, 1000.0);
8078 :
8079 492 : poDS->SetDescription(poOpenInfo->pszFilename);
8080 :
8081 : // Check if filename start with NETCDF: tag.
8082 492 : bool bTreatAsSubdataset = false;
8083 984 : CPLString osSubdatasetName;
8084 :
8085 : #ifdef ENABLE_NCDUMP
8086 492 : const char *pszHeader =
8087 : reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
8088 492 : if (poOpenInfo->fpL != nullptr && STARTS_WITH(pszHeader, "netcdf ") &&
8089 3 : strstr(pszHeader, "dimensions:") && strstr(pszHeader, "variables:"))
8090 : {
8091 : // By default create a temporary file that will be destroyed,
8092 : // unless NETCDF_TMP_FILE is defined. Can be useful to see which
8093 : // netCDF file has been generated from a potential fuzzed input.
8094 3 : poDS->osFilename = CPLGetConfigOption("NETCDF_TMP_FILE", "");
8095 3 : if (poDS->osFilename.empty())
8096 : {
8097 3 : poDS->bFileToDestroyAtClosing = true;
8098 3 : poDS->osFilename = CPLGenerateTempFilenameSafe("netcdf_tmp");
8099 : }
8100 3 : if (!netCDFDatasetCreateTempFile(eTmpFormat, poDS->osFilename,
8101 : poOpenInfo->fpL))
8102 : {
8103 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8104 : // deadlock with GDALDataset own mutex.
8105 0 : delete poDS;
8106 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8107 0 : return nullptr;
8108 : }
8109 3 : bTreatAsSubdataset = false;
8110 3 : poDS->eFormat = eTmpFormat;
8111 : }
8112 : else
8113 : #endif
8114 :
8115 489 : if (STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
8116 : {
8117 : char **papszName =
8118 59 : CSLTokenizeString2(poOpenInfo->pszFilename, ":",
8119 : CSLT_HONOURSTRINGS | CSLT_PRESERVEESCAPES);
8120 :
8121 118 : if (CSLCount(papszName) >= 3 &&
8122 59 : ((strlen(papszName[1]) == 1 && /* D:\\bla */
8123 0 : (papszName[2][0] == '/' || papszName[2][0] == '\\')) ||
8124 59 : EQUAL(papszName[1], "http") || EQUAL(papszName[1], "https") ||
8125 59 : EQUAL(papszName[1], "/vsicurl/http") ||
8126 59 : EQUAL(papszName[1], "/vsicurl/https") ||
8127 59 : EQUAL(papszName[1], "/vsicurl_streaming/http") ||
8128 59 : EQUAL(papszName[1], "/vsicurl_streaming/https")))
8129 : {
8130 0 : const int nCountBefore = CSLCount(papszName);
8131 0 : CPLString osTmp = papszName[1];
8132 0 : osTmp += ':';
8133 0 : osTmp += papszName[2];
8134 0 : CPLFree(papszName[1]);
8135 0 : CPLFree(papszName[2]);
8136 0 : papszName[1] = CPLStrdup(osTmp);
8137 0 : memmove(papszName + 2, papszName + 3,
8138 0 : (nCountBefore - 2) * sizeof(char *));
8139 : }
8140 :
8141 59 : if (CSLCount(papszName) == 3)
8142 : {
8143 59 : poDS->osFilename = papszName[1];
8144 59 : osSubdatasetName = papszName[2];
8145 59 : bTreatAsSubdataset = true;
8146 59 : CSLDestroy(papszName);
8147 : }
8148 0 : else if (CSLCount(papszName) == 2)
8149 : {
8150 0 : poDS->osFilename = papszName[1];
8151 0 : osSubdatasetName = "";
8152 0 : bTreatAsSubdataset = false;
8153 0 : CSLDestroy(papszName);
8154 : }
8155 : else
8156 : {
8157 0 : CSLDestroy(papszName);
8158 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8159 : // deadlock with GDALDataset own mutex.
8160 0 : delete poDS;
8161 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8162 0 : CPLError(CE_Failure, CPLE_AppDefined,
8163 : "Failed to parse NETCDF: prefix string into expected 2, 3 "
8164 : "or 4 fields.");
8165 0 : return nullptr;
8166 : }
8167 :
8168 118 : if (!STARTS_WITH(poDS->osFilename, "http://") &&
8169 59 : !STARTS_WITH(poDS->osFilename, "https://"))
8170 : {
8171 : // Identify Format from real file, with bCheckExt=FALSE.
8172 : GDALOpenInfo *poOpenInfo2 =
8173 59 : new GDALOpenInfo(poDS->osFilename.c_str(), GA_ReadOnly);
8174 59 : poDS->eFormat =
8175 59 : netCDFIdentifyFormat(poOpenInfo2, /* bCheckExt = */ false);
8176 59 : delete poOpenInfo2;
8177 59 : if (NCDF_FORMAT_NONE == poDS->eFormat ||
8178 59 : NCDF_FORMAT_UNKNOWN == poDS->eFormat)
8179 : {
8180 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8181 : // deadlock with GDALDataset own mutex.
8182 0 : delete poDS;
8183 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8184 0 : return nullptr;
8185 : }
8186 : }
8187 : }
8188 : else
8189 : {
8190 430 : poDS->osFilename = poOpenInfo->pszFilename;
8191 430 : bTreatAsSubdataset = false;
8192 430 : poDS->eFormat = eTmpFormat;
8193 : }
8194 :
8195 : // Try opening the dataset.
8196 : #if defined(NCDF_DEBUG) && defined(ENABLE_UFFD)
8197 : CPLDebug("GDAL_netCDF", "calling nc_open_mem(%s)",
8198 : poDS->osFilename.c_str());
8199 : #elif defined(NCDF_DEBUG) && !defined(ENABLE_UFFD)
8200 : CPLDebug("GDAL_netCDF", "calling nc_open(%s)", poDS->osFilename.c_str());
8201 : #endif
8202 492 : int cdfid = -1;
8203 492 : const int nMode = ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE) != 0)
8204 : ? NC_WRITE
8205 : : NC_NOWRITE;
8206 984 : CPLString osFilenameForNCOpen(poDS->osFilename);
8207 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
8208 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
8209 : {
8210 : char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
8211 : osFilenameForNCOpen = pszTemp;
8212 : CPLFree(pszTemp);
8213 : }
8214 : #endif
8215 492 : int status2 = -1;
8216 :
8217 : #ifdef ENABLE_UFFD
8218 492 : cpl_uffd_context *pCtx = nullptr;
8219 : #endif
8220 :
8221 507 : if (STARTS_WITH(osFilenameForNCOpen, "/vsimem/") &&
8222 15 : poOpenInfo->eAccess == GA_ReadOnly)
8223 : {
8224 15 : vsi_l_offset nLength = 0;
8225 15 : poDS->fpVSIMEM = VSIFOpenL(osFilenameForNCOpen, "rb");
8226 15 : if (poDS->fpVSIMEM)
8227 : {
8228 : // We assume that the file will not be modified. If it is, then
8229 : // pabyBuffer might become invalid.
8230 : GByte *pabyBuffer =
8231 15 : VSIGetMemFileBuffer(osFilenameForNCOpen, &nLength, false);
8232 15 : if (pabyBuffer)
8233 : {
8234 15 : status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen),
8235 : nMode, static_cast<size_t>(nLength),
8236 : pabyBuffer, &cdfid);
8237 : }
8238 : }
8239 : }
8240 : else
8241 : {
8242 : const bool bVsiFile =
8243 477 : !strncmp(osFilenameForNCOpen, "/vsi", strlen("/vsi"));
8244 : #ifdef ENABLE_UFFD
8245 477 : bool bReadOnly = (poOpenInfo->eAccess == GA_ReadOnly);
8246 477 : void *pVma = nullptr;
8247 477 : uint64_t nVmaSize = 0;
8248 :
8249 477 : if (bVsiFile)
8250 : {
8251 2 : if (bReadOnly)
8252 : {
8253 2 : if (CPLIsUserFaultMappingSupported())
8254 : {
8255 2 : pCtx = CPLCreateUserFaultMapping(osFilenameForNCOpen, &pVma,
8256 : &nVmaSize);
8257 : }
8258 : else
8259 : {
8260 0 : CPLError(CE_Failure, CPLE_AppDefined,
8261 : "Opening a /vsi file with the netCDF driver "
8262 : "requires Linux userfaultfd to be available. "
8263 : "If running from Docker, "
8264 : "--security-opt seccomp=unconfined might be "
8265 : "needed.%s",
8266 0 : ((poDS->eFormat == NCDF_FORMAT_NC4 ||
8267 0 : poDS->eFormat == NCDF_FORMAT_HDF5) &&
8268 0 : GDALGetDriverByName("HDF5"))
8269 : ? " Or you may set the GDAL_SKIP=netCDF "
8270 : "configuration option to force the use of "
8271 : "the HDF5 driver."
8272 : : "");
8273 : }
8274 : }
8275 : else
8276 : {
8277 0 : CPLError(CE_Failure, CPLE_AppDefined,
8278 : "Opening a /vsi file with the netCDF driver is only "
8279 : "supported in read-only mode");
8280 : }
8281 : }
8282 477 : if (pCtx != nullptr && pVma != nullptr && nVmaSize > 0)
8283 : {
8284 : // netCDF code, at least for netCDF 4.7.0, is confused by filenames
8285 : // like /vsicurl/http[s]://example.com/foo.nc, so just pass the
8286 : // final part
8287 2 : status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen), nMode,
8288 : static_cast<size_t>(nVmaSize), pVma, &cdfid);
8289 : }
8290 : else
8291 475 : status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
8292 : #else
8293 : if (bVsiFile)
8294 : {
8295 : CPLError(
8296 : CE_Failure, CPLE_AppDefined,
8297 : "Opening a /vsi file with the netCDF driver requires Linux "
8298 : "userfaultfd to be available.%s",
8299 : ((poDS->eFormat == NCDF_FORMAT_NC4 ||
8300 : poDS->eFormat == NCDF_FORMAT_HDF5) &&
8301 : GDALGetDriverByName("HDF5"))
8302 : ? " Or you may set the GDAL_SKIP=netCDF "
8303 : "configuration option to force the use of the HDF5 "
8304 : "driver."
8305 : : "");
8306 : status2 = NC_EIO;
8307 : }
8308 : else
8309 : {
8310 : status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
8311 : }
8312 : #endif
8313 : }
8314 492 : if (status2 != NC_NOERR)
8315 : {
8316 : #ifdef NCDF_DEBUG
8317 : CPLDebug("GDAL_netCDF", "error opening");
8318 : #endif
8319 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8320 : // with GDALDataset own mutex.
8321 0 : delete poDS;
8322 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8323 0 : return nullptr;
8324 : }
8325 : #ifdef NCDF_DEBUG
8326 : CPLDebug("GDAL_netCDF", "got cdfid=%d", cdfid);
8327 : #endif
8328 :
8329 : #if defined(ENABLE_NCDUMP) && !defined(_WIN32)
8330 : // Try to destroy the temporary file right now on Unix
8331 492 : if (poDS->bFileToDestroyAtClosing)
8332 : {
8333 3 : if (VSIUnlink(poDS->osFilename) == 0)
8334 : {
8335 3 : poDS->bFileToDestroyAtClosing = false;
8336 : }
8337 : }
8338 : #endif
8339 :
8340 : // Is this a real netCDF file?
8341 : int ndims;
8342 : int ngatts;
8343 : int nvars;
8344 : int unlimdimid;
8345 492 : int status = nc_inq(cdfid, &ndims, &nvars, &ngatts, &unlimdimid);
8346 492 : if (status != NC_NOERR)
8347 : {
8348 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8349 : // with GDALDataset own mutex.
8350 0 : delete poDS;
8351 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8352 0 : return nullptr;
8353 : }
8354 :
8355 : // Get file type from netcdf.
8356 492 : int nTmpFormat = NCDF_FORMAT_NONE;
8357 492 : status = nc_inq_format(cdfid, &nTmpFormat);
8358 492 : if (status != NC_NOERR)
8359 : {
8360 0 : NCDF_ERR(status);
8361 : }
8362 : else
8363 : {
8364 492 : CPLDebug("GDAL_netCDF",
8365 : "driver detected file type=%d, libnetcdf detected type=%d",
8366 492 : poDS->eFormat, nTmpFormat);
8367 492 : if (static_cast<NetCDFFormatEnum>(nTmpFormat) != poDS->eFormat)
8368 : {
8369 : // Warn if file detection conflicts with that from libnetcdf
8370 : // except for NC4C, which we have no way of detecting initially.
8371 26 : if (nTmpFormat != NCDF_FORMAT_NC4C &&
8372 13 : !STARTS_WITH(poDS->osFilename, "http://") &&
8373 0 : !STARTS_WITH(poDS->osFilename, "https://"))
8374 : {
8375 0 : CPLError(CE_Warning, CPLE_AppDefined,
8376 : "NetCDF driver detected file type=%d, but libnetcdf "
8377 : "detected type=%d",
8378 0 : poDS->eFormat, nTmpFormat);
8379 : }
8380 13 : CPLDebug("GDAL_netCDF", "setting file type to %d, was %d",
8381 13 : nTmpFormat, poDS->eFormat);
8382 13 : poDS->eFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
8383 : }
8384 : }
8385 :
8386 : // Does the request variable exist?
8387 492 : if (bTreatAsSubdataset)
8388 : {
8389 : int dummy;
8390 59 : if (NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &dummy,
8391 59 : &dummy) != CE_None)
8392 : {
8393 0 : CPLError(CE_Warning, CPLE_AppDefined,
8394 : "%s is a netCDF file, but %s is not a variable.",
8395 : poOpenInfo->pszFilename, osSubdatasetName.c_str());
8396 :
8397 0 : GDAL_nc_close(cdfid);
8398 : #ifdef ENABLE_UFFD
8399 0 : NETCDF_UFFD_UNMAP(pCtx);
8400 : #endif
8401 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8402 : // deadlock with GDALDataset own mutex.
8403 0 : delete poDS;
8404 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8405 0 : return nullptr;
8406 : }
8407 : }
8408 :
8409 : // Figure out whether or not the listed dataset has support for simple
8410 : // geometries (CF-1.8)
8411 492 : poDS->nCFVersion = nccfdriver::getCFVersion(cdfid);
8412 492 : bool bHasSimpleGeometries = false; // but not necessarily valid
8413 492 : if (poDS->nCFVersion >= 1.8)
8414 : {
8415 75 : bHasSimpleGeometries = poDS->DetectAndFillSGLayers(cdfid);
8416 75 : if (bHasSimpleGeometries)
8417 : {
8418 67 : poDS->bSGSupport = true;
8419 67 : poDS->vcdf.enableFullVirtualMode();
8420 : }
8421 : }
8422 :
8423 : char szConventions[NC_MAX_NAME + 1];
8424 492 : szConventions[0] = '\0';
8425 492 : nc_type nAttype = NC_NAT;
8426 492 : size_t nAttlen = 0;
8427 492 : nc_inq_att(cdfid, NC_GLOBAL, "Conventions", &nAttype, &nAttlen);
8428 984 : if (nAttlen >= sizeof(szConventions) ||
8429 492 : nc_get_att_text(cdfid, NC_GLOBAL, "Conventions", szConventions) !=
8430 : NC_NOERR)
8431 : {
8432 56 : CPLDebug("GDAL_netCDF", "No UNIDATA NC_GLOBAL:Conventions attribute");
8433 : // Note that 'Conventions' is always capital 'C' in CF spec.
8434 : }
8435 : else
8436 : {
8437 436 : szConventions[nAttlen] = '\0';
8438 : }
8439 :
8440 : // Create band information objects.
8441 492 : CPLDebug("GDAL_netCDF", "var_count = %d", nvars);
8442 :
8443 : // Create a corresponding GDALDataset.
8444 : // Create Netcdf Subdataset if filename as NETCDF tag.
8445 492 : poDS->cdfid = cdfid;
8446 : #ifdef ENABLE_UFFD
8447 492 : poDS->pCtx = pCtx;
8448 : #endif
8449 492 : poDS->eAccess = poOpenInfo->eAccess;
8450 492 : poDS->bDefineMode = false;
8451 :
8452 492 : poDS->ReadAttributes(cdfid, NC_GLOBAL);
8453 :
8454 : // Identify coordinate and boundary variables that we should
8455 : // ignore as Raster Bands.
8456 492 : char **papszIgnoreVars = nullptr;
8457 492 : NCDFGetCoordAndBoundVarFullNames(cdfid, &papszIgnoreVars);
8458 : // Filter variables to keep only valid 2+D raster bands and vector fields.
8459 492 : int nRasterVars = 0;
8460 492 : int nIgnoredVars = 0;
8461 492 : int nGroupID = -1;
8462 492 : int nVarID = -1;
8463 :
8464 : std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>
8465 984 : oMap2DDimsToGroupAndVar;
8466 1136 : if ((poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
8467 152 : STARTS_WITH(CSLFetchNameValueDef(poDS->papszMetadata,
8468 : "NC_GLOBAL#mission_name", ""),
8469 1 : "Sentinel 3") &&
8470 1 : EQUAL(CSLFetchNameValueDef(poDS->papszMetadata,
8471 : "NC_GLOBAL#altimeter_sensor_name", ""),
8472 644 : "SRAL") &&
8473 1 : EQUAL(CSLFetchNameValueDef(poDS->papszMetadata,
8474 : "NC_GLOBAL#radiometer_sensor_name", ""),
8475 : "MWR"))
8476 : {
8477 1 : if (poDS->eAccess == GA_Update)
8478 : {
8479 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8480 : // deadlock with GDALDataset own mutex.
8481 0 : delete poDS;
8482 0 : return nullptr;
8483 : }
8484 1 : poDS->ProcessSentinel3_SRAL_MWR();
8485 : }
8486 : else
8487 : {
8488 491 : poDS->FilterVars(cdfid, (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0,
8489 642 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
8490 151 : !bHasSimpleGeometries,
8491 : papszIgnoreVars, &nRasterVars, &nGroupID, &nVarID,
8492 : &nIgnoredVars, oMap2DDimsToGroupAndVar);
8493 : }
8494 492 : CSLDestroy(papszIgnoreVars);
8495 :
8496 : // Case where there is no raster variable
8497 492 : if (nRasterVars == 0 && !bTreatAsSubdataset)
8498 : {
8499 119 : poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
8500 119 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8501 : // with GDALDataset own mutex.
8502 119 : poDS->TryLoadXML();
8503 : // If the dataset has been opened in raster mode only, exit
8504 119 : if ((poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0 &&
8505 9 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) == 0)
8506 : {
8507 4 : delete poDS;
8508 4 : poDS = nullptr;
8509 : }
8510 : // Otherwise if the dataset is opened in vector mode, that there is
8511 : // no vector layer and we are in read-only, exit too.
8512 115 : else if (poDS->GetLayerCount() == 0 &&
8513 123 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
8514 8 : poOpenInfo->eAccess == GA_ReadOnly)
8515 : {
8516 8 : delete poDS;
8517 8 : poDS = nullptr;
8518 : }
8519 119 : CPLAcquireMutex(hNCMutex, 1000.0);
8520 119 : return poDS;
8521 : }
8522 :
8523 : // We have more than one variable with 2 dimensions in the
8524 : // file, then treat this as a subdataset container dataset.
8525 373 : bool bSeveralVariablesAsBands = false;
8526 373 : const bool bListAllArrays = CPLTestBool(
8527 373 : CSLFetchNameValueDef(poDS->papszOpenOptions, "LIST_ALL_ARRAYS", "NO"));
8528 373 : if (bListAllArrays || ((nRasterVars > 1) && !bTreatAsSubdataset))
8529 : {
8530 28 : if (CPLFetchBool(poOpenInfo->papszOpenOptions, "VARIABLES_AS_BANDS",
8531 34 : false) &&
8532 6 : oMap2DDimsToGroupAndVar.size() == 1)
8533 : {
8534 6 : std::tie(nGroupID, nVarID) =
8535 12 : oMap2DDimsToGroupAndVar.begin()->second.front();
8536 6 : bSeveralVariablesAsBands = true;
8537 : }
8538 : else
8539 : {
8540 22 : poDS->CreateSubDatasetList(cdfid);
8541 22 : poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
8542 22 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8543 : // deadlock with GDALDataset own mutex.
8544 22 : poDS->TryLoadXML();
8545 22 : CPLAcquireMutex(hNCMutex, 1000.0);
8546 22 : return poDS;
8547 : }
8548 : }
8549 :
8550 : // If we are not treating things as a subdataset, then capture
8551 : // the name of the single available variable as the subdataset.
8552 351 : if (!bTreatAsSubdataset)
8553 : {
8554 292 : char *pszVarName = nullptr;
8555 292 : NCDF_ERR(NCDFGetVarFullName(nGroupID, nVarID, &pszVarName));
8556 292 : osSubdatasetName = (pszVarName != nullptr ? pszVarName : "");
8557 292 : CPLFree(pszVarName);
8558 : }
8559 :
8560 : // We have ignored at least one variable, so we should report them
8561 : // as subdatasets for reference.
8562 351 : if (nIgnoredVars > 0 && !bTreatAsSubdataset)
8563 : {
8564 24 : CPLDebug("GDAL_netCDF",
8565 : "As %d variables were ignored, creating subdataset list "
8566 : "for reference. Variable #%d [%s] is the main variable",
8567 : nIgnoredVars, nVarID, osSubdatasetName.c_str());
8568 24 : poDS->CreateSubDatasetList(cdfid);
8569 : }
8570 :
8571 : // Open the NETCDF subdataset NETCDF:"filename":subdataset.
8572 351 : int var = -1;
8573 351 : NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &nGroupID, &var);
8574 : // Now we can forget the root cdfid and only use the selected group.
8575 351 : cdfid = nGroupID;
8576 351 : int nd = 0;
8577 351 : nc_inq_varndims(cdfid, var, &nd);
8578 :
8579 351 : poDS->m_anDimIds.resize(nd);
8580 :
8581 : // X, Y, Z position in array
8582 702 : std::vector<int> anBandDimPos(nd);
8583 :
8584 351 : nc_inq_vardimid(cdfid, var, poDS->m_anDimIds.data());
8585 :
8586 : // Check if somebody tried to pass a variable with less than 1D.
8587 351 : if (nd < 1)
8588 : {
8589 0 : CPLError(CE_Warning, CPLE_AppDefined,
8590 : "Variable has %d dimension(s) - not supported.", nd);
8591 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8592 : // with GDALDataset own mutex.
8593 0 : delete poDS;
8594 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8595 0 : return nullptr;
8596 : }
8597 :
8598 : // CF-1 Convention
8599 : //
8600 : // Dimensions to appear in the relative order T, then Z, then Y,
8601 : // then X to the file. All other dimensions should, whenever
8602 : // possible, be placed to the left of the spatiotemporal
8603 : // dimensions.
8604 :
8605 : // Verify that dimensions are in the {T,Z,Y,X} or {T,Z,Y,X} order
8606 : // Ideally we should detect for other ordering and act accordingly
8607 : // Only done if file has Conventions=CF-* and only prints warning
8608 : // To disable set GDAL_NETCDF_VERIFY_DIMS=NO and to use only
8609 : // attributes (not varnames) set GDAL_NETCDF_VERIFY_DIMS=STRICT
8610 : const bool bCheckDims =
8611 702 : CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES")) &&
8612 351 : STARTS_WITH_CI(szConventions, "CF");
8613 :
8614 351 : if (nd >= 2 && bCheckDims)
8615 : {
8616 267 : char szDimName1[NC_MAX_NAME + 1] = {};
8617 267 : char szDimName2[NC_MAX_NAME + 1] = {};
8618 267 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 1], szDimName1);
8619 267 : NCDF_ERR(status);
8620 267 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 2], szDimName2);
8621 267 : NCDF_ERR(status);
8622 425 : if (NCDFIsVarLongitude(cdfid, -1, szDimName1) == false &&
8623 158 : NCDFIsVarProjectionX(cdfid, -1, szDimName1) == false)
8624 : {
8625 4 : CPLError(CE_Warning, CPLE_AppDefined,
8626 : "dimension #%d (%s) is not a Longitude/X dimension.",
8627 : nd - 1, szDimName1);
8628 : }
8629 425 : if (NCDFIsVarLatitude(cdfid, -1, szDimName2) == false &&
8630 158 : NCDFIsVarProjectionY(cdfid, -1, szDimName2) == false)
8631 : {
8632 4 : CPLError(CE_Warning, CPLE_AppDefined,
8633 : "dimension #%d (%s) is not a Latitude/Y dimension.",
8634 : nd - 2, szDimName2);
8635 : }
8636 267 : if ((NCDFIsVarLongitude(cdfid, -1, szDimName2) ||
8637 269 : NCDFIsVarProjectionX(cdfid, -1, szDimName2)) &&
8638 2 : (NCDFIsVarLatitude(cdfid, -1, szDimName1) ||
8639 0 : NCDFIsVarProjectionY(cdfid, -1, szDimName1)))
8640 : {
8641 2 : poDS->bSwitchedXY = true;
8642 : }
8643 267 : if (nd >= 3)
8644 : {
8645 52 : char szDimName3[NC_MAX_NAME + 1] = {};
8646 : status =
8647 52 : nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 3], szDimName3);
8648 52 : NCDF_ERR(status);
8649 52 : if (nd >= 4)
8650 : {
8651 13 : char szDimName4[NC_MAX_NAME + 1] = {};
8652 : status =
8653 13 : nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 4], szDimName4);
8654 13 : NCDF_ERR(status);
8655 13 : if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false)
8656 : {
8657 0 : CPLError(CE_Warning, CPLE_AppDefined,
8658 : "dimension #%d (%s) is not a Vertical dimension.",
8659 : nd - 3, szDimName3);
8660 : }
8661 13 : if (NCDFIsVarTimeCoord(cdfid, -1, szDimName4) == false)
8662 : {
8663 0 : CPLError(CE_Warning, CPLE_AppDefined,
8664 : "dimension #%d (%s) is not a Time dimension.",
8665 : nd - 4, szDimName4);
8666 : }
8667 : }
8668 : else
8669 : {
8670 75 : if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false &&
8671 36 : NCDFIsVarTimeCoord(cdfid, -1, szDimName3) == false)
8672 : {
8673 0 : CPLError(CE_Warning, CPLE_AppDefined,
8674 : "dimension #%d (%s) is not a "
8675 : "Time or Vertical dimension.",
8676 : nd - 3, szDimName3);
8677 : }
8678 : }
8679 : }
8680 : }
8681 :
8682 : // For example for EMIT data (https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/),
8683 : // dimension order is downtrack, crosstrack, bands
8684 351 : bool bYXBandOrder = false;
8685 351 : if (nd == 3)
8686 : {
8687 44 : char szDimName[NC_MAX_NAME + 1] = {};
8688 44 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[2], szDimName);
8689 44 : NCDF_ERR(status);
8690 44 : bYXBandOrder =
8691 44 : strcmp(szDimName, "bands") == 0 || strcmp(szDimName, "band") == 0;
8692 : }
8693 :
8694 : // Get X dimensions information.
8695 : size_t xdim;
8696 351 : poDS->nXDimID = poDS->m_anDimIds[bYXBandOrder ? 1 : nd - 1];
8697 351 : nc_inq_dimlen(cdfid, poDS->nXDimID, &xdim);
8698 :
8699 : // Get Y dimension information.
8700 : size_t ydim;
8701 351 : if (nd >= 2)
8702 : {
8703 347 : poDS->nYDimID = poDS->m_anDimIds[bYXBandOrder ? 0 : nd - 2];
8704 347 : nc_inq_dimlen(cdfid, poDS->nYDimID, &ydim);
8705 : }
8706 : else
8707 : {
8708 4 : poDS->nYDimID = -1;
8709 4 : ydim = 1;
8710 : }
8711 :
8712 351 : if (xdim > INT_MAX || ydim > INT_MAX)
8713 : {
8714 0 : CPLError(CE_Failure, CPLE_AppDefined,
8715 : "Invalid raster dimensions: " CPL_FRMT_GUIB "x" CPL_FRMT_GUIB,
8716 : static_cast<GUIntBig>(xdim), static_cast<GUIntBig>(ydim));
8717 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8718 : // with GDALDataset own mutex.
8719 0 : delete poDS;
8720 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8721 0 : return nullptr;
8722 : }
8723 :
8724 351 : poDS->nRasterXSize = static_cast<int>(xdim);
8725 351 : poDS->nRasterYSize = static_cast<int>(ydim);
8726 :
8727 351 : unsigned int k = 0;
8728 1128 : for (int j = 0; j < nd; j++)
8729 : {
8730 777 : if (poDS->m_anDimIds[j] == poDS->nXDimID)
8731 : {
8732 351 : anBandDimPos[0] = j; // Save Position of XDim
8733 351 : k++;
8734 : }
8735 777 : if (poDS->m_anDimIds[j] == poDS->nYDimID)
8736 : {
8737 347 : anBandDimPos[1] = j; // Save Position of YDim
8738 347 : k++;
8739 : }
8740 : }
8741 : // X and Y Dimension Ids were not found!
8742 351 : if ((nd >= 2 && k != 2) || (nd == 1 && k != 1))
8743 : {
8744 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8745 : // with GDALDataset own mutex.
8746 0 : delete poDS;
8747 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8748 0 : return nullptr;
8749 : }
8750 :
8751 : // Read Metadata for this variable.
8752 :
8753 : // Should disable as is also done at band level, except driver needs the
8754 : // variables as metadata (e.g. projection).
8755 351 : poDS->ReadAttributes(cdfid, var);
8756 :
8757 : // Read Metadata for each dimension.
8758 351 : int *panDimIds = nullptr;
8759 351 : NCDFGetVisibleDims(cdfid, &ndims, &panDimIds);
8760 : // With NetCDF-4 groups panDimIds is not always [0..dim_count-1] like
8761 : // in NetCDF-3 because we see only the dimensions of the selected group
8762 : // and its parents.
8763 : // poDS->papszDimName is indexed by dim IDs, so it must contains all IDs
8764 : // [0..max(panDimIds)], but they are not all useful so we fill names
8765 : // of useless dims with empty string.
8766 351 : if (panDimIds)
8767 : {
8768 351 : const int nMaxDimId = *std::max_element(panDimIds, panDimIds + ndims);
8769 351 : std::set<int> oSetExistingDimIds;
8770 1164 : for (int i = 0; i < ndims; i++)
8771 : {
8772 813 : oSetExistingDimIds.insert(panDimIds[i]);
8773 : }
8774 351 : std::set<int> oSetDimIdsUsedByVar;
8775 1128 : for (int i = 0; i < nd; i++)
8776 : {
8777 777 : oSetDimIdsUsedByVar.insert(poDS->m_anDimIds[i]);
8778 : }
8779 1166 : for (int j = 0; j <= nMaxDimId; j++)
8780 : {
8781 : // Is j dim used?
8782 815 : if (oSetExistingDimIds.find(j) != oSetExistingDimIds.end())
8783 : {
8784 : // Useful dim.
8785 813 : char szTemp[NC_MAX_NAME + 1] = {};
8786 813 : status = nc_inq_dimname(cdfid, j, szTemp);
8787 813 : if (status != NC_NOERR)
8788 : {
8789 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8790 : // deadlock with GDALDataset own
8791 : // mutex.
8792 0 : delete poDS;
8793 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8794 0 : return nullptr;
8795 : }
8796 813 : poDS->papszDimName.AddString(szTemp);
8797 :
8798 813 : if (oSetDimIdsUsedByVar.find(j) != oSetDimIdsUsedByVar.end())
8799 : {
8800 777 : int nDimGroupId = -1;
8801 777 : int nDimVarId = -1;
8802 777 : if (NCDFResolveVar(cdfid, poDS->papszDimName[j],
8803 777 : &nDimGroupId, &nDimVarId) == CE_None)
8804 : {
8805 577 : poDS->ReadAttributes(nDimGroupId, nDimVarId);
8806 : }
8807 : }
8808 : }
8809 : else
8810 : {
8811 : // Useless dim.
8812 2 : poDS->papszDimName.AddString("");
8813 : }
8814 : }
8815 351 : CPLFree(panDimIds);
8816 : }
8817 :
8818 : // Set projection info.
8819 702 : std::vector<std::string> aosRemovedMDItems;
8820 351 : if (nd > 1)
8821 : {
8822 347 : poDS->SetProjectionFromVar(cdfid, var,
8823 : /*bReadSRSOnly=*/false,
8824 : /* pszGivenGM = */ nullptr,
8825 : /* returnProjStr = */ nullptr,
8826 : /* sg = */ nullptr, &aosRemovedMDItems);
8827 : }
8828 :
8829 : // Override bottom-up with GDAL_NETCDF_BOTTOMUP config option.
8830 351 : const char *pszValue = CPLGetConfigOption("GDAL_NETCDF_BOTTOMUP", nullptr);
8831 351 : if (pszValue)
8832 : {
8833 24 : poDS->bBottomUp = CPLTestBool(pszValue);
8834 24 : CPLDebug("GDAL_netCDF",
8835 : "set bBottomUp=%d because GDAL_NETCDF_BOTTOMUP=%s",
8836 24 : static_cast<int>(poDS->bBottomUp), pszValue);
8837 : }
8838 :
8839 : // Save non-spatial dimension info.
8840 :
8841 351 : int *panBandZLev = nullptr;
8842 351 : int nDim = (nd >= 2) ? 2 : 1;
8843 : size_t lev_count;
8844 351 : size_t nTotLevCount = 1;
8845 351 : nc_type nType = NC_NAT;
8846 :
8847 702 : CPLString osExtraDimNames;
8848 :
8849 351 : if (nd > 2)
8850 : {
8851 60 : nDim = 2;
8852 60 : panBandZLev = static_cast<int *>(CPLCalloc(nd - 2, sizeof(int)));
8853 :
8854 60 : osExtraDimNames = "{";
8855 :
8856 60 : char szDimName[NC_MAX_NAME + 1] = {};
8857 :
8858 60 : bool bREPORT_EXTRA_DIM_VALUESWarningEmitted = false;
8859 259 : for (int j = 0; j < nd; j++)
8860 : {
8861 338 : if ((poDS->m_anDimIds[j] != poDS->nXDimID) &&
8862 139 : (poDS->m_anDimIds[j] != poDS->nYDimID))
8863 : {
8864 79 : nc_inq_dimlen(cdfid, poDS->m_anDimIds[j], &lev_count);
8865 79 : nTotLevCount *= lev_count;
8866 79 : panBandZLev[nDim - 2] = static_cast<int>(lev_count);
8867 79 : anBandDimPos[nDim] = j; // Save Position of ZDim
8868 : // Save non-spatial dimension names.
8869 79 : if (nc_inq_dimname(cdfid, poDS->m_anDimIds[j], szDimName) ==
8870 : NC_NOERR)
8871 : {
8872 79 : osExtraDimNames += szDimName;
8873 79 : if (j < nd - 3)
8874 : {
8875 19 : osExtraDimNames += ",";
8876 : }
8877 :
8878 79 : int nIdxGroupID = -1;
8879 79 : int nIdxVarID = Get1DVariableIndexedByDimension(
8880 79 : cdfid, poDS->m_anDimIds[j], szDimName, true,
8881 79 : &nIdxGroupID);
8882 79 : poDS->m_anExtraDimGroupIds.push_back(nIdxGroupID);
8883 79 : poDS->m_anExtraDimVarIds.push_back(nIdxVarID);
8884 :
8885 79 : if (nIdxVarID >= 0)
8886 : {
8887 70 : nc_inq_vartype(nIdxGroupID, nIdxVarID, &nType);
8888 : char szExtraDimDef[NC_MAX_NAME + 1];
8889 70 : snprintf(szExtraDimDef, sizeof(szExtraDimDef),
8890 : "{%ld,%d}", (long)lev_count, nType);
8891 : char szTemp[NC_MAX_NAME + 32 + 1];
8892 70 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
8893 : szDimName);
8894 70 : poDS->papszMetadata = CSLSetNameValue(
8895 : poDS->papszMetadata, szTemp, szExtraDimDef);
8896 :
8897 : // Retrieving data for unlimited dimensions might be
8898 : // costly on network storage, so don't do it.
8899 : // Each band will capture the value along the extra
8900 : // dimension in its NETCDF_DIM_xxxx band metadata item
8901 : // Addresses use case of
8902 : // https://lists.osgeo.org/pipermail/gdal-dev/2023-May/057209.html
8903 : const bool bIsLocal =
8904 70 : VSIIsLocal(osFilenameForNCOpen.c_str());
8905 : bool bListDimValues =
8906 71 : bIsLocal || lev_count == 1 ||
8907 1 : !NCDFIsUnlimitedDim(poDS->eFormat ==
8908 : NCDF_FORMAT_NC4,
8909 1 : cdfid, poDS->m_anDimIds[j]);
8910 : const char *pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES =
8911 70 : CPLGetConfigOption(
8912 : "GDAL_NETCDF_REPORT_EXTRA_DIM_VALUES", nullptr);
8913 70 : if (pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES)
8914 : {
8915 2 : bListDimValues = CPLTestBool(
8916 : pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES);
8917 : }
8918 68 : else if (!bListDimValues && !bIsLocal &&
8919 1 : !bREPORT_EXTRA_DIM_VALUESWarningEmitted)
8920 : {
8921 1 : bREPORT_EXTRA_DIM_VALUESWarningEmitted = true;
8922 1 : CPLDebug(
8923 : "GDAL_netCDF",
8924 : "Listing extra dimension values is skipped "
8925 : "because this dataset is hosted on a network "
8926 : "file system, and such an operation could be "
8927 : "slow. If you still want to proceed, set the "
8928 : "GDAL_NETCDF_REPORT_EXTRA_DIM_VALUES "
8929 : "configuration option to YES");
8930 : }
8931 70 : if (bListDimValues)
8932 : {
8933 68 : char *pszTemp = nullptr;
8934 68 : if (NCDFGet1DVar(nIdxGroupID, nIdxVarID,
8935 68 : &pszTemp) == CE_None)
8936 : {
8937 68 : snprintf(szTemp, sizeof(szTemp),
8938 : "NETCDF_DIM_%s_VALUES", szDimName);
8939 68 : poDS->papszMetadata = CSLSetNameValue(
8940 : poDS->papszMetadata, szTemp, pszTemp);
8941 68 : CPLFree(pszTemp);
8942 : }
8943 : }
8944 : }
8945 : }
8946 : else
8947 : {
8948 0 : poDS->m_anExtraDimGroupIds.push_back(-1);
8949 0 : poDS->m_anExtraDimVarIds.push_back(-1);
8950 : }
8951 :
8952 79 : nDim++;
8953 : }
8954 : }
8955 60 : osExtraDimNames += "}";
8956 60 : poDS->papszMetadata = CSLSetNameValue(
8957 : poDS->papszMetadata, "NETCDF_DIM_EXTRA", osExtraDimNames);
8958 : }
8959 :
8960 : // Store Metadata.
8961 361 : for (const auto &osStr : aosRemovedMDItems)
8962 10 : poDS->papszMetadata =
8963 10 : CSLSetNameValue(poDS->papszMetadata, osStr.c_str(), nullptr);
8964 :
8965 351 : poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
8966 :
8967 : // Create bands.
8968 :
8969 : // Arbitrary threshold.
8970 : int nMaxBandCount =
8971 351 : atoi(CPLGetConfigOption("GDAL_MAX_BAND_COUNT", "32768"));
8972 351 : if (nMaxBandCount <= 0)
8973 0 : nMaxBandCount = 32768;
8974 351 : if (nTotLevCount > static_cast<unsigned int>(nMaxBandCount))
8975 : {
8976 0 : CPLError(CE_Warning, CPLE_AppDefined,
8977 : "Limiting number of bands to %d instead of %u", nMaxBandCount,
8978 : static_cast<unsigned int>(nTotLevCount));
8979 0 : nTotLevCount = static_cast<unsigned int>(nMaxBandCount);
8980 : }
8981 351 : if (poDS->nRasterXSize == 0 || poDS->nRasterYSize == 0)
8982 : {
8983 0 : poDS->nRasterXSize = 0;
8984 0 : poDS->nRasterYSize = 0;
8985 0 : nTotLevCount = 0;
8986 0 : if (poDS->GetLayerCount() == 0)
8987 : {
8988 0 : CPLFree(panBandZLev);
8989 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8990 : // deadlock with GDALDataset own mutex.
8991 0 : delete poDS;
8992 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8993 0 : return nullptr;
8994 : }
8995 : }
8996 351 : if (bSeveralVariablesAsBands)
8997 : {
8998 6 : const auto &listVariables = oMap2DDimsToGroupAndVar.begin()->second;
8999 24 : for (int iBand = 0; iBand < static_cast<int>(listVariables.size());
9000 : ++iBand)
9001 : {
9002 18 : int bandVarGroupId = listVariables[iBand].first;
9003 18 : int bandVarId = listVariables[iBand].second;
9004 : netCDFRasterBand *poBand = new netCDFRasterBand(
9005 0 : netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, bandVarGroupId,
9006 18 : bandVarId, nDim, 0, nullptr, anBandDimPos.data(), iBand + 1);
9007 18 : poDS->SetBand(iBand + 1, poBand);
9008 : }
9009 : }
9010 : else
9011 : {
9012 798 : for (unsigned int lev = 0; lev < nTotLevCount; lev++)
9013 : {
9014 : netCDFRasterBand *poBand = new netCDFRasterBand(
9015 0 : netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, cdfid, var, nDim,
9016 453 : lev, panBandZLev, anBandDimPos.data(), lev + 1);
9017 453 : poDS->SetBand(lev + 1, poBand);
9018 : }
9019 : }
9020 :
9021 351 : if (panBandZLev)
9022 60 : CPLFree(panBandZLev);
9023 : // Handle angular geographic coordinates here
9024 :
9025 : // Initialize any PAM information.
9026 351 : if (bTreatAsSubdataset)
9027 : {
9028 59 : poDS->SetPhysicalFilename(poDS->osFilename);
9029 59 : poDS->SetSubdatasetName(osSubdatasetName);
9030 : }
9031 :
9032 351 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
9033 : // GDALDataset own mutex.
9034 351 : poDS->TryLoadXML();
9035 :
9036 351 : if (bTreatAsSubdataset)
9037 59 : poDS->oOvManager.Initialize(poDS, ":::VIRTUAL:::");
9038 : else
9039 292 : poDS->oOvManager.Initialize(poDS, poDS->osFilename);
9040 :
9041 351 : CPLAcquireMutex(hNCMutex, 1000.0);
9042 :
9043 351 : return poDS;
9044 : }
9045 :
9046 : /************************************************************************/
9047 : /* CopyMetadata() */
9048 : /* */
9049 : /* Create a copy of metadata for NC_GLOBAL or a variable */
9050 : /************************************************************************/
9051 :
9052 157 : static void CopyMetadata(GDALDataset *poSrcDS, GDALRasterBand *poSrcBand,
9053 : GDALRasterBand *poDstBand, int nCdfId, int CDFVarID,
9054 : const char *pszPrefix)
9055 : {
9056 : // Remove the following band meta but set them later from band data.
9057 157 : const char *const papszIgnoreBand[] = {
9058 : CF_ADD_OFFSET, CF_SCALE_FACTOR, "valid_range", "_Unsigned",
9059 : NCDF_FillValue, "coordinates", nullptr};
9060 157 : const char *const papszIgnoreGlobal[] = {"NETCDF_DIM_EXTRA", nullptr};
9061 :
9062 157 : CSLConstList papszMetadata = nullptr;
9063 157 : if (poSrcDS)
9064 : {
9065 66 : papszMetadata = poSrcDS->GetMetadata();
9066 : }
9067 91 : else if (poSrcBand)
9068 : {
9069 91 : papszMetadata = poSrcBand->GetMetadata();
9070 : }
9071 :
9072 637 : for (const auto &[pszKey, pszValue] : cpl::IterateNameValue(papszMetadata))
9073 : {
9074 : #ifdef NCDF_DEBUG
9075 : CPLDebug("GDAL_netCDF", "copy metadata [%s]=[%s]", pszKey, pszValue);
9076 : #endif
9077 :
9078 480 : CPLString osMetaName(pszKey);
9079 :
9080 : // Check for items that match pszPrefix if applicable.
9081 480 : if (pszPrefix && !EQUAL(pszPrefix, ""))
9082 : {
9083 : // Remove prefix.
9084 115 : if (STARTS_WITH(osMetaName.c_str(), pszPrefix))
9085 : {
9086 17 : osMetaName = osMetaName.substr(strlen(pszPrefix));
9087 : }
9088 : // Only copy items that match prefix.
9089 : else
9090 : {
9091 98 : continue;
9092 : }
9093 : }
9094 :
9095 : // Fix various issues with metadata translation.
9096 382 : if (CDFVarID == NC_GLOBAL)
9097 : {
9098 : // Do not copy items in papszIgnoreGlobal and NETCDF_DIM_*.
9099 481 : if ((CSLFindString(papszIgnoreGlobal, osMetaName) != -1) ||
9100 238 : (STARTS_WITH(osMetaName, "NETCDF_DIM_")))
9101 21 : continue;
9102 : // Remove NC_GLOBAL prefix for netcdf global Metadata.
9103 222 : else if (STARTS_WITH(osMetaName, "NC_GLOBAL#"))
9104 : {
9105 33 : osMetaName = osMetaName.substr(strlen("NC_GLOBAL#"));
9106 : }
9107 : // GDAL Metadata renamed as GDAL-[meta].
9108 189 : else if (strstr(osMetaName, "#") == nullptr)
9109 : {
9110 16 : osMetaName = "GDAL_" + osMetaName;
9111 : }
9112 : // Keep time, lev and depth information for safe-keeping.
9113 : // Time and vertical coordinate handling need improvements.
9114 : /*
9115 : else if( STARTS_WITH(szMetaName, "time#") )
9116 : {
9117 : szMetaName[4] = '-';
9118 : }
9119 : else if( STARTS_WITH(szMetaName, "lev#") )
9120 : {
9121 : szMetaName[3] = '-';
9122 : }
9123 : else if( STARTS_WITH(szMetaName, "depth#") )
9124 : {
9125 : szMetaName[5] = '-';
9126 : }
9127 : */
9128 : // Only copy data without # (previously all data was copied).
9129 222 : if (strstr(osMetaName, "#") != nullptr)
9130 173 : continue;
9131 : // netCDF attributes do not like the '#' character.
9132 : // for( unsigned int h=0; h < strlen(szMetaName) -1 ; h++ ) {
9133 : // if( szMetaName[h] == '#') szMetaName[h] = '-';
9134 : // }
9135 : }
9136 : else
9137 : {
9138 : // Do not copy varname, stats, NETCDF_DIM_*, nodata
9139 : // and items in papszIgnoreBand.
9140 139 : if (STARTS_WITH(osMetaName, "NETCDF_VARNAME") ||
9141 107 : STARTS_WITH(osMetaName, "STATISTICS_") ||
9142 107 : STARTS_WITH(osMetaName, "NETCDF_DIM_") ||
9143 74 : STARTS_WITH(osMetaName, "missing_value") ||
9144 293 : STARTS_WITH(osMetaName, "_FillValue") ||
9145 47 : CSLFindString(papszIgnoreBand, osMetaName) != -1)
9146 97 : continue;
9147 : }
9148 :
9149 : #ifdef NCDF_DEBUG
9150 : CPLDebug("GDAL_netCDF", "copy name=[%s] value=[%s]", osMetaName.c_str(),
9151 : pszValue);
9152 : #endif
9153 91 : if (NCDFPutAttr(nCdfId, CDFVarID, osMetaName, pszValue) != CE_None)
9154 : {
9155 0 : CPLDebug("GDAL_netCDF", "NCDFPutAttr(%d, %d, %s, %s) failed",
9156 : nCdfId, CDFVarID, osMetaName.c_str(), pszValue);
9157 : }
9158 : }
9159 :
9160 : // Set add_offset and scale_factor here if present.
9161 157 : if (poSrcBand && poDstBand)
9162 : {
9163 :
9164 91 : int bGotAddOffset = FALSE;
9165 91 : const double dfAddOffset = poSrcBand->GetOffset(&bGotAddOffset);
9166 91 : int bGotScale = FALSE;
9167 91 : const double dfScale = poSrcBand->GetScale(&bGotScale);
9168 :
9169 91 : if (bGotAddOffset && dfAddOffset != 0.0)
9170 1 : poDstBand->SetOffset(dfAddOffset);
9171 91 : if (bGotScale && dfScale != 1.0)
9172 1 : poDstBand->SetScale(dfScale);
9173 : }
9174 157 : }
9175 :
9176 : /************************************************************************/
9177 : /* CreateLL() */
9178 : /* */
9179 : /* Shared functionality between netCDFDataset::Create() and */
9180 : /* netCDF::CreateCopy() for creating netcdf file based on a set of */
9181 : /* options and a configuration. */
9182 : /************************************************************************/
9183 :
9184 198 : netCDFDataset *netCDFDataset::CreateLL(const char *pszFilename, int nXSize,
9185 : int nYSize, int nBandsIn,
9186 : char **papszOptions)
9187 : {
9188 198 : if (!((nXSize == 0 && nYSize == 0 && nBandsIn == 0) ||
9189 126 : (nXSize > 0 && nYSize > 0 && nBandsIn > 0)))
9190 : {
9191 1 : return nullptr;
9192 : }
9193 :
9194 197 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
9195 : // GDALDataset own mutex.
9196 197 : netCDFDataset *poDS = new netCDFDataset();
9197 197 : CPLAcquireMutex(hNCMutex, 1000.0);
9198 :
9199 197 : poDS->nRasterXSize = nXSize;
9200 197 : poDS->nRasterYSize = nYSize;
9201 197 : poDS->eAccess = GA_Update;
9202 197 : poDS->osFilename = pszFilename;
9203 :
9204 : // From gtiff driver, is this ok?
9205 : /*
9206 : poDS->nBlockXSize = nXSize;
9207 : poDS->nBlockYSize = 1;
9208 : poDS->nBlocksPerBand =
9209 : ((nYSize + poDS->nBlockYSize - 1) / poDS->nBlockYSize)
9210 : * ((nXSize + poDS->nBlockXSize - 1) / poDS->nBlockXSize);
9211 : */
9212 :
9213 : // process options.
9214 197 : poDS->papszCreationOptions = CSLDuplicate(papszOptions);
9215 197 : poDS->ProcessCreationOptions();
9216 :
9217 197 : if (poDS->eMultipleLayerBehavior == SEPARATE_FILES)
9218 : {
9219 : VSIStatBuf sStat;
9220 2 : if (VSIStat(pszFilename, &sStat) == 0)
9221 : {
9222 0 : if (!VSI_ISDIR(sStat.st_mode))
9223 : {
9224 0 : CPLError(CE_Failure, CPLE_FileIO,
9225 : "%s is an existing file, but not a directory",
9226 : pszFilename);
9227 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9228 : // deadlock with GDALDataset own
9229 : // mutex.
9230 0 : delete poDS;
9231 0 : CPLAcquireMutex(hNCMutex, 1000.0);
9232 0 : return nullptr;
9233 : }
9234 : }
9235 2 : else if (VSIMkdir(pszFilename, 0755) != 0)
9236 : {
9237 1 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s directory",
9238 : pszFilename);
9239 1 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9240 : // deadlock with GDALDataset own mutex.
9241 1 : delete poDS;
9242 1 : CPLAcquireMutex(hNCMutex, 1000.0);
9243 1 : return nullptr;
9244 : }
9245 :
9246 1 : return poDS;
9247 : }
9248 : // Create the dataset.
9249 390 : CPLString osFilenameForNCCreate(pszFilename);
9250 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
9251 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
9252 : {
9253 : char *pszTemp =
9254 : CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
9255 : osFilenameForNCCreate = pszTemp;
9256 : CPLFree(pszTemp);
9257 : }
9258 : #endif
9259 :
9260 : #if defined(_WIN32)
9261 : {
9262 : // Works around bug of msys2 netCDF 4.9.0 package where nc_create()
9263 : // crashes
9264 : VSIStatBuf sStat;
9265 : const std::string osDirname =
9266 : CPLGetDirnameSafe(osFilenameForNCCreate.c_str());
9267 : if (VSIStat(osDirname.c_str(), &sStat) != 0)
9268 : {
9269 : CPLError(CE_Failure, CPLE_OpenFailed,
9270 : "Unable to create netCDF file %s: non existing output "
9271 : "directory",
9272 : pszFilename);
9273 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9274 : // deadlock with GDALDataset own mutex.
9275 : delete poDS;
9276 : CPLAcquireMutex(hNCMutex, 1000.0);
9277 : return nullptr;
9278 : }
9279 : }
9280 : #endif
9281 :
9282 : int status =
9283 195 : nc_create(osFilenameForNCCreate, poDS->nCreateMode, &(poDS->cdfid));
9284 :
9285 : // Put into define mode.
9286 195 : poDS->SetDefineMode(true);
9287 :
9288 195 : if (status != NC_NOERR)
9289 : {
9290 30 : CPLError(CE_Failure, CPLE_OpenFailed,
9291 : "Unable to create netCDF file %s (Error code %d): %s .",
9292 : pszFilename, status, nc_strerror(status));
9293 30 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
9294 : // with GDALDataset own mutex.
9295 30 : delete poDS;
9296 30 : CPLAcquireMutex(hNCMutex, 1000.0);
9297 30 : return nullptr;
9298 : }
9299 :
9300 : // Define dimensions.
9301 165 : if (nXSize > 0 && nYSize > 0)
9302 : {
9303 112 : poDS->papszDimName.AddString(NCDF_DIMNAME_X);
9304 : status =
9305 112 : nc_def_dim(poDS->cdfid, NCDF_DIMNAME_X, nXSize, &(poDS->nXDimID));
9306 112 : NCDF_ERR(status);
9307 112 : CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
9308 : poDS->cdfid, NCDF_DIMNAME_X, nXSize, poDS->nXDimID);
9309 :
9310 112 : poDS->papszDimName.AddString(NCDF_DIMNAME_Y);
9311 : status =
9312 112 : nc_def_dim(poDS->cdfid, NCDF_DIMNAME_Y, nYSize, &(poDS->nYDimID));
9313 112 : NCDF_ERR(status);
9314 112 : CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
9315 : poDS->cdfid, NCDF_DIMNAME_Y, nYSize, poDS->nYDimID);
9316 : }
9317 :
9318 165 : return poDS;
9319 : }
9320 :
9321 : /************************************************************************/
9322 : /* Create() */
9323 : /************************************************************************/
9324 :
9325 126 : GDALDataset *netCDFDataset::Create(const char *pszFilename, int nXSize,
9326 : int nYSize, int nBandsIn, GDALDataType eType,
9327 : char **papszOptions)
9328 : {
9329 126 : CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::Create(%s, ...)",
9330 : pszFilename);
9331 :
9332 : const char *legacyCreationOp =
9333 126 : CSLFetchNameValueDef(papszOptions, "GEOMETRY_ENCODING", "CF_1.8");
9334 252 : std::string legacyCreationOp_s = std::string(legacyCreationOp);
9335 :
9336 : // Check legacy creation op FIRST
9337 :
9338 126 : bool legacyCreateMode = false;
9339 :
9340 126 : if (nXSize != 0 || nYSize != 0 || nBandsIn != 0)
9341 : {
9342 56 : legacyCreateMode = true;
9343 : }
9344 70 : else if (legacyCreationOp_s == "CF_1.8")
9345 : {
9346 54 : legacyCreateMode = false;
9347 : }
9348 :
9349 16 : else if (legacyCreationOp_s == "WKT")
9350 : {
9351 16 : legacyCreateMode = true;
9352 : }
9353 :
9354 : else
9355 : {
9356 0 : CPLError(
9357 : CE_Failure, CPLE_NotSupported,
9358 : "Dataset creation option GEOMETRY_ENCODING=%s is not supported.",
9359 : legacyCreationOp_s.c_str());
9360 0 : return nullptr;
9361 : }
9362 :
9363 252 : CPLStringList aosOptions(CSLDuplicate(papszOptions));
9364 238 : if (aosOptions.FetchNameValue("FORMAT") == nullptr &&
9365 112 : (eType == GDT_UInt16 || eType == GDT_UInt32 || eType == GDT_UInt64 ||
9366 : eType == GDT_Int64))
9367 : {
9368 10 : CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
9369 10 : aosOptions.SetNameValue("FORMAT", "NC4");
9370 : }
9371 :
9372 252 : CPLStringList aosBandNames;
9373 126 : if (const char *pszBandNames = aosOptions.FetchNameValue("BAND_NAMES"))
9374 : {
9375 : aosBandNames =
9376 2 : CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);
9377 :
9378 2 : if (aosBandNames.Count() != nBandsIn)
9379 : {
9380 1 : CPLError(CE_Failure, CPLE_OpenFailed,
9381 : "Attempted to create netCDF with %d bands but %d names "
9382 : "provided in BAND_NAMES.",
9383 : nBandsIn, aosBandNames.Count());
9384 :
9385 1 : return nullptr;
9386 : }
9387 : }
9388 :
9389 250 : CPLMutexHolderD(&hNCMutex);
9390 :
9391 125 : auto poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize, nBandsIn,
9392 : aosOptions.List());
9393 :
9394 125 : if (!poDS)
9395 19 : return nullptr;
9396 :
9397 106 : if (!legacyCreateMode)
9398 : {
9399 37 : poDS->bSGSupport = true;
9400 37 : poDS->vcdf.enableFullVirtualMode();
9401 : }
9402 :
9403 : else
9404 : {
9405 69 : poDS->bSGSupport = false;
9406 : }
9407 :
9408 : // Should we write signed or unsigned byte?
9409 : // TODO should this only be done in Create()
9410 106 : poDS->bSignedData = true;
9411 106 : const char *pszValue = CSLFetchNameValueDef(papszOptions, "PIXELTYPE", "");
9412 106 : if (eType == GDT_Byte && !EQUAL(pszValue, "SIGNEDBYTE"))
9413 15 : poDS->bSignedData = false;
9414 :
9415 : // Add Conventions, GDAL info and history.
9416 106 : if (poDS->cdfid >= 0)
9417 : {
9418 : const char *CF_Vector_Conv =
9419 173 : poDS->bSGSupport ||
9420 : // Use of variable length strings require CF-1.8
9421 68 : EQUAL(aosOptions.FetchNameValueDef("FORMAT", ""), "NC4")
9422 : ? NCDF_CONVENTIONS_CF_V1_8
9423 173 : : NCDF_CONVENTIONS_CF_V1_6;
9424 105 : poDS->bWriteGDALVersion = CPLTestBool(
9425 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
9426 105 : poDS->bWriteGDALHistory = CPLTestBool(
9427 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
9428 105 : NCDFAddGDALHistory(poDS->cdfid, pszFilename, poDS->bWriteGDALVersion,
9429 105 : poDS->bWriteGDALHistory, "", "Create",
9430 : (nBandsIn == 0) ? CF_Vector_Conv
9431 : : GDAL_DEFAULT_NCDF_CONVENTIONS);
9432 : }
9433 :
9434 : // Define bands.
9435 197 : for (int iBand = 1; iBand <= nBandsIn; iBand++)
9436 : {
9437 : const char *pszBandName =
9438 91 : aosBandNames.empty() ? nullptr : aosBandNames[iBand - 1];
9439 :
9440 91 : poDS->SetBand(iBand, new netCDFRasterBand(
9441 91 : netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS,
9442 91 : eType, iBand, poDS->bSignedData, pszBandName));
9443 : }
9444 :
9445 106 : CPLDebug("GDAL_netCDF", "netCDFDataset::Create(%s, ...) done", pszFilename);
9446 : // Return same dataset.
9447 106 : return poDS;
9448 : }
9449 :
9450 : template <class T>
9451 91 : static CPLErr NCDFCopyBand(GDALRasterBand *poSrcBand, GDALRasterBand *poDstBand,
9452 : int nXSize, int nYSize, GDALProgressFunc pfnProgress,
9453 : void *pProgressData)
9454 : {
9455 91 : GDALDataType eDT = poSrcBand->GetRasterDataType();
9456 91 : CPLErr eErr = CE_None;
9457 91 : T *patScanline = static_cast<T *>(CPLMalloc(nXSize * sizeof(T)));
9458 :
9459 6308 : for (int iLine = 0; iLine < nYSize && eErr == CE_None; iLine++)
9460 : {
9461 6217 : eErr = poSrcBand->RasterIO(GF_Read, 0, iLine, nXSize, 1, patScanline,
9462 : nXSize, 1, eDT, 0, 0, nullptr);
9463 6217 : if (eErr != CE_None)
9464 : {
9465 0 : CPLDebug(
9466 : "GDAL_netCDF",
9467 : "NCDFCopyBand(), poSrcBand->RasterIO() returned error code %d",
9468 : eErr);
9469 : }
9470 : else
9471 : {
9472 6217 : eErr =
9473 : poDstBand->RasterIO(GF_Write, 0, iLine, nXSize, 1, patScanline,
9474 : nXSize, 1, eDT, 0, 0, nullptr);
9475 6217 : if (eErr != CE_None)
9476 0 : CPLDebug("GDAL_netCDF",
9477 : "NCDFCopyBand(), poDstBand->RasterIO() returned error "
9478 : "code %d",
9479 : eErr);
9480 : }
9481 :
9482 6217 : if (nYSize > 10 && (iLine % (nYSize / 10) == 1))
9483 : {
9484 277 : if (!pfnProgress(1.0 * iLine / nYSize, nullptr, pProgressData))
9485 : {
9486 0 : eErr = CE_Failure;
9487 0 : CPLError(CE_Failure, CPLE_UserInterrupt,
9488 : "User terminated CreateCopy()");
9489 : }
9490 : }
9491 : }
9492 :
9493 91 : CPLFree(patScanline);
9494 :
9495 91 : pfnProgress(1.0, nullptr, pProgressData);
9496 :
9497 91 : return eErr;
9498 : }
9499 :
9500 : /************************************************************************/
9501 : /* CreateCopy() */
9502 : /************************************************************************/
9503 :
9504 : GDALDataset *
9505 82 : netCDFDataset::CreateCopy(const char *pszFilename, GDALDataset *poSrcDS,
9506 : CPL_UNUSED int bStrict, char **papszOptions,
9507 : GDALProgressFunc pfnProgress, void *pProgressData)
9508 : {
9509 164 : CPLMutexHolderD(&hNCMutex);
9510 :
9511 82 : CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::CreateCopy(%s, ...)",
9512 : pszFilename);
9513 :
9514 82 : if (poSrcDS->GetRootGroup())
9515 : {
9516 5 : auto poDrv = GDALDriver::FromHandle(GDALGetDriverByName("netCDF"));
9517 5 : if (poDrv)
9518 : {
9519 5 : return poDrv->DefaultCreateCopy(pszFilename, poSrcDS, bStrict,
9520 : papszOptions, pfnProgress,
9521 5 : pProgressData);
9522 : }
9523 : }
9524 :
9525 77 : const int nBands = poSrcDS->GetRasterCount();
9526 77 : const int nXSize = poSrcDS->GetRasterXSize();
9527 77 : const int nYSize = poSrcDS->GetRasterYSize();
9528 77 : const char *pszWKT = poSrcDS->GetProjectionRef();
9529 :
9530 : // Check input bands for errors.
9531 77 : if (nBands == 0)
9532 : {
9533 1 : CPLError(CE_Failure, CPLE_NotSupported,
9534 : "NetCDF driver does not support "
9535 : "source dataset with zero band.");
9536 1 : return nullptr;
9537 : }
9538 :
9539 76 : GDALDataType eDT = GDT_Unknown;
9540 76 : GDALRasterBand *poSrcBand = nullptr;
9541 181 : for (int iBand = 1; iBand <= nBands; iBand++)
9542 : {
9543 109 : poSrcBand = poSrcDS->GetRasterBand(iBand);
9544 109 : eDT = poSrcBand->GetRasterDataType();
9545 109 : if (eDT == GDT_Unknown || GDALDataTypeIsComplex(eDT))
9546 : {
9547 4 : CPLError(CE_Failure, CPLE_NotSupported,
9548 : "NetCDF driver does not support source dataset with band "
9549 : "of complex type.");
9550 4 : return nullptr;
9551 : }
9552 : }
9553 :
9554 144 : CPLStringList aosBandNames;
9555 72 : if (const char *pszBandNames =
9556 72 : CSLFetchNameValue(papszOptions, "BAND_NAMES"))
9557 : {
9558 : aosBandNames =
9559 2 : CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);
9560 :
9561 2 : if (aosBandNames.Count() != nBands)
9562 : {
9563 1 : CPLError(CE_Failure, CPLE_OpenFailed,
9564 : "Attempted to create netCDF with %d bands but %d names "
9565 : "provided in BAND_NAMES.",
9566 : nBands, aosBandNames.Count());
9567 :
9568 1 : return nullptr;
9569 : }
9570 : }
9571 :
9572 71 : if (!pfnProgress(0.0, nullptr, pProgressData))
9573 0 : return nullptr;
9574 :
9575 : // Same as in Create().
9576 142 : CPLStringList aosOptions(CSLDuplicate(papszOptions));
9577 133 : if (aosOptions.FetchNameValue("FORMAT") == nullptr &&
9578 62 : (eDT == GDT_UInt16 || eDT == GDT_UInt32 || eDT == GDT_UInt64 ||
9579 : eDT == GDT_Int64))
9580 : {
9581 6 : CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
9582 6 : aosOptions.SetNameValue("FORMAT", "NC4");
9583 : }
9584 71 : netCDFDataset *poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize,
9585 : nBands, aosOptions.List());
9586 71 : if (!poDS)
9587 13 : return nullptr;
9588 :
9589 : // Copy global metadata.
9590 : // Add Conventions, GDAL info and history.
9591 58 : CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid, NC_GLOBAL, nullptr);
9592 58 : const bool bWriteGDALVersion = CPLTestBool(
9593 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
9594 58 : const bool bWriteGDALHistory = CPLTestBool(
9595 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
9596 58 : NCDFAddGDALHistory(
9597 : poDS->cdfid, pszFilename, bWriteGDALVersion, bWriteGDALHistory,
9598 58 : poSrcDS->GetMetadataItem("NC_GLOBAL#history"), "CreateCopy",
9599 58 : poSrcDS->GetMetadataItem("NC_GLOBAL#Conventions"));
9600 :
9601 58 : pfnProgress(0.1, nullptr, pProgressData);
9602 :
9603 : // Check for extra dimensions.
9604 58 : int nDim = 2;
9605 : char **papszExtraDimNames =
9606 58 : NCDFTokenizeArray(poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
9607 58 : char **papszExtraDimValues = nullptr;
9608 :
9609 58 : if (papszExtraDimNames != nullptr && CSLCount(papszExtraDimNames) > 0)
9610 : {
9611 5 : size_t nDimSizeTot = 1;
9612 : // first make sure dimensions lengths compatible with band count
9613 : // for( int i=0; i<CSLCount(papszExtraDimNames ); i++ ) {
9614 13 : for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--)
9615 : {
9616 : char szTemp[NC_MAX_NAME + 32 + 1];
9617 8 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
9618 8 : papszExtraDimNames[i]);
9619 : papszExtraDimValues =
9620 8 : NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
9621 8 : const size_t nDimSize = atol(papszExtraDimValues[0]);
9622 8 : CSLDestroy(papszExtraDimValues);
9623 8 : nDimSizeTot *= nDimSize;
9624 : }
9625 5 : if (nDimSizeTot == (size_t)nBands)
9626 : {
9627 5 : nDim = 2 + CSLCount(papszExtraDimNames);
9628 : }
9629 : else
9630 : {
9631 : // if nBands != #bands computed raise a warning
9632 : // just issue a debug message, because it was probably intentional
9633 0 : CPLDebug("GDAL_netCDF",
9634 : "Warning: Number of bands (%d) is not compatible with "
9635 : "dimensions "
9636 : "(total=%ld names=%s)",
9637 : nBands, (long)nDimSizeTot,
9638 0 : poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
9639 0 : CSLDestroy(papszExtraDimNames);
9640 0 : papszExtraDimNames = nullptr;
9641 : }
9642 : }
9643 :
9644 58 : int *panDimIds = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
9645 58 : int *panBandDimPos = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
9646 :
9647 : nc_type nVarType;
9648 58 : int *panBandZLev = nullptr;
9649 58 : int *panDimVarIds = nullptr;
9650 :
9651 58 : if (nDim > 2)
9652 : {
9653 5 : panBandZLev = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
9654 5 : panDimVarIds = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
9655 :
9656 : // Define all dims.
9657 13 : for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--)
9658 : {
9659 8 : poDS->papszDimName.AddString(papszExtraDimNames[i]);
9660 : char szTemp[NC_MAX_NAME + 32 + 1];
9661 8 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
9662 8 : papszExtraDimNames[i]);
9663 : papszExtraDimValues =
9664 8 : NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
9665 8 : const int nDimSize = papszExtraDimValues && papszExtraDimValues[0]
9666 16 : ? atoi(papszExtraDimValues[0])
9667 : : 0;
9668 : // nc_type is an enum in netcdf-3, needs casting.
9669 8 : nVarType = static_cast<nc_type>(papszExtraDimValues &&
9670 8 : papszExtraDimValues[0] &&
9671 8 : papszExtraDimValues[1]
9672 8 : ? atol(papszExtraDimValues[1])
9673 : : 0);
9674 8 : CSLDestroy(papszExtraDimValues);
9675 8 : panBandZLev[i] = nDimSize;
9676 8 : panBandDimPos[i + 2] = i; // Save Position of ZDim.
9677 :
9678 : // Define dim.
9679 16 : int status = nc_def_dim(poDS->cdfid, papszExtraDimNames[i],
9680 8 : nDimSize, &(panDimIds[i]));
9681 8 : NCDF_ERR(status);
9682 :
9683 : // Define dim var.
9684 8 : int anDim[1] = {panDimIds[i]};
9685 16 : status = nc_def_var(poDS->cdfid, papszExtraDimNames[i], nVarType, 1,
9686 8 : anDim, &(panDimVarIds[i]));
9687 8 : NCDF_ERR(status);
9688 :
9689 : // Add dim metadata, using global var# items.
9690 8 : snprintf(szTemp, sizeof(szTemp), "%s#", papszExtraDimNames[i]);
9691 8 : CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid,
9692 8 : panDimVarIds[i], szTemp);
9693 : }
9694 : }
9695 :
9696 : // Copy GeoTransform and Projection.
9697 :
9698 : // Copy geolocation info.
9699 58 : char **papszGeolocationInfo = poSrcDS->GetMetadata("GEOLOCATION");
9700 58 : if (papszGeolocationInfo != nullptr)
9701 5 : poDS->GDALPamDataset::SetMetadata(papszGeolocationInfo, "GEOLOCATION");
9702 :
9703 : // Copy geotransform.
9704 58 : bool bGotGeoTransform = false;
9705 : double adfGeoTransform[6];
9706 58 : CPLErr eErr = poSrcDS->GetGeoTransform(adfGeoTransform);
9707 58 : if (eErr == CE_None)
9708 : {
9709 40 : poDS->SetGeoTransform(adfGeoTransform);
9710 : // Disable AddProjectionVars() from being called.
9711 40 : bGotGeoTransform = true;
9712 40 : poDS->m_bHasGeoTransform = false;
9713 : }
9714 :
9715 : // Copy projection.
9716 58 : void *pScaledProgress = nullptr;
9717 58 : if (bGotGeoTransform || (pszWKT && pszWKT[0] != 0))
9718 : {
9719 41 : poDS->SetProjection(pszWKT ? pszWKT : "");
9720 :
9721 : // Now we can call AddProjectionVars() directly.
9722 41 : poDS->m_bHasGeoTransform = bGotGeoTransform;
9723 41 : poDS->AddProjectionVars(true, nullptr, nullptr);
9724 : pScaledProgress =
9725 41 : GDALCreateScaledProgress(0.1, 0.25, pfnProgress, pProgressData);
9726 41 : poDS->AddProjectionVars(false, GDALScaledProgress, pScaledProgress);
9727 41 : GDALDestroyScaledProgress(pScaledProgress);
9728 : }
9729 : else
9730 : {
9731 17 : poDS->bBottomUp =
9732 17 : CPL_TO_BOOL(CSLFetchBoolean(papszOptions, "WRITE_BOTTOMUP", TRUE));
9733 17 : if (papszGeolocationInfo)
9734 : {
9735 4 : poDS->AddProjectionVars(true, nullptr, nullptr);
9736 4 : poDS->AddProjectionVars(false, nullptr, nullptr);
9737 : }
9738 : }
9739 :
9740 : // Save X,Y dim positions.
9741 58 : panDimIds[nDim - 1] = poDS->nXDimID;
9742 58 : panBandDimPos[0] = nDim - 1;
9743 58 : panDimIds[nDim - 2] = poDS->nYDimID;
9744 58 : panBandDimPos[1] = nDim - 2;
9745 :
9746 : // Write extra dim values - after projection for optimization.
9747 58 : if (nDim > 2)
9748 : {
9749 : // Make sure we are in data mode.
9750 5 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
9751 13 : for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--)
9752 : {
9753 : char szTemp[NC_MAX_NAME + 32 + 1];
9754 8 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_VALUES",
9755 8 : papszExtraDimNames[i]);
9756 8 : if (poSrcDS->GetMetadataItem(szTemp) != nullptr)
9757 : {
9758 8 : NCDFPut1DVar(poDS->cdfid, panDimVarIds[i],
9759 8 : poSrcDS->GetMetadataItem(szTemp));
9760 : }
9761 : }
9762 : }
9763 :
9764 58 : pfnProgress(0.25, nullptr, pProgressData);
9765 :
9766 : // Define Bands.
9767 58 : netCDFRasterBand *poBand = nullptr;
9768 58 : int nBandID = -1;
9769 :
9770 149 : for (int iBand = 1; iBand <= nBands; iBand++)
9771 : {
9772 91 : CPLDebug("GDAL_netCDF", "creating band # %d/%d nDim = %d", iBand,
9773 : nBands, nDim);
9774 :
9775 91 : poSrcBand = poSrcDS->GetRasterBand(iBand);
9776 91 : eDT = poSrcBand->GetRasterDataType();
9777 :
9778 : // Get var name from NETCDF_VARNAME.
9779 : const char *pszNETCDF_VARNAME =
9780 91 : poSrcBand->GetMetadataItem("NETCDF_VARNAME");
9781 : char szBandName[NC_MAX_NAME + 1];
9782 91 : if (!aosBandNames.empty())
9783 : {
9784 2 : snprintf(szBandName, sizeof(szBandName), "%s",
9785 : aosBandNames[iBand - 1]);
9786 : }
9787 89 : else if (pszNETCDF_VARNAME)
9788 : {
9789 32 : if (nBands > 1 && papszExtraDimNames == nullptr)
9790 0 : snprintf(szBandName, sizeof(szBandName), "%s%d",
9791 : pszNETCDF_VARNAME, iBand);
9792 : else
9793 32 : snprintf(szBandName, sizeof(szBandName), "%s",
9794 : pszNETCDF_VARNAME);
9795 : }
9796 : else
9797 : {
9798 57 : szBandName[0] = '\0';
9799 : }
9800 :
9801 : // Get long_name from <var>#long_name.
9802 91 : const char *pszLongName = "";
9803 91 : if (pszNETCDF_VARNAME)
9804 : {
9805 : pszLongName =
9806 64 : poSrcDS->GetMetadataItem(std::string(pszNETCDF_VARNAME)
9807 32 : .append("#")
9808 32 : .append(CF_LNG_NAME)
9809 32 : .c_str());
9810 32 : if (!pszLongName)
9811 25 : pszLongName = "";
9812 : }
9813 :
9814 91 : constexpr bool bSignedData = false;
9815 :
9816 91 : if (nDim > 2)
9817 27 : poBand = new netCDFRasterBand(
9818 27 : netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
9819 : bSignedData, szBandName, pszLongName, nBandID, nDim, iBand - 1,
9820 27 : panBandZLev, panBandDimPos, panDimIds);
9821 : else
9822 64 : poBand = new netCDFRasterBand(
9823 64 : netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
9824 64 : bSignedData, szBandName, pszLongName);
9825 :
9826 91 : poDS->SetBand(iBand, poBand);
9827 :
9828 : // Set nodata value, if any.
9829 91 : GDALCopyNoDataValue(poBand, poSrcBand);
9830 :
9831 : // Copy Metadata for band.
9832 91 : CopyMetadata(nullptr, poSrcDS->GetRasterBand(iBand), poBand,
9833 : poDS->cdfid, poBand->nZId);
9834 :
9835 : // If more than 2D pass the first band's netcdf var ID to subsequent
9836 : // bands.
9837 91 : if (nDim > 2)
9838 27 : nBandID = poBand->nZId;
9839 : }
9840 :
9841 : // Write projection variable to band variable.
9842 58 : poDS->AddGridMappingRef();
9843 :
9844 58 : pfnProgress(0.5, nullptr, pProgressData);
9845 :
9846 : // Write bands.
9847 :
9848 : // Make sure we are in data mode.
9849 58 : poDS->SetDefineMode(false);
9850 :
9851 58 : double dfTemp = 0.5;
9852 :
9853 58 : eErr = CE_None;
9854 :
9855 149 : for (int iBand = 1; iBand <= nBands && eErr == CE_None; iBand++)
9856 : {
9857 91 : const double dfTemp2 = dfTemp + 0.4 / nBands;
9858 91 : pScaledProgress = GDALCreateScaledProgress(dfTemp, dfTemp2, pfnProgress,
9859 : pProgressData);
9860 91 : dfTemp = dfTemp2;
9861 :
9862 91 : CPLDebug("GDAL_netCDF", "copying band data # %d/%d ", iBand, nBands);
9863 :
9864 91 : poSrcBand = poSrcDS->GetRasterBand(iBand);
9865 91 : eDT = poSrcBand->GetRasterDataType();
9866 :
9867 91 : GDALRasterBand *poDstBand = poDS->GetRasterBand(iBand);
9868 :
9869 : // Copy band data.
9870 91 : if (eDT == GDT_Byte)
9871 : {
9872 51 : CPLDebug("GDAL_netCDF", "GByte Band#%d", iBand);
9873 51 : eErr = NCDFCopyBand<GByte>(poSrcBand, poDstBand, nXSize, nYSize,
9874 : GDALScaledProgress, pScaledProgress);
9875 : }
9876 40 : else if (eDT == GDT_Int8)
9877 : {
9878 1 : CPLDebug("GDAL_netCDF", "GInt8 Band#%d", iBand);
9879 1 : eErr = NCDFCopyBand<GInt8>(poSrcBand, poDstBand, nXSize, nYSize,
9880 : GDALScaledProgress, pScaledProgress);
9881 : }
9882 39 : else if (eDT == GDT_UInt16)
9883 : {
9884 2 : CPLDebug("GDAL_netCDF", "GUInt16 Band#%d", iBand);
9885 2 : eErr = NCDFCopyBand<GInt16>(poSrcBand, poDstBand, nXSize, nYSize,
9886 : GDALScaledProgress, pScaledProgress);
9887 : }
9888 37 : else if (eDT == GDT_Int16)
9889 : {
9890 5 : CPLDebug("GDAL_netCDF", "GInt16 Band#%d", iBand);
9891 5 : eErr = NCDFCopyBand<GUInt16>(poSrcBand, poDstBand, nXSize, nYSize,
9892 : GDALScaledProgress, pScaledProgress);
9893 : }
9894 32 : else if (eDT == GDT_UInt32)
9895 : {
9896 2 : CPLDebug("GDAL_netCDF", "GUInt32 Band#%d", iBand);
9897 2 : eErr = NCDFCopyBand<GUInt32>(poSrcBand, poDstBand, nXSize, nYSize,
9898 : GDALScaledProgress, pScaledProgress);
9899 : }
9900 30 : else if (eDT == GDT_Int32)
9901 : {
9902 18 : CPLDebug("GDAL_netCDF", "GInt32 Band#%d", iBand);
9903 18 : eErr = NCDFCopyBand<GInt32>(poSrcBand, poDstBand, nXSize, nYSize,
9904 : GDALScaledProgress, pScaledProgress);
9905 : }
9906 12 : else if (eDT == GDT_UInt64)
9907 : {
9908 2 : CPLDebug("GDAL_netCDF", "GUInt64 Band#%d", iBand);
9909 2 : eErr = NCDFCopyBand<std::uint64_t>(poSrcBand, poDstBand, nXSize,
9910 : nYSize, GDALScaledProgress,
9911 : pScaledProgress);
9912 : }
9913 10 : else if (eDT == GDT_Int64)
9914 : {
9915 2 : CPLDebug("GDAL_netCDF", "GInt64 Band#%d", iBand);
9916 : eErr =
9917 2 : NCDFCopyBand<std::int64_t>(poSrcBand, poDstBand, nXSize, nYSize,
9918 : GDALScaledProgress, pScaledProgress);
9919 : }
9920 8 : else if (eDT == GDT_Float32)
9921 : {
9922 6 : CPLDebug("GDAL_netCDF", "float Band#%d", iBand);
9923 6 : eErr = NCDFCopyBand<float>(poSrcBand, poDstBand, nXSize, nYSize,
9924 : GDALScaledProgress, pScaledProgress);
9925 : }
9926 2 : else if (eDT == GDT_Float64)
9927 : {
9928 2 : CPLDebug("GDAL_netCDF", "double Band#%d", iBand);
9929 2 : eErr = NCDFCopyBand<double>(poSrcBand, poDstBand, nXSize, nYSize,
9930 : GDALScaledProgress, pScaledProgress);
9931 : }
9932 : else
9933 : {
9934 0 : CPLError(CE_Failure, CPLE_NotSupported,
9935 : "The NetCDF driver does not support GDAL data type %d",
9936 : eDT);
9937 : }
9938 :
9939 91 : GDALDestroyScaledProgress(pScaledProgress);
9940 : }
9941 :
9942 58 : delete (poDS);
9943 :
9944 58 : CPLFree(panDimIds);
9945 58 : CPLFree(panBandDimPos);
9946 58 : CPLFree(panBandZLev);
9947 58 : CPLFree(panDimVarIds);
9948 58 : if (papszExtraDimNames)
9949 5 : CSLDestroy(papszExtraDimNames);
9950 :
9951 58 : if (eErr != CE_None)
9952 0 : return nullptr;
9953 :
9954 58 : pfnProgress(0.95, nullptr, pProgressData);
9955 :
9956 : // Re-open dataset so we can return it.
9957 116 : CPLStringList aosOpenOptions;
9958 58 : aosOpenOptions.AddString("VARIABLES_AS_BANDS=YES");
9959 58 : GDALOpenInfo oOpenInfo(pszFilename, GA_Update);
9960 58 : oOpenInfo.nOpenFlags = GDAL_OF_RASTER | GDAL_OF_UPDATE;
9961 58 : oOpenInfo.papszOpenOptions = aosOpenOptions.List();
9962 58 : auto poRetDS = Open(&oOpenInfo);
9963 :
9964 : // PAM cloning is disabled. See bug #4244.
9965 : // if( poDS )
9966 : // poDS->CloneInfo(poSrcDS, GCIF_PAM_DEFAULT);
9967 :
9968 58 : pfnProgress(1.0, nullptr, pProgressData);
9969 :
9970 58 : return poRetDS;
9971 : }
9972 :
9973 : // Note: some logic depends on bIsProjected and bIsGeoGraphic.
9974 : // May not be known when Create() is called, see AddProjectionVars().
9975 254 : void netCDFDataset::ProcessCreationOptions()
9976 : {
9977 : const char *pszConfig =
9978 254 : CSLFetchNameValue(papszCreationOptions, "CONFIG_FILE");
9979 254 : if (pszConfig != nullptr)
9980 : {
9981 4 : if (oWriterConfig.Parse(pszConfig))
9982 : {
9983 : // Override dataset creation options from the config file
9984 2 : std::map<CPLString, CPLString>::iterator oIter;
9985 3 : for (oIter = oWriterConfig.m_oDatasetCreationOptions.begin();
9986 3 : oIter != oWriterConfig.m_oDatasetCreationOptions.end();
9987 1 : ++oIter)
9988 : {
9989 2 : papszCreationOptions = CSLSetNameValue(
9990 2 : papszCreationOptions, oIter->first, oIter->second);
9991 : }
9992 : }
9993 : }
9994 :
9995 : // File format.
9996 254 : eFormat = NCDF_FORMAT_NC;
9997 254 : const char *pszValue = CSLFetchNameValue(papszCreationOptions, "FORMAT");
9998 254 : if (pszValue != nullptr)
9999 : {
10000 93 : if (EQUAL(pszValue, "NC"))
10001 : {
10002 3 : eFormat = NCDF_FORMAT_NC;
10003 : }
10004 : #ifdef NETCDF_HAS_NC2
10005 90 : else if (EQUAL(pszValue, "NC2"))
10006 : {
10007 1 : eFormat = NCDF_FORMAT_NC2;
10008 : }
10009 : #endif
10010 89 : else if (EQUAL(pszValue, "NC4"))
10011 : {
10012 85 : eFormat = NCDF_FORMAT_NC4;
10013 : }
10014 4 : else if (EQUAL(pszValue, "NC4C"))
10015 : {
10016 4 : eFormat = NCDF_FORMAT_NC4C;
10017 : }
10018 : else
10019 : {
10020 0 : CPLError(CE_Failure, CPLE_NotSupported,
10021 : "FORMAT=%s in not supported, using the default NC format.",
10022 : pszValue);
10023 : }
10024 : }
10025 :
10026 : // COMPRESS option.
10027 254 : pszValue = CSLFetchNameValue(papszCreationOptions, "COMPRESS");
10028 254 : if (pszValue != nullptr)
10029 : {
10030 3 : if (EQUAL(pszValue, "NONE"))
10031 : {
10032 1 : eCompress = NCDF_COMPRESS_NONE;
10033 : }
10034 2 : else if (EQUAL(pszValue, "DEFLATE"))
10035 : {
10036 2 : eCompress = NCDF_COMPRESS_DEFLATE;
10037 2 : if (!((eFormat == NCDF_FORMAT_NC4) ||
10038 2 : (eFormat == NCDF_FORMAT_NC4C)))
10039 : {
10040 1 : CPLError(CE_Warning, CPLE_IllegalArg,
10041 : "NOTICE: Format set to NC4C because compression is "
10042 : "set to DEFLATE.");
10043 1 : eFormat = NCDF_FORMAT_NC4C;
10044 : }
10045 : }
10046 : else
10047 : {
10048 0 : CPLError(CE_Failure, CPLE_NotSupported,
10049 : "COMPRESS=%s is not supported.", pszValue);
10050 : }
10051 : }
10052 :
10053 : // ZLEVEL option.
10054 254 : pszValue = CSLFetchNameValue(papszCreationOptions, "ZLEVEL");
10055 254 : if (pszValue != nullptr)
10056 : {
10057 1 : nZLevel = atoi(pszValue);
10058 1 : if (!(nZLevel >= 1 && nZLevel <= 9))
10059 : {
10060 0 : CPLError(CE_Warning, CPLE_IllegalArg,
10061 : "ZLEVEL=%s value not recognised, ignoring.", pszValue);
10062 0 : nZLevel = NCDF_DEFLATE_LEVEL;
10063 : }
10064 : }
10065 :
10066 : // CHUNKING option.
10067 254 : bChunking =
10068 254 : CPL_TO_BOOL(CSLFetchBoolean(papszCreationOptions, "CHUNKING", TRUE));
10069 :
10070 : // MULTIPLE_LAYERS option.
10071 : const char *pszMultipleLayerBehavior =
10072 254 : CSLFetchNameValueDef(papszCreationOptions, "MULTIPLE_LAYERS", "NO");
10073 508 : const char *pszGeometryEnc = CSLFetchNameValueDef(
10074 254 : papszCreationOptions, "GEOMETRY_ENCODING", "CF_1.8");
10075 254 : if (EQUAL(pszMultipleLayerBehavior, "NO") ||
10076 3 : EQUAL(pszGeometryEnc, "CF_1.8"))
10077 : {
10078 251 : eMultipleLayerBehavior = SINGLE_LAYER;
10079 : }
10080 3 : else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_FILES"))
10081 : {
10082 2 : eMultipleLayerBehavior = SEPARATE_FILES;
10083 : }
10084 1 : else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_GROUPS"))
10085 : {
10086 1 : if (eFormat == NCDF_FORMAT_NC4)
10087 : {
10088 1 : eMultipleLayerBehavior = SEPARATE_GROUPS;
10089 : }
10090 : else
10091 : {
10092 0 : CPLError(CE_Warning, CPLE_IllegalArg,
10093 : "MULTIPLE_LAYERS=%s is recognised only with FORMAT=NC4",
10094 : pszMultipleLayerBehavior);
10095 : }
10096 : }
10097 : else
10098 : {
10099 0 : CPLError(CE_Warning, CPLE_IllegalArg,
10100 : "MULTIPLE_LAYERS=%s not recognised", pszMultipleLayerBehavior);
10101 : }
10102 :
10103 : // Set nCreateMode based on eFormat.
10104 254 : switch (eFormat)
10105 : {
10106 : #ifdef NETCDF_HAS_NC2
10107 1 : case NCDF_FORMAT_NC2:
10108 1 : nCreateMode = NC_CLOBBER | NC_64BIT_OFFSET;
10109 1 : break;
10110 : #endif
10111 85 : case NCDF_FORMAT_NC4:
10112 85 : nCreateMode = NC_CLOBBER | NC_NETCDF4;
10113 85 : break;
10114 5 : case NCDF_FORMAT_NC4C:
10115 5 : nCreateMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
10116 5 : break;
10117 163 : case NCDF_FORMAT_NC:
10118 : default:
10119 163 : nCreateMode = NC_CLOBBER;
10120 163 : break;
10121 : }
10122 :
10123 254 : CPLDebug("GDAL_netCDF", "file options: format=%d compress=%d zlevel=%d",
10124 254 : eFormat, eCompress, nZLevel);
10125 254 : }
10126 :
10127 278 : int netCDFDataset::DefVarDeflate(int nVarId, bool bChunkingArg)
10128 : {
10129 278 : if (eCompress == NCDF_COMPRESS_DEFLATE)
10130 : {
10131 : // Must set chunk size to avoid huge performance hit (set
10132 : // bChunkingArg=TRUE)
10133 : // perhaps another solution it to change the chunk cache?
10134 : // http://www.unidata.ucar.edu/software/netcdf/docs/netcdf.html#Chunk-Cache
10135 : // TODO: make sure this is okay.
10136 2 : CPLDebug("GDAL_netCDF", "DefVarDeflate(%d, %d) nZlevel=%d", nVarId,
10137 : static_cast<int>(bChunkingArg), nZLevel);
10138 :
10139 2 : int status = nc_def_var_deflate(cdfid, nVarId, 1, 1, nZLevel);
10140 2 : NCDF_ERR(status);
10141 :
10142 2 : if (status == NC_NOERR && bChunkingArg && bChunking)
10143 : {
10144 : // set chunking to be 1 for all dims, except X dim
10145 : // size_t chunksize[] = { 1, (size_t)nRasterXSize };
10146 : size_t chunksize[MAX_NC_DIMS];
10147 : int nd;
10148 2 : nc_inq_varndims(cdfid, nVarId, &nd);
10149 2 : chunksize[0] = (size_t)1;
10150 2 : chunksize[1] = (size_t)1;
10151 2 : for (int i = 2; i < nd; i++)
10152 0 : chunksize[i] = (size_t)1;
10153 2 : chunksize[nd - 1] = (size_t)nRasterXSize;
10154 :
10155 : // Config options just for testing purposes
10156 : const char *pszBlockXSize =
10157 2 : CPLGetConfigOption("BLOCKXSIZE", nullptr);
10158 2 : if (pszBlockXSize)
10159 0 : chunksize[nd - 1] = (size_t)atoi(pszBlockXSize);
10160 :
10161 : const char *pszBlockYSize =
10162 2 : CPLGetConfigOption("BLOCKYSIZE", nullptr);
10163 2 : if (nd >= 2 && pszBlockYSize)
10164 0 : chunksize[nd - 2] = (size_t)atoi(pszBlockYSize);
10165 :
10166 2 : CPLDebug("GDAL_netCDF",
10167 : "DefVarDeflate() chunksize={%ld, %ld} chunkX=%ld nd=%d",
10168 2 : (long)chunksize[0], (long)chunksize[1],
10169 2 : (long)chunksize[nd - 1], nd);
10170 : #ifdef NCDF_DEBUG
10171 : for (int i = 0; i < nd; i++)
10172 : CPLDebug("GDAL_netCDF", "DefVarDeflate() chunk[%d]=%ld", i,
10173 : chunksize[i]);
10174 : #endif
10175 :
10176 2 : status = nc_def_var_chunking(cdfid, nVarId, NC_CHUNKED, chunksize);
10177 2 : NCDF_ERR(status);
10178 : }
10179 : else
10180 : {
10181 0 : CPLDebug("GDAL_netCDF", "chunksize not set");
10182 : }
10183 2 : return status;
10184 : }
10185 276 : return NC_NOERR;
10186 : }
10187 :
10188 : /************************************************************************/
10189 : /* NCDFUnloadDriver() */
10190 : /************************************************************************/
10191 :
10192 8 : static void NCDFUnloadDriver(CPL_UNUSED GDALDriver *poDriver)
10193 : {
10194 8 : if (hNCMutex != nullptr)
10195 4 : CPLDestroyMutex(hNCMutex);
10196 8 : hNCMutex = nullptr;
10197 8 : }
10198 :
10199 : /************************************************************************/
10200 : /* GDALRegister_netCDF() */
10201 : /************************************************************************/
10202 :
10203 : class GDALnetCDFDriver final : public GDALDriver
10204 : {
10205 : public:
10206 18 : GDALnetCDFDriver() = default;
10207 :
10208 1339 : const char *GetMetadataItem(const char *pszName,
10209 : const char *pszDomain) override
10210 : {
10211 1339 : if (EQUAL(pszName, GDAL_DCAP_VIRTUALIO))
10212 : {
10213 13 : InitializeDCAPVirtualIO();
10214 : }
10215 1339 : return GDALDriver::GetMetadataItem(pszName, pszDomain);
10216 : }
10217 :
10218 89 : char **GetMetadata(const char *pszDomain) override
10219 : {
10220 89 : InitializeDCAPVirtualIO();
10221 89 : return GDALDriver::GetMetadata(pszDomain);
10222 : }
10223 :
10224 : private:
10225 : bool m_bInitialized = false;
10226 :
10227 102 : void InitializeDCAPVirtualIO()
10228 : {
10229 102 : if (!m_bInitialized)
10230 : {
10231 12 : m_bInitialized = true;
10232 :
10233 : #ifdef ENABLE_UFFD
10234 12 : if (CPLIsUserFaultMappingSupported())
10235 : {
10236 12 : SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
10237 : }
10238 : #endif
10239 : }
10240 102 : }
10241 : };
10242 :
10243 18 : void GDALRegister_netCDF()
10244 :
10245 : {
10246 18 : if (!GDAL_CHECK_VERSION("netCDF driver"))
10247 0 : return;
10248 :
10249 18 : if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
10250 0 : return;
10251 :
10252 18 : GDALDriver *poDriver = new GDALnetCDFDriver();
10253 18 : netCDFDriverSetCommonMetadata(poDriver);
10254 :
10255 18 : poDriver->SetMetadataItem("NETCDF_CONVENTIONS",
10256 18 : GDAL_DEFAULT_NCDF_CONVENTIONS);
10257 18 : poDriver->SetMetadataItem("NETCDF_VERSION", nc_inq_libvers());
10258 :
10259 : // Set pfns and register driver.
10260 18 : poDriver->pfnOpen = netCDFDataset::Open;
10261 18 : poDriver->pfnCreateCopy = netCDFDataset::CreateCopy;
10262 18 : poDriver->pfnCreate = netCDFDataset::Create;
10263 18 : poDriver->pfnCreateMultiDimensional = netCDFDataset::CreateMultiDimensional;
10264 18 : poDriver->pfnUnloadDriver = NCDFUnloadDriver;
10265 :
10266 18 : GetGDALDriverManager()->RegisterDriver(poDriver);
10267 : }
10268 :
10269 : /************************************************************************/
10270 : /* New functions */
10271 : /************************************************************************/
10272 :
10273 : /* Test for GDAL version string >= target */
10274 241 : static bool NCDFIsGDALVersionGTE(const char *pszVersion, int nTarget)
10275 : {
10276 :
10277 : // Valid strings are "GDAL 1.9dev, released 2011/01/18" and "GDAL 1.8.1 ".
10278 241 : if (pszVersion == nullptr || EQUAL(pszVersion, ""))
10279 0 : return false;
10280 241 : else if (!STARTS_WITH_CI(pszVersion, "GDAL "))
10281 0 : return false;
10282 : // 2.0dev of 2011/12/29 has been later renamed as 1.10dev.
10283 241 : else if (EQUAL("GDAL 2.0dev, released 2011/12/29", pszVersion))
10284 0 : return nTarget <= GDAL_COMPUTE_VERSION(1, 10, 0);
10285 241 : else if (STARTS_WITH_CI(pszVersion, "GDAL 1.9dev"))
10286 2 : return nTarget <= 1900;
10287 239 : else if (STARTS_WITH_CI(pszVersion, "GDAL 1.8dev"))
10288 0 : return nTarget <= 1800;
10289 :
10290 239 : char **papszTokens = CSLTokenizeString2(pszVersion + 5, ".", 0);
10291 :
10292 239 : int nVersions[] = {0, 0, 0, 0};
10293 956 : for (int iToken = 0; papszTokens && iToken < 4 && papszTokens[iToken];
10294 : iToken++)
10295 : {
10296 717 : nVersions[iToken] = atoi(papszTokens[iToken]);
10297 717 : if (nVersions[iToken] < 0)
10298 0 : nVersions[iToken] = 0;
10299 717 : else if (nVersions[iToken] > 99)
10300 0 : nVersions[iToken] = 99;
10301 : }
10302 :
10303 239 : int nVersion = 0;
10304 239 : if (nVersions[0] > 1 || nVersions[1] >= 10)
10305 239 : nVersion =
10306 239 : GDAL_COMPUTE_VERSION(nVersions[0], nVersions[1], nVersions[2]);
10307 : else
10308 0 : nVersion = nVersions[0] * 1000 + nVersions[1] * 100 +
10309 0 : nVersions[2] * 10 + nVersions[3];
10310 :
10311 239 : CSLDestroy(papszTokens);
10312 239 : return nTarget <= nVersion;
10313 : }
10314 :
10315 : // Add Conventions, GDAL version and history.
10316 167 : static void NCDFAddGDALHistory(int fpImage, const char *pszFilename,
10317 : bool bWriteGDALVersion, bool bWriteGDALHistory,
10318 : const char *pszOldHist,
10319 : const char *pszFunctionName,
10320 : const char *pszCFVersion)
10321 : {
10322 167 : if (pszCFVersion == nullptr)
10323 : {
10324 42 : pszCFVersion = GDAL_DEFAULT_NCDF_CONVENTIONS;
10325 : }
10326 167 : int status = nc_put_att_text(fpImage, NC_GLOBAL, "Conventions",
10327 : strlen(pszCFVersion), pszCFVersion);
10328 167 : NCDF_ERR(status);
10329 :
10330 167 : if (bWriteGDALVersion)
10331 : {
10332 165 : const char *pszNCDF_GDAL = GDALVersionInfo("--version");
10333 165 : status = nc_put_att_text(fpImage, NC_GLOBAL, "GDAL",
10334 : strlen(pszNCDF_GDAL), pszNCDF_GDAL);
10335 165 : NCDF_ERR(status);
10336 : }
10337 :
10338 167 : if (bWriteGDALHistory)
10339 : {
10340 : // Add history.
10341 330 : CPLString osTmp;
10342 : #ifdef GDAL_SET_CMD_LINE_DEFINED_TMP
10343 : if (!EQUAL(GDALGetCmdLine(), ""))
10344 : osTmp = GDALGetCmdLine();
10345 : else
10346 : osTmp =
10347 : CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
10348 : #else
10349 165 : osTmp = CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
10350 : #endif
10351 :
10352 165 : NCDFAddHistory(fpImage, osTmp.c_str(), pszOldHist);
10353 : }
10354 2 : else if (pszOldHist != nullptr)
10355 : {
10356 0 : status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
10357 : strlen(pszOldHist), pszOldHist);
10358 0 : NCDF_ERR(status);
10359 : }
10360 167 : }
10361 :
10362 : // Code taken from cdo and libcdi, used for writing the history attribute.
10363 :
10364 : // void cdoDefHistory(int fileID, char *histstring)
10365 165 : static void NCDFAddHistory(int fpImage, const char *pszAddHist,
10366 : const char *pszOldHist)
10367 : {
10368 : // Check pszOldHist - as if there was no previous history, it will be
10369 : // a null pointer - if so set as empty.
10370 165 : if (nullptr == pszOldHist)
10371 : {
10372 53 : pszOldHist = "";
10373 : }
10374 :
10375 : char strtime[32];
10376 165 : strtime[0] = '\0';
10377 :
10378 165 : time_t tp = time(nullptr);
10379 165 : if (tp != -1)
10380 : {
10381 : struct tm ltime;
10382 165 : VSILocalTime(&tp, <ime);
10383 165 : (void)strftime(strtime, sizeof(strtime),
10384 : "%a %b %d %H:%M:%S %Y: ", <ime);
10385 : }
10386 :
10387 : // status = nc_get_att_text(fpImage, NC_GLOBAL,
10388 : // "history", pszOldHist);
10389 : // printf("status: %d pszOldHist: [%s]\n",status,pszOldHist);
10390 :
10391 165 : size_t nNewHistSize =
10392 165 : strlen(pszOldHist) + strlen(strtime) + strlen(pszAddHist) + 1 + 1;
10393 : char *pszNewHist =
10394 165 : static_cast<char *>(CPLMalloc(nNewHistSize * sizeof(char)));
10395 :
10396 165 : strcpy(pszNewHist, strtime);
10397 165 : strcat(pszNewHist, pszAddHist);
10398 :
10399 : // int disableHistory = FALSE;
10400 : // if( !disableHistory )
10401 : {
10402 165 : if (!EQUAL(pszOldHist, ""))
10403 3 : strcat(pszNewHist, "\n");
10404 165 : strcat(pszNewHist, pszOldHist);
10405 : }
10406 :
10407 165 : const int status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
10408 : strlen(pszNewHist), pszNewHist);
10409 165 : NCDF_ERR(status);
10410 :
10411 165 : CPLFree(pszNewHist);
10412 165 : }
10413 :
10414 6045 : static CPLErr NCDFSafeStrcat(char **ppszDest, const char *pszSrc,
10415 : size_t *nDestSize)
10416 : {
10417 : /* Reallocate the data string until the content fits */
10418 6045 : while (*nDestSize < (strlen(*ppszDest) + strlen(pszSrc) + 1))
10419 : {
10420 394 : (*nDestSize) *= 2;
10421 394 : *ppszDest = static_cast<char *>(
10422 394 : CPLRealloc(reinterpret_cast<void *>(*ppszDest), *nDestSize));
10423 : #ifdef NCDF_DEBUG
10424 : CPLDebug("GDAL_netCDF", "NCDFSafeStrcat() resized str from %ld to %ld",
10425 : (*nDestSize) / 2, *nDestSize);
10426 : #endif
10427 : }
10428 5651 : strcat(*ppszDest, pszSrc);
10429 :
10430 5651 : return CE_None;
10431 : }
10432 :
10433 : /* helper function for NCDFGetAttr() */
10434 : /* if pdfValue != nullptr, sets *pdfValue to first value returned */
10435 : /* if ppszValue != nullptr, sets *ppszValue with all attribute values */
10436 : /* *ppszValue is the responsibility of the caller and must be freed */
10437 62984 : static CPLErr NCDFGetAttr1(int nCdfId, int nVarId, const char *pszAttrName,
10438 : double *pdfValue, char **ppszValue)
10439 : {
10440 62984 : nc_type nAttrType = NC_NAT;
10441 62984 : size_t nAttrLen = 0;
10442 :
10443 62984 : if (ppszValue)
10444 61838 : *ppszValue = nullptr;
10445 :
10446 62984 : int status = nc_inq_att(nCdfId, nVarId, pszAttrName, &nAttrType, &nAttrLen);
10447 62984 : if (status != NC_NOERR)
10448 33793 : return CE_Failure;
10449 :
10450 : #ifdef NCDF_DEBUG
10451 : CPLDebug("GDAL_netCDF", "NCDFGetAttr1(%s) len=%ld type=%d", pszAttrName,
10452 : nAttrLen, nAttrType);
10453 : #endif
10454 29191 : if (nAttrLen == 0 && nAttrType != NC_CHAR)
10455 1 : return CE_Failure;
10456 :
10457 : /* Allocate guaranteed minimum size (use 10 or 20 if not a string) */
10458 29190 : size_t nAttrValueSize = nAttrLen + 1;
10459 29190 : if (nAttrType != NC_CHAR && nAttrValueSize < 10)
10460 3156 : nAttrValueSize = 10;
10461 29190 : if (nAttrType == NC_DOUBLE && nAttrValueSize < 20)
10462 1558 : nAttrValueSize = 20;
10463 29190 : if (nAttrType == NC_INT64 && nAttrValueSize < 20)
10464 20 : nAttrValueSize = 22;
10465 : char *pszAttrValue =
10466 29190 : static_cast<char *>(CPLCalloc(nAttrValueSize, sizeof(char)));
10467 29190 : *pszAttrValue = '\0';
10468 :
10469 29190 : if (nAttrLen > 1 && nAttrType != NC_CHAR)
10470 578 : NCDFSafeStrcat(&pszAttrValue, "{", &nAttrValueSize);
10471 :
10472 29190 : double dfValue = 0.0;
10473 29190 : size_t m = 0;
10474 : char szTemp[256];
10475 29190 : bool bSetDoubleFromStr = false;
10476 :
10477 29190 : switch (nAttrType)
10478 : {
10479 26032 : case NC_CHAR:
10480 26032 : CPL_IGNORE_RET_VAL(
10481 26032 : nc_get_att_text(nCdfId, nVarId, pszAttrName, pszAttrValue));
10482 26032 : pszAttrValue[nAttrLen] = '\0';
10483 26032 : bSetDoubleFromStr = true;
10484 26032 : dfValue = 0.0;
10485 26032 : break;
10486 90 : case NC_BYTE:
10487 : {
10488 : signed char *pscTemp = static_cast<signed char *>(
10489 90 : CPLCalloc(nAttrLen, sizeof(signed char)));
10490 90 : nc_get_att_schar(nCdfId, nVarId, pszAttrName, pscTemp);
10491 90 : dfValue = static_cast<double>(pscTemp[0]);
10492 90 : if (nAttrLen > 1)
10493 : {
10494 20 : for (m = 0; m < nAttrLen - 1; m++)
10495 : {
10496 11 : snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
10497 11 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10498 : }
10499 : }
10500 90 : snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
10501 90 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10502 90 : CPLFree(pscTemp);
10503 90 : break;
10504 : }
10505 481 : case NC_SHORT:
10506 : {
10507 : short *psTemp =
10508 481 : static_cast<short *>(CPLCalloc(nAttrLen, sizeof(short)));
10509 481 : nc_get_att_short(nCdfId, nVarId, pszAttrName, psTemp);
10510 481 : dfValue = static_cast<double>(psTemp[0]);
10511 481 : if (nAttrLen > 1)
10512 : {
10513 760 : for (m = 0; m < nAttrLen - 1; m++)
10514 : {
10515 380 : snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
10516 380 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10517 : }
10518 : }
10519 481 : snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
10520 481 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10521 481 : CPLFree(psTemp);
10522 481 : break;
10523 : }
10524 524 : case NC_INT:
10525 : {
10526 524 : int *pnTemp = static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
10527 524 : nc_get_att_int(nCdfId, nVarId, pszAttrName, pnTemp);
10528 524 : dfValue = static_cast<double>(pnTemp[0]);
10529 524 : if (nAttrLen > 1)
10530 : {
10531 214 : for (m = 0; m < nAttrLen - 1; m++)
10532 : {
10533 137 : snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
10534 137 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10535 : }
10536 : }
10537 524 : snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
10538 524 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10539 524 : CPLFree(pnTemp);
10540 524 : break;
10541 : }
10542 391 : case NC_FLOAT:
10543 : {
10544 : float *pfTemp =
10545 391 : static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
10546 391 : nc_get_att_float(nCdfId, nVarId, pszAttrName, pfTemp);
10547 391 : dfValue = static_cast<double>(pfTemp[0]);
10548 391 : if (nAttrLen > 1)
10549 : {
10550 56 : for (m = 0; m < nAttrLen - 1; m++)
10551 : {
10552 28 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
10553 28 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10554 : }
10555 : }
10556 391 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
10557 391 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10558 391 : CPLFree(pfTemp);
10559 391 : break;
10560 : }
10561 1558 : case NC_DOUBLE:
10562 : {
10563 : double *pdfTemp =
10564 1558 : static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
10565 1558 : nc_get_att_double(nCdfId, nVarId, pszAttrName, pdfTemp);
10566 1558 : dfValue = pdfTemp[0];
10567 1558 : if (nAttrLen > 1)
10568 : {
10569 162 : for (m = 0; m < nAttrLen - 1; m++)
10570 : {
10571 88 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
10572 88 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10573 : }
10574 : }
10575 1558 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
10576 1558 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10577 1558 : CPLFree(pdfTemp);
10578 1558 : break;
10579 : }
10580 8 : case NC_STRING:
10581 : {
10582 : char **ppszTemp =
10583 8 : static_cast<char **>(CPLCalloc(nAttrLen, sizeof(char *)));
10584 8 : nc_get_att_string(nCdfId, nVarId, pszAttrName, ppszTemp);
10585 8 : bSetDoubleFromStr = true;
10586 8 : dfValue = 0.0;
10587 8 : if (nAttrLen > 1)
10588 : {
10589 15 : for (m = 0; m < nAttrLen - 1; m++)
10590 : {
10591 10 : NCDFSafeStrcat(&pszAttrValue,
10592 10 : ppszTemp[m] ? ppszTemp[m] : "{NULL}",
10593 : &nAttrValueSize);
10594 10 : NCDFSafeStrcat(&pszAttrValue, ",", &nAttrValueSize);
10595 : }
10596 : }
10597 8 : NCDFSafeStrcat(&pszAttrValue, ppszTemp[m] ? ppszTemp[m] : "{NULL}",
10598 : &nAttrValueSize);
10599 8 : nc_free_string(nAttrLen, ppszTemp);
10600 8 : CPLFree(ppszTemp);
10601 8 : break;
10602 : }
10603 26 : case NC_UBYTE:
10604 : {
10605 : unsigned char *pucTemp = static_cast<unsigned char *>(
10606 26 : CPLCalloc(nAttrLen, sizeof(unsigned char)));
10607 26 : nc_get_att_uchar(nCdfId, nVarId, pszAttrName, pucTemp);
10608 26 : dfValue = static_cast<double>(pucTemp[0]);
10609 26 : if (nAttrLen > 1)
10610 : {
10611 0 : for (m = 0; m < nAttrLen - 1; m++)
10612 : {
10613 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
10614 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10615 : }
10616 : }
10617 26 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
10618 26 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10619 26 : CPLFree(pucTemp);
10620 26 : break;
10621 : }
10622 24 : case NC_USHORT:
10623 : {
10624 : unsigned short *pusTemp;
10625 : pusTemp = static_cast<unsigned short *>(
10626 24 : CPLCalloc(nAttrLen, sizeof(unsigned short)));
10627 24 : nc_get_att_ushort(nCdfId, nVarId, pszAttrName, pusTemp);
10628 24 : dfValue = static_cast<double>(pusTemp[0]);
10629 24 : if (nAttrLen > 1)
10630 : {
10631 10 : for (m = 0; m < nAttrLen - 1; m++)
10632 : {
10633 5 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
10634 5 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10635 : }
10636 : }
10637 24 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
10638 24 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10639 24 : CPLFree(pusTemp);
10640 24 : break;
10641 : }
10642 16 : case NC_UINT:
10643 : {
10644 : unsigned int *punTemp =
10645 16 : static_cast<unsigned int *>(CPLCalloc(nAttrLen, sizeof(int)));
10646 16 : nc_get_att_uint(nCdfId, nVarId, pszAttrName, punTemp);
10647 16 : dfValue = static_cast<double>(punTemp[0]);
10648 16 : if (nAttrLen > 1)
10649 : {
10650 0 : for (m = 0; m < nAttrLen - 1; m++)
10651 : {
10652 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
10653 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10654 : }
10655 : }
10656 16 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
10657 16 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10658 16 : CPLFree(punTemp);
10659 16 : break;
10660 : }
10661 20 : case NC_INT64:
10662 : {
10663 : GIntBig *panTemp =
10664 20 : static_cast<GIntBig *>(CPLCalloc(nAttrLen, sizeof(GIntBig)));
10665 20 : nc_get_att_longlong(nCdfId, nVarId, pszAttrName, panTemp);
10666 20 : dfValue = static_cast<double>(panTemp[0]);
10667 20 : if (nAttrLen > 1)
10668 : {
10669 0 : for (m = 0; m < nAttrLen - 1; m++)
10670 : {
10671 0 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",",
10672 0 : panTemp[m]);
10673 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10674 : }
10675 : }
10676 20 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, panTemp[m]);
10677 20 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10678 20 : CPLFree(panTemp);
10679 20 : break;
10680 : }
10681 20 : case NC_UINT64:
10682 : {
10683 : GUIntBig *panTemp =
10684 20 : static_cast<GUIntBig *>(CPLCalloc(nAttrLen, sizeof(GUIntBig)));
10685 20 : nc_get_att_ulonglong(nCdfId, nVarId, pszAttrName, panTemp);
10686 20 : dfValue = static_cast<double>(panTemp[0]);
10687 20 : if (nAttrLen > 1)
10688 : {
10689 0 : for (m = 0; m < nAttrLen - 1; m++)
10690 : {
10691 0 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",",
10692 0 : panTemp[m]);
10693 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10694 : }
10695 : }
10696 20 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, panTemp[m]);
10697 20 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10698 20 : CPLFree(panTemp);
10699 20 : break;
10700 : }
10701 0 : default:
10702 0 : CPLDebug("GDAL_netCDF",
10703 : "NCDFGetAttr unsupported type %d for attribute %s",
10704 : nAttrType, pszAttrName);
10705 0 : break;
10706 : }
10707 :
10708 29190 : if (nAttrLen > 1 && nAttrType != NC_CHAR)
10709 578 : NCDFSafeStrcat(&pszAttrValue, "}", &nAttrValueSize);
10710 :
10711 29190 : if (bSetDoubleFromStr)
10712 : {
10713 26040 : if (CPLGetValueType(pszAttrValue) == CPL_VALUE_STRING)
10714 : {
10715 25858 : if (ppszValue == nullptr && pdfValue != nullptr)
10716 : {
10717 1 : CPLFree(pszAttrValue);
10718 1 : return CE_Failure;
10719 : }
10720 : }
10721 26039 : dfValue = CPLAtof(pszAttrValue);
10722 : }
10723 :
10724 : /* set return values */
10725 29189 : if (ppszValue)
10726 28877 : *ppszValue = pszAttrValue;
10727 : else
10728 312 : CPLFree(pszAttrValue);
10729 :
10730 29189 : if (pdfValue)
10731 312 : *pdfValue = dfValue;
10732 :
10733 29189 : return CE_None;
10734 : }
10735 :
10736 : /* sets pdfValue to first value found */
10737 1146 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
10738 : double *pdfValue)
10739 : {
10740 1146 : return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, pdfValue, nullptr);
10741 : }
10742 :
10743 : /* pszValue is the responsibility of the caller and must be freed */
10744 61838 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
10745 : char **pszValue)
10746 : {
10747 61838 : return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, nullptr, pszValue);
10748 : }
10749 :
10750 : /* By default write NC_CHAR, but detect for int/float/double and */
10751 : /* NC4 string arrays */
10752 106 : static CPLErr NCDFPutAttr(int nCdfId, int nVarId, const char *pszAttrName,
10753 : const char *pszValue)
10754 : {
10755 106 : int status = 0;
10756 106 : char *pszTemp = nullptr;
10757 :
10758 : /* get the attribute values as tokens */
10759 106 : char **papszValues = NCDFTokenizeArray(pszValue);
10760 106 : if (papszValues == nullptr)
10761 0 : return CE_Failure;
10762 :
10763 106 : size_t nAttrLen = CSLCount(papszValues);
10764 :
10765 : /* first detect type */
10766 106 : nc_type nAttrType = NC_CHAR;
10767 106 : nc_type nTmpAttrType = NC_CHAR;
10768 225 : for (size_t i = 0; i < nAttrLen; i++)
10769 : {
10770 119 : nTmpAttrType = NC_CHAR;
10771 119 : bool bFoundType = false;
10772 119 : errno = 0;
10773 119 : int nValue = static_cast<int>(strtol(papszValues[i], &pszTemp, 10));
10774 : /* test for int */
10775 : /* TODO test for Byte and short - can this be done safely? */
10776 119 : if (errno == 0 && papszValues[i] != pszTemp && *pszTemp == 0)
10777 : {
10778 : char szTemp[256];
10779 19 : CPLsnprintf(szTemp, sizeof(szTemp), "%d", nValue);
10780 19 : if (EQUAL(szTemp, papszValues[i]))
10781 : {
10782 19 : bFoundType = true;
10783 19 : nTmpAttrType = NC_INT;
10784 : }
10785 : else
10786 : {
10787 : unsigned int unValue = static_cast<unsigned int>(
10788 0 : strtoul(papszValues[i], &pszTemp, 10));
10789 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", unValue);
10790 0 : if (EQUAL(szTemp, papszValues[i]))
10791 : {
10792 0 : bFoundType = true;
10793 0 : nTmpAttrType = NC_UINT;
10794 : }
10795 : }
10796 : }
10797 119 : if (!bFoundType)
10798 : {
10799 : /* test for double */
10800 100 : errno = 0;
10801 100 : double dfValue = CPLStrtod(papszValues[i], &pszTemp);
10802 100 : if ((errno == 0) && (papszValues[i] != pszTemp) && (*pszTemp == 0))
10803 : {
10804 : // Test for float instead of double.
10805 : // strtof() is C89, which is not available in MSVC.
10806 : // See if we loose precision if we cast to float and write to
10807 : // char*.
10808 14 : float fValue = float(dfValue);
10809 : char szTemp[256];
10810 14 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", fValue);
10811 14 : if (EQUAL(szTemp, papszValues[i]))
10812 8 : nTmpAttrType = NC_FLOAT;
10813 : else
10814 6 : nTmpAttrType = NC_DOUBLE;
10815 : }
10816 : }
10817 119 : if ((nTmpAttrType <= NC_DOUBLE && nAttrType <= NC_DOUBLE &&
10818 99 : nTmpAttrType > nAttrType) ||
10819 99 : (nTmpAttrType == NC_UINT && nAttrType < NC_FLOAT) ||
10820 5 : (nTmpAttrType >= NC_FLOAT && nAttrType == NC_UINT))
10821 20 : nAttrType = nTmpAttrType;
10822 : }
10823 :
10824 : #ifdef DEBUG
10825 106 : if (EQUAL(pszAttrName, "DEBUG_EMPTY_DOUBLE_ATTR"))
10826 : {
10827 0 : nAttrType = NC_DOUBLE;
10828 0 : nAttrLen = 0;
10829 : }
10830 : #endif
10831 :
10832 : /* now write the data */
10833 106 : if (nAttrType == NC_CHAR)
10834 : {
10835 86 : int nTmpFormat = 0;
10836 86 : if (nAttrLen > 1)
10837 : {
10838 0 : status = nc_inq_format(nCdfId, &nTmpFormat);
10839 0 : NCDF_ERR(status);
10840 : }
10841 86 : if (nAttrLen > 1 && nTmpFormat == NCDF_FORMAT_NC4)
10842 0 : status = nc_put_att_string(nCdfId, nVarId, pszAttrName, nAttrLen,
10843 : const_cast<const char **>(papszValues));
10844 : else
10845 86 : status = nc_put_att_text(nCdfId, nVarId, pszAttrName,
10846 : strlen(pszValue), pszValue);
10847 86 : NCDF_ERR(status);
10848 : }
10849 : else
10850 : {
10851 20 : switch (nAttrType)
10852 : {
10853 11 : case NC_INT:
10854 : {
10855 : int *pnTemp =
10856 11 : static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
10857 30 : for (size_t i = 0; i < nAttrLen; i++)
10858 : {
10859 19 : pnTemp[i] =
10860 19 : static_cast<int>(strtol(papszValues[i], &pszTemp, 10));
10861 : }
10862 11 : status = nc_put_att_int(nCdfId, nVarId, pszAttrName, NC_INT,
10863 : nAttrLen, pnTemp);
10864 11 : NCDF_ERR(status);
10865 11 : CPLFree(pnTemp);
10866 11 : break;
10867 : }
10868 0 : case NC_UINT:
10869 : {
10870 : unsigned int *punTemp = static_cast<unsigned int *>(
10871 0 : CPLCalloc(nAttrLen, sizeof(unsigned int)));
10872 0 : for (size_t i = 0; i < nAttrLen; i++)
10873 : {
10874 0 : punTemp[i] = static_cast<unsigned int>(
10875 0 : strtol(papszValues[i], &pszTemp, 10));
10876 : }
10877 0 : status = nc_put_att_uint(nCdfId, nVarId, pszAttrName, NC_UINT,
10878 : nAttrLen, punTemp);
10879 0 : NCDF_ERR(status);
10880 0 : CPLFree(punTemp);
10881 0 : break;
10882 : }
10883 6 : case NC_FLOAT:
10884 : {
10885 : float *pfTemp =
10886 6 : static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
10887 14 : for (size_t i = 0; i < nAttrLen; i++)
10888 : {
10889 8 : pfTemp[i] =
10890 8 : static_cast<float>(CPLStrtod(papszValues[i], &pszTemp));
10891 : }
10892 6 : status = nc_put_att_float(nCdfId, nVarId, pszAttrName, NC_FLOAT,
10893 : nAttrLen, pfTemp);
10894 6 : NCDF_ERR(status);
10895 6 : CPLFree(pfTemp);
10896 6 : break;
10897 : }
10898 3 : case NC_DOUBLE:
10899 : {
10900 : double *pdfTemp =
10901 3 : static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
10902 9 : for (size_t i = 0; i < nAttrLen; i++)
10903 : {
10904 6 : pdfTemp[i] = CPLStrtod(papszValues[i], &pszTemp);
10905 : }
10906 3 : status = nc_put_att_double(nCdfId, nVarId, pszAttrName,
10907 : NC_DOUBLE, nAttrLen, pdfTemp);
10908 3 : NCDF_ERR(status);
10909 3 : CPLFree(pdfTemp);
10910 3 : break;
10911 : }
10912 0 : default:
10913 0 : if (papszValues)
10914 0 : CSLDestroy(papszValues);
10915 0 : return CE_Failure;
10916 : break;
10917 : }
10918 : }
10919 :
10920 106 : if (papszValues)
10921 106 : CSLDestroy(papszValues);
10922 :
10923 106 : return CE_None;
10924 : }
10925 :
10926 76 : static CPLErr NCDFGet1DVar(int nCdfId, int nVarId, char **pszValue)
10927 : {
10928 : /* get var information */
10929 76 : int nVarDimId = -1;
10930 76 : int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
10931 76 : if (status != NC_NOERR || nVarDimId != 1)
10932 0 : return CE_Failure;
10933 :
10934 76 : status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
10935 76 : if (status != NC_NOERR)
10936 0 : return CE_Failure;
10937 :
10938 76 : nc_type nVarType = NC_NAT;
10939 76 : status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
10940 76 : if (status != NC_NOERR)
10941 0 : return CE_Failure;
10942 :
10943 76 : size_t nVarLen = 0;
10944 76 : status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
10945 76 : if (status != NC_NOERR)
10946 0 : return CE_Failure;
10947 :
10948 76 : size_t start[1] = {0};
10949 76 : size_t count[1] = {nVarLen};
10950 :
10951 : /* Allocate guaranteed minimum size */
10952 76 : size_t nVarValueSize = NCDF_MAX_STR_LEN;
10953 : char *pszVarValue =
10954 76 : static_cast<char *>(CPLCalloc(nVarValueSize, sizeof(char)));
10955 76 : *pszVarValue = '\0';
10956 :
10957 76 : if (nVarLen == 0)
10958 : {
10959 : /* set return values */
10960 1 : *pszValue = pszVarValue;
10961 :
10962 1 : return CE_None;
10963 : }
10964 :
10965 75 : if (nVarLen > 1 && nVarType != NC_CHAR)
10966 40 : NCDFSafeStrcat(&pszVarValue, "{", &nVarValueSize);
10967 :
10968 75 : switch (nVarType)
10969 : {
10970 0 : case NC_CHAR:
10971 0 : nc_get_vara_text(nCdfId, nVarId, start, count, pszVarValue);
10972 0 : pszVarValue[nVarLen] = '\0';
10973 0 : break;
10974 0 : case NC_BYTE:
10975 : {
10976 : signed char *pscTemp = static_cast<signed char *>(
10977 0 : CPLCalloc(nVarLen, sizeof(signed char)));
10978 0 : nc_get_vara_schar(nCdfId, nVarId, start, count, pscTemp);
10979 : char szTemp[256];
10980 0 : size_t m = 0;
10981 0 : for (; m < nVarLen - 1; m++)
10982 : {
10983 0 : snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
10984 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10985 : }
10986 0 : snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
10987 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10988 0 : CPLFree(pscTemp);
10989 0 : break;
10990 : }
10991 0 : case NC_SHORT:
10992 : {
10993 : short *psTemp =
10994 0 : static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
10995 0 : nc_get_vara_short(nCdfId, nVarId, start, count, psTemp);
10996 : char szTemp[256];
10997 0 : size_t m = 0;
10998 0 : for (; m < nVarLen - 1; m++)
10999 : {
11000 0 : snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
11001 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11002 : }
11003 0 : snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
11004 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11005 0 : CPLFree(psTemp);
11006 0 : break;
11007 : }
11008 19 : case NC_INT:
11009 : {
11010 19 : int *pnTemp = static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
11011 19 : nc_get_vara_int(nCdfId, nVarId, start, count, pnTemp);
11012 : char szTemp[256];
11013 19 : size_t m = 0;
11014 36 : for (; m < nVarLen - 1; m++)
11015 : {
11016 17 : snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
11017 17 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11018 : }
11019 19 : snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
11020 19 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11021 19 : CPLFree(pnTemp);
11022 19 : break;
11023 : }
11024 8 : case NC_FLOAT:
11025 : {
11026 : float *pfTemp =
11027 8 : static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
11028 8 : nc_get_vara_float(nCdfId, nVarId, start, count, pfTemp);
11029 : char szTemp[256];
11030 8 : size_t m = 0;
11031 325 : for (; m < nVarLen - 1; m++)
11032 : {
11033 317 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
11034 317 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11035 : }
11036 8 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
11037 8 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11038 8 : CPLFree(pfTemp);
11039 8 : break;
11040 : }
11041 47 : case NC_DOUBLE:
11042 : {
11043 : double *pdfTemp =
11044 47 : static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
11045 47 : nc_get_vara_double(nCdfId, nVarId, start, count, pdfTemp);
11046 : char szTemp[256];
11047 47 : size_t m = 0;
11048 225 : for (; m < nVarLen - 1; m++)
11049 : {
11050 178 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
11051 178 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11052 : }
11053 47 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
11054 47 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11055 47 : CPLFree(pdfTemp);
11056 47 : break;
11057 : }
11058 0 : case NC_STRING:
11059 : {
11060 : char **ppszTemp =
11061 0 : static_cast<char **>(CPLCalloc(nVarLen, sizeof(char *)));
11062 0 : nc_get_vara_string(nCdfId, nVarId, start, count, ppszTemp);
11063 0 : size_t m = 0;
11064 0 : for (; m < nVarLen - 1; m++)
11065 : {
11066 0 : NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
11067 0 : NCDFSafeStrcat(&pszVarValue, ",", &nVarValueSize);
11068 : }
11069 0 : NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
11070 0 : nc_free_string(nVarLen, ppszTemp);
11071 0 : CPLFree(ppszTemp);
11072 0 : break;
11073 : }
11074 0 : case NC_UBYTE:
11075 : {
11076 : unsigned char *pucTemp;
11077 : pucTemp = static_cast<unsigned char *>(
11078 0 : CPLCalloc(nVarLen, sizeof(unsigned char)));
11079 0 : nc_get_vara_uchar(nCdfId, nVarId, start, count, pucTemp);
11080 : char szTemp[256];
11081 0 : size_t m = 0;
11082 0 : for (; m < nVarLen - 1; m++)
11083 : {
11084 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
11085 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11086 : }
11087 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
11088 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11089 0 : CPLFree(pucTemp);
11090 0 : break;
11091 : }
11092 0 : case NC_USHORT:
11093 : {
11094 : unsigned short *pusTemp;
11095 : pusTemp = static_cast<unsigned short *>(
11096 0 : CPLCalloc(nVarLen, sizeof(unsigned short)));
11097 0 : nc_get_vara_ushort(nCdfId, nVarId, start, count, pusTemp);
11098 : char szTemp[256];
11099 0 : size_t m = 0;
11100 0 : for (; m < nVarLen - 1; m++)
11101 : {
11102 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
11103 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11104 : }
11105 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
11106 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11107 0 : CPLFree(pusTemp);
11108 0 : break;
11109 : }
11110 0 : case NC_UINT:
11111 : {
11112 : unsigned int *punTemp;
11113 : punTemp = static_cast<unsigned int *>(
11114 0 : CPLCalloc(nVarLen, sizeof(unsigned int)));
11115 0 : nc_get_vara_uint(nCdfId, nVarId, start, count, punTemp);
11116 : char szTemp[256];
11117 0 : size_t m = 0;
11118 0 : for (; m < nVarLen - 1; m++)
11119 : {
11120 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
11121 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11122 : }
11123 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
11124 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11125 0 : CPLFree(punTemp);
11126 0 : break;
11127 : }
11128 1 : case NC_INT64:
11129 : {
11130 : long long *pnTemp =
11131 1 : static_cast<long long *>(CPLCalloc(nVarLen, sizeof(long long)));
11132 1 : nc_get_vara_longlong(nCdfId, nVarId, start, count, pnTemp);
11133 : char szTemp[256];
11134 1 : size_t m = 0;
11135 2 : for (; m < nVarLen - 1; m++)
11136 : {
11137 1 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",", pnTemp[m]);
11138 1 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11139 : }
11140 1 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, pnTemp[m]);
11141 1 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11142 1 : CPLFree(pnTemp);
11143 1 : break;
11144 : }
11145 0 : case NC_UINT64:
11146 : {
11147 : unsigned long long *pnTemp = static_cast<unsigned long long *>(
11148 0 : CPLCalloc(nVarLen, sizeof(unsigned long long)));
11149 0 : nc_get_vara_ulonglong(nCdfId, nVarId, start, count, pnTemp);
11150 : char szTemp[256];
11151 0 : size_t m = 0;
11152 0 : for (; m < nVarLen - 1; m++)
11153 : {
11154 0 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",", pnTemp[m]);
11155 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11156 : }
11157 0 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, pnTemp[m]);
11158 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11159 0 : CPLFree(pnTemp);
11160 0 : break;
11161 : }
11162 0 : default:
11163 0 : CPLDebug("GDAL_netCDF", "NCDFGetVar1D unsupported type %d",
11164 : nVarType);
11165 0 : CPLFree(pszVarValue);
11166 0 : pszVarValue = nullptr;
11167 0 : break;
11168 : }
11169 :
11170 75 : if (pszVarValue != nullptr && nVarLen > 1 && nVarType != NC_CHAR)
11171 40 : NCDFSafeStrcat(&pszVarValue, "}", &nVarValueSize);
11172 :
11173 : /* set return values */
11174 75 : *pszValue = pszVarValue;
11175 :
11176 75 : return CE_None;
11177 : }
11178 :
11179 8 : static CPLErr NCDFPut1DVar(int nCdfId, int nVarId, const char *pszValue)
11180 : {
11181 8 : if (EQUAL(pszValue, ""))
11182 0 : return CE_Failure;
11183 :
11184 : /* get var information */
11185 8 : int nVarDimId = -1;
11186 8 : int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
11187 8 : if (status != NC_NOERR || nVarDimId != 1)
11188 0 : return CE_Failure;
11189 :
11190 8 : status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
11191 8 : if (status != NC_NOERR)
11192 0 : return CE_Failure;
11193 :
11194 8 : nc_type nVarType = NC_CHAR;
11195 8 : status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
11196 8 : if (status != NC_NOERR)
11197 0 : return CE_Failure;
11198 :
11199 8 : size_t nVarLen = 0;
11200 8 : status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
11201 8 : if (status != NC_NOERR)
11202 0 : return CE_Failure;
11203 :
11204 8 : size_t start[1] = {0};
11205 8 : size_t count[1] = {nVarLen};
11206 :
11207 : /* get the values as tokens */
11208 8 : char **papszValues = NCDFTokenizeArray(pszValue);
11209 8 : if (papszValues == nullptr)
11210 0 : return CE_Failure;
11211 :
11212 8 : nVarLen = CSLCount(papszValues);
11213 :
11214 : /* now write the data */
11215 8 : if (nVarType == NC_CHAR)
11216 : {
11217 0 : status = nc_put_vara_text(nCdfId, nVarId, start, count, pszValue);
11218 0 : NCDF_ERR(status);
11219 : }
11220 : else
11221 : {
11222 8 : switch (nVarType)
11223 : {
11224 0 : case NC_BYTE:
11225 : {
11226 : signed char *pscTemp = static_cast<signed char *>(
11227 0 : CPLCalloc(nVarLen, sizeof(signed char)));
11228 0 : for (size_t i = 0; i < nVarLen; i++)
11229 : {
11230 0 : char *pszTemp = nullptr;
11231 0 : pscTemp[i] = static_cast<signed char>(
11232 0 : strtol(papszValues[i], &pszTemp, 10));
11233 : }
11234 : status =
11235 0 : nc_put_vara_schar(nCdfId, nVarId, start, count, pscTemp);
11236 0 : NCDF_ERR(status);
11237 0 : CPLFree(pscTemp);
11238 0 : break;
11239 : }
11240 0 : case NC_SHORT:
11241 : {
11242 : short *psTemp =
11243 0 : static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
11244 0 : for (size_t i = 0; i < nVarLen; i++)
11245 : {
11246 0 : char *pszTemp = nullptr;
11247 0 : psTemp[i] = static_cast<short>(
11248 0 : strtol(papszValues[i], &pszTemp, 10));
11249 : }
11250 : status =
11251 0 : nc_put_vara_short(nCdfId, nVarId, start, count, psTemp);
11252 0 : NCDF_ERR(status);
11253 0 : CPLFree(psTemp);
11254 0 : break;
11255 : }
11256 3 : case NC_INT:
11257 : {
11258 : int *pnTemp =
11259 3 : static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
11260 11 : for (size_t i = 0; i < nVarLen; i++)
11261 : {
11262 8 : char *pszTemp = nullptr;
11263 8 : pnTemp[i] =
11264 8 : static_cast<int>(strtol(papszValues[i], &pszTemp, 10));
11265 : }
11266 3 : status = nc_put_vara_int(nCdfId, nVarId, start, count, pnTemp);
11267 3 : NCDF_ERR(status);
11268 3 : CPLFree(pnTemp);
11269 3 : break;
11270 : }
11271 0 : case NC_FLOAT:
11272 : {
11273 : float *pfTemp =
11274 0 : static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
11275 0 : for (size_t i = 0; i < nVarLen; i++)
11276 : {
11277 0 : char *pszTemp = nullptr;
11278 0 : pfTemp[i] =
11279 0 : static_cast<float>(CPLStrtod(papszValues[i], &pszTemp));
11280 : }
11281 : status =
11282 0 : nc_put_vara_float(nCdfId, nVarId, start, count, pfTemp);
11283 0 : NCDF_ERR(status);
11284 0 : CPLFree(pfTemp);
11285 0 : break;
11286 : }
11287 5 : case NC_DOUBLE:
11288 : {
11289 : double *pdfTemp =
11290 5 : static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
11291 19 : for (size_t i = 0; i < nVarLen; i++)
11292 : {
11293 14 : char *pszTemp = nullptr;
11294 14 : pdfTemp[i] = CPLStrtod(papszValues[i], &pszTemp);
11295 : }
11296 : status =
11297 5 : nc_put_vara_double(nCdfId, nVarId, start, count, pdfTemp);
11298 5 : NCDF_ERR(status);
11299 5 : CPLFree(pdfTemp);
11300 5 : break;
11301 : }
11302 0 : default:
11303 : {
11304 0 : int nTmpFormat = 0;
11305 0 : status = nc_inq_format(nCdfId, &nTmpFormat);
11306 0 : NCDF_ERR(status);
11307 0 : if (nTmpFormat == NCDF_FORMAT_NC4)
11308 : {
11309 0 : switch (nVarType)
11310 : {
11311 0 : case NC_STRING:
11312 : {
11313 : status =
11314 0 : nc_put_vara_string(nCdfId, nVarId, start, count,
11315 : (const char **)papszValues);
11316 0 : NCDF_ERR(status);
11317 0 : break;
11318 : }
11319 0 : case NC_UBYTE:
11320 : {
11321 : unsigned char *pucTemp =
11322 : static_cast<unsigned char *>(
11323 0 : CPLCalloc(nVarLen, sizeof(unsigned char)));
11324 0 : for (size_t i = 0; i < nVarLen; i++)
11325 : {
11326 0 : char *pszTemp = nullptr;
11327 0 : pucTemp[i] = static_cast<unsigned char>(
11328 0 : strtoul(papszValues[i], &pszTemp, 10));
11329 : }
11330 0 : status = nc_put_vara_uchar(nCdfId, nVarId, start,
11331 : count, pucTemp);
11332 0 : NCDF_ERR(status);
11333 0 : CPLFree(pucTemp);
11334 0 : break;
11335 : }
11336 0 : case NC_USHORT:
11337 : {
11338 : unsigned short *pusTemp =
11339 : static_cast<unsigned short *>(
11340 0 : CPLCalloc(nVarLen, sizeof(unsigned short)));
11341 0 : for (size_t i = 0; i < nVarLen; i++)
11342 : {
11343 0 : char *pszTemp = nullptr;
11344 0 : pusTemp[i] = static_cast<unsigned short>(
11345 0 : strtoul(papszValues[i], &pszTemp, 10));
11346 : }
11347 0 : status = nc_put_vara_ushort(nCdfId, nVarId, start,
11348 : count, pusTemp);
11349 0 : NCDF_ERR(status);
11350 0 : CPLFree(pusTemp);
11351 0 : break;
11352 : }
11353 0 : case NC_UINT:
11354 : {
11355 : unsigned int *punTemp = static_cast<unsigned int *>(
11356 0 : CPLCalloc(nVarLen, sizeof(unsigned int)));
11357 0 : for (size_t i = 0; i < nVarLen; i++)
11358 : {
11359 0 : char *pszTemp = nullptr;
11360 0 : punTemp[i] = static_cast<unsigned int>(
11361 0 : strtoul(papszValues[i], &pszTemp, 10));
11362 : }
11363 0 : status = nc_put_vara_uint(nCdfId, nVarId, start,
11364 : count, punTemp);
11365 0 : NCDF_ERR(status);
11366 0 : CPLFree(punTemp);
11367 0 : break;
11368 : }
11369 0 : default:
11370 0 : if (papszValues)
11371 0 : CSLDestroy(papszValues);
11372 0 : return CE_Failure;
11373 : break;
11374 : }
11375 : }
11376 0 : break;
11377 : }
11378 : }
11379 : }
11380 :
11381 8 : if (papszValues)
11382 8 : CSLDestroy(papszValues);
11383 :
11384 8 : return CE_None;
11385 : }
11386 :
11387 : /************************************************************************/
11388 : /* GetDefaultNoDataValue() */
11389 : /************************************************************************/
11390 :
11391 188 : double NCDFGetDefaultNoDataValue(int nCdfId, int nVarId, int nVarType,
11392 : bool &bGotNoData)
11393 :
11394 : {
11395 188 : int nNoFill = 0;
11396 188 : double dfNoData = 0.0;
11397 :
11398 188 : switch (nVarType)
11399 : {
11400 0 : case NC_CHAR:
11401 : case NC_BYTE:
11402 : case NC_UBYTE:
11403 : // Don't do default fill-values for bytes, too risky.
11404 : // This function should not be called in those cases.
11405 0 : CPLAssert(false);
11406 : break;
11407 24 : case NC_SHORT:
11408 : {
11409 24 : short nFillVal = 0;
11410 24 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11411 : NC_NOERR)
11412 : {
11413 24 : if (!nNoFill)
11414 : {
11415 23 : bGotNoData = true;
11416 23 : dfNoData = nFillVal;
11417 : }
11418 : }
11419 : else
11420 0 : dfNoData = NC_FILL_SHORT;
11421 24 : break;
11422 : }
11423 26 : case NC_INT:
11424 : {
11425 26 : int nFillVal = 0;
11426 26 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11427 : NC_NOERR)
11428 : {
11429 26 : if (!nNoFill)
11430 : {
11431 25 : bGotNoData = true;
11432 25 : dfNoData = nFillVal;
11433 : }
11434 : }
11435 : else
11436 0 : dfNoData = NC_FILL_INT;
11437 26 : break;
11438 : }
11439 71 : case NC_FLOAT:
11440 : {
11441 71 : float fFillVal = 0;
11442 71 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &fFillVal) ==
11443 : NC_NOERR)
11444 : {
11445 71 : if (!nNoFill)
11446 : {
11447 67 : bGotNoData = true;
11448 67 : dfNoData = fFillVal;
11449 : }
11450 : }
11451 : else
11452 0 : dfNoData = NC_FILL_FLOAT;
11453 71 : break;
11454 : }
11455 34 : case NC_DOUBLE:
11456 : {
11457 34 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &dfNoData) ==
11458 : NC_NOERR)
11459 : {
11460 34 : if (!nNoFill)
11461 : {
11462 34 : bGotNoData = true;
11463 : }
11464 : }
11465 : else
11466 0 : dfNoData = NC_FILL_DOUBLE;
11467 34 : break;
11468 : }
11469 7 : case NC_USHORT:
11470 : {
11471 7 : unsigned short nFillVal = 0;
11472 7 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11473 : NC_NOERR)
11474 : {
11475 7 : if (!nNoFill)
11476 : {
11477 7 : bGotNoData = true;
11478 7 : dfNoData = nFillVal;
11479 : }
11480 : }
11481 : else
11482 0 : dfNoData = NC_FILL_USHORT;
11483 7 : break;
11484 : }
11485 7 : case NC_UINT:
11486 : {
11487 7 : unsigned int nFillVal = 0;
11488 7 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11489 : NC_NOERR)
11490 : {
11491 7 : if (!nNoFill)
11492 : {
11493 7 : bGotNoData = true;
11494 7 : dfNoData = nFillVal;
11495 : }
11496 : }
11497 : else
11498 0 : dfNoData = NC_FILL_UINT;
11499 7 : break;
11500 : }
11501 19 : default:
11502 19 : dfNoData = 0.0;
11503 19 : break;
11504 : }
11505 :
11506 188 : return dfNoData;
11507 : }
11508 :
11509 : /************************************************************************/
11510 : /* NCDFGetDefaultNoDataValueAsInt64() */
11511 : /************************************************************************/
11512 :
11513 2 : int64_t NCDFGetDefaultNoDataValueAsInt64(int nCdfId, int nVarId,
11514 : bool &bGotNoData)
11515 :
11516 : {
11517 2 : int nNoFill = 0;
11518 2 : long long nFillVal = 0;
11519 2 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
11520 : {
11521 2 : if (!nNoFill)
11522 : {
11523 2 : bGotNoData = true;
11524 2 : return static_cast<int64_t>(nFillVal);
11525 : }
11526 : }
11527 : else
11528 0 : return static_cast<int64_t>(NC_FILL_INT64);
11529 0 : return 0;
11530 : }
11531 :
11532 : /************************************************************************/
11533 : /* NCDFGetDefaultNoDataValueAsUInt64() */
11534 : /************************************************************************/
11535 :
11536 1 : uint64_t NCDFGetDefaultNoDataValueAsUInt64(int nCdfId, int nVarId,
11537 : bool &bGotNoData)
11538 :
11539 : {
11540 1 : int nNoFill = 0;
11541 1 : unsigned long long nFillVal = 0;
11542 1 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
11543 : {
11544 1 : if (!nNoFill)
11545 : {
11546 1 : bGotNoData = true;
11547 1 : return static_cast<uint64_t>(nFillVal);
11548 : }
11549 : }
11550 : else
11551 0 : return static_cast<uint64_t>(NC_FILL_UINT64);
11552 0 : return 0;
11553 : }
11554 :
11555 11059 : static int NCDFDoesVarContainAttribVal(int nCdfId,
11556 : const char *const *papszAttribNames,
11557 : const char *const *papszAttribValues,
11558 : int nVarId, const char *pszVarName,
11559 : bool bStrict = true)
11560 : {
11561 11059 : if (nVarId == -1 && pszVarName != nullptr)
11562 8097 : NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
11563 :
11564 11059 : if (nVarId == -1)
11565 878 : return -1;
11566 :
11567 10181 : bool bFound = false;
11568 47509 : for (int i = 0; !bFound && papszAttribNames != nullptr &&
11569 45251 : papszAttribNames[i] != nullptr;
11570 : i++)
11571 : {
11572 37328 : char *pszTemp = nullptr;
11573 37328 : if (NCDFGetAttr(nCdfId, nVarId, papszAttribNames[i], &pszTemp) ==
11574 53645 : CE_None &&
11575 16317 : pszTemp != nullptr)
11576 : {
11577 16317 : if (bStrict)
11578 : {
11579 16317 : if (EQUAL(pszTemp, papszAttribValues[i]))
11580 2258 : bFound = true;
11581 : }
11582 : else
11583 : {
11584 0 : if (EQUALN(pszTemp, papszAttribValues[i],
11585 : strlen(papszAttribValues[i])))
11586 0 : bFound = true;
11587 : }
11588 16317 : CPLFree(pszTemp);
11589 : }
11590 : }
11591 10181 : return bFound;
11592 : }
11593 :
11594 1952 : static int NCDFDoesVarContainAttribVal2(int nCdfId, const char *papszAttribName,
11595 : const char *const *papszAttribValues,
11596 : int nVarId, const char *pszVarName,
11597 : int bStrict = true)
11598 : {
11599 1952 : if (nVarId == -1 && pszVarName != nullptr)
11600 1569 : NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
11601 :
11602 1952 : if (nVarId == -1)
11603 0 : return -1;
11604 :
11605 1952 : bool bFound = false;
11606 1952 : char *pszTemp = nullptr;
11607 2331 : if (NCDFGetAttr(nCdfId, nVarId, papszAttribName, &pszTemp) != CE_None ||
11608 379 : pszTemp == nullptr)
11609 1573 : return FALSE;
11610 :
11611 7703 : for (int i = 0; !bFound && i < CSLCount(papszAttribValues); i++)
11612 : {
11613 7324 : if (bStrict)
11614 : {
11615 7296 : if (EQUAL(pszTemp, papszAttribValues[i]))
11616 31 : bFound = true;
11617 : }
11618 : else
11619 : {
11620 28 : if (EQUALN(pszTemp, papszAttribValues[i],
11621 : strlen(papszAttribValues[i])))
11622 0 : bFound = true;
11623 : }
11624 : }
11625 :
11626 379 : CPLFree(pszTemp);
11627 :
11628 379 : return bFound;
11629 : }
11630 :
11631 876 : static bool NCDFEqual(const char *papszName, const char *const *papszValues)
11632 : {
11633 876 : if (papszName == nullptr || EQUAL(papszName, ""))
11634 0 : return false;
11635 :
11636 2392 : for (int i = 0; papszValues && papszValues[i]; ++i)
11637 : {
11638 1636 : if (EQUAL(papszName, papszValues[i]))
11639 120 : return true;
11640 : }
11641 :
11642 756 : return false;
11643 : }
11644 :
11645 : // Test that a variable is longitude/latitude coordinate,
11646 : // following CF 4.1 and 4.2.
11647 3775 : bool NCDFIsVarLongitude(int nCdfId, int nVarId, const char *pszVarName)
11648 : {
11649 : // Check for matching attributes.
11650 3775 : int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLongitudeAttribNames,
11651 : papszCFLongitudeAttribValues, nVarId,
11652 : pszVarName);
11653 : // If not found using attributes then check using var name
11654 : // unless GDAL_NETCDF_VERIFY_DIMS=STRICT.
11655 3775 : if (bVal == -1)
11656 : {
11657 280 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11658 : "STRICT"))
11659 280 : bVal = NCDFEqual(pszVarName, papszCFLongitudeVarNames);
11660 : else
11661 0 : bVal = FALSE;
11662 : }
11663 3495 : else if (bVal)
11664 : {
11665 : // Check that the units is not 'm' or '1'. See #6759
11666 789 : char *pszTemp = nullptr;
11667 1169 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11668 380 : pszTemp != nullptr)
11669 : {
11670 380 : if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
11671 97 : bVal = false;
11672 380 : CPLFree(pszTemp);
11673 : }
11674 : }
11675 :
11676 3775 : return CPL_TO_BOOL(bVal);
11677 : }
11678 :
11679 2161 : bool NCDFIsVarLatitude(int nCdfId, int nVarId, const char *pszVarName)
11680 : {
11681 2161 : int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLatitudeAttribNames,
11682 : papszCFLatitudeAttribValues, nVarId,
11683 : pszVarName);
11684 2161 : if (bVal == -1)
11685 : {
11686 163 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11687 : "STRICT"))
11688 163 : bVal = NCDFEqual(pszVarName, papszCFLatitudeVarNames);
11689 : else
11690 0 : bVal = FALSE;
11691 : }
11692 1998 : else if (bVal)
11693 : {
11694 : // Check that the units is not 'm' or '1'. See #6759
11695 543 : char *pszTemp = nullptr;
11696 685 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11697 142 : pszTemp != nullptr)
11698 : {
11699 142 : if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
11700 36 : bVal = false;
11701 142 : CPLFree(pszTemp);
11702 : }
11703 : }
11704 :
11705 2161 : return CPL_TO_BOOL(bVal);
11706 : }
11707 :
11708 2287 : bool NCDFIsVarProjectionX(int nCdfId, int nVarId, const char *pszVarName)
11709 : {
11710 2287 : int bVal = NCDFDoesVarContainAttribVal(
11711 : nCdfId, papszCFProjectionXAttribNames, papszCFProjectionXAttribValues,
11712 : nVarId, pszVarName);
11713 2287 : if (bVal == -1)
11714 : {
11715 274 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11716 : "STRICT"))
11717 274 : bVal = NCDFEqual(pszVarName, papszCFProjectionXVarNames);
11718 : else
11719 0 : bVal = FALSE;
11720 : }
11721 2013 : else if (bVal)
11722 : {
11723 : // Check that the units is not '1'
11724 374 : char *pszTemp = nullptr;
11725 533 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11726 159 : pszTemp != nullptr)
11727 : {
11728 159 : if (EQUAL(pszTemp, "1"))
11729 5 : bVal = false;
11730 159 : CPLFree(pszTemp);
11731 : }
11732 : }
11733 :
11734 2287 : return CPL_TO_BOOL(bVal);
11735 : }
11736 :
11737 1617 : bool NCDFIsVarProjectionY(int nCdfId, int nVarId, const char *pszVarName)
11738 : {
11739 1617 : int bVal = NCDFDoesVarContainAttribVal(
11740 : nCdfId, papszCFProjectionYAttribNames, papszCFProjectionYAttribValues,
11741 : nVarId, pszVarName);
11742 1617 : if (bVal == -1)
11743 : {
11744 159 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11745 : "STRICT"))
11746 159 : bVal = NCDFEqual(pszVarName, papszCFProjectionYVarNames);
11747 : else
11748 0 : bVal = FALSE;
11749 : }
11750 1458 : else if (bVal)
11751 : {
11752 : // Check that the units is not '1'
11753 373 : char *pszTemp = nullptr;
11754 531 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11755 158 : pszTemp != nullptr)
11756 : {
11757 158 : if (EQUAL(pszTemp, "1"))
11758 5 : bVal = false;
11759 158 : CPLFree(pszTemp);
11760 : }
11761 : }
11762 :
11763 1617 : return CPL_TO_BOOL(bVal);
11764 : }
11765 :
11766 : /* test that a variable is a vertical coordinate, following CF 4.3 */
11767 1017 : bool NCDFIsVarVerticalCoord(int nCdfId, int nVarId, const char *pszVarName)
11768 : {
11769 : /* check for matching attributes */
11770 1017 : if (NCDFDoesVarContainAttribVal(nCdfId, papszCFVerticalAttribNames,
11771 : papszCFVerticalAttribValues, nVarId,
11772 1017 : pszVarName))
11773 72 : return true;
11774 : /* check for matching units */
11775 945 : else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
11776 : papszCFVerticalUnitsValues, nVarId,
11777 945 : pszVarName))
11778 31 : return true;
11779 : /* check for matching standard name */
11780 914 : else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_STD_NAME,
11781 : papszCFVerticalStandardNameValues,
11782 914 : nVarId, pszVarName))
11783 0 : return true;
11784 : else
11785 914 : return false;
11786 : }
11787 :
11788 : /* test that a variable is a time coordinate, following CF 4.4 */
11789 202 : bool NCDFIsVarTimeCoord(int nCdfId, int nVarId, const char *pszVarName)
11790 : {
11791 : /* check for matching attributes */
11792 202 : if (NCDFDoesVarContainAttribVal(nCdfId, papszCFTimeAttribNames,
11793 : papszCFTimeAttribValues, nVarId,
11794 202 : pszVarName))
11795 109 : return true;
11796 : /* check for matching units */
11797 93 : else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
11798 : papszCFTimeUnitsValues, nVarId,
11799 93 : pszVarName, false))
11800 0 : return true;
11801 : else
11802 93 : return false;
11803 : }
11804 :
11805 : // Parse a string, and return as a string list.
11806 : // If it an array of the form {a,b}, then tokenize it.
11807 : // Otherwise, return a copy.
11808 188 : static char **NCDFTokenizeArray(const char *pszValue)
11809 : {
11810 188 : if (pszValue == nullptr || EQUAL(pszValue, ""))
11811 53 : return nullptr;
11812 :
11813 135 : char **papszValues = nullptr;
11814 135 : const int nLen = static_cast<int>(strlen(pszValue));
11815 :
11816 135 : if (pszValue[0] == '{' && nLen > 2 && pszValue[nLen - 1] == '}')
11817 : {
11818 41 : char *pszTemp = static_cast<char *>(CPLMalloc((nLen - 2) + 1));
11819 41 : strncpy(pszTemp, pszValue + 1, nLen - 2);
11820 41 : pszTemp[nLen - 2] = '\0';
11821 41 : papszValues = CSLTokenizeString2(pszTemp, ",", CSLT_ALLOWEMPTYTOKENS);
11822 41 : CPLFree(pszTemp);
11823 : }
11824 : else
11825 : {
11826 94 : papszValues = reinterpret_cast<char **>(CPLCalloc(2, sizeof(char *)));
11827 94 : papszValues[0] = CPLStrdup(pszValue);
11828 94 : papszValues[1] = nullptr;
11829 : }
11830 :
11831 135 : return papszValues;
11832 : }
11833 :
11834 : // Open a NetCDF subdataset from full path /group1/group2/.../groupn/var.
11835 : // Leading slash is optional.
11836 410 : static CPLErr NCDFOpenSubDataset(int nCdfId, const char *pszSubdatasetName,
11837 : int *pnGroupId, int *pnVarId)
11838 : {
11839 410 : *pnGroupId = -1;
11840 410 : *pnVarId = -1;
11841 :
11842 : // Open group.
11843 : char *pszGroupFullName =
11844 410 : CPLStrdup(CPLGetPathSafe(pszSubdatasetName).c_str());
11845 : // Add a leading slash if needed.
11846 410 : if (pszGroupFullName[0] != '/')
11847 : {
11848 394 : char *old = pszGroupFullName;
11849 394 : pszGroupFullName = CPLStrdup(CPLSPrintf("/%s", pszGroupFullName));
11850 394 : CPLFree(old);
11851 : }
11852 : // Detect root group.
11853 410 : if (EQUAL(pszGroupFullName, "/"))
11854 : {
11855 394 : *pnGroupId = nCdfId;
11856 394 : CPLFree(pszGroupFullName);
11857 : }
11858 : else
11859 : {
11860 16 : int status = nc_inq_grp_full_ncid(nCdfId, pszGroupFullName, pnGroupId);
11861 16 : CPLFree(pszGroupFullName);
11862 16 : NCDF_ERR_RET(status);
11863 : }
11864 :
11865 : // Open var.
11866 410 : const char *pszVarName = CPLGetFilename(pszSubdatasetName);
11867 410 : NCDF_ERR_RET(nc_inq_varid(*pnGroupId, pszVarName, pnVarId));
11868 :
11869 410 : return CE_None;
11870 : }
11871 :
11872 : // Get all dimensions visible from a given NetCDF (or group) ID and any of
11873 : // its parents.
11874 351 : static CPLErr NCDFGetVisibleDims(int nGroupId, int *pnDims, int **ppanDimIds)
11875 : {
11876 351 : int nDims = 0;
11877 351 : int *panDimIds = nullptr;
11878 351 : NCDF_ERR_RET(nc_inq_dimids(nGroupId, &nDims, nullptr, true));
11879 :
11880 351 : panDimIds = static_cast<int *>(CPLMalloc(nDims * sizeof(int)));
11881 :
11882 351 : int status = nc_inq_dimids(nGroupId, nullptr, panDimIds, true);
11883 351 : if (status != NC_NOERR)
11884 0 : CPLFree(panDimIds);
11885 351 : NCDF_ERR_RET(status);
11886 :
11887 351 : *pnDims = nDims;
11888 351 : *ppanDimIds = panDimIds;
11889 :
11890 351 : return CE_None;
11891 : }
11892 :
11893 : // Get direct sub-groups IDs of a given NetCDF (or group) ID.
11894 : // Consider only direct children, does not get children of children.
11895 3045 : static CPLErr NCDFGetSubGroups(int nGroupId, int *pnSubGroups,
11896 : int **ppanSubGroupIds)
11897 : {
11898 3045 : *pnSubGroups = 0;
11899 3045 : *ppanSubGroupIds = nullptr;
11900 :
11901 : int nSubGroups;
11902 3045 : NCDF_ERR_RET(nc_inq_grps(nGroupId, &nSubGroups, nullptr));
11903 : int *panSubGroupIds =
11904 3045 : static_cast<int *>(CPLMalloc(nSubGroups * sizeof(int)));
11905 3045 : NCDF_ERR_RET(nc_inq_grps(nGroupId, nullptr, panSubGroupIds));
11906 3045 : *pnSubGroups = nSubGroups;
11907 3045 : *ppanSubGroupIds = panSubGroupIds;
11908 :
11909 3045 : return CE_None;
11910 : }
11911 :
11912 : // Get the full name of a given NetCDF (or group) ID
11913 : // (e.g. /group1/group2/.../groupn).
11914 : // bNC3Compat remove the leading slash for top-level variables for
11915 : // backward compatibility (top-level variables are the ones in the root group).
11916 15437 : static CPLErr NCDFGetGroupFullName(int nGroupId, char **ppszFullName,
11917 : bool bNC3Compat)
11918 : {
11919 15437 : *ppszFullName = nullptr;
11920 :
11921 : size_t nFullNameLen;
11922 15437 : NCDF_ERR_RET(nc_inq_grpname_len(nGroupId, &nFullNameLen));
11923 15437 : *ppszFullName =
11924 15437 : static_cast<char *>(CPLMalloc((nFullNameLen + 1) * sizeof(char)));
11925 15437 : int status = nc_inq_grpname_full(nGroupId, &nFullNameLen, *ppszFullName);
11926 15437 : if (status != NC_NOERR)
11927 : {
11928 0 : CPLFree(*ppszFullName);
11929 0 : *ppszFullName = nullptr;
11930 0 : NCDF_ERR_RET(status);
11931 : }
11932 :
11933 15437 : if (bNC3Compat && EQUAL(*ppszFullName, "/"))
11934 7924 : (*ppszFullName)[0] = '\0';
11935 :
11936 15437 : return CE_None;
11937 : }
11938 :
11939 7325 : CPLString NCDFGetGroupFullName(int nGroupId)
11940 : {
11941 7325 : char *pszFullname = nullptr;
11942 7325 : NCDFGetGroupFullName(nGroupId, &pszFullname, false);
11943 7325 : CPLString osRet(pszFullname ? pszFullname : "");
11944 7325 : CPLFree(pszFullname);
11945 14650 : return osRet;
11946 : }
11947 :
11948 : // Get the full name of a given NetCDF variable ID
11949 : // (e.g. /group1/group2/.../groupn/var).
11950 : // Handle also NC_GLOBAL as nVarId.
11951 : // bNC3Compat remove the leading slash for top-level variables for
11952 : // backward compatibility (top-level variables are the ones in the root group).
11953 8063 : static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId, char **ppszFullName,
11954 : bool bNC3Compat)
11955 : {
11956 8063 : *ppszFullName = nullptr;
11957 8063 : char *pszGroupFullName = nullptr;
11958 8063 : ERR_RET(NCDFGetGroupFullName(nGroupId, &pszGroupFullName, bNC3Compat));
11959 : char szVarName[NC_MAX_NAME + 1];
11960 8063 : if (nVarId == NC_GLOBAL)
11961 : {
11962 1090 : strcpy(szVarName, "NC_GLOBAL");
11963 : }
11964 : else
11965 : {
11966 6973 : int status = nc_inq_varname(nGroupId, nVarId, szVarName);
11967 6973 : if (status != NC_NOERR)
11968 : {
11969 0 : CPLFree(pszGroupFullName);
11970 0 : NCDF_ERR_RET(status);
11971 : }
11972 : }
11973 8063 : const char *pszSep = "/";
11974 8063 : if (EQUAL(pszGroupFullName, "/") || EQUAL(pszGroupFullName, ""))
11975 7877 : pszSep = "";
11976 8063 : *ppszFullName =
11977 8063 : CPLStrdup(CPLSPrintf("%s%s%s", pszGroupFullName, pszSep, szVarName));
11978 8063 : CPLFree(pszGroupFullName);
11979 8063 : return CE_None;
11980 : }
11981 :
11982 : // Get the NetCDF root group ID of a given group ID.
11983 0 : static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId)
11984 : {
11985 0 : *pnRootGroupId = -1;
11986 : // Recurse on parent group.
11987 : int nParentGroupId;
11988 0 : int status = nc_inq_grp_parent(nStartGroupId, &nParentGroupId);
11989 0 : if (status == NC_NOERR)
11990 0 : return NCDFGetRootGroup(nParentGroupId, pnRootGroupId);
11991 0 : else if (status != NC_ENOGRP)
11992 0 : NCDF_ERR_RET(status);
11993 : else // No more parent group.
11994 : {
11995 0 : *pnRootGroupId = nStartGroupId;
11996 : }
11997 :
11998 0 : return CE_None;
11999 : }
12000 :
12001 : // Implementation of NCDFResolveVar/Att.
12002 13153 : static CPLErr NCDFResolveElem(int nStartGroupId, const char *pszVar,
12003 : const char *pszAtt, int *pnGroupId, int *pnId,
12004 : bool bMandatory)
12005 : {
12006 13153 : if (!pszVar && !pszAtt)
12007 : {
12008 0 : CPLError(CE_Failure, CPLE_IllegalArg,
12009 : "pszVar and pszAtt NCDFResolveElem() args are both null.");
12010 0 : return CE_Failure;
12011 : }
12012 :
12013 : enum
12014 : {
12015 : NCRM_PARENT,
12016 : NCRM_WIDTH_WISE
12017 13153 : } eNCResolveMode = NCRM_PARENT;
12018 :
12019 26306 : std::queue<int> aoQueueGroupIdsToVisit;
12020 13153 : aoQueueGroupIdsToVisit.push(nStartGroupId);
12021 :
12022 14822 : while (!aoQueueGroupIdsToVisit.empty())
12023 : {
12024 : // Get the first group of the FIFO queue.
12025 13300 : *pnGroupId = aoQueueGroupIdsToVisit.front();
12026 13300 : aoQueueGroupIdsToVisit.pop();
12027 :
12028 : // Look if this group contains the searched element.
12029 : int status;
12030 13300 : if (pszVar)
12031 13081 : status = nc_inq_varid(*pnGroupId, pszVar, pnId);
12032 : else // pszAtt != nullptr.
12033 219 : status = nc_inq_attid(*pnGroupId, NC_GLOBAL, pszAtt, pnId);
12034 :
12035 13300 : if (status == NC_NOERR)
12036 : {
12037 11631 : return CE_None;
12038 : }
12039 1669 : else if ((pszVar && status != NC_ENOTVAR) ||
12040 216 : (pszAtt && status != NC_ENOTATT))
12041 : {
12042 0 : NCDF_ERR(status);
12043 : }
12044 : // Element not found, in NC4 case we must search in other groups
12045 : // following the CF logic.
12046 :
12047 : // The first resolve mode consists to search on parent groups.
12048 1669 : if (eNCResolveMode == NCRM_PARENT)
12049 : {
12050 1569 : int nParentGroupId = -1;
12051 1569 : int status2 = nc_inq_grp_parent(*pnGroupId, &nParentGroupId);
12052 1569 : if (status2 == NC_NOERR)
12053 45 : aoQueueGroupIdsToVisit.push(nParentGroupId);
12054 1524 : else if (status2 != NC_ENOGRP)
12055 0 : NCDF_ERR(status2);
12056 1524 : else if (pszVar)
12057 : // When resolving a variable, if there is no more
12058 : // parent group then we switch to width-wise search mode
12059 : // starting from the latest found parent group.
12060 1311 : eNCResolveMode = NCRM_WIDTH_WISE;
12061 : }
12062 :
12063 : // The second resolve mode is a width-wise search.
12064 1669 : if (eNCResolveMode == NCRM_WIDTH_WISE)
12065 : {
12066 : // Enqueue all direct sub-groups.
12067 1411 : int nSubGroups = 0;
12068 1411 : int *panSubGroupIds = nullptr;
12069 1411 : NCDFGetSubGroups(*pnGroupId, &nSubGroups, &panSubGroupIds);
12070 1513 : for (int i = 0; i < nSubGroups; i++)
12071 102 : aoQueueGroupIdsToVisit.push(panSubGroupIds[i]);
12072 1411 : CPLFree(panSubGroupIds);
12073 : }
12074 : }
12075 :
12076 1522 : if (bMandatory)
12077 : {
12078 0 : char *pszStartGroupFullName = nullptr;
12079 0 : NCDFGetGroupFullName(nStartGroupId, &pszStartGroupFullName);
12080 0 : CPLError(CE_Failure, CPLE_AppDefined,
12081 : "Cannot resolve mandatory %s %s from group %s",
12082 : (pszVar ? pszVar : pszAtt),
12083 : (pszVar ? "variable" : "attribute"),
12084 0 : (pszStartGroupFullName ? pszStartGroupFullName : ""));
12085 0 : CPLFree(pszStartGroupFullName);
12086 : }
12087 :
12088 1522 : *pnGroupId = -1;
12089 1522 : *pnId = -1;
12090 1522 : return CE_Failure;
12091 : }
12092 :
12093 : // Resolve a variable name from a given starting group following the CF logic:
12094 : // - if var name is an absolute path then directly open it
12095 : // - first search in the starting group and its parent groups
12096 : // - then if there is no more parent group we switch to a width-wise search
12097 : // mode starting from the latest found parent group.
12098 : // The full CF logic is described here:
12099 : // https://github.com/diwg/cf2/blob/master/group/cf2-group.adoc#scope
12100 : // If bMandatory then print an error if resolving fails.
12101 : // TODO: implement support of relative paths.
12102 : // TODO: to follow strictly the CF logic, when searching for a coordinate
12103 : // variable, we must stop the parent search mode once the corresponding
12104 : // dimension is found and start the width-wise search from this group.
12105 : // TODO: to follow strictly the CF logic, when searching in width-wise mode
12106 : // we should skip every groups already visited during the parent
12107 : // search mode (but revisiting them should have no impact so we could
12108 : // let as it is if it is simpler...)
12109 : // TODO: CF specifies that the width-wise search order is "left-to-right" so
12110 : // maybe we must sort sibling groups alphabetically? but maybe not
12111 : // necessary if nc_inq_grps() already sort them?
12112 12937 : CPLErr NCDFResolveVar(int nStartGroupId, const char *pszVar, int *pnGroupId,
12113 : int *pnVarId, bool bMandatory)
12114 : {
12115 12937 : *pnGroupId = -1;
12116 12937 : *pnVarId = -1;
12117 12937 : int nGroupId = nStartGroupId, nVarId;
12118 12937 : if (pszVar[0] == '/')
12119 : {
12120 : // This is an absolute path: we can open the var directly.
12121 : int nRootGroupId;
12122 0 : ERR_RET(NCDFGetRootGroup(nStartGroupId, &nRootGroupId));
12123 0 : ERR_RET(NCDFOpenSubDataset(nRootGroupId, pszVar, &nGroupId, &nVarId));
12124 : }
12125 : else
12126 : {
12127 : // We have to search the variable following the CF logic.
12128 12937 : ERR_RET(NCDFResolveElem(nStartGroupId, pszVar, nullptr, &nGroupId,
12129 : &nVarId, bMandatory));
12130 : }
12131 11628 : *pnGroupId = nGroupId;
12132 11628 : *pnVarId = nVarId;
12133 11628 : return CE_None;
12134 : }
12135 :
12136 : // Like NCDFResolveVar but returns directly the var full name.
12137 1380 : static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,
12138 : char **ppszFullName, bool bMandatory)
12139 : {
12140 1380 : *ppszFullName = nullptr;
12141 : int nGroupId, nVarId;
12142 1380 : ERR_RET(
12143 : NCDFResolveVar(nStartGroupId, pszVar, &nGroupId, &nVarId, bMandatory));
12144 1354 : return NCDFGetVarFullName(nGroupId, nVarId, ppszFullName);
12145 : }
12146 :
12147 : // Like NCDFResolveVar but resolves an attribute instead a variable and
12148 : // returns its integer value.
12149 : // Only GLOBAL attributes are supported for the moment.
12150 216 : static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,
12151 : const char *pszAtt, int *pnAtt, bool bMandatory)
12152 : {
12153 216 : int nGroupId = nStartGroupId, nAttId = nStartVarId;
12154 216 : ERR_RET(NCDFResolveElem(nStartGroupId, nullptr, pszAtt, &nGroupId, &nAttId,
12155 : bMandatory));
12156 3 : NCDF_ERR_RET(nc_get_att_int(nGroupId, NC_GLOBAL, pszAtt, pnAtt));
12157 3 : return CE_None;
12158 : }
12159 :
12160 : // Filter variables to keep only valid 2+D raster bands and vector fields in
12161 : // a given a NetCDF (or group) ID and its sub-groups.
12162 : // Coordinate or boundary variables are ignored.
12163 : // It also creates corresponding vector layers.
12164 523 : CPLErr netCDFDataset::FilterVars(
12165 : int nCdfId, bool bKeepRasters, bool bKeepVectors, char **papszIgnoreVars,
12166 : int *pnRasterVars, int *pnGroupId, int *pnVarId, int *pnIgnoredVars,
12167 : std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>
12168 : &oMap2DDimsToGroupAndVar)
12169 : {
12170 523 : int nVars = 0;
12171 523 : int nRasterVars = 0;
12172 523 : NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
12173 :
12174 1046 : std::vector<int> anPotentialVectorVarID;
12175 : // oMapDimIdToCount[x] = number of times dim x is the first dimension of
12176 : // potential vector variables
12177 1046 : std::map<int, int> oMapDimIdToCount;
12178 523 : int nVarXId = -1;
12179 523 : int nVarYId = -1;
12180 523 : int nVarZId = -1;
12181 523 : int nVarTimeId = -1;
12182 523 : int nVarTimeDimId = -1;
12183 523 : bool bIsVectorOnly = true;
12184 523 : int nProfileDimId = -1;
12185 523 : int nParentIndexVarID = -1;
12186 :
12187 3187 : for (int v = 0; v < nVars; v++)
12188 : {
12189 : int nVarDims;
12190 2664 : NCDF_ERR_RET(nc_inq_varndims(nCdfId, v, &nVarDims));
12191 : // Should we ignore this variable?
12192 : char szTemp[NC_MAX_NAME + 1];
12193 2664 : szTemp[0] = '\0';
12194 2664 : NCDF_ERR_RET(nc_inq_varname(nCdfId, v, szTemp));
12195 :
12196 2664 : if (strstr(szTemp, "_node_coordinates") ||
12197 2664 : strstr(szTemp, "_node_count"))
12198 : {
12199 : // Ignore CF-1.8 Simple Geometries helper variables
12200 69 : continue;
12201 : }
12202 :
12203 3886 : if (nVarDims == 1 && (NCDFIsVarLongitude(nCdfId, -1, szTemp) ||
12204 1291 : NCDFIsVarProjectionX(nCdfId, -1, szTemp)))
12205 : {
12206 358 : nVarXId = v;
12207 : }
12208 3170 : else if (nVarDims == 1 && (NCDFIsVarLatitude(nCdfId, -1, szTemp) ||
12209 933 : NCDFIsVarProjectionY(nCdfId, -1, szTemp)))
12210 : {
12211 358 : nVarYId = v;
12212 : }
12213 1879 : else if (nVarDims == 1 && NCDFIsVarVerticalCoord(nCdfId, -1, szTemp))
12214 : {
12215 78 : nVarZId = v;
12216 : }
12217 : else
12218 : {
12219 1801 : char *pszVarFullName = nullptr;
12220 1801 : CPLErr eErr = NCDFGetVarFullName(nCdfId, v, &pszVarFullName);
12221 1801 : if (eErr != CE_None)
12222 : {
12223 0 : CPLFree(pszVarFullName);
12224 0 : continue;
12225 : }
12226 : bool bIgnoreVar =
12227 1801 : (CSLFindString(papszIgnoreVars, pszVarFullName) != -1);
12228 1801 : CPLFree(pszVarFullName);
12229 1801 : if (bIgnoreVar)
12230 : {
12231 102 : if (nVarDims == 1 && NCDFIsVarTimeCoord(nCdfId, -1, szTemp))
12232 : {
12233 11 : nVarTimeId = v;
12234 11 : nc_inq_vardimid(nCdfId, v, &nVarTimeDimId);
12235 : }
12236 91 : else if (nVarDims > 1)
12237 : {
12238 87 : (*pnIgnoredVars)++;
12239 87 : CPLDebug("GDAL_netCDF", "variable #%d [%s] was ignored", v,
12240 : szTemp);
12241 : }
12242 : }
12243 : // Only accept 2+D vars.
12244 1699 : else if (nVarDims >= 2)
12245 : {
12246 680 : bool bRasterCandidate = true;
12247 : // Identify variables that might be vector variables
12248 680 : if (nVarDims == 2)
12249 : {
12250 609 : int anDimIds[2] = {-1, -1};
12251 609 : nc_inq_vardimid(nCdfId, v, anDimIds);
12252 :
12253 609 : nc_type vartype = NC_NAT;
12254 609 : nc_inq_vartype(nCdfId, v, &vartype);
12255 :
12256 : char szDimNameFirst[NC_MAX_NAME + 1];
12257 : char szDimNameSecond[NC_MAX_NAME + 1];
12258 609 : szDimNameFirst[0] = '\0';
12259 609 : szDimNameSecond[0] = '\0';
12260 1374 : if (vartype == NC_CHAR &&
12261 156 : nc_inq_dimname(nCdfId, anDimIds[0], szDimNameFirst) ==
12262 156 : NC_NOERR &&
12263 156 : nc_inq_dimname(nCdfId, anDimIds[1], szDimNameSecond) ==
12264 156 : NC_NOERR &&
12265 156 : !NCDFIsVarLongitude(nCdfId, -1, szDimNameSecond) &&
12266 156 : !NCDFIsVarProjectionX(nCdfId, -1, szDimNameSecond) &&
12267 921 : !NCDFIsVarLatitude(nCdfId, -1, szDimNameFirst) &&
12268 156 : !NCDFIsVarProjectionY(nCdfId, -1, szDimNameFirst))
12269 : {
12270 156 : anPotentialVectorVarID.push_back(v);
12271 156 : oMapDimIdToCount[anDimIds[0]]++;
12272 156 : if (strstr(szDimNameSecond, "_max_width"))
12273 : {
12274 127 : bRasterCandidate = false;
12275 : }
12276 : else
12277 : {
12278 29 : std::array<int, 3> oKey{anDimIds[0], anDimIds[1],
12279 29 : vartype};
12280 29 : oMap2DDimsToGroupAndVar[oKey].emplace_back(
12281 29 : std::pair(nCdfId, v));
12282 : }
12283 : }
12284 : else
12285 : {
12286 453 : std::array<int, 3> oKey{anDimIds[0], anDimIds[1],
12287 453 : vartype};
12288 453 : oMap2DDimsToGroupAndVar[oKey].emplace_back(
12289 453 : std::pair(nCdfId, v));
12290 453 : bIsVectorOnly = false;
12291 : }
12292 : }
12293 : else
12294 : {
12295 71 : bIsVectorOnly = false;
12296 : }
12297 680 : if (bKeepRasters && bRasterCandidate)
12298 : {
12299 524 : *pnGroupId = nCdfId;
12300 524 : *pnVarId = v;
12301 524 : nRasterVars++;
12302 : }
12303 : }
12304 1019 : else if (nVarDims == 1)
12305 : {
12306 725 : nc_type atttype = NC_NAT;
12307 725 : size_t attlen = 0;
12308 725 : if (nc_inq_att(nCdfId, v, "instance_dimension", &atttype,
12309 14 : &attlen) == NC_NOERR &&
12310 725 : atttype == NC_CHAR && attlen < NC_MAX_NAME)
12311 : {
12312 : char szInstanceDimension[NC_MAX_NAME + 1];
12313 14 : if (nc_get_att_text(nCdfId, v, "instance_dimension",
12314 14 : szInstanceDimension) == NC_NOERR)
12315 : {
12316 14 : szInstanceDimension[attlen] = 0;
12317 14 : int status = nc_inq_dimid(nCdfId, szInstanceDimension,
12318 : &nProfileDimId);
12319 14 : if (status == NC_NOERR)
12320 14 : nParentIndexVarID = v;
12321 : else
12322 0 : nProfileDimId = -1;
12323 14 : if (status == NC_EBADDIM)
12324 0 : CPLError(CE_Warning, CPLE_AppDefined,
12325 : "Attribute instance_dimension='%s' refers "
12326 : "to a non existing dimension",
12327 : szInstanceDimension);
12328 : else
12329 14 : NCDF_ERR(status);
12330 : }
12331 : }
12332 725 : if (v != nParentIndexVarID)
12333 : {
12334 711 : anPotentialVectorVarID.push_back(v);
12335 711 : int nDimId = -1;
12336 711 : nc_inq_vardimid(nCdfId, v, &nDimId);
12337 711 : oMapDimIdToCount[nDimId]++;
12338 : }
12339 : }
12340 : }
12341 : }
12342 :
12343 : // If we are opened in raster-only mode and that there are only 1D or 2D
12344 : // variables and that the 2D variables have no X/Y dim, and all
12345 : // variables refer to the same main dimension (or 2 dimensions for
12346 : // featureType=profile), then it is a pure vector dataset
12347 : CPLString osFeatureType(
12348 523 : CSLFetchNameValueDef(papszMetadata, "NC_GLOBAL#featureType", ""));
12349 412 : if (bKeepRasters && !bKeepVectors && bIsVectorOnly && nRasterVars > 0 &&
12350 935 : !anPotentialVectorVarID.empty() &&
12351 0 : (oMapDimIdToCount.size() == 1 ||
12352 0 : (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2 &&
12353 0 : nProfileDimId >= 0)))
12354 : {
12355 0 : anPotentialVectorVarID.resize(0);
12356 : }
12357 : else
12358 : {
12359 523 : *pnRasterVars += nRasterVars;
12360 : }
12361 :
12362 523 : if (!anPotentialVectorVarID.empty() && bKeepVectors)
12363 : {
12364 : // Take the dimension that is referenced the most times.
12365 64 : if (!(oMapDimIdToCount.size() == 1 ||
12366 27 : (EQUAL(osFeatureType, "profile") &&
12367 26 : oMapDimIdToCount.size() == 2 && nProfileDimId >= 0)))
12368 : {
12369 1 : CPLError(CE_Warning, CPLE_AppDefined,
12370 : "The dataset has several variables that could be "
12371 : "identified as vector fields, but not all share the same "
12372 : "primary dimension. Consequently they will be ignored.");
12373 : }
12374 : else
12375 : {
12376 50 : if (nVarTimeId >= 0 &&
12377 50 : oMapDimIdToCount.find(nVarTimeDimId) != oMapDimIdToCount.end())
12378 : {
12379 1 : anPotentialVectorVarID.push_back(nVarTimeId);
12380 : }
12381 49 : CreateGrpVectorLayers(nCdfId, osFeatureType, anPotentialVectorVarID,
12382 : oMapDimIdToCount, nVarXId, nVarYId, nVarZId,
12383 : nProfileDimId, nParentIndexVarID,
12384 : bKeepRasters);
12385 : }
12386 : }
12387 :
12388 : // Recurse on sub-groups.
12389 523 : int nSubGroups = 0;
12390 523 : int *panSubGroupIds = nullptr;
12391 523 : NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
12392 555 : for (int i = 0; i < nSubGroups; i++)
12393 : {
12394 32 : FilterVars(panSubGroupIds[i], bKeepRasters, bKeepVectors,
12395 : papszIgnoreVars, pnRasterVars, pnGroupId, pnVarId,
12396 : pnIgnoredVars, oMap2DDimsToGroupAndVar);
12397 : }
12398 523 : CPLFree(panSubGroupIds);
12399 :
12400 523 : return CE_None;
12401 : }
12402 :
12403 : // Create vector layers from given potentially identified vector variables
12404 : // resulting from the scanning of a NetCDF (or group) ID.
12405 49 : CPLErr netCDFDataset::CreateGrpVectorLayers(
12406 : int nCdfId, const CPLString &osFeatureType,
12407 : const std::vector<int> &anPotentialVectorVarID,
12408 : const std::map<int, int> &oMapDimIdToCount, int nVarXId, int nVarYId,
12409 : int nVarZId, int nProfileDimId, int nParentIndexVarID, bool bKeepRasters)
12410 : {
12411 49 : char *pszGroupName = nullptr;
12412 49 : NCDFGetGroupFullName(nCdfId, &pszGroupName);
12413 49 : if (pszGroupName == nullptr || pszGroupName[0] == '\0')
12414 : {
12415 47 : CPLFree(pszGroupName);
12416 47 : pszGroupName = CPLStrdup(CPLGetBasenameSafe(osFilename).c_str());
12417 : }
12418 49 : OGRwkbGeometryType eGType = wkbUnknown;
12419 : CPLString osLayerName = CSLFetchNameValueDef(
12420 98 : papszMetadata, "NC_GLOBAL#ogr_layer_name", pszGroupName);
12421 49 : CPLFree(pszGroupName);
12422 49 : papszMetadata =
12423 49 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_name", nullptr);
12424 :
12425 49 : if (EQUAL(osFeatureType, "point") || EQUAL(osFeatureType, "profile"))
12426 : {
12427 33 : papszMetadata =
12428 33 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#featureType", nullptr);
12429 33 : eGType = wkbPoint;
12430 : }
12431 :
12432 : const char *pszLayerType =
12433 49 : CSLFetchNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_type");
12434 49 : if (pszLayerType != nullptr)
12435 : {
12436 9 : eGType = OGRFromOGCGeomType(pszLayerType);
12437 9 : papszMetadata =
12438 9 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_type", nullptr);
12439 : }
12440 :
12441 : CPLString osGeometryField =
12442 98 : CSLFetchNameValueDef(papszMetadata, "NC_GLOBAL#ogr_geometry_field", "");
12443 49 : papszMetadata =
12444 49 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_geometry_field", nullptr);
12445 :
12446 49 : int nFirstVarId = -1;
12447 49 : int nVectorDim = oMapDimIdToCount.rbegin()->first;
12448 49 : if (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2)
12449 : {
12450 13 : if (nVectorDim == nProfileDimId)
12451 0 : nVectorDim = oMapDimIdToCount.begin()->first;
12452 : }
12453 : else
12454 : {
12455 36 : nProfileDimId = -1;
12456 : }
12457 62 : for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)
12458 : {
12459 62 : int anDimIds[2] = {-1, -1};
12460 62 : nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);
12461 62 : if (nVectorDim == anDimIds[0])
12462 : {
12463 49 : nFirstVarId = anPotentialVectorVarID[j];
12464 49 : break;
12465 : }
12466 : }
12467 :
12468 : // In case where coordinates are explicitly specified for one of the
12469 : // field/variable, use them in priority over the ones that might have been
12470 : // identified above.
12471 49 : char *pszCoordinates = nullptr;
12472 49 : if (NCDFGetAttr(nCdfId, nFirstVarId, "coordinates", &pszCoordinates) ==
12473 : CE_None)
12474 : {
12475 34 : char **papszTokens = NCDFTokenizeCoordinatesAttribute(pszCFCoordinates);
12476 34 : for (int i = 0; papszTokens != nullptr && papszTokens[i] != nullptr;
12477 : i++)
12478 : {
12479 0 : if (NCDFIsVarLongitude(nCdfId, -1, papszTokens[i]) ||
12480 0 : NCDFIsVarProjectionX(nCdfId, -1, papszTokens[i]))
12481 : {
12482 0 : nVarXId = -1;
12483 0 : CPL_IGNORE_RET_VAL(
12484 0 : nc_inq_varid(nCdfId, papszTokens[i], &nVarXId));
12485 : }
12486 0 : else if (NCDFIsVarLatitude(nCdfId, -1, papszTokens[i]) ||
12487 0 : NCDFIsVarProjectionY(nCdfId, -1, papszTokens[i]))
12488 : {
12489 0 : nVarYId = -1;
12490 0 : CPL_IGNORE_RET_VAL(
12491 0 : nc_inq_varid(nCdfId, papszTokens[i], &nVarYId));
12492 : }
12493 0 : else if (NCDFIsVarVerticalCoord(nCdfId, -1, papszTokens[i]))
12494 : {
12495 0 : nVarZId = -1;
12496 0 : CPL_IGNORE_RET_VAL(
12497 0 : nc_inq_varid(nCdfId, papszTokens[i], &nVarZId));
12498 : }
12499 : }
12500 34 : CSLDestroy(papszTokens);
12501 : }
12502 49 : CPLFree(pszCoordinates);
12503 :
12504 : // Check that the X,Y,Z vars share 1D and share the same dimension as
12505 : // attribute variables.
12506 49 : if (nVarXId >= 0 && nVarYId >= 0)
12507 : {
12508 38 : int nVarDimCount = -1;
12509 38 : int nVarDimId = -1;
12510 38 : if (nc_inq_varndims(nCdfId, nVarXId, &nVarDimCount) != NC_NOERR ||
12511 38 : nVarDimCount != 1 ||
12512 38 : nc_inq_vardimid(nCdfId, nVarXId, &nVarDimId) != NC_NOERR ||
12513 38 : nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim) ||
12514 35 : nc_inq_varndims(nCdfId, nVarYId, &nVarDimCount) != NC_NOERR ||
12515 35 : nVarDimCount != 1 ||
12516 111 : nc_inq_vardimid(nCdfId, nVarYId, &nVarDimId) != NC_NOERR ||
12517 35 : nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim))
12518 : {
12519 3 : nVarXId = nVarYId = -1;
12520 : }
12521 69 : else if (nVarZId >= 0 &&
12522 34 : (nc_inq_varndims(nCdfId, nVarZId, &nVarDimCount) != NC_NOERR ||
12523 34 : nVarDimCount != 1 ||
12524 34 : nc_inq_vardimid(nCdfId, nVarZId, &nVarDimId) != NC_NOERR ||
12525 34 : nVarDimId != nVectorDim))
12526 : {
12527 0 : nVarZId = -1;
12528 : }
12529 : }
12530 :
12531 49 : if (eGType == wkbUnknown && nVarXId >= 0 && nVarYId >= 0)
12532 : {
12533 2 : eGType = wkbPoint;
12534 : }
12535 49 : if (eGType == wkbPoint && nVarXId >= 0 && nVarYId >= 0 && nVarZId >= 0)
12536 : {
12537 34 : eGType = wkbPoint25D;
12538 : }
12539 49 : if (eGType == wkbUnknown && osGeometryField.empty())
12540 : {
12541 5 : eGType = wkbNone;
12542 : }
12543 :
12544 : // Read projection info
12545 49 : char **papszMetadataBackup = CSLDuplicate(papszMetadata);
12546 49 : ReadAttributes(nCdfId, nFirstVarId);
12547 49 : if (!this->bSGSupport)
12548 49 : SetProjectionFromVar(nCdfId, nFirstVarId, true);
12549 49 : const char *pszValue = FetchAttr(nCdfId, nFirstVarId, CF_GRD_MAPPING);
12550 49 : char *pszGridMapping = (pszValue ? CPLStrdup(pszValue) : nullptr);
12551 49 : CSLDestroy(papszMetadata);
12552 49 : papszMetadata = papszMetadataBackup;
12553 :
12554 49 : OGRSpatialReference *poSRS = nullptr;
12555 49 : if (!m_oSRS.IsEmpty())
12556 : {
12557 21 : poSRS = m_oSRS.Clone();
12558 : }
12559 : // Reset if there's a 2D raster
12560 49 : m_bHasProjection = false;
12561 49 : m_bHasGeoTransform = false;
12562 :
12563 49 : if (!bKeepRasters)
12564 : {
12565 : // Strip out uninteresting metadata.
12566 45 : papszMetadata =
12567 45 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#Conventions", nullptr);
12568 45 : papszMetadata =
12569 45 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#GDAL", nullptr);
12570 45 : papszMetadata =
12571 45 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#history", nullptr);
12572 : }
12573 :
12574 : std::shared_ptr<netCDFLayer> poLayer(
12575 49 : new netCDFLayer(this, nCdfId, osLayerName, eGType, poSRS));
12576 49 : if (poSRS != nullptr)
12577 21 : poSRS->Release();
12578 49 : poLayer->SetRecordDimID(nVectorDim);
12579 49 : if (wkbFlatten(eGType) == wkbPoint && nVarXId >= 0 && nVarYId >= 0)
12580 : {
12581 35 : poLayer->SetXYZVars(nVarXId, nVarYId, nVarZId);
12582 : }
12583 14 : else if (!osGeometryField.empty())
12584 : {
12585 9 : poLayer->SetWKTGeometryField(osGeometryField);
12586 : }
12587 49 : if (pszGridMapping != nullptr)
12588 : {
12589 21 : poLayer->SetGridMapping(pszGridMapping);
12590 21 : CPLFree(pszGridMapping);
12591 : }
12592 49 : poLayer->SetProfile(nProfileDimId, nParentIndexVarID);
12593 :
12594 574 : for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)
12595 : {
12596 525 : int anDimIds[2] = {-1, -1};
12597 525 : nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);
12598 525 : if (anDimIds[0] == nVectorDim ||
12599 24 : (nProfileDimId >= 0 && anDimIds[0] == nProfileDimId))
12600 : {
12601 : #ifdef NCDF_DEBUG
12602 : char szTemp2[NC_MAX_NAME + 1] = {};
12603 : CPL_IGNORE_RET_VAL(
12604 : nc_inq_varname(nCdfId, anPotentialVectorVarID[j], szTemp2));
12605 : CPLDebug("GDAL_netCDF", "Variable %s is a vector field", szTemp2);
12606 : #endif
12607 525 : poLayer->AddField(anPotentialVectorVarID[j]);
12608 : }
12609 : }
12610 :
12611 49 : if (poLayer->GetLayerDefn()->GetFieldCount() != 0 ||
12612 0 : poLayer->GetGeomType() != wkbNone)
12613 : {
12614 49 : papoLayers.push_back(poLayer);
12615 : }
12616 :
12617 98 : return CE_None;
12618 : }
12619 :
12620 : // Get all coordinate and boundary variables full names referenced in
12621 : // a given a NetCDF (or group) ID and its sub-groups.
12622 : // These variables are identified in other variable's
12623 : // "coordinates" and "bounds" attribute.
12624 : // Searching coordinate and boundary variables may need to explore
12625 : // parents groups (or other groups in case of reference given in form of an
12626 : // absolute path).
12627 : // See CF sections 5.2, 5.6 and 7.1
12628 524 : static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId, char ***ppapszVars)
12629 : {
12630 524 : int nVars = 0;
12631 524 : NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
12632 :
12633 3202 : for (int v = 0; v < nVars; v++)
12634 : {
12635 2678 : char *pszTemp = nullptr;
12636 2678 : char **papszTokens = nullptr;
12637 2678 : if (NCDFGetAttr(nCdfId, v, "coordinates", &pszTemp) == CE_None)
12638 445 : papszTokens = NCDFTokenizeCoordinatesAttribute(pszTemp);
12639 2678 : CPLFree(pszTemp);
12640 2678 : pszTemp = nullptr;
12641 2678 : if (NCDFGetAttr(nCdfId, v, "bounds", &pszTemp) == CE_None &&
12642 2678 : pszTemp != nullptr && !EQUAL(pszTemp, ""))
12643 17 : papszTokens = CSLAddString(papszTokens, pszTemp);
12644 2678 : CPLFree(pszTemp);
12645 3964 : for (int i = 0; papszTokens != nullptr && papszTokens[i] != nullptr;
12646 : i++)
12647 : {
12648 1286 : char *pszVarFullName = nullptr;
12649 1286 : if (NCDFResolveVarFullName(nCdfId, papszTokens[i],
12650 1286 : &pszVarFullName) == CE_None)
12651 1260 : *ppapszVars = CSLAddString(*ppapszVars, pszVarFullName);
12652 1286 : CPLFree(pszVarFullName);
12653 : }
12654 2678 : CSLDestroy(papszTokens);
12655 : }
12656 :
12657 : // Recurse on sub-groups.
12658 : int nSubGroups;
12659 524 : int *panSubGroupIds = nullptr;
12660 524 : NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
12661 556 : for (int i = 0; i < nSubGroups; i++)
12662 : {
12663 32 : NCDFGetCoordAndBoundVarFullNames(panSubGroupIds[i], ppapszVars);
12664 : }
12665 524 : CPLFree(panSubGroupIds);
12666 :
12667 524 : return CE_None;
12668 : }
12669 :
12670 : // Check if give type is user defined
12671 920 : bool NCDFIsUserDefinedType(int /*ncid*/, int type)
12672 : {
12673 920 : return type >= NC_FIRSTUSERTYPEID;
12674 : }
12675 :
12676 558 : char **NCDFTokenizeCoordinatesAttribute(const char *pszCoordinates)
12677 : {
12678 : // CF conventions use space as the separator for variable names in the
12679 : // coordinates attribute, but some products such as
12680 : // https://sentinel.esa.int/web/sentinel/technical-guides/sentinel-3-synergy/products-algorithms/level-2-aod-algorithms-and-products/level-2-aod-products-description
12681 : // use comma.
12682 558 : return CSLTokenizeString2(pszCoordinates, ", ", 0);
12683 : }
|