Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: netCDF read/write Driver
4 : * Purpose: GDAL bindings over netCDF library.
5 : * Author: Frank Warmerdam, warmerdam@pobox.com
6 : * Even Rouault <even.rouault at spatialys.com>
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2004, Frank Warmerdam
10 : * Copyright (c) 2007-2016, Even Rouault <even.rouault at spatialys.com>
11 : * Copyright (c) 2010, Kyle Shannon <kyle at pobox dot com>
12 : * Copyright (c) 2021, CLS
13 : *
14 : * SPDX-License-Identifier: MIT
15 : ****************************************************************************/
16 :
17 : #include "cpl_port.h"
18 :
19 : #include <array>
20 : #include <cassert>
21 : #include <cctype>
22 : #include <cerrno>
23 : #include <climits>
24 : #include <cmath>
25 : #include <cstdio>
26 : #include <cstdlib>
27 : #include <cstring>
28 : #include <ctime>
29 : #include <algorithm>
30 : #include <limits>
31 : #include <map>
32 : #include <mutex>
33 : #include <set>
34 : #include <queue>
35 : #include <string>
36 : #include <tuple>
37 : #include <utility>
38 : #include <vector>
39 :
40 : // Must be included after standard includes, otherwise VS2015 fails when
41 : // including <ctime>
42 : #include "netcdfdataset.h"
43 : #include "netcdfdrivercore.h"
44 : #include "netcdfsg.h"
45 : #include "netcdfuffd.h"
46 :
47 : #include "netcdf_mem.h"
48 :
49 : #include "cpl_conv.h"
50 : #include "cpl_error.h"
51 : #include "cpl_float.h"
52 : #include "cpl_json.h"
53 : #include "cpl_minixml.h"
54 : #include "cpl_multiproc.h"
55 : #include "cpl_progress.h"
56 : #include "cpl_time.h"
57 : #include "gdal.h"
58 : #include "gdal_frmts.h"
59 : #include "gdal_priv_templates.hpp"
60 : #include "ogr_core.h"
61 : #include "ogr_srs_api.h"
62 :
63 : // netCDF 4.8 switched to expecting filenames in UTF-8 on Windows
64 : // But with netCDF 4.9 and https://github.com/Unidata/netcdf-c/pull/2277/,
65 : // this is apparently back to expecting filenames in current codepage...
66 : // Detect netCDF 4.8 with NC_ENCZARR
67 : // Detect netCDF 4.9 with NC_NOATTCREORD
68 : #if defined(NC_ENCZARR) && !defined(NC_NOATTCREORD)
69 : #define NETCDF_USES_UTF8
70 : #endif
71 :
72 : // Internal function declarations.
73 :
74 : static bool NCDFIsGDALVersionGTE(const char *pszVersion, int nTarget);
75 :
76 : static void
77 : NCDFAddGDALHistory(int fpImage, const char *pszFilename, bool bWriteGDALVersion,
78 : bool bWriteGDALHistory, const char *pszOldHist,
79 : const char *pszFunctionName,
80 : const char *pszCFVersion = GDAL_DEFAULT_NCDF_CONVENTIONS);
81 :
82 : static void NCDFAddHistory(int fpImage, const char *pszAddHist,
83 : const char *pszOldHist);
84 :
85 : static CPLErr NCDFSafeStrcat(char **ppszDest, const char *pszSrc,
86 : size_t *nDestSize);
87 :
88 : // Var / attribute helper functions.
89 : static CPLErr NCDFPutAttr(int nCdfId, int nVarId, const char *pszAttrName,
90 : const char *pszValue);
91 :
92 : // Replace this where used.
93 : static CPLErr NCDFGet1DVar(int nCdfId, int nVarId, char **pszValue);
94 : static CPLErr NCDFPut1DVar(int nCdfId, int nVarId, const char *pszValue);
95 :
96 : // Replace this where used.
97 : static char **NCDFTokenizeArray(const char *pszValue);
98 : static void CopyMetadata(GDALDataset *poSrcDS, GDALRasterBand *poSrcBand,
99 : GDALRasterBand *poDstBand, int fpImage, int CDFVarID,
100 : const char *pszMatchPrefix = nullptr);
101 :
102 : // NetCDF-4 groups helper functions.
103 : // They all work also for NetCDF-3 files which are considered as
104 : // NetCDF-4 file with only one group.
105 : static CPLErr NCDFOpenSubDataset(int nCdfId, const char *pszSubdatasetName,
106 : int *pnGroupId, int *pnVarId);
107 : static CPLErr NCDFGetVisibleDims(int nGroupId, int *pnDims, int **ppanDimIds);
108 : static CPLErr NCDFGetSubGroups(int nGroupId, int *pnSubGroups,
109 : int **ppanSubGroupIds);
110 : static CPLErr NCDFGetGroupFullName(int nGroupId, char **ppszFullName,
111 : bool bNC3Compat = true);
112 : static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId, char **ppszFullName,
113 : bool bNC3Compat = true);
114 : static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId);
115 :
116 : static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,
117 : char **ppszFullName,
118 : bool bMandatory = false);
119 : static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,
120 : const char *pszAtt, int *pnAtt,
121 : bool bMandatory = false);
122 : static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId, char ***ppapszVars);
123 :
124 : // Uncomment this for more debug output.
125 : // #define NCDF_DEBUG 1
126 :
127 : CPLMutex *hNCMutex = nullptr;
128 :
129 : // Workaround https://github.com/OSGeo/gdal/issues/6253
130 : // Having 2 netCDF handles on the same file doesn't work in a multi-threaded
131 : // way. Apparently having the same handle works better (this is OK since
132 : // we have a global mutex on the netCDF library)
133 : static std::map<std::string, int> goMapNameToNetCDFId;
134 : static std::map<int, std::pair<std::string, int>> goMapNetCDFIdToKeyAndCount;
135 :
136 742 : int GDAL_nc_open(const char *pszFilename, int nMode, int *pID)
137 : {
138 1484 : std::string osKey(pszFilename);
139 742 : osKey += "#####";
140 742 : osKey += std::to_string(nMode);
141 742 : auto oIter = goMapNameToNetCDFId.find(osKey);
142 742 : if (oIter == goMapNameToNetCDFId.end())
143 : {
144 686 : int ret = nc_open(pszFilename, nMode, pID);
145 686 : if (ret != NC_NOERR)
146 3 : return ret;
147 683 : goMapNameToNetCDFId[osKey] = *pID;
148 683 : goMapNetCDFIdToKeyAndCount[*pID] =
149 1366 : std::pair<std::string, int>(osKey, 1);
150 683 : return ret;
151 : }
152 : else
153 : {
154 56 : *pID = oIter->second;
155 56 : goMapNetCDFIdToKeyAndCount[oIter->second].second++;
156 56 : return NC_NOERR;
157 : }
158 : }
159 :
160 1048 : int GDAL_nc_close(int cdfid)
161 : {
162 1048 : int ret = NC_NOERR;
163 1048 : auto oIter = goMapNetCDFIdToKeyAndCount.find(cdfid);
164 1048 : if (oIter != goMapNetCDFIdToKeyAndCount.end())
165 : {
166 739 : if (--oIter->second.second == 0)
167 : {
168 683 : ret = nc_close(cdfid);
169 683 : goMapNameToNetCDFId.erase(oIter->second.first);
170 683 : goMapNetCDFIdToKeyAndCount.erase(oIter);
171 : }
172 : }
173 : else
174 : {
175 : // we can go here if file opened with nc_open_mem() or nc_create()
176 309 : ret = nc_close(cdfid);
177 : }
178 1048 : 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 : char **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(char **papszMD, const char *pszDomain = "") override;
286 : };
287 :
288 : /************************************************************************/
289 : /* netCDFRasterBand() */
290 : /************************************************************************/
291 :
292 482 : netCDFRasterBand::netCDFRasterBand(const netCDFRasterBand::CONSTRUCTOR_OPEN &,
293 : netCDFDataset *poNCDFDS, int nGroupId,
294 : int nZIdIn, int nZDimIn, int nLevelIn,
295 : const int *panBandZLevIn,
296 482 : const int *panBandZPosIn, int nBandIn)
297 : : nc_datatype(NC_NAT), cdfid(nGroupId), nZId(nZIdIn), nZDim(nZDimIn),
298 482 : nLevel(nLevelIn), nBandXPos(panBandZPosIn[0]),
299 482 : nBandYPos(nZDim == 1 ? -1 : panBandZPosIn[1]), panBandZPos(nullptr),
300 : panBandZLev(nullptr),
301 : bSignedData(true), // Default signed, except for Byte.
302 964 : bCheckLongitude(false)
303 : {
304 482 : poDS = poNCDFDS;
305 482 : nBand = nBandIn;
306 :
307 : // Take care of all other dimensions.
308 482 : if (nZDim > 2)
309 : {
310 176 : panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
311 176 : panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
312 :
313 478 : for (int i = 0; i < nZDim - 2; i++)
314 : {
315 302 : panBandZPos[i] = panBandZPosIn[i + 2];
316 302 : panBandZLev[i] = panBandZLevIn[i];
317 : }
318 : }
319 :
320 482 : nRasterXSize = poDS->GetRasterXSize();
321 482 : nRasterYSize = poDS->GetRasterYSize();
322 482 : nBlockXSize = poDS->GetRasterXSize();
323 482 : nBlockYSize = 1;
324 :
325 : // Get the type of the "z" variable, our target raster array.
326 482 : if (nc_inq_var(cdfid, nZId, nullptr, &nc_datatype, nullptr, nullptr,
327 482 : nullptr) != NC_NOERR)
328 : {
329 0 : CPLError(CE_Failure, CPLE_AppDefined, "Error in nc_var_inq() on 'z'.");
330 0 : return;
331 : }
332 :
333 482 : if (NCDFIsUserDefinedType(cdfid, nc_datatype))
334 : {
335 : // First enquire and check that the number of fields is 2
336 : size_t nfields, compoundsize;
337 5 : if (nc_inq_compound(cdfid, nc_datatype, nullptr, &compoundsize,
338 5 : &nfields) != NC_NOERR)
339 : {
340 0 : CPLError(CE_Failure, CPLE_AppDefined,
341 : "Error in nc_inq_compound() on 'z'.");
342 0 : return;
343 : }
344 :
345 5 : if (nfields != 2)
346 : {
347 0 : CPLError(CE_Failure, CPLE_AppDefined,
348 : "Unsupported data type encountered in nc_inq_compound() "
349 : "on 'z'.");
350 0 : return;
351 : }
352 :
353 : // Now check that that two types are the same in the struct.
354 : nc_type field_type1, field_type2;
355 : int field_dims1, field_dims2;
356 5 : if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
357 : &field_type1, &field_dims1,
358 5 : nullptr) != NC_NOERR)
359 : {
360 0 : CPLError(
361 : CE_Failure, CPLE_AppDefined,
362 : "Error in querying Field 1 in nc_inq_compound_field() on 'z'.");
363 0 : return;
364 : }
365 :
366 5 : if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
367 : &field_type2, &field_dims2,
368 5 : nullptr) != NC_NOERR)
369 : {
370 0 : CPLError(
371 : CE_Failure, CPLE_AppDefined,
372 : "Error in querying Field 2 in nc_inq_compound_field() on 'z'.");
373 0 : return;
374 : }
375 :
376 5 : if ((field_type1 != field_type2) || (field_dims1 != field_dims2) ||
377 5 : (field_dims1 != 0))
378 : {
379 0 : CPLError(CE_Failure, CPLE_AppDefined,
380 : "Error in interpreting compound data type on 'z'.");
381 0 : return;
382 : }
383 :
384 5 : if (field_type1 == NC_SHORT)
385 0 : eDataType = GDT_CInt16;
386 5 : else if (field_type1 == NC_INT)
387 0 : eDataType = GDT_CInt32;
388 5 : else if (field_type1 == NC_FLOAT)
389 4 : eDataType = GDT_CFloat32;
390 1 : else if (field_type1 == NC_DOUBLE)
391 1 : eDataType = GDT_CFloat64;
392 : else
393 : {
394 0 : CPLError(CE_Failure, CPLE_AppDefined,
395 : "Unsupported netCDF compound data type encountered.");
396 0 : return;
397 : }
398 : }
399 : else
400 : {
401 477 : if (nc_datatype == NC_BYTE)
402 146 : eDataType = GDT_Byte;
403 331 : else if (nc_datatype == NC_CHAR)
404 0 : eDataType = GDT_Byte;
405 331 : else if (nc_datatype == NC_SHORT)
406 41 : eDataType = GDT_Int16;
407 290 : else if (nc_datatype == NC_INT)
408 89 : eDataType = GDT_Int32;
409 201 : else if (nc_datatype == NC_FLOAT)
410 123 : eDataType = GDT_Float32;
411 78 : else if (nc_datatype == NC_DOUBLE)
412 40 : eDataType = GDT_Float64;
413 38 : else if (nc_datatype == NC_UBYTE)
414 16 : eDataType = GDT_Byte;
415 22 : else if (nc_datatype == NC_USHORT)
416 4 : eDataType = GDT_UInt16;
417 18 : else if (nc_datatype == NC_UINT)
418 3 : eDataType = GDT_UInt32;
419 15 : else if (nc_datatype == NC_INT64)
420 8 : eDataType = GDT_Int64;
421 7 : else if (nc_datatype == NC_UINT64)
422 7 : eDataType = GDT_UInt64;
423 : else
424 : {
425 0 : if (nBand == 1)
426 0 : CPLError(CE_Warning, CPLE_AppDefined,
427 : "Unsupported netCDF datatype (%d), treat as Float32.",
428 0 : static_cast<int>(nc_datatype));
429 0 : eDataType = GDT_Float32;
430 0 : nc_datatype = NC_FLOAT;
431 : }
432 : }
433 :
434 : // Find and set No Data for this variable.
435 482 : nc_type atttype = NC_NAT;
436 482 : size_t attlen = 0;
437 482 : const char *pszNoValueName = nullptr;
438 :
439 : // Find attribute name, either _FillValue or missing_value.
440 482 : int status = nc_inq_att(cdfid, nZId, NCDF_FillValue, &atttype, &attlen);
441 482 : if (status == NC_NOERR)
442 : {
443 248 : pszNoValueName = NCDF_FillValue;
444 : }
445 : else
446 : {
447 234 : status = nc_inq_att(cdfid, nZId, "missing_value", &atttype, &attlen);
448 234 : if (status == NC_NOERR)
449 : {
450 12 : pszNoValueName = "missing_value";
451 : }
452 : }
453 :
454 : // Fetch missing value.
455 482 : double dfNoData = 0.0;
456 482 : bool bGotNoData = false;
457 482 : int64_t nNoDataAsInt64 = 0;
458 482 : bool bGotNoDataAsInt64 = false;
459 482 : uint64_t nNoDataAsUInt64 = 0;
460 482 : bool bGotNoDataAsUInt64 = false;
461 482 : if (status == NC_NOERR)
462 : {
463 260 : nc_type nAttrType = NC_NAT;
464 260 : size_t nAttrLen = 0;
465 260 : status = nc_inq_att(cdfid, nZId, pszNoValueName, &nAttrType, &nAttrLen);
466 260 : if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_INT64)
467 : {
468 : long long v;
469 7 : nc_get_att_longlong(cdfid, nZId, pszNoValueName, &v);
470 7 : bGotNoData = true;
471 7 : bGotNoDataAsInt64 = true;
472 7 : nNoDataAsInt64 = static_cast<int64_t>(v);
473 : }
474 253 : else if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_UINT64)
475 : {
476 : unsigned long long v;
477 7 : nc_get_att_ulonglong(cdfid, nZId, pszNoValueName, &v);
478 7 : bGotNoData = true;
479 7 : bGotNoDataAsUInt64 = true;
480 7 : nNoDataAsUInt64 = static_cast<uint64_t>(v);
481 : }
482 246 : else if (NCDFGetAttr(cdfid, nZId, pszNoValueName, &dfNoData) == CE_None)
483 : {
484 245 : bGotNoData = true;
485 : }
486 : }
487 :
488 : // If NoData was not found, use the default value, but for non-Byte types
489 : // as it is not recommended:
490 : // https://www.unidata.ucar.edu/software/netcdf/docs/attribute_conventions.html
491 482 : nc_type vartype = NC_NAT;
492 482 : if (!bGotNoData)
493 : {
494 223 : nc_inq_vartype(cdfid, nZId, &vartype);
495 223 : if (vartype == NC_INT64)
496 : {
497 : nNoDataAsInt64 =
498 1 : NCDFGetDefaultNoDataValueAsInt64(cdfid, nZId, bGotNoData);
499 1 : bGotNoDataAsInt64 = bGotNoData;
500 : }
501 222 : else if (vartype == NC_UINT64)
502 : {
503 : nNoDataAsUInt64 =
504 0 : NCDFGetDefaultNoDataValueAsUInt64(cdfid, nZId, bGotNoData);
505 0 : bGotNoDataAsUInt64 = bGotNoData;
506 : }
507 222 : else if (vartype != NC_CHAR && vartype != NC_BYTE &&
508 99 : vartype != NC_UBYTE)
509 : {
510 89 : dfNoData =
511 89 : NCDFGetDefaultNoDataValue(cdfid, nZId, vartype, bGotNoData);
512 89 : if (bGotNoData)
513 : {
514 78 : CPLDebug("GDAL_netCDF",
515 : "did not get nodata value for variable #%d, using "
516 : "default %f",
517 : nZId, dfNoData);
518 : }
519 : }
520 : }
521 :
522 482 : bool bHasUnderscoreUnsignedAttr = false;
523 482 : bool bUnderscoreUnsignedAttrVal = false;
524 : {
525 482 : char *pszTemp = nullptr;
526 482 : if (NCDFGetAttr(cdfid, nZId, "_Unsigned", &pszTemp) == CE_None)
527 : {
528 138 : if (EQUAL(pszTemp, "true"))
529 : {
530 130 : bHasUnderscoreUnsignedAttr = true;
531 130 : bUnderscoreUnsignedAttrVal = true;
532 : }
533 8 : else if (EQUAL(pszTemp, "false"))
534 : {
535 8 : bHasUnderscoreUnsignedAttr = true;
536 8 : bUnderscoreUnsignedAttrVal = false;
537 : }
538 138 : CPLFree(pszTemp);
539 : }
540 : }
541 :
542 : // Look for valid_range or valid_min/valid_max.
543 :
544 : // First look for valid_range.
545 482 : if (CPLFetchBool(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE", true))
546 : {
547 480 : char *pszValidRange = nullptr;
548 480 : if (NCDFGetAttr(cdfid, nZId, "valid_range", &pszValidRange) ==
549 131 : CE_None &&
550 611 : pszValidRange[0] == '{' &&
551 131 : pszValidRange[strlen(pszValidRange) - 1] == '}')
552 : {
553 : const std::string osValidRange =
554 393 : std::string(pszValidRange).substr(1, strlen(pszValidRange) - 2);
555 : const CPLStringList aosValidRange(
556 262 : CSLTokenizeString2(osValidRange.c_str(), ",", 0));
557 131 : if (aosValidRange.size() == 2 &&
558 262 : CPLGetValueType(aosValidRange[0]) != CPL_VALUE_STRING &&
559 131 : CPLGetValueType(aosValidRange[1]) != CPL_VALUE_STRING)
560 : {
561 131 : bValidRangeValid = true;
562 131 : adfValidRange[0] = CPLAtof(aosValidRange[0]);
563 131 : adfValidRange[1] = CPLAtof(aosValidRange[1]);
564 : }
565 : }
566 480 : CPLFree(pszValidRange);
567 :
568 : // If not found look for valid_min and valid_max.
569 480 : if (!bValidRangeValid)
570 : {
571 349 : double dfMin = 0;
572 349 : double dfMax = 0;
573 364 : if (NCDFGetAttr(cdfid, nZId, "valid_min", &dfMin) == CE_None &&
574 15 : NCDFGetAttr(cdfid, nZId, "valid_max", &dfMax) == CE_None)
575 : {
576 8 : adfValidRange[0] = dfMin;
577 8 : adfValidRange[1] = dfMax;
578 8 : bValidRangeValid = true;
579 : }
580 : }
581 :
582 480 : if (bValidRangeValid &&
583 139 : (adfValidRange[0] < 0 || adfValidRange[1] < 0) &&
584 17 : nc_datatype == NC_SHORT && bHasUnderscoreUnsignedAttr &&
585 : bUnderscoreUnsignedAttrVal)
586 : {
587 2 : if (adfValidRange[0] < 0)
588 0 : adfValidRange[0] += 65536;
589 2 : if (adfValidRange[1] < 0)
590 2 : adfValidRange[1] += 65536;
591 2 : if (adfValidRange[0] <= adfValidRange[1])
592 : {
593 : // Updating metadata item
594 2 : GDALPamRasterBand::SetMetadataItem(
595 : "valid_range",
596 2 : CPLSPrintf("{%d,%d}", static_cast<int>(adfValidRange[0]),
597 2 : static_cast<int>(adfValidRange[1])));
598 : }
599 : }
600 :
601 480 : if (bValidRangeValid && adfValidRange[0] > adfValidRange[1])
602 : {
603 0 : CPLError(CE_Warning, CPLE_AppDefined,
604 : "netCDFDataset::valid_range: min > max:\n"
605 : " min: %lf\n max: %lf\n",
606 : adfValidRange[0], adfValidRange[1]);
607 0 : bValidRangeValid = false;
608 0 : adfValidRange[0] = 0.0;
609 0 : adfValidRange[1] = 0.0;
610 : }
611 : }
612 :
613 : // Special For Byte Bands: check for signed/unsigned byte.
614 482 : if (nc_datatype == NC_BYTE)
615 : {
616 : // netcdf uses signed byte by default, but GDAL uses unsigned by default
617 : // This may cause unexpected results, but is needed for back-compat.
618 146 : if (poNCDFDS->bIsGdalFile)
619 124 : bSignedData = false;
620 : else
621 22 : bSignedData = true;
622 :
623 : // For NC4 format NC_BYTE is (normally) signed, NC_UBYTE is unsigned.
624 : // But in case a NC3 file was converted automatically and has hints
625 : // that it is unsigned, take them into account
626 146 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
627 : {
628 3 : bSignedData = true;
629 : }
630 :
631 : // If we got valid_range, test for signed/unsigned range.
632 : // https://docs.unidata.ucar.edu/netcdf-c/current/attribute_conventions.html
633 146 : if (bValidRangeValid)
634 : {
635 : // If we got valid_range={0,255}, treat as unsigned.
636 127 : if (adfValidRange[0] == 0 && adfValidRange[1] == 255)
637 : {
638 119 : bSignedData = false;
639 : // Reset valid_range.
640 119 : bValidRangeValid = false;
641 : }
642 : // If we got valid_range={-128,127}, treat as signed.
643 8 : else if (adfValidRange[0] == -128 && adfValidRange[1] == 127)
644 : {
645 8 : bSignedData = true;
646 : // Reset valid_range.
647 8 : bValidRangeValid = false;
648 : }
649 : }
650 : // Else test for _Unsigned.
651 : // https://docs.unidata.ucar.edu/nug/current/best_practices.html
652 : else
653 : {
654 19 : if (bHasUnderscoreUnsignedAttr)
655 7 : bSignedData = !bUnderscoreUnsignedAttrVal;
656 : }
657 :
658 146 : if (bSignedData)
659 : {
660 20 : eDataType = GDT_Int8;
661 : }
662 126 : else if (dfNoData < 0)
663 : {
664 : // Fix nodata value as it was stored signed.
665 6 : dfNoData += 256;
666 6 : if (pszNoValueName)
667 : {
668 : // Updating metadata item
669 6 : GDALPamRasterBand::SetMetadataItem(
670 : pszNoValueName,
671 : CPLSPrintf("%d", static_cast<int>(dfNoData)));
672 : }
673 : }
674 : }
675 336 : else if (nc_datatype == NC_SHORT)
676 : {
677 41 : if (bHasUnderscoreUnsignedAttr)
678 : {
679 4 : bSignedData = !bUnderscoreUnsignedAttrVal;
680 4 : if (!bSignedData)
681 4 : eDataType = GDT_UInt16;
682 : }
683 :
684 : // Fix nodata value as it was stored signed.
685 41 : if (!bSignedData && dfNoData < 0)
686 : {
687 4 : dfNoData += 65536;
688 4 : if (pszNoValueName)
689 : {
690 : // Updating metadata item
691 4 : GDALPamRasterBand::SetMetadataItem(
692 : pszNoValueName,
693 : CPLSPrintf("%d", static_cast<int>(dfNoData)));
694 : }
695 : }
696 : }
697 :
698 295 : else if (nc_datatype == NC_UBYTE || nc_datatype == NC_USHORT ||
699 275 : nc_datatype == NC_UINT || nc_datatype == NC_UINT64)
700 : {
701 30 : bSignedData = false;
702 : }
703 :
704 482 : CPLDebug("GDAL_netCDF", "netcdf type=%d gdal type=%d signedByte=%d",
705 482 : nc_datatype, eDataType, static_cast<int>(bSignedData));
706 :
707 482 : if (bGotNoData)
708 : {
709 : // Set nodata value.
710 338 : if (bGotNoDataAsInt64)
711 : {
712 8 : if (eDataType == GDT_Int64)
713 : {
714 8 : SetNoDataValueNoUpdate(nNoDataAsInt64);
715 : }
716 0 : else if (eDataType == GDT_UInt64 && nNoDataAsInt64 >= 0)
717 : {
718 0 : SetNoDataValueNoUpdate(static_cast<uint64_t>(nNoDataAsInt64));
719 : }
720 : else
721 : {
722 0 : SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsInt64));
723 : }
724 : }
725 330 : else if (bGotNoDataAsUInt64)
726 : {
727 7 : if (eDataType == GDT_UInt64)
728 : {
729 7 : SetNoDataValueNoUpdate(nNoDataAsUInt64);
730 : }
731 0 : else if (eDataType == GDT_Int64 &&
732 : nNoDataAsUInt64 <=
733 0 : static_cast<uint64_t>(
734 0 : std::numeric_limits<int64_t>::max()))
735 : {
736 0 : SetNoDataValueNoUpdate(static_cast<int64_t>(nNoDataAsUInt64));
737 : }
738 : else
739 : {
740 0 : SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsUInt64));
741 : }
742 : }
743 : else
744 : {
745 : #ifdef NCDF_DEBUG
746 : CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) read", dfNoData);
747 : #endif
748 323 : if (eDataType == GDT_Int64 && GDALIsValueExactAs<int64_t>(dfNoData))
749 : {
750 0 : SetNoDataValueNoUpdate(static_cast<int64_t>(dfNoData));
751 : }
752 323 : else if (eDataType == GDT_UInt64 &&
753 0 : GDALIsValueExactAs<uint64_t>(dfNoData))
754 : {
755 0 : SetNoDataValueNoUpdate(static_cast<uint64_t>(dfNoData));
756 : }
757 : else
758 : {
759 323 : SetNoDataValueNoUpdate(dfNoData);
760 : }
761 : }
762 : }
763 :
764 482 : CreateMetadataFromAttributes();
765 :
766 : // Attempt to fetch the scale_factor and add_offset attributes for the
767 : // variable and set them. If these values are not available, set
768 : // offset to 0 and scale to 1.
769 482 : if (nc_inq_attid(cdfid, nZId, CF_ADD_OFFSET, nullptr) == NC_NOERR)
770 : {
771 16 : double dfOffset = 0;
772 16 : status = nc_get_att_double(cdfid, nZId, CF_ADD_OFFSET, &dfOffset);
773 16 : CPLDebug("GDAL_netCDF", "got add_offset=%.16g, status=%d", dfOffset,
774 : status);
775 16 : SetOffsetNoUpdate(dfOffset);
776 : }
777 :
778 482 : bool bHasScale = false;
779 482 : if (nc_inq_attid(cdfid, nZId, CF_SCALE_FACTOR, nullptr) == NC_NOERR)
780 : {
781 20 : bHasScale = true;
782 20 : double dfScale = 1;
783 20 : status = nc_get_att_double(cdfid, nZId, CF_SCALE_FACTOR, &dfScale);
784 20 : CPLDebug("GDAL_netCDF", "got scale_factor=%.16g, status=%d", dfScale,
785 : status);
786 20 : SetScaleNoUpdate(dfScale);
787 : }
788 :
789 12 : if (bValidRangeValid && GDALDataTypeIsInteger(eDataType) &&
790 4 : eDataType != GDT_Int64 && eDataType != GDT_UInt64 &&
791 4 : (std::fabs(std::round(adfValidRange[0]) - adfValidRange[0]) > 1e-5 ||
792 494 : std::fabs(std::round(adfValidRange[1]) - adfValidRange[1]) > 1e-5) &&
793 1 : CSLFetchNameValue(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE") ==
794 : nullptr)
795 : {
796 1 : CPLError(CE_Warning, CPLE_AppDefined,
797 : "validity range = %f, %f contains floating-point values, "
798 : "whereas data type is integer. valid_range is thus likely "
799 : "wrong%s. Ignoring it.",
800 : adfValidRange[0], adfValidRange[1],
801 : bHasScale ? " (likely scaled using scale_factor/add_factor "
802 : "whereas it should be using the packed data type)"
803 : : "");
804 1 : bValidRangeValid = false;
805 1 : adfValidRange[0] = 0.0;
806 1 : adfValidRange[1] = 0.0;
807 : }
808 :
809 : // Should we check for longitude values > 360?
810 482 : bCheckLongitude =
811 964 : CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")) &&
812 482 : NCDFIsVarLongitude(cdfid, nZId, nullptr);
813 :
814 : // Attempt to fetch the units attribute for the variable and set it.
815 482 : SetUnitTypeNoUpdate(netCDFRasterBand::GetMetadataItem(CF_UNITS));
816 :
817 482 : SetBlockSize();
818 : }
819 :
820 664 : void netCDFRasterBand::SetBlockSize()
821 : {
822 : // Check for variable chunking (netcdf-4 only).
823 : // GDAL block size should be set to hdf5 chunk size.
824 664 : int nTmpFormat = 0;
825 664 : int status = nc_inq_format(cdfid, &nTmpFormat);
826 664 : NetCDFFormatEnum eTmpFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
827 664 : if ((status == NC_NOERR) &&
828 566 : (eTmpFormat == NCDF_FORMAT_NC4 || eTmpFormat == NCDF_FORMAT_NC4C))
829 : {
830 114 : size_t chunksize[MAX_NC_DIMS] = {};
831 : // Check for chunksize and set it as the blocksize (optimizes read).
832 114 : status = nc_inq_var_chunking(cdfid, nZId, &nTmpFormat, chunksize);
833 114 : if ((status == NC_NOERR) && (nTmpFormat == NC_CHUNKED))
834 : {
835 14 : nBlockXSize = (int)chunksize[nZDim - 1];
836 14 : if (nZDim >= 2)
837 14 : nBlockYSize = (int)chunksize[nZDim - 2];
838 : else
839 0 : nBlockYSize = 1;
840 : }
841 : }
842 :
843 : // Deal with bottom-up datasets and nBlockYSize != 1.
844 664 : auto poGDS = static_cast<netCDFDataset *>(poDS);
845 664 : if (poGDS->bBottomUp && nBlockYSize != 1 && poGDS->poChunkCache == nullptr)
846 : {
847 6 : if (poGDS->eAccess == GA_ReadOnly)
848 : {
849 : // Try to cache 1 or 2 'rows' of netCDF chunks along the whole
850 : // width of the raster
851 6 : size_t nChunks =
852 6 : static_cast<size_t>(DIV_ROUND_UP(nRasterXSize, nBlockXSize));
853 6 : if ((nRasterYSize % nBlockYSize) != 0)
854 2 : nChunks *= 2;
855 : const size_t nChunkSize =
856 6 : static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
857 6 : nBlockXSize * nBlockYSize;
858 6 : constexpr size_t MAX_CACHE_SIZE = 100 * 1024 * 1024;
859 6 : nChunks = std::min(nChunks, MAX_CACHE_SIZE / nChunkSize);
860 6 : if (nChunks)
861 : {
862 6 : poGDS->poChunkCache.reset(
863 6 : new netCDFDataset::ChunkCacheType(nChunks));
864 : }
865 : }
866 : else
867 : {
868 0 : nBlockYSize = 1;
869 : }
870 : }
871 664 : }
872 :
873 : // Constructor in create mode.
874 : // If nZId and following variables are not passed, the band will have 2
875 : // dimensions.
876 : // TODO: Get metadata, missing val from band #1 if nZDim > 2.
877 182 : netCDFRasterBand::netCDFRasterBand(
878 : const netCDFRasterBand::CONSTRUCTOR_CREATE &, netCDFDataset *poNCDFDS,
879 : const GDALDataType eTypeIn, int nBandIn, bool bSigned,
880 : const char *pszBandName, const char *pszLongName, int nZIdIn, int nZDimIn,
881 : int nLevelIn, const int *panBandZLevIn, const int *panBandZPosIn,
882 182 : const int *paDimIds)
883 182 : : nc_datatype(NC_NAT), cdfid(poNCDFDS->GetCDFID()), nZId(nZIdIn),
884 : nZDim(nZDimIn), nLevel(nLevelIn), nBandXPos(1), nBandYPos(0),
885 : panBandZPos(nullptr), panBandZLev(nullptr), bSignedData(bSigned),
886 182 : bCheckLongitude(false), m_bCreateMetadataFromOtherVarsDone(true)
887 : {
888 182 : poDS = poNCDFDS;
889 182 : nBand = nBandIn;
890 :
891 182 : nRasterXSize = poDS->GetRasterXSize();
892 182 : nRasterYSize = poDS->GetRasterYSize();
893 182 : nBlockXSize = poDS->GetRasterXSize();
894 182 : nBlockYSize = 1;
895 :
896 182 : if (poDS->GetAccess() != GA_Update)
897 : {
898 0 : CPLError(CE_Failure, CPLE_NotSupported,
899 : "Dataset is not in update mode, "
900 : "wrong netCDFRasterBand constructor");
901 0 : return;
902 : }
903 :
904 : // Take care of all other dimensions.
905 182 : if (nZDim > 2 && paDimIds != nullptr)
906 : {
907 27 : nBandXPos = panBandZPosIn[0];
908 27 : nBandYPos = panBandZPosIn[1];
909 :
910 27 : panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
911 27 : panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
912 :
913 76 : for (int i = 0; i < nZDim - 2; i++)
914 : {
915 49 : panBandZPos[i] = panBandZPosIn[i + 2];
916 49 : panBandZLev[i] = panBandZLevIn[i];
917 : }
918 : }
919 :
920 : // Get the type of the "z" variable, our target raster array.
921 182 : eDataType = eTypeIn;
922 :
923 182 : switch (eDataType)
924 : {
925 77 : case GDT_Byte:
926 77 : nc_datatype = NC_BYTE;
927 : // NC_UBYTE (unsigned byte) is only available for NC4.
928 77 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
929 3 : nc_datatype = NC_UBYTE;
930 77 : break;
931 7 : case GDT_Int8:
932 7 : nc_datatype = NC_BYTE;
933 7 : break;
934 11 : case GDT_Int16:
935 11 : nc_datatype = NC_SHORT;
936 11 : break;
937 24 : case GDT_Int32:
938 24 : nc_datatype = NC_INT;
939 24 : break;
940 13 : case GDT_Float32:
941 13 : nc_datatype = NC_FLOAT;
942 13 : break;
943 8 : case GDT_Float64:
944 8 : nc_datatype = NC_DOUBLE;
945 8 : break;
946 7 : case GDT_Int64:
947 7 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
948 : {
949 7 : nc_datatype = NC_INT64;
950 : }
951 : else
952 : {
953 0 : if (nBand == 1)
954 0 : CPLError(
955 : CE_Warning, CPLE_AppDefined,
956 : "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
957 : "Int64");
958 0 : nc_datatype = NC_DOUBLE;
959 0 : eDataType = GDT_Float64;
960 : }
961 7 : break;
962 7 : case GDT_UInt64:
963 7 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
964 : {
965 7 : nc_datatype = NC_UINT64;
966 : }
967 : else
968 : {
969 0 : if (nBand == 1)
970 0 : CPLError(
971 : CE_Warning, CPLE_AppDefined,
972 : "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
973 : "UInt64");
974 0 : nc_datatype = NC_DOUBLE;
975 0 : eDataType = GDT_Float64;
976 : }
977 7 : break;
978 6 : case GDT_UInt16:
979 6 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
980 : {
981 6 : nc_datatype = NC_USHORT;
982 6 : break;
983 : }
984 : [[fallthrough]];
985 : case GDT_UInt32:
986 6 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
987 : {
988 6 : nc_datatype = NC_UINT;
989 6 : break;
990 : }
991 : [[fallthrough]];
992 : default:
993 16 : if (nBand == 1)
994 8 : CPLError(CE_Warning, CPLE_AppDefined,
995 : "Unsupported GDAL datatype (%d), treat as NC_FLOAT.",
996 8 : static_cast<int>(eDataType));
997 16 : nc_datatype = NC_FLOAT;
998 16 : eDataType = GDT_Float32;
999 16 : break;
1000 : }
1001 :
1002 : // Define the variable if necessary (if nZId == -1).
1003 182 : bool bDefineVar = false;
1004 :
1005 182 : if (nZId == -1)
1006 : {
1007 160 : bDefineVar = true;
1008 :
1009 : // Make sure we are in define mode.
1010 160 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1011 :
1012 : char szTempPrivate[256 + 1];
1013 160 : const char *pszTemp = nullptr;
1014 160 : if (!pszBandName || EQUAL(pszBandName, ""))
1015 : {
1016 138 : snprintf(szTempPrivate, sizeof(szTempPrivate), "Band%d", nBand);
1017 138 : pszTemp = szTempPrivate;
1018 : }
1019 : else
1020 : {
1021 22 : pszTemp = pszBandName;
1022 : }
1023 :
1024 : int status;
1025 160 : if (nZDim > 2 && paDimIds != nullptr)
1026 : {
1027 5 : status =
1028 5 : nc_def_var(cdfid, pszTemp, nc_datatype, nZDim, paDimIds, &nZId);
1029 : }
1030 : else
1031 : {
1032 155 : int anBandDims[2] = {poNCDFDS->nYDimID, poNCDFDS->nXDimID};
1033 : status =
1034 155 : nc_def_var(cdfid, pszTemp, nc_datatype, 2, anBandDims, &nZId);
1035 : }
1036 160 : NCDF_ERR(status);
1037 160 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d) id=%d", cdfid, pszTemp,
1038 : nc_datatype, nZId);
1039 :
1040 160 : if (!pszLongName || EQUAL(pszLongName, ""))
1041 : {
1042 153 : snprintf(szTempPrivate, sizeof(szTempPrivate),
1043 : "GDAL Band Number %d", nBand);
1044 153 : pszTemp = szTempPrivate;
1045 : }
1046 : else
1047 : {
1048 7 : pszTemp = pszLongName;
1049 : }
1050 : status =
1051 160 : nc_put_att_text(cdfid, nZId, CF_LNG_NAME, strlen(pszTemp), pszTemp);
1052 160 : NCDF_ERR(status);
1053 :
1054 160 : poNCDFDS->DefVarDeflate(nZId, true);
1055 : }
1056 :
1057 : // For Byte data add signed/unsigned info.
1058 182 : if (eDataType == GDT_Byte || eDataType == GDT_Int8)
1059 : {
1060 84 : if (bDefineVar)
1061 : {
1062 : // Only add attributes if creating variable.
1063 : // For unsigned NC_BYTE (except NC4 format),
1064 : // add valid_range and _Unsigned ( defined in CF-1 and NUG ).
1065 76 : if (nc_datatype == NC_BYTE && poNCDFDS->eFormat != NCDF_FORMAT_NC4)
1066 : {
1067 73 : CPLDebug("GDAL_netCDF",
1068 : "adding valid_range attributes for Byte Band");
1069 73 : short l_adfValidRange[2] = {0, 0};
1070 : int status;
1071 73 : if (bSignedData || eDataType == GDT_Int8)
1072 : {
1073 7 : l_adfValidRange[0] = -128;
1074 7 : l_adfValidRange[1] = 127;
1075 7 : status =
1076 7 : nc_put_att_text(cdfid, nZId, "_Unsigned", 5, "false");
1077 : }
1078 : else
1079 : {
1080 66 : l_adfValidRange[0] = 0;
1081 66 : l_adfValidRange[1] = 255;
1082 : status =
1083 66 : nc_put_att_text(cdfid, nZId, "_Unsigned", 4, "true");
1084 : }
1085 73 : NCDF_ERR(status);
1086 73 : status = nc_put_att_short(cdfid, nZId, "valid_range", NC_SHORT,
1087 : 2, l_adfValidRange);
1088 73 : NCDF_ERR(status);
1089 : }
1090 : }
1091 : }
1092 :
1093 182 : if (nc_datatype != NC_BYTE && nc_datatype != NC_CHAR &&
1094 101 : nc_datatype != NC_UBYTE)
1095 : {
1096 : // Set default nodata.
1097 98 : bool bIgnored = false;
1098 : double dfNoData =
1099 98 : NCDFGetDefaultNoDataValue(cdfid, nZId, nc_datatype, bIgnored);
1100 : #ifdef NCDF_DEBUG
1101 : CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) default", dfNoData);
1102 : #endif
1103 98 : netCDFRasterBand::SetNoDataValue(dfNoData);
1104 : }
1105 :
1106 182 : SetBlockSize();
1107 : }
1108 :
1109 : /************************************************************************/
1110 : /* ~netCDFRasterBand() */
1111 : /************************************************************************/
1112 :
1113 1328 : netCDFRasterBand::~netCDFRasterBand()
1114 : {
1115 664 : netCDFRasterBand::FlushCache(true);
1116 664 : CPLFree(panBandZPos);
1117 664 : CPLFree(panBandZLev);
1118 1328 : }
1119 :
1120 : /************************************************************************/
1121 : /* GetMetadata() */
1122 : /************************************************************************/
1123 :
1124 50 : char **netCDFRasterBand::GetMetadata(const char *pszDomain)
1125 : {
1126 50 : if (!m_bCreateMetadataFromOtherVarsDone)
1127 48 : CreateMetadataFromOtherVars();
1128 50 : return GDALPamRasterBand::GetMetadata(pszDomain);
1129 : }
1130 :
1131 : /************************************************************************/
1132 : /* GetMetadataItem() */
1133 : /************************************************************************/
1134 :
1135 556 : const char *netCDFRasterBand::GetMetadataItem(const char *pszName,
1136 : const char *pszDomain)
1137 : {
1138 556 : if (!m_bCreateMetadataFromOtherVarsDone &&
1139 540 : STARTS_WITH(pszName, "NETCDF_DIM_") &&
1140 1 : (!pszDomain || pszDomain[0] == 0))
1141 1 : CreateMetadataFromOtherVars();
1142 556 : return GDALPamRasterBand::GetMetadataItem(pszName, pszDomain);
1143 : }
1144 :
1145 : /************************************************************************/
1146 : /* SetMetadataItem() */
1147 : /************************************************************************/
1148 :
1149 7 : CPLErr netCDFRasterBand::SetMetadataItem(const char *pszName,
1150 : const char *pszValue,
1151 : const char *pszDomain)
1152 : {
1153 9 : if (GetAccess() == GA_Update &&
1154 9 : (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)
1155 : {
1156 : // Same logic as in CopyMetadata()
1157 :
1158 2 : const char *const papszIgnoreBand[] = {
1159 : CF_ADD_OFFSET, CF_SCALE_FACTOR, "valid_range", "_Unsigned",
1160 : NCDF_FillValue, "coordinates", nullptr};
1161 : // Do not copy varname, stats, NETCDF_DIM_*, nodata
1162 : // and items in papszIgnoreBand.
1163 6 : if (STARTS_WITH(pszName, "NETCDF_VARNAME") ||
1164 2 : STARTS_WITH(pszName, "STATISTICS_") ||
1165 2 : STARTS_WITH(pszName, "NETCDF_DIM_") ||
1166 2 : STARTS_WITH(pszName, "missing_value") ||
1167 6 : STARTS_WITH(pszName, "_FillValue") ||
1168 2 : CSLFindString(papszIgnoreBand, pszName) != -1)
1169 : {
1170 : // do nothing
1171 : }
1172 : else
1173 : {
1174 2 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1175 :
1176 2 : if (!NCDFPutAttr(cdfid, nZId, pszName, pszValue))
1177 2 : return CE_Failure;
1178 : }
1179 : }
1180 :
1181 5 : return GDALPamRasterBand::SetMetadataItem(pszName, pszValue, pszDomain);
1182 : }
1183 :
1184 : /************************************************************************/
1185 : /* SetMetadata() */
1186 : /************************************************************************/
1187 :
1188 2 : CPLErr netCDFRasterBand::SetMetadata(char **papszMD, const char *pszDomain)
1189 : {
1190 4 : if (GetAccess() == GA_Update &&
1191 2 : (pszDomain == nullptr || pszDomain[0] == '\0'))
1192 : {
1193 : // We don't handle metadata item removal for now
1194 4 : for (const char *const *papszIter = papszMD; papszIter && *papszIter;
1195 : ++papszIter)
1196 : {
1197 2 : char *pszName = nullptr;
1198 2 : const char *pszValue = CPLParseNameValue(*papszIter, &pszName);
1199 2 : if (pszName && pszValue)
1200 2 : SetMetadataItem(pszName, pszValue);
1201 2 : CPLFree(pszName);
1202 : }
1203 : }
1204 2 : return GDALPamRasterBand::SetMetadata(papszMD, pszDomain);
1205 : }
1206 :
1207 : /************************************************************************/
1208 : /* GetOffset() */
1209 : /************************************************************************/
1210 50 : double netCDFRasterBand::GetOffset(int *pbSuccess)
1211 : {
1212 50 : if (pbSuccess != nullptr)
1213 45 : *pbSuccess = static_cast<int>(m_bHaveOffset);
1214 :
1215 50 : return m_dfOffset;
1216 : }
1217 :
1218 : /************************************************************************/
1219 : /* SetOffset() */
1220 : /************************************************************************/
1221 1 : CPLErr netCDFRasterBand::SetOffset(double dfNewOffset)
1222 : {
1223 2 : CPLMutexHolderD(&hNCMutex);
1224 :
1225 : // Write value if in update mode.
1226 1 : if (poDS->GetAccess() == GA_Update)
1227 : {
1228 : // Make sure we are in define mode.
1229 1 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1230 :
1231 1 : const int status = nc_put_att_double(cdfid, nZId, CF_ADD_OFFSET,
1232 : NC_DOUBLE, 1, &dfNewOffset);
1233 :
1234 1 : NCDF_ERR(status);
1235 1 : if (status == NC_NOERR)
1236 : {
1237 1 : SetOffsetNoUpdate(dfNewOffset);
1238 1 : return CE_None;
1239 : }
1240 :
1241 0 : return CE_Failure;
1242 : }
1243 :
1244 0 : SetOffsetNoUpdate(dfNewOffset);
1245 0 : return CE_None;
1246 : }
1247 :
1248 : /************************************************************************/
1249 : /* SetOffsetNoUpdate() */
1250 : /************************************************************************/
1251 17 : void netCDFRasterBand::SetOffsetNoUpdate(double dfVal)
1252 : {
1253 17 : m_dfOffset = dfVal;
1254 17 : m_bHaveOffset = true;
1255 17 : }
1256 :
1257 : /************************************************************************/
1258 : /* GetScale() */
1259 : /************************************************************************/
1260 50 : double netCDFRasterBand::GetScale(int *pbSuccess)
1261 : {
1262 50 : if (pbSuccess != nullptr)
1263 45 : *pbSuccess = static_cast<int>(m_bHaveScale);
1264 :
1265 50 : return m_dfScale;
1266 : }
1267 :
1268 : /************************************************************************/
1269 : /* SetScale() */
1270 : /************************************************************************/
1271 1 : CPLErr netCDFRasterBand::SetScale(double dfNewScale)
1272 : {
1273 2 : CPLMutexHolderD(&hNCMutex);
1274 :
1275 : // Write value if in update mode.
1276 1 : if (poDS->GetAccess() == GA_Update)
1277 : {
1278 : // Make sure we are in define mode.
1279 1 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1280 :
1281 1 : const int status = nc_put_att_double(cdfid, nZId, CF_SCALE_FACTOR,
1282 : NC_DOUBLE, 1, &dfNewScale);
1283 :
1284 1 : NCDF_ERR(status);
1285 1 : if (status == NC_NOERR)
1286 : {
1287 1 : SetScaleNoUpdate(dfNewScale);
1288 1 : return CE_None;
1289 : }
1290 :
1291 0 : return CE_Failure;
1292 : }
1293 :
1294 0 : SetScaleNoUpdate(dfNewScale);
1295 0 : return CE_None;
1296 : }
1297 :
1298 : /************************************************************************/
1299 : /* SetScaleNoUpdate() */
1300 : /************************************************************************/
1301 21 : void netCDFRasterBand::SetScaleNoUpdate(double dfVal)
1302 : {
1303 21 : m_dfScale = dfVal;
1304 21 : m_bHaveScale = true;
1305 21 : }
1306 :
1307 : /************************************************************************/
1308 : /* GetUnitType() */
1309 : /************************************************************************/
1310 :
1311 22 : const char *netCDFRasterBand::GetUnitType()
1312 :
1313 : {
1314 22 : if (!m_osUnitType.empty())
1315 6 : return m_osUnitType;
1316 :
1317 16 : return GDALRasterBand::GetUnitType();
1318 : }
1319 :
1320 : /************************************************************************/
1321 : /* SetUnitType() */
1322 : /************************************************************************/
1323 :
1324 1 : CPLErr netCDFRasterBand::SetUnitType(const char *pszNewValue)
1325 :
1326 : {
1327 2 : CPLMutexHolderD(&hNCMutex);
1328 :
1329 2 : const std::string osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
1330 :
1331 1 : if (!osUnitType.empty())
1332 : {
1333 : // Write value if in update mode.
1334 1 : if (poDS->GetAccess() == GA_Update)
1335 : {
1336 : // Make sure we are in define mode.
1337 1 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(TRUE);
1338 :
1339 1 : const int status = nc_put_att_text(
1340 : cdfid, nZId, CF_UNITS, osUnitType.size(), osUnitType.c_str());
1341 :
1342 1 : NCDF_ERR(status);
1343 1 : if (status == NC_NOERR)
1344 : {
1345 1 : SetUnitTypeNoUpdate(pszNewValue);
1346 1 : return CE_None;
1347 : }
1348 :
1349 0 : return CE_Failure;
1350 : }
1351 : }
1352 :
1353 0 : SetUnitTypeNoUpdate(pszNewValue);
1354 :
1355 0 : return CE_None;
1356 : }
1357 :
1358 : /************************************************************************/
1359 : /* SetUnitTypeNoUpdate() */
1360 : /************************************************************************/
1361 :
1362 483 : void netCDFRasterBand::SetUnitTypeNoUpdate(const char *pszNewValue)
1363 : {
1364 483 : m_osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
1365 483 : }
1366 :
1367 : /************************************************************************/
1368 : /* GetNoDataValue() */
1369 : /************************************************************************/
1370 :
1371 164 : double netCDFRasterBand::GetNoDataValue(int *pbSuccess)
1372 :
1373 : {
1374 164 : if (m_bNoDataSetAsInt64)
1375 : {
1376 0 : if (pbSuccess)
1377 0 : *pbSuccess = TRUE;
1378 0 : return GDALGetNoDataValueCastToDouble(m_nNodataValueInt64);
1379 : }
1380 :
1381 164 : if (m_bNoDataSetAsUInt64)
1382 : {
1383 0 : if (pbSuccess)
1384 0 : *pbSuccess = TRUE;
1385 0 : return GDALGetNoDataValueCastToDouble(m_nNodataValueUInt64);
1386 : }
1387 :
1388 164 : if (m_bNoDataSet)
1389 : {
1390 127 : if (pbSuccess)
1391 110 : *pbSuccess = TRUE;
1392 127 : return m_dfNoDataValue;
1393 : }
1394 :
1395 37 : return GDALPamRasterBand::GetNoDataValue(pbSuccess);
1396 : }
1397 :
1398 : /************************************************************************/
1399 : /* GetNoDataValueAsInt64() */
1400 : /************************************************************************/
1401 :
1402 4 : int64_t netCDFRasterBand::GetNoDataValueAsInt64(int *pbSuccess)
1403 :
1404 : {
1405 4 : if (m_bNoDataSetAsInt64)
1406 : {
1407 4 : if (pbSuccess)
1408 4 : *pbSuccess = TRUE;
1409 :
1410 4 : return m_nNodataValueInt64;
1411 : }
1412 :
1413 0 : return GDALPamRasterBand::GetNoDataValueAsInt64(pbSuccess);
1414 : }
1415 :
1416 : /************************************************************************/
1417 : /* GetNoDataValueAsUInt64() */
1418 : /************************************************************************/
1419 :
1420 4 : uint64_t netCDFRasterBand::GetNoDataValueAsUInt64(int *pbSuccess)
1421 :
1422 : {
1423 4 : if (m_bNoDataSetAsUInt64)
1424 : {
1425 4 : if (pbSuccess)
1426 4 : *pbSuccess = TRUE;
1427 :
1428 4 : return m_nNodataValueUInt64;
1429 : }
1430 :
1431 0 : return GDALPamRasterBand::GetNoDataValueAsUInt64(pbSuccess);
1432 : }
1433 :
1434 : /************************************************************************/
1435 : /* SetNoDataValue() */
1436 : /************************************************************************/
1437 :
1438 134 : CPLErr netCDFRasterBand::SetNoDataValue(double dfNoData)
1439 :
1440 : {
1441 268 : CPLMutexHolderD(&hNCMutex);
1442 :
1443 : // If already set to new value, don't do anything.
1444 134 : if (m_bNoDataSet && CPLIsEqual(dfNoData, m_dfNoDataValue))
1445 19 : return CE_None;
1446 :
1447 : // Write value if in update mode.
1448 115 : if (poDS->GetAccess() == GA_Update)
1449 : {
1450 : // netcdf-4 does not allow to set _FillValue after leaving define mode,
1451 : // but it is ok if variable has not been written to, so only print
1452 : // debug. See bug #4484.
1453 125 : if (m_bNoDataSet &&
1454 10 : !cpl::down_cast<netCDFDataset *>(poDS)->GetDefineMode())
1455 : {
1456 0 : CPLDebug("GDAL_netCDF",
1457 : "Setting NoDataValue to %.17g (previously set to %.17g) "
1458 : "but file is no longer in define mode (id #%d, band #%d)",
1459 : dfNoData, m_dfNoDataValue, cdfid, nBand);
1460 : }
1461 : #ifdef NCDF_DEBUG
1462 : else
1463 : {
1464 : CPLDebug("GDAL_netCDF",
1465 : "Setting NoDataValue to %.17g (id #%d, band #%d)",
1466 : dfNoData, cdfid, nBand);
1467 : }
1468 : #endif
1469 : // Make sure we are in define mode.
1470 115 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1471 :
1472 : int status;
1473 115 : if (eDataType == GDT_Byte)
1474 : {
1475 6 : if (bSignedData)
1476 : {
1477 0 : signed char cNoDataValue = static_cast<signed char>(dfNoData);
1478 0 : status = nc_put_att_schar(cdfid, nZId, NCDF_FillValue,
1479 : nc_datatype, 1, &cNoDataValue);
1480 : }
1481 : else
1482 : {
1483 6 : const unsigned char ucNoDataValue =
1484 6 : static_cast<unsigned char>(dfNoData);
1485 6 : status = nc_put_att_uchar(cdfid, nZId, NCDF_FillValue,
1486 : nc_datatype, 1, &ucNoDataValue);
1487 : }
1488 : }
1489 109 : else if (eDataType == GDT_Int16)
1490 : {
1491 14 : short nsNoDataValue = static_cast<short>(dfNoData);
1492 14 : status = nc_put_att_short(cdfid, nZId, NCDF_FillValue, nc_datatype,
1493 : 1, &nsNoDataValue);
1494 : }
1495 95 : else if (eDataType == GDT_Int32)
1496 : {
1497 27 : int nNoDataValue = static_cast<int>(dfNoData);
1498 27 : status = nc_put_att_int(cdfid, nZId, NCDF_FillValue, nc_datatype, 1,
1499 : &nNoDataValue);
1500 : }
1501 68 : else if (eDataType == GDT_Float32)
1502 : {
1503 31 : float fNoDataValue = static_cast<float>(dfNoData);
1504 31 : status = nc_put_att_float(cdfid, nZId, NCDF_FillValue, nc_datatype,
1505 : 1, &fNoDataValue);
1506 : }
1507 43 : else if (eDataType == GDT_UInt16 &&
1508 6 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat ==
1509 : NCDF_FORMAT_NC4)
1510 : {
1511 6 : unsigned short usNoDataValue =
1512 6 : static_cast<unsigned short>(dfNoData);
1513 6 : status = nc_put_att_ushort(cdfid, nZId, NCDF_FillValue, nc_datatype,
1514 : 1, &usNoDataValue);
1515 : }
1516 38 : else if (eDataType == GDT_UInt32 &&
1517 7 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat ==
1518 : NCDF_FORMAT_NC4)
1519 : {
1520 7 : unsigned int unNoDataValue = static_cast<unsigned int>(dfNoData);
1521 7 : status = nc_put_att_uint(cdfid, nZId, NCDF_FillValue, nc_datatype,
1522 : 1, &unNoDataValue);
1523 : }
1524 : else
1525 : {
1526 24 : status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
1527 : 1, &dfNoData);
1528 : }
1529 :
1530 115 : NCDF_ERR(status);
1531 :
1532 : // Update status if write worked.
1533 115 : if (status == NC_NOERR)
1534 : {
1535 115 : SetNoDataValueNoUpdate(dfNoData);
1536 115 : return CE_None;
1537 : }
1538 :
1539 0 : return CE_Failure;
1540 : }
1541 :
1542 0 : SetNoDataValueNoUpdate(dfNoData);
1543 0 : return CE_None;
1544 : }
1545 :
1546 : /************************************************************************/
1547 : /* SetNoDataValueNoUpdate() */
1548 : /************************************************************************/
1549 :
1550 438 : void netCDFRasterBand::SetNoDataValueNoUpdate(double dfNoData)
1551 : {
1552 438 : m_dfNoDataValue = dfNoData;
1553 438 : m_bNoDataSet = true;
1554 438 : m_bNoDataSetAsInt64 = false;
1555 438 : m_bNoDataSetAsUInt64 = false;
1556 438 : }
1557 :
1558 : /************************************************************************/
1559 : /* SetNoDataValueAsInt64() */
1560 : /************************************************************************/
1561 :
1562 3 : CPLErr netCDFRasterBand::SetNoDataValueAsInt64(int64_t nNoData)
1563 :
1564 : {
1565 6 : CPLMutexHolderD(&hNCMutex);
1566 :
1567 : // If already set to new value, don't do anything.
1568 3 : if (m_bNoDataSetAsInt64 && nNoData == m_nNodataValueInt64)
1569 0 : return CE_None;
1570 :
1571 : // Write value if in update mode.
1572 3 : if (poDS->GetAccess() == GA_Update)
1573 : {
1574 : // netcdf-4 does not allow to set NCDF_FillValue after leaving define mode,
1575 : // but it is ok if variable has not been written to, so only print
1576 : // debug. See bug #4484.
1577 3 : if (m_bNoDataSetAsInt64 &&
1578 0 : !cpl::down_cast<netCDFDataset *>(poDS)->GetDefineMode())
1579 : {
1580 0 : CPLDebug("GDAL_netCDF",
1581 : "Setting NoDataValue to " CPL_FRMT_GIB
1582 : " (previously set to " CPL_FRMT_GIB ") "
1583 : "but file is no longer in define mode (id #%d, band #%d)",
1584 : static_cast<GIntBig>(nNoData),
1585 0 : static_cast<GIntBig>(m_nNodataValueInt64), cdfid, nBand);
1586 : }
1587 : #ifdef NCDF_DEBUG
1588 : else
1589 : {
1590 : CPLDebug("GDAL_netCDF",
1591 : "Setting NoDataValue to " CPL_FRMT_GIB
1592 : " (id #%d, band #%d)",
1593 : static_cast<GIntBig>(nNoData), cdfid, nBand);
1594 : }
1595 : #endif
1596 : // Make sure we are in define mode.
1597 3 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1598 :
1599 : int status;
1600 6 : if (eDataType == GDT_Int64 &&
1601 3 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
1602 : {
1603 3 : long long tmp = static_cast<long long>(nNoData);
1604 3 : status = nc_put_att_longlong(cdfid, nZId, NCDF_FillValue,
1605 : nc_datatype, 1, &tmp);
1606 : }
1607 : else
1608 : {
1609 0 : double dfNoData = static_cast<double>(nNoData);
1610 0 : status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
1611 : 1, &dfNoData);
1612 : }
1613 :
1614 3 : NCDF_ERR(status);
1615 :
1616 : // Update status if write worked.
1617 3 : if (status == NC_NOERR)
1618 : {
1619 3 : SetNoDataValueNoUpdate(nNoData);
1620 3 : return CE_None;
1621 : }
1622 :
1623 0 : return CE_Failure;
1624 : }
1625 :
1626 0 : SetNoDataValueNoUpdate(nNoData);
1627 0 : return CE_None;
1628 : }
1629 :
1630 : /************************************************************************/
1631 : /* SetNoDataValueNoUpdate() */
1632 : /************************************************************************/
1633 :
1634 11 : void netCDFRasterBand::SetNoDataValueNoUpdate(int64_t nNoData)
1635 : {
1636 11 : m_nNodataValueInt64 = nNoData;
1637 11 : m_bNoDataSet = false;
1638 11 : m_bNoDataSetAsInt64 = true;
1639 11 : m_bNoDataSetAsUInt64 = false;
1640 11 : }
1641 :
1642 : /************************************************************************/
1643 : /* SetNoDataValueAsUInt64() */
1644 : /************************************************************************/
1645 :
1646 3 : CPLErr netCDFRasterBand::SetNoDataValueAsUInt64(uint64_t nNoData)
1647 :
1648 : {
1649 6 : CPLMutexHolderD(&hNCMutex);
1650 :
1651 : // If already set to new value, don't do anything.
1652 3 : if (m_bNoDataSetAsUInt64 && nNoData == m_nNodataValueUInt64)
1653 0 : return CE_None;
1654 :
1655 : // Write value if in update mode.
1656 3 : if (poDS->GetAccess() == GA_Update)
1657 : {
1658 : // netcdf-4 does not allow to set _FillValue after leaving define mode,
1659 : // but it is ok if variable has not been written to, so only print
1660 : // debug. See bug #4484.
1661 3 : if (m_bNoDataSetAsUInt64 &&
1662 0 : !cpl::down_cast<netCDFDataset *>(poDS)->GetDefineMode())
1663 : {
1664 0 : CPLDebug("GDAL_netCDF",
1665 : "Setting NoDataValue to " CPL_FRMT_GUIB
1666 : " (previously set to " CPL_FRMT_GUIB ") "
1667 : "but file is no longer in define mode (id #%d, band #%d)",
1668 : static_cast<GUIntBig>(nNoData),
1669 0 : static_cast<GUIntBig>(m_nNodataValueUInt64), cdfid, nBand);
1670 : }
1671 : #ifdef NCDF_DEBUG
1672 : else
1673 : {
1674 : CPLDebug("GDAL_netCDF",
1675 : "Setting NoDataValue to " CPL_FRMT_GUIB
1676 : " (id #%d, band #%d)",
1677 : static_cast<GUIntBig>(nNoData), cdfid, nBand);
1678 : }
1679 : #endif
1680 : // Make sure we are in define mode.
1681 3 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1682 :
1683 : int status;
1684 6 : if (eDataType == GDT_UInt64 &&
1685 3 : cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
1686 : {
1687 3 : unsigned long long tmp = static_cast<long long>(nNoData);
1688 3 : status = nc_put_att_ulonglong(cdfid, nZId, NCDF_FillValue,
1689 : nc_datatype, 1, &tmp);
1690 : }
1691 : else
1692 : {
1693 0 : double dfNoData = static_cast<double>(nNoData);
1694 0 : status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
1695 : 1, &dfNoData);
1696 : }
1697 :
1698 3 : NCDF_ERR(status);
1699 :
1700 : // Update status if write worked.
1701 3 : if (status == NC_NOERR)
1702 : {
1703 3 : SetNoDataValueNoUpdate(nNoData);
1704 3 : return CE_None;
1705 : }
1706 :
1707 0 : return CE_Failure;
1708 : }
1709 :
1710 0 : SetNoDataValueNoUpdate(nNoData);
1711 0 : return CE_None;
1712 : }
1713 :
1714 : /************************************************************************/
1715 : /* SetNoDataValueNoUpdate() */
1716 : /************************************************************************/
1717 :
1718 10 : void netCDFRasterBand::SetNoDataValueNoUpdate(uint64_t nNoData)
1719 : {
1720 10 : m_nNodataValueUInt64 = nNoData;
1721 10 : m_bNoDataSet = false;
1722 10 : m_bNoDataSetAsInt64 = false;
1723 10 : m_bNoDataSetAsUInt64 = true;
1724 10 : }
1725 :
1726 : /************************************************************************/
1727 : /* DeleteNoDataValue() */
1728 : /************************************************************************/
1729 :
1730 : #ifdef notdef
1731 : CPLErr netCDFRasterBand::DeleteNoDataValue()
1732 :
1733 : {
1734 : CPLMutexHolderD(&hNCMutex);
1735 :
1736 : if (!bNoDataSet)
1737 : return CE_None;
1738 :
1739 : // Write value if in update mode.
1740 : if (poDS->GetAccess() == GA_Update)
1741 : {
1742 : // Make sure we are in define mode.
1743 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1744 :
1745 : status = nc_del_att(cdfid, nZId, NCDF_FillValue);
1746 :
1747 : NCDF_ERR(status);
1748 :
1749 : // Update status if write worked.
1750 : if (status == NC_NOERR)
1751 : {
1752 : dfNoDataValue = 0.0;
1753 : bNoDataSet = false;
1754 : return CE_None;
1755 : }
1756 :
1757 : return CE_Failure;
1758 : }
1759 :
1760 : dfNoDataValue = 0.0;
1761 : bNoDataSet = false;
1762 : return CE_None;
1763 : }
1764 : #endif
1765 :
1766 : /************************************************************************/
1767 : /* SerializeToXML() */
1768 : /************************************************************************/
1769 :
1770 5 : CPLXMLNode *netCDFRasterBand::SerializeToXML(const char * /* pszUnused */)
1771 : {
1772 : // Overridden from GDALPamDataset to add only band histogram
1773 : // and statistics. See bug #4244.
1774 5 : if (psPam == nullptr)
1775 0 : return nullptr;
1776 :
1777 : // Setup root node and attributes.
1778 : CPLXMLNode *psTree =
1779 5 : CPLCreateXMLNode(nullptr, CXT_Element, "PAMRasterBand");
1780 :
1781 5 : if (GetBand() > 0)
1782 : {
1783 10 : CPLString oFmt;
1784 5 : CPLSetXMLValue(psTree, "#band", oFmt.Printf("%d", GetBand()));
1785 : }
1786 :
1787 : // Histograms.
1788 5 : if (psPam->psSavedHistograms != nullptr)
1789 1 : CPLAddXMLChild(psTree, CPLCloneXMLTree(psPam->psSavedHistograms));
1790 :
1791 : // Metadata (statistics only).
1792 5 : GDALMultiDomainMetadata oMDMDStats;
1793 5 : const char *papszMDStats[] = {"STATISTICS_MINIMUM", "STATISTICS_MAXIMUM",
1794 : "STATISTICS_MEAN", "STATISTICS_STDDEV",
1795 : nullptr};
1796 25 : for (int i = 0; i < CSLCount(papszMDStats); i++)
1797 : {
1798 20 : const char *pszMDI = GetMetadataItem(papszMDStats[i]);
1799 20 : if (pszMDI)
1800 4 : oMDMDStats.SetMetadataItem(papszMDStats[i], pszMDI);
1801 : }
1802 5 : CPLXMLNode *psMD = oMDMDStats.Serialize();
1803 :
1804 5 : if (psMD != nullptr)
1805 : {
1806 1 : if (psMD->psChild == nullptr)
1807 0 : CPLDestroyXMLNode(psMD);
1808 : else
1809 1 : CPLAddXMLChild(psTree, psMD);
1810 : }
1811 :
1812 : // We don't want to return anything if we had no metadata to attach.
1813 5 : if (psTree->psChild == nullptr || psTree->psChild->psNext == nullptr)
1814 : {
1815 3 : CPLDestroyXMLNode(psTree);
1816 3 : psTree = nullptr;
1817 : }
1818 :
1819 5 : return psTree;
1820 : }
1821 :
1822 : /************************************************************************/
1823 : /* Get1DVariableIndexedByDimension() */
1824 : /************************************************************************/
1825 :
1826 81 : static int Get1DVariableIndexedByDimension(int cdfid, int nDimId,
1827 : const char *pszDimName,
1828 : bool bVerboseError, int *pnGroupID)
1829 : {
1830 81 : *pnGroupID = -1;
1831 81 : int nVarID = -1;
1832 : // First try to find a variable whose name is identical to the dimension
1833 : // name, and check that it is indeed indexed by this dimension
1834 81 : if (NCDFResolveVar(cdfid, pszDimName, pnGroupID, &nVarID) == CE_None)
1835 : {
1836 67 : int nDimCountOfVariable = 0;
1837 67 : nc_inq_varndims(*pnGroupID, nVarID, &nDimCountOfVariable);
1838 67 : if (nDimCountOfVariable == 1)
1839 : {
1840 67 : int nDimIdOfVariable = -1;
1841 67 : nc_inq_vardimid(*pnGroupID, nVarID, &nDimIdOfVariable);
1842 67 : if (nDimIdOfVariable == nDimId)
1843 : {
1844 67 : return nVarID;
1845 : }
1846 : }
1847 : }
1848 :
1849 : // Otherwise iterate over the variables to find potential candidates
1850 : // TODO: should be modified to search also in other groups using the same
1851 : // logic than in NCDFResolveVar(), but maybe not needed if it's a
1852 : // very rare case? and I think this is not CF compliant.
1853 14 : int nvars = 0;
1854 14 : CPL_IGNORE_RET_VAL(nc_inq(cdfid, nullptr, &nvars, nullptr, nullptr));
1855 :
1856 14 : int nCountCandidateVars = 0;
1857 14 : int nCandidateVarID = -1;
1858 65 : for (int k = 0; k < nvars; k++)
1859 : {
1860 51 : int nDimCountOfVariable = 0;
1861 51 : nc_inq_varndims(cdfid, k, &nDimCountOfVariable);
1862 51 : if (nDimCountOfVariable == 1)
1863 : {
1864 27 : int nDimIdOfVariable = -1;
1865 27 : nc_inq_vardimid(cdfid, k, &nDimIdOfVariable);
1866 27 : if (nDimIdOfVariable == nDimId)
1867 : {
1868 7 : nCountCandidateVars++;
1869 7 : nCandidateVarID = k;
1870 : }
1871 : }
1872 : }
1873 14 : if (nCountCandidateVars > 1)
1874 : {
1875 1 : if (bVerboseError)
1876 : {
1877 1 : CPLError(CE_Warning, CPLE_AppDefined,
1878 : "Several 1D variables are indexed by dimension %s",
1879 : pszDimName);
1880 : }
1881 1 : *pnGroupID = -1;
1882 1 : return -1;
1883 : }
1884 13 : else if (nCandidateVarID < 0)
1885 : {
1886 8 : if (bVerboseError)
1887 : {
1888 8 : CPLError(CE_Warning, CPLE_AppDefined,
1889 : "No 1D variable is indexed by dimension %s", pszDimName);
1890 : }
1891 : }
1892 13 : *pnGroupID = cdfid;
1893 13 : return nCandidateVarID;
1894 : }
1895 :
1896 : /************************************************************************/
1897 : /* CreateMetadataFromAttributes() */
1898 : /************************************************************************/
1899 :
1900 482 : void netCDFRasterBand::CreateMetadataFromAttributes()
1901 : {
1902 482 : char szVarName[NC_MAX_NAME + 1] = {};
1903 482 : int status = nc_inq_varname(cdfid, nZId, szVarName);
1904 482 : NCDF_ERR(status);
1905 :
1906 482 : GDALPamRasterBand::SetMetadataItem("NETCDF_VARNAME", szVarName);
1907 :
1908 : // Get attribute metadata.
1909 482 : int nAtt = 0;
1910 482 : NCDF_ERR(nc_inq_varnatts(cdfid, nZId, &nAtt));
1911 :
1912 2042 : for (int i = 0; i < nAtt; i++)
1913 : {
1914 1560 : char szMetaName[NC_MAX_NAME + 1] = {};
1915 1560 : status = nc_inq_attname(cdfid, nZId, i, szMetaName);
1916 1560 : if (status != NC_NOERR)
1917 12 : continue;
1918 :
1919 1560 : if (GDALPamRasterBand::GetMetadataItem(szMetaName) != nullptr)
1920 : {
1921 12 : continue;
1922 : }
1923 :
1924 1548 : char *pszMetaValue = nullptr;
1925 1548 : if (NCDFGetAttr(cdfid, nZId, szMetaName, &pszMetaValue) == CE_None)
1926 : {
1927 1548 : GDALPamRasterBand::SetMetadataItem(szMetaName, pszMetaValue);
1928 : }
1929 : else
1930 : {
1931 0 : CPLDebug("GDAL_netCDF", "invalid Band metadata %s", szMetaName);
1932 : }
1933 :
1934 1548 : if (pszMetaValue)
1935 : {
1936 1548 : CPLFree(pszMetaValue);
1937 1548 : pszMetaValue = nullptr;
1938 : }
1939 : }
1940 482 : }
1941 :
1942 : /************************************************************************/
1943 : /* CreateMetadataFromOtherVars() */
1944 : /************************************************************************/
1945 :
1946 49 : void netCDFRasterBand::CreateMetadataFromOtherVars()
1947 :
1948 : {
1949 49 : CPLAssert(!m_bCreateMetadataFromOtherVarsDone);
1950 49 : m_bCreateMetadataFromOtherVarsDone = true;
1951 :
1952 49 : netCDFDataset *l_poDS = cpl::down_cast<netCDFDataset *>(poDS);
1953 49 : const int nPamFlagsBackup = l_poDS->nPamFlags;
1954 :
1955 : // Compute all dimensions from Band number and save in Metadata.
1956 49 : int nd = 0;
1957 49 : nc_inq_varndims(cdfid, nZId, &nd);
1958 : // Compute multidimention band position.
1959 : //
1960 : // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
1961 : // if Data[2,3,4,x,y]
1962 : //
1963 : // BandPos0 = (nBand) / (3*4)
1964 : // BandPos1 = (nBand - BandPos0*(3*4)) / (4)
1965 : // BandPos2 = (nBand - BandPos0*(3*4)) % (4)
1966 :
1967 49 : int Sum = 1;
1968 49 : if (nd == 3)
1969 : {
1970 5 : Sum *= panBandZLev[0];
1971 : }
1972 :
1973 : // Loop over non-spatial dimensions.
1974 49 : int Taken = 0;
1975 :
1976 89 : for (int i = 0; i < nd - 2; i++)
1977 : {
1978 : int result;
1979 40 : if (i != nd - 2 - 1)
1980 : {
1981 18 : Sum = 1;
1982 37 : for (int j = i + 1; j < nd - 2; j++)
1983 : {
1984 19 : Sum *= panBandZLev[j];
1985 : }
1986 18 : result = static_cast<int>((nLevel - Taken) / Sum);
1987 : }
1988 : else
1989 : {
1990 22 : result = static_cast<int>((nLevel - Taken) % Sum);
1991 : }
1992 :
1993 40 : char szName[NC_MAX_NAME + 1] = {};
1994 40 : snprintf(szName, sizeof(szName), "%s",
1995 40 : l_poDS->papszDimName[l_poDS->m_anDimIds[panBandZPos[i]]]);
1996 :
1997 : char szMetaName[NC_MAX_NAME + 1 + 32];
1998 40 : snprintf(szMetaName, sizeof(szMetaName), "NETCDF_DIM_%s", szName);
1999 :
2000 40 : const int nGroupID = l_poDS->m_anExtraDimGroupIds[i];
2001 40 : const int nVarID = l_poDS->m_anExtraDimVarIds[i];
2002 40 : if (nVarID < 0)
2003 : {
2004 2 : GDALPamRasterBand::SetMetadataItem(szMetaName,
2005 : CPLSPrintf("%d", result + 1));
2006 : }
2007 : else
2008 : {
2009 : // TODO: Make sure all the status checks make sense.
2010 :
2011 38 : nc_type nVarType = NC_NAT;
2012 38 : /* status = */ nc_inq_vartype(nGroupID, nVarID, &nVarType);
2013 :
2014 38 : int nDims = 0;
2015 38 : /* status = */ nc_inq_varndims(nGroupID, nVarID, &nDims);
2016 :
2017 38 : char szMetaTemp[256] = {};
2018 38 : if (nDims == 1)
2019 : {
2020 38 : size_t count[1] = {1};
2021 38 : size_t start[1] = {static_cast<size_t>(result)};
2022 :
2023 38 : switch (nVarType)
2024 : {
2025 0 : case NC_BYTE:
2026 : // TODO: Check for signed/unsigned byte.
2027 : signed char cData;
2028 0 : /* status = */ nc_get_vara_schar(nGroupID, nVarID,
2029 : start, count, &cData);
2030 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", cData);
2031 0 : break;
2032 0 : case NC_SHORT:
2033 : short sData;
2034 0 : /* status = */ nc_get_vara_short(nGroupID, nVarID,
2035 : start, count, &sData);
2036 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", sData);
2037 0 : break;
2038 19 : case NC_INT:
2039 : {
2040 : int nData;
2041 19 : /* status = */ nc_get_vara_int(nGroupID, nVarID, start,
2042 : count, &nData);
2043 19 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", nData);
2044 19 : break;
2045 : }
2046 0 : case NC_FLOAT:
2047 : float fData;
2048 0 : /* status = */ nc_get_vara_float(nGroupID, nVarID,
2049 : start, count, &fData);
2050 0 : CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.8g",
2051 : fData);
2052 0 : break;
2053 18 : case NC_DOUBLE:
2054 : double dfData;
2055 18 : /* status = */ nc_get_vara_double(
2056 : nGroupID, nVarID, start, count, &dfData);
2057 18 : CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.16g",
2058 : dfData);
2059 18 : break;
2060 0 : case NC_UBYTE:
2061 : unsigned char ucData;
2062 0 : /* status = */ nc_get_vara_uchar(nGroupID, nVarID,
2063 : start, count, &ucData);
2064 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", ucData);
2065 0 : break;
2066 0 : case NC_USHORT:
2067 : unsigned short usData;
2068 0 : /* status = */ nc_get_vara_ushort(
2069 : nGroupID, nVarID, start, count, &usData);
2070 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", usData);
2071 0 : break;
2072 0 : case NC_UINT:
2073 : {
2074 : unsigned int unData;
2075 0 : /* status = */ nc_get_vara_uint(nGroupID, nVarID, start,
2076 : count, &unData);
2077 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", unData);
2078 0 : break;
2079 : }
2080 1 : case NC_INT64:
2081 : {
2082 : long long nData;
2083 1 : /* status = */ nc_get_vara_longlong(
2084 : nGroupID, nVarID, start, count, &nData);
2085 1 : snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GIB,
2086 : nData);
2087 1 : break;
2088 : }
2089 0 : case NC_UINT64:
2090 : {
2091 : unsigned long long unData;
2092 0 : /* status = */ nc_get_vara_ulonglong(
2093 : nGroupID, nVarID, start, count, &unData);
2094 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GUIB,
2095 : unData);
2096 0 : break;
2097 : }
2098 0 : default:
2099 0 : CPLDebug("GDAL_netCDF", "invalid dim %s, type=%d",
2100 : szMetaTemp, nVarType);
2101 0 : break;
2102 : }
2103 : }
2104 : else
2105 : {
2106 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", result + 1);
2107 : }
2108 :
2109 : // Save dimension value.
2110 : // NOTE: removed #original_units as not part of CF-1.
2111 :
2112 38 : GDALPamRasterBand::SetMetadataItem(szMetaName, szMetaTemp);
2113 : }
2114 :
2115 : // Avoid int32 overflow. Perhaps something more sensible to do here ?
2116 40 : if (result > 0 && Sum > INT_MAX / result)
2117 0 : break;
2118 40 : if (Taken > INT_MAX - result * Sum)
2119 0 : break;
2120 :
2121 40 : Taken += result * Sum;
2122 : } // End loop non-spatial dimensions.
2123 :
2124 49 : l_poDS->nPamFlags = nPamFlagsBackup;
2125 49 : }
2126 :
2127 : /************************************************************************/
2128 : /* CheckData() */
2129 : /************************************************************************/
2130 : template <class T>
2131 5732 : void netCDFRasterBand::CheckData(void *pImage, void *pImageNC,
2132 : size_t nTmpBlockXSize, size_t nTmpBlockYSize,
2133 : bool bCheckIsNan)
2134 : {
2135 5732 : CPLAssert(pImage != nullptr && pImageNC != nullptr);
2136 :
2137 : // If this block is not a full block (in the x axis), we need to re-arrange
2138 : // the data this is because partial blocks are not arranged the same way in
2139 : // netcdf and gdal.
2140 5732 : if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
2141 : {
2142 6 : T *ptrWrite = static_cast<T *>(pImage);
2143 6 : T *ptrRead = static_cast<T *>(pImageNC);
2144 29 : for (size_t j = 0; j < nTmpBlockYSize;
2145 23 : j++, ptrWrite += nBlockXSize, ptrRead += nTmpBlockXSize)
2146 : {
2147 23 : memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T));
2148 : }
2149 : }
2150 :
2151 : // Is valid data checking needed or requested?
2152 5732 : if (bValidRangeValid || bCheckIsNan)
2153 : {
2154 1265 : T *ptrImage = static_cast<T *>(pImage);
2155 2584 : for (size_t j = 0; j < nTmpBlockYSize; j++)
2156 : {
2157 : // k moves along the gdal block, skipping the out-of-range pixels.
2158 1319 : size_t k = j * nBlockXSize;
2159 96938 : for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
2160 : {
2161 : // Check for nodata and nan.
2162 95619 : if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
2163 6301 : continue;
2164 89318 : if (bCheckIsNan && std::isnan((double)ptrImage[k]))
2165 : {
2166 5737 : ptrImage[k] = (T)m_dfNoDataValue;
2167 5737 : continue;
2168 : }
2169 : // Check for valid_range.
2170 83581 : if (bValidRangeValid)
2171 : {
2172 40986 : if (((adfValidRange[0] != m_dfNoDataValue) &&
2173 40986 : (ptrImage[k] < (T)adfValidRange[0])) ||
2174 40983 : ((adfValidRange[1] != m_dfNoDataValue) &&
2175 40983 : (ptrImage[k] > (T)adfValidRange[1])))
2176 : {
2177 4 : ptrImage[k] = (T)m_dfNoDataValue;
2178 : }
2179 : }
2180 : }
2181 : }
2182 : }
2183 :
2184 : // If minimum longitude is > 180, subtract 360 from all.
2185 : // If not, disable checking for further calls (check just once).
2186 : // Only check first and last block elements since lon must be monotonic.
2187 5732 : const bool bIsSigned = std::numeric_limits<T>::is_signed;
2188 5419 : if (bCheckLongitude && bIsSigned &&
2189 9 : !CPLIsEqual((double)((T *)pImage)[0], m_dfNoDataValue) &&
2190 8 : !CPLIsEqual((double)((T *)pImage)[nTmpBlockXSize - 1],
2191 2714 : m_dfNoDataValue) &&
2192 8 : std::min(((T *)pImage)[0], ((T *)pImage)[nTmpBlockXSize - 1]) > 180.0)
2193 : {
2194 0 : T *ptrImage = static_cast<T *>(pImage);
2195 0 : for (size_t j = 0; j < nTmpBlockYSize; j++)
2196 : {
2197 0 : size_t k = j * nBlockXSize;
2198 0 : for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
2199 : {
2200 0 : if (!CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
2201 0 : ptrImage[k] = static_cast<T>(ptrImage[k] - 360);
2202 : }
2203 : }
2204 : }
2205 : else
2206 : {
2207 5732 : bCheckLongitude = false;
2208 : }
2209 5732 : }
2210 :
2211 : /************************************************************************/
2212 : /* CheckDataCpx() */
2213 : /************************************************************************/
2214 : template <class T>
2215 25 : void netCDFRasterBand::CheckDataCpx(void *pImage, void *pImageNC,
2216 : size_t nTmpBlockXSize,
2217 : size_t nTmpBlockYSize, bool bCheckIsNan)
2218 : {
2219 25 : CPLAssert(pImage != nullptr && pImageNC != nullptr);
2220 :
2221 : // If this block is not a full block (in the x axis), we need to re-arrange
2222 : // the data this is because partial blocks are not arranged the same way in
2223 : // netcdf and gdal.
2224 25 : if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
2225 : {
2226 0 : T *ptrWrite = static_cast<T *>(pImage);
2227 0 : T *ptrRead = static_cast<T *>(pImageNC);
2228 0 : for (size_t j = 0; j < nTmpBlockYSize; j++,
2229 0 : ptrWrite += (2 * nBlockXSize),
2230 0 : ptrRead += (2 * nTmpBlockXSize))
2231 : {
2232 0 : memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T) * 2);
2233 : }
2234 : }
2235 :
2236 : // Is valid data checking needed or requested?
2237 25 : if (bValidRangeValid || bCheckIsNan)
2238 : {
2239 0 : T *ptrImage = static_cast<T *>(pImage);
2240 0 : for (size_t j = 0; j < nTmpBlockYSize; j++)
2241 : {
2242 : // k moves along the gdal block, skipping the out-of-range pixels.
2243 0 : size_t k = 2 * j * nBlockXSize;
2244 0 : for (size_t i = 0; i < (2 * nTmpBlockXSize); i++, k++)
2245 : {
2246 : // Check for nodata and nan.
2247 0 : if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
2248 0 : continue;
2249 0 : if (bCheckIsNan && std::isnan((double)ptrImage[k]))
2250 : {
2251 0 : ptrImage[k] = (T)m_dfNoDataValue;
2252 0 : continue;
2253 : }
2254 : // Check for valid_range.
2255 0 : if (bValidRangeValid)
2256 : {
2257 0 : if (((adfValidRange[0] != m_dfNoDataValue) &&
2258 0 : (ptrImage[k] < (T)adfValidRange[0])) ||
2259 0 : ((adfValidRange[1] != m_dfNoDataValue) &&
2260 0 : (ptrImage[k] > (T)adfValidRange[1])))
2261 : {
2262 0 : ptrImage[k] = (T)m_dfNoDataValue;
2263 : }
2264 : }
2265 : }
2266 : }
2267 : }
2268 25 : }
2269 :
2270 : /************************************************************************/
2271 : /* FetchNetcdfChunk() */
2272 : /************************************************************************/
2273 :
2274 5757 : bool netCDFRasterBand::FetchNetcdfChunk(size_t xstart, size_t ystart,
2275 : void *pImage)
2276 : {
2277 5757 : size_t start[MAX_NC_DIMS] = {};
2278 5757 : size_t edge[MAX_NC_DIMS] = {};
2279 :
2280 5757 : start[nBandXPos] = xstart;
2281 5757 : edge[nBandXPos] = nBlockXSize;
2282 5757 : if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
2283 6 : edge[nBandXPos] = nRasterXSize - start[nBandXPos];
2284 5757 : if (nBandYPos >= 0)
2285 : {
2286 5753 : start[nBandYPos] = ystart;
2287 5753 : edge[nBandYPos] = nBlockYSize;
2288 5753 : if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
2289 4 : edge[nBandYPos] = nRasterYSize - start[nBandYPos];
2290 : }
2291 5757 : const size_t nYChunkSize = nBandYPos < 0 ? 1 : edge[nBandYPos];
2292 :
2293 : #ifdef NCDF_DEBUG
2294 : CPLDebug("GDAL_netCDF", "start={%ld,%ld} edge={%ld,%ld} bBottomUp=%d",
2295 : start[nBandXPos], nBandYPos < 0 ? 0 : start[nBandYPos],
2296 : edge[nBandXPos], nYChunkSize, ((netCDFDataset *)poDS)->bBottomUp);
2297 : #endif
2298 :
2299 5757 : int nd = 0;
2300 5757 : nc_inq_varndims(cdfid, nZId, &nd);
2301 5757 : if (nd == 3)
2302 : {
2303 1078 : start[panBandZPos[0]] = nLevel; // z
2304 1078 : edge[panBandZPos[0]] = 1;
2305 : }
2306 :
2307 : // Compute multidimention band position.
2308 : //
2309 : // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
2310 : // if Data[2,3,4,x,y]
2311 : //
2312 : // BandPos0 = (nBand) / (3*4)
2313 : // BandPos1 = (nBand - (3*4)) / (4)
2314 : // BandPos2 = (nBand - (3*4)) % (4)
2315 5757 : if (nd > 3)
2316 : {
2317 160 : int Sum = -1;
2318 160 : int Taken = 0;
2319 480 : for (int i = 0; i < nd - 2; i++)
2320 : {
2321 320 : if (i != nd - 2 - 1)
2322 : {
2323 160 : Sum = 1;
2324 320 : for (int j = i + 1; j < nd - 2; j++)
2325 : {
2326 160 : Sum *= panBandZLev[j];
2327 : }
2328 160 : start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
2329 160 : edge[panBandZPos[i]] = 1;
2330 : }
2331 : else
2332 : {
2333 160 : start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
2334 160 : edge[panBandZPos[i]] = 1;
2335 : }
2336 320 : Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
2337 : }
2338 : }
2339 :
2340 : // Make sure we are in data mode.
2341 5757 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
2342 :
2343 : // If this block is not a full block in the x axis, we need to
2344 : // re-arrange the data because partial blocks are not arranged the
2345 : // same way in netcdf and gdal, so we first we read the netcdf data at
2346 : // the end of the gdal block buffer then re-arrange rows in CheckData().
2347 5757 : void *pImageNC = pImage;
2348 5757 : if (edge[nBandXPos] != static_cast<size_t>(nBlockXSize))
2349 : {
2350 6 : pImageNC = static_cast<GByte *>(pImage) +
2351 6 : ((static_cast<size_t>(nBlockXSize) * nBlockYSize -
2352 12 : edge[nBandXPos] * nYChunkSize) *
2353 6 : GDALGetDataTypeSizeBytes(eDataType));
2354 : }
2355 :
2356 : // Read data according to type.
2357 : int status;
2358 5757 : if (eDataType == GDT_Byte)
2359 : {
2360 3005 : if (bSignedData)
2361 : {
2362 0 : status = nc_get_vara_schar(cdfid, nZId, start, edge,
2363 : static_cast<signed char *>(pImageNC));
2364 0 : if (status == NC_NOERR)
2365 0 : CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
2366 : nYChunkSize, false);
2367 : }
2368 : else
2369 : {
2370 3005 : status = nc_get_vara_uchar(cdfid, nZId, start, edge,
2371 : static_cast<unsigned char *>(pImageNC));
2372 3005 : if (status == NC_NOERR)
2373 3005 : CheckData<unsigned char>(pImage, pImageNC, edge[nBandXPos],
2374 : nYChunkSize, false);
2375 : }
2376 : }
2377 2752 : else if (eDataType == GDT_Int8)
2378 : {
2379 60 : status = nc_get_vara_schar(cdfid, nZId, start, edge,
2380 : static_cast<signed char *>(pImageNC));
2381 60 : if (status == NC_NOERR)
2382 60 : CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
2383 : nYChunkSize, false);
2384 : }
2385 2692 : else if (nc_datatype == NC_SHORT)
2386 : {
2387 465 : status = nc_get_vara_short(cdfid, nZId, start, edge,
2388 : static_cast<short *>(pImageNC));
2389 465 : if (status == NC_NOERR)
2390 : {
2391 465 : if (eDataType == GDT_Int16)
2392 : {
2393 462 : CheckData<GInt16>(pImage, pImageNC, edge[nBandXPos],
2394 : nYChunkSize, false);
2395 : }
2396 : else
2397 : {
2398 3 : CheckData<GUInt16>(pImage, pImageNC, edge[nBandXPos],
2399 : nYChunkSize, false);
2400 : }
2401 : }
2402 : }
2403 2227 : else if (eDataType == GDT_Int32)
2404 : {
2405 : #if SIZEOF_UNSIGNED_LONG == 4
2406 : status = nc_get_vara_long(cdfid, nZId, start, edge,
2407 : static_cast<long *>(pImageNC));
2408 : if (status == NC_NOERR)
2409 : CheckData<long>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2410 : false);
2411 : #else
2412 912 : status = nc_get_vara_int(cdfid, nZId, start, edge,
2413 : static_cast<int *>(pImageNC));
2414 912 : if (status == NC_NOERR)
2415 912 : CheckData<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2416 : false);
2417 : #endif
2418 : }
2419 1315 : else if (eDataType == GDT_Float32)
2420 : {
2421 1178 : status = nc_get_vara_float(cdfid, nZId, start, edge,
2422 : static_cast<float *>(pImageNC));
2423 1178 : if (status == NC_NOERR)
2424 1178 : CheckData<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2425 : true);
2426 : }
2427 137 : else if (eDataType == GDT_Float64)
2428 : {
2429 86 : status = nc_get_vara_double(cdfid, nZId, start, edge,
2430 : static_cast<double *>(pImageNC));
2431 86 : if (status == NC_NOERR)
2432 86 : CheckData<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2433 : true);
2434 : }
2435 51 : else if (eDataType == GDT_UInt16)
2436 : {
2437 6 : status = nc_get_vara_ushort(cdfid, nZId, start, edge,
2438 : static_cast<unsigned short *>(pImageNC));
2439 6 : if (status == NC_NOERR)
2440 6 : CheckData<unsigned short>(pImage, pImageNC, edge[nBandXPos],
2441 : nYChunkSize, false);
2442 : }
2443 45 : else if (eDataType == GDT_UInt32)
2444 : {
2445 6 : status = nc_get_vara_uint(cdfid, nZId, start, edge,
2446 : static_cast<unsigned int *>(pImageNC));
2447 6 : if (status == NC_NOERR)
2448 6 : CheckData<unsigned int>(pImage, pImageNC, edge[nBandXPos],
2449 : nYChunkSize, false);
2450 : }
2451 39 : else if (eDataType == GDT_Int64)
2452 : {
2453 7 : status = nc_get_vara_longlong(cdfid, nZId, start, edge,
2454 : static_cast<long long *>(pImageNC));
2455 7 : if (status == NC_NOERR)
2456 7 : CheckData<std::int64_t>(pImage, pImageNC, edge[nBandXPos],
2457 : nYChunkSize, false);
2458 : }
2459 32 : else if (eDataType == GDT_UInt64)
2460 : {
2461 : status =
2462 7 : nc_get_vara_ulonglong(cdfid, nZId, start, edge,
2463 : static_cast<unsigned long long *>(pImageNC));
2464 7 : if (status == NC_NOERR)
2465 7 : CheckData<std::uint64_t>(pImage, pImageNC, edge[nBandXPos],
2466 : nYChunkSize, false);
2467 : }
2468 25 : else if (eDataType == GDT_CInt16)
2469 : {
2470 0 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2471 0 : if (status == NC_NOERR)
2472 0 : CheckDataCpx<short>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2473 : false);
2474 : }
2475 25 : else if (eDataType == GDT_CInt32)
2476 : {
2477 0 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2478 0 : if (status == NC_NOERR)
2479 0 : CheckDataCpx<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2480 : false);
2481 : }
2482 25 : else if (eDataType == GDT_CFloat32)
2483 : {
2484 20 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2485 20 : if (status == NC_NOERR)
2486 20 : CheckDataCpx<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2487 : false);
2488 : }
2489 5 : else if (eDataType == GDT_CFloat64)
2490 : {
2491 5 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2492 5 : if (status == NC_NOERR)
2493 5 : CheckDataCpx<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2494 : false);
2495 : }
2496 :
2497 : else
2498 0 : status = NC_EBADTYPE;
2499 :
2500 5757 : if (status != NC_NOERR)
2501 : {
2502 0 : CPLError(CE_Failure, CPLE_AppDefined,
2503 : "netCDF chunk fetch failed: #%d (%s)", status,
2504 : nc_strerror(status));
2505 0 : return false;
2506 : }
2507 5757 : return true;
2508 : }
2509 :
2510 : /************************************************************************/
2511 : /* IReadBlock() */
2512 : /************************************************************************/
2513 :
2514 5757 : CPLErr netCDFRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
2515 : void *pImage)
2516 :
2517 : {
2518 11514 : CPLMutexHolderD(&hNCMutex);
2519 :
2520 : // Locate X, Y and Z position in the array.
2521 :
2522 5757 : size_t xstart = static_cast<size_t>(nBlockXOff) * nBlockXSize;
2523 5757 : size_t ystart = 0;
2524 :
2525 : // Check y order.
2526 5757 : if (nBandYPos >= 0)
2527 : {
2528 5753 : auto poGDS = static_cast<netCDFDataset *>(poDS);
2529 5753 : if (poGDS->bBottomUp)
2530 : {
2531 4838 : if (nBlockYSize == 1)
2532 : {
2533 4825 : ystart = nRasterYSize - 1 - nBlockYOff;
2534 : }
2535 : else
2536 : {
2537 : // in GDAL space
2538 13 : ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
2539 : const size_t yend =
2540 26 : std::min(ystart + nBlockYSize - 1,
2541 13 : static_cast<size_t>(nRasterYSize - 1));
2542 : // in netCDF space
2543 13 : const size_t nFirstChunkLine = nRasterYSize - 1 - yend;
2544 13 : const size_t nLastChunkLine = nRasterYSize - 1 - ystart;
2545 13 : const size_t nFirstChunkBlock = nFirstChunkLine / nBlockYSize;
2546 13 : const size_t nLastChunkBlock = nLastChunkLine / nBlockYSize;
2547 :
2548 : const auto firstKey = netCDFDataset::ChunkKey(
2549 13 : nBlockXOff, nFirstChunkBlock, nBand);
2550 : const auto secondKey =
2551 13 : netCDFDataset::ChunkKey(nBlockXOff, nLastChunkBlock, nBand);
2552 :
2553 : // Retrieve data from the one or 2 needed netCDF chunks
2554 13 : std::shared_ptr<std::vector<GByte>> firstChunk;
2555 13 : std::shared_ptr<std::vector<GByte>> secondChunk;
2556 13 : if (poGDS->poChunkCache)
2557 : {
2558 13 : poGDS->poChunkCache->tryGet(firstKey, firstChunk);
2559 13 : if (firstKey != secondKey)
2560 6 : poGDS->poChunkCache->tryGet(secondKey, secondChunk);
2561 : }
2562 : const size_t nChunkLineSize =
2563 13 : static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
2564 13 : nBlockXSize;
2565 13 : const size_t nChunkSize = nChunkLineSize * nBlockYSize;
2566 13 : if (!firstChunk)
2567 : {
2568 11 : firstChunk.reset(new std::vector<GByte>(nChunkSize));
2569 11 : if (!FetchNetcdfChunk(xstart,
2570 11 : nFirstChunkBlock * nBlockYSize,
2571 11 : firstChunk.get()->data()))
2572 0 : return CE_Failure;
2573 11 : if (poGDS->poChunkCache)
2574 11 : poGDS->poChunkCache->insert(firstKey, firstChunk);
2575 : }
2576 13 : if (!secondChunk && firstKey != secondKey)
2577 : {
2578 2 : secondChunk.reset(new std::vector<GByte>(nChunkSize));
2579 2 : if (!FetchNetcdfChunk(xstart, nLastChunkBlock * nBlockYSize,
2580 2 : secondChunk.get()->data()))
2581 0 : return CE_Failure;
2582 2 : if (poGDS->poChunkCache)
2583 2 : poGDS->poChunkCache->insert(secondKey, secondChunk);
2584 : }
2585 :
2586 : // Assemble netCDF chunks into GDAL block
2587 13 : GByte *pabyImage = static_cast<GByte *>(pImage);
2588 13 : const size_t nFirstChunkBlockLine =
2589 13 : nFirstChunkBlock * nBlockYSize;
2590 13 : const size_t nLastChunkBlockLine =
2591 13 : nLastChunkBlock * nBlockYSize;
2592 146 : for (size_t iLine = ystart; iLine <= yend; iLine++)
2593 : {
2594 133 : const size_t nLineFromBottom = nRasterYSize - 1 - iLine;
2595 133 : const size_t nChunkY = nLineFromBottom / nBlockYSize;
2596 133 : if (nChunkY == nFirstChunkBlock)
2597 : {
2598 121 : memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
2599 121 : firstChunk.get()->data() +
2600 121 : (nLineFromBottom - nFirstChunkBlockLine) *
2601 : nChunkLineSize,
2602 : nChunkLineSize);
2603 : }
2604 : else
2605 : {
2606 12 : CPLAssert(nChunkY == nLastChunkBlock);
2607 12 : assert(secondChunk);
2608 12 : memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
2609 12 : secondChunk.get()->data() +
2610 12 : (nLineFromBottom - nLastChunkBlockLine) *
2611 : nChunkLineSize,
2612 : nChunkLineSize);
2613 : }
2614 : }
2615 13 : return CE_None;
2616 : }
2617 : }
2618 : else
2619 : {
2620 915 : ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
2621 : }
2622 : }
2623 :
2624 5744 : return FetchNetcdfChunk(xstart, ystart, pImage) ? CE_None : CE_Failure;
2625 : }
2626 :
2627 : /************************************************************************/
2628 : /* IWriteBlock() */
2629 : /************************************************************************/
2630 :
2631 6401 : CPLErr netCDFRasterBand::IWriteBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
2632 : void *pImage)
2633 : {
2634 12802 : CPLMutexHolderD(&hNCMutex);
2635 :
2636 : #ifdef NCDF_DEBUG
2637 : if (nBlockYOff == 0 || (nBlockYOff == nRasterYSize - 1))
2638 : CPLDebug("GDAL_netCDF",
2639 : "netCDFRasterBand::IWriteBlock( %d, %d, ...) nBand=%d",
2640 : nBlockXOff, nBlockYOff, nBand);
2641 : #endif
2642 :
2643 6401 : int nd = 0;
2644 6401 : nc_inq_varndims(cdfid, nZId, &nd);
2645 :
2646 : // Locate X, Y and Z position in the array.
2647 :
2648 : size_t start[MAX_NC_DIMS];
2649 6401 : memset(start, 0, sizeof(start));
2650 6401 : start[nBandXPos] = static_cast<size_t>(nBlockXOff) * nBlockXSize;
2651 :
2652 : // check y order.
2653 6401 : if (static_cast<netCDFDataset *>(poDS)->bBottomUp)
2654 : {
2655 6377 : if (nBlockYSize == 1)
2656 : {
2657 6377 : start[nBandYPos] = nRasterYSize - 1 - nBlockYOff;
2658 : }
2659 : else
2660 : {
2661 0 : CPLError(CE_Failure, CPLE_AppDefined,
2662 : "nBlockYSize = %d, only 1 supported when "
2663 : "writing bottom-up dataset",
2664 : nBlockYSize);
2665 0 : return CE_Failure;
2666 : }
2667 : }
2668 : else
2669 : {
2670 24 : start[nBandYPos] = static_cast<size_t>(nBlockYOff) * nBlockYSize; // y
2671 : }
2672 :
2673 6401 : size_t edge[MAX_NC_DIMS] = {};
2674 :
2675 6401 : edge[nBandXPos] = nBlockXSize;
2676 6401 : if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
2677 0 : edge[nBandXPos] = nRasterXSize - start[nBandXPos];
2678 6401 : edge[nBandYPos] = nBlockYSize;
2679 6401 : if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
2680 0 : edge[nBandYPos] = nRasterYSize - start[nBandYPos];
2681 :
2682 6401 : if (nd == 3)
2683 : {
2684 610 : start[panBandZPos[0]] = nLevel; // z
2685 610 : edge[panBandZPos[0]] = 1;
2686 : }
2687 :
2688 : // Compute multidimention band position.
2689 : //
2690 : // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
2691 : // if Data[2,3,4,x,y]
2692 : //
2693 : // BandPos0 = (nBand) / (3*4)
2694 : // BandPos1 = (nBand - (3*4)) / (4)
2695 : // BandPos2 = (nBand - (3*4)) % (4)
2696 6401 : if (nd > 3)
2697 : {
2698 178 : int Sum = -1;
2699 178 : int Taken = 0;
2700 534 : for (int i = 0; i < nd - 2; i++)
2701 : {
2702 356 : if (i != nd - 2 - 1)
2703 : {
2704 178 : Sum = 1;
2705 356 : for (int j = i + 1; j < nd - 2; j++)
2706 : {
2707 178 : Sum *= panBandZLev[j];
2708 : }
2709 178 : start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
2710 178 : edge[panBandZPos[i]] = 1;
2711 : }
2712 : else
2713 : {
2714 178 : start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
2715 178 : edge[panBandZPos[i]] = 1;
2716 : }
2717 356 : Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
2718 : }
2719 : }
2720 :
2721 : // Make sure we are in data mode.
2722 6401 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
2723 :
2724 : // Copy data according to type.
2725 6401 : int status = 0;
2726 6401 : if (eDataType == GDT_Byte)
2727 : {
2728 5842 : if (bSignedData)
2729 0 : status = nc_put_vara_schar(cdfid, nZId, start, edge,
2730 : static_cast<signed char *>(pImage));
2731 : else
2732 5842 : status = nc_put_vara_uchar(cdfid, nZId, start, edge,
2733 : static_cast<unsigned char *>(pImage));
2734 : }
2735 559 : else if (eDataType == GDT_Int8)
2736 : {
2737 40 : status = nc_put_vara_schar(cdfid, nZId, start, edge,
2738 : static_cast<signed char *>(pImage));
2739 : }
2740 519 : else if (nc_datatype == NC_SHORT)
2741 : {
2742 101 : status = nc_put_vara_short(cdfid, nZId, start, edge,
2743 : static_cast<short *>(pImage));
2744 : }
2745 418 : else if (eDataType == GDT_Int32)
2746 : {
2747 210 : status = nc_put_vara_int(cdfid, nZId, start, edge,
2748 : static_cast<int *>(pImage));
2749 : }
2750 208 : else if (eDataType == GDT_Float32)
2751 : {
2752 128 : status = nc_put_vara_float(cdfid, nZId, start, edge,
2753 : static_cast<float *>(pImage));
2754 : }
2755 80 : else if (eDataType == GDT_Float64)
2756 : {
2757 50 : status = nc_put_vara_double(cdfid, nZId, start, edge,
2758 : static_cast<double *>(pImage));
2759 : }
2760 30 : else if (eDataType == GDT_UInt16 &&
2761 12 : static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2762 : {
2763 12 : status = nc_put_vara_ushort(cdfid, nZId, start, edge,
2764 : static_cast<unsigned short *>(pImage));
2765 : }
2766 18 : else if (eDataType == GDT_UInt32 &&
2767 12 : static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2768 : {
2769 12 : status = nc_put_vara_uint(cdfid, nZId, start, edge,
2770 : static_cast<unsigned int *>(pImage));
2771 : }
2772 6 : else if (eDataType == GDT_UInt64 &&
2773 3 : static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2774 : {
2775 3 : status =
2776 3 : nc_put_vara_ulonglong(cdfid, nZId, start, edge,
2777 : static_cast<unsigned long long *>(pImage));
2778 : }
2779 3 : else if (eDataType == GDT_Int64 &&
2780 3 : static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2781 : {
2782 3 : status = nc_put_vara_longlong(cdfid, nZId, start, edge,
2783 : static_cast<long long *>(pImage));
2784 : }
2785 : else
2786 : {
2787 0 : CPLError(CE_Failure, CPLE_NotSupported,
2788 : "The NetCDF driver does not support GDAL data type %d",
2789 0 : eDataType);
2790 0 : status = NC_EBADTYPE;
2791 : }
2792 6401 : NCDF_ERR(status);
2793 :
2794 6401 : if (status != NC_NOERR)
2795 : {
2796 0 : CPLError(CE_Failure, CPLE_AppDefined,
2797 : "netCDF scanline write failed: %s", nc_strerror(status));
2798 0 : return CE_Failure;
2799 : }
2800 :
2801 6401 : return CE_None;
2802 : }
2803 :
2804 : /************************************************************************/
2805 : /* ==================================================================== */
2806 : /* netCDFDataset */
2807 : /* ==================================================================== */
2808 : /************************************************************************/
2809 :
2810 : /************************************************************************/
2811 : /* netCDFDataset() */
2812 : /************************************************************************/
2813 :
2814 1149 : netCDFDataset::netCDFDataset()
2815 : :
2816 : // Basic dataset vars.
2817 : #ifdef ENABLE_NCDUMP
2818 : bFileToDestroyAtClosing(false),
2819 : #endif
2820 : cdfid(-1), nSubDatasets(0), papszSubDatasets(nullptr),
2821 : papszMetadata(nullptr), bBottomUp(true), eFormat(NCDF_FORMAT_NONE),
2822 : bIsGdalFile(false), bIsGdalCfFile(false), pszCFProjection(nullptr),
2823 : pszCFCoordinates(nullptr), nCFVersion(1.6), bSGSupport(false),
2824 1149 : eMultipleLayerBehavior(SINGLE_LAYER), logCount(0), vcdf(this, cdfid),
2825 1149 : GeometryScribe(vcdf, this->generateLogName()),
2826 1149 : FieldScribe(vcdf, this->generateLogName()),
2827 2298 : bufManager(CPLGetUsablePhysicalRAM() / 5),
2828 :
2829 : // projection/GT.
2830 : nXDimID(-1), nYDimID(-1), bIsProjected(false),
2831 : bIsGeographic(false), // Can be not projected, and also not geographic
2832 : // State vars.
2833 : bDefineMode(true), bAddedGridMappingRef(false),
2834 :
2835 : // Create vars.
2836 : papszCreationOptions(nullptr), eCompress(NCDF_COMPRESS_NONE),
2837 : nZLevel(NCDF_DEFLATE_LEVEL), bChunking(false), nCreateMode(NC_CLOBBER),
2838 3447 : bSignedData(true)
2839 : {
2840 1149 : m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2841 :
2842 : // Set buffers
2843 1149 : bufManager.addBuffer(&(GeometryScribe.getMemBuffer()));
2844 1149 : bufManager.addBuffer(&(FieldScribe.getMemBuffer()));
2845 1149 : }
2846 :
2847 : /************************************************************************/
2848 : /* ~netCDFDataset() */
2849 : /************************************************************************/
2850 :
2851 2205 : netCDFDataset::~netCDFDataset()
2852 :
2853 : {
2854 1149 : netCDFDataset::Close();
2855 2205 : }
2856 :
2857 : /************************************************************************/
2858 : /* Close() */
2859 : /************************************************************************/
2860 :
2861 1956 : CPLErr netCDFDataset::Close()
2862 : {
2863 1956 : CPLErr eErr = CE_None;
2864 1956 : if (nOpenFlags != OPEN_FLAGS_CLOSED)
2865 : {
2866 2298 : CPLMutexHolderD(&hNCMutex);
2867 :
2868 : #ifdef NCDF_DEBUG
2869 : CPLDebug("GDAL_netCDF",
2870 : "netCDFDataset::~netCDFDataset(), cdfid=%d filename=%s", cdfid,
2871 : osFilename.c_str());
2872 : #endif
2873 :
2874 : // Write data related to geotransform
2875 1431 : if (GetAccess() == GA_Update && !m_bAddedProjectionVarsData &&
2876 282 : (m_bHasProjection || m_bHasGeoTransform))
2877 : {
2878 : // Ensure projection is written if GeoTransform OR Projection are
2879 : // missing.
2880 37 : if (!m_bAddedProjectionVarsDefs)
2881 : {
2882 2 : AddProjectionVars(true, nullptr, nullptr);
2883 : }
2884 37 : AddProjectionVars(false, nullptr, nullptr);
2885 : }
2886 :
2887 1149 : if (netCDFDataset::FlushCache(true) != CE_None)
2888 0 : eErr = CE_Failure;
2889 :
2890 1149 : if (GetAccess() == GA_Update && !SGCommitPendingTransaction())
2891 0 : eErr = CE_Failure;
2892 :
2893 1151 : for (size_t i = 0; i < apoVectorDatasets.size(); i++)
2894 2 : delete apoVectorDatasets[i];
2895 :
2896 : // Make sure projection variable is written to band variable.
2897 1149 : if (GetAccess() == GA_Update && !bAddedGridMappingRef)
2898 : {
2899 299 : if (!AddGridMappingRef())
2900 0 : eErr = CE_Failure;
2901 : }
2902 :
2903 1149 : CSLDestroy(papszMetadata);
2904 1149 : CSLDestroy(papszSubDatasets);
2905 1149 : CSLDestroy(papszCreationOptions);
2906 :
2907 1149 : CPLFree(pszCFProjection);
2908 :
2909 1149 : if (cdfid > 0)
2910 : {
2911 : #ifdef NCDF_DEBUG
2912 : CPLDebug("GDAL_netCDF", "calling nc_close( %d)", cdfid);
2913 : #endif
2914 663 : int status = GDAL_nc_close(cdfid);
2915 : #ifdef ENABLE_UFFD
2916 663 : NETCDF_UFFD_UNMAP(pCtx);
2917 : #endif
2918 663 : NCDF_ERR(status);
2919 663 : if (status != NC_NOERR)
2920 0 : eErr = CE_Failure;
2921 : }
2922 :
2923 1149 : if (fpVSIMEM)
2924 15 : VSIFCloseL(fpVSIMEM);
2925 :
2926 : #ifdef ENABLE_NCDUMP
2927 1149 : if (bFileToDestroyAtClosing)
2928 0 : VSIUnlink(osFilename);
2929 : #endif
2930 :
2931 1149 : if (GDALPamDataset::Close() != CE_None)
2932 0 : eErr = CE_Failure;
2933 : }
2934 1956 : return eErr;
2935 : }
2936 :
2937 : /************************************************************************/
2938 : /* SetDefineMode() */
2939 : /************************************************************************/
2940 14233 : bool netCDFDataset::SetDefineMode(bool bNewDefineMode)
2941 : {
2942 : // Do nothing if already in new define mode
2943 : // or if dataset is in read-only mode or if dataset is true NC4 dataset.
2944 14792 : if (bDefineMode == bNewDefineMode || GetAccess() == GA_ReadOnly ||
2945 559 : eFormat == NCDF_FORMAT_NC4)
2946 13821 : return true;
2947 :
2948 412 : CPLDebug("GDAL_netCDF", "SetDefineMode(%d) old=%d",
2949 412 : static_cast<int>(bNewDefineMode), static_cast<int>(bDefineMode));
2950 :
2951 412 : bDefineMode = bNewDefineMode;
2952 :
2953 : int status;
2954 412 : if (bDefineMode)
2955 143 : status = nc_redef(cdfid);
2956 : else
2957 269 : status = nc_enddef(cdfid);
2958 :
2959 412 : NCDF_ERR(status);
2960 412 : return status == NC_NOERR;
2961 : }
2962 :
2963 : /************************************************************************/
2964 : /* GetMetadataDomainList() */
2965 : /************************************************************************/
2966 :
2967 27 : char **netCDFDataset::GetMetadataDomainList()
2968 : {
2969 27 : char **papszDomains = BuildMetadataDomainList(
2970 : GDALDataset::GetMetadataDomainList(), TRUE, "SUBDATASETS", nullptr);
2971 28 : for (const auto &kv : m_oMapDomainToJSon)
2972 1 : papszDomains = CSLAddString(papszDomains, ("json:" + kv.first).c_str());
2973 27 : return papszDomains;
2974 : }
2975 :
2976 : /************************************************************************/
2977 : /* GetMetadata() */
2978 : /************************************************************************/
2979 369 : char **netCDFDataset::GetMetadata(const char *pszDomain)
2980 : {
2981 369 : if (pszDomain != nullptr && STARTS_WITH_CI(pszDomain, "SUBDATASETS"))
2982 39 : return papszSubDatasets;
2983 :
2984 330 : if (pszDomain != nullptr && STARTS_WITH(pszDomain, "json:"))
2985 : {
2986 1 : auto iter = m_oMapDomainToJSon.find(pszDomain + strlen("json:"));
2987 1 : if (iter != m_oMapDomainToJSon.end())
2988 1 : return iter->second.List();
2989 : }
2990 :
2991 329 : return GDALDataset::GetMetadata(pszDomain);
2992 : }
2993 :
2994 : /************************************************************************/
2995 : /* SetMetadataItem() */
2996 : /************************************************************************/
2997 :
2998 43 : CPLErr netCDFDataset::SetMetadataItem(const char *pszName, const char *pszValue,
2999 : const char *pszDomain)
3000 : {
3001 85 : if (GetAccess() == GA_Update &&
3002 85 : (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)
3003 : {
3004 42 : std::string osName(pszName);
3005 :
3006 : // Same logic as in CopyMetadata()
3007 42 : if (STARTS_WITH(osName.c_str(), "NC_GLOBAL#"))
3008 8 : osName = osName.substr(strlen("NC_GLOBAL#"));
3009 34 : else if (strchr(osName.c_str(), '#') == nullptr)
3010 5 : osName = "GDAL_" + osName;
3011 :
3012 84 : if (STARTS_WITH(osName.c_str(), "NETCDF_DIM_") ||
3013 42 : strchr(osName.c_str(), '#') != nullptr)
3014 : {
3015 : // do nothing
3016 29 : return CE_None;
3017 : }
3018 : else
3019 : {
3020 13 : SetDefineMode(true);
3021 :
3022 13 : if (!NCDFPutAttr(cdfid, NC_GLOBAL, osName.c_str(), pszValue))
3023 13 : return CE_Failure;
3024 : }
3025 : }
3026 :
3027 1 : return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);
3028 : }
3029 :
3030 : /************************************************************************/
3031 : /* SetMetadata() */
3032 : /************************************************************************/
3033 :
3034 8 : CPLErr netCDFDataset::SetMetadata(char **papszMD, const char *pszDomain)
3035 : {
3036 13 : if (GetAccess() == GA_Update &&
3037 5 : (pszDomain == nullptr || pszDomain[0] == '\0'))
3038 : {
3039 : // We don't handle metadata item removal for now
3040 50 : for (const char *const *papszIter = papszMD; papszIter && *papszIter;
3041 : ++papszIter)
3042 : {
3043 42 : char *pszName = nullptr;
3044 42 : const char *pszValue = CPLParseNameValue(*papszIter, &pszName);
3045 42 : if (pszName && pszValue)
3046 42 : SetMetadataItem(pszName, pszValue);
3047 42 : CPLFree(pszName);
3048 : }
3049 8 : return CE_None;
3050 : }
3051 0 : return GDALPamDataset::SetMetadata(papszMD, pszDomain);
3052 : }
3053 :
3054 : /************************************************************************/
3055 : /* GetSpatialRef() */
3056 : /************************************************************************/
3057 :
3058 203 : const OGRSpatialReference *netCDFDataset::GetSpatialRef() const
3059 : {
3060 203 : if (m_bHasProjection)
3061 77 : return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
3062 :
3063 126 : return GDALPamDataset::GetSpatialRef();
3064 : }
3065 :
3066 : /************************************************************************/
3067 : /* FetchCopyParam() */
3068 : /************************************************************************/
3069 :
3070 444 : double netCDFDataset::FetchCopyParam(const char *pszGridMappingValue,
3071 : const char *pszParam, double dfDefault,
3072 : bool *pbFound)
3073 :
3074 : {
3075 : char *pszTemp =
3076 444 : CPLStrdup(CPLSPrintf("%s#%s", pszGridMappingValue, pszParam));
3077 444 : const char *pszValue = CSLFetchNameValue(papszMetadata, pszTemp);
3078 444 : CPLFree(pszTemp);
3079 :
3080 444 : if (pbFound)
3081 : {
3082 444 : *pbFound = (pszValue != nullptr);
3083 : }
3084 :
3085 444 : if (pszValue)
3086 : {
3087 0 : return CPLAtofM(pszValue);
3088 : }
3089 :
3090 444 : return dfDefault;
3091 : }
3092 :
3093 : /************************************************************************/
3094 : /* FetchStandardParallels() */
3095 : /************************************************************************/
3096 :
3097 : std::vector<std::string>
3098 0 : netCDFDataset::FetchStandardParallels(const char *pszGridMappingValue)
3099 : {
3100 : // cf-1.0 tags
3101 0 : const char *pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL);
3102 :
3103 0 : std::vector<std::string> ret;
3104 0 : if (pszValue != nullptr)
3105 : {
3106 0 : CPLStringList aosValues;
3107 0 : if (pszValue[0] != '{' &&
3108 0 : CPLString(pszValue).Trim().find(' ') != std::string::npos)
3109 : {
3110 : // Some files like
3111 : // ftp://data.knmi.nl/download/KNW-NetCDF-3D/1.0/noversion/2013/11/14/KNW-1.0_H37-ERA_NL_20131114.nc
3112 : // do not use standard formatting for arrays, but just space
3113 : // separated syntax
3114 0 : aosValues = CSLTokenizeString2(pszValue, " ", 0);
3115 : }
3116 : else
3117 : {
3118 0 : aosValues = NCDFTokenizeArray(pszValue);
3119 : }
3120 0 : for (int i = 0; i < aosValues.size(); i++)
3121 : {
3122 0 : ret.push_back(aosValues[i]);
3123 : }
3124 : }
3125 : // Try gdal tags.
3126 : else
3127 : {
3128 0 : pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_1);
3129 :
3130 0 : if (pszValue != nullptr)
3131 0 : ret.push_back(pszValue);
3132 :
3133 0 : pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_2);
3134 :
3135 0 : if (pszValue != nullptr)
3136 0 : ret.push_back(pszValue);
3137 : }
3138 :
3139 0 : return ret;
3140 : }
3141 :
3142 : /************************************************************************/
3143 : /* FetchAttr() */
3144 : /************************************************************************/
3145 :
3146 3815 : const char *netCDFDataset::FetchAttr(const char *pszVarFullName,
3147 : const char *pszAttr)
3148 :
3149 : {
3150 3815 : char *pszKey = CPLStrdup(CPLSPrintf("%s#%s", pszVarFullName, pszAttr));
3151 3815 : const char *pszValue = CSLFetchNameValue(papszMetadata, pszKey);
3152 3815 : CPLFree(pszKey);
3153 3815 : return pszValue;
3154 : }
3155 :
3156 2514 : const char *netCDFDataset::FetchAttr(int nGroupId, int nVarId,
3157 : const char *pszAttr)
3158 :
3159 : {
3160 2514 : char *pszVarFullName = nullptr;
3161 2514 : NCDFGetVarFullName(nGroupId, nVarId, &pszVarFullName);
3162 2514 : const char *pszValue = FetchAttr(pszVarFullName, pszAttr);
3163 2514 : CPLFree(pszVarFullName);
3164 2514 : return pszValue;
3165 : }
3166 :
3167 : /************************************************************************/
3168 : /* IsDifferenceBelow() */
3169 : /************************************************************************/
3170 :
3171 1097 : static bool IsDifferenceBelow(double dfA, double dfB, double dfError)
3172 : {
3173 1097 : const double dfAbsDiff = fabs(dfA - dfB);
3174 1097 : return dfAbsDiff <= dfError;
3175 : }
3176 :
3177 : /************************************************************************/
3178 : /* SetProjectionFromVar() */
3179 : /************************************************************************/
3180 537 : void netCDFDataset::SetProjectionFromVar(
3181 : int nGroupId, int nVarId, bool bReadSRSOnly, const char *pszGivenGM,
3182 : std::string *returnProjStr, nccfdriver::SGeometry_Reader *sg,
3183 : std::vector<std::string> *paosRemovedMDItems)
3184 : {
3185 537 : bool bGotGeogCS = false;
3186 537 : bool bGotCfSRS = false;
3187 537 : bool bGotCfWktSRS = false;
3188 537 : bool bGotGdalSRS = false;
3189 537 : bool bGotCfGT = false;
3190 537 : bool bGotGdalGT = false;
3191 :
3192 : // These values from CF metadata.
3193 537 : OGRSpatialReference oSRS;
3194 537 : oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3195 537 : size_t xdim = nRasterXSize;
3196 537 : size_t ydim = nRasterYSize;
3197 :
3198 : // These values from GDAL metadata.
3199 537 : const char *pszWKT = nullptr;
3200 537 : const char *pszGeoTransform = nullptr;
3201 :
3202 537 : netCDFDataset *poDS = this; // Perhaps this should be removed for clarity.
3203 :
3204 537 : CPLDebug("GDAL_netCDF", "\n=====\nSetProjectionFromVar( %d, %d)", nGroupId,
3205 : nVarId);
3206 :
3207 : // Get x/y range information.
3208 :
3209 : // Temp variables to use in SetGeoTransform() and SetProjection().
3210 537 : GDALGeoTransform tmpGT;
3211 :
3212 : // Look for grid_mapping metadata.
3213 537 : const char *pszValue = pszGivenGM;
3214 537 : CPLString osTmpGridMapping; // let is in this outer scope as pszValue may
3215 : // point to it
3216 537 : if (pszValue == nullptr)
3217 : {
3218 494 : pszValue = FetchAttr(nGroupId, nVarId, CF_GRD_MAPPING);
3219 494 : if (pszValue && strchr(pszValue, ':') && strchr(pszValue, ' '))
3220 : {
3221 : // Expanded form of grid_mapping
3222 : // e.g. "crsOSGB: x y crsWGS84: lat lon"
3223 : // Pickup the grid_mapping whose coordinates are dimensions of the
3224 : // variable
3225 6 : CPLStringList aosTokens(CSLTokenizeString2(pszValue, " ", 0));
3226 3 : if ((aosTokens.size() % 3) == 0)
3227 : {
3228 3 : for (int i = 0; i < aosTokens.size() / 3; i++)
3229 : {
3230 3 : if (CSLFindString(poDS->papszDimName,
3231 9 : aosTokens[3 * i + 1]) >= 0 &&
3232 3 : CSLFindString(poDS->papszDimName,
3233 3 : aosTokens[3 * i + 2]) >= 0)
3234 : {
3235 3 : osTmpGridMapping = aosTokens[3 * i];
3236 6 : if (!osTmpGridMapping.empty() &&
3237 3 : osTmpGridMapping.back() == ':')
3238 : {
3239 3 : osTmpGridMapping.resize(osTmpGridMapping.size() -
3240 : 1);
3241 : }
3242 3 : pszValue = osTmpGridMapping.c_str();
3243 3 : break;
3244 : }
3245 : }
3246 : }
3247 : }
3248 : }
3249 537 : char *pszGridMappingValue = CPLStrdup(pszValue ? pszValue : "");
3250 :
3251 537 : if (!EQUAL(pszGridMappingValue, ""))
3252 : {
3253 : // Read grid_mapping metadata.
3254 224 : int nProjGroupID = -1;
3255 224 : int nProjVarID = -1;
3256 224 : if (NCDFResolveVar(nGroupId, pszGridMappingValue, &nProjGroupID,
3257 224 : &nProjVarID) == CE_None)
3258 : {
3259 223 : poDS->ReadAttributes(nProjGroupID, nProjVarID);
3260 :
3261 : // Look for GDAL spatial_ref and GeoTransform within grid_mapping.
3262 223 : CPLFree(pszGridMappingValue);
3263 223 : pszGridMappingValue = nullptr;
3264 223 : NCDFGetVarFullName(nProjGroupID, nProjVarID, &pszGridMappingValue);
3265 223 : if (pszGridMappingValue)
3266 : {
3267 223 : CPLDebug("GDAL_netCDF", "got grid_mapping %s",
3268 : pszGridMappingValue);
3269 223 : pszWKT = FetchAttr(pszGridMappingValue, NCDF_SPATIAL_REF);
3270 223 : if (!pszWKT)
3271 : {
3272 35 : pszWKT = FetchAttr(pszGridMappingValue, NCDF_CRS_WKT);
3273 : }
3274 : else
3275 : {
3276 188 : bGotGdalSRS = true;
3277 188 : CPLDebug("GDAL_netCDF", "setting WKT from GDAL");
3278 : }
3279 223 : if (pszWKT)
3280 : {
3281 193 : if (!bGotGdalSRS)
3282 : {
3283 5 : bGotCfWktSRS = true;
3284 5 : CPLDebug("GDAL_netCDF", "setting WKT from CF");
3285 : }
3286 193 : if (returnProjStr != nullptr)
3287 : {
3288 41 : (*returnProjStr) = std::string(pszWKT);
3289 : }
3290 : else
3291 : {
3292 152 : m_bAddedProjectionVarsDefs = true;
3293 152 : m_bAddedProjectionVarsData = true;
3294 304 : OGRSpatialReference oSRSTmp;
3295 152 : oSRSTmp.SetAxisMappingStrategy(
3296 : OAMS_TRADITIONAL_GIS_ORDER);
3297 152 : oSRSTmp.importFromWkt(pszWKT);
3298 152 : SetSpatialRefNoUpdate(&oSRSTmp);
3299 : }
3300 : pszGeoTransform =
3301 193 : FetchAttr(pszGridMappingValue, NCDF_GEOTRANSFORM);
3302 : }
3303 : }
3304 : else
3305 : {
3306 0 : pszGridMappingValue = CPLStrdup("");
3307 : }
3308 : }
3309 : }
3310 :
3311 : // Get information about the file.
3312 : //
3313 : // Was this file created by the GDAL netcdf driver?
3314 : // Was this file created by the newer (CF-conformant) driver?
3315 : //
3316 : // 1) If GDAL netcdf metadata is set, and version >= 1.9,
3317 : // it was created with the new driver
3318 : // 2) Else, if spatial_ref and GeoTransform are present in the
3319 : // grid_mapping variable, it was created by the old driver
3320 537 : pszValue = FetchAttr("NC_GLOBAL", "GDAL");
3321 :
3322 537 : if (pszValue && NCDFIsGDALVersionGTE(pszValue, 1900))
3323 : {
3324 242 : bIsGdalFile = true;
3325 242 : bIsGdalCfFile = true;
3326 : }
3327 295 : else if (pszWKT != nullptr && pszGeoTransform != nullptr)
3328 : {
3329 20 : bIsGdalFile = true;
3330 20 : bIsGdalCfFile = false;
3331 : }
3332 :
3333 : // Set default bottom-up default value.
3334 : // Y axis dimension and absence of GT can modify this value.
3335 : // Override with Config option GDAL_NETCDF_BOTTOMUP.
3336 :
3337 : // New driver is bottom-up by default.
3338 537 : if ((bIsGdalFile && !bIsGdalCfFile) || bSwitchedXY)
3339 22 : poDS->bBottomUp = false;
3340 : else
3341 515 : poDS->bBottomUp = true;
3342 :
3343 537 : CPLDebug("GDAL_netCDF",
3344 : "bIsGdalFile=%d bIsGdalCfFile=%d bSwitchedXY=%d bBottomUp=%d",
3345 537 : static_cast<int>(bIsGdalFile), static_cast<int>(bIsGdalCfFile),
3346 537 : static_cast<int>(bSwitchedXY), static_cast<int>(bBottomUp));
3347 :
3348 : // Read projection coordinates.
3349 :
3350 537 : int nGroupDimXID = -1;
3351 537 : int nVarDimXID = -1;
3352 537 : int nGroupDimYID = -1;
3353 537 : int nVarDimYID = -1;
3354 537 : if (sg != nullptr)
3355 : {
3356 43 : nGroupDimXID = sg->get_ncID();
3357 43 : nGroupDimYID = sg->get_ncID();
3358 43 : nVarDimXID = sg->getNodeCoordVars()[0];
3359 43 : nVarDimYID = sg->getNodeCoordVars()[1];
3360 : }
3361 :
3362 537 : if (!bReadSRSOnly)
3363 : {
3364 352 : NCDFResolveVar(nGroupId, poDS->papszDimName[nXDimID], &nGroupDimXID,
3365 : &nVarDimXID);
3366 352 : NCDFResolveVar(nGroupId, poDS->papszDimName[nYDimID], &nGroupDimYID,
3367 : &nVarDimYID);
3368 : // TODO: if above resolving fails we should also search for coordinate
3369 : // variables without same name than dimension using the same resolving
3370 : // logic. This should handle for example NASA Ocean Color L2 products.
3371 :
3372 : const bool bIgnoreXYAxisNameChecks =
3373 704 : CPLTestBool(CSLFetchNameValueDef(
3374 352 : papszOpenOptions, "IGNORE_XY_AXIS_NAME_CHECKS",
3375 : CPLGetConfigOption("GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS",
3376 352 : "NO"))) ||
3377 : // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a res
3378 : // and transform attributes
3379 352 : (FetchAttr(nGroupId, nVarId, "res") != nullptr &&
3380 704 : FetchAttr(nGroupId, nVarId, "transform") != nullptr) ||
3381 351 : FetchAttr(nGroupId, NC_GLOBAL, "GMT_version") != nullptr;
3382 :
3383 : // Check that they are 1D or 2D variables
3384 352 : if (nVarDimXID >= 0)
3385 : {
3386 256 : int ndims = -1;
3387 256 : nc_inq_varndims(nGroupId, nVarDimXID, &ndims);
3388 256 : if (ndims == 0 || ndims > 2)
3389 0 : nVarDimXID = -1;
3390 256 : else if (!bIgnoreXYAxisNameChecks)
3391 : {
3392 254 : if (!NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
3393 164 : !NCDFIsVarProjectionX(nGroupId, nVarDimXID, nullptr) &&
3394 : // In case of inversion of X/Y
3395 450 : !NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr) &&
3396 32 : !NCDFIsVarProjectionY(nGroupId, nVarDimXID, nullptr))
3397 : {
3398 : char szVarNameX[NC_MAX_NAME + 1];
3399 32 : CPL_IGNORE_RET_VAL(
3400 32 : nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
3401 32 : if (!(ndims == 1 &&
3402 31 : (EQUAL(szVarNameX, CF_LONGITUDE_STD_NAME) ||
3403 30 : EQUAL(szVarNameX, CF_LONGITUDE_VAR_NAME))))
3404 : {
3405 31 : CPLDebug(
3406 : "netCDF",
3407 : "Georeferencing ignored due to non-specific "
3408 : "enough X axis name. "
3409 : "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
3410 : "as configuration option to bypass this check");
3411 31 : nVarDimXID = -1;
3412 : }
3413 : }
3414 : }
3415 : }
3416 :
3417 352 : if (nVarDimYID >= 0)
3418 : {
3419 258 : int ndims = -1;
3420 258 : nc_inq_varndims(nGroupId, nVarDimYID, &ndims);
3421 258 : if (ndims == 0 || ndims > 2)
3422 1 : nVarDimYID = -1;
3423 257 : else if (!bIgnoreXYAxisNameChecks)
3424 : {
3425 255 : if (!NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr) &&
3426 165 : !NCDFIsVarProjectionY(nGroupId, nVarDimYID, nullptr) &&
3427 : // In case of inversion of X/Y
3428 453 : !NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
3429 33 : !NCDFIsVarProjectionX(nGroupId, nVarDimYID, nullptr))
3430 : {
3431 : char szVarNameY[NC_MAX_NAME + 1];
3432 33 : CPL_IGNORE_RET_VAL(
3433 33 : nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
3434 33 : if (!(ndims == 1 &&
3435 33 : (EQUAL(szVarNameY, CF_LATITUDE_STD_NAME) ||
3436 32 : EQUAL(szVarNameY, CF_LATITUDE_VAR_NAME))))
3437 : {
3438 32 : CPLDebug(
3439 : "netCDF",
3440 : "Georeferencing ignored due to non-specific "
3441 : "enough Y axis name. "
3442 : "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
3443 : "as configuration option to bypass this check");
3444 32 : nVarDimYID = -1;
3445 : }
3446 : }
3447 : }
3448 : }
3449 :
3450 352 : if ((nVarDimXID >= 0 && xdim == 1) || (nVarDimXID >= 0 && ydim == 1))
3451 : {
3452 0 : CPLError(CE_Warning, CPLE_AppDefined,
3453 : "1-pixel width/height files not supported, "
3454 : "xdim: %ld ydim: %ld",
3455 : static_cast<long>(xdim), static_cast<long>(ydim));
3456 0 : nVarDimXID = -1;
3457 0 : nVarDimYID = -1;
3458 : }
3459 : }
3460 :
3461 537 : const char *pszUnits = nullptr;
3462 537 : if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
3463 : {
3464 268 : const char *pszUnitsX = FetchAttr(nGroupDimXID, nVarDimXID, "units");
3465 268 : const char *pszUnitsY = FetchAttr(nGroupDimYID, nVarDimYID, "units");
3466 : // Normalize degrees_east/degrees_north to degrees
3467 : // Cf https://github.com/OSGeo/gdal/issues/11009
3468 268 : if (pszUnitsX && EQUAL(pszUnitsX, "degrees_east"))
3469 79 : pszUnitsX = "degrees";
3470 268 : if (pszUnitsY && EQUAL(pszUnitsY, "degrees_north"))
3471 79 : pszUnitsY = "degrees";
3472 :
3473 268 : if (pszUnitsX && pszUnitsY)
3474 : {
3475 221 : if (EQUAL(pszUnitsX, pszUnitsY))
3476 218 : pszUnits = pszUnitsX;
3477 3 : else if (!pszWKT && !EQUAL(pszGridMappingValue, ""))
3478 : {
3479 0 : CPLError(CE_Failure, CPLE_AppDefined,
3480 : "X axis unit (%s) is different from Y axis "
3481 : "unit (%s). SRS will ignore axis unit and be "
3482 : "likely wrong.",
3483 : pszUnitsX, pszUnitsY);
3484 : }
3485 : }
3486 47 : else if (pszUnitsX && !pszWKT && !EQUAL(pszGridMappingValue, ""))
3487 : {
3488 0 : CPLError(CE_Failure, CPLE_AppDefined,
3489 : "X axis unit is defined, but not Y one ."
3490 : "SRS will ignore axis unit and be likely wrong.");
3491 : }
3492 47 : else if (pszUnitsY && !pszWKT && !EQUAL(pszGridMappingValue, ""))
3493 : {
3494 0 : CPLError(CE_Failure, CPLE_AppDefined,
3495 : "Y axis unit is defined, but not X one ."
3496 : "SRS will ignore axis unit and be likely wrong.");
3497 : }
3498 : }
3499 :
3500 537 : if (!pszWKT && !EQUAL(pszGridMappingValue, ""))
3501 : {
3502 31 : CPLStringList aosGridMappingKeyValues;
3503 31 : const size_t nLenGridMappingValue = strlen(pszGridMappingValue);
3504 789 : for (const char *const *papszIter = papszMetadata;
3505 789 : papszIter && *papszIter; ++papszIter)
3506 : {
3507 758 : if (STARTS_WITH(*papszIter, pszGridMappingValue) &&
3508 236 : (*papszIter)[nLenGridMappingValue] == '#')
3509 : {
3510 236 : char *pszKey = nullptr;
3511 472 : pszValue = CPLParseNameValue(
3512 236 : *papszIter + nLenGridMappingValue + 1, &pszKey);
3513 236 : if (pszKey && pszValue)
3514 236 : aosGridMappingKeyValues.SetNameValue(pszKey, pszValue);
3515 236 : CPLFree(pszKey);
3516 : }
3517 : }
3518 :
3519 31 : bGotGeogCS = aosGridMappingKeyValues.FetchNameValue(
3520 : CF_PP_SEMI_MAJOR_AXIS) != nullptr;
3521 :
3522 31 : oSRS.importFromCF1(aosGridMappingKeyValues.List(), pszUnits);
3523 31 : bGotCfSRS = oSRS.IsGeographic() || oSRS.IsProjected();
3524 : }
3525 : else
3526 : {
3527 : // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a "crs"
3528 : // attribute hold on the variable of interest that contains a PROJ.4
3529 : // string
3530 506 : pszValue = FetchAttr(nGroupId, nVarId, "crs");
3531 507 : if (pszValue &&
3532 1 : (strstr(pszValue, "+proj=") != nullptr ||
3533 0 : strstr(pszValue, "GEOGCS") != nullptr ||
3534 0 : strstr(pszValue, "PROJCS") != nullptr ||
3535 507 : strstr(pszValue, "EPSG:") != nullptr) &&
3536 1 : oSRS.SetFromUserInput(pszValue) == OGRERR_NONE)
3537 : {
3538 1 : bGotCfSRS = true;
3539 : }
3540 : }
3541 :
3542 : // Set Projection from CF.
3543 537 : double dfLinearUnitsConvFactor = 1.0;
3544 537 : if ((bGotGeogCS || bGotCfSRS))
3545 : {
3546 31 : if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
3547 : {
3548 : // Set SRS Units.
3549 :
3550 : // Check units for x and y.
3551 28 : if (oSRS.IsProjected())
3552 : {
3553 25 : dfLinearUnitsConvFactor = oSRS.GetLinearUnits(nullptr);
3554 :
3555 : // If the user doesn't ask to preserve the axis unit,
3556 : // then normalize to metre
3557 31 : if (dfLinearUnitsConvFactor != 1.0 &&
3558 6 : !CPLFetchBool(GetOpenOptions(), "PRESERVE_AXIS_UNIT_IN_CRS",
3559 : false))
3560 : {
3561 5 : oSRS.SetLinearUnits("metre", 1.0);
3562 5 : oSRS.SetAuthority("PROJCS|UNIT", "EPSG", 9001);
3563 : }
3564 : else
3565 : {
3566 20 : dfLinearUnitsConvFactor = 1.0;
3567 : }
3568 : }
3569 : }
3570 :
3571 : // Set projection.
3572 31 : char *pszTempProjection = nullptr;
3573 31 : oSRS.exportToWkt(&pszTempProjection);
3574 31 : if (pszTempProjection)
3575 : {
3576 31 : CPLDebug("GDAL_netCDF", "setting WKT from CF");
3577 31 : if (returnProjStr != nullptr)
3578 : {
3579 2 : (*returnProjStr) = std::string(pszTempProjection);
3580 : }
3581 : else
3582 : {
3583 29 : m_bAddedProjectionVarsDefs = true;
3584 29 : m_bAddedProjectionVarsData = true;
3585 29 : SetSpatialRefNoUpdate(&oSRS);
3586 : }
3587 : }
3588 31 : CPLFree(pszTempProjection);
3589 : }
3590 :
3591 537 : if (!bReadSRSOnly && (nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 &&
3592 : ydim > 0)
3593 : {
3594 : double *pdfXCoord =
3595 225 : static_cast<double *>(CPLCalloc(xdim, sizeof(double)));
3596 : double *pdfYCoord =
3597 225 : static_cast<double *>(CPLCalloc(ydim, sizeof(double)));
3598 :
3599 225 : size_t start[2] = {0, 0};
3600 225 : size_t edge[2] = {xdim, 0};
3601 225 : int status = nc_get_vara_double(nGroupDimXID, nVarDimXID, start, edge,
3602 : pdfXCoord);
3603 225 : NCDF_ERR(status);
3604 :
3605 225 : edge[0] = ydim;
3606 225 : status = nc_get_vara_double(nGroupDimYID, nVarDimYID, start, edge,
3607 : pdfYCoord);
3608 225 : NCDF_ERR(status);
3609 :
3610 225 : nc_type nc_var_dimx_datatype = NC_NAT;
3611 : status =
3612 225 : nc_inq_vartype(nGroupDimXID, nVarDimXID, &nc_var_dimx_datatype);
3613 225 : NCDF_ERR(status);
3614 :
3615 225 : nc_type nc_var_dimy_datatype = NC_NAT;
3616 : status =
3617 225 : nc_inq_vartype(nGroupDimYID, nVarDimYID, &nc_var_dimy_datatype);
3618 225 : NCDF_ERR(status);
3619 :
3620 225 : if (!poDS->bSwitchedXY)
3621 : {
3622 : // Convert ]180,540] longitude values to ]-180,0].
3623 313 : if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
3624 90 : CPLTestBool(
3625 : CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")))
3626 : {
3627 : // If minimum longitude is > 180, subtract 360 from all.
3628 : // Add a check on the maximum X value too, since
3629 : // NCDFIsVarLongitude() is not very specific by default (see
3630 : // https://github.com/OSGeo/gdal/issues/1440)
3631 97 : if (std::min(pdfXCoord[0], pdfXCoord[xdim - 1]) > 180.0 &&
3632 7 : std::max(pdfXCoord[0], pdfXCoord[xdim - 1]) <= 540)
3633 : {
3634 0 : CPLDebug(
3635 : "GDAL_netCDF",
3636 : "Offsetting longitudes from ]180,540] to ]-180,180]. "
3637 : "Can be disabled with GDAL_NETCDF_CENTERLONG_180=NO");
3638 0 : for (size_t i = 0; i < xdim; i++)
3639 0 : pdfXCoord[i] -= 360;
3640 : }
3641 : }
3642 : }
3643 :
3644 : // Is pixel spacing uniform across the map?
3645 :
3646 : // Check Longitude.
3647 :
3648 225 : bool bLonSpacingOK = false;
3649 225 : if (xdim == 2)
3650 : {
3651 28 : bLonSpacingOK = true;
3652 : }
3653 : else
3654 : {
3655 197 : bool bWestIsLeft = (pdfXCoord[0] < pdfXCoord[xdim - 1]);
3656 :
3657 : // fix longitudes if longitudes should increase from
3658 : // west to east, but west > east
3659 277 : if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
3660 80 : !bWestIsLeft)
3661 : {
3662 2 : size_t ndecreases = 0;
3663 :
3664 : // there is lon wrap if longitudes increase
3665 : // with one single decrease
3666 107 : for (size_t i = 1; i < xdim; i++)
3667 : {
3668 105 : if (pdfXCoord[i] < pdfXCoord[i - 1])
3669 1 : ndecreases++;
3670 : }
3671 :
3672 2 : if (ndecreases == 1)
3673 : {
3674 1 : CPLDebug("GDAL_netCDF", "longitude wrap detected");
3675 4 : for (size_t i = 0; i < xdim; i++)
3676 : {
3677 3 : if (pdfXCoord[i] > pdfXCoord[xdim - 1])
3678 1 : pdfXCoord[i] -= 360;
3679 : }
3680 : }
3681 : }
3682 :
3683 197 : const double dfSpacingBegin = pdfXCoord[1] - pdfXCoord[0];
3684 197 : const double dfSpacingMiddle =
3685 197 : pdfXCoord[xdim / 2 + 1] - pdfXCoord[xdim / 2];
3686 197 : const double dfSpacingLast =
3687 197 : pdfXCoord[xdim - 1] - pdfXCoord[xdim - 2];
3688 :
3689 197 : CPLDebug("GDAL_netCDF",
3690 : "xdim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
3691 : "dfSpacingLast: %f",
3692 : static_cast<long>(xdim), dfSpacingBegin, dfSpacingMiddle,
3693 : dfSpacingLast);
3694 : #ifdef NCDF_DEBUG
3695 : CPLDebug("GDAL_netCDF", "xcoords: %f %f %f %f %f %f", pdfXCoord[0],
3696 : pdfXCoord[1], pdfXCoord[xdim / 2],
3697 : pdfXCoord[(xdim / 2) + 1], pdfXCoord[xdim - 2],
3698 : pdfXCoord[xdim - 1]);
3699 : #endif
3700 :
3701 : // ftp://ftp.cdc.noaa.gov/Datasets/NARR/Dailies/monolevel/vwnd.10m.2015.nc
3702 : // requires a 0.02% tolerance, so let's settle for 0.05%
3703 :
3704 : // For float variables, increase to 0.2% (as seen in
3705 : // https://github.com/OSGeo/gdal/issues/3663)
3706 197 : const double dfEpsRel =
3707 197 : nc_var_dimx_datatype == NC_FLOAT ? 0.002 : 0.0005;
3708 :
3709 : const double dfEps =
3710 : dfEpsRel *
3711 394 : std::max(fabs(dfSpacingBegin),
3712 197 : std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
3713 388 : if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
3714 388 : IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
3715 191 : IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
3716 : {
3717 191 : bLonSpacingOK = true;
3718 : }
3719 6 : else if (CPLTestBool(CPLGetConfigOption(
3720 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
3721 : {
3722 0 : bLonSpacingOK = true;
3723 0 : CPLDebug(
3724 : "GDAL_netCDF",
3725 : "Longitude/X is not equally spaced, but will be considered "
3726 : "as such because of "
3727 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
3728 : }
3729 : }
3730 :
3731 225 : if (bLonSpacingOK == false)
3732 : {
3733 6 : CPLDebug(
3734 : "GDAL_netCDF", "%s",
3735 : "Longitude/X is not equally spaced (with a 0.05% tolerance). "
3736 : "You may set the "
3737 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
3738 : "option to YES to ignore this check");
3739 : }
3740 :
3741 : // Check Latitude.
3742 225 : bool bLatSpacingOK = false;
3743 :
3744 225 : if (ydim == 2)
3745 : {
3746 48 : bLatSpacingOK = true;
3747 : }
3748 : else
3749 : {
3750 177 : const double dfSpacingBegin = pdfYCoord[1] - pdfYCoord[0];
3751 177 : const double dfSpacingMiddle =
3752 177 : pdfYCoord[ydim / 2 + 1] - pdfYCoord[ydim / 2];
3753 :
3754 177 : const double dfSpacingLast =
3755 177 : pdfYCoord[ydim - 1] - pdfYCoord[ydim - 2];
3756 :
3757 177 : CPLDebug("GDAL_netCDF",
3758 : "ydim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
3759 : "dfSpacingLast: %f",
3760 : (long)ydim, dfSpacingBegin, dfSpacingMiddle,
3761 : dfSpacingLast);
3762 : #ifdef NCDF_DEBUG
3763 : CPLDebug("GDAL_netCDF", "ycoords: %f %f %f %f %f %f", pdfYCoord[0],
3764 : pdfYCoord[1], pdfYCoord[ydim / 2],
3765 : pdfYCoord[(ydim / 2) + 1], pdfYCoord[ydim - 2],
3766 : pdfYCoord[ydim - 1]);
3767 : #endif
3768 :
3769 177 : const double dfEpsRel =
3770 177 : nc_var_dimy_datatype == NC_FLOAT ? 0.002 : 0.0005;
3771 :
3772 : const double dfEps =
3773 : dfEpsRel *
3774 354 : std::max(fabs(dfSpacingBegin),
3775 177 : std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
3776 352 : if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
3777 352 : IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
3778 166 : IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
3779 : {
3780 166 : bLatSpacingOK = true;
3781 : }
3782 11 : else if (CPLTestBool(CPLGetConfigOption(
3783 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
3784 : {
3785 0 : bLatSpacingOK = true;
3786 0 : CPLDebug(
3787 : "GDAL_netCDF",
3788 : "Latitude/Y is not equally spaced, but will be considered "
3789 : "as such because of "
3790 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
3791 : }
3792 11 : else if (!oSRS.IsProjected() &&
3793 11 : fabs(dfSpacingBegin - dfSpacingLast) <= 0.1 &&
3794 30 : fabs(dfSpacingBegin - dfSpacingMiddle) <= 0.1 &&
3795 8 : fabs(dfSpacingMiddle - dfSpacingLast) <= 0.1)
3796 : {
3797 8 : bLatSpacingOK = true;
3798 8 : CPLError(CE_Warning, CPLE_AppDefined,
3799 : "Latitude grid not spaced evenly. "
3800 : "Setting projection for grid spacing is "
3801 : "within 0.1 degrees threshold.");
3802 :
3803 8 : CPLDebug("GDAL_netCDF",
3804 : "Latitude grid not spaced evenly, but within 0.1 "
3805 : "degree threshold (probably a Gaussian grid). "
3806 : "Saving original latitude values in Y_VALUES "
3807 : "geolocation metadata");
3808 8 : Set1DGeolocation(nGroupDimYID, nVarDimYID, "Y");
3809 : }
3810 :
3811 177 : if (bLatSpacingOK == false)
3812 : {
3813 3 : CPLDebug(
3814 : "GDAL_netCDF", "%s",
3815 : "Latitude/Y is not equally spaced (with a 0.05% "
3816 : "tolerance). "
3817 : "You may set the "
3818 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
3819 : "option to YES to ignore this check");
3820 : }
3821 : }
3822 :
3823 225 : if (bLonSpacingOK && bLatSpacingOK)
3824 : {
3825 : // We have gridded data so we can set the Georeferencing info.
3826 :
3827 : // Enable GeoTransform.
3828 :
3829 : // In the following "actual_range" and "node_offset"
3830 : // are attributes used by netCDF files created by GMT.
3831 : // If we find them we know how to proceed. Else, use
3832 : // the original algorithm.
3833 218 : bGotCfGT = true;
3834 :
3835 218 : int node_offset = 0;
3836 : const bool bUseActualRange =
3837 218 : NCDFResolveAttInt(nGroupId, NC_GLOBAL, "node_offset",
3838 218 : &node_offset) == CE_None;
3839 :
3840 218 : double adfActualRange[2] = {0.0, 0.0};
3841 218 : double xMinMax[2] = {0.0, 0.0};
3842 218 : double yMinMax[2] = {0.0, 0.0};
3843 :
3844 : const auto RoundMinMaxForFloatVals =
3845 60 : [](double &dfMin, double &dfMax, int nIntervals)
3846 : {
3847 : // Helps for a case where longitudes range from
3848 : // -179.99 to 180.0 with a 0.01 degree spacing.
3849 : // However as this is encoded in a float array,
3850 : // -179.99 is actually read as -179.99000549316406 as
3851 : // a double. Try to detect that and correct the rounding
3852 :
3853 88 : const auto IsAlmostInteger = [](double dfVal)
3854 : {
3855 88 : constexpr double THRESHOLD_INTEGER = 1e-3;
3856 88 : return std::fabs(dfVal - std::round(dfVal)) <=
3857 88 : THRESHOLD_INTEGER;
3858 : };
3859 :
3860 60 : const double dfSpacing = (dfMax - dfMin) / nIntervals;
3861 60 : if (dfSpacing > 0)
3862 : {
3863 48 : const double dfInvSpacing = 1.0 / dfSpacing;
3864 48 : if (IsAlmostInteger(dfInvSpacing))
3865 : {
3866 20 : const double dfRoundedSpacing =
3867 20 : 1.0 / std::round(dfInvSpacing);
3868 20 : const double dfMinDivRoundedSpacing =
3869 20 : dfMin / dfRoundedSpacing;
3870 20 : const double dfMaxDivRoundedSpacing =
3871 20 : dfMax / dfRoundedSpacing;
3872 40 : if (IsAlmostInteger(dfMinDivRoundedSpacing) &&
3873 20 : IsAlmostInteger(dfMaxDivRoundedSpacing))
3874 : {
3875 20 : const double dfRoundedMin =
3876 20 : std::round(dfMinDivRoundedSpacing) *
3877 : dfRoundedSpacing;
3878 20 : const double dfRoundedMax =
3879 20 : std::round(dfMaxDivRoundedSpacing) *
3880 : dfRoundedSpacing;
3881 20 : if (static_cast<float>(dfMin) ==
3882 20 : static_cast<float>(dfRoundedMin) &&
3883 8 : static_cast<float>(dfMax) ==
3884 8 : static_cast<float>(dfRoundedMax))
3885 : {
3886 7 : dfMin = dfRoundedMin;
3887 7 : dfMax = dfRoundedMax;
3888 : }
3889 : }
3890 : }
3891 : }
3892 60 : };
3893 :
3894 221 : if (bUseActualRange &&
3895 3 : !nc_get_att_double(nGroupDimXID, nVarDimXID, "actual_range",
3896 : adfActualRange))
3897 : {
3898 1 : xMinMax[0] = adfActualRange[0];
3899 1 : xMinMax[1] = adfActualRange[1];
3900 :
3901 : // Present xMinMax[] in the same order as padfXCoord
3902 1 : if ((xMinMax[0] - xMinMax[1]) *
3903 1 : (pdfXCoord[0] - pdfXCoord[xdim - 1]) <
3904 : 0)
3905 : {
3906 0 : std::swap(xMinMax[0], xMinMax[1]);
3907 : }
3908 : }
3909 : else
3910 : {
3911 217 : xMinMax[0] = pdfXCoord[0];
3912 217 : xMinMax[1] = pdfXCoord[xdim - 1];
3913 217 : node_offset = 0;
3914 :
3915 217 : if (nc_var_dimx_datatype == NC_FLOAT)
3916 : {
3917 30 : RoundMinMaxForFloatVals(xMinMax[0], xMinMax[1],
3918 30 : poDS->nRasterXSize - 1);
3919 : }
3920 : }
3921 :
3922 221 : if (bUseActualRange &&
3923 3 : !nc_get_att_double(nGroupDimYID, nVarDimYID, "actual_range",
3924 : adfActualRange))
3925 : {
3926 1 : yMinMax[0] = adfActualRange[0];
3927 1 : yMinMax[1] = adfActualRange[1];
3928 :
3929 : // Present yMinMax[] in the same order as pdfYCoord
3930 1 : if ((yMinMax[0] - yMinMax[1]) *
3931 1 : (pdfYCoord[0] - pdfYCoord[ydim - 1]) <
3932 : 0)
3933 : {
3934 0 : std::swap(yMinMax[0], yMinMax[1]);
3935 : }
3936 : }
3937 : else
3938 : {
3939 217 : yMinMax[0] = pdfYCoord[0];
3940 217 : yMinMax[1] = pdfYCoord[ydim - 1];
3941 217 : node_offset = 0;
3942 :
3943 217 : if (nc_var_dimy_datatype == NC_FLOAT)
3944 : {
3945 30 : RoundMinMaxForFloatVals(yMinMax[0], yMinMax[1],
3946 30 : poDS->nRasterYSize - 1);
3947 : }
3948 : }
3949 :
3950 218 : double dfCoordOffset = 0.0;
3951 218 : double dfCoordScale = 1.0;
3952 218 : if (!nc_get_att_double(nGroupId, nVarDimXID, CF_ADD_OFFSET,
3953 222 : &dfCoordOffset) &&
3954 4 : !nc_get_att_double(nGroupId, nVarDimXID, CF_SCALE_FACTOR,
3955 : &dfCoordScale))
3956 : {
3957 4 : xMinMax[0] = dfCoordOffset + xMinMax[0] * dfCoordScale;
3958 4 : xMinMax[1] = dfCoordOffset + xMinMax[1] * dfCoordScale;
3959 : }
3960 :
3961 218 : if (!nc_get_att_double(nGroupId, nVarDimYID, CF_ADD_OFFSET,
3962 222 : &dfCoordOffset) &&
3963 4 : !nc_get_att_double(nGroupId, nVarDimYID, CF_SCALE_FACTOR,
3964 : &dfCoordScale))
3965 : {
3966 4 : yMinMax[0] = dfCoordOffset + yMinMax[0] * dfCoordScale;
3967 4 : yMinMax[1] = dfCoordOffset + yMinMax[1] * dfCoordScale;
3968 : }
3969 :
3970 : // Check for reverse order of y-coordinate.
3971 218 : if (!bSwitchedXY)
3972 : {
3973 216 : poDS->bBottomUp = (yMinMax[0] <= yMinMax[1]);
3974 216 : CPLDebug("GDAL_netCDF", "set bBottomUp = %d from Y axis",
3975 216 : static_cast<int>(poDS->bBottomUp));
3976 216 : 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 218 : const char *pszProjName = oSRS.GetAttrValue("PROJECTION");
3986 218 : 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 218 : tmpGT[0] = xMinMax[0];
4043 436 : tmpGT[1] = (xMinMax[1] - xMinMax[0]) /
4044 218 : (poDS->nRasterXSize + (node_offset - 1));
4045 218 : tmpGT[2] = 0;
4046 218 : 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 216 : tmpGT[3] = yMinMax[1];
4056 216 : tmpGT[4] = 0;
4057 216 : tmpGT[5] = (yMinMax[0] - yMinMax[1]) /
4058 216 : (poDS->nRasterYSize + (node_offset - 1));
4059 : }
4060 :
4061 : // Compute the center of the pixel.
4062 218 : if (!node_offset)
4063 : {
4064 : // Otherwise its already the pixel center.
4065 218 : tmpGT[0] -= (tmpGT[1] / 2);
4066 218 : 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 225 : 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 225 : FetchAttr(nGroupId, NC_GLOBAL, "geospatial_bounds_crs");
4130 225 : 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 225 : CPLFree(pdfXCoord);
4146 225 : CPLFree(pdfYCoord);
4147 : } // end if(has dims)
4148 :
4149 : // Process custom GeoTransform GDAL value.
4150 537 : if (!EQUAL(pszGridMappingValue, ""))
4151 : {
4152 224 : if (pszGeoTransform != nullptr)
4153 : {
4154 : CPLStringList aosGeoTransform(
4155 226 : CSLTokenizeString2(pszGeoTransform, " ", CSLT_HONOURSTRINGS));
4156 113 : if (aosGeoTransform.size() == 6)
4157 : {
4158 113 : GDALGeoTransform gtFromAttribute;
4159 791 : for (int i = 0; i < 6; i++)
4160 : {
4161 678 : gtFromAttribute[i] = CPLAtof(aosGeoTransform[i]);
4162 : }
4163 :
4164 113 : if (bGotCfGT)
4165 : {
4166 94 : constexpr double GT_RELERROR_WARN_THRESHOLD = 1e-6;
4167 94 : double dfMaxAbsoluteError = 0.0;
4168 658 : for (int i = 0; i < 6; i++)
4169 : {
4170 : double dfAbsoluteError =
4171 564 : std::abs(tmpGT[i] - gtFromAttribute[i]);
4172 564 : if (dfAbsoluteError >
4173 564 : std::abs(gtFromAttribute[i] *
4174 : GT_RELERROR_WARN_THRESHOLD))
4175 : {
4176 2 : dfMaxAbsoluteError =
4177 2 : std::max(dfMaxAbsoluteError, dfAbsoluteError);
4178 : }
4179 : }
4180 :
4181 94 : if (dfMaxAbsoluteError > 0)
4182 : {
4183 2 : CPLError(CE_Warning, CPLE_AppDefined,
4184 : "GeoTransform read from attribute of %s "
4185 : "variable differs from value calculated from "
4186 : "dimension variables (max diff = %g). Using "
4187 : "value from attribute.",
4188 : pszGridMappingValue, dfMaxAbsoluteError);
4189 : }
4190 : }
4191 :
4192 113 : tmpGT = std::move(gtFromAttribute);
4193 113 : bGotGdalGT = true;
4194 : }
4195 : }
4196 : else
4197 : {
4198 : // Look for corner array values.
4199 : // CPLDebug("GDAL_netCDF",
4200 : // "looking for geotransform corners");
4201 111 : bool bGotNN = false;
4202 111 : double dfNN = FetchCopyParam(pszGridMappingValue,
4203 : "Northernmost_Northing", 0, &bGotNN);
4204 :
4205 111 : bool bGotSN = false;
4206 111 : double dfSN = FetchCopyParam(pszGridMappingValue,
4207 : "Southernmost_Northing", 0, &bGotSN);
4208 :
4209 111 : bool bGotEE = false;
4210 111 : double dfEE = FetchCopyParam(pszGridMappingValue,
4211 : "Easternmost_Easting", 0, &bGotEE);
4212 :
4213 111 : bool bGotWE = false;
4214 111 : double dfWE = FetchCopyParam(pszGridMappingValue,
4215 : "Westernmost_Easting", 0, &bGotWE);
4216 :
4217 : // Only set the GeoTransform if we got all the values.
4218 111 : if (bGotNN && bGotSN && bGotEE && bGotWE)
4219 : {
4220 0 : bGotGdalGT = true;
4221 :
4222 0 : tmpGT[0] = dfWE;
4223 0 : tmpGT[1] = (dfEE - dfWE) / (poDS->GetRasterXSize() - 1);
4224 0 : tmpGT[2] = 0.0;
4225 0 : tmpGT[3] = dfNN;
4226 0 : tmpGT[4] = 0.0;
4227 0 : tmpGT[5] = (dfSN - dfNN) / (poDS->GetRasterYSize() - 1);
4228 : // Compute the center of the pixel.
4229 0 : tmpGT[0] = dfWE - (tmpGT[1] / 2);
4230 0 : tmpGT[3] = dfNN - (tmpGT[5] / 2);
4231 : }
4232 : } // (pszGeoTransform != NULL)
4233 :
4234 224 : if (bGotGdalSRS && !bGotGdalGT)
4235 75 : CPLDebug("GDAL_netCDF", "Got SRS but no geotransform from GDAL!");
4236 : }
4237 :
4238 537 : if (!pszWKT && !bGotCfSRS)
4239 : {
4240 : // Some netCDF files have a srid attribute (#6613) like
4241 : // urn:ogc:def:crs:EPSG::6931
4242 313 : const char *pszSRID = FetchAttr(pszGridMappingValue, "srid");
4243 313 : if (pszSRID != nullptr)
4244 : {
4245 0 : oSRS.Clear();
4246 0 : if (oSRS.SetFromUserInput(
4247 : pszSRID,
4248 : OGRSpatialReference::
4249 0 : SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE)
4250 : {
4251 0 : char *pszWKTExport = nullptr;
4252 0 : CPLDebug("GDAL_netCDF", "Got SRS from %s", pszSRID);
4253 0 : oSRS.exportToWkt(&pszWKTExport);
4254 0 : if (returnProjStr != nullptr)
4255 : {
4256 0 : (*returnProjStr) = std::string(pszWKTExport);
4257 : }
4258 : else
4259 : {
4260 0 : m_bAddedProjectionVarsDefs = true;
4261 0 : m_bAddedProjectionVarsData = true;
4262 0 : SetSpatialRefNoUpdate(&oSRS);
4263 : }
4264 0 : CPLFree(pszWKTExport);
4265 : }
4266 : }
4267 : }
4268 :
4269 537 : CPLFree(pszGridMappingValue);
4270 :
4271 537 : if (bReadSRSOnly)
4272 185 : return;
4273 :
4274 : // Determines the SRS to be used by the geolocation array, if any
4275 704 : std::string osGeolocWKT = SRS_WKT_WGS84_LAT_LONG;
4276 352 : if (!m_oSRS.IsEmpty())
4277 : {
4278 266 : OGRSpatialReference oGeogCRS;
4279 133 : oGeogCRS.CopyGeogCSFrom(&m_oSRS);
4280 133 : char *pszWKTTmp = nullptr;
4281 133 : const char *const apszOptions[] = {"FORMAT=WKT2_2019", nullptr};
4282 133 : if (oGeogCRS.exportToWkt(&pszWKTTmp, apszOptions) == OGRERR_NONE)
4283 : {
4284 133 : osGeolocWKT = pszWKTTmp;
4285 : }
4286 133 : CPLFree(pszWKTTmp);
4287 : }
4288 :
4289 : // Process geolocation arrays from CF "coordinates" attribute.
4290 704 : std::string osGeolocXName, osGeolocYName;
4291 352 : if (ProcessCFGeolocation(nGroupId, nVarId, osGeolocWKT, osGeolocXName,
4292 352 : osGeolocYName))
4293 : {
4294 53 : bool bCanCancelGT = true;
4295 53 : if ((nVarDimXID != -1) && (nVarDimYID != -1))
4296 : {
4297 : char szVarNameX[NC_MAX_NAME + 1];
4298 44 : CPL_IGNORE_RET_VAL(
4299 44 : nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
4300 : char szVarNameY[NC_MAX_NAME + 1];
4301 44 : CPL_IGNORE_RET_VAL(
4302 44 : nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
4303 44 : bCanCancelGT =
4304 44 : !(osGeolocXName == szVarNameX && osGeolocYName == szVarNameY);
4305 : }
4306 88 : if (bCanCancelGT && !m_oSRS.IsGeographic() && !m_oSRS.IsProjected() &&
4307 35 : !bSwitchedXY)
4308 : {
4309 33 : bGotCfGT = false;
4310 : }
4311 : }
4312 121 : else if (!bGotCfGT && !bReadSRSOnly && (nVarDimXID != -1) &&
4313 423 : (nVarDimYID != -1) && xdim > 0 && ydim > 0 &&
4314 3 : ((!bSwitchedXY &&
4315 3 : NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
4316 1 : NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr)) ||
4317 2 : (bSwitchedXY &&
4318 0 : NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
4319 0 : NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr))))
4320 : {
4321 : // Case of autotest/gdrivers/data/netcdf/GLMELT_4X5.OCN.nc
4322 : // which is indexed by lat, lon variables, but lat has irregular
4323 : // spacing.
4324 1 : const char *pszGeolocXFullName = poDS->papszDimName[poDS->nXDimID];
4325 1 : const char *pszGeolocYFullName = poDS->papszDimName[poDS->nYDimID];
4326 1 : if (bSwitchedXY)
4327 : {
4328 0 : std::swap(pszGeolocXFullName, pszGeolocYFullName);
4329 0 : GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", "GEOLOCATION");
4330 : }
4331 :
4332 1 : CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
4333 : pszGeolocXFullName, pszGeolocYFullName);
4334 :
4335 1 : GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
4336 : "GEOLOCATION");
4337 :
4338 2 : CPLString osTMP;
4339 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4340 1 : pszGeolocXFullName);
4341 :
4342 1 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
4343 1 : GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
4344 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4345 1 : pszGeolocYFullName);
4346 :
4347 1 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
4348 1 : GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
4349 :
4350 1 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
4351 1 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
4352 :
4353 1 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
4354 1 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
4355 :
4356 1 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
4357 : "PIXEL_CENTER", "GEOLOCATION");
4358 : }
4359 :
4360 : // Set GeoTransform if we got a complete one - after projection has been set
4361 352 : if (bGotCfGT || bGotGdalGT)
4362 : {
4363 200 : m_bAddedProjectionVarsDefs = true;
4364 200 : m_bAddedProjectionVarsData = true;
4365 200 : SetGeoTransformNoUpdate(tmpGT);
4366 : }
4367 :
4368 : // Debugging reports.
4369 352 : CPLDebug("GDAL_netCDF",
4370 : "bGotGeogCS=%d bGotCfSRS=%d bGotCfGT=%d bGotCfWktSRS=%d "
4371 : "bGotGdalSRS=%d bGotGdalGT=%d",
4372 : static_cast<int>(bGotGeogCS), static_cast<int>(bGotCfSRS),
4373 : static_cast<int>(bGotCfGT), static_cast<int>(bGotCfWktSRS),
4374 : static_cast<int>(bGotGdalSRS), static_cast<int>(bGotGdalGT));
4375 :
4376 352 : if (!bGotCfGT && !bGotGdalGT)
4377 152 : CPLDebug("GDAL_netCDF", "did not get geotransform from CF nor GDAL!");
4378 :
4379 352 : if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfGT && !bGotCfWktSRS)
4380 152 : CPLDebug("GDAL_netCDF", "did not get projection from CF nor GDAL!");
4381 :
4382 : // wish of 6195
4383 : // we don't have CS/SRS, but we do have GT, and we live in -180,360 -90,90
4384 352 : if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfWktSRS)
4385 : {
4386 219 : if (bGotCfGT || bGotGdalGT)
4387 : {
4388 134 : bool bAssumedLongLat = CPLTestBool(CSLFetchNameValueDef(
4389 67 : papszOpenOptions, "ASSUME_LONGLAT",
4390 : CPLGetConfigOption("GDAL_NETCDF_ASSUME_LONGLAT", "NO")));
4391 :
4392 2 : if (bAssumedLongLat && tmpGT[0] >= -180 && tmpGT[0] < 360 &&
4393 2 : (tmpGT[0] + tmpGT[1] * poDS->GetRasterXSize()) <= 360 &&
4394 71 : tmpGT[3] <= 90 && tmpGT[3] > -90 &&
4395 2 : (tmpGT[3] + tmpGT[5] * poDS->GetRasterYSize()) >= -90)
4396 : {
4397 :
4398 2 : poDS->bIsGeographic = true;
4399 2 : char *pszTempProjection = nullptr;
4400 : // seems odd to use 4326 so OGC:CRS84
4401 2 : oSRS.SetFromUserInput("OGC:CRS84");
4402 2 : oSRS.exportToWkt(&pszTempProjection);
4403 2 : if (returnProjStr != nullptr)
4404 : {
4405 0 : (*returnProjStr) = std::string(pszTempProjection);
4406 : }
4407 : else
4408 : {
4409 2 : m_bAddedProjectionVarsDefs = true;
4410 2 : m_bAddedProjectionVarsData = true;
4411 2 : SetSpatialRefNoUpdate(&oSRS);
4412 : }
4413 2 : CPLFree(pszTempProjection);
4414 :
4415 2 : CPLDebug("netCDF",
4416 : "Assumed Longitude Latitude CRS 'OGC:CRS84' because "
4417 : "none otherwise available and geotransform within "
4418 : "suitable bounds. "
4419 : "Set GDAL_NETCDF_ASSUME_LONGLAT=NO as configuration "
4420 : "option or "
4421 : " ASSUME_LONGLAT=NO as open option to bypass this "
4422 : "assumption.");
4423 : }
4424 : }
4425 : }
4426 :
4427 : // Search for Well-known GeogCS if got only CF WKT
4428 : // Disabled for now, as a named datum also include control points
4429 : // (see mailing list and bug#4281
4430 : // For example, WGS84 vs. GDA94 (EPSG:3577) - AEA in netcdf_cf.py
4431 :
4432 : // Disabled for now, but could be set in a config option.
4433 : #if 0
4434 : bool bLookForWellKnownGCS = false; // This could be a Config Option.
4435 :
4436 : if( bLookForWellKnownGCS && bGotCfSRS && !bGotGdalSRS )
4437 : {
4438 : // ET - Could use a more exhaustive method by scanning all EPSG codes in
4439 : // data/gcs.csv as proposed by Even in the gdal-dev mailing list "help
4440 : // for comparing two WKT".
4441 : // This code could be contributed to a new function.
4442 : // OGRSpatialReference * OGRSpatialReference::FindMatchingGeogCS(
4443 : // const OGRSpatialReference *poOther) */
4444 : CPLDebug("GDAL_netCDF", "Searching for Well-known GeogCS");
4445 : const char *pszWKGCSList[] = { "WGS84", "WGS72", "NAD27", "NAD83" };
4446 : char *pszWKGCS = NULL;
4447 : oSRS.exportToPrettyWkt(&pszWKGCS);
4448 : for( size_t i = 0; i < sizeof(pszWKGCSList) / 8; i++ )
4449 : {
4450 : pszWKGCS = CPLStrdup(pszWKGCSList[i]);
4451 : OGRSpatialReference oSRSTmp;
4452 : oSRSTmp.SetWellKnownGeogCS(pszWKGCSList[i]);
4453 : // Set datum to unknown, bug #4281.
4454 : if( oSRSTmp.GetAttrNode("DATUM" ) )
4455 : oSRSTmp.GetAttrNode("DATUM")->GetChild(0)->SetValue("unknown");
4456 : // Could use OGRSpatialReference::StripCTParms(), but let's keep
4457 : // TOWGS84.
4458 : oSRSTmp.GetRoot()->StripNodes("AXIS");
4459 : oSRSTmp.GetRoot()->StripNodes("AUTHORITY");
4460 : oSRSTmp.GetRoot()->StripNodes("EXTENSION");
4461 :
4462 : oSRSTmp.exportToPrettyWkt(&pszWKGCS);
4463 : if( oSRS.IsSameGeogCS(&oSRSTmp) )
4464 : {
4465 : oSRS.SetWellKnownGeogCS(pszWKGCSList[i]);
4466 : oSRS.exportToWkt(&(pszTempProjection));
4467 : SetProjection(pszTempProjection);
4468 : CPLFree(pszTempProjection);
4469 : }
4470 : }
4471 : }
4472 : #endif
4473 : }
4474 :
4475 142 : void netCDFDataset::SetProjectionFromVar(int nGroupId, int nVarId,
4476 : bool bReadSRSOnly)
4477 : {
4478 142 : SetProjectionFromVar(nGroupId, nVarId, bReadSRSOnly, nullptr, nullptr,
4479 : nullptr, nullptr);
4480 142 : }
4481 :
4482 288 : bool netCDFDataset::ProcessNASAL2OceanGeoLocation(int nGroupId, int nVarId)
4483 : {
4484 : // Cf https://oceancolor.gsfc.nasa.gov/docs/format/l2nc/
4485 : // and https://github.com/OSGeo/gdal/issues/7605
4486 :
4487 : // Check for a structure like:
4488 : /* netcdf SNPP_VIIRS.20230406T024200.L2.OC.NRT {
4489 : dimensions:
4490 : number_of_lines = 3248 ;
4491 : pixels_per_line = 3200 ;
4492 : [...]
4493 : pixel_control_points = 3200 ;
4494 : [...]
4495 : group: geophysical_data {
4496 : variables:
4497 : short aot_862(number_of_lines, pixels_per_line) ; <-- nVarId
4498 : [...]
4499 : }
4500 : group: navigation_data {
4501 : variables:
4502 : float longitude(number_of_lines, pixel_control_points) ;
4503 : [...]
4504 : float latitude(number_of_lines, pixel_control_points) ;
4505 : [...]
4506 : }
4507 : }
4508 : */
4509 : // Note that the longitude and latitude arrays are not indexed by the
4510 : // same dimensions. Handle only the case where
4511 : // pixel_control_points == pixels_per_line
4512 : // If there was a subsampling of the geolocation arrays, we'd need to
4513 : // add more logic.
4514 :
4515 576 : std::string osGroupName;
4516 288 : osGroupName.resize(NC_MAX_NAME);
4517 288 : NCDF_ERR(nc_inq_grpname(nGroupId, &osGroupName[0]));
4518 288 : osGroupName.resize(strlen(osGroupName.data()));
4519 288 : if (osGroupName != "geophysical_data")
4520 287 : return false;
4521 :
4522 1 : int nVarDims = 0;
4523 1 : NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
4524 1 : if (nVarDims != 2)
4525 0 : return false;
4526 :
4527 1 : int nNavigationDataGrpId = 0;
4528 1 : if (nc_inq_grp_ncid(cdfid, "navigation_data", &nNavigationDataGrpId) !=
4529 : NC_NOERR)
4530 0 : return false;
4531 :
4532 : std::array<int, 2> anVarDimIds;
4533 1 : NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
4534 :
4535 1 : int nLongitudeId = 0;
4536 1 : int nLatitudeId = 0;
4537 1 : if (nc_inq_varid(nNavigationDataGrpId, "longitude", &nLongitudeId) !=
4538 2 : NC_NOERR ||
4539 1 : nc_inq_varid(nNavigationDataGrpId, "latitude", &nLatitudeId) !=
4540 : NC_NOERR)
4541 : {
4542 0 : return false;
4543 : }
4544 :
4545 1 : int nDimsLongitude = 0;
4546 1 : NCDF_ERR(
4547 : nc_inq_varndims(nNavigationDataGrpId, nLongitudeId, &nDimsLongitude));
4548 1 : int nDimsLatitude = 0;
4549 1 : NCDF_ERR(
4550 : nc_inq_varndims(nNavigationDataGrpId, nLatitudeId, &nDimsLatitude));
4551 1 : if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
4552 : {
4553 0 : return false;
4554 : }
4555 :
4556 : std::array<int, 2> anDimLongitudeIds;
4557 1 : NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLongitudeId,
4558 : anDimLongitudeIds.data()));
4559 : std::array<int, 2> anDimLatitudeIds;
4560 1 : NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLatitudeId,
4561 : anDimLatitudeIds.data()));
4562 1 : if (anDimLongitudeIds != anDimLatitudeIds)
4563 : {
4564 0 : return false;
4565 : }
4566 :
4567 : std::array<size_t, 2> anSizeVarDimIds;
4568 : std::array<size_t, 2> anSizeLongLatIds;
4569 2 : if (!(nc_inq_dimlen(cdfid, anVarDimIds[0], &anSizeVarDimIds[0]) ==
4570 1 : NC_NOERR &&
4571 1 : nc_inq_dimlen(cdfid, anVarDimIds[1], &anSizeVarDimIds[1]) ==
4572 1 : NC_NOERR &&
4573 1 : nc_inq_dimlen(cdfid, anDimLongitudeIds[0], &anSizeLongLatIds[0]) ==
4574 1 : NC_NOERR &&
4575 1 : nc_inq_dimlen(cdfid, anDimLongitudeIds[1], &anSizeLongLatIds[1]) ==
4576 : NC_NOERR &&
4577 1 : anSizeVarDimIds == anSizeLongLatIds))
4578 : {
4579 0 : return false;
4580 : }
4581 :
4582 1 : const char *pszGeolocXFullName = "/navigation_data/longitude";
4583 1 : const char *pszGeolocYFullName = "/navigation_data/latitude";
4584 :
4585 1 : if (bSwitchedXY)
4586 : {
4587 0 : std::swap(pszGeolocXFullName, pszGeolocYFullName);
4588 0 : GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", "GEOLOCATION");
4589 : }
4590 :
4591 1 : CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
4592 : pszGeolocXFullName, pszGeolocYFullName);
4593 :
4594 1 : GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
4595 : "GEOLOCATION");
4596 :
4597 1 : CPLString osTMP;
4598 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
4599 :
4600 1 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
4601 1 : GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
4602 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
4603 :
4604 1 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
4605 1 : GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
4606 :
4607 1 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
4608 1 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
4609 :
4610 1 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
4611 1 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
4612 :
4613 1 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
4614 : "GEOLOCATION");
4615 1 : return true;
4616 : }
4617 :
4618 287 : bool netCDFDataset::ProcessNASAEMITGeoLocation(int nGroupId, int nVarId)
4619 : {
4620 : // Cf https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/
4621 :
4622 : // Check for a structure like:
4623 : /* netcdf EMIT_L2A_RFL_001_20220903T163129_2224611_012 {
4624 : dimensions:
4625 : downtrack = 1280 ;
4626 : crosstrack = 1242 ;
4627 : bands = 285 ;
4628 : [...]
4629 :
4630 : variables:
4631 : float reflectance(downtrack, crosstrack, bands) ;
4632 :
4633 : group: location {
4634 : variables:
4635 : double lon(downtrack, crosstrack) ;
4636 : lon:_FillValue = -9999. ;
4637 : lon:long_name = "Longitude (WGS-84)" ;
4638 : lon:units = "degrees east" ;
4639 : double lat(downtrack, crosstrack) ;
4640 : lat:_FillValue = -9999. ;
4641 : lat:long_name = "Latitude (WGS-84)" ;
4642 : lat:units = "degrees north" ;
4643 : } // group location
4644 :
4645 : }
4646 : or
4647 : netcdf EMIT_L2B_MIN_001_20231024T055538_2329704_040 {
4648 : dimensions:
4649 : downtrack = 1664 ;
4650 : crosstrack = 1242 ;
4651 : [...]
4652 : variables:
4653 : float group_1_band_depth(downtrack, crosstrack) ;
4654 : group_1_band_depth:_FillValue = -9999.f ;
4655 : group_1_band_depth:long_name = "Group 1 Band Depth" ;
4656 : group_1_band_depth:units = "unitless" ;
4657 : [...]
4658 : group: location {
4659 : variables:
4660 : double lon(downtrack, crosstrack) ;
4661 : lon:_FillValue = -9999. ;
4662 : lon:long_name = "Longitude (WGS-84)" ;
4663 : lon:units = "degrees east" ;
4664 : double lat(downtrack, crosstrack) ;
4665 : lat:_FillValue = -9999. ;
4666 : lat:long_name = "Latitude (WGS-84)" ;
4667 : lat:units = "degrees north" ;
4668 : }
4669 : */
4670 :
4671 287 : int nVarDims = 0;
4672 287 : NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
4673 287 : if (nVarDims != 2 && nVarDims != 3)
4674 14 : return false;
4675 :
4676 273 : int nLocationGrpId = 0;
4677 273 : if (nc_inq_grp_ncid(cdfid, "location", &nLocationGrpId) != NC_NOERR)
4678 59 : return false;
4679 :
4680 : std::array<int, 3> anVarDimIds;
4681 214 : NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
4682 214 : if (nYDimID != anVarDimIds[0] || nXDimID != anVarDimIds[1])
4683 21 : return false;
4684 :
4685 193 : int nLongitudeId = 0;
4686 193 : int nLatitudeId = 0;
4687 231 : if (nc_inq_varid(nLocationGrpId, "lon", &nLongitudeId) != NC_NOERR ||
4688 38 : nc_inq_varid(nLocationGrpId, "lat", &nLatitudeId) != NC_NOERR)
4689 : {
4690 155 : return false;
4691 : }
4692 :
4693 38 : int nDimsLongitude = 0;
4694 38 : NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLongitudeId, &nDimsLongitude));
4695 38 : int nDimsLatitude = 0;
4696 38 : NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLatitudeId, &nDimsLatitude));
4697 38 : if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
4698 : {
4699 34 : return false;
4700 : }
4701 :
4702 : std::array<int, 2> anDimLongitudeIds;
4703 4 : NCDF_ERR(nc_inq_vardimid(nLocationGrpId, nLongitudeId,
4704 : anDimLongitudeIds.data()));
4705 : std::array<int, 2> anDimLatitudeIds;
4706 4 : NCDF_ERR(
4707 : nc_inq_vardimid(nLocationGrpId, nLatitudeId, anDimLatitudeIds.data()));
4708 4 : if (anDimLongitudeIds != anDimLatitudeIds)
4709 : {
4710 0 : return false;
4711 : }
4712 :
4713 8 : if (anDimLongitudeIds[0] != anVarDimIds[0] ||
4714 4 : anDimLongitudeIds[1] != anVarDimIds[1])
4715 : {
4716 0 : return false;
4717 : }
4718 :
4719 4 : const char *pszGeolocXFullName = "/location/lon";
4720 4 : const char *pszGeolocYFullName = "/location/lat";
4721 :
4722 4 : CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
4723 : pszGeolocXFullName, pszGeolocYFullName);
4724 :
4725 4 : GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
4726 : "GEOLOCATION");
4727 :
4728 4 : CPLString osTMP;
4729 4 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
4730 :
4731 4 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
4732 4 : GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
4733 4 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
4734 :
4735 4 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
4736 4 : GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
4737 :
4738 4 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
4739 4 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
4740 :
4741 4 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
4742 4 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
4743 :
4744 4 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
4745 : "GEOLOCATION");
4746 4 : return true;
4747 : }
4748 :
4749 352 : int netCDFDataset::ProcessCFGeolocation(int nGroupId, int nVarId,
4750 : const std::string &osGeolocWKT,
4751 : std::string &osGeolocXNameOut,
4752 : std::string &osGeolocYNameOut)
4753 : {
4754 352 : bool bAddGeoloc = false;
4755 352 : char *pszCoordinates = nullptr;
4756 :
4757 : // If there is no explicit "coordinates" attribute, check if there are
4758 : // "lon" and "lat" 2D variables whose dimensions are the last
4759 : // 2 ones of the variable of interest.
4760 352 : if (NCDFGetAttr(nGroupId, nVarId, "coordinates", &pszCoordinates) !=
4761 : CE_None)
4762 : {
4763 305 : CPLFree(pszCoordinates);
4764 305 : pszCoordinates = nullptr;
4765 :
4766 305 : int nVarDims = 0;
4767 305 : NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
4768 305 : if (nVarDims >= 2)
4769 : {
4770 610 : std::vector<int> anVarDimIds(nVarDims);
4771 305 : NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
4772 :
4773 305 : int nLongitudeId = 0;
4774 305 : int nLatitudeId = 0;
4775 373 : if (nc_inq_varid(nGroupId, "lon", &nLongitudeId) == NC_NOERR &&
4776 68 : nc_inq_varid(nGroupId, "lat", &nLatitudeId) == NC_NOERR)
4777 : {
4778 68 : int nDimsLongitude = 0;
4779 68 : NCDF_ERR(
4780 : nc_inq_varndims(nGroupId, nLongitudeId, &nDimsLongitude));
4781 68 : int nDimsLatitude = 0;
4782 68 : NCDF_ERR(
4783 : nc_inq_varndims(nGroupId, nLatitudeId, &nDimsLatitude));
4784 68 : if (nDimsLongitude == 2 && nDimsLatitude == 2)
4785 : {
4786 34 : std::vector<int> anDimLongitudeIds(2);
4787 17 : NCDF_ERR(nc_inq_vardimid(nGroupId, nLongitudeId,
4788 : anDimLongitudeIds.data()));
4789 34 : std::vector<int> anDimLatitudeIds(2);
4790 17 : NCDF_ERR(nc_inq_vardimid(nGroupId, nLatitudeId,
4791 : anDimLatitudeIds.data()));
4792 17 : if (anDimLongitudeIds == anDimLatitudeIds &&
4793 34 : anVarDimIds[anVarDimIds.size() - 2] ==
4794 51 : anDimLongitudeIds[0] &&
4795 34 : anVarDimIds[anVarDimIds.size() - 1] ==
4796 17 : anDimLongitudeIds[1])
4797 : {
4798 17 : pszCoordinates = CPLStrdup("lon lat");
4799 : }
4800 : }
4801 : }
4802 : }
4803 : }
4804 :
4805 352 : if (pszCoordinates)
4806 : {
4807 : // Get X and Y geolocation names from coordinates attribute.
4808 : const CPLStringList aosCoordinates(
4809 128 : NCDFTokenizeCoordinatesAttribute(pszCoordinates));
4810 64 : if (aosCoordinates.size() >= 2)
4811 : {
4812 : char szGeolocXName[NC_MAX_NAME + 1];
4813 : char szGeolocYName[NC_MAX_NAME + 1];
4814 61 : szGeolocXName[0] = '\0';
4815 61 : szGeolocYName[0] = '\0';
4816 :
4817 : // Test that each variable is longitude/latitude.
4818 196 : for (int i = 0; i < aosCoordinates.size(); i++)
4819 : {
4820 135 : if (NCDFIsVarLongitude(nGroupId, -1, aosCoordinates[i]))
4821 : {
4822 50 : int nOtherGroupId = -1;
4823 50 : int nOtherVarId = -1;
4824 : // Check that the variable actually exists
4825 : // Needed on Sentinel-3 products
4826 50 : if (NCDFResolveVar(nGroupId, aosCoordinates[i],
4827 50 : &nOtherGroupId, &nOtherVarId) == CE_None)
4828 : {
4829 48 : snprintf(szGeolocXName, sizeof(szGeolocXName), "%s",
4830 : aosCoordinates[i]);
4831 : }
4832 : }
4833 85 : else if (NCDFIsVarLatitude(nGroupId, -1, aosCoordinates[i]))
4834 : {
4835 50 : int nOtherGroupId = -1;
4836 50 : int nOtherVarId = -1;
4837 : // Check that the variable actually exists
4838 : // Needed on Sentinel-3 products
4839 50 : if (NCDFResolveVar(nGroupId, aosCoordinates[i],
4840 50 : &nOtherGroupId, &nOtherVarId) == CE_None)
4841 : {
4842 48 : snprintf(szGeolocYName, sizeof(szGeolocYName), "%s",
4843 : aosCoordinates[i]);
4844 : }
4845 : }
4846 : }
4847 : // Add GEOLOCATION metadata.
4848 61 : if (!EQUAL(szGeolocXName, "") && !EQUAL(szGeolocYName, ""))
4849 : {
4850 48 : osGeolocXNameOut = szGeolocXName;
4851 48 : osGeolocYNameOut = szGeolocYName;
4852 :
4853 48 : char *pszGeolocXFullName = nullptr;
4854 48 : char *pszGeolocYFullName = nullptr;
4855 48 : if (NCDFResolveVarFullName(nGroupId, szGeolocXName,
4856 96 : &pszGeolocXFullName) == CE_None &&
4857 48 : NCDFResolveVarFullName(nGroupId, szGeolocYName,
4858 : &pszGeolocYFullName) == CE_None)
4859 : {
4860 48 : if (bSwitchedXY)
4861 : {
4862 2 : std::swap(pszGeolocXFullName, pszGeolocYFullName);
4863 2 : GDALPamDataset::SetMetadataItem("SWAP_XY", "YES",
4864 : "GEOLOCATION");
4865 : }
4866 :
4867 48 : bAddGeoloc = true;
4868 48 : CPLDebug("GDAL_netCDF",
4869 : "using variables %s and %s for GEOLOCATION",
4870 : pszGeolocXFullName, pszGeolocYFullName);
4871 :
4872 48 : GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
4873 : "GEOLOCATION");
4874 :
4875 96 : CPLString osTMP;
4876 48 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4877 48 : pszGeolocXFullName);
4878 :
4879 48 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP,
4880 : "GEOLOCATION");
4881 48 : GDALPamDataset::SetMetadataItem("X_BAND", "1",
4882 : "GEOLOCATION");
4883 48 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4884 48 : pszGeolocYFullName);
4885 :
4886 48 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP,
4887 : "GEOLOCATION");
4888 48 : GDALPamDataset::SetMetadataItem("Y_BAND", "1",
4889 : "GEOLOCATION");
4890 :
4891 48 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0",
4892 : "GEOLOCATION");
4893 48 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1",
4894 : "GEOLOCATION");
4895 :
4896 48 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0",
4897 : "GEOLOCATION");
4898 48 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1",
4899 : "GEOLOCATION");
4900 :
4901 48 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
4902 : "PIXEL_CENTER",
4903 : "GEOLOCATION");
4904 : }
4905 : else
4906 : {
4907 0 : CPLDebug("GDAL_netCDF",
4908 : "cannot resolve location of "
4909 : "lat/lon variables specified by the coordinates "
4910 : "attribute [%s]",
4911 : pszCoordinates);
4912 : }
4913 48 : CPLFree(pszGeolocXFullName);
4914 48 : CPLFree(pszGeolocYFullName);
4915 : }
4916 : else
4917 : {
4918 13 : CPLDebug("GDAL_netCDF",
4919 : "coordinates attribute [%s] is unsupported",
4920 : pszCoordinates);
4921 : }
4922 : }
4923 : else
4924 : {
4925 3 : CPLDebug("GDAL_netCDF",
4926 : "coordinates attribute [%s] with %d element(s) is "
4927 : "unsupported",
4928 : pszCoordinates, aosCoordinates.size());
4929 : }
4930 : }
4931 :
4932 : else
4933 : {
4934 288 : bAddGeoloc = ProcessNASAL2OceanGeoLocation(nGroupId, nVarId);
4935 :
4936 288 : if (!bAddGeoloc)
4937 287 : bAddGeoloc = ProcessNASAEMITGeoLocation(nGroupId, nVarId);
4938 : }
4939 :
4940 352 : CPLFree(pszCoordinates);
4941 :
4942 352 : return bAddGeoloc;
4943 : }
4944 :
4945 8 : CPLErr netCDFDataset::Set1DGeolocation(int nGroupId, int nVarId,
4946 : const char *szDimName)
4947 : {
4948 : // Get values.
4949 8 : char *pszVarValues = nullptr;
4950 8 : CPLErr eErr = NCDFGet1DVar(nGroupId, nVarId, &pszVarValues);
4951 8 : if (eErr != CE_None)
4952 0 : return eErr;
4953 :
4954 : // Write metadata.
4955 8 : char szTemp[NC_MAX_NAME + 1 + 32] = {};
4956 8 : snprintf(szTemp, sizeof(szTemp), "%s_VALUES", szDimName);
4957 8 : GDALPamDataset::SetMetadataItem(szTemp, pszVarValues, "GEOLOCATION2");
4958 :
4959 8 : CPLFree(pszVarValues);
4960 :
4961 8 : return CE_None;
4962 : }
4963 :
4964 0 : double *netCDFDataset::Get1DGeolocation(CPL_UNUSED const char *szDimName,
4965 : int &nVarLen)
4966 : {
4967 0 : nVarLen = 0;
4968 :
4969 : // Get Y_VALUES as tokens.
4970 : char **papszValues =
4971 0 : NCDFTokenizeArray(GetMetadataItem("Y_VALUES", "GEOLOCATION2"));
4972 0 : if (papszValues == nullptr)
4973 0 : return nullptr;
4974 :
4975 : // Initialize and fill array.
4976 0 : nVarLen = CSLCount(papszValues);
4977 : double *pdfVarValues =
4978 0 : static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
4979 :
4980 0 : for (int i = 0, j = 0; i < nVarLen; i++)
4981 : {
4982 0 : if (!bBottomUp)
4983 0 : j = nVarLen - 1 - i;
4984 : else
4985 0 : j = i; // Invert latitude values.
4986 0 : char *pszTemp = nullptr;
4987 0 : pdfVarValues[j] = CPLStrtod(papszValues[i], &pszTemp);
4988 : }
4989 0 : CSLDestroy(papszValues);
4990 :
4991 0 : return pdfVarValues;
4992 : }
4993 :
4994 : /************************************************************************/
4995 : /* SetSpatialRefNoUpdate() */
4996 : /************************************************************************/
4997 :
4998 260 : void netCDFDataset::SetSpatialRefNoUpdate(const OGRSpatialReference *poSRS)
4999 : {
5000 260 : m_oSRS.Clear();
5001 260 : if (poSRS)
5002 253 : m_oSRS = *poSRS;
5003 260 : m_bHasProjection = true;
5004 260 : }
5005 :
5006 : /************************************************************************/
5007 : /* SetSpatialRef() */
5008 : /************************************************************************/
5009 :
5010 76 : CPLErr netCDFDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
5011 : {
5012 152 : CPLMutexHolderD(&hNCMutex);
5013 :
5014 76 : if (GetAccess() != GA_Update || m_bHasProjection)
5015 : {
5016 0 : CPLError(CE_Failure, CPLE_AppDefined,
5017 : "netCDFDataset::_SetProjection() should only be called once "
5018 : "in update mode!");
5019 0 : return CE_Failure;
5020 : }
5021 :
5022 76 : if (m_bHasGeoTransform)
5023 : {
5024 32 : SetSpatialRefNoUpdate(poSRS);
5025 :
5026 : // For NC4/NC4C, writing both projection variables and data,
5027 : // followed by redefining nodata value, cancels the projection
5028 : // info from the Band variable, so for now only write the
5029 : // variable definitions, and write data at the end.
5030 : // See https://trac.osgeo.org/gdal/ticket/7245
5031 32 : return AddProjectionVars(true, nullptr, nullptr);
5032 : }
5033 :
5034 44 : SetSpatialRefNoUpdate(poSRS);
5035 :
5036 44 : return CE_None;
5037 : }
5038 :
5039 : /************************************************************************/
5040 : /* SetGeoTransformNoUpdate() */
5041 : /************************************************************************/
5042 :
5043 277 : void netCDFDataset::SetGeoTransformNoUpdate(const GDALGeoTransform >)
5044 : {
5045 277 : m_gt = gt;
5046 277 : m_bHasGeoTransform = true;
5047 277 : }
5048 :
5049 : /************************************************************************/
5050 : /* SetGeoTransform() */
5051 : /************************************************************************/
5052 :
5053 77 : CPLErr netCDFDataset::SetGeoTransform(const GDALGeoTransform >)
5054 : {
5055 154 : CPLMutexHolderD(&hNCMutex);
5056 :
5057 77 : if (GetAccess() != GA_Update || m_bHasGeoTransform)
5058 : {
5059 0 : CPLError(CE_Failure, CPLE_AppDefined,
5060 : "netCDFDataset::SetGeoTransform() should only be called once "
5061 : "in update mode!");
5062 0 : return CE_Failure;
5063 : }
5064 :
5065 77 : CPLDebug("GDAL_netCDF", "SetGeoTransform(%f,%f,%f,%f,%f,%f)", gt[0], gt[1],
5066 : gt[2], gt[3], gt[4], gt[5]);
5067 :
5068 77 : SetGeoTransformNoUpdate(gt);
5069 :
5070 77 : if (m_bHasProjection)
5071 : {
5072 :
5073 : // For NC4/NC4C, writing both projection variables and data,
5074 : // followed by redefining nodata value, cancels the projection
5075 : // info from the Band variable, so for now only write the
5076 : // variable definitions, and write data at the end.
5077 : // See https://trac.osgeo.org/gdal/ticket/7245
5078 3 : return AddProjectionVars(true, nullptr, nullptr);
5079 : }
5080 :
5081 74 : return CE_None;
5082 : }
5083 :
5084 : /************************************************************************/
5085 : /* NCDFWriteSRSVariable() */
5086 : /************************************************************************/
5087 :
5088 130 : int NCDFWriteSRSVariable(int cdfid, const OGRSpatialReference *poSRS,
5089 : char **ppszCFProjection, bool bWriteGDALTags,
5090 : const std::string &srsVarName)
5091 : {
5092 130 : char *pszCFProjection = nullptr;
5093 130 : char **papszKeyValues = nullptr;
5094 130 : poSRS->exportToCF1(&pszCFProjection, &papszKeyValues, nullptr, nullptr);
5095 :
5096 130 : if (bWriteGDALTags)
5097 : {
5098 129 : const char *pszWKT = CSLFetchNameValue(papszKeyValues, NCDF_CRS_WKT);
5099 129 : if (pszWKT)
5100 : {
5101 : // SPATIAL_REF is deprecated. Will be removed in GDAL 4.
5102 129 : papszKeyValues =
5103 129 : CSLSetNameValue(papszKeyValues, NCDF_SPATIAL_REF, pszWKT);
5104 : }
5105 : }
5106 :
5107 130 : const int nValues = CSLCount(papszKeyValues);
5108 :
5109 : int NCDFVarID;
5110 260 : std::string varNameRadix(pszCFProjection);
5111 130 : int nCounter = 2;
5112 : while (true)
5113 : {
5114 132 : NCDFVarID = -1;
5115 132 : nc_inq_varid(cdfid, pszCFProjection, &NCDFVarID);
5116 132 : if (NCDFVarID < 0)
5117 127 : break;
5118 :
5119 5 : int nbAttr = 0;
5120 5 : NCDF_ERR(nc_inq_varnatts(cdfid, NCDFVarID, &nbAttr));
5121 5 : bool bSame = nbAttr == nValues;
5122 41 : for (int i = 0; bSame && (i < nbAttr); i++)
5123 : {
5124 : char szAttrName[NC_MAX_NAME + 1];
5125 38 : szAttrName[0] = 0;
5126 38 : NCDF_ERR(nc_inq_attname(cdfid, NCDFVarID, i, szAttrName));
5127 :
5128 : const char *pszValue =
5129 38 : CSLFetchNameValue(papszKeyValues, szAttrName);
5130 38 : if (!pszValue)
5131 : {
5132 0 : bSame = false;
5133 2 : break;
5134 : }
5135 :
5136 38 : nc_type atttype = NC_NAT;
5137 38 : size_t attlen = 0;
5138 38 : NCDF_ERR(
5139 : nc_inq_att(cdfid, NCDFVarID, szAttrName, &atttype, &attlen));
5140 38 : if (atttype != NC_CHAR && atttype != NC_DOUBLE)
5141 : {
5142 0 : bSame = false;
5143 0 : break;
5144 : }
5145 38 : if (atttype == NC_CHAR)
5146 : {
5147 15 : if (CPLGetValueType(pszValue) != CPL_VALUE_STRING)
5148 : {
5149 0 : bSame = false;
5150 0 : break;
5151 : }
5152 15 : std::string val;
5153 15 : val.resize(attlen);
5154 15 : nc_get_att_text(cdfid, NCDFVarID, szAttrName, &val[0]);
5155 15 : if (val != pszValue)
5156 : {
5157 0 : bSame = false;
5158 0 : break;
5159 : }
5160 : }
5161 : else
5162 : {
5163 : const CPLStringList aosTokens(
5164 23 : CSLTokenizeString2(pszValue, ",", 0));
5165 23 : if (static_cast<size_t>(aosTokens.size()) != attlen)
5166 : {
5167 0 : bSame = false;
5168 0 : break;
5169 : }
5170 : double vals[2];
5171 23 : nc_get_att_double(cdfid, NCDFVarID, szAttrName, vals);
5172 44 : if (vals[0] != CPLAtof(aosTokens[0]) ||
5173 21 : (attlen == 2 && vals[1] != CPLAtof(aosTokens[1])))
5174 : {
5175 2 : bSame = false;
5176 2 : break;
5177 : }
5178 : }
5179 : }
5180 5 : if (bSame)
5181 : {
5182 3 : *ppszCFProjection = pszCFProjection;
5183 3 : CSLDestroy(papszKeyValues);
5184 3 : return NCDFVarID;
5185 : }
5186 2 : CPLFree(pszCFProjection);
5187 2 : pszCFProjection =
5188 2 : CPLStrdup(CPLSPrintf("%s_%d", varNameRadix.c_str(), nCounter));
5189 2 : nCounter++;
5190 2 : }
5191 :
5192 127 : *ppszCFProjection = pszCFProjection;
5193 :
5194 : const char *pszVarName;
5195 :
5196 127 : if (srsVarName != "")
5197 : {
5198 38 : pszVarName = srsVarName.c_str();
5199 : }
5200 : else
5201 : {
5202 89 : pszVarName = pszCFProjection;
5203 : }
5204 :
5205 127 : int status = nc_def_var(cdfid, pszVarName, NC_CHAR, 0, nullptr, &NCDFVarID);
5206 127 : NCDF_ERR(status);
5207 1225 : for (int i = 0; i < nValues; ++i)
5208 : {
5209 1098 : char *pszKey = nullptr;
5210 1098 : const char *pszValue = CPLParseNameValue(papszKeyValues[i], &pszKey);
5211 1098 : if (pszKey && pszValue)
5212 : {
5213 2196 : const CPLStringList aosTokens(CSLTokenizeString2(pszValue, ",", 0));
5214 1098 : double adfValues[2] = {0, 0};
5215 1098 : const int nDoubleCount = std::min(2, aosTokens.size());
5216 1098 : if (!(aosTokens.size() == 2 &&
5217 2195 : CPLGetValueType(aosTokens[0]) != CPL_VALUE_STRING) &&
5218 1097 : CPLGetValueType(pszValue) == CPL_VALUE_STRING)
5219 : {
5220 507 : status = nc_put_att_text(cdfid, NCDFVarID, pszKey,
5221 : strlen(pszValue), pszValue);
5222 : }
5223 : else
5224 : {
5225 1183 : for (int j = 0; j < nDoubleCount; ++j)
5226 592 : adfValues[j] = CPLAtof(aosTokens[j]);
5227 591 : status = nc_put_att_double(cdfid, NCDFVarID, pszKey, NC_DOUBLE,
5228 : nDoubleCount, adfValues);
5229 : }
5230 1098 : NCDF_ERR(status);
5231 : }
5232 1098 : CPLFree(pszKey);
5233 : }
5234 :
5235 127 : CSLDestroy(papszKeyValues);
5236 127 : return NCDFVarID;
5237 : }
5238 :
5239 : /************************************************************************/
5240 : /* NCDFWriteLonLatVarsAttributes() */
5241 : /************************************************************************/
5242 :
5243 101 : void NCDFWriteLonLatVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarLonID,
5244 : int nVarLatID)
5245 : {
5246 :
5247 : try
5248 : {
5249 101 : vcdf.nc_put_vatt_text(nVarLatID, CF_STD_NAME, CF_LATITUDE_STD_NAME);
5250 101 : vcdf.nc_put_vatt_text(nVarLatID, CF_LNG_NAME, CF_LATITUDE_LNG_NAME);
5251 101 : vcdf.nc_put_vatt_text(nVarLatID, CF_UNITS, CF_DEGREES_NORTH);
5252 101 : vcdf.nc_put_vatt_text(nVarLonID, CF_STD_NAME, CF_LONGITUDE_STD_NAME);
5253 101 : vcdf.nc_put_vatt_text(nVarLonID, CF_LNG_NAME, CF_LONGITUDE_LNG_NAME);
5254 101 : vcdf.nc_put_vatt_text(nVarLonID, CF_UNITS, CF_DEGREES_EAST);
5255 : }
5256 0 : catch (nccfdriver::SG_Exception &e)
5257 : {
5258 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5259 : }
5260 101 : }
5261 :
5262 : /************************************************************************/
5263 : /* NCDFWriteRLonRLatVarsAttributes() */
5264 : /************************************************************************/
5265 :
5266 0 : void NCDFWriteRLonRLatVarsAttributes(nccfdriver::netCDFVID &vcdf,
5267 : int nVarRLonID, int nVarRLatID)
5268 : {
5269 : try
5270 : {
5271 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_STD_NAME, "grid_latitude");
5272 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_LNG_NAME,
5273 : "latitude in rotated pole grid");
5274 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_UNITS, "degrees");
5275 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_AXIS, "Y");
5276 :
5277 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_STD_NAME, "grid_longitude");
5278 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_LNG_NAME,
5279 : "longitude in rotated pole grid");
5280 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_UNITS, "degrees");
5281 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_AXIS, "X");
5282 : }
5283 0 : catch (nccfdriver::SG_Exception &e)
5284 : {
5285 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5286 : }
5287 0 : }
5288 :
5289 : /************************************************************************/
5290 : /* NCDFGetProjectedCFUnit() */
5291 : /************************************************************************/
5292 :
5293 42 : std::string NCDFGetProjectedCFUnit(const OGRSpatialReference *poSRS)
5294 : {
5295 42 : char *pszUnitsToWrite = nullptr;
5296 42 : poSRS->exportToCF1(nullptr, nullptr, &pszUnitsToWrite, nullptr);
5297 42 : std::string osRet = pszUnitsToWrite ? pszUnitsToWrite : std::string();
5298 42 : CPLFree(pszUnitsToWrite);
5299 84 : return osRet;
5300 : }
5301 :
5302 : /************************************************************************/
5303 : /* NCDFWriteXYVarsAttributes() */
5304 : /************************************************************************/
5305 :
5306 29 : void NCDFWriteXYVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarXID,
5307 : int nVarYID, const OGRSpatialReference *poSRS)
5308 : {
5309 58 : const std::string osUnitsToWrite = NCDFGetProjectedCFUnit(poSRS);
5310 :
5311 : try
5312 : {
5313 29 : vcdf.nc_put_vatt_text(nVarXID, CF_STD_NAME, CF_PROJ_X_COORD);
5314 29 : vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME, CF_PROJ_X_COORD_LONG_NAME);
5315 29 : if (!osUnitsToWrite.empty())
5316 29 : vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, osUnitsToWrite.c_str());
5317 29 : vcdf.nc_put_vatt_text(nVarYID, CF_STD_NAME, CF_PROJ_Y_COORD);
5318 29 : vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME, CF_PROJ_Y_COORD_LONG_NAME);
5319 29 : if (!osUnitsToWrite.empty())
5320 29 : vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, osUnitsToWrite.c_str());
5321 : }
5322 0 : catch (nccfdriver::SG_Exception &e)
5323 : {
5324 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5325 : }
5326 29 : }
5327 :
5328 : /************************************************************************/
5329 : /* AddProjectionVars() */
5330 : /************************************************************************/
5331 :
5332 164 : CPLErr netCDFDataset::AddProjectionVars(bool bDefsOnly,
5333 : GDALProgressFunc pfnProgress,
5334 : void *pProgressData)
5335 : {
5336 164 : if (nCFVersion >= 1.8)
5337 0 : return CE_None; // do nothing
5338 :
5339 164 : bool bWriteGridMapping = false;
5340 164 : bool bWriteLonLat = false;
5341 164 : bool bHasGeoloc = false;
5342 164 : bool bWriteGDALTags = false;
5343 164 : bool bWriteGeoTransform = false;
5344 :
5345 : // For GEOLOCATION information.
5346 164 : GDALDatasetUniquePtr poDS_X;
5347 164 : GDALDatasetUniquePtr poDS_Y;
5348 164 : GDALRasterBand *poBand_X = nullptr;
5349 164 : GDALRasterBand *poBand_Y = nullptr;
5350 :
5351 328 : OGRSpatialReference oSRS(m_oSRS);
5352 164 : if (!m_oSRS.IsEmpty())
5353 : {
5354 138 : if (oSRS.IsProjected())
5355 50 : bIsProjected = true;
5356 88 : else if (oSRS.IsGeographic())
5357 88 : bIsGeographic = true;
5358 : }
5359 :
5360 164 : if (bDefsOnly)
5361 : {
5362 82 : char *pszProjection = nullptr;
5363 82 : m_oSRS.exportToWkt(&pszProjection);
5364 82 : CPLDebug("GDAL_netCDF",
5365 : "SetProjection, WKT now = [%s]\nprojected: %d geographic: %d",
5366 82 : pszProjection ? pszProjection : "(null)",
5367 82 : static_cast<int>(bIsProjected),
5368 82 : static_cast<int>(bIsGeographic));
5369 82 : CPLFree(pszProjection);
5370 :
5371 82 : if (!m_bHasGeoTransform)
5372 5 : CPLDebug("GDAL_netCDF",
5373 : "netCDFDataset::AddProjectionVars() called, "
5374 : "but GeoTransform has not yet been defined!");
5375 :
5376 82 : if (!m_bHasProjection)
5377 6 : CPLDebug("GDAL_netCDF",
5378 : "netCDFDataset::AddProjectionVars() called, "
5379 : "but Projection has not yet been defined!");
5380 : }
5381 :
5382 : // Check GEOLOCATION information.
5383 : CSLConstList papszGeolocationInfo =
5384 164 : netCDFDataset::GetMetadata("GEOLOCATION");
5385 164 : if (papszGeolocationInfo != nullptr)
5386 : {
5387 : // Look for geolocation datasets.
5388 : const char *pszDSName =
5389 10 : CSLFetchNameValue(papszGeolocationInfo, "X_DATASET");
5390 10 : if (pszDSName != nullptr)
5391 10 : poDS_X.reset(GDALDataset::Open(
5392 : pszDSName,
5393 : GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR | GDAL_OF_SHARED));
5394 10 : pszDSName = CSLFetchNameValue(papszGeolocationInfo, "Y_DATASET");
5395 10 : if (pszDSName != nullptr)
5396 10 : poDS_Y.reset(GDALDataset::Open(
5397 : pszDSName,
5398 : GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR | GDAL_OF_SHARED));
5399 :
5400 10 : if (poDS_X != nullptr && poDS_Y != nullptr)
5401 : {
5402 10 : int nBand = std::max(1, atoi(CSLFetchNameValueDef(
5403 10 : papszGeolocationInfo, "X_BAND", "0")));
5404 10 : poBand_X = poDS_X->GetRasterBand(nBand);
5405 10 : nBand = std::max(1, atoi(CSLFetchNameValueDef(papszGeolocationInfo,
5406 10 : "Y_BAND", "0")));
5407 10 : poBand_Y = poDS_Y->GetRasterBand(nBand);
5408 :
5409 : // If geoloc bands are found, do basic validation based on their
5410 : // dimensions.
5411 10 : if (poBand_X != nullptr && poBand_Y != nullptr)
5412 : {
5413 10 : const int nXSize_XBand = poBand_X->GetXSize();
5414 10 : const int nYSize_XBand = poBand_X->GetYSize();
5415 10 : const int nXSize_YBand = poBand_Y->GetXSize();
5416 10 : const int nYSize_YBand = poBand_Y->GetYSize();
5417 :
5418 : // TODO 1D geolocation arrays not implemented.
5419 10 : if (nYSize_XBand == 1 && nYSize_YBand == 1)
5420 : {
5421 0 : bHasGeoloc = false;
5422 0 : CPLDebug("GDAL_netCDF",
5423 : "1D GEOLOCATION arrays not supported yet");
5424 : }
5425 : // 2D bands must have same sizes as the raster bands.
5426 10 : else if (nXSize_XBand != nRasterXSize ||
5427 10 : nYSize_XBand != nRasterYSize ||
5428 10 : nXSize_YBand != nRasterXSize ||
5429 10 : nYSize_YBand != nRasterYSize)
5430 : {
5431 0 : bHasGeoloc = false;
5432 0 : CPLDebug("GDAL_netCDF",
5433 : "GEOLOCATION array sizes (%dx%d %dx%d) differ "
5434 : "from raster (%dx%d), not supported",
5435 : nXSize_XBand, nYSize_XBand, nXSize_YBand,
5436 : nYSize_YBand, nRasterXSize, nRasterYSize);
5437 : }
5438 : else
5439 : {
5440 10 : bHasGeoloc = true;
5441 10 : CPLDebug("GDAL_netCDF",
5442 : "dataset has GEOLOCATION information, will try to "
5443 : "write it");
5444 : }
5445 : }
5446 : }
5447 : }
5448 :
5449 : // Process projection options.
5450 164 : if (bIsProjected)
5451 : {
5452 : bool bIsCfProjection =
5453 50 : oSRS.exportToCF1(nullptr, nullptr, nullptr, nullptr) == OGRERR_NONE;
5454 50 : bWriteGridMapping = true;
5455 50 : bWriteGDALTags = CPL_TO_BOOL(
5456 50 : CSLFetchBoolean(papszCreationOptions, "WRITE_GDAL_TAGS", TRUE));
5457 : // Force WRITE_GDAL_TAGS if is not a CF projection.
5458 50 : if (!bWriteGDALTags && !bIsCfProjection)
5459 0 : bWriteGDALTags = true;
5460 50 : if (bWriteGDALTags)
5461 50 : bWriteGeoTransform = true;
5462 :
5463 : // Write lon/lat: default is NO, except if has geolocation.
5464 : // With IF_NEEDED: write if has geoloc or is not CF projection.
5465 : const char *pszValue =
5466 50 : CSLFetchNameValue(papszCreationOptions, "WRITE_LONLAT");
5467 50 : if (pszValue)
5468 : {
5469 2 : if (EQUAL(pszValue, "IF_NEEDED"))
5470 : {
5471 0 : bWriteLonLat = bHasGeoloc || !bIsCfProjection;
5472 : }
5473 : else
5474 : {
5475 2 : bWriteLonLat = CPLTestBool(pszValue);
5476 : }
5477 : }
5478 : else
5479 : {
5480 48 : bWriteLonLat = bHasGeoloc;
5481 : }
5482 :
5483 : // Save value of pszCFCoordinates for later.
5484 50 : if (bWriteLonLat)
5485 : {
5486 4 : pszCFCoordinates = NCDF_LONLAT;
5487 : }
5488 : }
5489 : else
5490 : {
5491 : // Files without a Datum will not have a grid_mapping variable and
5492 : // geographic information.
5493 114 : bWriteGridMapping = bIsGeographic;
5494 :
5495 114 : if (bHasGeoloc)
5496 : {
5497 8 : bWriteLonLat = true;
5498 : }
5499 : else
5500 : {
5501 106 : bWriteGDALTags = CPL_TO_BOOL(CSLFetchBoolean(
5502 106 : papszCreationOptions, "WRITE_GDAL_TAGS", bWriteGridMapping));
5503 106 : if (bWriteGDALTags)
5504 88 : bWriteGeoTransform = true;
5505 :
5506 106 : const char *pszValue = CSLFetchNameValueDef(papszCreationOptions,
5507 : "WRITE_LONLAT", "YES");
5508 106 : if (EQUAL(pszValue, "IF_NEEDED"))
5509 0 : bWriteLonLat = true;
5510 : else
5511 106 : bWriteLonLat = CPLTestBool(pszValue);
5512 : // Don't write lon/lat if no source geotransform.
5513 106 : if (!m_bHasGeoTransform)
5514 0 : bWriteLonLat = false;
5515 : // If we don't write lon/lat, set dimnames to X/Y and write gdal
5516 : // tags.
5517 106 : if (!bWriteLonLat)
5518 : {
5519 0 : CPLError(CE_Warning, CPLE_AppDefined,
5520 : "creating geographic file without lon/lat values!");
5521 0 : if (m_bHasGeoTransform)
5522 : {
5523 0 : bWriteGDALTags = true; // Not desirable if no geotransform.
5524 0 : bWriteGeoTransform = true;
5525 : }
5526 : }
5527 : }
5528 : }
5529 :
5530 : // Make sure we write grid_mapping if we need to write GDAL tags.
5531 164 : if (bWriteGDALTags)
5532 138 : bWriteGridMapping = true;
5533 :
5534 : // bottom-up value: new driver is bottom-up by default.
5535 : // Override with WRITE_BOTTOMUP.
5536 164 : bBottomUp = CPL_TO_BOOL(
5537 164 : CSLFetchBoolean(papszCreationOptions, "WRITE_BOTTOMUP", TRUE));
5538 :
5539 164 : if (bDefsOnly)
5540 : {
5541 82 : CPLDebug(
5542 : "GDAL_netCDF",
5543 : "bIsProjected=%d bIsGeographic=%d bWriteGridMapping=%d "
5544 : "bWriteGDALTags=%d bWriteLonLat=%d bBottomUp=%d bHasGeoloc=%d",
5545 82 : static_cast<int>(bIsProjected), static_cast<int>(bIsGeographic),
5546 : static_cast<int>(bWriteGridMapping),
5547 : static_cast<int>(bWriteGDALTags), static_cast<int>(bWriteLonLat),
5548 82 : static_cast<int>(bBottomUp), static_cast<int>(bHasGeoloc));
5549 : }
5550 :
5551 : // Exit if nothing to do.
5552 164 : if (!bIsProjected && !bWriteLonLat)
5553 0 : return CE_None;
5554 :
5555 : // Define dimension names.
5556 :
5557 164 : constexpr const char *ROTATED_POLE_VAR_NAME = "rotated_pole";
5558 :
5559 164 : if (bDefsOnly)
5560 : {
5561 82 : int nVarLonID = -1;
5562 82 : int nVarLatID = -1;
5563 82 : int nVarXID = -1;
5564 82 : int nVarYID = -1;
5565 :
5566 82 : m_bAddedProjectionVarsDefs = true;
5567 :
5568 : // Make sure we are in define mode.
5569 82 : SetDefineMode(true);
5570 :
5571 : // Write projection attributes.
5572 82 : if (bWriteGridMapping)
5573 : {
5574 69 : const int NCDFVarID = NCDFWriteSRSVariable(
5575 : cdfid, &oSRS, &pszCFProjection, bWriteGDALTags);
5576 69 : if (NCDFVarID < 0)
5577 0 : return CE_Failure;
5578 :
5579 : // Optional GDAL custom projection tags.
5580 69 : if (bWriteGDALTags)
5581 : {
5582 138 : CPLString osGeoTransform;
5583 483 : for (int i = 0; i < 6; i++)
5584 : {
5585 414 : osGeoTransform += CPLSPrintf("%.17g ", m_gt[i]);
5586 : }
5587 69 : CPLDebug("GDAL_netCDF", "szGeoTransform = %s",
5588 : osGeoTransform.c_str());
5589 :
5590 : // if( strlen(pszProj4Defn) > 0 ) {
5591 : // nc_put_att_text(cdfid, NCDFVarID, "proj4",
5592 : // strlen(pszProj4Defn), pszProj4Defn);
5593 : // }
5594 :
5595 : // For now, write the geotransform for back-compat or else
5596 : // the old (1.8.1) driver overrides the CF geotransform with
5597 : // empty values from dfNN, dfSN, dfEE, dfWE;
5598 :
5599 : // TODO: fix this in 1.8 branch, and then remove this here.
5600 69 : if (bWriteGeoTransform && m_bHasGeoTransform)
5601 : {
5602 : {
5603 68 : const int status = nc_put_att_text(
5604 : cdfid, NCDFVarID, NCDF_GEOTRANSFORM,
5605 : osGeoTransform.size(), osGeoTransform.c_str());
5606 68 : NCDF_ERR(status);
5607 : }
5608 : }
5609 : }
5610 :
5611 : // Write projection variable to band variable.
5612 : // Need to call later if there are no bands.
5613 69 : AddGridMappingRef();
5614 : } // end if( bWriteGridMapping )
5615 :
5616 : // Write CF Projection vars.
5617 :
5618 82 : const bool bIsRotatedPole =
5619 151 : pszCFProjection != nullptr &&
5620 69 : EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
5621 82 : if (bIsRotatedPole)
5622 : {
5623 : // Rename dims to rlat/rlon.
5624 : papszDimName
5625 0 : .Clear(); // If we add other dims one day, this has to change
5626 0 : papszDimName.AddString(NCDF_DIMNAME_RLAT);
5627 0 : papszDimName.AddString(NCDF_DIMNAME_RLON);
5628 :
5629 0 : int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_RLAT);
5630 0 : NCDF_ERR(status);
5631 0 : status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_RLON);
5632 0 : NCDF_ERR(status);
5633 : }
5634 : // Rename dimensions if lon/lat.
5635 82 : else if (!bIsProjected && !bHasGeoloc)
5636 : {
5637 : // Rename dims to lat/lon.
5638 : papszDimName
5639 53 : .Clear(); // If we add other dims one day, this has to change
5640 53 : papszDimName.AddString(NCDF_DIMNAME_LAT);
5641 53 : papszDimName.AddString(NCDF_DIMNAME_LON);
5642 :
5643 53 : int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_LAT);
5644 53 : NCDF_ERR(status);
5645 53 : status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_LON);
5646 53 : NCDF_ERR(status);
5647 : }
5648 :
5649 : // Write X/Y attributes.
5650 : else /* if( bIsProjected || bHasGeoloc ) */
5651 : {
5652 : // X
5653 : int anXDims[1];
5654 29 : anXDims[0] = nXDimID;
5655 29 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
5656 : CF_PROJ_X_VAR_NAME, NC_DOUBLE);
5657 29 : int status = nc_def_var(cdfid, CF_PROJ_X_VAR_NAME, NC_DOUBLE, 1,
5658 : anXDims, &nVarXID);
5659 29 : NCDF_ERR(status);
5660 :
5661 : // Y
5662 : int anYDims[1];
5663 29 : anYDims[0] = nYDimID;
5664 29 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
5665 : CF_PROJ_Y_VAR_NAME, NC_DOUBLE);
5666 29 : status = nc_def_var(cdfid, CF_PROJ_Y_VAR_NAME, NC_DOUBLE, 1,
5667 : anYDims, &nVarYID);
5668 29 : NCDF_ERR(status);
5669 :
5670 29 : if (bIsProjected)
5671 : {
5672 25 : NCDFWriteXYVarsAttributes(this->vcdf, nVarXID, nVarYID, &oSRS);
5673 : }
5674 : else
5675 : {
5676 4 : CPLAssert(bHasGeoloc);
5677 : try
5678 : {
5679 4 : vcdf.nc_put_vatt_text(nVarXID, CF_AXIS, CF_SG_X_AXIS);
5680 4 : vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME,
5681 : "x-coordinate in Cartesian system");
5682 4 : vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, "m");
5683 4 : vcdf.nc_put_vatt_text(nVarYID, CF_AXIS, CF_SG_Y_AXIS);
5684 4 : vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME,
5685 : "y-coordinate in Cartesian system");
5686 4 : vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, "m");
5687 :
5688 4 : pszCFCoordinates = NCDF_LONLAT;
5689 : }
5690 0 : catch (nccfdriver::SG_Exception &e)
5691 : {
5692 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5693 0 : return CE_Failure;
5694 : }
5695 : }
5696 : }
5697 :
5698 : // Write lat/lon attributes if needed.
5699 82 : if (bWriteLonLat)
5700 : {
5701 59 : int anLatDims[2] = {0, 0};
5702 59 : int anLonDims[2] = {0, 0};
5703 59 : int nLatDims = -1;
5704 59 : int nLonDims = -1;
5705 :
5706 : // Get information.
5707 59 : if (bHasGeoloc)
5708 : {
5709 : // Geoloc
5710 5 : nLatDims = 2;
5711 5 : anLatDims[0] = nYDimID;
5712 5 : anLatDims[1] = nXDimID;
5713 5 : nLonDims = 2;
5714 5 : anLonDims[0] = nYDimID;
5715 5 : anLonDims[1] = nXDimID;
5716 : }
5717 54 : else if (bIsProjected)
5718 : {
5719 : // Projected
5720 1 : nLatDims = 2;
5721 1 : anLatDims[0] = nYDimID;
5722 1 : anLatDims[1] = nXDimID;
5723 1 : nLonDims = 2;
5724 1 : anLonDims[0] = nYDimID;
5725 1 : anLonDims[1] = nXDimID;
5726 : }
5727 : else
5728 : {
5729 : // Geographic
5730 53 : nLatDims = 1;
5731 53 : anLatDims[0] = nYDimID;
5732 53 : nLonDims = 1;
5733 53 : anLonDims[0] = nXDimID;
5734 : }
5735 :
5736 59 : nc_type eLonLatType = NC_NAT;
5737 59 : if (bIsProjected)
5738 : {
5739 2 : eLonLatType = NC_FLOAT;
5740 4 : const char *pszValue = CSLFetchNameValueDef(
5741 2 : papszCreationOptions, "TYPE_LONLAT", "FLOAT");
5742 2 : if (EQUAL(pszValue, "DOUBLE"))
5743 0 : eLonLatType = NC_DOUBLE;
5744 : }
5745 : else
5746 : {
5747 57 : eLonLatType = NC_DOUBLE;
5748 114 : const char *pszValue = CSLFetchNameValueDef(
5749 57 : papszCreationOptions, "TYPE_LONLAT", "DOUBLE");
5750 57 : if (EQUAL(pszValue, "FLOAT"))
5751 0 : eLonLatType = NC_FLOAT;
5752 : }
5753 :
5754 : // Def vars and attributes.
5755 : {
5756 59 : const char *pszVarName =
5757 59 : bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME;
5758 59 : int status = nc_def_var(cdfid, pszVarName, eLonLatType,
5759 : nLatDims, anLatDims, &nVarLatID);
5760 59 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
5761 : cdfid, pszVarName, eLonLatType, nLatDims, nVarLatID);
5762 59 : NCDF_ERR(status);
5763 59 : DefVarDeflate(nVarLatID, false); // Don't set chunking.
5764 : }
5765 :
5766 : {
5767 59 : const char *pszVarName =
5768 59 : bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME;
5769 59 : int status = nc_def_var(cdfid, pszVarName, eLonLatType,
5770 : nLonDims, anLonDims, &nVarLonID);
5771 59 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
5772 : cdfid, pszVarName, eLonLatType, nLatDims, nVarLonID);
5773 59 : NCDF_ERR(status);
5774 59 : DefVarDeflate(nVarLonID, false); // Don't set chunking.
5775 : }
5776 :
5777 59 : if (bIsRotatedPole)
5778 0 : NCDFWriteRLonRLatVarsAttributes(this->vcdf, nVarLonID,
5779 : nVarLatID);
5780 : else
5781 59 : NCDFWriteLonLatVarsAttributes(this->vcdf, nVarLonID, nVarLatID);
5782 : }
5783 : }
5784 :
5785 164 : if (!bDefsOnly)
5786 : {
5787 82 : m_bAddedProjectionVarsData = true;
5788 :
5789 82 : int nVarXID = -1;
5790 82 : int nVarYID = -1;
5791 :
5792 82 : nc_inq_varid(cdfid, CF_PROJ_X_VAR_NAME, &nVarXID);
5793 82 : nc_inq_varid(cdfid, CF_PROJ_Y_VAR_NAME, &nVarYID);
5794 :
5795 82 : int nVarLonID = -1;
5796 82 : int nVarLatID = -1;
5797 :
5798 82 : const bool bIsRotatedPole =
5799 151 : pszCFProjection != nullptr &&
5800 69 : EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
5801 82 : nc_inq_varid(cdfid,
5802 : bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME,
5803 : &nVarLonID);
5804 82 : nc_inq_varid(cdfid,
5805 : bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME,
5806 : &nVarLatID);
5807 :
5808 : // Get projection values.
5809 :
5810 82 : if (bIsProjected)
5811 : {
5812 0 : std::unique_ptr<OGRSpatialReference> poLatLonSRS;
5813 0 : std::unique_ptr<OGRCoordinateTransformation> poTransform;
5814 :
5815 : size_t startX[1];
5816 : size_t countX[1];
5817 : size_t startY[1];
5818 : size_t countY[1];
5819 :
5820 25 : CPLDebug("GDAL_netCDF", "Getting (X,Y) values");
5821 :
5822 : std::unique_ptr<double, decltype(&VSIFree)> adXValKeeper(
5823 : static_cast<double *>(
5824 50 : VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
5825 25 : VSIFree);
5826 : std::unique_ptr<double, decltype(&VSIFree)> adYValKeeper(
5827 : static_cast<double *>(
5828 50 : VSI_MALLOC2_VERBOSE(nRasterYSize, sizeof(double))),
5829 25 : VSIFree);
5830 25 : double *padXVal = adXValKeeper.get();
5831 25 : double *padYVal = adYValKeeper.get();
5832 25 : if (!padXVal || !padYVal)
5833 : {
5834 0 : return CE_Failure;
5835 : }
5836 :
5837 : // Get Y values.
5838 25 : const double dfY0 = (!bBottomUp) ? m_gt[3] :
5839 : // Invert latitude values.
5840 25 : m_gt[3] + (m_gt[5] * nRasterYSize);
5841 25 : const double dfDY = m_gt[5];
5842 :
5843 1456 : for (int j = 0; j < nRasterYSize; j++)
5844 : {
5845 : // The data point is centered inside the pixel.
5846 1431 : if (!bBottomUp)
5847 0 : padYVal[j] = dfY0 + (j + 0.5) * dfDY;
5848 : else // Invert latitude values.
5849 1431 : padYVal[j] = dfY0 - (j + 0.5) * dfDY;
5850 : }
5851 25 : startX[0] = 0;
5852 25 : countX[0] = nRasterXSize;
5853 :
5854 : // Get X values.
5855 25 : const double dfX0 = m_gt[0];
5856 25 : const double dfDX = m_gt[1];
5857 :
5858 1477 : for (int i = 0; i < nRasterXSize; i++)
5859 : {
5860 : // The data point is centered inside the pixel.
5861 1452 : padXVal[i] = dfX0 + (i + 0.5) * dfDX;
5862 : }
5863 25 : startY[0] = 0;
5864 25 : countY[0] = nRasterYSize;
5865 :
5866 : // Write X/Y values.
5867 :
5868 : // Make sure we are in data mode.
5869 25 : SetDefineMode(false);
5870 :
5871 25 : CPLDebug("GDAL_netCDF", "Writing X values");
5872 : int status =
5873 25 : nc_put_vara_double(cdfid, nVarXID, startX, countX, padXVal);
5874 25 : NCDF_ERR(status);
5875 :
5876 25 : CPLDebug("GDAL_netCDF", "Writing Y values");
5877 : status =
5878 25 : nc_put_vara_double(cdfid, nVarYID, startY, countY, padYVal);
5879 25 : NCDF_ERR(status);
5880 :
5881 25 : if (pfnProgress)
5882 21 : pfnProgress(0.20, nullptr, pProgressData);
5883 :
5884 : // Write lon/lat arrays (CF coordinates) if requested.
5885 :
5886 : // Get OGR transform if GEOLOCATION is not available.
5887 25 : if (bWriteLonLat && !bHasGeoloc)
5888 : {
5889 1 : poLatLonSRS.reset(m_oSRS.CloneGeogCS());
5890 1 : if (poLatLonSRS != nullptr)
5891 : {
5892 1 : poLatLonSRS->SetAxisMappingStrategy(
5893 : OAMS_TRADITIONAL_GIS_ORDER);
5894 1 : poTransform.reset(OGRCreateCoordinateTransformation(
5895 1 : &m_oSRS, poLatLonSRS.get()));
5896 : }
5897 : // If no OGR transform, then don't write CF lon/lat.
5898 1 : if (poTransform == nullptr)
5899 : {
5900 0 : CPLError(CE_Failure, CPLE_AppDefined,
5901 : "Unable to get Coordinate Transform");
5902 0 : bWriteLonLat = false;
5903 : }
5904 : }
5905 :
5906 25 : if (bWriteLonLat)
5907 : {
5908 2 : if (!bHasGeoloc)
5909 1 : CPLDebug("GDAL_netCDF", "Transforming (X,Y)->(lon,lat)");
5910 : else
5911 1 : CPLDebug("GDAL_netCDF",
5912 : "Writing (lon,lat) from GEOLOCATION arrays");
5913 :
5914 2 : bool bOK = true;
5915 2 : double dfProgress = 0.2;
5916 :
5917 2 : size_t start[] = {0, 0};
5918 2 : size_t count[] = {1, (size_t)nRasterXSize};
5919 : std::unique_ptr<double, decltype(&VSIFree)> adLatValKeeper(
5920 : static_cast<double *>(
5921 4 : VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
5922 2 : VSIFree);
5923 : std::unique_ptr<double, decltype(&VSIFree)> adLonValKeeper(
5924 : static_cast<double *>(
5925 4 : VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
5926 2 : VSIFree);
5927 2 : double *padLonVal = adLonValKeeper.get();
5928 2 : double *padLatVal = adLatValKeeper.get();
5929 2 : if (!padLonVal || !padLatVal)
5930 : {
5931 0 : return CE_Failure;
5932 : }
5933 :
5934 61 : for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR;
5935 : j++)
5936 : {
5937 59 : start[0] = j;
5938 :
5939 : // Get values from geotransform.
5940 59 : if (!bHasGeoloc)
5941 : {
5942 : // Fill values to transform.
5943 420 : for (int i = 0; i < nRasterXSize; i++)
5944 : {
5945 400 : padLatVal[i] = padYVal[j];
5946 400 : padLonVal[i] = padXVal[i];
5947 : }
5948 :
5949 : // Do the transform.
5950 40 : bOK = CPL_TO_BOOL(poTransform->Transform(
5951 20 : nRasterXSize, padLonVal, padLatVal, nullptr));
5952 20 : if (!bOK)
5953 : {
5954 0 : CPLError(CE_Failure, CPLE_AppDefined,
5955 : "Unable to Transform (X,Y) to (lon,lat).");
5956 : }
5957 : }
5958 : // Get values from geoloc arrays.
5959 : else
5960 : {
5961 39 : CPLErr eErr = poBand_Y->RasterIO(
5962 : GF_Read, 0, j, nRasterXSize, 1, padLatVal,
5963 : nRasterXSize, 1, GDT_Float64, 0, 0, nullptr);
5964 39 : if (eErr == CE_None)
5965 : {
5966 39 : eErr = poBand_X->RasterIO(
5967 : GF_Read, 0, j, nRasterXSize, 1, padLonVal,
5968 : nRasterXSize, 1, GDT_Float64, 0, 0, nullptr);
5969 : }
5970 :
5971 39 : if (eErr == CE_None)
5972 : {
5973 39 : bOK = true;
5974 : }
5975 : else
5976 : {
5977 0 : bOK = false;
5978 0 : CPLError(CE_Failure, CPLE_AppDefined,
5979 : "Unable to get scanline %d", j);
5980 : }
5981 : }
5982 :
5983 : // Write data.
5984 59 : if (bOK)
5985 : {
5986 59 : status = nc_put_vara_double(cdfid, nVarLatID, start,
5987 : count, padLatVal);
5988 59 : NCDF_ERR(status);
5989 59 : status = nc_put_vara_double(cdfid, nVarLonID, start,
5990 : count, padLonVal);
5991 59 : NCDF_ERR(status);
5992 : }
5993 :
5994 59 : if (pfnProgress && (nRasterYSize / 10) > 0 &&
5995 59 : (j % (nRasterYSize / 10) == 0))
5996 : {
5997 23 : dfProgress += 0.08;
5998 23 : pfnProgress(dfProgress, nullptr, pProgressData);
5999 : }
6000 : }
6001 : }
6002 : } // Projected
6003 :
6004 : // If not projected/geographic and has geoloc
6005 57 : else if (!bIsGeographic && bHasGeoloc)
6006 : {
6007 : // Use
6008 : // https://cfconventions.org/Data/cf-conventions/cf-conventions-1.9/cf-conventions.html#_two_dimensional_latitude_longitude_coordinate_variables
6009 :
6010 4 : bool bOK = true;
6011 4 : double dfProgress = 0.2;
6012 :
6013 : // Make sure we are in data mode.
6014 4 : SetDefineMode(false);
6015 :
6016 : size_t startX[1];
6017 : size_t countX[1];
6018 : size_t startY[1];
6019 : size_t countY[1];
6020 4 : startX[0] = 0;
6021 4 : countX[0] = nRasterXSize;
6022 :
6023 4 : startY[0] = 0;
6024 4 : countY[0] = nRasterYSize;
6025 :
6026 4 : std::vector<double> adfXVal;
6027 4 : std::vector<double> adfYVal;
6028 : try
6029 : {
6030 4 : adfXVal.resize(nRasterXSize);
6031 4 : adfYVal.resize(nRasterYSize);
6032 : }
6033 0 : catch (const std::exception &)
6034 : {
6035 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
6036 : "Out of memory allocating temporary array");
6037 0 : return CE_Failure;
6038 : }
6039 16 : for (int i = 0; i < nRasterXSize; i++)
6040 12 : adfXVal[i] = i;
6041 12 : for (int i = 0; i < nRasterYSize; i++)
6042 8 : adfYVal[i] = bBottomUp ? nRasterYSize - 1 - i : i;
6043 :
6044 4 : CPLDebug("GDAL_netCDF", "Writing X values");
6045 4 : int status = nc_put_vara_double(cdfid, nVarXID, startX, countX,
6046 4 : adfXVal.data());
6047 4 : NCDF_ERR(status);
6048 :
6049 4 : CPLDebug("GDAL_netCDF", "Writing Y values");
6050 4 : status = nc_put_vara_double(cdfid, nVarYID, startY, countY,
6051 4 : adfYVal.data());
6052 4 : NCDF_ERR(status);
6053 :
6054 4 : if (pfnProgress)
6055 0 : pfnProgress(0.20, nullptr, pProgressData);
6056 :
6057 4 : size_t start[] = {0, 0};
6058 4 : size_t count[] = {1, (size_t)nRasterXSize};
6059 :
6060 : std::unique_ptr<double, decltype(&VSIFree)> adLatValKeeper(
6061 : static_cast<double *>(
6062 8 : VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
6063 4 : VSIFree);
6064 : std::unique_ptr<double, decltype(&VSIFree)> adLonValKeeper(
6065 : static_cast<double *>(
6066 8 : VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
6067 4 : VSIFree);
6068 4 : double *padLonVal = adLonValKeeper.get();
6069 4 : double *padLatVal = adLatValKeeper.get();
6070 4 : if (!padLonVal || !padLatVal)
6071 : {
6072 0 : return CE_Failure;
6073 : }
6074 :
6075 12 : for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR; j++)
6076 : {
6077 8 : start[0] = j;
6078 :
6079 8 : CPLErr eErr = poBand_Y->RasterIO(
6080 8 : GF_Read, 0, bBottomUp ? nRasterYSize - 1 - j : j,
6081 : nRasterXSize, 1, padLatVal, nRasterXSize, 1, GDT_Float64, 0,
6082 : 0, nullptr);
6083 8 : if (eErr == CE_None)
6084 : {
6085 8 : eErr = poBand_X->RasterIO(
6086 8 : GF_Read, 0, bBottomUp ? nRasterYSize - 1 - j : j,
6087 : nRasterXSize, 1, padLonVal, nRasterXSize, 1,
6088 : GDT_Float64, 0, 0, nullptr);
6089 : }
6090 :
6091 8 : if (eErr == CE_None)
6092 : {
6093 8 : bOK = true;
6094 : }
6095 : else
6096 : {
6097 0 : bOK = false;
6098 0 : CPLError(CE_Failure, CPLE_AppDefined,
6099 : "Unable to get scanline %d", j);
6100 : }
6101 :
6102 : // Write data.
6103 8 : if (bOK)
6104 : {
6105 8 : status = nc_put_vara_double(cdfid, nVarLatID, start, count,
6106 : padLatVal);
6107 8 : NCDF_ERR(status);
6108 8 : status = nc_put_vara_double(cdfid, nVarLonID, start, count,
6109 : padLonVal);
6110 8 : NCDF_ERR(status);
6111 : }
6112 :
6113 8 : if (pfnProgress && (nRasterYSize / 10) > 0 &&
6114 0 : (j % (nRasterYSize / 10) == 0))
6115 : {
6116 0 : dfProgress += 0.08;
6117 0 : pfnProgress(dfProgress, nullptr, pProgressData);
6118 : }
6119 4 : }
6120 : }
6121 :
6122 : // If not projected, assume geographic to catch grids without Datum.
6123 53 : else if (bWriteLonLat)
6124 : {
6125 : // Get latitude values.
6126 53 : const double dfY0 = (!bBottomUp) ? m_gt[3] :
6127 : // Invert latitude values.
6128 53 : m_gt[3] + (m_gt[5] * nRasterYSize);
6129 53 : const double dfDY = m_gt[5];
6130 :
6131 : std::unique_ptr<double, decltype(&VSIFree)> adLatValKeeper(nullptr,
6132 53 : VSIFree);
6133 53 : double *padLatVal = nullptr;
6134 : // Override lat values with the ones in GEOLOCATION/Y_VALUES.
6135 53 : if (netCDFDataset::GetMetadataItem("Y_VALUES", "GEOLOCATION") !=
6136 : nullptr)
6137 : {
6138 0 : int nTemp = 0;
6139 0 : adLatValKeeper.reset(Get1DGeolocation("Y_VALUES", nTemp));
6140 0 : padLatVal = adLatValKeeper.get();
6141 : // Make sure we got the correct amount, if not fallback to GT */
6142 : // could add test fabs(fabs(padLatVal[0]) - fabs(dfY0)) <= 0.1))
6143 0 : if (nTemp == nRasterYSize)
6144 : {
6145 0 : CPLDebug(
6146 : "GDAL_netCDF",
6147 : "Using Y_VALUES geolocation metadata for lat values");
6148 : }
6149 : else
6150 : {
6151 0 : CPLDebug("GDAL_netCDF",
6152 : "Got %d elements from Y_VALUES geolocation "
6153 : "metadata, need %d",
6154 : nTemp, nRasterYSize);
6155 0 : padLatVal = nullptr;
6156 : }
6157 : }
6158 :
6159 53 : if (padLatVal == nullptr)
6160 : {
6161 53 : adLatValKeeper.reset(static_cast<double *>(
6162 53 : VSI_MALLOC2_VERBOSE(nRasterYSize, sizeof(double))));
6163 53 : padLatVal = adLatValKeeper.get();
6164 53 : if (!padLatVal)
6165 : {
6166 0 : return CE_Failure;
6167 : }
6168 7105 : for (int i = 0; i < nRasterYSize; i++)
6169 : {
6170 : // The data point is centered inside the pixel.
6171 7052 : if (!bBottomUp)
6172 0 : padLatVal[i] = dfY0 + (i + 0.5) * dfDY;
6173 : else // Invert latitude values.
6174 7052 : padLatVal[i] = dfY0 - (i + 0.5) * dfDY;
6175 : }
6176 : }
6177 :
6178 53 : size_t startLat[1] = {0};
6179 53 : size_t countLat[1] = {static_cast<size_t>(nRasterYSize)};
6180 :
6181 : // Get longitude values.
6182 53 : const double dfX0 = m_gt[0];
6183 53 : const double dfDX = m_gt[1];
6184 :
6185 : std::unique_ptr<double, decltype(&VSIFree)> adLonValKeeper(
6186 : static_cast<double *>(
6187 106 : VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
6188 53 : VSIFree);
6189 53 : double *padLonVal = adLonValKeeper.get();
6190 53 : if (!padLonVal)
6191 : {
6192 0 : return CE_Failure;
6193 : }
6194 7157 : for (int i = 0; i < nRasterXSize; i++)
6195 : {
6196 : // The data point is centered inside the pixel.
6197 7104 : padLonVal[i] = dfX0 + (i + 0.5) * dfDX;
6198 : }
6199 :
6200 53 : size_t startLon[1] = {0};
6201 53 : size_t countLon[1] = {static_cast<size_t>(nRasterXSize)};
6202 :
6203 : // Write latitude and longitude values.
6204 :
6205 : // Make sure we are in data mode.
6206 53 : SetDefineMode(false);
6207 :
6208 : // Write values.
6209 53 : CPLDebug("GDAL_netCDF", "Writing lat values");
6210 :
6211 53 : int status = nc_put_vara_double(cdfid, nVarLatID, startLat,
6212 : countLat, padLatVal);
6213 53 : NCDF_ERR(status);
6214 :
6215 53 : CPLDebug("GDAL_netCDF", "Writing lon values");
6216 53 : status = nc_put_vara_double(cdfid, nVarLonID, startLon, countLon,
6217 : padLonVal);
6218 53 : NCDF_ERR(status);
6219 :
6220 : } // Not projected.
6221 :
6222 82 : if (pfnProgress)
6223 41 : pfnProgress(1.00, nullptr, pProgressData);
6224 : }
6225 :
6226 164 : return CE_None;
6227 : }
6228 :
6229 : // Write Projection variable to band variable.
6230 : // Moved from AddProjectionVars() for cases when bands are added after
6231 : // projection.
6232 426 : bool netCDFDataset::AddGridMappingRef()
6233 : {
6234 426 : bool bRet = true;
6235 426 : bool bOldDefineMode = bDefineMode;
6236 :
6237 617 : if ((GetAccess() == GA_Update) && (nBands >= 1) && (GetRasterBand(1)) &&
6238 191 : ((pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, "")) ||
6239 185 : (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))))
6240 : {
6241 73 : bAddedGridMappingRef = true;
6242 :
6243 : // Make sure we are in define mode.
6244 73 : SetDefineMode(true);
6245 :
6246 192 : for (int i = 1; i <= nBands; i++)
6247 : {
6248 : const int nVarId =
6249 119 : static_cast<netCDFRasterBand *>(GetRasterBand(i))->nZId;
6250 :
6251 119 : if (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))
6252 : {
6253 : int status =
6254 230 : nc_put_att_text(cdfid, nVarId, CF_GRD_MAPPING,
6255 115 : strlen(pszCFProjection), pszCFProjection);
6256 115 : NCDF_ERR(status);
6257 115 : if (status != NC_NOERR)
6258 0 : bRet = false;
6259 : }
6260 119 : if (pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, ""))
6261 : {
6262 : int status =
6263 6 : nc_put_att_text(cdfid, nVarId, CF_COORDINATES,
6264 : strlen(pszCFCoordinates), pszCFCoordinates);
6265 6 : NCDF_ERR(status);
6266 6 : if (status != NC_NOERR)
6267 0 : bRet = false;
6268 : }
6269 : }
6270 :
6271 : // Go back to previous define mode.
6272 73 : SetDefineMode(bOldDefineMode);
6273 : }
6274 426 : return bRet;
6275 : }
6276 :
6277 : /************************************************************************/
6278 : /* GetGeoTransform() */
6279 : /************************************************************************/
6280 :
6281 118 : CPLErr netCDFDataset::GetGeoTransform(GDALGeoTransform >) const
6282 :
6283 : {
6284 118 : gt = m_gt;
6285 118 : if (m_bHasGeoTransform)
6286 87 : return CE_None;
6287 :
6288 31 : return GDALPamDataset::GetGeoTransform(gt);
6289 : }
6290 :
6291 : /************************************************************************/
6292 : /* rint() */
6293 : /************************************************************************/
6294 :
6295 0 : double netCDFDataset::rint(double dfX)
6296 : {
6297 0 : return std::round(dfX);
6298 : }
6299 :
6300 : /************************************************************************/
6301 : /* NCDFReadIsoMetadata() */
6302 : /************************************************************************/
6303 :
6304 16 : static void NCDFReadMetadataAsJson(int cdfid, CPLJSONObject &obj)
6305 : {
6306 16 : int nbAttr = 0;
6307 16 : NCDF_ERR(nc_inq_varnatts(cdfid, NC_GLOBAL, &nbAttr));
6308 :
6309 32 : std::map<std::string, CPLJSONArray> oMapNameToArray;
6310 40 : for (int l = 0; l < nbAttr; l++)
6311 : {
6312 : char szAttrName[NC_MAX_NAME + 1];
6313 24 : szAttrName[0] = 0;
6314 24 : NCDF_ERR(nc_inq_attname(cdfid, NC_GLOBAL, l, szAttrName));
6315 :
6316 24 : char *pszMetaValue = nullptr;
6317 24 : if (NCDFGetAttr(cdfid, NC_GLOBAL, szAttrName, &pszMetaValue) == CE_None)
6318 : {
6319 24 : nc_type nAttrType = NC_NAT;
6320 24 : size_t nAttrLen = 0;
6321 :
6322 24 : NCDF_ERR(nc_inq_att(cdfid, NC_GLOBAL, szAttrName, &nAttrType,
6323 : &nAttrLen));
6324 :
6325 24 : std::string osAttrName(szAttrName);
6326 24 : const auto sharpPos = osAttrName.find('#');
6327 24 : if (sharpPos == std::string::npos)
6328 : {
6329 16 : if (nAttrType == NC_DOUBLE || nAttrType == NC_FLOAT)
6330 4 : obj.Add(osAttrName, CPLAtof(pszMetaValue));
6331 : else
6332 12 : obj.Add(osAttrName, pszMetaValue);
6333 : }
6334 : else
6335 : {
6336 8 : osAttrName.resize(sharpPos);
6337 8 : auto iter = oMapNameToArray.find(osAttrName);
6338 8 : if (iter == oMapNameToArray.end())
6339 : {
6340 8 : CPLJSONArray array;
6341 4 : obj.Add(osAttrName, array);
6342 4 : oMapNameToArray[osAttrName] = array;
6343 4 : array.Add(pszMetaValue);
6344 : }
6345 : else
6346 : {
6347 4 : iter->second.Add(pszMetaValue);
6348 : }
6349 : }
6350 24 : CPLFree(pszMetaValue);
6351 24 : pszMetaValue = nullptr;
6352 : }
6353 : }
6354 :
6355 16 : int nSubGroups = 0;
6356 16 : int *panSubGroupIds = nullptr;
6357 16 : NCDFGetSubGroups(cdfid, &nSubGroups, &panSubGroupIds);
6358 16 : oMapNameToArray.clear();
6359 28 : for (int i = 0; i < nSubGroups; i++)
6360 : {
6361 24 : CPLJSONObject subObj;
6362 12 : NCDFReadMetadataAsJson(panSubGroupIds[i], subObj);
6363 :
6364 24 : std::string osGroupName;
6365 12 : osGroupName.resize(NC_MAX_NAME);
6366 12 : NCDF_ERR(nc_inq_grpname(panSubGroupIds[i], &osGroupName[0]));
6367 12 : osGroupName.resize(strlen(osGroupName.data()));
6368 12 : const auto sharpPos = osGroupName.find('#');
6369 12 : if (sharpPos == std::string::npos)
6370 : {
6371 4 : obj.Add(osGroupName, subObj);
6372 : }
6373 : else
6374 : {
6375 8 : osGroupName.resize(sharpPos);
6376 8 : auto iter = oMapNameToArray.find(osGroupName);
6377 8 : if (iter == oMapNameToArray.end())
6378 : {
6379 8 : CPLJSONArray array;
6380 4 : obj.Add(osGroupName, array);
6381 4 : oMapNameToArray[osGroupName] = array;
6382 4 : array.Add(subObj);
6383 : }
6384 : else
6385 : {
6386 4 : iter->second.Add(subObj);
6387 : }
6388 : }
6389 : }
6390 16 : CPLFree(panSubGroupIds);
6391 16 : }
6392 :
6393 4 : std::string NCDFReadMetadataAsJson(int cdfid)
6394 : {
6395 8 : CPLJSONDocument oDoc;
6396 8 : CPLJSONObject oRoot = oDoc.GetRoot();
6397 4 : NCDFReadMetadataAsJson(cdfid, oRoot);
6398 8 : return oDoc.SaveAsString();
6399 : }
6400 :
6401 : /************************************************************************/
6402 : /* ReadAttributes() */
6403 : /************************************************************************/
6404 1836 : CPLErr netCDFDataset::ReadAttributes(int cdfidIn, int var)
6405 :
6406 : {
6407 1836 : char *pszVarFullName = nullptr;
6408 1836 : ERR_RET(NCDFGetVarFullName(cdfidIn, var, &pszVarFullName));
6409 :
6410 : // For metadata in Sentinel 5
6411 1836 : if (STARTS_WITH(pszVarFullName, "/METADATA/"))
6412 : {
6413 6 : for (const char *key :
6414 : {"ISO_METADATA", "ESA_METADATA", "EOP_METADATA", "QA_STATISTICS",
6415 8 : "GRANULE_DESCRIPTION", "ALGORITHM_SETTINGS"})
6416 : {
6417 14 : if (var == NC_GLOBAL &&
6418 7 : strcmp(pszVarFullName,
6419 : CPLSPrintf("/METADATA/%s/NC_GLOBAL", key)) == 0)
6420 : {
6421 1 : CPLFree(pszVarFullName);
6422 1 : CPLStringList aosList;
6423 2 : aosList.AddString(CPLString(NCDFReadMetadataAsJson(cdfidIn))
6424 1 : .replaceAll("\\/", '/'));
6425 1 : m_oMapDomainToJSon[key] = std::move(aosList);
6426 1 : return CE_None;
6427 : }
6428 : }
6429 : }
6430 1835 : if (STARTS_WITH(pszVarFullName, "/PRODUCT/SUPPORT_DATA/"))
6431 : {
6432 0 : CPLFree(pszVarFullName);
6433 0 : CPLStringList aosList;
6434 : aosList.AddString(
6435 0 : CPLString(NCDFReadMetadataAsJson(cdfidIn)).replaceAll("\\/", '/'));
6436 0 : m_oMapDomainToJSon["SUPPORT_DATA"] = std::move(aosList);
6437 0 : return CE_None;
6438 : }
6439 :
6440 1835 : size_t nMetaNameSize =
6441 1835 : sizeof(char) * (strlen(pszVarFullName) + 1 + NC_MAX_NAME + 1);
6442 1835 : char *pszMetaName = static_cast<char *>(CPLMalloc(nMetaNameSize));
6443 :
6444 1835 : int nbAttr = 0;
6445 1835 : NCDF_ERR(nc_inq_varnatts(cdfidIn, var, &nbAttr));
6446 :
6447 9191 : for (int l = 0; l < nbAttr; l++)
6448 : {
6449 : char szAttrName[NC_MAX_NAME + 1];
6450 7356 : szAttrName[0] = 0;
6451 7356 : NCDF_ERR(nc_inq_attname(cdfidIn, var, l, szAttrName));
6452 7356 : snprintf(pszMetaName, nMetaNameSize, "%s#%s", pszVarFullName,
6453 : szAttrName);
6454 :
6455 7356 : char *pszMetaTemp = nullptr;
6456 7356 : if (NCDFGetAttr(cdfidIn, var, szAttrName, &pszMetaTemp) == CE_None)
6457 : {
6458 7355 : papszMetadata =
6459 7355 : CSLSetNameValue(papszMetadata, pszMetaName, pszMetaTemp);
6460 7355 : CPLFree(pszMetaTemp);
6461 7355 : pszMetaTemp = nullptr;
6462 : }
6463 : else
6464 : {
6465 1 : CPLDebug("GDAL_netCDF", "invalid metadata %s", pszMetaName);
6466 : }
6467 : }
6468 :
6469 1835 : CPLFree(pszVarFullName);
6470 1835 : CPLFree(pszMetaName);
6471 :
6472 1835 : if (var == NC_GLOBAL)
6473 : {
6474 : // Recurse on sub-groups.
6475 529 : int nSubGroups = 0;
6476 529 : int *panSubGroupIds = nullptr;
6477 529 : NCDFGetSubGroups(cdfidIn, &nSubGroups, &panSubGroupIds);
6478 561 : for (int i = 0; i < nSubGroups; i++)
6479 : {
6480 32 : ReadAttributes(panSubGroupIds[i], var);
6481 : }
6482 529 : CPLFree(panSubGroupIds);
6483 : }
6484 :
6485 1835 : return CE_None;
6486 : }
6487 :
6488 : /************************************************************************/
6489 : /* netCDFDataset::CreateSubDatasetList() */
6490 : /************************************************************************/
6491 55 : void netCDFDataset::CreateSubDatasetList(int nGroupId)
6492 : {
6493 : char szVarStdName[NC_MAX_NAME + 1];
6494 55 : int *ponDimIds = nullptr;
6495 : nc_type nAttype;
6496 : size_t nAttlen;
6497 :
6498 55 : netCDFDataset *poDS = this;
6499 :
6500 : int nVarCount;
6501 55 : nc_inq_nvars(nGroupId, &nVarCount);
6502 :
6503 55 : const bool bListAllArrays = CPLTestBool(
6504 55 : CSLFetchNameValueDef(papszOpenOptions, "LIST_ALL_ARRAYS", "NO"));
6505 :
6506 338 : for (int nVar = 0; nVar < nVarCount; nVar++)
6507 : {
6508 :
6509 : int nDims;
6510 283 : nc_inq_varndims(nGroupId, nVar, &nDims);
6511 :
6512 283 : if ((bListAllArrays && nDims > 0) || nDims >= 2)
6513 : {
6514 162 : ponDimIds = static_cast<int *>(CPLCalloc(nDims, sizeof(int)));
6515 162 : nc_inq_vardimid(nGroupId, nVar, ponDimIds);
6516 :
6517 : // Create Sub dataset list.
6518 162 : CPLString osDim;
6519 499 : for (int i = 0; i < nDims; i++)
6520 : {
6521 : size_t nDimLen;
6522 337 : nc_inq_dimlen(nGroupId, ponDimIds[i], &nDimLen);
6523 337 : if (!osDim.empty())
6524 175 : osDim += 'x';
6525 337 : osDim += CPLSPrintf("%d", (int)nDimLen);
6526 : }
6527 162 : CPLFree(ponDimIds);
6528 :
6529 : nc_type nVarType;
6530 162 : nc_inq_vartype(nGroupId, nVar, &nVarType);
6531 162 : const char *pszType = "";
6532 162 : switch (nVarType)
6533 : {
6534 38 : case NC_BYTE:
6535 38 : pszType = "8-bit integer";
6536 38 : break;
6537 2 : case NC_CHAR:
6538 2 : pszType = "8-bit character";
6539 2 : break;
6540 6 : case NC_SHORT:
6541 6 : pszType = "16-bit integer";
6542 6 : break;
6543 10 : case NC_INT:
6544 10 : pszType = "32-bit integer";
6545 10 : break;
6546 54 : case NC_FLOAT:
6547 54 : pszType = "32-bit floating-point";
6548 54 : break;
6549 34 : case NC_DOUBLE:
6550 34 : pszType = "64-bit floating-point";
6551 34 : break;
6552 4 : case NC_UBYTE:
6553 4 : pszType = "8-bit unsigned integer";
6554 4 : break;
6555 1 : case NC_USHORT:
6556 1 : pszType = "16-bit unsigned integer";
6557 1 : break;
6558 1 : case NC_UINT:
6559 1 : pszType = "32-bit unsigned integer";
6560 1 : break;
6561 1 : case NC_INT64:
6562 1 : pszType = "64-bit integer";
6563 1 : break;
6564 1 : case NC_UINT64:
6565 1 : pszType = "64-bit unsigned integer";
6566 1 : break;
6567 10 : default:
6568 10 : break;
6569 : }
6570 :
6571 162 : char *pszName = nullptr;
6572 162 : if (NCDFGetVarFullName(nGroupId, nVar, &pszName) != CE_None)
6573 0 : continue;
6574 :
6575 162 : nSubDatasets++;
6576 :
6577 162 : nAttlen = 0;
6578 162 : nc_inq_att(nGroupId, nVar, CF_STD_NAME, &nAttype, &nAttlen);
6579 324 : if (nAttlen < sizeof(szVarStdName) &&
6580 162 : nc_get_att_text(nGroupId, nVar, CF_STD_NAME, szVarStdName) ==
6581 : NC_NOERR)
6582 : {
6583 56 : szVarStdName[nAttlen] = '\0';
6584 : }
6585 : else
6586 : {
6587 106 : snprintf(szVarStdName, sizeof(szVarStdName), "%s", pszName);
6588 : }
6589 :
6590 : char szTemp[NC_MAX_NAME + 1];
6591 162 : snprintf(szTemp, sizeof(szTemp), "SUBDATASET_%d_NAME",
6592 : nSubDatasets);
6593 :
6594 162 : if (strchr(pszName, ' ') || strchr(pszName, ':'))
6595 : {
6596 1 : poDS->papszSubDatasets = CSLSetNameValue(
6597 : poDS->papszSubDatasets, szTemp,
6598 : CPLSPrintf("NETCDF:\"%s\":\"%s\"", poDS->osFilename.c_str(),
6599 : pszName));
6600 : }
6601 : else
6602 : {
6603 161 : poDS->papszSubDatasets = CSLSetNameValue(
6604 : poDS->papszSubDatasets, szTemp,
6605 : CPLSPrintf("NETCDF:\"%s\":%s", poDS->osFilename.c_str(),
6606 : pszName));
6607 : }
6608 :
6609 162 : CPLFree(pszName);
6610 :
6611 162 : snprintf(szTemp, sizeof(szTemp), "SUBDATASET_%d_DESC",
6612 : nSubDatasets);
6613 :
6614 162 : poDS->papszSubDatasets =
6615 162 : CSLSetNameValue(poDS->papszSubDatasets, szTemp,
6616 : CPLSPrintf("[%s] %s (%s)", osDim.c_str(),
6617 : szVarStdName, pszType));
6618 : }
6619 : }
6620 :
6621 : // Recurse on sub groups.
6622 55 : int nSubGroups = 0;
6623 55 : int *panSubGroupIds = nullptr;
6624 55 : NCDFGetSubGroups(nGroupId, &nSubGroups, &panSubGroupIds);
6625 62 : for (int i = 0; i < nSubGroups; i++)
6626 : {
6627 7 : CreateSubDatasetList(panSubGroupIds[i]);
6628 : }
6629 55 : CPLFree(panSubGroupIds);
6630 55 : }
6631 :
6632 : /************************************************************************/
6633 : /* TestCapability() */
6634 : /************************************************************************/
6635 :
6636 249 : int netCDFDataset::TestCapability(const char *pszCap) const
6637 : {
6638 249 : if (EQUAL(pszCap, ODsCCreateLayer))
6639 : {
6640 225 : return eAccess == GA_Update && nBands == 0 &&
6641 219 : (eMultipleLayerBehavior != SINGLE_LAYER ||
6642 230 : this->GetLayerCount() == 0 || bSGSupport);
6643 : }
6644 136 : else if (EQUAL(pszCap, ODsCZGeometries))
6645 2 : return true;
6646 :
6647 134 : return false;
6648 : }
6649 :
6650 : /************************************************************************/
6651 : /* GetLayer() */
6652 : /************************************************************************/
6653 :
6654 385 : const OGRLayer *netCDFDataset::GetLayer(int nIdx) const
6655 : {
6656 385 : if (nIdx < 0 || nIdx >= this->GetLayerCount())
6657 2 : return nullptr;
6658 383 : return papoLayers[nIdx].get();
6659 : }
6660 :
6661 : /************************************************************************/
6662 : /* ICreateLayer() */
6663 : /************************************************************************/
6664 :
6665 60 : OGRLayer *netCDFDataset::ICreateLayer(const char *pszName,
6666 : const OGRGeomFieldDefn *poGeomFieldDefn,
6667 : CSLConstList papszOptions)
6668 : {
6669 60 : int nLayerCDFId = cdfid;
6670 60 : if (!TestCapability(ODsCCreateLayer))
6671 0 : return nullptr;
6672 :
6673 60 : const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
6674 : const auto poSpatialRef =
6675 60 : poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
6676 :
6677 120 : CPLString osNetCDFLayerName(pszName);
6678 60 : const netCDFWriterConfigLayer *poLayerConfig = nullptr;
6679 60 : if (oWriterConfig.m_bIsValid)
6680 : {
6681 : std::map<CPLString, netCDFWriterConfigLayer>::const_iterator
6682 2 : oLayerIter = oWriterConfig.m_oLayers.find(pszName);
6683 2 : if (oLayerIter != oWriterConfig.m_oLayers.end())
6684 : {
6685 1 : poLayerConfig = &(oLayerIter->second);
6686 1 : osNetCDFLayerName = poLayerConfig->m_osNetCDFName;
6687 : }
6688 : }
6689 :
6690 60 : netCDFDataset *poLayerDataset = nullptr;
6691 60 : if (eMultipleLayerBehavior == SEPARATE_FILES)
6692 : {
6693 3 : if (CPLLaunderForFilenameSafe(osNetCDFLayerName.c_str(), nullptr) !=
6694 : osNetCDFLayerName)
6695 : {
6696 1 : CPLError(CE_Failure, CPLE_AppDefined,
6697 : "Illegal characters in '%s' to form a valid filename",
6698 : osNetCDFLayerName.c_str());
6699 1 : return nullptr;
6700 : }
6701 2 : char **papszDatasetOptions = nullptr;
6702 2 : papszDatasetOptions = CSLSetNameValue(
6703 : papszDatasetOptions, "CONFIG_FILE",
6704 2 : CSLFetchNameValue(papszCreationOptions, "CONFIG_FILE"));
6705 : papszDatasetOptions =
6706 2 : CSLSetNameValue(papszDatasetOptions, "FORMAT",
6707 2 : CSLFetchNameValue(papszCreationOptions, "FORMAT"));
6708 2 : papszDatasetOptions = CSLSetNameValue(
6709 : papszDatasetOptions, "WRITE_GDAL_TAGS",
6710 2 : CSLFetchNameValue(papszCreationOptions, "WRITE_GDAL_TAGS"));
6711 : const CPLString osLayerFilename(
6712 2 : CPLFormFilenameSafe(osFilename, osNetCDFLayerName, "nc"));
6713 2 : CPLAcquireMutex(hNCMutex, 1000.0);
6714 2 : poLayerDataset =
6715 2 : CreateLL(osLayerFilename, 0, 0, 0, papszDatasetOptions);
6716 2 : CPLReleaseMutex(hNCMutex);
6717 2 : CSLDestroy(papszDatasetOptions);
6718 2 : if (poLayerDataset == nullptr)
6719 0 : return nullptr;
6720 :
6721 2 : nLayerCDFId = poLayerDataset->cdfid;
6722 2 : NCDFAddGDALHistory(nLayerCDFId, osLayerFilename, bWriteGDALVersion,
6723 2 : bWriteGDALHistory, "", "Create",
6724 : NCDF_CONVENTIONS_CF_V1_6);
6725 : }
6726 57 : else if (eMultipleLayerBehavior == SEPARATE_GROUPS)
6727 : {
6728 2 : SetDefineMode(true);
6729 :
6730 2 : nLayerCDFId = -1;
6731 2 : int status = nc_def_grp(cdfid, osNetCDFLayerName, &nLayerCDFId);
6732 2 : NCDF_ERR(status);
6733 2 : if (status != NC_NOERR)
6734 0 : return nullptr;
6735 :
6736 2 : NCDFAddGDALHistory(nLayerCDFId, osFilename, bWriteGDALVersion,
6737 2 : bWriteGDALHistory, "", "Create",
6738 : NCDF_CONVENTIONS_CF_V1_6);
6739 : }
6740 :
6741 : // Make a clone to workaround a bug in released MapServer versions
6742 : // that destroys the passed SRS instead of releasing it .
6743 59 : OGRSpatialReference *poSRS = nullptr;
6744 59 : if (poSpatialRef)
6745 : {
6746 43 : poSRS = poSpatialRef->Clone();
6747 43 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
6748 : }
6749 : std::shared_ptr<netCDFLayer> poLayer(
6750 59 : new netCDFLayer(poLayerDataset ? poLayerDataset : this, nLayerCDFId,
6751 118 : osNetCDFLayerName, eGType, poSRS));
6752 59 : if (poSRS != nullptr)
6753 43 : poSRS->Release();
6754 :
6755 : // Fetch layer creation options coming from config file
6756 59 : char **papszNewOptions = CSLDuplicate(papszOptions);
6757 59 : if (oWriterConfig.m_bIsValid)
6758 : {
6759 2 : std::map<CPLString, CPLString>::const_iterator oIter;
6760 3 : for (oIter = oWriterConfig.m_oLayerCreationOptions.begin();
6761 3 : oIter != oWriterConfig.m_oLayerCreationOptions.end(); ++oIter)
6762 : {
6763 : papszNewOptions =
6764 1 : CSLSetNameValue(papszNewOptions, oIter->first, oIter->second);
6765 : }
6766 2 : if (poLayerConfig != nullptr)
6767 : {
6768 3 : for (oIter = poLayerConfig->m_oLayerCreationOptions.begin();
6769 3 : oIter != poLayerConfig->m_oLayerCreationOptions.end(); ++oIter)
6770 : {
6771 2 : papszNewOptions = CSLSetNameValue(papszNewOptions, oIter->first,
6772 2 : oIter->second);
6773 : }
6774 : }
6775 : }
6776 :
6777 59 : const bool bRet = poLayer->Create(papszNewOptions, poLayerConfig);
6778 59 : CSLDestroy(papszNewOptions);
6779 :
6780 59 : if (!bRet)
6781 : {
6782 0 : return nullptr;
6783 : }
6784 :
6785 59 : if (poLayerDataset != nullptr)
6786 2 : apoVectorDatasets.push_back(poLayerDataset);
6787 :
6788 59 : papoLayers.push_back(poLayer);
6789 59 : return poLayer.get();
6790 : }
6791 :
6792 : /************************************************************************/
6793 : /* CloneAttributes() */
6794 : /************************************************************************/
6795 :
6796 137 : bool netCDFDataset::CloneAttributes(int old_cdfid, int new_cdfid, int nSrcVarId,
6797 : int nDstVarId)
6798 : {
6799 137 : int nAttCount = -1;
6800 137 : int status = nc_inq_varnatts(old_cdfid, nSrcVarId, &nAttCount);
6801 137 : NCDF_ERR(status);
6802 :
6803 693 : for (int i = 0; i < nAttCount; i++)
6804 : {
6805 : char szName[NC_MAX_NAME + 1];
6806 556 : szName[0] = 0;
6807 556 : status = nc_inq_attname(old_cdfid, nSrcVarId, i, szName);
6808 556 : NCDF_ERR(status);
6809 :
6810 : status =
6811 556 : nc_copy_att(old_cdfid, nSrcVarId, szName, new_cdfid, nDstVarId);
6812 556 : NCDF_ERR(status);
6813 556 : if (status != NC_NOERR)
6814 0 : return false;
6815 : }
6816 :
6817 137 : return true;
6818 : }
6819 :
6820 : /************************************************************************/
6821 : /* CloneVariableContent() */
6822 : /************************************************************************/
6823 :
6824 121 : bool netCDFDataset::CloneVariableContent(int old_cdfid, int new_cdfid,
6825 : int nSrcVarId, int nDstVarId)
6826 : {
6827 121 : int nVarDimCount = -1;
6828 121 : int status = nc_inq_varndims(old_cdfid, nSrcVarId, &nVarDimCount);
6829 121 : NCDF_ERR(status);
6830 121 : int anDimIds[] = {-1, 1};
6831 121 : status = nc_inq_vardimid(old_cdfid, nSrcVarId, anDimIds);
6832 121 : NCDF_ERR(status);
6833 121 : nc_type nc_datatype = NC_NAT;
6834 121 : status = nc_inq_vartype(old_cdfid, nSrcVarId, &nc_datatype);
6835 121 : NCDF_ERR(status);
6836 121 : size_t nTypeSize = 0;
6837 121 : switch (nc_datatype)
6838 : {
6839 35 : case NC_BYTE:
6840 : case NC_CHAR:
6841 35 : nTypeSize = 1;
6842 35 : break;
6843 4 : case NC_SHORT:
6844 4 : nTypeSize = 2;
6845 4 : break;
6846 24 : case NC_INT:
6847 24 : nTypeSize = 4;
6848 24 : break;
6849 4 : case NC_FLOAT:
6850 4 : nTypeSize = 4;
6851 4 : break;
6852 43 : case NC_DOUBLE:
6853 43 : nTypeSize = 8;
6854 43 : break;
6855 2 : case NC_UBYTE:
6856 2 : nTypeSize = 1;
6857 2 : break;
6858 2 : case NC_USHORT:
6859 2 : nTypeSize = 2;
6860 2 : break;
6861 2 : case NC_UINT:
6862 2 : nTypeSize = 4;
6863 2 : break;
6864 4 : case NC_INT64:
6865 : case NC_UINT64:
6866 4 : nTypeSize = 8;
6867 4 : break;
6868 1 : case NC_STRING:
6869 1 : nTypeSize = sizeof(char *);
6870 1 : break;
6871 0 : default:
6872 : {
6873 0 : CPLError(CE_Failure, CPLE_NotSupported, "Unsupported data type: %d",
6874 : nc_datatype);
6875 0 : return false;
6876 : }
6877 : }
6878 :
6879 121 : size_t nElems = 1;
6880 : size_t anStart[NC_MAX_DIMS];
6881 : size_t anCount[NC_MAX_DIMS];
6882 121 : size_t nRecords = 1;
6883 261 : for (int i = 0; i < nVarDimCount; i++)
6884 : {
6885 140 : anStart[i] = 0;
6886 140 : if (i == 0)
6887 : {
6888 116 : anCount[i] = 1;
6889 116 : status = nc_inq_dimlen(old_cdfid, anDimIds[i], &nRecords);
6890 116 : NCDF_ERR(status);
6891 : }
6892 : else
6893 : {
6894 24 : anCount[i] = 0;
6895 24 : status = nc_inq_dimlen(old_cdfid, anDimIds[i], &anCount[i]);
6896 24 : NCDF_ERR(status);
6897 24 : nElems *= anCount[i];
6898 : }
6899 : }
6900 :
6901 : /* Workaround in some cases a netCDF bug:
6902 : * https://github.com/Unidata/netcdf-c/pull/1442 */
6903 121 : if (nRecords > 0 && nRecords < 10 * 1000 * 1000 / (nElems * nTypeSize))
6904 : {
6905 119 : nElems *= nRecords;
6906 119 : anCount[0] = nRecords;
6907 119 : nRecords = 1;
6908 : }
6909 :
6910 121 : void *pBuffer = VSI_MALLOC2_VERBOSE(nElems, nTypeSize);
6911 121 : if (pBuffer == nullptr)
6912 0 : return false;
6913 :
6914 240 : for (size_t iRecord = 0; iRecord < nRecords; iRecord++)
6915 : {
6916 119 : anStart[0] = iRecord;
6917 :
6918 119 : switch (nc_datatype)
6919 : {
6920 5 : case NC_BYTE:
6921 : status =
6922 5 : nc_get_vara_schar(old_cdfid, nSrcVarId, anStart, anCount,
6923 : static_cast<signed char *>(pBuffer));
6924 5 : if (!status)
6925 5 : status = nc_put_vara_schar(
6926 : new_cdfid, nDstVarId, anStart, anCount,
6927 : static_cast<signed char *>(pBuffer));
6928 5 : break;
6929 28 : case NC_CHAR:
6930 : status =
6931 28 : nc_get_vara_text(old_cdfid, nSrcVarId, anStart, anCount,
6932 : static_cast<char *>(pBuffer));
6933 28 : if (!status)
6934 : status =
6935 28 : nc_put_vara_text(new_cdfid, nDstVarId, anStart, anCount,
6936 : static_cast<char *>(pBuffer));
6937 28 : break;
6938 4 : case NC_SHORT:
6939 : status =
6940 4 : nc_get_vara_short(old_cdfid, nSrcVarId, anStart, anCount,
6941 : static_cast<short *>(pBuffer));
6942 4 : if (!status)
6943 4 : status = nc_put_vara_short(new_cdfid, nDstVarId, anStart,
6944 : anCount,
6945 : static_cast<short *>(pBuffer));
6946 4 : break;
6947 24 : case NC_INT:
6948 24 : status = nc_get_vara_int(old_cdfid, nSrcVarId, anStart, anCount,
6949 : static_cast<int *>(pBuffer));
6950 24 : if (!status)
6951 : status =
6952 24 : nc_put_vara_int(new_cdfid, nDstVarId, anStart, anCount,
6953 : static_cast<int *>(pBuffer));
6954 24 : break;
6955 4 : case NC_FLOAT:
6956 : status =
6957 4 : nc_get_vara_float(old_cdfid, nSrcVarId, anStart, anCount,
6958 : static_cast<float *>(pBuffer));
6959 4 : if (!status)
6960 4 : status = nc_put_vara_float(new_cdfid, nDstVarId, anStart,
6961 : anCount,
6962 : static_cast<float *>(pBuffer));
6963 4 : break;
6964 43 : case NC_DOUBLE:
6965 : status =
6966 43 : nc_get_vara_double(old_cdfid, nSrcVarId, anStart, anCount,
6967 : static_cast<double *>(pBuffer));
6968 43 : if (!status)
6969 43 : status = nc_put_vara_double(new_cdfid, nDstVarId, anStart,
6970 : anCount,
6971 : static_cast<double *>(pBuffer));
6972 43 : break;
6973 1 : case NC_STRING:
6974 : status =
6975 1 : nc_get_vara_string(old_cdfid, nSrcVarId, anStart, anCount,
6976 : static_cast<char **>(pBuffer));
6977 1 : if (!status)
6978 : {
6979 1 : status = nc_put_vara_string(
6980 : new_cdfid, nDstVarId, anStart, anCount,
6981 : static_cast<const char **>(pBuffer));
6982 1 : nc_free_string(nElems, static_cast<char **>(pBuffer));
6983 : }
6984 1 : break;
6985 :
6986 2 : case NC_UBYTE:
6987 : status =
6988 2 : nc_get_vara_uchar(old_cdfid, nSrcVarId, anStart, anCount,
6989 : static_cast<unsigned char *>(pBuffer));
6990 2 : if (!status)
6991 2 : status = nc_put_vara_uchar(
6992 : new_cdfid, nDstVarId, anStart, anCount,
6993 : static_cast<unsigned char *>(pBuffer));
6994 2 : break;
6995 2 : case NC_USHORT:
6996 : status =
6997 2 : nc_get_vara_ushort(old_cdfid, nSrcVarId, anStart, anCount,
6998 : static_cast<unsigned short *>(pBuffer));
6999 2 : if (!status)
7000 2 : status = nc_put_vara_ushort(
7001 : new_cdfid, nDstVarId, anStart, anCount,
7002 : static_cast<unsigned short *>(pBuffer));
7003 2 : break;
7004 2 : case NC_UINT:
7005 : status =
7006 2 : nc_get_vara_uint(old_cdfid, nSrcVarId, anStart, anCount,
7007 : static_cast<unsigned int *>(pBuffer));
7008 2 : if (!status)
7009 : status =
7010 2 : nc_put_vara_uint(new_cdfid, nDstVarId, anStart, anCount,
7011 : static_cast<unsigned int *>(pBuffer));
7012 2 : break;
7013 2 : case NC_INT64:
7014 : status =
7015 2 : nc_get_vara_longlong(old_cdfid, nSrcVarId, anStart, anCount,
7016 : static_cast<long long *>(pBuffer));
7017 2 : if (!status)
7018 2 : status = nc_put_vara_longlong(
7019 : new_cdfid, nDstVarId, anStart, anCount,
7020 : static_cast<long long *>(pBuffer));
7021 2 : break;
7022 2 : case NC_UINT64:
7023 2 : status = nc_get_vara_ulonglong(
7024 : old_cdfid, nSrcVarId, anStart, anCount,
7025 : static_cast<unsigned long long *>(pBuffer));
7026 2 : if (!status)
7027 2 : status = nc_put_vara_ulonglong(
7028 : new_cdfid, nDstVarId, anStart, anCount,
7029 : static_cast<unsigned long long *>(pBuffer));
7030 2 : break;
7031 0 : default:
7032 0 : status = NC_EBADTYPE;
7033 : }
7034 :
7035 119 : NCDF_ERR(status);
7036 119 : if (status != NC_NOERR)
7037 : {
7038 0 : VSIFree(pBuffer);
7039 0 : return false;
7040 : }
7041 : }
7042 :
7043 121 : VSIFree(pBuffer);
7044 121 : return true;
7045 : }
7046 :
7047 : /************************************************************************/
7048 : /* NCDFIsUnlimitedDim() */
7049 : /************************************************************************/
7050 :
7051 58 : bool NCDFIsUnlimitedDim(bool bIsNC4, int cdfid, int nDimId)
7052 : {
7053 58 : if (bIsNC4)
7054 : {
7055 16 : int nUnlimitedDims = 0;
7056 16 : nc_inq_unlimdims(cdfid, &nUnlimitedDims, nullptr);
7057 16 : bool bFound = false;
7058 16 : if (nUnlimitedDims)
7059 : {
7060 : int *panUnlimitedDimIds =
7061 16 : static_cast<int *>(CPLMalloc(sizeof(int) * nUnlimitedDims));
7062 16 : nc_inq_unlimdims(cdfid, nullptr, panUnlimitedDimIds);
7063 30 : for (int i = 0; i < nUnlimitedDims; i++)
7064 : {
7065 22 : if (panUnlimitedDimIds[i] == nDimId)
7066 : {
7067 8 : bFound = true;
7068 8 : break;
7069 : }
7070 : }
7071 16 : CPLFree(panUnlimitedDimIds);
7072 : }
7073 16 : return bFound;
7074 : }
7075 : else
7076 : {
7077 42 : int nUnlimitedDimId = -1;
7078 42 : nc_inq(cdfid, nullptr, nullptr, nullptr, &nUnlimitedDimId);
7079 42 : return nDimId == nUnlimitedDimId;
7080 : }
7081 : }
7082 :
7083 : /************************************************************************/
7084 : /* CloneGrp() */
7085 : /************************************************************************/
7086 :
7087 16 : bool netCDFDataset::CloneGrp(int nOldGrpId, int nNewGrpId, bool bIsNC4,
7088 : int nLayerId, int nDimIdToGrow, size_t nNewSize)
7089 : {
7090 : // Clone dimensions
7091 16 : int nDimCount = -1;
7092 16 : int status = nc_inq_ndims(nOldGrpId, &nDimCount);
7093 16 : NCDF_ERR(status);
7094 16 : if (nDimCount < 0 || nDimCount > NC_MAX_DIMS)
7095 0 : return false;
7096 : int anDimIds[NC_MAX_DIMS];
7097 16 : int nUnlimiDimID = -1;
7098 16 : status = nc_inq_unlimdim(nOldGrpId, &nUnlimiDimID);
7099 16 : NCDF_ERR(status);
7100 16 : if (bIsNC4)
7101 : {
7102 : // In NC4, the dimension ids of a group are not necessarily in
7103 : // [0,nDimCount-1] range
7104 8 : int nDimCount2 = -1;
7105 8 : status = nc_inq_dimids(nOldGrpId, &nDimCount2, anDimIds, FALSE);
7106 8 : NCDF_ERR(status);
7107 8 : CPLAssert(nDimCount == nDimCount2);
7108 : }
7109 : else
7110 : {
7111 36 : for (int i = 0; i < nDimCount; i++)
7112 28 : anDimIds[i] = i;
7113 : }
7114 60 : for (int i = 0; i < nDimCount; i++)
7115 : {
7116 : char szDimName[NC_MAX_NAME + 1];
7117 44 : szDimName[0] = 0;
7118 44 : size_t nLen = 0;
7119 44 : const int nDimId = anDimIds[i];
7120 44 : status = nc_inq_dim(nOldGrpId, nDimId, szDimName, &nLen);
7121 44 : NCDF_ERR(status);
7122 44 : if (NCDFIsUnlimitedDim(bIsNC4, nOldGrpId, nDimId))
7123 16 : nLen = NC_UNLIMITED;
7124 28 : else if (nDimId == nDimIdToGrow && nOldGrpId == nLayerId)
7125 13 : nLen = nNewSize;
7126 44 : int nNewDimId = -1;
7127 44 : status = nc_def_dim(nNewGrpId, szDimName, nLen, &nNewDimId);
7128 44 : NCDF_ERR(status);
7129 44 : CPLAssert(nDimId == nNewDimId);
7130 44 : if (status != NC_NOERR)
7131 : {
7132 0 : return false;
7133 : }
7134 : }
7135 :
7136 : // Clone main attributes
7137 16 : if (!CloneAttributes(nOldGrpId, nNewGrpId, NC_GLOBAL, NC_GLOBAL))
7138 : {
7139 0 : return false;
7140 : }
7141 :
7142 : // Clone variable definitions
7143 16 : int nVarCount = -1;
7144 16 : status = nc_inq_nvars(nOldGrpId, &nVarCount);
7145 16 : NCDF_ERR(status);
7146 :
7147 137 : for (int i = 0; i < nVarCount; i++)
7148 : {
7149 : char szVarName[NC_MAX_NAME + 1];
7150 121 : szVarName[0] = 0;
7151 121 : status = nc_inq_varname(nOldGrpId, i, szVarName);
7152 121 : NCDF_ERR(status);
7153 121 : nc_type nc_datatype = NC_NAT;
7154 121 : status = nc_inq_vartype(nOldGrpId, i, &nc_datatype);
7155 121 : NCDF_ERR(status);
7156 121 : int nVarDimCount = -1;
7157 121 : status = nc_inq_varndims(nOldGrpId, i, &nVarDimCount);
7158 121 : NCDF_ERR(status);
7159 121 : status = nc_inq_vardimid(nOldGrpId, i, anDimIds);
7160 121 : NCDF_ERR(status);
7161 121 : int nNewVarId = -1;
7162 121 : status = nc_def_var(nNewGrpId, szVarName, nc_datatype, nVarDimCount,
7163 : anDimIds, &nNewVarId);
7164 121 : NCDF_ERR(status);
7165 121 : CPLAssert(i == nNewVarId);
7166 121 : if (status != NC_NOERR)
7167 : {
7168 0 : return false;
7169 : }
7170 :
7171 121 : if (!CloneAttributes(nOldGrpId, nNewGrpId, i, i))
7172 : {
7173 0 : return false;
7174 : }
7175 : }
7176 :
7177 16 : status = nc_enddef(nNewGrpId);
7178 16 : NCDF_ERR(status);
7179 16 : if (status != NC_NOERR)
7180 : {
7181 0 : return false;
7182 : }
7183 :
7184 : // Clone variable content
7185 137 : for (int i = 0; i < nVarCount; i++)
7186 : {
7187 121 : if (!CloneVariableContent(nOldGrpId, nNewGrpId, i, i))
7188 : {
7189 0 : return false;
7190 : }
7191 : }
7192 :
7193 16 : return true;
7194 : }
7195 :
7196 : /************************************************************************/
7197 : /* GrowDim() */
7198 : /************************************************************************/
7199 :
7200 13 : bool netCDFDataset::GrowDim(int nLayerId, int nDimIdToGrow, size_t nNewSize)
7201 : {
7202 : int nCreationMode;
7203 : // Set nCreationMode based on eFormat.
7204 13 : switch (eFormat)
7205 : {
7206 : #ifdef NETCDF_HAS_NC2
7207 0 : case NCDF_FORMAT_NC2:
7208 0 : nCreationMode = NC_CLOBBER | NC_64BIT_OFFSET;
7209 0 : break;
7210 : #endif
7211 5 : case NCDF_FORMAT_NC4:
7212 5 : nCreationMode = NC_CLOBBER | NC_NETCDF4;
7213 5 : break;
7214 0 : case NCDF_FORMAT_NC4C:
7215 0 : nCreationMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
7216 0 : break;
7217 8 : case NCDF_FORMAT_NC:
7218 : default:
7219 8 : nCreationMode = NC_CLOBBER;
7220 8 : break;
7221 : }
7222 :
7223 13 : int new_cdfid = -1;
7224 26 : CPLString osTmpFilename(osFilename + ".tmp");
7225 26 : CPLString osFilenameForNCCreate(osTmpFilename);
7226 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
7227 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
7228 : {
7229 : char *pszTemp =
7230 : CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
7231 : osFilenameForNCCreate = pszTemp;
7232 : CPLFree(pszTemp);
7233 : }
7234 : #endif
7235 13 : int status = nc_create(osFilenameForNCCreate, nCreationMode, &new_cdfid);
7236 13 : NCDF_ERR(status);
7237 13 : if (status != NC_NOERR)
7238 0 : return false;
7239 :
7240 13 : if (!CloneGrp(cdfid, new_cdfid, eFormat == NCDF_FORMAT_NC4, nLayerId,
7241 : nDimIdToGrow, nNewSize))
7242 : {
7243 0 : GDAL_nc_close(new_cdfid);
7244 0 : return false;
7245 : }
7246 :
7247 13 : int nGroupCount = 0;
7248 26 : std::vector<CPLString> oListGrpName;
7249 31 : if (eFormat == NCDF_FORMAT_NC4 &&
7250 18 : nc_inq_grps(cdfid, &nGroupCount, nullptr) == NC_NOERR &&
7251 5 : nGroupCount > 0)
7252 : {
7253 : int *panGroupIds =
7254 2 : static_cast<int *>(CPLMalloc(sizeof(int) * nGroupCount));
7255 2 : status = nc_inq_grps(cdfid, nullptr, panGroupIds);
7256 2 : NCDF_ERR(status);
7257 5 : for (int i = 0; i < nGroupCount; i++)
7258 : {
7259 : char szGroupName[NC_MAX_NAME + 1];
7260 3 : szGroupName[0] = 0;
7261 3 : NCDF_ERR(nc_inq_grpname(panGroupIds[i], szGroupName));
7262 3 : int nNewGrpId = -1;
7263 3 : status = nc_def_grp(new_cdfid, szGroupName, &nNewGrpId);
7264 3 : NCDF_ERR(status);
7265 3 : if (status != NC_NOERR)
7266 : {
7267 0 : CPLFree(panGroupIds);
7268 0 : GDAL_nc_close(new_cdfid);
7269 0 : return false;
7270 : }
7271 3 : if (!CloneGrp(panGroupIds[i], nNewGrpId, /*bIsNC4=*/true, nLayerId,
7272 : nDimIdToGrow, nNewSize))
7273 : {
7274 0 : CPLFree(panGroupIds);
7275 0 : GDAL_nc_close(new_cdfid);
7276 0 : return false;
7277 : }
7278 : }
7279 2 : CPLFree(panGroupIds);
7280 :
7281 5 : for (int i = 0; i < this->GetLayerCount(); i++)
7282 : {
7283 3 : auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
7284 3 : if (poLayer)
7285 : {
7286 : char szGroupName[NC_MAX_NAME + 1];
7287 3 : szGroupName[0] = 0;
7288 3 : status = nc_inq_grpname(poLayer->GetCDFID(), szGroupName);
7289 3 : NCDF_ERR(status);
7290 3 : oListGrpName.push_back(szGroupName);
7291 : }
7292 : }
7293 : }
7294 :
7295 13 : GDAL_nc_close(cdfid);
7296 13 : cdfid = -1;
7297 13 : GDAL_nc_close(new_cdfid);
7298 :
7299 26 : CPLString osOriFilename(osFilename + ".ori");
7300 26 : if (VSIRename(osFilename, osOriFilename) != 0 ||
7301 13 : VSIRename(osTmpFilename, osFilename) != 0)
7302 : {
7303 0 : CPLError(CE_Failure, CPLE_FileIO, "Renaming of files failed");
7304 0 : return false;
7305 : }
7306 13 : VSIUnlink(osOriFilename);
7307 :
7308 26 : CPLString osFilenameForNCOpen(osFilename);
7309 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
7310 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
7311 : {
7312 : char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
7313 : osFilenameForNCOpen = pszTemp;
7314 : CPLFree(pszTemp);
7315 : }
7316 : #endif
7317 13 : status = GDAL_nc_open(osFilenameForNCOpen, NC_WRITE, &cdfid);
7318 13 : NCDF_ERR(status);
7319 13 : if (status != NC_NOERR)
7320 0 : return false;
7321 13 : bDefineMode = false;
7322 :
7323 13 : if (!oListGrpName.empty())
7324 : {
7325 5 : for (int i = 0; i < this->GetLayerCount(); i++)
7326 : {
7327 3 : auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
7328 3 : if (poLayer)
7329 : {
7330 3 : int nNewLayerCDFID = -1;
7331 3 : status = nc_inq_ncid(cdfid, oListGrpName[i].c_str(),
7332 : &nNewLayerCDFID);
7333 3 : NCDF_ERR(status);
7334 3 : poLayer->SetCDFID(nNewLayerCDFID);
7335 : }
7336 : }
7337 : }
7338 : else
7339 : {
7340 22 : for (int i = 0; i < this->GetLayerCount(); i++)
7341 : {
7342 11 : auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
7343 11 : if (poLayer)
7344 11 : poLayer->SetCDFID(cdfid);
7345 : }
7346 : }
7347 :
7348 13 : return true;
7349 : }
7350 :
7351 : #ifdef ENABLE_NCDUMP
7352 :
7353 : /************************************************************************/
7354 : /* netCDFDatasetCreateTempFile() */
7355 : /************************************************************************/
7356 :
7357 : /* Create a netCDF file from a text dump (format of ncdump) */
7358 : /* Mostly to easy fuzzing of the driver, while still generating valid */
7359 : /* netCDF files. */
7360 : /* Note: not all data types are supported ! */
7361 4 : bool netCDFDatasetCreateTempFile(NetCDFFormatEnum eFormat,
7362 : const char *pszTmpFilename, VSILFILE *fpSrc)
7363 : {
7364 4 : CPL_IGNORE_RET_VAL(eFormat);
7365 4 : int nCreateMode = NC_CLOBBER;
7366 4 : if (eFormat == NCDF_FORMAT_NC4)
7367 1 : nCreateMode |= NC_NETCDF4;
7368 3 : else if (eFormat == NCDF_FORMAT_NC4C)
7369 0 : nCreateMode |= NC_NETCDF4 | NC_CLASSIC_MODEL;
7370 4 : int nCdfId = -1;
7371 4 : int status = nc_create(pszTmpFilename, nCreateMode, &nCdfId);
7372 4 : if (status != NC_NOERR)
7373 : {
7374 0 : return false;
7375 : }
7376 4 : VSIFSeekL(fpSrc, 0, SEEK_SET);
7377 : const char *pszLine;
7378 4 : constexpr int SECTION_NONE = 0;
7379 4 : constexpr int SECTION_DIMENSIONS = 1;
7380 4 : constexpr int SECTION_VARIABLES = 2;
7381 4 : constexpr int SECTION_DATA = 3;
7382 4 : int nActiveSection = SECTION_NONE;
7383 8 : std::map<CPLString, int> oMapDimToId;
7384 8 : std::map<int, int> oMapDimIdToDimLen;
7385 8 : std::map<CPLString, int> oMapVarToId;
7386 8 : std::map<int, std::vector<int>> oMapVarIdToVectorOfDimId;
7387 8 : std::map<int, int> oMapVarIdToType;
7388 4 : std::set<CPLString> oSetAttrDefined;
7389 4 : oMapVarToId[""] = -1;
7390 4 : size_t nTotalVarSize = 0;
7391 208 : while ((pszLine = CPLReadLineL(fpSrc)) != nullptr)
7392 : {
7393 204 : if (STARTS_WITH(pszLine, "dimensions:") &&
7394 : nActiveSection == SECTION_NONE)
7395 : {
7396 4 : nActiveSection = SECTION_DIMENSIONS;
7397 : }
7398 200 : else if (STARTS_WITH(pszLine, "variables:") &&
7399 : nActiveSection == SECTION_DIMENSIONS)
7400 : {
7401 4 : nActiveSection = SECTION_VARIABLES;
7402 : }
7403 196 : else if (STARTS_WITH(pszLine, "data:") &&
7404 : nActiveSection == SECTION_VARIABLES)
7405 : {
7406 4 : nActiveSection = SECTION_DATA;
7407 4 : status = nc_enddef(nCdfId);
7408 4 : if (status != NC_NOERR)
7409 : {
7410 0 : CPLDebug("netCDF", "nc_enddef() failed: %s",
7411 : nc_strerror(status));
7412 : }
7413 : }
7414 192 : else if (nActiveSection == SECTION_DIMENSIONS)
7415 : {
7416 9 : char **papszTokens = CSLTokenizeString2(pszLine, " \t=;", 0);
7417 9 : if (CSLCount(papszTokens) == 2)
7418 : {
7419 9 : const char *pszDimName = papszTokens[0];
7420 9 : bool bValidName = true;
7421 9 : if (STARTS_WITH(pszDimName, "_nc4_non_coord_"))
7422 : {
7423 : // This is an internal netcdf prefix. Using it may
7424 : // cause memory leaks.
7425 0 : bValidName = false;
7426 : }
7427 9 : if (!bValidName)
7428 : {
7429 0 : CPLDebug("netCDF",
7430 : "nc_def_dim(%s) failed: invalid name found",
7431 : pszDimName);
7432 0 : CSLDestroy(papszTokens);
7433 0 : continue;
7434 : }
7435 :
7436 : const bool bIsASCII =
7437 9 : CPLIsASCII(pszDimName, static_cast<size_t>(-1));
7438 9 : if (!bIsASCII)
7439 : {
7440 : // Workaround https://github.com/Unidata/netcdf-c/pull/450
7441 0 : CPLDebug("netCDF",
7442 : "nc_def_dim(%s) failed: rejected because "
7443 : "of non-ASCII characters",
7444 : pszDimName);
7445 0 : CSLDestroy(papszTokens);
7446 0 : continue;
7447 : }
7448 9 : int nDimSize = EQUAL(papszTokens[1], "UNLIMITED")
7449 : ? NC_UNLIMITED
7450 9 : : atoi(papszTokens[1]);
7451 9 : if (nDimSize >= 1000)
7452 1 : nDimSize = 1000; // to avoid very long processing
7453 9 : if (nDimSize >= 0)
7454 : {
7455 9 : int nDimId = -1;
7456 9 : status = nc_def_dim(nCdfId, pszDimName, nDimSize, &nDimId);
7457 9 : if (status != NC_NOERR)
7458 : {
7459 0 : CPLDebug("netCDF", "nc_def_dim(%s, %d) failed: %s",
7460 : pszDimName, nDimSize, nc_strerror(status));
7461 : }
7462 : else
7463 : {
7464 : #ifdef DEBUG_VERBOSE
7465 : CPLDebug("netCDF", "nc_def_dim(%s, %d) (%s) succeeded",
7466 : pszDimName, nDimSize, pszLine);
7467 : #endif
7468 9 : oMapDimToId[pszDimName] = nDimId;
7469 9 : oMapDimIdToDimLen[nDimId] = nDimSize;
7470 : }
7471 : }
7472 : }
7473 9 : CSLDestroy(papszTokens);
7474 : }
7475 183 : else if (nActiveSection == SECTION_VARIABLES)
7476 : {
7477 390 : while (*pszLine == ' ' || *pszLine == '\t')
7478 249 : pszLine++;
7479 141 : const char *pszColumn = strchr(pszLine, ':');
7480 141 : const char *pszEqual = strchr(pszLine, '=');
7481 141 : if (pszColumn == nullptr)
7482 : {
7483 21 : char **papszTokens = CSLTokenizeString2(pszLine, " \t=(),;", 0);
7484 21 : if (CSLCount(papszTokens) >= 2)
7485 : {
7486 17 : const char *pszVarName = papszTokens[1];
7487 17 : bool bValidName = true;
7488 17 : if (STARTS_WITH(pszVarName, "_nc4_non_coord_"))
7489 : {
7490 : // This is an internal netcdf prefix. Using it may
7491 : // cause memory leaks.
7492 0 : bValidName = false;
7493 : }
7494 138 : for (int i = 0; pszVarName[i]; i++)
7495 : {
7496 121 : if (!((pszVarName[i] >= 'a' && pszVarName[i] <= 'z') ||
7497 28 : (pszVarName[i] >= 'A' && pszVarName[i] <= 'Z') ||
7498 9 : (pszVarName[i] >= '0' && pszVarName[i] <= '9') ||
7499 6 : pszVarName[i] == '_'))
7500 : {
7501 0 : bValidName = false;
7502 : }
7503 : }
7504 17 : if (!bValidName)
7505 : {
7506 0 : CPLDebug(
7507 : "netCDF",
7508 : "nc_def_var(%s) failed: illegal character found",
7509 : pszVarName);
7510 0 : CSLDestroy(papszTokens);
7511 0 : continue;
7512 : }
7513 17 : if (oMapVarToId.find(pszVarName) != oMapVarToId.end())
7514 : {
7515 0 : CPLDebug("netCDF",
7516 : "nc_def_var(%s) failed: already defined",
7517 : pszVarName);
7518 0 : CSLDestroy(papszTokens);
7519 0 : continue;
7520 : }
7521 17 : const char *pszVarType = papszTokens[0];
7522 17 : int nc_datatype = NC_BYTE;
7523 17 : size_t nDataTypeSize = 1;
7524 17 : if (EQUAL(pszVarType, "char"))
7525 : {
7526 6 : nc_datatype = NC_CHAR;
7527 6 : nDataTypeSize = 1;
7528 : }
7529 11 : else if (EQUAL(pszVarType, "byte"))
7530 : {
7531 3 : nc_datatype = NC_BYTE;
7532 3 : nDataTypeSize = 1;
7533 : }
7534 8 : else if (EQUAL(pszVarType, "short"))
7535 : {
7536 0 : nc_datatype = NC_SHORT;
7537 0 : nDataTypeSize = 2;
7538 : }
7539 8 : else if (EQUAL(pszVarType, "int"))
7540 : {
7541 0 : nc_datatype = NC_INT;
7542 0 : nDataTypeSize = 4;
7543 : }
7544 8 : else if (EQUAL(pszVarType, "float"))
7545 : {
7546 0 : nc_datatype = NC_FLOAT;
7547 0 : nDataTypeSize = 4;
7548 : }
7549 8 : else if (EQUAL(pszVarType, "double"))
7550 : {
7551 8 : nc_datatype = NC_DOUBLE;
7552 8 : nDataTypeSize = 8;
7553 : }
7554 0 : else if (EQUAL(pszVarType, "ubyte"))
7555 : {
7556 0 : nc_datatype = NC_UBYTE;
7557 0 : nDataTypeSize = 1;
7558 : }
7559 0 : else if (EQUAL(pszVarType, "ushort"))
7560 : {
7561 0 : nc_datatype = NC_USHORT;
7562 0 : nDataTypeSize = 2;
7563 : }
7564 0 : else if (EQUAL(pszVarType, "uint"))
7565 : {
7566 0 : nc_datatype = NC_UINT;
7567 0 : nDataTypeSize = 4;
7568 : }
7569 0 : else if (EQUAL(pszVarType, "int64"))
7570 : {
7571 0 : nc_datatype = NC_INT64;
7572 0 : nDataTypeSize = 8;
7573 : }
7574 0 : else if (EQUAL(pszVarType, "uint64"))
7575 : {
7576 0 : nc_datatype = NC_UINT64;
7577 0 : nDataTypeSize = 8;
7578 : }
7579 :
7580 17 : int nDims = CSLCount(papszTokens) - 2;
7581 17 : if (nDims >= 32)
7582 : {
7583 : // The number of dimensions in a netCDFv4 file is
7584 : // limited by #define H5S_MAX_RANK 32
7585 : // but libnetcdf doesn't check that...
7586 0 : CPLDebug("netCDF",
7587 : "nc_def_var(%s) failed: too many dimensions",
7588 : pszVarName);
7589 0 : CSLDestroy(papszTokens);
7590 0 : continue;
7591 : }
7592 17 : std::vector<int> aoDimIds;
7593 17 : bool bFailed = false;
7594 17 : size_t nSize = 1;
7595 35 : for (int i = 0; i < nDims; i++)
7596 : {
7597 18 : const char *pszDimName = papszTokens[2 + i];
7598 18 : if (oMapDimToId.find(pszDimName) == oMapDimToId.end())
7599 : {
7600 0 : bFailed = true;
7601 0 : break;
7602 : }
7603 18 : const int nDimId = oMapDimToId[pszDimName];
7604 18 : aoDimIds.push_back(nDimId);
7605 :
7606 18 : const size_t nDimSize = oMapDimIdToDimLen[nDimId];
7607 18 : if (nDimSize != 0)
7608 : {
7609 18 : if (nSize >
7610 18 : std::numeric_limits<size_t>::max() / nDimSize)
7611 : {
7612 0 : bFailed = true;
7613 0 : break;
7614 : }
7615 : else
7616 : {
7617 18 : nSize *= nDimSize;
7618 : }
7619 : }
7620 : }
7621 17 : if (bFailed)
7622 : {
7623 0 : CPLDebug("netCDF",
7624 : "nc_def_var(%s) failed: unknown dimension(s)",
7625 : pszVarName);
7626 0 : CSLDestroy(papszTokens);
7627 0 : continue;
7628 : }
7629 17 : if (nSize > 100U * 1024 * 1024 / nDataTypeSize)
7630 : {
7631 0 : CPLDebug("netCDF",
7632 : "nc_def_var(%s) failed: too large data",
7633 : pszVarName);
7634 0 : CSLDestroy(papszTokens);
7635 0 : continue;
7636 : }
7637 17 : if (nTotalVarSize >
7638 34 : std::numeric_limits<size_t>::max() - nSize ||
7639 17 : nTotalVarSize + nSize > 100 * 1024 * 1024)
7640 : {
7641 0 : CPLDebug("netCDF",
7642 : "nc_def_var(%s) failed: too large data",
7643 : pszVarName);
7644 0 : CSLDestroy(papszTokens);
7645 0 : continue;
7646 : }
7647 17 : nTotalVarSize += nSize;
7648 :
7649 17 : int nVarId = -1;
7650 : status =
7651 30 : nc_def_var(nCdfId, pszVarName, nc_datatype, nDims,
7652 13 : (nDims) ? &aoDimIds[0] : nullptr, &nVarId);
7653 17 : if (status != NC_NOERR)
7654 : {
7655 0 : CPLDebug("netCDF", "nc_def_var(%s) failed: %s",
7656 : pszVarName, nc_strerror(status));
7657 : }
7658 : else
7659 : {
7660 : #ifdef DEBUG_VERBOSE
7661 : CPLDebug("netCDF", "nc_def_var(%s) (%s) succeeded",
7662 : pszVarName, pszLine);
7663 : #endif
7664 17 : oMapVarToId[pszVarName] = nVarId;
7665 17 : oMapVarIdToType[nVarId] = nc_datatype;
7666 17 : oMapVarIdToVectorOfDimId[nVarId] = std::move(aoDimIds);
7667 : }
7668 : }
7669 21 : CSLDestroy(papszTokens);
7670 : }
7671 120 : else if (pszEqual != nullptr && pszEqual - pszColumn > 0)
7672 : {
7673 116 : CPLString osVarName(pszLine, pszColumn - pszLine);
7674 116 : CPLString osAttrName(pszColumn + 1, pszEqual - pszColumn - 1);
7675 116 : osAttrName.Trim();
7676 116 : if (oMapVarToId.find(osVarName) == oMapVarToId.end())
7677 : {
7678 0 : CPLDebug("netCDF",
7679 : "nc_put_att(%s:%s) failed: "
7680 : "no corresponding variable",
7681 : osVarName.c_str(), osAttrName.c_str());
7682 0 : continue;
7683 : }
7684 116 : bool bValidName = true;
7685 1743 : for (size_t i = 0; i < osAttrName.size(); i++)
7686 : {
7687 1865 : if (!((osAttrName[i] >= 'a' && osAttrName[i] <= 'z') ||
7688 238 : (osAttrName[i] >= 'A' && osAttrName[i] <= 'Z') ||
7689 158 : (osAttrName[i] >= '0' && osAttrName[i] <= '9') ||
7690 158 : osAttrName[i] == '_'))
7691 : {
7692 0 : bValidName = false;
7693 : }
7694 : }
7695 116 : if (!bValidName)
7696 : {
7697 0 : CPLDebug(
7698 : "netCDF",
7699 : "nc_put_att(%s:%s) failed: illegal character found",
7700 : osVarName.c_str(), osAttrName.c_str());
7701 0 : continue;
7702 : }
7703 116 : if (oSetAttrDefined.find(osVarName + ":" + osAttrName) !=
7704 232 : oSetAttrDefined.end())
7705 : {
7706 0 : CPLDebug("netCDF",
7707 : "nc_put_att(%s:%s) failed: already defined",
7708 : osVarName.c_str(), osAttrName.c_str());
7709 0 : continue;
7710 : }
7711 :
7712 116 : const int nVarId = oMapVarToId[osVarName];
7713 116 : const char *pszValue = pszEqual + 1;
7714 232 : while (*pszValue == ' ')
7715 116 : pszValue++;
7716 :
7717 116 : status = NC_EBADTYPE;
7718 116 : if (*pszValue == '"')
7719 : {
7720 : // For _FillValue, the attribute type should match
7721 : // the variable type. Leaks memory with NC4 otherwise
7722 74 : if (osAttrName == "_FillValue")
7723 : {
7724 0 : CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
7725 : osVarName.c_str(), osAttrName.c_str(),
7726 : nc_strerror(status));
7727 0 : continue;
7728 : }
7729 :
7730 : // Unquote and unescape string value
7731 74 : CPLString osVal(pszValue + 1);
7732 222 : while (!osVal.empty())
7733 : {
7734 222 : if (osVal.back() == ';' || osVal.back() == ' ')
7735 : {
7736 148 : osVal.pop_back();
7737 : }
7738 74 : else if (osVal.back() == '"')
7739 : {
7740 74 : osVal.pop_back();
7741 74 : break;
7742 : }
7743 : else
7744 : {
7745 0 : break;
7746 : }
7747 : }
7748 74 : osVal.replaceAll("\\\"", '"');
7749 74 : status = nc_put_att_text(nCdfId, nVarId, osAttrName,
7750 : osVal.size(), osVal.c_str());
7751 : }
7752 : else
7753 : {
7754 84 : CPLString osVal(pszValue);
7755 126 : while (!osVal.empty())
7756 : {
7757 126 : if (osVal.back() == ';' || osVal.back() == ' ')
7758 : {
7759 84 : osVal.pop_back();
7760 : }
7761 : else
7762 : {
7763 42 : break;
7764 : }
7765 : }
7766 42 : int nc_datatype = -1;
7767 42 : if (!osVal.empty() && osVal.back() == 'b')
7768 : {
7769 3 : nc_datatype = NC_BYTE;
7770 3 : osVal.pop_back();
7771 : }
7772 39 : else if (!osVal.empty() && osVal.back() == 's')
7773 : {
7774 3 : nc_datatype = NC_SHORT;
7775 3 : osVal.pop_back();
7776 : }
7777 42 : if (CPLGetValueType(osVal) == CPL_VALUE_INTEGER)
7778 : {
7779 7 : if (nc_datatype < 0)
7780 4 : nc_datatype = NC_INT;
7781 : }
7782 35 : else if (CPLGetValueType(osVal) == CPL_VALUE_REAL)
7783 : {
7784 32 : nc_datatype = NC_DOUBLE;
7785 : }
7786 : else
7787 : {
7788 3 : nc_datatype = -1;
7789 : }
7790 :
7791 : // For _FillValue, check that the attribute type matches
7792 : // the variable type. Leaks memory with NC4 otherwise
7793 42 : if (osAttrName == "_FillValue")
7794 : {
7795 6 : if (nVarId < 0 ||
7796 3 : nc_datatype != oMapVarIdToType[nVarId])
7797 : {
7798 0 : nc_datatype = -1;
7799 : }
7800 : }
7801 :
7802 42 : if (nc_datatype == NC_BYTE)
7803 : {
7804 : signed char chVal =
7805 3 : static_cast<signed char>(atoi(osVal));
7806 3 : status = nc_put_att_schar(nCdfId, nVarId, osAttrName,
7807 : NC_BYTE, 1, &chVal);
7808 : }
7809 39 : else if (nc_datatype == NC_SHORT)
7810 : {
7811 0 : short nVal = static_cast<short>(atoi(osVal));
7812 0 : status = nc_put_att_short(nCdfId, nVarId, osAttrName,
7813 : NC_SHORT, 1, &nVal);
7814 : }
7815 39 : else if (nc_datatype == NC_INT)
7816 : {
7817 4 : int nVal = static_cast<int>(atoi(osVal));
7818 4 : status = nc_put_att_int(nCdfId, nVarId, osAttrName,
7819 : NC_INT, 1, &nVal);
7820 : }
7821 35 : else if (nc_datatype == NC_DOUBLE)
7822 : {
7823 32 : double dfVal = CPLAtof(osVal);
7824 32 : status = nc_put_att_double(nCdfId, nVarId, osAttrName,
7825 : NC_DOUBLE, 1, &dfVal);
7826 : }
7827 : }
7828 116 : if (status != NC_NOERR)
7829 : {
7830 3 : CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
7831 : osVarName.c_str(), osAttrName.c_str(),
7832 : nc_strerror(status));
7833 : }
7834 : else
7835 : {
7836 113 : oSetAttrDefined.insert(osVarName + ":" + osAttrName);
7837 : #ifdef DEBUG_VERBOSE
7838 : CPLDebug("netCDF", "nc_put_att_(%s:%s) (%s) succeeded",
7839 : osVarName.c_str(), osAttrName.c_str(), pszLine);
7840 : #endif
7841 : }
7842 : }
7843 : }
7844 42 : else if (nActiveSection == SECTION_DATA)
7845 : {
7846 55 : while (*pszLine == ' ' || *pszLine == '\t')
7847 17 : pszLine++;
7848 38 : const char *pszEqual = strchr(pszLine, '=');
7849 38 : if (pszEqual)
7850 : {
7851 17 : CPLString osVarName(pszLine, pszEqual - pszLine);
7852 17 : osVarName.Trim();
7853 17 : if (oMapVarToId.find(osVarName) == oMapVarToId.end())
7854 0 : continue;
7855 17 : const int nVarId = oMapVarToId[osVarName];
7856 17 : CPLString osAccVal(pszEqual + 1);
7857 17 : osAccVal.Trim();
7858 153 : while (osAccVal.empty() || osAccVal.back() != ';')
7859 : {
7860 136 : pszLine = CPLReadLineL(fpSrc);
7861 136 : if (pszLine == nullptr)
7862 0 : break;
7863 272 : CPLString osVal(pszLine);
7864 136 : osVal.Trim();
7865 136 : osAccVal += osVal;
7866 : }
7867 17 : if (pszLine == nullptr)
7868 0 : break;
7869 17 : osAccVal.pop_back();
7870 :
7871 : const std::vector<int> aoDimIds =
7872 34 : oMapVarIdToVectorOfDimId[nVarId];
7873 17 : size_t nSize = 1;
7874 34 : std::vector<size_t> aoStart, aoEdge;
7875 17 : aoStart.resize(aoDimIds.size());
7876 17 : aoEdge.resize(aoDimIds.size());
7877 35 : for (size_t i = 0; i < aoDimIds.size(); ++i)
7878 : {
7879 18 : const size_t nDimSize = oMapDimIdToDimLen[aoDimIds[i]];
7880 36 : if (nDimSize != 0 &&
7881 18 : nSize > std::numeric_limits<size_t>::max() / nDimSize)
7882 : {
7883 0 : nSize = 0;
7884 : }
7885 : else
7886 : {
7887 18 : nSize *= nDimSize;
7888 : }
7889 18 : aoStart[i] = 0;
7890 18 : aoEdge[i] = nDimSize;
7891 : }
7892 :
7893 17 : status = NC_EBADTYPE;
7894 17 : if (nSize == 0)
7895 : {
7896 : // Might happen with a unlimited dimension
7897 : }
7898 17 : else if (oMapVarIdToType[nVarId] == NC_DOUBLE)
7899 : {
7900 8 : if (!aoStart.empty())
7901 : {
7902 : char **papszTokens =
7903 8 : CSLTokenizeString2(osAccVal, " ,;", 0);
7904 8 : size_t nTokens = CSLCount(papszTokens);
7905 8 : if (nTokens >= nSize)
7906 : {
7907 : double *padfVals = static_cast<double *>(
7908 8 : VSI_CALLOC_VERBOSE(nSize, sizeof(double)));
7909 8 : if (padfVals)
7910 : {
7911 132 : for (size_t i = 0; i < nSize; i++)
7912 : {
7913 124 : padfVals[i] = CPLAtof(papszTokens[i]);
7914 : }
7915 8 : status = nc_put_vara_double(
7916 8 : nCdfId, nVarId, &aoStart[0], &aoEdge[0],
7917 : padfVals);
7918 8 : VSIFree(padfVals);
7919 : }
7920 : }
7921 8 : CSLDestroy(papszTokens);
7922 : }
7923 : }
7924 9 : else if (oMapVarIdToType[nVarId] == NC_BYTE)
7925 : {
7926 3 : if (!aoStart.empty())
7927 : {
7928 : char **papszTokens =
7929 3 : CSLTokenizeString2(osAccVal, " ,;", 0);
7930 3 : size_t nTokens = CSLCount(papszTokens);
7931 3 : if (nTokens >= nSize)
7932 : {
7933 : signed char *panVals = static_cast<signed char *>(
7934 3 : VSI_CALLOC_VERBOSE(nSize, sizeof(signed char)));
7935 3 : if (panVals)
7936 : {
7937 1203 : for (size_t i = 0; i < nSize; i++)
7938 : {
7939 1200 : panVals[i] = static_cast<signed char>(
7940 1200 : atoi(papszTokens[i]));
7941 : }
7942 3 : status = nc_put_vara_schar(nCdfId, nVarId,
7943 3 : &aoStart[0],
7944 3 : &aoEdge[0], panVals);
7945 3 : VSIFree(panVals);
7946 : }
7947 : }
7948 3 : CSLDestroy(papszTokens);
7949 : }
7950 : }
7951 6 : else if (oMapVarIdToType[nVarId] == NC_CHAR)
7952 : {
7953 6 : if (aoStart.size() == 2)
7954 : {
7955 4 : std::vector<CPLString> aoStrings;
7956 2 : bool bInString = false;
7957 4 : CPLString osCurString;
7958 935 : for (size_t i = 0; i < osAccVal.size();)
7959 : {
7960 933 : if (!bInString)
7961 : {
7962 8 : if (osAccVal[i] == '"')
7963 : {
7964 4 : bInString = true;
7965 4 : osCurString.clear();
7966 : }
7967 8 : i++;
7968 : }
7969 926 : else if (osAccVal[i] == '\\' &&
7970 926 : i + 1 < osAccVal.size() &&
7971 1 : osAccVal[i + 1] == '"')
7972 : {
7973 1 : osCurString += '"';
7974 1 : i += 2;
7975 : }
7976 924 : else if (osAccVal[i] == '"')
7977 : {
7978 4 : aoStrings.push_back(osCurString);
7979 4 : osCurString.clear();
7980 4 : bInString = false;
7981 4 : i++;
7982 : }
7983 : else
7984 : {
7985 920 : osCurString += osAccVal[i];
7986 920 : i++;
7987 : }
7988 : }
7989 2 : const size_t nRecords = oMapDimIdToDimLen[aoDimIds[0]];
7990 2 : const size_t nWidth = oMapDimIdToDimLen[aoDimIds[1]];
7991 2 : size_t nIters = aoStrings.size();
7992 2 : if (nIters > nRecords)
7993 0 : nIters = nRecords;
7994 6 : for (size_t i = 0; i < nIters; i++)
7995 : {
7996 : size_t anIndex[2];
7997 4 : anIndex[0] = i;
7998 4 : anIndex[1] = 0;
7999 : size_t anCount[2];
8000 4 : anCount[0] = 1;
8001 4 : anCount[1] = aoStrings[i].size();
8002 4 : if (anCount[1] > nWidth)
8003 0 : anCount[1] = nWidth;
8004 : status =
8005 4 : nc_put_vara_text(nCdfId, nVarId, anIndex,
8006 4 : anCount, aoStrings[i].c_str());
8007 4 : if (status != NC_NOERR)
8008 0 : break;
8009 : }
8010 : }
8011 : }
8012 17 : if (status != NC_NOERR)
8013 : {
8014 4 : CPLDebug("netCDF", "nc_put_var_(%s) failed: %s",
8015 : osVarName.c_str(), nc_strerror(status));
8016 : }
8017 : }
8018 : }
8019 : }
8020 :
8021 4 : GDAL_nc_close(nCdfId);
8022 4 : return true;
8023 : }
8024 :
8025 : #endif // ENABLE_NCDUMP
8026 :
8027 : /************************************************************************/
8028 : /* Open() */
8029 : /************************************************************************/
8030 :
8031 753 : GDALDataset *netCDFDataset::Open(GDALOpenInfo *poOpenInfo)
8032 :
8033 : {
8034 : #ifdef NCDF_DEBUG
8035 : CPLDebug("GDAL_netCDF", "\n=====\nOpen(), filename=[%s]",
8036 : poOpenInfo->pszFilename);
8037 : #endif
8038 :
8039 : // Does this appear to be a netcdf file?
8040 753 : NetCDFFormatEnum eTmpFormat = NCDF_FORMAT_NONE;
8041 753 : if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
8042 : {
8043 693 : eTmpFormat = netCDFIdentifyFormat(poOpenInfo, /* bCheckExt = */ true);
8044 : #ifdef NCDF_DEBUG
8045 : CPLDebug("GDAL_netCDF", "identified format %d", eTmpFormat);
8046 : #endif
8047 : // Note: not calling Identify() directly, because we want the file type.
8048 : // Only support NCDF_FORMAT* formats.
8049 693 : if (NCDF_FORMAT_NC == eTmpFormat || NCDF_FORMAT_NC2 == eTmpFormat ||
8050 2 : NCDF_FORMAT_NC4 == eTmpFormat || NCDF_FORMAT_NC4C == eTmpFormat)
8051 : {
8052 : // ok
8053 : }
8054 2 : else if (eTmpFormat == NCDF_FORMAT_HDF4 &&
8055 0 : poOpenInfo->IsSingleAllowedDriver("netCDF"))
8056 : {
8057 : // ok
8058 : }
8059 : else
8060 : {
8061 2 : return nullptr;
8062 : }
8063 : }
8064 : else
8065 : {
8066 : #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
8067 : // We don't necessarily want to catch bugs in libnetcdf ...
8068 : if (CPLGetConfigOption("DISABLE_OPEN_REAL_NETCDF_FILES", nullptr))
8069 : {
8070 : return nullptr;
8071 : }
8072 : #endif
8073 : }
8074 :
8075 751 : if (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER)
8076 : {
8077 253 : return OpenMultiDim(poOpenInfo);
8078 : }
8079 :
8080 996 : CPLMutexHolderD(&hNCMutex);
8081 :
8082 498 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
8083 : // GDALDataset own mutex.
8084 498 : netCDFDataset *poDS = new netCDFDataset();
8085 498 : poDS->papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
8086 498 : CPLAcquireMutex(hNCMutex, 1000.0);
8087 :
8088 498 : poDS->SetDescription(poOpenInfo->pszFilename);
8089 :
8090 : // Check if filename start with NETCDF: tag.
8091 498 : bool bTreatAsSubdataset = false;
8092 996 : CPLString osSubdatasetName;
8093 :
8094 : #ifdef ENABLE_NCDUMP
8095 498 : const char *pszHeader =
8096 : reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
8097 498 : if (poOpenInfo->fpL != nullptr && STARTS_WITH(pszHeader, "netcdf ") &&
8098 3 : strstr(pszHeader, "dimensions:") && strstr(pszHeader, "variables:"))
8099 : {
8100 : // By default create a temporary file that will be destroyed,
8101 : // unless NETCDF_TMP_FILE is defined. Can be useful to see which
8102 : // netCDF file has been generated from a potential fuzzed input.
8103 3 : poDS->osFilename = CPLGetConfigOption("NETCDF_TMP_FILE", "");
8104 3 : if (poDS->osFilename.empty())
8105 : {
8106 3 : poDS->bFileToDestroyAtClosing = true;
8107 3 : poDS->osFilename = CPLGenerateTempFilenameSafe("netcdf_tmp");
8108 : }
8109 3 : if (!netCDFDatasetCreateTempFile(eTmpFormat, poDS->osFilename,
8110 : poOpenInfo->fpL))
8111 : {
8112 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8113 : // deadlock with GDALDataset own mutex.
8114 0 : delete poDS;
8115 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8116 0 : return nullptr;
8117 : }
8118 3 : bTreatAsSubdataset = false;
8119 3 : poDS->eFormat = eTmpFormat;
8120 : }
8121 : else
8122 : #endif
8123 :
8124 495 : if (STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
8125 : {
8126 : char **papszName =
8127 60 : CSLTokenizeString2(poOpenInfo->pszFilename, ":",
8128 : CSLT_HONOURSTRINGS | CSLT_PRESERVEESCAPES);
8129 :
8130 120 : if (CSLCount(papszName) >= 3 &&
8131 60 : ((strlen(papszName[1]) == 1 && /* D:\\bla */
8132 0 : (papszName[2][0] == '/' || papszName[2][0] == '\\')) ||
8133 60 : EQUAL(papszName[1], "http") || EQUAL(papszName[1], "https") ||
8134 60 : EQUAL(papszName[1], "/vsicurl/http") ||
8135 60 : EQUAL(papszName[1], "/vsicurl/https") ||
8136 60 : EQUAL(papszName[1], "/vsicurl_streaming/http") ||
8137 60 : EQUAL(papszName[1], "/vsicurl_streaming/https")))
8138 : {
8139 0 : const int nCountBefore = CSLCount(papszName);
8140 0 : CPLString osTmp = papszName[1];
8141 0 : osTmp += ':';
8142 0 : osTmp += papszName[2];
8143 0 : CPLFree(papszName[1]);
8144 0 : CPLFree(papszName[2]);
8145 0 : papszName[1] = CPLStrdup(osTmp);
8146 0 : memmove(papszName + 2, papszName + 3,
8147 0 : (nCountBefore - 2) * sizeof(char *));
8148 : }
8149 :
8150 60 : if (CSLCount(papszName) == 3)
8151 : {
8152 60 : poDS->osFilename = papszName[1];
8153 60 : osSubdatasetName = papszName[2];
8154 60 : bTreatAsSubdataset = true;
8155 60 : CSLDestroy(papszName);
8156 : }
8157 0 : else if (CSLCount(papszName) == 2)
8158 : {
8159 0 : poDS->osFilename = papszName[1];
8160 0 : osSubdatasetName = "";
8161 0 : bTreatAsSubdataset = false;
8162 0 : CSLDestroy(papszName);
8163 : }
8164 : else
8165 : {
8166 0 : CSLDestroy(papszName);
8167 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8168 : // deadlock with GDALDataset own mutex.
8169 0 : delete poDS;
8170 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8171 0 : CPLError(CE_Failure, CPLE_AppDefined,
8172 : "Failed to parse NETCDF: prefix string into expected 2, 3 "
8173 : "or 4 fields.");
8174 0 : return nullptr;
8175 : }
8176 :
8177 120 : if (!STARTS_WITH(poDS->osFilename, "http://") &&
8178 60 : !STARTS_WITH(poDS->osFilename, "https://"))
8179 : {
8180 : // Identify Format from real file, with bCheckExt=FALSE.
8181 : GDALOpenInfo *poOpenInfo2 =
8182 60 : new GDALOpenInfo(poDS->osFilename.c_str(), GA_ReadOnly);
8183 60 : poDS->eFormat =
8184 60 : netCDFIdentifyFormat(poOpenInfo2, /* bCheckExt = */ false);
8185 60 : delete poOpenInfo2;
8186 60 : if (NCDF_FORMAT_NONE == poDS->eFormat ||
8187 60 : NCDF_FORMAT_UNKNOWN == poDS->eFormat)
8188 : {
8189 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8190 : // deadlock with GDALDataset own mutex.
8191 0 : delete poDS;
8192 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8193 0 : return nullptr;
8194 : }
8195 : }
8196 : }
8197 : else
8198 : {
8199 435 : poDS->osFilename = poOpenInfo->pszFilename;
8200 435 : bTreatAsSubdataset = false;
8201 435 : poDS->eFormat = eTmpFormat;
8202 : }
8203 :
8204 : // Try opening the dataset.
8205 : #if defined(NCDF_DEBUG) && defined(ENABLE_UFFD)
8206 : CPLDebug("GDAL_netCDF", "calling nc_open_mem(%s)",
8207 : poDS->osFilename.c_str());
8208 : #elif defined(NCDF_DEBUG) && !defined(ENABLE_UFFD)
8209 : CPLDebug("GDAL_netCDF", "calling nc_open(%s)", poDS->osFilename.c_str());
8210 : #endif
8211 498 : int cdfid = -1;
8212 498 : const int nMode = ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE) != 0)
8213 : ? NC_WRITE
8214 : : NC_NOWRITE;
8215 996 : CPLString osFilenameForNCOpen(poDS->osFilename);
8216 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
8217 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
8218 : {
8219 : char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
8220 : osFilenameForNCOpen = pszTemp;
8221 : CPLFree(pszTemp);
8222 : }
8223 : #endif
8224 498 : int status2 = -1;
8225 :
8226 : #ifdef ENABLE_UFFD
8227 498 : cpl_uffd_context *pCtx = nullptr;
8228 : #endif
8229 :
8230 513 : if (STARTS_WITH(osFilenameForNCOpen, "/vsimem/") &&
8231 15 : poOpenInfo->eAccess == GA_ReadOnly)
8232 : {
8233 15 : vsi_l_offset nLength = 0;
8234 15 : poDS->fpVSIMEM = VSIFOpenL(osFilenameForNCOpen, "rb");
8235 15 : if (poDS->fpVSIMEM)
8236 : {
8237 : // We assume that the file will not be modified. If it is, then
8238 : // pabyBuffer might become invalid.
8239 : GByte *pabyBuffer =
8240 15 : VSIGetMemFileBuffer(osFilenameForNCOpen, &nLength, false);
8241 15 : if (pabyBuffer)
8242 : {
8243 15 : status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen),
8244 : nMode, static_cast<size_t>(nLength),
8245 : pabyBuffer, &cdfid);
8246 : }
8247 : }
8248 : }
8249 : else
8250 : {
8251 : const bool bVsiFile =
8252 483 : !strncmp(osFilenameForNCOpen, "/vsi", strlen("/vsi"));
8253 : #ifdef ENABLE_UFFD
8254 483 : bool bReadOnly = (poOpenInfo->eAccess == GA_ReadOnly);
8255 483 : void *pVma = nullptr;
8256 483 : uint64_t nVmaSize = 0;
8257 :
8258 483 : if (bVsiFile)
8259 : {
8260 2 : if (bReadOnly)
8261 : {
8262 2 : if (CPLIsUserFaultMappingSupported())
8263 : {
8264 2 : pCtx = CPLCreateUserFaultMapping(osFilenameForNCOpen, &pVma,
8265 : &nVmaSize);
8266 : }
8267 : else
8268 : {
8269 0 : CPLError(CE_Failure, CPLE_AppDefined,
8270 : "Opening a /vsi file with the netCDF driver "
8271 : "requires Linux userfaultfd to be available. "
8272 : "If running from Docker, "
8273 : "--security-opt seccomp=unconfined might be "
8274 : "needed.%s",
8275 0 : ((poDS->eFormat == NCDF_FORMAT_NC4 ||
8276 0 : poDS->eFormat == NCDF_FORMAT_HDF5) &&
8277 0 : GDALGetDriverByName("HDF5"))
8278 : ? " Or you may set the GDAL_SKIP=netCDF "
8279 : "configuration option to force the use of "
8280 : "the HDF5 driver."
8281 : : "");
8282 : }
8283 : }
8284 : else
8285 : {
8286 0 : CPLError(CE_Failure, CPLE_AppDefined,
8287 : "Opening a /vsi file with the netCDF driver is only "
8288 : "supported in read-only mode");
8289 : }
8290 : }
8291 483 : if (pCtx != nullptr && pVma != nullptr && nVmaSize > 0)
8292 : {
8293 : // netCDF code, at least for netCDF 4.7.0, is confused by filenames
8294 : // like /vsicurl/http[s]://example.com/foo.nc, so just pass the
8295 : // final part
8296 2 : status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen), nMode,
8297 : static_cast<size_t>(nVmaSize), pVma, &cdfid);
8298 : }
8299 : else
8300 481 : status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
8301 : #else
8302 : if (bVsiFile)
8303 : {
8304 : CPLError(
8305 : CE_Failure, CPLE_AppDefined,
8306 : "Opening a /vsi file with the netCDF driver requires Linux "
8307 : "userfaultfd to be available.%s",
8308 : ((poDS->eFormat == NCDF_FORMAT_NC4 ||
8309 : poDS->eFormat == NCDF_FORMAT_HDF5) &&
8310 : GDALGetDriverByName("HDF5"))
8311 : ? " Or you may set the GDAL_SKIP=netCDF "
8312 : "configuration option to force the use of the HDF5 "
8313 : "driver."
8314 : : "");
8315 : status2 = NC_EIO;
8316 : }
8317 : else
8318 : {
8319 : status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
8320 : }
8321 : #endif
8322 : }
8323 498 : if (status2 != NC_NOERR)
8324 : {
8325 : #ifdef NCDF_DEBUG
8326 : CPLDebug("GDAL_netCDF", "error opening");
8327 : #endif
8328 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8329 : // with GDALDataset own mutex.
8330 0 : delete poDS;
8331 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8332 0 : return nullptr;
8333 : }
8334 : #ifdef NCDF_DEBUG
8335 : CPLDebug("GDAL_netCDF", "got cdfid=%d", cdfid);
8336 : #endif
8337 :
8338 : #if defined(ENABLE_NCDUMP) && !defined(_WIN32)
8339 : // Try to destroy the temporary file right now on Unix
8340 498 : if (poDS->bFileToDestroyAtClosing)
8341 : {
8342 3 : if (VSIUnlink(poDS->osFilename) == 0)
8343 : {
8344 3 : poDS->bFileToDestroyAtClosing = false;
8345 : }
8346 : }
8347 : #endif
8348 :
8349 : // Is this a real netCDF file?
8350 : int ndims;
8351 : int ngatts;
8352 : int nvars;
8353 : int unlimdimid;
8354 498 : int status = nc_inq(cdfid, &ndims, &nvars, &ngatts, &unlimdimid);
8355 498 : if (status != NC_NOERR)
8356 : {
8357 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8358 : // with GDALDataset own mutex.
8359 0 : delete poDS;
8360 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8361 0 : return nullptr;
8362 : }
8363 :
8364 : // Get file type from netcdf.
8365 498 : int nTmpFormat = NCDF_FORMAT_NONE;
8366 498 : status = nc_inq_format(cdfid, &nTmpFormat);
8367 498 : if (status != NC_NOERR)
8368 : {
8369 0 : NCDF_ERR(status);
8370 : }
8371 : else
8372 : {
8373 498 : CPLDebug("GDAL_netCDF",
8374 : "driver detected file type=%d, libnetcdf detected type=%d",
8375 498 : poDS->eFormat, nTmpFormat);
8376 498 : if (static_cast<NetCDFFormatEnum>(nTmpFormat) != poDS->eFormat)
8377 : {
8378 : // Warn if file detection conflicts with that from libnetcdf
8379 : // except for NC4C, which we have no way of detecting initially.
8380 26 : if (nTmpFormat != NCDF_FORMAT_NC4C &&
8381 13 : !STARTS_WITH(poDS->osFilename, "http://") &&
8382 0 : !STARTS_WITH(poDS->osFilename, "https://"))
8383 : {
8384 0 : CPLError(CE_Warning, CPLE_AppDefined,
8385 : "NetCDF driver detected file type=%d, but libnetcdf "
8386 : "detected type=%d",
8387 0 : poDS->eFormat, nTmpFormat);
8388 : }
8389 13 : CPLDebug("GDAL_netCDF", "setting file type to %d, was %d",
8390 13 : nTmpFormat, poDS->eFormat);
8391 13 : poDS->eFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
8392 : }
8393 : }
8394 :
8395 : // Does the request variable exist?
8396 498 : if (bTreatAsSubdataset)
8397 : {
8398 : int dummy;
8399 60 : if (NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &dummy,
8400 60 : &dummy) != CE_None)
8401 : {
8402 0 : CPLError(CE_Warning, CPLE_AppDefined,
8403 : "%s is a netCDF file, but %s is not a variable.",
8404 : poOpenInfo->pszFilename, osSubdatasetName.c_str());
8405 :
8406 0 : GDAL_nc_close(cdfid);
8407 : #ifdef ENABLE_UFFD
8408 0 : NETCDF_UFFD_UNMAP(pCtx);
8409 : #endif
8410 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8411 : // deadlock with GDALDataset own mutex.
8412 0 : delete poDS;
8413 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8414 0 : return nullptr;
8415 : }
8416 : }
8417 :
8418 : // Figure out whether or not the listed dataset has support for simple
8419 : // geometries (CF-1.8)
8420 498 : poDS->nCFVersion = nccfdriver::getCFVersion(cdfid);
8421 498 : bool bHasSimpleGeometries = false; // but not necessarily valid
8422 498 : if (poDS->nCFVersion >= 1.8)
8423 : {
8424 75 : bHasSimpleGeometries = poDS->DetectAndFillSGLayers(cdfid);
8425 75 : if (bHasSimpleGeometries)
8426 : {
8427 67 : poDS->bSGSupport = true;
8428 67 : poDS->vcdf.enableFullVirtualMode();
8429 : }
8430 : }
8431 :
8432 : char szConventions[NC_MAX_NAME + 1];
8433 498 : szConventions[0] = '\0';
8434 498 : nc_type nAttype = NC_NAT;
8435 498 : size_t nAttlen = 0;
8436 498 : nc_inq_att(cdfid, NC_GLOBAL, "Conventions", &nAttype, &nAttlen);
8437 996 : if (nAttlen >= sizeof(szConventions) ||
8438 498 : nc_get_att_text(cdfid, NC_GLOBAL, "Conventions", szConventions) !=
8439 : NC_NOERR)
8440 : {
8441 59 : CPLDebug("GDAL_netCDF", "No UNIDATA NC_GLOBAL:Conventions attribute");
8442 : // Note that 'Conventions' is always capital 'C' in CF spec.
8443 : }
8444 : else
8445 : {
8446 439 : szConventions[nAttlen] = '\0';
8447 : }
8448 :
8449 : // Create band information objects.
8450 498 : CPLDebug("GDAL_netCDF", "var_count = %d", nvars);
8451 :
8452 : // Create a corresponding GDALDataset.
8453 : // Create Netcdf Subdataset if filename as NETCDF tag.
8454 498 : poDS->cdfid = cdfid;
8455 : #ifdef ENABLE_UFFD
8456 498 : poDS->pCtx = pCtx;
8457 : #endif
8458 498 : poDS->eAccess = poOpenInfo->eAccess;
8459 498 : poDS->bDefineMode = false;
8460 :
8461 498 : poDS->ReadAttributes(cdfid, NC_GLOBAL);
8462 :
8463 : // Identify coordinate and boundary variables that we should
8464 : // ignore as Raster Bands.
8465 498 : char **papszIgnoreVars = nullptr;
8466 498 : NCDFGetCoordAndBoundVarFullNames(cdfid, &papszIgnoreVars);
8467 : // Filter variables to keep only valid 2+D raster bands and vector fields.
8468 498 : int nRasterVars = 0;
8469 498 : int nIgnoredVars = 0;
8470 498 : int nGroupID = -1;
8471 498 : int nVarID = -1;
8472 :
8473 : std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>
8474 996 : oMap2DDimsToGroupAndVar;
8475 1149 : if ((poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
8476 153 : STARTS_WITH(CSLFetchNameValueDef(poDS->papszMetadata,
8477 : "NC_GLOBAL#mission_name", ""),
8478 1 : "Sentinel 3") &&
8479 1 : EQUAL(CSLFetchNameValueDef(poDS->papszMetadata,
8480 : "NC_GLOBAL#altimeter_sensor_name", ""),
8481 651 : "SRAL") &&
8482 1 : EQUAL(CSLFetchNameValueDef(poDS->papszMetadata,
8483 : "NC_GLOBAL#radiometer_sensor_name", ""),
8484 : "MWR"))
8485 : {
8486 1 : if (poDS->eAccess == GA_Update)
8487 : {
8488 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8489 : // deadlock with GDALDataset own mutex.
8490 0 : delete poDS;
8491 0 : return nullptr;
8492 : }
8493 1 : poDS->ProcessSentinel3_SRAL_MWR();
8494 : }
8495 : else
8496 : {
8497 497 : poDS->FilterVars(cdfid, (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0,
8498 649 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
8499 152 : !bHasSimpleGeometries,
8500 : papszIgnoreVars, &nRasterVars, &nGroupID, &nVarID,
8501 : &nIgnoredVars, oMap2DDimsToGroupAndVar);
8502 : }
8503 498 : CSLDestroy(papszIgnoreVars);
8504 :
8505 498 : const bool bListAllArrays = CPLTestBool(
8506 498 : CSLFetchNameValueDef(poDS->papszOpenOptions, "LIST_ALL_ARRAYS", "NO"));
8507 :
8508 : // Case where there is no raster variable
8509 498 : if (!bListAllArrays && nRasterVars == 0 && !bTreatAsSubdataset)
8510 : {
8511 119 : poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
8512 119 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8513 : // with GDALDataset own mutex.
8514 119 : poDS->TryLoadXML();
8515 : // If the dataset has been opened in raster mode only, exit
8516 119 : if ((poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0 &&
8517 9 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) == 0)
8518 : {
8519 4 : delete poDS;
8520 4 : poDS = nullptr;
8521 : }
8522 : // Otherwise if the dataset is opened in vector mode, that there is
8523 : // no vector layer and we are in read-only, exit too.
8524 115 : else if (poDS->GetLayerCount() == 0 &&
8525 123 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
8526 8 : poOpenInfo->eAccess == GA_ReadOnly)
8527 : {
8528 8 : delete poDS;
8529 8 : poDS = nullptr;
8530 : }
8531 119 : CPLAcquireMutex(hNCMutex, 1000.0);
8532 119 : return poDS;
8533 : }
8534 :
8535 : // We have more than one variable with 2 dimensions in the
8536 : // file, then treat this as a subdataset container dataset.
8537 379 : bool bSeveralVariablesAsBands = false;
8538 379 : if (bListAllArrays || ((nRasterVars > 1) && !bTreatAsSubdataset))
8539 : {
8540 29 : if (CPLFetchBool(poOpenInfo->papszOpenOptions, "VARIABLES_AS_BANDS",
8541 35 : false) &&
8542 6 : oMap2DDimsToGroupAndVar.size() == 1)
8543 : {
8544 6 : std::tie(nGroupID, nVarID) =
8545 12 : oMap2DDimsToGroupAndVar.begin()->second.front();
8546 6 : bSeveralVariablesAsBands = true;
8547 : }
8548 : else
8549 : {
8550 23 : poDS->CreateSubDatasetList(cdfid);
8551 23 : poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
8552 23 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8553 : // deadlock with GDALDataset own mutex.
8554 23 : poDS->TryLoadXML();
8555 23 : CPLAcquireMutex(hNCMutex, 1000.0);
8556 23 : return poDS;
8557 : }
8558 : }
8559 :
8560 : // If we are not treating things as a subdataset, then capture
8561 : // the name of the single available variable as the subdataset.
8562 356 : if (!bTreatAsSubdataset)
8563 : {
8564 296 : char *pszVarName = nullptr;
8565 296 : NCDF_ERR(NCDFGetVarFullName(nGroupID, nVarID, &pszVarName));
8566 296 : osSubdatasetName = (pszVarName != nullptr ? pszVarName : "");
8567 296 : CPLFree(pszVarName);
8568 : }
8569 :
8570 : // We have ignored at least one variable, so we should report them
8571 : // as subdatasets for reference.
8572 356 : if (nIgnoredVars > 0 && !bTreatAsSubdataset)
8573 : {
8574 25 : CPLDebug("GDAL_netCDF",
8575 : "As %d variables were ignored, creating subdataset list "
8576 : "for reference. Variable #%d [%s] is the main variable",
8577 : nIgnoredVars, nVarID, osSubdatasetName.c_str());
8578 25 : poDS->CreateSubDatasetList(cdfid);
8579 : }
8580 :
8581 : // Open the NETCDF subdataset NETCDF:"filename":subdataset.
8582 356 : int var = -1;
8583 356 : NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &nGroupID, &var);
8584 : // Now we can forget the root cdfid and only use the selected group.
8585 356 : cdfid = nGroupID;
8586 356 : int nd = 0;
8587 356 : nc_inq_varndims(cdfid, var, &nd);
8588 :
8589 356 : poDS->m_anDimIds.resize(nd);
8590 :
8591 : // X, Y, Z position in array
8592 712 : std::vector<int> anBandDimPos(nd);
8593 :
8594 356 : nc_inq_vardimid(cdfid, var, poDS->m_anDimIds.data());
8595 :
8596 : // Check if somebody tried to pass a variable with less than 1D.
8597 356 : if (nd < 1)
8598 : {
8599 0 : CPLError(CE_Warning, CPLE_AppDefined,
8600 : "Variable has %d dimension(s) - not supported.", nd);
8601 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8602 : // with GDALDataset own mutex.
8603 0 : delete poDS;
8604 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8605 0 : return nullptr;
8606 : }
8607 :
8608 : // CF-1 Convention
8609 : //
8610 : // Dimensions to appear in the relative order T, then Z, then Y,
8611 : // then X to the file. All other dimensions should, whenever
8612 : // possible, be placed to the left of the spatiotemporal
8613 : // dimensions.
8614 :
8615 : // Verify that dimensions are in the {T,Z,Y,X} or {T,Z,Y,X} order
8616 : // Ideally we should detect for other ordering and act accordingly
8617 : // Only done if file has Conventions=CF-* and only prints warning
8618 : // To disable set GDAL_NETCDF_VERIFY_DIMS=NO and to use only
8619 : // attributes (not varnames) set GDAL_NETCDF_VERIFY_DIMS=STRICT
8620 : const bool bCheckDims =
8621 712 : CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES")) &&
8622 356 : STARTS_WITH_CI(szConventions, "CF");
8623 :
8624 356 : bool bYXBandOrder = false;
8625 356 : if (nd == 3)
8626 : {
8627 : // If there's a coordinates attributes, and the variable it points to
8628 : // are 2D variables indexed by the same first and second dimension than
8629 : // our variable of interest, then it is Y,X,Band order.
8630 46 : char *pszCoordinates = nullptr;
8631 46 : if (NCDFGetAttr(cdfid, var, "coordinates", &pszCoordinates) ==
8632 63 : CE_None &&
8633 17 : pszCoordinates)
8634 : {
8635 : const CPLStringList aosCoordinates(
8636 34 : NCDFTokenizeCoordinatesAttribute(pszCoordinates));
8637 17 : if (aosCoordinates.size() == 2)
8638 : {
8639 : // Test that each variable is longitude/latitude.
8640 13 : for (int i = 0; i < aosCoordinates.size(); i++)
8641 : {
8642 13 : if (NCDFIsVarLongitude(cdfid, -1, aosCoordinates[i]) ||
8643 4 : NCDFIsVarLatitude(cdfid, -1, aosCoordinates[i]))
8644 : {
8645 9 : int nOtherGroupId = -1;
8646 9 : int nOtherVarId = -1;
8647 9 : if (NCDFResolveVar(cdfid, aosCoordinates[i],
8648 : &nOtherGroupId,
8649 9 : &nOtherVarId) == CE_None)
8650 : {
8651 9 : int coordDimCount = 0;
8652 9 : nc_inq_varndims(nOtherGroupId, nOtherVarId,
8653 : &coordDimCount);
8654 9 : if (coordDimCount == 2)
8655 : {
8656 3 : int coordDimIds[2] = {0, 0};
8657 3 : nc_inq_vardimid(nOtherGroupId, nOtherVarId,
8658 : coordDimIds);
8659 4 : if (coordDimIds[0] == poDS->m_anDimIds[0] &&
8660 1 : coordDimIds[1] == poDS->m_anDimIds[1])
8661 : {
8662 1 : bYXBandOrder = true;
8663 1 : break;
8664 : }
8665 : }
8666 : }
8667 : }
8668 : }
8669 : }
8670 : }
8671 46 : CPLFree(pszCoordinates);
8672 :
8673 46 : if (!bYXBandOrder)
8674 : {
8675 45 : char szDim0Name[NC_MAX_NAME + 1] = {};
8676 45 : char szDim1Name[NC_MAX_NAME + 1] = {};
8677 45 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[0], szDim0Name);
8678 45 : NCDF_ERR(status);
8679 45 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[1], szDim1Name);
8680 45 : NCDF_ERR(status);
8681 :
8682 45 : if (strcmp(szDim0Name, "number_of_lines") == 0 &&
8683 1 : strcmp(szDim1Name, "pixels_per_line") == 0)
8684 : {
8685 : // Like in PACE OCI products
8686 1 : bYXBandOrder = true;
8687 : }
8688 : else
8689 : {
8690 : // For example for EMIT data (https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/),
8691 : // dimension order is downtrack, crosstrack, bands
8692 44 : char szDim2Name[NC_MAX_NAME + 1] = {};
8693 44 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[2], szDim2Name);
8694 44 : NCDF_ERR(status);
8695 86 : bYXBandOrder = strcmp(szDim2Name, "bands") == 0 ||
8696 42 : strcmp(szDim2Name, "band") == 0;
8697 : }
8698 : }
8699 : }
8700 :
8701 356 : if (nd >= 2 && bCheckDims && !bYXBandOrder)
8702 : {
8703 269 : char szDimName1[NC_MAX_NAME + 1] = {};
8704 269 : char szDimName2[NC_MAX_NAME + 1] = {};
8705 269 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 1], szDimName1);
8706 269 : NCDF_ERR(status);
8707 269 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 2], szDimName2);
8708 269 : NCDF_ERR(status);
8709 429 : if (NCDFIsVarLongitude(cdfid, -1, szDimName1) == false &&
8710 160 : NCDFIsVarProjectionX(cdfid, -1, szDimName1) == false)
8711 : {
8712 4 : CPLError(CE_Warning, CPLE_AppDefined,
8713 : "dimension #%d (%s) is not a Longitude/X dimension.",
8714 : nd - 1, szDimName1);
8715 : }
8716 429 : if (NCDFIsVarLatitude(cdfid, -1, szDimName2) == false &&
8717 160 : NCDFIsVarProjectionY(cdfid, -1, szDimName2) == false)
8718 : {
8719 4 : CPLError(CE_Warning, CPLE_AppDefined,
8720 : "dimension #%d (%s) is not a Latitude/Y dimension.",
8721 : nd - 2, szDimName2);
8722 : }
8723 269 : if ((NCDFIsVarLongitude(cdfid, -1, szDimName2) ||
8724 271 : NCDFIsVarProjectionX(cdfid, -1, szDimName2)) &&
8725 2 : (NCDFIsVarLatitude(cdfid, -1, szDimName1) ||
8726 0 : NCDFIsVarProjectionY(cdfid, -1, szDimName1)))
8727 : {
8728 2 : poDS->bSwitchedXY = true;
8729 : }
8730 269 : if (nd >= 3)
8731 : {
8732 52 : char szDimName3[NC_MAX_NAME + 1] = {};
8733 : status =
8734 52 : nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 3], szDimName3);
8735 52 : NCDF_ERR(status);
8736 52 : if (nd >= 4)
8737 : {
8738 13 : char szDimName4[NC_MAX_NAME + 1] = {};
8739 : status =
8740 13 : nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 4], szDimName4);
8741 13 : NCDF_ERR(status);
8742 13 : if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false)
8743 : {
8744 0 : CPLError(CE_Warning, CPLE_AppDefined,
8745 : "dimension #%d (%s) is not a Vertical dimension.",
8746 : nd - 3, szDimName3);
8747 : }
8748 13 : if (NCDFIsVarTimeCoord(cdfid, -1, szDimName4) == false)
8749 : {
8750 0 : CPLError(CE_Warning, CPLE_AppDefined,
8751 : "dimension #%d (%s) is not a Time dimension.",
8752 : nd - 4, szDimName4);
8753 : }
8754 : }
8755 : else
8756 : {
8757 75 : if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false &&
8758 36 : NCDFIsVarTimeCoord(cdfid, -1, szDimName3) == false)
8759 : {
8760 0 : CPLError(CE_Warning, CPLE_AppDefined,
8761 : "dimension #%d (%s) is not a "
8762 : "Time or Vertical dimension.",
8763 : nd - 3, szDimName3);
8764 : }
8765 : }
8766 : }
8767 : }
8768 :
8769 : // Get X dimensions information.
8770 : size_t xdim;
8771 356 : poDS->nXDimID = poDS->m_anDimIds[bYXBandOrder ? 1 : nd - 1];
8772 356 : nc_inq_dimlen(cdfid, poDS->nXDimID, &xdim);
8773 :
8774 : // Get Y dimension information.
8775 : size_t ydim;
8776 356 : if (nd >= 2)
8777 : {
8778 352 : poDS->nYDimID = poDS->m_anDimIds[bYXBandOrder ? 0 : nd - 2];
8779 352 : nc_inq_dimlen(cdfid, poDS->nYDimID, &ydim);
8780 : }
8781 : else
8782 : {
8783 4 : poDS->nYDimID = -1;
8784 4 : ydim = 1;
8785 : }
8786 :
8787 356 : if (xdim > INT_MAX || ydim > INT_MAX)
8788 : {
8789 0 : CPLError(CE_Failure, CPLE_AppDefined,
8790 : "Invalid raster dimensions: " CPL_FRMT_GUIB "x" CPL_FRMT_GUIB,
8791 : static_cast<GUIntBig>(xdim), static_cast<GUIntBig>(ydim));
8792 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8793 : // with GDALDataset own mutex.
8794 0 : delete poDS;
8795 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8796 0 : return nullptr;
8797 : }
8798 :
8799 356 : poDS->nRasterXSize = static_cast<int>(xdim);
8800 356 : poDS->nRasterYSize = static_cast<int>(ydim);
8801 :
8802 356 : unsigned int k = 0;
8803 1145 : for (int j = 0; j < nd; j++)
8804 : {
8805 789 : if (poDS->m_anDimIds[j] == poDS->nXDimID)
8806 : {
8807 356 : anBandDimPos[0] = j; // Save Position of XDim
8808 356 : k++;
8809 : }
8810 789 : if (poDS->m_anDimIds[j] == poDS->nYDimID)
8811 : {
8812 352 : anBandDimPos[1] = j; // Save Position of YDim
8813 352 : k++;
8814 : }
8815 : }
8816 : // X and Y Dimension Ids were not found!
8817 356 : if ((nd >= 2 && k != 2) || (nd == 1 && k != 1))
8818 : {
8819 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8820 : // with GDALDataset own mutex.
8821 0 : delete poDS;
8822 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8823 0 : return nullptr;
8824 : }
8825 :
8826 : // Read Metadata for this variable.
8827 :
8828 : // Should disable as is also done at band level, except driver needs the
8829 : // variables as metadata (e.g. projection).
8830 356 : poDS->ReadAttributes(cdfid, var);
8831 :
8832 : // Read Metadata for each dimension.
8833 356 : int *panDimIds = nullptr;
8834 356 : NCDFGetVisibleDims(cdfid, &ndims, &panDimIds);
8835 : // With NetCDF-4 groups panDimIds is not always [0..dim_count-1] like
8836 : // in NetCDF-3 because we see only the dimensions of the selected group
8837 : // and its parents.
8838 : // poDS->papszDimName is indexed by dim IDs, so it must contains all IDs
8839 : // [0..max(panDimIds)], but they are not all useful so we fill names
8840 : // of useless dims with empty string.
8841 356 : if (panDimIds)
8842 : {
8843 356 : const int nMaxDimId = *std::max_element(panDimIds, panDimIds + ndims);
8844 356 : std::set<int> oSetExistingDimIds;
8845 1185 : for (int i = 0; i < ndims; i++)
8846 : {
8847 829 : oSetExistingDimIds.insert(panDimIds[i]);
8848 : }
8849 356 : std::set<int> oSetDimIdsUsedByVar;
8850 1145 : for (int i = 0; i < nd; i++)
8851 : {
8852 789 : oSetDimIdsUsedByVar.insert(poDS->m_anDimIds[i]);
8853 : }
8854 1187 : for (int j = 0; j <= nMaxDimId; j++)
8855 : {
8856 : // Is j dim used?
8857 831 : if (oSetExistingDimIds.find(j) != oSetExistingDimIds.end())
8858 : {
8859 : // Useful dim.
8860 829 : char szTemp[NC_MAX_NAME + 1] = {};
8861 829 : status = nc_inq_dimname(cdfid, j, szTemp);
8862 829 : if (status != NC_NOERR)
8863 : {
8864 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8865 : // deadlock with GDALDataset own
8866 : // mutex.
8867 0 : delete poDS;
8868 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8869 0 : return nullptr;
8870 : }
8871 829 : poDS->papszDimName.AddString(szTemp);
8872 :
8873 829 : if (oSetDimIdsUsedByVar.find(j) != oSetDimIdsUsedByVar.end())
8874 : {
8875 789 : int nDimGroupId = -1;
8876 789 : int nDimVarId = -1;
8877 789 : if (NCDFResolveVar(cdfid, poDS->papszDimName[j],
8878 789 : &nDimGroupId, &nDimVarId) == CE_None)
8879 : {
8880 585 : poDS->ReadAttributes(nDimGroupId, nDimVarId);
8881 : }
8882 : }
8883 : }
8884 : else
8885 : {
8886 : // Useless dim.
8887 2 : poDS->papszDimName.AddString("");
8888 : }
8889 : }
8890 356 : CPLFree(panDimIds);
8891 : }
8892 :
8893 : // Set projection info.
8894 712 : std::vector<std::string> aosRemovedMDItems;
8895 356 : if (nd > 1)
8896 : {
8897 352 : poDS->SetProjectionFromVar(cdfid, var,
8898 : /*bReadSRSOnly=*/false,
8899 : /* pszGivenGM = */ nullptr,
8900 : /* returnProjStr = */ nullptr,
8901 : /* sg = */ nullptr, &aosRemovedMDItems);
8902 : }
8903 :
8904 : // Override bottom-up with GDAL_NETCDF_BOTTOMUP config option.
8905 356 : const char *pszValue = CPLGetConfigOption("GDAL_NETCDF_BOTTOMUP", nullptr);
8906 356 : if (pszValue)
8907 : {
8908 24 : poDS->bBottomUp = CPLTestBool(pszValue);
8909 24 : CPLDebug("GDAL_netCDF",
8910 : "set bBottomUp=%d because GDAL_NETCDF_BOTTOMUP=%s",
8911 24 : static_cast<int>(poDS->bBottomUp), pszValue);
8912 : }
8913 :
8914 : // Save non-spatial dimension info.
8915 :
8916 356 : int *panBandZLev = nullptr;
8917 356 : int nDim = (nd >= 2) ? 2 : 1;
8918 : size_t lev_count;
8919 356 : size_t nTotLevCount = 1;
8920 356 : nc_type nType = NC_NAT;
8921 :
8922 712 : CPLString osExtraDimNames;
8923 :
8924 356 : if (nd > 2)
8925 : {
8926 62 : nDim = 2;
8927 62 : panBandZLev = static_cast<int *>(CPLCalloc(nd - 2, sizeof(int)));
8928 :
8929 62 : osExtraDimNames = "{";
8930 :
8931 62 : char szDimName[NC_MAX_NAME + 1] = {};
8932 :
8933 62 : bool bREPORT_EXTRA_DIM_VALUESWarningEmitted = false;
8934 267 : for (int j = 0; j < nd; j++)
8935 : {
8936 348 : if ((poDS->m_anDimIds[j] != poDS->nXDimID) &&
8937 143 : (poDS->m_anDimIds[j] != poDS->nYDimID))
8938 : {
8939 81 : nc_inq_dimlen(cdfid, poDS->m_anDimIds[j], &lev_count);
8940 81 : nTotLevCount *= lev_count;
8941 81 : panBandZLev[nDim - 2] = static_cast<int>(lev_count);
8942 81 : anBandDimPos[nDim] = j; // Save Position of ZDim
8943 : // Save non-spatial dimension names.
8944 81 : if (nc_inq_dimname(cdfid, poDS->m_anDimIds[j], szDimName) ==
8945 : NC_NOERR)
8946 : {
8947 81 : osExtraDimNames += szDimName;
8948 81 : if (j < nd - 3)
8949 : {
8950 19 : osExtraDimNames += ",";
8951 : }
8952 :
8953 81 : int nIdxGroupID = -1;
8954 81 : int nIdxVarID = Get1DVariableIndexedByDimension(
8955 81 : cdfid, poDS->m_anDimIds[j], szDimName, true,
8956 81 : &nIdxGroupID);
8957 81 : poDS->m_anExtraDimGroupIds.push_back(nIdxGroupID);
8958 81 : poDS->m_anExtraDimVarIds.push_back(nIdxVarID);
8959 :
8960 81 : if (nIdxVarID >= 0)
8961 : {
8962 72 : nc_inq_vartype(nIdxGroupID, nIdxVarID, &nType);
8963 : char szExtraDimDef[NC_MAX_NAME + 1];
8964 72 : snprintf(szExtraDimDef, sizeof(szExtraDimDef),
8965 : "{%ld,%d}", (long)lev_count, nType);
8966 : char szTemp[NC_MAX_NAME + 32 + 1];
8967 72 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
8968 : szDimName);
8969 72 : poDS->papszMetadata = CSLSetNameValue(
8970 : poDS->papszMetadata, szTemp, szExtraDimDef);
8971 :
8972 : // Retrieving data for unlimited dimensions might be
8973 : // costly on network storage, so don't do it.
8974 : // Each band will capture the value along the extra
8975 : // dimension in its NETCDF_DIM_xxxx band metadata item
8976 : // Addresses use case of
8977 : // https://lists.osgeo.org/pipermail/gdal-dev/2023-May/057209.html
8978 : const bool bIsLocal =
8979 72 : VSIIsLocal(osFilenameForNCOpen.c_str());
8980 : bool bListDimValues =
8981 73 : bIsLocal || lev_count == 1 ||
8982 1 : !NCDFIsUnlimitedDim(poDS->eFormat ==
8983 : NCDF_FORMAT_NC4,
8984 1 : cdfid, poDS->m_anDimIds[j]);
8985 : const char *pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES =
8986 72 : CPLGetConfigOption(
8987 : "GDAL_NETCDF_REPORT_EXTRA_DIM_VALUES", nullptr);
8988 72 : if (pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES)
8989 : {
8990 2 : bListDimValues = CPLTestBool(
8991 : pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES);
8992 : }
8993 70 : else if (!bListDimValues && !bIsLocal &&
8994 1 : !bREPORT_EXTRA_DIM_VALUESWarningEmitted)
8995 : {
8996 1 : bREPORT_EXTRA_DIM_VALUESWarningEmitted = true;
8997 1 : CPLDebug(
8998 : "GDAL_netCDF",
8999 : "Listing extra dimension values is skipped "
9000 : "because this dataset is hosted on a network "
9001 : "file system, and such an operation could be "
9002 : "slow. If you still want to proceed, set the "
9003 : "GDAL_NETCDF_REPORT_EXTRA_DIM_VALUES "
9004 : "configuration option to YES");
9005 : }
9006 72 : if (bListDimValues)
9007 : {
9008 70 : char *pszTemp = nullptr;
9009 70 : if (NCDFGet1DVar(nIdxGroupID, nIdxVarID,
9010 70 : &pszTemp) == CE_None)
9011 : {
9012 70 : snprintf(szTemp, sizeof(szTemp),
9013 : "NETCDF_DIM_%s_VALUES", szDimName);
9014 70 : poDS->papszMetadata = CSLSetNameValue(
9015 : poDS->papszMetadata, szTemp, pszTemp);
9016 70 : CPLFree(pszTemp);
9017 : }
9018 : }
9019 : }
9020 : }
9021 : else
9022 : {
9023 0 : poDS->m_anExtraDimGroupIds.push_back(-1);
9024 0 : poDS->m_anExtraDimVarIds.push_back(-1);
9025 : }
9026 :
9027 81 : nDim++;
9028 : }
9029 : }
9030 62 : osExtraDimNames += "}";
9031 62 : poDS->papszMetadata = CSLSetNameValue(
9032 : poDS->papszMetadata, "NETCDF_DIM_EXTRA", osExtraDimNames);
9033 : }
9034 :
9035 : // Store Metadata.
9036 366 : for (const auto &osStr : aosRemovedMDItems)
9037 10 : poDS->papszMetadata =
9038 10 : CSLSetNameValue(poDS->papszMetadata, osStr.c_str(), nullptr);
9039 :
9040 356 : poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
9041 :
9042 : // Create bands.
9043 :
9044 : // Arbitrary threshold.
9045 : int nMaxBandCount =
9046 356 : atoi(CPLGetConfigOption("GDAL_MAX_BAND_COUNT", "32768"));
9047 356 : if (nMaxBandCount <= 0)
9048 0 : nMaxBandCount = 32768;
9049 356 : if (nTotLevCount > static_cast<unsigned int>(nMaxBandCount))
9050 : {
9051 0 : CPLError(CE_Warning, CPLE_AppDefined,
9052 : "Limiting number of bands to %d instead of %u", nMaxBandCount,
9053 : static_cast<unsigned int>(nTotLevCount));
9054 0 : nTotLevCount = static_cast<unsigned int>(nMaxBandCount);
9055 : }
9056 356 : if (poDS->nRasterXSize == 0 || poDS->nRasterYSize == 0)
9057 : {
9058 0 : poDS->nRasterXSize = 0;
9059 0 : poDS->nRasterYSize = 0;
9060 0 : nTotLevCount = 0;
9061 0 : if (poDS->GetLayerCount() == 0)
9062 : {
9063 0 : CPLFree(panBandZLev);
9064 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9065 : // deadlock with GDALDataset own mutex.
9066 0 : delete poDS;
9067 0 : CPLAcquireMutex(hNCMutex, 1000.0);
9068 0 : return nullptr;
9069 : }
9070 : }
9071 356 : if (bSeveralVariablesAsBands)
9072 : {
9073 6 : const auto &listVariables = oMap2DDimsToGroupAndVar.begin()->second;
9074 24 : for (int iBand = 0; iBand < static_cast<int>(listVariables.size());
9075 : ++iBand)
9076 : {
9077 18 : int bandVarGroupId = listVariables[iBand].first;
9078 18 : int bandVarId = listVariables[iBand].second;
9079 : netCDFRasterBand *poBand = new netCDFRasterBand(
9080 0 : netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, bandVarGroupId,
9081 18 : bandVarId, nDim, 0, nullptr, anBandDimPos.data(), iBand + 1);
9082 18 : poDS->SetBand(iBand + 1, poBand);
9083 : }
9084 : }
9085 : else
9086 : {
9087 814 : for (unsigned int lev = 0; lev < nTotLevCount; lev++)
9088 : {
9089 : netCDFRasterBand *poBand = new netCDFRasterBand(
9090 0 : netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, cdfid, var, nDim,
9091 464 : lev, panBandZLev, anBandDimPos.data(), lev + 1);
9092 464 : poDS->SetBand(lev + 1, poBand);
9093 : }
9094 : }
9095 :
9096 356 : if (panBandZLev)
9097 62 : CPLFree(panBandZLev);
9098 : // Handle angular geographic coordinates here
9099 :
9100 : // Initialize any PAM information.
9101 356 : if (bTreatAsSubdataset)
9102 : {
9103 60 : poDS->SetPhysicalFilename(poDS->osFilename);
9104 60 : poDS->SetSubdatasetName(osSubdatasetName);
9105 : }
9106 :
9107 356 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
9108 : // GDALDataset own mutex.
9109 356 : poDS->TryLoadXML();
9110 :
9111 356 : if (bTreatAsSubdataset)
9112 60 : poDS->oOvManager.Initialize(poDS, ":::VIRTUAL:::");
9113 : else
9114 296 : poDS->oOvManager.Initialize(poDS, poDS->osFilename);
9115 :
9116 356 : CPLAcquireMutex(hNCMutex, 1000.0);
9117 :
9118 356 : return poDS;
9119 : }
9120 :
9121 : /************************************************************************/
9122 : /* CopyMetadata() */
9123 : /* */
9124 : /* Create a copy of metadata for NC_GLOBAL or a variable */
9125 : /************************************************************************/
9126 :
9127 157 : static void CopyMetadata(GDALDataset *poSrcDS, GDALRasterBand *poSrcBand,
9128 : GDALRasterBand *poDstBand, int nCdfId, int CDFVarID,
9129 : const char *pszPrefix)
9130 : {
9131 : // Remove the following band meta but set them later from band data.
9132 157 : const char *const papszIgnoreBand[] = {
9133 : CF_ADD_OFFSET, CF_SCALE_FACTOR, "valid_range", "_Unsigned",
9134 : NCDF_FillValue, "coordinates", nullptr};
9135 157 : const char *const papszIgnoreGlobal[] = {"NETCDF_DIM_EXTRA", nullptr};
9136 :
9137 157 : CSLConstList papszMetadata = nullptr;
9138 157 : if (poSrcDS)
9139 : {
9140 66 : papszMetadata = poSrcDS->GetMetadata();
9141 : }
9142 91 : else if (poSrcBand)
9143 : {
9144 91 : papszMetadata = poSrcBand->GetMetadata();
9145 : }
9146 :
9147 637 : for (const auto &[pszKey, pszValue] : cpl::IterateNameValue(papszMetadata))
9148 : {
9149 : #ifdef NCDF_DEBUG
9150 : CPLDebug("GDAL_netCDF", "copy metadata [%s]=[%s]", pszKey, pszValue);
9151 : #endif
9152 :
9153 480 : CPLString osMetaName(pszKey);
9154 :
9155 : // Check for items that match pszPrefix if applicable.
9156 480 : if (pszPrefix && !EQUAL(pszPrefix, ""))
9157 : {
9158 : // Remove prefix.
9159 115 : if (STARTS_WITH(osMetaName.c_str(), pszPrefix))
9160 : {
9161 17 : osMetaName = osMetaName.substr(strlen(pszPrefix));
9162 : }
9163 : // Only copy items that match prefix.
9164 : else
9165 : {
9166 98 : continue;
9167 : }
9168 : }
9169 :
9170 : // Fix various issues with metadata translation.
9171 382 : if (CDFVarID == NC_GLOBAL)
9172 : {
9173 : // Do not copy items in papszIgnoreGlobal and NETCDF_DIM_*.
9174 481 : if ((CSLFindString(papszIgnoreGlobal, osMetaName) != -1) ||
9175 238 : (STARTS_WITH(osMetaName, "NETCDF_DIM_")))
9176 21 : continue;
9177 : // Remove NC_GLOBAL prefix for netcdf global Metadata.
9178 222 : else if (STARTS_WITH(osMetaName, "NC_GLOBAL#"))
9179 : {
9180 33 : osMetaName = osMetaName.substr(strlen("NC_GLOBAL#"));
9181 : }
9182 : // GDAL Metadata renamed as GDAL-[meta].
9183 189 : else if (strstr(osMetaName, "#") == nullptr)
9184 : {
9185 16 : osMetaName = "GDAL_" + osMetaName;
9186 : }
9187 : // Keep time, lev and depth information for safe-keeping.
9188 : // Time and vertical coordinate handling need improvements.
9189 : /*
9190 : else if( STARTS_WITH(szMetaName, "time#") )
9191 : {
9192 : szMetaName[4] = '-';
9193 : }
9194 : else if( STARTS_WITH(szMetaName, "lev#") )
9195 : {
9196 : szMetaName[3] = '-';
9197 : }
9198 : else if( STARTS_WITH(szMetaName, "depth#") )
9199 : {
9200 : szMetaName[5] = '-';
9201 : }
9202 : */
9203 : // Only copy data without # (previously all data was copied).
9204 222 : if (strstr(osMetaName, "#") != nullptr)
9205 173 : continue;
9206 : // netCDF attributes do not like the '#' character.
9207 : // for( unsigned int h=0; h < strlen(szMetaName) -1 ; h++ ) {
9208 : // if( szMetaName[h] == '#') szMetaName[h] = '-';
9209 : // }
9210 : }
9211 : else
9212 : {
9213 : // Do not copy varname, stats, NETCDF_DIM_*, nodata
9214 : // and items in papszIgnoreBand.
9215 139 : if (STARTS_WITH(osMetaName, "NETCDF_VARNAME") ||
9216 107 : STARTS_WITH(osMetaName, "STATISTICS_") ||
9217 107 : STARTS_WITH(osMetaName, "NETCDF_DIM_") ||
9218 74 : STARTS_WITH(osMetaName, "missing_value") ||
9219 293 : STARTS_WITH(osMetaName, "_FillValue") ||
9220 47 : CSLFindString(papszIgnoreBand, osMetaName) != -1)
9221 97 : continue;
9222 : }
9223 :
9224 : #ifdef NCDF_DEBUG
9225 : CPLDebug("GDAL_netCDF", "copy name=[%s] value=[%s]", osMetaName.c_str(),
9226 : pszValue);
9227 : #endif
9228 91 : if (NCDFPutAttr(nCdfId, CDFVarID, osMetaName, pszValue) != CE_None)
9229 : {
9230 0 : CPLDebug("GDAL_netCDF", "NCDFPutAttr(%d, %d, %s, %s) failed",
9231 : nCdfId, CDFVarID, osMetaName.c_str(), pszValue);
9232 : }
9233 : }
9234 :
9235 : // Set add_offset and scale_factor here if present.
9236 157 : if (poSrcBand && poDstBand)
9237 : {
9238 :
9239 91 : int bGotAddOffset = FALSE;
9240 91 : const double dfAddOffset = poSrcBand->GetOffset(&bGotAddOffset);
9241 91 : int bGotScale = FALSE;
9242 91 : const double dfScale = poSrcBand->GetScale(&bGotScale);
9243 :
9244 91 : if (bGotAddOffset && dfAddOffset != 0.0)
9245 1 : poDstBand->SetOffset(dfAddOffset);
9246 91 : if (bGotScale && dfScale != 1.0)
9247 1 : poDstBand->SetScale(dfScale);
9248 : }
9249 157 : }
9250 :
9251 : /************************************************************************/
9252 : /* CreateLL() */
9253 : /* */
9254 : /* Shared functionality between netCDFDataset::Create() and */
9255 : /* netCDF::CreateCopy() for creating netcdf file based on a set of */
9256 : /* options and a configuration. */
9257 : /************************************************************************/
9258 :
9259 199 : netCDFDataset *netCDFDataset::CreateLL(const char *pszFilename, int nXSize,
9260 : int nYSize, int nBandsIn,
9261 : char **papszOptions)
9262 : {
9263 199 : if (!((nXSize == 0 && nYSize == 0 && nBandsIn == 0) ||
9264 126 : (nXSize > 0 && nYSize > 0 && nBandsIn > 0)))
9265 : {
9266 1 : return nullptr;
9267 : }
9268 :
9269 198 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
9270 : // GDALDataset own mutex.
9271 198 : netCDFDataset *poDS = new netCDFDataset();
9272 198 : CPLAcquireMutex(hNCMutex, 1000.0);
9273 :
9274 198 : poDS->nRasterXSize = nXSize;
9275 198 : poDS->nRasterYSize = nYSize;
9276 198 : poDS->eAccess = GA_Update;
9277 198 : poDS->osFilename = pszFilename;
9278 :
9279 : // From gtiff driver, is this ok?
9280 : /*
9281 : poDS->nBlockXSize = nXSize;
9282 : poDS->nBlockYSize = 1;
9283 : poDS->nBlocksPerBand =
9284 : DIV_ROUND_UP((nYSize, poDS->nBlockYSize))
9285 : * DIV_ROUND_UP((nXSize, poDS->nBlockXSize));
9286 : */
9287 :
9288 : // process options.
9289 198 : poDS->papszCreationOptions = CSLDuplicate(papszOptions);
9290 198 : poDS->ProcessCreationOptions();
9291 :
9292 198 : if (poDS->eMultipleLayerBehavior == SEPARATE_FILES)
9293 : {
9294 : VSIStatBuf sStat;
9295 3 : if (VSIStat(pszFilename, &sStat) == 0)
9296 : {
9297 0 : if (!VSI_ISDIR(sStat.st_mode))
9298 : {
9299 0 : CPLError(CE_Failure, CPLE_FileIO,
9300 : "%s is an existing file, but not a directory",
9301 : pszFilename);
9302 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9303 : // deadlock with GDALDataset own
9304 : // mutex.
9305 0 : delete poDS;
9306 0 : CPLAcquireMutex(hNCMutex, 1000.0);
9307 0 : return nullptr;
9308 : }
9309 : }
9310 3 : else if (VSIMkdir(pszFilename, 0755) != 0)
9311 : {
9312 1 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s directory",
9313 : pszFilename);
9314 1 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9315 : // deadlock with GDALDataset own mutex.
9316 1 : delete poDS;
9317 1 : CPLAcquireMutex(hNCMutex, 1000.0);
9318 1 : return nullptr;
9319 : }
9320 :
9321 2 : return poDS;
9322 : }
9323 : // Create the dataset.
9324 390 : CPLString osFilenameForNCCreate(pszFilename);
9325 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
9326 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
9327 : {
9328 : char *pszTemp =
9329 : CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
9330 : osFilenameForNCCreate = pszTemp;
9331 : CPLFree(pszTemp);
9332 : }
9333 : #endif
9334 :
9335 : #if defined(_WIN32)
9336 : {
9337 : // Works around bug of msys2 netCDF 4.9.0 package where nc_create()
9338 : // crashes
9339 : VSIStatBuf sStat;
9340 : const std::string osDirname =
9341 : CPLGetDirnameSafe(osFilenameForNCCreate.c_str());
9342 : if (VSIStat(osDirname.c_str(), &sStat) != 0)
9343 : {
9344 : CPLError(CE_Failure, CPLE_OpenFailed,
9345 : "Unable to create netCDF file %s: non existing output "
9346 : "directory",
9347 : pszFilename);
9348 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9349 : // deadlock with GDALDataset own mutex.
9350 : delete poDS;
9351 : CPLAcquireMutex(hNCMutex, 1000.0);
9352 : return nullptr;
9353 : }
9354 : }
9355 : #endif
9356 :
9357 : int status =
9358 195 : nc_create(osFilenameForNCCreate, poDS->nCreateMode, &(poDS->cdfid));
9359 :
9360 : // Put into define mode.
9361 195 : poDS->SetDefineMode(true);
9362 :
9363 195 : if (status != NC_NOERR)
9364 : {
9365 30 : CPLError(CE_Failure, CPLE_OpenFailed,
9366 : "Unable to create netCDF file %s (Error code %d): %s .",
9367 : pszFilename, status, nc_strerror(status));
9368 30 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
9369 : // with GDALDataset own mutex.
9370 30 : delete poDS;
9371 30 : CPLAcquireMutex(hNCMutex, 1000.0);
9372 30 : return nullptr;
9373 : }
9374 :
9375 : // Define dimensions.
9376 165 : if (nXSize > 0 && nYSize > 0)
9377 : {
9378 112 : poDS->papszDimName.AddString(NCDF_DIMNAME_X);
9379 : status =
9380 112 : nc_def_dim(poDS->cdfid, NCDF_DIMNAME_X, nXSize, &(poDS->nXDimID));
9381 112 : NCDF_ERR(status);
9382 112 : CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
9383 : poDS->cdfid, NCDF_DIMNAME_X, nXSize, poDS->nXDimID);
9384 :
9385 112 : poDS->papszDimName.AddString(NCDF_DIMNAME_Y);
9386 : status =
9387 112 : nc_def_dim(poDS->cdfid, NCDF_DIMNAME_Y, nYSize, &(poDS->nYDimID));
9388 112 : NCDF_ERR(status);
9389 112 : CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
9390 : poDS->cdfid, NCDF_DIMNAME_Y, nYSize, poDS->nYDimID);
9391 : }
9392 :
9393 165 : return poDS;
9394 : }
9395 :
9396 : /************************************************************************/
9397 : /* Create() */
9398 : /************************************************************************/
9399 :
9400 127 : GDALDataset *netCDFDataset::Create(const char *pszFilename, int nXSize,
9401 : int nYSize, int nBandsIn, GDALDataType eType,
9402 : char **papszOptions)
9403 : {
9404 127 : CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::Create(%s, ...)",
9405 : pszFilename);
9406 :
9407 : const char *legacyCreationOp =
9408 127 : CSLFetchNameValueDef(papszOptions, "GEOMETRY_ENCODING", "CF_1.8");
9409 254 : std::string legacyCreationOp_s = std::string(legacyCreationOp);
9410 :
9411 : // Check legacy creation op FIRST
9412 :
9413 127 : bool legacyCreateMode = false;
9414 :
9415 127 : if (nXSize != 0 || nYSize != 0 || nBandsIn != 0)
9416 : {
9417 56 : legacyCreateMode = true;
9418 : }
9419 71 : else if (legacyCreationOp_s == "CF_1.8")
9420 : {
9421 54 : legacyCreateMode = false;
9422 : }
9423 :
9424 17 : else if (legacyCreationOp_s == "WKT")
9425 : {
9426 17 : legacyCreateMode = true;
9427 : }
9428 :
9429 : else
9430 : {
9431 0 : CPLError(
9432 : CE_Failure, CPLE_NotSupported,
9433 : "Dataset creation option GEOMETRY_ENCODING=%s is not supported.",
9434 : legacyCreationOp_s.c_str());
9435 0 : return nullptr;
9436 : }
9437 :
9438 254 : CPLStringList aosOptions(CSLDuplicate(papszOptions));
9439 240 : if (aosOptions.FetchNameValue("FORMAT") == nullptr &&
9440 113 : (eType == GDT_UInt16 || eType == GDT_UInt32 || eType == GDT_UInt64 ||
9441 : eType == GDT_Int64))
9442 : {
9443 10 : CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
9444 10 : aosOptions.SetNameValue("FORMAT", "NC4");
9445 : }
9446 :
9447 254 : CPLStringList aosBandNames;
9448 127 : if (const char *pszBandNames = aosOptions.FetchNameValue("BAND_NAMES"))
9449 : {
9450 : aosBandNames =
9451 2 : CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);
9452 :
9453 2 : if (aosBandNames.Count() != nBandsIn)
9454 : {
9455 1 : CPLError(CE_Failure, CPLE_OpenFailed,
9456 : "Attempted to create netCDF with %d bands but %d names "
9457 : "provided in BAND_NAMES.",
9458 : nBandsIn, aosBandNames.Count());
9459 :
9460 1 : return nullptr;
9461 : }
9462 : }
9463 :
9464 252 : CPLMutexHolderD(&hNCMutex);
9465 :
9466 126 : auto poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize, nBandsIn,
9467 : aosOptions.List());
9468 :
9469 126 : if (!poDS)
9470 19 : return nullptr;
9471 :
9472 107 : if (!legacyCreateMode)
9473 : {
9474 37 : poDS->bSGSupport = true;
9475 37 : poDS->vcdf.enableFullVirtualMode();
9476 : }
9477 :
9478 : else
9479 : {
9480 70 : poDS->bSGSupport = false;
9481 : }
9482 :
9483 : // Should we write signed or unsigned byte?
9484 : // TODO should this only be done in Create()
9485 107 : poDS->bSignedData = true;
9486 107 : const char *pszValue = CSLFetchNameValueDef(papszOptions, "PIXELTYPE", "");
9487 107 : if (eType == GDT_Byte && !EQUAL(pszValue, "SIGNEDBYTE"))
9488 15 : poDS->bSignedData = false;
9489 :
9490 : // Add Conventions, GDAL info and history.
9491 107 : if (poDS->cdfid >= 0)
9492 : {
9493 : const char *CF_Vector_Conv =
9494 173 : poDS->bSGSupport ||
9495 : // Use of variable length strings require CF-1.8
9496 68 : EQUAL(aosOptions.FetchNameValueDef("FORMAT", ""), "NC4")
9497 : ? NCDF_CONVENTIONS_CF_V1_8
9498 173 : : NCDF_CONVENTIONS_CF_V1_6;
9499 105 : poDS->bWriteGDALVersion = CPLTestBool(
9500 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
9501 105 : poDS->bWriteGDALHistory = CPLTestBool(
9502 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
9503 105 : NCDFAddGDALHistory(poDS->cdfid, pszFilename, poDS->bWriteGDALVersion,
9504 105 : poDS->bWriteGDALHistory, "", "Create",
9505 : (nBandsIn == 0) ? CF_Vector_Conv
9506 : : GDAL_DEFAULT_NCDF_CONVENTIONS);
9507 : }
9508 :
9509 : // Define bands.
9510 198 : for (int iBand = 1; iBand <= nBandsIn; iBand++)
9511 : {
9512 : const char *pszBandName =
9513 91 : aosBandNames.empty() ? nullptr : aosBandNames[iBand - 1];
9514 :
9515 91 : poDS->SetBand(iBand, new netCDFRasterBand(
9516 91 : netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS,
9517 91 : eType, iBand, poDS->bSignedData, pszBandName));
9518 : }
9519 :
9520 107 : CPLDebug("GDAL_netCDF", "netCDFDataset::Create(%s, ...) done", pszFilename);
9521 : // Return same dataset.
9522 107 : return poDS;
9523 : }
9524 :
9525 : template <class T>
9526 91 : static CPLErr NCDFCopyBand(GDALRasterBand *poSrcBand, GDALRasterBand *poDstBand,
9527 : int nXSize, int nYSize, GDALProgressFunc pfnProgress,
9528 : void *pProgressData)
9529 : {
9530 91 : const GDALDataType eDT = poSrcBand->GetRasterDataType();
9531 91 : T *patScanline = static_cast<T *>(VSI_MALLOC2_VERBOSE(nXSize, sizeof(T)));
9532 91 : CPLErr eErr = patScanline ? CE_None : CE_Failure;
9533 :
9534 6308 : for (int iLine = 0; iLine < nYSize && eErr == CE_None; iLine++)
9535 : {
9536 6217 : eErr = poSrcBand->RasterIO(GF_Read, 0, iLine, nXSize, 1, patScanline,
9537 : nXSize, 1, eDT, 0, 0, nullptr);
9538 6217 : if (eErr != CE_None)
9539 : {
9540 0 : CPLDebug(
9541 : "GDAL_netCDF",
9542 : "NCDFCopyBand(), poSrcBand->RasterIO() returned error code %d",
9543 : eErr);
9544 : }
9545 : else
9546 : {
9547 6217 : eErr =
9548 : poDstBand->RasterIO(GF_Write, 0, iLine, nXSize, 1, patScanline,
9549 : nXSize, 1, eDT, 0, 0, nullptr);
9550 6217 : if (eErr != CE_None)
9551 0 : CPLDebug("GDAL_netCDF",
9552 : "NCDFCopyBand(), poDstBand->RasterIO() returned error "
9553 : "code %d",
9554 : eErr);
9555 : }
9556 :
9557 6217 : if (nYSize > 10 && (iLine % (nYSize / 10) == 1))
9558 : {
9559 277 : if (!pfnProgress(1.0 * iLine / nYSize, nullptr, pProgressData))
9560 : {
9561 0 : eErr = CE_Failure;
9562 0 : CPLError(CE_Failure, CPLE_UserInterrupt,
9563 : "User terminated CreateCopy()");
9564 : }
9565 : }
9566 : }
9567 :
9568 91 : CPLFree(patScanline);
9569 :
9570 91 : pfnProgress(1.0, nullptr, pProgressData);
9571 :
9572 91 : return eErr;
9573 : }
9574 :
9575 : /************************************************************************/
9576 : /* CreateCopy() */
9577 : /************************************************************************/
9578 :
9579 : GDALDataset *
9580 87 : netCDFDataset::CreateCopy(const char *pszFilename, GDALDataset *poSrcDS,
9581 : CPL_UNUSED int bStrict, char **papszOptions,
9582 : GDALProgressFunc pfnProgress, void *pProgressData)
9583 : {
9584 174 : CPLMutexHolderD(&hNCMutex);
9585 :
9586 87 : CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::CreateCopy(%s, ...)",
9587 : pszFilename);
9588 :
9589 87 : if (poSrcDS->GetRootGroup())
9590 : {
9591 10 : auto poDrv = GDALDriver::FromHandle(GDALGetDriverByName("netCDF"));
9592 10 : if (poDrv)
9593 : {
9594 10 : return poDrv->DefaultCreateCopy(pszFilename, poSrcDS, bStrict,
9595 : papszOptions, pfnProgress,
9596 10 : pProgressData);
9597 : }
9598 : }
9599 :
9600 77 : const int nBands = poSrcDS->GetRasterCount();
9601 77 : const int nXSize = poSrcDS->GetRasterXSize();
9602 77 : const int nYSize = poSrcDS->GetRasterYSize();
9603 77 : const char *pszWKT = poSrcDS->GetProjectionRef();
9604 :
9605 : // Check input bands for errors.
9606 77 : if (nBands == 0)
9607 : {
9608 1 : CPLError(CE_Failure, CPLE_NotSupported,
9609 : "NetCDF driver does not support "
9610 : "source dataset with zero band.");
9611 1 : return nullptr;
9612 : }
9613 :
9614 76 : GDALDataType eDT = GDT_Unknown;
9615 76 : GDALRasterBand *poSrcBand = nullptr;
9616 181 : for (int iBand = 1; iBand <= nBands; iBand++)
9617 : {
9618 109 : poSrcBand = poSrcDS->GetRasterBand(iBand);
9619 109 : eDT = poSrcBand->GetRasterDataType();
9620 109 : if (eDT == GDT_Unknown || GDALDataTypeIsComplex(eDT))
9621 : {
9622 4 : CPLError(CE_Failure, CPLE_NotSupported,
9623 : "NetCDF driver does not support source dataset with band "
9624 : "of complex type.");
9625 4 : return nullptr;
9626 : }
9627 : }
9628 :
9629 144 : CPLStringList aosBandNames;
9630 72 : if (const char *pszBandNames =
9631 72 : CSLFetchNameValue(papszOptions, "BAND_NAMES"))
9632 : {
9633 : aosBandNames =
9634 2 : CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);
9635 :
9636 2 : if (aosBandNames.Count() != nBands)
9637 : {
9638 1 : CPLError(CE_Failure, CPLE_OpenFailed,
9639 : "Attempted to create netCDF with %d bands but %d names "
9640 : "provided in BAND_NAMES.",
9641 : nBands, aosBandNames.Count());
9642 :
9643 1 : return nullptr;
9644 : }
9645 : }
9646 :
9647 71 : if (!pfnProgress(0.0, nullptr, pProgressData))
9648 0 : return nullptr;
9649 :
9650 : // Same as in Create().
9651 142 : CPLStringList aosOptions(CSLDuplicate(papszOptions));
9652 133 : if (aosOptions.FetchNameValue("FORMAT") == nullptr &&
9653 62 : (eDT == GDT_UInt16 || eDT == GDT_UInt32 || eDT == GDT_UInt64 ||
9654 : eDT == GDT_Int64))
9655 : {
9656 6 : CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
9657 6 : aosOptions.SetNameValue("FORMAT", "NC4");
9658 : }
9659 71 : netCDFDataset *poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize,
9660 : nBands, aosOptions.List());
9661 71 : if (!poDS)
9662 13 : return nullptr;
9663 :
9664 : // Copy global metadata.
9665 : // Add Conventions, GDAL info and history.
9666 58 : CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid, NC_GLOBAL, nullptr);
9667 58 : const bool bWriteGDALVersion = CPLTestBool(
9668 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
9669 58 : const bool bWriteGDALHistory = CPLTestBool(
9670 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
9671 58 : NCDFAddGDALHistory(
9672 : poDS->cdfid, pszFilename, bWriteGDALVersion, bWriteGDALHistory,
9673 58 : poSrcDS->GetMetadataItem("NC_GLOBAL#history"), "CreateCopy",
9674 58 : poSrcDS->GetMetadataItem("NC_GLOBAL#Conventions"));
9675 :
9676 58 : pfnProgress(0.1, nullptr, pProgressData);
9677 :
9678 : // Check for extra dimensions.
9679 58 : int nDim = 2;
9680 : char **papszExtraDimNames =
9681 58 : NCDFTokenizeArray(poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
9682 58 : char **papszExtraDimValues = nullptr;
9683 :
9684 58 : if (papszExtraDimNames != nullptr && CSLCount(papszExtraDimNames) > 0)
9685 : {
9686 5 : size_t nDimSizeTot = 1;
9687 : // first make sure dimensions lengths compatible with band count
9688 : // for( int i=0; i<CSLCount(papszExtraDimNames ); i++ ) {
9689 13 : for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--)
9690 : {
9691 : char szTemp[NC_MAX_NAME + 32 + 1];
9692 8 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
9693 8 : papszExtraDimNames[i]);
9694 : papszExtraDimValues =
9695 8 : NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
9696 8 : const size_t nDimSize = atol(papszExtraDimValues[0]);
9697 8 : CSLDestroy(papszExtraDimValues);
9698 8 : nDimSizeTot *= nDimSize;
9699 : }
9700 5 : if (nDimSizeTot == (size_t)nBands)
9701 : {
9702 5 : nDim = 2 + CSLCount(papszExtraDimNames);
9703 : }
9704 : else
9705 : {
9706 : // if nBands != #bands computed raise a warning
9707 : // just issue a debug message, because it was probably intentional
9708 0 : CPLDebug("GDAL_netCDF",
9709 : "Warning: Number of bands (%d) is not compatible with "
9710 : "dimensions "
9711 : "(total=%ld names=%s)",
9712 : nBands, (long)nDimSizeTot,
9713 0 : poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
9714 0 : CSLDestroy(papszExtraDimNames);
9715 0 : papszExtraDimNames = nullptr;
9716 : }
9717 : }
9718 :
9719 58 : int *panDimIds = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
9720 58 : int *panBandDimPos = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
9721 :
9722 : nc_type nVarType;
9723 58 : int *panBandZLev = nullptr;
9724 58 : int *panDimVarIds = nullptr;
9725 :
9726 58 : if (nDim > 2)
9727 : {
9728 5 : panBandZLev = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
9729 5 : panDimVarIds = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
9730 :
9731 : // Define all dims.
9732 13 : for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--)
9733 : {
9734 8 : poDS->papszDimName.AddString(papszExtraDimNames[i]);
9735 : char szTemp[NC_MAX_NAME + 32 + 1];
9736 8 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
9737 8 : papszExtraDimNames[i]);
9738 : papszExtraDimValues =
9739 8 : NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
9740 8 : const int nDimSize = papszExtraDimValues && papszExtraDimValues[0]
9741 16 : ? atoi(papszExtraDimValues[0])
9742 : : 0;
9743 : // nc_type is an enum in netcdf-3, needs casting.
9744 8 : nVarType = static_cast<nc_type>(papszExtraDimValues &&
9745 8 : papszExtraDimValues[0] &&
9746 8 : papszExtraDimValues[1]
9747 8 : ? atol(papszExtraDimValues[1])
9748 : : 0);
9749 8 : CSLDestroy(papszExtraDimValues);
9750 8 : panBandZLev[i] = nDimSize;
9751 8 : panBandDimPos[i + 2] = i; // Save Position of ZDim.
9752 :
9753 : // Define dim.
9754 16 : int status = nc_def_dim(poDS->cdfid, papszExtraDimNames[i],
9755 8 : nDimSize, &(panDimIds[i]));
9756 8 : NCDF_ERR(status);
9757 :
9758 : // Define dim var.
9759 8 : int anDim[1] = {panDimIds[i]};
9760 16 : status = nc_def_var(poDS->cdfid, papszExtraDimNames[i], nVarType, 1,
9761 8 : anDim, &(panDimVarIds[i]));
9762 8 : NCDF_ERR(status);
9763 :
9764 : // Add dim metadata, using global var# items.
9765 8 : snprintf(szTemp, sizeof(szTemp), "%s#", papszExtraDimNames[i]);
9766 8 : CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid,
9767 8 : panDimVarIds[i], szTemp);
9768 : }
9769 : }
9770 :
9771 : // Copy GeoTransform and Projection.
9772 :
9773 : // Copy geolocation info.
9774 58 : char **papszGeolocationInfo = poSrcDS->GetMetadata("GEOLOCATION");
9775 58 : if (papszGeolocationInfo != nullptr)
9776 5 : poDS->GDALPamDataset::SetMetadata(papszGeolocationInfo, "GEOLOCATION");
9777 :
9778 : // Copy geotransform.
9779 58 : bool bGotGeoTransform = false;
9780 58 : GDALGeoTransform gt;
9781 58 : CPLErr eErr = poSrcDS->GetGeoTransform(gt);
9782 58 : if (eErr == CE_None)
9783 : {
9784 40 : poDS->SetGeoTransform(gt);
9785 : // Disable AddProjectionVars() from being called.
9786 40 : bGotGeoTransform = true;
9787 40 : poDS->m_bHasGeoTransform = false;
9788 : }
9789 :
9790 : // Copy projection.
9791 58 : void *pScaledProgress = nullptr;
9792 58 : if (bGotGeoTransform || (pszWKT && pszWKT[0] != 0))
9793 : {
9794 41 : poDS->SetProjection(pszWKT ? pszWKT : "");
9795 :
9796 : // Now we can call AddProjectionVars() directly.
9797 41 : poDS->m_bHasGeoTransform = bGotGeoTransform;
9798 41 : poDS->AddProjectionVars(true, nullptr, nullptr);
9799 : pScaledProgress =
9800 41 : GDALCreateScaledProgress(0.1, 0.25, pfnProgress, pProgressData);
9801 41 : poDS->AddProjectionVars(false, GDALScaledProgress, pScaledProgress);
9802 41 : GDALDestroyScaledProgress(pScaledProgress);
9803 : }
9804 : else
9805 : {
9806 17 : poDS->bBottomUp =
9807 17 : CPL_TO_BOOL(CSLFetchBoolean(papszOptions, "WRITE_BOTTOMUP", TRUE));
9808 17 : if (papszGeolocationInfo)
9809 : {
9810 4 : poDS->AddProjectionVars(true, nullptr, nullptr);
9811 4 : poDS->AddProjectionVars(false, nullptr, nullptr);
9812 : }
9813 : }
9814 :
9815 : // Save X,Y dim positions.
9816 58 : panDimIds[nDim - 1] = poDS->nXDimID;
9817 58 : panBandDimPos[0] = nDim - 1;
9818 58 : panDimIds[nDim - 2] = poDS->nYDimID;
9819 58 : panBandDimPos[1] = nDim - 2;
9820 :
9821 : // Write extra dim values - after projection for optimization.
9822 58 : if (nDim > 2)
9823 : {
9824 : // Make sure we are in data mode.
9825 5 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
9826 13 : for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--)
9827 : {
9828 : char szTemp[NC_MAX_NAME + 32 + 1];
9829 8 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_VALUES",
9830 8 : papszExtraDimNames[i]);
9831 8 : if (poSrcDS->GetMetadataItem(szTemp) != nullptr)
9832 : {
9833 8 : NCDFPut1DVar(poDS->cdfid, panDimVarIds[i],
9834 8 : poSrcDS->GetMetadataItem(szTemp));
9835 : }
9836 : }
9837 : }
9838 :
9839 58 : pfnProgress(0.25, nullptr, pProgressData);
9840 :
9841 : // Define Bands.
9842 58 : netCDFRasterBand *poBand = nullptr;
9843 58 : int nBandID = -1;
9844 :
9845 149 : for (int iBand = 1; iBand <= nBands; iBand++)
9846 : {
9847 91 : CPLDebug("GDAL_netCDF", "creating band # %d/%d nDim = %d", iBand,
9848 : nBands, nDim);
9849 :
9850 91 : poSrcBand = poSrcDS->GetRasterBand(iBand);
9851 91 : eDT = poSrcBand->GetRasterDataType();
9852 :
9853 : // Get var name from NETCDF_VARNAME.
9854 : const char *pszNETCDF_VARNAME =
9855 91 : poSrcBand->GetMetadataItem("NETCDF_VARNAME");
9856 : char szBandName[NC_MAX_NAME + 1];
9857 91 : if (!aosBandNames.empty())
9858 : {
9859 2 : snprintf(szBandName, sizeof(szBandName), "%s",
9860 : aosBandNames[iBand - 1]);
9861 : }
9862 89 : else if (pszNETCDF_VARNAME)
9863 : {
9864 32 : if (nBands > 1 && papszExtraDimNames == nullptr)
9865 0 : snprintf(szBandName, sizeof(szBandName), "%s%d",
9866 : pszNETCDF_VARNAME, iBand);
9867 : else
9868 32 : snprintf(szBandName, sizeof(szBandName), "%s",
9869 : pszNETCDF_VARNAME);
9870 : }
9871 : else
9872 : {
9873 57 : szBandName[0] = '\0';
9874 : }
9875 :
9876 : // Get long_name from <var>#long_name.
9877 91 : const char *pszLongName = "";
9878 91 : if (pszNETCDF_VARNAME)
9879 : {
9880 : pszLongName =
9881 64 : poSrcDS->GetMetadataItem(std::string(pszNETCDF_VARNAME)
9882 32 : .append("#")
9883 32 : .append(CF_LNG_NAME)
9884 32 : .c_str());
9885 32 : if (!pszLongName)
9886 25 : pszLongName = "";
9887 : }
9888 :
9889 91 : constexpr bool bSignedData = false;
9890 :
9891 91 : if (nDim > 2)
9892 27 : poBand = new netCDFRasterBand(
9893 27 : netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
9894 : bSignedData, szBandName, pszLongName, nBandID, nDim, iBand - 1,
9895 27 : panBandZLev, panBandDimPos, panDimIds);
9896 : else
9897 64 : poBand = new netCDFRasterBand(
9898 64 : netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
9899 64 : bSignedData, szBandName, pszLongName);
9900 :
9901 91 : poDS->SetBand(iBand, poBand);
9902 :
9903 : // Set nodata value, if any.
9904 91 : GDALCopyNoDataValue(poBand, poSrcBand);
9905 :
9906 : // Copy Metadata for band.
9907 91 : CopyMetadata(nullptr, poSrcDS->GetRasterBand(iBand), poBand,
9908 : poDS->cdfid, poBand->nZId);
9909 :
9910 : // If more than 2D pass the first band's netcdf var ID to subsequent
9911 : // bands.
9912 91 : if (nDim > 2)
9913 27 : nBandID = poBand->nZId;
9914 : }
9915 :
9916 : // Write projection variable to band variable.
9917 58 : poDS->AddGridMappingRef();
9918 :
9919 58 : pfnProgress(0.5, nullptr, pProgressData);
9920 :
9921 : // Write bands.
9922 :
9923 : // Make sure we are in data mode.
9924 58 : poDS->SetDefineMode(false);
9925 :
9926 58 : double dfTemp = 0.5;
9927 :
9928 58 : eErr = CE_None;
9929 :
9930 149 : for (int iBand = 1; iBand <= nBands && eErr == CE_None; iBand++)
9931 : {
9932 91 : const double dfTemp2 = dfTemp + 0.4 / nBands;
9933 91 : pScaledProgress = GDALCreateScaledProgress(dfTemp, dfTemp2, pfnProgress,
9934 : pProgressData);
9935 91 : dfTemp = dfTemp2;
9936 :
9937 91 : CPLDebug("GDAL_netCDF", "copying band data # %d/%d ", iBand, nBands);
9938 :
9939 91 : poSrcBand = poSrcDS->GetRasterBand(iBand);
9940 91 : eDT = poSrcBand->GetRasterDataType();
9941 :
9942 91 : GDALRasterBand *poDstBand = poDS->GetRasterBand(iBand);
9943 :
9944 : // Copy band data.
9945 91 : if (eDT == GDT_Byte)
9946 : {
9947 51 : CPLDebug("GDAL_netCDF", "GByte Band#%d", iBand);
9948 51 : eErr = NCDFCopyBand<GByte>(poSrcBand, poDstBand, nXSize, nYSize,
9949 : GDALScaledProgress, pScaledProgress);
9950 : }
9951 40 : else if (eDT == GDT_Int8)
9952 : {
9953 1 : CPLDebug("GDAL_netCDF", "GInt8 Band#%d", iBand);
9954 1 : eErr = NCDFCopyBand<GInt8>(poSrcBand, poDstBand, nXSize, nYSize,
9955 : GDALScaledProgress, pScaledProgress);
9956 : }
9957 39 : else if (eDT == GDT_UInt16)
9958 : {
9959 2 : CPLDebug("GDAL_netCDF", "GUInt16 Band#%d", iBand);
9960 2 : eErr = NCDFCopyBand<GInt16>(poSrcBand, poDstBand, nXSize, nYSize,
9961 : GDALScaledProgress, pScaledProgress);
9962 : }
9963 37 : else if (eDT == GDT_Int16)
9964 : {
9965 5 : CPLDebug("GDAL_netCDF", "GInt16 Band#%d", iBand);
9966 5 : eErr = NCDFCopyBand<GUInt16>(poSrcBand, poDstBand, nXSize, nYSize,
9967 : GDALScaledProgress, pScaledProgress);
9968 : }
9969 32 : else if (eDT == GDT_UInt32)
9970 : {
9971 2 : CPLDebug("GDAL_netCDF", "GUInt32 Band#%d", iBand);
9972 2 : eErr = NCDFCopyBand<GUInt32>(poSrcBand, poDstBand, nXSize, nYSize,
9973 : GDALScaledProgress, pScaledProgress);
9974 : }
9975 30 : else if (eDT == GDT_Int32)
9976 : {
9977 18 : CPLDebug("GDAL_netCDF", "GInt32 Band#%d", iBand);
9978 18 : eErr = NCDFCopyBand<GInt32>(poSrcBand, poDstBand, nXSize, nYSize,
9979 : GDALScaledProgress, pScaledProgress);
9980 : }
9981 12 : else if (eDT == GDT_UInt64)
9982 : {
9983 2 : CPLDebug("GDAL_netCDF", "GUInt64 Band#%d", iBand);
9984 2 : eErr = NCDFCopyBand<std::uint64_t>(poSrcBand, poDstBand, nXSize,
9985 : nYSize, GDALScaledProgress,
9986 : pScaledProgress);
9987 : }
9988 10 : else if (eDT == GDT_Int64)
9989 : {
9990 2 : CPLDebug("GDAL_netCDF", "GInt64 Band#%d", iBand);
9991 : eErr =
9992 2 : NCDFCopyBand<std::int64_t>(poSrcBand, poDstBand, nXSize, nYSize,
9993 : GDALScaledProgress, pScaledProgress);
9994 : }
9995 8 : else if (eDT == GDT_Float32)
9996 : {
9997 6 : CPLDebug("GDAL_netCDF", "float Band#%d", iBand);
9998 6 : eErr = NCDFCopyBand<float>(poSrcBand, poDstBand, nXSize, nYSize,
9999 : GDALScaledProgress, pScaledProgress);
10000 : }
10001 2 : else if (eDT == GDT_Float64)
10002 : {
10003 2 : CPLDebug("GDAL_netCDF", "double Band#%d", iBand);
10004 2 : eErr = NCDFCopyBand<double>(poSrcBand, poDstBand, nXSize, nYSize,
10005 : GDALScaledProgress, pScaledProgress);
10006 : }
10007 : else
10008 : {
10009 0 : CPLError(CE_Failure, CPLE_NotSupported,
10010 : "The NetCDF driver does not support GDAL data type %d",
10011 : eDT);
10012 : }
10013 :
10014 91 : GDALDestroyScaledProgress(pScaledProgress);
10015 : }
10016 :
10017 58 : delete (poDS);
10018 :
10019 58 : CPLFree(panDimIds);
10020 58 : CPLFree(panBandDimPos);
10021 58 : CPLFree(panBandZLev);
10022 58 : CPLFree(panDimVarIds);
10023 58 : if (papszExtraDimNames)
10024 5 : CSLDestroy(papszExtraDimNames);
10025 :
10026 58 : if (eErr != CE_None)
10027 0 : return nullptr;
10028 :
10029 58 : pfnProgress(0.95, nullptr, pProgressData);
10030 :
10031 : // Re-open dataset so we can return it.
10032 116 : CPLStringList aosOpenOptions;
10033 58 : aosOpenOptions.AddString("VARIABLES_AS_BANDS=YES");
10034 58 : GDALOpenInfo oOpenInfo(pszFilename, GA_Update);
10035 58 : oOpenInfo.nOpenFlags = GDAL_OF_RASTER | GDAL_OF_UPDATE;
10036 58 : oOpenInfo.papszOpenOptions = aosOpenOptions.List();
10037 58 : auto poRetDS = Open(&oOpenInfo);
10038 :
10039 : // PAM cloning is disabled. See bug #4244.
10040 : // if( poDS )
10041 : // poDS->CloneInfo(poSrcDS, GCIF_PAM_DEFAULT);
10042 :
10043 58 : pfnProgress(1.0, nullptr, pProgressData);
10044 :
10045 58 : return poRetDS;
10046 : }
10047 :
10048 : // Note: some logic depends on bIsProjected and bIsGeoGraphic.
10049 : // May not be known when Create() is called, see AddProjectionVars().
10050 305 : void netCDFDataset::ProcessCreationOptions()
10051 : {
10052 : const char *pszConfig =
10053 305 : CSLFetchNameValue(papszCreationOptions, "CONFIG_FILE");
10054 305 : if (pszConfig != nullptr)
10055 : {
10056 4 : if (oWriterConfig.Parse(pszConfig))
10057 : {
10058 : // Override dataset creation options from the config file
10059 2 : std::map<CPLString, CPLString>::iterator oIter;
10060 3 : for (oIter = oWriterConfig.m_oDatasetCreationOptions.begin();
10061 3 : oIter != oWriterConfig.m_oDatasetCreationOptions.end();
10062 1 : ++oIter)
10063 : {
10064 2 : papszCreationOptions = CSLSetNameValue(
10065 2 : papszCreationOptions, oIter->first, oIter->second);
10066 : }
10067 : }
10068 : }
10069 :
10070 : // File format.
10071 305 : eFormat = NCDF_FORMAT_NC;
10072 305 : const char *pszValue = CSLFetchNameValue(papszCreationOptions, "FORMAT");
10073 305 : if (pszValue != nullptr)
10074 : {
10075 143 : if (EQUAL(pszValue, "NC"))
10076 : {
10077 3 : eFormat = NCDF_FORMAT_NC;
10078 : }
10079 : #ifdef NETCDF_HAS_NC2
10080 140 : else if (EQUAL(pszValue, "NC2"))
10081 : {
10082 1 : eFormat = NCDF_FORMAT_NC2;
10083 : }
10084 : #endif
10085 139 : else if (EQUAL(pszValue, "NC4"))
10086 : {
10087 135 : eFormat = NCDF_FORMAT_NC4;
10088 : }
10089 4 : else if (EQUAL(pszValue, "NC4C"))
10090 : {
10091 4 : eFormat = NCDF_FORMAT_NC4C;
10092 : }
10093 : else
10094 : {
10095 0 : CPLError(CE_Failure, CPLE_NotSupported,
10096 : "FORMAT=%s in not supported, using the default NC format.",
10097 : pszValue);
10098 : }
10099 : }
10100 :
10101 : // COMPRESS option.
10102 305 : pszValue = CSLFetchNameValue(papszCreationOptions, "COMPRESS");
10103 305 : if (pszValue != nullptr)
10104 : {
10105 3 : if (EQUAL(pszValue, "NONE"))
10106 : {
10107 1 : eCompress = NCDF_COMPRESS_NONE;
10108 : }
10109 2 : else if (EQUAL(pszValue, "DEFLATE"))
10110 : {
10111 2 : eCompress = NCDF_COMPRESS_DEFLATE;
10112 2 : if (!((eFormat == NCDF_FORMAT_NC4) ||
10113 2 : (eFormat == NCDF_FORMAT_NC4C)))
10114 : {
10115 1 : CPLError(CE_Warning, CPLE_IllegalArg,
10116 : "NOTICE: Format set to NC4C because compression is "
10117 : "set to DEFLATE.");
10118 1 : eFormat = NCDF_FORMAT_NC4C;
10119 : }
10120 : }
10121 : else
10122 : {
10123 0 : CPLError(CE_Failure, CPLE_NotSupported,
10124 : "COMPRESS=%s is not supported.", pszValue);
10125 : }
10126 : }
10127 :
10128 : // ZLEVEL option.
10129 305 : pszValue = CSLFetchNameValue(papszCreationOptions, "ZLEVEL");
10130 305 : if (pszValue != nullptr)
10131 : {
10132 1 : nZLevel = atoi(pszValue);
10133 1 : if (!(nZLevel >= 1 && nZLevel <= 9))
10134 : {
10135 0 : CPLError(CE_Warning, CPLE_IllegalArg,
10136 : "ZLEVEL=%s value not recognised, ignoring.", pszValue);
10137 0 : nZLevel = NCDF_DEFLATE_LEVEL;
10138 : }
10139 : }
10140 :
10141 : // CHUNKING option.
10142 305 : bChunking =
10143 305 : CPL_TO_BOOL(CSLFetchBoolean(papszCreationOptions, "CHUNKING", TRUE));
10144 :
10145 : // MULTIPLE_LAYERS option.
10146 : const char *pszMultipleLayerBehavior =
10147 305 : CSLFetchNameValueDef(papszCreationOptions, "MULTIPLE_LAYERS", "NO");
10148 610 : const char *pszGeometryEnc = CSLFetchNameValueDef(
10149 305 : papszCreationOptions, "GEOMETRY_ENCODING", "CF_1.8");
10150 305 : if (EQUAL(pszMultipleLayerBehavior, "NO") ||
10151 4 : EQUAL(pszGeometryEnc, "CF_1.8"))
10152 : {
10153 301 : eMultipleLayerBehavior = SINGLE_LAYER;
10154 : }
10155 4 : else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_FILES"))
10156 : {
10157 3 : eMultipleLayerBehavior = SEPARATE_FILES;
10158 : }
10159 1 : else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_GROUPS"))
10160 : {
10161 1 : if (eFormat == NCDF_FORMAT_NC4)
10162 : {
10163 1 : eMultipleLayerBehavior = SEPARATE_GROUPS;
10164 : }
10165 : else
10166 : {
10167 0 : CPLError(CE_Warning, CPLE_IllegalArg,
10168 : "MULTIPLE_LAYERS=%s is recognised only with FORMAT=NC4",
10169 : pszMultipleLayerBehavior);
10170 : }
10171 : }
10172 : else
10173 : {
10174 0 : CPLError(CE_Warning, CPLE_IllegalArg,
10175 : "MULTIPLE_LAYERS=%s not recognised", pszMultipleLayerBehavior);
10176 : }
10177 :
10178 : // Set nCreateMode based on eFormat.
10179 305 : switch (eFormat)
10180 : {
10181 : #ifdef NETCDF_HAS_NC2
10182 1 : case NCDF_FORMAT_NC2:
10183 1 : nCreateMode = NC_CLOBBER | NC_64BIT_OFFSET;
10184 1 : break;
10185 : #endif
10186 135 : case NCDF_FORMAT_NC4:
10187 135 : nCreateMode = NC_CLOBBER | NC_NETCDF4;
10188 135 : break;
10189 5 : case NCDF_FORMAT_NC4C:
10190 5 : nCreateMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
10191 5 : break;
10192 164 : case NCDF_FORMAT_NC:
10193 : default:
10194 164 : nCreateMode = NC_CLOBBER;
10195 164 : break;
10196 : }
10197 :
10198 305 : CPLDebug("GDAL_netCDF", "file options: format=%d compress=%d zlevel=%d",
10199 305 : eFormat, eCompress, nZLevel);
10200 305 : }
10201 :
10202 278 : int netCDFDataset::DefVarDeflate(int nVarId, bool bChunkingArg)
10203 : {
10204 278 : if (eCompress == NCDF_COMPRESS_DEFLATE)
10205 : {
10206 : // Must set chunk size to avoid huge performance hit (set
10207 : // bChunkingArg=TRUE)
10208 : // perhaps another solution it to change the chunk cache?
10209 : // http://www.unidata.ucar.edu/software/netcdf/docs/netcdf.html#Chunk-Cache
10210 : // TODO: make sure this is okay.
10211 2 : CPLDebug("GDAL_netCDF", "DefVarDeflate(%d, %d) nZlevel=%d", nVarId,
10212 : static_cast<int>(bChunkingArg), nZLevel);
10213 :
10214 2 : int status = nc_def_var_deflate(cdfid, nVarId, 1, 1, nZLevel);
10215 2 : NCDF_ERR(status);
10216 :
10217 2 : if (status == NC_NOERR && bChunkingArg && bChunking)
10218 : {
10219 : // set chunking to be 1 for all dims, except X dim
10220 : // size_t chunksize[] = { 1, (size_t)nRasterXSize };
10221 : size_t chunksize[MAX_NC_DIMS];
10222 : int nd;
10223 2 : nc_inq_varndims(cdfid, nVarId, &nd);
10224 2 : chunksize[0] = (size_t)1;
10225 2 : chunksize[1] = (size_t)1;
10226 2 : for (int i = 2; i < nd; i++)
10227 0 : chunksize[i] = (size_t)1;
10228 2 : chunksize[nd - 1] = (size_t)nRasterXSize;
10229 :
10230 : // Config options just for testing purposes
10231 : const char *pszBlockXSize =
10232 2 : CPLGetConfigOption("BLOCKXSIZE", nullptr);
10233 2 : if (pszBlockXSize)
10234 0 : chunksize[nd - 1] = (size_t)atoi(pszBlockXSize);
10235 :
10236 : const char *pszBlockYSize =
10237 2 : CPLGetConfigOption("BLOCKYSIZE", nullptr);
10238 2 : if (nd >= 2 && pszBlockYSize)
10239 0 : chunksize[nd - 2] = (size_t)atoi(pszBlockYSize);
10240 :
10241 2 : CPLDebug("GDAL_netCDF",
10242 : "DefVarDeflate() chunksize={%ld, %ld} chunkX=%ld nd=%d",
10243 2 : (long)chunksize[0], (long)chunksize[1],
10244 2 : (long)chunksize[nd - 1], nd);
10245 : #ifdef NCDF_DEBUG
10246 : for (int i = 0; i < nd; i++)
10247 : CPLDebug("GDAL_netCDF", "DefVarDeflate() chunk[%d]=%ld", i,
10248 : chunksize[i]);
10249 : #endif
10250 :
10251 2 : status = nc_def_var_chunking(cdfid, nVarId, NC_CHUNKED, chunksize);
10252 2 : NCDF_ERR(status);
10253 : }
10254 : else
10255 : {
10256 0 : CPLDebug("GDAL_netCDF", "chunksize not set");
10257 : }
10258 2 : return status;
10259 : }
10260 276 : return NC_NOERR;
10261 : }
10262 :
10263 : /************************************************************************/
10264 : /* NCDFUnloadDriver() */
10265 : /************************************************************************/
10266 :
10267 8 : static void NCDFUnloadDriver(CPL_UNUSED GDALDriver *poDriver)
10268 : {
10269 8 : if (hNCMutex != nullptr)
10270 4 : CPLDestroyMutex(hNCMutex);
10271 8 : hNCMutex = nullptr;
10272 8 : }
10273 :
10274 : /************************************************************************/
10275 : /* GDALRegister_netCDF() */
10276 : /************************************************************************/
10277 :
10278 : class GDALnetCDFDriver final : public GDALDriver
10279 : {
10280 : public:
10281 19 : GDALnetCDFDriver() = default;
10282 :
10283 : const char *GetMetadataItem(const char *pszName,
10284 : const char *pszDomain) override;
10285 :
10286 91 : char **GetMetadata(const char *pszDomain) override
10287 : {
10288 182 : std::lock_guard oLock(m_oMutex);
10289 91 : InitializeDCAPVirtualIO();
10290 182 : return GDALDriver::GetMetadata(pszDomain);
10291 : }
10292 :
10293 : private:
10294 : std::recursive_mutex m_oMutex{};
10295 : bool m_bInitialized = false;
10296 :
10297 104 : void InitializeDCAPVirtualIO()
10298 : {
10299 104 : if (!m_bInitialized)
10300 : {
10301 12 : m_bInitialized = true;
10302 :
10303 : #ifdef ENABLE_UFFD
10304 12 : if (CPLIsUserFaultMappingSupported())
10305 : {
10306 12 : SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
10307 : }
10308 : #endif
10309 : }
10310 104 : }
10311 : };
10312 :
10313 1393 : const char *GDALnetCDFDriver::GetMetadataItem(const char *pszName,
10314 : const char *pszDomain)
10315 : {
10316 2786 : std::lock_guard oLock(m_oMutex);
10317 1393 : if (EQUAL(pszName, GDAL_DCAP_VIRTUALIO))
10318 : {
10319 13 : InitializeDCAPVirtualIO();
10320 : }
10321 2786 : return GDALDriver::GetMetadataItem(pszName, pszDomain);
10322 : }
10323 :
10324 19 : void GDALRegister_netCDF()
10325 :
10326 : {
10327 19 : if (!GDAL_CHECK_VERSION("netCDF driver"))
10328 0 : return;
10329 :
10330 19 : if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
10331 0 : return;
10332 :
10333 19 : GDALDriver *poDriver = new GDALnetCDFDriver();
10334 19 : netCDFDriverSetCommonMetadata(poDriver);
10335 :
10336 19 : poDriver->SetMetadataItem("NETCDF_CONVENTIONS",
10337 19 : GDAL_DEFAULT_NCDF_CONVENTIONS);
10338 19 : poDriver->SetMetadataItem("NETCDF_VERSION", nc_inq_libvers());
10339 :
10340 : // Set pfns and register driver.
10341 19 : poDriver->pfnOpen = netCDFDataset::Open;
10342 19 : poDriver->pfnCreateCopy = netCDFDataset::CreateCopy;
10343 19 : poDriver->pfnCreate = netCDFDataset::Create;
10344 19 : poDriver->pfnCreateMultiDimensional = netCDFDataset::CreateMultiDimensional;
10345 19 : poDriver->pfnUnloadDriver = NCDFUnloadDriver;
10346 :
10347 19 : GetGDALDriverManager()->RegisterDriver(poDriver);
10348 : }
10349 :
10350 : /************************************************************************/
10351 : /* New functions */
10352 : /************************************************************************/
10353 :
10354 : /* Test for GDAL version string >= target */
10355 242 : static bool NCDFIsGDALVersionGTE(const char *pszVersion, int nTarget)
10356 : {
10357 :
10358 : // Valid strings are "GDAL 1.9dev, released 2011/01/18" and "GDAL 1.8.1 ".
10359 242 : if (pszVersion == nullptr || EQUAL(pszVersion, ""))
10360 0 : return false;
10361 242 : else if (!STARTS_WITH_CI(pszVersion, "GDAL "))
10362 0 : return false;
10363 : // 2.0dev of 2011/12/29 has been later renamed as 1.10dev.
10364 242 : else if (EQUAL("GDAL 2.0dev, released 2011/12/29", pszVersion))
10365 0 : return nTarget <= GDAL_COMPUTE_VERSION(1, 10, 0);
10366 242 : else if (STARTS_WITH_CI(pszVersion, "GDAL 1.9dev"))
10367 2 : return nTarget <= 1900;
10368 240 : else if (STARTS_WITH_CI(pszVersion, "GDAL 1.8dev"))
10369 0 : return nTarget <= 1800;
10370 :
10371 240 : char **papszTokens = CSLTokenizeString2(pszVersion + 5, ".", 0);
10372 :
10373 240 : int nVersions[] = {0, 0, 0, 0};
10374 960 : for (int iToken = 0; papszTokens && iToken < 4 && papszTokens[iToken];
10375 : iToken++)
10376 : {
10377 720 : nVersions[iToken] = atoi(papszTokens[iToken]);
10378 720 : if (nVersions[iToken] < 0)
10379 0 : nVersions[iToken] = 0;
10380 720 : else if (nVersions[iToken] > 99)
10381 0 : nVersions[iToken] = 99;
10382 : }
10383 :
10384 240 : int nVersion = 0;
10385 240 : if (nVersions[0] > 1 || nVersions[1] >= 10)
10386 240 : nVersion =
10387 240 : GDAL_COMPUTE_VERSION(nVersions[0], nVersions[1], nVersions[2]);
10388 : else
10389 0 : nVersion = nVersions[0] * 1000 + nVersions[1] * 100 +
10390 0 : nVersions[2] * 10 + nVersions[3];
10391 :
10392 240 : CSLDestroy(papszTokens);
10393 240 : return nTarget <= nVersion;
10394 : }
10395 :
10396 : // Add Conventions, GDAL version and history.
10397 167 : static void NCDFAddGDALHistory(int fpImage, const char *pszFilename,
10398 : bool bWriteGDALVersion, bool bWriteGDALHistory,
10399 : const char *pszOldHist,
10400 : const char *pszFunctionName,
10401 : const char *pszCFVersion)
10402 : {
10403 167 : if (pszCFVersion == nullptr)
10404 : {
10405 42 : pszCFVersion = GDAL_DEFAULT_NCDF_CONVENTIONS;
10406 : }
10407 167 : int status = nc_put_att_text(fpImage, NC_GLOBAL, "Conventions",
10408 : strlen(pszCFVersion), pszCFVersion);
10409 167 : NCDF_ERR(status);
10410 :
10411 167 : if (bWriteGDALVersion)
10412 : {
10413 165 : const char *pszNCDF_GDAL = GDALVersionInfo("--version");
10414 165 : status = nc_put_att_text(fpImage, NC_GLOBAL, "GDAL",
10415 : strlen(pszNCDF_GDAL), pszNCDF_GDAL);
10416 165 : NCDF_ERR(status);
10417 : }
10418 :
10419 167 : if (bWriteGDALHistory)
10420 : {
10421 : // Add history.
10422 330 : CPLString osTmp;
10423 : #ifdef GDAL_SET_CMD_LINE_DEFINED_TMP
10424 : if (!EQUAL(GDALGetCmdLine(), ""))
10425 : osTmp = GDALGetCmdLine();
10426 : else
10427 : osTmp =
10428 : CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
10429 : #else
10430 165 : osTmp = CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
10431 : #endif
10432 :
10433 165 : NCDFAddHistory(fpImage, osTmp.c_str(), pszOldHist);
10434 : }
10435 2 : else if (pszOldHist != nullptr)
10436 : {
10437 0 : status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
10438 : strlen(pszOldHist), pszOldHist);
10439 0 : NCDF_ERR(status);
10440 : }
10441 167 : }
10442 :
10443 : // Code taken from cdo and libcdi, used for writing the history attribute.
10444 :
10445 : // void cdoDefHistory(int fileID, char *histstring)
10446 165 : static void NCDFAddHistory(int fpImage, const char *pszAddHist,
10447 : const char *pszOldHist)
10448 : {
10449 : // Check pszOldHist - as if there was no previous history, it will be
10450 : // a null pointer - if so set as empty.
10451 165 : if (nullptr == pszOldHist)
10452 : {
10453 53 : pszOldHist = "";
10454 : }
10455 :
10456 : char strtime[32];
10457 165 : strtime[0] = '\0';
10458 :
10459 165 : time_t tp = time(nullptr);
10460 165 : if (tp != -1)
10461 : {
10462 : struct tm ltime;
10463 165 : VSILocalTime(&tp, <ime);
10464 165 : (void)strftime(strtime, sizeof(strtime),
10465 : "%a %b %d %H:%M:%S %Y: ", <ime);
10466 : }
10467 :
10468 : // status = nc_get_att_text(fpImage, NC_GLOBAL,
10469 : // "history", pszOldHist);
10470 : // printf("status: %d pszOldHist: [%s]\n",status,pszOldHist);
10471 :
10472 165 : size_t nNewHistSize =
10473 165 : strlen(pszOldHist) + strlen(strtime) + strlen(pszAddHist) + 1 + 1;
10474 : char *pszNewHist =
10475 165 : static_cast<char *>(CPLMalloc(nNewHistSize * sizeof(char)));
10476 :
10477 165 : strcpy(pszNewHist, strtime);
10478 165 : strcat(pszNewHist, pszAddHist);
10479 :
10480 : // int disableHistory = FALSE;
10481 : // if( !disableHistory )
10482 : {
10483 165 : if (!EQUAL(pszOldHist, ""))
10484 3 : strcat(pszNewHist, "\n");
10485 165 : strcat(pszNewHist, pszOldHist);
10486 : }
10487 :
10488 165 : const int status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
10489 : strlen(pszNewHist), pszNewHist);
10490 165 : NCDF_ERR(status);
10491 :
10492 165 : CPLFree(pszNewHist);
10493 165 : }
10494 :
10495 6366 : static CPLErr NCDFSafeStrcat(char **ppszDest, const char *pszSrc,
10496 : size_t *nDestSize)
10497 : {
10498 : /* Reallocate the data string until the content fits */
10499 6366 : while (*nDestSize < (strlen(*ppszDest) + strlen(pszSrc) + 1))
10500 : {
10501 408 : (*nDestSize) *= 2;
10502 408 : *ppszDest = static_cast<char *>(
10503 408 : CPLRealloc(reinterpret_cast<void *>(*ppszDest), *nDestSize));
10504 : #ifdef NCDF_DEBUG
10505 : CPLDebug("GDAL_netCDF", "NCDFSafeStrcat() resized str from %ld to %ld",
10506 : (*nDestSize) / 2, *nDestSize);
10507 : #endif
10508 : }
10509 5958 : strcat(*ppszDest, pszSrc);
10510 :
10511 5958 : return CE_None;
10512 : }
10513 :
10514 : /* helper function for NCDFGetAttr() */
10515 : /* if pdfValue != nullptr, sets *pdfValue to first value returned */
10516 : /* if ppszValue != nullptr, sets *ppszValue with all attribute values */
10517 : /* *ppszValue is the responsibility of the caller and must be freed */
10518 66112 : static CPLErr NCDFGetAttr1(int nCdfId, int nVarId, const char *pszAttrName,
10519 : double *pdfValue, char **ppszValue)
10520 : {
10521 66112 : nc_type nAttrType = NC_NAT;
10522 66112 : size_t nAttrLen = 0;
10523 :
10524 66112 : if (ppszValue)
10525 64956 : *ppszValue = nullptr;
10526 :
10527 66112 : int status = nc_inq_att(nCdfId, nVarId, pszAttrName, &nAttrType, &nAttrLen);
10528 66112 : if (status != NC_NOERR)
10529 36120 : return CE_Failure;
10530 :
10531 : #ifdef NCDF_DEBUG
10532 : CPLDebug("GDAL_netCDF", "NCDFGetAttr1(%s) len=%ld type=%d", pszAttrName,
10533 : nAttrLen, nAttrType);
10534 : #endif
10535 29992 : if (nAttrLen == 0 && nAttrType != NC_CHAR)
10536 1 : return CE_Failure;
10537 :
10538 : /* Allocate guaranteed minimum size (use 10 or 20 if not a string) */
10539 29991 : size_t nAttrValueSize = nAttrLen + 1;
10540 29991 : if (nAttrType != NC_CHAR && nAttrValueSize < 10)
10541 3413 : nAttrValueSize = 10;
10542 29991 : if (nAttrType == NC_DOUBLE && nAttrValueSize < 20)
10543 1602 : nAttrValueSize = 20;
10544 29991 : if (nAttrType == NC_INT64 && nAttrValueSize < 20)
10545 22 : nAttrValueSize = 22;
10546 : char *pszAttrValue =
10547 29991 : static_cast<char *>(CPLCalloc(nAttrValueSize, sizeof(char)));
10548 29991 : *pszAttrValue = '\0';
10549 :
10550 29991 : if (nAttrLen > 1 && nAttrType != NC_CHAR)
10551 602 : NCDFSafeStrcat(&pszAttrValue, "{", &nAttrValueSize);
10552 :
10553 29991 : double dfValue = 0.0;
10554 29991 : size_t m = 0;
10555 : char szTemp[256];
10556 29991 : bool bSetDoubleFromStr = false;
10557 :
10558 29991 : switch (nAttrType)
10559 : {
10560 26576 : case NC_CHAR:
10561 26576 : CPL_IGNORE_RET_VAL(
10562 26576 : nc_get_att_text(nCdfId, nVarId, pszAttrName, pszAttrValue));
10563 26576 : pszAttrValue[nAttrLen] = '\0';
10564 26576 : bSetDoubleFromStr = true;
10565 26576 : dfValue = 0.0;
10566 26576 : break;
10567 94 : case NC_BYTE:
10568 : {
10569 : signed char *pscTemp = static_cast<signed char *>(
10570 94 : CPLCalloc(nAttrLen, sizeof(signed char)));
10571 94 : nc_get_att_schar(nCdfId, nVarId, pszAttrName, pscTemp);
10572 94 : dfValue = static_cast<double>(pscTemp[0]);
10573 94 : if (nAttrLen > 1)
10574 : {
10575 24 : for (m = 0; m < nAttrLen - 1; m++)
10576 : {
10577 13 : snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
10578 13 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10579 : }
10580 : }
10581 94 : snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
10582 94 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10583 94 : CPLFree(pscTemp);
10584 94 : break;
10585 : }
10586 487 : case NC_SHORT:
10587 : {
10588 : short *psTemp =
10589 487 : static_cast<short *>(CPLCalloc(nAttrLen, sizeof(short)));
10590 487 : nc_get_att_short(nCdfId, nVarId, pszAttrName, psTemp);
10591 487 : dfValue = static_cast<double>(psTemp[0]);
10592 487 : if (nAttrLen > 1)
10593 : {
10594 768 : for (m = 0; m < nAttrLen - 1; m++)
10595 : {
10596 384 : snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
10597 384 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10598 : }
10599 : }
10600 487 : snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
10601 487 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10602 487 : CPLFree(psTemp);
10603 487 : break;
10604 : }
10605 528 : case NC_INT:
10606 : {
10607 528 : int *pnTemp = static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
10608 528 : nc_get_att_int(nCdfId, nVarId, pszAttrName, pnTemp);
10609 528 : dfValue = static_cast<double>(pnTemp[0]);
10610 528 : if (nAttrLen > 1)
10611 : {
10612 218 : for (m = 0; m < nAttrLen - 1; m++)
10613 : {
10614 139 : snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
10615 139 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10616 : }
10617 : }
10618 528 : snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
10619 528 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10620 528 : CPLFree(pnTemp);
10621 528 : break;
10622 : }
10623 395 : case NC_FLOAT:
10624 : {
10625 : float *pfTemp =
10626 395 : static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
10627 395 : nc_get_att_float(nCdfId, nVarId, pszAttrName, pfTemp);
10628 395 : dfValue = static_cast<double>(pfTemp[0]);
10629 395 : if (nAttrLen > 1)
10630 : {
10631 60 : for (m = 0; m < nAttrLen - 1; m++)
10632 : {
10633 30 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
10634 30 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10635 : }
10636 : }
10637 395 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
10638 395 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10639 395 : CPLFree(pfTemp);
10640 395 : break;
10641 : }
10642 1602 : case NC_DOUBLE:
10643 : {
10644 : double *pdfTemp =
10645 1602 : static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
10646 1602 : nc_get_att_double(nCdfId, nVarId, pszAttrName, pdfTemp);
10647 1602 : dfValue = pdfTemp[0];
10648 1602 : if (nAttrLen > 1)
10649 : {
10650 166 : for (m = 0; m < nAttrLen - 1; m++)
10651 : {
10652 90 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
10653 90 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10654 : }
10655 : }
10656 1602 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
10657 1602 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10658 1602 : CPLFree(pdfTemp);
10659 1602 : break;
10660 : }
10661 167 : case NC_STRING:
10662 : {
10663 : char **ppszTemp =
10664 167 : static_cast<char **>(CPLCalloc(nAttrLen, sizeof(char *)));
10665 167 : nc_get_att_string(nCdfId, nVarId, pszAttrName, ppszTemp);
10666 167 : bSetDoubleFromStr = true;
10667 167 : dfValue = 0.0;
10668 167 : if (nAttrLen > 1)
10669 : {
10670 19 : for (m = 0; m < nAttrLen - 1; m++)
10671 : {
10672 12 : NCDFSafeStrcat(&pszAttrValue,
10673 12 : ppszTemp[m] ? ppszTemp[m] : "{NULL}",
10674 : &nAttrValueSize);
10675 12 : NCDFSafeStrcat(&pszAttrValue, ",", &nAttrValueSize);
10676 : }
10677 : }
10678 167 : NCDFSafeStrcat(&pszAttrValue, ppszTemp[m] ? ppszTemp[m] : "{NULL}",
10679 : &nAttrValueSize);
10680 167 : nc_free_string(nAttrLen, ppszTemp);
10681 167 : CPLFree(ppszTemp);
10682 167 : break;
10683 : }
10684 28 : case NC_UBYTE:
10685 : {
10686 : unsigned char *pucTemp = static_cast<unsigned char *>(
10687 28 : CPLCalloc(nAttrLen, sizeof(unsigned char)));
10688 28 : nc_get_att_uchar(nCdfId, nVarId, pszAttrName, pucTemp);
10689 28 : dfValue = static_cast<double>(pucTemp[0]);
10690 28 : if (nAttrLen > 1)
10691 : {
10692 0 : for (m = 0; m < nAttrLen - 1; m++)
10693 : {
10694 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
10695 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10696 : }
10697 : }
10698 28 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
10699 28 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10700 28 : CPLFree(pucTemp);
10701 28 : break;
10702 : }
10703 26 : case NC_USHORT:
10704 : {
10705 : unsigned short *pusTemp;
10706 : pusTemp = static_cast<unsigned short *>(
10707 26 : CPLCalloc(nAttrLen, sizeof(unsigned short)));
10708 26 : nc_get_att_ushort(nCdfId, nVarId, pszAttrName, pusTemp);
10709 26 : dfValue = static_cast<double>(pusTemp[0]);
10710 26 : if (nAttrLen > 1)
10711 : {
10712 10 : for (m = 0; m < nAttrLen - 1; m++)
10713 : {
10714 5 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
10715 5 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10716 : }
10717 : }
10718 26 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
10719 26 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10720 26 : CPLFree(pusTemp);
10721 26 : break;
10722 : }
10723 18 : case NC_UINT:
10724 : {
10725 : unsigned int *punTemp =
10726 18 : static_cast<unsigned int *>(CPLCalloc(nAttrLen, sizeof(int)));
10727 18 : nc_get_att_uint(nCdfId, nVarId, pszAttrName, punTemp);
10728 18 : dfValue = static_cast<double>(punTemp[0]);
10729 18 : if (nAttrLen > 1)
10730 : {
10731 0 : for (m = 0; m < nAttrLen - 1; m++)
10732 : {
10733 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
10734 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10735 : }
10736 : }
10737 18 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
10738 18 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10739 18 : CPLFree(punTemp);
10740 18 : break;
10741 : }
10742 22 : case NC_INT64:
10743 : {
10744 : GIntBig *panTemp =
10745 22 : static_cast<GIntBig *>(CPLCalloc(nAttrLen, sizeof(GIntBig)));
10746 22 : nc_get_att_longlong(nCdfId, nVarId, pszAttrName, panTemp);
10747 22 : dfValue = static_cast<double>(panTemp[0]);
10748 22 : if (nAttrLen > 1)
10749 : {
10750 0 : for (m = 0; m < nAttrLen - 1; m++)
10751 : {
10752 0 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",",
10753 0 : panTemp[m]);
10754 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10755 : }
10756 : }
10757 22 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, panTemp[m]);
10758 22 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10759 22 : CPLFree(panTemp);
10760 22 : break;
10761 : }
10762 22 : case NC_UINT64:
10763 : {
10764 : GUIntBig *panTemp =
10765 22 : static_cast<GUIntBig *>(CPLCalloc(nAttrLen, sizeof(GUIntBig)));
10766 22 : nc_get_att_ulonglong(nCdfId, nVarId, pszAttrName, panTemp);
10767 22 : dfValue = static_cast<double>(panTemp[0]);
10768 22 : if (nAttrLen > 1)
10769 : {
10770 0 : for (m = 0; m < nAttrLen - 1; m++)
10771 : {
10772 0 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",",
10773 0 : panTemp[m]);
10774 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10775 : }
10776 : }
10777 22 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, panTemp[m]);
10778 22 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10779 22 : CPLFree(panTemp);
10780 22 : break;
10781 : }
10782 26 : default:
10783 26 : CPLDebug("GDAL_netCDF",
10784 : "NCDFGetAttr unsupported type %d for attribute %s",
10785 : nAttrType, pszAttrName);
10786 26 : break;
10787 : }
10788 :
10789 29991 : if (nAttrLen > 1 && nAttrType != NC_CHAR)
10790 602 : NCDFSafeStrcat(&pszAttrValue, "}", &nAttrValueSize);
10791 :
10792 29991 : if (bSetDoubleFromStr)
10793 : {
10794 26743 : if (CPLGetValueType(pszAttrValue) == CPL_VALUE_STRING)
10795 : {
10796 26561 : if (ppszValue == nullptr && pdfValue != nullptr)
10797 : {
10798 1 : CPLFree(pszAttrValue);
10799 1 : return CE_Failure;
10800 : }
10801 : }
10802 26742 : dfValue = CPLAtof(pszAttrValue);
10803 : }
10804 :
10805 : /* set return values */
10806 29990 : if (ppszValue)
10807 29678 : *ppszValue = pszAttrValue;
10808 : else
10809 312 : CPLFree(pszAttrValue);
10810 :
10811 29990 : if (pdfValue)
10812 312 : *pdfValue = dfValue;
10813 :
10814 29990 : return CE_None;
10815 : }
10816 :
10817 : /* sets pdfValue to first value found */
10818 1156 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
10819 : double *pdfValue)
10820 : {
10821 1156 : return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, pdfValue, nullptr);
10822 : }
10823 :
10824 : /* pszValue is the responsibility of the caller and must be freed */
10825 64956 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
10826 : char **pszValue)
10827 : {
10828 64956 : return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, nullptr, pszValue);
10829 : }
10830 :
10831 : /* By default write NC_CHAR, but detect for int/float/double and */
10832 : /* NC4 string arrays */
10833 106 : static CPLErr NCDFPutAttr(int nCdfId, int nVarId, const char *pszAttrName,
10834 : const char *pszValue)
10835 : {
10836 106 : int status = 0;
10837 106 : char *pszTemp = nullptr;
10838 :
10839 : /* get the attribute values as tokens */
10840 106 : char **papszValues = NCDFTokenizeArray(pszValue);
10841 106 : if (papszValues == nullptr)
10842 0 : return CE_Failure;
10843 :
10844 106 : size_t nAttrLen = CSLCount(papszValues);
10845 :
10846 : /* first detect type */
10847 106 : nc_type nAttrType = NC_CHAR;
10848 106 : nc_type nTmpAttrType = NC_CHAR;
10849 225 : for (size_t i = 0; i < nAttrLen; i++)
10850 : {
10851 119 : nTmpAttrType = NC_CHAR;
10852 119 : bool bFoundType = false;
10853 119 : errno = 0;
10854 119 : int nValue = static_cast<int>(strtol(papszValues[i], &pszTemp, 10));
10855 : /* test for int */
10856 : /* TODO test for Byte and short - can this be done safely? */
10857 119 : if (errno == 0 && papszValues[i] != pszTemp && *pszTemp == 0)
10858 : {
10859 : char szTemp[256];
10860 19 : CPLsnprintf(szTemp, sizeof(szTemp), "%d", nValue);
10861 19 : if (EQUAL(szTemp, papszValues[i]))
10862 : {
10863 19 : bFoundType = true;
10864 19 : nTmpAttrType = NC_INT;
10865 : }
10866 : else
10867 : {
10868 : unsigned int unValue = static_cast<unsigned int>(
10869 0 : strtoul(papszValues[i], &pszTemp, 10));
10870 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", unValue);
10871 0 : if (EQUAL(szTemp, papszValues[i]))
10872 : {
10873 0 : bFoundType = true;
10874 0 : nTmpAttrType = NC_UINT;
10875 : }
10876 : }
10877 : }
10878 119 : if (!bFoundType)
10879 : {
10880 : /* test for double */
10881 100 : errno = 0;
10882 100 : double dfValue = CPLStrtod(papszValues[i], &pszTemp);
10883 100 : if ((errno == 0) && (papszValues[i] != pszTemp) && (*pszTemp == 0))
10884 : {
10885 : // Test for float instead of double.
10886 : // strtof() is C89, which is not available in MSVC.
10887 : // See if we loose precision if we cast to float and write to
10888 : // char*.
10889 14 : float fValue = float(dfValue);
10890 : char szTemp[256];
10891 14 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", fValue);
10892 14 : if (EQUAL(szTemp, papszValues[i]))
10893 8 : nTmpAttrType = NC_FLOAT;
10894 : else
10895 6 : nTmpAttrType = NC_DOUBLE;
10896 : }
10897 : }
10898 119 : if ((nTmpAttrType <= NC_DOUBLE && nAttrType <= NC_DOUBLE &&
10899 99 : nTmpAttrType > nAttrType) ||
10900 99 : (nTmpAttrType == NC_UINT && nAttrType < NC_FLOAT) ||
10901 5 : (nTmpAttrType >= NC_FLOAT && nAttrType == NC_UINT))
10902 20 : nAttrType = nTmpAttrType;
10903 : }
10904 :
10905 : #ifdef DEBUG
10906 106 : if (EQUAL(pszAttrName, "DEBUG_EMPTY_DOUBLE_ATTR"))
10907 : {
10908 0 : nAttrType = NC_DOUBLE;
10909 0 : nAttrLen = 0;
10910 : }
10911 : #endif
10912 :
10913 : /* now write the data */
10914 106 : if (nAttrType == NC_CHAR)
10915 : {
10916 86 : int nTmpFormat = 0;
10917 86 : if (nAttrLen > 1)
10918 : {
10919 0 : status = nc_inq_format(nCdfId, &nTmpFormat);
10920 0 : NCDF_ERR(status);
10921 : }
10922 86 : if (nAttrLen > 1 && nTmpFormat == NCDF_FORMAT_NC4)
10923 0 : status = nc_put_att_string(nCdfId, nVarId, pszAttrName, nAttrLen,
10924 : const_cast<const char **>(papszValues));
10925 : else
10926 86 : status = nc_put_att_text(nCdfId, nVarId, pszAttrName,
10927 : strlen(pszValue), pszValue);
10928 86 : NCDF_ERR(status);
10929 : }
10930 : else
10931 : {
10932 20 : switch (nAttrType)
10933 : {
10934 11 : case NC_INT:
10935 : {
10936 : int *pnTemp =
10937 11 : static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
10938 30 : for (size_t i = 0; i < nAttrLen; i++)
10939 : {
10940 19 : pnTemp[i] =
10941 19 : static_cast<int>(strtol(papszValues[i], &pszTemp, 10));
10942 : }
10943 11 : status = nc_put_att_int(nCdfId, nVarId, pszAttrName, NC_INT,
10944 : nAttrLen, pnTemp);
10945 11 : NCDF_ERR(status);
10946 11 : CPLFree(pnTemp);
10947 11 : break;
10948 : }
10949 0 : case NC_UINT:
10950 : {
10951 : unsigned int *punTemp = static_cast<unsigned int *>(
10952 0 : CPLCalloc(nAttrLen, sizeof(unsigned int)));
10953 0 : for (size_t i = 0; i < nAttrLen; i++)
10954 : {
10955 0 : punTemp[i] = static_cast<unsigned int>(
10956 0 : strtol(papszValues[i], &pszTemp, 10));
10957 : }
10958 0 : status = nc_put_att_uint(nCdfId, nVarId, pszAttrName, NC_UINT,
10959 : nAttrLen, punTemp);
10960 0 : NCDF_ERR(status);
10961 0 : CPLFree(punTemp);
10962 0 : break;
10963 : }
10964 6 : case NC_FLOAT:
10965 : {
10966 : float *pfTemp =
10967 6 : static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
10968 14 : for (size_t i = 0; i < nAttrLen; i++)
10969 : {
10970 8 : pfTemp[i] =
10971 8 : static_cast<float>(CPLStrtod(papszValues[i], &pszTemp));
10972 : }
10973 6 : status = nc_put_att_float(nCdfId, nVarId, pszAttrName, NC_FLOAT,
10974 : nAttrLen, pfTemp);
10975 6 : NCDF_ERR(status);
10976 6 : CPLFree(pfTemp);
10977 6 : break;
10978 : }
10979 3 : case NC_DOUBLE:
10980 : {
10981 : double *pdfTemp =
10982 3 : static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
10983 9 : for (size_t i = 0; i < nAttrLen; i++)
10984 : {
10985 6 : pdfTemp[i] = CPLStrtod(papszValues[i], &pszTemp);
10986 : }
10987 3 : status = nc_put_att_double(nCdfId, nVarId, pszAttrName,
10988 : NC_DOUBLE, nAttrLen, pdfTemp);
10989 3 : NCDF_ERR(status);
10990 3 : CPLFree(pdfTemp);
10991 3 : break;
10992 : }
10993 0 : default:
10994 0 : if (papszValues)
10995 0 : CSLDestroy(papszValues);
10996 0 : return CE_Failure;
10997 : }
10998 : }
10999 :
11000 106 : if (papszValues)
11001 106 : CSLDestroy(papszValues);
11002 :
11003 106 : return CE_None;
11004 : }
11005 :
11006 78 : static CPLErr NCDFGet1DVar(int nCdfId, int nVarId, char **pszValue)
11007 : {
11008 : /* get var information */
11009 78 : int nVarDimId = -1;
11010 78 : int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
11011 78 : if (status != NC_NOERR || nVarDimId != 1)
11012 0 : return CE_Failure;
11013 :
11014 78 : status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
11015 78 : if (status != NC_NOERR)
11016 0 : return CE_Failure;
11017 :
11018 78 : nc_type nVarType = NC_NAT;
11019 78 : status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
11020 78 : if (status != NC_NOERR)
11021 0 : return CE_Failure;
11022 :
11023 78 : size_t nVarLen = 0;
11024 78 : status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
11025 78 : if (status != NC_NOERR)
11026 0 : return CE_Failure;
11027 :
11028 78 : size_t start[1] = {0};
11029 78 : size_t count[1] = {nVarLen};
11030 :
11031 : /* Allocate guaranteed minimum size */
11032 78 : size_t nVarValueSize = NCDF_MAX_STR_LEN;
11033 : char *pszVarValue =
11034 78 : static_cast<char *>(CPLCalloc(nVarValueSize, sizeof(char)));
11035 78 : *pszVarValue = '\0';
11036 :
11037 78 : if (nVarLen == 0)
11038 : {
11039 : /* set return values */
11040 1 : *pszValue = pszVarValue;
11041 :
11042 1 : return CE_None;
11043 : }
11044 :
11045 77 : if (nVarLen > 1 && nVarType != NC_CHAR)
11046 42 : NCDFSafeStrcat(&pszVarValue, "{", &nVarValueSize);
11047 :
11048 77 : switch (nVarType)
11049 : {
11050 0 : case NC_CHAR:
11051 0 : nc_get_vara_text(nCdfId, nVarId, start, count, pszVarValue);
11052 0 : pszVarValue[nVarLen] = '\0';
11053 0 : break;
11054 0 : case NC_BYTE:
11055 : {
11056 : signed char *pscTemp = static_cast<signed char *>(
11057 0 : CPLCalloc(nVarLen, sizeof(signed char)));
11058 0 : nc_get_vara_schar(nCdfId, nVarId, start, count, pscTemp);
11059 : char szTemp[256];
11060 0 : size_t m = 0;
11061 0 : for (; m < nVarLen - 1; m++)
11062 : {
11063 0 : snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
11064 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11065 : }
11066 0 : snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
11067 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11068 0 : CPLFree(pscTemp);
11069 0 : break;
11070 : }
11071 0 : case NC_SHORT:
11072 : {
11073 : short *psTemp =
11074 0 : static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
11075 0 : nc_get_vara_short(nCdfId, nVarId, start, count, psTemp);
11076 : char szTemp[256];
11077 0 : size_t m = 0;
11078 0 : for (; m < nVarLen - 1; m++)
11079 : {
11080 0 : snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
11081 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11082 : }
11083 0 : snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
11084 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11085 0 : CPLFree(psTemp);
11086 0 : break;
11087 : }
11088 21 : case NC_INT:
11089 : {
11090 21 : int *pnTemp = static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
11091 21 : nc_get_vara_int(nCdfId, nVarId, start, count, pnTemp);
11092 : char szTemp[256];
11093 21 : size_t m = 0;
11094 44 : for (; m < nVarLen - 1; m++)
11095 : {
11096 23 : snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
11097 23 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11098 : }
11099 21 : snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
11100 21 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11101 21 : CPLFree(pnTemp);
11102 21 : break;
11103 : }
11104 8 : case NC_FLOAT:
11105 : {
11106 : float *pfTemp =
11107 8 : static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
11108 8 : nc_get_vara_float(nCdfId, nVarId, start, count, pfTemp);
11109 : char szTemp[256];
11110 8 : size_t m = 0;
11111 325 : for (; m < nVarLen - 1; m++)
11112 : {
11113 317 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
11114 317 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11115 : }
11116 8 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
11117 8 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11118 8 : CPLFree(pfTemp);
11119 8 : break;
11120 : }
11121 47 : case NC_DOUBLE:
11122 : {
11123 : double *pdfTemp =
11124 47 : static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
11125 47 : nc_get_vara_double(nCdfId, nVarId, start, count, pdfTemp);
11126 : char szTemp[256];
11127 47 : size_t m = 0;
11128 225 : for (; m < nVarLen - 1; m++)
11129 : {
11130 178 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
11131 178 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11132 : }
11133 47 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
11134 47 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11135 47 : CPLFree(pdfTemp);
11136 47 : break;
11137 : }
11138 0 : case NC_STRING:
11139 : {
11140 : char **ppszTemp =
11141 0 : static_cast<char **>(CPLCalloc(nVarLen, sizeof(char *)));
11142 0 : nc_get_vara_string(nCdfId, nVarId, start, count, ppszTemp);
11143 0 : size_t m = 0;
11144 0 : for (; m < nVarLen - 1; m++)
11145 : {
11146 0 : NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
11147 0 : NCDFSafeStrcat(&pszVarValue, ",", &nVarValueSize);
11148 : }
11149 0 : NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
11150 0 : nc_free_string(nVarLen, ppszTemp);
11151 0 : CPLFree(ppszTemp);
11152 0 : break;
11153 : }
11154 0 : case NC_UBYTE:
11155 : {
11156 : unsigned char *pucTemp;
11157 : pucTemp = static_cast<unsigned char *>(
11158 0 : CPLCalloc(nVarLen, sizeof(unsigned char)));
11159 0 : nc_get_vara_uchar(nCdfId, nVarId, start, count, pucTemp);
11160 : char szTemp[256];
11161 0 : size_t m = 0;
11162 0 : for (; m < nVarLen - 1; m++)
11163 : {
11164 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
11165 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11166 : }
11167 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
11168 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11169 0 : CPLFree(pucTemp);
11170 0 : break;
11171 : }
11172 0 : case NC_USHORT:
11173 : {
11174 : unsigned short *pusTemp;
11175 : pusTemp = static_cast<unsigned short *>(
11176 0 : CPLCalloc(nVarLen, sizeof(unsigned short)));
11177 0 : nc_get_vara_ushort(nCdfId, nVarId, start, count, pusTemp);
11178 : char szTemp[256];
11179 0 : size_t m = 0;
11180 0 : for (; m < nVarLen - 1; m++)
11181 : {
11182 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
11183 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11184 : }
11185 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
11186 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11187 0 : CPLFree(pusTemp);
11188 0 : break;
11189 : }
11190 0 : case NC_UINT:
11191 : {
11192 : unsigned int *punTemp;
11193 : punTemp = static_cast<unsigned int *>(
11194 0 : CPLCalloc(nVarLen, sizeof(unsigned int)));
11195 0 : nc_get_vara_uint(nCdfId, nVarId, start, count, punTemp);
11196 : char szTemp[256];
11197 0 : size_t m = 0;
11198 0 : for (; m < nVarLen - 1; m++)
11199 : {
11200 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
11201 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11202 : }
11203 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
11204 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11205 0 : CPLFree(punTemp);
11206 0 : break;
11207 : }
11208 1 : case NC_INT64:
11209 : {
11210 : long long *pnTemp =
11211 1 : static_cast<long long *>(CPLCalloc(nVarLen, sizeof(long long)));
11212 1 : nc_get_vara_longlong(nCdfId, nVarId, start, count, pnTemp);
11213 : char szTemp[256];
11214 1 : size_t m = 0;
11215 2 : for (; m < nVarLen - 1; m++)
11216 : {
11217 1 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",", pnTemp[m]);
11218 1 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11219 : }
11220 1 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, pnTemp[m]);
11221 1 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11222 1 : CPLFree(pnTemp);
11223 1 : break;
11224 : }
11225 0 : case NC_UINT64:
11226 : {
11227 : unsigned long long *pnTemp = static_cast<unsigned long long *>(
11228 0 : CPLCalloc(nVarLen, sizeof(unsigned long long)));
11229 0 : nc_get_vara_ulonglong(nCdfId, nVarId, start, count, pnTemp);
11230 : char szTemp[256];
11231 0 : size_t m = 0;
11232 0 : for (; m < nVarLen - 1; m++)
11233 : {
11234 0 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",", pnTemp[m]);
11235 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11236 : }
11237 0 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, pnTemp[m]);
11238 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11239 0 : CPLFree(pnTemp);
11240 0 : break;
11241 : }
11242 0 : default:
11243 0 : CPLDebug("GDAL_netCDF", "NCDFGetVar1D unsupported type %d",
11244 : nVarType);
11245 0 : CPLFree(pszVarValue);
11246 0 : pszVarValue = nullptr;
11247 0 : break;
11248 : }
11249 :
11250 77 : if (pszVarValue != nullptr && nVarLen > 1 && nVarType != NC_CHAR)
11251 42 : NCDFSafeStrcat(&pszVarValue, "}", &nVarValueSize);
11252 :
11253 : /* set return values */
11254 77 : *pszValue = pszVarValue;
11255 :
11256 77 : return CE_None;
11257 : }
11258 :
11259 8 : static CPLErr NCDFPut1DVar(int nCdfId, int nVarId, const char *pszValue)
11260 : {
11261 8 : if (EQUAL(pszValue, ""))
11262 0 : return CE_Failure;
11263 :
11264 : /* get var information */
11265 8 : int nVarDimId = -1;
11266 8 : int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
11267 8 : if (status != NC_NOERR || nVarDimId != 1)
11268 0 : return CE_Failure;
11269 :
11270 8 : status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
11271 8 : if (status != NC_NOERR)
11272 0 : return CE_Failure;
11273 :
11274 8 : nc_type nVarType = NC_CHAR;
11275 8 : status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
11276 8 : if (status != NC_NOERR)
11277 0 : return CE_Failure;
11278 :
11279 8 : size_t nVarLen = 0;
11280 8 : status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
11281 8 : if (status != NC_NOERR)
11282 0 : return CE_Failure;
11283 :
11284 8 : size_t start[1] = {0};
11285 8 : size_t count[1] = {nVarLen};
11286 :
11287 : /* get the values as tokens */
11288 8 : char **papszValues = NCDFTokenizeArray(pszValue);
11289 8 : if (papszValues == nullptr)
11290 0 : return CE_Failure;
11291 :
11292 8 : nVarLen = CSLCount(papszValues);
11293 :
11294 : /* now write the data */
11295 8 : if (nVarType == NC_CHAR)
11296 : {
11297 0 : status = nc_put_vara_text(nCdfId, nVarId, start, count, pszValue);
11298 0 : NCDF_ERR(status);
11299 : }
11300 : else
11301 : {
11302 8 : switch (nVarType)
11303 : {
11304 0 : case NC_BYTE:
11305 : {
11306 : signed char *pscTemp = static_cast<signed char *>(
11307 0 : CPLCalloc(nVarLen, sizeof(signed char)));
11308 0 : for (size_t i = 0; i < nVarLen; i++)
11309 : {
11310 0 : char *pszTemp = nullptr;
11311 0 : pscTemp[i] = static_cast<signed char>(
11312 0 : strtol(papszValues[i], &pszTemp, 10));
11313 : }
11314 : status =
11315 0 : nc_put_vara_schar(nCdfId, nVarId, start, count, pscTemp);
11316 0 : NCDF_ERR(status);
11317 0 : CPLFree(pscTemp);
11318 0 : break;
11319 : }
11320 0 : case NC_SHORT:
11321 : {
11322 : short *psTemp =
11323 0 : static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
11324 0 : for (size_t i = 0; i < nVarLen; i++)
11325 : {
11326 0 : char *pszTemp = nullptr;
11327 0 : psTemp[i] = static_cast<short>(
11328 0 : strtol(papszValues[i], &pszTemp, 10));
11329 : }
11330 : status =
11331 0 : nc_put_vara_short(nCdfId, nVarId, start, count, psTemp);
11332 0 : NCDF_ERR(status);
11333 0 : CPLFree(psTemp);
11334 0 : break;
11335 : }
11336 3 : case NC_INT:
11337 : {
11338 : int *pnTemp =
11339 3 : static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
11340 11 : for (size_t i = 0; i < nVarLen; i++)
11341 : {
11342 8 : char *pszTemp = nullptr;
11343 8 : pnTemp[i] =
11344 8 : static_cast<int>(strtol(papszValues[i], &pszTemp, 10));
11345 : }
11346 3 : status = nc_put_vara_int(nCdfId, nVarId, start, count, pnTemp);
11347 3 : NCDF_ERR(status);
11348 3 : CPLFree(pnTemp);
11349 3 : break;
11350 : }
11351 0 : case NC_FLOAT:
11352 : {
11353 : float *pfTemp =
11354 0 : static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
11355 0 : for (size_t i = 0; i < nVarLen; i++)
11356 : {
11357 0 : char *pszTemp = nullptr;
11358 0 : pfTemp[i] =
11359 0 : static_cast<float>(CPLStrtod(papszValues[i], &pszTemp));
11360 : }
11361 : status =
11362 0 : nc_put_vara_float(nCdfId, nVarId, start, count, pfTemp);
11363 0 : NCDF_ERR(status);
11364 0 : CPLFree(pfTemp);
11365 0 : break;
11366 : }
11367 5 : case NC_DOUBLE:
11368 : {
11369 : double *pdfTemp =
11370 5 : static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
11371 19 : for (size_t i = 0; i < nVarLen; i++)
11372 : {
11373 14 : char *pszTemp = nullptr;
11374 14 : pdfTemp[i] = CPLStrtod(papszValues[i], &pszTemp);
11375 : }
11376 : status =
11377 5 : nc_put_vara_double(nCdfId, nVarId, start, count, pdfTemp);
11378 5 : NCDF_ERR(status);
11379 5 : CPLFree(pdfTemp);
11380 5 : break;
11381 : }
11382 0 : default:
11383 : {
11384 0 : int nTmpFormat = 0;
11385 0 : status = nc_inq_format(nCdfId, &nTmpFormat);
11386 0 : NCDF_ERR(status);
11387 0 : if (nTmpFormat == NCDF_FORMAT_NC4)
11388 : {
11389 0 : switch (nVarType)
11390 : {
11391 0 : case NC_STRING:
11392 : {
11393 : status =
11394 0 : nc_put_vara_string(nCdfId, nVarId, start, count,
11395 : (const char **)papszValues);
11396 0 : NCDF_ERR(status);
11397 0 : break;
11398 : }
11399 0 : case NC_UBYTE:
11400 : {
11401 : unsigned char *pucTemp =
11402 : static_cast<unsigned char *>(
11403 0 : CPLCalloc(nVarLen, sizeof(unsigned char)));
11404 0 : for (size_t i = 0; i < nVarLen; i++)
11405 : {
11406 0 : char *pszTemp = nullptr;
11407 0 : pucTemp[i] = static_cast<unsigned char>(
11408 0 : strtoul(papszValues[i], &pszTemp, 10));
11409 : }
11410 0 : status = nc_put_vara_uchar(nCdfId, nVarId, start,
11411 : count, pucTemp);
11412 0 : NCDF_ERR(status);
11413 0 : CPLFree(pucTemp);
11414 0 : break;
11415 : }
11416 0 : case NC_USHORT:
11417 : {
11418 : unsigned short *pusTemp =
11419 : static_cast<unsigned short *>(
11420 0 : CPLCalloc(nVarLen, sizeof(unsigned short)));
11421 0 : for (size_t i = 0; i < nVarLen; i++)
11422 : {
11423 0 : char *pszTemp = nullptr;
11424 0 : pusTemp[i] = static_cast<unsigned short>(
11425 0 : strtoul(papszValues[i], &pszTemp, 10));
11426 : }
11427 0 : status = nc_put_vara_ushort(nCdfId, nVarId, start,
11428 : count, pusTemp);
11429 0 : NCDF_ERR(status);
11430 0 : CPLFree(pusTemp);
11431 0 : break;
11432 : }
11433 0 : case NC_UINT:
11434 : {
11435 : unsigned int *punTemp = static_cast<unsigned int *>(
11436 0 : CPLCalloc(nVarLen, sizeof(unsigned int)));
11437 0 : for (size_t i = 0; i < nVarLen; i++)
11438 : {
11439 0 : char *pszTemp = nullptr;
11440 0 : punTemp[i] = static_cast<unsigned int>(
11441 0 : strtoul(papszValues[i], &pszTemp, 10));
11442 : }
11443 0 : status = nc_put_vara_uint(nCdfId, nVarId, start,
11444 : count, punTemp);
11445 0 : NCDF_ERR(status);
11446 0 : CPLFree(punTemp);
11447 0 : break;
11448 : }
11449 0 : default:
11450 0 : if (papszValues)
11451 0 : CSLDestroy(papszValues);
11452 0 : return CE_Failure;
11453 : }
11454 : }
11455 0 : break;
11456 : }
11457 : }
11458 : }
11459 :
11460 8 : if (papszValues)
11461 8 : CSLDestroy(papszValues);
11462 :
11463 8 : return CE_None;
11464 : }
11465 :
11466 : /************************************************************************/
11467 : /* GetDefaultNoDataValue() */
11468 : /************************************************************************/
11469 :
11470 196 : double NCDFGetDefaultNoDataValue(int nCdfId, int nVarId, int nVarType,
11471 : bool &bGotNoData)
11472 :
11473 : {
11474 196 : int nNoFill = 0;
11475 196 : double dfNoData = 0.0;
11476 :
11477 196 : switch (nVarType)
11478 : {
11479 0 : case NC_CHAR:
11480 : case NC_BYTE:
11481 : case NC_UBYTE:
11482 : // Don't do default fill-values for bytes, too risky.
11483 : // This function should not be called in those cases.
11484 0 : CPLAssert(false);
11485 : break;
11486 24 : case NC_SHORT:
11487 : {
11488 24 : short nFillVal = 0;
11489 24 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11490 : NC_NOERR)
11491 : {
11492 24 : if (!nNoFill)
11493 : {
11494 23 : bGotNoData = true;
11495 23 : dfNoData = nFillVal;
11496 : }
11497 : }
11498 : else
11499 0 : dfNoData = NC_FILL_SHORT;
11500 24 : break;
11501 : }
11502 26 : case NC_INT:
11503 : {
11504 26 : int nFillVal = 0;
11505 26 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11506 : NC_NOERR)
11507 : {
11508 26 : if (!nNoFill)
11509 : {
11510 25 : bGotNoData = true;
11511 25 : dfNoData = nFillVal;
11512 : }
11513 : }
11514 : else
11515 0 : dfNoData = NC_FILL_INT;
11516 26 : break;
11517 : }
11518 79 : case NC_FLOAT:
11519 : {
11520 79 : float fFillVal = 0;
11521 79 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &fFillVal) ==
11522 : NC_NOERR)
11523 : {
11524 79 : if (!nNoFill)
11525 : {
11526 75 : bGotNoData = true;
11527 75 : dfNoData = fFillVal;
11528 : }
11529 : }
11530 : else
11531 0 : dfNoData = NC_FILL_FLOAT;
11532 79 : break;
11533 : }
11534 34 : case NC_DOUBLE:
11535 : {
11536 34 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &dfNoData) ==
11537 : NC_NOERR)
11538 : {
11539 34 : if (!nNoFill)
11540 : {
11541 34 : bGotNoData = true;
11542 : }
11543 : }
11544 : else
11545 0 : dfNoData = NC_FILL_DOUBLE;
11546 34 : break;
11547 : }
11548 7 : case NC_USHORT:
11549 : {
11550 7 : unsigned short nFillVal = 0;
11551 7 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11552 : NC_NOERR)
11553 : {
11554 7 : if (!nNoFill)
11555 : {
11556 7 : bGotNoData = true;
11557 7 : dfNoData = nFillVal;
11558 : }
11559 : }
11560 : else
11561 0 : dfNoData = NC_FILL_USHORT;
11562 7 : break;
11563 : }
11564 7 : case NC_UINT:
11565 : {
11566 7 : unsigned int nFillVal = 0;
11567 7 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11568 : NC_NOERR)
11569 : {
11570 7 : if (!nNoFill)
11571 : {
11572 7 : bGotNoData = true;
11573 7 : dfNoData = nFillVal;
11574 : }
11575 : }
11576 : else
11577 0 : dfNoData = NC_FILL_UINT;
11578 7 : break;
11579 : }
11580 19 : default:
11581 19 : dfNoData = 0.0;
11582 19 : break;
11583 : }
11584 :
11585 196 : return dfNoData;
11586 : }
11587 :
11588 : /************************************************************************/
11589 : /* NCDFGetDefaultNoDataValueAsInt64() */
11590 : /************************************************************************/
11591 :
11592 2 : int64_t NCDFGetDefaultNoDataValueAsInt64(int nCdfId, int nVarId,
11593 : bool &bGotNoData)
11594 :
11595 : {
11596 2 : int nNoFill = 0;
11597 2 : long long nFillVal = 0;
11598 2 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
11599 : {
11600 2 : if (!nNoFill)
11601 : {
11602 2 : bGotNoData = true;
11603 2 : return static_cast<int64_t>(nFillVal);
11604 : }
11605 : }
11606 : else
11607 0 : return static_cast<int64_t>(NC_FILL_INT64);
11608 0 : return 0;
11609 : }
11610 :
11611 : /************************************************************************/
11612 : /* NCDFGetDefaultNoDataValueAsUInt64() */
11613 : /************************************************************************/
11614 :
11615 1 : uint64_t NCDFGetDefaultNoDataValueAsUInt64(int nCdfId, int nVarId,
11616 : bool &bGotNoData)
11617 :
11618 : {
11619 1 : int nNoFill = 0;
11620 1 : unsigned long long nFillVal = 0;
11621 1 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
11622 : {
11623 1 : if (!nNoFill)
11624 : {
11625 1 : bGotNoData = true;
11626 1 : return static_cast<uint64_t>(nFillVal);
11627 : }
11628 : }
11629 : else
11630 0 : return static_cast<uint64_t>(NC_FILL_UINT64);
11631 0 : return 0;
11632 : }
11633 :
11634 11772 : static int NCDFDoesVarContainAttribVal(int nCdfId,
11635 : const char *const *papszAttribNames,
11636 : const char *const *papszAttribValues,
11637 : int nVarId, const char *pszVarName,
11638 : bool bStrict = true)
11639 : {
11640 11772 : if (nVarId == -1 && pszVarName != nullptr)
11641 8168 : NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
11642 :
11643 11772 : if (nVarId == -1)
11644 878 : return -1;
11645 :
11646 10894 : bool bFound = false;
11647 50751 : for (int i = 0; !bFound && papszAttribNames != nullptr &&
11648 48404 : papszAttribNames[i] != nullptr;
11649 : i++)
11650 : {
11651 39857 : char *pszTemp = nullptr;
11652 39857 : if (NCDFGetAttr(nCdfId, nVarId, papszAttribNames[i], &pszTemp) ==
11653 56729 : CE_None &&
11654 16872 : pszTemp != nullptr)
11655 : {
11656 16872 : if (bStrict)
11657 : {
11658 16872 : if (EQUAL(pszTemp, papszAttribValues[i]))
11659 2347 : bFound = true;
11660 : }
11661 : else
11662 : {
11663 0 : if (EQUALN(pszTemp, papszAttribValues[i],
11664 : strlen(papszAttribValues[i])))
11665 0 : bFound = true;
11666 : }
11667 16872 : CPLFree(pszTemp);
11668 : }
11669 : }
11670 10894 : return bFound;
11671 : }
11672 :
11673 2133 : static int NCDFDoesVarContainAttribVal2(int nCdfId, const char *papszAttribName,
11674 : const char *const *papszAttribValues,
11675 : int nVarId, const char *pszVarName,
11676 : int bStrict = true)
11677 : {
11678 2133 : if (nVarId == -1 && pszVarName != nullptr)
11679 1579 : NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
11680 :
11681 2133 : if (nVarId == -1)
11682 0 : return -1;
11683 :
11684 2133 : bool bFound = false;
11685 2133 : char *pszTemp = nullptr;
11686 2512 : if (NCDFGetAttr(nCdfId, nVarId, papszAttribName, &pszTemp) != CE_None ||
11687 379 : pszTemp == nullptr)
11688 1754 : return FALSE;
11689 :
11690 7703 : for (int i = 0; !bFound && i < CSLCount(papszAttribValues); i++)
11691 : {
11692 7324 : if (bStrict)
11693 : {
11694 7296 : if (EQUAL(pszTemp, papszAttribValues[i]))
11695 31 : bFound = true;
11696 : }
11697 : else
11698 : {
11699 28 : if (EQUALN(pszTemp, papszAttribValues[i],
11700 : strlen(papszAttribValues[i])))
11701 0 : bFound = true;
11702 : }
11703 : }
11704 :
11705 379 : CPLFree(pszTemp);
11706 :
11707 379 : return bFound;
11708 : }
11709 :
11710 876 : static bool NCDFEqual(const char *papszName, const char *const *papszValues)
11711 : {
11712 876 : if (papszName == nullptr || EQUAL(papszName, ""))
11713 0 : return false;
11714 :
11715 2392 : for (int i = 0; papszValues && papszValues[i]; ++i)
11716 : {
11717 1636 : if (EQUAL(papszName, papszValues[i]))
11718 120 : return true;
11719 : }
11720 :
11721 756 : return false;
11722 : }
11723 :
11724 : // Test that a variable is longitude/latitude coordinate,
11725 : // following CF 4.1 and 4.2.
11726 3942 : bool NCDFIsVarLongitude(int nCdfId, int nVarId, const char *pszVarName)
11727 : {
11728 : // Check for matching attributes.
11729 3942 : int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLongitudeAttribNames,
11730 : papszCFLongitudeAttribValues, nVarId,
11731 : pszVarName);
11732 : // If not found using attributes then check using var name
11733 : // unless GDAL_NETCDF_VERIFY_DIMS=STRICT.
11734 3942 : if (bVal == -1)
11735 : {
11736 280 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11737 : "STRICT"))
11738 280 : bVal = NCDFEqual(pszVarName, papszCFLongitudeVarNames);
11739 : else
11740 0 : bVal = FALSE;
11741 : }
11742 3662 : else if (bVal)
11743 : {
11744 : // Check that the units is not 'm' or '1'. See #6759
11745 795 : char *pszTemp = nullptr;
11746 1175 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11747 380 : pszTemp != nullptr)
11748 : {
11749 380 : if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
11750 97 : bVal = false;
11751 380 : CPLFree(pszTemp);
11752 : }
11753 : }
11754 :
11755 3942 : return CPL_TO_BOOL(bVal);
11756 : }
11757 :
11758 2287 : bool NCDFIsVarLatitude(int nCdfId, int nVarId, const char *pszVarName)
11759 : {
11760 2287 : int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLatitudeAttribNames,
11761 : papszCFLatitudeAttribValues, nVarId,
11762 : pszVarName);
11763 2287 : if (bVal == -1)
11764 : {
11765 163 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11766 : "STRICT"))
11767 163 : bVal = NCDFEqual(pszVarName, papszCFLatitudeVarNames);
11768 : else
11769 0 : bVal = FALSE;
11770 : }
11771 2124 : else if (bVal)
11772 : {
11773 : // Check that the units is not 'm' or '1'. See #6759
11774 548 : char *pszTemp = nullptr;
11775 690 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11776 142 : pszTemp != nullptr)
11777 : {
11778 142 : if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
11779 36 : bVal = false;
11780 142 : CPLFree(pszTemp);
11781 : }
11782 : }
11783 :
11784 2287 : return CPL_TO_BOOL(bVal);
11785 : }
11786 :
11787 2428 : bool NCDFIsVarProjectionX(int nCdfId, int nVarId, const char *pszVarName)
11788 : {
11789 2428 : int bVal = NCDFDoesVarContainAttribVal(
11790 : nCdfId, papszCFProjectionXAttribNames, papszCFProjectionXAttribValues,
11791 : nVarId, pszVarName);
11792 2428 : if (bVal == -1)
11793 : {
11794 274 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11795 : "STRICT"))
11796 274 : bVal = NCDFEqual(pszVarName, papszCFProjectionXVarNames);
11797 : else
11798 0 : bVal = FALSE;
11799 : }
11800 2154 : else if (bVal)
11801 : {
11802 : // Check that the units is not '1'
11803 396 : char *pszTemp = nullptr;
11804 570 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11805 174 : pszTemp != nullptr)
11806 : {
11807 174 : if (EQUAL(pszTemp, "1"))
11808 5 : bVal = false;
11809 174 : CPLFree(pszTemp);
11810 : }
11811 : }
11812 :
11813 2428 : return CPL_TO_BOOL(bVal);
11814 : }
11815 :
11816 1738 : bool NCDFIsVarProjectionY(int nCdfId, int nVarId, const char *pszVarName)
11817 : {
11818 1738 : int bVal = NCDFDoesVarContainAttribVal(
11819 : nCdfId, papszCFProjectionYAttribNames, papszCFProjectionYAttribValues,
11820 : nVarId, pszVarName);
11821 1738 : if (bVal == -1)
11822 : {
11823 159 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11824 : "STRICT"))
11825 159 : bVal = NCDFEqual(pszVarName, papszCFProjectionYVarNames);
11826 : else
11827 0 : bVal = FALSE;
11828 : }
11829 1579 : else if (bVal)
11830 : {
11831 : // Check that the units is not '1'
11832 390 : char *pszTemp = nullptr;
11833 559 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11834 169 : pszTemp != nullptr)
11835 : {
11836 169 : if (EQUAL(pszTemp, "1"))
11837 5 : bVal = false;
11838 169 : CPLFree(pszTemp);
11839 : }
11840 : }
11841 :
11842 1738 : return CPL_TO_BOOL(bVal);
11843 : }
11844 :
11845 : /* test that a variable is a vertical coordinate, following CF 4.3 */
11846 1118 : bool NCDFIsVarVerticalCoord(int nCdfId, int nVarId, const char *pszVarName)
11847 : {
11848 : /* check for matching attributes */
11849 1118 : if (NCDFDoesVarContainAttribVal(nCdfId, papszCFVerticalAttribNames,
11850 : papszCFVerticalAttribValues, nVarId,
11851 1118 : pszVarName))
11852 111 : return true;
11853 : /* check for matching units */
11854 1007 : else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
11855 : papszCFVerticalUnitsValues, nVarId,
11856 1007 : pszVarName))
11857 31 : return true;
11858 : /* check for matching standard name */
11859 976 : else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_STD_NAME,
11860 : papszCFVerticalStandardNameValues,
11861 976 : nVarId, pszVarName))
11862 0 : return true;
11863 : else
11864 976 : return false;
11865 : }
11866 :
11867 : /* test that a variable is a time coordinate, following CF 4.4 */
11868 259 : bool NCDFIsVarTimeCoord(int nCdfId, int nVarId, const char *pszVarName)
11869 : {
11870 : /* check for matching attributes */
11871 259 : if (NCDFDoesVarContainAttribVal(nCdfId, papszCFTimeAttribNames,
11872 : papszCFTimeAttribValues, nVarId,
11873 259 : pszVarName))
11874 109 : return true;
11875 : /* check for matching units */
11876 150 : else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
11877 : papszCFTimeUnitsValues, nVarId,
11878 150 : pszVarName, false))
11879 0 : return true;
11880 : else
11881 150 : return false;
11882 : }
11883 :
11884 : // Parse a string, and return as a string list.
11885 : // If it an array of the form {a,b}, then tokenize it.
11886 : // Otherwise, return a copy.
11887 188 : static char **NCDFTokenizeArray(const char *pszValue)
11888 : {
11889 188 : if (pszValue == nullptr || EQUAL(pszValue, ""))
11890 53 : return nullptr;
11891 :
11892 135 : char **papszValues = nullptr;
11893 135 : const int nLen = static_cast<int>(strlen(pszValue));
11894 :
11895 135 : if (pszValue[0] == '{' && nLen > 2 && pszValue[nLen - 1] == '}')
11896 : {
11897 41 : char *pszTemp = static_cast<char *>(CPLMalloc((nLen - 2) + 1));
11898 41 : strncpy(pszTemp, pszValue + 1, nLen - 2);
11899 41 : pszTemp[nLen - 2] = '\0';
11900 41 : papszValues = CSLTokenizeString2(pszTemp, ",", CSLT_ALLOWEMPTYTOKENS);
11901 41 : CPLFree(pszTemp);
11902 : }
11903 : else
11904 : {
11905 94 : papszValues = static_cast<char **>(CPLCalloc(2, sizeof(char *)));
11906 94 : papszValues[0] = CPLStrdup(pszValue);
11907 94 : papszValues[1] = nullptr;
11908 : }
11909 :
11910 135 : return papszValues;
11911 : }
11912 :
11913 : // Open a NetCDF subdataset from full path /group1/group2/.../groupn/var.
11914 : // Leading slash is optional.
11915 416 : static CPLErr NCDFOpenSubDataset(int nCdfId, const char *pszSubdatasetName,
11916 : int *pnGroupId, int *pnVarId)
11917 : {
11918 416 : *pnGroupId = -1;
11919 416 : *pnVarId = -1;
11920 :
11921 : // Open group.
11922 : char *pszGroupFullName =
11923 416 : CPLStrdup(CPLGetPathSafe(pszSubdatasetName).c_str());
11924 : // Add a leading slash if needed.
11925 416 : if (pszGroupFullName[0] != '/')
11926 : {
11927 399 : char *old = pszGroupFullName;
11928 399 : pszGroupFullName = CPLStrdup(CPLSPrintf("/%s", pszGroupFullName));
11929 399 : CPLFree(old);
11930 : }
11931 : // Detect root group.
11932 416 : if (EQUAL(pszGroupFullName, "/"))
11933 : {
11934 399 : *pnGroupId = nCdfId;
11935 399 : 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 416 : const char *pszVarName = CPLGetFilename(pszSubdatasetName);
11946 416 : NCDF_ERR_RET(nc_inq_varid(*pnGroupId, pszVarName, pnVarId));
11947 :
11948 416 : return CE_None;
11949 : }
11950 :
11951 : // Get all dimensions visible from a given NetCDF (or group) ID and any of
11952 : // its parents.
11953 356 : static CPLErr NCDFGetVisibleDims(int nGroupId, int *pnDims, int **ppanDimIds)
11954 : {
11955 356 : int nDims = 0;
11956 356 : int *panDimIds = nullptr;
11957 356 : NCDF_ERR_RET(nc_inq_dimids(nGroupId, &nDims, nullptr, true));
11958 :
11959 356 : panDimIds = static_cast<int *>(CPLMalloc(nDims * sizeof(int)));
11960 :
11961 356 : int status = nc_inq_dimids(nGroupId, nullptr, panDimIds, true);
11962 356 : if (status != NC_NOERR)
11963 0 : CPLFree(panDimIds);
11964 356 : NCDF_ERR_RET(status);
11965 :
11966 356 : *pnDims = nDims;
11967 356 : *ppanDimIds = panDimIds;
11968 :
11969 356 : 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 3114 : static CPLErr NCDFGetSubGroups(int nGroupId, int *pnSubGroups,
11975 : int **ppanSubGroupIds)
11976 : {
11977 3114 : *pnSubGroups = 0;
11978 3114 : *ppanSubGroupIds = nullptr;
11979 :
11980 : int nSubGroups;
11981 3114 : NCDF_ERR_RET(nc_inq_grps(nGroupId, &nSubGroups, nullptr));
11982 : int *panSubGroupIds =
11983 3114 : static_cast<int *>(CPLMalloc(nSubGroups * sizeof(int)));
11984 3114 : NCDF_ERR_RET(nc_inq_grps(nGroupId, nullptr, panSubGroupIds));
11985 3114 : *pnSubGroups = nSubGroups;
11986 3114 : *ppanSubGroupIds = panSubGroupIds;
11987 :
11988 3114 : 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 17243 : static CPLErr NCDFGetGroupFullName(int nGroupId, char **ppszFullName,
11996 : bool bNC3Compat)
11997 : {
11998 17243 : *ppszFullName = nullptr;
11999 :
12000 : size_t nFullNameLen;
12001 17243 : NCDF_ERR_RET(nc_inq_grpname_len(nGroupId, &nFullNameLen));
12002 17243 : *ppszFullName =
12003 17243 : static_cast<char *>(CPLMalloc((nFullNameLen + 1) * sizeof(char)));
12004 17243 : int status = nc_inq_grpname_full(nGroupId, &nFullNameLen, *ppszFullName);
12005 17243 : if (status != NC_NOERR)
12006 : {
12007 0 : CPLFree(*ppszFullName);
12008 0 : *ppszFullName = nullptr;
12009 0 : NCDF_ERR_RET(status);
12010 : }
12011 :
12012 17243 : if (bNC3Compat && EQUAL(*ppszFullName, "/"))
12013 8069 : (*ppszFullName)[0] = '\0';
12014 :
12015 17243 : return CE_None;
12016 : }
12017 :
12018 8966 : CPLString NCDFGetGroupFullName(int nGroupId)
12019 : {
12020 8966 : char *pszFullname = nullptr;
12021 8966 : NCDFGetGroupFullName(nGroupId, &pszFullname, false);
12022 8966 : CPLString osRet(pszFullname ? pszFullname : "");
12023 8966 : CPLFree(pszFullname);
12024 17932 : 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 8228 : static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId, char **ppszFullName,
12033 : bool bNC3Compat)
12034 : {
12035 8228 : *ppszFullName = nullptr;
12036 8228 : char *pszGroupFullName = nullptr;
12037 8228 : ERR_RET(NCDFGetGroupFullName(nGroupId, &pszGroupFullName, bNC3Compat));
12038 : char szVarName[NC_MAX_NAME + 1];
12039 8228 : if (nVarId == NC_GLOBAL)
12040 : {
12041 1106 : strcpy(szVarName, "NC_GLOBAL");
12042 : }
12043 : else
12044 : {
12045 7122 : int status = nc_inq_varname(nGroupId, nVarId, szVarName);
12046 7122 : if (status != NC_NOERR)
12047 : {
12048 0 : CPLFree(pszGroupFullName);
12049 0 : NCDF_ERR_RET(status);
12050 : }
12051 : }
12052 8228 : const char *pszSep = "/";
12053 8228 : if (EQUAL(pszGroupFullName, "/") || EQUAL(pszGroupFullName, ""))
12054 8022 : pszSep = "";
12055 8228 : *ppszFullName =
12056 8228 : CPLStrdup(CPLSPrintf("%s%s%s", pszGroupFullName, pszSep, szVarName));
12057 8228 : CPLFree(pszGroupFullName);
12058 8228 : 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 13280 : static CPLErr NCDFResolveElem(int nStartGroupId, const char *pszVar,
12082 : const char *pszAtt, int *pnGroupId, int *pnId,
12083 : bool bMandatory)
12084 : {
12085 13280 : 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 13280 : } eNCResolveMode = NCRM_PARENT;
12097 :
12098 26560 : std::queue<int> aoQueueGroupIdsToVisit;
12099 13280 : aoQueueGroupIdsToVisit.push(nStartGroupId);
12100 :
12101 15006 : while (!aoQueueGroupIdsToVisit.empty())
12102 : {
12103 : // Get the first group of the FIFO queue.
12104 13474 : *pnGroupId = aoQueueGroupIdsToVisit.front();
12105 13474 : aoQueueGroupIdsToVisit.pop();
12106 :
12107 : // Look if this group contains the searched element.
12108 : int status;
12109 13474 : if (pszVar)
12110 13253 : status = nc_inq_varid(*pnGroupId, pszVar, pnId);
12111 : else // pszAtt != nullptr.
12112 221 : status = nc_inq_attid(*pnGroupId, NC_GLOBAL, pszAtt, pnId);
12113 :
12114 13474 : if (status == NC_NOERR)
12115 : {
12116 11748 : return CE_None;
12117 : }
12118 1726 : else if ((pszVar && status != NC_ENOTVAR) ||
12119 218 : (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 1726 : if (eNCResolveMode == NCRM_PARENT)
12128 : {
12129 1607 : int nParentGroupId = -1;
12130 1607 : int status2 = nc_inq_grp_parent(*pnGroupId, &nParentGroupId);
12131 1607 : if (status2 == NC_NOERR)
12132 62 : aoQueueGroupIdsToVisit.push(nParentGroupId);
12133 1545 : else if (status2 != NC_ENOGRP)
12134 0 : NCDF_ERR(status2);
12135 1545 : 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 1330 : eNCResolveMode = NCRM_WIDTH_WISE;
12140 : }
12141 :
12142 : // The second resolve mode is a width-wise search.
12143 1726 : if (eNCResolveMode == NCRM_WIDTH_WISE)
12144 : {
12145 : // Enqueue all direct sub-groups.
12146 1449 : int nSubGroups = 0;
12147 1449 : int *panSubGroupIds = nullptr;
12148 1449 : NCDFGetSubGroups(*pnGroupId, &nSubGroups, &panSubGroupIds);
12149 1581 : for (int i = 0; i < nSubGroups; i++)
12150 132 : aoQueueGroupIdsToVisit.push(panSubGroupIds[i]);
12151 1449 : CPLFree(panSubGroupIds);
12152 : }
12153 : }
12154 :
12155 1532 : 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 1532 : *pnGroupId = -1;
12168 1532 : *pnId = -1;
12169 1532 : 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 13062 : CPLErr NCDFResolveVar(int nStartGroupId, const char *pszVar, int *pnGroupId,
12192 : int *pnVarId, bool bMandatory)
12193 : {
12194 13062 : *pnGroupId = -1;
12195 13062 : *pnVarId = -1;
12196 13062 : int nGroupId = nStartGroupId, nVarId;
12197 13062 : 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 13062 : ERR_RET(NCDFResolveElem(nStartGroupId, pszVar, nullptr, &nGroupId,
12208 : &nVarId, bMandatory));
12209 : }
12210 11745 : *pnGroupId = nGroupId;
12211 11745 : *pnVarId = nVarId;
12212 11745 : return CE_None;
12213 : }
12214 :
12215 : // Like NCDFResolveVar but returns directly the var full name.
12216 1384 : static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,
12217 : char **ppszFullName, bool bMandatory)
12218 : {
12219 1384 : *ppszFullName = nullptr;
12220 : int nGroupId, nVarId;
12221 1384 : ERR_RET(
12222 : NCDFResolveVar(nStartGroupId, pszVar, &nGroupId, &nVarId, bMandatory));
12223 1358 : 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 218 : static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,
12230 : const char *pszAtt, int *pnAtt, bool bMandatory)
12231 : {
12232 218 : int nGroupId = nStartGroupId, nAttId = nStartVarId;
12233 218 : 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 532 : 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 532 : int nVars = 0;
12250 532 : int nRasterVars = 0;
12251 532 : NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
12252 :
12253 1064 : std::vector<int> anPotentialVectorVarID;
12254 : // oMapDimIdToCount[x] = number of times dim x is the first dimension of
12255 : // potential vector variables
12256 1064 : std::map<int, int> oMapDimIdToCount;
12257 532 : int nVarXId = -1;
12258 532 : int nVarYId = -1;
12259 532 : int nVarZId = -1;
12260 532 : int nVarTimeId = -1;
12261 532 : int nVarTimeDimId = -1;
12262 532 : bool bIsVectorOnly = true;
12263 532 : int nProfileDimId = -1;
12264 532 : int nParentIndexVarID = -1;
12265 :
12266 3239 : for (int v = 0; v < nVars; v++)
12267 : {
12268 : int nVarDims;
12269 2707 : NCDF_ERR_RET(nc_inq_varndims(nCdfId, v, &nVarDims));
12270 : // Should we ignore this variable?
12271 : char szTemp[NC_MAX_NAME + 1];
12272 2707 : szTemp[0] = '\0';
12273 2707 : NCDF_ERR_RET(nc_inq_varname(nCdfId, v, szTemp));
12274 :
12275 2707 : if (strstr(szTemp, "_node_coordinates") ||
12276 2707 : strstr(szTemp, "_node_count"))
12277 : {
12278 : // Ignore CF-1.8 Simple Geometries helper variables
12279 69 : continue;
12280 : }
12281 :
12282 3939 : if (nVarDims == 1 && (NCDFIsVarLongitude(nCdfId, -1, szTemp) ||
12283 1301 : NCDFIsVarProjectionX(nCdfId, -1, szTemp)))
12284 : {
12285 361 : nVarXId = v;
12286 : }
12287 3217 : else if (nVarDims == 1 && (NCDFIsVarLatitude(nCdfId, -1, szTemp) ||
12288 940 : NCDFIsVarProjectionY(nCdfId, -1, szTemp)))
12289 : {
12290 360 : nVarYId = v;
12291 : }
12292 1917 : else if (nVarDims == 1 && NCDFIsVarVerticalCoord(nCdfId, -1, szTemp))
12293 : {
12294 78 : nVarZId = v;
12295 : }
12296 : else
12297 : {
12298 1839 : char *pszVarFullName = nullptr;
12299 1839 : CPLErr eErr = NCDFGetVarFullName(nCdfId, v, &pszVarFullName);
12300 1839 : if (eErr != CE_None)
12301 : {
12302 0 : CPLFree(pszVarFullName);
12303 0 : continue;
12304 : }
12305 : bool bIgnoreVar =
12306 1839 : (CSLFindString(papszIgnoreVars, pszVarFullName) != -1);
12307 1839 : CPLFree(pszVarFullName);
12308 1839 : if (bIgnoreVar)
12309 : {
12310 104 : if (nVarDims == 1 && NCDFIsVarTimeCoord(nCdfId, -1, szTemp))
12311 : {
12312 11 : nVarTimeId = v;
12313 11 : nc_inq_vardimid(nCdfId, v, &nVarTimeDimId);
12314 : }
12315 93 : else if (nVarDims > 1)
12316 : {
12317 89 : (*pnIgnoredVars)++;
12318 89 : CPLDebug("GDAL_netCDF", "variable #%d [%s] was ignored", v,
12319 : szTemp);
12320 : }
12321 : }
12322 : // Only accept 2+D vars.
12323 1735 : else if (nVarDims >= 2)
12324 : {
12325 708 : bool bRasterCandidate = true;
12326 : // Identify variables that might be vector variables
12327 708 : if (nVarDims == 2)
12328 : {
12329 632 : int anDimIds[2] = {-1, -1};
12330 632 : nc_inq_vardimid(nCdfId, v, anDimIds);
12331 :
12332 632 : nc_type vartype = NC_NAT;
12333 632 : nc_inq_vartype(nCdfId, v, &vartype);
12334 :
12335 : char szDimNameFirst[NC_MAX_NAME + 1];
12336 : char szDimNameSecond[NC_MAX_NAME + 1];
12337 632 : szDimNameFirst[0] = '\0';
12338 632 : szDimNameSecond[0] = '\0';
12339 1421 : 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 946 : !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 475 : std::array<int, 3> oKey{anDimIds[0], anDimIds[1],
12366 475 : vartype};
12367 475 : oMap2DDimsToGroupAndVar[oKey].emplace_back(
12368 475 : std::pair(nCdfId, v));
12369 475 : bIsVectorOnly = false;
12370 : }
12371 : }
12372 : else
12373 : {
12374 76 : bIsVectorOnly = false;
12375 : }
12376 708 : if (bKeepRasters && bRasterCandidate)
12377 : {
12378 552 : *pnGroupId = nCdfId;
12379 552 : *pnVarId = v;
12380 552 : nRasterVars++;
12381 : }
12382 : }
12383 1027 : 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 532 : CSLFetchNameValueDef(papszMetadata, "NC_GLOBAL#featureType", ""));
12428 421 : if (bKeepRasters && !bKeepVectors && bIsVectorOnly && nRasterVars > 0 &&
12429 953 : !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 532 : *pnRasterVars += nRasterVars;
12439 : }
12440 :
12441 532 : 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 532 : int nSubGroups = 0;
12469 532 : int *panSubGroupIds = nullptr;
12470 532 : NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
12471 567 : 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 532 : CPLFree(panSubGroupIds);
12478 :
12479 532 : 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 34 : char **papszTokens = NCDFTokenizeCoordinatesAttribute(pszCFCoordinates);
12555 34 : for (int i = 0; papszTokens != nullptr && papszTokens[i] != nullptr;
12556 : i++)
12557 : {
12558 0 : if (NCDFIsVarLongitude(nCdfId, -1, papszTokens[i]) ||
12559 0 : NCDFIsVarProjectionX(nCdfId, -1, papszTokens[i]))
12560 : {
12561 0 : nVarXId = -1;
12562 0 : CPL_IGNORE_RET_VAL(
12563 0 : nc_inq_varid(nCdfId, papszTokens[i], &nVarXId));
12564 : }
12565 0 : else if (NCDFIsVarLatitude(nCdfId, -1, papszTokens[i]) ||
12566 0 : NCDFIsVarProjectionY(nCdfId, -1, papszTokens[i]))
12567 : {
12568 0 : nVarYId = -1;
12569 0 : CPL_IGNORE_RET_VAL(
12570 0 : nc_inq_varid(nCdfId, papszTokens[i], &nVarYId));
12571 : }
12572 0 : else if (NCDFIsVarVerticalCoord(nCdfId, -1, papszTokens[i]))
12573 : {
12574 0 : nVarZId = -1;
12575 0 : CPL_IGNORE_RET_VAL(
12576 0 : nc_inq_varid(nCdfId, papszTokens[i], &nVarZId));
12577 : }
12578 : }
12579 34 : CSLDestroy(papszTokens);
12580 : }
12581 49 : CPLFree(pszCoordinates);
12582 :
12583 : // Check that the X,Y,Z vars share 1D and share the same dimension as
12584 : // attribute variables.
12585 49 : if (nVarXId >= 0 && nVarYId >= 0)
12586 : {
12587 38 : int nVarDimCount = -1;
12588 38 : int nVarDimId = -1;
12589 38 : if (nc_inq_varndims(nCdfId, nVarXId, &nVarDimCount) != NC_NOERR ||
12590 38 : nVarDimCount != 1 ||
12591 38 : nc_inq_vardimid(nCdfId, nVarXId, &nVarDimId) != NC_NOERR ||
12592 38 : nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim) ||
12593 35 : nc_inq_varndims(nCdfId, nVarYId, &nVarDimCount) != NC_NOERR ||
12594 35 : nVarDimCount != 1 ||
12595 111 : nc_inq_vardimid(nCdfId, nVarYId, &nVarDimId) != NC_NOERR ||
12596 35 : nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim))
12597 : {
12598 3 : nVarXId = nVarYId = -1;
12599 : }
12600 69 : else if (nVarZId >= 0 &&
12601 34 : (nc_inq_varndims(nCdfId, nVarZId, &nVarDimCount) != NC_NOERR ||
12602 34 : nVarDimCount != 1 ||
12603 34 : nc_inq_vardimid(nCdfId, nVarZId, &nVarDimId) != NC_NOERR ||
12604 34 : nVarDimId != nVectorDim))
12605 : {
12606 0 : nVarZId = -1;
12607 : }
12608 : }
12609 :
12610 49 : if (eGType == wkbUnknown && nVarXId >= 0 && nVarYId >= 0)
12611 : {
12612 2 : eGType = wkbPoint;
12613 : }
12614 49 : if (eGType == wkbPoint && nVarXId >= 0 && nVarYId >= 0 && nVarZId >= 0)
12615 : {
12616 34 : eGType = wkbPoint25D;
12617 : }
12618 49 : if (eGType == wkbUnknown && osGeometryField.empty())
12619 : {
12620 5 : eGType = wkbNone;
12621 : }
12622 :
12623 : // Read projection info
12624 49 : char **papszMetadataBackup = CSLDuplicate(papszMetadata);
12625 49 : ReadAttributes(nCdfId, nFirstVarId);
12626 49 : if (!this->bSGSupport)
12627 49 : SetProjectionFromVar(nCdfId, nFirstVarId, true);
12628 49 : const char *pszValue = FetchAttr(nCdfId, nFirstVarId, CF_GRD_MAPPING);
12629 49 : char *pszGridMapping = (pszValue ? CPLStrdup(pszValue) : nullptr);
12630 49 : CSLDestroy(papszMetadata);
12631 49 : papszMetadata = papszMetadataBackup;
12632 :
12633 49 : OGRSpatialReference *poSRS = nullptr;
12634 49 : if (!m_oSRS.IsEmpty())
12635 : {
12636 21 : poSRS = m_oSRS.Clone();
12637 : }
12638 : // Reset if there's a 2D raster
12639 49 : m_bHasProjection = false;
12640 49 : m_bHasGeoTransform = false;
12641 :
12642 49 : if (!bKeepRasters)
12643 : {
12644 : // Strip out uninteresting metadata.
12645 45 : papszMetadata =
12646 45 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#Conventions", nullptr);
12647 45 : papszMetadata =
12648 45 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#GDAL", nullptr);
12649 45 : papszMetadata =
12650 45 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#history", nullptr);
12651 : }
12652 :
12653 : std::shared_ptr<netCDFLayer> poLayer(
12654 49 : new netCDFLayer(this, nCdfId, osLayerName, eGType, poSRS));
12655 49 : if (poSRS != nullptr)
12656 21 : poSRS->Release();
12657 49 : poLayer->SetRecordDimID(nVectorDim);
12658 49 : if (wkbFlatten(eGType) == wkbPoint && nVarXId >= 0 && nVarYId >= 0)
12659 : {
12660 35 : poLayer->SetXYZVars(nVarXId, nVarYId, nVarZId);
12661 : }
12662 14 : else if (!osGeometryField.empty())
12663 : {
12664 9 : poLayer->SetWKTGeometryField(osGeometryField);
12665 : }
12666 49 : if (pszGridMapping != nullptr)
12667 : {
12668 21 : poLayer->SetGridMapping(pszGridMapping);
12669 21 : CPLFree(pszGridMapping);
12670 : }
12671 49 : poLayer->SetProfile(nProfileDimId, nParentIndexVarID);
12672 :
12673 574 : for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)
12674 : {
12675 525 : int anDimIds[2] = {-1, -1};
12676 525 : nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);
12677 525 : if (anDimIds[0] == nVectorDim ||
12678 24 : (nProfileDimId >= 0 && anDimIds[0] == nProfileDimId))
12679 : {
12680 : #ifdef NCDF_DEBUG
12681 : char szTemp2[NC_MAX_NAME + 1] = {};
12682 : CPL_IGNORE_RET_VAL(
12683 : nc_inq_varname(nCdfId, anPotentialVectorVarID[j], szTemp2));
12684 : CPLDebug("GDAL_netCDF", "Variable %s is a vector field", szTemp2);
12685 : #endif
12686 525 : poLayer->AddField(anPotentialVectorVarID[j]);
12687 : }
12688 : }
12689 :
12690 49 : if (poLayer->GetLayerDefn()->GetFieldCount() != 0 ||
12691 0 : poLayer->GetGeomType() != wkbNone)
12692 : {
12693 49 : papoLayers.push_back(poLayer);
12694 : }
12695 :
12696 98 : return CE_None;
12697 : }
12698 :
12699 : // Get all coordinate and boundary variables full names referenced in
12700 : // a given a NetCDF (or group) ID and its sub-groups.
12701 : // These variables are identified in other variable's
12702 : // "coordinates" and "bounds" attribute.
12703 : // Searching coordinate and boundary variables may need to explore
12704 : // parents groups (or other groups in case of reference given in form of an
12705 : // absolute path).
12706 : // See CF sections 5.2, 5.6 and 7.1
12707 533 : static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId, char ***ppapszVars)
12708 : {
12709 533 : int nVars = 0;
12710 533 : NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
12711 :
12712 3254 : for (int v = 0; v < nVars; v++)
12713 : {
12714 2721 : char *pszTemp = nullptr;
12715 2721 : char **papszTokens = nullptr;
12716 2721 : if (NCDFGetAttr(nCdfId, v, "coordinates", &pszTemp) == CE_None)
12717 446 : papszTokens = NCDFTokenizeCoordinatesAttribute(pszTemp);
12718 2721 : CPLFree(pszTemp);
12719 2721 : pszTemp = nullptr;
12720 2721 : if (NCDFGetAttr(nCdfId, v, "bounds", &pszTemp) == CE_None &&
12721 2721 : pszTemp != nullptr && !EQUAL(pszTemp, ""))
12722 17 : papszTokens = CSLAddString(papszTokens, pszTemp);
12723 2721 : CPLFree(pszTemp);
12724 4009 : for (int i = 0; papszTokens != nullptr && papszTokens[i] != nullptr;
12725 : i++)
12726 : {
12727 1288 : char *pszVarFullName = nullptr;
12728 1288 : if (NCDFResolveVarFullName(nCdfId, papszTokens[i],
12729 1288 : &pszVarFullName) == CE_None)
12730 1262 : *ppapszVars = CSLAddString(*ppapszVars, pszVarFullName);
12731 1288 : CPLFree(pszVarFullName);
12732 : }
12733 2721 : CSLDestroy(papszTokens);
12734 : }
12735 :
12736 : // Recurse on sub-groups.
12737 : int nSubGroups;
12738 533 : int *panSubGroupIds = nullptr;
12739 533 : NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
12740 568 : for (int i = 0; i < nSubGroups; i++)
12741 : {
12742 35 : NCDFGetCoordAndBoundVarFullNames(panSubGroupIds[i], ppapszVars);
12743 : }
12744 533 : CPLFree(panSubGroupIds);
12745 :
12746 533 : return CE_None;
12747 : }
12748 :
12749 : // Check if give type is user defined
12750 1837 : bool NCDFIsUserDefinedType(int /*ncid*/, int type)
12751 : {
12752 1837 : return type >= NC_FIRSTUSERTYPEID;
12753 : }
12754 :
12755 577 : char **NCDFTokenizeCoordinatesAttribute(const char *pszCoordinates)
12756 : {
12757 : // CF conventions use space as the separator for variable names in the
12758 : // coordinates attribute, but some products such as
12759 : // https://sentinel.esa.int/web/sentinel/technical-guides/sentinel-3-synergy/products-algorithms/level-2-aod-algorithms-and-products/level-2-aod-products-description
12760 : // use comma.
12761 577 : return CSLTokenizeString2(pszCoordinates, ", ", 0);
12762 : }
|