Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: netCDF read/write Driver
4 : * Purpose: GDAL bindings over netCDF library.
5 : * Author: Frank Warmerdam, warmerdam@pobox.com
6 : * Even Rouault <even.rouault at spatialys.com>
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2004, Frank Warmerdam
10 : * Copyright (c) 2007-2016, Even Rouault <even.rouault at spatialys.com>
11 : * Copyright (c) 2010, Kyle Shannon <kyle at pobox dot com>
12 : * Copyright (c) 2021, CLS
13 : *
14 : * SPDX-License-Identifier: MIT
15 : ****************************************************************************/
16 :
17 : #include "cpl_port.h"
18 :
19 : #include <array>
20 : #include <cassert>
21 : #include <cctype>
22 : #include <cerrno>
23 : #include <climits>
24 : #include <cmath>
25 : #include <cstdio>
26 : #include <cstdlib>
27 : #include <cstring>
28 : #include <ctime>
29 : #include <algorithm>
30 : #include <limits>
31 : #include <map>
32 : #include <mutex>
33 : #include <set>
34 : #include <queue>
35 : #include <string>
36 : #include <tuple>
37 : #include <utility>
38 : #include <vector>
39 :
40 : // Must be included after standard includes, otherwise VS2015 fails when
41 : // including <ctime>
42 : #include "netcdfdataset.h"
43 : #include "netcdfdrivercore.h"
44 : #include "netcdfsg.h"
45 : #include "netcdfuffd.h"
46 :
47 : #include "netcdf_mem.h"
48 :
49 : #include "cpl_conv.h"
50 : #include "cpl_error.h"
51 : #include "cpl_float.h"
52 : #include "cpl_json.h"
53 : #include "cpl_minixml.h"
54 : #include "cpl_multiproc.h"
55 : #include "cpl_progress.h"
56 : #include "cpl_time.h"
57 : #include "gdal.h"
58 : #include "gdal_frmts.h"
59 : #include "gdal_priv_templates.hpp"
60 : #include "ogr_core.h"
61 : #include "ogr_srs_api.h"
62 :
63 : // netCDF 4.8 switched to expecting filenames in UTF-8 on Windows
64 : // But with netCDF 4.9 and https://github.com/Unidata/netcdf-c/pull/2277/,
65 : // this is apparently back to expecting filenames in current codepage...
66 : // Detect netCDF 4.8 with NC_ENCZARR
67 : // Detect netCDF 4.9 with NC_NOATTCREORD
68 : #if defined(NC_ENCZARR) && !defined(NC_NOATTCREORD)
69 : #define NETCDF_USES_UTF8
70 : #endif
71 :
72 : // Internal function declarations.
73 :
74 : static bool NCDFIsGDALVersionGTE(const char *pszVersion, int nTarget);
75 :
76 : static void
77 : NCDFAddGDALHistory(int fpImage, const char *pszFilename, bool bWriteGDALVersion,
78 : bool bWriteGDALHistory, const char *pszOldHist,
79 : const char *pszFunctionName,
80 : const char *pszCFVersion = GDAL_DEFAULT_NCDF_CONVENTIONS);
81 :
82 : static void NCDFAddHistory(int fpImage, const char *pszAddHist,
83 : const char *pszOldHist);
84 :
85 : static CPLErr NCDFSafeStrcat(char **ppszDest, const char *pszSrc,
86 : size_t *nDestSize);
87 :
88 : // Var / attribute helper functions.
89 : static CPLErr NCDFPutAttr(int nCdfId, int nVarId, const char *pszAttrName,
90 : const char *pszValue);
91 :
92 : // Replace this where used.
93 : static CPLErr NCDFGet1DVar(int nCdfId, int nVarId, char **pszValue);
94 : static CPLErr NCDFPut1DVar(int nCdfId, int nVarId, const char *pszValue);
95 :
96 : // Replace this where used.
97 : static CPLStringList NCDFTokenizeArray(const char *pszValue);
98 : static void CopyMetadata(GDALDataset *poSrcDS, GDALRasterBand *poSrcBand,
99 : GDALRasterBand *poDstBand, int fpImage, int CDFVarID,
100 : const char *pszMatchPrefix = nullptr);
101 :
102 : // NetCDF-4 groups helper functions.
103 : // They all work also for NetCDF-3 files which are considered as
104 : // NetCDF-4 file with only one group.
105 : static CPLErr NCDFOpenSubDataset(int nCdfId, const char *pszSubdatasetName,
106 : int *pnGroupId, int *pnVarId);
107 : static CPLErr NCDFGetVisibleDims(int nGroupId, int *pnDims, int **ppanDimIds);
108 : static CPLErr NCDFGetSubGroups(int nGroupId, int *pnSubGroups,
109 : int **ppanSubGroupIds);
110 : static CPLErr NCDFGetGroupFullName(int nGroupId, char **ppszFullName,
111 : bool bNC3Compat = true);
112 : static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId, char **ppszFullName,
113 : bool bNC3Compat = true);
114 : static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId);
115 :
116 : static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,
117 : char **ppszFullName,
118 : bool bMandatory = false);
119 : static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,
120 : const char *pszAtt, int *pnAtt,
121 : bool bMandatory = false);
122 : static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId, char ***ppapszVars);
123 :
124 : // Uncomment this for more debug output.
125 : // #define NCDF_DEBUG 1
126 :
127 : CPLMutex *hNCMutex = nullptr;
128 :
129 : // Workaround https://github.com/OSGeo/gdal/issues/6253
130 : // Having 2 netCDF handles on the same file doesn't work in a multi-threaded
131 : // way. Apparently having the same handle works better (this is OK since
132 : // we have a global mutex on the netCDF library)
133 : static std::map<std::string, int> goMapNameToNetCDFId;
134 : static std::map<int, std::pair<std::string, int>> goMapNetCDFIdToKeyAndCount;
135 :
136 784 : int GDAL_nc_open(const char *pszFilename, int nMode, int *pID)
137 : {
138 1568 : std::string osKey(pszFilename);
139 784 : osKey += "#####";
140 784 : osKey += std::to_string(nMode);
141 784 : auto oIter = goMapNameToNetCDFId.find(osKey);
142 784 : if (oIter == goMapNameToNetCDFId.end())
143 : {
144 724 : int ret = nc_open(pszFilename, nMode, pID);
145 724 : if (ret != NC_NOERR)
146 3 : return ret;
147 721 : goMapNameToNetCDFId[osKey] = *pID;
148 721 : goMapNetCDFIdToKeyAndCount[*pID] =
149 1442 : std::pair<std::string, int>(osKey, 1);
150 721 : return ret;
151 : }
152 : else
153 : {
154 60 : *pID = oIter->second;
155 60 : goMapNetCDFIdToKeyAndCount[oIter->second].second++;
156 60 : return NC_NOERR;
157 : }
158 : }
159 :
160 1099 : int GDAL_nc_close(int cdfid)
161 : {
162 1099 : int ret = NC_NOERR;
163 1099 : auto oIter = goMapNetCDFIdToKeyAndCount.find(cdfid);
164 1099 : if (oIter != goMapNetCDFIdToKeyAndCount.end())
165 : {
166 781 : if (--oIter->second.second == 0)
167 : {
168 721 : ret = nc_close(cdfid);
169 721 : goMapNameToNetCDFId.erase(oIter->second.first);
170 721 : goMapNetCDFIdToKeyAndCount.erase(oIter);
171 : }
172 : }
173 : else
174 : {
175 : // we can go here if file opened with nc_open_mem() or nc_create()
176 318 : ret = nc_close(cdfid);
177 : }
178 1099 : return ret;
179 : }
180 :
181 : /************************************************************************/
182 : /* ==================================================================== */
183 : /* netCDFRasterBand */
184 : /* ==================================================================== */
185 : /************************************************************************/
186 :
187 : class netCDFRasterBand final : public GDALPamRasterBand
188 : {
189 : friend class netCDFDataset;
190 :
191 : nc_type nc_datatype;
192 : int cdfid;
193 : int nZId;
194 : int nZDim;
195 : int nLevel;
196 : int nBandXPos;
197 : int nBandYPos;
198 : int *panBandZPos;
199 : int *panBandZLev;
200 : bool m_bNoDataSet = false;
201 : double m_dfNoDataValue = 0;
202 : bool m_bNoDataSetAsInt64 = false;
203 : int64_t m_nNodataValueInt64 = 0;
204 : bool m_bNoDataSetAsUInt64 = false;
205 : uint64_t m_nNodataValueUInt64 = 0;
206 : bool bValidRangeValid = false;
207 : double adfValidRange[2]{0, 0};
208 : bool m_bHaveScale = false;
209 : bool m_bHaveOffset = false;
210 : double m_dfScale = 1;
211 : double m_dfOffset = 0;
212 : CPLString m_osUnitType{};
213 : bool bSignedData;
214 : bool bCheckLongitude;
215 : bool m_bCreateMetadataFromOtherVarsDone = false;
216 :
217 : void CreateMetadataFromAttributes();
218 : void CreateMetadataFromOtherVars();
219 :
220 : template <class T>
221 : void CheckData(void *pImage, void *pImageNC, size_t nTmpBlockXSize,
222 : size_t nTmpBlockYSize, bool bCheckIsNan = false);
223 : template <class T>
224 : void CheckDataCpx(void *pImage, void *pImageNC, size_t nTmpBlockXSize,
225 : size_t nTmpBlockYSize, bool bCheckIsNan = false);
226 : void SetBlockSize();
227 :
228 : bool FetchNetcdfChunk(size_t xstart, size_t ystart, void *pImage);
229 :
230 : void SetNoDataValueNoUpdate(double dfNoData);
231 : void SetNoDataValueNoUpdate(int64_t nNoData);
232 : void SetNoDataValueNoUpdate(uint64_t nNoData);
233 :
234 : void SetOffsetNoUpdate(double dfVal);
235 : void SetScaleNoUpdate(double dfVal);
236 : void SetUnitTypeNoUpdate(const char *pszNewValue);
237 :
238 : protected:
239 : CPLXMLNode *SerializeToXML(const char *pszUnused) override;
240 :
241 : public:
242 : struct CONSTRUCTOR_OPEN
243 : {
244 : };
245 :
246 : struct CONSTRUCTOR_CREATE
247 : {
248 : };
249 :
250 : netCDFRasterBand(const CONSTRUCTOR_OPEN &, netCDFDataset *poDS,
251 : int nGroupId, int nZId, int nZDim, int nLevel,
252 : const int *panBandZLen, const int *panBandPos, int nBand);
253 : netCDFRasterBand(const CONSTRUCTOR_CREATE &, netCDFDataset *poDS,
254 : GDALDataType eType, int nBand, bool bSigned = true,
255 : const char *pszBandName = nullptr,
256 : const char *pszLongName = nullptr, int nZId = -1,
257 : int nZDim = 2, int nLevel = 0,
258 : const int *panBandZLev = nullptr,
259 : const int *panBandZPos = nullptr,
260 : const int *paDimIds = nullptr);
261 : ~netCDFRasterBand() override;
262 :
263 : double GetNoDataValue(int *) override;
264 : int64_t GetNoDataValueAsInt64(int *pbSuccess = nullptr) override;
265 : uint64_t GetNoDataValueAsUInt64(int *pbSuccess = nullptr) override;
266 : CPLErr SetNoDataValue(double) override;
267 : CPLErr SetNoDataValueAsInt64(int64_t nNoData) override;
268 : CPLErr SetNoDataValueAsUInt64(uint64_t nNoData) override;
269 : // virtual CPLErr DeleteNoDataValue();
270 : double GetOffset(int *) override;
271 : CPLErr SetOffset(double) override;
272 : double GetScale(int *) override;
273 : CPLErr SetScale(double) override;
274 : const char *GetUnitType() override;
275 : CPLErr SetUnitType(const char *) override;
276 : CPLErr IReadBlock(int, int, void *) override;
277 : CPLErr IWriteBlock(int, int, void *) override;
278 :
279 : CSLConstList GetMetadata(const char *pszDomain = "") override;
280 : const char *GetMetadataItem(const char *pszName,
281 : const char *pszDomain = "") override;
282 :
283 : CPLErr SetMetadataItem(const char *pszName, const char *pszValue,
284 : const char *pszDomain = "") override;
285 : CPLErr SetMetadata(CSLConstList papszMD,
286 : const char *pszDomain = "") override;
287 : };
288 :
289 : /************************************************************************/
290 : /* netCDFRasterBand() */
291 : /************************************************************************/
292 :
293 498 : netCDFRasterBand::netCDFRasterBand(const netCDFRasterBand::CONSTRUCTOR_OPEN &,
294 : netCDFDataset *poNCDFDS, int nGroupId,
295 : int nZIdIn, int nZDimIn, int nLevelIn,
296 : const int *panBandZLevIn,
297 498 : const int *panBandZPosIn, int nBandIn)
298 : : nc_datatype(NC_NAT), cdfid(nGroupId), nZId(nZIdIn), nZDim(nZDimIn),
299 498 : nLevel(nLevelIn), nBandXPos(panBandZPosIn[0]),
300 498 : nBandYPos(nZDim == 1 ? -1 : panBandZPosIn[1]), panBandZPos(nullptr),
301 : panBandZLev(nullptr),
302 : bSignedData(true), // Default signed, except for Byte.
303 996 : bCheckLongitude(false)
304 : {
305 498 : poDS = poNCDFDS;
306 498 : nBand = nBandIn;
307 :
308 : // Take care of all other dimensions.
309 498 : if (nZDim > 2)
310 : {
311 176 : panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
312 176 : panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
313 :
314 478 : for (int i = 0; i < nZDim - 2; i++)
315 : {
316 302 : panBandZPos[i] = panBandZPosIn[i + 2];
317 302 : panBandZLev[i] = panBandZLevIn[i];
318 : }
319 : }
320 :
321 498 : nRasterXSize = poDS->GetRasterXSize();
322 498 : nRasterYSize = poDS->GetRasterYSize();
323 498 : nBlockXSize = poDS->GetRasterXSize();
324 498 : nBlockYSize = 1;
325 :
326 : // Get the type of the "z" variable, our target raster array.
327 498 : if (nc_inq_var(cdfid, nZId, nullptr, &nc_datatype, nullptr, nullptr,
328 498 : nullptr) != NC_NOERR)
329 : {
330 0 : CPLError(CE_Failure, CPLE_AppDefined, "Error in nc_var_inq() on 'z'.");
331 0 : return;
332 : }
333 :
334 498 : if (NCDFIsUserDefinedType(cdfid, nc_datatype))
335 : {
336 : // First enquire and check that the number of fields is 2
337 : size_t nfields, compoundsize;
338 5 : if (nc_inq_compound(cdfid, nc_datatype, nullptr, &compoundsize,
339 5 : &nfields) != NC_NOERR)
340 : {
341 0 : CPLError(CE_Failure, CPLE_AppDefined,
342 : "Error in nc_inq_compound() on 'z'.");
343 0 : return;
344 : }
345 :
346 5 : if (nfields != 2)
347 : {
348 0 : CPLError(CE_Failure, CPLE_AppDefined,
349 : "Unsupported data type encountered in nc_inq_compound() "
350 : "on 'z'.");
351 0 : return;
352 : }
353 :
354 : // Now check that that two types are the same in the struct.
355 : nc_type field_type1, field_type2;
356 : int field_dims1, field_dims2;
357 5 : if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
358 : &field_type1, &field_dims1,
359 5 : nullptr) != NC_NOERR)
360 : {
361 0 : CPLError(
362 : CE_Failure, CPLE_AppDefined,
363 : "Error in querying Field 1 in nc_inq_compound_field() on 'z'.");
364 0 : return;
365 : }
366 :
367 5 : if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
368 : &field_type2, &field_dims2,
369 5 : nullptr) != NC_NOERR)
370 : {
371 0 : CPLError(
372 : CE_Failure, CPLE_AppDefined,
373 : "Error in querying Field 2 in nc_inq_compound_field() on 'z'.");
374 0 : return;
375 : }
376 :
377 5 : if ((field_type1 != field_type2) || (field_dims1 != field_dims2) ||
378 5 : (field_dims1 != 0))
379 : {
380 0 : CPLError(CE_Failure, CPLE_AppDefined,
381 : "Error in interpreting compound data type on 'z'.");
382 0 : return;
383 : }
384 :
385 5 : if (field_type1 == NC_SHORT)
386 0 : eDataType = GDT_CInt16;
387 5 : else if (field_type1 == NC_INT)
388 0 : eDataType = GDT_CInt32;
389 5 : else if (field_type1 == NC_FLOAT)
390 4 : eDataType = GDT_CFloat32;
391 1 : else if (field_type1 == NC_DOUBLE)
392 1 : eDataType = GDT_CFloat64;
393 : else
394 : {
395 0 : CPLError(CE_Failure, CPLE_AppDefined,
396 : "Unsupported netCDF compound data type encountered.");
397 0 : return;
398 : }
399 : }
400 : else
401 : {
402 493 : if (nc_datatype == NC_BYTE)
403 157 : eDataType = GDT_UInt8;
404 336 : else if (nc_datatype == NC_CHAR)
405 0 : eDataType = GDT_UInt8;
406 336 : else if (nc_datatype == NC_SHORT)
407 41 : eDataType = GDT_Int16;
408 295 : else if (nc_datatype == NC_INT)
409 89 : eDataType = GDT_Int32;
410 206 : else if (nc_datatype == NC_FLOAT)
411 127 : eDataType = GDT_Float32;
412 79 : else if (nc_datatype == NC_DOUBLE)
413 40 : eDataType = GDT_Float64;
414 39 : else if (nc_datatype == NC_UBYTE)
415 16 : eDataType = GDT_UInt8;
416 23 : else if (nc_datatype == NC_USHORT)
417 4 : eDataType = GDT_UInt16;
418 19 : else if (nc_datatype == NC_UINT)
419 4 : eDataType = GDT_UInt32;
420 15 : else if (nc_datatype == NC_INT64)
421 8 : eDataType = GDT_Int64;
422 7 : else if (nc_datatype == NC_UINT64)
423 7 : eDataType = GDT_UInt64;
424 : else
425 : {
426 0 : if (nBand == 1)
427 0 : CPLError(CE_Warning, CPLE_AppDefined,
428 : "Unsupported netCDF datatype (%d), treat as Float32.",
429 0 : static_cast<int>(nc_datatype));
430 0 : eDataType = GDT_Float32;
431 0 : nc_datatype = NC_FLOAT;
432 : }
433 : }
434 :
435 : // Find and set No Data for this variable.
436 498 : nc_type atttype = NC_NAT;
437 498 : size_t attlen = 0;
438 498 : const char *pszNoValueName = nullptr;
439 :
440 : // Find attribute name, either _FillValue or missing_value.
441 498 : int status = nc_inq_att(cdfid, nZId, NCDF_FillValue, &atttype, &attlen);
442 498 : if (status == NC_NOERR)
443 : {
444 249 : pszNoValueName = NCDF_FillValue;
445 : }
446 : else
447 : {
448 249 : status = nc_inq_att(cdfid, nZId, "missing_value", &atttype, &attlen);
449 249 : if (status == NC_NOERR)
450 : {
451 12 : pszNoValueName = "missing_value";
452 : }
453 : }
454 :
455 : // Fetch missing value.
456 498 : double dfNoData = 0.0;
457 498 : bool bGotNoData = false;
458 498 : int64_t nNoDataAsInt64 = 0;
459 498 : bool bGotNoDataAsInt64 = false;
460 498 : uint64_t nNoDataAsUInt64 = 0;
461 498 : bool bGotNoDataAsUInt64 = false;
462 498 : if (status == NC_NOERR)
463 : {
464 261 : nc_type nAttrType = NC_NAT;
465 261 : size_t nAttrLen = 0;
466 261 : status = nc_inq_att(cdfid, nZId, pszNoValueName, &nAttrType, &nAttrLen);
467 261 : if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_INT64)
468 : {
469 : long long v;
470 7 : nc_get_att_longlong(cdfid, nZId, pszNoValueName, &v);
471 7 : bGotNoData = true;
472 7 : bGotNoDataAsInt64 = true;
473 7 : nNoDataAsInt64 = static_cast<int64_t>(v);
474 : }
475 254 : else if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_UINT64)
476 : {
477 : unsigned long long v;
478 7 : nc_get_att_ulonglong(cdfid, nZId, pszNoValueName, &v);
479 7 : bGotNoData = true;
480 7 : bGotNoDataAsUInt64 = true;
481 7 : nNoDataAsUInt64 = static_cast<uint64_t>(v);
482 : }
483 247 : else if (NCDFGetAttr(cdfid, nZId, pszNoValueName, &dfNoData) == CE_None)
484 : {
485 246 : bGotNoData = true;
486 : }
487 : }
488 :
489 : // If NoData was not found, use the default value, but for non-Byte types
490 : // as it is not recommended:
491 : // https://www.unidata.ucar.edu/software/netcdf/docs/attribute_conventions.html
492 498 : nc_type vartype = NC_NAT;
493 498 : if (!bGotNoData)
494 : {
495 238 : nc_inq_vartype(cdfid, nZId, &vartype);
496 238 : if (vartype == NC_INT64)
497 : {
498 : nNoDataAsInt64 =
499 1 : NCDFGetDefaultNoDataValueAsInt64(cdfid, nZId, bGotNoData);
500 1 : bGotNoDataAsInt64 = bGotNoData;
501 : }
502 237 : else if (vartype == NC_UINT64)
503 : {
504 : nNoDataAsUInt64 =
505 0 : NCDFGetDefaultNoDataValueAsUInt64(cdfid, nZId, bGotNoData);
506 0 : bGotNoDataAsUInt64 = bGotNoData;
507 : }
508 237 : else if (vartype != NC_CHAR && vartype != NC_BYTE &&
509 103 : vartype != NC_UBYTE)
510 : {
511 93 : dfNoData =
512 93 : NCDFGetDefaultNoDataValue(cdfid, nZId, vartype, bGotNoData);
513 93 : if (bGotNoData)
514 : {
515 82 : CPLDebug("GDAL_netCDF",
516 : "did not get nodata value for variable #%d, using "
517 : "default %f",
518 : nZId, dfNoData);
519 : }
520 : }
521 : }
522 :
523 498 : bool bHasUnderscoreUnsignedAttr = false;
524 498 : bool bUnderscoreUnsignedAttrVal = false;
525 : {
526 498 : char *pszTemp = nullptr;
527 498 : if (NCDFGetAttr(cdfid, nZId, "_Unsigned", &pszTemp) == CE_None)
528 : {
529 149 : if (EQUAL(pszTemp, "true"))
530 : {
531 141 : bHasUnderscoreUnsignedAttr = true;
532 141 : bUnderscoreUnsignedAttrVal = true;
533 : }
534 8 : else if (EQUAL(pszTemp, "false"))
535 : {
536 8 : bHasUnderscoreUnsignedAttr = true;
537 8 : bUnderscoreUnsignedAttrVal = false;
538 : }
539 149 : CPLFree(pszTemp);
540 : }
541 : }
542 :
543 : // Look for valid_range or valid_min/valid_max.
544 :
545 : // First look for valid_range.
546 498 : if (CPLFetchBool(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE", true))
547 : {
548 496 : char *pszValidRange = nullptr;
549 496 : if (NCDFGetAttr(cdfid, nZId, "valid_range", &pszValidRange) ==
550 142 : CE_None &&
551 638 : pszValidRange[0] == '{' &&
552 142 : pszValidRange[strlen(pszValidRange) - 1] == '}')
553 : {
554 : const std::string osValidRange =
555 426 : std::string(pszValidRange).substr(1, strlen(pszValidRange) - 2);
556 : const CPLStringList aosValidRange(
557 284 : CSLTokenizeString2(osValidRange.c_str(), ",", 0));
558 142 : if (aosValidRange.size() == 2 &&
559 284 : CPLGetValueType(aosValidRange[0]) != CPL_VALUE_STRING &&
560 142 : CPLGetValueType(aosValidRange[1]) != CPL_VALUE_STRING)
561 : {
562 142 : bValidRangeValid = true;
563 142 : adfValidRange[0] = CPLAtof(aosValidRange[0]);
564 142 : adfValidRange[1] = CPLAtof(aosValidRange[1]);
565 : }
566 : }
567 496 : CPLFree(pszValidRange);
568 :
569 : // If not found look for valid_min and valid_max.
570 496 : if (!bValidRangeValid)
571 : {
572 354 : double dfMin = 0;
573 354 : double dfMax = 0;
574 369 : if (NCDFGetAttr(cdfid, nZId, "valid_min", &dfMin) == CE_None &&
575 15 : NCDFGetAttr(cdfid, nZId, "valid_max", &dfMax) == CE_None)
576 : {
577 8 : adfValidRange[0] = dfMin;
578 8 : adfValidRange[1] = dfMax;
579 8 : bValidRangeValid = true;
580 : }
581 : }
582 :
583 496 : if (bValidRangeValid &&
584 150 : (adfValidRange[0] < 0 || adfValidRange[1] < 0) &&
585 17 : nc_datatype == NC_SHORT && bHasUnderscoreUnsignedAttr &&
586 : bUnderscoreUnsignedAttrVal)
587 : {
588 2 : if (adfValidRange[0] < 0)
589 0 : adfValidRange[0] += 65536;
590 2 : if (adfValidRange[1] < 0)
591 2 : adfValidRange[1] += 65536;
592 2 : if (adfValidRange[0] <= adfValidRange[1])
593 : {
594 : // Updating metadata item
595 2 : GDALPamRasterBand::SetMetadataItem(
596 : "valid_range",
597 2 : CPLSPrintf("{%d,%d}", static_cast<int>(adfValidRange[0]),
598 2 : static_cast<int>(adfValidRange[1])));
599 : }
600 : }
601 :
602 496 : if (bValidRangeValid && adfValidRange[0] > adfValidRange[1])
603 : {
604 0 : CPLError(CE_Warning, CPLE_AppDefined,
605 : "netCDFDataset::valid_range: min > max:\n"
606 : " min: %lf\n max: %lf\n",
607 : adfValidRange[0], adfValidRange[1]);
608 0 : bValidRangeValid = false;
609 0 : adfValidRange[0] = 0.0;
610 0 : adfValidRange[1] = 0.0;
611 : }
612 : }
613 :
614 : // Special For Byte Bands: check for signed/unsigned byte.
615 498 : if (nc_datatype == NC_BYTE)
616 : {
617 : // netcdf uses signed byte by default, but GDAL uses unsigned by default
618 : // This may cause unexpected results, but is needed for back-compat.
619 157 : if (poNCDFDS->bIsGdalFile)
620 135 : bSignedData = false;
621 : else
622 22 : bSignedData = true;
623 :
624 : // For NC4 format NC_BYTE is (normally) signed, NC_UBYTE is unsigned.
625 : // But in case a NC3 file was converted automatically and has hints
626 : // that it is unsigned, take them into account
627 157 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
628 : {
629 3 : bSignedData = true;
630 : }
631 :
632 : // If we got valid_range, test for signed/unsigned range.
633 : // https://docs.unidata.ucar.edu/netcdf-c/current/attribute_conventions.html
634 157 : if (bValidRangeValid)
635 : {
636 : // If we got valid_range={0,255}, treat as unsigned.
637 138 : if (adfValidRange[0] == 0 && adfValidRange[1] == 255)
638 : {
639 130 : bSignedData = false;
640 : // Reset valid_range.
641 130 : bValidRangeValid = false;
642 : }
643 : // If we got valid_range={-128,127}, treat as signed.
644 8 : else if (adfValidRange[0] == -128 && adfValidRange[1] == 127)
645 : {
646 8 : bSignedData = true;
647 : // Reset valid_range.
648 8 : bValidRangeValid = false;
649 : }
650 : }
651 : // Else test for _Unsigned.
652 : // https://docs.unidata.ucar.edu/nug/current/best_practices.html
653 : else
654 : {
655 19 : if (bHasUnderscoreUnsignedAttr)
656 7 : bSignedData = !bUnderscoreUnsignedAttrVal;
657 : }
658 :
659 157 : if (bSignedData)
660 : {
661 20 : eDataType = GDT_Int8;
662 : }
663 137 : else if (dfNoData < 0)
664 : {
665 : // Fix nodata value as it was stored signed.
666 6 : dfNoData += 256;
667 6 : if (pszNoValueName)
668 : {
669 : // Updating metadata item
670 6 : GDALPamRasterBand::SetMetadataItem(
671 : pszNoValueName,
672 : CPLSPrintf("%d", static_cast<int>(dfNoData)));
673 : }
674 : }
675 : }
676 341 : else if (nc_datatype == NC_SHORT)
677 : {
678 41 : if (bHasUnderscoreUnsignedAttr)
679 : {
680 4 : bSignedData = !bUnderscoreUnsignedAttrVal;
681 4 : if (!bSignedData)
682 4 : eDataType = GDT_UInt16;
683 : }
684 :
685 : // Fix nodata value as it was stored signed.
686 41 : if (!bSignedData && dfNoData < 0)
687 : {
688 4 : dfNoData += 65536;
689 4 : if (pszNoValueName)
690 : {
691 : // Updating metadata item
692 4 : GDALPamRasterBand::SetMetadataItem(
693 : pszNoValueName,
694 : CPLSPrintf("%d", static_cast<int>(dfNoData)));
695 : }
696 : }
697 : }
698 :
699 300 : else if (nc_datatype == NC_UBYTE || nc_datatype == NC_USHORT ||
700 280 : nc_datatype == NC_UINT || nc_datatype == NC_UINT64)
701 : {
702 31 : bSignedData = false;
703 : }
704 :
705 498 : CPLDebug("GDAL_netCDF", "netcdf type=%d gdal type=%d signedByte=%d",
706 498 : nc_datatype, eDataType, static_cast<int>(bSignedData));
707 :
708 498 : if (bGotNoData)
709 : {
710 : // Set nodata value.
711 343 : if (bGotNoDataAsInt64)
712 : {
713 8 : if (eDataType == GDT_Int64)
714 : {
715 8 : SetNoDataValueNoUpdate(nNoDataAsInt64);
716 : }
717 0 : else if (eDataType == GDT_UInt64 && nNoDataAsInt64 >= 0)
718 : {
719 0 : SetNoDataValueNoUpdate(static_cast<uint64_t>(nNoDataAsInt64));
720 : }
721 : else
722 : {
723 0 : SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsInt64));
724 : }
725 : }
726 335 : else if (bGotNoDataAsUInt64)
727 : {
728 7 : if (eDataType == GDT_UInt64)
729 : {
730 7 : SetNoDataValueNoUpdate(nNoDataAsUInt64);
731 : }
732 0 : else if (eDataType == GDT_Int64 &&
733 : nNoDataAsUInt64 <=
734 0 : static_cast<uint64_t>(
735 0 : std::numeric_limits<int64_t>::max()))
736 : {
737 0 : SetNoDataValueNoUpdate(static_cast<int64_t>(nNoDataAsUInt64));
738 : }
739 : else
740 : {
741 0 : SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsUInt64));
742 : }
743 : }
744 : else
745 : {
746 : #ifdef NCDF_DEBUG
747 : CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) read", dfNoData);
748 : #endif
749 328 : if (eDataType == GDT_Int64 && GDALIsValueExactAs<int64_t>(dfNoData))
750 : {
751 0 : SetNoDataValueNoUpdate(static_cast<int64_t>(dfNoData));
752 : }
753 328 : else if (eDataType == GDT_UInt64 &&
754 0 : GDALIsValueExactAs<uint64_t>(dfNoData))
755 : {
756 0 : SetNoDataValueNoUpdate(static_cast<uint64_t>(dfNoData));
757 : }
758 : else
759 : {
760 328 : SetNoDataValueNoUpdate(dfNoData);
761 : }
762 : }
763 : }
764 :
765 498 : CreateMetadataFromAttributes();
766 :
767 : // Attempt to fetch the scale_factor and add_offset attributes for the
768 : // variable and set them. If these values are not available, set
769 : // offset to 0 and scale to 1.
770 498 : if (nc_inq_attid(cdfid, nZId, CF_ADD_OFFSET, nullptr) == NC_NOERR)
771 : {
772 16 : double dfOffset = 0;
773 16 : status = nc_get_att_double(cdfid, nZId, CF_ADD_OFFSET, &dfOffset);
774 16 : CPLDebug("GDAL_netCDF", "got add_offset=%.16g, status=%d", dfOffset,
775 : status);
776 16 : SetOffsetNoUpdate(dfOffset);
777 : }
778 :
779 498 : bool bHasScale = false;
780 498 : if (nc_inq_attid(cdfid, nZId, CF_SCALE_FACTOR, nullptr) == NC_NOERR)
781 : {
782 20 : bHasScale = true;
783 20 : double dfScale = 1;
784 20 : status = nc_get_att_double(cdfid, nZId, CF_SCALE_FACTOR, &dfScale);
785 20 : CPLDebug("GDAL_netCDF", "got scale_factor=%.16g, status=%d", dfScale,
786 : status);
787 20 : SetScaleNoUpdate(dfScale);
788 : }
789 :
790 12 : if (bValidRangeValid && GDALDataTypeIsInteger(eDataType) &&
791 4 : eDataType != GDT_Int64 && eDataType != GDT_UInt64 &&
792 4 : (std::fabs(std::round(adfValidRange[0]) - adfValidRange[0]) > 1e-5 ||
793 510 : std::fabs(std::round(adfValidRange[1]) - adfValidRange[1]) > 1e-5) &&
794 1 : CSLFetchNameValue(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE") ==
795 : nullptr)
796 : {
797 1 : CPLError(CE_Warning, CPLE_AppDefined,
798 : "validity range = %f, %f contains floating-point values, "
799 : "whereas data type is integer. valid_range is thus likely "
800 : "wrong%s. Ignoring it.",
801 : adfValidRange[0], adfValidRange[1],
802 : bHasScale ? " (likely scaled using scale_factor/add_factor "
803 : "whereas it should be using the packed data type)"
804 : : "");
805 1 : bValidRangeValid = false;
806 1 : adfValidRange[0] = 0.0;
807 1 : adfValidRange[1] = 0.0;
808 : }
809 :
810 : // Should we check for longitude values > 360?
811 498 : bCheckLongitude =
812 996 : CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")) &&
813 498 : NCDFIsVarLongitude(cdfid, nZId, nullptr);
814 :
815 : // Attempt to fetch the units attribute for the variable and set it.
816 498 : SetUnitTypeNoUpdate(netCDFRasterBand::GetMetadataItem(CF_UNITS));
817 :
818 498 : SetBlockSize();
819 : }
820 :
821 686 : void netCDFRasterBand::SetBlockSize()
822 : {
823 : // Check for variable chunking (netcdf-4 only).
824 : // GDAL block size should be set to hdf5 chunk size.
825 686 : int nTmpFormat = 0;
826 686 : int status = nc_inq_format(cdfid, &nTmpFormat);
827 686 : NetCDFFormatEnum eTmpFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
828 686 : if ((status == NC_NOERR) &&
829 587 : (eTmpFormat == NCDF_FORMAT_NC4 || eTmpFormat == NCDF_FORMAT_NC4C))
830 : {
831 115 : size_t chunksize[MAX_NC_DIMS] = {};
832 : // Check for chunksize and set it as the blocksize (optimizes read).
833 115 : status = nc_inq_var_chunking(cdfid, nZId, &nTmpFormat, chunksize);
834 115 : if ((status == NC_NOERR) && (nTmpFormat == NC_CHUNKED))
835 : {
836 14 : nBlockXSize = (int)chunksize[nZDim - 1];
837 14 : if (nZDim >= 2)
838 14 : nBlockYSize = (int)chunksize[nZDim - 2];
839 : else
840 0 : nBlockYSize = 1;
841 : }
842 : }
843 :
844 : // Deal with bottom-up datasets and nBlockYSize != 1.
845 686 : auto poGDS = cpl::down_cast<netCDFDataset *>(poDS);
846 686 : if (poGDS->bBottomUp && nBlockYSize != 1 && poGDS->poChunkCache == nullptr)
847 : {
848 6 : if (poGDS->eAccess == GA_ReadOnly)
849 : {
850 : // Try to cache 1 or 2 'rows' of netCDF chunks along the whole
851 : // width of the raster
852 6 : size_t nChunks =
853 6 : static_cast<size_t>(DIV_ROUND_UP(nRasterXSize, nBlockXSize));
854 6 : if ((nRasterYSize % nBlockYSize) != 0)
855 2 : nChunks *= 2;
856 : const size_t nChunkSize =
857 6 : static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
858 6 : nBlockXSize * nBlockYSize;
859 6 : constexpr size_t MAX_CACHE_SIZE = 100 * 1024 * 1024;
860 6 : nChunks = std::min(nChunks, MAX_CACHE_SIZE / nChunkSize);
861 6 : if (nChunks)
862 : {
863 6 : poGDS->poChunkCache.reset(
864 6 : new netCDFDataset::ChunkCacheType(nChunks));
865 : }
866 : }
867 : else
868 : {
869 0 : nBlockYSize = 1;
870 : }
871 : }
872 686 : }
873 :
874 : // Constructor in create mode.
875 : // If nZId and following variables are not passed, the band will have 2
876 : // dimensions.
877 : // TODO: Get metadata, missing val from band #1 if nZDim > 2.
878 188 : netCDFRasterBand::netCDFRasterBand(
879 : const netCDFRasterBand::CONSTRUCTOR_CREATE &, netCDFDataset *poNCDFDS,
880 : const GDALDataType eTypeIn, int nBandIn, bool bSigned,
881 : const char *pszBandName, const char *pszLongName, int nZIdIn, int nZDimIn,
882 : int nLevelIn, const int *panBandZLevIn, const int *panBandZPosIn,
883 188 : const int *paDimIds)
884 188 : : nc_datatype(NC_NAT), cdfid(poNCDFDS->GetCDFID()), nZId(nZIdIn),
885 : nZDim(nZDimIn), nLevel(nLevelIn), nBandXPos(1), nBandYPos(0),
886 : panBandZPos(nullptr), panBandZLev(nullptr), bSignedData(bSigned),
887 188 : bCheckLongitude(false), m_bCreateMetadataFromOtherVarsDone(true)
888 : {
889 188 : poDS = poNCDFDS;
890 188 : nBand = nBandIn;
891 :
892 188 : nRasterXSize = poDS->GetRasterXSize();
893 188 : nRasterYSize = poDS->GetRasterYSize();
894 188 : nBlockXSize = poDS->GetRasterXSize();
895 188 : nBlockYSize = 1;
896 :
897 188 : if (poDS->GetAccess() != GA_Update)
898 : {
899 0 : CPLError(CE_Failure, CPLE_NotSupported,
900 : "Dataset is not in update mode, "
901 : "wrong netCDFRasterBand constructor");
902 0 : return;
903 : }
904 :
905 : // Take care of all other dimensions.
906 188 : if (nZDim > 2 && paDimIds != nullptr)
907 : {
908 27 : nBandXPos = panBandZPosIn[0];
909 27 : nBandYPos = panBandZPosIn[1];
910 :
911 27 : panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
912 27 : panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
913 :
914 76 : for (int i = 0; i < nZDim - 2; i++)
915 : {
916 49 : panBandZPos[i] = panBandZPosIn[i + 2];
917 49 : panBandZLev[i] = panBandZLevIn[i];
918 : }
919 : }
920 :
921 : // Get the type of the "z" variable, our target raster array.
922 188 : eDataType = eTypeIn;
923 :
924 188 : switch (eDataType)
925 : {
926 83 : case GDT_UInt8:
927 83 : nc_datatype = NC_BYTE;
928 : // NC_UBYTE (unsigned byte) is only available for NC4.
929 83 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
930 3 : nc_datatype = NC_UBYTE;
931 83 : break;
932 7 : case GDT_Int8:
933 7 : nc_datatype = NC_BYTE;
934 7 : break;
935 11 : case GDT_Int16:
936 11 : nc_datatype = NC_SHORT;
937 11 : break;
938 24 : case GDT_Int32:
939 24 : nc_datatype = NC_INT;
940 24 : break;
941 13 : case GDT_Float32:
942 13 : nc_datatype = NC_FLOAT;
943 13 : break;
944 8 : case GDT_Float64:
945 8 : nc_datatype = NC_DOUBLE;
946 8 : break;
947 7 : case GDT_Int64:
948 7 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
949 : {
950 7 : nc_datatype = NC_INT64;
951 : }
952 : else
953 : {
954 0 : if (nBand == 1)
955 0 : CPLError(
956 : CE_Warning, CPLE_AppDefined,
957 : "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
958 : "Int64");
959 0 : nc_datatype = NC_DOUBLE;
960 0 : eDataType = GDT_Float64;
961 : }
962 7 : break;
963 7 : case GDT_UInt64:
964 7 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
965 : {
966 7 : nc_datatype = NC_UINT64;
967 : }
968 : else
969 : {
970 0 : if (nBand == 1)
971 0 : CPLError(
972 : CE_Warning, CPLE_AppDefined,
973 : "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
974 : "UInt64");
975 0 : nc_datatype = NC_DOUBLE;
976 0 : eDataType = GDT_Float64;
977 : }
978 7 : break;
979 6 : case GDT_UInt16:
980 6 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
981 : {
982 6 : nc_datatype = NC_USHORT;
983 6 : break;
984 : }
985 : [[fallthrough]];
986 : case GDT_UInt32:
987 6 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
988 : {
989 6 : nc_datatype = NC_UINT;
990 6 : break;
991 : }
992 : [[fallthrough]];
993 : default:
994 16 : if (nBand == 1)
995 8 : CPLError(CE_Warning, CPLE_AppDefined,
996 : "Unsupported GDAL datatype (%d), treat as NC_FLOAT.",
997 8 : static_cast<int>(eDataType));
998 16 : nc_datatype = NC_FLOAT;
999 16 : eDataType = GDT_Float32;
1000 16 : break;
1001 : }
1002 :
1003 : // Define the variable if necessary (if nZId == -1).
1004 188 : bool bDefineVar = false;
1005 :
1006 188 : if (nZId == -1)
1007 : {
1008 166 : bDefineVar = true;
1009 :
1010 : // Make sure we are in define mode.
1011 166 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1012 :
1013 : char szTempPrivate[256 + 1];
1014 166 : const char *pszTemp = nullptr;
1015 166 : if (!pszBandName || EQUAL(pszBandName, ""))
1016 : {
1017 144 : snprintf(szTempPrivate, sizeof(szTempPrivate), "Band%d", nBand);
1018 144 : pszTemp = szTempPrivate;
1019 : }
1020 : else
1021 : {
1022 22 : pszTemp = pszBandName;
1023 : }
1024 :
1025 : int status;
1026 166 : if (nZDim > 2 && paDimIds != nullptr)
1027 : {
1028 5 : status =
1029 5 : nc_def_var(cdfid, pszTemp, nc_datatype, nZDim, paDimIds, &nZId);
1030 : }
1031 : else
1032 : {
1033 161 : int anBandDims[2] = {poNCDFDS->nYDimID, poNCDFDS->nXDimID};
1034 : status =
1035 161 : nc_def_var(cdfid, pszTemp, nc_datatype, 2, anBandDims, &nZId);
1036 : }
1037 166 : NCDF_ERR(status);
1038 166 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d) id=%d", cdfid, pszTemp,
1039 : nc_datatype, nZId);
1040 :
1041 166 : if (!pszLongName || EQUAL(pszLongName, ""))
1042 : {
1043 159 : snprintf(szTempPrivate, sizeof(szTempPrivate),
1044 : "GDAL Band Number %d", nBand);
1045 159 : pszTemp = szTempPrivate;
1046 : }
1047 : else
1048 : {
1049 7 : pszTemp = pszLongName;
1050 : }
1051 : status =
1052 166 : nc_put_att_text(cdfid, nZId, CF_LNG_NAME, strlen(pszTemp), pszTemp);
1053 166 : NCDF_ERR(status);
1054 :
1055 166 : poNCDFDS->DefVarDeflate(nZId, true);
1056 : }
1057 :
1058 : // For Byte data add signed/unsigned info.
1059 188 : if (eDataType == GDT_UInt8 || eDataType == GDT_Int8)
1060 : {
1061 90 : if (bDefineVar)
1062 : {
1063 : // Only add attributes if creating variable.
1064 : // For unsigned NC_BYTE (except NC4 format),
1065 : // add valid_range and _Unsigned ( defined in CF-1 and NUG ).
1066 82 : if (nc_datatype == NC_BYTE && poNCDFDS->eFormat != NCDF_FORMAT_NC4)
1067 : {
1068 79 : CPLDebug("GDAL_netCDF",
1069 : "adding valid_range attributes for Byte Band");
1070 79 : short l_adfValidRange[2] = {0, 0};
1071 : int status;
1072 79 : if (bSignedData || eDataType == GDT_Int8)
1073 : {
1074 7 : l_adfValidRange[0] = -128;
1075 7 : l_adfValidRange[1] = 127;
1076 7 : status =
1077 7 : nc_put_att_text(cdfid, nZId, "_Unsigned", 5, "false");
1078 : }
1079 : else
1080 : {
1081 72 : l_adfValidRange[0] = 0;
1082 72 : l_adfValidRange[1] = 255;
1083 : status =
1084 72 : nc_put_att_text(cdfid, nZId, "_Unsigned", 4, "true");
1085 : }
1086 79 : NCDF_ERR(status);
1087 79 : status = nc_put_att_short(cdfid, nZId, "valid_range", NC_SHORT,
1088 : 2, l_adfValidRange);
1089 79 : NCDF_ERR(status);
1090 : }
1091 : }
1092 : }
1093 :
1094 188 : if (nc_datatype != NC_BYTE && nc_datatype != NC_CHAR &&
1095 101 : nc_datatype != NC_UBYTE)
1096 : {
1097 : // Set default nodata.
1098 98 : bool bIgnored = false;
1099 : double dfNoData =
1100 98 : NCDFGetDefaultNoDataValue(cdfid, nZId, nc_datatype, bIgnored);
1101 : #ifdef NCDF_DEBUG
1102 : CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) default", dfNoData);
1103 : #endif
1104 98 : netCDFRasterBand::SetNoDataValue(dfNoData);
1105 : }
1106 :
1107 188 : SetBlockSize();
1108 : }
1109 :
1110 : /************************************************************************/
1111 : /* ~netCDFRasterBand() */
1112 : /************************************************************************/
1113 :
1114 1372 : netCDFRasterBand::~netCDFRasterBand()
1115 : {
1116 686 : netCDFRasterBand::FlushCache(true);
1117 686 : CPLFree(panBandZPos);
1118 686 : CPLFree(panBandZLev);
1119 1372 : }
1120 :
1121 : /************************************************************************/
1122 : /* GetMetadata() */
1123 : /************************************************************************/
1124 :
1125 54 : CSLConstList netCDFRasterBand::GetMetadata(const char *pszDomain)
1126 : {
1127 54 : if (!m_bCreateMetadataFromOtherVarsDone)
1128 52 : CreateMetadataFromOtherVars();
1129 54 : return GDALPamRasterBand::GetMetadata(pszDomain);
1130 : }
1131 :
1132 : /************************************************************************/
1133 : /* GetMetadataItem() */
1134 : /************************************************************************/
1135 :
1136 572 : const char *netCDFRasterBand::GetMetadataItem(const char *pszName,
1137 : const char *pszDomain)
1138 : {
1139 572 : if (!m_bCreateMetadataFromOtherVarsDone &&
1140 556 : STARTS_WITH(pszName, "NETCDF_DIM_") &&
1141 1 : (!pszDomain || pszDomain[0] == 0))
1142 1 : CreateMetadataFromOtherVars();
1143 572 : return GDALPamRasterBand::GetMetadataItem(pszName, pszDomain);
1144 : }
1145 :
1146 : /************************************************************************/
1147 : /* SetMetadataItem() */
1148 : /************************************************************************/
1149 :
1150 7 : CPLErr netCDFRasterBand::SetMetadataItem(const char *pszName,
1151 : const char *pszValue,
1152 : const char *pszDomain)
1153 : {
1154 9 : if (GetAccess() == GA_Update &&
1155 9 : (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)
1156 : {
1157 : // Same logic as in CopyMetadata()
1158 :
1159 2 : const char *const papszIgnoreBand[] = {
1160 : CF_ADD_OFFSET, CF_SCALE_FACTOR, "valid_range", "_Unsigned",
1161 : NCDF_FillValue, "coordinates", nullptr};
1162 : // Do not copy varname, stats, NETCDF_DIM_*, nodata
1163 : // and items in papszIgnoreBand.
1164 6 : if (STARTS_WITH(pszName, "NETCDF_VARNAME") ||
1165 2 : STARTS_WITH(pszName, "STATISTICS_") ||
1166 2 : STARTS_WITH(pszName, "NETCDF_DIM_") ||
1167 2 : STARTS_WITH(pszName, "missing_value") ||
1168 6 : STARTS_WITH(pszName, "_FillValue") ||
1169 2 : CSLFindString(papszIgnoreBand, pszName) != -1)
1170 : {
1171 : // do nothing
1172 : }
1173 : else
1174 : {
1175 2 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1176 :
1177 2 : if (!NCDFPutAttr(cdfid, nZId, pszName, pszValue))
1178 2 : return CE_Failure;
1179 : }
1180 : }
1181 :
1182 5 : return GDALPamRasterBand::SetMetadataItem(pszName, pszValue, pszDomain);
1183 : }
1184 :
1185 : /************************************************************************/
1186 : /* SetMetadata() */
1187 : /************************************************************************/
1188 :
1189 2 : CPLErr netCDFRasterBand::SetMetadata(CSLConstList papszMD,
1190 : const char *pszDomain)
1191 : {
1192 4 : if (GetAccess() == GA_Update &&
1193 2 : (pszDomain == nullptr || pszDomain[0] == '\0'))
1194 : {
1195 : // We don't handle metadata item removal for now
1196 4 : for (const char *const *papszIter = papszMD; papszIter && *papszIter;
1197 : ++papszIter)
1198 : {
1199 2 : char *pszName = nullptr;
1200 2 : const char *pszValue = CPLParseNameValue(*papszIter, &pszName);
1201 2 : if (pszName && pszValue)
1202 2 : SetMetadataItem(pszName, pszValue);
1203 2 : CPLFree(pszName);
1204 : }
1205 : }
1206 2 : return GDALPamRasterBand::SetMetadata(papszMD, pszDomain);
1207 : }
1208 :
1209 : /************************************************************************/
1210 : /* GetOffset() */
1211 : /************************************************************************/
1212 54 : double netCDFRasterBand::GetOffset(int *pbSuccess)
1213 : {
1214 54 : if (pbSuccess != nullptr)
1215 49 : *pbSuccess = static_cast<int>(m_bHaveOffset);
1216 :
1217 54 : return m_dfOffset;
1218 : }
1219 :
1220 : /************************************************************************/
1221 : /* SetOffset() */
1222 : /************************************************************************/
1223 1 : CPLErr netCDFRasterBand::SetOffset(double dfNewOffset)
1224 : {
1225 2 : CPLMutexHolderD(&hNCMutex);
1226 :
1227 : // Write value if in update mode.
1228 1 : if (poDS->GetAccess() == GA_Update)
1229 : {
1230 : // Make sure we are in define mode.
1231 1 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1232 :
1233 1 : const int status = nc_put_att_double(cdfid, nZId, CF_ADD_OFFSET,
1234 : NC_DOUBLE, 1, &dfNewOffset);
1235 :
1236 1 : NCDF_ERR(status);
1237 1 : if (status == NC_NOERR)
1238 : {
1239 1 : SetOffsetNoUpdate(dfNewOffset);
1240 1 : return CE_None;
1241 : }
1242 :
1243 0 : return CE_Failure;
1244 : }
1245 :
1246 0 : SetOffsetNoUpdate(dfNewOffset);
1247 0 : return CE_None;
1248 : }
1249 :
1250 : /************************************************************************/
1251 : /* SetOffsetNoUpdate() */
1252 : /************************************************************************/
1253 17 : void netCDFRasterBand::SetOffsetNoUpdate(double dfVal)
1254 : {
1255 17 : m_dfOffset = dfVal;
1256 17 : m_bHaveOffset = true;
1257 17 : }
1258 :
1259 : /************************************************************************/
1260 : /* GetScale() */
1261 : /************************************************************************/
1262 54 : double netCDFRasterBand::GetScale(int *pbSuccess)
1263 : {
1264 54 : if (pbSuccess != nullptr)
1265 49 : *pbSuccess = static_cast<int>(m_bHaveScale);
1266 :
1267 54 : return m_dfScale;
1268 : }
1269 :
1270 : /************************************************************************/
1271 : /* SetScale() */
1272 : /************************************************************************/
1273 1 : CPLErr netCDFRasterBand::SetScale(double dfNewScale)
1274 : {
1275 2 : CPLMutexHolderD(&hNCMutex);
1276 :
1277 : // Write value if in update mode.
1278 1 : if (poDS->GetAccess() == GA_Update)
1279 : {
1280 : // Make sure we are in define mode.
1281 1 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1282 :
1283 1 : const int status = nc_put_att_double(cdfid, nZId, CF_SCALE_FACTOR,
1284 : NC_DOUBLE, 1, &dfNewScale);
1285 :
1286 1 : NCDF_ERR(status);
1287 1 : if (status == NC_NOERR)
1288 : {
1289 1 : SetScaleNoUpdate(dfNewScale);
1290 1 : return CE_None;
1291 : }
1292 :
1293 0 : return CE_Failure;
1294 : }
1295 :
1296 0 : SetScaleNoUpdate(dfNewScale);
1297 0 : return CE_None;
1298 : }
1299 :
1300 : /************************************************************************/
1301 : /* SetScaleNoUpdate() */
1302 : /************************************************************************/
1303 21 : void netCDFRasterBand::SetScaleNoUpdate(double dfVal)
1304 : {
1305 21 : m_dfScale = dfVal;
1306 21 : m_bHaveScale = true;
1307 21 : }
1308 :
1309 : /************************************************************************/
1310 : /* GetUnitType() */
1311 : /************************************************************************/
1312 :
1313 26 : const char *netCDFRasterBand::GetUnitType()
1314 :
1315 : {
1316 26 : if (!m_osUnitType.empty())
1317 6 : return m_osUnitType;
1318 :
1319 20 : return GDALRasterBand::GetUnitType();
1320 : }
1321 :
1322 : /************************************************************************/
1323 : /* SetUnitType() */
1324 : /************************************************************************/
1325 :
1326 1 : CPLErr netCDFRasterBand::SetUnitType(const char *pszNewValue)
1327 :
1328 : {
1329 2 : CPLMutexHolderD(&hNCMutex);
1330 :
1331 2 : const std::string osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
1332 :
1333 1 : if (!osUnitType.empty())
1334 : {
1335 : // Write value if in update mode.
1336 1 : if (poDS->GetAccess() == GA_Update)
1337 : {
1338 : // Make sure we are in define mode.
1339 1 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(TRUE);
1340 :
1341 1 : const int status = nc_put_att_text(
1342 : cdfid, nZId, CF_UNITS, osUnitType.size(), osUnitType.c_str());
1343 :
1344 1 : NCDF_ERR(status);
1345 1 : if (status == NC_NOERR)
1346 : {
1347 1 : SetUnitTypeNoUpdate(pszNewValue);
1348 1 : return CE_None;
1349 : }
1350 :
1351 0 : return CE_Failure;
1352 : }
1353 : }
1354 :
1355 0 : SetUnitTypeNoUpdate(pszNewValue);
1356 :
1357 0 : return CE_None;
1358 : }
1359 :
1360 : /************************************************************************/
1361 : /* SetUnitTypeNoUpdate() */
1362 : /************************************************************************/
1363 :
1364 499 : void netCDFRasterBand::SetUnitTypeNoUpdate(const char *pszNewValue)
1365 : {
1366 499 : m_osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
1367 499 : }
1368 :
1369 : /************************************************************************/
1370 : /* GetNoDataValue() */
1371 : /************************************************************************/
1372 :
1373 193 : double netCDFRasterBand::GetNoDataValue(int *pbSuccess)
1374 :
1375 : {
1376 193 : if (m_bNoDataSetAsInt64)
1377 : {
1378 0 : if (pbSuccess)
1379 0 : *pbSuccess = TRUE;
1380 0 : return GDALGetNoDataValueCastToDouble(m_nNodataValueInt64);
1381 : }
1382 :
1383 193 : if (m_bNoDataSetAsUInt64)
1384 : {
1385 0 : if (pbSuccess)
1386 0 : *pbSuccess = TRUE;
1387 0 : return GDALGetNoDataValueCastToDouble(m_nNodataValueUInt64);
1388 : }
1389 :
1390 193 : if (m_bNoDataSet)
1391 : {
1392 144 : if (pbSuccess)
1393 127 : *pbSuccess = TRUE;
1394 144 : return m_dfNoDataValue;
1395 : }
1396 :
1397 49 : return GDALPamRasterBand::GetNoDataValue(pbSuccess);
1398 : }
1399 :
1400 : /************************************************************************/
1401 : /* GetNoDataValueAsInt64() */
1402 : /************************************************************************/
1403 :
1404 4 : int64_t netCDFRasterBand::GetNoDataValueAsInt64(int *pbSuccess)
1405 :
1406 : {
1407 4 : if (m_bNoDataSetAsInt64)
1408 : {
1409 4 : if (pbSuccess)
1410 4 : *pbSuccess = TRUE;
1411 :
1412 4 : return m_nNodataValueInt64;
1413 : }
1414 :
1415 0 : return GDALPamRasterBand::GetNoDataValueAsInt64(pbSuccess);
1416 : }
1417 :
1418 : /************************************************************************/
1419 : /* GetNoDataValueAsUInt64() */
1420 : /************************************************************************/
1421 :
1422 4 : uint64_t netCDFRasterBand::GetNoDataValueAsUInt64(int *pbSuccess)
1423 :
1424 : {
1425 4 : if (m_bNoDataSetAsUInt64)
1426 : {
1427 4 : if (pbSuccess)
1428 4 : *pbSuccess = TRUE;
1429 :
1430 4 : return m_nNodataValueUInt64;
1431 : }
1432 :
1433 0 : return GDALPamRasterBand::GetNoDataValueAsUInt64(pbSuccess);
1434 : }
1435 :
1436 : /************************************************************************/
1437 : /* SetNoDataValue() */
1438 : /************************************************************************/
1439 :
1440 134 : CPLErr netCDFRasterBand::SetNoDataValue(double dfNoData)
1441 :
1442 : {
1443 268 : CPLMutexHolderD(&hNCMutex);
1444 :
1445 : // If already set to new value, don't do anything.
1446 134 : if (m_bNoDataSet && CPLIsEqual(dfNoData, m_dfNoDataValue))
1447 19 : return CE_None;
1448 :
1449 : // Write value if in update mode.
1450 115 : if (poDS->GetAccess() == GA_Update)
1451 : {
1452 : // netcdf-4 does not allow to set _FillValue after leaving define mode,
1453 : // but it is ok if variable has not been written to, so only print
1454 : // debug. See bug #4484.
1455 125 : if (m_bNoDataSet &&
1456 10 : !cpl::down_cast<netCDFDataset *>(poDS)->GetDefineMode())
1457 : {
1458 0 : CPLDebug("GDAL_netCDF",
1459 : "Setting NoDataValue to %.17g (previously set to %.17g) "
1460 : "but file is no longer in define mode (id #%d, band #%d)",
1461 : dfNoData, m_dfNoDataValue, cdfid, nBand);
1462 : }
1463 : #ifdef NCDF_DEBUG
1464 : else
1465 : {
1466 : CPLDebug("GDAL_netCDF",
1467 : "Setting NoDataValue to %.17g (id #%d, band #%d)",
1468 : dfNoData, cdfid, nBand);
1469 : }
1470 : #endif
1471 : // Make sure we are in define mode.
1472 115 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1473 :
1474 : int status;
1475 115 : if (eDataType == GDT_UInt8)
1476 : {
1477 6 : if (bSignedData)
1478 : {
1479 0 : signed char cNoDataValue = static_cast<signed char>(dfNoData);
1480 0 : status = nc_put_att_schar(cdfid, nZId, NCDF_FillValue,
1481 : nc_datatype, 1, &cNoDataValue);
1482 : }
1483 : else
1484 : {
1485 6 : const unsigned char ucNoDataValue =
1486 6 : static_cast<unsigned char>(dfNoData);
1487 6 : status = nc_put_att_uchar(cdfid, nZId, NCDF_FillValue,
1488 : nc_datatype, 1, &ucNoDataValue);
1489 : }
1490 : }
1491 109 : else if (eDataType == GDT_Int16)
1492 : {
1493 14 : short nsNoDataValue = static_cast<short>(dfNoData);
1494 14 : status = nc_put_att_short(cdfid, nZId, NCDF_FillValue, nc_datatype,
1495 : 1, &nsNoDataValue);
1496 : }
1497 95 : else if (eDataType == GDT_Int32)
1498 : {
1499 27 : int nNoDataValue = static_cast<int>(dfNoData);
1500 27 : status = nc_put_att_int(cdfid, nZId, NCDF_FillValue, nc_datatype, 1,
1501 : &nNoDataValue);
1502 : }
1503 68 : else if (eDataType == GDT_Float32)
1504 : {
1505 31 : float fNoDataValue = static_cast<float>(dfNoData);
1506 31 : status = nc_put_att_float(cdfid, nZId, NCDF_FillValue, nc_datatype,
1507 : 1, &fNoDataValue);
1508 : }
1509 43 : else if (eDataType == GDT_UInt16 &&
1510 6 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat ==
1511 : NCDF_FORMAT_NC4)
1512 : {
1513 6 : unsigned short usNoDataValue =
1514 6 : static_cast<unsigned short>(dfNoData);
1515 6 : status = nc_put_att_ushort(cdfid, nZId, NCDF_FillValue, nc_datatype,
1516 : 1, &usNoDataValue);
1517 : }
1518 38 : else if (eDataType == GDT_UInt32 &&
1519 7 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat ==
1520 : NCDF_FORMAT_NC4)
1521 : {
1522 7 : unsigned int unNoDataValue = static_cast<unsigned int>(dfNoData);
1523 7 : status = nc_put_att_uint(cdfid, nZId, NCDF_FillValue, nc_datatype,
1524 : 1, &unNoDataValue);
1525 : }
1526 : else
1527 : {
1528 24 : status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
1529 : 1, &dfNoData);
1530 : }
1531 :
1532 115 : NCDF_ERR(status);
1533 :
1534 : // Update status if write worked.
1535 115 : if (status == NC_NOERR)
1536 : {
1537 115 : SetNoDataValueNoUpdate(dfNoData);
1538 115 : return CE_None;
1539 : }
1540 :
1541 0 : return CE_Failure;
1542 : }
1543 :
1544 0 : SetNoDataValueNoUpdate(dfNoData);
1545 0 : return CE_None;
1546 : }
1547 :
1548 : /************************************************************************/
1549 : /* SetNoDataValueNoUpdate() */
1550 : /************************************************************************/
1551 :
1552 443 : void netCDFRasterBand::SetNoDataValueNoUpdate(double dfNoData)
1553 : {
1554 443 : m_dfNoDataValue = dfNoData;
1555 443 : m_bNoDataSet = true;
1556 443 : m_bNoDataSetAsInt64 = false;
1557 443 : m_bNoDataSetAsUInt64 = false;
1558 443 : }
1559 :
1560 : /************************************************************************/
1561 : /* SetNoDataValueAsInt64() */
1562 : /************************************************************************/
1563 :
1564 3 : CPLErr netCDFRasterBand::SetNoDataValueAsInt64(int64_t nNoData)
1565 :
1566 : {
1567 6 : CPLMutexHolderD(&hNCMutex);
1568 :
1569 : // If already set to new value, don't do anything.
1570 3 : if (m_bNoDataSetAsInt64 && nNoData == m_nNodataValueInt64)
1571 0 : return CE_None;
1572 :
1573 : // Write value if in update mode.
1574 3 : if (poDS->GetAccess() == GA_Update)
1575 : {
1576 : // netcdf-4 does not allow to set NCDF_FillValue after leaving define mode,
1577 : // but it is ok if variable has not been written to, so only print
1578 : // debug. See bug #4484.
1579 3 : if (m_bNoDataSetAsInt64 &&
1580 0 : !cpl::down_cast<netCDFDataset *>(poDS)->GetDefineMode())
1581 : {
1582 0 : CPLDebug("GDAL_netCDF",
1583 : "Setting NoDataValue to " CPL_FRMT_GIB
1584 : " (previously set to " CPL_FRMT_GIB ") "
1585 : "but file is no longer in define mode (id #%d, band #%d)",
1586 : static_cast<GIntBig>(nNoData),
1587 0 : static_cast<GIntBig>(m_nNodataValueInt64), cdfid, nBand);
1588 : }
1589 : #ifdef NCDF_DEBUG
1590 : else
1591 : {
1592 : CPLDebug("GDAL_netCDF",
1593 : "Setting NoDataValue to " CPL_FRMT_GIB
1594 : " (id #%d, band #%d)",
1595 : static_cast<GIntBig>(nNoData), cdfid, nBand);
1596 : }
1597 : #endif
1598 : // Make sure we are in define mode.
1599 3 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1600 :
1601 : int status;
1602 6 : if (eDataType == GDT_Int64 &&
1603 3 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
1604 : {
1605 3 : long long tmp = static_cast<long long>(nNoData);
1606 3 : status = nc_put_att_longlong(cdfid, nZId, NCDF_FillValue,
1607 : nc_datatype, 1, &tmp);
1608 : }
1609 : else
1610 : {
1611 0 : double dfNoData = static_cast<double>(nNoData);
1612 0 : status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
1613 : 1, &dfNoData);
1614 : }
1615 :
1616 3 : NCDF_ERR(status);
1617 :
1618 : // Update status if write worked.
1619 3 : if (status == NC_NOERR)
1620 : {
1621 3 : SetNoDataValueNoUpdate(nNoData);
1622 3 : return CE_None;
1623 : }
1624 :
1625 0 : return CE_Failure;
1626 : }
1627 :
1628 0 : SetNoDataValueNoUpdate(nNoData);
1629 0 : return CE_None;
1630 : }
1631 :
1632 : /************************************************************************/
1633 : /* SetNoDataValueNoUpdate() */
1634 : /************************************************************************/
1635 :
1636 11 : void netCDFRasterBand::SetNoDataValueNoUpdate(int64_t nNoData)
1637 : {
1638 11 : m_nNodataValueInt64 = nNoData;
1639 11 : m_bNoDataSet = false;
1640 11 : m_bNoDataSetAsInt64 = true;
1641 11 : m_bNoDataSetAsUInt64 = false;
1642 11 : }
1643 :
1644 : /************************************************************************/
1645 : /* SetNoDataValueAsUInt64() */
1646 : /************************************************************************/
1647 :
1648 3 : CPLErr netCDFRasterBand::SetNoDataValueAsUInt64(uint64_t nNoData)
1649 :
1650 : {
1651 6 : CPLMutexHolderD(&hNCMutex);
1652 :
1653 : // If already set to new value, don't do anything.
1654 3 : if (m_bNoDataSetAsUInt64 && nNoData == m_nNodataValueUInt64)
1655 0 : return CE_None;
1656 :
1657 : // Write value if in update mode.
1658 3 : if (poDS->GetAccess() == GA_Update)
1659 : {
1660 : // netcdf-4 does not allow to set _FillValue after leaving define mode,
1661 : // but it is ok if variable has not been written to, so only print
1662 : // debug. See bug #4484.
1663 3 : if (m_bNoDataSetAsUInt64 &&
1664 0 : !cpl::down_cast<netCDFDataset *>(poDS)->GetDefineMode())
1665 : {
1666 0 : CPLDebug("GDAL_netCDF",
1667 : "Setting NoDataValue to " CPL_FRMT_GUIB
1668 : " (previously set to " CPL_FRMT_GUIB ") "
1669 : "but file is no longer in define mode (id #%d, band #%d)",
1670 : static_cast<GUIntBig>(nNoData),
1671 0 : static_cast<GUIntBig>(m_nNodataValueUInt64), cdfid, nBand);
1672 : }
1673 : #ifdef NCDF_DEBUG
1674 : else
1675 : {
1676 : CPLDebug("GDAL_netCDF",
1677 : "Setting NoDataValue to " CPL_FRMT_GUIB
1678 : " (id #%d, band #%d)",
1679 : static_cast<GUIntBig>(nNoData), cdfid, nBand);
1680 : }
1681 : #endif
1682 : // Make sure we are in define mode.
1683 3 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1684 :
1685 : int status;
1686 6 : if (eDataType == GDT_UInt64 &&
1687 3 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
1688 : {
1689 3 : unsigned long long tmp = static_cast<long long>(nNoData);
1690 3 : status = nc_put_att_ulonglong(cdfid, nZId, NCDF_FillValue,
1691 : nc_datatype, 1, &tmp);
1692 : }
1693 : else
1694 : {
1695 0 : double dfNoData = static_cast<double>(nNoData);
1696 0 : status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
1697 : 1, &dfNoData);
1698 : }
1699 :
1700 3 : NCDF_ERR(status);
1701 :
1702 : // Update status if write worked.
1703 3 : if (status == NC_NOERR)
1704 : {
1705 3 : SetNoDataValueNoUpdate(nNoData);
1706 3 : return CE_None;
1707 : }
1708 :
1709 0 : return CE_Failure;
1710 : }
1711 :
1712 0 : SetNoDataValueNoUpdate(nNoData);
1713 0 : return CE_None;
1714 : }
1715 :
1716 : /************************************************************************/
1717 : /* SetNoDataValueNoUpdate() */
1718 : /************************************************************************/
1719 :
1720 10 : void netCDFRasterBand::SetNoDataValueNoUpdate(uint64_t nNoData)
1721 : {
1722 10 : m_nNodataValueUInt64 = nNoData;
1723 10 : m_bNoDataSet = false;
1724 10 : m_bNoDataSetAsInt64 = false;
1725 10 : m_bNoDataSetAsUInt64 = true;
1726 10 : }
1727 :
1728 : /************************************************************************/
1729 : /* DeleteNoDataValue() */
1730 : /************************************************************************/
1731 :
1732 : #ifdef notdef
1733 : CPLErr netCDFRasterBand::DeleteNoDataValue()
1734 :
1735 : {
1736 : CPLMutexHolderD(&hNCMutex);
1737 :
1738 : if (!bNoDataSet)
1739 : return CE_None;
1740 :
1741 : // Write value if in update mode.
1742 : if (poDS->GetAccess() == GA_Update)
1743 : {
1744 : // Make sure we are in define mode.
1745 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1746 :
1747 : status = nc_del_att(cdfid, nZId, NCDF_FillValue);
1748 :
1749 : NCDF_ERR(status);
1750 :
1751 : // Update status if write worked.
1752 : if (status == NC_NOERR)
1753 : {
1754 : dfNoDataValue = 0.0;
1755 : bNoDataSet = false;
1756 : return CE_None;
1757 : }
1758 :
1759 : return CE_Failure;
1760 : }
1761 :
1762 : dfNoDataValue = 0.0;
1763 : bNoDataSet = false;
1764 : return CE_None;
1765 : }
1766 : #endif
1767 :
1768 : /************************************************************************/
1769 : /* SerializeToXML() */
1770 : /************************************************************************/
1771 :
1772 5 : CPLXMLNode *netCDFRasterBand::SerializeToXML(const char * /* pszUnused */)
1773 : {
1774 : // Overridden from GDALPamDataset to add only band histogram
1775 : // and statistics. See bug #4244.
1776 5 : if (psPam == nullptr)
1777 0 : return nullptr;
1778 :
1779 : // Setup root node and attributes.
1780 : CPLXMLNode *psTree =
1781 5 : CPLCreateXMLNode(nullptr, CXT_Element, "PAMRasterBand");
1782 :
1783 5 : if (GetBand() > 0)
1784 : {
1785 10 : CPLString oFmt;
1786 5 : CPLSetXMLValue(psTree, "#band", oFmt.Printf("%d", GetBand()));
1787 : }
1788 :
1789 : // Histograms.
1790 5 : if (psPam->psSavedHistograms != nullptr)
1791 1 : CPLAddXMLChild(psTree, CPLCloneXMLTree(psPam->psSavedHistograms));
1792 :
1793 : // Metadata (statistics only).
1794 5 : GDALMultiDomainMetadata oMDMDStats;
1795 5 : const char *papszMDStats[] = {"STATISTICS_MINIMUM", "STATISTICS_MAXIMUM",
1796 : "STATISTICS_MEAN", "STATISTICS_STDDEV",
1797 : nullptr};
1798 25 : for (int i = 0; i < CSLCount(papszMDStats); i++)
1799 : {
1800 20 : const char *pszMDI = GetMetadataItem(papszMDStats[i]);
1801 20 : if (pszMDI)
1802 4 : oMDMDStats.SetMetadataItem(papszMDStats[i], pszMDI);
1803 : }
1804 5 : CPLXMLNode *psMD = oMDMDStats.Serialize();
1805 :
1806 5 : if (psMD != nullptr)
1807 : {
1808 1 : if (psMD->psChild == nullptr)
1809 0 : CPLDestroyXMLNode(psMD);
1810 : else
1811 1 : CPLAddXMLChild(psTree, psMD);
1812 : }
1813 :
1814 : // We don't want to return anything if we had no metadata to attach.
1815 5 : if (psTree->psChild == nullptr || psTree->psChild->psNext == nullptr)
1816 : {
1817 3 : CPLDestroyXMLNode(psTree);
1818 3 : psTree = nullptr;
1819 : }
1820 :
1821 5 : return psTree;
1822 : }
1823 :
1824 : /************************************************************************/
1825 : /* Get1DVariableIndexedByDimension() */
1826 : /************************************************************************/
1827 :
1828 81 : static int Get1DVariableIndexedByDimension(int cdfid, int nDimId,
1829 : const char *pszDimName,
1830 : bool bVerboseError, int *pnGroupID)
1831 : {
1832 81 : *pnGroupID = -1;
1833 81 : int nVarID = -1;
1834 : // First try to find a variable whose name is identical to the dimension
1835 : // name, and check that it is indeed indexed by this dimension
1836 81 : if (NCDFResolveVar(cdfid, pszDimName, pnGroupID, &nVarID) == CE_None)
1837 : {
1838 67 : int nDimCountOfVariable = 0;
1839 67 : nc_inq_varndims(*pnGroupID, nVarID, &nDimCountOfVariable);
1840 67 : if (nDimCountOfVariable == 1)
1841 : {
1842 67 : int nDimIdOfVariable = -1;
1843 67 : nc_inq_vardimid(*pnGroupID, nVarID, &nDimIdOfVariable);
1844 67 : if (nDimIdOfVariable == nDimId)
1845 : {
1846 67 : return nVarID;
1847 : }
1848 : }
1849 : }
1850 :
1851 : // Otherwise iterate over the variables to find potential candidates
1852 : // TODO: should be modified to search also in other groups using the same
1853 : // logic than in NCDFResolveVar(), but maybe not needed if it's a
1854 : // very rare case? and I think this is not CF compliant.
1855 14 : int nvars = 0;
1856 14 : CPL_IGNORE_RET_VAL(nc_inq(cdfid, nullptr, &nvars, nullptr, nullptr));
1857 :
1858 14 : int nCountCandidateVars = 0;
1859 14 : int nCandidateVarID = -1;
1860 65 : for (int k = 0; k < nvars; k++)
1861 : {
1862 51 : int nDimCountOfVariable = 0;
1863 51 : nc_inq_varndims(cdfid, k, &nDimCountOfVariable);
1864 51 : if (nDimCountOfVariable == 1)
1865 : {
1866 27 : int nDimIdOfVariable = -1;
1867 27 : nc_inq_vardimid(cdfid, k, &nDimIdOfVariable);
1868 27 : if (nDimIdOfVariable == nDimId)
1869 : {
1870 7 : nCountCandidateVars++;
1871 7 : nCandidateVarID = k;
1872 : }
1873 : }
1874 : }
1875 14 : if (nCountCandidateVars > 1)
1876 : {
1877 1 : if (bVerboseError)
1878 : {
1879 1 : CPLError(CE_Warning, CPLE_AppDefined,
1880 : "Several 1D variables are indexed by dimension %s",
1881 : pszDimName);
1882 : }
1883 1 : *pnGroupID = -1;
1884 1 : return -1;
1885 : }
1886 13 : else if (nCandidateVarID < 0)
1887 : {
1888 8 : if (bVerboseError)
1889 : {
1890 8 : CPLError(CE_Warning, CPLE_AppDefined,
1891 : "No 1D variable is indexed by dimension %s", pszDimName);
1892 : }
1893 : }
1894 13 : *pnGroupID = cdfid;
1895 13 : return nCandidateVarID;
1896 : }
1897 :
1898 : /************************************************************************/
1899 : /* CreateMetadataFromAttributes() */
1900 : /************************************************************************/
1901 :
1902 498 : void netCDFRasterBand::CreateMetadataFromAttributes()
1903 : {
1904 498 : char szVarName[NC_MAX_NAME + 1] = {};
1905 498 : int status = nc_inq_varname(cdfid, nZId, szVarName);
1906 498 : NCDF_ERR(status);
1907 :
1908 498 : GDALPamRasterBand::SetMetadataItem("NETCDF_VARNAME", szVarName);
1909 :
1910 : // Get attribute metadata.
1911 498 : int nAtt = 0;
1912 498 : NCDF_ERR(nc_inq_varnatts(cdfid, nZId, &nAtt));
1913 :
1914 2120 : for (int i = 0; i < nAtt; i++)
1915 : {
1916 1622 : char szMetaName[NC_MAX_NAME + 1] = {};
1917 1622 : status = nc_inq_attname(cdfid, nZId, i, szMetaName);
1918 1622 : if (status != NC_NOERR)
1919 12 : continue;
1920 :
1921 1622 : if (GDALPamRasterBand::GetMetadataItem(szMetaName) != nullptr)
1922 : {
1923 12 : continue;
1924 : }
1925 :
1926 1610 : char *pszMetaValue = nullptr;
1927 1610 : if (NCDFGetAttr(cdfid, nZId, szMetaName, &pszMetaValue) == CE_None)
1928 : {
1929 1610 : GDALPamRasterBand::SetMetadataItem(szMetaName, pszMetaValue);
1930 : }
1931 : else
1932 : {
1933 0 : CPLDebug("GDAL_netCDF", "invalid Band metadata %s", szMetaName);
1934 : }
1935 :
1936 1610 : if (pszMetaValue)
1937 : {
1938 1610 : CPLFree(pszMetaValue);
1939 1610 : pszMetaValue = nullptr;
1940 : }
1941 : }
1942 498 : }
1943 :
1944 : /************************************************************************/
1945 : /* CreateMetadataFromOtherVars() */
1946 : /************************************************************************/
1947 :
1948 53 : void netCDFRasterBand::CreateMetadataFromOtherVars()
1949 :
1950 : {
1951 53 : CPLAssert(!m_bCreateMetadataFromOtherVarsDone);
1952 53 : m_bCreateMetadataFromOtherVarsDone = true;
1953 :
1954 53 : netCDFDataset *l_poDS = cpl::down_cast<netCDFDataset *>(poDS);
1955 53 : const int nPamFlagsBackup = l_poDS->nPamFlags;
1956 :
1957 : // Compute all dimensions from Band number and save in Metadata.
1958 53 : int nd = 0;
1959 53 : nc_inq_varndims(cdfid, nZId, &nd);
1960 : // Compute multidimention band position.
1961 : //
1962 : // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
1963 : // if Data[2,3,4,x,y]
1964 : //
1965 : // BandPos0 = (nBand) / (3*4)
1966 : // BandPos1 = (nBand - BandPos0*(3*4)) / (4)
1967 : // BandPos2 = (nBand - BandPos0*(3*4)) % (4)
1968 :
1969 53 : int Sum = 1;
1970 53 : if (nd == 3)
1971 : {
1972 5 : Sum *= panBandZLev[0];
1973 : }
1974 :
1975 : // Loop over non-spatial dimensions.
1976 53 : int Taken = 0;
1977 :
1978 93 : for (int i = 0; i < nd - 2; i++)
1979 : {
1980 : int result;
1981 40 : if (i != nd - 2 - 1)
1982 : {
1983 18 : Sum = 1;
1984 37 : for (int j = i + 1; j < nd - 2; j++)
1985 : {
1986 19 : Sum *= panBandZLev[j];
1987 : }
1988 18 : result = static_cast<int>((nLevel - Taken) / Sum);
1989 : }
1990 : else
1991 : {
1992 22 : result = static_cast<int>((nLevel - Taken) % Sum);
1993 : }
1994 :
1995 40 : char szName[NC_MAX_NAME + 1] = {};
1996 40 : snprintf(szName, sizeof(szName), "%s",
1997 40 : l_poDS->papszDimName[l_poDS->m_anDimIds[panBandZPos[i]]]);
1998 :
1999 : char szMetaName[NC_MAX_NAME + 1 + 32];
2000 40 : snprintf(szMetaName, sizeof(szMetaName), "NETCDF_DIM_%s", szName);
2001 :
2002 40 : const int nGroupID = l_poDS->m_anExtraDimGroupIds[i];
2003 40 : const int nVarID = l_poDS->m_anExtraDimVarIds[i];
2004 40 : if (nVarID < 0)
2005 : {
2006 2 : GDALPamRasterBand::SetMetadataItem(szMetaName,
2007 : CPLSPrintf("%d", result + 1));
2008 : }
2009 : else
2010 : {
2011 : // TODO: Make sure all the status checks make sense.
2012 :
2013 38 : nc_type nVarType = NC_NAT;
2014 38 : /* status = */ nc_inq_vartype(nGroupID, nVarID, &nVarType);
2015 :
2016 38 : int nDims = 0;
2017 38 : /* status = */ nc_inq_varndims(nGroupID, nVarID, &nDims);
2018 :
2019 38 : char szMetaTemp[256] = {};
2020 38 : if (nDims == 1)
2021 : {
2022 38 : size_t count[1] = {1};
2023 38 : size_t start[1] = {static_cast<size_t>(result)};
2024 :
2025 38 : switch (nVarType)
2026 : {
2027 0 : case NC_BYTE:
2028 : // TODO: Check for signed/unsigned byte.
2029 : signed char cData;
2030 0 : /* status = */ nc_get_vara_schar(nGroupID, nVarID,
2031 : start, count, &cData);
2032 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", cData);
2033 0 : break;
2034 0 : case NC_SHORT:
2035 : short sData;
2036 0 : /* status = */ nc_get_vara_short(nGroupID, nVarID,
2037 : start, count, &sData);
2038 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", sData);
2039 0 : break;
2040 19 : case NC_INT:
2041 : {
2042 : int nData;
2043 19 : /* status = */ nc_get_vara_int(nGroupID, nVarID, start,
2044 : count, &nData);
2045 19 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", nData);
2046 19 : break;
2047 : }
2048 0 : case NC_FLOAT:
2049 : float fData;
2050 0 : /* status = */ nc_get_vara_float(nGroupID, nVarID,
2051 : start, count, &fData);
2052 0 : CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.8g",
2053 : fData);
2054 0 : break;
2055 18 : case NC_DOUBLE:
2056 : double dfData;
2057 18 : /* status = */ nc_get_vara_double(
2058 : nGroupID, nVarID, start, count, &dfData);
2059 18 : CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.16g",
2060 : dfData);
2061 18 : break;
2062 0 : case NC_UBYTE:
2063 : unsigned char ucData;
2064 0 : /* status = */ nc_get_vara_uchar(nGroupID, nVarID,
2065 : start, count, &ucData);
2066 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", ucData);
2067 0 : break;
2068 0 : case NC_USHORT:
2069 : unsigned short usData;
2070 0 : /* status = */ nc_get_vara_ushort(
2071 : nGroupID, nVarID, start, count, &usData);
2072 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", usData);
2073 0 : break;
2074 0 : case NC_UINT:
2075 : {
2076 : unsigned int unData;
2077 0 : /* status = */ nc_get_vara_uint(nGroupID, nVarID, start,
2078 : count, &unData);
2079 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", unData);
2080 0 : break;
2081 : }
2082 1 : case NC_INT64:
2083 : {
2084 : long long nData;
2085 1 : /* status = */ nc_get_vara_longlong(
2086 : nGroupID, nVarID, start, count, &nData);
2087 1 : snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GIB,
2088 : nData);
2089 1 : break;
2090 : }
2091 0 : case NC_UINT64:
2092 : {
2093 : unsigned long long unData;
2094 0 : /* status = */ nc_get_vara_ulonglong(
2095 : nGroupID, nVarID, start, count, &unData);
2096 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GUIB,
2097 : unData);
2098 0 : break;
2099 : }
2100 0 : default:
2101 0 : CPLDebug("GDAL_netCDF", "invalid dim %s, type=%d",
2102 : szMetaTemp, nVarType);
2103 0 : break;
2104 : }
2105 : }
2106 : else
2107 : {
2108 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", result + 1);
2109 : }
2110 :
2111 : // Save dimension value.
2112 : // NOTE: removed #original_units as not part of CF-1.
2113 :
2114 38 : GDALPamRasterBand::SetMetadataItem(szMetaName, szMetaTemp);
2115 : }
2116 :
2117 : // Avoid int32 overflow. Perhaps something more sensible to do here ?
2118 40 : if (result > 0 && Sum > INT_MAX / result)
2119 0 : break;
2120 40 : if (Taken > INT_MAX - result * Sum)
2121 0 : break;
2122 :
2123 40 : Taken += result * Sum;
2124 : } // End loop non-spatial dimensions.
2125 :
2126 53 : l_poDS->nPamFlags = nPamFlagsBackup;
2127 53 : }
2128 :
2129 : /************************************************************************/
2130 : /* CheckData() */
2131 : /************************************************************************/
2132 : template <class T>
2133 5912 : void netCDFRasterBand::CheckData(void *pImage, void *pImageNC,
2134 : size_t nTmpBlockXSize, size_t nTmpBlockYSize,
2135 : bool bCheckIsNan)
2136 : {
2137 5912 : CPLAssert(pImage != nullptr && pImageNC != nullptr);
2138 :
2139 : // If this block is not a full block (in the x axis), we need to re-arrange
2140 : // the data this is because partial blocks are not arranged the same way in
2141 : // netcdf and gdal.
2142 5912 : if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
2143 : {
2144 6 : T *ptrWrite = static_cast<T *>(pImage);
2145 6 : T *ptrRead = static_cast<T *>(pImageNC);
2146 29 : for (size_t j = 0; j < nTmpBlockYSize;
2147 23 : j++, ptrWrite += nBlockXSize, ptrRead += nTmpBlockXSize)
2148 : {
2149 23 : memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T));
2150 : }
2151 : }
2152 :
2153 : // Is valid data checking needed or requested?
2154 5912 : if (bValidRangeValid || bCheckIsNan)
2155 : {
2156 1345 : T *ptrImage = static_cast<T *>(pImage);
2157 2744 : for (size_t j = 0; j < nTmpBlockYSize; j++)
2158 : {
2159 : // k moves along the gdal block, skipping the out-of-range pixels.
2160 1399 : size_t k = j * nBlockXSize;
2161 98618 : for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
2162 : {
2163 : // Check for nodata and nan.
2164 97219 : if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
2165 6301 : continue;
2166 90918 : if (bCheckIsNan && std::isnan((double)ptrImage[k]))
2167 : {
2168 5737 : ptrImage[k] = (T)m_dfNoDataValue;
2169 5737 : continue;
2170 : }
2171 : // Check for valid_range.
2172 85181 : if (bValidRangeValid)
2173 : {
2174 40986 : if (((adfValidRange[0] != m_dfNoDataValue) &&
2175 40986 : (ptrImage[k] < (T)adfValidRange[0])) ||
2176 40983 : ((adfValidRange[1] != m_dfNoDataValue) &&
2177 40983 : (ptrImage[k] > (T)adfValidRange[1])))
2178 : {
2179 4 : ptrImage[k] = (T)m_dfNoDataValue;
2180 : }
2181 : }
2182 : }
2183 : }
2184 : }
2185 :
2186 : // If minimum longitude is > 180, subtract 360 from all.
2187 : // If not, disable checking for further calls (check just once).
2188 : // Only check first and last block elements since lon must be monotonic.
2189 5912 : const bool bIsSigned = std::numeric_limits<T>::is_signed;
2190 5581 : if (bCheckLongitude && bIsSigned &&
2191 11 : !CPLIsEqual((double)((T *)pImage)[0], m_dfNoDataValue) &&
2192 10 : !CPLIsEqual((double)((T *)pImage)[nTmpBlockXSize - 1],
2193 2796 : m_dfNoDataValue) &&
2194 10 : std::min(((T *)pImage)[0], ((T *)pImage)[nTmpBlockXSize - 1]) > 180.0)
2195 : {
2196 0 : T *ptrImage = static_cast<T *>(pImage);
2197 0 : for (size_t j = 0; j < nTmpBlockYSize; j++)
2198 : {
2199 0 : size_t k = j * nBlockXSize;
2200 0 : for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
2201 : {
2202 0 : if (!CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
2203 0 : ptrImage[k] = static_cast<T>(ptrImage[k] - 360);
2204 : }
2205 : }
2206 : }
2207 : else
2208 : {
2209 5912 : bCheckLongitude = false;
2210 : }
2211 5912 : }
2212 :
2213 : /************************************************************************/
2214 : /* CheckDataCpx() */
2215 : /************************************************************************/
2216 : template <class T>
2217 25 : void netCDFRasterBand::CheckDataCpx(void *pImage, void *pImageNC,
2218 : size_t nTmpBlockXSize,
2219 : size_t nTmpBlockYSize, bool bCheckIsNan)
2220 : {
2221 25 : CPLAssert(pImage != nullptr && pImageNC != nullptr);
2222 :
2223 : // If this block is not a full block (in the x axis), we need to re-arrange
2224 : // the data this is because partial blocks are not arranged the same way in
2225 : // netcdf and gdal.
2226 25 : if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
2227 : {
2228 0 : T *ptrWrite = static_cast<T *>(pImage);
2229 0 : T *ptrRead = static_cast<T *>(pImageNC);
2230 0 : for (size_t j = 0; j < nTmpBlockYSize; j++,
2231 0 : ptrWrite += (2 * nBlockXSize),
2232 0 : ptrRead += (2 * nTmpBlockXSize))
2233 : {
2234 0 : memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T) * 2);
2235 : }
2236 : }
2237 :
2238 : // Is valid data checking needed or requested?
2239 25 : if (bValidRangeValid || bCheckIsNan)
2240 : {
2241 0 : T *ptrImage = static_cast<T *>(pImage);
2242 0 : for (size_t j = 0; j < nTmpBlockYSize; j++)
2243 : {
2244 : // k moves along the gdal block, skipping the out-of-range pixels.
2245 0 : size_t k = 2 * j * nBlockXSize;
2246 0 : for (size_t i = 0; i < (2 * nTmpBlockXSize); i++, k++)
2247 : {
2248 : // Check for nodata and nan.
2249 0 : if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
2250 0 : continue;
2251 0 : if (bCheckIsNan && std::isnan((double)ptrImage[k]))
2252 : {
2253 0 : ptrImage[k] = (T)m_dfNoDataValue;
2254 0 : continue;
2255 : }
2256 : // Check for valid_range.
2257 0 : if (bValidRangeValid)
2258 : {
2259 0 : if (((adfValidRange[0] != m_dfNoDataValue) &&
2260 0 : (ptrImage[k] < (T)adfValidRange[0])) ||
2261 0 : ((adfValidRange[1] != m_dfNoDataValue) &&
2262 0 : (ptrImage[k] > (T)adfValidRange[1])))
2263 : {
2264 0 : ptrImage[k] = (T)m_dfNoDataValue;
2265 : }
2266 : }
2267 : }
2268 : }
2269 : }
2270 25 : }
2271 :
2272 : /************************************************************************/
2273 : /* FetchNetcdfChunk() */
2274 : /************************************************************************/
2275 :
2276 5937 : bool netCDFRasterBand::FetchNetcdfChunk(size_t xstart, size_t ystart,
2277 : void *pImage)
2278 : {
2279 5937 : size_t start[MAX_NC_DIMS] = {};
2280 5937 : size_t edge[MAX_NC_DIMS] = {};
2281 :
2282 5937 : start[nBandXPos] = xstart;
2283 5937 : edge[nBandXPos] = nBlockXSize;
2284 5937 : if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
2285 6 : edge[nBandXPos] = nRasterXSize - start[nBandXPos];
2286 5937 : if (nBandYPos >= 0)
2287 : {
2288 5933 : start[nBandYPos] = ystart;
2289 5933 : edge[nBandYPos] = nBlockYSize;
2290 5933 : if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
2291 4 : edge[nBandYPos] = nRasterYSize - start[nBandYPos];
2292 : }
2293 5937 : const size_t nYChunkSize = nBandYPos < 0 ? 1 : edge[nBandYPos];
2294 :
2295 : #ifdef NCDF_DEBUG
2296 : CPLDebug("GDAL_netCDF", "start={%ld,%ld} edge={%ld,%ld} bBottomUp=%d",
2297 : start[nBandXPos], nBandYPos < 0 ? 0 : start[nBandYPos],
2298 : edge[nBandXPos], nYChunkSize, ((netCDFDataset *)poDS)->bBottomUp);
2299 : #endif
2300 :
2301 5937 : int nd = 0;
2302 5937 : nc_inq_varndims(cdfid, nZId, &nd);
2303 5937 : if (nd == 3)
2304 : {
2305 1078 : start[panBandZPos[0]] = nLevel; // z
2306 1078 : edge[panBandZPos[0]] = 1;
2307 : }
2308 :
2309 : // Compute multidimention band position.
2310 : //
2311 : // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
2312 : // if Data[2,3,4,x,y]
2313 : //
2314 : // BandPos0 = (nBand) / (3*4)
2315 : // BandPos1 = (nBand - (3*4)) / (4)
2316 : // BandPos2 = (nBand - (3*4)) % (4)
2317 5937 : if (nd > 3)
2318 : {
2319 160 : int Sum = -1;
2320 160 : int Taken = 0;
2321 480 : for (int i = 0; i < nd - 2; i++)
2322 : {
2323 320 : if (i != nd - 2 - 1)
2324 : {
2325 160 : Sum = 1;
2326 320 : for (int j = i + 1; j < nd - 2; j++)
2327 : {
2328 160 : Sum *= panBandZLev[j];
2329 : }
2330 160 : start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
2331 160 : edge[panBandZPos[i]] = 1;
2332 : }
2333 : else
2334 : {
2335 160 : start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
2336 160 : edge[panBandZPos[i]] = 1;
2337 : }
2338 320 : Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
2339 : }
2340 : }
2341 :
2342 : // Make sure we are in data mode.
2343 5937 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
2344 :
2345 : // If this block is not a full block in the x axis, we need to
2346 : // re-arrange the data because partial blocks are not arranged the
2347 : // same way in netcdf and gdal, so we first we read the netcdf data at
2348 : // the end of the gdal block buffer then re-arrange rows in CheckData().
2349 5937 : void *pImageNC = pImage;
2350 5937 : if (edge[nBandXPos] != static_cast<size_t>(nBlockXSize))
2351 : {
2352 6 : pImageNC = static_cast<GByte *>(pImage) +
2353 6 : ((static_cast<size_t>(nBlockXSize) * nBlockYSize -
2354 12 : edge[nBandXPos] * nYChunkSize) *
2355 6 : GDALGetDataTypeSizeBytes(eDataType));
2356 : }
2357 :
2358 : // Read data according to type.
2359 : int status;
2360 5937 : if (eDataType == GDT_UInt8)
2361 : {
2362 3105 : if (bSignedData)
2363 : {
2364 0 : status = nc_get_vara_schar(cdfid, nZId, start, edge,
2365 : static_cast<signed char *>(pImageNC));
2366 0 : if (status == NC_NOERR)
2367 0 : CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
2368 : nYChunkSize, false);
2369 : }
2370 : else
2371 : {
2372 3105 : status = nc_get_vara_uchar(cdfid, nZId, start, edge,
2373 : static_cast<unsigned char *>(pImageNC));
2374 3105 : if (status == NC_NOERR)
2375 3105 : CheckData<unsigned char>(pImage, pImageNC, edge[nBandXPos],
2376 : nYChunkSize, false);
2377 : }
2378 : }
2379 2832 : else if (eDataType == GDT_Int8)
2380 : {
2381 60 : status = nc_get_vara_schar(cdfid, nZId, start, edge,
2382 : static_cast<signed char *>(pImageNC));
2383 60 : if (status == NC_NOERR)
2384 60 : CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
2385 : nYChunkSize, false);
2386 : }
2387 2772 : else if (nc_datatype == NC_SHORT)
2388 : {
2389 465 : status = nc_get_vara_short(cdfid, nZId, start, edge,
2390 : static_cast<short *>(pImageNC));
2391 465 : if (status == NC_NOERR)
2392 : {
2393 465 : if (eDataType == GDT_Int16)
2394 : {
2395 462 : CheckData<GInt16>(pImage, pImageNC, edge[nBandXPos],
2396 : nYChunkSize, false);
2397 : }
2398 : else
2399 : {
2400 3 : CheckData<GUInt16>(pImage, pImageNC, edge[nBandXPos],
2401 : nYChunkSize, false);
2402 : }
2403 : }
2404 : }
2405 2307 : else if (eDataType == GDT_Int32)
2406 : {
2407 : #if SIZEOF_UNSIGNED_LONG == 4
2408 : status = nc_get_vara_long(cdfid, nZId, start, edge,
2409 : static_cast<long *>(pImageNC));
2410 : if (status == NC_NOERR)
2411 : CheckData<long>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2412 : false);
2413 : #else
2414 912 : status = nc_get_vara_int(cdfid, nZId, start, edge,
2415 : static_cast<int *>(pImageNC));
2416 912 : if (status == NC_NOERR)
2417 912 : CheckData<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2418 : false);
2419 : #endif
2420 : }
2421 1395 : else if (eDataType == GDT_Float32)
2422 : {
2423 1258 : status = nc_get_vara_float(cdfid, nZId, start, edge,
2424 : static_cast<float *>(pImageNC));
2425 1258 : if (status == NC_NOERR)
2426 1258 : CheckData<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2427 : true);
2428 : }
2429 137 : else if (eDataType == GDT_Float64)
2430 : {
2431 86 : status = nc_get_vara_double(cdfid, nZId, start, edge,
2432 : static_cast<double *>(pImageNC));
2433 86 : if (status == NC_NOERR)
2434 86 : CheckData<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2435 : true);
2436 : }
2437 51 : else if (eDataType == GDT_UInt16)
2438 : {
2439 6 : status = nc_get_vara_ushort(cdfid, nZId, start, edge,
2440 : static_cast<unsigned short *>(pImageNC));
2441 6 : if (status == NC_NOERR)
2442 6 : CheckData<unsigned short>(pImage, pImageNC, edge[nBandXPos],
2443 : nYChunkSize, false);
2444 : }
2445 45 : else if (eDataType == GDT_UInt32)
2446 : {
2447 6 : status = nc_get_vara_uint(cdfid, nZId, start, edge,
2448 : static_cast<unsigned int *>(pImageNC));
2449 6 : if (status == NC_NOERR)
2450 6 : CheckData<unsigned int>(pImage, pImageNC, edge[nBandXPos],
2451 : nYChunkSize, false);
2452 : }
2453 39 : else if (eDataType == GDT_Int64)
2454 : {
2455 7 : status = nc_get_vara_longlong(cdfid, nZId, start, edge,
2456 : static_cast<long long *>(pImageNC));
2457 7 : if (status == NC_NOERR)
2458 7 : CheckData<std::int64_t>(pImage, pImageNC, edge[nBandXPos],
2459 : nYChunkSize, false);
2460 : }
2461 32 : else if (eDataType == GDT_UInt64)
2462 : {
2463 : status =
2464 7 : nc_get_vara_ulonglong(cdfid, nZId, start, edge,
2465 : static_cast<unsigned long long *>(pImageNC));
2466 7 : if (status == NC_NOERR)
2467 7 : CheckData<std::uint64_t>(pImage, pImageNC, edge[nBandXPos],
2468 : nYChunkSize, false);
2469 : }
2470 25 : else if (eDataType == GDT_CInt16)
2471 : {
2472 0 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2473 0 : if (status == NC_NOERR)
2474 0 : CheckDataCpx<short>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2475 : false);
2476 : }
2477 25 : else if (eDataType == GDT_CInt32)
2478 : {
2479 0 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2480 0 : if (status == NC_NOERR)
2481 0 : CheckDataCpx<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2482 : false);
2483 : }
2484 25 : else if (eDataType == GDT_CFloat32)
2485 : {
2486 20 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2487 20 : if (status == NC_NOERR)
2488 20 : CheckDataCpx<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2489 : false);
2490 : }
2491 5 : else if (eDataType == GDT_CFloat64)
2492 : {
2493 5 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2494 5 : if (status == NC_NOERR)
2495 5 : CheckDataCpx<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2496 : false);
2497 : }
2498 :
2499 : else
2500 0 : status = NC_EBADTYPE;
2501 :
2502 5937 : if (status != NC_NOERR)
2503 : {
2504 0 : CPLError(CE_Failure, CPLE_AppDefined,
2505 : "netCDF chunk fetch failed: #%d (%s)", status,
2506 : nc_strerror(status));
2507 0 : return false;
2508 : }
2509 5937 : return true;
2510 : }
2511 :
2512 : /************************************************************************/
2513 : /* IReadBlock() */
2514 : /************************************************************************/
2515 :
2516 5937 : CPLErr netCDFRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
2517 : void *pImage)
2518 :
2519 : {
2520 11874 : CPLMutexHolderD(&hNCMutex);
2521 :
2522 : // Locate X, Y and Z position in the array.
2523 :
2524 5937 : size_t xstart = static_cast<size_t>(nBlockXOff) * nBlockXSize;
2525 5937 : size_t ystart = 0;
2526 :
2527 : // Check y order.
2528 5937 : if (nBandYPos >= 0)
2529 : {
2530 5933 : auto poGDS = cpl::down_cast<netCDFDataset *>(poDS);
2531 5933 : if (poGDS->bBottomUp)
2532 : {
2533 5018 : if (nBlockYSize == 1)
2534 : {
2535 5005 : ystart = nRasterYSize - 1 - nBlockYOff;
2536 : }
2537 : else
2538 : {
2539 : // in GDAL space
2540 13 : ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
2541 : const size_t yend =
2542 26 : std::min(ystart + nBlockYSize - 1,
2543 13 : static_cast<size_t>(nRasterYSize - 1));
2544 : // in netCDF space
2545 13 : const size_t nFirstChunkLine = nRasterYSize - 1 - yend;
2546 13 : const size_t nLastChunkLine = nRasterYSize - 1 - ystart;
2547 13 : const size_t nFirstChunkBlock = nFirstChunkLine / nBlockYSize;
2548 13 : const size_t nLastChunkBlock = nLastChunkLine / nBlockYSize;
2549 :
2550 : const auto firstKey = netCDFDataset::ChunkKey(
2551 13 : nBlockXOff, nFirstChunkBlock, nBand);
2552 : const auto secondKey =
2553 13 : netCDFDataset::ChunkKey(nBlockXOff, nLastChunkBlock, nBand);
2554 :
2555 : // Retrieve data from the one or 2 needed netCDF chunks
2556 13 : std::shared_ptr<std::vector<GByte>> firstChunk;
2557 13 : std::shared_ptr<std::vector<GByte>> secondChunk;
2558 13 : if (poGDS->poChunkCache)
2559 : {
2560 13 : poGDS->poChunkCache->tryGet(firstKey, firstChunk);
2561 13 : if (firstKey != secondKey)
2562 6 : poGDS->poChunkCache->tryGet(secondKey, secondChunk);
2563 : }
2564 : const size_t nChunkLineSize =
2565 13 : static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
2566 13 : nBlockXSize;
2567 13 : const size_t nChunkSize = nChunkLineSize * nBlockYSize;
2568 13 : if (!firstChunk)
2569 : {
2570 11 : firstChunk.reset(new std::vector<GByte>(nChunkSize));
2571 11 : if (!FetchNetcdfChunk(xstart,
2572 11 : nFirstChunkBlock * nBlockYSize,
2573 11 : firstChunk.get()->data()))
2574 0 : return CE_Failure;
2575 11 : if (poGDS->poChunkCache)
2576 11 : poGDS->poChunkCache->insert(firstKey, firstChunk);
2577 : }
2578 13 : if (!secondChunk && firstKey != secondKey)
2579 : {
2580 2 : secondChunk.reset(new std::vector<GByte>(nChunkSize));
2581 2 : if (!FetchNetcdfChunk(xstart, nLastChunkBlock * nBlockYSize,
2582 2 : secondChunk.get()->data()))
2583 0 : return CE_Failure;
2584 2 : if (poGDS->poChunkCache)
2585 2 : poGDS->poChunkCache->insert(secondKey, secondChunk);
2586 : }
2587 :
2588 : // Assemble netCDF chunks into GDAL block
2589 13 : GByte *pabyImage = static_cast<GByte *>(pImage);
2590 13 : const size_t nFirstChunkBlockLine =
2591 13 : nFirstChunkBlock * nBlockYSize;
2592 13 : const size_t nLastChunkBlockLine =
2593 13 : nLastChunkBlock * nBlockYSize;
2594 146 : for (size_t iLine = ystart; iLine <= yend; iLine++)
2595 : {
2596 133 : const size_t nLineFromBottom = nRasterYSize - 1 - iLine;
2597 133 : const size_t nChunkY = nLineFromBottom / nBlockYSize;
2598 133 : if (nChunkY == nFirstChunkBlock)
2599 : {
2600 121 : memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
2601 121 : firstChunk.get()->data() +
2602 121 : (nLineFromBottom - nFirstChunkBlockLine) *
2603 : nChunkLineSize,
2604 : nChunkLineSize);
2605 : }
2606 : else
2607 : {
2608 12 : CPLAssert(nChunkY == nLastChunkBlock);
2609 12 : assert(secondChunk);
2610 12 : memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
2611 12 : secondChunk.get()->data() +
2612 12 : (nLineFromBottom - nLastChunkBlockLine) *
2613 : nChunkLineSize,
2614 : nChunkLineSize);
2615 : }
2616 : }
2617 13 : return CE_None;
2618 : }
2619 : }
2620 : else
2621 : {
2622 915 : ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
2623 : }
2624 : }
2625 :
2626 5924 : return FetchNetcdfChunk(xstart, ystart, pImage) ? CE_None : CE_Failure;
2627 : }
2628 :
2629 : /************************************************************************/
2630 : /* IWriteBlock() */
2631 : /************************************************************************/
2632 :
2633 6501 : CPLErr netCDFRasterBand::IWriteBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
2634 : void *pImage)
2635 : {
2636 13002 : CPLMutexHolderD(&hNCMutex);
2637 :
2638 : #ifdef NCDF_DEBUG
2639 : if (nBlockYOff == 0 || (nBlockYOff == nRasterYSize - 1))
2640 : CPLDebug("GDAL_netCDF",
2641 : "netCDFRasterBand::IWriteBlock( %d, %d, ...) nBand=%d",
2642 : nBlockXOff, nBlockYOff, nBand);
2643 : #endif
2644 :
2645 6501 : int nd = 0;
2646 6501 : nc_inq_varndims(cdfid, nZId, &nd);
2647 :
2648 : // Locate X, Y and Z position in the array.
2649 :
2650 : size_t start[MAX_NC_DIMS];
2651 6501 : memset(start, 0, sizeof(start));
2652 6501 : start[nBandXPos] = static_cast<size_t>(nBlockXOff) * nBlockXSize;
2653 :
2654 : // check y order.
2655 6501 : if (cpl::down_cast<netCDFDataset *>(poDS)->bBottomUp)
2656 : {
2657 6437 : if (nBlockYSize == 1)
2658 : {
2659 6437 : start[nBandYPos] = nRasterYSize - 1 - nBlockYOff;
2660 : }
2661 : else
2662 : {
2663 0 : CPLError(CE_Failure, CPLE_AppDefined,
2664 : "nBlockYSize = %d, only 1 supported when "
2665 : "writing bottom-up dataset",
2666 : nBlockYSize);
2667 0 : return CE_Failure;
2668 : }
2669 : }
2670 : else
2671 : {
2672 64 : start[nBandYPos] = static_cast<size_t>(nBlockYOff) * nBlockYSize; // y
2673 : }
2674 :
2675 6501 : size_t edge[MAX_NC_DIMS] = {};
2676 :
2677 6501 : edge[nBandXPos] = nBlockXSize;
2678 6501 : if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
2679 0 : edge[nBandXPos] = nRasterXSize - start[nBandXPos];
2680 6501 : edge[nBandYPos] = nBlockYSize;
2681 6501 : if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
2682 0 : edge[nBandYPos] = nRasterYSize - start[nBandYPos];
2683 :
2684 6501 : if (nd == 3)
2685 : {
2686 610 : start[panBandZPos[0]] = nLevel; // z
2687 610 : edge[panBandZPos[0]] = 1;
2688 : }
2689 :
2690 : // Compute multidimention band position.
2691 : //
2692 : // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
2693 : // if Data[2,3,4,x,y]
2694 : //
2695 : // BandPos0 = (nBand) / (3*4)
2696 : // BandPos1 = (nBand - (3*4)) / (4)
2697 : // BandPos2 = (nBand - (3*4)) % (4)
2698 6501 : if (nd > 3)
2699 : {
2700 178 : int Sum = -1;
2701 178 : int Taken = 0;
2702 534 : for (int i = 0; i < nd - 2; i++)
2703 : {
2704 356 : if (i != nd - 2 - 1)
2705 : {
2706 178 : Sum = 1;
2707 356 : for (int j = i + 1; j < nd - 2; j++)
2708 : {
2709 178 : Sum *= panBandZLev[j];
2710 : }
2711 178 : start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
2712 178 : edge[panBandZPos[i]] = 1;
2713 : }
2714 : else
2715 : {
2716 178 : start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
2717 178 : edge[panBandZPos[i]] = 1;
2718 : }
2719 356 : Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
2720 : }
2721 : }
2722 :
2723 : // Make sure we are in data mode.
2724 6501 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
2725 :
2726 : // Copy data according to type.
2727 6501 : int status = 0;
2728 6501 : if (eDataType == GDT_UInt8)
2729 : {
2730 5942 : if (bSignedData)
2731 0 : status = nc_put_vara_schar(cdfid, nZId, start, edge,
2732 : static_cast<signed char *>(pImage));
2733 : else
2734 5942 : status = nc_put_vara_uchar(cdfid, nZId, start, edge,
2735 : static_cast<unsigned char *>(pImage));
2736 : }
2737 559 : else if (eDataType == GDT_Int8)
2738 : {
2739 40 : status = nc_put_vara_schar(cdfid, nZId, start, edge,
2740 : static_cast<signed char *>(pImage));
2741 : }
2742 519 : else if (nc_datatype == NC_SHORT)
2743 : {
2744 101 : status = nc_put_vara_short(cdfid, nZId, start, edge,
2745 : static_cast<short *>(pImage));
2746 : }
2747 418 : else if (eDataType == GDT_Int32)
2748 : {
2749 210 : status = nc_put_vara_int(cdfid, nZId, start, edge,
2750 : static_cast<int *>(pImage));
2751 : }
2752 208 : else if (eDataType == GDT_Float32)
2753 : {
2754 128 : status = nc_put_vara_float(cdfid, nZId, start, edge,
2755 : static_cast<float *>(pImage));
2756 : }
2757 80 : else if (eDataType == GDT_Float64)
2758 : {
2759 50 : status = nc_put_vara_double(cdfid, nZId, start, edge,
2760 : static_cast<double *>(pImage));
2761 : }
2762 42 : else if (eDataType == GDT_UInt16 &&
2763 12 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2764 : {
2765 12 : status = nc_put_vara_ushort(cdfid, nZId, start, edge,
2766 : static_cast<unsigned short *>(pImage));
2767 : }
2768 30 : else if (eDataType == GDT_UInt32 &&
2769 12 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2770 : {
2771 12 : status = nc_put_vara_uint(cdfid, nZId, start, edge,
2772 : static_cast<unsigned int *>(pImage));
2773 : }
2774 9 : else if (eDataType == GDT_UInt64 &&
2775 3 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2776 : {
2777 : status =
2778 3 : nc_put_vara_ulonglong(cdfid, nZId, start, edge,
2779 : static_cast<unsigned long long *>(pImage));
2780 : }
2781 6 : else if (eDataType == GDT_Int64 &&
2782 3 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2783 : {
2784 3 : status = nc_put_vara_longlong(cdfid, nZId, start, edge,
2785 : static_cast<long long *>(pImage));
2786 : }
2787 : else
2788 : {
2789 0 : CPLError(CE_Failure, CPLE_NotSupported,
2790 : "The NetCDF driver does not support GDAL data type %d",
2791 0 : eDataType);
2792 0 : status = NC_EBADTYPE;
2793 : }
2794 6501 : NCDF_ERR(status);
2795 :
2796 6501 : if (status != NC_NOERR)
2797 : {
2798 0 : CPLError(CE_Failure, CPLE_AppDefined,
2799 : "netCDF scanline write failed: %s", nc_strerror(status));
2800 0 : return CE_Failure;
2801 : }
2802 :
2803 6501 : return CE_None;
2804 : }
2805 :
2806 : /************************************************************************/
2807 : /* ==================================================================== */
2808 : /* netCDFDataset */
2809 : /* ==================================================================== */
2810 : /************************************************************************/
2811 :
2812 : /************************************************************************/
2813 : /* netCDFDataset() */
2814 : /************************************************************************/
2815 :
2816 1205 : netCDFDataset::netCDFDataset()
2817 : :
2818 : // Basic dataset vars.
2819 : #ifdef ENABLE_NCDUMP
2820 : bFileToDestroyAtClosing(false),
2821 : #endif
2822 : cdfid(-1), nSubDatasets(0), papszSubDatasets(nullptr),
2823 : papszMetadata(nullptr), bBottomUp(true), eFormat(NCDF_FORMAT_NONE),
2824 : bIsGdalFile(false), bIsGdalCfFile(false), pszCFProjection(nullptr),
2825 : pszCFCoordinates(nullptr), nCFVersion(1.6), bSGSupport(false),
2826 1205 : eMultipleLayerBehavior(SINGLE_LAYER), logCount(0), vcdf(this, cdfid),
2827 1205 : GeometryScribe(vcdf, this->generateLogName()),
2828 1205 : FieldScribe(vcdf, this->generateLogName()),
2829 2410 : bufManager(CPLGetUsablePhysicalRAM() / 5),
2830 :
2831 : // projection/GT.
2832 : nXDimID(-1), nYDimID(-1), bIsProjected(false),
2833 : bIsGeographic(false), // Can be not projected, and also not geographic
2834 : // State vars.
2835 : bDefineMode(true), bAddedGridMappingRef(false),
2836 :
2837 : // Create vars.
2838 : papszCreationOptions(nullptr), eCompress(NCDF_COMPRESS_NONE),
2839 : nZLevel(NCDF_DEFLATE_LEVEL), bChunking(false), nCreateMode(NC_CLOBBER),
2840 3615 : bSignedData(true)
2841 : {
2842 1205 : m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2843 :
2844 : // Set buffers
2845 1205 : bufManager.addBuffer(&(GeometryScribe.getMemBuffer()));
2846 1205 : bufManager.addBuffer(&(FieldScribe.getMemBuffer()));
2847 1205 : }
2848 :
2849 : /************************************************************************/
2850 : /* ~netCDFDataset() */
2851 : /************************************************************************/
2852 :
2853 2312 : netCDFDataset::~netCDFDataset()
2854 :
2855 : {
2856 1205 : netCDFDataset::Close();
2857 2312 : }
2858 :
2859 : /************************************************************************/
2860 : /* Close() */
2861 : /************************************************************************/
2862 :
2863 2032 : CPLErr netCDFDataset::Close(GDALProgressFunc, void *)
2864 : {
2865 2032 : CPLErr eErr = CE_None;
2866 2032 : if (nOpenFlags != OPEN_FLAGS_CLOSED)
2867 : {
2868 2410 : CPLMutexHolderD(&hNCMutex);
2869 :
2870 : #ifdef NCDF_DEBUG
2871 : CPLDebug("GDAL_netCDF",
2872 : "netCDFDataset::~netCDFDataset(), cdfid=%d filename=%s", cdfid,
2873 : osFilename.c_str());
2874 : #endif
2875 :
2876 : // Write data related to geotransform
2877 1490 : if (GetAccess() == GA_Update && !m_bAddedProjectionVarsData &&
2878 285 : (m_bHasProjection || m_bHasGeoTransform))
2879 : {
2880 : // Ensure projection is written if GeoTransform OR Projection are
2881 : // missing.
2882 37 : if (!m_bAddedProjectionVarsDefs)
2883 : {
2884 2 : AddProjectionVars(true, nullptr, nullptr);
2885 : }
2886 37 : AddProjectionVars(false, nullptr, nullptr);
2887 : }
2888 :
2889 1205 : if (netCDFDataset::FlushCache(true) != CE_None)
2890 0 : eErr = CE_Failure;
2891 :
2892 1205 : if (GetAccess() == GA_Update && !SGCommitPendingTransaction())
2893 0 : eErr = CE_Failure;
2894 :
2895 1207 : for (size_t i = 0; i < apoVectorDatasets.size(); i++)
2896 2 : delete apoVectorDatasets[i];
2897 :
2898 : // Make sure projection variable is written to band variable.
2899 1205 : if (GetAccess() == GA_Update && !bAddedGridMappingRef)
2900 : {
2901 308 : if (!AddGridMappingRef())
2902 0 : eErr = CE_Failure;
2903 : }
2904 :
2905 1205 : CSLDestroy(papszMetadata);
2906 1205 : CSLDestroy(papszSubDatasets);
2907 1205 : CSLDestroy(papszCreationOptions);
2908 :
2909 1205 : CPLFree(pszCFProjection);
2910 :
2911 1205 : if (cdfid > 0)
2912 : {
2913 : #ifdef NCDF_DEBUG
2914 : CPLDebug("GDAL_netCDF", "calling nc_close( %d)", cdfid);
2915 : #endif
2916 685 : int status = GDAL_nc_close(cdfid);
2917 : #ifdef ENABLE_UFFD
2918 685 : NETCDF_UFFD_UNMAP(pCtx);
2919 : #endif
2920 685 : NCDF_ERR(status);
2921 685 : if (status != NC_NOERR)
2922 0 : eErr = CE_Failure;
2923 : }
2924 :
2925 1205 : if (fpVSIMEM)
2926 15 : VSIFCloseL(fpVSIMEM);
2927 :
2928 : #ifdef ENABLE_NCDUMP
2929 1205 : if (bFileToDestroyAtClosing)
2930 0 : VSIUnlink(osFilename);
2931 : #endif
2932 :
2933 1205 : if (GDALPamDataset::Close() != CE_None)
2934 0 : eErr = CE_Failure;
2935 : }
2936 2032 : return eErr;
2937 : }
2938 :
2939 : /************************************************************************/
2940 : /* SetDefineMode() */
2941 : /************************************************************************/
2942 14555 : bool netCDFDataset::SetDefineMode(bool bNewDefineMode)
2943 : {
2944 : // Do nothing if already in new define mode
2945 : // or if dataset is in read-only mode or if dataset is true NC4 dataset.
2946 15132 : if (bDefineMode == bNewDefineMode || GetAccess() == GA_ReadOnly ||
2947 577 : eFormat == NCDF_FORMAT_NC4)
2948 14125 : return true;
2949 :
2950 430 : CPLDebug("GDAL_netCDF", "SetDefineMode(%d) old=%d",
2951 430 : static_cast<int>(bNewDefineMode), static_cast<int>(bDefineMode));
2952 :
2953 430 : bDefineMode = bNewDefineMode;
2954 :
2955 : int status;
2956 430 : if (bDefineMode)
2957 149 : status = nc_redef(cdfid);
2958 : else
2959 281 : status = nc_enddef(cdfid);
2960 :
2961 430 : NCDF_ERR(status);
2962 430 : return status == NC_NOERR;
2963 : }
2964 :
2965 : /************************************************************************/
2966 : /* GetMetadataDomainList() */
2967 : /************************************************************************/
2968 :
2969 27 : char **netCDFDataset::GetMetadataDomainList()
2970 : {
2971 27 : char **papszDomains = BuildMetadataDomainList(
2972 : GDALDataset::GetMetadataDomainList(), TRUE, "SUBDATASETS", nullptr);
2973 28 : for (const auto &kv : m_oMapDomainToJSon)
2974 1 : papszDomains = CSLAddString(papszDomains, ("json:" + kv.first).c_str());
2975 27 : return papszDomains;
2976 : }
2977 :
2978 : /************************************************************************/
2979 : /* GetMetadata() */
2980 : /************************************************************************/
2981 403 : CSLConstList netCDFDataset::GetMetadata(const char *pszDomain)
2982 : {
2983 403 : if (pszDomain != nullptr && STARTS_WITH_CI(pszDomain, "SUBDATASETS"))
2984 39 : return papszSubDatasets;
2985 :
2986 364 : if (pszDomain != nullptr && STARTS_WITH(pszDomain, "json:"))
2987 : {
2988 1 : auto iter = m_oMapDomainToJSon.find(pszDomain + strlen("json:"));
2989 1 : if (iter != m_oMapDomainToJSon.end())
2990 1 : return iter->second.List();
2991 : }
2992 :
2993 363 : return GDALDataset::GetMetadata(pszDomain);
2994 : }
2995 :
2996 : /************************************************************************/
2997 : /* SetMetadataItem() */
2998 : /************************************************************************/
2999 :
3000 43 : CPLErr netCDFDataset::SetMetadataItem(const char *pszName, const char *pszValue,
3001 : const char *pszDomain)
3002 : {
3003 85 : if (GetAccess() == GA_Update &&
3004 85 : (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)
3005 : {
3006 42 : std::string osName(pszName);
3007 :
3008 : // Same logic as in CopyMetadata()
3009 42 : if (STARTS_WITH(osName.c_str(), "NC_GLOBAL#"))
3010 8 : osName = osName.substr(strlen("NC_GLOBAL#"));
3011 34 : else if (strchr(osName.c_str(), '#') == nullptr)
3012 5 : osName = "GDAL_" + osName;
3013 :
3014 84 : if (STARTS_WITH(osName.c_str(), "NETCDF_DIM_") ||
3015 42 : strchr(osName.c_str(), '#') != nullptr)
3016 : {
3017 : // do nothing
3018 29 : return CE_None;
3019 : }
3020 : else
3021 : {
3022 13 : SetDefineMode(true);
3023 :
3024 13 : if (!NCDFPutAttr(cdfid, NC_GLOBAL, osName.c_str(), pszValue))
3025 13 : return CE_Failure;
3026 : }
3027 : }
3028 :
3029 1 : return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);
3030 : }
3031 :
3032 : /************************************************************************/
3033 : /* SetMetadata() */
3034 : /************************************************************************/
3035 :
3036 8 : CPLErr netCDFDataset::SetMetadata(CSLConstList papszMD, const char *pszDomain)
3037 : {
3038 13 : if (GetAccess() == GA_Update &&
3039 5 : (pszDomain == nullptr || pszDomain[0] == '\0'))
3040 : {
3041 : // We don't handle metadata item removal for now
3042 50 : for (const char *const *papszIter = papszMD; papszIter && *papszIter;
3043 : ++papszIter)
3044 : {
3045 42 : char *pszName = nullptr;
3046 42 : const char *pszValue = CPLParseNameValue(*papszIter, &pszName);
3047 42 : if (pszName && pszValue)
3048 42 : SetMetadataItem(pszName, pszValue);
3049 42 : CPLFree(pszName);
3050 : }
3051 8 : return CE_None;
3052 : }
3053 0 : return GDALPamDataset::SetMetadata(papszMD, pszDomain);
3054 : }
3055 :
3056 : /************************************************************************/
3057 : /* GetSpatialRef() */
3058 : /************************************************************************/
3059 :
3060 230 : const OGRSpatialReference *netCDFDataset::GetSpatialRef() const
3061 : {
3062 230 : if (m_bHasProjection)
3063 102 : return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
3064 :
3065 128 : return GDALPamDataset::GetSpatialRef();
3066 : }
3067 :
3068 : /************************************************************************/
3069 : /* FetchCopyParam() */
3070 : /************************************************************************/
3071 :
3072 444 : double netCDFDataset::FetchCopyParam(const char *pszGridMappingValue,
3073 : const char *pszParam, double dfDefault,
3074 : bool *pbFound) const
3075 :
3076 : {
3077 : char *pszTemp =
3078 444 : CPLStrdup(CPLSPrintf("%s#%s", pszGridMappingValue, pszParam));
3079 444 : const char *pszValue = CSLFetchNameValue(papszMetadata, pszTemp);
3080 444 : CPLFree(pszTemp);
3081 :
3082 444 : if (pbFound)
3083 : {
3084 444 : *pbFound = (pszValue != nullptr);
3085 : }
3086 :
3087 444 : if (pszValue)
3088 : {
3089 0 : return CPLAtofM(pszValue);
3090 : }
3091 :
3092 444 : return dfDefault;
3093 : }
3094 :
3095 : /************************************************************************/
3096 : /* FetchStandardParallels() */
3097 : /************************************************************************/
3098 :
3099 : std::vector<std::string>
3100 0 : netCDFDataset::FetchStandardParallels(const char *pszGridMappingValue) const
3101 : {
3102 : // cf-1.0 tags
3103 0 : const char *pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL);
3104 :
3105 0 : std::vector<std::string> ret;
3106 0 : if (pszValue != nullptr)
3107 : {
3108 0 : CPLStringList aosValues;
3109 0 : if (pszValue[0] != '{' &&
3110 0 : CPLString(pszValue).Trim().find(' ') != std::string::npos)
3111 : {
3112 : // Some files like
3113 : // ftp://data.knmi.nl/download/KNW-NetCDF-3D/1.0/noversion/2013/11/14/KNW-1.0_H37-ERA_NL_20131114.nc
3114 : // do not use standard formatting for arrays, but just space
3115 : // separated syntax
3116 0 : aosValues = CSLTokenizeString2(pszValue, " ", 0);
3117 : }
3118 : else
3119 : {
3120 0 : aosValues = NCDFTokenizeArray(pszValue);
3121 : }
3122 0 : for (int i = 0; i < aosValues.size(); i++)
3123 : {
3124 0 : ret.push_back(aosValues[i]);
3125 : }
3126 : }
3127 : // Try gdal tags.
3128 : else
3129 : {
3130 0 : pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_1);
3131 :
3132 0 : if (pszValue != nullptr)
3133 0 : ret.push_back(pszValue);
3134 :
3135 0 : pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_2);
3136 :
3137 0 : if (pszValue != nullptr)
3138 0 : ret.push_back(pszValue);
3139 : }
3140 :
3141 0 : return ret;
3142 : }
3143 :
3144 : /************************************************************************/
3145 : /* FetchAttr() */
3146 : /************************************************************************/
3147 :
3148 3958 : const char *netCDFDataset::FetchAttr(const char *pszVarFullName,
3149 : const char *pszAttr) const
3150 :
3151 : {
3152 3958 : char *pszKey = CPLStrdup(CPLSPrintf("%s#%s", pszVarFullName, pszAttr));
3153 3958 : const char *pszValue = CSLFetchNameValue(papszMetadata, pszKey);
3154 3958 : CPLFree(pszKey);
3155 3958 : return pszValue;
3156 : }
3157 :
3158 2600 : const char *netCDFDataset::FetchAttr(int nGroupId, int nVarId,
3159 : const char *pszAttr) const
3160 :
3161 : {
3162 2600 : char *pszVarFullName = nullptr;
3163 2600 : NCDFGetVarFullName(nGroupId, nVarId, &pszVarFullName);
3164 2600 : const char *pszValue = FetchAttr(pszVarFullName, pszAttr);
3165 2600 : CPLFree(pszVarFullName);
3166 2600 : return pszValue;
3167 : }
3168 :
3169 : /************************************************************************/
3170 : /* IsDifferenceBelow() */
3171 : /************************************************************************/
3172 :
3173 1115 : static bool IsDifferenceBelow(double dfA, double dfB, double dfError)
3174 : {
3175 1115 : const double dfAbsDiff = fabs(dfA - dfB);
3176 1115 : return dfAbsDiff <= dfError;
3177 : }
3178 :
3179 : /************************************************************************/
3180 : /* SetProjectionFromVar() */
3181 : /************************************************************************/
3182 558 : void netCDFDataset::SetProjectionFromVar(
3183 : int nGroupId, int nVarId, bool bReadSRSOnly, const char *pszGivenGM,
3184 : std::string *returnProjStr, nccfdriver::SGeometry_Reader *sg,
3185 : std::vector<std::string> *paosRemovedMDItems)
3186 : {
3187 558 : bool bGotGeogCS = false;
3188 558 : bool bGotCfSRS = false;
3189 558 : bool bGotCfWktSRS = false;
3190 558 : bool bGotGdalSRS = false;
3191 558 : bool bGotCfGT = false;
3192 558 : bool bGotGdalGT = false;
3193 :
3194 : // These values from CF metadata.
3195 558 : OGRSpatialReference oSRS;
3196 558 : oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3197 558 : size_t xdim = nRasterXSize;
3198 558 : size_t ydim = nRasterYSize;
3199 :
3200 : // These values from GDAL metadata.
3201 558 : const char *pszWKT = nullptr;
3202 558 : const char *pszGeoTransform = nullptr;
3203 :
3204 558 : netCDFDataset *poDS = this; // Perhaps this should be removed for clarity.
3205 :
3206 558 : CPLDebug("GDAL_netCDF", "\n=====\nSetProjectionFromVar( %d, %d)", nGroupId,
3207 : nVarId);
3208 :
3209 : // Get x/y range information.
3210 :
3211 : // Temp variables to use in SetGeoTransform() and SetProjection().
3212 558 : GDALGeoTransform tmpGT;
3213 :
3214 : // Look for grid_mapping metadata.
3215 558 : const char *pszValue = pszGivenGM;
3216 558 : CPLString osTmpGridMapping; // let is in this outer scope as pszValue may
3217 : // point to it
3218 558 : if (pszValue == nullptr)
3219 : {
3220 515 : pszValue = FetchAttr(nGroupId, nVarId, CF_GRD_MAPPING);
3221 515 : if (pszValue && strchr(pszValue, ':') && strchr(pszValue, ' '))
3222 : {
3223 : // Expanded form of grid_mapping
3224 : // e.g. "crsOSGB: x y crsWGS84: lat lon"
3225 : // Pickup the grid_mapping whose coordinates are dimensions of the
3226 : // variable
3227 6 : const CPLStringList aosTokens(CSLTokenizeString2(pszValue, " ", 0));
3228 3 : if ((aosTokens.size() % 3) == 0)
3229 : {
3230 3 : for (int i = 0; i < aosTokens.size() / 3; i++)
3231 : {
3232 3 : if (CSLFindString(poDS->papszDimName,
3233 9 : aosTokens[3 * i + 1]) >= 0 &&
3234 3 : CSLFindString(poDS->papszDimName,
3235 3 : aosTokens[3 * i + 2]) >= 0)
3236 : {
3237 3 : osTmpGridMapping = aosTokens[3 * i];
3238 6 : if (!osTmpGridMapping.empty() &&
3239 3 : osTmpGridMapping.back() == ':')
3240 : {
3241 3 : osTmpGridMapping.resize(osTmpGridMapping.size() -
3242 : 1);
3243 : }
3244 3 : pszValue = osTmpGridMapping.c_str();
3245 3 : break;
3246 : }
3247 : }
3248 : }
3249 : }
3250 : }
3251 558 : char *pszGridMappingValue = CPLStrdup(pszValue ? pszValue : "");
3252 :
3253 558 : if (!EQUAL(pszGridMappingValue, ""))
3254 : {
3255 : // Read grid_mapping metadata.
3256 239 : int nProjGroupID = -1;
3257 239 : int nProjVarID = -1;
3258 239 : if (NCDFResolveVar(nGroupId, pszGridMappingValue, &nProjGroupID,
3259 239 : &nProjVarID) == CE_None)
3260 : {
3261 238 : poDS->ReadAttributes(nProjGroupID, nProjVarID);
3262 :
3263 : // Look for GDAL spatial_ref and GeoTransform within grid_mapping.
3264 238 : CPLFree(pszGridMappingValue);
3265 238 : pszGridMappingValue = nullptr;
3266 238 : NCDFGetVarFullName(nProjGroupID, nProjVarID, &pszGridMappingValue);
3267 238 : if (pszGridMappingValue)
3268 : {
3269 238 : CPLDebug("GDAL_netCDF", "got grid_mapping %s",
3270 : pszGridMappingValue);
3271 238 : pszWKT = FetchAttr(pszGridMappingValue, NCDF_SPATIAL_REF);
3272 238 : if (!pszWKT)
3273 : {
3274 35 : pszWKT = FetchAttr(pszGridMappingValue, NCDF_CRS_WKT);
3275 : }
3276 : else
3277 : {
3278 203 : bGotGdalSRS = true;
3279 203 : CPLDebug("GDAL_netCDF", "setting WKT from GDAL");
3280 : }
3281 238 : if (pszWKT)
3282 : {
3283 208 : if (!bGotGdalSRS)
3284 : {
3285 5 : bGotCfWktSRS = true;
3286 5 : CPLDebug("GDAL_netCDF", "setting WKT from CF");
3287 : }
3288 208 : if (returnProjStr != nullptr)
3289 : {
3290 41 : (*returnProjStr) = std::string(pszWKT);
3291 : }
3292 : else
3293 : {
3294 167 : m_bAddedProjectionVarsDefs = true;
3295 167 : m_bAddedProjectionVarsData = true;
3296 334 : OGRSpatialReference oSRSTmp;
3297 167 : oSRSTmp.SetAxisMappingStrategy(
3298 : OAMS_TRADITIONAL_GIS_ORDER);
3299 167 : oSRSTmp.importFromWkt(pszWKT);
3300 167 : SetSpatialRefNoUpdate(&oSRSTmp);
3301 : }
3302 : pszGeoTransform =
3303 208 : FetchAttr(pszGridMappingValue, NCDF_GEOTRANSFORM);
3304 : }
3305 : }
3306 : else
3307 : {
3308 0 : pszGridMappingValue = CPLStrdup("");
3309 : }
3310 : }
3311 : }
3312 :
3313 : // Get information about the file.
3314 : //
3315 : // Was this file created by the GDAL netcdf driver?
3316 : // Was this file created by the newer (CF-conformant) driver?
3317 : //
3318 : // 1) If GDAL netcdf metadata is set, and version >= 1.9,
3319 : // it was created with the new driver
3320 : // 2) Else, if spatial_ref and GeoTransform are present in the
3321 : // grid_mapping variable, it was created by the old driver
3322 558 : pszValue = FetchAttr("NC_GLOBAL", "GDAL");
3323 :
3324 558 : if (pszValue && NCDFIsGDALVersionGTE(pszValue, 1900))
3325 : {
3326 257 : bIsGdalFile = true;
3327 257 : bIsGdalCfFile = true;
3328 : }
3329 301 : else if (pszWKT != nullptr && pszGeoTransform != nullptr)
3330 : {
3331 24 : bIsGdalFile = true;
3332 24 : bIsGdalCfFile = false;
3333 : }
3334 :
3335 : // Set default bottom-up default value.
3336 : // Y axis dimension and absence of GT can modify this value.
3337 : // Override with Config option GDAL_NETCDF_BOTTOMUP.
3338 :
3339 : // New driver is bottom-up by default.
3340 558 : if ((bIsGdalFile && !bIsGdalCfFile) || bSwitchedXY)
3341 26 : poDS->bBottomUp = false;
3342 : else
3343 532 : poDS->bBottomUp = true;
3344 :
3345 558 : CPLDebug("GDAL_netCDF",
3346 : "bIsGdalFile=%d bIsGdalCfFile=%d bSwitchedXY=%d bBottomUp=%d",
3347 558 : static_cast<int>(bIsGdalFile), static_cast<int>(bIsGdalCfFile),
3348 558 : static_cast<int>(bSwitchedXY), static_cast<int>(bBottomUp));
3349 :
3350 : // Read projection coordinates.
3351 :
3352 558 : int nGroupDimXID = -1;
3353 558 : int nVarDimXID = -1;
3354 558 : int nGroupDimYID = -1;
3355 558 : int nVarDimYID = -1;
3356 558 : if (sg != nullptr)
3357 : {
3358 43 : nGroupDimXID = sg->get_ncID();
3359 43 : nGroupDimYID = sg->get_ncID();
3360 43 : nVarDimXID = sg->getNodeCoordVars()[0];
3361 43 : nVarDimYID = sg->getNodeCoordVars()[1];
3362 : }
3363 :
3364 558 : if (!bReadSRSOnly)
3365 : {
3366 368 : NCDFResolveVar(nGroupId, poDS->papszDimName[nXDimID], &nGroupDimXID,
3367 : &nVarDimXID);
3368 368 : NCDFResolveVar(nGroupId, poDS->papszDimName[nYDimID], &nGroupDimYID,
3369 : &nVarDimYID);
3370 : // TODO: if above resolving fails we should also search for coordinate
3371 : // variables without same name than dimension using the same resolving
3372 : // logic. This should handle for example NASA Ocean Color L2 products.
3373 :
3374 : const bool bIgnoreXYAxisNameChecks =
3375 736 : CPLTestBool(CSLFetchNameValueDef(
3376 368 : papszOpenOptions, "IGNORE_XY_AXIS_NAME_CHECKS",
3377 : CPLGetConfigOption("GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS",
3378 368 : "NO"))) ||
3379 : // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a res
3380 : // and transform attributes
3381 368 : (FetchAttr(nGroupId, nVarId, "res") != nullptr &&
3382 736 : FetchAttr(nGroupId, nVarId, "transform") != nullptr) ||
3383 367 : FetchAttr(nGroupId, NC_GLOBAL, "GMT_version") != nullptr;
3384 :
3385 : // Check that they are 1D or 2D variables
3386 368 : if (nVarDimXID >= 0)
3387 : {
3388 260 : int ndims = -1;
3389 260 : nc_inq_varndims(nGroupId, nVarDimXID, &ndims);
3390 260 : if (ndims == 0 || ndims > 2)
3391 0 : nVarDimXID = -1;
3392 260 : else if (!bIgnoreXYAxisNameChecks)
3393 : {
3394 258 : if (!NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
3395 168 : !NCDFIsVarProjectionX(nGroupId, nVarDimXID, nullptr) &&
3396 : // In case of inversion of X/Y
3397 458 : !NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr) &&
3398 32 : !NCDFIsVarProjectionY(nGroupId, nVarDimXID, nullptr))
3399 : {
3400 : char szVarNameX[NC_MAX_NAME + 1];
3401 32 : CPL_IGNORE_RET_VAL(
3402 32 : nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
3403 32 : if (!(ndims == 1 &&
3404 31 : (EQUAL(szVarNameX, CF_LONGITUDE_STD_NAME) ||
3405 30 : EQUAL(szVarNameX, CF_LONGITUDE_VAR_NAME))))
3406 : {
3407 31 : CPLDebug(
3408 : "netCDF",
3409 : "Georeferencing ignored due to non-specific "
3410 : "enough X axis name. "
3411 : "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
3412 : "as configuration option to bypass this check");
3413 31 : nVarDimXID = -1;
3414 : }
3415 : }
3416 : }
3417 : }
3418 :
3419 368 : if (nVarDimYID >= 0)
3420 : {
3421 262 : int ndims = -1;
3422 262 : nc_inq_varndims(nGroupId, nVarDimYID, &ndims);
3423 262 : if (ndims == 0 || ndims > 2)
3424 1 : nVarDimYID = -1;
3425 261 : else if (!bIgnoreXYAxisNameChecks)
3426 : {
3427 259 : if (!NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr) &&
3428 169 : !NCDFIsVarProjectionY(nGroupId, nVarDimYID, nullptr) &&
3429 : // In case of inversion of X/Y
3430 461 : !NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
3431 33 : !NCDFIsVarProjectionX(nGroupId, nVarDimYID, nullptr))
3432 : {
3433 : char szVarNameY[NC_MAX_NAME + 1];
3434 33 : CPL_IGNORE_RET_VAL(
3435 33 : nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
3436 33 : if (!(ndims == 1 &&
3437 33 : (EQUAL(szVarNameY, CF_LATITUDE_STD_NAME) ||
3438 32 : EQUAL(szVarNameY, CF_LATITUDE_VAR_NAME))))
3439 : {
3440 32 : CPLDebug(
3441 : "netCDF",
3442 : "Georeferencing ignored due to non-specific "
3443 : "enough Y axis name. "
3444 : "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
3445 : "as configuration option to bypass this check");
3446 32 : nVarDimYID = -1;
3447 : }
3448 : }
3449 : }
3450 : }
3451 :
3452 368 : if ((nVarDimXID >= 0 && xdim == 1) || (nVarDimXID >= 0 && ydim == 1))
3453 : {
3454 0 : CPLError(CE_Warning, CPLE_AppDefined,
3455 : "1-pixel width/height files not supported, "
3456 : "xdim: %ld ydim: %ld",
3457 : static_cast<long>(xdim), static_cast<long>(ydim));
3458 0 : nVarDimXID = -1;
3459 0 : nVarDimYID = -1;
3460 : }
3461 : }
3462 :
3463 558 : const char *pszUnits = nullptr;
3464 558 : if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
3465 : {
3466 272 : const char *pszUnitsX = FetchAttr(nGroupDimXID, nVarDimXID, "units");
3467 272 : const char *pszUnitsY = FetchAttr(nGroupDimYID, nVarDimYID, "units");
3468 : // Normalize degrees_east/degrees_north to degrees
3469 : // Cf https://github.com/OSGeo/gdal/issues/11009
3470 272 : if (pszUnitsX && EQUAL(pszUnitsX, "degrees_east"))
3471 79 : pszUnitsX = "degrees";
3472 272 : if (pszUnitsY && EQUAL(pszUnitsY, "degrees_north"))
3473 79 : pszUnitsY = "degrees";
3474 :
3475 272 : if (pszUnitsX && pszUnitsY)
3476 : {
3477 225 : if (EQUAL(pszUnitsX, pszUnitsY))
3478 222 : pszUnits = pszUnitsX;
3479 3 : else if (!pszWKT && !EQUAL(pszGridMappingValue, ""))
3480 : {
3481 0 : CPLError(CE_Failure, CPLE_AppDefined,
3482 : "X axis unit (%s) is different from Y axis "
3483 : "unit (%s). SRS will ignore axis unit and be "
3484 : "likely wrong.",
3485 : pszUnitsX, pszUnitsY);
3486 : }
3487 : }
3488 47 : else if (pszUnitsX && !pszWKT && !EQUAL(pszGridMappingValue, ""))
3489 : {
3490 0 : CPLError(CE_Failure, CPLE_AppDefined,
3491 : "X axis unit is defined, but not Y one ."
3492 : "SRS will ignore axis unit and be likely wrong.");
3493 : }
3494 47 : else if (pszUnitsY && !pszWKT && !EQUAL(pszGridMappingValue, ""))
3495 : {
3496 0 : CPLError(CE_Failure, CPLE_AppDefined,
3497 : "Y axis unit is defined, but not X one ."
3498 : "SRS will ignore axis unit and be likely wrong.");
3499 : }
3500 : }
3501 :
3502 558 : if (!pszWKT && !EQUAL(pszGridMappingValue, ""))
3503 : {
3504 31 : CPLStringList aosGridMappingKeyValues;
3505 31 : const size_t nLenGridMappingValue = strlen(pszGridMappingValue);
3506 789 : for (const char *const *papszIter = papszMetadata;
3507 789 : papszIter && *papszIter; ++papszIter)
3508 : {
3509 758 : if (STARTS_WITH(*papszIter, pszGridMappingValue) &&
3510 236 : (*papszIter)[nLenGridMappingValue] == '#')
3511 : {
3512 236 : char *pszKey = nullptr;
3513 472 : pszValue = CPLParseNameValue(
3514 236 : *papszIter + nLenGridMappingValue + 1, &pszKey);
3515 236 : if (pszKey && pszValue)
3516 236 : aosGridMappingKeyValues.SetNameValue(pszKey, pszValue);
3517 236 : CPLFree(pszKey);
3518 : }
3519 : }
3520 :
3521 31 : bGotGeogCS = aosGridMappingKeyValues.FetchNameValue(
3522 : CF_PP_SEMI_MAJOR_AXIS) != nullptr;
3523 :
3524 31 : oSRS.importFromCF1(aosGridMappingKeyValues.List(), pszUnits);
3525 31 : bGotCfSRS = oSRS.IsGeographic() || oSRS.IsProjected();
3526 : }
3527 : else
3528 : {
3529 : // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a "crs"
3530 : // attribute hold on the variable of interest that contains a PROJ.4
3531 : // string
3532 527 : pszValue = FetchAttr(nGroupId, nVarId, "crs");
3533 528 : if (pszValue &&
3534 1 : (strstr(pszValue, "+proj=") != nullptr ||
3535 0 : strstr(pszValue, "GEOGCS") != nullptr ||
3536 0 : strstr(pszValue, "PROJCS") != nullptr ||
3537 528 : strstr(pszValue, "EPSG:") != nullptr) &&
3538 1 : oSRS.SetFromUserInput(pszValue) == OGRERR_NONE)
3539 : {
3540 1 : bGotCfSRS = true;
3541 : }
3542 : }
3543 :
3544 : // Set Projection from CF.
3545 558 : double dfLinearUnitsConvFactor = 1.0;
3546 558 : if ((bGotGeogCS || bGotCfSRS))
3547 : {
3548 31 : if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
3549 : {
3550 : // Set SRS Units.
3551 :
3552 : // Check units for x and y.
3553 28 : if (oSRS.IsProjected())
3554 : {
3555 25 : dfLinearUnitsConvFactor = oSRS.GetLinearUnits(nullptr);
3556 :
3557 : // If the user doesn't ask to preserve the axis unit,
3558 : // then normalize to metre
3559 31 : if (dfLinearUnitsConvFactor != 1.0 &&
3560 6 : !CPLFetchBool(GetOpenOptions(), "PRESERVE_AXIS_UNIT_IN_CRS",
3561 : false))
3562 : {
3563 5 : oSRS.SetLinearUnits("metre", 1.0);
3564 5 : oSRS.SetAuthority("PROJCS|UNIT", "EPSG", 9001);
3565 : }
3566 : else
3567 : {
3568 20 : dfLinearUnitsConvFactor = 1.0;
3569 : }
3570 : }
3571 : }
3572 :
3573 : // Set projection.
3574 31 : char *pszTempProjection = nullptr;
3575 31 : oSRS.exportToWkt(&pszTempProjection);
3576 31 : if (pszTempProjection)
3577 : {
3578 31 : CPLDebug("GDAL_netCDF", "setting WKT from CF");
3579 31 : if (returnProjStr != nullptr)
3580 : {
3581 2 : (*returnProjStr) = std::string(pszTempProjection);
3582 : }
3583 : else
3584 : {
3585 29 : m_bAddedProjectionVarsDefs = true;
3586 29 : m_bAddedProjectionVarsData = true;
3587 29 : SetSpatialRefNoUpdate(&oSRS);
3588 : }
3589 : }
3590 31 : CPLFree(pszTempProjection);
3591 : }
3592 :
3593 558 : if (!bReadSRSOnly && (nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 &&
3594 : ydim > 0)
3595 : {
3596 : double *pdfXCoord =
3597 229 : static_cast<double *>(CPLCalloc(xdim, sizeof(double)));
3598 : double *pdfYCoord =
3599 229 : static_cast<double *>(CPLCalloc(ydim, sizeof(double)));
3600 :
3601 229 : size_t start[2] = {0, 0};
3602 229 : size_t edge[2] = {xdim, 0};
3603 229 : int status = nc_get_vara_double(nGroupDimXID, nVarDimXID, start, edge,
3604 : pdfXCoord);
3605 229 : NCDF_ERR(status);
3606 :
3607 229 : edge[0] = ydim;
3608 229 : status = nc_get_vara_double(nGroupDimYID, nVarDimYID, start, edge,
3609 : pdfYCoord);
3610 229 : NCDF_ERR(status);
3611 :
3612 229 : nc_type nc_var_dimx_datatype = NC_NAT;
3613 : status =
3614 229 : nc_inq_vartype(nGroupDimXID, nVarDimXID, &nc_var_dimx_datatype);
3615 229 : NCDF_ERR(status);
3616 :
3617 229 : nc_type nc_var_dimy_datatype = NC_NAT;
3618 : status =
3619 229 : nc_inq_vartype(nGroupDimYID, nVarDimYID, &nc_var_dimy_datatype);
3620 229 : NCDF_ERR(status);
3621 :
3622 229 : if (!poDS->bSwitchedXY)
3623 : {
3624 : // Convert ]180,540] longitude values to ]-180,0].
3625 317 : if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
3626 90 : CPLTestBool(
3627 : CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")))
3628 : {
3629 : // If minimum longitude is > 180, subtract 360 from all.
3630 : // Add a check on the maximum X value too, since
3631 : // NCDFIsVarLongitude() is not very specific by default (see
3632 : // https://github.com/OSGeo/gdal/issues/1440)
3633 97 : if (std::min(pdfXCoord[0], pdfXCoord[xdim - 1]) > 180.0 &&
3634 7 : std::max(pdfXCoord[0], pdfXCoord[xdim - 1]) <= 540)
3635 : {
3636 0 : CPLDebug(
3637 : "GDAL_netCDF",
3638 : "Offsetting longitudes from ]180,540] to ]-180,180]. "
3639 : "Can be disabled with GDAL_NETCDF_CENTERLONG_180=NO");
3640 0 : for (size_t i = 0; i < xdim; i++)
3641 0 : pdfXCoord[i] -= 360;
3642 : }
3643 : }
3644 : }
3645 :
3646 : // Is pixel spacing uniform across the map?
3647 :
3648 : // Check Longitude.
3649 :
3650 229 : bool bLonSpacingOK = false;
3651 229 : if (xdim == 2)
3652 : {
3653 29 : bLonSpacingOK = true;
3654 : }
3655 : else
3656 : {
3657 200 : bool bWestIsLeft = (pdfXCoord[0] < pdfXCoord[xdim - 1]);
3658 :
3659 : // fix longitudes if longitudes should increase from
3660 : // west to east, but west > east
3661 280 : if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
3662 80 : !bWestIsLeft)
3663 : {
3664 2 : size_t ndecreases = 0;
3665 :
3666 : // there is lon wrap if longitudes increase
3667 : // with one single decrease
3668 107 : for (size_t i = 1; i < xdim; i++)
3669 : {
3670 105 : if (pdfXCoord[i] < pdfXCoord[i - 1])
3671 1 : ndecreases++;
3672 : }
3673 :
3674 2 : if (ndecreases == 1)
3675 : {
3676 1 : CPLDebug("GDAL_netCDF", "longitude wrap detected");
3677 4 : for (size_t i = 0; i < xdim; i++)
3678 : {
3679 3 : if (pdfXCoord[i] > pdfXCoord[xdim - 1])
3680 1 : pdfXCoord[i] -= 360;
3681 : }
3682 : }
3683 : }
3684 :
3685 200 : const double dfSpacingBegin = pdfXCoord[1] - pdfXCoord[0];
3686 200 : const double dfSpacingMiddle =
3687 200 : pdfXCoord[xdim / 2 + 1] - pdfXCoord[xdim / 2];
3688 200 : const double dfSpacingLast =
3689 200 : pdfXCoord[xdim - 1] - pdfXCoord[xdim - 2];
3690 :
3691 200 : CPLDebug("GDAL_netCDF",
3692 : "xdim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
3693 : "dfSpacingLast: %f",
3694 : static_cast<long>(xdim), dfSpacingBegin, dfSpacingMiddle,
3695 : dfSpacingLast);
3696 : #ifdef NCDF_DEBUG
3697 : CPLDebug("GDAL_netCDF", "xcoords: %f %f %f %f %f %f", pdfXCoord[0],
3698 : pdfXCoord[1], pdfXCoord[xdim / 2],
3699 : pdfXCoord[(xdim / 2) + 1], pdfXCoord[xdim - 2],
3700 : pdfXCoord[xdim - 1]);
3701 : #endif
3702 :
3703 : // ftp://ftp.cdc.noaa.gov/Datasets/NARR/Dailies/monolevel/vwnd.10m.2015.nc
3704 : // requires a 0.02% tolerance, so let's settle for 0.05%
3705 :
3706 : // For float variables, increase to 0.2% (as seen in
3707 : // https://github.com/OSGeo/gdal/issues/3663)
3708 200 : const double dfEpsRel =
3709 200 : nc_var_dimx_datatype == NC_FLOAT ? 0.002 : 0.0005;
3710 :
3711 : const double dfEps =
3712 : dfEpsRel *
3713 400 : std::max(fabs(dfSpacingBegin),
3714 200 : std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
3715 394 : if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
3716 394 : IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
3717 194 : IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
3718 : {
3719 194 : bLonSpacingOK = true;
3720 : }
3721 6 : else if (CPLTestBool(CPLGetConfigOption(
3722 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
3723 : {
3724 0 : bLonSpacingOK = true;
3725 0 : CPLDebug(
3726 : "GDAL_netCDF",
3727 : "Longitude/X is not equally spaced, but will be considered "
3728 : "as such because of "
3729 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
3730 : }
3731 : }
3732 :
3733 229 : if (bLonSpacingOK == false)
3734 : {
3735 6 : CPLDebug(
3736 : "GDAL_netCDF", "%s",
3737 : "Longitude/X is not equally spaced (with a 0.05% tolerance). "
3738 : "You may set the "
3739 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
3740 : "option to YES to ignore this check");
3741 : }
3742 :
3743 : // Check Latitude.
3744 229 : bool bLatSpacingOK = false;
3745 :
3746 229 : if (ydim == 2)
3747 : {
3748 49 : bLatSpacingOK = true;
3749 : }
3750 : else
3751 : {
3752 180 : const double dfSpacingBegin = pdfYCoord[1] - pdfYCoord[0];
3753 180 : const double dfSpacingMiddle =
3754 180 : pdfYCoord[ydim / 2 + 1] - pdfYCoord[ydim / 2];
3755 :
3756 180 : const double dfSpacingLast =
3757 180 : pdfYCoord[ydim - 1] - pdfYCoord[ydim - 2];
3758 :
3759 180 : CPLDebug("GDAL_netCDF",
3760 : "ydim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
3761 : "dfSpacingLast: %f",
3762 : (long)ydim, dfSpacingBegin, dfSpacingMiddle,
3763 : dfSpacingLast);
3764 : #ifdef NCDF_DEBUG
3765 : CPLDebug("GDAL_netCDF", "ycoords: %f %f %f %f %f %f", pdfYCoord[0],
3766 : pdfYCoord[1], pdfYCoord[ydim / 2],
3767 : pdfYCoord[(ydim / 2) + 1], pdfYCoord[ydim - 2],
3768 : pdfYCoord[ydim - 1]);
3769 : #endif
3770 :
3771 180 : const double dfEpsRel =
3772 180 : nc_var_dimy_datatype == NC_FLOAT ? 0.002 : 0.0005;
3773 :
3774 : const double dfEps =
3775 : dfEpsRel *
3776 360 : std::max(fabs(dfSpacingBegin),
3777 180 : std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
3778 358 : if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
3779 358 : IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
3780 169 : IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
3781 : {
3782 169 : bLatSpacingOK = true;
3783 : }
3784 11 : else if (CPLTestBool(CPLGetConfigOption(
3785 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
3786 : {
3787 0 : bLatSpacingOK = true;
3788 0 : CPLDebug(
3789 : "GDAL_netCDF",
3790 : "Latitude/Y is not equally spaced, but will be considered "
3791 : "as such because of "
3792 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
3793 : }
3794 11 : else if (!oSRS.IsProjected() &&
3795 11 : fabs(dfSpacingBegin - dfSpacingLast) <= 0.1 &&
3796 30 : fabs(dfSpacingBegin - dfSpacingMiddle) <= 0.1 &&
3797 8 : fabs(dfSpacingMiddle - dfSpacingLast) <= 0.1)
3798 : {
3799 8 : bLatSpacingOK = true;
3800 8 : CPLError(CE_Warning, CPLE_AppDefined,
3801 : "Latitude grid not spaced evenly. "
3802 : "Setting projection for grid spacing is "
3803 : "within 0.1 degrees threshold.");
3804 :
3805 8 : CPLDebug("GDAL_netCDF",
3806 : "Latitude grid not spaced evenly, but within 0.1 "
3807 : "degree threshold (probably a Gaussian grid). "
3808 : "Saving original latitude values in Y_VALUES "
3809 : "geolocation metadata");
3810 8 : Set1DGeolocation(nGroupDimYID, nVarDimYID, "Y");
3811 : }
3812 :
3813 180 : if (bLatSpacingOK == false)
3814 : {
3815 3 : CPLDebug(
3816 : "GDAL_netCDF", "%s",
3817 : "Latitude/Y is not equally spaced (with a 0.05% "
3818 : "tolerance). "
3819 : "You may set the "
3820 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
3821 : "option to YES to ignore this check");
3822 : }
3823 : }
3824 :
3825 229 : if (bLonSpacingOK && bLatSpacingOK)
3826 : {
3827 : // We have gridded data so we can set the Georeferencing info.
3828 :
3829 : // Enable GeoTransform.
3830 :
3831 : // In the following "actual_range" and "node_offset"
3832 : // are attributes used by netCDF files created by GMT.
3833 : // If we find them we know how to proceed. Else, use
3834 : // the original algorithm.
3835 222 : bGotCfGT = true;
3836 :
3837 222 : int node_offset = 0;
3838 : const bool bUseActualRange =
3839 222 : NCDFResolveAttInt(nGroupId, NC_GLOBAL, "node_offset",
3840 222 : &node_offset) == CE_None;
3841 :
3842 222 : double adfActualRange[2] = {0.0, 0.0};
3843 222 : double xMinMax[2] = {0.0, 0.0};
3844 222 : double yMinMax[2] = {0.0, 0.0};
3845 :
3846 : const auto RoundMinMaxForFloatVals =
3847 60 : [](double &dfMin, double &dfMax, int nIntervals)
3848 : {
3849 : // Helps for a case where longitudes range from
3850 : // -179.99 to 180.0 with a 0.01 degree spacing.
3851 : // However as this is encoded in a float array,
3852 : // -179.99 is actually read as -179.99000549316406 as
3853 : // a double. Try to detect that and correct the rounding
3854 :
3855 88 : const auto IsAlmostInteger = [](double dfVal)
3856 : {
3857 88 : constexpr double THRESHOLD_INTEGER = 1e-3;
3858 88 : return std::fabs(dfVal - std::round(dfVal)) <=
3859 88 : THRESHOLD_INTEGER;
3860 : };
3861 :
3862 60 : const double dfSpacing = (dfMax - dfMin) / nIntervals;
3863 60 : if (dfSpacing > 0)
3864 : {
3865 48 : const double dfInvSpacing = 1.0 / dfSpacing;
3866 48 : if (IsAlmostInteger(dfInvSpacing))
3867 : {
3868 20 : const double dfRoundedSpacing =
3869 20 : 1.0 / std::round(dfInvSpacing);
3870 20 : const double dfMinDivRoundedSpacing =
3871 20 : dfMin / dfRoundedSpacing;
3872 20 : const double dfMaxDivRoundedSpacing =
3873 20 : dfMax / dfRoundedSpacing;
3874 40 : if (IsAlmostInteger(dfMinDivRoundedSpacing) &&
3875 20 : IsAlmostInteger(dfMaxDivRoundedSpacing))
3876 : {
3877 20 : const double dfRoundedMin =
3878 20 : std::round(dfMinDivRoundedSpacing) *
3879 : dfRoundedSpacing;
3880 20 : const double dfRoundedMax =
3881 20 : std::round(dfMaxDivRoundedSpacing) *
3882 : dfRoundedSpacing;
3883 20 : if (static_cast<float>(dfMin) ==
3884 20 : static_cast<float>(dfRoundedMin) &&
3885 8 : static_cast<float>(dfMax) ==
3886 8 : static_cast<float>(dfRoundedMax))
3887 : {
3888 7 : dfMin = dfRoundedMin;
3889 7 : dfMax = dfRoundedMax;
3890 : }
3891 : }
3892 : }
3893 : }
3894 60 : };
3895 :
3896 225 : if (bUseActualRange &&
3897 3 : !nc_get_att_double(nGroupDimXID, nVarDimXID, "actual_range",
3898 : adfActualRange))
3899 : {
3900 1 : xMinMax[0] = adfActualRange[0];
3901 1 : xMinMax[1] = adfActualRange[1];
3902 :
3903 : // Present xMinMax[] in the same order as padfXCoord
3904 1 : if ((xMinMax[0] - xMinMax[1]) *
3905 1 : (pdfXCoord[0] - pdfXCoord[xdim - 1]) <
3906 : 0)
3907 : {
3908 0 : std::swap(xMinMax[0], xMinMax[1]);
3909 : }
3910 : }
3911 : else
3912 : {
3913 221 : xMinMax[0] = pdfXCoord[0];
3914 221 : xMinMax[1] = pdfXCoord[xdim - 1];
3915 221 : node_offset = 0;
3916 :
3917 221 : if (nc_var_dimx_datatype == NC_FLOAT)
3918 : {
3919 30 : RoundMinMaxForFloatVals(xMinMax[0], xMinMax[1],
3920 30 : poDS->nRasterXSize - 1);
3921 : }
3922 : }
3923 :
3924 225 : if (bUseActualRange &&
3925 3 : !nc_get_att_double(nGroupDimYID, nVarDimYID, "actual_range",
3926 : adfActualRange))
3927 : {
3928 1 : yMinMax[0] = adfActualRange[0];
3929 1 : yMinMax[1] = adfActualRange[1];
3930 :
3931 : // Present yMinMax[] in the same order as pdfYCoord
3932 1 : if ((yMinMax[0] - yMinMax[1]) *
3933 1 : (pdfYCoord[0] - pdfYCoord[ydim - 1]) <
3934 : 0)
3935 : {
3936 0 : std::swap(yMinMax[0], yMinMax[1]);
3937 : }
3938 : }
3939 : else
3940 : {
3941 221 : yMinMax[0] = pdfYCoord[0];
3942 221 : yMinMax[1] = pdfYCoord[ydim - 1];
3943 221 : node_offset = 0;
3944 :
3945 221 : if (nc_var_dimy_datatype == NC_FLOAT)
3946 : {
3947 30 : RoundMinMaxForFloatVals(yMinMax[0], yMinMax[1],
3948 30 : poDS->nRasterYSize - 1);
3949 : }
3950 : }
3951 :
3952 222 : double dfCoordOffset = 0.0;
3953 222 : double dfCoordScale = 1.0;
3954 222 : if (!nc_get_att_double(nGroupId, nVarDimXID, CF_ADD_OFFSET,
3955 226 : &dfCoordOffset) &&
3956 4 : !nc_get_att_double(nGroupId, nVarDimXID, CF_SCALE_FACTOR,
3957 : &dfCoordScale))
3958 : {
3959 4 : xMinMax[0] = dfCoordOffset + xMinMax[0] * dfCoordScale;
3960 4 : xMinMax[1] = dfCoordOffset + xMinMax[1] * dfCoordScale;
3961 : }
3962 :
3963 222 : if (!nc_get_att_double(nGroupId, nVarDimYID, CF_ADD_OFFSET,
3964 226 : &dfCoordOffset) &&
3965 4 : !nc_get_att_double(nGroupId, nVarDimYID, CF_SCALE_FACTOR,
3966 : &dfCoordScale))
3967 : {
3968 4 : yMinMax[0] = dfCoordOffset + yMinMax[0] * dfCoordScale;
3969 4 : yMinMax[1] = dfCoordOffset + yMinMax[1] * dfCoordScale;
3970 : }
3971 :
3972 : // Check for reverse order of y-coordinate.
3973 222 : if (!bSwitchedXY)
3974 : {
3975 220 : poDS->bBottomUp = (yMinMax[0] <= yMinMax[1]);
3976 220 : if (!poDS->bBottomUp)
3977 : {
3978 32 : std::swap(yMinMax[0], yMinMax[1]);
3979 : }
3980 : }
3981 :
3982 : // Geostationary satellites can specify units in (micro)radians
3983 : // So we check if they do, and if so convert to linear units
3984 : // (meters)
3985 222 : const char *pszProjName = oSRS.GetAttrValue("PROJECTION");
3986 222 : if (pszProjName != nullptr)
3987 : {
3988 24 : if (EQUAL(pszProjName, SRS_PT_GEOSTATIONARY_SATELLITE))
3989 : {
3990 : double satelliteHeight =
3991 3 : oSRS.GetProjParm(SRS_PP_SATELLITE_HEIGHT, 1.0);
3992 3 : size_t nAttlen = 0;
3993 : char szUnits[NC_MAX_NAME + 1];
3994 3 : szUnits[0] = '\0';
3995 3 : nc_type nAttype = NC_NAT;
3996 3 : nc_inq_att(nGroupId, nVarDimXID, "units", &nAttype,
3997 : &nAttlen);
3998 6 : if (nAttlen < sizeof(szUnits) &&
3999 3 : nc_get_att_text(nGroupId, nVarDimXID, "units",
4000 : szUnits) == NC_NOERR)
4001 : {
4002 3 : szUnits[nAttlen] = '\0';
4003 3 : if (EQUAL(szUnits, "microradian"))
4004 : {
4005 1 : xMinMax[0] =
4006 1 : xMinMax[0] * satelliteHeight * 0.000001;
4007 1 : xMinMax[1] =
4008 1 : xMinMax[1] * satelliteHeight * 0.000001;
4009 : }
4010 2 : else if (EQUAL(szUnits, "rad") ||
4011 1 : EQUAL(szUnits, "radian"))
4012 : {
4013 2 : xMinMax[0] = xMinMax[0] * satelliteHeight;
4014 2 : xMinMax[1] = xMinMax[1] * satelliteHeight;
4015 : }
4016 : }
4017 3 : szUnits[0] = '\0';
4018 3 : nc_inq_att(nGroupId, nVarDimYID, "units", &nAttype,
4019 : &nAttlen);
4020 6 : if (nAttlen < sizeof(szUnits) &&
4021 3 : nc_get_att_text(nGroupId, nVarDimYID, "units",
4022 : szUnits) == NC_NOERR)
4023 : {
4024 3 : szUnits[nAttlen] = '\0';
4025 3 : if (EQUAL(szUnits, "microradian"))
4026 : {
4027 1 : yMinMax[0] =
4028 1 : yMinMax[0] * satelliteHeight * 0.000001;
4029 1 : yMinMax[1] =
4030 1 : yMinMax[1] * satelliteHeight * 0.000001;
4031 : }
4032 2 : else if (EQUAL(szUnits, "rad") ||
4033 1 : EQUAL(szUnits, "radian"))
4034 : {
4035 2 : yMinMax[0] = yMinMax[0] * satelliteHeight;
4036 2 : yMinMax[1] = yMinMax[1] * satelliteHeight;
4037 : }
4038 : }
4039 : }
4040 : }
4041 :
4042 222 : tmpGT[0] = xMinMax[0];
4043 444 : tmpGT[1] = (xMinMax[1] - xMinMax[0]) /
4044 222 : (poDS->nRasterXSize + (node_offset - 1));
4045 222 : tmpGT[2] = 0;
4046 222 : if (bSwitchedXY)
4047 : {
4048 2 : tmpGT[3] = yMinMax[0];
4049 2 : tmpGT[4] = 0;
4050 2 : tmpGT[5] = (yMinMax[1] - yMinMax[0]) /
4051 2 : (poDS->nRasterYSize + (node_offset - 1));
4052 : }
4053 : else
4054 : {
4055 220 : tmpGT[3] = yMinMax[1];
4056 220 : tmpGT[4] = 0;
4057 220 : tmpGT[5] = (yMinMax[0] - yMinMax[1]) /
4058 220 : (poDS->nRasterYSize + (node_offset - 1));
4059 : }
4060 :
4061 : // Compute the center of the pixel.
4062 222 : if (!node_offset)
4063 : {
4064 : // Otherwise its already the pixel center.
4065 222 : tmpGT[0] -= (tmpGT[1] / 2);
4066 222 : tmpGT[3] -= (tmpGT[5] / 2);
4067 : }
4068 : }
4069 :
4070 : const auto AreSRSEqualThroughProj4String =
4071 2 : [](const OGRSpatialReference &oSRS1,
4072 : const OGRSpatialReference &oSRS2)
4073 : {
4074 2 : char *pszProj4Str1 = nullptr;
4075 2 : oSRS1.exportToProj4(&pszProj4Str1);
4076 :
4077 2 : char *pszProj4Str2 = nullptr;
4078 2 : oSRS2.exportToProj4(&pszProj4Str2);
4079 :
4080 : {
4081 2 : char *pszTmp = strstr(pszProj4Str1, "+datum=");
4082 2 : if (pszTmp)
4083 0 : memcpy(pszTmp, "+ellps=", strlen("+ellps="));
4084 : }
4085 :
4086 : {
4087 2 : char *pszTmp = strstr(pszProj4Str2, "+datum=");
4088 2 : if (pszTmp)
4089 2 : memcpy(pszTmp, "+ellps=", strlen("+ellps="));
4090 : }
4091 :
4092 2 : bool bRet = false;
4093 2 : if (pszProj4Str1 && pszProj4Str2 &&
4094 2 : EQUAL(pszProj4Str1, pszProj4Str2))
4095 : {
4096 1 : bRet = true;
4097 : }
4098 :
4099 2 : CPLFree(pszProj4Str1);
4100 2 : CPLFree(pszProj4Str2);
4101 2 : return bRet;
4102 : };
4103 :
4104 229 : if (dfLinearUnitsConvFactor != 1.0)
4105 : {
4106 35 : for (int i = 0; i < 6; ++i)
4107 30 : tmpGT[i] *= dfLinearUnitsConvFactor;
4108 :
4109 5 : if (paosRemovedMDItems)
4110 : {
4111 : char szVarNameX[NC_MAX_NAME + 1];
4112 5 : CPL_IGNORE_RET_VAL(
4113 5 : nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
4114 :
4115 : char szVarNameY[NC_MAX_NAME + 1];
4116 5 : CPL_IGNORE_RET_VAL(
4117 5 : nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
4118 :
4119 5 : paosRemovedMDItems->push_back(
4120 : CPLSPrintf("%s#units", szVarNameX));
4121 5 : paosRemovedMDItems->push_back(
4122 : CPLSPrintf("%s#units", szVarNameY));
4123 : }
4124 : }
4125 :
4126 : // If there is a global "geospatial_bounds_crs" attribute, check that it
4127 : // is consistent with the SRS, and if so, use it as the SRS
4128 : const char *pszGBCRS =
4129 229 : FetchAttr(nGroupId, NC_GLOBAL, "geospatial_bounds_crs");
4130 229 : if (pszGBCRS && STARTS_WITH(pszGBCRS, "EPSG:"))
4131 : {
4132 4 : OGRSpatialReference oSRSFromGBCRS;
4133 2 : oSRSFromGBCRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
4134 2 : if (oSRSFromGBCRS.SetFromUserInput(
4135 : pszGBCRS,
4136 : OGRSpatialReference::
4137 4 : SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE &&
4138 2 : AreSRSEqualThroughProj4String(oSRS, oSRSFromGBCRS))
4139 : {
4140 1 : oSRS = std::move(oSRSFromGBCRS);
4141 1 : SetSpatialRefNoUpdate(&oSRS);
4142 : }
4143 : }
4144 :
4145 229 : CPLFree(pdfXCoord);
4146 229 : CPLFree(pdfYCoord);
4147 : } // end if(has dims)
4148 :
4149 : // Process custom GeoTransform GDAL value.
4150 558 : if (!EQUAL(pszGridMappingValue, ""))
4151 : {
4152 239 : if (pszGeoTransform != nullptr)
4153 : {
4154 : const CPLStringList aosGeoTransform(
4155 256 : CSLTokenizeString2(pszGeoTransform, " ", CSLT_HONOURSTRINGS));
4156 128 : if (aosGeoTransform.size() == 6)
4157 : {
4158 128 : bool bUseGeoTransformFromAttribute = true;
4159 :
4160 128 : GDALGeoTransform gtFromAttribute;
4161 896 : for (int i = 0; i < 6; i++)
4162 : {
4163 768 : gtFromAttribute[i] = CPLAtof(aosGeoTransform[i]);
4164 : }
4165 :
4166 : // When GDAL writes a raster that is north-up oriented, it
4167 : // writes the "GeoTransform" attribute unmodified, that is with
4168 : // gt.yscale < 0, but the first line is actually the southern-most
4169 : // one, consistently with the values of the "y" coordinate
4170 : // variable. This is wrong... but we have always done that, so
4171 : // this is hard to fix now.
4172 : // However there are datasets like
4173 : // https://public.hub.geosphere.at/datahub/resources/spartacus-v2-1d-1km/filelisting/TN/SPARTACUS2-DAILY_TN_2026.nc
4174 : // that correctly use a positive gt.yscale value. So make sure to not emit
4175 : // a warning when comparing against the geotransform derived from
4176 : // the x/y coordinates.
4177 128 : GDALGeoTransform gtFromAttributeNorthUp = gtFromAttribute;
4178 133 : if (gtFromAttributeNorthUp.yscale > 0 &&
4179 5 : gtFromAttributeNorthUp.IsAxisAligned())
4180 : {
4181 1 : gtFromAttributeNorthUp.yorig +=
4182 1 : poDS->nRasterYSize * gtFromAttributeNorthUp.yscale;
4183 1 : gtFromAttributeNorthUp.yscale =
4184 1 : -gtFromAttributeNorthUp.yscale;
4185 : }
4186 :
4187 128 : if (bGotCfGT)
4188 : {
4189 98 : constexpr double GT_RELERROR_WARN_THRESHOLD = 1e-6;
4190 98 : double dfMaxAbsoluteError = 0.0;
4191 686 : for (int i = 0; i < 6; i++)
4192 : {
4193 : double dfAbsoluteError =
4194 588 : std::abs(tmpGT[i] - gtFromAttributeNorthUp[i]);
4195 588 : if (dfAbsoluteError >
4196 588 : std::abs(gtFromAttributeNorthUp[i] *
4197 : GT_RELERROR_WARN_THRESHOLD))
4198 : {
4199 3 : dfMaxAbsoluteError =
4200 3 : std::max(dfMaxAbsoluteError, dfAbsoluteError);
4201 : }
4202 : }
4203 :
4204 98 : if (dfMaxAbsoluteError > 0)
4205 : {
4206 3 : bUseGeoTransformFromAttribute = false;
4207 3 : CPLError(CE_Warning, CPLE_AppDefined,
4208 : "GeoTransform read from attribute of %s "
4209 : "variable differs from value calculated from "
4210 : "dimension variables (max diff = %g). Using "
4211 : "value calculated from dimension variables.",
4212 : pszGridMappingValue, dfMaxAbsoluteError);
4213 : }
4214 : }
4215 :
4216 128 : if (bUseGeoTransformFromAttribute)
4217 : {
4218 125 : if (bGotCfGT)
4219 : {
4220 95 : tmpGT = gtFromAttributeNorthUp;
4221 95 : if (gtFromAttributeNorthUp.IsAxisAligned())
4222 : {
4223 95 : poDS->bBottomUp = true;
4224 : }
4225 : }
4226 : else
4227 : {
4228 30 : tmpGT = gtFromAttribute;
4229 : }
4230 125 : bGotGdalGT = true;
4231 : }
4232 : }
4233 : }
4234 : else
4235 : {
4236 : // Look for corner array values.
4237 : // CPLDebug("GDAL_netCDF",
4238 : // "looking for geotransform corners");
4239 111 : bool bGotNN = false;
4240 111 : double dfNN = FetchCopyParam(pszGridMappingValue,
4241 : "Northernmost_Northing", 0, &bGotNN);
4242 :
4243 111 : bool bGotSN = false;
4244 111 : double dfSN = FetchCopyParam(pszGridMappingValue,
4245 : "Southernmost_Northing", 0, &bGotSN);
4246 :
4247 111 : bool bGotEE = false;
4248 111 : double dfEE = FetchCopyParam(pszGridMappingValue,
4249 : "Easternmost_Easting", 0, &bGotEE);
4250 :
4251 111 : bool bGotWE = false;
4252 111 : double dfWE = FetchCopyParam(pszGridMappingValue,
4253 : "Westernmost_Easting", 0, &bGotWE);
4254 :
4255 : // Only set the GeoTransform if we got all the values.
4256 111 : if (bGotNN && bGotSN && bGotEE && bGotWE)
4257 : {
4258 0 : bGotGdalGT = true;
4259 :
4260 0 : tmpGT[0] = dfWE;
4261 0 : tmpGT[1] = (dfEE - dfWE) / (poDS->GetRasterXSize() - 1);
4262 0 : tmpGT[2] = 0.0;
4263 0 : tmpGT[3] = dfNN;
4264 0 : tmpGT[4] = 0.0;
4265 0 : tmpGT[5] = (dfSN - dfNN) / (poDS->GetRasterYSize() - 1);
4266 : // Compute the center of the pixel.
4267 0 : tmpGT[0] = dfWE - (tmpGT[1] / 2);
4268 0 : tmpGT[3] = dfNN - (tmpGT[5] / 2);
4269 : }
4270 : } // (pszGeoTransform != NULL)
4271 :
4272 239 : if (bGotGdalSRS && !bGotGdalGT)
4273 78 : CPLDebug("GDAL_netCDF", "Got SRS but no geotransform from GDAL!");
4274 : }
4275 :
4276 558 : if (bGotCfGT || bGotGdalGT)
4277 : {
4278 252 : CPLDebug("GDAL_netCDF", "set bBottomUp = %d from Y axis",
4279 252 : static_cast<int>(poDS->bBottomUp));
4280 : }
4281 :
4282 558 : if (!pszWKT && !bGotCfSRS)
4283 : {
4284 : // Some netCDF files have a srid attribute (#6613) like
4285 : // urn:ogc:def:crs:EPSG::6931
4286 319 : const char *pszSRID = FetchAttr(pszGridMappingValue, "srid");
4287 319 : if (pszSRID != nullptr)
4288 : {
4289 0 : oSRS.Clear();
4290 0 : if (oSRS.SetFromUserInput(
4291 : pszSRID,
4292 : OGRSpatialReference::
4293 0 : SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE)
4294 : {
4295 0 : char *pszWKTExport = nullptr;
4296 0 : CPLDebug("GDAL_netCDF", "Got SRS from %s", pszSRID);
4297 0 : oSRS.exportToWkt(&pszWKTExport);
4298 0 : if (returnProjStr != nullptr)
4299 : {
4300 0 : (*returnProjStr) = std::string(pszWKTExport);
4301 : }
4302 : else
4303 : {
4304 0 : m_bAddedProjectionVarsDefs = true;
4305 0 : m_bAddedProjectionVarsData = true;
4306 0 : SetSpatialRefNoUpdate(&oSRS);
4307 : }
4308 0 : CPLFree(pszWKTExport);
4309 : }
4310 : }
4311 : }
4312 :
4313 558 : CPLFree(pszGridMappingValue);
4314 :
4315 558 : if (bReadSRSOnly)
4316 190 : return;
4317 :
4318 : // Determines the SRS to be used by the geolocation array, if any
4319 736 : std::string osGeolocWKT = SRS_WKT_WGS84_LAT_LONG;
4320 368 : if (!m_oSRS.IsEmpty())
4321 : {
4322 290 : OGRSpatialReference oGeogCRS;
4323 145 : oGeogCRS.CopyGeogCSFrom(&m_oSRS);
4324 145 : char *pszWKTTmp = nullptr;
4325 145 : const char *const apszOptions[] = {"FORMAT=WKT2_2019", nullptr};
4326 145 : if (oGeogCRS.exportToWkt(&pszWKTTmp, apszOptions) == OGRERR_NONE)
4327 : {
4328 145 : osGeolocWKT = pszWKTTmp;
4329 : }
4330 145 : CPLFree(pszWKTTmp);
4331 : }
4332 :
4333 : // Process geolocation arrays from CF "coordinates" attribute.
4334 736 : std::string osGeolocXName, osGeolocYName;
4335 368 : if (ProcessCFGeolocation(nGroupId, nVarId, osGeolocWKT, osGeolocXName,
4336 368 : osGeolocYName))
4337 : {
4338 61 : bool bCanCancelGT = true;
4339 61 : if ((nVarDimXID != -1) && (nVarDimYID != -1))
4340 : {
4341 : char szVarNameX[NC_MAX_NAME + 1];
4342 44 : CPL_IGNORE_RET_VAL(
4343 44 : nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
4344 : char szVarNameY[NC_MAX_NAME + 1];
4345 44 : CPL_IGNORE_RET_VAL(
4346 44 : nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
4347 44 : bCanCancelGT =
4348 44 : !(osGeolocXName == szVarNameX && osGeolocYName == szVarNameY);
4349 : }
4350 100 : if (bCanCancelGT && !m_oSRS.IsGeographic() && !m_oSRS.IsProjected() &&
4351 39 : !bSwitchedXY)
4352 : {
4353 37 : bGotCfGT = false;
4354 : }
4355 : }
4356 125 : else if (!bGotCfGT && !bReadSRSOnly && (nVarDimXID != -1) &&
4357 435 : (nVarDimYID != -1) && xdim > 0 && ydim > 0 &&
4358 3 : ((!bSwitchedXY &&
4359 3 : NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
4360 1 : NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr)) ||
4361 2 : (bSwitchedXY &&
4362 0 : NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
4363 0 : NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr))))
4364 : {
4365 : // Case of autotest/gdrivers/data/netcdf/GLMELT_4X5.OCN.nc
4366 : // which is indexed by lat, lon variables, but lat has irregular
4367 : // spacing.
4368 1 : const char *pszGeolocXFullName = poDS->papszDimName[poDS->nXDimID];
4369 1 : const char *pszGeolocYFullName = poDS->papszDimName[poDS->nYDimID];
4370 1 : if (bSwitchedXY)
4371 : {
4372 0 : std::swap(pszGeolocXFullName, pszGeolocYFullName);
4373 0 : GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", "GEOLOCATION");
4374 : }
4375 :
4376 1 : CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
4377 : pszGeolocXFullName, pszGeolocYFullName);
4378 :
4379 1 : GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
4380 : "GEOLOCATION");
4381 :
4382 2 : CPLString osTMP;
4383 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4384 1 : pszGeolocXFullName);
4385 :
4386 1 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
4387 1 : GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
4388 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4389 1 : pszGeolocYFullName);
4390 :
4391 1 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
4392 1 : GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
4393 :
4394 1 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
4395 1 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
4396 :
4397 1 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
4398 1 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
4399 :
4400 1 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
4401 : "PIXEL_CENTER", "GEOLOCATION");
4402 : }
4403 :
4404 : // Set GeoTransform if we got a complete one - after projection has been set
4405 368 : if (bGotCfGT || bGotGdalGT)
4406 : {
4407 212 : m_bAddedProjectionVarsDefs = true;
4408 212 : m_bAddedProjectionVarsData = true;
4409 212 : SetGeoTransformNoUpdate(tmpGT);
4410 : }
4411 :
4412 : // Debugging reports.
4413 368 : CPLDebug("GDAL_netCDF",
4414 : "bGotGeogCS=%d bGotCfSRS=%d bGotCfGT=%d bGotCfWktSRS=%d "
4415 : "bGotGdalSRS=%d bGotGdalGT=%d",
4416 : static_cast<int>(bGotGeogCS), static_cast<int>(bGotCfSRS),
4417 : static_cast<int>(bGotCfGT), static_cast<int>(bGotCfWktSRS),
4418 : static_cast<int>(bGotGdalSRS), static_cast<int>(bGotGdalGT));
4419 :
4420 368 : if (!bGotCfGT && !bGotGdalGT)
4421 156 : CPLDebug("GDAL_netCDF", "did not get geotransform from CF nor GDAL!");
4422 :
4423 368 : if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfGT && !bGotCfWktSRS)
4424 156 : CPLDebug("GDAL_netCDF", "did not get projection from CF nor GDAL!");
4425 :
4426 : // wish of 6195
4427 : // we don't have CS/SRS, but we do have GT, and we live in -180,360 -90,90
4428 368 : if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfWktSRS)
4429 : {
4430 223 : if (bGotCfGT || bGotGdalGT)
4431 : {
4432 134 : bool bAssumedLongLat = CPLTestBool(CSLFetchNameValueDef(
4433 67 : papszOpenOptions, "ASSUME_LONGLAT",
4434 : CPLGetConfigOption("GDAL_NETCDF_ASSUME_LONGLAT", "NO")));
4435 :
4436 2 : if (bAssumedLongLat && tmpGT[0] >= -180 && tmpGT[0] < 360 &&
4437 2 : (tmpGT[0] + tmpGT[1] * poDS->GetRasterXSize()) <= 360 &&
4438 71 : tmpGT[3] <= 90 && tmpGT[3] > -90 &&
4439 2 : (tmpGT[3] + tmpGT[5] * poDS->GetRasterYSize()) >= -90)
4440 : {
4441 :
4442 2 : poDS->bIsGeographic = true;
4443 2 : char *pszTempProjection = nullptr;
4444 : // seems odd to use 4326 so OGC:CRS84
4445 2 : oSRS.SetFromUserInput("OGC:CRS84");
4446 2 : oSRS.exportToWkt(&pszTempProjection);
4447 2 : if (returnProjStr != nullptr)
4448 : {
4449 0 : (*returnProjStr) = std::string(pszTempProjection);
4450 : }
4451 : else
4452 : {
4453 2 : m_bAddedProjectionVarsDefs = true;
4454 2 : m_bAddedProjectionVarsData = true;
4455 2 : SetSpatialRefNoUpdate(&oSRS);
4456 : }
4457 2 : CPLFree(pszTempProjection);
4458 :
4459 2 : CPLDebug("netCDF",
4460 : "Assumed Longitude Latitude CRS 'OGC:CRS84' because "
4461 : "none otherwise available and geotransform within "
4462 : "suitable bounds. "
4463 : "Set GDAL_NETCDF_ASSUME_LONGLAT=NO as configuration "
4464 : "option or "
4465 : " ASSUME_LONGLAT=NO as open option to bypass this "
4466 : "assumption.");
4467 : }
4468 : }
4469 : }
4470 :
4471 : // Search for Well-known GeogCS if got only CF WKT
4472 : // Disabled for now, as a named datum also include control points
4473 : // (see mailing list and bug#4281
4474 : // For example, WGS84 vs. GDA94 (EPSG:3577) - AEA in netcdf_cf.py
4475 :
4476 : // Disabled for now, but could be set in a config option.
4477 : #if 0
4478 : bool bLookForWellKnownGCS = false; // This could be a Config Option.
4479 :
4480 : if( bLookForWellKnownGCS && bGotCfSRS && !bGotGdalSRS )
4481 : {
4482 : // ET - Could use a more exhaustive method by scanning all EPSG codes in
4483 : // data/gcs.csv as proposed by Even in the gdal-dev mailing list "help
4484 : // for comparing two WKT".
4485 : // This code could be contributed to a new function.
4486 : // OGRSpatialReference * OGRSpatialReference::FindMatchingGeogCS(
4487 : // const OGRSpatialReference *poOther) */
4488 : CPLDebug("GDAL_netCDF", "Searching for Well-known GeogCS");
4489 : const char *pszWKGCSList[] = { "WGS84", "WGS72", "NAD27", "NAD83" };
4490 : char *pszWKGCS = NULL;
4491 : oSRS.exportToPrettyWkt(&pszWKGCS);
4492 : for( size_t i = 0; i < sizeof(pszWKGCSList) / 8; i++ )
4493 : {
4494 : pszWKGCS = CPLStrdup(pszWKGCSList[i]);
4495 : OGRSpatialReference oSRSTmp;
4496 : oSRSTmp.SetWellKnownGeogCS(pszWKGCSList[i]);
4497 : // Set datum to unknown, bug #4281.
4498 : if( oSRSTmp.GetAttrNode("DATUM" ) )
4499 : oSRSTmp.GetAttrNode("DATUM")->GetChild(0)->SetValue("unknown");
4500 : // Could use OGRSpatialReference::StripCTParms(), but let's keep
4501 : // TOWGS84.
4502 : oSRSTmp.GetRoot()->StripNodes("AXIS");
4503 : oSRSTmp.GetRoot()->StripNodes("AUTHORITY");
4504 : oSRSTmp.GetRoot()->StripNodes("EXTENSION");
4505 :
4506 : oSRSTmp.exportToPrettyWkt(&pszWKGCS);
4507 : if( oSRS.IsSameGeogCS(&oSRSTmp) )
4508 : {
4509 : oSRS.SetWellKnownGeogCS(pszWKGCSList[i]);
4510 : oSRS.exportToWkt(&(pszTempProjection));
4511 : SetProjection(pszTempProjection);
4512 : CPLFree(pszTempProjection);
4513 : }
4514 : }
4515 : }
4516 : #endif
4517 : }
4518 :
4519 147 : void netCDFDataset::SetProjectionFromVar(int nGroupId, int nVarId,
4520 : bool bReadSRSOnly)
4521 : {
4522 147 : SetProjectionFromVar(nGroupId, nVarId, bReadSRSOnly, nullptr, nullptr,
4523 : nullptr, nullptr);
4524 147 : }
4525 :
4526 296 : bool netCDFDataset::ProcessNASAL2OceanGeoLocation(int nGroupId, int nVarId)
4527 : {
4528 : // Cf https://oceancolor.gsfc.nasa.gov/docs/format/l2nc/
4529 : // and https://github.com/OSGeo/gdal/issues/7605
4530 :
4531 : // Check for a structure like:
4532 : /* netcdf SNPP_VIIRS.20230406T024200.L2.OC.NRT {
4533 : dimensions:
4534 : number_of_lines = 3248 ;
4535 : pixels_per_line = 3200 ;
4536 : [...]
4537 : pixel_control_points = 3200 ;
4538 : [...]
4539 : group: geophysical_data {
4540 : variables:
4541 : short aot_862(number_of_lines, pixels_per_line) ; <-- nVarId
4542 : [...]
4543 : }
4544 : group: navigation_data {
4545 : variables:
4546 : float longitude(number_of_lines, pixel_control_points) ;
4547 : [...]
4548 : float latitude(number_of_lines, pixel_control_points) ;
4549 : [...]
4550 : }
4551 : }
4552 : */
4553 : // Note that the longitude and latitude arrays are not indexed by the
4554 : // same dimensions. Handle only the case where
4555 : // pixel_control_points == pixels_per_line
4556 : // If there was a subsampling of the geolocation arrays, we'd need to
4557 : // add more logic.
4558 :
4559 592 : std::string osGroupName;
4560 296 : osGroupName.resize(NC_MAX_NAME);
4561 296 : NCDF_ERR(nc_inq_grpname(nGroupId, &osGroupName[0]));
4562 296 : osGroupName.resize(strlen(osGroupName.data()));
4563 296 : if (osGroupName != "geophysical_data")
4564 295 : return false;
4565 :
4566 1 : int nVarDims = 0;
4567 1 : NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
4568 1 : if (nVarDims != 2)
4569 0 : return false;
4570 :
4571 1 : int nNavigationDataGrpId = 0;
4572 1 : if (nc_inq_grp_ncid(cdfid, "navigation_data", &nNavigationDataGrpId) !=
4573 : NC_NOERR)
4574 0 : return false;
4575 :
4576 : std::array<int, 2> anVarDimIds;
4577 1 : NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
4578 :
4579 1 : int nLongitudeId = 0;
4580 1 : int nLatitudeId = 0;
4581 1 : if (nc_inq_varid(nNavigationDataGrpId, "longitude", &nLongitudeId) !=
4582 2 : NC_NOERR ||
4583 1 : nc_inq_varid(nNavigationDataGrpId, "latitude", &nLatitudeId) !=
4584 : NC_NOERR)
4585 : {
4586 0 : return false;
4587 : }
4588 :
4589 1 : int nDimsLongitude = 0;
4590 1 : NCDF_ERR(
4591 : nc_inq_varndims(nNavigationDataGrpId, nLongitudeId, &nDimsLongitude));
4592 1 : int nDimsLatitude = 0;
4593 1 : NCDF_ERR(
4594 : nc_inq_varndims(nNavigationDataGrpId, nLatitudeId, &nDimsLatitude));
4595 1 : if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
4596 : {
4597 0 : return false;
4598 : }
4599 :
4600 : std::array<int, 2> anDimLongitudeIds;
4601 1 : NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLongitudeId,
4602 : anDimLongitudeIds.data()));
4603 : std::array<int, 2> anDimLatitudeIds;
4604 1 : NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLatitudeId,
4605 : anDimLatitudeIds.data()));
4606 1 : if (anDimLongitudeIds != anDimLatitudeIds)
4607 : {
4608 0 : return false;
4609 : }
4610 :
4611 : std::array<size_t, 2> anSizeVarDimIds;
4612 : std::array<size_t, 2> anSizeLongLatIds;
4613 2 : if (!(nc_inq_dimlen(cdfid, anVarDimIds[0], &anSizeVarDimIds[0]) ==
4614 1 : NC_NOERR &&
4615 1 : nc_inq_dimlen(cdfid, anVarDimIds[1], &anSizeVarDimIds[1]) ==
4616 1 : NC_NOERR &&
4617 1 : nc_inq_dimlen(cdfid, anDimLongitudeIds[0], &anSizeLongLatIds[0]) ==
4618 1 : NC_NOERR &&
4619 1 : nc_inq_dimlen(cdfid, anDimLongitudeIds[1], &anSizeLongLatIds[1]) ==
4620 : NC_NOERR &&
4621 1 : anSizeVarDimIds == anSizeLongLatIds))
4622 : {
4623 0 : return false;
4624 : }
4625 :
4626 1 : const char *pszGeolocXFullName = "/navigation_data/longitude";
4627 1 : const char *pszGeolocYFullName = "/navigation_data/latitude";
4628 :
4629 1 : if (bSwitchedXY)
4630 : {
4631 0 : std::swap(pszGeolocXFullName, pszGeolocYFullName);
4632 0 : GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", "GEOLOCATION");
4633 : }
4634 :
4635 1 : CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
4636 : pszGeolocXFullName, pszGeolocYFullName);
4637 :
4638 1 : GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
4639 : "GEOLOCATION");
4640 :
4641 1 : CPLString osTMP;
4642 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
4643 :
4644 1 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
4645 1 : GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
4646 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
4647 :
4648 1 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
4649 1 : GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
4650 :
4651 1 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
4652 1 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
4653 :
4654 1 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
4655 1 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
4656 :
4657 1 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
4658 : "GEOLOCATION");
4659 1 : return true;
4660 : }
4661 :
4662 295 : bool netCDFDataset::ProcessNASAEMITGeoLocation(int nGroupId, int nVarId)
4663 : {
4664 : // Cf https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/
4665 :
4666 : // Check for a structure like:
4667 : /* netcdf EMIT_L2A_RFL_001_20220903T163129_2224611_012 {
4668 : dimensions:
4669 : downtrack = 1280 ;
4670 : crosstrack = 1242 ;
4671 : bands = 285 ;
4672 : [...]
4673 :
4674 : variables:
4675 : float reflectance(downtrack, crosstrack, bands) ;
4676 :
4677 : group: location {
4678 : variables:
4679 : double lon(downtrack, crosstrack) ;
4680 : lon:_FillValue = -9999. ;
4681 : lon:long_name = "Longitude (WGS-84)" ;
4682 : lon:units = "degrees east" ;
4683 : double lat(downtrack, crosstrack) ;
4684 : lat:_FillValue = -9999. ;
4685 : lat:long_name = "Latitude (WGS-84)" ;
4686 : lat:units = "degrees north" ;
4687 : } // group location
4688 :
4689 : }
4690 : or
4691 : netcdf EMIT_L2B_MIN_001_20231024T055538_2329704_040 {
4692 : dimensions:
4693 : downtrack = 1664 ;
4694 : crosstrack = 1242 ;
4695 : [...]
4696 : variables:
4697 : float group_1_band_depth(downtrack, crosstrack) ;
4698 : group_1_band_depth:_FillValue = -9999.f ;
4699 : group_1_band_depth:long_name = "Group 1 Band Depth" ;
4700 : group_1_band_depth:units = "unitless" ;
4701 : [...]
4702 : group: location {
4703 : variables:
4704 : double lon(downtrack, crosstrack) ;
4705 : lon:_FillValue = -9999. ;
4706 : lon:long_name = "Longitude (WGS-84)" ;
4707 : lon:units = "degrees east" ;
4708 : double lat(downtrack, crosstrack) ;
4709 : lat:_FillValue = -9999. ;
4710 : lat:long_name = "Latitude (WGS-84)" ;
4711 : lat:units = "degrees north" ;
4712 : }
4713 : */
4714 :
4715 295 : int nVarDims = 0;
4716 295 : NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
4717 295 : if (nVarDims != 2 && nVarDims != 3)
4718 14 : return false;
4719 :
4720 281 : int nLocationGrpId = 0;
4721 281 : if (nc_inq_grp_ncid(cdfid, "location", &nLocationGrpId) != NC_NOERR)
4722 60 : return false;
4723 :
4724 : std::array<int, 3> anVarDimIds;
4725 221 : NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
4726 221 : if (nYDimID != anVarDimIds[0] || nXDimID != anVarDimIds[1])
4727 21 : return false;
4728 :
4729 200 : int nLongitudeId = 0;
4730 200 : int nLatitudeId = 0;
4731 238 : if (nc_inq_varid(nLocationGrpId, "lon", &nLongitudeId) != NC_NOERR ||
4732 38 : nc_inq_varid(nLocationGrpId, "lat", &nLatitudeId) != NC_NOERR)
4733 : {
4734 162 : return false;
4735 : }
4736 :
4737 38 : int nDimsLongitude = 0;
4738 38 : NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLongitudeId, &nDimsLongitude));
4739 38 : int nDimsLatitude = 0;
4740 38 : NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLatitudeId, &nDimsLatitude));
4741 38 : if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
4742 : {
4743 34 : return false;
4744 : }
4745 :
4746 : std::array<int, 2> anDimLongitudeIds;
4747 4 : NCDF_ERR(nc_inq_vardimid(nLocationGrpId, nLongitudeId,
4748 : anDimLongitudeIds.data()));
4749 : std::array<int, 2> anDimLatitudeIds;
4750 4 : NCDF_ERR(
4751 : nc_inq_vardimid(nLocationGrpId, nLatitudeId, anDimLatitudeIds.data()));
4752 4 : if (anDimLongitudeIds != anDimLatitudeIds)
4753 : {
4754 0 : return false;
4755 : }
4756 :
4757 8 : if (anDimLongitudeIds[0] != anVarDimIds[0] ||
4758 4 : anDimLongitudeIds[1] != anVarDimIds[1])
4759 : {
4760 0 : return false;
4761 : }
4762 :
4763 4 : const char *pszGeolocXFullName = "/location/lon";
4764 4 : const char *pszGeolocYFullName = "/location/lat";
4765 :
4766 4 : CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
4767 : pszGeolocXFullName, pszGeolocYFullName);
4768 :
4769 4 : GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
4770 : "GEOLOCATION");
4771 :
4772 4 : CPLString osTMP;
4773 4 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
4774 :
4775 4 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
4776 4 : GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
4777 4 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
4778 :
4779 4 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
4780 4 : GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
4781 :
4782 4 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
4783 4 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
4784 :
4785 4 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
4786 4 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
4787 :
4788 4 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
4789 : "GEOLOCATION");
4790 4 : return true;
4791 : }
4792 :
4793 368 : int netCDFDataset::ProcessCFGeolocation(int nGroupId, int nVarId,
4794 : const std::string &osGeolocWKT,
4795 : std::string &osGeolocXNameOut,
4796 : std::string &osGeolocYNameOut)
4797 : {
4798 368 : bool bAddGeoloc = false;
4799 368 : char *pszCoordinates = nullptr;
4800 :
4801 : // If there is no explicit "coordinates" attribute, check if there are
4802 : // "lon" and "lat" 2D variables whose dimensions are the last
4803 : // 2 ones of the variable of interest.
4804 368 : if (NCDFGetAttr(nGroupId, nVarId, "coordinates", &pszCoordinates) !=
4805 : CE_None)
4806 : {
4807 317 : CPLFree(pszCoordinates);
4808 317 : pszCoordinates = nullptr;
4809 :
4810 317 : int nVarDims = 0;
4811 317 : NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
4812 317 : if (nVarDims >= 2)
4813 : {
4814 634 : std::vector<int> anVarDimIds(nVarDims);
4815 317 : NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
4816 :
4817 317 : int nLongitudeId = 0;
4818 317 : int nLatitudeId = 0;
4819 389 : if (nc_inq_varid(nGroupId, "lon", &nLongitudeId) == NC_NOERR &&
4820 72 : nc_inq_varid(nGroupId, "lat", &nLatitudeId) == NC_NOERR)
4821 : {
4822 72 : int nDimsLongitude = 0;
4823 72 : NCDF_ERR(
4824 : nc_inq_varndims(nGroupId, nLongitudeId, &nDimsLongitude));
4825 72 : int nDimsLatitude = 0;
4826 72 : NCDF_ERR(
4827 : nc_inq_varndims(nGroupId, nLatitudeId, &nDimsLatitude));
4828 72 : if (nDimsLongitude == 2 && nDimsLatitude == 2)
4829 : {
4830 42 : std::vector<int> anDimLongitudeIds(2);
4831 21 : NCDF_ERR(nc_inq_vardimid(nGroupId, nLongitudeId,
4832 : anDimLongitudeIds.data()));
4833 42 : std::vector<int> anDimLatitudeIds(2);
4834 21 : NCDF_ERR(nc_inq_vardimid(nGroupId, nLatitudeId,
4835 : anDimLatitudeIds.data()));
4836 21 : if (anDimLongitudeIds == anDimLatitudeIds &&
4837 42 : anVarDimIds[anVarDimIds.size() - 2] ==
4838 63 : anDimLongitudeIds[0] &&
4839 42 : anVarDimIds[anVarDimIds.size() - 1] ==
4840 21 : anDimLongitudeIds[1])
4841 : {
4842 21 : pszCoordinates = CPLStrdup("lon lat");
4843 : }
4844 : }
4845 : }
4846 : }
4847 : }
4848 :
4849 368 : if (pszCoordinates)
4850 : {
4851 : // Get X and Y geolocation names from coordinates attribute.
4852 : const CPLStringList aosCoordinates(
4853 144 : NCDFTokenizeCoordinatesAttribute(pszCoordinates));
4854 72 : if (aosCoordinates.size() >= 2)
4855 : {
4856 : char szGeolocXName[NC_MAX_NAME + 1];
4857 : char szGeolocYName[NC_MAX_NAME + 1];
4858 69 : szGeolocXName[0] = '\0';
4859 69 : szGeolocYName[0] = '\0';
4860 :
4861 : // Test that each variable is longitude/latitude.
4862 220 : for (int i = 0; i < aosCoordinates.size(); i++)
4863 : {
4864 151 : if (NCDFIsVarLongitude(nGroupId, -1, aosCoordinates[i]))
4865 : {
4866 58 : int nOtherGroupId = -1;
4867 58 : int nOtherVarId = -1;
4868 : // Check that the variable actually exists
4869 : // Needed on Sentinel-3 products
4870 58 : if (NCDFResolveVar(nGroupId, aosCoordinates[i],
4871 58 : &nOtherGroupId, &nOtherVarId) == CE_None)
4872 : {
4873 56 : snprintf(szGeolocXName, sizeof(szGeolocXName), "%s",
4874 : aosCoordinates[i]);
4875 : }
4876 : }
4877 93 : else if (NCDFIsVarLatitude(nGroupId, -1, aosCoordinates[i]))
4878 : {
4879 58 : int nOtherGroupId = -1;
4880 58 : int nOtherVarId = -1;
4881 : // Check that the variable actually exists
4882 : // Needed on Sentinel-3 products
4883 58 : if (NCDFResolveVar(nGroupId, aosCoordinates[i],
4884 58 : &nOtherGroupId, &nOtherVarId) == CE_None)
4885 : {
4886 56 : snprintf(szGeolocYName, sizeof(szGeolocYName), "%s",
4887 : aosCoordinates[i]);
4888 : }
4889 : }
4890 : }
4891 : // Add GEOLOCATION metadata.
4892 69 : if (!EQUAL(szGeolocXName, "") && !EQUAL(szGeolocYName, ""))
4893 : {
4894 56 : osGeolocXNameOut = szGeolocXName;
4895 56 : osGeolocYNameOut = szGeolocYName;
4896 :
4897 56 : char *pszGeolocXFullName = nullptr;
4898 56 : char *pszGeolocYFullName = nullptr;
4899 56 : if (NCDFResolveVarFullName(nGroupId, szGeolocXName,
4900 112 : &pszGeolocXFullName) == CE_None &&
4901 56 : NCDFResolveVarFullName(nGroupId, szGeolocYName,
4902 : &pszGeolocYFullName) == CE_None)
4903 : {
4904 56 : if (bSwitchedXY)
4905 : {
4906 2 : std::swap(pszGeolocXFullName, pszGeolocYFullName);
4907 2 : GDALPamDataset::SetMetadataItem("SWAP_XY", "YES",
4908 : "GEOLOCATION");
4909 : }
4910 :
4911 56 : bAddGeoloc = true;
4912 56 : CPLDebug("GDAL_netCDF",
4913 : "using variables %s and %s for GEOLOCATION",
4914 : pszGeolocXFullName, pszGeolocYFullName);
4915 :
4916 56 : GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
4917 : "GEOLOCATION");
4918 :
4919 112 : CPLString osTMP;
4920 56 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4921 56 : pszGeolocXFullName);
4922 :
4923 56 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP,
4924 : "GEOLOCATION");
4925 56 : GDALPamDataset::SetMetadataItem("X_BAND", "1",
4926 : "GEOLOCATION");
4927 56 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4928 56 : pszGeolocYFullName);
4929 :
4930 56 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP,
4931 : "GEOLOCATION");
4932 56 : GDALPamDataset::SetMetadataItem("Y_BAND", "1",
4933 : "GEOLOCATION");
4934 :
4935 56 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0",
4936 : "GEOLOCATION");
4937 56 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1",
4938 : "GEOLOCATION");
4939 :
4940 56 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0",
4941 : "GEOLOCATION");
4942 56 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1",
4943 : "GEOLOCATION");
4944 :
4945 56 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
4946 : "PIXEL_CENTER",
4947 : "GEOLOCATION");
4948 : }
4949 : else
4950 : {
4951 0 : CPLDebug("GDAL_netCDF",
4952 : "cannot resolve location of "
4953 : "lat/lon variables specified by the coordinates "
4954 : "attribute [%s]",
4955 : pszCoordinates);
4956 : }
4957 56 : CPLFree(pszGeolocXFullName);
4958 56 : CPLFree(pszGeolocYFullName);
4959 : }
4960 : else
4961 : {
4962 13 : CPLDebug("GDAL_netCDF",
4963 : "coordinates attribute [%s] is unsupported",
4964 : pszCoordinates);
4965 : }
4966 : }
4967 : else
4968 : {
4969 3 : CPLDebug("GDAL_netCDF",
4970 : "coordinates attribute [%s] with %d element(s) is "
4971 : "unsupported",
4972 : pszCoordinates, aosCoordinates.size());
4973 : }
4974 : }
4975 :
4976 : else
4977 : {
4978 296 : bAddGeoloc = ProcessNASAL2OceanGeoLocation(nGroupId, nVarId);
4979 :
4980 296 : if (!bAddGeoloc)
4981 295 : bAddGeoloc = ProcessNASAEMITGeoLocation(nGroupId, nVarId);
4982 : }
4983 :
4984 368 : CPLFree(pszCoordinates);
4985 :
4986 368 : return bAddGeoloc;
4987 : }
4988 :
4989 8 : CPLErr netCDFDataset::Set1DGeolocation(int nGroupId, int nVarId,
4990 : const char *szDimName)
4991 : {
4992 : // Get values.
4993 8 : char *pszVarValues = nullptr;
4994 8 : CPLErr eErr = NCDFGet1DVar(nGroupId, nVarId, &pszVarValues);
4995 8 : if (eErr != CE_None)
4996 0 : return eErr;
4997 :
4998 : // Write metadata.
4999 8 : char szTemp[NC_MAX_NAME + 1 + 32] = {};
5000 8 : snprintf(szTemp, sizeof(szTemp), "%s_VALUES", szDimName);
5001 8 : GDALPamDataset::SetMetadataItem(szTemp, pszVarValues, "GEOLOCATION2");
5002 :
5003 8 : CPLFree(pszVarValues);
5004 :
5005 8 : return CE_None;
5006 : }
5007 :
5008 0 : double *netCDFDataset::Get1DGeolocation(CPL_UNUSED const char *szDimName,
5009 : int &nVarLen)
5010 : {
5011 0 : nVarLen = 0;
5012 :
5013 : // Get Y_VALUES as tokens.
5014 : const CPLStringList aosValues(
5015 0 : NCDFTokenizeArray(GetMetadataItem("Y_VALUES", "GEOLOCATION2")));
5016 0 : if (aosValues.empty())
5017 0 : return nullptr;
5018 :
5019 : // Initialize and fill array.
5020 0 : nVarLen = aosValues.size();
5021 : double *pdfVarValues =
5022 0 : static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
5023 :
5024 0 : for (int i = 0, j = 0; i < nVarLen; i++)
5025 : {
5026 0 : if (!bBottomUp)
5027 0 : j = nVarLen - 1 - i;
5028 : else
5029 0 : j = i; // Invert latitude values.
5030 0 : char *pszTemp = nullptr;
5031 0 : pdfVarValues[j] = CPLStrtod(aosValues[i], &pszTemp);
5032 : }
5033 :
5034 0 : return pdfVarValues;
5035 : }
5036 :
5037 : /************************************************************************/
5038 : /* SetSpatialRefNoUpdate() */
5039 : /************************************************************************/
5040 :
5041 281 : void netCDFDataset::SetSpatialRefNoUpdate(const OGRSpatialReference *poSRS)
5042 : {
5043 281 : m_oSRS.Clear();
5044 281 : if (poSRS)
5045 274 : m_oSRS = *poSRS;
5046 281 : m_bHasProjection = true;
5047 281 : }
5048 :
5049 : /************************************************************************/
5050 : /* SetSpatialRef() */
5051 : /************************************************************************/
5052 :
5053 82 : CPLErr netCDFDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
5054 : {
5055 164 : CPLMutexHolderD(&hNCMutex);
5056 :
5057 82 : if (GetAccess() != GA_Update || m_bHasProjection)
5058 : {
5059 0 : CPLError(CE_Failure, CPLE_AppDefined,
5060 : "netCDFDataset::_SetProjection() should only be called once "
5061 : "in update mode!");
5062 0 : return CE_Failure;
5063 : }
5064 :
5065 82 : if (m_bHasGeoTransform)
5066 : {
5067 32 : SetSpatialRefNoUpdate(poSRS);
5068 :
5069 : // For NC4/NC4C, writing both projection variables and data,
5070 : // followed by redefining nodata value, cancels the projection
5071 : // info from the Band variable, so for now only write the
5072 : // variable definitions, and write data at the end.
5073 : // See https://trac.osgeo.org/gdal/ticket/7245
5074 32 : return AddProjectionVars(true, nullptr, nullptr);
5075 : }
5076 :
5077 50 : SetSpatialRefNoUpdate(poSRS);
5078 :
5079 50 : return CE_None;
5080 : }
5081 :
5082 : /************************************************************************/
5083 : /* SetGeoTransformNoUpdate() */
5084 : /************************************************************************/
5085 :
5086 295 : void netCDFDataset::SetGeoTransformNoUpdate(const GDALGeoTransform >)
5087 : {
5088 295 : m_gt = gt;
5089 295 : m_bHasGeoTransform = true;
5090 295 : }
5091 :
5092 : /************************************************************************/
5093 : /* SetGeoTransform() */
5094 : /************************************************************************/
5095 :
5096 83 : CPLErr netCDFDataset::SetGeoTransform(const GDALGeoTransform >)
5097 : {
5098 166 : CPLMutexHolderD(&hNCMutex);
5099 :
5100 83 : if (GetAccess() != GA_Update || m_bHasGeoTransform)
5101 : {
5102 0 : CPLError(CE_Failure, CPLE_AppDefined,
5103 : "netCDFDataset::SetGeoTransform() should only be called once "
5104 : "in update mode!");
5105 0 : return CE_Failure;
5106 : }
5107 :
5108 83 : CPLDebug("GDAL_netCDF", "SetGeoTransform(%f,%f,%f,%f,%f,%f)", gt.xorig,
5109 83 : gt.xscale, gt.xrot, gt.yorig, gt.yrot, gt.yscale);
5110 :
5111 83 : SetGeoTransformNoUpdate(gt);
5112 :
5113 83 : if (m_bHasProjection)
5114 : {
5115 :
5116 : // For NC4/NC4C, writing both projection variables and data,
5117 : // followed by redefining nodata value, cancels the projection
5118 : // info from the Band variable, so for now only write the
5119 : // variable definitions, and write data at the end.
5120 : // See https://trac.osgeo.org/gdal/ticket/7245
5121 3 : return AddProjectionVars(true, nullptr, nullptr);
5122 : }
5123 :
5124 80 : return CE_None;
5125 : }
5126 :
5127 : /************************************************************************/
5128 : /* NCDFWriteSRSVariable() */
5129 : /************************************************************************/
5130 :
5131 136 : int NCDFWriteSRSVariable(int cdfid, const OGRSpatialReference *poSRS,
5132 : char **ppszCFProjection, bool bWriteGDALTags,
5133 : const std::string &srsVarName)
5134 : {
5135 136 : char *pszCFProjection = nullptr;
5136 136 : char **papszKeyValues = nullptr;
5137 136 : poSRS->exportToCF1(&pszCFProjection, &papszKeyValues, nullptr, nullptr);
5138 :
5139 136 : if (bWriteGDALTags)
5140 : {
5141 135 : const char *pszWKT = CSLFetchNameValue(papszKeyValues, NCDF_CRS_WKT);
5142 135 : if (pszWKT)
5143 : {
5144 : // SPATIAL_REF is deprecated. Will be removed in GDAL 4.
5145 135 : papszKeyValues =
5146 135 : CSLSetNameValue(papszKeyValues, NCDF_SPATIAL_REF, pszWKT);
5147 : }
5148 : }
5149 :
5150 136 : const int nValues = CSLCount(papszKeyValues);
5151 :
5152 : int NCDFVarID;
5153 272 : std::string varNameRadix(pszCFProjection);
5154 136 : int nCounter = 2;
5155 : while (true)
5156 : {
5157 138 : NCDFVarID = -1;
5158 138 : nc_inq_varid(cdfid, pszCFProjection, &NCDFVarID);
5159 138 : if (NCDFVarID < 0)
5160 133 : break;
5161 :
5162 5 : int nbAttr = 0;
5163 5 : NCDF_ERR(nc_inq_varnatts(cdfid, NCDFVarID, &nbAttr));
5164 5 : bool bSame = nbAttr == nValues;
5165 41 : for (int i = 0; bSame && (i < nbAttr); i++)
5166 : {
5167 : char szAttrName[NC_MAX_NAME + 1];
5168 38 : szAttrName[0] = 0;
5169 38 : NCDF_ERR(nc_inq_attname(cdfid, NCDFVarID, i, szAttrName));
5170 :
5171 : const char *pszValue =
5172 38 : CSLFetchNameValue(papszKeyValues, szAttrName);
5173 38 : if (!pszValue)
5174 : {
5175 0 : bSame = false;
5176 2 : break;
5177 : }
5178 :
5179 38 : nc_type atttype = NC_NAT;
5180 38 : size_t attlen = 0;
5181 38 : NCDF_ERR(
5182 : nc_inq_att(cdfid, NCDFVarID, szAttrName, &atttype, &attlen));
5183 38 : if (atttype != NC_CHAR && atttype != NC_DOUBLE)
5184 : {
5185 0 : bSame = false;
5186 0 : break;
5187 : }
5188 38 : if (atttype == NC_CHAR)
5189 : {
5190 15 : if (CPLGetValueType(pszValue) != CPL_VALUE_STRING)
5191 : {
5192 0 : bSame = false;
5193 0 : break;
5194 : }
5195 15 : std::string val;
5196 15 : val.resize(attlen);
5197 15 : nc_get_att_text(cdfid, NCDFVarID, szAttrName, &val[0]);
5198 15 : if (val != pszValue)
5199 : {
5200 0 : bSame = false;
5201 0 : break;
5202 : }
5203 : }
5204 : else
5205 : {
5206 : const CPLStringList aosTokens(
5207 23 : CSLTokenizeString2(pszValue, ",", 0));
5208 23 : if (static_cast<size_t>(aosTokens.size()) != attlen)
5209 : {
5210 0 : bSame = false;
5211 0 : break;
5212 : }
5213 : double vals[2];
5214 23 : nc_get_att_double(cdfid, NCDFVarID, szAttrName, vals);
5215 44 : if (vals[0] != CPLAtof(aosTokens[0]) ||
5216 21 : (attlen == 2 && vals[1] != CPLAtof(aosTokens[1])))
5217 : {
5218 2 : bSame = false;
5219 2 : break;
5220 : }
5221 : }
5222 : }
5223 5 : if (bSame)
5224 : {
5225 3 : *ppszCFProjection = pszCFProjection;
5226 3 : CSLDestroy(papszKeyValues);
5227 3 : return NCDFVarID;
5228 : }
5229 2 : CPLFree(pszCFProjection);
5230 2 : pszCFProjection =
5231 2 : CPLStrdup(CPLSPrintf("%s_%d", varNameRadix.c_str(), nCounter));
5232 2 : nCounter++;
5233 2 : }
5234 :
5235 133 : *ppszCFProjection = pszCFProjection;
5236 :
5237 : const char *pszVarName;
5238 :
5239 133 : if (srsVarName != "")
5240 : {
5241 38 : pszVarName = srsVarName.c_str();
5242 : }
5243 : else
5244 : {
5245 95 : pszVarName = pszCFProjection;
5246 : }
5247 :
5248 133 : int status = nc_def_var(cdfid, pszVarName, NC_CHAR, 0, nullptr, &NCDFVarID);
5249 133 : NCDF_ERR(status);
5250 1303 : for (int i = 0; i < nValues; ++i)
5251 : {
5252 1170 : char *pszKey = nullptr;
5253 1170 : const char *pszValue = CPLParseNameValue(papszKeyValues[i], &pszKey);
5254 1170 : if (pszKey && pszValue)
5255 : {
5256 2340 : const CPLStringList aosTokens(CSLTokenizeString2(pszValue, ",", 0));
5257 1170 : double adfValues[2] = {0, 0};
5258 1170 : const int nDoubleCount = std::min(2, aosTokens.size());
5259 1170 : if (!(aosTokens.size() == 2 &&
5260 2339 : CPLGetValueType(aosTokens[0]) != CPL_VALUE_STRING) &&
5261 1169 : CPLGetValueType(pszValue) == CPL_VALUE_STRING)
5262 : {
5263 531 : status = nc_put_att_text(cdfid, NCDFVarID, pszKey,
5264 : strlen(pszValue), pszValue);
5265 : }
5266 : else
5267 : {
5268 1279 : for (int j = 0; j < nDoubleCount; ++j)
5269 640 : adfValues[j] = CPLAtof(aosTokens[j]);
5270 639 : status = nc_put_att_double(cdfid, NCDFVarID, pszKey, NC_DOUBLE,
5271 : nDoubleCount, adfValues);
5272 : }
5273 1170 : NCDF_ERR(status);
5274 : }
5275 1170 : CPLFree(pszKey);
5276 : }
5277 :
5278 133 : CSLDestroy(papszKeyValues);
5279 133 : return NCDFVarID;
5280 : }
5281 :
5282 : /************************************************************************/
5283 : /* NCDFWriteLonLatVarsAttributes() */
5284 : /************************************************************************/
5285 :
5286 103 : void NCDFWriteLonLatVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarLonID,
5287 : int nVarLatID)
5288 : {
5289 :
5290 : try
5291 : {
5292 103 : vcdf.nc_put_vatt_text(nVarLatID, CF_STD_NAME, CF_LATITUDE_STD_NAME);
5293 103 : vcdf.nc_put_vatt_text(nVarLatID, CF_LNG_NAME, CF_LATITUDE_LNG_NAME);
5294 103 : vcdf.nc_put_vatt_text(nVarLatID, CF_UNITS, CF_DEGREES_NORTH);
5295 103 : vcdf.nc_put_vatt_text(nVarLonID, CF_STD_NAME, CF_LONGITUDE_STD_NAME);
5296 103 : vcdf.nc_put_vatt_text(nVarLonID, CF_LNG_NAME, CF_LONGITUDE_LNG_NAME);
5297 103 : vcdf.nc_put_vatt_text(nVarLonID, CF_UNITS, CF_DEGREES_EAST);
5298 : }
5299 0 : catch (nccfdriver::SG_Exception &e)
5300 : {
5301 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5302 : }
5303 103 : }
5304 :
5305 : /************************************************************************/
5306 : /* NCDFWriteRLonRLatVarsAttributes() */
5307 : /************************************************************************/
5308 :
5309 0 : void NCDFWriteRLonRLatVarsAttributes(nccfdriver::netCDFVID &vcdf,
5310 : int nVarRLonID, int nVarRLatID)
5311 : {
5312 : try
5313 : {
5314 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_STD_NAME, "grid_latitude");
5315 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_LNG_NAME,
5316 : "latitude in rotated pole grid");
5317 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_UNITS, "degrees");
5318 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_AXIS, "Y");
5319 :
5320 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_STD_NAME, "grid_longitude");
5321 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_LNG_NAME,
5322 : "longitude in rotated pole grid");
5323 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_UNITS, "degrees");
5324 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_AXIS, "X");
5325 : }
5326 0 : catch (nccfdriver::SG_Exception &e)
5327 : {
5328 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5329 : }
5330 0 : }
5331 :
5332 : /************************************************************************/
5333 : /* NCDFGetProjectedCFUnit() */
5334 : /************************************************************************/
5335 :
5336 44 : std::string NCDFGetProjectedCFUnit(const OGRSpatialReference *poSRS)
5337 : {
5338 44 : char *pszUnitsToWrite = nullptr;
5339 44 : poSRS->exportToCF1(nullptr, nullptr, &pszUnitsToWrite, nullptr);
5340 44 : std::string osRet = pszUnitsToWrite ? pszUnitsToWrite : std::string();
5341 44 : CPLFree(pszUnitsToWrite);
5342 88 : return osRet;
5343 : }
5344 :
5345 : /************************************************************************/
5346 : /* NCDFWriteXYVarsAttributes() */
5347 : /************************************************************************/
5348 :
5349 31 : void NCDFWriteXYVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarXID,
5350 : int nVarYID, const OGRSpatialReference *poSRS)
5351 : {
5352 62 : const std::string osUnitsToWrite = NCDFGetProjectedCFUnit(poSRS);
5353 :
5354 : try
5355 : {
5356 31 : vcdf.nc_put_vatt_text(nVarXID, CF_STD_NAME, CF_PROJ_X_COORD);
5357 31 : vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME, CF_PROJ_X_COORD_LONG_NAME);
5358 31 : if (!osUnitsToWrite.empty())
5359 31 : vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, osUnitsToWrite.c_str());
5360 31 : vcdf.nc_put_vatt_text(nVarYID, CF_STD_NAME, CF_PROJ_Y_COORD);
5361 31 : vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME, CF_PROJ_Y_COORD_LONG_NAME);
5362 31 : if (!osUnitsToWrite.empty())
5363 31 : vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, osUnitsToWrite.c_str());
5364 : }
5365 0 : catch (nccfdriver::SG_Exception &e)
5366 : {
5367 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5368 : }
5369 31 : }
5370 :
5371 : /************************************************************************/
5372 : /* AddProjectionVars() */
5373 : /************************************************************************/
5374 :
5375 176 : CPLErr netCDFDataset::AddProjectionVars(bool bDefsOnly,
5376 : GDALProgressFunc pfnProgress,
5377 : void *pProgressData)
5378 : {
5379 176 : if (nCFVersion >= 1.8)
5380 0 : return CE_None; // do nothing
5381 :
5382 176 : bool bWriteGridMapping = false;
5383 176 : bool bWriteLonLat = false;
5384 176 : bool bHasGeoloc = false;
5385 176 : bool bWriteGDALTags = false;
5386 176 : bool bWriteGeoTransform = false;
5387 :
5388 : // For GEOLOCATION information.
5389 176 : GDALDatasetUniquePtr poDS_X;
5390 176 : GDALDatasetUniquePtr poDS_Y;
5391 176 : GDALRasterBand *poBand_X = nullptr;
5392 176 : GDALRasterBand *poBand_Y = nullptr;
5393 :
5394 352 : OGRSpatialReference oSRS(m_oSRS);
5395 176 : if (!m_oSRS.IsEmpty())
5396 : {
5397 150 : if (oSRS.IsProjected())
5398 62 : bIsProjected = true;
5399 88 : else if (oSRS.IsGeographic())
5400 88 : bIsGeographic = true;
5401 : }
5402 :
5403 176 : if (bDefsOnly)
5404 : {
5405 88 : char *pszProjection = nullptr;
5406 88 : m_oSRS.exportToWkt(&pszProjection);
5407 88 : CPLDebug("GDAL_netCDF",
5408 : "SetProjection, WKT now = [%s]\nprojected: %d geographic: %d",
5409 88 : pszProjection ? pszProjection : "(null)",
5410 88 : static_cast<int>(bIsProjected),
5411 88 : static_cast<int>(bIsGeographic));
5412 88 : CPLFree(pszProjection);
5413 :
5414 88 : if (!m_bHasGeoTransform)
5415 5 : CPLDebug("GDAL_netCDF",
5416 : "netCDFDataset::AddProjectionVars() called, "
5417 : "but GeoTransform has not yet been defined!");
5418 :
5419 88 : if (!m_bHasProjection)
5420 6 : CPLDebug("GDAL_netCDF",
5421 : "netCDFDataset::AddProjectionVars() called, "
5422 : "but Projection has not yet been defined!");
5423 : }
5424 :
5425 : // Check GEOLOCATION information.
5426 : CSLConstList papszGeolocationInfo =
5427 176 : netCDFDataset::GetMetadata("GEOLOCATION");
5428 176 : if (papszGeolocationInfo != nullptr)
5429 : {
5430 : // Look for geolocation datasets.
5431 : const char *pszDSName =
5432 10 : CSLFetchNameValue(papszGeolocationInfo, "X_DATASET");
5433 10 : if (pszDSName != nullptr)
5434 10 : poDS_X.reset(GDALDataset::Open(
5435 : pszDSName,
5436 : GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR | GDAL_OF_SHARED));
5437 10 : pszDSName = CSLFetchNameValue(papszGeolocationInfo, "Y_DATASET");
5438 10 : if (pszDSName != nullptr)
5439 10 : poDS_Y.reset(GDALDataset::Open(
5440 : pszDSName,
5441 : GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR | GDAL_OF_SHARED));
5442 :
5443 10 : if (poDS_X != nullptr && poDS_Y != nullptr)
5444 : {
5445 10 : int nBand = std::max(1, atoi(CSLFetchNameValueDef(
5446 10 : papszGeolocationInfo, "X_BAND", "0")));
5447 10 : poBand_X = poDS_X->GetRasterBand(nBand);
5448 10 : nBand = std::max(1, atoi(CSLFetchNameValueDef(papszGeolocationInfo,
5449 10 : "Y_BAND", "0")));
5450 10 : poBand_Y = poDS_Y->GetRasterBand(nBand);
5451 :
5452 : // If geoloc bands are found, do basic validation based on their
5453 : // dimensions.
5454 10 : if (poBand_X != nullptr && poBand_Y != nullptr)
5455 : {
5456 10 : const int nXSize_XBand = poBand_X->GetXSize();
5457 10 : const int nYSize_XBand = poBand_X->GetYSize();
5458 10 : const int nXSize_YBand = poBand_Y->GetXSize();
5459 10 : const int nYSize_YBand = poBand_Y->GetYSize();
5460 :
5461 : // TODO 1D geolocation arrays not implemented.
5462 10 : if (nYSize_XBand == 1 && nYSize_YBand == 1)
5463 : {
5464 0 : bHasGeoloc = false;
5465 0 : CPLDebug("GDAL_netCDF",
5466 : "1D GEOLOCATION arrays not supported yet");
5467 : }
5468 : // 2D bands must have same sizes as the raster bands.
5469 10 : else if (nXSize_XBand != nRasterXSize ||
5470 10 : nYSize_XBand != nRasterYSize ||
5471 10 : nXSize_YBand != nRasterXSize ||
5472 10 : nYSize_YBand != nRasterYSize)
5473 : {
5474 0 : bHasGeoloc = false;
5475 0 : CPLDebug("GDAL_netCDF",
5476 : "GEOLOCATION array sizes (%dx%d %dx%d) differ "
5477 : "from raster (%dx%d), not supported",
5478 : nXSize_XBand, nYSize_XBand, nXSize_YBand,
5479 : nYSize_YBand, nRasterXSize, nRasterYSize);
5480 : }
5481 : else
5482 : {
5483 10 : bHasGeoloc = true;
5484 10 : CPLDebug("GDAL_netCDF",
5485 : "dataset has GEOLOCATION information, will try to "
5486 : "write it");
5487 : }
5488 : }
5489 : }
5490 : }
5491 :
5492 : // Process projection options.
5493 176 : if (bIsProjected)
5494 : {
5495 : bool bIsCfProjection =
5496 62 : oSRS.exportToCF1(nullptr, nullptr, nullptr, nullptr) == OGRERR_NONE;
5497 62 : bWriteGridMapping = true;
5498 62 : bWriteGDALTags = CPL_TO_BOOL(
5499 62 : CSLFetchBoolean(papszCreationOptions, "WRITE_GDAL_TAGS", TRUE));
5500 : // Force WRITE_GDAL_TAGS if is not a CF projection.
5501 62 : if (!bWriteGDALTags && !bIsCfProjection)
5502 0 : bWriteGDALTags = true;
5503 62 : if (bWriteGDALTags)
5504 62 : bWriteGeoTransform = true;
5505 :
5506 : // Write lon/lat: default is NO, except if has geolocation.
5507 : // With IF_NEEDED: write if has geoloc or is not CF projection.
5508 : const char *pszValue =
5509 62 : CSLFetchNameValue(papszCreationOptions, "WRITE_LONLAT");
5510 62 : if (pszValue)
5511 : {
5512 6 : if (EQUAL(pszValue, "IF_NEEDED"))
5513 : {
5514 0 : bWriteLonLat = bHasGeoloc || !bIsCfProjection;
5515 : }
5516 : else
5517 : {
5518 6 : bWriteLonLat = CPLTestBool(pszValue);
5519 : }
5520 : }
5521 : else
5522 : {
5523 56 : bWriteLonLat = bHasGeoloc;
5524 : }
5525 :
5526 : // Save value of pszCFCoordinates for later.
5527 62 : if (bWriteLonLat)
5528 : {
5529 8 : pszCFCoordinates = NCDF_LONLAT;
5530 : }
5531 : }
5532 : else
5533 : {
5534 : // Files without a Datum will not have a grid_mapping variable and
5535 : // geographic information.
5536 114 : bWriteGridMapping = bIsGeographic;
5537 :
5538 114 : if (bHasGeoloc)
5539 : {
5540 8 : bWriteLonLat = true;
5541 : }
5542 : else
5543 : {
5544 106 : bWriteGDALTags = CPL_TO_BOOL(CSLFetchBoolean(
5545 106 : papszCreationOptions, "WRITE_GDAL_TAGS", bWriteGridMapping));
5546 106 : if (bWriteGDALTags)
5547 88 : bWriteGeoTransform = true;
5548 :
5549 106 : const char *pszValue = CSLFetchNameValueDef(papszCreationOptions,
5550 : "WRITE_LONLAT", "YES");
5551 106 : if (EQUAL(pszValue, "IF_NEEDED"))
5552 0 : bWriteLonLat = true;
5553 : else
5554 106 : bWriteLonLat = CPLTestBool(pszValue);
5555 : // Don't write lon/lat if no source geotransform.
5556 106 : if (!m_bHasGeoTransform)
5557 0 : bWriteLonLat = false;
5558 : // If we don't write lon/lat, set dimnames to X/Y and write gdal
5559 : // tags.
5560 106 : if (!bWriteLonLat)
5561 : {
5562 0 : CPLError(CE_Warning, CPLE_AppDefined,
5563 : "creating geographic file without lon/lat values!");
5564 0 : if (m_bHasGeoTransform)
5565 : {
5566 0 : bWriteGDALTags = true; // Not desirable if no geotransform.
5567 0 : bWriteGeoTransform = true;
5568 : }
5569 : }
5570 : }
5571 : }
5572 :
5573 : // Make sure we write grid_mapping if we need to write GDAL tags.
5574 176 : if (bWriteGDALTags)
5575 150 : bWriteGridMapping = true;
5576 :
5577 : // bottom-up value: new driver is bottom-up by default.
5578 : // Override with WRITE_BOTTOMUP.
5579 176 : bBottomUp = CPL_TO_BOOL(
5580 176 : CSLFetchBoolean(papszCreationOptions, "WRITE_BOTTOMUP", TRUE));
5581 :
5582 176 : if (bDefsOnly)
5583 : {
5584 88 : CPLDebug(
5585 : "GDAL_netCDF",
5586 : "bIsProjected=%d bIsGeographic=%d bWriteGridMapping=%d "
5587 : "bWriteGDALTags=%d bWriteLonLat=%d bBottomUp=%d bHasGeoloc=%d",
5588 88 : static_cast<int>(bIsProjected), static_cast<int>(bIsGeographic),
5589 : static_cast<int>(bWriteGridMapping),
5590 : static_cast<int>(bWriteGDALTags), static_cast<int>(bWriteLonLat),
5591 88 : static_cast<int>(bBottomUp), static_cast<int>(bHasGeoloc));
5592 : }
5593 :
5594 : // Exit if nothing to do.
5595 176 : if (!bIsProjected && !bWriteLonLat)
5596 0 : return CE_None;
5597 :
5598 : // Define dimension names.
5599 :
5600 176 : constexpr const char *ROTATED_POLE_VAR_NAME = "rotated_pole";
5601 :
5602 176 : if (bDefsOnly)
5603 : {
5604 88 : int nVarLonID = -1;
5605 88 : int nVarLatID = -1;
5606 88 : int nVarXID = -1;
5607 88 : int nVarYID = -1;
5608 :
5609 88 : m_bAddedProjectionVarsDefs = true;
5610 :
5611 : // Make sure we are in define mode.
5612 88 : SetDefineMode(true);
5613 :
5614 : // Write projection attributes.
5615 88 : if (bWriteGridMapping)
5616 : {
5617 75 : const int NCDFVarID = NCDFWriteSRSVariable(
5618 : cdfid, &oSRS, &pszCFProjection, bWriteGDALTags);
5619 75 : if (NCDFVarID < 0)
5620 0 : return CE_Failure;
5621 :
5622 : // Optional GDAL custom projection tags.
5623 75 : if (bWriteGDALTags && bWriteGeoTransform && m_bHasGeoTransform)
5624 : {
5625 74 : GDALGeoTransform gt(m_gt);
5626 74 : if (!bBottomUp)
5627 : {
5628 : // Change origin from top to bottom and sign of coefficients
5629 : // indexed by row
5630 2 : gt.yorig += nRasterYSize * gt.yscale;
5631 2 : gt.xorig += nRasterYSize * gt.xrot;
5632 2 : gt.xrot = -gt.xrot;
5633 2 : gt.yscale = -gt.yscale;
5634 : }
5635 148 : std::string osGeoTransform = gt.ToString(" ");
5636 74 : CPLDebug("GDAL_netCDF", "szGeoTransform = %s",
5637 : osGeoTransform.c_str());
5638 :
5639 74 : const int status = nc_put_att_text(
5640 : cdfid, NCDFVarID, NCDF_GEOTRANSFORM, osGeoTransform.size(),
5641 : osGeoTransform.c_str());
5642 74 : NCDF_ERR(status);
5643 : }
5644 :
5645 : // Write projection variable to band variable.
5646 : // Need to call later if there are no bands.
5647 75 : AddGridMappingRef();
5648 : } // end if( bWriteGridMapping )
5649 :
5650 : // Write CF Projection vars.
5651 :
5652 88 : const bool bIsRotatedPole =
5653 163 : pszCFProjection != nullptr &&
5654 75 : EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
5655 :
5656 88 : if (m_bHasGeoTransform && !m_gt.IsAxisAligned())
5657 : {
5658 : // Do not write X/Y coordinate arrays
5659 : }
5660 :
5661 84 : else if (bIsRotatedPole)
5662 : {
5663 : // Rename dims to rlat/rlon.
5664 : papszDimName
5665 0 : .Clear(); // If we add other dims one day, this has to change
5666 0 : papszDimName.AddString(NCDF_DIMNAME_RLAT);
5667 0 : papszDimName.AddString(NCDF_DIMNAME_RLON);
5668 :
5669 0 : int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_RLAT);
5670 0 : NCDF_ERR(status);
5671 0 : status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_RLON);
5672 0 : NCDF_ERR(status);
5673 : }
5674 : // Rename dimensions if lon/lat.
5675 84 : else if (!bIsProjected && !bHasGeoloc)
5676 : {
5677 : // Rename dims to lat/lon.
5678 : papszDimName
5679 53 : .Clear(); // If we add other dims one day, this has to change
5680 53 : papszDimName.AddString(NCDF_DIMNAME_LAT);
5681 53 : papszDimName.AddString(NCDF_DIMNAME_LON);
5682 :
5683 53 : int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_LAT);
5684 53 : NCDF_ERR(status);
5685 53 : status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_LON);
5686 53 : NCDF_ERR(status);
5687 : }
5688 :
5689 : // Write X/Y attributes.
5690 : else /* if( bIsProjected || bHasGeoloc ) */
5691 : {
5692 : // X
5693 : int anXDims[1];
5694 31 : anXDims[0] = nXDimID;
5695 31 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
5696 : CF_PROJ_X_VAR_NAME, NC_DOUBLE);
5697 31 : int status = nc_def_var(cdfid, CF_PROJ_X_VAR_NAME, NC_DOUBLE, 1,
5698 : anXDims, &nVarXID);
5699 31 : NCDF_ERR(status);
5700 :
5701 : // Y
5702 : int anYDims[1];
5703 31 : anYDims[0] = nYDimID;
5704 31 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
5705 : CF_PROJ_Y_VAR_NAME, NC_DOUBLE);
5706 31 : status = nc_def_var(cdfid, CF_PROJ_Y_VAR_NAME, NC_DOUBLE, 1,
5707 : anYDims, &nVarYID);
5708 31 : NCDF_ERR(status);
5709 :
5710 31 : if (bIsProjected)
5711 : {
5712 27 : NCDFWriteXYVarsAttributes(this->vcdf, nVarXID, nVarYID, &oSRS);
5713 : }
5714 : else
5715 : {
5716 4 : CPLAssert(bHasGeoloc);
5717 : try
5718 : {
5719 4 : vcdf.nc_put_vatt_text(nVarXID, CF_AXIS, CF_SG_X_AXIS);
5720 4 : vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME,
5721 : "x-coordinate in Cartesian system");
5722 4 : vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, "m");
5723 4 : vcdf.nc_put_vatt_text(nVarYID, CF_AXIS, CF_SG_Y_AXIS);
5724 4 : vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME,
5725 : "y-coordinate in Cartesian system");
5726 4 : vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, "m");
5727 :
5728 4 : pszCFCoordinates = NCDF_LONLAT;
5729 : }
5730 0 : catch (nccfdriver::SG_Exception &e)
5731 : {
5732 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5733 0 : return CE_Failure;
5734 : }
5735 : }
5736 : }
5737 :
5738 : // Write lat/lon attributes if needed.
5739 88 : if (bWriteLonLat)
5740 : {
5741 61 : int anLatDims[2] = {0, 0};
5742 61 : int anLonDims[2] = {0, 0};
5743 61 : int nLatDims = -1;
5744 61 : int nLonDims = -1;
5745 :
5746 : // Get information.
5747 61 : if (bHasGeoloc)
5748 : {
5749 : // Geoloc
5750 5 : nLatDims = 2;
5751 5 : anLatDims[0] = nYDimID;
5752 5 : anLatDims[1] = nXDimID;
5753 5 : nLonDims = 2;
5754 5 : anLonDims[0] = nYDimID;
5755 5 : anLonDims[1] = nXDimID;
5756 : }
5757 56 : else if (bIsProjected)
5758 : {
5759 : // Projected
5760 3 : nLatDims = 2;
5761 3 : anLatDims[0] = nYDimID;
5762 3 : anLatDims[1] = nXDimID;
5763 3 : nLonDims = 2;
5764 3 : anLonDims[0] = nYDimID;
5765 3 : anLonDims[1] = nXDimID;
5766 : }
5767 : else
5768 : {
5769 : // Geographic
5770 53 : nLatDims = 1;
5771 53 : anLatDims[0] = nYDimID;
5772 53 : nLonDims = 1;
5773 53 : anLonDims[0] = nXDimID;
5774 : }
5775 :
5776 61 : nc_type eLonLatType = NC_NAT;
5777 61 : if (bIsProjected)
5778 : {
5779 4 : eLonLatType = NC_FLOAT;
5780 8 : const char *pszValue = CSLFetchNameValueDef(
5781 4 : papszCreationOptions, "TYPE_LONLAT", "FLOAT");
5782 4 : if (EQUAL(pszValue, "DOUBLE"))
5783 0 : eLonLatType = NC_DOUBLE;
5784 : }
5785 : else
5786 : {
5787 57 : eLonLatType = NC_DOUBLE;
5788 114 : const char *pszValue = CSLFetchNameValueDef(
5789 57 : papszCreationOptions, "TYPE_LONLAT", "DOUBLE");
5790 57 : if (EQUAL(pszValue, "FLOAT"))
5791 0 : eLonLatType = NC_FLOAT;
5792 : }
5793 :
5794 : // Def vars and attributes.
5795 : {
5796 61 : const char *pszVarName =
5797 61 : bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME;
5798 61 : int status = nc_def_var(cdfid, pszVarName, eLonLatType,
5799 : nLatDims, anLatDims, &nVarLatID);
5800 61 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
5801 : cdfid, pszVarName, eLonLatType, nLatDims, nVarLatID);
5802 61 : NCDF_ERR(status);
5803 61 : DefVarDeflate(nVarLatID, false); // Don't set chunking.
5804 : }
5805 :
5806 : {
5807 61 : const char *pszVarName =
5808 61 : bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME;
5809 61 : int status = nc_def_var(cdfid, pszVarName, eLonLatType,
5810 : nLonDims, anLonDims, &nVarLonID);
5811 61 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
5812 : cdfid, pszVarName, eLonLatType, nLatDims, nVarLonID);
5813 61 : NCDF_ERR(status);
5814 61 : DefVarDeflate(nVarLonID, false); // Don't set chunking.
5815 : }
5816 :
5817 61 : if (bIsRotatedPole)
5818 0 : NCDFWriteRLonRLatVarsAttributes(this->vcdf, nVarLonID,
5819 : nVarLatID);
5820 : else
5821 61 : NCDFWriteLonLatVarsAttributes(this->vcdf, nVarLonID, nVarLatID);
5822 : }
5823 : }
5824 :
5825 176 : if (!bDefsOnly)
5826 : {
5827 88 : m_bAddedProjectionVarsData = true;
5828 :
5829 88 : int nVarXID = -1;
5830 88 : int nVarYID = -1;
5831 :
5832 88 : nc_inq_varid(cdfid, CF_PROJ_X_VAR_NAME, &nVarXID);
5833 88 : nc_inq_varid(cdfid, CF_PROJ_Y_VAR_NAME, &nVarYID);
5834 :
5835 88 : int nVarLonID = -1;
5836 88 : int nVarLatID = -1;
5837 :
5838 88 : const bool bIsRotatedPole =
5839 163 : pszCFProjection != nullptr &&
5840 75 : EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
5841 88 : nc_inq_varid(cdfid,
5842 : bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME,
5843 : &nVarLonID);
5844 88 : nc_inq_varid(cdfid,
5845 : bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME,
5846 : &nVarLatID);
5847 :
5848 : // Get projection values.
5849 :
5850 88 : if (bIsProjected)
5851 : {
5852 0 : std::unique_ptr<OGRSpatialReference> poLatLonSRS;
5853 0 : std::unique_ptr<OGRCoordinateTransformation> poTransform;
5854 :
5855 : size_t startX[1];
5856 : size_t countX[1];
5857 : size_t startY[1];
5858 : size_t countY[1];
5859 :
5860 31 : CPLDebug("GDAL_netCDF", "Getting (X,Y) values");
5861 :
5862 : std::unique_ptr<double, decltype(&VSIFree)> adXValKeeper(
5863 : static_cast<double *>(
5864 62 : VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
5865 31 : VSIFree);
5866 : std::unique_ptr<double, decltype(&VSIFree)> adYValKeeper(
5867 : static_cast<double *>(
5868 62 : VSI_MALLOC2_VERBOSE(nRasterYSize, sizeof(double))),
5869 31 : VSIFree);
5870 31 : double *padXVal = adXValKeeper.get();
5871 31 : double *padYVal = adYValKeeper.get();
5872 31 : if (!padXVal || !padYVal)
5873 : {
5874 0 : return CE_Failure;
5875 : }
5876 :
5877 : // Make sure we are in data mode.
5878 31 : SetDefineMode(false);
5879 :
5880 31 : int status = NC_NOERR;
5881 :
5882 31 : if (m_gt.IsAxisAligned())
5883 : {
5884 : // Get Y values.
5885 27 : const double dfY0 =
5886 27 : (!bBottomUp) ? m_gt.yorig :
5887 : // Invert latitude values.
5888 27 : m_gt.yorig + (m_gt.yscale * nRasterYSize);
5889 27 : const double dfDY = m_gt.yscale;
5890 :
5891 1478 : for (int j = 0; j < nRasterYSize; j++)
5892 : {
5893 : // The data point is centered inside the pixel.
5894 1451 : if (!bBottomUp)
5895 0 : padYVal[j] = dfY0 + (j + 0.5) * dfDY;
5896 : else // Invert latitude values.
5897 1451 : padYVal[j] = dfY0 - (j + 0.5) * dfDY;
5898 : }
5899 27 : startX[0] = 0;
5900 27 : countX[0] = nRasterXSize;
5901 :
5902 : // Get X values.
5903 27 : const double dfX0 = m_gt.xorig;
5904 27 : const double dfDX = m_gt.xscale;
5905 :
5906 1519 : for (int i = 0; i < nRasterXSize; i++)
5907 : {
5908 : // The data point is centered inside the pixel.
5909 1492 : padXVal[i] = dfX0 + (i + 0.5) * dfDX;
5910 : }
5911 27 : startY[0] = 0;
5912 27 : countY[0] = nRasterYSize;
5913 :
5914 : // Write X/Y values.
5915 :
5916 27 : CPLDebug("GDAL_netCDF", "Writing X values");
5917 : status =
5918 27 : nc_put_vara_double(cdfid, nVarXID, startX, countX, padXVal);
5919 27 : NCDF_ERR(status);
5920 :
5921 27 : CPLDebug("GDAL_netCDF", "Writing Y values");
5922 : status =
5923 27 : nc_put_vara_double(cdfid, nVarYID, startY, countY, padYVal);
5924 27 : NCDF_ERR(status);
5925 : }
5926 :
5927 31 : if (pfnProgress)
5928 27 : pfnProgress(0.20, nullptr, pProgressData);
5929 :
5930 : // Write lon/lat arrays (CF coordinates) if requested.
5931 :
5932 : // Get OGR transform if GEOLOCATION is not available.
5933 31 : if (bWriteLonLat && !bHasGeoloc)
5934 : {
5935 3 : poLatLonSRS.reset(m_oSRS.CloneGeogCS());
5936 3 : if (poLatLonSRS != nullptr)
5937 : {
5938 3 : poLatLonSRS->SetAxisMappingStrategy(
5939 : OAMS_TRADITIONAL_GIS_ORDER);
5940 3 : poTransform.reset(OGRCreateCoordinateTransformation(
5941 3 : &m_oSRS, poLatLonSRS.get()));
5942 : }
5943 : // If no OGR transform, then don't write CF lon/lat.
5944 3 : if (poTransform == nullptr)
5945 : {
5946 0 : CPLError(CE_Failure, CPLE_AppDefined,
5947 : "Unable to get Coordinate Transform");
5948 0 : bWriteLonLat = false;
5949 : }
5950 : }
5951 :
5952 31 : if (bWriteLonLat)
5953 : {
5954 4 : if (!bHasGeoloc)
5955 3 : CPLDebug("GDAL_netCDF", "Transforming (X,Y)->(lon,lat)");
5956 : else
5957 1 : CPLDebug("GDAL_netCDF",
5958 : "Writing (lon,lat) from GEOLOCATION arrays");
5959 :
5960 4 : bool bOK = true;
5961 4 : double dfProgress = 0.2;
5962 :
5963 4 : size_t start[] = {0, 0};
5964 4 : size_t count[] = {1, (size_t)nRasterXSize};
5965 : std::unique_ptr<double, decltype(&VSIFree)> adLatValKeeper(
5966 : static_cast<double *>(
5967 8 : VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
5968 4 : VSIFree);
5969 : std::unique_ptr<double, decltype(&VSIFree)> adLonValKeeper(
5970 : static_cast<double *>(
5971 8 : VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
5972 4 : VSIFree);
5973 4 : double *padLonVal = adLonValKeeper.get();
5974 4 : double *padLatVal = adLatValKeeper.get();
5975 4 : if (!padLonVal || !padLatVal)
5976 : {
5977 0 : return CE_Failure;
5978 : }
5979 :
5980 103 : for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR;
5981 : j++)
5982 : {
5983 99 : start[0] = j;
5984 :
5985 : // Get values from geotransform.
5986 99 : if (!bHasGeoloc)
5987 : {
5988 : // Fill values to transform.
5989 60 : if (m_gt.IsAxisAligned())
5990 : {
5991 420 : for (int i = 0; i < nRasterXSize; i++)
5992 : {
5993 400 : padLatVal[i] = padYVal[j];
5994 400 : padLonVal[i] = padXVal[i];
5995 : }
5996 : }
5997 : else
5998 : {
5999 840 : for (int i = 0; i < nRasterXSize; i++)
6000 : {
6001 800 : if (!bBottomUp)
6002 : {
6003 400 : padLatVal[i] = m_gt.yorig +
6004 400 : (i + 0.5) * m_gt.yrot +
6005 400 : (j + 0.5) * m_gt.yscale;
6006 400 : padLonVal[i] = m_gt.xorig +
6007 400 : (i + 0.5) * m_gt.xscale +
6008 400 : (j + 0.5) * m_gt.xrot;
6009 : }
6010 : else
6011 : {
6012 400 : padLatVal[i] =
6013 400 : m_gt.yorig + (i + 0.5) * m_gt.yrot +
6014 400 : (nRasterYSize - j - 0.5) * m_gt.yscale;
6015 400 : padLonVal[i] =
6016 400 : m_gt.xorig + (i + 0.5) * m_gt.xscale +
6017 400 : (nRasterYSize - j - 0.5) * m_gt.xrot;
6018 : }
6019 : }
6020 : }
6021 :
6022 : // Do the transform.
6023 120 : bOK = CPL_TO_BOOL(poTransform->Transform(
6024 60 : nRasterXSize, padLonVal, padLatVal, nullptr));
6025 60 : if (!bOK)
6026 : {
6027 0 : CPLError(CE_Failure, CPLE_AppDefined,
6028 : "Unable to Transform (X,Y) to (lon,lat).");
6029 : }
6030 : }
6031 : // Get values from geoloc arrays.
6032 : else
6033 : {
6034 39 : CPLErr eErr = poBand_Y->RasterIO(
6035 : GF_Read, 0, j, nRasterXSize, 1, padLatVal,
6036 : nRasterXSize, 1, GDT_Float64, 0, 0, nullptr);
6037 39 : if (eErr == CE_None)
6038 : {
6039 39 : eErr = poBand_X->RasterIO(
6040 : GF_Read, 0, j, nRasterXSize, 1, padLonVal,
6041 : nRasterXSize, 1, GDT_Float64, 0, 0, nullptr);
6042 : }
6043 :
6044 39 : if (eErr == CE_None)
6045 : {
6046 39 : bOK = true;
6047 : }
6048 : else
6049 : {
6050 0 : bOK = false;
6051 0 : CPLError(CE_Failure, CPLE_AppDefined,
6052 : "Unable to get scanline %d", j);
6053 : }
6054 : }
6055 :
6056 : // Write data.
6057 99 : if (bOK)
6058 : {
6059 99 : status = nc_put_vara_double(cdfid, nVarLatID, start,
6060 : count, padLatVal);
6061 99 : NCDF_ERR(status);
6062 99 : status = nc_put_vara_double(cdfid, nVarLonID, start,
6063 : count, padLonVal);
6064 99 : NCDF_ERR(status);
6065 : }
6066 :
6067 99 : if (pfnProgress && (nRasterYSize / 10) > 0 &&
6068 99 : (j % (nRasterYSize / 10) == 0))
6069 : {
6070 43 : dfProgress += 0.08;
6071 43 : pfnProgress(dfProgress, nullptr, pProgressData);
6072 : }
6073 : }
6074 : }
6075 : } // Projected
6076 :
6077 : // If not projected/geographic and has geoloc
6078 57 : else if (!bIsGeographic && bHasGeoloc && m_gt.IsAxisAligned())
6079 : {
6080 : // Use
6081 : // https://cfconventions.org/Data/cf-conventions/cf-conventions-1.9/cf-conventions.html#_two_dimensional_latitude_longitude_coordinate_variables
6082 :
6083 4 : bool bOK = true;
6084 4 : double dfProgress = 0.2;
6085 :
6086 : // Make sure we are in data mode.
6087 4 : SetDefineMode(false);
6088 :
6089 : size_t startX[1];
6090 : size_t countX[1];
6091 : size_t startY[1];
6092 : size_t countY[1];
6093 4 : startX[0] = 0;
6094 4 : countX[0] = nRasterXSize;
6095 :
6096 4 : startY[0] = 0;
6097 4 : countY[0] = nRasterYSize;
6098 :
6099 4 : std::vector<double> adfXVal;
6100 4 : std::vector<double> adfYVal;
6101 : try
6102 : {
6103 4 : adfXVal.resize(nRasterXSize);
6104 4 : adfYVal.resize(nRasterYSize);
6105 : }
6106 0 : catch (const std::exception &)
6107 : {
6108 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
6109 : "Out of memory allocating temporary array");
6110 0 : return CE_Failure;
6111 : }
6112 16 : for (int i = 0; i < nRasterXSize; i++)
6113 12 : adfXVal[i] = i;
6114 12 : for (int i = 0; i < nRasterYSize; i++)
6115 8 : adfYVal[i] = bBottomUp ? nRasterYSize - 1 - i : i;
6116 :
6117 4 : CPLDebug("GDAL_netCDF", "Writing X values");
6118 4 : int status = nc_put_vara_double(cdfid, nVarXID, startX, countX,
6119 4 : adfXVal.data());
6120 4 : NCDF_ERR(status);
6121 :
6122 4 : CPLDebug("GDAL_netCDF", "Writing Y values");
6123 4 : status = nc_put_vara_double(cdfid, nVarYID, startY, countY,
6124 4 : adfYVal.data());
6125 4 : NCDF_ERR(status);
6126 :
6127 4 : if (pfnProgress)
6128 0 : pfnProgress(0.20, nullptr, pProgressData);
6129 :
6130 4 : size_t start[] = {0, 0};
6131 4 : size_t count[] = {1, (size_t)nRasterXSize};
6132 :
6133 : std::unique_ptr<double, decltype(&VSIFree)> adLatValKeeper(
6134 : static_cast<double *>(
6135 8 : VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
6136 4 : VSIFree);
6137 : std::unique_ptr<double, decltype(&VSIFree)> adLonValKeeper(
6138 : static_cast<double *>(
6139 8 : VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
6140 4 : VSIFree);
6141 4 : double *padLonVal = adLonValKeeper.get();
6142 4 : double *padLatVal = adLatValKeeper.get();
6143 4 : if (!padLonVal || !padLatVal)
6144 : {
6145 0 : return CE_Failure;
6146 : }
6147 :
6148 12 : for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR; j++)
6149 : {
6150 8 : start[0] = j;
6151 :
6152 8 : CPLErr eErr = poBand_Y->RasterIO(
6153 8 : GF_Read, 0, bBottomUp ? nRasterYSize - 1 - j : j,
6154 : nRasterXSize, 1, padLatVal, nRasterXSize, 1, GDT_Float64, 0,
6155 : 0, nullptr);
6156 8 : if (eErr == CE_None)
6157 : {
6158 8 : eErr = poBand_X->RasterIO(
6159 8 : GF_Read, 0, bBottomUp ? nRasterYSize - 1 - j : j,
6160 : nRasterXSize, 1, padLonVal, nRasterXSize, 1,
6161 : GDT_Float64, 0, 0, nullptr);
6162 : }
6163 :
6164 8 : if (eErr == CE_None)
6165 : {
6166 8 : bOK = true;
6167 : }
6168 : else
6169 : {
6170 0 : bOK = false;
6171 0 : CPLError(CE_Failure, CPLE_AppDefined,
6172 : "Unable to get scanline %d", j);
6173 : }
6174 :
6175 : // Write data.
6176 8 : if (bOK)
6177 : {
6178 8 : status = nc_put_vara_double(cdfid, nVarLatID, start, count,
6179 : padLatVal);
6180 8 : NCDF_ERR(status);
6181 8 : status = nc_put_vara_double(cdfid, nVarLonID, start, count,
6182 : padLonVal);
6183 8 : NCDF_ERR(status);
6184 : }
6185 :
6186 8 : if (pfnProgress && (nRasterYSize / 10) > 0 &&
6187 0 : (j % (nRasterYSize / 10) == 0))
6188 : {
6189 0 : dfProgress += 0.08;
6190 0 : pfnProgress(dfProgress, nullptr, pProgressData);
6191 : }
6192 : }
6193 : }
6194 :
6195 : // If not projected, assume geographic to catch grids without Datum.
6196 53 : else if (bWriteLonLat)
6197 : {
6198 : // Get latitude values.
6199 53 : const double dfY0 = (!bBottomUp) ? m_gt.yorig :
6200 : // Invert latitude values.
6201 53 : m_gt.yorig + (m_gt.yscale * nRasterYSize);
6202 53 : const double dfDY = m_gt.yscale;
6203 :
6204 : std::unique_ptr<double, decltype(&VSIFree)> adLatValKeeper(nullptr,
6205 53 : VSIFree);
6206 53 : double *padLatVal = nullptr;
6207 : // Override lat values with the ones in GEOLOCATION/Y_VALUES.
6208 53 : if (netCDFDataset::GetMetadataItem("Y_VALUES", "GEOLOCATION") !=
6209 : nullptr)
6210 : {
6211 0 : int nTemp = 0;
6212 0 : adLatValKeeper.reset(Get1DGeolocation("Y_VALUES", nTemp));
6213 0 : padLatVal = adLatValKeeper.get();
6214 : // Make sure we got the correct amount, if not fallback to GT */
6215 : // could add test fabs(fabs(padLatVal[0]) - fabs(dfY0)) <= 0.1))
6216 0 : if (nTemp == nRasterYSize)
6217 : {
6218 0 : CPLDebug(
6219 : "GDAL_netCDF",
6220 : "Using Y_VALUES geolocation metadata for lat values");
6221 : }
6222 : else
6223 : {
6224 0 : CPLDebug("GDAL_netCDF",
6225 : "Got %d elements from Y_VALUES geolocation "
6226 : "metadata, need %d",
6227 : nTemp, nRasterYSize);
6228 0 : padLatVal = nullptr;
6229 : }
6230 : }
6231 :
6232 53 : if (padLatVal == nullptr)
6233 : {
6234 53 : adLatValKeeper.reset(static_cast<double *>(
6235 53 : VSI_MALLOC2_VERBOSE(nRasterYSize, sizeof(double))));
6236 53 : padLatVal = adLatValKeeper.get();
6237 53 : if (!padLatVal)
6238 : {
6239 0 : return CE_Failure;
6240 : }
6241 7105 : for (int i = 0; i < nRasterYSize; i++)
6242 : {
6243 : // The data point is centered inside the pixel.
6244 7052 : if (!bBottomUp)
6245 0 : padLatVal[i] = dfY0 + (i + 0.5) * dfDY;
6246 : else // Invert latitude values.
6247 7052 : padLatVal[i] = dfY0 - (i + 0.5) * dfDY;
6248 : }
6249 : }
6250 :
6251 53 : size_t startLat[1] = {0};
6252 53 : size_t countLat[1] = {static_cast<size_t>(nRasterYSize)};
6253 :
6254 : // Get longitude values.
6255 53 : const double dfX0 = m_gt.xorig;
6256 53 : const double dfDX = m_gt.xscale;
6257 :
6258 : std::unique_ptr<double, decltype(&VSIFree)> adLonValKeeper(
6259 : static_cast<double *>(
6260 106 : VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
6261 53 : VSIFree);
6262 53 : double *padLonVal = adLonValKeeper.get();
6263 53 : if (!padLonVal)
6264 : {
6265 0 : return CE_Failure;
6266 : }
6267 7157 : for (int i = 0; i < nRasterXSize; i++)
6268 : {
6269 : // The data point is centered inside the pixel.
6270 7104 : padLonVal[i] = dfX0 + (i + 0.5) * dfDX;
6271 : }
6272 :
6273 53 : size_t startLon[1] = {0};
6274 53 : size_t countLon[1] = {static_cast<size_t>(nRasterXSize)};
6275 :
6276 : // Write latitude and longitude values.
6277 :
6278 : // Make sure we are in data mode.
6279 53 : SetDefineMode(false);
6280 :
6281 : // Write values.
6282 53 : CPLDebug("GDAL_netCDF", "Writing lat values");
6283 :
6284 53 : int status = nc_put_vara_double(cdfid, nVarLatID, startLat,
6285 : countLat, padLatVal);
6286 53 : NCDF_ERR(status);
6287 :
6288 53 : CPLDebug("GDAL_netCDF", "Writing lon values");
6289 53 : status = nc_put_vara_double(cdfid, nVarLonID, startLon, countLon,
6290 : padLonVal);
6291 53 : NCDF_ERR(status);
6292 :
6293 : } // Not projected.
6294 :
6295 88 : if (pfnProgress)
6296 47 : pfnProgress(1.00, nullptr, pProgressData);
6297 : }
6298 :
6299 176 : return CE_None;
6300 : }
6301 :
6302 : // Write Projection variable to band variable.
6303 : // Moved from AddProjectionVars() for cases when bands are added after
6304 : // projection.
6305 447 : bool netCDFDataset::AddGridMappingRef()
6306 : {
6307 447 : bool bRet = true;
6308 447 : bool bOldDefineMode = bDefineMode;
6309 :
6310 650 : if ((GetAccess() == GA_Update) && (nBands >= 1) && (GetRasterBand(1)) &&
6311 203 : ((pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, "")) ||
6312 195 : (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))))
6313 : {
6314 79 : bAddedGridMappingRef = true;
6315 :
6316 : // Make sure we are in define mode.
6317 79 : SetDefineMode(true);
6318 :
6319 204 : for (int i = 1; i <= nBands; i++)
6320 : {
6321 : const int nVarId =
6322 125 : cpl::down_cast<netCDFRasterBand *>(GetRasterBand(i))->nZId;
6323 :
6324 125 : if (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))
6325 : {
6326 : int status =
6327 242 : nc_put_att_text(cdfid, nVarId, CF_GRD_MAPPING,
6328 121 : strlen(pszCFProjection), pszCFProjection);
6329 121 : NCDF_ERR(status);
6330 121 : if (status != NC_NOERR)
6331 0 : bRet = false;
6332 : }
6333 125 : if (pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, ""))
6334 : {
6335 : int status =
6336 8 : nc_put_att_text(cdfid, nVarId, CF_COORDINATES,
6337 : strlen(pszCFCoordinates), pszCFCoordinates);
6338 8 : NCDF_ERR(status);
6339 8 : if (status != NC_NOERR)
6340 0 : bRet = false;
6341 : }
6342 : }
6343 :
6344 : // Go back to previous define mode.
6345 79 : SetDefineMode(bOldDefineMode);
6346 : }
6347 447 : return bRet;
6348 : }
6349 :
6350 : /************************************************************************/
6351 : /* GetGeoTransform() */
6352 : /************************************************************************/
6353 :
6354 129 : CPLErr netCDFDataset::GetGeoTransform(GDALGeoTransform >) const
6355 :
6356 : {
6357 129 : gt = m_gt;
6358 129 : if (m_bHasGeoTransform)
6359 97 : return CE_None;
6360 :
6361 32 : return GDALPamDataset::GetGeoTransform(gt);
6362 : }
6363 :
6364 : /************************************************************************/
6365 : /* rint() */
6366 : /************************************************************************/
6367 :
6368 0 : double netCDFDataset::rint(double dfX)
6369 : {
6370 0 : return std::round(dfX);
6371 : }
6372 :
6373 : /************************************************************************/
6374 : /* NCDFReadIsoMetadata() */
6375 : /************************************************************************/
6376 :
6377 16 : static void NCDFReadMetadataAsJson(int cdfid, CPLJSONObject &obj)
6378 : {
6379 16 : int nbAttr = 0;
6380 16 : NCDF_ERR(nc_inq_varnatts(cdfid, NC_GLOBAL, &nbAttr));
6381 :
6382 32 : std::map<std::string, CPLJSONArray> oMapNameToArray;
6383 40 : for (int l = 0; l < nbAttr; l++)
6384 : {
6385 : char szAttrName[NC_MAX_NAME + 1];
6386 24 : szAttrName[0] = 0;
6387 24 : NCDF_ERR(nc_inq_attname(cdfid, NC_GLOBAL, l, szAttrName));
6388 :
6389 24 : char *pszMetaValue = nullptr;
6390 24 : if (NCDFGetAttr(cdfid, NC_GLOBAL, szAttrName, &pszMetaValue) == CE_None)
6391 : {
6392 24 : nc_type nAttrType = NC_NAT;
6393 24 : size_t nAttrLen = 0;
6394 :
6395 24 : NCDF_ERR(nc_inq_att(cdfid, NC_GLOBAL, szAttrName, &nAttrType,
6396 : &nAttrLen));
6397 :
6398 24 : std::string osAttrName(szAttrName);
6399 24 : const auto sharpPos = osAttrName.find('#');
6400 24 : if (sharpPos == std::string::npos)
6401 : {
6402 16 : if (nAttrType == NC_DOUBLE || nAttrType == NC_FLOAT)
6403 4 : obj.Add(osAttrName, CPLAtof(pszMetaValue));
6404 : else
6405 12 : obj.Add(osAttrName, pszMetaValue);
6406 : }
6407 : else
6408 : {
6409 8 : osAttrName.resize(sharpPos);
6410 8 : auto iter = oMapNameToArray.find(osAttrName);
6411 8 : if (iter == oMapNameToArray.end())
6412 : {
6413 8 : CPLJSONArray array;
6414 4 : obj.Add(osAttrName, array);
6415 4 : oMapNameToArray[osAttrName] = array;
6416 4 : array.Add(pszMetaValue);
6417 : }
6418 : else
6419 : {
6420 4 : iter->second.Add(pszMetaValue);
6421 : }
6422 : }
6423 24 : CPLFree(pszMetaValue);
6424 24 : pszMetaValue = nullptr;
6425 : }
6426 : }
6427 :
6428 16 : int nSubGroups = 0;
6429 16 : int *panSubGroupIds = nullptr;
6430 16 : NCDFGetSubGroups(cdfid, &nSubGroups, &panSubGroupIds);
6431 16 : oMapNameToArray.clear();
6432 28 : for (int i = 0; i < nSubGroups; i++)
6433 : {
6434 24 : CPLJSONObject subObj;
6435 12 : NCDFReadMetadataAsJson(panSubGroupIds[i], subObj);
6436 :
6437 24 : std::string osGroupName;
6438 12 : osGroupName.resize(NC_MAX_NAME);
6439 12 : NCDF_ERR(nc_inq_grpname(panSubGroupIds[i], &osGroupName[0]));
6440 12 : osGroupName.resize(strlen(osGroupName.data()));
6441 12 : const auto sharpPos = osGroupName.find('#');
6442 12 : if (sharpPos == std::string::npos)
6443 : {
6444 4 : obj.Add(osGroupName, subObj);
6445 : }
6446 : else
6447 : {
6448 8 : osGroupName.resize(sharpPos);
6449 8 : auto iter = oMapNameToArray.find(osGroupName);
6450 8 : if (iter == oMapNameToArray.end())
6451 : {
6452 8 : CPLJSONArray array;
6453 4 : obj.Add(osGroupName, array);
6454 4 : oMapNameToArray[osGroupName] = array;
6455 4 : array.Add(subObj);
6456 : }
6457 : else
6458 : {
6459 4 : iter->second.Add(subObj);
6460 : }
6461 : }
6462 : }
6463 16 : CPLFree(panSubGroupIds);
6464 16 : }
6465 :
6466 4 : std::string NCDFReadMetadataAsJson(int cdfid)
6467 : {
6468 8 : CPLJSONDocument oDoc;
6469 8 : CPLJSONObject oRoot = oDoc.GetRoot();
6470 4 : NCDFReadMetadataAsJson(cdfid, oRoot);
6471 8 : return oDoc.SaveAsString();
6472 : }
6473 :
6474 : /************************************************************************/
6475 : /* ReadAttributes() */
6476 : /************************************************************************/
6477 1896 : CPLErr netCDFDataset::ReadAttributes(int cdfidIn, int var)
6478 :
6479 : {
6480 1896 : char *pszVarFullName = nullptr;
6481 1896 : ERR_RET(NCDFGetVarFullName(cdfidIn, var, &pszVarFullName));
6482 :
6483 : // For metadata in Sentinel 5
6484 1896 : if (STARTS_WITH(pszVarFullName, "/METADATA/"))
6485 : {
6486 6 : for (const char *key :
6487 : {"ISO_METADATA", "ESA_METADATA", "EOP_METADATA", "QA_STATISTICS",
6488 8 : "GRANULE_DESCRIPTION", "ALGORITHM_SETTINGS"})
6489 : {
6490 14 : if (var == NC_GLOBAL &&
6491 7 : strcmp(pszVarFullName,
6492 : CPLSPrintf("/METADATA/%s/NC_GLOBAL", key)) == 0)
6493 : {
6494 1 : CPLFree(pszVarFullName);
6495 1 : CPLStringList aosList;
6496 2 : aosList.AddString(CPLString(NCDFReadMetadataAsJson(cdfidIn))
6497 1 : .replaceAll("\\/", '/'));
6498 1 : m_oMapDomainToJSon[key] = std::move(aosList);
6499 1 : return CE_None;
6500 : }
6501 : }
6502 : }
6503 1895 : if (STARTS_WITH(pszVarFullName, "/PRODUCT/SUPPORT_DATA/"))
6504 : {
6505 0 : CPLFree(pszVarFullName);
6506 0 : CPLStringList aosList;
6507 : aosList.AddString(
6508 0 : CPLString(NCDFReadMetadataAsJson(cdfidIn)).replaceAll("\\/", '/'));
6509 0 : m_oMapDomainToJSon["SUPPORT_DATA"] = std::move(aosList);
6510 0 : return CE_None;
6511 : }
6512 :
6513 1895 : size_t nMetaNameSize =
6514 1895 : sizeof(char) * (strlen(pszVarFullName) + 1 + NC_MAX_NAME + 1);
6515 1895 : char *pszMetaName = static_cast<char *>(CPLMalloc(nMetaNameSize));
6516 :
6517 1895 : int nbAttr = 0;
6518 1895 : NCDF_ERR(nc_inq_varnatts(cdfidIn, var, &nbAttr));
6519 :
6520 9609 : for (int l = 0; l < nbAttr; l++)
6521 : {
6522 : char szAttrName[NC_MAX_NAME + 1];
6523 7714 : szAttrName[0] = 0;
6524 7714 : NCDF_ERR(nc_inq_attname(cdfidIn, var, l, szAttrName));
6525 7714 : snprintf(pszMetaName, nMetaNameSize, "%s#%s", pszVarFullName,
6526 : szAttrName);
6527 :
6528 7714 : char *pszMetaTemp = nullptr;
6529 7714 : if (NCDFGetAttr(cdfidIn, var, szAttrName, &pszMetaTemp) == CE_None)
6530 : {
6531 7713 : papszMetadata =
6532 7713 : CSLSetNameValue(papszMetadata, pszMetaName, pszMetaTemp);
6533 7713 : CPLFree(pszMetaTemp);
6534 7713 : pszMetaTemp = nullptr;
6535 : }
6536 : else
6537 : {
6538 1 : CPLDebug("GDAL_netCDF", "invalid metadata %s", pszMetaName);
6539 : }
6540 : }
6541 :
6542 1895 : CPLFree(pszVarFullName);
6543 1895 : CPLFree(pszMetaName);
6544 :
6545 1895 : if (var == NC_GLOBAL)
6546 : {
6547 : // Recurse on sub-groups.
6548 545 : int nSubGroups = 0;
6549 545 : int *panSubGroupIds = nullptr;
6550 545 : NCDFGetSubGroups(cdfidIn, &nSubGroups, &panSubGroupIds);
6551 577 : for (int i = 0; i < nSubGroups; i++)
6552 : {
6553 32 : ReadAttributes(panSubGroupIds[i], var);
6554 : }
6555 545 : CPLFree(panSubGroupIds);
6556 : }
6557 :
6558 1895 : return CE_None;
6559 : }
6560 :
6561 : /************************************************************************/
6562 : /* netCDFDataset::CreateSubDatasetList() */
6563 : /************************************************************************/
6564 59 : void netCDFDataset::CreateSubDatasetList(int nGroupId)
6565 : {
6566 : char szVarStdName[NC_MAX_NAME + 1];
6567 59 : int *ponDimIds = nullptr;
6568 : nc_type nAttype;
6569 : size_t nAttlen;
6570 :
6571 59 : netCDFDataset *poDS = this;
6572 :
6573 : int nVarCount;
6574 59 : nc_inq_nvars(nGroupId, &nVarCount);
6575 :
6576 59 : const bool bListAllArrays = CPLTestBool(
6577 59 : CSLFetchNameValueDef(papszOpenOptions, "LIST_ALL_ARRAYS", "NO"));
6578 :
6579 358 : for (int nVar = 0; nVar < nVarCount; nVar++)
6580 : {
6581 :
6582 : int nDims;
6583 299 : nc_inq_varndims(nGroupId, nVar, &nDims);
6584 :
6585 299 : if ((bListAllArrays && nDims > 0) || nDims >= 2)
6586 : {
6587 174 : ponDimIds = static_cast<int *>(CPLCalloc(nDims, sizeof(int)));
6588 174 : nc_inq_vardimid(nGroupId, nVar, ponDimIds);
6589 :
6590 : // Create Sub dataset list.
6591 174 : CPLString osDim;
6592 535 : for (int i = 0; i < nDims; i++)
6593 : {
6594 : size_t nDimLen;
6595 361 : nc_inq_dimlen(nGroupId, ponDimIds[i], &nDimLen);
6596 361 : if (!osDim.empty())
6597 187 : osDim += 'x';
6598 361 : osDim += CPLSPrintf("%d", (int)nDimLen);
6599 : }
6600 174 : CPLFree(ponDimIds);
6601 :
6602 : nc_type nVarType;
6603 174 : nc_inq_vartype(nGroupId, nVar, &nVarType);
6604 174 : const char *pszType = "";
6605 174 : switch (nVarType)
6606 : {
6607 42 : case NC_BYTE:
6608 42 : pszType = "8-bit integer";
6609 42 : break;
6610 2 : case NC_CHAR:
6611 2 : pszType = "8-bit character";
6612 2 : break;
6613 6 : case NC_SHORT:
6614 6 : pszType = "16-bit integer";
6615 6 : break;
6616 10 : case NC_INT:
6617 10 : pszType = "32-bit integer";
6618 10 : break;
6619 62 : case NC_FLOAT:
6620 62 : pszType = "32-bit floating-point";
6621 62 : break;
6622 34 : case NC_DOUBLE:
6623 34 : pszType = "64-bit floating-point";
6624 34 : break;
6625 4 : case NC_UBYTE:
6626 4 : pszType = "8-bit unsigned integer";
6627 4 : break;
6628 1 : case NC_USHORT:
6629 1 : pszType = "16-bit unsigned integer";
6630 1 : break;
6631 1 : case NC_UINT:
6632 1 : pszType = "32-bit unsigned integer";
6633 1 : break;
6634 1 : case NC_INT64:
6635 1 : pszType = "64-bit integer";
6636 1 : break;
6637 1 : case NC_UINT64:
6638 1 : pszType = "64-bit unsigned integer";
6639 1 : break;
6640 10 : default:
6641 10 : break;
6642 : }
6643 :
6644 174 : char *pszName = nullptr;
6645 174 : if (NCDFGetVarFullName(nGroupId, nVar, &pszName) != CE_None)
6646 0 : continue;
6647 :
6648 174 : nSubDatasets++;
6649 :
6650 174 : nAttlen = 0;
6651 174 : nc_inq_att(nGroupId, nVar, CF_STD_NAME, &nAttype, &nAttlen);
6652 348 : if (nAttlen < sizeof(szVarStdName) &&
6653 174 : nc_get_att_text(nGroupId, nVar, CF_STD_NAME, szVarStdName) ==
6654 : NC_NOERR)
6655 : {
6656 64 : szVarStdName[nAttlen] = '\0';
6657 : }
6658 : else
6659 : {
6660 110 : snprintf(szVarStdName, sizeof(szVarStdName), "%s", pszName);
6661 : }
6662 :
6663 : char szTemp[NC_MAX_NAME + 1];
6664 174 : snprintf(szTemp, sizeof(szTemp), "SUBDATASET_%d_NAME",
6665 : nSubDatasets);
6666 :
6667 174 : if (strchr(pszName, ' ') || strchr(pszName, ':'))
6668 : {
6669 1 : poDS->papszSubDatasets = CSLSetNameValue(
6670 : poDS->papszSubDatasets, szTemp,
6671 : CPLSPrintf("NETCDF:\"%s\":\"%s\"", poDS->osFilename.c_str(),
6672 : pszName));
6673 : }
6674 : else
6675 : {
6676 173 : poDS->papszSubDatasets = CSLSetNameValue(
6677 : poDS->papszSubDatasets, szTemp,
6678 : CPLSPrintf("NETCDF:\"%s\":%s", poDS->osFilename.c_str(),
6679 : pszName));
6680 : }
6681 :
6682 174 : CPLFree(pszName);
6683 :
6684 174 : snprintf(szTemp, sizeof(szTemp), "SUBDATASET_%d_DESC",
6685 : nSubDatasets);
6686 :
6687 174 : poDS->papszSubDatasets =
6688 174 : CSLSetNameValue(poDS->papszSubDatasets, szTemp,
6689 : CPLSPrintf("[%s] %s (%s)", osDim.c_str(),
6690 : szVarStdName, pszType));
6691 : }
6692 : }
6693 :
6694 : // Recurse on sub groups.
6695 59 : int nSubGroups = 0;
6696 59 : int *panSubGroupIds = nullptr;
6697 59 : NCDFGetSubGroups(nGroupId, &nSubGroups, &panSubGroupIds);
6698 66 : for (int i = 0; i < nSubGroups; i++)
6699 : {
6700 7 : CreateSubDatasetList(panSubGroupIds[i]);
6701 : }
6702 59 : CPLFree(panSubGroupIds);
6703 59 : }
6704 :
6705 : /************************************************************************/
6706 : /* TestCapability() */
6707 : /************************************************************************/
6708 :
6709 249 : int netCDFDataset::TestCapability(const char *pszCap) const
6710 : {
6711 249 : if (EQUAL(pszCap, ODsCCreateLayer))
6712 : {
6713 225 : return eAccess == GA_Update && nBands == 0 &&
6714 219 : (eMultipleLayerBehavior != SINGLE_LAYER ||
6715 230 : this->GetLayerCount() == 0 || bSGSupport);
6716 : }
6717 136 : else if (EQUAL(pszCap, ODsCZGeometries))
6718 2 : return true;
6719 :
6720 134 : return false;
6721 : }
6722 :
6723 : /************************************************************************/
6724 : /* GetLayer() */
6725 : /************************************************************************/
6726 :
6727 385 : const OGRLayer *netCDFDataset::GetLayer(int nIdx) const
6728 : {
6729 385 : if (nIdx < 0 || nIdx >= this->GetLayerCount())
6730 2 : return nullptr;
6731 383 : return papoLayers[nIdx].get();
6732 : }
6733 :
6734 : /************************************************************************/
6735 : /* ICreateLayer() */
6736 : /************************************************************************/
6737 :
6738 60 : OGRLayer *netCDFDataset::ICreateLayer(const char *pszName,
6739 : const OGRGeomFieldDefn *poGeomFieldDefn,
6740 : CSLConstList papszOptions)
6741 : {
6742 60 : int nLayerCDFId = cdfid;
6743 60 : if (!TestCapability(ODsCCreateLayer))
6744 0 : return nullptr;
6745 :
6746 60 : const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
6747 : const auto poSpatialRef =
6748 60 : poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
6749 :
6750 120 : CPLString osNetCDFLayerName(pszName);
6751 60 : const netCDFWriterConfigLayer *poLayerConfig = nullptr;
6752 60 : if (oWriterConfig.m_bIsValid)
6753 : {
6754 : std::map<CPLString, netCDFWriterConfigLayer>::const_iterator
6755 2 : oLayerIter = oWriterConfig.m_oLayers.find(pszName);
6756 2 : if (oLayerIter != oWriterConfig.m_oLayers.end())
6757 : {
6758 1 : poLayerConfig = &(oLayerIter->second);
6759 1 : osNetCDFLayerName = poLayerConfig->m_osNetCDFName;
6760 : }
6761 : }
6762 :
6763 60 : netCDFDataset *poLayerDataset = nullptr;
6764 60 : if (eMultipleLayerBehavior == SEPARATE_FILES)
6765 : {
6766 3 : if (CPLLaunderForFilenameSafe(osNetCDFLayerName.c_str(), nullptr) !=
6767 : osNetCDFLayerName)
6768 : {
6769 1 : CPLError(CE_Failure, CPLE_AppDefined,
6770 : "Illegal characters in '%s' to form a valid filename",
6771 : osNetCDFLayerName.c_str());
6772 1 : return nullptr;
6773 : }
6774 2 : CPLStringList aosDatasetOptions;
6775 : aosDatasetOptions.SetNameValue(
6776 : "CONFIG_FILE",
6777 2 : CSLFetchNameValue(papszCreationOptions, "CONFIG_FILE"));
6778 : aosDatasetOptions.SetNameValue(
6779 2 : "FORMAT", CSLFetchNameValue(papszCreationOptions, "FORMAT"));
6780 : aosDatasetOptions.SetNameValue(
6781 : "WRITE_GDAL_TAGS",
6782 2 : CSLFetchNameValue(papszCreationOptions, "WRITE_GDAL_TAGS"));
6783 : const CPLString osLayerFilename(
6784 2 : CPLFormFilenameSafe(osFilename, osNetCDFLayerName, "nc"));
6785 2 : CPLAcquireMutex(hNCMutex, 1000.0);
6786 2 : poLayerDataset =
6787 2 : CreateLL(osLayerFilename, 0, 0, 0, aosDatasetOptions.List());
6788 2 : CPLReleaseMutex(hNCMutex);
6789 2 : if (poLayerDataset == nullptr)
6790 0 : return nullptr;
6791 :
6792 2 : nLayerCDFId = poLayerDataset->cdfid;
6793 2 : NCDFAddGDALHistory(nLayerCDFId, osLayerFilename, bWriteGDALVersion,
6794 2 : bWriteGDALHistory, "", "Create",
6795 : NCDF_CONVENTIONS_CF_V1_6);
6796 : }
6797 57 : else if (eMultipleLayerBehavior == SEPARATE_GROUPS)
6798 : {
6799 2 : SetDefineMode(true);
6800 :
6801 2 : nLayerCDFId = -1;
6802 2 : int status = nc_def_grp(cdfid, osNetCDFLayerName, &nLayerCDFId);
6803 2 : NCDF_ERR(status);
6804 2 : if (status != NC_NOERR)
6805 0 : return nullptr;
6806 :
6807 2 : NCDFAddGDALHistory(nLayerCDFId, osFilename, bWriteGDALVersion,
6808 2 : bWriteGDALHistory, "", "Create",
6809 : NCDF_CONVENTIONS_CF_V1_6);
6810 : }
6811 :
6812 : // Make a clone to workaround a bug in released MapServer versions
6813 : // that destroys the passed SRS instead of releasing it .
6814 59 : OGRSpatialReference *poSRS = nullptr;
6815 59 : if (poSpatialRef)
6816 : {
6817 43 : poSRS = poSpatialRef->Clone();
6818 43 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
6819 : }
6820 : std::shared_ptr<netCDFLayer> poLayer(
6821 59 : new netCDFLayer(poLayerDataset ? poLayerDataset : this, nLayerCDFId,
6822 118 : osNetCDFLayerName, eGType, poSRS));
6823 59 : if (poSRS != nullptr)
6824 43 : poSRS->Release();
6825 :
6826 : // Fetch layer creation options coming from config file
6827 118 : CPLStringList aosNewOptions(CSLDuplicate(papszOptions));
6828 59 : if (oWriterConfig.m_bIsValid)
6829 : {
6830 2 : for (const auto &[osName, osValue] :
6831 4 : oWriterConfig.m_oLayerCreationOptions)
6832 : {
6833 1 : aosNewOptions.SetNameValue(osName, osValue);
6834 : }
6835 2 : if (poLayerConfig != nullptr)
6836 : {
6837 4 : for (const auto &[osName, osValue] :
6838 5 : poLayerConfig->m_oLayerCreationOptions)
6839 : {
6840 2 : aosNewOptions.SetNameValue(osName, osValue);
6841 : }
6842 : }
6843 : }
6844 :
6845 59 : const bool bRet = poLayer->Create(aosNewOptions.List(), poLayerConfig);
6846 :
6847 59 : if (!bRet)
6848 : {
6849 0 : return nullptr;
6850 : }
6851 :
6852 59 : if (poLayerDataset != nullptr)
6853 2 : apoVectorDatasets.push_back(poLayerDataset);
6854 :
6855 59 : papoLayers.push_back(poLayer);
6856 59 : return poLayer.get();
6857 : }
6858 :
6859 : /************************************************************************/
6860 : /* CloneAttributes() */
6861 : /************************************************************************/
6862 :
6863 137 : bool netCDFDataset::CloneAttributes(int old_cdfid, int new_cdfid, int nSrcVarId,
6864 : int nDstVarId)
6865 : {
6866 137 : int nAttCount = -1;
6867 137 : int status = nc_inq_varnatts(old_cdfid, nSrcVarId, &nAttCount);
6868 137 : NCDF_ERR(status);
6869 :
6870 693 : for (int i = 0; i < nAttCount; i++)
6871 : {
6872 : char szName[NC_MAX_NAME + 1];
6873 556 : szName[0] = 0;
6874 556 : status = nc_inq_attname(old_cdfid, nSrcVarId, i, szName);
6875 556 : NCDF_ERR(status);
6876 :
6877 : status =
6878 556 : nc_copy_att(old_cdfid, nSrcVarId, szName, new_cdfid, nDstVarId);
6879 556 : NCDF_ERR(status);
6880 556 : if (status != NC_NOERR)
6881 0 : return false;
6882 : }
6883 :
6884 137 : return true;
6885 : }
6886 :
6887 : /************************************************************************/
6888 : /* CloneVariableContent() */
6889 : /************************************************************************/
6890 :
6891 121 : bool netCDFDataset::CloneVariableContent(int old_cdfid, int new_cdfid,
6892 : int nSrcVarId, int nDstVarId)
6893 : {
6894 121 : int nVarDimCount = -1;
6895 121 : int status = nc_inq_varndims(old_cdfid, nSrcVarId, &nVarDimCount);
6896 121 : NCDF_ERR(status);
6897 121 : int anDimIds[] = {-1, 1};
6898 121 : status = nc_inq_vardimid(old_cdfid, nSrcVarId, anDimIds);
6899 121 : NCDF_ERR(status);
6900 121 : nc_type nc_datatype = NC_NAT;
6901 121 : status = nc_inq_vartype(old_cdfid, nSrcVarId, &nc_datatype);
6902 121 : NCDF_ERR(status);
6903 121 : size_t nTypeSize = 0;
6904 121 : switch (nc_datatype)
6905 : {
6906 35 : case NC_BYTE:
6907 : case NC_CHAR:
6908 35 : nTypeSize = 1;
6909 35 : break;
6910 4 : case NC_SHORT:
6911 4 : nTypeSize = 2;
6912 4 : break;
6913 24 : case NC_INT:
6914 24 : nTypeSize = 4;
6915 24 : break;
6916 4 : case NC_FLOAT:
6917 4 : nTypeSize = 4;
6918 4 : break;
6919 43 : case NC_DOUBLE:
6920 43 : nTypeSize = 8;
6921 43 : break;
6922 2 : case NC_UBYTE:
6923 2 : nTypeSize = 1;
6924 2 : break;
6925 2 : case NC_USHORT:
6926 2 : nTypeSize = 2;
6927 2 : break;
6928 2 : case NC_UINT:
6929 2 : nTypeSize = 4;
6930 2 : break;
6931 4 : case NC_INT64:
6932 : case NC_UINT64:
6933 4 : nTypeSize = 8;
6934 4 : break;
6935 1 : case NC_STRING:
6936 1 : nTypeSize = sizeof(char *);
6937 1 : break;
6938 0 : default:
6939 : {
6940 0 : CPLError(CE_Failure, CPLE_NotSupported, "Unsupported data type: %d",
6941 : nc_datatype);
6942 0 : return false;
6943 : }
6944 : }
6945 :
6946 121 : size_t nElems = 1;
6947 : size_t anStart[NC_MAX_DIMS];
6948 : size_t anCount[NC_MAX_DIMS];
6949 121 : size_t nRecords = 1;
6950 261 : for (int i = 0; i < nVarDimCount; i++)
6951 : {
6952 140 : anStart[i] = 0;
6953 140 : if (i == 0)
6954 : {
6955 116 : anCount[i] = 1;
6956 116 : status = nc_inq_dimlen(old_cdfid, anDimIds[i], &nRecords);
6957 116 : NCDF_ERR(status);
6958 : }
6959 : else
6960 : {
6961 24 : anCount[i] = 0;
6962 24 : status = nc_inq_dimlen(old_cdfid, anDimIds[i], &anCount[i]);
6963 24 : NCDF_ERR(status);
6964 24 : nElems *= anCount[i];
6965 : }
6966 : }
6967 :
6968 : /* Workaround in some cases a netCDF bug:
6969 : * https://github.com/Unidata/netcdf-c/pull/1442 */
6970 121 : if (nRecords > 0 && nRecords < 10 * 1000 * 1000 / (nElems * nTypeSize))
6971 : {
6972 119 : nElems *= nRecords;
6973 119 : anCount[0] = nRecords;
6974 119 : nRecords = 1;
6975 : }
6976 :
6977 121 : void *pBuffer = VSI_MALLOC2_VERBOSE(nElems, nTypeSize);
6978 121 : if (pBuffer == nullptr)
6979 0 : return false;
6980 :
6981 240 : for (size_t iRecord = 0; iRecord < nRecords; iRecord++)
6982 : {
6983 119 : anStart[0] = iRecord;
6984 :
6985 119 : switch (nc_datatype)
6986 : {
6987 5 : case NC_BYTE:
6988 : status =
6989 5 : nc_get_vara_schar(old_cdfid, nSrcVarId, anStart, anCount,
6990 : static_cast<signed char *>(pBuffer));
6991 5 : if (!status)
6992 5 : status = nc_put_vara_schar(
6993 : new_cdfid, nDstVarId, anStart, anCount,
6994 : static_cast<signed char *>(pBuffer));
6995 5 : break;
6996 28 : case NC_CHAR:
6997 : status =
6998 28 : nc_get_vara_text(old_cdfid, nSrcVarId, anStart, anCount,
6999 : static_cast<char *>(pBuffer));
7000 28 : if (!status)
7001 : status =
7002 28 : nc_put_vara_text(new_cdfid, nDstVarId, anStart, anCount,
7003 : static_cast<char *>(pBuffer));
7004 28 : break;
7005 4 : case NC_SHORT:
7006 : status =
7007 4 : nc_get_vara_short(old_cdfid, nSrcVarId, anStart, anCount,
7008 : static_cast<short *>(pBuffer));
7009 4 : if (!status)
7010 4 : status = nc_put_vara_short(new_cdfid, nDstVarId, anStart,
7011 : anCount,
7012 : static_cast<short *>(pBuffer));
7013 4 : break;
7014 24 : case NC_INT:
7015 24 : status = nc_get_vara_int(old_cdfid, nSrcVarId, anStart, anCount,
7016 : static_cast<int *>(pBuffer));
7017 24 : if (!status)
7018 : status =
7019 24 : nc_put_vara_int(new_cdfid, nDstVarId, anStart, anCount,
7020 : static_cast<int *>(pBuffer));
7021 24 : break;
7022 4 : case NC_FLOAT:
7023 : status =
7024 4 : nc_get_vara_float(old_cdfid, nSrcVarId, anStart, anCount,
7025 : static_cast<float *>(pBuffer));
7026 4 : if (!status)
7027 4 : status = nc_put_vara_float(new_cdfid, nDstVarId, anStart,
7028 : anCount,
7029 : static_cast<float *>(pBuffer));
7030 4 : break;
7031 43 : case NC_DOUBLE:
7032 : status =
7033 43 : nc_get_vara_double(old_cdfid, nSrcVarId, anStart, anCount,
7034 : static_cast<double *>(pBuffer));
7035 43 : if (!status)
7036 43 : status = nc_put_vara_double(new_cdfid, nDstVarId, anStart,
7037 : anCount,
7038 : static_cast<double *>(pBuffer));
7039 43 : break;
7040 1 : case NC_STRING:
7041 : status =
7042 1 : nc_get_vara_string(old_cdfid, nSrcVarId, anStart, anCount,
7043 : static_cast<char **>(pBuffer));
7044 1 : if (!status)
7045 : {
7046 1 : status = nc_put_vara_string(
7047 : new_cdfid, nDstVarId, anStart, anCount,
7048 : static_cast<const char **>(pBuffer));
7049 1 : nc_free_string(nElems, static_cast<char **>(pBuffer));
7050 : }
7051 1 : break;
7052 :
7053 2 : case NC_UBYTE:
7054 : status =
7055 2 : nc_get_vara_uchar(old_cdfid, nSrcVarId, anStart, anCount,
7056 : static_cast<unsigned char *>(pBuffer));
7057 2 : if (!status)
7058 2 : status = nc_put_vara_uchar(
7059 : new_cdfid, nDstVarId, anStart, anCount,
7060 : static_cast<unsigned char *>(pBuffer));
7061 2 : break;
7062 2 : case NC_USHORT:
7063 : status =
7064 2 : nc_get_vara_ushort(old_cdfid, nSrcVarId, anStart, anCount,
7065 : static_cast<unsigned short *>(pBuffer));
7066 2 : if (!status)
7067 2 : status = nc_put_vara_ushort(
7068 : new_cdfid, nDstVarId, anStart, anCount,
7069 : static_cast<unsigned short *>(pBuffer));
7070 2 : break;
7071 2 : case NC_UINT:
7072 : status =
7073 2 : nc_get_vara_uint(old_cdfid, nSrcVarId, anStart, anCount,
7074 : static_cast<unsigned int *>(pBuffer));
7075 2 : if (!status)
7076 : status =
7077 2 : nc_put_vara_uint(new_cdfid, nDstVarId, anStart, anCount,
7078 : static_cast<unsigned int *>(pBuffer));
7079 2 : break;
7080 2 : case NC_INT64:
7081 : status =
7082 2 : nc_get_vara_longlong(old_cdfid, nSrcVarId, anStart, anCount,
7083 : static_cast<long long *>(pBuffer));
7084 2 : if (!status)
7085 2 : status = nc_put_vara_longlong(
7086 : new_cdfid, nDstVarId, anStart, anCount,
7087 : static_cast<long long *>(pBuffer));
7088 2 : break;
7089 2 : case NC_UINT64:
7090 2 : status = nc_get_vara_ulonglong(
7091 : old_cdfid, nSrcVarId, anStart, anCount,
7092 : static_cast<unsigned long long *>(pBuffer));
7093 2 : if (!status)
7094 2 : status = nc_put_vara_ulonglong(
7095 : new_cdfid, nDstVarId, anStart, anCount,
7096 : static_cast<unsigned long long *>(pBuffer));
7097 2 : break;
7098 0 : default:
7099 0 : status = NC_EBADTYPE;
7100 : }
7101 :
7102 119 : NCDF_ERR(status);
7103 119 : if (status != NC_NOERR)
7104 : {
7105 0 : VSIFree(pBuffer);
7106 0 : return false;
7107 : }
7108 : }
7109 :
7110 121 : VSIFree(pBuffer);
7111 121 : return true;
7112 : }
7113 :
7114 : /************************************************************************/
7115 : /* NCDFIsUnlimitedDim() */
7116 : /************************************************************************/
7117 :
7118 58 : bool NCDFIsUnlimitedDim(bool bIsNC4, int cdfid, int nDimId)
7119 : {
7120 58 : if (bIsNC4)
7121 : {
7122 16 : int nUnlimitedDims = 0;
7123 16 : nc_inq_unlimdims(cdfid, &nUnlimitedDims, nullptr);
7124 16 : bool bFound = false;
7125 16 : if (nUnlimitedDims)
7126 : {
7127 : int *panUnlimitedDimIds =
7128 16 : static_cast<int *>(CPLMalloc(sizeof(int) * nUnlimitedDims));
7129 16 : nc_inq_unlimdims(cdfid, nullptr, panUnlimitedDimIds);
7130 30 : for (int i = 0; i < nUnlimitedDims; i++)
7131 : {
7132 22 : if (panUnlimitedDimIds[i] == nDimId)
7133 : {
7134 8 : bFound = true;
7135 8 : break;
7136 : }
7137 : }
7138 16 : CPLFree(panUnlimitedDimIds);
7139 : }
7140 16 : return bFound;
7141 : }
7142 : else
7143 : {
7144 42 : int nUnlimitedDimId = -1;
7145 42 : nc_inq(cdfid, nullptr, nullptr, nullptr, &nUnlimitedDimId);
7146 42 : return nDimId == nUnlimitedDimId;
7147 : }
7148 : }
7149 :
7150 : /************************************************************************/
7151 : /* CloneGrp() */
7152 : /************************************************************************/
7153 :
7154 16 : bool netCDFDataset::CloneGrp(int nOldGrpId, int nNewGrpId, bool bIsNC4,
7155 : int nLayerId, int nDimIdToGrow, size_t nNewSize)
7156 : {
7157 : // Clone dimensions
7158 16 : int nDimCount = -1;
7159 16 : int status = nc_inq_ndims(nOldGrpId, &nDimCount);
7160 16 : NCDF_ERR(status);
7161 16 : if (nDimCount < 0 || nDimCount > NC_MAX_DIMS)
7162 0 : return false;
7163 : int anDimIds[NC_MAX_DIMS];
7164 16 : int nUnlimiDimID = -1;
7165 16 : status = nc_inq_unlimdim(nOldGrpId, &nUnlimiDimID);
7166 16 : NCDF_ERR(status);
7167 16 : if (bIsNC4)
7168 : {
7169 : // In NC4, the dimension ids of a group are not necessarily in
7170 : // [0,nDimCount-1] range
7171 8 : int nDimCount2 = -1;
7172 8 : status = nc_inq_dimids(nOldGrpId, &nDimCount2, anDimIds, FALSE);
7173 8 : NCDF_ERR(status);
7174 8 : CPLAssert(nDimCount == nDimCount2);
7175 : }
7176 : else
7177 : {
7178 36 : for (int i = 0; i < nDimCount; i++)
7179 28 : anDimIds[i] = i;
7180 : }
7181 60 : for (int i = 0; i < nDimCount; i++)
7182 : {
7183 : char szDimName[NC_MAX_NAME + 1];
7184 44 : szDimName[0] = 0;
7185 44 : size_t nLen = 0;
7186 44 : const int nDimId = anDimIds[i];
7187 44 : status = nc_inq_dim(nOldGrpId, nDimId, szDimName, &nLen);
7188 44 : NCDF_ERR(status);
7189 44 : if (NCDFIsUnlimitedDim(bIsNC4, nOldGrpId, nDimId))
7190 16 : nLen = NC_UNLIMITED;
7191 28 : else if (nDimId == nDimIdToGrow && nOldGrpId == nLayerId)
7192 13 : nLen = nNewSize;
7193 44 : int nNewDimId = -1;
7194 44 : status = nc_def_dim(nNewGrpId, szDimName, nLen, &nNewDimId);
7195 44 : NCDF_ERR(status);
7196 44 : CPLAssert(nDimId == nNewDimId);
7197 44 : if (status != NC_NOERR)
7198 : {
7199 0 : return false;
7200 : }
7201 : }
7202 :
7203 : // Clone main attributes
7204 16 : if (!CloneAttributes(nOldGrpId, nNewGrpId, NC_GLOBAL, NC_GLOBAL))
7205 : {
7206 0 : return false;
7207 : }
7208 :
7209 : // Clone variable definitions
7210 16 : int nVarCount = -1;
7211 16 : status = nc_inq_nvars(nOldGrpId, &nVarCount);
7212 16 : NCDF_ERR(status);
7213 :
7214 137 : for (int i = 0; i < nVarCount; i++)
7215 : {
7216 : char szVarName[NC_MAX_NAME + 1];
7217 121 : szVarName[0] = 0;
7218 121 : status = nc_inq_varname(nOldGrpId, i, szVarName);
7219 121 : NCDF_ERR(status);
7220 121 : nc_type nc_datatype = NC_NAT;
7221 121 : status = nc_inq_vartype(nOldGrpId, i, &nc_datatype);
7222 121 : NCDF_ERR(status);
7223 121 : int nVarDimCount = -1;
7224 121 : status = nc_inq_varndims(nOldGrpId, i, &nVarDimCount);
7225 121 : NCDF_ERR(status);
7226 121 : status = nc_inq_vardimid(nOldGrpId, i, anDimIds);
7227 121 : NCDF_ERR(status);
7228 121 : int nNewVarId = -1;
7229 121 : status = nc_def_var(nNewGrpId, szVarName, nc_datatype, nVarDimCount,
7230 : anDimIds, &nNewVarId);
7231 121 : NCDF_ERR(status);
7232 121 : CPLAssert(i == nNewVarId);
7233 121 : if (status != NC_NOERR)
7234 : {
7235 0 : return false;
7236 : }
7237 :
7238 121 : if (!CloneAttributes(nOldGrpId, nNewGrpId, i, i))
7239 : {
7240 0 : return false;
7241 : }
7242 : }
7243 :
7244 16 : status = nc_enddef(nNewGrpId);
7245 16 : NCDF_ERR(status);
7246 16 : if (status != NC_NOERR)
7247 : {
7248 0 : return false;
7249 : }
7250 :
7251 : // Clone variable content
7252 137 : for (int i = 0; i < nVarCount; i++)
7253 : {
7254 121 : if (!CloneVariableContent(nOldGrpId, nNewGrpId, i, i))
7255 : {
7256 0 : return false;
7257 : }
7258 : }
7259 :
7260 16 : return true;
7261 : }
7262 :
7263 : /************************************************************************/
7264 : /* GrowDim() */
7265 : /************************************************************************/
7266 :
7267 13 : bool netCDFDataset::GrowDim(int nLayerId, int nDimIdToGrow, size_t nNewSize)
7268 : {
7269 : int nCreationMode;
7270 : // Set nCreationMode based on eFormat.
7271 13 : switch (eFormat)
7272 : {
7273 : #ifdef NETCDF_HAS_NC2
7274 0 : case NCDF_FORMAT_NC2:
7275 0 : nCreationMode = NC_CLOBBER | NC_64BIT_OFFSET;
7276 0 : break;
7277 : #endif
7278 5 : case NCDF_FORMAT_NC4:
7279 5 : nCreationMode = NC_CLOBBER | NC_NETCDF4;
7280 5 : break;
7281 0 : case NCDF_FORMAT_NC4C:
7282 0 : nCreationMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
7283 0 : break;
7284 8 : case NCDF_FORMAT_NC:
7285 : default:
7286 8 : nCreationMode = NC_CLOBBER;
7287 8 : break;
7288 : }
7289 :
7290 13 : int new_cdfid = -1;
7291 26 : CPLString osTmpFilename(osFilename + ".tmp");
7292 26 : CPLString osFilenameForNCCreate(osTmpFilename);
7293 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
7294 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
7295 : {
7296 : char *pszTemp =
7297 : CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
7298 : osFilenameForNCCreate = pszTemp;
7299 : CPLFree(pszTemp);
7300 : }
7301 : #endif
7302 13 : int status = nc_create(osFilenameForNCCreate, nCreationMode, &new_cdfid);
7303 13 : NCDF_ERR(status);
7304 13 : if (status != NC_NOERR)
7305 0 : return false;
7306 :
7307 13 : if (!CloneGrp(cdfid, new_cdfid, eFormat == NCDF_FORMAT_NC4, nLayerId,
7308 : nDimIdToGrow, nNewSize))
7309 : {
7310 0 : GDAL_nc_close(new_cdfid);
7311 0 : return false;
7312 : }
7313 :
7314 13 : int nGroupCount = 0;
7315 26 : std::vector<CPLString> oListGrpName;
7316 31 : if (eFormat == NCDF_FORMAT_NC4 &&
7317 18 : nc_inq_grps(cdfid, &nGroupCount, nullptr) == NC_NOERR &&
7318 5 : nGroupCount > 0)
7319 : {
7320 : int *panGroupIds =
7321 2 : static_cast<int *>(CPLMalloc(sizeof(int) * nGroupCount));
7322 2 : status = nc_inq_grps(cdfid, nullptr, panGroupIds);
7323 2 : NCDF_ERR(status);
7324 5 : for (int i = 0; i < nGroupCount; i++)
7325 : {
7326 : char szGroupName[NC_MAX_NAME + 1];
7327 3 : szGroupName[0] = 0;
7328 3 : NCDF_ERR(nc_inq_grpname(panGroupIds[i], szGroupName));
7329 3 : int nNewGrpId = -1;
7330 3 : status = nc_def_grp(new_cdfid, szGroupName, &nNewGrpId);
7331 3 : NCDF_ERR(status);
7332 3 : if (status != NC_NOERR)
7333 : {
7334 0 : CPLFree(panGroupIds);
7335 0 : GDAL_nc_close(new_cdfid);
7336 0 : return false;
7337 : }
7338 3 : if (!CloneGrp(panGroupIds[i], nNewGrpId, /*bIsNC4=*/true, nLayerId,
7339 : nDimIdToGrow, nNewSize))
7340 : {
7341 0 : CPLFree(panGroupIds);
7342 0 : GDAL_nc_close(new_cdfid);
7343 0 : return false;
7344 : }
7345 : }
7346 2 : CPLFree(panGroupIds);
7347 :
7348 5 : for (int i = 0; i < this->GetLayerCount(); i++)
7349 : {
7350 3 : auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
7351 3 : if (poLayer)
7352 : {
7353 : char szGroupName[NC_MAX_NAME + 1];
7354 3 : szGroupName[0] = 0;
7355 3 : status = nc_inq_grpname(poLayer->GetCDFID(), szGroupName);
7356 3 : NCDF_ERR(status);
7357 3 : oListGrpName.push_back(szGroupName);
7358 : }
7359 : }
7360 : }
7361 :
7362 13 : GDAL_nc_close(cdfid);
7363 13 : cdfid = -1;
7364 13 : GDAL_nc_close(new_cdfid);
7365 :
7366 26 : CPLString osOriFilename(osFilename + ".ori");
7367 26 : if (VSIRename(osFilename, osOriFilename) != 0 ||
7368 13 : VSIRename(osTmpFilename, osFilename) != 0)
7369 : {
7370 0 : CPLError(CE_Failure, CPLE_FileIO, "Renaming of files failed");
7371 0 : return false;
7372 : }
7373 13 : VSIUnlink(osOriFilename);
7374 :
7375 26 : CPLString osFilenameForNCOpen(osFilename);
7376 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
7377 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
7378 : {
7379 : char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
7380 : osFilenameForNCOpen = pszTemp;
7381 : CPLFree(pszTemp);
7382 : }
7383 : #endif
7384 13 : status = GDAL_nc_open(osFilenameForNCOpen, NC_WRITE, &cdfid);
7385 13 : NCDF_ERR(status);
7386 13 : if (status != NC_NOERR)
7387 0 : return false;
7388 13 : bDefineMode = false;
7389 :
7390 13 : if (!oListGrpName.empty())
7391 : {
7392 5 : for (int i = 0; i < this->GetLayerCount(); i++)
7393 : {
7394 3 : auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
7395 3 : if (poLayer)
7396 : {
7397 3 : int nNewLayerCDFID = -1;
7398 3 : status = nc_inq_ncid(cdfid, oListGrpName[i].c_str(),
7399 : &nNewLayerCDFID);
7400 3 : NCDF_ERR(status);
7401 3 : poLayer->SetCDFID(nNewLayerCDFID);
7402 : }
7403 : }
7404 : }
7405 : else
7406 : {
7407 22 : for (int i = 0; i < this->GetLayerCount(); i++)
7408 : {
7409 11 : auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
7410 11 : if (poLayer)
7411 11 : poLayer->SetCDFID(cdfid);
7412 : }
7413 : }
7414 :
7415 13 : return true;
7416 : }
7417 :
7418 : #ifdef ENABLE_NCDUMP
7419 :
7420 : /************************************************************************/
7421 : /* netCDFDatasetCreateTempFile() */
7422 : /************************************************************************/
7423 :
7424 : /* Create a netCDF file from a text dump (format of ncdump) */
7425 : /* Mostly to easy fuzzing of the driver, while still generating valid */
7426 : /* netCDF files. */
7427 : /* Note: not all data types are supported ! */
7428 4 : bool netCDFDatasetCreateTempFile(NetCDFFormatEnum eFormat,
7429 : const char *pszTmpFilename, VSILFILE *fpSrc)
7430 : {
7431 4 : CPL_IGNORE_RET_VAL(eFormat);
7432 4 : int nCreateMode = NC_CLOBBER;
7433 4 : if (eFormat == NCDF_FORMAT_NC4)
7434 1 : nCreateMode |= NC_NETCDF4;
7435 3 : else if (eFormat == NCDF_FORMAT_NC4C)
7436 0 : nCreateMode |= NC_NETCDF4 | NC_CLASSIC_MODEL;
7437 4 : int nCdfId = -1;
7438 4 : int status = nc_create(pszTmpFilename, nCreateMode, &nCdfId);
7439 4 : if (status != NC_NOERR)
7440 : {
7441 0 : return false;
7442 : }
7443 4 : VSIFSeekL(fpSrc, 0, SEEK_SET);
7444 : const char *pszLine;
7445 4 : constexpr int SECTION_NONE = 0;
7446 4 : constexpr int SECTION_DIMENSIONS = 1;
7447 4 : constexpr int SECTION_VARIABLES = 2;
7448 4 : constexpr int SECTION_DATA = 3;
7449 4 : int nActiveSection = SECTION_NONE;
7450 8 : std::map<CPLString, int> oMapDimToId;
7451 8 : std::map<int, int> oMapDimIdToDimLen;
7452 8 : std::map<CPLString, int> oMapVarToId;
7453 8 : std::map<int, std::vector<int>> oMapVarIdToVectorOfDimId;
7454 8 : std::map<int, int> oMapVarIdToType;
7455 4 : std::set<CPLString> oSetAttrDefined;
7456 4 : oMapVarToId[""] = -1;
7457 4 : size_t nTotalVarSize = 0;
7458 208 : while ((pszLine = CPLReadLineL(fpSrc)) != nullptr)
7459 : {
7460 204 : if (STARTS_WITH(pszLine, "dimensions:") &&
7461 : nActiveSection == SECTION_NONE)
7462 : {
7463 4 : nActiveSection = SECTION_DIMENSIONS;
7464 : }
7465 200 : else if (STARTS_WITH(pszLine, "variables:") &&
7466 : nActiveSection == SECTION_DIMENSIONS)
7467 : {
7468 4 : nActiveSection = SECTION_VARIABLES;
7469 : }
7470 196 : else if (STARTS_WITH(pszLine, "data:") &&
7471 : nActiveSection == SECTION_VARIABLES)
7472 : {
7473 4 : nActiveSection = SECTION_DATA;
7474 4 : status = nc_enddef(nCdfId);
7475 4 : if (status != NC_NOERR)
7476 : {
7477 0 : CPLDebug("netCDF", "nc_enddef() failed: %s",
7478 : nc_strerror(status));
7479 : }
7480 : }
7481 192 : else if (nActiveSection == SECTION_DIMENSIONS)
7482 : {
7483 : const CPLStringList aosTokens(
7484 9 : CSLTokenizeString2(pszLine, " \t=;", 0));
7485 9 : if (aosTokens.size() == 2)
7486 : {
7487 9 : const char *pszDimName = aosTokens[0];
7488 9 : bool bValidName = true;
7489 9 : if (STARTS_WITH(pszDimName, "_nc4_non_coord_"))
7490 : {
7491 : // This is an internal netcdf prefix. Using it may
7492 : // cause memory leaks.
7493 0 : bValidName = false;
7494 : }
7495 9 : if (!bValidName)
7496 : {
7497 0 : CPLDebug("netCDF",
7498 : "nc_def_dim(%s) failed: invalid name found",
7499 : pszDimName);
7500 0 : continue;
7501 : }
7502 :
7503 : const bool bIsASCII =
7504 9 : CPLIsASCII(pszDimName, static_cast<size_t>(-1));
7505 9 : if (!bIsASCII)
7506 : {
7507 : // Workaround https://github.com/Unidata/netcdf-c/pull/450
7508 0 : CPLDebug("netCDF",
7509 : "nc_def_dim(%s) failed: rejected because "
7510 : "of non-ASCII characters",
7511 : pszDimName);
7512 0 : continue;
7513 : }
7514 9 : int nDimSize = EQUAL(aosTokens[1], "UNLIMITED")
7515 : ? NC_UNLIMITED
7516 9 : : atoi(aosTokens[1]);
7517 9 : if (nDimSize >= 1000)
7518 1 : nDimSize = 1000; // to avoid very long processing
7519 9 : if (nDimSize >= 0)
7520 : {
7521 9 : int nDimId = -1;
7522 9 : status = nc_def_dim(nCdfId, pszDimName, nDimSize, &nDimId);
7523 9 : if (status != NC_NOERR)
7524 : {
7525 0 : CPLDebug("netCDF", "nc_def_dim(%s, %d) failed: %s",
7526 : pszDimName, nDimSize, nc_strerror(status));
7527 : }
7528 : else
7529 : {
7530 : #ifdef DEBUG_VERBOSE
7531 : CPLDebug("netCDF", "nc_def_dim(%s, %d) (%s) succeeded",
7532 : pszDimName, nDimSize, pszLine);
7533 : #endif
7534 9 : oMapDimToId[pszDimName] = nDimId;
7535 9 : oMapDimIdToDimLen[nDimId] = nDimSize;
7536 : }
7537 : }
7538 : }
7539 : }
7540 183 : else if (nActiveSection == SECTION_VARIABLES)
7541 : {
7542 390 : while (*pszLine == ' ' || *pszLine == '\t')
7543 249 : pszLine++;
7544 141 : const char *pszColumn = strchr(pszLine, ':');
7545 141 : const char *pszEqual = strchr(pszLine, '=');
7546 141 : if (pszColumn == nullptr)
7547 : {
7548 : const CPLStringList aosTokens(
7549 21 : CSLTokenizeString2(pszLine, " \t=(),;", 0));
7550 21 : if (aosTokens.size() >= 2)
7551 : {
7552 17 : const char *pszVarName = aosTokens[1];
7553 17 : bool bValidName = true;
7554 17 : if (STARTS_WITH(pszVarName, "_nc4_non_coord_"))
7555 : {
7556 : // This is an internal netcdf prefix. Using it may
7557 : // cause memory leaks.
7558 0 : bValidName = false;
7559 : }
7560 138 : for (int i = 0; pszVarName[i]; i++)
7561 : {
7562 121 : if (!((pszVarName[i] >= 'a' && pszVarName[i] <= 'z') ||
7563 28 : (pszVarName[i] >= 'A' && pszVarName[i] <= 'Z') ||
7564 9 : (pszVarName[i] >= '0' && pszVarName[i] <= '9') ||
7565 6 : pszVarName[i] == '_'))
7566 : {
7567 0 : bValidName = false;
7568 : }
7569 : }
7570 17 : if (!bValidName)
7571 : {
7572 0 : CPLDebug(
7573 : "netCDF",
7574 : "nc_def_var(%s) failed: illegal character found",
7575 : pszVarName);
7576 0 : continue;
7577 : }
7578 17 : if (oMapVarToId.find(pszVarName) != oMapVarToId.end())
7579 : {
7580 0 : CPLDebug("netCDF",
7581 : "nc_def_var(%s) failed: already defined",
7582 : pszVarName);
7583 0 : continue;
7584 : }
7585 17 : const char *pszVarType = aosTokens[0];
7586 17 : int nc_datatype = NC_BYTE;
7587 17 : size_t nDataTypeSize = 1;
7588 17 : if (EQUAL(pszVarType, "char"))
7589 : {
7590 6 : nc_datatype = NC_CHAR;
7591 6 : nDataTypeSize = 1;
7592 : }
7593 11 : else if (EQUAL(pszVarType, "byte"))
7594 : {
7595 3 : nc_datatype = NC_BYTE;
7596 3 : nDataTypeSize = 1;
7597 : }
7598 8 : else if (EQUAL(pszVarType, "short"))
7599 : {
7600 0 : nc_datatype = NC_SHORT;
7601 0 : nDataTypeSize = 2;
7602 : }
7603 8 : else if (EQUAL(pszVarType, "int"))
7604 : {
7605 0 : nc_datatype = NC_INT;
7606 0 : nDataTypeSize = 4;
7607 : }
7608 8 : else if (EQUAL(pszVarType, "float"))
7609 : {
7610 0 : nc_datatype = NC_FLOAT;
7611 0 : nDataTypeSize = 4;
7612 : }
7613 8 : else if (EQUAL(pszVarType, "double"))
7614 : {
7615 8 : nc_datatype = NC_DOUBLE;
7616 8 : nDataTypeSize = 8;
7617 : }
7618 0 : else if (EQUAL(pszVarType, "ubyte"))
7619 : {
7620 0 : nc_datatype = NC_UBYTE;
7621 0 : nDataTypeSize = 1;
7622 : }
7623 0 : else if (EQUAL(pszVarType, "ushort"))
7624 : {
7625 0 : nc_datatype = NC_USHORT;
7626 0 : nDataTypeSize = 2;
7627 : }
7628 0 : else if (EQUAL(pszVarType, "uint"))
7629 : {
7630 0 : nc_datatype = NC_UINT;
7631 0 : nDataTypeSize = 4;
7632 : }
7633 0 : else if (EQUAL(pszVarType, "int64"))
7634 : {
7635 0 : nc_datatype = NC_INT64;
7636 0 : nDataTypeSize = 8;
7637 : }
7638 0 : else if (EQUAL(pszVarType, "uint64"))
7639 : {
7640 0 : nc_datatype = NC_UINT64;
7641 0 : nDataTypeSize = 8;
7642 : }
7643 :
7644 17 : int nDims = aosTokens.size() - 2;
7645 17 : if (nDims >= 32)
7646 : {
7647 : // The number of dimensions in a netCDFv4 file is
7648 : // limited by #define H5S_MAX_RANK 32
7649 : // but libnetcdf doesn't check that...
7650 0 : CPLDebug("netCDF",
7651 : "nc_def_var(%s) failed: too many dimensions",
7652 : pszVarName);
7653 0 : continue;
7654 : }
7655 17 : std::vector<int> aoDimIds;
7656 17 : bool bFailed = false;
7657 17 : size_t nSize = 1;
7658 35 : for (int i = 0; i < nDims; i++)
7659 : {
7660 18 : const char *pszDimName = aosTokens[2 + i];
7661 18 : if (oMapDimToId.find(pszDimName) == oMapDimToId.end())
7662 : {
7663 0 : bFailed = true;
7664 0 : break;
7665 : }
7666 18 : const int nDimId = oMapDimToId[pszDimName];
7667 18 : aoDimIds.push_back(nDimId);
7668 :
7669 18 : const size_t nDimSize = oMapDimIdToDimLen[nDimId];
7670 18 : if (nDimSize != 0)
7671 : {
7672 18 : if (nSize >
7673 18 : std::numeric_limits<size_t>::max() / nDimSize)
7674 : {
7675 0 : bFailed = true;
7676 0 : break;
7677 : }
7678 : else
7679 : {
7680 18 : nSize *= nDimSize;
7681 : }
7682 : }
7683 : }
7684 17 : if (bFailed)
7685 : {
7686 0 : CPLDebug("netCDF",
7687 : "nc_def_var(%s) failed: unknown dimension(s)",
7688 : pszVarName);
7689 0 : continue;
7690 : }
7691 17 : if (nSize > 100U * 1024 * 1024 / nDataTypeSize)
7692 : {
7693 0 : CPLDebug("netCDF",
7694 : "nc_def_var(%s) failed: too large data",
7695 : pszVarName);
7696 0 : continue;
7697 : }
7698 17 : if (nTotalVarSize >
7699 34 : std::numeric_limits<size_t>::max() - nSize ||
7700 17 : nTotalVarSize + nSize > 100 * 1024 * 1024)
7701 : {
7702 0 : CPLDebug("netCDF",
7703 : "nc_def_var(%s) failed: too large data",
7704 : pszVarName);
7705 0 : continue;
7706 : }
7707 17 : nTotalVarSize += nSize;
7708 :
7709 17 : int nVarId = -1;
7710 : status =
7711 30 : nc_def_var(nCdfId, pszVarName, nc_datatype, nDims,
7712 13 : (nDims) ? &aoDimIds[0] : nullptr, &nVarId);
7713 17 : if (status != NC_NOERR)
7714 : {
7715 0 : CPLDebug("netCDF", "nc_def_var(%s) failed: %s",
7716 : pszVarName, nc_strerror(status));
7717 : }
7718 : else
7719 : {
7720 : #ifdef DEBUG_VERBOSE
7721 : CPLDebug("netCDF", "nc_def_var(%s) (%s) succeeded",
7722 : pszVarName, pszLine);
7723 : #endif
7724 17 : oMapVarToId[pszVarName] = nVarId;
7725 17 : oMapVarIdToType[nVarId] = nc_datatype;
7726 17 : oMapVarIdToVectorOfDimId[nVarId] = std::move(aoDimIds);
7727 : }
7728 : }
7729 : }
7730 120 : else if (pszEqual != nullptr && pszEqual - pszColumn > 0)
7731 : {
7732 116 : CPLString osVarName(pszLine, pszColumn - pszLine);
7733 116 : CPLString osAttrName(pszColumn + 1, pszEqual - pszColumn - 1);
7734 116 : osAttrName.Trim();
7735 116 : if (oMapVarToId.find(osVarName) == oMapVarToId.end())
7736 : {
7737 0 : CPLDebug("netCDF",
7738 : "nc_put_att(%s:%s) failed: "
7739 : "no corresponding variable",
7740 : osVarName.c_str(), osAttrName.c_str());
7741 0 : continue;
7742 : }
7743 116 : bool bValidName = true;
7744 1743 : for (size_t i = 0; i < osAttrName.size(); i++)
7745 : {
7746 1865 : if (!((osAttrName[i] >= 'a' && osAttrName[i] <= 'z') ||
7747 238 : (osAttrName[i] >= 'A' && osAttrName[i] <= 'Z') ||
7748 158 : (osAttrName[i] >= '0' && osAttrName[i] <= '9') ||
7749 158 : osAttrName[i] == '_'))
7750 : {
7751 0 : bValidName = false;
7752 : }
7753 : }
7754 116 : if (!bValidName)
7755 : {
7756 0 : CPLDebug(
7757 : "netCDF",
7758 : "nc_put_att(%s:%s) failed: illegal character found",
7759 : osVarName.c_str(), osAttrName.c_str());
7760 0 : continue;
7761 : }
7762 116 : if (oSetAttrDefined.find(osVarName + ":" + osAttrName) !=
7763 232 : oSetAttrDefined.end())
7764 : {
7765 0 : CPLDebug("netCDF",
7766 : "nc_put_att(%s:%s) failed: already defined",
7767 : osVarName.c_str(), osAttrName.c_str());
7768 0 : continue;
7769 : }
7770 :
7771 116 : const int nVarId = oMapVarToId[osVarName];
7772 116 : const char *pszValue = pszEqual + 1;
7773 232 : while (*pszValue == ' ')
7774 116 : pszValue++;
7775 :
7776 116 : status = NC_EBADTYPE;
7777 116 : if (*pszValue == '"')
7778 : {
7779 : // For _FillValue, the attribute type should match
7780 : // the variable type. Leaks memory with NC4 otherwise
7781 74 : if (osAttrName == "_FillValue")
7782 : {
7783 0 : CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
7784 : osVarName.c_str(), osAttrName.c_str(),
7785 : nc_strerror(status));
7786 0 : continue;
7787 : }
7788 :
7789 : // Unquote and unescape string value
7790 74 : CPLString osVal(pszValue + 1);
7791 222 : while (!osVal.empty())
7792 : {
7793 222 : if (osVal.back() == ';' || osVal.back() == ' ')
7794 : {
7795 148 : osVal.pop_back();
7796 : }
7797 74 : else if (osVal.back() == '"')
7798 : {
7799 74 : osVal.pop_back();
7800 74 : break;
7801 : }
7802 : else
7803 : {
7804 0 : break;
7805 : }
7806 : }
7807 74 : osVal.replaceAll("\\\"", '"');
7808 74 : status = nc_put_att_text(nCdfId, nVarId, osAttrName,
7809 : osVal.size(), osVal.c_str());
7810 : }
7811 : else
7812 : {
7813 84 : CPLString osVal(pszValue);
7814 126 : while (!osVal.empty())
7815 : {
7816 126 : if (osVal.back() == ';' || osVal.back() == ' ')
7817 : {
7818 84 : osVal.pop_back();
7819 : }
7820 : else
7821 : {
7822 42 : break;
7823 : }
7824 : }
7825 42 : int nc_datatype = -1;
7826 42 : if (!osVal.empty() && osVal.back() == 'b')
7827 : {
7828 3 : nc_datatype = NC_BYTE;
7829 3 : osVal.pop_back();
7830 : }
7831 39 : else if (!osVal.empty() && osVal.back() == 's')
7832 : {
7833 3 : nc_datatype = NC_SHORT;
7834 3 : osVal.pop_back();
7835 : }
7836 42 : if (CPLGetValueType(osVal) == CPL_VALUE_INTEGER)
7837 : {
7838 7 : if (nc_datatype < 0)
7839 4 : nc_datatype = NC_INT;
7840 : }
7841 35 : else if (CPLGetValueType(osVal) == CPL_VALUE_REAL)
7842 : {
7843 32 : nc_datatype = NC_DOUBLE;
7844 : }
7845 : else
7846 : {
7847 3 : nc_datatype = -1;
7848 : }
7849 :
7850 : // For _FillValue, check that the attribute type matches
7851 : // the variable type. Leaks memory with NC4 otherwise
7852 42 : if (osAttrName == "_FillValue")
7853 : {
7854 6 : if (nVarId < 0 ||
7855 3 : nc_datatype != oMapVarIdToType[nVarId])
7856 : {
7857 0 : nc_datatype = -1;
7858 : }
7859 : }
7860 :
7861 42 : if (nc_datatype == NC_BYTE)
7862 : {
7863 : signed char chVal =
7864 3 : static_cast<signed char>(atoi(osVal));
7865 3 : status = nc_put_att_schar(nCdfId, nVarId, osAttrName,
7866 : NC_BYTE, 1, &chVal);
7867 : }
7868 39 : else if (nc_datatype == NC_SHORT)
7869 : {
7870 0 : short nVal = static_cast<short>(atoi(osVal));
7871 0 : status = nc_put_att_short(nCdfId, nVarId, osAttrName,
7872 : NC_SHORT, 1, &nVal);
7873 : }
7874 39 : else if (nc_datatype == NC_INT)
7875 : {
7876 4 : int nVal = static_cast<int>(atoi(osVal));
7877 4 : status = nc_put_att_int(nCdfId, nVarId, osAttrName,
7878 : NC_INT, 1, &nVal);
7879 : }
7880 35 : else if (nc_datatype == NC_DOUBLE)
7881 : {
7882 32 : double dfVal = CPLAtof(osVal);
7883 32 : status = nc_put_att_double(nCdfId, nVarId, osAttrName,
7884 : NC_DOUBLE, 1, &dfVal);
7885 : }
7886 : }
7887 116 : if (status != NC_NOERR)
7888 : {
7889 3 : CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
7890 : osVarName.c_str(), osAttrName.c_str(),
7891 : nc_strerror(status));
7892 : }
7893 : else
7894 : {
7895 113 : oSetAttrDefined.insert(osVarName + ":" + osAttrName);
7896 : #ifdef DEBUG_VERBOSE
7897 : CPLDebug("netCDF", "nc_put_att_(%s:%s) (%s) succeeded",
7898 : osVarName.c_str(), osAttrName.c_str(), pszLine);
7899 : #endif
7900 : }
7901 : }
7902 : }
7903 42 : else if (nActiveSection == SECTION_DATA)
7904 : {
7905 55 : while (*pszLine == ' ' || *pszLine == '\t')
7906 17 : pszLine++;
7907 38 : const char *pszEqual = strchr(pszLine, '=');
7908 38 : if (pszEqual)
7909 : {
7910 17 : CPLString osVarName(pszLine, pszEqual - pszLine);
7911 17 : osVarName.Trim();
7912 17 : if (oMapVarToId.find(osVarName) == oMapVarToId.end())
7913 0 : continue;
7914 17 : const int nVarId = oMapVarToId[osVarName];
7915 17 : CPLString osAccVal(pszEqual + 1);
7916 17 : osAccVal.Trim();
7917 153 : while (osAccVal.empty() || osAccVal.back() != ';')
7918 : {
7919 136 : pszLine = CPLReadLineL(fpSrc);
7920 136 : if (pszLine == nullptr)
7921 0 : break;
7922 272 : CPLString osVal(pszLine);
7923 136 : osVal.Trim();
7924 136 : osAccVal += osVal;
7925 : }
7926 17 : if (pszLine == nullptr)
7927 0 : break;
7928 17 : osAccVal.pop_back();
7929 :
7930 : const std::vector<int> aoDimIds =
7931 34 : oMapVarIdToVectorOfDimId[nVarId];
7932 17 : size_t nSize = 1;
7933 34 : std::vector<size_t> aoStart, aoEdge;
7934 17 : aoStart.resize(aoDimIds.size());
7935 17 : aoEdge.resize(aoDimIds.size());
7936 35 : for (size_t i = 0; i < aoDimIds.size(); ++i)
7937 : {
7938 18 : const size_t nDimSize = oMapDimIdToDimLen[aoDimIds[i]];
7939 36 : if (nDimSize != 0 &&
7940 18 : nSize > std::numeric_limits<size_t>::max() / nDimSize)
7941 : {
7942 0 : nSize = 0;
7943 : }
7944 : else
7945 : {
7946 18 : nSize *= nDimSize;
7947 : }
7948 18 : aoStart[i] = 0;
7949 18 : aoEdge[i] = nDimSize;
7950 : }
7951 :
7952 17 : status = NC_EBADTYPE;
7953 17 : if (nSize == 0)
7954 : {
7955 : // Might happen with an unlimited dimension
7956 : }
7957 17 : else if (oMapVarIdToType[nVarId] == NC_DOUBLE)
7958 : {
7959 8 : if (!aoStart.empty())
7960 : {
7961 : const CPLStringList aosTokens(
7962 16 : CSLTokenizeString2(osAccVal, " ,;", 0));
7963 8 : size_t nTokens = aosTokens.size();
7964 8 : if (nTokens >= nSize)
7965 : {
7966 : double *padfVals = static_cast<double *>(
7967 8 : VSI_CALLOC_VERBOSE(nSize, sizeof(double)));
7968 8 : if (padfVals)
7969 : {
7970 132 : for (size_t i = 0; i < nSize; i++)
7971 : {
7972 124 : padfVals[i] = CPLAtof(aosTokens[i]);
7973 : }
7974 8 : status = nc_put_vara_double(
7975 8 : nCdfId, nVarId, &aoStart[0], &aoEdge[0],
7976 : padfVals);
7977 8 : VSIFree(padfVals);
7978 : }
7979 : }
7980 : }
7981 : }
7982 9 : else if (oMapVarIdToType[nVarId] == NC_BYTE)
7983 : {
7984 3 : if (!aoStart.empty())
7985 : {
7986 : const CPLStringList aosTokens(
7987 6 : CSLTokenizeString2(osAccVal, " ,;", 0));
7988 3 : size_t nTokens = aosTokens.size();
7989 3 : if (nTokens >= nSize)
7990 : {
7991 : signed char *panVals = static_cast<signed char *>(
7992 3 : VSI_CALLOC_VERBOSE(nSize, sizeof(signed char)));
7993 3 : if (panVals)
7994 : {
7995 1203 : for (size_t i = 0; i < nSize; i++)
7996 : {
7997 1200 : panVals[i] = static_cast<signed char>(
7998 1200 : atoi(aosTokens[i]));
7999 : }
8000 3 : status = nc_put_vara_schar(nCdfId, nVarId,
8001 3 : &aoStart[0],
8002 3 : &aoEdge[0], panVals);
8003 3 : VSIFree(panVals);
8004 : }
8005 : }
8006 : }
8007 : }
8008 6 : else if (oMapVarIdToType[nVarId] == NC_CHAR)
8009 : {
8010 6 : if (aoStart.size() == 2)
8011 : {
8012 4 : std::vector<CPLString> aoStrings;
8013 2 : bool bInString = false;
8014 4 : CPLString osCurString;
8015 935 : for (size_t i = 0; i < osAccVal.size();)
8016 : {
8017 933 : if (!bInString)
8018 : {
8019 8 : if (osAccVal[i] == '"')
8020 : {
8021 4 : bInString = true;
8022 4 : osCurString.clear();
8023 : }
8024 8 : i++;
8025 : }
8026 926 : else if (osAccVal[i] == '\\' &&
8027 926 : i + 1 < osAccVal.size() &&
8028 1 : osAccVal[i + 1] == '"')
8029 : {
8030 1 : osCurString += '"';
8031 1 : i += 2;
8032 : }
8033 924 : else if (osAccVal[i] == '"')
8034 : {
8035 4 : aoStrings.push_back(osCurString);
8036 4 : osCurString.clear();
8037 4 : bInString = false;
8038 4 : i++;
8039 : }
8040 : else
8041 : {
8042 920 : osCurString += osAccVal[i];
8043 920 : i++;
8044 : }
8045 : }
8046 2 : const size_t nRecords = oMapDimIdToDimLen[aoDimIds[0]];
8047 2 : const size_t nWidth = oMapDimIdToDimLen[aoDimIds[1]];
8048 2 : size_t nIters = aoStrings.size();
8049 2 : if (nIters > nRecords)
8050 0 : nIters = nRecords;
8051 6 : for (size_t i = 0; i < nIters; i++)
8052 : {
8053 : size_t anIndex[2];
8054 4 : anIndex[0] = i;
8055 4 : anIndex[1] = 0;
8056 : size_t anCount[2];
8057 4 : anCount[0] = 1;
8058 4 : anCount[1] = aoStrings[i].size();
8059 4 : if (anCount[1] > nWidth)
8060 0 : anCount[1] = nWidth;
8061 : status =
8062 4 : nc_put_vara_text(nCdfId, nVarId, anIndex,
8063 4 : anCount, aoStrings[i].c_str());
8064 4 : if (status != NC_NOERR)
8065 0 : break;
8066 : }
8067 : }
8068 : }
8069 17 : if (status != NC_NOERR)
8070 : {
8071 4 : CPLDebug("netCDF", "nc_put_var_(%s) failed: %s",
8072 : osVarName.c_str(), nc_strerror(status));
8073 : }
8074 : }
8075 : }
8076 : }
8077 :
8078 4 : GDAL_nc_close(nCdfId);
8079 4 : return true;
8080 : }
8081 :
8082 : #endif // ENABLE_NCDUMP
8083 :
8084 : /************************************************************************/
8085 : /* Open() */
8086 : /************************************************************************/
8087 :
8088 795 : GDALDataset *netCDFDataset::Open(GDALOpenInfo *poOpenInfo)
8089 :
8090 : {
8091 : #ifdef NCDF_DEBUG
8092 : CPLDebug("GDAL_netCDF", "\n=====\nOpen(), filename=[%s]",
8093 : poOpenInfo->pszFilename);
8094 : #endif
8095 :
8096 : // Does this appear to be a netcdf file?
8097 795 : NetCDFFormatEnum eTmpFormat = NCDF_FORMAT_NONE;
8098 795 : if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
8099 : {
8100 731 : eTmpFormat = netCDFIdentifyFormat(poOpenInfo, /* bCheckExt = */ true);
8101 : #ifdef NCDF_DEBUG
8102 : CPLDebug("GDAL_netCDF", "identified format %d", eTmpFormat);
8103 : #endif
8104 : // Note: not calling Identify() directly, because we want the file type.
8105 : // Only support NCDF_FORMAT* formats.
8106 731 : if (NCDF_FORMAT_NC == eTmpFormat || NCDF_FORMAT_NC2 == eTmpFormat ||
8107 2 : NCDF_FORMAT_NC4 == eTmpFormat || NCDF_FORMAT_NC4C == eTmpFormat)
8108 : {
8109 : // ok
8110 : }
8111 2 : else if (eTmpFormat == NCDF_FORMAT_HDF4 &&
8112 0 : poOpenInfo->IsSingleAllowedDriver("netCDF"))
8113 : {
8114 : // ok
8115 : }
8116 : else
8117 : {
8118 2 : return nullptr;
8119 : }
8120 : }
8121 : else
8122 : {
8123 : #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
8124 : // We don't necessarily want to catch bugs in libnetcdf ...
8125 : if (CPLGetConfigOption("DISABLE_OPEN_REAL_NETCDF_FILES", nullptr))
8126 : {
8127 : return nullptr;
8128 : }
8129 : #endif
8130 : }
8131 :
8132 793 : if (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER)
8133 : {
8134 279 : return OpenMultiDim(poOpenInfo);
8135 : }
8136 :
8137 1028 : CPLMutexHolderD(&hNCMutex);
8138 :
8139 514 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
8140 : // GDALDataset own mutex.
8141 514 : netCDFDataset *poDS = new netCDFDataset();
8142 514 : poDS->papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
8143 514 : CPLAcquireMutex(hNCMutex, 1000.0);
8144 :
8145 514 : poDS->SetDescription(poOpenInfo->pszFilename);
8146 :
8147 : // Check if filename start with NETCDF: tag.
8148 514 : bool bTreatAsSubdataset = false;
8149 1028 : CPLString osSubdatasetName;
8150 :
8151 : #ifdef ENABLE_NCDUMP
8152 514 : const char *pszHeader =
8153 : reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
8154 514 : if (poOpenInfo->fpL != nullptr && STARTS_WITH(pszHeader, "netcdf ") &&
8155 3 : strstr(pszHeader, "dimensions:") && strstr(pszHeader, "variables:"))
8156 : {
8157 : // By default create a temporary file that will be destroyed,
8158 : // unless NETCDF_TMP_FILE is defined. Can be useful to see which
8159 : // netCDF file has been generated from a potential fuzzed input.
8160 3 : poDS->osFilename = CPLGetConfigOption("NETCDF_TMP_FILE", "");
8161 3 : if (poDS->osFilename.empty())
8162 : {
8163 3 : poDS->bFileToDestroyAtClosing = true;
8164 3 : poDS->osFilename = CPLGenerateTempFilenameSafe("netcdf_tmp");
8165 : }
8166 3 : if (!netCDFDatasetCreateTempFile(eTmpFormat, poDS->osFilename,
8167 : poOpenInfo->fpL))
8168 : {
8169 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8170 : // deadlock with GDALDataset own mutex.
8171 0 : delete poDS;
8172 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8173 0 : return nullptr;
8174 : }
8175 3 : bTreatAsSubdataset = false;
8176 3 : poDS->eFormat = eTmpFormat;
8177 : }
8178 : else
8179 : #endif
8180 :
8181 511 : if (STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
8182 : {
8183 : GDALSubdatasetInfoH hInfo =
8184 64 : GDALGetSubdatasetInfo(poOpenInfo->pszFilename);
8185 64 : if (hInfo)
8186 : {
8187 64 : char *pszPath = GDALSubdatasetInfoGetPathComponent(hInfo);
8188 64 : poDS->osFilename = pszPath;
8189 64 : CPLFree(pszPath);
8190 :
8191 : char *pszSubdataset =
8192 64 : GDALSubdatasetInfoGetSubdatasetComponent(hInfo);
8193 64 : if (pszSubdataset && pszSubdataset[0] != '\0')
8194 : {
8195 64 : osSubdatasetName = pszSubdataset;
8196 64 : bTreatAsSubdataset = true;
8197 : }
8198 : else
8199 : {
8200 0 : osSubdatasetName = "";
8201 0 : bTreatAsSubdataset = false;
8202 : }
8203 64 : CPLFree(pszSubdataset);
8204 :
8205 64 : GDALDestroySubdatasetInfo(hInfo);
8206 : }
8207 :
8208 128 : if (!STARTS_WITH(poDS->osFilename, "http://") &&
8209 64 : !STARTS_WITH(poDS->osFilename, "https://"))
8210 : {
8211 : // Identify Format from real file, with bCheckExt=FALSE.
8212 : auto poOpenInfo2 = std::make_unique<GDALOpenInfo>(
8213 64 : poDS->osFilename.c_str(), GA_ReadOnly);
8214 64 : poDS->eFormat = netCDFIdentifyFormat(poOpenInfo2.get(),
8215 : /* bCheckExt = */ false);
8216 64 : if (NCDF_FORMAT_NONE == poDS->eFormat ||
8217 64 : NCDF_FORMAT_UNKNOWN == poDS->eFormat)
8218 : {
8219 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8220 : // deadlock with GDALDataset own mutex.
8221 0 : delete poDS;
8222 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8223 0 : return nullptr;
8224 : }
8225 : }
8226 : }
8227 : else
8228 : {
8229 447 : poDS->osFilename = poOpenInfo->pszFilename;
8230 447 : bTreatAsSubdataset = false;
8231 447 : poDS->eFormat = eTmpFormat;
8232 : }
8233 :
8234 : // Try opening the dataset.
8235 : #if defined(NCDF_DEBUG) && defined(ENABLE_UFFD)
8236 : CPLDebug("GDAL_netCDF", "calling nc_open_mem(%s)",
8237 : poDS->osFilename.c_str());
8238 : #elif defined(NCDF_DEBUG) && !defined(ENABLE_UFFD)
8239 : CPLDebug("GDAL_netCDF", "calling nc_open(%s)", poDS->osFilename.c_str());
8240 : #endif
8241 514 : int cdfid = -1;
8242 514 : const int nMode = ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE) != 0)
8243 : ? NC_WRITE
8244 : : NC_NOWRITE;
8245 1028 : CPLString osFilenameForNCOpen(poDS->osFilename);
8246 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
8247 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
8248 : {
8249 : char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
8250 : osFilenameForNCOpen = pszTemp;
8251 : CPLFree(pszTemp);
8252 : }
8253 : #endif
8254 514 : int status2 = -1;
8255 :
8256 : #ifdef ENABLE_UFFD
8257 514 : cpl_uffd_context *pCtx = nullptr;
8258 : #endif
8259 :
8260 529 : if (STARTS_WITH(osFilenameForNCOpen, "/vsimem/") &&
8261 15 : poOpenInfo->eAccess == GA_ReadOnly)
8262 : {
8263 15 : vsi_l_offset nLength = 0;
8264 15 : poDS->fpVSIMEM = VSIFOpenL(osFilenameForNCOpen, "rb");
8265 15 : if (poDS->fpVSIMEM)
8266 : {
8267 : // We assume that the file will not be modified. If it is, then
8268 : // pabyBuffer might become invalid.
8269 : GByte *pabyBuffer =
8270 15 : VSIGetMemFileBuffer(osFilenameForNCOpen, &nLength, false);
8271 15 : if (pabyBuffer)
8272 : {
8273 15 : status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen),
8274 : nMode, static_cast<size_t>(nLength),
8275 : pabyBuffer, &cdfid);
8276 : }
8277 : }
8278 : }
8279 : else
8280 : {
8281 : const bool bVsiFile =
8282 499 : !strncmp(osFilenameForNCOpen, "/vsi", strlen("/vsi"));
8283 : #ifdef ENABLE_UFFD
8284 499 : bool bReadOnly = (poOpenInfo->eAccess == GA_ReadOnly);
8285 499 : void *pVma = nullptr;
8286 499 : uint64_t nVmaSize = 0;
8287 :
8288 499 : if (bVsiFile)
8289 : {
8290 2 : if (bReadOnly)
8291 : {
8292 2 : if (CPLIsUserFaultMappingSupported())
8293 : {
8294 2 : pCtx = CPLCreateUserFaultMapping(osFilenameForNCOpen, &pVma,
8295 : &nVmaSize);
8296 : }
8297 : else
8298 : {
8299 0 : CPLError(CE_Failure, CPLE_AppDefined,
8300 : "Opening a /vsi file with the netCDF driver "
8301 : "requires Linux userfaultfd to be available. "
8302 : "If running from Docker, "
8303 : "--security-opt seccomp=unconfined might be "
8304 : "needed.%s",
8305 0 : ((poDS->eFormat == NCDF_FORMAT_NC4 ||
8306 0 : poDS->eFormat == NCDF_FORMAT_HDF5) &&
8307 0 : GDALGetDriverByName("HDF5"))
8308 : ? " Or you may set the GDAL_SKIP=netCDF "
8309 : "configuration option to force the use of "
8310 : "the HDF5 driver."
8311 : : "");
8312 : }
8313 : }
8314 : else
8315 : {
8316 0 : CPLError(CE_Failure, CPLE_AppDefined,
8317 : "Opening a /vsi file with the netCDF driver is only "
8318 : "supported in read-only mode");
8319 : }
8320 : }
8321 499 : if (pCtx != nullptr && pVma != nullptr && nVmaSize > 0)
8322 : {
8323 : // netCDF code, at least for netCDF 4.7.0, is confused by filenames
8324 : // like /vsicurl/http[s]://example.com/foo.nc, so just pass the
8325 : // final part
8326 2 : status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen), nMode,
8327 : static_cast<size_t>(nVmaSize), pVma, &cdfid);
8328 : }
8329 : else
8330 497 : status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
8331 : #else
8332 : if (bVsiFile)
8333 : {
8334 : CPLError(
8335 : CE_Failure, CPLE_AppDefined,
8336 : "Opening a /vsi file with the netCDF driver requires Linux "
8337 : "userfaultfd to be available.%s",
8338 : ((poDS->eFormat == NCDF_FORMAT_NC4 ||
8339 : poDS->eFormat == NCDF_FORMAT_HDF5) &&
8340 : GDALGetDriverByName("HDF5"))
8341 : ? " Or you may set the GDAL_SKIP=netCDF "
8342 : "configuration option to force the use of the HDF5 "
8343 : "driver."
8344 : : "");
8345 : status2 = NC_EIO;
8346 : }
8347 : else
8348 : {
8349 : status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
8350 : }
8351 : #endif
8352 : }
8353 514 : if (status2 != NC_NOERR)
8354 : {
8355 : #ifdef NCDF_DEBUG
8356 : CPLDebug("GDAL_netCDF", "error opening");
8357 : #endif
8358 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8359 : // with GDALDataset own mutex.
8360 0 : delete poDS;
8361 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8362 0 : return nullptr;
8363 : }
8364 : #ifdef NCDF_DEBUG
8365 : CPLDebug("GDAL_netCDF", "got cdfid=%d", cdfid);
8366 : #endif
8367 :
8368 : #if defined(ENABLE_NCDUMP) && !defined(_WIN32)
8369 : // Try to destroy the temporary file right now on Unix
8370 514 : if (poDS->bFileToDestroyAtClosing)
8371 : {
8372 3 : if (VSIUnlink(poDS->osFilename) == 0)
8373 : {
8374 3 : poDS->bFileToDestroyAtClosing = false;
8375 : }
8376 : }
8377 : #endif
8378 :
8379 : // Is this a real netCDF file?
8380 : int ndims;
8381 : int ngatts;
8382 : int nvars;
8383 : int unlimdimid;
8384 514 : int status = nc_inq(cdfid, &ndims, &nvars, &ngatts, &unlimdimid);
8385 514 : if (status != NC_NOERR)
8386 : {
8387 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8388 : // with GDALDataset own mutex.
8389 0 : delete poDS;
8390 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8391 0 : return nullptr;
8392 : }
8393 :
8394 : // Get file type from netcdf.
8395 514 : int nTmpFormat = NCDF_FORMAT_NONE;
8396 514 : status = nc_inq_format(cdfid, &nTmpFormat);
8397 514 : if (status != NC_NOERR)
8398 : {
8399 0 : NCDF_ERR(status);
8400 : }
8401 : else
8402 : {
8403 514 : CPLDebug("GDAL_netCDF",
8404 : "driver detected file type=%d, libnetcdf detected type=%d",
8405 514 : poDS->eFormat, nTmpFormat);
8406 514 : if (static_cast<NetCDFFormatEnum>(nTmpFormat) != poDS->eFormat)
8407 : {
8408 : // Warn if file detection conflicts with that from libnetcdf
8409 : // except for NC4C, which we have no way of detecting initially.
8410 26 : if (nTmpFormat != NCDF_FORMAT_NC4C &&
8411 13 : !STARTS_WITH(poDS->osFilename, "http://") &&
8412 0 : !STARTS_WITH(poDS->osFilename, "https://"))
8413 : {
8414 0 : CPLError(CE_Warning, CPLE_AppDefined,
8415 : "NetCDF driver detected file type=%d, but libnetcdf "
8416 : "detected type=%d",
8417 0 : poDS->eFormat, nTmpFormat);
8418 : }
8419 13 : CPLDebug("GDAL_netCDF", "setting file type to %d, was %d",
8420 13 : nTmpFormat, poDS->eFormat);
8421 13 : poDS->eFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
8422 : }
8423 : }
8424 :
8425 : // Does the request variable exist?
8426 514 : if (bTreatAsSubdataset)
8427 : {
8428 : int dummy;
8429 64 : if (NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &dummy,
8430 64 : &dummy) != CE_None)
8431 : {
8432 0 : CPLError(CE_Warning, CPLE_AppDefined,
8433 : "%s is a netCDF file, but %s is not a variable.",
8434 : poOpenInfo->pszFilename, osSubdatasetName.c_str());
8435 :
8436 0 : GDAL_nc_close(cdfid);
8437 : #ifdef ENABLE_UFFD
8438 0 : NETCDF_UFFD_UNMAP(pCtx);
8439 : #endif
8440 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8441 : // deadlock with GDALDataset own mutex.
8442 0 : delete poDS;
8443 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8444 0 : return nullptr;
8445 : }
8446 : }
8447 :
8448 : // Figure out whether or not the listed dataset has support for simple
8449 : // geometries (CF-1.8)
8450 514 : poDS->nCFVersion = nccfdriver::getCFVersion(cdfid);
8451 514 : bool bHasSimpleGeometries = false; // but not necessarily valid
8452 514 : if (poDS->nCFVersion >= 1.8)
8453 : {
8454 75 : bHasSimpleGeometries = poDS->DetectAndFillSGLayers(cdfid);
8455 75 : if (bHasSimpleGeometries)
8456 : {
8457 67 : poDS->bSGSupport = true;
8458 67 : poDS->vcdf.enableFullVirtualMode();
8459 : }
8460 : }
8461 :
8462 : char szConventions[NC_MAX_NAME + 1];
8463 514 : szConventions[0] = '\0';
8464 514 : nc_type nAttype = NC_NAT;
8465 514 : size_t nAttlen = 0;
8466 514 : nc_inq_att(cdfid, NC_GLOBAL, "Conventions", &nAttype, &nAttlen);
8467 1028 : if (nAttlen >= sizeof(szConventions) ||
8468 514 : nc_get_att_text(cdfid, NC_GLOBAL, "Conventions", szConventions) !=
8469 : NC_NOERR)
8470 : {
8471 59 : CPLDebug("GDAL_netCDF", "No UNIDATA NC_GLOBAL:Conventions attribute");
8472 : // Note that 'Conventions' is always capital 'C' in CF spec.
8473 : }
8474 : else
8475 : {
8476 455 : szConventions[nAttlen] = '\0';
8477 : }
8478 :
8479 : // Create band information objects.
8480 514 : CPLDebug("GDAL_netCDF", "var_count = %d", nvars);
8481 :
8482 : // Create a corresponding GDALDataset.
8483 : // Create Netcdf Subdataset if filename as NETCDF tag.
8484 514 : poDS->cdfid = cdfid;
8485 : #ifdef ENABLE_UFFD
8486 514 : poDS->pCtx = pCtx;
8487 : #endif
8488 514 : poDS->eAccess = poOpenInfo->eAccess;
8489 514 : poDS->bDefineMode = false;
8490 :
8491 514 : poDS->ReadAttributes(cdfid, NC_GLOBAL);
8492 :
8493 : // Identify coordinate and boundary variables that we should
8494 : // ignore as Raster Bands.
8495 514 : char **papszIgnoreVars = nullptr;
8496 514 : NCDFGetCoordAndBoundVarFullNames(cdfid, &papszIgnoreVars);
8497 : // Filter variables to keep only valid 2+D raster bands and vector fields.
8498 514 : int nRasterVars = 0;
8499 514 : int nIgnoredVars = 0;
8500 514 : int nGroupID = -1;
8501 514 : int nVarID = -1;
8502 :
8503 : std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>
8504 1028 : oMap2DDimsToGroupAndVar;
8505 1181 : if ((poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
8506 153 : STARTS_WITH(CSLFetchNameValueDef(poDS->papszMetadata,
8507 : "NC_GLOBAL#mission_name", ""),
8508 1 : "Sentinel 3") &&
8509 1 : EQUAL(CSLFetchNameValueDef(poDS->papszMetadata,
8510 : "NC_GLOBAL#altimeter_sensor_name", ""),
8511 667 : "SRAL") &&
8512 1 : EQUAL(CSLFetchNameValueDef(poDS->papszMetadata,
8513 : "NC_GLOBAL#radiometer_sensor_name", ""),
8514 : "MWR"))
8515 : {
8516 1 : if (poDS->eAccess == GA_Update)
8517 : {
8518 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8519 : // deadlock with GDALDataset own mutex.
8520 0 : delete poDS;
8521 0 : return nullptr;
8522 : }
8523 1 : poDS->ProcessSentinel3_SRAL_MWR();
8524 : }
8525 : else
8526 : {
8527 513 : poDS->FilterVars(cdfid, (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0,
8528 665 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
8529 152 : !bHasSimpleGeometries,
8530 : papszIgnoreVars, &nRasterVars, &nGroupID, &nVarID,
8531 : &nIgnoredVars, oMap2DDimsToGroupAndVar);
8532 : }
8533 514 : CSLDestroy(papszIgnoreVars);
8534 :
8535 514 : const bool bListAllArrays = CPLTestBool(
8536 514 : CSLFetchNameValueDef(poDS->papszOpenOptions, "LIST_ALL_ARRAYS", "NO"));
8537 :
8538 : // Case where there is no raster variable
8539 514 : if (!bListAllArrays && nRasterVars == 0 && !bTreatAsSubdataset)
8540 : {
8541 119 : poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
8542 119 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8543 : // with GDALDataset own mutex.
8544 119 : poDS->TryLoadXML();
8545 : // If the dataset has been opened in raster mode only, exit
8546 119 : if ((poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0 &&
8547 9 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) == 0)
8548 : {
8549 4 : delete poDS;
8550 4 : poDS = nullptr;
8551 : }
8552 : // Otherwise if the dataset is opened in vector mode, that there is
8553 : // no vector layer and we are in read-only, exit too.
8554 115 : else if (poDS->GetLayerCount() == 0 &&
8555 123 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
8556 8 : poOpenInfo->eAccess == GA_ReadOnly)
8557 : {
8558 8 : delete poDS;
8559 8 : poDS = nullptr;
8560 : }
8561 119 : CPLAcquireMutex(hNCMutex, 1000.0);
8562 119 : return poDS;
8563 : }
8564 :
8565 : // We have more than one variable with 2 dimensions in the
8566 : // file, then treat this as a subdataset container dataset.
8567 395 : bool bSeveralVariablesAsBands = false;
8568 395 : if (bListAllArrays || ((nRasterVars > 1) && !bTreatAsSubdataset))
8569 : {
8570 29 : if (CPLFetchBool(poOpenInfo->papszOpenOptions, "VARIABLES_AS_BANDS",
8571 35 : false) &&
8572 6 : oMap2DDimsToGroupAndVar.size() == 1)
8573 : {
8574 6 : std::tie(nGroupID, nVarID) =
8575 12 : oMap2DDimsToGroupAndVar.begin()->second.front();
8576 6 : bSeveralVariablesAsBands = true;
8577 : }
8578 : else
8579 : {
8580 23 : poDS->CreateSubDatasetList(cdfid);
8581 23 : poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
8582 23 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8583 : // deadlock with GDALDataset own mutex.
8584 23 : poDS->TryLoadXML();
8585 23 : CPLAcquireMutex(hNCMutex, 1000.0);
8586 23 : return poDS;
8587 : }
8588 : }
8589 :
8590 : // If we are not treating things as a subdataset, then capture
8591 : // the name of the single available variable as the subdataset.
8592 372 : if (!bTreatAsSubdataset)
8593 : {
8594 308 : char *pszVarName = nullptr;
8595 308 : NCDF_ERR(NCDFGetVarFullName(nGroupID, nVarID, &pszVarName));
8596 308 : osSubdatasetName = (pszVarName != nullptr ? pszVarName : "");
8597 308 : CPLFree(pszVarName);
8598 : }
8599 :
8600 : // We have ignored at least one variable, so we should report them
8601 : // as subdatasets for reference.
8602 372 : if (nIgnoredVars > 0 && !bTreatAsSubdataset)
8603 : {
8604 29 : CPLDebug("GDAL_netCDF",
8605 : "As %d variables were ignored, creating subdataset list "
8606 : "for reference. Variable #%d [%s] is the main variable",
8607 : nIgnoredVars, nVarID, osSubdatasetName.c_str());
8608 29 : poDS->CreateSubDatasetList(cdfid);
8609 : }
8610 :
8611 : // Open the NETCDF subdataset NETCDF:"filename":subdataset.
8612 372 : int var = -1;
8613 372 : NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &nGroupID, &var);
8614 : // Now we can forget the root cdfid and only use the selected group.
8615 372 : cdfid = nGroupID;
8616 372 : int nd = 0;
8617 372 : nc_inq_varndims(cdfid, var, &nd);
8618 :
8619 372 : poDS->m_anDimIds.resize(nd);
8620 :
8621 : // X, Y, Z position in array
8622 744 : std::vector<int> anBandDimPos(nd);
8623 :
8624 372 : nc_inq_vardimid(cdfid, var, poDS->m_anDimIds.data());
8625 :
8626 : // Check if somebody tried to pass a variable with less than 1D.
8627 372 : if (nd < 1)
8628 : {
8629 0 : CPLError(CE_Warning, CPLE_AppDefined,
8630 : "Variable has %d dimension(s) - not supported.", nd);
8631 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8632 : // with GDALDataset own mutex.
8633 0 : delete poDS;
8634 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8635 0 : return nullptr;
8636 : }
8637 :
8638 : // CF-1 Convention
8639 : //
8640 : // Dimensions to appear in the relative order T, then Z, then Y,
8641 : // then X to the file. All other dimensions should, whenever
8642 : // possible, be placed to the left of the spatiotemporal
8643 : // dimensions.
8644 :
8645 : // Verify that dimensions are in the {T,Z,Y,X} or {T,Z,Y,X} order
8646 : // Ideally we should detect for other ordering and act accordingly
8647 : // Only done if file has Conventions=CF-* and only prints warning
8648 : // To disable set GDAL_NETCDF_VERIFY_DIMS=NO and to use only
8649 : // attributes (not varnames) set GDAL_NETCDF_VERIFY_DIMS=STRICT
8650 : const bool bCheckDims =
8651 744 : CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES")) &&
8652 372 : STARTS_WITH_CI(szConventions, "CF");
8653 :
8654 372 : bool bYXBandOrder = false;
8655 372 : if (nd == 3)
8656 : {
8657 : // If there's a coordinates attributes, and the variable it points to
8658 : // are 2D variables indexed by the same first and second dimension than
8659 : // our variable of interest, then it is Y,X,Band order.
8660 46 : char *pszCoordinates = nullptr;
8661 46 : if (NCDFGetAttr(cdfid, var, "coordinates", &pszCoordinates) ==
8662 63 : CE_None &&
8663 17 : pszCoordinates)
8664 : {
8665 : const CPLStringList aosCoordinates(
8666 34 : NCDFTokenizeCoordinatesAttribute(pszCoordinates));
8667 17 : if (aosCoordinates.size() == 2)
8668 : {
8669 : // Test that each variable is longitude/latitude.
8670 13 : for (int i = 0; i < aosCoordinates.size(); i++)
8671 : {
8672 13 : if (NCDFIsVarLongitude(cdfid, -1, aosCoordinates[i]) ||
8673 4 : NCDFIsVarLatitude(cdfid, -1, aosCoordinates[i]))
8674 : {
8675 9 : int nOtherGroupId = -1;
8676 9 : int nOtherVarId = -1;
8677 9 : if (NCDFResolveVar(cdfid, aosCoordinates[i],
8678 : &nOtherGroupId,
8679 9 : &nOtherVarId) == CE_None)
8680 : {
8681 9 : int coordDimCount = 0;
8682 9 : nc_inq_varndims(nOtherGroupId, nOtherVarId,
8683 : &coordDimCount);
8684 9 : if (coordDimCount == 2)
8685 : {
8686 3 : int coordDimIds[2] = {0, 0};
8687 3 : nc_inq_vardimid(nOtherGroupId, nOtherVarId,
8688 : coordDimIds);
8689 4 : if (coordDimIds[0] == poDS->m_anDimIds[0] &&
8690 1 : coordDimIds[1] == poDS->m_anDimIds[1])
8691 : {
8692 1 : bYXBandOrder = true;
8693 1 : break;
8694 : }
8695 : }
8696 : }
8697 : }
8698 : }
8699 : }
8700 : }
8701 46 : CPLFree(pszCoordinates);
8702 :
8703 46 : if (!bYXBandOrder)
8704 : {
8705 45 : char szDim0Name[NC_MAX_NAME + 1] = {};
8706 45 : char szDim1Name[NC_MAX_NAME + 1] = {};
8707 45 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[0], szDim0Name);
8708 45 : NCDF_ERR(status);
8709 45 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[1], szDim1Name);
8710 45 : NCDF_ERR(status);
8711 :
8712 45 : if (strcmp(szDim0Name, "number_of_lines") == 0 &&
8713 1 : strcmp(szDim1Name, "pixels_per_line") == 0)
8714 : {
8715 : // Like in PACE OCI products
8716 1 : bYXBandOrder = true;
8717 : }
8718 : else
8719 : {
8720 : // For example for EMIT data (https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/),
8721 : // dimension order is downtrack, crosstrack, bands
8722 44 : char szDim2Name[NC_MAX_NAME + 1] = {};
8723 44 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[2], szDim2Name);
8724 44 : NCDF_ERR(status);
8725 86 : bYXBandOrder = strcmp(szDim2Name, "bands") == 0 ||
8726 42 : strcmp(szDim2Name, "band") == 0;
8727 : }
8728 : }
8729 : }
8730 :
8731 372 : if (nd >= 2 && bCheckDims && !bYXBandOrder)
8732 : {
8733 285 : char szDimName1[NC_MAX_NAME + 1] = {};
8734 285 : char szDimName2[NC_MAX_NAME + 1] = {};
8735 285 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 1], szDimName1);
8736 285 : NCDF_ERR(status);
8737 285 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 2], szDimName2);
8738 285 : NCDF_ERR(status);
8739 461 : if (NCDFIsVarLongitude(cdfid, -1, szDimName1) == false &&
8740 176 : NCDFIsVarProjectionX(cdfid, -1, szDimName1) == false)
8741 : {
8742 4 : CPLError(CE_Warning, CPLE_AppDefined,
8743 : "dimension #%d (%s) is not a Longitude/X dimension.",
8744 : nd - 1, szDimName1);
8745 : }
8746 461 : if (NCDFIsVarLatitude(cdfid, -1, szDimName2) == false &&
8747 176 : NCDFIsVarProjectionY(cdfid, -1, szDimName2) == false)
8748 : {
8749 4 : CPLError(CE_Warning, CPLE_AppDefined,
8750 : "dimension #%d (%s) is not a Latitude/Y dimension.",
8751 : nd - 2, szDimName2);
8752 : }
8753 285 : if ((NCDFIsVarLongitude(cdfid, -1, szDimName2) ||
8754 287 : NCDFIsVarProjectionX(cdfid, -1, szDimName2)) &&
8755 2 : (NCDFIsVarLatitude(cdfid, -1, szDimName1) ||
8756 0 : NCDFIsVarProjectionY(cdfid, -1, szDimName1)))
8757 : {
8758 2 : poDS->bSwitchedXY = true;
8759 : }
8760 285 : if (nd >= 3)
8761 : {
8762 52 : char szDimName3[NC_MAX_NAME + 1] = {};
8763 : status =
8764 52 : nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 3], szDimName3);
8765 52 : NCDF_ERR(status);
8766 52 : if (nd >= 4)
8767 : {
8768 13 : char szDimName4[NC_MAX_NAME + 1] = {};
8769 : status =
8770 13 : nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 4], szDimName4);
8771 13 : NCDF_ERR(status);
8772 13 : if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false)
8773 : {
8774 0 : CPLError(CE_Warning, CPLE_AppDefined,
8775 : "dimension #%d (%s) is not a Vertical dimension.",
8776 : nd - 3, szDimName3);
8777 : }
8778 13 : if (NCDFIsVarTimeCoord(cdfid, -1, szDimName4) == false)
8779 : {
8780 0 : CPLError(CE_Warning, CPLE_AppDefined,
8781 : "dimension #%d (%s) is not a Time dimension.",
8782 : nd - 4, szDimName4);
8783 : }
8784 : }
8785 : else
8786 : {
8787 75 : if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false &&
8788 36 : NCDFIsVarTimeCoord(cdfid, -1, szDimName3) == false)
8789 : {
8790 0 : CPLError(CE_Warning, CPLE_AppDefined,
8791 : "dimension #%d (%s) is not a "
8792 : "Time or Vertical dimension.",
8793 : nd - 3, szDimName3);
8794 : }
8795 : }
8796 : }
8797 : }
8798 :
8799 : // Get X dimensions information.
8800 : size_t xdim;
8801 372 : poDS->nXDimID = poDS->m_anDimIds[bYXBandOrder ? 1 : nd - 1];
8802 372 : nc_inq_dimlen(cdfid, poDS->nXDimID, &xdim);
8803 :
8804 : // Get Y dimension information.
8805 : size_t ydim;
8806 372 : if (nd >= 2)
8807 : {
8808 368 : poDS->nYDimID = poDS->m_anDimIds[bYXBandOrder ? 0 : nd - 2];
8809 368 : nc_inq_dimlen(cdfid, poDS->nYDimID, &ydim);
8810 : }
8811 : else
8812 : {
8813 4 : poDS->nYDimID = -1;
8814 4 : ydim = 1;
8815 : }
8816 :
8817 372 : if (xdim > INT_MAX || ydim > INT_MAX)
8818 : {
8819 0 : CPLError(CE_Failure, CPLE_AppDefined,
8820 : "Invalid raster dimensions: " CPL_FRMT_GUIB "x" CPL_FRMT_GUIB,
8821 : static_cast<GUIntBig>(xdim), static_cast<GUIntBig>(ydim));
8822 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8823 : // with GDALDataset own mutex.
8824 0 : delete poDS;
8825 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8826 0 : return nullptr;
8827 : }
8828 :
8829 372 : poDS->nRasterXSize = static_cast<int>(xdim);
8830 372 : poDS->nRasterYSize = static_cast<int>(ydim);
8831 :
8832 372 : unsigned int k = 0;
8833 1193 : for (int j = 0; j < nd; j++)
8834 : {
8835 821 : if (poDS->m_anDimIds[j] == poDS->nXDimID)
8836 : {
8837 372 : anBandDimPos[0] = j; // Save Position of XDim
8838 372 : k++;
8839 : }
8840 821 : if (poDS->m_anDimIds[j] == poDS->nYDimID)
8841 : {
8842 368 : anBandDimPos[1] = j; // Save Position of YDim
8843 368 : k++;
8844 : }
8845 : }
8846 : // X and Y Dimension Ids were not found!
8847 372 : if ((nd >= 2 && k != 2) || (nd == 1 && k != 1))
8848 : {
8849 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8850 : // with GDALDataset own mutex.
8851 0 : delete poDS;
8852 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8853 0 : return nullptr;
8854 : }
8855 :
8856 : // Read Metadata for this variable.
8857 :
8858 : // Should disable as is also done at band level, except driver needs the
8859 : // variables as metadata (e.g. projection).
8860 372 : poDS->ReadAttributes(cdfid, var);
8861 :
8862 : // Read Metadata for each dimension.
8863 372 : int *panDimIds = nullptr;
8864 372 : NCDFGetVisibleDims(cdfid, &ndims, &panDimIds);
8865 : // With NetCDF-4 groups panDimIds is not always [0..dim_count-1] like
8866 : // in NetCDF-3 because we see only the dimensions of the selected group
8867 : // and its parents.
8868 : // poDS->papszDimName is indexed by dim IDs, so it must contains all IDs
8869 : // [0..max(panDimIds)], but they are not all useful so we fill names
8870 : // of useless dims with empty string.
8871 372 : if (panDimIds)
8872 : {
8873 372 : const int nMaxDimId = *std::max_element(panDimIds, panDimIds + ndims);
8874 372 : std::set<int> oSetExistingDimIds;
8875 1233 : for (int i = 0; i < ndims; i++)
8876 : {
8877 861 : oSetExistingDimIds.insert(panDimIds[i]);
8878 : }
8879 372 : std::set<int> oSetDimIdsUsedByVar;
8880 1193 : for (int i = 0; i < nd; i++)
8881 : {
8882 821 : oSetDimIdsUsedByVar.insert(poDS->m_anDimIds[i]);
8883 : }
8884 1235 : for (int j = 0; j <= nMaxDimId; j++)
8885 : {
8886 : // Is j dim used?
8887 863 : if (oSetExistingDimIds.find(j) != oSetExistingDimIds.end())
8888 : {
8889 : // Useful dim.
8890 861 : char szTemp[NC_MAX_NAME + 1] = {};
8891 861 : status = nc_inq_dimname(cdfid, j, szTemp);
8892 861 : if (status != NC_NOERR)
8893 : {
8894 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8895 : // deadlock with GDALDataset own
8896 : // mutex.
8897 0 : delete poDS;
8898 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8899 0 : return nullptr;
8900 : }
8901 861 : poDS->papszDimName.AddString(szTemp);
8902 :
8903 861 : if (oSetDimIdsUsedByVar.find(j) != oSetDimIdsUsedByVar.end())
8904 : {
8905 821 : int nDimGroupId = -1;
8906 821 : int nDimVarId = -1;
8907 821 : if (NCDFResolveVar(cdfid, poDS->papszDimName[j],
8908 821 : &nDimGroupId, &nDimVarId) == CE_None)
8909 : {
8910 593 : poDS->ReadAttributes(nDimGroupId, nDimVarId);
8911 : }
8912 : }
8913 : }
8914 : else
8915 : {
8916 : // Useless dim.
8917 2 : poDS->papszDimName.AddString("");
8918 : }
8919 : }
8920 372 : CPLFree(panDimIds);
8921 : }
8922 :
8923 : // Set projection info.
8924 744 : std::vector<std::string> aosRemovedMDItems;
8925 372 : if (nd > 1)
8926 : {
8927 368 : poDS->SetProjectionFromVar(cdfid, var,
8928 : /*bReadSRSOnly=*/false,
8929 : /* pszGivenGM = */ nullptr,
8930 : /* returnProjStr = */ nullptr,
8931 : /* sg = */ nullptr, &aosRemovedMDItems);
8932 : }
8933 :
8934 : // Override bottom-up with GDAL_NETCDF_BOTTOMUP config option.
8935 372 : const char *pszValue = CPLGetConfigOption("GDAL_NETCDF_BOTTOMUP", nullptr);
8936 372 : if (pszValue)
8937 : {
8938 24 : poDS->bBottomUp = CPLTestBool(pszValue);
8939 24 : CPLDebug("GDAL_netCDF",
8940 : "set bBottomUp=%d because GDAL_NETCDF_BOTTOMUP=%s",
8941 24 : static_cast<int>(poDS->bBottomUp), pszValue);
8942 : }
8943 :
8944 : // Save non-spatial dimension info.
8945 :
8946 372 : int *panBandZLev = nullptr;
8947 372 : int nDim = (nd >= 2) ? 2 : 1;
8948 : size_t lev_count;
8949 372 : size_t nTotLevCount = 1;
8950 372 : nc_type nType = NC_NAT;
8951 :
8952 372 : if (nd > 2)
8953 : {
8954 62 : nDim = 2;
8955 62 : panBandZLev = static_cast<int *>(CPLCalloc(nd - 2, sizeof(int)));
8956 :
8957 62 : CPLString osExtraDimNames = "{";
8958 :
8959 62 : char szDimName[NC_MAX_NAME + 1] = {};
8960 :
8961 62 : bool bREPORT_EXTRA_DIM_VALUESWarningEmitted = false;
8962 267 : for (int j = 0; j < nd; j++)
8963 : {
8964 348 : if ((poDS->m_anDimIds[j] != poDS->nXDimID) &&
8965 143 : (poDS->m_anDimIds[j] != poDS->nYDimID))
8966 : {
8967 81 : nc_inq_dimlen(cdfid, poDS->m_anDimIds[j], &lev_count);
8968 81 : nTotLevCount *= lev_count;
8969 81 : panBandZLev[nDim - 2] = static_cast<int>(lev_count);
8970 81 : anBandDimPos[nDim] = j; // Save Position of ZDim
8971 : // Save non-spatial dimension names.
8972 81 : if (nc_inq_dimname(cdfid, poDS->m_anDimIds[j], szDimName) ==
8973 : NC_NOERR)
8974 : {
8975 81 : osExtraDimNames += szDimName;
8976 81 : if (j < nd - 3)
8977 : {
8978 19 : osExtraDimNames += ",";
8979 : }
8980 :
8981 81 : int nIdxGroupID = -1;
8982 81 : int nIdxVarID = Get1DVariableIndexedByDimension(
8983 81 : cdfid, poDS->m_anDimIds[j], szDimName, true,
8984 81 : &nIdxGroupID);
8985 81 : poDS->m_anExtraDimGroupIds.push_back(nIdxGroupID);
8986 81 : poDS->m_anExtraDimVarIds.push_back(nIdxVarID);
8987 :
8988 81 : if (nIdxVarID >= 0)
8989 : {
8990 72 : nc_inq_vartype(nIdxGroupID, nIdxVarID, &nType);
8991 : char szExtraDimDef[NC_MAX_NAME + 1];
8992 72 : snprintf(szExtraDimDef, sizeof(szExtraDimDef),
8993 : "{%ld,%d}", (long)lev_count, nType);
8994 : char szTemp[NC_MAX_NAME + 32 + 1];
8995 72 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
8996 : szDimName);
8997 72 : poDS->papszMetadata = CSLSetNameValue(
8998 : poDS->papszMetadata, szTemp, szExtraDimDef);
8999 :
9000 : // Retrieving data for unlimited dimensions might be
9001 : // costly on network storage, so don't do it.
9002 : // Each band will capture the value along the extra
9003 : // dimension in its NETCDF_DIM_xxxx band metadata item
9004 : // Addresses use case of
9005 : // https://lists.osgeo.org/pipermail/gdal-dev/2023-May/057209.html
9006 : const bool bIsLocal =
9007 72 : VSIIsLocal(osFilenameForNCOpen.c_str());
9008 : bool bListDimValues =
9009 73 : bIsLocal || lev_count == 1 ||
9010 1 : !NCDFIsUnlimitedDim(poDS->eFormat ==
9011 : NCDF_FORMAT_NC4,
9012 1 : cdfid, poDS->m_anDimIds[j]);
9013 : const char *pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES =
9014 72 : CPLGetConfigOption(
9015 : "GDAL_NETCDF_REPORT_EXTRA_DIM_VALUES", nullptr);
9016 72 : if (pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES)
9017 : {
9018 2 : bListDimValues = CPLTestBool(
9019 : pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES);
9020 : }
9021 70 : else if (!bListDimValues && !bIsLocal &&
9022 1 : !bREPORT_EXTRA_DIM_VALUESWarningEmitted)
9023 : {
9024 1 : bREPORT_EXTRA_DIM_VALUESWarningEmitted = true;
9025 1 : CPLDebug(
9026 : "GDAL_netCDF",
9027 : "Listing extra dimension values is skipped "
9028 : "because this dataset is hosted on a network "
9029 : "file system, and such an operation could be "
9030 : "slow. If you still want to proceed, set the "
9031 : "GDAL_NETCDF_REPORT_EXTRA_DIM_VALUES "
9032 : "configuration option to YES");
9033 : }
9034 72 : if (bListDimValues)
9035 : {
9036 70 : char *pszTemp = nullptr;
9037 70 : if (NCDFGet1DVar(nIdxGroupID, nIdxVarID,
9038 70 : &pszTemp) == CE_None)
9039 : {
9040 70 : snprintf(szTemp, sizeof(szTemp),
9041 : "NETCDF_DIM_%s_VALUES", szDimName);
9042 70 : poDS->papszMetadata = CSLSetNameValue(
9043 : poDS->papszMetadata, szTemp, pszTemp);
9044 70 : CPLFree(pszTemp);
9045 : }
9046 : }
9047 : }
9048 : }
9049 : else
9050 : {
9051 0 : poDS->m_anExtraDimGroupIds.push_back(-1);
9052 0 : poDS->m_anExtraDimVarIds.push_back(-1);
9053 : }
9054 :
9055 81 : nDim++;
9056 : }
9057 : }
9058 62 : osExtraDimNames += "}";
9059 62 : poDS->papszMetadata = CSLSetNameValue(
9060 : poDS->papszMetadata, "NETCDF_DIM_EXTRA", osExtraDimNames);
9061 : }
9062 :
9063 : // Store Metadata.
9064 382 : for (const auto &osStr : aosRemovedMDItems)
9065 10 : poDS->papszMetadata =
9066 10 : CSLSetNameValue(poDS->papszMetadata, osStr.c_str(), nullptr);
9067 :
9068 372 : poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
9069 :
9070 : // Create bands.
9071 :
9072 : // Arbitrary threshold.
9073 : int nMaxBandCount =
9074 372 : atoi(CPLGetConfigOption("GDAL_MAX_BAND_COUNT", "32768"));
9075 372 : if (nMaxBandCount <= 0)
9076 0 : nMaxBandCount = 32768;
9077 372 : if (nTotLevCount > static_cast<unsigned int>(nMaxBandCount))
9078 : {
9079 0 : CPLError(CE_Warning, CPLE_AppDefined,
9080 : "Limiting number of bands to %d instead of %u", nMaxBandCount,
9081 : static_cast<unsigned int>(nTotLevCount));
9082 0 : nTotLevCount = static_cast<unsigned int>(nMaxBandCount);
9083 : }
9084 372 : if (poDS->nRasterXSize == 0 || poDS->nRasterYSize == 0)
9085 : {
9086 0 : poDS->nRasterXSize = 0;
9087 0 : poDS->nRasterYSize = 0;
9088 0 : nTotLevCount = 0;
9089 0 : if (poDS->GetLayerCount() == 0)
9090 : {
9091 0 : CPLFree(panBandZLev);
9092 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9093 : // deadlock with GDALDataset own mutex.
9094 0 : delete poDS;
9095 0 : CPLAcquireMutex(hNCMutex, 1000.0);
9096 0 : return nullptr;
9097 : }
9098 : }
9099 372 : if (bSeveralVariablesAsBands)
9100 : {
9101 6 : const auto &listVariables = oMap2DDimsToGroupAndVar.begin()->second;
9102 24 : for (int iBand = 0; iBand < static_cast<int>(listVariables.size());
9103 : ++iBand)
9104 : {
9105 18 : int bandVarGroupId = listVariables[iBand].first;
9106 18 : int bandVarId = listVariables[iBand].second;
9107 : netCDFRasterBand *poBand = new netCDFRasterBand(
9108 0 : netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, bandVarGroupId,
9109 18 : bandVarId, nDim, 0, nullptr, anBandDimPos.data(), iBand + 1);
9110 18 : poDS->SetBand(iBand + 1, poBand);
9111 : }
9112 : }
9113 : else
9114 : {
9115 846 : for (unsigned int lev = 0; lev < nTotLevCount; lev++)
9116 : {
9117 : netCDFRasterBand *poBand = new netCDFRasterBand(
9118 0 : netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, cdfid, var, nDim,
9119 480 : lev, panBandZLev, anBandDimPos.data(), lev + 1);
9120 480 : poDS->SetBand(lev + 1, poBand);
9121 : }
9122 : }
9123 :
9124 372 : if (panBandZLev)
9125 62 : CPLFree(panBandZLev);
9126 : // Handle angular geographic coordinates here
9127 :
9128 : // Initialize any PAM information.
9129 372 : if (bTreatAsSubdataset)
9130 : {
9131 64 : poDS->SetPhysicalFilename(poDS->osFilename);
9132 64 : poDS->SetSubdatasetName(osSubdatasetName);
9133 : }
9134 :
9135 372 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
9136 : // GDALDataset own mutex.
9137 372 : poDS->TryLoadXML();
9138 :
9139 372 : if (bTreatAsSubdataset)
9140 64 : poDS->oOvManager.Initialize(poDS, ":::VIRTUAL:::");
9141 : else
9142 308 : poDS->oOvManager.Initialize(poDS, poDS->osFilename);
9143 :
9144 372 : CPLAcquireMutex(hNCMutex, 1000.0);
9145 :
9146 372 : return poDS;
9147 : }
9148 :
9149 : /************************************************************************/
9150 : /* CopyMetadata() */
9151 : /* */
9152 : /* Create a copy of metadata for NC_GLOBAL or a variable */
9153 : /************************************************************************/
9154 :
9155 169 : static void CopyMetadata(GDALDataset *poSrcDS, GDALRasterBand *poSrcBand,
9156 : GDALRasterBand *poDstBand, int nCdfId, int CDFVarID,
9157 : const char *pszPrefix)
9158 : {
9159 : // Remove the following band meta but set them later from band data.
9160 169 : const char *const papszIgnoreBand[] = {
9161 : CF_ADD_OFFSET, CF_SCALE_FACTOR, "valid_range", "_Unsigned",
9162 : NCDF_FillValue, "coordinates", nullptr};
9163 169 : const char *const papszIgnoreGlobal[] = {"NETCDF_DIM_EXTRA", nullptr};
9164 :
9165 169 : CSLConstList papszMetadata = nullptr;
9166 169 : if (poSrcDS)
9167 : {
9168 72 : papszMetadata = poSrcDS->GetMetadata();
9169 : }
9170 97 : else if (poSrcBand)
9171 : {
9172 97 : papszMetadata = poSrcBand->GetMetadata();
9173 : }
9174 :
9175 655 : for (const auto &[pszKey, pszValue] : cpl::IterateNameValue(papszMetadata))
9176 : {
9177 : #ifdef NCDF_DEBUG
9178 : CPLDebug("GDAL_netCDF", "copy metadata [%s]=[%s]", pszKey, pszValue);
9179 : #endif
9180 :
9181 486 : CPLString osMetaName(pszKey);
9182 :
9183 : // Check for items that match pszPrefix if applicable.
9184 486 : if (pszPrefix && !EQUAL(pszPrefix, ""))
9185 : {
9186 : // Remove prefix.
9187 115 : if (STARTS_WITH(osMetaName.c_str(), pszPrefix))
9188 : {
9189 17 : osMetaName = osMetaName.substr(strlen(pszPrefix));
9190 : }
9191 : // Only copy items that match prefix.
9192 : else
9193 : {
9194 98 : continue;
9195 : }
9196 : }
9197 :
9198 : // Fix various issues with metadata translation.
9199 388 : if (CDFVarID == NC_GLOBAL)
9200 : {
9201 : // Do not copy items in papszIgnoreGlobal and NETCDF_DIM_*.
9202 493 : if ((CSLFindString(papszIgnoreGlobal, osMetaName) != -1) ||
9203 244 : (STARTS_WITH(osMetaName, "NETCDF_DIM_")))
9204 21 : continue;
9205 : // Remove NC_GLOBAL prefix for netcdf global Metadata.
9206 228 : else if (STARTS_WITH(osMetaName, "NC_GLOBAL#"))
9207 : {
9208 33 : osMetaName = osMetaName.substr(strlen("NC_GLOBAL#"));
9209 : }
9210 : // GDAL Metadata renamed as GDAL-[meta].
9211 195 : else if (strstr(osMetaName, "#") == nullptr)
9212 : {
9213 22 : osMetaName = "GDAL_" + osMetaName;
9214 : }
9215 : // Keep time, lev and depth information for safe-keeping.
9216 : // Time and vertical coordinate handling need improvements.
9217 : /*
9218 : else if( STARTS_WITH(szMetaName, "time#") )
9219 : {
9220 : szMetaName[4] = '-';
9221 : }
9222 : else if( STARTS_WITH(szMetaName, "lev#") )
9223 : {
9224 : szMetaName[3] = '-';
9225 : }
9226 : else if( STARTS_WITH(szMetaName, "depth#") )
9227 : {
9228 : szMetaName[5] = '-';
9229 : }
9230 : */
9231 : // Only copy data without # (previously all data was copied).
9232 228 : if (strstr(osMetaName, "#") != nullptr)
9233 173 : continue;
9234 : // netCDF attributes do not like the '#' character.
9235 : // for( unsigned int h=0; h < strlen(szMetaName) -1 ; h++ ) {
9236 : // if( szMetaName[h] == '#') szMetaName[h] = '-';
9237 : // }
9238 : }
9239 : else
9240 : {
9241 : // Do not copy varname, stats, NETCDF_DIM_*, nodata
9242 : // and items in papszIgnoreBand.
9243 139 : if (STARTS_WITH(osMetaName, "NETCDF_VARNAME") ||
9244 107 : STARTS_WITH(osMetaName, "STATISTICS_") ||
9245 107 : STARTS_WITH(osMetaName, "NETCDF_DIM_") ||
9246 74 : STARTS_WITH(osMetaName, "missing_value") ||
9247 293 : STARTS_WITH(osMetaName, "_FillValue") ||
9248 47 : CSLFindString(papszIgnoreBand, osMetaName) != -1)
9249 97 : continue;
9250 : }
9251 :
9252 : #ifdef NCDF_DEBUG
9253 : CPLDebug("GDAL_netCDF", "copy name=[%s] value=[%s]", osMetaName.c_str(),
9254 : pszValue);
9255 : #endif
9256 97 : if (NCDFPutAttr(nCdfId, CDFVarID, osMetaName, pszValue) != CE_None)
9257 : {
9258 0 : CPLDebug("GDAL_netCDF", "NCDFPutAttr(%d, %d, %s, %s) failed",
9259 : nCdfId, CDFVarID, osMetaName.c_str(), pszValue);
9260 : }
9261 : }
9262 :
9263 : // Set add_offset and scale_factor here if present.
9264 169 : if (poSrcBand && poDstBand)
9265 : {
9266 :
9267 97 : int bGotAddOffset = FALSE;
9268 97 : const double dfAddOffset = poSrcBand->GetOffset(&bGotAddOffset);
9269 97 : int bGotScale = FALSE;
9270 97 : const double dfScale = poSrcBand->GetScale(&bGotScale);
9271 :
9272 97 : if (bGotAddOffset && dfAddOffset != 0.0)
9273 1 : poDstBand->SetOffset(dfAddOffset);
9274 97 : if (bGotScale && dfScale != 1.0)
9275 1 : poDstBand->SetScale(dfScale);
9276 : }
9277 169 : }
9278 :
9279 : /************************************************************************/
9280 : /* CreateLL() */
9281 : /* */
9282 : /* Shared functionality between netCDFDataset::Create() and */
9283 : /* netCDF::CreateCopy() for creating netcdf file based on a set of */
9284 : /* options and a configuration. */
9285 : /************************************************************************/
9286 :
9287 205 : netCDFDataset *netCDFDataset::CreateLL(const char *pszFilename, int nXSize,
9288 : int nYSize, int nBandsIn,
9289 : CSLConstList papszOptions)
9290 : {
9291 205 : if (!((nXSize == 0 && nYSize == 0 && nBandsIn == 0) ||
9292 132 : (nXSize > 0 && nYSize > 0 && nBandsIn > 0)))
9293 : {
9294 1 : return nullptr;
9295 : }
9296 :
9297 204 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
9298 : // GDALDataset own mutex.
9299 204 : netCDFDataset *poDS = new netCDFDataset();
9300 204 : CPLAcquireMutex(hNCMutex, 1000.0);
9301 :
9302 204 : poDS->nRasterXSize = nXSize;
9303 204 : poDS->nRasterYSize = nYSize;
9304 204 : poDS->eAccess = GA_Update;
9305 204 : poDS->osFilename = pszFilename;
9306 :
9307 : // From gtiff driver, is this ok?
9308 : /*
9309 : poDS->nBlockXSize = nXSize;
9310 : poDS->nBlockYSize = 1;
9311 : poDS->nBlocksPerBand =
9312 : DIV_ROUND_UP((nYSize, poDS->nBlockYSize))
9313 : * DIV_ROUND_UP((nXSize, poDS->nBlockXSize));
9314 : */
9315 :
9316 : // process options.
9317 204 : poDS->papszCreationOptions = CSLDuplicate(papszOptions);
9318 204 : poDS->ProcessCreationOptions();
9319 :
9320 204 : if (poDS->eMultipleLayerBehavior == SEPARATE_FILES)
9321 : {
9322 : VSIStatBuf sStat;
9323 3 : if (VSIStat(pszFilename, &sStat) == 0)
9324 : {
9325 0 : if (!VSI_ISDIR(sStat.st_mode))
9326 : {
9327 0 : CPLError(CE_Failure, CPLE_FileIO,
9328 : "%s is an existing file, but not a directory",
9329 : pszFilename);
9330 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9331 : // deadlock with GDALDataset own
9332 : // mutex.
9333 0 : delete poDS;
9334 0 : CPLAcquireMutex(hNCMutex, 1000.0);
9335 0 : return nullptr;
9336 : }
9337 : }
9338 3 : else if (VSIMkdir(pszFilename, 0755) != 0)
9339 : {
9340 1 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s directory",
9341 : pszFilename);
9342 1 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9343 : // deadlock with GDALDataset own mutex.
9344 1 : delete poDS;
9345 1 : CPLAcquireMutex(hNCMutex, 1000.0);
9346 1 : return nullptr;
9347 : }
9348 :
9349 2 : return poDS;
9350 : }
9351 : // Create the dataset.
9352 402 : CPLString osFilenameForNCCreate(pszFilename);
9353 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
9354 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
9355 : {
9356 : char *pszTemp =
9357 : CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
9358 : osFilenameForNCCreate = pszTemp;
9359 : CPLFree(pszTemp);
9360 : }
9361 : #endif
9362 :
9363 : #if defined(_WIN32)
9364 : {
9365 : // Works around bug of msys2 netCDF 4.9.0 package where nc_create()
9366 : // crashes
9367 : VSIStatBuf sStat;
9368 : const std::string osDirname =
9369 : CPLGetDirnameSafe(osFilenameForNCCreate.c_str());
9370 : if (VSIStat(osDirname.c_str(), &sStat) != 0)
9371 : {
9372 : CPLError(CE_Failure, CPLE_OpenFailed,
9373 : "Unable to create netCDF file %s: non existing output "
9374 : "directory",
9375 : pszFilename);
9376 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9377 : // deadlock with GDALDataset own mutex.
9378 : delete poDS;
9379 : CPLAcquireMutex(hNCMutex, 1000.0);
9380 : return nullptr;
9381 : }
9382 : }
9383 : #endif
9384 :
9385 : int status =
9386 201 : nc_create(osFilenameForNCCreate, poDS->nCreateMode, &(poDS->cdfid));
9387 :
9388 : // Put into define mode.
9389 201 : poDS->SetDefineMode(true);
9390 :
9391 201 : if (status != NC_NOERR)
9392 : {
9393 30 : CPLError(CE_Failure, CPLE_OpenFailed,
9394 : "Unable to create netCDF file %s (Error code %d): %s .",
9395 : pszFilename, status, nc_strerror(status));
9396 30 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
9397 : // with GDALDataset own mutex.
9398 30 : delete poDS;
9399 30 : CPLAcquireMutex(hNCMutex, 1000.0);
9400 30 : return nullptr;
9401 : }
9402 :
9403 : // Define dimensions.
9404 171 : if (nXSize > 0 && nYSize > 0)
9405 : {
9406 118 : poDS->papszDimName.AddString(NCDF_DIMNAME_X);
9407 : status =
9408 118 : nc_def_dim(poDS->cdfid, NCDF_DIMNAME_X, nXSize, &(poDS->nXDimID));
9409 118 : NCDF_ERR(status);
9410 118 : CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
9411 : poDS->cdfid, NCDF_DIMNAME_X, nXSize, poDS->nXDimID);
9412 :
9413 118 : poDS->papszDimName.AddString(NCDF_DIMNAME_Y);
9414 : status =
9415 118 : nc_def_dim(poDS->cdfid, NCDF_DIMNAME_Y, nYSize, &(poDS->nYDimID));
9416 118 : NCDF_ERR(status);
9417 118 : CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
9418 : poDS->cdfid, NCDF_DIMNAME_Y, nYSize, poDS->nYDimID);
9419 : }
9420 :
9421 171 : return poDS;
9422 : }
9423 :
9424 : /************************************************************************/
9425 : /* Create() */
9426 : /************************************************************************/
9427 :
9428 127 : GDALDataset *netCDFDataset::Create(const char *pszFilename, int nXSize,
9429 : int nYSize, int nBandsIn, GDALDataType eType,
9430 : CSLConstList papszOptions)
9431 : {
9432 127 : CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::Create(%s, ...)",
9433 : pszFilename);
9434 :
9435 : const char *legacyCreationOp =
9436 127 : CSLFetchNameValueDef(papszOptions, "GEOMETRY_ENCODING", "CF_1.8");
9437 254 : std::string legacyCreationOp_s = std::string(legacyCreationOp);
9438 :
9439 : // Check legacy creation op FIRST
9440 :
9441 127 : bool legacyCreateMode = false;
9442 :
9443 127 : if (nXSize != 0 || nYSize != 0 || nBandsIn != 0)
9444 : {
9445 56 : legacyCreateMode = true;
9446 : }
9447 71 : else if (legacyCreationOp_s == "CF_1.8")
9448 : {
9449 54 : legacyCreateMode = false;
9450 : }
9451 :
9452 17 : else if (legacyCreationOp_s == "WKT")
9453 : {
9454 17 : legacyCreateMode = true;
9455 : }
9456 :
9457 : else
9458 : {
9459 0 : CPLError(
9460 : CE_Failure, CPLE_NotSupported,
9461 : "Dataset creation option GEOMETRY_ENCODING=%s is not supported.",
9462 : legacyCreationOp_s.c_str());
9463 0 : return nullptr;
9464 : }
9465 :
9466 254 : CPLStringList aosOptions(CSLDuplicate(papszOptions));
9467 240 : if (aosOptions.FetchNameValue("FORMAT") == nullptr &&
9468 113 : (eType == GDT_UInt16 || eType == GDT_UInt32 || eType == GDT_UInt64 ||
9469 : eType == GDT_Int64))
9470 : {
9471 10 : CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
9472 10 : aosOptions.SetNameValue("FORMAT", "NC4");
9473 : }
9474 :
9475 254 : CPLStringList aosBandNames;
9476 127 : if (const char *pszBandNames = aosOptions.FetchNameValue("BAND_NAMES"))
9477 : {
9478 : aosBandNames =
9479 2 : CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);
9480 :
9481 2 : if (aosBandNames.Count() != nBandsIn)
9482 : {
9483 1 : CPLError(CE_Failure, CPLE_OpenFailed,
9484 : "Attempted to create netCDF with %d bands but %d names "
9485 : "provided in BAND_NAMES.",
9486 : nBandsIn, aosBandNames.Count());
9487 :
9488 1 : return nullptr;
9489 : }
9490 : }
9491 :
9492 252 : CPLMutexHolderD(&hNCMutex);
9493 :
9494 126 : auto poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize, nBandsIn,
9495 126 : aosOptions.List());
9496 :
9497 126 : if (!poDS)
9498 19 : return nullptr;
9499 :
9500 107 : if (!legacyCreateMode)
9501 : {
9502 37 : poDS->bSGSupport = true;
9503 37 : poDS->vcdf.enableFullVirtualMode();
9504 : }
9505 :
9506 : else
9507 : {
9508 70 : poDS->bSGSupport = false;
9509 : }
9510 :
9511 : // Should we write signed or unsigned byte?
9512 : // TODO should this only be done in Create()
9513 107 : poDS->bSignedData = true;
9514 107 : const char *pszValue = CSLFetchNameValueDef(papszOptions, "PIXELTYPE", "");
9515 107 : if (eType == GDT_UInt8 && !EQUAL(pszValue, "SIGNEDBYTE"))
9516 15 : poDS->bSignedData = false;
9517 :
9518 : // Add Conventions, GDAL info and history.
9519 107 : if (poDS->cdfid >= 0)
9520 : {
9521 : const char *CF_Vector_Conv =
9522 173 : poDS->bSGSupport ||
9523 : // Use of variable length strings require CF-1.8
9524 68 : EQUAL(aosOptions.FetchNameValueDef("FORMAT", ""), "NC4")
9525 : ? NCDF_CONVENTIONS_CF_V1_8
9526 173 : : NCDF_CONVENTIONS_CF_V1_6;
9527 105 : poDS->bWriteGDALVersion = CPLTestBool(
9528 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
9529 105 : poDS->bWriteGDALHistory = CPLTestBool(
9530 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
9531 105 : NCDFAddGDALHistory(poDS->cdfid, pszFilename, poDS->bWriteGDALVersion,
9532 105 : poDS->bWriteGDALHistory, "", "Create",
9533 : (nBandsIn == 0) ? CF_Vector_Conv
9534 : : GDAL_DEFAULT_NCDF_CONVENTIONS);
9535 : }
9536 :
9537 : // Define bands.
9538 198 : for (int iBand = 1; iBand <= nBandsIn; iBand++)
9539 : {
9540 : const char *pszBandName =
9541 91 : aosBandNames.empty() ? nullptr : aosBandNames[iBand - 1];
9542 :
9543 91 : poDS->SetBand(iBand, new netCDFRasterBand(
9544 91 : netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS,
9545 91 : eType, iBand, poDS->bSignedData, pszBandName));
9546 : }
9547 :
9548 107 : CPLDebug("GDAL_netCDF", "netCDFDataset::Create(%s, ...) done", pszFilename);
9549 : // Return same dataset.
9550 107 : return poDS;
9551 : }
9552 :
9553 : template <class T>
9554 97 : static CPLErr NCDFCopyBand(GDALRasterBand *poSrcBand, GDALRasterBand *poDstBand,
9555 : int nXSize, int nYSize, GDALProgressFunc pfnProgress,
9556 : void *pProgressData)
9557 : {
9558 97 : const GDALDataType eDT = poSrcBand->GetRasterDataType();
9559 97 : T *patScanline = static_cast<T *>(VSI_MALLOC2_VERBOSE(nXSize, sizeof(T)));
9560 97 : CPLErr eErr = patScanline ? CE_None : CE_Failure;
9561 :
9562 6414 : for (int iLine = 0; iLine < nYSize && eErr == CE_None; iLine++)
9563 : {
9564 6317 : eErr = poSrcBand->RasterIO(GF_Read, 0, iLine, nXSize, 1, patScanline,
9565 : nXSize, 1, eDT, 0, 0, nullptr);
9566 6317 : if (eErr != CE_None)
9567 : {
9568 0 : CPLDebug(
9569 : "GDAL_netCDF",
9570 : "NCDFCopyBand(), poSrcBand->RasterIO() returned error code %d",
9571 : eErr);
9572 : }
9573 : else
9574 : {
9575 6317 : eErr =
9576 : poDstBand->RasterIO(GF_Write, 0, iLine, nXSize, 1, patScanline,
9577 : nXSize, 1, eDT, 0, 0, nullptr);
9578 6317 : if (eErr != CE_None)
9579 0 : CPLDebug("GDAL_netCDF",
9580 : "NCDFCopyBand(), poDstBand->RasterIO() returned error "
9581 : "code %d",
9582 : eErr);
9583 : }
9584 :
9585 6317 : if (nYSize > 10 && (iLine % (nYSize / 10) == 1))
9586 : {
9587 317 : if (!pfnProgress(1.0 * iLine / nYSize, nullptr, pProgressData))
9588 : {
9589 0 : eErr = CE_Failure;
9590 0 : CPLError(CE_Failure, CPLE_UserInterrupt,
9591 : "User terminated CreateCopy()");
9592 : }
9593 : }
9594 : }
9595 :
9596 97 : CPLFree(patScanline);
9597 :
9598 97 : pfnProgress(1.0, nullptr, pProgressData);
9599 :
9600 97 : return eErr;
9601 : }
9602 :
9603 : /************************************************************************/
9604 : /* CreateCopy() */
9605 : /************************************************************************/
9606 :
9607 : GDALDataset *
9608 93 : netCDFDataset::CreateCopy(const char *pszFilename, GDALDataset *poSrcDS,
9609 : CPL_UNUSED int bStrict, CSLConstList papszOptions,
9610 : GDALProgressFunc pfnProgress, void *pProgressData)
9611 : {
9612 186 : CPLMutexHolderD(&hNCMutex);
9613 :
9614 93 : CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::CreateCopy(%s, ...)",
9615 : pszFilename);
9616 :
9617 93 : if (poSrcDS->GetRootGroup())
9618 : {
9619 10 : auto poDrv = GDALDriver::FromHandle(GDALGetDriverByName("netCDF"));
9620 10 : if (poDrv)
9621 : {
9622 10 : return poDrv->DefaultCreateCopy(pszFilename, poSrcDS, bStrict,
9623 : papszOptions, pfnProgress,
9624 10 : pProgressData);
9625 : }
9626 : }
9627 :
9628 83 : const int nBands = poSrcDS->GetRasterCount();
9629 83 : const int nXSize = poSrcDS->GetRasterXSize();
9630 83 : const int nYSize = poSrcDS->GetRasterYSize();
9631 83 : const char *pszWKT = poSrcDS->GetProjectionRef();
9632 :
9633 : // Check input bands for errors.
9634 83 : if (nBands == 0)
9635 : {
9636 1 : CPLError(CE_Failure, CPLE_NotSupported,
9637 : "NetCDF driver does not support "
9638 : "source dataset with zero band.");
9639 1 : return nullptr;
9640 : }
9641 :
9642 82 : GDALDataType eDT = GDT_Unknown;
9643 82 : GDALRasterBand *poSrcBand = nullptr;
9644 193 : for (int iBand = 1; iBand <= nBands; iBand++)
9645 : {
9646 115 : poSrcBand = poSrcDS->GetRasterBand(iBand);
9647 115 : eDT = poSrcBand->GetRasterDataType();
9648 115 : if (eDT == GDT_Unknown || GDALDataTypeIsComplex(eDT))
9649 : {
9650 4 : CPLError(CE_Failure, CPLE_NotSupported,
9651 : "NetCDF driver does not support source dataset with band "
9652 : "of complex type.");
9653 4 : return nullptr;
9654 : }
9655 : }
9656 :
9657 156 : CPLStringList aosBandNames;
9658 78 : if (const char *pszBandNames =
9659 78 : CSLFetchNameValue(papszOptions, "BAND_NAMES"))
9660 : {
9661 : aosBandNames =
9662 2 : CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);
9663 :
9664 2 : if (aosBandNames.Count() != nBands)
9665 : {
9666 1 : CPLError(CE_Failure, CPLE_OpenFailed,
9667 : "Attempted to create netCDF with %d bands but %d names "
9668 : "provided in BAND_NAMES.",
9669 : nBands, aosBandNames.Count());
9670 :
9671 1 : return nullptr;
9672 : }
9673 : }
9674 :
9675 77 : if (!pfnProgress(0.0, nullptr, pProgressData))
9676 0 : return nullptr;
9677 :
9678 : // Same as in Create().
9679 154 : CPLStringList aosOptions(CSLDuplicate(papszOptions));
9680 145 : if (aosOptions.FetchNameValue("FORMAT") == nullptr &&
9681 68 : (eDT == GDT_UInt16 || eDT == GDT_UInt32 || eDT == GDT_UInt64 ||
9682 : eDT == GDT_Int64))
9683 : {
9684 6 : CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
9685 6 : aosOptions.SetNameValue("FORMAT", "NC4");
9686 : }
9687 77 : netCDFDataset *poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize,
9688 77 : nBands, aosOptions.List());
9689 77 : if (!poDS)
9690 13 : return nullptr;
9691 :
9692 : // Copy global metadata.
9693 : // Add Conventions, GDAL info and history.
9694 64 : CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid, NC_GLOBAL, nullptr);
9695 64 : const bool bWriteGDALVersion = CPLTestBool(
9696 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
9697 64 : const bool bWriteGDALHistory = CPLTestBool(
9698 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
9699 64 : NCDFAddGDALHistory(
9700 : poDS->cdfid, pszFilename, bWriteGDALVersion, bWriteGDALHistory,
9701 64 : poSrcDS->GetMetadataItem("NC_GLOBAL#history"), "CreateCopy",
9702 64 : poSrcDS->GetMetadataItem("NC_GLOBAL#Conventions"));
9703 :
9704 64 : pfnProgress(0.1, nullptr, pProgressData);
9705 :
9706 : // Check for extra dimensions.
9707 64 : int nDim = 2;
9708 : CPLStringList aosExtraDimNames =
9709 128 : NCDFTokenizeArray(poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
9710 :
9711 64 : if (!aosExtraDimNames.empty())
9712 : {
9713 5 : size_t nDimSizeTot = 1;
9714 : // first make sure dimensions lengths compatible with band count
9715 : // for( int i=0; i<CSLCount(papszExtraDimNames ); i++ ) {
9716 13 : for (int i = aosExtraDimNames.size() - 1; i >= 0; i--)
9717 : {
9718 : char szTemp[NC_MAX_NAME + 32 + 1];
9719 8 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
9720 : aosExtraDimNames[i]);
9721 : const CPLStringList aosExtraDimValues =
9722 8 : NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
9723 8 : const size_t nDimSize = atol(aosExtraDimValues[0]);
9724 8 : nDimSizeTot *= nDimSize;
9725 : }
9726 5 : if (nDimSizeTot == (size_t)nBands)
9727 : {
9728 5 : nDim = 2 + aosExtraDimNames.size();
9729 : }
9730 : else
9731 : {
9732 : // if nBands != #bands computed raise a warning
9733 : // just issue a debug message, because it was probably intentional
9734 0 : CPLDebug("GDAL_netCDF",
9735 : "Warning: Number of bands (%d) is not compatible with "
9736 : "dimensions "
9737 : "(total=%ld names=%s)",
9738 : nBands, (long)nDimSizeTot,
9739 0 : poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
9740 0 : aosExtraDimNames.clear();
9741 : }
9742 : }
9743 :
9744 64 : int *panDimIds = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
9745 64 : int *panBandDimPos = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
9746 :
9747 : nc_type nVarType;
9748 64 : int *panBandZLev = nullptr;
9749 64 : int *panDimVarIds = nullptr;
9750 :
9751 64 : if (nDim > 2)
9752 : {
9753 5 : panBandZLev = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
9754 5 : panDimVarIds = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
9755 :
9756 : // Define all dims.
9757 13 : for (int i = aosExtraDimNames.size() - 1; i >= 0; i--)
9758 : {
9759 8 : poDS->papszDimName.AddString(aosExtraDimNames[i]);
9760 : char szTemp[NC_MAX_NAME + 32 + 1];
9761 8 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
9762 : aosExtraDimNames[i]);
9763 : const CPLStringList aosExtraDimValues =
9764 16 : NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
9765 : const int nDimSize =
9766 8 : aosExtraDimValues.empty() ? 0 : atoi(aosExtraDimValues[0]);
9767 : // nc_type is an enum in netcdf-3, needs casting.
9768 0 : nVarType = static_cast<nc_type>(
9769 8 : aosExtraDimValues.size() >= 2 ? atol(aosExtraDimValues[1]) : 0);
9770 8 : panBandZLev[i] = nDimSize;
9771 8 : panBandDimPos[i + 2] = i; // Save Position of ZDim.
9772 :
9773 : // Define dim.
9774 8 : int status = nc_def_dim(poDS->cdfid, aosExtraDimNames[i], nDimSize,
9775 8 : &(panDimIds[i]));
9776 8 : NCDF_ERR(status);
9777 :
9778 : // Define dim var.
9779 8 : int anDim[1] = {panDimIds[i]};
9780 8 : status = nc_def_var(poDS->cdfid, aosExtraDimNames[i], nVarType, 1,
9781 8 : anDim, &(panDimVarIds[i]));
9782 8 : NCDF_ERR(status);
9783 :
9784 : // Add dim metadata, using global var# items.
9785 8 : snprintf(szTemp, sizeof(szTemp), "%s#", aosExtraDimNames[i]);
9786 8 : CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid,
9787 8 : panDimVarIds[i], szTemp);
9788 : }
9789 : }
9790 :
9791 : // Copy GeoTransform and Projection.
9792 :
9793 : // Copy geolocation info.
9794 64 : CSLConstList papszGeolocationInfo = poSrcDS->GetMetadata("GEOLOCATION");
9795 64 : if (papszGeolocationInfo != nullptr)
9796 5 : poDS->GDALPamDataset::SetMetadata(papszGeolocationInfo, "GEOLOCATION");
9797 :
9798 : // Copy geotransform.
9799 64 : bool bGotGeoTransform = false;
9800 64 : GDALGeoTransform gt;
9801 64 : CPLErr eErr = poSrcDS->GetGeoTransform(gt);
9802 64 : if (eErr == CE_None)
9803 : {
9804 46 : poDS->SetGeoTransform(gt);
9805 : // Disable AddProjectionVars() from being called.
9806 46 : bGotGeoTransform = true;
9807 46 : poDS->m_bHasGeoTransform = false;
9808 : }
9809 :
9810 : // Copy projection.
9811 64 : void *pScaledProgress = nullptr;
9812 64 : if (bGotGeoTransform || (pszWKT && pszWKT[0] != 0))
9813 : {
9814 47 : poDS->SetProjection(pszWKT ? pszWKT : "");
9815 :
9816 : // Now we can call AddProjectionVars() directly.
9817 47 : poDS->m_bHasGeoTransform = bGotGeoTransform;
9818 47 : poDS->AddProjectionVars(true, nullptr, nullptr);
9819 : pScaledProgress =
9820 47 : GDALCreateScaledProgress(0.1, 0.25, pfnProgress, pProgressData);
9821 47 : poDS->AddProjectionVars(false, GDALScaledProgress, pScaledProgress);
9822 47 : GDALDestroyScaledProgress(pScaledProgress);
9823 : }
9824 : else
9825 : {
9826 17 : poDS->bBottomUp =
9827 17 : CPL_TO_BOOL(CSLFetchBoolean(papszOptions, "WRITE_BOTTOMUP", TRUE));
9828 17 : if (papszGeolocationInfo)
9829 : {
9830 4 : poDS->AddProjectionVars(true, nullptr, nullptr);
9831 4 : poDS->AddProjectionVars(false, nullptr, nullptr);
9832 : }
9833 : }
9834 :
9835 : // Save X,Y dim positions.
9836 64 : panDimIds[nDim - 1] = poDS->nXDimID;
9837 64 : panBandDimPos[0] = nDim - 1;
9838 64 : panDimIds[nDim - 2] = poDS->nYDimID;
9839 64 : panBandDimPos[1] = nDim - 2;
9840 :
9841 : // Write extra dim values - after projection for optimization.
9842 64 : if (nDim > 2)
9843 : {
9844 : // Make sure we are in data mode.
9845 5 : poDS->SetDefineMode(false);
9846 13 : for (int i = aosExtraDimNames.size() - 1; i >= 0; i--)
9847 : {
9848 : char szTemp[NC_MAX_NAME + 32 + 1];
9849 8 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_VALUES",
9850 : aosExtraDimNames[i]);
9851 8 : if (poSrcDS->GetMetadataItem(szTemp) != nullptr)
9852 : {
9853 8 : NCDFPut1DVar(poDS->cdfid, panDimVarIds[i],
9854 8 : poSrcDS->GetMetadataItem(szTemp));
9855 : }
9856 : }
9857 : }
9858 :
9859 64 : pfnProgress(0.25, nullptr, pProgressData);
9860 :
9861 : // Define Bands.
9862 64 : netCDFRasterBand *poBand = nullptr;
9863 64 : int nBandID = -1;
9864 :
9865 161 : for (int iBand = 1; iBand <= nBands; iBand++)
9866 : {
9867 97 : CPLDebug("GDAL_netCDF", "creating band # %d/%d nDim = %d", iBand,
9868 : nBands, nDim);
9869 :
9870 97 : poSrcBand = poSrcDS->GetRasterBand(iBand);
9871 97 : eDT = poSrcBand->GetRasterDataType();
9872 :
9873 : // Get var name from NETCDF_VARNAME.
9874 : const char *pszNETCDF_VARNAME =
9875 97 : poSrcBand->GetMetadataItem("NETCDF_VARNAME");
9876 : char szBandName[NC_MAX_NAME + 1];
9877 97 : if (!aosBandNames.empty())
9878 : {
9879 2 : snprintf(szBandName, sizeof(szBandName), "%s",
9880 : aosBandNames[iBand - 1]);
9881 : }
9882 95 : else if (pszNETCDF_VARNAME)
9883 : {
9884 32 : if (nBands > 1 && aosExtraDimNames.empty())
9885 0 : snprintf(szBandName, sizeof(szBandName), "%s%d",
9886 : pszNETCDF_VARNAME, iBand);
9887 : else
9888 32 : snprintf(szBandName, sizeof(szBandName), "%s",
9889 : pszNETCDF_VARNAME);
9890 : }
9891 : else
9892 : {
9893 63 : szBandName[0] = '\0';
9894 : }
9895 :
9896 : // Get long_name from <var>#long_name.
9897 97 : const char *pszLongName = "";
9898 97 : if (pszNETCDF_VARNAME)
9899 : {
9900 : pszLongName =
9901 64 : poSrcDS->GetMetadataItem(std::string(pszNETCDF_VARNAME)
9902 32 : .append("#")
9903 32 : .append(CF_LNG_NAME)
9904 32 : .c_str());
9905 32 : if (!pszLongName)
9906 25 : pszLongName = "";
9907 : }
9908 :
9909 97 : constexpr bool bSignedData = false;
9910 :
9911 97 : if (nDim > 2)
9912 27 : poBand = new netCDFRasterBand(
9913 27 : netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
9914 : bSignedData, szBandName, pszLongName, nBandID, nDim, iBand - 1,
9915 27 : panBandZLev, panBandDimPos, panDimIds);
9916 : else
9917 70 : poBand = new netCDFRasterBand(
9918 70 : netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
9919 70 : bSignedData, szBandName, pszLongName);
9920 :
9921 97 : poDS->SetBand(iBand, poBand);
9922 :
9923 : // Set nodata value, if any.
9924 97 : GDALCopyNoDataValue(poBand, poSrcBand);
9925 :
9926 : // Copy Metadata for band.
9927 97 : CopyMetadata(nullptr, poSrcDS->GetRasterBand(iBand), poBand,
9928 : poDS->cdfid, poBand->nZId);
9929 :
9930 : // If more than 2D pass the first band's netcdf var ID to subsequent
9931 : // bands.
9932 97 : if (nDim > 2)
9933 27 : nBandID = poBand->nZId;
9934 : }
9935 :
9936 : // Write projection variable to band variable.
9937 64 : poDS->AddGridMappingRef();
9938 :
9939 64 : pfnProgress(0.5, nullptr, pProgressData);
9940 :
9941 : // Write bands.
9942 :
9943 : // Make sure we are in data mode.
9944 64 : poDS->SetDefineMode(false);
9945 :
9946 64 : double dfTemp = 0.5;
9947 :
9948 64 : eErr = CE_None;
9949 :
9950 161 : for (int iBand = 1; iBand <= nBands && eErr == CE_None; iBand++)
9951 : {
9952 97 : const double dfTemp2 = dfTemp + 0.4 / nBands;
9953 97 : pScaledProgress = GDALCreateScaledProgress(dfTemp, dfTemp2, pfnProgress,
9954 : pProgressData);
9955 97 : dfTemp = dfTemp2;
9956 :
9957 97 : CPLDebug("GDAL_netCDF", "copying band data # %d/%d ", iBand, nBands);
9958 :
9959 97 : poSrcBand = poSrcDS->GetRasterBand(iBand);
9960 97 : eDT = poSrcBand->GetRasterDataType();
9961 :
9962 97 : GDALRasterBand *poDstBand = poDS->GetRasterBand(iBand);
9963 :
9964 : // Copy band data.
9965 97 : if (eDT == GDT_UInt8)
9966 : {
9967 57 : CPLDebug("GDAL_netCDF", "GByte Band#%d", iBand);
9968 57 : eErr = NCDFCopyBand<GByte>(poSrcBand, poDstBand, nXSize, nYSize,
9969 : GDALScaledProgress, pScaledProgress);
9970 : }
9971 40 : else if (eDT == GDT_Int8)
9972 : {
9973 1 : CPLDebug("GDAL_netCDF", "GInt8 Band#%d", iBand);
9974 1 : eErr = NCDFCopyBand<GInt8>(poSrcBand, poDstBand, nXSize, nYSize,
9975 : GDALScaledProgress, pScaledProgress);
9976 : }
9977 39 : else if (eDT == GDT_UInt16)
9978 : {
9979 2 : CPLDebug("GDAL_netCDF", "GUInt16 Band#%d", iBand);
9980 2 : eErr = NCDFCopyBand<GInt16>(poSrcBand, poDstBand, nXSize, nYSize,
9981 : GDALScaledProgress, pScaledProgress);
9982 : }
9983 37 : else if (eDT == GDT_Int16)
9984 : {
9985 5 : CPLDebug("GDAL_netCDF", "GInt16 Band#%d", iBand);
9986 5 : eErr = NCDFCopyBand<GUInt16>(poSrcBand, poDstBand, nXSize, nYSize,
9987 : GDALScaledProgress, pScaledProgress);
9988 : }
9989 32 : else if (eDT == GDT_UInt32)
9990 : {
9991 2 : CPLDebug("GDAL_netCDF", "GUInt32 Band#%d", iBand);
9992 2 : eErr = NCDFCopyBand<GUInt32>(poSrcBand, poDstBand, nXSize, nYSize,
9993 : GDALScaledProgress, pScaledProgress);
9994 : }
9995 30 : else if (eDT == GDT_Int32)
9996 : {
9997 18 : CPLDebug("GDAL_netCDF", "GInt32 Band#%d", iBand);
9998 18 : eErr = NCDFCopyBand<GInt32>(poSrcBand, poDstBand, nXSize, nYSize,
9999 : GDALScaledProgress, pScaledProgress);
10000 : }
10001 12 : else if (eDT == GDT_UInt64)
10002 : {
10003 2 : CPLDebug("GDAL_netCDF", "GUInt64 Band#%d", iBand);
10004 2 : eErr = NCDFCopyBand<std::uint64_t>(poSrcBand, poDstBand, nXSize,
10005 : nYSize, GDALScaledProgress,
10006 : pScaledProgress);
10007 : }
10008 10 : else if (eDT == GDT_Int64)
10009 : {
10010 2 : CPLDebug("GDAL_netCDF", "GInt64 Band#%d", iBand);
10011 : eErr =
10012 2 : NCDFCopyBand<std::int64_t>(poSrcBand, poDstBand, nXSize, nYSize,
10013 : GDALScaledProgress, pScaledProgress);
10014 : }
10015 8 : else if (eDT == GDT_Float32)
10016 : {
10017 6 : CPLDebug("GDAL_netCDF", "float Band#%d", iBand);
10018 6 : eErr = NCDFCopyBand<float>(poSrcBand, poDstBand, nXSize, nYSize,
10019 : GDALScaledProgress, pScaledProgress);
10020 : }
10021 2 : else if (eDT == GDT_Float64)
10022 : {
10023 2 : CPLDebug("GDAL_netCDF", "double Band#%d", iBand);
10024 2 : eErr = NCDFCopyBand<double>(poSrcBand, poDstBand, nXSize, nYSize,
10025 : GDALScaledProgress, pScaledProgress);
10026 : }
10027 : else
10028 : {
10029 0 : CPLError(CE_Failure, CPLE_NotSupported,
10030 : "The NetCDF driver does not support GDAL data type %d",
10031 : eDT);
10032 : }
10033 :
10034 97 : GDALDestroyScaledProgress(pScaledProgress);
10035 : }
10036 :
10037 64 : delete (poDS);
10038 :
10039 64 : CPLFree(panDimIds);
10040 64 : CPLFree(panBandDimPos);
10041 64 : CPLFree(panBandZLev);
10042 64 : CPLFree(panDimVarIds);
10043 :
10044 64 : if (eErr != CE_None)
10045 0 : return nullptr;
10046 :
10047 64 : pfnProgress(0.95, nullptr, pProgressData);
10048 :
10049 : // Re-open dataset so we can return it.
10050 128 : CPLStringList aosOpenOptions;
10051 64 : aosOpenOptions.AddString("VARIABLES_AS_BANDS=YES");
10052 64 : GDALOpenInfo oOpenInfo(pszFilename, GA_Update);
10053 64 : oOpenInfo.nOpenFlags = GDAL_OF_RASTER | GDAL_OF_UPDATE;
10054 64 : oOpenInfo.papszOpenOptions = aosOpenOptions.List();
10055 64 : auto poRetDS = Open(&oOpenInfo);
10056 :
10057 : // PAM cloning is disabled. See bug #4244.
10058 : // if( poDS )
10059 : // poDS->CloneInfo(poSrcDS, GCIF_PAM_DEFAULT);
10060 :
10061 64 : pfnProgress(1.0, nullptr, pProgressData);
10062 :
10063 64 : return poRetDS;
10064 : }
10065 :
10066 : // Note: some logic depends on bIsProjected and bIsGeoGraphic.
10067 : // May not be known when Create() is called, see AddProjectionVars().
10068 314 : void netCDFDataset::ProcessCreationOptions()
10069 : {
10070 : const char *pszConfig =
10071 314 : CSLFetchNameValue(papszCreationOptions, "CONFIG_FILE");
10072 314 : if (pszConfig != nullptr)
10073 : {
10074 4 : if (oWriterConfig.Parse(pszConfig))
10075 : {
10076 : // Override dataset creation options from the config file
10077 2 : for (const auto &[osName, osValue] :
10078 3 : oWriterConfig.m_oDatasetCreationOptions)
10079 : {
10080 1 : papszCreationOptions =
10081 1 : CSLSetNameValue(papszCreationOptions, osName, osValue);
10082 : }
10083 : }
10084 : }
10085 :
10086 : // File format.
10087 314 : eFormat = NCDF_FORMAT_NC;
10088 314 : const char *pszValue = CSLFetchNameValue(papszCreationOptions, "FORMAT");
10089 314 : if (pszValue != nullptr)
10090 : {
10091 146 : if (EQUAL(pszValue, "NC"))
10092 : {
10093 3 : eFormat = NCDF_FORMAT_NC;
10094 : }
10095 : #ifdef NETCDF_HAS_NC2
10096 143 : else if (EQUAL(pszValue, "NC2"))
10097 : {
10098 1 : eFormat = NCDF_FORMAT_NC2;
10099 : }
10100 : #endif
10101 142 : else if (EQUAL(pszValue, "NC4"))
10102 : {
10103 138 : eFormat = NCDF_FORMAT_NC4;
10104 : }
10105 4 : else if (EQUAL(pszValue, "NC4C"))
10106 : {
10107 4 : eFormat = NCDF_FORMAT_NC4C;
10108 : }
10109 : else
10110 : {
10111 0 : CPLError(CE_Failure, CPLE_NotSupported,
10112 : "FORMAT=%s in not supported, using the default NC format.",
10113 : pszValue);
10114 : }
10115 : }
10116 :
10117 : // COMPRESS option.
10118 314 : pszValue = CSLFetchNameValue(papszCreationOptions, "COMPRESS");
10119 314 : if (pszValue != nullptr)
10120 : {
10121 3 : if (EQUAL(pszValue, "NONE"))
10122 : {
10123 1 : eCompress = NCDF_COMPRESS_NONE;
10124 : }
10125 2 : else if (EQUAL(pszValue, "DEFLATE"))
10126 : {
10127 2 : eCompress = NCDF_COMPRESS_DEFLATE;
10128 2 : if (!((eFormat == NCDF_FORMAT_NC4) ||
10129 2 : (eFormat == NCDF_FORMAT_NC4C)))
10130 : {
10131 1 : CPLError(CE_Warning, CPLE_IllegalArg,
10132 : "NOTICE: Format set to NC4C because compression is "
10133 : "set to DEFLATE.");
10134 1 : eFormat = NCDF_FORMAT_NC4C;
10135 : }
10136 : }
10137 : else
10138 : {
10139 0 : CPLError(CE_Failure, CPLE_NotSupported,
10140 : "COMPRESS=%s is not supported.", pszValue);
10141 : }
10142 : }
10143 :
10144 : // ZLEVEL option.
10145 314 : pszValue = CSLFetchNameValue(papszCreationOptions, "ZLEVEL");
10146 314 : if (pszValue != nullptr)
10147 : {
10148 1 : nZLevel = atoi(pszValue);
10149 1 : if (!(nZLevel >= 1 && nZLevel <= 9))
10150 : {
10151 0 : CPLError(CE_Warning, CPLE_IllegalArg,
10152 : "ZLEVEL=%s value not recognised, ignoring.", pszValue);
10153 0 : nZLevel = NCDF_DEFLATE_LEVEL;
10154 : }
10155 : }
10156 :
10157 : // CHUNKING option.
10158 314 : bChunking =
10159 314 : CPL_TO_BOOL(CSLFetchBoolean(papszCreationOptions, "CHUNKING", TRUE));
10160 :
10161 : // MULTIPLE_LAYERS option.
10162 : const char *pszMultipleLayerBehavior =
10163 314 : CSLFetchNameValueDef(papszCreationOptions, "MULTIPLE_LAYERS", "NO");
10164 628 : const char *pszGeometryEnc = CSLFetchNameValueDef(
10165 314 : papszCreationOptions, "GEOMETRY_ENCODING", "CF_1.8");
10166 314 : if (EQUAL(pszMultipleLayerBehavior, "NO") ||
10167 4 : EQUAL(pszGeometryEnc, "CF_1.8"))
10168 : {
10169 310 : eMultipleLayerBehavior = SINGLE_LAYER;
10170 : }
10171 4 : else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_FILES"))
10172 : {
10173 3 : eMultipleLayerBehavior = SEPARATE_FILES;
10174 : }
10175 1 : else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_GROUPS"))
10176 : {
10177 1 : if (eFormat == NCDF_FORMAT_NC4)
10178 : {
10179 1 : eMultipleLayerBehavior = SEPARATE_GROUPS;
10180 : }
10181 : else
10182 : {
10183 0 : CPLError(CE_Warning, CPLE_IllegalArg,
10184 : "MULTIPLE_LAYERS=%s is recognised only with FORMAT=NC4",
10185 : pszMultipleLayerBehavior);
10186 : }
10187 : }
10188 : else
10189 : {
10190 0 : CPLError(CE_Warning, CPLE_IllegalArg,
10191 : "MULTIPLE_LAYERS=%s not recognised", pszMultipleLayerBehavior);
10192 : }
10193 :
10194 : // Set nCreateMode based on eFormat.
10195 314 : switch (eFormat)
10196 : {
10197 : #ifdef NETCDF_HAS_NC2
10198 1 : case NCDF_FORMAT_NC2:
10199 1 : nCreateMode = NC_CLOBBER | NC_64BIT_OFFSET;
10200 1 : break;
10201 : #endif
10202 138 : case NCDF_FORMAT_NC4:
10203 138 : nCreateMode = NC_CLOBBER | NC_NETCDF4;
10204 138 : break;
10205 5 : case NCDF_FORMAT_NC4C:
10206 5 : nCreateMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
10207 5 : break;
10208 170 : case NCDF_FORMAT_NC:
10209 : default:
10210 170 : nCreateMode = NC_CLOBBER;
10211 170 : break;
10212 : }
10213 :
10214 314 : CPLDebug("GDAL_netCDF", "file options: format=%d compress=%d zlevel=%d",
10215 314 : eFormat, eCompress, nZLevel);
10216 314 : }
10217 :
10218 288 : int netCDFDataset::DefVarDeflate(int nVarId, bool bChunkingArg) const
10219 : {
10220 288 : if (eCompress == NCDF_COMPRESS_DEFLATE)
10221 : {
10222 : // Must set chunk size to avoid huge performance hit (set
10223 : // bChunkingArg=TRUE)
10224 : // perhaps another solution it to change the chunk cache?
10225 : // http://www.unidata.ucar.edu/software/netcdf/docs/netcdf.html#Chunk-Cache
10226 : // TODO: make sure this is okay.
10227 2 : CPLDebug("GDAL_netCDF", "DefVarDeflate(%d, %d) nZlevel=%d", nVarId,
10228 2 : static_cast<int>(bChunkingArg), nZLevel);
10229 :
10230 2 : int status = nc_def_var_deflate(cdfid, nVarId, 1, 1, nZLevel);
10231 2 : NCDF_ERR(status);
10232 :
10233 2 : if (status == NC_NOERR && bChunkingArg && bChunking)
10234 : {
10235 : // set chunking to be 1 for all dims, except X dim
10236 : // size_t chunksize[] = { 1, (size_t)nRasterXSize };
10237 : size_t chunksize[MAX_NC_DIMS];
10238 : int nd;
10239 2 : nc_inq_varndims(cdfid, nVarId, &nd);
10240 2 : chunksize[0] = (size_t)1;
10241 2 : chunksize[1] = (size_t)1;
10242 2 : for (int i = 2; i < nd; i++)
10243 0 : chunksize[i] = (size_t)1;
10244 2 : chunksize[nd - 1] = (size_t)nRasterXSize;
10245 :
10246 : // Config options just for testing purposes
10247 : const char *pszBlockXSize =
10248 2 : CPLGetConfigOption("BLOCKXSIZE", nullptr);
10249 2 : if (pszBlockXSize)
10250 0 : chunksize[nd - 1] = (size_t)atoi(pszBlockXSize);
10251 :
10252 : const char *pszBlockYSize =
10253 2 : CPLGetConfigOption("BLOCKYSIZE", nullptr);
10254 2 : if (nd >= 2 && pszBlockYSize)
10255 0 : chunksize[nd - 2] = (size_t)atoi(pszBlockYSize);
10256 :
10257 2 : CPLDebug("GDAL_netCDF",
10258 : "DefVarDeflate() chunksize={%ld, %ld} chunkX=%ld nd=%d",
10259 2 : (long)chunksize[0], (long)chunksize[1],
10260 2 : (long)chunksize[nd - 1], nd);
10261 : #ifdef NCDF_DEBUG
10262 : for (int i = 0; i < nd; i++)
10263 : CPLDebug("GDAL_netCDF", "DefVarDeflate() chunk[%d]=%ld", i,
10264 : chunksize[i]);
10265 : #endif
10266 :
10267 2 : status = nc_def_var_chunking(cdfid, nVarId, NC_CHUNKED, chunksize);
10268 2 : NCDF_ERR(status);
10269 : }
10270 : else
10271 : {
10272 0 : CPLDebug("GDAL_netCDF", "chunksize not set");
10273 : }
10274 2 : return status;
10275 : }
10276 286 : return NC_NOERR;
10277 : }
10278 :
10279 : /************************************************************************/
10280 : /* NCDFUnloadDriver() */
10281 : /************************************************************************/
10282 :
10283 8 : static void NCDFUnloadDriver(CPL_UNUSED GDALDriver *poDriver)
10284 : {
10285 8 : if (hNCMutex != nullptr)
10286 4 : CPLDestroyMutex(hNCMutex);
10287 8 : hNCMutex = nullptr;
10288 8 : }
10289 :
10290 : /************************************************************************/
10291 : /* GDALRegister_netCDF() */
10292 : /************************************************************************/
10293 :
10294 : class GDALnetCDFDriver final : public GDALDriver
10295 : {
10296 : public:
10297 19 : GDALnetCDFDriver() = default;
10298 :
10299 : const char *GetMetadataItem(const char *pszName,
10300 : const char *pszDomain) override;
10301 :
10302 93 : CSLConstList GetMetadata(const char *pszDomain) override
10303 : {
10304 186 : std::lock_guard oLock(m_oMutex);
10305 93 : InitializeDCAPVirtualIO();
10306 186 : return GDALDriver::GetMetadata(pszDomain);
10307 : }
10308 :
10309 : private:
10310 : std::recursive_mutex m_oMutex{};
10311 : bool m_bInitialized = false;
10312 :
10313 106 : void InitializeDCAPVirtualIO()
10314 : {
10315 106 : if (!m_bInitialized)
10316 : {
10317 12 : m_bInitialized = true;
10318 :
10319 : #ifdef ENABLE_UFFD
10320 12 : if (CPLIsUserFaultMappingSupported())
10321 : {
10322 12 : SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
10323 : }
10324 : #endif
10325 : }
10326 106 : }
10327 : };
10328 :
10329 1412 : const char *GDALnetCDFDriver::GetMetadataItem(const char *pszName,
10330 : const char *pszDomain)
10331 : {
10332 2824 : std::lock_guard oLock(m_oMutex);
10333 1412 : if (EQUAL(pszName, GDAL_DCAP_VIRTUALIO))
10334 : {
10335 13 : InitializeDCAPVirtualIO();
10336 : }
10337 2824 : return GDALDriver::GetMetadataItem(pszName, pszDomain);
10338 : }
10339 :
10340 19 : void GDALRegister_netCDF()
10341 :
10342 : {
10343 19 : if (!GDAL_CHECK_VERSION("netCDF driver"))
10344 0 : return;
10345 :
10346 19 : if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
10347 0 : return;
10348 :
10349 19 : GDALDriver *poDriver = new GDALnetCDFDriver();
10350 19 : netCDFDriverSetCommonMetadata(poDriver);
10351 :
10352 19 : poDriver->SetMetadataItem("NETCDF_CONVENTIONS",
10353 19 : GDAL_DEFAULT_NCDF_CONVENTIONS);
10354 19 : poDriver->SetMetadataItem("NETCDF_VERSION", nc_inq_libvers());
10355 :
10356 : // Set pfns and register driver.
10357 19 : poDriver->pfnOpen = netCDFDataset::Open;
10358 19 : poDriver->pfnCreateCopy = netCDFDataset::CreateCopy;
10359 19 : poDriver->pfnCreate = netCDFDataset::Create;
10360 19 : poDriver->pfnCreateMultiDimensional = netCDFDataset::CreateMultiDimensional;
10361 19 : poDriver->pfnUnloadDriver = NCDFUnloadDriver;
10362 :
10363 19 : GetGDALDriverManager()->RegisterDriver(poDriver);
10364 : }
10365 :
10366 : /************************************************************************/
10367 : /* New functions */
10368 : /************************************************************************/
10369 :
10370 : /* Test for GDAL version string >= target */
10371 257 : static bool NCDFIsGDALVersionGTE(const char *pszVersion, int nTarget)
10372 : {
10373 :
10374 : // Valid strings are "GDAL 1.9dev, released 2011/01/18" and "GDAL 1.8.1 ".
10375 257 : if (pszVersion == nullptr || EQUAL(pszVersion, ""))
10376 0 : return false;
10377 257 : else if (!STARTS_WITH_CI(pszVersion, "GDAL "))
10378 0 : return false;
10379 : // 2.0dev of 2011/12/29 has been later renamed as 1.10dev.
10380 257 : else if (EQUAL("GDAL 2.0dev, released 2011/12/29", pszVersion))
10381 0 : return nTarget <= GDAL_COMPUTE_VERSION(1, 10, 0);
10382 257 : else if (STARTS_WITH_CI(pszVersion, "GDAL 1.9dev"))
10383 2 : return nTarget <= 1900;
10384 255 : else if (STARTS_WITH_CI(pszVersion, "GDAL 1.8dev"))
10385 0 : return nTarget <= 1800;
10386 :
10387 255 : const CPLStringList aosTokens(CSLTokenizeString2(pszVersion + 5, ".", 0));
10388 :
10389 255 : int nVersions[] = {0, 0, 0, 0};
10390 1020 : for (int iToken = 0; iToken < std::min(4, aosTokens.size()); iToken++)
10391 : {
10392 765 : nVersions[iToken] = atoi(aosTokens[iToken]);
10393 765 : if (nVersions[iToken] < 0)
10394 0 : nVersions[iToken] = 0;
10395 765 : else if (nVersions[iToken] > 99)
10396 0 : nVersions[iToken] = 99;
10397 : }
10398 :
10399 255 : int nVersion = 0;
10400 255 : if (nVersions[0] > 1 || nVersions[1] >= 10)
10401 255 : nVersion =
10402 255 : GDAL_COMPUTE_VERSION(nVersions[0], nVersions[1], nVersions[2]);
10403 : else
10404 0 : nVersion = nVersions[0] * 1000 + nVersions[1] * 100 +
10405 0 : nVersions[2] * 10 + nVersions[3];
10406 :
10407 255 : return nTarget <= nVersion;
10408 : }
10409 :
10410 : // Add Conventions, GDAL version and history.
10411 173 : static void NCDFAddGDALHistory(int fpImage, const char *pszFilename,
10412 : bool bWriteGDALVersion, bool bWriteGDALHistory,
10413 : const char *pszOldHist,
10414 : const char *pszFunctionName,
10415 : const char *pszCFVersion)
10416 : {
10417 173 : if (pszCFVersion == nullptr)
10418 : {
10419 48 : pszCFVersion = GDAL_DEFAULT_NCDF_CONVENTIONS;
10420 : }
10421 173 : int status = nc_put_att_text(fpImage, NC_GLOBAL, "Conventions",
10422 : strlen(pszCFVersion), pszCFVersion);
10423 173 : NCDF_ERR(status);
10424 :
10425 173 : if (bWriteGDALVersion)
10426 : {
10427 171 : const char *pszNCDF_GDAL = GDALVersionInfo("--version");
10428 171 : status = nc_put_att_text(fpImage, NC_GLOBAL, "GDAL",
10429 : strlen(pszNCDF_GDAL), pszNCDF_GDAL);
10430 171 : NCDF_ERR(status);
10431 : }
10432 :
10433 173 : if (bWriteGDALHistory)
10434 : {
10435 : // Add history.
10436 342 : CPLString osTmp;
10437 : #ifdef GDAL_SET_CMD_LINE_DEFINED_TMP
10438 : if (!EQUAL(GDALGetCmdLine(), ""))
10439 : osTmp = GDALGetCmdLine();
10440 : else
10441 : osTmp =
10442 : CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
10443 : #else
10444 171 : osTmp = CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
10445 : #endif
10446 :
10447 171 : NCDFAddHistory(fpImage, osTmp.c_str(), pszOldHist);
10448 : }
10449 2 : else if (pszOldHist != nullptr)
10450 : {
10451 0 : status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
10452 : strlen(pszOldHist), pszOldHist);
10453 0 : NCDF_ERR(status);
10454 : }
10455 173 : }
10456 :
10457 : // Code taken from cdo and libcdi, used for writing the history attribute.
10458 :
10459 : // void cdoDefHistory(int fileID, char *histstring)
10460 171 : static void NCDFAddHistory(int fpImage, const char *pszAddHist,
10461 : const char *pszOldHist)
10462 : {
10463 : // Check pszOldHist - as if there was no previous history, it will be
10464 : // a null pointer - if so set as empty.
10465 171 : if (nullptr == pszOldHist)
10466 : {
10467 59 : pszOldHist = "";
10468 : }
10469 :
10470 : char strtime[32];
10471 171 : strtime[0] = '\0';
10472 :
10473 171 : time_t tp = time(nullptr);
10474 171 : if (tp != -1)
10475 : {
10476 : struct tm ltime;
10477 171 : VSILocalTime(&tp, <ime);
10478 171 : (void)strftime(strtime, sizeof(strtime),
10479 : "%a %b %d %H:%M:%S %Y: ", <ime);
10480 : }
10481 :
10482 : // status = nc_get_att_text(fpImage, NC_GLOBAL,
10483 : // "history", pszOldHist);
10484 : // printf("status: %d pszOldHist: [%s]\n",status,pszOldHist);
10485 :
10486 171 : size_t nNewHistSize =
10487 171 : strlen(pszOldHist) + strlen(strtime) + strlen(pszAddHist) + 1 + 1;
10488 : char *pszNewHist =
10489 171 : static_cast<char *>(CPLMalloc(nNewHistSize * sizeof(char)));
10490 :
10491 171 : strcpy(pszNewHist, strtime);
10492 171 : strcat(pszNewHist, pszAddHist);
10493 :
10494 : // int disableHistory = FALSE;
10495 : // if( !disableHistory )
10496 : {
10497 171 : if (!EQUAL(pszOldHist, ""))
10498 3 : strcat(pszNewHist, "\n");
10499 171 : strcat(pszNewHist, pszOldHist);
10500 : }
10501 :
10502 171 : const int status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
10503 : strlen(pszNewHist), pszNewHist);
10504 171 : NCDF_ERR(status);
10505 :
10506 171 : CPLFree(pszNewHist);
10507 171 : }
10508 :
10509 6636 : static CPLErr NCDFSafeStrcat(char **ppszDest, const char *pszSrc,
10510 : size_t *nDestSize)
10511 : {
10512 : /* Reallocate the data string until the content fits */
10513 6636 : while (*nDestSize < (strlen(*ppszDest) + strlen(pszSrc) + 1))
10514 : {
10515 411 : (*nDestSize) *= 2;
10516 411 : *ppszDest = static_cast<char *>(
10517 411 : CPLRealloc(reinterpret_cast<void *>(*ppszDest), *nDestSize));
10518 : #ifdef NCDF_DEBUG
10519 : CPLDebug("GDAL_netCDF", "NCDFSafeStrcat() resized str from %ld to %ld",
10520 : (*nDestSize) / 2, *nDestSize);
10521 : #endif
10522 : }
10523 6225 : strcat(*ppszDest, pszSrc);
10524 :
10525 6225 : return CE_None;
10526 : }
10527 :
10528 : /* helper function for NCDFGetAttr() */
10529 : /* if pdfValue != nullptr, sets *pdfValue to first value returned */
10530 : /* if ppszValue != nullptr, sets *ppszValue with all attribute values */
10531 : /* *ppszValue is the responsibility of the caller and must be freed */
10532 68413 : static CPLErr NCDFGetAttr1(int nCdfId, int nVarId, const char *pszAttrName,
10533 : double *pdfValue, char **ppszValue)
10534 : {
10535 68413 : nc_type nAttrType = NC_NAT;
10536 68413 : size_t nAttrLen = 0;
10537 :
10538 68413 : if (ppszValue)
10539 67251 : *ppszValue = nullptr;
10540 :
10541 68413 : int status = nc_inq_att(nCdfId, nVarId, pszAttrName, &nAttrType, &nAttrLen);
10542 68413 : if (status != NC_NOERR)
10543 36601 : return CE_Failure;
10544 :
10545 : #ifdef NCDF_DEBUG
10546 : CPLDebug("GDAL_netCDF", "NCDFGetAttr1(%s) len=%ld type=%d", pszAttrName,
10547 : nAttrLen, nAttrType);
10548 : #endif
10549 31812 : if (nAttrLen == 0 && nAttrType != NC_CHAR)
10550 1 : return CE_Failure;
10551 :
10552 : /* Allocate guaranteed minimum size (use 10 or 20 if not a string) */
10553 31811 : size_t nAttrValueSize = nAttrLen + 1;
10554 31811 : if (nAttrType != NC_CHAR && nAttrValueSize < 10)
10555 3572 : nAttrValueSize = 10;
10556 31811 : if (nAttrType == NC_DOUBLE && nAttrValueSize < 20)
10557 1722 : nAttrValueSize = 20;
10558 31811 : if (nAttrType == NC_INT64 && nAttrValueSize < 20)
10559 22 : nAttrValueSize = 22;
10560 : char *pszAttrValue =
10561 31811 : static_cast<char *>(CPLCalloc(nAttrValueSize, sizeof(char)));
10562 31811 : *pszAttrValue = '\0';
10563 :
10564 31811 : if (nAttrLen > 1 && nAttrType != NC_CHAR)
10565 638 : NCDFSafeStrcat(&pszAttrValue, "{", &nAttrValueSize);
10566 :
10567 31811 : double dfValue = 0.0;
10568 31811 : size_t m = 0;
10569 : char szTemp[256];
10570 31811 : bool bSetDoubleFromStr = false;
10571 :
10572 31811 : switch (nAttrType)
10573 : {
10574 28237 : case NC_CHAR:
10575 28237 : CPL_IGNORE_RET_VAL(
10576 28237 : nc_get_att_text(nCdfId, nVarId, pszAttrName, pszAttrValue));
10577 28237 : pszAttrValue[nAttrLen] = '\0';
10578 28237 : bSetDoubleFromStr = true;
10579 28237 : dfValue = 0.0;
10580 28237 : break;
10581 94 : case NC_BYTE:
10582 : {
10583 : signed char *pscTemp = static_cast<signed char *>(
10584 94 : CPLCalloc(nAttrLen, sizeof(signed char)));
10585 94 : nc_get_att_schar(nCdfId, nVarId, pszAttrName, pscTemp);
10586 94 : dfValue = static_cast<double>(pscTemp[0]);
10587 94 : if (nAttrLen > 1)
10588 : {
10589 24 : for (m = 0; m < nAttrLen - 1; m++)
10590 : {
10591 13 : snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
10592 13 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10593 : }
10594 : }
10595 94 : snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
10596 94 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10597 94 : CPLFree(pscTemp);
10598 94 : break;
10599 : }
10600 523 : case NC_SHORT:
10601 : {
10602 : short *psTemp =
10603 523 : static_cast<short *>(CPLCalloc(nAttrLen, sizeof(short)));
10604 523 : nc_get_att_short(nCdfId, nVarId, pszAttrName, psTemp);
10605 523 : dfValue = static_cast<double>(psTemp[0]);
10606 523 : if (nAttrLen > 1)
10607 : {
10608 840 : for (m = 0; m < nAttrLen - 1; m++)
10609 : {
10610 420 : snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
10611 420 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10612 : }
10613 : }
10614 523 : snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
10615 523 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10616 523 : CPLFree(psTemp);
10617 523 : break;
10618 : }
10619 528 : case NC_INT:
10620 : {
10621 528 : int *pnTemp = static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
10622 528 : nc_get_att_int(nCdfId, nVarId, pszAttrName, pnTemp);
10623 528 : dfValue = static_cast<double>(pnTemp[0]);
10624 528 : if (nAttrLen > 1)
10625 : {
10626 218 : for (m = 0; m < nAttrLen - 1; m++)
10627 : {
10628 139 : snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
10629 139 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10630 : }
10631 : }
10632 528 : snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
10633 528 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10634 528 : CPLFree(pnTemp);
10635 528 : break;
10636 : }
10637 395 : case NC_FLOAT:
10638 : {
10639 : float *pfTemp =
10640 395 : static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
10641 395 : nc_get_att_float(nCdfId, nVarId, pszAttrName, pfTemp);
10642 395 : dfValue = static_cast<double>(pfTemp[0]);
10643 395 : if (nAttrLen > 1)
10644 : {
10645 60 : for (m = 0; m < nAttrLen - 1; m++)
10646 : {
10647 30 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
10648 30 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10649 : }
10650 : }
10651 395 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
10652 395 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10653 395 : CPLFree(pfTemp);
10654 395 : break;
10655 : }
10656 1722 : case NC_DOUBLE:
10657 : {
10658 : double *pdfTemp =
10659 1722 : static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
10660 1722 : nc_get_att_double(nCdfId, nVarId, pszAttrName, pdfTemp);
10661 1722 : dfValue = pdfTemp[0];
10662 1722 : if (nAttrLen > 1)
10663 : {
10664 166 : for (m = 0; m < nAttrLen - 1; m++)
10665 : {
10666 90 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
10667 90 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10668 : }
10669 : }
10670 1722 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
10671 1722 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10672 1722 : CPLFree(pdfTemp);
10673 1722 : break;
10674 : }
10675 167 : case NC_STRING:
10676 : {
10677 : char **ppszTemp =
10678 167 : static_cast<char **>(CPLCalloc(nAttrLen, sizeof(char *)));
10679 167 : nc_get_att_string(nCdfId, nVarId, pszAttrName, ppszTemp);
10680 167 : bSetDoubleFromStr = true;
10681 167 : dfValue = 0.0;
10682 167 : if (nAttrLen > 1)
10683 : {
10684 19 : for (m = 0; m < nAttrLen - 1; m++)
10685 : {
10686 12 : NCDFSafeStrcat(&pszAttrValue,
10687 12 : ppszTemp[m] ? ppszTemp[m] : "{NULL}",
10688 : &nAttrValueSize);
10689 12 : NCDFSafeStrcat(&pszAttrValue, ",", &nAttrValueSize);
10690 : }
10691 : }
10692 167 : NCDFSafeStrcat(&pszAttrValue, ppszTemp[m] ? ppszTemp[m] : "{NULL}",
10693 : &nAttrValueSize);
10694 167 : nc_free_string(nAttrLen, ppszTemp);
10695 167 : CPLFree(ppszTemp);
10696 167 : break;
10697 : }
10698 28 : case NC_UBYTE:
10699 : {
10700 : unsigned char *pucTemp = static_cast<unsigned char *>(
10701 28 : CPLCalloc(nAttrLen, sizeof(unsigned char)));
10702 28 : nc_get_att_uchar(nCdfId, nVarId, pszAttrName, pucTemp);
10703 28 : dfValue = static_cast<double>(pucTemp[0]);
10704 28 : if (nAttrLen > 1)
10705 : {
10706 0 : for (m = 0; m < nAttrLen - 1; m++)
10707 : {
10708 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
10709 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10710 : }
10711 : }
10712 28 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
10713 28 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10714 28 : CPLFree(pucTemp);
10715 28 : break;
10716 : }
10717 26 : case NC_USHORT:
10718 : {
10719 : unsigned short *pusTemp = static_cast<unsigned short *>(
10720 26 : CPLCalloc(nAttrLen, sizeof(unsigned short)));
10721 26 : nc_get_att_ushort(nCdfId, nVarId, pszAttrName, pusTemp);
10722 26 : dfValue = static_cast<double>(pusTemp[0]);
10723 26 : if (nAttrLen > 1)
10724 : {
10725 10 : for (m = 0; m < nAttrLen - 1; m++)
10726 : {
10727 5 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
10728 5 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10729 : }
10730 : }
10731 26 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
10732 26 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10733 26 : CPLFree(pusTemp);
10734 26 : break;
10735 : }
10736 21 : case NC_UINT:
10737 : {
10738 : unsigned int *punTemp =
10739 21 : static_cast<unsigned int *>(CPLCalloc(nAttrLen, sizeof(int)));
10740 21 : nc_get_att_uint(nCdfId, nVarId, pszAttrName, punTemp);
10741 21 : dfValue = static_cast<double>(punTemp[0]);
10742 21 : if (nAttrLen > 1)
10743 : {
10744 0 : for (m = 0; m < nAttrLen - 1; m++)
10745 : {
10746 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
10747 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10748 : }
10749 : }
10750 21 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
10751 21 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10752 21 : CPLFree(punTemp);
10753 21 : break;
10754 : }
10755 22 : case NC_INT64:
10756 : {
10757 : GIntBig *panTemp =
10758 22 : static_cast<GIntBig *>(CPLCalloc(nAttrLen, sizeof(GIntBig)));
10759 22 : nc_get_att_longlong(nCdfId, nVarId, pszAttrName, panTemp);
10760 22 : dfValue = static_cast<double>(panTemp[0]);
10761 22 : if (nAttrLen > 1)
10762 : {
10763 0 : for (m = 0; m < nAttrLen - 1; m++)
10764 : {
10765 0 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",",
10766 0 : panTemp[m]);
10767 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10768 : }
10769 : }
10770 22 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, panTemp[m]);
10771 22 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10772 22 : CPLFree(panTemp);
10773 22 : break;
10774 : }
10775 22 : case NC_UINT64:
10776 : {
10777 : GUIntBig *panTemp =
10778 22 : static_cast<GUIntBig *>(CPLCalloc(nAttrLen, sizeof(GUIntBig)));
10779 22 : nc_get_att_ulonglong(nCdfId, nVarId, pszAttrName, panTemp);
10780 22 : dfValue = static_cast<double>(panTemp[0]);
10781 22 : if (nAttrLen > 1)
10782 : {
10783 0 : for (m = 0; m < nAttrLen - 1; m++)
10784 : {
10785 0 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",",
10786 0 : panTemp[m]);
10787 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10788 : }
10789 : }
10790 22 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, panTemp[m]);
10791 22 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10792 22 : CPLFree(panTemp);
10793 22 : break;
10794 : }
10795 26 : default:
10796 26 : CPLDebug("GDAL_netCDF",
10797 : "NCDFGetAttr unsupported type %d for attribute %s",
10798 : nAttrType, pszAttrName);
10799 26 : break;
10800 : }
10801 :
10802 31811 : if (nAttrLen > 1 && nAttrType != NC_CHAR)
10803 638 : NCDFSafeStrcat(&pszAttrValue, "}", &nAttrValueSize);
10804 :
10805 31811 : if (bSetDoubleFromStr)
10806 : {
10807 28404 : if (CPLGetValueType(pszAttrValue) == CPL_VALUE_STRING)
10808 : {
10809 28222 : if (ppszValue == nullptr && pdfValue != nullptr)
10810 : {
10811 1 : CPLFree(pszAttrValue);
10812 1 : return CE_Failure;
10813 : }
10814 : }
10815 28403 : dfValue = CPLAtof(pszAttrValue);
10816 : }
10817 :
10818 : /* set return values */
10819 31810 : if (ppszValue)
10820 31497 : *ppszValue = pszAttrValue;
10821 : else
10822 313 : CPLFree(pszAttrValue);
10823 :
10824 31810 : if (pdfValue)
10825 313 : *pdfValue = dfValue;
10826 :
10827 31810 : return CE_None;
10828 : }
10829 :
10830 : /* sets pdfValue to first value found */
10831 1162 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
10832 : double *pdfValue)
10833 : {
10834 1162 : return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, pdfValue, nullptr);
10835 : }
10836 :
10837 : /* pszValue is the responsibility of the caller and must be freed */
10838 67251 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
10839 : char **pszValue)
10840 : {
10841 67251 : return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, nullptr, pszValue);
10842 : }
10843 :
10844 : /* By default write NC_CHAR, but detect for int/float/double and */
10845 : /* NC4 string arrays */
10846 112 : static CPLErr NCDFPutAttr(int nCdfId, int nVarId, const char *pszAttrName,
10847 : const char *pszValue)
10848 : {
10849 112 : int status = 0;
10850 112 : char *pszTemp = nullptr;
10851 :
10852 : /* get the attribute values as tokens */
10853 224 : CPLStringList aosValues = NCDFTokenizeArray(pszValue);
10854 112 : if (aosValues.empty())
10855 0 : return CE_Failure;
10856 :
10857 112 : size_t nAttrLen = aosValues.size();
10858 :
10859 : /* first detect type */
10860 112 : nc_type nAttrType = NC_CHAR;
10861 112 : nc_type nTmpAttrType = NC_CHAR;
10862 237 : for (size_t i = 0; i < nAttrLen; i++)
10863 : {
10864 125 : nTmpAttrType = NC_CHAR;
10865 125 : bool bFoundType = false;
10866 125 : errno = 0;
10867 125 : int nValue = static_cast<int>(strtol(aosValues[i], &pszTemp, 10));
10868 : /* test for int */
10869 : /* TODO test for Byte and short - can this be done safely? */
10870 125 : if (errno == 0 && aosValues[i] != pszTemp && *pszTemp == 0)
10871 : {
10872 : char szTemp[256];
10873 19 : CPLsnprintf(szTemp, sizeof(szTemp), "%d", nValue);
10874 19 : if (EQUAL(szTemp, aosValues[i]))
10875 : {
10876 19 : bFoundType = true;
10877 19 : nTmpAttrType = NC_INT;
10878 : }
10879 : else
10880 : {
10881 : unsigned int unValue = static_cast<unsigned int>(
10882 0 : strtoul(aosValues[i], &pszTemp, 10));
10883 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", unValue);
10884 0 : if (EQUAL(szTemp, aosValues[i]))
10885 : {
10886 0 : bFoundType = true;
10887 0 : nTmpAttrType = NC_UINT;
10888 : }
10889 : }
10890 : }
10891 125 : if (!bFoundType)
10892 : {
10893 : /* test for double */
10894 106 : errno = 0;
10895 106 : double dfValue = CPLStrtod(aosValues[i], &pszTemp);
10896 106 : if ((errno == 0) && (aosValues[i] != pszTemp) && (*pszTemp == 0))
10897 : {
10898 : // Test for float instead of double.
10899 : // strtof() is C89, which is not available in MSVC.
10900 : // See if we lose precision if we cast to float and write to
10901 : // char*.
10902 14 : float fValue = float(dfValue);
10903 : char szTemp[256];
10904 14 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", fValue);
10905 14 : if (EQUAL(szTemp, aosValues[i]))
10906 8 : nTmpAttrType = NC_FLOAT;
10907 : else
10908 6 : nTmpAttrType = NC_DOUBLE;
10909 : }
10910 : }
10911 125 : if ((nTmpAttrType <= NC_DOUBLE && nAttrType <= NC_DOUBLE &&
10912 105 : nTmpAttrType > nAttrType) ||
10913 105 : (nTmpAttrType == NC_UINT && nAttrType < NC_FLOAT) ||
10914 5 : (nTmpAttrType >= NC_FLOAT && nAttrType == NC_UINT))
10915 20 : nAttrType = nTmpAttrType;
10916 : }
10917 :
10918 : #ifdef DEBUG
10919 112 : if (EQUAL(pszAttrName, "DEBUG_EMPTY_DOUBLE_ATTR"))
10920 : {
10921 0 : nAttrType = NC_DOUBLE;
10922 0 : nAttrLen = 0;
10923 : }
10924 : #endif
10925 :
10926 : /* now write the data */
10927 112 : if (nAttrType == NC_CHAR)
10928 : {
10929 92 : int nTmpFormat = 0;
10930 92 : if (nAttrLen > 1)
10931 : {
10932 0 : status = nc_inq_format(nCdfId, &nTmpFormat);
10933 0 : NCDF_ERR(status);
10934 : }
10935 92 : if (nAttrLen > 1 && nTmpFormat == NCDF_FORMAT_NC4)
10936 0 : status =
10937 0 : nc_put_att_string(nCdfId, nVarId, pszAttrName, nAttrLen,
10938 0 : const_cast<const char **>(aosValues.List()));
10939 : else
10940 92 : status = nc_put_att_text(nCdfId, nVarId, pszAttrName,
10941 : strlen(pszValue), pszValue);
10942 92 : NCDF_ERR(status);
10943 : }
10944 : else
10945 : {
10946 20 : switch (nAttrType)
10947 : {
10948 11 : case NC_INT:
10949 : {
10950 : int *pnTemp =
10951 11 : static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
10952 30 : for (size_t i = 0; i < nAttrLen; i++)
10953 : {
10954 19 : pnTemp[i] =
10955 19 : static_cast<int>(strtol(aosValues[i], &pszTemp, 10));
10956 : }
10957 11 : status = nc_put_att_int(nCdfId, nVarId, pszAttrName, NC_INT,
10958 : nAttrLen, pnTemp);
10959 11 : NCDF_ERR(status);
10960 11 : CPLFree(pnTemp);
10961 11 : break;
10962 : }
10963 0 : case NC_UINT:
10964 : {
10965 : unsigned int *punTemp = static_cast<unsigned int *>(
10966 0 : CPLCalloc(nAttrLen, sizeof(unsigned int)));
10967 0 : for (size_t i = 0; i < nAttrLen; i++)
10968 : {
10969 0 : punTemp[i] = static_cast<unsigned int>(
10970 0 : strtol(aosValues[i], &pszTemp, 10));
10971 : }
10972 0 : status = nc_put_att_uint(nCdfId, nVarId, pszAttrName, NC_UINT,
10973 : nAttrLen, punTemp);
10974 0 : NCDF_ERR(status);
10975 0 : CPLFree(punTemp);
10976 0 : break;
10977 : }
10978 6 : case NC_FLOAT:
10979 : {
10980 : float *pfTemp =
10981 6 : static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
10982 14 : for (size_t i = 0; i < nAttrLen; i++)
10983 : {
10984 8 : pfTemp[i] =
10985 8 : static_cast<float>(CPLStrtod(aosValues[i], &pszTemp));
10986 : }
10987 6 : status = nc_put_att_float(nCdfId, nVarId, pszAttrName, NC_FLOAT,
10988 : nAttrLen, pfTemp);
10989 6 : NCDF_ERR(status);
10990 6 : CPLFree(pfTemp);
10991 6 : break;
10992 : }
10993 3 : case NC_DOUBLE:
10994 : {
10995 : double *pdfTemp =
10996 3 : static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
10997 9 : for (size_t i = 0; i < nAttrLen; i++)
10998 : {
10999 6 : pdfTemp[i] = CPLStrtod(aosValues[i], &pszTemp);
11000 : }
11001 3 : status = nc_put_att_double(nCdfId, nVarId, pszAttrName,
11002 : NC_DOUBLE, nAttrLen, pdfTemp);
11003 3 : NCDF_ERR(status);
11004 3 : CPLFree(pdfTemp);
11005 3 : break;
11006 : }
11007 0 : default:
11008 0 : return CE_Failure;
11009 : }
11010 : }
11011 :
11012 112 : return CE_None;
11013 : }
11014 :
11015 78 : static CPLErr NCDFGet1DVar(int nCdfId, int nVarId, char **pszValue)
11016 : {
11017 : /* get var information */
11018 78 : int nVarDimId = -1;
11019 78 : int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
11020 78 : if (status != NC_NOERR || nVarDimId != 1)
11021 0 : return CE_Failure;
11022 :
11023 78 : status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
11024 78 : if (status != NC_NOERR)
11025 0 : return CE_Failure;
11026 :
11027 78 : nc_type nVarType = NC_NAT;
11028 78 : status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
11029 78 : if (status != NC_NOERR)
11030 0 : return CE_Failure;
11031 :
11032 78 : size_t nVarLen = 0;
11033 78 : status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
11034 78 : if (status != NC_NOERR)
11035 0 : return CE_Failure;
11036 :
11037 78 : size_t start[1] = {0};
11038 78 : size_t count[1] = {nVarLen};
11039 :
11040 : /* Allocate guaranteed minimum size */
11041 78 : size_t nVarValueSize = NCDF_MAX_STR_LEN;
11042 : char *pszVarValue =
11043 78 : static_cast<char *>(CPLCalloc(nVarValueSize, sizeof(char)));
11044 78 : *pszVarValue = '\0';
11045 :
11046 78 : if (nVarLen == 0)
11047 : {
11048 : /* set return values */
11049 1 : *pszValue = pszVarValue;
11050 :
11051 1 : return CE_None;
11052 : }
11053 :
11054 77 : if (nVarLen > 1 && nVarType != NC_CHAR)
11055 42 : NCDFSafeStrcat(&pszVarValue, "{", &nVarValueSize);
11056 :
11057 77 : switch (nVarType)
11058 : {
11059 0 : case NC_CHAR:
11060 0 : nc_get_vara_text(nCdfId, nVarId, start, count, pszVarValue);
11061 0 : pszVarValue[nVarLen] = '\0';
11062 0 : break;
11063 0 : case NC_BYTE:
11064 : {
11065 : signed char *pscTemp = static_cast<signed char *>(
11066 0 : CPLCalloc(nVarLen, sizeof(signed char)));
11067 0 : nc_get_vara_schar(nCdfId, nVarId, start, count, pscTemp);
11068 : char szTemp[256];
11069 0 : size_t m = 0;
11070 0 : for (; m < nVarLen - 1; m++)
11071 : {
11072 0 : snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
11073 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11074 : }
11075 0 : snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
11076 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11077 0 : CPLFree(pscTemp);
11078 0 : break;
11079 : }
11080 0 : case NC_SHORT:
11081 : {
11082 : short *psTemp =
11083 0 : static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
11084 0 : nc_get_vara_short(nCdfId, nVarId, start, count, psTemp);
11085 : char szTemp[256];
11086 0 : size_t m = 0;
11087 0 : for (; m < nVarLen - 1; m++)
11088 : {
11089 0 : snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
11090 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11091 : }
11092 0 : snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
11093 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11094 0 : CPLFree(psTemp);
11095 0 : break;
11096 : }
11097 21 : case NC_INT:
11098 : {
11099 21 : int *pnTemp = static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
11100 21 : nc_get_vara_int(nCdfId, nVarId, start, count, pnTemp);
11101 : char szTemp[256];
11102 21 : size_t m = 0;
11103 44 : for (; m < nVarLen - 1; m++)
11104 : {
11105 23 : snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
11106 23 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11107 : }
11108 21 : snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
11109 21 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11110 21 : CPLFree(pnTemp);
11111 21 : break;
11112 : }
11113 8 : case NC_FLOAT:
11114 : {
11115 : float *pfTemp =
11116 8 : static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
11117 8 : nc_get_vara_float(nCdfId, nVarId, start, count, pfTemp);
11118 : char szTemp[256];
11119 8 : size_t m = 0;
11120 325 : for (; m < nVarLen - 1; m++)
11121 : {
11122 317 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
11123 317 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11124 : }
11125 8 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
11126 8 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11127 8 : CPLFree(pfTemp);
11128 8 : break;
11129 : }
11130 47 : case NC_DOUBLE:
11131 : {
11132 : double *pdfTemp =
11133 47 : static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
11134 47 : nc_get_vara_double(nCdfId, nVarId, start, count, pdfTemp);
11135 : char szTemp[256];
11136 47 : size_t m = 0;
11137 225 : for (; m < nVarLen - 1; m++)
11138 : {
11139 178 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
11140 178 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11141 : }
11142 47 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
11143 47 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11144 47 : CPLFree(pdfTemp);
11145 47 : break;
11146 : }
11147 0 : case NC_STRING:
11148 : {
11149 : char **ppszTemp =
11150 0 : static_cast<char **>(CPLCalloc(nVarLen, sizeof(char *)));
11151 0 : nc_get_vara_string(nCdfId, nVarId, start, count, ppszTemp);
11152 0 : size_t m = 0;
11153 0 : for (; m < nVarLen - 1; m++)
11154 : {
11155 0 : NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
11156 0 : NCDFSafeStrcat(&pszVarValue, ",", &nVarValueSize);
11157 : }
11158 0 : NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
11159 0 : nc_free_string(nVarLen, ppszTemp);
11160 0 : CPLFree(ppszTemp);
11161 0 : break;
11162 : }
11163 0 : case NC_UBYTE:
11164 : {
11165 : unsigned char *pucTemp = static_cast<unsigned char *>(
11166 0 : CPLCalloc(nVarLen, sizeof(unsigned char)));
11167 0 : nc_get_vara_uchar(nCdfId, nVarId, start, count, pucTemp);
11168 : char szTemp[256];
11169 0 : size_t m = 0;
11170 0 : for (; m < nVarLen - 1; m++)
11171 : {
11172 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
11173 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11174 : }
11175 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
11176 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11177 0 : CPLFree(pucTemp);
11178 0 : break;
11179 : }
11180 0 : case NC_USHORT:
11181 : {
11182 : unsigned short *pusTemp = static_cast<unsigned short *>(
11183 0 : CPLCalloc(nVarLen, sizeof(unsigned short)));
11184 0 : nc_get_vara_ushort(nCdfId, nVarId, start, count, pusTemp);
11185 : char szTemp[256];
11186 0 : size_t m = 0;
11187 0 : for (; m < nVarLen - 1; m++)
11188 : {
11189 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
11190 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11191 : }
11192 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
11193 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11194 0 : CPLFree(pusTemp);
11195 0 : break;
11196 : }
11197 0 : case NC_UINT:
11198 : {
11199 : unsigned int *punTemp = static_cast<unsigned int *>(
11200 0 : CPLCalloc(nVarLen, sizeof(unsigned int)));
11201 0 : nc_get_vara_uint(nCdfId, nVarId, start, count, punTemp);
11202 : char szTemp[256];
11203 0 : size_t m = 0;
11204 0 : for (; m < nVarLen - 1; m++)
11205 : {
11206 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
11207 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11208 : }
11209 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
11210 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11211 0 : CPLFree(punTemp);
11212 0 : break;
11213 : }
11214 1 : case NC_INT64:
11215 : {
11216 : long long *pnTemp =
11217 1 : static_cast<long long *>(CPLCalloc(nVarLen, sizeof(long long)));
11218 1 : nc_get_vara_longlong(nCdfId, nVarId, start, count, pnTemp);
11219 : char szTemp[256];
11220 1 : size_t m = 0;
11221 2 : for (; m < nVarLen - 1; m++)
11222 : {
11223 1 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",", pnTemp[m]);
11224 1 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11225 : }
11226 1 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, pnTemp[m]);
11227 1 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11228 1 : CPLFree(pnTemp);
11229 1 : break;
11230 : }
11231 0 : case NC_UINT64:
11232 : {
11233 : unsigned long long *pnTemp = static_cast<unsigned long long *>(
11234 0 : CPLCalloc(nVarLen, sizeof(unsigned long long)));
11235 0 : nc_get_vara_ulonglong(nCdfId, nVarId, start, count, pnTemp);
11236 : char szTemp[256];
11237 0 : size_t m = 0;
11238 0 : for (; m < nVarLen - 1; m++)
11239 : {
11240 0 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",", pnTemp[m]);
11241 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11242 : }
11243 0 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, pnTemp[m]);
11244 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11245 0 : CPLFree(pnTemp);
11246 0 : break;
11247 : }
11248 0 : default:
11249 0 : CPLDebug("GDAL_netCDF", "NCDFGetVar1D unsupported type %d",
11250 : nVarType);
11251 0 : CPLFree(pszVarValue);
11252 0 : pszVarValue = nullptr;
11253 0 : break;
11254 : }
11255 :
11256 77 : if (pszVarValue != nullptr && nVarLen > 1 && nVarType != NC_CHAR)
11257 42 : NCDFSafeStrcat(&pszVarValue, "}", &nVarValueSize);
11258 :
11259 : /* set return values */
11260 77 : *pszValue = pszVarValue;
11261 :
11262 77 : return CE_None;
11263 : }
11264 :
11265 8 : static CPLErr NCDFPut1DVar(int nCdfId, int nVarId, const char *pszValue)
11266 : {
11267 8 : if (EQUAL(pszValue, ""))
11268 0 : return CE_Failure;
11269 :
11270 : /* get var information */
11271 8 : int nVarDimId = -1;
11272 8 : int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
11273 8 : if (status != NC_NOERR || nVarDimId != 1)
11274 0 : return CE_Failure;
11275 :
11276 8 : status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
11277 8 : if (status != NC_NOERR)
11278 0 : return CE_Failure;
11279 :
11280 8 : nc_type nVarType = NC_CHAR;
11281 8 : status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
11282 8 : if (status != NC_NOERR)
11283 0 : return CE_Failure;
11284 :
11285 8 : size_t nVarLen = 0;
11286 8 : status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
11287 8 : if (status != NC_NOERR)
11288 0 : return CE_Failure;
11289 :
11290 8 : size_t start[1] = {0};
11291 8 : size_t count[1] = {nVarLen};
11292 :
11293 : /* get the values as tokens */
11294 16 : CPLStringList aosValues = NCDFTokenizeArray(pszValue);
11295 8 : if (aosValues.empty())
11296 0 : return CE_Failure;
11297 :
11298 8 : nVarLen = aosValues.size();
11299 :
11300 : /* now write the data */
11301 8 : if (nVarType == NC_CHAR)
11302 : {
11303 0 : status = nc_put_vara_text(nCdfId, nVarId, start, count, pszValue);
11304 0 : NCDF_ERR(status);
11305 : }
11306 : else
11307 : {
11308 8 : switch (nVarType)
11309 : {
11310 0 : case NC_BYTE:
11311 : {
11312 : signed char *pscTemp = static_cast<signed char *>(
11313 0 : CPLCalloc(nVarLen, sizeof(signed char)));
11314 0 : for (size_t i = 0; i < nVarLen; i++)
11315 : {
11316 0 : char *pszTemp = nullptr;
11317 0 : pscTemp[i] = static_cast<signed char>(
11318 0 : strtol(aosValues[i], &pszTemp, 10));
11319 : }
11320 : status =
11321 0 : nc_put_vara_schar(nCdfId, nVarId, start, count, pscTemp);
11322 0 : NCDF_ERR(status);
11323 0 : CPLFree(pscTemp);
11324 0 : break;
11325 : }
11326 0 : case NC_SHORT:
11327 : {
11328 : short *psTemp =
11329 0 : static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
11330 0 : for (size_t i = 0; i < nVarLen; i++)
11331 : {
11332 0 : char *pszTemp = nullptr;
11333 0 : psTemp[i] =
11334 0 : static_cast<short>(strtol(aosValues[i], &pszTemp, 10));
11335 : }
11336 : status =
11337 0 : nc_put_vara_short(nCdfId, nVarId, start, count, psTemp);
11338 0 : NCDF_ERR(status);
11339 0 : CPLFree(psTemp);
11340 0 : break;
11341 : }
11342 3 : case NC_INT:
11343 : {
11344 : int *pnTemp =
11345 3 : static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
11346 11 : for (size_t i = 0; i < nVarLen; i++)
11347 : {
11348 8 : char *pszTemp = nullptr;
11349 8 : pnTemp[i] =
11350 8 : static_cast<int>(strtol(aosValues[i], &pszTemp, 10));
11351 : }
11352 3 : status = nc_put_vara_int(nCdfId, nVarId, start, count, pnTemp);
11353 3 : NCDF_ERR(status);
11354 3 : CPLFree(pnTemp);
11355 3 : break;
11356 : }
11357 0 : case NC_FLOAT:
11358 : {
11359 : float *pfTemp =
11360 0 : static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
11361 0 : for (size_t i = 0; i < nVarLen; i++)
11362 : {
11363 0 : char *pszTemp = nullptr;
11364 0 : pfTemp[i] =
11365 0 : static_cast<float>(CPLStrtod(aosValues[i], &pszTemp));
11366 : }
11367 : status =
11368 0 : nc_put_vara_float(nCdfId, nVarId, start, count, pfTemp);
11369 0 : NCDF_ERR(status);
11370 0 : CPLFree(pfTemp);
11371 0 : break;
11372 : }
11373 5 : case NC_DOUBLE:
11374 : {
11375 : double *pdfTemp =
11376 5 : static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
11377 19 : for (size_t i = 0; i < nVarLen; i++)
11378 : {
11379 14 : char *pszTemp = nullptr;
11380 14 : pdfTemp[i] = CPLStrtod(aosValues[i], &pszTemp);
11381 : }
11382 : status =
11383 5 : nc_put_vara_double(nCdfId, nVarId, start, count, pdfTemp);
11384 5 : NCDF_ERR(status);
11385 5 : CPLFree(pdfTemp);
11386 5 : break;
11387 : }
11388 0 : default:
11389 : {
11390 0 : int nTmpFormat = 0;
11391 0 : status = nc_inq_format(nCdfId, &nTmpFormat);
11392 0 : NCDF_ERR(status);
11393 0 : if (nTmpFormat == NCDF_FORMAT_NC4)
11394 : {
11395 0 : switch (nVarType)
11396 : {
11397 0 : case NC_STRING:
11398 : {
11399 0 : status = nc_put_vara_string(
11400 : nCdfId, nVarId, start, count,
11401 0 : const_cast<const char **>(aosValues.List()));
11402 0 : NCDF_ERR(status);
11403 0 : break;
11404 : }
11405 0 : case NC_UBYTE:
11406 : {
11407 : unsigned char *pucTemp =
11408 : static_cast<unsigned char *>(
11409 0 : CPLCalloc(nVarLen, sizeof(unsigned char)));
11410 0 : for (size_t i = 0; i < nVarLen; i++)
11411 : {
11412 0 : char *pszTemp = nullptr;
11413 0 : pucTemp[i] = static_cast<unsigned char>(
11414 0 : strtoul(aosValues[i], &pszTemp, 10));
11415 : }
11416 0 : status = nc_put_vara_uchar(nCdfId, nVarId, start,
11417 : count, pucTemp);
11418 0 : NCDF_ERR(status);
11419 0 : CPLFree(pucTemp);
11420 0 : break;
11421 : }
11422 0 : case NC_USHORT:
11423 : {
11424 : unsigned short *pusTemp =
11425 : static_cast<unsigned short *>(
11426 0 : CPLCalloc(nVarLen, sizeof(unsigned short)));
11427 0 : for (size_t i = 0; i < nVarLen; i++)
11428 : {
11429 0 : char *pszTemp = nullptr;
11430 0 : pusTemp[i] = static_cast<unsigned short>(
11431 0 : strtoul(aosValues[i], &pszTemp, 10));
11432 : }
11433 0 : status = nc_put_vara_ushort(nCdfId, nVarId, start,
11434 : count, pusTemp);
11435 0 : NCDF_ERR(status);
11436 0 : CPLFree(pusTemp);
11437 0 : break;
11438 : }
11439 0 : case NC_UINT:
11440 : {
11441 : unsigned int *punTemp = static_cast<unsigned int *>(
11442 0 : CPLCalloc(nVarLen, sizeof(unsigned int)));
11443 0 : for (size_t i = 0; i < nVarLen; i++)
11444 : {
11445 0 : char *pszTemp = nullptr;
11446 0 : punTemp[i] = static_cast<unsigned int>(
11447 0 : strtoul(aosValues[i], &pszTemp, 10));
11448 : }
11449 0 : status = nc_put_vara_uint(nCdfId, nVarId, start,
11450 : count, punTemp);
11451 0 : NCDF_ERR(status);
11452 0 : CPLFree(punTemp);
11453 0 : break;
11454 : }
11455 0 : default:
11456 0 : return CE_Failure;
11457 : }
11458 : }
11459 0 : break;
11460 : }
11461 : }
11462 : }
11463 :
11464 8 : return CE_None;
11465 : }
11466 :
11467 : /************************************************************************/
11468 : /* GetDefaultNoDataValue() */
11469 : /************************************************************************/
11470 :
11471 200 : double NCDFGetDefaultNoDataValue(int nCdfId, int nVarId, int nVarType,
11472 : bool &bGotNoData)
11473 :
11474 : {
11475 200 : int nNoFill = 0;
11476 200 : double dfNoData = 0.0;
11477 :
11478 200 : switch (nVarType)
11479 : {
11480 0 : case NC_CHAR:
11481 : case NC_BYTE:
11482 : case NC_UBYTE:
11483 : // Don't do default fill-values for bytes, too risky.
11484 : // This function should not be called in those cases.
11485 0 : CPLAssert(false);
11486 : break;
11487 24 : case NC_SHORT:
11488 : {
11489 24 : short nFillVal = 0;
11490 24 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11491 : NC_NOERR)
11492 : {
11493 24 : if (!nNoFill)
11494 : {
11495 23 : bGotNoData = true;
11496 23 : dfNoData = nFillVal;
11497 : }
11498 : }
11499 : else
11500 0 : dfNoData = NC_FILL_SHORT;
11501 24 : break;
11502 : }
11503 26 : case NC_INT:
11504 : {
11505 26 : int nFillVal = 0;
11506 26 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11507 : NC_NOERR)
11508 : {
11509 26 : if (!nNoFill)
11510 : {
11511 25 : bGotNoData = true;
11512 25 : dfNoData = nFillVal;
11513 : }
11514 : }
11515 : else
11516 0 : dfNoData = NC_FILL_INT;
11517 26 : break;
11518 : }
11519 83 : case NC_FLOAT:
11520 : {
11521 83 : float fFillVal = 0;
11522 83 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &fFillVal) ==
11523 : NC_NOERR)
11524 : {
11525 83 : if (!nNoFill)
11526 : {
11527 79 : bGotNoData = true;
11528 79 : dfNoData = fFillVal;
11529 : }
11530 : }
11531 : else
11532 0 : dfNoData = NC_FILL_FLOAT;
11533 83 : break;
11534 : }
11535 34 : case NC_DOUBLE:
11536 : {
11537 34 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &dfNoData) ==
11538 : NC_NOERR)
11539 : {
11540 34 : if (!nNoFill)
11541 : {
11542 34 : bGotNoData = true;
11543 : }
11544 : }
11545 : else
11546 0 : dfNoData = NC_FILL_DOUBLE;
11547 34 : break;
11548 : }
11549 7 : case NC_USHORT:
11550 : {
11551 7 : unsigned short nFillVal = 0;
11552 7 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11553 : NC_NOERR)
11554 : {
11555 7 : if (!nNoFill)
11556 : {
11557 7 : bGotNoData = true;
11558 7 : dfNoData = nFillVal;
11559 : }
11560 : }
11561 : else
11562 0 : dfNoData = NC_FILL_USHORT;
11563 7 : break;
11564 : }
11565 7 : case NC_UINT:
11566 : {
11567 7 : unsigned int nFillVal = 0;
11568 7 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11569 : NC_NOERR)
11570 : {
11571 7 : if (!nNoFill)
11572 : {
11573 7 : bGotNoData = true;
11574 7 : dfNoData = nFillVal;
11575 : }
11576 : }
11577 : else
11578 0 : dfNoData = NC_FILL_UINT;
11579 7 : break;
11580 : }
11581 19 : default:
11582 19 : dfNoData = 0.0;
11583 19 : break;
11584 : }
11585 :
11586 200 : return dfNoData;
11587 : }
11588 :
11589 : /************************************************************************/
11590 : /* NCDFGetDefaultNoDataValueAsInt64() */
11591 : /************************************************************************/
11592 :
11593 2 : int64_t NCDFGetDefaultNoDataValueAsInt64(int nCdfId, int nVarId,
11594 : bool &bGotNoData)
11595 :
11596 : {
11597 2 : int nNoFill = 0;
11598 2 : long long nFillVal = 0;
11599 2 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
11600 : {
11601 2 : if (!nNoFill)
11602 : {
11603 2 : bGotNoData = true;
11604 2 : return static_cast<int64_t>(nFillVal);
11605 : }
11606 : }
11607 : else
11608 0 : return static_cast<int64_t>(NC_FILL_INT64);
11609 0 : return 0;
11610 : }
11611 :
11612 : /************************************************************************/
11613 : /* NCDFGetDefaultNoDataValueAsUInt64() */
11614 : /************************************************************************/
11615 :
11616 1 : uint64_t NCDFGetDefaultNoDataValueAsUInt64(int nCdfId, int nVarId,
11617 : bool &bGotNoData)
11618 :
11619 : {
11620 1 : int nNoFill = 0;
11621 1 : unsigned long long nFillVal = 0;
11622 1 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
11623 : {
11624 1 : if (!nNoFill)
11625 : {
11626 1 : bGotNoData = true;
11627 1 : return static_cast<uint64_t>(nFillVal);
11628 : }
11629 : }
11630 : else
11631 0 : return static_cast<uint64_t>(NC_FILL_UINT64);
11632 0 : return 0;
11633 : }
11634 :
11635 12255 : static int NCDFDoesVarContainAttribVal(int nCdfId,
11636 : const char *const *papszAttribNames,
11637 : const char *const *papszAttribValues,
11638 : int nVarId, const char *pszVarName,
11639 : bool bStrict = true)
11640 : {
11641 12255 : if (nVarId == -1 && pszVarName != nullptr)
11642 8312 : NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
11643 :
11644 12255 : if (nVarId == -1)
11645 950 : return -1;
11646 :
11647 11305 : bool bFound = false;
11648 52686 : for (int i = 0; !bFound && papszAttribNames != nullptr &&
11649 50197 : papszAttribNames[i] != nullptr;
11650 : i++)
11651 : {
11652 41381 : char *pszTemp = nullptr;
11653 41381 : if (NCDFGetAttr(nCdfId, nVarId, papszAttribNames[i], &pszTemp) ==
11654 59459 : CE_None &&
11655 18078 : pszTemp != nullptr)
11656 : {
11657 18078 : if (bStrict)
11658 : {
11659 18078 : if (EQUAL(pszTemp, papszAttribValues[i]))
11660 2489 : bFound = true;
11661 : }
11662 : else
11663 : {
11664 0 : if (EQUALN(pszTemp, papszAttribValues[i],
11665 : strlen(papszAttribValues[i])))
11666 0 : bFound = true;
11667 : }
11668 18078 : CPLFree(pszTemp);
11669 : }
11670 : }
11671 11305 : return bFound;
11672 : }
11673 :
11674 2133 : static int NCDFDoesVarContainAttribVal2(int nCdfId, const char *papszAttribName,
11675 : const char *const *papszAttribValues,
11676 : int nVarId, const char *pszVarName,
11677 : int bStrict = true)
11678 : {
11679 2133 : if (nVarId == -1 && pszVarName != nullptr)
11680 1579 : NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
11681 :
11682 2133 : if (nVarId == -1)
11683 0 : return -1;
11684 :
11685 2133 : bool bFound = false;
11686 2133 : char *pszTemp = nullptr;
11687 2512 : if (NCDFGetAttr(nCdfId, nVarId, papszAttribName, &pszTemp) != CE_None ||
11688 379 : pszTemp == nullptr)
11689 1754 : return FALSE;
11690 :
11691 7703 : for (int i = 0; !bFound && i < CSLCount(papszAttribValues); i++)
11692 : {
11693 7324 : if (bStrict)
11694 : {
11695 7296 : if (EQUAL(pszTemp, papszAttribValues[i]))
11696 31 : bFound = true;
11697 : }
11698 : else
11699 : {
11700 28 : if (EQUALN(pszTemp, papszAttribValues[i],
11701 : strlen(papszAttribValues[i])))
11702 0 : bFound = true;
11703 : }
11704 : }
11705 :
11706 379 : CPLFree(pszTemp);
11707 :
11708 379 : return bFound;
11709 : }
11710 :
11711 948 : static bool NCDFEqual(const char *papszName, const char *const *papszValues)
11712 : {
11713 948 : if (papszName == nullptr || EQUAL(papszName, ""))
11714 0 : return false;
11715 :
11716 2560 : for (int i = 0; papszValues && papszValues[i]; ++i)
11717 : {
11718 1756 : if (EQUAL(papszName, papszValues[i]))
11719 144 : return true;
11720 : }
11721 :
11722 804 : return false;
11723 : }
11724 :
11725 : // Test that a variable is longitude/latitude coordinate,
11726 : // following CF 4.1 and 4.2.
11727 4125 : bool NCDFIsVarLongitude(int nCdfId, int nVarId, const char *pszVarName)
11728 : {
11729 : // Check for matching attributes.
11730 4125 : int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLongitudeAttribNames,
11731 : papszCFLongitudeAttribValues, nVarId,
11732 : pszVarName);
11733 : // If not found using attributes then check using var name
11734 : // unless GDAL_NETCDF_VERIFY_DIMS=STRICT.
11735 4125 : if (bVal == -1)
11736 : {
11737 304 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11738 : "STRICT"))
11739 304 : bVal = NCDFEqual(pszVarName, papszCFLongitudeVarNames);
11740 : else
11741 0 : bVal = FALSE;
11742 : }
11743 3821 : else if (bVal)
11744 : {
11745 : // Check that the units is not 'm' or '1'. See #6759
11746 805 : char *pszTemp = nullptr;
11747 1187 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11748 382 : pszTemp != nullptr)
11749 : {
11750 382 : if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
11751 97 : bVal = false;
11752 382 : CPLFree(pszTemp);
11753 : }
11754 : }
11755 :
11756 4125 : return CPL_TO_BOOL(bVal);
11757 : }
11758 :
11759 2369 : bool NCDFIsVarLatitude(int nCdfId, int nVarId, const char *pszVarName)
11760 : {
11761 2369 : int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLatitudeAttribNames,
11762 : papszCFLatitudeAttribValues, nVarId,
11763 : pszVarName);
11764 2369 : if (bVal == -1)
11765 : {
11766 175 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11767 : "STRICT"))
11768 175 : bVal = NCDFEqual(pszVarName, papszCFLatitudeVarNames);
11769 : else
11770 0 : bVal = FALSE;
11771 : }
11772 2194 : else if (bVal)
11773 : {
11774 : // Check that the units is not 'm' or '1'. See #6759
11775 556 : char *pszTemp = nullptr;
11776 698 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11777 142 : pszTemp != nullptr)
11778 : {
11779 142 : if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
11780 36 : bVal = false;
11781 142 : CPLFree(pszTemp);
11782 : }
11783 : }
11784 :
11785 2369 : return CPL_TO_BOOL(bVal);
11786 : }
11787 :
11788 2572 : bool NCDFIsVarProjectionX(int nCdfId, int nVarId, const char *pszVarName)
11789 : {
11790 2572 : int bVal = NCDFDoesVarContainAttribVal(
11791 : nCdfId, papszCFProjectionXAttribNames, papszCFProjectionXAttribValues,
11792 : nVarId, pszVarName);
11793 2572 : if (bVal == -1)
11794 : {
11795 298 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11796 : "STRICT"))
11797 298 : bVal = NCDFEqual(pszVarName, papszCFProjectionXVarNames);
11798 : else
11799 0 : bVal = FALSE;
11800 : }
11801 2274 : else if (bVal)
11802 : {
11803 : // Check that the units is not '1'
11804 458 : char *pszTemp = nullptr;
11805 686 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11806 228 : pszTemp != nullptr)
11807 : {
11808 228 : if (EQUAL(pszTemp, "1"))
11809 5 : bVal = false;
11810 228 : CPLFree(pszTemp);
11811 : }
11812 : }
11813 :
11814 2572 : return CPL_TO_BOOL(bVal);
11815 : }
11816 :
11817 1812 : bool NCDFIsVarProjectionY(int nCdfId, int nVarId, const char *pszVarName)
11818 : {
11819 1812 : int bVal = NCDFDoesVarContainAttribVal(
11820 : nCdfId, papszCFProjectionYAttribNames, papszCFProjectionYAttribValues,
11821 : nVarId, pszVarName);
11822 1812 : if (bVal == -1)
11823 : {
11824 171 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11825 : "STRICT"))
11826 171 : bVal = NCDFEqual(pszVarName, papszCFProjectionYVarNames);
11827 : else
11828 0 : bVal = FALSE;
11829 : }
11830 1641 : else if (bVal)
11831 : {
11832 : // Check that the units is not '1'
11833 452 : char *pszTemp = nullptr;
11834 675 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11835 223 : pszTemp != nullptr)
11836 : {
11837 223 : if (EQUAL(pszTemp, "1"))
11838 5 : bVal = false;
11839 223 : CPLFree(pszTemp);
11840 : }
11841 : }
11842 :
11843 1812 : return CPL_TO_BOOL(bVal);
11844 : }
11845 :
11846 : /* test that a variable is a vertical coordinate, following CF 4.3 */
11847 1118 : bool NCDFIsVarVerticalCoord(int nCdfId, int nVarId, const char *pszVarName)
11848 : {
11849 : /* check for matching attributes */
11850 1118 : if (NCDFDoesVarContainAttribVal(nCdfId, papszCFVerticalAttribNames,
11851 : papszCFVerticalAttribValues, nVarId,
11852 1118 : pszVarName))
11853 111 : return true;
11854 : /* check for matching units */
11855 1007 : else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
11856 : papszCFVerticalUnitsValues, nVarId,
11857 1007 : pszVarName))
11858 31 : return true;
11859 : /* check for matching standard name */
11860 976 : else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_STD_NAME,
11861 : papszCFVerticalStandardNameValues,
11862 976 : nVarId, pszVarName))
11863 0 : return true;
11864 : else
11865 976 : return false;
11866 : }
11867 :
11868 : /* test that a variable is a time coordinate, following CF 4.4 */
11869 259 : bool NCDFIsVarTimeCoord(int nCdfId, int nVarId, const char *pszVarName)
11870 : {
11871 : /* check for matching attributes */
11872 259 : if (NCDFDoesVarContainAttribVal(nCdfId, papszCFTimeAttribNames,
11873 : papszCFTimeAttribValues, nVarId,
11874 259 : pszVarName))
11875 109 : return true;
11876 : /* check for matching units */
11877 150 : else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
11878 : papszCFTimeUnitsValues, nVarId,
11879 150 : pszVarName, false))
11880 0 : return true;
11881 : else
11882 150 : return false;
11883 : }
11884 :
11885 : // Parse a string, and return as a string list.
11886 : // If it is an array of the form {a,b}, then tokenize it.
11887 : // Otherwise, return a copy.
11888 200 : static CPLStringList NCDFTokenizeArray(const char *pszValue)
11889 : {
11890 200 : if (pszValue == nullptr || EQUAL(pszValue, ""))
11891 59 : return CPLStringList();
11892 :
11893 282 : CPLStringList aosValues;
11894 141 : const int nLen = static_cast<int>(strlen(pszValue));
11895 :
11896 141 : if (pszValue[0] == '{' && nLen > 2 && pszValue[nLen - 1] == '}')
11897 : {
11898 41 : char *pszTemp = static_cast<char *>(CPLMalloc((nLen - 2) + 1));
11899 41 : strncpy(pszTemp, pszValue + 1, nLen - 2);
11900 41 : pszTemp[nLen - 2] = '\0';
11901 : aosValues.Assign(
11902 41 : CSLTokenizeString2(pszTemp, ",", CSLT_ALLOWEMPTYTOKENS));
11903 41 : CPLFree(pszTemp);
11904 : }
11905 : else
11906 : {
11907 100 : aosValues.AddString(pszValue);
11908 : }
11909 :
11910 141 : return aosValues;
11911 : }
11912 :
11913 : // Open a NetCDF subdataset from full path /group1/group2/.../groupn/var.
11914 : // Leading slash is optional.
11915 436 : static CPLErr NCDFOpenSubDataset(int nCdfId, const char *pszSubdatasetName,
11916 : int *pnGroupId, int *pnVarId)
11917 : {
11918 436 : *pnGroupId = -1;
11919 436 : *pnVarId = -1;
11920 :
11921 : // Open group.
11922 : char *pszGroupFullName =
11923 436 : CPLStrdup(CPLGetPathSafe(pszSubdatasetName).c_str());
11924 : // Add a leading slash if needed.
11925 436 : if (pszGroupFullName[0] != '/')
11926 : {
11927 419 : char *old = pszGroupFullName;
11928 419 : pszGroupFullName = CPLStrdup(CPLSPrintf("/%s", pszGroupFullName));
11929 419 : CPLFree(old);
11930 : }
11931 : // Detect root group.
11932 436 : if (EQUAL(pszGroupFullName, "/"))
11933 : {
11934 419 : *pnGroupId = nCdfId;
11935 419 : CPLFree(pszGroupFullName);
11936 : }
11937 : else
11938 : {
11939 17 : int status = nc_inq_grp_full_ncid(nCdfId, pszGroupFullName, pnGroupId);
11940 17 : CPLFree(pszGroupFullName);
11941 17 : NCDF_ERR_RET(status);
11942 : }
11943 :
11944 : // Open var.
11945 436 : const char *pszVarName = CPLGetFilename(pszSubdatasetName);
11946 436 : NCDF_ERR_RET(nc_inq_varid(*pnGroupId, pszVarName, pnVarId));
11947 :
11948 436 : return CE_None;
11949 : }
11950 :
11951 : // Get all dimensions visible from a given NetCDF (or group) ID and any of
11952 : // its parents.
11953 372 : static CPLErr NCDFGetVisibleDims(int nGroupId, int *pnDims, int **ppanDimIds)
11954 : {
11955 372 : int nDims = 0;
11956 372 : int *panDimIds = nullptr;
11957 372 : NCDF_ERR_RET(nc_inq_dimids(nGroupId, &nDims, nullptr, true));
11958 :
11959 372 : panDimIds = static_cast<int *>(CPLMalloc(nDims * sizeof(int)));
11960 :
11961 372 : int status = nc_inq_dimids(nGroupId, nullptr, panDimIds, true);
11962 372 : if (status != NC_NOERR)
11963 0 : CPLFree(panDimIds);
11964 372 : NCDF_ERR_RET(status);
11965 :
11966 372 : *pnDims = nDims;
11967 372 : *ppanDimIds = panDimIds;
11968 :
11969 372 : return CE_None;
11970 : }
11971 :
11972 : // Get direct sub-groups IDs of a given NetCDF (or group) ID.
11973 : // Consider only direct children, does not get children of children.
11974 3286 : static CPLErr NCDFGetSubGroups(int nGroupId, int *pnSubGroups,
11975 : int **ppanSubGroupIds)
11976 : {
11977 3286 : *pnSubGroups = 0;
11978 3286 : *ppanSubGroupIds = nullptr;
11979 :
11980 : int nSubGroups;
11981 3286 : NCDF_ERR_RET(nc_inq_grps(nGroupId, &nSubGroups, nullptr));
11982 : int *panSubGroupIds =
11983 3286 : static_cast<int *>(CPLMalloc(nSubGroups * sizeof(int)));
11984 3286 : NCDF_ERR_RET(nc_inq_grps(nGroupId, nullptr, panSubGroupIds));
11985 3286 : *pnSubGroups = nSubGroups;
11986 3286 : *ppanSubGroupIds = panSubGroupIds;
11987 :
11988 3286 : return CE_None;
11989 : }
11990 :
11991 : // Get the full name of a given NetCDF (or group) ID
11992 : // (e.g. /group1/group2/.../groupn).
11993 : // bNC3Compat remove the leading slash for top-level variables for
11994 : // backward compatibility (top-level variables are the ones in the root group).
11995 18634 : static CPLErr NCDFGetGroupFullName(int nGroupId, char **ppszFullName,
11996 : bool bNC3Compat)
11997 : {
11998 18634 : *ppszFullName = nullptr;
11999 :
12000 : size_t nFullNameLen;
12001 18634 : NCDF_ERR_RET(nc_inq_grpname_len(nGroupId, &nFullNameLen));
12002 18634 : *ppszFullName =
12003 18634 : static_cast<char *>(CPLMalloc((nFullNameLen + 1) * sizeof(char)));
12004 18634 : int status = nc_inq_grpname_full(nGroupId, &nFullNameLen, *ppszFullName);
12005 18634 : if (status != NC_NOERR)
12006 : {
12007 0 : CPLFree(*ppszFullName);
12008 0 : *ppszFullName = nullptr;
12009 0 : NCDF_ERR_RET(status);
12010 : }
12011 :
12012 18634 : if (bNC3Compat && EQUAL(*ppszFullName, "/"))
12013 8334 : (*ppszFullName)[0] = '\0';
12014 :
12015 18634 : return CE_None;
12016 : }
12017 :
12018 10092 : CPLString NCDFGetGroupFullName(int nGroupId)
12019 : {
12020 10092 : char *pszFullname = nullptr;
12021 10092 : NCDFGetGroupFullName(nGroupId, &pszFullname, false);
12022 10092 : CPLString osRet(pszFullname ? pszFullname : "");
12023 10092 : CPLFree(pszFullname);
12024 20184 : return osRet;
12025 : }
12026 :
12027 : // Get the full name of a given NetCDF variable ID
12028 : // (e.g. /group1/group2/.../groupn/var).
12029 : // Handle also NC_GLOBAL as nVarId.
12030 : // bNC3Compat remove the leading slash for top-level variables for
12031 : // backward compatibility (top-level variables are the ones in the root group).
12032 8493 : static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId, char **ppszFullName,
12033 : bool bNC3Compat)
12034 : {
12035 8493 : *ppszFullName = nullptr;
12036 8493 : char *pszGroupFullName = nullptr;
12037 8493 : ERR_RET(NCDFGetGroupFullName(nGroupId, &pszGroupFullName, bNC3Compat));
12038 : char szVarName[NC_MAX_NAME + 1];
12039 8493 : if (nVarId == NC_GLOBAL)
12040 : {
12041 1142 : strcpy(szVarName, "NC_GLOBAL");
12042 : }
12043 : else
12044 : {
12045 7351 : int status = nc_inq_varname(nGroupId, nVarId, szVarName);
12046 7351 : if (status != NC_NOERR)
12047 : {
12048 0 : CPLFree(pszGroupFullName);
12049 0 : NCDF_ERR_RET(status);
12050 : }
12051 : }
12052 8493 : const char *pszSep = "/";
12053 8493 : if (EQUAL(pszGroupFullName, "/") || EQUAL(pszGroupFullName, ""))
12054 8287 : pszSep = "";
12055 8493 : *ppszFullName =
12056 8493 : CPLStrdup(CPLSPrintf("%s%s%s", pszGroupFullName, pszSep, szVarName));
12057 8493 : CPLFree(pszGroupFullName);
12058 8493 : return CE_None;
12059 : }
12060 :
12061 : // Get the NetCDF root group ID of a given group ID.
12062 0 : static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId)
12063 : {
12064 0 : *pnRootGroupId = -1;
12065 : // Recurse on parent group.
12066 : int nParentGroupId;
12067 0 : int status = nc_inq_grp_parent(nStartGroupId, &nParentGroupId);
12068 0 : if (status == NC_NOERR)
12069 0 : return NCDFGetRootGroup(nParentGroupId, pnRootGroupId);
12070 0 : else if (status != NC_ENOGRP)
12071 0 : NCDF_ERR_RET(status);
12072 : else // No more parent group.
12073 : {
12074 0 : *pnRootGroupId = nStartGroupId;
12075 : }
12076 :
12077 0 : return CE_None;
12078 : }
12079 :
12080 : // Implementation of NCDFResolveVar/Att.
12081 13555 : static CPLErr NCDFResolveElem(int nStartGroupId, const char *pszVar,
12082 : const char *pszAtt, int *pnGroupId, int *pnId,
12083 : bool bMandatory)
12084 : {
12085 13555 : if (!pszVar && !pszAtt)
12086 : {
12087 0 : CPLError(CE_Failure, CPLE_IllegalArg,
12088 : "pszVar and pszAtt NCDFResolveElem() args are both null.");
12089 0 : return CE_Failure;
12090 : }
12091 :
12092 : enum
12093 : {
12094 : NCRM_PARENT,
12095 : NCRM_WIDTH_WISE
12096 13555 : } eNCResolveMode = NCRM_PARENT;
12097 :
12098 27110 : std::queue<int> aoQueueGroupIdsToVisit;
12099 13555 : aoQueueGroupIdsToVisit.push(nStartGroupId);
12100 :
12101 15405 : while (!aoQueueGroupIdsToVisit.empty())
12102 : {
12103 : // Get the first group of the FIFO queue.
12104 13749 : *pnGroupId = aoQueueGroupIdsToVisit.front();
12105 13749 : aoQueueGroupIdsToVisit.pop();
12106 :
12107 : // Look if this group contains the searched element.
12108 : int status;
12109 13749 : if (pszVar)
12110 13524 : status = nc_inq_varid(*pnGroupId, pszVar, pnId);
12111 : else // pszAtt != nullptr.
12112 225 : status = nc_inq_attid(*pnGroupId, NC_GLOBAL, pszAtt, pnId);
12113 :
12114 13749 : if (status == NC_NOERR)
12115 : {
12116 11899 : return CE_None;
12117 : }
12118 1850 : else if ((pszVar && status != NC_ENOTVAR) ||
12119 222 : (pszAtt && status != NC_ENOTATT))
12120 : {
12121 0 : NCDF_ERR(status);
12122 : }
12123 : // Element not found, in NC4 case we must search in other groups
12124 : // following the CF logic.
12125 :
12126 : // The first resolve mode consists to search on parent groups.
12127 1850 : if (eNCResolveMode == NCRM_PARENT)
12128 : {
12129 1731 : int nParentGroupId = -1;
12130 1731 : int status2 = nc_inq_grp_parent(*pnGroupId, &nParentGroupId);
12131 1731 : if (status2 == NC_NOERR)
12132 62 : aoQueueGroupIdsToVisit.push(nParentGroupId);
12133 1669 : else if (status2 != NC_ENOGRP)
12134 0 : NCDF_ERR(status2);
12135 1669 : else if (pszVar)
12136 : // When resolving a variable, if there is no more
12137 : // parent group then we switch to width-wise search mode
12138 : // starting from the latest found parent group.
12139 1450 : eNCResolveMode = NCRM_WIDTH_WISE;
12140 : }
12141 :
12142 : // The second resolve mode is a width-wise search.
12143 1850 : if (eNCResolveMode == NCRM_WIDTH_WISE)
12144 : {
12145 : // Enqueue all direct sub-groups.
12146 1569 : int nSubGroups = 0;
12147 1569 : int *panSubGroupIds = nullptr;
12148 1569 : NCDFGetSubGroups(*pnGroupId, &nSubGroups, &panSubGroupIds);
12149 1701 : for (int i = 0; i < nSubGroups; i++)
12150 132 : aoQueueGroupIdsToVisit.push(panSubGroupIds[i]);
12151 1569 : CPLFree(panSubGroupIds);
12152 : }
12153 : }
12154 :
12155 1656 : if (bMandatory)
12156 : {
12157 0 : char *pszStartGroupFullName = nullptr;
12158 0 : NCDFGetGroupFullName(nStartGroupId, &pszStartGroupFullName);
12159 0 : CPLError(CE_Failure, CPLE_AppDefined,
12160 : "Cannot resolve mandatory %s %s from group %s",
12161 : (pszVar ? pszVar : pszAtt),
12162 : (pszVar ? "variable" : "attribute"),
12163 0 : (pszStartGroupFullName ? pszStartGroupFullName : ""));
12164 0 : CPLFree(pszStartGroupFullName);
12165 : }
12166 :
12167 1656 : *pnGroupId = -1;
12168 1656 : *pnId = -1;
12169 1656 : return CE_Failure;
12170 : }
12171 :
12172 : // Resolve a variable name from a given starting group following the CF logic:
12173 : // - if var name is an absolute path then directly open it
12174 : // - first search in the starting group and its parent groups
12175 : // - then if there is no more parent group we switch to a width-wise search
12176 : // mode starting from the latest found parent group.
12177 : // The full CF logic is described here:
12178 : // https://github.com/diwg/cf2/blob/master/group/cf2-group.adoc#scope
12179 : // If bMandatory then print an error if resolving fails.
12180 : // TODO: implement support of relative paths.
12181 : // TODO: to follow strictly the CF logic, when searching for a coordinate
12182 : // variable, we must stop the parent search mode once the corresponding
12183 : // dimension is found and start the width-wise search from this group.
12184 : // TODO: to follow strictly the CF logic, when searching in width-wise mode
12185 : // we should skip every groups already visited during the parent
12186 : // search mode (but revisiting them should have no impact so we could
12187 : // let as it is if it is simpler...)
12188 : // TODO: CF specifies that the width-wise search order is "left-to-right" so
12189 : // maybe we must sort sibling groups alphabetically? but maybe not
12190 : // necessary if nc_inq_grps() already sort them?
12191 13333 : CPLErr NCDFResolveVar(int nStartGroupId, const char *pszVar, int *pnGroupId,
12192 : int *pnVarId, bool bMandatory)
12193 : {
12194 13333 : *pnGroupId = -1;
12195 13333 : *pnVarId = -1;
12196 13333 : int nGroupId = nStartGroupId, nVarId;
12197 13333 : if (pszVar[0] == '/')
12198 : {
12199 : // This is an absolute path: we can open the var directly.
12200 : int nRootGroupId;
12201 0 : ERR_RET(NCDFGetRootGroup(nStartGroupId, &nRootGroupId));
12202 0 : ERR_RET(NCDFOpenSubDataset(nRootGroupId, pszVar, &nGroupId, &nVarId));
12203 : }
12204 : else
12205 : {
12206 : // We have to search the variable following the CF logic.
12207 13333 : ERR_RET(NCDFResolveElem(nStartGroupId, pszVar, nullptr, &nGroupId,
12208 : &nVarId, bMandatory));
12209 : }
12210 11896 : *pnGroupId = nGroupId;
12211 11896 : *pnVarId = nVarId;
12212 11896 : return CE_None;
12213 : }
12214 :
12215 : // Like NCDFResolveVar but returns directly the var full name.
12216 1416 : static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,
12217 : char **ppszFullName, bool bMandatory)
12218 : {
12219 1416 : *ppszFullName = nullptr;
12220 : int nGroupId, nVarId;
12221 1416 : ERR_RET(
12222 : NCDFResolveVar(nStartGroupId, pszVar, &nGroupId, &nVarId, bMandatory));
12223 1390 : return NCDFGetVarFullName(nGroupId, nVarId, ppszFullName);
12224 : }
12225 :
12226 : // Like NCDFResolveVar but resolves an attribute instead a variable and
12227 : // returns its integer value.
12228 : // Only GLOBAL attributes are supported for the moment.
12229 222 : static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,
12230 : const char *pszAtt, int *pnAtt, bool bMandatory)
12231 : {
12232 222 : int nGroupId = nStartGroupId, nAttId = nStartVarId;
12233 222 : ERR_RET(NCDFResolveElem(nStartGroupId, nullptr, pszAtt, &nGroupId, &nAttId,
12234 : bMandatory));
12235 3 : NCDF_ERR_RET(nc_get_att_int(nGroupId, NC_GLOBAL, pszAtt, pnAtt));
12236 3 : return CE_None;
12237 : }
12238 :
12239 : // Filter variables to keep only valid 2+D raster bands and vector fields in
12240 : // a given a NetCDF (or group) ID and its sub-groups.
12241 : // Coordinate or boundary variables are ignored.
12242 : // It also creates corresponding vector layers.
12243 548 : CPLErr netCDFDataset::FilterVars(
12244 : int nCdfId, bool bKeepRasters, bool bKeepVectors, char **papszIgnoreVars,
12245 : int *pnRasterVars, int *pnGroupId, int *pnVarId, int *pnIgnoredVars,
12246 : std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>
12247 : &oMap2DDimsToGroupAndVar)
12248 : {
12249 548 : int nVars = 0;
12250 548 : int nRasterVars = 0;
12251 548 : NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
12252 :
12253 1096 : std::vector<int> anPotentialVectorVarID;
12254 : // oMapDimIdToCount[x] = number of times dim x is the first dimension of
12255 : // potential vector variables
12256 1096 : std::map<int, int> oMapDimIdToCount;
12257 548 : int nVarXId = -1;
12258 548 : int nVarYId = -1;
12259 548 : int nVarZId = -1;
12260 548 : int nVarTimeId = -1;
12261 548 : int nVarTimeDimId = -1;
12262 548 : bool bIsVectorOnly = true;
12263 548 : int nProfileDimId = -1;
12264 548 : int nParentIndexVarID = -1;
12265 :
12266 3311 : for (int v = 0; v < nVars; v++)
12267 : {
12268 : int nVarDims;
12269 2763 : NCDF_ERR_RET(nc_inq_varndims(nCdfId, v, &nVarDims));
12270 : // Should we ignore this variable?
12271 : char szTemp[NC_MAX_NAME + 1];
12272 2763 : szTemp[0] = '\0';
12273 2763 : NCDF_ERR_RET(nc_inq_varname(nCdfId, v, szTemp));
12274 :
12275 2763 : if (strstr(szTemp, "_node_coordinates") ||
12276 2763 : strstr(szTemp, "_node_count"))
12277 : {
12278 : // Ignore CF-1.8 Simple Geometries helper variables
12279 69 : continue;
12280 : }
12281 :
12282 4003 : if (nVarDims == 1 && (NCDFIsVarLongitude(nCdfId, -1, szTemp) ||
12283 1309 : NCDFIsVarProjectionX(nCdfId, -1, szTemp)))
12284 : {
12285 365 : nVarXId = v;
12286 : }
12287 3273 : else if (nVarDims == 1 && (NCDFIsVarLatitude(nCdfId, -1, szTemp) ||
12288 944 : NCDFIsVarProjectionY(nCdfId, -1, szTemp)))
12289 : {
12290 364 : nVarYId = v;
12291 : }
12292 1965 : else if (nVarDims == 1 && NCDFIsVarVerticalCoord(nCdfId, -1, szTemp))
12293 : {
12294 78 : nVarZId = v;
12295 : }
12296 : else
12297 : {
12298 1887 : char *pszVarFullName = nullptr;
12299 1887 : CPLErr eErr = NCDFGetVarFullName(nCdfId, v, &pszVarFullName);
12300 1887 : if (eErr != CE_None)
12301 : {
12302 0 : CPLFree(pszVarFullName);
12303 0 : continue;
12304 : }
12305 : bool bIgnoreVar =
12306 1887 : (CSLFindString(papszIgnoreVars, pszVarFullName) != -1);
12307 1887 : CPLFree(pszVarFullName);
12308 1887 : if (bIgnoreVar)
12309 : {
12310 120 : if (nVarDims == 1 && NCDFIsVarTimeCoord(nCdfId, -1, szTemp))
12311 : {
12312 11 : nVarTimeId = v;
12313 11 : nc_inq_vardimid(nCdfId, v, &nVarTimeDimId);
12314 : }
12315 109 : else if (nVarDims > 1)
12316 : {
12317 105 : (*pnIgnoredVars)++;
12318 105 : CPLDebug("GDAL_netCDF", "variable #%d [%s] was ignored", v,
12319 : szTemp);
12320 : }
12321 : }
12322 : // Only accept 2+D vars.
12323 1767 : else if (nVarDims >= 2)
12324 : {
12325 724 : bool bRasterCandidate = true;
12326 : // Identify variables that might be vector variables
12327 724 : if (nVarDims == 2)
12328 : {
12329 648 : int anDimIds[2] = {-1, -1};
12330 648 : nc_inq_vardimid(nCdfId, v, anDimIds);
12331 :
12332 648 : nc_type vartype = NC_NAT;
12333 648 : nc_inq_vartype(nCdfId, v, &vartype);
12334 :
12335 : char szDimNameFirst[NC_MAX_NAME + 1];
12336 : char szDimNameSecond[NC_MAX_NAME + 1];
12337 648 : szDimNameFirst[0] = '\0';
12338 648 : szDimNameSecond[0] = '\0';
12339 1453 : if (vartype == NC_CHAR &&
12340 157 : nc_inq_dimname(nCdfId, anDimIds[0], szDimNameFirst) ==
12341 157 : NC_NOERR &&
12342 157 : nc_inq_dimname(nCdfId, anDimIds[1], szDimNameSecond) ==
12343 157 : NC_NOERR &&
12344 157 : !NCDFIsVarLongitude(nCdfId, -1, szDimNameSecond) &&
12345 157 : !NCDFIsVarProjectionX(nCdfId, -1, szDimNameSecond) &&
12346 962 : !NCDFIsVarLatitude(nCdfId, -1, szDimNameFirst) &&
12347 157 : !NCDFIsVarProjectionY(nCdfId, -1, szDimNameFirst))
12348 : {
12349 157 : anPotentialVectorVarID.push_back(v);
12350 157 : oMapDimIdToCount[anDimIds[0]]++;
12351 157 : if (strstr(szDimNameSecond, "_max_width"))
12352 : {
12353 127 : bRasterCandidate = false;
12354 : }
12355 : else
12356 : {
12357 30 : std::array<int, 3> oKey{anDimIds[0], anDimIds[1],
12358 30 : vartype};
12359 30 : oMap2DDimsToGroupAndVar[oKey].emplace_back(
12360 30 : std::pair(nCdfId, v));
12361 : }
12362 : }
12363 : else
12364 : {
12365 491 : std::array<int, 3> oKey{anDimIds[0], anDimIds[1],
12366 491 : vartype};
12367 491 : oMap2DDimsToGroupAndVar[oKey].emplace_back(
12368 491 : std::pair(nCdfId, v));
12369 491 : bIsVectorOnly = false;
12370 : }
12371 : }
12372 : else
12373 : {
12374 76 : bIsVectorOnly = false;
12375 : }
12376 724 : if (bKeepRasters && bRasterCandidate)
12377 : {
12378 568 : *pnGroupId = nCdfId;
12379 568 : *pnVarId = v;
12380 568 : nRasterVars++;
12381 : }
12382 : }
12383 1043 : else if (nVarDims == 1)
12384 : {
12385 730 : nc_type atttype = NC_NAT;
12386 730 : size_t attlen = 0;
12387 730 : if (nc_inq_att(nCdfId, v, "instance_dimension", &atttype,
12388 14 : &attlen) == NC_NOERR &&
12389 730 : atttype == NC_CHAR && attlen < NC_MAX_NAME)
12390 : {
12391 : char szInstanceDimension[NC_MAX_NAME + 1];
12392 14 : if (nc_get_att_text(nCdfId, v, "instance_dimension",
12393 14 : szInstanceDimension) == NC_NOERR)
12394 : {
12395 14 : szInstanceDimension[attlen] = 0;
12396 14 : int status = nc_inq_dimid(nCdfId, szInstanceDimension,
12397 : &nProfileDimId);
12398 14 : if (status == NC_NOERR)
12399 14 : nParentIndexVarID = v;
12400 : else
12401 0 : nProfileDimId = -1;
12402 14 : if (status == NC_EBADDIM)
12403 0 : CPLError(CE_Warning, CPLE_AppDefined,
12404 : "Attribute instance_dimension='%s' refers "
12405 : "to a non existing dimension",
12406 : szInstanceDimension);
12407 : else
12408 14 : NCDF_ERR(status);
12409 : }
12410 : }
12411 730 : if (v != nParentIndexVarID)
12412 : {
12413 716 : anPotentialVectorVarID.push_back(v);
12414 716 : int nDimId = -1;
12415 716 : nc_inq_vardimid(nCdfId, v, &nDimId);
12416 716 : oMapDimIdToCount[nDimId]++;
12417 : }
12418 : }
12419 : }
12420 : }
12421 :
12422 : // If we are opened in raster-only mode and that there are only 1D or 2D
12423 : // variables and that the 2D variables have no X/Y dim, and all
12424 : // variables refer to the same main dimension (or 2 dimensions for
12425 : // featureType=profile), then it is a pure vector dataset
12426 : CPLString osFeatureType(
12427 548 : CSLFetchNameValueDef(papszMetadata, "NC_GLOBAL#featureType", ""));
12428 437 : if (bKeepRasters && !bKeepVectors && bIsVectorOnly && nRasterVars > 0 &&
12429 985 : !anPotentialVectorVarID.empty() &&
12430 0 : (oMapDimIdToCount.size() == 1 ||
12431 0 : (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2 &&
12432 0 : nProfileDimId >= 0)))
12433 : {
12434 0 : anPotentialVectorVarID.resize(0);
12435 : }
12436 : else
12437 : {
12438 548 : *pnRasterVars += nRasterVars;
12439 : }
12440 :
12441 548 : if (!anPotentialVectorVarID.empty() && bKeepVectors)
12442 : {
12443 : // Take the dimension that is referenced the most times.
12444 64 : if (!(oMapDimIdToCount.size() == 1 ||
12445 27 : (EQUAL(osFeatureType, "profile") &&
12446 26 : oMapDimIdToCount.size() == 2 && nProfileDimId >= 0)))
12447 : {
12448 1 : CPLError(CE_Warning, CPLE_AppDefined,
12449 : "The dataset has several variables that could be "
12450 : "identified as vector fields, but not all share the same "
12451 : "primary dimension. Consequently they will be ignored.");
12452 : }
12453 : else
12454 : {
12455 50 : if (nVarTimeId >= 0 &&
12456 50 : oMapDimIdToCount.find(nVarTimeDimId) != oMapDimIdToCount.end())
12457 : {
12458 1 : anPotentialVectorVarID.push_back(nVarTimeId);
12459 : }
12460 49 : CreateGrpVectorLayers(nCdfId, osFeatureType, anPotentialVectorVarID,
12461 : oMapDimIdToCount, nVarXId, nVarYId, nVarZId,
12462 : nProfileDimId, nParentIndexVarID,
12463 : bKeepRasters);
12464 : }
12465 : }
12466 :
12467 : // Recurse on sub-groups.
12468 548 : int nSubGroups = 0;
12469 548 : int *panSubGroupIds = nullptr;
12470 548 : NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
12471 583 : for (int i = 0; i < nSubGroups; i++)
12472 : {
12473 35 : FilterVars(panSubGroupIds[i], bKeepRasters, bKeepVectors,
12474 : papszIgnoreVars, pnRasterVars, pnGroupId, pnVarId,
12475 : pnIgnoredVars, oMap2DDimsToGroupAndVar);
12476 : }
12477 548 : CPLFree(panSubGroupIds);
12478 :
12479 548 : return CE_None;
12480 : }
12481 :
12482 : // Create vector layers from given potentially identified vector variables
12483 : // resulting from the scanning of a NetCDF (or group) ID.
12484 49 : CPLErr netCDFDataset::CreateGrpVectorLayers(
12485 : int nCdfId, const CPLString &osFeatureType,
12486 : const std::vector<int> &anPotentialVectorVarID,
12487 : const std::map<int, int> &oMapDimIdToCount, int nVarXId, int nVarYId,
12488 : int nVarZId, int nProfileDimId, int nParentIndexVarID, bool bKeepRasters)
12489 : {
12490 49 : char *pszGroupName = nullptr;
12491 49 : NCDFGetGroupFullName(nCdfId, &pszGroupName);
12492 49 : if (pszGroupName == nullptr || pszGroupName[0] == '\0')
12493 : {
12494 47 : CPLFree(pszGroupName);
12495 47 : pszGroupName = CPLStrdup(CPLGetBasenameSafe(osFilename).c_str());
12496 : }
12497 49 : OGRwkbGeometryType eGType = wkbUnknown;
12498 : CPLString osLayerName = CSLFetchNameValueDef(
12499 98 : papszMetadata, "NC_GLOBAL#ogr_layer_name", pszGroupName);
12500 49 : CPLFree(pszGroupName);
12501 49 : papszMetadata =
12502 49 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_name", nullptr);
12503 :
12504 49 : if (EQUAL(osFeatureType, "point") || EQUAL(osFeatureType, "profile"))
12505 : {
12506 33 : papszMetadata =
12507 33 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#featureType", nullptr);
12508 33 : eGType = wkbPoint;
12509 : }
12510 :
12511 : const char *pszLayerType =
12512 49 : CSLFetchNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_type");
12513 49 : if (pszLayerType != nullptr)
12514 : {
12515 9 : eGType = OGRFromOGCGeomType(pszLayerType);
12516 9 : papszMetadata =
12517 9 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_type", nullptr);
12518 : }
12519 :
12520 : CPLString osGeometryField =
12521 98 : CSLFetchNameValueDef(papszMetadata, "NC_GLOBAL#ogr_geometry_field", "");
12522 49 : papszMetadata =
12523 49 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_geometry_field", nullptr);
12524 :
12525 49 : int nFirstVarId = -1;
12526 49 : int nVectorDim = oMapDimIdToCount.rbegin()->first;
12527 49 : if (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2)
12528 : {
12529 13 : if (nVectorDim == nProfileDimId)
12530 0 : nVectorDim = oMapDimIdToCount.begin()->first;
12531 : }
12532 : else
12533 : {
12534 36 : nProfileDimId = -1;
12535 : }
12536 62 : for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)
12537 : {
12538 62 : int anDimIds[2] = {-1, -1};
12539 62 : nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);
12540 62 : if (nVectorDim == anDimIds[0])
12541 : {
12542 49 : nFirstVarId = anPotentialVectorVarID[j];
12543 49 : break;
12544 : }
12545 : }
12546 :
12547 : // In case where coordinates are explicitly specified for one of the
12548 : // field/variable, use them in priority over the ones that might have been
12549 : // identified above.
12550 49 : char *pszCoordinates = nullptr;
12551 49 : if (NCDFGetAttr(nCdfId, nFirstVarId, "coordinates", &pszCoordinates) ==
12552 : CE_None)
12553 : {
12554 : const CPLStringList aosTokens(
12555 68 : NCDFTokenizeCoordinatesAttribute(pszCFCoordinates));
12556 34 : for (int i = 0; i < aosTokens.size(); i++)
12557 : {
12558 0 : if (NCDFIsVarLongitude(nCdfId, -1, aosTokens[i]) ||
12559 0 : NCDFIsVarProjectionX(nCdfId, -1, aosTokens[i]))
12560 : {
12561 0 : nVarXId = -1;
12562 0 : CPL_IGNORE_RET_VAL(
12563 0 : nc_inq_varid(nCdfId, aosTokens[i], &nVarXId));
12564 : }
12565 0 : else if (NCDFIsVarLatitude(nCdfId, -1, aosTokens[i]) ||
12566 0 : NCDFIsVarProjectionY(nCdfId, -1, aosTokens[i]))
12567 : {
12568 0 : nVarYId = -1;
12569 0 : CPL_IGNORE_RET_VAL(
12570 0 : nc_inq_varid(nCdfId, aosTokens[i], &nVarYId));
12571 : }
12572 0 : else if (NCDFIsVarVerticalCoord(nCdfId, -1, aosTokens[i]))
12573 : {
12574 0 : nVarZId = -1;
12575 0 : CPL_IGNORE_RET_VAL(
12576 0 : nc_inq_varid(nCdfId, aosTokens[i], &nVarZId));
12577 : }
12578 : }
12579 : }
12580 49 : CPLFree(pszCoordinates);
12581 :
12582 : // Check that the X,Y,Z vars share 1D and share the same dimension as
12583 : // attribute variables.
12584 49 : if (nVarXId >= 0 && nVarYId >= 0)
12585 : {
12586 38 : int nVarDimCount = -1;
12587 38 : int nVarDimId = -1;
12588 38 : if (nc_inq_varndims(nCdfId, nVarXId, &nVarDimCount) != NC_NOERR ||
12589 38 : nVarDimCount != 1 ||
12590 38 : nc_inq_vardimid(nCdfId, nVarXId, &nVarDimId) != NC_NOERR ||
12591 38 : nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim) ||
12592 35 : nc_inq_varndims(nCdfId, nVarYId, &nVarDimCount) != NC_NOERR ||
12593 35 : nVarDimCount != 1 ||
12594 111 : nc_inq_vardimid(nCdfId, nVarYId, &nVarDimId) != NC_NOERR ||
12595 35 : nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim))
12596 : {
12597 3 : nVarXId = nVarYId = -1;
12598 : }
12599 69 : else if (nVarZId >= 0 &&
12600 34 : (nc_inq_varndims(nCdfId, nVarZId, &nVarDimCount) != NC_NOERR ||
12601 34 : nVarDimCount != 1 ||
12602 34 : nc_inq_vardimid(nCdfId, nVarZId, &nVarDimId) != NC_NOERR ||
12603 34 : nVarDimId != nVectorDim))
12604 : {
12605 0 : nVarZId = -1;
12606 : }
12607 : }
12608 :
12609 49 : if (eGType == wkbUnknown && nVarXId >= 0 && nVarYId >= 0)
12610 : {
12611 2 : eGType = wkbPoint;
12612 : }
12613 49 : if (eGType == wkbPoint && nVarXId >= 0 && nVarYId >= 0 && nVarZId >= 0)
12614 : {
12615 34 : eGType = wkbPoint25D;
12616 : }
12617 49 : if (eGType == wkbUnknown && osGeometryField.empty())
12618 : {
12619 5 : eGType = wkbNone;
12620 : }
12621 :
12622 : // Read projection info
12623 49 : char **papszMetadataBackup = CSLDuplicate(papszMetadata);
12624 49 : ReadAttributes(nCdfId, nFirstVarId);
12625 49 : if (!this->bSGSupport)
12626 49 : SetProjectionFromVar(nCdfId, nFirstVarId, true);
12627 49 : const char *pszValue = FetchAttr(nCdfId, nFirstVarId, CF_GRD_MAPPING);
12628 49 : char *pszGridMapping = (pszValue ? CPLStrdup(pszValue) : nullptr);
12629 49 : CSLDestroy(papszMetadata);
12630 49 : papszMetadata = papszMetadataBackup;
12631 :
12632 49 : OGRSpatialReference *poSRS = nullptr;
12633 49 : if (!m_oSRS.IsEmpty())
12634 : {
12635 21 : poSRS = m_oSRS.Clone();
12636 : }
12637 : // Reset if there's a 2D raster
12638 49 : m_bHasProjection = false;
12639 49 : m_bHasGeoTransform = false;
12640 :
12641 49 : if (!bKeepRasters)
12642 : {
12643 : // Strip out uninteresting metadata.
12644 45 : papszMetadata =
12645 45 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#Conventions", nullptr);
12646 45 : papszMetadata =
12647 45 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#GDAL", nullptr);
12648 45 : papszMetadata =
12649 45 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#history", nullptr);
12650 : }
12651 :
12652 : std::shared_ptr<netCDFLayer> poLayer(
12653 49 : new netCDFLayer(this, nCdfId, osLayerName, eGType, poSRS));
12654 49 : if (poSRS != nullptr)
12655 21 : poSRS->Release();
12656 49 : poLayer->SetRecordDimID(nVectorDim);
12657 49 : if (wkbFlatten(eGType) == wkbPoint && nVarXId >= 0 && nVarYId >= 0)
12658 : {
12659 35 : poLayer->SetXYZVars(nVarXId, nVarYId, nVarZId);
12660 : }
12661 14 : else if (!osGeometryField.empty())
12662 : {
12663 9 : poLayer->SetWKTGeometryField(osGeometryField);
12664 : }
12665 49 : if (pszGridMapping != nullptr)
12666 : {
12667 21 : poLayer->SetGridMapping(pszGridMapping);
12668 21 : CPLFree(pszGridMapping);
12669 : }
12670 49 : poLayer->SetProfile(nProfileDimId, nParentIndexVarID);
12671 :
12672 574 : for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)
12673 : {
12674 525 : int anDimIds[2] = {-1, -1};
12675 525 : nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);
12676 525 : if (anDimIds[0] == nVectorDim ||
12677 24 : (nProfileDimId >= 0 && anDimIds[0] == nProfileDimId))
12678 : {
12679 : #ifdef NCDF_DEBUG
12680 : char szTemp2[NC_MAX_NAME + 1] = {};
12681 : CPL_IGNORE_RET_VAL(
12682 : nc_inq_varname(nCdfId, anPotentialVectorVarID[j], szTemp2));
12683 : CPLDebug("GDAL_netCDF", "Variable %s is a vector field", szTemp2);
12684 : #endif
12685 525 : poLayer->AddField(anPotentialVectorVarID[j]);
12686 : }
12687 : }
12688 :
12689 49 : if (poLayer->GetLayerDefn()->GetFieldCount() != 0 ||
12690 0 : poLayer->GetGeomType() != wkbNone)
12691 : {
12692 49 : papoLayers.push_back(poLayer);
12693 : }
12694 :
12695 98 : return CE_None;
12696 : }
12697 :
12698 : // Get all coordinate and boundary variables full names referenced in
12699 : // a given a NetCDF (or group) ID and its sub-groups.
12700 : // These variables are identified in other variable's
12701 : // "coordinates" and "bounds" attribute.
12702 : // Searching coordinate and boundary variables may need to explore
12703 : // parents groups (or other groups in case of reference given in form of an
12704 : // absolute path).
12705 : // See CF sections 5.2, 5.6 and 7.1
12706 549 : static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId, char ***ppapszVars)
12707 : {
12708 549 : int nVars = 0;
12709 549 : NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
12710 :
12711 3326 : for (int v = 0; v < nVars; v++)
12712 : {
12713 2777 : char *pszTemp = nullptr;
12714 5554 : CPLStringList aosTokens;
12715 2777 : if (NCDFGetAttr(nCdfId, v, "coordinates", &pszTemp) == CE_None)
12716 454 : aosTokens.Assign(NCDFTokenizeCoordinatesAttribute(pszTemp));
12717 2777 : CPLFree(pszTemp);
12718 2777 : pszTemp = nullptr;
12719 2777 : if (NCDFGetAttr(nCdfId, v, "bounds", &pszTemp) == CE_None &&
12720 2777 : pszTemp != nullptr && !EQUAL(pszTemp, ""))
12721 17 : aosTokens.AddString(pszTemp);
12722 2777 : CPLFree(pszTemp);
12723 4081 : for (int i = 0; i < aosTokens.size(); i++)
12724 : {
12725 1304 : char *pszVarFullName = nullptr;
12726 1304 : if (NCDFResolveVarFullName(nCdfId, aosTokens[i], &pszVarFullName) ==
12727 : CE_None)
12728 1278 : *ppapszVars = CSLAddString(*ppapszVars, pszVarFullName);
12729 1304 : CPLFree(pszVarFullName);
12730 : }
12731 : }
12732 :
12733 : // Recurse on sub-groups.
12734 : int nSubGroups;
12735 549 : int *panSubGroupIds = nullptr;
12736 549 : NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
12737 584 : for (int i = 0; i < nSubGroups; i++)
12738 : {
12739 35 : NCDFGetCoordAndBoundVarFullNames(panSubGroupIds[i], ppapszVars);
12740 : }
12741 549 : CPLFree(panSubGroupIds);
12742 :
12743 549 : return CE_None;
12744 : }
12745 :
12746 : // Check if give type is user defined
12747 1927 : bool NCDFIsUserDefinedType(int /*ncid*/, int type)
12748 : {
12749 1927 : return type >= NC_FIRSTUSERTYPEID;
12750 : }
12751 :
12752 593 : char **NCDFTokenizeCoordinatesAttribute(const char *pszCoordinates)
12753 : {
12754 : // CF conventions use space as the separator for variable names in the
12755 : // coordinates attribute, but some products such as
12756 : // https://sentinel.esa.int/web/sentinel/technical-guides/sentinel-3-synergy/products-algorithms/level-2-aod-algorithms-and-products/level-2-aod-products-description
12757 : // use comma.
12758 593 : return CSLTokenizeString2(pszCoordinates, ", ", 0);
12759 : }
|