Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: netCDF read/write Driver
4 : * Purpose: GDAL bindings over netCDF library.
5 : * Author: Frank Warmerdam, warmerdam@pobox.com
6 : * Even Rouault <even.rouault at spatialys.com>
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2004, Frank Warmerdam
10 : * Copyright (c) 2007-2016, Even Rouault <even.rouault at spatialys.com>
11 : * Copyright (c) 2010, Kyle Shannon <kyle at pobox dot com>
12 : * Copyright (c) 2021, CLS
13 : *
14 : * SPDX-License-Identifier: MIT
15 : ****************************************************************************/
16 :
17 : #include "cpl_port.h"
18 :
19 : #include <array>
20 : #include <cassert>
21 : #include <cctype>
22 : #include <cerrno>
23 : #include <climits>
24 : #include <cmath>
25 : #include <cstdio>
26 : #include <cstdlib>
27 : #include <cstring>
28 : #include <ctime>
29 : #include <algorithm>
30 : #include <limits>
31 : #include <map>
32 : #include <mutex>
33 : #include <set>
34 : #include <queue>
35 : #include <string>
36 : #include <tuple>
37 : #include <utility>
38 : #include <vector>
39 :
40 : // Must be included after standard includes, otherwise VS2015 fails when
41 : // including <ctime>
42 : #include "netcdfdataset.h"
43 : #include "netcdfdrivercore.h"
44 : #include "netcdfsg.h"
45 : #include "netcdfuffd.h"
46 :
47 : #include "netcdf_mem.h"
48 :
49 : #include "cpl_conv.h"
50 : #include "cpl_error.h"
51 : #include "cpl_float.h"
52 : #include "cpl_json.h"
53 : #include "cpl_minixml.h"
54 : #include "cpl_multiproc.h"
55 : #include "cpl_progress.h"
56 : #include "cpl_time.h"
57 : #include "gdal.h"
58 : #include "gdal_frmts.h"
59 : #include "gdal_priv_templates.hpp"
60 : #include "ogr_core.h"
61 : #include "ogr_srs_api.h"
62 :
63 : // netCDF 4.8 switched to expecting filenames in UTF-8 on Windows
64 : // But with netCDF 4.9 and https://github.com/Unidata/netcdf-c/pull/2277/,
65 : // this is apparently back to expecting filenames in current codepage...
66 : // Detect netCDF 4.8 with NC_ENCZARR
67 : // Detect netCDF 4.9 with NC_NOATTCREORD
68 : #if defined(NC_ENCZARR) && !defined(NC_NOATTCREORD)
69 : #define NETCDF_USES_UTF8
70 : #endif
71 :
72 : // Internal function declarations.
73 :
74 : static bool NCDFIsGDALVersionGTE(const char *pszVersion, int nTarget);
75 :
76 : static void
77 : NCDFAddGDALHistory(int fpImage, const char *pszFilename, bool bWriteGDALVersion,
78 : bool bWriteGDALHistory, const char *pszOldHist,
79 : const char *pszFunctionName,
80 : const char *pszCFVersion = GDAL_DEFAULT_NCDF_CONVENTIONS);
81 :
82 : static void NCDFAddHistory(int fpImage, const char *pszAddHist,
83 : const char *pszOldHist);
84 :
85 : static CPLErr NCDFSafeStrcat(char **ppszDest, const char *pszSrc,
86 : size_t *nDestSize);
87 :
88 : // Var / attribute helper functions.
89 : static CPLErr NCDFPutAttr(int nCdfId, int nVarId, const char *pszAttrName,
90 : const char *pszValue);
91 :
92 : // Replace this where used.
93 : static CPLErr NCDFGet1DVar(int nCdfId, int nVarId, char **pszValue);
94 : static CPLErr NCDFPut1DVar(int nCdfId, int nVarId, const char *pszValue);
95 :
96 : // Replace this where used.
97 : static CPLStringList NCDFTokenizeArray(const char *pszValue);
98 : static void CopyMetadata(GDALDataset *poSrcDS, GDALRasterBand *poSrcBand,
99 : GDALRasterBand *poDstBand, int fpImage, int CDFVarID,
100 : const char *pszMatchPrefix = nullptr);
101 :
102 : // NetCDF-4 groups helper functions.
103 : // They all work also for NetCDF-3 files which are considered as
104 : // NetCDF-4 file with only one group.
105 : static CPLErr NCDFOpenSubDataset(int nCdfId, const char *pszSubdatasetName,
106 : int *pnGroupId, int *pnVarId);
107 : static CPLErr NCDFGetVisibleDims(int nGroupId, int *pnDims, int **ppanDimIds);
108 : static CPLErr NCDFGetSubGroups(int nGroupId, int *pnSubGroups,
109 : int **ppanSubGroupIds);
110 : static CPLErr NCDFGetGroupFullName(int nGroupId, std::string &osFullName,
111 : bool bNC3Compat = true);
112 : static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId,
113 : std::string &osFullName,
114 : bool bNC3Compat = true);
115 : static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId);
116 :
117 : static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,
118 : std::string &osFullName,
119 : bool bMandatory = false);
120 : static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,
121 : const char *pszAtt, int *pnAtt,
122 : bool bMandatory = false);
123 : static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId,
124 : CPLStringList &aosVars);
125 :
126 : // Uncomment this for more debug output.
127 : // #define NCDF_DEBUG 1
128 :
129 : CPLMutex *hNCMutex = nullptr;
130 :
131 : // Workaround https://github.com/OSGeo/gdal/issues/6253
132 : // Having 2 netCDF handles on the same file doesn't work in a multi-threaded
133 : // way. Apparently having the same handle works better (this is OK since
134 : // we have a global mutex on the netCDF library)
135 : static std::map<std::string, int> goMapNameToNetCDFId;
136 : static std::map<int, std::pair<std::string, int>> goMapNetCDFIdToKeyAndCount;
137 :
138 787 : int GDAL_nc_open(const char *pszFilename, int nMode, int *pID)
139 : {
140 1574 : std::string osKey(pszFilename);
141 787 : osKey += "#####";
142 787 : osKey += std::to_string(nMode);
143 787 : auto oIter = goMapNameToNetCDFId.find(osKey);
144 787 : if (oIter == goMapNameToNetCDFId.end())
145 : {
146 727 : int ret = nc_open(pszFilename, nMode, pID);
147 727 : if (ret != NC_NOERR)
148 3 : return ret;
149 724 : goMapNameToNetCDFId[osKey] = *pID;
150 724 : goMapNetCDFIdToKeyAndCount[*pID] =
151 1448 : std::pair<std::string, int>(osKey, 1);
152 724 : return ret;
153 : }
154 : else
155 : {
156 60 : *pID = oIter->second;
157 60 : goMapNetCDFIdToKeyAndCount[oIter->second].second++;
158 60 : return NC_NOERR;
159 : }
160 : }
161 :
162 1102 : int GDAL_nc_close(int cdfid)
163 : {
164 1102 : int ret = NC_NOERR;
165 1102 : auto oIter = goMapNetCDFIdToKeyAndCount.find(cdfid);
166 1102 : if (oIter != goMapNetCDFIdToKeyAndCount.end())
167 : {
168 784 : if (--oIter->second.second == 0)
169 : {
170 724 : ret = nc_close(cdfid);
171 724 : goMapNameToNetCDFId.erase(oIter->second.first);
172 724 : goMapNetCDFIdToKeyAndCount.erase(oIter);
173 : }
174 : }
175 : else
176 : {
177 : // we can go here if file opened with nc_open_mem() or nc_create()
178 318 : ret = nc_close(cdfid);
179 : }
180 1102 : return ret;
181 : }
182 :
183 : /************************************************************************/
184 : /* ==================================================================== */
185 : /* netCDFRasterBand */
186 : /* ==================================================================== */
187 : /************************************************************************/
188 :
189 : class netCDFRasterBand final : public GDALPamRasterBand
190 : {
191 : friend class netCDFDataset;
192 :
193 : nc_type nc_datatype;
194 : int cdfid;
195 : int nZId;
196 : int nZDim;
197 : int nLevel;
198 : int nBandXPos;
199 : int nBandYPos;
200 : int *panBandZPos;
201 : int *panBandZLev;
202 : bool m_bNoDataSet = false;
203 : double m_dfNoDataValue = 0;
204 : bool m_bNoDataSetAsInt64 = false;
205 : int64_t m_nNodataValueInt64 = 0;
206 : bool m_bNoDataSetAsUInt64 = false;
207 : uint64_t m_nNodataValueUInt64 = 0;
208 : bool bValidRangeValid = false;
209 : double adfValidRange[2]{0, 0};
210 : bool m_bHaveScale = false;
211 : bool m_bHaveOffset = false;
212 : double m_dfScale = 1;
213 : double m_dfOffset = 0;
214 : CPLString m_osUnitType{};
215 : bool bSignedData;
216 : bool bCheckLongitude;
217 : bool m_bCreateMetadataFromOtherVarsDone = false;
218 :
219 : void CreateMetadataFromAttributes();
220 : void CreateMetadataFromOtherVars();
221 :
222 : template <class T>
223 : void CheckData(void *pImage, void *pImageNC, size_t nTmpBlockXSize,
224 : size_t nTmpBlockYSize, bool bCheckIsNan = false);
225 : template <class T>
226 : void CheckDataCpx(void *pImage, void *pImageNC, size_t nTmpBlockXSize,
227 : size_t nTmpBlockYSize, bool bCheckIsNan = false);
228 : void SetBlockSize();
229 :
230 : bool FetchNetcdfChunk(size_t xstart, size_t ystart, void *pImage);
231 :
232 : void SetNoDataValueNoUpdate(double dfNoData);
233 : void SetNoDataValueNoUpdate(int64_t nNoData);
234 : void SetNoDataValueNoUpdate(uint64_t nNoData);
235 :
236 : void SetOffsetNoUpdate(double dfVal);
237 : void SetScaleNoUpdate(double dfVal);
238 : void SetUnitTypeNoUpdate(const char *pszNewValue);
239 :
240 : protected:
241 : CPLXMLNode *SerializeToXML(const char *pszUnused) override;
242 :
243 : public:
244 : struct CONSTRUCTOR_OPEN
245 : {
246 : };
247 :
248 : struct CONSTRUCTOR_CREATE
249 : {
250 : };
251 :
252 : netCDFRasterBand(const CONSTRUCTOR_OPEN &, netCDFDataset *poDS,
253 : int nGroupId, int nZId, int nZDim, int nLevel,
254 : const int *panBandZLen, const int *panBandPos, int nBand);
255 : netCDFRasterBand(const CONSTRUCTOR_CREATE &, netCDFDataset *poDS,
256 : GDALDataType eType, int nBand, bool bSigned = true,
257 : const char *pszBandName = nullptr,
258 : const char *pszLongName = nullptr, int nZId = -1,
259 : int nZDim = 2, int nLevel = 0,
260 : const int *panBandZLev = nullptr,
261 : const int *panBandZPos = nullptr,
262 : const int *paDimIds = nullptr);
263 : ~netCDFRasterBand() override;
264 :
265 : double GetNoDataValue(int *) override;
266 : int64_t GetNoDataValueAsInt64(int *pbSuccess = nullptr) override;
267 : uint64_t GetNoDataValueAsUInt64(int *pbSuccess = nullptr) override;
268 : CPLErr SetNoDataValue(double) override;
269 : CPLErr SetNoDataValueAsInt64(int64_t nNoData) override;
270 : CPLErr SetNoDataValueAsUInt64(uint64_t nNoData) override;
271 : // virtual CPLErr DeleteNoDataValue();
272 : double GetOffset(int *) override;
273 : CPLErr SetOffset(double) override;
274 : double GetScale(int *) override;
275 : CPLErr SetScale(double) override;
276 : const char *GetUnitType() override;
277 : CPLErr SetUnitType(const char *) override;
278 : CPLErr IReadBlock(int, int, void *) override;
279 : CPLErr IWriteBlock(int, int, void *) override;
280 :
281 : CSLConstList GetMetadata(const char *pszDomain = "") override;
282 : const char *GetMetadataItem(const char *pszName,
283 : const char *pszDomain = "") override;
284 :
285 : CPLErr SetMetadataItem(const char *pszName, const char *pszValue,
286 : const char *pszDomain = "") override;
287 : CPLErr SetMetadata(CSLConstList papszMD,
288 : const char *pszDomain = "") override;
289 : };
290 :
291 : /************************************************************************/
292 : /* netCDFRasterBand() */
293 : /************************************************************************/
294 :
295 498 : netCDFRasterBand::netCDFRasterBand(const netCDFRasterBand::CONSTRUCTOR_OPEN &,
296 : netCDFDataset *poNCDFDS, int nGroupId,
297 : int nZIdIn, int nZDimIn, int nLevelIn,
298 : const int *panBandZLevIn,
299 498 : const int *panBandZPosIn, int nBandIn)
300 : : nc_datatype(NC_NAT), cdfid(nGroupId), nZId(nZIdIn), nZDim(nZDimIn),
301 498 : nLevel(nLevelIn), nBandXPos(panBandZPosIn[0]),
302 498 : nBandYPos(nZDim == 1 ? -1 : panBandZPosIn[1]), panBandZPos(nullptr),
303 : panBandZLev(nullptr),
304 : bSignedData(true), // Default signed, except for Byte.
305 996 : bCheckLongitude(false)
306 : {
307 498 : poDS = poNCDFDS;
308 498 : nBand = nBandIn;
309 :
310 : // Take care of all other dimensions.
311 498 : if (nZDim > 2)
312 : {
313 176 : panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
314 176 : panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
315 :
316 478 : for (int i = 0; i < nZDim - 2; i++)
317 : {
318 302 : panBandZPos[i] = panBandZPosIn[i + 2];
319 302 : panBandZLev[i] = panBandZLevIn[i];
320 : }
321 : }
322 :
323 498 : nRasterXSize = poDS->GetRasterXSize();
324 498 : nRasterYSize = poDS->GetRasterYSize();
325 498 : nBlockXSize = poDS->GetRasterXSize();
326 498 : nBlockYSize = 1;
327 :
328 : // Get the type of the "z" variable, our target raster array.
329 498 : if (nc_inq_var(cdfid, nZId, nullptr, &nc_datatype, nullptr, nullptr,
330 498 : nullptr) != NC_NOERR)
331 : {
332 0 : CPLError(CE_Failure, CPLE_AppDefined, "Error in nc_var_inq() on 'z'.");
333 0 : return;
334 : }
335 :
336 498 : if (NCDFIsUserDefinedType(cdfid, nc_datatype))
337 : {
338 : // First enquire and check that the number of fields is 2
339 : size_t nfields, compoundsize;
340 5 : if (nc_inq_compound(cdfid, nc_datatype, nullptr, &compoundsize,
341 5 : &nfields) != NC_NOERR)
342 : {
343 0 : CPLError(CE_Failure, CPLE_AppDefined,
344 : "Error in nc_inq_compound() on 'z'.");
345 0 : return;
346 : }
347 :
348 5 : if (nfields != 2)
349 : {
350 0 : CPLError(CE_Failure, CPLE_AppDefined,
351 : "Unsupported data type encountered in nc_inq_compound() "
352 : "on 'z'.");
353 0 : return;
354 : }
355 :
356 : // Now check that that two types are the same in the struct.
357 : nc_type field_type1, field_type2;
358 : int field_dims1, field_dims2;
359 5 : if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
360 : &field_type1, &field_dims1,
361 5 : nullptr) != NC_NOERR)
362 : {
363 0 : CPLError(
364 : CE_Failure, CPLE_AppDefined,
365 : "Error in querying Field 1 in nc_inq_compound_field() on 'z'.");
366 0 : return;
367 : }
368 :
369 5 : if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
370 : &field_type2, &field_dims2,
371 5 : nullptr) != NC_NOERR)
372 : {
373 0 : CPLError(
374 : CE_Failure, CPLE_AppDefined,
375 : "Error in querying Field 2 in nc_inq_compound_field() on 'z'.");
376 0 : return;
377 : }
378 :
379 5 : if ((field_type1 != field_type2) || (field_dims1 != field_dims2) ||
380 5 : (field_dims1 != 0))
381 : {
382 0 : CPLError(CE_Failure, CPLE_AppDefined,
383 : "Error in interpreting compound data type on 'z'.");
384 0 : return;
385 : }
386 :
387 5 : if (field_type1 == NC_SHORT)
388 0 : eDataType = GDT_CInt16;
389 5 : else if (field_type1 == NC_INT)
390 0 : eDataType = GDT_CInt32;
391 5 : else if (field_type1 == NC_FLOAT)
392 4 : eDataType = GDT_CFloat32;
393 1 : else if (field_type1 == NC_DOUBLE)
394 1 : eDataType = GDT_CFloat64;
395 : else
396 : {
397 0 : CPLError(CE_Failure, CPLE_AppDefined,
398 : "Unsupported netCDF compound data type encountered.");
399 0 : return;
400 : }
401 : }
402 : else
403 : {
404 493 : if (nc_datatype == NC_BYTE)
405 157 : eDataType = GDT_UInt8;
406 336 : else if (nc_datatype == NC_CHAR)
407 0 : eDataType = GDT_UInt8;
408 336 : else if (nc_datatype == NC_SHORT)
409 41 : eDataType = GDT_Int16;
410 295 : else if (nc_datatype == NC_INT)
411 89 : eDataType = GDT_Int32;
412 206 : else if (nc_datatype == NC_FLOAT)
413 127 : eDataType = GDT_Float32;
414 79 : else if (nc_datatype == NC_DOUBLE)
415 40 : eDataType = GDT_Float64;
416 39 : else if (nc_datatype == NC_UBYTE)
417 16 : eDataType = GDT_UInt8;
418 23 : else if (nc_datatype == NC_USHORT)
419 4 : eDataType = GDT_UInt16;
420 19 : else if (nc_datatype == NC_UINT)
421 4 : eDataType = GDT_UInt32;
422 15 : else if (nc_datatype == NC_INT64)
423 8 : eDataType = GDT_Int64;
424 7 : else if (nc_datatype == NC_UINT64)
425 7 : eDataType = GDT_UInt64;
426 : else
427 : {
428 0 : if (nBand == 1)
429 0 : CPLError(CE_Warning, CPLE_AppDefined,
430 : "Unsupported netCDF datatype (%d), treat as Float32.",
431 0 : static_cast<int>(nc_datatype));
432 0 : eDataType = GDT_Float32;
433 0 : nc_datatype = NC_FLOAT;
434 : }
435 : }
436 :
437 : // Find and set No Data for this variable.
438 498 : nc_type atttype = NC_NAT;
439 498 : size_t attlen = 0;
440 498 : const char *pszNoValueName = nullptr;
441 :
442 : // Find attribute name, either _FillValue or missing_value.
443 498 : int status = nc_inq_att(cdfid, nZId, NCDF_FillValue, &atttype, &attlen);
444 498 : if (status == NC_NOERR)
445 : {
446 249 : pszNoValueName = NCDF_FillValue;
447 : }
448 : else
449 : {
450 249 : status = nc_inq_att(cdfid, nZId, "missing_value", &atttype, &attlen);
451 249 : if (status == NC_NOERR)
452 : {
453 12 : pszNoValueName = "missing_value";
454 : }
455 : }
456 :
457 : // Fetch missing value.
458 498 : double dfNoData = 0.0;
459 498 : bool bGotNoData = false;
460 498 : int64_t nNoDataAsInt64 = 0;
461 498 : bool bGotNoDataAsInt64 = false;
462 498 : uint64_t nNoDataAsUInt64 = 0;
463 498 : bool bGotNoDataAsUInt64 = false;
464 498 : if (status == NC_NOERR)
465 : {
466 261 : nc_type nAttrType = NC_NAT;
467 261 : size_t nAttrLen = 0;
468 261 : status = nc_inq_att(cdfid, nZId, pszNoValueName, &nAttrType, &nAttrLen);
469 261 : if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_INT64)
470 : {
471 : long long v;
472 7 : nc_get_att_longlong(cdfid, nZId, pszNoValueName, &v);
473 7 : bGotNoData = true;
474 7 : bGotNoDataAsInt64 = true;
475 7 : nNoDataAsInt64 = static_cast<int64_t>(v);
476 : }
477 254 : else if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_UINT64)
478 : {
479 : unsigned long long v;
480 7 : nc_get_att_ulonglong(cdfid, nZId, pszNoValueName, &v);
481 7 : bGotNoData = true;
482 7 : bGotNoDataAsUInt64 = true;
483 7 : nNoDataAsUInt64 = static_cast<uint64_t>(v);
484 : }
485 247 : else if (NCDFGetAttr(cdfid, nZId, pszNoValueName, &dfNoData) == CE_None)
486 : {
487 246 : bGotNoData = true;
488 : }
489 : }
490 :
491 : // If NoData was not found, use the default value, but for non-Byte types
492 : // as it is not recommended:
493 : // https://www.unidata.ucar.edu/software/netcdf/docs/attribute_conventions.html
494 498 : nc_type vartype = NC_NAT;
495 498 : if (!bGotNoData)
496 : {
497 238 : nc_inq_vartype(cdfid, nZId, &vartype);
498 238 : if (vartype == NC_INT64)
499 : {
500 : nNoDataAsInt64 =
501 1 : NCDFGetDefaultNoDataValueAsInt64(cdfid, nZId, bGotNoData);
502 1 : bGotNoDataAsInt64 = bGotNoData;
503 : }
504 237 : else if (vartype == NC_UINT64)
505 : {
506 : nNoDataAsUInt64 =
507 0 : NCDFGetDefaultNoDataValueAsUInt64(cdfid, nZId, bGotNoData);
508 0 : bGotNoDataAsUInt64 = bGotNoData;
509 : }
510 237 : else if (vartype != NC_CHAR && vartype != NC_BYTE &&
511 103 : vartype != NC_UBYTE)
512 : {
513 93 : dfNoData =
514 93 : NCDFGetDefaultNoDataValue(cdfid, nZId, vartype, bGotNoData);
515 93 : if (bGotNoData)
516 : {
517 82 : CPLDebug("GDAL_netCDF",
518 : "did not get nodata value for variable #%d, using "
519 : "default %f",
520 : nZId, dfNoData);
521 : }
522 : }
523 : }
524 :
525 498 : bool bHasUnderscoreUnsignedAttr = false;
526 498 : bool bUnderscoreUnsignedAttrVal = false;
527 : {
528 498 : char *pszTemp = nullptr;
529 498 : if (NCDFGetAttr(cdfid, nZId, "_Unsigned", &pszTemp) == CE_None)
530 : {
531 149 : if (EQUAL(pszTemp, "true"))
532 : {
533 141 : bHasUnderscoreUnsignedAttr = true;
534 141 : bUnderscoreUnsignedAttrVal = true;
535 : }
536 8 : else if (EQUAL(pszTemp, "false"))
537 : {
538 8 : bHasUnderscoreUnsignedAttr = true;
539 8 : bUnderscoreUnsignedAttrVal = false;
540 : }
541 149 : CPLFree(pszTemp);
542 : }
543 : }
544 :
545 : // Look for valid_range or valid_min/valid_max.
546 :
547 : // First look for valid_range.
548 498 : if (CPLFetchBool(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE", true))
549 : {
550 496 : char *pszValidRange = nullptr;
551 496 : if (NCDFGetAttr(cdfid, nZId, "valid_range", &pszValidRange) ==
552 142 : CE_None &&
553 638 : pszValidRange[0] == '{' &&
554 142 : pszValidRange[strlen(pszValidRange) - 1] == '}')
555 : {
556 : const std::string osValidRange =
557 426 : std::string(pszValidRange).substr(1, strlen(pszValidRange) - 2);
558 : const CPLStringList aosValidRange(
559 284 : CSLTokenizeString2(osValidRange.c_str(), ",", 0));
560 142 : if (aosValidRange.size() == 2 &&
561 284 : CPLGetValueType(aosValidRange[0]) != CPL_VALUE_STRING &&
562 142 : CPLGetValueType(aosValidRange[1]) != CPL_VALUE_STRING)
563 : {
564 142 : bValidRangeValid = true;
565 142 : adfValidRange[0] = CPLAtof(aosValidRange[0]);
566 142 : adfValidRange[1] = CPLAtof(aosValidRange[1]);
567 : }
568 : }
569 496 : CPLFree(pszValidRange);
570 :
571 : // If not found look for valid_min and valid_max.
572 496 : if (!bValidRangeValid)
573 : {
574 354 : double dfMin = 0;
575 354 : double dfMax = 0;
576 369 : if (NCDFGetAttr(cdfid, nZId, "valid_min", &dfMin) == CE_None &&
577 15 : NCDFGetAttr(cdfid, nZId, "valid_max", &dfMax) == CE_None)
578 : {
579 8 : adfValidRange[0] = dfMin;
580 8 : adfValidRange[1] = dfMax;
581 8 : bValidRangeValid = true;
582 : }
583 : }
584 :
585 496 : if (bValidRangeValid &&
586 150 : (adfValidRange[0] < 0 || adfValidRange[1] < 0) &&
587 17 : nc_datatype == NC_SHORT && bHasUnderscoreUnsignedAttr &&
588 : bUnderscoreUnsignedAttrVal)
589 : {
590 2 : if (adfValidRange[0] < 0)
591 0 : adfValidRange[0] += 65536;
592 2 : if (adfValidRange[1] < 0)
593 2 : adfValidRange[1] += 65536;
594 2 : if (adfValidRange[0] <= adfValidRange[1])
595 : {
596 : // Updating metadata item
597 2 : GDALPamRasterBand::SetMetadataItem(
598 : "valid_range",
599 2 : CPLSPrintf("{%d,%d}", static_cast<int>(adfValidRange[0]),
600 2 : static_cast<int>(adfValidRange[1])));
601 : }
602 : }
603 :
604 496 : if (bValidRangeValid && adfValidRange[0] > adfValidRange[1])
605 : {
606 0 : CPLError(CE_Warning, CPLE_AppDefined,
607 : "netCDFDataset::valid_range: min > max:\n"
608 : " min: %lf\n max: %lf\n",
609 : adfValidRange[0], adfValidRange[1]);
610 0 : bValidRangeValid = false;
611 0 : adfValidRange[0] = 0.0;
612 0 : adfValidRange[1] = 0.0;
613 : }
614 : }
615 :
616 : // Special For Byte Bands: check for signed/unsigned byte.
617 498 : if (nc_datatype == NC_BYTE)
618 : {
619 : // netcdf uses signed byte by default, but GDAL uses unsigned by default
620 : // This may cause unexpected results, but is needed for back-compat.
621 157 : if (poNCDFDS->bIsGdalFile)
622 135 : bSignedData = false;
623 : else
624 22 : bSignedData = true;
625 :
626 : // For NC4 format NC_BYTE is (normally) signed, NC_UBYTE is unsigned.
627 : // But in case a NC3 file was converted automatically and has hints
628 : // that it is unsigned, take them into account
629 157 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
630 : {
631 3 : bSignedData = true;
632 : }
633 :
634 : // If we got valid_range, test for signed/unsigned range.
635 : // https://docs.unidata.ucar.edu/netcdf-c/current/attribute_conventions.html
636 157 : if (bValidRangeValid)
637 : {
638 : // If we got valid_range={0,255}, treat as unsigned.
639 138 : if (adfValidRange[0] == 0 && adfValidRange[1] == 255)
640 : {
641 130 : bSignedData = false;
642 : // Reset valid_range.
643 130 : bValidRangeValid = false;
644 : }
645 : // If we got valid_range={-128,127}, treat as signed.
646 8 : else if (adfValidRange[0] == -128 && adfValidRange[1] == 127)
647 : {
648 8 : bSignedData = true;
649 : // Reset valid_range.
650 8 : bValidRangeValid = false;
651 : }
652 : }
653 : // Else test for _Unsigned.
654 : // https://docs.unidata.ucar.edu/nug/current/best_practices.html
655 : else
656 : {
657 19 : if (bHasUnderscoreUnsignedAttr)
658 7 : bSignedData = !bUnderscoreUnsignedAttrVal;
659 : }
660 :
661 157 : if (bSignedData)
662 : {
663 20 : eDataType = GDT_Int8;
664 : }
665 137 : else if (dfNoData < 0)
666 : {
667 : // Fix nodata value as it was stored signed.
668 6 : dfNoData += 256;
669 6 : if (pszNoValueName)
670 : {
671 : // Updating metadata item
672 6 : GDALPamRasterBand::SetMetadataItem(
673 : pszNoValueName,
674 : CPLSPrintf("%d", static_cast<int>(dfNoData)));
675 : }
676 : }
677 : }
678 341 : else if (nc_datatype == NC_SHORT)
679 : {
680 41 : if (bHasUnderscoreUnsignedAttr)
681 : {
682 4 : bSignedData = !bUnderscoreUnsignedAttrVal;
683 4 : if (!bSignedData)
684 4 : eDataType = GDT_UInt16;
685 : }
686 :
687 : // Fix nodata value as it was stored signed.
688 41 : if (!bSignedData && dfNoData < 0)
689 : {
690 4 : dfNoData += 65536;
691 4 : if (pszNoValueName)
692 : {
693 : // Updating metadata item
694 4 : GDALPamRasterBand::SetMetadataItem(
695 : pszNoValueName,
696 : CPLSPrintf("%d", static_cast<int>(dfNoData)));
697 : }
698 : }
699 : }
700 :
701 300 : else if (nc_datatype == NC_UBYTE || nc_datatype == NC_USHORT ||
702 280 : nc_datatype == NC_UINT || nc_datatype == NC_UINT64)
703 : {
704 31 : bSignedData = false;
705 : }
706 :
707 498 : CPLDebug("GDAL_netCDF", "netcdf type=%d gdal type=%d signedByte=%d",
708 498 : nc_datatype, eDataType, static_cast<int>(bSignedData));
709 :
710 498 : if (bGotNoData)
711 : {
712 : // Set nodata value.
713 343 : if (bGotNoDataAsInt64)
714 : {
715 8 : if (eDataType == GDT_Int64)
716 : {
717 8 : SetNoDataValueNoUpdate(nNoDataAsInt64);
718 : }
719 0 : else if (eDataType == GDT_UInt64 && nNoDataAsInt64 >= 0)
720 : {
721 0 : SetNoDataValueNoUpdate(static_cast<uint64_t>(nNoDataAsInt64));
722 : }
723 : else
724 : {
725 0 : SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsInt64));
726 : }
727 : }
728 335 : else if (bGotNoDataAsUInt64)
729 : {
730 7 : if (eDataType == GDT_UInt64)
731 : {
732 7 : SetNoDataValueNoUpdate(nNoDataAsUInt64);
733 : }
734 0 : else if (eDataType == GDT_Int64 &&
735 : nNoDataAsUInt64 <=
736 0 : static_cast<uint64_t>(
737 0 : std::numeric_limits<int64_t>::max()))
738 : {
739 0 : SetNoDataValueNoUpdate(static_cast<int64_t>(nNoDataAsUInt64));
740 : }
741 : else
742 : {
743 0 : SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsUInt64));
744 : }
745 : }
746 : else
747 : {
748 : #ifdef NCDF_DEBUG
749 : CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) read", dfNoData);
750 : #endif
751 328 : if (eDataType == GDT_Int64 && GDALIsValueExactAs<int64_t>(dfNoData))
752 : {
753 0 : SetNoDataValueNoUpdate(static_cast<int64_t>(dfNoData));
754 : }
755 328 : else if (eDataType == GDT_UInt64 &&
756 0 : GDALIsValueExactAs<uint64_t>(dfNoData))
757 : {
758 0 : SetNoDataValueNoUpdate(static_cast<uint64_t>(dfNoData));
759 : }
760 : else
761 : {
762 328 : SetNoDataValueNoUpdate(dfNoData);
763 : }
764 : }
765 : }
766 :
767 498 : CreateMetadataFromAttributes();
768 :
769 : // Attempt to fetch the scale_factor and add_offset attributes for the
770 : // variable and set them. If these values are not available, set
771 : // offset to 0 and scale to 1.
772 498 : if (nc_inq_attid(cdfid, nZId, CF_ADD_OFFSET, nullptr) == NC_NOERR)
773 : {
774 16 : double dfOffset = 0;
775 16 : status = nc_get_att_double(cdfid, nZId, CF_ADD_OFFSET, &dfOffset);
776 16 : CPLDebug("GDAL_netCDF", "got add_offset=%.16g, status=%d", dfOffset,
777 : status);
778 16 : SetOffsetNoUpdate(dfOffset);
779 : }
780 :
781 498 : bool bHasScale = false;
782 498 : if (nc_inq_attid(cdfid, nZId, CF_SCALE_FACTOR, nullptr) == NC_NOERR)
783 : {
784 20 : bHasScale = true;
785 20 : double dfScale = 1;
786 20 : status = nc_get_att_double(cdfid, nZId, CF_SCALE_FACTOR, &dfScale);
787 20 : CPLDebug("GDAL_netCDF", "got scale_factor=%.16g, status=%d", dfScale,
788 : status);
789 20 : SetScaleNoUpdate(dfScale);
790 : }
791 :
792 12 : if (bValidRangeValid && GDALDataTypeIsInteger(eDataType) &&
793 4 : eDataType != GDT_Int64 && eDataType != GDT_UInt64 &&
794 4 : (std::fabs(std::round(adfValidRange[0]) - adfValidRange[0]) > 1e-5 ||
795 510 : std::fabs(std::round(adfValidRange[1]) - adfValidRange[1]) > 1e-5) &&
796 1 : CSLFetchNameValue(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE") ==
797 : nullptr)
798 : {
799 1 : CPLError(CE_Warning, CPLE_AppDefined,
800 : "validity range = %f, %f contains floating-point values, "
801 : "whereas data type is integer. valid_range is thus likely "
802 : "wrong%s. Ignoring it.",
803 : adfValidRange[0], adfValidRange[1],
804 : bHasScale ? " (likely scaled using scale_factor/add_factor "
805 : "whereas it should be using the packed data type)"
806 : : "");
807 1 : bValidRangeValid = false;
808 1 : adfValidRange[0] = 0.0;
809 1 : adfValidRange[1] = 0.0;
810 : }
811 :
812 : // Should we check for longitude values > 360?
813 498 : bCheckLongitude =
814 996 : CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")) &&
815 498 : NCDFIsVarLongitude(cdfid, nZId, nullptr);
816 :
817 : // Attempt to fetch the units attribute for the variable and set it.
818 498 : SetUnitTypeNoUpdate(netCDFRasterBand::GetMetadataItem(CF_UNITS));
819 :
820 498 : SetBlockSize();
821 : }
822 :
823 686 : void netCDFRasterBand::SetBlockSize()
824 : {
825 : // Check for variable chunking (netcdf-4 only).
826 : // GDAL block size should be set to hdf5 chunk size.
827 686 : int nTmpFormat = 0;
828 686 : int status = nc_inq_format(cdfid, &nTmpFormat);
829 686 : NetCDFFormatEnum eTmpFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
830 686 : if ((status == NC_NOERR) &&
831 587 : (eTmpFormat == NCDF_FORMAT_NC4 || eTmpFormat == NCDF_FORMAT_NC4C))
832 : {
833 115 : size_t chunksize[MAX_NC_DIMS] = {};
834 : // Check for chunksize and set it as the blocksize (optimizes read).
835 115 : status = nc_inq_var_chunking(cdfid, nZId, &nTmpFormat, chunksize);
836 115 : if ((status == NC_NOERR) && (nTmpFormat == NC_CHUNKED))
837 : {
838 14 : nBlockXSize = (int)chunksize[nZDim - 1];
839 14 : if (nZDim >= 2)
840 14 : nBlockYSize = (int)chunksize[nZDim - 2];
841 : else
842 0 : nBlockYSize = 1;
843 : }
844 : }
845 :
846 : // Deal with bottom-up datasets and nBlockYSize != 1.
847 686 : auto poGDS = cpl::down_cast<netCDFDataset *>(poDS);
848 686 : if (poGDS->bBottomUp && nBlockYSize != 1 && poGDS->poChunkCache == nullptr)
849 : {
850 6 : if (poGDS->eAccess == GA_ReadOnly)
851 : {
852 : // Try to cache 1 or 2 'rows' of netCDF chunks along the whole
853 : // width of the raster
854 6 : size_t nChunks =
855 6 : static_cast<size_t>(DIV_ROUND_UP(nRasterXSize, nBlockXSize));
856 6 : if ((nRasterYSize % nBlockYSize) != 0)
857 2 : nChunks *= 2;
858 : const size_t nChunkSize =
859 6 : static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
860 6 : nBlockXSize * nBlockYSize;
861 6 : constexpr size_t MAX_CACHE_SIZE = 100 * 1024 * 1024;
862 6 : nChunks = std::min(nChunks, MAX_CACHE_SIZE / nChunkSize);
863 6 : if (nChunks)
864 : {
865 6 : poGDS->poChunkCache.reset(
866 6 : new netCDFDataset::ChunkCacheType(nChunks));
867 : }
868 : }
869 : else
870 : {
871 0 : nBlockYSize = 1;
872 : }
873 : }
874 686 : }
875 :
876 : // Constructor in create mode.
877 : // If nZId and following variables are not passed, the band will have 2
878 : // dimensions.
879 : // TODO: Get metadata, missing val from band #1 if nZDim > 2.
880 188 : netCDFRasterBand::netCDFRasterBand(
881 : const netCDFRasterBand::CONSTRUCTOR_CREATE &, netCDFDataset *poNCDFDS,
882 : const GDALDataType eTypeIn, int nBandIn, bool bSigned,
883 : const char *pszBandName, const char *pszLongName, int nZIdIn, int nZDimIn,
884 : int nLevelIn, const int *panBandZLevIn, const int *panBandZPosIn,
885 188 : const int *paDimIds)
886 188 : : nc_datatype(NC_NAT), cdfid(poNCDFDS->GetCDFID()), nZId(nZIdIn),
887 : nZDim(nZDimIn), nLevel(nLevelIn), nBandXPos(1), nBandYPos(0),
888 : panBandZPos(nullptr), panBandZLev(nullptr), bSignedData(bSigned),
889 188 : bCheckLongitude(false), m_bCreateMetadataFromOtherVarsDone(true)
890 : {
891 188 : poDS = poNCDFDS;
892 188 : nBand = nBandIn;
893 :
894 188 : nRasterXSize = poDS->GetRasterXSize();
895 188 : nRasterYSize = poDS->GetRasterYSize();
896 188 : nBlockXSize = poDS->GetRasterXSize();
897 188 : nBlockYSize = 1;
898 :
899 188 : if (poDS->GetAccess() != GA_Update)
900 : {
901 0 : CPLError(CE_Failure, CPLE_NotSupported,
902 : "Dataset is not in update mode, "
903 : "wrong netCDFRasterBand constructor");
904 0 : return;
905 : }
906 :
907 : // Take care of all other dimensions.
908 188 : if (nZDim > 2 && paDimIds != nullptr)
909 : {
910 27 : nBandXPos = panBandZPosIn[0];
911 27 : nBandYPos = panBandZPosIn[1];
912 :
913 27 : panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
914 27 : panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
915 :
916 76 : for (int i = 0; i < nZDim - 2; i++)
917 : {
918 49 : panBandZPos[i] = panBandZPosIn[i + 2];
919 49 : panBandZLev[i] = panBandZLevIn[i];
920 : }
921 : }
922 :
923 : // Get the type of the "z" variable, our target raster array.
924 188 : eDataType = eTypeIn;
925 :
926 188 : switch (eDataType)
927 : {
928 83 : case GDT_UInt8:
929 83 : nc_datatype = NC_BYTE;
930 : // NC_UBYTE (unsigned byte) is only available for NC4.
931 83 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
932 3 : nc_datatype = NC_UBYTE;
933 83 : break;
934 7 : case GDT_Int8:
935 7 : nc_datatype = NC_BYTE;
936 7 : break;
937 11 : case GDT_Int16:
938 11 : nc_datatype = NC_SHORT;
939 11 : break;
940 24 : case GDT_Int32:
941 24 : nc_datatype = NC_INT;
942 24 : break;
943 13 : case GDT_Float32:
944 13 : nc_datatype = NC_FLOAT;
945 13 : break;
946 8 : case GDT_Float64:
947 8 : nc_datatype = NC_DOUBLE;
948 8 : break;
949 7 : case GDT_Int64:
950 7 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
951 : {
952 7 : nc_datatype = NC_INT64;
953 : }
954 : else
955 : {
956 0 : if (nBand == 1)
957 0 : CPLError(
958 : CE_Warning, CPLE_AppDefined,
959 : "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
960 : "Int64");
961 0 : nc_datatype = NC_DOUBLE;
962 0 : eDataType = GDT_Float64;
963 : }
964 7 : break;
965 7 : case GDT_UInt64:
966 7 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
967 : {
968 7 : nc_datatype = NC_UINT64;
969 : }
970 : else
971 : {
972 0 : if (nBand == 1)
973 0 : CPLError(
974 : CE_Warning, CPLE_AppDefined,
975 : "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
976 : "UInt64");
977 0 : nc_datatype = NC_DOUBLE;
978 0 : eDataType = GDT_Float64;
979 : }
980 7 : break;
981 6 : case GDT_UInt16:
982 6 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
983 : {
984 6 : nc_datatype = NC_USHORT;
985 6 : break;
986 : }
987 : [[fallthrough]];
988 : case GDT_UInt32:
989 6 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
990 : {
991 6 : nc_datatype = NC_UINT;
992 6 : break;
993 : }
994 : [[fallthrough]];
995 : default:
996 16 : if (nBand == 1)
997 8 : CPLError(CE_Warning, CPLE_AppDefined,
998 : "Unsupported GDAL datatype (%d), treat as NC_FLOAT.",
999 8 : static_cast<int>(eDataType));
1000 16 : nc_datatype = NC_FLOAT;
1001 16 : eDataType = GDT_Float32;
1002 16 : break;
1003 : }
1004 :
1005 : // Define the variable if necessary (if nZId == -1).
1006 188 : bool bDefineVar = false;
1007 :
1008 188 : if (nZId == -1)
1009 : {
1010 166 : bDefineVar = true;
1011 :
1012 : // Make sure we are in define mode.
1013 166 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1014 :
1015 : char szTempPrivate[256 + 1];
1016 166 : const char *pszTemp = nullptr;
1017 166 : if (!pszBandName || EQUAL(pszBandName, ""))
1018 : {
1019 144 : snprintf(szTempPrivate, sizeof(szTempPrivate), "Band%d", nBand);
1020 144 : pszTemp = szTempPrivate;
1021 : }
1022 : else
1023 : {
1024 22 : pszTemp = pszBandName;
1025 : }
1026 :
1027 : int status;
1028 166 : if (nZDim > 2 && paDimIds != nullptr)
1029 : {
1030 5 : status =
1031 5 : nc_def_var(cdfid, pszTemp, nc_datatype, nZDim, paDimIds, &nZId);
1032 : }
1033 : else
1034 : {
1035 161 : int anBandDims[2] = {poNCDFDS->nYDimID, poNCDFDS->nXDimID};
1036 : status =
1037 161 : nc_def_var(cdfid, pszTemp, nc_datatype, 2, anBandDims, &nZId);
1038 : }
1039 166 : NCDF_ERR(status);
1040 166 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d) id=%d", cdfid, pszTemp,
1041 : nc_datatype, nZId);
1042 :
1043 166 : if (!pszLongName || EQUAL(pszLongName, ""))
1044 : {
1045 159 : snprintf(szTempPrivate, sizeof(szTempPrivate),
1046 : "GDAL Band Number %d", nBand);
1047 159 : pszTemp = szTempPrivate;
1048 : }
1049 : else
1050 : {
1051 7 : pszTemp = pszLongName;
1052 : }
1053 : status =
1054 166 : nc_put_att_text(cdfid, nZId, CF_LNG_NAME, strlen(pszTemp), pszTemp);
1055 166 : NCDF_ERR(status);
1056 :
1057 166 : poNCDFDS->DefVarDeflate(nZId, true);
1058 : }
1059 :
1060 : // For Byte data add signed/unsigned info.
1061 188 : if (eDataType == GDT_UInt8 || eDataType == GDT_Int8)
1062 : {
1063 90 : if (bDefineVar)
1064 : {
1065 : // Only add attributes if creating variable.
1066 : // For unsigned NC_BYTE (except NC4 format),
1067 : // add valid_range and _Unsigned ( defined in CF-1 and NUG ).
1068 82 : if (nc_datatype == NC_BYTE && poNCDFDS->eFormat != NCDF_FORMAT_NC4)
1069 : {
1070 79 : CPLDebug("GDAL_netCDF",
1071 : "adding valid_range attributes for Byte Band");
1072 79 : short l_adfValidRange[2] = {0, 0};
1073 : int status;
1074 79 : if (bSignedData || eDataType == GDT_Int8)
1075 : {
1076 7 : l_adfValidRange[0] = -128;
1077 7 : l_adfValidRange[1] = 127;
1078 7 : status =
1079 7 : nc_put_att_text(cdfid, nZId, "_Unsigned", 5, "false");
1080 : }
1081 : else
1082 : {
1083 72 : l_adfValidRange[0] = 0;
1084 72 : l_adfValidRange[1] = 255;
1085 : status =
1086 72 : nc_put_att_text(cdfid, nZId, "_Unsigned", 4, "true");
1087 : }
1088 79 : NCDF_ERR(status);
1089 79 : status = nc_put_att_short(cdfid, nZId, "valid_range", NC_SHORT,
1090 : 2, l_adfValidRange);
1091 79 : NCDF_ERR(status);
1092 : }
1093 : }
1094 : }
1095 :
1096 188 : if (nc_datatype != NC_BYTE && nc_datatype != NC_CHAR &&
1097 101 : nc_datatype != NC_UBYTE)
1098 : {
1099 : // Set default nodata.
1100 98 : bool bIgnored = false;
1101 : double dfNoData =
1102 98 : NCDFGetDefaultNoDataValue(cdfid, nZId, nc_datatype, bIgnored);
1103 : #ifdef NCDF_DEBUG
1104 : CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) default", dfNoData);
1105 : #endif
1106 98 : netCDFRasterBand::SetNoDataValue(dfNoData);
1107 : }
1108 :
1109 188 : SetBlockSize();
1110 : }
1111 :
1112 : /************************************************************************/
1113 : /* ~netCDFRasterBand() */
1114 : /************************************************************************/
1115 :
1116 1372 : netCDFRasterBand::~netCDFRasterBand()
1117 : {
1118 686 : netCDFRasterBand::FlushCache(true);
1119 686 : CPLFree(panBandZPos);
1120 686 : CPLFree(panBandZLev);
1121 1372 : }
1122 :
1123 : /************************************************************************/
1124 : /* GetMetadata() */
1125 : /************************************************************************/
1126 :
1127 54 : CSLConstList netCDFRasterBand::GetMetadata(const char *pszDomain)
1128 : {
1129 54 : if (!m_bCreateMetadataFromOtherVarsDone)
1130 52 : CreateMetadataFromOtherVars();
1131 54 : return GDALPamRasterBand::GetMetadata(pszDomain);
1132 : }
1133 :
1134 : /************************************************************************/
1135 : /* GetMetadataItem() */
1136 : /************************************************************************/
1137 :
1138 572 : const char *netCDFRasterBand::GetMetadataItem(const char *pszName,
1139 : const char *pszDomain)
1140 : {
1141 572 : if (!m_bCreateMetadataFromOtherVarsDone &&
1142 556 : STARTS_WITH(pszName, "NETCDF_DIM_") &&
1143 1 : (!pszDomain || pszDomain[0] == 0))
1144 1 : CreateMetadataFromOtherVars();
1145 572 : return GDALPamRasterBand::GetMetadataItem(pszName, pszDomain);
1146 : }
1147 :
1148 : /************************************************************************/
1149 : /* SetMetadataItem() */
1150 : /************************************************************************/
1151 :
1152 7 : CPLErr netCDFRasterBand::SetMetadataItem(const char *pszName,
1153 : const char *pszValue,
1154 : const char *pszDomain)
1155 : {
1156 9 : if (GetAccess() == GA_Update &&
1157 9 : (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)
1158 : {
1159 : // Same logic as in CopyMetadata()
1160 :
1161 2 : const char *const papszIgnoreBand[] = {
1162 : CF_ADD_OFFSET, CF_SCALE_FACTOR, "valid_range", "_Unsigned",
1163 : NCDF_FillValue, "coordinates", nullptr};
1164 : // Do not copy varname, stats, NETCDF_DIM_*, nodata
1165 : // and items in papszIgnoreBand.
1166 6 : if (STARTS_WITH(pszName, "NETCDF_VARNAME") ||
1167 2 : STARTS_WITH(pszName, "STATISTICS_") ||
1168 2 : STARTS_WITH(pszName, "NETCDF_DIM_") ||
1169 2 : STARTS_WITH(pszName, "missing_value") ||
1170 6 : STARTS_WITH(pszName, "_FillValue") ||
1171 2 : CSLFindString(papszIgnoreBand, pszName) != -1)
1172 : {
1173 : // do nothing
1174 : }
1175 : else
1176 : {
1177 2 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1178 :
1179 2 : if (!NCDFPutAttr(cdfid, nZId, pszName, pszValue))
1180 2 : return CE_Failure;
1181 : }
1182 : }
1183 :
1184 5 : return GDALPamRasterBand::SetMetadataItem(pszName, pszValue, pszDomain);
1185 : }
1186 :
1187 : /************************************************************************/
1188 : /* SetMetadata() */
1189 : /************************************************************************/
1190 :
1191 2 : CPLErr netCDFRasterBand::SetMetadata(CSLConstList papszMD,
1192 : const char *pszDomain)
1193 : {
1194 4 : if (GetAccess() == GA_Update &&
1195 2 : (pszDomain == nullptr || pszDomain[0] == '\0'))
1196 : {
1197 : // We don't handle metadata item removal for now
1198 4 : for (const char *const *papszIter = papszMD; papszIter && *papszIter;
1199 : ++papszIter)
1200 : {
1201 2 : char *pszName = nullptr;
1202 2 : const char *pszValue = CPLParseNameValue(*papszIter, &pszName);
1203 2 : if (pszName && pszValue)
1204 2 : SetMetadataItem(pszName, pszValue);
1205 2 : CPLFree(pszName);
1206 : }
1207 : }
1208 2 : return GDALPamRasterBand::SetMetadata(papszMD, pszDomain);
1209 : }
1210 :
1211 : /************************************************************************/
1212 : /* GetOffset() */
1213 : /************************************************************************/
1214 54 : double netCDFRasterBand::GetOffset(int *pbSuccess)
1215 : {
1216 54 : if (pbSuccess != nullptr)
1217 49 : *pbSuccess = static_cast<int>(m_bHaveOffset);
1218 :
1219 54 : return m_dfOffset;
1220 : }
1221 :
1222 : /************************************************************************/
1223 : /* SetOffset() */
1224 : /************************************************************************/
1225 1 : CPLErr netCDFRasterBand::SetOffset(double dfNewOffset)
1226 : {
1227 2 : CPLMutexHolderD(&hNCMutex);
1228 :
1229 : // Write value if in update mode.
1230 1 : if (poDS->GetAccess() == GA_Update)
1231 : {
1232 : // Make sure we are in define mode.
1233 1 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1234 :
1235 1 : const int status = nc_put_att_double(cdfid, nZId, CF_ADD_OFFSET,
1236 : NC_DOUBLE, 1, &dfNewOffset);
1237 :
1238 1 : NCDF_ERR(status);
1239 1 : if (status == NC_NOERR)
1240 : {
1241 1 : SetOffsetNoUpdate(dfNewOffset);
1242 1 : return CE_None;
1243 : }
1244 :
1245 0 : return CE_Failure;
1246 : }
1247 :
1248 0 : SetOffsetNoUpdate(dfNewOffset);
1249 0 : return CE_None;
1250 : }
1251 :
1252 : /************************************************************************/
1253 : /* SetOffsetNoUpdate() */
1254 : /************************************************************************/
1255 17 : void netCDFRasterBand::SetOffsetNoUpdate(double dfVal)
1256 : {
1257 17 : m_dfOffset = dfVal;
1258 17 : m_bHaveOffset = true;
1259 17 : }
1260 :
1261 : /************************************************************************/
1262 : /* GetScale() */
1263 : /************************************************************************/
1264 54 : double netCDFRasterBand::GetScale(int *pbSuccess)
1265 : {
1266 54 : if (pbSuccess != nullptr)
1267 49 : *pbSuccess = static_cast<int>(m_bHaveScale);
1268 :
1269 54 : return m_dfScale;
1270 : }
1271 :
1272 : /************************************************************************/
1273 : /* SetScale() */
1274 : /************************************************************************/
1275 1 : CPLErr netCDFRasterBand::SetScale(double dfNewScale)
1276 : {
1277 2 : CPLMutexHolderD(&hNCMutex);
1278 :
1279 : // Write value if in update mode.
1280 1 : if (poDS->GetAccess() == GA_Update)
1281 : {
1282 : // Make sure we are in define mode.
1283 1 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1284 :
1285 1 : const int status = nc_put_att_double(cdfid, nZId, CF_SCALE_FACTOR,
1286 : NC_DOUBLE, 1, &dfNewScale);
1287 :
1288 1 : NCDF_ERR(status);
1289 1 : if (status == NC_NOERR)
1290 : {
1291 1 : SetScaleNoUpdate(dfNewScale);
1292 1 : return CE_None;
1293 : }
1294 :
1295 0 : return CE_Failure;
1296 : }
1297 :
1298 0 : SetScaleNoUpdate(dfNewScale);
1299 0 : return CE_None;
1300 : }
1301 :
1302 : /************************************************************************/
1303 : /* SetScaleNoUpdate() */
1304 : /************************************************************************/
1305 21 : void netCDFRasterBand::SetScaleNoUpdate(double dfVal)
1306 : {
1307 21 : m_dfScale = dfVal;
1308 21 : m_bHaveScale = true;
1309 21 : }
1310 :
1311 : /************************************************************************/
1312 : /* GetUnitType() */
1313 : /************************************************************************/
1314 :
1315 26 : const char *netCDFRasterBand::GetUnitType()
1316 :
1317 : {
1318 26 : if (!m_osUnitType.empty())
1319 6 : return m_osUnitType;
1320 :
1321 20 : return GDALRasterBand::GetUnitType();
1322 : }
1323 :
1324 : /************************************************************************/
1325 : /* SetUnitType() */
1326 : /************************************************************************/
1327 :
1328 1 : CPLErr netCDFRasterBand::SetUnitType(const char *pszNewValue)
1329 :
1330 : {
1331 2 : CPLMutexHolderD(&hNCMutex);
1332 :
1333 2 : const std::string osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
1334 :
1335 1 : if (!osUnitType.empty())
1336 : {
1337 : // Write value if in update mode.
1338 1 : if (poDS->GetAccess() == GA_Update)
1339 : {
1340 : // Make sure we are in define mode.
1341 1 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(TRUE);
1342 :
1343 1 : const int status = nc_put_att_text(
1344 : cdfid, nZId, CF_UNITS, osUnitType.size(), osUnitType.c_str());
1345 :
1346 1 : NCDF_ERR(status);
1347 1 : if (status == NC_NOERR)
1348 : {
1349 1 : SetUnitTypeNoUpdate(pszNewValue);
1350 1 : return CE_None;
1351 : }
1352 :
1353 0 : return CE_Failure;
1354 : }
1355 : }
1356 :
1357 0 : SetUnitTypeNoUpdate(pszNewValue);
1358 :
1359 0 : return CE_None;
1360 : }
1361 :
1362 : /************************************************************************/
1363 : /* SetUnitTypeNoUpdate() */
1364 : /************************************************************************/
1365 :
1366 499 : void netCDFRasterBand::SetUnitTypeNoUpdate(const char *pszNewValue)
1367 : {
1368 499 : m_osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
1369 499 : }
1370 :
1371 : /************************************************************************/
1372 : /* GetNoDataValue() */
1373 : /************************************************************************/
1374 :
1375 193 : double netCDFRasterBand::GetNoDataValue(int *pbSuccess)
1376 :
1377 : {
1378 193 : if (m_bNoDataSetAsInt64)
1379 : {
1380 0 : if (pbSuccess)
1381 0 : *pbSuccess = TRUE;
1382 0 : return GDALGetNoDataValueCastToDouble(m_nNodataValueInt64);
1383 : }
1384 :
1385 193 : if (m_bNoDataSetAsUInt64)
1386 : {
1387 0 : if (pbSuccess)
1388 0 : *pbSuccess = TRUE;
1389 0 : return GDALGetNoDataValueCastToDouble(m_nNodataValueUInt64);
1390 : }
1391 :
1392 193 : if (m_bNoDataSet)
1393 : {
1394 144 : if (pbSuccess)
1395 127 : *pbSuccess = TRUE;
1396 144 : return m_dfNoDataValue;
1397 : }
1398 :
1399 49 : return GDALPamRasterBand::GetNoDataValue(pbSuccess);
1400 : }
1401 :
1402 : /************************************************************************/
1403 : /* GetNoDataValueAsInt64() */
1404 : /************************************************************************/
1405 :
1406 4 : int64_t netCDFRasterBand::GetNoDataValueAsInt64(int *pbSuccess)
1407 :
1408 : {
1409 4 : if (m_bNoDataSetAsInt64)
1410 : {
1411 4 : if (pbSuccess)
1412 4 : *pbSuccess = TRUE;
1413 :
1414 4 : return m_nNodataValueInt64;
1415 : }
1416 :
1417 0 : return GDALPamRasterBand::GetNoDataValueAsInt64(pbSuccess);
1418 : }
1419 :
1420 : /************************************************************************/
1421 : /* GetNoDataValueAsUInt64() */
1422 : /************************************************************************/
1423 :
1424 4 : uint64_t netCDFRasterBand::GetNoDataValueAsUInt64(int *pbSuccess)
1425 :
1426 : {
1427 4 : if (m_bNoDataSetAsUInt64)
1428 : {
1429 4 : if (pbSuccess)
1430 4 : *pbSuccess = TRUE;
1431 :
1432 4 : return m_nNodataValueUInt64;
1433 : }
1434 :
1435 0 : return GDALPamRasterBand::GetNoDataValueAsUInt64(pbSuccess);
1436 : }
1437 :
1438 : /************************************************************************/
1439 : /* SetNoDataValue() */
1440 : /************************************************************************/
1441 :
1442 134 : CPLErr netCDFRasterBand::SetNoDataValue(double dfNoData)
1443 :
1444 : {
1445 268 : CPLMutexHolderD(&hNCMutex);
1446 :
1447 : // If already set to new value, don't do anything.
1448 134 : if (m_bNoDataSet && CPLIsEqual(dfNoData, m_dfNoDataValue))
1449 19 : return CE_None;
1450 :
1451 : // Write value if in update mode.
1452 115 : if (poDS->GetAccess() == GA_Update)
1453 : {
1454 : // netcdf-4 does not allow to set _FillValue after leaving define mode,
1455 : // but it is ok if variable has not been written to, so only print
1456 : // debug. See bug #4484.
1457 125 : if (m_bNoDataSet &&
1458 10 : !cpl::down_cast<netCDFDataset *>(poDS)->GetDefineMode())
1459 : {
1460 0 : CPLDebug("GDAL_netCDF",
1461 : "Setting NoDataValue to %.17g (previously set to %.17g) "
1462 : "but file is no longer in define mode (id #%d, band #%d)",
1463 : dfNoData, m_dfNoDataValue, cdfid, nBand);
1464 : }
1465 : #ifdef NCDF_DEBUG
1466 : else
1467 : {
1468 : CPLDebug("GDAL_netCDF",
1469 : "Setting NoDataValue to %.17g (id #%d, band #%d)",
1470 : dfNoData, cdfid, nBand);
1471 : }
1472 : #endif
1473 : // Make sure we are in define mode.
1474 115 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1475 :
1476 : int status;
1477 115 : if (eDataType == GDT_UInt8)
1478 : {
1479 6 : if (bSignedData)
1480 : {
1481 0 : signed char cNoDataValue = static_cast<signed char>(dfNoData);
1482 0 : status = nc_put_att_schar(cdfid, nZId, NCDF_FillValue,
1483 : nc_datatype, 1, &cNoDataValue);
1484 : }
1485 : else
1486 : {
1487 6 : const unsigned char ucNoDataValue =
1488 6 : static_cast<unsigned char>(dfNoData);
1489 6 : status = nc_put_att_uchar(cdfid, nZId, NCDF_FillValue,
1490 : nc_datatype, 1, &ucNoDataValue);
1491 : }
1492 : }
1493 109 : else if (eDataType == GDT_Int16)
1494 : {
1495 14 : short nsNoDataValue = static_cast<short>(dfNoData);
1496 14 : status = nc_put_att_short(cdfid, nZId, NCDF_FillValue, nc_datatype,
1497 : 1, &nsNoDataValue);
1498 : }
1499 95 : else if (eDataType == GDT_Int32)
1500 : {
1501 27 : int nNoDataValue = static_cast<int>(dfNoData);
1502 27 : status = nc_put_att_int(cdfid, nZId, NCDF_FillValue, nc_datatype, 1,
1503 : &nNoDataValue);
1504 : }
1505 68 : else if (eDataType == GDT_Float32)
1506 : {
1507 31 : float fNoDataValue = static_cast<float>(dfNoData);
1508 31 : status = nc_put_att_float(cdfid, nZId, NCDF_FillValue, nc_datatype,
1509 : 1, &fNoDataValue);
1510 : }
1511 43 : else if (eDataType == GDT_UInt16 &&
1512 6 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat ==
1513 : NCDF_FORMAT_NC4)
1514 : {
1515 6 : unsigned short usNoDataValue =
1516 6 : static_cast<unsigned short>(dfNoData);
1517 6 : status = nc_put_att_ushort(cdfid, nZId, NCDF_FillValue, nc_datatype,
1518 : 1, &usNoDataValue);
1519 : }
1520 38 : else if (eDataType == GDT_UInt32 &&
1521 7 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat ==
1522 : NCDF_FORMAT_NC4)
1523 : {
1524 7 : unsigned int unNoDataValue = static_cast<unsigned int>(dfNoData);
1525 7 : status = nc_put_att_uint(cdfid, nZId, NCDF_FillValue, nc_datatype,
1526 : 1, &unNoDataValue);
1527 : }
1528 : else
1529 : {
1530 24 : status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
1531 : 1, &dfNoData);
1532 : }
1533 :
1534 115 : NCDF_ERR(status);
1535 :
1536 : // Update status if write worked.
1537 115 : if (status == NC_NOERR)
1538 : {
1539 115 : SetNoDataValueNoUpdate(dfNoData);
1540 115 : return CE_None;
1541 : }
1542 :
1543 0 : return CE_Failure;
1544 : }
1545 :
1546 0 : SetNoDataValueNoUpdate(dfNoData);
1547 0 : return CE_None;
1548 : }
1549 :
1550 : /************************************************************************/
1551 : /* SetNoDataValueNoUpdate() */
1552 : /************************************************************************/
1553 :
1554 443 : void netCDFRasterBand::SetNoDataValueNoUpdate(double dfNoData)
1555 : {
1556 443 : m_dfNoDataValue = dfNoData;
1557 443 : m_bNoDataSet = true;
1558 443 : m_bNoDataSetAsInt64 = false;
1559 443 : m_bNoDataSetAsUInt64 = false;
1560 443 : }
1561 :
1562 : /************************************************************************/
1563 : /* SetNoDataValueAsInt64() */
1564 : /************************************************************************/
1565 :
1566 3 : CPLErr netCDFRasterBand::SetNoDataValueAsInt64(int64_t nNoData)
1567 :
1568 : {
1569 6 : CPLMutexHolderD(&hNCMutex);
1570 :
1571 : // If already set to new value, don't do anything.
1572 3 : if (m_bNoDataSetAsInt64 && nNoData == m_nNodataValueInt64)
1573 0 : return CE_None;
1574 :
1575 : // Write value if in update mode.
1576 3 : if (poDS->GetAccess() == GA_Update)
1577 : {
1578 : // netcdf-4 does not allow to set NCDF_FillValue after leaving define mode,
1579 : // but it is ok if variable has not been written to, so only print
1580 : // debug. See bug #4484.
1581 3 : if (m_bNoDataSetAsInt64 &&
1582 0 : !cpl::down_cast<netCDFDataset *>(poDS)->GetDefineMode())
1583 : {
1584 0 : CPLDebug("GDAL_netCDF",
1585 : "Setting NoDataValue to " CPL_FRMT_GIB
1586 : " (previously set to " CPL_FRMT_GIB ") "
1587 : "but file is no longer in define mode (id #%d, band #%d)",
1588 : static_cast<GIntBig>(nNoData),
1589 0 : static_cast<GIntBig>(m_nNodataValueInt64), cdfid, nBand);
1590 : }
1591 : #ifdef NCDF_DEBUG
1592 : else
1593 : {
1594 : CPLDebug("GDAL_netCDF",
1595 : "Setting NoDataValue to " CPL_FRMT_GIB
1596 : " (id #%d, band #%d)",
1597 : static_cast<GIntBig>(nNoData), cdfid, nBand);
1598 : }
1599 : #endif
1600 : // Make sure we are in define mode.
1601 3 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1602 :
1603 : int status;
1604 6 : if (eDataType == GDT_Int64 &&
1605 3 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
1606 : {
1607 3 : long long tmp = static_cast<long long>(nNoData);
1608 3 : status = nc_put_att_longlong(cdfid, nZId, NCDF_FillValue,
1609 : nc_datatype, 1, &tmp);
1610 : }
1611 : else
1612 : {
1613 0 : double dfNoData = static_cast<double>(nNoData);
1614 0 : status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
1615 : 1, &dfNoData);
1616 : }
1617 :
1618 3 : NCDF_ERR(status);
1619 :
1620 : // Update status if write worked.
1621 3 : if (status == NC_NOERR)
1622 : {
1623 3 : SetNoDataValueNoUpdate(nNoData);
1624 3 : return CE_None;
1625 : }
1626 :
1627 0 : return CE_Failure;
1628 : }
1629 :
1630 0 : SetNoDataValueNoUpdate(nNoData);
1631 0 : return CE_None;
1632 : }
1633 :
1634 : /************************************************************************/
1635 : /* SetNoDataValueNoUpdate() */
1636 : /************************************************************************/
1637 :
1638 11 : void netCDFRasterBand::SetNoDataValueNoUpdate(int64_t nNoData)
1639 : {
1640 11 : m_nNodataValueInt64 = nNoData;
1641 11 : m_bNoDataSet = false;
1642 11 : m_bNoDataSetAsInt64 = true;
1643 11 : m_bNoDataSetAsUInt64 = false;
1644 11 : }
1645 :
1646 : /************************************************************************/
1647 : /* SetNoDataValueAsUInt64() */
1648 : /************************************************************************/
1649 :
1650 3 : CPLErr netCDFRasterBand::SetNoDataValueAsUInt64(uint64_t nNoData)
1651 :
1652 : {
1653 6 : CPLMutexHolderD(&hNCMutex);
1654 :
1655 : // If already set to new value, don't do anything.
1656 3 : if (m_bNoDataSetAsUInt64 && nNoData == m_nNodataValueUInt64)
1657 0 : return CE_None;
1658 :
1659 : // Write value if in update mode.
1660 3 : if (poDS->GetAccess() == GA_Update)
1661 : {
1662 : // netcdf-4 does not allow to set _FillValue after leaving define mode,
1663 : // but it is ok if variable has not been written to, so only print
1664 : // debug. See bug #4484.
1665 3 : if (m_bNoDataSetAsUInt64 &&
1666 0 : !cpl::down_cast<netCDFDataset *>(poDS)->GetDefineMode())
1667 : {
1668 0 : CPLDebug("GDAL_netCDF",
1669 : "Setting NoDataValue to " CPL_FRMT_GUIB
1670 : " (previously set to " CPL_FRMT_GUIB ") "
1671 : "but file is no longer in define mode (id #%d, band #%d)",
1672 : static_cast<GUIntBig>(nNoData),
1673 0 : static_cast<GUIntBig>(m_nNodataValueUInt64), cdfid, nBand);
1674 : }
1675 : #ifdef NCDF_DEBUG
1676 : else
1677 : {
1678 : CPLDebug("GDAL_netCDF",
1679 : "Setting NoDataValue to " CPL_FRMT_GUIB
1680 : " (id #%d, band #%d)",
1681 : static_cast<GUIntBig>(nNoData), cdfid, nBand);
1682 : }
1683 : #endif
1684 : // Make sure we are in define mode.
1685 3 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1686 :
1687 : int status;
1688 6 : if (eDataType == GDT_UInt64 &&
1689 3 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
1690 : {
1691 3 : unsigned long long tmp = static_cast<long long>(nNoData);
1692 3 : status = nc_put_att_ulonglong(cdfid, nZId, NCDF_FillValue,
1693 : nc_datatype, 1, &tmp);
1694 : }
1695 : else
1696 : {
1697 0 : double dfNoData = static_cast<double>(nNoData);
1698 0 : status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
1699 : 1, &dfNoData);
1700 : }
1701 :
1702 3 : NCDF_ERR(status);
1703 :
1704 : // Update status if write worked.
1705 3 : if (status == NC_NOERR)
1706 : {
1707 3 : SetNoDataValueNoUpdate(nNoData);
1708 3 : return CE_None;
1709 : }
1710 :
1711 0 : return CE_Failure;
1712 : }
1713 :
1714 0 : SetNoDataValueNoUpdate(nNoData);
1715 0 : return CE_None;
1716 : }
1717 :
1718 : /************************************************************************/
1719 : /* SetNoDataValueNoUpdate() */
1720 : /************************************************************************/
1721 :
1722 10 : void netCDFRasterBand::SetNoDataValueNoUpdate(uint64_t nNoData)
1723 : {
1724 10 : m_nNodataValueUInt64 = nNoData;
1725 10 : m_bNoDataSet = false;
1726 10 : m_bNoDataSetAsInt64 = false;
1727 10 : m_bNoDataSetAsUInt64 = true;
1728 10 : }
1729 :
1730 : /************************************************************************/
1731 : /* DeleteNoDataValue() */
1732 : /************************************************************************/
1733 :
1734 : #ifdef notdef
1735 : CPLErr netCDFRasterBand::DeleteNoDataValue()
1736 :
1737 : {
1738 : CPLMutexHolderD(&hNCMutex);
1739 :
1740 : if (!bNoDataSet)
1741 : return CE_None;
1742 :
1743 : // Write value if in update mode.
1744 : if (poDS->GetAccess() == GA_Update)
1745 : {
1746 : // Make sure we are in define mode.
1747 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1748 :
1749 : status = nc_del_att(cdfid, nZId, NCDF_FillValue);
1750 :
1751 : NCDF_ERR(status);
1752 :
1753 : // Update status if write worked.
1754 : if (status == NC_NOERR)
1755 : {
1756 : dfNoDataValue = 0.0;
1757 : bNoDataSet = false;
1758 : return CE_None;
1759 : }
1760 :
1761 : return CE_Failure;
1762 : }
1763 :
1764 : dfNoDataValue = 0.0;
1765 : bNoDataSet = false;
1766 : return CE_None;
1767 : }
1768 : #endif
1769 :
1770 : /************************************************************************/
1771 : /* SerializeToXML() */
1772 : /************************************************************************/
1773 :
1774 5 : CPLXMLNode *netCDFRasterBand::SerializeToXML(const char * /* pszUnused */)
1775 : {
1776 : // Overridden from GDALPamDataset to add only band histogram
1777 : // and statistics. See bug #4244.
1778 5 : if (psPam == nullptr)
1779 0 : return nullptr;
1780 :
1781 : // Setup root node and attributes.
1782 : CPLXMLNode *psTree =
1783 5 : CPLCreateXMLNode(nullptr, CXT_Element, "PAMRasterBand");
1784 :
1785 5 : if (GetBand() > 0)
1786 : {
1787 10 : CPLString oFmt;
1788 5 : CPLSetXMLValue(psTree, "#band", oFmt.Printf("%d", GetBand()));
1789 : }
1790 :
1791 : // Histograms.
1792 5 : if (psPam->psSavedHistograms != nullptr)
1793 1 : CPLAddXMLChild(psTree, CPLCloneXMLTree(psPam->psSavedHistograms));
1794 :
1795 : // Metadata (statistics only).
1796 5 : GDALMultiDomainMetadata oMDMDStats;
1797 5 : const char *papszMDStats[] = {"STATISTICS_MINIMUM", "STATISTICS_MAXIMUM",
1798 : "STATISTICS_MEAN", "STATISTICS_STDDEV",
1799 : nullptr};
1800 25 : for (int i = 0; i < CSLCount(papszMDStats); i++)
1801 : {
1802 20 : const char *pszMDI = GetMetadataItem(papszMDStats[i]);
1803 20 : if (pszMDI)
1804 4 : oMDMDStats.SetMetadataItem(papszMDStats[i], pszMDI);
1805 : }
1806 5 : CPLXMLNode *psMD = oMDMDStats.Serialize();
1807 :
1808 5 : if (psMD != nullptr)
1809 : {
1810 1 : if (psMD->psChild == nullptr)
1811 0 : CPLDestroyXMLNode(psMD);
1812 : else
1813 1 : CPLAddXMLChild(psTree, psMD);
1814 : }
1815 :
1816 : // We don't want to return anything if we had no metadata to attach.
1817 5 : if (psTree->psChild == nullptr || psTree->psChild->psNext == nullptr)
1818 : {
1819 3 : CPLDestroyXMLNode(psTree);
1820 3 : psTree = nullptr;
1821 : }
1822 :
1823 5 : return psTree;
1824 : }
1825 :
1826 : /************************************************************************/
1827 : /* Get1DVariableIndexedByDimension() */
1828 : /************************************************************************/
1829 :
1830 81 : static int Get1DVariableIndexedByDimension(int cdfid, int nDimId,
1831 : const char *pszDimName,
1832 : bool bVerboseError, int *pnGroupID)
1833 : {
1834 81 : *pnGroupID = -1;
1835 81 : int nVarID = -1;
1836 : // First try to find a variable whose name is identical to the dimension
1837 : // name, and check that it is indeed indexed by this dimension
1838 81 : if (NCDFResolveVar(cdfid, pszDimName, pnGroupID, &nVarID) == CE_None)
1839 : {
1840 67 : int nDimCountOfVariable = 0;
1841 67 : nc_inq_varndims(*pnGroupID, nVarID, &nDimCountOfVariable);
1842 67 : if (nDimCountOfVariable == 1)
1843 : {
1844 67 : int nDimIdOfVariable = -1;
1845 67 : nc_inq_vardimid(*pnGroupID, nVarID, &nDimIdOfVariable);
1846 67 : if (nDimIdOfVariable == nDimId)
1847 : {
1848 67 : return nVarID;
1849 : }
1850 : }
1851 : }
1852 :
1853 : // Otherwise iterate over the variables to find potential candidates
1854 : // TODO: should be modified to search also in other groups using the same
1855 : // logic than in NCDFResolveVar(), but maybe not needed if it's a
1856 : // very rare case? and I think this is not CF compliant.
1857 14 : int nvars = 0;
1858 14 : CPL_IGNORE_RET_VAL(nc_inq(cdfid, nullptr, &nvars, nullptr, nullptr));
1859 :
1860 14 : int nCountCandidateVars = 0;
1861 14 : int nCandidateVarID = -1;
1862 65 : for (int k = 0; k < nvars; k++)
1863 : {
1864 51 : int nDimCountOfVariable = 0;
1865 51 : nc_inq_varndims(cdfid, k, &nDimCountOfVariable);
1866 51 : if (nDimCountOfVariable == 1)
1867 : {
1868 27 : int nDimIdOfVariable = -1;
1869 27 : nc_inq_vardimid(cdfid, k, &nDimIdOfVariable);
1870 27 : if (nDimIdOfVariable == nDimId)
1871 : {
1872 7 : nCountCandidateVars++;
1873 7 : nCandidateVarID = k;
1874 : }
1875 : }
1876 : }
1877 14 : if (nCountCandidateVars > 1)
1878 : {
1879 1 : if (bVerboseError)
1880 : {
1881 1 : CPLError(CE_Warning, CPLE_AppDefined,
1882 : "Several 1D variables are indexed by dimension %s",
1883 : pszDimName);
1884 : }
1885 1 : *pnGroupID = -1;
1886 1 : return -1;
1887 : }
1888 13 : else if (nCandidateVarID < 0)
1889 : {
1890 8 : if (bVerboseError)
1891 : {
1892 8 : CPLError(CE_Warning, CPLE_AppDefined,
1893 : "No 1D variable is indexed by dimension %s", pszDimName);
1894 : }
1895 : }
1896 13 : *pnGroupID = cdfid;
1897 13 : return nCandidateVarID;
1898 : }
1899 :
1900 : /************************************************************************/
1901 : /* CreateMetadataFromAttributes() */
1902 : /************************************************************************/
1903 :
1904 498 : void netCDFRasterBand::CreateMetadataFromAttributes()
1905 : {
1906 498 : char szVarName[NC_MAX_NAME + 1] = {};
1907 498 : int status = nc_inq_varname(cdfid, nZId, szVarName);
1908 498 : NCDF_ERR(status);
1909 :
1910 498 : GDALPamRasterBand::SetMetadataItem("NETCDF_VARNAME", szVarName);
1911 :
1912 : // Get attribute metadata.
1913 498 : int nAtt = 0;
1914 498 : NCDF_ERR(nc_inq_varnatts(cdfid, nZId, &nAtt));
1915 :
1916 2120 : for (int i = 0; i < nAtt; i++)
1917 : {
1918 1622 : char szMetaName[NC_MAX_NAME + 1] = {};
1919 1622 : status = nc_inq_attname(cdfid, nZId, i, szMetaName);
1920 1622 : if (status != NC_NOERR)
1921 12 : continue;
1922 :
1923 1622 : if (GDALPamRasterBand::GetMetadataItem(szMetaName) != nullptr)
1924 : {
1925 12 : continue;
1926 : }
1927 :
1928 1610 : char *pszMetaValue = nullptr;
1929 1610 : if (NCDFGetAttr(cdfid, nZId, szMetaName, &pszMetaValue) == CE_None)
1930 : {
1931 1610 : GDALPamRasterBand::SetMetadataItem(szMetaName, pszMetaValue);
1932 : }
1933 : else
1934 : {
1935 0 : CPLDebug("GDAL_netCDF", "invalid Band metadata %s", szMetaName);
1936 : }
1937 :
1938 1610 : if (pszMetaValue)
1939 : {
1940 1610 : CPLFree(pszMetaValue);
1941 1610 : pszMetaValue = nullptr;
1942 : }
1943 : }
1944 498 : }
1945 :
1946 : /************************************************************************/
1947 : /* CreateMetadataFromOtherVars() */
1948 : /************************************************************************/
1949 :
1950 53 : void netCDFRasterBand::CreateMetadataFromOtherVars()
1951 :
1952 : {
1953 53 : CPLAssert(!m_bCreateMetadataFromOtherVarsDone);
1954 53 : m_bCreateMetadataFromOtherVarsDone = true;
1955 :
1956 53 : netCDFDataset *l_poDS = cpl::down_cast<netCDFDataset *>(poDS);
1957 53 : const int nPamFlagsBackup = l_poDS->nPamFlags;
1958 :
1959 : // Compute all dimensions from Band number and save in Metadata.
1960 53 : int nd = 0;
1961 53 : nc_inq_varndims(cdfid, nZId, &nd);
1962 : // Compute multidimention band position.
1963 : //
1964 : // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
1965 : // if Data[2,3,4,x,y]
1966 : //
1967 : // BandPos0 = (nBand) / (3*4)
1968 : // BandPos1 = (nBand - BandPos0*(3*4)) / (4)
1969 : // BandPos2 = (nBand - BandPos0*(3*4)) % (4)
1970 :
1971 53 : int Sum = 1;
1972 53 : if (nd == 3)
1973 : {
1974 5 : Sum *= panBandZLev[0];
1975 : }
1976 :
1977 : // Loop over non-spatial dimensions.
1978 53 : int Taken = 0;
1979 :
1980 93 : for (int i = 0; i < nd - 2; i++)
1981 : {
1982 : int result;
1983 40 : if (i != nd - 2 - 1)
1984 : {
1985 18 : Sum = 1;
1986 37 : for (int j = i + 1; j < nd - 2; j++)
1987 : {
1988 19 : Sum *= panBandZLev[j];
1989 : }
1990 18 : result = static_cast<int>((nLevel - Taken) / Sum);
1991 : }
1992 : else
1993 : {
1994 22 : result = static_cast<int>((nLevel - Taken) % Sum);
1995 : }
1996 :
1997 40 : char szName[NC_MAX_NAME + 1] = {};
1998 40 : snprintf(szName, sizeof(szName), "%s",
1999 40 : l_poDS->papszDimName[l_poDS->m_anDimIds[panBandZPos[i]]]);
2000 :
2001 : char szMetaName[NC_MAX_NAME + 1 + 32];
2002 40 : snprintf(szMetaName, sizeof(szMetaName), "NETCDF_DIM_%s", szName);
2003 :
2004 40 : const int nGroupID = l_poDS->m_anExtraDimGroupIds[i];
2005 40 : const int nVarID = l_poDS->m_anExtraDimVarIds[i];
2006 40 : if (nVarID < 0)
2007 : {
2008 2 : GDALPamRasterBand::SetMetadataItem(szMetaName,
2009 : CPLSPrintf("%d", result + 1));
2010 : }
2011 : else
2012 : {
2013 : // TODO: Make sure all the status checks make sense.
2014 :
2015 38 : nc_type nVarType = NC_NAT;
2016 38 : /* status = */ nc_inq_vartype(nGroupID, nVarID, &nVarType);
2017 :
2018 38 : int nDims = 0;
2019 38 : /* status = */ nc_inq_varndims(nGroupID, nVarID, &nDims);
2020 :
2021 38 : char szMetaTemp[256] = {};
2022 38 : if (nDims == 1)
2023 : {
2024 38 : size_t count[1] = {1};
2025 38 : size_t start[1] = {static_cast<size_t>(result)};
2026 :
2027 38 : switch (nVarType)
2028 : {
2029 0 : case NC_BYTE:
2030 : // TODO: Check for signed/unsigned byte.
2031 : signed char cData;
2032 0 : /* status = */ nc_get_vara_schar(nGroupID, nVarID,
2033 : start, count, &cData);
2034 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", cData);
2035 0 : break;
2036 0 : case NC_SHORT:
2037 : short sData;
2038 0 : /* status = */ nc_get_vara_short(nGroupID, nVarID,
2039 : start, count, &sData);
2040 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", sData);
2041 0 : break;
2042 19 : case NC_INT:
2043 : {
2044 : int nData;
2045 19 : /* status = */ nc_get_vara_int(nGroupID, nVarID, start,
2046 : count, &nData);
2047 19 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", nData);
2048 19 : break;
2049 : }
2050 0 : case NC_FLOAT:
2051 : float fData;
2052 0 : /* status = */ nc_get_vara_float(nGroupID, nVarID,
2053 : start, count, &fData);
2054 0 : CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.8g",
2055 : fData);
2056 0 : break;
2057 18 : case NC_DOUBLE:
2058 : double dfData;
2059 18 : /* status = */ nc_get_vara_double(
2060 : nGroupID, nVarID, start, count, &dfData);
2061 18 : CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.16g",
2062 : dfData);
2063 18 : break;
2064 0 : case NC_UBYTE:
2065 : unsigned char ucData;
2066 0 : /* status = */ nc_get_vara_uchar(nGroupID, nVarID,
2067 : start, count, &ucData);
2068 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", ucData);
2069 0 : break;
2070 0 : case NC_USHORT:
2071 : unsigned short usData;
2072 0 : /* status = */ nc_get_vara_ushort(
2073 : nGroupID, nVarID, start, count, &usData);
2074 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", usData);
2075 0 : break;
2076 0 : case NC_UINT:
2077 : {
2078 : unsigned int unData;
2079 0 : /* status = */ nc_get_vara_uint(nGroupID, nVarID, start,
2080 : count, &unData);
2081 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", unData);
2082 0 : break;
2083 : }
2084 1 : case NC_INT64:
2085 : {
2086 : long long nData;
2087 1 : /* status = */ nc_get_vara_longlong(
2088 : nGroupID, nVarID, start, count, &nData);
2089 1 : snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GIB,
2090 : nData);
2091 1 : break;
2092 : }
2093 0 : case NC_UINT64:
2094 : {
2095 : unsigned long long unData;
2096 0 : /* status = */ nc_get_vara_ulonglong(
2097 : nGroupID, nVarID, start, count, &unData);
2098 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GUIB,
2099 : unData);
2100 0 : break;
2101 : }
2102 0 : default:
2103 0 : CPLDebug("GDAL_netCDF", "invalid dim %s, type=%d",
2104 : szMetaTemp, nVarType);
2105 0 : break;
2106 : }
2107 : }
2108 : else
2109 : {
2110 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", result + 1);
2111 : }
2112 :
2113 : // Save dimension value.
2114 : // NOTE: removed #original_units as not part of CF-1.
2115 :
2116 38 : GDALPamRasterBand::SetMetadataItem(szMetaName, szMetaTemp);
2117 : }
2118 :
2119 : // Avoid int32 overflow. Perhaps something more sensible to do here ?
2120 40 : if (result > 0 && Sum > INT_MAX / result)
2121 0 : break;
2122 40 : if (Taken > INT_MAX - result * Sum)
2123 0 : break;
2124 :
2125 40 : Taken += result * Sum;
2126 : } // End loop non-spatial dimensions.
2127 :
2128 53 : l_poDS->nPamFlags = nPamFlagsBackup;
2129 53 : }
2130 :
2131 : /************************************************************************/
2132 : /* CheckData() */
2133 : /************************************************************************/
2134 : template <class T>
2135 5912 : void netCDFRasterBand::CheckData(void *pImage, void *pImageNC,
2136 : size_t nTmpBlockXSize, size_t nTmpBlockYSize,
2137 : bool bCheckIsNan)
2138 : {
2139 5912 : CPLAssert(pImage != nullptr && pImageNC != nullptr);
2140 :
2141 : // If this block is not a full block (in the x axis), we need to re-arrange
2142 : // the data this is because partial blocks are not arranged the same way in
2143 : // netcdf and gdal.
2144 5912 : if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
2145 : {
2146 6 : T *ptrWrite = static_cast<T *>(pImage);
2147 6 : T *ptrRead = static_cast<T *>(pImageNC);
2148 29 : for (size_t j = 0; j < nTmpBlockYSize;
2149 23 : j++, ptrWrite += nBlockXSize, ptrRead += nTmpBlockXSize)
2150 : {
2151 23 : memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T));
2152 : }
2153 : }
2154 :
2155 : // Is valid data checking needed or requested?
2156 5912 : if (bValidRangeValid || bCheckIsNan)
2157 : {
2158 1345 : T *ptrImage = static_cast<T *>(pImage);
2159 2744 : for (size_t j = 0; j < nTmpBlockYSize; j++)
2160 : {
2161 : // k moves along the gdal block, skipping the out-of-range pixels.
2162 1399 : size_t k = j * nBlockXSize;
2163 98618 : for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
2164 : {
2165 : // Check for nodata and nan.
2166 97219 : if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
2167 6301 : continue;
2168 90918 : if (bCheckIsNan && std::isnan((double)ptrImage[k]))
2169 : {
2170 5737 : ptrImage[k] = (T)m_dfNoDataValue;
2171 5737 : continue;
2172 : }
2173 : // Check for valid_range.
2174 85181 : if (bValidRangeValid)
2175 : {
2176 40986 : if (((adfValidRange[0] != m_dfNoDataValue) &&
2177 40986 : (ptrImage[k] < (T)adfValidRange[0])) ||
2178 40983 : ((adfValidRange[1] != m_dfNoDataValue) &&
2179 40983 : (ptrImage[k] > (T)adfValidRange[1])))
2180 : {
2181 4 : ptrImage[k] = (T)m_dfNoDataValue;
2182 : }
2183 : }
2184 : }
2185 : }
2186 : }
2187 :
2188 : // If minimum longitude is > 180, subtract 360 from all.
2189 : // If not, disable checking for further calls (check just once).
2190 : // Only check first and last block elements since lon must be monotonic.
2191 5912 : const bool bIsSigned = std::numeric_limits<T>::is_signed;
2192 5581 : if (bCheckLongitude && bIsSigned &&
2193 11 : !CPLIsEqual((double)((T *)pImage)[0], m_dfNoDataValue) &&
2194 10 : !CPLIsEqual((double)((T *)pImage)[nTmpBlockXSize - 1],
2195 2796 : m_dfNoDataValue) &&
2196 10 : std::min(((T *)pImage)[0], ((T *)pImage)[nTmpBlockXSize - 1]) > 180.0)
2197 : {
2198 0 : T *ptrImage = static_cast<T *>(pImage);
2199 0 : for (size_t j = 0; j < nTmpBlockYSize; j++)
2200 : {
2201 0 : size_t k = j * nBlockXSize;
2202 0 : for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
2203 : {
2204 0 : if (!CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
2205 0 : ptrImage[k] = static_cast<T>(ptrImage[k] - 360);
2206 : }
2207 : }
2208 : }
2209 : else
2210 : {
2211 5912 : bCheckLongitude = false;
2212 : }
2213 5912 : }
2214 :
2215 : /************************************************************************/
2216 : /* CheckDataCpx() */
2217 : /************************************************************************/
2218 : template <class T>
2219 25 : void netCDFRasterBand::CheckDataCpx(void *pImage, void *pImageNC,
2220 : size_t nTmpBlockXSize,
2221 : size_t nTmpBlockYSize, bool bCheckIsNan)
2222 : {
2223 25 : CPLAssert(pImage != nullptr && pImageNC != nullptr);
2224 :
2225 : // If this block is not a full block (in the x axis), we need to re-arrange
2226 : // the data this is because partial blocks are not arranged the same way in
2227 : // netcdf and gdal.
2228 25 : if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
2229 : {
2230 0 : T *ptrWrite = static_cast<T *>(pImage);
2231 0 : T *ptrRead = static_cast<T *>(pImageNC);
2232 0 : for (size_t j = 0; j < nTmpBlockYSize; j++,
2233 0 : ptrWrite += (2 * nBlockXSize),
2234 0 : ptrRead += (2 * nTmpBlockXSize))
2235 : {
2236 0 : memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T) * 2);
2237 : }
2238 : }
2239 :
2240 : // Is valid data checking needed or requested?
2241 25 : if (bValidRangeValid || bCheckIsNan)
2242 : {
2243 0 : T *ptrImage = static_cast<T *>(pImage);
2244 0 : for (size_t j = 0; j < nTmpBlockYSize; j++)
2245 : {
2246 : // k moves along the gdal block, skipping the out-of-range pixels.
2247 0 : size_t k = 2 * j * nBlockXSize;
2248 0 : for (size_t i = 0; i < (2 * nTmpBlockXSize); i++, k++)
2249 : {
2250 : // Check for nodata and nan.
2251 0 : if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
2252 0 : continue;
2253 0 : if (bCheckIsNan && std::isnan((double)ptrImage[k]))
2254 : {
2255 0 : ptrImage[k] = (T)m_dfNoDataValue;
2256 0 : continue;
2257 : }
2258 : // Check for valid_range.
2259 0 : if (bValidRangeValid)
2260 : {
2261 0 : if (((adfValidRange[0] != m_dfNoDataValue) &&
2262 0 : (ptrImage[k] < (T)adfValidRange[0])) ||
2263 0 : ((adfValidRange[1] != m_dfNoDataValue) &&
2264 0 : (ptrImage[k] > (T)adfValidRange[1])))
2265 : {
2266 0 : ptrImage[k] = (T)m_dfNoDataValue;
2267 : }
2268 : }
2269 : }
2270 : }
2271 : }
2272 25 : }
2273 :
2274 : /************************************************************************/
2275 : /* FetchNetcdfChunk() */
2276 : /************************************************************************/
2277 :
2278 5937 : bool netCDFRasterBand::FetchNetcdfChunk(size_t xstart, size_t ystart,
2279 : void *pImage)
2280 : {
2281 5937 : size_t start[MAX_NC_DIMS] = {};
2282 5937 : size_t edge[MAX_NC_DIMS] = {};
2283 :
2284 5937 : start[nBandXPos] = xstart;
2285 5937 : edge[nBandXPos] = nBlockXSize;
2286 5937 : if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
2287 6 : edge[nBandXPos] = nRasterXSize - start[nBandXPos];
2288 5937 : if (nBandYPos >= 0)
2289 : {
2290 5933 : start[nBandYPos] = ystart;
2291 5933 : edge[nBandYPos] = nBlockYSize;
2292 5933 : if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
2293 4 : edge[nBandYPos] = nRasterYSize - start[nBandYPos];
2294 : }
2295 5937 : const size_t nYChunkSize = nBandYPos < 0 ? 1 : edge[nBandYPos];
2296 :
2297 : #ifdef NCDF_DEBUG
2298 : CPLDebug("GDAL_netCDF", "start={%ld,%ld} edge={%ld,%ld} bBottomUp=%d",
2299 : start[nBandXPos], nBandYPos < 0 ? 0 : start[nBandYPos],
2300 : edge[nBandXPos], nYChunkSize, ((netCDFDataset *)poDS)->bBottomUp);
2301 : #endif
2302 :
2303 5937 : int nd = 0;
2304 5937 : nc_inq_varndims(cdfid, nZId, &nd);
2305 5937 : if (nd == 3)
2306 : {
2307 1078 : start[panBandZPos[0]] = nLevel; // z
2308 1078 : edge[panBandZPos[0]] = 1;
2309 : }
2310 :
2311 : // Compute multidimention band position.
2312 : //
2313 : // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
2314 : // if Data[2,3,4,x,y]
2315 : //
2316 : // BandPos0 = (nBand) / (3*4)
2317 : // BandPos1 = (nBand - (3*4)) / (4)
2318 : // BandPos2 = (nBand - (3*4)) % (4)
2319 5937 : if (nd > 3)
2320 : {
2321 160 : int Sum = -1;
2322 160 : int Taken = 0;
2323 480 : for (int i = 0; i < nd - 2; i++)
2324 : {
2325 320 : if (i != nd - 2 - 1)
2326 : {
2327 160 : Sum = 1;
2328 320 : for (int j = i + 1; j < nd - 2; j++)
2329 : {
2330 160 : Sum *= panBandZLev[j];
2331 : }
2332 160 : start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
2333 160 : edge[panBandZPos[i]] = 1;
2334 : }
2335 : else
2336 : {
2337 160 : start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
2338 160 : edge[panBandZPos[i]] = 1;
2339 : }
2340 320 : Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
2341 : }
2342 : }
2343 :
2344 : // Make sure we are in data mode.
2345 5937 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
2346 :
2347 : // If this block is not a full block in the x axis, we need to
2348 : // re-arrange the data because partial blocks are not arranged the
2349 : // same way in netcdf and gdal, so we first we read the netcdf data at
2350 : // the end of the gdal block buffer then re-arrange rows in CheckData().
2351 5937 : void *pImageNC = pImage;
2352 5937 : if (edge[nBandXPos] != static_cast<size_t>(nBlockXSize))
2353 : {
2354 6 : pImageNC = static_cast<GByte *>(pImage) +
2355 6 : ((static_cast<size_t>(nBlockXSize) * nBlockYSize -
2356 12 : edge[nBandXPos] * nYChunkSize) *
2357 6 : GDALGetDataTypeSizeBytes(eDataType));
2358 : }
2359 :
2360 : // Read data according to type.
2361 : int status;
2362 5937 : if (eDataType == GDT_UInt8)
2363 : {
2364 3105 : if (bSignedData)
2365 : {
2366 0 : status = nc_get_vara_schar(cdfid, nZId, start, edge,
2367 : static_cast<signed char *>(pImageNC));
2368 0 : if (status == NC_NOERR)
2369 0 : CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
2370 : nYChunkSize, false);
2371 : }
2372 : else
2373 : {
2374 3105 : status = nc_get_vara_uchar(cdfid, nZId, start, edge,
2375 : static_cast<unsigned char *>(pImageNC));
2376 3105 : if (status == NC_NOERR)
2377 3105 : CheckData<unsigned char>(pImage, pImageNC, edge[nBandXPos],
2378 : nYChunkSize, false);
2379 : }
2380 : }
2381 2832 : else if (eDataType == GDT_Int8)
2382 : {
2383 60 : status = nc_get_vara_schar(cdfid, nZId, start, edge,
2384 : static_cast<signed char *>(pImageNC));
2385 60 : if (status == NC_NOERR)
2386 60 : CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
2387 : nYChunkSize, false);
2388 : }
2389 2772 : else if (nc_datatype == NC_SHORT)
2390 : {
2391 465 : status = nc_get_vara_short(cdfid, nZId, start, edge,
2392 : static_cast<short *>(pImageNC));
2393 465 : if (status == NC_NOERR)
2394 : {
2395 465 : if (eDataType == GDT_Int16)
2396 : {
2397 462 : CheckData<GInt16>(pImage, pImageNC, edge[nBandXPos],
2398 : nYChunkSize, false);
2399 : }
2400 : else
2401 : {
2402 3 : CheckData<GUInt16>(pImage, pImageNC, edge[nBandXPos],
2403 : nYChunkSize, false);
2404 : }
2405 : }
2406 : }
2407 2307 : else if (eDataType == GDT_Int32)
2408 : {
2409 : #if SIZEOF_UNSIGNED_LONG == 4
2410 : status = nc_get_vara_long(cdfid, nZId, start, edge,
2411 : static_cast<long *>(pImageNC));
2412 : if (status == NC_NOERR)
2413 : CheckData<long>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2414 : false);
2415 : #else
2416 912 : status = nc_get_vara_int(cdfid, nZId, start, edge,
2417 : static_cast<int *>(pImageNC));
2418 912 : if (status == NC_NOERR)
2419 912 : CheckData<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2420 : false);
2421 : #endif
2422 : }
2423 1395 : else if (eDataType == GDT_Float32)
2424 : {
2425 1258 : status = nc_get_vara_float(cdfid, nZId, start, edge,
2426 : static_cast<float *>(pImageNC));
2427 1258 : if (status == NC_NOERR)
2428 1258 : CheckData<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2429 : true);
2430 : }
2431 137 : else if (eDataType == GDT_Float64)
2432 : {
2433 86 : status = nc_get_vara_double(cdfid, nZId, start, edge,
2434 : static_cast<double *>(pImageNC));
2435 86 : if (status == NC_NOERR)
2436 86 : CheckData<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2437 : true);
2438 : }
2439 51 : else if (eDataType == GDT_UInt16)
2440 : {
2441 6 : status = nc_get_vara_ushort(cdfid, nZId, start, edge,
2442 : static_cast<unsigned short *>(pImageNC));
2443 6 : if (status == NC_NOERR)
2444 6 : CheckData<unsigned short>(pImage, pImageNC, edge[nBandXPos],
2445 : nYChunkSize, false);
2446 : }
2447 45 : else if (eDataType == GDT_UInt32)
2448 : {
2449 6 : status = nc_get_vara_uint(cdfid, nZId, start, edge,
2450 : static_cast<unsigned int *>(pImageNC));
2451 6 : if (status == NC_NOERR)
2452 6 : CheckData<unsigned int>(pImage, pImageNC, edge[nBandXPos],
2453 : nYChunkSize, false);
2454 : }
2455 39 : else if (eDataType == GDT_Int64)
2456 : {
2457 7 : status = nc_get_vara_longlong(cdfid, nZId, start, edge,
2458 : static_cast<long long *>(pImageNC));
2459 7 : if (status == NC_NOERR)
2460 7 : CheckData<std::int64_t>(pImage, pImageNC, edge[nBandXPos],
2461 : nYChunkSize, false);
2462 : }
2463 32 : else if (eDataType == GDT_UInt64)
2464 : {
2465 : status =
2466 7 : nc_get_vara_ulonglong(cdfid, nZId, start, edge,
2467 : static_cast<unsigned long long *>(pImageNC));
2468 7 : if (status == NC_NOERR)
2469 7 : CheckData<std::uint64_t>(pImage, pImageNC, edge[nBandXPos],
2470 : nYChunkSize, false);
2471 : }
2472 25 : else if (eDataType == GDT_CInt16)
2473 : {
2474 0 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2475 0 : if (status == NC_NOERR)
2476 0 : CheckDataCpx<short>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2477 : false);
2478 : }
2479 25 : else if (eDataType == GDT_CInt32)
2480 : {
2481 0 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2482 0 : if (status == NC_NOERR)
2483 0 : CheckDataCpx<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2484 : false);
2485 : }
2486 25 : else if (eDataType == GDT_CFloat32)
2487 : {
2488 20 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2489 20 : if (status == NC_NOERR)
2490 20 : CheckDataCpx<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2491 : false);
2492 : }
2493 5 : else if (eDataType == GDT_CFloat64)
2494 : {
2495 5 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2496 5 : if (status == NC_NOERR)
2497 5 : CheckDataCpx<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2498 : false);
2499 : }
2500 :
2501 : else
2502 0 : status = NC_EBADTYPE;
2503 :
2504 5937 : if (status != NC_NOERR)
2505 : {
2506 0 : CPLError(CE_Failure, CPLE_AppDefined,
2507 : "netCDF chunk fetch failed: #%d (%s)", status,
2508 : nc_strerror(status));
2509 0 : return false;
2510 : }
2511 5937 : return true;
2512 : }
2513 :
2514 : /************************************************************************/
2515 : /* IReadBlock() */
2516 : /************************************************************************/
2517 :
2518 5937 : CPLErr netCDFRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
2519 : void *pImage)
2520 :
2521 : {
2522 11874 : CPLMutexHolderD(&hNCMutex);
2523 :
2524 : // Locate X, Y and Z position in the array.
2525 :
2526 5937 : size_t xstart = static_cast<size_t>(nBlockXOff) * nBlockXSize;
2527 5937 : size_t ystart = 0;
2528 :
2529 : // Check y order.
2530 5937 : if (nBandYPos >= 0)
2531 : {
2532 5933 : auto poGDS = cpl::down_cast<netCDFDataset *>(poDS);
2533 5933 : if (poGDS->bBottomUp)
2534 : {
2535 5018 : if (nBlockYSize == 1)
2536 : {
2537 5005 : ystart = nRasterYSize - 1 - nBlockYOff;
2538 : }
2539 : else
2540 : {
2541 : // in GDAL space
2542 13 : ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
2543 : const size_t yend =
2544 26 : std::min(ystart + nBlockYSize - 1,
2545 13 : static_cast<size_t>(nRasterYSize - 1));
2546 : // in netCDF space
2547 13 : const size_t nFirstChunkLine = nRasterYSize - 1 - yend;
2548 13 : const size_t nLastChunkLine = nRasterYSize - 1 - ystart;
2549 13 : const size_t nFirstChunkBlock = nFirstChunkLine / nBlockYSize;
2550 13 : const size_t nLastChunkBlock = nLastChunkLine / nBlockYSize;
2551 :
2552 : const auto firstKey = netCDFDataset::ChunkKey(
2553 13 : nBlockXOff, nFirstChunkBlock, nBand);
2554 : const auto secondKey =
2555 13 : netCDFDataset::ChunkKey(nBlockXOff, nLastChunkBlock, nBand);
2556 :
2557 : // Retrieve data from the one or 2 needed netCDF chunks
2558 13 : std::shared_ptr<std::vector<GByte>> firstChunk;
2559 13 : std::shared_ptr<std::vector<GByte>> secondChunk;
2560 13 : if (poGDS->poChunkCache)
2561 : {
2562 13 : poGDS->poChunkCache->tryGet(firstKey, firstChunk);
2563 13 : if (firstKey != secondKey)
2564 6 : poGDS->poChunkCache->tryGet(secondKey, secondChunk);
2565 : }
2566 : const size_t nChunkLineSize =
2567 13 : static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
2568 13 : nBlockXSize;
2569 13 : const size_t nChunkSize = nChunkLineSize * nBlockYSize;
2570 13 : if (!firstChunk)
2571 : {
2572 11 : firstChunk.reset(new std::vector<GByte>(nChunkSize));
2573 11 : if (!FetchNetcdfChunk(xstart,
2574 11 : nFirstChunkBlock * nBlockYSize,
2575 11 : firstChunk.get()->data()))
2576 0 : return CE_Failure;
2577 11 : if (poGDS->poChunkCache)
2578 11 : poGDS->poChunkCache->insert(firstKey, firstChunk);
2579 : }
2580 13 : if (!secondChunk && firstKey != secondKey)
2581 : {
2582 2 : secondChunk.reset(new std::vector<GByte>(nChunkSize));
2583 2 : if (!FetchNetcdfChunk(xstart, nLastChunkBlock * nBlockYSize,
2584 2 : secondChunk.get()->data()))
2585 0 : return CE_Failure;
2586 2 : if (poGDS->poChunkCache)
2587 2 : poGDS->poChunkCache->insert(secondKey, secondChunk);
2588 : }
2589 :
2590 : // Assemble netCDF chunks into GDAL block
2591 13 : GByte *pabyImage = static_cast<GByte *>(pImage);
2592 13 : const size_t nFirstChunkBlockLine =
2593 13 : nFirstChunkBlock * nBlockYSize;
2594 13 : const size_t nLastChunkBlockLine =
2595 13 : nLastChunkBlock * nBlockYSize;
2596 146 : for (size_t iLine = ystart; iLine <= yend; iLine++)
2597 : {
2598 133 : const size_t nLineFromBottom = nRasterYSize - 1 - iLine;
2599 133 : const size_t nChunkY = nLineFromBottom / nBlockYSize;
2600 133 : if (nChunkY == nFirstChunkBlock)
2601 : {
2602 121 : memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
2603 121 : firstChunk.get()->data() +
2604 121 : (nLineFromBottom - nFirstChunkBlockLine) *
2605 : nChunkLineSize,
2606 : nChunkLineSize);
2607 : }
2608 : else
2609 : {
2610 12 : CPLAssert(nChunkY == nLastChunkBlock);
2611 12 : assert(secondChunk);
2612 12 : memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
2613 12 : secondChunk.get()->data() +
2614 12 : (nLineFromBottom - nLastChunkBlockLine) *
2615 : nChunkLineSize,
2616 : nChunkLineSize);
2617 : }
2618 : }
2619 13 : return CE_None;
2620 : }
2621 : }
2622 : else
2623 : {
2624 915 : ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
2625 : }
2626 : }
2627 :
2628 5924 : return FetchNetcdfChunk(xstart, ystart, pImage) ? CE_None : CE_Failure;
2629 : }
2630 :
2631 : /************************************************************************/
2632 : /* IWriteBlock() */
2633 : /************************************************************************/
2634 :
2635 6501 : CPLErr netCDFRasterBand::IWriteBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
2636 : void *pImage)
2637 : {
2638 13002 : CPLMutexHolderD(&hNCMutex);
2639 :
2640 : #ifdef NCDF_DEBUG
2641 : if (nBlockYOff == 0 || (nBlockYOff == nRasterYSize - 1))
2642 : CPLDebug("GDAL_netCDF",
2643 : "netCDFRasterBand::IWriteBlock( %d, %d, ...) nBand=%d",
2644 : nBlockXOff, nBlockYOff, nBand);
2645 : #endif
2646 :
2647 6501 : int nd = 0;
2648 6501 : nc_inq_varndims(cdfid, nZId, &nd);
2649 :
2650 : // Locate X, Y and Z position in the array.
2651 :
2652 : size_t start[MAX_NC_DIMS];
2653 6501 : memset(start, 0, sizeof(start));
2654 6501 : start[nBandXPos] = static_cast<size_t>(nBlockXOff) * nBlockXSize;
2655 :
2656 : // check y order.
2657 6501 : if (cpl::down_cast<netCDFDataset *>(poDS)->bBottomUp)
2658 : {
2659 6437 : if (nBlockYSize == 1)
2660 : {
2661 6437 : start[nBandYPos] = nRasterYSize - 1 - nBlockYOff;
2662 : }
2663 : else
2664 : {
2665 0 : CPLError(CE_Failure, CPLE_AppDefined,
2666 : "nBlockYSize = %d, only 1 supported when "
2667 : "writing bottom-up dataset",
2668 : nBlockYSize);
2669 0 : return CE_Failure;
2670 : }
2671 : }
2672 : else
2673 : {
2674 64 : start[nBandYPos] = static_cast<size_t>(nBlockYOff) * nBlockYSize; // y
2675 : }
2676 :
2677 6501 : size_t edge[MAX_NC_DIMS] = {};
2678 :
2679 6501 : edge[nBandXPos] = nBlockXSize;
2680 6501 : if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
2681 0 : edge[nBandXPos] = nRasterXSize - start[nBandXPos];
2682 6501 : edge[nBandYPos] = nBlockYSize;
2683 6501 : if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
2684 0 : edge[nBandYPos] = nRasterYSize - start[nBandYPos];
2685 :
2686 6501 : if (nd == 3)
2687 : {
2688 610 : start[panBandZPos[0]] = nLevel; // z
2689 610 : edge[panBandZPos[0]] = 1;
2690 : }
2691 :
2692 : // Compute multidimention band position.
2693 : //
2694 : // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
2695 : // if Data[2,3,4,x,y]
2696 : //
2697 : // BandPos0 = (nBand) / (3*4)
2698 : // BandPos1 = (nBand - (3*4)) / (4)
2699 : // BandPos2 = (nBand - (3*4)) % (4)
2700 6501 : if (nd > 3)
2701 : {
2702 178 : int Sum = -1;
2703 178 : int Taken = 0;
2704 534 : for (int i = 0; i < nd - 2; i++)
2705 : {
2706 356 : if (i != nd - 2 - 1)
2707 : {
2708 178 : Sum = 1;
2709 356 : for (int j = i + 1; j < nd - 2; j++)
2710 : {
2711 178 : Sum *= panBandZLev[j];
2712 : }
2713 178 : start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
2714 178 : edge[panBandZPos[i]] = 1;
2715 : }
2716 : else
2717 : {
2718 178 : start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
2719 178 : edge[panBandZPos[i]] = 1;
2720 : }
2721 356 : Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
2722 : }
2723 : }
2724 :
2725 : // Make sure we are in data mode.
2726 6501 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
2727 :
2728 : // Copy data according to type.
2729 6501 : int status = 0;
2730 6501 : if (eDataType == GDT_UInt8)
2731 : {
2732 5942 : if (bSignedData)
2733 0 : status = nc_put_vara_schar(cdfid, nZId, start, edge,
2734 : static_cast<signed char *>(pImage));
2735 : else
2736 5942 : status = nc_put_vara_uchar(cdfid, nZId, start, edge,
2737 : static_cast<unsigned char *>(pImage));
2738 : }
2739 559 : else if (eDataType == GDT_Int8)
2740 : {
2741 40 : status = nc_put_vara_schar(cdfid, nZId, start, edge,
2742 : static_cast<signed char *>(pImage));
2743 : }
2744 519 : else if (nc_datatype == NC_SHORT)
2745 : {
2746 101 : status = nc_put_vara_short(cdfid, nZId, start, edge,
2747 : static_cast<short *>(pImage));
2748 : }
2749 418 : else if (eDataType == GDT_Int32)
2750 : {
2751 210 : status = nc_put_vara_int(cdfid, nZId, start, edge,
2752 : static_cast<int *>(pImage));
2753 : }
2754 208 : else if (eDataType == GDT_Float32)
2755 : {
2756 128 : status = nc_put_vara_float(cdfid, nZId, start, edge,
2757 : static_cast<float *>(pImage));
2758 : }
2759 80 : else if (eDataType == GDT_Float64)
2760 : {
2761 50 : status = nc_put_vara_double(cdfid, nZId, start, edge,
2762 : static_cast<double *>(pImage));
2763 : }
2764 42 : else if (eDataType == GDT_UInt16 &&
2765 12 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2766 : {
2767 12 : status = nc_put_vara_ushort(cdfid, nZId, start, edge,
2768 : static_cast<unsigned short *>(pImage));
2769 : }
2770 30 : else if (eDataType == GDT_UInt32 &&
2771 12 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2772 : {
2773 12 : status = nc_put_vara_uint(cdfid, nZId, start, edge,
2774 : static_cast<unsigned int *>(pImage));
2775 : }
2776 9 : else if (eDataType == GDT_UInt64 &&
2777 3 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2778 : {
2779 : status =
2780 3 : nc_put_vara_ulonglong(cdfid, nZId, start, edge,
2781 : static_cast<unsigned long long *>(pImage));
2782 : }
2783 6 : else if (eDataType == GDT_Int64 &&
2784 3 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2785 : {
2786 3 : status = nc_put_vara_longlong(cdfid, nZId, start, edge,
2787 : static_cast<long long *>(pImage));
2788 : }
2789 : else
2790 : {
2791 0 : CPLError(CE_Failure, CPLE_NotSupported,
2792 : "The NetCDF driver does not support GDAL data type %d",
2793 0 : eDataType);
2794 0 : status = NC_EBADTYPE;
2795 : }
2796 6501 : NCDF_ERR(status);
2797 :
2798 6501 : if (status != NC_NOERR)
2799 : {
2800 0 : CPLError(CE_Failure, CPLE_AppDefined,
2801 : "netCDF scanline write failed: %s", nc_strerror(status));
2802 0 : return CE_Failure;
2803 : }
2804 :
2805 6501 : return CE_None;
2806 : }
2807 :
2808 : /************************************************************************/
2809 : /* ==================================================================== */
2810 : /* netCDFDataset */
2811 : /* ==================================================================== */
2812 : /************************************************************************/
2813 :
2814 : /************************************************************************/
2815 : /* netCDFDataset() */
2816 : /************************************************************************/
2817 :
2818 1208 : netCDFDataset::netCDFDataset()
2819 : :
2820 : // Basic dataset vars.
2821 : #ifdef ENABLE_NCDUMP
2822 : bFileToDestroyAtClosing(false),
2823 : #endif
2824 : cdfid(-1), nSubDatasets(0), bBottomUp(true), eFormat(NCDF_FORMAT_NONE),
2825 : bIsGdalFile(false), bIsGdalCfFile(false), pszCFProjection(nullptr),
2826 : pszCFCoordinates(nullptr), nCFVersion(1.6), bSGSupport(false),
2827 1208 : eMultipleLayerBehavior(SINGLE_LAYER), logCount(0), vcdf(this, cdfid),
2828 1208 : GeometryScribe(vcdf, this->generateLogName()),
2829 1208 : FieldScribe(vcdf, this->generateLogName()),
2830 2416 : bufManager(CPLGetUsablePhysicalRAM() / 5),
2831 :
2832 : // projection/GT.
2833 : nXDimID(-1), nYDimID(-1), bIsProjected(false),
2834 : bIsGeographic(false), // Can be not projected, and also not geographic
2835 : // State vars.
2836 : bDefineMode(true), bAddedGridMappingRef(false),
2837 :
2838 : // Create vars.
2839 : eCompress(NCDF_COMPRESS_NONE), nZLevel(NCDF_DEFLATE_LEVEL),
2840 3624 : bChunking(false), nCreateMode(NC_CLOBBER), bSignedData(true)
2841 : {
2842 1208 : m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2843 :
2844 : // Set buffers
2845 1208 : bufManager.addBuffer(&(GeometryScribe.getMemBuffer()));
2846 1208 : bufManager.addBuffer(&(FieldScribe.getMemBuffer()));
2847 1208 : }
2848 :
2849 : /************************************************************************/
2850 : /* ~netCDFDataset() */
2851 : /************************************************************************/
2852 :
2853 2318 : netCDFDataset::~netCDFDataset()
2854 :
2855 : {
2856 1208 : netCDFDataset::Close();
2857 2318 : }
2858 :
2859 : /************************************************************************/
2860 : /* Close() */
2861 : /************************************************************************/
2862 :
2863 2036 : CPLErr netCDFDataset::Close(GDALProgressFunc, void *)
2864 : {
2865 2036 : CPLErr eErr = CE_None;
2866 2036 : if (nOpenFlags != OPEN_FLAGS_CLOSED)
2867 : {
2868 2416 : CPLMutexHolderD(&hNCMutex);
2869 :
2870 : #ifdef NCDF_DEBUG
2871 : CPLDebug("GDAL_netCDF",
2872 : "netCDFDataset::~netCDFDataset(), cdfid=%d filename=%s", cdfid,
2873 : osFilename.c_str());
2874 : #endif
2875 :
2876 : // Write data related to geotransform
2877 1493 : if (GetAccess() == GA_Update && !m_bAddedProjectionVarsData &&
2878 285 : (m_bHasProjection || m_bHasGeoTransform))
2879 : {
2880 : // Ensure projection is written if GeoTransform OR Projection are
2881 : // missing.
2882 37 : if (!m_bAddedProjectionVarsDefs)
2883 : {
2884 2 : AddProjectionVars(true, nullptr, nullptr);
2885 : }
2886 37 : AddProjectionVars(false, nullptr, nullptr);
2887 : }
2888 :
2889 1208 : if (netCDFDataset::FlushCache(true) != CE_None)
2890 0 : eErr = CE_Failure;
2891 :
2892 1208 : if (GetAccess() == GA_Update && !SGCommitPendingTransaction())
2893 0 : eErr = CE_Failure;
2894 :
2895 1210 : for (size_t i = 0; i < apoVectorDatasets.size(); i++)
2896 2 : delete apoVectorDatasets[i];
2897 :
2898 : // Make sure projection variable is written to band variable.
2899 1208 : if (GetAccess() == GA_Update && !bAddedGridMappingRef)
2900 : {
2901 308 : if (!AddGridMappingRef())
2902 0 : eErr = CE_Failure;
2903 : }
2904 :
2905 1208 : CPLFree(pszCFProjection);
2906 :
2907 1208 : if (cdfid > 0)
2908 : {
2909 : #ifdef NCDF_DEBUG
2910 : CPLDebug("GDAL_netCDF", "calling nc_close( %d)", cdfid);
2911 : #endif
2912 686 : int status = GDAL_nc_close(cdfid);
2913 : #ifdef ENABLE_UFFD
2914 686 : NETCDF_UFFD_UNMAP(pCtx);
2915 : #endif
2916 686 : NCDF_ERR(status);
2917 686 : if (status != NC_NOERR)
2918 0 : eErr = CE_Failure;
2919 : }
2920 :
2921 1208 : if (fpVSIMEM)
2922 15 : VSIFCloseL(fpVSIMEM);
2923 :
2924 : #ifdef ENABLE_NCDUMP
2925 1208 : if (bFileToDestroyAtClosing)
2926 0 : VSIUnlink(osFilename);
2927 : #endif
2928 :
2929 1208 : if (GDALPamDataset::Close() != CE_None)
2930 0 : eErr = CE_Failure;
2931 : }
2932 2036 : return eErr;
2933 : }
2934 :
2935 : /************************************************************************/
2936 : /* SetDefineMode() */
2937 : /************************************************************************/
2938 14555 : bool netCDFDataset::SetDefineMode(bool bNewDefineMode)
2939 : {
2940 : // Do nothing if already in new define mode
2941 : // or if dataset is in read-only mode or if dataset is true NC4 dataset.
2942 15132 : if (bDefineMode == bNewDefineMode || GetAccess() == GA_ReadOnly ||
2943 577 : eFormat == NCDF_FORMAT_NC4)
2944 14125 : return true;
2945 :
2946 430 : CPLDebug("GDAL_netCDF", "SetDefineMode(%d) old=%d",
2947 430 : static_cast<int>(bNewDefineMode), static_cast<int>(bDefineMode));
2948 :
2949 430 : bDefineMode = bNewDefineMode;
2950 :
2951 : int status;
2952 430 : if (bDefineMode)
2953 149 : status = nc_redef(cdfid);
2954 : else
2955 281 : status = nc_enddef(cdfid);
2956 :
2957 430 : NCDF_ERR(status);
2958 430 : return status == NC_NOERR;
2959 : }
2960 :
2961 : /************************************************************************/
2962 : /* GetMetadataDomainList() */
2963 : /************************************************************************/
2964 :
2965 27 : char **netCDFDataset::GetMetadataDomainList()
2966 : {
2967 27 : char **papszDomains = BuildMetadataDomainList(
2968 : GDALDataset::GetMetadataDomainList(), TRUE, "SUBDATASETS", nullptr);
2969 28 : for (const auto &kv : m_oMapDomainToJSon)
2970 1 : papszDomains = CSLAddString(papszDomains, ("json:" + kv.first).c_str());
2971 27 : return papszDomains;
2972 : }
2973 :
2974 : /************************************************************************/
2975 : /* GetMetadata() */
2976 : /************************************************************************/
2977 403 : CSLConstList netCDFDataset::GetMetadata(const char *pszDomain)
2978 : {
2979 403 : if (pszDomain != nullptr && STARTS_WITH_CI(pszDomain, "SUBDATASETS"))
2980 39 : return aosSubDatasets.List();
2981 :
2982 364 : if (pszDomain != nullptr && STARTS_WITH(pszDomain, "json:"))
2983 : {
2984 1 : auto iter = m_oMapDomainToJSon.find(pszDomain + strlen("json:"));
2985 1 : if (iter != m_oMapDomainToJSon.end())
2986 1 : return iter->second.List();
2987 : }
2988 :
2989 363 : return GDALDataset::GetMetadata(pszDomain);
2990 : }
2991 :
2992 : /************************************************************************/
2993 : /* SetMetadataItem() */
2994 : /************************************************************************/
2995 :
2996 43 : CPLErr netCDFDataset::SetMetadataItem(const char *pszName, const char *pszValue,
2997 : const char *pszDomain)
2998 : {
2999 85 : if (GetAccess() == GA_Update &&
3000 85 : (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)
3001 : {
3002 42 : std::string osName(pszName);
3003 :
3004 : // Same logic as in CopyMetadata()
3005 42 : if (cpl::starts_with(osName, "NC_GLOBAL#"))
3006 8 : osName = osName.substr(strlen("NC_GLOBAL#"));
3007 34 : else if (strchr(osName.c_str(), '#') == nullptr)
3008 5 : osName = "GDAL_" + osName;
3009 :
3010 84 : if (cpl::starts_with(osName, "NETCDF_DIM_") ||
3011 42 : strchr(osName.c_str(), '#') != nullptr)
3012 : {
3013 : // do nothing
3014 29 : return CE_None;
3015 : }
3016 : else
3017 : {
3018 13 : SetDefineMode(true);
3019 :
3020 13 : if (!NCDFPutAttr(cdfid, NC_GLOBAL, osName.c_str(), pszValue))
3021 13 : return CE_Failure;
3022 : }
3023 : }
3024 :
3025 1 : return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);
3026 : }
3027 :
3028 : /************************************************************************/
3029 : /* SetMetadata() */
3030 : /************************************************************************/
3031 :
3032 8 : CPLErr netCDFDataset::SetMetadata(CSLConstList papszMD, const char *pszDomain)
3033 : {
3034 13 : if (GetAccess() == GA_Update &&
3035 5 : (pszDomain == nullptr || pszDomain[0] == '\0'))
3036 : {
3037 : // We don't handle metadata item removal for now
3038 50 : for (const char *const *papszIter = papszMD; papszIter && *papszIter;
3039 : ++papszIter)
3040 : {
3041 42 : char *pszName = nullptr;
3042 42 : const char *pszValue = CPLParseNameValue(*papszIter, &pszName);
3043 42 : if (pszName && pszValue)
3044 42 : SetMetadataItem(pszName, pszValue);
3045 42 : CPLFree(pszName);
3046 : }
3047 8 : return CE_None;
3048 : }
3049 0 : return GDALPamDataset::SetMetadata(papszMD, pszDomain);
3050 : }
3051 :
3052 : /************************************************************************/
3053 : /* GetSpatialRef() */
3054 : /************************************************************************/
3055 :
3056 230 : const OGRSpatialReference *netCDFDataset::GetSpatialRef() const
3057 : {
3058 230 : if (m_bHasProjection)
3059 102 : return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
3060 :
3061 128 : return GDALPamDataset::GetSpatialRef();
3062 : }
3063 :
3064 : /************************************************************************/
3065 : /* FetchCopyParam() */
3066 : /************************************************************************/
3067 :
3068 444 : double netCDFDataset::FetchCopyParam(const char *pszGridMappingValue,
3069 : const char *pszParam, double dfDefault,
3070 : bool *pbFound) const
3071 :
3072 : {
3073 888 : std::string osTemp = CPLOPrintf("%s#%s", pszGridMappingValue, pszParam);
3074 444 : const char *pszValue = aosMetadata.FetchNameValue(osTemp.c_str());
3075 :
3076 444 : if (pbFound)
3077 : {
3078 444 : *pbFound = (pszValue != nullptr);
3079 : }
3080 :
3081 444 : if (pszValue)
3082 : {
3083 0 : return CPLAtofM(pszValue);
3084 : }
3085 :
3086 444 : return dfDefault;
3087 : }
3088 :
3089 : /************************************************************************/
3090 : /* FetchStandardParallels() */
3091 : /************************************************************************/
3092 :
3093 : std::vector<std::string>
3094 0 : netCDFDataset::FetchStandardParallels(const char *pszGridMappingValue) const
3095 : {
3096 : // cf-1.0 tags
3097 0 : const char *pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL);
3098 :
3099 0 : std::vector<std::string> ret;
3100 0 : if (pszValue != nullptr)
3101 : {
3102 0 : CPLStringList aosValues;
3103 0 : if (pszValue[0] != '{' &&
3104 0 : CPLString(pszValue).Trim().find(' ') != std::string::npos)
3105 : {
3106 : // Some files like
3107 : // ftp://data.knmi.nl/download/KNW-NetCDF-3D/1.0/noversion/2013/11/14/KNW-1.0_H37-ERA_NL_20131114.nc
3108 : // do not use standard formatting for arrays, but just space
3109 : // separated syntax
3110 0 : aosValues = CSLTokenizeString2(pszValue, " ", 0);
3111 : }
3112 : else
3113 : {
3114 0 : aosValues = NCDFTokenizeArray(pszValue);
3115 : }
3116 0 : for (int i = 0; i < aosValues.size(); i++)
3117 : {
3118 0 : ret.push_back(aosValues[i]);
3119 : }
3120 : }
3121 : // Try gdal tags.
3122 : else
3123 : {
3124 0 : pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_1);
3125 :
3126 0 : if (pszValue != nullptr)
3127 0 : ret.push_back(pszValue);
3128 :
3129 0 : pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_2);
3130 :
3131 0 : if (pszValue != nullptr)
3132 0 : ret.push_back(pszValue);
3133 : }
3134 :
3135 0 : return ret;
3136 : }
3137 :
3138 : /************************************************************************/
3139 : /* FetchAttr() */
3140 : /************************************************************************/
3141 :
3142 3968 : const char *netCDFDataset::FetchAttr(const char *pszVarFullName,
3143 : const char *pszAttr) const
3144 :
3145 : {
3146 3968 : auto oKey = CPLOPrintf("%s#%s", pszVarFullName, pszAttr);
3147 3968 : const char *pszValue = aosMetadata.FetchNameValue(oKey.c_str());
3148 7936 : return pszValue;
3149 : }
3150 :
3151 2606 : const char *netCDFDataset::FetchAttr(int nGroupId, int nVarId,
3152 : const char *pszAttr) const
3153 :
3154 : {
3155 2606 : std::string osFullName;
3156 2606 : NCDFGetVarFullName(nGroupId, nVarId, osFullName);
3157 2606 : const char *pszValue = FetchAttr(osFullName.c_str(), pszAttr);
3158 5212 : return pszValue;
3159 : }
3160 :
3161 : /************************************************************************/
3162 : /* IsDifferenceBelow() */
3163 : /************************************************************************/
3164 :
3165 1115 : static bool IsDifferenceBelow(double dfA, double dfB, double dfError)
3166 : {
3167 1115 : const double dfAbsDiff = fabs(dfA - dfB);
3168 1115 : return dfAbsDiff <= dfError;
3169 : }
3170 :
3171 : /************************************************************************/
3172 : /* SetProjectionFromVar() */
3173 : /************************************************************************/
3174 560 : void netCDFDataset::SetProjectionFromVar(
3175 : int nGroupId, int nVarId, bool bReadSRSOnly, const char *pszGivenGM,
3176 : std::string *returnProjStr, nccfdriver::SGeometry_Reader *sg,
3177 : std::vector<std::string> *paosRemovedMDItems)
3178 : {
3179 560 : bool bGotGeogCS = false;
3180 560 : bool bGotCfSRS = false;
3181 560 : bool bGotCfWktSRS = false;
3182 560 : bool bGotGdalSRS = false;
3183 560 : bool bGotCfGT = false;
3184 560 : bool bGotGdalGT = false;
3185 :
3186 : // These values from CF metadata.
3187 560 : OGRSpatialReference oSRS;
3188 560 : oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3189 560 : size_t xdim = nRasterXSize;
3190 560 : size_t ydim = nRasterYSize;
3191 :
3192 : // These values from GDAL metadata.
3193 560 : const char *pszWKT = nullptr;
3194 560 : const char *pszGeoTransform = nullptr;
3195 :
3196 560 : netCDFDataset *poDS = this; // Perhaps this should be removed for clarity.
3197 :
3198 560 : CPLDebug("GDAL_netCDF", "\n=====\nSetProjectionFromVar( %d, %d)", nGroupId,
3199 : nVarId);
3200 :
3201 : // Get x/y range information.
3202 :
3203 : // Temp variables to use in SetGeoTransform() and SetProjection().
3204 560 : GDALGeoTransform tmpGT;
3205 :
3206 : // Look for grid_mapping metadata.
3207 560 : const char *pszValue = pszGivenGM;
3208 560 : CPLString osTmpGridMapping; // let is in this outer scope as pszValue may
3209 : // point to it
3210 560 : if (pszValue == nullptr)
3211 : {
3212 517 : pszValue = FetchAttr(nGroupId, nVarId, CF_GRD_MAPPING);
3213 517 : if (pszValue && strchr(pszValue, ':') && strchr(pszValue, ' '))
3214 : {
3215 : // Expanded form of grid_mapping
3216 : // e.g. "crsOSGB: x y crsWGS84: lat lon"
3217 : // Pickup the grid_mapping whose coordinates are dimensions of the
3218 : // variable
3219 6 : const CPLStringList aosTokens(CSLTokenizeString2(pszValue, " ", 0));
3220 3 : if ((aosTokens.size() % 3) == 0)
3221 : {
3222 3 : for (int i = 0; i < aosTokens.size() / 3; i++)
3223 : {
3224 3 : if (CSLFindString(poDS->papszDimName,
3225 9 : aosTokens[3 * i + 1]) >= 0 &&
3226 3 : CSLFindString(poDS->papszDimName,
3227 3 : aosTokens[3 * i + 2]) >= 0)
3228 : {
3229 3 : osTmpGridMapping = aosTokens[3 * i];
3230 6 : if (!osTmpGridMapping.empty() &&
3231 3 : osTmpGridMapping.back() == ':')
3232 : {
3233 3 : osTmpGridMapping.resize(osTmpGridMapping.size() -
3234 : 1);
3235 : }
3236 3 : pszValue = osTmpGridMapping.c_str();
3237 3 : break;
3238 : }
3239 : }
3240 : }
3241 : }
3242 : }
3243 560 : std::string osGridMappingValue = pszValue ? pszValue : "";
3244 :
3245 560 : if (!osGridMappingValue.empty())
3246 : {
3247 : // Read grid_mapping metadata.
3248 239 : int nProjGroupID = -1;
3249 239 : int nProjVarID = -1;
3250 239 : if (NCDFResolveVar(nGroupId, osGridMappingValue.c_str(), &nProjGroupID,
3251 239 : &nProjVarID) == CE_None)
3252 : {
3253 238 : poDS->ReadAttributes(nProjGroupID, nProjVarID);
3254 :
3255 : // Look for GDAL spatial_ref and GeoTransform within grid_mapping.
3256 238 : if (NCDFGetVarFullName(nProjGroupID, nProjVarID,
3257 238 : osGridMappingValue) == CE_None)
3258 : {
3259 238 : CPLDebug("GDAL_netCDF", "got grid_mapping %s",
3260 : osGridMappingValue.c_str());
3261 : pszWKT =
3262 238 : FetchAttr(osGridMappingValue.c_str(), NCDF_SPATIAL_REF);
3263 238 : if (!pszWKT)
3264 : {
3265 : pszWKT =
3266 35 : FetchAttr(osGridMappingValue.c_str(), NCDF_CRS_WKT);
3267 : }
3268 : else
3269 : {
3270 203 : bGotGdalSRS = true;
3271 203 : CPLDebug("GDAL_netCDF", "setting WKT from GDAL");
3272 : }
3273 238 : if (pszWKT)
3274 : {
3275 208 : if (!bGotGdalSRS)
3276 : {
3277 5 : bGotCfWktSRS = true;
3278 5 : CPLDebug("GDAL_netCDF", "setting WKT from CF");
3279 : }
3280 208 : if (returnProjStr != nullptr)
3281 : {
3282 41 : (*returnProjStr) = std::string(pszWKT);
3283 : }
3284 : else
3285 : {
3286 167 : m_bAddedProjectionVarsDefs = true;
3287 167 : m_bAddedProjectionVarsData = true;
3288 334 : OGRSpatialReference oSRSTmp;
3289 167 : oSRSTmp.SetAxisMappingStrategy(
3290 : OAMS_TRADITIONAL_GIS_ORDER);
3291 167 : oSRSTmp.importFromWkt(pszWKT);
3292 167 : SetSpatialRefNoUpdate(&oSRSTmp);
3293 : }
3294 208 : pszGeoTransform = FetchAttr(osGridMappingValue.c_str(),
3295 : NCDF_GEOTRANSFORM);
3296 : }
3297 : }
3298 : }
3299 : }
3300 :
3301 : // Get information about the file.
3302 : //
3303 : // Was this file created by the GDAL netcdf driver?
3304 : // Was this file created by the newer (CF-conformant) driver?
3305 : //
3306 : // 1) If GDAL netcdf metadata is set, and version >= 1.9,
3307 : // it was created with the new driver
3308 : // 2) Else, if spatial_ref and GeoTransform are present in the
3309 : // grid_mapping variable, it was created by the old driver
3310 560 : pszValue = FetchAttr("NC_GLOBAL", "GDAL");
3311 :
3312 560 : if (pszValue && NCDFIsGDALVersionGTE(pszValue, 1900))
3313 : {
3314 257 : bIsGdalFile = true;
3315 257 : bIsGdalCfFile = true;
3316 : }
3317 303 : else if (pszWKT != nullptr && pszGeoTransform != nullptr)
3318 : {
3319 24 : bIsGdalFile = true;
3320 24 : bIsGdalCfFile = false;
3321 : }
3322 :
3323 : // Set default bottom-up default value.
3324 : // Y axis dimension and absence of GT can modify this value.
3325 : // Override with Config option GDAL_NETCDF_BOTTOMUP.
3326 :
3327 : // New driver is bottom-up by default.
3328 560 : if ((bIsGdalFile && !bIsGdalCfFile) || bSwitchedXY)
3329 26 : poDS->bBottomUp = false;
3330 : else
3331 534 : poDS->bBottomUp = true;
3332 :
3333 560 : CPLDebug("GDAL_netCDF",
3334 : "bIsGdalFile=%d bIsGdalCfFile=%d bSwitchedXY=%d bBottomUp=%d",
3335 560 : static_cast<int>(bIsGdalFile), static_cast<int>(bIsGdalCfFile),
3336 560 : static_cast<int>(bSwitchedXY), static_cast<int>(bBottomUp));
3337 :
3338 : // Read projection coordinates.
3339 :
3340 560 : int nGroupDimXID = -1;
3341 560 : int nVarDimXID = -1;
3342 560 : int nGroupDimYID = -1;
3343 560 : int nVarDimYID = -1;
3344 560 : if (sg != nullptr)
3345 : {
3346 43 : nGroupDimXID = sg->get_ncID();
3347 43 : nGroupDimYID = sg->get_ncID();
3348 43 : nVarDimXID = sg->getNodeCoordVars()[0];
3349 43 : nVarDimYID = sg->getNodeCoordVars()[1];
3350 : }
3351 :
3352 560 : if (!bReadSRSOnly)
3353 : {
3354 368 : NCDFResolveVar(nGroupId, poDS->papszDimName[nXDimID], &nGroupDimXID,
3355 : &nVarDimXID);
3356 368 : NCDFResolveVar(nGroupId, poDS->papszDimName[nYDimID], &nGroupDimYID,
3357 : &nVarDimYID);
3358 : // TODO: if above resolving fails we should also search for coordinate
3359 : // variables without same name than dimension using the same resolving
3360 : // logic. This should handle for example NASA Ocean Color L2 products.
3361 :
3362 : const bool bIgnoreXYAxisNameChecks =
3363 736 : CPLTestBool(CSLFetchNameValueDef(
3364 368 : papszOpenOptions, "IGNORE_XY_AXIS_NAME_CHECKS",
3365 : CPLGetConfigOption("GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS",
3366 368 : "NO"))) ||
3367 : // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a res
3368 : // and transform attributes
3369 368 : (FetchAttr(nGroupId, nVarId, "res") != nullptr &&
3370 736 : FetchAttr(nGroupId, nVarId, "transform") != nullptr) ||
3371 367 : FetchAttr(nGroupId, NC_GLOBAL, "GMT_version") != nullptr;
3372 :
3373 : // Check that they are 1D or 2D variables
3374 368 : if (nVarDimXID >= 0)
3375 : {
3376 260 : int ndims = -1;
3377 260 : nc_inq_varndims(nGroupId, nVarDimXID, &ndims);
3378 260 : if (ndims == 0 || ndims > 2)
3379 0 : nVarDimXID = -1;
3380 260 : else if (!bIgnoreXYAxisNameChecks)
3381 : {
3382 258 : if (!NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
3383 168 : !NCDFIsVarProjectionX(nGroupId, nVarDimXID, nullptr) &&
3384 : // In case of inversion of X/Y
3385 458 : !NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr) &&
3386 32 : !NCDFIsVarProjectionY(nGroupId, nVarDimXID, nullptr))
3387 : {
3388 : char szVarNameX[NC_MAX_NAME + 1];
3389 32 : CPL_IGNORE_RET_VAL(
3390 32 : nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
3391 32 : if (!(ndims == 1 &&
3392 31 : (EQUAL(szVarNameX, CF_LONGITUDE_STD_NAME) ||
3393 30 : EQUAL(szVarNameX, CF_LONGITUDE_VAR_NAME))))
3394 : {
3395 31 : CPLDebug(
3396 : "netCDF",
3397 : "Georeferencing ignored due to non-specific "
3398 : "enough X axis name. "
3399 : "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
3400 : "as configuration option to bypass this check");
3401 31 : nVarDimXID = -1;
3402 : }
3403 : }
3404 : }
3405 : }
3406 :
3407 368 : if (nVarDimYID >= 0)
3408 : {
3409 262 : int ndims = -1;
3410 262 : nc_inq_varndims(nGroupId, nVarDimYID, &ndims);
3411 262 : if (ndims == 0 || ndims > 2)
3412 1 : nVarDimYID = -1;
3413 261 : else if (!bIgnoreXYAxisNameChecks)
3414 : {
3415 259 : if (!NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr) &&
3416 169 : !NCDFIsVarProjectionY(nGroupId, nVarDimYID, nullptr) &&
3417 : // In case of inversion of X/Y
3418 461 : !NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
3419 33 : !NCDFIsVarProjectionX(nGroupId, nVarDimYID, nullptr))
3420 : {
3421 : char szVarNameY[NC_MAX_NAME + 1];
3422 33 : CPL_IGNORE_RET_VAL(
3423 33 : nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
3424 33 : if (!(ndims == 1 &&
3425 33 : (EQUAL(szVarNameY, CF_LATITUDE_STD_NAME) ||
3426 32 : EQUAL(szVarNameY, CF_LATITUDE_VAR_NAME))))
3427 : {
3428 32 : CPLDebug(
3429 : "netCDF",
3430 : "Georeferencing ignored due to non-specific "
3431 : "enough Y axis name. "
3432 : "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
3433 : "as configuration option to bypass this check");
3434 32 : nVarDimYID = -1;
3435 : }
3436 : }
3437 : }
3438 : }
3439 :
3440 368 : if ((nVarDimXID >= 0 && xdim == 1) || (nVarDimXID >= 0 && ydim == 1))
3441 : {
3442 0 : CPLError(CE_Warning, CPLE_AppDefined,
3443 : "1-pixel width/height files not supported, "
3444 : "xdim: %ld ydim: %ld",
3445 : static_cast<long>(xdim), static_cast<long>(ydim));
3446 0 : nVarDimXID = -1;
3447 0 : nVarDimYID = -1;
3448 : }
3449 : }
3450 :
3451 560 : const char *pszUnits = nullptr;
3452 560 : if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
3453 : {
3454 272 : const char *pszUnitsX = FetchAttr(nGroupDimXID, nVarDimXID, "units");
3455 272 : const char *pszUnitsY = FetchAttr(nGroupDimYID, nVarDimYID, "units");
3456 : // Normalize degrees_east/degrees_north to degrees
3457 : // Cf https://github.com/OSGeo/gdal/issues/11009
3458 272 : if (pszUnitsX && EQUAL(pszUnitsX, "degrees_east"))
3459 79 : pszUnitsX = "degrees";
3460 272 : if (pszUnitsY && EQUAL(pszUnitsY, "degrees_north"))
3461 79 : pszUnitsY = "degrees";
3462 :
3463 272 : if (pszUnitsX && pszUnitsY)
3464 : {
3465 225 : if (EQUAL(pszUnitsX, pszUnitsY))
3466 222 : pszUnits = pszUnitsX;
3467 3 : else if (!pszWKT && !osGridMappingValue.empty())
3468 : {
3469 0 : CPLError(CE_Failure, CPLE_AppDefined,
3470 : "X axis unit (%s) is different from Y axis "
3471 : "unit (%s). SRS will ignore axis unit and be "
3472 : "likely wrong.",
3473 : pszUnitsX, pszUnitsY);
3474 : }
3475 : }
3476 47 : else if (pszUnitsX && !pszWKT && !osGridMappingValue.empty())
3477 : {
3478 0 : CPLError(CE_Failure, CPLE_AppDefined,
3479 : "X axis unit is defined, but not Y one ."
3480 : "SRS will ignore axis unit and be likely wrong.");
3481 : }
3482 47 : else if (pszUnitsY && !pszWKT && !osGridMappingValue.empty())
3483 : {
3484 0 : CPLError(CE_Failure, CPLE_AppDefined,
3485 : "Y axis unit is defined, but not X one ."
3486 : "SRS will ignore axis unit and be likely wrong.");
3487 : }
3488 : }
3489 :
3490 560 : if (!pszWKT && !osGridMappingValue.empty())
3491 : {
3492 31 : CPLStringList aosGridMappingKeyValues;
3493 31 : const size_t nLenGridMappingValue = osGridMappingValue.size();
3494 789 : for (const char *pszIter : aosMetadata)
3495 : {
3496 994 : if (STARTS_WITH(pszIter, osGridMappingValue.c_str()) &&
3497 236 : pszIter[nLenGridMappingValue] == '#')
3498 : {
3499 236 : char *pszKey = nullptr;
3500 236 : pszValue = CPLParseNameValue(pszIter + nLenGridMappingValue + 1,
3501 : &pszKey);
3502 236 : if (pszKey && pszValue)
3503 236 : aosGridMappingKeyValues.SetNameValue(pszKey, pszValue);
3504 236 : CPLFree(pszKey);
3505 : }
3506 : }
3507 :
3508 31 : bGotGeogCS = aosGridMappingKeyValues.FetchNameValue(
3509 : CF_PP_SEMI_MAJOR_AXIS) != nullptr;
3510 :
3511 31 : oSRS.importFromCF1(aosGridMappingKeyValues.List(), pszUnits);
3512 31 : bGotCfSRS = oSRS.IsGeographic() || oSRS.IsProjected();
3513 : }
3514 : else
3515 : {
3516 : // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a "crs"
3517 : // attribute hold on the variable of interest that contains a PROJ.4
3518 : // string
3519 529 : pszValue = FetchAttr(nGroupId, nVarId, "crs");
3520 530 : if (pszValue &&
3521 1 : (strstr(pszValue, "+proj=") != nullptr ||
3522 0 : strstr(pszValue, "GEOGCS") != nullptr ||
3523 0 : strstr(pszValue, "PROJCS") != nullptr ||
3524 530 : strstr(pszValue, "EPSG:") != nullptr) &&
3525 1 : oSRS.SetFromUserInput(pszValue) == OGRERR_NONE)
3526 : {
3527 1 : bGotCfSRS = true;
3528 : }
3529 : }
3530 :
3531 : // Set Projection from CF.
3532 560 : double dfLinearUnitsConvFactor = 1.0;
3533 560 : if ((bGotGeogCS || bGotCfSRS))
3534 : {
3535 31 : if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
3536 : {
3537 : // Set SRS Units.
3538 :
3539 : // Check units for x and y.
3540 28 : if (oSRS.IsProjected())
3541 : {
3542 25 : dfLinearUnitsConvFactor = oSRS.GetLinearUnits(nullptr);
3543 :
3544 : // If the user doesn't ask to preserve the axis unit,
3545 : // then normalize to metre
3546 31 : if (dfLinearUnitsConvFactor != 1.0 &&
3547 6 : !CPLFetchBool(GetOpenOptions(), "PRESERVE_AXIS_UNIT_IN_CRS",
3548 : false))
3549 : {
3550 5 : oSRS.SetLinearUnits("metre", 1.0);
3551 5 : oSRS.SetAuthority("PROJCS|UNIT", "EPSG", 9001);
3552 : }
3553 : else
3554 : {
3555 20 : dfLinearUnitsConvFactor = 1.0;
3556 : }
3557 : }
3558 : }
3559 :
3560 : // Set projection.
3561 31 : char *pszTempProjection = nullptr;
3562 31 : oSRS.exportToWkt(&pszTempProjection);
3563 31 : if (pszTempProjection)
3564 : {
3565 31 : CPLDebug("GDAL_netCDF", "setting WKT from CF");
3566 31 : if (returnProjStr != nullptr)
3567 : {
3568 2 : (*returnProjStr) = std::string(pszTempProjection);
3569 : }
3570 : else
3571 : {
3572 29 : m_bAddedProjectionVarsDefs = true;
3573 29 : m_bAddedProjectionVarsData = true;
3574 29 : SetSpatialRefNoUpdate(&oSRS);
3575 : }
3576 : }
3577 31 : CPLFree(pszTempProjection);
3578 : }
3579 :
3580 560 : if (!bReadSRSOnly && (nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 &&
3581 : ydim > 0)
3582 : {
3583 : double *pdfXCoord =
3584 229 : static_cast<double *>(CPLCalloc(xdim, sizeof(double)));
3585 : double *pdfYCoord =
3586 229 : static_cast<double *>(CPLCalloc(ydim, sizeof(double)));
3587 :
3588 229 : size_t start[2] = {0, 0};
3589 229 : size_t edge[2] = {xdim, 0};
3590 229 : int status = nc_get_vara_double(nGroupDimXID, nVarDimXID, start, edge,
3591 : pdfXCoord);
3592 229 : NCDF_ERR(status);
3593 :
3594 229 : edge[0] = ydim;
3595 229 : status = nc_get_vara_double(nGroupDimYID, nVarDimYID, start, edge,
3596 : pdfYCoord);
3597 229 : NCDF_ERR(status);
3598 :
3599 229 : nc_type nc_var_dimx_datatype = NC_NAT;
3600 : status =
3601 229 : nc_inq_vartype(nGroupDimXID, nVarDimXID, &nc_var_dimx_datatype);
3602 229 : NCDF_ERR(status);
3603 :
3604 229 : nc_type nc_var_dimy_datatype = NC_NAT;
3605 : status =
3606 229 : nc_inq_vartype(nGroupDimYID, nVarDimYID, &nc_var_dimy_datatype);
3607 229 : NCDF_ERR(status);
3608 :
3609 229 : if (!poDS->bSwitchedXY)
3610 : {
3611 : // Convert ]180,540] longitude values to ]-180,0].
3612 317 : if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
3613 90 : CPLTestBool(
3614 : CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")))
3615 : {
3616 : // If minimum longitude is > 180, subtract 360 from all.
3617 : // Add a check on the maximum X value too, since
3618 : // NCDFIsVarLongitude() is not very specific by default (see
3619 : // https://github.com/OSGeo/gdal/issues/1440)
3620 97 : if (std::min(pdfXCoord[0], pdfXCoord[xdim - 1]) > 180.0 &&
3621 7 : std::max(pdfXCoord[0], pdfXCoord[xdim - 1]) <= 540)
3622 : {
3623 0 : CPLDebug(
3624 : "GDAL_netCDF",
3625 : "Offsetting longitudes from ]180,540] to ]-180,180]. "
3626 : "Can be disabled with GDAL_NETCDF_CENTERLONG_180=NO");
3627 0 : for (size_t i = 0; i < xdim; i++)
3628 0 : pdfXCoord[i] -= 360;
3629 : }
3630 : }
3631 : }
3632 :
3633 : // Is pixel spacing uniform across the map?
3634 :
3635 : // Check Longitude.
3636 :
3637 229 : bool bLonSpacingOK = false;
3638 229 : if (xdim == 2)
3639 : {
3640 29 : bLonSpacingOK = true;
3641 : }
3642 : else
3643 : {
3644 200 : bool bWestIsLeft = (pdfXCoord[0] < pdfXCoord[xdim - 1]);
3645 :
3646 : // fix longitudes if longitudes should increase from
3647 : // west to east, but west > east
3648 280 : if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
3649 80 : !bWestIsLeft)
3650 : {
3651 2 : size_t ndecreases = 0;
3652 :
3653 : // there is lon wrap if longitudes increase
3654 : // with one single decrease
3655 107 : for (size_t i = 1; i < xdim; i++)
3656 : {
3657 105 : if (pdfXCoord[i] < pdfXCoord[i - 1])
3658 1 : ndecreases++;
3659 : }
3660 :
3661 2 : if (ndecreases == 1)
3662 : {
3663 1 : CPLDebug("GDAL_netCDF", "longitude wrap detected");
3664 4 : for (size_t i = 0; i < xdim; i++)
3665 : {
3666 3 : if (pdfXCoord[i] > pdfXCoord[xdim - 1])
3667 1 : pdfXCoord[i] -= 360;
3668 : }
3669 : }
3670 : }
3671 :
3672 200 : const double dfSpacingBegin = pdfXCoord[1] - pdfXCoord[0];
3673 200 : const double dfSpacingMiddle =
3674 200 : pdfXCoord[xdim / 2 + 1] - pdfXCoord[xdim / 2];
3675 200 : const double dfSpacingLast =
3676 200 : pdfXCoord[xdim - 1] - pdfXCoord[xdim - 2];
3677 :
3678 200 : CPLDebug("GDAL_netCDF",
3679 : "xdim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
3680 : "dfSpacingLast: %f",
3681 : static_cast<long>(xdim), dfSpacingBegin, dfSpacingMiddle,
3682 : dfSpacingLast);
3683 : #ifdef NCDF_DEBUG
3684 : CPLDebug("GDAL_netCDF", "xcoords: %f %f %f %f %f %f", pdfXCoord[0],
3685 : pdfXCoord[1], pdfXCoord[xdim / 2],
3686 : pdfXCoord[(xdim / 2) + 1], pdfXCoord[xdim - 2],
3687 : pdfXCoord[xdim - 1]);
3688 : #endif
3689 :
3690 : // ftp://ftp.cdc.noaa.gov/Datasets/NARR/Dailies/monolevel/vwnd.10m.2015.nc
3691 : // requires a 0.02% tolerance, so let's settle for 0.05%
3692 :
3693 : // For float variables, increase to 0.2% (as seen in
3694 : // https://github.com/OSGeo/gdal/issues/3663)
3695 200 : const double dfEpsRel =
3696 200 : nc_var_dimx_datatype == NC_FLOAT ? 0.002 : 0.0005;
3697 :
3698 : const double dfEps =
3699 : dfEpsRel *
3700 400 : std::max(fabs(dfSpacingBegin),
3701 200 : std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
3702 394 : if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
3703 394 : IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
3704 194 : IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
3705 : {
3706 194 : bLonSpacingOK = true;
3707 : }
3708 6 : else if (CPLTestBool(CPLGetConfigOption(
3709 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
3710 : {
3711 0 : bLonSpacingOK = true;
3712 0 : CPLDebug(
3713 : "GDAL_netCDF",
3714 : "Longitude/X is not equally spaced, but will be considered "
3715 : "as such because of "
3716 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
3717 : }
3718 : }
3719 :
3720 229 : if (bLonSpacingOK == false)
3721 : {
3722 6 : CPLDebug(
3723 : "GDAL_netCDF", "%s",
3724 : "Longitude/X is not equally spaced (with a 0.05% tolerance). "
3725 : "You may set the "
3726 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
3727 : "option to YES to ignore this check");
3728 : }
3729 :
3730 : // Check Latitude.
3731 229 : bool bLatSpacingOK = false;
3732 :
3733 229 : if (ydim == 2)
3734 : {
3735 49 : bLatSpacingOK = true;
3736 : }
3737 : else
3738 : {
3739 180 : const double dfSpacingBegin = pdfYCoord[1] - pdfYCoord[0];
3740 180 : const double dfSpacingMiddle =
3741 180 : pdfYCoord[ydim / 2 + 1] - pdfYCoord[ydim / 2];
3742 :
3743 180 : const double dfSpacingLast =
3744 180 : pdfYCoord[ydim - 1] - pdfYCoord[ydim - 2];
3745 :
3746 180 : CPLDebug("GDAL_netCDF",
3747 : "ydim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
3748 : "dfSpacingLast: %f",
3749 : (long)ydim, dfSpacingBegin, dfSpacingMiddle,
3750 : dfSpacingLast);
3751 : #ifdef NCDF_DEBUG
3752 : CPLDebug("GDAL_netCDF", "ycoords: %f %f %f %f %f %f", pdfYCoord[0],
3753 : pdfYCoord[1], pdfYCoord[ydim / 2],
3754 : pdfYCoord[(ydim / 2) + 1], pdfYCoord[ydim - 2],
3755 : pdfYCoord[ydim - 1]);
3756 : #endif
3757 :
3758 180 : const double dfEpsRel =
3759 180 : nc_var_dimy_datatype == NC_FLOAT ? 0.002 : 0.0005;
3760 :
3761 : const double dfEps =
3762 : dfEpsRel *
3763 360 : std::max(fabs(dfSpacingBegin),
3764 180 : std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
3765 358 : if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
3766 358 : IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
3767 169 : IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
3768 : {
3769 169 : bLatSpacingOK = true;
3770 : }
3771 11 : else if (CPLTestBool(CPLGetConfigOption(
3772 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
3773 : {
3774 0 : bLatSpacingOK = true;
3775 0 : CPLDebug(
3776 : "GDAL_netCDF",
3777 : "Latitude/Y is not equally spaced, but will be considered "
3778 : "as such because of "
3779 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
3780 : }
3781 11 : else if (!oSRS.IsProjected() &&
3782 11 : fabs(dfSpacingBegin - dfSpacingLast) <= 0.1 &&
3783 30 : fabs(dfSpacingBegin - dfSpacingMiddle) <= 0.1 &&
3784 8 : fabs(dfSpacingMiddle - dfSpacingLast) <= 0.1)
3785 : {
3786 8 : bLatSpacingOK = true;
3787 8 : CPLError(CE_Warning, CPLE_AppDefined,
3788 : "Latitude grid not spaced evenly. "
3789 : "Setting projection for grid spacing is "
3790 : "within 0.1 degrees threshold.");
3791 :
3792 8 : CPLDebug("GDAL_netCDF",
3793 : "Latitude grid not spaced evenly, but within 0.1 "
3794 : "degree threshold (probably a Gaussian grid). "
3795 : "Saving original latitude values in Y_VALUES "
3796 : "geolocation metadata");
3797 8 : Set1DGeolocation(nGroupDimYID, nVarDimYID, "Y");
3798 : }
3799 :
3800 180 : if (bLatSpacingOK == false)
3801 : {
3802 3 : CPLDebug(
3803 : "GDAL_netCDF", "%s",
3804 : "Latitude/Y is not equally spaced (with a 0.05% "
3805 : "tolerance). "
3806 : "You may set the "
3807 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
3808 : "option to YES to ignore this check");
3809 : }
3810 : }
3811 :
3812 229 : if (bLonSpacingOK && bLatSpacingOK)
3813 : {
3814 : // We have gridded data so we can set the Georeferencing info.
3815 :
3816 : // Enable GeoTransform.
3817 :
3818 : // In the following "actual_range" and "node_offset"
3819 : // are attributes used by netCDF files created by GMT.
3820 : // If we find them we know how to proceed. Else, use
3821 : // the original algorithm.
3822 222 : bGotCfGT = true;
3823 :
3824 222 : int node_offset = 0;
3825 : const bool bUseActualRange =
3826 222 : NCDFResolveAttInt(nGroupId, NC_GLOBAL, "node_offset",
3827 222 : &node_offset) == CE_None;
3828 :
3829 222 : double adfActualRange[2] = {0.0, 0.0};
3830 222 : double xMinMax[2] = {0.0, 0.0};
3831 222 : double yMinMax[2] = {0.0, 0.0};
3832 :
3833 : const auto RoundMinMaxForFloatVals =
3834 60 : [](double &dfMin, double &dfMax, int nIntervals)
3835 : {
3836 : // Helps for a case where longitudes range from
3837 : // -179.99 to 180.0 with a 0.01 degree spacing.
3838 : // However as this is encoded in a float array,
3839 : // -179.99 is actually read as -179.99000549316406 as
3840 : // a double. Try to detect that and correct the rounding
3841 :
3842 88 : const auto IsAlmostInteger = [](double dfVal)
3843 : {
3844 88 : constexpr double THRESHOLD_INTEGER = 1e-3;
3845 88 : return std::fabs(dfVal - std::round(dfVal)) <=
3846 88 : THRESHOLD_INTEGER;
3847 : };
3848 :
3849 60 : const double dfSpacing = (dfMax - dfMin) / nIntervals;
3850 60 : if (dfSpacing > 0)
3851 : {
3852 48 : const double dfInvSpacing = 1.0 / dfSpacing;
3853 48 : if (IsAlmostInteger(dfInvSpacing))
3854 : {
3855 20 : const double dfRoundedSpacing =
3856 20 : 1.0 / std::round(dfInvSpacing);
3857 20 : const double dfMinDivRoundedSpacing =
3858 20 : dfMin / dfRoundedSpacing;
3859 20 : const double dfMaxDivRoundedSpacing =
3860 20 : dfMax / dfRoundedSpacing;
3861 40 : if (IsAlmostInteger(dfMinDivRoundedSpacing) &&
3862 20 : IsAlmostInteger(dfMaxDivRoundedSpacing))
3863 : {
3864 20 : const double dfRoundedMin =
3865 20 : std::round(dfMinDivRoundedSpacing) *
3866 : dfRoundedSpacing;
3867 20 : const double dfRoundedMax =
3868 20 : std::round(dfMaxDivRoundedSpacing) *
3869 : dfRoundedSpacing;
3870 20 : if (static_cast<float>(dfMin) ==
3871 20 : static_cast<float>(dfRoundedMin) &&
3872 8 : static_cast<float>(dfMax) ==
3873 8 : static_cast<float>(dfRoundedMax))
3874 : {
3875 7 : dfMin = dfRoundedMin;
3876 7 : dfMax = dfRoundedMax;
3877 : }
3878 : }
3879 : }
3880 : }
3881 60 : };
3882 :
3883 225 : if (bUseActualRange &&
3884 3 : !nc_get_att_double(nGroupDimXID, nVarDimXID, "actual_range",
3885 : adfActualRange))
3886 : {
3887 1 : xMinMax[0] = adfActualRange[0];
3888 1 : xMinMax[1] = adfActualRange[1];
3889 :
3890 : // Present xMinMax[] in the same order as padfXCoord
3891 1 : if ((xMinMax[0] - xMinMax[1]) *
3892 1 : (pdfXCoord[0] - pdfXCoord[xdim - 1]) <
3893 : 0)
3894 : {
3895 0 : std::swap(xMinMax[0], xMinMax[1]);
3896 : }
3897 : }
3898 : else
3899 : {
3900 221 : xMinMax[0] = pdfXCoord[0];
3901 221 : xMinMax[1] = pdfXCoord[xdim - 1];
3902 221 : node_offset = 0;
3903 :
3904 221 : if (nc_var_dimx_datatype == NC_FLOAT)
3905 : {
3906 30 : RoundMinMaxForFloatVals(xMinMax[0], xMinMax[1],
3907 30 : poDS->nRasterXSize - 1);
3908 : }
3909 : }
3910 :
3911 225 : if (bUseActualRange &&
3912 3 : !nc_get_att_double(nGroupDimYID, nVarDimYID, "actual_range",
3913 : adfActualRange))
3914 : {
3915 1 : yMinMax[0] = adfActualRange[0];
3916 1 : yMinMax[1] = adfActualRange[1];
3917 :
3918 : // Present yMinMax[] in the same order as pdfYCoord
3919 1 : if ((yMinMax[0] - yMinMax[1]) *
3920 1 : (pdfYCoord[0] - pdfYCoord[ydim - 1]) <
3921 : 0)
3922 : {
3923 0 : std::swap(yMinMax[0], yMinMax[1]);
3924 : }
3925 : }
3926 : else
3927 : {
3928 221 : yMinMax[0] = pdfYCoord[0];
3929 221 : yMinMax[1] = pdfYCoord[ydim - 1];
3930 221 : node_offset = 0;
3931 :
3932 221 : if (nc_var_dimy_datatype == NC_FLOAT)
3933 : {
3934 30 : RoundMinMaxForFloatVals(yMinMax[0], yMinMax[1],
3935 30 : poDS->nRasterYSize - 1);
3936 : }
3937 : }
3938 :
3939 222 : double dfCoordOffset = 0.0;
3940 222 : double dfCoordScale = 1.0;
3941 222 : if (!nc_get_att_double(nGroupId, nVarDimXID, CF_ADD_OFFSET,
3942 226 : &dfCoordOffset) &&
3943 4 : !nc_get_att_double(nGroupId, nVarDimXID, CF_SCALE_FACTOR,
3944 : &dfCoordScale))
3945 : {
3946 4 : xMinMax[0] = dfCoordOffset + xMinMax[0] * dfCoordScale;
3947 4 : xMinMax[1] = dfCoordOffset + xMinMax[1] * dfCoordScale;
3948 : }
3949 :
3950 222 : if (!nc_get_att_double(nGroupId, nVarDimYID, CF_ADD_OFFSET,
3951 226 : &dfCoordOffset) &&
3952 4 : !nc_get_att_double(nGroupId, nVarDimYID, CF_SCALE_FACTOR,
3953 : &dfCoordScale))
3954 : {
3955 4 : yMinMax[0] = dfCoordOffset + yMinMax[0] * dfCoordScale;
3956 4 : yMinMax[1] = dfCoordOffset + yMinMax[1] * dfCoordScale;
3957 : }
3958 :
3959 : // Check for reverse order of y-coordinate.
3960 222 : if (!bSwitchedXY)
3961 : {
3962 220 : poDS->bBottomUp = (yMinMax[0] <= yMinMax[1]);
3963 220 : if (!poDS->bBottomUp)
3964 : {
3965 32 : std::swap(yMinMax[0], yMinMax[1]);
3966 : }
3967 : }
3968 :
3969 : // Geostationary satellites can specify units in (micro)radians
3970 : // So we check if they do, and if so convert to linear units
3971 : // (meters)
3972 222 : const char *pszProjName = oSRS.GetAttrValue("PROJECTION");
3973 222 : if (pszProjName != nullptr)
3974 : {
3975 24 : if (EQUAL(pszProjName, SRS_PT_GEOSTATIONARY_SATELLITE))
3976 : {
3977 : const double satelliteHeight =
3978 3 : oSRS.GetProjParm(SRS_PP_SATELLITE_HEIGHT, 1.0);
3979 6 : std::string osUnits;
3980 3 : if (NCDFGetAttr(nGroupId, nVarDimXID, "units", osUnits) ==
3981 : CE_None)
3982 : {
3983 3 : if (EQUAL(osUnits.c_str(), "microradian"))
3984 : {
3985 1 : xMinMax[0] =
3986 1 : xMinMax[0] * satelliteHeight * 0.000001;
3987 1 : xMinMax[1] =
3988 1 : xMinMax[1] * satelliteHeight * 0.000001;
3989 : }
3990 3 : else if (EQUAL(osUnits.c_str(), "rad") ||
3991 1 : EQUAL(osUnits.c_str(), "radian"))
3992 : {
3993 2 : xMinMax[0] = xMinMax[0] * satelliteHeight;
3994 2 : xMinMax[1] = xMinMax[1] * satelliteHeight;
3995 : }
3996 : }
3997 3 : if (NCDFGetAttr(nGroupId, nVarDimYID, "units", osUnits) ==
3998 : CE_None)
3999 : {
4000 3 : if (EQUAL(osUnits.c_str(), "microradian"))
4001 : {
4002 1 : yMinMax[0] =
4003 1 : yMinMax[0] * satelliteHeight * 0.000001;
4004 1 : yMinMax[1] =
4005 1 : yMinMax[1] * satelliteHeight * 0.000001;
4006 : }
4007 3 : else if (EQUAL(osUnits.c_str(), "rad") ||
4008 1 : EQUAL(osUnits.c_str(), "radian"))
4009 : {
4010 2 : yMinMax[0] = yMinMax[0] * satelliteHeight;
4011 2 : yMinMax[1] = yMinMax[1] * satelliteHeight;
4012 : }
4013 : }
4014 : }
4015 : }
4016 :
4017 222 : tmpGT[0] = xMinMax[0];
4018 444 : tmpGT[1] = (xMinMax[1] - xMinMax[0]) /
4019 222 : (poDS->nRasterXSize + (node_offset - 1));
4020 222 : tmpGT[2] = 0;
4021 222 : if (bSwitchedXY)
4022 : {
4023 2 : tmpGT[3] = yMinMax[0];
4024 2 : tmpGT[4] = 0;
4025 2 : tmpGT[5] = (yMinMax[1] - yMinMax[0]) /
4026 2 : (poDS->nRasterYSize + (node_offset - 1));
4027 : }
4028 : else
4029 : {
4030 220 : tmpGT[3] = yMinMax[1];
4031 220 : tmpGT[4] = 0;
4032 220 : tmpGT[5] = (yMinMax[0] - yMinMax[1]) /
4033 220 : (poDS->nRasterYSize + (node_offset - 1));
4034 : }
4035 :
4036 : // Compute the center of the pixel.
4037 222 : if (!node_offset)
4038 : {
4039 : // Otherwise its already the pixel center.
4040 222 : tmpGT[0] -= (tmpGT[1] / 2);
4041 222 : tmpGT[3] -= (tmpGT[5] / 2);
4042 : }
4043 : }
4044 :
4045 : const auto AreSRSEqualThroughProj4String =
4046 2 : [](const OGRSpatialReference &oSRS1,
4047 : const OGRSpatialReference &oSRS2)
4048 : {
4049 2 : char *pszProj4Str1 = nullptr;
4050 2 : oSRS1.exportToProj4(&pszProj4Str1);
4051 :
4052 2 : char *pszProj4Str2 = nullptr;
4053 2 : oSRS2.exportToProj4(&pszProj4Str2);
4054 :
4055 : {
4056 2 : char *pszTmp = strstr(pszProj4Str1, "+datum=");
4057 2 : if (pszTmp)
4058 0 : memcpy(pszTmp, "+ellps=", strlen("+ellps="));
4059 : }
4060 :
4061 : {
4062 2 : char *pszTmp = strstr(pszProj4Str2, "+datum=");
4063 2 : if (pszTmp)
4064 2 : memcpy(pszTmp, "+ellps=", strlen("+ellps="));
4065 : }
4066 :
4067 2 : bool bRet = false;
4068 2 : if (pszProj4Str1 && pszProj4Str2 &&
4069 2 : EQUAL(pszProj4Str1, pszProj4Str2))
4070 : {
4071 1 : bRet = true;
4072 : }
4073 :
4074 2 : CPLFree(pszProj4Str1);
4075 2 : CPLFree(pszProj4Str2);
4076 2 : return bRet;
4077 : };
4078 :
4079 229 : if (dfLinearUnitsConvFactor != 1.0)
4080 : {
4081 35 : for (int i = 0; i < 6; ++i)
4082 30 : tmpGT[i] *= dfLinearUnitsConvFactor;
4083 :
4084 5 : if (paosRemovedMDItems)
4085 : {
4086 : char szVarNameX[NC_MAX_NAME + 1];
4087 5 : CPL_IGNORE_RET_VAL(
4088 5 : nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
4089 :
4090 : char szVarNameY[NC_MAX_NAME + 1];
4091 5 : CPL_IGNORE_RET_VAL(
4092 5 : nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
4093 :
4094 5 : paosRemovedMDItems->push_back(
4095 : CPLSPrintf("%s#units", szVarNameX));
4096 5 : paosRemovedMDItems->push_back(
4097 : CPLSPrintf("%s#units", szVarNameY));
4098 : }
4099 : }
4100 :
4101 : // If there is a global "geospatial_bounds_crs" attribute, check that it
4102 : // is consistent with the SRS, and if so, use it as the SRS
4103 : const char *pszGBCRS =
4104 229 : FetchAttr(nGroupId, NC_GLOBAL, "geospatial_bounds_crs");
4105 229 : if (pszGBCRS && STARTS_WITH(pszGBCRS, "EPSG:"))
4106 : {
4107 4 : OGRSpatialReference oSRSFromGBCRS;
4108 2 : oSRSFromGBCRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
4109 2 : if (oSRSFromGBCRS.SetFromUserInput(
4110 : pszGBCRS,
4111 : OGRSpatialReference::
4112 4 : SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE &&
4113 2 : AreSRSEqualThroughProj4String(oSRS, oSRSFromGBCRS))
4114 : {
4115 1 : oSRS = std::move(oSRSFromGBCRS);
4116 1 : SetSpatialRefNoUpdate(&oSRS);
4117 : }
4118 : }
4119 :
4120 229 : CPLFree(pdfXCoord);
4121 229 : CPLFree(pdfYCoord);
4122 : } // end if(has dims)
4123 :
4124 : // Process custom GeoTransform GDAL value.
4125 560 : if (!osGridMappingValue.empty())
4126 : {
4127 239 : if (pszGeoTransform != nullptr)
4128 : {
4129 : const CPLStringList aosGeoTransform(
4130 256 : CSLTokenizeString2(pszGeoTransform, " ", CSLT_HONOURSTRINGS));
4131 128 : if (aosGeoTransform.size() == 6)
4132 : {
4133 128 : bool bUseGeoTransformFromAttribute = true;
4134 :
4135 128 : GDALGeoTransform gtFromAttribute;
4136 896 : for (int i = 0; i < 6; i++)
4137 : {
4138 768 : gtFromAttribute[i] = CPLAtof(aosGeoTransform[i]);
4139 : }
4140 :
4141 : // When GDAL writes a raster that is north-up oriented, it
4142 : // writes the "GeoTransform" attribute unmodified, that is with
4143 : // gt.yscale < 0, but the first line is actually the southern-most
4144 : // one, consistently with the values of the "y" coordinate
4145 : // variable. This is wrong... but we have always done that, so
4146 : // this is hard to fix now.
4147 : // However there are datasets like
4148 : // https://public.hub.geosphere.at/datahub/resources/spartacus-v2-1d-1km/filelisting/TN/SPARTACUS2-DAILY_TN_2026.nc
4149 : // that correctly use a positive gt.yscale value. So make sure to not emit
4150 : // a warning when comparing against the geotransform derived from
4151 : // the x/y coordinates.
4152 128 : GDALGeoTransform gtFromAttributeNorthUp = gtFromAttribute;
4153 133 : if (gtFromAttributeNorthUp.yscale > 0 &&
4154 5 : gtFromAttributeNorthUp.IsAxisAligned())
4155 : {
4156 1 : gtFromAttributeNorthUp.yorig +=
4157 1 : poDS->nRasterYSize * gtFromAttributeNorthUp.yscale;
4158 1 : gtFromAttributeNorthUp.yscale =
4159 1 : -gtFromAttributeNorthUp.yscale;
4160 : }
4161 :
4162 128 : if (bGotCfGT)
4163 : {
4164 98 : constexpr double GT_RELERROR_WARN_THRESHOLD = 1e-6;
4165 98 : double dfMaxAbsoluteError = 0.0;
4166 686 : for (int i = 0; i < 6; i++)
4167 : {
4168 : double dfAbsoluteError =
4169 588 : std::abs(tmpGT[i] - gtFromAttributeNorthUp[i]);
4170 588 : if (dfAbsoluteError >
4171 588 : std::abs(gtFromAttributeNorthUp[i] *
4172 : GT_RELERROR_WARN_THRESHOLD))
4173 : {
4174 3 : dfMaxAbsoluteError =
4175 3 : std::max(dfMaxAbsoluteError, dfAbsoluteError);
4176 : }
4177 : }
4178 :
4179 98 : if (dfMaxAbsoluteError > 0)
4180 : {
4181 3 : bUseGeoTransformFromAttribute = false;
4182 3 : CPLError(CE_Warning, CPLE_AppDefined,
4183 : "GeoTransform read from attribute of %s "
4184 : "variable differs from value calculated from "
4185 : "dimension variables (max diff = %g). Using "
4186 : "value calculated from dimension variables.",
4187 : osGridMappingValue.c_str(),
4188 : dfMaxAbsoluteError);
4189 : }
4190 : }
4191 :
4192 128 : if (bUseGeoTransformFromAttribute)
4193 : {
4194 125 : if (bGotCfGT)
4195 : {
4196 95 : tmpGT = gtFromAttributeNorthUp;
4197 95 : if (gtFromAttributeNorthUp.IsAxisAligned())
4198 : {
4199 95 : poDS->bBottomUp = true;
4200 : }
4201 : }
4202 : else
4203 : {
4204 30 : tmpGT = gtFromAttribute;
4205 : }
4206 125 : bGotGdalGT = true;
4207 : }
4208 : }
4209 : }
4210 : else
4211 : {
4212 : // Look for corner array values.
4213 : // CPLDebug("GDAL_netCDF",
4214 : // "looking for geotransform corners");
4215 111 : bool bGotNN = false;
4216 111 : double dfNN = FetchCopyParam(osGridMappingValue.c_str(),
4217 : "Northernmost_Northing", 0, &bGotNN);
4218 :
4219 111 : bool bGotSN = false;
4220 111 : double dfSN = FetchCopyParam(osGridMappingValue.c_str(),
4221 : "Southernmost_Northing", 0, &bGotSN);
4222 :
4223 111 : bool bGotEE = false;
4224 111 : double dfEE = FetchCopyParam(osGridMappingValue.c_str(),
4225 : "Easternmost_Easting", 0, &bGotEE);
4226 :
4227 111 : bool bGotWE = false;
4228 111 : double dfWE = FetchCopyParam(osGridMappingValue.c_str(),
4229 : "Westernmost_Easting", 0, &bGotWE);
4230 :
4231 : // Only set the GeoTransform if we got all the values.
4232 111 : if (bGotNN && bGotSN && bGotEE && bGotWE)
4233 : {
4234 0 : bGotGdalGT = true;
4235 :
4236 0 : tmpGT[0] = dfWE;
4237 0 : tmpGT[1] = (dfEE - dfWE) / (poDS->GetRasterXSize() - 1);
4238 0 : tmpGT[2] = 0.0;
4239 0 : tmpGT[3] = dfNN;
4240 0 : tmpGT[4] = 0.0;
4241 0 : tmpGT[5] = (dfSN - dfNN) / (poDS->GetRasterYSize() - 1);
4242 : // Compute the center of the pixel.
4243 0 : tmpGT[0] = dfWE - (tmpGT[1] / 2);
4244 0 : tmpGT[3] = dfNN - (tmpGT[5] / 2);
4245 : }
4246 : } // (pszGeoTransform != NULL)
4247 :
4248 239 : if (bGotGdalSRS && !bGotGdalGT)
4249 78 : CPLDebug("GDAL_netCDF", "Got SRS but no geotransform from GDAL!");
4250 : }
4251 :
4252 560 : if (bGotCfGT || bGotGdalGT)
4253 : {
4254 252 : CPLDebug("GDAL_netCDF", "set bBottomUp = %d from Y axis",
4255 252 : static_cast<int>(poDS->bBottomUp));
4256 : }
4257 :
4258 560 : if (!pszWKT && !bGotCfSRS)
4259 : {
4260 : // Some netCDF files have a srid attribute (#6613) like
4261 : // urn:ogc:def:crs:EPSG::6931
4262 321 : const char *pszSRID = FetchAttr(osGridMappingValue.c_str(), "srid");
4263 321 : if (pszSRID != nullptr)
4264 : {
4265 0 : oSRS.Clear();
4266 0 : if (oSRS.SetFromUserInput(
4267 : pszSRID,
4268 : OGRSpatialReference::
4269 0 : SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE)
4270 : {
4271 0 : CPLDebug("GDAL_netCDF", "Got SRS from %s", pszSRID);
4272 0 : std::string osWKTExport = oSRS.exportToWkt();
4273 0 : if (!osWKTExport.empty())
4274 : {
4275 0 : (*returnProjStr) = std::move(osWKTExport);
4276 : }
4277 : else
4278 : {
4279 0 : m_bAddedProjectionVarsDefs = true;
4280 0 : m_bAddedProjectionVarsData = true;
4281 0 : SetSpatialRefNoUpdate(&oSRS);
4282 : }
4283 : }
4284 : }
4285 : }
4286 :
4287 560 : if (bReadSRSOnly)
4288 192 : return;
4289 :
4290 : // Determines the SRS to be used by the geolocation array, if any
4291 736 : std::string osGeolocWKT = SRS_WKT_WGS84_LAT_LONG;
4292 368 : if (!m_oSRS.IsEmpty())
4293 : {
4294 290 : OGRSpatialReference oGeogCRS;
4295 145 : oGeogCRS.CopyGeogCSFrom(&m_oSRS);
4296 145 : const char *const apszOptions[] = {"FORMAT=WKT2_2019", nullptr};
4297 :
4298 290 : std::string osWKTTmp = oGeogCRS.exportToWkt(apszOptions);
4299 145 : if (!osWKTTmp.empty())
4300 : {
4301 145 : osGeolocWKT = std::move(osWKTTmp);
4302 : }
4303 : }
4304 :
4305 : // Process geolocation arrays from CF "coordinates" attribute.
4306 736 : std::string osGeolocXName, osGeolocYName;
4307 368 : if (ProcessCFGeolocation(nGroupId, nVarId, osGeolocWKT, osGeolocXName,
4308 368 : osGeolocYName))
4309 : {
4310 61 : bool bCanCancelGT = true;
4311 61 : if ((nVarDimXID != -1) && (nVarDimYID != -1))
4312 : {
4313 : char szVarNameX[NC_MAX_NAME + 1];
4314 44 : CPL_IGNORE_RET_VAL(
4315 44 : nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
4316 : char szVarNameY[NC_MAX_NAME + 1];
4317 44 : CPL_IGNORE_RET_VAL(
4318 44 : nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
4319 44 : bCanCancelGT =
4320 44 : !(osGeolocXName == szVarNameX && osGeolocYName == szVarNameY);
4321 : }
4322 100 : if (bCanCancelGT && !m_oSRS.IsGeographic() && !m_oSRS.IsProjected() &&
4323 39 : !bSwitchedXY)
4324 : {
4325 37 : bGotCfGT = false;
4326 : }
4327 : }
4328 125 : else if (!bGotCfGT && !bReadSRSOnly && (nVarDimXID != -1) &&
4329 435 : (nVarDimYID != -1) && xdim > 0 && ydim > 0 &&
4330 3 : ((!bSwitchedXY &&
4331 3 : NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
4332 1 : NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr)) ||
4333 2 : (bSwitchedXY &&
4334 0 : NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
4335 0 : NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr))))
4336 : {
4337 : // Case of autotest/gdrivers/data/netcdf/GLMELT_4X5.OCN.nc
4338 : // which is indexed by lat, lon variables, but lat has irregular
4339 : // spacing.
4340 1 : const char *pszGeolocXFullName = poDS->papszDimName[poDS->nXDimID];
4341 1 : const char *pszGeolocYFullName = poDS->papszDimName[poDS->nYDimID];
4342 1 : if (bSwitchedXY)
4343 : {
4344 0 : std::swap(pszGeolocXFullName, pszGeolocYFullName);
4345 0 : GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", "GEOLOCATION");
4346 : }
4347 :
4348 1 : CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
4349 : pszGeolocXFullName, pszGeolocYFullName);
4350 :
4351 1 : GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
4352 : "GEOLOCATION");
4353 :
4354 2 : CPLString osTMP;
4355 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4356 1 : pszGeolocXFullName);
4357 :
4358 1 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
4359 1 : GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
4360 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4361 1 : pszGeolocYFullName);
4362 :
4363 1 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
4364 1 : GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
4365 :
4366 1 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
4367 1 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
4368 :
4369 1 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
4370 1 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
4371 :
4372 1 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
4373 : "PIXEL_CENTER", "GEOLOCATION");
4374 : }
4375 :
4376 : // Set GeoTransform if we got a complete one - after projection has been set
4377 368 : if (bGotCfGT || bGotGdalGT)
4378 : {
4379 212 : m_bAddedProjectionVarsDefs = true;
4380 212 : m_bAddedProjectionVarsData = true;
4381 212 : SetGeoTransformNoUpdate(tmpGT);
4382 : }
4383 :
4384 : // Debugging reports.
4385 368 : CPLDebug("GDAL_netCDF",
4386 : "bGotGeogCS=%d bGotCfSRS=%d bGotCfGT=%d bGotCfWktSRS=%d "
4387 : "bGotGdalSRS=%d bGotGdalGT=%d",
4388 : static_cast<int>(bGotGeogCS), static_cast<int>(bGotCfSRS),
4389 : static_cast<int>(bGotCfGT), static_cast<int>(bGotCfWktSRS),
4390 : static_cast<int>(bGotGdalSRS), static_cast<int>(bGotGdalGT));
4391 :
4392 368 : if (!bGotCfGT && !bGotGdalGT)
4393 156 : CPLDebug("GDAL_netCDF", "did not get geotransform from CF nor GDAL!");
4394 :
4395 368 : if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfGT && !bGotCfWktSRS)
4396 156 : CPLDebug("GDAL_netCDF", "did not get projection from CF nor GDAL!");
4397 :
4398 : // wish of 6195
4399 : // we don't have CS/SRS, but we do have GT, and we live in -180,360 -90,90
4400 368 : if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfWktSRS)
4401 : {
4402 223 : if (bGotCfGT || bGotGdalGT)
4403 : {
4404 134 : bool bAssumedLongLat = CPLTestBool(CSLFetchNameValueDef(
4405 67 : papszOpenOptions, "ASSUME_LONGLAT",
4406 : CPLGetConfigOption("GDAL_NETCDF_ASSUME_LONGLAT", "NO")));
4407 :
4408 2 : if (bAssumedLongLat && tmpGT[0] >= -180 && tmpGT[0] < 360 &&
4409 2 : (tmpGT[0] + tmpGT[1] * poDS->GetRasterXSize()) <= 360 &&
4410 71 : tmpGT[3] <= 90 && tmpGT[3] > -90 &&
4411 2 : (tmpGT[3] + tmpGT[5] * poDS->GetRasterYSize()) >= -90)
4412 : {
4413 :
4414 2 : poDS->bIsGeographic = true;
4415 : // seems odd to use 4326 so OGC:CRS84
4416 2 : oSRS.SetFromUserInput("OGC:CRS84");
4417 2 : if (returnProjStr != nullptr)
4418 : {
4419 0 : *returnProjStr = oSRS.exportToWkt();
4420 : }
4421 : else
4422 : {
4423 2 : m_bAddedProjectionVarsDefs = true;
4424 2 : m_bAddedProjectionVarsData = true;
4425 2 : SetSpatialRefNoUpdate(&oSRS);
4426 : }
4427 :
4428 2 : CPLDebug("netCDF",
4429 : "Assumed Longitude Latitude CRS 'OGC:CRS84' because "
4430 : "none otherwise available and geotransform within "
4431 : "suitable bounds. "
4432 : "Set GDAL_NETCDF_ASSUME_LONGLAT=NO as configuration "
4433 : "option or "
4434 : " ASSUME_LONGLAT=NO as open option to bypass this "
4435 : "assumption.");
4436 : }
4437 : }
4438 : }
4439 :
4440 : // Search for Well-known GeogCS if got only CF WKT
4441 : // Disabled for now, as a named datum also include control points
4442 : // (see mailing list and bug#4281
4443 : // For example, WGS84 vs. GDA94 (EPSG:3577) - AEA in netcdf_cf.py
4444 :
4445 : // Disabled for now, but could be set in a config option.
4446 : #if 0
4447 : bool bLookForWellKnownGCS = false; // This could be a Config Option.
4448 :
4449 : if( bLookForWellKnownGCS && bGotCfSRS && !bGotGdalSRS )
4450 : {
4451 : // ET - Could use a more exhaustive method by scanning all EPSG codes in
4452 : // data/gcs.csv as proposed by Even in the gdal-dev mailing list "help
4453 : // for comparing two WKT".
4454 : // This code could be contributed to a new function.
4455 : // OGRSpatialReference * OGRSpatialReference::FindMatchingGeogCS(
4456 : // const OGRSpatialReference *poOther) */
4457 : CPLDebug("GDAL_netCDF", "Searching for Well-known GeogCS");
4458 : const char *pszWKGCSList[] = { "WGS84", "WGS72", "NAD27", "NAD83" };
4459 : char *pszWKGCS = NULL;
4460 : oSRS.exportToPrettyWkt(&pszWKGCS);
4461 : for( size_t i = 0; i < sizeof(pszWKGCSList) / 8; i++ )
4462 : {
4463 : pszWKGCS = CPLStrdup(pszWKGCSList[i]);
4464 : OGRSpatialReference oSRSTmp;
4465 : oSRSTmp.SetWellKnownGeogCS(pszWKGCSList[i]);
4466 : // Set datum to unknown, bug #4281.
4467 : if( oSRSTmp.GetAttrNode("DATUM" ) )
4468 : oSRSTmp.GetAttrNode("DATUM")->GetChild(0)->SetValue("unknown");
4469 : // Could use OGRSpatialReference::StripCTParms(), but let's keep
4470 : // TOWGS84.
4471 : oSRSTmp.GetRoot()->StripNodes("AXIS");
4472 : oSRSTmp.GetRoot()->StripNodes("AUTHORITY");
4473 : oSRSTmp.GetRoot()->StripNodes("EXTENSION");
4474 :
4475 : oSRSTmp.exportToPrettyWkt(&pszWKGCS);
4476 : if( oSRS.IsSameGeogCS(&oSRSTmp) )
4477 : {
4478 : oSRS.SetWellKnownGeogCS(pszWKGCSList[i]);
4479 : oSRS.exportToWkt(&(pszTempProjection));
4480 : SetProjection(pszTempProjection);
4481 : CPLFree(pszTempProjection);
4482 : }
4483 : }
4484 : }
4485 : #endif
4486 : }
4487 :
4488 149 : void netCDFDataset::SetProjectionFromVar(int nGroupId, int nVarId,
4489 : bool bReadSRSOnly)
4490 : {
4491 149 : SetProjectionFromVar(nGroupId, nVarId, bReadSRSOnly, nullptr, nullptr,
4492 : nullptr, nullptr);
4493 149 : }
4494 :
4495 296 : bool netCDFDataset::ProcessNASAL2OceanGeoLocation(int nGroupId, int nVarId)
4496 : {
4497 : // Cf https://oceancolor.gsfc.nasa.gov/docs/format/l2nc/
4498 : // and https://github.com/OSGeo/gdal/issues/7605
4499 :
4500 : // Check for a structure like:
4501 : /* netcdf SNPP_VIIRS.20230406T024200.L2.OC.NRT {
4502 : dimensions:
4503 : number_of_lines = 3248 ;
4504 : pixels_per_line = 3200 ;
4505 : [...]
4506 : pixel_control_points = 3200 ;
4507 : [...]
4508 : group: geophysical_data {
4509 : variables:
4510 : short aot_862(number_of_lines, pixels_per_line) ; <-- nVarId
4511 : [...]
4512 : }
4513 : group: navigation_data {
4514 : variables:
4515 : float longitude(number_of_lines, pixel_control_points) ;
4516 : [...]
4517 : float latitude(number_of_lines, pixel_control_points) ;
4518 : [...]
4519 : }
4520 : }
4521 : */
4522 : // Note that the longitude and latitude arrays are not indexed by the
4523 : // same dimensions. Handle only the case where
4524 : // pixel_control_points == pixels_per_line
4525 : // If there was a subsampling of the geolocation arrays, we'd need to
4526 : // add more logic.
4527 :
4528 592 : std::string osGroupName;
4529 296 : osGroupName.resize(NC_MAX_NAME);
4530 296 : NCDF_ERR(nc_inq_grpname(nGroupId, &osGroupName[0]));
4531 296 : osGroupName.resize(strlen(osGroupName.data()));
4532 296 : if (osGroupName != "geophysical_data")
4533 295 : return false;
4534 :
4535 1 : int nVarDims = 0;
4536 1 : NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
4537 1 : if (nVarDims != 2)
4538 0 : return false;
4539 :
4540 1 : int nNavigationDataGrpId = 0;
4541 1 : if (nc_inq_grp_ncid(cdfid, "navigation_data", &nNavigationDataGrpId) !=
4542 : NC_NOERR)
4543 0 : return false;
4544 :
4545 : std::array<int, 2> anVarDimIds;
4546 1 : NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
4547 :
4548 1 : int nLongitudeId = 0;
4549 1 : int nLatitudeId = 0;
4550 1 : if (nc_inq_varid(nNavigationDataGrpId, "longitude", &nLongitudeId) !=
4551 2 : NC_NOERR ||
4552 1 : nc_inq_varid(nNavigationDataGrpId, "latitude", &nLatitudeId) !=
4553 : NC_NOERR)
4554 : {
4555 0 : return false;
4556 : }
4557 :
4558 1 : int nDimsLongitude = 0;
4559 1 : NCDF_ERR(
4560 : nc_inq_varndims(nNavigationDataGrpId, nLongitudeId, &nDimsLongitude));
4561 1 : int nDimsLatitude = 0;
4562 1 : NCDF_ERR(
4563 : nc_inq_varndims(nNavigationDataGrpId, nLatitudeId, &nDimsLatitude));
4564 1 : if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
4565 : {
4566 0 : return false;
4567 : }
4568 :
4569 : std::array<int, 2> anDimLongitudeIds;
4570 1 : NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLongitudeId,
4571 : anDimLongitudeIds.data()));
4572 : std::array<int, 2> anDimLatitudeIds;
4573 1 : NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLatitudeId,
4574 : anDimLatitudeIds.data()));
4575 1 : if (anDimLongitudeIds != anDimLatitudeIds)
4576 : {
4577 0 : return false;
4578 : }
4579 :
4580 : std::array<size_t, 2> anSizeVarDimIds;
4581 : std::array<size_t, 2> anSizeLongLatIds;
4582 2 : if (!(nc_inq_dimlen(cdfid, anVarDimIds[0], &anSizeVarDimIds[0]) ==
4583 1 : NC_NOERR &&
4584 1 : nc_inq_dimlen(cdfid, anVarDimIds[1], &anSizeVarDimIds[1]) ==
4585 1 : NC_NOERR &&
4586 1 : nc_inq_dimlen(cdfid, anDimLongitudeIds[0], &anSizeLongLatIds[0]) ==
4587 1 : NC_NOERR &&
4588 1 : nc_inq_dimlen(cdfid, anDimLongitudeIds[1], &anSizeLongLatIds[1]) ==
4589 : NC_NOERR &&
4590 1 : anSizeVarDimIds == anSizeLongLatIds))
4591 : {
4592 0 : return false;
4593 : }
4594 :
4595 1 : const char *pszGeolocXFullName = "/navigation_data/longitude";
4596 1 : const char *pszGeolocYFullName = "/navigation_data/latitude";
4597 :
4598 1 : if (bSwitchedXY)
4599 : {
4600 0 : std::swap(pszGeolocXFullName, pszGeolocYFullName);
4601 0 : GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", "GEOLOCATION");
4602 : }
4603 :
4604 1 : CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
4605 : pszGeolocXFullName, pszGeolocYFullName);
4606 :
4607 1 : GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
4608 : "GEOLOCATION");
4609 :
4610 1 : CPLString osTMP;
4611 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
4612 :
4613 1 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
4614 1 : GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
4615 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
4616 :
4617 1 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
4618 1 : GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
4619 :
4620 1 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
4621 1 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
4622 :
4623 1 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
4624 1 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
4625 :
4626 1 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
4627 : "GEOLOCATION");
4628 1 : return true;
4629 : }
4630 :
4631 295 : bool netCDFDataset::ProcessNASAEMITGeoLocation(int nGroupId, int nVarId)
4632 : {
4633 : // Cf https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/
4634 :
4635 : // Check for a structure like:
4636 : /* netcdf EMIT_L2A_RFL_001_20220903T163129_2224611_012 {
4637 : dimensions:
4638 : downtrack = 1280 ;
4639 : crosstrack = 1242 ;
4640 : bands = 285 ;
4641 : [...]
4642 :
4643 : variables:
4644 : float reflectance(downtrack, crosstrack, bands) ;
4645 :
4646 : group: location {
4647 : variables:
4648 : double lon(downtrack, crosstrack) ;
4649 : lon:_FillValue = -9999. ;
4650 : lon:long_name = "Longitude (WGS-84)" ;
4651 : lon:units = "degrees east" ;
4652 : double lat(downtrack, crosstrack) ;
4653 : lat:_FillValue = -9999. ;
4654 : lat:long_name = "Latitude (WGS-84)" ;
4655 : lat:units = "degrees north" ;
4656 : } // group location
4657 :
4658 : }
4659 : or
4660 : netcdf EMIT_L2B_MIN_001_20231024T055538_2329704_040 {
4661 : dimensions:
4662 : downtrack = 1664 ;
4663 : crosstrack = 1242 ;
4664 : [...]
4665 : variables:
4666 : float group_1_band_depth(downtrack, crosstrack) ;
4667 : group_1_band_depth:_FillValue = -9999.f ;
4668 : group_1_band_depth:long_name = "Group 1 Band Depth" ;
4669 : group_1_band_depth:units = "unitless" ;
4670 : [...]
4671 : group: location {
4672 : variables:
4673 : double lon(downtrack, crosstrack) ;
4674 : lon:_FillValue = -9999. ;
4675 : lon:long_name = "Longitude (WGS-84)" ;
4676 : lon:units = "degrees east" ;
4677 : double lat(downtrack, crosstrack) ;
4678 : lat:_FillValue = -9999. ;
4679 : lat:long_name = "Latitude (WGS-84)" ;
4680 : lat:units = "degrees north" ;
4681 : }
4682 : */
4683 :
4684 295 : int nVarDims = 0;
4685 295 : NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
4686 295 : if (nVarDims != 2 && nVarDims != 3)
4687 14 : return false;
4688 :
4689 281 : int nLocationGrpId = 0;
4690 281 : if (nc_inq_grp_ncid(cdfid, "location", &nLocationGrpId) != NC_NOERR)
4691 60 : return false;
4692 :
4693 : std::array<int, 3> anVarDimIds;
4694 221 : NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
4695 221 : if (nYDimID != anVarDimIds[0] || nXDimID != anVarDimIds[1])
4696 21 : return false;
4697 :
4698 200 : int nLongitudeId = 0;
4699 200 : int nLatitudeId = 0;
4700 238 : if (nc_inq_varid(nLocationGrpId, "lon", &nLongitudeId) != NC_NOERR ||
4701 38 : nc_inq_varid(nLocationGrpId, "lat", &nLatitudeId) != NC_NOERR)
4702 : {
4703 162 : return false;
4704 : }
4705 :
4706 38 : int nDimsLongitude = 0;
4707 38 : NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLongitudeId, &nDimsLongitude));
4708 38 : int nDimsLatitude = 0;
4709 38 : NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLatitudeId, &nDimsLatitude));
4710 38 : if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
4711 : {
4712 34 : return false;
4713 : }
4714 :
4715 : std::array<int, 2> anDimLongitudeIds;
4716 4 : NCDF_ERR(nc_inq_vardimid(nLocationGrpId, nLongitudeId,
4717 : anDimLongitudeIds.data()));
4718 : std::array<int, 2> anDimLatitudeIds;
4719 4 : NCDF_ERR(
4720 : nc_inq_vardimid(nLocationGrpId, nLatitudeId, anDimLatitudeIds.data()));
4721 4 : if (anDimLongitudeIds != anDimLatitudeIds)
4722 : {
4723 0 : return false;
4724 : }
4725 :
4726 8 : if (anDimLongitudeIds[0] != anVarDimIds[0] ||
4727 4 : anDimLongitudeIds[1] != anVarDimIds[1])
4728 : {
4729 0 : return false;
4730 : }
4731 :
4732 4 : const char *pszGeolocXFullName = "/location/lon";
4733 4 : const char *pszGeolocYFullName = "/location/lat";
4734 :
4735 4 : CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
4736 : pszGeolocXFullName, pszGeolocYFullName);
4737 :
4738 4 : GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
4739 : "GEOLOCATION");
4740 :
4741 4 : CPLString osTMP;
4742 4 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
4743 :
4744 4 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
4745 4 : GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
4746 4 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
4747 :
4748 4 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
4749 4 : GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
4750 :
4751 4 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
4752 4 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
4753 :
4754 4 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
4755 4 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
4756 :
4757 4 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
4758 : "GEOLOCATION");
4759 4 : return true;
4760 : }
4761 :
4762 368 : int netCDFDataset::ProcessCFGeolocation(int nGroupId, int nVarId,
4763 : const std::string &osGeolocWKT,
4764 : std::string &osGeolocXNameOut,
4765 : std::string &osGeolocYNameOut)
4766 : {
4767 368 : bool bAddGeoloc = false;
4768 368 : char *pszCoordinates = nullptr;
4769 :
4770 : // If there is no explicit "coordinates" attribute, check if there are
4771 : // "lon" and "lat" 2D variables whose dimensions are the last
4772 : // 2 ones of the variable of interest.
4773 368 : if (NCDFGetAttr(nGroupId, nVarId, "coordinates", &pszCoordinates) !=
4774 : CE_None)
4775 : {
4776 317 : CPLFree(pszCoordinates);
4777 317 : pszCoordinates = nullptr;
4778 :
4779 317 : int nVarDims = 0;
4780 317 : NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
4781 317 : if (nVarDims >= 2)
4782 : {
4783 634 : std::vector<int> anVarDimIds(nVarDims);
4784 317 : NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
4785 :
4786 317 : int nLongitudeId = 0;
4787 317 : int nLatitudeId = 0;
4788 389 : if (nc_inq_varid(nGroupId, "lon", &nLongitudeId) == NC_NOERR &&
4789 72 : nc_inq_varid(nGroupId, "lat", &nLatitudeId) == NC_NOERR)
4790 : {
4791 72 : int nDimsLongitude = 0;
4792 72 : NCDF_ERR(
4793 : nc_inq_varndims(nGroupId, nLongitudeId, &nDimsLongitude));
4794 72 : int nDimsLatitude = 0;
4795 72 : NCDF_ERR(
4796 : nc_inq_varndims(nGroupId, nLatitudeId, &nDimsLatitude));
4797 72 : if (nDimsLongitude == 2 && nDimsLatitude == 2)
4798 : {
4799 42 : std::vector<int> anDimLongitudeIds(2);
4800 21 : NCDF_ERR(nc_inq_vardimid(nGroupId, nLongitudeId,
4801 : anDimLongitudeIds.data()));
4802 42 : std::vector<int> anDimLatitudeIds(2);
4803 21 : NCDF_ERR(nc_inq_vardimid(nGroupId, nLatitudeId,
4804 : anDimLatitudeIds.data()));
4805 21 : if (anDimLongitudeIds == anDimLatitudeIds &&
4806 42 : anVarDimIds[anVarDimIds.size() - 2] ==
4807 63 : anDimLongitudeIds[0] &&
4808 42 : anVarDimIds[anVarDimIds.size() - 1] ==
4809 21 : anDimLongitudeIds[1])
4810 : {
4811 21 : pszCoordinates = CPLStrdup("lon lat");
4812 : }
4813 : }
4814 : }
4815 : }
4816 : }
4817 :
4818 368 : if (pszCoordinates)
4819 : {
4820 : // Get X and Y geolocation names from coordinates attribute.
4821 : const CPLStringList aosCoordinates(
4822 144 : NCDFTokenizeCoordinatesAttribute(pszCoordinates));
4823 72 : if (aosCoordinates.size() >= 2)
4824 : {
4825 : char szGeolocXName[NC_MAX_NAME + 1];
4826 : char szGeolocYName[NC_MAX_NAME + 1];
4827 69 : szGeolocXName[0] = '\0';
4828 69 : szGeolocYName[0] = '\0';
4829 :
4830 : // Test that each variable is longitude/latitude.
4831 220 : for (int i = 0; i < aosCoordinates.size(); i++)
4832 : {
4833 151 : if (NCDFIsVarLongitude(nGroupId, -1, aosCoordinates[i]))
4834 : {
4835 58 : int nOtherGroupId = -1;
4836 58 : int nOtherVarId = -1;
4837 : // Check that the variable actually exists
4838 : // Needed on Sentinel-3 products
4839 58 : if (NCDFResolveVar(nGroupId, aosCoordinates[i],
4840 58 : &nOtherGroupId, &nOtherVarId) == CE_None)
4841 : {
4842 56 : snprintf(szGeolocXName, sizeof(szGeolocXName), "%s",
4843 : aosCoordinates[i]);
4844 : }
4845 : }
4846 93 : else if (NCDFIsVarLatitude(nGroupId, -1, aosCoordinates[i]))
4847 : {
4848 58 : int nOtherGroupId = -1;
4849 58 : int nOtherVarId = -1;
4850 : // Check that the variable actually exists
4851 : // Needed on Sentinel-3 products
4852 58 : if (NCDFResolveVar(nGroupId, aosCoordinates[i],
4853 58 : &nOtherGroupId, &nOtherVarId) == CE_None)
4854 : {
4855 56 : snprintf(szGeolocYName, sizeof(szGeolocYName), "%s",
4856 : aosCoordinates[i]);
4857 : }
4858 : }
4859 : }
4860 : // Add GEOLOCATION metadata.
4861 69 : if (!EQUAL(szGeolocXName, "") && !EQUAL(szGeolocYName, ""))
4862 : {
4863 56 : osGeolocXNameOut = szGeolocXName;
4864 56 : osGeolocYNameOut = szGeolocYName;
4865 :
4866 112 : std::string osGeolocXFullName;
4867 112 : std::string osGeolocYFullName;
4868 56 : if (NCDFResolveVarFullName(nGroupId, szGeolocXName,
4869 112 : osGeolocXFullName) == CE_None &&
4870 56 : NCDFResolveVarFullName(nGroupId, szGeolocYName,
4871 : osGeolocYFullName) == CE_None)
4872 : {
4873 56 : if (bSwitchedXY)
4874 : {
4875 2 : std::swap(osGeolocXFullName, osGeolocYFullName);
4876 2 : GDALPamDataset::SetMetadataItem("SWAP_XY", "YES",
4877 : "GEOLOCATION");
4878 : }
4879 :
4880 56 : bAddGeoloc = true;
4881 56 : CPLDebug("GDAL_netCDF",
4882 : "using variables %s and %s for GEOLOCATION",
4883 : osGeolocXFullName.c_str(),
4884 : osGeolocYFullName.c_str());
4885 :
4886 56 : GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
4887 : "GEOLOCATION");
4888 :
4889 112 : CPLString osTMP;
4890 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4891 56 : osGeolocXFullName.c_str());
4892 :
4893 56 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP,
4894 : "GEOLOCATION");
4895 56 : GDALPamDataset::SetMetadataItem("X_BAND", "1",
4896 : "GEOLOCATION");
4897 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4898 56 : osGeolocYFullName.c_str());
4899 :
4900 56 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP,
4901 : "GEOLOCATION");
4902 56 : GDALPamDataset::SetMetadataItem("Y_BAND", "1",
4903 : "GEOLOCATION");
4904 :
4905 56 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0",
4906 : "GEOLOCATION");
4907 56 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1",
4908 : "GEOLOCATION");
4909 :
4910 56 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0",
4911 : "GEOLOCATION");
4912 56 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1",
4913 : "GEOLOCATION");
4914 :
4915 56 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
4916 : "PIXEL_CENTER",
4917 : "GEOLOCATION");
4918 : }
4919 : else
4920 : {
4921 0 : CPLDebug("GDAL_netCDF",
4922 : "cannot resolve location of "
4923 : "lat/lon variables specified by the coordinates "
4924 : "attribute [%s]",
4925 : pszCoordinates);
4926 56 : }
4927 : }
4928 : else
4929 : {
4930 13 : CPLDebug("GDAL_netCDF",
4931 : "coordinates attribute [%s] is unsupported",
4932 : pszCoordinates);
4933 : }
4934 : }
4935 : else
4936 : {
4937 3 : CPLDebug("GDAL_netCDF",
4938 : "coordinates attribute [%s] with %d element(s) is "
4939 : "unsupported",
4940 : pszCoordinates, aosCoordinates.size());
4941 : }
4942 : }
4943 :
4944 : else
4945 : {
4946 296 : bAddGeoloc = ProcessNASAL2OceanGeoLocation(nGroupId, nVarId);
4947 :
4948 296 : if (!bAddGeoloc)
4949 295 : bAddGeoloc = ProcessNASAEMITGeoLocation(nGroupId, nVarId);
4950 : }
4951 :
4952 368 : CPLFree(pszCoordinates);
4953 :
4954 368 : return bAddGeoloc;
4955 : }
4956 :
4957 8 : CPLErr netCDFDataset::Set1DGeolocation(int nGroupId, int nVarId,
4958 : const char *szDimName)
4959 : {
4960 : // Get values.
4961 8 : char *pszVarValues = nullptr;
4962 8 : CPLErr eErr = NCDFGet1DVar(nGroupId, nVarId, &pszVarValues);
4963 8 : if (eErr != CE_None)
4964 0 : return eErr;
4965 :
4966 : // Write metadata.
4967 8 : char szTemp[NC_MAX_NAME + 1 + 32] = {};
4968 8 : snprintf(szTemp, sizeof(szTemp), "%s_VALUES", szDimName);
4969 8 : GDALPamDataset::SetMetadataItem(szTemp, pszVarValues, "GEOLOCATION2");
4970 :
4971 8 : CPLFree(pszVarValues);
4972 :
4973 8 : return CE_None;
4974 : }
4975 :
4976 0 : double *netCDFDataset::Get1DGeolocation(CPL_UNUSED const char *szDimName,
4977 : int &nVarLen)
4978 : {
4979 0 : nVarLen = 0;
4980 :
4981 : // Get Y_VALUES as tokens.
4982 : const CPLStringList aosValues(
4983 0 : NCDFTokenizeArray(GetMetadataItem("Y_VALUES", "GEOLOCATION2")));
4984 0 : if (aosValues.empty())
4985 0 : return nullptr;
4986 :
4987 : // Initialize and fill array.
4988 0 : nVarLen = aosValues.size();
4989 : double *pdfVarValues =
4990 0 : static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
4991 :
4992 0 : for (int i = 0, j = 0; i < nVarLen; i++)
4993 : {
4994 0 : if (!bBottomUp)
4995 0 : j = nVarLen - 1 - i;
4996 : else
4997 0 : j = i; // Invert latitude values.
4998 0 : char *pszTemp = nullptr;
4999 0 : pdfVarValues[j] = CPLStrtod(aosValues[i], &pszTemp);
5000 : }
5001 :
5002 0 : return pdfVarValues;
5003 : }
5004 :
5005 : /************************************************************************/
5006 : /* SetSpatialRefNoUpdate() */
5007 : /************************************************************************/
5008 :
5009 281 : void netCDFDataset::SetSpatialRefNoUpdate(const OGRSpatialReference *poSRS)
5010 : {
5011 281 : m_oSRS.Clear();
5012 281 : if (poSRS)
5013 274 : m_oSRS = *poSRS;
5014 281 : m_bHasProjection = true;
5015 281 : }
5016 :
5017 : /************************************************************************/
5018 : /* SetSpatialRef() */
5019 : /************************************************************************/
5020 :
5021 82 : CPLErr netCDFDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
5022 : {
5023 164 : CPLMutexHolderD(&hNCMutex);
5024 :
5025 82 : if (GetAccess() != GA_Update || m_bHasProjection)
5026 : {
5027 0 : CPLError(CE_Failure, CPLE_AppDefined,
5028 : "netCDFDataset::_SetProjection() should only be called once "
5029 : "in update mode!");
5030 0 : return CE_Failure;
5031 : }
5032 :
5033 82 : if (m_bHasGeoTransform)
5034 : {
5035 32 : SetSpatialRefNoUpdate(poSRS);
5036 :
5037 : // For NC4/NC4C, writing both projection variables and data,
5038 : // followed by redefining nodata value, cancels the projection
5039 : // info from the Band variable, so for now only write the
5040 : // variable definitions, and write data at the end.
5041 : // See https://trac.osgeo.org/gdal/ticket/7245
5042 32 : return AddProjectionVars(true, nullptr, nullptr);
5043 : }
5044 :
5045 50 : SetSpatialRefNoUpdate(poSRS);
5046 :
5047 50 : return CE_None;
5048 : }
5049 :
5050 : /************************************************************************/
5051 : /* SetGeoTransformNoUpdate() */
5052 : /************************************************************************/
5053 :
5054 295 : void netCDFDataset::SetGeoTransformNoUpdate(const GDALGeoTransform >)
5055 : {
5056 295 : m_gt = gt;
5057 295 : m_bHasGeoTransform = true;
5058 295 : }
5059 :
5060 : /************************************************************************/
5061 : /* SetGeoTransform() */
5062 : /************************************************************************/
5063 :
5064 83 : CPLErr netCDFDataset::SetGeoTransform(const GDALGeoTransform >)
5065 : {
5066 166 : CPLMutexHolderD(&hNCMutex);
5067 :
5068 83 : if (GetAccess() != GA_Update || m_bHasGeoTransform)
5069 : {
5070 0 : CPLError(CE_Failure, CPLE_AppDefined,
5071 : "netCDFDataset::SetGeoTransform() should only be called once "
5072 : "in update mode!");
5073 0 : return CE_Failure;
5074 : }
5075 :
5076 83 : CPLDebug("GDAL_netCDF", "SetGeoTransform(%f,%f,%f,%f,%f,%f)", gt.xorig,
5077 83 : gt.xscale, gt.xrot, gt.yorig, gt.yrot, gt.yscale);
5078 :
5079 83 : SetGeoTransformNoUpdate(gt);
5080 :
5081 83 : if (m_bHasProjection)
5082 : {
5083 :
5084 : // For NC4/NC4C, writing both projection variables and data,
5085 : // followed by redefining nodata value, cancels the projection
5086 : // info from the Band variable, so for now only write the
5087 : // variable definitions, and write data at the end.
5088 : // See https://trac.osgeo.org/gdal/ticket/7245
5089 3 : return AddProjectionVars(true, nullptr, nullptr);
5090 : }
5091 :
5092 80 : return CE_None;
5093 : }
5094 :
5095 : /************************************************************************/
5096 : /* NCDFWriteSRSVariable() */
5097 : /************************************************************************/
5098 :
5099 136 : int NCDFWriteSRSVariable(int cdfid, const OGRSpatialReference *poSRS,
5100 : char **ppszCFProjection, bool bWriteGDALTags,
5101 : const std::string &srsVarName)
5102 : {
5103 136 : char *pszCFProjection = nullptr;
5104 136 : char **papszKeyValues = nullptr;
5105 136 : poSRS->exportToCF1(&pszCFProjection, &papszKeyValues, nullptr, nullptr);
5106 :
5107 136 : if (bWriteGDALTags)
5108 : {
5109 135 : const char *pszWKT = CSLFetchNameValue(papszKeyValues, NCDF_CRS_WKT);
5110 135 : if (pszWKT)
5111 : {
5112 : // SPATIAL_REF is deprecated. Will be removed in GDAL 4.
5113 135 : papszKeyValues =
5114 135 : CSLSetNameValue(papszKeyValues, NCDF_SPATIAL_REF, pszWKT);
5115 : }
5116 : }
5117 :
5118 136 : const int nValues = CSLCount(papszKeyValues);
5119 :
5120 : int NCDFVarID;
5121 272 : std::string varNameRadix(pszCFProjection);
5122 136 : int nCounter = 2;
5123 : while (true)
5124 : {
5125 138 : NCDFVarID = -1;
5126 138 : nc_inq_varid(cdfid, pszCFProjection, &NCDFVarID);
5127 138 : if (NCDFVarID < 0)
5128 133 : break;
5129 :
5130 5 : int nbAttr = 0;
5131 5 : NCDF_ERR(nc_inq_varnatts(cdfid, NCDFVarID, &nbAttr));
5132 5 : bool bSame = nbAttr == nValues;
5133 41 : for (int i = 0; bSame && (i < nbAttr); i++)
5134 : {
5135 : char szAttrName[NC_MAX_NAME + 1];
5136 38 : szAttrName[0] = 0;
5137 38 : NCDF_ERR(nc_inq_attname(cdfid, NCDFVarID, i, szAttrName));
5138 :
5139 : const char *pszValue =
5140 38 : CSLFetchNameValue(papszKeyValues, szAttrName);
5141 38 : if (!pszValue)
5142 : {
5143 0 : bSame = false;
5144 2 : break;
5145 : }
5146 :
5147 38 : nc_type atttype = NC_NAT;
5148 38 : size_t attlen = 0;
5149 38 : NCDF_ERR(
5150 : nc_inq_att(cdfid, NCDFVarID, szAttrName, &atttype, &attlen));
5151 38 : if (atttype != NC_CHAR && atttype != NC_DOUBLE)
5152 : {
5153 0 : bSame = false;
5154 0 : break;
5155 : }
5156 38 : if (atttype == NC_CHAR)
5157 : {
5158 15 : if (CPLGetValueType(pszValue) != CPL_VALUE_STRING)
5159 : {
5160 0 : bSame = false;
5161 0 : break;
5162 : }
5163 15 : std::string val;
5164 15 : NCDFGetAttr(cdfid, NCDFVarID, szAttrName, val);
5165 15 : if (val != pszValue)
5166 : {
5167 0 : bSame = false;
5168 0 : break;
5169 : }
5170 : }
5171 : else
5172 : {
5173 : const CPLStringList aosTokens(
5174 23 : CSLTokenizeString2(pszValue, ",", 0));
5175 23 : if (static_cast<size_t>(aosTokens.size()) != attlen)
5176 : {
5177 0 : bSame = false;
5178 0 : break;
5179 : }
5180 : double vals[2];
5181 23 : nc_get_att_double(cdfid, NCDFVarID, szAttrName, vals);
5182 44 : if (vals[0] != CPLAtof(aosTokens[0]) ||
5183 21 : (attlen == 2 && vals[1] != CPLAtof(aosTokens[1])))
5184 : {
5185 2 : bSame = false;
5186 2 : break;
5187 : }
5188 : }
5189 : }
5190 5 : if (bSame)
5191 : {
5192 3 : *ppszCFProjection = pszCFProjection;
5193 3 : CSLDestroy(papszKeyValues);
5194 3 : return NCDFVarID;
5195 : }
5196 2 : CPLFree(pszCFProjection);
5197 2 : pszCFProjection =
5198 2 : CPLStrdup(CPLSPrintf("%s_%d", varNameRadix.c_str(), nCounter));
5199 2 : nCounter++;
5200 2 : }
5201 :
5202 133 : *ppszCFProjection = pszCFProjection;
5203 :
5204 : const char *pszVarName;
5205 :
5206 133 : if (srsVarName != "")
5207 : {
5208 38 : pszVarName = srsVarName.c_str();
5209 : }
5210 : else
5211 : {
5212 95 : pszVarName = pszCFProjection;
5213 : }
5214 :
5215 133 : int status = nc_def_var(cdfid, pszVarName, NC_CHAR, 0, nullptr, &NCDFVarID);
5216 133 : NCDF_ERR(status);
5217 1303 : for (int i = 0; i < nValues; ++i)
5218 : {
5219 1170 : char *pszKey = nullptr;
5220 1170 : const char *pszValue = CPLParseNameValue(papszKeyValues[i], &pszKey);
5221 1170 : if (pszKey && pszValue)
5222 : {
5223 2340 : const CPLStringList aosTokens(CSLTokenizeString2(pszValue, ",", 0));
5224 1170 : double adfValues[2] = {0, 0};
5225 1170 : const int nDoubleCount = std::min(2, aosTokens.size());
5226 1170 : if (!(aosTokens.size() == 2 &&
5227 2339 : CPLGetValueType(aosTokens[0]) != CPL_VALUE_STRING) &&
5228 1169 : CPLGetValueType(pszValue) == CPL_VALUE_STRING)
5229 : {
5230 531 : status = nc_put_att_text(cdfid, NCDFVarID, pszKey,
5231 : strlen(pszValue), pszValue);
5232 : }
5233 : else
5234 : {
5235 1279 : for (int j = 0; j < nDoubleCount; ++j)
5236 640 : adfValues[j] = CPLAtof(aosTokens[j]);
5237 639 : status = nc_put_att_double(cdfid, NCDFVarID, pszKey, NC_DOUBLE,
5238 : nDoubleCount, adfValues);
5239 : }
5240 1170 : NCDF_ERR(status);
5241 : }
5242 1170 : CPLFree(pszKey);
5243 : }
5244 :
5245 133 : CSLDestroy(papszKeyValues);
5246 133 : return NCDFVarID;
5247 : }
5248 :
5249 : /************************************************************************/
5250 : /* NCDFWriteLonLatVarsAttributes() */
5251 : /************************************************************************/
5252 :
5253 103 : void NCDFWriteLonLatVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarLonID,
5254 : int nVarLatID)
5255 : {
5256 :
5257 : try
5258 : {
5259 103 : vcdf.nc_put_vatt_text(nVarLatID, CF_STD_NAME, CF_LATITUDE_STD_NAME);
5260 103 : vcdf.nc_put_vatt_text(nVarLatID, CF_LNG_NAME, CF_LATITUDE_LNG_NAME);
5261 103 : vcdf.nc_put_vatt_text(nVarLatID, CF_UNITS, CF_DEGREES_NORTH);
5262 103 : vcdf.nc_put_vatt_text(nVarLonID, CF_STD_NAME, CF_LONGITUDE_STD_NAME);
5263 103 : vcdf.nc_put_vatt_text(nVarLonID, CF_LNG_NAME, CF_LONGITUDE_LNG_NAME);
5264 103 : vcdf.nc_put_vatt_text(nVarLonID, CF_UNITS, CF_DEGREES_EAST);
5265 : }
5266 0 : catch (nccfdriver::SG_Exception &e)
5267 : {
5268 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5269 : }
5270 103 : }
5271 :
5272 : /************************************************************************/
5273 : /* NCDFWriteRLonRLatVarsAttributes() */
5274 : /************************************************************************/
5275 :
5276 0 : void NCDFWriteRLonRLatVarsAttributes(nccfdriver::netCDFVID &vcdf,
5277 : int nVarRLonID, int nVarRLatID)
5278 : {
5279 : try
5280 : {
5281 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_STD_NAME, "grid_latitude");
5282 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_LNG_NAME,
5283 : "latitude in rotated pole grid");
5284 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_UNITS, "degrees");
5285 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_AXIS, "Y");
5286 :
5287 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_STD_NAME, "grid_longitude");
5288 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_LNG_NAME,
5289 : "longitude in rotated pole grid");
5290 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_UNITS, "degrees");
5291 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_AXIS, "X");
5292 : }
5293 0 : catch (nccfdriver::SG_Exception &e)
5294 : {
5295 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5296 : }
5297 0 : }
5298 :
5299 : /************************************************************************/
5300 : /* NCDFGetProjectedCFUnit() */
5301 : /************************************************************************/
5302 :
5303 44 : std::string NCDFGetProjectedCFUnit(const OGRSpatialReference *poSRS)
5304 : {
5305 44 : char *pszUnitsToWrite = nullptr;
5306 44 : poSRS->exportToCF1(nullptr, nullptr, &pszUnitsToWrite, nullptr);
5307 44 : std::string osRet = pszUnitsToWrite ? pszUnitsToWrite : std::string();
5308 44 : CPLFree(pszUnitsToWrite);
5309 88 : return osRet;
5310 : }
5311 :
5312 : /************************************************************************/
5313 : /* NCDFWriteXYVarsAttributes() */
5314 : /************************************************************************/
5315 :
5316 31 : void NCDFWriteXYVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarXID,
5317 : int nVarYID, const OGRSpatialReference *poSRS)
5318 : {
5319 62 : const std::string osUnitsToWrite = NCDFGetProjectedCFUnit(poSRS);
5320 :
5321 : try
5322 : {
5323 31 : vcdf.nc_put_vatt_text(nVarXID, CF_STD_NAME, CF_PROJ_X_COORD);
5324 31 : vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME, CF_PROJ_X_COORD_LONG_NAME);
5325 31 : if (!osUnitsToWrite.empty())
5326 31 : vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, osUnitsToWrite.c_str());
5327 31 : vcdf.nc_put_vatt_text(nVarYID, CF_STD_NAME, CF_PROJ_Y_COORD);
5328 31 : vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME, CF_PROJ_Y_COORD_LONG_NAME);
5329 31 : if (!osUnitsToWrite.empty())
5330 31 : vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, osUnitsToWrite.c_str());
5331 : }
5332 0 : catch (nccfdriver::SG_Exception &e)
5333 : {
5334 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5335 : }
5336 31 : }
5337 :
5338 : /************************************************************************/
5339 : /* AddProjectionVars() */
5340 : /************************************************************************/
5341 :
5342 176 : CPLErr netCDFDataset::AddProjectionVars(bool bDefsOnly,
5343 : GDALProgressFunc pfnProgress,
5344 : void *pProgressData)
5345 : {
5346 176 : if (nCFVersion >= 1.8)
5347 0 : return CE_None; // do nothing
5348 :
5349 176 : bool bWriteGridMapping = false;
5350 176 : bool bWriteLonLat = false;
5351 176 : bool bHasGeoloc = false;
5352 176 : bool bWriteGDALTags = false;
5353 176 : bool bWriteGeoTransform = false;
5354 :
5355 : // For GEOLOCATION information.
5356 176 : GDALDatasetUniquePtr poDS_X;
5357 176 : GDALDatasetUniquePtr poDS_Y;
5358 176 : GDALRasterBand *poBand_X = nullptr;
5359 176 : GDALRasterBand *poBand_Y = nullptr;
5360 :
5361 352 : OGRSpatialReference oSRS(m_oSRS);
5362 176 : if (!m_oSRS.IsEmpty())
5363 : {
5364 150 : if (oSRS.IsProjected())
5365 62 : bIsProjected = true;
5366 88 : else if (oSRS.IsGeographic())
5367 88 : bIsGeographic = true;
5368 : }
5369 :
5370 176 : if (bDefsOnly)
5371 : {
5372 176 : const std::string osProjection = m_oSRS.exportToWkt();
5373 163 : CPLDebug("GDAL_netCDF",
5374 : "SetProjection, WKT now = [%s]\nprojected: %d geographic: %d",
5375 75 : osProjection.empty() ? "(null)" : osProjection.c_str(),
5376 88 : static_cast<int>(bIsProjected),
5377 88 : static_cast<int>(bIsGeographic));
5378 :
5379 88 : if (!m_bHasGeoTransform)
5380 5 : CPLDebug("GDAL_netCDF",
5381 : "netCDFDataset::AddProjectionVars() called, "
5382 : "but GeoTransform has not yet been defined!");
5383 :
5384 88 : if (!m_bHasProjection)
5385 6 : CPLDebug("GDAL_netCDF",
5386 : "netCDFDataset::AddProjectionVars() called, "
5387 : "but Projection has not yet been defined!");
5388 : }
5389 :
5390 : // Check GEOLOCATION information.
5391 : CSLConstList papszGeolocationInfo =
5392 176 : netCDFDataset::GetMetadata("GEOLOCATION");
5393 176 : if (papszGeolocationInfo != nullptr)
5394 : {
5395 : // Look for geolocation datasets.
5396 : const char *pszDSName =
5397 10 : CSLFetchNameValue(papszGeolocationInfo, "X_DATASET");
5398 10 : if (pszDSName != nullptr)
5399 10 : poDS_X.reset(GDALDataset::Open(
5400 : pszDSName,
5401 : GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR | GDAL_OF_SHARED));
5402 10 : pszDSName = CSLFetchNameValue(papszGeolocationInfo, "Y_DATASET");
5403 10 : if (pszDSName != nullptr)
5404 10 : poDS_Y.reset(GDALDataset::Open(
5405 : pszDSName,
5406 : GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR | GDAL_OF_SHARED));
5407 :
5408 10 : if (poDS_X != nullptr && poDS_Y != nullptr)
5409 : {
5410 10 : int nBand = std::max(1, atoi(CSLFetchNameValueDef(
5411 10 : papszGeolocationInfo, "X_BAND", "0")));
5412 10 : poBand_X = poDS_X->GetRasterBand(nBand);
5413 10 : nBand = std::max(1, atoi(CSLFetchNameValueDef(papszGeolocationInfo,
5414 10 : "Y_BAND", "0")));
5415 10 : poBand_Y = poDS_Y->GetRasterBand(nBand);
5416 :
5417 : // If geoloc bands are found, do basic validation based on their
5418 : // dimensions.
5419 10 : if (poBand_X != nullptr && poBand_Y != nullptr)
5420 : {
5421 10 : const int nXSize_XBand = poBand_X->GetXSize();
5422 10 : const int nYSize_XBand = poBand_X->GetYSize();
5423 10 : const int nXSize_YBand = poBand_Y->GetXSize();
5424 10 : const int nYSize_YBand = poBand_Y->GetYSize();
5425 :
5426 : // TODO 1D geolocation arrays not implemented.
5427 10 : if (nYSize_XBand == 1 && nYSize_YBand == 1)
5428 : {
5429 0 : bHasGeoloc = false;
5430 0 : CPLDebug("GDAL_netCDF",
5431 : "1D GEOLOCATION arrays not supported yet");
5432 : }
5433 : // 2D bands must have same sizes as the raster bands.
5434 10 : else if (nXSize_XBand != nRasterXSize ||
5435 10 : nYSize_XBand != nRasterYSize ||
5436 10 : nXSize_YBand != nRasterXSize ||
5437 10 : nYSize_YBand != nRasterYSize)
5438 : {
5439 0 : bHasGeoloc = false;
5440 0 : CPLDebug("GDAL_netCDF",
5441 : "GEOLOCATION array sizes (%dx%d %dx%d) differ "
5442 : "from raster (%dx%d), not supported",
5443 : nXSize_XBand, nYSize_XBand, nXSize_YBand,
5444 : nYSize_YBand, nRasterXSize, nRasterYSize);
5445 : }
5446 : else
5447 : {
5448 10 : bHasGeoloc = true;
5449 10 : CPLDebug("GDAL_netCDF",
5450 : "dataset has GEOLOCATION information, will try to "
5451 : "write it");
5452 : }
5453 : }
5454 : }
5455 : }
5456 :
5457 : // Process projection options.
5458 176 : if (bIsProjected)
5459 : {
5460 : bool bIsCfProjection =
5461 62 : oSRS.exportToCF1(nullptr, nullptr, nullptr, nullptr) == OGRERR_NONE;
5462 62 : bWriteGridMapping = true;
5463 62 : bWriteGDALTags = aosCreationOptions.FetchBool("WRITE_GDAL_TAGS", true);
5464 : // Force WRITE_GDAL_TAGS if is not a CF projection.
5465 62 : if (!bWriteGDALTags && !bIsCfProjection)
5466 0 : bWriteGDALTags = true;
5467 62 : if (bWriteGDALTags)
5468 62 : bWriteGeoTransform = true;
5469 :
5470 : // Write lon/lat: default is NO, except if has geolocation.
5471 : // With IF_NEEDED: write if has geoloc or is not CF projection.
5472 : const char *pszValue =
5473 62 : aosCreationOptions.FetchNameValue("WRITE_LONLAT");
5474 62 : if (pszValue)
5475 : {
5476 6 : if (EQUAL(pszValue, "IF_NEEDED"))
5477 : {
5478 0 : bWriteLonLat = bHasGeoloc || !bIsCfProjection;
5479 : }
5480 : else
5481 : {
5482 6 : bWriteLonLat = CPLTestBool(pszValue);
5483 : }
5484 : }
5485 : else
5486 : {
5487 56 : bWriteLonLat = bHasGeoloc;
5488 : }
5489 :
5490 : // Save value of pszCFCoordinates for later.
5491 62 : if (bWriteLonLat)
5492 : {
5493 8 : pszCFCoordinates = NCDF_LONLAT;
5494 : }
5495 : }
5496 : else
5497 : {
5498 : // Files without a Datum will not have a grid_mapping variable and
5499 : // geographic information.
5500 114 : bWriteGridMapping = bIsGeographic;
5501 :
5502 114 : if (bHasGeoloc)
5503 : {
5504 8 : bWriteLonLat = true;
5505 : }
5506 : else
5507 : {
5508 106 : bWriteGDALTags = aosCreationOptions.FetchBool("WRITE_GDAL_TAGS",
5509 : bWriteGridMapping);
5510 106 : if (bWriteGDALTags)
5511 88 : bWriteGeoTransform = true;
5512 :
5513 : const char *pszValue =
5514 106 : aosCreationOptions.FetchNameValueDef("WRITE_LONLAT", "YES");
5515 106 : if (EQUAL(pszValue, "IF_NEEDED"))
5516 0 : bWriteLonLat = true;
5517 : else
5518 106 : bWriteLonLat = CPLTestBool(pszValue);
5519 : // Don't write lon/lat if no source geotransform.
5520 106 : if (!m_bHasGeoTransform)
5521 0 : bWriteLonLat = false;
5522 : // If we don't write lon/lat, set dimnames to X/Y and write gdal
5523 : // tags.
5524 106 : if (!bWriteLonLat)
5525 : {
5526 0 : CPLError(CE_Warning, CPLE_AppDefined,
5527 : "creating geographic file without lon/lat values!");
5528 0 : if (m_bHasGeoTransform)
5529 : {
5530 0 : bWriteGDALTags = true; // Not desirable if no geotransform.
5531 0 : bWriteGeoTransform = true;
5532 : }
5533 : }
5534 : }
5535 : }
5536 :
5537 : // Make sure we write grid_mapping if we need to write GDAL tags.
5538 176 : if (bWriteGDALTags)
5539 150 : bWriteGridMapping = true;
5540 :
5541 : // bottom-up value: new driver is bottom-up by default.
5542 : // Override with WRITE_BOTTOMUP.
5543 176 : bBottomUp = aosCreationOptions.FetchBool("WRITE_BOTTOMUP", true);
5544 :
5545 176 : if (bDefsOnly)
5546 : {
5547 88 : CPLDebug(
5548 : "GDAL_netCDF",
5549 : "bIsProjected=%d bIsGeographic=%d bWriteGridMapping=%d "
5550 : "bWriteGDALTags=%d bWriteLonLat=%d bBottomUp=%d bHasGeoloc=%d",
5551 88 : static_cast<int>(bIsProjected), static_cast<int>(bIsGeographic),
5552 : static_cast<int>(bWriteGridMapping),
5553 : static_cast<int>(bWriteGDALTags), static_cast<int>(bWriteLonLat),
5554 88 : static_cast<int>(bBottomUp), static_cast<int>(bHasGeoloc));
5555 : }
5556 :
5557 : // Exit if nothing to do.
5558 176 : if (!bIsProjected && !bWriteLonLat)
5559 0 : return CE_None;
5560 :
5561 : // Define dimension names.
5562 :
5563 176 : constexpr const char *ROTATED_POLE_VAR_NAME = "rotated_pole";
5564 :
5565 176 : if (bDefsOnly)
5566 : {
5567 88 : int nVarLonID = -1;
5568 88 : int nVarLatID = -1;
5569 88 : int nVarXID = -1;
5570 88 : int nVarYID = -1;
5571 :
5572 88 : m_bAddedProjectionVarsDefs = true;
5573 :
5574 : // Make sure we are in define mode.
5575 88 : SetDefineMode(true);
5576 :
5577 : // Write projection attributes.
5578 88 : if (bWriteGridMapping)
5579 : {
5580 75 : const int NCDFVarID = NCDFWriteSRSVariable(
5581 : cdfid, &oSRS, &pszCFProjection, bWriteGDALTags);
5582 75 : if (NCDFVarID < 0)
5583 0 : return CE_Failure;
5584 :
5585 : // Optional GDAL custom projection tags.
5586 75 : if (bWriteGDALTags && bWriteGeoTransform && m_bHasGeoTransform)
5587 : {
5588 74 : GDALGeoTransform gt(m_gt);
5589 74 : if (!bBottomUp)
5590 : {
5591 : // Change origin from top to bottom and sign of coefficients
5592 : // indexed by row
5593 2 : gt.yorig += nRasterYSize * gt.yscale;
5594 2 : gt.xorig += nRasterYSize * gt.xrot;
5595 2 : gt.xrot = -gt.xrot;
5596 2 : gt.yscale = -gt.yscale;
5597 : }
5598 148 : std::string osGeoTransform = gt.ToString(" ");
5599 74 : CPLDebug("GDAL_netCDF", "szGeoTransform = %s",
5600 : osGeoTransform.c_str());
5601 :
5602 74 : const int status = nc_put_att_text(
5603 : cdfid, NCDFVarID, NCDF_GEOTRANSFORM, osGeoTransform.size(),
5604 : osGeoTransform.c_str());
5605 74 : NCDF_ERR(status);
5606 : }
5607 :
5608 : // Write projection variable to band variable.
5609 : // Need to call later if there are no bands.
5610 75 : AddGridMappingRef();
5611 : } // end if( bWriteGridMapping )
5612 :
5613 : // Write CF Projection vars.
5614 :
5615 88 : const bool bIsRotatedPole =
5616 163 : pszCFProjection != nullptr &&
5617 75 : EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
5618 :
5619 88 : if (m_bHasGeoTransform && !m_gt.IsAxisAligned())
5620 : {
5621 : // Do not write X/Y coordinate arrays
5622 : }
5623 :
5624 84 : else if (bIsRotatedPole)
5625 : {
5626 : // Rename dims to rlat/rlon.
5627 : papszDimName
5628 0 : .Clear(); // If we add other dims one day, this has to change
5629 0 : papszDimName.AddString(NCDF_DIMNAME_RLAT);
5630 0 : papszDimName.AddString(NCDF_DIMNAME_RLON);
5631 :
5632 0 : int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_RLAT);
5633 0 : NCDF_ERR(status);
5634 0 : status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_RLON);
5635 0 : NCDF_ERR(status);
5636 : }
5637 : // Rename dimensions if lon/lat.
5638 84 : else if (!bIsProjected && !bHasGeoloc)
5639 : {
5640 : // Rename dims to lat/lon.
5641 : papszDimName
5642 53 : .Clear(); // If we add other dims one day, this has to change
5643 53 : papszDimName.AddString(NCDF_DIMNAME_LAT);
5644 53 : papszDimName.AddString(NCDF_DIMNAME_LON);
5645 :
5646 53 : int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_LAT);
5647 53 : NCDF_ERR(status);
5648 53 : status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_LON);
5649 53 : NCDF_ERR(status);
5650 : }
5651 :
5652 : // Write X/Y attributes.
5653 : else /* if( bIsProjected || bHasGeoloc ) */
5654 : {
5655 : // X
5656 : int anXDims[1];
5657 31 : anXDims[0] = nXDimID;
5658 31 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
5659 : CF_PROJ_X_VAR_NAME, NC_DOUBLE);
5660 31 : int status = nc_def_var(cdfid, CF_PROJ_X_VAR_NAME, NC_DOUBLE, 1,
5661 : anXDims, &nVarXID);
5662 31 : NCDF_ERR(status);
5663 :
5664 : // Y
5665 : int anYDims[1];
5666 31 : anYDims[0] = nYDimID;
5667 31 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
5668 : CF_PROJ_Y_VAR_NAME, NC_DOUBLE);
5669 31 : status = nc_def_var(cdfid, CF_PROJ_Y_VAR_NAME, NC_DOUBLE, 1,
5670 : anYDims, &nVarYID);
5671 31 : NCDF_ERR(status);
5672 :
5673 31 : if (bIsProjected)
5674 : {
5675 27 : NCDFWriteXYVarsAttributes(this->vcdf, nVarXID, nVarYID, &oSRS);
5676 : }
5677 : else
5678 : {
5679 4 : CPLAssert(bHasGeoloc);
5680 : try
5681 : {
5682 4 : vcdf.nc_put_vatt_text(nVarXID, CF_AXIS, CF_SG_X_AXIS);
5683 4 : vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME,
5684 : "x-coordinate in Cartesian system");
5685 4 : vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, "m");
5686 4 : vcdf.nc_put_vatt_text(nVarYID, CF_AXIS, CF_SG_Y_AXIS);
5687 4 : vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME,
5688 : "y-coordinate in Cartesian system");
5689 4 : vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, "m");
5690 :
5691 4 : pszCFCoordinates = NCDF_LONLAT;
5692 : }
5693 0 : catch (nccfdriver::SG_Exception &e)
5694 : {
5695 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5696 0 : return CE_Failure;
5697 : }
5698 : }
5699 : }
5700 :
5701 : // Write lat/lon attributes if needed.
5702 88 : if (bWriteLonLat)
5703 : {
5704 61 : int anLatDims[2] = {0, 0};
5705 61 : int anLonDims[2] = {0, 0};
5706 61 : int nLatDims = -1;
5707 61 : int nLonDims = -1;
5708 :
5709 : // Get information.
5710 61 : if (bHasGeoloc)
5711 : {
5712 : // Geoloc
5713 5 : nLatDims = 2;
5714 5 : anLatDims[0] = nYDimID;
5715 5 : anLatDims[1] = nXDimID;
5716 5 : nLonDims = 2;
5717 5 : anLonDims[0] = nYDimID;
5718 5 : anLonDims[1] = nXDimID;
5719 : }
5720 56 : else if (bIsProjected)
5721 : {
5722 : // Projected
5723 3 : nLatDims = 2;
5724 3 : anLatDims[0] = nYDimID;
5725 3 : anLatDims[1] = nXDimID;
5726 3 : nLonDims = 2;
5727 3 : anLonDims[0] = nYDimID;
5728 3 : anLonDims[1] = nXDimID;
5729 : }
5730 : else
5731 : {
5732 : // Geographic
5733 53 : nLatDims = 1;
5734 53 : anLatDims[0] = nYDimID;
5735 53 : nLonDims = 1;
5736 53 : anLonDims[0] = nXDimID;
5737 : }
5738 :
5739 61 : nc_type eLonLatType = NC_NAT;
5740 61 : if (bIsProjected)
5741 : {
5742 4 : eLonLatType = NC_FLOAT;
5743 4 : const char *pszValue = aosCreationOptions.FetchNameValueDef(
5744 : "TYPE_LONLAT", "FLOAT");
5745 4 : if (EQUAL(pszValue, "DOUBLE"))
5746 0 : eLonLatType = NC_DOUBLE;
5747 : }
5748 : else
5749 : {
5750 57 : eLonLatType = NC_DOUBLE;
5751 57 : const char *pszValue = aosCreationOptions.FetchNameValueDef(
5752 : "TYPE_LONLAT", "DOUBLE");
5753 57 : if (EQUAL(pszValue, "FLOAT"))
5754 0 : eLonLatType = NC_FLOAT;
5755 : }
5756 :
5757 : // Def vars and attributes.
5758 : {
5759 61 : const char *pszVarName =
5760 61 : bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME;
5761 61 : int status = nc_def_var(cdfid, pszVarName, eLonLatType,
5762 : nLatDims, anLatDims, &nVarLatID);
5763 61 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
5764 : cdfid, pszVarName, eLonLatType, nLatDims, nVarLatID);
5765 61 : NCDF_ERR(status);
5766 61 : DefVarDeflate(nVarLatID, false); // Don't set chunking.
5767 : }
5768 :
5769 : {
5770 61 : const char *pszVarName =
5771 61 : bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME;
5772 61 : int status = nc_def_var(cdfid, pszVarName, eLonLatType,
5773 : nLonDims, anLonDims, &nVarLonID);
5774 61 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
5775 : cdfid, pszVarName, eLonLatType, nLatDims, nVarLonID);
5776 61 : NCDF_ERR(status);
5777 61 : DefVarDeflate(nVarLonID, false); // Don't set chunking.
5778 : }
5779 :
5780 61 : if (bIsRotatedPole)
5781 0 : NCDFWriteRLonRLatVarsAttributes(this->vcdf, nVarLonID,
5782 : nVarLatID);
5783 : else
5784 61 : NCDFWriteLonLatVarsAttributes(this->vcdf, nVarLonID, nVarLatID);
5785 : }
5786 : }
5787 :
5788 176 : if (!bDefsOnly)
5789 : {
5790 88 : m_bAddedProjectionVarsData = true;
5791 :
5792 88 : int nVarXID = -1;
5793 88 : int nVarYID = -1;
5794 :
5795 88 : nc_inq_varid(cdfid, CF_PROJ_X_VAR_NAME, &nVarXID);
5796 88 : nc_inq_varid(cdfid, CF_PROJ_Y_VAR_NAME, &nVarYID);
5797 :
5798 88 : int nVarLonID = -1;
5799 88 : int nVarLatID = -1;
5800 :
5801 88 : const bool bIsRotatedPole =
5802 163 : pszCFProjection != nullptr &&
5803 75 : EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
5804 88 : nc_inq_varid(cdfid,
5805 : bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME,
5806 : &nVarLonID);
5807 88 : nc_inq_varid(cdfid,
5808 : bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME,
5809 : &nVarLatID);
5810 :
5811 : // Get projection values.
5812 :
5813 88 : if (bIsProjected)
5814 : {
5815 0 : std::unique_ptr<OGRSpatialReference> poLatLonSRS;
5816 0 : std::unique_ptr<OGRCoordinateTransformation> poTransform;
5817 :
5818 : size_t startX[1];
5819 : size_t countX[1];
5820 : size_t startY[1];
5821 : size_t countY[1];
5822 :
5823 31 : CPLDebug("GDAL_netCDF", "Getting (X,Y) values");
5824 :
5825 : std::unique_ptr<double, decltype(&VSIFree)> adXValKeeper(
5826 : static_cast<double *>(
5827 62 : VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
5828 31 : VSIFree);
5829 : std::unique_ptr<double, decltype(&VSIFree)> adYValKeeper(
5830 : static_cast<double *>(
5831 62 : VSI_MALLOC2_VERBOSE(nRasterYSize, sizeof(double))),
5832 31 : VSIFree);
5833 31 : double *padXVal = adXValKeeper.get();
5834 31 : double *padYVal = adYValKeeper.get();
5835 31 : if (!padXVal || !padYVal)
5836 : {
5837 0 : return CE_Failure;
5838 : }
5839 :
5840 : // Make sure we are in data mode.
5841 31 : SetDefineMode(false);
5842 :
5843 31 : int status = NC_NOERR;
5844 :
5845 31 : if (m_gt.IsAxisAligned())
5846 : {
5847 : // Get Y values.
5848 27 : const double dfY0 =
5849 27 : (!bBottomUp) ? m_gt.yorig :
5850 : // Invert latitude values.
5851 27 : m_gt.yorig + (m_gt.yscale * nRasterYSize);
5852 27 : const double dfDY = m_gt.yscale;
5853 :
5854 1478 : for (int j = 0; j < nRasterYSize; j++)
5855 : {
5856 : // The data point is centered inside the pixel.
5857 1451 : if (!bBottomUp)
5858 0 : padYVal[j] = dfY0 + (j + 0.5) * dfDY;
5859 : else // Invert latitude values.
5860 1451 : padYVal[j] = dfY0 - (j + 0.5) * dfDY;
5861 : }
5862 27 : startX[0] = 0;
5863 27 : countX[0] = nRasterXSize;
5864 :
5865 : // Get X values.
5866 27 : const double dfX0 = m_gt.xorig;
5867 27 : const double dfDX = m_gt.xscale;
5868 :
5869 1519 : for (int i = 0; i < nRasterXSize; i++)
5870 : {
5871 : // The data point is centered inside the pixel.
5872 1492 : padXVal[i] = dfX0 + (i + 0.5) * dfDX;
5873 : }
5874 27 : startY[0] = 0;
5875 27 : countY[0] = nRasterYSize;
5876 :
5877 : // Write X/Y values.
5878 :
5879 27 : CPLDebug("GDAL_netCDF", "Writing X values");
5880 : status =
5881 27 : nc_put_vara_double(cdfid, nVarXID, startX, countX, padXVal);
5882 27 : NCDF_ERR(status);
5883 :
5884 27 : CPLDebug("GDAL_netCDF", "Writing Y values");
5885 : status =
5886 27 : nc_put_vara_double(cdfid, nVarYID, startY, countY, padYVal);
5887 27 : NCDF_ERR(status);
5888 : }
5889 :
5890 31 : if (pfnProgress)
5891 27 : pfnProgress(0.20, nullptr, pProgressData);
5892 :
5893 : // Write lon/lat arrays (CF coordinates) if requested.
5894 :
5895 : // Get OGR transform if GEOLOCATION is not available.
5896 31 : if (bWriteLonLat && !bHasGeoloc)
5897 : {
5898 3 : poLatLonSRS.reset(m_oSRS.CloneGeogCS());
5899 3 : if (poLatLonSRS != nullptr)
5900 : {
5901 3 : poLatLonSRS->SetAxisMappingStrategy(
5902 : OAMS_TRADITIONAL_GIS_ORDER);
5903 3 : poTransform.reset(OGRCreateCoordinateTransformation(
5904 3 : &m_oSRS, poLatLonSRS.get()));
5905 : }
5906 : // If no OGR transform, then don't write CF lon/lat.
5907 3 : if (poTransform == nullptr)
5908 : {
5909 0 : CPLError(CE_Failure, CPLE_AppDefined,
5910 : "Unable to get Coordinate Transform");
5911 0 : bWriteLonLat = false;
5912 : }
5913 : }
5914 :
5915 31 : if (bWriteLonLat)
5916 : {
5917 4 : if (!bHasGeoloc)
5918 3 : CPLDebug("GDAL_netCDF", "Transforming (X,Y)->(lon,lat)");
5919 : else
5920 1 : CPLDebug("GDAL_netCDF",
5921 : "Writing (lon,lat) from GEOLOCATION arrays");
5922 :
5923 4 : bool bOK = true;
5924 4 : double dfProgress = 0.2;
5925 :
5926 4 : size_t start[] = {0, 0};
5927 4 : size_t count[] = {1, (size_t)nRasterXSize};
5928 : std::unique_ptr<double, decltype(&VSIFree)> adLatValKeeper(
5929 : static_cast<double *>(
5930 8 : VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
5931 4 : VSIFree);
5932 : std::unique_ptr<double, decltype(&VSIFree)> adLonValKeeper(
5933 : static_cast<double *>(
5934 8 : VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
5935 4 : VSIFree);
5936 4 : double *padLonVal = adLonValKeeper.get();
5937 4 : double *padLatVal = adLatValKeeper.get();
5938 4 : if (!padLonVal || !padLatVal)
5939 : {
5940 0 : return CE_Failure;
5941 : }
5942 :
5943 103 : for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR;
5944 : j++)
5945 : {
5946 99 : start[0] = j;
5947 :
5948 : // Get values from geotransform.
5949 99 : if (!bHasGeoloc)
5950 : {
5951 : // Fill values to transform.
5952 60 : if (m_gt.IsAxisAligned())
5953 : {
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 : else
5961 : {
5962 840 : for (int i = 0; i < nRasterXSize; i++)
5963 : {
5964 800 : if (!bBottomUp)
5965 : {
5966 400 : padLatVal[i] = m_gt.yorig +
5967 400 : (i + 0.5) * m_gt.yrot +
5968 400 : (j + 0.5) * m_gt.yscale;
5969 400 : padLonVal[i] = m_gt.xorig +
5970 400 : (i + 0.5) * m_gt.xscale +
5971 400 : (j + 0.5) * m_gt.xrot;
5972 : }
5973 : else
5974 : {
5975 400 : padLatVal[i] =
5976 400 : m_gt.yorig + (i + 0.5) * m_gt.yrot +
5977 400 : (nRasterYSize - j - 0.5) * m_gt.yscale;
5978 400 : padLonVal[i] =
5979 400 : m_gt.xorig + (i + 0.5) * m_gt.xscale +
5980 400 : (nRasterYSize - j - 0.5) * m_gt.xrot;
5981 : }
5982 : }
5983 : }
5984 :
5985 : // Do the transform.
5986 120 : bOK = CPL_TO_BOOL(poTransform->Transform(
5987 60 : nRasterXSize, padLonVal, padLatVal, nullptr));
5988 60 : if (!bOK)
5989 : {
5990 0 : CPLError(CE_Failure, CPLE_AppDefined,
5991 : "Unable to Transform (X,Y) to (lon,lat).");
5992 : }
5993 : }
5994 : // Get values from geoloc arrays.
5995 : else
5996 : {
5997 39 : CPLErr eErr = poBand_Y->RasterIO(
5998 : GF_Read, 0, j, nRasterXSize, 1, padLatVal,
5999 : nRasterXSize, 1, GDT_Float64, 0, 0, nullptr);
6000 39 : if (eErr == CE_None)
6001 : {
6002 39 : eErr = poBand_X->RasterIO(
6003 : GF_Read, 0, j, nRasterXSize, 1, padLonVal,
6004 : nRasterXSize, 1, GDT_Float64, 0, 0, nullptr);
6005 : }
6006 :
6007 39 : if (eErr == CE_None)
6008 : {
6009 39 : bOK = true;
6010 : }
6011 : else
6012 : {
6013 0 : bOK = false;
6014 0 : CPLError(CE_Failure, CPLE_AppDefined,
6015 : "Unable to get scanline %d", j);
6016 : }
6017 : }
6018 :
6019 : // Write data.
6020 99 : if (bOK)
6021 : {
6022 99 : status = nc_put_vara_double(cdfid, nVarLatID, start,
6023 : count, padLatVal);
6024 99 : NCDF_ERR(status);
6025 99 : status = nc_put_vara_double(cdfid, nVarLonID, start,
6026 : count, padLonVal);
6027 99 : NCDF_ERR(status);
6028 : }
6029 :
6030 99 : if (pfnProgress && (nRasterYSize / 10) > 0 &&
6031 99 : (j % (nRasterYSize / 10) == 0))
6032 : {
6033 43 : dfProgress += 0.08;
6034 43 : pfnProgress(dfProgress, nullptr, pProgressData);
6035 : }
6036 : }
6037 : }
6038 : } // Projected
6039 :
6040 : // If not projected/geographic and has geoloc
6041 57 : else if (!bIsGeographic && bHasGeoloc && m_gt.IsAxisAligned())
6042 : {
6043 : // Use
6044 : // https://cfconventions.org/Data/cf-conventions/cf-conventions-1.9/cf-conventions.html#_two_dimensional_latitude_longitude_coordinate_variables
6045 :
6046 4 : bool bOK = true;
6047 4 : double dfProgress = 0.2;
6048 :
6049 : // Make sure we are in data mode.
6050 4 : SetDefineMode(false);
6051 :
6052 : size_t startX[1];
6053 : size_t countX[1];
6054 : size_t startY[1];
6055 : size_t countY[1];
6056 4 : startX[0] = 0;
6057 4 : countX[0] = nRasterXSize;
6058 :
6059 4 : startY[0] = 0;
6060 4 : countY[0] = nRasterYSize;
6061 :
6062 4 : std::vector<double> adfXVal;
6063 4 : std::vector<double> adfYVal;
6064 : try
6065 : {
6066 4 : adfXVal.resize(nRasterXSize);
6067 4 : adfYVal.resize(nRasterYSize);
6068 : }
6069 0 : catch (const std::exception &)
6070 : {
6071 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
6072 : "Out of memory allocating temporary array");
6073 0 : return CE_Failure;
6074 : }
6075 16 : for (int i = 0; i < nRasterXSize; i++)
6076 12 : adfXVal[i] = i;
6077 12 : for (int i = 0; i < nRasterYSize; i++)
6078 8 : adfYVal[i] = bBottomUp ? nRasterYSize - 1 - i : i;
6079 :
6080 4 : CPLDebug("GDAL_netCDF", "Writing X values");
6081 4 : int status = nc_put_vara_double(cdfid, nVarXID, startX, countX,
6082 4 : adfXVal.data());
6083 4 : NCDF_ERR(status);
6084 :
6085 4 : CPLDebug("GDAL_netCDF", "Writing Y values");
6086 4 : status = nc_put_vara_double(cdfid, nVarYID, startY, countY,
6087 4 : adfYVal.data());
6088 4 : NCDF_ERR(status);
6089 :
6090 4 : if (pfnProgress)
6091 0 : pfnProgress(0.20, nullptr, pProgressData);
6092 :
6093 4 : size_t start[] = {0, 0};
6094 4 : size_t count[] = {1, (size_t)nRasterXSize};
6095 :
6096 : std::unique_ptr<double, decltype(&VSIFree)> adLatValKeeper(
6097 : static_cast<double *>(
6098 8 : VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
6099 4 : VSIFree);
6100 : std::unique_ptr<double, decltype(&VSIFree)> adLonValKeeper(
6101 : static_cast<double *>(
6102 8 : VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
6103 4 : VSIFree);
6104 4 : double *padLonVal = adLonValKeeper.get();
6105 4 : double *padLatVal = adLatValKeeper.get();
6106 4 : if (!padLonVal || !padLatVal)
6107 : {
6108 0 : return CE_Failure;
6109 : }
6110 :
6111 12 : for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR; j++)
6112 : {
6113 8 : start[0] = j;
6114 :
6115 8 : CPLErr eErr = poBand_Y->RasterIO(
6116 8 : GF_Read, 0, bBottomUp ? nRasterYSize - 1 - j : j,
6117 : nRasterXSize, 1, padLatVal, nRasterXSize, 1, GDT_Float64, 0,
6118 : 0, nullptr);
6119 8 : if (eErr == CE_None)
6120 : {
6121 8 : eErr = poBand_X->RasterIO(
6122 8 : GF_Read, 0, bBottomUp ? nRasterYSize - 1 - j : j,
6123 : nRasterXSize, 1, padLonVal, nRasterXSize, 1,
6124 : GDT_Float64, 0, 0, nullptr);
6125 : }
6126 :
6127 8 : if (eErr == CE_None)
6128 : {
6129 8 : bOK = true;
6130 : }
6131 : else
6132 : {
6133 0 : bOK = false;
6134 0 : CPLError(CE_Failure, CPLE_AppDefined,
6135 : "Unable to get scanline %d", j);
6136 : }
6137 :
6138 : // Write data.
6139 8 : if (bOK)
6140 : {
6141 8 : status = nc_put_vara_double(cdfid, nVarLatID, start, count,
6142 : padLatVal);
6143 8 : NCDF_ERR(status);
6144 8 : status = nc_put_vara_double(cdfid, nVarLonID, start, count,
6145 : padLonVal);
6146 8 : NCDF_ERR(status);
6147 : }
6148 :
6149 8 : if (pfnProgress && (nRasterYSize / 10) > 0 &&
6150 0 : (j % (nRasterYSize / 10) == 0))
6151 : {
6152 0 : dfProgress += 0.08;
6153 0 : pfnProgress(dfProgress, nullptr, pProgressData);
6154 : }
6155 : }
6156 : }
6157 :
6158 : // If not projected, assume geographic to catch grids without Datum.
6159 53 : else if (bWriteLonLat)
6160 : {
6161 : // Get latitude values.
6162 53 : const double dfY0 = (!bBottomUp) ? m_gt.yorig :
6163 : // Invert latitude values.
6164 53 : m_gt.yorig + (m_gt.yscale * nRasterYSize);
6165 53 : const double dfDY = m_gt.yscale;
6166 :
6167 : std::unique_ptr<double, decltype(&VSIFree)> adLatValKeeper(nullptr,
6168 53 : VSIFree);
6169 53 : double *padLatVal = nullptr;
6170 : // Override lat values with the ones in GEOLOCATION/Y_VALUES.
6171 53 : if (netCDFDataset::GetMetadataItem("Y_VALUES", "GEOLOCATION") !=
6172 : nullptr)
6173 : {
6174 0 : int nTemp = 0;
6175 0 : adLatValKeeper.reset(Get1DGeolocation("Y_VALUES", nTemp));
6176 0 : padLatVal = adLatValKeeper.get();
6177 : // Make sure we got the correct amount, if not fallback to GT */
6178 : // could add test fabs(fabs(padLatVal[0]) - fabs(dfY0)) <= 0.1))
6179 0 : if (nTemp == nRasterYSize)
6180 : {
6181 0 : CPLDebug(
6182 : "GDAL_netCDF",
6183 : "Using Y_VALUES geolocation metadata for lat values");
6184 : }
6185 : else
6186 : {
6187 0 : CPLDebug("GDAL_netCDF",
6188 : "Got %d elements from Y_VALUES geolocation "
6189 : "metadata, need %d",
6190 : nTemp, nRasterYSize);
6191 0 : padLatVal = nullptr;
6192 : }
6193 : }
6194 :
6195 53 : if (padLatVal == nullptr)
6196 : {
6197 53 : adLatValKeeper.reset(static_cast<double *>(
6198 53 : VSI_MALLOC2_VERBOSE(nRasterYSize, sizeof(double))));
6199 53 : padLatVal = adLatValKeeper.get();
6200 53 : if (!padLatVal)
6201 : {
6202 0 : return CE_Failure;
6203 : }
6204 7105 : for (int i = 0; i < nRasterYSize; i++)
6205 : {
6206 : // The data point is centered inside the pixel.
6207 7052 : if (!bBottomUp)
6208 0 : padLatVal[i] = dfY0 + (i + 0.5) * dfDY;
6209 : else // Invert latitude values.
6210 7052 : padLatVal[i] = dfY0 - (i + 0.5) * dfDY;
6211 : }
6212 : }
6213 :
6214 53 : size_t startLat[1] = {0};
6215 53 : size_t countLat[1] = {static_cast<size_t>(nRasterYSize)};
6216 :
6217 : // Get longitude values.
6218 53 : const double dfX0 = m_gt.xorig;
6219 53 : const double dfDX = m_gt.xscale;
6220 :
6221 : std::unique_ptr<double, decltype(&VSIFree)> adLonValKeeper(
6222 : static_cast<double *>(
6223 106 : VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
6224 53 : VSIFree);
6225 53 : double *padLonVal = adLonValKeeper.get();
6226 53 : if (!padLonVal)
6227 : {
6228 0 : return CE_Failure;
6229 : }
6230 7157 : for (int i = 0; i < nRasterXSize; i++)
6231 : {
6232 : // The data point is centered inside the pixel.
6233 7104 : padLonVal[i] = dfX0 + (i + 0.5) * dfDX;
6234 : }
6235 :
6236 53 : size_t startLon[1] = {0};
6237 53 : size_t countLon[1] = {static_cast<size_t>(nRasterXSize)};
6238 :
6239 : // Write latitude and longitude values.
6240 :
6241 : // Make sure we are in data mode.
6242 53 : SetDefineMode(false);
6243 :
6244 : // Write values.
6245 53 : CPLDebug("GDAL_netCDF", "Writing lat values");
6246 :
6247 53 : int status = nc_put_vara_double(cdfid, nVarLatID, startLat,
6248 : countLat, padLatVal);
6249 53 : NCDF_ERR(status);
6250 :
6251 53 : CPLDebug("GDAL_netCDF", "Writing lon values");
6252 53 : status = nc_put_vara_double(cdfid, nVarLonID, startLon, countLon,
6253 : padLonVal);
6254 53 : NCDF_ERR(status);
6255 :
6256 : } // Not projected.
6257 :
6258 88 : if (pfnProgress)
6259 47 : pfnProgress(1.00, nullptr, pProgressData);
6260 : }
6261 :
6262 176 : return CE_None;
6263 : }
6264 :
6265 : // Write Projection variable to band variable.
6266 : // Moved from AddProjectionVars() for cases when bands are added after
6267 : // projection.
6268 447 : bool netCDFDataset::AddGridMappingRef()
6269 : {
6270 447 : bool bRet = true;
6271 447 : bool bOldDefineMode = bDefineMode;
6272 :
6273 650 : if ((GetAccess() == GA_Update) && (nBands >= 1) && (GetRasterBand(1)) &&
6274 203 : ((pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, "")) ||
6275 195 : (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))))
6276 : {
6277 79 : bAddedGridMappingRef = true;
6278 :
6279 : // Make sure we are in define mode.
6280 79 : SetDefineMode(true);
6281 :
6282 204 : for (int i = 1; i <= nBands; i++)
6283 : {
6284 : const int nVarId =
6285 125 : cpl::down_cast<netCDFRasterBand *>(GetRasterBand(i))->nZId;
6286 :
6287 125 : if (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))
6288 : {
6289 : int status =
6290 242 : nc_put_att_text(cdfid, nVarId, CF_GRD_MAPPING,
6291 121 : strlen(pszCFProjection), pszCFProjection);
6292 121 : NCDF_ERR(status);
6293 121 : if (status != NC_NOERR)
6294 0 : bRet = false;
6295 : }
6296 125 : if (pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, ""))
6297 : {
6298 : int status =
6299 8 : nc_put_att_text(cdfid, nVarId, CF_COORDINATES,
6300 : strlen(pszCFCoordinates), pszCFCoordinates);
6301 8 : NCDF_ERR(status);
6302 8 : if (status != NC_NOERR)
6303 0 : bRet = false;
6304 : }
6305 : }
6306 :
6307 : // Go back to previous define mode.
6308 79 : SetDefineMode(bOldDefineMode);
6309 : }
6310 447 : return bRet;
6311 : }
6312 :
6313 : /************************************************************************/
6314 : /* GetGeoTransform() */
6315 : /************************************************************************/
6316 :
6317 129 : CPLErr netCDFDataset::GetGeoTransform(GDALGeoTransform >) const
6318 :
6319 : {
6320 129 : gt = m_gt;
6321 129 : if (m_bHasGeoTransform)
6322 97 : return CE_None;
6323 :
6324 32 : return GDALPamDataset::GetGeoTransform(gt);
6325 : }
6326 :
6327 : /************************************************************************/
6328 : /* rint() */
6329 : /************************************************************************/
6330 :
6331 0 : double netCDFDataset::rint(double dfX)
6332 : {
6333 0 : return std::round(dfX);
6334 : }
6335 :
6336 : /************************************************************************/
6337 : /* NCDFReadIsoMetadata() */
6338 : /************************************************************************/
6339 :
6340 16 : static void NCDFReadMetadataAsJson(int cdfid, CPLJSONObject &obj)
6341 : {
6342 16 : int nbAttr = 0;
6343 16 : NCDF_ERR(nc_inq_varnatts(cdfid, NC_GLOBAL, &nbAttr));
6344 :
6345 32 : std::map<std::string, CPLJSONArray> oMapNameToArray;
6346 40 : for (int l = 0; l < nbAttr; l++)
6347 : {
6348 : char szAttrName[NC_MAX_NAME + 1];
6349 24 : szAttrName[0] = 0;
6350 24 : NCDF_ERR(nc_inq_attname(cdfid, NC_GLOBAL, l, szAttrName));
6351 :
6352 24 : char *pszMetaValue = nullptr;
6353 24 : if (NCDFGetAttr(cdfid, NC_GLOBAL, szAttrName, &pszMetaValue) == CE_None)
6354 : {
6355 24 : nc_type nAttrType = NC_NAT;
6356 24 : size_t nAttrLen = 0;
6357 :
6358 24 : NCDF_ERR(nc_inq_att(cdfid, NC_GLOBAL, szAttrName, &nAttrType,
6359 : &nAttrLen));
6360 :
6361 24 : std::string osAttrName(szAttrName);
6362 24 : const auto sharpPos = osAttrName.find('#');
6363 24 : if (sharpPos == std::string::npos)
6364 : {
6365 16 : if (nAttrType == NC_DOUBLE || nAttrType == NC_FLOAT)
6366 4 : obj.Add(osAttrName, CPLAtof(pszMetaValue));
6367 : else
6368 12 : obj.Add(osAttrName, pszMetaValue);
6369 : }
6370 : else
6371 : {
6372 8 : osAttrName.resize(sharpPos);
6373 8 : auto iter = oMapNameToArray.find(osAttrName);
6374 8 : if (iter == oMapNameToArray.end())
6375 : {
6376 8 : CPLJSONArray array;
6377 4 : obj.Add(osAttrName, array);
6378 4 : oMapNameToArray[osAttrName] = array;
6379 4 : array.Add(pszMetaValue);
6380 : }
6381 : else
6382 : {
6383 4 : iter->second.Add(pszMetaValue);
6384 : }
6385 : }
6386 24 : CPLFree(pszMetaValue);
6387 24 : pszMetaValue = nullptr;
6388 : }
6389 : }
6390 :
6391 16 : int nSubGroups = 0;
6392 16 : int *panSubGroupIds = nullptr;
6393 16 : NCDFGetSubGroups(cdfid, &nSubGroups, &panSubGroupIds);
6394 16 : oMapNameToArray.clear();
6395 28 : for (int i = 0; i < nSubGroups; i++)
6396 : {
6397 24 : CPLJSONObject subObj;
6398 12 : NCDFReadMetadataAsJson(panSubGroupIds[i], subObj);
6399 :
6400 24 : std::string osGroupName;
6401 12 : osGroupName.resize(NC_MAX_NAME);
6402 12 : NCDF_ERR(nc_inq_grpname(panSubGroupIds[i], &osGroupName[0]));
6403 12 : osGroupName.resize(strlen(osGroupName.data()));
6404 12 : const auto sharpPos = osGroupName.find('#');
6405 12 : if (sharpPos == std::string::npos)
6406 : {
6407 4 : obj.Add(osGroupName, subObj);
6408 : }
6409 : else
6410 : {
6411 8 : osGroupName.resize(sharpPos);
6412 8 : auto iter = oMapNameToArray.find(osGroupName);
6413 8 : if (iter == oMapNameToArray.end())
6414 : {
6415 8 : CPLJSONArray array;
6416 4 : obj.Add(osGroupName, array);
6417 4 : oMapNameToArray[osGroupName] = array;
6418 4 : array.Add(subObj);
6419 : }
6420 : else
6421 : {
6422 4 : iter->second.Add(subObj);
6423 : }
6424 : }
6425 : }
6426 16 : CPLFree(panSubGroupIds);
6427 16 : }
6428 :
6429 4 : std::string NCDFReadMetadataAsJson(int cdfid)
6430 : {
6431 8 : CPLJSONDocument oDoc;
6432 8 : CPLJSONObject oRoot = oDoc.GetRoot();
6433 4 : NCDFReadMetadataAsJson(cdfid, oRoot);
6434 8 : return oDoc.SaveAsString();
6435 : }
6436 :
6437 : /************************************************************************/
6438 : /* ReadAttributes() */
6439 : /************************************************************************/
6440 1900 : CPLErr netCDFDataset::ReadAttributes(int cdfidIn, int var)
6441 :
6442 : {
6443 3800 : std::string osVarFullName;
6444 1900 : ERR_RET(NCDFGetVarFullName(cdfidIn, var, osVarFullName));
6445 :
6446 : // For metadata in Sentinel 5
6447 1900 : if (cpl::starts_with(osVarFullName, "/METADATA/"))
6448 : {
6449 6 : for (const char *key :
6450 : {"ISO_METADATA", "ESA_METADATA", "EOP_METADATA", "QA_STATISTICS",
6451 8 : "GRANULE_DESCRIPTION", "ALGORITHM_SETTINGS"})
6452 : {
6453 14 : if (var == NC_GLOBAL &&
6454 14 : osVarFullName == CPLOPrintf("/METADATA/%s/NC_GLOBAL", key))
6455 : {
6456 1 : CPLStringList aosList;
6457 2 : aosList.AddString(CPLString(NCDFReadMetadataAsJson(cdfidIn))
6458 1 : .replaceAll("\\/", '/'));
6459 1 : m_oMapDomainToJSon[key] = std::move(aosList);
6460 1 : return CE_None;
6461 : }
6462 : }
6463 : }
6464 1899 : if (cpl::starts_with(osVarFullName, "/PRODUCT/SUPPORT_DATA/"))
6465 : {
6466 0 : CPLStringList aosList;
6467 : aosList.AddString(
6468 0 : CPLString(NCDFReadMetadataAsJson(cdfidIn)).replaceAll("\\/", '/'));
6469 0 : m_oMapDomainToJSon["SUPPORT_DATA"] = std::move(aosList);
6470 0 : return CE_None;
6471 : }
6472 :
6473 : size_t nMetaNameSize =
6474 1899 : sizeof(char) * (osVarFullName.size() + 1 + NC_MAX_NAME + 1);
6475 1899 : char *pszMetaName = static_cast<char *>(CPLMalloc(nMetaNameSize));
6476 :
6477 1899 : int nbAttr = 0;
6478 1899 : NCDF_ERR(nc_inq_varnatts(cdfidIn, var, &nbAttr));
6479 :
6480 9620 : for (int l = 0; l < nbAttr; l++)
6481 : {
6482 : char szAttrName[NC_MAX_NAME + 1];
6483 7721 : szAttrName[0] = 0;
6484 7721 : NCDF_ERR(nc_inq_attname(cdfidIn, var, l, szAttrName));
6485 7721 : snprintf(pszMetaName, nMetaNameSize, "%s#%s", osVarFullName.c_str(),
6486 : szAttrName);
6487 :
6488 7721 : char *pszMetaTemp = nullptr;
6489 7721 : if (NCDFGetAttr(cdfidIn, var, szAttrName, &pszMetaTemp) == CE_None)
6490 : {
6491 7720 : aosMetadata.SetNameValue(pszMetaName, pszMetaTemp);
6492 7720 : CPLFree(pszMetaTemp);
6493 7720 : pszMetaTemp = nullptr;
6494 : }
6495 : else
6496 : {
6497 1 : CPLDebug("GDAL_netCDF", "invalid metadata %s", pszMetaName);
6498 : }
6499 : }
6500 :
6501 1899 : CPLFree(pszMetaName);
6502 :
6503 1899 : if (var == NC_GLOBAL)
6504 : {
6505 : // Recurse on sub-groups.
6506 547 : int nSubGroups = 0;
6507 547 : int *panSubGroupIds = nullptr;
6508 547 : NCDFGetSubGroups(cdfidIn, &nSubGroups, &panSubGroupIds);
6509 580 : for (int i = 0; i < nSubGroups; i++)
6510 : {
6511 33 : ReadAttributes(panSubGroupIds[i], var);
6512 : }
6513 547 : CPLFree(panSubGroupIds);
6514 : }
6515 :
6516 1899 : return CE_None;
6517 : }
6518 :
6519 : /************************************************************************/
6520 : /* netCDFDataset::CreateSubDatasetList() */
6521 : /************************************************************************/
6522 61 : void netCDFDataset::CreateSubDatasetList(int nGroupId)
6523 : {
6524 122 : std::string osVarStdName;
6525 61 : int *ponDimIds = nullptr;
6526 :
6527 61 : netCDFDataset *poDS = this;
6528 :
6529 : int nVarCount;
6530 61 : nc_inq_nvars(nGroupId, &nVarCount);
6531 :
6532 61 : const bool bListAllArrays = CPLTestBool(
6533 61 : CSLFetchNameValueDef(papszOpenOptions, "LIST_ALL_ARRAYS", "NO"));
6534 :
6535 366 : for (int nVar = 0; nVar < nVarCount; nVar++)
6536 : {
6537 :
6538 : int nDims;
6539 305 : nc_inq_varndims(nGroupId, nVar, &nDims);
6540 :
6541 305 : if ((bListAllArrays && nDims > 0) || nDims >= 2)
6542 : {
6543 177 : ponDimIds = static_cast<int *>(CPLCalloc(nDims, sizeof(int)));
6544 177 : nc_inq_vardimid(nGroupId, nVar, ponDimIds);
6545 :
6546 : // Create Sub dataset list.
6547 177 : CPLString osDim;
6548 545 : for (int i = 0; i < nDims; i++)
6549 : {
6550 : size_t nDimLen;
6551 368 : nc_inq_dimlen(nGroupId, ponDimIds[i], &nDimLen);
6552 368 : if (!osDim.empty())
6553 191 : osDim += 'x';
6554 368 : osDim += CPLSPrintf("%d", (int)nDimLen);
6555 : }
6556 177 : CPLFree(ponDimIds);
6557 :
6558 : nc_type nVarType;
6559 177 : nc_inq_vartype(nGroupId, nVar, &nVarType);
6560 177 : const char *pszType = "";
6561 177 : switch (nVarType)
6562 : {
6563 42 : case NC_BYTE:
6564 42 : pszType = "8-bit integer";
6565 42 : break;
6566 2 : case NC_CHAR:
6567 2 : pszType = "8-bit character";
6568 2 : break;
6569 6 : case NC_SHORT:
6570 6 : pszType = "16-bit integer";
6571 6 : break;
6572 10 : case NC_INT:
6573 10 : pszType = "32-bit integer";
6574 10 : break;
6575 62 : case NC_FLOAT:
6576 62 : pszType = "32-bit floating-point";
6577 62 : break;
6578 34 : case NC_DOUBLE:
6579 34 : pszType = "64-bit floating-point";
6580 34 : break;
6581 4 : case NC_UBYTE:
6582 4 : pszType = "8-bit unsigned integer";
6583 4 : break;
6584 1 : case NC_USHORT:
6585 1 : pszType = "16-bit unsigned integer";
6586 1 : break;
6587 1 : case NC_UINT:
6588 1 : pszType = "32-bit unsigned integer";
6589 1 : break;
6590 1 : case NC_INT64:
6591 1 : pszType = "64-bit integer";
6592 1 : break;
6593 1 : case NC_UINT64:
6594 1 : pszType = "64-bit unsigned integer";
6595 1 : break;
6596 13 : default:
6597 13 : break;
6598 : }
6599 :
6600 177 : std::string osVarName;
6601 177 : if (NCDFGetVarFullName(nGroupId, nVar, osVarName) != CE_None)
6602 0 : continue;
6603 :
6604 177 : nSubDatasets++;
6605 :
6606 177 : if (NCDFGetAttr(nGroupId, nVar, CF_STD_NAME, osVarStdName) !=
6607 : CE_None)
6608 : {
6609 113 : osVarStdName = osVarName;
6610 : }
6611 :
6612 : const std::string osSubDatasetName =
6613 354 : CPLOPrintf("SUBDATASET_%d_NAME", nSubDatasets);
6614 :
6615 354 : if (osVarName.find(' ') != std::string::npos ||
6616 177 : osVarName.find(':') != std::string::npos)
6617 : {
6618 : poDS->aosSubDatasets.SetNameValue(
6619 : osSubDatasetName.c_str(),
6620 : CPLSPrintf("NETCDF:\"%s\":\"%s\"", poDS->osFilename.c_str(),
6621 1 : osVarName.c_str()));
6622 : }
6623 : else
6624 : {
6625 : poDS->aosSubDatasets.SetNameValue(
6626 : osSubDatasetName.c_str(),
6627 : CPLSPrintf("NETCDF:\"%s\":%s", poDS->osFilename.c_str(),
6628 176 : osVarName.c_str()));
6629 : }
6630 :
6631 : const std::string osSubDatasetDesc =
6632 354 : CPLOPrintf("SUBDATASET_%d_DESC", nSubDatasets);
6633 :
6634 : poDS->aosSubDatasets.SetNameValue(
6635 : osSubDatasetDesc.c_str(),
6636 : CPLSPrintf("[%s] %s (%s)", osDim.c_str(), osVarStdName.c_str(),
6637 177 : pszType));
6638 : }
6639 : }
6640 :
6641 : // Recurse on sub groups.
6642 61 : int nSubGroups = 0;
6643 61 : int *panSubGroupIds = nullptr;
6644 61 : NCDFGetSubGroups(nGroupId, &nSubGroups, &panSubGroupIds);
6645 69 : for (int i = 0; i < nSubGroups; i++)
6646 : {
6647 8 : CreateSubDatasetList(panSubGroupIds[i]);
6648 : }
6649 61 : CPLFree(panSubGroupIds);
6650 61 : }
6651 :
6652 : /************************************************************************/
6653 : /* TestCapability() */
6654 : /************************************************************************/
6655 :
6656 249 : int netCDFDataset::TestCapability(const char *pszCap) const
6657 : {
6658 249 : if (EQUAL(pszCap, ODsCCreateLayer))
6659 : {
6660 225 : return eAccess == GA_Update && nBands == 0 &&
6661 219 : (eMultipleLayerBehavior != SINGLE_LAYER ||
6662 230 : this->GetLayerCount() == 0 || bSGSupport);
6663 : }
6664 136 : else if (EQUAL(pszCap, ODsCZGeometries))
6665 2 : return true;
6666 :
6667 134 : return false;
6668 : }
6669 :
6670 : /************************************************************************/
6671 : /* GetLayer() */
6672 : /************************************************************************/
6673 :
6674 395 : const OGRLayer *netCDFDataset::GetLayer(int nIdx) const
6675 : {
6676 395 : if (nIdx < 0 || nIdx >= this->GetLayerCount())
6677 2 : return nullptr;
6678 393 : return papoLayers[nIdx].get();
6679 : }
6680 :
6681 : /************************************************************************/
6682 : /* ICreateLayer() */
6683 : /************************************************************************/
6684 :
6685 60 : OGRLayer *netCDFDataset::ICreateLayer(const char *pszName,
6686 : const OGRGeomFieldDefn *poGeomFieldDefn,
6687 : CSLConstList papszOptions)
6688 : {
6689 60 : int nLayerCDFId = cdfid;
6690 60 : if (!TestCapability(ODsCCreateLayer))
6691 0 : return nullptr;
6692 :
6693 60 : const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
6694 : const auto poSpatialRef =
6695 60 : poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
6696 :
6697 120 : CPLString osNetCDFLayerName(pszName);
6698 60 : const netCDFWriterConfigLayer *poLayerConfig = nullptr;
6699 60 : if (oWriterConfig.m_bIsValid)
6700 : {
6701 : std::map<CPLString, netCDFWriterConfigLayer>::const_iterator
6702 2 : oLayerIter = oWriterConfig.m_oLayers.find(pszName);
6703 2 : if (oLayerIter != oWriterConfig.m_oLayers.end())
6704 : {
6705 1 : poLayerConfig = &(oLayerIter->second);
6706 1 : osNetCDFLayerName = poLayerConfig->m_osNetCDFName;
6707 : }
6708 : }
6709 :
6710 60 : netCDFDataset *poLayerDataset = nullptr;
6711 60 : if (eMultipleLayerBehavior == SEPARATE_FILES)
6712 : {
6713 3 : if (CPLLaunderForFilenameSafe(osNetCDFLayerName.c_str(), nullptr) !=
6714 : osNetCDFLayerName)
6715 : {
6716 1 : CPLError(CE_Failure, CPLE_AppDefined,
6717 : "Illegal characters in '%s' to form a valid filename",
6718 : osNetCDFLayerName.c_str());
6719 1 : return nullptr;
6720 : }
6721 2 : CPLStringList aosDatasetOptions;
6722 : aosDatasetOptions.SetNameValue(
6723 2 : "CONFIG_FILE", aosCreationOptions.FetchNameValue("CONFIG_FILE"));
6724 : aosDatasetOptions.SetNameValue(
6725 2 : "FORMAT", aosCreationOptions.FetchNameValue("FORMAT"));
6726 : aosDatasetOptions.SetNameValue(
6727 : "WRITE_GDAL_TAGS",
6728 2 : aosCreationOptions.FetchNameValue("WRITE_GDAL_TAGS"));
6729 : const CPLString osLayerFilename(
6730 2 : CPLFormFilenameSafe(osFilename, osNetCDFLayerName, "nc"));
6731 2 : CPLAcquireMutex(hNCMutex, 1000.0);
6732 2 : poLayerDataset =
6733 2 : CreateLL(osLayerFilename, 0, 0, 0, aosDatasetOptions.List());
6734 2 : CPLReleaseMutex(hNCMutex);
6735 2 : if (poLayerDataset == nullptr)
6736 0 : return nullptr;
6737 :
6738 2 : nLayerCDFId = poLayerDataset->cdfid;
6739 2 : NCDFAddGDALHistory(nLayerCDFId, osLayerFilename, bWriteGDALVersion,
6740 2 : bWriteGDALHistory, "", "Create",
6741 : NCDF_CONVENTIONS_CF_V1_6);
6742 : }
6743 57 : else if (eMultipleLayerBehavior == SEPARATE_GROUPS)
6744 : {
6745 2 : SetDefineMode(true);
6746 :
6747 2 : nLayerCDFId = -1;
6748 2 : int status = nc_def_grp(cdfid, osNetCDFLayerName, &nLayerCDFId);
6749 2 : NCDF_ERR(status);
6750 2 : if (status != NC_NOERR)
6751 0 : return nullptr;
6752 :
6753 2 : NCDFAddGDALHistory(nLayerCDFId, osFilename, bWriteGDALVersion,
6754 2 : bWriteGDALHistory, "", "Create",
6755 : NCDF_CONVENTIONS_CF_V1_6);
6756 : }
6757 :
6758 : // Make a clone to workaround a bug in released MapServer versions
6759 : // that destroys the passed SRS instead of releasing it .
6760 59 : OGRSpatialReference *poSRS = nullptr;
6761 59 : if (poSpatialRef)
6762 : {
6763 43 : poSRS = poSpatialRef->Clone();
6764 43 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
6765 : }
6766 : std::shared_ptr<netCDFLayer> poLayer(
6767 59 : new netCDFLayer(poLayerDataset ? poLayerDataset : this, nLayerCDFId,
6768 118 : osNetCDFLayerName, eGType, poSRS));
6769 59 : if (poSRS != nullptr)
6770 43 : poSRS->Release();
6771 :
6772 : // Fetch layer creation options coming from config file
6773 118 : CPLStringList aosNewOptions(CSLDuplicate(papszOptions));
6774 59 : if (oWriterConfig.m_bIsValid)
6775 : {
6776 2 : for (const auto &[osName, osValue] :
6777 4 : oWriterConfig.m_oLayerCreationOptions)
6778 : {
6779 1 : aosNewOptions.SetNameValue(osName, osValue);
6780 : }
6781 2 : if (poLayerConfig != nullptr)
6782 : {
6783 4 : for (const auto &[osName, osValue] :
6784 5 : poLayerConfig->m_oLayerCreationOptions)
6785 : {
6786 2 : aosNewOptions.SetNameValue(osName, osValue);
6787 : }
6788 : }
6789 : }
6790 :
6791 59 : const bool bRet = poLayer->Create(aosNewOptions.List(), poLayerConfig);
6792 :
6793 59 : if (!bRet)
6794 : {
6795 0 : return nullptr;
6796 : }
6797 :
6798 59 : if (poLayerDataset != nullptr)
6799 2 : apoVectorDatasets.push_back(poLayerDataset);
6800 :
6801 59 : papoLayers.push_back(poLayer);
6802 59 : return poLayer.get();
6803 : }
6804 :
6805 : /************************************************************************/
6806 : /* CloneAttributes() */
6807 : /************************************************************************/
6808 :
6809 137 : bool netCDFDataset::CloneAttributes(int old_cdfid, int new_cdfid, int nSrcVarId,
6810 : int nDstVarId)
6811 : {
6812 137 : int nAttCount = -1;
6813 137 : int status = nc_inq_varnatts(old_cdfid, nSrcVarId, &nAttCount);
6814 137 : NCDF_ERR(status);
6815 :
6816 693 : for (int i = 0; i < nAttCount; i++)
6817 : {
6818 : char szName[NC_MAX_NAME + 1];
6819 556 : szName[0] = 0;
6820 556 : status = nc_inq_attname(old_cdfid, nSrcVarId, i, szName);
6821 556 : NCDF_ERR(status);
6822 :
6823 : status =
6824 556 : nc_copy_att(old_cdfid, nSrcVarId, szName, new_cdfid, nDstVarId);
6825 556 : NCDF_ERR(status);
6826 556 : if (status != NC_NOERR)
6827 0 : return false;
6828 : }
6829 :
6830 137 : return true;
6831 : }
6832 :
6833 : /************************************************************************/
6834 : /* CloneVariableContent() */
6835 : /************************************************************************/
6836 :
6837 121 : bool netCDFDataset::CloneVariableContent(int old_cdfid, int new_cdfid,
6838 : int nSrcVarId, int nDstVarId)
6839 : {
6840 121 : int nVarDimCount = -1;
6841 121 : int status = nc_inq_varndims(old_cdfid, nSrcVarId, &nVarDimCount);
6842 121 : NCDF_ERR(status);
6843 121 : int anDimIds[] = {-1, 1};
6844 121 : status = nc_inq_vardimid(old_cdfid, nSrcVarId, anDimIds);
6845 121 : NCDF_ERR(status);
6846 121 : nc_type nc_datatype = NC_NAT;
6847 121 : status = nc_inq_vartype(old_cdfid, nSrcVarId, &nc_datatype);
6848 121 : NCDF_ERR(status);
6849 121 : size_t nTypeSize = 0;
6850 121 : switch (nc_datatype)
6851 : {
6852 35 : case NC_BYTE:
6853 : case NC_CHAR:
6854 35 : nTypeSize = 1;
6855 35 : break;
6856 4 : case NC_SHORT:
6857 4 : nTypeSize = 2;
6858 4 : break;
6859 24 : case NC_INT:
6860 24 : nTypeSize = 4;
6861 24 : break;
6862 4 : case NC_FLOAT:
6863 4 : nTypeSize = 4;
6864 4 : break;
6865 43 : case NC_DOUBLE:
6866 43 : nTypeSize = 8;
6867 43 : break;
6868 2 : case NC_UBYTE:
6869 2 : nTypeSize = 1;
6870 2 : break;
6871 2 : case NC_USHORT:
6872 2 : nTypeSize = 2;
6873 2 : break;
6874 2 : case NC_UINT:
6875 2 : nTypeSize = 4;
6876 2 : break;
6877 4 : case NC_INT64:
6878 : case NC_UINT64:
6879 4 : nTypeSize = 8;
6880 4 : break;
6881 1 : case NC_STRING:
6882 1 : nTypeSize = sizeof(char *);
6883 1 : break;
6884 0 : default:
6885 : {
6886 0 : CPLError(CE_Failure, CPLE_NotSupported, "Unsupported data type: %d",
6887 : nc_datatype);
6888 0 : return false;
6889 : }
6890 : }
6891 :
6892 121 : size_t nElems = 1;
6893 : size_t anStart[NC_MAX_DIMS];
6894 : size_t anCount[NC_MAX_DIMS];
6895 121 : size_t nRecords = 1;
6896 261 : for (int i = 0; i < nVarDimCount; i++)
6897 : {
6898 140 : anStart[i] = 0;
6899 140 : if (i == 0)
6900 : {
6901 116 : anCount[i] = 1;
6902 116 : status = nc_inq_dimlen(old_cdfid, anDimIds[i], &nRecords);
6903 116 : NCDF_ERR(status);
6904 : }
6905 : else
6906 : {
6907 24 : anCount[i] = 0;
6908 24 : status = nc_inq_dimlen(old_cdfid, anDimIds[i], &anCount[i]);
6909 24 : NCDF_ERR(status);
6910 24 : nElems *= anCount[i];
6911 : }
6912 : }
6913 :
6914 : /* Workaround in some cases a netCDF bug:
6915 : * https://github.com/Unidata/netcdf-c/pull/1442 */
6916 121 : if (nRecords > 0 && nRecords < 10 * 1000 * 1000 / (nElems * nTypeSize))
6917 : {
6918 119 : nElems *= nRecords;
6919 119 : anCount[0] = nRecords;
6920 119 : nRecords = 1;
6921 : }
6922 :
6923 121 : void *pBuffer = VSI_MALLOC2_VERBOSE(nElems, nTypeSize);
6924 121 : if (pBuffer == nullptr)
6925 0 : return false;
6926 :
6927 240 : for (size_t iRecord = 0; iRecord < nRecords; iRecord++)
6928 : {
6929 119 : anStart[0] = iRecord;
6930 :
6931 119 : switch (nc_datatype)
6932 : {
6933 5 : case NC_BYTE:
6934 : status =
6935 5 : nc_get_vara_schar(old_cdfid, nSrcVarId, anStart, anCount,
6936 : static_cast<signed char *>(pBuffer));
6937 5 : if (!status)
6938 5 : status = nc_put_vara_schar(
6939 : new_cdfid, nDstVarId, anStart, anCount,
6940 : static_cast<signed char *>(pBuffer));
6941 5 : break;
6942 28 : case NC_CHAR:
6943 : status =
6944 28 : nc_get_vara_text(old_cdfid, nSrcVarId, anStart, anCount,
6945 : static_cast<char *>(pBuffer));
6946 28 : if (!status)
6947 : status =
6948 28 : nc_put_vara_text(new_cdfid, nDstVarId, anStart, anCount,
6949 : static_cast<char *>(pBuffer));
6950 28 : break;
6951 4 : case NC_SHORT:
6952 : status =
6953 4 : nc_get_vara_short(old_cdfid, nSrcVarId, anStart, anCount,
6954 : static_cast<short *>(pBuffer));
6955 4 : if (!status)
6956 4 : status = nc_put_vara_short(new_cdfid, nDstVarId, anStart,
6957 : anCount,
6958 : static_cast<short *>(pBuffer));
6959 4 : break;
6960 24 : case NC_INT:
6961 24 : status = nc_get_vara_int(old_cdfid, nSrcVarId, anStart, anCount,
6962 : static_cast<int *>(pBuffer));
6963 24 : if (!status)
6964 : status =
6965 24 : nc_put_vara_int(new_cdfid, nDstVarId, anStart, anCount,
6966 : static_cast<int *>(pBuffer));
6967 24 : break;
6968 4 : case NC_FLOAT:
6969 : status =
6970 4 : nc_get_vara_float(old_cdfid, nSrcVarId, anStart, anCount,
6971 : static_cast<float *>(pBuffer));
6972 4 : if (!status)
6973 4 : status = nc_put_vara_float(new_cdfid, nDstVarId, anStart,
6974 : anCount,
6975 : static_cast<float *>(pBuffer));
6976 4 : break;
6977 43 : case NC_DOUBLE:
6978 : status =
6979 43 : nc_get_vara_double(old_cdfid, nSrcVarId, anStart, anCount,
6980 : static_cast<double *>(pBuffer));
6981 43 : if (!status)
6982 43 : status = nc_put_vara_double(new_cdfid, nDstVarId, anStart,
6983 : anCount,
6984 : static_cast<double *>(pBuffer));
6985 43 : break;
6986 1 : case NC_STRING:
6987 : status =
6988 1 : nc_get_vara_string(old_cdfid, nSrcVarId, anStart, anCount,
6989 : static_cast<char **>(pBuffer));
6990 1 : if (!status)
6991 : {
6992 1 : status = nc_put_vara_string(
6993 : new_cdfid, nDstVarId, anStart, anCount,
6994 : static_cast<const char **>(pBuffer));
6995 1 : nc_free_string(nElems, static_cast<char **>(pBuffer));
6996 : }
6997 1 : break;
6998 :
6999 2 : case NC_UBYTE:
7000 : status =
7001 2 : nc_get_vara_uchar(old_cdfid, nSrcVarId, anStart, anCount,
7002 : static_cast<unsigned char *>(pBuffer));
7003 2 : if (!status)
7004 2 : status = nc_put_vara_uchar(
7005 : new_cdfid, nDstVarId, anStart, anCount,
7006 : static_cast<unsigned char *>(pBuffer));
7007 2 : break;
7008 2 : case NC_USHORT:
7009 : status =
7010 2 : nc_get_vara_ushort(old_cdfid, nSrcVarId, anStart, anCount,
7011 : static_cast<unsigned short *>(pBuffer));
7012 2 : if (!status)
7013 2 : status = nc_put_vara_ushort(
7014 : new_cdfid, nDstVarId, anStart, anCount,
7015 : static_cast<unsigned short *>(pBuffer));
7016 2 : break;
7017 2 : case NC_UINT:
7018 : status =
7019 2 : nc_get_vara_uint(old_cdfid, nSrcVarId, anStart, anCount,
7020 : static_cast<unsigned int *>(pBuffer));
7021 2 : if (!status)
7022 : status =
7023 2 : nc_put_vara_uint(new_cdfid, nDstVarId, anStart, anCount,
7024 : static_cast<unsigned int *>(pBuffer));
7025 2 : break;
7026 2 : case NC_INT64:
7027 : status =
7028 2 : nc_get_vara_longlong(old_cdfid, nSrcVarId, anStart, anCount,
7029 : static_cast<long long *>(pBuffer));
7030 2 : if (!status)
7031 2 : status = nc_put_vara_longlong(
7032 : new_cdfid, nDstVarId, anStart, anCount,
7033 : static_cast<long long *>(pBuffer));
7034 2 : break;
7035 2 : case NC_UINT64:
7036 2 : status = nc_get_vara_ulonglong(
7037 : old_cdfid, nSrcVarId, anStart, anCount,
7038 : static_cast<unsigned long long *>(pBuffer));
7039 2 : if (!status)
7040 2 : status = nc_put_vara_ulonglong(
7041 : new_cdfid, nDstVarId, anStart, anCount,
7042 : static_cast<unsigned long long *>(pBuffer));
7043 2 : break;
7044 0 : default:
7045 0 : status = NC_EBADTYPE;
7046 : }
7047 :
7048 119 : NCDF_ERR(status);
7049 119 : if (status != NC_NOERR)
7050 : {
7051 0 : VSIFree(pBuffer);
7052 0 : return false;
7053 : }
7054 : }
7055 :
7056 121 : VSIFree(pBuffer);
7057 121 : return true;
7058 : }
7059 :
7060 : /************************************************************************/
7061 : /* NCDFIsUnlimitedDim() */
7062 : /************************************************************************/
7063 :
7064 58 : bool NCDFIsUnlimitedDim(bool bIsNC4, int cdfid, int nDimId)
7065 : {
7066 58 : if (bIsNC4)
7067 : {
7068 16 : int nUnlimitedDims = 0;
7069 16 : nc_inq_unlimdims(cdfid, &nUnlimitedDims, nullptr);
7070 16 : bool bFound = false;
7071 16 : if (nUnlimitedDims)
7072 : {
7073 : int *panUnlimitedDimIds =
7074 16 : static_cast<int *>(CPLMalloc(sizeof(int) * nUnlimitedDims));
7075 16 : nc_inq_unlimdims(cdfid, nullptr, panUnlimitedDimIds);
7076 30 : for (int i = 0; i < nUnlimitedDims; i++)
7077 : {
7078 22 : if (panUnlimitedDimIds[i] == nDimId)
7079 : {
7080 8 : bFound = true;
7081 8 : break;
7082 : }
7083 : }
7084 16 : CPLFree(panUnlimitedDimIds);
7085 : }
7086 16 : return bFound;
7087 : }
7088 : else
7089 : {
7090 42 : int nUnlimitedDimId = -1;
7091 42 : nc_inq(cdfid, nullptr, nullptr, nullptr, &nUnlimitedDimId);
7092 42 : return nDimId == nUnlimitedDimId;
7093 : }
7094 : }
7095 :
7096 : /************************************************************************/
7097 : /* CloneGrp() */
7098 : /************************************************************************/
7099 :
7100 16 : bool netCDFDataset::CloneGrp(int nOldGrpId, int nNewGrpId, bool bIsNC4,
7101 : int nLayerId, int nDimIdToGrow, size_t nNewSize)
7102 : {
7103 : // Clone dimensions
7104 16 : int nDimCount = -1;
7105 16 : int status = nc_inq_ndims(nOldGrpId, &nDimCount);
7106 16 : NCDF_ERR(status);
7107 16 : if (nDimCount < 0 || nDimCount > NC_MAX_DIMS)
7108 0 : return false;
7109 : int anDimIds[NC_MAX_DIMS];
7110 16 : int nUnlimiDimID = -1;
7111 16 : status = nc_inq_unlimdim(nOldGrpId, &nUnlimiDimID);
7112 16 : NCDF_ERR(status);
7113 16 : if (bIsNC4)
7114 : {
7115 : // In NC4, the dimension ids of a group are not necessarily in
7116 : // [0,nDimCount-1] range
7117 8 : int nDimCount2 = -1;
7118 8 : status = nc_inq_dimids(nOldGrpId, &nDimCount2, anDimIds, FALSE);
7119 8 : NCDF_ERR(status);
7120 8 : CPLAssert(nDimCount == nDimCount2);
7121 : }
7122 : else
7123 : {
7124 36 : for (int i = 0; i < nDimCount; i++)
7125 28 : anDimIds[i] = i;
7126 : }
7127 60 : for (int i = 0; i < nDimCount; i++)
7128 : {
7129 : char szDimName[NC_MAX_NAME + 1];
7130 44 : szDimName[0] = 0;
7131 44 : size_t nLen = 0;
7132 44 : const int nDimId = anDimIds[i];
7133 44 : status = nc_inq_dim(nOldGrpId, nDimId, szDimName, &nLen);
7134 44 : NCDF_ERR(status);
7135 44 : if (NCDFIsUnlimitedDim(bIsNC4, nOldGrpId, nDimId))
7136 16 : nLen = NC_UNLIMITED;
7137 28 : else if (nDimId == nDimIdToGrow && nOldGrpId == nLayerId)
7138 13 : nLen = nNewSize;
7139 44 : int nNewDimId = -1;
7140 44 : status = nc_def_dim(nNewGrpId, szDimName, nLen, &nNewDimId);
7141 44 : NCDF_ERR(status);
7142 44 : CPLAssert(nDimId == nNewDimId);
7143 44 : if (status != NC_NOERR)
7144 : {
7145 0 : return false;
7146 : }
7147 : }
7148 :
7149 : // Clone main attributes
7150 16 : if (!CloneAttributes(nOldGrpId, nNewGrpId, NC_GLOBAL, NC_GLOBAL))
7151 : {
7152 0 : return false;
7153 : }
7154 :
7155 : // Clone variable definitions
7156 16 : int nVarCount = -1;
7157 16 : status = nc_inq_nvars(nOldGrpId, &nVarCount);
7158 16 : NCDF_ERR(status);
7159 :
7160 137 : for (int i = 0; i < nVarCount; i++)
7161 : {
7162 : char szVarName[NC_MAX_NAME + 1];
7163 121 : szVarName[0] = 0;
7164 121 : status = nc_inq_varname(nOldGrpId, i, szVarName);
7165 121 : NCDF_ERR(status);
7166 121 : nc_type nc_datatype = NC_NAT;
7167 121 : status = nc_inq_vartype(nOldGrpId, i, &nc_datatype);
7168 121 : NCDF_ERR(status);
7169 121 : int nVarDimCount = -1;
7170 121 : status = nc_inq_varndims(nOldGrpId, i, &nVarDimCount);
7171 121 : NCDF_ERR(status);
7172 121 : status = nc_inq_vardimid(nOldGrpId, i, anDimIds);
7173 121 : NCDF_ERR(status);
7174 121 : int nNewVarId = -1;
7175 121 : status = nc_def_var(nNewGrpId, szVarName, nc_datatype, nVarDimCount,
7176 : anDimIds, &nNewVarId);
7177 121 : NCDF_ERR(status);
7178 121 : CPLAssert(i == nNewVarId);
7179 121 : if (status != NC_NOERR)
7180 : {
7181 0 : return false;
7182 : }
7183 :
7184 121 : if (!CloneAttributes(nOldGrpId, nNewGrpId, i, i))
7185 : {
7186 0 : return false;
7187 : }
7188 : }
7189 :
7190 16 : status = nc_enddef(nNewGrpId);
7191 16 : NCDF_ERR(status);
7192 16 : if (status != NC_NOERR)
7193 : {
7194 0 : return false;
7195 : }
7196 :
7197 : // Clone variable content
7198 137 : for (int i = 0; i < nVarCount; i++)
7199 : {
7200 121 : if (!CloneVariableContent(nOldGrpId, nNewGrpId, i, i))
7201 : {
7202 0 : return false;
7203 : }
7204 : }
7205 :
7206 16 : return true;
7207 : }
7208 :
7209 : /************************************************************************/
7210 : /* GrowDim() */
7211 : /************************************************************************/
7212 :
7213 13 : bool netCDFDataset::GrowDim(int nLayerId, int nDimIdToGrow, size_t nNewSize)
7214 : {
7215 : int nCreationMode;
7216 : // Set nCreationMode based on eFormat.
7217 13 : switch (eFormat)
7218 : {
7219 : #ifdef NETCDF_HAS_NC2
7220 0 : case NCDF_FORMAT_NC2:
7221 0 : nCreationMode = NC_CLOBBER | NC_64BIT_OFFSET;
7222 0 : break;
7223 : #endif
7224 5 : case NCDF_FORMAT_NC4:
7225 5 : nCreationMode = NC_CLOBBER | NC_NETCDF4;
7226 5 : break;
7227 0 : case NCDF_FORMAT_NC4C:
7228 0 : nCreationMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
7229 0 : break;
7230 8 : case NCDF_FORMAT_NC:
7231 : default:
7232 8 : nCreationMode = NC_CLOBBER;
7233 8 : break;
7234 : }
7235 :
7236 13 : int new_cdfid = -1;
7237 26 : CPLString osTmpFilename(osFilename + ".tmp");
7238 26 : CPLString osFilenameForNCCreate(osTmpFilename);
7239 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
7240 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
7241 : {
7242 : char *pszTemp =
7243 : CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
7244 : osFilenameForNCCreate = pszTemp;
7245 : CPLFree(pszTemp);
7246 : }
7247 : #endif
7248 13 : int status = nc_create(osFilenameForNCCreate, nCreationMode, &new_cdfid);
7249 13 : NCDF_ERR(status);
7250 13 : if (status != NC_NOERR)
7251 0 : return false;
7252 :
7253 13 : if (!CloneGrp(cdfid, new_cdfid, eFormat == NCDF_FORMAT_NC4, nLayerId,
7254 : nDimIdToGrow, nNewSize))
7255 : {
7256 0 : GDAL_nc_close(new_cdfid);
7257 0 : return false;
7258 : }
7259 :
7260 13 : int nGroupCount = 0;
7261 26 : std::vector<CPLString> oListGrpName;
7262 31 : if (eFormat == NCDF_FORMAT_NC4 &&
7263 18 : nc_inq_grps(cdfid, &nGroupCount, nullptr) == NC_NOERR &&
7264 5 : nGroupCount > 0)
7265 : {
7266 : int *panGroupIds =
7267 2 : static_cast<int *>(CPLMalloc(sizeof(int) * nGroupCount));
7268 2 : status = nc_inq_grps(cdfid, nullptr, panGroupIds);
7269 2 : NCDF_ERR(status);
7270 5 : for (int i = 0; i < nGroupCount; i++)
7271 : {
7272 : char szGroupName[NC_MAX_NAME + 1];
7273 3 : szGroupName[0] = 0;
7274 3 : NCDF_ERR(nc_inq_grpname(panGroupIds[i], szGroupName));
7275 3 : int nNewGrpId = -1;
7276 3 : status = nc_def_grp(new_cdfid, szGroupName, &nNewGrpId);
7277 3 : NCDF_ERR(status);
7278 3 : if (status != NC_NOERR)
7279 : {
7280 0 : CPLFree(panGroupIds);
7281 0 : GDAL_nc_close(new_cdfid);
7282 0 : return false;
7283 : }
7284 3 : if (!CloneGrp(panGroupIds[i], nNewGrpId, /*bIsNC4=*/true, nLayerId,
7285 : nDimIdToGrow, nNewSize))
7286 : {
7287 0 : CPLFree(panGroupIds);
7288 0 : GDAL_nc_close(new_cdfid);
7289 0 : return false;
7290 : }
7291 : }
7292 2 : CPLFree(panGroupIds);
7293 :
7294 5 : for (int i = 0; i < this->GetLayerCount(); i++)
7295 : {
7296 3 : auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
7297 3 : if (poLayer)
7298 : {
7299 : char szGroupName[NC_MAX_NAME + 1];
7300 3 : szGroupName[0] = 0;
7301 3 : status = nc_inq_grpname(poLayer->GetCDFID(), szGroupName);
7302 3 : NCDF_ERR(status);
7303 3 : oListGrpName.push_back(szGroupName);
7304 : }
7305 : }
7306 : }
7307 :
7308 13 : GDAL_nc_close(cdfid);
7309 13 : cdfid = -1;
7310 13 : GDAL_nc_close(new_cdfid);
7311 :
7312 26 : CPLString osOriFilename(osFilename + ".ori");
7313 26 : if (VSIRename(osFilename, osOriFilename) != 0 ||
7314 13 : VSIRename(osTmpFilename, osFilename) != 0)
7315 : {
7316 0 : CPLError(CE_Failure, CPLE_FileIO, "Renaming of files failed");
7317 0 : return false;
7318 : }
7319 13 : VSIUnlink(osOriFilename);
7320 :
7321 26 : CPLString osFilenameForNCOpen(osFilename);
7322 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
7323 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
7324 : {
7325 : char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
7326 : osFilenameForNCOpen = pszTemp;
7327 : CPLFree(pszTemp);
7328 : }
7329 : #endif
7330 13 : status = GDAL_nc_open(osFilenameForNCOpen, NC_WRITE, &cdfid);
7331 13 : NCDF_ERR(status);
7332 13 : if (status != NC_NOERR)
7333 0 : return false;
7334 13 : bDefineMode = false;
7335 :
7336 13 : if (!oListGrpName.empty())
7337 : {
7338 5 : for (int i = 0; i < this->GetLayerCount(); i++)
7339 : {
7340 3 : auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
7341 3 : if (poLayer)
7342 : {
7343 3 : int nNewLayerCDFID = -1;
7344 3 : status = nc_inq_ncid(cdfid, oListGrpName[i].c_str(),
7345 : &nNewLayerCDFID);
7346 3 : NCDF_ERR(status);
7347 3 : poLayer->SetCDFID(nNewLayerCDFID);
7348 : }
7349 : }
7350 : }
7351 : else
7352 : {
7353 22 : for (int i = 0; i < this->GetLayerCount(); i++)
7354 : {
7355 11 : auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
7356 11 : if (poLayer)
7357 11 : poLayer->SetCDFID(cdfid);
7358 : }
7359 : }
7360 :
7361 13 : return true;
7362 : }
7363 :
7364 : #ifdef ENABLE_NCDUMP
7365 :
7366 : /************************************************************************/
7367 : /* netCDFDatasetCreateTempFile() */
7368 : /************************************************************************/
7369 :
7370 : /* Create a netCDF file from a text dump (format of ncdump) */
7371 : /* Mostly to easy fuzzing of the driver, while still generating valid */
7372 : /* netCDF files. */
7373 : /* Note: not all data types are supported ! */
7374 4 : bool netCDFDatasetCreateTempFile(NetCDFFormatEnum eFormat,
7375 : const char *pszTmpFilename, VSILFILE *fpSrc)
7376 : {
7377 4 : CPL_IGNORE_RET_VAL(eFormat);
7378 4 : int nCreateMode = NC_CLOBBER;
7379 4 : if (eFormat == NCDF_FORMAT_NC4)
7380 1 : nCreateMode |= NC_NETCDF4;
7381 3 : else if (eFormat == NCDF_FORMAT_NC4C)
7382 0 : nCreateMode |= NC_NETCDF4 | NC_CLASSIC_MODEL;
7383 4 : int nCdfId = -1;
7384 4 : int status = nc_create(pszTmpFilename, nCreateMode, &nCdfId);
7385 4 : if (status != NC_NOERR)
7386 : {
7387 0 : return false;
7388 : }
7389 4 : VSIFSeekL(fpSrc, 0, SEEK_SET);
7390 : const char *pszLine;
7391 4 : constexpr int SECTION_NONE = 0;
7392 4 : constexpr int SECTION_DIMENSIONS = 1;
7393 4 : constexpr int SECTION_VARIABLES = 2;
7394 4 : constexpr int SECTION_DATA = 3;
7395 4 : int nActiveSection = SECTION_NONE;
7396 8 : std::map<CPLString, int> oMapDimToId;
7397 8 : std::map<int, int> oMapDimIdToDimLen;
7398 8 : std::map<CPLString, int> oMapVarToId;
7399 8 : std::map<int, std::vector<int>> oMapVarIdToVectorOfDimId;
7400 8 : std::map<int, int> oMapVarIdToType;
7401 4 : std::set<CPLString> oSetAttrDefined;
7402 4 : oMapVarToId[""] = -1;
7403 4 : size_t nTotalVarSize = 0;
7404 208 : while ((pszLine = CPLReadLineL(fpSrc)) != nullptr)
7405 : {
7406 204 : if (STARTS_WITH(pszLine, "dimensions:") &&
7407 : nActiveSection == SECTION_NONE)
7408 : {
7409 4 : nActiveSection = SECTION_DIMENSIONS;
7410 : }
7411 200 : else if (STARTS_WITH(pszLine, "variables:") &&
7412 : nActiveSection == SECTION_DIMENSIONS)
7413 : {
7414 4 : nActiveSection = SECTION_VARIABLES;
7415 : }
7416 196 : else if (STARTS_WITH(pszLine, "data:") &&
7417 : nActiveSection == SECTION_VARIABLES)
7418 : {
7419 4 : nActiveSection = SECTION_DATA;
7420 4 : status = nc_enddef(nCdfId);
7421 4 : if (status != NC_NOERR)
7422 : {
7423 0 : CPLDebug("netCDF", "nc_enddef() failed: %s",
7424 : nc_strerror(status));
7425 : }
7426 : }
7427 192 : else if (nActiveSection == SECTION_DIMENSIONS)
7428 : {
7429 : const CPLStringList aosTokens(
7430 9 : CSLTokenizeString2(pszLine, " \t=;", 0));
7431 9 : if (aosTokens.size() == 2)
7432 : {
7433 9 : const char *pszDimName = aosTokens[0];
7434 9 : bool bValidName = true;
7435 9 : if (STARTS_WITH(pszDimName, "_nc4_non_coord_"))
7436 : {
7437 : // This is an internal netcdf prefix. Using it may
7438 : // cause memory leaks.
7439 0 : bValidName = false;
7440 : }
7441 9 : if (!bValidName)
7442 : {
7443 0 : CPLDebug("netCDF",
7444 : "nc_def_dim(%s) failed: invalid name found",
7445 : pszDimName);
7446 0 : continue;
7447 : }
7448 :
7449 : const bool bIsASCII =
7450 9 : CPLIsASCII(pszDimName, static_cast<size_t>(-1));
7451 9 : if (!bIsASCII)
7452 : {
7453 : // Workaround https://github.com/Unidata/netcdf-c/pull/450
7454 0 : CPLDebug("netCDF",
7455 : "nc_def_dim(%s) failed: rejected because "
7456 : "of non-ASCII characters",
7457 : pszDimName);
7458 0 : continue;
7459 : }
7460 9 : int nDimSize = EQUAL(aosTokens[1], "UNLIMITED")
7461 : ? NC_UNLIMITED
7462 9 : : atoi(aosTokens[1]);
7463 9 : if (nDimSize >= 1000)
7464 1 : nDimSize = 1000; // to avoid very long processing
7465 9 : if (nDimSize >= 0)
7466 : {
7467 9 : int nDimId = -1;
7468 9 : status = nc_def_dim(nCdfId, pszDimName, nDimSize, &nDimId);
7469 9 : if (status != NC_NOERR)
7470 : {
7471 0 : CPLDebug("netCDF", "nc_def_dim(%s, %d) failed: %s",
7472 : pszDimName, nDimSize, nc_strerror(status));
7473 : }
7474 : else
7475 : {
7476 : #ifdef DEBUG_VERBOSE
7477 : CPLDebug("netCDF", "nc_def_dim(%s, %d) (%s) succeeded",
7478 : pszDimName, nDimSize, pszLine);
7479 : #endif
7480 9 : oMapDimToId[pszDimName] = nDimId;
7481 9 : oMapDimIdToDimLen[nDimId] = nDimSize;
7482 : }
7483 : }
7484 : }
7485 : }
7486 183 : else if (nActiveSection == SECTION_VARIABLES)
7487 : {
7488 390 : while (*pszLine == ' ' || *pszLine == '\t')
7489 249 : pszLine++;
7490 141 : const char *pszColumn = strchr(pszLine, ':');
7491 141 : const char *pszEqual = strchr(pszLine, '=');
7492 141 : if (pszColumn == nullptr)
7493 : {
7494 : const CPLStringList aosTokens(
7495 21 : CSLTokenizeString2(pszLine, " \t=(),;", 0));
7496 21 : if (aosTokens.size() >= 2)
7497 : {
7498 17 : const char *pszVarName = aosTokens[1];
7499 17 : bool bValidName = true;
7500 17 : if (STARTS_WITH(pszVarName, "_nc4_non_coord_"))
7501 : {
7502 : // This is an internal netcdf prefix. Using it may
7503 : // cause memory leaks.
7504 0 : bValidName = false;
7505 : }
7506 138 : for (int i = 0; pszVarName[i]; i++)
7507 : {
7508 121 : if (!((pszVarName[i] >= 'a' && pszVarName[i] <= 'z') ||
7509 28 : (pszVarName[i] >= 'A' && pszVarName[i] <= 'Z') ||
7510 9 : (pszVarName[i] >= '0' && pszVarName[i] <= '9') ||
7511 6 : pszVarName[i] == '_'))
7512 : {
7513 0 : bValidName = false;
7514 : }
7515 : }
7516 17 : if (!bValidName)
7517 : {
7518 0 : CPLDebug(
7519 : "netCDF",
7520 : "nc_def_var(%s) failed: illegal character found",
7521 : pszVarName);
7522 0 : continue;
7523 : }
7524 17 : if (oMapVarToId.find(pszVarName) != oMapVarToId.end())
7525 : {
7526 0 : CPLDebug("netCDF",
7527 : "nc_def_var(%s) failed: already defined",
7528 : pszVarName);
7529 0 : continue;
7530 : }
7531 17 : const char *pszVarType = aosTokens[0];
7532 17 : int nc_datatype = NC_BYTE;
7533 17 : size_t nDataTypeSize = 1;
7534 17 : if (EQUAL(pszVarType, "char"))
7535 : {
7536 6 : nc_datatype = NC_CHAR;
7537 6 : nDataTypeSize = 1;
7538 : }
7539 11 : else if (EQUAL(pszVarType, "byte"))
7540 : {
7541 3 : nc_datatype = NC_BYTE;
7542 3 : nDataTypeSize = 1;
7543 : }
7544 8 : else if (EQUAL(pszVarType, "short"))
7545 : {
7546 0 : nc_datatype = NC_SHORT;
7547 0 : nDataTypeSize = 2;
7548 : }
7549 8 : else if (EQUAL(pszVarType, "int"))
7550 : {
7551 0 : nc_datatype = NC_INT;
7552 0 : nDataTypeSize = 4;
7553 : }
7554 8 : else if (EQUAL(pszVarType, "float"))
7555 : {
7556 0 : nc_datatype = NC_FLOAT;
7557 0 : nDataTypeSize = 4;
7558 : }
7559 8 : else if (EQUAL(pszVarType, "double"))
7560 : {
7561 8 : nc_datatype = NC_DOUBLE;
7562 8 : nDataTypeSize = 8;
7563 : }
7564 0 : else if (EQUAL(pszVarType, "ubyte"))
7565 : {
7566 0 : nc_datatype = NC_UBYTE;
7567 0 : nDataTypeSize = 1;
7568 : }
7569 0 : else if (EQUAL(pszVarType, "ushort"))
7570 : {
7571 0 : nc_datatype = NC_USHORT;
7572 0 : nDataTypeSize = 2;
7573 : }
7574 0 : else if (EQUAL(pszVarType, "uint"))
7575 : {
7576 0 : nc_datatype = NC_UINT;
7577 0 : nDataTypeSize = 4;
7578 : }
7579 0 : else if (EQUAL(pszVarType, "int64"))
7580 : {
7581 0 : nc_datatype = NC_INT64;
7582 0 : nDataTypeSize = 8;
7583 : }
7584 0 : else if (EQUAL(pszVarType, "uint64"))
7585 : {
7586 0 : nc_datatype = NC_UINT64;
7587 0 : nDataTypeSize = 8;
7588 : }
7589 :
7590 17 : int nDims = aosTokens.size() - 2;
7591 17 : if (nDims >= 32)
7592 : {
7593 : // The number of dimensions in a netCDFv4 file is
7594 : // limited by #define H5S_MAX_RANK 32
7595 : // but libnetcdf doesn't check that...
7596 0 : CPLDebug("netCDF",
7597 : "nc_def_var(%s) failed: too many dimensions",
7598 : pszVarName);
7599 0 : continue;
7600 : }
7601 17 : std::vector<int> aoDimIds;
7602 17 : bool bFailed = false;
7603 17 : size_t nSize = 1;
7604 35 : for (int i = 0; i < nDims; i++)
7605 : {
7606 18 : const char *pszDimName = aosTokens[2 + i];
7607 18 : if (oMapDimToId.find(pszDimName) == oMapDimToId.end())
7608 : {
7609 0 : bFailed = true;
7610 0 : break;
7611 : }
7612 18 : const int nDimId = oMapDimToId[pszDimName];
7613 18 : aoDimIds.push_back(nDimId);
7614 :
7615 18 : const size_t nDimSize = oMapDimIdToDimLen[nDimId];
7616 18 : if (nDimSize != 0)
7617 : {
7618 18 : if (nSize >
7619 18 : std::numeric_limits<size_t>::max() / nDimSize)
7620 : {
7621 0 : bFailed = true;
7622 0 : break;
7623 : }
7624 : else
7625 : {
7626 18 : nSize *= nDimSize;
7627 : }
7628 : }
7629 : }
7630 17 : if (bFailed)
7631 : {
7632 0 : CPLDebug("netCDF",
7633 : "nc_def_var(%s) failed: unknown dimension(s)",
7634 : pszVarName);
7635 0 : continue;
7636 : }
7637 17 : if (nSize > 100U * 1024 * 1024 / nDataTypeSize)
7638 : {
7639 0 : CPLDebug("netCDF",
7640 : "nc_def_var(%s) failed: too large data",
7641 : pszVarName);
7642 0 : continue;
7643 : }
7644 17 : if (nTotalVarSize >
7645 34 : std::numeric_limits<size_t>::max() - nSize ||
7646 17 : nTotalVarSize + nSize > 100 * 1024 * 1024)
7647 : {
7648 0 : CPLDebug("netCDF",
7649 : "nc_def_var(%s) failed: too large data",
7650 : pszVarName);
7651 0 : continue;
7652 : }
7653 17 : nTotalVarSize += nSize;
7654 :
7655 17 : int nVarId = -1;
7656 : status =
7657 30 : nc_def_var(nCdfId, pszVarName, nc_datatype, nDims,
7658 13 : (nDims) ? &aoDimIds[0] : nullptr, &nVarId);
7659 17 : if (status != NC_NOERR)
7660 : {
7661 0 : CPLDebug("netCDF", "nc_def_var(%s) failed: %s",
7662 : pszVarName, nc_strerror(status));
7663 : }
7664 : else
7665 : {
7666 : #ifdef DEBUG_VERBOSE
7667 : CPLDebug("netCDF", "nc_def_var(%s) (%s) succeeded",
7668 : pszVarName, pszLine);
7669 : #endif
7670 17 : oMapVarToId[pszVarName] = nVarId;
7671 17 : oMapVarIdToType[nVarId] = nc_datatype;
7672 17 : oMapVarIdToVectorOfDimId[nVarId] = std::move(aoDimIds);
7673 : }
7674 : }
7675 : }
7676 120 : else if (pszEqual != nullptr && pszEqual - pszColumn > 0)
7677 : {
7678 116 : CPLString osVarName(pszLine, pszColumn - pszLine);
7679 116 : CPLString osAttrName(pszColumn + 1, pszEqual - pszColumn - 1);
7680 116 : osAttrName.Trim();
7681 116 : if (oMapVarToId.find(osVarName) == oMapVarToId.end())
7682 : {
7683 0 : CPLDebug("netCDF",
7684 : "nc_put_att(%s:%s) failed: "
7685 : "no corresponding variable",
7686 : osVarName.c_str(), osAttrName.c_str());
7687 0 : continue;
7688 : }
7689 116 : bool bValidName = true;
7690 1743 : for (size_t i = 0; i < osAttrName.size(); i++)
7691 : {
7692 1865 : if (!((osAttrName[i] >= 'a' && osAttrName[i] <= 'z') ||
7693 238 : (osAttrName[i] >= 'A' && osAttrName[i] <= 'Z') ||
7694 158 : (osAttrName[i] >= '0' && osAttrName[i] <= '9') ||
7695 158 : osAttrName[i] == '_'))
7696 : {
7697 0 : bValidName = false;
7698 : }
7699 : }
7700 116 : if (!bValidName)
7701 : {
7702 0 : CPLDebug(
7703 : "netCDF",
7704 : "nc_put_att(%s:%s) failed: illegal character found",
7705 : osVarName.c_str(), osAttrName.c_str());
7706 0 : continue;
7707 : }
7708 116 : if (oSetAttrDefined.find(osVarName + ":" + osAttrName) !=
7709 232 : oSetAttrDefined.end())
7710 : {
7711 0 : CPLDebug("netCDF",
7712 : "nc_put_att(%s:%s) failed: already defined",
7713 : osVarName.c_str(), osAttrName.c_str());
7714 0 : continue;
7715 : }
7716 :
7717 116 : const int nVarId = oMapVarToId[osVarName];
7718 116 : const char *pszValue = pszEqual + 1;
7719 232 : while (*pszValue == ' ')
7720 116 : pszValue++;
7721 :
7722 116 : status = NC_EBADTYPE;
7723 116 : if (*pszValue == '"')
7724 : {
7725 : // For _FillValue, the attribute type should match
7726 : // the variable type. Leaks memory with NC4 otherwise
7727 74 : if (osAttrName == "_FillValue")
7728 : {
7729 0 : CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
7730 : osVarName.c_str(), osAttrName.c_str(),
7731 : nc_strerror(status));
7732 0 : continue;
7733 : }
7734 :
7735 : // Unquote and unescape string value
7736 74 : CPLString osVal(pszValue + 1);
7737 222 : while (!osVal.empty())
7738 : {
7739 222 : if (osVal.back() == ';' || osVal.back() == ' ')
7740 : {
7741 148 : osVal.pop_back();
7742 : }
7743 74 : else if (osVal.back() == '"')
7744 : {
7745 74 : osVal.pop_back();
7746 74 : break;
7747 : }
7748 : else
7749 : {
7750 0 : break;
7751 : }
7752 : }
7753 74 : osVal.replaceAll("\\\"", '"');
7754 74 : status = nc_put_att_text(nCdfId, nVarId, osAttrName,
7755 : osVal.size(), osVal.c_str());
7756 : }
7757 : else
7758 : {
7759 84 : CPLString osVal(pszValue);
7760 126 : while (!osVal.empty())
7761 : {
7762 126 : if (osVal.back() == ';' || osVal.back() == ' ')
7763 : {
7764 84 : osVal.pop_back();
7765 : }
7766 : else
7767 : {
7768 42 : break;
7769 : }
7770 : }
7771 42 : int nc_datatype = -1;
7772 42 : if (!osVal.empty() && osVal.back() == 'b')
7773 : {
7774 3 : nc_datatype = NC_BYTE;
7775 3 : osVal.pop_back();
7776 : }
7777 39 : else if (!osVal.empty() && osVal.back() == 's')
7778 : {
7779 3 : nc_datatype = NC_SHORT;
7780 3 : osVal.pop_back();
7781 : }
7782 42 : if (CPLGetValueType(osVal) == CPL_VALUE_INTEGER)
7783 : {
7784 7 : if (nc_datatype < 0)
7785 4 : nc_datatype = NC_INT;
7786 : }
7787 35 : else if (CPLGetValueType(osVal) == CPL_VALUE_REAL)
7788 : {
7789 32 : nc_datatype = NC_DOUBLE;
7790 : }
7791 : else
7792 : {
7793 3 : nc_datatype = -1;
7794 : }
7795 :
7796 : // For _FillValue, check that the attribute type matches
7797 : // the variable type. Leaks memory with NC4 otherwise
7798 42 : if (osAttrName == "_FillValue")
7799 : {
7800 6 : if (nVarId < 0 ||
7801 3 : nc_datatype != oMapVarIdToType[nVarId])
7802 : {
7803 0 : nc_datatype = -1;
7804 : }
7805 : }
7806 :
7807 42 : if (nc_datatype == NC_BYTE)
7808 : {
7809 : signed char chVal =
7810 3 : static_cast<signed char>(atoi(osVal));
7811 3 : status = nc_put_att_schar(nCdfId, nVarId, osAttrName,
7812 : NC_BYTE, 1, &chVal);
7813 : }
7814 39 : else if (nc_datatype == NC_SHORT)
7815 : {
7816 0 : short nVal = static_cast<short>(atoi(osVal));
7817 0 : status = nc_put_att_short(nCdfId, nVarId, osAttrName,
7818 : NC_SHORT, 1, &nVal);
7819 : }
7820 39 : else if (nc_datatype == NC_INT)
7821 : {
7822 4 : int nVal = static_cast<int>(atoi(osVal));
7823 4 : status = nc_put_att_int(nCdfId, nVarId, osAttrName,
7824 : NC_INT, 1, &nVal);
7825 : }
7826 35 : else if (nc_datatype == NC_DOUBLE)
7827 : {
7828 32 : double dfVal = CPLAtof(osVal);
7829 32 : status = nc_put_att_double(nCdfId, nVarId, osAttrName,
7830 : NC_DOUBLE, 1, &dfVal);
7831 : }
7832 : }
7833 116 : if (status != NC_NOERR)
7834 : {
7835 3 : CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
7836 : osVarName.c_str(), osAttrName.c_str(),
7837 : nc_strerror(status));
7838 : }
7839 : else
7840 : {
7841 113 : oSetAttrDefined.insert(osVarName + ":" + osAttrName);
7842 : #ifdef DEBUG_VERBOSE
7843 : CPLDebug("netCDF", "nc_put_att_(%s:%s) (%s) succeeded",
7844 : osVarName.c_str(), osAttrName.c_str(), pszLine);
7845 : #endif
7846 : }
7847 : }
7848 : }
7849 42 : else if (nActiveSection == SECTION_DATA)
7850 : {
7851 55 : while (*pszLine == ' ' || *pszLine == '\t')
7852 17 : pszLine++;
7853 38 : const char *pszEqual = strchr(pszLine, '=');
7854 38 : if (pszEqual)
7855 : {
7856 17 : CPLString osVarName(pszLine, pszEqual - pszLine);
7857 17 : osVarName.Trim();
7858 17 : if (oMapVarToId.find(osVarName) == oMapVarToId.end())
7859 0 : continue;
7860 17 : const int nVarId = oMapVarToId[osVarName];
7861 17 : CPLString osAccVal(pszEqual + 1);
7862 17 : osAccVal.Trim();
7863 153 : while (osAccVal.empty() || osAccVal.back() != ';')
7864 : {
7865 136 : pszLine = CPLReadLineL(fpSrc);
7866 136 : if (pszLine == nullptr)
7867 0 : break;
7868 272 : CPLString osVal(pszLine);
7869 136 : osVal.Trim();
7870 136 : osAccVal += osVal;
7871 : }
7872 17 : if (pszLine == nullptr)
7873 0 : break;
7874 17 : osAccVal.pop_back();
7875 :
7876 : const std::vector<int> aoDimIds =
7877 34 : oMapVarIdToVectorOfDimId[nVarId];
7878 17 : size_t nSize = 1;
7879 34 : std::vector<size_t> aoStart, aoEdge;
7880 17 : aoStart.resize(aoDimIds.size());
7881 17 : aoEdge.resize(aoDimIds.size());
7882 35 : for (size_t i = 0; i < aoDimIds.size(); ++i)
7883 : {
7884 18 : const size_t nDimSize = oMapDimIdToDimLen[aoDimIds[i]];
7885 36 : if (nDimSize != 0 &&
7886 18 : nSize > std::numeric_limits<size_t>::max() / nDimSize)
7887 : {
7888 0 : nSize = 0;
7889 : }
7890 : else
7891 : {
7892 18 : nSize *= nDimSize;
7893 : }
7894 18 : aoStart[i] = 0;
7895 18 : aoEdge[i] = nDimSize;
7896 : }
7897 :
7898 17 : status = NC_EBADTYPE;
7899 17 : if (nSize == 0)
7900 : {
7901 : // Might happen with an unlimited dimension
7902 : }
7903 17 : else if (oMapVarIdToType[nVarId] == NC_DOUBLE)
7904 : {
7905 8 : if (!aoStart.empty())
7906 : {
7907 : const CPLStringList aosTokens(
7908 16 : CSLTokenizeString2(osAccVal, " ,;", 0));
7909 8 : size_t nTokens = aosTokens.size();
7910 8 : if (nTokens >= nSize)
7911 : {
7912 : double *padfVals = static_cast<double *>(
7913 8 : VSI_CALLOC_VERBOSE(nSize, sizeof(double)));
7914 8 : if (padfVals)
7915 : {
7916 132 : for (size_t i = 0; i < nSize; i++)
7917 : {
7918 124 : padfVals[i] = CPLAtof(aosTokens[i]);
7919 : }
7920 8 : status = nc_put_vara_double(
7921 8 : nCdfId, nVarId, &aoStart[0], &aoEdge[0],
7922 : padfVals);
7923 8 : VSIFree(padfVals);
7924 : }
7925 : }
7926 : }
7927 : }
7928 9 : else if (oMapVarIdToType[nVarId] == NC_BYTE)
7929 : {
7930 3 : if (!aoStart.empty())
7931 : {
7932 : const CPLStringList aosTokens(
7933 6 : CSLTokenizeString2(osAccVal, " ,;", 0));
7934 3 : size_t nTokens = aosTokens.size();
7935 3 : if (nTokens >= nSize)
7936 : {
7937 : signed char *panVals = static_cast<signed char *>(
7938 3 : VSI_CALLOC_VERBOSE(nSize, sizeof(signed char)));
7939 3 : if (panVals)
7940 : {
7941 1203 : for (size_t i = 0; i < nSize; i++)
7942 : {
7943 1200 : panVals[i] = static_cast<signed char>(
7944 1200 : atoi(aosTokens[i]));
7945 : }
7946 3 : status = nc_put_vara_schar(nCdfId, nVarId,
7947 3 : &aoStart[0],
7948 3 : &aoEdge[0], panVals);
7949 3 : VSIFree(panVals);
7950 : }
7951 : }
7952 : }
7953 : }
7954 6 : else if (oMapVarIdToType[nVarId] == NC_CHAR)
7955 : {
7956 6 : if (aoStart.size() == 2)
7957 : {
7958 4 : std::vector<CPLString> aoStrings;
7959 2 : bool bInString = false;
7960 4 : CPLString osCurString;
7961 935 : for (size_t i = 0; i < osAccVal.size();)
7962 : {
7963 933 : if (!bInString)
7964 : {
7965 8 : if (osAccVal[i] == '"')
7966 : {
7967 4 : bInString = true;
7968 4 : osCurString.clear();
7969 : }
7970 8 : i++;
7971 : }
7972 926 : else if (osAccVal[i] == '\\' &&
7973 926 : i + 1 < osAccVal.size() &&
7974 1 : osAccVal[i + 1] == '"')
7975 : {
7976 1 : osCurString += '"';
7977 1 : i += 2;
7978 : }
7979 924 : else if (osAccVal[i] == '"')
7980 : {
7981 4 : aoStrings.push_back(osCurString);
7982 4 : osCurString.clear();
7983 4 : bInString = false;
7984 4 : i++;
7985 : }
7986 : else
7987 : {
7988 920 : osCurString += osAccVal[i];
7989 920 : i++;
7990 : }
7991 : }
7992 2 : const size_t nRecords = oMapDimIdToDimLen[aoDimIds[0]];
7993 2 : const size_t nWidth = oMapDimIdToDimLen[aoDimIds[1]];
7994 2 : size_t nIters = aoStrings.size();
7995 2 : if (nIters > nRecords)
7996 0 : nIters = nRecords;
7997 6 : for (size_t i = 0; i < nIters; i++)
7998 : {
7999 : size_t anIndex[2];
8000 4 : anIndex[0] = i;
8001 4 : anIndex[1] = 0;
8002 : size_t anCount[2];
8003 4 : anCount[0] = 1;
8004 4 : anCount[1] = aoStrings[i].size();
8005 4 : if (anCount[1] > nWidth)
8006 0 : anCount[1] = nWidth;
8007 : status =
8008 4 : nc_put_vara_text(nCdfId, nVarId, anIndex,
8009 4 : anCount, aoStrings[i].c_str());
8010 4 : if (status != NC_NOERR)
8011 0 : break;
8012 : }
8013 : }
8014 : }
8015 17 : if (status != NC_NOERR)
8016 : {
8017 4 : CPLDebug("netCDF", "nc_put_var_(%s) failed: %s",
8018 : osVarName.c_str(), nc_strerror(status));
8019 : }
8020 : }
8021 : }
8022 : }
8023 :
8024 4 : GDAL_nc_close(nCdfId);
8025 4 : return true;
8026 : }
8027 :
8028 : #endif // ENABLE_NCDUMP
8029 :
8030 : /************************************************************************/
8031 : /* Open() */
8032 : /************************************************************************/
8033 :
8034 798 : GDALDataset *netCDFDataset::Open(GDALOpenInfo *poOpenInfo)
8035 :
8036 : {
8037 : #ifdef NCDF_DEBUG
8038 : CPLDebug("GDAL_netCDF", "\n=====\nOpen(), filename=[%s]",
8039 : poOpenInfo->pszFilename);
8040 : #endif
8041 :
8042 : // Does this appear to be a netcdf file?
8043 798 : NetCDFFormatEnum eTmpFormat = NCDF_FORMAT_NONE;
8044 798 : if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
8045 : {
8046 734 : eTmpFormat = netCDFIdentifyFormat(poOpenInfo, /* bCheckExt = */ true);
8047 : #ifdef NCDF_DEBUG
8048 : CPLDebug("GDAL_netCDF", "identified format %d", eTmpFormat);
8049 : #endif
8050 : // Note: not calling Identify() directly, because we want the file type.
8051 : // Only support NCDF_FORMAT* formats.
8052 734 : if (NCDF_FORMAT_NC == eTmpFormat || NCDF_FORMAT_NC2 == eTmpFormat ||
8053 2 : NCDF_FORMAT_NC4 == eTmpFormat || NCDF_FORMAT_NC4C == eTmpFormat)
8054 : {
8055 : // ok
8056 : }
8057 2 : else if (eTmpFormat == NCDF_FORMAT_HDF4 &&
8058 0 : poOpenInfo->IsSingleAllowedDriver("netCDF"))
8059 : {
8060 : // ok
8061 : }
8062 : else
8063 : {
8064 2 : return nullptr;
8065 : }
8066 : }
8067 : else
8068 : {
8069 : #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
8070 : // We don't necessarily want to catch bugs in libnetcdf ...
8071 : if (CPLGetConfigOption("DISABLE_OPEN_REAL_NETCDF_FILES", nullptr))
8072 : {
8073 : return nullptr;
8074 : }
8075 : #endif
8076 : }
8077 :
8078 796 : if (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER)
8079 : {
8080 281 : return OpenMultiDim(poOpenInfo);
8081 : }
8082 :
8083 1030 : CPLMutexHolderD(&hNCMutex);
8084 :
8085 515 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
8086 : // GDALDataset own mutex.
8087 515 : netCDFDataset *poDS = new netCDFDataset();
8088 515 : poDS->papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
8089 515 : CPLAcquireMutex(hNCMutex, 1000.0);
8090 :
8091 515 : poDS->SetDescription(poOpenInfo->pszFilename);
8092 :
8093 : // Check if filename start with NETCDF: tag.
8094 515 : bool bTreatAsSubdataset = false;
8095 1030 : CPLString osSubdatasetName;
8096 :
8097 : #ifdef ENABLE_NCDUMP
8098 515 : const char *pszHeader =
8099 : reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
8100 515 : if (poOpenInfo->fpL != nullptr && STARTS_WITH(pszHeader, "netcdf ") &&
8101 3 : strstr(pszHeader, "dimensions:") && strstr(pszHeader, "variables:"))
8102 : {
8103 : // By default create a temporary file that will be destroyed,
8104 : // unless NETCDF_TMP_FILE is defined. Can be useful to see which
8105 : // netCDF file has been generated from a potential fuzzed input.
8106 3 : poDS->osFilename = CPLGetConfigOption("NETCDF_TMP_FILE", "");
8107 3 : if (poDS->osFilename.empty())
8108 : {
8109 3 : poDS->bFileToDestroyAtClosing = true;
8110 3 : poDS->osFilename = CPLGenerateTempFilenameSafe("netcdf_tmp");
8111 : }
8112 3 : if (!netCDFDatasetCreateTempFile(eTmpFormat, poDS->osFilename,
8113 : poOpenInfo->fpL))
8114 : {
8115 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8116 : // deadlock with GDALDataset own mutex.
8117 0 : delete poDS;
8118 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8119 0 : return nullptr;
8120 : }
8121 3 : bTreatAsSubdataset = false;
8122 3 : poDS->eFormat = eTmpFormat;
8123 : }
8124 : else
8125 : #endif
8126 :
8127 512 : if (STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
8128 : {
8129 : GDALSubdatasetInfoH hInfo =
8130 64 : GDALGetSubdatasetInfo(poOpenInfo->pszFilename);
8131 64 : if (hInfo)
8132 : {
8133 64 : char *pszPath = GDALSubdatasetInfoGetPathComponent(hInfo);
8134 64 : poDS->osFilename = pszPath;
8135 64 : CPLFree(pszPath);
8136 :
8137 : char *pszSubdataset =
8138 64 : GDALSubdatasetInfoGetSubdatasetComponent(hInfo);
8139 64 : if (pszSubdataset && pszSubdataset[0] != '\0')
8140 : {
8141 64 : osSubdatasetName = pszSubdataset;
8142 64 : bTreatAsSubdataset = true;
8143 : }
8144 : else
8145 : {
8146 0 : osSubdatasetName = "";
8147 0 : bTreatAsSubdataset = false;
8148 : }
8149 64 : CPLFree(pszSubdataset);
8150 :
8151 64 : GDALDestroySubdatasetInfo(hInfo);
8152 : }
8153 :
8154 128 : if (!STARTS_WITH(poDS->osFilename, "http://") &&
8155 64 : !STARTS_WITH(poDS->osFilename, "https://"))
8156 : {
8157 : // Identify Format from real file, with bCheckExt=FALSE.
8158 : auto poOpenInfo2 = std::make_unique<GDALOpenInfo>(
8159 64 : poDS->osFilename.c_str(), GA_ReadOnly);
8160 64 : poDS->eFormat = netCDFIdentifyFormat(poOpenInfo2.get(),
8161 : /* bCheckExt = */ false);
8162 64 : if (NCDF_FORMAT_NONE == poDS->eFormat ||
8163 64 : NCDF_FORMAT_UNKNOWN == poDS->eFormat)
8164 : {
8165 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8166 : // deadlock with GDALDataset own mutex.
8167 0 : delete poDS;
8168 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8169 0 : return nullptr;
8170 : }
8171 : }
8172 : }
8173 : else
8174 : {
8175 448 : poDS->osFilename = poOpenInfo->pszFilename;
8176 448 : bTreatAsSubdataset = false;
8177 448 : poDS->eFormat = eTmpFormat;
8178 : }
8179 :
8180 : // Try opening the dataset.
8181 : #if defined(NCDF_DEBUG) && defined(ENABLE_UFFD)
8182 : CPLDebug("GDAL_netCDF", "calling nc_open_mem(%s)",
8183 : poDS->osFilename.c_str());
8184 : #elif defined(NCDF_DEBUG) && !defined(ENABLE_UFFD)
8185 : CPLDebug("GDAL_netCDF", "calling nc_open(%s)", poDS->osFilename.c_str());
8186 : #endif
8187 515 : int cdfid = -1;
8188 515 : const int nMode = ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE) != 0)
8189 : ? NC_WRITE
8190 : : NC_NOWRITE;
8191 1030 : CPLString osFilenameForNCOpen(poDS->osFilename);
8192 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
8193 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
8194 : {
8195 : char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
8196 : osFilenameForNCOpen = pszTemp;
8197 : CPLFree(pszTemp);
8198 : }
8199 : #endif
8200 515 : int status2 = -1;
8201 :
8202 : #ifdef ENABLE_UFFD
8203 515 : cpl_uffd_context *pCtx = nullptr;
8204 : #endif
8205 :
8206 530 : if (STARTS_WITH(osFilenameForNCOpen, "/vsimem/") &&
8207 15 : poOpenInfo->eAccess == GA_ReadOnly)
8208 : {
8209 15 : vsi_l_offset nLength = 0;
8210 15 : poDS->fpVSIMEM = VSIFOpenL(osFilenameForNCOpen, "rb");
8211 15 : if (poDS->fpVSIMEM)
8212 : {
8213 : // We assume that the file will not be modified. If it is, then
8214 : // pabyBuffer might become invalid.
8215 : GByte *pabyBuffer =
8216 15 : VSIGetMemFileBuffer(osFilenameForNCOpen, &nLength, false);
8217 15 : if (pabyBuffer)
8218 : {
8219 15 : status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen),
8220 : nMode, static_cast<size_t>(nLength),
8221 : pabyBuffer, &cdfid);
8222 : }
8223 : }
8224 : }
8225 : else
8226 : {
8227 : const bool bVsiFile =
8228 500 : !strncmp(osFilenameForNCOpen, "/vsi", strlen("/vsi"));
8229 : #ifdef ENABLE_UFFD
8230 500 : bool bReadOnly = (poOpenInfo->eAccess == GA_ReadOnly);
8231 500 : void *pVma = nullptr;
8232 500 : uint64_t nVmaSize = 0;
8233 :
8234 500 : if (bVsiFile)
8235 : {
8236 2 : if (bReadOnly)
8237 : {
8238 2 : if (CPLIsUserFaultMappingSupported())
8239 : {
8240 2 : pCtx = CPLCreateUserFaultMapping(osFilenameForNCOpen, &pVma,
8241 : &nVmaSize);
8242 : }
8243 : else
8244 : {
8245 0 : CPLError(CE_Failure, CPLE_AppDefined,
8246 : "Opening a /vsi file with the netCDF driver "
8247 : "requires Linux userfaultfd to be available. "
8248 : "If running from Docker, "
8249 : "--security-opt seccomp=unconfined might be "
8250 : "needed.%s",
8251 0 : ((poDS->eFormat == NCDF_FORMAT_NC4 ||
8252 0 : poDS->eFormat == NCDF_FORMAT_HDF5) &&
8253 0 : GDALGetDriverByName("HDF5"))
8254 : ? " Or you may set the GDAL_SKIP=netCDF "
8255 : "configuration option to force the use of "
8256 : "the HDF5 driver."
8257 : : "");
8258 : }
8259 : }
8260 : else
8261 : {
8262 0 : CPLError(CE_Failure, CPLE_AppDefined,
8263 : "Opening a /vsi file with the netCDF driver is only "
8264 : "supported in read-only mode");
8265 : }
8266 : }
8267 500 : if (pCtx != nullptr && pVma != nullptr && nVmaSize > 0)
8268 : {
8269 : // netCDF code, at least for netCDF 4.7.0, is confused by filenames
8270 : // like /vsicurl/http[s]://example.com/foo.nc, so just pass the
8271 : // final part
8272 2 : status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen), nMode,
8273 : static_cast<size_t>(nVmaSize), pVma, &cdfid);
8274 : }
8275 : else
8276 498 : status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
8277 : #else
8278 : if (bVsiFile)
8279 : {
8280 : CPLError(
8281 : CE_Failure, CPLE_AppDefined,
8282 : "Opening a /vsi file with the netCDF driver requires Linux "
8283 : "userfaultfd to be available.%s",
8284 : ((poDS->eFormat == NCDF_FORMAT_NC4 ||
8285 : poDS->eFormat == NCDF_FORMAT_HDF5) &&
8286 : GDALGetDriverByName("HDF5"))
8287 : ? " Or you may set the GDAL_SKIP=netCDF "
8288 : "configuration option to force the use of the HDF5 "
8289 : "driver."
8290 : : "");
8291 : status2 = NC_EIO;
8292 : }
8293 : else
8294 : {
8295 : status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
8296 : }
8297 : #endif
8298 : }
8299 515 : if (status2 != NC_NOERR)
8300 : {
8301 : #ifdef NCDF_DEBUG
8302 : CPLDebug("GDAL_netCDF", "error opening");
8303 : #endif
8304 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8305 : // with GDALDataset own mutex.
8306 0 : delete poDS;
8307 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8308 0 : return nullptr;
8309 : }
8310 : #ifdef NCDF_DEBUG
8311 : CPLDebug("GDAL_netCDF", "got cdfid=%d", cdfid);
8312 : #endif
8313 :
8314 : #if defined(ENABLE_NCDUMP) && !defined(_WIN32)
8315 : // Try to destroy the temporary file right now on Unix
8316 515 : if (poDS->bFileToDestroyAtClosing)
8317 : {
8318 3 : if (VSIUnlink(poDS->osFilename) == 0)
8319 : {
8320 3 : poDS->bFileToDestroyAtClosing = false;
8321 : }
8322 : }
8323 : #endif
8324 :
8325 : // Is this a real netCDF file?
8326 : int ndims;
8327 : int ngatts;
8328 : int nvars;
8329 : int unlimdimid;
8330 515 : int status = nc_inq(cdfid, &ndims, &nvars, &ngatts, &unlimdimid);
8331 515 : if (status != NC_NOERR)
8332 : {
8333 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8334 : // with GDALDataset own mutex.
8335 0 : delete poDS;
8336 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8337 0 : return nullptr;
8338 : }
8339 :
8340 : // Get file type from netcdf.
8341 515 : int nTmpFormat = NCDF_FORMAT_NONE;
8342 515 : status = nc_inq_format(cdfid, &nTmpFormat);
8343 515 : if (status != NC_NOERR)
8344 : {
8345 0 : NCDF_ERR(status);
8346 : }
8347 : else
8348 : {
8349 515 : CPLDebug("GDAL_netCDF",
8350 : "driver detected file type=%d, libnetcdf detected type=%d",
8351 515 : poDS->eFormat, nTmpFormat);
8352 515 : if (static_cast<NetCDFFormatEnum>(nTmpFormat) != poDS->eFormat)
8353 : {
8354 : // Warn if file detection conflicts with that from libnetcdf
8355 : // except for NC4C, which we have no way of detecting initially.
8356 26 : if (nTmpFormat != NCDF_FORMAT_NC4C &&
8357 13 : !STARTS_WITH(poDS->osFilename, "http://") &&
8358 0 : !STARTS_WITH(poDS->osFilename, "https://"))
8359 : {
8360 0 : CPLError(CE_Warning, CPLE_AppDefined,
8361 : "NetCDF driver detected file type=%d, but libnetcdf "
8362 : "detected type=%d",
8363 0 : poDS->eFormat, nTmpFormat);
8364 : }
8365 13 : CPLDebug("GDAL_netCDF", "setting file type to %d, was %d",
8366 13 : nTmpFormat, poDS->eFormat);
8367 13 : poDS->eFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
8368 : }
8369 : }
8370 :
8371 : // Does the request variable exist?
8372 515 : if (bTreatAsSubdataset)
8373 : {
8374 : int dummy;
8375 64 : if (NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &dummy,
8376 64 : &dummy) != CE_None)
8377 : {
8378 0 : CPLError(CE_Warning, CPLE_AppDefined,
8379 : "%s is a netCDF file, but %s is not a variable.",
8380 : poOpenInfo->pszFilename, osSubdatasetName.c_str());
8381 :
8382 0 : GDAL_nc_close(cdfid);
8383 : #ifdef ENABLE_UFFD
8384 0 : NETCDF_UFFD_UNMAP(pCtx);
8385 : #endif
8386 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8387 : // deadlock with GDALDataset own mutex.
8388 0 : delete poDS;
8389 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8390 0 : return nullptr;
8391 : }
8392 : }
8393 :
8394 : // Figure out whether or not the listed dataset has support for simple
8395 : // geometries (CF-1.8)
8396 515 : poDS->nCFVersion = nccfdriver::getCFVersion(cdfid);
8397 515 : bool bHasSimpleGeometries = false; // but not necessarily valid
8398 515 : if (poDS->nCFVersion >= 1.8)
8399 : {
8400 75 : bHasSimpleGeometries = poDS->DetectAndFillSGLayers(cdfid);
8401 75 : if (bHasSimpleGeometries)
8402 : {
8403 67 : poDS->bSGSupport = true;
8404 67 : poDS->vcdf.enableFullVirtualMode();
8405 : }
8406 : }
8407 :
8408 1030 : std::string osConventions;
8409 515 : if (NCDFGetAttr(cdfid, NC_GLOBAL, "Conventions", osConventions) != CE_None)
8410 : {
8411 59 : CPLDebug("GDAL_netCDF", "No UNIDATA NC_GLOBAL:Conventions attribute");
8412 : // Note that 'Conventions' is always capital 'C' in CF spec.
8413 : }
8414 :
8415 : // Create band information objects.
8416 515 : CPLDebug("GDAL_netCDF", "var_count = %d", nvars);
8417 :
8418 : // Create a corresponding GDALDataset.
8419 : // Create Netcdf Subdataset if filename as NETCDF tag.
8420 515 : poDS->cdfid = cdfid;
8421 : #ifdef ENABLE_UFFD
8422 515 : poDS->pCtx = pCtx;
8423 : #endif
8424 515 : poDS->eAccess = poOpenInfo->eAccess;
8425 515 : poDS->bDefineMode = false;
8426 :
8427 515 : poDS->ReadAttributes(cdfid, NC_GLOBAL);
8428 :
8429 : // Identify coordinate and boundary variables that we should
8430 : // ignore as Raster Bands.
8431 1030 : CPLStringList aosIgnoreVars;
8432 515 : NCDFGetCoordAndBoundVarFullNames(cdfid, aosIgnoreVars);
8433 : // Filter variables to keep only valid 2+D raster bands and vector fields.
8434 515 : int nRasterVars = 0;
8435 515 : int nIgnoredVars = 0;
8436 515 : int nGroupID = -1;
8437 515 : int nVarID = -1;
8438 :
8439 : std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>
8440 1030 : oMap2DDimsToGroupAndVar;
8441 1188 : if ((poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
8442 158 : STARTS_WITH(
8443 : poDS->aosMetadata.FetchNameValueDef("NC_GLOBAL#mission_name", ""),
8444 1 : "Sentinel 3") &&
8445 1 : EQUAL(poDS->aosMetadata.FetchNameValueDef(
8446 : "NC_GLOBAL#altimeter_sensor_name", ""),
8447 673 : "SRAL") &&
8448 1 : EQUAL(poDS->aosMetadata.FetchNameValueDef(
8449 : "NC_GLOBAL#radiometer_sensor_name", ""),
8450 : "MWR"))
8451 : {
8452 1 : if (poDS->eAccess == GA_Update)
8453 : {
8454 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8455 : // deadlock with GDALDataset own mutex.
8456 0 : delete poDS;
8457 0 : return nullptr;
8458 : }
8459 1 : poDS->ProcessSentinel3_SRAL_MWR();
8460 : }
8461 : else
8462 : {
8463 514 : poDS->FilterVars(cdfid, (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0,
8464 671 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
8465 157 : !bHasSimpleGeometries,
8466 : aosIgnoreVars, &nRasterVars, &nGroupID, &nVarID,
8467 : &nIgnoredVars, oMap2DDimsToGroupAndVar);
8468 : }
8469 :
8470 515 : const bool bListAllArrays = CPLTestBool(
8471 515 : CSLFetchNameValueDef(poDS->papszOpenOptions, "LIST_ALL_ARRAYS", "NO"));
8472 :
8473 : // Case where there is no raster variable
8474 515 : if (!bListAllArrays && nRasterVars == 0 && !bTreatAsSubdataset)
8475 : {
8476 119 : poDS->GDALPamDataset::SetMetadata(poDS->aosMetadata);
8477 119 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8478 : // with GDALDataset own mutex.
8479 119 : poDS->TryLoadXML();
8480 : // If the dataset has been opened in raster mode only, exit
8481 119 : if ((poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0 &&
8482 9 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) == 0)
8483 : {
8484 4 : delete poDS;
8485 4 : poDS = nullptr;
8486 : }
8487 : // Otherwise if the dataset is opened in vector mode, that there is
8488 : // no vector layer and we are in read-only, exit too.
8489 115 : else if (poDS->GetLayerCount() == 0 &&
8490 123 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
8491 8 : poOpenInfo->eAccess == GA_ReadOnly)
8492 : {
8493 8 : delete poDS;
8494 8 : poDS = nullptr;
8495 : }
8496 119 : CPLAcquireMutex(hNCMutex, 1000.0);
8497 119 : return poDS;
8498 : }
8499 :
8500 : // We have more than one variable with 2 dimensions in the
8501 : // file, then treat this as a subdataset container dataset.
8502 396 : bool bSeveralVariablesAsBands = false;
8503 396 : if (bListAllArrays || ((nRasterVars > 1) && !bTreatAsSubdataset))
8504 : {
8505 30 : if (CPLFetchBool(poOpenInfo->papszOpenOptions, "VARIABLES_AS_BANDS",
8506 36 : false) &&
8507 6 : oMap2DDimsToGroupAndVar.size() == 1)
8508 : {
8509 6 : std::tie(nGroupID, nVarID) =
8510 12 : oMap2DDimsToGroupAndVar.begin()->second.front();
8511 6 : bSeveralVariablesAsBands = true;
8512 : }
8513 : else
8514 : {
8515 24 : poDS->CreateSubDatasetList(cdfid);
8516 24 : poDS->GDALPamDataset::SetMetadata(poDS->aosMetadata);
8517 24 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8518 : // deadlock with GDALDataset own mutex.
8519 24 : poDS->TryLoadXML();
8520 24 : CPLAcquireMutex(hNCMutex, 1000.0);
8521 24 : return poDS;
8522 : }
8523 : }
8524 :
8525 : // If we are not treating things as a subdataset, then capture
8526 : // the name of the single available variable as the subdataset.
8527 372 : if (!bTreatAsSubdataset)
8528 : {
8529 308 : NCDF_ERR(NCDFGetVarFullName(nGroupID, nVarID, osSubdatasetName));
8530 : }
8531 :
8532 : // We have ignored at least one variable, so we should report them
8533 : // as subdatasets for reference.
8534 372 : if (nIgnoredVars > 0 && !bTreatAsSubdataset)
8535 : {
8536 29 : CPLDebug("GDAL_netCDF",
8537 : "As %d variables were ignored, creating subdataset list "
8538 : "for reference. Variable #%d [%s] is the main variable",
8539 : nIgnoredVars, nVarID, osSubdatasetName.c_str());
8540 29 : poDS->CreateSubDatasetList(cdfid);
8541 : }
8542 :
8543 : // Open the NETCDF subdataset NETCDF:"filename":subdataset.
8544 372 : int var = -1;
8545 372 : NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &nGroupID, &var);
8546 : // Now we can forget the root cdfid and only use the selected group.
8547 372 : cdfid = nGroupID;
8548 372 : int nd = 0;
8549 372 : nc_inq_varndims(cdfid, var, &nd);
8550 :
8551 372 : poDS->m_anDimIds.resize(nd);
8552 :
8553 : // X, Y, Z position in array
8554 744 : std::vector<int> anBandDimPos(nd);
8555 :
8556 372 : nc_inq_vardimid(cdfid, var, poDS->m_anDimIds.data());
8557 :
8558 : // Check if somebody tried to pass a variable with less than 1D.
8559 372 : if (nd < 1)
8560 : {
8561 0 : CPLError(CE_Warning, CPLE_AppDefined,
8562 : "Variable has %d dimension(s) - not supported.", nd);
8563 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8564 : // with GDALDataset own mutex.
8565 0 : delete poDS;
8566 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8567 0 : return nullptr;
8568 : }
8569 :
8570 : // CF-1 Convention
8571 : //
8572 : // Dimensions to appear in the relative order T, then Z, then Y,
8573 : // then X to the file. All other dimensions should, whenever
8574 : // possible, be placed to the left of the spatiotemporal
8575 : // dimensions.
8576 :
8577 : // Verify that dimensions are in the {T,Z,Y,X} or {T,Z,Y,X} order
8578 : // Ideally we should detect for other ordering and act accordingly
8579 : // Only done if file has Conventions=CF-* and only prints warning
8580 : // To disable set GDAL_NETCDF_VERIFY_DIMS=NO and to use only
8581 : // attributes (not varnames) set GDAL_NETCDF_VERIFY_DIMS=STRICT
8582 : const bool bCheckDims =
8583 744 : CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES")) &&
8584 372 : STARTS_WITH_CI(osConventions.c_str(), "CF");
8585 :
8586 372 : bool bYXBandOrder = false;
8587 372 : if (nd == 3)
8588 : {
8589 : // If there's a coordinates attributes, and the variable it points to
8590 : // are 2D variables indexed by the same first and second dimension than
8591 : // our variable of interest, then it is Y,X,Band order.
8592 46 : char *pszCoordinates = nullptr;
8593 46 : if (NCDFGetAttr(cdfid, var, "coordinates", &pszCoordinates) ==
8594 63 : CE_None &&
8595 17 : pszCoordinates)
8596 : {
8597 : const CPLStringList aosCoordinates(
8598 34 : NCDFTokenizeCoordinatesAttribute(pszCoordinates));
8599 17 : if (aosCoordinates.size() == 2)
8600 : {
8601 : // Test that each variable is longitude/latitude.
8602 13 : for (int i = 0; i < aosCoordinates.size(); i++)
8603 : {
8604 13 : if (NCDFIsVarLongitude(cdfid, -1, aosCoordinates[i]) ||
8605 4 : NCDFIsVarLatitude(cdfid, -1, aosCoordinates[i]))
8606 : {
8607 9 : int nOtherGroupId = -1;
8608 9 : int nOtherVarId = -1;
8609 9 : if (NCDFResolveVar(cdfid, aosCoordinates[i],
8610 : &nOtherGroupId,
8611 9 : &nOtherVarId) == CE_None)
8612 : {
8613 9 : int coordDimCount = 0;
8614 9 : nc_inq_varndims(nOtherGroupId, nOtherVarId,
8615 : &coordDimCount);
8616 9 : if (coordDimCount == 2)
8617 : {
8618 3 : int coordDimIds[2] = {0, 0};
8619 3 : nc_inq_vardimid(nOtherGroupId, nOtherVarId,
8620 : coordDimIds);
8621 4 : if (coordDimIds[0] == poDS->m_anDimIds[0] &&
8622 1 : coordDimIds[1] == poDS->m_anDimIds[1])
8623 : {
8624 1 : bYXBandOrder = true;
8625 1 : break;
8626 : }
8627 : }
8628 : }
8629 : }
8630 : }
8631 : }
8632 : }
8633 46 : CPLFree(pszCoordinates);
8634 :
8635 46 : if (!bYXBandOrder)
8636 : {
8637 45 : char szDim0Name[NC_MAX_NAME + 1] = {};
8638 45 : char szDim1Name[NC_MAX_NAME + 1] = {};
8639 45 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[0], szDim0Name);
8640 45 : NCDF_ERR(status);
8641 45 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[1], szDim1Name);
8642 45 : NCDF_ERR(status);
8643 :
8644 45 : if (strcmp(szDim0Name, "number_of_lines") == 0 &&
8645 1 : strcmp(szDim1Name, "pixels_per_line") == 0)
8646 : {
8647 : // Like in PACE OCI products
8648 1 : bYXBandOrder = true;
8649 : }
8650 : else
8651 : {
8652 : // For example for EMIT data (https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/),
8653 : // dimension order is downtrack, crosstrack, bands
8654 44 : char szDim2Name[NC_MAX_NAME + 1] = {};
8655 44 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[2], szDim2Name);
8656 44 : NCDF_ERR(status);
8657 86 : bYXBandOrder = strcmp(szDim2Name, "bands") == 0 ||
8658 42 : strcmp(szDim2Name, "band") == 0;
8659 : }
8660 : }
8661 : }
8662 :
8663 372 : if (nd >= 2 && bCheckDims && !bYXBandOrder)
8664 : {
8665 285 : char szDimName1[NC_MAX_NAME + 1] = {};
8666 285 : char szDimName2[NC_MAX_NAME + 1] = {};
8667 285 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 1], szDimName1);
8668 285 : NCDF_ERR(status);
8669 285 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 2], szDimName2);
8670 285 : NCDF_ERR(status);
8671 461 : if (NCDFIsVarLongitude(cdfid, -1, szDimName1) == false &&
8672 176 : NCDFIsVarProjectionX(cdfid, -1, szDimName1) == false)
8673 : {
8674 4 : CPLError(CE_Warning, CPLE_AppDefined,
8675 : "dimension #%d (%s) is not a Longitude/X dimension.",
8676 : nd - 1, szDimName1);
8677 : }
8678 461 : if (NCDFIsVarLatitude(cdfid, -1, szDimName2) == false &&
8679 176 : NCDFIsVarProjectionY(cdfid, -1, szDimName2) == false)
8680 : {
8681 4 : CPLError(CE_Warning, CPLE_AppDefined,
8682 : "dimension #%d (%s) is not a Latitude/Y dimension.",
8683 : nd - 2, szDimName2);
8684 : }
8685 285 : if ((NCDFIsVarLongitude(cdfid, -1, szDimName2) ||
8686 287 : NCDFIsVarProjectionX(cdfid, -1, szDimName2)) &&
8687 2 : (NCDFIsVarLatitude(cdfid, -1, szDimName1) ||
8688 0 : NCDFIsVarProjectionY(cdfid, -1, szDimName1)))
8689 : {
8690 2 : poDS->bSwitchedXY = true;
8691 : }
8692 285 : if (nd >= 3)
8693 : {
8694 52 : char szDimName3[NC_MAX_NAME + 1] = {};
8695 : status =
8696 52 : nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 3], szDimName3);
8697 52 : NCDF_ERR(status);
8698 52 : if (nd >= 4)
8699 : {
8700 13 : char szDimName4[NC_MAX_NAME + 1] = {};
8701 : status =
8702 13 : nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 4], szDimName4);
8703 13 : NCDF_ERR(status);
8704 13 : if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false)
8705 : {
8706 0 : CPLError(CE_Warning, CPLE_AppDefined,
8707 : "dimension #%d (%s) is not a Vertical dimension.",
8708 : nd - 3, szDimName3);
8709 : }
8710 13 : if (NCDFIsVarTimeCoord(cdfid, -1, szDimName4) == false)
8711 : {
8712 0 : CPLError(CE_Warning, CPLE_AppDefined,
8713 : "dimension #%d (%s) is not a Time dimension.",
8714 : nd - 4, szDimName4);
8715 : }
8716 : }
8717 : else
8718 : {
8719 75 : if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false &&
8720 36 : NCDFIsVarTimeCoord(cdfid, -1, szDimName3) == false)
8721 : {
8722 0 : CPLError(CE_Warning, CPLE_AppDefined,
8723 : "dimension #%d (%s) is not a "
8724 : "Time or Vertical dimension.",
8725 : nd - 3, szDimName3);
8726 : }
8727 : }
8728 : }
8729 : }
8730 :
8731 : // Get X dimensions information.
8732 : size_t xdim;
8733 372 : poDS->nXDimID = poDS->m_anDimIds[bYXBandOrder ? 1 : nd - 1];
8734 372 : nc_inq_dimlen(cdfid, poDS->nXDimID, &xdim);
8735 :
8736 : // Get Y dimension information.
8737 : size_t ydim;
8738 372 : if (nd >= 2)
8739 : {
8740 368 : poDS->nYDimID = poDS->m_anDimIds[bYXBandOrder ? 0 : nd - 2];
8741 368 : nc_inq_dimlen(cdfid, poDS->nYDimID, &ydim);
8742 : }
8743 : else
8744 : {
8745 4 : poDS->nYDimID = -1;
8746 4 : ydim = 1;
8747 : }
8748 :
8749 372 : if (xdim > INT_MAX || ydim > INT_MAX)
8750 : {
8751 0 : CPLError(CE_Failure, CPLE_AppDefined,
8752 : "Invalid raster dimensions: " CPL_FRMT_GUIB "x" CPL_FRMT_GUIB,
8753 : static_cast<GUIntBig>(xdim), static_cast<GUIntBig>(ydim));
8754 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8755 : // with GDALDataset own mutex.
8756 0 : delete poDS;
8757 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8758 0 : return nullptr;
8759 : }
8760 :
8761 372 : poDS->nRasterXSize = static_cast<int>(xdim);
8762 372 : poDS->nRasterYSize = static_cast<int>(ydim);
8763 :
8764 372 : unsigned int k = 0;
8765 1193 : for (int j = 0; j < nd; j++)
8766 : {
8767 821 : if (poDS->m_anDimIds[j] == poDS->nXDimID)
8768 : {
8769 372 : anBandDimPos[0] = j; // Save Position of XDim
8770 372 : k++;
8771 : }
8772 821 : if (poDS->m_anDimIds[j] == poDS->nYDimID)
8773 : {
8774 368 : anBandDimPos[1] = j; // Save Position of YDim
8775 368 : k++;
8776 : }
8777 : }
8778 : // X and Y Dimension Ids were not found!
8779 372 : if ((nd >= 2 && k != 2) || (nd == 1 && k != 1))
8780 : {
8781 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8782 : // with GDALDataset own mutex.
8783 0 : delete poDS;
8784 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8785 0 : return nullptr;
8786 : }
8787 :
8788 : // Read Metadata for this variable.
8789 :
8790 : // Should disable as is also done at band level, except driver needs the
8791 : // variables as metadata (e.g. projection).
8792 372 : poDS->ReadAttributes(cdfid, var);
8793 :
8794 : // Read Metadata for each dimension.
8795 372 : int *panDimIds = nullptr;
8796 372 : NCDFGetVisibleDims(cdfid, &ndims, &panDimIds);
8797 : // With NetCDF-4 groups panDimIds is not always [0..dim_count-1] like
8798 : // in NetCDF-3 because we see only the dimensions of the selected group
8799 : // and its parents.
8800 : // poDS->papszDimName is indexed by dim IDs, so it must contains all IDs
8801 : // [0..max(panDimIds)], but they are not all useful so we fill names
8802 : // of useless dims with empty string.
8803 372 : if (panDimIds)
8804 : {
8805 372 : const int nMaxDimId = *std::max_element(panDimIds, panDimIds + ndims);
8806 372 : std::set<int> oSetExistingDimIds;
8807 1233 : for (int i = 0; i < ndims; i++)
8808 : {
8809 861 : oSetExistingDimIds.insert(panDimIds[i]);
8810 : }
8811 372 : std::set<int> oSetDimIdsUsedByVar;
8812 1193 : for (int i = 0; i < nd; i++)
8813 : {
8814 821 : oSetDimIdsUsedByVar.insert(poDS->m_anDimIds[i]);
8815 : }
8816 1235 : for (int j = 0; j <= nMaxDimId; j++)
8817 : {
8818 : // Is j dim used?
8819 863 : if (oSetExistingDimIds.find(j) != oSetExistingDimIds.end())
8820 : {
8821 : // Useful dim.
8822 861 : char szTemp[NC_MAX_NAME + 1] = {};
8823 861 : status = nc_inq_dimname(cdfid, j, szTemp);
8824 861 : if (status != NC_NOERR)
8825 : {
8826 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8827 : // deadlock with GDALDataset own
8828 : // mutex.
8829 0 : delete poDS;
8830 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8831 0 : return nullptr;
8832 : }
8833 861 : poDS->papszDimName.AddString(szTemp);
8834 :
8835 861 : if (oSetDimIdsUsedByVar.find(j) != oSetDimIdsUsedByVar.end())
8836 : {
8837 821 : int nDimGroupId = -1;
8838 821 : int nDimVarId = -1;
8839 821 : if (NCDFResolveVar(cdfid, poDS->papszDimName[j],
8840 821 : &nDimGroupId, &nDimVarId) == CE_None)
8841 : {
8842 593 : poDS->ReadAttributes(nDimGroupId, nDimVarId);
8843 : }
8844 : }
8845 : }
8846 : else
8847 : {
8848 : // Useless dim.
8849 2 : poDS->papszDimName.AddString("");
8850 : }
8851 : }
8852 372 : CPLFree(panDimIds);
8853 : }
8854 :
8855 : // Set projection info.
8856 744 : std::vector<std::string> aosRemovedMDItems;
8857 372 : if (nd > 1)
8858 : {
8859 368 : poDS->SetProjectionFromVar(cdfid, var,
8860 : /*bReadSRSOnly=*/false,
8861 : /* pszGivenGM = */ nullptr,
8862 : /* returnProjStr = */ nullptr,
8863 : /* sg = */ nullptr, &aosRemovedMDItems);
8864 : }
8865 :
8866 : // Override bottom-up with GDAL_NETCDF_BOTTOMUP config option.
8867 372 : const char *pszValue = CPLGetConfigOption("GDAL_NETCDF_BOTTOMUP", nullptr);
8868 372 : if (pszValue)
8869 : {
8870 24 : poDS->bBottomUp = CPLTestBool(pszValue);
8871 24 : CPLDebug("GDAL_netCDF",
8872 : "set bBottomUp=%d because GDAL_NETCDF_BOTTOMUP=%s",
8873 24 : static_cast<int>(poDS->bBottomUp), pszValue);
8874 : }
8875 :
8876 : // Save non-spatial dimension info.
8877 :
8878 372 : int *panBandZLev = nullptr;
8879 372 : int nDim = (nd >= 2) ? 2 : 1;
8880 : size_t lev_count;
8881 372 : size_t nTotLevCount = 1;
8882 372 : nc_type nType = NC_NAT;
8883 :
8884 372 : if (nd > 2)
8885 : {
8886 62 : nDim = 2;
8887 62 : panBandZLev = static_cast<int *>(CPLCalloc(nd - 2, sizeof(int)));
8888 :
8889 124 : CPLString osExtraDimNames = "{";
8890 :
8891 62 : char szDimName[NC_MAX_NAME + 1] = {};
8892 :
8893 62 : bool bREPORT_EXTRA_DIM_VALUESWarningEmitted = false;
8894 267 : for (int j = 0; j < nd; j++)
8895 : {
8896 348 : if ((poDS->m_anDimIds[j] != poDS->nXDimID) &&
8897 143 : (poDS->m_anDimIds[j] != poDS->nYDimID))
8898 : {
8899 81 : nc_inq_dimlen(cdfid, poDS->m_anDimIds[j], &lev_count);
8900 81 : nTotLevCount *= lev_count;
8901 81 : panBandZLev[nDim - 2] = static_cast<int>(lev_count);
8902 81 : anBandDimPos[nDim] = j; // Save Position of ZDim
8903 : // Save non-spatial dimension names.
8904 81 : if (nc_inq_dimname(cdfid, poDS->m_anDimIds[j], szDimName) ==
8905 : NC_NOERR)
8906 : {
8907 81 : osExtraDimNames += szDimName;
8908 81 : if (j < nd - 3)
8909 : {
8910 19 : osExtraDimNames += ",";
8911 : }
8912 :
8913 81 : int nIdxGroupID = -1;
8914 81 : int nIdxVarID = Get1DVariableIndexedByDimension(
8915 81 : cdfid, poDS->m_anDimIds[j], szDimName, true,
8916 81 : &nIdxGroupID);
8917 81 : poDS->m_anExtraDimGroupIds.push_back(nIdxGroupID);
8918 81 : poDS->m_anExtraDimVarIds.push_back(nIdxVarID);
8919 :
8920 81 : if (nIdxVarID >= 0)
8921 : {
8922 72 : nc_inq_vartype(nIdxGroupID, nIdxVarID, &nType);
8923 : char szExtraDimDef[NC_MAX_NAME + 1];
8924 72 : snprintf(szExtraDimDef, sizeof(szExtraDimDef),
8925 : "{%ld,%d}", (long)lev_count, nType);
8926 : char szTemp[NC_MAX_NAME + 32 + 1];
8927 72 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
8928 : szDimName);
8929 72 : poDS->aosMetadata.SetNameValue(szTemp, szExtraDimDef);
8930 :
8931 : // Retrieving data for unlimited dimensions might be
8932 : // costly on network storage, so don't do it.
8933 : // Each band will capture the value along the extra
8934 : // dimension in its NETCDF_DIM_xxxx band metadata item
8935 : // Addresses use case of
8936 : // https://lists.osgeo.org/pipermail/gdal-dev/2023-May/057209.html
8937 : const bool bIsLocal =
8938 72 : VSIIsLocal(osFilenameForNCOpen.c_str());
8939 : bool bListDimValues =
8940 73 : bIsLocal || lev_count == 1 ||
8941 1 : !NCDFIsUnlimitedDim(poDS->eFormat ==
8942 : NCDF_FORMAT_NC4,
8943 1 : cdfid, poDS->m_anDimIds[j]);
8944 : const char *pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES =
8945 72 : CPLGetConfigOption(
8946 : "GDAL_NETCDF_REPORT_EXTRA_DIM_VALUES", nullptr);
8947 72 : if (pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES)
8948 : {
8949 2 : bListDimValues = CPLTestBool(
8950 : pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES);
8951 : }
8952 70 : else if (!bListDimValues && !bIsLocal &&
8953 1 : !bREPORT_EXTRA_DIM_VALUESWarningEmitted)
8954 : {
8955 1 : bREPORT_EXTRA_DIM_VALUESWarningEmitted = true;
8956 1 : CPLDebug(
8957 : "GDAL_netCDF",
8958 : "Listing extra dimension values is skipped "
8959 : "because this dataset is hosted on a network "
8960 : "file system, and such an operation could be "
8961 : "slow. If you still want to proceed, set the "
8962 : "GDAL_NETCDF_REPORT_EXTRA_DIM_VALUES "
8963 : "configuration option to YES");
8964 : }
8965 72 : if (bListDimValues)
8966 : {
8967 70 : char *pszTemp = nullptr;
8968 70 : if (NCDFGet1DVar(nIdxGroupID, nIdxVarID,
8969 70 : &pszTemp) == CE_None)
8970 : {
8971 70 : snprintf(szTemp, sizeof(szTemp),
8972 : "NETCDF_DIM_%s_VALUES", szDimName);
8973 70 : poDS->aosMetadata.SetNameValue(szTemp, pszTemp);
8974 70 : CPLFree(pszTemp);
8975 : }
8976 : }
8977 : }
8978 : }
8979 : else
8980 : {
8981 0 : poDS->m_anExtraDimGroupIds.push_back(-1);
8982 0 : poDS->m_anExtraDimVarIds.push_back(-1);
8983 : }
8984 :
8985 81 : nDim++;
8986 : }
8987 : }
8988 62 : osExtraDimNames += "}";
8989 62 : poDS->aosMetadata.SetNameValue("NETCDF_DIM_EXTRA", osExtraDimNames);
8990 : }
8991 :
8992 : // Store Metadata.
8993 382 : for (const auto &osStr : aosRemovedMDItems)
8994 10 : poDS->aosMetadata.SetNameValue(osStr.c_str(), nullptr);
8995 :
8996 372 : poDS->GDALPamDataset::SetMetadata(poDS->aosMetadata);
8997 :
8998 : // Create bands.
8999 :
9000 : // Arbitrary threshold.
9001 : int nMaxBandCount =
9002 372 : atoi(CPLGetConfigOption("GDAL_MAX_BAND_COUNT", "32768"));
9003 372 : if (nMaxBandCount <= 0)
9004 0 : nMaxBandCount = 32768;
9005 372 : if (nTotLevCount > static_cast<unsigned int>(nMaxBandCount))
9006 : {
9007 0 : CPLError(CE_Warning, CPLE_AppDefined,
9008 : "Limiting number of bands to %d instead of %u", nMaxBandCount,
9009 : static_cast<unsigned int>(nTotLevCount));
9010 0 : nTotLevCount = static_cast<unsigned int>(nMaxBandCount);
9011 : }
9012 372 : if (poDS->nRasterXSize == 0 || poDS->nRasterYSize == 0)
9013 : {
9014 0 : poDS->nRasterXSize = 0;
9015 0 : poDS->nRasterYSize = 0;
9016 0 : nTotLevCount = 0;
9017 0 : if (poDS->GetLayerCount() == 0)
9018 : {
9019 0 : CPLFree(panBandZLev);
9020 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9021 : // deadlock with GDALDataset own mutex.
9022 0 : delete poDS;
9023 0 : CPLAcquireMutex(hNCMutex, 1000.0);
9024 0 : return nullptr;
9025 : }
9026 : }
9027 372 : if (bSeveralVariablesAsBands)
9028 : {
9029 6 : const auto &listVariables = oMap2DDimsToGroupAndVar.begin()->second;
9030 24 : for (int iBand = 0; iBand < static_cast<int>(listVariables.size());
9031 : ++iBand)
9032 : {
9033 18 : int bandVarGroupId = listVariables[iBand].first;
9034 18 : int bandVarId = listVariables[iBand].second;
9035 : netCDFRasterBand *poBand = new netCDFRasterBand(
9036 0 : netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, bandVarGroupId,
9037 18 : bandVarId, nDim, 0, nullptr, anBandDimPos.data(), iBand + 1);
9038 18 : poDS->SetBand(iBand + 1, poBand);
9039 : }
9040 : }
9041 : else
9042 : {
9043 846 : for (unsigned int lev = 0; lev < nTotLevCount; lev++)
9044 : {
9045 : netCDFRasterBand *poBand = new netCDFRasterBand(
9046 0 : netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, cdfid, var, nDim,
9047 480 : lev, panBandZLev, anBandDimPos.data(), lev + 1);
9048 480 : poDS->SetBand(lev + 1, poBand);
9049 : }
9050 : }
9051 :
9052 372 : if (panBandZLev)
9053 62 : CPLFree(panBandZLev);
9054 : // Handle angular geographic coordinates here
9055 :
9056 : // Initialize any PAM information.
9057 372 : if (bTreatAsSubdataset)
9058 : {
9059 64 : poDS->SetPhysicalFilename(poDS->osFilename);
9060 64 : poDS->SetSubdatasetName(osSubdatasetName);
9061 : }
9062 :
9063 372 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
9064 : // GDALDataset own mutex.
9065 372 : poDS->TryLoadXML();
9066 :
9067 372 : if (bTreatAsSubdataset)
9068 64 : poDS->oOvManager.Initialize(poDS, ":::VIRTUAL:::");
9069 : else
9070 308 : poDS->oOvManager.Initialize(poDS, poDS->osFilename);
9071 :
9072 372 : CPLAcquireMutex(hNCMutex, 1000.0);
9073 :
9074 372 : return poDS;
9075 : }
9076 :
9077 : /************************************************************************/
9078 : /* CopyMetadata() */
9079 : /* */
9080 : /* Create a copy of metadata for NC_GLOBAL or a variable */
9081 : /************************************************************************/
9082 :
9083 169 : static void CopyMetadata(GDALDataset *poSrcDS, GDALRasterBand *poSrcBand,
9084 : GDALRasterBand *poDstBand, int nCdfId, int CDFVarID,
9085 : const char *pszPrefix)
9086 : {
9087 : // Remove the following band meta but set them later from band data.
9088 169 : const char *const papszIgnoreBand[] = {
9089 : CF_ADD_OFFSET, CF_SCALE_FACTOR, "valid_range", "_Unsigned",
9090 : NCDF_FillValue, "coordinates", nullptr};
9091 169 : const char *const papszIgnoreGlobal[] = {"NETCDF_DIM_EXTRA", nullptr};
9092 :
9093 169 : CSLConstList papszMetadata = nullptr;
9094 169 : if (poSrcDS)
9095 : {
9096 72 : papszMetadata = poSrcDS->GetMetadata();
9097 : }
9098 97 : else if (poSrcBand)
9099 : {
9100 97 : papszMetadata = poSrcBand->GetMetadata();
9101 : }
9102 :
9103 655 : for (const auto &[pszKey, pszValue] : cpl::IterateNameValue(papszMetadata))
9104 : {
9105 : #ifdef NCDF_DEBUG
9106 : CPLDebug("GDAL_netCDF", "copy metadata [%s]=[%s]", pszKey, pszValue);
9107 : #endif
9108 :
9109 486 : CPLString osMetaName(pszKey);
9110 :
9111 : // Check for items that match pszPrefix if applicable.
9112 486 : if (pszPrefix && !EQUAL(pszPrefix, ""))
9113 : {
9114 : // Remove prefix.
9115 115 : if (STARTS_WITH(osMetaName.c_str(), pszPrefix))
9116 : {
9117 17 : osMetaName = osMetaName.substr(strlen(pszPrefix));
9118 : }
9119 : // Only copy items that match prefix.
9120 : else
9121 : {
9122 98 : continue;
9123 : }
9124 : }
9125 :
9126 : // Fix various issues with metadata translation.
9127 388 : if (CDFVarID == NC_GLOBAL)
9128 : {
9129 : // Do not copy items in papszIgnoreGlobal and NETCDF_DIM_*.
9130 493 : if ((CSLFindString(papszIgnoreGlobal, osMetaName) != -1) ||
9131 244 : (STARTS_WITH(osMetaName, "NETCDF_DIM_")))
9132 21 : continue;
9133 : // Remove NC_GLOBAL prefix for netcdf global Metadata.
9134 228 : else if (STARTS_WITH(osMetaName, "NC_GLOBAL#"))
9135 : {
9136 33 : osMetaName = osMetaName.substr(strlen("NC_GLOBAL#"));
9137 : }
9138 : // GDAL Metadata renamed as GDAL-[meta].
9139 195 : else if (strstr(osMetaName, "#") == nullptr)
9140 : {
9141 22 : osMetaName = "GDAL_" + osMetaName;
9142 : }
9143 : // Keep time, lev and depth information for safe-keeping.
9144 : // Time and vertical coordinate handling need improvements.
9145 : /*
9146 : else if( STARTS_WITH(szMetaName, "time#") )
9147 : {
9148 : szMetaName[4] = '-';
9149 : }
9150 : else if( STARTS_WITH(szMetaName, "lev#") )
9151 : {
9152 : szMetaName[3] = '-';
9153 : }
9154 : else if( STARTS_WITH(szMetaName, "depth#") )
9155 : {
9156 : szMetaName[5] = '-';
9157 : }
9158 : */
9159 : // Only copy data without # (previously all data was copied).
9160 228 : if (strstr(osMetaName, "#") != nullptr)
9161 173 : continue;
9162 : // netCDF attributes do not like the '#' character.
9163 : // for( unsigned int h=0; h < strlen(szMetaName) -1 ; h++ ) {
9164 : // if( szMetaName[h] == '#') szMetaName[h] = '-';
9165 : // }
9166 : }
9167 : else
9168 : {
9169 : // Do not copy varname, stats, NETCDF_DIM_*, nodata
9170 : // and items in papszIgnoreBand.
9171 139 : if (STARTS_WITH(osMetaName, "NETCDF_VARNAME") ||
9172 107 : STARTS_WITH(osMetaName, "STATISTICS_") ||
9173 107 : STARTS_WITH(osMetaName, "NETCDF_DIM_") ||
9174 74 : STARTS_WITH(osMetaName, "missing_value") ||
9175 293 : STARTS_WITH(osMetaName, "_FillValue") ||
9176 47 : CSLFindString(papszIgnoreBand, osMetaName) != -1)
9177 97 : continue;
9178 : }
9179 :
9180 : #ifdef NCDF_DEBUG
9181 : CPLDebug("GDAL_netCDF", "copy name=[%s] value=[%s]", osMetaName.c_str(),
9182 : pszValue);
9183 : #endif
9184 97 : if (NCDFPutAttr(nCdfId, CDFVarID, osMetaName, pszValue) != CE_None)
9185 : {
9186 0 : CPLDebug("GDAL_netCDF", "NCDFPutAttr(%d, %d, %s, %s) failed",
9187 : nCdfId, CDFVarID, osMetaName.c_str(), pszValue);
9188 : }
9189 : }
9190 :
9191 : // Set add_offset and scale_factor here if present.
9192 169 : if (poSrcBand && poDstBand)
9193 : {
9194 :
9195 97 : int bGotAddOffset = FALSE;
9196 97 : const double dfAddOffset = poSrcBand->GetOffset(&bGotAddOffset);
9197 97 : int bGotScale = FALSE;
9198 97 : const double dfScale = poSrcBand->GetScale(&bGotScale);
9199 :
9200 97 : if (bGotAddOffset && dfAddOffset != 0.0)
9201 1 : poDstBand->SetOffset(dfAddOffset);
9202 97 : if (bGotScale && dfScale != 1.0)
9203 1 : poDstBand->SetScale(dfScale);
9204 : }
9205 169 : }
9206 :
9207 : /************************************************************************/
9208 : /* CreateLL() */
9209 : /* */
9210 : /* Shared functionality between netCDFDataset::Create() and */
9211 : /* netCDF::CreateCopy() for creating netcdf file based on a set of */
9212 : /* options and a configuration. */
9213 : /************************************************************************/
9214 :
9215 205 : netCDFDataset *netCDFDataset::CreateLL(const char *pszFilename, int nXSize,
9216 : int nYSize, int nBandsIn,
9217 : CSLConstList papszOptions)
9218 : {
9219 205 : if (!((nXSize == 0 && nYSize == 0 && nBandsIn == 0) ||
9220 132 : (nXSize > 0 && nYSize > 0 && nBandsIn > 0)))
9221 : {
9222 1 : return nullptr;
9223 : }
9224 :
9225 204 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
9226 : // GDALDataset own mutex.
9227 204 : netCDFDataset *poDS = new netCDFDataset();
9228 204 : CPLAcquireMutex(hNCMutex, 1000.0);
9229 :
9230 204 : poDS->nRasterXSize = nXSize;
9231 204 : poDS->nRasterYSize = nYSize;
9232 204 : poDS->eAccess = GA_Update;
9233 204 : poDS->osFilename = pszFilename;
9234 :
9235 : // From gtiff driver, is this ok?
9236 : /*
9237 : poDS->nBlockXSize = nXSize;
9238 : poDS->nBlockYSize = 1;
9239 : poDS->nBlocksPerBand =
9240 : DIV_ROUND_UP((nYSize, poDS->nBlockYSize))
9241 : * DIV_ROUND_UP((nXSize, poDS->nBlockXSize));
9242 : */
9243 :
9244 : // process options.
9245 204 : poDS->aosCreationOptions = CSLDuplicate(papszOptions);
9246 204 : poDS->ProcessCreationOptions();
9247 :
9248 204 : if (poDS->eMultipleLayerBehavior == SEPARATE_FILES)
9249 : {
9250 : VSIStatBuf sStat;
9251 3 : if (VSIStat(pszFilename, &sStat) == 0)
9252 : {
9253 0 : if (!VSI_ISDIR(sStat.st_mode))
9254 : {
9255 0 : CPLError(CE_Failure, CPLE_FileIO,
9256 : "%s is an existing file, but not a directory",
9257 : pszFilename);
9258 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9259 : // deadlock with GDALDataset own
9260 : // mutex.
9261 0 : delete poDS;
9262 0 : CPLAcquireMutex(hNCMutex, 1000.0);
9263 0 : return nullptr;
9264 : }
9265 : }
9266 3 : else if (VSIMkdir(pszFilename, 0755) != 0)
9267 : {
9268 1 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s directory",
9269 : pszFilename);
9270 1 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9271 : // deadlock with GDALDataset own mutex.
9272 1 : delete poDS;
9273 1 : CPLAcquireMutex(hNCMutex, 1000.0);
9274 1 : return nullptr;
9275 : }
9276 :
9277 2 : return poDS;
9278 : }
9279 : // Create the dataset.
9280 402 : CPLString osFilenameForNCCreate(pszFilename);
9281 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
9282 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
9283 : {
9284 : char *pszTemp =
9285 : CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
9286 : osFilenameForNCCreate = pszTemp;
9287 : CPLFree(pszTemp);
9288 : }
9289 : #endif
9290 :
9291 : #if defined(_WIN32)
9292 : {
9293 : // Works around bug of msys2 netCDF 4.9.0 package where nc_create()
9294 : // crashes
9295 : VSIStatBuf sStat;
9296 : const std::string osDirname =
9297 : CPLGetDirnameSafe(osFilenameForNCCreate.c_str());
9298 : if (VSIStat(osDirname.c_str(), &sStat) != 0)
9299 : {
9300 : CPLError(CE_Failure, CPLE_OpenFailed,
9301 : "Unable to create netCDF file %s: non existing output "
9302 : "directory",
9303 : pszFilename);
9304 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9305 : // deadlock with GDALDataset own mutex.
9306 : delete poDS;
9307 : CPLAcquireMutex(hNCMutex, 1000.0);
9308 : return nullptr;
9309 : }
9310 : }
9311 : #endif
9312 :
9313 : int status =
9314 201 : nc_create(osFilenameForNCCreate, poDS->nCreateMode, &(poDS->cdfid));
9315 :
9316 : // Put into define mode.
9317 201 : poDS->SetDefineMode(true);
9318 :
9319 201 : if (status != NC_NOERR)
9320 : {
9321 30 : CPLError(CE_Failure, CPLE_OpenFailed,
9322 : "Unable to create netCDF file %s (Error code %d): %s .",
9323 : pszFilename, status, nc_strerror(status));
9324 30 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
9325 : // with GDALDataset own mutex.
9326 30 : delete poDS;
9327 30 : CPLAcquireMutex(hNCMutex, 1000.0);
9328 30 : return nullptr;
9329 : }
9330 :
9331 : // Define dimensions.
9332 171 : if (nXSize > 0 && nYSize > 0)
9333 : {
9334 118 : poDS->papszDimName.AddString(NCDF_DIMNAME_X);
9335 : status =
9336 118 : nc_def_dim(poDS->cdfid, NCDF_DIMNAME_X, nXSize, &(poDS->nXDimID));
9337 118 : NCDF_ERR(status);
9338 118 : CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
9339 : poDS->cdfid, NCDF_DIMNAME_X, nXSize, poDS->nXDimID);
9340 :
9341 118 : poDS->papszDimName.AddString(NCDF_DIMNAME_Y);
9342 : status =
9343 118 : nc_def_dim(poDS->cdfid, NCDF_DIMNAME_Y, nYSize, &(poDS->nYDimID));
9344 118 : NCDF_ERR(status);
9345 118 : CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
9346 : poDS->cdfid, NCDF_DIMNAME_Y, nYSize, poDS->nYDimID);
9347 : }
9348 :
9349 171 : return poDS;
9350 : }
9351 :
9352 : /************************************************************************/
9353 : /* Create() */
9354 : /************************************************************************/
9355 :
9356 127 : GDALDataset *netCDFDataset::Create(const char *pszFilename, int nXSize,
9357 : int nYSize, int nBandsIn, GDALDataType eType,
9358 : CSLConstList papszOptions)
9359 : {
9360 127 : CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::Create(%s, ...)",
9361 : pszFilename);
9362 :
9363 : const char *legacyCreationOp =
9364 127 : CSLFetchNameValueDef(papszOptions, "GEOMETRY_ENCODING", "CF_1.8");
9365 254 : std::string legacyCreationOp_s = std::string(legacyCreationOp);
9366 :
9367 : // Check legacy creation op FIRST
9368 :
9369 127 : bool legacyCreateMode = false;
9370 :
9371 127 : if (nXSize != 0 || nYSize != 0 || nBandsIn != 0)
9372 : {
9373 56 : legacyCreateMode = true;
9374 : }
9375 71 : else if (legacyCreationOp_s == "CF_1.8")
9376 : {
9377 54 : legacyCreateMode = false;
9378 : }
9379 :
9380 17 : else if (legacyCreationOp_s == "WKT")
9381 : {
9382 17 : legacyCreateMode = true;
9383 : }
9384 :
9385 : else
9386 : {
9387 0 : CPLError(
9388 : CE_Failure, CPLE_NotSupported,
9389 : "Dataset creation option GEOMETRY_ENCODING=%s is not supported.",
9390 : legacyCreationOp_s.c_str());
9391 0 : return nullptr;
9392 : }
9393 :
9394 254 : CPLStringList aosOptions(CSLDuplicate(papszOptions));
9395 240 : if (aosOptions.FetchNameValue("FORMAT") == nullptr &&
9396 113 : (eType == GDT_UInt16 || eType == GDT_UInt32 || eType == GDT_UInt64 ||
9397 : eType == GDT_Int64))
9398 : {
9399 10 : CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
9400 10 : aosOptions.SetNameValue("FORMAT", "NC4");
9401 : }
9402 :
9403 254 : CPLStringList aosBandNames;
9404 127 : if (const char *pszBandNames = aosOptions.FetchNameValue("BAND_NAMES"))
9405 : {
9406 : aosBandNames =
9407 2 : CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);
9408 :
9409 2 : if (aosBandNames.Count() != nBandsIn)
9410 : {
9411 1 : CPLError(CE_Failure, CPLE_OpenFailed,
9412 : "Attempted to create netCDF with %d bands but %d names "
9413 : "provided in BAND_NAMES.",
9414 : nBandsIn, aosBandNames.Count());
9415 :
9416 1 : return nullptr;
9417 : }
9418 : }
9419 :
9420 252 : CPLMutexHolderD(&hNCMutex);
9421 :
9422 126 : auto poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize, nBandsIn,
9423 126 : aosOptions.List());
9424 :
9425 126 : if (!poDS)
9426 19 : return nullptr;
9427 :
9428 107 : if (!legacyCreateMode)
9429 : {
9430 37 : poDS->bSGSupport = true;
9431 37 : poDS->vcdf.enableFullVirtualMode();
9432 : }
9433 :
9434 : else
9435 : {
9436 70 : poDS->bSGSupport = false;
9437 : }
9438 :
9439 : // Should we write signed or unsigned byte?
9440 : // TODO should this only be done in Create()
9441 107 : poDS->bSignedData = true;
9442 107 : const char *pszValue = CSLFetchNameValueDef(papszOptions, "PIXELTYPE", "");
9443 107 : if (eType == GDT_UInt8 && !EQUAL(pszValue, "SIGNEDBYTE"))
9444 15 : poDS->bSignedData = false;
9445 :
9446 : // Add Conventions, GDAL info and history.
9447 107 : if (poDS->cdfid >= 0)
9448 : {
9449 : const char *CF_Vector_Conv =
9450 173 : poDS->bSGSupport ||
9451 : // Use of variable length strings require CF-1.8
9452 68 : EQUAL(aosOptions.FetchNameValueDef("FORMAT", ""), "NC4")
9453 : ? NCDF_CONVENTIONS_CF_V1_8
9454 173 : : NCDF_CONVENTIONS_CF_V1_6;
9455 105 : poDS->bWriteGDALVersion = CPLTestBool(
9456 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
9457 105 : poDS->bWriteGDALHistory = CPLTestBool(
9458 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
9459 105 : NCDFAddGDALHistory(poDS->cdfid, pszFilename, poDS->bWriteGDALVersion,
9460 105 : poDS->bWriteGDALHistory, "", "Create",
9461 : (nBandsIn == 0) ? CF_Vector_Conv
9462 : : GDAL_DEFAULT_NCDF_CONVENTIONS);
9463 : }
9464 :
9465 : // Define bands.
9466 198 : for (int iBand = 1; iBand <= nBandsIn; iBand++)
9467 : {
9468 : const char *pszBandName =
9469 91 : aosBandNames.empty() ? nullptr : aosBandNames[iBand - 1];
9470 :
9471 91 : poDS->SetBand(iBand, new netCDFRasterBand(
9472 91 : netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS,
9473 91 : eType, iBand, poDS->bSignedData, pszBandName));
9474 : }
9475 :
9476 107 : CPLDebug("GDAL_netCDF", "netCDFDataset::Create(%s, ...) done", pszFilename);
9477 : // Return same dataset.
9478 107 : return poDS;
9479 : }
9480 :
9481 : template <class T>
9482 97 : static CPLErr NCDFCopyBand(GDALRasterBand *poSrcBand, GDALRasterBand *poDstBand,
9483 : int nXSize, int nYSize, GDALProgressFunc pfnProgress,
9484 : void *pProgressData)
9485 : {
9486 97 : const GDALDataType eDT = poSrcBand->GetRasterDataType();
9487 97 : T *patScanline = static_cast<T *>(VSI_MALLOC2_VERBOSE(nXSize, sizeof(T)));
9488 97 : CPLErr eErr = patScanline ? CE_None : CE_Failure;
9489 :
9490 6414 : for (int iLine = 0; iLine < nYSize && eErr == CE_None; iLine++)
9491 : {
9492 6317 : eErr = poSrcBand->RasterIO(GF_Read, 0, iLine, nXSize, 1, patScanline,
9493 : nXSize, 1, eDT, 0, 0, nullptr);
9494 6317 : if (eErr != CE_None)
9495 : {
9496 0 : CPLDebug(
9497 : "GDAL_netCDF",
9498 : "NCDFCopyBand(), poSrcBand->RasterIO() returned error code %d",
9499 : eErr);
9500 : }
9501 : else
9502 : {
9503 6317 : eErr =
9504 : poDstBand->RasterIO(GF_Write, 0, iLine, nXSize, 1, patScanline,
9505 : nXSize, 1, eDT, 0, 0, nullptr);
9506 6317 : if (eErr != CE_None)
9507 0 : CPLDebug("GDAL_netCDF",
9508 : "NCDFCopyBand(), poDstBand->RasterIO() returned error "
9509 : "code %d",
9510 : eErr);
9511 : }
9512 :
9513 6317 : if (nYSize > 10 && (iLine % (nYSize / 10) == 1))
9514 : {
9515 317 : if (!pfnProgress(1.0 * iLine / nYSize, nullptr, pProgressData))
9516 : {
9517 0 : eErr = CE_Failure;
9518 0 : CPLError(CE_Failure, CPLE_UserInterrupt,
9519 : "User terminated CreateCopy()");
9520 : }
9521 : }
9522 : }
9523 :
9524 97 : CPLFree(patScanline);
9525 :
9526 97 : pfnProgress(1.0, nullptr, pProgressData);
9527 :
9528 97 : return eErr;
9529 : }
9530 :
9531 : /************************************************************************/
9532 : /* CreateCopy() */
9533 : /************************************************************************/
9534 :
9535 : GDALDataset *
9536 93 : netCDFDataset::CreateCopy(const char *pszFilename, GDALDataset *poSrcDS,
9537 : CPL_UNUSED int bStrict, CSLConstList papszOptions,
9538 : GDALProgressFunc pfnProgress, void *pProgressData)
9539 : {
9540 186 : CPLMutexHolderD(&hNCMutex);
9541 :
9542 93 : CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::CreateCopy(%s, ...)",
9543 : pszFilename);
9544 :
9545 93 : if (poSrcDS->GetRootGroup())
9546 : {
9547 10 : auto poDrv = GDALDriver::FromHandle(GDALGetDriverByName("netCDF"));
9548 10 : if (poDrv)
9549 : {
9550 10 : return poDrv->DefaultCreateCopy(pszFilename, poSrcDS, bStrict,
9551 : papszOptions, pfnProgress,
9552 10 : pProgressData);
9553 : }
9554 : }
9555 :
9556 83 : const int nBands = poSrcDS->GetRasterCount();
9557 83 : const int nXSize = poSrcDS->GetRasterXSize();
9558 83 : const int nYSize = poSrcDS->GetRasterYSize();
9559 83 : const char *pszWKT = poSrcDS->GetProjectionRef();
9560 :
9561 : // Check input bands for errors.
9562 83 : if (nBands == 0)
9563 : {
9564 1 : CPLError(CE_Failure, CPLE_NotSupported,
9565 : "NetCDF driver does not support "
9566 : "source datasets with zero bands.");
9567 1 : return nullptr;
9568 : }
9569 :
9570 82 : GDALDataType eDT = GDT_Unknown;
9571 82 : GDALRasterBand *poSrcBand = nullptr;
9572 193 : for (int iBand = 1; iBand <= nBands; iBand++)
9573 : {
9574 115 : poSrcBand = poSrcDS->GetRasterBand(iBand);
9575 115 : eDT = poSrcBand->GetRasterDataType();
9576 115 : if (eDT == GDT_Unknown || GDALDataTypeIsComplex(eDT))
9577 : {
9578 4 : CPLError(CE_Failure, CPLE_NotSupported,
9579 : "NetCDF driver does not support source dataset with band "
9580 : "of complex type.");
9581 4 : return nullptr;
9582 : }
9583 : }
9584 :
9585 156 : CPLStringList aosBandNames;
9586 78 : if (const char *pszBandNames =
9587 78 : CSLFetchNameValue(papszOptions, "BAND_NAMES"))
9588 : {
9589 : aosBandNames =
9590 2 : CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);
9591 :
9592 2 : if (aosBandNames.Count() != nBands)
9593 : {
9594 1 : CPLError(CE_Failure, CPLE_OpenFailed,
9595 : "Attempted to create netCDF with %d bands but %d names "
9596 : "provided in BAND_NAMES.",
9597 : nBands, aosBandNames.Count());
9598 :
9599 1 : return nullptr;
9600 : }
9601 : }
9602 :
9603 77 : if (!pfnProgress(0.0, nullptr, pProgressData))
9604 0 : return nullptr;
9605 :
9606 : // Same as in Create().
9607 154 : CPLStringList aosOptions(CSLDuplicate(papszOptions));
9608 145 : if (aosOptions.FetchNameValue("FORMAT") == nullptr &&
9609 68 : (eDT == GDT_UInt16 || eDT == GDT_UInt32 || eDT == GDT_UInt64 ||
9610 : eDT == GDT_Int64))
9611 : {
9612 6 : CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
9613 6 : aosOptions.SetNameValue("FORMAT", "NC4");
9614 : }
9615 77 : netCDFDataset *poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize,
9616 77 : nBands, aosOptions.List());
9617 77 : if (!poDS)
9618 13 : return nullptr;
9619 :
9620 : // Copy global metadata.
9621 : // Add Conventions, GDAL info and history.
9622 64 : CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid, NC_GLOBAL, nullptr);
9623 64 : const bool bWriteGDALVersion = CPLTestBool(
9624 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
9625 64 : const bool bWriteGDALHistory = CPLTestBool(
9626 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
9627 64 : NCDFAddGDALHistory(
9628 : poDS->cdfid, pszFilename, bWriteGDALVersion, bWriteGDALHistory,
9629 64 : poSrcDS->GetMetadataItem("NC_GLOBAL#history"), "CreateCopy",
9630 64 : poSrcDS->GetMetadataItem("NC_GLOBAL#Conventions"));
9631 :
9632 64 : pfnProgress(0.1, nullptr, pProgressData);
9633 :
9634 : // Check for extra dimensions.
9635 64 : int nDim = 2;
9636 : CPLStringList aosExtraDimNames =
9637 128 : NCDFTokenizeArray(poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
9638 :
9639 64 : if (!aosExtraDimNames.empty())
9640 : {
9641 5 : size_t nDimSizeTot = 1;
9642 : // first make sure dimensions lengths compatible with band count
9643 : // for( int i=0; i<CSLCount(papszExtraDimNames ); i++ ) {
9644 13 : for (int i = aosExtraDimNames.size() - 1; i >= 0; i--)
9645 : {
9646 : char szTemp[NC_MAX_NAME + 32 + 1];
9647 8 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
9648 : aosExtraDimNames[i]);
9649 : const CPLStringList aosExtraDimValues =
9650 8 : NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
9651 8 : const size_t nDimSize = atol(aosExtraDimValues[0]);
9652 8 : nDimSizeTot *= nDimSize;
9653 : }
9654 5 : if (nDimSizeTot == (size_t)nBands)
9655 : {
9656 5 : nDim = 2 + aosExtraDimNames.size();
9657 : }
9658 : else
9659 : {
9660 : // if nBands != #bands computed raise a warning
9661 : // just issue a debug message, because it was probably intentional
9662 0 : CPLDebug("GDAL_netCDF",
9663 : "Warning: Number of bands (%d) is not compatible with "
9664 : "dimensions "
9665 : "(total=%ld names=%s)",
9666 : nBands, (long)nDimSizeTot,
9667 0 : poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
9668 0 : aosExtraDimNames.clear();
9669 : }
9670 : }
9671 :
9672 64 : int *panDimIds = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
9673 64 : int *panBandDimPos = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
9674 :
9675 : nc_type nVarType;
9676 64 : int *panBandZLev = nullptr;
9677 64 : int *panDimVarIds = nullptr;
9678 :
9679 64 : if (nDim > 2)
9680 : {
9681 5 : panBandZLev = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
9682 5 : panDimVarIds = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
9683 :
9684 : // Define all dims.
9685 13 : for (int i = aosExtraDimNames.size() - 1; i >= 0; i--)
9686 : {
9687 8 : poDS->papszDimName.AddString(aosExtraDimNames[i]);
9688 : char szTemp[NC_MAX_NAME + 32 + 1];
9689 8 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
9690 : aosExtraDimNames[i]);
9691 : const CPLStringList aosExtraDimValues =
9692 16 : NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
9693 : const int nDimSize =
9694 8 : aosExtraDimValues.empty() ? 0 : atoi(aosExtraDimValues[0]);
9695 : // nc_type is an enum in netcdf-3, needs casting.
9696 0 : nVarType = static_cast<nc_type>(
9697 8 : aosExtraDimValues.size() >= 2 ? atol(aosExtraDimValues[1]) : 0);
9698 8 : panBandZLev[i] = nDimSize;
9699 8 : panBandDimPos[i + 2] = i; // Save Position of ZDim.
9700 :
9701 : // Define dim.
9702 8 : int status = nc_def_dim(poDS->cdfid, aosExtraDimNames[i], nDimSize,
9703 8 : &(panDimIds[i]));
9704 8 : NCDF_ERR(status);
9705 :
9706 : // Define dim var.
9707 8 : int anDim[1] = {panDimIds[i]};
9708 8 : status = nc_def_var(poDS->cdfid, aosExtraDimNames[i], nVarType, 1,
9709 8 : anDim, &(panDimVarIds[i]));
9710 8 : NCDF_ERR(status);
9711 :
9712 : // Add dim metadata, using global var# items.
9713 8 : snprintf(szTemp, sizeof(szTemp), "%s#", aosExtraDimNames[i]);
9714 8 : CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid,
9715 8 : panDimVarIds[i], szTemp);
9716 : }
9717 : }
9718 :
9719 : // Copy GeoTransform and Projection.
9720 :
9721 : // Copy geolocation info.
9722 64 : CSLConstList papszGeolocationInfo = poSrcDS->GetMetadata("GEOLOCATION");
9723 64 : if (papszGeolocationInfo != nullptr)
9724 5 : poDS->GDALPamDataset::SetMetadata(papszGeolocationInfo, "GEOLOCATION");
9725 :
9726 : // Copy geotransform.
9727 64 : bool bGotGeoTransform = false;
9728 64 : GDALGeoTransform gt;
9729 64 : CPLErr eErr = poSrcDS->GetGeoTransform(gt);
9730 64 : if (eErr == CE_None)
9731 : {
9732 46 : poDS->SetGeoTransform(gt);
9733 : // Disable AddProjectionVars() from being called.
9734 46 : bGotGeoTransform = true;
9735 46 : poDS->m_bHasGeoTransform = false;
9736 : }
9737 :
9738 : // Copy projection.
9739 64 : void *pScaledProgress = nullptr;
9740 64 : if (bGotGeoTransform || (pszWKT && pszWKT[0] != 0))
9741 : {
9742 47 : poDS->SetProjection(pszWKT ? pszWKT : "");
9743 :
9744 : // Now we can call AddProjectionVars() directly.
9745 47 : poDS->m_bHasGeoTransform = bGotGeoTransform;
9746 47 : poDS->AddProjectionVars(true, nullptr, nullptr);
9747 : pScaledProgress =
9748 47 : GDALCreateScaledProgress(0.1, 0.25, pfnProgress, pProgressData);
9749 47 : poDS->AddProjectionVars(false, GDALScaledProgress, pScaledProgress);
9750 47 : GDALDestroyScaledProgress(pScaledProgress);
9751 : }
9752 : else
9753 : {
9754 17 : poDS->bBottomUp =
9755 17 : CPL_TO_BOOL(CSLFetchBoolean(papszOptions, "WRITE_BOTTOMUP", TRUE));
9756 17 : if (papszGeolocationInfo)
9757 : {
9758 4 : poDS->AddProjectionVars(true, nullptr, nullptr);
9759 4 : poDS->AddProjectionVars(false, nullptr, nullptr);
9760 : }
9761 : }
9762 :
9763 : // Save X,Y dim positions.
9764 64 : panDimIds[nDim - 1] = poDS->nXDimID;
9765 64 : panBandDimPos[0] = nDim - 1;
9766 64 : panDimIds[nDim - 2] = poDS->nYDimID;
9767 64 : panBandDimPos[1] = nDim - 2;
9768 :
9769 : // Write extra dim values - after projection for optimization.
9770 64 : if (nDim > 2)
9771 : {
9772 : // Make sure we are in data mode.
9773 5 : poDS->SetDefineMode(false);
9774 13 : for (int i = aosExtraDimNames.size() - 1; i >= 0; i--)
9775 : {
9776 : char szTemp[NC_MAX_NAME + 32 + 1];
9777 8 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_VALUES",
9778 : aosExtraDimNames[i]);
9779 8 : if (poSrcDS->GetMetadataItem(szTemp) != nullptr)
9780 : {
9781 8 : NCDFPut1DVar(poDS->cdfid, panDimVarIds[i],
9782 8 : poSrcDS->GetMetadataItem(szTemp));
9783 : }
9784 : }
9785 : }
9786 :
9787 64 : pfnProgress(0.25, nullptr, pProgressData);
9788 :
9789 : // Define Bands.
9790 64 : netCDFRasterBand *poBand = nullptr;
9791 64 : int nBandID = -1;
9792 :
9793 161 : for (int iBand = 1; iBand <= nBands; iBand++)
9794 : {
9795 97 : CPLDebug("GDAL_netCDF", "creating band # %d/%d nDim = %d", iBand,
9796 : nBands, nDim);
9797 :
9798 97 : poSrcBand = poSrcDS->GetRasterBand(iBand);
9799 97 : eDT = poSrcBand->GetRasterDataType();
9800 :
9801 : // Get var name from NETCDF_VARNAME.
9802 : const char *pszNETCDF_VARNAME =
9803 97 : poSrcBand->GetMetadataItem("NETCDF_VARNAME");
9804 : char szBandName[NC_MAX_NAME + 1];
9805 97 : if (!aosBandNames.empty())
9806 : {
9807 2 : snprintf(szBandName, sizeof(szBandName), "%s",
9808 : aosBandNames[iBand - 1]);
9809 : }
9810 95 : else if (pszNETCDF_VARNAME)
9811 : {
9812 32 : if (nBands > 1 && aosExtraDimNames.empty())
9813 0 : snprintf(szBandName, sizeof(szBandName), "%s%d",
9814 : pszNETCDF_VARNAME, iBand);
9815 : else
9816 32 : snprintf(szBandName, sizeof(szBandName), "%s",
9817 : pszNETCDF_VARNAME);
9818 : }
9819 : else
9820 : {
9821 63 : szBandName[0] = '\0';
9822 : }
9823 :
9824 : // Get long_name from <var>#long_name.
9825 97 : const char *pszLongName = "";
9826 97 : if (pszNETCDF_VARNAME)
9827 : {
9828 : pszLongName =
9829 64 : poSrcDS->GetMetadataItem(std::string(pszNETCDF_VARNAME)
9830 32 : .append("#")
9831 32 : .append(CF_LNG_NAME)
9832 32 : .c_str());
9833 32 : if (!pszLongName)
9834 25 : pszLongName = "";
9835 : }
9836 :
9837 97 : constexpr bool bSignedData = false;
9838 :
9839 97 : if (nDim > 2)
9840 27 : poBand = new netCDFRasterBand(
9841 27 : netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
9842 : bSignedData, szBandName, pszLongName, nBandID, nDim, iBand - 1,
9843 27 : panBandZLev, panBandDimPos, panDimIds);
9844 : else
9845 70 : poBand = new netCDFRasterBand(
9846 70 : netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
9847 70 : bSignedData, szBandName, pszLongName);
9848 :
9849 97 : poDS->SetBand(iBand, poBand);
9850 :
9851 : // Set nodata value, if any.
9852 97 : GDALCopyNoDataValue(poBand, poSrcBand);
9853 :
9854 : // Copy Metadata for band.
9855 97 : CopyMetadata(nullptr, poSrcDS->GetRasterBand(iBand), poBand,
9856 : poDS->cdfid, poBand->nZId);
9857 :
9858 : // If more than 2D pass the first band's netcdf var ID to subsequent
9859 : // bands.
9860 97 : if (nDim > 2)
9861 27 : nBandID = poBand->nZId;
9862 : }
9863 :
9864 : // Write projection variable to band variable.
9865 64 : poDS->AddGridMappingRef();
9866 :
9867 64 : pfnProgress(0.5, nullptr, pProgressData);
9868 :
9869 : // Write bands.
9870 :
9871 : // Make sure we are in data mode.
9872 64 : poDS->SetDefineMode(false);
9873 :
9874 64 : double dfTemp = 0.5;
9875 :
9876 64 : eErr = CE_None;
9877 :
9878 161 : for (int iBand = 1; iBand <= nBands && eErr == CE_None; iBand++)
9879 : {
9880 97 : const double dfTemp2 = dfTemp + 0.4 / nBands;
9881 97 : pScaledProgress = GDALCreateScaledProgress(dfTemp, dfTemp2, pfnProgress,
9882 : pProgressData);
9883 97 : dfTemp = dfTemp2;
9884 :
9885 97 : CPLDebug("GDAL_netCDF", "copying band data # %d/%d ", iBand, nBands);
9886 :
9887 97 : poSrcBand = poSrcDS->GetRasterBand(iBand);
9888 97 : eDT = poSrcBand->GetRasterDataType();
9889 :
9890 97 : GDALRasterBand *poDstBand = poDS->GetRasterBand(iBand);
9891 :
9892 : // Copy band data.
9893 97 : if (eDT == GDT_UInt8)
9894 : {
9895 57 : CPLDebug("GDAL_netCDF", "GByte Band#%d", iBand);
9896 57 : eErr = NCDFCopyBand<GByte>(poSrcBand, poDstBand, nXSize, nYSize,
9897 : GDALScaledProgress, pScaledProgress);
9898 : }
9899 40 : else if (eDT == GDT_Int8)
9900 : {
9901 1 : CPLDebug("GDAL_netCDF", "GInt8 Band#%d", iBand);
9902 1 : eErr = NCDFCopyBand<GInt8>(poSrcBand, poDstBand, nXSize, nYSize,
9903 : GDALScaledProgress, pScaledProgress);
9904 : }
9905 39 : else if (eDT == GDT_UInt16)
9906 : {
9907 2 : CPLDebug("GDAL_netCDF", "GUInt16 Band#%d", iBand);
9908 2 : eErr = NCDFCopyBand<GInt16>(poSrcBand, poDstBand, nXSize, nYSize,
9909 : GDALScaledProgress, pScaledProgress);
9910 : }
9911 37 : else if (eDT == GDT_Int16)
9912 : {
9913 5 : CPLDebug("GDAL_netCDF", "GInt16 Band#%d", iBand);
9914 5 : eErr = NCDFCopyBand<GUInt16>(poSrcBand, poDstBand, nXSize, nYSize,
9915 : GDALScaledProgress, pScaledProgress);
9916 : }
9917 32 : else if (eDT == GDT_UInt32)
9918 : {
9919 2 : CPLDebug("GDAL_netCDF", "GUInt32 Band#%d", iBand);
9920 2 : eErr = NCDFCopyBand<GUInt32>(poSrcBand, poDstBand, nXSize, nYSize,
9921 : GDALScaledProgress, pScaledProgress);
9922 : }
9923 30 : else if (eDT == GDT_Int32)
9924 : {
9925 18 : CPLDebug("GDAL_netCDF", "GInt32 Band#%d", iBand);
9926 18 : eErr = NCDFCopyBand<GInt32>(poSrcBand, poDstBand, nXSize, nYSize,
9927 : GDALScaledProgress, pScaledProgress);
9928 : }
9929 12 : else if (eDT == GDT_UInt64)
9930 : {
9931 2 : CPLDebug("GDAL_netCDF", "GUInt64 Band#%d", iBand);
9932 2 : eErr = NCDFCopyBand<std::uint64_t>(poSrcBand, poDstBand, nXSize,
9933 : nYSize, GDALScaledProgress,
9934 : pScaledProgress);
9935 : }
9936 10 : else if (eDT == GDT_Int64)
9937 : {
9938 2 : CPLDebug("GDAL_netCDF", "GInt64 Band#%d", iBand);
9939 : eErr =
9940 2 : NCDFCopyBand<std::int64_t>(poSrcBand, poDstBand, nXSize, nYSize,
9941 : GDALScaledProgress, pScaledProgress);
9942 : }
9943 8 : else if (eDT == GDT_Float32)
9944 : {
9945 6 : CPLDebug("GDAL_netCDF", "float Band#%d", iBand);
9946 6 : eErr = NCDFCopyBand<float>(poSrcBand, poDstBand, nXSize, nYSize,
9947 : GDALScaledProgress, pScaledProgress);
9948 : }
9949 2 : else if (eDT == GDT_Float64)
9950 : {
9951 2 : CPLDebug("GDAL_netCDF", "double Band#%d", iBand);
9952 2 : eErr = NCDFCopyBand<double>(poSrcBand, poDstBand, nXSize, nYSize,
9953 : GDALScaledProgress, pScaledProgress);
9954 : }
9955 : else
9956 : {
9957 0 : CPLError(CE_Failure, CPLE_NotSupported,
9958 : "The NetCDF driver does not support GDAL data type %d",
9959 : eDT);
9960 : }
9961 :
9962 97 : GDALDestroyScaledProgress(pScaledProgress);
9963 : }
9964 :
9965 64 : delete (poDS);
9966 :
9967 64 : CPLFree(panDimIds);
9968 64 : CPLFree(panBandDimPos);
9969 64 : CPLFree(panBandZLev);
9970 64 : CPLFree(panDimVarIds);
9971 :
9972 64 : if (eErr != CE_None)
9973 0 : return nullptr;
9974 :
9975 64 : pfnProgress(0.95, nullptr, pProgressData);
9976 :
9977 : // Re-open dataset so we can return it.
9978 128 : CPLStringList aosOpenOptions;
9979 64 : aosOpenOptions.AddString("VARIABLES_AS_BANDS=YES");
9980 64 : GDALOpenInfo oOpenInfo(pszFilename, GA_Update);
9981 64 : oOpenInfo.nOpenFlags = GDAL_OF_RASTER | GDAL_OF_UPDATE;
9982 64 : oOpenInfo.papszOpenOptions = aosOpenOptions.List();
9983 64 : auto poRetDS = Open(&oOpenInfo);
9984 :
9985 : // PAM cloning is disabled. See bug #4244.
9986 : // if( poDS )
9987 : // poDS->CloneInfo(poSrcDS, GCIF_PAM_DEFAULT);
9988 :
9989 64 : pfnProgress(1.0, nullptr, pProgressData);
9990 :
9991 64 : return poRetDS;
9992 : }
9993 :
9994 : // Note: some logic depends on bIsProjected and bIsGeoGraphic.
9995 : // May not be known when Create() is called, see AddProjectionVars().
9996 314 : void netCDFDataset::ProcessCreationOptions()
9997 : {
9998 314 : const char *pszConfig = aosCreationOptions.FetchNameValue("CONFIG_FILE");
9999 314 : if (pszConfig != nullptr)
10000 : {
10001 4 : if (oWriterConfig.Parse(pszConfig))
10002 : {
10003 : // Override dataset creation options from the config file
10004 2 : for (const auto &[osName, osValue] :
10005 4 : oWriterConfig.m_oDatasetCreationOptions)
10006 : {
10007 1 : aosCreationOptions.SetNameValue(osName, osValue);
10008 : }
10009 : }
10010 : }
10011 :
10012 : // File format.
10013 314 : eFormat = NCDF_FORMAT_NC;
10014 314 : const char *pszValue = aosCreationOptions.FetchNameValue("FORMAT");
10015 314 : if (pszValue != nullptr)
10016 : {
10017 146 : if (EQUAL(pszValue, "NC"))
10018 : {
10019 3 : eFormat = NCDF_FORMAT_NC;
10020 : }
10021 : #ifdef NETCDF_HAS_NC2
10022 143 : else if (EQUAL(pszValue, "NC2"))
10023 : {
10024 1 : eFormat = NCDF_FORMAT_NC2;
10025 : }
10026 : #endif
10027 142 : else if (EQUAL(pszValue, "NC4"))
10028 : {
10029 138 : eFormat = NCDF_FORMAT_NC4;
10030 : }
10031 4 : else if (EQUAL(pszValue, "NC4C"))
10032 : {
10033 4 : eFormat = NCDF_FORMAT_NC4C;
10034 : }
10035 : else
10036 : {
10037 0 : CPLError(CE_Failure, CPLE_NotSupported,
10038 : "FORMAT=%s in not supported, using the default NC format.",
10039 : pszValue);
10040 : }
10041 : }
10042 :
10043 : // COMPRESS option.
10044 314 : pszValue = aosCreationOptions.FetchNameValue("COMPRESS");
10045 314 : if (pszValue != nullptr)
10046 : {
10047 3 : if (EQUAL(pszValue, "NONE"))
10048 : {
10049 1 : eCompress = NCDF_COMPRESS_NONE;
10050 : }
10051 2 : else if (EQUAL(pszValue, "DEFLATE"))
10052 : {
10053 2 : eCompress = NCDF_COMPRESS_DEFLATE;
10054 2 : if (!((eFormat == NCDF_FORMAT_NC4) ||
10055 2 : (eFormat == NCDF_FORMAT_NC4C)))
10056 : {
10057 1 : CPLError(CE_Warning, CPLE_IllegalArg,
10058 : "NOTICE: Format set to NC4C because compression is "
10059 : "set to DEFLATE.");
10060 1 : eFormat = NCDF_FORMAT_NC4C;
10061 : }
10062 : }
10063 : else
10064 : {
10065 0 : CPLError(CE_Failure, CPLE_NotSupported,
10066 : "COMPRESS=%s is not supported.", pszValue);
10067 : }
10068 : }
10069 :
10070 : // ZLEVEL option.
10071 314 : pszValue = aosCreationOptions.FetchNameValue("ZLEVEL");
10072 314 : if (pszValue != nullptr)
10073 : {
10074 1 : nZLevel = atoi(pszValue);
10075 1 : if (!(nZLevel >= 1 && nZLevel <= 9))
10076 : {
10077 0 : CPLError(CE_Warning, CPLE_IllegalArg,
10078 : "ZLEVEL=%s value not recognised, ignoring.", pszValue);
10079 0 : nZLevel = NCDF_DEFLATE_LEVEL;
10080 : }
10081 : }
10082 :
10083 : // CHUNKING option.
10084 314 : bChunking = aosCreationOptions.FetchBool("CHUNKING", true);
10085 :
10086 : // MULTIPLE_LAYERS option.
10087 : const char *pszMultipleLayerBehavior =
10088 314 : aosCreationOptions.FetchNameValueDef("MULTIPLE_LAYERS", "NO");
10089 : const char *pszGeometryEnc =
10090 314 : aosCreationOptions.FetchNameValueDef("GEOMETRY_ENCODING", "CF_1.8");
10091 314 : if (EQUAL(pszMultipleLayerBehavior, "NO") ||
10092 4 : EQUAL(pszGeometryEnc, "CF_1.8"))
10093 : {
10094 310 : eMultipleLayerBehavior = SINGLE_LAYER;
10095 : }
10096 4 : else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_FILES"))
10097 : {
10098 3 : eMultipleLayerBehavior = SEPARATE_FILES;
10099 : }
10100 1 : else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_GROUPS"))
10101 : {
10102 1 : if (eFormat == NCDF_FORMAT_NC4)
10103 : {
10104 1 : eMultipleLayerBehavior = SEPARATE_GROUPS;
10105 : }
10106 : else
10107 : {
10108 0 : CPLError(CE_Warning, CPLE_IllegalArg,
10109 : "MULTIPLE_LAYERS=%s is recognised only with FORMAT=NC4",
10110 : pszMultipleLayerBehavior);
10111 : }
10112 : }
10113 : else
10114 : {
10115 0 : CPLError(CE_Warning, CPLE_IllegalArg,
10116 : "MULTIPLE_LAYERS=%s not recognised", pszMultipleLayerBehavior);
10117 : }
10118 :
10119 : // Set nCreateMode based on eFormat.
10120 314 : switch (eFormat)
10121 : {
10122 : #ifdef NETCDF_HAS_NC2
10123 1 : case NCDF_FORMAT_NC2:
10124 1 : nCreateMode = NC_CLOBBER | NC_64BIT_OFFSET;
10125 1 : break;
10126 : #endif
10127 138 : case NCDF_FORMAT_NC4:
10128 138 : nCreateMode = NC_CLOBBER | NC_NETCDF4;
10129 138 : break;
10130 5 : case NCDF_FORMAT_NC4C:
10131 5 : nCreateMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
10132 5 : break;
10133 170 : case NCDF_FORMAT_NC:
10134 : default:
10135 170 : nCreateMode = NC_CLOBBER;
10136 170 : break;
10137 : }
10138 :
10139 314 : CPLDebug("GDAL_netCDF", "file options: format=%d compress=%d zlevel=%d",
10140 314 : eFormat, eCompress, nZLevel);
10141 314 : }
10142 :
10143 288 : int netCDFDataset::DefVarDeflate(int nVarId, bool bChunkingArg) const
10144 : {
10145 288 : if (eCompress == NCDF_COMPRESS_DEFLATE)
10146 : {
10147 : // Must set chunk size to avoid huge performance hit (set
10148 : // bChunkingArg=TRUE)
10149 : // perhaps another solution it to change the chunk cache?
10150 : // http://www.unidata.ucar.edu/software/netcdf/docs/netcdf.html#Chunk-Cache
10151 : // TODO: make sure this is okay.
10152 2 : CPLDebug("GDAL_netCDF", "DefVarDeflate(%d, %d) nZlevel=%d", nVarId,
10153 2 : static_cast<int>(bChunkingArg), nZLevel);
10154 :
10155 2 : int status = nc_def_var_deflate(cdfid, nVarId, 1, 1, nZLevel);
10156 2 : NCDF_ERR(status);
10157 :
10158 2 : if (status == NC_NOERR && bChunkingArg && bChunking)
10159 : {
10160 : // set chunking to be 1 for all dims, except X dim
10161 : // size_t chunksize[] = { 1, (size_t)nRasterXSize };
10162 : size_t chunksize[MAX_NC_DIMS];
10163 : int nd;
10164 2 : nc_inq_varndims(cdfid, nVarId, &nd);
10165 2 : chunksize[0] = (size_t)1;
10166 2 : chunksize[1] = (size_t)1;
10167 2 : for (int i = 2; i < nd; i++)
10168 0 : chunksize[i] = (size_t)1;
10169 2 : chunksize[nd - 1] = (size_t)nRasterXSize;
10170 :
10171 : // Config options just for testing purposes
10172 : const char *pszBlockXSize =
10173 2 : CPLGetConfigOption("BLOCKXSIZE", nullptr);
10174 2 : if (pszBlockXSize)
10175 0 : chunksize[nd - 1] = (size_t)atoi(pszBlockXSize);
10176 :
10177 : const char *pszBlockYSize =
10178 2 : CPLGetConfigOption("BLOCKYSIZE", nullptr);
10179 2 : if (nd >= 2 && pszBlockYSize)
10180 0 : chunksize[nd - 2] = (size_t)atoi(pszBlockYSize);
10181 :
10182 2 : CPLDebug("GDAL_netCDF",
10183 : "DefVarDeflate() chunksize={%ld, %ld} chunkX=%ld nd=%d",
10184 2 : (long)chunksize[0], (long)chunksize[1],
10185 2 : (long)chunksize[nd - 1], nd);
10186 : #ifdef NCDF_DEBUG
10187 : for (int i = 0; i < nd; i++)
10188 : CPLDebug("GDAL_netCDF", "DefVarDeflate() chunk[%d]=%ld", i,
10189 : chunksize[i]);
10190 : #endif
10191 :
10192 2 : status = nc_def_var_chunking(cdfid, nVarId, NC_CHUNKED, chunksize);
10193 2 : NCDF_ERR(status);
10194 : }
10195 : else
10196 : {
10197 0 : CPLDebug("GDAL_netCDF", "chunksize not set");
10198 : }
10199 2 : return status;
10200 : }
10201 286 : return NC_NOERR;
10202 : }
10203 :
10204 : /************************************************************************/
10205 : /* NCDFUnloadDriver() */
10206 : /************************************************************************/
10207 :
10208 9 : static void NCDFUnloadDriver(CPL_UNUSED GDALDriver *poDriver)
10209 : {
10210 9 : if (hNCMutex != nullptr)
10211 5 : CPLDestroyMutex(hNCMutex);
10212 9 : hNCMutex = nullptr;
10213 9 : }
10214 :
10215 : /************************************************************************/
10216 : /* GDALRegister_netCDF() */
10217 : /************************************************************************/
10218 :
10219 : class GDALnetCDFDriver final : public GDALDriver
10220 : {
10221 : public:
10222 19 : GDALnetCDFDriver() = default;
10223 :
10224 : const char *GetMetadataItem(const char *pszName,
10225 : const char *pszDomain) override;
10226 :
10227 93 : CSLConstList GetMetadata(const char *pszDomain) override
10228 : {
10229 186 : std::lock_guard oLock(m_oMutex);
10230 93 : InitializeDCAPVirtualIO();
10231 186 : return GDALDriver::GetMetadata(pszDomain);
10232 : }
10233 :
10234 : private:
10235 : std::recursive_mutex m_oMutex{};
10236 : bool m_bInitialized = false;
10237 :
10238 106 : void InitializeDCAPVirtualIO()
10239 : {
10240 106 : if (!m_bInitialized)
10241 : {
10242 12 : m_bInitialized = true;
10243 :
10244 : #ifdef ENABLE_UFFD
10245 12 : if (CPLIsUserFaultMappingSupported())
10246 : {
10247 12 : SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
10248 : }
10249 : #endif
10250 : }
10251 106 : }
10252 : };
10253 :
10254 1412 : const char *GDALnetCDFDriver::GetMetadataItem(const char *pszName,
10255 : const char *pszDomain)
10256 : {
10257 2824 : std::lock_guard oLock(m_oMutex);
10258 1412 : if (EQUAL(pszName, GDAL_DCAP_VIRTUALIO))
10259 : {
10260 13 : InitializeDCAPVirtualIO();
10261 : }
10262 2824 : return GDALDriver::GetMetadataItem(pszName, pszDomain);
10263 : }
10264 :
10265 19 : void GDALRegister_netCDF()
10266 :
10267 : {
10268 19 : if (!GDAL_CHECK_VERSION("netCDF driver"))
10269 0 : return;
10270 :
10271 19 : if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
10272 0 : return;
10273 :
10274 19 : GDALDriver *poDriver = new GDALnetCDFDriver();
10275 19 : netCDFDriverSetCommonMetadata(poDriver);
10276 :
10277 19 : poDriver->SetMetadataItem("NETCDF_CONVENTIONS",
10278 19 : GDAL_DEFAULT_NCDF_CONVENTIONS);
10279 19 : poDriver->SetMetadataItem("NETCDF_VERSION", nc_inq_libvers());
10280 :
10281 : // Set pfns and register driver.
10282 19 : poDriver->pfnOpen = netCDFDataset::Open;
10283 19 : poDriver->pfnCreateCopy = netCDFDataset::CreateCopy;
10284 19 : poDriver->pfnCreate = netCDFDataset::Create;
10285 19 : poDriver->pfnCreateMultiDimensional = netCDFDataset::CreateMultiDimensional;
10286 19 : poDriver->pfnUnloadDriver = NCDFUnloadDriver;
10287 :
10288 19 : GetGDALDriverManager()->RegisterDriver(poDriver);
10289 : }
10290 :
10291 : /************************************************************************/
10292 : /* New functions */
10293 : /************************************************************************/
10294 :
10295 : /* Test for GDAL version string >= target */
10296 257 : static bool NCDFIsGDALVersionGTE(const char *pszVersion, int nTarget)
10297 : {
10298 :
10299 : // Valid strings are "GDAL 1.9dev, released 2011/01/18" and "GDAL 1.8.1 ".
10300 257 : if (pszVersion == nullptr || EQUAL(pszVersion, ""))
10301 0 : return false;
10302 257 : else if (!STARTS_WITH_CI(pszVersion, "GDAL "))
10303 0 : return false;
10304 : // 2.0dev of 2011/12/29 has been later renamed as 1.10dev.
10305 257 : else if (EQUAL("GDAL 2.0dev, released 2011/12/29", pszVersion))
10306 0 : return nTarget <= GDAL_COMPUTE_VERSION(1, 10, 0);
10307 257 : else if (STARTS_WITH_CI(pszVersion, "GDAL 1.9dev"))
10308 2 : return nTarget <= 1900;
10309 255 : else if (STARTS_WITH_CI(pszVersion, "GDAL 1.8dev"))
10310 0 : return nTarget <= 1800;
10311 :
10312 255 : const CPLStringList aosTokens(CSLTokenizeString2(pszVersion + 5, ".", 0));
10313 :
10314 255 : int nVersions[] = {0, 0, 0, 0};
10315 1020 : for (int iToken = 0; iToken < std::min(4, aosTokens.size()); iToken++)
10316 : {
10317 765 : nVersions[iToken] = atoi(aosTokens[iToken]);
10318 765 : if (nVersions[iToken] < 0)
10319 0 : nVersions[iToken] = 0;
10320 765 : else if (nVersions[iToken] > 99)
10321 0 : nVersions[iToken] = 99;
10322 : }
10323 :
10324 255 : int nVersion = 0;
10325 255 : if (nVersions[0] > 1 || nVersions[1] >= 10)
10326 255 : nVersion =
10327 255 : GDAL_COMPUTE_VERSION(nVersions[0], nVersions[1], nVersions[2]);
10328 : else
10329 0 : nVersion = nVersions[0] * 1000 + nVersions[1] * 100 +
10330 0 : nVersions[2] * 10 + nVersions[3];
10331 :
10332 255 : return nTarget <= nVersion;
10333 : }
10334 :
10335 : // Add Conventions, GDAL version and history.
10336 173 : static void NCDFAddGDALHistory(int fpImage, const char *pszFilename,
10337 : bool bWriteGDALVersion, bool bWriteGDALHistory,
10338 : const char *pszOldHist,
10339 : const char *pszFunctionName,
10340 : const char *pszCFVersion)
10341 : {
10342 173 : if (pszCFVersion == nullptr)
10343 : {
10344 48 : pszCFVersion = GDAL_DEFAULT_NCDF_CONVENTIONS;
10345 : }
10346 173 : int status = nc_put_att_text(fpImage, NC_GLOBAL, "Conventions",
10347 : strlen(pszCFVersion), pszCFVersion);
10348 173 : NCDF_ERR(status);
10349 :
10350 173 : if (bWriteGDALVersion)
10351 : {
10352 171 : const char *pszNCDF_GDAL = GDALVersionInfo("--version");
10353 171 : status = nc_put_att_text(fpImage, NC_GLOBAL, "GDAL",
10354 : strlen(pszNCDF_GDAL), pszNCDF_GDAL);
10355 171 : NCDF_ERR(status);
10356 : }
10357 :
10358 173 : if (bWriteGDALHistory)
10359 : {
10360 : // Add history.
10361 342 : CPLString osTmp;
10362 : #ifdef GDAL_SET_CMD_LINE_DEFINED_TMP
10363 : if (!EQUAL(GDALGetCmdLine(), ""))
10364 : osTmp = GDALGetCmdLine();
10365 : else
10366 : osTmp =
10367 : CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
10368 : #else
10369 171 : osTmp = CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
10370 : #endif
10371 :
10372 171 : NCDFAddHistory(fpImage, osTmp.c_str(), pszOldHist);
10373 : }
10374 2 : else if (pszOldHist != nullptr)
10375 : {
10376 0 : status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
10377 : strlen(pszOldHist), pszOldHist);
10378 0 : NCDF_ERR(status);
10379 : }
10380 173 : }
10381 :
10382 : // Code taken from cdo and libcdi, used for writing the history attribute.
10383 :
10384 : // void cdoDefHistory(int fileID, char *histstring)
10385 171 : static void NCDFAddHistory(int fpImage, const char *pszAddHist,
10386 : const char *pszOldHist)
10387 : {
10388 : // Check pszOldHist - as if there was no previous history, it will be
10389 : // a null pointer - if so set as empty.
10390 171 : if (nullptr == pszOldHist)
10391 : {
10392 59 : pszOldHist = "";
10393 : }
10394 :
10395 : char strtime[32];
10396 171 : strtime[0] = '\0';
10397 :
10398 171 : time_t tp = time(nullptr);
10399 171 : if (tp != -1)
10400 : {
10401 : struct tm ltime;
10402 171 : VSILocalTime(&tp, <ime);
10403 171 : (void)strftime(strtime, sizeof(strtime),
10404 : "%a %b %d %H:%M:%S %Y: ", <ime);
10405 : }
10406 :
10407 : // status = nc_get_att_text(fpImage, NC_GLOBAL,
10408 : // "history", pszOldHist);
10409 : // printf("status: %d pszOldHist: [%s]\n",status,pszOldHist);
10410 :
10411 171 : size_t nNewHistSize =
10412 171 : strlen(pszOldHist) + strlen(strtime) + strlen(pszAddHist) + 1 + 1;
10413 : char *pszNewHist =
10414 171 : static_cast<char *>(CPLMalloc(nNewHistSize * sizeof(char)));
10415 :
10416 171 : strcpy(pszNewHist, strtime);
10417 171 : strcat(pszNewHist, pszAddHist);
10418 :
10419 : // int disableHistory = FALSE;
10420 : // if( !disableHistory )
10421 : {
10422 171 : if (!EQUAL(pszOldHist, ""))
10423 3 : strcat(pszNewHist, "\n");
10424 171 : strcat(pszNewHist, pszOldHist);
10425 : }
10426 :
10427 171 : const int status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
10428 : strlen(pszNewHist), pszNewHist);
10429 171 : NCDF_ERR(status);
10430 :
10431 171 : CPLFree(pszNewHist);
10432 171 : }
10433 :
10434 6640 : static CPLErr NCDFSafeStrcat(char **ppszDest, const char *pszSrc,
10435 : size_t *nDestSize)
10436 : {
10437 : /* Reallocate the data string until the content fits */
10438 6640 : while (*nDestSize < (strlen(*ppszDest) + strlen(pszSrc) + 1))
10439 : {
10440 413 : (*nDestSize) *= 2;
10441 413 : *ppszDest = static_cast<char *>(
10442 413 : CPLRealloc(reinterpret_cast<void *>(*ppszDest), *nDestSize));
10443 : #ifdef NCDF_DEBUG
10444 : CPLDebug("GDAL_netCDF", "NCDFSafeStrcat() resized str from %ld to %ld",
10445 : (*nDestSize) / 2, *nDestSize);
10446 : #endif
10447 : }
10448 6227 : strcat(*ppszDest, pszSrc);
10449 :
10450 6227 : return CE_None;
10451 : }
10452 :
10453 : /* helper function for NCDFGetAttr() */
10454 : /* if pdfValue != nullptr, sets *pdfValue to first value returned */
10455 : /* if ppszValue != nullptr, sets *ppszValue with all attribute values */
10456 : /* *ppszValue is the responsibility of the caller and must be freed */
10457 68566 : static CPLErr NCDFGetAttr1(int nCdfId, int nVarId, const char *pszAttrName,
10458 : double *pdfValue, char **ppszValue)
10459 : {
10460 68566 : nc_type nAttrType = NC_NAT;
10461 68566 : size_t nAttrLen = 0;
10462 :
10463 68566 : if (ppszValue)
10464 67404 : *ppszValue = nullptr;
10465 :
10466 68566 : int status = nc_inq_att(nCdfId, nVarId, pszAttrName, &nAttrType, &nAttrLen);
10467 68566 : if (status != NC_NOERR)
10468 36699 : return CE_Failure;
10469 :
10470 : #ifdef NCDF_DEBUG
10471 : CPLDebug("GDAL_netCDF", "NCDFGetAttr1(%s) len=%ld type=%d", pszAttrName,
10472 : nAttrLen, nAttrType);
10473 : #endif
10474 31867 : if (nAttrLen == 0 && nAttrType != NC_CHAR)
10475 1 : return CE_Failure;
10476 :
10477 : /* Allocate guaranteed minimum size (use 10 or 20 if not a string) */
10478 31866 : size_t nAttrValueSize = nAttrLen + 1;
10479 31866 : if (nAttrType != NC_CHAR && nAttrValueSize < 10)
10480 3574 : nAttrValueSize = 10;
10481 31866 : if (nAttrType == NC_DOUBLE && nAttrValueSize < 20)
10482 1722 : nAttrValueSize = 20;
10483 31866 : if (nAttrType == NC_INT64 && nAttrValueSize < 20)
10484 22 : nAttrValueSize = 22;
10485 : char *pszAttrValue =
10486 31866 : static_cast<char *>(CPLCalloc(nAttrValueSize, sizeof(char)));
10487 31866 : *pszAttrValue = '\0';
10488 :
10489 31866 : if (nAttrLen > 1 && nAttrType != NC_CHAR)
10490 638 : NCDFSafeStrcat(&pszAttrValue, "{", &nAttrValueSize);
10491 :
10492 31866 : double dfValue = 0.0;
10493 31866 : size_t m = 0;
10494 : char szTemp[256];
10495 31866 : bool bSetDoubleFromStr = false;
10496 :
10497 31866 : switch (nAttrType)
10498 : {
10499 28290 : case NC_CHAR:
10500 28290 : CPL_IGNORE_RET_VAL(
10501 28290 : nc_get_att_text(nCdfId, nVarId, pszAttrName, pszAttrValue));
10502 28290 : pszAttrValue[nAttrLen] = '\0';
10503 28290 : bSetDoubleFromStr = true;
10504 28290 : dfValue = 0.0;
10505 28290 : break;
10506 94 : case NC_BYTE:
10507 : {
10508 : signed char *pscTemp = static_cast<signed char *>(
10509 94 : CPLCalloc(nAttrLen, sizeof(signed char)));
10510 94 : nc_get_att_schar(nCdfId, nVarId, pszAttrName, pscTemp);
10511 94 : dfValue = static_cast<double>(pscTemp[0]);
10512 94 : if (nAttrLen > 1)
10513 : {
10514 24 : for (m = 0; m < nAttrLen - 1; m++)
10515 : {
10516 13 : snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
10517 13 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10518 : }
10519 : }
10520 94 : snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
10521 94 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10522 94 : CPLFree(pscTemp);
10523 94 : break;
10524 : }
10525 523 : case NC_SHORT:
10526 : {
10527 : short *psTemp =
10528 523 : static_cast<short *>(CPLCalloc(nAttrLen, sizeof(short)));
10529 523 : nc_get_att_short(nCdfId, nVarId, pszAttrName, psTemp);
10530 523 : dfValue = static_cast<double>(psTemp[0]);
10531 523 : if (nAttrLen > 1)
10532 : {
10533 840 : for (m = 0; m < nAttrLen - 1; m++)
10534 : {
10535 420 : snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
10536 420 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10537 : }
10538 : }
10539 523 : snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
10540 523 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10541 523 : CPLFree(psTemp);
10542 523 : break;
10543 : }
10544 530 : case NC_INT:
10545 : {
10546 530 : int *pnTemp = static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
10547 530 : nc_get_att_int(nCdfId, nVarId, pszAttrName, pnTemp);
10548 530 : dfValue = static_cast<double>(pnTemp[0]);
10549 530 : if (nAttrLen > 1)
10550 : {
10551 218 : for (m = 0; m < nAttrLen - 1; m++)
10552 : {
10553 139 : snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
10554 139 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10555 : }
10556 : }
10557 530 : snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
10558 530 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10559 530 : CPLFree(pnTemp);
10560 530 : break;
10561 : }
10562 395 : case NC_FLOAT:
10563 : {
10564 : float *pfTemp =
10565 395 : static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
10566 395 : nc_get_att_float(nCdfId, nVarId, pszAttrName, pfTemp);
10567 395 : dfValue = static_cast<double>(pfTemp[0]);
10568 395 : if (nAttrLen > 1)
10569 : {
10570 60 : for (m = 0; m < nAttrLen - 1; m++)
10571 : {
10572 30 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
10573 30 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10574 : }
10575 : }
10576 395 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
10577 395 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10578 395 : CPLFree(pfTemp);
10579 395 : break;
10580 : }
10581 1722 : case NC_DOUBLE:
10582 : {
10583 : double *pdfTemp =
10584 1722 : static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
10585 1722 : nc_get_att_double(nCdfId, nVarId, pszAttrName, pdfTemp);
10586 1722 : dfValue = pdfTemp[0];
10587 1722 : if (nAttrLen > 1)
10588 : {
10589 166 : for (m = 0; m < nAttrLen - 1; m++)
10590 : {
10591 90 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
10592 90 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10593 : }
10594 : }
10595 1722 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
10596 1722 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10597 1722 : CPLFree(pdfTemp);
10598 1722 : break;
10599 : }
10600 167 : case NC_STRING:
10601 : {
10602 : char **ppszTemp =
10603 167 : static_cast<char **>(CPLCalloc(nAttrLen, sizeof(char *)));
10604 167 : nc_get_att_string(nCdfId, nVarId, pszAttrName, ppszTemp);
10605 167 : bSetDoubleFromStr = true;
10606 167 : dfValue = 0.0;
10607 167 : if (nAttrLen > 1)
10608 : {
10609 19 : for (m = 0; m < nAttrLen - 1; m++)
10610 : {
10611 12 : NCDFSafeStrcat(&pszAttrValue,
10612 12 : ppszTemp[m] ? ppszTemp[m] : "{NULL}",
10613 : &nAttrValueSize);
10614 12 : NCDFSafeStrcat(&pszAttrValue, ",", &nAttrValueSize);
10615 : }
10616 : }
10617 167 : NCDFSafeStrcat(&pszAttrValue, ppszTemp[m] ? ppszTemp[m] : "{NULL}",
10618 : &nAttrValueSize);
10619 167 : nc_free_string(nAttrLen, ppszTemp);
10620 167 : CPLFree(ppszTemp);
10621 167 : break;
10622 : }
10623 28 : case NC_UBYTE:
10624 : {
10625 : unsigned char *pucTemp = static_cast<unsigned char *>(
10626 28 : CPLCalloc(nAttrLen, sizeof(unsigned char)));
10627 28 : nc_get_att_uchar(nCdfId, nVarId, pszAttrName, pucTemp);
10628 28 : dfValue = static_cast<double>(pucTemp[0]);
10629 28 : if (nAttrLen > 1)
10630 : {
10631 0 : for (m = 0; m < nAttrLen - 1; m++)
10632 : {
10633 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
10634 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10635 : }
10636 : }
10637 28 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
10638 28 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10639 28 : CPLFree(pucTemp);
10640 28 : break;
10641 : }
10642 26 : case NC_USHORT:
10643 : {
10644 : unsigned short *pusTemp = static_cast<unsigned short *>(
10645 26 : CPLCalloc(nAttrLen, sizeof(unsigned short)));
10646 26 : nc_get_att_ushort(nCdfId, nVarId, pszAttrName, pusTemp);
10647 26 : dfValue = static_cast<double>(pusTemp[0]);
10648 26 : if (nAttrLen > 1)
10649 : {
10650 10 : for (m = 0; m < nAttrLen - 1; m++)
10651 : {
10652 5 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
10653 5 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10654 : }
10655 : }
10656 26 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
10657 26 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10658 26 : CPLFree(pusTemp);
10659 26 : break;
10660 : }
10661 21 : case NC_UINT:
10662 : {
10663 : unsigned int *punTemp =
10664 21 : static_cast<unsigned int *>(CPLCalloc(nAttrLen, sizeof(int)));
10665 21 : nc_get_att_uint(nCdfId, nVarId, pszAttrName, punTemp);
10666 21 : dfValue = static_cast<double>(punTemp[0]);
10667 21 : if (nAttrLen > 1)
10668 : {
10669 0 : for (m = 0; m < nAttrLen - 1; m++)
10670 : {
10671 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
10672 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10673 : }
10674 : }
10675 21 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
10676 21 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10677 21 : CPLFree(punTemp);
10678 21 : break;
10679 : }
10680 22 : case NC_INT64:
10681 : {
10682 : GIntBig *panTemp =
10683 22 : static_cast<GIntBig *>(CPLCalloc(nAttrLen, sizeof(GIntBig)));
10684 22 : nc_get_att_longlong(nCdfId, nVarId, pszAttrName, panTemp);
10685 22 : dfValue = static_cast<double>(panTemp[0]);
10686 22 : if (nAttrLen > 1)
10687 : {
10688 0 : for (m = 0; m < nAttrLen - 1; m++)
10689 : {
10690 0 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",",
10691 0 : panTemp[m]);
10692 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10693 : }
10694 : }
10695 22 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, panTemp[m]);
10696 22 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10697 22 : CPLFree(panTemp);
10698 22 : break;
10699 : }
10700 22 : case NC_UINT64:
10701 : {
10702 : GUIntBig *panTemp =
10703 22 : static_cast<GUIntBig *>(CPLCalloc(nAttrLen, sizeof(GUIntBig)));
10704 22 : nc_get_att_ulonglong(nCdfId, nVarId, pszAttrName, panTemp);
10705 22 : dfValue = static_cast<double>(panTemp[0]);
10706 22 : if (nAttrLen > 1)
10707 : {
10708 0 : for (m = 0; m < nAttrLen - 1; m++)
10709 : {
10710 0 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",",
10711 0 : panTemp[m]);
10712 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10713 : }
10714 : }
10715 22 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, panTemp[m]);
10716 22 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10717 22 : CPLFree(panTemp);
10718 22 : break;
10719 : }
10720 26 : default:
10721 26 : CPLDebug("GDAL_netCDF",
10722 : "NCDFGetAttr unsupported type %d for attribute %s",
10723 : nAttrType, pszAttrName);
10724 26 : break;
10725 : }
10726 :
10727 31866 : if (nAttrLen > 1 && nAttrType != NC_CHAR)
10728 638 : NCDFSafeStrcat(&pszAttrValue, "}", &nAttrValueSize);
10729 :
10730 31866 : if (bSetDoubleFromStr)
10731 : {
10732 28457 : if (CPLGetValueType(pszAttrValue) == CPL_VALUE_STRING)
10733 : {
10734 28275 : if (ppszValue == nullptr && pdfValue != nullptr)
10735 : {
10736 1 : CPLFree(pszAttrValue);
10737 1 : return CE_Failure;
10738 : }
10739 : }
10740 28456 : dfValue = CPLAtof(pszAttrValue);
10741 : }
10742 :
10743 : /* set return values */
10744 31865 : if (ppszValue)
10745 31552 : *ppszValue = pszAttrValue;
10746 : else
10747 313 : CPLFree(pszAttrValue);
10748 :
10749 31865 : if (pdfValue)
10750 313 : *pdfValue = dfValue;
10751 :
10752 31865 : return CE_None;
10753 : }
10754 :
10755 : /* sets pdfValue to first value found */
10756 1162 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
10757 : double *pdfValue)
10758 : {
10759 1162 : return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, pdfValue, nullptr);
10760 : }
10761 :
10762 : /* pszValue is the responsibility of the caller and must be freed */
10763 67404 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
10764 : char **pszValue)
10765 : {
10766 67404 : return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, nullptr, pszValue);
10767 : }
10768 :
10769 3113 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
10770 : std::string &osValue)
10771 : {
10772 3113 : nc_type nAttrType = NC_NAT;
10773 3113 : size_t nAttrLen = 0;
10774 :
10775 3113 : int status = nc_inq_att(nCdfId, nVarId, pszAttrName, &nAttrType, &nAttrLen);
10776 3113 : if (status != NC_NOERR)
10777 1412 : return CE_Failure;
10778 :
10779 1701 : if (nAttrType != NC_CHAR)
10780 0 : return CE_Failure;
10781 :
10782 : try
10783 : {
10784 1701 : osValue.resize(nAttrLen, 0);
10785 : }
10786 0 : catch (const std::exception &)
10787 : {
10788 0 : return CE_Failure;
10789 : }
10790 :
10791 1701 : const auto nErr = nc_get_att_text(nCdfId, nVarId, pszAttrName,
10792 1701 : osValue.data()) != NC_NOERR;
10793 1701 : NCDF_ERR_RET(nErr);
10794 :
10795 1701 : return CE_None;
10796 : }
10797 :
10798 : /* By default write NC_CHAR, but detect for int/float/double and */
10799 : /* NC4 string arrays */
10800 112 : static CPLErr NCDFPutAttr(int nCdfId, int nVarId, const char *pszAttrName,
10801 : const char *pszValue)
10802 : {
10803 112 : int status = 0;
10804 112 : char *pszTemp = nullptr;
10805 :
10806 : /* get the attribute values as tokens */
10807 224 : CPLStringList aosValues = NCDFTokenizeArray(pszValue);
10808 112 : if (aosValues.empty())
10809 0 : return CE_Failure;
10810 :
10811 112 : size_t nAttrLen = aosValues.size();
10812 :
10813 : /* first detect type */
10814 112 : nc_type nAttrType = NC_CHAR;
10815 112 : nc_type nTmpAttrType = NC_CHAR;
10816 237 : for (size_t i = 0; i < nAttrLen; i++)
10817 : {
10818 125 : nTmpAttrType = NC_CHAR;
10819 125 : bool bFoundType = false;
10820 125 : errno = 0;
10821 125 : int nValue = static_cast<int>(strtol(aosValues[i], &pszTemp, 10));
10822 : /* test for int */
10823 : /* TODO test for Byte and short - can this be done safely? */
10824 125 : if (errno == 0 && aosValues[i] != pszTemp && *pszTemp == 0)
10825 : {
10826 : char szTemp[256];
10827 19 : CPLsnprintf(szTemp, sizeof(szTemp), "%d", nValue);
10828 19 : if (EQUAL(szTemp, aosValues[i]))
10829 : {
10830 19 : bFoundType = true;
10831 19 : nTmpAttrType = NC_INT;
10832 : }
10833 : else
10834 : {
10835 : unsigned int unValue = static_cast<unsigned int>(
10836 0 : strtoul(aosValues[i], &pszTemp, 10));
10837 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", unValue);
10838 0 : if (EQUAL(szTemp, aosValues[i]))
10839 : {
10840 0 : bFoundType = true;
10841 0 : nTmpAttrType = NC_UINT;
10842 : }
10843 : }
10844 : }
10845 125 : if (!bFoundType)
10846 : {
10847 : /* test for double */
10848 106 : errno = 0;
10849 106 : double dfValue = CPLStrtod(aosValues[i], &pszTemp);
10850 106 : if ((errno == 0) && (aosValues[i] != pszTemp) && (*pszTemp == 0))
10851 : {
10852 : // Test for float instead of double.
10853 : // strtof() is C89, which is not available in MSVC.
10854 : // See if we lose precision if we cast to float and write to
10855 : // char*.
10856 14 : float fValue = float(dfValue);
10857 : char szTemp[256];
10858 14 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", fValue);
10859 14 : if (EQUAL(szTemp, aosValues[i]))
10860 8 : nTmpAttrType = NC_FLOAT;
10861 : else
10862 6 : nTmpAttrType = NC_DOUBLE;
10863 : }
10864 : }
10865 125 : if ((nTmpAttrType <= NC_DOUBLE && nAttrType <= NC_DOUBLE &&
10866 105 : nTmpAttrType > nAttrType) ||
10867 105 : (nTmpAttrType == NC_UINT && nAttrType < NC_FLOAT) ||
10868 5 : (nTmpAttrType >= NC_FLOAT && nAttrType == NC_UINT))
10869 20 : nAttrType = nTmpAttrType;
10870 : }
10871 :
10872 : #ifdef DEBUG
10873 112 : if (EQUAL(pszAttrName, "DEBUG_EMPTY_DOUBLE_ATTR"))
10874 : {
10875 0 : nAttrType = NC_DOUBLE;
10876 0 : nAttrLen = 0;
10877 : }
10878 : #endif
10879 :
10880 : /* now write the data */
10881 112 : if (nAttrType == NC_CHAR)
10882 : {
10883 92 : int nTmpFormat = 0;
10884 92 : if (nAttrLen > 1)
10885 : {
10886 0 : status = nc_inq_format(nCdfId, &nTmpFormat);
10887 0 : NCDF_ERR(status);
10888 : }
10889 92 : if (nAttrLen > 1 && nTmpFormat == NCDF_FORMAT_NC4)
10890 0 : status =
10891 0 : nc_put_att_string(nCdfId, nVarId, pszAttrName, nAttrLen,
10892 0 : const_cast<const char **>(aosValues.List()));
10893 : else
10894 92 : status = nc_put_att_text(nCdfId, nVarId, pszAttrName,
10895 : strlen(pszValue), pszValue);
10896 92 : NCDF_ERR(status);
10897 : }
10898 : else
10899 : {
10900 20 : switch (nAttrType)
10901 : {
10902 11 : case NC_INT:
10903 : {
10904 : int *pnTemp =
10905 11 : static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
10906 30 : for (size_t i = 0; i < nAttrLen; i++)
10907 : {
10908 19 : pnTemp[i] =
10909 19 : static_cast<int>(strtol(aosValues[i], &pszTemp, 10));
10910 : }
10911 11 : status = nc_put_att_int(nCdfId, nVarId, pszAttrName, NC_INT,
10912 : nAttrLen, pnTemp);
10913 11 : NCDF_ERR(status);
10914 11 : CPLFree(pnTemp);
10915 11 : break;
10916 : }
10917 0 : case NC_UINT:
10918 : {
10919 : unsigned int *punTemp = static_cast<unsigned int *>(
10920 0 : CPLCalloc(nAttrLen, sizeof(unsigned int)));
10921 0 : for (size_t i = 0; i < nAttrLen; i++)
10922 : {
10923 0 : punTemp[i] = static_cast<unsigned int>(
10924 0 : strtol(aosValues[i], &pszTemp, 10));
10925 : }
10926 0 : status = nc_put_att_uint(nCdfId, nVarId, pszAttrName, NC_UINT,
10927 : nAttrLen, punTemp);
10928 0 : NCDF_ERR(status);
10929 0 : CPLFree(punTemp);
10930 0 : break;
10931 : }
10932 6 : case NC_FLOAT:
10933 : {
10934 : float *pfTemp =
10935 6 : static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
10936 14 : for (size_t i = 0; i < nAttrLen; i++)
10937 : {
10938 8 : pfTemp[i] =
10939 8 : static_cast<float>(CPLStrtod(aosValues[i], &pszTemp));
10940 : }
10941 6 : status = nc_put_att_float(nCdfId, nVarId, pszAttrName, NC_FLOAT,
10942 : nAttrLen, pfTemp);
10943 6 : NCDF_ERR(status);
10944 6 : CPLFree(pfTemp);
10945 6 : break;
10946 : }
10947 3 : case NC_DOUBLE:
10948 : {
10949 : double *pdfTemp =
10950 3 : static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
10951 9 : for (size_t i = 0; i < nAttrLen; i++)
10952 : {
10953 6 : pdfTemp[i] = CPLStrtod(aosValues[i], &pszTemp);
10954 : }
10955 3 : status = nc_put_att_double(nCdfId, nVarId, pszAttrName,
10956 : NC_DOUBLE, nAttrLen, pdfTemp);
10957 3 : NCDF_ERR(status);
10958 3 : CPLFree(pdfTemp);
10959 3 : break;
10960 : }
10961 0 : default:
10962 0 : return CE_Failure;
10963 : }
10964 : }
10965 :
10966 112 : return CE_None;
10967 : }
10968 :
10969 78 : static CPLErr NCDFGet1DVar(int nCdfId, int nVarId, char **pszValue)
10970 : {
10971 : /* get var information */
10972 78 : int nVarDimId = -1;
10973 78 : int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
10974 78 : if (status != NC_NOERR || nVarDimId != 1)
10975 0 : return CE_Failure;
10976 :
10977 78 : status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
10978 78 : if (status != NC_NOERR)
10979 0 : return CE_Failure;
10980 :
10981 78 : nc_type nVarType = NC_NAT;
10982 78 : status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
10983 78 : if (status != NC_NOERR)
10984 0 : return CE_Failure;
10985 :
10986 78 : size_t nVarLen = 0;
10987 78 : status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
10988 78 : if (status != NC_NOERR)
10989 0 : return CE_Failure;
10990 :
10991 78 : size_t start[1] = {0};
10992 78 : size_t count[1] = {nVarLen};
10993 :
10994 : /* Allocate guaranteed minimum size */
10995 78 : size_t nVarValueSize = NCDF_MAX_STR_LEN;
10996 : char *pszVarValue =
10997 78 : static_cast<char *>(CPLCalloc(nVarValueSize, sizeof(char)));
10998 78 : *pszVarValue = '\0';
10999 :
11000 78 : if (nVarLen == 0)
11001 : {
11002 : /* set return values */
11003 1 : *pszValue = pszVarValue;
11004 :
11005 1 : return CE_None;
11006 : }
11007 :
11008 77 : if (nVarLen > 1 && nVarType != NC_CHAR)
11009 42 : NCDFSafeStrcat(&pszVarValue, "{", &nVarValueSize);
11010 :
11011 77 : switch (nVarType)
11012 : {
11013 0 : case NC_CHAR:
11014 0 : nc_get_vara_text(nCdfId, nVarId, start, count, pszVarValue);
11015 0 : pszVarValue[nVarLen] = '\0';
11016 0 : break;
11017 0 : case NC_BYTE:
11018 : {
11019 : signed char *pscTemp = static_cast<signed char *>(
11020 0 : CPLCalloc(nVarLen, sizeof(signed char)));
11021 0 : nc_get_vara_schar(nCdfId, nVarId, start, count, pscTemp);
11022 : char szTemp[256];
11023 0 : size_t m = 0;
11024 0 : for (; m < nVarLen - 1; m++)
11025 : {
11026 0 : snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
11027 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11028 : }
11029 0 : snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
11030 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11031 0 : CPLFree(pscTemp);
11032 0 : break;
11033 : }
11034 0 : case NC_SHORT:
11035 : {
11036 : short *psTemp =
11037 0 : static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
11038 0 : nc_get_vara_short(nCdfId, nVarId, start, count, psTemp);
11039 : char szTemp[256];
11040 0 : size_t m = 0;
11041 0 : for (; m < nVarLen - 1; m++)
11042 : {
11043 0 : snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
11044 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11045 : }
11046 0 : snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
11047 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11048 0 : CPLFree(psTemp);
11049 0 : break;
11050 : }
11051 21 : case NC_INT:
11052 : {
11053 21 : int *pnTemp = static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
11054 21 : nc_get_vara_int(nCdfId, nVarId, start, count, pnTemp);
11055 : char szTemp[256];
11056 21 : size_t m = 0;
11057 44 : for (; m < nVarLen - 1; m++)
11058 : {
11059 23 : snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
11060 23 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11061 : }
11062 21 : snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
11063 21 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11064 21 : CPLFree(pnTemp);
11065 21 : break;
11066 : }
11067 8 : case NC_FLOAT:
11068 : {
11069 : float *pfTemp =
11070 8 : static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
11071 8 : nc_get_vara_float(nCdfId, nVarId, start, count, pfTemp);
11072 : char szTemp[256];
11073 8 : size_t m = 0;
11074 325 : for (; m < nVarLen - 1; m++)
11075 : {
11076 317 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
11077 317 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11078 : }
11079 8 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
11080 8 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11081 8 : CPLFree(pfTemp);
11082 8 : break;
11083 : }
11084 47 : case NC_DOUBLE:
11085 : {
11086 : double *pdfTemp =
11087 47 : static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
11088 47 : nc_get_vara_double(nCdfId, nVarId, start, count, pdfTemp);
11089 : char szTemp[256];
11090 47 : size_t m = 0;
11091 225 : for (; m < nVarLen - 1; m++)
11092 : {
11093 178 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
11094 178 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11095 : }
11096 47 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
11097 47 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11098 47 : CPLFree(pdfTemp);
11099 47 : break;
11100 : }
11101 0 : case NC_STRING:
11102 : {
11103 : char **ppszTemp =
11104 0 : static_cast<char **>(CPLCalloc(nVarLen, sizeof(char *)));
11105 0 : nc_get_vara_string(nCdfId, nVarId, start, count, ppszTemp);
11106 0 : size_t m = 0;
11107 0 : for (; m < nVarLen - 1; m++)
11108 : {
11109 0 : NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
11110 0 : NCDFSafeStrcat(&pszVarValue, ",", &nVarValueSize);
11111 : }
11112 0 : NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
11113 0 : nc_free_string(nVarLen, ppszTemp);
11114 0 : CPLFree(ppszTemp);
11115 0 : break;
11116 : }
11117 0 : case NC_UBYTE:
11118 : {
11119 : unsigned char *pucTemp = static_cast<unsigned char *>(
11120 0 : CPLCalloc(nVarLen, sizeof(unsigned char)));
11121 0 : nc_get_vara_uchar(nCdfId, nVarId, start, count, pucTemp);
11122 : char szTemp[256];
11123 0 : size_t m = 0;
11124 0 : for (; m < nVarLen - 1; m++)
11125 : {
11126 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
11127 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11128 : }
11129 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
11130 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11131 0 : CPLFree(pucTemp);
11132 0 : break;
11133 : }
11134 0 : case NC_USHORT:
11135 : {
11136 : unsigned short *pusTemp = static_cast<unsigned short *>(
11137 0 : CPLCalloc(nVarLen, sizeof(unsigned short)));
11138 0 : nc_get_vara_ushort(nCdfId, nVarId, start, count, pusTemp);
11139 : char szTemp[256];
11140 0 : size_t m = 0;
11141 0 : for (; m < nVarLen - 1; m++)
11142 : {
11143 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
11144 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11145 : }
11146 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
11147 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11148 0 : CPLFree(pusTemp);
11149 0 : break;
11150 : }
11151 0 : case NC_UINT:
11152 : {
11153 : unsigned int *punTemp = static_cast<unsigned int *>(
11154 0 : CPLCalloc(nVarLen, sizeof(unsigned int)));
11155 0 : nc_get_vara_uint(nCdfId, nVarId, start, count, punTemp);
11156 : char szTemp[256];
11157 0 : size_t m = 0;
11158 0 : for (; m < nVarLen - 1; m++)
11159 : {
11160 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
11161 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11162 : }
11163 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
11164 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11165 0 : CPLFree(punTemp);
11166 0 : break;
11167 : }
11168 1 : case NC_INT64:
11169 : {
11170 : long long *pnTemp =
11171 1 : static_cast<long long *>(CPLCalloc(nVarLen, sizeof(long long)));
11172 1 : nc_get_vara_longlong(nCdfId, nVarId, start, count, pnTemp);
11173 : char szTemp[256];
11174 1 : size_t m = 0;
11175 2 : for (; m < nVarLen - 1; m++)
11176 : {
11177 1 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",", pnTemp[m]);
11178 1 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11179 : }
11180 1 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, pnTemp[m]);
11181 1 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11182 1 : CPLFree(pnTemp);
11183 1 : break;
11184 : }
11185 0 : case NC_UINT64:
11186 : {
11187 : unsigned long long *pnTemp = static_cast<unsigned long long *>(
11188 0 : CPLCalloc(nVarLen, sizeof(unsigned long long)));
11189 0 : nc_get_vara_ulonglong(nCdfId, nVarId, start, count, pnTemp);
11190 : char szTemp[256];
11191 0 : size_t m = 0;
11192 0 : for (; m < nVarLen - 1; m++)
11193 : {
11194 0 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",", pnTemp[m]);
11195 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11196 : }
11197 0 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, pnTemp[m]);
11198 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11199 0 : CPLFree(pnTemp);
11200 0 : break;
11201 : }
11202 0 : default:
11203 0 : CPLDebug("GDAL_netCDF", "NCDFGetVar1D unsupported type %d",
11204 : nVarType);
11205 0 : CPLFree(pszVarValue);
11206 0 : pszVarValue = nullptr;
11207 0 : break;
11208 : }
11209 :
11210 77 : if (pszVarValue != nullptr && nVarLen > 1 && nVarType != NC_CHAR)
11211 42 : NCDFSafeStrcat(&pszVarValue, "}", &nVarValueSize);
11212 :
11213 : /* set return values */
11214 77 : *pszValue = pszVarValue;
11215 :
11216 77 : return CE_None;
11217 : }
11218 :
11219 8 : static CPLErr NCDFPut1DVar(int nCdfId, int nVarId, const char *pszValue)
11220 : {
11221 8 : if (EQUAL(pszValue, ""))
11222 0 : return CE_Failure;
11223 :
11224 : /* get var information */
11225 8 : int nVarDimId = -1;
11226 8 : int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
11227 8 : if (status != NC_NOERR || nVarDimId != 1)
11228 0 : return CE_Failure;
11229 :
11230 8 : status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
11231 8 : if (status != NC_NOERR)
11232 0 : return CE_Failure;
11233 :
11234 8 : nc_type nVarType = NC_CHAR;
11235 8 : status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
11236 8 : if (status != NC_NOERR)
11237 0 : return CE_Failure;
11238 :
11239 8 : size_t nVarLen = 0;
11240 8 : status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
11241 8 : if (status != NC_NOERR)
11242 0 : return CE_Failure;
11243 :
11244 8 : size_t start[1] = {0};
11245 8 : size_t count[1] = {nVarLen};
11246 :
11247 : /* get the values as tokens */
11248 16 : CPLStringList aosValues = NCDFTokenizeArray(pszValue);
11249 8 : if (aosValues.empty())
11250 0 : return CE_Failure;
11251 :
11252 8 : nVarLen = aosValues.size();
11253 :
11254 : /* now write the data */
11255 8 : if (nVarType == NC_CHAR)
11256 : {
11257 0 : status = nc_put_vara_text(nCdfId, nVarId, start, count, pszValue);
11258 0 : NCDF_ERR(status);
11259 : }
11260 : else
11261 : {
11262 8 : switch (nVarType)
11263 : {
11264 0 : case NC_BYTE:
11265 : {
11266 : signed char *pscTemp = static_cast<signed char *>(
11267 0 : CPLCalloc(nVarLen, sizeof(signed char)));
11268 0 : for (size_t i = 0; i < nVarLen; i++)
11269 : {
11270 0 : char *pszTemp = nullptr;
11271 0 : pscTemp[i] = static_cast<signed char>(
11272 0 : strtol(aosValues[i], &pszTemp, 10));
11273 : }
11274 : status =
11275 0 : nc_put_vara_schar(nCdfId, nVarId, start, count, pscTemp);
11276 0 : NCDF_ERR(status);
11277 0 : CPLFree(pscTemp);
11278 0 : break;
11279 : }
11280 0 : case NC_SHORT:
11281 : {
11282 : short *psTemp =
11283 0 : static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
11284 0 : for (size_t i = 0; i < nVarLen; i++)
11285 : {
11286 0 : char *pszTemp = nullptr;
11287 0 : psTemp[i] =
11288 0 : static_cast<short>(strtol(aosValues[i], &pszTemp, 10));
11289 : }
11290 : status =
11291 0 : nc_put_vara_short(nCdfId, nVarId, start, count, psTemp);
11292 0 : NCDF_ERR(status);
11293 0 : CPLFree(psTemp);
11294 0 : break;
11295 : }
11296 3 : case NC_INT:
11297 : {
11298 : int *pnTemp =
11299 3 : static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
11300 11 : for (size_t i = 0; i < nVarLen; i++)
11301 : {
11302 8 : char *pszTemp = nullptr;
11303 8 : pnTemp[i] =
11304 8 : static_cast<int>(strtol(aosValues[i], &pszTemp, 10));
11305 : }
11306 3 : status = nc_put_vara_int(nCdfId, nVarId, start, count, pnTemp);
11307 3 : NCDF_ERR(status);
11308 3 : CPLFree(pnTemp);
11309 3 : break;
11310 : }
11311 0 : case NC_FLOAT:
11312 : {
11313 : float *pfTemp =
11314 0 : static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
11315 0 : for (size_t i = 0; i < nVarLen; i++)
11316 : {
11317 0 : char *pszTemp = nullptr;
11318 0 : pfTemp[i] =
11319 0 : static_cast<float>(CPLStrtod(aosValues[i], &pszTemp));
11320 : }
11321 : status =
11322 0 : nc_put_vara_float(nCdfId, nVarId, start, count, pfTemp);
11323 0 : NCDF_ERR(status);
11324 0 : CPLFree(pfTemp);
11325 0 : break;
11326 : }
11327 5 : case NC_DOUBLE:
11328 : {
11329 : double *pdfTemp =
11330 5 : static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
11331 19 : for (size_t i = 0; i < nVarLen; i++)
11332 : {
11333 14 : char *pszTemp = nullptr;
11334 14 : pdfTemp[i] = CPLStrtod(aosValues[i], &pszTemp);
11335 : }
11336 : status =
11337 5 : nc_put_vara_double(nCdfId, nVarId, start, count, pdfTemp);
11338 5 : NCDF_ERR(status);
11339 5 : CPLFree(pdfTemp);
11340 5 : break;
11341 : }
11342 0 : default:
11343 : {
11344 0 : int nTmpFormat = 0;
11345 0 : status = nc_inq_format(nCdfId, &nTmpFormat);
11346 0 : NCDF_ERR(status);
11347 0 : if (nTmpFormat == NCDF_FORMAT_NC4)
11348 : {
11349 0 : switch (nVarType)
11350 : {
11351 0 : case NC_STRING:
11352 : {
11353 0 : status = nc_put_vara_string(
11354 : nCdfId, nVarId, start, count,
11355 0 : const_cast<const char **>(aosValues.List()));
11356 0 : NCDF_ERR(status);
11357 0 : break;
11358 : }
11359 0 : case NC_UBYTE:
11360 : {
11361 : unsigned char *pucTemp =
11362 : static_cast<unsigned char *>(
11363 0 : CPLCalloc(nVarLen, sizeof(unsigned char)));
11364 0 : for (size_t i = 0; i < nVarLen; i++)
11365 : {
11366 0 : char *pszTemp = nullptr;
11367 0 : pucTemp[i] = static_cast<unsigned char>(
11368 0 : strtoul(aosValues[i], &pszTemp, 10));
11369 : }
11370 0 : status = nc_put_vara_uchar(nCdfId, nVarId, start,
11371 : count, pucTemp);
11372 0 : NCDF_ERR(status);
11373 0 : CPLFree(pucTemp);
11374 0 : break;
11375 : }
11376 0 : case NC_USHORT:
11377 : {
11378 : unsigned short *pusTemp =
11379 : static_cast<unsigned short *>(
11380 0 : CPLCalloc(nVarLen, sizeof(unsigned short)));
11381 0 : for (size_t i = 0; i < nVarLen; i++)
11382 : {
11383 0 : char *pszTemp = nullptr;
11384 0 : pusTemp[i] = static_cast<unsigned short>(
11385 0 : strtoul(aosValues[i], &pszTemp, 10));
11386 : }
11387 0 : status = nc_put_vara_ushort(nCdfId, nVarId, start,
11388 : count, pusTemp);
11389 0 : NCDF_ERR(status);
11390 0 : CPLFree(pusTemp);
11391 0 : break;
11392 : }
11393 0 : case NC_UINT:
11394 : {
11395 : unsigned int *punTemp = static_cast<unsigned int *>(
11396 0 : CPLCalloc(nVarLen, sizeof(unsigned int)));
11397 0 : for (size_t i = 0; i < nVarLen; i++)
11398 : {
11399 0 : char *pszTemp = nullptr;
11400 0 : punTemp[i] = static_cast<unsigned int>(
11401 0 : strtoul(aosValues[i], &pszTemp, 10));
11402 : }
11403 0 : status = nc_put_vara_uint(nCdfId, nVarId, start,
11404 : count, punTemp);
11405 0 : NCDF_ERR(status);
11406 0 : CPLFree(punTemp);
11407 0 : break;
11408 : }
11409 0 : default:
11410 0 : return CE_Failure;
11411 : }
11412 : }
11413 0 : break;
11414 : }
11415 : }
11416 : }
11417 :
11418 8 : return CE_None;
11419 : }
11420 :
11421 : /************************************************************************/
11422 : /* GetDefaultNoDataValue() */
11423 : /************************************************************************/
11424 :
11425 200 : double NCDFGetDefaultNoDataValue(int nCdfId, int nVarId, int nVarType,
11426 : bool &bGotNoData)
11427 :
11428 : {
11429 200 : int nNoFill = 0;
11430 200 : double dfNoData = 0.0;
11431 :
11432 200 : switch (nVarType)
11433 : {
11434 0 : case NC_CHAR:
11435 : case NC_BYTE:
11436 : case NC_UBYTE:
11437 : // Don't do default fill-values for bytes, too risky.
11438 : // This function should not be called in those cases.
11439 0 : CPLAssert(false);
11440 : break;
11441 24 : case NC_SHORT:
11442 : {
11443 24 : short nFillVal = 0;
11444 24 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11445 : NC_NOERR)
11446 : {
11447 24 : if (!nNoFill)
11448 : {
11449 23 : bGotNoData = true;
11450 23 : dfNoData = nFillVal;
11451 : }
11452 : }
11453 : else
11454 0 : dfNoData = NC_FILL_SHORT;
11455 24 : break;
11456 : }
11457 26 : case NC_INT:
11458 : {
11459 26 : int nFillVal = 0;
11460 26 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11461 : NC_NOERR)
11462 : {
11463 26 : if (!nNoFill)
11464 : {
11465 25 : bGotNoData = true;
11466 25 : dfNoData = nFillVal;
11467 : }
11468 : }
11469 : else
11470 0 : dfNoData = NC_FILL_INT;
11471 26 : break;
11472 : }
11473 83 : case NC_FLOAT:
11474 : {
11475 83 : float fFillVal = 0;
11476 83 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &fFillVal) ==
11477 : NC_NOERR)
11478 : {
11479 83 : if (!nNoFill)
11480 : {
11481 79 : bGotNoData = true;
11482 79 : dfNoData = fFillVal;
11483 : }
11484 : }
11485 : else
11486 0 : dfNoData = NC_FILL_FLOAT;
11487 83 : break;
11488 : }
11489 34 : case NC_DOUBLE:
11490 : {
11491 34 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &dfNoData) ==
11492 : NC_NOERR)
11493 : {
11494 34 : if (!nNoFill)
11495 : {
11496 34 : bGotNoData = true;
11497 : }
11498 : }
11499 : else
11500 0 : dfNoData = NC_FILL_DOUBLE;
11501 34 : break;
11502 : }
11503 7 : case NC_USHORT:
11504 : {
11505 7 : unsigned short nFillVal = 0;
11506 7 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11507 : NC_NOERR)
11508 : {
11509 7 : if (!nNoFill)
11510 : {
11511 7 : bGotNoData = true;
11512 7 : dfNoData = nFillVal;
11513 : }
11514 : }
11515 : else
11516 0 : dfNoData = NC_FILL_USHORT;
11517 7 : break;
11518 : }
11519 7 : case NC_UINT:
11520 : {
11521 7 : unsigned int nFillVal = 0;
11522 7 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11523 : NC_NOERR)
11524 : {
11525 7 : if (!nNoFill)
11526 : {
11527 7 : bGotNoData = true;
11528 7 : dfNoData = nFillVal;
11529 : }
11530 : }
11531 : else
11532 0 : dfNoData = NC_FILL_UINT;
11533 7 : break;
11534 : }
11535 19 : default:
11536 19 : dfNoData = 0.0;
11537 19 : break;
11538 : }
11539 :
11540 200 : return dfNoData;
11541 : }
11542 :
11543 : /************************************************************************/
11544 : /* NCDFGetDefaultNoDataValueAsInt64() */
11545 : /************************************************************************/
11546 :
11547 2 : int64_t NCDFGetDefaultNoDataValueAsInt64(int nCdfId, int nVarId,
11548 : bool &bGotNoData)
11549 :
11550 : {
11551 2 : int nNoFill = 0;
11552 2 : long long nFillVal = 0;
11553 2 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
11554 : {
11555 2 : if (!nNoFill)
11556 : {
11557 2 : bGotNoData = true;
11558 2 : return static_cast<int64_t>(nFillVal);
11559 : }
11560 : }
11561 : else
11562 0 : return static_cast<int64_t>(NC_FILL_INT64);
11563 0 : return 0;
11564 : }
11565 :
11566 : /************************************************************************/
11567 : /* NCDFGetDefaultNoDataValueAsUInt64() */
11568 : /************************************************************************/
11569 :
11570 1 : uint64_t NCDFGetDefaultNoDataValueAsUInt64(int nCdfId, int nVarId,
11571 : bool &bGotNoData)
11572 :
11573 : {
11574 1 : int nNoFill = 0;
11575 1 : unsigned long long nFillVal = 0;
11576 1 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
11577 : {
11578 1 : if (!nNoFill)
11579 : {
11580 1 : bGotNoData = true;
11581 1 : return static_cast<uint64_t>(nFillVal);
11582 : }
11583 : }
11584 : else
11585 0 : return static_cast<uint64_t>(NC_FILL_UINT64);
11586 0 : return 0;
11587 : }
11588 :
11589 12282 : static int NCDFDoesVarContainAttribVal(int nCdfId,
11590 : const char *const *papszAttribNames,
11591 : const char *const *papszAttribValues,
11592 : int nVarId, const char *pszVarName,
11593 : bool bStrict = true)
11594 : {
11595 12282 : if (nVarId == -1 && pszVarName != nullptr)
11596 8327 : NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
11597 :
11598 12282 : if (nVarId == -1)
11599 950 : return -1;
11600 :
11601 11332 : bool bFound = false;
11602 52814 : for (int i = 0; !bFound && papszAttribNames != nullptr &&
11603 50321 : papszAttribNames[i] != nullptr;
11604 : i++)
11605 : {
11606 41482 : char *pszTemp = nullptr;
11607 41482 : if (NCDFGetAttr(nCdfId, nVarId, papszAttribNames[i], &pszTemp) ==
11608 59596 : CE_None &&
11609 18114 : pszTemp != nullptr)
11610 : {
11611 18114 : if (bStrict)
11612 : {
11613 18114 : if (EQUAL(pszTemp, papszAttribValues[i]))
11614 2493 : bFound = true;
11615 : }
11616 : else
11617 : {
11618 0 : if (EQUALN(pszTemp, papszAttribValues[i],
11619 : strlen(papszAttribValues[i])))
11620 0 : bFound = true;
11621 : }
11622 18114 : CPLFree(pszTemp);
11623 : }
11624 : }
11625 11332 : return bFound;
11626 : }
11627 :
11628 2139 : static int NCDFDoesVarContainAttribVal2(int nCdfId, const char *papszAttribName,
11629 : const char *const *papszAttribValues,
11630 : int nVarId, const char *pszVarName,
11631 : int bStrict = true)
11632 : {
11633 2139 : if (nVarId == -1 && pszVarName != nullptr)
11634 1585 : NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
11635 :
11636 2139 : if (nVarId == -1)
11637 0 : return -1;
11638 :
11639 2139 : bool bFound = false;
11640 2139 : char *pszTemp = nullptr;
11641 2518 : if (NCDFGetAttr(nCdfId, nVarId, papszAttribName, &pszTemp) != CE_None ||
11642 379 : pszTemp == nullptr)
11643 1760 : return FALSE;
11644 :
11645 7703 : for (int i = 0; !bFound && i < CSLCount(papszAttribValues); i++)
11646 : {
11647 7324 : if (bStrict)
11648 : {
11649 7296 : if (EQUAL(pszTemp, papszAttribValues[i]))
11650 31 : bFound = true;
11651 : }
11652 : else
11653 : {
11654 28 : if (EQUALN(pszTemp, papszAttribValues[i],
11655 : strlen(papszAttribValues[i])))
11656 0 : bFound = true;
11657 : }
11658 : }
11659 :
11660 379 : CPLFree(pszTemp);
11661 :
11662 379 : return bFound;
11663 : }
11664 :
11665 948 : static bool NCDFEqual(const char *papszName, const char *const *papszValues)
11666 : {
11667 948 : if (papszName == nullptr || EQUAL(papszName, ""))
11668 0 : return false;
11669 :
11670 2560 : for (int i = 0; papszValues && papszValues[i]; ++i)
11671 : {
11672 1756 : if (EQUAL(papszName, papszValues[i]))
11673 144 : return true;
11674 : }
11675 :
11676 804 : return false;
11677 : }
11678 :
11679 : // Test that a variable is longitude/latitude coordinate,
11680 : // following CF 4.1 and 4.2.
11681 4132 : bool NCDFIsVarLongitude(int nCdfId, int nVarId, const char *pszVarName)
11682 : {
11683 : // Check for matching attributes.
11684 4132 : int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLongitudeAttribNames,
11685 : papszCFLongitudeAttribValues, nVarId,
11686 : pszVarName);
11687 : // If not found using attributes then check using var name
11688 : // unless GDAL_NETCDF_VERIFY_DIMS=STRICT.
11689 4132 : if (bVal == -1)
11690 : {
11691 304 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11692 : "STRICT"))
11693 304 : bVal = NCDFEqual(pszVarName, papszCFLongitudeVarNames);
11694 : else
11695 0 : bVal = FALSE;
11696 : }
11697 3828 : else if (bVal)
11698 : {
11699 : // Check that the units is not 'm' or '1'. See #6759
11700 805 : char *pszTemp = nullptr;
11701 1187 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11702 382 : pszTemp != nullptr)
11703 : {
11704 382 : if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
11705 97 : bVal = false;
11706 382 : CPLFree(pszTemp);
11707 : }
11708 : }
11709 :
11710 4132 : return CPL_TO_BOOL(bVal);
11711 : }
11712 :
11713 2374 : bool NCDFIsVarLatitude(int nCdfId, int nVarId, const char *pszVarName)
11714 : {
11715 2374 : int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLatitudeAttribNames,
11716 : papszCFLatitudeAttribValues, nVarId,
11717 : pszVarName);
11718 2374 : if (bVal == -1)
11719 : {
11720 175 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11721 : "STRICT"))
11722 175 : bVal = NCDFEqual(pszVarName, papszCFLatitudeVarNames);
11723 : else
11724 0 : bVal = FALSE;
11725 : }
11726 2199 : else if (bVal)
11727 : {
11728 : // Check that the units is not 'm' or '1'. See #6759
11729 556 : char *pszTemp = nullptr;
11730 698 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11731 142 : pszTemp != nullptr)
11732 : {
11733 142 : if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
11734 36 : bVal = false;
11735 142 : CPLFree(pszTemp);
11736 : }
11737 : }
11738 :
11739 2374 : return CPL_TO_BOOL(bVal);
11740 : }
11741 :
11742 2579 : bool NCDFIsVarProjectionX(int nCdfId, int nVarId, const char *pszVarName)
11743 : {
11744 2579 : int bVal = NCDFDoesVarContainAttribVal(
11745 : nCdfId, papszCFProjectionXAttribNames, papszCFProjectionXAttribValues,
11746 : nVarId, pszVarName);
11747 2579 : if (bVal == -1)
11748 : {
11749 298 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11750 : "STRICT"))
11751 298 : bVal = NCDFEqual(pszVarName, papszCFProjectionXVarNames);
11752 : else
11753 0 : bVal = FALSE;
11754 : }
11755 2281 : else if (bVal)
11756 : {
11757 : // Check that the units is not '1'
11758 460 : char *pszTemp = nullptr;
11759 690 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11760 230 : pszTemp != nullptr)
11761 : {
11762 230 : if (EQUAL(pszTemp, "1"))
11763 5 : bVal = false;
11764 230 : CPLFree(pszTemp);
11765 : }
11766 : }
11767 :
11768 2579 : return CPL_TO_BOOL(bVal);
11769 : }
11770 :
11771 1817 : bool NCDFIsVarProjectionY(int nCdfId, int nVarId, const char *pszVarName)
11772 : {
11773 1817 : int bVal = NCDFDoesVarContainAttribVal(
11774 : nCdfId, papszCFProjectionYAttribNames, papszCFProjectionYAttribValues,
11775 : nVarId, pszVarName);
11776 1817 : if (bVal == -1)
11777 : {
11778 171 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11779 : "STRICT"))
11780 171 : bVal = NCDFEqual(pszVarName, papszCFProjectionYVarNames);
11781 : else
11782 0 : bVal = FALSE;
11783 : }
11784 1646 : else if (bVal)
11785 : {
11786 : // Check that the units is not '1'
11787 454 : char *pszTemp = nullptr;
11788 679 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11789 225 : pszTemp != nullptr)
11790 : {
11791 225 : if (EQUAL(pszTemp, "1"))
11792 5 : bVal = false;
11793 225 : CPLFree(pszTemp);
11794 : }
11795 : }
11796 :
11797 1817 : return CPL_TO_BOOL(bVal);
11798 : }
11799 :
11800 : /* test that a variable is a vertical coordinate, following CF 4.3 */
11801 1121 : bool NCDFIsVarVerticalCoord(int nCdfId, int nVarId, const char *pszVarName)
11802 : {
11803 : /* check for matching attributes */
11804 1121 : if (NCDFDoesVarContainAttribVal(nCdfId, papszCFVerticalAttribNames,
11805 : papszCFVerticalAttribValues, nVarId,
11806 1121 : pszVarName))
11807 111 : return true;
11808 : /* check for matching units */
11809 1010 : else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
11810 : papszCFVerticalUnitsValues, nVarId,
11811 1010 : pszVarName))
11812 31 : return true;
11813 : /* check for matching standard name */
11814 979 : else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_STD_NAME,
11815 : papszCFVerticalStandardNameValues,
11816 979 : nVarId, pszVarName))
11817 0 : return true;
11818 : else
11819 979 : return false;
11820 : }
11821 :
11822 : /* test that a variable is a time coordinate, following CF 4.4 */
11823 259 : bool NCDFIsVarTimeCoord(int nCdfId, int nVarId, const char *pszVarName)
11824 : {
11825 : /* check for matching attributes */
11826 259 : if (NCDFDoesVarContainAttribVal(nCdfId, papszCFTimeAttribNames,
11827 : papszCFTimeAttribValues, nVarId,
11828 259 : pszVarName))
11829 109 : return true;
11830 : /* check for matching units */
11831 150 : else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
11832 : papszCFTimeUnitsValues, nVarId,
11833 150 : pszVarName, false))
11834 0 : return true;
11835 : else
11836 150 : return false;
11837 : }
11838 :
11839 : // Parse a string, and return as a string list.
11840 : // If it is an array of the form {a,b}, then tokenize it.
11841 : // Otherwise, return a copy.
11842 200 : static CPLStringList NCDFTokenizeArray(const char *pszValue)
11843 : {
11844 200 : if (pszValue == nullptr || EQUAL(pszValue, ""))
11845 59 : return CPLStringList();
11846 :
11847 282 : CPLStringList aosValues;
11848 141 : const int nLen = static_cast<int>(strlen(pszValue));
11849 :
11850 141 : if (pszValue[0] == '{' && nLen > 2 && pszValue[nLen - 1] == '}')
11851 : {
11852 41 : char *pszTemp = static_cast<char *>(CPLMalloc((nLen - 2) + 1));
11853 41 : strncpy(pszTemp, pszValue + 1, nLen - 2);
11854 41 : pszTemp[nLen - 2] = '\0';
11855 : aosValues.Assign(
11856 41 : CSLTokenizeString2(pszTemp, ",", CSLT_ALLOWEMPTYTOKENS));
11857 41 : CPLFree(pszTemp);
11858 : }
11859 : else
11860 : {
11861 100 : aosValues.AddString(pszValue);
11862 : }
11863 :
11864 141 : return aosValues;
11865 : }
11866 :
11867 : // Open a NetCDF subdataset from full path /group1/group2/.../groupn/var.
11868 : // Leading slash is optional.
11869 436 : static CPLErr NCDFOpenSubDataset(int nCdfId, const char *pszSubdatasetName,
11870 : int *pnGroupId, int *pnVarId)
11871 : {
11872 436 : *pnGroupId = -1;
11873 436 : *pnVarId = -1;
11874 :
11875 : // Open group.
11876 872 : std::string osGroupFullName = CPLGetPathSafe(pszSubdatasetName);
11877 : // Add a leading slash if needed.
11878 436 : if (osGroupFullName.empty() || osGroupFullName[0] != '/')
11879 : {
11880 419 : osGroupFullName = "/" + osGroupFullName;
11881 : }
11882 : // Detect root group.
11883 436 : if (osGroupFullName == "/")
11884 : {
11885 419 : *pnGroupId = nCdfId;
11886 : }
11887 : else
11888 : {
11889 : int status =
11890 17 : nc_inq_grp_full_ncid(nCdfId, osGroupFullName.c_str(), pnGroupId);
11891 17 : NCDF_ERR_RET(status);
11892 : }
11893 :
11894 : // Open var.
11895 436 : const char *pszVarName = CPLGetFilename(pszSubdatasetName);
11896 436 : NCDF_ERR_RET(nc_inq_varid(*pnGroupId, pszVarName, pnVarId));
11897 :
11898 436 : return CE_None;
11899 : }
11900 :
11901 : // Get all dimensions visible from a given NetCDF (or group) ID and any of
11902 : // its parents.
11903 372 : static CPLErr NCDFGetVisibleDims(int nGroupId, int *pnDims, int **ppanDimIds)
11904 : {
11905 372 : int nDims = 0;
11906 372 : int *panDimIds = nullptr;
11907 372 : NCDF_ERR_RET(nc_inq_dimids(nGroupId, &nDims, nullptr, true));
11908 :
11909 372 : panDimIds = static_cast<int *>(CPLMalloc(nDims * sizeof(int)));
11910 :
11911 372 : int status = nc_inq_dimids(nGroupId, nullptr, panDimIds, true);
11912 372 : if (status != NC_NOERR)
11913 0 : CPLFree(panDimIds);
11914 372 : NCDF_ERR_RET(status);
11915 :
11916 372 : *pnDims = nDims;
11917 372 : *ppanDimIds = panDimIds;
11918 :
11919 372 : return CE_None;
11920 : }
11921 :
11922 : // Get direct sub-groups IDs of a given NetCDF (or group) ID.
11923 : // Consider only direct children, does not get children of children.
11924 3294 : static CPLErr NCDFGetSubGroups(int nGroupId, int *pnSubGroups,
11925 : int **ppanSubGroupIds)
11926 : {
11927 3294 : *pnSubGroups = 0;
11928 3294 : *ppanSubGroupIds = nullptr;
11929 :
11930 : int nSubGroups;
11931 3294 : NCDF_ERR_RET(nc_inq_grps(nGroupId, &nSubGroups, nullptr));
11932 : int *panSubGroupIds =
11933 3294 : static_cast<int *>(CPLMalloc(nSubGroups * sizeof(int)));
11934 3294 : NCDF_ERR_RET(nc_inq_grps(nGroupId, nullptr, panSubGroupIds));
11935 3294 : *pnSubGroups = nSubGroups;
11936 3294 : *ppanSubGroupIds = panSubGroupIds;
11937 :
11938 3294 : return CE_None;
11939 : }
11940 :
11941 : // Get the full name of a given NetCDF (or group) ID
11942 : // (e.g. /group1/group2/.../groupn).
11943 : // bNC3Compat remove the leading slash for top-level variables for
11944 : // backward compatibility (top-level variables are the ones in the root group).
11945 18711 : static CPLErr NCDFGetGroupFullName(int nGroupId, std::string &osFullName,
11946 : bool bNC3Compat)
11947 : {
11948 18711 : osFullName = "";
11949 :
11950 : size_t nFullNameLen;
11951 18711 : NCDF_ERR_RET(nc_inq_grpname_len(nGroupId, &nFullNameLen));
11952 18711 : osFullName.resize(nFullNameLen);
11953 :
11954 : const int status =
11955 18711 : nc_inq_grpname_full(nGroupId, &nFullNameLen, osFullName.data());
11956 18711 : if (status != NC_NOERR)
11957 : {
11958 0 : osFullName = "";
11959 0 : NCDF_ERR_RET(status);
11960 : }
11961 :
11962 18711 : if (bNC3Compat && osFullName == "/")
11963 8352 : osFullName = "";
11964 :
11965 18711 : return CE_None;
11966 : }
11967 :
11968 10148 : CPLString NCDFGetGroupFullName(int nGroupId)
11969 : {
11970 10148 : CPLString osFullName;
11971 10148 : NCDFGetGroupFullName(nGroupId, osFullName, false);
11972 :
11973 10148 : return osFullName;
11974 : }
11975 :
11976 : // Get the full name of a given NetCDF variable ID
11977 : // (e.g. /group1/group2/.../groupn/var).
11978 : // Handle also NC_GLOBAL as nVarId.
11979 : // bNC3Compat remove the leading slash for top-level variables for
11980 : // backward compatibility (top-level variables are the ones in the root group).
11981 8512 : static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId,
11982 : std::string &osFullName, bool bNC3Compat)
11983 : {
11984 8512 : osFullName = "";
11985 17024 : std::string osGroupFullName;
11986 8512 : ERR_RET(NCDFGetGroupFullName(nGroupId, osGroupFullName, bNC3Compat));
11987 : char szVarName[NC_MAX_NAME + 1];
11988 8512 : if (nVarId == NC_GLOBAL)
11989 : {
11990 1144 : strcpy(szVarName, "NC_GLOBAL");
11991 : }
11992 : else
11993 : {
11994 7368 : int status = nc_inq_varname(nGroupId, nVarId, szVarName);
11995 7368 : if (status != NC_NOERR)
11996 : {
11997 0 : NCDF_ERR_RET(status);
11998 : }
11999 : }
12000 8512 : const char *pszSep = "/";
12001 8512 : if (osGroupFullName.empty() || osGroupFullName == "/")
12002 8303 : pszSep = "";
12003 : osFullName =
12004 8512 : CPLOPrintf("%s%s%s", osGroupFullName.c_str(), pszSep, szVarName);
12005 :
12006 8512 : return CE_None;
12007 : }
12008 :
12009 : // Get the NetCDF root group ID of a given group ID.
12010 0 : static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId)
12011 : {
12012 0 : *pnRootGroupId = -1;
12013 : // Recurse on parent group.
12014 : int nParentGroupId;
12015 0 : int status = nc_inq_grp_parent(nStartGroupId, &nParentGroupId);
12016 0 : if (status == NC_NOERR)
12017 0 : return NCDFGetRootGroup(nParentGroupId, pnRootGroupId);
12018 0 : else if (status != NC_ENOGRP)
12019 0 : NCDF_ERR_RET(status);
12020 : else // No more parent group.
12021 : {
12022 0 : *pnRootGroupId = nStartGroupId;
12023 : }
12024 :
12025 0 : return CE_None;
12026 : }
12027 :
12028 : // Implementation of NCDFResolveVar/Att.
12029 13576 : static CPLErr NCDFResolveElem(int nStartGroupId, const char *pszVar,
12030 : const char *pszAtt, int *pnGroupId, int *pnId,
12031 : bool bMandatory)
12032 : {
12033 13576 : if (!pszVar && !pszAtt)
12034 : {
12035 0 : CPLError(CE_Failure, CPLE_IllegalArg,
12036 : "pszVar and pszAtt NCDFResolveElem() args are both null.");
12037 0 : return CE_Failure;
12038 : }
12039 :
12040 : enum
12041 : {
12042 : NCRM_PARENT,
12043 : NCRM_WIDTH_WISE
12044 13576 : } eNCResolveMode = NCRM_PARENT;
12045 :
12046 27152 : std::queue<int> aoQueueGroupIdsToVisit;
12047 13576 : aoQueueGroupIdsToVisit.push(nStartGroupId);
12048 :
12049 15426 : while (!aoQueueGroupIdsToVisit.empty())
12050 : {
12051 : // Get the first group of the FIFO queue.
12052 13770 : *pnGroupId = aoQueueGroupIdsToVisit.front();
12053 13770 : aoQueueGroupIdsToVisit.pop();
12054 :
12055 : // Look if this group contains the searched element.
12056 : int status;
12057 13770 : if (pszVar)
12058 13545 : status = nc_inq_varid(*pnGroupId, pszVar, pnId);
12059 : else // pszAtt != nullptr.
12060 225 : status = nc_inq_attid(*pnGroupId, NC_GLOBAL, pszAtt, pnId);
12061 :
12062 13770 : if (status == NC_NOERR)
12063 : {
12064 11920 : return CE_None;
12065 : }
12066 1850 : else if ((pszVar && status != NC_ENOTVAR) ||
12067 222 : (pszAtt && status != NC_ENOTATT))
12068 : {
12069 0 : NCDF_ERR(status);
12070 : }
12071 : // Element not found, in NC4 case we must search in other groups
12072 : // following the CF logic.
12073 :
12074 : // The first resolve mode consists to search on parent groups.
12075 1850 : if (eNCResolveMode == NCRM_PARENT)
12076 : {
12077 1731 : int nParentGroupId = -1;
12078 1731 : int status2 = nc_inq_grp_parent(*pnGroupId, &nParentGroupId);
12079 1731 : if (status2 == NC_NOERR)
12080 62 : aoQueueGroupIdsToVisit.push(nParentGroupId);
12081 1669 : else if (status2 != NC_ENOGRP)
12082 0 : NCDF_ERR(status2);
12083 1669 : else if (pszVar)
12084 : // When resolving a variable, if there is no more
12085 : // parent group then we switch to width-wise search mode
12086 : // starting from the latest found parent group.
12087 1450 : eNCResolveMode = NCRM_WIDTH_WISE;
12088 : }
12089 :
12090 : // The second resolve mode is a width-wise search.
12091 1850 : if (eNCResolveMode == NCRM_WIDTH_WISE)
12092 : {
12093 : // Enqueue all direct sub-groups.
12094 1569 : int nSubGroups = 0;
12095 1569 : int *panSubGroupIds = nullptr;
12096 1569 : NCDFGetSubGroups(*pnGroupId, &nSubGroups, &panSubGroupIds);
12097 1701 : for (int i = 0; i < nSubGroups; i++)
12098 132 : aoQueueGroupIdsToVisit.push(panSubGroupIds[i]);
12099 1569 : CPLFree(panSubGroupIds);
12100 : }
12101 : }
12102 :
12103 1656 : if (bMandatory)
12104 : {
12105 0 : std::string osStartGroupFullName;
12106 0 : NCDFGetGroupFullName(nStartGroupId, osStartGroupFullName);
12107 0 : CPLError(CE_Failure, CPLE_AppDefined,
12108 : "Cannot resolve mandatory %s %s from group %s",
12109 : (pszVar ? pszVar : pszAtt),
12110 : (pszVar ? "variable" : "attribute"),
12111 : osStartGroupFullName.c_str());
12112 : }
12113 :
12114 1656 : *pnGroupId = -1;
12115 1656 : *pnId = -1;
12116 1656 : return CE_Failure;
12117 : }
12118 :
12119 : // Resolve a variable name from a given starting group following the CF logic:
12120 : // - if var name is an absolute path then directly open it
12121 : // - first search in the starting group and its parent groups
12122 : // - then if there is no more parent group we switch to a width-wise search
12123 : // mode starting from the latest found parent group.
12124 : // The full CF logic is described here:
12125 : // https://github.com/diwg/cf2/blob/master/group/cf2-group.adoc#scope
12126 : // If bMandatory then print an error if resolving fails.
12127 : // TODO: implement support of relative paths.
12128 : // TODO: to follow strictly the CF logic, when searching for a coordinate
12129 : // variable, we must stop the parent search mode once the corresponding
12130 : // dimension is found and start the width-wise search from this group.
12131 : // TODO: to follow strictly the CF logic, when searching in width-wise mode
12132 : // we should skip every groups already visited during the parent
12133 : // search mode (but revisiting them should have no impact so we could
12134 : // let as it is if it is simpler...)
12135 : // TODO: CF specifies that the width-wise search order is "left-to-right" so
12136 : // maybe we must sort sibling groups alphabetically? but maybe not
12137 : // necessary if nc_inq_grps() already sort them?
12138 13354 : CPLErr NCDFResolveVar(int nStartGroupId, const char *pszVar, int *pnGroupId,
12139 : int *pnVarId, bool bMandatory)
12140 : {
12141 13354 : *pnGroupId = -1;
12142 13354 : *pnVarId = -1;
12143 13354 : int nGroupId = nStartGroupId, nVarId;
12144 13354 : if (pszVar[0] == '/')
12145 : {
12146 : // This is an absolute path: we can open the var directly.
12147 : int nRootGroupId;
12148 0 : ERR_RET(NCDFGetRootGroup(nStartGroupId, &nRootGroupId));
12149 0 : ERR_RET(NCDFOpenSubDataset(nRootGroupId, pszVar, &nGroupId, &nVarId));
12150 : }
12151 : else
12152 : {
12153 : // We have to search the variable following the CF logic.
12154 13354 : ERR_RET(NCDFResolveElem(nStartGroupId, pszVar, nullptr, &nGroupId,
12155 : &nVarId, bMandatory));
12156 : }
12157 11917 : *pnGroupId = nGroupId;
12158 11917 : *pnVarId = nVarId;
12159 11917 : return CE_None;
12160 : }
12161 :
12162 : // Like NCDFResolveVar but returns directly the var full name.
12163 1416 : static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,
12164 : std::string &osFullName, bool bMandatory)
12165 : {
12166 : int nGroupId, nVarId;
12167 1416 : osFullName = "";
12168 1416 : ERR_RET(
12169 : NCDFResolveVar(nStartGroupId, pszVar, &nGroupId, &nVarId, bMandatory));
12170 1390 : return NCDFGetVarFullName(nGroupId, nVarId, osFullName);
12171 : }
12172 :
12173 : // Like NCDFResolveVar but resolves an attribute instead a variable and
12174 : // returns its integer value.
12175 : // Only GLOBAL attributes are supported for the moment.
12176 222 : static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,
12177 : const char *pszAtt, int *pnAtt, bool bMandatory)
12178 : {
12179 222 : int nGroupId = nStartGroupId, nAttId = nStartVarId;
12180 222 : ERR_RET(NCDFResolveElem(nStartGroupId, nullptr, pszAtt, &nGroupId, &nAttId,
12181 : bMandatory));
12182 3 : NCDF_ERR_RET(nc_get_att_int(nGroupId, NC_GLOBAL, pszAtt, pnAtt));
12183 3 : return CE_None;
12184 : }
12185 :
12186 : // Filter variables to keep only valid 2+D raster bands and vector fields in
12187 : // a given a NetCDF (or group) ID and its sub-groups.
12188 : // Coordinate or boundary variables are ignored.
12189 : // It also creates corresponding vector layers.
12190 550 : CPLErr netCDFDataset::FilterVars(
12191 : int nCdfId, bool bKeepRasters, bool bKeepVectors,
12192 : const CPLStringList &aosIgnoreVars, int *pnRasterVars, int *pnGroupId,
12193 : int *pnVarId, int *pnIgnoredVars,
12194 : std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>
12195 : &oMap2DDimsToGroupAndVar)
12196 : {
12197 550 : int nVars = 0;
12198 550 : int nRasterVars = 0;
12199 550 : NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
12200 :
12201 1100 : std::vector<int> anPotentialVectorVarID;
12202 : // oMapDimIdToCount[x] = number of times dim x is the first dimension of
12203 : // potential vector variables
12204 1100 : std::map<int, int> oMapDimIdToCount;
12205 550 : int nVarXId = -1;
12206 550 : int nVarYId = -1;
12207 550 : int nVarZId = -1;
12208 550 : int nVarTimeId = -1;
12209 550 : int nVarTimeDimId = -1;
12210 550 : bool bIsVectorOnly = true;
12211 550 : int nProfileDimId = -1;
12212 550 : int nParentIndexVarID = -1;
12213 :
12214 3319 : for (int v = 0; v < nVars; v++)
12215 : {
12216 : int nVarDims;
12217 2769 : NCDF_ERR_RET(nc_inq_varndims(nCdfId, v, &nVarDims));
12218 : // Should we ignore this variable?
12219 : char szTemp[NC_MAX_NAME + 1];
12220 2769 : szTemp[0] = '\0';
12221 2769 : NCDF_ERR_RET(nc_inq_varname(nCdfId, v, szTemp));
12222 :
12223 2769 : if (strstr(szTemp, "_node_coordinates") ||
12224 2769 : strstr(szTemp, "_node_count"))
12225 : {
12226 : // Ignore CF-1.8 Simple Geometries helper variables
12227 69 : continue;
12228 : }
12229 :
12230 4012 : if (nVarDims == 1 && (NCDFIsVarLongitude(nCdfId, -1, szTemp) ||
12231 1312 : NCDFIsVarProjectionX(nCdfId, -1, szTemp)))
12232 : {
12233 365 : nVarXId = v;
12234 : }
12235 3282 : else if (nVarDims == 1 && (NCDFIsVarLatitude(nCdfId, -1, szTemp) ||
12236 947 : NCDFIsVarProjectionY(nCdfId, -1, szTemp)))
12237 : {
12238 364 : nVarYId = v;
12239 : }
12240 1971 : else if (nVarDims == 1 && NCDFIsVarVerticalCoord(nCdfId, -1, szTemp))
12241 : {
12242 78 : nVarZId = v;
12243 : }
12244 : else
12245 : {
12246 1893 : std::string osFullName;
12247 1893 : CPLErr eErr = NCDFGetVarFullName(nCdfId, v, osFullName);
12248 1893 : if (eErr != CE_None)
12249 : {
12250 0 : continue;
12251 : }
12252 : const bool bIgnoreVar =
12253 1893 : aosIgnoreVars.FindString(osFullName.c_str()) != -1;
12254 1893 : if (bIgnoreVar)
12255 : {
12256 120 : if (nVarDims == 1 && NCDFIsVarTimeCoord(nCdfId, -1, szTemp))
12257 : {
12258 11 : nVarTimeId = v;
12259 11 : nc_inq_vardimid(nCdfId, v, &nVarTimeDimId);
12260 : }
12261 109 : else if (nVarDims > 1)
12262 : {
12263 105 : (*pnIgnoredVars)++;
12264 105 : CPLDebug("GDAL_netCDF", "variable #%d [%s] was ignored", v,
12265 : szTemp);
12266 : }
12267 : }
12268 : // Only accept 2+D vars.
12269 1773 : else if (nVarDims >= 2)
12270 : {
12271 727 : bool bRasterCandidate = true;
12272 : // Identify variables that might be vector variables
12273 727 : if (nVarDims == 2)
12274 : {
12275 650 : int anDimIds[2] = {-1, -1};
12276 650 : nc_inq_vardimid(nCdfId, v, anDimIds);
12277 :
12278 650 : nc_type vartype = NC_NAT;
12279 650 : nc_inq_vartype(nCdfId, v, &vartype);
12280 :
12281 : char szDimNameFirst[NC_MAX_NAME + 1];
12282 : char szDimNameSecond[NC_MAX_NAME + 1];
12283 650 : szDimNameFirst[0] = '\0';
12284 650 : szDimNameSecond[0] = '\0';
12285 1457 : if (vartype == NC_CHAR &&
12286 157 : nc_inq_dimname(nCdfId, anDimIds[0], szDimNameFirst) ==
12287 157 : NC_NOERR &&
12288 157 : nc_inq_dimname(nCdfId, anDimIds[1], szDimNameSecond) ==
12289 157 : NC_NOERR &&
12290 157 : !NCDFIsVarLongitude(nCdfId, -1, szDimNameSecond) &&
12291 157 : !NCDFIsVarProjectionX(nCdfId, -1, szDimNameSecond) &&
12292 964 : !NCDFIsVarLatitude(nCdfId, -1, szDimNameFirst) &&
12293 157 : !NCDFIsVarProjectionY(nCdfId, -1, szDimNameFirst))
12294 : {
12295 157 : anPotentialVectorVarID.push_back(v);
12296 157 : oMapDimIdToCount[anDimIds[0]]++;
12297 157 : if (strstr(szDimNameSecond, "_max_width"))
12298 : {
12299 127 : bRasterCandidate = false;
12300 : }
12301 : else
12302 : {
12303 30 : std::array<int, 3> oKey{anDimIds[0], anDimIds[1],
12304 30 : vartype};
12305 30 : oMap2DDimsToGroupAndVar[oKey].emplace_back(
12306 30 : std::pair(nCdfId, v));
12307 : }
12308 : }
12309 : else
12310 : {
12311 493 : std::array<int, 3> oKey{anDimIds[0], anDimIds[1],
12312 493 : vartype};
12313 493 : oMap2DDimsToGroupAndVar[oKey].emplace_back(
12314 493 : std::pair(nCdfId, v));
12315 493 : bIsVectorOnly = false;
12316 : }
12317 : }
12318 : else
12319 : {
12320 77 : bIsVectorOnly = false;
12321 : }
12322 727 : if (bKeepRasters && bRasterCandidate)
12323 : {
12324 571 : *pnGroupId = nCdfId;
12325 571 : *pnVarId = v;
12326 571 : nRasterVars++;
12327 : }
12328 : }
12329 1046 : else if (nVarDims == 1)
12330 : {
12331 733 : nc_type atttype = NC_NAT;
12332 733 : size_t attlen = 0;
12333 733 : if (nc_inq_att(nCdfId, v, "instance_dimension", &atttype,
12334 14 : &attlen) == NC_NOERR &&
12335 733 : atttype == NC_CHAR && attlen < NC_MAX_NAME)
12336 : {
12337 28 : std::string osInstanceDimension;
12338 14 : if (NCDFGetAttr(nCdfId, v, "instance_dimension",
12339 14 : osInstanceDimension) == CE_None)
12340 : {
12341 : const int status =
12342 14 : nc_inq_dimid(nCdfId, osInstanceDimension.c_str(),
12343 : &nProfileDimId);
12344 14 : if (status == NC_NOERR)
12345 14 : nParentIndexVarID = v;
12346 : else
12347 0 : nProfileDimId = -1;
12348 14 : if (status == NC_EBADDIM)
12349 0 : CPLError(CE_Warning, CPLE_AppDefined,
12350 : "Attribute instance_dimension='%s' refers "
12351 : "to a non existing dimension",
12352 : osInstanceDimension.c_str());
12353 : else
12354 14 : NCDF_ERR(status);
12355 : }
12356 : }
12357 733 : if (v != nParentIndexVarID)
12358 : {
12359 719 : anPotentialVectorVarID.push_back(v);
12360 719 : int nDimId = -1;
12361 719 : nc_inq_vardimid(nCdfId, v, &nDimId);
12362 719 : oMapDimIdToCount[nDimId]++;
12363 : }
12364 : }
12365 : }
12366 : }
12367 :
12368 : // If we are opened in raster-only mode and that there are only 1D or 2D
12369 : // variables and that the 2D variables have no X/Y dim, and all
12370 : // variables refer to the same main dimension (or 2 dimensions for
12371 : // featureType=profile), then it is a pure vector dataset
12372 : CPLString osFeatureType(
12373 550 : aosMetadata.FetchNameValueDef("NC_GLOBAL#featureType", ""));
12374 439 : if (bKeepRasters && !bKeepVectors && bIsVectorOnly && nRasterVars > 0 &&
12375 989 : !anPotentialVectorVarID.empty() &&
12376 0 : (oMapDimIdToCount.size() == 1 ||
12377 0 : (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2 &&
12378 0 : nProfileDimId >= 0)))
12379 : {
12380 0 : anPotentialVectorVarID.resize(0);
12381 : }
12382 : else
12383 : {
12384 550 : *pnRasterVars += nRasterVars;
12385 : }
12386 :
12387 550 : if (!anPotentialVectorVarID.empty() && bKeepVectors)
12388 : {
12389 : // Take the dimension that is referenced the most times.
12390 66 : if (!(oMapDimIdToCount.size() == 1 ||
12391 27 : (EQUAL(osFeatureType, "profile") &&
12392 26 : oMapDimIdToCount.size() == 2 && nProfileDimId >= 0)))
12393 : {
12394 1 : CPLError(CE_Warning, CPLE_AppDefined,
12395 : "The dataset has several variables that could be "
12396 : "identified as vector fields, but not all share the same "
12397 : "primary dimension. Consequently they will be ignored.");
12398 : }
12399 : else
12400 : {
12401 52 : if (nVarTimeId >= 0 &&
12402 52 : oMapDimIdToCount.find(nVarTimeDimId) != oMapDimIdToCount.end())
12403 : {
12404 1 : anPotentialVectorVarID.push_back(nVarTimeId);
12405 : }
12406 51 : CreateGrpVectorLayers(nCdfId, osFeatureType, anPotentialVectorVarID,
12407 : oMapDimIdToCount, nVarXId, nVarYId, nVarZId,
12408 : nProfileDimId, nParentIndexVarID,
12409 : bKeepRasters);
12410 : }
12411 : }
12412 :
12413 : // Recurse on sub-groups.
12414 550 : int nSubGroups = 0;
12415 550 : int *panSubGroupIds = nullptr;
12416 550 : NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
12417 586 : for (int i = 0; i < nSubGroups; i++)
12418 : {
12419 36 : FilterVars(panSubGroupIds[i], bKeepRasters, bKeepVectors, aosIgnoreVars,
12420 : pnRasterVars, pnGroupId, pnVarId, pnIgnoredVars,
12421 : oMap2DDimsToGroupAndVar);
12422 : }
12423 550 : CPLFree(panSubGroupIds);
12424 :
12425 550 : return CE_None;
12426 : }
12427 :
12428 : // Create vector layers from given potentially identified vector variables
12429 : // resulting from the scanning of a NetCDF (or group) ID.
12430 51 : CPLErr netCDFDataset::CreateGrpVectorLayers(
12431 : int nCdfId, const CPLString &osFeatureType,
12432 : const std::vector<int> &anPotentialVectorVarID,
12433 : const std::map<int, int> &oMapDimIdToCount, int nVarXId, int nVarYId,
12434 : int nVarZId, int nProfileDimId, int nParentIndexVarID, bool bKeepRasters)
12435 : {
12436 102 : std::string osGroupName;
12437 51 : NCDFGetGroupFullName(nCdfId, osGroupName);
12438 51 : if (osGroupName.empty())
12439 : {
12440 49 : osGroupName = CPLGetBasenameSafe(osFilename);
12441 : }
12442 51 : OGRwkbGeometryType eGType = wkbUnknown;
12443 : CPLString osLayerName = aosMetadata.FetchNameValueDef(
12444 102 : "NC_GLOBAL#ogr_layer_name", osGroupName.c_str());
12445 51 : aosMetadata.SetNameValue("NC_GLOBAL#ogr_layer_name", nullptr);
12446 :
12447 51 : if (EQUAL(osFeatureType, "point") || EQUAL(osFeatureType, "profile"))
12448 : {
12449 33 : aosMetadata.SetNameValue("NC_GLOBAL#featureType", nullptr);
12450 33 : eGType = wkbPoint;
12451 : }
12452 :
12453 : const char *pszLayerType =
12454 51 : aosMetadata.FetchNameValue("NC_GLOBAL#ogr_layer_type");
12455 51 : if (pszLayerType != nullptr)
12456 : {
12457 9 : eGType = OGRFromOGCGeomType(pszLayerType);
12458 9 : aosMetadata.SetNameValue("NC_GLOBAL#ogr_layer_type", nullptr);
12459 : }
12460 :
12461 : CPLString osGeometryField =
12462 102 : aosMetadata.FetchNameValueDef("NC_GLOBAL#ogr_geometry_field", "");
12463 51 : aosMetadata.SetNameValue("NC_GLOBAL#ogr_geometry_field", nullptr);
12464 :
12465 51 : int nFirstVarId = -1;
12466 51 : int nVectorDim = oMapDimIdToCount.rbegin()->first;
12467 51 : if (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2)
12468 : {
12469 13 : if (nVectorDim == nProfileDimId)
12470 0 : nVectorDim = oMapDimIdToCount.begin()->first;
12471 : }
12472 : else
12473 : {
12474 38 : nProfileDimId = -1;
12475 : }
12476 64 : for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)
12477 : {
12478 64 : int anDimIds[2] = {-1, -1};
12479 64 : nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);
12480 64 : if (nVectorDim == anDimIds[0])
12481 : {
12482 51 : nFirstVarId = anPotentialVectorVarID[j];
12483 51 : break;
12484 : }
12485 : }
12486 :
12487 : // In case where coordinates are explicitly specified for one of the
12488 : // field/variable, use them in priority over the ones that might have been
12489 : // identified above.
12490 51 : char *pszCoordinates = nullptr;
12491 51 : if (NCDFGetAttr(nCdfId, nFirstVarId, "coordinates", &pszCoordinates) ==
12492 : CE_None)
12493 : {
12494 : const CPLStringList aosTokens(
12495 68 : NCDFTokenizeCoordinatesAttribute(pszCFCoordinates));
12496 34 : for (int i = 0; i < aosTokens.size(); i++)
12497 : {
12498 0 : if (NCDFIsVarLongitude(nCdfId, -1, aosTokens[i]) ||
12499 0 : NCDFIsVarProjectionX(nCdfId, -1, aosTokens[i]))
12500 : {
12501 0 : nVarXId = -1;
12502 0 : CPL_IGNORE_RET_VAL(
12503 0 : nc_inq_varid(nCdfId, aosTokens[i], &nVarXId));
12504 : }
12505 0 : else if (NCDFIsVarLatitude(nCdfId, -1, aosTokens[i]) ||
12506 0 : NCDFIsVarProjectionY(nCdfId, -1, aosTokens[i]))
12507 : {
12508 0 : nVarYId = -1;
12509 0 : CPL_IGNORE_RET_VAL(
12510 0 : nc_inq_varid(nCdfId, aosTokens[i], &nVarYId));
12511 : }
12512 0 : else if (NCDFIsVarVerticalCoord(nCdfId, -1, aosTokens[i]))
12513 : {
12514 0 : nVarZId = -1;
12515 0 : CPL_IGNORE_RET_VAL(
12516 0 : nc_inq_varid(nCdfId, aosTokens[i], &nVarZId));
12517 : }
12518 : }
12519 : }
12520 51 : CPLFree(pszCoordinates);
12521 :
12522 : // Check that the X,Y,Z vars share 1D and share the same dimension as
12523 : // attribute variables.
12524 51 : if (nVarXId >= 0 && nVarYId >= 0)
12525 : {
12526 39 : int nVarDimCount = -1;
12527 39 : int nVarDimId = -1;
12528 39 : if (nc_inq_varndims(nCdfId, nVarXId, &nVarDimCount) != NC_NOERR ||
12529 39 : nVarDimCount != 1 ||
12530 39 : nc_inq_vardimid(nCdfId, nVarXId, &nVarDimId) != NC_NOERR ||
12531 39 : nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim) ||
12532 35 : nc_inq_varndims(nCdfId, nVarYId, &nVarDimCount) != NC_NOERR ||
12533 35 : nVarDimCount != 1 ||
12534 113 : nc_inq_vardimid(nCdfId, nVarYId, &nVarDimId) != NC_NOERR ||
12535 35 : nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim))
12536 : {
12537 4 : nVarXId = nVarYId = -1;
12538 : }
12539 69 : else if (nVarZId >= 0 &&
12540 34 : (nc_inq_varndims(nCdfId, nVarZId, &nVarDimCount) != NC_NOERR ||
12541 34 : nVarDimCount != 1 ||
12542 34 : nc_inq_vardimid(nCdfId, nVarZId, &nVarDimId) != NC_NOERR ||
12543 34 : nVarDimId != nVectorDim))
12544 : {
12545 0 : nVarZId = -1;
12546 : }
12547 : }
12548 :
12549 51 : if (eGType == wkbUnknown && nVarXId >= 0 && nVarYId >= 0)
12550 : {
12551 2 : eGType = wkbPoint;
12552 : }
12553 51 : if (eGType == wkbPoint && nVarXId >= 0 && nVarYId >= 0 && nVarZId >= 0)
12554 : {
12555 34 : eGType = wkbPoint25D;
12556 : }
12557 51 : if (eGType == wkbUnknown && osGeometryField.empty())
12558 : {
12559 7 : eGType = wkbNone;
12560 : }
12561 :
12562 : // Read projection info
12563 102 : CPLStringList aosMetadataBackup = aosMetadata;
12564 51 : ReadAttributes(nCdfId, nFirstVarId);
12565 51 : if (!this->bSGSupport)
12566 51 : SetProjectionFromVar(nCdfId, nFirstVarId, true);
12567 51 : const char *pszValue = FetchAttr(nCdfId, nFirstVarId, CF_GRD_MAPPING);
12568 102 : std::string osGridMapping = pszValue ? pszValue : "";
12569 51 : aosMetadata = aosMetadataBackup;
12570 :
12571 51 : OGRSpatialReference *poSRS = nullptr;
12572 51 : if (!m_oSRS.IsEmpty())
12573 : {
12574 21 : poSRS = m_oSRS.Clone();
12575 : }
12576 : // Reset if there's a 2D raster
12577 51 : m_bHasProjection = false;
12578 51 : m_bHasGeoTransform = false;
12579 :
12580 51 : if (!bKeepRasters)
12581 : {
12582 : // Strip out uninteresting metadata.
12583 45 : aosMetadata.SetNameValue("NC_GLOBAL#Conventions", nullptr);
12584 45 : aosMetadata.SetNameValue("NC_GLOBAL#GDAL", nullptr);
12585 45 : aosMetadata.SetNameValue("NC_GLOBAL#history", nullptr);
12586 : }
12587 :
12588 : std::shared_ptr<netCDFLayer> poLayer(
12589 51 : new netCDFLayer(this, nCdfId, osLayerName, eGType, poSRS));
12590 51 : if (poSRS != nullptr)
12591 21 : poSRS->Release();
12592 51 : poLayer->SetRecordDimID(nVectorDim);
12593 51 : if (wkbFlatten(eGType) == wkbPoint && nVarXId >= 0 && nVarYId >= 0)
12594 : {
12595 35 : poLayer->SetXYZVars(nVarXId, nVarYId, nVarZId);
12596 : }
12597 16 : else if (!osGeometryField.empty())
12598 : {
12599 9 : poLayer->SetWKTGeometryField(osGeometryField);
12600 : }
12601 51 : if (!osGridMapping.empty())
12602 : {
12603 21 : poLayer->SetGridMapping(osGridMapping.c_str());
12604 : }
12605 51 : poLayer->SetProfile(nProfileDimId, nParentIndexVarID);
12606 :
12607 578 : for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)
12608 : {
12609 527 : int anDimIds[2] = {-1, -1};
12610 527 : nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);
12611 527 : if (anDimIds[0] == nVectorDim ||
12612 24 : (nProfileDimId >= 0 && anDimIds[0] == nProfileDimId))
12613 : {
12614 : #ifdef NCDF_DEBUG
12615 : char szTemp2[NC_MAX_NAME + 1] = {};
12616 : CPL_IGNORE_RET_VAL(
12617 : nc_inq_varname(nCdfId, anPotentialVectorVarID[j], szTemp2));
12618 : CPLDebug("GDAL_netCDF", "Variable %s is a vector field", szTemp2);
12619 : #endif
12620 527 : poLayer->AddField(anPotentialVectorVarID[j]);
12621 : }
12622 : }
12623 :
12624 51 : if (poLayer->GetLayerDefn()->GetFieldCount() != 0 ||
12625 0 : poLayer->GetGeomType() != wkbNone)
12626 : {
12627 51 : papoLayers.push_back(poLayer);
12628 : }
12629 :
12630 102 : return CE_None;
12631 : }
12632 :
12633 : // Get all coordinate and boundary variables full names referenced in
12634 : // a given a NetCDF (or group) ID and its sub-groups.
12635 : // These variables are identified in other variable's
12636 : // "coordinates" and "bounds" attribute.
12637 : // Searching coordinate and boundary variables may need to explore
12638 : // parents groups (or other groups in case of reference given in form of an
12639 : // absolute path).
12640 : // See CF sections 5.2, 5.6 and 7.1
12641 551 : static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId,
12642 : CPLStringList &aosVars)
12643 : {
12644 551 : int nVars = 0;
12645 551 : NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
12646 :
12647 3334 : for (int v = 0; v < nVars; v++)
12648 : {
12649 2783 : char *pszTemp = nullptr;
12650 5566 : CPLStringList aosTokens;
12651 2783 : if (NCDFGetAttr(nCdfId, v, "coordinates", &pszTemp) == CE_None)
12652 454 : aosTokens.Assign(NCDFTokenizeCoordinatesAttribute(pszTemp));
12653 2783 : CPLFree(pszTemp);
12654 2783 : pszTemp = nullptr;
12655 2783 : if (NCDFGetAttr(nCdfId, v, "bounds", &pszTemp) == CE_None &&
12656 2783 : pszTemp != nullptr && !EQUAL(pszTemp, ""))
12657 17 : aosTokens.AddString(pszTemp);
12658 2783 : CPLFree(pszTemp);
12659 4087 : for (int i = 0; i < aosTokens.size(); i++)
12660 : {
12661 2608 : std::string osVarFullName;
12662 1304 : if (NCDFResolveVarFullName(nCdfId, aosTokens[i], osVarFullName) ==
12663 : CE_None)
12664 1278 : aosVars.AddString(osVarFullName);
12665 : }
12666 : }
12667 :
12668 : // Recurse on sub-groups.
12669 : int nSubGroups;
12670 551 : int *panSubGroupIds = nullptr;
12671 551 : NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
12672 587 : for (int i = 0; i < nSubGroups; i++)
12673 : {
12674 36 : NCDFGetCoordAndBoundVarFullNames(panSubGroupIds[i], aosVars);
12675 : }
12676 551 : CPLFree(panSubGroupIds);
12677 :
12678 551 : return CE_None;
12679 : }
12680 :
12681 : // Check if give type is user defined
12682 1935 : bool NCDFIsUserDefinedType(int /*ncid*/, int type)
12683 : {
12684 1935 : return type >= NC_FIRSTUSERTYPEID;
12685 : }
12686 :
12687 593 : char **NCDFTokenizeCoordinatesAttribute(const char *pszCoordinates)
12688 : {
12689 : // CF conventions use space as the separator for variable names in the
12690 : // coordinates attribute, but some products such as
12691 : // https://sentinel.esa.int/web/sentinel/technical-guides/sentinel-3-synergy/products-algorithms/level-2-aod-algorithms-and-products/level-2-aod-products-description
12692 : // use comma.
12693 593 : return CSLTokenizeString2(pszCoordinates, ", ", 0);
12694 : }
|