Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: netCDF read/write Driver
4 : * Purpose: GDAL bindings over netCDF library.
5 : * Author: Frank Warmerdam, warmerdam@pobox.com
6 : * Even Rouault <even.rouault at spatialys.com>
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2004, Frank Warmerdam
10 : * Copyright (c) 2007-2016, Even Rouault <even.rouault at spatialys.com>
11 : * Copyright (c) 2010, Kyle Shannon <kyle at pobox dot com>
12 : * Copyright (c) 2021, CLS
13 : *
14 : * SPDX-License-Identifier: MIT
15 : ****************************************************************************/
16 :
17 : #include "cpl_port.h"
18 :
19 : #include <array>
20 : #include <cassert>
21 : #include <cctype>
22 : #include <cerrno>
23 : #include <climits>
24 : #include <cmath>
25 : #include <cstdio>
26 : #include <cstdlib>
27 : #include <cstring>
28 : #include <ctime>
29 : #include <algorithm>
30 : #include <limits>
31 : #include <map>
32 : #include <set>
33 : #include <queue>
34 : #include <string>
35 : #include <tuple>
36 : #include <utility>
37 : #include <vector>
38 :
39 : // Must be included after standard includes, otherwise VS2015 fails when
40 : // including <ctime>
41 : #include "netcdfdataset.h"
42 : #include "netcdfdrivercore.h"
43 : #include "netcdfsg.h"
44 : #include "netcdfuffd.h"
45 :
46 : #include "netcdf_mem.h"
47 :
48 : #include "cpl_conv.h"
49 : #include "cpl_error.h"
50 : #include "cpl_json.h"
51 : #include "cpl_minixml.h"
52 : #include "cpl_multiproc.h"
53 : #include "cpl_progress.h"
54 : #include "cpl_time.h"
55 : #include "gdal.h"
56 : #include "gdal_frmts.h"
57 : #include "gdal_priv_templates.hpp"
58 : #include "ogr_core.h"
59 : #include "ogr_srs_api.h"
60 :
61 : // netCDF 4.8 switched to expecting filenames in UTF-8 on Windows
62 : // But with netCDF 4.9 and https://github.com/Unidata/netcdf-c/pull/2277/,
63 : // this is apparently back to expecting filenames in current codepage...
64 : // Detect netCDF 4.8 with NC_ENCZARR
65 : // Detect netCDF 4.9 with NC_NOATTCREORD
66 : #if defined(NC_ENCZARR) && !defined(NC_NOATTCREORD)
67 : #define NETCDF_USES_UTF8
68 : #endif
69 :
70 : // Internal function declarations.
71 :
72 : static bool NCDFIsGDALVersionGTE(const char *pszVersion, int nTarget);
73 :
74 : static void
75 : NCDFAddGDALHistory(int fpImage, const char *pszFilename, bool bWriteGDALVersion,
76 : bool bWriteGDALHistory, const char *pszOldHist,
77 : const char *pszFunctionName,
78 : const char *pszCFVersion = GDAL_DEFAULT_NCDF_CONVENTIONS);
79 :
80 : static void NCDFAddHistory(int fpImage, const char *pszAddHist,
81 : const char *pszOldHist);
82 :
83 : static CPLErr NCDFSafeStrcat(char **ppszDest, const char *pszSrc,
84 : size_t *nDestSize);
85 :
86 : // Var / attribute helper functions.
87 : static CPLErr NCDFPutAttr(int nCdfId, int nVarId, const char *pszAttrName,
88 : const char *pszValue);
89 :
90 : // Replace this where used.
91 : static CPLErr NCDFGet1DVar(int nCdfId, int nVarId, char **pszValue);
92 : static CPLErr NCDFPut1DVar(int nCdfId, int nVarId, const char *pszValue);
93 :
94 : // Replace this where used.
95 : static char **NCDFTokenizeArray(const char *pszValue);
96 : static void CopyMetadata(GDALDataset *poSrcDS, GDALRasterBand *poSrcBand,
97 : GDALRasterBand *poDstBand, int fpImage, int CDFVarID,
98 : const char *pszMatchPrefix = nullptr);
99 :
100 : // NetCDF-4 groups helper functions.
101 : // They all work also for NetCDF-3 files which are considered as
102 : // NetCDF-4 file with only one group.
103 : static CPLErr NCDFOpenSubDataset(int nCdfId, const char *pszSubdatasetName,
104 : int *pnGroupId, int *pnVarId);
105 : static CPLErr NCDFGetVisibleDims(int nGroupId, int *pnDims, int **ppanDimIds);
106 : static CPLErr NCDFGetSubGroups(int nGroupId, int *pnSubGroups,
107 : int **ppanSubGroupIds);
108 : static CPLErr NCDFGetGroupFullName(int nGroupId, char **ppszFullName,
109 : bool bNC3Compat = true);
110 : static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId, char **ppszFullName,
111 : bool bNC3Compat = true);
112 : static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId);
113 :
114 : static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,
115 : char **ppszFullName,
116 : bool bMandatory = false);
117 : static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,
118 : const char *pszAtt, int *pnAtt,
119 : bool bMandatory = false);
120 : static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId, char ***ppapszVars);
121 :
122 : // Uncomment this for more debug output.
123 : // #define NCDF_DEBUG 1
124 :
125 : CPLMutex *hNCMutex = nullptr;
126 :
127 : // Workaround https://github.com/OSGeo/gdal/issues/6253
128 : // Having 2 netCDF handles on the same file doesn't work in a multi-threaded
129 : // way. Apparently having the same handle works better (this is OK since
130 : // we have a global mutex on the netCDF library)
131 : static std::map<std::string, int> goMapNameToNetCDFId;
132 : static std::map<int, std::pair<std::string, int>> goMapNetCDFIdToKeyAndCount;
133 :
134 646 : int GDAL_nc_open(const char *pszFilename, int nMode, int *pID)
135 : {
136 1292 : std::string osKey(pszFilename);
137 646 : osKey += "#####";
138 646 : osKey += std::to_string(nMode);
139 646 : auto oIter = goMapNameToNetCDFId.find(osKey);
140 646 : if (oIter == goMapNameToNetCDFId.end())
141 : {
142 601 : int ret = nc_open(pszFilename, nMode, pID);
143 601 : if (ret != NC_NOERR)
144 3 : return ret;
145 598 : goMapNameToNetCDFId[osKey] = *pID;
146 598 : goMapNetCDFIdToKeyAndCount[*pID] =
147 1196 : std::pair<std::string, int>(osKey, 1);
148 598 : return ret;
149 : }
150 : else
151 : {
152 45 : *pID = oIter->second;
153 45 : goMapNetCDFIdToKeyAndCount[oIter->second].second++;
154 45 : return NC_NOERR;
155 : }
156 : }
157 :
158 898 : int GDAL_nc_close(int cdfid)
159 : {
160 898 : int ret = NC_NOERR;
161 898 : auto oIter = goMapNetCDFIdToKeyAndCount.find(cdfid);
162 898 : if (oIter != goMapNetCDFIdToKeyAndCount.end())
163 : {
164 643 : if (--oIter->second.second == 0)
165 : {
166 598 : ret = nc_close(cdfid);
167 598 : goMapNameToNetCDFId.erase(oIter->second.first);
168 598 : goMapNetCDFIdToKeyAndCount.erase(oIter);
169 : }
170 : }
171 : else
172 : {
173 : // we can go here if file opened with nc_open_mem() or nc_create()
174 255 : ret = nc_close(cdfid);
175 : }
176 898 : return ret;
177 : }
178 :
179 : /************************************************************************/
180 : /* ==================================================================== */
181 : /* netCDFRasterBand */
182 : /* ==================================================================== */
183 : /************************************************************************/
184 :
185 : class netCDFRasterBand final : public GDALPamRasterBand
186 : {
187 : friend class netCDFDataset;
188 :
189 : nc_type nc_datatype;
190 : int cdfid;
191 : int nZId;
192 : int nZDim;
193 : int nLevel;
194 : int nBandXPos;
195 : int nBandYPos;
196 : int *panBandZPos;
197 : int *panBandZLev;
198 : bool m_bNoDataSet = false;
199 : double m_dfNoDataValue = 0;
200 : bool m_bNoDataSetAsInt64 = false;
201 : int64_t m_nNodataValueInt64 = 0;
202 : bool m_bNoDataSetAsUInt64 = false;
203 : uint64_t m_nNodataValueUInt64 = 0;
204 : bool bValidRangeValid = false;
205 : double adfValidRange[2]{0, 0};
206 : bool m_bHaveScale = false;
207 : bool m_bHaveOffset = false;
208 : double m_dfScale = 1;
209 : double m_dfOffset = 0;
210 : CPLString m_osUnitType{};
211 : bool bSignedData;
212 : bool bCheckLongitude;
213 : bool m_bCreateMetadataFromOtherVarsDone = false;
214 :
215 : void CreateMetadataFromAttributes();
216 : void CreateMetadataFromOtherVars();
217 :
218 : template <class T>
219 : void CheckData(void *pImage, void *pImageNC, size_t nTmpBlockXSize,
220 : size_t nTmpBlockYSize, bool bCheckIsNan = false);
221 : template <class T>
222 : void CheckDataCpx(void *pImage, void *pImageNC, size_t nTmpBlockXSize,
223 : size_t nTmpBlockYSize, bool bCheckIsNan = false);
224 : void SetBlockSize();
225 :
226 : bool FetchNetcdfChunk(size_t xstart, size_t ystart, void *pImage);
227 :
228 : void SetNoDataValueNoUpdate(double dfNoData);
229 : void SetNoDataValueNoUpdate(int64_t nNoData);
230 : void SetNoDataValueNoUpdate(uint64_t nNoData);
231 :
232 : void SetOffsetNoUpdate(double dfVal);
233 : void SetScaleNoUpdate(double dfVal);
234 : void SetUnitTypeNoUpdate(const char *pszNewValue);
235 :
236 : protected:
237 : CPLXMLNode *SerializeToXML(const char *pszUnused) override;
238 :
239 : public:
240 : struct CONSTRUCTOR_OPEN
241 : {
242 : };
243 :
244 : struct CONSTRUCTOR_CREATE
245 : {
246 : };
247 :
248 : netCDFRasterBand(const CONSTRUCTOR_OPEN &, netCDFDataset *poDS,
249 : int nGroupId, int nZId, int nZDim, int nLevel,
250 : const int *panBandZLen, const int *panBandPos, int nBand);
251 : netCDFRasterBand(const CONSTRUCTOR_CREATE &, netCDFDataset *poDS,
252 : GDALDataType eType, int nBand, bool bSigned = true,
253 : const char *pszBandName = nullptr,
254 : const char *pszLongName = nullptr, int nZId = -1,
255 : int nZDim = 2, int nLevel = 0,
256 : const int *panBandZLev = nullptr,
257 : const int *panBandZPos = nullptr,
258 : const int *paDimIds = nullptr);
259 : virtual ~netCDFRasterBand();
260 :
261 : virtual double GetNoDataValue(int *) override;
262 : virtual int64_t GetNoDataValueAsInt64(int *pbSuccess = nullptr) override;
263 : virtual uint64_t GetNoDataValueAsUInt64(int *pbSuccess = nullptr) override;
264 : virtual CPLErr SetNoDataValue(double) override;
265 : virtual CPLErr SetNoDataValueAsInt64(int64_t nNoData) override;
266 : virtual CPLErr SetNoDataValueAsUInt64(uint64_t nNoData) override;
267 : // virtual CPLErr DeleteNoDataValue();
268 : virtual double GetOffset(int *) override;
269 : virtual CPLErr SetOffset(double) override;
270 : virtual double GetScale(int *) override;
271 : virtual CPLErr SetScale(double) override;
272 : virtual const char *GetUnitType() override;
273 : virtual CPLErr SetUnitType(const char *) override;
274 : virtual CPLErr IReadBlock(int, int, void *) override;
275 : virtual CPLErr IWriteBlock(int, int, void *) override;
276 :
277 : char **GetMetadata(const char *pszDomain = "") override;
278 : const char *GetMetadataItem(const char *pszName,
279 : const char *pszDomain = "") override;
280 :
281 : virtual CPLErr SetMetadataItem(const char *pszName, const char *pszValue,
282 : const char *pszDomain = "") override;
283 : virtual CPLErr SetMetadata(char **papszMD,
284 : const char *pszDomain = "") override;
285 : };
286 :
287 : /************************************************************************/
288 : /* netCDFRasterBand() */
289 : /************************************************************************/
290 :
291 457 : netCDFRasterBand::netCDFRasterBand(const netCDFRasterBand::CONSTRUCTOR_OPEN &,
292 : netCDFDataset *poNCDFDS, int nGroupId,
293 : int nZIdIn, int nZDimIn, int nLevelIn,
294 : const int *panBandZLevIn,
295 457 : const int *panBandZPosIn, int nBandIn)
296 : : nc_datatype(NC_NAT), cdfid(nGroupId), nZId(nZIdIn), nZDim(nZDimIn),
297 457 : nLevel(nLevelIn), nBandXPos(panBandZPosIn[0]),
298 457 : nBandYPos(nZDim == 1 ? -1 : panBandZPosIn[1]), panBandZPos(nullptr),
299 : panBandZLev(nullptr),
300 : bSignedData(true), // Default signed, except for Byte.
301 914 : bCheckLongitude(false)
302 : {
303 457 : poDS = poNCDFDS;
304 457 : nBand = nBandIn;
305 :
306 : // Take care of all other dimensions.
307 457 : if (nZDim > 2)
308 : {
309 159 : panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
310 159 : panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
311 :
312 443 : for (int i = 0; i < nZDim - 2; i++)
313 : {
314 284 : panBandZPos[i] = panBandZPosIn[i + 2];
315 284 : panBandZLev[i] = panBandZLevIn[i];
316 : }
317 : }
318 :
319 457 : nRasterXSize = poDS->GetRasterXSize();
320 457 : nRasterYSize = poDS->GetRasterYSize();
321 457 : nBlockXSize = poDS->GetRasterXSize();
322 457 : nBlockYSize = 1;
323 :
324 : // Get the type of the "z" variable, our target raster array.
325 457 : if (nc_inq_var(cdfid, nZId, nullptr, &nc_datatype, nullptr, nullptr,
326 457 : nullptr) != NC_NOERR)
327 : {
328 0 : CPLError(CE_Failure, CPLE_AppDefined, "Error in nc_var_inq() on 'z'.");
329 0 : return;
330 : }
331 :
332 457 : if (NCDFIsUserDefinedType(cdfid, nc_datatype))
333 : {
334 : // First enquire and check that the number of fields is 2
335 : size_t nfields, compoundsize;
336 5 : if (nc_inq_compound(cdfid, nc_datatype, nullptr, &compoundsize,
337 5 : &nfields) != NC_NOERR)
338 : {
339 0 : CPLError(CE_Failure, CPLE_AppDefined,
340 : "Error in nc_inq_compound() on 'z'.");
341 0 : return;
342 : }
343 :
344 5 : if (nfields != 2)
345 : {
346 0 : CPLError(CE_Failure, CPLE_AppDefined,
347 : "Unsupported data type encountered in nc_inq_compound() "
348 : "on 'z'.");
349 0 : return;
350 : }
351 :
352 : // Now check that that two types are the same in the struct.
353 : nc_type field_type1, field_type2;
354 : int field_dims1, field_dims2;
355 5 : if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
356 : &field_type1, &field_dims1,
357 5 : nullptr) != NC_NOERR)
358 : {
359 0 : CPLError(
360 : CE_Failure, CPLE_AppDefined,
361 : "Error in querying Field 1 in nc_inq_compound_field() on 'z'.");
362 0 : return;
363 : }
364 :
365 5 : if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
366 : &field_type2, &field_dims2,
367 5 : nullptr) != NC_NOERR)
368 : {
369 0 : CPLError(
370 : CE_Failure, CPLE_AppDefined,
371 : "Error in querying Field 2 in nc_inq_compound_field() on 'z'.");
372 0 : return;
373 : }
374 :
375 5 : if ((field_type1 != field_type2) || (field_dims1 != field_dims2) ||
376 5 : (field_dims1 != 0))
377 : {
378 0 : CPLError(CE_Failure, CPLE_AppDefined,
379 : "Error in interpreting compound data type on 'z'.");
380 0 : return;
381 : }
382 :
383 5 : if (field_type1 == NC_SHORT)
384 0 : eDataType = GDT_CInt16;
385 5 : else if (field_type1 == NC_INT)
386 0 : eDataType = GDT_CInt32;
387 5 : else if (field_type1 == NC_FLOAT)
388 4 : eDataType = GDT_CFloat32;
389 1 : else if (field_type1 == NC_DOUBLE)
390 1 : eDataType = GDT_CFloat64;
391 : else
392 : {
393 0 : CPLError(CE_Failure, CPLE_AppDefined,
394 : "Unsupported netCDF compound data type encountered.");
395 0 : return;
396 : }
397 : }
398 : else
399 : {
400 452 : if (nc_datatype == NC_BYTE)
401 140 : eDataType = GDT_Byte;
402 312 : else if (nc_datatype == NC_CHAR)
403 0 : eDataType = GDT_Byte;
404 312 : else if (nc_datatype == NC_SHORT)
405 41 : eDataType = GDT_Int16;
406 271 : else if (nc_datatype == NC_INT)
407 89 : eDataType = GDT_Int32;
408 182 : else if (nc_datatype == NC_FLOAT)
409 106 : eDataType = GDT_Float32;
410 76 : else if (nc_datatype == NC_DOUBLE)
411 40 : eDataType = GDT_Float64;
412 36 : else if (nc_datatype == NC_UBYTE)
413 14 : eDataType = GDT_Byte;
414 22 : else if (nc_datatype == NC_USHORT)
415 4 : eDataType = GDT_UInt16;
416 18 : else if (nc_datatype == NC_UINT)
417 3 : eDataType = GDT_UInt32;
418 15 : else if (nc_datatype == NC_INT64)
419 8 : eDataType = GDT_Int64;
420 7 : else if (nc_datatype == NC_UINT64)
421 7 : eDataType = GDT_UInt64;
422 : else
423 : {
424 0 : if (nBand == 1)
425 0 : CPLError(CE_Warning, CPLE_AppDefined,
426 : "Unsupported netCDF datatype (%d), treat as Float32.",
427 0 : static_cast<int>(nc_datatype));
428 0 : eDataType = GDT_Float32;
429 0 : nc_datatype = NC_FLOAT;
430 : }
431 : }
432 :
433 : // Find and set No Data for this variable.
434 457 : nc_type atttype = NC_NAT;
435 457 : size_t attlen = 0;
436 457 : const char *pszNoValueName = nullptr;
437 :
438 : // Find attribute name, either _FillValue or missing_value.
439 457 : int status = nc_inq_att(cdfid, nZId, NCDF_FillValue, &atttype, &attlen);
440 457 : if (status == NC_NOERR)
441 : {
442 240 : pszNoValueName = NCDF_FillValue;
443 : }
444 : else
445 : {
446 217 : status = nc_inq_att(cdfid, nZId, "missing_value", &atttype, &attlen);
447 217 : if (status == NC_NOERR)
448 : {
449 12 : pszNoValueName = "missing_value";
450 : }
451 : }
452 :
453 : // Fetch missing value.
454 457 : double dfNoData = 0.0;
455 457 : bool bGotNoData = false;
456 457 : int64_t nNoDataAsInt64 = 0;
457 457 : bool bGotNoDataAsInt64 = false;
458 457 : uint64_t nNoDataAsUInt64 = 0;
459 457 : bool bGotNoDataAsUInt64 = false;
460 457 : if (status == NC_NOERR)
461 : {
462 252 : nc_type nAttrType = NC_NAT;
463 252 : size_t nAttrLen = 0;
464 252 : status = nc_inq_att(cdfid, nZId, pszNoValueName, &nAttrType, &nAttrLen);
465 252 : if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_INT64)
466 : {
467 : long long v;
468 7 : nc_get_att_longlong(cdfid, nZId, pszNoValueName, &v);
469 7 : bGotNoData = true;
470 7 : bGotNoDataAsInt64 = true;
471 7 : nNoDataAsInt64 = static_cast<int64_t>(v);
472 : }
473 245 : else if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_UINT64)
474 : {
475 : unsigned long long v;
476 7 : nc_get_att_ulonglong(cdfid, nZId, pszNoValueName, &v);
477 7 : bGotNoData = true;
478 7 : bGotNoDataAsUInt64 = true;
479 7 : nNoDataAsUInt64 = static_cast<uint64_t>(v);
480 : }
481 238 : else if (NCDFGetAttr(cdfid, nZId, pszNoValueName, &dfNoData) == CE_None)
482 : {
483 237 : bGotNoData = true;
484 : }
485 : }
486 :
487 : // If NoData was not found, use the default value, but for non-Byte types
488 : // as it is not recommended:
489 : // https://www.unidata.ucar.edu/software/netcdf/docs/attribute_conventions.html
490 457 : nc_type vartype = NC_NAT;
491 457 : if (!bGotNoData)
492 : {
493 206 : nc_inq_vartype(cdfid, nZId, &vartype);
494 206 : if (vartype == NC_INT64)
495 : {
496 : nNoDataAsInt64 =
497 1 : NCDFGetDefaultNoDataValueAsInt64(cdfid, nZId, bGotNoData);
498 1 : bGotNoDataAsInt64 = bGotNoData;
499 : }
500 205 : else if (vartype == NC_UINT64)
501 : {
502 : nNoDataAsUInt64 =
503 0 : NCDFGetDefaultNoDataValueAsUInt64(cdfid, nZId, bGotNoData);
504 0 : bGotNoDataAsUInt64 = bGotNoData;
505 : }
506 205 : else if (vartype != NC_CHAR && vartype != NC_BYTE &&
507 88 : vartype != NC_UBYTE)
508 : {
509 80 : dfNoData =
510 80 : NCDFGetDefaultNoDataValue(cdfid, nZId, vartype, bGotNoData);
511 80 : if (bGotNoData)
512 : {
513 69 : CPLDebug("GDAL_netCDF",
514 : "did not get nodata value for variable #%d, using "
515 : "default %f",
516 : nZId, dfNoData);
517 : }
518 : }
519 : }
520 :
521 457 : bool bHasUnderscoreUnsignedAttr = false;
522 457 : bool bUnderscoreUnsignedAttrVal = false;
523 : {
524 457 : char *pszTemp = nullptr;
525 457 : if (NCDFGetAttr(cdfid, nZId, "_Unsigned", &pszTemp) == CE_None)
526 : {
527 132 : if (EQUAL(pszTemp, "true"))
528 : {
529 124 : bHasUnderscoreUnsignedAttr = true;
530 124 : bUnderscoreUnsignedAttrVal = true;
531 : }
532 8 : else if (EQUAL(pszTemp, "false"))
533 : {
534 8 : bHasUnderscoreUnsignedAttr = true;
535 8 : bUnderscoreUnsignedAttrVal = false;
536 : }
537 132 : CPLFree(pszTemp);
538 : }
539 : }
540 :
541 : // Look for valid_range or valid_min/valid_max.
542 :
543 : // First look for valid_range.
544 457 : if (CPLFetchBool(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE", true))
545 : {
546 455 : char *pszValidRange = nullptr;
547 455 : if (NCDFGetAttr(cdfid, nZId, "valid_range", &pszValidRange) ==
548 125 : CE_None &&
549 580 : pszValidRange[0] == '{' &&
550 125 : pszValidRange[strlen(pszValidRange) - 1] == '}')
551 : {
552 : const std::string osValidRange =
553 375 : std::string(pszValidRange).substr(1, strlen(pszValidRange) - 2);
554 : const CPLStringList aosValidRange(
555 250 : CSLTokenizeString2(osValidRange.c_str(), ",", 0));
556 125 : if (aosValidRange.size() == 2 &&
557 250 : CPLGetValueType(aosValidRange[0]) != CPL_VALUE_STRING &&
558 125 : CPLGetValueType(aosValidRange[1]) != CPL_VALUE_STRING)
559 : {
560 125 : bValidRangeValid = true;
561 125 : adfValidRange[0] = CPLAtof(aosValidRange[0]);
562 125 : adfValidRange[1] = CPLAtof(aosValidRange[1]);
563 : }
564 : }
565 455 : CPLFree(pszValidRange);
566 :
567 : // If not found look for valid_min and valid_max.
568 455 : if (!bValidRangeValid)
569 : {
570 330 : double dfMin = 0;
571 330 : double dfMax = 0;
572 345 : if (NCDFGetAttr(cdfid, nZId, "valid_min", &dfMin) == CE_None &&
573 15 : NCDFGetAttr(cdfid, nZId, "valid_max", &dfMax) == CE_None)
574 : {
575 8 : adfValidRange[0] = dfMin;
576 8 : adfValidRange[1] = dfMax;
577 8 : bValidRangeValid = true;
578 : }
579 : }
580 :
581 455 : if (bValidRangeValid &&
582 133 : (adfValidRange[0] < 0 || adfValidRange[1] < 0) &&
583 17 : nc_datatype == NC_SHORT && bHasUnderscoreUnsignedAttr &&
584 : bUnderscoreUnsignedAttrVal)
585 : {
586 2 : if (adfValidRange[0] < 0)
587 0 : adfValidRange[0] += 65536;
588 2 : if (adfValidRange[1] < 0)
589 2 : adfValidRange[1] += 65536;
590 2 : if (adfValidRange[0] <= adfValidRange[1])
591 : {
592 : // Updating metadata item
593 2 : GDALPamRasterBand::SetMetadataItem(
594 : "valid_range",
595 2 : CPLSPrintf("{%d,%d}", static_cast<int>(adfValidRange[0]),
596 2 : static_cast<int>(adfValidRange[1])));
597 : }
598 : }
599 :
600 455 : if (bValidRangeValid && adfValidRange[0] > adfValidRange[1])
601 : {
602 0 : CPLError(CE_Warning, CPLE_AppDefined,
603 : "netCDFDataset::valid_range: min > max:\n"
604 : " min: %lf\n max: %lf\n",
605 : adfValidRange[0], adfValidRange[1]);
606 0 : bValidRangeValid = false;
607 0 : adfValidRange[0] = 0.0;
608 0 : adfValidRange[1] = 0.0;
609 : }
610 : }
611 :
612 : // Special For Byte Bands: check for signed/unsigned byte.
613 457 : if (nc_datatype == NC_BYTE)
614 : {
615 : // netcdf uses signed byte by default, but GDAL uses unsigned by default
616 : // This may cause unexpected results, but is needed for back-compat.
617 140 : if (poNCDFDS->bIsGdalFile)
618 119 : bSignedData = false;
619 : else
620 21 : bSignedData = true;
621 :
622 : // For NC4 format NC_BYTE is (normally) signed, NC_UBYTE is unsigned.
623 : // But in case a NC3 file was converted automatically and has hints
624 : // that it is unsigned, take them into account
625 140 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
626 : {
627 3 : bSignedData = true;
628 : }
629 :
630 : // If we got valid_range, test for signed/unsigned range.
631 : // https://docs.unidata.ucar.edu/netcdf-c/current/attribute_conventions.html
632 140 : if (bValidRangeValid)
633 : {
634 : // If we got valid_range={0,255}, treat as unsigned.
635 121 : if (adfValidRange[0] == 0 && adfValidRange[1] == 255)
636 : {
637 113 : bSignedData = false;
638 : // Reset valid_range.
639 113 : bValidRangeValid = false;
640 : }
641 : // If we got valid_range={-128,127}, treat as signed.
642 8 : else if (adfValidRange[0] == -128 && adfValidRange[1] == 127)
643 : {
644 8 : bSignedData = true;
645 : // Reset valid_range.
646 8 : bValidRangeValid = false;
647 : }
648 : }
649 : // Else test for _Unsigned.
650 : // https://docs.unidata.ucar.edu/nug/current/best_practices.html
651 : else
652 : {
653 19 : if (bHasUnderscoreUnsignedAttr)
654 7 : bSignedData = !bUnderscoreUnsignedAttrVal;
655 : }
656 :
657 140 : if (bSignedData)
658 : {
659 20 : eDataType = GDT_Int8;
660 : }
661 120 : else if (dfNoData < 0)
662 : {
663 : // Fix nodata value as it was stored signed.
664 6 : dfNoData += 256;
665 6 : if (pszNoValueName)
666 : {
667 : // Updating metadata item
668 6 : GDALPamRasterBand::SetMetadataItem(
669 : pszNoValueName,
670 : CPLSPrintf("%d", static_cast<int>(dfNoData)));
671 : }
672 : }
673 : }
674 317 : else if (nc_datatype == NC_SHORT)
675 : {
676 41 : if (bHasUnderscoreUnsignedAttr)
677 : {
678 4 : bSignedData = !bUnderscoreUnsignedAttrVal;
679 4 : if (!bSignedData)
680 4 : eDataType = GDT_UInt16;
681 : }
682 :
683 : // Fix nodata value as it was stored signed.
684 41 : if (!bSignedData && dfNoData < 0)
685 : {
686 4 : dfNoData += 65536;
687 4 : if (pszNoValueName)
688 : {
689 : // Updating metadata item
690 4 : GDALPamRasterBand::SetMetadataItem(
691 : pszNoValueName,
692 : CPLSPrintf("%d", static_cast<int>(dfNoData)));
693 : }
694 : }
695 : }
696 :
697 276 : else if (nc_datatype == NC_UBYTE || nc_datatype == NC_USHORT ||
698 258 : nc_datatype == NC_UINT || nc_datatype == NC_UINT64)
699 : {
700 28 : bSignedData = false;
701 : }
702 :
703 457 : CPLDebug("GDAL_netCDF", "netcdf type=%d gdal type=%d signedByte=%d",
704 457 : nc_datatype, eDataType, static_cast<int>(bSignedData));
705 :
706 457 : if (bGotNoData)
707 : {
708 : // Set nodata value.
709 321 : if (bGotNoDataAsInt64)
710 : {
711 8 : if (eDataType == GDT_Int64)
712 : {
713 8 : SetNoDataValueNoUpdate(nNoDataAsInt64);
714 : }
715 0 : else if (eDataType == GDT_UInt64 && nNoDataAsInt64 >= 0)
716 : {
717 0 : SetNoDataValueNoUpdate(static_cast<uint64_t>(nNoDataAsInt64));
718 : }
719 : else
720 : {
721 0 : SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsInt64));
722 : }
723 : }
724 313 : else if (bGotNoDataAsUInt64)
725 : {
726 7 : if (eDataType == GDT_UInt64)
727 : {
728 7 : SetNoDataValueNoUpdate(nNoDataAsUInt64);
729 : }
730 0 : else if (eDataType == GDT_Int64 &&
731 : nNoDataAsUInt64 <=
732 0 : static_cast<uint64_t>(
733 0 : std::numeric_limits<int64_t>::max()))
734 : {
735 0 : SetNoDataValueNoUpdate(static_cast<int64_t>(nNoDataAsUInt64));
736 : }
737 : else
738 : {
739 0 : SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsUInt64));
740 : }
741 : }
742 : else
743 : {
744 : #ifdef NCDF_DEBUG
745 : CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) read", dfNoData);
746 : #endif
747 306 : if (eDataType == GDT_Int64 && GDALIsValueExactAs<int64_t>(dfNoData))
748 : {
749 0 : SetNoDataValueNoUpdate(static_cast<int64_t>(dfNoData));
750 : }
751 306 : else if (eDataType == GDT_UInt64 &&
752 0 : GDALIsValueExactAs<uint64_t>(dfNoData))
753 : {
754 0 : SetNoDataValueNoUpdate(static_cast<uint64_t>(dfNoData));
755 : }
756 : else
757 : {
758 306 : SetNoDataValueNoUpdate(dfNoData);
759 : }
760 : }
761 : }
762 :
763 457 : CreateMetadataFromAttributes();
764 :
765 : // Attempt to fetch the scale_factor and add_offset attributes for the
766 : // variable and set them. If these values are not available, set
767 : // offset to 0 and scale to 1.
768 457 : if (nc_inq_attid(cdfid, nZId, CF_ADD_OFFSET, nullptr) == NC_NOERR)
769 : {
770 16 : double dfOffset = 0;
771 16 : status = nc_get_att_double(cdfid, nZId, CF_ADD_OFFSET, &dfOffset);
772 16 : CPLDebug("GDAL_netCDF", "got add_offset=%.16g, status=%d", dfOffset,
773 : status);
774 16 : SetOffsetNoUpdate(dfOffset);
775 : }
776 :
777 457 : bool bHasScale = false;
778 457 : if (nc_inq_attid(cdfid, nZId, CF_SCALE_FACTOR, nullptr) == NC_NOERR)
779 : {
780 20 : bHasScale = true;
781 20 : double dfScale = 1;
782 20 : status = nc_get_att_double(cdfid, nZId, CF_SCALE_FACTOR, &dfScale);
783 20 : CPLDebug("GDAL_netCDF", "got scale_factor=%.16g, status=%d", dfScale,
784 : status);
785 20 : SetScaleNoUpdate(dfScale);
786 : }
787 :
788 12 : if (bValidRangeValid && GDALDataTypeIsInteger(eDataType) &&
789 4 : eDataType != GDT_Int64 && eDataType != GDT_UInt64 &&
790 4 : (std::fabs(std::round(adfValidRange[0]) - adfValidRange[0]) > 1e-5 ||
791 469 : std::fabs(std::round(adfValidRange[1]) - adfValidRange[1]) > 1e-5) &&
792 1 : CSLFetchNameValue(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE") ==
793 : nullptr)
794 : {
795 1 : CPLError(CE_Warning, CPLE_AppDefined,
796 : "validity range = %f, %f contains floating-point values, "
797 : "whereas data type is integer. valid_range is thus likely "
798 : "wrong%s. Ignoring it.",
799 : adfValidRange[0], adfValidRange[1],
800 : bHasScale ? " (likely scaled using scale_factor/add_factor "
801 : "whereas it should be using the packed data type)"
802 : : "");
803 1 : bValidRangeValid = false;
804 1 : adfValidRange[0] = 0.0;
805 1 : adfValidRange[1] = 0.0;
806 : }
807 :
808 : // Should we check for longitude values > 360?
809 457 : bCheckLongitude =
810 914 : CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")) &&
811 457 : NCDFIsVarLongitude(cdfid, nZId, nullptr);
812 :
813 : // Attempt to fetch the units attribute for the variable and set it.
814 457 : SetUnitTypeNoUpdate(netCDFRasterBand::GetMetadataItem(CF_UNITS));
815 :
816 457 : SetBlockSize();
817 : }
818 :
819 636 : void netCDFRasterBand::SetBlockSize()
820 : {
821 : // Check for variable chunking (netcdf-4 only).
822 : // GDAL block size should be set to hdf5 chunk size.
823 636 : int nTmpFormat = 0;
824 636 : int status = nc_inq_format(cdfid, &nTmpFormat);
825 636 : NetCDFFormatEnum eTmpFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
826 636 : if ((status == NC_NOERR) &&
827 544 : (eTmpFormat == NCDF_FORMAT_NC4 || eTmpFormat == NCDF_FORMAT_NC4C))
828 : {
829 106 : size_t chunksize[MAX_NC_DIMS] = {};
830 : // Check for chunksize and set it as the blocksize (optimizes read).
831 106 : status = nc_inq_var_chunking(cdfid, nZId, &nTmpFormat, chunksize);
832 106 : if ((status == NC_NOERR) && (nTmpFormat == NC_CHUNKED))
833 : {
834 11 : nBlockXSize = (int)chunksize[nZDim - 1];
835 11 : if (nZDim >= 2)
836 11 : nBlockYSize = (int)chunksize[nZDim - 2];
837 : else
838 0 : nBlockYSize = 1;
839 : }
840 : }
841 :
842 : // Deal with bottom-up datasets and nBlockYSize != 1.
843 636 : auto poGDS = static_cast<netCDFDataset *>(poDS);
844 636 : if (poGDS->bBottomUp && nBlockYSize != 1 && poGDS->poChunkCache == nullptr)
845 : {
846 5 : if (poGDS->eAccess == GA_ReadOnly)
847 : {
848 : // Try to cache 1 or 2 'rows' of netCDF chunks along the whole
849 : // width of the raster
850 5 : size_t nChunks =
851 5 : static_cast<size_t>(DIV_ROUND_UP(nRasterXSize, nBlockXSize));
852 5 : if ((nRasterYSize % nBlockYSize) != 0)
853 1 : nChunks *= 2;
854 : const size_t nChunkSize =
855 5 : static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
856 5 : nBlockXSize * nBlockYSize;
857 5 : constexpr size_t MAX_CACHE_SIZE = 100 * 1024 * 1024;
858 5 : nChunks = std::min(nChunks, MAX_CACHE_SIZE / nChunkSize);
859 5 : if (nChunks)
860 : {
861 5 : poGDS->poChunkCache.reset(
862 5 : new netCDFDataset::ChunkCacheType(nChunks));
863 : }
864 : }
865 : else
866 : {
867 0 : nBlockYSize = 1;
868 : }
869 : }
870 636 : }
871 :
872 : // Constructor in create mode.
873 : // If nZId and following variables are not passed, the band will have 2
874 : // dimensions.
875 : // TODO: Get metadata, missing val from band #1 if nZDim > 2.
876 179 : netCDFRasterBand::netCDFRasterBand(
877 : const netCDFRasterBand::CONSTRUCTOR_CREATE &, netCDFDataset *poNCDFDS,
878 : const GDALDataType eTypeIn, int nBandIn, bool bSigned,
879 : const char *pszBandName, const char *pszLongName, int nZIdIn, int nZDimIn,
880 : int nLevelIn, const int *panBandZLevIn, const int *panBandZPosIn,
881 179 : const int *paDimIds)
882 179 : : nc_datatype(NC_NAT), cdfid(poNCDFDS->GetCDFID()), nZId(nZIdIn),
883 : nZDim(nZDimIn), nLevel(nLevelIn), nBandXPos(1), nBandYPos(0),
884 : panBandZPos(nullptr), panBandZLev(nullptr), bSignedData(bSigned),
885 179 : bCheckLongitude(false), m_bCreateMetadataFromOtherVarsDone(true)
886 : {
887 179 : poDS = poNCDFDS;
888 179 : nBand = nBandIn;
889 :
890 179 : nRasterXSize = poDS->GetRasterXSize();
891 179 : nRasterYSize = poDS->GetRasterYSize();
892 179 : nBlockXSize = poDS->GetRasterXSize();
893 179 : nBlockYSize = 1;
894 :
895 179 : if (poDS->GetAccess() != GA_Update)
896 : {
897 0 : CPLError(CE_Failure, CPLE_NotSupported,
898 : "Dataset is not in update mode, "
899 : "wrong netCDFRasterBand constructor");
900 0 : return;
901 : }
902 :
903 : // Take care of all other dimensions.
904 179 : if (nZDim > 2 && paDimIds != nullptr)
905 : {
906 27 : nBandXPos = panBandZPosIn[0];
907 27 : nBandYPos = panBandZPosIn[1];
908 :
909 27 : panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
910 27 : panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
911 :
912 76 : for (int i = 0; i < nZDim - 2; i++)
913 : {
914 49 : panBandZPos[i] = panBandZPosIn[i + 2];
915 49 : panBandZLev[i] = panBandZLevIn[i];
916 : }
917 : }
918 :
919 : // Get the type of the "z" variable, our target raster array.
920 179 : eDataType = eTypeIn;
921 :
922 179 : switch (eDataType)
923 : {
924 74 : case GDT_Byte:
925 74 : nc_datatype = NC_BYTE;
926 : // NC_UBYTE (unsigned byte) is only available for NC4.
927 74 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
928 3 : nc_datatype = NC_UBYTE;
929 74 : break;
930 7 : case GDT_Int8:
931 7 : nc_datatype = NC_BYTE;
932 7 : break;
933 11 : case GDT_Int16:
934 11 : nc_datatype = NC_SHORT;
935 11 : break;
936 24 : case GDT_Int32:
937 24 : nc_datatype = NC_INT;
938 24 : break;
939 13 : case GDT_Float32:
940 13 : nc_datatype = NC_FLOAT;
941 13 : break;
942 8 : case GDT_Float64:
943 8 : nc_datatype = NC_DOUBLE;
944 8 : break;
945 7 : case GDT_Int64:
946 7 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
947 : {
948 7 : nc_datatype = NC_INT64;
949 : }
950 : else
951 : {
952 0 : if (nBand == 1)
953 0 : CPLError(
954 : CE_Warning, CPLE_AppDefined,
955 : "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
956 : "Int64");
957 0 : nc_datatype = NC_DOUBLE;
958 0 : eDataType = GDT_Float64;
959 : }
960 7 : break;
961 7 : case GDT_UInt64:
962 7 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
963 : {
964 7 : nc_datatype = NC_UINT64;
965 : }
966 : else
967 : {
968 0 : if (nBand == 1)
969 0 : CPLError(
970 : CE_Warning, CPLE_AppDefined,
971 : "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
972 : "UInt64");
973 0 : nc_datatype = NC_DOUBLE;
974 0 : eDataType = GDT_Float64;
975 : }
976 7 : break;
977 6 : case GDT_UInt16:
978 6 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
979 : {
980 6 : nc_datatype = NC_USHORT;
981 6 : break;
982 : }
983 : [[fallthrough]];
984 : case GDT_UInt32:
985 6 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
986 : {
987 6 : nc_datatype = NC_UINT;
988 6 : break;
989 : }
990 : [[fallthrough]];
991 : default:
992 16 : if (nBand == 1)
993 8 : CPLError(CE_Warning, CPLE_AppDefined,
994 : "Unsupported GDAL datatype (%d), treat as NC_FLOAT.",
995 8 : static_cast<int>(eDataType));
996 16 : nc_datatype = NC_FLOAT;
997 16 : eDataType = GDT_Float32;
998 16 : break;
999 : }
1000 :
1001 : // Define the variable if necessary (if nZId == -1).
1002 179 : bool bDefineVar = false;
1003 :
1004 179 : if (nZId == -1)
1005 : {
1006 157 : bDefineVar = true;
1007 :
1008 : // Make sure we are in define mode.
1009 157 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1010 :
1011 : char szTempPrivate[256 + 1];
1012 157 : const char *pszTemp = nullptr;
1013 157 : if (!pszBandName || EQUAL(pszBandName, ""))
1014 : {
1015 135 : snprintf(szTempPrivate, sizeof(szTempPrivate), "Band%d", nBand);
1016 135 : pszTemp = szTempPrivate;
1017 : }
1018 : else
1019 : {
1020 22 : pszTemp = pszBandName;
1021 : }
1022 :
1023 : int status;
1024 157 : if (nZDim > 2 && paDimIds != nullptr)
1025 : {
1026 5 : status =
1027 5 : nc_def_var(cdfid, pszTemp, nc_datatype, nZDim, paDimIds, &nZId);
1028 : }
1029 : else
1030 : {
1031 152 : int anBandDims[2] = {poNCDFDS->nYDimID, poNCDFDS->nXDimID};
1032 : status =
1033 152 : nc_def_var(cdfid, pszTemp, nc_datatype, 2, anBandDims, &nZId);
1034 : }
1035 157 : NCDF_ERR(status);
1036 157 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d) id=%d", cdfid, pszTemp,
1037 : nc_datatype, nZId);
1038 :
1039 157 : if (!pszLongName || EQUAL(pszLongName, ""))
1040 : {
1041 150 : snprintf(szTempPrivate, sizeof(szTempPrivate),
1042 : "GDAL Band Number %d", nBand);
1043 150 : pszTemp = szTempPrivate;
1044 : }
1045 : else
1046 : {
1047 7 : pszTemp = pszLongName;
1048 : }
1049 : status =
1050 157 : nc_put_att_text(cdfid, nZId, CF_LNG_NAME, strlen(pszTemp), pszTemp);
1051 157 : NCDF_ERR(status);
1052 :
1053 157 : poNCDFDS->DefVarDeflate(nZId, true);
1054 : }
1055 :
1056 : // For Byte data add signed/unsigned info.
1057 179 : if (eDataType == GDT_Byte || eDataType == GDT_Int8)
1058 : {
1059 81 : if (bDefineVar)
1060 : {
1061 : // Only add attributes if creating variable.
1062 : // For unsigned NC_BYTE (except NC4 format),
1063 : // add valid_range and _Unsigned ( defined in CF-1 and NUG ).
1064 73 : if (nc_datatype == NC_BYTE && poNCDFDS->eFormat != NCDF_FORMAT_NC4)
1065 : {
1066 70 : CPLDebug("GDAL_netCDF",
1067 : "adding valid_range attributes for Byte Band");
1068 70 : short l_adfValidRange[2] = {0, 0};
1069 : int status;
1070 70 : if (bSignedData || eDataType == GDT_Int8)
1071 : {
1072 7 : l_adfValidRange[0] = -128;
1073 7 : l_adfValidRange[1] = 127;
1074 7 : status =
1075 7 : nc_put_att_text(cdfid, nZId, "_Unsigned", 5, "false");
1076 : }
1077 : else
1078 : {
1079 63 : l_adfValidRange[0] = 0;
1080 63 : l_adfValidRange[1] = 255;
1081 : status =
1082 63 : nc_put_att_text(cdfid, nZId, "_Unsigned", 4, "true");
1083 : }
1084 70 : NCDF_ERR(status);
1085 70 : status = nc_put_att_short(cdfid, nZId, "valid_range", NC_SHORT,
1086 : 2, l_adfValidRange);
1087 70 : NCDF_ERR(status);
1088 : }
1089 : }
1090 : }
1091 :
1092 179 : if (nc_datatype != NC_BYTE && nc_datatype != NC_CHAR &&
1093 101 : nc_datatype != NC_UBYTE)
1094 : {
1095 : // Set default nodata.
1096 98 : bool bIgnored = false;
1097 : double dfNoData =
1098 98 : NCDFGetDefaultNoDataValue(cdfid, nZId, nc_datatype, bIgnored);
1099 : #ifdef NCDF_DEBUG
1100 : CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) default", dfNoData);
1101 : #endif
1102 98 : netCDFRasterBand::SetNoDataValue(dfNoData);
1103 : }
1104 :
1105 179 : SetBlockSize();
1106 : }
1107 :
1108 : /************************************************************************/
1109 : /* ~netCDFRasterBand() */
1110 : /************************************************************************/
1111 :
1112 1272 : netCDFRasterBand::~netCDFRasterBand()
1113 : {
1114 636 : netCDFRasterBand::FlushCache(true);
1115 636 : CPLFree(panBandZPos);
1116 636 : CPLFree(panBandZLev);
1117 1272 : }
1118 :
1119 : /************************************************************************/
1120 : /* GetMetadata() */
1121 : /************************************************************************/
1122 :
1123 47 : char **netCDFRasterBand::GetMetadata(const char *pszDomain)
1124 : {
1125 47 : if (!m_bCreateMetadataFromOtherVarsDone)
1126 47 : CreateMetadataFromOtherVars();
1127 47 : return GDALPamRasterBand::GetMetadata(pszDomain);
1128 : }
1129 :
1130 : /************************************************************************/
1131 : /* GetMetadataItem() */
1132 : /************************************************************************/
1133 :
1134 522 : const char *netCDFRasterBand::GetMetadataItem(const char *pszName,
1135 : const char *pszDomain)
1136 : {
1137 522 : if (!m_bCreateMetadataFromOtherVarsDone &&
1138 506 : STARTS_WITH(pszName, "NETCDF_DIM_") &&
1139 1 : (!pszDomain || pszDomain[0] == 0))
1140 1 : CreateMetadataFromOtherVars();
1141 522 : return GDALPamRasterBand::GetMetadataItem(pszName, pszDomain);
1142 : }
1143 :
1144 : /************************************************************************/
1145 : /* SetMetadataItem() */
1146 : /************************************************************************/
1147 :
1148 6 : CPLErr netCDFRasterBand::SetMetadataItem(const char *pszName,
1149 : const char *pszValue,
1150 : const char *pszDomain)
1151 : {
1152 7 : if (GetAccess() == GA_Update &&
1153 7 : (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)
1154 : {
1155 : // Same logic as in CopyMetadata()
1156 :
1157 1 : const char *const papszIgnoreBand[] = {
1158 : CF_ADD_OFFSET, CF_SCALE_FACTOR, "valid_range", "_Unsigned",
1159 : NCDF_FillValue, "coordinates", nullptr};
1160 : // Do not copy varname, stats, NETCDF_DIM_*, nodata
1161 : // and items in papszIgnoreBand.
1162 3 : if (STARTS_WITH(pszName, "NETCDF_VARNAME") ||
1163 1 : STARTS_WITH(pszName, "STATISTICS_") ||
1164 1 : STARTS_WITH(pszName, "NETCDF_DIM_") ||
1165 1 : STARTS_WITH(pszName, "missing_value") ||
1166 3 : STARTS_WITH(pszName, "_FillValue") ||
1167 1 : CSLFindString(papszIgnoreBand, pszName) != -1)
1168 : {
1169 : // do nothing
1170 : }
1171 : else
1172 : {
1173 1 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1174 :
1175 1 : if (!NCDFPutAttr(cdfid, nZId, pszName, pszValue))
1176 1 : return CE_Failure;
1177 : }
1178 : }
1179 :
1180 5 : return GDALPamRasterBand::SetMetadataItem(pszName, pszValue, pszDomain);
1181 : }
1182 :
1183 : /************************************************************************/
1184 : /* SetMetadata() */
1185 : /************************************************************************/
1186 :
1187 1 : CPLErr netCDFRasterBand::SetMetadata(char **papszMD, const char *pszDomain)
1188 : {
1189 2 : if (GetAccess() == GA_Update &&
1190 1 : (pszDomain == nullptr || pszDomain[0] == '\0'))
1191 : {
1192 : // We don't handle metadata item removal for now
1193 2 : for (const char *const *papszIter = papszMD; papszIter && *papszIter;
1194 : ++papszIter)
1195 : {
1196 1 : char *pszName = nullptr;
1197 1 : const char *pszValue = CPLParseNameValue(*papszIter, &pszName);
1198 1 : if (pszName && pszValue)
1199 1 : SetMetadataItem(pszName, pszValue);
1200 1 : CPLFree(pszName);
1201 : }
1202 : }
1203 1 : return GDALPamRasterBand::SetMetadata(papszMD, pszDomain);
1204 : }
1205 :
1206 : /************************************************************************/
1207 : /* GetOffset() */
1208 : /************************************************************************/
1209 49 : double netCDFRasterBand::GetOffset(int *pbSuccess)
1210 : {
1211 49 : if (pbSuccess != nullptr)
1212 44 : *pbSuccess = static_cast<int>(m_bHaveOffset);
1213 :
1214 49 : return m_dfOffset;
1215 : }
1216 :
1217 : /************************************************************************/
1218 : /* SetOffset() */
1219 : /************************************************************************/
1220 1 : CPLErr netCDFRasterBand::SetOffset(double dfNewOffset)
1221 : {
1222 2 : CPLMutexHolderD(&hNCMutex);
1223 :
1224 : // Write value if in update mode.
1225 1 : if (poDS->GetAccess() == GA_Update)
1226 : {
1227 : // Make sure we are in define mode.
1228 1 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1229 :
1230 1 : const int status = nc_put_att_double(cdfid, nZId, CF_ADD_OFFSET,
1231 : NC_DOUBLE, 1, &dfNewOffset);
1232 :
1233 1 : NCDF_ERR(status);
1234 1 : if (status == NC_NOERR)
1235 : {
1236 1 : SetOffsetNoUpdate(dfNewOffset);
1237 1 : return CE_None;
1238 : }
1239 :
1240 0 : return CE_Failure;
1241 : }
1242 :
1243 0 : SetOffsetNoUpdate(dfNewOffset);
1244 0 : return CE_None;
1245 : }
1246 :
1247 : /************************************************************************/
1248 : /* SetOffsetNoUpdate() */
1249 : /************************************************************************/
1250 17 : void netCDFRasterBand::SetOffsetNoUpdate(double dfVal)
1251 : {
1252 17 : m_dfOffset = dfVal;
1253 17 : m_bHaveOffset = true;
1254 17 : }
1255 :
1256 : /************************************************************************/
1257 : /* GetScale() */
1258 : /************************************************************************/
1259 49 : double netCDFRasterBand::GetScale(int *pbSuccess)
1260 : {
1261 49 : if (pbSuccess != nullptr)
1262 44 : *pbSuccess = static_cast<int>(m_bHaveScale);
1263 :
1264 49 : return m_dfScale;
1265 : }
1266 :
1267 : /************************************************************************/
1268 : /* SetScale() */
1269 : /************************************************************************/
1270 1 : CPLErr netCDFRasterBand::SetScale(double dfNewScale)
1271 : {
1272 2 : CPLMutexHolderD(&hNCMutex);
1273 :
1274 : // Write value if in update mode.
1275 1 : if (poDS->GetAccess() == GA_Update)
1276 : {
1277 : // Make sure we are in define mode.
1278 1 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1279 :
1280 1 : const int status = nc_put_att_double(cdfid, nZId, CF_SCALE_FACTOR,
1281 : NC_DOUBLE, 1, &dfNewScale);
1282 :
1283 1 : NCDF_ERR(status);
1284 1 : if (status == NC_NOERR)
1285 : {
1286 1 : SetScaleNoUpdate(dfNewScale);
1287 1 : return CE_None;
1288 : }
1289 :
1290 0 : return CE_Failure;
1291 : }
1292 :
1293 0 : SetScaleNoUpdate(dfNewScale);
1294 0 : return CE_None;
1295 : }
1296 :
1297 : /************************************************************************/
1298 : /* SetScaleNoUpdate() */
1299 : /************************************************************************/
1300 21 : void netCDFRasterBand::SetScaleNoUpdate(double dfVal)
1301 : {
1302 21 : m_dfScale = dfVal;
1303 21 : m_bHaveScale = true;
1304 21 : }
1305 :
1306 : /************************************************************************/
1307 : /* GetUnitType() */
1308 : /************************************************************************/
1309 :
1310 21 : const char *netCDFRasterBand::GetUnitType()
1311 :
1312 : {
1313 21 : if (!m_osUnitType.empty())
1314 6 : return m_osUnitType;
1315 :
1316 15 : return GDALRasterBand::GetUnitType();
1317 : }
1318 :
1319 : /************************************************************************/
1320 : /* SetUnitType() */
1321 : /************************************************************************/
1322 :
1323 1 : CPLErr netCDFRasterBand::SetUnitType(const char *pszNewValue)
1324 :
1325 : {
1326 2 : CPLMutexHolderD(&hNCMutex);
1327 :
1328 2 : const std::string osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
1329 :
1330 1 : if (!osUnitType.empty())
1331 : {
1332 : // Write value if in update mode.
1333 1 : if (poDS->GetAccess() == GA_Update)
1334 : {
1335 : // Make sure we are in define mode.
1336 1 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(TRUE);
1337 :
1338 1 : const int status = nc_put_att_text(
1339 : cdfid, nZId, CF_UNITS, osUnitType.size(), osUnitType.c_str());
1340 :
1341 1 : NCDF_ERR(status);
1342 1 : if (status == NC_NOERR)
1343 : {
1344 1 : SetUnitTypeNoUpdate(pszNewValue);
1345 1 : return CE_None;
1346 : }
1347 :
1348 0 : return CE_Failure;
1349 : }
1350 : }
1351 :
1352 0 : SetUnitTypeNoUpdate(pszNewValue);
1353 :
1354 0 : return CE_None;
1355 : }
1356 :
1357 : /************************************************************************/
1358 : /* SetUnitTypeNoUpdate() */
1359 : /************************************************************************/
1360 :
1361 458 : void netCDFRasterBand::SetUnitTypeNoUpdate(const char *pszNewValue)
1362 : {
1363 458 : m_osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
1364 458 : }
1365 :
1366 : /************************************************************************/
1367 : /* GetNoDataValue() */
1368 : /************************************************************************/
1369 :
1370 153 : double netCDFRasterBand::GetNoDataValue(int *pbSuccess)
1371 :
1372 : {
1373 153 : if (m_bNoDataSetAsInt64)
1374 : {
1375 0 : if (pbSuccess)
1376 0 : *pbSuccess = TRUE;
1377 0 : return GDALGetNoDataValueCastToDouble(m_nNodataValueInt64);
1378 : }
1379 :
1380 153 : if (m_bNoDataSetAsUInt64)
1381 : {
1382 0 : if (pbSuccess)
1383 0 : *pbSuccess = TRUE;
1384 0 : return GDALGetNoDataValueCastToDouble(m_nNodataValueUInt64);
1385 : }
1386 :
1387 153 : if (m_bNoDataSet)
1388 : {
1389 118 : if (pbSuccess)
1390 102 : *pbSuccess = TRUE;
1391 118 : return m_dfNoDataValue;
1392 : }
1393 :
1394 35 : return GDALPamRasterBand::GetNoDataValue(pbSuccess);
1395 : }
1396 :
1397 : /************************************************************************/
1398 : /* GetNoDataValueAsInt64() */
1399 : /************************************************************************/
1400 :
1401 4 : int64_t netCDFRasterBand::GetNoDataValueAsInt64(int *pbSuccess)
1402 :
1403 : {
1404 4 : if (m_bNoDataSetAsInt64)
1405 : {
1406 4 : if (pbSuccess)
1407 4 : *pbSuccess = TRUE;
1408 :
1409 4 : return m_nNodataValueInt64;
1410 : }
1411 :
1412 0 : return GDALPamRasterBand::GetNoDataValueAsInt64(pbSuccess);
1413 : }
1414 :
1415 : /************************************************************************/
1416 : /* GetNoDataValueAsUInt64() */
1417 : /************************************************************************/
1418 :
1419 4 : uint64_t netCDFRasterBand::GetNoDataValueAsUInt64(int *pbSuccess)
1420 :
1421 : {
1422 4 : if (m_bNoDataSetAsUInt64)
1423 : {
1424 4 : if (pbSuccess)
1425 4 : *pbSuccess = TRUE;
1426 :
1427 4 : return m_nNodataValueUInt64;
1428 : }
1429 :
1430 0 : return GDALPamRasterBand::GetNoDataValueAsUInt64(pbSuccess);
1431 : }
1432 :
1433 : /************************************************************************/
1434 : /* SetNoDataValue() */
1435 : /************************************************************************/
1436 :
1437 134 : CPLErr netCDFRasterBand::SetNoDataValue(double dfNoData)
1438 :
1439 : {
1440 268 : CPLMutexHolderD(&hNCMutex);
1441 :
1442 : // If already set to new value, don't do anything.
1443 134 : if (m_bNoDataSet && CPLIsEqual(dfNoData, m_dfNoDataValue))
1444 19 : return CE_None;
1445 :
1446 : // Write value if in update mode.
1447 115 : if (poDS->GetAccess() == GA_Update)
1448 : {
1449 : // netcdf-4 does not allow to set _FillValue after leaving define mode,
1450 : // but it is ok if variable has not been written to, so only print
1451 : // debug. See bug #4484.
1452 125 : if (m_bNoDataSet &&
1453 10 : !reinterpret_cast<netCDFDataset *>(poDS)->GetDefineMode())
1454 : {
1455 0 : CPLDebug("GDAL_netCDF",
1456 : "Setting NoDataValue to %.17g (previously set to %.17g) "
1457 : "but file is no longer in define mode (id #%d, band #%d)",
1458 : dfNoData, m_dfNoDataValue, cdfid, nBand);
1459 : }
1460 : #ifdef NCDF_DEBUG
1461 : else
1462 : {
1463 : CPLDebug("GDAL_netCDF",
1464 : "Setting NoDataValue to %.17g (id #%d, band #%d)",
1465 : dfNoData, cdfid, nBand);
1466 : }
1467 : #endif
1468 : // Make sure we are in define mode.
1469 115 : reinterpret_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1470 :
1471 : int status;
1472 115 : if (eDataType == GDT_Byte)
1473 : {
1474 6 : if (bSignedData)
1475 : {
1476 0 : signed char cNoDataValue = static_cast<signed char>(dfNoData);
1477 0 : status = nc_put_att_schar(cdfid, nZId, NCDF_FillValue,
1478 : nc_datatype, 1, &cNoDataValue);
1479 : }
1480 : else
1481 : {
1482 6 : const unsigned char ucNoDataValue =
1483 6 : static_cast<unsigned char>(dfNoData);
1484 6 : status = nc_put_att_uchar(cdfid, nZId, NCDF_FillValue,
1485 : nc_datatype, 1, &ucNoDataValue);
1486 : }
1487 : }
1488 109 : else if (eDataType == GDT_Int16)
1489 : {
1490 14 : short nsNoDataValue = static_cast<short>(dfNoData);
1491 14 : status = nc_put_att_short(cdfid, nZId, NCDF_FillValue, nc_datatype,
1492 : 1, &nsNoDataValue);
1493 : }
1494 95 : else if (eDataType == GDT_Int32)
1495 : {
1496 27 : int nNoDataValue = static_cast<int>(dfNoData);
1497 27 : status = nc_put_att_int(cdfid, nZId, NCDF_FillValue, nc_datatype, 1,
1498 : &nNoDataValue);
1499 : }
1500 68 : else if (eDataType == GDT_Float32)
1501 : {
1502 31 : float fNoDataValue = static_cast<float>(dfNoData);
1503 31 : status = nc_put_att_float(cdfid, nZId, NCDF_FillValue, nc_datatype,
1504 : 1, &fNoDataValue);
1505 : }
1506 37 : else if (eDataType == GDT_UInt16 &&
1507 6 : reinterpret_cast<netCDFDataset *>(poDS)->eFormat ==
1508 : NCDF_FORMAT_NC4)
1509 : {
1510 6 : unsigned short usNoDataValue =
1511 6 : static_cast<unsigned short>(dfNoData);
1512 6 : status = nc_put_att_ushort(cdfid, nZId, NCDF_FillValue, nc_datatype,
1513 6 : 1, &usNoDataValue);
1514 : }
1515 31 : else if (eDataType == GDT_UInt32 &&
1516 7 : reinterpret_cast<netCDFDataset *>(poDS)->eFormat ==
1517 : NCDF_FORMAT_NC4)
1518 : {
1519 7 : unsigned int unNoDataValue = static_cast<unsigned int>(dfNoData);
1520 7 : status = nc_put_att_uint(cdfid, nZId, NCDF_FillValue, nc_datatype,
1521 7 : 1, &unNoDataValue);
1522 : }
1523 : else
1524 : {
1525 24 : status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
1526 : 1, &dfNoData);
1527 : }
1528 :
1529 115 : NCDF_ERR(status);
1530 :
1531 : // Update status if write worked.
1532 115 : if (status == NC_NOERR)
1533 : {
1534 115 : SetNoDataValueNoUpdate(dfNoData);
1535 115 : return CE_None;
1536 : }
1537 :
1538 0 : return CE_Failure;
1539 : }
1540 :
1541 0 : SetNoDataValueNoUpdate(dfNoData);
1542 0 : return CE_None;
1543 : }
1544 :
1545 : /************************************************************************/
1546 : /* SetNoDataValueNoUpdate() */
1547 : /************************************************************************/
1548 :
1549 421 : void netCDFRasterBand::SetNoDataValueNoUpdate(double dfNoData)
1550 : {
1551 421 : m_dfNoDataValue = dfNoData;
1552 421 : m_bNoDataSet = true;
1553 421 : m_bNoDataSetAsInt64 = false;
1554 421 : m_bNoDataSetAsUInt64 = false;
1555 421 : }
1556 :
1557 : /************************************************************************/
1558 : /* SetNoDataValueAsInt64() */
1559 : /************************************************************************/
1560 :
1561 3 : CPLErr netCDFRasterBand::SetNoDataValueAsInt64(int64_t nNoData)
1562 :
1563 : {
1564 6 : CPLMutexHolderD(&hNCMutex);
1565 :
1566 : // If already set to new value, don't do anything.
1567 3 : if (m_bNoDataSetAsInt64 && nNoData == m_nNodataValueInt64)
1568 0 : return CE_None;
1569 :
1570 : // Write value if in update mode.
1571 3 : if (poDS->GetAccess() == GA_Update)
1572 : {
1573 : // netcdf-4 does not allow to set NCDF_FillValue after leaving define mode,
1574 : // but it is ok if variable has not been written to, so only print
1575 : // debug. See bug #4484.
1576 3 : if (m_bNoDataSetAsInt64 &&
1577 0 : !reinterpret_cast<netCDFDataset *>(poDS)->GetDefineMode())
1578 : {
1579 0 : CPLDebug("GDAL_netCDF",
1580 : "Setting NoDataValue to " CPL_FRMT_GIB
1581 : " (previously set to " CPL_FRMT_GIB ") "
1582 : "but file is no longer in define mode (id #%d, band #%d)",
1583 : static_cast<GIntBig>(nNoData),
1584 0 : static_cast<GIntBig>(m_nNodataValueInt64), cdfid, nBand);
1585 : }
1586 : #ifdef NCDF_DEBUG
1587 : else
1588 : {
1589 : CPLDebug("GDAL_netCDF",
1590 : "Setting NoDataValue to " CPL_FRMT_GIB
1591 : " (id #%d, band #%d)",
1592 : static_cast<GIntBig>(nNoData), cdfid, nBand);
1593 : }
1594 : #endif
1595 : // Make sure we are in define mode.
1596 3 : reinterpret_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1597 :
1598 : int status;
1599 3 : if (eDataType == GDT_Int64 &&
1600 3 : reinterpret_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
1601 : {
1602 3 : long long tmp = static_cast<long long>(nNoData);
1603 3 : status = nc_put_att_longlong(cdfid, nZId, NCDF_FillValue,
1604 3 : nc_datatype, 1, &tmp);
1605 : }
1606 : else
1607 : {
1608 0 : double dfNoData = static_cast<double>(nNoData);
1609 0 : status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
1610 : 1, &dfNoData);
1611 : }
1612 :
1613 3 : NCDF_ERR(status);
1614 :
1615 : // Update status if write worked.
1616 3 : if (status == NC_NOERR)
1617 : {
1618 3 : SetNoDataValueNoUpdate(nNoData);
1619 3 : return CE_None;
1620 : }
1621 :
1622 0 : return CE_Failure;
1623 : }
1624 :
1625 0 : SetNoDataValueNoUpdate(nNoData);
1626 0 : return CE_None;
1627 : }
1628 :
1629 : /************************************************************************/
1630 : /* SetNoDataValueNoUpdate() */
1631 : /************************************************************************/
1632 :
1633 11 : void netCDFRasterBand::SetNoDataValueNoUpdate(int64_t nNoData)
1634 : {
1635 11 : m_nNodataValueInt64 = nNoData;
1636 11 : m_bNoDataSet = false;
1637 11 : m_bNoDataSetAsInt64 = true;
1638 11 : m_bNoDataSetAsUInt64 = false;
1639 11 : }
1640 :
1641 : /************************************************************************/
1642 : /* SetNoDataValueAsUInt64() */
1643 : /************************************************************************/
1644 :
1645 3 : CPLErr netCDFRasterBand::SetNoDataValueAsUInt64(uint64_t nNoData)
1646 :
1647 : {
1648 6 : CPLMutexHolderD(&hNCMutex);
1649 :
1650 : // If already set to new value, don't do anything.
1651 3 : if (m_bNoDataSetAsUInt64 && nNoData == m_nNodataValueUInt64)
1652 0 : return CE_None;
1653 :
1654 : // Write value if in update mode.
1655 3 : if (poDS->GetAccess() == GA_Update)
1656 : {
1657 : // netcdf-4 does not allow to set _FillValue after leaving define mode,
1658 : // but it is ok if variable has not been written to, so only print
1659 : // debug. See bug #4484.
1660 3 : if (m_bNoDataSetAsUInt64 &&
1661 0 : !reinterpret_cast<netCDFDataset *>(poDS)->GetDefineMode())
1662 : {
1663 0 : CPLDebug("GDAL_netCDF",
1664 : "Setting NoDataValue to " CPL_FRMT_GUIB
1665 : " (previously set to " CPL_FRMT_GUIB ") "
1666 : "but file is no longer in define mode (id #%d, band #%d)",
1667 : static_cast<GUIntBig>(nNoData),
1668 0 : static_cast<GUIntBig>(m_nNodataValueUInt64), cdfid, nBand);
1669 : }
1670 : #ifdef NCDF_DEBUG
1671 : else
1672 : {
1673 : CPLDebug("GDAL_netCDF",
1674 : "Setting NoDataValue to " CPL_FRMT_GUIB
1675 : " (id #%d, band #%d)",
1676 : static_cast<GUIntBig>(nNoData), cdfid, nBand);
1677 : }
1678 : #endif
1679 : // Make sure we are in define mode.
1680 3 : reinterpret_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1681 :
1682 : int status;
1683 3 : if (eDataType == GDT_UInt64 &&
1684 3 : reinterpret_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
1685 : {
1686 3 : unsigned long long tmp = static_cast<long long>(nNoData);
1687 3 : status = nc_put_att_ulonglong(cdfid, nZId, NCDF_FillValue,
1688 3 : nc_datatype, 1, &tmp);
1689 : }
1690 : else
1691 : {
1692 0 : double dfNoData = static_cast<double>(nNoData);
1693 0 : status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
1694 : 1, &dfNoData);
1695 : }
1696 :
1697 3 : NCDF_ERR(status);
1698 :
1699 : // Update status if write worked.
1700 3 : if (status == NC_NOERR)
1701 : {
1702 3 : SetNoDataValueNoUpdate(nNoData);
1703 3 : return CE_None;
1704 : }
1705 :
1706 0 : return CE_Failure;
1707 : }
1708 :
1709 0 : SetNoDataValueNoUpdate(nNoData);
1710 0 : return CE_None;
1711 : }
1712 :
1713 : /************************************************************************/
1714 : /* SetNoDataValueNoUpdate() */
1715 : /************************************************************************/
1716 :
1717 10 : void netCDFRasterBand::SetNoDataValueNoUpdate(uint64_t nNoData)
1718 : {
1719 10 : m_nNodataValueUInt64 = nNoData;
1720 10 : m_bNoDataSet = false;
1721 10 : m_bNoDataSetAsInt64 = false;
1722 10 : m_bNoDataSetAsUInt64 = true;
1723 10 : }
1724 :
1725 : /************************************************************************/
1726 : /* DeleteNoDataValue() */
1727 : /************************************************************************/
1728 :
1729 : #ifdef notdef
1730 : CPLErr netCDFRasterBand::DeleteNoDataValue()
1731 :
1732 : {
1733 : CPLMutexHolderD(&hNCMutex);
1734 :
1735 : if (!bNoDataSet)
1736 : return CE_None;
1737 :
1738 : // Write value if in update mode.
1739 : if (poDS->GetAccess() == GA_Update)
1740 : {
1741 : // Make sure we are in define mode.
1742 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1743 :
1744 : status = nc_del_att(cdfid, nZId, NCDF_FillValue);
1745 :
1746 : NCDF_ERR(status);
1747 :
1748 : // Update status if write worked.
1749 : if (status == NC_NOERR)
1750 : {
1751 : dfNoDataValue = 0.0;
1752 : bNoDataSet = false;
1753 : return CE_None;
1754 : }
1755 :
1756 : return CE_Failure;
1757 : }
1758 :
1759 : dfNoDataValue = 0.0;
1760 : bNoDataSet = false;
1761 : return CE_None;
1762 : }
1763 : #endif
1764 :
1765 : /************************************************************************/
1766 : /* SerializeToXML() */
1767 : /************************************************************************/
1768 :
1769 4 : CPLXMLNode *netCDFRasterBand::SerializeToXML(const char * /* pszUnused */)
1770 : {
1771 : // Overridden from GDALPamDataset to add only band histogram
1772 : // and statistics. See bug #4244.
1773 4 : if (psPam == nullptr)
1774 0 : return nullptr;
1775 :
1776 : // Setup root node and attributes.
1777 : CPLXMLNode *psTree =
1778 4 : CPLCreateXMLNode(nullptr, CXT_Element, "PAMRasterBand");
1779 :
1780 4 : if (GetBand() > 0)
1781 : {
1782 8 : CPLString oFmt;
1783 4 : CPLSetXMLValue(psTree, "#band", oFmt.Printf("%d", GetBand()));
1784 : }
1785 :
1786 : // Histograms.
1787 4 : if (psPam->psSavedHistograms != nullptr)
1788 1 : CPLAddXMLChild(psTree, CPLCloneXMLTree(psPam->psSavedHistograms));
1789 :
1790 : // Metadata (statistics only).
1791 4 : GDALMultiDomainMetadata oMDMDStats;
1792 4 : const char *papszMDStats[] = {"STATISTICS_MINIMUM", "STATISTICS_MAXIMUM",
1793 : "STATISTICS_MEAN", "STATISTICS_STDDEV",
1794 : nullptr};
1795 20 : for (int i = 0; i < CSLCount(papszMDStats); i++)
1796 : {
1797 16 : const char *pszMDI = GetMetadataItem(papszMDStats[i]);
1798 16 : if (pszMDI)
1799 4 : oMDMDStats.SetMetadataItem(papszMDStats[i], pszMDI);
1800 : }
1801 4 : CPLXMLNode *psMD = oMDMDStats.Serialize();
1802 :
1803 4 : if (psMD != nullptr)
1804 : {
1805 1 : if (psMD->psChild == nullptr)
1806 0 : CPLDestroyXMLNode(psMD);
1807 : else
1808 1 : CPLAddXMLChild(psTree, psMD);
1809 : }
1810 :
1811 : // We don't want to return anything if we had no metadata to attach.
1812 4 : if (psTree->psChild == nullptr || psTree->psChild->psNext == nullptr)
1813 : {
1814 2 : CPLDestroyXMLNode(psTree);
1815 2 : psTree = nullptr;
1816 : }
1817 :
1818 4 : return psTree;
1819 : }
1820 :
1821 : /************************************************************************/
1822 : /* Get1DVariableIndexedByDimension() */
1823 : /************************************************************************/
1824 :
1825 73 : static int Get1DVariableIndexedByDimension(int cdfid, int nDimId,
1826 : const char *pszDimName,
1827 : bool bVerboseError, int *pnGroupID)
1828 : {
1829 73 : *pnGroupID = -1;
1830 73 : int nVarID = -1;
1831 : // First try to find a variable whose name is identical to the dimension
1832 : // name, and check that it is indeed indexed by this dimension
1833 73 : if (NCDFResolveVar(cdfid, pszDimName, pnGroupID, &nVarID) == CE_None)
1834 : {
1835 61 : int nDimCountOfVariable = 0;
1836 61 : nc_inq_varndims(*pnGroupID, nVarID, &nDimCountOfVariable);
1837 61 : if (nDimCountOfVariable == 1)
1838 : {
1839 61 : int nDimIdOfVariable = -1;
1840 61 : nc_inq_vardimid(*pnGroupID, nVarID, &nDimIdOfVariable);
1841 61 : if (nDimIdOfVariable == nDimId)
1842 : {
1843 61 : return nVarID;
1844 : }
1845 : }
1846 : }
1847 :
1848 : // Otherwise iterate over the variables to find potential candidates
1849 : // TODO: should be modified to search also in other groups using the same
1850 : // logic than in NCDFResolveVar(), but maybe not needed if it's a
1851 : // very rare case? and I think this is not CF compliant.
1852 12 : int nvars = 0;
1853 12 : CPL_IGNORE_RET_VAL(nc_inq(cdfid, nullptr, &nvars, nullptr, nullptr));
1854 :
1855 12 : int nCountCandidateVars = 0;
1856 12 : int nCandidateVarID = -1;
1857 53 : for (int k = 0; k < nvars; k++)
1858 : {
1859 41 : int nDimCountOfVariable = 0;
1860 41 : nc_inq_varndims(cdfid, k, &nDimCountOfVariable);
1861 41 : if (nDimCountOfVariable == 1)
1862 : {
1863 23 : int nDimIdOfVariable = -1;
1864 23 : nc_inq_vardimid(cdfid, k, &nDimIdOfVariable);
1865 23 : if (nDimIdOfVariable == nDimId)
1866 : {
1867 5 : nCountCandidateVars++;
1868 5 : nCandidateVarID = k;
1869 : }
1870 : }
1871 : }
1872 12 : if (nCountCandidateVars > 1)
1873 : {
1874 1 : if (bVerboseError)
1875 : {
1876 1 : CPLError(CE_Warning, CPLE_AppDefined,
1877 : "Several 1D variables are indexed by dimension %s",
1878 : pszDimName);
1879 : }
1880 1 : *pnGroupID = -1;
1881 1 : return -1;
1882 : }
1883 11 : else if (nCandidateVarID < 0)
1884 : {
1885 8 : if (bVerboseError)
1886 : {
1887 8 : CPLError(CE_Warning, CPLE_AppDefined,
1888 : "No 1D variable is indexed by dimension %s", pszDimName);
1889 : }
1890 : }
1891 11 : *pnGroupID = cdfid;
1892 11 : return nCandidateVarID;
1893 : }
1894 :
1895 : /************************************************************************/
1896 : /* CreateMetadataFromAttributes() */
1897 : /************************************************************************/
1898 :
1899 457 : void netCDFRasterBand::CreateMetadataFromAttributes()
1900 : {
1901 457 : char szVarName[NC_MAX_NAME + 1] = {};
1902 457 : int status = nc_inq_varname(cdfid, nZId, szVarName);
1903 457 : NCDF_ERR(status);
1904 :
1905 457 : GDALPamRasterBand::SetMetadataItem("NETCDF_VARNAME", szVarName);
1906 :
1907 : // Get attribute metadata.
1908 457 : int nAtt = 0;
1909 457 : NCDF_ERR(nc_inq_varnatts(cdfid, nZId, &nAtt));
1910 :
1911 1911 : for (int i = 0; i < nAtt; i++)
1912 : {
1913 1454 : char szMetaName[NC_MAX_NAME + 1] = {};
1914 1454 : status = nc_inq_attname(cdfid, nZId, i, szMetaName);
1915 1454 : if (status != NC_NOERR)
1916 12 : continue;
1917 :
1918 1454 : if (GDALPamRasterBand::GetMetadataItem(szMetaName) != nullptr)
1919 : {
1920 12 : continue;
1921 : }
1922 :
1923 1442 : char *pszMetaValue = nullptr;
1924 1442 : if (NCDFGetAttr(cdfid, nZId, szMetaName, &pszMetaValue) == CE_None)
1925 : {
1926 1442 : GDALPamRasterBand::SetMetadataItem(szMetaName, pszMetaValue);
1927 : }
1928 : else
1929 : {
1930 0 : CPLDebug("GDAL_netCDF", "invalid Band metadata %s", szMetaName);
1931 : }
1932 :
1933 1442 : if (pszMetaValue)
1934 : {
1935 1442 : CPLFree(pszMetaValue);
1936 1442 : pszMetaValue = nullptr;
1937 : }
1938 : }
1939 457 : }
1940 :
1941 : /************************************************************************/
1942 : /* CreateMetadataFromOtherVars() */
1943 : /************************************************************************/
1944 :
1945 48 : void netCDFRasterBand::CreateMetadataFromOtherVars()
1946 :
1947 : {
1948 48 : CPLAssert(!m_bCreateMetadataFromOtherVarsDone);
1949 48 : m_bCreateMetadataFromOtherVarsDone = true;
1950 :
1951 48 : netCDFDataset *l_poDS = reinterpret_cast<netCDFDataset *>(poDS);
1952 48 : const int nPamFlagsBackup = l_poDS->nPamFlags;
1953 :
1954 : // Compute all dimensions from Band number and save in Metadata.
1955 48 : int nd = 0;
1956 48 : nc_inq_varndims(cdfid, nZId, &nd);
1957 : // Compute multidimention band position.
1958 : //
1959 : // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
1960 : // if Data[2,3,4,x,y]
1961 : //
1962 : // BandPos0 = (nBand) / (3*4)
1963 : // BandPos1 = (nBand - BandPos0*(3*4)) / (4)
1964 : // BandPos2 = (nBand - BandPos0*(3*4)) % (4)
1965 :
1966 48 : int Sum = 1;
1967 48 : if (nd == 3)
1968 : {
1969 5 : Sum *= panBandZLev[0];
1970 : }
1971 :
1972 : // Loop over non-spatial dimensions.
1973 48 : int Taken = 0;
1974 :
1975 88 : for (int i = 0; i < nd - 2; i++)
1976 : {
1977 : int result;
1978 40 : if (i != nd - 2 - 1)
1979 : {
1980 18 : Sum = 1;
1981 37 : for (int j = i + 1; j < nd - 2; j++)
1982 : {
1983 19 : Sum *= panBandZLev[j];
1984 : }
1985 18 : result = static_cast<int>((nLevel - Taken) / Sum);
1986 : }
1987 : else
1988 : {
1989 22 : result = static_cast<int>((nLevel - Taken) % Sum);
1990 : }
1991 :
1992 40 : char szName[NC_MAX_NAME + 1] = {};
1993 40 : snprintf(szName, sizeof(szName), "%s",
1994 40 : l_poDS->papszDimName[l_poDS->m_anDimIds[panBandZPos[i]]]);
1995 :
1996 : char szMetaName[NC_MAX_NAME + 1 + 32];
1997 40 : snprintf(szMetaName, sizeof(szMetaName), "NETCDF_DIM_%s", szName);
1998 :
1999 40 : const int nGroupID = l_poDS->m_anExtraDimGroupIds[i];
2000 40 : const int nVarID = l_poDS->m_anExtraDimVarIds[i];
2001 40 : if (nVarID < 0)
2002 : {
2003 2 : GDALPamRasterBand::SetMetadataItem(szMetaName,
2004 : CPLSPrintf("%d", result + 1));
2005 : }
2006 : else
2007 : {
2008 : // TODO: Make sure all the status checks make sense.
2009 :
2010 38 : nc_type nVarType = NC_NAT;
2011 38 : /* status = */ nc_inq_vartype(nGroupID, nVarID, &nVarType);
2012 :
2013 38 : int nDims = 0;
2014 38 : /* status = */ nc_inq_varndims(nGroupID, nVarID, &nDims);
2015 :
2016 38 : char szMetaTemp[256] = {};
2017 38 : if (nDims == 1)
2018 : {
2019 38 : size_t count[1] = {1};
2020 38 : size_t start[1] = {static_cast<size_t>(result)};
2021 :
2022 38 : switch (nVarType)
2023 : {
2024 0 : case NC_BYTE:
2025 : // TODO: Check for signed/unsigned byte.
2026 : signed char cData;
2027 0 : /* status = */ nc_get_vara_schar(nGroupID, nVarID,
2028 : start, count, &cData);
2029 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", cData);
2030 0 : break;
2031 0 : case NC_SHORT:
2032 : short sData;
2033 0 : /* status = */ nc_get_vara_short(nGroupID, nVarID,
2034 : start, count, &sData);
2035 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", sData);
2036 0 : break;
2037 19 : case NC_INT:
2038 : {
2039 : int nData;
2040 19 : /* status = */ nc_get_vara_int(nGroupID, nVarID, start,
2041 : count, &nData);
2042 19 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", nData);
2043 19 : break;
2044 : }
2045 0 : case NC_FLOAT:
2046 : float fData;
2047 0 : /* status = */ nc_get_vara_float(nGroupID, nVarID,
2048 : start, count, &fData);
2049 0 : CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.8g",
2050 : fData);
2051 0 : break;
2052 18 : case NC_DOUBLE:
2053 : double dfData;
2054 18 : /* status = */ nc_get_vara_double(
2055 : nGroupID, nVarID, start, count, &dfData);
2056 18 : CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.16g",
2057 : dfData);
2058 18 : break;
2059 0 : case NC_UBYTE:
2060 : unsigned char ucData;
2061 0 : /* status = */ nc_get_vara_uchar(nGroupID, nVarID,
2062 : start, count, &ucData);
2063 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", ucData);
2064 0 : break;
2065 0 : case NC_USHORT:
2066 : unsigned short usData;
2067 0 : /* status = */ nc_get_vara_ushort(
2068 : nGroupID, nVarID, start, count, &usData);
2069 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", usData);
2070 0 : break;
2071 0 : case NC_UINT:
2072 : {
2073 : unsigned int unData;
2074 0 : /* status = */ nc_get_vara_uint(nGroupID, nVarID, start,
2075 : count, &unData);
2076 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", unData);
2077 0 : break;
2078 : }
2079 1 : case NC_INT64:
2080 : {
2081 : long long nData;
2082 1 : /* status = */ nc_get_vara_longlong(
2083 : nGroupID, nVarID, start, count, &nData);
2084 1 : snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GIB,
2085 : nData);
2086 1 : break;
2087 : }
2088 0 : case NC_UINT64:
2089 : {
2090 : unsigned long long unData;
2091 0 : /* status = */ nc_get_vara_ulonglong(
2092 : nGroupID, nVarID, start, count, &unData);
2093 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GUIB,
2094 : unData);
2095 0 : break;
2096 : }
2097 0 : default:
2098 0 : CPLDebug("GDAL_netCDF", "invalid dim %s, type=%d",
2099 : szMetaTemp, nVarType);
2100 0 : break;
2101 : }
2102 : }
2103 : else
2104 : {
2105 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", result + 1);
2106 : }
2107 :
2108 : // Save dimension value.
2109 : // NOTE: removed #original_units as not part of CF-1.
2110 :
2111 38 : GDALPamRasterBand::SetMetadataItem(szMetaName, szMetaTemp);
2112 : }
2113 :
2114 : // Avoid int32 overflow. Perhaps something more sensible to do here ?
2115 40 : if (result > 0 && Sum > INT_MAX / result)
2116 0 : break;
2117 40 : if (Taken > INT_MAX - result * Sum)
2118 0 : break;
2119 :
2120 40 : Taken += result * Sum;
2121 : } // End loop non-spatial dimensions.
2122 :
2123 48 : l_poDS->nPamFlags = nPamFlagsBackup;
2124 48 : }
2125 :
2126 : /************************************************************************/
2127 : /* CheckData() */
2128 : /************************************************************************/
2129 : template <class T>
2130 5730 : void netCDFRasterBand::CheckData(void *pImage, void *pImageNC,
2131 : size_t nTmpBlockXSize, size_t nTmpBlockYSize,
2132 : bool bCheckIsNan)
2133 : {
2134 5730 : CPLAssert(pImage != nullptr && pImageNC != nullptr);
2135 :
2136 : // If this block is not a full block (in the x axis), we need to re-arrange
2137 : // the data this is because partial blocks are not arranged the same way in
2138 : // netcdf and gdal.
2139 5730 : if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
2140 : {
2141 6 : T *ptrWrite = static_cast<T *>(pImage);
2142 6 : T *ptrRead = static_cast<T *>(pImageNC);
2143 29 : for (size_t j = 0; j < nTmpBlockYSize;
2144 23 : j++, ptrWrite += nBlockXSize, ptrRead += nTmpBlockXSize)
2145 : {
2146 23 : memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T));
2147 : }
2148 : }
2149 :
2150 : // Is valid data checking needed or requested?
2151 5730 : if (bValidRangeValid || bCheckIsNan)
2152 : {
2153 1265 : T *ptrImage = static_cast<T *>(pImage);
2154 2584 : for (size_t j = 0; j < nTmpBlockYSize; j++)
2155 : {
2156 : // k moves along the gdal block, skipping the out-of-range pixels.
2157 1319 : size_t k = j * nBlockXSize;
2158 96938 : for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
2159 : {
2160 : // Check for nodata and nan.
2161 95619 : if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
2162 6301 : continue;
2163 89318 : if (bCheckIsNan && std::isnan((double)ptrImage[k]))
2164 : {
2165 5737 : ptrImage[k] = (T)m_dfNoDataValue;
2166 5737 : continue;
2167 : }
2168 : // Check for valid_range.
2169 83581 : if (bValidRangeValid)
2170 : {
2171 40986 : if (((adfValidRange[0] != m_dfNoDataValue) &&
2172 40986 : (ptrImage[k] < (T)adfValidRange[0])) ||
2173 40983 : ((adfValidRange[1] != m_dfNoDataValue) &&
2174 40983 : (ptrImage[k] > (T)adfValidRange[1])))
2175 : {
2176 4 : ptrImage[k] = (T)m_dfNoDataValue;
2177 : }
2178 : }
2179 : }
2180 : }
2181 : }
2182 :
2183 : // If minimum longitude is > 180, subtract 360 from all.
2184 : // If not, disable checking for further calls (check just once).
2185 : // Only check first and last block elements since lon must be monotonic.
2186 5730 : const bool bIsSigned = std::numeric_limits<T>::is_signed;
2187 5419 : if (bCheckLongitude && bIsSigned &&
2188 9 : !CPLIsEqual((double)((T *)pImage)[0], m_dfNoDataValue) &&
2189 8 : !CPLIsEqual((double)((T *)pImage)[nTmpBlockXSize - 1],
2190 2714 : m_dfNoDataValue) &&
2191 8 : std::min(((T *)pImage)[0], ((T *)pImage)[nTmpBlockXSize - 1]) > 180.0)
2192 : {
2193 0 : T *ptrImage = static_cast<T *>(pImage);
2194 0 : for (size_t j = 0; j < nTmpBlockYSize; j++)
2195 : {
2196 0 : size_t k = j * nBlockXSize;
2197 0 : for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
2198 : {
2199 0 : if (!CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
2200 0 : ptrImage[k] = static_cast<T>(ptrImage[k] - 360);
2201 : }
2202 : }
2203 : }
2204 : else
2205 : {
2206 5730 : bCheckLongitude = false;
2207 : }
2208 5730 : }
2209 :
2210 : /************************************************************************/
2211 : /* CheckDataCpx() */
2212 : /************************************************************************/
2213 : template <class T>
2214 25 : void netCDFRasterBand::CheckDataCpx(void *pImage, void *pImageNC,
2215 : size_t nTmpBlockXSize,
2216 : size_t nTmpBlockYSize, bool bCheckIsNan)
2217 : {
2218 25 : CPLAssert(pImage != nullptr && pImageNC != nullptr);
2219 :
2220 : // If this block is not a full block (in the x axis), we need to re-arrange
2221 : // the data this is because partial blocks are not arranged the same way in
2222 : // netcdf and gdal.
2223 25 : if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
2224 : {
2225 0 : T *ptrWrite = static_cast<T *>(pImage);
2226 0 : T *ptrRead = static_cast<T *>(pImageNC);
2227 0 : for (size_t j = 0; j < nTmpBlockYSize; j++,
2228 0 : ptrWrite += (2 * nBlockXSize),
2229 0 : ptrRead += (2 * nTmpBlockXSize))
2230 : {
2231 0 : memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T) * 2);
2232 : }
2233 : }
2234 :
2235 : // Is valid data checking needed or requested?
2236 25 : if (bValidRangeValid || bCheckIsNan)
2237 : {
2238 0 : T *ptrImage = static_cast<T *>(pImage);
2239 0 : for (size_t j = 0; j < nTmpBlockYSize; j++)
2240 : {
2241 : // k moves along the gdal block, skipping the out-of-range pixels.
2242 0 : size_t k = 2 * j * nBlockXSize;
2243 0 : for (size_t i = 0; i < (2 * nTmpBlockXSize); i++, k++)
2244 : {
2245 : // Check for nodata and nan.
2246 0 : if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
2247 0 : continue;
2248 0 : if (bCheckIsNan && std::isnan((double)ptrImage[k]))
2249 : {
2250 0 : ptrImage[k] = (T)m_dfNoDataValue;
2251 0 : continue;
2252 : }
2253 : // Check for valid_range.
2254 0 : if (bValidRangeValid)
2255 : {
2256 0 : if (((adfValidRange[0] != m_dfNoDataValue) &&
2257 0 : (ptrImage[k] < (T)adfValidRange[0])) ||
2258 0 : ((adfValidRange[1] != m_dfNoDataValue) &&
2259 0 : (ptrImage[k] > (T)adfValidRange[1])))
2260 : {
2261 0 : ptrImage[k] = (T)m_dfNoDataValue;
2262 : }
2263 : }
2264 : }
2265 : }
2266 : }
2267 25 : }
2268 :
2269 : /************************************************************************/
2270 : /* FetchNetcdfChunk() */
2271 : /************************************************************************/
2272 :
2273 5755 : bool netCDFRasterBand::FetchNetcdfChunk(size_t xstart, size_t ystart,
2274 : void *pImage)
2275 : {
2276 5755 : size_t start[MAX_NC_DIMS] = {};
2277 5755 : size_t edge[MAX_NC_DIMS] = {};
2278 :
2279 5755 : start[nBandXPos] = xstart;
2280 5755 : edge[nBandXPos] = nBlockXSize;
2281 5755 : if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
2282 6 : edge[nBandXPos] = nRasterXSize - start[nBandXPos];
2283 5755 : if (nBandYPos >= 0)
2284 : {
2285 5751 : start[nBandYPos] = ystart;
2286 5751 : edge[nBandYPos] = nBlockYSize;
2287 5751 : if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
2288 4 : edge[nBandYPos] = nRasterYSize - start[nBandYPos];
2289 : }
2290 5755 : const size_t nYChunkSize = nBandYPos < 0 ? 1 : edge[nBandYPos];
2291 :
2292 : #ifdef NCDF_DEBUG
2293 : CPLDebug("GDAL_netCDF", "start={%ld,%ld} edge={%ld,%ld} bBottomUp=%d",
2294 : start[nBandXPos], nBandYPos < 0 ? 0 : start[nBandYPos],
2295 : edge[nBandXPos], nYChunkSize, ((netCDFDataset *)poDS)->bBottomUp);
2296 : #endif
2297 :
2298 5755 : int nd = 0;
2299 5755 : nc_inq_varndims(cdfid, nZId, &nd);
2300 5755 : if (nd == 3)
2301 : {
2302 1078 : start[panBandZPos[0]] = nLevel; // z
2303 1078 : edge[panBandZPos[0]] = 1;
2304 : }
2305 :
2306 : // Compute multidimention band position.
2307 : //
2308 : // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
2309 : // if Data[2,3,4,x,y]
2310 : //
2311 : // BandPos0 = (nBand) / (3*4)
2312 : // BandPos1 = (nBand - (3*4)) / (4)
2313 : // BandPos2 = (nBand - (3*4)) % (4)
2314 5755 : if (nd > 3)
2315 : {
2316 160 : int Sum = -1;
2317 160 : int Taken = 0;
2318 480 : for (int i = 0; i < nd - 2; i++)
2319 : {
2320 320 : if (i != nd - 2 - 1)
2321 : {
2322 160 : Sum = 1;
2323 320 : for (int j = i + 1; j < nd - 2; j++)
2324 : {
2325 160 : Sum *= panBandZLev[j];
2326 : }
2327 160 : start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
2328 160 : edge[panBandZPos[i]] = 1;
2329 : }
2330 : else
2331 : {
2332 160 : start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
2333 160 : edge[panBandZPos[i]] = 1;
2334 : }
2335 320 : Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
2336 : }
2337 : }
2338 :
2339 : // Make sure we are in data mode.
2340 5755 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
2341 :
2342 : // If this block is not a full block in the x axis, we need to
2343 : // re-arrange the data because partial blocks are not arranged the
2344 : // same way in netcdf and gdal, so we first we read the netcdf data at
2345 : // the end of the gdal block buffer then re-arrange rows in CheckData().
2346 5755 : void *pImageNC = pImage;
2347 5755 : if (edge[nBandXPos] != static_cast<size_t>(nBlockXSize))
2348 : {
2349 6 : pImageNC = static_cast<GByte *>(pImage) +
2350 6 : ((static_cast<size_t>(nBlockXSize) * nBlockYSize -
2351 12 : edge[nBandXPos] * nYChunkSize) *
2352 6 : (GDALGetDataTypeSize(eDataType) / 8));
2353 : }
2354 :
2355 : // Read data according to type.
2356 : int status;
2357 5755 : if (eDataType == GDT_Byte)
2358 : {
2359 3003 : if (bSignedData)
2360 : {
2361 0 : status = nc_get_vara_schar(cdfid, nZId, start, edge,
2362 : static_cast<signed char *>(pImageNC));
2363 0 : if (status == NC_NOERR)
2364 0 : CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
2365 : nYChunkSize, false);
2366 : }
2367 : else
2368 : {
2369 3003 : status = nc_get_vara_uchar(cdfid, nZId, start, edge,
2370 : static_cast<unsigned char *>(pImageNC));
2371 3003 : if (status == NC_NOERR)
2372 3003 : CheckData<unsigned char>(pImage, pImageNC, edge[nBandXPos],
2373 : nYChunkSize, false);
2374 : }
2375 : }
2376 2752 : else if (eDataType == GDT_Int8)
2377 : {
2378 60 : status = nc_get_vara_schar(cdfid, nZId, start, edge,
2379 : static_cast<signed char *>(pImageNC));
2380 60 : if (status == NC_NOERR)
2381 60 : CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
2382 : nYChunkSize, false);
2383 : }
2384 2692 : else if (nc_datatype == NC_SHORT)
2385 : {
2386 465 : status = nc_get_vara_short(cdfid, nZId, start, edge,
2387 : static_cast<short *>(pImageNC));
2388 465 : if (status == NC_NOERR)
2389 : {
2390 465 : if (eDataType == GDT_Int16)
2391 : {
2392 462 : CheckData<GInt16>(pImage, pImageNC, edge[nBandXPos],
2393 : nYChunkSize, false);
2394 : }
2395 : else
2396 : {
2397 3 : CheckData<GUInt16>(pImage, pImageNC, edge[nBandXPos],
2398 : nYChunkSize, false);
2399 : }
2400 : }
2401 : }
2402 2227 : else if (eDataType == GDT_Int32)
2403 : {
2404 : #if SIZEOF_UNSIGNED_LONG == 4
2405 : status = nc_get_vara_long(cdfid, nZId, start, edge,
2406 : static_cast<long *>(pImageNC));
2407 : if (status == NC_NOERR)
2408 : CheckData<long>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2409 : false);
2410 : #else
2411 912 : status = nc_get_vara_int(cdfid, nZId, start, edge,
2412 : static_cast<int *>(pImageNC));
2413 912 : if (status == NC_NOERR)
2414 912 : CheckData<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2415 : false);
2416 : #endif
2417 : }
2418 1315 : else if (eDataType == GDT_Float32)
2419 : {
2420 1178 : status = nc_get_vara_float(cdfid, nZId, start, edge,
2421 : static_cast<float *>(pImageNC));
2422 1178 : if (status == NC_NOERR)
2423 1178 : CheckData<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2424 : true);
2425 : }
2426 137 : else if (eDataType == GDT_Float64)
2427 : {
2428 86 : status = nc_get_vara_double(cdfid, nZId, start, edge,
2429 : static_cast<double *>(pImageNC));
2430 86 : if (status == NC_NOERR)
2431 86 : CheckData<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2432 : true);
2433 : }
2434 51 : else if (eDataType == GDT_UInt16)
2435 : {
2436 6 : status = nc_get_vara_ushort(cdfid, nZId, start, edge,
2437 : static_cast<unsigned short *>(pImageNC));
2438 6 : if (status == NC_NOERR)
2439 6 : CheckData<unsigned short>(pImage, pImageNC, edge[nBandXPos],
2440 : nYChunkSize, false);
2441 : }
2442 45 : else if (eDataType == GDT_UInt32)
2443 : {
2444 6 : status = nc_get_vara_uint(cdfid, nZId, start, edge,
2445 : static_cast<unsigned int *>(pImageNC));
2446 6 : if (status == NC_NOERR)
2447 6 : CheckData<unsigned int>(pImage, pImageNC, edge[nBandXPos],
2448 : nYChunkSize, false);
2449 : }
2450 39 : else if (eDataType == GDT_Int64)
2451 : {
2452 7 : status = nc_get_vara_longlong(cdfid, nZId, start, edge,
2453 : static_cast<long long *>(pImageNC));
2454 7 : if (status == NC_NOERR)
2455 7 : CheckData<std::int64_t>(pImage, pImageNC, edge[nBandXPos],
2456 : nYChunkSize, false);
2457 : }
2458 32 : else if (eDataType == GDT_UInt64)
2459 : {
2460 : status =
2461 7 : nc_get_vara_ulonglong(cdfid, nZId, start, edge,
2462 : static_cast<unsigned long long *>(pImageNC));
2463 7 : if (status == NC_NOERR)
2464 7 : CheckData<std::uint64_t>(pImage, pImageNC, edge[nBandXPos],
2465 : nYChunkSize, false);
2466 : }
2467 25 : else if (eDataType == GDT_CInt16)
2468 : {
2469 0 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2470 0 : if (status == NC_NOERR)
2471 0 : CheckDataCpx<short>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2472 : false);
2473 : }
2474 25 : else if (eDataType == GDT_CInt32)
2475 : {
2476 0 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2477 0 : if (status == NC_NOERR)
2478 0 : CheckDataCpx<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2479 : false);
2480 : }
2481 25 : else if (eDataType == GDT_CFloat32)
2482 : {
2483 20 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2484 20 : if (status == NC_NOERR)
2485 20 : CheckDataCpx<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2486 : false);
2487 : }
2488 5 : else if (eDataType == GDT_CFloat64)
2489 : {
2490 5 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2491 5 : if (status == NC_NOERR)
2492 5 : CheckDataCpx<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2493 : false);
2494 : }
2495 :
2496 : else
2497 0 : status = NC_EBADTYPE;
2498 :
2499 5755 : if (status != NC_NOERR)
2500 : {
2501 0 : CPLError(CE_Failure, CPLE_AppDefined,
2502 : "netCDF chunk fetch failed: #%d (%s)", status,
2503 : nc_strerror(status));
2504 0 : return false;
2505 : }
2506 5755 : return true;
2507 : }
2508 :
2509 : /************************************************************************/
2510 : /* IReadBlock() */
2511 : /************************************************************************/
2512 :
2513 5755 : CPLErr netCDFRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
2514 : void *pImage)
2515 :
2516 : {
2517 11510 : CPLMutexHolderD(&hNCMutex);
2518 :
2519 : // Locate X, Y and Z position in the array.
2520 :
2521 5755 : size_t xstart = static_cast<size_t>(nBlockXOff) * nBlockXSize;
2522 5755 : size_t ystart = 0;
2523 :
2524 : // Check y order.
2525 5755 : if (nBandYPos >= 0)
2526 : {
2527 5751 : auto poGDS = static_cast<netCDFDataset *>(poDS);
2528 5751 : if (poGDS->bBottomUp)
2529 : {
2530 4836 : if (nBlockYSize == 1)
2531 : {
2532 4823 : ystart = nRasterYSize - 1 - nBlockYOff;
2533 : }
2534 : else
2535 : {
2536 : // in GDAL space
2537 13 : ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
2538 : const size_t yend =
2539 26 : std::min(ystart + nBlockYSize - 1,
2540 13 : static_cast<size_t>(nRasterYSize - 1));
2541 : // in netCDF space
2542 13 : const size_t nFirstChunkLine = nRasterYSize - 1 - yend;
2543 13 : const size_t nLastChunkLine = nRasterYSize - 1 - ystart;
2544 13 : const size_t nFirstChunkBlock = nFirstChunkLine / nBlockYSize;
2545 13 : const size_t nLastChunkBlock = nLastChunkLine / nBlockYSize;
2546 :
2547 : const auto firstKey = netCDFDataset::ChunkKey(
2548 13 : nBlockXOff, nFirstChunkBlock, nBand);
2549 : const auto secondKey =
2550 13 : netCDFDataset::ChunkKey(nBlockXOff, nLastChunkBlock, nBand);
2551 :
2552 : // Retrieve data from the one or 2 needed netCDF chunks
2553 13 : std::shared_ptr<std::vector<GByte>> firstChunk;
2554 13 : std::shared_ptr<std::vector<GByte>> secondChunk;
2555 13 : if (poGDS->poChunkCache)
2556 : {
2557 13 : poGDS->poChunkCache->tryGet(firstKey, firstChunk);
2558 13 : if (firstKey != secondKey)
2559 6 : poGDS->poChunkCache->tryGet(secondKey, secondChunk);
2560 : }
2561 : const size_t nChunkLineSize =
2562 13 : static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
2563 13 : nBlockXSize;
2564 13 : const size_t nChunkSize = nChunkLineSize * nBlockYSize;
2565 13 : if (!firstChunk)
2566 : {
2567 11 : firstChunk.reset(new std::vector<GByte>(nChunkSize));
2568 11 : if (!FetchNetcdfChunk(xstart,
2569 11 : nFirstChunkBlock * nBlockYSize,
2570 11 : firstChunk.get()->data()))
2571 0 : return CE_Failure;
2572 11 : if (poGDS->poChunkCache)
2573 11 : poGDS->poChunkCache->insert(firstKey, firstChunk);
2574 : }
2575 13 : if (!secondChunk && firstKey != secondKey)
2576 : {
2577 2 : secondChunk.reset(new std::vector<GByte>(nChunkSize));
2578 2 : if (!FetchNetcdfChunk(xstart, nLastChunkBlock * nBlockYSize,
2579 2 : secondChunk.get()->data()))
2580 0 : return CE_Failure;
2581 2 : if (poGDS->poChunkCache)
2582 2 : poGDS->poChunkCache->insert(secondKey, secondChunk);
2583 : }
2584 :
2585 : // Assemble netCDF chunks into GDAL block
2586 13 : GByte *pabyImage = static_cast<GByte *>(pImage);
2587 13 : const size_t nFirstChunkBlockLine =
2588 13 : nFirstChunkBlock * nBlockYSize;
2589 13 : const size_t nLastChunkBlockLine =
2590 13 : nLastChunkBlock * nBlockYSize;
2591 146 : for (size_t iLine = ystart; iLine <= yend; iLine++)
2592 : {
2593 133 : const size_t nLineFromBottom = nRasterYSize - 1 - iLine;
2594 133 : const size_t nChunkY = nLineFromBottom / nBlockYSize;
2595 133 : if (nChunkY == nFirstChunkBlock)
2596 : {
2597 121 : memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
2598 121 : firstChunk.get()->data() +
2599 121 : (nLineFromBottom - nFirstChunkBlockLine) *
2600 : nChunkLineSize,
2601 : nChunkLineSize);
2602 : }
2603 : else
2604 : {
2605 12 : CPLAssert(nChunkY == nLastChunkBlock);
2606 12 : assert(secondChunk);
2607 12 : memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
2608 12 : secondChunk.get()->data() +
2609 12 : (nLineFromBottom - nLastChunkBlockLine) *
2610 : nChunkLineSize,
2611 : nChunkLineSize);
2612 : }
2613 : }
2614 13 : return CE_None;
2615 : }
2616 : }
2617 : else
2618 : {
2619 915 : ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
2620 : }
2621 : }
2622 :
2623 5742 : return FetchNetcdfChunk(xstart, ystart, pImage) ? CE_None : CE_Failure;
2624 : }
2625 :
2626 : /************************************************************************/
2627 : /* IWriteBlock() */
2628 : /************************************************************************/
2629 :
2630 2652 : CPLErr netCDFRasterBand::IWriteBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
2631 : void *pImage)
2632 : {
2633 5304 : CPLMutexHolderD(&hNCMutex);
2634 :
2635 : #ifdef NCDF_DEBUG
2636 : if (nBlockYOff == 0 || (nBlockYOff == nRasterYSize - 1))
2637 : CPLDebug("GDAL_netCDF",
2638 : "netCDFRasterBand::IWriteBlock( %d, %d, ...) nBand=%d",
2639 : nBlockXOff, nBlockYOff, nBand);
2640 : #endif
2641 :
2642 2652 : int nd = 0;
2643 2652 : nc_inq_varndims(cdfid, nZId, &nd);
2644 :
2645 : // Locate X, Y and Z position in the array.
2646 :
2647 : size_t start[MAX_NC_DIMS];
2648 2652 : memset(start, 0, sizeof(start));
2649 2652 : start[nBandXPos] = static_cast<size_t>(nBlockXOff) * nBlockXSize;
2650 :
2651 : // check y order.
2652 2652 : if (static_cast<netCDFDataset *>(poDS)->bBottomUp)
2653 : {
2654 2628 : if (nBlockYSize == 1)
2655 : {
2656 2628 : start[nBandYPos] = nRasterYSize - 1 - nBlockYOff;
2657 : }
2658 : else
2659 : {
2660 0 : CPLError(CE_Failure, CPLE_AppDefined,
2661 : "nBlockYSize = %d, only 1 supported when "
2662 : "writing bottom-up dataset",
2663 : nBlockYSize);
2664 0 : return CE_Failure;
2665 : }
2666 : }
2667 : else
2668 : {
2669 24 : start[nBandYPos] = static_cast<size_t>(nBlockYOff) * nBlockYSize; // y
2670 : }
2671 :
2672 2652 : size_t edge[MAX_NC_DIMS] = {};
2673 :
2674 2652 : edge[nBandXPos] = nBlockXSize;
2675 2652 : if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
2676 0 : edge[nBandXPos] = nRasterXSize - start[nBandXPos];
2677 2652 : edge[nBandYPos] = nBlockYSize;
2678 2652 : if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
2679 0 : edge[nBandYPos] = nRasterYSize - start[nBandYPos];
2680 :
2681 2652 : if (nd == 3)
2682 : {
2683 610 : start[panBandZPos[0]] = nLevel; // z
2684 610 : edge[panBandZPos[0]] = 1;
2685 : }
2686 :
2687 : // Compute multidimention band position.
2688 : //
2689 : // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
2690 : // if Data[2,3,4,x,y]
2691 : //
2692 : // BandPos0 = (nBand) / (3*4)
2693 : // BandPos1 = (nBand - (3*4)) / (4)
2694 : // BandPos2 = (nBand - (3*4)) % (4)
2695 2652 : if (nd > 3)
2696 : {
2697 178 : int Sum = -1;
2698 178 : int Taken = 0;
2699 534 : for (int i = 0; i < nd - 2; i++)
2700 : {
2701 356 : if (i != nd - 2 - 1)
2702 : {
2703 178 : Sum = 1;
2704 356 : for (int j = i + 1; j < nd - 2; j++)
2705 : {
2706 178 : Sum *= panBandZLev[j];
2707 : }
2708 178 : start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
2709 178 : edge[panBandZPos[i]] = 1;
2710 : }
2711 : else
2712 : {
2713 178 : start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
2714 178 : edge[panBandZPos[i]] = 1;
2715 : }
2716 356 : Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
2717 : }
2718 : }
2719 :
2720 : // Make sure we are in data mode.
2721 2652 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
2722 :
2723 : // Copy data according to type.
2724 2652 : int status = 0;
2725 2652 : if (eDataType == GDT_Byte)
2726 : {
2727 2093 : if (bSignedData)
2728 0 : status = nc_put_vara_schar(cdfid, nZId, start, edge,
2729 : static_cast<signed char *>(pImage));
2730 : else
2731 2093 : status = nc_put_vara_uchar(cdfid, nZId, start, edge,
2732 : static_cast<unsigned char *>(pImage));
2733 : }
2734 559 : else if (eDataType == GDT_Int8)
2735 : {
2736 40 : status = nc_put_vara_schar(cdfid, nZId, start, edge,
2737 : static_cast<signed char *>(pImage));
2738 : }
2739 519 : else if (nc_datatype == NC_SHORT)
2740 : {
2741 101 : status = nc_put_vara_short(cdfid, nZId, start, edge,
2742 : static_cast<short *>(pImage));
2743 : }
2744 418 : else if (eDataType == GDT_Int32)
2745 : {
2746 210 : status = nc_put_vara_int(cdfid, nZId, start, edge,
2747 : static_cast<int *>(pImage));
2748 : }
2749 208 : else if (eDataType == GDT_Float32)
2750 : {
2751 128 : status = nc_put_vara_float(cdfid, nZId, start, edge,
2752 : static_cast<float *>(pImage));
2753 : }
2754 80 : else if (eDataType == GDT_Float64)
2755 : {
2756 50 : status = nc_put_vara_double(cdfid, nZId, start, edge,
2757 : static_cast<double *>(pImage));
2758 : }
2759 30 : else if (eDataType == GDT_UInt16 &&
2760 12 : static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2761 : {
2762 12 : status = nc_put_vara_ushort(cdfid, nZId, start, edge,
2763 : static_cast<unsigned short *>(pImage));
2764 : }
2765 18 : else if (eDataType == GDT_UInt32 &&
2766 12 : static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2767 : {
2768 12 : status = nc_put_vara_uint(cdfid, nZId, start, edge,
2769 : static_cast<unsigned int *>(pImage));
2770 : }
2771 6 : else if (eDataType == GDT_UInt64 &&
2772 3 : static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2773 : {
2774 3 : status =
2775 3 : nc_put_vara_ulonglong(cdfid, nZId, start, edge,
2776 : static_cast<unsigned long long *>(pImage));
2777 : }
2778 3 : else if (eDataType == GDT_Int64 &&
2779 3 : static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2780 : {
2781 3 : status = nc_put_vara_longlong(cdfid, nZId, start, edge,
2782 : static_cast<long long *>(pImage));
2783 : }
2784 : else
2785 : {
2786 0 : CPLError(CE_Failure, CPLE_NotSupported,
2787 : "The NetCDF driver does not support GDAL data type %d",
2788 0 : eDataType);
2789 0 : status = NC_EBADTYPE;
2790 : }
2791 2652 : NCDF_ERR(status);
2792 :
2793 2652 : if (status != NC_NOERR)
2794 : {
2795 0 : CPLError(CE_Failure, CPLE_AppDefined,
2796 : "netCDF scanline write failed: %s", nc_strerror(status));
2797 0 : return CE_Failure;
2798 : }
2799 :
2800 2652 : return CE_None;
2801 : }
2802 :
2803 : /************************************************************************/
2804 : /* ==================================================================== */
2805 : /* netCDFDataset */
2806 : /* ==================================================================== */
2807 : /************************************************************************/
2808 :
2809 : /************************************************************************/
2810 : /* netCDFDataset() */
2811 : /************************************************************************/
2812 :
2813 966 : netCDFDataset::netCDFDataset()
2814 : :
2815 : // Basic dataset vars.
2816 : #ifdef ENABLE_NCDUMP
2817 : bFileToDestroyAtClosing(false),
2818 : #endif
2819 : cdfid(-1), nSubDatasets(0), papszSubDatasets(nullptr),
2820 : papszMetadata(nullptr), bBottomUp(true), eFormat(NCDF_FORMAT_NONE),
2821 : bIsGdalFile(false), bIsGdalCfFile(false), pszCFProjection(nullptr),
2822 : pszCFCoordinates(nullptr), nCFVersion(1.6), bSGSupport(false),
2823 966 : eMultipleLayerBehavior(SINGLE_LAYER), logCount(0), vcdf(this, cdfid),
2824 966 : GeometryScribe(vcdf, this->generateLogName()),
2825 966 : FieldScribe(vcdf, this->generateLogName()),
2826 1932 : bufManager(CPLGetUsablePhysicalRAM() / 5),
2827 :
2828 : // projection/GT.
2829 : nXDimID(-1), nYDimID(-1), bIsProjected(false),
2830 : bIsGeographic(false), // Can be not projected, and also not geographic
2831 : // State vars.
2832 : bDefineMode(true), bAddedGridMappingRef(false),
2833 :
2834 : // Create vars.
2835 : papszCreationOptions(nullptr), eCompress(NCDF_COMPRESS_NONE),
2836 : nZLevel(NCDF_DEFLATE_LEVEL), bChunking(false), nCreateMode(NC_CLOBBER),
2837 2898 : bSignedData(true)
2838 : {
2839 966 : m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2840 :
2841 : // Projection/GT.
2842 966 : m_adfGeoTransform[0] = 0.0;
2843 966 : m_adfGeoTransform[1] = 1.0;
2844 966 : m_adfGeoTransform[2] = 0.0;
2845 966 : m_adfGeoTransform[3] = 0.0;
2846 966 : m_adfGeoTransform[4] = 0.0;
2847 966 : m_adfGeoTransform[5] = 1.0;
2848 :
2849 : // Set buffers
2850 966 : bufManager.addBuffer(&(GeometryScribe.getMemBuffer()));
2851 966 : bufManager.addBuffer(&(FieldScribe.getMemBuffer()));
2852 966 : }
2853 :
2854 : /************************************************************************/
2855 : /* ~netCDFDataset() */
2856 : /************************************************************************/
2857 :
2858 1871 : netCDFDataset::~netCDFDataset()
2859 :
2860 : {
2861 966 : netCDFDataset::Close();
2862 1871 : }
2863 :
2864 : /************************************************************************/
2865 : /* Close() */
2866 : /************************************************************************/
2867 :
2868 1694 : CPLErr netCDFDataset::Close()
2869 : {
2870 1694 : CPLErr eErr = CE_None;
2871 1694 : if (nOpenFlags != OPEN_FLAGS_CLOSED)
2872 : {
2873 1932 : CPLMutexHolderD(&hNCMutex);
2874 :
2875 : #ifdef NCDF_DEBUG
2876 : CPLDebug("GDAL_netCDF",
2877 : "netCDFDataset::~netCDFDataset(), cdfid=%d filename=%s", cdfid,
2878 : osFilename.c_str());
2879 : #endif
2880 :
2881 : // Write data related to geotransform
2882 1197 : if (GetAccess() == GA_Update && !m_bAddedProjectionVarsData &&
2883 231 : (m_bHasProjection || m_bHasGeoTransform))
2884 : {
2885 : // Ensure projection is written if GeoTransform OR Projection are
2886 : // missing.
2887 37 : if (!m_bAddedProjectionVarsDefs)
2888 : {
2889 2 : AddProjectionVars(true, nullptr, nullptr);
2890 : }
2891 37 : AddProjectionVars(false, nullptr, nullptr);
2892 : }
2893 :
2894 966 : if (netCDFDataset::FlushCache(true) != CE_None)
2895 0 : eErr = CE_Failure;
2896 :
2897 966 : if (GetAccess() == GA_Update && !SGCommitPendingTransaction())
2898 0 : eErr = CE_Failure;
2899 :
2900 968 : for (size_t i = 0; i < apoVectorDatasets.size(); i++)
2901 2 : delete apoVectorDatasets[i];
2902 :
2903 : // Make sure projection variable is written to band variable.
2904 966 : if (GetAccess() == GA_Update && !bAddedGridMappingRef)
2905 : {
2906 244 : if (!AddGridMappingRef())
2907 0 : eErr = CE_Failure;
2908 : }
2909 :
2910 966 : CSLDestroy(papszMetadata);
2911 966 : CSLDestroy(papszSubDatasets);
2912 966 : CSLDestroy(papszCreationOptions);
2913 :
2914 966 : CPLFree(pszCFProjection);
2915 :
2916 966 : if (cdfid > 0)
2917 : {
2918 : #ifdef NCDF_DEBUG
2919 : CPLDebug("GDAL_netCDF", "calling nc_close( %d)", cdfid);
2920 : #endif
2921 643 : int status = GDAL_nc_close(cdfid);
2922 : #ifdef ENABLE_UFFD
2923 643 : NETCDF_UFFD_UNMAP(pCtx);
2924 : #endif
2925 643 : NCDF_ERR(status);
2926 643 : if (status != NC_NOERR)
2927 0 : eErr = CE_Failure;
2928 : }
2929 :
2930 966 : if (fpVSIMEM)
2931 15 : VSIFCloseL(fpVSIMEM);
2932 :
2933 : #ifdef ENABLE_NCDUMP
2934 966 : if (bFileToDestroyAtClosing)
2935 0 : VSIUnlink(osFilename);
2936 : #endif
2937 :
2938 966 : if (GDALPamDataset::Close() != CE_None)
2939 0 : eErr = CE_Failure;
2940 : }
2941 1694 : return eErr;
2942 : }
2943 :
2944 : /************************************************************************/
2945 : /* SetDefineMode() */
2946 : /************************************************************************/
2947 10452 : bool netCDFDataset::SetDefineMode(bool bNewDefineMode)
2948 : {
2949 : // Do nothing if already in new define mode
2950 : // or if dataset is in read-only mode or if dataset is true NC4 dataset.
2951 11000 : if (bDefineMode == bNewDefineMode || GetAccess() == GA_ReadOnly ||
2952 548 : eFormat == NCDF_FORMAT_NC4)
2953 10051 : return true;
2954 :
2955 401 : CPLDebug("GDAL_netCDF", "SetDefineMode(%d) old=%d",
2956 401 : static_cast<int>(bNewDefineMode), static_cast<int>(bDefineMode));
2957 :
2958 401 : bDefineMode = bNewDefineMode;
2959 :
2960 : int status;
2961 401 : if (bDefineMode)
2962 139 : status = nc_redef(cdfid);
2963 : else
2964 262 : status = nc_enddef(cdfid);
2965 :
2966 401 : NCDF_ERR(status);
2967 401 : return status == NC_NOERR;
2968 : }
2969 :
2970 : /************************************************************************/
2971 : /* GetMetadataDomainList() */
2972 : /************************************************************************/
2973 :
2974 27 : char **netCDFDataset::GetMetadataDomainList()
2975 : {
2976 27 : char **papszDomains = BuildMetadataDomainList(
2977 : GDALDataset::GetMetadataDomainList(), TRUE, "SUBDATASETS", nullptr);
2978 28 : for (const auto &kv : m_oMapDomainToJSon)
2979 1 : papszDomains = CSLAddString(papszDomains, ("json:" + kv.first).c_str());
2980 27 : return papszDomains;
2981 : }
2982 :
2983 : /************************************************************************/
2984 : /* GetMetadata() */
2985 : /************************************************************************/
2986 353 : char **netCDFDataset::GetMetadata(const char *pszDomain)
2987 : {
2988 353 : if (pszDomain != nullptr && STARTS_WITH_CI(pszDomain, "SUBDATASETS"))
2989 36 : return papszSubDatasets;
2990 :
2991 317 : if (pszDomain != nullptr && STARTS_WITH(pszDomain, "json:"))
2992 : {
2993 1 : auto iter = m_oMapDomainToJSon.find(pszDomain + strlen("json:"));
2994 1 : if (iter != m_oMapDomainToJSon.end())
2995 1 : return iter->second.List();
2996 : }
2997 :
2998 316 : return GDALDataset::GetMetadata(pszDomain);
2999 : }
3000 :
3001 : /************************************************************************/
3002 : /* SetMetadataItem() */
3003 : /************************************************************************/
3004 :
3005 42 : CPLErr netCDFDataset::SetMetadataItem(const char *pszName, const char *pszValue,
3006 : const char *pszDomain)
3007 : {
3008 83 : if (GetAccess() == GA_Update &&
3009 83 : (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)
3010 : {
3011 41 : std::string osName(pszName);
3012 :
3013 : // Same logic as in CopyMetadata()
3014 41 : if (STARTS_WITH(osName.c_str(), "NC_GLOBAL#"))
3015 8 : osName = osName.substr(strlen("NC_GLOBAL#"));
3016 33 : else if (strchr(osName.c_str(), '#') == nullptr)
3017 4 : osName = "GDAL_" + osName;
3018 :
3019 82 : if (STARTS_WITH(osName.c_str(), "NETCDF_DIM_") ||
3020 41 : strchr(osName.c_str(), '#') != nullptr)
3021 : {
3022 : // do nothing
3023 29 : return CE_None;
3024 : }
3025 : else
3026 : {
3027 12 : SetDefineMode(true);
3028 :
3029 12 : if (!NCDFPutAttr(cdfid, NC_GLOBAL, osName.c_str(), pszValue))
3030 12 : return CE_Failure;
3031 : }
3032 : }
3033 :
3034 1 : return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);
3035 : }
3036 :
3037 : /************************************************************************/
3038 : /* SetMetadata() */
3039 : /************************************************************************/
3040 :
3041 7 : CPLErr netCDFDataset::SetMetadata(char **papszMD, const char *pszDomain)
3042 : {
3043 11 : if (GetAccess() == GA_Update &&
3044 4 : (pszDomain == nullptr || pszDomain[0] == '\0'))
3045 : {
3046 : // We don't handle metadata item removal for now
3047 48 : for (const char *const *papszIter = papszMD; papszIter && *papszIter;
3048 : ++papszIter)
3049 : {
3050 41 : char *pszName = nullptr;
3051 41 : const char *pszValue = CPLParseNameValue(*papszIter, &pszName);
3052 41 : if (pszName && pszValue)
3053 41 : SetMetadataItem(pszName, pszValue);
3054 41 : CPLFree(pszName);
3055 : }
3056 7 : return CE_None;
3057 : }
3058 0 : return GDALPamDataset::SetMetadata(papszMD, pszDomain);
3059 : }
3060 :
3061 : /************************************************************************/
3062 : /* GetSpatialRef() */
3063 : /************************************************************************/
3064 :
3065 168 : const OGRSpatialReference *netCDFDataset::GetSpatialRef() const
3066 : {
3067 168 : if (m_bHasProjection)
3068 69 : return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
3069 :
3070 99 : return GDALPamDataset::GetSpatialRef();
3071 : }
3072 :
3073 : /************************************************************************/
3074 : /* FetchCopyParam() */
3075 : /************************************************************************/
3076 :
3077 312 : double netCDFDataset::FetchCopyParam(const char *pszGridMappingValue,
3078 : const char *pszParam, double dfDefault,
3079 : bool *pbFound)
3080 :
3081 : {
3082 : char *pszTemp =
3083 312 : CPLStrdup(CPLSPrintf("%s#%s", pszGridMappingValue, pszParam));
3084 312 : const char *pszValue = CSLFetchNameValue(papszMetadata, pszTemp);
3085 312 : CPLFree(pszTemp);
3086 :
3087 312 : if (pbFound)
3088 : {
3089 312 : *pbFound = (pszValue != nullptr);
3090 : }
3091 :
3092 312 : if (pszValue)
3093 : {
3094 0 : return CPLAtofM(pszValue);
3095 : }
3096 :
3097 312 : return dfDefault;
3098 : }
3099 :
3100 : /************************************************************************/
3101 : /* FetchStandardParallels() */
3102 : /************************************************************************/
3103 :
3104 : std::vector<std::string>
3105 0 : netCDFDataset::FetchStandardParallels(const char *pszGridMappingValue)
3106 : {
3107 : // cf-1.0 tags
3108 0 : const char *pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL);
3109 :
3110 0 : std::vector<std::string> ret;
3111 0 : if (pszValue != nullptr)
3112 : {
3113 0 : CPLStringList aosValues;
3114 0 : if (pszValue[0] != '{' &&
3115 0 : CPLString(pszValue).Trim().find(' ') != std::string::npos)
3116 : {
3117 : // Some files like
3118 : // ftp://data.knmi.nl/download/KNW-NetCDF-3D/1.0/noversion/2013/11/14/KNW-1.0_H37-ERA_NL_20131114.nc
3119 : // do not use standard formatting for arrays, but just space
3120 : // separated syntax
3121 0 : aosValues = CSLTokenizeString2(pszValue, " ", 0);
3122 : }
3123 : else
3124 : {
3125 0 : aosValues = NCDFTokenizeArray(pszValue);
3126 : }
3127 0 : for (int i = 0; i < aosValues.size(); i++)
3128 : {
3129 0 : ret.push_back(aosValues[i]);
3130 : }
3131 : }
3132 : // Try gdal tags.
3133 : else
3134 : {
3135 0 : pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_1);
3136 :
3137 0 : if (pszValue != nullptr)
3138 0 : ret.push_back(pszValue);
3139 :
3140 0 : pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_2);
3141 :
3142 0 : if (pszValue != nullptr)
3143 0 : ret.push_back(pszValue);
3144 : }
3145 :
3146 0 : return ret;
3147 : }
3148 :
3149 : /************************************************************************/
3150 : /* FetchAttr() */
3151 : /************************************************************************/
3152 :
3153 3550 : const char *netCDFDataset::FetchAttr(const char *pszVarFullName,
3154 : const char *pszAttr)
3155 :
3156 : {
3157 3550 : char *pszKey = CPLStrdup(CPLSPrintf("%s#%s", pszVarFullName, pszAttr));
3158 3550 : const char *pszValue = CSLFetchNameValue(papszMetadata, pszKey);
3159 3550 : CPLFree(pszKey);
3160 3550 : return pszValue;
3161 : }
3162 :
3163 2357 : const char *netCDFDataset::FetchAttr(int nGroupId, int nVarId,
3164 : const char *pszAttr)
3165 :
3166 : {
3167 2357 : char *pszVarFullName = nullptr;
3168 2357 : NCDFGetVarFullName(nGroupId, nVarId, &pszVarFullName);
3169 2357 : const char *pszValue = FetchAttr(pszVarFullName, pszAttr);
3170 2357 : CPLFree(pszVarFullName);
3171 2357 : return pszValue;
3172 : }
3173 :
3174 : /************************************************************************/
3175 : /* IsDifferenceBelow() */
3176 : /************************************************************************/
3177 :
3178 1039 : static bool IsDifferenceBelow(double dfA, double dfB, double dfError)
3179 : {
3180 1039 : const double dfAbsDiff = fabs(dfA - dfB);
3181 1039 : return dfAbsDiff <= dfError;
3182 : }
3183 :
3184 : /************************************************************************/
3185 : /* SetProjectionFromVar() */
3186 : /************************************************************************/
3187 490 : void netCDFDataset::SetProjectionFromVar(
3188 : int nGroupId, int nVarId, bool bReadSRSOnly, const char *pszGivenGM,
3189 : std::string *returnProjStr, nccfdriver::SGeometry_Reader *sg,
3190 : std::vector<std::string> *paosRemovedMDItems)
3191 : {
3192 490 : bool bGotGeogCS = false;
3193 490 : bool bGotCfSRS = false;
3194 490 : bool bGotCfWktSRS = false;
3195 490 : bool bGotGdalSRS = false;
3196 490 : bool bGotCfGT = false;
3197 490 : bool bGotGdalGT = false;
3198 :
3199 : // These values from CF metadata.
3200 490 : OGRSpatialReference oSRS;
3201 490 : oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3202 490 : size_t xdim = nRasterXSize;
3203 490 : size_t ydim = nRasterYSize;
3204 :
3205 : // These values from GDAL metadata.
3206 490 : const char *pszWKT = nullptr;
3207 490 : const char *pszGeoTransform = nullptr;
3208 :
3209 490 : netCDFDataset *poDS = this; // Perhaps this should be removed for clarity.
3210 :
3211 490 : CPLDebug("GDAL_netCDF", "\n=====\nSetProjectionFromVar( %d, %d)", nGroupId,
3212 : nVarId);
3213 :
3214 : // Get x/y range information.
3215 :
3216 : // Temp variables to use in SetGeoTransform() and SetProjection().
3217 490 : double adfTempGeoTransform[6] = {0.0, 1.0, 0.0, 0.0, 0.0, 1.0};
3218 :
3219 : // Look for grid_mapping metadata.
3220 490 : const char *pszValue = pszGivenGM;
3221 490 : CPLString osTmpGridMapping; // let is in this outer scope as pszValue may
3222 : // point to it
3223 490 : if (pszValue == nullptr)
3224 : {
3225 447 : pszValue = FetchAttr(nGroupId, nVarId, CF_GRD_MAPPING);
3226 447 : if (pszValue && strchr(pszValue, ':') && strchr(pszValue, ' '))
3227 : {
3228 : // Expanded form of grid_mapping
3229 : // e.g. "crsOSGB: x y crsWGS84: lat lon"
3230 : // Pickup the grid_mapping whose coordinates are dimensions of the
3231 : // variable
3232 6 : CPLStringList aosTokens(CSLTokenizeString2(pszValue, " ", 0));
3233 3 : if ((aosTokens.size() % 3) == 0)
3234 : {
3235 3 : for (int i = 0; i < aosTokens.size() / 3; i++)
3236 : {
3237 3 : if (CSLFindString(poDS->papszDimName,
3238 9 : aosTokens[3 * i + 1]) >= 0 &&
3239 3 : CSLFindString(poDS->papszDimName,
3240 3 : aosTokens[3 * i + 2]) >= 0)
3241 : {
3242 3 : osTmpGridMapping = aosTokens[3 * i];
3243 6 : if (!osTmpGridMapping.empty() &&
3244 3 : osTmpGridMapping.back() == ':')
3245 : {
3246 3 : osTmpGridMapping.resize(osTmpGridMapping.size() -
3247 : 1);
3248 : }
3249 3 : pszValue = osTmpGridMapping.c_str();
3250 3 : break;
3251 : }
3252 : }
3253 : }
3254 : }
3255 : }
3256 490 : char *pszGridMappingValue = CPLStrdup(pszValue ? pszValue : "");
3257 :
3258 490 : if (!EQUAL(pszGridMappingValue, ""))
3259 : {
3260 : // Read grid_mapping metadata.
3261 211 : int nProjGroupID = -1;
3262 211 : int nProjVarID = -1;
3263 211 : if (NCDFResolveVar(nGroupId, pszGridMappingValue, &nProjGroupID,
3264 211 : &nProjVarID) == CE_None)
3265 : {
3266 210 : poDS->ReadAttributes(nProjGroupID, nProjVarID);
3267 :
3268 : // Look for GDAL spatial_ref and GeoTransform within grid_mapping.
3269 210 : CPLFree(pszGridMappingValue);
3270 210 : pszGridMappingValue = nullptr;
3271 210 : NCDFGetVarFullName(nProjGroupID, nProjVarID, &pszGridMappingValue);
3272 210 : if (pszGridMappingValue)
3273 : {
3274 210 : CPLDebug("GDAL_netCDF", "got grid_mapping %s",
3275 : pszGridMappingValue);
3276 210 : pszWKT = FetchAttr(pszGridMappingValue, NCDF_SPATIAL_REF);
3277 210 : if (!pszWKT)
3278 : {
3279 34 : pszWKT = FetchAttr(pszGridMappingValue, NCDF_CRS_WKT);
3280 : }
3281 : else
3282 : {
3283 176 : bGotGdalSRS = true;
3284 176 : CPLDebug("GDAL_netCDF", "setting WKT from GDAL");
3285 : }
3286 210 : if (pszWKT)
3287 : {
3288 180 : if (!bGotGdalSRS)
3289 : {
3290 4 : bGotCfWktSRS = true;
3291 4 : CPLDebug("GDAL_netCDF", "setting WKT from CF");
3292 : }
3293 180 : if (returnProjStr != nullptr)
3294 : {
3295 41 : (*returnProjStr) = std::string(pszWKT);
3296 : }
3297 : else
3298 : {
3299 139 : m_bAddedProjectionVarsDefs = true;
3300 139 : m_bAddedProjectionVarsData = true;
3301 278 : OGRSpatialReference oSRSTmp;
3302 139 : oSRSTmp.SetAxisMappingStrategy(
3303 : OAMS_TRADITIONAL_GIS_ORDER);
3304 139 : oSRSTmp.importFromWkt(pszWKT);
3305 139 : SetSpatialRefNoUpdate(&oSRSTmp);
3306 : }
3307 : pszGeoTransform =
3308 180 : FetchAttr(pszGridMappingValue, NCDF_GEOTRANSFORM);
3309 : }
3310 : }
3311 : else
3312 : {
3313 0 : pszGridMappingValue = CPLStrdup("");
3314 : }
3315 : }
3316 : }
3317 :
3318 : // Get information about the file.
3319 : //
3320 : // Was this file created by the GDAL netcdf driver?
3321 : // Was this file created by the newer (CF-conformant) driver?
3322 : //
3323 : // 1) If GDAL netcdf metadata is set, and version >= 1.9,
3324 : // it was created with the new driver
3325 : // 2) Else, if spatial_ref and GeoTransform are present in the
3326 : // grid_mapping variable, it was created by the old driver
3327 490 : pszValue = FetchAttr("NC_GLOBAL", "GDAL");
3328 :
3329 490 : if (pszValue && NCDFIsGDALVersionGTE(pszValue, 1900))
3330 : {
3331 236 : bIsGdalFile = true;
3332 236 : bIsGdalCfFile = true;
3333 : }
3334 254 : else if (pszWKT != nullptr && pszGeoTransform != nullptr)
3335 : {
3336 14 : bIsGdalFile = true;
3337 14 : bIsGdalCfFile = false;
3338 : }
3339 :
3340 : // Set default bottom-up default value.
3341 : // Y axis dimension and absence of GT can modify this value.
3342 : // Override with Config option GDAL_NETCDF_BOTTOMUP.
3343 :
3344 : // New driver is bottom-up by default.
3345 490 : if ((bIsGdalFile && !bIsGdalCfFile) || bSwitchedXY)
3346 16 : poDS->bBottomUp = false;
3347 : else
3348 474 : poDS->bBottomUp = true;
3349 :
3350 490 : CPLDebug("GDAL_netCDF",
3351 : "bIsGdalFile=%d bIsGdalCfFile=%d bSwitchedXY=%d bBottomUp=%d",
3352 490 : static_cast<int>(bIsGdalFile), static_cast<int>(bIsGdalCfFile),
3353 490 : static_cast<int>(bSwitchedXY), static_cast<int>(bBottomUp));
3354 :
3355 : // Read projection coordinates.
3356 :
3357 490 : int nGroupDimXID = -1;
3358 490 : int nVarDimXID = -1;
3359 490 : int nGroupDimYID = -1;
3360 490 : int nVarDimYID = -1;
3361 490 : if (sg != nullptr)
3362 : {
3363 43 : nGroupDimXID = sg->get_ncID();
3364 43 : nGroupDimYID = sg->get_ncID();
3365 43 : nVarDimXID = sg->getNodeCoordVars()[0];
3366 43 : nVarDimYID = sg->getNodeCoordVars()[1];
3367 : }
3368 :
3369 490 : if (!bReadSRSOnly)
3370 : {
3371 337 : NCDFResolveVar(nGroupId, poDS->papszDimName[nXDimID], &nGroupDimXID,
3372 : &nVarDimXID);
3373 337 : NCDFResolveVar(nGroupId, poDS->papszDimName[nYDimID], &nGroupDimYID,
3374 : &nVarDimYID);
3375 : // TODO: if above resolving fails we should also search for coordinate
3376 : // variables without same name than dimension using the same resolving
3377 : // logic. This should handle for example NASA Ocean Color L2 products.
3378 :
3379 : const bool bIgnoreXYAxisNameChecks =
3380 674 : CPLTestBool(CSLFetchNameValueDef(
3381 337 : papszOpenOptions, "IGNORE_XY_AXIS_NAME_CHECKS",
3382 : CPLGetConfigOption("GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS",
3383 337 : "NO"))) ||
3384 : // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a res
3385 : // and transform attributes
3386 337 : (FetchAttr(nGroupId, nVarId, "res") != nullptr &&
3387 674 : FetchAttr(nGroupId, nVarId, "transform") != nullptr) ||
3388 336 : FetchAttr(nGroupId, NC_GLOBAL, "GMT_version") != nullptr;
3389 :
3390 : // Check that they are 1D or 2D variables
3391 337 : if (nVarDimXID >= 0)
3392 : {
3393 244 : int ndims = -1;
3394 244 : nc_inq_varndims(nGroupId, nVarDimXID, &ndims);
3395 244 : if (ndims == 0 || ndims > 2)
3396 0 : nVarDimXID = -1;
3397 244 : else if (!bIgnoreXYAxisNameChecks)
3398 : {
3399 242 : if (!NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
3400 159 : !NCDFIsVarProjectionX(nGroupId, nVarDimXID, nullptr) &&
3401 : // In case of inversion of X/Y
3402 432 : !NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr) &&
3403 31 : !NCDFIsVarProjectionY(nGroupId, nVarDimXID, nullptr))
3404 : {
3405 : char szVarNameX[NC_MAX_NAME + 1];
3406 31 : CPL_IGNORE_RET_VAL(
3407 31 : nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
3408 31 : if (!(ndims == 1 &&
3409 30 : (EQUAL(szVarNameX, CF_LONGITUDE_STD_NAME) ||
3410 29 : EQUAL(szVarNameX, CF_LONGITUDE_VAR_NAME))))
3411 : {
3412 30 : CPLDebug(
3413 : "netCDF",
3414 : "Georeferencing ignored due to non-specific "
3415 : "enough X axis name. "
3416 : "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
3417 : "as configuration option to bypass this check");
3418 30 : nVarDimXID = -1;
3419 : }
3420 : }
3421 : }
3422 : }
3423 :
3424 337 : if (nVarDimYID >= 0)
3425 : {
3426 246 : int ndims = -1;
3427 246 : nc_inq_varndims(nGroupId, nVarDimYID, &ndims);
3428 246 : if (ndims == 0 || ndims > 2)
3429 1 : nVarDimYID = -1;
3430 245 : else if (!bIgnoreXYAxisNameChecks)
3431 : {
3432 243 : if (!NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr) &&
3433 160 : !NCDFIsVarProjectionY(nGroupId, nVarDimYID, nullptr) &&
3434 : // In case of inversion of X/Y
3435 435 : !NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
3436 32 : !NCDFIsVarProjectionX(nGroupId, nVarDimYID, nullptr))
3437 : {
3438 : char szVarNameY[NC_MAX_NAME + 1];
3439 32 : CPL_IGNORE_RET_VAL(
3440 32 : nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
3441 32 : if (!(ndims == 1 &&
3442 32 : (EQUAL(szVarNameY, CF_LATITUDE_STD_NAME) ||
3443 31 : EQUAL(szVarNameY, CF_LATITUDE_VAR_NAME))))
3444 : {
3445 31 : CPLDebug(
3446 : "netCDF",
3447 : "Georeferencing ignored due to non-specific "
3448 : "enough Y axis name. "
3449 : "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
3450 : "as configuration option to bypass this check");
3451 31 : nVarDimYID = -1;
3452 : }
3453 : }
3454 : }
3455 : }
3456 :
3457 337 : if ((nVarDimXID >= 0 && xdim == 1) || (nVarDimXID >= 0 && ydim == 1))
3458 : {
3459 0 : CPLError(CE_Warning, CPLE_AppDefined,
3460 : "1-pixel width/height files not supported, "
3461 : "xdim: %ld ydim: %ld",
3462 : static_cast<long>(xdim), static_cast<long>(ydim));
3463 0 : nVarDimXID = -1;
3464 0 : nVarDimYID = -1;
3465 : }
3466 : }
3467 :
3468 490 : const char *pszUnits = nullptr;
3469 490 : if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
3470 : {
3471 257 : const char *pszUnitsX = FetchAttr(nGroupDimXID, nVarDimXID, "units");
3472 257 : const char *pszUnitsY = FetchAttr(nGroupDimYID, nVarDimYID, "units");
3473 : // Normalize degrees_east/degrees_north to degrees
3474 : // Cf https://github.com/OSGeo/gdal/issues/11009
3475 257 : if (pszUnitsX && EQUAL(pszUnitsX, "degrees_east"))
3476 72 : pszUnitsX = "degrees";
3477 257 : if (pszUnitsY && EQUAL(pszUnitsY, "degrees_north"))
3478 72 : pszUnitsY = "degrees";
3479 :
3480 257 : if (pszUnitsX && pszUnitsY)
3481 : {
3482 210 : if (EQUAL(pszUnitsX, pszUnitsY))
3483 207 : pszUnits = pszUnitsX;
3484 3 : else if (!pszWKT && !EQUAL(pszGridMappingValue, ""))
3485 : {
3486 0 : CPLError(CE_Failure, CPLE_AppDefined,
3487 : "X axis unit (%s) is different from Y axis "
3488 : "unit (%s). SRS will ignore axis unit and be "
3489 : "likely wrong.",
3490 : pszUnitsX, pszUnitsY);
3491 : }
3492 : }
3493 47 : else if (pszUnitsX && !pszWKT && !EQUAL(pszGridMappingValue, ""))
3494 : {
3495 0 : CPLError(CE_Failure, CPLE_AppDefined,
3496 : "X axis unit is defined, but not Y one ."
3497 : "SRS will ignore axis unit and be likely wrong.");
3498 : }
3499 47 : else if (pszUnitsY && !pszWKT && !EQUAL(pszGridMappingValue, ""))
3500 : {
3501 0 : CPLError(CE_Failure, CPLE_AppDefined,
3502 : "Y axis unit is defined, but not X one ."
3503 : "SRS will ignore axis unit and be likely wrong.");
3504 : }
3505 : }
3506 :
3507 490 : if (!pszWKT && !EQUAL(pszGridMappingValue, ""))
3508 : {
3509 31 : CPLStringList aosGridMappingKeyValues;
3510 31 : const size_t nLenGridMappingValue = strlen(pszGridMappingValue);
3511 789 : for (const char *const *papszIter = papszMetadata;
3512 789 : papszIter && *papszIter; ++papszIter)
3513 : {
3514 758 : if (STARTS_WITH(*papszIter, pszGridMappingValue) &&
3515 236 : (*papszIter)[nLenGridMappingValue] == '#')
3516 : {
3517 236 : char *pszKey = nullptr;
3518 472 : pszValue = CPLParseNameValue(
3519 236 : *papszIter + nLenGridMappingValue + 1, &pszKey);
3520 236 : if (pszKey && pszValue)
3521 236 : aosGridMappingKeyValues.SetNameValue(pszKey, pszValue);
3522 236 : CPLFree(pszKey);
3523 : }
3524 : }
3525 :
3526 31 : bGotGeogCS = aosGridMappingKeyValues.FetchNameValue(
3527 : CF_PP_SEMI_MAJOR_AXIS) != nullptr;
3528 :
3529 31 : oSRS.importFromCF1(aosGridMappingKeyValues.List(), pszUnits);
3530 31 : bGotCfSRS = oSRS.IsGeographic() || oSRS.IsProjected();
3531 : }
3532 : else
3533 : {
3534 : // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a "crs"
3535 : // attribute hold on the variable of interest that contains a PROJ.4
3536 : // string
3537 459 : pszValue = FetchAttr(nGroupId, nVarId, "crs");
3538 460 : if (pszValue &&
3539 1 : (strstr(pszValue, "+proj=") != nullptr ||
3540 0 : strstr(pszValue, "GEOGCS") != nullptr ||
3541 0 : strstr(pszValue, "PROJCS") != nullptr ||
3542 460 : strstr(pszValue, "EPSG:") != nullptr) &&
3543 1 : oSRS.SetFromUserInput(pszValue) == OGRERR_NONE)
3544 : {
3545 1 : bGotCfSRS = true;
3546 : }
3547 : }
3548 :
3549 : // Set Projection from CF.
3550 490 : double dfLinearUnitsConvFactor = 1.0;
3551 490 : if ((bGotGeogCS || bGotCfSRS))
3552 : {
3553 31 : if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
3554 : {
3555 : // Set SRS Units.
3556 :
3557 : // Check units for x and y.
3558 28 : if (oSRS.IsProjected())
3559 : {
3560 25 : dfLinearUnitsConvFactor = oSRS.GetLinearUnits(nullptr);
3561 :
3562 : // If the user doesn't ask to preserve the axis unit,
3563 : // then normalize to metre
3564 31 : if (dfLinearUnitsConvFactor != 1.0 &&
3565 6 : !CPLFetchBool(GetOpenOptions(), "PRESERVE_AXIS_UNIT_IN_CRS",
3566 : false))
3567 : {
3568 5 : oSRS.SetLinearUnits("metre", 1.0);
3569 5 : oSRS.SetAuthority("PROJCS|UNIT", "EPSG", 9001);
3570 : }
3571 : else
3572 : {
3573 20 : dfLinearUnitsConvFactor = 1.0;
3574 : }
3575 : }
3576 : }
3577 :
3578 : // Set projection.
3579 31 : char *pszTempProjection = nullptr;
3580 31 : oSRS.exportToWkt(&pszTempProjection);
3581 31 : if (pszTempProjection)
3582 : {
3583 31 : CPLDebug("GDAL_netCDF", "setting WKT from CF");
3584 31 : if (returnProjStr != nullptr)
3585 : {
3586 2 : (*returnProjStr) = std::string(pszTempProjection);
3587 : }
3588 : else
3589 : {
3590 29 : m_bAddedProjectionVarsDefs = true;
3591 29 : m_bAddedProjectionVarsData = true;
3592 29 : SetSpatialRefNoUpdate(&oSRS);
3593 : }
3594 : }
3595 31 : CPLFree(pszTempProjection);
3596 : }
3597 :
3598 490 : if (!bReadSRSOnly && (nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 &&
3599 : ydim > 0)
3600 : {
3601 : double *pdfXCoord =
3602 214 : static_cast<double *>(CPLCalloc(xdim, sizeof(double)));
3603 : double *pdfYCoord =
3604 214 : static_cast<double *>(CPLCalloc(ydim, sizeof(double)));
3605 :
3606 214 : size_t start[2] = {0, 0};
3607 214 : size_t edge[2] = {xdim, 0};
3608 214 : int status = nc_get_vara_double(nGroupDimXID, nVarDimXID, start, edge,
3609 : pdfXCoord);
3610 214 : NCDF_ERR(status);
3611 :
3612 214 : edge[0] = ydim;
3613 214 : status = nc_get_vara_double(nGroupDimYID, nVarDimYID, start, edge,
3614 : pdfYCoord);
3615 214 : NCDF_ERR(status);
3616 :
3617 214 : nc_type nc_var_dimx_datatype = NC_NAT;
3618 : status =
3619 214 : nc_inq_vartype(nGroupDimXID, nVarDimXID, &nc_var_dimx_datatype);
3620 214 : NCDF_ERR(status);
3621 :
3622 214 : nc_type nc_var_dimy_datatype = NC_NAT;
3623 : status =
3624 214 : nc_inq_vartype(nGroupDimYID, nVarDimYID, &nc_var_dimy_datatype);
3625 214 : NCDF_ERR(status);
3626 :
3627 214 : if (!poDS->bSwitchedXY)
3628 : {
3629 : // Convert ]180,540] longitude values to ]-180,0].
3630 295 : if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
3631 83 : CPLTestBool(
3632 : CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")))
3633 : {
3634 : // If minimum longitude is > 180, subtract 360 from all.
3635 : // Add a check on the maximum X value too, since
3636 : // NCDFIsVarLongitude() is not very specific by default (see
3637 : // https://github.com/OSGeo/gdal/issues/1440)
3638 90 : if (std::min(pdfXCoord[0], pdfXCoord[xdim - 1]) > 180.0 &&
3639 7 : std::max(pdfXCoord[0], pdfXCoord[xdim - 1]) <= 540)
3640 : {
3641 0 : CPLDebug(
3642 : "GDAL_netCDF",
3643 : "Offsetting longitudes from ]180,540] to ]-180,180]. "
3644 : "Can be disabled with GDAL_NETCDF_CENTERLONG_180=NO");
3645 0 : for (size_t i = 0; i < xdim; i++)
3646 0 : pdfXCoord[i] -= 360;
3647 : }
3648 : }
3649 : }
3650 :
3651 : // Is pixel spacing uniform across the map?
3652 :
3653 : // Check Longitude.
3654 :
3655 214 : bool bLonSpacingOK = false;
3656 214 : if (xdim == 2)
3657 : {
3658 28 : bLonSpacingOK = true;
3659 : }
3660 : else
3661 : {
3662 186 : bool bWestIsLeft = (pdfXCoord[0] < pdfXCoord[xdim - 1]);
3663 :
3664 : // fix longitudes if longitudes should increase from
3665 : // west to east, but west > east
3666 259 : if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
3667 73 : !bWestIsLeft)
3668 : {
3669 2 : size_t ndecreases = 0;
3670 :
3671 : // there is lon wrap if longitudes increase
3672 : // with one single decrease
3673 107 : for (size_t i = 1; i < xdim; i++)
3674 : {
3675 105 : if (pdfXCoord[i] < pdfXCoord[i - 1])
3676 1 : ndecreases++;
3677 : }
3678 :
3679 2 : if (ndecreases == 1)
3680 : {
3681 1 : CPLDebug("GDAL_netCDF", "longitude wrap detected");
3682 4 : for (size_t i = 0; i < xdim; i++)
3683 : {
3684 3 : if (pdfXCoord[i] > pdfXCoord[xdim - 1])
3685 1 : pdfXCoord[i] -= 360;
3686 : }
3687 : }
3688 : }
3689 :
3690 186 : const double dfSpacingBegin = pdfXCoord[1] - pdfXCoord[0];
3691 186 : const double dfSpacingMiddle =
3692 186 : pdfXCoord[xdim / 2 + 1] - pdfXCoord[xdim / 2];
3693 186 : const double dfSpacingLast =
3694 186 : pdfXCoord[xdim - 1] - pdfXCoord[xdim - 2];
3695 :
3696 186 : CPLDebug("GDAL_netCDF",
3697 : "xdim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
3698 : "dfSpacingLast: %f",
3699 : static_cast<long>(xdim), dfSpacingBegin, dfSpacingMiddle,
3700 : dfSpacingLast);
3701 : #ifdef NCDF_DEBUG
3702 : CPLDebug("GDAL_netCDF", "xcoords: %f %f %f %f %f %f", pdfXCoord[0],
3703 : pdfXCoord[1], pdfXCoord[xdim / 2],
3704 : pdfXCoord[(xdim / 2) + 1], pdfXCoord[xdim - 2],
3705 : pdfXCoord[xdim - 1]);
3706 : #endif
3707 :
3708 : // ftp://ftp.cdc.noaa.gov/Datasets/NARR/Dailies/monolevel/vwnd.10m.2015.nc
3709 : // requires a 0.02% tolerance, so let's settle for 0.05%
3710 :
3711 : // For float variables, increase to 0.2% (as seen in
3712 : // https://github.com/OSGeo/gdal/issues/3663)
3713 186 : const double dfEpsRel =
3714 186 : nc_var_dimx_datatype == NC_FLOAT ? 0.002 : 0.0005;
3715 :
3716 : const double dfEps =
3717 : dfEpsRel *
3718 372 : std::max(fabs(dfSpacingBegin),
3719 186 : std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
3720 370 : if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
3721 370 : IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
3722 184 : IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
3723 : {
3724 184 : bLonSpacingOK = true;
3725 : }
3726 2 : else if (CPLTestBool(CPLGetConfigOption(
3727 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
3728 : {
3729 0 : bLonSpacingOK = true;
3730 0 : CPLDebug(
3731 : "GDAL_netCDF",
3732 : "Longitude/X is not equally spaced, but will be considered "
3733 : "as such because of "
3734 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
3735 : }
3736 : }
3737 :
3738 214 : if (bLonSpacingOK == false)
3739 : {
3740 2 : CPLDebug(
3741 : "GDAL_netCDF", "%s",
3742 : "Longitude/X is not equally spaced (with a 0.05% tolerance). "
3743 : "You may set the "
3744 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
3745 : "option to YES to ignore this check");
3746 : }
3747 :
3748 : // Check Latitude.
3749 214 : bool bLatSpacingOK = false;
3750 :
3751 214 : if (ydim == 2)
3752 : {
3753 48 : bLatSpacingOK = true;
3754 : }
3755 : else
3756 : {
3757 166 : const double dfSpacingBegin = pdfYCoord[1] - pdfYCoord[0];
3758 166 : const double dfSpacingMiddle =
3759 166 : pdfYCoord[ydim / 2 + 1] - pdfYCoord[ydim / 2];
3760 :
3761 166 : const double dfSpacingLast =
3762 166 : pdfYCoord[ydim - 1] - pdfYCoord[ydim - 2];
3763 :
3764 166 : CPLDebug("GDAL_netCDF",
3765 : "ydim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
3766 : "dfSpacingLast: %f",
3767 : (long)ydim, dfSpacingBegin, dfSpacingMiddle,
3768 : dfSpacingLast);
3769 : #ifdef NCDF_DEBUG
3770 : CPLDebug("GDAL_netCDF", "ycoords: %f %f %f %f %f %f", pdfYCoord[0],
3771 : pdfYCoord[1], pdfYCoord[ydim / 2],
3772 : pdfYCoord[(ydim / 2) + 1], pdfYCoord[ydim - 2],
3773 : pdfYCoord[ydim - 1]);
3774 : #endif
3775 :
3776 166 : const double dfEpsRel =
3777 166 : nc_var_dimy_datatype == NC_FLOAT ? 0.002 : 0.0005;
3778 :
3779 : const double dfEps =
3780 : dfEpsRel *
3781 332 : std::max(fabs(dfSpacingBegin),
3782 166 : std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
3783 330 : if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
3784 330 : IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
3785 155 : IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
3786 : {
3787 155 : bLatSpacingOK = true;
3788 : }
3789 11 : else if (CPLTestBool(CPLGetConfigOption(
3790 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
3791 : {
3792 0 : bLatSpacingOK = true;
3793 0 : CPLDebug(
3794 : "GDAL_netCDF",
3795 : "Latitude/Y is not equally spaced, but will be considered "
3796 : "as such because of "
3797 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
3798 : }
3799 11 : else if (!oSRS.IsProjected() &&
3800 11 : fabs(dfSpacingBegin - dfSpacingLast) <= 0.1 &&
3801 30 : fabs(dfSpacingBegin - dfSpacingMiddle) <= 0.1 &&
3802 8 : fabs(dfSpacingMiddle - dfSpacingLast) <= 0.1)
3803 : {
3804 8 : bLatSpacingOK = true;
3805 8 : CPLError(CE_Warning, CPLE_AppDefined,
3806 : "Latitude grid not spaced evenly. "
3807 : "Setting projection for grid spacing is "
3808 : "within 0.1 degrees threshold.");
3809 :
3810 8 : CPLDebug("GDAL_netCDF",
3811 : "Latitude grid not spaced evenly, but within 0.1 "
3812 : "degree threshold (probably a Gaussian grid). "
3813 : "Saving original latitude values in Y_VALUES "
3814 : "geolocation metadata");
3815 8 : Set1DGeolocation(nGroupDimYID, nVarDimYID, "Y");
3816 : }
3817 :
3818 166 : if (bLatSpacingOK == false)
3819 : {
3820 3 : CPLDebug(
3821 : "GDAL_netCDF", "%s",
3822 : "Latitude/Y is not equally spaced (with a 0.05% "
3823 : "tolerance). "
3824 : "You may set the "
3825 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
3826 : "option to YES to ignore this check");
3827 : }
3828 : }
3829 :
3830 214 : if (bLonSpacingOK && bLatSpacingOK)
3831 : {
3832 : // We have gridded data so we can set the Georeferencing info.
3833 :
3834 : // Enable GeoTransform.
3835 :
3836 : // In the following "actual_range" and "node_offset"
3837 : // are attributes used by netCDF files created by GMT.
3838 : // If we find them we know how to proceed. Else, use
3839 : // the original algorithm.
3840 211 : bGotCfGT = true;
3841 :
3842 211 : int node_offset = 0;
3843 211 : NCDFResolveAttInt(nGroupId, NC_GLOBAL, "node_offset", &node_offset);
3844 :
3845 211 : double adfActualRange[2] = {0.0, 0.0};
3846 211 : double xMinMax[2] = {0.0, 0.0};
3847 211 : double yMinMax[2] = {0.0, 0.0};
3848 :
3849 : const auto RoundMinMaxForFloatVals =
3850 58 : [](double &dfMin, double &dfMax, int nIntervals)
3851 : {
3852 : // Helps for a case where longitudes range from
3853 : // -179.99 to 180.0 with a 0.01 degree spacing.
3854 : // However as this is encoded in a float array,
3855 : // -179.99 is actually read as -179.99000549316406 as
3856 : // a double. Try to detect that and correct the rounding
3857 :
3858 87 : const auto IsAlmostInteger = [](double dfVal)
3859 : {
3860 87 : constexpr double THRESHOLD_INTEGER = 1e-3;
3861 87 : return std::fabs(dfVal - std::round(dfVal)) <=
3862 87 : THRESHOLD_INTEGER;
3863 : };
3864 :
3865 58 : const double dfSpacing = (dfMax - dfMin) / nIntervals;
3866 58 : if (dfSpacing > 0)
3867 : {
3868 47 : const double dfInvSpacing = 1.0 / dfSpacing;
3869 47 : if (IsAlmostInteger(dfInvSpacing))
3870 : {
3871 20 : const double dfRoundedSpacing =
3872 20 : 1.0 / std::round(dfInvSpacing);
3873 20 : const double dfMinDivRoundedSpacing =
3874 20 : dfMin / dfRoundedSpacing;
3875 20 : const double dfMaxDivRoundedSpacing =
3876 20 : dfMax / dfRoundedSpacing;
3877 40 : if (IsAlmostInteger(dfMinDivRoundedSpacing) &&
3878 20 : IsAlmostInteger(dfMaxDivRoundedSpacing))
3879 : {
3880 20 : const double dfRoundedMin =
3881 20 : std::round(dfMinDivRoundedSpacing) *
3882 : dfRoundedSpacing;
3883 20 : const double dfRoundedMax =
3884 20 : std::round(dfMaxDivRoundedSpacing) *
3885 : dfRoundedSpacing;
3886 20 : if (static_cast<float>(dfMin) ==
3887 20 : static_cast<float>(dfRoundedMin) &&
3888 8 : static_cast<float>(dfMax) ==
3889 8 : static_cast<float>(dfRoundedMax))
3890 : {
3891 7 : dfMin = dfRoundedMin;
3892 7 : dfMax = dfRoundedMax;
3893 : }
3894 : }
3895 : }
3896 : }
3897 58 : };
3898 :
3899 211 : if (!nc_get_att_double(nGroupDimXID, nVarDimXID, "actual_range",
3900 : adfActualRange))
3901 : {
3902 3 : xMinMax[0] = adfActualRange[0];
3903 3 : xMinMax[1] = adfActualRange[1];
3904 :
3905 : // Present xMinMax[] in the same order as padfXCoord
3906 3 : if ((xMinMax[0] - xMinMax[1]) *
3907 3 : (pdfXCoord[0] - pdfXCoord[xdim - 1]) <
3908 : 0)
3909 : {
3910 0 : std::swap(xMinMax[0], xMinMax[1]);
3911 : }
3912 : }
3913 : else
3914 : {
3915 208 : xMinMax[0] = pdfXCoord[0];
3916 208 : xMinMax[1] = pdfXCoord[xdim - 1];
3917 208 : node_offset = 0;
3918 :
3919 208 : if (nc_var_dimx_datatype == NC_FLOAT)
3920 : {
3921 29 : RoundMinMaxForFloatVals(xMinMax[0], xMinMax[1],
3922 29 : poDS->nRasterXSize - 1);
3923 : }
3924 : }
3925 :
3926 211 : if (!nc_get_att_double(nGroupDimYID, nVarDimYID, "actual_range",
3927 : adfActualRange))
3928 : {
3929 3 : yMinMax[0] = adfActualRange[0];
3930 3 : yMinMax[1] = adfActualRange[1];
3931 :
3932 : // Present yMinMax[] in the same order as pdfYCoord
3933 3 : if ((yMinMax[0] - yMinMax[1]) *
3934 3 : (pdfYCoord[0] - pdfYCoord[ydim - 1]) <
3935 : 0)
3936 : {
3937 1 : std::swap(yMinMax[0], yMinMax[1]);
3938 : }
3939 : }
3940 : else
3941 : {
3942 208 : yMinMax[0] = pdfYCoord[0];
3943 208 : yMinMax[1] = pdfYCoord[ydim - 1];
3944 208 : node_offset = 0;
3945 :
3946 208 : if (nc_var_dimy_datatype == NC_FLOAT)
3947 : {
3948 29 : RoundMinMaxForFloatVals(yMinMax[0], yMinMax[1],
3949 29 : poDS->nRasterYSize - 1);
3950 : }
3951 : }
3952 :
3953 211 : double dfCoordOffset = 0.0;
3954 211 : double dfCoordScale = 1.0;
3955 211 : if (!nc_get_att_double(nGroupId, nVarDimXID, CF_ADD_OFFSET,
3956 215 : &dfCoordOffset) &&
3957 4 : !nc_get_att_double(nGroupId, nVarDimXID, CF_SCALE_FACTOR,
3958 : &dfCoordScale))
3959 : {
3960 4 : xMinMax[0] = dfCoordOffset + xMinMax[0] * dfCoordScale;
3961 4 : xMinMax[1] = dfCoordOffset + xMinMax[1] * dfCoordScale;
3962 : }
3963 :
3964 211 : if (!nc_get_att_double(nGroupId, nVarDimYID, CF_ADD_OFFSET,
3965 215 : &dfCoordOffset) &&
3966 4 : !nc_get_att_double(nGroupId, nVarDimYID, CF_SCALE_FACTOR,
3967 : &dfCoordScale))
3968 : {
3969 4 : yMinMax[0] = dfCoordOffset + yMinMax[0] * dfCoordScale;
3970 4 : yMinMax[1] = dfCoordOffset + yMinMax[1] * dfCoordScale;
3971 : }
3972 :
3973 : // Check for reverse order of y-coordinate.
3974 211 : if (!bSwitchedXY)
3975 : {
3976 209 : poDS->bBottomUp = (yMinMax[0] <= yMinMax[1]);
3977 209 : CPLDebug("GDAL_netCDF", "set bBottomUp = %d from Y axis",
3978 209 : static_cast<int>(poDS->bBottomUp));
3979 209 : if (!poDS->bBottomUp)
3980 : {
3981 32 : std::swap(yMinMax[0], yMinMax[1]);
3982 : }
3983 : }
3984 :
3985 : // Geostationary satellites can specify units in (micro)radians
3986 : // So we check if they do, and if so convert to linear units
3987 : // (meters)
3988 211 : const char *pszProjName = oSRS.GetAttrValue("PROJECTION");
3989 211 : if (pszProjName != nullptr)
3990 : {
3991 24 : if (EQUAL(pszProjName, SRS_PT_GEOSTATIONARY_SATELLITE))
3992 : {
3993 : double satelliteHeight =
3994 3 : oSRS.GetProjParm(SRS_PP_SATELLITE_HEIGHT, 1.0);
3995 3 : size_t nAttlen = 0;
3996 : char szUnits[NC_MAX_NAME + 1];
3997 3 : szUnits[0] = '\0';
3998 3 : nc_type nAttype = NC_NAT;
3999 3 : nc_inq_att(nGroupId, nVarDimXID, "units", &nAttype,
4000 : &nAttlen);
4001 6 : if (nAttlen < sizeof(szUnits) &&
4002 3 : nc_get_att_text(nGroupId, nVarDimXID, "units",
4003 : szUnits) == NC_NOERR)
4004 : {
4005 3 : szUnits[nAttlen] = '\0';
4006 3 : if (EQUAL(szUnits, "microradian"))
4007 : {
4008 1 : xMinMax[0] =
4009 1 : xMinMax[0] * satelliteHeight * 0.000001;
4010 1 : xMinMax[1] =
4011 1 : xMinMax[1] * satelliteHeight * 0.000001;
4012 : }
4013 2 : else if (EQUAL(szUnits, "rad") ||
4014 1 : EQUAL(szUnits, "radian"))
4015 : {
4016 2 : xMinMax[0] = xMinMax[0] * satelliteHeight;
4017 2 : xMinMax[1] = xMinMax[1] * satelliteHeight;
4018 : }
4019 : }
4020 3 : szUnits[0] = '\0';
4021 3 : nc_inq_att(nGroupId, nVarDimYID, "units", &nAttype,
4022 : &nAttlen);
4023 6 : if (nAttlen < sizeof(szUnits) &&
4024 3 : nc_get_att_text(nGroupId, nVarDimYID, "units",
4025 : szUnits) == NC_NOERR)
4026 : {
4027 3 : szUnits[nAttlen] = '\0';
4028 3 : if (EQUAL(szUnits, "microradian"))
4029 : {
4030 1 : yMinMax[0] =
4031 1 : yMinMax[0] * satelliteHeight * 0.000001;
4032 1 : yMinMax[1] =
4033 1 : yMinMax[1] * satelliteHeight * 0.000001;
4034 : }
4035 2 : else if (EQUAL(szUnits, "rad") ||
4036 1 : EQUAL(szUnits, "radian"))
4037 : {
4038 2 : yMinMax[0] = yMinMax[0] * satelliteHeight;
4039 2 : yMinMax[1] = yMinMax[1] * satelliteHeight;
4040 : }
4041 : }
4042 : }
4043 : }
4044 :
4045 211 : adfTempGeoTransform[0] = xMinMax[0];
4046 211 : adfTempGeoTransform[1] = (xMinMax[1] - xMinMax[0]) /
4047 211 : (poDS->nRasterXSize + (node_offset - 1));
4048 211 : adfTempGeoTransform[2] = 0;
4049 211 : if (bSwitchedXY)
4050 : {
4051 2 : adfTempGeoTransform[3] = yMinMax[0];
4052 2 : adfTempGeoTransform[4] = 0;
4053 2 : adfTempGeoTransform[5] =
4054 2 : (yMinMax[1] - yMinMax[0]) /
4055 2 : (poDS->nRasterYSize + (node_offset - 1));
4056 : }
4057 : else
4058 : {
4059 209 : adfTempGeoTransform[3] = yMinMax[1];
4060 209 : adfTempGeoTransform[4] = 0;
4061 209 : adfTempGeoTransform[5] =
4062 209 : (yMinMax[0] - yMinMax[1]) /
4063 209 : (poDS->nRasterYSize + (node_offset - 1));
4064 : }
4065 :
4066 : // Compute the center of the pixel.
4067 211 : if (!node_offset)
4068 : {
4069 : // Otherwise its already the pixel center.
4070 211 : adfTempGeoTransform[0] -= (adfTempGeoTransform[1] / 2);
4071 211 : adfTempGeoTransform[3] -= (adfTempGeoTransform[5] / 2);
4072 : }
4073 : }
4074 :
4075 : const auto AreSRSEqualThroughProj4String =
4076 2 : [](const OGRSpatialReference &oSRS1,
4077 : const OGRSpatialReference &oSRS2)
4078 : {
4079 2 : char *pszProj4Str1 = nullptr;
4080 2 : oSRS1.exportToProj4(&pszProj4Str1);
4081 :
4082 2 : char *pszProj4Str2 = nullptr;
4083 2 : oSRS2.exportToProj4(&pszProj4Str2);
4084 :
4085 : {
4086 2 : char *pszTmp = strstr(pszProj4Str1, "+datum=");
4087 2 : if (pszTmp)
4088 0 : memcpy(pszTmp, "+ellps=", strlen("+ellps="));
4089 : }
4090 :
4091 : {
4092 2 : char *pszTmp = strstr(pszProj4Str2, "+datum=");
4093 2 : if (pszTmp)
4094 2 : memcpy(pszTmp, "+ellps=", strlen("+ellps="));
4095 : }
4096 :
4097 2 : bool bRet = false;
4098 2 : if (pszProj4Str1 && pszProj4Str2 &&
4099 2 : EQUAL(pszProj4Str1, pszProj4Str2))
4100 : {
4101 1 : bRet = true;
4102 : }
4103 :
4104 2 : CPLFree(pszProj4Str1);
4105 2 : CPLFree(pszProj4Str2);
4106 2 : return bRet;
4107 : };
4108 :
4109 214 : if (dfLinearUnitsConvFactor != 1.0)
4110 : {
4111 35 : for (int i = 0; i < 6; ++i)
4112 30 : adfTempGeoTransform[i] *= dfLinearUnitsConvFactor;
4113 :
4114 5 : if (paosRemovedMDItems)
4115 : {
4116 : char szVarNameX[NC_MAX_NAME + 1];
4117 5 : CPL_IGNORE_RET_VAL(
4118 5 : nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
4119 :
4120 : char szVarNameY[NC_MAX_NAME + 1];
4121 5 : CPL_IGNORE_RET_VAL(
4122 5 : nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
4123 :
4124 5 : paosRemovedMDItems->push_back(
4125 : CPLSPrintf("%s#units", szVarNameX));
4126 5 : paosRemovedMDItems->push_back(
4127 : CPLSPrintf("%s#units", szVarNameY));
4128 : }
4129 : }
4130 :
4131 : // If there is a global "geospatial_bounds_crs" attribute, check that it
4132 : // is consistent with the SRS, and if so, use it as the SRS
4133 : const char *pszGBCRS =
4134 214 : FetchAttr(nGroupId, NC_GLOBAL, "geospatial_bounds_crs");
4135 214 : if (pszGBCRS && STARTS_WITH(pszGBCRS, "EPSG:"))
4136 : {
4137 4 : OGRSpatialReference oSRSFromGBCRS;
4138 2 : oSRSFromGBCRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
4139 2 : if (oSRSFromGBCRS.SetFromUserInput(
4140 : pszGBCRS,
4141 : OGRSpatialReference::
4142 4 : SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE &&
4143 2 : AreSRSEqualThroughProj4String(oSRS, oSRSFromGBCRS))
4144 : {
4145 1 : oSRS = std::move(oSRSFromGBCRS);
4146 1 : SetSpatialRefNoUpdate(&oSRS);
4147 : }
4148 : }
4149 :
4150 214 : CPLFree(pdfXCoord);
4151 214 : CPLFree(pdfYCoord);
4152 : } // end if(has dims)
4153 :
4154 : // Process custom GeoTransform GDAL value.
4155 490 : if (!EQUAL(pszGridMappingValue, "") && !bGotCfGT)
4156 : {
4157 : // TODO: Read the GT values and detect for conflict with CF.
4158 : // This could resolve the GT precision loss issue.
4159 :
4160 91 : if (pszGeoTransform != nullptr)
4161 : {
4162 : char **papszGeoTransform =
4163 13 : CSLTokenizeString2(pszGeoTransform, " ", CSLT_HONOURSTRINGS);
4164 13 : if (CSLCount(papszGeoTransform) == 6)
4165 : {
4166 13 : bGotGdalGT = true;
4167 91 : for (int i = 0; i < 6; i++)
4168 78 : adfTempGeoTransform[i] = CPLAtof(papszGeoTransform[i]);
4169 : }
4170 13 : CSLDestroy(papszGeoTransform);
4171 : }
4172 : else
4173 : {
4174 : // Look for corner array values.
4175 : // CPLDebug("GDAL_netCDF",
4176 : // "looking for geotransform corners");
4177 78 : bool bGotNN = false;
4178 78 : double dfNN = FetchCopyParam(pszGridMappingValue,
4179 : "Northernmost_Northing", 0, &bGotNN);
4180 :
4181 78 : bool bGotSN = false;
4182 78 : double dfSN = FetchCopyParam(pszGridMappingValue,
4183 : "Southernmost_Northing", 0, &bGotSN);
4184 :
4185 78 : bool bGotEE = false;
4186 78 : double dfEE = FetchCopyParam(pszGridMappingValue,
4187 : "Easternmost_Easting", 0, &bGotEE);
4188 :
4189 78 : bool bGotWE = false;
4190 78 : double dfWE = FetchCopyParam(pszGridMappingValue,
4191 : "Westernmost_Easting", 0, &bGotWE);
4192 :
4193 : // Only set the GeoTransform if we got all the values.
4194 78 : if (bGotNN && bGotSN && bGotEE && bGotWE)
4195 : {
4196 0 : bGotGdalGT = true;
4197 :
4198 0 : adfTempGeoTransform[0] = dfWE;
4199 0 : adfTempGeoTransform[1] =
4200 0 : (dfEE - dfWE) / (poDS->GetRasterXSize() - 1);
4201 0 : adfTempGeoTransform[2] = 0.0;
4202 0 : adfTempGeoTransform[3] = dfNN;
4203 0 : adfTempGeoTransform[4] = 0.0;
4204 0 : adfTempGeoTransform[5] =
4205 0 : (dfSN - dfNN) / (poDS->GetRasterYSize() - 1);
4206 : // Compute the center of the pixel.
4207 0 : adfTempGeoTransform[0] = dfWE - (adfTempGeoTransform[1] / 2);
4208 0 : adfTempGeoTransform[3] = dfNN - (adfTempGeoTransform[5] / 2);
4209 : }
4210 : } // (pszGeoTransform != NULL)
4211 :
4212 91 : if (bGotGdalSRS && !bGotGdalGT)
4213 72 : CPLDebug("GDAL_netCDF", "Got SRS but no geotransform from GDAL!");
4214 : }
4215 :
4216 490 : if (!pszWKT && !bGotCfSRS)
4217 : {
4218 : // Some netCDF files have a srid attribute (#6613) like
4219 : // urn:ogc:def:crs:EPSG::6931
4220 279 : const char *pszSRID = FetchAttr(pszGridMappingValue, "srid");
4221 279 : if (pszSRID != nullptr)
4222 : {
4223 0 : oSRS.Clear();
4224 0 : if (oSRS.SetFromUserInput(
4225 : pszSRID,
4226 : OGRSpatialReference::
4227 0 : SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE)
4228 : {
4229 0 : char *pszWKTExport = nullptr;
4230 0 : CPLDebug("GDAL_netCDF", "Got SRS from %s", pszSRID);
4231 0 : oSRS.exportToWkt(&pszWKTExport);
4232 0 : if (returnProjStr != nullptr)
4233 : {
4234 0 : (*returnProjStr) = std::string(pszWKTExport);
4235 : }
4236 : else
4237 : {
4238 0 : m_bAddedProjectionVarsDefs = true;
4239 0 : m_bAddedProjectionVarsData = true;
4240 0 : SetSpatialRefNoUpdate(&oSRS);
4241 : }
4242 0 : CPLFree(pszWKTExport);
4243 : }
4244 : }
4245 : }
4246 :
4247 490 : CPLFree(pszGridMappingValue);
4248 :
4249 490 : if (bReadSRSOnly)
4250 153 : return;
4251 :
4252 : // Determines the SRS to be used by the geolocation array, if any
4253 674 : std::string osGeolocWKT = SRS_WKT_WGS84_LAT_LONG;
4254 337 : if (!m_oSRS.IsEmpty())
4255 : {
4256 252 : OGRSpatialReference oGeogCRS;
4257 126 : oGeogCRS.CopyGeogCSFrom(&m_oSRS);
4258 126 : char *pszWKTTmp = nullptr;
4259 126 : const char *const apszOptions[] = {"FORMAT=WKT2_2019", nullptr};
4260 126 : if (oGeogCRS.exportToWkt(&pszWKTTmp, apszOptions) == OGRERR_NONE)
4261 : {
4262 126 : osGeolocWKT = pszWKTTmp;
4263 : }
4264 126 : CPLFree(pszWKTTmp);
4265 : }
4266 :
4267 : // Process geolocation arrays from CF "coordinates" attribute.
4268 674 : std::string osGeolocXName, osGeolocYName;
4269 337 : if (ProcessCFGeolocation(nGroupId, nVarId, osGeolocWKT, osGeolocXName,
4270 337 : osGeolocYName))
4271 : {
4272 51 : bool bCanCancelGT = true;
4273 51 : if ((nVarDimXID != -1) && (nVarDimYID != -1))
4274 : {
4275 : char szVarNameX[NC_MAX_NAME + 1];
4276 40 : CPL_IGNORE_RET_VAL(
4277 40 : nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
4278 : char szVarNameY[NC_MAX_NAME + 1];
4279 40 : CPL_IGNORE_RET_VAL(
4280 40 : nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
4281 40 : bCanCancelGT =
4282 40 : !(osGeolocXName == szVarNameX && osGeolocYName == szVarNameY);
4283 : }
4284 88 : if (bCanCancelGT && !m_oSRS.IsGeographic() && !m_oSRS.IsProjected() &&
4285 37 : !bSwitchedXY)
4286 : {
4287 35 : bGotCfGT = false;
4288 : }
4289 : }
4290 115 : else if (!bGotCfGT && !bReadSRSOnly && (nVarDimXID != -1) &&
4291 404 : (nVarDimYID != -1) && xdim > 0 && ydim > 0 &&
4292 3 : ((!bSwitchedXY &&
4293 3 : NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
4294 1 : NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr)) ||
4295 2 : (bSwitchedXY &&
4296 0 : NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
4297 0 : NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr))))
4298 : {
4299 : // Case of autotest/gdrivers/data/netcdf/GLMELT_4X5.OCN.nc
4300 : // which is indexed by lat, lon variables, but lat has irregular
4301 : // spacing.
4302 1 : const char *pszGeolocXFullName = poDS->papszDimName[poDS->nXDimID];
4303 1 : const char *pszGeolocYFullName = poDS->papszDimName[poDS->nYDimID];
4304 1 : if (bSwitchedXY)
4305 : {
4306 0 : std::swap(pszGeolocXFullName, pszGeolocYFullName);
4307 0 : GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", "GEOLOCATION");
4308 : }
4309 :
4310 1 : CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
4311 : pszGeolocXFullName, pszGeolocYFullName);
4312 :
4313 1 : GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
4314 : "GEOLOCATION");
4315 :
4316 2 : CPLString osTMP;
4317 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4318 1 : pszGeolocXFullName);
4319 :
4320 1 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
4321 1 : GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
4322 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4323 1 : pszGeolocYFullName);
4324 :
4325 1 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
4326 1 : GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
4327 :
4328 1 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
4329 1 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
4330 :
4331 1 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
4332 1 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
4333 :
4334 1 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
4335 : "PIXEL_CENTER", "GEOLOCATION");
4336 : }
4337 :
4338 : // Set GeoTransform if we got a complete one - after projection has been set
4339 337 : if (bGotCfGT || bGotGdalGT)
4340 : {
4341 193 : m_bAddedProjectionVarsDefs = true;
4342 193 : m_bAddedProjectionVarsData = true;
4343 193 : SetGeoTransformNoUpdate(adfTempGeoTransform);
4344 : }
4345 :
4346 : // Debugging reports.
4347 337 : CPLDebug("GDAL_netCDF",
4348 : "bGotGeogCS=%d bGotCfSRS=%d bGotCfGT=%d bGotCfWktSRS=%d "
4349 : "bGotGdalSRS=%d bGotGdalGT=%d",
4350 : static_cast<int>(bGotGeogCS), static_cast<int>(bGotCfSRS),
4351 : static_cast<int>(bGotCfGT), static_cast<int>(bGotCfWktSRS),
4352 : static_cast<int>(bGotGdalSRS), static_cast<int>(bGotGdalGT));
4353 :
4354 337 : if (!bGotCfGT && !bGotGdalGT)
4355 144 : CPLDebug("GDAL_netCDF", "did not get geotransform from CF nor GDAL!");
4356 :
4357 337 : if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfGT && !bGotCfWktSRS)
4358 144 : CPLDebug("GDAL_netCDF", "did not get projection from CF nor GDAL!");
4359 :
4360 : // wish of 6195
4361 : // we don't have CS/SRS, but we do have GT, and we live in -180,360 -90,90
4362 337 : if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfWktSRS)
4363 : {
4364 211 : if (bGotCfGT || bGotGdalGT)
4365 : {
4366 134 : bool bAssumedLongLat = CPLTestBool(CSLFetchNameValueDef(
4367 67 : papszOpenOptions, "ASSUME_LONGLAT",
4368 : CPLGetConfigOption("GDAL_NETCDF_ASSUME_LONGLAT", "NO")));
4369 :
4370 2 : if (bAssumedLongLat && adfTempGeoTransform[0] >= -180 &&
4371 2 : adfTempGeoTransform[0] < 360 &&
4372 4 : (adfTempGeoTransform[0] +
4373 2 : adfTempGeoTransform[1] * poDS->GetRasterXSize()) <= 360 &&
4374 71 : adfTempGeoTransform[3] <= 90 && adfTempGeoTransform[3] > -90 &&
4375 4 : (adfTempGeoTransform[3] +
4376 2 : adfTempGeoTransform[5] * poDS->GetRasterYSize()) >= -90)
4377 : {
4378 :
4379 2 : poDS->bIsGeographic = true;
4380 2 : char *pszTempProjection = nullptr;
4381 : // seems odd to use 4326 so OGC:CRS84
4382 2 : oSRS.SetFromUserInput("OGC:CRS84");
4383 2 : oSRS.exportToWkt(&pszTempProjection);
4384 2 : if (returnProjStr != nullptr)
4385 : {
4386 0 : (*returnProjStr) = std::string(pszTempProjection);
4387 : }
4388 : else
4389 : {
4390 2 : m_bAddedProjectionVarsDefs = true;
4391 2 : m_bAddedProjectionVarsData = true;
4392 2 : SetSpatialRefNoUpdate(&oSRS);
4393 : }
4394 2 : CPLFree(pszTempProjection);
4395 :
4396 2 : CPLDebug("netCDF",
4397 : "Assumed Longitude Latitude CRS 'OGC:CRS84' because "
4398 : "none otherwise available and geotransform within "
4399 : "suitable bounds. "
4400 : "Set GDAL_NETCDF_ASSUME_LONGLAT=NO as configuration "
4401 : "option or "
4402 : " ASSUME_LONGLAT=NO as open option to bypass this "
4403 : "assumption.");
4404 : }
4405 : }
4406 : }
4407 :
4408 : // Search for Well-known GeogCS if got only CF WKT
4409 : // Disabled for now, as a named datum also include control points
4410 : // (see mailing list and bug#4281
4411 : // For example, WGS84 vs. GDA94 (EPSG:3577) - AEA in netcdf_cf.py
4412 :
4413 : // Disabled for now, but could be set in a config option.
4414 : #if 0
4415 : bool bLookForWellKnownGCS = false; // This could be a Config Option.
4416 :
4417 : if( bLookForWellKnownGCS && bGotCfSRS && !bGotGdalSRS )
4418 : {
4419 : // ET - Could use a more exhaustive method by scanning all EPSG codes in
4420 : // data/gcs.csv as proposed by Even in the gdal-dev mailing list "help
4421 : // for comparing two WKT".
4422 : // This code could be contributed to a new function.
4423 : // OGRSpatialReference * OGRSpatialReference::FindMatchingGeogCS(
4424 : // const OGRSpatialReference *poOther) */
4425 : CPLDebug("GDAL_netCDF", "Searching for Well-known GeogCS");
4426 : const char *pszWKGCSList[] = { "WGS84", "WGS72", "NAD27", "NAD83" };
4427 : char *pszWKGCS = NULL;
4428 : oSRS.exportToPrettyWkt(&pszWKGCS);
4429 : for( size_t i = 0; i < sizeof(pszWKGCSList) / 8; i++ )
4430 : {
4431 : pszWKGCS = CPLStrdup(pszWKGCSList[i]);
4432 : OGRSpatialReference oSRSTmp;
4433 : oSRSTmp.SetWellKnownGeogCS(pszWKGCSList[i]);
4434 : // Set datum to unknown, bug #4281.
4435 : if( oSRSTmp.GetAttrNode("DATUM" ) )
4436 : oSRSTmp.GetAttrNode("DATUM")->GetChild(0)->SetValue("unknown");
4437 : // Could use OGRSpatialReference::StripCTParms(), but let's keep
4438 : // TOWGS84.
4439 : oSRSTmp.GetRoot()->StripNodes("AXIS");
4440 : oSRSTmp.GetRoot()->StripNodes("AUTHORITY");
4441 : oSRSTmp.GetRoot()->StripNodes("EXTENSION");
4442 :
4443 : oSRSTmp.exportToPrettyWkt(&pszWKGCS);
4444 : if( oSRS.IsSameGeogCS(&oSRSTmp) )
4445 : {
4446 : oSRS.SetWellKnownGeogCS(pszWKGCSList[i]);
4447 : oSRS.exportToWkt(&(pszTempProjection));
4448 : SetProjection(pszTempProjection);
4449 : CPLFree(pszTempProjection);
4450 : }
4451 : }
4452 : }
4453 : #endif
4454 : }
4455 :
4456 110 : void netCDFDataset::SetProjectionFromVar(int nGroupId, int nVarId,
4457 : bool bReadSRSOnly)
4458 : {
4459 110 : SetProjectionFromVar(nGroupId, nVarId, bReadSRSOnly, nullptr, nullptr,
4460 : nullptr, nullptr);
4461 110 : }
4462 :
4463 295 : bool netCDFDataset::ProcessNASAL2OceanGeoLocation(int nGroupId, int nVarId)
4464 : {
4465 : // Cf https://oceancolor.gsfc.nasa.gov/docs/format/l2nc/
4466 : // and https://github.com/OSGeo/gdal/issues/7605
4467 :
4468 : // Check for a structure like:
4469 : /* netcdf SNPP_VIIRS.20230406T024200.L2.OC.NRT {
4470 : dimensions:
4471 : number_of_lines = 3248 ;
4472 : pixels_per_line = 3200 ;
4473 : [...]
4474 : pixel_control_points = 3200 ;
4475 : [...]
4476 : group: geophysical_data {
4477 : variables:
4478 : short aot_862(number_of_lines, pixels_per_line) ; <-- nVarId
4479 : [...]
4480 : }
4481 : group: navigation_data {
4482 : variables:
4483 : float longitude(number_of_lines, pixel_control_points) ;
4484 : [...]
4485 : float latitude(number_of_lines, pixel_control_points) ;
4486 : [...]
4487 : }
4488 : }
4489 : */
4490 : // Note that the longitude and latitude arrays are not indexed by the
4491 : // same dimensions. Handle only the case where
4492 : // pixel_control_points == pixels_per_line
4493 : // If there was a subsampling of the geolocation arrays, we'd need to
4494 : // add more logic.
4495 :
4496 590 : std::string osGroupName;
4497 295 : osGroupName.resize(NC_MAX_NAME);
4498 295 : NCDF_ERR(nc_inq_grpname(nGroupId, &osGroupName[0]));
4499 295 : osGroupName.resize(strlen(osGroupName.data()));
4500 295 : if (osGroupName != "geophysical_data")
4501 294 : return false;
4502 :
4503 1 : int nVarDims = 0;
4504 1 : NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
4505 1 : if (nVarDims != 2)
4506 0 : return false;
4507 :
4508 1 : int nNavigationDataGrpId = 0;
4509 1 : if (nc_inq_grp_ncid(cdfid, "navigation_data", &nNavigationDataGrpId) !=
4510 : NC_NOERR)
4511 0 : return false;
4512 :
4513 : std::array<int, 2> anVarDimIds;
4514 1 : NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
4515 :
4516 1 : int nLongitudeId = 0;
4517 1 : int nLatitudeId = 0;
4518 1 : if (nc_inq_varid(nNavigationDataGrpId, "longitude", &nLongitudeId) !=
4519 2 : NC_NOERR ||
4520 1 : nc_inq_varid(nNavigationDataGrpId, "latitude", &nLatitudeId) !=
4521 : NC_NOERR)
4522 : {
4523 0 : return false;
4524 : }
4525 :
4526 1 : int nDimsLongitude = 0;
4527 1 : NCDF_ERR(
4528 : nc_inq_varndims(nNavigationDataGrpId, nLongitudeId, &nDimsLongitude));
4529 1 : int nDimsLatitude = 0;
4530 1 : NCDF_ERR(
4531 : nc_inq_varndims(nNavigationDataGrpId, nLatitudeId, &nDimsLatitude));
4532 1 : if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
4533 : {
4534 0 : return false;
4535 : }
4536 :
4537 : std::array<int, 2> anDimLongitudeIds;
4538 1 : NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLongitudeId,
4539 : anDimLongitudeIds.data()));
4540 : std::array<int, 2> anDimLatitudeIds;
4541 1 : NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLatitudeId,
4542 : anDimLatitudeIds.data()));
4543 1 : if (anDimLongitudeIds != anDimLatitudeIds)
4544 : {
4545 0 : return false;
4546 : }
4547 :
4548 : std::array<size_t, 2> anSizeVarDimIds;
4549 : std::array<size_t, 2> anSizeLongLatIds;
4550 2 : if (!(nc_inq_dimlen(cdfid, anVarDimIds[0], &anSizeVarDimIds[0]) ==
4551 1 : NC_NOERR &&
4552 1 : nc_inq_dimlen(cdfid, anVarDimIds[1], &anSizeVarDimIds[1]) ==
4553 1 : NC_NOERR &&
4554 1 : nc_inq_dimlen(cdfid, anDimLongitudeIds[0], &anSizeLongLatIds[0]) ==
4555 1 : NC_NOERR &&
4556 1 : nc_inq_dimlen(cdfid, anDimLongitudeIds[1], &anSizeLongLatIds[1]) ==
4557 : NC_NOERR &&
4558 1 : anSizeVarDimIds == anSizeLongLatIds))
4559 : {
4560 0 : return false;
4561 : }
4562 :
4563 1 : const char *pszGeolocXFullName = "/navigation_data/longitude";
4564 1 : const char *pszGeolocYFullName = "/navigation_data/latitude";
4565 :
4566 1 : if (bSwitchedXY)
4567 : {
4568 0 : std::swap(pszGeolocXFullName, pszGeolocYFullName);
4569 0 : GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", "GEOLOCATION");
4570 : }
4571 :
4572 1 : CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
4573 : pszGeolocXFullName, pszGeolocYFullName);
4574 :
4575 1 : GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
4576 : "GEOLOCATION");
4577 :
4578 1 : CPLString osTMP;
4579 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
4580 :
4581 1 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
4582 1 : GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
4583 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
4584 :
4585 1 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
4586 1 : GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
4587 :
4588 1 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
4589 1 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
4590 :
4591 1 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
4592 1 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
4593 :
4594 1 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
4595 : "GEOLOCATION");
4596 1 : return true;
4597 : }
4598 :
4599 294 : bool netCDFDataset::ProcessNASAEMITGeoLocation(int nGroupId, int nVarId)
4600 : {
4601 : // Cf https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/
4602 :
4603 : // Check for a structure like:
4604 : /* netcdf EMIT_L2A_RFL_001_20220903T163129_2224611_012 {
4605 : dimensions:
4606 : downtrack = 1280 ;
4607 : crosstrack = 1242 ;
4608 : bands = 285 ;
4609 : [...]
4610 :
4611 : variables:
4612 : float reflectance(downtrack, crosstrack, bands) ;
4613 :
4614 : group: location {
4615 : variables:
4616 : double lon(downtrack, crosstrack) ;
4617 : lon:_FillValue = -9999. ;
4618 : lon:long_name = "Longitude (WGS-84)" ;
4619 : lon:units = "degrees east" ;
4620 : double lat(downtrack, crosstrack) ;
4621 : lat:_FillValue = -9999. ;
4622 : lat:long_name = "Latitude (WGS-84)" ;
4623 : lat:units = "degrees north" ;
4624 : } // group location
4625 :
4626 : }
4627 : or
4628 : netcdf EMIT_L2B_MIN_001_20231024T055538_2329704_040 {
4629 : dimensions:
4630 : downtrack = 1664 ;
4631 : crosstrack = 1242 ;
4632 : [...]
4633 : variables:
4634 : float group_1_band_depth(downtrack, crosstrack) ;
4635 : group_1_band_depth:_FillValue = -9999.f ;
4636 : group_1_band_depth:long_name = "Group 1 Band Depth" ;
4637 : group_1_band_depth:units = "unitless" ;
4638 : [...]
4639 : group: location {
4640 : variables:
4641 : double lon(downtrack, crosstrack) ;
4642 : lon:_FillValue = -9999. ;
4643 : lon:long_name = "Longitude (WGS-84)" ;
4644 : lon:units = "degrees east" ;
4645 : double lat(downtrack, crosstrack) ;
4646 : lat:_FillValue = -9999. ;
4647 : lat:long_name = "Latitude (WGS-84)" ;
4648 : lat:units = "degrees north" ;
4649 : }
4650 : */
4651 :
4652 294 : int nVarDims = 0;
4653 294 : NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
4654 294 : if (nVarDims != 2 && nVarDims != 3)
4655 14 : return false;
4656 :
4657 280 : int nLocationGrpId = 0;
4658 280 : if (nc_inq_grp_ncid(cdfid, "location", &nLocationGrpId) != NC_NOERR)
4659 56 : return false;
4660 :
4661 : std::array<int, 3> anVarDimIds;
4662 224 : NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
4663 224 : if (nYDimID != anVarDimIds[0] || nXDimID != anVarDimIds[1])
4664 21 : return false;
4665 :
4666 203 : int nLongitudeId = 0;
4667 203 : int nLatitudeId = 0;
4668 254 : if (nc_inq_varid(nLocationGrpId, "lon", &nLongitudeId) != NC_NOERR ||
4669 51 : nc_inq_varid(nLocationGrpId, "lat", &nLatitudeId) != NC_NOERR)
4670 : {
4671 152 : return false;
4672 : }
4673 :
4674 51 : int nDimsLongitude = 0;
4675 51 : NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLongitudeId, &nDimsLongitude));
4676 51 : int nDimsLatitude = 0;
4677 51 : NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLatitudeId, &nDimsLatitude));
4678 51 : if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
4679 : {
4680 31 : return false;
4681 : }
4682 :
4683 : std::array<int, 2> anDimLongitudeIds;
4684 20 : NCDF_ERR(nc_inq_vardimid(nLocationGrpId, nLongitudeId,
4685 : anDimLongitudeIds.data()));
4686 : std::array<int, 2> anDimLatitudeIds;
4687 20 : NCDF_ERR(
4688 : nc_inq_vardimid(nLocationGrpId, nLatitudeId, anDimLatitudeIds.data()));
4689 20 : if (anDimLongitudeIds != anDimLatitudeIds)
4690 : {
4691 0 : return false;
4692 : }
4693 :
4694 40 : if (anDimLongitudeIds[0] != anVarDimIds[0] ||
4695 20 : anDimLongitudeIds[1] != anVarDimIds[1])
4696 : {
4697 0 : return false;
4698 : }
4699 :
4700 20 : const char *pszGeolocXFullName = "/location/lon";
4701 20 : const char *pszGeolocYFullName = "/location/lat";
4702 :
4703 20 : CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
4704 : pszGeolocXFullName, pszGeolocYFullName);
4705 :
4706 20 : GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
4707 : "GEOLOCATION");
4708 :
4709 20 : CPLString osTMP;
4710 20 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
4711 :
4712 20 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
4713 20 : GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
4714 20 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
4715 :
4716 20 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
4717 20 : GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
4718 :
4719 20 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
4720 20 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
4721 :
4722 20 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
4723 20 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
4724 :
4725 20 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
4726 : "GEOLOCATION");
4727 20 : return true;
4728 : }
4729 :
4730 337 : int netCDFDataset::ProcessCFGeolocation(int nGroupId, int nVarId,
4731 : const std::string &osGeolocWKT,
4732 : std::string &osGeolocXNameOut,
4733 : std::string &osGeolocYNameOut)
4734 : {
4735 337 : bool bAddGeoloc = false;
4736 337 : char *pszTemp = nullptr;
4737 :
4738 337 : if (NCDFGetAttr(nGroupId, nVarId, "coordinates", &pszTemp) == CE_None)
4739 : {
4740 : // Get X and Y geolocation names from coordinates attribute.
4741 42 : char **papszTokens = NCDFTokenizeCoordinatesAttribute(pszTemp);
4742 42 : if (CSLCount(papszTokens) >= 2)
4743 : {
4744 : char szGeolocXName[NC_MAX_NAME + 1];
4745 : char szGeolocYName[NC_MAX_NAME + 1];
4746 39 : szGeolocXName[0] = '\0';
4747 39 : szGeolocYName[0] = '\0';
4748 :
4749 : // Test that each variable is longitude/latitude.
4750 126 : for (int i = 0; i < CSLCount(papszTokens); i++)
4751 : {
4752 87 : if (NCDFIsVarLongitude(nGroupId, -1, papszTokens[i]))
4753 : {
4754 32 : int nOtherGroupId = -1;
4755 32 : int nOtherVarId = -1;
4756 : // Check that the variable actually exists
4757 : // Needed on Sentinel-3 products
4758 32 : if (NCDFResolveVar(nGroupId, papszTokens[i], &nOtherGroupId,
4759 32 : &nOtherVarId) == CE_None)
4760 : {
4761 30 : snprintf(szGeolocXName, sizeof(szGeolocXName), "%s",
4762 30 : papszTokens[i]);
4763 : }
4764 : }
4765 55 : else if (NCDFIsVarLatitude(nGroupId, -1, papszTokens[i]))
4766 : {
4767 32 : int nOtherGroupId = -1;
4768 32 : int nOtherVarId = -1;
4769 : // Check that the variable actually exists
4770 : // Needed on Sentinel-3 products
4771 32 : if (NCDFResolveVar(nGroupId, papszTokens[i], &nOtherGroupId,
4772 32 : &nOtherVarId) == CE_None)
4773 : {
4774 30 : snprintf(szGeolocYName, sizeof(szGeolocYName), "%s",
4775 30 : papszTokens[i]);
4776 : }
4777 : }
4778 : }
4779 : // Add GEOLOCATION metadata.
4780 39 : if (!EQUAL(szGeolocXName, "") && !EQUAL(szGeolocYName, ""))
4781 : {
4782 30 : osGeolocXNameOut = szGeolocXName;
4783 30 : osGeolocYNameOut = szGeolocYName;
4784 :
4785 30 : char *pszGeolocXFullName = nullptr;
4786 30 : char *pszGeolocYFullName = nullptr;
4787 30 : if (NCDFResolveVarFullName(nGroupId, szGeolocXName,
4788 60 : &pszGeolocXFullName) == CE_None &&
4789 30 : NCDFResolveVarFullName(nGroupId, szGeolocYName,
4790 : &pszGeolocYFullName) == CE_None)
4791 : {
4792 30 : if (bSwitchedXY)
4793 : {
4794 2 : std::swap(pszGeolocXFullName, pszGeolocYFullName);
4795 2 : GDALPamDataset::SetMetadataItem("SWAP_XY", "YES",
4796 : "GEOLOCATION");
4797 : }
4798 :
4799 30 : bAddGeoloc = true;
4800 30 : CPLDebug("GDAL_netCDF",
4801 : "using variables %s and %s for GEOLOCATION",
4802 : pszGeolocXFullName, pszGeolocYFullName);
4803 :
4804 30 : GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
4805 : "GEOLOCATION");
4806 :
4807 60 : CPLString osTMP;
4808 30 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4809 30 : pszGeolocXFullName);
4810 :
4811 30 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP,
4812 : "GEOLOCATION");
4813 30 : GDALPamDataset::SetMetadataItem("X_BAND", "1",
4814 : "GEOLOCATION");
4815 30 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4816 30 : pszGeolocYFullName);
4817 :
4818 30 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP,
4819 : "GEOLOCATION");
4820 30 : GDALPamDataset::SetMetadataItem("Y_BAND", "1",
4821 : "GEOLOCATION");
4822 :
4823 30 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0",
4824 : "GEOLOCATION");
4825 30 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1",
4826 : "GEOLOCATION");
4827 :
4828 30 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0",
4829 : "GEOLOCATION");
4830 30 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1",
4831 : "GEOLOCATION");
4832 :
4833 30 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
4834 : "PIXEL_CENTER",
4835 : "GEOLOCATION");
4836 : }
4837 : else
4838 : {
4839 0 : CPLDebug("GDAL_netCDF",
4840 : "cannot resolve location of "
4841 : "lat/lon variables specified by the coordinates "
4842 : "attribute [%s]",
4843 : pszTemp);
4844 : }
4845 30 : CPLFree(pszGeolocXFullName);
4846 30 : CPLFree(pszGeolocYFullName);
4847 : }
4848 : else
4849 : {
4850 9 : CPLDebug("GDAL_netCDF",
4851 : "coordinates attribute [%s] is unsupported", pszTemp);
4852 : }
4853 : }
4854 : else
4855 : {
4856 3 : CPLDebug("GDAL_netCDF",
4857 : "coordinates attribute [%s] with %d element(s) is "
4858 : "unsupported",
4859 : pszTemp, CSLCount(papszTokens));
4860 : }
4861 42 : if (papszTokens)
4862 42 : CSLDestroy(papszTokens);
4863 : }
4864 :
4865 : else
4866 : {
4867 295 : bAddGeoloc = ProcessNASAL2OceanGeoLocation(nGroupId, nVarId);
4868 :
4869 295 : if (!bAddGeoloc)
4870 294 : bAddGeoloc = ProcessNASAEMITGeoLocation(nGroupId, nVarId);
4871 : }
4872 :
4873 337 : CPLFree(pszTemp);
4874 :
4875 337 : return bAddGeoloc;
4876 : }
4877 :
4878 8 : CPLErr netCDFDataset::Set1DGeolocation(int nGroupId, int nVarId,
4879 : const char *szDimName)
4880 : {
4881 : // Get values.
4882 8 : char *pszVarValues = nullptr;
4883 8 : CPLErr eErr = NCDFGet1DVar(nGroupId, nVarId, &pszVarValues);
4884 8 : if (eErr != CE_None)
4885 0 : return eErr;
4886 :
4887 : // Write metadata.
4888 8 : char szTemp[NC_MAX_NAME + 1 + 32] = {};
4889 8 : snprintf(szTemp, sizeof(szTemp), "%s_VALUES", szDimName);
4890 8 : GDALPamDataset::SetMetadataItem(szTemp, pszVarValues, "GEOLOCATION2");
4891 :
4892 8 : CPLFree(pszVarValues);
4893 :
4894 8 : return CE_None;
4895 : }
4896 :
4897 0 : double *netCDFDataset::Get1DGeolocation(CPL_UNUSED const char *szDimName,
4898 : int &nVarLen)
4899 : {
4900 0 : nVarLen = 0;
4901 :
4902 : // Get Y_VALUES as tokens.
4903 : char **papszValues =
4904 0 : NCDFTokenizeArray(GetMetadataItem("Y_VALUES", "GEOLOCATION2"));
4905 0 : if (papszValues == nullptr)
4906 0 : return nullptr;
4907 :
4908 : // Initialize and fill array.
4909 0 : nVarLen = CSLCount(papszValues);
4910 : double *pdfVarValues =
4911 0 : static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
4912 :
4913 0 : for (int i = 0, j = 0; i < nVarLen; i++)
4914 : {
4915 0 : if (!bBottomUp)
4916 0 : j = nVarLen - 1 - i;
4917 : else
4918 0 : j = i; // Invert latitude values.
4919 0 : char *pszTemp = nullptr;
4920 0 : pdfVarValues[j] = CPLStrtod(papszValues[i], &pszTemp);
4921 : }
4922 0 : CSLDestroy(papszValues);
4923 :
4924 0 : return pdfVarValues;
4925 : }
4926 :
4927 : /************************************************************************/
4928 : /* SetSpatialRefNoUpdate() */
4929 : /************************************************************************/
4930 :
4931 244 : void netCDFDataset::SetSpatialRefNoUpdate(const OGRSpatialReference *poSRS)
4932 : {
4933 244 : m_oSRS.Clear();
4934 244 : if (poSRS)
4935 237 : m_oSRS = *poSRS;
4936 244 : m_bHasProjection = true;
4937 244 : }
4938 :
4939 : /************************************************************************/
4940 : /* SetSpatialRef() */
4941 : /************************************************************************/
4942 :
4943 73 : CPLErr netCDFDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
4944 : {
4945 146 : CPLMutexHolderD(&hNCMutex);
4946 :
4947 73 : if (GetAccess() != GA_Update || m_bHasProjection)
4948 : {
4949 0 : CPLError(CE_Failure, CPLE_AppDefined,
4950 : "netCDFDataset::_SetProjection() should only be called once "
4951 : "in update mode!");
4952 0 : return CE_Failure;
4953 : }
4954 :
4955 73 : if (m_bHasGeoTransform)
4956 : {
4957 32 : SetSpatialRefNoUpdate(poSRS);
4958 :
4959 : // For NC4/NC4C, writing both projection variables and data,
4960 : // followed by redefining nodata value, cancels the projection
4961 : // info from the Band variable, so for now only write the
4962 : // variable definitions, and write data at the end.
4963 : // See https://trac.osgeo.org/gdal/ticket/7245
4964 32 : return AddProjectionVars(true, nullptr, nullptr);
4965 : }
4966 :
4967 41 : SetSpatialRefNoUpdate(poSRS);
4968 :
4969 41 : return CE_None;
4970 : }
4971 :
4972 : /************************************************************************/
4973 : /* SetGeoTransformNoUpdate() */
4974 : /************************************************************************/
4975 :
4976 267 : void netCDFDataset::SetGeoTransformNoUpdate(double *padfTransform)
4977 : {
4978 267 : memcpy(m_adfGeoTransform, padfTransform, sizeof(double) * 6);
4979 267 : m_bHasGeoTransform = true;
4980 267 : }
4981 :
4982 : /************************************************************************/
4983 : /* SetGeoTransform() */
4984 : /************************************************************************/
4985 :
4986 74 : CPLErr netCDFDataset::SetGeoTransform(double *padfTransform)
4987 : {
4988 148 : CPLMutexHolderD(&hNCMutex);
4989 :
4990 74 : if (GetAccess() != GA_Update || m_bHasGeoTransform)
4991 : {
4992 0 : CPLError(CE_Failure, CPLE_AppDefined,
4993 : "netCDFDataset::SetGeoTransform() should only be called once "
4994 : "in update mode!");
4995 0 : return CE_Failure;
4996 : }
4997 :
4998 74 : CPLDebug("GDAL_netCDF", "SetGeoTransform(%f,%f,%f,%f,%f,%f)",
4999 74 : padfTransform[0], padfTransform[1], padfTransform[2],
5000 74 : padfTransform[3], padfTransform[4], padfTransform[5]);
5001 :
5002 74 : if (m_bHasProjection)
5003 : {
5004 3 : SetGeoTransformNoUpdate(padfTransform);
5005 :
5006 : // For NC4/NC4C, writing both projection variables and data,
5007 : // followed by redefining nodata value, cancels the projection
5008 : // info from the Band variable, so for now only write the
5009 : // variable definitions, and write data at the end.
5010 : // See https://trac.osgeo.org/gdal/ticket/7245
5011 3 : return AddProjectionVars(true, nullptr, nullptr);
5012 : }
5013 :
5014 71 : SetGeoTransformNoUpdate(padfTransform);
5015 71 : return CE_None;
5016 : }
5017 :
5018 : /************************************************************************/
5019 : /* NCDFWriteSRSVariable() */
5020 : /************************************************************************/
5021 :
5022 125 : int NCDFWriteSRSVariable(int cdfid, const OGRSpatialReference *poSRS,
5023 : char **ppszCFProjection, bool bWriteGDALTags,
5024 : const std::string &srsVarName)
5025 : {
5026 125 : char *pszCFProjection = nullptr;
5027 125 : char **papszKeyValues = nullptr;
5028 125 : poSRS->exportToCF1(&pszCFProjection, &papszKeyValues, nullptr, nullptr);
5029 :
5030 125 : if (bWriteGDALTags)
5031 : {
5032 124 : const char *pszWKT = CSLFetchNameValue(papszKeyValues, NCDF_CRS_WKT);
5033 124 : if (pszWKT)
5034 : {
5035 : // SPATIAL_REF is deprecated. Will be removed in GDAL 4.
5036 124 : papszKeyValues =
5037 124 : CSLSetNameValue(papszKeyValues, NCDF_SPATIAL_REF, pszWKT);
5038 : }
5039 : }
5040 :
5041 125 : const int nValues = CSLCount(papszKeyValues);
5042 :
5043 : int NCDFVarID;
5044 250 : std::string varNameRadix(pszCFProjection);
5045 125 : int nCounter = 2;
5046 : while (true)
5047 : {
5048 127 : NCDFVarID = -1;
5049 127 : nc_inq_varid(cdfid, pszCFProjection, &NCDFVarID);
5050 127 : if (NCDFVarID < 0)
5051 122 : break;
5052 :
5053 5 : int nbAttr = 0;
5054 5 : NCDF_ERR(nc_inq_varnatts(cdfid, NCDFVarID, &nbAttr));
5055 5 : bool bSame = nbAttr == nValues;
5056 41 : for (int i = 0; bSame && (i < nbAttr); i++)
5057 : {
5058 : char szAttrName[NC_MAX_NAME + 1];
5059 38 : szAttrName[0] = 0;
5060 38 : NCDF_ERR(nc_inq_attname(cdfid, NCDFVarID, i, szAttrName));
5061 :
5062 : const char *pszValue =
5063 38 : CSLFetchNameValue(papszKeyValues, szAttrName);
5064 38 : if (!pszValue)
5065 : {
5066 0 : bSame = false;
5067 2 : break;
5068 : }
5069 :
5070 38 : nc_type atttype = NC_NAT;
5071 38 : size_t attlen = 0;
5072 38 : NCDF_ERR(
5073 : nc_inq_att(cdfid, NCDFVarID, szAttrName, &atttype, &attlen));
5074 38 : if (atttype != NC_CHAR && atttype != NC_DOUBLE)
5075 : {
5076 0 : bSame = false;
5077 0 : break;
5078 : }
5079 38 : if (atttype == NC_CHAR)
5080 : {
5081 15 : if (CPLGetValueType(pszValue) != CPL_VALUE_STRING)
5082 : {
5083 0 : bSame = false;
5084 0 : break;
5085 : }
5086 15 : std::string val;
5087 15 : val.resize(attlen);
5088 15 : nc_get_att_text(cdfid, NCDFVarID, szAttrName, &val[0]);
5089 15 : if (val != pszValue)
5090 : {
5091 0 : bSame = false;
5092 0 : break;
5093 : }
5094 : }
5095 : else
5096 : {
5097 : const CPLStringList aosTokens(
5098 23 : CSLTokenizeString2(pszValue, ",", 0));
5099 23 : if (static_cast<size_t>(aosTokens.size()) != attlen)
5100 : {
5101 0 : bSame = false;
5102 0 : break;
5103 : }
5104 : double vals[2];
5105 23 : nc_get_att_double(cdfid, NCDFVarID, szAttrName, vals);
5106 44 : if (vals[0] != CPLAtof(aosTokens[0]) ||
5107 21 : (attlen == 2 && vals[1] != CPLAtof(aosTokens[1])))
5108 : {
5109 2 : bSame = false;
5110 2 : break;
5111 : }
5112 : }
5113 : }
5114 5 : if (bSame)
5115 : {
5116 3 : *ppszCFProjection = pszCFProjection;
5117 3 : CSLDestroy(papszKeyValues);
5118 3 : return NCDFVarID;
5119 : }
5120 2 : CPLFree(pszCFProjection);
5121 2 : pszCFProjection =
5122 2 : CPLStrdup(CPLSPrintf("%s_%d", varNameRadix.c_str(), nCounter));
5123 2 : nCounter++;
5124 2 : }
5125 :
5126 122 : *ppszCFProjection = pszCFProjection;
5127 :
5128 : const char *pszVarName;
5129 :
5130 122 : if (srsVarName != "")
5131 : {
5132 38 : pszVarName = srsVarName.c_str();
5133 : }
5134 : else
5135 : {
5136 84 : pszVarName = pszCFProjection;
5137 : }
5138 :
5139 122 : int status = nc_def_var(cdfid, pszVarName, NC_CHAR, 0, nullptr, &NCDFVarID);
5140 122 : NCDF_ERR(status);
5141 1170 : for (int i = 0; i < nValues; ++i)
5142 : {
5143 1048 : char *pszKey = nullptr;
5144 1048 : const char *pszValue = CPLParseNameValue(papszKeyValues[i], &pszKey);
5145 1048 : if (pszKey && pszValue)
5146 : {
5147 2096 : const CPLStringList aosTokens(CSLTokenizeString2(pszValue, ",", 0));
5148 1048 : double adfValues[2] = {0, 0};
5149 1048 : const int nDoubleCount = std::min(2, aosTokens.size());
5150 1048 : if (!(aosTokens.size() == 2 &&
5151 2095 : CPLGetValueType(aosTokens[0]) != CPL_VALUE_STRING) &&
5152 1047 : CPLGetValueType(pszValue) == CPL_VALUE_STRING)
5153 : {
5154 487 : status = nc_put_att_text(cdfid, NCDFVarID, pszKey,
5155 : strlen(pszValue), pszValue);
5156 : }
5157 : else
5158 : {
5159 1123 : for (int j = 0; j < nDoubleCount; ++j)
5160 562 : adfValues[j] = CPLAtof(aosTokens[j]);
5161 561 : status = nc_put_att_double(cdfid, NCDFVarID, pszKey, NC_DOUBLE,
5162 : nDoubleCount, adfValues);
5163 : }
5164 1048 : NCDF_ERR(status);
5165 : }
5166 1048 : CPLFree(pszKey);
5167 : }
5168 :
5169 122 : CSLDestroy(papszKeyValues);
5170 122 : return NCDFVarID;
5171 : }
5172 :
5173 : /************************************************************************/
5174 : /* NCDFWriteLonLatVarsAttributes() */
5175 : /************************************************************************/
5176 :
5177 99 : void NCDFWriteLonLatVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarLonID,
5178 : int nVarLatID)
5179 : {
5180 :
5181 : try
5182 : {
5183 99 : vcdf.nc_put_vatt_text(nVarLatID, CF_STD_NAME, CF_LATITUDE_STD_NAME);
5184 99 : vcdf.nc_put_vatt_text(nVarLatID, CF_LNG_NAME, CF_LATITUDE_LNG_NAME);
5185 99 : vcdf.nc_put_vatt_text(nVarLatID, CF_UNITS, CF_DEGREES_NORTH);
5186 99 : vcdf.nc_put_vatt_text(nVarLonID, CF_STD_NAME, CF_LONGITUDE_STD_NAME);
5187 99 : vcdf.nc_put_vatt_text(nVarLonID, CF_LNG_NAME, CF_LONGITUDE_LNG_NAME);
5188 99 : vcdf.nc_put_vatt_text(nVarLonID, CF_UNITS, CF_DEGREES_EAST);
5189 : }
5190 0 : catch (nccfdriver::SG_Exception &e)
5191 : {
5192 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5193 : }
5194 99 : }
5195 :
5196 : /************************************************************************/
5197 : /* NCDFWriteRLonRLatVarsAttributes() */
5198 : /************************************************************************/
5199 :
5200 0 : void NCDFWriteRLonRLatVarsAttributes(nccfdriver::netCDFVID &vcdf,
5201 : int nVarRLonID, int nVarRLatID)
5202 : {
5203 : try
5204 : {
5205 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_STD_NAME, "grid_latitude");
5206 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_LNG_NAME,
5207 : "latitude in rotated pole grid");
5208 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_UNITS, "degrees");
5209 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_AXIS, "Y");
5210 :
5211 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_STD_NAME, "grid_longitude");
5212 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_LNG_NAME,
5213 : "longitude in rotated pole grid");
5214 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_UNITS, "degrees");
5215 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_AXIS, "X");
5216 : }
5217 0 : catch (nccfdriver::SG_Exception &e)
5218 : {
5219 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5220 : }
5221 0 : }
5222 :
5223 : /************************************************************************/
5224 : /* NCDFGetProjectedCFUnit() */
5225 : /************************************************************************/
5226 :
5227 39 : std::string NCDFGetProjectedCFUnit(const OGRSpatialReference *poSRS)
5228 : {
5229 39 : char *pszUnitsToWrite = nullptr;
5230 39 : poSRS->exportToCF1(nullptr, nullptr, &pszUnitsToWrite, nullptr);
5231 39 : const std::string osRet = pszUnitsToWrite ? pszUnitsToWrite : std::string();
5232 39 : CPLFree(pszUnitsToWrite);
5233 78 : return osRet;
5234 : }
5235 :
5236 : /************************************************************************/
5237 : /* NCDFWriteXYVarsAttributes() */
5238 : /************************************************************************/
5239 :
5240 28 : void NCDFWriteXYVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarXID,
5241 : int nVarYID, const OGRSpatialReference *poSRS)
5242 : {
5243 56 : const std::string osUnitsToWrite = NCDFGetProjectedCFUnit(poSRS);
5244 :
5245 : try
5246 : {
5247 28 : vcdf.nc_put_vatt_text(nVarXID, CF_STD_NAME, CF_PROJ_X_COORD);
5248 28 : vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME, CF_PROJ_X_COORD_LONG_NAME);
5249 28 : if (!osUnitsToWrite.empty())
5250 28 : vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, osUnitsToWrite.c_str());
5251 28 : vcdf.nc_put_vatt_text(nVarYID, CF_STD_NAME, CF_PROJ_Y_COORD);
5252 28 : vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME, CF_PROJ_Y_COORD_LONG_NAME);
5253 28 : if (!osUnitsToWrite.empty())
5254 28 : vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, osUnitsToWrite.c_str());
5255 : }
5256 0 : catch (nccfdriver::SG_Exception &e)
5257 : {
5258 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5259 : }
5260 28 : }
5261 :
5262 : /************************************************************************/
5263 : /* AddProjectionVars() */
5264 : /************************************************************************/
5265 :
5266 158 : CPLErr netCDFDataset::AddProjectionVars(bool bDefsOnly,
5267 : GDALProgressFunc pfnProgress,
5268 : void *pProgressData)
5269 : {
5270 158 : if (nCFVersion >= 1.8)
5271 0 : return CE_None; // do nothing
5272 :
5273 158 : bool bWriteGridMapping = false;
5274 158 : bool bWriteLonLat = false;
5275 158 : bool bHasGeoloc = false;
5276 158 : bool bWriteGDALTags = false;
5277 158 : bool bWriteGeoTransform = false;
5278 :
5279 : // For GEOLOCATION information.
5280 158 : GDALDatasetH hDS_X = nullptr;
5281 158 : GDALRasterBandH hBand_X = nullptr;
5282 158 : GDALDatasetH hDS_Y = nullptr;
5283 158 : GDALRasterBandH hBand_Y = nullptr;
5284 :
5285 316 : OGRSpatialReference oSRS(m_oSRS);
5286 158 : if (!m_oSRS.IsEmpty())
5287 : {
5288 132 : if (oSRS.IsProjected())
5289 48 : bIsProjected = true;
5290 84 : else if (oSRS.IsGeographic())
5291 84 : bIsGeographic = true;
5292 : }
5293 :
5294 158 : if (bDefsOnly)
5295 : {
5296 79 : char *pszProjection = nullptr;
5297 79 : m_oSRS.exportToWkt(&pszProjection);
5298 79 : CPLDebug("GDAL_netCDF",
5299 : "SetProjection, WKT now = [%s]\nprojected: %d geographic: %d",
5300 79 : pszProjection ? pszProjection : "(null)",
5301 79 : static_cast<int>(bIsProjected),
5302 79 : static_cast<int>(bIsGeographic));
5303 79 : CPLFree(pszProjection);
5304 :
5305 79 : if (!m_bHasGeoTransform)
5306 5 : CPLDebug("GDAL_netCDF",
5307 : "netCDFDataset::AddProjectionVars() called, "
5308 : "but GeoTransform has not yet been defined!");
5309 :
5310 79 : if (!m_bHasProjection)
5311 6 : CPLDebug("GDAL_netCDF",
5312 : "netCDFDataset::AddProjectionVars() called, "
5313 : "but Projection has not yet been defined!");
5314 : }
5315 :
5316 : // Check GEOLOCATION information.
5317 158 : char **papszGeolocationInfo = netCDFDataset::GetMetadata("GEOLOCATION");
5318 158 : if (papszGeolocationInfo != nullptr)
5319 : {
5320 : // Look for geolocation datasets.
5321 : const char *pszDSName =
5322 10 : CSLFetchNameValue(papszGeolocationInfo, "X_DATASET");
5323 10 : if (pszDSName != nullptr)
5324 10 : hDS_X = GDALOpenShared(pszDSName, GA_ReadOnly);
5325 10 : pszDSName = CSLFetchNameValue(papszGeolocationInfo, "Y_DATASET");
5326 10 : if (pszDSName != nullptr)
5327 10 : hDS_Y = GDALOpenShared(pszDSName, GA_ReadOnly);
5328 :
5329 10 : if (hDS_X != nullptr && hDS_Y != nullptr)
5330 : {
5331 10 : int nBand = std::max(1, atoi(CSLFetchNameValueDef(
5332 10 : papszGeolocationInfo, "X_BAND", "0")));
5333 10 : hBand_X = GDALGetRasterBand(hDS_X, nBand);
5334 10 : nBand = std::max(1, atoi(CSLFetchNameValueDef(papszGeolocationInfo,
5335 10 : "Y_BAND", "0")));
5336 10 : hBand_Y = GDALGetRasterBand(hDS_Y, nBand);
5337 :
5338 : // If geoloc bands are found, do basic validation based on their
5339 : // dimensions.
5340 10 : if (hBand_X != nullptr && hBand_Y != nullptr)
5341 : {
5342 10 : int nXSize_XBand = GDALGetRasterXSize(hDS_X);
5343 10 : int nYSize_XBand = GDALGetRasterYSize(hDS_X);
5344 10 : int nXSize_YBand = GDALGetRasterXSize(hDS_Y);
5345 10 : int nYSize_YBand = GDALGetRasterYSize(hDS_Y);
5346 :
5347 : // TODO 1D geolocation arrays not implemented.
5348 10 : if (nYSize_XBand == 1 && nYSize_YBand == 1)
5349 : {
5350 0 : bHasGeoloc = false;
5351 0 : CPLDebug("GDAL_netCDF",
5352 : "1D GEOLOCATION arrays not supported yet");
5353 : }
5354 : // 2D bands must have same sizes as the raster bands.
5355 10 : else if (nXSize_XBand != nRasterXSize ||
5356 10 : nYSize_XBand != nRasterYSize ||
5357 10 : nXSize_YBand != nRasterXSize ||
5358 10 : nYSize_YBand != nRasterYSize)
5359 : {
5360 0 : bHasGeoloc = false;
5361 0 : CPLDebug("GDAL_netCDF",
5362 : "GEOLOCATION array sizes (%dx%d %dx%d) differ "
5363 : "from raster (%dx%d), not supported",
5364 : nXSize_XBand, nYSize_XBand, nXSize_YBand,
5365 : nYSize_YBand, nRasterXSize, nRasterYSize);
5366 : }
5367 : else
5368 : {
5369 10 : bHasGeoloc = true;
5370 10 : CPLDebug("GDAL_netCDF",
5371 : "dataset has GEOLOCATION information, will try to "
5372 : "write it");
5373 : }
5374 : }
5375 : }
5376 : }
5377 :
5378 : // Process projection options.
5379 158 : if (bIsProjected)
5380 : {
5381 : bool bIsCfProjection =
5382 48 : oSRS.exportToCF1(nullptr, nullptr, nullptr, nullptr) == OGRERR_NONE;
5383 48 : bWriteGridMapping = true;
5384 48 : bWriteGDALTags = CPL_TO_BOOL(
5385 48 : CSLFetchBoolean(papszCreationOptions, "WRITE_GDAL_TAGS", TRUE));
5386 : // Force WRITE_GDAL_TAGS if is not a CF projection.
5387 48 : if (!bWriteGDALTags && !bIsCfProjection)
5388 0 : bWriteGDALTags = true;
5389 48 : if (bWriteGDALTags)
5390 48 : bWriteGeoTransform = true;
5391 :
5392 : // Write lon/lat: default is NO, except if has geolocation.
5393 : // With IF_NEEDED: write if has geoloc or is not CF projection.
5394 : const char *pszValue =
5395 48 : CSLFetchNameValue(papszCreationOptions, "WRITE_LONLAT");
5396 48 : if (pszValue)
5397 : {
5398 2 : if (EQUAL(pszValue, "IF_NEEDED"))
5399 : {
5400 0 : bWriteLonLat = bHasGeoloc || !bIsCfProjection;
5401 : }
5402 : else
5403 : {
5404 2 : bWriteLonLat = CPLTestBool(pszValue);
5405 : }
5406 : }
5407 : else
5408 : {
5409 46 : bWriteLonLat = bHasGeoloc;
5410 : }
5411 :
5412 : // Save value of pszCFCoordinates for later.
5413 48 : if (bWriteLonLat)
5414 : {
5415 4 : pszCFCoordinates = NCDF_LONLAT;
5416 : }
5417 : }
5418 : else
5419 : {
5420 : // Files without a Datum will not have a grid_mapping variable and
5421 : // geographic information.
5422 110 : bWriteGridMapping = bIsGeographic;
5423 :
5424 110 : if (bHasGeoloc)
5425 : {
5426 8 : bWriteLonLat = true;
5427 : }
5428 : else
5429 : {
5430 102 : bWriteGDALTags = CPL_TO_BOOL(CSLFetchBoolean(
5431 102 : papszCreationOptions, "WRITE_GDAL_TAGS", bWriteGridMapping));
5432 102 : if (bWriteGDALTags)
5433 84 : bWriteGeoTransform = true;
5434 :
5435 102 : const char *pszValue = CSLFetchNameValueDef(papszCreationOptions,
5436 : "WRITE_LONLAT", "YES");
5437 102 : if (EQUAL(pszValue, "IF_NEEDED"))
5438 0 : bWriteLonLat = true;
5439 : else
5440 102 : bWriteLonLat = CPLTestBool(pszValue);
5441 : // Don't write lon/lat if no source geotransform.
5442 102 : if (!m_bHasGeoTransform)
5443 0 : bWriteLonLat = false;
5444 : // If we don't write lon/lat, set dimnames to X/Y and write gdal
5445 : // tags.
5446 102 : if (!bWriteLonLat)
5447 : {
5448 0 : CPLError(CE_Warning, CPLE_AppDefined,
5449 : "creating geographic file without lon/lat values!");
5450 0 : if (m_bHasGeoTransform)
5451 : {
5452 0 : bWriteGDALTags = true; // Not desirable if no geotransform.
5453 0 : bWriteGeoTransform = true;
5454 : }
5455 : }
5456 : }
5457 : }
5458 :
5459 : // Make sure we write grid_mapping if we need to write GDAL tags.
5460 158 : if (bWriteGDALTags)
5461 132 : bWriteGridMapping = true;
5462 :
5463 : // bottom-up value: new driver is bottom-up by default.
5464 : // Override with WRITE_BOTTOMUP.
5465 158 : bBottomUp = CPL_TO_BOOL(
5466 158 : CSLFetchBoolean(papszCreationOptions, "WRITE_BOTTOMUP", TRUE));
5467 :
5468 158 : if (bDefsOnly)
5469 : {
5470 79 : CPLDebug(
5471 : "GDAL_netCDF",
5472 : "bIsProjected=%d bIsGeographic=%d bWriteGridMapping=%d "
5473 : "bWriteGDALTags=%d bWriteLonLat=%d bBottomUp=%d bHasGeoloc=%d",
5474 79 : static_cast<int>(bIsProjected), static_cast<int>(bIsGeographic),
5475 : static_cast<int>(bWriteGridMapping),
5476 : static_cast<int>(bWriteGDALTags), static_cast<int>(bWriteLonLat),
5477 79 : static_cast<int>(bBottomUp), static_cast<int>(bHasGeoloc));
5478 : }
5479 :
5480 : // Exit if nothing to do.
5481 158 : if (!bIsProjected && !bWriteLonLat)
5482 0 : return CE_None;
5483 :
5484 : // Define dimension names.
5485 :
5486 158 : constexpr const char *ROTATED_POLE_VAR_NAME = "rotated_pole";
5487 :
5488 158 : if (bDefsOnly)
5489 : {
5490 79 : int nVarLonID = -1;
5491 79 : int nVarLatID = -1;
5492 79 : int nVarXID = -1;
5493 79 : int nVarYID = -1;
5494 :
5495 79 : m_bAddedProjectionVarsDefs = true;
5496 :
5497 : // Make sure we are in define mode.
5498 79 : SetDefineMode(true);
5499 :
5500 : // Write projection attributes.
5501 79 : if (bWriteGridMapping)
5502 : {
5503 66 : const int NCDFVarID = NCDFWriteSRSVariable(
5504 : cdfid, &oSRS, &pszCFProjection, bWriteGDALTags);
5505 66 : if (NCDFVarID < 0)
5506 0 : return CE_Failure;
5507 :
5508 : // Optional GDAL custom projection tags.
5509 66 : if (bWriteGDALTags)
5510 : {
5511 132 : CPLString osGeoTransform;
5512 462 : for (int i = 0; i < 6; i++)
5513 : {
5514 : osGeoTransform +=
5515 396 : CPLSPrintf("%.16g ", m_adfGeoTransform[i]);
5516 : }
5517 66 : CPLDebug("GDAL_netCDF", "szGeoTransform = %s",
5518 : osGeoTransform.c_str());
5519 :
5520 : // if( strlen(pszProj4Defn) > 0 ) {
5521 : // nc_put_att_text(cdfid, NCDFVarID, "proj4",
5522 : // strlen(pszProj4Defn), pszProj4Defn);
5523 : // }
5524 :
5525 : // For now, write the geotransform for back-compat or else
5526 : // the old (1.8.1) driver overrides the CF geotransform with
5527 : // empty values from dfNN, dfSN, dfEE, dfWE;
5528 :
5529 : // TODO: fix this in 1.8 branch, and then remove this here.
5530 66 : if (bWriteGeoTransform && m_bHasGeoTransform)
5531 : {
5532 : {
5533 65 : const int status = nc_put_att_text(
5534 : cdfid, NCDFVarID, NCDF_GEOTRANSFORM,
5535 : osGeoTransform.size(), osGeoTransform.c_str());
5536 65 : NCDF_ERR(status);
5537 : }
5538 : }
5539 : }
5540 :
5541 : // Write projection variable to band variable.
5542 : // Need to call later if there are no bands.
5543 66 : AddGridMappingRef();
5544 : } // end if( bWriteGridMapping )
5545 :
5546 : // Write CF Projection vars.
5547 :
5548 79 : const bool bIsRotatedPole =
5549 145 : pszCFProjection != nullptr &&
5550 66 : EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
5551 79 : if (bIsRotatedPole)
5552 : {
5553 : // Rename dims to rlat/rlon.
5554 : papszDimName
5555 0 : .Clear(); // If we add other dims one day, this has to change
5556 0 : papszDimName.AddString(NCDF_DIMNAME_RLAT);
5557 0 : papszDimName.AddString(NCDF_DIMNAME_RLON);
5558 :
5559 0 : int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_RLAT);
5560 0 : NCDF_ERR(status);
5561 0 : status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_RLON);
5562 0 : NCDF_ERR(status);
5563 : }
5564 : // Rename dimensions if lon/lat.
5565 79 : else if (!bIsProjected && !bHasGeoloc)
5566 : {
5567 : // Rename dims to lat/lon.
5568 : papszDimName
5569 51 : .Clear(); // If we add other dims one day, this has to change
5570 51 : papszDimName.AddString(NCDF_DIMNAME_LAT);
5571 51 : papszDimName.AddString(NCDF_DIMNAME_LON);
5572 :
5573 51 : int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_LAT);
5574 51 : NCDF_ERR(status);
5575 51 : status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_LON);
5576 51 : NCDF_ERR(status);
5577 : }
5578 :
5579 : // Write X/Y attributes.
5580 : else /* if( bIsProjected || bHasGeoloc ) */
5581 : {
5582 : // X
5583 : int anXDims[1];
5584 28 : anXDims[0] = nXDimID;
5585 28 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
5586 : CF_PROJ_X_VAR_NAME, NC_DOUBLE);
5587 28 : int status = nc_def_var(cdfid, CF_PROJ_X_VAR_NAME, NC_DOUBLE, 1,
5588 : anXDims, &nVarXID);
5589 28 : NCDF_ERR(status);
5590 :
5591 : // Y
5592 : int anYDims[1];
5593 28 : anYDims[0] = nYDimID;
5594 28 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
5595 : CF_PROJ_Y_VAR_NAME, NC_DOUBLE);
5596 28 : status = nc_def_var(cdfid, CF_PROJ_Y_VAR_NAME, NC_DOUBLE, 1,
5597 : anYDims, &nVarYID);
5598 28 : NCDF_ERR(status);
5599 :
5600 28 : if (bIsProjected)
5601 : {
5602 24 : NCDFWriteXYVarsAttributes(this->vcdf, nVarXID, nVarYID, &oSRS);
5603 : }
5604 : else
5605 : {
5606 4 : CPLAssert(bHasGeoloc);
5607 : try
5608 : {
5609 4 : vcdf.nc_put_vatt_text(nVarXID, CF_AXIS, CF_SG_X_AXIS);
5610 4 : vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME,
5611 : "x-coordinate in Cartesian system");
5612 4 : vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, "m");
5613 4 : vcdf.nc_put_vatt_text(nVarYID, CF_AXIS, CF_SG_Y_AXIS);
5614 4 : vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME,
5615 : "y-coordinate in Cartesian system");
5616 4 : vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, "m");
5617 :
5618 4 : pszCFCoordinates = NCDF_LONLAT;
5619 : }
5620 0 : catch (nccfdriver::SG_Exception &e)
5621 : {
5622 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5623 0 : return CE_Failure;
5624 : }
5625 : }
5626 : }
5627 :
5628 : // Write lat/lon attributes if needed.
5629 79 : if (bWriteLonLat)
5630 : {
5631 57 : int *panLatDims = nullptr;
5632 57 : int *panLonDims = nullptr;
5633 57 : int nLatDims = -1;
5634 57 : int nLonDims = -1;
5635 :
5636 : // Get information.
5637 57 : if (bHasGeoloc)
5638 : {
5639 : // Geoloc
5640 5 : nLatDims = 2;
5641 : panLatDims =
5642 5 : static_cast<int *>(CPLCalloc(nLatDims, sizeof(int)));
5643 5 : panLatDims[0] = nYDimID;
5644 5 : panLatDims[1] = nXDimID;
5645 5 : nLonDims = 2;
5646 : panLonDims =
5647 5 : static_cast<int *>(CPLCalloc(nLonDims, sizeof(int)));
5648 5 : panLonDims[0] = nYDimID;
5649 5 : panLonDims[1] = nXDimID;
5650 : }
5651 52 : else if (bIsProjected)
5652 : {
5653 : // Projected
5654 1 : nLatDims = 2;
5655 : panLatDims =
5656 1 : static_cast<int *>(CPLCalloc(nLatDims, sizeof(int)));
5657 1 : panLatDims[0] = nYDimID;
5658 1 : panLatDims[1] = nXDimID;
5659 1 : nLonDims = 2;
5660 : panLonDims =
5661 1 : static_cast<int *>(CPLCalloc(nLonDims, sizeof(int)));
5662 1 : panLonDims[0] = nYDimID;
5663 1 : panLonDims[1] = nXDimID;
5664 : }
5665 : else
5666 : {
5667 : // Geographic
5668 51 : nLatDims = 1;
5669 : panLatDims =
5670 51 : static_cast<int *>(CPLCalloc(nLatDims, sizeof(int)));
5671 51 : panLatDims[0] = nYDimID;
5672 51 : nLonDims = 1;
5673 : panLonDims =
5674 51 : static_cast<int *>(CPLCalloc(nLonDims, sizeof(int)));
5675 51 : panLonDims[0] = nXDimID;
5676 : }
5677 :
5678 57 : nc_type eLonLatType = NC_NAT;
5679 57 : if (bIsProjected)
5680 : {
5681 2 : eLonLatType = NC_FLOAT;
5682 4 : const char *pszValue = CSLFetchNameValueDef(
5683 2 : papszCreationOptions, "TYPE_LONLAT", "FLOAT");
5684 2 : if (EQUAL(pszValue, "DOUBLE"))
5685 0 : eLonLatType = NC_DOUBLE;
5686 : }
5687 : else
5688 : {
5689 55 : eLonLatType = NC_DOUBLE;
5690 110 : const char *pszValue = CSLFetchNameValueDef(
5691 55 : papszCreationOptions, "TYPE_LONLAT", "DOUBLE");
5692 55 : if (EQUAL(pszValue, "FLOAT"))
5693 0 : eLonLatType = NC_FLOAT;
5694 : }
5695 :
5696 : // Def vars and attributes.
5697 : {
5698 57 : const char *pszVarName =
5699 57 : bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME;
5700 57 : int status = nc_def_var(cdfid, pszVarName, eLonLatType,
5701 : nLatDims, panLatDims, &nVarLatID);
5702 57 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
5703 : cdfid, pszVarName, eLonLatType, nLatDims, nVarLatID);
5704 57 : NCDF_ERR(status);
5705 57 : DefVarDeflate(nVarLatID, false); // Don't set chunking.
5706 : }
5707 :
5708 : {
5709 57 : const char *pszVarName =
5710 57 : bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME;
5711 57 : int status = nc_def_var(cdfid, pszVarName, eLonLatType,
5712 : nLonDims, panLonDims, &nVarLonID);
5713 57 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
5714 : cdfid, pszVarName, eLonLatType, nLatDims, nVarLonID);
5715 57 : NCDF_ERR(status);
5716 57 : DefVarDeflate(nVarLonID, false); // Don't set chunking.
5717 : }
5718 :
5719 57 : if (bIsRotatedPole)
5720 0 : NCDFWriteRLonRLatVarsAttributes(this->vcdf, nVarLonID,
5721 : nVarLatID);
5722 : else
5723 57 : NCDFWriteLonLatVarsAttributes(this->vcdf, nVarLonID, nVarLatID);
5724 :
5725 57 : CPLFree(panLatDims);
5726 57 : CPLFree(panLonDims);
5727 : }
5728 : }
5729 :
5730 158 : if (!bDefsOnly)
5731 : {
5732 79 : m_bAddedProjectionVarsData = true;
5733 :
5734 79 : int nVarXID = -1;
5735 79 : int nVarYID = -1;
5736 :
5737 79 : nc_inq_varid(cdfid, CF_PROJ_X_VAR_NAME, &nVarXID);
5738 79 : nc_inq_varid(cdfid, CF_PROJ_Y_VAR_NAME, &nVarYID);
5739 :
5740 79 : int nVarLonID = -1;
5741 79 : int nVarLatID = -1;
5742 :
5743 79 : const bool bIsRotatedPole =
5744 145 : pszCFProjection != nullptr &&
5745 66 : EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
5746 79 : nc_inq_varid(cdfid,
5747 : bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME,
5748 : &nVarLonID);
5749 79 : nc_inq_varid(cdfid,
5750 : bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME,
5751 : &nVarLatID);
5752 :
5753 : // Get projection values.
5754 :
5755 79 : double *padLonVal = nullptr;
5756 79 : double *padLatVal = nullptr;
5757 :
5758 79 : if (bIsProjected)
5759 : {
5760 24 : OGRSpatialReference *poLatLonSRS = nullptr;
5761 24 : OGRCoordinateTransformation *poTransform = nullptr;
5762 :
5763 : size_t startX[1];
5764 : size_t countX[1];
5765 : size_t startY[1];
5766 : size_t countY[1];
5767 :
5768 24 : CPLDebug("GDAL_netCDF", "Getting (X,Y) values");
5769 :
5770 : double *padXVal =
5771 24 : static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
5772 : double *padYVal =
5773 24 : static_cast<double *>(CPLMalloc(nRasterYSize * sizeof(double)));
5774 :
5775 : // Get Y values.
5776 24 : const double dfY0 = (!bBottomUp) ? m_adfGeoTransform[3] :
5777 : // Invert latitude values.
5778 24 : m_adfGeoTransform[3] +
5779 24 : (m_adfGeoTransform[5] * nRasterYSize);
5780 24 : const double dfDY = m_adfGeoTransform[5];
5781 :
5782 1435 : for (int j = 0; j < nRasterYSize; j++)
5783 : {
5784 : // The data point is centered inside the pixel.
5785 1411 : if (!bBottomUp)
5786 0 : padYVal[j] = dfY0 + (j + 0.5) * dfDY;
5787 : else // Invert latitude values.
5788 1411 : padYVal[j] = dfY0 - (j + 0.5) * dfDY;
5789 : }
5790 24 : startX[0] = 0;
5791 24 : countX[0] = nRasterXSize;
5792 :
5793 : // Get X values.
5794 24 : const double dfX0 = m_adfGeoTransform[0];
5795 24 : const double dfDX = m_adfGeoTransform[1];
5796 :
5797 1456 : for (int i = 0; i < nRasterXSize; i++)
5798 : {
5799 : // The data point is centered inside the pixel.
5800 1432 : padXVal[i] = dfX0 + (i + 0.5) * dfDX;
5801 : }
5802 24 : startY[0] = 0;
5803 24 : countY[0] = nRasterYSize;
5804 :
5805 : // Write X/Y values.
5806 :
5807 : // Make sure we are in data mode.
5808 24 : SetDefineMode(false);
5809 :
5810 24 : CPLDebug("GDAL_netCDF", "Writing X values");
5811 : int status =
5812 24 : nc_put_vara_double(cdfid, nVarXID, startX, countX, padXVal);
5813 24 : NCDF_ERR(status);
5814 :
5815 24 : CPLDebug("GDAL_netCDF", "Writing Y values");
5816 : status =
5817 24 : nc_put_vara_double(cdfid, nVarYID, startY, countY, padYVal);
5818 24 : NCDF_ERR(status);
5819 :
5820 24 : if (pfnProgress)
5821 20 : pfnProgress(0.20, nullptr, pProgressData);
5822 :
5823 : // Write lon/lat arrays (CF coordinates) if requested.
5824 :
5825 : // Get OGR transform if GEOLOCATION is not available.
5826 24 : if (bWriteLonLat && !bHasGeoloc)
5827 : {
5828 1 : poLatLonSRS = m_oSRS.CloneGeogCS();
5829 1 : if (poLatLonSRS != nullptr)
5830 : {
5831 1 : poLatLonSRS->SetAxisMappingStrategy(
5832 : OAMS_TRADITIONAL_GIS_ORDER);
5833 : poTransform =
5834 1 : OGRCreateCoordinateTransformation(&m_oSRS, poLatLonSRS);
5835 : }
5836 : // If no OGR transform, then don't write CF lon/lat.
5837 1 : if (poTransform == nullptr)
5838 : {
5839 0 : CPLError(CE_Failure, CPLE_AppDefined,
5840 : "Unable to get Coordinate Transform");
5841 0 : bWriteLonLat = false;
5842 : }
5843 : }
5844 :
5845 24 : if (bWriteLonLat)
5846 : {
5847 2 : if (!bHasGeoloc)
5848 1 : CPLDebug("GDAL_netCDF", "Transforming (X,Y)->(lon,lat)");
5849 : else
5850 1 : CPLDebug("GDAL_netCDF",
5851 : "Writing (lon,lat) from GEOLOCATION arrays");
5852 :
5853 2 : bool bOK = true;
5854 2 : double dfProgress = 0.2;
5855 :
5856 2 : size_t start[] = {0, 0};
5857 2 : size_t count[] = {1, (size_t)nRasterXSize};
5858 : padLatVal = static_cast<double *>(
5859 2 : CPLMalloc(nRasterXSize * sizeof(double)));
5860 : padLonVal = static_cast<double *>(
5861 2 : CPLMalloc(nRasterXSize * sizeof(double)));
5862 :
5863 61 : for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR;
5864 : j++)
5865 : {
5866 59 : start[0] = j;
5867 :
5868 : // Get values from geotransform.
5869 59 : if (!bHasGeoloc)
5870 : {
5871 : // Fill values to transform.
5872 420 : for (int i = 0; i < nRasterXSize; i++)
5873 : {
5874 400 : padLatVal[i] = padYVal[j];
5875 400 : padLonVal[i] = padXVal[i];
5876 : }
5877 :
5878 : // Do the transform.
5879 20 : bOK = CPL_TO_BOOL(poTransform->Transform(
5880 20 : nRasterXSize, padLonVal, padLatVal, nullptr));
5881 20 : if (!bOK)
5882 : {
5883 0 : CPLError(CE_Failure, CPLE_AppDefined,
5884 : "Unable to Transform (X,Y) to (lon,lat).");
5885 : }
5886 : }
5887 : // Get values from geoloc arrays.
5888 : else
5889 : {
5890 39 : CPLErr eErr = GDALRasterIO(
5891 : hBand_Y, GF_Read, 0, j, nRasterXSize, 1, padLatVal,
5892 : nRasterXSize, 1, GDT_Float64, 0, 0);
5893 39 : if (eErr == CE_None)
5894 : {
5895 39 : eErr = GDALRasterIO(
5896 : hBand_X, GF_Read, 0, j, nRasterXSize, 1,
5897 : padLonVal, nRasterXSize, 1, GDT_Float64, 0, 0);
5898 : }
5899 :
5900 39 : if (eErr == CE_None)
5901 : {
5902 39 : bOK = true;
5903 : }
5904 : else
5905 : {
5906 0 : bOK = false;
5907 0 : CPLError(CE_Failure, CPLE_AppDefined,
5908 : "Unable to get scanline %d", j);
5909 : }
5910 : }
5911 :
5912 : // Write data.
5913 59 : if (bOK)
5914 : {
5915 59 : status = nc_put_vara_double(cdfid, nVarLatID, start,
5916 : count, padLatVal);
5917 59 : NCDF_ERR(status);
5918 59 : status = nc_put_vara_double(cdfid, nVarLonID, start,
5919 : count, padLonVal);
5920 59 : NCDF_ERR(status);
5921 : }
5922 :
5923 59 : if (pfnProgress && (nRasterYSize / 10) > 0 &&
5924 59 : (j % (nRasterYSize / 10) == 0))
5925 : {
5926 23 : dfProgress += 0.08;
5927 23 : pfnProgress(dfProgress, nullptr, pProgressData);
5928 : }
5929 : }
5930 : }
5931 :
5932 24 : if (poLatLonSRS != nullptr)
5933 1 : delete poLatLonSRS;
5934 24 : if (poTransform != nullptr)
5935 1 : delete poTransform;
5936 :
5937 24 : CPLFree(padXVal);
5938 24 : CPLFree(padYVal);
5939 : } // Projected
5940 :
5941 : // If not projected/geographic and has geoloc
5942 55 : else if (!bIsGeographic && bHasGeoloc)
5943 : {
5944 : // Use
5945 : // https://cfconventions.org/Data/cf-conventions/cf-conventions-1.9/cf-conventions.html#_two_dimensional_latitude_longitude_coordinate_variables
5946 :
5947 4 : bool bOK = true;
5948 4 : double dfProgress = 0.2;
5949 :
5950 : // Make sure we are in data mode.
5951 4 : SetDefineMode(false);
5952 :
5953 : size_t startX[1];
5954 : size_t countX[1];
5955 : size_t startY[1];
5956 : size_t countY[1];
5957 4 : startX[0] = 0;
5958 4 : countX[0] = nRasterXSize;
5959 :
5960 4 : startY[0] = 0;
5961 4 : countY[0] = nRasterYSize;
5962 :
5963 8 : std::vector<double> adfXVal(nRasterXSize);
5964 16 : for (int i = 0; i < nRasterXSize; i++)
5965 12 : adfXVal[i] = i;
5966 :
5967 8 : std::vector<double> adfYVal(nRasterYSize);
5968 12 : for (int i = 0; i < nRasterYSize; i++)
5969 8 : adfYVal[i] = bBottomUp ? nRasterYSize - 1 - i : i;
5970 :
5971 4 : CPLDebug("GDAL_netCDF", "Writing X values");
5972 4 : int status = nc_put_vara_double(cdfid, nVarXID, startX, countX,
5973 4 : adfXVal.data());
5974 4 : NCDF_ERR(status);
5975 :
5976 4 : CPLDebug("GDAL_netCDF", "Writing Y values");
5977 4 : status = nc_put_vara_double(cdfid, nVarYID, startY, countY,
5978 4 : adfYVal.data());
5979 4 : NCDF_ERR(status);
5980 :
5981 4 : if (pfnProgress)
5982 0 : pfnProgress(0.20, nullptr, pProgressData);
5983 :
5984 4 : size_t start[] = {0, 0};
5985 4 : size_t count[] = {1, (size_t)nRasterXSize};
5986 : padLatVal =
5987 4 : static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
5988 : padLonVal =
5989 4 : static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
5990 :
5991 12 : for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR; j++)
5992 : {
5993 8 : start[0] = j;
5994 :
5995 8 : CPLErr eErr = GDALRasterIO(hBand_Y, GF_Read, 0,
5996 8 : bBottomUp ? nRasterYSize - 1 - j : j,
5997 : nRasterXSize, 1, padLatVal,
5998 : nRasterXSize, 1, GDT_Float64, 0, 0);
5999 8 : if (eErr == CE_None)
6000 : {
6001 8 : eErr = GDALRasterIO(hBand_X, GF_Read, 0,
6002 8 : bBottomUp ? nRasterYSize - 1 - j : j,
6003 : nRasterXSize, 1, padLonVal,
6004 : nRasterXSize, 1, GDT_Float64, 0, 0);
6005 : }
6006 :
6007 8 : if (eErr == CE_None)
6008 : {
6009 8 : bOK = true;
6010 : }
6011 : else
6012 : {
6013 0 : bOK = false;
6014 0 : CPLError(CE_Failure, CPLE_AppDefined,
6015 : "Unable to get scanline %d", j);
6016 : }
6017 :
6018 : // Write data.
6019 8 : if (bOK)
6020 : {
6021 8 : status = nc_put_vara_double(cdfid, nVarLatID, start, count,
6022 : padLatVal);
6023 8 : NCDF_ERR(status);
6024 8 : status = nc_put_vara_double(cdfid, nVarLonID, start, count,
6025 : padLonVal);
6026 8 : NCDF_ERR(status);
6027 : }
6028 :
6029 8 : if (pfnProgress && (nRasterYSize / 10) > 0 &&
6030 0 : (j % (nRasterYSize / 10) == 0))
6031 : {
6032 0 : dfProgress += 0.08;
6033 0 : pfnProgress(dfProgress, nullptr, pProgressData);
6034 : }
6035 4 : }
6036 : }
6037 :
6038 : // If not projected, assume geographic to catch grids without Datum.
6039 51 : else if (bWriteLonLat)
6040 : {
6041 : // Get latitude values.
6042 51 : const double dfY0 = (!bBottomUp) ? m_adfGeoTransform[3] :
6043 : // Invert latitude values.
6044 51 : m_adfGeoTransform[3] +
6045 51 : (m_adfGeoTransform[5] * nRasterYSize);
6046 51 : const double dfDY = m_adfGeoTransform[5];
6047 :
6048 : // Override lat values with the ones in GEOLOCATION/Y_VALUES.
6049 51 : if (netCDFDataset::GetMetadataItem("Y_VALUES", "GEOLOCATION") !=
6050 : nullptr)
6051 : {
6052 0 : int nTemp = 0;
6053 0 : padLatVal = Get1DGeolocation("Y_VALUES", nTemp);
6054 : // Make sure we got the correct amount, if not fallback to GT */
6055 : // could add test fabs(fabs(padLatVal[0]) - fabs(dfY0)) <= 0.1))
6056 0 : if (nTemp == nRasterYSize)
6057 : {
6058 0 : CPLDebug(
6059 : "GDAL_netCDF",
6060 : "Using Y_VALUES geolocation metadata for lat values");
6061 : }
6062 : else
6063 : {
6064 0 : CPLDebug("GDAL_netCDF",
6065 : "Got %d elements from Y_VALUES geolocation "
6066 : "metadata, need %d",
6067 : nTemp, nRasterYSize);
6068 0 : if (padLatVal)
6069 : {
6070 0 : CPLFree(padLatVal);
6071 0 : padLatVal = nullptr;
6072 : }
6073 : }
6074 : }
6075 :
6076 51 : if (padLatVal == nullptr)
6077 : {
6078 : padLatVal = static_cast<double *>(
6079 51 : CPLMalloc(nRasterYSize * sizeof(double)));
6080 3375 : for (int i = 0; i < nRasterYSize; i++)
6081 : {
6082 : // The data point is centered inside the pixel.
6083 3324 : if (!bBottomUp)
6084 0 : padLatVal[i] = dfY0 + (i + 0.5) * dfDY;
6085 : else // Invert latitude values.
6086 3324 : padLatVal[i] = dfY0 - (i + 0.5) * dfDY;
6087 : }
6088 : }
6089 :
6090 51 : size_t startLat[1] = {0};
6091 51 : size_t countLat[1] = {static_cast<size_t>(nRasterYSize)};
6092 :
6093 : // Get longitude values.
6094 51 : const double dfX0 = m_adfGeoTransform[0];
6095 51 : const double dfDX = m_adfGeoTransform[1];
6096 :
6097 : padLonVal =
6098 51 : static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
6099 3427 : for (int i = 0; i < nRasterXSize; i++)
6100 : {
6101 : // The data point is centered inside the pixel.
6102 3376 : padLonVal[i] = dfX0 + (i + 0.5) * dfDX;
6103 : }
6104 :
6105 51 : size_t startLon[1] = {0};
6106 51 : size_t countLon[1] = {static_cast<size_t>(nRasterXSize)};
6107 :
6108 : // Write latitude and longitude values.
6109 :
6110 : // Make sure we are in data mode.
6111 51 : SetDefineMode(false);
6112 :
6113 : // Write values.
6114 51 : CPLDebug("GDAL_netCDF", "Writing lat values");
6115 :
6116 51 : int status = nc_put_vara_double(cdfid, nVarLatID, startLat,
6117 : countLat, padLatVal);
6118 51 : NCDF_ERR(status);
6119 :
6120 51 : CPLDebug("GDAL_netCDF", "Writing lon values");
6121 51 : status = nc_put_vara_double(cdfid, nVarLonID, startLon, countLon,
6122 : padLonVal);
6123 51 : NCDF_ERR(status);
6124 :
6125 : } // Not projected.
6126 :
6127 79 : CPLFree(padLatVal);
6128 79 : CPLFree(padLonVal);
6129 :
6130 79 : if (pfnProgress)
6131 38 : pfnProgress(1.00, nullptr, pProgressData);
6132 : }
6133 :
6134 158 : if (hDS_X != nullptr)
6135 : {
6136 10 : GDALClose(hDS_X);
6137 : }
6138 158 : if (hDS_Y != nullptr)
6139 : {
6140 10 : GDALClose(hDS_Y);
6141 : }
6142 :
6143 158 : return CE_None;
6144 : }
6145 :
6146 : // Write Projection variable to band variable.
6147 : // Moved from AddProjectionVars() for cases when bands are added after
6148 : // projection.
6149 365 : bool netCDFDataset::AddGridMappingRef()
6150 : {
6151 365 : bool bRet = true;
6152 365 : bool bOldDefineMode = bDefineMode;
6153 :
6154 549 : if ((GetAccess() == GA_Update) && (nBands >= 1) && (GetRasterBand(1)) &&
6155 184 : ((pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, "")) ||
6156 178 : (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))))
6157 : {
6158 70 : bAddedGridMappingRef = true;
6159 :
6160 : // Make sure we are in define mode.
6161 70 : SetDefineMode(true);
6162 :
6163 186 : for (int i = 1; i <= nBands; i++)
6164 : {
6165 : const int nVarId =
6166 116 : static_cast<netCDFRasterBand *>(GetRasterBand(i))->nZId;
6167 :
6168 116 : if (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))
6169 : {
6170 : int status =
6171 224 : nc_put_att_text(cdfid, nVarId, CF_GRD_MAPPING,
6172 112 : strlen(pszCFProjection), pszCFProjection);
6173 112 : NCDF_ERR(status);
6174 112 : if (status != NC_NOERR)
6175 0 : bRet = false;
6176 : }
6177 116 : if (pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, ""))
6178 : {
6179 : int status =
6180 6 : nc_put_att_text(cdfid, nVarId, CF_COORDINATES,
6181 : strlen(pszCFCoordinates), pszCFCoordinates);
6182 6 : NCDF_ERR(status);
6183 6 : if (status != NC_NOERR)
6184 0 : bRet = false;
6185 : }
6186 : }
6187 :
6188 : // Go back to previous define mode.
6189 70 : SetDefineMode(bOldDefineMode);
6190 : }
6191 365 : return bRet;
6192 : }
6193 :
6194 : /************************************************************************/
6195 : /* GetGeoTransform() */
6196 : /************************************************************************/
6197 :
6198 107 : CPLErr netCDFDataset::GetGeoTransform(double *padfTransform)
6199 :
6200 : {
6201 107 : memcpy(padfTransform, m_adfGeoTransform, sizeof(double) * 6);
6202 107 : if (m_bHasGeoTransform)
6203 77 : return CE_None;
6204 :
6205 30 : return GDALPamDataset::GetGeoTransform(padfTransform);
6206 : }
6207 :
6208 : /************************************************************************/
6209 : /* rint() */
6210 : /************************************************************************/
6211 :
6212 0 : double netCDFDataset::rint(double dfX)
6213 : {
6214 0 : return std::round(dfX);
6215 : }
6216 :
6217 : /************************************************************************/
6218 : /* NCDFReadIsoMetadata() */
6219 : /************************************************************************/
6220 :
6221 16 : static void NCDFReadMetadataAsJson(int cdfid, CPLJSONObject &obj)
6222 : {
6223 16 : int nbAttr = 0;
6224 16 : NCDF_ERR(nc_inq_varnatts(cdfid, NC_GLOBAL, &nbAttr));
6225 :
6226 32 : std::map<std::string, CPLJSONArray> oMapNameToArray;
6227 40 : for (int l = 0; l < nbAttr; l++)
6228 : {
6229 : char szAttrName[NC_MAX_NAME + 1];
6230 24 : szAttrName[0] = 0;
6231 24 : NCDF_ERR(nc_inq_attname(cdfid, NC_GLOBAL, l, szAttrName));
6232 :
6233 24 : char *pszMetaValue = nullptr;
6234 24 : if (NCDFGetAttr(cdfid, NC_GLOBAL, szAttrName, &pszMetaValue) == CE_None)
6235 : {
6236 24 : nc_type nAttrType = NC_NAT;
6237 24 : size_t nAttrLen = 0;
6238 :
6239 24 : NCDF_ERR(nc_inq_att(cdfid, NC_GLOBAL, szAttrName, &nAttrType,
6240 : &nAttrLen));
6241 :
6242 24 : std::string osAttrName(szAttrName);
6243 24 : const auto sharpPos = osAttrName.find('#');
6244 24 : if (sharpPos == std::string::npos)
6245 : {
6246 16 : if (nAttrType == NC_DOUBLE || nAttrType == NC_FLOAT)
6247 4 : obj.Add(osAttrName, CPLAtof(pszMetaValue));
6248 : else
6249 12 : obj.Add(osAttrName, pszMetaValue);
6250 : }
6251 : else
6252 : {
6253 8 : osAttrName.resize(sharpPos);
6254 8 : auto iter = oMapNameToArray.find(osAttrName);
6255 8 : if (iter == oMapNameToArray.end())
6256 : {
6257 8 : CPLJSONArray array;
6258 4 : obj.Add(osAttrName, array);
6259 4 : oMapNameToArray[osAttrName] = array;
6260 4 : array.Add(pszMetaValue);
6261 : }
6262 : else
6263 : {
6264 4 : iter->second.Add(pszMetaValue);
6265 : }
6266 : }
6267 24 : CPLFree(pszMetaValue);
6268 24 : pszMetaValue = nullptr;
6269 : }
6270 : }
6271 :
6272 16 : int nSubGroups = 0;
6273 16 : int *panSubGroupIds = nullptr;
6274 16 : NCDFGetSubGroups(cdfid, &nSubGroups, &panSubGroupIds);
6275 16 : oMapNameToArray.clear();
6276 28 : for (int i = 0; i < nSubGroups; i++)
6277 : {
6278 24 : CPLJSONObject subObj;
6279 12 : NCDFReadMetadataAsJson(panSubGroupIds[i], subObj);
6280 :
6281 24 : std::string osGroupName;
6282 12 : osGroupName.resize(NC_MAX_NAME);
6283 12 : NCDF_ERR(nc_inq_grpname(panSubGroupIds[i], &osGroupName[0]));
6284 12 : osGroupName.resize(strlen(osGroupName.data()));
6285 12 : const auto sharpPos = osGroupName.find('#');
6286 12 : if (sharpPos == std::string::npos)
6287 : {
6288 4 : obj.Add(osGroupName, subObj);
6289 : }
6290 : else
6291 : {
6292 8 : osGroupName.resize(sharpPos);
6293 8 : auto iter = oMapNameToArray.find(osGroupName);
6294 8 : if (iter == oMapNameToArray.end())
6295 : {
6296 8 : CPLJSONArray array;
6297 4 : obj.Add(osGroupName, array);
6298 4 : oMapNameToArray[osGroupName] = array;
6299 4 : array.Add(subObj);
6300 : }
6301 : else
6302 : {
6303 4 : iter->second.Add(subObj);
6304 : }
6305 : }
6306 : }
6307 16 : CPLFree(panSubGroupIds);
6308 16 : }
6309 :
6310 4 : std::string NCDFReadMetadataAsJson(int cdfid)
6311 : {
6312 8 : CPLJSONDocument oDoc;
6313 8 : CPLJSONObject oRoot = oDoc.GetRoot();
6314 4 : NCDFReadMetadataAsJson(cdfid, oRoot);
6315 8 : return oDoc.SaveAsString();
6316 : }
6317 :
6318 : /************************************************************************/
6319 : /* ReadAttributes() */
6320 : /************************************************************************/
6321 1726 : CPLErr netCDFDataset::ReadAttributes(int cdfidIn, int var)
6322 :
6323 : {
6324 1726 : char *pszVarFullName = nullptr;
6325 1726 : ERR_RET(NCDFGetVarFullName(cdfidIn, var, &pszVarFullName));
6326 :
6327 : // For metadata in Sentinel 5
6328 1726 : if (STARTS_WITH(pszVarFullName, "/METADATA/"))
6329 : {
6330 6 : for (const char *key :
6331 : {"ISO_METADATA", "ESA_METADATA", "EOP_METADATA", "QA_STATISTICS",
6332 8 : "GRANULE_DESCRIPTION", "ALGORITHM_SETTINGS"})
6333 : {
6334 14 : if (var == NC_GLOBAL &&
6335 7 : strcmp(pszVarFullName,
6336 : CPLSPrintf("/METADATA/%s/NC_GLOBAL", key)) == 0)
6337 : {
6338 1 : CPLFree(pszVarFullName);
6339 1 : CPLStringList aosList;
6340 2 : aosList.AddString(CPLString(NCDFReadMetadataAsJson(cdfidIn))
6341 1 : .replaceAll("\\/", '/'));
6342 1 : m_oMapDomainToJSon[key] = std::move(aosList);
6343 1 : return CE_None;
6344 : }
6345 : }
6346 : }
6347 1725 : if (STARTS_WITH(pszVarFullName, "/PRODUCT/SUPPORT_DATA/"))
6348 : {
6349 0 : CPLFree(pszVarFullName);
6350 0 : CPLStringList aosList;
6351 : aosList.AddString(
6352 0 : CPLString(NCDFReadMetadataAsJson(cdfidIn)).replaceAll("\\/", '/'));
6353 0 : m_oMapDomainToJSon["SUPPORT_DATA"] = std::move(aosList);
6354 0 : return CE_None;
6355 : }
6356 :
6357 1725 : size_t nMetaNameSize =
6358 1725 : sizeof(char) * (strlen(pszVarFullName) + 1 + NC_MAX_NAME + 1);
6359 1725 : char *pszMetaName = static_cast<char *>(CPLMalloc(nMetaNameSize));
6360 :
6361 1725 : int nbAttr = 0;
6362 1725 : NCDF_ERR(nc_inq_varnatts(cdfidIn, var, &nbAttr));
6363 :
6364 8635 : for (int l = 0; l < nbAttr; l++)
6365 : {
6366 : char szAttrName[NC_MAX_NAME + 1];
6367 6910 : szAttrName[0] = 0;
6368 6910 : NCDF_ERR(nc_inq_attname(cdfidIn, var, l, szAttrName));
6369 6910 : snprintf(pszMetaName, nMetaNameSize, "%s#%s", pszVarFullName,
6370 : szAttrName);
6371 :
6372 6910 : char *pszMetaTemp = nullptr;
6373 6910 : if (NCDFGetAttr(cdfidIn, var, szAttrName, &pszMetaTemp) == CE_None)
6374 : {
6375 6909 : papszMetadata =
6376 6909 : CSLSetNameValue(papszMetadata, pszMetaName, pszMetaTemp);
6377 6909 : CPLFree(pszMetaTemp);
6378 6909 : pszMetaTemp = nullptr;
6379 : }
6380 : else
6381 : {
6382 1 : CPLDebug("GDAL_netCDF", "invalid metadata %s", pszMetaName);
6383 : }
6384 : }
6385 :
6386 1725 : CPLFree(pszVarFullName);
6387 1725 : CPLFree(pszMetaName);
6388 :
6389 1725 : if (var == NC_GLOBAL)
6390 : {
6391 : // Recurse on sub-groups.
6392 509 : int nSubGroups = 0;
6393 509 : int *panSubGroupIds = nullptr;
6394 509 : NCDFGetSubGroups(cdfidIn, &nSubGroups, &panSubGroupIds);
6395 538 : for (int i = 0; i < nSubGroups; i++)
6396 : {
6397 29 : ReadAttributes(panSubGroupIds[i], var);
6398 : }
6399 509 : CPLFree(panSubGroupIds);
6400 : }
6401 :
6402 1725 : return CE_None;
6403 : }
6404 :
6405 : /************************************************************************/
6406 : /* netCDFDataset::CreateSubDatasetList() */
6407 : /************************************************************************/
6408 50 : void netCDFDataset::CreateSubDatasetList(int nGroupId)
6409 : {
6410 : char szVarStdName[NC_MAX_NAME + 1];
6411 50 : int *ponDimIds = nullptr;
6412 : nc_type nAttype;
6413 : size_t nAttlen;
6414 :
6415 50 : netCDFDataset *poDS = this;
6416 :
6417 : int nVarCount;
6418 50 : nc_inq_nvars(nGroupId, &nVarCount);
6419 :
6420 324 : for (int nVar = 0; nVar < nVarCount; nVar++)
6421 : {
6422 :
6423 : int nDims;
6424 274 : nc_inq_varndims(nGroupId, nVar, &nDims);
6425 :
6426 274 : if (nDims >= 2)
6427 : {
6428 155 : ponDimIds = static_cast<int *>(CPLCalloc(nDims, sizeof(int)));
6429 155 : nc_inq_vardimid(nGroupId, nVar, ponDimIds);
6430 :
6431 : // Create Sub dataset list.
6432 155 : CPLString osDim;
6433 480 : for (int i = 0; i < nDims; i++)
6434 : {
6435 : size_t nDimLen;
6436 325 : nc_inq_dimlen(nGroupId, ponDimIds[i], &nDimLen);
6437 325 : osDim += CPLSPrintf("%dx", (int)nDimLen);
6438 : }
6439 155 : CPLFree(ponDimIds);
6440 :
6441 : nc_type nVarType;
6442 155 : nc_inq_vartype(nGroupId, nVar, &nVarType);
6443 : // Get rid of the last "x" character.
6444 155 : osDim.pop_back();
6445 155 : const char *pszType = "";
6446 155 : switch (nVarType)
6447 : {
6448 37 : case NC_BYTE:
6449 37 : pszType = "8-bit integer";
6450 37 : break;
6451 2 : case NC_CHAR:
6452 2 : pszType = "8-bit character";
6453 2 : break;
6454 6 : case NC_SHORT:
6455 6 : pszType = "16-bit integer";
6456 6 : break;
6457 10 : case NC_INT:
6458 10 : pszType = "32-bit integer";
6459 10 : break;
6460 51 : case NC_FLOAT:
6461 51 : pszType = "32-bit floating-point";
6462 51 : break;
6463 31 : case NC_DOUBLE:
6464 31 : pszType = "64-bit floating-point";
6465 31 : break;
6466 4 : case NC_UBYTE:
6467 4 : pszType = "8-bit unsigned integer";
6468 4 : break;
6469 1 : case NC_USHORT:
6470 1 : pszType = "16-bit unsigned integer";
6471 1 : break;
6472 1 : case NC_UINT:
6473 1 : pszType = "32-bit unsigned integer";
6474 1 : break;
6475 1 : case NC_INT64:
6476 1 : pszType = "64-bit integer";
6477 1 : break;
6478 1 : case NC_UINT64:
6479 1 : pszType = "64-bit unsigned integer";
6480 1 : break;
6481 10 : default:
6482 10 : break;
6483 : }
6484 :
6485 155 : char *pszName = nullptr;
6486 155 : if (NCDFGetVarFullName(nGroupId, nVar, &pszName) != CE_None)
6487 0 : continue;
6488 :
6489 155 : nSubDatasets++;
6490 :
6491 155 : nAttlen = 0;
6492 155 : nc_inq_att(nGroupId, nVar, CF_STD_NAME, &nAttype, &nAttlen);
6493 310 : if (nAttlen < sizeof(szVarStdName) &&
6494 155 : nc_get_att_text(nGroupId, nVar, CF_STD_NAME, szVarStdName) ==
6495 : NC_NOERR)
6496 : {
6497 51 : szVarStdName[nAttlen] = '\0';
6498 : }
6499 : else
6500 : {
6501 104 : snprintf(szVarStdName, sizeof(szVarStdName), "%s", pszName);
6502 : }
6503 :
6504 : char szTemp[NC_MAX_NAME + 1];
6505 155 : snprintf(szTemp, sizeof(szTemp), "SUBDATASET_%d_NAME",
6506 : nSubDatasets);
6507 :
6508 155 : if (strchr(pszName, ' ') || strchr(pszName, ':'))
6509 : {
6510 1 : poDS->papszSubDatasets = CSLSetNameValue(
6511 : poDS->papszSubDatasets, szTemp,
6512 : CPLSPrintf("NETCDF:\"%s\":\"%s\"", poDS->osFilename.c_str(),
6513 : pszName));
6514 : }
6515 : else
6516 : {
6517 154 : poDS->papszSubDatasets = CSLSetNameValue(
6518 : poDS->papszSubDatasets, szTemp,
6519 : CPLSPrintf("NETCDF:\"%s\":%s", poDS->osFilename.c_str(),
6520 : pszName));
6521 : }
6522 :
6523 155 : CPLFree(pszName);
6524 :
6525 155 : snprintf(szTemp, sizeof(szTemp), "SUBDATASET_%d_DESC",
6526 : nSubDatasets);
6527 :
6528 155 : poDS->papszSubDatasets =
6529 155 : CSLSetNameValue(poDS->papszSubDatasets, szTemp,
6530 : CPLSPrintf("[%s] %s (%s)", osDim.c_str(),
6531 : szVarStdName, pszType));
6532 : }
6533 : }
6534 :
6535 : // Recurse on sub groups.
6536 50 : int nSubGroups = 0;
6537 50 : int *panSubGroupIds = nullptr;
6538 50 : NCDFGetSubGroups(nGroupId, &nSubGroups, &panSubGroupIds);
6539 55 : for (int i = 0; i < nSubGroups; i++)
6540 : {
6541 5 : CreateSubDatasetList(panSubGroupIds[i]);
6542 : }
6543 50 : CPLFree(panSubGroupIds);
6544 50 : }
6545 :
6546 : /************************************************************************/
6547 : /* TestCapability() */
6548 : /************************************************************************/
6549 :
6550 248 : int netCDFDataset::TestCapability(const char *pszCap)
6551 : {
6552 248 : if (EQUAL(pszCap, ODsCCreateLayer))
6553 : {
6554 223 : return eAccess == GA_Update && nBands == 0 &&
6555 218 : (eMultipleLayerBehavior != SINGLE_LAYER ||
6556 229 : this->GetLayerCount() == 0 || bSGSupport);
6557 : }
6558 136 : else if (EQUAL(pszCap, ODsCZGeometries))
6559 2 : return true;
6560 :
6561 134 : return false;
6562 : }
6563 :
6564 : /************************************************************************/
6565 : /* GetLayer() */
6566 : /************************************************************************/
6567 :
6568 384 : OGRLayer *netCDFDataset::GetLayer(int nIdx)
6569 : {
6570 384 : if (nIdx < 0 || nIdx >= this->GetLayerCount())
6571 2 : return nullptr;
6572 382 : return papoLayers[nIdx].get();
6573 : }
6574 :
6575 : /************************************************************************/
6576 : /* ICreateLayer() */
6577 : /************************************************************************/
6578 :
6579 59 : OGRLayer *netCDFDataset::ICreateLayer(const char *pszName,
6580 : const OGRGeomFieldDefn *poGeomFieldDefn,
6581 : CSLConstList papszOptions)
6582 : {
6583 59 : int nLayerCDFId = cdfid;
6584 59 : if (!TestCapability(ODsCCreateLayer))
6585 0 : return nullptr;
6586 :
6587 59 : const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
6588 : const auto poSpatialRef =
6589 59 : poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
6590 :
6591 118 : CPLString osNetCDFLayerName(pszName);
6592 59 : const netCDFWriterConfigLayer *poLayerConfig = nullptr;
6593 59 : if (oWriterConfig.m_bIsValid)
6594 : {
6595 : std::map<CPLString, netCDFWriterConfigLayer>::const_iterator
6596 2 : oLayerIter = oWriterConfig.m_oLayers.find(pszName);
6597 2 : if (oLayerIter != oWriterConfig.m_oLayers.end())
6598 : {
6599 1 : poLayerConfig = &(oLayerIter->second);
6600 1 : osNetCDFLayerName = poLayerConfig->m_osNetCDFName;
6601 : }
6602 : }
6603 :
6604 59 : netCDFDataset *poLayerDataset = nullptr;
6605 59 : if (eMultipleLayerBehavior == SEPARATE_FILES)
6606 : {
6607 2 : char **papszDatasetOptions = nullptr;
6608 2 : papszDatasetOptions = CSLSetNameValue(
6609 : papszDatasetOptions, "CONFIG_FILE",
6610 2 : CSLFetchNameValue(papszCreationOptions, "CONFIG_FILE"));
6611 : papszDatasetOptions =
6612 2 : CSLSetNameValue(papszDatasetOptions, "FORMAT",
6613 2 : CSLFetchNameValue(papszCreationOptions, "FORMAT"));
6614 2 : papszDatasetOptions = CSLSetNameValue(
6615 : papszDatasetOptions, "WRITE_GDAL_TAGS",
6616 2 : CSLFetchNameValue(papszCreationOptions, "WRITE_GDAL_TAGS"));
6617 : CPLString osLayerFilename(
6618 2 : CPLFormFilename(osFilename, osNetCDFLayerName, "nc"));
6619 2 : CPLAcquireMutex(hNCMutex, 1000.0);
6620 2 : poLayerDataset =
6621 2 : CreateLL(osLayerFilename, 0, 0, 0, papszDatasetOptions);
6622 2 : CPLReleaseMutex(hNCMutex);
6623 2 : CSLDestroy(papszDatasetOptions);
6624 2 : if (poLayerDataset == nullptr)
6625 0 : return nullptr;
6626 :
6627 2 : nLayerCDFId = poLayerDataset->cdfid;
6628 2 : NCDFAddGDALHistory(nLayerCDFId, osLayerFilename, bWriteGDALVersion,
6629 2 : bWriteGDALHistory, "", "Create",
6630 : NCDF_CONVENTIONS_CF_V1_6);
6631 : }
6632 57 : else if (eMultipleLayerBehavior == SEPARATE_GROUPS)
6633 : {
6634 2 : SetDefineMode(true);
6635 :
6636 2 : nLayerCDFId = -1;
6637 2 : int status = nc_def_grp(cdfid, osNetCDFLayerName, &nLayerCDFId);
6638 2 : NCDF_ERR(status);
6639 2 : if (status != NC_NOERR)
6640 0 : return nullptr;
6641 :
6642 2 : NCDFAddGDALHistory(nLayerCDFId, osFilename, bWriteGDALVersion,
6643 2 : bWriteGDALHistory, "", "Create",
6644 : NCDF_CONVENTIONS_CF_V1_6);
6645 : }
6646 :
6647 : // Make a clone to workaround a bug in released MapServer versions
6648 : // that destroys the passed SRS instead of releasing it .
6649 59 : OGRSpatialReference *poSRS = nullptr;
6650 59 : if (poSpatialRef)
6651 : {
6652 43 : poSRS = poSpatialRef->Clone();
6653 43 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
6654 : }
6655 : std::shared_ptr<netCDFLayer> poLayer(
6656 59 : new netCDFLayer(poLayerDataset ? poLayerDataset : this, nLayerCDFId,
6657 118 : osNetCDFLayerName, eGType, poSRS));
6658 59 : if (poSRS != nullptr)
6659 43 : poSRS->Release();
6660 :
6661 : // Fetch layer creation options coming from config file
6662 59 : char **papszNewOptions = CSLDuplicate(papszOptions);
6663 59 : if (oWriterConfig.m_bIsValid)
6664 : {
6665 2 : std::map<CPLString, CPLString>::const_iterator oIter;
6666 3 : for (oIter = oWriterConfig.m_oLayerCreationOptions.begin();
6667 3 : oIter != oWriterConfig.m_oLayerCreationOptions.end(); ++oIter)
6668 : {
6669 : papszNewOptions =
6670 1 : CSLSetNameValue(papszNewOptions, oIter->first, oIter->second);
6671 : }
6672 2 : if (poLayerConfig != nullptr)
6673 : {
6674 3 : for (oIter = poLayerConfig->m_oLayerCreationOptions.begin();
6675 3 : oIter != poLayerConfig->m_oLayerCreationOptions.end(); ++oIter)
6676 : {
6677 2 : papszNewOptions = CSLSetNameValue(papszNewOptions, oIter->first,
6678 2 : oIter->second);
6679 : }
6680 : }
6681 : }
6682 :
6683 59 : const bool bRet = poLayer->Create(papszNewOptions, poLayerConfig);
6684 59 : CSLDestroy(papszNewOptions);
6685 :
6686 59 : if (!bRet)
6687 : {
6688 0 : return nullptr;
6689 : }
6690 :
6691 59 : if (poLayerDataset != nullptr)
6692 2 : apoVectorDatasets.push_back(poLayerDataset);
6693 :
6694 59 : papoLayers.push_back(poLayer);
6695 59 : return poLayer.get();
6696 : }
6697 :
6698 : /************************************************************************/
6699 : /* CloneAttributes() */
6700 : /************************************************************************/
6701 :
6702 137 : bool netCDFDataset::CloneAttributes(int old_cdfid, int new_cdfid, int nSrcVarId,
6703 : int nDstVarId)
6704 : {
6705 137 : int nAttCount = -1;
6706 137 : int status = nc_inq_varnatts(old_cdfid, nSrcVarId, &nAttCount);
6707 137 : NCDF_ERR(status);
6708 :
6709 693 : for (int i = 0; i < nAttCount; i++)
6710 : {
6711 : char szName[NC_MAX_NAME + 1];
6712 556 : szName[0] = 0;
6713 556 : status = nc_inq_attname(old_cdfid, nSrcVarId, i, szName);
6714 556 : NCDF_ERR(status);
6715 :
6716 : status =
6717 556 : nc_copy_att(old_cdfid, nSrcVarId, szName, new_cdfid, nDstVarId);
6718 556 : NCDF_ERR(status);
6719 556 : if (status != NC_NOERR)
6720 0 : return false;
6721 : }
6722 :
6723 137 : return true;
6724 : }
6725 :
6726 : /************************************************************************/
6727 : /* CloneVariableContent() */
6728 : /************************************************************************/
6729 :
6730 121 : bool netCDFDataset::CloneVariableContent(int old_cdfid, int new_cdfid,
6731 : int nSrcVarId, int nDstVarId)
6732 : {
6733 121 : int nVarDimCount = -1;
6734 121 : int status = nc_inq_varndims(old_cdfid, nSrcVarId, &nVarDimCount);
6735 121 : NCDF_ERR(status);
6736 121 : int anDimIds[] = {-1, 1};
6737 121 : status = nc_inq_vardimid(old_cdfid, nSrcVarId, anDimIds);
6738 121 : NCDF_ERR(status);
6739 121 : nc_type nc_datatype = NC_NAT;
6740 121 : status = nc_inq_vartype(old_cdfid, nSrcVarId, &nc_datatype);
6741 121 : NCDF_ERR(status);
6742 121 : size_t nTypeSize = 0;
6743 121 : switch (nc_datatype)
6744 : {
6745 35 : case NC_BYTE:
6746 : case NC_CHAR:
6747 35 : nTypeSize = 1;
6748 35 : break;
6749 4 : case NC_SHORT:
6750 4 : nTypeSize = 2;
6751 4 : break;
6752 24 : case NC_INT:
6753 24 : nTypeSize = 4;
6754 24 : break;
6755 4 : case NC_FLOAT:
6756 4 : nTypeSize = 4;
6757 4 : break;
6758 43 : case NC_DOUBLE:
6759 43 : nTypeSize = 8;
6760 43 : break;
6761 2 : case NC_UBYTE:
6762 2 : nTypeSize = 1;
6763 2 : break;
6764 2 : case NC_USHORT:
6765 2 : nTypeSize = 2;
6766 2 : break;
6767 2 : case NC_UINT:
6768 2 : nTypeSize = 4;
6769 2 : break;
6770 4 : case NC_INT64:
6771 : case NC_UINT64:
6772 4 : nTypeSize = 8;
6773 4 : break;
6774 1 : case NC_STRING:
6775 1 : nTypeSize = sizeof(char *);
6776 1 : break;
6777 0 : default:
6778 : {
6779 0 : CPLError(CE_Failure, CPLE_NotSupported, "Unsupported data type: %d",
6780 : nc_datatype);
6781 0 : return false;
6782 : }
6783 : }
6784 :
6785 121 : size_t nElems = 1;
6786 : size_t anStart[NC_MAX_DIMS];
6787 : size_t anCount[NC_MAX_DIMS];
6788 121 : size_t nRecords = 1;
6789 261 : for (int i = 0; i < nVarDimCount; i++)
6790 : {
6791 140 : anStart[i] = 0;
6792 140 : if (i == 0)
6793 : {
6794 116 : anCount[i] = 1;
6795 116 : status = nc_inq_dimlen(old_cdfid, anDimIds[i], &nRecords);
6796 116 : NCDF_ERR(status);
6797 : }
6798 : else
6799 : {
6800 24 : anCount[i] = 0;
6801 24 : status = nc_inq_dimlen(old_cdfid, anDimIds[i], &anCount[i]);
6802 24 : NCDF_ERR(status);
6803 24 : nElems *= anCount[i];
6804 : }
6805 : }
6806 :
6807 : /* Workaround in some cases a netCDF bug:
6808 : * https://github.com/Unidata/netcdf-c/pull/1442 */
6809 121 : if (nRecords > 0 && nRecords < 10 * 1000 * 1000 / (nElems * nTypeSize))
6810 : {
6811 119 : nElems *= nRecords;
6812 119 : anCount[0] = nRecords;
6813 119 : nRecords = 1;
6814 : }
6815 :
6816 121 : void *pBuffer = VSI_MALLOC2_VERBOSE(nElems, nTypeSize);
6817 121 : if (pBuffer == nullptr)
6818 0 : return false;
6819 :
6820 240 : for (size_t iRecord = 0; iRecord < nRecords; iRecord++)
6821 : {
6822 119 : anStart[0] = iRecord;
6823 :
6824 119 : switch (nc_datatype)
6825 : {
6826 5 : case NC_BYTE:
6827 : status =
6828 5 : nc_get_vara_schar(old_cdfid, nSrcVarId, anStart, anCount,
6829 : static_cast<signed char *>(pBuffer));
6830 5 : if (!status)
6831 5 : status = nc_put_vara_schar(
6832 : new_cdfid, nDstVarId, anStart, anCount,
6833 : static_cast<signed char *>(pBuffer));
6834 5 : break;
6835 28 : case NC_CHAR:
6836 : status =
6837 28 : nc_get_vara_text(old_cdfid, nSrcVarId, anStart, anCount,
6838 : static_cast<char *>(pBuffer));
6839 28 : if (!status)
6840 : status =
6841 28 : nc_put_vara_text(new_cdfid, nDstVarId, anStart, anCount,
6842 : static_cast<char *>(pBuffer));
6843 28 : break;
6844 4 : case NC_SHORT:
6845 : status =
6846 4 : nc_get_vara_short(old_cdfid, nSrcVarId, anStart, anCount,
6847 : static_cast<short *>(pBuffer));
6848 4 : if (!status)
6849 4 : status = nc_put_vara_short(new_cdfid, nDstVarId, anStart,
6850 : anCount,
6851 : static_cast<short *>(pBuffer));
6852 4 : break;
6853 24 : case NC_INT:
6854 24 : status = nc_get_vara_int(old_cdfid, nSrcVarId, anStart, anCount,
6855 : static_cast<int *>(pBuffer));
6856 24 : if (!status)
6857 : status =
6858 24 : nc_put_vara_int(new_cdfid, nDstVarId, anStart, anCount,
6859 : static_cast<int *>(pBuffer));
6860 24 : break;
6861 4 : case NC_FLOAT:
6862 : status =
6863 4 : nc_get_vara_float(old_cdfid, nSrcVarId, anStart, anCount,
6864 : static_cast<float *>(pBuffer));
6865 4 : if (!status)
6866 4 : status = nc_put_vara_float(new_cdfid, nDstVarId, anStart,
6867 : anCount,
6868 : static_cast<float *>(pBuffer));
6869 4 : break;
6870 43 : case NC_DOUBLE:
6871 : status =
6872 43 : nc_get_vara_double(old_cdfid, nSrcVarId, anStart, anCount,
6873 : static_cast<double *>(pBuffer));
6874 43 : if (!status)
6875 43 : status = nc_put_vara_double(new_cdfid, nDstVarId, anStart,
6876 : anCount,
6877 : static_cast<double *>(pBuffer));
6878 43 : break;
6879 1 : case NC_STRING:
6880 : status =
6881 1 : nc_get_vara_string(old_cdfid, nSrcVarId, anStart, anCount,
6882 : static_cast<char **>(pBuffer));
6883 1 : if (!status)
6884 : {
6885 1 : status = nc_put_vara_string(
6886 : new_cdfid, nDstVarId, anStart, anCount,
6887 : static_cast<const char **>(pBuffer));
6888 1 : nc_free_string(nElems, static_cast<char **>(pBuffer));
6889 : }
6890 1 : break;
6891 :
6892 2 : case NC_UBYTE:
6893 : status =
6894 2 : nc_get_vara_uchar(old_cdfid, nSrcVarId, anStart, anCount,
6895 : static_cast<unsigned char *>(pBuffer));
6896 2 : if (!status)
6897 2 : status = nc_put_vara_uchar(
6898 : new_cdfid, nDstVarId, anStart, anCount,
6899 : static_cast<unsigned char *>(pBuffer));
6900 2 : break;
6901 2 : case NC_USHORT:
6902 : status =
6903 2 : nc_get_vara_ushort(old_cdfid, nSrcVarId, anStart, anCount,
6904 : static_cast<unsigned short *>(pBuffer));
6905 2 : if (!status)
6906 2 : status = nc_put_vara_ushort(
6907 : new_cdfid, nDstVarId, anStart, anCount,
6908 : static_cast<unsigned short *>(pBuffer));
6909 2 : break;
6910 2 : case NC_UINT:
6911 : status =
6912 2 : nc_get_vara_uint(old_cdfid, nSrcVarId, anStart, anCount,
6913 : static_cast<unsigned int *>(pBuffer));
6914 2 : if (!status)
6915 : status =
6916 2 : nc_put_vara_uint(new_cdfid, nDstVarId, anStart, anCount,
6917 : static_cast<unsigned int *>(pBuffer));
6918 2 : break;
6919 2 : case NC_INT64:
6920 : status =
6921 2 : nc_get_vara_longlong(old_cdfid, nSrcVarId, anStart, anCount,
6922 : static_cast<long long *>(pBuffer));
6923 2 : if (!status)
6924 2 : status = nc_put_vara_longlong(
6925 : new_cdfid, nDstVarId, anStart, anCount,
6926 : static_cast<long long *>(pBuffer));
6927 2 : break;
6928 2 : case NC_UINT64:
6929 2 : status = nc_get_vara_ulonglong(
6930 : old_cdfid, nSrcVarId, anStart, anCount,
6931 : static_cast<unsigned long long *>(pBuffer));
6932 2 : if (!status)
6933 2 : status = nc_put_vara_ulonglong(
6934 : new_cdfid, nDstVarId, anStart, anCount,
6935 : static_cast<unsigned long long *>(pBuffer));
6936 2 : break;
6937 0 : default:
6938 0 : status = NC_EBADTYPE;
6939 : }
6940 :
6941 119 : NCDF_ERR(status);
6942 119 : if (status != NC_NOERR)
6943 : {
6944 0 : VSIFree(pBuffer);
6945 0 : return false;
6946 : }
6947 : }
6948 :
6949 121 : VSIFree(pBuffer);
6950 121 : return true;
6951 : }
6952 :
6953 : /************************************************************************/
6954 : /* NCDFIsUnlimitedDim() */
6955 : /************************************************************************/
6956 :
6957 57 : bool NCDFIsUnlimitedDim(bool bIsNC4, int cdfid, int nDimId)
6958 : {
6959 57 : if (bIsNC4)
6960 : {
6961 16 : int nUnlimitedDims = 0;
6962 16 : nc_inq_unlimdims(cdfid, &nUnlimitedDims, nullptr);
6963 16 : bool bFound = false;
6964 16 : if (nUnlimitedDims)
6965 : {
6966 : int *panUnlimitedDimIds =
6967 16 : static_cast<int *>(CPLMalloc(sizeof(int) * nUnlimitedDims));
6968 16 : nc_inq_unlimdims(cdfid, nullptr, panUnlimitedDimIds);
6969 30 : for (int i = 0; i < nUnlimitedDims; i++)
6970 : {
6971 22 : if (panUnlimitedDimIds[i] == nDimId)
6972 : {
6973 8 : bFound = true;
6974 8 : break;
6975 : }
6976 : }
6977 16 : CPLFree(panUnlimitedDimIds);
6978 : }
6979 16 : return bFound;
6980 : }
6981 : else
6982 : {
6983 41 : int nUnlimitedDimId = -1;
6984 41 : nc_inq(cdfid, nullptr, nullptr, nullptr, &nUnlimitedDimId);
6985 41 : return nDimId == nUnlimitedDimId;
6986 : }
6987 : }
6988 :
6989 : /************************************************************************/
6990 : /* CloneGrp() */
6991 : /************************************************************************/
6992 :
6993 16 : bool netCDFDataset::CloneGrp(int nOldGrpId, int nNewGrpId, bool bIsNC4,
6994 : int nLayerId, int nDimIdToGrow, size_t nNewSize)
6995 : {
6996 : // Clone dimensions
6997 16 : int nDimCount = -1;
6998 16 : int status = nc_inq_ndims(nOldGrpId, &nDimCount);
6999 16 : NCDF_ERR(status);
7000 16 : if (nDimCount < 0 || nDimCount > NC_MAX_DIMS)
7001 0 : return false;
7002 : int anDimIds[NC_MAX_DIMS];
7003 16 : int nUnlimiDimID = -1;
7004 16 : status = nc_inq_unlimdim(nOldGrpId, &nUnlimiDimID);
7005 16 : NCDF_ERR(status);
7006 16 : if (bIsNC4)
7007 : {
7008 : // In NC4, the dimension ids of a group are not necessarily in
7009 : // [0,nDimCount-1] range
7010 8 : int nDimCount2 = -1;
7011 8 : status = nc_inq_dimids(nOldGrpId, &nDimCount2, anDimIds, FALSE);
7012 8 : NCDF_ERR(status);
7013 8 : CPLAssert(nDimCount == nDimCount2);
7014 : }
7015 : else
7016 : {
7017 36 : for (int i = 0; i < nDimCount; i++)
7018 28 : anDimIds[i] = i;
7019 : }
7020 60 : for (int i = 0; i < nDimCount; i++)
7021 : {
7022 : char szDimName[NC_MAX_NAME + 1];
7023 44 : szDimName[0] = 0;
7024 44 : size_t nLen = 0;
7025 44 : const int nDimId = anDimIds[i];
7026 44 : status = nc_inq_dim(nOldGrpId, nDimId, szDimName, &nLen);
7027 44 : NCDF_ERR(status);
7028 44 : if (NCDFIsUnlimitedDim(bIsNC4, nOldGrpId, nDimId))
7029 16 : nLen = NC_UNLIMITED;
7030 28 : else if (nDimId == nDimIdToGrow && nOldGrpId == nLayerId)
7031 13 : nLen = nNewSize;
7032 44 : int nNewDimId = -1;
7033 44 : status = nc_def_dim(nNewGrpId, szDimName, nLen, &nNewDimId);
7034 44 : NCDF_ERR(status);
7035 44 : CPLAssert(nDimId == nNewDimId);
7036 44 : if (status != NC_NOERR)
7037 : {
7038 0 : return false;
7039 : }
7040 : }
7041 :
7042 : // Clone main attributes
7043 16 : if (!CloneAttributes(nOldGrpId, nNewGrpId, NC_GLOBAL, NC_GLOBAL))
7044 : {
7045 0 : return false;
7046 : }
7047 :
7048 : // Clone variable definitions
7049 16 : int nVarCount = -1;
7050 16 : status = nc_inq_nvars(nOldGrpId, &nVarCount);
7051 16 : NCDF_ERR(status);
7052 :
7053 137 : for (int i = 0; i < nVarCount; i++)
7054 : {
7055 : char szVarName[NC_MAX_NAME + 1];
7056 121 : szVarName[0] = 0;
7057 121 : status = nc_inq_varname(nOldGrpId, i, szVarName);
7058 121 : NCDF_ERR(status);
7059 121 : nc_type nc_datatype = NC_NAT;
7060 121 : status = nc_inq_vartype(nOldGrpId, i, &nc_datatype);
7061 121 : NCDF_ERR(status);
7062 121 : int nVarDimCount = -1;
7063 121 : status = nc_inq_varndims(nOldGrpId, i, &nVarDimCount);
7064 121 : NCDF_ERR(status);
7065 121 : status = nc_inq_vardimid(nOldGrpId, i, anDimIds);
7066 121 : NCDF_ERR(status);
7067 121 : int nNewVarId = -1;
7068 121 : status = nc_def_var(nNewGrpId, szVarName, nc_datatype, nVarDimCount,
7069 : anDimIds, &nNewVarId);
7070 121 : NCDF_ERR(status);
7071 121 : CPLAssert(i == nNewVarId);
7072 121 : if (status != NC_NOERR)
7073 : {
7074 0 : return false;
7075 : }
7076 :
7077 121 : if (!CloneAttributes(nOldGrpId, nNewGrpId, i, i))
7078 : {
7079 0 : return false;
7080 : }
7081 : }
7082 :
7083 16 : status = nc_enddef(nNewGrpId);
7084 16 : NCDF_ERR(status);
7085 16 : if (status != NC_NOERR)
7086 : {
7087 0 : return false;
7088 : }
7089 :
7090 : // Clone variable content
7091 137 : for (int i = 0; i < nVarCount; i++)
7092 : {
7093 121 : if (!CloneVariableContent(nOldGrpId, nNewGrpId, i, i))
7094 : {
7095 0 : return false;
7096 : }
7097 : }
7098 :
7099 16 : return true;
7100 : }
7101 :
7102 : /************************************************************************/
7103 : /* GrowDim() */
7104 : /************************************************************************/
7105 :
7106 13 : bool netCDFDataset::GrowDim(int nLayerId, int nDimIdToGrow, size_t nNewSize)
7107 : {
7108 : int nCreationMode;
7109 : // Set nCreationMode based on eFormat.
7110 13 : switch (eFormat)
7111 : {
7112 : #ifdef NETCDF_HAS_NC2
7113 0 : case NCDF_FORMAT_NC2:
7114 0 : nCreationMode = NC_CLOBBER | NC_64BIT_OFFSET;
7115 0 : break;
7116 : #endif
7117 5 : case NCDF_FORMAT_NC4:
7118 5 : nCreationMode = NC_CLOBBER | NC_NETCDF4;
7119 5 : break;
7120 0 : case NCDF_FORMAT_NC4C:
7121 0 : nCreationMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
7122 0 : break;
7123 8 : case NCDF_FORMAT_NC:
7124 : default:
7125 8 : nCreationMode = NC_CLOBBER;
7126 8 : break;
7127 : }
7128 :
7129 13 : int new_cdfid = -1;
7130 26 : CPLString osTmpFilename(osFilename + ".tmp");
7131 26 : CPLString osFilenameForNCCreate(osTmpFilename);
7132 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
7133 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
7134 : {
7135 : char *pszTemp =
7136 : CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
7137 : osFilenameForNCCreate = pszTemp;
7138 : CPLFree(pszTemp);
7139 : }
7140 : #endif
7141 13 : int status = nc_create(osFilenameForNCCreate, nCreationMode, &new_cdfid);
7142 13 : NCDF_ERR(status);
7143 13 : if (status != NC_NOERR)
7144 0 : return false;
7145 :
7146 13 : if (!CloneGrp(cdfid, new_cdfid, eFormat == NCDF_FORMAT_NC4, nLayerId,
7147 : nDimIdToGrow, nNewSize))
7148 : {
7149 0 : GDAL_nc_close(new_cdfid);
7150 0 : return false;
7151 : }
7152 :
7153 13 : int nGroupCount = 0;
7154 26 : std::vector<CPLString> oListGrpName;
7155 31 : if (eFormat == NCDF_FORMAT_NC4 &&
7156 18 : nc_inq_grps(cdfid, &nGroupCount, nullptr) == NC_NOERR &&
7157 5 : nGroupCount > 0)
7158 : {
7159 : int *panGroupIds =
7160 2 : static_cast<int *>(CPLMalloc(sizeof(int) * nGroupCount));
7161 2 : status = nc_inq_grps(cdfid, nullptr, panGroupIds);
7162 2 : NCDF_ERR(status);
7163 5 : for (int i = 0; i < nGroupCount; i++)
7164 : {
7165 : char szGroupName[NC_MAX_NAME + 1];
7166 3 : szGroupName[0] = 0;
7167 3 : NCDF_ERR(nc_inq_grpname(panGroupIds[i], szGroupName));
7168 3 : int nNewGrpId = -1;
7169 3 : status = nc_def_grp(new_cdfid, szGroupName, &nNewGrpId);
7170 3 : NCDF_ERR(status);
7171 3 : if (status != NC_NOERR)
7172 : {
7173 0 : CPLFree(panGroupIds);
7174 0 : GDAL_nc_close(new_cdfid);
7175 0 : return false;
7176 : }
7177 3 : if (!CloneGrp(panGroupIds[i], nNewGrpId, /*bIsNC4=*/true, nLayerId,
7178 : nDimIdToGrow, nNewSize))
7179 : {
7180 0 : CPLFree(panGroupIds);
7181 0 : GDAL_nc_close(new_cdfid);
7182 0 : return false;
7183 : }
7184 : }
7185 2 : CPLFree(panGroupIds);
7186 :
7187 5 : for (int i = 0; i < this->GetLayerCount(); i++)
7188 : {
7189 3 : auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
7190 3 : if (poLayer)
7191 : {
7192 : char szGroupName[NC_MAX_NAME + 1];
7193 3 : szGroupName[0] = 0;
7194 3 : status = nc_inq_grpname(poLayer->GetCDFID(), szGroupName);
7195 3 : NCDF_ERR(status);
7196 3 : oListGrpName.push_back(szGroupName);
7197 : }
7198 : }
7199 : }
7200 :
7201 13 : GDAL_nc_close(cdfid);
7202 13 : cdfid = -1;
7203 13 : GDAL_nc_close(new_cdfid);
7204 :
7205 26 : CPLString osOriFilename(osFilename + ".ori");
7206 26 : if (VSIRename(osFilename, osOriFilename) != 0 ||
7207 13 : VSIRename(osTmpFilename, osFilename) != 0)
7208 : {
7209 0 : CPLError(CE_Failure, CPLE_FileIO, "Renaming of files failed");
7210 0 : return false;
7211 : }
7212 13 : VSIUnlink(osOriFilename);
7213 :
7214 26 : CPLString osFilenameForNCOpen(osFilename);
7215 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
7216 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
7217 : {
7218 : char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
7219 : osFilenameForNCOpen = pszTemp;
7220 : CPLFree(pszTemp);
7221 : }
7222 : #endif
7223 13 : status = GDAL_nc_open(osFilenameForNCOpen, NC_WRITE, &cdfid);
7224 13 : NCDF_ERR(status);
7225 13 : if (status != NC_NOERR)
7226 0 : return false;
7227 13 : bDefineMode = false;
7228 :
7229 13 : if (!oListGrpName.empty())
7230 : {
7231 5 : for (int i = 0; i < this->GetLayerCount(); i++)
7232 : {
7233 3 : auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
7234 3 : if (poLayer)
7235 : {
7236 3 : int nNewLayerCDFID = -1;
7237 3 : status = nc_inq_ncid(cdfid, oListGrpName[i].c_str(),
7238 : &nNewLayerCDFID);
7239 3 : NCDF_ERR(status);
7240 3 : poLayer->SetCDFID(nNewLayerCDFID);
7241 : }
7242 : }
7243 : }
7244 : else
7245 : {
7246 22 : for (int i = 0; i < this->GetLayerCount(); i++)
7247 : {
7248 11 : auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
7249 11 : if (poLayer)
7250 11 : poLayer->SetCDFID(cdfid);
7251 : }
7252 : }
7253 :
7254 13 : return true;
7255 : }
7256 :
7257 : #ifdef ENABLE_NCDUMP
7258 :
7259 : /************************************************************************/
7260 : /* netCDFDatasetCreateTempFile() */
7261 : /************************************************************************/
7262 :
7263 : /* Create a netCDF file from a text dump (format of ncdump) */
7264 : /* Mostly to easy fuzzing of the driver, while still generating valid */
7265 : /* netCDF files. */
7266 : /* Note: not all data types are supported ! */
7267 4 : bool netCDFDatasetCreateTempFile(NetCDFFormatEnum eFormat,
7268 : const char *pszTmpFilename, VSILFILE *fpSrc)
7269 : {
7270 4 : CPL_IGNORE_RET_VAL(eFormat);
7271 4 : int nCreateMode = NC_CLOBBER;
7272 4 : if (eFormat == NCDF_FORMAT_NC4)
7273 1 : nCreateMode |= NC_NETCDF4;
7274 3 : else if (eFormat == NCDF_FORMAT_NC4C)
7275 0 : nCreateMode |= NC_NETCDF4 | NC_CLASSIC_MODEL;
7276 4 : int nCdfId = -1;
7277 4 : int status = nc_create(pszTmpFilename, nCreateMode, &nCdfId);
7278 4 : if (status != NC_NOERR)
7279 : {
7280 0 : return false;
7281 : }
7282 4 : VSIFSeekL(fpSrc, 0, SEEK_SET);
7283 : const char *pszLine;
7284 4 : constexpr int SECTION_NONE = 0;
7285 4 : constexpr int SECTION_DIMENSIONS = 1;
7286 4 : constexpr int SECTION_VARIABLES = 2;
7287 4 : constexpr int SECTION_DATA = 3;
7288 4 : int nActiveSection = SECTION_NONE;
7289 8 : std::map<CPLString, int> oMapDimToId;
7290 8 : std::map<int, int> oMapDimIdToDimLen;
7291 8 : std::map<CPLString, int> oMapVarToId;
7292 8 : std::map<int, std::vector<int>> oMapVarIdToVectorOfDimId;
7293 8 : std::map<int, int> oMapVarIdToType;
7294 4 : std::set<CPLString> oSetAttrDefined;
7295 4 : oMapVarToId[""] = -1;
7296 4 : size_t nTotalVarSize = 0;
7297 208 : while ((pszLine = CPLReadLineL(fpSrc)) != nullptr)
7298 : {
7299 204 : if (STARTS_WITH(pszLine, "dimensions:") &&
7300 : nActiveSection == SECTION_NONE)
7301 : {
7302 4 : nActiveSection = SECTION_DIMENSIONS;
7303 : }
7304 200 : else if (STARTS_WITH(pszLine, "variables:") &&
7305 : nActiveSection == SECTION_DIMENSIONS)
7306 : {
7307 4 : nActiveSection = SECTION_VARIABLES;
7308 : }
7309 196 : else if (STARTS_WITH(pszLine, "data:") &&
7310 : nActiveSection == SECTION_VARIABLES)
7311 : {
7312 4 : nActiveSection = SECTION_DATA;
7313 4 : status = nc_enddef(nCdfId);
7314 4 : if (status != NC_NOERR)
7315 : {
7316 0 : CPLDebug("netCDF", "nc_enddef() failed: %s",
7317 : nc_strerror(status));
7318 : }
7319 : }
7320 192 : else if (nActiveSection == SECTION_DIMENSIONS)
7321 : {
7322 9 : char **papszTokens = CSLTokenizeString2(pszLine, " \t=;", 0);
7323 9 : if (CSLCount(papszTokens) == 2)
7324 : {
7325 9 : const char *pszDimName = papszTokens[0];
7326 9 : bool bValidName = true;
7327 9 : if (STARTS_WITH(pszDimName, "_nc4_non_coord_"))
7328 : {
7329 : // This is an internal netcdf prefix. Using it may
7330 : // cause memory leaks.
7331 0 : bValidName = false;
7332 : }
7333 9 : if (!bValidName)
7334 : {
7335 0 : CPLDebug("netCDF",
7336 : "nc_def_dim(%s) failed: invalid name found",
7337 : pszDimName);
7338 0 : CSLDestroy(papszTokens);
7339 0 : continue;
7340 : }
7341 :
7342 : const bool bIsASCII =
7343 9 : CPLIsASCII(pszDimName, static_cast<size_t>(-1));
7344 9 : if (!bIsASCII)
7345 : {
7346 : // Workaround https://github.com/Unidata/netcdf-c/pull/450
7347 0 : CPLDebug("netCDF",
7348 : "nc_def_dim(%s) failed: rejected because "
7349 : "of non-ASCII characters",
7350 : pszDimName);
7351 0 : CSLDestroy(papszTokens);
7352 0 : continue;
7353 : }
7354 9 : int nDimSize = EQUAL(papszTokens[1], "UNLIMITED")
7355 : ? NC_UNLIMITED
7356 9 : : atoi(papszTokens[1]);
7357 9 : if (nDimSize >= 1000)
7358 1 : nDimSize = 1000; // to avoid very long processing
7359 9 : if (nDimSize >= 0)
7360 : {
7361 9 : int nDimId = -1;
7362 9 : status = nc_def_dim(nCdfId, pszDimName, nDimSize, &nDimId);
7363 9 : if (status != NC_NOERR)
7364 : {
7365 0 : CPLDebug("netCDF", "nc_def_dim(%s, %d) failed: %s",
7366 : pszDimName, nDimSize, nc_strerror(status));
7367 : }
7368 : else
7369 : {
7370 : #ifdef DEBUG_VERBOSE
7371 : CPLDebug("netCDF", "nc_def_dim(%s, %d) (%s) succeeded",
7372 : pszDimName, nDimSize, pszLine);
7373 : #endif
7374 9 : oMapDimToId[pszDimName] = nDimId;
7375 9 : oMapDimIdToDimLen[nDimId] = nDimSize;
7376 : }
7377 : }
7378 : }
7379 9 : CSLDestroy(papszTokens);
7380 : }
7381 183 : else if (nActiveSection == SECTION_VARIABLES)
7382 : {
7383 390 : while (*pszLine == ' ' || *pszLine == '\t')
7384 249 : pszLine++;
7385 141 : const char *pszColumn = strchr(pszLine, ':');
7386 141 : const char *pszEqual = strchr(pszLine, '=');
7387 141 : if (pszColumn == nullptr)
7388 : {
7389 21 : char **papszTokens = CSLTokenizeString2(pszLine, " \t=(),;", 0);
7390 21 : if (CSLCount(papszTokens) >= 2)
7391 : {
7392 17 : const char *pszVarName = papszTokens[1];
7393 17 : bool bValidName = true;
7394 17 : if (STARTS_WITH(pszVarName, "_nc4_non_coord_"))
7395 : {
7396 : // This is an internal netcdf prefix. Using it may
7397 : // cause memory leaks.
7398 0 : bValidName = false;
7399 : }
7400 138 : for (int i = 0; pszVarName[i]; i++)
7401 : {
7402 121 : if (!((pszVarName[i] >= 'a' && pszVarName[i] <= 'z') ||
7403 28 : (pszVarName[i] >= 'A' && pszVarName[i] <= 'Z') ||
7404 9 : (pszVarName[i] >= '0' && pszVarName[i] <= '9') ||
7405 6 : pszVarName[i] == '_'))
7406 : {
7407 0 : bValidName = false;
7408 : }
7409 : }
7410 17 : if (!bValidName)
7411 : {
7412 0 : CPLDebug(
7413 : "netCDF",
7414 : "nc_def_var(%s) failed: illegal character found",
7415 : pszVarName);
7416 0 : CSLDestroy(papszTokens);
7417 0 : continue;
7418 : }
7419 17 : if (oMapVarToId.find(pszVarName) != oMapVarToId.end())
7420 : {
7421 0 : CPLDebug("netCDF",
7422 : "nc_def_var(%s) failed: already defined",
7423 : pszVarName);
7424 0 : CSLDestroy(papszTokens);
7425 0 : continue;
7426 : }
7427 17 : const char *pszVarType = papszTokens[0];
7428 17 : int nc_datatype = NC_BYTE;
7429 17 : size_t nDataTypeSize = 1;
7430 17 : if (EQUAL(pszVarType, "char"))
7431 : {
7432 6 : nc_datatype = NC_CHAR;
7433 6 : nDataTypeSize = 1;
7434 : }
7435 11 : else if (EQUAL(pszVarType, "byte"))
7436 : {
7437 3 : nc_datatype = NC_BYTE;
7438 3 : nDataTypeSize = 1;
7439 : }
7440 8 : else if (EQUAL(pszVarType, "short"))
7441 : {
7442 0 : nc_datatype = NC_SHORT;
7443 0 : nDataTypeSize = 2;
7444 : }
7445 8 : else if (EQUAL(pszVarType, "int"))
7446 : {
7447 0 : nc_datatype = NC_INT;
7448 0 : nDataTypeSize = 4;
7449 : }
7450 8 : else if (EQUAL(pszVarType, "float"))
7451 : {
7452 0 : nc_datatype = NC_FLOAT;
7453 0 : nDataTypeSize = 4;
7454 : }
7455 8 : else if (EQUAL(pszVarType, "double"))
7456 : {
7457 8 : nc_datatype = NC_DOUBLE;
7458 8 : nDataTypeSize = 8;
7459 : }
7460 0 : else if (EQUAL(pszVarType, "ubyte"))
7461 : {
7462 0 : nc_datatype = NC_UBYTE;
7463 0 : nDataTypeSize = 1;
7464 : }
7465 0 : else if (EQUAL(pszVarType, "ushort"))
7466 : {
7467 0 : nc_datatype = NC_USHORT;
7468 0 : nDataTypeSize = 2;
7469 : }
7470 0 : else if (EQUAL(pszVarType, "uint"))
7471 : {
7472 0 : nc_datatype = NC_UINT;
7473 0 : nDataTypeSize = 4;
7474 : }
7475 0 : else if (EQUAL(pszVarType, "int64"))
7476 : {
7477 0 : nc_datatype = NC_INT64;
7478 0 : nDataTypeSize = 8;
7479 : }
7480 0 : else if (EQUAL(pszVarType, "uint64"))
7481 : {
7482 0 : nc_datatype = NC_UINT64;
7483 0 : nDataTypeSize = 8;
7484 : }
7485 :
7486 17 : int nDims = CSLCount(papszTokens) - 2;
7487 17 : if (nDims >= 32)
7488 : {
7489 : // The number of dimensions in a netCDFv4 file is
7490 : // limited by #define H5S_MAX_RANK 32
7491 : // but libnetcdf doesn't check that...
7492 0 : CPLDebug("netCDF",
7493 : "nc_def_var(%s) failed: too many dimensions",
7494 : pszVarName);
7495 0 : CSLDestroy(papszTokens);
7496 0 : continue;
7497 : }
7498 17 : std::vector<int> aoDimIds;
7499 17 : bool bFailed = false;
7500 17 : size_t nSize = 1;
7501 35 : for (int i = 0; i < nDims; i++)
7502 : {
7503 18 : const char *pszDimName = papszTokens[2 + i];
7504 18 : if (oMapDimToId.find(pszDimName) == oMapDimToId.end())
7505 : {
7506 0 : bFailed = true;
7507 0 : break;
7508 : }
7509 18 : const int nDimId = oMapDimToId[pszDimName];
7510 18 : aoDimIds.push_back(nDimId);
7511 :
7512 18 : const size_t nDimSize = oMapDimIdToDimLen[nDimId];
7513 18 : if (nDimSize != 0)
7514 : {
7515 18 : if (nSize >
7516 18 : std::numeric_limits<size_t>::max() / nDimSize)
7517 : {
7518 0 : bFailed = true;
7519 0 : break;
7520 : }
7521 : else
7522 : {
7523 18 : nSize *= nDimSize;
7524 : }
7525 : }
7526 : }
7527 17 : if (bFailed)
7528 : {
7529 0 : CPLDebug("netCDF",
7530 : "nc_def_var(%s) failed: unknown dimension(s)",
7531 : pszVarName);
7532 0 : CSLDestroy(papszTokens);
7533 0 : continue;
7534 : }
7535 17 : if (nSize > 100U * 1024 * 1024 / nDataTypeSize)
7536 : {
7537 0 : CPLDebug("netCDF",
7538 : "nc_def_var(%s) failed: too large data",
7539 : pszVarName);
7540 0 : CSLDestroy(papszTokens);
7541 0 : continue;
7542 : }
7543 17 : if (nTotalVarSize >
7544 34 : std::numeric_limits<size_t>::max() - nSize ||
7545 17 : nTotalVarSize + nSize > 100 * 1024 * 1024)
7546 : {
7547 0 : CPLDebug("netCDF",
7548 : "nc_def_var(%s) failed: too large data",
7549 : pszVarName);
7550 0 : CSLDestroy(papszTokens);
7551 0 : continue;
7552 : }
7553 17 : nTotalVarSize += nSize;
7554 :
7555 17 : int nVarId = -1;
7556 : status =
7557 30 : nc_def_var(nCdfId, pszVarName, nc_datatype, nDims,
7558 13 : (nDims) ? &aoDimIds[0] : nullptr, &nVarId);
7559 17 : if (status != NC_NOERR)
7560 : {
7561 0 : CPLDebug("netCDF", "nc_def_var(%s) failed: %s",
7562 : pszVarName, nc_strerror(status));
7563 : }
7564 : else
7565 : {
7566 : #ifdef DEBUG_VERBOSE
7567 : CPLDebug("netCDF", "nc_def_var(%s) (%s) succeeded",
7568 : pszVarName, pszLine);
7569 : #endif
7570 17 : oMapVarToId[pszVarName] = nVarId;
7571 17 : oMapVarIdToType[nVarId] = nc_datatype;
7572 17 : oMapVarIdToVectorOfDimId[nVarId] = std::move(aoDimIds);
7573 : }
7574 : }
7575 21 : CSLDestroy(papszTokens);
7576 : }
7577 120 : else if (pszEqual != nullptr && pszEqual - pszColumn > 0)
7578 : {
7579 116 : CPLString osVarName(pszLine, pszColumn - pszLine);
7580 116 : CPLString osAttrName(pszColumn + 1, pszEqual - pszColumn - 1);
7581 116 : osAttrName.Trim();
7582 116 : if (oMapVarToId.find(osVarName) == oMapVarToId.end())
7583 : {
7584 0 : CPLDebug("netCDF",
7585 : "nc_put_att(%s:%s) failed: "
7586 : "no corresponding variable",
7587 : osVarName.c_str(), osAttrName.c_str());
7588 0 : continue;
7589 : }
7590 116 : bool bValidName = true;
7591 1743 : for (size_t i = 0; i < osAttrName.size(); i++)
7592 : {
7593 1865 : if (!((osAttrName[i] >= 'a' && osAttrName[i] <= 'z') ||
7594 238 : (osAttrName[i] >= 'A' && osAttrName[i] <= 'Z') ||
7595 158 : (osAttrName[i] >= '0' && osAttrName[i] <= '9') ||
7596 158 : osAttrName[i] == '_'))
7597 : {
7598 0 : bValidName = false;
7599 : }
7600 : }
7601 116 : if (!bValidName)
7602 : {
7603 0 : CPLDebug(
7604 : "netCDF",
7605 : "nc_put_att(%s:%s) failed: illegal character found",
7606 : osVarName.c_str(), osAttrName.c_str());
7607 0 : continue;
7608 : }
7609 116 : if (oSetAttrDefined.find(osVarName + ":" + osAttrName) !=
7610 232 : oSetAttrDefined.end())
7611 : {
7612 0 : CPLDebug("netCDF",
7613 : "nc_put_att(%s:%s) failed: already defined",
7614 : osVarName.c_str(), osAttrName.c_str());
7615 0 : continue;
7616 : }
7617 :
7618 116 : const int nVarId = oMapVarToId[osVarName];
7619 116 : const char *pszValue = pszEqual + 1;
7620 232 : while (*pszValue == ' ')
7621 116 : pszValue++;
7622 :
7623 116 : status = NC_EBADTYPE;
7624 116 : if (*pszValue == '"')
7625 : {
7626 : // For _FillValue, the attribute type should match
7627 : // the variable type. Leaks memory with NC4 otherwise
7628 74 : if (osAttrName == "_FillValue")
7629 : {
7630 0 : CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
7631 : osVarName.c_str(), osAttrName.c_str(),
7632 : nc_strerror(status));
7633 0 : continue;
7634 : }
7635 :
7636 : // Unquote and unescape string value
7637 74 : CPLString osVal(pszValue + 1);
7638 222 : while (!osVal.empty())
7639 : {
7640 222 : if (osVal.back() == ';' || osVal.back() == ' ')
7641 : {
7642 148 : osVal.pop_back();
7643 : }
7644 74 : else if (osVal.back() == '"')
7645 : {
7646 74 : osVal.pop_back();
7647 74 : break;
7648 : }
7649 : else
7650 : {
7651 0 : break;
7652 : }
7653 : }
7654 74 : osVal.replaceAll("\\\"", '"');
7655 74 : status = nc_put_att_text(nCdfId, nVarId, osAttrName,
7656 : osVal.size(), osVal.c_str());
7657 : }
7658 : else
7659 : {
7660 84 : CPLString osVal(pszValue);
7661 126 : while (!osVal.empty())
7662 : {
7663 126 : if (osVal.back() == ';' || osVal.back() == ' ')
7664 : {
7665 84 : osVal.pop_back();
7666 : }
7667 : else
7668 : {
7669 42 : break;
7670 : }
7671 : }
7672 42 : int nc_datatype = -1;
7673 42 : if (!osVal.empty() && osVal.back() == 'b')
7674 : {
7675 3 : nc_datatype = NC_BYTE;
7676 3 : osVal.pop_back();
7677 : }
7678 39 : else if (!osVal.empty() && osVal.back() == 's')
7679 : {
7680 3 : nc_datatype = NC_SHORT;
7681 3 : osVal.pop_back();
7682 : }
7683 42 : if (CPLGetValueType(osVal) == CPL_VALUE_INTEGER)
7684 : {
7685 7 : if (nc_datatype < 0)
7686 4 : nc_datatype = NC_INT;
7687 : }
7688 35 : else if (CPLGetValueType(osVal) == CPL_VALUE_REAL)
7689 : {
7690 32 : nc_datatype = NC_DOUBLE;
7691 : }
7692 : else
7693 : {
7694 3 : nc_datatype = -1;
7695 : }
7696 :
7697 : // For _FillValue, check that the attribute type matches
7698 : // the variable type. Leaks memory with NC4 otherwise
7699 42 : if (osAttrName == "_FillValue")
7700 : {
7701 6 : if (nVarId < 0 ||
7702 3 : nc_datatype != oMapVarIdToType[nVarId])
7703 : {
7704 0 : nc_datatype = -1;
7705 : }
7706 : }
7707 :
7708 42 : if (nc_datatype == NC_BYTE)
7709 : {
7710 : signed char chVal =
7711 3 : static_cast<signed char>(atoi(osVal));
7712 3 : status = nc_put_att_schar(nCdfId, nVarId, osAttrName,
7713 : NC_BYTE, 1, &chVal);
7714 : }
7715 39 : else if (nc_datatype == NC_SHORT)
7716 : {
7717 0 : short nVal = static_cast<short>(atoi(osVal));
7718 0 : status = nc_put_att_short(nCdfId, nVarId, osAttrName,
7719 : NC_SHORT, 1, &nVal);
7720 : }
7721 39 : else if (nc_datatype == NC_INT)
7722 : {
7723 4 : int nVal = static_cast<int>(atoi(osVal));
7724 4 : status = nc_put_att_int(nCdfId, nVarId, osAttrName,
7725 : NC_INT, 1, &nVal);
7726 : }
7727 35 : else if (nc_datatype == NC_DOUBLE)
7728 : {
7729 32 : double dfVal = CPLAtof(osVal);
7730 32 : status = nc_put_att_double(nCdfId, nVarId, osAttrName,
7731 : NC_DOUBLE, 1, &dfVal);
7732 : }
7733 : }
7734 116 : if (status != NC_NOERR)
7735 : {
7736 3 : CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
7737 : osVarName.c_str(), osAttrName.c_str(),
7738 : nc_strerror(status));
7739 : }
7740 : else
7741 : {
7742 113 : oSetAttrDefined.insert(osVarName + ":" + osAttrName);
7743 : #ifdef DEBUG_VERBOSE
7744 : CPLDebug("netCDF", "nc_put_att_(%s:%s) (%s) succeeded",
7745 : osVarName.c_str(), osAttrName.c_str(), pszLine);
7746 : #endif
7747 : }
7748 : }
7749 : }
7750 42 : else if (nActiveSection == SECTION_DATA)
7751 : {
7752 55 : while (*pszLine == ' ' || *pszLine == '\t')
7753 17 : pszLine++;
7754 38 : const char *pszEqual = strchr(pszLine, '=');
7755 38 : if (pszEqual)
7756 : {
7757 17 : CPLString osVarName(pszLine, pszEqual - pszLine);
7758 17 : osVarName.Trim();
7759 17 : if (oMapVarToId.find(osVarName) == oMapVarToId.end())
7760 0 : continue;
7761 17 : const int nVarId = oMapVarToId[osVarName];
7762 17 : CPLString osAccVal(pszEqual + 1);
7763 17 : osAccVal.Trim();
7764 153 : while (osAccVal.empty() || osAccVal.back() != ';')
7765 : {
7766 136 : pszLine = CPLReadLineL(fpSrc);
7767 136 : if (pszLine == nullptr)
7768 0 : break;
7769 272 : CPLString osVal(pszLine);
7770 136 : osVal.Trim();
7771 136 : osAccVal += osVal;
7772 : }
7773 17 : if (pszLine == nullptr)
7774 0 : break;
7775 17 : osAccVal.pop_back();
7776 :
7777 : const std::vector<int> aoDimIds =
7778 34 : oMapVarIdToVectorOfDimId[nVarId];
7779 17 : size_t nSize = 1;
7780 34 : std::vector<size_t> aoStart, aoEdge;
7781 17 : aoStart.resize(aoDimIds.size());
7782 17 : aoEdge.resize(aoDimIds.size());
7783 35 : for (size_t i = 0; i < aoDimIds.size(); ++i)
7784 : {
7785 18 : const size_t nDimSize = oMapDimIdToDimLen[aoDimIds[i]];
7786 36 : if (nDimSize != 0 &&
7787 18 : nSize > std::numeric_limits<size_t>::max() / nDimSize)
7788 : {
7789 0 : nSize = 0;
7790 : }
7791 : else
7792 : {
7793 18 : nSize *= nDimSize;
7794 : }
7795 18 : aoStart[i] = 0;
7796 18 : aoEdge[i] = nDimSize;
7797 : }
7798 :
7799 17 : status = NC_EBADTYPE;
7800 17 : if (nSize == 0)
7801 : {
7802 : // Might happen with a unlimited dimension
7803 : }
7804 17 : else if (oMapVarIdToType[nVarId] == NC_DOUBLE)
7805 : {
7806 8 : if (!aoStart.empty())
7807 : {
7808 : char **papszTokens =
7809 8 : CSLTokenizeString2(osAccVal, " ,;", 0);
7810 8 : size_t nTokens = CSLCount(papszTokens);
7811 8 : if (nTokens >= nSize)
7812 : {
7813 : double *padfVals = static_cast<double *>(
7814 8 : VSI_CALLOC_VERBOSE(nSize, sizeof(double)));
7815 8 : if (padfVals)
7816 : {
7817 132 : for (size_t i = 0; i < nSize; i++)
7818 : {
7819 124 : padfVals[i] = CPLAtof(papszTokens[i]);
7820 : }
7821 8 : status = nc_put_vara_double(
7822 8 : nCdfId, nVarId, &aoStart[0], &aoEdge[0],
7823 : padfVals);
7824 8 : VSIFree(padfVals);
7825 : }
7826 : }
7827 8 : CSLDestroy(papszTokens);
7828 : }
7829 : }
7830 9 : else if (oMapVarIdToType[nVarId] == NC_BYTE)
7831 : {
7832 3 : if (!aoStart.empty())
7833 : {
7834 : char **papszTokens =
7835 3 : CSLTokenizeString2(osAccVal, " ,;", 0);
7836 3 : size_t nTokens = CSLCount(papszTokens);
7837 3 : if (nTokens >= nSize)
7838 : {
7839 : signed char *panVals = static_cast<signed char *>(
7840 3 : VSI_CALLOC_VERBOSE(nSize, sizeof(signed char)));
7841 3 : if (panVals)
7842 : {
7843 1203 : for (size_t i = 0; i < nSize; i++)
7844 : {
7845 1200 : panVals[i] = static_cast<signed char>(
7846 1200 : atoi(papszTokens[i]));
7847 : }
7848 3 : status = nc_put_vara_schar(nCdfId, nVarId,
7849 3 : &aoStart[0],
7850 3 : &aoEdge[0], panVals);
7851 3 : VSIFree(panVals);
7852 : }
7853 : }
7854 3 : CSLDestroy(papszTokens);
7855 : }
7856 : }
7857 6 : else if (oMapVarIdToType[nVarId] == NC_CHAR)
7858 : {
7859 6 : if (aoStart.size() == 2)
7860 : {
7861 4 : std::vector<CPLString> aoStrings;
7862 2 : bool bInString = false;
7863 4 : CPLString osCurString;
7864 935 : for (size_t i = 0; i < osAccVal.size();)
7865 : {
7866 933 : if (!bInString)
7867 : {
7868 8 : if (osAccVal[i] == '"')
7869 : {
7870 4 : bInString = true;
7871 4 : osCurString.clear();
7872 : }
7873 8 : i++;
7874 : }
7875 926 : else if (osAccVal[i] == '\\' &&
7876 926 : i + 1 < osAccVal.size() &&
7877 1 : osAccVal[i + 1] == '"')
7878 : {
7879 1 : osCurString += '"';
7880 1 : i += 2;
7881 : }
7882 924 : else if (osAccVal[i] == '"')
7883 : {
7884 4 : aoStrings.push_back(osCurString);
7885 4 : osCurString.clear();
7886 4 : bInString = false;
7887 4 : i++;
7888 : }
7889 : else
7890 : {
7891 920 : osCurString += osAccVal[i];
7892 920 : i++;
7893 : }
7894 : }
7895 2 : const size_t nRecords = oMapDimIdToDimLen[aoDimIds[0]];
7896 2 : const size_t nWidth = oMapDimIdToDimLen[aoDimIds[1]];
7897 2 : size_t nIters = aoStrings.size();
7898 2 : if (nIters > nRecords)
7899 0 : nIters = nRecords;
7900 6 : for (size_t i = 0; i < nIters; i++)
7901 : {
7902 : size_t anIndex[2];
7903 4 : anIndex[0] = i;
7904 4 : anIndex[1] = 0;
7905 : size_t anCount[2];
7906 4 : anCount[0] = 1;
7907 4 : anCount[1] = aoStrings[i].size();
7908 4 : if (anCount[1] > nWidth)
7909 0 : anCount[1] = nWidth;
7910 : status =
7911 4 : nc_put_vara_text(nCdfId, nVarId, anIndex,
7912 4 : anCount, aoStrings[i].c_str());
7913 4 : if (status != NC_NOERR)
7914 0 : break;
7915 : }
7916 : }
7917 : }
7918 17 : if (status != NC_NOERR)
7919 : {
7920 4 : CPLDebug("netCDF", "nc_put_var_(%s) failed: %s",
7921 : osVarName.c_str(), nc_strerror(status));
7922 : }
7923 : }
7924 : }
7925 : }
7926 :
7927 4 : GDAL_nc_close(nCdfId);
7928 4 : return true;
7929 : }
7930 :
7931 : #endif // ENABLE_NCDUMP
7932 :
7933 : /************************************************************************/
7934 : /* Open() */
7935 : /************************************************************************/
7936 :
7937 656 : GDALDataset *netCDFDataset::Open(GDALOpenInfo *poOpenInfo)
7938 :
7939 : {
7940 : #ifdef NCDF_DEBUG
7941 : CPLDebug("GDAL_netCDF", "\n=====\nOpen(), filename=[%s]",
7942 : poOpenInfo->pszFilename);
7943 : #endif
7944 :
7945 : // Does this appear to be a netcdf file?
7946 656 : NetCDFFormatEnum eTmpFormat = NCDF_FORMAT_NONE;
7947 656 : if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
7948 : {
7949 598 : eTmpFormat = netCDFIdentifyFormat(poOpenInfo, /* bCheckExt = */ true);
7950 : #ifdef NCDF_DEBUG
7951 : CPLDebug("GDAL_netCDF", "identified format %d", eTmpFormat);
7952 : #endif
7953 : // Note: not calling Identify() directly, because we want the file type.
7954 : // Only support NCDF_FORMAT* formats.
7955 598 : if (NCDF_FORMAT_NC == eTmpFormat || NCDF_FORMAT_NC2 == eTmpFormat ||
7956 2 : NCDF_FORMAT_NC4 == eTmpFormat || NCDF_FORMAT_NC4C == eTmpFormat)
7957 : {
7958 : // ok
7959 : }
7960 2 : else if (eTmpFormat == NCDF_FORMAT_HDF4 &&
7961 0 : poOpenInfo->IsSingleAllowedDriver("netCDF"))
7962 : {
7963 : // ok
7964 : }
7965 : else
7966 : {
7967 2 : return nullptr;
7968 : }
7969 : }
7970 : else
7971 : {
7972 : #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
7973 : // We don't necessarily want to catch bugs in libnetcdf ...
7974 : if (CPLGetConfigOption("DISABLE_OPEN_REAL_NETCDF_FILES", nullptr))
7975 : {
7976 : return nullptr;
7977 : }
7978 : #endif
7979 : }
7980 :
7981 654 : if (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER)
7982 : {
7983 173 : return OpenMultiDim(poOpenInfo);
7984 : }
7985 :
7986 962 : CPLMutexHolderD(&hNCMutex);
7987 :
7988 481 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
7989 : // GDALDataset own mutex.
7990 481 : netCDFDataset *poDS = new netCDFDataset();
7991 481 : poDS->papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
7992 481 : CPLAcquireMutex(hNCMutex, 1000.0);
7993 :
7994 481 : poDS->SetDescription(poOpenInfo->pszFilename);
7995 :
7996 : // Check if filename start with NETCDF: tag.
7997 481 : bool bTreatAsSubdataset = false;
7998 962 : CPLString osSubdatasetName;
7999 :
8000 : #ifdef ENABLE_NCDUMP
8001 481 : const char *pszHeader =
8002 : reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
8003 481 : if (poOpenInfo->fpL != nullptr && STARTS_WITH(pszHeader, "netcdf ") &&
8004 3 : strstr(pszHeader, "dimensions:") && strstr(pszHeader, "variables:"))
8005 : {
8006 : // By default create a temporary file that will be destroyed,
8007 : // unless NETCDF_TMP_FILE is defined. Can be useful to see which
8008 : // netCDF file has been generated from a potential fuzzed input.
8009 3 : poDS->osFilename = CPLGetConfigOption("NETCDF_TMP_FILE", "");
8010 3 : if (poDS->osFilename.empty())
8011 : {
8012 3 : poDS->bFileToDestroyAtClosing = true;
8013 3 : poDS->osFilename = CPLGenerateTempFilename("netcdf_tmp");
8014 : }
8015 3 : if (!netCDFDatasetCreateTempFile(eTmpFormat, poDS->osFilename,
8016 : poOpenInfo->fpL))
8017 : {
8018 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8019 : // deadlock with GDALDataset own mutex.
8020 0 : delete poDS;
8021 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8022 0 : return nullptr;
8023 : }
8024 3 : bTreatAsSubdataset = false;
8025 3 : poDS->eFormat = eTmpFormat;
8026 : }
8027 : else
8028 : #endif
8029 :
8030 478 : if (STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
8031 : {
8032 : char **papszName =
8033 58 : CSLTokenizeString2(poOpenInfo->pszFilename, ":",
8034 : CSLT_HONOURSTRINGS | CSLT_PRESERVEESCAPES);
8035 :
8036 116 : if (CSLCount(papszName) >= 3 &&
8037 58 : ((strlen(papszName[1]) == 1 && /* D:\\bla */
8038 0 : (papszName[2][0] == '/' || papszName[2][0] == '\\')) ||
8039 58 : EQUAL(papszName[1], "http") || EQUAL(papszName[1], "https") ||
8040 58 : EQUAL(papszName[1], "/vsicurl/http") ||
8041 58 : EQUAL(papszName[1], "/vsicurl/https") ||
8042 58 : EQUAL(papszName[1], "/vsicurl_streaming/http") ||
8043 58 : EQUAL(papszName[1], "/vsicurl_streaming/https")))
8044 : {
8045 0 : const int nCountBefore = CSLCount(papszName);
8046 0 : CPLString osTmp = papszName[1];
8047 0 : osTmp += ':';
8048 0 : osTmp += papszName[2];
8049 0 : CPLFree(papszName[1]);
8050 0 : CPLFree(papszName[2]);
8051 0 : papszName[1] = CPLStrdup(osTmp);
8052 0 : memmove(papszName + 2, papszName + 3,
8053 0 : (nCountBefore - 2) * sizeof(char *));
8054 : }
8055 :
8056 58 : if (CSLCount(papszName) == 3)
8057 : {
8058 58 : poDS->osFilename = papszName[1];
8059 58 : osSubdatasetName = papszName[2];
8060 58 : bTreatAsSubdataset = true;
8061 58 : CSLDestroy(papszName);
8062 : }
8063 0 : else if (CSLCount(papszName) == 2)
8064 : {
8065 0 : poDS->osFilename = papszName[1];
8066 0 : osSubdatasetName = "";
8067 0 : bTreatAsSubdataset = false;
8068 0 : CSLDestroy(papszName);
8069 : }
8070 : else
8071 : {
8072 0 : CSLDestroy(papszName);
8073 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8074 : // deadlock with GDALDataset own mutex.
8075 0 : delete poDS;
8076 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8077 0 : CPLError(CE_Failure, CPLE_AppDefined,
8078 : "Failed to parse NETCDF: prefix string into expected 2, 3 "
8079 : "or 4 fields.");
8080 0 : return nullptr;
8081 : }
8082 :
8083 116 : if (!STARTS_WITH(poDS->osFilename, "http://") &&
8084 58 : !STARTS_WITH(poDS->osFilename, "https://"))
8085 : {
8086 : // Identify Format from real file, with bCheckExt=FALSE.
8087 : GDALOpenInfo *poOpenInfo2 =
8088 58 : new GDALOpenInfo(poDS->osFilename.c_str(), GA_ReadOnly);
8089 58 : poDS->eFormat =
8090 58 : netCDFIdentifyFormat(poOpenInfo2, /* bCheckExt = */ false);
8091 58 : delete poOpenInfo2;
8092 58 : if (NCDF_FORMAT_NONE == poDS->eFormat ||
8093 58 : NCDF_FORMAT_UNKNOWN == poDS->eFormat)
8094 : {
8095 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8096 : // deadlock with GDALDataset own mutex.
8097 0 : delete poDS;
8098 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8099 0 : return nullptr;
8100 : }
8101 : }
8102 : }
8103 : else
8104 : {
8105 420 : poDS->osFilename = poOpenInfo->pszFilename;
8106 420 : bTreatAsSubdataset = false;
8107 420 : poDS->eFormat = eTmpFormat;
8108 : }
8109 :
8110 : // Try opening the dataset.
8111 : #if defined(NCDF_DEBUG) && defined(ENABLE_UFFD)
8112 : CPLDebug("GDAL_netCDF", "calling nc_open_mem(%s)",
8113 : poDS->osFilename.c_str());
8114 : #elif defined(NCDF_DEBUG) && !defined(ENABLE_UFFD)
8115 : CPLDebug("GDAL_netCDF", "calling nc_open(%s)", poDS->osFilename.c_str());
8116 : #endif
8117 481 : int cdfid = -1;
8118 481 : const int nMode = ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE) != 0)
8119 : ? NC_WRITE
8120 : : NC_NOWRITE;
8121 962 : CPLString osFilenameForNCOpen(poDS->osFilename);
8122 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
8123 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
8124 : {
8125 : char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
8126 : osFilenameForNCOpen = pszTemp;
8127 : CPLFree(pszTemp);
8128 : }
8129 : #endif
8130 481 : int status2 = -1;
8131 :
8132 : #ifdef ENABLE_UFFD
8133 481 : cpl_uffd_context *pCtx = nullptr;
8134 : #endif
8135 :
8136 496 : if (STARTS_WITH(osFilenameForNCOpen, "/vsimem/") &&
8137 15 : poOpenInfo->eAccess == GA_ReadOnly)
8138 : {
8139 15 : vsi_l_offset nLength = 0;
8140 15 : poDS->fpVSIMEM = VSIFOpenL(osFilenameForNCOpen, "rb");
8141 15 : if (poDS->fpVSIMEM)
8142 : {
8143 : // We assume that the file will not be modified. If it is, then
8144 : // pabyBuffer might become invalid.
8145 : GByte *pabyBuffer =
8146 15 : VSIGetMemFileBuffer(osFilenameForNCOpen, &nLength, false);
8147 15 : if (pabyBuffer)
8148 : {
8149 15 : status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen),
8150 : nMode, static_cast<size_t>(nLength),
8151 : pabyBuffer, &cdfid);
8152 : }
8153 : }
8154 : }
8155 : else
8156 : {
8157 : const bool bVsiFile =
8158 466 : !strncmp(osFilenameForNCOpen, "/vsi", strlen("/vsi"));
8159 : #ifdef ENABLE_UFFD
8160 466 : bool bReadOnly = (poOpenInfo->eAccess == GA_ReadOnly);
8161 466 : void *pVma = nullptr;
8162 466 : uint64_t nVmaSize = 0;
8163 :
8164 466 : if (bVsiFile)
8165 : {
8166 1 : if (bReadOnly)
8167 : {
8168 1 : if (CPLIsUserFaultMappingSupported())
8169 : {
8170 1 : pCtx = CPLCreateUserFaultMapping(osFilenameForNCOpen, &pVma,
8171 : &nVmaSize);
8172 : }
8173 : else
8174 : {
8175 0 : CPLError(CE_Failure, CPLE_AppDefined,
8176 : "Opening a /vsi file with the netCDF driver "
8177 : "requires Linux userfaultfd to be available. "
8178 : "If running from Docker, "
8179 : "--security-opt seccomp=unconfined might be "
8180 : "needed.%s",
8181 0 : ((poDS->eFormat == NCDF_FORMAT_NC4 ||
8182 0 : poDS->eFormat == NCDF_FORMAT_HDF5) &&
8183 0 : GDALGetDriverByName("HDF5"))
8184 : ? " Or you may set the GDAL_SKIP=netCDF "
8185 : "configuration option to force the use of "
8186 : "the HDF5 driver."
8187 : : "");
8188 : }
8189 : }
8190 : else
8191 : {
8192 0 : CPLError(CE_Failure, CPLE_AppDefined,
8193 : "Opening a /vsi file with the netCDF driver is only "
8194 : "supported in read-only mode");
8195 : }
8196 : }
8197 466 : if (pCtx != nullptr && pVma != nullptr && nVmaSize > 0)
8198 : {
8199 : // netCDF code, at least for netCDF 4.7.0, is confused by filenames
8200 : // like /vsicurl/http[s]://example.com/foo.nc, so just pass the
8201 : // final part
8202 1 : status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen), nMode,
8203 : static_cast<size_t>(nVmaSize), pVma, &cdfid);
8204 : }
8205 : else
8206 465 : status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
8207 : #else
8208 : if (bVsiFile)
8209 : {
8210 : CPLError(
8211 : CE_Failure, CPLE_AppDefined,
8212 : "Opening a /vsi file with the netCDF driver requires Linux "
8213 : "userfaultfd to be available.%s",
8214 : ((poDS->eFormat == NCDF_FORMAT_NC4 ||
8215 : poDS->eFormat == NCDF_FORMAT_HDF5) &&
8216 : GDALGetDriverByName("HDF5"))
8217 : ? " Or you may set the GDAL_SKIP=netCDF "
8218 : "configuration option to force the use of the HDF5 "
8219 : "driver."
8220 : : "");
8221 : status2 = NC_EIO;
8222 : }
8223 : else
8224 : {
8225 : status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
8226 : }
8227 : #endif
8228 : }
8229 481 : if (status2 != NC_NOERR)
8230 : {
8231 : #ifdef NCDF_DEBUG
8232 : CPLDebug("GDAL_netCDF", "error opening");
8233 : #endif
8234 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8235 : // with GDALDataset own mutex.
8236 0 : delete poDS;
8237 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8238 0 : return nullptr;
8239 : }
8240 : #ifdef NCDF_DEBUG
8241 : CPLDebug("GDAL_netCDF", "got cdfid=%d", cdfid);
8242 : #endif
8243 :
8244 : #if defined(ENABLE_NCDUMP) && !defined(_WIN32)
8245 : // Try to destroy the temporary file right now on Unix
8246 481 : if (poDS->bFileToDestroyAtClosing)
8247 : {
8248 3 : if (VSIUnlink(poDS->osFilename) == 0)
8249 : {
8250 3 : poDS->bFileToDestroyAtClosing = false;
8251 : }
8252 : }
8253 : #endif
8254 :
8255 : // Is this a real netCDF file?
8256 : int ndims;
8257 : int ngatts;
8258 : int nvars;
8259 : int unlimdimid;
8260 481 : int status = nc_inq(cdfid, &ndims, &nvars, &ngatts, &unlimdimid);
8261 481 : if (status != NC_NOERR)
8262 : {
8263 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8264 : // with GDALDataset own mutex.
8265 0 : delete poDS;
8266 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8267 0 : return nullptr;
8268 : }
8269 :
8270 : // Get file type from netcdf.
8271 481 : int nTmpFormat = NCDF_FORMAT_NONE;
8272 481 : status = nc_inq_format(cdfid, &nTmpFormat);
8273 481 : if (status != NC_NOERR)
8274 : {
8275 0 : NCDF_ERR(status);
8276 : }
8277 : else
8278 : {
8279 481 : CPLDebug("GDAL_netCDF",
8280 : "driver detected file type=%d, libnetcdf detected type=%d",
8281 481 : poDS->eFormat, nTmpFormat);
8282 481 : if (static_cast<NetCDFFormatEnum>(nTmpFormat) != poDS->eFormat)
8283 : {
8284 : // Warn if file detection conflicts with that from libnetcdf
8285 : // except for NC4C, which we have no way of detecting initially.
8286 24 : if (nTmpFormat != NCDF_FORMAT_NC4C &&
8287 12 : !STARTS_WITH(poDS->osFilename, "http://") &&
8288 0 : !STARTS_WITH(poDS->osFilename, "https://"))
8289 : {
8290 0 : CPLError(CE_Warning, CPLE_AppDefined,
8291 : "NetCDF driver detected file type=%d, but libnetcdf "
8292 : "detected type=%d",
8293 0 : poDS->eFormat, nTmpFormat);
8294 : }
8295 12 : CPLDebug("GDAL_netCDF", "setting file type to %d, was %d",
8296 12 : nTmpFormat, poDS->eFormat);
8297 12 : poDS->eFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
8298 : }
8299 : }
8300 :
8301 : // Does the request variable exist?
8302 481 : if (bTreatAsSubdataset)
8303 : {
8304 : int dummy;
8305 58 : if (NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &dummy,
8306 58 : &dummy) != CE_None)
8307 : {
8308 0 : CPLError(CE_Warning, CPLE_AppDefined,
8309 : "%s is a netCDF file, but %s is not a variable.",
8310 : poOpenInfo->pszFilename, osSubdatasetName.c_str());
8311 :
8312 0 : GDAL_nc_close(cdfid);
8313 : #ifdef ENABLE_UFFD
8314 0 : NETCDF_UFFD_UNMAP(pCtx);
8315 : #endif
8316 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8317 : // deadlock with GDALDataset own mutex.
8318 0 : delete poDS;
8319 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8320 0 : return nullptr;
8321 : }
8322 : }
8323 :
8324 : // Figure out whether or not the listed dataset has support for simple
8325 : // geometries (CF-1.8)
8326 481 : poDS->nCFVersion = nccfdriver::getCFVersion(cdfid);
8327 481 : bool bHasSimpleGeometries = false; // but not necessarily valid
8328 481 : if (poDS->nCFVersion >= 1.8)
8329 : {
8330 75 : bHasSimpleGeometries = poDS->DetectAndFillSGLayers(cdfid);
8331 75 : if (bHasSimpleGeometries)
8332 : {
8333 67 : poDS->bSGSupport = true;
8334 67 : poDS->vcdf.enableFullVirtualMode();
8335 : }
8336 : }
8337 :
8338 : char szConventions[NC_MAX_NAME + 1];
8339 481 : szConventions[0] = '\0';
8340 481 : nc_type nAttype = NC_NAT;
8341 481 : size_t nAttlen = 0;
8342 481 : nc_inq_att(cdfid, NC_GLOBAL, "Conventions", &nAttype, &nAttlen);
8343 962 : if (nAttlen >= sizeof(szConventions) ||
8344 481 : nc_get_att_text(cdfid, NC_GLOBAL, "Conventions", szConventions) !=
8345 : NC_NOERR)
8346 : {
8347 56 : CPLDebug("GDAL_netCDF", "No UNIDATA NC_GLOBAL:Conventions attribute");
8348 : // Note that 'Conventions' is always capital 'C' in CF spec.
8349 : }
8350 : else
8351 : {
8352 425 : szConventions[nAttlen] = '\0';
8353 : }
8354 :
8355 : // Create band information objects.
8356 481 : CPLDebug("GDAL_netCDF", "var_count = %d", nvars);
8357 :
8358 : // Create a corresponding GDALDataset.
8359 : // Create Netcdf Subdataset if filename as NETCDF tag.
8360 481 : poDS->cdfid = cdfid;
8361 : #ifdef ENABLE_UFFD
8362 481 : poDS->pCtx = pCtx;
8363 : #endif
8364 481 : poDS->eAccess = poOpenInfo->eAccess;
8365 481 : poDS->bDefineMode = false;
8366 :
8367 481 : poDS->ReadAttributes(cdfid, NC_GLOBAL);
8368 :
8369 : // Identify coordinate and boundary variables that we should
8370 : // ignore as Raster Bands.
8371 481 : char **papszIgnoreVars = nullptr;
8372 481 : NCDFGetCoordAndBoundVarFullNames(cdfid, &papszIgnoreVars);
8373 : // Filter variables to keep only valid 2+D raster bands and vector fields.
8374 481 : int nRasterVars = 0;
8375 481 : int nIgnoredVars = 0;
8376 481 : int nGroupID = -1;
8377 481 : int nVarID = -1;
8378 :
8379 : std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>
8380 962 : oMap2DDimsToGroupAndVar;
8381 1111 : if ((poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
8382 149 : STARTS_WITH(CSLFetchNameValueDef(poDS->papszMetadata,
8383 : "NC_GLOBAL#mission_name", ""),
8384 1 : "Sentinel 3") &&
8385 1 : EQUAL(CSLFetchNameValueDef(poDS->papszMetadata,
8386 : "NC_GLOBAL#altimeter_sensor_name", ""),
8387 630 : "SRAL") &&
8388 1 : EQUAL(CSLFetchNameValueDef(poDS->papszMetadata,
8389 : "NC_GLOBAL#radiometer_sensor_name", ""),
8390 : "MWR"))
8391 : {
8392 1 : if (poDS->eAccess == GA_Update)
8393 : {
8394 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8395 : // deadlock with GDALDataset own mutex.
8396 0 : delete poDS;
8397 0 : return nullptr;
8398 : }
8399 1 : poDS->ProcessSentinel3_SRAL_MWR();
8400 : }
8401 : else
8402 : {
8403 480 : poDS->FilterVars(cdfid, (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0,
8404 628 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
8405 148 : !bHasSimpleGeometries,
8406 : papszIgnoreVars, &nRasterVars, &nGroupID, &nVarID,
8407 : &nIgnoredVars, oMap2DDimsToGroupAndVar);
8408 : }
8409 481 : CSLDestroy(papszIgnoreVars);
8410 :
8411 : // Case where there is no raster variable
8412 481 : if (nRasterVars == 0 && !bTreatAsSubdataset)
8413 : {
8414 119 : poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
8415 119 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8416 : // with GDALDataset own mutex.
8417 119 : poDS->TryLoadXML();
8418 : // If the dataset has been opened in raster mode only, exit
8419 119 : if ((poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0 &&
8420 9 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) == 0)
8421 : {
8422 4 : delete poDS;
8423 4 : poDS = nullptr;
8424 : }
8425 : // Otherwise if the dataset is opened in vector mode, that there is
8426 : // no vector layer and we are in read-only, exit too.
8427 115 : else if (poDS->GetLayerCount() == 0 &&
8428 123 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
8429 8 : poOpenInfo->eAccess == GA_ReadOnly)
8430 : {
8431 8 : delete poDS;
8432 8 : poDS = nullptr;
8433 : }
8434 119 : CPLAcquireMutex(hNCMutex, 1000.0);
8435 119 : return poDS;
8436 : }
8437 :
8438 : // We have more than one variable with 2 dimensions in the
8439 : // file, then treat this as a subdataset container dataset.
8440 362 : bool bSeveralVariablesAsBands = false;
8441 362 : if ((nRasterVars > 1) && !bTreatAsSubdataset)
8442 : {
8443 27 : if (CPLFetchBool(poOpenInfo->papszOpenOptions, "VARIABLES_AS_BANDS",
8444 33 : false) &&
8445 6 : oMap2DDimsToGroupAndVar.size() == 1)
8446 : {
8447 6 : std::tie(nGroupID, nVarID) =
8448 12 : oMap2DDimsToGroupAndVar.begin()->second.front();
8449 6 : bSeveralVariablesAsBands = true;
8450 : }
8451 : else
8452 : {
8453 21 : poDS->CreateSubDatasetList(cdfid);
8454 21 : poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
8455 21 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8456 : // deadlock with GDALDataset own mutex.
8457 21 : poDS->TryLoadXML();
8458 21 : CPLAcquireMutex(hNCMutex, 1000.0);
8459 21 : return poDS;
8460 : }
8461 : }
8462 :
8463 : // If we are not treating things as a subdataset, then capture
8464 : // the name of the single available variable as the subdataset.
8465 341 : if (!bTreatAsSubdataset)
8466 : {
8467 283 : char *pszVarName = nullptr;
8468 283 : NCDF_ERR(NCDFGetVarFullName(nGroupID, nVarID, &pszVarName));
8469 283 : osSubdatasetName = (pszVarName != nullptr ? pszVarName : "");
8470 283 : CPLFree(pszVarName);
8471 : }
8472 :
8473 : // We have ignored at least one variable, so we should report them
8474 : // as subdatasets for reference.
8475 341 : if (nIgnoredVars > 0 && !bTreatAsSubdataset)
8476 : {
8477 24 : CPLDebug("GDAL_netCDF",
8478 : "As %d variables were ignored, creating subdataset list "
8479 : "for reference. Variable #%d [%s] is the main variable",
8480 : nIgnoredVars, nVarID, osSubdatasetName.c_str());
8481 24 : poDS->CreateSubDatasetList(cdfid);
8482 : }
8483 :
8484 : // Open the NETCDF subdataset NETCDF:"filename":subdataset.
8485 341 : int var = -1;
8486 341 : NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &nGroupID, &var);
8487 : // Now we can forget the root cdfid and only use the selected group.
8488 341 : cdfid = nGroupID;
8489 341 : int nd = 0;
8490 341 : nc_inq_varndims(cdfid, var, &nd);
8491 :
8492 341 : poDS->m_anDimIds.resize(nd);
8493 :
8494 : // X, Y, Z position in array
8495 682 : std::vector<int> anBandDimPos(nd);
8496 :
8497 341 : nc_inq_vardimid(cdfid, var, poDS->m_anDimIds.data());
8498 :
8499 : // Check if somebody tried to pass a variable with less than 1D.
8500 341 : if (nd < 1)
8501 : {
8502 0 : CPLError(CE_Warning, CPLE_AppDefined,
8503 : "Variable has %d dimension(s) - not supported.", nd);
8504 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8505 : // with GDALDataset own mutex.
8506 0 : delete poDS;
8507 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8508 0 : return nullptr;
8509 : }
8510 :
8511 : // CF-1 Convention
8512 : //
8513 : // Dimensions to appear in the relative order T, then Z, then Y,
8514 : // then X to the file. All other dimensions should, whenever
8515 : // possible, be placed to the left of the spatiotemporal
8516 : // dimensions.
8517 :
8518 : // Verify that dimensions are in the {T,Z,Y,X} or {T,Z,Y,X} order
8519 : // Ideally we should detect for other ordering and act accordingly
8520 : // Only done if file has Conventions=CF-* and only prints warning
8521 : // To disable set GDAL_NETCDF_VERIFY_DIMS=NO and to use only
8522 : // attributes (not varnames) set GDAL_NETCDF_VERIFY_DIMS=STRICT
8523 : const bool bCheckDims =
8524 682 : CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES")) &&
8525 341 : STARTS_WITH_CI(szConventions, "CF");
8526 :
8527 341 : if (nd >= 2 && bCheckDims)
8528 : {
8529 258 : char szDimName1[NC_MAX_NAME + 1] = {};
8530 258 : char szDimName2[NC_MAX_NAME + 1] = {};
8531 258 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 1], szDimName1);
8532 258 : NCDF_ERR(status);
8533 258 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 2], szDimName2);
8534 258 : NCDF_ERR(status);
8535 414 : if (NCDFIsVarLongitude(cdfid, -1, szDimName1) == false &&
8536 156 : NCDFIsVarProjectionX(cdfid, -1, szDimName1) == false)
8537 : {
8538 4 : CPLError(CE_Warning, CPLE_AppDefined,
8539 : "dimension #%d (%s) is not a Longitude/X dimension.",
8540 : nd - 1, szDimName1);
8541 : }
8542 414 : if (NCDFIsVarLatitude(cdfid, -1, szDimName2) == false &&
8543 156 : NCDFIsVarProjectionY(cdfid, -1, szDimName2) == false)
8544 : {
8545 4 : CPLError(CE_Warning, CPLE_AppDefined,
8546 : "dimension #%d (%s) is not a Latitude/Y dimension.",
8547 : nd - 2, szDimName2);
8548 : }
8549 258 : if ((NCDFIsVarLongitude(cdfid, -1, szDimName2) ||
8550 260 : NCDFIsVarProjectionX(cdfid, -1, szDimName2)) &&
8551 2 : (NCDFIsVarLatitude(cdfid, -1, szDimName1) ||
8552 0 : NCDFIsVarProjectionY(cdfid, -1, szDimName1)))
8553 : {
8554 2 : poDS->bSwitchedXY = true;
8555 : }
8556 258 : if (nd >= 3)
8557 : {
8558 48 : char szDimName3[NC_MAX_NAME + 1] = {};
8559 : status =
8560 48 : nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 3], szDimName3);
8561 48 : NCDF_ERR(status);
8562 48 : if (nd >= 4)
8563 : {
8564 13 : char szDimName4[NC_MAX_NAME + 1] = {};
8565 : status =
8566 13 : nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 4], szDimName4);
8567 13 : NCDF_ERR(status);
8568 13 : if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false)
8569 : {
8570 0 : CPLError(CE_Warning, CPLE_AppDefined,
8571 : "dimension #%d (%s) is not a Time dimension.",
8572 : nd - 3, szDimName3);
8573 : }
8574 13 : if (NCDFIsVarTimeCoord(cdfid, -1, szDimName4) == false)
8575 : {
8576 0 : CPLError(CE_Warning, CPLE_AppDefined,
8577 : "dimension #%d (%s) is not a Time dimension.",
8578 : nd - 4, szDimName4);
8579 : }
8580 : }
8581 : else
8582 : {
8583 67 : if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false &&
8584 32 : NCDFIsVarTimeCoord(cdfid, -1, szDimName3) == false)
8585 : {
8586 0 : CPLError(CE_Warning, CPLE_AppDefined,
8587 : "dimension #%d (%s) is not a "
8588 : "Time or Vertical dimension.",
8589 : nd - 3, szDimName3);
8590 : }
8591 : }
8592 : }
8593 : }
8594 :
8595 : // For example for EMIT data (https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/),
8596 : // dimension order is downtrack, crosstrack, bands
8597 341 : bool bYXBandOrder = false;
8598 341 : if (nd == 3)
8599 : {
8600 40 : char szDimName[NC_MAX_NAME + 1] = {};
8601 40 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[2], szDimName);
8602 40 : NCDF_ERR(status);
8603 40 : bYXBandOrder =
8604 40 : strcmp(szDimName, "bands") == 0 || strcmp(szDimName, "band") == 0;
8605 : }
8606 :
8607 : // Get X dimensions information.
8608 : size_t xdim;
8609 341 : poDS->nXDimID = poDS->m_anDimIds[bYXBandOrder ? 1 : nd - 1];
8610 341 : nc_inq_dimlen(cdfid, poDS->nXDimID, &xdim);
8611 :
8612 : // Get Y dimension information.
8613 : size_t ydim;
8614 341 : if (nd >= 2)
8615 : {
8616 337 : poDS->nYDimID = poDS->m_anDimIds[bYXBandOrder ? 0 : nd - 2];
8617 337 : nc_inq_dimlen(cdfid, poDS->nYDimID, &ydim);
8618 : }
8619 : else
8620 : {
8621 4 : poDS->nYDimID = -1;
8622 4 : ydim = 1;
8623 : }
8624 :
8625 341 : if (xdim > INT_MAX || ydim > INT_MAX)
8626 : {
8627 0 : CPLError(CE_Failure, CPLE_AppDefined,
8628 : "Invalid raster dimensions: " CPL_FRMT_GUIB "x" CPL_FRMT_GUIB,
8629 : static_cast<GUIntBig>(xdim), static_cast<GUIntBig>(ydim));
8630 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8631 : // with GDALDataset own mutex.
8632 0 : delete poDS;
8633 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8634 0 : return nullptr;
8635 : }
8636 :
8637 341 : poDS->nRasterXSize = static_cast<int>(xdim);
8638 341 : poDS->nRasterYSize = static_cast<int>(ydim);
8639 :
8640 341 : unsigned int k = 0;
8641 1092 : for (int j = 0; j < nd; j++)
8642 : {
8643 751 : if (poDS->m_anDimIds[j] == poDS->nXDimID)
8644 : {
8645 341 : anBandDimPos[0] = j; // Save Position of XDim
8646 341 : k++;
8647 : }
8648 751 : if (poDS->m_anDimIds[j] == poDS->nYDimID)
8649 : {
8650 337 : anBandDimPos[1] = j; // Save Position of YDim
8651 337 : k++;
8652 : }
8653 : }
8654 : // X and Y Dimension Ids were not found!
8655 341 : if ((nd >= 2 && k != 2) || (nd == 1 && k != 1))
8656 : {
8657 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8658 : // with GDALDataset own mutex.
8659 0 : delete poDS;
8660 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8661 0 : return nullptr;
8662 : }
8663 :
8664 : // Read Metadata for this variable.
8665 :
8666 : // Should disable as is also done at band level, except driver needs the
8667 : // variables as metadata (e.g. projection).
8668 341 : poDS->ReadAttributes(cdfid, var);
8669 :
8670 : // Read Metadata for each dimension.
8671 341 : int *panDimIds = nullptr;
8672 341 : NCDFGetVisibleDims(cdfid, &ndims, &panDimIds);
8673 : // With NetCDF-4 groups panDimIds is not always [0..dim_count-1] like
8674 : // in NetCDF-3 because we see only the dimensions of the selected group
8675 : // and its parents.
8676 : // poDS->papszDimName is indexed by dim IDs, so it must contains all IDs
8677 : // [0..max(panDimIds)], but they are not all useful so we fill names
8678 : // of useless dims with empty string.
8679 341 : if (panDimIds)
8680 : {
8681 341 : const int nMaxDimId = *std::max_element(panDimIds, panDimIds + ndims);
8682 341 : std::set<int> oSetExistingDimIds;
8683 1128 : for (int i = 0; i < ndims; i++)
8684 : {
8685 787 : oSetExistingDimIds.insert(panDimIds[i]);
8686 : }
8687 341 : std::set<int> oSetDimIdsUsedByVar;
8688 1092 : for (int i = 0; i < nd; i++)
8689 : {
8690 751 : oSetDimIdsUsedByVar.insert(poDS->m_anDimIds[i]);
8691 : }
8692 1130 : for (int j = 0; j <= nMaxDimId; j++)
8693 : {
8694 : // Is j dim used?
8695 789 : if (oSetExistingDimIds.find(j) != oSetExistingDimIds.end())
8696 : {
8697 : // Useful dim.
8698 787 : char szTemp[NC_MAX_NAME + 1] = {};
8699 787 : status = nc_inq_dimname(cdfid, j, szTemp);
8700 787 : if (status != NC_NOERR)
8701 : {
8702 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8703 : // deadlock with GDALDataset own
8704 : // mutex.
8705 0 : delete poDS;
8706 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8707 0 : return nullptr;
8708 : }
8709 787 : poDS->papszDimName.AddString(szTemp);
8710 :
8711 787 : if (oSetDimIdsUsedByVar.find(j) != oSetDimIdsUsedByVar.end())
8712 : {
8713 751 : int nDimGroupId = -1;
8714 751 : int nDimVarId = -1;
8715 751 : if (NCDFResolveVar(cdfid, poDS->papszDimName[j],
8716 751 : &nDimGroupId, &nDimVarId) == CE_None)
8717 : {
8718 555 : poDS->ReadAttributes(nDimGroupId, nDimVarId);
8719 : }
8720 : }
8721 : }
8722 : else
8723 : {
8724 : // Useless dim.
8725 2 : poDS->papszDimName.AddString("");
8726 : }
8727 : }
8728 341 : CPLFree(panDimIds);
8729 : }
8730 :
8731 : // Set projection info.
8732 682 : std::vector<std::string> aosRemovedMDItems;
8733 341 : if (nd > 1)
8734 : {
8735 337 : poDS->SetProjectionFromVar(cdfid, var,
8736 : /*bReadSRSOnly=*/false,
8737 : /* pszGivenGM = */ nullptr,
8738 : /* returnProjStr = */ nullptr,
8739 : /* sg = */ nullptr, &aosRemovedMDItems);
8740 : }
8741 :
8742 : // Override bottom-up with GDAL_NETCDF_BOTTOMUP config option.
8743 341 : const char *pszValue = CPLGetConfigOption("GDAL_NETCDF_BOTTOMUP", nullptr);
8744 341 : if (pszValue)
8745 : {
8746 24 : poDS->bBottomUp = CPLTestBool(pszValue);
8747 24 : CPLDebug("GDAL_netCDF",
8748 : "set bBottomUp=%d because GDAL_NETCDF_BOTTOMUP=%s",
8749 24 : static_cast<int>(poDS->bBottomUp), pszValue);
8750 : }
8751 :
8752 : // Save non-spatial dimension info.
8753 :
8754 341 : int *panBandZLev = nullptr;
8755 341 : int nDim = (nd >= 2) ? 2 : 1;
8756 : size_t lev_count;
8757 341 : size_t nTotLevCount = 1;
8758 341 : nc_type nType = NC_NAT;
8759 :
8760 682 : CPLString osExtraDimNames;
8761 :
8762 341 : if (nd > 2)
8763 : {
8764 55 : nDim = 2;
8765 55 : panBandZLev = static_cast<int *>(CPLCalloc(nd - 2, sizeof(int)));
8766 :
8767 55 : osExtraDimNames = "{";
8768 :
8769 55 : char szDimName[NC_MAX_NAME + 1] = {};
8770 :
8771 238 : for (int j = 0; j < nd; j++)
8772 : {
8773 311 : if ((poDS->m_anDimIds[j] != poDS->nXDimID) &&
8774 128 : (poDS->m_anDimIds[j] != poDS->nYDimID))
8775 : {
8776 73 : nc_inq_dimlen(cdfid, poDS->m_anDimIds[j], &lev_count);
8777 73 : nTotLevCount *= lev_count;
8778 73 : panBandZLev[nDim - 2] = static_cast<int>(lev_count);
8779 73 : anBandDimPos[nDim] = j; // Save Position of ZDim
8780 : // Save non-spatial dimension names.
8781 73 : if (nc_inq_dimname(cdfid, poDS->m_anDimIds[j], szDimName) ==
8782 : NC_NOERR)
8783 : {
8784 73 : osExtraDimNames += szDimName;
8785 73 : if (j < nd - 3)
8786 : {
8787 18 : osExtraDimNames += ",";
8788 : }
8789 :
8790 73 : int nIdxGroupID = -1;
8791 73 : int nIdxVarID = Get1DVariableIndexedByDimension(
8792 73 : cdfid, poDS->m_anDimIds[j], szDimName, true,
8793 73 : &nIdxGroupID);
8794 73 : poDS->m_anExtraDimGroupIds.push_back(nIdxGroupID);
8795 73 : poDS->m_anExtraDimVarIds.push_back(nIdxVarID);
8796 :
8797 73 : if (nIdxVarID >= 0)
8798 : {
8799 64 : nc_inq_vartype(nIdxGroupID, nIdxVarID, &nType);
8800 : char szExtraDimDef[NC_MAX_NAME + 1];
8801 64 : snprintf(szExtraDimDef, sizeof(szExtraDimDef),
8802 : "{%ld,%d}", (long)lev_count, nType);
8803 : char szTemp[NC_MAX_NAME + 32 + 1];
8804 64 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
8805 : szDimName);
8806 64 : poDS->papszMetadata = CSLSetNameValue(
8807 : poDS->papszMetadata, szTemp, szExtraDimDef);
8808 :
8809 : // Retrieving data for unlimited dimensions might be
8810 : // costly on network storage, so don't do it.
8811 : // Each band will capture the value along the extra
8812 : // dimension in its NETCDF_DIM_xxxx band metadata item
8813 : // Addresses use case of
8814 : // https://lists.osgeo.org/pipermail/gdal-dev/2023-May/057209.html
8815 64 : if (VSIIsLocal(osFilenameForNCOpen.c_str()) ||
8816 0 : !NCDFIsUnlimitedDim(poDS->eFormat ==
8817 : NCDF_FORMAT_NC4,
8818 0 : cdfid, poDS->m_anDimIds[j]))
8819 : {
8820 64 : char *pszTemp = nullptr;
8821 64 : if (NCDFGet1DVar(nIdxGroupID, nIdxVarID,
8822 64 : &pszTemp) == CE_None)
8823 : {
8824 64 : snprintf(szTemp, sizeof(szTemp),
8825 : "NETCDF_DIM_%s_VALUES", szDimName);
8826 64 : poDS->papszMetadata = CSLSetNameValue(
8827 : poDS->papszMetadata, szTemp, pszTemp);
8828 64 : CPLFree(pszTemp);
8829 : }
8830 : }
8831 : }
8832 : }
8833 : else
8834 : {
8835 0 : poDS->m_anExtraDimGroupIds.push_back(-1);
8836 0 : poDS->m_anExtraDimVarIds.push_back(-1);
8837 : }
8838 :
8839 73 : nDim++;
8840 : }
8841 : }
8842 55 : osExtraDimNames += "}";
8843 55 : poDS->papszMetadata = CSLSetNameValue(
8844 : poDS->papszMetadata, "NETCDF_DIM_EXTRA", osExtraDimNames);
8845 : }
8846 :
8847 : // Store Metadata.
8848 351 : for (const auto &osStr : aosRemovedMDItems)
8849 10 : poDS->papszMetadata =
8850 10 : CSLSetNameValue(poDS->papszMetadata, osStr.c_str(), nullptr);
8851 :
8852 341 : poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
8853 :
8854 : // Create bands.
8855 :
8856 : // Arbitrary threshold.
8857 : int nMaxBandCount =
8858 341 : atoi(CPLGetConfigOption("GDAL_MAX_BAND_COUNT", "32768"));
8859 341 : if (nMaxBandCount <= 0)
8860 0 : nMaxBandCount = 32768;
8861 341 : if (nTotLevCount > static_cast<unsigned int>(nMaxBandCount))
8862 : {
8863 0 : CPLError(CE_Warning, CPLE_AppDefined,
8864 : "Limiting number of bands to %d instead of %u", nMaxBandCount,
8865 : static_cast<unsigned int>(nTotLevCount));
8866 0 : nTotLevCount = static_cast<unsigned int>(nMaxBandCount);
8867 : }
8868 341 : if (poDS->nRasterXSize == 0 || poDS->nRasterYSize == 0)
8869 : {
8870 0 : poDS->nRasterXSize = 0;
8871 0 : poDS->nRasterYSize = 0;
8872 0 : nTotLevCount = 0;
8873 0 : if (poDS->GetLayerCount() == 0)
8874 : {
8875 0 : CPLFree(panBandZLev);
8876 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8877 : // deadlock with GDALDataset own mutex.
8878 0 : delete poDS;
8879 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8880 0 : return nullptr;
8881 : }
8882 : }
8883 341 : if (bSeveralVariablesAsBands)
8884 : {
8885 6 : const auto &listVariables = oMap2DDimsToGroupAndVar.begin()->second;
8886 24 : for (int iBand = 0; iBand < static_cast<int>(listVariables.size());
8887 : ++iBand)
8888 : {
8889 18 : int bandVarGroupId = listVariables[iBand].first;
8890 18 : int bandVarId = listVariables[iBand].second;
8891 : netCDFRasterBand *poBand = new netCDFRasterBand(
8892 0 : netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, bandVarGroupId,
8893 18 : bandVarId, nDim, 0, nullptr, anBandDimPos.data(), iBand + 1);
8894 18 : poDS->SetBand(iBand + 1, poBand);
8895 : }
8896 : }
8897 : else
8898 : {
8899 774 : for (unsigned int lev = 0; lev < nTotLevCount; lev++)
8900 : {
8901 : netCDFRasterBand *poBand = new netCDFRasterBand(
8902 0 : netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, cdfid, var, nDim,
8903 439 : lev, panBandZLev, anBandDimPos.data(), lev + 1);
8904 439 : poDS->SetBand(lev + 1, poBand);
8905 : }
8906 : }
8907 :
8908 341 : if (panBandZLev)
8909 55 : CPLFree(panBandZLev);
8910 : // Handle angular geographic coordinates here
8911 :
8912 : // Initialize any PAM information.
8913 341 : if (bTreatAsSubdataset)
8914 : {
8915 58 : poDS->SetPhysicalFilename(poDS->osFilename);
8916 58 : poDS->SetSubdatasetName(osSubdatasetName);
8917 : }
8918 :
8919 341 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
8920 : // GDALDataset own mutex.
8921 341 : poDS->TryLoadXML();
8922 :
8923 341 : if (bTreatAsSubdataset)
8924 58 : poDS->oOvManager.Initialize(poDS, ":::VIRTUAL:::");
8925 : else
8926 283 : poDS->oOvManager.Initialize(poDS, poDS->osFilename);
8927 :
8928 341 : CPLAcquireMutex(hNCMutex, 1000.0);
8929 :
8930 341 : return poDS;
8931 : }
8932 :
8933 : /************************************************************************/
8934 : /* CopyMetadata() */
8935 : /* */
8936 : /* Create a copy of metadata for NC_GLOBAL or a variable */
8937 : /************************************************************************/
8938 :
8939 151 : static void CopyMetadata(GDALDataset *poSrcDS, GDALRasterBand *poSrcBand,
8940 : GDALRasterBand *poDstBand, int nCdfId, int CDFVarID,
8941 : const char *pszPrefix)
8942 : {
8943 : // Remove the following band meta but set them later from band data.
8944 151 : const char *const papszIgnoreBand[] = {
8945 : CF_ADD_OFFSET, CF_SCALE_FACTOR, "valid_range", "_Unsigned",
8946 : NCDF_FillValue, "coordinates", nullptr};
8947 151 : const char *const papszIgnoreGlobal[] = {"NETCDF_DIM_EXTRA", nullptr};
8948 :
8949 151 : CSLConstList papszMetadata = nullptr;
8950 151 : if (poSrcDS)
8951 : {
8952 63 : papszMetadata = poSrcDS->GetMetadata();
8953 : }
8954 88 : else if (poSrcBand)
8955 : {
8956 88 : papszMetadata = poSrcBand->GetMetadata();
8957 : }
8958 :
8959 630 : for (const auto &[pszKey, pszValue] : cpl::IterateNameValue(papszMetadata))
8960 : {
8961 : #ifdef NCDF_DEBUG
8962 : CPLDebug("GDAL_netCDF", "copy metadata [%s]=[%s]", pszKey, pszValue);
8963 : #endif
8964 :
8965 479 : CPLString osMetaName(pszKey);
8966 :
8967 : // Check for items that match pszPrefix if applicable.
8968 479 : if (pszPrefix && !EQUAL(pszPrefix, ""))
8969 : {
8970 : // Remove prefix.
8971 115 : if (STARTS_WITH(osMetaName.c_str(), pszPrefix))
8972 : {
8973 17 : osMetaName = osMetaName.substr(strlen(pszPrefix));
8974 : }
8975 : // Only copy items that match prefix.
8976 : else
8977 : {
8978 98 : continue;
8979 : }
8980 : }
8981 :
8982 : // Fix various issues with metadata translation.
8983 381 : if (CDFVarID == NC_GLOBAL)
8984 : {
8985 : // Do not copy items in papszIgnoreGlobal and NETCDF_DIM_*.
8986 479 : if ((CSLFindString(papszIgnoreGlobal, osMetaName) != -1) ||
8987 237 : (STARTS_WITH(osMetaName, "NETCDF_DIM_")))
8988 21 : continue;
8989 : // Remove NC_GLOBAL prefix for netcdf global Metadata.
8990 221 : else if (STARTS_WITH(osMetaName, "NC_GLOBAL#"))
8991 : {
8992 33 : osMetaName = osMetaName.substr(strlen("NC_GLOBAL#"));
8993 : }
8994 : // GDAL Metadata renamed as GDAL-[meta].
8995 188 : else if (strstr(osMetaName, "#") == nullptr)
8996 : {
8997 15 : osMetaName = "GDAL_" + osMetaName;
8998 : }
8999 : // Keep time, lev and depth information for safe-keeping.
9000 : // Time and vertical coordinate handling need improvements.
9001 : /*
9002 : else if( STARTS_WITH(szMetaName, "time#") )
9003 : {
9004 : szMetaName[4] = '-';
9005 : }
9006 : else if( STARTS_WITH(szMetaName, "lev#") )
9007 : {
9008 : szMetaName[3] = '-';
9009 : }
9010 : else if( STARTS_WITH(szMetaName, "depth#") )
9011 : {
9012 : szMetaName[5] = '-';
9013 : }
9014 : */
9015 : // Only copy data without # (previously all data was copied).
9016 221 : if (strstr(osMetaName, "#") != nullptr)
9017 173 : continue;
9018 : // netCDF attributes do not like the '#' character.
9019 : // for( unsigned int h=0; h < strlen(szMetaName) -1 ; h++ ) {
9020 : // if( szMetaName[h] == '#') szMetaName[h] = '-';
9021 : // }
9022 : }
9023 : else
9024 : {
9025 : // Do not copy varname, stats, NETCDF_DIM_*, nodata
9026 : // and items in papszIgnoreBand.
9027 139 : if (STARTS_WITH(osMetaName, "NETCDF_VARNAME") ||
9028 107 : STARTS_WITH(osMetaName, "STATISTICS_") ||
9029 107 : STARTS_WITH(osMetaName, "NETCDF_DIM_") ||
9030 74 : STARTS_WITH(osMetaName, "missing_value") ||
9031 293 : STARTS_WITH(osMetaName, "_FillValue") ||
9032 47 : CSLFindString(papszIgnoreBand, osMetaName) != -1)
9033 97 : continue;
9034 : }
9035 :
9036 : #ifdef NCDF_DEBUG
9037 : CPLDebug("GDAL_netCDF", "copy name=[%s] value=[%s]", osMetaName.c_str(),
9038 : pszValue);
9039 : #endif
9040 90 : if (NCDFPutAttr(nCdfId, CDFVarID, osMetaName, pszValue) != CE_None)
9041 : {
9042 0 : CPLDebug("GDAL_netCDF", "NCDFPutAttr(%d, %d, %s, %s) failed",
9043 : nCdfId, CDFVarID, osMetaName.c_str(), pszValue);
9044 : }
9045 : }
9046 :
9047 : // Set add_offset and scale_factor here if present.
9048 151 : if (poSrcBand && poDstBand)
9049 : {
9050 :
9051 88 : int bGotAddOffset = FALSE;
9052 88 : const double dfAddOffset = poSrcBand->GetOffset(&bGotAddOffset);
9053 88 : int bGotScale = FALSE;
9054 88 : const double dfScale = poSrcBand->GetScale(&bGotScale);
9055 :
9056 88 : if (bGotAddOffset && dfAddOffset != 0.0)
9057 1 : poDstBand->SetOffset(dfAddOffset);
9058 88 : if (bGotScale && dfScale != 1.0)
9059 1 : poDstBand->SetScale(dfScale);
9060 : }
9061 151 : }
9062 :
9063 : /************************************************************************/
9064 : /* CreateLL() */
9065 : /* */
9066 : /* Shared functionality between netCDFDataset::Create() and */
9067 : /* netCDF::CreateCopy() for creating netcdf file based on a set of */
9068 : /* options and a configuration. */
9069 : /************************************************************************/
9070 :
9071 195 : netCDFDataset *netCDFDataset::CreateLL(const char *pszFilename, int nXSize,
9072 : int nYSize, int nBandsIn,
9073 : char **papszOptions)
9074 : {
9075 195 : if (!((nXSize == 0 && nYSize == 0 && nBandsIn == 0) ||
9076 123 : (nXSize > 0 && nYSize > 0 && nBandsIn > 0)))
9077 : {
9078 1 : return nullptr;
9079 : }
9080 :
9081 194 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
9082 : // GDALDataset own mutex.
9083 194 : netCDFDataset *poDS = new netCDFDataset();
9084 194 : CPLAcquireMutex(hNCMutex, 1000.0);
9085 :
9086 194 : poDS->nRasterXSize = nXSize;
9087 194 : poDS->nRasterYSize = nYSize;
9088 194 : poDS->eAccess = GA_Update;
9089 194 : poDS->osFilename = pszFilename;
9090 :
9091 : // From gtiff driver, is this ok?
9092 : /*
9093 : poDS->nBlockXSize = nXSize;
9094 : poDS->nBlockYSize = 1;
9095 : poDS->nBlocksPerBand =
9096 : ((nYSize + poDS->nBlockYSize - 1) / poDS->nBlockYSize)
9097 : * ((nXSize + poDS->nBlockXSize - 1) / poDS->nBlockXSize);
9098 : */
9099 :
9100 : // process options.
9101 194 : poDS->papszCreationOptions = CSLDuplicate(papszOptions);
9102 194 : poDS->ProcessCreationOptions();
9103 :
9104 194 : if (poDS->eMultipleLayerBehavior == SEPARATE_FILES)
9105 : {
9106 : VSIStatBuf sStat;
9107 2 : if (VSIStat(pszFilename, &sStat) == 0)
9108 : {
9109 0 : if (!VSI_ISDIR(sStat.st_mode))
9110 : {
9111 0 : CPLError(CE_Failure, CPLE_FileIO,
9112 : "%s is an existing file, but not a directory",
9113 : pszFilename);
9114 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9115 : // deadlock with GDALDataset own
9116 : // mutex.
9117 0 : delete poDS;
9118 0 : CPLAcquireMutex(hNCMutex, 1000.0);
9119 0 : return nullptr;
9120 : }
9121 : }
9122 2 : else if (VSIMkdir(pszFilename, 0755) != 0)
9123 : {
9124 1 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s directory",
9125 : pszFilename);
9126 1 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9127 : // deadlock with GDALDataset own mutex.
9128 1 : delete poDS;
9129 1 : CPLAcquireMutex(hNCMutex, 1000.0);
9130 1 : return nullptr;
9131 : }
9132 :
9133 1 : return poDS;
9134 : }
9135 : // Create the dataset.
9136 384 : CPLString osFilenameForNCCreate(pszFilename);
9137 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
9138 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
9139 : {
9140 : char *pszTemp =
9141 : CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
9142 : osFilenameForNCCreate = pszTemp;
9143 : CPLFree(pszTemp);
9144 : }
9145 : #endif
9146 :
9147 : #if defined(_WIN32)
9148 : {
9149 : // Works around bug of msys2 netCDF 4.9.0 package where nc_create()
9150 : // crashes
9151 : VSIStatBuf sStat;
9152 : const char *pszDir = CPLGetDirname(osFilenameForNCCreate.c_str());
9153 : if (VSIStat(pszDir, &sStat) != 0)
9154 : {
9155 : CPLError(CE_Failure, CPLE_OpenFailed,
9156 : "Unable to create netCDF file %s: non existing output "
9157 : "directory",
9158 : pszFilename);
9159 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9160 : // deadlock with GDALDataset own mutex.
9161 : delete poDS;
9162 : CPLAcquireMutex(hNCMutex, 1000.0);
9163 : return nullptr;
9164 : }
9165 : }
9166 : #endif
9167 :
9168 : int status =
9169 192 : nc_create(osFilenameForNCCreate, poDS->nCreateMode, &(poDS->cdfid));
9170 :
9171 : // Put into define mode.
9172 192 : poDS->SetDefineMode(true);
9173 :
9174 192 : if (status != NC_NOERR)
9175 : {
9176 30 : CPLError(CE_Failure, CPLE_OpenFailed,
9177 : "Unable to create netCDF file %s (Error code %d): %s .",
9178 : pszFilename, status, nc_strerror(status));
9179 30 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
9180 : // with GDALDataset own mutex.
9181 30 : delete poDS;
9182 30 : CPLAcquireMutex(hNCMutex, 1000.0);
9183 30 : return nullptr;
9184 : }
9185 :
9186 : // Define dimensions.
9187 162 : if (nXSize > 0 && nYSize > 0)
9188 : {
9189 109 : poDS->papszDimName.AddString(NCDF_DIMNAME_X);
9190 : status =
9191 109 : nc_def_dim(poDS->cdfid, NCDF_DIMNAME_X, nXSize, &(poDS->nXDimID));
9192 109 : NCDF_ERR(status);
9193 109 : CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
9194 : poDS->cdfid, NCDF_DIMNAME_X, nXSize, poDS->nXDimID);
9195 :
9196 109 : poDS->papszDimName.AddString(NCDF_DIMNAME_Y);
9197 : status =
9198 109 : nc_def_dim(poDS->cdfid, NCDF_DIMNAME_Y, nYSize, &(poDS->nYDimID));
9199 109 : NCDF_ERR(status);
9200 109 : CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
9201 : poDS->cdfid, NCDF_DIMNAME_Y, nYSize, poDS->nYDimID);
9202 : }
9203 :
9204 162 : return poDS;
9205 : }
9206 :
9207 : /************************************************************************/
9208 : /* Create() */
9209 : /************************************************************************/
9210 :
9211 126 : GDALDataset *netCDFDataset::Create(const char *pszFilename, int nXSize,
9212 : int nYSize, int nBandsIn, GDALDataType eType,
9213 : char **papszOptions)
9214 : {
9215 126 : CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::Create(%s, ...)",
9216 : pszFilename);
9217 :
9218 : const char *legacyCreationOp =
9219 126 : CSLFetchNameValueDef(papszOptions, "GEOMETRY_ENCODING", "CF_1.8");
9220 252 : std::string legacyCreationOp_s = std::string(legacyCreationOp);
9221 :
9222 : // Check legacy creation op FIRST
9223 :
9224 126 : bool legacyCreateMode = false;
9225 :
9226 126 : if (nXSize != 0 || nYSize != 0 || nBandsIn != 0)
9227 : {
9228 56 : legacyCreateMode = true;
9229 : }
9230 70 : else if (legacyCreationOp_s == "CF_1.8")
9231 : {
9232 54 : legacyCreateMode = false;
9233 : }
9234 :
9235 16 : else if (legacyCreationOp_s == "WKT")
9236 : {
9237 16 : legacyCreateMode = true;
9238 : }
9239 :
9240 : else
9241 : {
9242 0 : CPLError(
9243 : CE_Failure, CPLE_NotSupported,
9244 : "Dataset creation option GEOMETRY_ENCODING=%s is not supported.",
9245 : legacyCreationOp_s.c_str());
9246 0 : return nullptr;
9247 : }
9248 :
9249 252 : CPLStringList aosOptions(CSLDuplicate(papszOptions));
9250 238 : if (aosOptions.FetchNameValue("FORMAT") == nullptr &&
9251 112 : (eType == GDT_UInt16 || eType == GDT_UInt32 || eType == GDT_UInt64 ||
9252 : eType == GDT_Int64))
9253 : {
9254 10 : CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
9255 10 : aosOptions.SetNameValue("FORMAT", "NC4");
9256 : }
9257 :
9258 252 : CPLStringList aosBandNames;
9259 126 : if (const char *pszBandNames = aosOptions.FetchNameValue("BAND_NAMES"))
9260 : {
9261 : aosBandNames =
9262 2 : CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);
9263 :
9264 2 : if (aosBandNames.Count() != nBandsIn)
9265 : {
9266 1 : CPLError(CE_Failure, CPLE_OpenFailed,
9267 : "Attempted to create netCDF with %d bands but %d names "
9268 : "provided in BAND_NAMES.",
9269 : nBandsIn, aosBandNames.Count());
9270 :
9271 1 : return nullptr;
9272 : }
9273 : }
9274 :
9275 250 : CPLMutexHolderD(&hNCMutex);
9276 :
9277 125 : auto poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize, nBandsIn,
9278 : aosOptions.List());
9279 :
9280 125 : if (!poDS)
9281 19 : return nullptr;
9282 :
9283 106 : if (!legacyCreateMode)
9284 : {
9285 37 : poDS->bSGSupport = true;
9286 37 : poDS->vcdf.enableFullVirtualMode();
9287 : }
9288 :
9289 : else
9290 : {
9291 69 : poDS->bSGSupport = false;
9292 : }
9293 :
9294 : // Should we write signed or unsigned byte?
9295 : // TODO should this only be done in Create()
9296 106 : poDS->bSignedData = true;
9297 106 : const char *pszValue = CSLFetchNameValueDef(papszOptions, "PIXELTYPE", "");
9298 106 : if (eType == GDT_Byte && !EQUAL(pszValue, "SIGNEDBYTE"))
9299 15 : poDS->bSignedData = false;
9300 :
9301 : // Add Conventions, GDAL info and history.
9302 106 : if (poDS->cdfid >= 0)
9303 : {
9304 : const char *CF_Vector_Conv =
9305 173 : poDS->bSGSupport ||
9306 : // Use of variable length strings require CF-1.8
9307 68 : EQUAL(aosOptions.FetchNameValueDef("FORMAT", ""), "NC4")
9308 : ? NCDF_CONVENTIONS_CF_V1_8
9309 173 : : NCDF_CONVENTIONS_CF_V1_6;
9310 105 : poDS->bWriteGDALVersion = CPLTestBool(
9311 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
9312 105 : poDS->bWriteGDALHistory = CPLTestBool(
9313 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
9314 105 : NCDFAddGDALHistory(poDS->cdfid, pszFilename, poDS->bWriteGDALVersion,
9315 105 : poDS->bWriteGDALHistory, "", "Create",
9316 : (nBandsIn == 0) ? CF_Vector_Conv
9317 : : GDAL_DEFAULT_NCDF_CONVENTIONS);
9318 : }
9319 :
9320 : // Define bands.
9321 197 : for (int iBand = 1; iBand <= nBandsIn; iBand++)
9322 : {
9323 : const char *pszBandName =
9324 91 : aosBandNames.empty() ? nullptr : aosBandNames[iBand - 1];
9325 :
9326 91 : poDS->SetBand(iBand, new netCDFRasterBand(
9327 91 : netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS,
9328 91 : eType, iBand, poDS->bSignedData, pszBandName));
9329 : }
9330 :
9331 106 : CPLDebug("GDAL_netCDF", "netCDFDataset::Create(%s, ...) done", pszFilename);
9332 : // Return same dataset.
9333 106 : return poDS;
9334 : }
9335 :
9336 : template <class T>
9337 88 : static CPLErr NCDFCopyBand(GDALRasterBand *poSrcBand, GDALRasterBand *poDstBand,
9338 : int nXSize, int nYSize, GDALProgressFunc pfnProgress,
9339 : void *pProgressData)
9340 : {
9341 88 : GDALDataType eDT = poSrcBand->GetRasterDataType();
9342 88 : CPLErr eErr = CE_None;
9343 88 : T *patScanline = static_cast<T *>(CPLMalloc(nXSize * sizeof(T)));
9344 :
9345 2557 : for (int iLine = 0; iLine < nYSize && eErr == CE_None; iLine++)
9346 : {
9347 2469 : eErr = poSrcBand->RasterIO(GF_Read, 0, iLine, nXSize, 1, patScanline,
9348 : nXSize, 1, eDT, 0, 0, nullptr);
9349 2469 : if (eErr != CE_None)
9350 : {
9351 0 : CPLDebug(
9352 : "GDAL_netCDF",
9353 : "NCDFCopyBand(), poSrcBand->RasterIO() returned error code %d",
9354 : eErr);
9355 : }
9356 : else
9357 : {
9358 2469 : eErr =
9359 : poDstBand->RasterIO(GF_Write, 0, iLine, nXSize, 1, patScanline,
9360 : nXSize, 1, eDT, 0, 0, nullptr);
9361 2469 : if (eErr != CE_None)
9362 0 : CPLDebug("GDAL_netCDF",
9363 : "NCDFCopyBand(), poDstBand->RasterIO() returned error "
9364 : "code %d",
9365 : eErr);
9366 : }
9367 :
9368 2469 : if (nYSize > 10 && (iLine % (nYSize / 10) == 1))
9369 : {
9370 246 : if (!pfnProgress(1.0 * iLine / nYSize, nullptr, pProgressData))
9371 : {
9372 0 : eErr = CE_Failure;
9373 0 : CPLError(CE_Failure, CPLE_UserInterrupt,
9374 : "User terminated CreateCopy()");
9375 : }
9376 : }
9377 : }
9378 :
9379 88 : CPLFree(patScanline);
9380 :
9381 88 : pfnProgress(1.0, nullptr, pProgressData);
9382 :
9383 88 : return eErr;
9384 : }
9385 :
9386 : /************************************************************************/
9387 : /* CreateCopy() */
9388 : /************************************************************************/
9389 :
9390 : GDALDataset *
9391 79 : netCDFDataset::CreateCopy(const char *pszFilename, GDALDataset *poSrcDS,
9392 : CPL_UNUSED int bStrict, char **papszOptions,
9393 : GDALProgressFunc pfnProgress, void *pProgressData)
9394 : {
9395 158 : CPLMutexHolderD(&hNCMutex);
9396 :
9397 79 : CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::CreateCopy(%s, ...)",
9398 : pszFilename);
9399 :
9400 79 : if (poSrcDS->GetRootGroup())
9401 : {
9402 5 : auto poDrv = GDALDriver::FromHandle(GDALGetDriverByName("netCDF"));
9403 5 : if (poDrv)
9404 : {
9405 5 : return poDrv->DefaultCreateCopy(pszFilename, poSrcDS, bStrict,
9406 : papszOptions, pfnProgress,
9407 5 : pProgressData);
9408 : }
9409 : }
9410 :
9411 74 : const int nBands = poSrcDS->GetRasterCount();
9412 74 : const int nXSize = poSrcDS->GetRasterXSize();
9413 74 : const int nYSize = poSrcDS->GetRasterYSize();
9414 74 : const char *pszWKT = poSrcDS->GetProjectionRef();
9415 :
9416 : // Check input bands for errors.
9417 74 : if (nBands == 0)
9418 : {
9419 1 : CPLError(CE_Failure, CPLE_NotSupported,
9420 : "NetCDF driver does not support "
9421 : "source dataset with zero band.");
9422 1 : return nullptr;
9423 : }
9424 :
9425 73 : GDALDataType eDT = GDT_Unknown;
9426 73 : GDALRasterBand *poSrcBand = nullptr;
9427 175 : for (int iBand = 1; iBand <= nBands; iBand++)
9428 : {
9429 106 : poSrcBand = poSrcDS->GetRasterBand(iBand);
9430 106 : eDT = poSrcBand->GetRasterDataType();
9431 106 : if (eDT == GDT_Unknown || GDALDataTypeIsComplex(eDT))
9432 : {
9433 4 : CPLError(CE_Failure, CPLE_NotSupported,
9434 : "NetCDF driver does not support source dataset with band "
9435 : "of complex type.");
9436 4 : return nullptr;
9437 : }
9438 : }
9439 :
9440 138 : CPLStringList aosBandNames;
9441 69 : if (const char *pszBandNames =
9442 69 : CSLFetchNameValue(papszOptions, "BAND_NAMES"))
9443 : {
9444 : aosBandNames =
9445 2 : CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);
9446 :
9447 2 : if (aosBandNames.Count() != nBands)
9448 : {
9449 1 : CPLError(CE_Failure, CPLE_OpenFailed,
9450 : "Attempted to create netCDF with %d bands but %d names "
9451 : "provided in BAND_NAMES.",
9452 : nBands, aosBandNames.Count());
9453 :
9454 1 : return nullptr;
9455 : }
9456 : }
9457 :
9458 68 : if (!pfnProgress(0.0, nullptr, pProgressData))
9459 0 : return nullptr;
9460 :
9461 : // Same as in Create().
9462 136 : CPLStringList aosOptions(CSLDuplicate(papszOptions));
9463 127 : if (aosOptions.FetchNameValue("FORMAT") == nullptr &&
9464 59 : (eDT == GDT_UInt16 || eDT == GDT_UInt32 || eDT == GDT_UInt64 ||
9465 : eDT == GDT_Int64))
9466 : {
9467 6 : CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
9468 6 : aosOptions.SetNameValue("FORMAT", "NC4");
9469 : }
9470 68 : netCDFDataset *poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize,
9471 : nBands, aosOptions.List());
9472 68 : if (!poDS)
9473 13 : return nullptr;
9474 :
9475 : // Copy global metadata.
9476 : // Add Conventions, GDAL info and history.
9477 55 : CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid, NC_GLOBAL, nullptr);
9478 55 : const bool bWriteGDALVersion = CPLTestBool(
9479 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
9480 55 : const bool bWriteGDALHistory = CPLTestBool(
9481 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
9482 55 : NCDFAddGDALHistory(
9483 : poDS->cdfid, pszFilename, bWriteGDALVersion, bWriteGDALHistory,
9484 55 : poSrcDS->GetMetadataItem("NC_GLOBAL#history"), "CreateCopy",
9485 55 : poSrcDS->GetMetadataItem("NC_GLOBAL#Conventions"));
9486 :
9487 55 : pfnProgress(0.1, nullptr, pProgressData);
9488 :
9489 : // Check for extra dimensions.
9490 55 : int nDim = 2;
9491 : char **papszExtraDimNames =
9492 55 : NCDFTokenizeArray(poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
9493 55 : char **papszExtraDimValues = nullptr;
9494 :
9495 55 : if (papszExtraDimNames != nullptr && CSLCount(papszExtraDimNames) > 0)
9496 : {
9497 5 : size_t nDimSizeTot = 1;
9498 : // first make sure dimensions lengths compatible with band count
9499 : // for( int i=0; i<CSLCount(papszExtraDimNames ); i++ ) {
9500 13 : for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--)
9501 : {
9502 : char szTemp[NC_MAX_NAME + 32 + 1];
9503 8 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
9504 8 : papszExtraDimNames[i]);
9505 : papszExtraDimValues =
9506 8 : NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
9507 8 : const size_t nDimSize = atol(papszExtraDimValues[0]);
9508 8 : CSLDestroy(papszExtraDimValues);
9509 8 : nDimSizeTot *= nDimSize;
9510 : }
9511 5 : if (nDimSizeTot == (size_t)nBands)
9512 : {
9513 5 : nDim = 2 + CSLCount(papszExtraDimNames);
9514 : }
9515 : else
9516 : {
9517 : // if nBands != #bands computed raise a warning
9518 : // just issue a debug message, because it was probably intentional
9519 0 : CPLDebug("GDAL_netCDF",
9520 : "Warning: Number of bands (%d) is not compatible with "
9521 : "dimensions "
9522 : "(total=%ld names=%s)",
9523 : nBands, (long)nDimSizeTot,
9524 0 : poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
9525 0 : CSLDestroy(papszExtraDimNames);
9526 0 : papszExtraDimNames = nullptr;
9527 : }
9528 : }
9529 :
9530 55 : int *panDimIds = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
9531 55 : int *panBandDimPos = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
9532 :
9533 : nc_type nVarType;
9534 55 : int *panBandZLev = nullptr;
9535 55 : int *panDimVarIds = nullptr;
9536 :
9537 55 : if (nDim > 2)
9538 : {
9539 5 : panBandZLev = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
9540 5 : panDimVarIds = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
9541 :
9542 : // Define all dims.
9543 13 : for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--)
9544 : {
9545 8 : poDS->papszDimName.AddString(papszExtraDimNames[i]);
9546 : char szTemp[NC_MAX_NAME + 32 + 1];
9547 8 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
9548 8 : papszExtraDimNames[i]);
9549 : papszExtraDimValues =
9550 8 : NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
9551 8 : const int nDimSize = papszExtraDimValues && papszExtraDimValues[0]
9552 16 : ? atoi(papszExtraDimValues[0])
9553 : : 0;
9554 : // nc_type is an enum in netcdf-3, needs casting.
9555 8 : nVarType = static_cast<nc_type>(papszExtraDimValues &&
9556 8 : papszExtraDimValues[0] &&
9557 8 : papszExtraDimValues[1]
9558 8 : ? atol(papszExtraDimValues[1])
9559 : : 0);
9560 8 : CSLDestroy(papszExtraDimValues);
9561 8 : panBandZLev[i] = nDimSize;
9562 8 : panBandDimPos[i + 2] = i; // Save Position of ZDim.
9563 :
9564 : // Define dim.
9565 16 : int status = nc_def_dim(poDS->cdfid, papszExtraDimNames[i],
9566 8 : nDimSize, &(panDimIds[i]));
9567 8 : NCDF_ERR(status);
9568 :
9569 : // Define dim var.
9570 8 : int anDim[1] = {panDimIds[i]};
9571 16 : status = nc_def_var(poDS->cdfid, papszExtraDimNames[i], nVarType, 1,
9572 8 : anDim, &(panDimVarIds[i]));
9573 8 : NCDF_ERR(status);
9574 :
9575 : // Add dim metadata, using global var# items.
9576 8 : snprintf(szTemp, sizeof(szTemp), "%s#", papszExtraDimNames[i]);
9577 8 : CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid,
9578 8 : panDimVarIds[i], szTemp);
9579 : }
9580 : }
9581 :
9582 : // Copy GeoTransform and Projection.
9583 :
9584 : // Copy geolocation info.
9585 55 : char **papszGeolocationInfo = poSrcDS->GetMetadata("GEOLOCATION");
9586 55 : if (papszGeolocationInfo != nullptr)
9587 5 : poDS->GDALPamDataset::SetMetadata(papszGeolocationInfo, "GEOLOCATION");
9588 :
9589 : // Copy geotransform.
9590 55 : bool bGotGeoTransform = false;
9591 : double adfGeoTransform[6];
9592 55 : CPLErr eErr = poSrcDS->GetGeoTransform(adfGeoTransform);
9593 55 : if (eErr == CE_None)
9594 : {
9595 37 : poDS->SetGeoTransform(adfGeoTransform);
9596 : // Disable AddProjectionVars() from being called.
9597 37 : bGotGeoTransform = true;
9598 37 : poDS->m_bHasGeoTransform = false;
9599 : }
9600 :
9601 : // Copy projection.
9602 55 : void *pScaledProgress = nullptr;
9603 55 : if (bGotGeoTransform || (pszWKT && pszWKT[0] != 0))
9604 : {
9605 38 : poDS->SetProjection(pszWKT ? pszWKT : "");
9606 :
9607 : // Now we can call AddProjectionVars() directly.
9608 38 : poDS->m_bHasGeoTransform = bGotGeoTransform;
9609 38 : poDS->AddProjectionVars(true, nullptr, nullptr);
9610 : pScaledProgress =
9611 38 : GDALCreateScaledProgress(0.1, 0.25, pfnProgress, pProgressData);
9612 38 : poDS->AddProjectionVars(false, GDALScaledProgress, pScaledProgress);
9613 38 : GDALDestroyScaledProgress(pScaledProgress);
9614 : }
9615 : else
9616 : {
9617 17 : poDS->bBottomUp =
9618 17 : CPL_TO_BOOL(CSLFetchBoolean(papszOptions, "WRITE_BOTTOMUP", TRUE));
9619 17 : if (papszGeolocationInfo)
9620 : {
9621 4 : poDS->AddProjectionVars(true, nullptr, nullptr);
9622 4 : poDS->AddProjectionVars(false, nullptr, nullptr);
9623 : }
9624 : }
9625 :
9626 : // Save X,Y dim positions.
9627 55 : panDimIds[nDim - 1] = poDS->nXDimID;
9628 55 : panBandDimPos[0] = nDim - 1;
9629 55 : panDimIds[nDim - 2] = poDS->nYDimID;
9630 55 : panBandDimPos[1] = nDim - 2;
9631 :
9632 : // Write extra dim values - after projection for optimization.
9633 55 : if (nDim > 2)
9634 : {
9635 : // Make sure we are in data mode.
9636 5 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
9637 13 : for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--)
9638 : {
9639 : char szTemp[NC_MAX_NAME + 32 + 1];
9640 8 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_VALUES",
9641 8 : papszExtraDimNames[i]);
9642 8 : if (poSrcDS->GetMetadataItem(szTemp) != nullptr)
9643 : {
9644 8 : NCDFPut1DVar(poDS->cdfid, panDimVarIds[i],
9645 8 : poSrcDS->GetMetadataItem(szTemp));
9646 : }
9647 : }
9648 : }
9649 :
9650 55 : pfnProgress(0.25, nullptr, pProgressData);
9651 :
9652 : // Define Bands.
9653 55 : netCDFRasterBand *poBand = nullptr;
9654 55 : int nBandID = -1;
9655 :
9656 143 : for (int iBand = 1; iBand <= nBands; iBand++)
9657 : {
9658 88 : CPLDebug("GDAL_netCDF", "creating band # %d/%d nDim = %d", iBand,
9659 : nBands, nDim);
9660 :
9661 88 : poSrcBand = poSrcDS->GetRasterBand(iBand);
9662 88 : eDT = poSrcBand->GetRasterDataType();
9663 :
9664 : // Get var name from NETCDF_VARNAME.
9665 : const char *pszNETCDF_VARNAME =
9666 88 : poSrcBand->GetMetadataItem("NETCDF_VARNAME");
9667 : char szBandName[NC_MAX_NAME + 1];
9668 88 : if (!aosBandNames.empty())
9669 : {
9670 2 : snprintf(szBandName, sizeof(szBandName), "%s",
9671 : aosBandNames[iBand - 1]);
9672 : }
9673 86 : else if (pszNETCDF_VARNAME)
9674 : {
9675 32 : if (nBands > 1 && papszExtraDimNames == nullptr)
9676 0 : snprintf(szBandName, sizeof(szBandName), "%s%d",
9677 : pszNETCDF_VARNAME, iBand);
9678 : else
9679 32 : snprintf(szBandName, sizeof(szBandName), "%s",
9680 : pszNETCDF_VARNAME);
9681 : }
9682 : else
9683 : {
9684 54 : szBandName[0] = '\0';
9685 : }
9686 :
9687 : // Get long_name from <var>#long_name.
9688 88 : const char *pszLongName = "";
9689 88 : if (pszNETCDF_VARNAME)
9690 : {
9691 : pszLongName =
9692 64 : poSrcDS->GetMetadataItem(std::string(pszNETCDF_VARNAME)
9693 32 : .append("#")
9694 32 : .append(CF_LNG_NAME)
9695 32 : .c_str());
9696 32 : if (!pszLongName)
9697 25 : pszLongName = "";
9698 : }
9699 :
9700 88 : constexpr bool bSignedData = false;
9701 :
9702 88 : if (nDim > 2)
9703 27 : poBand = new netCDFRasterBand(
9704 27 : netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
9705 : bSignedData, szBandName, pszLongName, nBandID, nDim, iBand - 1,
9706 27 : panBandZLev, panBandDimPos, panDimIds);
9707 : else
9708 61 : poBand = new netCDFRasterBand(
9709 61 : netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
9710 61 : bSignedData, szBandName, pszLongName);
9711 :
9712 88 : poDS->SetBand(iBand, poBand);
9713 :
9714 : // Set nodata value, if any.
9715 88 : GDALCopyNoDataValue(poBand, poSrcBand);
9716 :
9717 : // Copy Metadata for band.
9718 88 : CopyMetadata(nullptr, poSrcDS->GetRasterBand(iBand), poBand,
9719 : poDS->cdfid, poBand->nZId);
9720 :
9721 : // If more than 2D pass the first band's netcdf var ID to subsequent
9722 : // bands.
9723 88 : if (nDim > 2)
9724 27 : nBandID = poBand->nZId;
9725 : }
9726 :
9727 : // Write projection variable to band variable.
9728 55 : poDS->AddGridMappingRef();
9729 :
9730 55 : pfnProgress(0.5, nullptr, pProgressData);
9731 :
9732 : // Write bands.
9733 :
9734 : // Make sure we are in data mode.
9735 55 : poDS->SetDefineMode(false);
9736 :
9737 55 : double dfTemp = 0.5;
9738 :
9739 55 : eErr = CE_None;
9740 :
9741 143 : for (int iBand = 1; iBand <= nBands && eErr == CE_None; iBand++)
9742 : {
9743 88 : const double dfTemp2 = dfTemp + 0.4 / nBands;
9744 88 : pScaledProgress = GDALCreateScaledProgress(dfTemp, dfTemp2, pfnProgress,
9745 : pProgressData);
9746 88 : dfTemp = dfTemp2;
9747 :
9748 88 : CPLDebug("GDAL_netCDF", "copying band data # %d/%d ", iBand, nBands);
9749 :
9750 88 : poSrcBand = poSrcDS->GetRasterBand(iBand);
9751 88 : eDT = poSrcBand->GetRasterDataType();
9752 :
9753 88 : GDALRasterBand *poDstBand = poDS->GetRasterBand(iBand);
9754 :
9755 : // Copy band data.
9756 88 : if (eDT == GDT_Byte)
9757 : {
9758 48 : CPLDebug("GDAL_netCDF", "GByte Band#%d", iBand);
9759 48 : eErr = NCDFCopyBand<GByte>(poSrcBand, poDstBand, nXSize, nYSize,
9760 : GDALScaledProgress, pScaledProgress);
9761 : }
9762 40 : else if (eDT == GDT_Int8)
9763 : {
9764 1 : CPLDebug("GDAL_netCDF", "GInt8 Band#%d", iBand);
9765 1 : eErr = NCDFCopyBand<GInt8>(poSrcBand, poDstBand, nXSize, nYSize,
9766 : GDALScaledProgress, pScaledProgress);
9767 : }
9768 39 : else if (eDT == GDT_UInt16)
9769 : {
9770 2 : CPLDebug("GDAL_netCDF", "GUInt16 Band#%d", iBand);
9771 2 : eErr = NCDFCopyBand<GInt16>(poSrcBand, poDstBand, nXSize, nYSize,
9772 : GDALScaledProgress, pScaledProgress);
9773 : }
9774 37 : else if (eDT == GDT_Int16)
9775 : {
9776 5 : CPLDebug("GDAL_netCDF", "GInt16 Band#%d", iBand);
9777 5 : eErr = NCDFCopyBand<GUInt16>(poSrcBand, poDstBand, nXSize, nYSize,
9778 : GDALScaledProgress, pScaledProgress);
9779 : }
9780 32 : else if (eDT == GDT_UInt32)
9781 : {
9782 2 : CPLDebug("GDAL_netCDF", "GUInt32 Band#%d", iBand);
9783 2 : eErr = NCDFCopyBand<GUInt32>(poSrcBand, poDstBand, nXSize, nYSize,
9784 : GDALScaledProgress, pScaledProgress);
9785 : }
9786 30 : else if (eDT == GDT_Int32)
9787 : {
9788 18 : CPLDebug("GDAL_netCDF", "GInt32 Band#%d", iBand);
9789 18 : eErr = NCDFCopyBand<GInt32>(poSrcBand, poDstBand, nXSize, nYSize,
9790 : GDALScaledProgress, pScaledProgress);
9791 : }
9792 12 : else if (eDT == GDT_UInt64)
9793 : {
9794 2 : CPLDebug("GDAL_netCDF", "GUInt64 Band#%d", iBand);
9795 2 : eErr = NCDFCopyBand<std::uint64_t>(poSrcBand, poDstBand, nXSize,
9796 : nYSize, GDALScaledProgress,
9797 : pScaledProgress);
9798 : }
9799 10 : else if (eDT == GDT_Int64)
9800 : {
9801 2 : CPLDebug("GDAL_netCDF", "GInt64 Band#%d", iBand);
9802 : eErr =
9803 2 : NCDFCopyBand<std::int64_t>(poSrcBand, poDstBand, nXSize, nYSize,
9804 : GDALScaledProgress, pScaledProgress);
9805 : }
9806 8 : else if (eDT == GDT_Float32)
9807 : {
9808 6 : CPLDebug("GDAL_netCDF", "float Band#%d", iBand);
9809 6 : eErr = NCDFCopyBand<float>(poSrcBand, poDstBand, nXSize, nYSize,
9810 : GDALScaledProgress, pScaledProgress);
9811 : }
9812 2 : else if (eDT == GDT_Float64)
9813 : {
9814 2 : CPLDebug("GDAL_netCDF", "double Band#%d", iBand);
9815 2 : eErr = NCDFCopyBand<double>(poSrcBand, poDstBand, nXSize, nYSize,
9816 : GDALScaledProgress, pScaledProgress);
9817 : }
9818 : else
9819 : {
9820 0 : CPLError(CE_Failure, CPLE_NotSupported,
9821 : "The NetCDF driver does not support GDAL data type %d",
9822 : eDT);
9823 : }
9824 :
9825 88 : GDALDestroyScaledProgress(pScaledProgress);
9826 : }
9827 :
9828 55 : delete (poDS);
9829 :
9830 55 : CPLFree(panDimIds);
9831 55 : CPLFree(panBandDimPos);
9832 55 : CPLFree(panBandZLev);
9833 55 : CPLFree(panDimVarIds);
9834 55 : if (papszExtraDimNames)
9835 5 : CSLDestroy(papszExtraDimNames);
9836 :
9837 55 : if (eErr != CE_None)
9838 0 : return nullptr;
9839 :
9840 55 : pfnProgress(0.95, nullptr, pProgressData);
9841 :
9842 : // Re-open dataset so we can return it.
9843 110 : CPLStringList aosOpenOptions;
9844 55 : aosOpenOptions.AddString("VARIABLES_AS_BANDS=YES");
9845 55 : GDALOpenInfo oOpenInfo(pszFilename, GA_Update);
9846 55 : oOpenInfo.nOpenFlags = GDAL_OF_RASTER | GDAL_OF_UPDATE;
9847 55 : oOpenInfo.papszOpenOptions = aosOpenOptions.List();
9848 55 : auto poRetDS = Open(&oOpenInfo);
9849 :
9850 : // PAM cloning is disabled. See bug #4244.
9851 : // if( poDS )
9852 : // poDS->CloneInfo(poSrcDS, GCIF_PAM_DEFAULT);
9853 :
9854 55 : pfnProgress(1.0, nullptr, pProgressData);
9855 :
9856 55 : return poRetDS;
9857 : }
9858 :
9859 : // Note: some logic depends on bIsProjected and bIsGeoGraphic.
9860 : // May not be known when Create() is called, see AddProjectionVars().
9861 251 : void netCDFDataset::ProcessCreationOptions()
9862 : {
9863 : const char *pszConfig =
9864 251 : CSLFetchNameValue(papszCreationOptions, "CONFIG_FILE");
9865 251 : if (pszConfig != nullptr)
9866 : {
9867 4 : if (oWriterConfig.Parse(pszConfig))
9868 : {
9869 : // Override dataset creation options from the config file
9870 2 : std::map<CPLString, CPLString>::iterator oIter;
9871 3 : for (oIter = oWriterConfig.m_oDatasetCreationOptions.begin();
9872 3 : oIter != oWriterConfig.m_oDatasetCreationOptions.end();
9873 1 : ++oIter)
9874 : {
9875 2 : papszCreationOptions = CSLSetNameValue(
9876 2 : papszCreationOptions, oIter->first, oIter->second);
9877 : }
9878 : }
9879 : }
9880 :
9881 : // File format.
9882 251 : eFormat = NCDF_FORMAT_NC;
9883 251 : const char *pszValue = CSLFetchNameValue(papszCreationOptions, "FORMAT");
9884 251 : if (pszValue != nullptr)
9885 : {
9886 93 : if (EQUAL(pszValue, "NC"))
9887 : {
9888 3 : eFormat = NCDF_FORMAT_NC;
9889 : }
9890 : #ifdef NETCDF_HAS_NC2
9891 90 : else if (EQUAL(pszValue, "NC2"))
9892 : {
9893 1 : eFormat = NCDF_FORMAT_NC2;
9894 : }
9895 : #endif
9896 89 : else if (EQUAL(pszValue, "NC4"))
9897 : {
9898 85 : eFormat = NCDF_FORMAT_NC4;
9899 : }
9900 4 : else if (EQUAL(pszValue, "NC4C"))
9901 : {
9902 4 : eFormat = NCDF_FORMAT_NC4C;
9903 : }
9904 : else
9905 : {
9906 0 : CPLError(CE_Failure, CPLE_NotSupported,
9907 : "FORMAT=%s in not supported, using the default NC format.",
9908 : pszValue);
9909 : }
9910 : }
9911 :
9912 : // COMPRESS option.
9913 251 : pszValue = CSLFetchNameValue(papszCreationOptions, "COMPRESS");
9914 251 : if (pszValue != nullptr)
9915 : {
9916 2 : if (EQUAL(pszValue, "NONE"))
9917 : {
9918 1 : eCompress = NCDF_COMPRESS_NONE;
9919 : }
9920 1 : else if (EQUAL(pszValue, "DEFLATE"))
9921 : {
9922 1 : eCompress = NCDF_COMPRESS_DEFLATE;
9923 1 : if (!((eFormat == NCDF_FORMAT_NC4) ||
9924 1 : (eFormat == NCDF_FORMAT_NC4C)))
9925 : {
9926 0 : CPLError(CE_Warning, CPLE_IllegalArg,
9927 : "NOTICE: Format set to NC4C because compression is "
9928 : "set to DEFLATE.");
9929 0 : eFormat = NCDF_FORMAT_NC4C;
9930 : }
9931 : }
9932 : else
9933 : {
9934 0 : CPLError(CE_Failure, CPLE_NotSupported,
9935 : "COMPRESS=%s is not supported.", pszValue);
9936 : }
9937 : }
9938 :
9939 : // ZLEVEL option.
9940 251 : pszValue = CSLFetchNameValue(papszCreationOptions, "ZLEVEL");
9941 251 : if (pszValue != nullptr)
9942 : {
9943 1 : nZLevel = atoi(pszValue);
9944 1 : if (!(nZLevel >= 1 && nZLevel <= 9))
9945 : {
9946 0 : CPLError(CE_Warning, CPLE_IllegalArg,
9947 : "ZLEVEL=%s value not recognised, ignoring.", pszValue);
9948 0 : nZLevel = NCDF_DEFLATE_LEVEL;
9949 : }
9950 : }
9951 :
9952 : // CHUNKING option.
9953 251 : bChunking =
9954 251 : CPL_TO_BOOL(CSLFetchBoolean(papszCreationOptions, "CHUNKING", TRUE));
9955 :
9956 : // MULTIPLE_LAYERS option.
9957 : const char *pszMultipleLayerBehavior =
9958 251 : CSLFetchNameValueDef(papszCreationOptions, "MULTIPLE_LAYERS", "NO");
9959 502 : const char *pszGeometryEnc = CSLFetchNameValueDef(
9960 251 : papszCreationOptions, "GEOMETRY_ENCODING", "CF_1.8");
9961 251 : if (EQUAL(pszMultipleLayerBehavior, "NO") ||
9962 3 : EQUAL(pszGeometryEnc, "CF_1.8"))
9963 : {
9964 248 : eMultipleLayerBehavior = SINGLE_LAYER;
9965 : }
9966 3 : else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_FILES"))
9967 : {
9968 2 : eMultipleLayerBehavior = SEPARATE_FILES;
9969 : }
9970 1 : else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_GROUPS"))
9971 : {
9972 1 : if (eFormat == NCDF_FORMAT_NC4)
9973 : {
9974 1 : eMultipleLayerBehavior = SEPARATE_GROUPS;
9975 : }
9976 : else
9977 : {
9978 0 : CPLError(CE_Warning, CPLE_IllegalArg,
9979 : "MULTIPLE_LAYERS=%s is recognised only with FORMAT=NC4",
9980 : pszMultipleLayerBehavior);
9981 : }
9982 : }
9983 : else
9984 : {
9985 0 : CPLError(CE_Warning, CPLE_IllegalArg,
9986 : "MULTIPLE_LAYERS=%s not recognised", pszMultipleLayerBehavior);
9987 : }
9988 :
9989 : // Set nCreateMode based on eFormat.
9990 251 : switch (eFormat)
9991 : {
9992 : #ifdef NETCDF_HAS_NC2
9993 1 : case NCDF_FORMAT_NC2:
9994 1 : nCreateMode = NC_CLOBBER | NC_64BIT_OFFSET;
9995 1 : break;
9996 : #endif
9997 85 : case NCDF_FORMAT_NC4:
9998 85 : nCreateMode = NC_CLOBBER | NC_NETCDF4;
9999 85 : break;
10000 4 : case NCDF_FORMAT_NC4C:
10001 4 : nCreateMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
10002 4 : break;
10003 161 : case NCDF_FORMAT_NC:
10004 : default:
10005 161 : nCreateMode = NC_CLOBBER;
10006 161 : break;
10007 : }
10008 :
10009 251 : CPLDebug("GDAL_netCDF", "file options: format=%d compress=%d zlevel=%d",
10010 251 : eFormat, eCompress, nZLevel);
10011 251 : }
10012 :
10013 271 : int netCDFDataset::DefVarDeflate(int nVarId, bool bChunkingArg)
10014 : {
10015 271 : if (eCompress == NCDF_COMPRESS_DEFLATE)
10016 : {
10017 : // Must set chunk size to avoid huge performance hit (set
10018 : // bChunkingArg=TRUE)
10019 : // perhaps another solution it to change the chunk cache?
10020 : // http://www.unidata.ucar.edu/software/netcdf/docs/netcdf.html#Chunk-Cache
10021 : // TODO: make sure this is okay.
10022 1 : CPLDebug("GDAL_netCDF", "DefVarDeflate(%d, %d) nZlevel=%d", nVarId,
10023 : static_cast<int>(bChunkingArg), nZLevel);
10024 :
10025 1 : int status = nc_def_var_deflate(cdfid, nVarId, 1, 1, nZLevel);
10026 1 : NCDF_ERR(status);
10027 :
10028 1 : if (status == NC_NOERR && bChunkingArg && bChunking)
10029 : {
10030 : // set chunking to be 1 for all dims, except X dim
10031 : // size_t chunksize[] = { 1, (size_t)nRasterXSize };
10032 : size_t chunksize[MAX_NC_DIMS];
10033 : int nd;
10034 1 : nc_inq_varndims(cdfid, nVarId, &nd);
10035 1 : chunksize[0] = (size_t)1;
10036 1 : chunksize[1] = (size_t)1;
10037 1 : for (int i = 2; i < nd; i++)
10038 0 : chunksize[i] = (size_t)1;
10039 1 : chunksize[nd - 1] = (size_t)nRasterXSize;
10040 :
10041 : // Config options just for testing purposes
10042 : const char *pszBlockXSize =
10043 1 : CPLGetConfigOption("BLOCKXSIZE", nullptr);
10044 1 : if (pszBlockXSize)
10045 0 : chunksize[nd - 1] = (size_t)atoi(pszBlockXSize);
10046 :
10047 : const char *pszBlockYSize =
10048 1 : CPLGetConfigOption("BLOCKYSIZE", nullptr);
10049 1 : if (nd >= 2 && pszBlockYSize)
10050 0 : chunksize[nd - 2] = (size_t)atoi(pszBlockYSize);
10051 :
10052 1 : CPLDebug("GDAL_netCDF",
10053 : "DefVarDeflate() chunksize={%ld, %ld} chunkX=%ld nd=%d",
10054 1 : (long)chunksize[0], (long)chunksize[1],
10055 1 : (long)chunksize[nd - 1], nd);
10056 : #ifdef NCDF_DEBUG
10057 : for (int i = 0; i < nd; i++)
10058 : CPLDebug("GDAL_netCDF", "DefVarDeflate() chunk[%d]=%ld", i,
10059 : chunksize[i]);
10060 : #endif
10061 :
10062 1 : status = nc_def_var_chunking(cdfid, nVarId, NC_CHUNKED, chunksize);
10063 1 : NCDF_ERR(status);
10064 : }
10065 : else
10066 : {
10067 0 : CPLDebug("GDAL_netCDF", "chunksize not set");
10068 : }
10069 1 : return status;
10070 : }
10071 270 : return NC_NOERR;
10072 : }
10073 :
10074 : /************************************************************************/
10075 : /* NCDFUnloadDriver() */
10076 : /************************************************************************/
10077 :
10078 8 : static void NCDFUnloadDriver(CPL_UNUSED GDALDriver *poDriver)
10079 : {
10080 8 : if (hNCMutex != nullptr)
10081 4 : CPLDestroyMutex(hNCMutex);
10082 8 : hNCMutex = nullptr;
10083 8 : }
10084 :
10085 : /************************************************************************/
10086 : /* GDALRegister_netCDF() */
10087 : /************************************************************************/
10088 :
10089 : class GDALnetCDFDriver final : public GDALDriver
10090 : {
10091 : public:
10092 14 : GDALnetCDFDriver() = default;
10093 :
10094 1052 : const char *GetMetadataItem(const char *pszName,
10095 : const char *pszDomain) override
10096 : {
10097 1052 : if (EQUAL(pszName, GDAL_DCAP_VIRTUALIO))
10098 : {
10099 13 : InitializeDCAPVirtualIO();
10100 : }
10101 1052 : return GDALDriver::GetMetadataItem(pszName, pszDomain);
10102 : }
10103 :
10104 79 : char **GetMetadata(const char *pszDomain) override
10105 : {
10106 79 : InitializeDCAPVirtualIO();
10107 79 : return GDALDriver::GetMetadata(pszDomain);
10108 : }
10109 :
10110 : private:
10111 : bool m_bInitialized = false;
10112 :
10113 92 : void InitializeDCAPVirtualIO()
10114 : {
10115 92 : if (!m_bInitialized)
10116 : {
10117 11 : m_bInitialized = true;
10118 :
10119 : #ifdef ENABLE_UFFD
10120 11 : if (CPLIsUserFaultMappingSupported())
10121 : {
10122 11 : SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
10123 : }
10124 : #endif
10125 : }
10126 92 : }
10127 : };
10128 :
10129 14 : void GDALRegister_netCDF()
10130 :
10131 : {
10132 14 : if (!GDAL_CHECK_VERSION("netCDF driver"))
10133 0 : return;
10134 :
10135 14 : if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
10136 0 : return;
10137 :
10138 14 : GDALDriver *poDriver = new GDALnetCDFDriver();
10139 14 : netCDFDriverSetCommonMetadata(poDriver);
10140 :
10141 14 : poDriver->SetMetadataItem("NETCDF_CONVENTIONS",
10142 14 : GDAL_DEFAULT_NCDF_CONVENTIONS);
10143 14 : poDriver->SetMetadataItem("NETCDF_VERSION", nc_inq_libvers());
10144 :
10145 : // Set pfns and register driver.
10146 14 : poDriver->pfnOpen = netCDFDataset::Open;
10147 14 : poDriver->pfnCreateCopy = netCDFDataset::CreateCopy;
10148 14 : poDriver->pfnCreate = netCDFDataset::Create;
10149 14 : poDriver->pfnCreateMultiDimensional = netCDFDataset::CreateMultiDimensional;
10150 14 : poDriver->pfnUnloadDriver = NCDFUnloadDriver;
10151 :
10152 14 : GetGDALDriverManager()->RegisterDriver(poDriver);
10153 : }
10154 :
10155 : /************************************************************************/
10156 : /* New functions */
10157 : /************************************************************************/
10158 :
10159 : /* Test for GDAL version string >= target */
10160 236 : static bool NCDFIsGDALVersionGTE(const char *pszVersion, int nTarget)
10161 : {
10162 :
10163 : // Valid strings are "GDAL 1.9dev, released 2011/01/18" and "GDAL 1.8.1 ".
10164 236 : if (pszVersion == nullptr || EQUAL(pszVersion, ""))
10165 0 : return false;
10166 236 : else if (!STARTS_WITH_CI(pszVersion, "GDAL "))
10167 0 : return false;
10168 : // 2.0dev of 2011/12/29 has been later renamed as 1.10dev.
10169 236 : else if (EQUAL("GDAL 2.0dev, released 2011/12/29", pszVersion))
10170 0 : return nTarget <= GDAL_COMPUTE_VERSION(1, 10, 0);
10171 236 : else if (STARTS_WITH_CI(pszVersion, "GDAL 1.9dev"))
10172 2 : return nTarget <= 1900;
10173 234 : else if (STARTS_WITH_CI(pszVersion, "GDAL 1.8dev"))
10174 0 : return nTarget <= 1800;
10175 :
10176 234 : char **papszTokens = CSLTokenizeString2(pszVersion + 5, ".", 0);
10177 :
10178 234 : int nVersions[] = {0, 0, 0, 0};
10179 936 : for (int iToken = 0; papszTokens && iToken < 4 && papszTokens[iToken];
10180 : iToken++)
10181 : {
10182 702 : nVersions[iToken] = atoi(papszTokens[iToken]);
10183 702 : if (nVersions[iToken] < 0)
10184 0 : nVersions[iToken] = 0;
10185 702 : else if (nVersions[iToken] > 99)
10186 0 : nVersions[iToken] = 99;
10187 : }
10188 :
10189 234 : int nVersion = 0;
10190 234 : if (nVersions[0] > 1 || nVersions[1] >= 10)
10191 234 : nVersion =
10192 234 : GDAL_COMPUTE_VERSION(nVersions[0], nVersions[1], nVersions[2]);
10193 : else
10194 0 : nVersion = nVersions[0] * 1000 + nVersions[1] * 100 +
10195 0 : nVersions[2] * 10 + nVersions[3];
10196 :
10197 234 : CSLDestroy(papszTokens);
10198 234 : return nTarget <= nVersion;
10199 : }
10200 :
10201 : // Add Conventions, GDAL version and history.
10202 164 : static void NCDFAddGDALHistory(int fpImage, const char *pszFilename,
10203 : bool bWriteGDALVersion, bool bWriteGDALHistory,
10204 : const char *pszOldHist,
10205 : const char *pszFunctionName,
10206 : const char *pszCFVersion)
10207 : {
10208 164 : if (pszCFVersion == nullptr)
10209 : {
10210 39 : pszCFVersion = GDAL_DEFAULT_NCDF_CONVENTIONS;
10211 : }
10212 164 : int status = nc_put_att_text(fpImage, NC_GLOBAL, "Conventions",
10213 : strlen(pszCFVersion), pszCFVersion);
10214 164 : NCDF_ERR(status);
10215 :
10216 164 : if (bWriteGDALVersion)
10217 : {
10218 162 : const char *pszNCDF_GDAL = GDALVersionInfo("--version");
10219 162 : status = nc_put_att_text(fpImage, NC_GLOBAL, "GDAL",
10220 : strlen(pszNCDF_GDAL), pszNCDF_GDAL);
10221 162 : NCDF_ERR(status);
10222 : }
10223 :
10224 164 : if (bWriteGDALHistory)
10225 : {
10226 : // Add history.
10227 324 : CPLString osTmp;
10228 : #ifdef GDAL_SET_CMD_LINE_DEFINED_TMP
10229 : if (!EQUAL(GDALGetCmdLine(), ""))
10230 : osTmp = GDALGetCmdLine();
10231 : else
10232 : osTmp =
10233 : CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
10234 : #else
10235 162 : osTmp = CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
10236 : #endif
10237 :
10238 162 : NCDFAddHistory(fpImage, osTmp.c_str(), pszOldHist);
10239 : }
10240 2 : else if (pszOldHist != nullptr)
10241 : {
10242 0 : status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
10243 : strlen(pszOldHist), pszOldHist);
10244 0 : NCDF_ERR(status);
10245 : }
10246 164 : }
10247 :
10248 : // Code taken from cdo and libcdi, used for writing the history attribute.
10249 :
10250 : // void cdoDefHistory(int fileID, char *histstring)
10251 162 : static void NCDFAddHistory(int fpImage, const char *pszAddHist,
10252 : const char *pszOldHist)
10253 : {
10254 : // Check pszOldHist - as if there was no previous history, it will be
10255 : // a null pointer - if so set as empty.
10256 162 : if (nullptr == pszOldHist)
10257 : {
10258 50 : pszOldHist = "";
10259 : }
10260 :
10261 : char strtime[32];
10262 162 : strtime[0] = '\0';
10263 :
10264 162 : time_t tp = time(nullptr);
10265 162 : if (tp != -1)
10266 : {
10267 : struct tm ltime;
10268 162 : VSILocalTime(&tp, <ime);
10269 162 : (void)strftime(strtime, sizeof(strtime),
10270 : "%a %b %d %H:%M:%S %Y: ", <ime);
10271 : }
10272 :
10273 : // status = nc_get_att_text(fpImage, NC_GLOBAL,
10274 : // "history", pszOldHist);
10275 : // printf("status: %d pszOldHist: [%s]\n",status,pszOldHist);
10276 :
10277 162 : size_t nNewHistSize =
10278 162 : strlen(pszOldHist) + strlen(strtime) + strlen(pszAddHist) + 1 + 1;
10279 : char *pszNewHist =
10280 162 : static_cast<char *>(CPLMalloc(nNewHistSize * sizeof(char)));
10281 :
10282 162 : strcpy(pszNewHist, strtime);
10283 162 : strcat(pszNewHist, pszAddHist);
10284 :
10285 : // int disableHistory = FALSE;
10286 : // if( !disableHistory )
10287 : {
10288 162 : if (!EQUAL(pszOldHist, ""))
10289 3 : strcat(pszNewHist, "\n");
10290 162 : strcat(pszNewHist, pszOldHist);
10291 : }
10292 :
10293 162 : const int status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
10294 : strlen(pszNewHist), pszNewHist);
10295 162 : NCDF_ERR(status);
10296 :
10297 162 : CPLFree(pszNewHist);
10298 162 : }
10299 :
10300 5894 : static CPLErr NCDFSafeStrcat(char **ppszDest, const char *pszSrc,
10301 : size_t *nDestSize)
10302 : {
10303 : /* Reallocate the data string until the content fits */
10304 5894 : while (*nDestSize < (strlen(*ppszDest) + strlen(pszSrc) + 1))
10305 : {
10306 394 : (*nDestSize) *= 2;
10307 394 : *ppszDest = static_cast<char *>(
10308 394 : CPLRealloc(reinterpret_cast<void *>(*ppszDest), *nDestSize));
10309 : #ifdef NCDF_DEBUG
10310 : CPLDebug("GDAL_netCDF", "NCDFSafeStrcat() resized str from %ld to %ld",
10311 : (*nDestSize) / 2, *nDestSize);
10312 : #endif
10313 : }
10314 5500 : strcat(*ppszDest, pszSrc);
10315 :
10316 5500 : return CE_None;
10317 : }
10318 :
10319 : /* helper function for NCDFGetAttr() */
10320 : /* if pdfValue != nullptr, sets *pdfValue to first value returned */
10321 : /* if ppszValue != nullptr, sets *ppszValue with all attribute values */
10322 : /* *ppszValue is the responsibility of the caller and must be freed */
10323 61446 : static CPLErr NCDFGetAttr1(int nCdfId, int nVarId, const char *pszAttrName,
10324 : double *pdfValue, char **ppszValue)
10325 : {
10326 61446 : nc_type nAttrType = NC_NAT;
10327 61446 : size_t nAttrLen = 0;
10328 :
10329 61446 : if (ppszValue)
10330 60317 : *ppszValue = nullptr;
10331 :
10332 61446 : int status = nc_inq_att(nCdfId, nVarId, pszAttrName, &nAttrType, &nAttrLen);
10333 61446 : if (status != NC_NOERR)
10334 33370 : return CE_Failure;
10335 :
10336 : #ifdef NCDF_DEBUG
10337 : CPLDebug("GDAL_netCDF", "NCDFGetAttr1(%s) len=%ld type=%d", pszAttrName,
10338 : nAttrLen, nAttrType);
10339 : #endif
10340 28076 : if (nAttrLen == 0 && nAttrType != NC_CHAR)
10341 1 : return CE_Failure;
10342 :
10343 : /* Allocate guaranteed minimum size (use 10 or 20 if not a string) */
10344 28075 : size_t nAttrValueSize = nAttrLen + 1;
10345 28075 : if (nAttrType != NC_CHAR && nAttrValueSize < 10)
10346 3069 : nAttrValueSize = 10;
10347 28075 : if (nAttrType == NC_DOUBLE && nAttrValueSize < 20)
10348 1509 : nAttrValueSize = 20;
10349 28075 : if (nAttrType == NC_INT64 && nAttrValueSize < 20)
10350 20 : nAttrValueSize = 22;
10351 : char *pszAttrValue =
10352 28075 : static_cast<char *>(CPLCalloc(nAttrValueSize, sizeof(char)));
10353 28075 : *pszAttrValue = '\0';
10354 :
10355 28075 : if (nAttrLen > 1 && nAttrType != NC_CHAR)
10356 560 : NCDFSafeStrcat(&pszAttrValue, "{", &nAttrValueSize);
10357 :
10358 28075 : double dfValue = 0.0;
10359 28075 : size_t m = 0;
10360 : char szTemp[256];
10361 28075 : bool bSetDoubleFromStr = false;
10362 :
10363 28075 : switch (nAttrType)
10364 : {
10365 25004 : case NC_CHAR:
10366 25004 : CPL_IGNORE_RET_VAL(
10367 25004 : nc_get_att_text(nCdfId, nVarId, pszAttrName, pszAttrValue));
10368 25004 : pszAttrValue[nAttrLen] = '\0';
10369 25004 : bSetDoubleFromStr = true;
10370 25004 : dfValue = 0.0;
10371 25004 : break;
10372 90 : case NC_BYTE:
10373 : {
10374 : signed char *pscTemp = static_cast<signed char *>(
10375 90 : CPLCalloc(nAttrLen, sizeof(signed char)));
10376 90 : nc_get_att_schar(nCdfId, nVarId, pszAttrName, pscTemp);
10377 90 : dfValue = static_cast<double>(pscTemp[0]);
10378 90 : if (nAttrLen > 1)
10379 : {
10380 20 : for (m = 0; m < nAttrLen - 1; m++)
10381 : {
10382 11 : snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
10383 11 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10384 : }
10385 : }
10386 90 : snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
10387 90 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10388 90 : CPLFree(pscTemp);
10389 90 : break;
10390 : }
10391 463 : case NC_SHORT:
10392 : {
10393 : short *psTemp =
10394 463 : static_cast<short *>(CPLCalloc(nAttrLen, sizeof(short)));
10395 463 : nc_get_att_short(nCdfId, nVarId, pszAttrName, psTemp);
10396 463 : dfValue = static_cast<double>(psTemp[0]);
10397 463 : if (nAttrLen > 1)
10398 : {
10399 724 : for (m = 0; m < nAttrLen - 1; m++)
10400 : {
10401 362 : snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
10402 362 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10403 : }
10404 : }
10405 463 : snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
10406 463 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10407 463 : CPLFree(psTemp);
10408 463 : break;
10409 : }
10410 524 : case NC_INT:
10411 : {
10412 524 : int *pnTemp = static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
10413 524 : nc_get_att_int(nCdfId, nVarId, pszAttrName, pnTemp);
10414 524 : dfValue = static_cast<double>(pnTemp[0]);
10415 524 : if (nAttrLen > 1)
10416 : {
10417 214 : for (m = 0; m < nAttrLen - 1; m++)
10418 : {
10419 137 : snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
10420 137 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10421 : }
10422 : }
10423 524 : snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
10424 524 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10425 524 : CPLFree(pnTemp);
10426 524 : break;
10427 : }
10428 371 : case NC_FLOAT:
10429 : {
10430 : float *pfTemp =
10431 371 : static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
10432 371 : nc_get_att_float(nCdfId, nVarId, pszAttrName, pfTemp);
10433 371 : dfValue = static_cast<double>(pfTemp[0]);
10434 371 : if (nAttrLen > 1)
10435 : {
10436 56 : for (m = 0; m < nAttrLen - 1; m++)
10437 : {
10438 28 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
10439 28 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10440 : }
10441 : }
10442 371 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
10443 371 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10444 371 : CPLFree(pfTemp);
10445 371 : break;
10446 : }
10447 1509 : case NC_DOUBLE:
10448 : {
10449 : double *pdfTemp =
10450 1509 : static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
10451 1509 : nc_get_att_double(nCdfId, nVarId, pszAttrName, pdfTemp);
10452 1509 : dfValue = pdfTemp[0];
10453 1509 : if (nAttrLen > 1)
10454 : {
10455 162 : for (m = 0; m < nAttrLen - 1; m++)
10456 : {
10457 88 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
10458 88 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10459 : }
10460 : }
10461 1509 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
10462 1509 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10463 1509 : CPLFree(pdfTemp);
10464 1509 : break;
10465 : }
10466 8 : case NC_STRING:
10467 : {
10468 : char **ppszTemp =
10469 8 : static_cast<char **>(CPLCalloc(nAttrLen, sizeof(char *)));
10470 8 : nc_get_att_string(nCdfId, nVarId, pszAttrName, ppszTemp);
10471 8 : bSetDoubleFromStr = true;
10472 8 : dfValue = 0.0;
10473 8 : if (nAttrLen > 1)
10474 : {
10475 15 : for (m = 0; m < nAttrLen - 1; m++)
10476 : {
10477 10 : NCDFSafeStrcat(&pszAttrValue,
10478 10 : ppszTemp[m] ? ppszTemp[m] : "{NULL}",
10479 : &nAttrValueSize);
10480 10 : NCDFSafeStrcat(&pszAttrValue, ",", &nAttrValueSize);
10481 : }
10482 : }
10483 8 : NCDFSafeStrcat(&pszAttrValue, ppszTemp[m] ? ppszTemp[m] : "{NULL}",
10484 : &nAttrValueSize);
10485 8 : nc_free_string(nAttrLen, ppszTemp);
10486 8 : CPLFree(ppszTemp);
10487 8 : break;
10488 : }
10489 26 : case NC_UBYTE:
10490 : {
10491 : unsigned char *pucTemp = static_cast<unsigned char *>(
10492 26 : CPLCalloc(nAttrLen, sizeof(unsigned char)));
10493 26 : nc_get_att_uchar(nCdfId, nVarId, pszAttrName, pucTemp);
10494 26 : dfValue = static_cast<double>(pucTemp[0]);
10495 26 : if (nAttrLen > 1)
10496 : {
10497 0 : for (m = 0; m < nAttrLen - 1; m++)
10498 : {
10499 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
10500 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10501 : }
10502 : }
10503 26 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
10504 26 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10505 26 : CPLFree(pucTemp);
10506 26 : break;
10507 : }
10508 24 : case NC_USHORT:
10509 : {
10510 : unsigned short *pusTemp;
10511 : pusTemp = static_cast<unsigned short *>(
10512 24 : CPLCalloc(nAttrLen, sizeof(unsigned short)));
10513 24 : nc_get_att_ushort(nCdfId, nVarId, pszAttrName, pusTemp);
10514 24 : dfValue = static_cast<double>(pusTemp[0]);
10515 24 : if (nAttrLen > 1)
10516 : {
10517 10 : for (m = 0; m < nAttrLen - 1; m++)
10518 : {
10519 5 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
10520 5 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10521 : }
10522 : }
10523 24 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
10524 24 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10525 24 : CPLFree(pusTemp);
10526 24 : break;
10527 : }
10528 16 : case NC_UINT:
10529 : {
10530 : unsigned int *punTemp =
10531 16 : static_cast<unsigned int *>(CPLCalloc(nAttrLen, sizeof(int)));
10532 16 : nc_get_att_uint(nCdfId, nVarId, pszAttrName, punTemp);
10533 16 : dfValue = static_cast<double>(punTemp[0]);
10534 16 : if (nAttrLen > 1)
10535 : {
10536 0 : for (m = 0; m < nAttrLen - 1; m++)
10537 : {
10538 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
10539 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10540 : }
10541 : }
10542 16 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
10543 16 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10544 16 : CPLFree(punTemp);
10545 16 : break;
10546 : }
10547 20 : case NC_INT64:
10548 : {
10549 : GIntBig *panTemp =
10550 20 : static_cast<GIntBig *>(CPLCalloc(nAttrLen, sizeof(GIntBig)));
10551 20 : nc_get_att_longlong(nCdfId, nVarId, pszAttrName, panTemp);
10552 20 : dfValue = static_cast<double>(panTemp[0]);
10553 20 : if (nAttrLen > 1)
10554 : {
10555 0 : for (m = 0; m < nAttrLen - 1; m++)
10556 : {
10557 0 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",",
10558 0 : panTemp[m]);
10559 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10560 : }
10561 : }
10562 20 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, panTemp[m]);
10563 20 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10564 20 : CPLFree(panTemp);
10565 20 : break;
10566 : }
10567 20 : case NC_UINT64:
10568 : {
10569 : GUIntBig *panTemp =
10570 20 : static_cast<GUIntBig *>(CPLCalloc(nAttrLen, sizeof(GUIntBig)));
10571 20 : nc_get_att_ulonglong(nCdfId, nVarId, pszAttrName, panTemp);
10572 20 : dfValue = static_cast<double>(panTemp[0]);
10573 20 : if (nAttrLen > 1)
10574 : {
10575 0 : for (m = 0; m < nAttrLen - 1; m++)
10576 : {
10577 0 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",",
10578 0 : panTemp[m]);
10579 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10580 : }
10581 : }
10582 20 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, panTemp[m]);
10583 20 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10584 20 : CPLFree(panTemp);
10585 20 : break;
10586 : }
10587 0 : default:
10588 0 : CPLDebug("GDAL_netCDF",
10589 : "NCDFGetAttr unsupported type %d for attribute %s",
10590 : nAttrType, pszAttrName);
10591 0 : break;
10592 : }
10593 :
10594 28075 : if (nAttrLen > 1 && nAttrType != NC_CHAR)
10595 560 : NCDFSafeStrcat(&pszAttrValue, "}", &nAttrValueSize);
10596 :
10597 28075 : if (bSetDoubleFromStr)
10598 : {
10599 25012 : if (CPLGetValueType(pszAttrValue) == CPL_VALUE_STRING)
10600 : {
10601 24830 : if (ppszValue == nullptr && pdfValue != nullptr)
10602 : {
10603 1 : CPLFree(pszAttrValue);
10604 1 : return CE_Failure;
10605 : }
10606 : }
10607 25011 : dfValue = CPLAtof(pszAttrValue);
10608 : }
10609 :
10610 : /* set return values */
10611 28074 : if (ppszValue)
10612 27770 : *ppszValue = pszAttrValue;
10613 : else
10614 304 : CPLFree(pszAttrValue);
10615 :
10616 28074 : if (pdfValue)
10617 304 : *pdfValue = dfValue;
10618 :
10619 28074 : return CE_None;
10620 : }
10621 :
10622 : /* sets pdfValue to first value found */
10623 1129 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
10624 : double *pdfValue)
10625 : {
10626 1129 : return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, pdfValue, nullptr);
10627 : }
10628 :
10629 : /* pszValue is the responsibility of the caller and must be freed */
10630 60317 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
10631 : char **pszValue)
10632 : {
10633 60317 : return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, nullptr, pszValue);
10634 : }
10635 :
10636 : /* By default write NC_CHAR, but detect for int/float/double and */
10637 : /* NC4 string arrays */
10638 103 : static CPLErr NCDFPutAttr(int nCdfId, int nVarId, const char *pszAttrName,
10639 : const char *pszValue)
10640 : {
10641 103 : int status = 0;
10642 103 : char *pszTemp = nullptr;
10643 :
10644 : /* get the attribute values as tokens */
10645 103 : char **papszValues = NCDFTokenizeArray(pszValue);
10646 103 : if (papszValues == nullptr)
10647 0 : return CE_Failure;
10648 :
10649 103 : size_t nAttrLen = CSLCount(papszValues);
10650 :
10651 : /* first detect type */
10652 103 : nc_type nAttrType = NC_CHAR;
10653 103 : nc_type nTmpAttrType = NC_CHAR;
10654 219 : for (size_t i = 0; i < nAttrLen; i++)
10655 : {
10656 116 : nTmpAttrType = NC_CHAR;
10657 116 : bool bFoundType = false;
10658 116 : errno = 0;
10659 116 : int nValue = static_cast<int>(strtol(papszValues[i], &pszTemp, 10));
10660 : /* test for int */
10661 : /* TODO test for Byte and short - can this be done safely? */
10662 116 : if (errno == 0 && papszValues[i] != pszTemp && *pszTemp == 0)
10663 : {
10664 : char szTemp[256];
10665 19 : CPLsnprintf(szTemp, sizeof(szTemp), "%d", nValue);
10666 19 : if (EQUAL(szTemp, papszValues[i]))
10667 : {
10668 19 : bFoundType = true;
10669 19 : nTmpAttrType = NC_INT;
10670 : }
10671 : else
10672 : {
10673 : unsigned int unValue = static_cast<unsigned int>(
10674 0 : strtoul(papszValues[i], &pszTemp, 10));
10675 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", unValue);
10676 0 : if (EQUAL(szTemp, papszValues[i]))
10677 : {
10678 0 : bFoundType = true;
10679 0 : nTmpAttrType = NC_UINT;
10680 : }
10681 : }
10682 : }
10683 116 : if (!bFoundType)
10684 : {
10685 : /* test for double */
10686 97 : errno = 0;
10687 97 : double dfValue = CPLStrtod(papszValues[i], &pszTemp);
10688 97 : if ((errno == 0) && (papszValues[i] != pszTemp) && (*pszTemp == 0))
10689 : {
10690 : // Test for float instead of double.
10691 : // strtof() is C89, which is not available in MSVC.
10692 : // See if we loose precision if we cast to float and write to
10693 : // char*.
10694 14 : float fValue = float(dfValue);
10695 : char szTemp[256];
10696 14 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", fValue);
10697 14 : if (EQUAL(szTemp, papszValues[i]))
10698 8 : nTmpAttrType = NC_FLOAT;
10699 : else
10700 6 : nTmpAttrType = NC_DOUBLE;
10701 : }
10702 : }
10703 116 : if ((nTmpAttrType <= NC_DOUBLE && nAttrType <= NC_DOUBLE &&
10704 96 : nTmpAttrType > nAttrType) ||
10705 96 : (nTmpAttrType == NC_UINT && nAttrType < NC_FLOAT) ||
10706 5 : (nTmpAttrType >= NC_FLOAT && nAttrType == NC_UINT))
10707 20 : nAttrType = nTmpAttrType;
10708 : }
10709 :
10710 : #ifdef DEBUG
10711 103 : if (EQUAL(pszAttrName, "DEBUG_EMPTY_DOUBLE_ATTR"))
10712 : {
10713 0 : nAttrType = NC_DOUBLE;
10714 0 : nAttrLen = 0;
10715 : }
10716 : #endif
10717 :
10718 : /* now write the data */
10719 103 : if (nAttrType == NC_CHAR)
10720 : {
10721 83 : int nTmpFormat = 0;
10722 83 : if (nAttrLen > 1)
10723 : {
10724 0 : status = nc_inq_format(nCdfId, &nTmpFormat);
10725 0 : NCDF_ERR(status);
10726 : }
10727 83 : if (nAttrLen > 1 && nTmpFormat == NCDF_FORMAT_NC4)
10728 0 : status = nc_put_att_string(nCdfId, nVarId, pszAttrName, nAttrLen,
10729 : const_cast<const char **>(papszValues));
10730 : else
10731 83 : status = nc_put_att_text(nCdfId, nVarId, pszAttrName,
10732 : strlen(pszValue), pszValue);
10733 83 : NCDF_ERR(status);
10734 : }
10735 : else
10736 : {
10737 20 : switch (nAttrType)
10738 : {
10739 11 : case NC_INT:
10740 : {
10741 : int *pnTemp =
10742 11 : static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
10743 30 : for (size_t i = 0; i < nAttrLen; i++)
10744 : {
10745 19 : pnTemp[i] =
10746 19 : static_cast<int>(strtol(papszValues[i], &pszTemp, 10));
10747 : }
10748 11 : status = nc_put_att_int(nCdfId, nVarId, pszAttrName, NC_INT,
10749 : nAttrLen, pnTemp);
10750 11 : NCDF_ERR(status);
10751 11 : CPLFree(pnTemp);
10752 11 : break;
10753 : }
10754 0 : case NC_UINT:
10755 : {
10756 : unsigned int *punTemp = static_cast<unsigned int *>(
10757 0 : CPLCalloc(nAttrLen, sizeof(unsigned int)));
10758 0 : for (size_t i = 0; i < nAttrLen; i++)
10759 : {
10760 0 : punTemp[i] = static_cast<unsigned int>(
10761 0 : strtol(papszValues[i], &pszTemp, 10));
10762 : }
10763 0 : status = nc_put_att_uint(nCdfId, nVarId, pszAttrName, NC_UINT,
10764 : nAttrLen, punTemp);
10765 0 : NCDF_ERR(status);
10766 0 : CPLFree(punTemp);
10767 0 : break;
10768 : }
10769 6 : case NC_FLOAT:
10770 : {
10771 : float *pfTemp =
10772 6 : static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
10773 14 : for (size_t i = 0; i < nAttrLen; i++)
10774 : {
10775 8 : pfTemp[i] =
10776 8 : static_cast<float>(CPLStrtod(papszValues[i], &pszTemp));
10777 : }
10778 6 : status = nc_put_att_float(nCdfId, nVarId, pszAttrName, NC_FLOAT,
10779 : nAttrLen, pfTemp);
10780 6 : NCDF_ERR(status);
10781 6 : CPLFree(pfTemp);
10782 6 : break;
10783 : }
10784 3 : case NC_DOUBLE:
10785 : {
10786 : double *pdfTemp =
10787 3 : static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
10788 9 : for (size_t i = 0; i < nAttrLen; i++)
10789 : {
10790 6 : pdfTemp[i] = CPLStrtod(papszValues[i], &pszTemp);
10791 : }
10792 3 : status = nc_put_att_double(nCdfId, nVarId, pszAttrName,
10793 : NC_DOUBLE, nAttrLen, pdfTemp);
10794 3 : NCDF_ERR(status);
10795 3 : CPLFree(pdfTemp);
10796 3 : break;
10797 : }
10798 0 : default:
10799 0 : if (papszValues)
10800 0 : CSLDestroy(papszValues);
10801 0 : return CE_Failure;
10802 : break;
10803 : }
10804 : }
10805 :
10806 103 : if (papszValues)
10807 103 : CSLDestroy(papszValues);
10808 :
10809 103 : return CE_None;
10810 : }
10811 :
10812 72 : static CPLErr NCDFGet1DVar(int nCdfId, int nVarId, char **pszValue)
10813 : {
10814 : /* get var information */
10815 72 : int nVarDimId = -1;
10816 72 : int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
10817 72 : if (status != NC_NOERR || nVarDimId != 1)
10818 0 : return CE_Failure;
10819 :
10820 72 : status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
10821 72 : if (status != NC_NOERR)
10822 0 : return CE_Failure;
10823 :
10824 72 : nc_type nVarType = NC_NAT;
10825 72 : status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
10826 72 : if (status != NC_NOERR)
10827 0 : return CE_Failure;
10828 :
10829 72 : size_t nVarLen = 0;
10830 72 : status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
10831 72 : if (status != NC_NOERR)
10832 0 : return CE_Failure;
10833 :
10834 72 : size_t start[1] = {0};
10835 72 : size_t count[1] = {nVarLen};
10836 :
10837 : /* Allocate guaranteed minimum size */
10838 72 : size_t nVarValueSize = NCDF_MAX_STR_LEN;
10839 : char *pszVarValue =
10840 72 : static_cast<char *>(CPLCalloc(nVarValueSize, sizeof(char)));
10841 72 : *pszVarValue = '\0';
10842 :
10843 72 : if (nVarLen == 0)
10844 : {
10845 : /* set return values */
10846 1 : *pszValue = pszVarValue;
10847 :
10848 1 : return CE_None;
10849 : }
10850 :
10851 71 : if (nVarLen > 1 && nVarType != NC_CHAR)
10852 38 : NCDFSafeStrcat(&pszVarValue, "{", &nVarValueSize);
10853 :
10854 71 : switch (nVarType)
10855 : {
10856 0 : case NC_CHAR:
10857 0 : nc_get_vara_text(nCdfId, nVarId, start, count, pszVarValue);
10858 0 : pszVarValue[nVarLen] = '\0';
10859 0 : break;
10860 0 : case NC_BYTE:
10861 : {
10862 : signed char *pscTemp = static_cast<signed char *>(
10863 0 : CPLCalloc(nVarLen, sizeof(signed char)));
10864 0 : nc_get_vara_schar(nCdfId, nVarId, start, count, pscTemp);
10865 : char szTemp[256];
10866 0 : size_t m = 0;
10867 0 : for (; m < nVarLen - 1; m++)
10868 : {
10869 0 : snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
10870 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10871 : }
10872 0 : snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
10873 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10874 0 : CPLFree(pscTemp);
10875 0 : break;
10876 : }
10877 0 : case NC_SHORT:
10878 : {
10879 : short *psTemp =
10880 0 : static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
10881 0 : nc_get_vara_short(nCdfId, nVarId, start, count, psTemp);
10882 : char szTemp[256];
10883 0 : size_t m = 0;
10884 0 : for (; m < nVarLen - 1; m++)
10885 : {
10886 0 : snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
10887 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10888 : }
10889 0 : snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
10890 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10891 0 : CPLFree(psTemp);
10892 0 : break;
10893 : }
10894 19 : case NC_INT:
10895 : {
10896 19 : int *pnTemp = static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
10897 19 : nc_get_vara_int(nCdfId, nVarId, start, count, pnTemp);
10898 : char szTemp[256];
10899 19 : size_t m = 0;
10900 36 : for (; m < nVarLen - 1; m++)
10901 : {
10902 17 : snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
10903 17 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10904 : }
10905 19 : snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
10906 19 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10907 19 : CPLFree(pnTemp);
10908 19 : break;
10909 : }
10910 7 : case NC_FLOAT:
10911 : {
10912 : float *pfTemp =
10913 7 : static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
10914 7 : nc_get_vara_float(nCdfId, nVarId, start, count, pfTemp);
10915 : char szTemp[256];
10916 7 : size_t m = 0;
10917 324 : for (; m < nVarLen - 1; m++)
10918 : {
10919 317 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
10920 317 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10921 : }
10922 7 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
10923 7 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10924 7 : CPLFree(pfTemp);
10925 7 : break;
10926 : }
10927 44 : case NC_DOUBLE:
10928 : {
10929 : double *pdfTemp =
10930 44 : static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
10931 44 : nc_get_vara_double(nCdfId, nVarId, start, count, pdfTemp);
10932 : char szTemp[256];
10933 44 : size_t m = 0;
10934 220 : for (; m < nVarLen - 1; m++)
10935 : {
10936 176 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
10937 176 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10938 : }
10939 44 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
10940 44 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10941 44 : CPLFree(pdfTemp);
10942 44 : break;
10943 : }
10944 0 : case NC_STRING:
10945 : {
10946 : char **ppszTemp =
10947 0 : static_cast<char **>(CPLCalloc(nVarLen, sizeof(char *)));
10948 0 : nc_get_vara_string(nCdfId, nVarId, start, count, ppszTemp);
10949 0 : size_t m = 0;
10950 0 : for (; m < nVarLen - 1; m++)
10951 : {
10952 0 : NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
10953 0 : NCDFSafeStrcat(&pszVarValue, ",", &nVarValueSize);
10954 : }
10955 0 : NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
10956 0 : nc_free_string(nVarLen, ppszTemp);
10957 0 : CPLFree(ppszTemp);
10958 0 : break;
10959 : }
10960 0 : case NC_UBYTE:
10961 : {
10962 : unsigned char *pucTemp;
10963 : pucTemp = static_cast<unsigned char *>(
10964 0 : CPLCalloc(nVarLen, sizeof(unsigned char)));
10965 0 : nc_get_vara_uchar(nCdfId, nVarId, start, count, pucTemp);
10966 : char szTemp[256];
10967 0 : size_t m = 0;
10968 0 : for (; m < nVarLen - 1; m++)
10969 : {
10970 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
10971 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10972 : }
10973 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
10974 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10975 0 : CPLFree(pucTemp);
10976 0 : break;
10977 : }
10978 0 : case NC_USHORT:
10979 : {
10980 : unsigned short *pusTemp;
10981 : pusTemp = static_cast<unsigned short *>(
10982 0 : CPLCalloc(nVarLen, sizeof(unsigned short)));
10983 0 : nc_get_vara_ushort(nCdfId, nVarId, start, count, pusTemp);
10984 : char szTemp[256];
10985 0 : size_t m = 0;
10986 0 : for (; m < nVarLen - 1; m++)
10987 : {
10988 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
10989 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10990 : }
10991 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
10992 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10993 0 : CPLFree(pusTemp);
10994 0 : break;
10995 : }
10996 0 : case NC_UINT:
10997 : {
10998 : unsigned int *punTemp;
10999 : punTemp = static_cast<unsigned int *>(
11000 0 : CPLCalloc(nVarLen, sizeof(unsigned int)));
11001 0 : nc_get_vara_uint(nCdfId, nVarId, start, count, punTemp);
11002 : char szTemp[256];
11003 0 : size_t m = 0;
11004 0 : for (; m < nVarLen - 1; m++)
11005 : {
11006 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
11007 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11008 : }
11009 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
11010 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11011 0 : CPLFree(punTemp);
11012 0 : break;
11013 : }
11014 1 : case NC_INT64:
11015 : {
11016 : long long *pnTemp =
11017 1 : static_cast<long long *>(CPLCalloc(nVarLen, sizeof(long long)));
11018 1 : nc_get_vara_longlong(nCdfId, nVarId, start, count, pnTemp);
11019 : char szTemp[256];
11020 1 : size_t m = 0;
11021 2 : for (; m < nVarLen - 1; m++)
11022 : {
11023 1 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",", pnTemp[m]);
11024 1 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11025 : }
11026 1 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, pnTemp[m]);
11027 1 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11028 1 : CPLFree(pnTemp);
11029 1 : break;
11030 : }
11031 0 : case NC_UINT64:
11032 : {
11033 : unsigned long long *pnTemp = static_cast<unsigned long long *>(
11034 0 : CPLCalloc(nVarLen, sizeof(unsigned long long)));
11035 0 : nc_get_vara_ulonglong(nCdfId, nVarId, start, count, pnTemp);
11036 : char szTemp[256];
11037 0 : size_t m = 0;
11038 0 : for (; m < nVarLen - 1; m++)
11039 : {
11040 0 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",", pnTemp[m]);
11041 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11042 : }
11043 0 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, pnTemp[m]);
11044 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11045 0 : CPLFree(pnTemp);
11046 0 : break;
11047 : }
11048 0 : default:
11049 0 : CPLDebug("GDAL_netCDF", "NCDFGetVar1D unsupported type %d",
11050 : nVarType);
11051 0 : CPLFree(pszVarValue);
11052 0 : pszVarValue = nullptr;
11053 0 : break;
11054 : }
11055 :
11056 71 : if (pszVarValue != nullptr && nVarLen > 1 && nVarType != NC_CHAR)
11057 38 : NCDFSafeStrcat(&pszVarValue, "}", &nVarValueSize);
11058 :
11059 : /* set return values */
11060 71 : *pszValue = pszVarValue;
11061 :
11062 71 : return CE_None;
11063 : }
11064 :
11065 8 : static CPLErr NCDFPut1DVar(int nCdfId, int nVarId, const char *pszValue)
11066 : {
11067 8 : if (EQUAL(pszValue, ""))
11068 0 : return CE_Failure;
11069 :
11070 : /* get var information */
11071 8 : int nVarDimId = -1;
11072 8 : int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
11073 8 : if (status != NC_NOERR || nVarDimId != 1)
11074 0 : return CE_Failure;
11075 :
11076 8 : status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
11077 8 : if (status != NC_NOERR)
11078 0 : return CE_Failure;
11079 :
11080 8 : nc_type nVarType = NC_CHAR;
11081 8 : status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
11082 8 : if (status != NC_NOERR)
11083 0 : return CE_Failure;
11084 :
11085 8 : size_t nVarLen = 0;
11086 8 : status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
11087 8 : if (status != NC_NOERR)
11088 0 : return CE_Failure;
11089 :
11090 8 : size_t start[1] = {0};
11091 8 : size_t count[1] = {nVarLen};
11092 :
11093 : /* get the values as tokens */
11094 8 : char **papszValues = NCDFTokenizeArray(pszValue);
11095 8 : if (papszValues == nullptr)
11096 0 : return CE_Failure;
11097 :
11098 8 : nVarLen = CSLCount(papszValues);
11099 :
11100 : /* now write the data */
11101 8 : if (nVarType == NC_CHAR)
11102 : {
11103 0 : status = nc_put_vara_text(nCdfId, nVarId, start, count, pszValue);
11104 0 : NCDF_ERR(status);
11105 : }
11106 : else
11107 : {
11108 8 : switch (nVarType)
11109 : {
11110 0 : case NC_BYTE:
11111 : {
11112 : signed char *pscTemp = static_cast<signed char *>(
11113 0 : CPLCalloc(nVarLen, sizeof(signed char)));
11114 0 : for (size_t i = 0; i < nVarLen; i++)
11115 : {
11116 0 : char *pszTemp = nullptr;
11117 0 : pscTemp[i] = static_cast<signed char>(
11118 0 : strtol(papszValues[i], &pszTemp, 10));
11119 : }
11120 : status =
11121 0 : nc_put_vara_schar(nCdfId, nVarId, start, count, pscTemp);
11122 0 : NCDF_ERR(status);
11123 0 : CPLFree(pscTemp);
11124 0 : break;
11125 : }
11126 0 : case NC_SHORT:
11127 : {
11128 : short *psTemp =
11129 0 : static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
11130 0 : for (size_t i = 0; i < nVarLen; i++)
11131 : {
11132 0 : char *pszTemp = nullptr;
11133 0 : psTemp[i] = static_cast<short>(
11134 0 : strtol(papszValues[i], &pszTemp, 10));
11135 : }
11136 : status =
11137 0 : nc_put_vara_short(nCdfId, nVarId, start, count, psTemp);
11138 0 : NCDF_ERR(status);
11139 0 : CPLFree(psTemp);
11140 0 : break;
11141 : }
11142 3 : case NC_INT:
11143 : {
11144 : int *pnTemp =
11145 3 : static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
11146 11 : for (size_t i = 0; i < nVarLen; i++)
11147 : {
11148 8 : char *pszTemp = nullptr;
11149 8 : pnTemp[i] =
11150 8 : static_cast<int>(strtol(papszValues[i], &pszTemp, 10));
11151 : }
11152 3 : status = nc_put_vara_int(nCdfId, nVarId, start, count, pnTemp);
11153 3 : NCDF_ERR(status);
11154 3 : CPLFree(pnTemp);
11155 3 : break;
11156 : }
11157 0 : case NC_FLOAT:
11158 : {
11159 : float *pfTemp =
11160 0 : static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
11161 0 : for (size_t i = 0; i < nVarLen; i++)
11162 : {
11163 0 : char *pszTemp = nullptr;
11164 0 : pfTemp[i] =
11165 0 : static_cast<float>(CPLStrtod(papszValues[i], &pszTemp));
11166 : }
11167 : status =
11168 0 : nc_put_vara_float(nCdfId, nVarId, start, count, pfTemp);
11169 0 : NCDF_ERR(status);
11170 0 : CPLFree(pfTemp);
11171 0 : break;
11172 : }
11173 5 : case NC_DOUBLE:
11174 : {
11175 : double *pdfTemp =
11176 5 : static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
11177 19 : for (size_t i = 0; i < nVarLen; i++)
11178 : {
11179 14 : char *pszTemp = nullptr;
11180 14 : pdfTemp[i] = CPLStrtod(papszValues[i], &pszTemp);
11181 : }
11182 : status =
11183 5 : nc_put_vara_double(nCdfId, nVarId, start, count, pdfTemp);
11184 5 : NCDF_ERR(status);
11185 5 : CPLFree(pdfTemp);
11186 5 : break;
11187 : }
11188 0 : default:
11189 : {
11190 0 : int nTmpFormat = 0;
11191 0 : status = nc_inq_format(nCdfId, &nTmpFormat);
11192 0 : NCDF_ERR(status);
11193 0 : if (nTmpFormat == NCDF_FORMAT_NC4)
11194 : {
11195 0 : switch (nVarType)
11196 : {
11197 0 : case NC_STRING:
11198 : {
11199 : status =
11200 0 : nc_put_vara_string(nCdfId, nVarId, start, count,
11201 : (const char **)papszValues);
11202 0 : NCDF_ERR(status);
11203 0 : break;
11204 : }
11205 0 : case NC_UBYTE:
11206 : {
11207 : unsigned char *pucTemp =
11208 : static_cast<unsigned char *>(
11209 0 : CPLCalloc(nVarLen, sizeof(unsigned char)));
11210 0 : for (size_t i = 0; i < nVarLen; i++)
11211 : {
11212 0 : char *pszTemp = nullptr;
11213 0 : pucTemp[i] = static_cast<unsigned char>(
11214 0 : strtoul(papszValues[i], &pszTemp, 10));
11215 : }
11216 0 : status = nc_put_vara_uchar(nCdfId, nVarId, start,
11217 : count, pucTemp);
11218 0 : NCDF_ERR(status);
11219 0 : CPLFree(pucTemp);
11220 0 : break;
11221 : }
11222 0 : case NC_USHORT:
11223 : {
11224 : unsigned short *pusTemp =
11225 : static_cast<unsigned short *>(
11226 0 : CPLCalloc(nVarLen, sizeof(unsigned short)));
11227 0 : for (size_t i = 0; i < nVarLen; i++)
11228 : {
11229 0 : char *pszTemp = nullptr;
11230 0 : pusTemp[i] = static_cast<unsigned short>(
11231 0 : strtoul(papszValues[i], &pszTemp, 10));
11232 : }
11233 0 : status = nc_put_vara_ushort(nCdfId, nVarId, start,
11234 : count, pusTemp);
11235 0 : NCDF_ERR(status);
11236 0 : CPLFree(pusTemp);
11237 0 : break;
11238 : }
11239 0 : case NC_UINT:
11240 : {
11241 : unsigned int *punTemp = static_cast<unsigned int *>(
11242 0 : CPLCalloc(nVarLen, sizeof(unsigned int)));
11243 0 : for (size_t i = 0; i < nVarLen; i++)
11244 : {
11245 0 : char *pszTemp = nullptr;
11246 0 : punTemp[i] = static_cast<unsigned int>(
11247 0 : strtoul(papszValues[i], &pszTemp, 10));
11248 : }
11249 0 : status = nc_put_vara_uint(nCdfId, nVarId, start,
11250 : count, punTemp);
11251 0 : NCDF_ERR(status);
11252 0 : CPLFree(punTemp);
11253 0 : break;
11254 : }
11255 0 : default:
11256 0 : if (papszValues)
11257 0 : CSLDestroy(papszValues);
11258 0 : return CE_Failure;
11259 : break;
11260 : }
11261 : }
11262 0 : break;
11263 : }
11264 : }
11265 : }
11266 :
11267 8 : if (papszValues)
11268 8 : CSLDestroy(papszValues);
11269 :
11270 8 : return CE_None;
11271 : }
11272 :
11273 : /************************************************************************/
11274 : /* GetDefaultNoDataValue() */
11275 : /************************************************************************/
11276 :
11277 187 : double NCDFGetDefaultNoDataValue(int nCdfId, int nVarId, int nVarType,
11278 : bool &bGotNoData)
11279 :
11280 : {
11281 187 : int nNoFill = 0;
11282 187 : double dfNoData = 0.0;
11283 :
11284 187 : switch (nVarType)
11285 : {
11286 0 : case NC_CHAR:
11287 : case NC_BYTE:
11288 : case NC_UBYTE:
11289 : // Don't do default fill-values for bytes, too risky.
11290 : // This function should not be called in those cases.
11291 0 : CPLAssert(false);
11292 : break;
11293 24 : case NC_SHORT:
11294 : {
11295 24 : short nFillVal = 0;
11296 24 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11297 : NC_NOERR)
11298 : {
11299 24 : if (!nNoFill)
11300 : {
11301 23 : bGotNoData = true;
11302 23 : dfNoData = nFillVal;
11303 : }
11304 : }
11305 : else
11306 0 : dfNoData = NC_FILL_SHORT;
11307 24 : break;
11308 : }
11309 26 : case NC_INT:
11310 : {
11311 26 : int nFillVal = 0;
11312 26 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11313 : NC_NOERR)
11314 : {
11315 26 : if (!nNoFill)
11316 : {
11317 25 : bGotNoData = true;
11318 25 : dfNoData = nFillVal;
11319 : }
11320 : }
11321 : else
11322 0 : dfNoData = NC_FILL_INT;
11323 26 : break;
11324 : }
11325 70 : case NC_FLOAT:
11326 : {
11327 70 : float fFillVal = 0;
11328 70 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &fFillVal) ==
11329 : NC_NOERR)
11330 : {
11331 70 : if (!nNoFill)
11332 : {
11333 66 : bGotNoData = true;
11334 66 : dfNoData = fFillVal;
11335 : }
11336 : }
11337 : else
11338 0 : dfNoData = NC_FILL_FLOAT;
11339 70 : break;
11340 : }
11341 34 : case NC_DOUBLE:
11342 : {
11343 34 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &dfNoData) ==
11344 : NC_NOERR)
11345 : {
11346 34 : if (!nNoFill)
11347 : {
11348 34 : bGotNoData = true;
11349 : }
11350 : }
11351 : else
11352 0 : dfNoData = NC_FILL_DOUBLE;
11353 34 : break;
11354 : }
11355 7 : case NC_USHORT:
11356 : {
11357 7 : unsigned short nFillVal = 0;
11358 7 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11359 : NC_NOERR)
11360 : {
11361 7 : if (!nNoFill)
11362 : {
11363 7 : bGotNoData = true;
11364 7 : dfNoData = nFillVal;
11365 : }
11366 : }
11367 : else
11368 0 : dfNoData = NC_FILL_USHORT;
11369 7 : break;
11370 : }
11371 7 : case NC_UINT:
11372 : {
11373 7 : unsigned int nFillVal = 0;
11374 7 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11375 : NC_NOERR)
11376 : {
11377 7 : if (!nNoFill)
11378 : {
11379 7 : bGotNoData = true;
11380 7 : dfNoData = nFillVal;
11381 : }
11382 : }
11383 : else
11384 0 : dfNoData = NC_FILL_UINT;
11385 7 : break;
11386 : }
11387 19 : default:
11388 19 : dfNoData = 0.0;
11389 19 : break;
11390 : }
11391 :
11392 187 : return dfNoData;
11393 : }
11394 :
11395 : /************************************************************************/
11396 : /* NCDFGetDefaultNoDataValueAsInt64() */
11397 : /************************************************************************/
11398 :
11399 2 : int64_t NCDFGetDefaultNoDataValueAsInt64(int nCdfId, int nVarId,
11400 : bool &bGotNoData)
11401 :
11402 : {
11403 2 : int nNoFill = 0;
11404 2 : long long nFillVal = 0;
11405 2 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
11406 : {
11407 2 : if (!nNoFill)
11408 : {
11409 2 : bGotNoData = true;
11410 2 : return static_cast<int64_t>(nFillVal);
11411 : }
11412 : }
11413 : else
11414 0 : return static_cast<int64_t>(NC_FILL_INT64);
11415 0 : return 0;
11416 : }
11417 :
11418 : /************************************************************************/
11419 : /* NCDFGetDefaultNoDataValueAsUInt64() */
11420 : /************************************************************************/
11421 :
11422 1 : uint64_t NCDFGetDefaultNoDataValueAsUInt64(int nCdfId, int nVarId,
11423 : bool &bGotNoData)
11424 :
11425 : {
11426 1 : int nNoFill = 0;
11427 1 : unsigned long long nFillVal = 0;
11428 1 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
11429 : {
11430 1 : if (!nNoFill)
11431 : {
11432 1 : bGotNoData = true;
11433 1 : return static_cast<uint64_t>(nFillVal);
11434 : }
11435 : }
11436 : else
11437 0 : return static_cast<uint64_t>(NC_FILL_UINT64);
11438 0 : return 0;
11439 : }
11440 :
11441 10784 : static int NCDFDoesVarContainAttribVal(int nCdfId,
11442 : const char *const *papszAttribNames,
11443 : const char *const *papszAttribValues,
11444 : int nVarId, const char *pszVarName,
11445 : bool bStrict = true)
11446 : {
11447 10784 : if (nVarId == -1 && pszVarName != nullptr)
11448 7894 : NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
11449 :
11450 10784 : if (nVarId == -1)
11451 878 : return -1;
11452 :
11453 9906 : bool bFound = false;
11454 46332 : for (int i = 0; !bFound && papszAttribNames != nullptr &&
11455 44192 : papszAttribNames[i] != nullptr;
11456 : i++)
11457 : {
11458 36426 : char *pszTemp = nullptr;
11459 36426 : if (NCDFGetAttr(nCdfId, nVarId, papszAttribNames[i], &pszTemp) ==
11460 52080 : CE_None &&
11461 15654 : pszTemp != nullptr)
11462 : {
11463 15654 : if (bStrict)
11464 : {
11465 15654 : if (EQUAL(pszTemp, papszAttribValues[i]))
11466 2140 : bFound = true;
11467 : }
11468 : else
11469 : {
11470 0 : if (EQUALN(pszTemp, papszAttribValues[i],
11471 : strlen(papszAttribValues[i])))
11472 0 : bFound = true;
11473 : }
11474 15654 : CPLFree(pszTemp);
11475 : }
11476 : }
11477 9906 : return bFound;
11478 : }
11479 :
11480 1933 : static int NCDFDoesVarContainAttribVal2(int nCdfId, const char *papszAttribName,
11481 : const char *const *papszAttribValues,
11482 : int nVarId, const char *pszVarName,
11483 : int bStrict = true)
11484 : {
11485 1933 : if (nVarId == -1 && pszVarName != nullptr)
11486 1550 : NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
11487 :
11488 1933 : if (nVarId == -1)
11489 0 : return -1;
11490 :
11491 1933 : bool bFound = false;
11492 1933 : char *pszTemp = nullptr;
11493 2294 : if (NCDFGetAttr(nCdfId, nVarId, papszAttribName, &pszTemp) != CE_None ||
11494 361 : pszTemp == nullptr)
11495 1572 : return FALSE;
11496 :
11497 7323 : for (int i = 0; !bFound && i < CSLCount(papszAttribValues); i++)
11498 : {
11499 6962 : if (bStrict)
11500 : {
11501 6934 : if (EQUAL(pszTemp, papszAttribValues[i]))
11502 30 : bFound = true;
11503 : }
11504 : else
11505 : {
11506 28 : if (EQUALN(pszTemp, papszAttribValues[i],
11507 : strlen(papszAttribValues[i])))
11508 0 : bFound = true;
11509 : }
11510 : }
11511 :
11512 361 : CPLFree(pszTemp);
11513 :
11514 361 : return bFound;
11515 : }
11516 :
11517 876 : static bool NCDFEqual(const char *papszName, const char *const *papszValues)
11518 : {
11519 876 : if (papszName == nullptr || EQUAL(papszName, ""))
11520 0 : return false;
11521 :
11522 2392 : for (int i = 0; papszValues && papszValues[i]; ++i)
11523 : {
11524 1636 : if (EQUAL(papszName, papszValues[i]))
11525 120 : return true;
11526 : }
11527 :
11528 756 : return false;
11529 : }
11530 :
11531 : // Test that a variable is longitude/latitude coordinate,
11532 : // following CF 4.1 and 4.2.
11533 3638 : bool NCDFIsVarLongitude(int nCdfId, int nVarId, const char *pszVarName)
11534 : {
11535 : // Check for matching attributes.
11536 3638 : int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLongitudeAttribNames,
11537 : papszCFLongitudeAttribValues, nVarId,
11538 : pszVarName);
11539 : // If not found using attributes then check using var name
11540 : // unless GDAL_NETCDF_VERIFY_DIMS=STRICT.
11541 3638 : if (bVal == -1)
11542 : {
11543 280 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11544 : "STRICT"))
11545 280 : bVal = NCDFEqual(pszVarName, papszCFLongitudeVarNames);
11546 : else
11547 0 : bVal = FALSE;
11548 : }
11549 3358 : else if (bVal)
11550 : {
11551 : // Check that the units is not 'm' or '1'. See #6759
11552 737 : char *pszTemp = nullptr;
11553 1096 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11554 359 : pszTemp != nullptr)
11555 : {
11556 359 : if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
11557 97 : bVal = false;
11558 359 : CPLFree(pszTemp);
11559 : }
11560 : }
11561 :
11562 3638 : return CPL_TO_BOOL(bVal);
11563 : }
11564 :
11565 2095 : bool NCDFIsVarLatitude(int nCdfId, int nVarId, const char *pszVarName)
11566 : {
11567 2095 : int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLatitudeAttribNames,
11568 : papszCFLatitudeAttribValues, nVarId,
11569 : pszVarName);
11570 2095 : if (bVal == -1)
11571 : {
11572 163 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11573 : "STRICT"))
11574 163 : bVal = NCDFEqual(pszVarName, papszCFLatitudeVarNames);
11575 : else
11576 0 : bVal = FALSE;
11577 : }
11578 1932 : else if (bVal)
11579 : {
11580 : // Check that the units is not 'm' or '1'. See #6759
11581 505 : char *pszTemp = nullptr;
11582 640 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11583 135 : pszTemp != nullptr)
11584 : {
11585 135 : if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
11586 36 : bVal = false;
11587 135 : CPLFree(pszTemp);
11588 : }
11589 : }
11590 :
11591 2095 : return CPL_TO_BOOL(bVal);
11592 : }
11593 :
11594 2249 : bool NCDFIsVarProjectionX(int nCdfId, int nVarId, const char *pszVarName)
11595 : {
11596 2249 : int bVal = NCDFDoesVarContainAttribVal(
11597 : nCdfId, papszCFProjectionXAttribNames, papszCFProjectionXAttribValues,
11598 : nVarId, pszVarName);
11599 2249 : if (bVal == -1)
11600 : {
11601 274 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11602 : "STRICT"))
11603 274 : bVal = NCDFEqual(pszVarName, papszCFProjectionXVarNames);
11604 : else
11605 0 : bVal = FALSE;
11606 : }
11607 1975 : else if (bVal)
11608 : {
11609 : // Check that the units is not '1'
11610 364 : char *pszTemp = nullptr;
11611 518 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11612 154 : pszTemp != nullptr)
11613 : {
11614 154 : if (EQUAL(pszTemp, "1"))
11615 5 : bVal = false;
11616 154 : CPLFree(pszTemp);
11617 : }
11618 : }
11619 :
11620 2249 : return CPL_TO_BOOL(bVal);
11621 : }
11622 :
11623 1601 : bool NCDFIsVarProjectionY(int nCdfId, int nVarId, const char *pszVarName)
11624 : {
11625 1601 : int bVal = NCDFDoesVarContainAttribVal(
11626 : nCdfId, papszCFProjectionYAttribNames, papszCFProjectionYAttribValues,
11627 : nVarId, pszVarName);
11628 1601 : if (bVal == -1)
11629 : {
11630 159 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11631 : "STRICT"))
11632 159 : bVal = NCDFEqual(pszVarName, papszCFProjectionYVarNames);
11633 : else
11634 0 : bVal = FALSE;
11635 : }
11636 1442 : else if (bVal)
11637 : {
11638 : // Check that the units is not '1'
11639 363 : char *pszTemp = nullptr;
11640 516 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11641 153 : pszTemp != nullptr)
11642 : {
11643 153 : if (EQUAL(pszTemp, "1"))
11644 5 : bVal = false;
11645 153 : CPLFree(pszTemp);
11646 : }
11647 : }
11648 :
11649 1601 : return CPL_TO_BOOL(bVal);
11650 : }
11651 :
11652 : /* test that a variable is a vertical coordinate, following CF 4.3 */
11653 1007 : bool NCDFIsVarVerticalCoord(int nCdfId, int nVarId, const char *pszVarName)
11654 : {
11655 : /* check for matching attributes */
11656 1007 : if (NCDFDoesVarContainAttribVal(nCdfId, papszCFVerticalAttribNames,
11657 : papszCFVerticalAttribValues, nVarId,
11658 1007 : pszVarName))
11659 72 : return true;
11660 : /* check for matching units */
11661 935 : else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
11662 : papszCFVerticalUnitsValues, nVarId,
11663 935 : pszVarName))
11664 30 : return true;
11665 : /* check for matching standard name */
11666 905 : else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_STD_NAME,
11667 : papszCFVerticalStandardNameValues,
11668 905 : nVarId, pszVarName))
11669 0 : return true;
11670 : else
11671 905 : return false;
11672 : }
11673 :
11674 : /* test that a variable is a time coordinate, following CF 4.4 */
11675 194 : bool NCDFIsVarTimeCoord(int nCdfId, int nVarId, const char *pszVarName)
11676 : {
11677 : /* check for matching attributes */
11678 194 : if (NCDFDoesVarContainAttribVal(nCdfId, papszCFTimeAttribNames,
11679 : papszCFTimeAttribValues, nVarId,
11680 194 : pszVarName))
11681 101 : return true;
11682 : /* check for matching units */
11683 93 : else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
11684 : papszCFTimeUnitsValues, nVarId,
11685 93 : pszVarName, false))
11686 0 : return true;
11687 : else
11688 93 : return false;
11689 : }
11690 :
11691 : // Parse a string, and return as a string list.
11692 : // If it an array of the form {a,b}, then tokenize it.
11693 : // Otherwise, return a copy.
11694 182 : static char **NCDFTokenizeArray(const char *pszValue)
11695 : {
11696 182 : if (pszValue == nullptr || EQUAL(pszValue, ""))
11697 50 : return nullptr;
11698 :
11699 132 : char **papszValues = nullptr;
11700 132 : const int nLen = static_cast<int>(strlen(pszValue));
11701 :
11702 132 : if (pszValue[0] == '{' && nLen > 2 && pszValue[nLen - 1] == '}')
11703 : {
11704 41 : char *pszTemp = static_cast<char *>(CPLMalloc((nLen - 2) + 1));
11705 41 : strncpy(pszTemp, pszValue + 1, nLen - 2);
11706 41 : pszTemp[nLen - 2] = '\0';
11707 41 : papszValues = CSLTokenizeString2(pszTemp, ",", CSLT_ALLOWEMPTYTOKENS);
11708 41 : CPLFree(pszTemp);
11709 : }
11710 : else
11711 : {
11712 91 : papszValues = reinterpret_cast<char **>(CPLCalloc(2, sizeof(char *)));
11713 91 : papszValues[0] = CPLStrdup(pszValue);
11714 91 : papszValues[1] = nullptr;
11715 : }
11716 :
11717 132 : return papszValues;
11718 : }
11719 :
11720 : // Open a NetCDF subdataset from full path /group1/group2/.../groupn/var.
11721 : // Leading slash is optional.
11722 399 : static CPLErr NCDFOpenSubDataset(int nCdfId, const char *pszSubdatasetName,
11723 : int *pnGroupId, int *pnVarId)
11724 : {
11725 399 : *pnGroupId = -1;
11726 399 : *pnVarId = -1;
11727 :
11728 : // Open group.
11729 399 : char *pszGroupFullName = CPLStrdup(CPLGetPath(pszSubdatasetName));
11730 : // Add a leading slash if needed.
11731 399 : if (pszGroupFullName[0] != '/')
11732 : {
11733 383 : char *old = pszGroupFullName;
11734 383 : pszGroupFullName = CPLStrdup(CPLSPrintf("/%s", pszGroupFullName));
11735 383 : CPLFree(old);
11736 : }
11737 : // Detect root group.
11738 399 : if (EQUAL(pszGroupFullName, "/"))
11739 : {
11740 383 : *pnGroupId = nCdfId;
11741 383 : CPLFree(pszGroupFullName);
11742 : }
11743 : else
11744 : {
11745 16 : int status = nc_inq_grp_full_ncid(nCdfId, pszGroupFullName, pnGroupId);
11746 16 : CPLFree(pszGroupFullName);
11747 16 : NCDF_ERR_RET(status);
11748 : }
11749 :
11750 : // Open var.
11751 399 : const char *pszVarName = CPLGetFilename(pszSubdatasetName);
11752 399 : NCDF_ERR_RET(nc_inq_varid(*pnGroupId, pszVarName, pnVarId));
11753 :
11754 399 : return CE_None;
11755 : }
11756 :
11757 : // Get all dimensions visible from a given NetCDF (or group) ID and any of
11758 : // its parents.
11759 341 : static CPLErr NCDFGetVisibleDims(int nGroupId, int *pnDims, int **ppanDimIds)
11760 : {
11761 341 : int nDims = 0;
11762 341 : int *panDimIds = nullptr;
11763 341 : NCDF_ERR_RET(nc_inq_dimids(nGroupId, &nDims, nullptr, true));
11764 :
11765 341 : panDimIds = static_cast<int *>(CPLMalloc(nDims * sizeof(int)));
11766 :
11767 341 : int status = nc_inq_dimids(nGroupId, nullptr, panDimIds, true);
11768 341 : if (status != NC_NOERR)
11769 0 : CPLFree(panDimIds);
11770 341 : NCDF_ERR_RET(status);
11771 :
11772 341 : *pnDims = nDims;
11773 341 : *ppanDimIds = panDimIds;
11774 :
11775 341 : return CE_None;
11776 : }
11777 :
11778 : // Get direct sub-groups IDs of a given NetCDF (or group) ID.
11779 : // Consider only direct children, does not get children of children.
11780 2995 : static CPLErr NCDFGetSubGroups(int nGroupId, int *pnSubGroups,
11781 : int **ppanSubGroupIds)
11782 : {
11783 2995 : *pnSubGroups = 0;
11784 2995 : *ppanSubGroupIds = nullptr;
11785 :
11786 : int nSubGroups;
11787 2995 : NCDF_ERR_RET(nc_inq_grps(nGroupId, &nSubGroups, nullptr));
11788 : int *panSubGroupIds =
11789 2995 : static_cast<int *>(CPLMalloc(nSubGroups * sizeof(int)));
11790 2995 : NCDF_ERR_RET(nc_inq_grps(nGroupId, nullptr, panSubGroupIds));
11791 2995 : *pnSubGroups = nSubGroups;
11792 2995 : *ppanSubGroupIds = panSubGroupIds;
11793 :
11794 2995 : return CE_None;
11795 : }
11796 :
11797 : // Get the full name of a given NetCDF (or group) ID
11798 : // (e.g. /group1/group2/.../groupn).
11799 : // bNC3Compat remove the leading slash for top-level variables for
11800 : // backward compatibility (top-level variables are the ones in the root group).
11801 15024 : static CPLErr NCDFGetGroupFullName(int nGroupId, char **ppszFullName,
11802 : bool bNC3Compat)
11803 : {
11804 15024 : *ppszFullName = nullptr;
11805 :
11806 : size_t nFullNameLen;
11807 15024 : NCDF_ERR_RET(nc_inq_grpname_len(nGroupId, &nFullNameLen));
11808 15024 : *ppszFullName =
11809 15024 : static_cast<char *>(CPLMalloc((nFullNameLen + 1) * sizeof(char)));
11810 15024 : int status = nc_inq_grpname_full(nGroupId, &nFullNameLen, *ppszFullName);
11811 15024 : if (status != NC_NOERR)
11812 : {
11813 0 : CPLFree(*ppszFullName);
11814 0 : *ppszFullName = nullptr;
11815 0 : NCDF_ERR_RET(status);
11816 : }
11817 :
11818 15024 : if (bNC3Compat && EQUAL(*ppszFullName, "/"))
11819 7677 : (*ppszFullName)[0] = '\0';
11820 :
11821 15024 : return CE_None;
11822 : }
11823 :
11824 7159 : CPLString NCDFGetGroupFullName(int nGroupId)
11825 : {
11826 7159 : char *pszFullname = nullptr;
11827 7159 : NCDFGetGroupFullName(nGroupId, &pszFullname, false);
11828 7159 : CPLString osRet(pszFullname ? pszFullname : "");
11829 7159 : CPLFree(pszFullname);
11830 14318 : return osRet;
11831 : }
11832 :
11833 : // Get the full name of a given NetCDF variable ID
11834 : // (e.g. /group1/group2/.../groupn/var).
11835 : // Handle also NC_GLOBAL as nVarId.
11836 : // bNC3Compat remove the leading slash for top-level variables for
11837 : // backward compatibility (top-level variables are the ones in the root group).
11838 7816 : static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId, char **ppszFullName,
11839 : bool bNC3Compat)
11840 : {
11841 7816 : *ppszFullName = nullptr;
11842 7816 : char *pszGroupFullName = nullptr;
11843 7816 : ERR_RET(NCDFGetGroupFullName(nGroupId, &pszGroupFullName, bNC3Compat));
11844 : char szVarName[NC_MAX_NAME + 1];
11845 7816 : if (nVarId == NC_GLOBAL)
11846 : {
11847 1060 : strcpy(szVarName, "NC_GLOBAL");
11848 : }
11849 : else
11850 : {
11851 6756 : int status = nc_inq_varname(nGroupId, nVarId, szVarName);
11852 6756 : if (status != NC_NOERR)
11853 : {
11854 0 : CPLFree(pszGroupFullName);
11855 0 : NCDF_ERR_RET(status);
11856 : }
11857 : }
11858 7816 : const char *pszSep = "/";
11859 7816 : if (EQUAL(pszGroupFullName, "/") || EQUAL(pszGroupFullName, ""))
11860 7630 : pszSep = "";
11861 7816 : *ppszFullName =
11862 7816 : CPLStrdup(CPLSPrintf("%s%s%s", pszGroupFullName, pszSep, szVarName));
11863 7816 : CPLFree(pszGroupFullName);
11864 7816 : return CE_None;
11865 : }
11866 :
11867 : // Get the NetCDF root group ID of a given group ID.
11868 0 : static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId)
11869 : {
11870 0 : *pnRootGroupId = -1;
11871 : // Recurse on parent group.
11872 : int nParentGroupId;
11873 0 : int status = nc_inq_grp_parent(nStartGroupId, &nParentGroupId);
11874 0 : if (status == NC_NOERR)
11875 0 : return NCDFGetRootGroup(nParentGroupId, pnRootGroupId);
11876 0 : else if (status != NC_ENOGRP)
11877 0 : NCDF_ERR_RET(status);
11878 : else // No more parent group.
11879 : {
11880 0 : *pnRootGroupId = nStartGroupId;
11881 : }
11882 :
11883 0 : return CE_None;
11884 : }
11885 :
11886 : // Implementation of NCDFResolveVar/Att.
11887 12778 : static CPLErr NCDFResolveElem(int nStartGroupId, const char *pszVar,
11888 : const char *pszAtt, int *pnGroupId, int *pnId,
11889 : bool bMandatory)
11890 : {
11891 12778 : if (!pszVar && !pszAtt)
11892 : {
11893 0 : CPLError(CE_Failure, CPLE_IllegalArg,
11894 : "pszVar and pszAtt NCDFResolveElem() args are both null.");
11895 0 : return CE_Failure;
11896 : }
11897 :
11898 : enum
11899 : {
11900 : NCRM_PARENT,
11901 : NCRM_WIDTH_WISE
11902 12778 : } eNCResolveMode = NCRM_PARENT;
11903 :
11904 25556 : std::queue<int> aoQueueGroupIdsToVisit;
11905 12778 : aoQueueGroupIdsToVisit.push(nStartGroupId);
11906 :
11907 14426 : while (!aoQueueGroupIdsToVisit.empty())
11908 : {
11909 : // Get the first group of the FIFO queue.
11910 12925 : *pnGroupId = aoQueueGroupIdsToVisit.front();
11911 12925 : aoQueueGroupIdsToVisit.pop();
11912 :
11913 : // Look if this group contains the searched element.
11914 : int status;
11915 12925 : if (pszVar)
11916 12711 : status = nc_inq_varid(*pnGroupId, pszVar, pnId);
11917 : else // pszAtt != nullptr.
11918 214 : status = nc_inq_attid(*pnGroupId, NC_GLOBAL, pszAtt, pnId);
11919 :
11920 12925 : if (status == NC_NOERR)
11921 : {
11922 11277 : return CE_None;
11923 : }
11924 1648 : else if ((pszVar && status != NC_ENOTVAR) ||
11925 211 : (pszAtt && status != NC_ENOTATT))
11926 : {
11927 0 : NCDF_ERR(status);
11928 : }
11929 : // Element not found, in NC4 case we must search in other groups
11930 : // following the CF logic.
11931 :
11932 : // The first resolve mode consists to search on parent groups.
11933 1648 : if (eNCResolveMode == NCRM_PARENT)
11934 : {
11935 1548 : int nParentGroupId = -1;
11936 1548 : int status2 = nc_inq_grp_parent(*pnGroupId, &nParentGroupId);
11937 1548 : if (status2 == NC_NOERR)
11938 45 : aoQueueGroupIdsToVisit.push(nParentGroupId);
11939 1503 : else if (status2 != NC_ENOGRP)
11940 0 : NCDF_ERR(status2);
11941 1503 : else if (pszVar)
11942 : // When resolving a variable, if there is no more
11943 : // parent group then we switch to width-wise search mode
11944 : // starting from the latest found parent group.
11945 1295 : eNCResolveMode = NCRM_WIDTH_WISE;
11946 : }
11947 :
11948 : // The second resolve mode is a width-wise search.
11949 1648 : if (eNCResolveMode == NCRM_WIDTH_WISE)
11950 : {
11951 : // Enqueue all direct sub-groups.
11952 1395 : int nSubGroups = 0;
11953 1395 : int *panSubGroupIds = nullptr;
11954 1395 : NCDFGetSubGroups(*pnGroupId, &nSubGroups, &panSubGroupIds);
11955 1497 : for (int i = 0; i < nSubGroups; i++)
11956 102 : aoQueueGroupIdsToVisit.push(panSubGroupIds[i]);
11957 1395 : CPLFree(panSubGroupIds);
11958 : }
11959 : }
11960 :
11961 1501 : if (bMandatory)
11962 : {
11963 0 : char *pszStartGroupFullName = nullptr;
11964 0 : NCDFGetGroupFullName(nStartGroupId, &pszStartGroupFullName);
11965 0 : CPLError(CE_Failure, CPLE_AppDefined,
11966 : "Cannot resolve mandatory %s %s from group %s",
11967 : (pszVar ? pszVar : pszAtt),
11968 : (pszVar ? "variable" : "attribute"),
11969 0 : (pszStartGroupFullName ? pszStartGroupFullName : ""));
11970 0 : CPLFree(pszStartGroupFullName);
11971 : }
11972 :
11973 1501 : *pnGroupId = -1;
11974 1501 : *pnId = -1;
11975 1501 : return CE_Failure;
11976 : }
11977 :
11978 : // Resolve a variable name from a given starting group following the CF logic:
11979 : // - if var name is an absolute path then directly open it
11980 : // - first search in the starting group and its parent groups
11981 : // - then if there is no more parent group we switch to a width-wise search
11982 : // mode starting from the latest found parent group.
11983 : // The full CF logic is described here:
11984 : // https://github.com/diwg/cf2/blob/master/group/cf2-group.adoc#scope
11985 : // If bMandatory then print an error if resolving fails.
11986 : // TODO: implement support of relative paths.
11987 : // TODO: to follow strictly the CF logic, when searching for a coordinate
11988 : // variable, we must stop the parent search mode once the corresponding
11989 : // dimension is found and start the width-wise search from this group.
11990 : // TODO: to follow strictly the CF logic, when searching in width-wise mode
11991 : // we should skip every groups already visited during the parent
11992 : // search mode (but revisiting them should have no impact so we could
11993 : // let as it is if it is simpler...)
11994 : // TODO: CF specifies that the width-wise search order is "left-to-right" so
11995 : // maybe we must sort sibling groups alphabetically? but maybe not
11996 : // necessary if nc_inq_grps() already sort them?
11997 12567 : CPLErr NCDFResolveVar(int nStartGroupId, const char *pszVar, int *pnGroupId,
11998 : int *pnVarId, bool bMandatory)
11999 : {
12000 12567 : *pnGroupId = -1;
12001 12567 : *pnVarId = -1;
12002 12567 : int nGroupId = nStartGroupId, nVarId;
12003 12567 : if (pszVar[0] == '/')
12004 : {
12005 : // This is an absolute path: we can open the var directly.
12006 : int nRootGroupId;
12007 0 : ERR_RET(NCDFGetRootGroup(nStartGroupId, &nRootGroupId));
12008 0 : ERR_RET(NCDFOpenSubDataset(nRootGroupId, pszVar, &nGroupId, &nVarId));
12009 : }
12010 : else
12011 : {
12012 : // We have to search the variable following the CF logic.
12013 12567 : ERR_RET(NCDFResolveElem(nStartGroupId, pszVar, nullptr, &nGroupId,
12014 : &nVarId, bMandatory));
12015 : }
12016 11274 : *pnGroupId = nGroupId;
12017 11274 : *pnVarId = nVarId;
12018 11274 : return CE_None;
12019 : }
12020 :
12021 : // Like NCDFResolveVar but returns directly the var full name.
12022 1326 : static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,
12023 : char **ppszFullName, bool bMandatory)
12024 : {
12025 1326 : *ppszFullName = nullptr;
12026 : int nGroupId, nVarId;
12027 1326 : ERR_RET(
12028 : NCDFResolveVar(nStartGroupId, pszVar, &nGroupId, &nVarId, bMandatory));
12029 1308 : return NCDFGetVarFullName(nGroupId, nVarId, ppszFullName);
12030 : }
12031 :
12032 : // Like NCDFResolveVar but resolves an attribute instead a variable and
12033 : // returns its integer value.
12034 : // Only GLOBAL attributes are supported for the moment.
12035 211 : static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,
12036 : const char *pszAtt, int *pnAtt, bool bMandatory)
12037 : {
12038 211 : int nGroupId = nStartGroupId, nAttId = nStartVarId;
12039 211 : ERR_RET(NCDFResolveElem(nStartGroupId, nullptr, pszAtt, &nGroupId, &nAttId,
12040 : bMandatory));
12041 3 : NCDF_ERR_RET(nc_get_att_int(nGroupId, NC_GLOBAL, pszAtt, pnAtt));
12042 3 : return CE_None;
12043 : }
12044 :
12045 : // Filter variables to keep only valid 2+D raster bands and vector fields in
12046 : // a given a NetCDF (or group) ID and its sub-groups.
12047 : // Coordinate or boundary variables are ignored.
12048 : // It also creates corresponding vector layers.
12049 512 : CPLErr netCDFDataset::FilterVars(
12050 : int nCdfId, bool bKeepRasters, bool bKeepVectors, char **papszIgnoreVars,
12051 : int *pnRasterVars, int *pnGroupId, int *pnVarId, int *pnIgnoredVars,
12052 : std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>
12053 : &oMap2DDimsToGroupAndVar)
12054 : {
12055 512 : int nVars = 0;
12056 512 : int nRasterVars = 0;
12057 512 : NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
12058 :
12059 1024 : std::vector<int> anPotentialVectorVarID;
12060 : // oMapDimIdToCount[x] = number of times dim x is the first dimension of
12061 : // potential vector variables
12062 1024 : std::map<int, int> oMapDimIdToCount;
12063 512 : int nVarXId = -1;
12064 512 : int nVarYId = -1;
12065 512 : int nVarZId = -1;
12066 512 : int nVarTimeId = -1;
12067 512 : int nVarTimeDimId = -1;
12068 512 : bool bIsVectorOnly = true;
12069 512 : int nProfileDimId = -1;
12070 512 : int nParentIndexVarID = -1;
12071 :
12072 3131 : for (int v = 0; v < nVars; v++)
12073 : {
12074 : int nVarDims;
12075 2619 : NCDF_ERR_RET(nc_inq_varndims(nCdfId, v, &nVarDims));
12076 : // Should we ignore this variable?
12077 : char szTemp[NC_MAX_NAME + 1];
12078 2619 : szTemp[0] = '\0';
12079 2619 : NCDF_ERR_RET(nc_inq_varname(nCdfId, v, szTemp));
12080 :
12081 2619 : if (strstr(szTemp, "_node_coordinates") ||
12082 2619 : strstr(szTemp, "_node_count"))
12083 : {
12084 : // Ignore CF-1.8 Simple Geometries helper variables
12085 69 : continue;
12086 : }
12087 :
12088 3822 : if (nVarDims == 1 && (NCDFIsVarLongitude(nCdfId, -1, szTemp) ||
12089 1272 : NCDFIsVarProjectionX(nCdfId, -1, szTemp)))
12090 : {
12091 348 : nVarXId = v;
12092 : }
12093 3126 : else if (nVarDims == 1 && (NCDFIsVarLatitude(nCdfId, -1, szTemp) ||
12094 924 : NCDFIsVarProjectionY(nCdfId, -1, szTemp)))
12095 : {
12096 348 : nVarYId = v;
12097 : }
12098 1854 : else if (nVarDims == 1 && NCDFIsVarVerticalCoord(nCdfId, -1, szTemp))
12099 : {
12100 77 : nVarZId = v;
12101 : }
12102 : else
12103 : {
12104 1777 : char *pszVarFullName = nullptr;
12105 1777 : CPLErr eErr = NCDFGetVarFullName(nCdfId, v, &pszVarFullName);
12106 1777 : if (eErr != CE_None)
12107 : {
12108 0 : CPLFree(pszVarFullName);
12109 0 : continue;
12110 : }
12111 : bool bIgnoreVar =
12112 1777 : (CSLFindString(papszIgnoreVars, pszVarFullName) != -1);
12113 1777 : CPLFree(pszVarFullName);
12114 1777 : if (bIgnoreVar)
12115 : {
12116 98 : if (nVarDims == 1 && NCDFIsVarTimeCoord(nCdfId, -1, szTemp))
12117 : {
12118 7 : nVarTimeId = v;
12119 7 : nc_inq_vardimid(nCdfId, v, &nVarTimeDimId);
12120 : }
12121 91 : else if (nVarDims > 1)
12122 : {
12123 87 : (*pnIgnoredVars)++;
12124 87 : CPLDebug("GDAL_netCDF", "variable #%d [%s] was ignored", v,
12125 : szTemp);
12126 : }
12127 : }
12128 : // Only accept 2+D vars.
12129 1679 : else if (nVarDims >= 2)
12130 : {
12131 667 : bool bRasterCandidate = true;
12132 : // Identify variables that might be vector variables
12133 667 : if (nVarDims == 2)
12134 : {
12135 601 : int anDimIds[2] = {-1, -1};
12136 601 : nc_inq_vardimid(nCdfId, v, anDimIds);
12137 :
12138 601 : nc_type vartype = NC_NAT;
12139 601 : nc_inq_vartype(nCdfId, v, &vartype);
12140 :
12141 : char szDimNameFirst[NC_MAX_NAME + 1];
12142 : char szDimNameSecond[NC_MAX_NAME + 1];
12143 601 : szDimNameFirst[0] = '\0';
12144 601 : szDimNameSecond[0] = '\0';
12145 1358 : if (vartype == NC_CHAR &&
12146 156 : nc_inq_dimname(nCdfId, anDimIds[0], szDimNameFirst) ==
12147 156 : NC_NOERR &&
12148 156 : nc_inq_dimname(nCdfId, anDimIds[1], szDimNameSecond) ==
12149 156 : NC_NOERR &&
12150 156 : !NCDFIsVarLongitude(nCdfId, -1, szDimNameSecond) &&
12151 156 : !NCDFIsVarProjectionX(nCdfId, -1, szDimNameSecond) &&
12152 913 : !NCDFIsVarLatitude(nCdfId, -1, szDimNameFirst) &&
12153 156 : !NCDFIsVarProjectionY(nCdfId, -1, szDimNameFirst))
12154 : {
12155 156 : anPotentialVectorVarID.push_back(v);
12156 156 : oMapDimIdToCount[anDimIds[0]]++;
12157 156 : if (strstr(szDimNameSecond, "_max_width"))
12158 : {
12159 127 : bRasterCandidate = false;
12160 : }
12161 : else
12162 : {
12163 29 : std::array<int, 3> oKey{anDimIds[0], anDimIds[1],
12164 29 : vartype};
12165 29 : oMap2DDimsToGroupAndVar[oKey].emplace_back(
12166 29 : std::pair(nCdfId, v));
12167 : }
12168 : }
12169 : else
12170 : {
12171 445 : std::array<int, 3> oKey{anDimIds[0], anDimIds[1],
12172 445 : vartype};
12173 445 : oMap2DDimsToGroupAndVar[oKey].emplace_back(
12174 445 : std::pair(nCdfId, v));
12175 445 : bIsVectorOnly = false;
12176 : }
12177 : }
12178 : else
12179 : {
12180 66 : bIsVectorOnly = false;
12181 : }
12182 667 : if (bKeepRasters && bRasterCandidate)
12183 : {
12184 511 : *pnGroupId = nCdfId;
12185 511 : *pnVarId = v;
12186 511 : nRasterVars++;
12187 : }
12188 : }
12189 1012 : else if (nVarDims == 1)
12190 : {
12191 724 : nc_type atttype = NC_NAT;
12192 724 : size_t attlen = 0;
12193 724 : if (nc_inq_att(nCdfId, v, "instance_dimension", &atttype,
12194 14 : &attlen) == NC_NOERR &&
12195 724 : atttype == NC_CHAR && attlen < NC_MAX_NAME)
12196 : {
12197 : char szInstanceDimension[NC_MAX_NAME + 1];
12198 14 : if (nc_get_att_text(nCdfId, v, "instance_dimension",
12199 14 : szInstanceDimension) == NC_NOERR)
12200 : {
12201 14 : szInstanceDimension[attlen] = 0;
12202 14 : int status = nc_inq_dimid(nCdfId, szInstanceDimension,
12203 : &nProfileDimId);
12204 14 : if (status == NC_NOERR)
12205 14 : nParentIndexVarID = v;
12206 : else
12207 0 : nProfileDimId = -1;
12208 14 : if (status == NC_EBADDIM)
12209 0 : CPLError(CE_Warning, CPLE_AppDefined,
12210 : "Attribute instance_dimension='%s' refers "
12211 : "to a non existing dimension",
12212 : szInstanceDimension);
12213 : else
12214 14 : NCDF_ERR(status);
12215 : }
12216 : }
12217 724 : if (v != nParentIndexVarID)
12218 : {
12219 710 : anPotentialVectorVarID.push_back(v);
12220 710 : int nDimId = -1;
12221 710 : nc_inq_vardimid(nCdfId, v, &nDimId);
12222 710 : oMapDimIdToCount[nDimId]++;
12223 : }
12224 : }
12225 : }
12226 : }
12227 :
12228 : // If we are opened in raster-only mode and that there are only 1D or 2D
12229 : // variables and that the 2D variables have no X/Y dim, and all
12230 : // variables refer to the same main dimension (or 2 dimensions for
12231 : // featureType=profile), then it is a pure vector dataset
12232 : CPLString osFeatureType(
12233 512 : CSLFetchNameValueDef(papszMetadata, "NC_GLOBAL#featureType", ""));
12234 401 : if (bKeepRasters && !bKeepVectors && bIsVectorOnly && nRasterVars > 0 &&
12235 913 : !anPotentialVectorVarID.empty() &&
12236 0 : (oMapDimIdToCount.size() == 1 ||
12237 0 : (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2 &&
12238 0 : nProfileDimId >= 0)))
12239 : {
12240 0 : anPotentialVectorVarID.resize(0);
12241 : }
12242 : else
12243 : {
12244 512 : *pnRasterVars += nRasterVars;
12245 : }
12246 :
12247 512 : if (!anPotentialVectorVarID.empty() && bKeepVectors)
12248 : {
12249 : // Take the dimension that is referenced the most times.
12250 64 : if (!(oMapDimIdToCount.size() == 1 ||
12251 27 : (EQUAL(osFeatureType, "profile") &&
12252 26 : oMapDimIdToCount.size() == 2 && nProfileDimId >= 0)))
12253 : {
12254 1 : CPLError(CE_Warning, CPLE_AppDefined,
12255 : "The dataset has several variables that could be "
12256 : "identified as vector fields, but not all share the same "
12257 : "primary dimension. Consequently they will be ignored.");
12258 : }
12259 : else
12260 : {
12261 50 : if (nVarTimeId >= 0 &&
12262 50 : oMapDimIdToCount.find(nVarTimeDimId) != oMapDimIdToCount.end())
12263 : {
12264 1 : anPotentialVectorVarID.push_back(nVarTimeId);
12265 : }
12266 49 : CreateGrpVectorLayers(nCdfId, osFeatureType, anPotentialVectorVarID,
12267 : oMapDimIdToCount, nVarXId, nVarYId, nVarZId,
12268 : nProfileDimId, nParentIndexVarID,
12269 : bKeepRasters);
12270 : }
12271 : }
12272 :
12273 : // Recurse on sub-groups.
12274 512 : int nSubGroups = 0;
12275 512 : int *panSubGroupIds = nullptr;
12276 512 : NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
12277 544 : for (int i = 0; i < nSubGroups; i++)
12278 : {
12279 32 : FilterVars(panSubGroupIds[i], bKeepRasters, bKeepVectors,
12280 : papszIgnoreVars, pnRasterVars, pnGroupId, pnVarId,
12281 : pnIgnoredVars, oMap2DDimsToGroupAndVar);
12282 : }
12283 512 : CPLFree(panSubGroupIds);
12284 :
12285 512 : return CE_None;
12286 : }
12287 :
12288 : // Create vector layers from given potentially identified vector variables
12289 : // resulting from the scanning of a NetCDF (or group) ID.
12290 49 : CPLErr netCDFDataset::CreateGrpVectorLayers(
12291 : int nCdfId, const CPLString &osFeatureType,
12292 : const std::vector<int> &anPotentialVectorVarID,
12293 : const std::map<int, int> &oMapDimIdToCount, int nVarXId, int nVarYId,
12294 : int nVarZId, int nProfileDimId, int nParentIndexVarID, bool bKeepRasters)
12295 : {
12296 49 : char *pszGroupName = nullptr;
12297 49 : NCDFGetGroupFullName(nCdfId, &pszGroupName);
12298 49 : if (pszGroupName == nullptr || pszGroupName[0] == '\0')
12299 : {
12300 47 : CPLFree(pszGroupName);
12301 47 : pszGroupName = CPLStrdup(CPLGetBasename(osFilename));
12302 : }
12303 49 : OGRwkbGeometryType eGType = wkbUnknown;
12304 : CPLString osLayerName = CSLFetchNameValueDef(
12305 98 : papszMetadata, "NC_GLOBAL#ogr_layer_name", pszGroupName);
12306 49 : CPLFree(pszGroupName);
12307 49 : papszMetadata =
12308 49 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_name", nullptr);
12309 :
12310 49 : if (EQUAL(osFeatureType, "point") || EQUAL(osFeatureType, "profile"))
12311 : {
12312 33 : papszMetadata =
12313 33 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#featureType", nullptr);
12314 33 : eGType = wkbPoint;
12315 : }
12316 :
12317 : const char *pszLayerType =
12318 49 : CSLFetchNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_type");
12319 49 : if (pszLayerType != nullptr)
12320 : {
12321 9 : eGType = OGRFromOGCGeomType(pszLayerType);
12322 9 : papszMetadata =
12323 9 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_type", nullptr);
12324 : }
12325 :
12326 : CPLString osGeometryField =
12327 98 : CSLFetchNameValueDef(papszMetadata, "NC_GLOBAL#ogr_geometry_field", "");
12328 49 : papszMetadata =
12329 49 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_geometry_field", nullptr);
12330 :
12331 49 : int nFirstVarId = -1;
12332 49 : int nVectorDim = oMapDimIdToCount.rbegin()->first;
12333 49 : if (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2)
12334 : {
12335 13 : if (nVectorDim == nProfileDimId)
12336 0 : nVectorDim = oMapDimIdToCount.begin()->first;
12337 : }
12338 : else
12339 : {
12340 36 : nProfileDimId = -1;
12341 : }
12342 62 : for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)
12343 : {
12344 62 : int anDimIds[2] = {-1, -1};
12345 62 : nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);
12346 62 : if (nVectorDim == anDimIds[0])
12347 : {
12348 49 : nFirstVarId = anPotentialVectorVarID[j];
12349 49 : break;
12350 : }
12351 : }
12352 :
12353 : // In case where coordinates are explicitly specified for one of the
12354 : // field/variable, use them in priority over the ones that might have been
12355 : // identified above.
12356 49 : char *pszCoordinates = nullptr;
12357 49 : if (NCDFGetAttr(nCdfId, nFirstVarId, "coordinates", &pszCoordinates) ==
12358 : CE_None)
12359 : {
12360 34 : char **papszTokens = NCDFTokenizeCoordinatesAttribute(pszCFCoordinates);
12361 34 : for (int i = 0; papszTokens != nullptr && papszTokens[i] != nullptr;
12362 : i++)
12363 : {
12364 0 : if (NCDFIsVarLongitude(nCdfId, -1, papszTokens[i]) ||
12365 0 : NCDFIsVarProjectionX(nCdfId, -1, papszTokens[i]))
12366 : {
12367 0 : nVarXId = -1;
12368 0 : CPL_IGNORE_RET_VAL(
12369 0 : nc_inq_varid(nCdfId, papszTokens[i], &nVarXId));
12370 : }
12371 0 : else if (NCDFIsVarLatitude(nCdfId, -1, papszTokens[i]) ||
12372 0 : NCDFIsVarProjectionY(nCdfId, -1, papszTokens[i]))
12373 : {
12374 0 : nVarYId = -1;
12375 0 : CPL_IGNORE_RET_VAL(
12376 0 : nc_inq_varid(nCdfId, papszTokens[i], &nVarYId));
12377 : }
12378 0 : else if (NCDFIsVarVerticalCoord(nCdfId, -1, papszTokens[i]))
12379 : {
12380 0 : nVarZId = -1;
12381 0 : CPL_IGNORE_RET_VAL(
12382 0 : nc_inq_varid(nCdfId, papszTokens[i], &nVarZId));
12383 : }
12384 : }
12385 34 : CSLDestroy(papszTokens);
12386 : }
12387 49 : CPLFree(pszCoordinates);
12388 :
12389 : // Check that the X,Y,Z vars share 1D and share the same dimension as
12390 : // attribute variables.
12391 49 : if (nVarXId >= 0 && nVarYId >= 0)
12392 : {
12393 38 : int nVarDimCount = -1;
12394 38 : int nVarDimId = -1;
12395 38 : if (nc_inq_varndims(nCdfId, nVarXId, &nVarDimCount) != NC_NOERR ||
12396 38 : nVarDimCount != 1 ||
12397 38 : nc_inq_vardimid(nCdfId, nVarXId, &nVarDimId) != NC_NOERR ||
12398 38 : nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim) ||
12399 35 : nc_inq_varndims(nCdfId, nVarYId, &nVarDimCount) != NC_NOERR ||
12400 35 : nVarDimCount != 1 ||
12401 111 : nc_inq_vardimid(nCdfId, nVarYId, &nVarDimId) != NC_NOERR ||
12402 35 : nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim))
12403 : {
12404 3 : nVarXId = nVarYId = -1;
12405 : }
12406 69 : else if (nVarZId >= 0 &&
12407 34 : (nc_inq_varndims(nCdfId, nVarZId, &nVarDimCount) != NC_NOERR ||
12408 34 : nVarDimCount != 1 ||
12409 34 : nc_inq_vardimid(nCdfId, nVarZId, &nVarDimId) != NC_NOERR ||
12410 34 : nVarDimId != nVectorDim))
12411 : {
12412 0 : nVarZId = -1;
12413 : }
12414 : }
12415 :
12416 49 : if (eGType == wkbUnknown && nVarXId >= 0 && nVarYId >= 0)
12417 : {
12418 2 : eGType = wkbPoint;
12419 : }
12420 49 : if (eGType == wkbPoint && nVarXId >= 0 && nVarYId >= 0 && nVarZId >= 0)
12421 : {
12422 34 : eGType = wkbPoint25D;
12423 : }
12424 49 : if (eGType == wkbUnknown && osGeometryField.empty())
12425 : {
12426 5 : eGType = wkbNone;
12427 : }
12428 :
12429 : // Read projection info
12430 49 : char **papszMetadataBackup = CSLDuplicate(papszMetadata);
12431 49 : ReadAttributes(nCdfId, nFirstVarId);
12432 49 : if (!this->bSGSupport)
12433 49 : SetProjectionFromVar(nCdfId, nFirstVarId, true);
12434 49 : const char *pszValue = FetchAttr(nCdfId, nFirstVarId, CF_GRD_MAPPING);
12435 49 : char *pszGridMapping = (pszValue ? CPLStrdup(pszValue) : nullptr);
12436 49 : CSLDestroy(papszMetadata);
12437 49 : papszMetadata = papszMetadataBackup;
12438 :
12439 49 : OGRSpatialReference *poSRS = nullptr;
12440 49 : if (!m_oSRS.IsEmpty())
12441 : {
12442 21 : poSRS = m_oSRS.Clone();
12443 : }
12444 : // Reset if there's a 2D raster
12445 49 : m_bHasProjection = false;
12446 49 : m_bHasGeoTransform = false;
12447 :
12448 49 : if (!bKeepRasters)
12449 : {
12450 : // Strip out uninteresting metadata.
12451 45 : papszMetadata =
12452 45 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#Conventions", nullptr);
12453 45 : papszMetadata =
12454 45 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#GDAL", nullptr);
12455 45 : papszMetadata =
12456 45 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#history", nullptr);
12457 : }
12458 :
12459 : std::shared_ptr<netCDFLayer> poLayer(
12460 49 : new netCDFLayer(this, nCdfId, osLayerName, eGType, poSRS));
12461 49 : if (poSRS != nullptr)
12462 21 : poSRS->Release();
12463 49 : poLayer->SetRecordDimID(nVectorDim);
12464 49 : if (wkbFlatten(eGType) == wkbPoint && nVarXId >= 0 && nVarYId >= 0)
12465 : {
12466 35 : poLayer->SetXYZVars(nVarXId, nVarYId, nVarZId);
12467 : }
12468 14 : else if (!osGeometryField.empty())
12469 : {
12470 9 : poLayer->SetWKTGeometryField(osGeometryField);
12471 : }
12472 49 : if (pszGridMapping != nullptr)
12473 : {
12474 21 : poLayer->SetGridMapping(pszGridMapping);
12475 21 : CPLFree(pszGridMapping);
12476 : }
12477 49 : poLayer->SetProfile(nProfileDimId, nParentIndexVarID);
12478 :
12479 574 : for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)
12480 : {
12481 525 : int anDimIds[2] = {-1, -1};
12482 525 : nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);
12483 525 : if (anDimIds[0] == nVectorDim ||
12484 24 : (nProfileDimId >= 0 && anDimIds[0] == nProfileDimId))
12485 : {
12486 : #ifdef NCDF_DEBUG
12487 : char szTemp2[NC_MAX_NAME + 1] = {};
12488 : CPL_IGNORE_RET_VAL(
12489 : nc_inq_varname(nCdfId, anPotentialVectorVarID[j], szTemp2));
12490 : CPLDebug("GDAL_netCDF", "Variable %s is a vector field", szTemp2);
12491 : #endif
12492 525 : poLayer->AddField(anPotentialVectorVarID[j]);
12493 : }
12494 : }
12495 :
12496 49 : if (poLayer->GetLayerDefn()->GetFieldCount() != 0 ||
12497 0 : poLayer->GetGeomType() != wkbNone)
12498 : {
12499 49 : papoLayers.push_back(poLayer);
12500 : }
12501 :
12502 98 : return CE_None;
12503 : }
12504 :
12505 : // Get all coordinate and boundary variables full names referenced in
12506 : // a given a NetCDF (or group) ID and its sub-groups.
12507 : // These variables are identified in other variable's
12508 : // "coordinates" and "bounds" attribute.
12509 : // Searching coordinate and boundary variables may need to explore
12510 : // parents groups (or other groups in case of reference given in form of an
12511 : // absolute path).
12512 : // See CF sections 5.2, 5.6 and 7.1
12513 513 : static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId, char ***ppapszVars)
12514 : {
12515 513 : int nVars = 0;
12516 513 : NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
12517 :
12518 3146 : for (int v = 0; v < nVars; v++)
12519 : {
12520 2633 : char *pszTemp = nullptr;
12521 2633 : char **papszTokens = nullptr;
12522 2633 : if (NCDFGetAttr(nCdfId, v, "coordinates", &pszTemp) == CE_None)
12523 441 : papszTokens = NCDFTokenizeCoordinatesAttribute(pszTemp);
12524 2633 : CPLFree(pszTemp);
12525 2633 : pszTemp = nullptr;
12526 2633 : if (NCDFGetAttr(nCdfId, v, "bounds", &pszTemp) == CE_None &&
12527 2633 : pszTemp != nullptr && !EQUAL(pszTemp, ""))
12528 9 : papszTokens = CSLAddString(papszTokens, pszTemp);
12529 2633 : CPLFree(pszTemp);
12530 3899 : for (int i = 0; papszTokens != nullptr && papszTokens[i] != nullptr;
12531 : i++)
12532 : {
12533 1266 : char *pszVarFullName = nullptr;
12534 1266 : if (NCDFResolveVarFullName(nCdfId, papszTokens[i],
12535 1266 : &pszVarFullName) == CE_None)
12536 1248 : *ppapszVars = CSLAddString(*ppapszVars, pszVarFullName);
12537 1266 : CPLFree(pszVarFullName);
12538 : }
12539 2633 : CSLDestroy(papszTokens);
12540 : }
12541 :
12542 : // Recurse on sub-groups.
12543 : int nSubGroups;
12544 513 : int *panSubGroupIds = nullptr;
12545 513 : NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
12546 545 : for (int i = 0; i < nSubGroups; i++)
12547 : {
12548 32 : NCDFGetCoordAndBoundVarFullNames(panSubGroupIds[i], ppapszVars);
12549 : }
12550 513 : CPLFree(panSubGroupIds);
12551 :
12552 513 : return CE_None;
12553 : }
12554 :
12555 : // Check if give type is user defined
12556 889 : bool NCDFIsUserDefinedType(int /*ncid*/, int type)
12557 : {
12558 889 : return type >= NC_FIRSTUSERTYPEID;
12559 : }
12560 :
12561 533 : char **NCDFTokenizeCoordinatesAttribute(const char *pszCoordinates)
12562 : {
12563 : // CF conventions use space as the separator for variable names in the
12564 : // coordinates attribute, but some products such as
12565 : // https://sentinel.esa.int/web/sentinel/technical-guides/sentinel-3-synergy/products-algorithms/level-2-aod-algorithms-and-products/level-2-aod-products-description
12566 : // use comma.
12567 533 : return CSLTokenizeString2(pszCoordinates, ", ", 0);
12568 : }
|