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 char **NCDFTokenizeArray(const char *pszValue);
98 : static void CopyMetadata(GDALDataset *poSrcDS, GDALRasterBand *poSrcBand,
99 : GDALRasterBand *poDstBand, int fpImage, int CDFVarID,
100 : const char *pszMatchPrefix = nullptr);
101 :
102 : // NetCDF-4 groups helper functions.
103 : // They all work also for NetCDF-3 files which are considered as
104 : // NetCDF-4 file with only one group.
105 : static CPLErr NCDFOpenSubDataset(int nCdfId, const char *pszSubdatasetName,
106 : int *pnGroupId, int *pnVarId);
107 : static CPLErr NCDFGetVisibleDims(int nGroupId, int *pnDims, int **ppanDimIds);
108 : static CPLErr NCDFGetSubGroups(int nGroupId, int *pnSubGroups,
109 : int **ppanSubGroupIds);
110 : static CPLErr NCDFGetGroupFullName(int nGroupId, char **ppszFullName,
111 : bool bNC3Compat = true);
112 : static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId, char **ppszFullName,
113 : bool bNC3Compat = true);
114 : static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId);
115 :
116 : static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,
117 : char **ppszFullName,
118 : bool bMandatory = false);
119 : static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,
120 : const char *pszAtt, int *pnAtt,
121 : bool bMandatory = false);
122 : static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId, char ***ppapszVars);
123 :
124 : // Uncomment this for more debug output.
125 : // #define NCDF_DEBUG 1
126 :
127 : CPLMutex *hNCMutex = nullptr;
128 :
129 : // Workaround https://github.com/OSGeo/gdal/issues/6253
130 : // Having 2 netCDF handles on the same file doesn't work in a multi-threaded
131 : // way. Apparently having the same handle works better (this is OK since
132 : // we have a global mutex on the netCDF library)
133 : static std::map<std::string, int> goMapNameToNetCDFId;
134 : static std::map<int, std::pair<std::string, int>> goMapNetCDFIdToKeyAndCount;
135 :
136 673 : int GDAL_nc_open(const char *pszFilename, int nMode, int *pID)
137 : {
138 1346 : std::string osKey(pszFilename);
139 673 : osKey += "#####";
140 673 : osKey += std::to_string(nMode);
141 673 : auto oIter = goMapNameToNetCDFId.find(osKey);
142 673 : if (oIter == goMapNameToNetCDFId.end())
143 : {
144 623 : int ret = nc_open(pszFilename, nMode, pID);
145 623 : if (ret != NC_NOERR)
146 3 : return ret;
147 620 : goMapNameToNetCDFId[osKey] = *pID;
148 620 : goMapNetCDFIdToKeyAndCount[*pID] =
149 1240 : std::pair<std::string, int>(osKey, 1);
150 620 : return ret;
151 : }
152 : else
153 : {
154 50 : *pID = oIter->second;
155 50 : goMapNetCDFIdToKeyAndCount[oIter->second].second++;
156 50 : return NC_NOERR;
157 : }
158 : }
159 :
160 930 : int GDAL_nc_close(int cdfid)
161 : {
162 930 : int ret = NC_NOERR;
163 930 : auto oIter = goMapNetCDFIdToKeyAndCount.find(cdfid);
164 930 : if (oIter != goMapNetCDFIdToKeyAndCount.end())
165 : {
166 670 : if (--oIter->second.second == 0)
167 : {
168 620 : ret = nc_close(cdfid);
169 620 : goMapNameToNetCDFId.erase(oIter->second.first);
170 620 : goMapNetCDFIdToKeyAndCount.erase(oIter);
171 : }
172 : }
173 : else
174 : {
175 : // we can go here if file opened with nc_open_mem() or nc_create()
176 260 : ret = nc_close(cdfid);
177 : }
178 930 : return ret;
179 : }
180 :
181 : /************************************************************************/
182 : /* ==================================================================== */
183 : /* netCDFRasterBand */
184 : /* ==================================================================== */
185 : /************************************************************************/
186 :
187 : class netCDFRasterBand final : public GDALPamRasterBand
188 : {
189 : friend class netCDFDataset;
190 :
191 : nc_type nc_datatype;
192 : int cdfid;
193 : int nZId;
194 : int nZDim;
195 : int nLevel;
196 : int nBandXPos;
197 : int nBandYPos;
198 : int *panBandZPos;
199 : int *panBandZLev;
200 : bool m_bNoDataSet = false;
201 : double m_dfNoDataValue = 0;
202 : bool m_bNoDataSetAsInt64 = false;
203 : int64_t m_nNodataValueInt64 = 0;
204 : bool m_bNoDataSetAsUInt64 = false;
205 : uint64_t m_nNodataValueUInt64 = 0;
206 : bool bValidRangeValid = false;
207 : double adfValidRange[2]{0, 0};
208 : bool m_bHaveScale = false;
209 : bool m_bHaveOffset = false;
210 : double m_dfScale = 1;
211 : double m_dfOffset = 0;
212 : CPLString m_osUnitType{};
213 : bool bSignedData;
214 : bool bCheckLongitude;
215 : bool m_bCreateMetadataFromOtherVarsDone = false;
216 :
217 : void CreateMetadataFromAttributes();
218 : void CreateMetadataFromOtherVars();
219 :
220 : template <class T>
221 : void CheckData(void *pImage, void *pImageNC, size_t nTmpBlockXSize,
222 : size_t nTmpBlockYSize, bool bCheckIsNan = false);
223 : template <class T>
224 : void CheckDataCpx(void *pImage, void *pImageNC, size_t nTmpBlockXSize,
225 : size_t nTmpBlockYSize, bool bCheckIsNan = false);
226 : void SetBlockSize();
227 :
228 : bool FetchNetcdfChunk(size_t xstart, size_t ystart, void *pImage);
229 :
230 : void SetNoDataValueNoUpdate(double dfNoData);
231 : void SetNoDataValueNoUpdate(int64_t nNoData);
232 : void SetNoDataValueNoUpdate(uint64_t nNoData);
233 :
234 : void SetOffsetNoUpdate(double dfVal);
235 : void SetScaleNoUpdate(double dfVal);
236 : void SetUnitTypeNoUpdate(const char *pszNewValue);
237 :
238 : protected:
239 : CPLXMLNode *SerializeToXML(const char *pszUnused) override;
240 :
241 : public:
242 : struct CONSTRUCTOR_OPEN
243 : {
244 : };
245 :
246 : struct CONSTRUCTOR_CREATE
247 : {
248 : };
249 :
250 : netCDFRasterBand(const CONSTRUCTOR_OPEN &, netCDFDataset *poDS,
251 : int nGroupId, int nZId, int nZDim, int nLevel,
252 : const int *panBandZLen, const int *panBandPos, int nBand);
253 : netCDFRasterBand(const CONSTRUCTOR_CREATE &, netCDFDataset *poDS,
254 : GDALDataType eType, int nBand, bool bSigned = true,
255 : const char *pszBandName = nullptr,
256 : const char *pszLongName = nullptr, int nZId = -1,
257 : int nZDim = 2, int nLevel = 0,
258 : const int *panBandZLev = nullptr,
259 : const int *panBandZPos = nullptr,
260 : const int *paDimIds = nullptr);
261 : virtual ~netCDFRasterBand();
262 :
263 : virtual double GetNoDataValue(int *) override;
264 : virtual int64_t GetNoDataValueAsInt64(int *pbSuccess = nullptr) override;
265 : virtual uint64_t GetNoDataValueAsUInt64(int *pbSuccess = nullptr) override;
266 : virtual CPLErr SetNoDataValue(double) override;
267 : virtual CPLErr SetNoDataValueAsInt64(int64_t nNoData) override;
268 : virtual CPLErr SetNoDataValueAsUInt64(uint64_t nNoData) override;
269 : // virtual CPLErr DeleteNoDataValue();
270 : virtual double GetOffset(int *) override;
271 : virtual CPLErr SetOffset(double) override;
272 : virtual double GetScale(int *) override;
273 : virtual CPLErr SetScale(double) override;
274 : virtual const char *GetUnitType() override;
275 : virtual CPLErr SetUnitType(const char *) override;
276 : virtual CPLErr IReadBlock(int, int, void *) override;
277 : virtual CPLErr IWriteBlock(int, int, void *) override;
278 :
279 : char **GetMetadata(const char *pszDomain = "") override;
280 : const char *GetMetadataItem(const char *pszName,
281 : const char *pszDomain = "") override;
282 :
283 : virtual CPLErr SetMetadataItem(const char *pszName, const char *pszValue,
284 : const char *pszDomain = "") override;
285 : virtual CPLErr SetMetadata(char **papszMD,
286 : const char *pszDomain = "") override;
287 : };
288 :
289 : /************************************************************************/
290 : /* netCDFRasterBand() */
291 : /************************************************************************/
292 :
293 480 : netCDFRasterBand::netCDFRasterBand(const netCDFRasterBand::CONSTRUCTOR_OPEN &,
294 : netCDFDataset *poNCDFDS, int nGroupId,
295 : int nZIdIn, int nZDimIn, int nLevelIn,
296 : const int *panBandZLevIn,
297 480 : const int *panBandZPosIn, int nBandIn)
298 : : nc_datatype(NC_NAT), cdfid(nGroupId), nZId(nZIdIn), nZDim(nZDimIn),
299 480 : nLevel(nLevelIn), nBandXPos(panBandZPosIn[0]),
300 480 : nBandYPos(nZDim == 1 ? -1 : panBandZPosIn[1]), panBandZPos(nullptr),
301 : panBandZLev(nullptr),
302 : bSignedData(true), // Default signed, except for Byte.
303 960 : bCheckLongitude(false)
304 : {
305 480 : poDS = poNCDFDS;
306 480 : nBand = nBandIn;
307 :
308 : // Take care of all other dimensions.
309 480 : if (nZDim > 2)
310 : {
311 176 : panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
312 176 : panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
313 :
314 478 : for (int i = 0; i < nZDim - 2; i++)
315 : {
316 302 : panBandZPos[i] = panBandZPosIn[i + 2];
317 302 : panBandZLev[i] = panBandZLevIn[i];
318 : }
319 : }
320 :
321 480 : nRasterXSize = poDS->GetRasterXSize();
322 480 : nRasterYSize = poDS->GetRasterYSize();
323 480 : nBlockXSize = poDS->GetRasterXSize();
324 480 : nBlockYSize = 1;
325 :
326 : // Get the type of the "z" variable, our target raster array.
327 480 : if (nc_inq_var(cdfid, nZId, nullptr, &nc_datatype, nullptr, nullptr,
328 480 : nullptr) != NC_NOERR)
329 : {
330 0 : CPLError(CE_Failure, CPLE_AppDefined, "Error in nc_var_inq() on 'z'.");
331 0 : return;
332 : }
333 :
334 480 : if (NCDFIsUserDefinedType(cdfid, nc_datatype))
335 : {
336 : // First enquire and check that the number of fields is 2
337 : size_t nfields, compoundsize;
338 5 : if (nc_inq_compound(cdfid, nc_datatype, nullptr, &compoundsize,
339 5 : &nfields) != NC_NOERR)
340 : {
341 0 : CPLError(CE_Failure, CPLE_AppDefined,
342 : "Error in nc_inq_compound() on 'z'.");
343 0 : return;
344 : }
345 :
346 5 : if (nfields != 2)
347 : {
348 0 : CPLError(CE_Failure, CPLE_AppDefined,
349 : "Unsupported data type encountered in nc_inq_compound() "
350 : "on 'z'.");
351 0 : return;
352 : }
353 :
354 : // Now check that that two types are the same in the struct.
355 : nc_type field_type1, field_type2;
356 : int field_dims1, field_dims2;
357 5 : if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
358 : &field_type1, &field_dims1,
359 5 : nullptr) != NC_NOERR)
360 : {
361 0 : CPLError(
362 : CE_Failure, CPLE_AppDefined,
363 : "Error in querying Field 1 in nc_inq_compound_field() on 'z'.");
364 0 : return;
365 : }
366 :
367 5 : if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
368 : &field_type2, &field_dims2,
369 5 : nullptr) != NC_NOERR)
370 : {
371 0 : CPLError(
372 : CE_Failure, CPLE_AppDefined,
373 : "Error in querying Field 2 in nc_inq_compound_field() on 'z'.");
374 0 : return;
375 : }
376 :
377 5 : if ((field_type1 != field_type2) || (field_dims1 != field_dims2) ||
378 5 : (field_dims1 != 0))
379 : {
380 0 : CPLError(CE_Failure, CPLE_AppDefined,
381 : "Error in interpreting compound data type on 'z'.");
382 0 : return;
383 : }
384 :
385 5 : if (field_type1 == NC_SHORT)
386 0 : eDataType = GDT_CInt16;
387 5 : else if (field_type1 == NC_INT)
388 0 : eDataType = GDT_CInt32;
389 5 : else if (field_type1 == NC_FLOAT)
390 4 : eDataType = GDT_CFloat32;
391 1 : else if (field_type1 == NC_DOUBLE)
392 1 : eDataType = GDT_CFloat64;
393 : else
394 : {
395 0 : CPLError(CE_Failure, CPLE_AppDefined,
396 : "Unsupported netCDF compound data type encountered.");
397 0 : return;
398 : }
399 : }
400 : else
401 : {
402 475 : if (nc_datatype == NC_BYTE)
403 145 : eDataType = GDT_Byte;
404 330 : else if (nc_datatype == NC_CHAR)
405 0 : eDataType = GDT_Byte;
406 330 : else if (nc_datatype == NC_SHORT)
407 41 : eDataType = GDT_Int16;
408 289 : else if (nc_datatype == NC_INT)
409 89 : eDataType = GDT_Int32;
410 200 : else if (nc_datatype == NC_FLOAT)
411 123 : eDataType = GDT_Float32;
412 77 : else if (nc_datatype == NC_DOUBLE)
413 40 : eDataType = GDT_Float64;
414 37 : else if (nc_datatype == NC_UBYTE)
415 15 : eDataType = GDT_Byte;
416 22 : else if (nc_datatype == NC_USHORT)
417 4 : eDataType = GDT_UInt16;
418 18 : else if (nc_datatype == NC_UINT)
419 3 : eDataType = GDT_UInt32;
420 15 : else if (nc_datatype == NC_INT64)
421 8 : eDataType = GDT_Int64;
422 7 : else if (nc_datatype == NC_UINT64)
423 7 : eDataType = GDT_UInt64;
424 : else
425 : {
426 0 : if (nBand == 1)
427 0 : CPLError(CE_Warning, CPLE_AppDefined,
428 : "Unsupported netCDF datatype (%d), treat as Float32.",
429 0 : static_cast<int>(nc_datatype));
430 0 : eDataType = GDT_Float32;
431 0 : nc_datatype = NC_FLOAT;
432 : }
433 : }
434 :
435 : // Find and set No Data for this variable.
436 480 : nc_type atttype = NC_NAT;
437 480 : size_t attlen = 0;
438 480 : const char *pszNoValueName = nullptr;
439 :
440 : // Find attribute name, either _FillValue or missing_value.
441 480 : int status = nc_inq_att(cdfid, nZId, NCDF_FillValue, &atttype, &attlen);
442 480 : if (status == NC_NOERR)
443 : {
444 248 : pszNoValueName = NCDF_FillValue;
445 : }
446 : else
447 : {
448 232 : status = nc_inq_att(cdfid, nZId, "missing_value", &atttype, &attlen);
449 232 : if (status == NC_NOERR)
450 : {
451 12 : pszNoValueName = "missing_value";
452 : }
453 : }
454 :
455 : // Fetch missing value.
456 480 : double dfNoData = 0.0;
457 480 : bool bGotNoData = false;
458 480 : int64_t nNoDataAsInt64 = 0;
459 480 : bool bGotNoDataAsInt64 = false;
460 480 : uint64_t nNoDataAsUInt64 = 0;
461 480 : bool bGotNoDataAsUInt64 = false;
462 480 : if (status == NC_NOERR)
463 : {
464 260 : nc_type nAttrType = NC_NAT;
465 260 : size_t nAttrLen = 0;
466 260 : status = nc_inq_att(cdfid, nZId, pszNoValueName, &nAttrType, &nAttrLen);
467 260 : if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_INT64)
468 : {
469 : long long v;
470 7 : nc_get_att_longlong(cdfid, nZId, pszNoValueName, &v);
471 7 : bGotNoData = true;
472 7 : bGotNoDataAsInt64 = true;
473 7 : nNoDataAsInt64 = static_cast<int64_t>(v);
474 : }
475 253 : else if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_UINT64)
476 : {
477 : unsigned long long v;
478 7 : nc_get_att_ulonglong(cdfid, nZId, pszNoValueName, &v);
479 7 : bGotNoData = true;
480 7 : bGotNoDataAsUInt64 = true;
481 7 : nNoDataAsUInt64 = static_cast<uint64_t>(v);
482 : }
483 246 : else if (NCDFGetAttr(cdfid, nZId, pszNoValueName, &dfNoData) == CE_None)
484 : {
485 245 : bGotNoData = true;
486 : }
487 : }
488 :
489 : // If NoData was not found, use the default value, but for non-Byte types
490 : // as it is not recommended:
491 : // https://www.unidata.ucar.edu/software/netcdf/docs/attribute_conventions.html
492 480 : nc_type vartype = NC_NAT;
493 480 : if (!bGotNoData)
494 : {
495 221 : nc_inq_vartype(cdfid, nZId, &vartype);
496 221 : if (vartype == NC_INT64)
497 : {
498 : nNoDataAsInt64 =
499 1 : NCDFGetDefaultNoDataValueAsInt64(cdfid, nZId, bGotNoData);
500 1 : bGotNoDataAsInt64 = bGotNoData;
501 : }
502 220 : else if (vartype == NC_UINT64)
503 : {
504 : nNoDataAsUInt64 =
505 0 : NCDFGetDefaultNoDataValueAsUInt64(cdfid, nZId, bGotNoData);
506 0 : bGotNoDataAsUInt64 = bGotNoData;
507 : }
508 220 : else if (vartype != NC_CHAR && vartype != NC_BYTE &&
509 98 : vartype != NC_UBYTE)
510 : {
511 89 : dfNoData =
512 89 : NCDFGetDefaultNoDataValue(cdfid, nZId, vartype, bGotNoData);
513 89 : if (bGotNoData)
514 : {
515 78 : CPLDebug("GDAL_netCDF",
516 : "did not get nodata value for variable #%d, using "
517 : "default %f",
518 : nZId, dfNoData);
519 : }
520 : }
521 : }
522 :
523 480 : bool bHasUnderscoreUnsignedAttr = false;
524 480 : bool bUnderscoreUnsignedAttrVal = false;
525 : {
526 480 : char *pszTemp = nullptr;
527 480 : if (NCDFGetAttr(cdfid, nZId, "_Unsigned", &pszTemp) == CE_None)
528 : {
529 137 : if (EQUAL(pszTemp, "true"))
530 : {
531 129 : bHasUnderscoreUnsignedAttr = true;
532 129 : bUnderscoreUnsignedAttrVal = true;
533 : }
534 8 : else if (EQUAL(pszTemp, "false"))
535 : {
536 8 : bHasUnderscoreUnsignedAttr = true;
537 8 : bUnderscoreUnsignedAttrVal = false;
538 : }
539 137 : CPLFree(pszTemp);
540 : }
541 : }
542 :
543 : // Look for valid_range or valid_min/valid_max.
544 :
545 : // First look for valid_range.
546 480 : if (CPLFetchBool(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE", true))
547 : {
548 478 : char *pszValidRange = nullptr;
549 478 : if (NCDFGetAttr(cdfid, nZId, "valid_range", &pszValidRange) ==
550 130 : CE_None &&
551 608 : pszValidRange[0] == '{' &&
552 130 : pszValidRange[strlen(pszValidRange) - 1] == '}')
553 : {
554 : const std::string osValidRange =
555 390 : std::string(pszValidRange).substr(1, strlen(pszValidRange) - 2);
556 : const CPLStringList aosValidRange(
557 260 : CSLTokenizeString2(osValidRange.c_str(), ",", 0));
558 130 : if (aosValidRange.size() == 2 &&
559 260 : CPLGetValueType(aosValidRange[0]) != CPL_VALUE_STRING &&
560 130 : CPLGetValueType(aosValidRange[1]) != CPL_VALUE_STRING)
561 : {
562 130 : bValidRangeValid = true;
563 130 : adfValidRange[0] = CPLAtof(aosValidRange[0]);
564 130 : adfValidRange[1] = CPLAtof(aosValidRange[1]);
565 : }
566 : }
567 478 : CPLFree(pszValidRange);
568 :
569 : // If not found look for valid_min and valid_max.
570 478 : if (!bValidRangeValid)
571 : {
572 348 : double dfMin = 0;
573 348 : double dfMax = 0;
574 363 : if (NCDFGetAttr(cdfid, nZId, "valid_min", &dfMin) == CE_None &&
575 15 : NCDFGetAttr(cdfid, nZId, "valid_max", &dfMax) == CE_None)
576 : {
577 8 : adfValidRange[0] = dfMin;
578 8 : adfValidRange[1] = dfMax;
579 8 : bValidRangeValid = true;
580 : }
581 : }
582 :
583 478 : if (bValidRangeValid &&
584 138 : (adfValidRange[0] < 0 || adfValidRange[1] < 0) &&
585 17 : nc_datatype == NC_SHORT && bHasUnderscoreUnsignedAttr &&
586 : bUnderscoreUnsignedAttrVal)
587 : {
588 2 : if (adfValidRange[0] < 0)
589 0 : adfValidRange[0] += 65536;
590 2 : if (adfValidRange[1] < 0)
591 2 : adfValidRange[1] += 65536;
592 2 : if (adfValidRange[0] <= adfValidRange[1])
593 : {
594 : // Updating metadata item
595 2 : GDALPamRasterBand::SetMetadataItem(
596 : "valid_range",
597 2 : CPLSPrintf("{%d,%d}", static_cast<int>(adfValidRange[0]),
598 2 : static_cast<int>(adfValidRange[1])));
599 : }
600 : }
601 :
602 478 : if (bValidRangeValid && adfValidRange[0] > adfValidRange[1])
603 : {
604 0 : CPLError(CE_Warning, CPLE_AppDefined,
605 : "netCDFDataset::valid_range: min > max:\n"
606 : " min: %lf\n max: %lf\n",
607 : adfValidRange[0], adfValidRange[1]);
608 0 : bValidRangeValid = false;
609 0 : adfValidRange[0] = 0.0;
610 0 : adfValidRange[1] = 0.0;
611 : }
612 : }
613 :
614 : // Special For Byte Bands: check for signed/unsigned byte.
615 480 : if (nc_datatype == NC_BYTE)
616 : {
617 : // netcdf uses signed byte by default, but GDAL uses unsigned by default
618 : // This may cause unexpected results, but is needed for back-compat.
619 145 : if (poNCDFDS->bIsGdalFile)
620 124 : bSignedData = false;
621 : else
622 21 : bSignedData = true;
623 :
624 : // For NC4 format NC_BYTE is (normally) signed, NC_UBYTE is unsigned.
625 : // But in case a NC3 file was converted automatically and has hints
626 : // that it is unsigned, take them into account
627 145 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
628 : {
629 3 : bSignedData = true;
630 : }
631 :
632 : // If we got valid_range, test for signed/unsigned range.
633 : // https://docs.unidata.ucar.edu/netcdf-c/current/attribute_conventions.html
634 145 : if (bValidRangeValid)
635 : {
636 : // If we got valid_range={0,255}, treat as unsigned.
637 126 : if (adfValidRange[0] == 0 && adfValidRange[1] == 255)
638 : {
639 118 : bSignedData = false;
640 : // Reset valid_range.
641 118 : bValidRangeValid = false;
642 : }
643 : // If we got valid_range={-128,127}, treat as signed.
644 8 : else if (adfValidRange[0] == -128 && adfValidRange[1] == 127)
645 : {
646 8 : bSignedData = true;
647 : // Reset valid_range.
648 8 : bValidRangeValid = false;
649 : }
650 : }
651 : // Else test for _Unsigned.
652 : // https://docs.unidata.ucar.edu/nug/current/best_practices.html
653 : else
654 : {
655 19 : if (bHasUnderscoreUnsignedAttr)
656 7 : bSignedData = !bUnderscoreUnsignedAttrVal;
657 : }
658 :
659 145 : if (bSignedData)
660 : {
661 20 : eDataType = GDT_Int8;
662 : }
663 125 : else if (dfNoData < 0)
664 : {
665 : // Fix nodata value as it was stored signed.
666 6 : dfNoData += 256;
667 6 : if (pszNoValueName)
668 : {
669 : // Updating metadata item
670 6 : GDALPamRasterBand::SetMetadataItem(
671 : pszNoValueName,
672 : CPLSPrintf("%d", static_cast<int>(dfNoData)));
673 : }
674 : }
675 : }
676 335 : else if (nc_datatype == NC_SHORT)
677 : {
678 41 : if (bHasUnderscoreUnsignedAttr)
679 : {
680 4 : bSignedData = !bUnderscoreUnsignedAttrVal;
681 4 : if (!bSignedData)
682 4 : eDataType = GDT_UInt16;
683 : }
684 :
685 : // Fix nodata value as it was stored signed.
686 41 : if (!bSignedData && dfNoData < 0)
687 : {
688 4 : dfNoData += 65536;
689 4 : if (pszNoValueName)
690 : {
691 : // Updating metadata item
692 4 : GDALPamRasterBand::SetMetadataItem(
693 : pszNoValueName,
694 : CPLSPrintf("%d", static_cast<int>(dfNoData)));
695 : }
696 : }
697 : }
698 :
699 294 : else if (nc_datatype == NC_UBYTE || nc_datatype == NC_USHORT ||
700 275 : nc_datatype == NC_UINT || nc_datatype == NC_UINT64)
701 : {
702 29 : bSignedData = false;
703 : }
704 :
705 480 : CPLDebug("GDAL_netCDF", "netcdf type=%d gdal type=%d signedByte=%d",
706 480 : nc_datatype, eDataType, static_cast<int>(bSignedData));
707 :
708 480 : if (bGotNoData)
709 : {
710 : // Set nodata value.
711 338 : if (bGotNoDataAsInt64)
712 : {
713 8 : if (eDataType == GDT_Int64)
714 : {
715 8 : SetNoDataValueNoUpdate(nNoDataAsInt64);
716 : }
717 0 : else if (eDataType == GDT_UInt64 && nNoDataAsInt64 >= 0)
718 : {
719 0 : SetNoDataValueNoUpdate(static_cast<uint64_t>(nNoDataAsInt64));
720 : }
721 : else
722 : {
723 0 : SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsInt64));
724 : }
725 : }
726 330 : else if (bGotNoDataAsUInt64)
727 : {
728 7 : if (eDataType == GDT_UInt64)
729 : {
730 7 : SetNoDataValueNoUpdate(nNoDataAsUInt64);
731 : }
732 0 : else if (eDataType == GDT_Int64 &&
733 : nNoDataAsUInt64 <=
734 0 : static_cast<uint64_t>(
735 0 : std::numeric_limits<int64_t>::max()))
736 : {
737 0 : SetNoDataValueNoUpdate(static_cast<int64_t>(nNoDataAsUInt64));
738 : }
739 : else
740 : {
741 0 : SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsUInt64));
742 : }
743 : }
744 : else
745 : {
746 : #ifdef NCDF_DEBUG
747 : CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) read", dfNoData);
748 : #endif
749 323 : if (eDataType == GDT_Int64 && GDALIsValueExactAs<int64_t>(dfNoData))
750 : {
751 0 : SetNoDataValueNoUpdate(static_cast<int64_t>(dfNoData));
752 : }
753 323 : else if (eDataType == GDT_UInt64 &&
754 0 : GDALIsValueExactAs<uint64_t>(dfNoData))
755 : {
756 0 : SetNoDataValueNoUpdate(static_cast<uint64_t>(dfNoData));
757 : }
758 : else
759 : {
760 323 : SetNoDataValueNoUpdate(dfNoData);
761 : }
762 : }
763 : }
764 :
765 480 : CreateMetadataFromAttributes();
766 :
767 : // Attempt to fetch the scale_factor and add_offset attributes for the
768 : // variable and set them. If these values are not available, set
769 : // offset to 0 and scale to 1.
770 480 : if (nc_inq_attid(cdfid, nZId, CF_ADD_OFFSET, nullptr) == NC_NOERR)
771 : {
772 16 : double dfOffset = 0;
773 16 : status = nc_get_att_double(cdfid, nZId, CF_ADD_OFFSET, &dfOffset);
774 16 : CPLDebug("GDAL_netCDF", "got add_offset=%.16g, status=%d", dfOffset,
775 : status);
776 16 : SetOffsetNoUpdate(dfOffset);
777 : }
778 :
779 480 : bool bHasScale = false;
780 480 : if (nc_inq_attid(cdfid, nZId, CF_SCALE_FACTOR, nullptr) == NC_NOERR)
781 : {
782 20 : bHasScale = true;
783 20 : double dfScale = 1;
784 20 : status = nc_get_att_double(cdfid, nZId, CF_SCALE_FACTOR, &dfScale);
785 20 : CPLDebug("GDAL_netCDF", "got scale_factor=%.16g, status=%d", dfScale,
786 : status);
787 20 : SetScaleNoUpdate(dfScale);
788 : }
789 :
790 12 : if (bValidRangeValid && GDALDataTypeIsInteger(eDataType) &&
791 4 : eDataType != GDT_Int64 && eDataType != GDT_UInt64 &&
792 4 : (std::fabs(std::round(adfValidRange[0]) - adfValidRange[0]) > 1e-5 ||
793 492 : std::fabs(std::round(adfValidRange[1]) - adfValidRange[1]) > 1e-5) &&
794 1 : CSLFetchNameValue(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE") ==
795 : nullptr)
796 : {
797 1 : CPLError(CE_Warning, CPLE_AppDefined,
798 : "validity range = %f, %f contains floating-point values, "
799 : "whereas data type is integer. valid_range is thus likely "
800 : "wrong%s. Ignoring it.",
801 : adfValidRange[0], adfValidRange[1],
802 : bHasScale ? " (likely scaled using scale_factor/add_factor "
803 : "whereas it should be using the packed data type)"
804 : : "");
805 1 : bValidRangeValid = false;
806 1 : adfValidRange[0] = 0.0;
807 1 : adfValidRange[1] = 0.0;
808 : }
809 :
810 : // Should we check for longitude values > 360?
811 480 : bCheckLongitude =
812 960 : CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")) &&
813 480 : NCDFIsVarLongitude(cdfid, nZId, nullptr);
814 :
815 : // Attempt to fetch the units attribute for the variable and set it.
816 480 : SetUnitTypeNoUpdate(netCDFRasterBand::GetMetadataItem(CF_UNITS));
817 :
818 480 : SetBlockSize();
819 : }
820 :
821 662 : void netCDFRasterBand::SetBlockSize()
822 : {
823 : // Check for variable chunking (netcdf-4 only).
824 : // GDAL block size should be set to hdf5 chunk size.
825 662 : int nTmpFormat = 0;
826 662 : int status = nc_inq_format(cdfid, &nTmpFormat);
827 662 : NetCDFFormatEnum eTmpFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
828 662 : if ((status == NC_NOERR) &&
829 565 : (eTmpFormat == NCDF_FORMAT_NC4 || eTmpFormat == NCDF_FORMAT_NC4C))
830 : {
831 113 : size_t chunksize[MAX_NC_DIMS] = {};
832 : // Check for chunksize and set it as the blocksize (optimizes read).
833 113 : status = nc_inq_var_chunking(cdfid, nZId, &nTmpFormat, chunksize);
834 113 : if ((status == NC_NOERR) && (nTmpFormat == NC_CHUNKED))
835 : {
836 13 : nBlockXSize = (int)chunksize[nZDim - 1];
837 13 : if (nZDim >= 2)
838 13 : nBlockYSize = (int)chunksize[nZDim - 2];
839 : else
840 0 : nBlockYSize = 1;
841 : }
842 : }
843 :
844 : // Deal with bottom-up datasets and nBlockYSize != 1.
845 662 : auto poGDS = static_cast<netCDFDataset *>(poDS);
846 662 : if (poGDS->bBottomUp && nBlockYSize != 1 && poGDS->poChunkCache == nullptr)
847 : {
848 5 : if (poGDS->eAccess == GA_ReadOnly)
849 : {
850 : // Try to cache 1 or 2 'rows' of netCDF chunks along the whole
851 : // width of the raster
852 5 : size_t nChunks =
853 5 : static_cast<size_t>(DIV_ROUND_UP(nRasterXSize, nBlockXSize));
854 5 : if ((nRasterYSize % nBlockYSize) != 0)
855 1 : nChunks *= 2;
856 : const size_t nChunkSize =
857 5 : static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
858 5 : nBlockXSize * nBlockYSize;
859 5 : constexpr size_t MAX_CACHE_SIZE = 100 * 1024 * 1024;
860 5 : nChunks = std::min(nChunks, MAX_CACHE_SIZE / nChunkSize);
861 5 : if (nChunks)
862 : {
863 5 : poGDS->poChunkCache.reset(
864 5 : new netCDFDataset::ChunkCacheType(nChunks));
865 : }
866 : }
867 : else
868 : {
869 0 : nBlockYSize = 1;
870 : }
871 : }
872 662 : }
873 :
874 : // Constructor in create mode.
875 : // If nZId and following variables are not passed, the band will have 2
876 : // dimensions.
877 : // TODO: Get metadata, missing val from band #1 if nZDim > 2.
878 182 : netCDFRasterBand::netCDFRasterBand(
879 : const netCDFRasterBand::CONSTRUCTOR_CREATE &, netCDFDataset *poNCDFDS,
880 : const GDALDataType eTypeIn, int nBandIn, bool bSigned,
881 : const char *pszBandName, const char *pszLongName, int nZIdIn, int nZDimIn,
882 : int nLevelIn, const int *panBandZLevIn, const int *panBandZPosIn,
883 182 : const int *paDimIds)
884 182 : : nc_datatype(NC_NAT), cdfid(poNCDFDS->GetCDFID()), nZId(nZIdIn),
885 : nZDim(nZDimIn), nLevel(nLevelIn), nBandXPos(1), nBandYPos(0),
886 : panBandZPos(nullptr), panBandZLev(nullptr), bSignedData(bSigned),
887 182 : bCheckLongitude(false), m_bCreateMetadataFromOtherVarsDone(true)
888 : {
889 182 : poDS = poNCDFDS;
890 182 : nBand = nBandIn;
891 :
892 182 : nRasterXSize = poDS->GetRasterXSize();
893 182 : nRasterYSize = poDS->GetRasterYSize();
894 182 : nBlockXSize = poDS->GetRasterXSize();
895 182 : nBlockYSize = 1;
896 :
897 182 : if (poDS->GetAccess() != GA_Update)
898 : {
899 0 : CPLError(CE_Failure, CPLE_NotSupported,
900 : "Dataset is not in update mode, "
901 : "wrong netCDFRasterBand constructor");
902 0 : return;
903 : }
904 :
905 : // Take care of all other dimensions.
906 182 : if (nZDim > 2 && paDimIds != nullptr)
907 : {
908 27 : nBandXPos = panBandZPosIn[0];
909 27 : nBandYPos = panBandZPosIn[1];
910 :
911 27 : panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
912 27 : panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
913 :
914 76 : for (int i = 0; i < nZDim - 2; i++)
915 : {
916 49 : panBandZPos[i] = panBandZPosIn[i + 2];
917 49 : panBandZLev[i] = panBandZLevIn[i];
918 : }
919 : }
920 :
921 : // Get the type of the "z" variable, our target raster array.
922 182 : eDataType = eTypeIn;
923 :
924 182 : switch (eDataType)
925 : {
926 77 : case GDT_Byte:
927 77 : nc_datatype = NC_BYTE;
928 : // NC_UBYTE (unsigned byte) is only available for NC4.
929 77 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
930 3 : nc_datatype = NC_UBYTE;
931 77 : break;
932 7 : case GDT_Int8:
933 7 : nc_datatype = NC_BYTE;
934 7 : break;
935 11 : case GDT_Int16:
936 11 : nc_datatype = NC_SHORT;
937 11 : break;
938 24 : case GDT_Int32:
939 24 : nc_datatype = NC_INT;
940 24 : break;
941 13 : case GDT_Float32:
942 13 : nc_datatype = NC_FLOAT;
943 13 : break;
944 8 : case GDT_Float64:
945 8 : nc_datatype = NC_DOUBLE;
946 8 : break;
947 7 : case GDT_Int64:
948 7 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
949 : {
950 7 : nc_datatype = NC_INT64;
951 : }
952 : else
953 : {
954 0 : if (nBand == 1)
955 0 : CPLError(
956 : CE_Warning, CPLE_AppDefined,
957 : "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
958 : "Int64");
959 0 : nc_datatype = NC_DOUBLE;
960 0 : eDataType = GDT_Float64;
961 : }
962 7 : break;
963 7 : case GDT_UInt64:
964 7 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
965 : {
966 7 : nc_datatype = NC_UINT64;
967 : }
968 : else
969 : {
970 0 : if (nBand == 1)
971 0 : CPLError(
972 : CE_Warning, CPLE_AppDefined,
973 : "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
974 : "UInt64");
975 0 : nc_datatype = NC_DOUBLE;
976 0 : eDataType = GDT_Float64;
977 : }
978 7 : break;
979 6 : case GDT_UInt16:
980 6 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
981 : {
982 6 : nc_datatype = NC_USHORT;
983 6 : break;
984 : }
985 : [[fallthrough]];
986 : case GDT_UInt32:
987 6 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
988 : {
989 6 : nc_datatype = NC_UINT;
990 6 : break;
991 : }
992 : [[fallthrough]];
993 : default:
994 16 : if (nBand == 1)
995 8 : CPLError(CE_Warning, CPLE_AppDefined,
996 : "Unsupported GDAL datatype (%d), treat as NC_FLOAT.",
997 8 : static_cast<int>(eDataType));
998 16 : nc_datatype = NC_FLOAT;
999 16 : eDataType = GDT_Float32;
1000 16 : break;
1001 : }
1002 :
1003 : // Define the variable if necessary (if nZId == -1).
1004 182 : bool bDefineVar = false;
1005 :
1006 182 : if (nZId == -1)
1007 : {
1008 160 : bDefineVar = true;
1009 :
1010 : // Make sure we are in define mode.
1011 160 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1012 :
1013 : char szTempPrivate[256 + 1];
1014 160 : const char *pszTemp = nullptr;
1015 160 : if (!pszBandName || EQUAL(pszBandName, ""))
1016 : {
1017 138 : snprintf(szTempPrivate, sizeof(szTempPrivate), "Band%d", nBand);
1018 138 : pszTemp = szTempPrivate;
1019 : }
1020 : else
1021 : {
1022 22 : pszTemp = pszBandName;
1023 : }
1024 :
1025 : int status;
1026 160 : if (nZDim > 2 && paDimIds != nullptr)
1027 : {
1028 5 : status =
1029 5 : nc_def_var(cdfid, pszTemp, nc_datatype, nZDim, paDimIds, &nZId);
1030 : }
1031 : else
1032 : {
1033 155 : int anBandDims[2] = {poNCDFDS->nYDimID, poNCDFDS->nXDimID};
1034 : status =
1035 155 : nc_def_var(cdfid, pszTemp, nc_datatype, 2, anBandDims, &nZId);
1036 : }
1037 160 : NCDF_ERR(status);
1038 160 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d) id=%d", cdfid, pszTemp,
1039 : nc_datatype, nZId);
1040 :
1041 160 : if (!pszLongName || EQUAL(pszLongName, ""))
1042 : {
1043 153 : snprintf(szTempPrivate, sizeof(szTempPrivate),
1044 : "GDAL Band Number %d", nBand);
1045 153 : pszTemp = szTempPrivate;
1046 : }
1047 : else
1048 : {
1049 7 : pszTemp = pszLongName;
1050 : }
1051 : status =
1052 160 : nc_put_att_text(cdfid, nZId, CF_LNG_NAME, strlen(pszTemp), pszTemp);
1053 160 : NCDF_ERR(status);
1054 :
1055 160 : poNCDFDS->DefVarDeflate(nZId, true);
1056 : }
1057 :
1058 : // For Byte data add signed/unsigned info.
1059 182 : if (eDataType == GDT_Byte || eDataType == GDT_Int8)
1060 : {
1061 84 : if (bDefineVar)
1062 : {
1063 : // Only add attributes if creating variable.
1064 : // For unsigned NC_BYTE (except NC4 format),
1065 : // add valid_range and _Unsigned ( defined in CF-1 and NUG ).
1066 76 : if (nc_datatype == NC_BYTE && poNCDFDS->eFormat != NCDF_FORMAT_NC4)
1067 : {
1068 73 : CPLDebug("GDAL_netCDF",
1069 : "adding valid_range attributes for Byte Band");
1070 73 : short l_adfValidRange[2] = {0, 0};
1071 : int status;
1072 73 : if (bSignedData || eDataType == GDT_Int8)
1073 : {
1074 7 : l_adfValidRange[0] = -128;
1075 7 : l_adfValidRange[1] = 127;
1076 7 : status =
1077 7 : nc_put_att_text(cdfid, nZId, "_Unsigned", 5, "false");
1078 : }
1079 : else
1080 : {
1081 66 : l_adfValidRange[0] = 0;
1082 66 : l_adfValidRange[1] = 255;
1083 : status =
1084 66 : nc_put_att_text(cdfid, nZId, "_Unsigned", 4, "true");
1085 : }
1086 73 : NCDF_ERR(status);
1087 73 : status = nc_put_att_short(cdfid, nZId, "valid_range", NC_SHORT,
1088 : 2, l_adfValidRange);
1089 73 : NCDF_ERR(status);
1090 : }
1091 : }
1092 : }
1093 :
1094 182 : if (nc_datatype != NC_BYTE && nc_datatype != NC_CHAR &&
1095 101 : nc_datatype != NC_UBYTE)
1096 : {
1097 : // Set default nodata.
1098 98 : bool bIgnored = false;
1099 : double dfNoData =
1100 98 : NCDFGetDefaultNoDataValue(cdfid, nZId, nc_datatype, bIgnored);
1101 : #ifdef NCDF_DEBUG
1102 : CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) default", dfNoData);
1103 : #endif
1104 98 : netCDFRasterBand::SetNoDataValue(dfNoData);
1105 : }
1106 :
1107 182 : SetBlockSize();
1108 : }
1109 :
1110 : /************************************************************************/
1111 : /* ~netCDFRasterBand() */
1112 : /************************************************************************/
1113 :
1114 1324 : netCDFRasterBand::~netCDFRasterBand()
1115 : {
1116 662 : netCDFRasterBand::FlushCache(true);
1117 662 : CPLFree(panBandZPos);
1118 662 : CPLFree(panBandZLev);
1119 1324 : }
1120 :
1121 : /************************************************************************/
1122 : /* GetMetadata() */
1123 : /************************************************************************/
1124 :
1125 50 : char **netCDFRasterBand::GetMetadata(const char *pszDomain)
1126 : {
1127 50 : if (!m_bCreateMetadataFromOtherVarsDone)
1128 48 : CreateMetadataFromOtherVars();
1129 50 : return GDALPamRasterBand::GetMetadata(pszDomain);
1130 : }
1131 :
1132 : /************************************************************************/
1133 : /* GetMetadataItem() */
1134 : /************************************************************************/
1135 :
1136 554 : const char *netCDFRasterBand::GetMetadataItem(const char *pszName,
1137 : const char *pszDomain)
1138 : {
1139 554 : if (!m_bCreateMetadataFromOtherVarsDone &&
1140 538 : STARTS_WITH(pszName, "NETCDF_DIM_") &&
1141 1 : (!pszDomain || pszDomain[0] == 0))
1142 1 : CreateMetadataFromOtherVars();
1143 554 : return GDALPamRasterBand::GetMetadataItem(pszName, pszDomain);
1144 : }
1145 :
1146 : /************************************************************************/
1147 : /* SetMetadataItem() */
1148 : /************************************************************************/
1149 :
1150 7 : CPLErr netCDFRasterBand::SetMetadataItem(const char *pszName,
1151 : const char *pszValue,
1152 : const char *pszDomain)
1153 : {
1154 9 : if (GetAccess() == GA_Update &&
1155 9 : (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)
1156 : {
1157 : // Same logic as in CopyMetadata()
1158 :
1159 2 : const char *const papszIgnoreBand[] = {
1160 : CF_ADD_OFFSET, CF_SCALE_FACTOR, "valid_range", "_Unsigned",
1161 : NCDF_FillValue, "coordinates", nullptr};
1162 : // Do not copy varname, stats, NETCDF_DIM_*, nodata
1163 : // and items in papszIgnoreBand.
1164 6 : if (STARTS_WITH(pszName, "NETCDF_VARNAME") ||
1165 2 : STARTS_WITH(pszName, "STATISTICS_") ||
1166 2 : STARTS_WITH(pszName, "NETCDF_DIM_") ||
1167 2 : STARTS_WITH(pszName, "missing_value") ||
1168 6 : STARTS_WITH(pszName, "_FillValue") ||
1169 2 : CSLFindString(papszIgnoreBand, pszName) != -1)
1170 : {
1171 : // do nothing
1172 : }
1173 : else
1174 : {
1175 2 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1176 :
1177 2 : if (!NCDFPutAttr(cdfid, nZId, pszName, pszValue))
1178 2 : return CE_Failure;
1179 : }
1180 : }
1181 :
1182 5 : return GDALPamRasterBand::SetMetadataItem(pszName, pszValue, pszDomain);
1183 : }
1184 :
1185 : /************************************************************************/
1186 : /* SetMetadata() */
1187 : /************************************************************************/
1188 :
1189 2 : CPLErr netCDFRasterBand::SetMetadata(char **papszMD, const char *pszDomain)
1190 : {
1191 4 : if (GetAccess() == GA_Update &&
1192 2 : (pszDomain == nullptr || pszDomain[0] == '\0'))
1193 : {
1194 : // We don't handle metadata item removal for now
1195 4 : for (const char *const *papszIter = papszMD; papszIter && *papszIter;
1196 : ++papszIter)
1197 : {
1198 2 : char *pszName = nullptr;
1199 2 : const char *pszValue = CPLParseNameValue(*papszIter, &pszName);
1200 2 : if (pszName && pszValue)
1201 2 : SetMetadataItem(pszName, pszValue);
1202 2 : CPLFree(pszName);
1203 : }
1204 : }
1205 2 : return GDALPamRasterBand::SetMetadata(papszMD, pszDomain);
1206 : }
1207 :
1208 : /************************************************************************/
1209 : /* GetOffset() */
1210 : /************************************************************************/
1211 50 : double netCDFRasterBand::GetOffset(int *pbSuccess)
1212 : {
1213 50 : if (pbSuccess != nullptr)
1214 45 : *pbSuccess = static_cast<int>(m_bHaveOffset);
1215 :
1216 50 : return m_dfOffset;
1217 : }
1218 :
1219 : /************************************************************************/
1220 : /* SetOffset() */
1221 : /************************************************************************/
1222 1 : CPLErr netCDFRasterBand::SetOffset(double dfNewOffset)
1223 : {
1224 2 : CPLMutexHolderD(&hNCMutex);
1225 :
1226 : // Write value if in update mode.
1227 1 : if (poDS->GetAccess() == GA_Update)
1228 : {
1229 : // Make sure we are in define mode.
1230 1 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1231 :
1232 1 : const int status = nc_put_att_double(cdfid, nZId, CF_ADD_OFFSET,
1233 : NC_DOUBLE, 1, &dfNewOffset);
1234 :
1235 1 : NCDF_ERR(status);
1236 1 : if (status == NC_NOERR)
1237 : {
1238 1 : SetOffsetNoUpdate(dfNewOffset);
1239 1 : return CE_None;
1240 : }
1241 :
1242 0 : return CE_Failure;
1243 : }
1244 :
1245 0 : SetOffsetNoUpdate(dfNewOffset);
1246 0 : return CE_None;
1247 : }
1248 :
1249 : /************************************************************************/
1250 : /* SetOffsetNoUpdate() */
1251 : /************************************************************************/
1252 17 : void netCDFRasterBand::SetOffsetNoUpdate(double dfVal)
1253 : {
1254 17 : m_dfOffset = dfVal;
1255 17 : m_bHaveOffset = true;
1256 17 : }
1257 :
1258 : /************************************************************************/
1259 : /* GetScale() */
1260 : /************************************************************************/
1261 50 : double netCDFRasterBand::GetScale(int *pbSuccess)
1262 : {
1263 50 : if (pbSuccess != nullptr)
1264 45 : *pbSuccess = static_cast<int>(m_bHaveScale);
1265 :
1266 50 : return m_dfScale;
1267 : }
1268 :
1269 : /************************************************************************/
1270 : /* SetScale() */
1271 : /************************************************************************/
1272 1 : CPLErr netCDFRasterBand::SetScale(double dfNewScale)
1273 : {
1274 2 : CPLMutexHolderD(&hNCMutex);
1275 :
1276 : // Write value if in update mode.
1277 1 : if (poDS->GetAccess() == GA_Update)
1278 : {
1279 : // Make sure we are in define mode.
1280 1 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1281 :
1282 1 : const int status = nc_put_att_double(cdfid, nZId, CF_SCALE_FACTOR,
1283 : NC_DOUBLE, 1, &dfNewScale);
1284 :
1285 1 : NCDF_ERR(status);
1286 1 : if (status == NC_NOERR)
1287 : {
1288 1 : SetScaleNoUpdate(dfNewScale);
1289 1 : return CE_None;
1290 : }
1291 :
1292 0 : return CE_Failure;
1293 : }
1294 :
1295 0 : SetScaleNoUpdate(dfNewScale);
1296 0 : return CE_None;
1297 : }
1298 :
1299 : /************************************************************************/
1300 : /* SetScaleNoUpdate() */
1301 : /************************************************************************/
1302 21 : void netCDFRasterBand::SetScaleNoUpdate(double dfVal)
1303 : {
1304 21 : m_dfScale = dfVal;
1305 21 : m_bHaveScale = true;
1306 21 : }
1307 :
1308 : /************************************************************************/
1309 : /* GetUnitType() */
1310 : /************************************************************************/
1311 :
1312 22 : const char *netCDFRasterBand::GetUnitType()
1313 :
1314 : {
1315 22 : if (!m_osUnitType.empty())
1316 6 : return m_osUnitType;
1317 :
1318 16 : return GDALRasterBand::GetUnitType();
1319 : }
1320 :
1321 : /************************************************************************/
1322 : /* SetUnitType() */
1323 : /************************************************************************/
1324 :
1325 1 : CPLErr netCDFRasterBand::SetUnitType(const char *pszNewValue)
1326 :
1327 : {
1328 2 : CPLMutexHolderD(&hNCMutex);
1329 :
1330 2 : const std::string osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
1331 :
1332 1 : if (!osUnitType.empty())
1333 : {
1334 : // Write value if in update mode.
1335 1 : if (poDS->GetAccess() == GA_Update)
1336 : {
1337 : // Make sure we are in define mode.
1338 1 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(TRUE);
1339 :
1340 1 : const int status = nc_put_att_text(
1341 : cdfid, nZId, CF_UNITS, osUnitType.size(), osUnitType.c_str());
1342 :
1343 1 : NCDF_ERR(status);
1344 1 : if (status == NC_NOERR)
1345 : {
1346 1 : SetUnitTypeNoUpdate(pszNewValue);
1347 1 : return CE_None;
1348 : }
1349 :
1350 0 : return CE_Failure;
1351 : }
1352 : }
1353 :
1354 0 : SetUnitTypeNoUpdate(pszNewValue);
1355 :
1356 0 : return CE_None;
1357 : }
1358 :
1359 : /************************************************************************/
1360 : /* SetUnitTypeNoUpdate() */
1361 : /************************************************************************/
1362 :
1363 481 : void netCDFRasterBand::SetUnitTypeNoUpdate(const char *pszNewValue)
1364 : {
1365 481 : m_osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
1366 481 : }
1367 :
1368 : /************************************************************************/
1369 : /* GetNoDataValue() */
1370 : /************************************************************************/
1371 :
1372 162 : double netCDFRasterBand::GetNoDataValue(int *pbSuccess)
1373 :
1374 : {
1375 162 : if (m_bNoDataSetAsInt64)
1376 : {
1377 0 : if (pbSuccess)
1378 0 : *pbSuccess = TRUE;
1379 0 : return GDALGetNoDataValueCastToDouble(m_nNodataValueInt64);
1380 : }
1381 :
1382 162 : if (m_bNoDataSetAsUInt64)
1383 : {
1384 0 : if (pbSuccess)
1385 0 : *pbSuccess = TRUE;
1386 0 : return GDALGetNoDataValueCastToDouble(m_nNodataValueUInt64);
1387 : }
1388 :
1389 162 : if (m_bNoDataSet)
1390 : {
1391 125 : if (pbSuccess)
1392 109 : *pbSuccess = TRUE;
1393 125 : return m_dfNoDataValue;
1394 : }
1395 :
1396 37 : return GDALPamRasterBand::GetNoDataValue(pbSuccess);
1397 : }
1398 :
1399 : /************************************************************************/
1400 : /* GetNoDataValueAsInt64() */
1401 : /************************************************************************/
1402 :
1403 4 : int64_t netCDFRasterBand::GetNoDataValueAsInt64(int *pbSuccess)
1404 :
1405 : {
1406 4 : if (m_bNoDataSetAsInt64)
1407 : {
1408 4 : if (pbSuccess)
1409 4 : *pbSuccess = TRUE;
1410 :
1411 4 : return m_nNodataValueInt64;
1412 : }
1413 :
1414 0 : return GDALPamRasterBand::GetNoDataValueAsInt64(pbSuccess);
1415 : }
1416 :
1417 : /************************************************************************/
1418 : /* GetNoDataValueAsUInt64() */
1419 : /************************************************************************/
1420 :
1421 4 : uint64_t netCDFRasterBand::GetNoDataValueAsUInt64(int *pbSuccess)
1422 :
1423 : {
1424 4 : if (m_bNoDataSetAsUInt64)
1425 : {
1426 4 : if (pbSuccess)
1427 4 : *pbSuccess = TRUE;
1428 :
1429 4 : return m_nNodataValueUInt64;
1430 : }
1431 :
1432 0 : return GDALPamRasterBand::GetNoDataValueAsUInt64(pbSuccess);
1433 : }
1434 :
1435 : /************************************************************************/
1436 : /* SetNoDataValue() */
1437 : /************************************************************************/
1438 :
1439 134 : CPLErr netCDFRasterBand::SetNoDataValue(double dfNoData)
1440 :
1441 : {
1442 268 : CPLMutexHolderD(&hNCMutex);
1443 :
1444 : // If already set to new value, don't do anything.
1445 134 : if (m_bNoDataSet && CPLIsEqual(dfNoData, m_dfNoDataValue))
1446 19 : return CE_None;
1447 :
1448 : // Write value if in update mode.
1449 115 : if (poDS->GetAccess() == GA_Update)
1450 : {
1451 : // netcdf-4 does not allow to set _FillValue after leaving define mode,
1452 : // but it is ok if variable has not been written to, so only print
1453 : // debug. See bug #4484.
1454 125 : if (m_bNoDataSet &&
1455 10 : !cpl::down_cast<netCDFDataset *>(poDS)->GetDefineMode())
1456 : {
1457 0 : CPLDebug("GDAL_netCDF",
1458 : "Setting NoDataValue to %.17g (previously set to %.17g) "
1459 : "but file is no longer in define mode (id #%d, band #%d)",
1460 : dfNoData, m_dfNoDataValue, cdfid, nBand);
1461 : }
1462 : #ifdef NCDF_DEBUG
1463 : else
1464 : {
1465 : CPLDebug("GDAL_netCDF",
1466 : "Setting NoDataValue to %.17g (id #%d, band #%d)",
1467 : dfNoData, cdfid, nBand);
1468 : }
1469 : #endif
1470 : // Make sure we are in define mode.
1471 115 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1472 :
1473 : int status;
1474 115 : if (eDataType == GDT_Byte)
1475 : {
1476 6 : if (bSignedData)
1477 : {
1478 0 : signed char cNoDataValue = static_cast<signed char>(dfNoData);
1479 0 : status = nc_put_att_schar(cdfid, nZId, NCDF_FillValue,
1480 : nc_datatype, 1, &cNoDataValue);
1481 : }
1482 : else
1483 : {
1484 6 : const unsigned char ucNoDataValue =
1485 6 : static_cast<unsigned char>(dfNoData);
1486 6 : status = nc_put_att_uchar(cdfid, nZId, NCDF_FillValue,
1487 : nc_datatype, 1, &ucNoDataValue);
1488 : }
1489 : }
1490 109 : else if (eDataType == GDT_Int16)
1491 : {
1492 14 : short nsNoDataValue = static_cast<short>(dfNoData);
1493 14 : status = nc_put_att_short(cdfid, nZId, NCDF_FillValue, nc_datatype,
1494 : 1, &nsNoDataValue);
1495 : }
1496 95 : else if (eDataType == GDT_Int32)
1497 : {
1498 27 : int nNoDataValue = static_cast<int>(dfNoData);
1499 27 : status = nc_put_att_int(cdfid, nZId, NCDF_FillValue, nc_datatype, 1,
1500 : &nNoDataValue);
1501 : }
1502 68 : else if (eDataType == GDT_Float32)
1503 : {
1504 31 : float fNoDataValue = static_cast<float>(dfNoData);
1505 31 : status = nc_put_att_float(cdfid, nZId, NCDF_FillValue, nc_datatype,
1506 : 1, &fNoDataValue);
1507 : }
1508 43 : else if (eDataType == GDT_UInt16 &&
1509 6 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat ==
1510 : NCDF_FORMAT_NC4)
1511 : {
1512 6 : unsigned short usNoDataValue =
1513 6 : static_cast<unsigned short>(dfNoData);
1514 6 : status = nc_put_att_ushort(cdfid, nZId, NCDF_FillValue, nc_datatype,
1515 : 1, &usNoDataValue);
1516 : }
1517 38 : else if (eDataType == GDT_UInt32 &&
1518 7 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat ==
1519 : NCDF_FORMAT_NC4)
1520 : {
1521 7 : unsigned int unNoDataValue = static_cast<unsigned int>(dfNoData);
1522 7 : status = nc_put_att_uint(cdfid, nZId, NCDF_FillValue, nc_datatype,
1523 : 1, &unNoDataValue);
1524 : }
1525 : else
1526 : {
1527 24 : status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
1528 : 1, &dfNoData);
1529 : }
1530 :
1531 115 : NCDF_ERR(status);
1532 :
1533 : // Update status if write worked.
1534 115 : if (status == NC_NOERR)
1535 : {
1536 115 : SetNoDataValueNoUpdate(dfNoData);
1537 115 : return CE_None;
1538 : }
1539 :
1540 0 : return CE_Failure;
1541 : }
1542 :
1543 0 : SetNoDataValueNoUpdate(dfNoData);
1544 0 : return CE_None;
1545 : }
1546 :
1547 : /************************************************************************/
1548 : /* SetNoDataValueNoUpdate() */
1549 : /************************************************************************/
1550 :
1551 438 : void netCDFRasterBand::SetNoDataValueNoUpdate(double dfNoData)
1552 : {
1553 438 : m_dfNoDataValue = dfNoData;
1554 438 : m_bNoDataSet = true;
1555 438 : m_bNoDataSetAsInt64 = false;
1556 438 : m_bNoDataSetAsUInt64 = false;
1557 438 : }
1558 :
1559 : /************************************************************************/
1560 : /* SetNoDataValueAsInt64() */
1561 : /************************************************************************/
1562 :
1563 3 : CPLErr netCDFRasterBand::SetNoDataValueAsInt64(int64_t nNoData)
1564 :
1565 : {
1566 6 : CPLMutexHolderD(&hNCMutex);
1567 :
1568 : // If already set to new value, don't do anything.
1569 3 : if (m_bNoDataSetAsInt64 && nNoData == m_nNodataValueInt64)
1570 0 : return CE_None;
1571 :
1572 : // Write value if in update mode.
1573 3 : if (poDS->GetAccess() == GA_Update)
1574 : {
1575 : // netcdf-4 does not allow to set NCDF_FillValue after leaving define mode,
1576 : // but it is ok if variable has not been written to, so only print
1577 : // debug. See bug #4484.
1578 3 : if (m_bNoDataSetAsInt64 &&
1579 0 : !cpl::down_cast<netCDFDataset *>(poDS)->GetDefineMode())
1580 : {
1581 0 : CPLDebug("GDAL_netCDF",
1582 : "Setting NoDataValue to " CPL_FRMT_GIB
1583 : " (previously set to " CPL_FRMT_GIB ") "
1584 : "but file is no longer in define mode (id #%d, band #%d)",
1585 : static_cast<GIntBig>(nNoData),
1586 0 : static_cast<GIntBig>(m_nNodataValueInt64), cdfid, nBand);
1587 : }
1588 : #ifdef NCDF_DEBUG
1589 : else
1590 : {
1591 : CPLDebug("GDAL_netCDF",
1592 : "Setting NoDataValue to " CPL_FRMT_GIB
1593 : " (id #%d, band #%d)",
1594 : static_cast<GIntBig>(nNoData), cdfid, nBand);
1595 : }
1596 : #endif
1597 : // Make sure we are in define mode.
1598 3 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1599 :
1600 : int status;
1601 6 : if (eDataType == GDT_Int64 &&
1602 3 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
1603 : {
1604 3 : long long tmp = static_cast<long long>(nNoData);
1605 3 : status = nc_put_att_longlong(cdfid, nZId, NCDF_FillValue,
1606 : nc_datatype, 1, &tmp);
1607 : }
1608 : else
1609 : {
1610 0 : double dfNoData = static_cast<double>(nNoData);
1611 0 : status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
1612 : 1, &dfNoData);
1613 : }
1614 :
1615 3 : NCDF_ERR(status);
1616 :
1617 : // Update status if write worked.
1618 3 : if (status == NC_NOERR)
1619 : {
1620 3 : SetNoDataValueNoUpdate(nNoData);
1621 3 : return CE_None;
1622 : }
1623 :
1624 0 : return CE_Failure;
1625 : }
1626 :
1627 0 : SetNoDataValueNoUpdate(nNoData);
1628 0 : return CE_None;
1629 : }
1630 :
1631 : /************************************************************************/
1632 : /* SetNoDataValueNoUpdate() */
1633 : /************************************************************************/
1634 :
1635 11 : void netCDFRasterBand::SetNoDataValueNoUpdate(int64_t nNoData)
1636 : {
1637 11 : m_nNodataValueInt64 = nNoData;
1638 11 : m_bNoDataSet = false;
1639 11 : m_bNoDataSetAsInt64 = true;
1640 11 : m_bNoDataSetAsUInt64 = false;
1641 11 : }
1642 :
1643 : /************************************************************************/
1644 : /* SetNoDataValueAsUInt64() */
1645 : /************************************************************************/
1646 :
1647 3 : CPLErr netCDFRasterBand::SetNoDataValueAsUInt64(uint64_t nNoData)
1648 :
1649 : {
1650 6 : CPLMutexHolderD(&hNCMutex);
1651 :
1652 : // If already set to new value, don't do anything.
1653 3 : if (m_bNoDataSetAsUInt64 && nNoData == m_nNodataValueUInt64)
1654 0 : return CE_None;
1655 :
1656 : // Write value if in update mode.
1657 3 : if (poDS->GetAccess() == GA_Update)
1658 : {
1659 : // netcdf-4 does not allow to set _FillValue after leaving define mode,
1660 : // but it is ok if variable has not been written to, so only print
1661 : // debug. See bug #4484.
1662 3 : if (m_bNoDataSetAsUInt64 &&
1663 0 : !cpl::down_cast<netCDFDataset *>(poDS)->GetDefineMode())
1664 : {
1665 0 : CPLDebug("GDAL_netCDF",
1666 : "Setting NoDataValue to " CPL_FRMT_GUIB
1667 : " (previously set to " CPL_FRMT_GUIB ") "
1668 : "but file is no longer in define mode (id #%d, band #%d)",
1669 : static_cast<GUIntBig>(nNoData),
1670 0 : static_cast<GUIntBig>(m_nNodataValueUInt64), cdfid, nBand);
1671 : }
1672 : #ifdef NCDF_DEBUG
1673 : else
1674 : {
1675 : CPLDebug("GDAL_netCDF",
1676 : "Setting NoDataValue to " CPL_FRMT_GUIB
1677 : " (id #%d, band #%d)",
1678 : static_cast<GUIntBig>(nNoData), cdfid, nBand);
1679 : }
1680 : #endif
1681 : // Make sure we are in define mode.
1682 3 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1683 :
1684 : int status;
1685 6 : if (eDataType == GDT_UInt64 &&
1686 3 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
1687 : {
1688 3 : unsigned long long tmp = static_cast<long long>(nNoData);
1689 3 : status = nc_put_att_ulonglong(cdfid, nZId, NCDF_FillValue,
1690 : nc_datatype, 1, &tmp);
1691 : }
1692 : else
1693 : {
1694 0 : double dfNoData = static_cast<double>(nNoData);
1695 0 : status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
1696 : 1, &dfNoData);
1697 : }
1698 :
1699 3 : NCDF_ERR(status);
1700 :
1701 : // Update status if write worked.
1702 3 : if (status == NC_NOERR)
1703 : {
1704 3 : SetNoDataValueNoUpdate(nNoData);
1705 3 : return CE_None;
1706 : }
1707 :
1708 0 : return CE_Failure;
1709 : }
1710 :
1711 0 : SetNoDataValueNoUpdate(nNoData);
1712 0 : return CE_None;
1713 : }
1714 :
1715 : /************************************************************************/
1716 : /* SetNoDataValueNoUpdate() */
1717 : /************************************************************************/
1718 :
1719 10 : void netCDFRasterBand::SetNoDataValueNoUpdate(uint64_t nNoData)
1720 : {
1721 10 : m_nNodataValueUInt64 = nNoData;
1722 10 : m_bNoDataSet = false;
1723 10 : m_bNoDataSetAsInt64 = false;
1724 10 : m_bNoDataSetAsUInt64 = true;
1725 10 : }
1726 :
1727 : /************************************************************************/
1728 : /* DeleteNoDataValue() */
1729 : /************************************************************************/
1730 :
1731 : #ifdef notdef
1732 : CPLErr netCDFRasterBand::DeleteNoDataValue()
1733 :
1734 : {
1735 : CPLMutexHolderD(&hNCMutex);
1736 :
1737 : if (!bNoDataSet)
1738 : return CE_None;
1739 :
1740 : // Write value if in update mode.
1741 : if (poDS->GetAccess() == GA_Update)
1742 : {
1743 : // Make sure we are in define mode.
1744 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1745 :
1746 : status = nc_del_att(cdfid, nZId, NCDF_FillValue);
1747 :
1748 : NCDF_ERR(status);
1749 :
1750 : // Update status if write worked.
1751 : if (status == NC_NOERR)
1752 : {
1753 : dfNoDataValue = 0.0;
1754 : bNoDataSet = false;
1755 : return CE_None;
1756 : }
1757 :
1758 : return CE_Failure;
1759 : }
1760 :
1761 : dfNoDataValue = 0.0;
1762 : bNoDataSet = false;
1763 : return CE_None;
1764 : }
1765 : #endif
1766 :
1767 : /************************************************************************/
1768 : /* SerializeToXML() */
1769 : /************************************************************************/
1770 :
1771 5 : CPLXMLNode *netCDFRasterBand::SerializeToXML(const char * /* pszUnused */)
1772 : {
1773 : // Overridden from GDALPamDataset to add only band histogram
1774 : // and statistics. See bug #4244.
1775 5 : if (psPam == nullptr)
1776 0 : return nullptr;
1777 :
1778 : // Setup root node and attributes.
1779 : CPLXMLNode *psTree =
1780 5 : CPLCreateXMLNode(nullptr, CXT_Element, "PAMRasterBand");
1781 :
1782 5 : if (GetBand() > 0)
1783 : {
1784 10 : CPLString oFmt;
1785 5 : CPLSetXMLValue(psTree, "#band", oFmt.Printf("%d", GetBand()));
1786 : }
1787 :
1788 : // Histograms.
1789 5 : if (psPam->psSavedHistograms != nullptr)
1790 1 : CPLAddXMLChild(psTree, CPLCloneXMLTree(psPam->psSavedHistograms));
1791 :
1792 : // Metadata (statistics only).
1793 5 : GDALMultiDomainMetadata oMDMDStats;
1794 5 : const char *papszMDStats[] = {"STATISTICS_MINIMUM", "STATISTICS_MAXIMUM",
1795 : "STATISTICS_MEAN", "STATISTICS_STDDEV",
1796 : nullptr};
1797 25 : for (int i = 0; i < CSLCount(papszMDStats); i++)
1798 : {
1799 20 : const char *pszMDI = GetMetadataItem(papszMDStats[i]);
1800 20 : if (pszMDI)
1801 4 : oMDMDStats.SetMetadataItem(papszMDStats[i], pszMDI);
1802 : }
1803 5 : CPLXMLNode *psMD = oMDMDStats.Serialize();
1804 :
1805 5 : if (psMD != nullptr)
1806 : {
1807 1 : if (psMD->psChild == nullptr)
1808 0 : CPLDestroyXMLNode(psMD);
1809 : else
1810 1 : CPLAddXMLChild(psTree, psMD);
1811 : }
1812 :
1813 : // We don't want to return anything if we had no metadata to attach.
1814 5 : if (psTree->psChild == nullptr || psTree->psChild->psNext == nullptr)
1815 : {
1816 3 : CPLDestroyXMLNode(psTree);
1817 3 : psTree = nullptr;
1818 : }
1819 :
1820 5 : return psTree;
1821 : }
1822 :
1823 : /************************************************************************/
1824 : /* Get1DVariableIndexedByDimension() */
1825 : /************************************************************************/
1826 :
1827 81 : static int Get1DVariableIndexedByDimension(int cdfid, int nDimId,
1828 : const char *pszDimName,
1829 : bool bVerboseError, int *pnGroupID)
1830 : {
1831 81 : *pnGroupID = -1;
1832 81 : int nVarID = -1;
1833 : // First try to find a variable whose name is identical to the dimension
1834 : // name, and check that it is indeed indexed by this dimension
1835 81 : if (NCDFResolveVar(cdfid, pszDimName, pnGroupID, &nVarID) == CE_None)
1836 : {
1837 67 : int nDimCountOfVariable = 0;
1838 67 : nc_inq_varndims(*pnGroupID, nVarID, &nDimCountOfVariable);
1839 67 : if (nDimCountOfVariable == 1)
1840 : {
1841 67 : int nDimIdOfVariable = -1;
1842 67 : nc_inq_vardimid(*pnGroupID, nVarID, &nDimIdOfVariable);
1843 67 : if (nDimIdOfVariable == nDimId)
1844 : {
1845 67 : return nVarID;
1846 : }
1847 : }
1848 : }
1849 :
1850 : // Otherwise iterate over the variables to find potential candidates
1851 : // TODO: should be modified to search also in other groups using the same
1852 : // logic than in NCDFResolveVar(), but maybe not needed if it's a
1853 : // very rare case? and I think this is not CF compliant.
1854 14 : int nvars = 0;
1855 14 : CPL_IGNORE_RET_VAL(nc_inq(cdfid, nullptr, &nvars, nullptr, nullptr));
1856 :
1857 14 : int nCountCandidateVars = 0;
1858 14 : int nCandidateVarID = -1;
1859 65 : for (int k = 0; k < nvars; k++)
1860 : {
1861 51 : int nDimCountOfVariable = 0;
1862 51 : nc_inq_varndims(cdfid, k, &nDimCountOfVariable);
1863 51 : if (nDimCountOfVariable == 1)
1864 : {
1865 27 : int nDimIdOfVariable = -1;
1866 27 : nc_inq_vardimid(cdfid, k, &nDimIdOfVariable);
1867 27 : if (nDimIdOfVariable == nDimId)
1868 : {
1869 7 : nCountCandidateVars++;
1870 7 : nCandidateVarID = k;
1871 : }
1872 : }
1873 : }
1874 14 : if (nCountCandidateVars > 1)
1875 : {
1876 1 : if (bVerboseError)
1877 : {
1878 1 : CPLError(CE_Warning, CPLE_AppDefined,
1879 : "Several 1D variables are indexed by dimension %s",
1880 : pszDimName);
1881 : }
1882 1 : *pnGroupID = -1;
1883 1 : return -1;
1884 : }
1885 13 : else if (nCandidateVarID < 0)
1886 : {
1887 8 : if (bVerboseError)
1888 : {
1889 8 : CPLError(CE_Warning, CPLE_AppDefined,
1890 : "No 1D variable is indexed by dimension %s", pszDimName);
1891 : }
1892 : }
1893 13 : *pnGroupID = cdfid;
1894 13 : return nCandidateVarID;
1895 : }
1896 :
1897 : /************************************************************************/
1898 : /* CreateMetadataFromAttributes() */
1899 : /************************************************************************/
1900 :
1901 480 : void netCDFRasterBand::CreateMetadataFromAttributes()
1902 : {
1903 480 : char szVarName[NC_MAX_NAME + 1] = {};
1904 480 : int status = nc_inq_varname(cdfid, nZId, szVarName);
1905 480 : NCDF_ERR(status);
1906 :
1907 480 : GDALPamRasterBand::SetMetadataItem("NETCDF_VARNAME", szVarName);
1908 :
1909 : // Get attribute metadata.
1910 480 : int nAtt = 0;
1911 480 : NCDF_ERR(nc_inq_varnatts(cdfid, nZId, &nAtt));
1912 :
1913 2034 : for (int i = 0; i < nAtt; i++)
1914 : {
1915 1554 : char szMetaName[NC_MAX_NAME + 1] = {};
1916 1554 : status = nc_inq_attname(cdfid, nZId, i, szMetaName);
1917 1554 : if (status != NC_NOERR)
1918 12 : continue;
1919 :
1920 1554 : if (GDALPamRasterBand::GetMetadataItem(szMetaName) != nullptr)
1921 : {
1922 12 : continue;
1923 : }
1924 :
1925 1542 : char *pszMetaValue = nullptr;
1926 1542 : if (NCDFGetAttr(cdfid, nZId, szMetaName, &pszMetaValue) == CE_None)
1927 : {
1928 1542 : GDALPamRasterBand::SetMetadataItem(szMetaName, pszMetaValue);
1929 : }
1930 : else
1931 : {
1932 0 : CPLDebug("GDAL_netCDF", "invalid Band metadata %s", szMetaName);
1933 : }
1934 :
1935 1542 : if (pszMetaValue)
1936 : {
1937 1542 : CPLFree(pszMetaValue);
1938 1542 : pszMetaValue = nullptr;
1939 : }
1940 : }
1941 480 : }
1942 :
1943 : /************************************************************************/
1944 : /* CreateMetadataFromOtherVars() */
1945 : /************************************************************************/
1946 :
1947 49 : void netCDFRasterBand::CreateMetadataFromOtherVars()
1948 :
1949 : {
1950 49 : CPLAssert(!m_bCreateMetadataFromOtherVarsDone);
1951 49 : m_bCreateMetadataFromOtherVarsDone = true;
1952 :
1953 49 : netCDFDataset *l_poDS = cpl::down_cast<netCDFDataset *>(poDS);
1954 49 : const int nPamFlagsBackup = l_poDS->nPamFlags;
1955 :
1956 : // Compute all dimensions from Band number and save in Metadata.
1957 49 : int nd = 0;
1958 49 : nc_inq_varndims(cdfid, nZId, &nd);
1959 : // Compute multidimention band position.
1960 : //
1961 : // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
1962 : // if Data[2,3,4,x,y]
1963 : //
1964 : // BandPos0 = (nBand) / (3*4)
1965 : // BandPos1 = (nBand - BandPos0*(3*4)) / (4)
1966 : // BandPos2 = (nBand - BandPos0*(3*4)) % (4)
1967 :
1968 49 : int Sum = 1;
1969 49 : if (nd == 3)
1970 : {
1971 5 : Sum *= panBandZLev[0];
1972 : }
1973 :
1974 : // Loop over non-spatial dimensions.
1975 49 : int Taken = 0;
1976 :
1977 89 : for (int i = 0; i < nd - 2; i++)
1978 : {
1979 : int result;
1980 40 : if (i != nd - 2 - 1)
1981 : {
1982 18 : Sum = 1;
1983 37 : for (int j = i + 1; j < nd - 2; j++)
1984 : {
1985 19 : Sum *= panBandZLev[j];
1986 : }
1987 18 : result = static_cast<int>((nLevel - Taken) / Sum);
1988 : }
1989 : else
1990 : {
1991 22 : result = static_cast<int>((nLevel - Taken) % Sum);
1992 : }
1993 :
1994 40 : char szName[NC_MAX_NAME + 1] = {};
1995 40 : snprintf(szName, sizeof(szName), "%s",
1996 40 : l_poDS->papszDimName[l_poDS->m_anDimIds[panBandZPos[i]]]);
1997 :
1998 : char szMetaName[NC_MAX_NAME + 1 + 32];
1999 40 : snprintf(szMetaName, sizeof(szMetaName), "NETCDF_DIM_%s", szName);
2000 :
2001 40 : const int nGroupID = l_poDS->m_anExtraDimGroupIds[i];
2002 40 : const int nVarID = l_poDS->m_anExtraDimVarIds[i];
2003 40 : if (nVarID < 0)
2004 : {
2005 2 : GDALPamRasterBand::SetMetadataItem(szMetaName,
2006 : CPLSPrintf("%d", result + 1));
2007 : }
2008 : else
2009 : {
2010 : // TODO: Make sure all the status checks make sense.
2011 :
2012 38 : nc_type nVarType = NC_NAT;
2013 38 : /* status = */ nc_inq_vartype(nGroupID, nVarID, &nVarType);
2014 :
2015 38 : int nDims = 0;
2016 38 : /* status = */ nc_inq_varndims(nGroupID, nVarID, &nDims);
2017 :
2018 38 : char szMetaTemp[256] = {};
2019 38 : if (nDims == 1)
2020 : {
2021 38 : size_t count[1] = {1};
2022 38 : size_t start[1] = {static_cast<size_t>(result)};
2023 :
2024 38 : switch (nVarType)
2025 : {
2026 0 : case NC_BYTE:
2027 : // TODO: Check for signed/unsigned byte.
2028 : signed char cData;
2029 0 : /* status = */ nc_get_vara_schar(nGroupID, nVarID,
2030 : start, count, &cData);
2031 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", cData);
2032 0 : break;
2033 0 : case NC_SHORT:
2034 : short sData;
2035 0 : /* status = */ nc_get_vara_short(nGroupID, nVarID,
2036 : start, count, &sData);
2037 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", sData);
2038 0 : break;
2039 19 : case NC_INT:
2040 : {
2041 : int nData;
2042 19 : /* status = */ nc_get_vara_int(nGroupID, nVarID, start,
2043 : count, &nData);
2044 19 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", nData);
2045 19 : break;
2046 : }
2047 0 : case NC_FLOAT:
2048 : float fData;
2049 0 : /* status = */ nc_get_vara_float(nGroupID, nVarID,
2050 : start, count, &fData);
2051 0 : CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.8g",
2052 : fData);
2053 0 : break;
2054 18 : case NC_DOUBLE:
2055 : double dfData;
2056 18 : /* status = */ nc_get_vara_double(
2057 : nGroupID, nVarID, start, count, &dfData);
2058 18 : CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.16g",
2059 : dfData);
2060 18 : break;
2061 0 : case NC_UBYTE:
2062 : unsigned char ucData;
2063 0 : /* status = */ nc_get_vara_uchar(nGroupID, nVarID,
2064 : start, count, &ucData);
2065 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", ucData);
2066 0 : break;
2067 0 : case NC_USHORT:
2068 : unsigned short usData;
2069 0 : /* status = */ nc_get_vara_ushort(
2070 : nGroupID, nVarID, start, count, &usData);
2071 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", usData);
2072 0 : break;
2073 0 : case NC_UINT:
2074 : {
2075 : unsigned int unData;
2076 0 : /* status = */ nc_get_vara_uint(nGroupID, nVarID, start,
2077 : count, &unData);
2078 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", unData);
2079 0 : break;
2080 : }
2081 1 : case NC_INT64:
2082 : {
2083 : long long nData;
2084 1 : /* status = */ nc_get_vara_longlong(
2085 : nGroupID, nVarID, start, count, &nData);
2086 1 : snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GIB,
2087 : nData);
2088 1 : break;
2089 : }
2090 0 : case NC_UINT64:
2091 : {
2092 : unsigned long long unData;
2093 0 : /* status = */ nc_get_vara_ulonglong(
2094 : nGroupID, nVarID, start, count, &unData);
2095 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GUIB,
2096 : unData);
2097 0 : break;
2098 : }
2099 0 : default:
2100 0 : CPLDebug("GDAL_netCDF", "invalid dim %s, type=%d",
2101 : szMetaTemp, nVarType);
2102 0 : break;
2103 : }
2104 : }
2105 : else
2106 : {
2107 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", result + 1);
2108 : }
2109 :
2110 : // Save dimension value.
2111 : // NOTE: removed #original_units as not part of CF-1.
2112 :
2113 38 : GDALPamRasterBand::SetMetadataItem(szMetaName, szMetaTemp);
2114 : }
2115 :
2116 : // Avoid int32 overflow. Perhaps something more sensible to do here ?
2117 40 : if (result > 0 && Sum > INT_MAX / result)
2118 0 : break;
2119 40 : if (Taken > INT_MAX - result * Sum)
2120 0 : break;
2121 :
2122 40 : Taken += result * Sum;
2123 : } // End loop non-spatial dimensions.
2124 :
2125 49 : l_poDS->nPamFlags = nPamFlagsBackup;
2126 49 : }
2127 :
2128 : /************************************************************************/
2129 : /* CheckData() */
2130 : /************************************************************************/
2131 : template <class T>
2132 5732 : void netCDFRasterBand::CheckData(void *pImage, void *pImageNC,
2133 : size_t nTmpBlockXSize, size_t nTmpBlockYSize,
2134 : bool bCheckIsNan)
2135 : {
2136 5732 : CPLAssert(pImage != nullptr && pImageNC != nullptr);
2137 :
2138 : // If this block is not a full block (in the x axis), we need to re-arrange
2139 : // the data this is because partial blocks are not arranged the same way in
2140 : // netcdf and gdal.
2141 5732 : if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
2142 : {
2143 6 : T *ptrWrite = static_cast<T *>(pImage);
2144 6 : T *ptrRead = static_cast<T *>(pImageNC);
2145 29 : for (size_t j = 0; j < nTmpBlockYSize;
2146 23 : j++, ptrWrite += nBlockXSize, ptrRead += nTmpBlockXSize)
2147 : {
2148 23 : memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T));
2149 : }
2150 : }
2151 :
2152 : // Is valid data checking needed or requested?
2153 5732 : if (bValidRangeValid || bCheckIsNan)
2154 : {
2155 1265 : T *ptrImage = static_cast<T *>(pImage);
2156 2584 : for (size_t j = 0; j < nTmpBlockYSize; j++)
2157 : {
2158 : // k moves along the gdal block, skipping the out-of-range pixels.
2159 1319 : size_t k = j * nBlockXSize;
2160 96938 : for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
2161 : {
2162 : // Check for nodata and nan.
2163 95619 : if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
2164 6301 : continue;
2165 89318 : if (bCheckIsNan && std::isnan((double)ptrImage[k]))
2166 : {
2167 5737 : ptrImage[k] = (T)m_dfNoDataValue;
2168 5737 : continue;
2169 : }
2170 : // Check for valid_range.
2171 83581 : if (bValidRangeValid)
2172 : {
2173 40986 : if (((adfValidRange[0] != m_dfNoDataValue) &&
2174 40986 : (ptrImage[k] < (T)adfValidRange[0])) ||
2175 40983 : ((adfValidRange[1] != m_dfNoDataValue) &&
2176 40983 : (ptrImage[k] > (T)adfValidRange[1])))
2177 : {
2178 4 : ptrImage[k] = (T)m_dfNoDataValue;
2179 : }
2180 : }
2181 : }
2182 : }
2183 : }
2184 :
2185 : // If minimum longitude is > 180, subtract 360 from all.
2186 : // If not, disable checking for further calls (check just once).
2187 : // Only check first and last block elements since lon must be monotonic.
2188 5732 : const bool bIsSigned = std::numeric_limits<T>::is_signed;
2189 5419 : if (bCheckLongitude && bIsSigned &&
2190 9 : !CPLIsEqual((double)((T *)pImage)[0], m_dfNoDataValue) &&
2191 8 : !CPLIsEqual((double)((T *)pImage)[nTmpBlockXSize - 1],
2192 2714 : m_dfNoDataValue) &&
2193 8 : std::min(((T *)pImage)[0], ((T *)pImage)[nTmpBlockXSize - 1]) > 180.0)
2194 : {
2195 0 : T *ptrImage = static_cast<T *>(pImage);
2196 0 : for (size_t j = 0; j < nTmpBlockYSize; j++)
2197 : {
2198 0 : size_t k = j * nBlockXSize;
2199 0 : for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
2200 : {
2201 0 : if (!CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
2202 0 : ptrImage[k] = static_cast<T>(ptrImage[k] - 360);
2203 : }
2204 : }
2205 : }
2206 : else
2207 : {
2208 5732 : bCheckLongitude = false;
2209 : }
2210 5732 : }
2211 :
2212 : /************************************************************************/
2213 : /* CheckDataCpx() */
2214 : /************************************************************************/
2215 : template <class T>
2216 25 : void netCDFRasterBand::CheckDataCpx(void *pImage, void *pImageNC,
2217 : size_t nTmpBlockXSize,
2218 : size_t nTmpBlockYSize, bool bCheckIsNan)
2219 : {
2220 25 : CPLAssert(pImage != nullptr && pImageNC != nullptr);
2221 :
2222 : // If this block is not a full block (in the x axis), we need to re-arrange
2223 : // the data this is because partial blocks are not arranged the same way in
2224 : // netcdf and gdal.
2225 25 : if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
2226 : {
2227 0 : T *ptrWrite = static_cast<T *>(pImage);
2228 0 : T *ptrRead = static_cast<T *>(pImageNC);
2229 0 : for (size_t j = 0; j < nTmpBlockYSize; j++,
2230 0 : ptrWrite += (2 * nBlockXSize),
2231 0 : ptrRead += (2 * nTmpBlockXSize))
2232 : {
2233 0 : memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T) * 2);
2234 : }
2235 : }
2236 :
2237 : // Is valid data checking needed or requested?
2238 25 : if (bValidRangeValid || bCheckIsNan)
2239 : {
2240 0 : T *ptrImage = static_cast<T *>(pImage);
2241 0 : for (size_t j = 0; j < nTmpBlockYSize; j++)
2242 : {
2243 : // k moves along the gdal block, skipping the out-of-range pixels.
2244 0 : size_t k = 2 * j * nBlockXSize;
2245 0 : for (size_t i = 0; i < (2 * nTmpBlockXSize); i++, k++)
2246 : {
2247 : // Check for nodata and nan.
2248 0 : if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
2249 0 : continue;
2250 0 : if (bCheckIsNan && std::isnan((double)ptrImage[k]))
2251 : {
2252 0 : ptrImage[k] = (T)m_dfNoDataValue;
2253 0 : continue;
2254 : }
2255 : // Check for valid_range.
2256 0 : if (bValidRangeValid)
2257 : {
2258 0 : if (((adfValidRange[0] != m_dfNoDataValue) &&
2259 0 : (ptrImage[k] < (T)adfValidRange[0])) ||
2260 0 : ((adfValidRange[1] != m_dfNoDataValue) &&
2261 0 : (ptrImage[k] > (T)adfValidRange[1])))
2262 : {
2263 0 : ptrImage[k] = (T)m_dfNoDataValue;
2264 : }
2265 : }
2266 : }
2267 : }
2268 : }
2269 25 : }
2270 :
2271 : /************************************************************************/
2272 : /* FetchNetcdfChunk() */
2273 : /************************************************************************/
2274 :
2275 5757 : bool netCDFRasterBand::FetchNetcdfChunk(size_t xstart, size_t ystart,
2276 : void *pImage)
2277 : {
2278 5757 : size_t start[MAX_NC_DIMS] = {};
2279 5757 : size_t edge[MAX_NC_DIMS] = {};
2280 :
2281 5757 : start[nBandXPos] = xstart;
2282 5757 : edge[nBandXPos] = nBlockXSize;
2283 5757 : if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
2284 6 : edge[nBandXPos] = nRasterXSize - start[nBandXPos];
2285 5757 : if (nBandYPos >= 0)
2286 : {
2287 5753 : start[nBandYPos] = ystart;
2288 5753 : edge[nBandYPos] = nBlockYSize;
2289 5753 : if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
2290 4 : edge[nBandYPos] = nRasterYSize - start[nBandYPos];
2291 : }
2292 5757 : const size_t nYChunkSize = nBandYPos < 0 ? 1 : edge[nBandYPos];
2293 :
2294 : #ifdef NCDF_DEBUG
2295 : CPLDebug("GDAL_netCDF", "start={%ld,%ld} edge={%ld,%ld} bBottomUp=%d",
2296 : start[nBandXPos], nBandYPos < 0 ? 0 : start[nBandYPos],
2297 : edge[nBandXPos], nYChunkSize, ((netCDFDataset *)poDS)->bBottomUp);
2298 : #endif
2299 :
2300 5757 : int nd = 0;
2301 5757 : nc_inq_varndims(cdfid, nZId, &nd);
2302 5757 : if (nd == 3)
2303 : {
2304 1078 : start[panBandZPos[0]] = nLevel; // z
2305 1078 : edge[panBandZPos[0]] = 1;
2306 : }
2307 :
2308 : // Compute multidimention band position.
2309 : //
2310 : // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
2311 : // if Data[2,3,4,x,y]
2312 : //
2313 : // BandPos0 = (nBand) / (3*4)
2314 : // BandPos1 = (nBand - (3*4)) / (4)
2315 : // BandPos2 = (nBand - (3*4)) % (4)
2316 5757 : if (nd > 3)
2317 : {
2318 160 : int Sum = -1;
2319 160 : int Taken = 0;
2320 480 : for (int i = 0; i < nd - 2; i++)
2321 : {
2322 320 : if (i != nd - 2 - 1)
2323 : {
2324 160 : Sum = 1;
2325 320 : for (int j = i + 1; j < nd - 2; j++)
2326 : {
2327 160 : Sum *= panBandZLev[j];
2328 : }
2329 160 : start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
2330 160 : edge[panBandZPos[i]] = 1;
2331 : }
2332 : else
2333 : {
2334 160 : start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
2335 160 : edge[panBandZPos[i]] = 1;
2336 : }
2337 320 : Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
2338 : }
2339 : }
2340 :
2341 : // Make sure we are in data mode.
2342 5757 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
2343 :
2344 : // If this block is not a full block in the x axis, we need to
2345 : // re-arrange the data because partial blocks are not arranged the
2346 : // same way in netcdf and gdal, so we first we read the netcdf data at
2347 : // the end of the gdal block buffer then re-arrange rows in CheckData().
2348 5757 : void *pImageNC = pImage;
2349 5757 : if (edge[nBandXPos] != static_cast<size_t>(nBlockXSize))
2350 : {
2351 6 : pImageNC = static_cast<GByte *>(pImage) +
2352 6 : ((static_cast<size_t>(nBlockXSize) * nBlockYSize -
2353 12 : edge[nBandXPos] * nYChunkSize) *
2354 6 : GDALGetDataTypeSizeBytes(eDataType));
2355 : }
2356 :
2357 : // Read data according to type.
2358 : int status;
2359 5757 : if (eDataType == GDT_Byte)
2360 : {
2361 3005 : if (bSignedData)
2362 : {
2363 0 : status = nc_get_vara_schar(cdfid, nZId, start, edge,
2364 : static_cast<signed char *>(pImageNC));
2365 0 : if (status == NC_NOERR)
2366 0 : CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
2367 : nYChunkSize, false);
2368 : }
2369 : else
2370 : {
2371 3005 : status = nc_get_vara_uchar(cdfid, nZId, start, edge,
2372 : static_cast<unsigned char *>(pImageNC));
2373 3005 : if (status == NC_NOERR)
2374 3005 : CheckData<unsigned char>(pImage, pImageNC, edge[nBandXPos],
2375 : nYChunkSize, false);
2376 : }
2377 : }
2378 2752 : else if (eDataType == GDT_Int8)
2379 : {
2380 60 : status = nc_get_vara_schar(cdfid, nZId, start, edge,
2381 : static_cast<signed char *>(pImageNC));
2382 60 : if (status == NC_NOERR)
2383 60 : CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
2384 : nYChunkSize, false);
2385 : }
2386 2692 : else if (nc_datatype == NC_SHORT)
2387 : {
2388 465 : status = nc_get_vara_short(cdfid, nZId, start, edge,
2389 : static_cast<short *>(pImageNC));
2390 465 : if (status == NC_NOERR)
2391 : {
2392 465 : if (eDataType == GDT_Int16)
2393 : {
2394 462 : CheckData<GInt16>(pImage, pImageNC, edge[nBandXPos],
2395 : nYChunkSize, false);
2396 : }
2397 : else
2398 : {
2399 3 : CheckData<GUInt16>(pImage, pImageNC, edge[nBandXPos],
2400 : nYChunkSize, false);
2401 : }
2402 : }
2403 : }
2404 2227 : else if (eDataType == GDT_Int32)
2405 : {
2406 : #if SIZEOF_UNSIGNED_LONG == 4
2407 : status = nc_get_vara_long(cdfid, nZId, start, edge,
2408 : static_cast<long *>(pImageNC));
2409 : if (status == NC_NOERR)
2410 : CheckData<long>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2411 : false);
2412 : #else
2413 912 : status = nc_get_vara_int(cdfid, nZId, start, edge,
2414 : static_cast<int *>(pImageNC));
2415 912 : if (status == NC_NOERR)
2416 912 : CheckData<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2417 : false);
2418 : #endif
2419 : }
2420 1315 : else if (eDataType == GDT_Float32)
2421 : {
2422 1178 : status = nc_get_vara_float(cdfid, nZId, start, edge,
2423 : static_cast<float *>(pImageNC));
2424 1178 : if (status == NC_NOERR)
2425 1178 : CheckData<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2426 : true);
2427 : }
2428 137 : else if (eDataType == GDT_Float64)
2429 : {
2430 86 : status = nc_get_vara_double(cdfid, nZId, start, edge,
2431 : static_cast<double *>(pImageNC));
2432 86 : if (status == NC_NOERR)
2433 86 : CheckData<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2434 : true);
2435 : }
2436 51 : else if (eDataType == GDT_UInt16)
2437 : {
2438 6 : status = nc_get_vara_ushort(cdfid, nZId, start, edge,
2439 : static_cast<unsigned short *>(pImageNC));
2440 6 : if (status == NC_NOERR)
2441 6 : CheckData<unsigned short>(pImage, pImageNC, edge[nBandXPos],
2442 : nYChunkSize, false);
2443 : }
2444 45 : else if (eDataType == GDT_UInt32)
2445 : {
2446 6 : status = nc_get_vara_uint(cdfid, nZId, start, edge,
2447 : static_cast<unsigned int *>(pImageNC));
2448 6 : if (status == NC_NOERR)
2449 6 : CheckData<unsigned int>(pImage, pImageNC, edge[nBandXPos],
2450 : nYChunkSize, false);
2451 : }
2452 39 : else if (eDataType == GDT_Int64)
2453 : {
2454 7 : status = nc_get_vara_longlong(cdfid, nZId, start, edge,
2455 : static_cast<long long *>(pImageNC));
2456 7 : if (status == NC_NOERR)
2457 7 : CheckData<std::int64_t>(pImage, pImageNC, edge[nBandXPos],
2458 : nYChunkSize, false);
2459 : }
2460 32 : else if (eDataType == GDT_UInt64)
2461 : {
2462 : status =
2463 7 : nc_get_vara_ulonglong(cdfid, nZId, start, edge,
2464 : static_cast<unsigned long long *>(pImageNC));
2465 7 : if (status == NC_NOERR)
2466 7 : CheckData<std::uint64_t>(pImage, pImageNC, edge[nBandXPos],
2467 : nYChunkSize, false);
2468 : }
2469 25 : else if (eDataType == GDT_CInt16)
2470 : {
2471 0 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2472 0 : if (status == NC_NOERR)
2473 0 : CheckDataCpx<short>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2474 : false);
2475 : }
2476 25 : else if (eDataType == GDT_CInt32)
2477 : {
2478 0 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2479 0 : if (status == NC_NOERR)
2480 0 : CheckDataCpx<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2481 : false);
2482 : }
2483 25 : else if (eDataType == GDT_CFloat32)
2484 : {
2485 20 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2486 20 : if (status == NC_NOERR)
2487 20 : CheckDataCpx<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2488 : false);
2489 : }
2490 5 : else if (eDataType == GDT_CFloat64)
2491 : {
2492 5 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2493 5 : if (status == NC_NOERR)
2494 5 : CheckDataCpx<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2495 : false);
2496 : }
2497 :
2498 : else
2499 0 : status = NC_EBADTYPE;
2500 :
2501 5757 : if (status != NC_NOERR)
2502 : {
2503 0 : CPLError(CE_Failure, CPLE_AppDefined,
2504 : "netCDF chunk fetch failed: #%d (%s)", status,
2505 : nc_strerror(status));
2506 0 : return false;
2507 : }
2508 5757 : return true;
2509 : }
2510 :
2511 : /************************************************************************/
2512 : /* IReadBlock() */
2513 : /************************************************************************/
2514 :
2515 5757 : CPLErr netCDFRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
2516 : void *pImage)
2517 :
2518 : {
2519 11514 : CPLMutexHolderD(&hNCMutex);
2520 :
2521 : // Locate X, Y and Z position in the array.
2522 :
2523 5757 : size_t xstart = static_cast<size_t>(nBlockXOff) * nBlockXSize;
2524 5757 : size_t ystart = 0;
2525 :
2526 : // Check y order.
2527 5757 : if (nBandYPos >= 0)
2528 : {
2529 5753 : auto poGDS = static_cast<netCDFDataset *>(poDS);
2530 5753 : if (poGDS->bBottomUp)
2531 : {
2532 4838 : if (nBlockYSize == 1)
2533 : {
2534 4825 : ystart = nRasterYSize - 1 - nBlockYOff;
2535 : }
2536 : else
2537 : {
2538 : // in GDAL space
2539 13 : ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
2540 : const size_t yend =
2541 26 : std::min(ystart + nBlockYSize - 1,
2542 13 : static_cast<size_t>(nRasterYSize - 1));
2543 : // in netCDF space
2544 13 : const size_t nFirstChunkLine = nRasterYSize - 1 - yend;
2545 13 : const size_t nLastChunkLine = nRasterYSize - 1 - ystart;
2546 13 : const size_t nFirstChunkBlock = nFirstChunkLine / nBlockYSize;
2547 13 : const size_t nLastChunkBlock = nLastChunkLine / nBlockYSize;
2548 :
2549 : const auto firstKey = netCDFDataset::ChunkKey(
2550 13 : nBlockXOff, nFirstChunkBlock, nBand);
2551 : const auto secondKey =
2552 13 : netCDFDataset::ChunkKey(nBlockXOff, nLastChunkBlock, nBand);
2553 :
2554 : // Retrieve data from the one or 2 needed netCDF chunks
2555 13 : std::shared_ptr<std::vector<GByte>> firstChunk;
2556 13 : std::shared_ptr<std::vector<GByte>> secondChunk;
2557 13 : if (poGDS->poChunkCache)
2558 : {
2559 13 : poGDS->poChunkCache->tryGet(firstKey, firstChunk);
2560 13 : if (firstKey != secondKey)
2561 6 : poGDS->poChunkCache->tryGet(secondKey, secondChunk);
2562 : }
2563 : const size_t nChunkLineSize =
2564 13 : static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
2565 13 : nBlockXSize;
2566 13 : const size_t nChunkSize = nChunkLineSize * nBlockYSize;
2567 13 : if (!firstChunk)
2568 : {
2569 11 : firstChunk.reset(new std::vector<GByte>(nChunkSize));
2570 11 : if (!FetchNetcdfChunk(xstart,
2571 11 : nFirstChunkBlock * nBlockYSize,
2572 11 : firstChunk.get()->data()))
2573 0 : return CE_Failure;
2574 11 : if (poGDS->poChunkCache)
2575 11 : poGDS->poChunkCache->insert(firstKey, firstChunk);
2576 : }
2577 13 : if (!secondChunk && firstKey != secondKey)
2578 : {
2579 2 : secondChunk.reset(new std::vector<GByte>(nChunkSize));
2580 2 : if (!FetchNetcdfChunk(xstart, nLastChunkBlock * nBlockYSize,
2581 2 : secondChunk.get()->data()))
2582 0 : return CE_Failure;
2583 2 : if (poGDS->poChunkCache)
2584 2 : poGDS->poChunkCache->insert(secondKey, secondChunk);
2585 : }
2586 :
2587 : // Assemble netCDF chunks into GDAL block
2588 13 : GByte *pabyImage = static_cast<GByte *>(pImage);
2589 13 : const size_t nFirstChunkBlockLine =
2590 13 : nFirstChunkBlock * nBlockYSize;
2591 13 : const size_t nLastChunkBlockLine =
2592 13 : nLastChunkBlock * nBlockYSize;
2593 146 : for (size_t iLine = ystart; iLine <= yend; iLine++)
2594 : {
2595 133 : const size_t nLineFromBottom = nRasterYSize - 1 - iLine;
2596 133 : const size_t nChunkY = nLineFromBottom / nBlockYSize;
2597 133 : if (nChunkY == nFirstChunkBlock)
2598 : {
2599 121 : memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
2600 121 : firstChunk.get()->data() +
2601 121 : (nLineFromBottom - nFirstChunkBlockLine) *
2602 : nChunkLineSize,
2603 : nChunkLineSize);
2604 : }
2605 : else
2606 : {
2607 12 : CPLAssert(nChunkY == nLastChunkBlock);
2608 12 : assert(secondChunk);
2609 12 : memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
2610 12 : secondChunk.get()->data() +
2611 12 : (nLineFromBottom - nLastChunkBlockLine) *
2612 : nChunkLineSize,
2613 : nChunkLineSize);
2614 : }
2615 : }
2616 13 : return CE_None;
2617 : }
2618 : }
2619 : else
2620 : {
2621 915 : ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
2622 : }
2623 : }
2624 :
2625 5744 : return FetchNetcdfChunk(xstart, ystart, pImage) ? CE_None : CE_Failure;
2626 : }
2627 :
2628 : /************************************************************************/
2629 : /* IWriteBlock() */
2630 : /************************************************************************/
2631 :
2632 6401 : CPLErr netCDFRasterBand::IWriteBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
2633 : void *pImage)
2634 : {
2635 12802 : CPLMutexHolderD(&hNCMutex);
2636 :
2637 : #ifdef NCDF_DEBUG
2638 : if (nBlockYOff == 0 || (nBlockYOff == nRasterYSize - 1))
2639 : CPLDebug("GDAL_netCDF",
2640 : "netCDFRasterBand::IWriteBlock( %d, %d, ...) nBand=%d",
2641 : nBlockXOff, nBlockYOff, nBand);
2642 : #endif
2643 :
2644 6401 : int nd = 0;
2645 6401 : nc_inq_varndims(cdfid, nZId, &nd);
2646 :
2647 : // Locate X, Y and Z position in the array.
2648 :
2649 : size_t start[MAX_NC_DIMS];
2650 6401 : memset(start, 0, sizeof(start));
2651 6401 : start[nBandXPos] = static_cast<size_t>(nBlockXOff) * nBlockXSize;
2652 :
2653 : // check y order.
2654 6401 : if (static_cast<netCDFDataset *>(poDS)->bBottomUp)
2655 : {
2656 6377 : if (nBlockYSize == 1)
2657 : {
2658 6377 : start[nBandYPos] = nRasterYSize - 1 - nBlockYOff;
2659 : }
2660 : else
2661 : {
2662 0 : CPLError(CE_Failure, CPLE_AppDefined,
2663 : "nBlockYSize = %d, only 1 supported when "
2664 : "writing bottom-up dataset",
2665 : nBlockYSize);
2666 0 : return CE_Failure;
2667 : }
2668 : }
2669 : else
2670 : {
2671 24 : start[nBandYPos] = static_cast<size_t>(nBlockYOff) * nBlockYSize; // y
2672 : }
2673 :
2674 6401 : size_t edge[MAX_NC_DIMS] = {};
2675 :
2676 6401 : edge[nBandXPos] = nBlockXSize;
2677 6401 : if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
2678 0 : edge[nBandXPos] = nRasterXSize - start[nBandXPos];
2679 6401 : edge[nBandYPos] = nBlockYSize;
2680 6401 : if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
2681 0 : edge[nBandYPos] = nRasterYSize - start[nBandYPos];
2682 :
2683 6401 : if (nd == 3)
2684 : {
2685 610 : start[panBandZPos[0]] = nLevel; // z
2686 610 : edge[panBandZPos[0]] = 1;
2687 : }
2688 :
2689 : // Compute multidimention band position.
2690 : //
2691 : // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
2692 : // if Data[2,3,4,x,y]
2693 : //
2694 : // BandPos0 = (nBand) / (3*4)
2695 : // BandPos1 = (nBand - (3*4)) / (4)
2696 : // BandPos2 = (nBand - (3*4)) % (4)
2697 6401 : if (nd > 3)
2698 : {
2699 178 : int Sum = -1;
2700 178 : int Taken = 0;
2701 534 : for (int i = 0; i < nd - 2; i++)
2702 : {
2703 356 : if (i != nd - 2 - 1)
2704 : {
2705 178 : Sum = 1;
2706 356 : for (int j = i + 1; j < nd - 2; j++)
2707 : {
2708 178 : Sum *= panBandZLev[j];
2709 : }
2710 178 : start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
2711 178 : edge[panBandZPos[i]] = 1;
2712 : }
2713 : else
2714 : {
2715 178 : start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
2716 178 : edge[panBandZPos[i]] = 1;
2717 : }
2718 356 : Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
2719 : }
2720 : }
2721 :
2722 : // Make sure we are in data mode.
2723 6401 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
2724 :
2725 : // Copy data according to type.
2726 6401 : int status = 0;
2727 6401 : if (eDataType == GDT_Byte)
2728 : {
2729 5842 : if (bSignedData)
2730 0 : status = nc_put_vara_schar(cdfid, nZId, start, edge,
2731 : static_cast<signed char *>(pImage));
2732 : else
2733 5842 : status = nc_put_vara_uchar(cdfid, nZId, start, edge,
2734 : static_cast<unsigned char *>(pImage));
2735 : }
2736 559 : else if (eDataType == GDT_Int8)
2737 : {
2738 40 : status = nc_put_vara_schar(cdfid, nZId, start, edge,
2739 : static_cast<signed char *>(pImage));
2740 : }
2741 519 : else if (nc_datatype == NC_SHORT)
2742 : {
2743 101 : status = nc_put_vara_short(cdfid, nZId, start, edge,
2744 : static_cast<short *>(pImage));
2745 : }
2746 418 : else if (eDataType == GDT_Int32)
2747 : {
2748 210 : status = nc_put_vara_int(cdfid, nZId, start, edge,
2749 : static_cast<int *>(pImage));
2750 : }
2751 208 : else if (eDataType == GDT_Float32)
2752 : {
2753 128 : status = nc_put_vara_float(cdfid, nZId, start, edge,
2754 : static_cast<float *>(pImage));
2755 : }
2756 80 : else if (eDataType == GDT_Float64)
2757 : {
2758 50 : status = nc_put_vara_double(cdfid, nZId, start, edge,
2759 : static_cast<double *>(pImage));
2760 : }
2761 30 : else if (eDataType == GDT_UInt16 &&
2762 12 : static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2763 : {
2764 12 : status = nc_put_vara_ushort(cdfid, nZId, start, edge,
2765 : static_cast<unsigned short *>(pImage));
2766 : }
2767 18 : else if (eDataType == GDT_UInt32 &&
2768 12 : static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2769 : {
2770 12 : status = nc_put_vara_uint(cdfid, nZId, start, edge,
2771 : static_cast<unsigned int *>(pImage));
2772 : }
2773 6 : else if (eDataType == GDT_UInt64 &&
2774 3 : static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2775 : {
2776 3 : status =
2777 3 : nc_put_vara_ulonglong(cdfid, nZId, start, edge,
2778 : static_cast<unsigned long long *>(pImage));
2779 : }
2780 3 : else if (eDataType == GDT_Int64 &&
2781 3 : static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2782 : {
2783 3 : status = nc_put_vara_longlong(cdfid, nZId, start, edge,
2784 : static_cast<long long *>(pImage));
2785 : }
2786 : else
2787 : {
2788 0 : CPLError(CE_Failure, CPLE_NotSupported,
2789 : "The NetCDF driver does not support GDAL data type %d",
2790 0 : eDataType);
2791 0 : status = NC_EBADTYPE;
2792 : }
2793 6401 : NCDF_ERR(status);
2794 :
2795 6401 : if (status != NC_NOERR)
2796 : {
2797 0 : CPLError(CE_Failure, CPLE_AppDefined,
2798 : "netCDF scanline write failed: %s", nc_strerror(status));
2799 0 : return CE_Failure;
2800 : }
2801 :
2802 6401 : return CE_None;
2803 : }
2804 :
2805 : /************************************************************************/
2806 : /* ==================================================================== */
2807 : /* netCDFDataset */
2808 : /* ==================================================================== */
2809 : /************************************************************************/
2810 :
2811 : /************************************************************************/
2812 : /* netCDFDataset() */
2813 : /************************************************************************/
2814 :
2815 1017 : netCDFDataset::netCDFDataset()
2816 : :
2817 : // Basic dataset vars.
2818 : #ifdef ENABLE_NCDUMP
2819 : bFileToDestroyAtClosing(false),
2820 : #endif
2821 : cdfid(-1), nSubDatasets(0), papszSubDatasets(nullptr),
2822 : papszMetadata(nullptr), bBottomUp(true), eFormat(NCDF_FORMAT_NONE),
2823 : bIsGdalFile(false), bIsGdalCfFile(false), pszCFProjection(nullptr),
2824 : pszCFCoordinates(nullptr), nCFVersion(1.6), bSGSupport(false),
2825 1017 : eMultipleLayerBehavior(SINGLE_LAYER), logCount(0), vcdf(this, cdfid),
2826 1017 : GeometryScribe(vcdf, this->generateLogName()),
2827 1017 : FieldScribe(vcdf, this->generateLogName()),
2828 2034 : bufManager(CPLGetUsablePhysicalRAM() / 5),
2829 :
2830 : // projection/GT.
2831 : nXDimID(-1), nYDimID(-1), bIsProjected(false),
2832 : bIsGeographic(false), // Can be not projected, and also not geographic
2833 : // State vars.
2834 : bDefineMode(true), bAddedGridMappingRef(false),
2835 :
2836 : // Create vars.
2837 : papszCreationOptions(nullptr), eCompress(NCDF_COMPRESS_NONE),
2838 : nZLevel(NCDF_DEFLATE_LEVEL), bChunking(false), nCreateMode(NC_CLOBBER),
2839 3051 : bSignedData(true)
2840 : {
2841 1017 : m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2842 :
2843 : // Set buffers
2844 1017 : bufManager.addBuffer(&(GeometryScribe.getMemBuffer()));
2845 1017 : bufManager.addBuffer(&(FieldScribe.getMemBuffer()));
2846 1017 : }
2847 :
2848 : /************************************************************************/
2849 : /* ~netCDFDataset() */
2850 : /************************************************************************/
2851 :
2852 1954 : netCDFDataset::~netCDFDataset()
2853 :
2854 : {
2855 1017 : netCDFDataset::Close();
2856 1954 : }
2857 :
2858 : /************************************************************************/
2859 : /* Close() */
2860 : /************************************************************************/
2861 :
2862 1769 : CPLErr netCDFDataset::Close()
2863 : {
2864 1769 : CPLErr eErr = CE_None;
2865 1769 : if (nOpenFlags != OPEN_FLAGS_CLOSED)
2866 : {
2867 2034 : CPLMutexHolderD(&hNCMutex);
2868 :
2869 : #ifdef NCDF_DEBUG
2870 : CPLDebug("GDAL_netCDF",
2871 : "netCDFDataset::~netCDFDataset(), cdfid=%d filename=%s", cdfid,
2872 : osFilename.c_str());
2873 : #endif
2874 :
2875 : // Write data related to geotransform
2876 1249 : if (GetAccess() == GA_Update && !m_bAddedProjectionVarsData &&
2877 232 : (m_bHasProjection || m_bHasGeoTransform))
2878 : {
2879 : // Ensure projection is written if GeoTransform OR Projection are
2880 : // missing.
2881 37 : if (!m_bAddedProjectionVarsDefs)
2882 : {
2883 2 : AddProjectionVars(true, nullptr, nullptr);
2884 : }
2885 37 : AddProjectionVars(false, nullptr, nullptr);
2886 : }
2887 :
2888 1017 : if (netCDFDataset::FlushCache(true) != CE_None)
2889 0 : eErr = CE_Failure;
2890 :
2891 1017 : if (GetAccess() == GA_Update && !SGCommitPendingTransaction())
2892 0 : eErr = CE_Failure;
2893 :
2894 1019 : for (size_t i = 0; i < apoVectorDatasets.size(); i++)
2895 2 : delete apoVectorDatasets[i];
2896 :
2897 : // Make sure projection variable is written to band variable.
2898 1017 : if (GetAccess() == GA_Update && !bAddedGridMappingRef)
2899 : {
2900 249 : if (!AddGridMappingRef())
2901 0 : eErr = CE_Failure;
2902 : }
2903 :
2904 1017 : CSLDestroy(papszMetadata);
2905 1017 : CSLDestroy(papszSubDatasets);
2906 1017 : CSLDestroy(papszCreationOptions);
2907 :
2908 1017 : CPLFree(pszCFProjection);
2909 :
2910 1017 : if (cdfid > 0)
2911 : {
2912 : #ifdef NCDF_DEBUG
2913 : CPLDebug("GDAL_netCDF", "calling nc_close( %d)", cdfid);
2914 : #endif
2915 661 : int status = GDAL_nc_close(cdfid);
2916 : #ifdef ENABLE_UFFD
2917 661 : NETCDF_UFFD_UNMAP(pCtx);
2918 : #endif
2919 661 : NCDF_ERR(status);
2920 661 : if (status != NC_NOERR)
2921 0 : eErr = CE_Failure;
2922 : }
2923 :
2924 1017 : if (fpVSIMEM)
2925 15 : VSIFCloseL(fpVSIMEM);
2926 :
2927 : #ifdef ENABLE_NCDUMP
2928 1017 : if (bFileToDestroyAtClosing)
2929 0 : VSIUnlink(osFilename);
2930 : #endif
2931 :
2932 1017 : if (GDALPamDataset::Close() != CE_None)
2933 0 : eErr = CE_Failure;
2934 : }
2935 1769 : return eErr;
2936 : }
2937 :
2938 : /************************************************************************/
2939 : /* SetDefineMode() */
2940 : /************************************************************************/
2941 14226 : bool netCDFDataset::SetDefineMode(bool bNewDefineMode)
2942 : {
2943 : // Do nothing if already in new define mode
2944 : // or if dataset is in read-only mode or if dataset is true NC4 dataset.
2945 14785 : if (bDefineMode == bNewDefineMode || GetAccess() == GA_ReadOnly ||
2946 559 : eFormat == NCDF_FORMAT_NC4)
2947 13814 : return true;
2948 :
2949 412 : CPLDebug("GDAL_netCDF", "SetDefineMode(%d) old=%d",
2950 412 : static_cast<int>(bNewDefineMode), static_cast<int>(bDefineMode));
2951 :
2952 412 : bDefineMode = bNewDefineMode;
2953 :
2954 : int status;
2955 412 : if (bDefineMode)
2956 143 : status = nc_redef(cdfid);
2957 : else
2958 269 : status = nc_enddef(cdfid);
2959 :
2960 412 : NCDF_ERR(status);
2961 412 : return status == NC_NOERR;
2962 : }
2963 :
2964 : /************************************************************************/
2965 : /* GetMetadataDomainList() */
2966 : /************************************************************************/
2967 :
2968 27 : char **netCDFDataset::GetMetadataDomainList()
2969 : {
2970 27 : char **papszDomains = BuildMetadataDomainList(
2971 : GDALDataset::GetMetadataDomainList(), TRUE, "SUBDATASETS", nullptr);
2972 28 : for (const auto &kv : m_oMapDomainToJSon)
2973 1 : papszDomains = CSLAddString(papszDomains, ("json:" + kv.first).c_str());
2974 27 : return papszDomains;
2975 : }
2976 :
2977 : /************************************************************************/
2978 : /* GetMetadata() */
2979 : /************************************************************************/
2980 369 : char **netCDFDataset::GetMetadata(const char *pszDomain)
2981 : {
2982 369 : if (pszDomain != nullptr && STARTS_WITH_CI(pszDomain, "SUBDATASETS"))
2983 39 : return papszSubDatasets;
2984 :
2985 330 : if (pszDomain != nullptr && STARTS_WITH(pszDomain, "json:"))
2986 : {
2987 1 : auto iter = m_oMapDomainToJSon.find(pszDomain + strlen("json:"));
2988 1 : if (iter != m_oMapDomainToJSon.end())
2989 1 : return iter->second.List();
2990 : }
2991 :
2992 329 : return GDALDataset::GetMetadata(pszDomain);
2993 : }
2994 :
2995 : /************************************************************************/
2996 : /* SetMetadataItem() */
2997 : /************************************************************************/
2998 :
2999 43 : CPLErr netCDFDataset::SetMetadataItem(const char *pszName, const char *pszValue,
3000 : const char *pszDomain)
3001 : {
3002 85 : if (GetAccess() == GA_Update &&
3003 85 : (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)
3004 : {
3005 42 : std::string osName(pszName);
3006 :
3007 : // Same logic as in CopyMetadata()
3008 42 : if (STARTS_WITH(osName.c_str(), "NC_GLOBAL#"))
3009 8 : osName = osName.substr(strlen("NC_GLOBAL#"));
3010 34 : else if (strchr(osName.c_str(), '#') == nullptr)
3011 5 : osName = "GDAL_" + osName;
3012 :
3013 84 : if (STARTS_WITH(osName.c_str(), "NETCDF_DIM_") ||
3014 42 : strchr(osName.c_str(), '#') != nullptr)
3015 : {
3016 : // do nothing
3017 29 : return CE_None;
3018 : }
3019 : else
3020 : {
3021 13 : SetDefineMode(true);
3022 :
3023 13 : if (!NCDFPutAttr(cdfid, NC_GLOBAL, osName.c_str(), pszValue))
3024 13 : return CE_Failure;
3025 : }
3026 : }
3027 :
3028 1 : return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);
3029 : }
3030 :
3031 : /************************************************************************/
3032 : /* SetMetadata() */
3033 : /************************************************************************/
3034 :
3035 8 : CPLErr netCDFDataset::SetMetadata(char **papszMD, const char *pszDomain)
3036 : {
3037 13 : if (GetAccess() == GA_Update &&
3038 5 : (pszDomain == nullptr || pszDomain[0] == '\0'))
3039 : {
3040 : // We don't handle metadata item removal for now
3041 50 : for (const char *const *papszIter = papszMD; papszIter && *papszIter;
3042 : ++papszIter)
3043 : {
3044 42 : char *pszName = nullptr;
3045 42 : const char *pszValue = CPLParseNameValue(*papszIter, &pszName);
3046 42 : if (pszName && pszValue)
3047 42 : SetMetadataItem(pszName, pszValue);
3048 42 : CPLFree(pszName);
3049 : }
3050 8 : return CE_None;
3051 : }
3052 0 : return GDALPamDataset::SetMetadata(papszMD, pszDomain);
3053 : }
3054 :
3055 : /************************************************************************/
3056 : /* GetSpatialRef() */
3057 : /************************************************************************/
3058 :
3059 190 : const OGRSpatialReference *netCDFDataset::GetSpatialRef() const
3060 : {
3061 190 : if (m_bHasProjection)
3062 75 : return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
3063 :
3064 115 : return GDALPamDataset::GetSpatialRef();
3065 : }
3066 :
3067 : /************************************************************************/
3068 : /* FetchCopyParam() */
3069 : /************************************************************************/
3070 :
3071 436 : double netCDFDataset::FetchCopyParam(const char *pszGridMappingValue,
3072 : const char *pszParam, double dfDefault,
3073 : bool *pbFound)
3074 :
3075 : {
3076 : char *pszTemp =
3077 436 : CPLStrdup(CPLSPrintf("%s#%s", pszGridMappingValue, pszParam));
3078 436 : const char *pszValue = CSLFetchNameValue(papszMetadata, pszTemp);
3079 436 : CPLFree(pszTemp);
3080 :
3081 436 : if (pbFound)
3082 : {
3083 436 : *pbFound = (pszValue != nullptr);
3084 : }
3085 :
3086 436 : if (pszValue)
3087 : {
3088 0 : return CPLAtofM(pszValue);
3089 : }
3090 :
3091 436 : return dfDefault;
3092 : }
3093 :
3094 : /************************************************************************/
3095 : /* FetchStandardParallels() */
3096 : /************************************************************************/
3097 :
3098 : std::vector<std::string>
3099 0 : netCDFDataset::FetchStandardParallels(const char *pszGridMappingValue)
3100 : {
3101 : // cf-1.0 tags
3102 0 : const char *pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL);
3103 :
3104 0 : std::vector<std::string> ret;
3105 0 : if (pszValue != nullptr)
3106 : {
3107 0 : CPLStringList aosValues;
3108 0 : if (pszValue[0] != '{' &&
3109 0 : CPLString(pszValue).Trim().find(' ') != std::string::npos)
3110 : {
3111 : // Some files like
3112 : // ftp://data.knmi.nl/download/KNW-NetCDF-3D/1.0/noversion/2013/11/14/KNW-1.0_H37-ERA_NL_20131114.nc
3113 : // do not use standard formatting for arrays, but just space
3114 : // separated syntax
3115 0 : aosValues = CSLTokenizeString2(pszValue, " ", 0);
3116 : }
3117 : else
3118 : {
3119 0 : aosValues = NCDFTokenizeArray(pszValue);
3120 : }
3121 0 : for (int i = 0; i < aosValues.size(); i++)
3122 : {
3123 0 : ret.push_back(aosValues[i]);
3124 : }
3125 : }
3126 : // Try gdal tags.
3127 : else
3128 : {
3129 0 : pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_1);
3130 :
3131 0 : if (pszValue != nullptr)
3132 0 : ret.push_back(pszValue);
3133 :
3134 0 : pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_2);
3135 :
3136 0 : if (pszValue != nullptr)
3137 0 : ret.push_back(pszValue);
3138 : }
3139 :
3140 0 : return ret;
3141 : }
3142 :
3143 : /************************************************************************/
3144 : /* FetchAttr() */
3145 : /************************************************************************/
3146 :
3147 3740 : const char *netCDFDataset::FetchAttr(const char *pszVarFullName,
3148 : const char *pszAttr)
3149 :
3150 : {
3151 3740 : char *pszKey = CPLStrdup(CPLSPrintf("%s#%s", pszVarFullName, pszAttr));
3152 3740 : const char *pszValue = CSLFetchNameValue(papszMetadata, pszKey);
3153 3740 : CPLFree(pszKey);
3154 3740 : return pszValue;
3155 : }
3156 :
3157 2474 : const char *netCDFDataset::FetchAttr(int nGroupId, int nVarId,
3158 : const char *pszAttr)
3159 :
3160 : {
3161 2474 : char *pszVarFullName = nullptr;
3162 2474 : NCDFGetVarFullName(nGroupId, nVarId, &pszVarFullName);
3163 2474 : const char *pszValue = FetchAttr(pszVarFullName, pszAttr);
3164 2474 : CPLFree(pszVarFullName);
3165 2474 : return pszValue;
3166 : }
3167 :
3168 : /************************************************************************/
3169 : /* IsDifferenceBelow() */
3170 : /************************************************************************/
3171 :
3172 1085 : static bool IsDifferenceBelow(double dfA, double dfB, double dfError)
3173 : {
3174 1085 : const double dfAbsDiff = fabs(dfA - dfB);
3175 1085 : return dfAbsDiff <= dfError;
3176 : }
3177 :
3178 : /************************************************************************/
3179 : /* SetProjectionFromVar() */
3180 : /************************************************************************/
3181 522 : void netCDFDataset::SetProjectionFromVar(
3182 : int nGroupId, int nVarId, bool bReadSRSOnly, const char *pszGivenGM,
3183 : std::string *returnProjStr, nccfdriver::SGeometry_Reader *sg,
3184 : std::vector<std::string> *paosRemovedMDItems)
3185 : {
3186 522 : bool bGotGeogCS = false;
3187 522 : bool bGotCfSRS = false;
3188 522 : bool bGotCfWktSRS = false;
3189 522 : bool bGotGdalSRS = false;
3190 522 : bool bGotCfGT = false;
3191 522 : bool bGotGdalGT = false;
3192 :
3193 : // These values from CF metadata.
3194 522 : OGRSpatialReference oSRS;
3195 522 : oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3196 522 : size_t xdim = nRasterXSize;
3197 522 : size_t ydim = nRasterYSize;
3198 :
3199 : // These values from GDAL metadata.
3200 522 : const char *pszWKT = nullptr;
3201 522 : const char *pszGeoTransform = nullptr;
3202 :
3203 522 : netCDFDataset *poDS = this; // Perhaps this should be removed for clarity.
3204 :
3205 522 : CPLDebug("GDAL_netCDF", "\n=====\nSetProjectionFromVar( %d, %d)", nGroupId,
3206 : nVarId);
3207 :
3208 : // Get x/y range information.
3209 :
3210 : // Temp variables to use in SetGeoTransform() and SetProjection().
3211 522 : GDALGeoTransform tmpGT;
3212 :
3213 : // Look for grid_mapping metadata.
3214 522 : const char *pszValue = pszGivenGM;
3215 522 : CPLString osTmpGridMapping; // let is in this outer scope as pszValue may
3216 : // point to it
3217 522 : if (pszValue == nullptr)
3218 : {
3219 479 : pszValue = FetchAttr(nGroupId, nVarId, CF_GRD_MAPPING);
3220 479 : if (pszValue && strchr(pszValue, ':') && strchr(pszValue, ' '))
3221 : {
3222 : // Expanded form of grid_mapping
3223 : // e.g. "crsOSGB: x y crsWGS84: lat lon"
3224 : // Pickup the grid_mapping whose coordinates are dimensions of the
3225 : // variable
3226 6 : CPLStringList aosTokens(CSLTokenizeString2(pszValue, " ", 0));
3227 3 : if ((aosTokens.size() % 3) == 0)
3228 : {
3229 3 : for (int i = 0; i < aosTokens.size() / 3; i++)
3230 : {
3231 3 : if (CSLFindString(poDS->papszDimName,
3232 9 : aosTokens[3 * i + 1]) >= 0 &&
3233 3 : CSLFindString(poDS->papszDimName,
3234 3 : aosTokens[3 * i + 2]) >= 0)
3235 : {
3236 3 : osTmpGridMapping = aosTokens[3 * i];
3237 6 : if (!osTmpGridMapping.empty() &&
3238 3 : osTmpGridMapping.back() == ':')
3239 : {
3240 3 : osTmpGridMapping.resize(osTmpGridMapping.size() -
3241 : 1);
3242 : }
3243 3 : pszValue = osTmpGridMapping.c_str();
3244 3 : break;
3245 : }
3246 : }
3247 : }
3248 : }
3249 : }
3250 522 : char *pszGridMappingValue = CPLStrdup(pszValue ? pszValue : "");
3251 :
3252 522 : if (!EQUAL(pszGridMappingValue, ""))
3253 : {
3254 : // Read grid_mapping metadata.
3255 220 : int nProjGroupID = -1;
3256 220 : int nProjVarID = -1;
3257 220 : if (NCDFResolveVar(nGroupId, pszGridMappingValue, &nProjGroupID,
3258 220 : &nProjVarID) == CE_None)
3259 : {
3260 219 : poDS->ReadAttributes(nProjGroupID, nProjVarID);
3261 :
3262 : // Look for GDAL spatial_ref and GeoTransform within grid_mapping.
3263 219 : CPLFree(pszGridMappingValue);
3264 219 : pszGridMappingValue = nullptr;
3265 219 : NCDFGetVarFullName(nProjGroupID, nProjVarID, &pszGridMappingValue);
3266 219 : if (pszGridMappingValue)
3267 : {
3268 219 : CPLDebug("GDAL_netCDF", "got grid_mapping %s",
3269 : pszGridMappingValue);
3270 219 : pszWKT = FetchAttr(pszGridMappingValue, NCDF_SPATIAL_REF);
3271 219 : if (!pszWKT)
3272 : {
3273 34 : pszWKT = FetchAttr(pszGridMappingValue, NCDF_CRS_WKT);
3274 : }
3275 : else
3276 : {
3277 185 : bGotGdalSRS = true;
3278 185 : CPLDebug("GDAL_netCDF", "setting WKT from GDAL");
3279 : }
3280 219 : if (pszWKT)
3281 : {
3282 189 : if (!bGotGdalSRS)
3283 : {
3284 4 : bGotCfWktSRS = true;
3285 4 : CPLDebug("GDAL_netCDF", "setting WKT from CF");
3286 : }
3287 189 : if (returnProjStr != nullptr)
3288 : {
3289 41 : (*returnProjStr) = std::string(pszWKT);
3290 : }
3291 : else
3292 : {
3293 148 : m_bAddedProjectionVarsDefs = true;
3294 148 : m_bAddedProjectionVarsData = true;
3295 296 : OGRSpatialReference oSRSTmp;
3296 148 : oSRSTmp.SetAxisMappingStrategy(
3297 : OAMS_TRADITIONAL_GIS_ORDER);
3298 148 : oSRSTmp.importFromWkt(pszWKT);
3299 148 : SetSpatialRefNoUpdate(&oSRSTmp);
3300 : }
3301 : pszGeoTransform =
3302 189 : FetchAttr(pszGridMappingValue, NCDF_GEOTRANSFORM);
3303 : }
3304 : }
3305 : else
3306 : {
3307 0 : pszGridMappingValue = CPLStrdup("");
3308 : }
3309 : }
3310 : }
3311 :
3312 : // Get information about the file.
3313 : //
3314 : // Was this file created by the GDAL netcdf driver?
3315 : // Was this file created by the newer (CF-conformant) driver?
3316 : //
3317 : // 1) If GDAL netcdf metadata is set, and version >= 1.9,
3318 : // it was created with the new driver
3319 : // 2) Else, if spatial_ref and GeoTransform are present in the
3320 : // grid_mapping variable, it was created by the old driver
3321 522 : pszValue = FetchAttr("NC_GLOBAL", "GDAL");
3322 :
3323 522 : if (pszValue && NCDFIsGDALVersionGTE(pszValue, 1900))
3324 : {
3325 241 : bIsGdalFile = true;
3326 241 : bIsGdalCfFile = true;
3327 : }
3328 281 : else if (pszWKT != nullptr && pszGeoTransform != nullptr)
3329 : {
3330 18 : bIsGdalFile = true;
3331 18 : bIsGdalCfFile = false;
3332 : }
3333 :
3334 : // Set default bottom-up default value.
3335 : // Y axis dimension and absence of GT can modify this value.
3336 : // Override with Config option GDAL_NETCDF_BOTTOMUP.
3337 :
3338 : // New driver is bottom-up by default.
3339 522 : if ((bIsGdalFile && !bIsGdalCfFile) || bSwitchedXY)
3340 20 : poDS->bBottomUp = false;
3341 : else
3342 502 : poDS->bBottomUp = true;
3343 :
3344 522 : CPLDebug("GDAL_netCDF",
3345 : "bIsGdalFile=%d bIsGdalCfFile=%d bSwitchedXY=%d bBottomUp=%d",
3346 522 : static_cast<int>(bIsGdalFile), static_cast<int>(bIsGdalCfFile),
3347 522 : static_cast<int>(bSwitchedXY), static_cast<int>(bBottomUp));
3348 :
3349 : // Read projection coordinates.
3350 :
3351 522 : int nGroupDimXID = -1;
3352 522 : int nVarDimXID = -1;
3353 522 : int nGroupDimYID = -1;
3354 522 : int nVarDimYID = -1;
3355 522 : if (sg != nullptr)
3356 : {
3357 43 : nGroupDimXID = sg->get_ncID();
3358 43 : nGroupDimYID = sg->get_ncID();
3359 43 : nVarDimXID = sg->getNodeCoordVars()[0];
3360 43 : nVarDimYID = sg->getNodeCoordVars()[1];
3361 : }
3362 :
3363 522 : if (!bReadSRSOnly)
3364 : {
3365 350 : NCDFResolveVar(nGroupId, poDS->papszDimName[nXDimID], &nGroupDimXID,
3366 : &nVarDimXID);
3367 350 : NCDFResolveVar(nGroupId, poDS->papszDimName[nYDimID], &nGroupDimYID,
3368 : &nVarDimYID);
3369 : // TODO: if above resolving fails we should also search for coordinate
3370 : // variables without same name than dimension using the same resolving
3371 : // logic. This should handle for example NASA Ocean Color L2 products.
3372 :
3373 : const bool bIgnoreXYAxisNameChecks =
3374 700 : CPLTestBool(CSLFetchNameValueDef(
3375 350 : papszOpenOptions, "IGNORE_XY_AXIS_NAME_CHECKS",
3376 : CPLGetConfigOption("GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS",
3377 350 : "NO"))) ||
3378 : // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a res
3379 : // and transform attributes
3380 350 : (FetchAttr(nGroupId, nVarId, "res") != nullptr &&
3381 700 : FetchAttr(nGroupId, nVarId, "transform") != nullptr) ||
3382 349 : FetchAttr(nGroupId, NC_GLOBAL, "GMT_version") != nullptr;
3383 :
3384 : // Check that they are 1D or 2D variables
3385 350 : if (nVarDimXID >= 0)
3386 : {
3387 254 : int ndims = -1;
3388 254 : nc_inq_varndims(nGroupId, nVarDimXID, &ndims);
3389 254 : if (ndims == 0 || ndims > 2)
3390 0 : nVarDimXID = -1;
3391 254 : else if (!bIgnoreXYAxisNameChecks)
3392 : {
3393 252 : if (!NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
3394 162 : !NCDFIsVarProjectionX(nGroupId, nVarDimXID, nullptr) &&
3395 : // In case of inversion of X/Y
3396 446 : !NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr) &&
3397 32 : !NCDFIsVarProjectionY(nGroupId, nVarDimXID, nullptr))
3398 : {
3399 : char szVarNameX[NC_MAX_NAME + 1];
3400 32 : CPL_IGNORE_RET_VAL(
3401 32 : nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
3402 32 : if (!(ndims == 1 &&
3403 31 : (EQUAL(szVarNameX, CF_LONGITUDE_STD_NAME) ||
3404 30 : EQUAL(szVarNameX, CF_LONGITUDE_VAR_NAME))))
3405 : {
3406 31 : CPLDebug(
3407 : "netCDF",
3408 : "Georeferencing ignored due to non-specific "
3409 : "enough X axis name. "
3410 : "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
3411 : "as configuration option to bypass this check");
3412 31 : nVarDimXID = -1;
3413 : }
3414 : }
3415 : }
3416 : }
3417 :
3418 350 : if (nVarDimYID >= 0)
3419 : {
3420 256 : int ndims = -1;
3421 256 : nc_inq_varndims(nGroupId, nVarDimYID, &ndims);
3422 256 : if (ndims == 0 || ndims > 2)
3423 1 : nVarDimYID = -1;
3424 255 : else if (!bIgnoreXYAxisNameChecks)
3425 : {
3426 253 : if (!NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr) &&
3427 163 : !NCDFIsVarProjectionY(nGroupId, nVarDimYID, nullptr) &&
3428 : // In case of inversion of X/Y
3429 449 : !NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
3430 33 : !NCDFIsVarProjectionX(nGroupId, nVarDimYID, nullptr))
3431 : {
3432 : char szVarNameY[NC_MAX_NAME + 1];
3433 33 : CPL_IGNORE_RET_VAL(
3434 33 : nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
3435 33 : if (!(ndims == 1 &&
3436 33 : (EQUAL(szVarNameY, CF_LATITUDE_STD_NAME) ||
3437 32 : EQUAL(szVarNameY, CF_LATITUDE_VAR_NAME))))
3438 : {
3439 32 : CPLDebug(
3440 : "netCDF",
3441 : "Georeferencing ignored due to non-specific "
3442 : "enough Y axis name. "
3443 : "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
3444 : "as configuration option to bypass this check");
3445 32 : nVarDimYID = -1;
3446 : }
3447 : }
3448 : }
3449 : }
3450 :
3451 350 : if ((nVarDimXID >= 0 && xdim == 1) || (nVarDimXID >= 0 && ydim == 1))
3452 : {
3453 0 : CPLError(CE_Warning, CPLE_AppDefined,
3454 : "1-pixel width/height files not supported, "
3455 : "xdim: %ld ydim: %ld",
3456 : static_cast<long>(xdim), static_cast<long>(ydim));
3457 0 : nVarDimXID = -1;
3458 0 : nVarDimYID = -1;
3459 : }
3460 : }
3461 :
3462 522 : const char *pszUnits = nullptr;
3463 522 : if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
3464 : {
3465 266 : const char *pszUnitsX = FetchAttr(nGroupDimXID, nVarDimXID, "units");
3466 266 : const char *pszUnitsY = FetchAttr(nGroupDimYID, nVarDimYID, "units");
3467 : // Normalize degrees_east/degrees_north to degrees
3468 : // Cf https://github.com/OSGeo/gdal/issues/11009
3469 266 : if (pszUnitsX && EQUAL(pszUnitsX, "degrees_east"))
3470 79 : pszUnitsX = "degrees";
3471 266 : if (pszUnitsY && EQUAL(pszUnitsY, "degrees_north"))
3472 79 : pszUnitsY = "degrees";
3473 :
3474 266 : if (pszUnitsX && pszUnitsY)
3475 : {
3476 219 : if (EQUAL(pszUnitsX, pszUnitsY))
3477 216 : pszUnits = pszUnitsX;
3478 3 : else if (!pszWKT && !EQUAL(pszGridMappingValue, ""))
3479 : {
3480 0 : CPLError(CE_Failure, CPLE_AppDefined,
3481 : "X axis unit (%s) is different from Y axis "
3482 : "unit (%s). SRS will ignore axis unit and be "
3483 : "likely wrong.",
3484 : pszUnitsX, pszUnitsY);
3485 : }
3486 : }
3487 47 : else if (pszUnitsX && !pszWKT && !EQUAL(pszGridMappingValue, ""))
3488 : {
3489 0 : CPLError(CE_Failure, CPLE_AppDefined,
3490 : "X axis unit is defined, but not Y one ."
3491 : "SRS will ignore axis unit and be likely wrong.");
3492 : }
3493 47 : else if (pszUnitsY && !pszWKT && !EQUAL(pszGridMappingValue, ""))
3494 : {
3495 0 : CPLError(CE_Failure, CPLE_AppDefined,
3496 : "Y axis unit is defined, but not X one ."
3497 : "SRS will ignore axis unit and be likely wrong.");
3498 : }
3499 : }
3500 :
3501 522 : if (!pszWKT && !EQUAL(pszGridMappingValue, ""))
3502 : {
3503 31 : CPLStringList aosGridMappingKeyValues;
3504 31 : const size_t nLenGridMappingValue = strlen(pszGridMappingValue);
3505 789 : for (const char *const *papszIter = papszMetadata;
3506 789 : papszIter && *papszIter; ++papszIter)
3507 : {
3508 758 : if (STARTS_WITH(*papszIter, pszGridMappingValue) &&
3509 236 : (*papszIter)[nLenGridMappingValue] == '#')
3510 : {
3511 236 : char *pszKey = nullptr;
3512 472 : pszValue = CPLParseNameValue(
3513 236 : *papszIter + nLenGridMappingValue + 1, &pszKey);
3514 236 : if (pszKey && pszValue)
3515 236 : aosGridMappingKeyValues.SetNameValue(pszKey, pszValue);
3516 236 : CPLFree(pszKey);
3517 : }
3518 : }
3519 :
3520 31 : bGotGeogCS = aosGridMappingKeyValues.FetchNameValue(
3521 : CF_PP_SEMI_MAJOR_AXIS) != nullptr;
3522 :
3523 31 : oSRS.importFromCF1(aosGridMappingKeyValues.List(), pszUnits);
3524 31 : bGotCfSRS = oSRS.IsGeographic() || oSRS.IsProjected();
3525 : }
3526 : else
3527 : {
3528 : // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a "crs"
3529 : // attribute hold on the variable of interest that contains a PROJ.4
3530 : // string
3531 491 : pszValue = FetchAttr(nGroupId, nVarId, "crs");
3532 492 : if (pszValue &&
3533 1 : (strstr(pszValue, "+proj=") != nullptr ||
3534 0 : strstr(pszValue, "GEOGCS") != nullptr ||
3535 0 : strstr(pszValue, "PROJCS") != nullptr ||
3536 492 : strstr(pszValue, "EPSG:") != nullptr) &&
3537 1 : oSRS.SetFromUserInput(pszValue) == OGRERR_NONE)
3538 : {
3539 1 : bGotCfSRS = true;
3540 : }
3541 : }
3542 :
3543 : // Set Projection from CF.
3544 522 : double dfLinearUnitsConvFactor = 1.0;
3545 522 : if ((bGotGeogCS || bGotCfSRS))
3546 : {
3547 31 : if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
3548 : {
3549 : // Set SRS Units.
3550 :
3551 : // Check units for x and y.
3552 28 : if (oSRS.IsProjected())
3553 : {
3554 25 : dfLinearUnitsConvFactor = oSRS.GetLinearUnits(nullptr);
3555 :
3556 : // If the user doesn't ask to preserve the axis unit,
3557 : // then normalize to metre
3558 31 : if (dfLinearUnitsConvFactor != 1.0 &&
3559 6 : !CPLFetchBool(GetOpenOptions(), "PRESERVE_AXIS_UNIT_IN_CRS",
3560 : false))
3561 : {
3562 5 : oSRS.SetLinearUnits("metre", 1.0);
3563 5 : oSRS.SetAuthority("PROJCS|UNIT", "EPSG", 9001);
3564 : }
3565 : else
3566 : {
3567 20 : dfLinearUnitsConvFactor = 1.0;
3568 : }
3569 : }
3570 : }
3571 :
3572 : // Set projection.
3573 31 : char *pszTempProjection = nullptr;
3574 31 : oSRS.exportToWkt(&pszTempProjection);
3575 31 : if (pszTempProjection)
3576 : {
3577 31 : CPLDebug("GDAL_netCDF", "setting WKT from CF");
3578 31 : if (returnProjStr != nullptr)
3579 : {
3580 2 : (*returnProjStr) = std::string(pszTempProjection);
3581 : }
3582 : else
3583 : {
3584 29 : m_bAddedProjectionVarsDefs = true;
3585 29 : m_bAddedProjectionVarsData = true;
3586 29 : SetSpatialRefNoUpdate(&oSRS);
3587 : }
3588 : }
3589 31 : CPLFree(pszTempProjection);
3590 : }
3591 :
3592 522 : if (!bReadSRSOnly && (nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 &&
3593 : ydim > 0)
3594 : {
3595 : double *pdfXCoord =
3596 223 : static_cast<double *>(CPLCalloc(xdim, sizeof(double)));
3597 : double *pdfYCoord =
3598 223 : static_cast<double *>(CPLCalloc(ydim, sizeof(double)));
3599 :
3600 223 : size_t start[2] = {0, 0};
3601 223 : size_t edge[2] = {xdim, 0};
3602 223 : int status = nc_get_vara_double(nGroupDimXID, nVarDimXID, start, edge,
3603 : pdfXCoord);
3604 223 : NCDF_ERR(status);
3605 :
3606 223 : edge[0] = ydim;
3607 223 : status = nc_get_vara_double(nGroupDimYID, nVarDimYID, start, edge,
3608 : pdfYCoord);
3609 223 : NCDF_ERR(status);
3610 :
3611 223 : nc_type nc_var_dimx_datatype = NC_NAT;
3612 : status =
3613 223 : nc_inq_vartype(nGroupDimXID, nVarDimXID, &nc_var_dimx_datatype);
3614 223 : NCDF_ERR(status);
3615 :
3616 223 : nc_type nc_var_dimy_datatype = NC_NAT;
3617 : status =
3618 223 : nc_inq_vartype(nGroupDimYID, nVarDimYID, &nc_var_dimy_datatype);
3619 223 : NCDF_ERR(status);
3620 :
3621 223 : if (!poDS->bSwitchedXY)
3622 : {
3623 : // Convert ]180,540] longitude values to ]-180,0].
3624 311 : if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
3625 90 : CPLTestBool(
3626 : CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")))
3627 : {
3628 : // If minimum longitude is > 180, subtract 360 from all.
3629 : // Add a check on the maximum X value too, since
3630 : // NCDFIsVarLongitude() is not very specific by default (see
3631 : // https://github.com/OSGeo/gdal/issues/1440)
3632 97 : if (std::min(pdfXCoord[0], pdfXCoord[xdim - 1]) > 180.0 &&
3633 7 : std::max(pdfXCoord[0], pdfXCoord[xdim - 1]) <= 540)
3634 : {
3635 0 : CPLDebug(
3636 : "GDAL_netCDF",
3637 : "Offsetting longitudes from ]180,540] to ]-180,180]. "
3638 : "Can be disabled with GDAL_NETCDF_CENTERLONG_180=NO");
3639 0 : for (size_t i = 0; i < xdim; i++)
3640 0 : pdfXCoord[i] -= 360;
3641 : }
3642 : }
3643 : }
3644 :
3645 : // Is pixel spacing uniform across the map?
3646 :
3647 : // Check Longitude.
3648 :
3649 223 : bool bLonSpacingOK = false;
3650 223 : if (xdim == 2)
3651 : {
3652 28 : bLonSpacingOK = true;
3653 : }
3654 : else
3655 : {
3656 195 : bool bWestIsLeft = (pdfXCoord[0] < pdfXCoord[xdim - 1]);
3657 :
3658 : // fix longitudes if longitudes should increase from
3659 : // west to east, but west > east
3660 275 : if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
3661 80 : !bWestIsLeft)
3662 : {
3663 2 : size_t ndecreases = 0;
3664 :
3665 : // there is lon wrap if longitudes increase
3666 : // with one single decrease
3667 107 : for (size_t i = 1; i < xdim; i++)
3668 : {
3669 105 : if (pdfXCoord[i] < pdfXCoord[i - 1])
3670 1 : ndecreases++;
3671 : }
3672 :
3673 2 : if (ndecreases == 1)
3674 : {
3675 1 : CPLDebug("GDAL_netCDF", "longitude wrap detected");
3676 4 : for (size_t i = 0; i < xdim; i++)
3677 : {
3678 3 : if (pdfXCoord[i] > pdfXCoord[xdim - 1])
3679 1 : pdfXCoord[i] -= 360;
3680 : }
3681 : }
3682 : }
3683 :
3684 195 : const double dfSpacingBegin = pdfXCoord[1] - pdfXCoord[0];
3685 195 : const double dfSpacingMiddle =
3686 195 : pdfXCoord[xdim / 2 + 1] - pdfXCoord[xdim / 2];
3687 195 : const double dfSpacingLast =
3688 195 : pdfXCoord[xdim - 1] - pdfXCoord[xdim - 2];
3689 :
3690 195 : CPLDebug("GDAL_netCDF",
3691 : "xdim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
3692 : "dfSpacingLast: %f",
3693 : static_cast<long>(xdim), dfSpacingBegin, dfSpacingMiddle,
3694 : dfSpacingLast);
3695 : #ifdef NCDF_DEBUG
3696 : CPLDebug("GDAL_netCDF", "xcoords: %f %f %f %f %f %f", pdfXCoord[0],
3697 : pdfXCoord[1], pdfXCoord[xdim / 2],
3698 : pdfXCoord[(xdim / 2) + 1], pdfXCoord[xdim - 2],
3699 : pdfXCoord[xdim - 1]);
3700 : #endif
3701 :
3702 : // ftp://ftp.cdc.noaa.gov/Datasets/NARR/Dailies/monolevel/vwnd.10m.2015.nc
3703 : // requires a 0.02% tolerance, so let's settle for 0.05%
3704 :
3705 : // For float variables, increase to 0.2% (as seen in
3706 : // https://github.com/OSGeo/gdal/issues/3663)
3707 195 : const double dfEpsRel =
3708 195 : nc_var_dimx_datatype == NC_FLOAT ? 0.002 : 0.0005;
3709 :
3710 : const double dfEps =
3711 : dfEpsRel *
3712 390 : std::max(fabs(dfSpacingBegin),
3713 195 : std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
3714 384 : if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
3715 384 : IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
3716 189 : IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
3717 : {
3718 189 : bLonSpacingOK = true;
3719 : }
3720 6 : else if (CPLTestBool(CPLGetConfigOption(
3721 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
3722 : {
3723 0 : bLonSpacingOK = true;
3724 0 : CPLDebug(
3725 : "GDAL_netCDF",
3726 : "Longitude/X is not equally spaced, but will be considered "
3727 : "as such because of "
3728 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
3729 : }
3730 : }
3731 :
3732 223 : if (bLonSpacingOK == false)
3733 : {
3734 6 : CPLDebug(
3735 : "GDAL_netCDF", "%s",
3736 : "Longitude/X is not equally spaced (with a 0.05% tolerance). "
3737 : "You may set the "
3738 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
3739 : "option to YES to ignore this check");
3740 : }
3741 :
3742 : // Check Latitude.
3743 223 : bool bLatSpacingOK = false;
3744 :
3745 223 : if (ydim == 2)
3746 : {
3747 48 : bLatSpacingOK = true;
3748 : }
3749 : else
3750 : {
3751 175 : const double dfSpacingBegin = pdfYCoord[1] - pdfYCoord[0];
3752 175 : const double dfSpacingMiddle =
3753 175 : pdfYCoord[ydim / 2 + 1] - pdfYCoord[ydim / 2];
3754 :
3755 175 : const double dfSpacingLast =
3756 175 : pdfYCoord[ydim - 1] - pdfYCoord[ydim - 2];
3757 :
3758 175 : CPLDebug("GDAL_netCDF",
3759 : "ydim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
3760 : "dfSpacingLast: %f",
3761 : (long)ydim, dfSpacingBegin, dfSpacingMiddle,
3762 : dfSpacingLast);
3763 : #ifdef NCDF_DEBUG
3764 : CPLDebug("GDAL_netCDF", "ycoords: %f %f %f %f %f %f", pdfYCoord[0],
3765 : pdfYCoord[1], pdfYCoord[ydim / 2],
3766 : pdfYCoord[(ydim / 2) + 1], pdfYCoord[ydim - 2],
3767 : pdfYCoord[ydim - 1]);
3768 : #endif
3769 :
3770 175 : const double dfEpsRel =
3771 175 : nc_var_dimy_datatype == NC_FLOAT ? 0.002 : 0.0005;
3772 :
3773 : const double dfEps =
3774 : dfEpsRel *
3775 350 : std::max(fabs(dfSpacingBegin),
3776 175 : std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
3777 348 : if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
3778 348 : IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
3779 164 : IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
3780 : {
3781 164 : bLatSpacingOK = true;
3782 : }
3783 11 : else if (CPLTestBool(CPLGetConfigOption(
3784 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
3785 : {
3786 0 : bLatSpacingOK = true;
3787 0 : CPLDebug(
3788 : "GDAL_netCDF",
3789 : "Latitude/Y is not equally spaced, but will be considered "
3790 : "as such because of "
3791 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
3792 : }
3793 11 : else if (!oSRS.IsProjected() &&
3794 11 : fabs(dfSpacingBegin - dfSpacingLast) <= 0.1 &&
3795 30 : fabs(dfSpacingBegin - dfSpacingMiddle) <= 0.1 &&
3796 8 : fabs(dfSpacingMiddle - dfSpacingLast) <= 0.1)
3797 : {
3798 8 : bLatSpacingOK = true;
3799 8 : CPLError(CE_Warning, CPLE_AppDefined,
3800 : "Latitude grid not spaced evenly. "
3801 : "Setting projection for grid spacing is "
3802 : "within 0.1 degrees threshold.");
3803 :
3804 8 : CPLDebug("GDAL_netCDF",
3805 : "Latitude grid not spaced evenly, but within 0.1 "
3806 : "degree threshold (probably a Gaussian grid). "
3807 : "Saving original latitude values in Y_VALUES "
3808 : "geolocation metadata");
3809 8 : Set1DGeolocation(nGroupDimYID, nVarDimYID, "Y");
3810 : }
3811 :
3812 175 : if (bLatSpacingOK == false)
3813 : {
3814 3 : CPLDebug(
3815 : "GDAL_netCDF", "%s",
3816 : "Latitude/Y is not equally spaced (with a 0.05% "
3817 : "tolerance). "
3818 : "You may set the "
3819 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
3820 : "option to YES to ignore this check");
3821 : }
3822 : }
3823 :
3824 223 : if (bLonSpacingOK && bLatSpacingOK)
3825 : {
3826 : // We have gridded data so we can set the Georeferencing info.
3827 :
3828 : // Enable GeoTransform.
3829 :
3830 : // In the following "actual_range" and "node_offset"
3831 : // are attributes used by netCDF files created by GMT.
3832 : // If we find them we know how to proceed. Else, use
3833 : // the original algorithm.
3834 216 : bGotCfGT = true;
3835 :
3836 216 : int node_offset = 0;
3837 216 : NCDFResolveAttInt(nGroupId, NC_GLOBAL, "node_offset", &node_offset);
3838 :
3839 216 : double adfActualRange[2] = {0.0, 0.0};
3840 216 : double xMinMax[2] = {0.0, 0.0};
3841 216 : double yMinMax[2] = {0.0, 0.0};
3842 :
3843 : const auto RoundMinMaxForFloatVals =
3844 58 : [](double &dfMin, double &dfMax, int nIntervals)
3845 : {
3846 : // Helps for a case where longitudes range from
3847 : // -179.99 to 180.0 with a 0.01 degree spacing.
3848 : // However as this is encoded in a float array,
3849 : // -179.99 is actually read as -179.99000549316406 as
3850 : // a double. Try to detect that and correct the rounding
3851 :
3852 87 : const auto IsAlmostInteger = [](double dfVal)
3853 : {
3854 87 : constexpr double THRESHOLD_INTEGER = 1e-3;
3855 87 : return std::fabs(dfVal - std::round(dfVal)) <=
3856 87 : THRESHOLD_INTEGER;
3857 : };
3858 :
3859 58 : const double dfSpacing = (dfMax - dfMin) / nIntervals;
3860 58 : if (dfSpacing > 0)
3861 : {
3862 47 : const double dfInvSpacing = 1.0 / dfSpacing;
3863 47 : if (IsAlmostInteger(dfInvSpacing))
3864 : {
3865 20 : const double dfRoundedSpacing =
3866 20 : 1.0 / std::round(dfInvSpacing);
3867 20 : const double dfMinDivRoundedSpacing =
3868 20 : dfMin / dfRoundedSpacing;
3869 20 : const double dfMaxDivRoundedSpacing =
3870 20 : dfMax / dfRoundedSpacing;
3871 40 : if (IsAlmostInteger(dfMinDivRoundedSpacing) &&
3872 20 : IsAlmostInteger(dfMaxDivRoundedSpacing))
3873 : {
3874 20 : const double dfRoundedMin =
3875 20 : std::round(dfMinDivRoundedSpacing) *
3876 : dfRoundedSpacing;
3877 20 : const double dfRoundedMax =
3878 20 : std::round(dfMaxDivRoundedSpacing) *
3879 : dfRoundedSpacing;
3880 20 : if (static_cast<float>(dfMin) ==
3881 20 : static_cast<float>(dfRoundedMin) &&
3882 8 : static_cast<float>(dfMax) ==
3883 8 : static_cast<float>(dfRoundedMax))
3884 : {
3885 7 : dfMin = dfRoundedMin;
3886 7 : dfMax = dfRoundedMax;
3887 : }
3888 : }
3889 : }
3890 : }
3891 58 : };
3892 :
3893 216 : if (!nc_get_att_double(nGroupDimXID, nVarDimXID, "actual_range",
3894 : adfActualRange))
3895 : {
3896 3 : xMinMax[0] = adfActualRange[0];
3897 3 : xMinMax[1] = adfActualRange[1];
3898 :
3899 : // Present xMinMax[] in the same order as padfXCoord
3900 3 : if ((xMinMax[0] - xMinMax[1]) *
3901 3 : (pdfXCoord[0] - pdfXCoord[xdim - 1]) <
3902 : 0)
3903 : {
3904 0 : std::swap(xMinMax[0], xMinMax[1]);
3905 : }
3906 : }
3907 : else
3908 : {
3909 213 : xMinMax[0] = pdfXCoord[0];
3910 213 : xMinMax[1] = pdfXCoord[xdim - 1];
3911 213 : node_offset = 0;
3912 :
3913 213 : if (nc_var_dimx_datatype == NC_FLOAT)
3914 : {
3915 29 : RoundMinMaxForFloatVals(xMinMax[0], xMinMax[1],
3916 29 : poDS->nRasterXSize - 1);
3917 : }
3918 : }
3919 :
3920 216 : if (!nc_get_att_double(nGroupDimYID, nVarDimYID, "actual_range",
3921 : adfActualRange))
3922 : {
3923 3 : yMinMax[0] = adfActualRange[0];
3924 3 : yMinMax[1] = adfActualRange[1];
3925 :
3926 : // Present yMinMax[] in the same order as pdfYCoord
3927 3 : if ((yMinMax[0] - yMinMax[1]) *
3928 3 : (pdfYCoord[0] - pdfYCoord[ydim - 1]) <
3929 : 0)
3930 : {
3931 1 : std::swap(yMinMax[0], yMinMax[1]);
3932 : }
3933 : }
3934 : else
3935 : {
3936 213 : yMinMax[0] = pdfYCoord[0];
3937 213 : yMinMax[1] = pdfYCoord[ydim - 1];
3938 213 : node_offset = 0;
3939 :
3940 213 : if (nc_var_dimy_datatype == NC_FLOAT)
3941 : {
3942 29 : RoundMinMaxForFloatVals(yMinMax[0], yMinMax[1],
3943 29 : poDS->nRasterYSize - 1);
3944 : }
3945 : }
3946 :
3947 216 : double dfCoordOffset = 0.0;
3948 216 : double dfCoordScale = 1.0;
3949 216 : if (!nc_get_att_double(nGroupId, nVarDimXID, CF_ADD_OFFSET,
3950 220 : &dfCoordOffset) &&
3951 4 : !nc_get_att_double(nGroupId, nVarDimXID, CF_SCALE_FACTOR,
3952 : &dfCoordScale))
3953 : {
3954 4 : xMinMax[0] = dfCoordOffset + xMinMax[0] * dfCoordScale;
3955 4 : xMinMax[1] = dfCoordOffset + xMinMax[1] * dfCoordScale;
3956 : }
3957 :
3958 216 : if (!nc_get_att_double(nGroupId, nVarDimYID, CF_ADD_OFFSET,
3959 220 : &dfCoordOffset) &&
3960 4 : !nc_get_att_double(nGroupId, nVarDimYID, CF_SCALE_FACTOR,
3961 : &dfCoordScale))
3962 : {
3963 4 : yMinMax[0] = dfCoordOffset + yMinMax[0] * dfCoordScale;
3964 4 : yMinMax[1] = dfCoordOffset + yMinMax[1] * dfCoordScale;
3965 : }
3966 :
3967 : // Check for reverse order of y-coordinate.
3968 216 : if (!bSwitchedXY)
3969 : {
3970 214 : poDS->bBottomUp = (yMinMax[0] <= yMinMax[1]);
3971 214 : CPLDebug("GDAL_netCDF", "set bBottomUp = %d from Y axis",
3972 214 : static_cast<int>(poDS->bBottomUp));
3973 214 : if (!poDS->bBottomUp)
3974 : {
3975 32 : std::swap(yMinMax[0], yMinMax[1]);
3976 : }
3977 : }
3978 :
3979 : // Geostationary satellites can specify units in (micro)radians
3980 : // So we check if they do, and if so convert to linear units
3981 : // (meters)
3982 216 : const char *pszProjName = oSRS.GetAttrValue("PROJECTION");
3983 216 : if (pszProjName != nullptr)
3984 : {
3985 24 : if (EQUAL(pszProjName, SRS_PT_GEOSTATIONARY_SATELLITE))
3986 : {
3987 : double satelliteHeight =
3988 3 : oSRS.GetProjParm(SRS_PP_SATELLITE_HEIGHT, 1.0);
3989 3 : size_t nAttlen = 0;
3990 : char szUnits[NC_MAX_NAME + 1];
3991 3 : szUnits[0] = '\0';
3992 3 : nc_type nAttype = NC_NAT;
3993 3 : nc_inq_att(nGroupId, nVarDimXID, "units", &nAttype,
3994 : &nAttlen);
3995 6 : if (nAttlen < sizeof(szUnits) &&
3996 3 : nc_get_att_text(nGroupId, nVarDimXID, "units",
3997 : szUnits) == NC_NOERR)
3998 : {
3999 3 : szUnits[nAttlen] = '\0';
4000 3 : if (EQUAL(szUnits, "microradian"))
4001 : {
4002 1 : xMinMax[0] =
4003 1 : xMinMax[0] * satelliteHeight * 0.000001;
4004 1 : xMinMax[1] =
4005 1 : xMinMax[1] * satelliteHeight * 0.000001;
4006 : }
4007 2 : else if (EQUAL(szUnits, "rad") ||
4008 1 : EQUAL(szUnits, "radian"))
4009 : {
4010 2 : xMinMax[0] = xMinMax[0] * satelliteHeight;
4011 2 : xMinMax[1] = xMinMax[1] * satelliteHeight;
4012 : }
4013 : }
4014 3 : szUnits[0] = '\0';
4015 3 : nc_inq_att(nGroupId, nVarDimYID, "units", &nAttype,
4016 : &nAttlen);
4017 6 : if (nAttlen < sizeof(szUnits) &&
4018 3 : nc_get_att_text(nGroupId, nVarDimYID, "units",
4019 : szUnits) == NC_NOERR)
4020 : {
4021 3 : szUnits[nAttlen] = '\0';
4022 3 : if (EQUAL(szUnits, "microradian"))
4023 : {
4024 1 : yMinMax[0] =
4025 1 : yMinMax[0] * satelliteHeight * 0.000001;
4026 1 : yMinMax[1] =
4027 1 : yMinMax[1] * satelliteHeight * 0.000001;
4028 : }
4029 2 : else if (EQUAL(szUnits, "rad") ||
4030 1 : EQUAL(szUnits, "radian"))
4031 : {
4032 2 : yMinMax[0] = yMinMax[0] * satelliteHeight;
4033 2 : yMinMax[1] = yMinMax[1] * satelliteHeight;
4034 : }
4035 : }
4036 : }
4037 : }
4038 :
4039 216 : tmpGT[0] = xMinMax[0];
4040 432 : tmpGT[1] = (xMinMax[1] - xMinMax[0]) /
4041 216 : (poDS->nRasterXSize + (node_offset - 1));
4042 216 : tmpGT[2] = 0;
4043 216 : if (bSwitchedXY)
4044 : {
4045 2 : tmpGT[3] = yMinMax[0];
4046 2 : tmpGT[4] = 0;
4047 2 : tmpGT[5] = (yMinMax[1] - yMinMax[0]) /
4048 2 : (poDS->nRasterYSize + (node_offset - 1));
4049 : }
4050 : else
4051 : {
4052 214 : tmpGT[3] = yMinMax[1];
4053 214 : tmpGT[4] = 0;
4054 214 : tmpGT[5] = (yMinMax[0] - yMinMax[1]) /
4055 214 : (poDS->nRasterYSize + (node_offset - 1));
4056 : }
4057 :
4058 : // Compute the center of the pixel.
4059 216 : if (!node_offset)
4060 : {
4061 : // Otherwise its already the pixel center.
4062 216 : tmpGT[0] -= (tmpGT[1] / 2);
4063 216 : tmpGT[3] -= (tmpGT[5] / 2);
4064 : }
4065 : }
4066 :
4067 : const auto AreSRSEqualThroughProj4String =
4068 2 : [](const OGRSpatialReference &oSRS1,
4069 : const OGRSpatialReference &oSRS2)
4070 : {
4071 2 : char *pszProj4Str1 = nullptr;
4072 2 : oSRS1.exportToProj4(&pszProj4Str1);
4073 :
4074 2 : char *pszProj4Str2 = nullptr;
4075 2 : oSRS2.exportToProj4(&pszProj4Str2);
4076 :
4077 : {
4078 2 : char *pszTmp = strstr(pszProj4Str1, "+datum=");
4079 2 : if (pszTmp)
4080 0 : memcpy(pszTmp, "+ellps=", strlen("+ellps="));
4081 : }
4082 :
4083 : {
4084 2 : char *pszTmp = strstr(pszProj4Str2, "+datum=");
4085 2 : if (pszTmp)
4086 2 : memcpy(pszTmp, "+ellps=", strlen("+ellps="));
4087 : }
4088 :
4089 2 : bool bRet = false;
4090 2 : if (pszProj4Str1 && pszProj4Str2 &&
4091 2 : EQUAL(pszProj4Str1, pszProj4Str2))
4092 : {
4093 1 : bRet = true;
4094 : }
4095 :
4096 2 : CPLFree(pszProj4Str1);
4097 2 : CPLFree(pszProj4Str2);
4098 2 : return bRet;
4099 : };
4100 :
4101 223 : if (dfLinearUnitsConvFactor != 1.0)
4102 : {
4103 35 : for (int i = 0; i < 6; ++i)
4104 30 : tmpGT[i] *= dfLinearUnitsConvFactor;
4105 :
4106 5 : if (paosRemovedMDItems)
4107 : {
4108 : char szVarNameX[NC_MAX_NAME + 1];
4109 5 : CPL_IGNORE_RET_VAL(
4110 5 : nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
4111 :
4112 : char szVarNameY[NC_MAX_NAME + 1];
4113 5 : CPL_IGNORE_RET_VAL(
4114 5 : nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
4115 :
4116 5 : paosRemovedMDItems->push_back(
4117 : CPLSPrintf("%s#units", szVarNameX));
4118 5 : paosRemovedMDItems->push_back(
4119 : CPLSPrintf("%s#units", szVarNameY));
4120 : }
4121 : }
4122 :
4123 : // If there is a global "geospatial_bounds_crs" attribute, check that it
4124 : // is consistent with the SRS, and if so, use it as the SRS
4125 : const char *pszGBCRS =
4126 223 : FetchAttr(nGroupId, NC_GLOBAL, "geospatial_bounds_crs");
4127 223 : if (pszGBCRS && STARTS_WITH(pszGBCRS, "EPSG:"))
4128 : {
4129 4 : OGRSpatialReference oSRSFromGBCRS;
4130 2 : oSRSFromGBCRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
4131 2 : if (oSRSFromGBCRS.SetFromUserInput(
4132 : pszGBCRS,
4133 : OGRSpatialReference::
4134 4 : SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE &&
4135 2 : AreSRSEqualThroughProj4String(oSRS, oSRSFromGBCRS))
4136 : {
4137 1 : oSRS = std::move(oSRSFromGBCRS);
4138 1 : SetSpatialRefNoUpdate(&oSRS);
4139 : }
4140 : }
4141 :
4142 223 : CPLFree(pdfXCoord);
4143 223 : CPLFree(pdfYCoord);
4144 : } // end if(has dims)
4145 :
4146 : // Process custom GeoTransform GDAL value.
4147 522 : if (!EQUAL(pszGridMappingValue, ""))
4148 : {
4149 220 : if (pszGeoTransform != nullptr)
4150 : {
4151 : CPLStringList aosGeoTransform(
4152 222 : CSLTokenizeString2(pszGeoTransform, " ", CSLT_HONOURSTRINGS));
4153 111 : if (aosGeoTransform.size() == 6)
4154 : {
4155 111 : GDALGeoTransform gtFromAttribute;
4156 777 : for (int i = 0; i < 6; i++)
4157 : {
4158 666 : gtFromAttribute[i] = CPLAtof(aosGeoTransform[i]);
4159 : }
4160 :
4161 111 : if (bGotCfGT)
4162 : {
4163 94 : constexpr double GT_RELERROR_WARN_THRESHOLD = 1e-6;
4164 94 : double dfMaxAbsoluteError = 0.0;
4165 658 : for (int i = 0; i < 6; i++)
4166 : {
4167 : double dfAbsoluteError =
4168 564 : std::abs(tmpGT[i] - gtFromAttribute[i]);
4169 564 : if (dfAbsoluteError >
4170 564 : std::abs(gtFromAttribute[i] *
4171 : GT_RELERROR_WARN_THRESHOLD))
4172 : {
4173 2 : dfMaxAbsoluteError =
4174 2 : std::max(dfMaxAbsoluteError, dfAbsoluteError);
4175 : }
4176 : }
4177 :
4178 94 : if (dfMaxAbsoluteError > 0)
4179 : {
4180 2 : CPLError(CE_Warning, CPLE_AppDefined,
4181 : "GeoTransform read from attribute of %s "
4182 : "variable differs from value calculated from "
4183 : "dimension variables (max diff = %g). Using "
4184 : "value from attribute.",
4185 : pszGridMappingValue, dfMaxAbsoluteError);
4186 : }
4187 : }
4188 :
4189 111 : tmpGT = std::move(gtFromAttribute);
4190 111 : bGotGdalGT = true;
4191 : }
4192 : }
4193 : else
4194 : {
4195 : // Look for corner array values.
4196 : // CPLDebug("GDAL_netCDF",
4197 : // "looking for geotransform corners");
4198 109 : bool bGotNN = false;
4199 109 : double dfNN = FetchCopyParam(pszGridMappingValue,
4200 : "Northernmost_Northing", 0, &bGotNN);
4201 :
4202 109 : bool bGotSN = false;
4203 109 : double dfSN = FetchCopyParam(pszGridMappingValue,
4204 : "Southernmost_Northing", 0, &bGotSN);
4205 :
4206 109 : bool bGotEE = false;
4207 109 : double dfEE = FetchCopyParam(pszGridMappingValue,
4208 : "Easternmost_Easting", 0, &bGotEE);
4209 :
4210 109 : bool bGotWE = false;
4211 109 : double dfWE = FetchCopyParam(pszGridMappingValue,
4212 : "Westernmost_Easting", 0, &bGotWE);
4213 :
4214 : // Only set the GeoTransform if we got all the values.
4215 109 : if (bGotNN && bGotSN && bGotEE && bGotWE)
4216 : {
4217 0 : bGotGdalGT = true;
4218 :
4219 0 : tmpGT[0] = dfWE;
4220 0 : tmpGT[1] = (dfEE - dfWE) / (poDS->GetRasterXSize() - 1);
4221 0 : tmpGT[2] = 0.0;
4222 0 : tmpGT[3] = dfNN;
4223 0 : tmpGT[4] = 0.0;
4224 0 : tmpGT[5] = (dfSN - dfNN) / (poDS->GetRasterYSize() - 1);
4225 : // Compute the center of the pixel.
4226 0 : tmpGT[0] = dfWE - (tmpGT[1] / 2);
4227 0 : tmpGT[3] = dfNN - (tmpGT[5] / 2);
4228 : }
4229 : } // (pszGeoTransform != NULL)
4230 :
4231 220 : if (bGotGdalSRS && !bGotGdalGT)
4232 74 : CPLDebug("GDAL_netCDF", "Got SRS but no geotransform from GDAL!");
4233 : }
4234 :
4235 522 : if (!pszWKT && !bGotCfSRS)
4236 : {
4237 : // Some netCDF files have a srid attribute (#6613) like
4238 : // urn:ogc:def:crs:EPSG::6931
4239 302 : const char *pszSRID = FetchAttr(pszGridMappingValue, "srid");
4240 302 : if (pszSRID != nullptr)
4241 : {
4242 0 : oSRS.Clear();
4243 0 : if (oSRS.SetFromUserInput(
4244 : pszSRID,
4245 : OGRSpatialReference::
4246 0 : SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE)
4247 : {
4248 0 : char *pszWKTExport = nullptr;
4249 0 : CPLDebug("GDAL_netCDF", "Got SRS from %s", pszSRID);
4250 0 : oSRS.exportToWkt(&pszWKTExport);
4251 0 : if (returnProjStr != nullptr)
4252 : {
4253 0 : (*returnProjStr) = std::string(pszWKTExport);
4254 : }
4255 : else
4256 : {
4257 0 : m_bAddedProjectionVarsDefs = true;
4258 0 : m_bAddedProjectionVarsData = true;
4259 0 : SetSpatialRefNoUpdate(&oSRS);
4260 : }
4261 0 : CPLFree(pszWKTExport);
4262 : }
4263 : }
4264 : }
4265 :
4266 522 : CPLFree(pszGridMappingValue);
4267 :
4268 522 : if (bReadSRSOnly)
4269 172 : return;
4270 :
4271 : // Determines the SRS to be used by the geolocation array, if any
4272 700 : std::string osGeolocWKT = SRS_WKT_WGS84_LAT_LONG;
4273 350 : if (!m_oSRS.IsEmpty())
4274 : {
4275 262 : OGRSpatialReference oGeogCRS;
4276 131 : oGeogCRS.CopyGeogCSFrom(&m_oSRS);
4277 131 : char *pszWKTTmp = nullptr;
4278 131 : const char *const apszOptions[] = {"FORMAT=WKT2_2019", nullptr};
4279 131 : if (oGeogCRS.exportToWkt(&pszWKTTmp, apszOptions) == OGRERR_NONE)
4280 : {
4281 131 : osGeolocWKT = pszWKTTmp;
4282 : }
4283 131 : CPLFree(pszWKTTmp);
4284 : }
4285 :
4286 : // Process geolocation arrays from CF "coordinates" attribute.
4287 700 : std::string osGeolocXName, osGeolocYName;
4288 350 : if (ProcessCFGeolocation(nGroupId, nVarId, osGeolocWKT, osGeolocXName,
4289 350 : osGeolocYName))
4290 : {
4291 53 : bool bCanCancelGT = true;
4292 53 : if ((nVarDimXID != -1) && (nVarDimYID != -1))
4293 : {
4294 : char szVarNameX[NC_MAX_NAME + 1];
4295 44 : CPL_IGNORE_RET_VAL(
4296 44 : nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
4297 : char szVarNameY[NC_MAX_NAME + 1];
4298 44 : CPL_IGNORE_RET_VAL(
4299 44 : nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
4300 44 : bCanCancelGT =
4301 44 : !(osGeolocXName == szVarNameX && osGeolocYName == szVarNameY);
4302 : }
4303 88 : if (bCanCancelGT && !m_oSRS.IsGeographic() && !m_oSRS.IsProjected() &&
4304 35 : !bSwitchedXY)
4305 : {
4306 33 : bGotCfGT = false;
4307 : }
4308 : }
4309 121 : else if (!bGotCfGT && !bReadSRSOnly && (nVarDimXID != -1) &&
4310 421 : (nVarDimYID != -1) && xdim > 0 && ydim > 0 &&
4311 3 : ((!bSwitchedXY &&
4312 3 : NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
4313 1 : NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr)) ||
4314 2 : (bSwitchedXY &&
4315 0 : NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
4316 0 : NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr))))
4317 : {
4318 : // Case of autotest/gdrivers/data/netcdf/GLMELT_4X5.OCN.nc
4319 : // which is indexed by lat, lon variables, but lat has irregular
4320 : // spacing.
4321 1 : const char *pszGeolocXFullName = poDS->papszDimName[poDS->nXDimID];
4322 1 : const char *pszGeolocYFullName = poDS->papszDimName[poDS->nYDimID];
4323 1 : if (bSwitchedXY)
4324 : {
4325 0 : std::swap(pszGeolocXFullName, pszGeolocYFullName);
4326 0 : GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", "GEOLOCATION");
4327 : }
4328 :
4329 1 : CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
4330 : pszGeolocXFullName, pszGeolocYFullName);
4331 :
4332 1 : GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
4333 : "GEOLOCATION");
4334 :
4335 2 : CPLString osTMP;
4336 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4337 1 : pszGeolocXFullName);
4338 :
4339 1 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
4340 1 : GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
4341 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4342 1 : pszGeolocYFullName);
4343 :
4344 1 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
4345 1 : GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
4346 :
4347 1 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
4348 1 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
4349 :
4350 1 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
4351 1 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
4352 :
4353 1 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
4354 : "PIXEL_CENTER", "GEOLOCATION");
4355 : }
4356 :
4357 : // Set GeoTransform if we got a complete one - after projection has been set
4358 350 : if (bGotCfGT || bGotGdalGT)
4359 : {
4360 198 : m_bAddedProjectionVarsDefs = true;
4361 198 : m_bAddedProjectionVarsData = true;
4362 198 : SetGeoTransformNoUpdate(tmpGT);
4363 : }
4364 :
4365 : // Debugging reports.
4366 350 : CPLDebug("GDAL_netCDF",
4367 : "bGotGeogCS=%d bGotCfSRS=%d bGotCfGT=%d bGotCfWktSRS=%d "
4368 : "bGotGdalSRS=%d bGotGdalGT=%d",
4369 : static_cast<int>(bGotGeogCS), static_cast<int>(bGotCfSRS),
4370 : static_cast<int>(bGotCfGT), static_cast<int>(bGotCfWktSRS),
4371 : static_cast<int>(bGotGdalSRS), static_cast<int>(bGotGdalGT));
4372 :
4373 350 : if (!bGotCfGT && !bGotGdalGT)
4374 152 : CPLDebug("GDAL_netCDF", "did not get geotransform from CF nor GDAL!");
4375 :
4376 350 : if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfGT && !bGotCfWktSRS)
4377 152 : CPLDebug("GDAL_netCDF", "did not get projection from CF nor GDAL!");
4378 :
4379 : // wish of 6195
4380 : // we don't have CS/SRS, but we do have GT, and we live in -180,360 -90,90
4381 350 : if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfWktSRS)
4382 : {
4383 219 : if (bGotCfGT || bGotGdalGT)
4384 : {
4385 134 : bool bAssumedLongLat = CPLTestBool(CSLFetchNameValueDef(
4386 67 : papszOpenOptions, "ASSUME_LONGLAT",
4387 : CPLGetConfigOption("GDAL_NETCDF_ASSUME_LONGLAT", "NO")));
4388 :
4389 2 : if (bAssumedLongLat && tmpGT[0] >= -180 && tmpGT[0] < 360 &&
4390 2 : (tmpGT[0] + tmpGT[1] * poDS->GetRasterXSize()) <= 360 &&
4391 71 : tmpGT[3] <= 90 && tmpGT[3] > -90 &&
4392 2 : (tmpGT[3] + tmpGT[5] * poDS->GetRasterYSize()) >= -90)
4393 : {
4394 :
4395 2 : poDS->bIsGeographic = true;
4396 2 : char *pszTempProjection = nullptr;
4397 : // seems odd to use 4326 so OGC:CRS84
4398 2 : oSRS.SetFromUserInput("OGC:CRS84");
4399 2 : oSRS.exportToWkt(&pszTempProjection);
4400 2 : if (returnProjStr != nullptr)
4401 : {
4402 0 : (*returnProjStr) = std::string(pszTempProjection);
4403 : }
4404 : else
4405 : {
4406 2 : m_bAddedProjectionVarsDefs = true;
4407 2 : m_bAddedProjectionVarsData = true;
4408 2 : SetSpatialRefNoUpdate(&oSRS);
4409 : }
4410 2 : CPLFree(pszTempProjection);
4411 :
4412 2 : CPLDebug("netCDF",
4413 : "Assumed Longitude Latitude CRS 'OGC:CRS84' because "
4414 : "none otherwise available and geotransform within "
4415 : "suitable bounds. "
4416 : "Set GDAL_NETCDF_ASSUME_LONGLAT=NO as configuration "
4417 : "option or "
4418 : " ASSUME_LONGLAT=NO as open option to bypass this "
4419 : "assumption.");
4420 : }
4421 : }
4422 : }
4423 :
4424 : // Search for Well-known GeogCS if got only CF WKT
4425 : // Disabled for now, as a named datum also include control points
4426 : // (see mailing list and bug#4281
4427 : // For example, WGS84 vs. GDA94 (EPSG:3577) - AEA in netcdf_cf.py
4428 :
4429 : // Disabled for now, but could be set in a config option.
4430 : #if 0
4431 : bool bLookForWellKnownGCS = false; // This could be a Config Option.
4432 :
4433 : if( bLookForWellKnownGCS && bGotCfSRS && !bGotGdalSRS )
4434 : {
4435 : // ET - Could use a more exhaustive method by scanning all EPSG codes in
4436 : // data/gcs.csv as proposed by Even in the gdal-dev mailing list "help
4437 : // for comparing two WKT".
4438 : // This code could be contributed to a new function.
4439 : // OGRSpatialReference * OGRSpatialReference::FindMatchingGeogCS(
4440 : // const OGRSpatialReference *poOther) */
4441 : CPLDebug("GDAL_netCDF", "Searching for Well-known GeogCS");
4442 : const char *pszWKGCSList[] = { "WGS84", "WGS72", "NAD27", "NAD83" };
4443 : char *pszWKGCS = NULL;
4444 : oSRS.exportToPrettyWkt(&pszWKGCS);
4445 : for( size_t i = 0; i < sizeof(pszWKGCSList) / 8; i++ )
4446 : {
4447 : pszWKGCS = CPLStrdup(pszWKGCSList[i]);
4448 : OGRSpatialReference oSRSTmp;
4449 : oSRSTmp.SetWellKnownGeogCS(pszWKGCSList[i]);
4450 : // Set datum to unknown, bug #4281.
4451 : if( oSRSTmp.GetAttrNode("DATUM" ) )
4452 : oSRSTmp.GetAttrNode("DATUM")->GetChild(0)->SetValue("unknown");
4453 : // Could use OGRSpatialReference::StripCTParms(), but let's keep
4454 : // TOWGS84.
4455 : oSRSTmp.GetRoot()->StripNodes("AXIS");
4456 : oSRSTmp.GetRoot()->StripNodes("AUTHORITY");
4457 : oSRSTmp.GetRoot()->StripNodes("EXTENSION");
4458 :
4459 : oSRSTmp.exportToPrettyWkt(&pszWKGCS);
4460 : if( oSRS.IsSameGeogCS(&oSRSTmp) )
4461 : {
4462 : oSRS.SetWellKnownGeogCS(pszWKGCSList[i]);
4463 : oSRS.exportToWkt(&(pszTempProjection));
4464 : SetProjection(pszTempProjection);
4465 : CPLFree(pszTempProjection);
4466 : }
4467 : }
4468 : }
4469 : #endif
4470 : }
4471 :
4472 129 : void netCDFDataset::SetProjectionFromVar(int nGroupId, int nVarId,
4473 : bool bReadSRSOnly)
4474 : {
4475 129 : SetProjectionFromVar(nGroupId, nVarId, bReadSRSOnly, nullptr, nullptr,
4476 : nullptr, nullptr);
4477 129 : }
4478 :
4479 286 : bool netCDFDataset::ProcessNASAL2OceanGeoLocation(int nGroupId, int nVarId)
4480 : {
4481 : // Cf https://oceancolor.gsfc.nasa.gov/docs/format/l2nc/
4482 : // and https://github.com/OSGeo/gdal/issues/7605
4483 :
4484 : // Check for a structure like:
4485 : /* netcdf SNPP_VIIRS.20230406T024200.L2.OC.NRT {
4486 : dimensions:
4487 : number_of_lines = 3248 ;
4488 : pixels_per_line = 3200 ;
4489 : [...]
4490 : pixel_control_points = 3200 ;
4491 : [...]
4492 : group: geophysical_data {
4493 : variables:
4494 : short aot_862(number_of_lines, pixels_per_line) ; <-- nVarId
4495 : [...]
4496 : }
4497 : group: navigation_data {
4498 : variables:
4499 : float longitude(number_of_lines, pixel_control_points) ;
4500 : [...]
4501 : float latitude(number_of_lines, pixel_control_points) ;
4502 : [...]
4503 : }
4504 : }
4505 : */
4506 : // Note that the longitude and latitude arrays are not indexed by the
4507 : // same dimensions. Handle only the case where
4508 : // pixel_control_points == pixels_per_line
4509 : // If there was a subsampling of the geolocation arrays, we'd need to
4510 : // add more logic.
4511 :
4512 572 : std::string osGroupName;
4513 286 : osGroupName.resize(NC_MAX_NAME);
4514 286 : NCDF_ERR(nc_inq_grpname(nGroupId, &osGroupName[0]));
4515 286 : osGroupName.resize(strlen(osGroupName.data()));
4516 286 : if (osGroupName != "geophysical_data")
4517 285 : return false;
4518 :
4519 1 : int nVarDims = 0;
4520 1 : NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
4521 1 : if (nVarDims != 2)
4522 0 : return false;
4523 :
4524 1 : int nNavigationDataGrpId = 0;
4525 1 : if (nc_inq_grp_ncid(cdfid, "navigation_data", &nNavigationDataGrpId) !=
4526 : NC_NOERR)
4527 0 : return false;
4528 :
4529 : std::array<int, 2> anVarDimIds;
4530 1 : NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
4531 :
4532 1 : int nLongitudeId = 0;
4533 1 : int nLatitudeId = 0;
4534 1 : if (nc_inq_varid(nNavigationDataGrpId, "longitude", &nLongitudeId) !=
4535 2 : NC_NOERR ||
4536 1 : nc_inq_varid(nNavigationDataGrpId, "latitude", &nLatitudeId) !=
4537 : NC_NOERR)
4538 : {
4539 0 : return false;
4540 : }
4541 :
4542 1 : int nDimsLongitude = 0;
4543 1 : NCDF_ERR(
4544 : nc_inq_varndims(nNavigationDataGrpId, nLongitudeId, &nDimsLongitude));
4545 1 : int nDimsLatitude = 0;
4546 1 : NCDF_ERR(
4547 : nc_inq_varndims(nNavigationDataGrpId, nLatitudeId, &nDimsLatitude));
4548 1 : if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
4549 : {
4550 0 : return false;
4551 : }
4552 :
4553 : std::array<int, 2> anDimLongitudeIds;
4554 1 : NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLongitudeId,
4555 : anDimLongitudeIds.data()));
4556 : std::array<int, 2> anDimLatitudeIds;
4557 1 : NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLatitudeId,
4558 : anDimLatitudeIds.data()));
4559 1 : if (anDimLongitudeIds != anDimLatitudeIds)
4560 : {
4561 0 : return false;
4562 : }
4563 :
4564 : std::array<size_t, 2> anSizeVarDimIds;
4565 : std::array<size_t, 2> anSizeLongLatIds;
4566 2 : if (!(nc_inq_dimlen(cdfid, anVarDimIds[0], &anSizeVarDimIds[0]) ==
4567 1 : NC_NOERR &&
4568 1 : nc_inq_dimlen(cdfid, anVarDimIds[1], &anSizeVarDimIds[1]) ==
4569 1 : NC_NOERR &&
4570 1 : nc_inq_dimlen(cdfid, anDimLongitudeIds[0], &anSizeLongLatIds[0]) ==
4571 1 : NC_NOERR &&
4572 1 : nc_inq_dimlen(cdfid, anDimLongitudeIds[1], &anSizeLongLatIds[1]) ==
4573 : NC_NOERR &&
4574 1 : anSizeVarDimIds == anSizeLongLatIds))
4575 : {
4576 0 : return false;
4577 : }
4578 :
4579 1 : const char *pszGeolocXFullName = "/navigation_data/longitude";
4580 1 : const char *pszGeolocYFullName = "/navigation_data/latitude";
4581 :
4582 1 : if (bSwitchedXY)
4583 : {
4584 0 : std::swap(pszGeolocXFullName, pszGeolocYFullName);
4585 0 : GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", "GEOLOCATION");
4586 : }
4587 :
4588 1 : CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
4589 : pszGeolocXFullName, pszGeolocYFullName);
4590 :
4591 1 : GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
4592 : "GEOLOCATION");
4593 :
4594 1 : CPLString osTMP;
4595 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
4596 :
4597 1 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
4598 1 : GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
4599 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
4600 :
4601 1 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
4602 1 : GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
4603 :
4604 1 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
4605 1 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
4606 :
4607 1 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
4608 1 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
4609 :
4610 1 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
4611 : "GEOLOCATION");
4612 1 : return true;
4613 : }
4614 :
4615 285 : bool netCDFDataset::ProcessNASAEMITGeoLocation(int nGroupId, int nVarId)
4616 : {
4617 : // Cf https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/
4618 :
4619 : // Check for a structure like:
4620 : /* netcdf EMIT_L2A_RFL_001_20220903T163129_2224611_012 {
4621 : dimensions:
4622 : downtrack = 1280 ;
4623 : crosstrack = 1242 ;
4624 : bands = 285 ;
4625 : [...]
4626 :
4627 : variables:
4628 : float reflectance(downtrack, crosstrack, bands) ;
4629 :
4630 : group: location {
4631 : variables:
4632 : double lon(downtrack, crosstrack) ;
4633 : lon:_FillValue = -9999. ;
4634 : lon:long_name = "Longitude (WGS-84)" ;
4635 : lon:units = "degrees east" ;
4636 : double lat(downtrack, crosstrack) ;
4637 : lat:_FillValue = -9999. ;
4638 : lat:long_name = "Latitude (WGS-84)" ;
4639 : lat:units = "degrees north" ;
4640 : } // group location
4641 :
4642 : }
4643 : or
4644 : netcdf EMIT_L2B_MIN_001_20231024T055538_2329704_040 {
4645 : dimensions:
4646 : downtrack = 1664 ;
4647 : crosstrack = 1242 ;
4648 : [...]
4649 : variables:
4650 : float group_1_band_depth(downtrack, crosstrack) ;
4651 : group_1_band_depth:_FillValue = -9999.f ;
4652 : group_1_band_depth:long_name = "Group 1 Band Depth" ;
4653 : group_1_band_depth:units = "unitless" ;
4654 : [...]
4655 : group: location {
4656 : variables:
4657 : double lon(downtrack, crosstrack) ;
4658 : lon:_FillValue = -9999. ;
4659 : lon:long_name = "Longitude (WGS-84)" ;
4660 : lon:units = "degrees east" ;
4661 : double lat(downtrack, crosstrack) ;
4662 : lat:_FillValue = -9999. ;
4663 : lat:long_name = "Latitude (WGS-84)" ;
4664 : lat:units = "degrees north" ;
4665 : }
4666 : */
4667 :
4668 285 : int nVarDims = 0;
4669 285 : NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
4670 285 : if (nVarDims != 2 && nVarDims != 3)
4671 14 : return false;
4672 :
4673 271 : int nLocationGrpId = 0;
4674 271 : if (nc_inq_grp_ncid(cdfid, "location", &nLocationGrpId) != NC_NOERR)
4675 58 : return false;
4676 :
4677 : std::array<int, 3> anVarDimIds;
4678 213 : NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
4679 213 : if (nYDimID != anVarDimIds[0] || nXDimID != anVarDimIds[1])
4680 21 : return false;
4681 :
4682 192 : int nLongitudeId = 0;
4683 192 : int nLatitudeId = 0;
4684 230 : if (nc_inq_varid(nLocationGrpId, "lon", &nLongitudeId) != NC_NOERR ||
4685 38 : nc_inq_varid(nLocationGrpId, "lat", &nLatitudeId) != NC_NOERR)
4686 : {
4687 154 : return false;
4688 : }
4689 :
4690 38 : int nDimsLongitude = 0;
4691 38 : NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLongitudeId, &nDimsLongitude));
4692 38 : int nDimsLatitude = 0;
4693 38 : NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLatitudeId, &nDimsLatitude));
4694 38 : if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
4695 : {
4696 34 : return false;
4697 : }
4698 :
4699 : std::array<int, 2> anDimLongitudeIds;
4700 4 : NCDF_ERR(nc_inq_vardimid(nLocationGrpId, nLongitudeId,
4701 : anDimLongitudeIds.data()));
4702 : std::array<int, 2> anDimLatitudeIds;
4703 4 : NCDF_ERR(
4704 : nc_inq_vardimid(nLocationGrpId, nLatitudeId, anDimLatitudeIds.data()));
4705 4 : if (anDimLongitudeIds != anDimLatitudeIds)
4706 : {
4707 0 : return false;
4708 : }
4709 :
4710 8 : if (anDimLongitudeIds[0] != anVarDimIds[0] ||
4711 4 : anDimLongitudeIds[1] != anVarDimIds[1])
4712 : {
4713 0 : return false;
4714 : }
4715 :
4716 4 : const char *pszGeolocXFullName = "/location/lon";
4717 4 : const char *pszGeolocYFullName = "/location/lat";
4718 :
4719 4 : CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
4720 : pszGeolocXFullName, pszGeolocYFullName);
4721 :
4722 4 : GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
4723 : "GEOLOCATION");
4724 :
4725 4 : CPLString osTMP;
4726 4 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
4727 :
4728 4 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
4729 4 : GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
4730 4 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
4731 :
4732 4 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
4733 4 : GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
4734 :
4735 4 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
4736 4 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
4737 :
4738 4 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
4739 4 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
4740 :
4741 4 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
4742 : "GEOLOCATION");
4743 4 : return true;
4744 : }
4745 :
4746 350 : int netCDFDataset::ProcessCFGeolocation(int nGroupId, int nVarId,
4747 : const std::string &osGeolocWKT,
4748 : std::string &osGeolocXNameOut,
4749 : std::string &osGeolocYNameOut)
4750 : {
4751 350 : bool bAddGeoloc = false;
4752 350 : char *pszCoordinates = nullptr;
4753 :
4754 : // If there is no explicit "coordinates" attribute, check if there are
4755 : // "lon" and "lat" 2D variables whose dimensions are the last
4756 : // 2 ones of the variable of interest.
4757 350 : if (NCDFGetAttr(nGroupId, nVarId, "coordinates", &pszCoordinates) !=
4758 : CE_None)
4759 : {
4760 303 : CPLFree(pszCoordinates);
4761 303 : pszCoordinates = nullptr;
4762 :
4763 303 : int nVarDims = 0;
4764 303 : NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
4765 303 : if (nVarDims >= 2)
4766 : {
4767 606 : std::vector<int> anVarDimIds(nVarDims);
4768 303 : NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
4769 :
4770 303 : int nLongitudeId = 0;
4771 303 : int nLatitudeId = 0;
4772 371 : if (nc_inq_varid(nGroupId, "lon", &nLongitudeId) == NC_NOERR &&
4773 68 : nc_inq_varid(nGroupId, "lat", &nLatitudeId) == NC_NOERR)
4774 : {
4775 68 : int nDimsLongitude = 0;
4776 68 : NCDF_ERR(
4777 : nc_inq_varndims(nGroupId, nLongitudeId, &nDimsLongitude));
4778 68 : int nDimsLatitude = 0;
4779 68 : NCDF_ERR(
4780 : nc_inq_varndims(nGroupId, nLatitudeId, &nDimsLatitude));
4781 68 : if (nDimsLongitude == 2 && nDimsLatitude == 2)
4782 : {
4783 34 : std::vector<int> anDimLongitudeIds(2);
4784 17 : NCDF_ERR(nc_inq_vardimid(nGroupId, nLongitudeId,
4785 : anDimLongitudeIds.data()));
4786 34 : std::vector<int> anDimLatitudeIds(2);
4787 17 : NCDF_ERR(nc_inq_vardimid(nGroupId, nLatitudeId,
4788 : anDimLatitudeIds.data()));
4789 17 : if (anDimLongitudeIds == anDimLatitudeIds &&
4790 34 : anVarDimIds[anVarDimIds.size() - 2] ==
4791 51 : anDimLongitudeIds[0] &&
4792 34 : anVarDimIds[anVarDimIds.size() - 1] ==
4793 17 : anDimLongitudeIds[1])
4794 : {
4795 17 : pszCoordinates = CPLStrdup("lon lat");
4796 : }
4797 : }
4798 : }
4799 : }
4800 : }
4801 :
4802 350 : if (pszCoordinates)
4803 : {
4804 : // Get X and Y geolocation names from coordinates attribute.
4805 : const CPLStringList aosCoordinates(
4806 128 : NCDFTokenizeCoordinatesAttribute(pszCoordinates));
4807 64 : if (aosCoordinates.size() >= 2)
4808 : {
4809 : char szGeolocXName[NC_MAX_NAME + 1];
4810 : char szGeolocYName[NC_MAX_NAME + 1];
4811 61 : szGeolocXName[0] = '\0';
4812 61 : szGeolocYName[0] = '\0';
4813 :
4814 : // Test that each variable is longitude/latitude.
4815 196 : for (int i = 0; i < aosCoordinates.size(); i++)
4816 : {
4817 135 : if (NCDFIsVarLongitude(nGroupId, -1, aosCoordinates[i]))
4818 : {
4819 50 : int nOtherGroupId = -1;
4820 50 : int nOtherVarId = -1;
4821 : // Check that the variable actually exists
4822 : // Needed on Sentinel-3 products
4823 50 : if (NCDFResolveVar(nGroupId, aosCoordinates[i],
4824 50 : &nOtherGroupId, &nOtherVarId) == CE_None)
4825 : {
4826 48 : snprintf(szGeolocXName, sizeof(szGeolocXName), "%s",
4827 : aosCoordinates[i]);
4828 : }
4829 : }
4830 85 : else if (NCDFIsVarLatitude(nGroupId, -1, aosCoordinates[i]))
4831 : {
4832 50 : int nOtherGroupId = -1;
4833 50 : int nOtherVarId = -1;
4834 : // Check that the variable actually exists
4835 : // Needed on Sentinel-3 products
4836 50 : if (NCDFResolveVar(nGroupId, aosCoordinates[i],
4837 50 : &nOtherGroupId, &nOtherVarId) == CE_None)
4838 : {
4839 48 : snprintf(szGeolocYName, sizeof(szGeolocYName), "%s",
4840 : aosCoordinates[i]);
4841 : }
4842 : }
4843 : }
4844 : // Add GEOLOCATION metadata.
4845 61 : if (!EQUAL(szGeolocXName, "") && !EQUAL(szGeolocYName, ""))
4846 : {
4847 48 : osGeolocXNameOut = szGeolocXName;
4848 48 : osGeolocYNameOut = szGeolocYName;
4849 :
4850 48 : char *pszGeolocXFullName = nullptr;
4851 48 : char *pszGeolocYFullName = nullptr;
4852 48 : if (NCDFResolveVarFullName(nGroupId, szGeolocXName,
4853 96 : &pszGeolocXFullName) == CE_None &&
4854 48 : NCDFResolveVarFullName(nGroupId, szGeolocYName,
4855 : &pszGeolocYFullName) == CE_None)
4856 : {
4857 48 : if (bSwitchedXY)
4858 : {
4859 2 : std::swap(pszGeolocXFullName, pszGeolocYFullName);
4860 2 : GDALPamDataset::SetMetadataItem("SWAP_XY", "YES",
4861 : "GEOLOCATION");
4862 : }
4863 :
4864 48 : bAddGeoloc = true;
4865 48 : CPLDebug("GDAL_netCDF",
4866 : "using variables %s and %s for GEOLOCATION",
4867 : pszGeolocXFullName, pszGeolocYFullName);
4868 :
4869 48 : GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
4870 : "GEOLOCATION");
4871 :
4872 96 : CPLString osTMP;
4873 48 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4874 48 : pszGeolocXFullName);
4875 :
4876 48 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP,
4877 : "GEOLOCATION");
4878 48 : GDALPamDataset::SetMetadataItem("X_BAND", "1",
4879 : "GEOLOCATION");
4880 48 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4881 48 : pszGeolocYFullName);
4882 :
4883 48 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP,
4884 : "GEOLOCATION");
4885 48 : GDALPamDataset::SetMetadataItem("Y_BAND", "1",
4886 : "GEOLOCATION");
4887 :
4888 48 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0",
4889 : "GEOLOCATION");
4890 48 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1",
4891 : "GEOLOCATION");
4892 :
4893 48 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0",
4894 : "GEOLOCATION");
4895 48 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1",
4896 : "GEOLOCATION");
4897 :
4898 48 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
4899 : "PIXEL_CENTER",
4900 : "GEOLOCATION");
4901 : }
4902 : else
4903 : {
4904 0 : CPLDebug("GDAL_netCDF",
4905 : "cannot resolve location of "
4906 : "lat/lon variables specified by the coordinates "
4907 : "attribute [%s]",
4908 : pszCoordinates);
4909 : }
4910 48 : CPLFree(pszGeolocXFullName);
4911 48 : CPLFree(pszGeolocYFullName);
4912 : }
4913 : else
4914 : {
4915 13 : CPLDebug("GDAL_netCDF",
4916 : "coordinates attribute [%s] is unsupported",
4917 : pszCoordinates);
4918 : }
4919 : }
4920 : else
4921 : {
4922 3 : CPLDebug("GDAL_netCDF",
4923 : "coordinates attribute [%s] with %d element(s) is "
4924 : "unsupported",
4925 : pszCoordinates, aosCoordinates.size());
4926 : }
4927 : }
4928 :
4929 : else
4930 : {
4931 286 : bAddGeoloc = ProcessNASAL2OceanGeoLocation(nGroupId, nVarId);
4932 :
4933 286 : if (!bAddGeoloc)
4934 285 : bAddGeoloc = ProcessNASAEMITGeoLocation(nGroupId, nVarId);
4935 : }
4936 :
4937 350 : CPLFree(pszCoordinates);
4938 :
4939 350 : return bAddGeoloc;
4940 : }
4941 :
4942 8 : CPLErr netCDFDataset::Set1DGeolocation(int nGroupId, int nVarId,
4943 : const char *szDimName)
4944 : {
4945 : // Get values.
4946 8 : char *pszVarValues = nullptr;
4947 8 : CPLErr eErr = NCDFGet1DVar(nGroupId, nVarId, &pszVarValues);
4948 8 : if (eErr != CE_None)
4949 0 : return eErr;
4950 :
4951 : // Write metadata.
4952 8 : char szTemp[NC_MAX_NAME + 1 + 32] = {};
4953 8 : snprintf(szTemp, sizeof(szTemp), "%s_VALUES", szDimName);
4954 8 : GDALPamDataset::SetMetadataItem(szTemp, pszVarValues, "GEOLOCATION2");
4955 :
4956 8 : CPLFree(pszVarValues);
4957 :
4958 8 : return CE_None;
4959 : }
4960 :
4961 0 : double *netCDFDataset::Get1DGeolocation(CPL_UNUSED const char *szDimName,
4962 : int &nVarLen)
4963 : {
4964 0 : nVarLen = 0;
4965 :
4966 : // Get Y_VALUES as tokens.
4967 : char **papszValues =
4968 0 : NCDFTokenizeArray(GetMetadataItem("Y_VALUES", "GEOLOCATION2"));
4969 0 : if (papszValues == nullptr)
4970 0 : return nullptr;
4971 :
4972 : // Initialize and fill array.
4973 0 : nVarLen = CSLCount(papszValues);
4974 : double *pdfVarValues =
4975 0 : static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
4976 :
4977 0 : for (int i = 0, j = 0; i < nVarLen; i++)
4978 : {
4979 0 : if (!bBottomUp)
4980 0 : j = nVarLen - 1 - i;
4981 : else
4982 0 : j = i; // Invert latitude values.
4983 0 : char *pszTemp = nullptr;
4984 0 : pdfVarValues[j] = CPLStrtod(papszValues[i], &pszTemp);
4985 : }
4986 0 : CSLDestroy(papszValues);
4987 :
4988 0 : return pdfVarValues;
4989 : }
4990 :
4991 : /************************************************************************/
4992 : /* SetSpatialRefNoUpdate() */
4993 : /************************************************************************/
4994 :
4995 256 : void netCDFDataset::SetSpatialRefNoUpdate(const OGRSpatialReference *poSRS)
4996 : {
4997 256 : m_oSRS.Clear();
4998 256 : if (poSRS)
4999 249 : m_oSRS = *poSRS;
5000 256 : m_bHasProjection = true;
5001 256 : }
5002 :
5003 : /************************************************************************/
5004 : /* SetSpatialRef() */
5005 : /************************************************************************/
5006 :
5007 76 : CPLErr netCDFDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
5008 : {
5009 152 : CPLMutexHolderD(&hNCMutex);
5010 :
5011 76 : if (GetAccess() != GA_Update || m_bHasProjection)
5012 : {
5013 0 : CPLError(CE_Failure, CPLE_AppDefined,
5014 : "netCDFDataset::_SetProjection() should only be called once "
5015 : "in update mode!");
5016 0 : return CE_Failure;
5017 : }
5018 :
5019 76 : if (m_bHasGeoTransform)
5020 : {
5021 32 : SetSpatialRefNoUpdate(poSRS);
5022 :
5023 : // For NC4/NC4C, writing both projection variables and data,
5024 : // followed by redefining nodata value, cancels the projection
5025 : // info from the Band variable, so for now only write the
5026 : // variable definitions, and write data at the end.
5027 : // See https://trac.osgeo.org/gdal/ticket/7245
5028 32 : return AddProjectionVars(true, nullptr, nullptr);
5029 : }
5030 :
5031 44 : SetSpatialRefNoUpdate(poSRS);
5032 :
5033 44 : return CE_None;
5034 : }
5035 :
5036 : /************************************************************************/
5037 : /* SetGeoTransformNoUpdate() */
5038 : /************************************************************************/
5039 :
5040 275 : void netCDFDataset::SetGeoTransformNoUpdate(const GDALGeoTransform >)
5041 : {
5042 275 : m_gt = gt;
5043 275 : m_bHasGeoTransform = true;
5044 275 : }
5045 :
5046 : /************************************************************************/
5047 : /* SetGeoTransform() */
5048 : /************************************************************************/
5049 :
5050 77 : CPLErr netCDFDataset::SetGeoTransform(const GDALGeoTransform >)
5051 : {
5052 154 : CPLMutexHolderD(&hNCMutex);
5053 :
5054 77 : if (GetAccess() != GA_Update || m_bHasGeoTransform)
5055 : {
5056 0 : CPLError(CE_Failure, CPLE_AppDefined,
5057 : "netCDFDataset::SetGeoTransform() should only be called once "
5058 : "in update mode!");
5059 0 : return CE_Failure;
5060 : }
5061 :
5062 77 : CPLDebug("GDAL_netCDF", "SetGeoTransform(%f,%f,%f,%f,%f,%f)", gt[0], gt[1],
5063 : gt[2], gt[3], gt[4], gt[5]);
5064 :
5065 77 : SetGeoTransformNoUpdate(gt);
5066 :
5067 77 : if (m_bHasProjection)
5068 : {
5069 :
5070 : // For NC4/NC4C, writing both projection variables and data,
5071 : // followed by redefining nodata value, cancels the projection
5072 : // info from the Band variable, so for now only write the
5073 : // variable definitions, and write data at the end.
5074 : // See https://trac.osgeo.org/gdal/ticket/7245
5075 3 : return AddProjectionVars(true, nullptr, nullptr);
5076 : }
5077 :
5078 74 : return CE_None;
5079 : }
5080 :
5081 : /************************************************************************/
5082 : /* NCDFWriteSRSVariable() */
5083 : /************************************************************************/
5084 :
5085 128 : int NCDFWriteSRSVariable(int cdfid, const OGRSpatialReference *poSRS,
5086 : char **ppszCFProjection, bool bWriteGDALTags,
5087 : const std::string &srsVarName)
5088 : {
5089 128 : char *pszCFProjection = nullptr;
5090 128 : char **papszKeyValues = nullptr;
5091 128 : poSRS->exportToCF1(&pszCFProjection, &papszKeyValues, nullptr, nullptr);
5092 :
5093 128 : if (bWriteGDALTags)
5094 : {
5095 127 : const char *pszWKT = CSLFetchNameValue(papszKeyValues, NCDF_CRS_WKT);
5096 127 : if (pszWKT)
5097 : {
5098 : // SPATIAL_REF is deprecated. Will be removed in GDAL 4.
5099 127 : papszKeyValues =
5100 127 : CSLSetNameValue(papszKeyValues, NCDF_SPATIAL_REF, pszWKT);
5101 : }
5102 : }
5103 :
5104 128 : const int nValues = CSLCount(papszKeyValues);
5105 :
5106 : int NCDFVarID;
5107 256 : std::string varNameRadix(pszCFProjection);
5108 128 : int nCounter = 2;
5109 : while (true)
5110 : {
5111 130 : NCDFVarID = -1;
5112 130 : nc_inq_varid(cdfid, pszCFProjection, &NCDFVarID);
5113 130 : if (NCDFVarID < 0)
5114 125 : break;
5115 :
5116 5 : int nbAttr = 0;
5117 5 : NCDF_ERR(nc_inq_varnatts(cdfid, NCDFVarID, &nbAttr));
5118 5 : bool bSame = nbAttr == nValues;
5119 41 : for (int i = 0; bSame && (i < nbAttr); i++)
5120 : {
5121 : char szAttrName[NC_MAX_NAME + 1];
5122 38 : szAttrName[0] = 0;
5123 38 : NCDF_ERR(nc_inq_attname(cdfid, NCDFVarID, i, szAttrName));
5124 :
5125 : const char *pszValue =
5126 38 : CSLFetchNameValue(papszKeyValues, szAttrName);
5127 38 : if (!pszValue)
5128 : {
5129 0 : bSame = false;
5130 2 : break;
5131 : }
5132 :
5133 38 : nc_type atttype = NC_NAT;
5134 38 : size_t attlen = 0;
5135 38 : NCDF_ERR(
5136 : nc_inq_att(cdfid, NCDFVarID, szAttrName, &atttype, &attlen));
5137 38 : if (atttype != NC_CHAR && atttype != NC_DOUBLE)
5138 : {
5139 0 : bSame = false;
5140 0 : break;
5141 : }
5142 38 : if (atttype == NC_CHAR)
5143 : {
5144 15 : if (CPLGetValueType(pszValue) != CPL_VALUE_STRING)
5145 : {
5146 0 : bSame = false;
5147 0 : break;
5148 : }
5149 15 : std::string val;
5150 15 : val.resize(attlen);
5151 15 : nc_get_att_text(cdfid, NCDFVarID, szAttrName, &val[0]);
5152 15 : if (val != pszValue)
5153 : {
5154 0 : bSame = false;
5155 0 : break;
5156 : }
5157 : }
5158 : else
5159 : {
5160 : const CPLStringList aosTokens(
5161 23 : CSLTokenizeString2(pszValue, ",", 0));
5162 23 : if (static_cast<size_t>(aosTokens.size()) != attlen)
5163 : {
5164 0 : bSame = false;
5165 0 : break;
5166 : }
5167 : double vals[2];
5168 23 : nc_get_att_double(cdfid, NCDFVarID, szAttrName, vals);
5169 44 : if (vals[0] != CPLAtof(aosTokens[0]) ||
5170 21 : (attlen == 2 && vals[1] != CPLAtof(aosTokens[1])))
5171 : {
5172 2 : bSame = false;
5173 2 : break;
5174 : }
5175 : }
5176 : }
5177 5 : if (bSame)
5178 : {
5179 3 : *ppszCFProjection = pszCFProjection;
5180 3 : CSLDestroy(papszKeyValues);
5181 3 : return NCDFVarID;
5182 : }
5183 2 : CPLFree(pszCFProjection);
5184 2 : pszCFProjection =
5185 2 : CPLStrdup(CPLSPrintf("%s_%d", varNameRadix.c_str(), nCounter));
5186 2 : nCounter++;
5187 2 : }
5188 :
5189 125 : *ppszCFProjection = pszCFProjection;
5190 :
5191 : const char *pszVarName;
5192 :
5193 125 : if (srsVarName != "")
5194 : {
5195 38 : pszVarName = srsVarName.c_str();
5196 : }
5197 : else
5198 : {
5199 87 : pszVarName = pszCFProjection;
5200 : }
5201 :
5202 125 : int status = nc_def_var(cdfid, pszVarName, NC_CHAR, 0, nullptr, &NCDFVarID);
5203 125 : NCDF_ERR(status);
5204 1199 : for (int i = 0; i < nValues; ++i)
5205 : {
5206 1074 : char *pszKey = nullptr;
5207 1074 : const char *pszValue = CPLParseNameValue(papszKeyValues[i], &pszKey);
5208 1074 : if (pszKey && pszValue)
5209 : {
5210 2148 : const CPLStringList aosTokens(CSLTokenizeString2(pszValue, ",", 0));
5211 1074 : double adfValues[2] = {0, 0};
5212 1074 : const int nDoubleCount = std::min(2, aosTokens.size());
5213 1074 : if (!(aosTokens.size() == 2 &&
5214 2147 : CPLGetValueType(aosTokens[0]) != CPL_VALUE_STRING) &&
5215 1073 : CPLGetValueType(pszValue) == CPL_VALUE_STRING)
5216 : {
5217 499 : status = nc_put_att_text(cdfid, NCDFVarID, pszKey,
5218 : strlen(pszValue), pszValue);
5219 : }
5220 : else
5221 : {
5222 1151 : for (int j = 0; j < nDoubleCount; ++j)
5223 576 : adfValues[j] = CPLAtof(aosTokens[j]);
5224 575 : status = nc_put_att_double(cdfid, NCDFVarID, pszKey, NC_DOUBLE,
5225 : nDoubleCount, adfValues);
5226 : }
5227 1074 : NCDF_ERR(status);
5228 : }
5229 1074 : CPLFree(pszKey);
5230 : }
5231 :
5232 125 : CSLDestroy(papszKeyValues);
5233 125 : return NCDFVarID;
5234 : }
5235 :
5236 : /************************************************************************/
5237 : /* NCDFWriteLonLatVarsAttributes() */
5238 : /************************************************************************/
5239 :
5240 101 : void NCDFWriteLonLatVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarLonID,
5241 : int nVarLatID)
5242 : {
5243 :
5244 : try
5245 : {
5246 101 : vcdf.nc_put_vatt_text(nVarLatID, CF_STD_NAME, CF_LATITUDE_STD_NAME);
5247 101 : vcdf.nc_put_vatt_text(nVarLatID, CF_LNG_NAME, CF_LATITUDE_LNG_NAME);
5248 101 : vcdf.nc_put_vatt_text(nVarLatID, CF_UNITS, CF_DEGREES_NORTH);
5249 101 : vcdf.nc_put_vatt_text(nVarLonID, CF_STD_NAME, CF_LONGITUDE_STD_NAME);
5250 101 : vcdf.nc_put_vatt_text(nVarLonID, CF_LNG_NAME, CF_LONGITUDE_LNG_NAME);
5251 101 : vcdf.nc_put_vatt_text(nVarLonID, CF_UNITS, CF_DEGREES_EAST);
5252 : }
5253 0 : catch (nccfdriver::SG_Exception &e)
5254 : {
5255 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5256 : }
5257 101 : }
5258 :
5259 : /************************************************************************/
5260 : /* NCDFWriteRLonRLatVarsAttributes() */
5261 : /************************************************************************/
5262 :
5263 0 : void NCDFWriteRLonRLatVarsAttributes(nccfdriver::netCDFVID &vcdf,
5264 : int nVarRLonID, int nVarRLatID)
5265 : {
5266 : try
5267 : {
5268 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_STD_NAME, "grid_latitude");
5269 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_LNG_NAME,
5270 : "latitude in rotated pole grid");
5271 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_UNITS, "degrees");
5272 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_AXIS, "Y");
5273 :
5274 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_STD_NAME, "grid_longitude");
5275 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_LNG_NAME,
5276 : "longitude in rotated pole grid");
5277 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_UNITS, "degrees");
5278 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_AXIS, "X");
5279 : }
5280 0 : catch (nccfdriver::SG_Exception &e)
5281 : {
5282 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5283 : }
5284 0 : }
5285 :
5286 : /************************************************************************/
5287 : /* NCDFGetProjectedCFUnit() */
5288 : /************************************************************************/
5289 :
5290 40 : std::string NCDFGetProjectedCFUnit(const OGRSpatialReference *poSRS)
5291 : {
5292 40 : char *pszUnitsToWrite = nullptr;
5293 40 : poSRS->exportToCF1(nullptr, nullptr, &pszUnitsToWrite, nullptr);
5294 40 : std::string osRet = pszUnitsToWrite ? pszUnitsToWrite : std::string();
5295 40 : CPLFree(pszUnitsToWrite);
5296 80 : return osRet;
5297 : }
5298 :
5299 : /************************************************************************/
5300 : /* NCDFWriteXYVarsAttributes() */
5301 : /************************************************************************/
5302 :
5303 29 : void NCDFWriteXYVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarXID,
5304 : int nVarYID, const OGRSpatialReference *poSRS)
5305 : {
5306 58 : const std::string osUnitsToWrite = NCDFGetProjectedCFUnit(poSRS);
5307 :
5308 : try
5309 : {
5310 29 : vcdf.nc_put_vatt_text(nVarXID, CF_STD_NAME, CF_PROJ_X_COORD);
5311 29 : vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME, CF_PROJ_X_COORD_LONG_NAME);
5312 29 : if (!osUnitsToWrite.empty())
5313 29 : vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, osUnitsToWrite.c_str());
5314 29 : vcdf.nc_put_vatt_text(nVarYID, CF_STD_NAME, CF_PROJ_Y_COORD);
5315 29 : vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME, CF_PROJ_Y_COORD_LONG_NAME);
5316 29 : if (!osUnitsToWrite.empty())
5317 29 : vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, osUnitsToWrite.c_str());
5318 : }
5319 0 : catch (nccfdriver::SG_Exception &e)
5320 : {
5321 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5322 : }
5323 29 : }
5324 :
5325 : /************************************************************************/
5326 : /* AddProjectionVars() */
5327 : /************************************************************************/
5328 :
5329 164 : CPLErr netCDFDataset::AddProjectionVars(bool bDefsOnly,
5330 : GDALProgressFunc pfnProgress,
5331 : void *pProgressData)
5332 : {
5333 164 : if (nCFVersion >= 1.8)
5334 0 : return CE_None; // do nothing
5335 :
5336 164 : bool bWriteGridMapping = false;
5337 164 : bool bWriteLonLat = false;
5338 164 : bool bHasGeoloc = false;
5339 164 : bool bWriteGDALTags = false;
5340 164 : bool bWriteGeoTransform = false;
5341 :
5342 : // For GEOLOCATION information.
5343 164 : GDALDatasetH hDS_X = nullptr;
5344 164 : GDALRasterBandH hBand_X = nullptr;
5345 164 : GDALDatasetH hDS_Y = nullptr;
5346 164 : GDALRasterBandH hBand_Y = nullptr;
5347 :
5348 328 : OGRSpatialReference oSRS(m_oSRS);
5349 164 : if (!m_oSRS.IsEmpty())
5350 : {
5351 138 : if (oSRS.IsProjected())
5352 50 : bIsProjected = true;
5353 88 : else if (oSRS.IsGeographic())
5354 88 : bIsGeographic = true;
5355 : }
5356 :
5357 164 : if (bDefsOnly)
5358 : {
5359 82 : char *pszProjection = nullptr;
5360 82 : m_oSRS.exportToWkt(&pszProjection);
5361 82 : CPLDebug("GDAL_netCDF",
5362 : "SetProjection, WKT now = [%s]\nprojected: %d geographic: %d",
5363 82 : pszProjection ? pszProjection : "(null)",
5364 82 : static_cast<int>(bIsProjected),
5365 82 : static_cast<int>(bIsGeographic));
5366 82 : CPLFree(pszProjection);
5367 :
5368 82 : if (!m_bHasGeoTransform)
5369 5 : CPLDebug("GDAL_netCDF",
5370 : "netCDFDataset::AddProjectionVars() called, "
5371 : "but GeoTransform has not yet been defined!");
5372 :
5373 82 : if (!m_bHasProjection)
5374 6 : CPLDebug("GDAL_netCDF",
5375 : "netCDFDataset::AddProjectionVars() called, "
5376 : "but Projection has not yet been defined!");
5377 : }
5378 :
5379 : // Check GEOLOCATION information.
5380 164 : char **papszGeolocationInfo = netCDFDataset::GetMetadata("GEOLOCATION");
5381 164 : if (papszGeolocationInfo != nullptr)
5382 : {
5383 : // Look for geolocation datasets.
5384 : const char *pszDSName =
5385 10 : CSLFetchNameValue(papszGeolocationInfo, "X_DATASET");
5386 10 : if (pszDSName != nullptr)
5387 10 : hDS_X = GDALOpenShared(pszDSName, GA_ReadOnly);
5388 10 : pszDSName = CSLFetchNameValue(papszGeolocationInfo, "Y_DATASET");
5389 10 : if (pszDSName != nullptr)
5390 10 : hDS_Y = GDALOpenShared(pszDSName, GA_ReadOnly);
5391 :
5392 10 : if (hDS_X != nullptr && hDS_Y != nullptr)
5393 : {
5394 10 : int nBand = std::max(1, atoi(CSLFetchNameValueDef(
5395 10 : papszGeolocationInfo, "X_BAND", "0")));
5396 10 : hBand_X = GDALGetRasterBand(hDS_X, nBand);
5397 10 : nBand = std::max(1, atoi(CSLFetchNameValueDef(papszGeolocationInfo,
5398 10 : "Y_BAND", "0")));
5399 10 : hBand_Y = GDALGetRasterBand(hDS_Y, nBand);
5400 :
5401 : // If geoloc bands are found, do basic validation based on their
5402 : // dimensions.
5403 10 : if (hBand_X != nullptr && hBand_Y != nullptr)
5404 : {
5405 10 : int nXSize_XBand = GDALGetRasterXSize(hDS_X);
5406 10 : int nYSize_XBand = GDALGetRasterYSize(hDS_X);
5407 10 : int nXSize_YBand = GDALGetRasterXSize(hDS_Y);
5408 10 : int nYSize_YBand = GDALGetRasterYSize(hDS_Y);
5409 :
5410 : // TODO 1D geolocation arrays not implemented.
5411 10 : if (nYSize_XBand == 1 && nYSize_YBand == 1)
5412 : {
5413 0 : bHasGeoloc = false;
5414 0 : CPLDebug("GDAL_netCDF",
5415 : "1D GEOLOCATION arrays not supported yet");
5416 : }
5417 : // 2D bands must have same sizes as the raster bands.
5418 10 : else if (nXSize_XBand != nRasterXSize ||
5419 10 : nYSize_XBand != nRasterYSize ||
5420 10 : nXSize_YBand != nRasterXSize ||
5421 10 : nYSize_YBand != nRasterYSize)
5422 : {
5423 0 : bHasGeoloc = false;
5424 0 : CPLDebug("GDAL_netCDF",
5425 : "GEOLOCATION array sizes (%dx%d %dx%d) differ "
5426 : "from raster (%dx%d), not supported",
5427 : nXSize_XBand, nYSize_XBand, nXSize_YBand,
5428 : nYSize_YBand, nRasterXSize, nRasterYSize);
5429 : }
5430 : else
5431 : {
5432 10 : bHasGeoloc = true;
5433 10 : CPLDebug("GDAL_netCDF",
5434 : "dataset has GEOLOCATION information, will try to "
5435 : "write it");
5436 : }
5437 : }
5438 : }
5439 : }
5440 :
5441 : // Process projection options.
5442 164 : if (bIsProjected)
5443 : {
5444 : bool bIsCfProjection =
5445 50 : oSRS.exportToCF1(nullptr, nullptr, nullptr, nullptr) == OGRERR_NONE;
5446 50 : bWriteGridMapping = true;
5447 50 : bWriteGDALTags = CPL_TO_BOOL(
5448 50 : CSLFetchBoolean(papszCreationOptions, "WRITE_GDAL_TAGS", TRUE));
5449 : // Force WRITE_GDAL_TAGS if is not a CF projection.
5450 50 : if (!bWriteGDALTags && !bIsCfProjection)
5451 0 : bWriteGDALTags = true;
5452 50 : if (bWriteGDALTags)
5453 50 : bWriteGeoTransform = true;
5454 :
5455 : // Write lon/lat: default is NO, except if has geolocation.
5456 : // With IF_NEEDED: write if has geoloc or is not CF projection.
5457 : const char *pszValue =
5458 50 : CSLFetchNameValue(papszCreationOptions, "WRITE_LONLAT");
5459 50 : if (pszValue)
5460 : {
5461 2 : if (EQUAL(pszValue, "IF_NEEDED"))
5462 : {
5463 0 : bWriteLonLat = bHasGeoloc || !bIsCfProjection;
5464 : }
5465 : else
5466 : {
5467 2 : bWriteLonLat = CPLTestBool(pszValue);
5468 : }
5469 : }
5470 : else
5471 : {
5472 48 : bWriteLonLat = bHasGeoloc;
5473 : }
5474 :
5475 : // Save value of pszCFCoordinates for later.
5476 50 : if (bWriteLonLat)
5477 : {
5478 4 : pszCFCoordinates = NCDF_LONLAT;
5479 : }
5480 : }
5481 : else
5482 : {
5483 : // Files without a Datum will not have a grid_mapping variable and
5484 : // geographic information.
5485 114 : bWriteGridMapping = bIsGeographic;
5486 :
5487 114 : if (bHasGeoloc)
5488 : {
5489 8 : bWriteLonLat = true;
5490 : }
5491 : else
5492 : {
5493 106 : bWriteGDALTags = CPL_TO_BOOL(CSLFetchBoolean(
5494 106 : papszCreationOptions, "WRITE_GDAL_TAGS", bWriteGridMapping));
5495 106 : if (bWriteGDALTags)
5496 88 : bWriteGeoTransform = true;
5497 :
5498 106 : const char *pszValue = CSLFetchNameValueDef(papszCreationOptions,
5499 : "WRITE_LONLAT", "YES");
5500 106 : if (EQUAL(pszValue, "IF_NEEDED"))
5501 0 : bWriteLonLat = true;
5502 : else
5503 106 : bWriteLonLat = CPLTestBool(pszValue);
5504 : // Don't write lon/lat if no source geotransform.
5505 106 : if (!m_bHasGeoTransform)
5506 0 : bWriteLonLat = false;
5507 : // If we don't write lon/lat, set dimnames to X/Y and write gdal
5508 : // tags.
5509 106 : if (!bWriteLonLat)
5510 : {
5511 0 : CPLError(CE_Warning, CPLE_AppDefined,
5512 : "creating geographic file without lon/lat values!");
5513 0 : if (m_bHasGeoTransform)
5514 : {
5515 0 : bWriteGDALTags = true; // Not desirable if no geotransform.
5516 0 : bWriteGeoTransform = true;
5517 : }
5518 : }
5519 : }
5520 : }
5521 :
5522 : // Make sure we write grid_mapping if we need to write GDAL tags.
5523 164 : if (bWriteGDALTags)
5524 138 : bWriteGridMapping = true;
5525 :
5526 : // bottom-up value: new driver is bottom-up by default.
5527 : // Override with WRITE_BOTTOMUP.
5528 164 : bBottomUp = CPL_TO_BOOL(
5529 164 : CSLFetchBoolean(papszCreationOptions, "WRITE_BOTTOMUP", TRUE));
5530 :
5531 164 : if (bDefsOnly)
5532 : {
5533 82 : CPLDebug(
5534 : "GDAL_netCDF",
5535 : "bIsProjected=%d bIsGeographic=%d bWriteGridMapping=%d "
5536 : "bWriteGDALTags=%d bWriteLonLat=%d bBottomUp=%d bHasGeoloc=%d",
5537 82 : static_cast<int>(bIsProjected), static_cast<int>(bIsGeographic),
5538 : static_cast<int>(bWriteGridMapping),
5539 : static_cast<int>(bWriteGDALTags), static_cast<int>(bWriteLonLat),
5540 82 : static_cast<int>(bBottomUp), static_cast<int>(bHasGeoloc));
5541 : }
5542 :
5543 : // Exit if nothing to do.
5544 164 : if (!bIsProjected && !bWriteLonLat)
5545 0 : return CE_None;
5546 :
5547 : // Define dimension names.
5548 :
5549 164 : constexpr const char *ROTATED_POLE_VAR_NAME = "rotated_pole";
5550 :
5551 164 : if (bDefsOnly)
5552 : {
5553 82 : int nVarLonID = -1;
5554 82 : int nVarLatID = -1;
5555 82 : int nVarXID = -1;
5556 82 : int nVarYID = -1;
5557 :
5558 82 : m_bAddedProjectionVarsDefs = true;
5559 :
5560 : // Make sure we are in define mode.
5561 82 : SetDefineMode(true);
5562 :
5563 : // Write projection attributes.
5564 82 : if (bWriteGridMapping)
5565 : {
5566 69 : const int NCDFVarID = NCDFWriteSRSVariable(
5567 : cdfid, &oSRS, &pszCFProjection, bWriteGDALTags);
5568 69 : if (NCDFVarID < 0)
5569 0 : return CE_Failure;
5570 :
5571 : // Optional GDAL custom projection tags.
5572 69 : if (bWriteGDALTags)
5573 : {
5574 138 : CPLString osGeoTransform;
5575 483 : for (int i = 0; i < 6; i++)
5576 : {
5577 414 : osGeoTransform += CPLSPrintf("%.17g ", m_gt[i]);
5578 : }
5579 69 : CPLDebug("GDAL_netCDF", "szGeoTransform = %s",
5580 : osGeoTransform.c_str());
5581 :
5582 : // if( strlen(pszProj4Defn) > 0 ) {
5583 : // nc_put_att_text(cdfid, NCDFVarID, "proj4",
5584 : // strlen(pszProj4Defn), pszProj4Defn);
5585 : // }
5586 :
5587 : // For now, write the geotransform for back-compat or else
5588 : // the old (1.8.1) driver overrides the CF geotransform with
5589 : // empty values from dfNN, dfSN, dfEE, dfWE;
5590 :
5591 : // TODO: fix this in 1.8 branch, and then remove this here.
5592 69 : if (bWriteGeoTransform && m_bHasGeoTransform)
5593 : {
5594 : {
5595 68 : const int status = nc_put_att_text(
5596 : cdfid, NCDFVarID, NCDF_GEOTRANSFORM,
5597 : osGeoTransform.size(), osGeoTransform.c_str());
5598 68 : NCDF_ERR(status);
5599 : }
5600 : }
5601 : }
5602 :
5603 : // Write projection variable to band variable.
5604 : // Need to call later if there are no bands.
5605 69 : AddGridMappingRef();
5606 : } // end if( bWriteGridMapping )
5607 :
5608 : // Write CF Projection vars.
5609 :
5610 82 : const bool bIsRotatedPole =
5611 151 : pszCFProjection != nullptr &&
5612 69 : EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
5613 82 : if (bIsRotatedPole)
5614 : {
5615 : // Rename dims to rlat/rlon.
5616 : papszDimName
5617 0 : .Clear(); // If we add other dims one day, this has to change
5618 0 : papszDimName.AddString(NCDF_DIMNAME_RLAT);
5619 0 : papszDimName.AddString(NCDF_DIMNAME_RLON);
5620 :
5621 0 : int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_RLAT);
5622 0 : NCDF_ERR(status);
5623 0 : status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_RLON);
5624 0 : NCDF_ERR(status);
5625 : }
5626 : // Rename dimensions if lon/lat.
5627 82 : else if (!bIsProjected && !bHasGeoloc)
5628 : {
5629 : // Rename dims to lat/lon.
5630 : papszDimName
5631 53 : .Clear(); // If we add other dims one day, this has to change
5632 53 : papszDimName.AddString(NCDF_DIMNAME_LAT);
5633 53 : papszDimName.AddString(NCDF_DIMNAME_LON);
5634 :
5635 53 : int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_LAT);
5636 53 : NCDF_ERR(status);
5637 53 : status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_LON);
5638 53 : NCDF_ERR(status);
5639 : }
5640 :
5641 : // Write X/Y attributes.
5642 : else /* if( bIsProjected || bHasGeoloc ) */
5643 : {
5644 : // X
5645 : int anXDims[1];
5646 29 : anXDims[0] = nXDimID;
5647 29 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
5648 : CF_PROJ_X_VAR_NAME, NC_DOUBLE);
5649 29 : int status = nc_def_var(cdfid, CF_PROJ_X_VAR_NAME, NC_DOUBLE, 1,
5650 : anXDims, &nVarXID);
5651 29 : NCDF_ERR(status);
5652 :
5653 : // Y
5654 : int anYDims[1];
5655 29 : anYDims[0] = nYDimID;
5656 29 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
5657 : CF_PROJ_Y_VAR_NAME, NC_DOUBLE);
5658 29 : status = nc_def_var(cdfid, CF_PROJ_Y_VAR_NAME, NC_DOUBLE, 1,
5659 : anYDims, &nVarYID);
5660 29 : NCDF_ERR(status);
5661 :
5662 29 : if (bIsProjected)
5663 : {
5664 25 : NCDFWriteXYVarsAttributes(this->vcdf, nVarXID, nVarYID, &oSRS);
5665 : }
5666 : else
5667 : {
5668 4 : CPLAssert(bHasGeoloc);
5669 : try
5670 : {
5671 4 : vcdf.nc_put_vatt_text(nVarXID, CF_AXIS, CF_SG_X_AXIS);
5672 4 : vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME,
5673 : "x-coordinate in Cartesian system");
5674 4 : vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, "m");
5675 4 : vcdf.nc_put_vatt_text(nVarYID, CF_AXIS, CF_SG_Y_AXIS);
5676 4 : vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME,
5677 : "y-coordinate in Cartesian system");
5678 4 : vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, "m");
5679 :
5680 4 : pszCFCoordinates = NCDF_LONLAT;
5681 : }
5682 0 : catch (nccfdriver::SG_Exception &e)
5683 : {
5684 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5685 0 : return CE_Failure;
5686 : }
5687 : }
5688 : }
5689 :
5690 : // Write lat/lon attributes if needed.
5691 82 : if (bWriteLonLat)
5692 : {
5693 59 : int *panLatDims = nullptr;
5694 59 : int *panLonDims = nullptr;
5695 59 : int nLatDims = -1;
5696 59 : int nLonDims = -1;
5697 :
5698 : // Get information.
5699 59 : if (bHasGeoloc)
5700 : {
5701 : // Geoloc
5702 5 : nLatDims = 2;
5703 : panLatDims =
5704 5 : static_cast<int *>(CPLCalloc(nLatDims, sizeof(int)));
5705 5 : panLatDims[0] = nYDimID;
5706 5 : panLatDims[1] = nXDimID;
5707 5 : nLonDims = 2;
5708 : panLonDims =
5709 5 : static_cast<int *>(CPLCalloc(nLonDims, sizeof(int)));
5710 5 : panLonDims[0] = nYDimID;
5711 5 : panLonDims[1] = nXDimID;
5712 : }
5713 54 : else if (bIsProjected)
5714 : {
5715 : // Projected
5716 1 : nLatDims = 2;
5717 : panLatDims =
5718 1 : static_cast<int *>(CPLCalloc(nLatDims, sizeof(int)));
5719 1 : panLatDims[0] = nYDimID;
5720 1 : panLatDims[1] = nXDimID;
5721 1 : nLonDims = 2;
5722 : panLonDims =
5723 1 : static_cast<int *>(CPLCalloc(nLonDims, sizeof(int)));
5724 1 : panLonDims[0] = nYDimID;
5725 1 : panLonDims[1] = nXDimID;
5726 : }
5727 : else
5728 : {
5729 : // Geographic
5730 53 : nLatDims = 1;
5731 : panLatDims =
5732 53 : static_cast<int *>(CPLCalloc(nLatDims, sizeof(int)));
5733 53 : panLatDims[0] = nYDimID;
5734 53 : nLonDims = 1;
5735 : panLonDims =
5736 53 : static_cast<int *>(CPLCalloc(nLonDims, sizeof(int)));
5737 53 : panLonDims[0] = nXDimID;
5738 : }
5739 :
5740 59 : nc_type eLonLatType = NC_NAT;
5741 59 : if (bIsProjected)
5742 : {
5743 2 : eLonLatType = NC_FLOAT;
5744 4 : const char *pszValue = CSLFetchNameValueDef(
5745 2 : papszCreationOptions, "TYPE_LONLAT", "FLOAT");
5746 2 : if (EQUAL(pszValue, "DOUBLE"))
5747 0 : eLonLatType = NC_DOUBLE;
5748 : }
5749 : else
5750 : {
5751 57 : eLonLatType = NC_DOUBLE;
5752 114 : const char *pszValue = CSLFetchNameValueDef(
5753 57 : papszCreationOptions, "TYPE_LONLAT", "DOUBLE");
5754 57 : if (EQUAL(pszValue, "FLOAT"))
5755 0 : eLonLatType = NC_FLOAT;
5756 : }
5757 :
5758 : // Def vars and attributes.
5759 : {
5760 59 : const char *pszVarName =
5761 59 : bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME;
5762 59 : int status = nc_def_var(cdfid, pszVarName, eLonLatType,
5763 : nLatDims, panLatDims, &nVarLatID);
5764 59 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
5765 : cdfid, pszVarName, eLonLatType, nLatDims, nVarLatID);
5766 59 : NCDF_ERR(status);
5767 59 : DefVarDeflate(nVarLatID, false); // Don't set chunking.
5768 : }
5769 :
5770 : {
5771 59 : const char *pszVarName =
5772 59 : bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME;
5773 59 : int status = nc_def_var(cdfid, pszVarName, eLonLatType,
5774 : nLonDims, panLonDims, &nVarLonID);
5775 59 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
5776 : cdfid, pszVarName, eLonLatType, nLatDims, nVarLonID);
5777 59 : NCDF_ERR(status);
5778 59 : DefVarDeflate(nVarLonID, false); // Don't set chunking.
5779 : }
5780 :
5781 59 : if (bIsRotatedPole)
5782 0 : NCDFWriteRLonRLatVarsAttributes(this->vcdf, nVarLonID,
5783 : nVarLatID);
5784 : else
5785 59 : NCDFWriteLonLatVarsAttributes(this->vcdf, nVarLonID, nVarLatID);
5786 :
5787 59 : CPLFree(panLatDims);
5788 59 : CPLFree(panLonDims);
5789 : }
5790 : }
5791 :
5792 164 : if (!bDefsOnly)
5793 : {
5794 82 : m_bAddedProjectionVarsData = true;
5795 :
5796 82 : int nVarXID = -1;
5797 82 : int nVarYID = -1;
5798 :
5799 82 : nc_inq_varid(cdfid, CF_PROJ_X_VAR_NAME, &nVarXID);
5800 82 : nc_inq_varid(cdfid, CF_PROJ_Y_VAR_NAME, &nVarYID);
5801 :
5802 82 : int nVarLonID = -1;
5803 82 : int nVarLatID = -1;
5804 :
5805 82 : const bool bIsRotatedPole =
5806 151 : pszCFProjection != nullptr &&
5807 69 : EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
5808 82 : nc_inq_varid(cdfid,
5809 : bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME,
5810 : &nVarLonID);
5811 82 : nc_inq_varid(cdfid,
5812 : bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME,
5813 : &nVarLatID);
5814 :
5815 : // Get projection values.
5816 :
5817 82 : double *padLonVal = nullptr;
5818 82 : double *padLatVal = nullptr;
5819 :
5820 82 : if (bIsProjected)
5821 : {
5822 25 : OGRSpatialReference *poLatLonSRS = nullptr;
5823 25 : OGRCoordinateTransformation *poTransform = nullptr;
5824 :
5825 : size_t startX[1];
5826 : size_t countX[1];
5827 : size_t startY[1];
5828 : size_t countY[1];
5829 :
5830 25 : CPLDebug("GDAL_netCDF", "Getting (X,Y) values");
5831 :
5832 : double *padXVal =
5833 25 : static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
5834 : double *padYVal =
5835 25 : static_cast<double *>(CPLMalloc(nRasterYSize * sizeof(double)));
5836 :
5837 : // Get Y values.
5838 25 : const double dfY0 = (!bBottomUp) ? m_gt[3] :
5839 : // Invert latitude values.
5840 25 : m_gt[3] + (m_gt[5] * nRasterYSize);
5841 25 : const double dfDY = m_gt[5];
5842 :
5843 1456 : for (int j = 0; j < nRasterYSize; j++)
5844 : {
5845 : // The data point is centered inside the pixel.
5846 1431 : if (!bBottomUp)
5847 0 : padYVal[j] = dfY0 + (j + 0.5) * dfDY;
5848 : else // Invert latitude values.
5849 1431 : padYVal[j] = dfY0 - (j + 0.5) * dfDY;
5850 : }
5851 25 : startX[0] = 0;
5852 25 : countX[0] = nRasterXSize;
5853 :
5854 : // Get X values.
5855 25 : const double dfX0 = m_gt[0];
5856 25 : const double dfDX = m_gt[1];
5857 :
5858 1477 : for (int i = 0; i < nRasterXSize; i++)
5859 : {
5860 : // The data point is centered inside the pixel.
5861 1452 : padXVal[i] = dfX0 + (i + 0.5) * dfDX;
5862 : }
5863 25 : startY[0] = 0;
5864 25 : countY[0] = nRasterYSize;
5865 :
5866 : // Write X/Y values.
5867 :
5868 : // Make sure we are in data mode.
5869 25 : SetDefineMode(false);
5870 :
5871 25 : CPLDebug("GDAL_netCDF", "Writing X values");
5872 : int status =
5873 25 : nc_put_vara_double(cdfid, nVarXID, startX, countX, padXVal);
5874 25 : NCDF_ERR(status);
5875 :
5876 25 : CPLDebug("GDAL_netCDF", "Writing Y values");
5877 : status =
5878 25 : nc_put_vara_double(cdfid, nVarYID, startY, countY, padYVal);
5879 25 : NCDF_ERR(status);
5880 :
5881 25 : if (pfnProgress)
5882 21 : pfnProgress(0.20, nullptr, pProgressData);
5883 :
5884 : // Write lon/lat arrays (CF coordinates) if requested.
5885 :
5886 : // Get OGR transform if GEOLOCATION is not available.
5887 25 : if (bWriteLonLat && !bHasGeoloc)
5888 : {
5889 1 : poLatLonSRS = m_oSRS.CloneGeogCS();
5890 1 : if (poLatLonSRS != nullptr)
5891 : {
5892 1 : poLatLonSRS->SetAxisMappingStrategy(
5893 : OAMS_TRADITIONAL_GIS_ORDER);
5894 : poTransform =
5895 1 : OGRCreateCoordinateTransformation(&m_oSRS, poLatLonSRS);
5896 : }
5897 : // If no OGR transform, then don't write CF lon/lat.
5898 1 : if (poTransform == nullptr)
5899 : {
5900 0 : CPLError(CE_Failure, CPLE_AppDefined,
5901 : "Unable to get Coordinate Transform");
5902 0 : bWriteLonLat = false;
5903 : }
5904 : }
5905 :
5906 25 : if (bWriteLonLat)
5907 : {
5908 2 : if (!bHasGeoloc)
5909 1 : CPLDebug("GDAL_netCDF", "Transforming (X,Y)->(lon,lat)");
5910 : else
5911 1 : CPLDebug("GDAL_netCDF",
5912 : "Writing (lon,lat) from GEOLOCATION arrays");
5913 :
5914 2 : bool bOK = true;
5915 2 : double dfProgress = 0.2;
5916 :
5917 2 : size_t start[] = {0, 0};
5918 2 : size_t count[] = {1, (size_t)nRasterXSize};
5919 : padLatVal = static_cast<double *>(
5920 2 : CPLMalloc(nRasterXSize * sizeof(double)));
5921 : padLonVal = static_cast<double *>(
5922 2 : CPLMalloc(nRasterXSize * sizeof(double)));
5923 :
5924 61 : for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR;
5925 : j++)
5926 : {
5927 59 : start[0] = j;
5928 :
5929 : // Get values from geotransform.
5930 59 : if (!bHasGeoloc)
5931 : {
5932 : // Fill values to transform.
5933 420 : for (int i = 0; i < nRasterXSize; i++)
5934 : {
5935 400 : padLatVal[i] = padYVal[j];
5936 400 : padLonVal[i] = padXVal[i];
5937 : }
5938 :
5939 : // Do the transform.
5940 20 : bOK = CPL_TO_BOOL(poTransform->Transform(
5941 20 : nRasterXSize, padLonVal, padLatVal, nullptr));
5942 20 : if (!bOK)
5943 : {
5944 0 : CPLError(CE_Failure, CPLE_AppDefined,
5945 : "Unable to Transform (X,Y) to (lon,lat).");
5946 : }
5947 : }
5948 : // Get values from geoloc arrays.
5949 : else
5950 : {
5951 39 : CPLErr eErr = GDALRasterIO(
5952 : hBand_Y, GF_Read, 0, j, nRasterXSize, 1, padLatVal,
5953 : nRasterXSize, 1, GDT_Float64, 0, 0);
5954 39 : if (eErr == CE_None)
5955 : {
5956 39 : eErr = GDALRasterIO(
5957 : hBand_X, GF_Read, 0, j, nRasterXSize, 1,
5958 : padLonVal, nRasterXSize, 1, GDT_Float64, 0, 0);
5959 : }
5960 :
5961 39 : if (eErr == CE_None)
5962 : {
5963 39 : bOK = true;
5964 : }
5965 : else
5966 : {
5967 0 : bOK = false;
5968 0 : CPLError(CE_Failure, CPLE_AppDefined,
5969 : "Unable to get scanline %d", j);
5970 : }
5971 : }
5972 :
5973 : // Write data.
5974 59 : if (bOK)
5975 : {
5976 59 : status = nc_put_vara_double(cdfid, nVarLatID, start,
5977 : count, padLatVal);
5978 59 : NCDF_ERR(status);
5979 59 : status = nc_put_vara_double(cdfid, nVarLonID, start,
5980 : count, padLonVal);
5981 59 : NCDF_ERR(status);
5982 : }
5983 :
5984 59 : if (pfnProgress && (nRasterYSize / 10) > 0 &&
5985 59 : (j % (nRasterYSize / 10) == 0))
5986 : {
5987 23 : dfProgress += 0.08;
5988 23 : pfnProgress(dfProgress, nullptr, pProgressData);
5989 : }
5990 : }
5991 : }
5992 :
5993 25 : if (poLatLonSRS != nullptr)
5994 1 : delete poLatLonSRS;
5995 25 : if (poTransform != nullptr)
5996 1 : delete poTransform;
5997 :
5998 25 : CPLFree(padXVal);
5999 25 : CPLFree(padYVal);
6000 : } // Projected
6001 :
6002 : // If not projected/geographic and has geoloc
6003 57 : else if (!bIsGeographic && bHasGeoloc)
6004 : {
6005 : // Use
6006 : // https://cfconventions.org/Data/cf-conventions/cf-conventions-1.9/cf-conventions.html#_two_dimensional_latitude_longitude_coordinate_variables
6007 :
6008 4 : bool bOK = true;
6009 4 : double dfProgress = 0.2;
6010 :
6011 : // Make sure we are in data mode.
6012 4 : SetDefineMode(false);
6013 :
6014 : size_t startX[1];
6015 : size_t countX[1];
6016 : size_t startY[1];
6017 : size_t countY[1];
6018 4 : startX[0] = 0;
6019 4 : countX[0] = nRasterXSize;
6020 :
6021 4 : startY[0] = 0;
6022 4 : countY[0] = nRasterYSize;
6023 :
6024 8 : std::vector<double> adfXVal(nRasterXSize);
6025 16 : for (int i = 0; i < nRasterXSize; i++)
6026 12 : adfXVal[i] = i;
6027 :
6028 8 : std::vector<double> adfYVal(nRasterYSize);
6029 12 : for (int i = 0; i < nRasterYSize; i++)
6030 8 : adfYVal[i] = bBottomUp ? nRasterYSize - 1 - i : i;
6031 :
6032 4 : CPLDebug("GDAL_netCDF", "Writing X values");
6033 4 : int status = nc_put_vara_double(cdfid, nVarXID, startX, countX,
6034 4 : adfXVal.data());
6035 4 : NCDF_ERR(status);
6036 :
6037 4 : CPLDebug("GDAL_netCDF", "Writing Y values");
6038 4 : status = nc_put_vara_double(cdfid, nVarYID, startY, countY,
6039 4 : adfYVal.data());
6040 4 : NCDF_ERR(status);
6041 :
6042 4 : if (pfnProgress)
6043 0 : pfnProgress(0.20, nullptr, pProgressData);
6044 :
6045 4 : size_t start[] = {0, 0};
6046 4 : size_t count[] = {1, (size_t)nRasterXSize};
6047 : padLatVal =
6048 4 : static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
6049 : padLonVal =
6050 4 : static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
6051 :
6052 12 : for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR; j++)
6053 : {
6054 8 : start[0] = j;
6055 :
6056 8 : CPLErr eErr = GDALRasterIO(hBand_Y, GF_Read, 0,
6057 8 : bBottomUp ? nRasterYSize - 1 - j : j,
6058 : nRasterXSize, 1, padLatVal,
6059 : nRasterXSize, 1, GDT_Float64, 0, 0);
6060 8 : if (eErr == CE_None)
6061 : {
6062 8 : eErr = GDALRasterIO(hBand_X, GF_Read, 0,
6063 8 : bBottomUp ? nRasterYSize - 1 - j : j,
6064 : nRasterXSize, 1, padLonVal,
6065 : nRasterXSize, 1, GDT_Float64, 0, 0);
6066 : }
6067 :
6068 8 : if (eErr == CE_None)
6069 : {
6070 8 : bOK = true;
6071 : }
6072 : else
6073 : {
6074 0 : bOK = false;
6075 0 : CPLError(CE_Failure, CPLE_AppDefined,
6076 : "Unable to get scanline %d", j);
6077 : }
6078 :
6079 : // Write data.
6080 8 : if (bOK)
6081 : {
6082 8 : status = nc_put_vara_double(cdfid, nVarLatID, start, count,
6083 : padLatVal);
6084 8 : NCDF_ERR(status);
6085 8 : status = nc_put_vara_double(cdfid, nVarLonID, start, count,
6086 : padLonVal);
6087 8 : NCDF_ERR(status);
6088 : }
6089 :
6090 8 : if (pfnProgress && (nRasterYSize / 10) > 0 &&
6091 0 : (j % (nRasterYSize / 10) == 0))
6092 : {
6093 0 : dfProgress += 0.08;
6094 0 : pfnProgress(dfProgress, nullptr, pProgressData);
6095 : }
6096 4 : }
6097 : }
6098 :
6099 : // If not projected, assume geographic to catch grids without Datum.
6100 53 : else if (bWriteLonLat)
6101 : {
6102 : // Get latitude values.
6103 53 : const double dfY0 = (!bBottomUp) ? m_gt[3] :
6104 : // Invert latitude values.
6105 53 : m_gt[3] + (m_gt[5] * nRasterYSize);
6106 53 : const double dfDY = m_gt[5];
6107 :
6108 : // Override lat values with the ones in GEOLOCATION/Y_VALUES.
6109 53 : if (netCDFDataset::GetMetadataItem("Y_VALUES", "GEOLOCATION") !=
6110 : nullptr)
6111 : {
6112 0 : int nTemp = 0;
6113 0 : padLatVal = Get1DGeolocation("Y_VALUES", nTemp);
6114 : // Make sure we got the correct amount, if not fallback to GT */
6115 : // could add test fabs(fabs(padLatVal[0]) - fabs(dfY0)) <= 0.1))
6116 0 : if (nTemp == nRasterYSize)
6117 : {
6118 0 : CPLDebug(
6119 : "GDAL_netCDF",
6120 : "Using Y_VALUES geolocation metadata for lat values");
6121 : }
6122 : else
6123 : {
6124 0 : CPLDebug("GDAL_netCDF",
6125 : "Got %d elements from Y_VALUES geolocation "
6126 : "metadata, need %d",
6127 : nTemp, nRasterYSize);
6128 0 : if (padLatVal)
6129 : {
6130 0 : CPLFree(padLatVal);
6131 0 : padLatVal = nullptr;
6132 : }
6133 : }
6134 : }
6135 :
6136 53 : if (padLatVal == nullptr)
6137 : {
6138 : padLatVal = static_cast<double *>(
6139 53 : CPLMalloc(nRasterYSize * sizeof(double)));
6140 7105 : for (int i = 0; i < nRasterYSize; i++)
6141 : {
6142 : // The data point is centered inside the pixel.
6143 7052 : if (!bBottomUp)
6144 0 : padLatVal[i] = dfY0 + (i + 0.5) * dfDY;
6145 : else // Invert latitude values.
6146 7052 : padLatVal[i] = dfY0 - (i + 0.5) * dfDY;
6147 : }
6148 : }
6149 :
6150 53 : size_t startLat[1] = {0};
6151 53 : size_t countLat[1] = {static_cast<size_t>(nRasterYSize)};
6152 :
6153 : // Get longitude values.
6154 53 : const double dfX0 = m_gt[0];
6155 53 : const double dfDX = m_gt[1];
6156 :
6157 : padLonVal =
6158 53 : static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
6159 7157 : for (int i = 0; i < nRasterXSize; i++)
6160 : {
6161 : // The data point is centered inside the pixel.
6162 7104 : padLonVal[i] = dfX0 + (i + 0.5) * dfDX;
6163 : }
6164 :
6165 53 : size_t startLon[1] = {0};
6166 53 : size_t countLon[1] = {static_cast<size_t>(nRasterXSize)};
6167 :
6168 : // Write latitude and longitude values.
6169 :
6170 : // Make sure we are in data mode.
6171 53 : SetDefineMode(false);
6172 :
6173 : // Write values.
6174 53 : CPLDebug("GDAL_netCDF", "Writing lat values");
6175 :
6176 53 : int status = nc_put_vara_double(cdfid, nVarLatID, startLat,
6177 : countLat, padLatVal);
6178 53 : NCDF_ERR(status);
6179 :
6180 53 : CPLDebug("GDAL_netCDF", "Writing lon values");
6181 53 : status = nc_put_vara_double(cdfid, nVarLonID, startLon, countLon,
6182 : padLonVal);
6183 53 : NCDF_ERR(status);
6184 :
6185 : } // Not projected.
6186 :
6187 82 : CPLFree(padLatVal);
6188 82 : CPLFree(padLonVal);
6189 :
6190 82 : if (pfnProgress)
6191 41 : pfnProgress(1.00, nullptr, pProgressData);
6192 : }
6193 :
6194 164 : if (hDS_X != nullptr)
6195 : {
6196 10 : GDALClose(hDS_X);
6197 : }
6198 164 : if (hDS_Y != nullptr)
6199 : {
6200 10 : GDALClose(hDS_Y);
6201 : }
6202 :
6203 164 : return CE_None;
6204 : }
6205 :
6206 : // Write Projection variable to band variable.
6207 : // Moved from AddProjectionVars() for cases when bands are added after
6208 : // projection.
6209 376 : bool netCDFDataset::AddGridMappingRef()
6210 : {
6211 376 : bool bRet = true;
6212 376 : bool bOldDefineMode = bDefineMode;
6213 :
6214 567 : if ((GetAccess() == GA_Update) && (nBands >= 1) && (GetRasterBand(1)) &&
6215 191 : ((pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, "")) ||
6216 185 : (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))))
6217 : {
6218 73 : bAddedGridMappingRef = true;
6219 :
6220 : // Make sure we are in define mode.
6221 73 : SetDefineMode(true);
6222 :
6223 192 : for (int i = 1; i <= nBands; i++)
6224 : {
6225 : const int nVarId =
6226 119 : static_cast<netCDFRasterBand *>(GetRasterBand(i))->nZId;
6227 :
6228 119 : if (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))
6229 : {
6230 : int status =
6231 230 : nc_put_att_text(cdfid, nVarId, CF_GRD_MAPPING,
6232 115 : strlen(pszCFProjection), pszCFProjection);
6233 115 : NCDF_ERR(status);
6234 115 : if (status != NC_NOERR)
6235 0 : bRet = false;
6236 : }
6237 119 : if (pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, ""))
6238 : {
6239 : int status =
6240 6 : nc_put_att_text(cdfid, nVarId, CF_COORDINATES,
6241 : strlen(pszCFCoordinates), pszCFCoordinates);
6242 6 : NCDF_ERR(status);
6243 6 : if (status != NC_NOERR)
6244 0 : bRet = false;
6245 : }
6246 : }
6247 :
6248 : // Go back to previous define mode.
6249 73 : SetDefineMode(bOldDefineMode);
6250 : }
6251 376 : return bRet;
6252 : }
6253 :
6254 : /************************************************************************/
6255 : /* GetGeoTransform() */
6256 : /************************************************************************/
6257 :
6258 117 : CPLErr netCDFDataset::GetGeoTransform(GDALGeoTransform >) const
6259 :
6260 : {
6261 117 : gt = m_gt;
6262 117 : if (m_bHasGeoTransform)
6263 86 : return CE_None;
6264 :
6265 31 : return GDALPamDataset::GetGeoTransform(gt);
6266 : }
6267 :
6268 : /************************************************************************/
6269 : /* rint() */
6270 : /************************************************************************/
6271 :
6272 0 : double netCDFDataset::rint(double dfX)
6273 : {
6274 0 : return std::round(dfX);
6275 : }
6276 :
6277 : /************************************************************************/
6278 : /* NCDFReadIsoMetadata() */
6279 : /************************************************************************/
6280 :
6281 16 : static void NCDFReadMetadataAsJson(int cdfid, CPLJSONObject &obj)
6282 : {
6283 16 : int nbAttr = 0;
6284 16 : NCDF_ERR(nc_inq_varnatts(cdfid, NC_GLOBAL, &nbAttr));
6285 :
6286 32 : std::map<std::string, CPLJSONArray> oMapNameToArray;
6287 40 : for (int l = 0; l < nbAttr; l++)
6288 : {
6289 : char szAttrName[NC_MAX_NAME + 1];
6290 24 : szAttrName[0] = 0;
6291 24 : NCDF_ERR(nc_inq_attname(cdfid, NC_GLOBAL, l, szAttrName));
6292 :
6293 24 : char *pszMetaValue = nullptr;
6294 24 : if (NCDFGetAttr(cdfid, NC_GLOBAL, szAttrName, &pszMetaValue) == CE_None)
6295 : {
6296 24 : nc_type nAttrType = NC_NAT;
6297 24 : size_t nAttrLen = 0;
6298 :
6299 24 : NCDF_ERR(nc_inq_att(cdfid, NC_GLOBAL, szAttrName, &nAttrType,
6300 : &nAttrLen));
6301 :
6302 24 : std::string osAttrName(szAttrName);
6303 24 : const auto sharpPos = osAttrName.find('#');
6304 24 : if (sharpPos == std::string::npos)
6305 : {
6306 16 : if (nAttrType == NC_DOUBLE || nAttrType == NC_FLOAT)
6307 4 : obj.Add(osAttrName, CPLAtof(pszMetaValue));
6308 : else
6309 12 : obj.Add(osAttrName, pszMetaValue);
6310 : }
6311 : else
6312 : {
6313 8 : osAttrName.resize(sharpPos);
6314 8 : auto iter = oMapNameToArray.find(osAttrName);
6315 8 : if (iter == oMapNameToArray.end())
6316 : {
6317 8 : CPLJSONArray array;
6318 4 : obj.Add(osAttrName, array);
6319 4 : oMapNameToArray[osAttrName] = array;
6320 4 : array.Add(pszMetaValue);
6321 : }
6322 : else
6323 : {
6324 4 : iter->second.Add(pszMetaValue);
6325 : }
6326 : }
6327 24 : CPLFree(pszMetaValue);
6328 24 : pszMetaValue = nullptr;
6329 : }
6330 : }
6331 :
6332 16 : int nSubGroups = 0;
6333 16 : int *panSubGroupIds = nullptr;
6334 16 : NCDFGetSubGroups(cdfid, &nSubGroups, &panSubGroupIds);
6335 16 : oMapNameToArray.clear();
6336 28 : for (int i = 0; i < nSubGroups; i++)
6337 : {
6338 24 : CPLJSONObject subObj;
6339 12 : NCDFReadMetadataAsJson(panSubGroupIds[i], subObj);
6340 :
6341 24 : std::string osGroupName;
6342 12 : osGroupName.resize(NC_MAX_NAME);
6343 12 : NCDF_ERR(nc_inq_grpname(panSubGroupIds[i], &osGroupName[0]));
6344 12 : osGroupName.resize(strlen(osGroupName.data()));
6345 12 : const auto sharpPos = osGroupName.find('#');
6346 12 : if (sharpPos == std::string::npos)
6347 : {
6348 4 : obj.Add(osGroupName, subObj);
6349 : }
6350 : else
6351 : {
6352 8 : osGroupName.resize(sharpPos);
6353 8 : auto iter = oMapNameToArray.find(osGroupName);
6354 8 : if (iter == oMapNameToArray.end())
6355 : {
6356 8 : CPLJSONArray array;
6357 4 : obj.Add(osGroupName, array);
6358 4 : oMapNameToArray[osGroupName] = array;
6359 4 : array.Add(subObj);
6360 : }
6361 : else
6362 : {
6363 4 : iter->second.Add(subObj);
6364 : }
6365 : }
6366 : }
6367 16 : CPLFree(panSubGroupIds);
6368 16 : }
6369 :
6370 4 : std::string NCDFReadMetadataAsJson(int cdfid)
6371 : {
6372 8 : CPLJSONDocument oDoc;
6373 8 : CPLJSONObject oRoot = oDoc.GetRoot();
6374 4 : NCDFReadMetadataAsJson(cdfid, oRoot);
6375 8 : return oDoc.SaveAsString();
6376 : }
6377 :
6378 : /************************************************************************/
6379 : /* ReadAttributes() */
6380 : /************************************************************************/
6381 1811 : CPLErr netCDFDataset::ReadAttributes(int cdfidIn, int var)
6382 :
6383 : {
6384 1811 : char *pszVarFullName = nullptr;
6385 1811 : ERR_RET(NCDFGetVarFullName(cdfidIn, var, &pszVarFullName));
6386 :
6387 : // For metadata in Sentinel 5
6388 1811 : if (STARTS_WITH(pszVarFullName, "/METADATA/"))
6389 : {
6390 6 : for (const char *key :
6391 : {"ISO_METADATA", "ESA_METADATA", "EOP_METADATA", "QA_STATISTICS",
6392 8 : "GRANULE_DESCRIPTION", "ALGORITHM_SETTINGS"})
6393 : {
6394 14 : if (var == NC_GLOBAL &&
6395 7 : strcmp(pszVarFullName,
6396 : CPLSPrintf("/METADATA/%s/NC_GLOBAL", key)) == 0)
6397 : {
6398 1 : CPLFree(pszVarFullName);
6399 1 : CPLStringList aosList;
6400 2 : aosList.AddString(CPLString(NCDFReadMetadataAsJson(cdfidIn))
6401 1 : .replaceAll("\\/", '/'));
6402 1 : m_oMapDomainToJSon[key] = std::move(aosList);
6403 1 : return CE_None;
6404 : }
6405 : }
6406 : }
6407 1810 : if (STARTS_WITH(pszVarFullName, "/PRODUCT/SUPPORT_DATA/"))
6408 : {
6409 0 : CPLFree(pszVarFullName);
6410 0 : CPLStringList aosList;
6411 : aosList.AddString(
6412 0 : CPLString(NCDFReadMetadataAsJson(cdfidIn)).replaceAll("\\/", '/'));
6413 0 : m_oMapDomainToJSon["SUPPORT_DATA"] = std::move(aosList);
6414 0 : return CE_None;
6415 : }
6416 :
6417 1810 : size_t nMetaNameSize =
6418 1810 : sizeof(char) * (strlen(pszVarFullName) + 1 + NC_MAX_NAME + 1);
6419 1810 : char *pszMetaName = static_cast<char *>(CPLMalloc(nMetaNameSize));
6420 :
6421 1810 : int nbAttr = 0;
6422 1810 : NCDF_ERR(nc_inq_varnatts(cdfidIn, var, &nbAttr));
6423 :
6424 9083 : for (int l = 0; l < nbAttr; l++)
6425 : {
6426 : char szAttrName[NC_MAX_NAME + 1];
6427 7273 : szAttrName[0] = 0;
6428 7273 : NCDF_ERR(nc_inq_attname(cdfidIn, var, l, szAttrName));
6429 7273 : snprintf(pszMetaName, nMetaNameSize, "%s#%s", pszVarFullName,
6430 : szAttrName);
6431 :
6432 7273 : char *pszMetaTemp = nullptr;
6433 7273 : if (NCDFGetAttr(cdfidIn, var, szAttrName, &pszMetaTemp) == CE_None)
6434 : {
6435 7272 : papszMetadata =
6436 7272 : CSLSetNameValue(papszMetadata, pszMetaName, pszMetaTemp);
6437 7272 : CPLFree(pszMetaTemp);
6438 7272 : pszMetaTemp = nullptr;
6439 : }
6440 : else
6441 : {
6442 1 : CPLDebug("GDAL_netCDF", "invalid metadata %s", pszMetaName);
6443 : }
6444 : }
6445 :
6446 1810 : CPLFree(pszVarFullName);
6447 1810 : CPLFree(pszMetaName);
6448 :
6449 1810 : if (var == NC_GLOBAL)
6450 : {
6451 : // Recurse on sub-groups.
6452 527 : int nSubGroups = 0;
6453 527 : int *panSubGroupIds = nullptr;
6454 527 : NCDFGetSubGroups(cdfidIn, &nSubGroups, &panSubGroupIds);
6455 559 : for (int i = 0; i < nSubGroups; i++)
6456 : {
6457 32 : ReadAttributes(panSubGroupIds[i], var);
6458 : }
6459 527 : CPLFree(panSubGroupIds);
6460 : }
6461 :
6462 1810 : return CE_None;
6463 : }
6464 :
6465 : /************************************************************************/
6466 : /* netCDFDataset::CreateSubDatasetList() */
6467 : /************************************************************************/
6468 55 : void netCDFDataset::CreateSubDatasetList(int nGroupId)
6469 : {
6470 : char szVarStdName[NC_MAX_NAME + 1];
6471 55 : int *ponDimIds = nullptr;
6472 : nc_type nAttype;
6473 : size_t nAttlen;
6474 :
6475 55 : netCDFDataset *poDS = this;
6476 :
6477 : int nVarCount;
6478 55 : nc_inq_nvars(nGroupId, &nVarCount);
6479 :
6480 55 : const bool bListAllArrays = CPLTestBool(
6481 55 : CSLFetchNameValueDef(papszOpenOptions, "LIST_ALL_ARRAYS", "NO"));
6482 :
6483 338 : for (int nVar = 0; nVar < nVarCount; nVar++)
6484 : {
6485 :
6486 : int nDims;
6487 283 : nc_inq_varndims(nGroupId, nVar, &nDims);
6488 :
6489 283 : if ((bListAllArrays && nDims > 0) || nDims >= 2)
6490 : {
6491 162 : ponDimIds = static_cast<int *>(CPLCalloc(nDims, sizeof(int)));
6492 162 : nc_inq_vardimid(nGroupId, nVar, ponDimIds);
6493 :
6494 : // Create Sub dataset list.
6495 162 : CPLString osDim;
6496 499 : for (int i = 0; i < nDims; i++)
6497 : {
6498 : size_t nDimLen;
6499 337 : nc_inq_dimlen(nGroupId, ponDimIds[i], &nDimLen);
6500 337 : if (!osDim.empty())
6501 175 : osDim += 'x';
6502 337 : osDim += CPLSPrintf("%d", (int)nDimLen);
6503 : }
6504 162 : CPLFree(ponDimIds);
6505 :
6506 : nc_type nVarType;
6507 162 : nc_inq_vartype(nGroupId, nVar, &nVarType);
6508 162 : const char *pszType = "";
6509 162 : switch (nVarType)
6510 : {
6511 38 : case NC_BYTE:
6512 38 : pszType = "8-bit integer";
6513 38 : break;
6514 2 : case NC_CHAR:
6515 2 : pszType = "8-bit character";
6516 2 : break;
6517 6 : case NC_SHORT:
6518 6 : pszType = "16-bit integer";
6519 6 : break;
6520 10 : case NC_INT:
6521 10 : pszType = "32-bit integer";
6522 10 : break;
6523 54 : case NC_FLOAT:
6524 54 : pszType = "32-bit floating-point";
6525 54 : break;
6526 34 : case NC_DOUBLE:
6527 34 : pszType = "64-bit floating-point";
6528 34 : break;
6529 4 : case NC_UBYTE:
6530 4 : pszType = "8-bit unsigned integer";
6531 4 : break;
6532 1 : case NC_USHORT:
6533 1 : pszType = "16-bit unsigned integer";
6534 1 : break;
6535 1 : case NC_UINT:
6536 1 : pszType = "32-bit unsigned integer";
6537 1 : break;
6538 1 : case NC_INT64:
6539 1 : pszType = "64-bit integer";
6540 1 : break;
6541 1 : case NC_UINT64:
6542 1 : pszType = "64-bit unsigned integer";
6543 1 : break;
6544 10 : default:
6545 10 : break;
6546 : }
6547 :
6548 162 : char *pszName = nullptr;
6549 162 : if (NCDFGetVarFullName(nGroupId, nVar, &pszName) != CE_None)
6550 0 : continue;
6551 :
6552 162 : nSubDatasets++;
6553 :
6554 162 : nAttlen = 0;
6555 162 : nc_inq_att(nGroupId, nVar, CF_STD_NAME, &nAttype, &nAttlen);
6556 324 : if (nAttlen < sizeof(szVarStdName) &&
6557 162 : nc_get_att_text(nGroupId, nVar, CF_STD_NAME, szVarStdName) ==
6558 : NC_NOERR)
6559 : {
6560 56 : szVarStdName[nAttlen] = '\0';
6561 : }
6562 : else
6563 : {
6564 106 : snprintf(szVarStdName, sizeof(szVarStdName), "%s", pszName);
6565 : }
6566 :
6567 : char szTemp[NC_MAX_NAME + 1];
6568 162 : snprintf(szTemp, sizeof(szTemp), "SUBDATASET_%d_NAME",
6569 : nSubDatasets);
6570 :
6571 162 : if (strchr(pszName, ' ') || strchr(pszName, ':'))
6572 : {
6573 1 : poDS->papszSubDatasets = CSLSetNameValue(
6574 : poDS->papszSubDatasets, szTemp,
6575 : CPLSPrintf("NETCDF:\"%s\":\"%s\"", poDS->osFilename.c_str(),
6576 : pszName));
6577 : }
6578 : else
6579 : {
6580 161 : poDS->papszSubDatasets = CSLSetNameValue(
6581 : poDS->papszSubDatasets, szTemp,
6582 : CPLSPrintf("NETCDF:\"%s\":%s", poDS->osFilename.c_str(),
6583 : pszName));
6584 : }
6585 :
6586 162 : CPLFree(pszName);
6587 :
6588 162 : snprintf(szTemp, sizeof(szTemp), "SUBDATASET_%d_DESC",
6589 : nSubDatasets);
6590 :
6591 162 : poDS->papszSubDatasets =
6592 162 : CSLSetNameValue(poDS->papszSubDatasets, szTemp,
6593 : CPLSPrintf("[%s] %s (%s)", osDim.c_str(),
6594 : szVarStdName, pszType));
6595 : }
6596 : }
6597 :
6598 : // Recurse on sub groups.
6599 55 : int nSubGroups = 0;
6600 55 : int *panSubGroupIds = nullptr;
6601 55 : NCDFGetSubGroups(nGroupId, &nSubGroups, &panSubGroupIds);
6602 62 : for (int i = 0; i < nSubGroups; i++)
6603 : {
6604 7 : CreateSubDatasetList(panSubGroupIds[i]);
6605 : }
6606 55 : CPLFree(panSubGroupIds);
6607 55 : }
6608 :
6609 : /************************************************************************/
6610 : /* TestCapability() */
6611 : /************************************************************************/
6612 :
6613 248 : int netCDFDataset::TestCapability(const char *pszCap)
6614 : {
6615 248 : if (EQUAL(pszCap, ODsCCreateLayer))
6616 : {
6617 223 : return eAccess == GA_Update && nBands == 0 &&
6618 218 : (eMultipleLayerBehavior != SINGLE_LAYER ||
6619 229 : this->GetLayerCount() == 0 || bSGSupport);
6620 : }
6621 136 : else if (EQUAL(pszCap, ODsCZGeometries))
6622 2 : return true;
6623 :
6624 134 : return false;
6625 : }
6626 :
6627 : /************************************************************************/
6628 : /* GetLayer() */
6629 : /************************************************************************/
6630 :
6631 384 : OGRLayer *netCDFDataset::GetLayer(int nIdx)
6632 : {
6633 384 : if (nIdx < 0 || nIdx >= this->GetLayerCount())
6634 2 : return nullptr;
6635 382 : return papoLayers[nIdx].get();
6636 : }
6637 :
6638 : /************************************************************************/
6639 : /* ICreateLayer() */
6640 : /************************************************************************/
6641 :
6642 59 : OGRLayer *netCDFDataset::ICreateLayer(const char *pszName,
6643 : const OGRGeomFieldDefn *poGeomFieldDefn,
6644 : CSLConstList papszOptions)
6645 : {
6646 59 : int nLayerCDFId = cdfid;
6647 59 : if (!TestCapability(ODsCCreateLayer))
6648 0 : return nullptr;
6649 :
6650 59 : const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
6651 : const auto poSpatialRef =
6652 59 : poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
6653 :
6654 118 : CPLString osNetCDFLayerName(pszName);
6655 59 : const netCDFWriterConfigLayer *poLayerConfig = nullptr;
6656 59 : if (oWriterConfig.m_bIsValid)
6657 : {
6658 : std::map<CPLString, netCDFWriterConfigLayer>::const_iterator
6659 2 : oLayerIter = oWriterConfig.m_oLayers.find(pszName);
6660 2 : if (oLayerIter != oWriterConfig.m_oLayers.end())
6661 : {
6662 1 : poLayerConfig = &(oLayerIter->second);
6663 1 : osNetCDFLayerName = poLayerConfig->m_osNetCDFName;
6664 : }
6665 : }
6666 :
6667 59 : netCDFDataset *poLayerDataset = nullptr;
6668 59 : if (eMultipleLayerBehavior == SEPARATE_FILES)
6669 : {
6670 2 : char **papszDatasetOptions = nullptr;
6671 2 : papszDatasetOptions = CSLSetNameValue(
6672 : papszDatasetOptions, "CONFIG_FILE",
6673 2 : CSLFetchNameValue(papszCreationOptions, "CONFIG_FILE"));
6674 : papszDatasetOptions =
6675 2 : CSLSetNameValue(papszDatasetOptions, "FORMAT",
6676 2 : CSLFetchNameValue(papszCreationOptions, "FORMAT"));
6677 2 : papszDatasetOptions = CSLSetNameValue(
6678 : papszDatasetOptions, "WRITE_GDAL_TAGS",
6679 2 : CSLFetchNameValue(papszCreationOptions, "WRITE_GDAL_TAGS"));
6680 : const CPLString osLayerFilename(
6681 2 : CPLFormFilenameSafe(osFilename, osNetCDFLayerName, "nc"));
6682 2 : CPLAcquireMutex(hNCMutex, 1000.0);
6683 2 : poLayerDataset =
6684 2 : CreateLL(osLayerFilename, 0, 0, 0, papszDatasetOptions);
6685 2 : CPLReleaseMutex(hNCMutex);
6686 2 : CSLDestroy(papszDatasetOptions);
6687 2 : if (poLayerDataset == nullptr)
6688 0 : return nullptr;
6689 :
6690 2 : nLayerCDFId = poLayerDataset->cdfid;
6691 2 : NCDFAddGDALHistory(nLayerCDFId, osLayerFilename, bWriteGDALVersion,
6692 2 : bWriteGDALHistory, "", "Create",
6693 : NCDF_CONVENTIONS_CF_V1_6);
6694 : }
6695 57 : else if (eMultipleLayerBehavior == SEPARATE_GROUPS)
6696 : {
6697 2 : SetDefineMode(true);
6698 :
6699 2 : nLayerCDFId = -1;
6700 2 : int status = nc_def_grp(cdfid, osNetCDFLayerName, &nLayerCDFId);
6701 2 : NCDF_ERR(status);
6702 2 : if (status != NC_NOERR)
6703 0 : return nullptr;
6704 :
6705 2 : NCDFAddGDALHistory(nLayerCDFId, osFilename, bWriteGDALVersion,
6706 2 : bWriteGDALHistory, "", "Create",
6707 : NCDF_CONVENTIONS_CF_V1_6);
6708 : }
6709 :
6710 : // Make a clone to workaround a bug in released MapServer versions
6711 : // that destroys the passed SRS instead of releasing it .
6712 59 : OGRSpatialReference *poSRS = nullptr;
6713 59 : if (poSpatialRef)
6714 : {
6715 43 : poSRS = poSpatialRef->Clone();
6716 43 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
6717 : }
6718 : std::shared_ptr<netCDFLayer> poLayer(
6719 59 : new netCDFLayer(poLayerDataset ? poLayerDataset : this, nLayerCDFId,
6720 118 : osNetCDFLayerName, eGType, poSRS));
6721 59 : if (poSRS != nullptr)
6722 43 : poSRS->Release();
6723 :
6724 : // Fetch layer creation options coming from config file
6725 59 : char **papszNewOptions = CSLDuplicate(papszOptions);
6726 59 : if (oWriterConfig.m_bIsValid)
6727 : {
6728 2 : std::map<CPLString, CPLString>::const_iterator oIter;
6729 3 : for (oIter = oWriterConfig.m_oLayerCreationOptions.begin();
6730 3 : oIter != oWriterConfig.m_oLayerCreationOptions.end(); ++oIter)
6731 : {
6732 : papszNewOptions =
6733 1 : CSLSetNameValue(papszNewOptions, oIter->first, oIter->second);
6734 : }
6735 2 : if (poLayerConfig != nullptr)
6736 : {
6737 3 : for (oIter = poLayerConfig->m_oLayerCreationOptions.begin();
6738 3 : oIter != poLayerConfig->m_oLayerCreationOptions.end(); ++oIter)
6739 : {
6740 2 : papszNewOptions = CSLSetNameValue(papszNewOptions, oIter->first,
6741 2 : oIter->second);
6742 : }
6743 : }
6744 : }
6745 :
6746 59 : const bool bRet = poLayer->Create(papszNewOptions, poLayerConfig);
6747 59 : CSLDestroy(papszNewOptions);
6748 :
6749 59 : if (!bRet)
6750 : {
6751 0 : return nullptr;
6752 : }
6753 :
6754 59 : if (poLayerDataset != nullptr)
6755 2 : apoVectorDatasets.push_back(poLayerDataset);
6756 :
6757 59 : papoLayers.push_back(poLayer);
6758 59 : return poLayer.get();
6759 : }
6760 :
6761 : /************************************************************************/
6762 : /* CloneAttributes() */
6763 : /************************************************************************/
6764 :
6765 137 : bool netCDFDataset::CloneAttributes(int old_cdfid, int new_cdfid, int nSrcVarId,
6766 : int nDstVarId)
6767 : {
6768 137 : int nAttCount = -1;
6769 137 : int status = nc_inq_varnatts(old_cdfid, nSrcVarId, &nAttCount);
6770 137 : NCDF_ERR(status);
6771 :
6772 693 : for (int i = 0; i < nAttCount; i++)
6773 : {
6774 : char szName[NC_MAX_NAME + 1];
6775 556 : szName[0] = 0;
6776 556 : status = nc_inq_attname(old_cdfid, nSrcVarId, i, szName);
6777 556 : NCDF_ERR(status);
6778 :
6779 : status =
6780 556 : nc_copy_att(old_cdfid, nSrcVarId, szName, new_cdfid, nDstVarId);
6781 556 : NCDF_ERR(status);
6782 556 : if (status != NC_NOERR)
6783 0 : return false;
6784 : }
6785 :
6786 137 : return true;
6787 : }
6788 :
6789 : /************************************************************************/
6790 : /* CloneVariableContent() */
6791 : /************************************************************************/
6792 :
6793 121 : bool netCDFDataset::CloneVariableContent(int old_cdfid, int new_cdfid,
6794 : int nSrcVarId, int nDstVarId)
6795 : {
6796 121 : int nVarDimCount = -1;
6797 121 : int status = nc_inq_varndims(old_cdfid, nSrcVarId, &nVarDimCount);
6798 121 : NCDF_ERR(status);
6799 121 : int anDimIds[] = {-1, 1};
6800 121 : status = nc_inq_vardimid(old_cdfid, nSrcVarId, anDimIds);
6801 121 : NCDF_ERR(status);
6802 121 : nc_type nc_datatype = NC_NAT;
6803 121 : status = nc_inq_vartype(old_cdfid, nSrcVarId, &nc_datatype);
6804 121 : NCDF_ERR(status);
6805 121 : size_t nTypeSize = 0;
6806 121 : switch (nc_datatype)
6807 : {
6808 35 : case NC_BYTE:
6809 : case NC_CHAR:
6810 35 : nTypeSize = 1;
6811 35 : break;
6812 4 : case NC_SHORT:
6813 4 : nTypeSize = 2;
6814 4 : break;
6815 24 : case NC_INT:
6816 24 : nTypeSize = 4;
6817 24 : break;
6818 4 : case NC_FLOAT:
6819 4 : nTypeSize = 4;
6820 4 : break;
6821 43 : case NC_DOUBLE:
6822 43 : nTypeSize = 8;
6823 43 : break;
6824 2 : case NC_UBYTE:
6825 2 : nTypeSize = 1;
6826 2 : break;
6827 2 : case NC_USHORT:
6828 2 : nTypeSize = 2;
6829 2 : break;
6830 2 : case NC_UINT:
6831 2 : nTypeSize = 4;
6832 2 : break;
6833 4 : case NC_INT64:
6834 : case NC_UINT64:
6835 4 : nTypeSize = 8;
6836 4 : break;
6837 1 : case NC_STRING:
6838 1 : nTypeSize = sizeof(char *);
6839 1 : break;
6840 0 : default:
6841 : {
6842 0 : CPLError(CE_Failure, CPLE_NotSupported, "Unsupported data type: %d",
6843 : nc_datatype);
6844 0 : return false;
6845 : }
6846 : }
6847 :
6848 121 : size_t nElems = 1;
6849 : size_t anStart[NC_MAX_DIMS];
6850 : size_t anCount[NC_MAX_DIMS];
6851 121 : size_t nRecords = 1;
6852 261 : for (int i = 0; i < nVarDimCount; i++)
6853 : {
6854 140 : anStart[i] = 0;
6855 140 : if (i == 0)
6856 : {
6857 116 : anCount[i] = 1;
6858 116 : status = nc_inq_dimlen(old_cdfid, anDimIds[i], &nRecords);
6859 116 : NCDF_ERR(status);
6860 : }
6861 : else
6862 : {
6863 24 : anCount[i] = 0;
6864 24 : status = nc_inq_dimlen(old_cdfid, anDimIds[i], &anCount[i]);
6865 24 : NCDF_ERR(status);
6866 24 : nElems *= anCount[i];
6867 : }
6868 : }
6869 :
6870 : /* Workaround in some cases a netCDF bug:
6871 : * https://github.com/Unidata/netcdf-c/pull/1442 */
6872 121 : if (nRecords > 0 && nRecords < 10 * 1000 * 1000 / (nElems * nTypeSize))
6873 : {
6874 119 : nElems *= nRecords;
6875 119 : anCount[0] = nRecords;
6876 119 : nRecords = 1;
6877 : }
6878 :
6879 121 : void *pBuffer = VSI_MALLOC2_VERBOSE(nElems, nTypeSize);
6880 121 : if (pBuffer == nullptr)
6881 0 : return false;
6882 :
6883 240 : for (size_t iRecord = 0; iRecord < nRecords; iRecord++)
6884 : {
6885 119 : anStart[0] = iRecord;
6886 :
6887 119 : switch (nc_datatype)
6888 : {
6889 5 : case NC_BYTE:
6890 : status =
6891 5 : nc_get_vara_schar(old_cdfid, nSrcVarId, anStart, anCount,
6892 : static_cast<signed char *>(pBuffer));
6893 5 : if (!status)
6894 5 : status = nc_put_vara_schar(
6895 : new_cdfid, nDstVarId, anStart, anCount,
6896 : static_cast<signed char *>(pBuffer));
6897 5 : break;
6898 28 : case NC_CHAR:
6899 : status =
6900 28 : nc_get_vara_text(old_cdfid, nSrcVarId, anStart, anCount,
6901 : static_cast<char *>(pBuffer));
6902 28 : if (!status)
6903 : status =
6904 28 : nc_put_vara_text(new_cdfid, nDstVarId, anStart, anCount,
6905 : static_cast<char *>(pBuffer));
6906 28 : break;
6907 4 : case NC_SHORT:
6908 : status =
6909 4 : nc_get_vara_short(old_cdfid, nSrcVarId, anStart, anCount,
6910 : static_cast<short *>(pBuffer));
6911 4 : if (!status)
6912 4 : status = nc_put_vara_short(new_cdfid, nDstVarId, anStart,
6913 : anCount,
6914 : static_cast<short *>(pBuffer));
6915 4 : break;
6916 24 : case NC_INT:
6917 24 : status = nc_get_vara_int(old_cdfid, nSrcVarId, anStart, anCount,
6918 : static_cast<int *>(pBuffer));
6919 24 : if (!status)
6920 : status =
6921 24 : nc_put_vara_int(new_cdfid, nDstVarId, anStart, anCount,
6922 : static_cast<int *>(pBuffer));
6923 24 : break;
6924 4 : case NC_FLOAT:
6925 : status =
6926 4 : nc_get_vara_float(old_cdfid, nSrcVarId, anStart, anCount,
6927 : static_cast<float *>(pBuffer));
6928 4 : if (!status)
6929 4 : status = nc_put_vara_float(new_cdfid, nDstVarId, anStart,
6930 : anCount,
6931 : static_cast<float *>(pBuffer));
6932 4 : break;
6933 43 : case NC_DOUBLE:
6934 : status =
6935 43 : nc_get_vara_double(old_cdfid, nSrcVarId, anStart, anCount,
6936 : static_cast<double *>(pBuffer));
6937 43 : if (!status)
6938 43 : status = nc_put_vara_double(new_cdfid, nDstVarId, anStart,
6939 : anCount,
6940 : static_cast<double *>(pBuffer));
6941 43 : break;
6942 1 : case NC_STRING:
6943 : status =
6944 1 : nc_get_vara_string(old_cdfid, nSrcVarId, anStart, anCount,
6945 : static_cast<char **>(pBuffer));
6946 1 : if (!status)
6947 : {
6948 1 : status = nc_put_vara_string(
6949 : new_cdfid, nDstVarId, anStart, anCount,
6950 : static_cast<const char **>(pBuffer));
6951 1 : nc_free_string(nElems, static_cast<char **>(pBuffer));
6952 : }
6953 1 : break;
6954 :
6955 2 : case NC_UBYTE:
6956 : status =
6957 2 : nc_get_vara_uchar(old_cdfid, nSrcVarId, anStart, anCount,
6958 : static_cast<unsigned char *>(pBuffer));
6959 2 : if (!status)
6960 2 : status = nc_put_vara_uchar(
6961 : new_cdfid, nDstVarId, anStart, anCount,
6962 : static_cast<unsigned char *>(pBuffer));
6963 2 : break;
6964 2 : case NC_USHORT:
6965 : status =
6966 2 : nc_get_vara_ushort(old_cdfid, nSrcVarId, anStart, anCount,
6967 : static_cast<unsigned short *>(pBuffer));
6968 2 : if (!status)
6969 2 : status = nc_put_vara_ushort(
6970 : new_cdfid, nDstVarId, anStart, anCount,
6971 : static_cast<unsigned short *>(pBuffer));
6972 2 : break;
6973 2 : case NC_UINT:
6974 : status =
6975 2 : nc_get_vara_uint(old_cdfid, nSrcVarId, anStart, anCount,
6976 : static_cast<unsigned int *>(pBuffer));
6977 2 : if (!status)
6978 : status =
6979 2 : nc_put_vara_uint(new_cdfid, nDstVarId, anStart, anCount,
6980 : static_cast<unsigned int *>(pBuffer));
6981 2 : break;
6982 2 : case NC_INT64:
6983 : status =
6984 2 : nc_get_vara_longlong(old_cdfid, nSrcVarId, anStart, anCount,
6985 : static_cast<long long *>(pBuffer));
6986 2 : if (!status)
6987 2 : status = nc_put_vara_longlong(
6988 : new_cdfid, nDstVarId, anStart, anCount,
6989 : static_cast<long long *>(pBuffer));
6990 2 : break;
6991 2 : case NC_UINT64:
6992 2 : status = nc_get_vara_ulonglong(
6993 : old_cdfid, nSrcVarId, anStart, anCount,
6994 : static_cast<unsigned long long *>(pBuffer));
6995 2 : if (!status)
6996 2 : status = nc_put_vara_ulonglong(
6997 : new_cdfid, nDstVarId, anStart, anCount,
6998 : static_cast<unsigned long long *>(pBuffer));
6999 2 : break;
7000 0 : default:
7001 0 : status = NC_EBADTYPE;
7002 : }
7003 :
7004 119 : NCDF_ERR(status);
7005 119 : if (status != NC_NOERR)
7006 : {
7007 0 : VSIFree(pBuffer);
7008 0 : return false;
7009 : }
7010 : }
7011 :
7012 121 : VSIFree(pBuffer);
7013 121 : return true;
7014 : }
7015 :
7016 : /************************************************************************/
7017 : /* NCDFIsUnlimitedDim() */
7018 : /************************************************************************/
7019 :
7020 58 : bool NCDFIsUnlimitedDim(bool bIsNC4, int cdfid, int nDimId)
7021 : {
7022 58 : if (bIsNC4)
7023 : {
7024 16 : int nUnlimitedDims = 0;
7025 16 : nc_inq_unlimdims(cdfid, &nUnlimitedDims, nullptr);
7026 16 : bool bFound = false;
7027 16 : if (nUnlimitedDims)
7028 : {
7029 : int *panUnlimitedDimIds =
7030 16 : static_cast<int *>(CPLMalloc(sizeof(int) * nUnlimitedDims));
7031 16 : nc_inq_unlimdims(cdfid, nullptr, panUnlimitedDimIds);
7032 30 : for (int i = 0; i < nUnlimitedDims; i++)
7033 : {
7034 22 : if (panUnlimitedDimIds[i] == nDimId)
7035 : {
7036 8 : bFound = true;
7037 8 : break;
7038 : }
7039 : }
7040 16 : CPLFree(panUnlimitedDimIds);
7041 : }
7042 16 : return bFound;
7043 : }
7044 : else
7045 : {
7046 42 : int nUnlimitedDimId = -1;
7047 42 : nc_inq(cdfid, nullptr, nullptr, nullptr, &nUnlimitedDimId);
7048 42 : return nDimId == nUnlimitedDimId;
7049 : }
7050 : }
7051 :
7052 : /************************************************************************/
7053 : /* CloneGrp() */
7054 : /************************************************************************/
7055 :
7056 16 : bool netCDFDataset::CloneGrp(int nOldGrpId, int nNewGrpId, bool bIsNC4,
7057 : int nLayerId, int nDimIdToGrow, size_t nNewSize)
7058 : {
7059 : // Clone dimensions
7060 16 : int nDimCount = -1;
7061 16 : int status = nc_inq_ndims(nOldGrpId, &nDimCount);
7062 16 : NCDF_ERR(status);
7063 16 : if (nDimCount < 0 || nDimCount > NC_MAX_DIMS)
7064 0 : return false;
7065 : int anDimIds[NC_MAX_DIMS];
7066 16 : int nUnlimiDimID = -1;
7067 16 : status = nc_inq_unlimdim(nOldGrpId, &nUnlimiDimID);
7068 16 : NCDF_ERR(status);
7069 16 : if (bIsNC4)
7070 : {
7071 : // In NC4, the dimension ids of a group are not necessarily in
7072 : // [0,nDimCount-1] range
7073 8 : int nDimCount2 = -1;
7074 8 : status = nc_inq_dimids(nOldGrpId, &nDimCount2, anDimIds, FALSE);
7075 8 : NCDF_ERR(status);
7076 8 : CPLAssert(nDimCount == nDimCount2);
7077 : }
7078 : else
7079 : {
7080 36 : for (int i = 0; i < nDimCount; i++)
7081 28 : anDimIds[i] = i;
7082 : }
7083 60 : for (int i = 0; i < nDimCount; i++)
7084 : {
7085 : char szDimName[NC_MAX_NAME + 1];
7086 44 : szDimName[0] = 0;
7087 44 : size_t nLen = 0;
7088 44 : const int nDimId = anDimIds[i];
7089 44 : status = nc_inq_dim(nOldGrpId, nDimId, szDimName, &nLen);
7090 44 : NCDF_ERR(status);
7091 44 : if (NCDFIsUnlimitedDim(bIsNC4, nOldGrpId, nDimId))
7092 16 : nLen = NC_UNLIMITED;
7093 28 : else if (nDimId == nDimIdToGrow && nOldGrpId == nLayerId)
7094 13 : nLen = nNewSize;
7095 44 : int nNewDimId = -1;
7096 44 : status = nc_def_dim(nNewGrpId, szDimName, nLen, &nNewDimId);
7097 44 : NCDF_ERR(status);
7098 44 : CPLAssert(nDimId == nNewDimId);
7099 44 : if (status != NC_NOERR)
7100 : {
7101 0 : return false;
7102 : }
7103 : }
7104 :
7105 : // Clone main attributes
7106 16 : if (!CloneAttributes(nOldGrpId, nNewGrpId, NC_GLOBAL, NC_GLOBAL))
7107 : {
7108 0 : return false;
7109 : }
7110 :
7111 : // Clone variable definitions
7112 16 : int nVarCount = -1;
7113 16 : status = nc_inq_nvars(nOldGrpId, &nVarCount);
7114 16 : NCDF_ERR(status);
7115 :
7116 137 : for (int i = 0; i < nVarCount; i++)
7117 : {
7118 : char szVarName[NC_MAX_NAME + 1];
7119 121 : szVarName[0] = 0;
7120 121 : status = nc_inq_varname(nOldGrpId, i, szVarName);
7121 121 : NCDF_ERR(status);
7122 121 : nc_type nc_datatype = NC_NAT;
7123 121 : status = nc_inq_vartype(nOldGrpId, i, &nc_datatype);
7124 121 : NCDF_ERR(status);
7125 121 : int nVarDimCount = -1;
7126 121 : status = nc_inq_varndims(nOldGrpId, i, &nVarDimCount);
7127 121 : NCDF_ERR(status);
7128 121 : status = nc_inq_vardimid(nOldGrpId, i, anDimIds);
7129 121 : NCDF_ERR(status);
7130 121 : int nNewVarId = -1;
7131 121 : status = nc_def_var(nNewGrpId, szVarName, nc_datatype, nVarDimCount,
7132 : anDimIds, &nNewVarId);
7133 121 : NCDF_ERR(status);
7134 121 : CPLAssert(i == nNewVarId);
7135 121 : if (status != NC_NOERR)
7136 : {
7137 0 : return false;
7138 : }
7139 :
7140 121 : if (!CloneAttributes(nOldGrpId, nNewGrpId, i, i))
7141 : {
7142 0 : return false;
7143 : }
7144 : }
7145 :
7146 16 : status = nc_enddef(nNewGrpId);
7147 16 : NCDF_ERR(status);
7148 16 : if (status != NC_NOERR)
7149 : {
7150 0 : return false;
7151 : }
7152 :
7153 : // Clone variable content
7154 137 : for (int i = 0; i < nVarCount; i++)
7155 : {
7156 121 : if (!CloneVariableContent(nOldGrpId, nNewGrpId, i, i))
7157 : {
7158 0 : return false;
7159 : }
7160 : }
7161 :
7162 16 : return true;
7163 : }
7164 :
7165 : /************************************************************************/
7166 : /* GrowDim() */
7167 : /************************************************************************/
7168 :
7169 13 : bool netCDFDataset::GrowDim(int nLayerId, int nDimIdToGrow, size_t nNewSize)
7170 : {
7171 : int nCreationMode;
7172 : // Set nCreationMode based on eFormat.
7173 13 : switch (eFormat)
7174 : {
7175 : #ifdef NETCDF_HAS_NC2
7176 0 : case NCDF_FORMAT_NC2:
7177 0 : nCreationMode = NC_CLOBBER | NC_64BIT_OFFSET;
7178 0 : break;
7179 : #endif
7180 5 : case NCDF_FORMAT_NC4:
7181 5 : nCreationMode = NC_CLOBBER | NC_NETCDF4;
7182 5 : break;
7183 0 : case NCDF_FORMAT_NC4C:
7184 0 : nCreationMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
7185 0 : break;
7186 8 : case NCDF_FORMAT_NC:
7187 : default:
7188 8 : nCreationMode = NC_CLOBBER;
7189 8 : break;
7190 : }
7191 :
7192 13 : int new_cdfid = -1;
7193 26 : CPLString osTmpFilename(osFilename + ".tmp");
7194 26 : CPLString osFilenameForNCCreate(osTmpFilename);
7195 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
7196 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
7197 : {
7198 : char *pszTemp =
7199 : CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
7200 : osFilenameForNCCreate = pszTemp;
7201 : CPLFree(pszTemp);
7202 : }
7203 : #endif
7204 13 : int status = nc_create(osFilenameForNCCreate, nCreationMode, &new_cdfid);
7205 13 : NCDF_ERR(status);
7206 13 : if (status != NC_NOERR)
7207 0 : return false;
7208 :
7209 13 : if (!CloneGrp(cdfid, new_cdfid, eFormat == NCDF_FORMAT_NC4, nLayerId,
7210 : nDimIdToGrow, nNewSize))
7211 : {
7212 0 : GDAL_nc_close(new_cdfid);
7213 0 : return false;
7214 : }
7215 :
7216 13 : int nGroupCount = 0;
7217 26 : std::vector<CPLString> oListGrpName;
7218 31 : if (eFormat == NCDF_FORMAT_NC4 &&
7219 18 : nc_inq_grps(cdfid, &nGroupCount, nullptr) == NC_NOERR &&
7220 5 : nGroupCount > 0)
7221 : {
7222 : int *panGroupIds =
7223 2 : static_cast<int *>(CPLMalloc(sizeof(int) * nGroupCount));
7224 2 : status = nc_inq_grps(cdfid, nullptr, panGroupIds);
7225 2 : NCDF_ERR(status);
7226 5 : for (int i = 0; i < nGroupCount; i++)
7227 : {
7228 : char szGroupName[NC_MAX_NAME + 1];
7229 3 : szGroupName[0] = 0;
7230 3 : NCDF_ERR(nc_inq_grpname(panGroupIds[i], szGroupName));
7231 3 : int nNewGrpId = -1;
7232 3 : status = nc_def_grp(new_cdfid, szGroupName, &nNewGrpId);
7233 3 : NCDF_ERR(status);
7234 3 : if (status != NC_NOERR)
7235 : {
7236 0 : CPLFree(panGroupIds);
7237 0 : GDAL_nc_close(new_cdfid);
7238 0 : return false;
7239 : }
7240 3 : if (!CloneGrp(panGroupIds[i], nNewGrpId, /*bIsNC4=*/true, nLayerId,
7241 : nDimIdToGrow, nNewSize))
7242 : {
7243 0 : CPLFree(panGroupIds);
7244 0 : GDAL_nc_close(new_cdfid);
7245 0 : return false;
7246 : }
7247 : }
7248 2 : CPLFree(panGroupIds);
7249 :
7250 5 : for (int i = 0; i < this->GetLayerCount(); i++)
7251 : {
7252 3 : auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
7253 3 : if (poLayer)
7254 : {
7255 : char szGroupName[NC_MAX_NAME + 1];
7256 3 : szGroupName[0] = 0;
7257 3 : status = nc_inq_grpname(poLayer->GetCDFID(), szGroupName);
7258 3 : NCDF_ERR(status);
7259 3 : oListGrpName.push_back(szGroupName);
7260 : }
7261 : }
7262 : }
7263 :
7264 13 : GDAL_nc_close(cdfid);
7265 13 : cdfid = -1;
7266 13 : GDAL_nc_close(new_cdfid);
7267 :
7268 26 : CPLString osOriFilename(osFilename + ".ori");
7269 26 : if (VSIRename(osFilename, osOriFilename) != 0 ||
7270 13 : VSIRename(osTmpFilename, osFilename) != 0)
7271 : {
7272 0 : CPLError(CE_Failure, CPLE_FileIO, "Renaming of files failed");
7273 0 : return false;
7274 : }
7275 13 : VSIUnlink(osOriFilename);
7276 :
7277 26 : CPLString osFilenameForNCOpen(osFilename);
7278 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
7279 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
7280 : {
7281 : char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
7282 : osFilenameForNCOpen = pszTemp;
7283 : CPLFree(pszTemp);
7284 : }
7285 : #endif
7286 13 : status = GDAL_nc_open(osFilenameForNCOpen, NC_WRITE, &cdfid);
7287 13 : NCDF_ERR(status);
7288 13 : if (status != NC_NOERR)
7289 0 : return false;
7290 13 : bDefineMode = false;
7291 :
7292 13 : if (!oListGrpName.empty())
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 3 : int nNewLayerCDFID = -1;
7300 3 : status = nc_inq_ncid(cdfid, oListGrpName[i].c_str(),
7301 : &nNewLayerCDFID);
7302 3 : NCDF_ERR(status);
7303 3 : poLayer->SetCDFID(nNewLayerCDFID);
7304 : }
7305 : }
7306 : }
7307 : else
7308 : {
7309 22 : for (int i = 0; i < this->GetLayerCount(); i++)
7310 : {
7311 11 : auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
7312 11 : if (poLayer)
7313 11 : poLayer->SetCDFID(cdfid);
7314 : }
7315 : }
7316 :
7317 13 : return true;
7318 : }
7319 :
7320 : #ifdef ENABLE_NCDUMP
7321 :
7322 : /************************************************************************/
7323 : /* netCDFDatasetCreateTempFile() */
7324 : /************************************************************************/
7325 :
7326 : /* Create a netCDF file from a text dump (format of ncdump) */
7327 : /* Mostly to easy fuzzing of the driver, while still generating valid */
7328 : /* netCDF files. */
7329 : /* Note: not all data types are supported ! */
7330 4 : bool netCDFDatasetCreateTempFile(NetCDFFormatEnum eFormat,
7331 : const char *pszTmpFilename, VSILFILE *fpSrc)
7332 : {
7333 4 : CPL_IGNORE_RET_VAL(eFormat);
7334 4 : int nCreateMode = NC_CLOBBER;
7335 4 : if (eFormat == NCDF_FORMAT_NC4)
7336 1 : nCreateMode |= NC_NETCDF4;
7337 3 : else if (eFormat == NCDF_FORMAT_NC4C)
7338 0 : nCreateMode |= NC_NETCDF4 | NC_CLASSIC_MODEL;
7339 4 : int nCdfId = -1;
7340 4 : int status = nc_create(pszTmpFilename, nCreateMode, &nCdfId);
7341 4 : if (status != NC_NOERR)
7342 : {
7343 0 : return false;
7344 : }
7345 4 : VSIFSeekL(fpSrc, 0, SEEK_SET);
7346 : const char *pszLine;
7347 4 : constexpr int SECTION_NONE = 0;
7348 4 : constexpr int SECTION_DIMENSIONS = 1;
7349 4 : constexpr int SECTION_VARIABLES = 2;
7350 4 : constexpr int SECTION_DATA = 3;
7351 4 : int nActiveSection = SECTION_NONE;
7352 8 : std::map<CPLString, int> oMapDimToId;
7353 8 : std::map<int, int> oMapDimIdToDimLen;
7354 8 : std::map<CPLString, int> oMapVarToId;
7355 8 : std::map<int, std::vector<int>> oMapVarIdToVectorOfDimId;
7356 8 : std::map<int, int> oMapVarIdToType;
7357 4 : std::set<CPLString> oSetAttrDefined;
7358 4 : oMapVarToId[""] = -1;
7359 4 : size_t nTotalVarSize = 0;
7360 208 : while ((pszLine = CPLReadLineL(fpSrc)) != nullptr)
7361 : {
7362 204 : if (STARTS_WITH(pszLine, "dimensions:") &&
7363 : nActiveSection == SECTION_NONE)
7364 : {
7365 4 : nActiveSection = SECTION_DIMENSIONS;
7366 : }
7367 200 : else if (STARTS_WITH(pszLine, "variables:") &&
7368 : nActiveSection == SECTION_DIMENSIONS)
7369 : {
7370 4 : nActiveSection = SECTION_VARIABLES;
7371 : }
7372 196 : else if (STARTS_WITH(pszLine, "data:") &&
7373 : nActiveSection == SECTION_VARIABLES)
7374 : {
7375 4 : nActiveSection = SECTION_DATA;
7376 4 : status = nc_enddef(nCdfId);
7377 4 : if (status != NC_NOERR)
7378 : {
7379 0 : CPLDebug("netCDF", "nc_enddef() failed: %s",
7380 : nc_strerror(status));
7381 : }
7382 : }
7383 192 : else if (nActiveSection == SECTION_DIMENSIONS)
7384 : {
7385 9 : char **papszTokens = CSLTokenizeString2(pszLine, " \t=;", 0);
7386 9 : if (CSLCount(papszTokens) == 2)
7387 : {
7388 9 : const char *pszDimName = papszTokens[0];
7389 9 : bool bValidName = true;
7390 9 : if (STARTS_WITH(pszDimName, "_nc4_non_coord_"))
7391 : {
7392 : // This is an internal netcdf prefix. Using it may
7393 : // cause memory leaks.
7394 0 : bValidName = false;
7395 : }
7396 9 : if (!bValidName)
7397 : {
7398 0 : CPLDebug("netCDF",
7399 : "nc_def_dim(%s) failed: invalid name found",
7400 : pszDimName);
7401 0 : CSLDestroy(papszTokens);
7402 0 : continue;
7403 : }
7404 :
7405 : const bool bIsASCII =
7406 9 : CPLIsASCII(pszDimName, static_cast<size_t>(-1));
7407 9 : if (!bIsASCII)
7408 : {
7409 : // Workaround https://github.com/Unidata/netcdf-c/pull/450
7410 0 : CPLDebug("netCDF",
7411 : "nc_def_dim(%s) failed: rejected because "
7412 : "of non-ASCII characters",
7413 : pszDimName);
7414 0 : CSLDestroy(papszTokens);
7415 0 : continue;
7416 : }
7417 9 : int nDimSize = EQUAL(papszTokens[1], "UNLIMITED")
7418 : ? NC_UNLIMITED
7419 9 : : atoi(papszTokens[1]);
7420 9 : if (nDimSize >= 1000)
7421 1 : nDimSize = 1000; // to avoid very long processing
7422 9 : if (nDimSize >= 0)
7423 : {
7424 9 : int nDimId = -1;
7425 9 : status = nc_def_dim(nCdfId, pszDimName, nDimSize, &nDimId);
7426 9 : if (status != NC_NOERR)
7427 : {
7428 0 : CPLDebug("netCDF", "nc_def_dim(%s, %d) failed: %s",
7429 : pszDimName, nDimSize, nc_strerror(status));
7430 : }
7431 : else
7432 : {
7433 : #ifdef DEBUG_VERBOSE
7434 : CPLDebug("netCDF", "nc_def_dim(%s, %d) (%s) succeeded",
7435 : pszDimName, nDimSize, pszLine);
7436 : #endif
7437 9 : oMapDimToId[pszDimName] = nDimId;
7438 9 : oMapDimIdToDimLen[nDimId] = nDimSize;
7439 : }
7440 : }
7441 : }
7442 9 : CSLDestroy(papszTokens);
7443 : }
7444 183 : else if (nActiveSection == SECTION_VARIABLES)
7445 : {
7446 390 : while (*pszLine == ' ' || *pszLine == '\t')
7447 249 : pszLine++;
7448 141 : const char *pszColumn = strchr(pszLine, ':');
7449 141 : const char *pszEqual = strchr(pszLine, '=');
7450 141 : if (pszColumn == nullptr)
7451 : {
7452 21 : char **papszTokens = CSLTokenizeString2(pszLine, " \t=(),;", 0);
7453 21 : if (CSLCount(papszTokens) >= 2)
7454 : {
7455 17 : const char *pszVarName = papszTokens[1];
7456 17 : bool bValidName = true;
7457 17 : if (STARTS_WITH(pszVarName, "_nc4_non_coord_"))
7458 : {
7459 : // This is an internal netcdf prefix. Using it may
7460 : // cause memory leaks.
7461 0 : bValidName = false;
7462 : }
7463 138 : for (int i = 0; pszVarName[i]; i++)
7464 : {
7465 121 : if (!((pszVarName[i] >= 'a' && pszVarName[i] <= 'z') ||
7466 28 : (pszVarName[i] >= 'A' && pszVarName[i] <= 'Z') ||
7467 9 : (pszVarName[i] >= '0' && pszVarName[i] <= '9') ||
7468 6 : pszVarName[i] == '_'))
7469 : {
7470 0 : bValidName = false;
7471 : }
7472 : }
7473 17 : if (!bValidName)
7474 : {
7475 0 : CPLDebug(
7476 : "netCDF",
7477 : "nc_def_var(%s) failed: illegal character found",
7478 : pszVarName);
7479 0 : CSLDestroy(papszTokens);
7480 0 : continue;
7481 : }
7482 17 : if (oMapVarToId.find(pszVarName) != oMapVarToId.end())
7483 : {
7484 0 : CPLDebug("netCDF",
7485 : "nc_def_var(%s) failed: already defined",
7486 : pszVarName);
7487 0 : CSLDestroy(papszTokens);
7488 0 : continue;
7489 : }
7490 17 : const char *pszVarType = papszTokens[0];
7491 17 : int nc_datatype = NC_BYTE;
7492 17 : size_t nDataTypeSize = 1;
7493 17 : if (EQUAL(pszVarType, "char"))
7494 : {
7495 6 : nc_datatype = NC_CHAR;
7496 6 : nDataTypeSize = 1;
7497 : }
7498 11 : else if (EQUAL(pszVarType, "byte"))
7499 : {
7500 3 : nc_datatype = NC_BYTE;
7501 3 : nDataTypeSize = 1;
7502 : }
7503 8 : else if (EQUAL(pszVarType, "short"))
7504 : {
7505 0 : nc_datatype = NC_SHORT;
7506 0 : nDataTypeSize = 2;
7507 : }
7508 8 : else if (EQUAL(pszVarType, "int"))
7509 : {
7510 0 : nc_datatype = NC_INT;
7511 0 : nDataTypeSize = 4;
7512 : }
7513 8 : else if (EQUAL(pszVarType, "float"))
7514 : {
7515 0 : nc_datatype = NC_FLOAT;
7516 0 : nDataTypeSize = 4;
7517 : }
7518 8 : else if (EQUAL(pszVarType, "double"))
7519 : {
7520 8 : nc_datatype = NC_DOUBLE;
7521 8 : nDataTypeSize = 8;
7522 : }
7523 0 : else if (EQUAL(pszVarType, "ubyte"))
7524 : {
7525 0 : nc_datatype = NC_UBYTE;
7526 0 : nDataTypeSize = 1;
7527 : }
7528 0 : else if (EQUAL(pszVarType, "ushort"))
7529 : {
7530 0 : nc_datatype = NC_USHORT;
7531 0 : nDataTypeSize = 2;
7532 : }
7533 0 : else if (EQUAL(pszVarType, "uint"))
7534 : {
7535 0 : nc_datatype = NC_UINT;
7536 0 : nDataTypeSize = 4;
7537 : }
7538 0 : else if (EQUAL(pszVarType, "int64"))
7539 : {
7540 0 : nc_datatype = NC_INT64;
7541 0 : nDataTypeSize = 8;
7542 : }
7543 0 : else if (EQUAL(pszVarType, "uint64"))
7544 : {
7545 0 : nc_datatype = NC_UINT64;
7546 0 : nDataTypeSize = 8;
7547 : }
7548 :
7549 17 : int nDims = CSLCount(papszTokens) - 2;
7550 17 : if (nDims >= 32)
7551 : {
7552 : // The number of dimensions in a netCDFv4 file is
7553 : // limited by #define H5S_MAX_RANK 32
7554 : // but libnetcdf doesn't check that...
7555 0 : CPLDebug("netCDF",
7556 : "nc_def_var(%s) failed: too many dimensions",
7557 : pszVarName);
7558 0 : CSLDestroy(papszTokens);
7559 0 : continue;
7560 : }
7561 17 : std::vector<int> aoDimIds;
7562 17 : bool bFailed = false;
7563 17 : size_t nSize = 1;
7564 35 : for (int i = 0; i < nDims; i++)
7565 : {
7566 18 : const char *pszDimName = papszTokens[2 + i];
7567 18 : if (oMapDimToId.find(pszDimName) == oMapDimToId.end())
7568 : {
7569 0 : bFailed = true;
7570 0 : break;
7571 : }
7572 18 : const int nDimId = oMapDimToId[pszDimName];
7573 18 : aoDimIds.push_back(nDimId);
7574 :
7575 18 : const size_t nDimSize = oMapDimIdToDimLen[nDimId];
7576 18 : if (nDimSize != 0)
7577 : {
7578 18 : if (nSize >
7579 18 : std::numeric_limits<size_t>::max() / nDimSize)
7580 : {
7581 0 : bFailed = true;
7582 0 : break;
7583 : }
7584 : else
7585 : {
7586 18 : nSize *= nDimSize;
7587 : }
7588 : }
7589 : }
7590 17 : if (bFailed)
7591 : {
7592 0 : CPLDebug("netCDF",
7593 : "nc_def_var(%s) failed: unknown dimension(s)",
7594 : pszVarName);
7595 0 : CSLDestroy(papszTokens);
7596 0 : continue;
7597 : }
7598 17 : if (nSize > 100U * 1024 * 1024 / nDataTypeSize)
7599 : {
7600 0 : CPLDebug("netCDF",
7601 : "nc_def_var(%s) failed: too large data",
7602 : pszVarName);
7603 0 : CSLDestroy(papszTokens);
7604 0 : continue;
7605 : }
7606 17 : if (nTotalVarSize >
7607 34 : std::numeric_limits<size_t>::max() - nSize ||
7608 17 : nTotalVarSize + nSize > 100 * 1024 * 1024)
7609 : {
7610 0 : CPLDebug("netCDF",
7611 : "nc_def_var(%s) failed: too large data",
7612 : pszVarName);
7613 0 : CSLDestroy(papszTokens);
7614 0 : continue;
7615 : }
7616 17 : nTotalVarSize += nSize;
7617 :
7618 17 : int nVarId = -1;
7619 : status =
7620 30 : nc_def_var(nCdfId, pszVarName, nc_datatype, nDims,
7621 13 : (nDims) ? &aoDimIds[0] : nullptr, &nVarId);
7622 17 : if (status != NC_NOERR)
7623 : {
7624 0 : CPLDebug("netCDF", "nc_def_var(%s) failed: %s",
7625 : pszVarName, nc_strerror(status));
7626 : }
7627 : else
7628 : {
7629 : #ifdef DEBUG_VERBOSE
7630 : CPLDebug("netCDF", "nc_def_var(%s) (%s) succeeded",
7631 : pszVarName, pszLine);
7632 : #endif
7633 17 : oMapVarToId[pszVarName] = nVarId;
7634 17 : oMapVarIdToType[nVarId] = nc_datatype;
7635 17 : oMapVarIdToVectorOfDimId[nVarId] = std::move(aoDimIds);
7636 : }
7637 : }
7638 21 : CSLDestroy(papszTokens);
7639 : }
7640 120 : else if (pszEqual != nullptr && pszEqual - pszColumn > 0)
7641 : {
7642 116 : CPLString osVarName(pszLine, pszColumn - pszLine);
7643 116 : CPLString osAttrName(pszColumn + 1, pszEqual - pszColumn - 1);
7644 116 : osAttrName.Trim();
7645 116 : if (oMapVarToId.find(osVarName) == oMapVarToId.end())
7646 : {
7647 0 : CPLDebug("netCDF",
7648 : "nc_put_att(%s:%s) failed: "
7649 : "no corresponding variable",
7650 : osVarName.c_str(), osAttrName.c_str());
7651 0 : continue;
7652 : }
7653 116 : bool bValidName = true;
7654 1743 : for (size_t i = 0; i < osAttrName.size(); i++)
7655 : {
7656 1865 : if (!((osAttrName[i] >= 'a' && osAttrName[i] <= 'z') ||
7657 238 : (osAttrName[i] >= 'A' && osAttrName[i] <= 'Z') ||
7658 158 : (osAttrName[i] >= '0' && osAttrName[i] <= '9') ||
7659 158 : osAttrName[i] == '_'))
7660 : {
7661 0 : bValidName = false;
7662 : }
7663 : }
7664 116 : if (!bValidName)
7665 : {
7666 0 : CPLDebug(
7667 : "netCDF",
7668 : "nc_put_att(%s:%s) failed: illegal character found",
7669 : osVarName.c_str(), osAttrName.c_str());
7670 0 : continue;
7671 : }
7672 116 : if (oSetAttrDefined.find(osVarName + ":" + osAttrName) !=
7673 232 : oSetAttrDefined.end())
7674 : {
7675 0 : CPLDebug("netCDF",
7676 : "nc_put_att(%s:%s) failed: already defined",
7677 : osVarName.c_str(), osAttrName.c_str());
7678 0 : continue;
7679 : }
7680 :
7681 116 : const int nVarId = oMapVarToId[osVarName];
7682 116 : const char *pszValue = pszEqual + 1;
7683 232 : while (*pszValue == ' ')
7684 116 : pszValue++;
7685 :
7686 116 : status = NC_EBADTYPE;
7687 116 : if (*pszValue == '"')
7688 : {
7689 : // For _FillValue, the attribute type should match
7690 : // the variable type. Leaks memory with NC4 otherwise
7691 74 : if (osAttrName == "_FillValue")
7692 : {
7693 0 : CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
7694 : osVarName.c_str(), osAttrName.c_str(),
7695 : nc_strerror(status));
7696 0 : continue;
7697 : }
7698 :
7699 : // Unquote and unescape string value
7700 74 : CPLString osVal(pszValue + 1);
7701 222 : while (!osVal.empty())
7702 : {
7703 222 : if (osVal.back() == ';' || osVal.back() == ' ')
7704 : {
7705 148 : osVal.pop_back();
7706 : }
7707 74 : else if (osVal.back() == '"')
7708 : {
7709 74 : osVal.pop_back();
7710 74 : break;
7711 : }
7712 : else
7713 : {
7714 0 : break;
7715 : }
7716 : }
7717 74 : osVal.replaceAll("\\\"", '"');
7718 74 : status = nc_put_att_text(nCdfId, nVarId, osAttrName,
7719 : osVal.size(), osVal.c_str());
7720 : }
7721 : else
7722 : {
7723 84 : CPLString osVal(pszValue);
7724 126 : while (!osVal.empty())
7725 : {
7726 126 : if (osVal.back() == ';' || osVal.back() == ' ')
7727 : {
7728 84 : osVal.pop_back();
7729 : }
7730 : else
7731 : {
7732 42 : break;
7733 : }
7734 : }
7735 42 : int nc_datatype = -1;
7736 42 : if (!osVal.empty() && osVal.back() == 'b')
7737 : {
7738 3 : nc_datatype = NC_BYTE;
7739 3 : osVal.pop_back();
7740 : }
7741 39 : else if (!osVal.empty() && osVal.back() == 's')
7742 : {
7743 3 : nc_datatype = NC_SHORT;
7744 3 : osVal.pop_back();
7745 : }
7746 42 : if (CPLGetValueType(osVal) == CPL_VALUE_INTEGER)
7747 : {
7748 7 : if (nc_datatype < 0)
7749 4 : nc_datatype = NC_INT;
7750 : }
7751 35 : else if (CPLGetValueType(osVal) == CPL_VALUE_REAL)
7752 : {
7753 32 : nc_datatype = NC_DOUBLE;
7754 : }
7755 : else
7756 : {
7757 3 : nc_datatype = -1;
7758 : }
7759 :
7760 : // For _FillValue, check that the attribute type matches
7761 : // the variable type. Leaks memory with NC4 otherwise
7762 42 : if (osAttrName == "_FillValue")
7763 : {
7764 6 : if (nVarId < 0 ||
7765 3 : nc_datatype != oMapVarIdToType[nVarId])
7766 : {
7767 0 : nc_datatype = -1;
7768 : }
7769 : }
7770 :
7771 42 : if (nc_datatype == NC_BYTE)
7772 : {
7773 : signed char chVal =
7774 3 : static_cast<signed char>(atoi(osVal));
7775 3 : status = nc_put_att_schar(nCdfId, nVarId, osAttrName,
7776 : NC_BYTE, 1, &chVal);
7777 : }
7778 39 : else if (nc_datatype == NC_SHORT)
7779 : {
7780 0 : short nVal = static_cast<short>(atoi(osVal));
7781 0 : status = nc_put_att_short(nCdfId, nVarId, osAttrName,
7782 : NC_SHORT, 1, &nVal);
7783 : }
7784 39 : else if (nc_datatype == NC_INT)
7785 : {
7786 4 : int nVal = static_cast<int>(atoi(osVal));
7787 4 : status = nc_put_att_int(nCdfId, nVarId, osAttrName,
7788 : NC_INT, 1, &nVal);
7789 : }
7790 35 : else if (nc_datatype == NC_DOUBLE)
7791 : {
7792 32 : double dfVal = CPLAtof(osVal);
7793 32 : status = nc_put_att_double(nCdfId, nVarId, osAttrName,
7794 : NC_DOUBLE, 1, &dfVal);
7795 : }
7796 : }
7797 116 : if (status != NC_NOERR)
7798 : {
7799 3 : CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
7800 : osVarName.c_str(), osAttrName.c_str(),
7801 : nc_strerror(status));
7802 : }
7803 : else
7804 : {
7805 113 : oSetAttrDefined.insert(osVarName + ":" + osAttrName);
7806 : #ifdef DEBUG_VERBOSE
7807 : CPLDebug("netCDF", "nc_put_att_(%s:%s) (%s) succeeded",
7808 : osVarName.c_str(), osAttrName.c_str(), pszLine);
7809 : #endif
7810 : }
7811 : }
7812 : }
7813 42 : else if (nActiveSection == SECTION_DATA)
7814 : {
7815 55 : while (*pszLine == ' ' || *pszLine == '\t')
7816 17 : pszLine++;
7817 38 : const char *pszEqual = strchr(pszLine, '=');
7818 38 : if (pszEqual)
7819 : {
7820 17 : CPLString osVarName(pszLine, pszEqual - pszLine);
7821 17 : osVarName.Trim();
7822 17 : if (oMapVarToId.find(osVarName) == oMapVarToId.end())
7823 0 : continue;
7824 17 : const int nVarId = oMapVarToId[osVarName];
7825 17 : CPLString osAccVal(pszEqual + 1);
7826 17 : osAccVal.Trim();
7827 153 : while (osAccVal.empty() || osAccVal.back() != ';')
7828 : {
7829 136 : pszLine = CPLReadLineL(fpSrc);
7830 136 : if (pszLine == nullptr)
7831 0 : break;
7832 272 : CPLString osVal(pszLine);
7833 136 : osVal.Trim();
7834 136 : osAccVal += osVal;
7835 : }
7836 17 : if (pszLine == nullptr)
7837 0 : break;
7838 17 : osAccVal.pop_back();
7839 :
7840 : const std::vector<int> aoDimIds =
7841 34 : oMapVarIdToVectorOfDimId[nVarId];
7842 17 : size_t nSize = 1;
7843 34 : std::vector<size_t> aoStart, aoEdge;
7844 17 : aoStart.resize(aoDimIds.size());
7845 17 : aoEdge.resize(aoDimIds.size());
7846 35 : for (size_t i = 0; i < aoDimIds.size(); ++i)
7847 : {
7848 18 : const size_t nDimSize = oMapDimIdToDimLen[aoDimIds[i]];
7849 36 : if (nDimSize != 0 &&
7850 18 : nSize > std::numeric_limits<size_t>::max() / nDimSize)
7851 : {
7852 0 : nSize = 0;
7853 : }
7854 : else
7855 : {
7856 18 : nSize *= nDimSize;
7857 : }
7858 18 : aoStart[i] = 0;
7859 18 : aoEdge[i] = nDimSize;
7860 : }
7861 :
7862 17 : status = NC_EBADTYPE;
7863 17 : if (nSize == 0)
7864 : {
7865 : // Might happen with a unlimited dimension
7866 : }
7867 17 : else if (oMapVarIdToType[nVarId] == NC_DOUBLE)
7868 : {
7869 8 : if (!aoStart.empty())
7870 : {
7871 : char **papszTokens =
7872 8 : CSLTokenizeString2(osAccVal, " ,;", 0);
7873 8 : size_t nTokens = CSLCount(papszTokens);
7874 8 : if (nTokens >= nSize)
7875 : {
7876 : double *padfVals = static_cast<double *>(
7877 8 : VSI_CALLOC_VERBOSE(nSize, sizeof(double)));
7878 8 : if (padfVals)
7879 : {
7880 132 : for (size_t i = 0; i < nSize; i++)
7881 : {
7882 124 : padfVals[i] = CPLAtof(papszTokens[i]);
7883 : }
7884 8 : status = nc_put_vara_double(
7885 8 : nCdfId, nVarId, &aoStart[0], &aoEdge[0],
7886 : padfVals);
7887 8 : VSIFree(padfVals);
7888 : }
7889 : }
7890 8 : CSLDestroy(papszTokens);
7891 : }
7892 : }
7893 9 : else if (oMapVarIdToType[nVarId] == NC_BYTE)
7894 : {
7895 3 : if (!aoStart.empty())
7896 : {
7897 : char **papszTokens =
7898 3 : CSLTokenizeString2(osAccVal, " ,;", 0);
7899 3 : size_t nTokens = CSLCount(papszTokens);
7900 3 : if (nTokens >= nSize)
7901 : {
7902 : signed char *panVals = static_cast<signed char *>(
7903 3 : VSI_CALLOC_VERBOSE(nSize, sizeof(signed char)));
7904 3 : if (panVals)
7905 : {
7906 1203 : for (size_t i = 0; i < nSize; i++)
7907 : {
7908 1200 : panVals[i] = static_cast<signed char>(
7909 1200 : atoi(papszTokens[i]));
7910 : }
7911 3 : status = nc_put_vara_schar(nCdfId, nVarId,
7912 3 : &aoStart[0],
7913 3 : &aoEdge[0], panVals);
7914 3 : VSIFree(panVals);
7915 : }
7916 : }
7917 3 : CSLDestroy(papszTokens);
7918 : }
7919 : }
7920 6 : else if (oMapVarIdToType[nVarId] == NC_CHAR)
7921 : {
7922 6 : if (aoStart.size() == 2)
7923 : {
7924 4 : std::vector<CPLString> aoStrings;
7925 2 : bool bInString = false;
7926 4 : CPLString osCurString;
7927 935 : for (size_t i = 0; i < osAccVal.size();)
7928 : {
7929 933 : if (!bInString)
7930 : {
7931 8 : if (osAccVal[i] == '"')
7932 : {
7933 4 : bInString = true;
7934 4 : osCurString.clear();
7935 : }
7936 8 : i++;
7937 : }
7938 926 : else if (osAccVal[i] == '\\' &&
7939 926 : i + 1 < osAccVal.size() &&
7940 1 : osAccVal[i + 1] == '"')
7941 : {
7942 1 : osCurString += '"';
7943 1 : i += 2;
7944 : }
7945 924 : else if (osAccVal[i] == '"')
7946 : {
7947 4 : aoStrings.push_back(osCurString);
7948 4 : osCurString.clear();
7949 4 : bInString = false;
7950 4 : i++;
7951 : }
7952 : else
7953 : {
7954 920 : osCurString += osAccVal[i];
7955 920 : i++;
7956 : }
7957 : }
7958 2 : const size_t nRecords = oMapDimIdToDimLen[aoDimIds[0]];
7959 2 : const size_t nWidth = oMapDimIdToDimLen[aoDimIds[1]];
7960 2 : size_t nIters = aoStrings.size();
7961 2 : if (nIters > nRecords)
7962 0 : nIters = nRecords;
7963 6 : for (size_t i = 0; i < nIters; i++)
7964 : {
7965 : size_t anIndex[2];
7966 4 : anIndex[0] = i;
7967 4 : anIndex[1] = 0;
7968 : size_t anCount[2];
7969 4 : anCount[0] = 1;
7970 4 : anCount[1] = aoStrings[i].size();
7971 4 : if (anCount[1] > nWidth)
7972 0 : anCount[1] = nWidth;
7973 : status =
7974 4 : nc_put_vara_text(nCdfId, nVarId, anIndex,
7975 4 : anCount, aoStrings[i].c_str());
7976 4 : if (status != NC_NOERR)
7977 0 : break;
7978 : }
7979 : }
7980 : }
7981 17 : if (status != NC_NOERR)
7982 : {
7983 4 : CPLDebug("netCDF", "nc_put_var_(%s) failed: %s",
7984 : osVarName.c_str(), nc_strerror(status));
7985 : }
7986 : }
7987 : }
7988 : }
7989 :
7990 4 : GDAL_nc_close(nCdfId);
7991 4 : return true;
7992 : }
7993 :
7994 : #endif // ENABLE_NCDUMP
7995 :
7996 : /************************************************************************/
7997 : /* Open() */
7998 : /************************************************************************/
7999 :
8000 684 : GDALDataset *netCDFDataset::Open(GDALOpenInfo *poOpenInfo)
8001 :
8002 : {
8003 : #ifdef NCDF_DEBUG
8004 : CPLDebug("GDAL_netCDF", "\n=====\nOpen(), filename=[%s]",
8005 : poOpenInfo->pszFilename);
8006 : #endif
8007 :
8008 : // Does this appear to be a netcdf file?
8009 684 : NetCDFFormatEnum eTmpFormat = NCDF_FORMAT_NONE;
8010 684 : if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
8011 : {
8012 624 : eTmpFormat = netCDFIdentifyFormat(poOpenInfo, /* bCheckExt = */ true);
8013 : #ifdef NCDF_DEBUG
8014 : CPLDebug("GDAL_netCDF", "identified format %d", eTmpFormat);
8015 : #endif
8016 : // Note: not calling Identify() directly, because we want the file type.
8017 : // Only support NCDF_FORMAT* formats.
8018 624 : if (NCDF_FORMAT_NC == eTmpFormat || NCDF_FORMAT_NC2 == eTmpFormat ||
8019 2 : NCDF_FORMAT_NC4 == eTmpFormat || NCDF_FORMAT_NC4C == eTmpFormat)
8020 : {
8021 : // ok
8022 : }
8023 2 : else if (eTmpFormat == NCDF_FORMAT_HDF4 &&
8024 0 : poOpenInfo->IsSingleAllowedDriver("netCDF"))
8025 : {
8026 : // ok
8027 : }
8028 : else
8029 : {
8030 2 : return nullptr;
8031 : }
8032 : }
8033 : else
8034 : {
8035 : #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
8036 : // We don't necessarily want to catch bugs in libnetcdf ...
8037 : if (CPLGetConfigOption("DISABLE_OPEN_REAL_NETCDF_FILES", nullptr))
8038 : {
8039 : return nullptr;
8040 : }
8041 : #endif
8042 : }
8043 :
8044 682 : if (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER)
8045 : {
8046 186 : return OpenMultiDim(poOpenInfo);
8047 : }
8048 :
8049 992 : CPLMutexHolderD(&hNCMutex);
8050 :
8051 496 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
8052 : // GDALDataset own mutex.
8053 496 : netCDFDataset *poDS = new netCDFDataset();
8054 496 : poDS->papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
8055 496 : CPLAcquireMutex(hNCMutex, 1000.0);
8056 :
8057 496 : poDS->SetDescription(poOpenInfo->pszFilename);
8058 :
8059 : // Check if filename start with NETCDF: tag.
8060 496 : bool bTreatAsSubdataset = false;
8061 992 : CPLString osSubdatasetName;
8062 :
8063 : #ifdef ENABLE_NCDUMP
8064 496 : const char *pszHeader =
8065 : reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
8066 496 : if (poOpenInfo->fpL != nullptr && STARTS_WITH(pszHeader, "netcdf ") &&
8067 3 : strstr(pszHeader, "dimensions:") && strstr(pszHeader, "variables:"))
8068 : {
8069 : // By default create a temporary file that will be destroyed,
8070 : // unless NETCDF_TMP_FILE is defined. Can be useful to see which
8071 : // netCDF file has been generated from a potential fuzzed input.
8072 3 : poDS->osFilename = CPLGetConfigOption("NETCDF_TMP_FILE", "");
8073 3 : if (poDS->osFilename.empty())
8074 : {
8075 3 : poDS->bFileToDestroyAtClosing = true;
8076 3 : poDS->osFilename = CPLGenerateTempFilenameSafe("netcdf_tmp");
8077 : }
8078 3 : if (!netCDFDatasetCreateTempFile(eTmpFormat, poDS->osFilename,
8079 : poOpenInfo->fpL))
8080 : {
8081 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8082 : // deadlock with GDALDataset own mutex.
8083 0 : delete poDS;
8084 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8085 0 : return nullptr;
8086 : }
8087 3 : bTreatAsSubdataset = false;
8088 3 : poDS->eFormat = eTmpFormat;
8089 : }
8090 : else
8091 : #endif
8092 :
8093 493 : if (STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
8094 : {
8095 : char **papszName =
8096 60 : CSLTokenizeString2(poOpenInfo->pszFilename, ":",
8097 : CSLT_HONOURSTRINGS | CSLT_PRESERVEESCAPES);
8098 :
8099 120 : if (CSLCount(papszName) >= 3 &&
8100 60 : ((strlen(papszName[1]) == 1 && /* D:\\bla */
8101 0 : (papszName[2][0] == '/' || papszName[2][0] == '\\')) ||
8102 60 : EQUAL(papszName[1], "http") || EQUAL(papszName[1], "https") ||
8103 60 : EQUAL(papszName[1], "/vsicurl/http") ||
8104 60 : EQUAL(papszName[1], "/vsicurl/https") ||
8105 60 : EQUAL(papszName[1], "/vsicurl_streaming/http") ||
8106 60 : EQUAL(papszName[1], "/vsicurl_streaming/https")))
8107 : {
8108 0 : const int nCountBefore = CSLCount(papszName);
8109 0 : CPLString osTmp = papszName[1];
8110 0 : osTmp += ':';
8111 0 : osTmp += papszName[2];
8112 0 : CPLFree(papszName[1]);
8113 0 : CPLFree(papszName[2]);
8114 0 : papszName[1] = CPLStrdup(osTmp);
8115 0 : memmove(papszName + 2, papszName + 3,
8116 0 : (nCountBefore - 2) * sizeof(char *));
8117 : }
8118 :
8119 60 : if (CSLCount(papszName) == 3)
8120 : {
8121 60 : poDS->osFilename = papszName[1];
8122 60 : osSubdatasetName = papszName[2];
8123 60 : bTreatAsSubdataset = true;
8124 60 : CSLDestroy(papszName);
8125 : }
8126 0 : else if (CSLCount(papszName) == 2)
8127 : {
8128 0 : poDS->osFilename = papszName[1];
8129 0 : osSubdatasetName = "";
8130 0 : bTreatAsSubdataset = false;
8131 0 : CSLDestroy(papszName);
8132 : }
8133 : else
8134 : {
8135 0 : CSLDestroy(papszName);
8136 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8137 : // deadlock with GDALDataset own mutex.
8138 0 : delete poDS;
8139 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8140 0 : CPLError(CE_Failure, CPLE_AppDefined,
8141 : "Failed to parse NETCDF: prefix string into expected 2, 3 "
8142 : "or 4 fields.");
8143 0 : return nullptr;
8144 : }
8145 :
8146 120 : if (!STARTS_WITH(poDS->osFilename, "http://") &&
8147 60 : !STARTS_WITH(poDS->osFilename, "https://"))
8148 : {
8149 : // Identify Format from real file, with bCheckExt=FALSE.
8150 : GDALOpenInfo *poOpenInfo2 =
8151 60 : new GDALOpenInfo(poDS->osFilename.c_str(), GA_ReadOnly);
8152 60 : poDS->eFormat =
8153 60 : netCDFIdentifyFormat(poOpenInfo2, /* bCheckExt = */ false);
8154 60 : delete poOpenInfo2;
8155 60 : if (NCDF_FORMAT_NONE == poDS->eFormat ||
8156 60 : NCDF_FORMAT_UNKNOWN == poDS->eFormat)
8157 : {
8158 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8159 : // deadlock with GDALDataset own mutex.
8160 0 : delete poDS;
8161 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8162 0 : return nullptr;
8163 : }
8164 : }
8165 : }
8166 : else
8167 : {
8168 433 : poDS->osFilename = poOpenInfo->pszFilename;
8169 433 : bTreatAsSubdataset = false;
8170 433 : poDS->eFormat = eTmpFormat;
8171 : }
8172 :
8173 : // Try opening the dataset.
8174 : #if defined(NCDF_DEBUG) && defined(ENABLE_UFFD)
8175 : CPLDebug("GDAL_netCDF", "calling nc_open_mem(%s)",
8176 : poDS->osFilename.c_str());
8177 : #elif defined(NCDF_DEBUG) && !defined(ENABLE_UFFD)
8178 : CPLDebug("GDAL_netCDF", "calling nc_open(%s)", poDS->osFilename.c_str());
8179 : #endif
8180 496 : int cdfid = -1;
8181 496 : const int nMode = ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE) != 0)
8182 : ? NC_WRITE
8183 : : NC_NOWRITE;
8184 992 : CPLString osFilenameForNCOpen(poDS->osFilename);
8185 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
8186 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
8187 : {
8188 : char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
8189 : osFilenameForNCOpen = pszTemp;
8190 : CPLFree(pszTemp);
8191 : }
8192 : #endif
8193 496 : int status2 = -1;
8194 :
8195 : #ifdef ENABLE_UFFD
8196 496 : cpl_uffd_context *pCtx = nullptr;
8197 : #endif
8198 :
8199 511 : if (STARTS_WITH(osFilenameForNCOpen, "/vsimem/") &&
8200 15 : poOpenInfo->eAccess == GA_ReadOnly)
8201 : {
8202 15 : vsi_l_offset nLength = 0;
8203 15 : poDS->fpVSIMEM = VSIFOpenL(osFilenameForNCOpen, "rb");
8204 15 : if (poDS->fpVSIMEM)
8205 : {
8206 : // We assume that the file will not be modified. If it is, then
8207 : // pabyBuffer might become invalid.
8208 : GByte *pabyBuffer =
8209 15 : VSIGetMemFileBuffer(osFilenameForNCOpen, &nLength, false);
8210 15 : if (pabyBuffer)
8211 : {
8212 15 : status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen),
8213 : nMode, static_cast<size_t>(nLength),
8214 : pabyBuffer, &cdfid);
8215 : }
8216 : }
8217 : }
8218 : else
8219 : {
8220 : const bool bVsiFile =
8221 481 : !strncmp(osFilenameForNCOpen, "/vsi", strlen("/vsi"));
8222 : #ifdef ENABLE_UFFD
8223 481 : bool bReadOnly = (poOpenInfo->eAccess == GA_ReadOnly);
8224 481 : void *pVma = nullptr;
8225 481 : uint64_t nVmaSize = 0;
8226 :
8227 481 : if (bVsiFile)
8228 : {
8229 2 : if (bReadOnly)
8230 : {
8231 2 : if (CPLIsUserFaultMappingSupported())
8232 : {
8233 2 : pCtx = CPLCreateUserFaultMapping(osFilenameForNCOpen, &pVma,
8234 : &nVmaSize);
8235 : }
8236 : else
8237 : {
8238 0 : CPLError(CE_Failure, CPLE_AppDefined,
8239 : "Opening a /vsi file with the netCDF driver "
8240 : "requires Linux userfaultfd to be available. "
8241 : "If running from Docker, "
8242 : "--security-opt seccomp=unconfined might be "
8243 : "needed.%s",
8244 0 : ((poDS->eFormat == NCDF_FORMAT_NC4 ||
8245 0 : poDS->eFormat == NCDF_FORMAT_HDF5) &&
8246 0 : GDALGetDriverByName("HDF5"))
8247 : ? " Or you may set the GDAL_SKIP=netCDF "
8248 : "configuration option to force the use of "
8249 : "the HDF5 driver."
8250 : : "");
8251 : }
8252 : }
8253 : else
8254 : {
8255 0 : CPLError(CE_Failure, CPLE_AppDefined,
8256 : "Opening a /vsi file with the netCDF driver is only "
8257 : "supported in read-only mode");
8258 : }
8259 : }
8260 481 : if (pCtx != nullptr && pVma != nullptr && nVmaSize > 0)
8261 : {
8262 : // netCDF code, at least for netCDF 4.7.0, is confused by filenames
8263 : // like /vsicurl/http[s]://example.com/foo.nc, so just pass the
8264 : // final part
8265 2 : status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen), nMode,
8266 : static_cast<size_t>(nVmaSize), pVma, &cdfid);
8267 : }
8268 : else
8269 479 : status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
8270 : #else
8271 : if (bVsiFile)
8272 : {
8273 : CPLError(
8274 : CE_Failure, CPLE_AppDefined,
8275 : "Opening a /vsi file with the netCDF driver requires Linux "
8276 : "userfaultfd to be available.%s",
8277 : ((poDS->eFormat == NCDF_FORMAT_NC4 ||
8278 : poDS->eFormat == NCDF_FORMAT_HDF5) &&
8279 : GDALGetDriverByName("HDF5"))
8280 : ? " Or you may set the GDAL_SKIP=netCDF "
8281 : "configuration option to force the use of the HDF5 "
8282 : "driver."
8283 : : "");
8284 : status2 = NC_EIO;
8285 : }
8286 : else
8287 : {
8288 : status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
8289 : }
8290 : #endif
8291 : }
8292 496 : if (status2 != NC_NOERR)
8293 : {
8294 : #ifdef NCDF_DEBUG
8295 : CPLDebug("GDAL_netCDF", "error opening");
8296 : #endif
8297 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8298 : // with GDALDataset own mutex.
8299 0 : delete poDS;
8300 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8301 0 : return nullptr;
8302 : }
8303 : #ifdef NCDF_DEBUG
8304 : CPLDebug("GDAL_netCDF", "got cdfid=%d", cdfid);
8305 : #endif
8306 :
8307 : #if defined(ENABLE_NCDUMP) && !defined(_WIN32)
8308 : // Try to destroy the temporary file right now on Unix
8309 496 : if (poDS->bFileToDestroyAtClosing)
8310 : {
8311 3 : if (VSIUnlink(poDS->osFilename) == 0)
8312 : {
8313 3 : poDS->bFileToDestroyAtClosing = false;
8314 : }
8315 : }
8316 : #endif
8317 :
8318 : // Is this a real netCDF file?
8319 : int ndims;
8320 : int ngatts;
8321 : int nvars;
8322 : int unlimdimid;
8323 496 : int status = nc_inq(cdfid, &ndims, &nvars, &ngatts, &unlimdimid);
8324 496 : if (status != NC_NOERR)
8325 : {
8326 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8327 : // with GDALDataset own mutex.
8328 0 : delete poDS;
8329 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8330 0 : return nullptr;
8331 : }
8332 :
8333 : // Get file type from netcdf.
8334 496 : int nTmpFormat = NCDF_FORMAT_NONE;
8335 496 : status = nc_inq_format(cdfid, &nTmpFormat);
8336 496 : if (status != NC_NOERR)
8337 : {
8338 0 : NCDF_ERR(status);
8339 : }
8340 : else
8341 : {
8342 496 : CPLDebug("GDAL_netCDF",
8343 : "driver detected file type=%d, libnetcdf detected type=%d",
8344 496 : poDS->eFormat, nTmpFormat);
8345 496 : if (static_cast<NetCDFFormatEnum>(nTmpFormat) != poDS->eFormat)
8346 : {
8347 : // Warn if file detection conflicts with that from libnetcdf
8348 : // except for NC4C, which we have no way of detecting initially.
8349 26 : if (nTmpFormat != NCDF_FORMAT_NC4C &&
8350 13 : !STARTS_WITH(poDS->osFilename, "http://") &&
8351 0 : !STARTS_WITH(poDS->osFilename, "https://"))
8352 : {
8353 0 : CPLError(CE_Warning, CPLE_AppDefined,
8354 : "NetCDF driver detected file type=%d, but libnetcdf "
8355 : "detected type=%d",
8356 0 : poDS->eFormat, nTmpFormat);
8357 : }
8358 13 : CPLDebug("GDAL_netCDF", "setting file type to %d, was %d",
8359 13 : nTmpFormat, poDS->eFormat);
8360 13 : poDS->eFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
8361 : }
8362 : }
8363 :
8364 : // Does the request variable exist?
8365 496 : if (bTreatAsSubdataset)
8366 : {
8367 : int dummy;
8368 60 : if (NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &dummy,
8369 60 : &dummy) != CE_None)
8370 : {
8371 0 : CPLError(CE_Warning, CPLE_AppDefined,
8372 : "%s is a netCDF file, but %s is not a variable.",
8373 : poOpenInfo->pszFilename, osSubdatasetName.c_str());
8374 :
8375 0 : GDAL_nc_close(cdfid);
8376 : #ifdef ENABLE_UFFD
8377 0 : NETCDF_UFFD_UNMAP(pCtx);
8378 : #endif
8379 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8380 : // deadlock with GDALDataset own mutex.
8381 0 : delete poDS;
8382 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8383 0 : return nullptr;
8384 : }
8385 : }
8386 :
8387 : // Figure out whether or not the listed dataset has support for simple
8388 : // geometries (CF-1.8)
8389 496 : poDS->nCFVersion = nccfdriver::getCFVersion(cdfid);
8390 496 : bool bHasSimpleGeometries = false; // but not necessarily valid
8391 496 : if (poDS->nCFVersion >= 1.8)
8392 : {
8393 75 : bHasSimpleGeometries = poDS->DetectAndFillSGLayers(cdfid);
8394 75 : if (bHasSimpleGeometries)
8395 : {
8396 67 : poDS->bSGSupport = true;
8397 67 : poDS->vcdf.enableFullVirtualMode();
8398 : }
8399 : }
8400 :
8401 : char szConventions[NC_MAX_NAME + 1];
8402 496 : szConventions[0] = '\0';
8403 496 : nc_type nAttype = NC_NAT;
8404 496 : size_t nAttlen = 0;
8405 496 : nc_inq_att(cdfid, NC_GLOBAL, "Conventions", &nAttype, &nAttlen);
8406 992 : if (nAttlen >= sizeof(szConventions) ||
8407 496 : nc_get_att_text(cdfid, NC_GLOBAL, "Conventions", szConventions) !=
8408 : NC_NOERR)
8409 : {
8410 59 : CPLDebug("GDAL_netCDF", "No UNIDATA NC_GLOBAL:Conventions attribute");
8411 : // Note that 'Conventions' is always capital 'C' in CF spec.
8412 : }
8413 : else
8414 : {
8415 437 : szConventions[nAttlen] = '\0';
8416 : }
8417 :
8418 : // Create band information objects.
8419 496 : CPLDebug("GDAL_netCDF", "var_count = %d", nvars);
8420 :
8421 : // Create a corresponding GDALDataset.
8422 : // Create Netcdf Subdataset if filename as NETCDF tag.
8423 496 : poDS->cdfid = cdfid;
8424 : #ifdef ENABLE_UFFD
8425 496 : poDS->pCtx = pCtx;
8426 : #endif
8427 496 : poDS->eAccess = poOpenInfo->eAccess;
8428 496 : poDS->bDefineMode = false;
8429 :
8430 496 : poDS->ReadAttributes(cdfid, NC_GLOBAL);
8431 :
8432 : // Identify coordinate and boundary variables that we should
8433 : // ignore as Raster Bands.
8434 496 : char **papszIgnoreVars = nullptr;
8435 496 : NCDFGetCoordAndBoundVarFullNames(cdfid, &papszIgnoreVars);
8436 : // Filter variables to keep only valid 2+D raster bands and vector fields.
8437 496 : int nRasterVars = 0;
8438 496 : int nIgnoredVars = 0;
8439 496 : int nGroupID = -1;
8440 496 : int nVarID = -1;
8441 :
8442 : std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>
8443 992 : oMap2DDimsToGroupAndVar;
8444 1145 : if ((poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
8445 153 : STARTS_WITH(CSLFetchNameValueDef(poDS->papszMetadata,
8446 : "NC_GLOBAL#mission_name", ""),
8447 1 : "Sentinel 3") &&
8448 1 : EQUAL(CSLFetchNameValueDef(poDS->papszMetadata,
8449 : "NC_GLOBAL#altimeter_sensor_name", ""),
8450 649 : "SRAL") &&
8451 1 : EQUAL(CSLFetchNameValueDef(poDS->papszMetadata,
8452 : "NC_GLOBAL#radiometer_sensor_name", ""),
8453 : "MWR"))
8454 : {
8455 1 : if (poDS->eAccess == GA_Update)
8456 : {
8457 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8458 : // deadlock with GDALDataset own mutex.
8459 0 : delete poDS;
8460 0 : return nullptr;
8461 : }
8462 1 : poDS->ProcessSentinel3_SRAL_MWR();
8463 : }
8464 : else
8465 : {
8466 495 : poDS->FilterVars(cdfid, (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0,
8467 647 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
8468 152 : !bHasSimpleGeometries,
8469 : papszIgnoreVars, &nRasterVars, &nGroupID, &nVarID,
8470 : &nIgnoredVars, oMap2DDimsToGroupAndVar);
8471 : }
8472 496 : CSLDestroy(papszIgnoreVars);
8473 :
8474 496 : const bool bListAllArrays = CPLTestBool(
8475 496 : CSLFetchNameValueDef(poDS->papszOpenOptions, "LIST_ALL_ARRAYS", "NO"));
8476 :
8477 : // Case where there is no raster variable
8478 496 : if (!bListAllArrays && nRasterVars == 0 && !bTreatAsSubdataset)
8479 : {
8480 119 : poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
8481 119 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8482 : // with GDALDataset own mutex.
8483 119 : poDS->TryLoadXML();
8484 : // If the dataset has been opened in raster mode only, exit
8485 119 : if ((poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0 &&
8486 9 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) == 0)
8487 : {
8488 4 : delete poDS;
8489 4 : poDS = nullptr;
8490 : }
8491 : // Otherwise if the dataset is opened in vector mode, that there is
8492 : // no vector layer and we are in read-only, exit too.
8493 115 : else if (poDS->GetLayerCount() == 0 &&
8494 123 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
8495 8 : poOpenInfo->eAccess == GA_ReadOnly)
8496 : {
8497 8 : delete poDS;
8498 8 : poDS = nullptr;
8499 : }
8500 119 : CPLAcquireMutex(hNCMutex, 1000.0);
8501 119 : return poDS;
8502 : }
8503 :
8504 : // We have more than one variable with 2 dimensions in the
8505 : // file, then treat this as a subdataset container dataset.
8506 377 : bool bSeveralVariablesAsBands = false;
8507 377 : if (bListAllArrays || ((nRasterVars > 1) && !bTreatAsSubdataset))
8508 : {
8509 29 : if (CPLFetchBool(poOpenInfo->papszOpenOptions, "VARIABLES_AS_BANDS",
8510 35 : false) &&
8511 6 : oMap2DDimsToGroupAndVar.size() == 1)
8512 : {
8513 6 : std::tie(nGroupID, nVarID) =
8514 12 : oMap2DDimsToGroupAndVar.begin()->second.front();
8515 6 : bSeveralVariablesAsBands = true;
8516 : }
8517 : else
8518 : {
8519 23 : poDS->CreateSubDatasetList(cdfid);
8520 23 : poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
8521 23 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8522 : // deadlock with GDALDataset own mutex.
8523 23 : poDS->TryLoadXML();
8524 23 : CPLAcquireMutex(hNCMutex, 1000.0);
8525 23 : return poDS;
8526 : }
8527 : }
8528 :
8529 : // If we are not treating things as a subdataset, then capture
8530 : // the name of the single available variable as the subdataset.
8531 354 : if (!bTreatAsSubdataset)
8532 : {
8533 294 : char *pszVarName = nullptr;
8534 294 : NCDF_ERR(NCDFGetVarFullName(nGroupID, nVarID, &pszVarName));
8535 294 : osSubdatasetName = (pszVarName != nullptr ? pszVarName : "");
8536 294 : CPLFree(pszVarName);
8537 : }
8538 :
8539 : // We have ignored at least one variable, so we should report them
8540 : // as subdatasets for reference.
8541 354 : if (nIgnoredVars > 0 && !bTreatAsSubdataset)
8542 : {
8543 25 : CPLDebug("GDAL_netCDF",
8544 : "As %d variables were ignored, creating subdataset list "
8545 : "for reference. Variable #%d [%s] is the main variable",
8546 : nIgnoredVars, nVarID, osSubdatasetName.c_str());
8547 25 : poDS->CreateSubDatasetList(cdfid);
8548 : }
8549 :
8550 : // Open the NETCDF subdataset NETCDF:"filename":subdataset.
8551 354 : int var = -1;
8552 354 : NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &nGroupID, &var);
8553 : // Now we can forget the root cdfid and only use the selected group.
8554 354 : cdfid = nGroupID;
8555 354 : int nd = 0;
8556 354 : nc_inq_varndims(cdfid, var, &nd);
8557 :
8558 354 : poDS->m_anDimIds.resize(nd);
8559 :
8560 : // X, Y, Z position in array
8561 708 : std::vector<int> anBandDimPos(nd);
8562 :
8563 354 : nc_inq_vardimid(cdfid, var, poDS->m_anDimIds.data());
8564 :
8565 : // Check if somebody tried to pass a variable with less than 1D.
8566 354 : if (nd < 1)
8567 : {
8568 0 : CPLError(CE_Warning, CPLE_AppDefined,
8569 : "Variable has %d dimension(s) - not supported.", nd);
8570 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8571 : // with GDALDataset own mutex.
8572 0 : delete poDS;
8573 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8574 0 : return nullptr;
8575 : }
8576 :
8577 : // CF-1 Convention
8578 : //
8579 : // Dimensions to appear in the relative order T, then Z, then Y,
8580 : // then X to the file. All other dimensions should, whenever
8581 : // possible, be placed to the left of the spatiotemporal
8582 : // dimensions.
8583 :
8584 : // Verify that dimensions are in the {T,Z,Y,X} or {T,Z,Y,X} order
8585 : // Ideally we should detect for other ordering and act accordingly
8586 : // Only done if file has Conventions=CF-* and only prints warning
8587 : // To disable set GDAL_NETCDF_VERIFY_DIMS=NO and to use only
8588 : // attributes (not varnames) set GDAL_NETCDF_VERIFY_DIMS=STRICT
8589 : const bool bCheckDims =
8590 708 : CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES")) &&
8591 354 : STARTS_WITH_CI(szConventions, "CF");
8592 :
8593 354 : bool bYXBandOrder = false;
8594 354 : if (nd == 3)
8595 : {
8596 : // If there's a coordinates attributes, and the variable it points to
8597 : // are 2D variables indexed by the same first and second dimension than
8598 : // our variable of interest, then it is Y,X,Band order.
8599 46 : char *pszCoordinates = nullptr;
8600 46 : if (NCDFGetAttr(cdfid, var, "coordinates", &pszCoordinates) ==
8601 63 : CE_None &&
8602 17 : pszCoordinates)
8603 : {
8604 : const CPLStringList aosCoordinates(
8605 34 : NCDFTokenizeCoordinatesAttribute(pszCoordinates));
8606 17 : if (aosCoordinates.size() == 2)
8607 : {
8608 : // Test that each variable is longitude/latitude.
8609 13 : for (int i = 0; i < aosCoordinates.size(); i++)
8610 : {
8611 13 : if (NCDFIsVarLongitude(cdfid, -1, aosCoordinates[i]) ||
8612 4 : NCDFIsVarLatitude(cdfid, -1, aosCoordinates[i]))
8613 : {
8614 9 : int nOtherGroupId = -1;
8615 9 : int nOtherVarId = -1;
8616 9 : if (NCDFResolveVar(cdfid, aosCoordinates[i],
8617 : &nOtherGroupId,
8618 9 : &nOtherVarId) == CE_None)
8619 : {
8620 9 : int coordDimCount = 0;
8621 9 : nc_inq_varndims(nOtherGroupId, nOtherVarId,
8622 : &coordDimCount);
8623 9 : if (coordDimCount == 2)
8624 : {
8625 3 : int coordDimIds[2] = {0, 0};
8626 3 : nc_inq_vardimid(nOtherGroupId, nOtherVarId,
8627 : coordDimIds);
8628 4 : if (coordDimIds[0] == poDS->m_anDimIds[0] &&
8629 1 : coordDimIds[1] == poDS->m_anDimIds[1])
8630 : {
8631 1 : bYXBandOrder = true;
8632 1 : break;
8633 : }
8634 : }
8635 : }
8636 : }
8637 : }
8638 : }
8639 : }
8640 46 : CPLFree(pszCoordinates);
8641 :
8642 46 : if (!bYXBandOrder)
8643 : {
8644 45 : char szDim0Name[NC_MAX_NAME + 1] = {};
8645 45 : char szDim1Name[NC_MAX_NAME + 1] = {};
8646 45 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[0], szDim0Name);
8647 45 : NCDF_ERR(status);
8648 45 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[1], szDim1Name);
8649 45 : NCDF_ERR(status);
8650 :
8651 45 : if (strcmp(szDim0Name, "number_of_lines") == 0 &&
8652 1 : strcmp(szDim1Name, "pixels_per_line") == 0)
8653 : {
8654 : // Like in PACE OCI products
8655 1 : bYXBandOrder = true;
8656 : }
8657 : else
8658 : {
8659 : // For example for EMIT data (https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/),
8660 : // dimension order is downtrack, crosstrack, bands
8661 44 : char szDim2Name[NC_MAX_NAME + 1] = {};
8662 44 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[2], szDim2Name);
8663 44 : NCDF_ERR(status);
8664 86 : bYXBandOrder = strcmp(szDim2Name, "bands") == 0 ||
8665 42 : strcmp(szDim2Name, "band") == 0;
8666 : }
8667 : }
8668 : }
8669 :
8670 354 : if (nd >= 2 && bCheckDims && !bYXBandOrder)
8671 : {
8672 267 : char szDimName1[NC_MAX_NAME + 1] = {};
8673 267 : char szDimName2[NC_MAX_NAME + 1] = {};
8674 267 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 1], szDimName1);
8675 267 : NCDF_ERR(status);
8676 267 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 2], szDimName2);
8677 267 : NCDF_ERR(status);
8678 425 : if (NCDFIsVarLongitude(cdfid, -1, szDimName1) == false &&
8679 158 : NCDFIsVarProjectionX(cdfid, -1, szDimName1) == false)
8680 : {
8681 4 : CPLError(CE_Warning, CPLE_AppDefined,
8682 : "dimension #%d (%s) is not a Longitude/X dimension.",
8683 : nd - 1, szDimName1);
8684 : }
8685 425 : if (NCDFIsVarLatitude(cdfid, -1, szDimName2) == false &&
8686 158 : NCDFIsVarProjectionY(cdfid, -1, szDimName2) == false)
8687 : {
8688 4 : CPLError(CE_Warning, CPLE_AppDefined,
8689 : "dimension #%d (%s) is not a Latitude/Y dimension.",
8690 : nd - 2, szDimName2);
8691 : }
8692 267 : if ((NCDFIsVarLongitude(cdfid, -1, szDimName2) ||
8693 269 : NCDFIsVarProjectionX(cdfid, -1, szDimName2)) &&
8694 2 : (NCDFIsVarLatitude(cdfid, -1, szDimName1) ||
8695 0 : NCDFIsVarProjectionY(cdfid, -1, szDimName1)))
8696 : {
8697 2 : poDS->bSwitchedXY = true;
8698 : }
8699 267 : if (nd >= 3)
8700 : {
8701 52 : char szDimName3[NC_MAX_NAME + 1] = {};
8702 : status =
8703 52 : nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 3], szDimName3);
8704 52 : NCDF_ERR(status);
8705 52 : if (nd >= 4)
8706 : {
8707 13 : char szDimName4[NC_MAX_NAME + 1] = {};
8708 : status =
8709 13 : nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 4], szDimName4);
8710 13 : NCDF_ERR(status);
8711 13 : if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false)
8712 : {
8713 0 : CPLError(CE_Warning, CPLE_AppDefined,
8714 : "dimension #%d (%s) is not a Vertical dimension.",
8715 : nd - 3, szDimName3);
8716 : }
8717 13 : if (NCDFIsVarTimeCoord(cdfid, -1, szDimName4) == false)
8718 : {
8719 0 : CPLError(CE_Warning, CPLE_AppDefined,
8720 : "dimension #%d (%s) is not a Time dimension.",
8721 : nd - 4, szDimName4);
8722 : }
8723 : }
8724 : else
8725 : {
8726 75 : if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false &&
8727 36 : NCDFIsVarTimeCoord(cdfid, -1, szDimName3) == false)
8728 : {
8729 0 : CPLError(CE_Warning, CPLE_AppDefined,
8730 : "dimension #%d (%s) is not a "
8731 : "Time or Vertical dimension.",
8732 : nd - 3, szDimName3);
8733 : }
8734 : }
8735 : }
8736 : }
8737 :
8738 : // Get X dimensions information.
8739 : size_t xdim;
8740 354 : poDS->nXDimID = poDS->m_anDimIds[bYXBandOrder ? 1 : nd - 1];
8741 354 : nc_inq_dimlen(cdfid, poDS->nXDimID, &xdim);
8742 :
8743 : // Get Y dimension information.
8744 : size_t ydim;
8745 354 : if (nd >= 2)
8746 : {
8747 350 : poDS->nYDimID = poDS->m_anDimIds[bYXBandOrder ? 0 : nd - 2];
8748 350 : nc_inq_dimlen(cdfid, poDS->nYDimID, &ydim);
8749 : }
8750 : else
8751 : {
8752 4 : poDS->nYDimID = -1;
8753 4 : ydim = 1;
8754 : }
8755 :
8756 354 : if (xdim > INT_MAX || ydim > INT_MAX)
8757 : {
8758 0 : CPLError(CE_Failure, CPLE_AppDefined,
8759 : "Invalid raster dimensions: " CPL_FRMT_GUIB "x" CPL_FRMT_GUIB,
8760 : static_cast<GUIntBig>(xdim), static_cast<GUIntBig>(ydim));
8761 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8762 : // with GDALDataset own mutex.
8763 0 : delete poDS;
8764 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8765 0 : return nullptr;
8766 : }
8767 :
8768 354 : poDS->nRasterXSize = static_cast<int>(xdim);
8769 354 : poDS->nRasterYSize = static_cast<int>(ydim);
8770 :
8771 354 : unsigned int k = 0;
8772 1139 : for (int j = 0; j < nd; j++)
8773 : {
8774 785 : if (poDS->m_anDimIds[j] == poDS->nXDimID)
8775 : {
8776 354 : anBandDimPos[0] = j; // Save Position of XDim
8777 354 : k++;
8778 : }
8779 785 : if (poDS->m_anDimIds[j] == poDS->nYDimID)
8780 : {
8781 350 : anBandDimPos[1] = j; // Save Position of YDim
8782 350 : k++;
8783 : }
8784 : }
8785 : // X and Y Dimension Ids were not found!
8786 354 : if ((nd >= 2 && k != 2) || (nd == 1 && k != 1))
8787 : {
8788 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8789 : // with GDALDataset own mutex.
8790 0 : delete poDS;
8791 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8792 0 : return nullptr;
8793 : }
8794 :
8795 : // Read Metadata for this variable.
8796 :
8797 : // Should disable as is also done at band level, except driver needs the
8798 : // variables as metadata (e.g. projection).
8799 354 : poDS->ReadAttributes(cdfid, var);
8800 :
8801 : // Read Metadata for each dimension.
8802 354 : int *panDimIds = nullptr;
8803 354 : NCDFGetVisibleDims(cdfid, &ndims, &panDimIds);
8804 : // With NetCDF-4 groups panDimIds is not always [0..dim_count-1] like
8805 : // in NetCDF-3 because we see only the dimensions of the selected group
8806 : // and its parents.
8807 : // poDS->papszDimName is indexed by dim IDs, so it must contains all IDs
8808 : // [0..max(panDimIds)], but they are not all useful so we fill names
8809 : // of useless dims with empty string.
8810 354 : if (panDimIds)
8811 : {
8812 354 : const int nMaxDimId = *std::max_element(panDimIds, panDimIds + ndims);
8813 354 : std::set<int> oSetExistingDimIds;
8814 1179 : for (int i = 0; i < ndims; i++)
8815 : {
8816 825 : oSetExistingDimIds.insert(panDimIds[i]);
8817 : }
8818 354 : std::set<int> oSetDimIdsUsedByVar;
8819 1139 : for (int i = 0; i < nd; i++)
8820 : {
8821 785 : oSetDimIdsUsedByVar.insert(poDS->m_anDimIds[i]);
8822 : }
8823 1181 : for (int j = 0; j <= nMaxDimId; j++)
8824 : {
8825 : // Is j dim used?
8826 827 : if (oSetExistingDimIds.find(j) != oSetExistingDimIds.end())
8827 : {
8828 : // Useful dim.
8829 825 : char szTemp[NC_MAX_NAME + 1] = {};
8830 825 : status = nc_inq_dimname(cdfid, j, szTemp);
8831 825 : if (status != NC_NOERR)
8832 : {
8833 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8834 : // deadlock with GDALDataset own
8835 : // mutex.
8836 0 : delete poDS;
8837 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8838 0 : return nullptr;
8839 : }
8840 825 : poDS->papszDimName.AddString(szTemp);
8841 :
8842 825 : if (oSetDimIdsUsedByVar.find(j) != oSetDimIdsUsedByVar.end())
8843 : {
8844 785 : int nDimGroupId = -1;
8845 785 : int nDimVarId = -1;
8846 785 : if (NCDFResolveVar(cdfid, poDS->papszDimName[j],
8847 785 : &nDimGroupId, &nDimVarId) == CE_None)
8848 : {
8849 581 : poDS->ReadAttributes(nDimGroupId, nDimVarId);
8850 : }
8851 : }
8852 : }
8853 : else
8854 : {
8855 : // Useless dim.
8856 2 : poDS->papszDimName.AddString("");
8857 : }
8858 : }
8859 354 : CPLFree(panDimIds);
8860 : }
8861 :
8862 : // Set projection info.
8863 708 : std::vector<std::string> aosRemovedMDItems;
8864 354 : if (nd > 1)
8865 : {
8866 350 : poDS->SetProjectionFromVar(cdfid, var,
8867 : /*bReadSRSOnly=*/false,
8868 : /* pszGivenGM = */ nullptr,
8869 : /* returnProjStr = */ nullptr,
8870 : /* sg = */ nullptr, &aosRemovedMDItems);
8871 : }
8872 :
8873 : // Override bottom-up with GDAL_NETCDF_BOTTOMUP config option.
8874 354 : const char *pszValue = CPLGetConfigOption("GDAL_NETCDF_BOTTOMUP", nullptr);
8875 354 : if (pszValue)
8876 : {
8877 24 : poDS->bBottomUp = CPLTestBool(pszValue);
8878 24 : CPLDebug("GDAL_netCDF",
8879 : "set bBottomUp=%d because GDAL_NETCDF_BOTTOMUP=%s",
8880 24 : static_cast<int>(poDS->bBottomUp), pszValue);
8881 : }
8882 :
8883 : // Save non-spatial dimension info.
8884 :
8885 354 : int *panBandZLev = nullptr;
8886 354 : int nDim = (nd >= 2) ? 2 : 1;
8887 : size_t lev_count;
8888 354 : size_t nTotLevCount = 1;
8889 354 : nc_type nType = NC_NAT;
8890 :
8891 708 : CPLString osExtraDimNames;
8892 :
8893 354 : if (nd > 2)
8894 : {
8895 62 : nDim = 2;
8896 62 : panBandZLev = static_cast<int *>(CPLCalloc(nd - 2, sizeof(int)));
8897 :
8898 62 : osExtraDimNames = "{";
8899 :
8900 62 : char szDimName[NC_MAX_NAME + 1] = {};
8901 :
8902 62 : bool bREPORT_EXTRA_DIM_VALUESWarningEmitted = false;
8903 267 : for (int j = 0; j < nd; j++)
8904 : {
8905 348 : if ((poDS->m_anDimIds[j] != poDS->nXDimID) &&
8906 143 : (poDS->m_anDimIds[j] != poDS->nYDimID))
8907 : {
8908 81 : nc_inq_dimlen(cdfid, poDS->m_anDimIds[j], &lev_count);
8909 81 : nTotLevCount *= lev_count;
8910 81 : panBandZLev[nDim - 2] = static_cast<int>(lev_count);
8911 81 : anBandDimPos[nDim] = j; // Save Position of ZDim
8912 : // Save non-spatial dimension names.
8913 81 : if (nc_inq_dimname(cdfid, poDS->m_anDimIds[j], szDimName) ==
8914 : NC_NOERR)
8915 : {
8916 81 : osExtraDimNames += szDimName;
8917 81 : if (j < nd - 3)
8918 : {
8919 19 : osExtraDimNames += ",";
8920 : }
8921 :
8922 81 : int nIdxGroupID = -1;
8923 81 : int nIdxVarID = Get1DVariableIndexedByDimension(
8924 81 : cdfid, poDS->m_anDimIds[j], szDimName, true,
8925 81 : &nIdxGroupID);
8926 81 : poDS->m_anExtraDimGroupIds.push_back(nIdxGroupID);
8927 81 : poDS->m_anExtraDimVarIds.push_back(nIdxVarID);
8928 :
8929 81 : if (nIdxVarID >= 0)
8930 : {
8931 72 : nc_inq_vartype(nIdxGroupID, nIdxVarID, &nType);
8932 : char szExtraDimDef[NC_MAX_NAME + 1];
8933 72 : snprintf(szExtraDimDef, sizeof(szExtraDimDef),
8934 : "{%ld,%d}", (long)lev_count, nType);
8935 : char szTemp[NC_MAX_NAME + 32 + 1];
8936 72 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
8937 : szDimName);
8938 72 : poDS->papszMetadata = CSLSetNameValue(
8939 : poDS->papszMetadata, szTemp, szExtraDimDef);
8940 :
8941 : // Retrieving data for unlimited dimensions might be
8942 : // costly on network storage, so don't do it.
8943 : // Each band will capture the value along the extra
8944 : // dimension in its NETCDF_DIM_xxxx band metadata item
8945 : // Addresses use case of
8946 : // https://lists.osgeo.org/pipermail/gdal-dev/2023-May/057209.html
8947 : const bool bIsLocal =
8948 72 : VSIIsLocal(osFilenameForNCOpen.c_str());
8949 : bool bListDimValues =
8950 73 : bIsLocal || lev_count == 1 ||
8951 1 : !NCDFIsUnlimitedDim(poDS->eFormat ==
8952 : NCDF_FORMAT_NC4,
8953 1 : cdfid, poDS->m_anDimIds[j]);
8954 : const char *pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES =
8955 72 : CPLGetConfigOption(
8956 : "GDAL_NETCDF_REPORT_EXTRA_DIM_VALUES", nullptr);
8957 72 : if (pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES)
8958 : {
8959 2 : bListDimValues = CPLTestBool(
8960 : pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES);
8961 : }
8962 70 : else if (!bListDimValues && !bIsLocal &&
8963 1 : !bREPORT_EXTRA_DIM_VALUESWarningEmitted)
8964 : {
8965 1 : bREPORT_EXTRA_DIM_VALUESWarningEmitted = true;
8966 1 : CPLDebug(
8967 : "GDAL_netCDF",
8968 : "Listing extra dimension values is skipped "
8969 : "because this dataset is hosted on a network "
8970 : "file system, and such an operation could be "
8971 : "slow. If you still want to proceed, set the "
8972 : "GDAL_NETCDF_REPORT_EXTRA_DIM_VALUES "
8973 : "configuration option to YES");
8974 : }
8975 72 : if (bListDimValues)
8976 : {
8977 70 : char *pszTemp = nullptr;
8978 70 : if (NCDFGet1DVar(nIdxGroupID, nIdxVarID,
8979 70 : &pszTemp) == CE_None)
8980 : {
8981 70 : snprintf(szTemp, sizeof(szTemp),
8982 : "NETCDF_DIM_%s_VALUES", szDimName);
8983 70 : poDS->papszMetadata = CSLSetNameValue(
8984 : poDS->papszMetadata, szTemp, pszTemp);
8985 70 : CPLFree(pszTemp);
8986 : }
8987 : }
8988 : }
8989 : }
8990 : else
8991 : {
8992 0 : poDS->m_anExtraDimGroupIds.push_back(-1);
8993 0 : poDS->m_anExtraDimVarIds.push_back(-1);
8994 : }
8995 :
8996 81 : nDim++;
8997 : }
8998 : }
8999 62 : osExtraDimNames += "}";
9000 62 : poDS->papszMetadata = CSLSetNameValue(
9001 : poDS->papszMetadata, "NETCDF_DIM_EXTRA", osExtraDimNames);
9002 : }
9003 :
9004 : // Store Metadata.
9005 364 : for (const auto &osStr : aosRemovedMDItems)
9006 10 : poDS->papszMetadata =
9007 10 : CSLSetNameValue(poDS->papszMetadata, osStr.c_str(), nullptr);
9008 :
9009 354 : poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
9010 :
9011 : // Create bands.
9012 :
9013 : // Arbitrary threshold.
9014 : int nMaxBandCount =
9015 354 : atoi(CPLGetConfigOption("GDAL_MAX_BAND_COUNT", "32768"));
9016 354 : if (nMaxBandCount <= 0)
9017 0 : nMaxBandCount = 32768;
9018 354 : if (nTotLevCount > static_cast<unsigned int>(nMaxBandCount))
9019 : {
9020 0 : CPLError(CE_Warning, CPLE_AppDefined,
9021 : "Limiting number of bands to %d instead of %u", nMaxBandCount,
9022 : static_cast<unsigned int>(nTotLevCount));
9023 0 : nTotLevCount = static_cast<unsigned int>(nMaxBandCount);
9024 : }
9025 354 : if (poDS->nRasterXSize == 0 || poDS->nRasterYSize == 0)
9026 : {
9027 0 : poDS->nRasterXSize = 0;
9028 0 : poDS->nRasterYSize = 0;
9029 0 : nTotLevCount = 0;
9030 0 : if (poDS->GetLayerCount() == 0)
9031 : {
9032 0 : CPLFree(panBandZLev);
9033 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9034 : // deadlock with GDALDataset own mutex.
9035 0 : delete poDS;
9036 0 : CPLAcquireMutex(hNCMutex, 1000.0);
9037 0 : return nullptr;
9038 : }
9039 : }
9040 354 : if (bSeveralVariablesAsBands)
9041 : {
9042 6 : const auto &listVariables = oMap2DDimsToGroupAndVar.begin()->second;
9043 24 : for (int iBand = 0; iBand < static_cast<int>(listVariables.size());
9044 : ++iBand)
9045 : {
9046 18 : int bandVarGroupId = listVariables[iBand].first;
9047 18 : int bandVarId = listVariables[iBand].second;
9048 : netCDFRasterBand *poBand = new netCDFRasterBand(
9049 0 : netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, bandVarGroupId,
9050 18 : bandVarId, nDim, 0, nullptr, anBandDimPos.data(), iBand + 1);
9051 18 : poDS->SetBand(iBand + 1, poBand);
9052 : }
9053 : }
9054 : else
9055 : {
9056 810 : for (unsigned int lev = 0; lev < nTotLevCount; lev++)
9057 : {
9058 : netCDFRasterBand *poBand = new netCDFRasterBand(
9059 0 : netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, cdfid, var, nDim,
9060 462 : lev, panBandZLev, anBandDimPos.data(), lev + 1);
9061 462 : poDS->SetBand(lev + 1, poBand);
9062 : }
9063 : }
9064 :
9065 354 : if (panBandZLev)
9066 62 : CPLFree(panBandZLev);
9067 : // Handle angular geographic coordinates here
9068 :
9069 : // Initialize any PAM information.
9070 354 : if (bTreatAsSubdataset)
9071 : {
9072 60 : poDS->SetPhysicalFilename(poDS->osFilename);
9073 60 : poDS->SetSubdatasetName(osSubdatasetName);
9074 : }
9075 :
9076 354 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
9077 : // GDALDataset own mutex.
9078 354 : poDS->TryLoadXML();
9079 :
9080 354 : if (bTreatAsSubdataset)
9081 60 : poDS->oOvManager.Initialize(poDS, ":::VIRTUAL:::");
9082 : else
9083 294 : poDS->oOvManager.Initialize(poDS, poDS->osFilename);
9084 :
9085 354 : CPLAcquireMutex(hNCMutex, 1000.0);
9086 :
9087 354 : return poDS;
9088 : }
9089 :
9090 : /************************************************************************/
9091 : /* CopyMetadata() */
9092 : /* */
9093 : /* Create a copy of metadata for NC_GLOBAL or a variable */
9094 : /************************************************************************/
9095 :
9096 157 : static void CopyMetadata(GDALDataset *poSrcDS, GDALRasterBand *poSrcBand,
9097 : GDALRasterBand *poDstBand, int nCdfId, int CDFVarID,
9098 : const char *pszPrefix)
9099 : {
9100 : // Remove the following band meta but set them later from band data.
9101 157 : const char *const papszIgnoreBand[] = {
9102 : CF_ADD_OFFSET, CF_SCALE_FACTOR, "valid_range", "_Unsigned",
9103 : NCDF_FillValue, "coordinates", nullptr};
9104 157 : const char *const papszIgnoreGlobal[] = {"NETCDF_DIM_EXTRA", nullptr};
9105 :
9106 157 : CSLConstList papszMetadata = nullptr;
9107 157 : if (poSrcDS)
9108 : {
9109 66 : papszMetadata = poSrcDS->GetMetadata();
9110 : }
9111 91 : else if (poSrcBand)
9112 : {
9113 91 : papszMetadata = poSrcBand->GetMetadata();
9114 : }
9115 :
9116 637 : for (const auto &[pszKey, pszValue] : cpl::IterateNameValue(papszMetadata))
9117 : {
9118 : #ifdef NCDF_DEBUG
9119 : CPLDebug("GDAL_netCDF", "copy metadata [%s]=[%s]", pszKey, pszValue);
9120 : #endif
9121 :
9122 480 : CPLString osMetaName(pszKey);
9123 :
9124 : // Check for items that match pszPrefix if applicable.
9125 480 : if (pszPrefix && !EQUAL(pszPrefix, ""))
9126 : {
9127 : // Remove prefix.
9128 115 : if (STARTS_WITH(osMetaName.c_str(), pszPrefix))
9129 : {
9130 17 : osMetaName = osMetaName.substr(strlen(pszPrefix));
9131 : }
9132 : // Only copy items that match prefix.
9133 : else
9134 : {
9135 98 : continue;
9136 : }
9137 : }
9138 :
9139 : // Fix various issues with metadata translation.
9140 382 : if (CDFVarID == NC_GLOBAL)
9141 : {
9142 : // Do not copy items in papszIgnoreGlobal and NETCDF_DIM_*.
9143 481 : if ((CSLFindString(papszIgnoreGlobal, osMetaName) != -1) ||
9144 238 : (STARTS_WITH(osMetaName, "NETCDF_DIM_")))
9145 21 : continue;
9146 : // Remove NC_GLOBAL prefix for netcdf global Metadata.
9147 222 : else if (STARTS_WITH(osMetaName, "NC_GLOBAL#"))
9148 : {
9149 33 : osMetaName = osMetaName.substr(strlen("NC_GLOBAL#"));
9150 : }
9151 : // GDAL Metadata renamed as GDAL-[meta].
9152 189 : else if (strstr(osMetaName, "#") == nullptr)
9153 : {
9154 16 : osMetaName = "GDAL_" + osMetaName;
9155 : }
9156 : // Keep time, lev and depth information for safe-keeping.
9157 : // Time and vertical coordinate handling need improvements.
9158 : /*
9159 : else if( STARTS_WITH(szMetaName, "time#") )
9160 : {
9161 : szMetaName[4] = '-';
9162 : }
9163 : else if( STARTS_WITH(szMetaName, "lev#") )
9164 : {
9165 : szMetaName[3] = '-';
9166 : }
9167 : else if( STARTS_WITH(szMetaName, "depth#") )
9168 : {
9169 : szMetaName[5] = '-';
9170 : }
9171 : */
9172 : // Only copy data without # (previously all data was copied).
9173 222 : if (strstr(osMetaName, "#") != nullptr)
9174 173 : continue;
9175 : // netCDF attributes do not like the '#' character.
9176 : // for( unsigned int h=0; h < strlen(szMetaName) -1 ; h++ ) {
9177 : // if( szMetaName[h] == '#') szMetaName[h] = '-';
9178 : // }
9179 : }
9180 : else
9181 : {
9182 : // Do not copy varname, stats, NETCDF_DIM_*, nodata
9183 : // and items in papszIgnoreBand.
9184 139 : if (STARTS_WITH(osMetaName, "NETCDF_VARNAME") ||
9185 107 : STARTS_WITH(osMetaName, "STATISTICS_") ||
9186 107 : STARTS_WITH(osMetaName, "NETCDF_DIM_") ||
9187 74 : STARTS_WITH(osMetaName, "missing_value") ||
9188 293 : STARTS_WITH(osMetaName, "_FillValue") ||
9189 47 : CSLFindString(papszIgnoreBand, osMetaName) != -1)
9190 97 : continue;
9191 : }
9192 :
9193 : #ifdef NCDF_DEBUG
9194 : CPLDebug("GDAL_netCDF", "copy name=[%s] value=[%s]", osMetaName.c_str(),
9195 : pszValue);
9196 : #endif
9197 91 : if (NCDFPutAttr(nCdfId, CDFVarID, osMetaName, pszValue) != CE_None)
9198 : {
9199 0 : CPLDebug("GDAL_netCDF", "NCDFPutAttr(%d, %d, %s, %s) failed",
9200 : nCdfId, CDFVarID, osMetaName.c_str(), pszValue);
9201 : }
9202 : }
9203 :
9204 : // Set add_offset and scale_factor here if present.
9205 157 : if (poSrcBand && poDstBand)
9206 : {
9207 :
9208 91 : int bGotAddOffset = FALSE;
9209 91 : const double dfAddOffset = poSrcBand->GetOffset(&bGotAddOffset);
9210 91 : int bGotScale = FALSE;
9211 91 : const double dfScale = poSrcBand->GetScale(&bGotScale);
9212 :
9213 91 : if (bGotAddOffset && dfAddOffset != 0.0)
9214 1 : poDstBand->SetOffset(dfAddOffset);
9215 91 : if (bGotScale && dfScale != 1.0)
9216 1 : poDstBand->SetScale(dfScale);
9217 : }
9218 157 : }
9219 :
9220 : /************************************************************************/
9221 : /* CreateLL() */
9222 : /* */
9223 : /* Shared functionality between netCDFDataset::Create() and */
9224 : /* netCDF::CreateCopy() for creating netcdf file based on a set of */
9225 : /* options and a configuration. */
9226 : /************************************************************************/
9227 :
9228 198 : netCDFDataset *netCDFDataset::CreateLL(const char *pszFilename, int nXSize,
9229 : int nYSize, int nBandsIn,
9230 : char **papszOptions)
9231 : {
9232 198 : if (!((nXSize == 0 && nYSize == 0 && nBandsIn == 0) ||
9233 126 : (nXSize > 0 && nYSize > 0 && nBandsIn > 0)))
9234 : {
9235 1 : return nullptr;
9236 : }
9237 :
9238 197 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
9239 : // GDALDataset own mutex.
9240 197 : netCDFDataset *poDS = new netCDFDataset();
9241 197 : CPLAcquireMutex(hNCMutex, 1000.0);
9242 :
9243 197 : poDS->nRasterXSize = nXSize;
9244 197 : poDS->nRasterYSize = nYSize;
9245 197 : poDS->eAccess = GA_Update;
9246 197 : poDS->osFilename = pszFilename;
9247 :
9248 : // From gtiff driver, is this ok?
9249 : /*
9250 : poDS->nBlockXSize = nXSize;
9251 : poDS->nBlockYSize = 1;
9252 : poDS->nBlocksPerBand =
9253 : DIV_ROUND_UP((nYSize, poDS->nBlockYSize))
9254 : * DIV_ROUND_UP((nXSize, poDS->nBlockXSize));
9255 : */
9256 :
9257 : // process options.
9258 197 : poDS->papszCreationOptions = CSLDuplicate(papszOptions);
9259 197 : poDS->ProcessCreationOptions();
9260 :
9261 197 : if (poDS->eMultipleLayerBehavior == SEPARATE_FILES)
9262 : {
9263 : VSIStatBuf sStat;
9264 2 : if (VSIStat(pszFilename, &sStat) == 0)
9265 : {
9266 0 : if (!VSI_ISDIR(sStat.st_mode))
9267 : {
9268 0 : CPLError(CE_Failure, CPLE_FileIO,
9269 : "%s is an existing file, but not a directory",
9270 : pszFilename);
9271 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9272 : // deadlock with GDALDataset own
9273 : // mutex.
9274 0 : delete poDS;
9275 0 : CPLAcquireMutex(hNCMutex, 1000.0);
9276 0 : return nullptr;
9277 : }
9278 : }
9279 2 : else if (VSIMkdir(pszFilename, 0755) != 0)
9280 : {
9281 1 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s directory",
9282 : pszFilename);
9283 1 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9284 : // deadlock with GDALDataset own mutex.
9285 1 : delete poDS;
9286 1 : CPLAcquireMutex(hNCMutex, 1000.0);
9287 1 : return nullptr;
9288 : }
9289 :
9290 1 : return poDS;
9291 : }
9292 : // Create the dataset.
9293 390 : CPLString osFilenameForNCCreate(pszFilename);
9294 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
9295 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
9296 : {
9297 : char *pszTemp =
9298 : CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
9299 : osFilenameForNCCreate = pszTemp;
9300 : CPLFree(pszTemp);
9301 : }
9302 : #endif
9303 :
9304 : #if defined(_WIN32)
9305 : {
9306 : // Works around bug of msys2 netCDF 4.9.0 package where nc_create()
9307 : // crashes
9308 : VSIStatBuf sStat;
9309 : const std::string osDirname =
9310 : CPLGetDirnameSafe(osFilenameForNCCreate.c_str());
9311 : if (VSIStat(osDirname.c_str(), &sStat) != 0)
9312 : {
9313 : CPLError(CE_Failure, CPLE_OpenFailed,
9314 : "Unable to create netCDF file %s: non existing output "
9315 : "directory",
9316 : pszFilename);
9317 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9318 : // deadlock with GDALDataset own mutex.
9319 : delete poDS;
9320 : CPLAcquireMutex(hNCMutex, 1000.0);
9321 : return nullptr;
9322 : }
9323 : }
9324 : #endif
9325 :
9326 : int status =
9327 195 : nc_create(osFilenameForNCCreate, poDS->nCreateMode, &(poDS->cdfid));
9328 :
9329 : // Put into define mode.
9330 195 : poDS->SetDefineMode(true);
9331 :
9332 195 : if (status != NC_NOERR)
9333 : {
9334 30 : CPLError(CE_Failure, CPLE_OpenFailed,
9335 : "Unable to create netCDF file %s (Error code %d): %s .",
9336 : pszFilename, status, nc_strerror(status));
9337 30 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
9338 : // with GDALDataset own mutex.
9339 30 : delete poDS;
9340 30 : CPLAcquireMutex(hNCMutex, 1000.0);
9341 30 : return nullptr;
9342 : }
9343 :
9344 : // Define dimensions.
9345 165 : if (nXSize > 0 && nYSize > 0)
9346 : {
9347 112 : poDS->papszDimName.AddString(NCDF_DIMNAME_X);
9348 : status =
9349 112 : nc_def_dim(poDS->cdfid, NCDF_DIMNAME_X, nXSize, &(poDS->nXDimID));
9350 112 : NCDF_ERR(status);
9351 112 : CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
9352 : poDS->cdfid, NCDF_DIMNAME_X, nXSize, poDS->nXDimID);
9353 :
9354 112 : poDS->papszDimName.AddString(NCDF_DIMNAME_Y);
9355 : status =
9356 112 : nc_def_dim(poDS->cdfid, NCDF_DIMNAME_Y, nYSize, &(poDS->nYDimID));
9357 112 : NCDF_ERR(status);
9358 112 : CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
9359 : poDS->cdfid, NCDF_DIMNAME_Y, nYSize, poDS->nYDimID);
9360 : }
9361 :
9362 165 : return poDS;
9363 : }
9364 :
9365 : /************************************************************************/
9366 : /* Create() */
9367 : /************************************************************************/
9368 :
9369 126 : GDALDataset *netCDFDataset::Create(const char *pszFilename, int nXSize,
9370 : int nYSize, int nBandsIn, GDALDataType eType,
9371 : char **papszOptions)
9372 : {
9373 126 : CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::Create(%s, ...)",
9374 : pszFilename);
9375 :
9376 : const char *legacyCreationOp =
9377 126 : CSLFetchNameValueDef(papszOptions, "GEOMETRY_ENCODING", "CF_1.8");
9378 252 : std::string legacyCreationOp_s = std::string(legacyCreationOp);
9379 :
9380 : // Check legacy creation op FIRST
9381 :
9382 126 : bool legacyCreateMode = false;
9383 :
9384 126 : if (nXSize != 0 || nYSize != 0 || nBandsIn != 0)
9385 : {
9386 56 : legacyCreateMode = true;
9387 : }
9388 70 : else if (legacyCreationOp_s == "CF_1.8")
9389 : {
9390 54 : legacyCreateMode = false;
9391 : }
9392 :
9393 16 : else if (legacyCreationOp_s == "WKT")
9394 : {
9395 16 : legacyCreateMode = true;
9396 : }
9397 :
9398 : else
9399 : {
9400 0 : CPLError(
9401 : CE_Failure, CPLE_NotSupported,
9402 : "Dataset creation option GEOMETRY_ENCODING=%s is not supported.",
9403 : legacyCreationOp_s.c_str());
9404 0 : return nullptr;
9405 : }
9406 :
9407 252 : CPLStringList aosOptions(CSLDuplicate(papszOptions));
9408 238 : if (aosOptions.FetchNameValue("FORMAT") == nullptr &&
9409 112 : (eType == GDT_UInt16 || eType == GDT_UInt32 || eType == GDT_UInt64 ||
9410 : eType == GDT_Int64))
9411 : {
9412 10 : CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
9413 10 : aosOptions.SetNameValue("FORMAT", "NC4");
9414 : }
9415 :
9416 252 : CPLStringList aosBandNames;
9417 126 : if (const char *pszBandNames = aosOptions.FetchNameValue("BAND_NAMES"))
9418 : {
9419 : aosBandNames =
9420 2 : CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);
9421 :
9422 2 : if (aosBandNames.Count() != nBandsIn)
9423 : {
9424 1 : CPLError(CE_Failure, CPLE_OpenFailed,
9425 : "Attempted to create netCDF with %d bands but %d names "
9426 : "provided in BAND_NAMES.",
9427 : nBandsIn, aosBandNames.Count());
9428 :
9429 1 : return nullptr;
9430 : }
9431 : }
9432 :
9433 250 : CPLMutexHolderD(&hNCMutex);
9434 :
9435 125 : auto poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize, nBandsIn,
9436 : aosOptions.List());
9437 :
9438 125 : if (!poDS)
9439 19 : return nullptr;
9440 :
9441 106 : if (!legacyCreateMode)
9442 : {
9443 37 : poDS->bSGSupport = true;
9444 37 : poDS->vcdf.enableFullVirtualMode();
9445 : }
9446 :
9447 : else
9448 : {
9449 69 : poDS->bSGSupport = false;
9450 : }
9451 :
9452 : // Should we write signed or unsigned byte?
9453 : // TODO should this only be done in Create()
9454 106 : poDS->bSignedData = true;
9455 106 : const char *pszValue = CSLFetchNameValueDef(papszOptions, "PIXELTYPE", "");
9456 106 : if (eType == GDT_Byte && !EQUAL(pszValue, "SIGNEDBYTE"))
9457 15 : poDS->bSignedData = false;
9458 :
9459 : // Add Conventions, GDAL info and history.
9460 106 : if (poDS->cdfid >= 0)
9461 : {
9462 : const char *CF_Vector_Conv =
9463 173 : poDS->bSGSupport ||
9464 : // Use of variable length strings require CF-1.8
9465 68 : EQUAL(aosOptions.FetchNameValueDef("FORMAT", ""), "NC4")
9466 : ? NCDF_CONVENTIONS_CF_V1_8
9467 173 : : NCDF_CONVENTIONS_CF_V1_6;
9468 105 : poDS->bWriteGDALVersion = CPLTestBool(
9469 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
9470 105 : poDS->bWriteGDALHistory = CPLTestBool(
9471 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
9472 105 : NCDFAddGDALHistory(poDS->cdfid, pszFilename, poDS->bWriteGDALVersion,
9473 105 : poDS->bWriteGDALHistory, "", "Create",
9474 : (nBandsIn == 0) ? CF_Vector_Conv
9475 : : GDAL_DEFAULT_NCDF_CONVENTIONS);
9476 : }
9477 :
9478 : // Define bands.
9479 197 : for (int iBand = 1; iBand <= nBandsIn; iBand++)
9480 : {
9481 : const char *pszBandName =
9482 91 : aosBandNames.empty() ? nullptr : aosBandNames[iBand - 1];
9483 :
9484 91 : poDS->SetBand(iBand, new netCDFRasterBand(
9485 91 : netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS,
9486 91 : eType, iBand, poDS->bSignedData, pszBandName));
9487 : }
9488 :
9489 106 : CPLDebug("GDAL_netCDF", "netCDFDataset::Create(%s, ...) done", pszFilename);
9490 : // Return same dataset.
9491 106 : return poDS;
9492 : }
9493 :
9494 : template <class T>
9495 91 : static CPLErr NCDFCopyBand(GDALRasterBand *poSrcBand, GDALRasterBand *poDstBand,
9496 : int nXSize, int nYSize, GDALProgressFunc pfnProgress,
9497 : void *pProgressData)
9498 : {
9499 91 : GDALDataType eDT = poSrcBand->GetRasterDataType();
9500 91 : CPLErr eErr = CE_None;
9501 91 : T *patScanline = static_cast<T *>(CPLMalloc(nXSize * sizeof(T)));
9502 :
9503 6308 : for (int iLine = 0; iLine < nYSize && eErr == CE_None; iLine++)
9504 : {
9505 6217 : eErr = poSrcBand->RasterIO(GF_Read, 0, iLine, nXSize, 1, patScanline,
9506 : nXSize, 1, eDT, 0, 0, nullptr);
9507 6217 : if (eErr != CE_None)
9508 : {
9509 0 : CPLDebug(
9510 : "GDAL_netCDF",
9511 : "NCDFCopyBand(), poSrcBand->RasterIO() returned error code %d",
9512 : eErr);
9513 : }
9514 : else
9515 : {
9516 6217 : eErr =
9517 : poDstBand->RasterIO(GF_Write, 0, iLine, nXSize, 1, patScanline,
9518 : nXSize, 1, eDT, 0, 0, nullptr);
9519 6217 : if (eErr != CE_None)
9520 0 : CPLDebug("GDAL_netCDF",
9521 : "NCDFCopyBand(), poDstBand->RasterIO() returned error "
9522 : "code %d",
9523 : eErr);
9524 : }
9525 :
9526 6217 : if (nYSize > 10 && (iLine % (nYSize / 10) == 1))
9527 : {
9528 277 : if (!pfnProgress(1.0 * iLine / nYSize, nullptr, pProgressData))
9529 : {
9530 0 : eErr = CE_Failure;
9531 0 : CPLError(CE_Failure, CPLE_UserInterrupt,
9532 : "User terminated CreateCopy()");
9533 : }
9534 : }
9535 : }
9536 :
9537 91 : CPLFree(patScanline);
9538 :
9539 91 : pfnProgress(1.0, nullptr, pProgressData);
9540 :
9541 91 : return eErr;
9542 : }
9543 :
9544 : /************************************************************************/
9545 : /* CreateCopy() */
9546 : /************************************************************************/
9547 :
9548 : GDALDataset *
9549 83 : netCDFDataset::CreateCopy(const char *pszFilename, GDALDataset *poSrcDS,
9550 : CPL_UNUSED int bStrict, char **papszOptions,
9551 : GDALProgressFunc pfnProgress, void *pProgressData)
9552 : {
9553 166 : CPLMutexHolderD(&hNCMutex);
9554 :
9555 83 : CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::CreateCopy(%s, ...)",
9556 : pszFilename);
9557 :
9558 83 : if (poSrcDS->GetRootGroup())
9559 : {
9560 6 : auto poDrv = GDALDriver::FromHandle(GDALGetDriverByName("netCDF"));
9561 6 : if (poDrv)
9562 : {
9563 6 : return poDrv->DefaultCreateCopy(pszFilename, poSrcDS, bStrict,
9564 : papszOptions, pfnProgress,
9565 6 : pProgressData);
9566 : }
9567 : }
9568 :
9569 77 : const int nBands = poSrcDS->GetRasterCount();
9570 77 : const int nXSize = poSrcDS->GetRasterXSize();
9571 77 : const int nYSize = poSrcDS->GetRasterYSize();
9572 77 : const char *pszWKT = poSrcDS->GetProjectionRef();
9573 :
9574 : // Check input bands for errors.
9575 77 : if (nBands == 0)
9576 : {
9577 1 : CPLError(CE_Failure, CPLE_NotSupported,
9578 : "NetCDF driver does not support "
9579 : "source dataset with zero band.");
9580 1 : return nullptr;
9581 : }
9582 :
9583 76 : GDALDataType eDT = GDT_Unknown;
9584 76 : GDALRasterBand *poSrcBand = nullptr;
9585 181 : for (int iBand = 1; iBand <= nBands; iBand++)
9586 : {
9587 109 : poSrcBand = poSrcDS->GetRasterBand(iBand);
9588 109 : eDT = poSrcBand->GetRasterDataType();
9589 109 : if (eDT == GDT_Unknown || GDALDataTypeIsComplex(eDT))
9590 : {
9591 4 : CPLError(CE_Failure, CPLE_NotSupported,
9592 : "NetCDF driver does not support source dataset with band "
9593 : "of complex type.");
9594 4 : return nullptr;
9595 : }
9596 : }
9597 :
9598 144 : CPLStringList aosBandNames;
9599 72 : if (const char *pszBandNames =
9600 72 : CSLFetchNameValue(papszOptions, "BAND_NAMES"))
9601 : {
9602 : aosBandNames =
9603 2 : CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);
9604 :
9605 2 : if (aosBandNames.Count() != nBands)
9606 : {
9607 1 : CPLError(CE_Failure, CPLE_OpenFailed,
9608 : "Attempted to create netCDF with %d bands but %d names "
9609 : "provided in BAND_NAMES.",
9610 : nBands, aosBandNames.Count());
9611 :
9612 1 : return nullptr;
9613 : }
9614 : }
9615 :
9616 71 : if (!pfnProgress(0.0, nullptr, pProgressData))
9617 0 : return nullptr;
9618 :
9619 : // Same as in Create().
9620 142 : CPLStringList aosOptions(CSLDuplicate(papszOptions));
9621 133 : if (aosOptions.FetchNameValue("FORMAT") == nullptr &&
9622 62 : (eDT == GDT_UInt16 || eDT == GDT_UInt32 || eDT == GDT_UInt64 ||
9623 : eDT == GDT_Int64))
9624 : {
9625 6 : CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
9626 6 : aosOptions.SetNameValue("FORMAT", "NC4");
9627 : }
9628 71 : netCDFDataset *poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize,
9629 : nBands, aosOptions.List());
9630 71 : if (!poDS)
9631 13 : return nullptr;
9632 :
9633 : // Copy global metadata.
9634 : // Add Conventions, GDAL info and history.
9635 58 : CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid, NC_GLOBAL, nullptr);
9636 58 : const bool bWriteGDALVersion = CPLTestBool(
9637 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
9638 58 : const bool bWriteGDALHistory = CPLTestBool(
9639 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
9640 58 : NCDFAddGDALHistory(
9641 : poDS->cdfid, pszFilename, bWriteGDALVersion, bWriteGDALHistory,
9642 58 : poSrcDS->GetMetadataItem("NC_GLOBAL#history"), "CreateCopy",
9643 58 : poSrcDS->GetMetadataItem("NC_GLOBAL#Conventions"));
9644 :
9645 58 : pfnProgress(0.1, nullptr, pProgressData);
9646 :
9647 : // Check for extra dimensions.
9648 58 : int nDim = 2;
9649 : char **papszExtraDimNames =
9650 58 : NCDFTokenizeArray(poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
9651 58 : char **papszExtraDimValues = nullptr;
9652 :
9653 58 : if (papszExtraDimNames != nullptr && CSLCount(papszExtraDimNames) > 0)
9654 : {
9655 5 : size_t nDimSizeTot = 1;
9656 : // first make sure dimensions lengths compatible with band count
9657 : // for( int i=0; i<CSLCount(papszExtraDimNames ); i++ ) {
9658 13 : for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--)
9659 : {
9660 : char szTemp[NC_MAX_NAME + 32 + 1];
9661 8 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
9662 8 : papszExtraDimNames[i]);
9663 : papszExtraDimValues =
9664 8 : NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
9665 8 : const size_t nDimSize = atol(papszExtraDimValues[0]);
9666 8 : CSLDestroy(papszExtraDimValues);
9667 8 : nDimSizeTot *= nDimSize;
9668 : }
9669 5 : if (nDimSizeTot == (size_t)nBands)
9670 : {
9671 5 : nDim = 2 + CSLCount(papszExtraDimNames);
9672 : }
9673 : else
9674 : {
9675 : // if nBands != #bands computed raise a warning
9676 : // just issue a debug message, because it was probably intentional
9677 0 : CPLDebug("GDAL_netCDF",
9678 : "Warning: Number of bands (%d) is not compatible with "
9679 : "dimensions "
9680 : "(total=%ld names=%s)",
9681 : nBands, (long)nDimSizeTot,
9682 0 : poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
9683 0 : CSLDestroy(papszExtraDimNames);
9684 0 : papszExtraDimNames = nullptr;
9685 : }
9686 : }
9687 :
9688 58 : int *panDimIds = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
9689 58 : int *panBandDimPos = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
9690 :
9691 : nc_type nVarType;
9692 58 : int *panBandZLev = nullptr;
9693 58 : int *panDimVarIds = nullptr;
9694 :
9695 58 : if (nDim > 2)
9696 : {
9697 5 : panBandZLev = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
9698 5 : panDimVarIds = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
9699 :
9700 : // Define all dims.
9701 13 : for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--)
9702 : {
9703 8 : poDS->papszDimName.AddString(papszExtraDimNames[i]);
9704 : char szTemp[NC_MAX_NAME + 32 + 1];
9705 8 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
9706 8 : papszExtraDimNames[i]);
9707 : papszExtraDimValues =
9708 8 : NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
9709 8 : const int nDimSize = papszExtraDimValues && papszExtraDimValues[0]
9710 16 : ? atoi(papszExtraDimValues[0])
9711 : : 0;
9712 : // nc_type is an enum in netcdf-3, needs casting.
9713 8 : nVarType = static_cast<nc_type>(papszExtraDimValues &&
9714 8 : papszExtraDimValues[0] &&
9715 8 : papszExtraDimValues[1]
9716 8 : ? atol(papszExtraDimValues[1])
9717 : : 0);
9718 8 : CSLDestroy(papszExtraDimValues);
9719 8 : panBandZLev[i] = nDimSize;
9720 8 : panBandDimPos[i + 2] = i; // Save Position of ZDim.
9721 :
9722 : // Define dim.
9723 16 : int status = nc_def_dim(poDS->cdfid, papszExtraDimNames[i],
9724 8 : nDimSize, &(panDimIds[i]));
9725 8 : NCDF_ERR(status);
9726 :
9727 : // Define dim var.
9728 8 : int anDim[1] = {panDimIds[i]};
9729 16 : status = nc_def_var(poDS->cdfid, papszExtraDimNames[i], nVarType, 1,
9730 8 : anDim, &(panDimVarIds[i]));
9731 8 : NCDF_ERR(status);
9732 :
9733 : // Add dim metadata, using global var# items.
9734 8 : snprintf(szTemp, sizeof(szTemp), "%s#", papszExtraDimNames[i]);
9735 8 : CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid,
9736 8 : panDimVarIds[i], szTemp);
9737 : }
9738 : }
9739 :
9740 : // Copy GeoTransform and Projection.
9741 :
9742 : // Copy geolocation info.
9743 58 : char **papszGeolocationInfo = poSrcDS->GetMetadata("GEOLOCATION");
9744 58 : if (papszGeolocationInfo != nullptr)
9745 5 : poDS->GDALPamDataset::SetMetadata(papszGeolocationInfo, "GEOLOCATION");
9746 :
9747 : // Copy geotransform.
9748 58 : bool bGotGeoTransform = false;
9749 58 : GDALGeoTransform gt;
9750 58 : CPLErr eErr = poSrcDS->GetGeoTransform(gt);
9751 58 : if (eErr == CE_None)
9752 : {
9753 40 : poDS->SetGeoTransform(gt);
9754 : // Disable AddProjectionVars() from being called.
9755 40 : bGotGeoTransform = true;
9756 40 : poDS->m_bHasGeoTransform = false;
9757 : }
9758 :
9759 : // Copy projection.
9760 58 : void *pScaledProgress = nullptr;
9761 58 : if (bGotGeoTransform || (pszWKT && pszWKT[0] != 0))
9762 : {
9763 41 : poDS->SetProjection(pszWKT ? pszWKT : "");
9764 :
9765 : // Now we can call AddProjectionVars() directly.
9766 41 : poDS->m_bHasGeoTransform = bGotGeoTransform;
9767 41 : poDS->AddProjectionVars(true, nullptr, nullptr);
9768 : pScaledProgress =
9769 41 : GDALCreateScaledProgress(0.1, 0.25, pfnProgress, pProgressData);
9770 41 : poDS->AddProjectionVars(false, GDALScaledProgress, pScaledProgress);
9771 41 : GDALDestroyScaledProgress(pScaledProgress);
9772 : }
9773 : else
9774 : {
9775 17 : poDS->bBottomUp =
9776 17 : CPL_TO_BOOL(CSLFetchBoolean(papszOptions, "WRITE_BOTTOMUP", TRUE));
9777 17 : if (papszGeolocationInfo)
9778 : {
9779 4 : poDS->AddProjectionVars(true, nullptr, nullptr);
9780 4 : poDS->AddProjectionVars(false, nullptr, nullptr);
9781 : }
9782 : }
9783 :
9784 : // Save X,Y dim positions.
9785 58 : panDimIds[nDim - 1] = poDS->nXDimID;
9786 58 : panBandDimPos[0] = nDim - 1;
9787 58 : panDimIds[nDim - 2] = poDS->nYDimID;
9788 58 : panBandDimPos[1] = nDim - 2;
9789 :
9790 : // Write extra dim values - after projection for optimization.
9791 58 : if (nDim > 2)
9792 : {
9793 : // Make sure we are in data mode.
9794 5 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
9795 13 : for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--)
9796 : {
9797 : char szTemp[NC_MAX_NAME + 32 + 1];
9798 8 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_VALUES",
9799 8 : papszExtraDimNames[i]);
9800 8 : if (poSrcDS->GetMetadataItem(szTemp) != nullptr)
9801 : {
9802 8 : NCDFPut1DVar(poDS->cdfid, panDimVarIds[i],
9803 8 : poSrcDS->GetMetadataItem(szTemp));
9804 : }
9805 : }
9806 : }
9807 :
9808 58 : pfnProgress(0.25, nullptr, pProgressData);
9809 :
9810 : // Define Bands.
9811 58 : netCDFRasterBand *poBand = nullptr;
9812 58 : int nBandID = -1;
9813 :
9814 149 : for (int iBand = 1; iBand <= nBands; iBand++)
9815 : {
9816 91 : CPLDebug("GDAL_netCDF", "creating band # %d/%d nDim = %d", iBand,
9817 : nBands, nDim);
9818 :
9819 91 : poSrcBand = poSrcDS->GetRasterBand(iBand);
9820 91 : eDT = poSrcBand->GetRasterDataType();
9821 :
9822 : // Get var name from NETCDF_VARNAME.
9823 : const char *pszNETCDF_VARNAME =
9824 91 : poSrcBand->GetMetadataItem("NETCDF_VARNAME");
9825 : char szBandName[NC_MAX_NAME + 1];
9826 91 : if (!aosBandNames.empty())
9827 : {
9828 2 : snprintf(szBandName, sizeof(szBandName), "%s",
9829 : aosBandNames[iBand - 1]);
9830 : }
9831 89 : else if (pszNETCDF_VARNAME)
9832 : {
9833 32 : if (nBands > 1 && papszExtraDimNames == nullptr)
9834 0 : snprintf(szBandName, sizeof(szBandName), "%s%d",
9835 : pszNETCDF_VARNAME, iBand);
9836 : else
9837 32 : snprintf(szBandName, sizeof(szBandName), "%s",
9838 : pszNETCDF_VARNAME);
9839 : }
9840 : else
9841 : {
9842 57 : szBandName[0] = '\0';
9843 : }
9844 :
9845 : // Get long_name from <var>#long_name.
9846 91 : const char *pszLongName = "";
9847 91 : if (pszNETCDF_VARNAME)
9848 : {
9849 : pszLongName =
9850 64 : poSrcDS->GetMetadataItem(std::string(pszNETCDF_VARNAME)
9851 32 : .append("#")
9852 32 : .append(CF_LNG_NAME)
9853 32 : .c_str());
9854 32 : if (!pszLongName)
9855 25 : pszLongName = "";
9856 : }
9857 :
9858 91 : constexpr bool bSignedData = false;
9859 :
9860 91 : if (nDim > 2)
9861 27 : poBand = new netCDFRasterBand(
9862 27 : netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
9863 : bSignedData, szBandName, pszLongName, nBandID, nDim, iBand - 1,
9864 27 : panBandZLev, panBandDimPos, panDimIds);
9865 : else
9866 64 : poBand = new netCDFRasterBand(
9867 64 : netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
9868 64 : bSignedData, szBandName, pszLongName);
9869 :
9870 91 : poDS->SetBand(iBand, poBand);
9871 :
9872 : // Set nodata value, if any.
9873 91 : GDALCopyNoDataValue(poBand, poSrcBand);
9874 :
9875 : // Copy Metadata for band.
9876 91 : CopyMetadata(nullptr, poSrcDS->GetRasterBand(iBand), poBand,
9877 : poDS->cdfid, poBand->nZId);
9878 :
9879 : // If more than 2D pass the first band's netcdf var ID to subsequent
9880 : // bands.
9881 91 : if (nDim > 2)
9882 27 : nBandID = poBand->nZId;
9883 : }
9884 :
9885 : // Write projection variable to band variable.
9886 58 : poDS->AddGridMappingRef();
9887 :
9888 58 : pfnProgress(0.5, nullptr, pProgressData);
9889 :
9890 : // Write bands.
9891 :
9892 : // Make sure we are in data mode.
9893 58 : poDS->SetDefineMode(false);
9894 :
9895 58 : double dfTemp = 0.5;
9896 :
9897 58 : eErr = CE_None;
9898 :
9899 149 : for (int iBand = 1; iBand <= nBands && eErr == CE_None; iBand++)
9900 : {
9901 91 : const double dfTemp2 = dfTemp + 0.4 / nBands;
9902 91 : pScaledProgress = GDALCreateScaledProgress(dfTemp, dfTemp2, pfnProgress,
9903 : pProgressData);
9904 91 : dfTemp = dfTemp2;
9905 :
9906 91 : CPLDebug("GDAL_netCDF", "copying band data # %d/%d ", iBand, nBands);
9907 :
9908 91 : poSrcBand = poSrcDS->GetRasterBand(iBand);
9909 91 : eDT = poSrcBand->GetRasterDataType();
9910 :
9911 91 : GDALRasterBand *poDstBand = poDS->GetRasterBand(iBand);
9912 :
9913 : // Copy band data.
9914 91 : if (eDT == GDT_Byte)
9915 : {
9916 51 : CPLDebug("GDAL_netCDF", "GByte Band#%d", iBand);
9917 51 : eErr = NCDFCopyBand<GByte>(poSrcBand, poDstBand, nXSize, nYSize,
9918 : GDALScaledProgress, pScaledProgress);
9919 : }
9920 40 : else if (eDT == GDT_Int8)
9921 : {
9922 1 : CPLDebug("GDAL_netCDF", "GInt8 Band#%d", iBand);
9923 1 : eErr = NCDFCopyBand<GInt8>(poSrcBand, poDstBand, nXSize, nYSize,
9924 : GDALScaledProgress, pScaledProgress);
9925 : }
9926 39 : else if (eDT == GDT_UInt16)
9927 : {
9928 2 : CPLDebug("GDAL_netCDF", "GUInt16 Band#%d", iBand);
9929 2 : eErr = NCDFCopyBand<GInt16>(poSrcBand, poDstBand, nXSize, nYSize,
9930 : GDALScaledProgress, pScaledProgress);
9931 : }
9932 37 : else if (eDT == GDT_Int16)
9933 : {
9934 5 : CPLDebug("GDAL_netCDF", "GInt16 Band#%d", iBand);
9935 5 : eErr = NCDFCopyBand<GUInt16>(poSrcBand, poDstBand, nXSize, nYSize,
9936 : GDALScaledProgress, pScaledProgress);
9937 : }
9938 32 : else if (eDT == GDT_UInt32)
9939 : {
9940 2 : CPLDebug("GDAL_netCDF", "GUInt32 Band#%d", iBand);
9941 2 : eErr = NCDFCopyBand<GUInt32>(poSrcBand, poDstBand, nXSize, nYSize,
9942 : GDALScaledProgress, pScaledProgress);
9943 : }
9944 30 : else if (eDT == GDT_Int32)
9945 : {
9946 18 : CPLDebug("GDAL_netCDF", "GInt32 Band#%d", iBand);
9947 18 : eErr = NCDFCopyBand<GInt32>(poSrcBand, poDstBand, nXSize, nYSize,
9948 : GDALScaledProgress, pScaledProgress);
9949 : }
9950 12 : else if (eDT == GDT_UInt64)
9951 : {
9952 2 : CPLDebug("GDAL_netCDF", "GUInt64 Band#%d", iBand);
9953 2 : eErr = NCDFCopyBand<std::uint64_t>(poSrcBand, poDstBand, nXSize,
9954 : nYSize, GDALScaledProgress,
9955 : pScaledProgress);
9956 : }
9957 10 : else if (eDT == GDT_Int64)
9958 : {
9959 2 : CPLDebug("GDAL_netCDF", "GInt64 Band#%d", iBand);
9960 : eErr =
9961 2 : NCDFCopyBand<std::int64_t>(poSrcBand, poDstBand, nXSize, nYSize,
9962 : GDALScaledProgress, pScaledProgress);
9963 : }
9964 8 : else if (eDT == GDT_Float32)
9965 : {
9966 6 : CPLDebug("GDAL_netCDF", "float Band#%d", iBand);
9967 6 : eErr = NCDFCopyBand<float>(poSrcBand, poDstBand, nXSize, nYSize,
9968 : GDALScaledProgress, pScaledProgress);
9969 : }
9970 2 : else if (eDT == GDT_Float64)
9971 : {
9972 2 : CPLDebug("GDAL_netCDF", "double Band#%d", iBand);
9973 2 : eErr = NCDFCopyBand<double>(poSrcBand, poDstBand, nXSize, nYSize,
9974 : GDALScaledProgress, pScaledProgress);
9975 : }
9976 : else
9977 : {
9978 0 : CPLError(CE_Failure, CPLE_NotSupported,
9979 : "The NetCDF driver does not support GDAL data type %d",
9980 : eDT);
9981 : }
9982 :
9983 91 : GDALDestroyScaledProgress(pScaledProgress);
9984 : }
9985 :
9986 58 : delete (poDS);
9987 :
9988 58 : CPLFree(panDimIds);
9989 58 : CPLFree(panBandDimPos);
9990 58 : CPLFree(panBandZLev);
9991 58 : CPLFree(panDimVarIds);
9992 58 : if (papszExtraDimNames)
9993 5 : CSLDestroy(papszExtraDimNames);
9994 :
9995 58 : if (eErr != CE_None)
9996 0 : return nullptr;
9997 :
9998 58 : pfnProgress(0.95, nullptr, pProgressData);
9999 :
10000 : // Re-open dataset so we can return it.
10001 116 : CPLStringList aosOpenOptions;
10002 58 : aosOpenOptions.AddString("VARIABLES_AS_BANDS=YES");
10003 58 : GDALOpenInfo oOpenInfo(pszFilename, GA_Update);
10004 58 : oOpenInfo.nOpenFlags = GDAL_OF_RASTER | GDAL_OF_UPDATE;
10005 58 : oOpenInfo.papszOpenOptions = aosOpenOptions.List();
10006 58 : auto poRetDS = Open(&oOpenInfo);
10007 :
10008 : // PAM cloning is disabled. See bug #4244.
10009 : // if( poDS )
10010 : // poDS->CloneInfo(poSrcDS, GCIF_PAM_DEFAULT);
10011 :
10012 58 : pfnProgress(1.0, nullptr, pProgressData);
10013 :
10014 58 : return poRetDS;
10015 : }
10016 :
10017 : // Note: some logic depends on bIsProjected and bIsGeoGraphic.
10018 : // May not be known when Create() is called, see AddProjectionVars().
10019 255 : void netCDFDataset::ProcessCreationOptions()
10020 : {
10021 : const char *pszConfig =
10022 255 : CSLFetchNameValue(papszCreationOptions, "CONFIG_FILE");
10023 255 : if (pszConfig != nullptr)
10024 : {
10025 4 : if (oWriterConfig.Parse(pszConfig))
10026 : {
10027 : // Override dataset creation options from the config file
10028 2 : std::map<CPLString, CPLString>::iterator oIter;
10029 3 : for (oIter = oWriterConfig.m_oDatasetCreationOptions.begin();
10030 3 : oIter != oWriterConfig.m_oDatasetCreationOptions.end();
10031 1 : ++oIter)
10032 : {
10033 2 : papszCreationOptions = CSLSetNameValue(
10034 2 : papszCreationOptions, oIter->first, oIter->second);
10035 : }
10036 : }
10037 : }
10038 :
10039 : // File format.
10040 255 : eFormat = NCDF_FORMAT_NC;
10041 255 : const char *pszValue = CSLFetchNameValue(papszCreationOptions, "FORMAT");
10042 255 : if (pszValue != nullptr)
10043 : {
10044 94 : if (EQUAL(pszValue, "NC"))
10045 : {
10046 3 : eFormat = NCDF_FORMAT_NC;
10047 : }
10048 : #ifdef NETCDF_HAS_NC2
10049 91 : else if (EQUAL(pszValue, "NC2"))
10050 : {
10051 1 : eFormat = NCDF_FORMAT_NC2;
10052 : }
10053 : #endif
10054 90 : else if (EQUAL(pszValue, "NC4"))
10055 : {
10056 86 : eFormat = NCDF_FORMAT_NC4;
10057 : }
10058 4 : else if (EQUAL(pszValue, "NC4C"))
10059 : {
10060 4 : eFormat = NCDF_FORMAT_NC4C;
10061 : }
10062 : else
10063 : {
10064 0 : CPLError(CE_Failure, CPLE_NotSupported,
10065 : "FORMAT=%s in not supported, using the default NC format.",
10066 : pszValue);
10067 : }
10068 : }
10069 :
10070 : // COMPRESS option.
10071 255 : pszValue = CSLFetchNameValue(papszCreationOptions, "COMPRESS");
10072 255 : if (pszValue != nullptr)
10073 : {
10074 3 : if (EQUAL(pszValue, "NONE"))
10075 : {
10076 1 : eCompress = NCDF_COMPRESS_NONE;
10077 : }
10078 2 : else if (EQUAL(pszValue, "DEFLATE"))
10079 : {
10080 2 : eCompress = NCDF_COMPRESS_DEFLATE;
10081 2 : if (!((eFormat == NCDF_FORMAT_NC4) ||
10082 2 : (eFormat == NCDF_FORMAT_NC4C)))
10083 : {
10084 1 : CPLError(CE_Warning, CPLE_IllegalArg,
10085 : "NOTICE: Format set to NC4C because compression is "
10086 : "set to DEFLATE.");
10087 1 : eFormat = NCDF_FORMAT_NC4C;
10088 : }
10089 : }
10090 : else
10091 : {
10092 0 : CPLError(CE_Failure, CPLE_NotSupported,
10093 : "COMPRESS=%s is not supported.", pszValue);
10094 : }
10095 : }
10096 :
10097 : // ZLEVEL option.
10098 255 : pszValue = CSLFetchNameValue(papszCreationOptions, "ZLEVEL");
10099 255 : if (pszValue != nullptr)
10100 : {
10101 1 : nZLevel = atoi(pszValue);
10102 1 : if (!(nZLevel >= 1 && nZLevel <= 9))
10103 : {
10104 0 : CPLError(CE_Warning, CPLE_IllegalArg,
10105 : "ZLEVEL=%s value not recognised, ignoring.", pszValue);
10106 0 : nZLevel = NCDF_DEFLATE_LEVEL;
10107 : }
10108 : }
10109 :
10110 : // CHUNKING option.
10111 255 : bChunking =
10112 255 : CPL_TO_BOOL(CSLFetchBoolean(papszCreationOptions, "CHUNKING", TRUE));
10113 :
10114 : // MULTIPLE_LAYERS option.
10115 : const char *pszMultipleLayerBehavior =
10116 255 : CSLFetchNameValueDef(papszCreationOptions, "MULTIPLE_LAYERS", "NO");
10117 510 : const char *pszGeometryEnc = CSLFetchNameValueDef(
10118 255 : papszCreationOptions, "GEOMETRY_ENCODING", "CF_1.8");
10119 255 : if (EQUAL(pszMultipleLayerBehavior, "NO") ||
10120 3 : EQUAL(pszGeometryEnc, "CF_1.8"))
10121 : {
10122 252 : eMultipleLayerBehavior = SINGLE_LAYER;
10123 : }
10124 3 : else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_FILES"))
10125 : {
10126 2 : eMultipleLayerBehavior = SEPARATE_FILES;
10127 : }
10128 1 : else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_GROUPS"))
10129 : {
10130 1 : if (eFormat == NCDF_FORMAT_NC4)
10131 : {
10132 1 : eMultipleLayerBehavior = SEPARATE_GROUPS;
10133 : }
10134 : else
10135 : {
10136 0 : CPLError(CE_Warning, CPLE_IllegalArg,
10137 : "MULTIPLE_LAYERS=%s is recognised only with FORMAT=NC4",
10138 : pszMultipleLayerBehavior);
10139 : }
10140 : }
10141 : else
10142 : {
10143 0 : CPLError(CE_Warning, CPLE_IllegalArg,
10144 : "MULTIPLE_LAYERS=%s not recognised", pszMultipleLayerBehavior);
10145 : }
10146 :
10147 : // Set nCreateMode based on eFormat.
10148 255 : switch (eFormat)
10149 : {
10150 : #ifdef NETCDF_HAS_NC2
10151 1 : case NCDF_FORMAT_NC2:
10152 1 : nCreateMode = NC_CLOBBER | NC_64BIT_OFFSET;
10153 1 : break;
10154 : #endif
10155 86 : case NCDF_FORMAT_NC4:
10156 86 : nCreateMode = NC_CLOBBER | NC_NETCDF4;
10157 86 : break;
10158 5 : case NCDF_FORMAT_NC4C:
10159 5 : nCreateMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
10160 5 : break;
10161 163 : case NCDF_FORMAT_NC:
10162 : default:
10163 163 : nCreateMode = NC_CLOBBER;
10164 163 : break;
10165 : }
10166 :
10167 255 : CPLDebug("GDAL_netCDF", "file options: format=%d compress=%d zlevel=%d",
10168 255 : eFormat, eCompress, nZLevel);
10169 255 : }
10170 :
10171 278 : int netCDFDataset::DefVarDeflate(int nVarId, bool bChunkingArg)
10172 : {
10173 278 : if (eCompress == NCDF_COMPRESS_DEFLATE)
10174 : {
10175 : // Must set chunk size to avoid huge performance hit (set
10176 : // bChunkingArg=TRUE)
10177 : // perhaps another solution it to change the chunk cache?
10178 : // http://www.unidata.ucar.edu/software/netcdf/docs/netcdf.html#Chunk-Cache
10179 : // TODO: make sure this is okay.
10180 2 : CPLDebug("GDAL_netCDF", "DefVarDeflate(%d, %d) nZlevel=%d", nVarId,
10181 : static_cast<int>(bChunkingArg), nZLevel);
10182 :
10183 2 : int status = nc_def_var_deflate(cdfid, nVarId, 1, 1, nZLevel);
10184 2 : NCDF_ERR(status);
10185 :
10186 2 : if (status == NC_NOERR && bChunkingArg && bChunking)
10187 : {
10188 : // set chunking to be 1 for all dims, except X dim
10189 : // size_t chunksize[] = { 1, (size_t)nRasterXSize };
10190 : size_t chunksize[MAX_NC_DIMS];
10191 : int nd;
10192 2 : nc_inq_varndims(cdfid, nVarId, &nd);
10193 2 : chunksize[0] = (size_t)1;
10194 2 : chunksize[1] = (size_t)1;
10195 2 : for (int i = 2; i < nd; i++)
10196 0 : chunksize[i] = (size_t)1;
10197 2 : chunksize[nd - 1] = (size_t)nRasterXSize;
10198 :
10199 : // Config options just for testing purposes
10200 : const char *pszBlockXSize =
10201 2 : CPLGetConfigOption("BLOCKXSIZE", nullptr);
10202 2 : if (pszBlockXSize)
10203 0 : chunksize[nd - 1] = (size_t)atoi(pszBlockXSize);
10204 :
10205 : const char *pszBlockYSize =
10206 2 : CPLGetConfigOption("BLOCKYSIZE", nullptr);
10207 2 : if (nd >= 2 && pszBlockYSize)
10208 0 : chunksize[nd - 2] = (size_t)atoi(pszBlockYSize);
10209 :
10210 2 : CPLDebug("GDAL_netCDF",
10211 : "DefVarDeflate() chunksize={%ld, %ld} chunkX=%ld nd=%d",
10212 2 : (long)chunksize[0], (long)chunksize[1],
10213 2 : (long)chunksize[nd - 1], nd);
10214 : #ifdef NCDF_DEBUG
10215 : for (int i = 0; i < nd; i++)
10216 : CPLDebug("GDAL_netCDF", "DefVarDeflate() chunk[%d]=%ld", i,
10217 : chunksize[i]);
10218 : #endif
10219 :
10220 2 : status = nc_def_var_chunking(cdfid, nVarId, NC_CHUNKED, chunksize);
10221 2 : NCDF_ERR(status);
10222 : }
10223 : else
10224 : {
10225 0 : CPLDebug("GDAL_netCDF", "chunksize not set");
10226 : }
10227 2 : return status;
10228 : }
10229 276 : return NC_NOERR;
10230 : }
10231 :
10232 : /************************************************************************/
10233 : /* NCDFUnloadDriver() */
10234 : /************************************************************************/
10235 :
10236 8 : static void NCDFUnloadDriver(CPL_UNUSED GDALDriver *poDriver)
10237 : {
10238 8 : if (hNCMutex != nullptr)
10239 4 : CPLDestroyMutex(hNCMutex);
10240 8 : hNCMutex = nullptr;
10241 8 : }
10242 :
10243 : /************************************************************************/
10244 : /* GDALRegister_netCDF() */
10245 : /************************************************************************/
10246 :
10247 : class GDALnetCDFDriver final : public GDALDriver
10248 : {
10249 : public:
10250 19 : GDALnetCDFDriver() = default;
10251 :
10252 : const char *GetMetadataItem(const char *pszName,
10253 : const char *pszDomain) override;
10254 :
10255 90 : char **GetMetadata(const char *pszDomain) override
10256 : {
10257 180 : std::lock_guard oLock(m_oMutex);
10258 90 : InitializeDCAPVirtualIO();
10259 180 : return GDALDriver::GetMetadata(pszDomain);
10260 : }
10261 :
10262 : private:
10263 : std::mutex m_oMutex{};
10264 : bool m_bInitialized = false;
10265 :
10266 103 : void InitializeDCAPVirtualIO()
10267 : {
10268 103 : if (!m_bInitialized)
10269 : {
10270 12 : m_bInitialized = true;
10271 :
10272 : #ifdef ENABLE_UFFD
10273 12 : if (CPLIsUserFaultMappingSupported())
10274 : {
10275 12 : SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
10276 : }
10277 : #endif
10278 : }
10279 103 : }
10280 : };
10281 :
10282 1391 : const char *GDALnetCDFDriver::GetMetadataItem(const char *pszName,
10283 : const char *pszDomain)
10284 : {
10285 2782 : std::lock_guard oLock(m_oMutex);
10286 1391 : if (EQUAL(pszName, GDAL_DCAP_VIRTUALIO))
10287 : {
10288 13 : InitializeDCAPVirtualIO();
10289 : }
10290 2782 : return GDALDriver::GetMetadataItem(pszName, pszDomain);
10291 : }
10292 :
10293 19 : void GDALRegister_netCDF()
10294 :
10295 : {
10296 19 : if (!GDAL_CHECK_VERSION("netCDF driver"))
10297 0 : return;
10298 :
10299 19 : if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
10300 0 : return;
10301 :
10302 19 : GDALDriver *poDriver = new GDALnetCDFDriver();
10303 19 : netCDFDriverSetCommonMetadata(poDriver);
10304 :
10305 19 : poDriver->SetMetadataItem("NETCDF_CONVENTIONS",
10306 19 : GDAL_DEFAULT_NCDF_CONVENTIONS);
10307 19 : poDriver->SetMetadataItem("NETCDF_VERSION", nc_inq_libvers());
10308 :
10309 : // Set pfns and register driver.
10310 19 : poDriver->pfnOpen = netCDFDataset::Open;
10311 19 : poDriver->pfnCreateCopy = netCDFDataset::CreateCopy;
10312 19 : poDriver->pfnCreate = netCDFDataset::Create;
10313 19 : poDriver->pfnCreateMultiDimensional = netCDFDataset::CreateMultiDimensional;
10314 19 : poDriver->pfnUnloadDriver = NCDFUnloadDriver;
10315 :
10316 19 : GetGDALDriverManager()->RegisterDriver(poDriver);
10317 : }
10318 :
10319 : /************************************************************************/
10320 : /* New functions */
10321 : /************************************************************************/
10322 :
10323 : /* Test for GDAL version string >= target */
10324 241 : static bool NCDFIsGDALVersionGTE(const char *pszVersion, int nTarget)
10325 : {
10326 :
10327 : // Valid strings are "GDAL 1.9dev, released 2011/01/18" and "GDAL 1.8.1 ".
10328 241 : if (pszVersion == nullptr || EQUAL(pszVersion, ""))
10329 0 : return false;
10330 241 : else if (!STARTS_WITH_CI(pszVersion, "GDAL "))
10331 0 : return false;
10332 : // 2.0dev of 2011/12/29 has been later renamed as 1.10dev.
10333 241 : else if (EQUAL("GDAL 2.0dev, released 2011/12/29", pszVersion))
10334 0 : return nTarget <= GDAL_COMPUTE_VERSION(1, 10, 0);
10335 241 : else if (STARTS_WITH_CI(pszVersion, "GDAL 1.9dev"))
10336 2 : return nTarget <= 1900;
10337 239 : else if (STARTS_WITH_CI(pszVersion, "GDAL 1.8dev"))
10338 0 : return nTarget <= 1800;
10339 :
10340 239 : char **papszTokens = CSLTokenizeString2(pszVersion + 5, ".", 0);
10341 :
10342 239 : int nVersions[] = {0, 0, 0, 0};
10343 956 : for (int iToken = 0; papszTokens && iToken < 4 && papszTokens[iToken];
10344 : iToken++)
10345 : {
10346 717 : nVersions[iToken] = atoi(papszTokens[iToken]);
10347 717 : if (nVersions[iToken] < 0)
10348 0 : nVersions[iToken] = 0;
10349 717 : else if (nVersions[iToken] > 99)
10350 0 : nVersions[iToken] = 99;
10351 : }
10352 :
10353 239 : int nVersion = 0;
10354 239 : if (nVersions[0] > 1 || nVersions[1] >= 10)
10355 239 : nVersion =
10356 239 : GDAL_COMPUTE_VERSION(nVersions[0], nVersions[1], nVersions[2]);
10357 : else
10358 0 : nVersion = nVersions[0] * 1000 + nVersions[1] * 100 +
10359 0 : nVersions[2] * 10 + nVersions[3];
10360 :
10361 239 : CSLDestroy(papszTokens);
10362 239 : return nTarget <= nVersion;
10363 : }
10364 :
10365 : // Add Conventions, GDAL version and history.
10366 167 : static void NCDFAddGDALHistory(int fpImage, const char *pszFilename,
10367 : bool bWriteGDALVersion, bool bWriteGDALHistory,
10368 : const char *pszOldHist,
10369 : const char *pszFunctionName,
10370 : const char *pszCFVersion)
10371 : {
10372 167 : if (pszCFVersion == nullptr)
10373 : {
10374 42 : pszCFVersion = GDAL_DEFAULT_NCDF_CONVENTIONS;
10375 : }
10376 167 : int status = nc_put_att_text(fpImage, NC_GLOBAL, "Conventions",
10377 : strlen(pszCFVersion), pszCFVersion);
10378 167 : NCDF_ERR(status);
10379 :
10380 167 : if (bWriteGDALVersion)
10381 : {
10382 165 : const char *pszNCDF_GDAL = GDALVersionInfo("--version");
10383 165 : status = nc_put_att_text(fpImage, NC_GLOBAL, "GDAL",
10384 : strlen(pszNCDF_GDAL), pszNCDF_GDAL);
10385 165 : NCDF_ERR(status);
10386 : }
10387 :
10388 167 : if (bWriteGDALHistory)
10389 : {
10390 : // Add history.
10391 330 : CPLString osTmp;
10392 : #ifdef GDAL_SET_CMD_LINE_DEFINED_TMP
10393 : if (!EQUAL(GDALGetCmdLine(), ""))
10394 : osTmp = GDALGetCmdLine();
10395 : else
10396 : osTmp =
10397 : CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
10398 : #else
10399 165 : osTmp = CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
10400 : #endif
10401 :
10402 165 : NCDFAddHistory(fpImage, osTmp.c_str(), pszOldHist);
10403 : }
10404 2 : else if (pszOldHist != nullptr)
10405 : {
10406 0 : status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
10407 : strlen(pszOldHist), pszOldHist);
10408 0 : NCDF_ERR(status);
10409 : }
10410 167 : }
10411 :
10412 : // Code taken from cdo and libcdi, used for writing the history attribute.
10413 :
10414 : // void cdoDefHistory(int fileID, char *histstring)
10415 165 : static void NCDFAddHistory(int fpImage, const char *pszAddHist,
10416 : const char *pszOldHist)
10417 : {
10418 : // Check pszOldHist - as if there was no previous history, it will be
10419 : // a null pointer - if so set as empty.
10420 165 : if (nullptr == pszOldHist)
10421 : {
10422 53 : pszOldHist = "";
10423 : }
10424 :
10425 : char strtime[32];
10426 165 : strtime[0] = '\0';
10427 :
10428 165 : time_t tp = time(nullptr);
10429 165 : if (tp != -1)
10430 : {
10431 : struct tm ltime;
10432 165 : VSILocalTime(&tp, <ime);
10433 165 : (void)strftime(strtime, sizeof(strtime),
10434 : "%a %b %d %H:%M:%S %Y: ", <ime);
10435 : }
10436 :
10437 : // status = nc_get_att_text(fpImage, NC_GLOBAL,
10438 : // "history", pszOldHist);
10439 : // printf("status: %d pszOldHist: [%s]\n",status,pszOldHist);
10440 :
10441 165 : size_t nNewHistSize =
10442 165 : strlen(pszOldHist) + strlen(strtime) + strlen(pszAddHist) + 1 + 1;
10443 : char *pszNewHist =
10444 165 : static_cast<char *>(CPLMalloc(nNewHistSize * sizeof(char)));
10445 :
10446 165 : strcpy(pszNewHist, strtime);
10447 165 : strcat(pszNewHist, pszAddHist);
10448 :
10449 : // int disableHistory = FALSE;
10450 : // if( !disableHistory )
10451 : {
10452 165 : if (!EQUAL(pszOldHist, ""))
10453 3 : strcat(pszNewHist, "\n");
10454 165 : strcat(pszNewHist, pszOldHist);
10455 : }
10456 :
10457 165 : const int status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
10458 : strlen(pszNewHist), pszNewHist);
10459 165 : NCDF_ERR(status);
10460 :
10461 165 : CPLFree(pszNewHist);
10462 165 : }
10463 :
10464 6157 : static CPLErr NCDFSafeStrcat(char **ppszDest, const char *pszSrc,
10465 : size_t *nDestSize)
10466 : {
10467 : /* Reallocate the data string until the content fits */
10468 6157 : while (*nDestSize < (strlen(*ppszDest) + strlen(pszSrc) + 1))
10469 : {
10470 408 : (*nDestSize) *= 2;
10471 408 : *ppszDest = static_cast<char *>(
10472 408 : CPLRealloc(reinterpret_cast<void *>(*ppszDest), *nDestSize));
10473 : #ifdef NCDF_DEBUG
10474 : CPLDebug("GDAL_netCDF", "NCDFSafeStrcat() resized str from %ld to %ld",
10475 : (*nDestSize) / 2, *nDestSize);
10476 : #endif
10477 : }
10478 5749 : strcat(*ppszDest, pszSrc);
10479 :
10480 5749 : return CE_None;
10481 : }
10482 :
10483 : /* helper function for NCDFGetAttr() */
10484 : /* if pdfValue != nullptr, sets *pdfValue to first value returned */
10485 : /* if ppszValue != nullptr, sets *ppszValue with all attribute values */
10486 : /* *ppszValue is the responsibility of the caller and must be freed */
10487 63547 : static CPLErr NCDFGetAttr1(int nCdfId, int nVarId, const char *pszAttrName,
10488 : double *pdfValue, char **ppszValue)
10489 : {
10490 63547 : nc_type nAttrType = NC_NAT;
10491 63547 : size_t nAttrLen = 0;
10492 :
10493 63547 : if (ppszValue)
10494 62392 : *ppszValue = nullptr;
10495 :
10496 63547 : int status = nc_inq_att(nCdfId, nVarId, pszAttrName, &nAttrType, &nAttrLen);
10497 63547 : if (status != NC_NOERR)
10498 34147 : return CE_Failure;
10499 :
10500 : #ifdef NCDF_DEBUG
10501 : CPLDebug("GDAL_netCDF", "NCDFGetAttr1(%s) len=%ld type=%d", pszAttrName,
10502 : nAttrLen, nAttrType);
10503 : #endif
10504 29400 : if (nAttrLen == 0 && nAttrType != NC_CHAR)
10505 1 : return CE_Failure;
10506 :
10507 : /* Allocate guaranteed minimum size (use 10 or 20 if not a string) */
10508 29399 : size_t nAttrValueSize = nAttrLen + 1;
10509 29399 : if (nAttrType != NC_CHAR && nAttrValueSize < 10)
10510 3219 : nAttrValueSize = 10;
10511 29399 : if (nAttrType == NC_DOUBLE && nAttrValueSize < 20)
10512 1570 : nAttrValueSize = 20;
10513 29399 : if (nAttrType == NC_INT64 && nAttrValueSize < 20)
10514 22 : nAttrValueSize = 22;
10515 : char *pszAttrValue =
10516 29399 : static_cast<char *>(CPLCalloc(nAttrValueSize, sizeof(char)));
10517 29399 : *pszAttrValue = '\0';
10518 :
10519 29399 : if (nAttrLen > 1 && nAttrType != NC_CHAR)
10520 597 : NCDFSafeStrcat(&pszAttrValue, "{", &nAttrValueSize);
10521 :
10522 29399 : double dfValue = 0.0;
10523 29399 : size_t m = 0;
10524 : char szTemp[256];
10525 29399 : bool bSetDoubleFromStr = false;
10526 :
10527 29399 : switch (nAttrType)
10528 : {
10529 26178 : case NC_CHAR:
10530 26178 : CPL_IGNORE_RET_VAL(
10531 26178 : nc_get_att_text(nCdfId, nVarId, pszAttrName, pszAttrValue));
10532 26178 : pszAttrValue[nAttrLen] = '\0';
10533 26178 : bSetDoubleFromStr = true;
10534 26178 : dfValue = 0.0;
10535 26178 : break;
10536 94 : case NC_BYTE:
10537 : {
10538 : signed char *pscTemp = static_cast<signed char *>(
10539 94 : CPLCalloc(nAttrLen, sizeof(signed char)));
10540 94 : nc_get_att_schar(nCdfId, nVarId, pszAttrName, pscTemp);
10541 94 : dfValue = static_cast<double>(pscTemp[0]);
10542 94 : if (nAttrLen > 1)
10543 : {
10544 24 : for (m = 0; m < nAttrLen - 1; m++)
10545 : {
10546 13 : snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
10547 13 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10548 : }
10549 : }
10550 94 : snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
10551 94 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10552 94 : CPLFree(pscTemp);
10553 94 : break;
10554 : }
10555 484 : case NC_SHORT:
10556 : {
10557 : short *psTemp =
10558 484 : static_cast<short *>(CPLCalloc(nAttrLen, sizeof(short)));
10559 484 : nc_get_att_short(nCdfId, nVarId, pszAttrName, psTemp);
10560 484 : dfValue = static_cast<double>(psTemp[0]);
10561 484 : if (nAttrLen > 1)
10562 : {
10563 762 : for (m = 0; m < nAttrLen - 1; m++)
10564 : {
10565 381 : snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
10566 381 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10567 : }
10568 : }
10569 484 : snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
10570 484 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10571 484 : CPLFree(psTemp);
10572 484 : break;
10573 : }
10574 528 : case NC_INT:
10575 : {
10576 528 : int *pnTemp = static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
10577 528 : nc_get_att_int(nCdfId, nVarId, pszAttrName, pnTemp);
10578 528 : dfValue = static_cast<double>(pnTemp[0]);
10579 528 : if (nAttrLen > 1)
10580 : {
10581 218 : for (m = 0; m < nAttrLen - 1; m++)
10582 : {
10583 139 : snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
10584 139 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10585 : }
10586 : }
10587 528 : snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
10588 528 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10589 528 : CPLFree(pnTemp);
10590 528 : break;
10591 : }
10592 393 : case NC_FLOAT:
10593 : {
10594 : float *pfTemp =
10595 393 : static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
10596 393 : nc_get_att_float(nCdfId, nVarId, pszAttrName, pfTemp);
10597 393 : dfValue = static_cast<double>(pfTemp[0]);
10598 393 : if (nAttrLen > 1)
10599 : {
10600 56 : for (m = 0; m < nAttrLen - 1; m++)
10601 : {
10602 28 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
10603 28 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10604 : }
10605 : }
10606 393 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
10607 393 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10608 393 : CPLFree(pfTemp);
10609 393 : break;
10610 : }
10611 1570 : case NC_DOUBLE:
10612 : {
10613 : double *pdfTemp =
10614 1570 : static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
10615 1570 : nc_get_att_double(nCdfId, nVarId, pszAttrName, pdfTemp);
10616 1570 : dfValue = pdfTemp[0];
10617 1570 : if (nAttrLen > 1)
10618 : {
10619 166 : for (m = 0; m < nAttrLen - 1; m++)
10620 : {
10621 90 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
10622 90 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10623 : }
10624 : }
10625 1570 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
10626 1570 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10627 1570 : CPLFree(pdfTemp);
10628 1570 : break;
10629 : }
10630 10 : case NC_STRING:
10631 : {
10632 : char **ppszTemp =
10633 10 : static_cast<char **>(CPLCalloc(nAttrLen, sizeof(char *)));
10634 10 : nc_get_att_string(nCdfId, nVarId, pszAttrName, ppszTemp);
10635 10 : bSetDoubleFromStr = true;
10636 10 : dfValue = 0.0;
10637 10 : if (nAttrLen > 1)
10638 : {
10639 19 : for (m = 0; m < nAttrLen - 1; m++)
10640 : {
10641 12 : NCDFSafeStrcat(&pszAttrValue,
10642 12 : ppszTemp[m] ? ppszTemp[m] : "{NULL}",
10643 : &nAttrValueSize);
10644 12 : NCDFSafeStrcat(&pszAttrValue, ",", &nAttrValueSize);
10645 : }
10646 : }
10647 10 : NCDFSafeStrcat(&pszAttrValue, ppszTemp[m] ? ppszTemp[m] : "{NULL}",
10648 : &nAttrValueSize);
10649 10 : nc_free_string(nAttrLen, ppszTemp);
10650 10 : CPLFree(ppszTemp);
10651 10 : break;
10652 : }
10653 28 : case NC_UBYTE:
10654 : {
10655 : unsigned char *pucTemp = static_cast<unsigned char *>(
10656 28 : CPLCalloc(nAttrLen, sizeof(unsigned char)));
10657 28 : nc_get_att_uchar(nCdfId, nVarId, pszAttrName, pucTemp);
10658 28 : dfValue = static_cast<double>(pucTemp[0]);
10659 28 : if (nAttrLen > 1)
10660 : {
10661 0 : for (m = 0; m < nAttrLen - 1; m++)
10662 : {
10663 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
10664 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10665 : }
10666 : }
10667 28 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
10668 28 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10669 28 : CPLFree(pucTemp);
10670 28 : break;
10671 : }
10672 26 : case NC_USHORT:
10673 : {
10674 : unsigned short *pusTemp;
10675 : pusTemp = static_cast<unsigned short *>(
10676 26 : CPLCalloc(nAttrLen, sizeof(unsigned short)));
10677 26 : nc_get_att_ushort(nCdfId, nVarId, pszAttrName, pusTemp);
10678 26 : dfValue = static_cast<double>(pusTemp[0]);
10679 26 : if (nAttrLen > 1)
10680 : {
10681 10 : for (m = 0; m < nAttrLen - 1; m++)
10682 : {
10683 5 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
10684 5 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10685 : }
10686 : }
10687 26 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
10688 26 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10689 26 : CPLFree(pusTemp);
10690 26 : break;
10691 : }
10692 18 : case NC_UINT:
10693 : {
10694 : unsigned int *punTemp =
10695 18 : static_cast<unsigned int *>(CPLCalloc(nAttrLen, sizeof(int)));
10696 18 : nc_get_att_uint(nCdfId, nVarId, pszAttrName, punTemp);
10697 18 : dfValue = static_cast<double>(punTemp[0]);
10698 18 : if (nAttrLen > 1)
10699 : {
10700 0 : for (m = 0; m < nAttrLen - 1; m++)
10701 : {
10702 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
10703 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10704 : }
10705 : }
10706 18 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
10707 18 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10708 18 : CPLFree(punTemp);
10709 18 : break;
10710 : }
10711 22 : case NC_INT64:
10712 : {
10713 : GIntBig *panTemp =
10714 22 : static_cast<GIntBig *>(CPLCalloc(nAttrLen, sizeof(GIntBig)));
10715 22 : nc_get_att_longlong(nCdfId, nVarId, pszAttrName, panTemp);
10716 22 : dfValue = static_cast<double>(panTemp[0]);
10717 22 : if (nAttrLen > 1)
10718 : {
10719 0 : for (m = 0; m < nAttrLen - 1; m++)
10720 : {
10721 0 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",",
10722 0 : panTemp[m]);
10723 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10724 : }
10725 : }
10726 22 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, panTemp[m]);
10727 22 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10728 22 : CPLFree(panTemp);
10729 22 : break;
10730 : }
10731 22 : case NC_UINT64:
10732 : {
10733 : GUIntBig *panTemp =
10734 22 : static_cast<GUIntBig *>(CPLCalloc(nAttrLen, sizeof(GUIntBig)));
10735 22 : nc_get_att_ulonglong(nCdfId, nVarId, pszAttrName, panTemp);
10736 22 : dfValue = static_cast<double>(panTemp[0]);
10737 22 : if (nAttrLen > 1)
10738 : {
10739 0 : for (m = 0; m < nAttrLen - 1; m++)
10740 : {
10741 0 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",",
10742 0 : panTemp[m]);
10743 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10744 : }
10745 : }
10746 22 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, panTemp[m]);
10747 22 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10748 22 : CPLFree(panTemp);
10749 22 : break;
10750 : }
10751 26 : default:
10752 26 : CPLDebug("GDAL_netCDF",
10753 : "NCDFGetAttr unsupported type %d for attribute %s",
10754 : nAttrType, pszAttrName);
10755 26 : break;
10756 : }
10757 :
10758 29399 : if (nAttrLen > 1 && nAttrType != NC_CHAR)
10759 597 : NCDFSafeStrcat(&pszAttrValue, "}", &nAttrValueSize);
10760 :
10761 29399 : if (bSetDoubleFromStr)
10762 : {
10763 26188 : if (CPLGetValueType(pszAttrValue) == CPL_VALUE_STRING)
10764 : {
10765 26006 : if (ppszValue == nullptr && pdfValue != nullptr)
10766 : {
10767 1 : CPLFree(pszAttrValue);
10768 1 : return CE_Failure;
10769 : }
10770 : }
10771 26187 : dfValue = CPLAtof(pszAttrValue);
10772 : }
10773 :
10774 : /* set return values */
10775 29398 : if (ppszValue)
10776 29086 : *ppszValue = pszAttrValue;
10777 : else
10778 312 : CPLFree(pszAttrValue);
10779 :
10780 29398 : if (pdfValue)
10781 312 : *pdfValue = dfValue;
10782 :
10783 29398 : return CE_None;
10784 : }
10785 :
10786 : /* sets pdfValue to first value found */
10787 1155 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
10788 : double *pdfValue)
10789 : {
10790 1155 : return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, pdfValue, nullptr);
10791 : }
10792 :
10793 : /* pszValue is the responsibility of the caller and must be freed */
10794 62392 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
10795 : char **pszValue)
10796 : {
10797 62392 : return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, nullptr, pszValue);
10798 : }
10799 :
10800 : /* By default write NC_CHAR, but detect for int/float/double and */
10801 : /* NC4 string arrays */
10802 106 : static CPLErr NCDFPutAttr(int nCdfId, int nVarId, const char *pszAttrName,
10803 : const char *pszValue)
10804 : {
10805 106 : int status = 0;
10806 106 : char *pszTemp = nullptr;
10807 :
10808 : /* get the attribute values as tokens */
10809 106 : char **papszValues = NCDFTokenizeArray(pszValue);
10810 106 : if (papszValues == nullptr)
10811 0 : return CE_Failure;
10812 :
10813 106 : size_t nAttrLen = CSLCount(papszValues);
10814 :
10815 : /* first detect type */
10816 106 : nc_type nAttrType = NC_CHAR;
10817 106 : nc_type nTmpAttrType = NC_CHAR;
10818 225 : for (size_t i = 0; i < nAttrLen; i++)
10819 : {
10820 119 : nTmpAttrType = NC_CHAR;
10821 119 : bool bFoundType = false;
10822 119 : errno = 0;
10823 119 : int nValue = static_cast<int>(strtol(papszValues[i], &pszTemp, 10));
10824 : /* test for int */
10825 : /* TODO test for Byte and short - can this be done safely? */
10826 119 : if (errno == 0 && papszValues[i] != pszTemp && *pszTemp == 0)
10827 : {
10828 : char szTemp[256];
10829 19 : CPLsnprintf(szTemp, sizeof(szTemp), "%d", nValue);
10830 19 : if (EQUAL(szTemp, papszValues[i]))
10831 : {
10832 19 : bFoundType = true;
10833 19 : nTmpAttrType = NC_INT;
10834 : }
10835 : else
10836 : {
10837 : unsigned int unValue = static_cast<unsigned int>(
10838 0 : strtoul(papszValues[i], &pszTemp, 10));
10839 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", unValue);
10840 0 : if (EQUAL(szTemp, papszValues[i]))
10841 : {
10842 0 : bFoundType = true;
10843 0 : nTmpAttrType = NC_UINT;
10844 : }
10845 : }
10846 : }
10847 119 : if (!bFoundType)
10848 : {
10849 : /* test for double */
10850 100 : errno = 0;
10851 100 : double dfValue = CPLStrtod(papszValues[i], &pszTemp);
10852 100 : if ((errno == 0) && (papszValues[i] != pszTemp) && (*pszTemp == 0))
10853 : {
10854 : // Test for float instead of double.
10855 : // strtof() is C89, which is not available in MSVC.
10856 : // See if we loose precision if we cast to float and write to
10857 : // char*.
10858 14 : float fValue = float(dfValue);
10859 : char szTemp[256];
10860 14 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", fValue);
10861 14 : if (EQUAL(szTemp, papszValues[i]))
10862 8 : nTmpAttrType = NC_FLOAT;
10863 : else
10864 6 : nTmpAttrType = NC_DOUBLE;
10865 : }
10866 : }
10867 119 : if ((nTmpAttrType <= NC_DOUBLE && nAttrType <= NC_DOUBLE &&
10868 99 : nTmpAttrType > nAttrType) ||
10869 99 : (nTmpAttrType == NC_UINT && nAttrType < NC_FLOAT) ||
10870 5 : (nTmpAttrType >= NC_FLOAT && nAttrType == NC_UINT))
10871 20 : nAttrType = nTmpAttrType;
10872 : }
10873 :
10874 : #ifdef DEBUG
10875 106 : if (EQUAL(pszAttrName, "DEBUG_EMPTY_DOUBLE_ATTR"))
10876 : {
10877 0 : nAttrType = NC_DOUBLE;
10878 0 : nAttrLen = 0;
10879 : }
10880 : #endif
10881 :
10882 : /* now write the data */
10883 106 : if (nAttrType == NC_CHAR)
10884 : {
10885 86 : int nTmpFormat = 0;
10886 86 : if (nAttrLen > 1)
10887 : {
10888 0 : status = nc_inq_format(nCdfId, &nTmpFormat);
10889 0 : NCDF_ERR(status);
10890 : }
10891 86 : if (nAttrLen > 1 && nTmpFormat == NCDF_FORMAT_NC4)
10892 0 : status = nc_put_att_string(nCdfId, nVarId, pszAttrName, nAttrLen,
10893 : const_cast<const char **>(papszValues));
10894 : else
10895 86 : status = nc_put_att_text(nCdfId, nVarId, pszAttrName,
10896 : strlen(pszValue), pszValue);
10897 86 : NCDF_ERR(status);
10898 : }
10899 : else
10900 : {
10901 20 : switch (nAttrType)
10902 : {
10903 11 : case NC_INT:
10904 : {
10905 : int *pnTemp =
10906 11 : static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
10907 30 : for (size_t i = 0; i < nAttrLen; i++)
10908 : {
10909 19 : pnTemp[i] =
10910 19 : static_cast<int>(strtol(papszValues[i], &pszTemp, 10));
10911 : }
10912 11 : status = nc_put_att_int(nCdfId, nVarId, pszAttrName, NC_INT,
10913 : nAttrLen, pnTemp);
10914 11 : NCDF_ERR(status);
10915 11 : CPLFree(pnTemp);
10916 11 : break;
10917 : }
10918 0 : case NC_UINT:
10919 : {
10920 : unsigned int *punTemp = static_cast<unsigned int *>(
10921 0 : CPLCalloc(nAttrLen, sizeof(unsigned int)));
10922 0 : for (size_t i = 0; i < nAttrLen; i++)
10923 : {
10924 0 : punTemp[i] = static_cast<unsigned int>(
10925 0 : strtol(papszValues[i], &pszTemp, 10));
10926 : }
10927 0 : status = nc_put_att_uint(nCdfId, nVarId, pszAttrName, NC_UINT,
10928 : nAttrLen, punTemp);
10929 0 : NCDF_ERR(status);
10930 0 : CPLFree(punTemp);
10931 0 : break;
10932 : }
10933 6 : case NC_FLOAT:
10934 : {
10935 : float *pfTemp =
10936 6 : static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
10937 14 : for (size_t i = 0; i < nAttrLen; i++)
10938 : {
10939 8 : pfTemp[i] =
10940 8 : static_cast<float>(CPLStrtod(papszValues[i], &pszTemp));
10941 : }
10942 6 : status = nc_put_att_float(nCdfId, nVarId, pszAttrName, NC_FLOAT,
10943 : nAttrLen, pfTemp);
10944 6 : NCDF_ERR(status);
10945 6 : CPLFree(pfTemp);
10946 6 : break;
10947 : }
10948 3 : case NC_DOUBLE:
10949 : {
10950 : double *pdfTemp =
10951 3 : static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
10952 9 : for (size_t i = 0; i < nAttrLen; i++)
10953 : {
10954 6 : pdfTemp[i] = CPLStrtod(papszValues[i], &pszTemp);
10955 : }
10956 3 : status = nc_put_att_double(nCdfId, nVarId, pszAttrName,
10957 : NC_DOUBLE, nAttrLen, pdfTemp);
10958 3 : NCDF_ERR(status);
10959 3 : CPLFree(pdfTemp);
10960 3 : break;
10961 : }
10962 0 : default:
10963 0 : if (papszValues)
10964 0 : CSLDestroy(papszValues);
10965 0 : return CE_Failure;
10966 : break;
10967 : }
10968 : }
10969 :
10970 106 : if (papszValues)
10971 106 : CSLDestroy(papszValues);
10972 :
10973 106 : return CE_None;
10974 : }
10975 :
10976 78 : static CPLErr NCDFGet1DVar(int nCdfId, int nVarId, char **pszValue)
10977 : {
10978 : /* get var information */
10979 78 : int nVarDimId = -1;
10980 78 : int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
10981 78 : if (status != NC_NOERR || nVarDimId != 1)
10982 0 : return CE_Failure;
10983 :
10984 78 : status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
10985 78 : if (status != NC_NOERR)
10986 0 : return CE_Failure;
10987 :
10988 78 : nc_type nVarType = NC_NAT;
10989 78 : status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
10990 78 : if (status != NC_NOERR)
10991 0 : return CE_Failure;
10992 :
10993 78 : size_t nVarLen = 0;
10994 78 : status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
10995 78 : if (status != NC_NOERR)
10996 0 : return CE_Failure;
10997 :
10998 78 : size_t start[1] = {0};
10999 78 : size_t count[1] = {nVarLen};
11000 :
11001 : /* Allocate guaranteed minimum size */
11002 78 : size_t nVarValueSize = NCDF_MAX_STR_LEN;
11003 : char *pszVarValue =
11004 78 : static_cast<char *>(CPLCalloc(nVarValueSize, sizeof(char)));
11005 78 : *pszVarValue = '\0';
11006 :
11007 78 : if (nVarLen == 0)
11008 : {
11009 : /* set return values */
11010 1 : *pszValue = pszVarValue;
11011 :
11012 1 : return CE_None;
11013 : }
11014 :
11015 77 : if (nVarLen > 1 && nVarType != NC_CHAR)
11016 42 : NCDFSafeStrcat(&pszVarValue, "{", &nVarValueSize);
11017 :
11018 77 : switch (nVarType)
11019 : {
11020 0 : case NC_CHAR:
11021 0 : nc_get_vara_text(nCdfId, nVarId, start, count, pszVarValue);
11022 0 : pszVarValue[nVarLen] = '\0';
11023 0 : break;
11024 0 : case NC_BYTE:
11025 : {
11026 : signed char *pscTemp = static_cast<signed char *>(
11027 0 : CPLCalloc(nVarLen, sizeof(signed char)));
11028 0 : nc_get_vara_schar(nCdfId, nVarId, start, count, pscTemp);
11029 : char szTemp[256];
11030 0 : size_t m = 0;
11031 0 : for (; m < nVarLen - 1; m++)
11032 : {
11033 0 : snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
11034 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11035 : }
11036 0 : snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
11037 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11038 0 : CPLFree(pscTemp);
11039 0 : break;
11040 : }
11041 0 : case NC_SHORT:
11042 : {
11043 : short *psTemp =
11044 0 : static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
11045 0 : nc_get_vara_short(nCdfId, nVarId, start, count, psTemp);
11046 : char szTemp[256];
11047 0 : size_t m = 0;
11048 0 : for (; m < nVarLen - 1; m++)
11049 : {
11050 0 : snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
11051 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11052 : }
11053 0 : snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
11054 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11055 0 : CPLFree(psTemp);
11056 0 : break;
11057 : }
11058 21 : case NC_INT:
11059 : {
11060 21 : int *pnTemp = static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
11061 21 : nc_get_vara_int(nCdfId, nVarId, start, count, pnTemp);
11062 : char szTemp[256];
11063 21 : size_t m = 0;
11064 44 : for (; m < nVarLen - 1; m++)
11065 : {
11066 23 : snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
11067 23 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11068 : }
11069 21 : snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
11070 21 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11071 21 : CPLFree(pnTemp);
11072 21 : break;
11073 : }
11074 8 : case NC_FLOAT:
11075 : {
11076 : float *pfTemp =
11077 8 : static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
11078 8 : nc_get_vara_float(nCdfId, nVarId, start, count, pfTemp);
11079 : char szTemp[256];
11080 8 : size_t m = 0;
11081 325 : for (; m < nVarLen - 1; m++)
11082 : {
11083 317 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
11084 317 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11085 : }
11086 8 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
11087 8 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11088 8 : CPLFree(pfTemp);
11089 8 : break;
11090 : }
11091 47 : case NC_DOUBLE:
11092 : {
11093 : double *pdfTemp =
11094 47 : static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
11095 47 : nc_get_vara_double(nCdfId, nVarId, start, count, pdfTemp);
11096 : char szTemp[256];
11097 47 : size_t m = 0;
11098 225 : for (; m < nVarLen - 1; m++)
11099 : {
11100 178 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
11101 178 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11102 : }
11103 47 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
11104 47 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11105 47 : CPLFree(pdfTemp);
11106 47 : break;
11107 : }
11108 0 : case NC_STRING:
11109 : {
11110 : char **ppszTemp =
11111 0 : static_cast<char **>(CPLCalloc(nVarLen, sizeof(char *)));
11112 0 : nc_get_vara_string(nCdfId, nVarId, start, count, ppszTemp);
11113 0 : size_t m = 0;
11114 0 : for (; m < nVarLen - 1; m++)
11115 : {
11116 0 : NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
11117 0 : NCDFSafeStrcat(&pszVarValue, ",", &nVarValueSize);
11118 : }
11119 0 : NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
11120 0 : nc_free_string(nVarLen, ppszTemp);
11121 0 : CPLFree(ppszTemp);
11122 0 : break;
11123 : }
11124 0 : case NC_UBYTE:
11125 : {
11126 : unsigned char *pucTemp;
11127 : pucTemp = static_cast<unsigned char *>(
11128 0 : CPLCalloc(nVarLen, sizeof(unsigned char)));
11129 0 : nc_get_vara_uchar(nCdfId, nVarId, start, count, pucTemp);
11130 : char szTemp[256];
11131 0 : size_t m = 0;
11132 0 : for (; m < nVarLen - 1; m++)
11133 : {
11134 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
11135 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11136 : }
11137 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
11138 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11139 0 : CPLFree(pucTemp);
11140 0 : break;
11141 : }
11142 0 : case NC_USHORT:
11143 : {
11144 : unsigned short *pusTemp;
11145 : pusTemp = static_cast<unsigned short *>(
11146 0 : CPLCalloc(nVarLen, sizeof(unsigned short)));
11147 0 : nc_get_vara_ushort(nCdfId, nVarId, start, count, pusTemp);
11148 : char szTemp[256];
11149 0 : size_t m = 0;
11150 0 : for (; m < nVarLen - 1; m++)
11151 : {
11152 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
11153 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11154 : }
11155 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
11156 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11157 0 : CPLFree(pusTemp);
11158 0 : break;
11159 : }
11160 0 : case NC_UINT:
11161 : {
11162 : unsigned int *punTemp;
11163 : punTemp = static_cast<unsigned int *>(
11164 0 : CPLCalloc(nVarLen, sizeof(unsigned int)));
11165 0 : nc_get_vara_uint(nCdfId, nVarId, start, count, punTemp);
11166 : char szTemp[256];
11167 0 : size_t m = 0;
11168 0 : for (; m < nVarLen - 1; m++)
11169 : {
11170 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
11171 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11172 : }
11173 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
11174 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11175 0 : CPLFree(punTemp);
11176 0 : break;
11177 : }
11178 1 : case NC_INT64:
11179 : {
11180 : long long *pnTemp =
11181 1 : static_cast<long long *>(CPLCalloc(nVarLen, sizeof(long long)));
11182 1 : nc_get_vara_longlong(nCdfId, nVarId, start, count, pnTemp);
11183 : char szTemp[256];
11184 1 : size_t m = 0;
11185 2 : for (; m < nVarLen - 1; m++)
11186 : {
11187 1 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",", pnTemp[m]);
11188 1 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11189 : }
11190 1 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, pnTemp[m]);
11191 1 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11192 1 : CPLFree(pnTemp);
11193 1 : break;
11194 : }
11195 0 : case NC_UINT64:
11196 : {
11197 : unsigned long long *pnTemp = static_cast<unsigned long long *>(
11198 0 : CPLCalloc(nVarLen, sizeof(unsigned long long)));
11199 0 : nc_get_vara_ulonglong(nCdfId, nVarId, start, count, pnTemp);
11200 : char szTemp[256];
11201 0 : size_t m = 0;
11202 0 : for (; m < nVarLen - 1; m++)
11203 : {
11204 0 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",", pnTemp[m]);
11205 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11206 : }
11207 0 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, pnTemp[m]);
11208 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11209 0 : CPLFree(pnTemp);
11210 0 : break;
11211 : }
11212 0 : default:
11213 0 : CPLDebug("GDAL_netCDF", "NCDFGetVar1D unsupported type %d",
11214 : nVarType);
11215 0 : CPLFree(pszVarValue);
11216 0 : pszVarValue = nullptr;
11217 0 : break;
11218 : }
11219 :
11220 77 : if (pszVarValue != nullptr && nVarLen > 1 && nVarType != NC_CHAR)
11221 42 : NCDFSafeStrcat(&pszVarValue, "}", &nVarValueSize);
11222 :
11223 : /* set return values */
11224 77 : *pszValue = pszVarValue;
11225 :
11226 77 : return CE_None;
11227 : }
11228 :
11229 8 : static CPLErr NCDFPut1DVar(int nCdfId, int nVarId, const char *pszValue)
11230 : {
11231 8 : if (EQUAL(pszValue, ""))
11232 0 : return CE_Failure;
11233 :
11234 : /* get var information */
11235 8 : int nVarDimId = -1;
11236 8 : int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
11237 8 : if (status != NC_NOERR || nVarDimId != 1)
11238 0 : return CE_Failure;
11239 :
11240 8 : status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
11241 8 : if (status != NC_NOERR)
11242 0 : return CE_Failure;
11243 :
11244 8 : nc_type nVarType = NC_CHAR;
11245 8 : status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
11246 8 : if (status != NC_NOERR)
11247 0 : return CE_Failure;
11248 :
11249 8 : size_t nVarLen = 0;
11250 8 : status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
11251 8 : if (status != NC_NOERR)
11252 0 : return CE_Failure;
11253 :
11254 8 : size_t start[1] = {0};
11255 8 : size_t count[1] = {nVarLen};
11256 :
11257 : /* get the values as tokens */
11258 8 : char **papszValues = NCDFTokenizeArray(pszValue);
11259 8 : if (papszValues == nullptr)
11260 0 : return CE_Failure;
11261 :
11262 8 : nVarLen = CSLCount(papszValues);
11263 :
11264 : /* now write the data */
11265 8 : if (nVarType == NC_CHAR)
11266 : {
11267 0 : status = nc_put_vara_text(nCdfId, nVarId, start, count, pszValue);
11268 0 : NCDF_ERR(status);
11269 : }
11270 : else
11271 : {
11272 8 : switch (nVarType)
11273 : {
11274 0 : case NC_BYTE:
11275 : {
11276 : signed char *pscTemp = static_cast<signed char *>(
11277 0 : CPLCalloc(nVarLen, sizeof(signed char)));
11278 0 : for (size_t i = 0; i < nVarLen; i++)
11279 : {
11280 0 : char *pszTemp = nullptr;
11281 0 : pscTemp[i] = static_cast<signed char>(
11282 0 : strtol(papszValues[i], &pszTemp, 10));
11283 : }
11284 : status =
11285 0 : nc_put_vara_schar(nCdfId, nVarId, start, count, pscTemp);
11286 0 : NCDF_ERR(status);
11287 0 : CPLFree(pscTemp);
11288 0 : break;
11289 : }
11290 0 : case NC_SHORT:
11291 : {
11292 : short *psTemp =
11293 0 : static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
11294 0 : for (size_t i = 0; i < nVarLen; i++)
11295 : {
11296 0 : char *pszTemp = nullptr;
11297 0 : psTemp[i] = static_cast<short>(
11298 0 : strtol(papszValues[i], &pszTemp, 10));
11299 : }
11300 : status =
11301 0 : nc_put_vara_short(nCdfId, nVarId, start, count, psTemp);
11302 0 : NCDF_ERR(status);
11303 0 : CPLFree(psTemp);
11304 0 : break;
11305 : }
11306 3 : case NC_INT:
11307 : {
11308 : int *pnTemp =
11309 3 : static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
11310 11 : for (size_t i = 0; i < nVarLen; i++)
11311 : {
11312 8 : char *pszTemp = nullptr;
11313 8 : pnTemp[i] =
11314 8 : static_cast<int>(strtol(papszValues[i], &pszTemp, 10));
11315 : }
11316 3 : status = nc_put_vara_int(nCdfId, nVarId, start, count, pnTemp);
11317 3 : NCDF_ERR(status);
11318 3 : CPLFree(pnTemp);
11319 3 : break;
11320 : }
11321 0 : case NC_FLOAT:
11322 : {
11323 : float *pfTemp =
11324 0 : static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
11325 0 : for (size_t i = 0; i < nVarLen; i++)
11326 : {
11327 0 : char *pszTemp = nullptr;
11328 0 : pfTemp[i] =
11329 0 : static_cast<float>(CPLStrtod(papszValues[i], &pszTemp));
11330 : }
11331 : status =
11332 0 : nc_put_vara_float(nCdfId, nVarId, start, count, pfTemp);
11333 0 : NCDF_ERR(status);
11334 0 : CPLFree(pfTemp);
11335 0 : break;
11336 : }
11337 5 : case NC_DOUBLE:
11338 : {
11339 : double *pdfTemp =
11340 5 : static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
11341 19 : for (size_t i = 0; i < nVarLen; i++)
11342 : {
11343 14 : char *pszTemp = nullptr;
11344 14 : pdfTemp[i] = CPLStrtod(papszValues[i], &pszTemp);
11345 : }
11346 : status =
11347 5 : nc_put_vara_double(nCdfId, nVarId, start, count, pdfTemp);
11348 5 : NCDF_ERR(status);
11349 5 : CPLFree(pdfTemp);
11350 5 : break;
11351 : }
11352 0 : default:
11353 : {
11354 0 : int nTmpFormat = 0;
11355 0 : status = nc_inq_format(nCdfId, &nTmpFormat);
11356 0 : NCDF_ERR(status);
11357 0 : if (nTmpFormat == NCDF_FORMAT_NC4)
11358 : {
11359 0 : switch (nVarType)
11360 : {
11361 0 : case NC_STRING:
11362 : {
11363 : status =
11364 0 : nc_put_vara_string(nCdfId, nVarId, start, count,
11365 : (const char **)papszValues);
11366 0 : NCDF_ERR(status);
11367 0 : break;
11368 : }
11369 0 : case NC_UBYTE:
11370 : {
11371 : unsigned char *pucTemp =
11372 : static_cast<unsigned char *>(
11373 0 : CPLCalloc(nVarLen, sizeof(unsigned char)));
11374 0 : for (size_t i = 0; i < nVarLen; i++)
11375 : {
11376 0 : char *pszTemp = nullptr;
11377 0 : pucTemp[i] = static_cast<unsigned char>(
11378 0 : strtoul(papszValues[i], &pszTemp, 10));
11379 : }
11380 0 : status = nc_put_vara_uchar(nCdfId, nVarId, start,
11381 : count, pucTemp);
11382 0 : NCDF_ERR(status);
11383 0 : CPLFree(pucTemp);
11384 0 : break;
11385 : }
11386 0 : case NC_USHORT:
11387 : {
11388 : unsigned short *pusTemp =
11389 : static_cast<unsigned short *>(
11390 0 : CPLCalloc(nVarLen, sizeof(unsigned short)));
11391 0 : for (size_t i = 0; i < nVarLen; i++)
11392 : {
11393 0 : char *pszTemp = nullptr;
11394 0 : pusTemp[i] = static_cast<unsigned short>(
11395 0 : strtoul(papszValues[i], &pszTemp, 10));
11396 : }
11397 0 : status = nc_put_vara_ushort(nCdfId, nVarId, start,
11398 : count, pusTemp);
11399 0 : NCDF_ERR(status);
11400 0 : CPLFree(pusTemp);
11401 0 : break;
11402 : }
11403 0 : case NC_UINT:
11404 : {
11405 : unsigned int *punTemp = static_cast<unsigned int *>(
11406 0 : CPLCalloc(nVarLen, sizeof(unsigned int)));
11407 0 : for (size_t i = 0; i < nVarLen; i++)
11408 : {
11409 0 : char *pszTemp = nullptr;
11410 0 : punTemp[i] = static_cast<unsigned int>(
11411 0 : strtoul(papszValues[i], &pszTemp, 10));
11412 : }
11413 0 : status = nc_put_vara_uint(nCdfId, nVarId, start,
11414 : count, punTemp);
11415 0 : NCDF_ERR(status);
11416 0 : CPLFree(punTemp);
11417 0 : break;
11418 : }
11419 0 : default:
11420 0 : if (papszValues)
11421 0 : CSLDestroy(papszValues);
11422 0 : return CE_Failure;
11423 : break;
11424 : }
11425 : }
11426 0 : break;
11427 : }
11428 : }
11429 : }
11430 :
11431 8 : if (papszValues)
11432 8 : CSLDestroy(papszValues);
11433 :
11434 8 : return CE_None;
11435 : }
11436 :
11437 : /************************************************************************/
11438 : /* GetDefaultNoDataValue() */
11439 : /************************************************************************/
11440 :
11441 196 : double NCDFGetDefaultNoDataValue(int nCdfId, int nVarId, int nVarType,
11442 : bool &bGotNoData)
11443 :
11444 : {
11445 196 : int nNoFill = 0;
11446 196 : double dfNoData = 0.0;
11447 :
11448 196 : switch (nVarType)
11449 : {
11450 0 : case NC_CHAR:
11451 : case NC_BYTE:
11452 : case NC_UBYTE:
11453 : // Don't do default fill-values for bytes, too risky.
11454 : // This function should not be called in those cases.
11455 0 : CPLAssert(false);
11456 : break;
11457 24 : case NC_SHORT:
11458 : {
11459 24 : short nFillVal = 0;
11460 24 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11461 : NC_NOERR)
11462 : {
11463 24 : if (!nNoFill)
11464 : {
11465 23 : bGotNoData = true;
11466 23 : dfNoData = nFillVal;
11467 : }
11468 : }
11469 : else
11470 0 : dfNoData = NC_FILL_SHORT;
11471 24 : break;
11472 : }
11473 26 : case NC_INT:
11474 : {
11475 26 : int nFillVal = 0;
11476 26 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11477 : NC_NOERR)
11478 : {
11479 26 : if (!nNoFill)
11480 : {
11481 25 : bGotNoData = true;
11482 25 : dfNoData = nFillVal;
11483 : }
11484 : }
11485 : else
11486 0 : dfNoData = NC_FILL_INT;
11487 26 : break;
11488 : }
11489 79 : case NC_FLOAT:
11490 : {
11491 79 : float fFillVal = 0;
11492 79 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &fFillVal) ==
11493 : NC_NOERR)
11494 : {
11495 79 : if (!nNoFill)
11496 : {
11497 75 : bGotNoData = true;
11498 75 : dfNoData = fFillVal;
11499 : }
11500 : }
11501 : else
11502 0 : dfNoData = NC_FILL_FLOAT;
11503 79 : break;
11504 : }
11505 34 : case NC_DOUBLE:
11506 : {
11507 34 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &dfNoData) ==
11508 : NC_NOERR)
11509 : {
11510 34 : if (!nNoFill)
11511 : {
11512 34 : bGotNoData = true;
11513 : }
11514 : }
11515 : else
11516 0 : dfNoData = NC_FILL_DOUBLE;
11517 34 : break;
11518 : }
11519 7 : case NC_USHORT:
11520 : {
11521 7 : unsigned short 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_USHORT;
11533 7 : break;
11534 : }
11535 7 : case NC_UINT:
11536 : {
11537 7 : unsigned int nFillVal = 0;
11538 7 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11539 : NC_NOERR)
11540 : {
11541 7 : if (!nNoFill)
11542 : {
11543 7 : bGotNoData = true;
11544 7 : dfNoData = nFillVal;
11545 : }
11546 : }
11547 : else
11548 0 : dfNoData = NC_FILL_UINT;
11549 7 : break;
11550 : }
11551 19 : default:
11552 19 : dfNoData = 0.0;
11553 19 : break;
11554 : }
11555 :
11556 196 : return dfNoData;
11557 : }
11558 :
11559 : /************************************************************************/
11560 : /* NCDFGetDefaultNoDataValueAsInt64() */
11561 : /************************************************************************/
11562 :
11563 2 : int64_t NCDFGetDefaultNoDataValueAsInt64(int nCdfId, int nVarId,
11564 : bool &bGotNoData)
11565 :
11566 : {
11567 2 : int nNoFill = 0;
11568 2 : long long nFillVal = 0;
11569 2 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
11570 : {
11571 2 : if (!nNoFill)
11572 : {
11573 2 : bGotNoData = true;
11574 2 : return static_cast<int64_t>(nFillVal);
11575 : }
11576 : }
11577 : else
11578 0 : return static_cast<int64_t>(NC_FILL_INT64);
11579 0 : return 0;
11580 : }
11581 :
11582 : /************************************************************************/
11583 : /* NCDFGetDefaultNoDataValueAsUInt64() */
11584 : /************************************************************************/
11585 :
11586 1 : uint64_t NCDFGetDefaultNoDataValueAsUInt64(int nCdfId, int nVarId,
11587 : bool &bGotNoData)
11588 :
11589 : {
11590 1 : int nNoFill = 0;
11591 1 : unsigned long long nFillVal = 0;
11592 1 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
11593 : {
11594 1 : if (!nNoFill)
11595 : {
11596 1 : bGotNoData = true;
11597 1 : return static_cast<uint64_t>(nFillVal);
11598 : }
11599 : }
11600 : else
11601 0 : return static_cast<uint64_t>(NC_FILL_UINT64);
11602 0 : return 0;
11603 : }
11604 :
11605 11133 : static int NCDFDoesVarContainAttribVal(int nCdfId,
11606 : const char *const *papszAttribNames,
11607 : const char *const *papszAttribValues,
11608 : int nVarId, const char *pszVarName,
11609 : bool bStrict = true)
11610 : {
11611 11133 : if (nVarId == -1 && pszVarName != nullptr)
11612 8144 : NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
11613 :
11614 11133 : if (nVarId == -1)
11615 878 : return -1;
11616 :
11617 10255 : bool bFound = false;
11618 47864 : for (int i = 0; !bFound && papszAttribNames != nullptr &&
11619 45590 : papszAttribNames[i] != nullptr;
11620 : i++)
11621 : {
11622 37609 : char *pszTemp = nullptr;
11623 37609 : if (NCDFGetAttr(nCdfId, nVarId, papszAttribNames[i], &pszTemp) ==
11624 54002 : CE_None &&
11625 16393 : pszTemp != nullptr)
11626 : {
11627 16393 : if (bStrict)
11628 : {
11629 16393 : if (EQUAL(pszTemp, papszAttribValues[i]))
11630 2274 : bFound = true;
11631 : }
11632 : else
11633 : {
11634 0 : if (EQUALN(pszTemp, papszAttribValues[i],
11635 : strlen(papszAttribValues[i])))
11636 0 : bFound = true;
11637 : }
11638 16393 : CPLFree(pszTemp);
11639 : }
11640 : }
11641 10255 : return bFound;
11642 : }
11643 :
11644 1962 : static int NCDFDoesVarContainAttribVal2(int nCdfId, const char *papszAttribName,
11645 : const char *const *papszAttribValues,
11646 : int nVarId, const char *pszVarName,
11647 : int bStrict = true)
11648 : {
11649 1962 : if (nVarId == -1 && pszVarName != nullptr)
11650 1579 : NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
11651 :
11652 1962 : if (nVarId == -1)
11653 0 : return -1;
11654 :
11655 1962 : bool bFound = false;
11656 1962 : char *pszTemp = nullptr;
11657 2341 : if (NCDFGetAttr(nCdfId, nVarId, papszAttribName, &pszTemp) != CE_None ||
11658 379 : pszTemp == nullptr)
11659 1583 : return FALSE;
11660 :
11661 7703 : for (int i = 0; !bFound && i < CSLCount(papszAttribValues); i++)
11662 : {
11663 7324 : if (bStrict)
11664 : {
11665 7296 : if (EQUAL(pszTemp, papszAttribValues[i]))
11666 31 : bFound = true;
11667 : }
11668 : else
11669 : {
11670 28 : if (EQUALN(pszTemp, papszAttribValues[i],
11671 : strlen(papszAttribValues[i])))
11672 0 : bFound = true;
11673 : }
11674 : }
11675 :
11676 379 : CPLFree(pszTemp);
11677 :
11678 379 : return bFound;
11679 : }
11680 :
11681 876 : static bool NCDFEqual(const char *papszName, const char *const *papszValues)
11682 : {
11683 876 : if (papszName == nullptr || EQUAL(papszName, ""))
11684 0 : return false;
11685 :
11686 2392 : for (int i = 0; papszValues && papszValues[i]; ++i)
11687 : {
11688 1636 : if (EQUAL(papszName, papszValues[i]))
11689 120 : return true;
11690 : }
11691 :
11692 756 : return false;
11693 : }
11694 :
11695 : // Test that a variable is longitude/latitude coordinate,
11696 : // following CF 4.1 and 4.2.
11697 3808 : bool NCDFIsVarLongitude(int nCdfId, int nVarId, const char *pszVarName)
11698 : {
11699 : // Check for matching attributes.
11700 3808 : int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLongitudeAttribNames,
11701 : papszCFLongitudeAttribValues, nVarId,
11702 : pszVarName);
11703 : // If not found using attributes then check using var name
11704 : // unless GDAL_NETCDF_VERIFY_DIMS=STRICT.
11705 3808 : if (bVal == -1)
11706 : {
11707 280 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11708 : "STRICT"))
11709 280 : bVal = NCDFEqual(pszVarName, papszCFLongitudeVarNames);
11710 : else
11711 0 : bVal = FALSE;
11712 : }
11713 3528 : else if (bVal)
11714 : {
11715 : // Check that the units is not 'm' or '1'. See #6759
11716 795 : char *pszTemp = nullptr;
11717 1175 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11718 380 : pszTemp != nullptr)
11719 : {
11720 380 : if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
11721 97 : bVal = false;
11722 380 : CPLFree(pszTemp);
11723 : }
11724 : }
11725 :
11726 3808 : return CPL_TO_BOOL(bVal);
11727 : }
11728 :
11729 2175 : bool NCDFIsVarLatitude(int nCdfId, int nVarId, const char *pszVarName)
11730 : {
11731 2175 : int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLatitudeAttribNames,
11732 : papszCFLatitudeAttribValues, nVarId,
11733 : pszVarName);
11734 2175 : if (bVal == -1)
11735 : {
11736 163 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11737 : "STRICT"))
11738 163 : bVal = NCDFEqual(pszVarName, papszCFLatitudeVarNames);
11739 : else
11740 0 : bVal = FALSE;
11741 : }
11742 2012 : else if (bVal)
11743 : {
11744 : // Check that the units is not 'm' or '1'. See #6759
11745 548 : char *pszTemp = nullptr;
11746 690 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11747 142 : pszTemp != nullptr)
11748 : {
11749 142 : if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
11750 36 : bVal = false;
11751 142 : CPLFree(pszTemp);
11752 : }
11753 : }
11754 :
11755 2175 : return CPL_TO_BOOL(bVal);
11756 : }
11757 :
11758 2300 : bool NCDFIsVarProjectionX(int nCdfId, int nVarId, const char *pszVarName)
11759 : {
11760 2300 : int bVal = NCDFDoesVarContainAttribVal(
11761 : nCdfId, papszCFProjectionXAttribNames, papszCFProjectionXAttribValues,
11762 : nVarId, pszVarName);
11763 2300 : if (bVal == -1)
11764 : {
11765 274 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11766 : "STRICT"))
11767 274 : bVal = NCDFEqual(pszVarName, papszCFProjectionXVarNames);
11768 : else
11769 0 : bVal = FALSE;
11770 : }
11771 2026 : else if (bVal)
11772 : {
11773 : // Check that the units is not '1'
11774 378 : char *pszTemp = nullptr;
11775 540 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11776 162 : pszTemp != nullptr)
11777 : {
11778 162 : if (EQUAL(pszTemp, "1"))
11779 5 : bVal = false;
11780 162 : CPLFree(pszTemp);
11781 : }
11782 : }
11783 :
11784 2300 : return CPL_TO_BOOL(bVal);
11785 : }
11786 :
11787 1626 : bool NCDFIsVarProjectionY(int nCdfId, int nVarId, const char *pszVarName)
11788 : {
11789 1626 : int bVal = NCDFDoesVarContainAttribVal(
11790 : nCdfId, papszCFProjectionYAttribNames, papszCFProjectionYAttribValues,
11791 : nVarId, pszVarName);
11792 1626 : if (bVal == -1)
11793 : {
11794 159 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11795 : "STRICT"))
11796 159 : bVal = NCDFEqual(pszVarName, papszCFProjectionYVarNames);
11797 : else
11798 0 : bVal = FALSE;
11799 : }
11800 1467 : else if (bVal)
11801 : {
11802 : // Check that the units is not '1'
11803 374 : char *pszTemp = nullptr;
11804 533 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11805 159 : pszTemp != nullptr)
11806 : {
11807 159 : if (EQUAL(pszTemp, "1"))
11808 5 : bVal = false;
11809 159 : CPLFree(pszTemp);
11810 : }
11811 : }
11812 :
11813 1626 : return CPL_TO_BOOL(bVal);
11814 : }
11815 :
11816 : /* test that a variable is a vertical coordinate, following CF 4.3 */
11817 1022 : bool NCDFIsVarVerticalCoord(int nCdfId, int nVarId, const char *pszVarName)
11818 : {
11819 : /* check for matching attributes */
11820 1022 : if (NCDFDoesVarContainAttribVal(nCdfId, papszCFVerticalAttribNames,
11821 : papszCFVerticalAttribValues, nVarId,
11822 1022 : pszVarName))
11823 72 : return true;
11824 : /* check for matching units */
11825 950 : else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
11826 : papszCFVerticalUnitsValues, nVarId,
11827 950 : pszVarName))
11828 31 : return true;
11829 : /* check for matching standard name */
11830 919 : else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_STD_NAME,
11831 : papszCFVerticalStandardNameValues,
11832 919 : nVarId, pszVarName))
11833 0 : return true;
11834 : else
11835 919 : return false;
11836 : }
11837 :
11838 : /* test that a variable is a time coordinate, following CF 4.4 */
11839 202 : bool NCDFIsVarTimeCoord(int nCdfId, int nVarId, const char *pszVarName)
11840 : {
11841 : /* check for matching attributes */
11842 202 : if (NCDFDoesVarContainAttribVal(nCdfId, papszCFTimeAttribNames,
11843 : papszCFTimeAttribValues, nVarId,
11844 202 : pszVarName))
11845 109 : return true;
11846 : /* check for matching units */
11847 93 : else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
11848 : papszCFTimeUnitsValues, nVarId,
11849 93 : pszVarName, false))
11850 0 : return true;
11851 : else
11852 93 : return false;
11853 : }
11854 :
11855 : // Parse a string, and return as a string list.
11856 : // If it an array of the form {a,b}, then tokenize it.
11857 : // Otherwise, return a copy.
11858 188 : static char **NCDFTokenizeArray(const char *pszValue)
11859 : {
11860 188 : if (pszValue == nullptr || EQUAL(pszValue, ""))
11861 53 : return nullptr;
11862 :
11863 135 : char **papszValues = nullptr;
11864 135 : const int nLen = static_cast<int>(strlen(pszValue));
11865 :
11866 135 : if (pszValue[0] == '{' && nLen > 2 && pszValue[nLen - 1] == '}')
11867 : {
11868 41 : char *pszTemp = static_cast<char *>(CPLMalloc((nLen - 2) + 1));
11869 41 : strncpy(pszTemp, pszValue + 1, nLen - 2);
11870 41 : pszTemp[nLen - 2] = '\0';
11871 41 : papszValues = CSLTokenizeString2(pszTemp, ",", CSLT_ALLOWEMPTYTOKENS);
11872 41 : CPLFree(pszTemp);
11873 : }
11874 : else
11875 : {
11876 94 : papszValues = static_cast<char **>(CPLCalloc(2, sizeof(char *)));
11877 94 : papszValues[0] = CPLStrdup(pszValue);
11878 94 : papszValues[1] = nullptr;
11879 : }
11880 :
11881 135 : return papszValues;
11882 : }
11883 :
11884 : // Open a NetCDF subdataset from full path /group1/group2/.../groupn/var.
11885 : // Leading slash is optional.
11886 414 : static CPLErr NCDFOpenSubDataset(int nCdfId, const char *pszSubdatasetName,
11887 : int *pnGroupId, int *pnVarId)
11888 : {
11889 414 : *pnGroupId = -1;
11890 414 : *pnVarId = -1;
11891 :
11892 : // Open group.
11893 : char *pszGroupFullName =
11894 414 : CPLStrdup(CPLGetPathSafe(pszSubdatasetName).c_str());
11895 : // Add a leading slash if needed.
11896 414 : if (pszGroupFullName[0] != '/')
11897 : {
11898 397 : char *old = pszGroupFullName;
11899 397 : pszGroupFullName = CPLStrdup(CPLSPrintf("/%s", pszGroupFullName));
11900 397 : CPLFree(old);
11901 : }
11902 : // Detect root group.
11903 414 : if (EQUAL(pszGroupFullName, "/"))
11904 : {
11905 397 : *pnGroupId = nCdfId;
11906 397 : CPLFree(pszGroupFullName);
11907 : }
11908 : else
11909 : {
11910 17 : int status = nc_inq_grp_full_ncid(nCdfId, pszGroupFullName, pnGroupId);
11911 17 : CPLFree(pszGroupFullName);
11912 17 : NCDF_ERR_RET(status);
11913 : }
11914 :
11915 : // Open var.
11916 414 : const char *pszVarName = CPLGetFilename(pszSubdatasetName);
11917 414 : NCDF_ERR_RET(nc_inq_varid(*pnGroupId, pszVarName, pnVarId));
11918 :
11919 414 : return CE_None;
11920 : }
11921 :
11922 : // Get all dimensions visible from a given NetCDF (or group) ID and any of
11923 : // its parents.
11924 354 : static CPLErr NCDFGetVisibleDims(int nGroupId, int *pnDims, int **ppanDimIds)
11925 : {
11926 354 : int nDims = 0;
11927 354 : int *panDimIds = nullptr;
11928 354 : NCDF_ERR_RET(nc_inq_dimids(nGroupId, &nDims, nullptr, true));
11929 :
11930 354 : panDimIds = static_cast<int *>(CPLMalloc(nDims * sizeof(int)));
11931 :
11932 354 : int status = nc_inq_dimids(nGroupId, nullptr, panDimIds, true);
11933 354 : if (status != NC_NOERR)
11934 0 : CPLFree(panDimIds);
11935 354 : NCDF_ERR_RET(status);
11936 :
11937 354 : *pnDims = nDims;
11938 354 : *ppanDimIds = panDimIds;
11939 :
11940 354 : return CE_None;
11941 : }
11942 :
11943 : // Get direct sub-groups IDs of a given NetCDF (or group) ID.
11944 : // Consider only direct children, does not get children of children.
11945 3108 : static CPLErr NCDFGetSubGroups(int nGroupId, int *pnSubGroups,
11946 : int **ppanSubGroupIds)
11947 : {
11948 3108 : *pnSubGroups = 0;
11949 3108 : *ppanSubGroupIds = nullptr;
11950 :
11951 : int nSubGroups;
11952 3108 : NCDF_ERR_RET(nc_inq_grps(nGroupId, &nSubGroups, nullptr));
11953 : int *panSubGroupIds =
11954 3108 : static_cast<int *>(CPLMalloc(nSubGroups * sizeof(int)));
11955 3108 : NCDF_ERR_RET(nc_inq_grps(nGroupId, nullptr, panSubGroupIds));
11956 3108 : *pnSubGroups = nSubGroups;
11957 3108 : *ppanSubGroupIds = panSubGroupIds;
11958 :
11959 3108 : return CE_None;
11960 : }
11961 :
11962 : // Get the full name of a given NetCDF (or group) ID
11963 : // (e.g. /group1/group2/.../groupn).
11964 : // bNC3Compat remove the leading slash for top-level variables for
11965 : // backward compatibility (top-level variables are the ones in the root group).
11966 15643 : static CPLErr NCDFGetGroupFullName(int nGroupId, char **ppszFullName,
11967 : bool bNC3Compat)
11968 : {
11969 15643 : *ppszFullName = nullptr;
11970 :
11971 : size_t nFullNameLen;
11972 15643 : NCDF_ERR_RET(nc_inq_grpname_len(nGroupId, &nFullNameLen));
11973 15643 : *ppszFullName =
11974 15643 : static_cast<char *>(CPLMalloc((nFullNameLen + 1) * sizeof(char)));
11975 15643 : int status = nc_inq_grpname_full(nGroupId, &nFullNameLen, *ppszFullName);
11976 15643 : if (status != NC_NOERR)
11977 : {
11978 0 : CPLFree(*ppszFullName);
11979 0 : *ppszFullName = nullptr;
11980 0 : NCDF_ERR_RET(status);
11981 : }
11982 :
11983 15643 : if (bNC3Compat && EQUAL(*ppszFullName, "/"))
11984 7994 : (*ppszFullName)[0] = '\0';
11985 :
11986 15643 : return CE_None;
11987 : }
11988 :
11989 7441 : CPLString NCDFGetGroupFullName(int nGroupId)
11990 : {
11991 7441 : char *pszFullname = nullptr;
11992 7441 : NCDFGetGroupFullName(nGroupId, &pszFullname, false);
11993 7441 : CPLString osRet(pszFullname ? pszFullname : "");
11994 7441 : CPLFree(pszFullname);
11995 14882 : return osRet;
11996 : }
11997 :
11998 : // Get the full name of a given NetCDF variable ID
11999 : // (e.g. /group1/group2/.../groupn/var).
12000 : // Handle also NC_GLOBAL as nVarId.
12001 : // bNC3Compat remove the leading slash for top-level variables for
12002 : // backward compatibility (top-level variables are the ones in the root group).
12003 8153 : static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId, char **ppszFullName,
12004 : bool bNC3Compat)
12005 : {
12006 8153 : *ppszFullName = nullptr;
12007 8153 : char *pszGroupFullName = nullptr;
12008 8153 : ERR_RET(NCDFGetGroupFullName(nGroupId, &pszGroupFullName, bNC3Compat));
12009 : char szVarName[NC_MAX_NAME + 1];
12010 8153 : if (nVarId == NC_GLOBAL)
12011 : {
12012 1100 : strcpy(szVarName, "NC_GLOBAL");
12013 : }
12014 : else
12015 : {
12016 7053 : int status = nc_inq_varname(nGroupId, nVarId, szVarName);
12017 7053 : if (status != NC_NOERR)
12018 : {
12019 0 : CPLFree(pszGroupFullName);
12020 0 : NCDF_ERR_RET(status);
12021 : }
12022 : }
12023 8153 : const char *pszSep = "/";
12024 8153 : if (EQUAL(pszGroupFullName, "/") || EQUAL(pszGroupFullName, ""))
12025 7947 : pszSep = "";
12026 8153 : *ppszFullName =
12027 8153 : CPLStrdup(CPLSPrintf("%s%s%s", pszGroupFullName, pszSep, szVarName));
12028 8153 : CPLFree(pszGroupFullName);
12029 8153 : return CE_None;
12030 : }
12031 :
12032 : // Get the NetCDF root group ID of a given group ID.
12033 0 : static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId)
12034 : {
12035 0 : *pnRootGroupId = -1;
12036 : // Recurse on parent group.
12037 : int nParentGroupId;
12038 0 : int status = nc_inq_grp_parent(nStartGroupId, &nParentGroupId);
12039 0 : if (status == NC_NOERR)
12040 0 : return NCDFGetRootGroup(nParentGroupId, pnRootGroupId);
12041 0 : else if (status != NC_ENOGRP)
12042 0 : NCDF_ERR_RET(status);
12043 : else // No more parent group.
12044 : {
12045 0 : *pnRootGroupId = nStartGroupId;
12046 : }
12047 :
12048 0 : return CE_None;
12049 : }
12050 :
12051 : // Implementation of NCDFResolveVar/Att.
12052 13242 : static CPLErr NCDFResolveElem(int nStartGroupId, const char *pszVar,
12053 : const char *pszAtt, int *pnGroupId, int *pnId,
12054 : bool bMandatory)
12055 : {
12056 13242 : if (!pszVar && !pszAtt)
12057 : {
12058 0 : CPLError(CE_Failure, CPLE_IllegalArg,
12059 : "pszVar and pszAtt NCDFResolveElem() args are both null.");
12060 0 : return CE_Failure;
12061 : }
12062 :
12063 : enum
12064 : {
12065 : NCRM_PARENT,
12066 : NCRM_WIDTH_WISE
12067 13242 : } eNCResolveMode = NCRM_PARENT;
12068 :
12069 26484 : std::queue<int> aoQueueGroupIdsToVisit;
12070 13242 : aoQueueGroupIdsToVisit.push(nStartGroupId);
12071 :
12072 14966 : while (!aoQueueGroupIdsToVisit.empty())
12073 : {
12074 : // Get the first group of the FIFO queue.
12075 13436 : *pnGroupId = aoQueueGroupIdsToVisit.front();
12076 13436 : aoQueueGroupIdsToVisit.pop();
12077 :
12078 : // Look if this group contains the searched element.
12079 : int status;
12080 13436 : if (pszVar)
12081 13217 : status = nc_inq_varid(*pnGroupId, pszVar, pnId);
12082 : else // pszAtt != nullptr.
12083 219 : status = nc_inq_attid(*pnGroupId, NC_GLOBAL, pszAtt, pnId);
12084 :
12085 13436 : if (status == NC_NOERR)
12086 : {
12087 11712 : return CE_None;
12088 : }
12089 1724 : else if ((pszVar && status != NC_ENOTVAR) ||
12090 216 : (pszAtt && status != NC_ENOTATT))
12091 : {
12092 0 : NCDF_ERR(status);
12093 : }
12094 : // Element not found, in NC4 case we must search in other groups
12095 : // following the CF logic.
12096 :
12097 : // The first resolve mode consists to search on parent groups.
12098 1724 : if (eNCResolveMode == NCRM_PARENT)
12099 : {
12100 1605 : int nParentGroupId = -1;
12101 1605 : int status2 = nc_inq_grp_parent(*pnGroupId, &nParentGroupId);
12102 1605 : if (status2 == NC_NOERR)
12103 62 : aoQueueGroupIdsToVisit.push(nParentGroupId);
12104 1543 : else if (status2 != NC_ENOGRP)
12105 0 : NCDF_ERR(status2);
12106 1543 : else if (pszVar)
12107 : // When resolving a variable, if there is no more
12108 : // parent group then we switch to width-wise search mode
12109 : // starting from the latest found parent group.
12110 1330 : eNCResolveMode = NCRM_WIDTH_WISE;
12111 : }
12112 :
12113 : // The second resolve mode is a width-wise search.
12114 1724 : if (eNCResolveMode == NCRM_WIDTH_WISE)
12115 : {
12116 : // Enqueue all direct sub-groups.
12117 1449 : int nSubGroups = 0;
12118 1449 : int *panSubGroupIds = nullptr;
12119 1449 : NCDFGetSubGroups(*pnGroupId, &nSubGroups, &panSubGroupIds);
12120 1581 : for (int i = 0; i < nSubGroups; i++)
12121 132 : aoQueueGroupIdsToVisit.push(panSubGroupIds[i]);
12122 1449 : CPLFree(panSubGroupIds);
12123 : }
12124 : }
12125 :
12126 1530 : if (bMandatory)
12127 : {
12128 0 : char *pszStartGroupFullName = nullptr;
12129 0 : NCDFGetGroupFullName(nStartGroupId, &pszStartGroupFullName);
12130 0 : CPLError(CE_Failure, CPLE_AppDefined,
12131 : "Cannot resolve mandatory %s %s from group %s",
12132 : (pszVar ? pszVar : pszAtt),
12133 : (pszVar ? "variable" : "attribute"),
12134 0 : (pszStartGroupFullName ? pszStartGroupFullName : ""));
12135 0 : CPLFree(pszStartGroupFullName);
12136 : }
12137 :
12138 1530 : *pnGroupId = -1;
12139 1530 : *pnId = -1;
12140 1530 : return CE_Failure;
12141 : }
12142 :
12143 : // Resolve a variable name from a given starting group following the CF logic:
12144 : // - if var name is an absolute path then directly open it
12145 : // - first search in the starting group and its parent groups
12146 : // - then if there is no more parent group we switch to a width-wise search
12147 : // mode starting from the latest found parent group.
12148 : // The full CF logic is described here:
12149 : // https://github.com/diwg/cf2/blob/master/group/cf2-group.adoc#scope
12150 : // If bMandatory then print an error if resolving fails.
12151 : // TODO: implement support of relative paths.
12152 : // TODO: to follow strictly the CF logic, when searching for a coordinate
12153 : // variable, we must stop the parent search mode once the corresponding
12154 : // dimension is found and start the width-wise search from this group.
12155 : // TODO: to follow strictly the CF logic, when searching in width-wise mode
12156 : // we should skip every groups already visited during the parent
12157 : // search mode (but revisiting them should have no impact so we could
12158 : // let as it is if it is simpler...)
12159 : // TODO: CF specifies that the width-wise search order is "left-to-right" so
12160 : // maybe we must sort sibling groups alphabetically? but maybe not
12161 : // necessary if nc_inq_grps() already sort them?
12162 13026 : CPLErr NCDFResolveVar(int nStartGroupId, const char *pszVar, int *pnGroupId,
12163 : int *pnVarId, bool bMandatory)
12164 : {
12165 13026 : *pnGroupId = -1;
12166 13026 : *pnVarId = -1;
12167 13026 : int nGroupId = nStartGroupId, nVarId;
12168 13026 : if (pszVar[0] == '/')
12169 : {
12170 : // This is an absolute path: we can open the var directly.
12171 : int nRootGroupId;
12172 0 : ERR_RET(NCDFGetRootGroup(nStartGroupId, &nRootGroupId));
12173 0 : ERR_RET(NCDFOpenSubDataset(nRootGroupId, pszVar, &nGroupId, &nVarId));
12174 : }
12175 : else
12176 : {
12177 : // We have to search the variable following the CF logic.
12178 13026 : ERR_RET(NCDFResolveElem(nStartGroupId, pszVar, nullptr, &nGroupId,
12179 : &nVarId, bMandatory));
12180 : }
12181 11709 : *pnGroupId = nGroupId;
12182 11709 : *pnVarId = nVarId;
12183 11709 : return CE_None;
12184 : }
12185 :
12186 : // Like NCDFResolveVar but returns directly the var full name.
12187 1384 : static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,
12188 : char **ppszFullName, bool bMandatory)
12189 : {
12190 1384 : *ppszFullName = nullptr;
12191 : int nGroupId, nVarId;
12192 1384 : ERR_RET(
12193 : NCDFResolveVar(nStartGroupId, pszVar, &nGroupId, &nVarId, bMandatory));
12194 1358 : return NCDFGetVarFullName(nGroupId, nVarId, ppszFullName);
12195 : }
12196 :
12197 : // Like NCDFResolveVar but resolves an attribute instead a variable and
12198 : // returns its integer value.
12199 : // Only GLOBAL attributes are supported for the moment.
12200 216 : static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,
12201 : const char *pszAtt, int *pnAtt, bool bMandatory)
12202 : {
12203 216 : int nGroupId = nStartGroupId, nAttId = nStartVarId;
12204 216 : ERR_RET(NCDFResolveElem(nStartGroupId, nullptr, pszAtt, &nGroupId, &nAttId,
12205 : bMandatory));
12206 3 : NCDF_ERR_RET(nc_get_att_int(nGroupId, NC_GLOBAL, pszAtt, pnAtt));
12207 3 : return CE_None;
12208 : }
12209 :
12210 : // Filter variables to keep only valid 2+D raster bands and vector fields in
12211 : // a given a NetCDF (or group) ID and its sub-groups.
12212 : // Coordinate or boundary variables are ignored.
12213 : // It also creates corresponding vector layers.
12214 530 : CPLErr netCDFDataset::FilterVars(
12215 : int nCdfId, bool bKeepRasters, bool bKeepVectors, char **papszIgnoreVars,
12216 : int *pnRasterVars, int *pnGroupId, int *pnVarId, int *pnIgnoredVars,
12217 : std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>
12218 : &oMap2DDimsToGroupAndVar)
12219 : {
12220 530 : int nVars = 0;
12221 530 : int nRasterVars = 0;
12222 530 : NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
12223 :
12224 1060 : std::vector<int> anPotentialVectorVarID;
12225 : // oMapDimIdToCount[x] = number of times dim x is the first dimension of
12226 : // potential vector variables
12227 1060 : std::map<int, int> oMapDimIdToCount;
12228 530 : int nVarXId = -1;
12229 530 : int nVarYId = -1;
12230 530 : int nVarZId = -1;
12231 530 : int nVarTimeId = -1;
12232 530 : int nVarTimeDimId = -1;
12233 530 : bool bIsVectorOnly = true;
12234 530 : int nProfileDimId = -1;
12235 530 : int nParentIndexVarID = -1;
12236 :
12237 3229 : for (int v = 0; v < nVars; v++)
12238 : {
12239 : int nVarDims;
12240 2699 : NCDF_ERR_RET(nc_inq_varndims(nCdfId, v, &nVarDims));
12241 : // Should we ignore this variable?
12242 : char szTemp[NC_MAX_NAME + 1];
12243 2699 : szTemp[0] = '\0';
12244 2699 : NCDF_ERR_RET(nc_inq_varname(nCdfId, v, szTemp));
12245 :
12246 2699 : if (strstr(szTemp, "_node_coordinates") ||
12247 2699 : strstr(szTemp, "_node_count"))
12248 : {
12249 : // Ignore CF-1.8 Simple Geometries helper variables
12250 69 : continue;
12251 : }
12252 :
12253 3927 : if (nVarDims == 1 && (NCDFIsVarLongitude(nCdfId, -1, szTemp) ||
12254 1297 : NCDFIsVarProjectionX(nCdfId, -1, szTemp)))
12255 : {
12256 359 : nVarXId = v;
12257 : }
12258 3209 : else if (nVarDims == 1 && (NCDFIsVarLatitude(nCdfId, -1, szTemp) ||
12259 938 : NCDFIsVarProjectionY(nCdfId, -1, szTemp)))
12260 : {
12261 358 : nVarYId = v;
12262 : }
12263 1913 : else if (nVarDims == 1 && NCDFIsVarVerticalCoord(nCdfId, -1, szTemp))
12264 : {
12265 78 : nVarZId = v;
12266 : }
12267 : else
12268 : {
12269 1835 : char *pszVarFullName = nullptr;
12270 1835 : CPLErr eErr = NCDFGetVarFullName(nCdfId, v, &pszVarFullName);
12271 1835 : if (eErr != CE_None)
12272 : {
12273 0 : CPLFree(pszVarFullName);
12274 0 : continue;
12275 : }
12276 : bool bIgnoreVar =
12277 1835 : (CSLFindString(papszIgnoreVars, pszVarFullName) != -1);
12278 1835 : CPLFree(pszVarFullName);
12279 1835 : if (bIgnoreVar)
12280 : {
12281 104 : if (nVarDims == 1 && NCDFIsVarTimeCoord(nCdfId, -1, szTemp))
12282 : {
12283 11 : nVarTimeId = v;
12284 11 : nc_inq_vardimid(nCdfId, v, &nVarTimeDimId);
12285 : }
12286 93 : else if (nVarDims > 1)
12287 : {
12288 89 : (*pnIgnoredVars)++;
12289 89 : CPLDebug("GDAL_netCDF", "variable #%d [%s] was ignored", v,
12290 : szTemp);
12291 : }
12292 : }
12293 : // Only accept 2+D vars.
12294 1731 : else if (nVarDims >= 2)
12295 : {
12296 706 : bool bRasterCandidate = true;
12297 : // Identify variables that might be vector variables
12298 706 : if (nVarDims == 2)
12299 : {
12300 630 : int anDimIds[2] = {-1, -1};
12301 630 : nc_inq_vardimid(nCdfId, v, anDimIds);
12302 :
12303 630 : nc_type vartype = NC_NAT;
12304 630 : nc_inq_vartype(nCdfId, v, &vartype);
12305 :
12306 : char szDimNameFirst[NC_MAX_NAME + 1];
12307 : char szDimNameSecond[NC_MAX_NAME + 1];
12308 630 : szDimNameFirst[0] = '\0';
12309 630 : szDimNameSecond[0] = '\0';
12310 1417 : if (vartype == NC_CHAR &&
12311 157 : nc_inq_dimname(nCdfId, anDimIds[0], szDimNameFirst) ==
12312 157 : NC_NOERR &&
12313 157 : nc_inq_dimname(nCdfId, anDimIds[1], szDimNameSecond) ==
12314 157 : NC_NOERR &&
12315 157 : !NCDFIsVarLongitude(nCdfId, -1, szDimNameSecond) &&
12316 157 : !NCDFIsVarProjectionX(nCdfId, -1, szDimNameSecond) &&
12317 944 : !NCDFIsVarLatitude(nCdfId, -1, szDimNameFirst) &&
12318 157 : !NCDFIsVarProjectionY(nCdfId, -1, szDimNameFirst))
12319 : {
12320 157 : anPotentialVectorVarID.push_back(v);
12321 157 : oMapDimIdToCount[anDimIds[0]]++;
12322 157 : if (strstr(szDimNameSecond, "_max_width"))
12323 : {
12324 127 : bRasterCandidate = false;
12325 : }
12326 : else
12327 : {
12328 30 : std::array<int, 3> oKey{anDimIds[0], anDimIds[1],
12329 30 : vartype};
12330 30 : oMap2DDimsToGroupAndVar[oKey].emplace_back(
12331 30 : std::pair(nCdfId, v));
12332 : }
12333 : }
12334 : else
12335 : {
12336 473 : std::array<int, 3> oKey{anDimIds[0], anDimIds[1],
12337 473 : vartype};
12338 473 : oMap2DDimsToGroupAndVar[oKey].emplace_back(
12339 473 : std::pair(nCdfId, v));
12340 473 : bIsVectorOnly = false;
12341 : }
12342 : }
12343 : else
12344 : {
12345 76 : bIsVectorOnly = false;
12346 : }
12347 706 : if (bKeepRasters && bRasterCandidate)
12348 : {
12349 550 : *pnGroupId = nCdfId;
12350 550 : *pnVarId = v;
12351 550 : nRasterVars++;
12352 : }
12353 : }
12354 1025 : else if (nVarDims == 1)
12355 : {
12356 730 : nc_type atttype = NC_NAT;
12357 730 : size_t attlen = 0;
12358 730 : if (nc_inq_att(nCdfId, v, "instance_dimension", &atttype,
12359 14 : &attlen) == NC_NOERR &&
12360 730 : atttype == NC_CHAR && attlen < NC_MAX_NAME)
12361 : {
12362 : char szInstanceDimension[NC_MAX_NAME + 1];
12363 14 : if (nc_get_att_text(nCdfId, v, "instance_dimension",
12364 14 : szInstanceDimension) == NC_NOERR)
12365 : {
12366 14 : szInstanceDimension[attlen] = 0;
12367 14 : int status = nc_inq_dimid(nCdfId, szInstanceDimension,
12368 : &nProfileDimId);
12369 14 : if (status == NC_NOERR)
12370 14 : nParentIndexVarID = v;
12371 : else
12372 0 : nProfileDimId = -1;
12373 14 : if (status == NC_EBADDIM)
12374 0 : CPLError(CE_Warning, CPLE_AppDefined,
12375 : "Attribute instance_dimension='%s' refers "
12376 : "to a non existing dimension",
12377 : szInstanceDimension);
12378 : else
12379 14 : NCDF_ERR(status);
12380 : }
12381 : }
12382 730 : if (v != nParentIndexVarID)
12383 : {
12384 716 : anPotentialVectorVarID.push_back(v);
12385 716 : int nDimId = -1;
12386 716 : nc_inq_vardimid(nCdfId, v, &nDimId);
12387 716 : oMapDimIdToCount[nDimId]++;
12388 : }
12389 : }
12390 : }
12391 : }
12392 :
12393 : // If we are opened in raster-only mode and that there are only 1D or 2D
12394 : // variables and that the 2D variables have no X/Y dim, and all
12395 : // variables refer to the same main dimension (or 2 dimensions for
12396 : // featureType=profile), then it is a pure vector dataset
12397 : CPLString osFeatureType(
12398 530 : CSLFetchNameValueDef(papszMetadata, "NC_GLOBAL#featureType", ""));
12399 419 : if (bKeepRasters && !bKeepVectors && bIsVectorOnly && nRasterVars > 0 &&
12400 949 : !anPotentialVectorVarID.empty() &&
12401 0 : (oMapDimIdToCount.size() == 1 ||
12402 0 : (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2 &&
12403 0 : nProfileDimId >= 0)))
12404 : {
12405 0 : anPotentialVectorVarID.resize(0);
12406 : }
12407 : else
12408 : {
12409 530 : *pnRasterVars += nRasterVars;
12410 : }
12411 :
12412 530 : if (!anPotentialVectorVarID.empty() && bKeepVectors)
12413 : {
12414 : // Take the dimension that is referenced the most times.
12415 64 : if (!(oMapDimIdToCount.size() == 1 ||
12416 27 : (EQUAL(osFeatureType, "profile") &&
12417 26 : oMapDimIdToCount.size() == 2 && nProfileDimId >= 0)))
12418 : {
12419 1 : CPLError(CE_Warning, CPLE_AppDefined,
12420 : "The dataset has several variables that could be "
12421 : "identified as vector fields, but not all share the same "
12422 : "primary dimension. Consequently they will be ignored.");
12423 : }
12424 : else
12425 : {
12426 50 : if (nVarTimeId >= 0 &&
12427 50 : oMapDimIdToCount.find(nVarTimeDimId) != oMapDimIdToCount.end())
12428 : {
12429 1 : anPotentialVectorVarID.push_back(nVarTimeId);
12430 : }
12431 49 : CreateGrpVectorLayers(nCdfId, osFeatureType, anPotentialVectorVarID,
12432 : oMapDimIdToCount, nVarXId, nVarYId, nVarZId,
12433 : nProfileDimId, nParentIndexVarID,
12434 : bKeepRasters);
12435 : }
12436 : }
12437 :
12438 : // Recurse on sub-groups.
12439 530 : int nSubGroups = 0;
12440 530 : int *panSubGroupIds = nullptr;
12441 530 : NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
12442 565 : for (int i = 0; i < nSubGroups; i++)
12443 : {
12444 35 : FilterVars(panSubGroupIds[i], bKeepRasters, bKeepVectors,
12445 : papszIgnoreVars, pnRasterVars, pnGroupId, pnVarId,
12446 : pnIgnoredVars, oMap2DDimsToGroupAndVar);
12447 : }
12448 530 : CPLFree(panSubGroupIds);
12449 :
12450 530 : return CE_None;
12451 : }
12452 :
12453 : // Create vector layers from given potentially identified vector variables
12454 : // resulting from the scanning of a NetCDF (or group) ID.
12455 49 : CPLErr netCDFDataset::CreateGrpVectorLayers(
12456 : int nCdfId, const CPLString &osFeatureType,
12457 : const std::vector<int> &anPotentialVectorVarID,
12458 : const std::map<int, int> &oMapDimIdToCount, int nVarXId, int nVarYId,
12459 : int nVarZId, int nProfileDimId, int nParentIndexVarID, bool bKeepRasters)
12460 : {
12461 49 : char *pszGroupName = nullptr;
12462 49 : NCDFGetGroupFullName(nCdfId, &pszGroupName);
12463 49 : if (pszGroupName == nullptr || pszGroupName[0] == '\0')
12464 : {
12465 47 : CPLFree(pszGroupName);
12466 47 : pszGroupName = CPLStrdup(CPLGetBasenameSafe(osFilename).c_str());
12467 : }
12468 49 : OGRwkbGeometryType eGType = wkbUnknown;
12469 : CPLString osLayerName = CSLFetchNameValueDef(
12470 98 : papszMetadata, "NC_GLOBAL#ogr_layer_name", pszGroupName);
12471 49 : CPLFree(pszGroupName);
12472 49 : papszMetadata =
12473 49 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_name", nullptr);
12474 :
12475 49 : if (EQUAL(osFeatureType, "point") || EQUAL(osFeatureType, "profile"))
12476 : {
12477 33 : papszMetadata =
12478 33 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#featureType", nullptr);
12479 33 : eGType = wkbPoint;
12480 : }
12481 :
12482 : const char *pszLayerType =
12483 49 : CSLFetchNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_type");
12484 49 : if (pszLayerType != nullptr)
12485 : {
12486 9 : eGType = OGRFromOGCGeomType(pszLayerType);
12487 9 : papszMetadata =
12488 9 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_type", nullptr);
12489 : }
12490 :
12491 : CPLString osGeometryField =
12492 98 : CSLFetchNameValueDef(papszMetadata, "NC_GLOBAL#ogr_geometry_field", "");
12493 49 : papszMetadata =
12494 49 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_geometry_field", nullptr);
12495 :
12496 49 : int nFirstVarId = -1;
12497 49 : int nVectorDim = oMapDimIdToCount.rbegin()->first;
12498 49 : if (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2)
12499 : {
12500 13 : if (nVectorDim == nProfileDimId)
12501 0 : nVectorDim = oMapDimIdToCount.begin()->first;
12502 : }
12503 : else
12504 : {
12505 36 : nProfileDimId = -1;
12506 : }
12507 62 : for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)
12508 : {
12509 62 : int anDimIds[2] = {-1, -1};
12510 62 : nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);
12511 62 : if (nVectorDim == anDimIds[0])
12512 : {
12513 49 : nFirstVarId = anPotentialVectorVarID[j];
12514 49 : break;
12515 : }
12516 : }
12517 :
12518 : // In case where coordinates are explicitly specified for one of the
12519 : // field/variable, use them in priority over the ones that might have been
12520 : // identified above.
12521 49 : char *pszCoordinates = nullptr;
12522 49 : if (NCDFGetAttr(nCdfId, nFirstVarId, "coordinates", &pszCoordinates) ==
12523 : CE_None)
12524 : {
12525 34 : char **papszTokens = NCDFTokenizeCoordinatesAttribute(pszCFCoordinates);
12526 34 : for (int i = 0; papszTokens != nullptr && papszTokens[i] != nullptr;
12527 : i++)
12528 : {
12529 0 : if (NCDFIsVarLongitude(nCdfId, -1, papszTokens[i]) ||
12530 0 : NCDFIsVarProjectionX(nCdfId, -1, papszTokens[i]))
12531 : {
12532 0 : nVarXId = -1;
12533 0 : CPL_IGNORE_RET_VAL(
12534 0 : nc_inq_varid(nCdfId, papszTokens[i], &nVarXId));
12535 : }
12536 0 : else if (NCDFIsVarLatitude(nCdfId, -1, papszTokens[i]) ||
12537 0 : NCDFIsVarProjectionY(nCdfId, -1, papszTokens[i]))
12538 : {
12539 0 : nVarYId = -1;
12540 0 : CPL_IGNORE_RET_VAL(
12541 0 : nc_inq_varid(nCdfId, papszTokens[i], &nVarYId));
12542 : }
12543 0 : else if (NCDFIsVarVerticalCoord(nCdfId, -1, papszTokens[i]))
12544 : {
12545 0 : nVarZId = -1;
12546 0 : CPL_IGNORE_RET_VAL(
12547 0 : nc_inq_varid(nCdfId, papszTokens[i], &nVarZId));
12548 : }
12549 : }
12550 34 : CSLDestroy(papszTokens);
12551 : }
12552 49 : CPLFree(pszCoordinates);
12553 :
12554 : // Check that the X,Y,Z vars share 1D and share the same dimension as
12555 : // attribute variables.
12556 49 : if (nVarXId >= 0 && nVarYId >= 0)
12557 : {
12558 38 : int nVarDimCount = -1;
12559 38 : int nVarDimId = -1;
12560 38 : if (nc_inq_varndims(nCdfId, nVarXId, &nVarDimCount) != NC_NOERR ||
12561 38 : nVarDimCount != 1 ||
12562 38 : nc_inq_vardimid(nCdfId, nVarXId, &nVarDimId) != NC_NOERR ||
12563 38 : nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim) ||
12564 35 : nc_inq_varndims(nCdfId, nVarYId, &nVarDimCount) != NC_NOERR ||
12565 35 : nVarDimCount != 1 ||
12566 111 : nc_inq_vardimid(nCdfId, nVarYId, &nVarDimId) != NC_NOERR ||
12567 35 : nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim))
12568 : {
12569 3 : nVarXId = nVarYId = -1;
12570 : }
12571 69 : else if (nVarZId >= 0 &&
12572 34 : (nc_inq_varndims(nCdfId, nVarZId, &nVarDimCount) != NC_NOERR ||
12573 34 : nVarDimCount != 1 ||
12574 34 : nc_inq_vardimid(nCdfId, nVarZId, &nVarDimId) != NC_NOERR ||
12575 34 : nVarDimId != nVectorDim))
12576 : {
12577 0 : nVarZId = -1;
12578 : }
12579 : }
12580 :
12581 49 : if (eGType == wkbUnknown && nVarXId >= 0 && nVarYId >= 0)
12582 : {
12583 2 : eGType = wkbPoint;
12584 : }
12585 49 : if (eGType == wkbPoint && nVarXId >= 0 && nVarYId >= 0 && nVarZId >= 0)
12586 : {
12587 34 : eGType = wkbPoint25D;
12588 : }
12589 49 : if (eGType == wkbUnknown && osGeometryField.empty())
12590 : {
12591 5 : eGType = wkbNone;
12592 : }
12593 :
12594 : // Read projection info
12595 49 : char **papszMetadataBackup = CSLDuplicate(papszMetadata);
12596 49 : ReadAttributes(nCdfId, nFirstVarId);
12597 49 : if (!this->bSGSupport)
12598 49 : SetProjectionFromVar(nCdfId, nFirstVarId, true);
12599 49 : const char *pszValue = FetchAttr(nCdfId, nFirstVarId, CF_GRD_MAPPING);
12600 49 : char *pszGridMapping = (pszValue ? CPLStrdup(pszValue) : nullptr);
12601 49 : CSLDestroy(papszMetadata);
12602 49 : papszMetadata = papszMetadataBackup;
12603 :
12604 49 : OGRSpatialReference *poSRS = nullptr;
12605 49 : if (!m_oSRS.IsEmpty())
12606 : {
12607 21 : poSRS = m_oSRS.Clone();
12608 : }
12609 : // Reset if there's a 2D raster
12610 49 : m_bHasProjection = false;
12611 49 : m_bHasGeoTransform = false;
12612 :
12613 49 : if (!bKeepRasters)
12614 : {
12615 : // Strip out uninteresting metadata.
12616 45 : papszMetadata =
12617 45 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#Conventions", nullptr);
12618 45 : papszMetadata =
12619 45 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#GDAL", nullptr);
12620 45 : papszMetadata =
12621 45 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#history", nullptr);
12622 : }
12623 :
12624 : std::shared_ptr<netCDFLayer> poLayer(
12625 49 : new netCDFLayer(this, nCdfId, osLayerName, eGType, poSRS));
12626 49 : if (poSRS != nullptr)
12627 21 : poSRS->Release();
12628 49 : poLayer->SetRecordDimID(nVectorDim);
12629 49 : if (wkbFlatten(eGType) == wkbPoint && nVarXId >= 0 && nVarYId >= 0)
12630 : {
12631 35 : poLayer->SetXYZVars(nVarXId, nVarYId, nVarZId);
12632 : }
12633 14 : else if (!osGeometryField.empty())
12634 : {
12635 9 : poLayer->SetWKTGeometryField(osGeometryField);
12636 : }
12637 49 : if (pszGridMapping != nullptr)
12638 : {
12639 21 : poLayer->SetGridMapping(pszGridMapping);
12640 21 : CPLFree(pszGridMapping);
12641 : }
12642 49 : poLayer->SetProfile(nProfileDimId, nParentIndexVarID);
12643 :
12644 574 : for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)
12645 : {
12646 525 : int anDimIds[2] = {-1, -1};
12647 525 : nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);
12648 525 : if (anDimIds[0] == nVectorDim ||
12649 24 : (nProfileDimId >= 0 && anDimIds[0] == nProfileDimId))
12650 : {
12651 : #ifdef NCDF_DEBUG
12652 : char szTemp2[NC_MAX_NAME + 1] = {};
12653 : CPL_IGNORE_RET_VAL(
12654 : nc_inq_varname(nCdfId, anPotentialVectorVarID[j], szTemp2));
12655 : CPLDebug("GDAL_netCDF", "Variable %s is a vector field", szTemp2);
12656 : #endif
12657 525 : poLayer->AddField(anPotentialVectorVarID[j]);
12658 : }
12659 : }
12660 :
12661 49 : if (poLayer->GetLayerDefn()->GetFieldCount() != 0 ||
12662 0 : poLayer->GetGeomType() != wkbNone)
12663 : {
12664 49 : papoLayers.push_back(poLayer);
12665 : }
12666 :
12667 98 : return CE_None;
12668 : }
12669 :
12670 : // Get all coordinate and boundary variables full names referenced in
12671 : // a given a NetCDF (or group) ID and its sub-groups.
12672 : // These variables are identified in other variable's
12673 : // "coordinates" and "bounds" attribute.
12674 : // Searching coordinate and boundary variables may need to explore
12675 : // parents groups (or other groups in case of reference given in form of an
12676 : // absolute path).
12677 : // See CF sections 5.2, 5.6 and 7.1
12678 531 : static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId, char ***ppapszVars)
12679 : {
12680 531 : int nVars = 0;
12681 531 : NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
12682 :
12683 3244 : for (int v = 0; v < nVars; v++)
12684 : {
12685 2713 : char *pszTemp = nullptr;
12686 2713 : char **papszTokens = nullptr;
12687 2713 : if (NCDFGetAttr(nCdfId, v, "coordinates", &pszTemp) == CE_None)
12688 446 : papszTokens = NCDFTokenizeCoordinatesAttribute(pszTemp);
12689 2713 : CPLFree(pszTemp);
12690 2713 : pszTemp = nullptr;
12691 2713 : if (NCDFGetAttr(nCdfId, v, "bounds", &pszTemp) == CE_None &&
12692 2713 : pszTemp != nullptr && !EQUAL(pszTemp, ""))
12693 17 : papszTokens = CSLAddString(papszTokens, pszTemp);
12694 2713 : CPLFree(pszTemp);
12695 4001 : for (int i = 0; papszTokens != nullptr && papszTokens[i] != nullptr;
12696 : i++)
12697 : {
12698 1288 : char *pszVarFullName = nullptr;
12699 1288 : if (NCDFResolveVarFullName(nCdfId, papszTokens[i],
12700 1288 : &pszVarFullName) == CE_None)
12701 1262 : *ppapszVars = CSLAddString(*ppapszVars, pszVarFullName);
12702 1288 : CPLFree(pszVarFullName);
12703 : }
12704 2713 : CSLDestroy(papszTokens);
12705 : }
12706 :
12707 : // Recurse on sub-groups.
12708 : int nSubGroups;
12709 531 : int *panSubGroupIds = nullptr;
12710 531 : NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
12711 566 : for (int i = 0; i < nSubGroups; i++)
12712 : {
12713 35 : NCDFGetCoordAndBoundVarFullNames(panSubGroupIds[i], ppapszVars);
12714 : }
12715 531 : CPLFree(panSubGroupIds);
12716 :
12717 531 : return CE_None;
12718 : }
12719 :
12720 : // Check if give type is user defined
12721 1537 : bool NCDFIsUserDefinedType(int /*ncid*/, int type)
12722 : {
12723 1537 : return type >= NC_FIRSTUSERTYPEID;
12724 : }
12725 :
12726 577 : char **NCDFTokenizeCoordinatesAttribute(const char *pszCoordinates)
12727 : {
12728 : // CF conventions use space as the separator for variable names in the
12729 : // coordinates attribute, but some products such as
12730 : // https://sentinel.esa.int/web/sentinel/technical-guides/sentinel-3-synergy/products-algorithms/level-2-aod-algorithms-and-products/level-2-aod-products-description
12731 : // use comma.
12732 577 : return CSLTokenizeString2(pszCoordinates, ", ", 0);
12733 : }
|