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 : * Permission is hereby granted, free of charge, to any person obtaining a
15 : * copy of this software and associated documentation files (the "Software"),
16 : * to deal in the Software without restriction, including without limitation
17 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
18 : * and/or sell copies of the Software, and to permit persons to whom the
19 : * Software is furnished to do so, subject to the following conditions:
20 : *
21 : * The above copyright notice and this permission notice shall be included
22 : * in all copies or substantial portions of the Software.
23 : *
24 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
25 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
27 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
29 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
30 : * DEALINGS IN THE SOFTWARE.
31 : ****************************************************************************/
32 :
33 : #include "cpl_port.h"
34 :
35 : #include <array>
36 : #include <cassert>
37 : #include <cctype>
38 : #include <cerrno>
39 : #include <climits>
40 : #include <cmath>
41 : #include <cstdio>
42 : #include <cstdlib>
43 : #include <cstring>
44 : #include <ctime>
45 : #include <algorithm>
46 : #include <limits>
47 : #include <map>
48 : #include <set>
49 : #include <queue>
50 : #include <string>
51 : #include <tuple>
52 : #include <utility>
53 : #include <vector>
54 :
55 : // Must be included after standard includes, otherwise VS2015 fails when
56 : // including <ctime>
57 : #include "netcdfdataset.h"
58 : #include "netcdfdrivercore.h"
59 : #include "netcdfsg.h"
60 : #include "netcdfuffd.h"
61 :
62 : #include "netcdf_mem.h"
63 :
64 : #include "cpl_conv.h"
65 : #include "cpl_error.h"
66 : #include "cpl_json.h"
67 : #include "cpl_minixml.h"
68 : #include "cpl_multiproc.h"
69 : #include "cpl_progress.h"
70 : #include "cpl_time.h"
71 : #include "gdal.h"
72 : #include "gdal_frmts.h"
73 : #include "ogr_core.h"
74 : #include "ogr_srs_api.h"
75 :
76 : // netCDF 4.8 switched to expecting filenames in UTF-8 on Windows
77 : // But with netCDF 4.9 and https://github.com/Unidata/netcdf-c/pull/2277/,
78 : // this is apparently back to expecting filenames in current codepage...
79 : // Detect netCDF 4.8 with NC_ENCZARR
80 : // Detect netCDF 4.9 with NC_NOATTCREORD
81 : #if defined(NC_ENCZARR) && !defined(NC_NOATTCREORD)
82 : #define NETCDF_USES_UTF8
83 : #endif
84 :
85 : // Internal function declarations.
86 :
87 : static bool NCDFIsGDALVersionGTE(const char *pszVersion, int nTarget);
88 :
89 : static void
90 : NCDFAddGDALHistory(int fpImage, const char *pszFilename, bool bWriteGDALVersion,
91 : bool bWriteGDALHistory, const char *pszOldHist,
92 : const char *pszFunctionName,
93 : const char *pszCFVersion = GDAL_DEFAULT_NCDF_CONVENTIONS);
94 :
95 : static void NCDFAddHistory(int fpImage, const char *pszAddHist,
96 : const char *pszOldHist);
97 :
98 : static CPLErr NCDFSafeStrcat(char **ppszDest, const char *pszSrc,
99 : size_t *nDestSize);
100 :
101 : // Var / attribute helper functions.
102 : static CPLErr NCDFPutAttr(int nCdfId, int nVarId, const char *pszAttrName,
103 : const char *pszValue);
104 :
105 : // Replace this where used.
106 : static CPLErr NCDFGet1DVar(int nCdfId, int nVarId, char **pszValue);
107 : static CPLErr NCDFPut1DVar(int nCdfId, int nVarId, const char *pszValue);
108 :
109 : // Replace this where used.
110 : static char **NCDFTokenizeArray(const char *pszValue);
111 : static void CopyMetadata(GDALDataset *poSrcDS, GDALRasterBand *poSrcBand,
112 : GDALRasterBand *poDstBand, int fpImage, int CDFVarID,
113 : const char *pszMatchPrefix = nullptr);
114 :
115 : // NetCDF-4 groups helper functions.
116 : // They all work also for NetCDF-3 files which are considered as
117 : // NetCDF-4 file with only one group.
118 : static CPLErr NCDFOpenSubDataset(int nCdfId, const char *pszSubdatasetName,
119 : int *pnGroupId, int *pnVarId);
120 : static CPLErr NCDFGetVisibleDims(int nGroupId, int *pnDims, int **ppanDimIds);
121 : static CPLErr NCDFGetSubGroups(int nGroupId, int *pnSubGroups,
122 : int **ppanSubGroupIds);
123 : static CPLErr NCDFGetGroupFullName(int nGroupId, char **ppszFullName,
124 : bool bNC3Compat = true);
125 : static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId, char **ppszFullName,
126 : bool bNC3Compat = true);
127 : static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId);
128 :
129 : static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,
130 : char **ppszFullName,
131 : bool bMandatory = false);
132 : static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,
133 : const char *pszAtt, int *pnAtt,
134 : bool bMandatory = false);
135 : static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId, char ***ppapszVars);
136 :
137 : // Uncomment this for more debug output.
138 : // #define NCDF_DEBUG 1
139 :
140 : CPLMutex *hNCMutex = nullptr;
141 :
142 : // Workaround https://github.com/OSGeo/gdal/issues/6253
143 : // Having 2 netCDF handles on the same file doesn't work in a multi-threaded
144 : // way. Apparently having the same handle works better (this is OK since
145 : // we have a global mutex on the netCDF library)
146 : static std::map<std::string, int> goMapNameToNetCDFId;
147 : static std::map<int, std::pair<std::string, int>> goMapNetCDFIdToKeyAndCount;
148 :
149 644 : int GDAL_nc_open(const char *pszFilename, int nMode, int *pID)
150 : {
151 1288 : std::string osKey(pszFilename);
152 644 : osKey += "#####";
153 644 : osKey += std::to_string(nMode);
154 644 : auto oIter = goMapNameToNetCDFId.find(osKey);
155 644 : if (oIter == goMapNameToNetCDFId.end())
156 : {
157 600 : int ret = nc_open(pszFilename, nMode, pID);
158 600 : if (ret != NC_NOERR)
159 3 : return ret;
160 597 : goMapNameToNetCDFId[osKey] = *pID;
161 597 : goMapNetCDFIdToKeyAndCount[*pID] =
162 1194 : std::pair<std::string, int>(osKey, 1);
163 597 : return ret;
164 : }
165 : else
166 : {
167 44 : *pID = oIter->second;
168 44 : goMapNetCDFIdToKeyAndCount[oIter->second].second++;
169 44 : return NC_NOERR;
170 : }
171 : }
172 :
173 892 : int GDAL_nc_close(int cdfid)
174 : {
175 892 : int ret = NC_NOERR;
176 892 : auto oIter = goMapNetCDFIdToKeyAndCount.find(cdfid);
177 892 : if (oIter != goMapNetCDFIdToKeyAndCount.end())
178 : {
179 641 : if (--oIter->second.second == 0)
180 : {
181 597 : ret = nc_close(cdfid);
182 597 : goMapNameToNetCDFId.erase(oIter->second.first);
183 597 : goMapNetCDFIdToKeyAndCount.erase(oIter);
184 : }
185 : }
186 : else
187 : {
188 : // we can go here if file opened with nc_open_mem() or nc_create()
189 251 : ret = nc_close(cdfid);
190 : }
191 892 : return ret;
192 : }
193 :
194 : /************************************************************************/
195 : /* ==================================================================== */
196 : /* netCDFRasterBand */
197 : /* ==================================================================== */
198 : /************************************************************************/
199 :
200 : class netCDFRasterBand final : public GDALPamRasterBand
201 : {
202 : friend class netCDFDataset;
203 :
204 : nc_type nc_datatype;
205 : int cdfid;
206 : int nZId;
207 : int nZDim;
208 : int nLevel;
209 : int nBandXPos;
210 : int nBandYPos;
211 : int *panBandZPos;
212 : int *panBandZLev;
213 : bool m_bNoDataSet = false;
214 : double m_dfNoDataValue = 0;
215 : bool m_bNoDataSetAsInt64 = false;
216 : int64_t m_nNodataValueInt64 = 0;
217 : bool m_bNoDataSetAsUInt64 = false;
218 : uint64_t m_nNodataValueUInt64 = 0;
219 : bool bValidRangeValid = false;
220 : double adfValidRange[2]{0, 0};
221 : bool m_bHaveScale = false;
222 : bool m_bHaveOffset = false;
223 : double m_dfScale = 1;
224 : double m_dfOffset = 0;
225 : CPLString m_osUnitType{};
226 : bool bSignedData;
227 : bool bCheckLongitude;
228 : bool m_bCreateMetadataFromOtherVarsDone = false;
229 :
230 : void CreateMetadataFromAttributes();
231 : void CreateMetadataFromOtherVars();
232 :
233 : template <class T>
234 : void CheckData(void *pImage, void *pImageNC, size_t nTmpBlockXSize,
235 : size_t nTmpBlockYSize, bool bCheckIsNan = false);
236 : template <class T>
237 : void CheckDataCpx(void *pImage, void *pImageNC, size_t nTmpBlockXSize,
238 : size_t nTmpBlockYSize, bool bCheckIsNan = false);
239 : void SetBlockSize();
240 :
241 : bool FetchNetcdfChunk(size_t xstart, size_t ystart, void *pImage);
242 :
243 : void SetNoDataValueNoUpdate(double dfNoData);
244 : void SetNoDataValueNoUpdate(int64_t nNoData);
245 : void SetNoDataValueNoUpdate(uint64_t nNoData);
246 :
247 : void SetOffsetNoUpdate(double dfVal);
248 : void SetScaleNoUpdate(double dfVal);
249 : void SetUnitTypeNoUpdate(const char *pszNewValue);
250 :
251 : protected:
252 : CPLXMLNode *SerializeToXML(const char *pszUnused) override;
253 :
254 : public:
255 : struct CONSTRUCTOR_OPEN
256 : {
257 : };
258 :
259 : struct CONSTRUCTOR_CREATE
260 : {
261 : };
262 :
263 : netCDFRasterBand(const CONSTRUCTOR_OPEN &, netCDFDataset *poDS,
264 : int nGroupId, int nZId, int nZDim, int nLevel,
265 : const int *panBandZLen, const int *panBandPos, int nBand);
266 : netCDFRasterBand(const CONSTRUCTOR_CREATE &, netCDFDataset *poDS,
267 : GDALDataType eType, int nBand, bool bSigned = true,
268 : const char *pszBandName = nullptr,
269 : const char *pszLongName = nullptr, int nZId = -1,
270 : int nZDim = 2, int nLevel = 0,
271 : const int *panBandZLev = nullptr,
272 : const int *panBandZPos = nullptr,
273 : const int *paDimIds = nullptr);
274 : virtual ~netCDFRasterBand();
275 :
276 : virtual double GetNoDataValue(int *) override;
277 : virtual int64_t GetNoDataValueAsInt64(int *pbSuccess = nullptr) override;
278 : virtual uint64_t GetNoDataValueAsUInt64(int *pbSuccess = nullptr) override;
279 : virtual CPLErr SetNoDataValue(double) override;
280 : virtual CPLErr SetNoDataValueAsInt64(int64_t nNoData) override;
281 : virtual CPLErr SetNoDataValueAsUInt64(uint64_t nNoData) override;
282 : // virtual CPLErr DeleteNoDataValue();
283 : virtual double GetOffset(int *) override;
284 : virtual CPLErr SetOffset(double) override;
285 : virtual double GetScale(int *) override;
286 : virtual CPLErr SetScale(double) override;
287 : virtual const char *GetUnitType() override;
288 : virtual CPLErr SetUnitType(const char *) override;
289 : virtual CPLErr IReadBlock(int, int, void *) override;
290 : virtual CPLErr IWriteBlock(int, int, void *) override;
291 :
292 : char **GetMetadata(const char *pszDomain = "") override;
293 : const char *GetMetadataItem(const char *pszName,
294 : const char *pszDomain = "") override;
295 :
296 : virtual CPLErr SetMetadataItem(const char *pszName, const char *pszValue,
297 : const char *pszDomain = "") override;
298 : virtual CPLErr SetMetadata(char **papszMD,
299 : const char *pszDomain = "") override;
300 : };
301 :
302 : /************************************************************************/
303 : /* netCDFRasterBand() */
304 : /************************************************************************/
305 :
306 451 : netCDFRasterBand::netCDFRasterBand(const netCDFRasterBand::CONSTRUCTOR_OPEN &,
307 : netCDFDataset *poNCDFDS, int nGroupId,
308 : int nZIdIn, int nZDimIn, int nLevelIn,
309 : const int *panBandZLevIn,
310 451 : const int *panBandZPosIn, int nBandIn)
311 : : nc_datatype(NC_NAT), cdfid(nGroupId), nZId(nZIdIn), nZDim(nZDimIn),
312 451 : nLevel(nLevelIn), nBandXPos(panBandZPosIn[0]),
313 451 : nBandYPos(nZDim == 1 ? -1 : panBandZPosIn[1]), panBandZPos(nullptr),
314 : panBandZLev(nullptr),
315 : bSignedData(true), // Default signed, except for Byte.
316 902 : bCheckLongitude(false)
317 : {
318 451 : poDS = poNCDFDS;
319 451 : nBand = nBandIn;
320 :
321 : // Take care of all other dimensions.
322 451 : if (nZDim > 2)
323 : {
324 150 : panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
325 150 : panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
326 :
327 425 : for (int i = 0; i < nZDim - 2; i++)
328 : {
329 275 : panBandZPos[i] = panBandZPosIn[i + 2];
330 275 : panBandZLev[i] = panBandZLevIn[i];
331 : }
332 : }
333 :
334 451 : nRasterXSize = poDS->GetRasterXSize();
335 451 : nRasterYSize = poDS->GetRasterYSize();
336 451 : nBlockXSize = poDS->GetRasterXSize();
337 451 : nBlockYSize = 1;
338 :
339 : // Get the type of the "z" variable, our target raster array.
340 451 : if (nc_inq_var(cdfid, nZId, nullptr, &nc_datatype, nullptr, nullptr,
341 451 : nullptr) != NC_NOERR)
342 : {
343 0 : CPLError(CE_Failure, CPLE_AppDefined, "Error in nc_var_inq() on 'z'.");
344 0 : return;
345 : }
346 :
347 451 : if (NCDFIsUserDefinedType(cdfid, nc_datatype))
348 : {
349 : // First enquire and check that the number of fields is 2
350 : size_t nfields, compoundsize;
351 5 : if (nc_inq_compound(cdfid, nc_datatype, nullptr, &compoundsize,
352 5 : &nfields) != NC_NOERR)
353 : {
354 0 : CPLError(CE_Failure, CPLE_AppDefined,
355 : "Error in nc_inq_compound() on 'z'.");
356 0 : return;
357 : }
358 :
359 5 : if (nfields != 2)
360 : {
361 0 : CPLError(CE_Failure, CPLE_AppDefined,
362 : "Unsupported data type encountered in nc_inq_compound() "
363 : "on 'z'.");
364 0 : return;
365 : }
366 :
367 : // Now check that that two types are the same in the struct.
368 : nc_type field_type1, field_type2;
369 : int field_dims1, field_dims2;
370 5 : if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
371 : &field_type1, &field_dims1,
372 5 : nullptr) != NC_NOERR)
373 : {
374 0 : CPLError(
375 : CE_Failure, CPLE_AppDefined,
376 : "Error in querying Field 1 in nc_inq_compound_field() on 'z'.");
377 0 : return;
378 : }
379 :
380 5 : if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
381 : &field_type2, &field_dims2,
382 5 : nullptr) != NC_NOERR)
383 : {
384 0 : CPLError(
385 : CE_Failure, CPLE_AppDefined,
386 : "Error in querying Field 2 in nc_inq_compound_field() on 'z'.");
387 0 : return;
388 : }
389 :
390 5 : if ((field_type1 != field_type2) || (field_dims1 != field_dims2) ||
391 5 : (field_dims1 != 0))
392 : {
393 0 : CPLError(CE_Failure, CPLE_AppDefined,
394 : "Error in interpreting compound data type on 'z'.");
395 0 : return;
396 : }
397 :
398 5 : if (field_type1 == NC_SHORT)
399 0 : eDataType = GDT_CInt16;
400 5 : else if (field_type1 == NC_INT)
401 0 : eDataType = GDT_CInt32;
402 5 : else if (field_type1 == NC_FLOAT)
403 4 : eDataType = GDT_CFloat32;
404 1 : else if (field_type1 == NC_DOUBLE)
405 1 : eDataType = GDT_CFloat64;
406 : else
407 : {
408 0 : CPLError(CE_Failure, CPLE_AppDefined,
409 : "Unsupported netCDF compound data type encountered.");
410 0 : return;
411 : }
412 : }
413 : else
414 : {
415 446 : if (nc_datatype == NC_BYTE)
416 127 : eDataType = GDT_Byte;
417 319 : else if (nc_datatype == NC_CHAR)
418 0 : eDataType = GDT_Byte;
419 319 : else if (nc_datatype == NC_SHORT)
420 42 : eDataType = GDT_Int16;
421 277 : else if (nc_datatype == NC_INT)
422 88 : eDataType = GDT_Int32;
423 189 : else if (nc_datatype == NC_FLOAT)
424 106 : eDataType = GDT_Float32;
425 83 : else if (nc_datatype == NC_DOUBLE)
426 40 : eDataType = GDT_Float64;
427 43 : else if (nc_datatype == NC_UBYTE)
428 14 : eDataType = GDT_Byte;
429 29 : else if (nc_datatype == NC_USHORT)
430 6 : eDataType = GDT_UInt16;
431 23 : else if (nc_datatype == NC_UINT)
432 4 : eDataType = GDT_UInt32;
433 19 : else if (nc_datatype == NC_INT64)
434 10 : eDataType = GDT_Int64;
435 9 : else if (nc_datatype == NC_UINT64)
436 9 : eDataType = GDT_UInt64;
437 : else
438 : {
439 0 : if (nBand == 1)
440 0 : CPLError(CE_Warning, CPLE_AppDefined,
441 : "Unsupported netCDF datatype (%d), treat as Float32.",
442 0 : static_cast<int>(nc_datatype));
443 0 : eDataType = GDT_Float32;
444 0 : nc_datatype = NC_FLOAT;
445 : }
446 : }
447 :
448 : // Find and set No Data for this variable.
449 451 : nc_type atttype = NC_NAT;
450 451 : size_t attlen = 0;
451 451 : const char *pszNoValueName = nullptr;
452 :
453 : // Find attribute name, either _FillValue or missing_value.
454 451 : int status = nc_inq_att(cdfid, nZId, _FillValue, &atttype, &attlen);
455 451 : if (status == NC_NOERR)
456 : {
457 247 : pszNoValueName = _FillValue;
458 : }
459 : else
460 : {
461 204 : status = nc_inq_att(cdfid, nZId, "missing_value", &atttype, &attlen);
462 204 : if (status == NC_NOERR)
463 : {
464 12 : pszNoValueName = "missing_value";
465 : }
466 : }
467 :
468 : // Fetch missing value.
469 451 : double dfNoData = 0.0;
470 451 : bool bGotNoData = false;
471 451 : int64_t nNoDataAsInt64 = 0;
472 451 : bool bGotNoDataAsInt64 = false;
473 451 : uint64_t nNoDataAsUInt64 = 0;
474 451 : bool bGotNoDataAsUInt64 = false;
475 451 : if (status == NC_NOERR)
476 : {
477 259 : nc_type nAttrType = NC_NAT;
478 259 : size_t nAttrLen = 0;
479 259 : status = nc_inq_att(cdfid, nZId, pszNoValueName, &nAttrType, &nAttrLen);
480 259 : if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_INT64)
481 : {
482 : long long v;
483 9 : nc_get_att_longlong(cdfid, nZId, pszNoValueName, &v);
484 9 : bGotNoData = true;
485 9 : bGotNoDataAsInt64 = true;
486 9 : nNoDataAsInt64 = static_cast<int64_t>(v);
487 : }
488 250 : else if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_UINT64)
489 : {
490 : unsigned long long v;
491 9 : nc_get_att_ulonglong(cdfid, nZId, pszNoValueName, &v);
492 9 : bGotNoData = true;
493 9 : bGotNoDataAsUInt64 = true;
494 9 : nNoDataAsUInt64 = static_cast<uint64_t>(v);
495 : }
496 241 : else if (NCDFGetAttr(cdfid, nZId, pszNoValueName, &dfNoData) == CE_None)
497 : {
498 240 : bGotNoData = true;
499 : }
500 : }
501 :
502 : // If NoData was not found, use the default value, but for non-Byte types
503 : // as it is not recommended:
504 : // https://www.unidata.ucar.edu/software/netcdf/docs/attribute_conventions.html
505 451 : nc_type vartype = NC_NAT;
506 451 : if (!bGotNoData)
507 : {
508 193 : nc_inq_vartype(cdfid, nZId, &vartype);
509 193 : if (vartype == NC_INT64)
510 : {
511 : nNoDataAsInt64 =
512 1 : NCDFGetDefaultNoDataValueAsInt64(cdfid, nZId, bGotNoData);
513 1 : bGotNoDataAsInt64 = bGotNoData;
514 : }
515 192 : else if (vartype == NC_UINT64)
516 : {
517 : nNoDataAsUInt64 =
518 0 : NCDFGetDefaultNoDataValueAsUInt64(cdfid, nZId, bGotNoData);
519 0 : bGotNoDataAsUInt64 = bGotNoData;
520 : }
521 192 : else if (vartype != NC_CHAR && vartype != NC_BYTE &&
522 87 : vartype != NC_UBYTE)
523 : {
524 79 : dfNoData =
525 79 : NCDFGetDefaultNoDataValue(cdfid, nZId, vartype, bGotNoData);
526 79 : if (bGotNoData)
527 : {
528 69 : CPLDebug("GDAL_netCDF",
529 : "did not get nodata value for variable #%d, using "
530 : "default %f",
531 : nZId, dfNoData);
532 : }
533 : }
534 : }
535 :
536 451 : bool bHasUnderscoreUnsignedAttr = false;
537 451 : bool bUnderscoreUnsignedAttrVal = false;
538 : {
539 451 : char *pszTemp = nullptr;
540 451 : if (NCDFGetAttr(cdfid, nZId, "_Unsigned", &pszTemp) == CE_None)
541 : {
542 120 : if (EQUAL(pszTemp, "true"))
543 : {
544 112 : bHasUnderscoreUnsignedAttr = true;
545 112 : bUnderscoreUnsignedAttrVal = true;
546 : }
547 8 : else if (EQUAL(pszTemp, "false"))
548 : {
549 8 : bHasUnderscoreUnsignedAttr = true;
550 8 : bUnderscoreUnsignedAttrVal = false;
551 : }
552 120 : CPLFree(pszTemp);
553 : }
554 : }
555 :
556 : // Look for valid_range or valid_min/valid_max.
557 :
558 : // First look for valid_range.
559 451 : if (CPLFetchBool(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE", true))
560 : {
561 449 : char *pszValidRange = nullptr;
562 449 : if (NCDFGetAttr(cdfid, nZId, "valid_range", &pszValidRange) ==
563 113 : CE_None &&
564 562 : pszValidRange[0] == '{' &&
565 113 : pszValidRange[strlen(pszValidRange) - 1] == '}')
566 : {
567 : const std::string osValidRange =
568 339 : std::string(pszValidRange).substr(1, strlen(pszValidRange) - 2);
569 : const CPLStringList aosValidRange(
570 226 : CSLTokenizeString2(osValidRange.c_str(), ",", 0));
571 113 : if (aosValidRange.size() == 2 &&
572 226 : CPLGetValueType(aosValidRange[0]) != CPL_VALUE_STRING &&
573 113 : CPLGetValueType(aosValidRange[1]) != CPL_VALUE_STRING)
574 : {
575 113 : bValidRangeValid = true;
576 113 : adfValidRange[0] = CPLAtof(aosValidRange[0]);
577 113 : adfValidRange[1] = CPLAtof(aosValidRange[1]);
578 : }
579 : }
580 449 : CPLFree(pszValidRange);
581 :
582 : // If not found look for valid_min and valid_max.
583 449 : if (!bValidRangeValid)
584 : {
585 336 : double dfMin = 0;
586 336 : double dfMax = 0;
587 351 : if (NCDFGetAttr(cdfid, nZId, "valid_min", &dfMin) == CE_None &&
588 15 : NCDFGetAttr(cdfid, nZId, "valid_max", &dfMax) == CE_None)
589 : {
590 8 : adfValidRange[0] = dfMin;
591 8 : adfValidRange[1] = dfMax;
592 8 : bValidRangeValid = true;
593 : }
594 : }
595 :
596 449 : if (bValidRangeValid &&
597 121 : (adfValidRange[0] < 0 || adfValidRange[1] < 0) &&
598 18 : nc_datatype == NC_SHORT && bHasUnderscoreUnsignedAttr &&
599 : bUnderscoreUnsignedAttrVal)
600 : {
601 3 : if (adfValidRange[0] < 0)
602 0 : adfValidRange[0] += 65536;
603 3 : if (adfValidRange[1] < 0)
604 3 : adfValidRange[1] += 65536;
605 3 : if (adfValidRange[0] <= adfValidRange[1])
606 : {
607 : // Updating metadata item
608 3 : GDALPamRasterBand::SetMetadataItem(
609 : "valid_range",
610 3 : CPLSPrintf("{%d,%d}", static_cast<int>(adfValidRange[0]),
611 3 : static_cast<int>(adfValidRange[1])));
612 : }
613 : }
614 :
615 449 : if (bValidRangeValid && adfValidRange[0] > adfValidRange[1])
616 : {
617 0 : CPLError(CE_Warning, CPLE_AppDefined,
618 : "netCDFDataset::valid_range: min > max:\n"
619 : " min: %lf\n max: %lf\n",
620 : adfValidRange[0], adfValidRange[1]);
621 0 : bValidRangeValid = false;
622 0 : adfValidRange[0] = 0.0;
623 0 : adfValidRange[1] = 0.0;
624 : }
625 : }
626 :
627 : // Special For Byte Bands: check for signed/unsigned byte.
628 451 : if (nc_datatype == NC_BYTE)
629 : {
630 : // netcdf uses signed byte by default, but GDAL uses unsigned by default
631 : // This may cause unexpected results, but is needed for back-compat.
632 127 : if (poNCDFDS->bIsGdalFile)
633 106 : bSignedData = false;
634 : else
635 21 : bSignedData = true;
636 :
637 : // For NC4 format NC_BYTE is (normally) signed, NC_UBYTE is unsigned.
638 : // But in case a NC3 file was converted automatically and has hints
639 : // that it is unsigned, take them into account
640 127 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
641 : {
642 3 : bSignedData = true;
643 : }
644 :
645 : // If we got valid_range, test for signed/unsigned range.
646 : // https://docs.unidata.ucar.edu/netcdf-c/current/attribute_conventions.html
647 127 : if (bValidRangeValid)
648 : {
649 : // If we got valid_range={0,255}, treat as unsigned.
650 108 : if (adfValidRange[0] == 0 && adfValidRange[1] == 255)
651 : {
652 100 : bSignedData = false;
653 : // Reset valid_range.
654 100 : bValidRangeValid = false;
655 : }
656 : // If we got valid_range={-128,127}, treat as signed.
657 8 : else if (adfValidRange[0] == -128 && adfValidRange[1] == 127)
658 : {
659 8 : bSignedData = true;
660 : // Reset valid_range.
661 8 : bValidRangeValid = false;
662 : }
663 : }
664 : // Else test for _Unsigned.
665 : // https://docs.unidata.ucar.edu/nug/current/best_practices.html
666 : else
667 : {
668 19 : if (bHasUnderscoreUnsignedAttr)
669 7 : bSignedData = !bUnderscoreUnsignedAttrVal;
670 : }
671 :
672 127 : if (bSignedData)
673 : {
674 20 : eDataType = GDT_Int8;
675 : }
676 107 : else if (dfNoData < 0)
677 : {
678 : // Fix nodata value as it was stored signed.
679 5 : dfNoData += 256;
680 5 : if (pszNoValueName)
681 : {
682 : // Updating metadata item
683 5 : GDALPamRasterBand::SetMetadataItem(
684 : pszNoValueName,
685 : CPLSPrintf("%d", static_cast<int>(dfNoData)));
686 : }
687 : }
688 : }
689 324 : else if (nc_datatype == NC_SHORT)
690 : {
691 42 : if (bHasUnderscoreUnsignedAttr)
692 : {
693 5 : bSignedData = !bUnderscoreUnsignedAttrVal;
694 5 : if (!bSignedData)
695 5 : eDataType = GDT_UInt16;
696 : }
697 :
698 : // Fix nodata value as it was stored signed.
699 42 : if (!bSignedData && dfNoData < 0)
700 : {
701 5 : dfNoData += 65536;
702 5 : if (pszNoValueName)
703 : {
704 : // Updating metadata item
705 5 : GDALPamRasterBand::SetMetadataItem(
706 : pszNoValueName,
707 : CPLSPrintf("%d", static_cast<int>(dfNoData)));
708 : }
709 : }
710 : }
711 :
712 282 : else if (nc_datatype == NC_UBYTE || nc_datatype == NC_USHORT ||
713 262 : nc_datatype == NC_UINT || nc_datatype == NC_UINT64)
714 : {
715 33 : bSignedData = false;
716 : }
717 :
718 451 : CPLDebug("GDAL_netCDF", "netcdf type=%d gdal type=%d signedByte=%d",
719 451 : nc_datatype, eDataType, static_cast<int>(bSignedData));
720 :
721 451 : if (bGotNoData)
722 : {
723 : // Set nodata value.
724 328 : if (bGotNoDataAsInt64)
725 : {
726 10 : if (eDataType == GDT_Int64)
727 : {
728 10 : SetNoDataValueNoUpdate(nNoDataAsInt64);
729 : }
730 0 : else if (eDataType == GDT_UInt64 && nNoDataAsInt64 >= 0)
731 : {
732 0 : SetNoDataValueNoUpdate(static_cast<uint64_t>(nNoDataAsInt64));
733 : }
734 : else
735 : {
736 0 : SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsInt64));
737 : }
738 : }
739 318 : else if (bGotNoDataAsUInt64)
740 : {
741 9 : if (eDataType == GDT_UInt64)
742 : {
743 9 : SetNoDataValueNoUpdate(nNoDataAsUInt64);
744 : }
745 0 : else if (eDataType == GDT_Int64 &&
746 : nNoDataAsUInt64 <=
747 0 : static_cast<uint64_t>(
748 0 : std::numeric_limits<int64_t>::max()))
749 : {
750 0 : SetNoDataValueNoUpdate(static_cast<int64_t>(nNoDataAsUInt64));
751 : }
752 : else
753 : {
754 0 : SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsUInt64));
755 : }
756 : }
757 : else
758 : {
759 : #ifdef NCDF_DEBUG
760 : CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) read", dfNoData);
761 : #endif
762 618 : if (eDataType == GDT_Int64 &&
763 0 : dfNoData >=
764 0 : static_cast<double>(std::numeric_limits<int64_t>::min()) &&
765 0 : dfNoData <=
766 309 : static_cast<double>(std::numeric_limits<int64_t>::max()) &&
767 0 : dfNoData == static_cast<double>(static_cast<int64_t>(dfNoData)))
768 : {
769 0 : SetNoDataValueNoUpdate(static_cast<int64_t>(dfNoData));
770 : }
771 618 : else if (eDataType == GDT_UInt64 &&
772 0 : dfNoData >= static_cast<double>(
773 0 : std::numeric_limits<uint64_t>::min()) &&
774 0 : dfNoData <= static_cast<double>(
775 309 : std::numeric_limits<uint64_t>::max()) &&
776 0 : dfNoData ==
777 0 : static_cast<double>(static_cast<uint64_t>(dfNoData)))
778 : {
779 0 : SetNoDataValueNoUpdate(static_cast<uint64_t>(dfNoData));
780 : }
781 : else
782 : {
783 309 : SetNoDataValueNoUpdate(dfNoData);
784 : }
785 : }
786 : }
787 :
788 451 : CreateMetadataFromAttributes();
789 :
790 : // Attempt to fetch the scale_factor and add_offset attributes for the
791 : // variable and set them. If these values are not available, set
792 : // offset to 0 and scale to 1.
793 451 : if (nc_inq_attid(cdfid, nZId, CF_ADD_OFFSET, nullptr) == NC_NOERR)
794 : {
795 17 : double dfOffset = 0;
796 17 : status = nc_get_att_double(cdfid, nZId, CF_ADD_OFFSET, &dfOffset);
797 17 : CPLDebug("GDAL_netCDF", "got add_offset=%.16g, status=%d", dfOffset,
798 : status);
799 17 : SetOffsetNoUpdate(dfOffset);
800 : }
801 :
802 451 : bool bHasScale = false;
803 451 : if (nc_inq_attid(cdfid, nZId, CF_SCALE_FACTOR, nullptr) == NC_NOERR)
804 : {
805 21 : bHasScale = true;
806 21 : double dfScale = 1;
807 21 : status = nc_get_att_double(cdfid, nZId, CF_SCALE_FACTOR, &dfScale);
808 21 : CPLDebug("GDAL_netCDF", "got scale_factor=%.16g, status=%d", dfScale,
809 : status);
810 21 : SetScaleNoUpdate(dfScale);
811 : }
812 :
813 13 : if (bValidRangeValid && GDALDataTypeIsInteger(eDataType) &&
814 5 : eDataType != GDT_Int64 && eDataType != GDT_UInt64 &&
815 5 : (std::fabs(std::round(adfValidRange[0]) - adfValidRange[0]) > 1e-5 ||
816 464 : std::fabs(std::round(adfValidRange[1]) - adfValidRange[1]) > 1e-5) &&
817 1 : CSLFetchNameValue(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE") ==
818 : nullptr)
819 : {
820 1 : CPLError(CE_Warning, CPLE_AppDefined,
821 : "validity range = %f, %f contains floating-point values, "
822 : "whereas data type is integer. valid_range is thus likely "
823 : "wrong%s. Ignoring it.",
824 : adfValidRange[0], adfValidRange[1],
825 : bHasScale ? " (likely scaled using scale_factor/add_factor "
826 : "whereas it should be using the packed data type)"
827 : : "");
828 1 : bValidRangeValid = false;
829 1 : adfValidRange[0] = 0.0;
830 1 : adfValidRange[1] = 0.0;
831 : }
832 :
833 : // Should we check for longitude values > 360?
834 451 : bCheckLongitude =
835 902 : CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")) &&
836 451 : NCDFIsVarLongitude(cdfid, nZId, nullptr);
837 :
838 : // Attempt to fetch the units attribute for the variable and set it.
839 451 : SetUnitTypeNoUpdate(netCDFRasterBand::GetMetadataItem(CF_UNITS));
840 :
841 451 : SetBlockSize();
842 : }
843 :
844 622 : void netCDFRasterBand::SetBlockSize()
845 : {
846 : // Check for variable chunking (netcdf-4 only).
847 : // GDAL block size should be set to hdf5 chunk size.
848 622 : int nTmpFormat = 0;
849 622 : int status = nc_inq_format(cdfid, &nTmpFormat);
850 622 : NetCDFFormatEnum eTmpFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
851 622 : if ((status == NC_NOERR) &&
852 524 : (eTmpFormat == NCDF_FORMAT_NC4 || eTmpFormat == NCDF_FORMAT_NC4C))
853 : {
854 112 : size_t chunksize[MAX_NC_DIMS] = {};
855 : // Check for chunksize and set it as the blocksize (optimizes read).
856 112 : status = nc_inq_var_chunking(cdfid, nZId, &nTmpFormat, chunksize);
857 112 : if ((status == NC_NOERR) && (nTmpFormat == NC_CHUNKED))
858 : {
859 11 : nBlockXSize = (int)chunksize[nZDim - 1];
860 11 : if (nZDim >= 2)
861 11 : nBlockYSize = (int)chunksize[nZDim - 2];
862 : else
863 0 : nBlockYSize = 1;
864 : }
865 : }
866 :
867 : // Deal with bottom-up datasets and nBlockYSize != 1.
868 622 : auto poGDS = static_cast<netCDFDataset *>(poDS);
869 622 : if (poGDS->bBottomUp && nBlockYSize != 1 && poGDS->poChunkCache == nullptr)
870 : {
871 5 : if (poGDS->eAccess == GA_ReadOnly)
872 : {
873 : // Try to cache 1 or 2 'rows' of netCDF chunks along the whole
874 : // width of the raster
875 5 : size_t nChunks =
876 5 : static_cast<size_t>(DIV_ROUND_UP(nRasterXSize, nBlockXSize));
877 5 : if ((nRasterYSize % nBlockYSize) != 0)
878 1 : nChunks *= 2;
879 : const size_t nChunkSize =
880 5 : static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
881 5 : nBlockXSize * nBlockYSize;
882 5 : constexpr size_t MAX_CACHE_SIZE = 100 * 1024 * 1024;
883 5 : nChunks = std::min(nChunks, MAX_CACHE_SIZE / nChunkSize);
884 5 : if (nChunks)
885 : {
886 5 : poGDS->poChunkCache.reset(
887 5 : new netCDFDataset::ChunkCacheType(nChunks));
888 : }
889 : }
890 : else
891 : {
892 0 : nBlockYSize = 1;
893 : }
894 : }
895 622 : }
896 :
897 : // Constructor in create mode.
898 : // If nZId and following variables are not passed, the band will have 2
899 : // dimensions.
900 : // TODO: Get metadata, missing val from band #1 if nZDim > 2.
901 171 : netCDFRasterBand::netCDFRasterBand(
902 : const netCDFRasterBand::CONSTRUCTOR_CREATE &, netCDFDataset *poNCDFDS,
903 : const GDALDataType eTypeIn, int nBandIn, bool bSigned,
904 : const char *pszBandName, const char *pszLongName, int nZIdIn, int nZDimIn,
905 : int nLevelIn, const int *panBandZLevIn, const int *panBandZPosIn,
906 171 : const int *paDimIds)
907 171 : : nc_datatype(NC_NAT), cdfid(poNCDFDS->GetCDFID()), nZId(nZIdIn),
908 : nZDim(nZDimIn), nLevel(nLevelIn), nBandXPos(1), nBandYPos(0),
909 : panBandZPos(nullptr), panBandZLev(nullptr), bSignedData(bSigned),
910 171 : bCheckLongitude(false), m_bCreateMetadataFromOtherVarsDone(true)
911 : {
912 171 : poDS = poNCDFDS;
913 171 : nBand = nBandIn;
914 :
915 171 : nRasterXSize = poDS->GetRasterXSize();
916 171 : nRasterYSize = poDS->GetRasterYSize();
917 171 : nBlockXSize = poDS->GetRasterXSize();
918 171 : nBlockYSize = 1;
919 :
920 171 : if (poDS->GetAccess() != GA_Update)
921 : {
922 0 : CPLError(CE_Failure, CPLE_NotSupported,
923 : "Dataset is not in update mode, "
924 : "wrong netCDFRasterBand constructor");
925 0 : return;
926 : }
927 :
928 : // Take care of all other dimensions.
929 171 : if (nZDim > 2 && paDimIds != nullptr)
930 : {
931 23 : nBandXPos = panBandZPosIn[0];
932 23 : nBandYPos = panBandZPosIn[1];
933 :
934 23 : panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
935 23 : panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
936 :
937 68 : for (int i = 0; i < nZDim - 2; i++)
938 : {
939 45 : panBandZPos[i] = panBandZPosIn[i + 2];
940 45 : panBandZLev[i] = panBandZLevIn[i];
941 : }
942 : }
943 :
944 : // Get the type of the "z" variable, our target raster array.
945 171 : eDataType = eTypeIn;
946 :
947 171 : switch (eDataType)
948 : {
949 66 : case GDT_Byte:
950 66 : nc_datatype = NC_BYTE;
951 : // NC_UBYTE (unsigned byte) is only available for NC4.
952 66 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
953 3 : nc_datatype = NC_UBYTE;
954 66 : break;
955 7 : case GDT_Int8:
956 7 : nc_datatype = NC_BYTE;
957 7 : break;
958 11 : case GDT_Int16:
959 11 : nc_datatype = NC_SHORT;
960 11 : break;
961 24 : case GDT_Int32:
962 24 : nc_datatype = NC_INT;
963 24 : break;
964 13 : case GDT_Float32:
965 13 : nc_datatype = NC_FLOAT;
966 13 : break;
967 8 : case GDT_Float64:
968 8 : nc_datatype = NC_DOUBLE;
969 8 : break;
970 7 : case GDT_Int64:
971 7 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
972 : {
973 7 : nc_datatype = NC_INT64;
974 : }
975 : else
976 : {
977 0 : if (nBand == 1)
978 0 : CPLError(
979 : CE_Warning, CPLE_AppDefined,
980 : "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
981 : "Int64");
982 0 : nc_datatype = NC_DOUBLE;
983 0 : eDataType = GDT_Float64;
984 : }
985 7 : break;
986 7 : case GDT_UInt64:
987 7 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
988 : {
989 7 : nc_datatype = NC_UINT64;
990 : }
991 : else
992 : {
993 0 : if (nBand == 1)
994 0 : CPLError(
995 : CE_Warning, CPLE_AppDefined,
996 : "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
997 : "UInt64");
998 0 : nc_datatype = NC_DOUBLE;
999 0 : eDataType = GDT_Float64;
1000 : }
1001 7 : break;
1002 6 : case GDT_UInt16:
1003 6 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
1004 : {
1005 6 : nc_datatype = NC_USHORT;
1006 6 : break;
1007 : }
1008 : [[fallthrough]];
1009 : case GDT_UInt32:
1010 6 : if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
1011 : {
1012 6 : nc_datatype = NC_UINT;
1013 6 : break;
1014 : }
1015 : [[fallthrough]];
1016 : default:
1017 16 : if (nBand == 1)
1018 8 : CPLError(CE_Warning, CPLE_AppDefined,
1019 : "Unsupported GDAL datatype (%d), treat as NC_FLOAT.",
1020 8 : static_cast<int>(eDataType));
1021 16 : nc_datatype = NC_FLOAT;
1022 16 : eDataType = GDT_Float32;
1023 16 : break;
1024 : }
1025 :
1026 : // Define the variable if necessary (if nZId == -1).
1027 171 : bool bDefineVar = false;
1028 :
1029 171 : if (nZId == -1)
1030 : {
1031 152 : bDefineVar = true;
1032 :
1033 : // Make sure we are in define mode.
1034 152 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1035 :
1036 : char szTempPrivate[256 + 1];
1037 152 : const char *pszTemp = nullptr;
1038 152 : if (!pszBandName || EQUAL(pszBandName, ""))
1039 : {
1040 132 : snprintf(szTempPrivate, sizeof(szTempPrivate), "Band%d", nBand);
1041 132 : pszTemp = szTempPrivate;
1042 : }
1043 : else
1044 : {
1045 20 : pszTemp = pszBandName;
1046 : }
1047 :
1048 : int status;
1049 152 : if (nZDim > 2 && paDimIds != nullptr)
1050 : {
1051 4 : status =
1052 4 : nc_def_var(cdfid, pszTemp, nc_datatype, nZDim, paDimIds, &nZId);
1053 : }
1054 : else
1055 : {
1056 148 : int anBandDims[2] = {poNCDFDS->nYDimID, poNCDFDS->nXDimID};
1057 : status =
1058 148 : nc_def_var(cdfid, pszTemp, nc_datatype, 2, anBandDims, &nZId);
1059 : }
1060 152 : NCDF_ERR(status);
1061 152 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d) id=%d", cdfid, pszTemp,
1062 : nc_datatype, nZId);
1063 :
1064 152 : if (!pszLongName || EQUAL(pszLongName, ""))
1065 : {
1066 145 : snprintf(szTempPrivate, sizeof(szTempPrivate),
1067 : "GDAL Band Number %d", nBand);
1068 145 : pszTemp = szTempPrivate;
1069 : }
1070 : else
1071 : {
1072 7 : pszTemp = pszLongName;
1073 : }
1074 : status =
1075 152 : nc_put_att_text(cdfid, nZId, CF_LNG_NAME, strlen(pszTemp), pszTemp);
1076 152 : NCDF_ERR(status);
1077 :
1078 152 : poNCDFDS->DefVarDeflate(nZId, true);
1079 : }
1080 :
1081 : // For Byte data add signed/unsigned info.
1082 171 : if (eDataType == GDT_Byte || eDataType == GDT_Int8)
1083 : {
1084 73 : if (bDefineVar)
1085 : {
1086 : // Only add attributes if creating variable.
1087 : // For unsigned NC_BYTE (except NC4 format),
1088 : // add valid_range and _Unsigned ( defined in CF-1 and NUG ).
1089 68 : if (nc_datatype == NC_BYTE && poNCDFDS->eFormat != NCDF_FORMAT_NC4)
1090 : {
1091 65 : CPLDebug("GDAL_netCDF",
1092 : "adding valid_range attributes for Byte Band");
1093 65 : short l_adfValidRange[2] = {0, 0};
1094 : int status;
1095 65 : if (bSignedData || eDataType == GDT_Int8)
1096 : {
1097 7 : l_adfValidRange[0] = -128;
1098 7 : l_adfValidRange[1] = 127;
1099 7 : status =
1100 7 : nc_put_att_text(cdfid, nZId, "_Unsigned", 5, "false");
1101 : }
1102 : else
1103 : {
1104 58 : l_adfValidRange[0] = 0;
1105 58 : l_adfValidRange[1] = 255;
1106 : status =
1107 58 : nc_put_att_text(cdfid, nZId, "_Unsigned", 4, "true");
1108 : }
1109 65 : NCDF_ERR(status);
1110 65 : status = nc_put_att_short(cdfid, nZId, "valid_range", NC_SHORT,
1111 : 2, l_adfValidRange);
1112 65 : NCDF_ERR(status);
1113 : }
1114 : }
1115 : }
1116 :
1117 171 : if (nc_datatype != NC_BYTE && nc_datatype != NC_CHAR &&
1118 101 : nc_datatype != NC_UBYTE)
1119 : {
1120 : // Set default nodata.
1121 98 : bool bIgnored = false;
1122 : double dfNoData =
1123 98 : NCDFGetDefaultNoDataValue(cdfid, nZId, nc_datatype, bIgnored);
1124 : #ifdef NCDF_DEBUG
1125 : CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) default", dfNoData);
1126 : #endif
1127 98 : netCDFRasterBand::SetNoDataValue(dfNoData);
1128 : }
1129 :
1130 171 : SetBlockSize();
1131 : }
1132 :
1133 : /************************************************************************/
1134 : /* ~netCDFRasterBand() */
1135 : /************************************************************************/
1136 :
1137 1244 : netCDFRasterBand::~netCDFRasterBand()
1138 : {
1139 622 : netCDFRasterBand::FlushCache(true);
1140 622 : CPLFree(panBandZPos);
1141 622 : CPLFree(panBandZLev);
1142 1244 : }
1143 :
1144 : /************************************************************************/
1145 : /* GetMetadata() */
1146 : /************************************************************************/
1147 :
1148 47 : char **netCDFRasterBand::GetMetadata(const char *pszDomain)
1149 : {
1150 47 : if (!m_bCreateMetadataFromOtherVarsDone)
1151 47 : CreateMetadataFromOtherVars();
1152 47 : return GDALPamRasterBand::GetMetadata(pszDomain);
1153 : }
1154 :
1155 : /************************************************************************/
1156 : /* GetMetadataItem() */
1157 : /************************************************************************/
1158 :
1159 624 : const char *netCDFRasterBand::GetMetadataItem(const char *pszName,
1160 : const char *pszDomain)
1161 : {
1162 624 : if (!m_bCreateMetadataFromOtherVarsDone &&
1163 512 : STARTS_WITH(pszName, "NETCDF_DIM_") &&
1164 1 : (!pszDomain || pszDomain[0] == 0))
1165 1 : CreateMetadataFromOtherVars();
1166 624 : return GDALPamRasterBand::GetMetadataItem(pszName, pszDomain);
1167 : }
1168 :
1169 : /************************************************************************/
1170 : /* SetMetadataItem() */
1171 : /************************************************************************/
1172 :
1173 6 : CPLErr netCDFRasterBand::SetMetadataItem(const char *pszName,
1174 : const char *pszValue,
1175 : const char *pszDomain)
1176 : {
1177 7 : if (GetAccess() == GA_Update &&
1178 7 : (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)
1179 : {
1180 : // Same logic as in CopyMetadata()
1181 :
1182 1 : const char *const papszIgnoreBand[] = {
1183 : CF_ADD_OFFSET, CF_SCALE_FACTOR, "valid_range", "_Unsigned",
1184 : _FillValue, "coordinates", nullptr};
1185 : // Do not copy varname, stats, NETCDF_DIM_*, nodata
1186 : // and items in papszIgnoreBand.
1187 3 : if (STARTS_WITH(pszName, "NETCDF_VARNAME") ||
1188 1 : STARTS_WITH(pszName, "STATISTICS_") ||
1189 1 : STARTS_WITH(pszName, "NETCDF_DIM_") ||
1190 1 : STARTS_WITH(pszName, "missing_value") ||
1191 3 : STARTS_WITH(pszName, "_FillValue") ||
1192 1 : CSLFindString(papszIgnoreBand, pszName) != -1)
1193 : {
1194 : // do nothing
1195 : }
1196 : else
1197 : {
1198 1 : cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1199 :
1200 1 : if (!NCDFPutAttr(cdfid, nZId, pszName, pszValue))
1201 1 : return CE_Failure;
1202 : }
1203 : }
1204 :
1205 5 : return GDALPamRasterBand::SetMetadataItem(pszName, pszValue, pszDomain);
1206 : }
1207 :
1208 : /************************************************************************/
1209 : /* SetMetadata() */
1210 : /************************************************************************/
1211 :
1212 1 : CPLErr netCDFRasterBand::SetMetadata(char **papszMD, const char *pszDomain)
1213 : {
1214 2 : if (GetAccess() == GA_Update &&
1215 1 : (pszDomain == nullptr || pszDomain[0] == '\0'))
1216 : {
1217 : // We don't handle metadata item removal for now
1218 2 : for (const char *const *papszIter = papszMD; papszIter && *papszIter;
1219 : ++papszIter)
1220 : {
1221 1 : char *pszName = nullptr;
1222 1 : const char *pszValue = CPLParseNameValue(*papszIter, &pszName);
1223 1 : if (pszName && pszValue)
1224 1 : SetMetadataItem(pszName, pszValue);
1225 1 : CPLFree(pszName);
1226 : }
1227 : }
1228 1 : return GDALPamRasterBand::SetMetadata(papszMD, pszDomain);
1229 : }
1230 :
1231 : /************************************************************************/
1232 : /* GetOffset() */
1233 : /************************************************************************/
1234 49 : double netCDFRasterBand::GetOffset(int *pbSuccess)
1235 : {
1236 49 : if (pbSuccess != nullptr)
1237 44 : *pbSuccess = static_cast<int>(m_bHaveOffset);
1238 :
1239 49 : return m_dfOffset;
1240 : }
1241 :
1242 : /************************************************************************/
1243 : /* SetOffset() */
1244 : /************************************************************************/
1245 1 : CPLErr netCDFRasterBand::SetOffset(double dfNewOffset)
1246 : {
1247 2 : CPLMutexHolderD(&hNCMutex);
1248 :
1249 : // Write value if in update mode.
1250 1 : if (poDS->GetAccess() == GA_Update)
1251 : {
1252 : // Make sure we are in define mode.
1253 1 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1254 :
1255 1 : const int status = nc_put_att_double(cdfid, nZId, CF_ADD_OFFSET,
1256 : NC_DOUBLE, 1, &dfNewOffset);
1257 :
1258 1 : NCDF_ERR(status);
1259 1 : if (status == NC_NOERR)
1260 : {
1261 1 : SetOffsetNoUpdate(dfNewOffset);
1262 1 : return CE_None;
1263 : }
1264 :
1265 0 : return CE_Failure;
1266 : }
1267 :
1268 0 : SetOffsetNoUpdate(dfNewOffset);
1269 0 : return CE_None;
1270 : }
1271 :
1272 : /************************************************************************/
1273 : /* SetOffsetNoUpdate() */
1274 : /************************************************************************/
1275 18 : void netCDFRasterBand::SetOffsetNoUpdate(double dfVal)
1276 : {
1277 18 : m_dfOffset = dfVal;
1278 18 : m_bHaveOffset = true;
1279 18 : }
1280 :
1281 : /************************************************************************/
1282 : /* GetScale() */
1283 : /************************************************************************/
1284 49 : double netCDFRasterBand::GetScale(int *pbSuccess)
1285 : {
1286 49 : if (pbSuccess != nullptr)
1287 44 : *pbSuccess = static_cast<int>(m_bHaveScale);
1288 :
1289 49 : return m_dfScale;
1290 : }
1291 :
1292 : /************************************************************************/
1293 : /* SetScale() */
1294 : /************************************************************************/
1295 1 : CPLErr netCDFRasterBand::SetScale(double dfNewScale)
1296 : {
1297 2 : CPLMutexHolderD(&hNCMutex);
1298 :
1299 : // Write value if in update mode.
1300 1 : if (poDS->GetAccess() == GA_Update)
1301 : {
1302 : // Make sure we are in define mode.
1303 1 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1304 :
1305 1 : const int status = nc_put_att_double(cdfid, nZId, CF_SCALE_FACTOR,
1306 : NC_DOUBLE, 1, &dfNewScale);
1307 :
1308 1 : NCDF_ERR(status);
1309 1 : if (status == NC_NOERR)
1310 : {
1311 1 : SetScaleNoUpdate(dfNewScale);
1312 1 : return CE_None;
1313 : }
1314 :
1315 0 : return CE_Failure;
1316 : }
1317 :
1318 0 : SetScaleNoUpdate(dfNewScale);
1319 0 : return CE_None;
1320 : }
1321 :
1322 : /************************************************************************/
1323 : /* SetScaleNoUpdate() */
1324 : /************************************************************************/
1325 22 : void netCDFRasterBand::SetScaleNoUpdate(double dfVal)
1326 : {
1327 22 : m_dfScale = dfVal;
1328 22 : m_bHaveScale = true;
1329 22 : }
1330 :
1331 : /************************************************************************/
1332 : /* GetUnitType() */
1333 : /************************************************************************/
1334 :
1335 21 : const char *netCDFRasterBand::GetUnitType()
1336 :
1337 : {
1338 21 : if (!m_osUnitType.empty())
1339 6 : return m_osUnitType;
1340 :
1341 15 : return GDALRasterBand::GetUnitType();
1342 : }
1343 :
1344 : /************************************************************************/
1345 : /* SetUnitType() */
1346 : /************************************************************************/
1347 :
1348 1 : CPLErr netCDFRasterBand::SetUnitType(const char *pszNewValue)
1349 :
1350 : {
1351 2 : CPLMutexHolderD(&hNCMutex);
1352 :
1353 2 : const std::string osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
1354 :
1355 1 : if (!osUnitType.empty())
1356 : {
1357 : // Write value if in update mode.
1358 1 : if (poDS->GetAccess() == GA_Update)
1359 : {
1360 : // Make sure we are in define mode.
1361 1 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(TRUE);
1362 :
1363 1 : const int status = nc_put_att_text(
1364 : cdfid, nZId, CF_UNITS, osUnitType.size(), osUnitType.c_str());
1365 :
1366 1 : NCDF_ERR(status);
1367 1 : if (status == NC_NOERR)
1368 : {
1369 1 : SetUnitTypeNoUpdate(pszNewValue);
1370 1 : return CE_None;
1371 : }
1372 :
1373 0 : return CE_Failure;
1374 : }
1375 : }
1376 :
1377 0 : SetUnitTypeNoUpdate(pszNewValue);
1378 :
1379 0 : return CE_None;
1380 : }
1381 :
1382 : /************************************************************************/
1383 : /* SetUnitTypeNoUpdate() */
1384 : /************************************************************************/
1385 :
1386 452 : void netCDFRasterBand::SetUnitTypeNoUpdate(const char *pszNewValue)
1387 : {
1388 452 : m_osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
1389 452 : }
1390 :
1391 : /************************************************************************/
1392 : /* GetNoDataValue() */
1393 : /************************************************************************/
1394 :
1395 151 : double netCDFRasterBand::GetNoDataValue(int *pbSuccess)
1396 :
1397 : {
1398 151 : if (m_bNoDataSetAsInt64)
1399 : {
1400 0 : if (pbSuccess)
1401 0 : *pbSuccess = TRUE;
1402 0 : return GDALGetNoDataValueCastToDouble(m_nNodataValueInt64);
1403 : }
1404 :
1405 151 : if (m_bNoDataSetAsUInt64)
1406 : {
1407 0 : if (pbSuccess)
1408 0 : *pbSuccess = TRUE;
1409 0 : return GDALGetNoDataValueCastToDouble(m_nNodataValueUInt64);
1410 : }
1411 :
1412 151 : if (m_bNoDataSet)
1413 : {
1414 117 : if (pbSuccess)
1415 101 : *pbSuccess = TRUE;
1416 117 : return m_dfNoDataValue;
1417 : }
1418 :
1419 34 : return GDALPamRasterBand::GetNoDataValue(pbSuccess);
1420 : }
1421 :
1422 : /************************************************************************/
1423 : /* GetNoDataValueAsInt64() */
1424 : /************************************************************************/
1425 :
1426 4 : int64_t netCDFRasterBand::GetNoDataValueAsInt64(int *pbSuccess)
1427 :
1428 : {
1429 4 : if (m_bNoDataSetAsInt64)
1430 : {
1431 4 : if (pbSuccess)
1432 4 : *pbSuccess = TRUE;
1433 :
1434 4 : return m_nNodataValueInt64;
1435 : }
1436 :
1437 0 : return GDALPamRasterBand::GetNoDataValueAsInt64(pbSuccess);
1438 : }
1439 :
1440 : /************************************************************************/
1441 : /* GetNoDataValueAsUInt64() */
1442 : /************************************************************************/
1443 :
1444 4 : uint64_t netCDFRasterBand::GetNoDataValueAsUInt64(int *pbSuccess)
1445 :
1446 : {
1447 4 : if (m_bNoDataSetAsUInt64)
1448 : {
1449 4 : if (pbSuccess)
1450 4 : *pbSuccess = TRUE;
1451 :
1452 4 : return m_nNodataValueUInt64;
1453 : }
1454 :
1455 0 : return GDALPamRasterBand::GetNoDataValueAsUInt64(pbSuccess);
1456 : }
1457 :
1458 : /************************************************************************/
1459 : /* SetNoDataValue() */
1460 : /************************************************************************/
1461 :
1462 133 : CPLErr netCDFRasterBand::SetNoDataValue(double dfNoData)
1463 :
1464 : {
1465 266 : CPLMutexHolderD(&hNCMutex);
1466 :
1467 : // If already set to new value, don't do anything.
1468 133 : if (m_bNoDataSet && CPLIsEqual(dfNoData, m_dfNoDataValue))
1469 19 : return CE_None;
1470 :
1471 : // Write value if in update mode.
1472 114 : if (poDS->GetAccess() == GA_Update)
1473 : {
1474 : // netcdf-4 does not allow to set _FillValue after leaving define mode,
1475 : // but it is ok if variable has not been written to, so only print
1476 : // debug. See bug #4484.
1477 124 : if (m_bNoDataSet &&
1478 10 : !reinterpret_cast<netCDFDataset *>(poDS)->GetDefineMode())
1479 : {
1480 0 : CPLDebug("GDAL_netCDF",
1481 : "Setting NoDataValue to %.18g (previously set to %.18g) "
1482 : "but file is no longer in define mode (id #%d, band #%d)",
1483 : dfNoData, m_dfNoDataValue, cdfid, nBand);
1484 : }
1485 : #ifdef NCDF_DEBUG
1486 : else
1487 : {
1488 : CPLDebug("GDAL_netCDF",
1489 : "Setting NoDataValue to %.18g (id #%d, band #%d)",
1490 : dfNoData, cdfid, nBand);
1491 : }
1492 : #endif
1493 : // Make sure we are in define mode.
1494 114 : reinterpret_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1495 :
1496 : int status;
1497 114 : if (eDataType == GDT_Byte)
1498 : {
1499 5 : if (bSignedData)
1500 : {
1501 0 : signed char cNoDataValue = static_cast<signed char>(dfNoData);
1502 0 : status = nc_put_att_schar(cdfid, nZId, _FillValue, nc_datatype,
1503 : 1, &cNoDataValue);
1504 : }
1505 : else
1506 : {
1507 5 : const unsigned char ucNoDataValue =
1508 5 : static_cast<unsigned char>(dfNoData);
1509 5 : status = nc_put_att_uchar(cdfid, nZId, _FillValue, nc_datatype,
1510 : 1, &ucNoDataValue);
1511 : }
1512 : }
1513 109 : else if (eDataType == GDT_Int16)
1514 : {
1515 14 : short nsNoDataValue = static_cast<short>(dfNoData);
1516 14 : status = nc_put_att_short(cdfid, nZId, _FillValue, nc_datatype, 1,
1517 : &nsNoDataValue);
1518 : }
1519 95 : else if (eDataType == GDT_Int32)
1520 : {
1521 27 : int nNoDataValue = static_cast<int>(dfNoData);
1522 27 : status = nc_put_att_int(cdfid, nZId, _FillValue, nc_datatype, 1,
1523 : &nNoDataValue);
1524 : }
1525 68 : else if (eDataType == GDT_Float32)
1526 : {
1527 31 : float fNoDataValue = static_cast<float>(dfNoData);
1528 31 : status = nc_put_att_float(cdfid, nZId, _FillValue, nc_datatype, 1,
1529 : &fNoDataValue);
1530 : }
1531 37 : else if (eDataType == GDT_UInt16 &&
1532 6 : reinterpret_cast<netCDFDataset *>(poDS)->eFormat ==
1533 : NCDF_FORMAT_NC4)
1534 : {
1535 6 : unsigned short usNoDataValue =
1536 6 : static_cast<unsigned short>(dfNoData);
1537 6 : status = nc_put_att_ushort(cdfid, nZId, _FillValue, nc_datatype, 1,
1538 6 : &usNoDataValue);
1539 : }
1540 31 : else if (eDataType == GDT_UInt32 &&
1541 7 : reinterpret_cast<netCDFDataset *>(poDS)->eFormat ==
1542 : NCDF_FORMAT_NC4)
1543 : {
1544 7 : unsigned int unNoDataValue = static_cast<unsigned int>(dfNoData);
1545 7 : status = nc_put_att_uint(cdfid, nZId, _FillValue, nc_datatype, 1,
1546 7 : &unNoDataValue);
1547 : }
1548 : else
1549 : {
1550 24 : status = nc_put_att_double(cdfid, nZId, _FillValue, nc_datatype, 1,
1551 : &dfNoData);
1552 : }
1553 :
1554 114 : NCDF_ERR(status);
1555 :
1556 : // Update status if write worked.
1557 114 : if (status == NC_NOERR)
1558 : {
1559 114 : SetNoDataValueNoUpdate(dfNoData);
1560 114 : return CE_None;
1561 : }
1562 :
1563 0 : return CE_Failure;
1564 : }
1565 :
1566 0 : SetNoDataValueNoUpdate(dfNoData);
1567 0 : return CE_None;
1568 : }
1569 :
1570 : /************************************************************************/
1571 : /* SetNoDataValueNoUpdate() */
1572 : /************************************************************************/
1573 :
1574 423 : void netCDFRasterBand::SetNoDataValueNoUpdate(double dfNoData)
1575 : {
1576 423 : m_dfNoDataValue = dfNoData;
1577 423 : m_bNoDataSet = true;
1578 423 : m_bNoDataSetAsInt64 = false;
1579 423 : m_bNoDataSetAsUInt64 = false;
1580 423 : }
1581 :
1582 : /************************************************************************/
1583 : /* SetNoDataValueAsInt64() */
1584 : /************************************************************************/
1585 :
1586 3 : CPLErr netCDFRasterBand::SetNoDataValueAsInt64(int64_t nNoData)
1587 :
1588 : {
1589 6 : CPLMutexHolderD(&hNCMutex);
1590 :
1591 : // If already set to new value, don't do anything.
1592 3 : if (m_bNoDataSetAsInt64 && nNoData == m_nNodataValueInt64)
1593 0 : return CE_None;
1594 :
1595 : // Write value if in update mode.
1596 3 : if (poDS->GetAccess() == GA_Update)
1597 : {
1598 : // netcdf-4 does not allow to set _FillValue after leaving define mode,
1599 : // but it is ok if variable has not been written to, so only print
1600 : // debug. See bug #4484.
1601 3 : if (m_bNoDataSetAsInt64 &&
1602 0 : !reinterpret_cast<netCDFDataset *>(poDS)->GetDefineMode())
1603 : {
1604 0 : CPLDebug("GDAL_netCDF",
1605 : "Setting NoDataValue to " CPL_FRMT_GIB
1606 : " (previously set to " CPL_FRMT_GIB ") "
1607 : "but file is no longer in define mode (id #%d, band #%d)",
1608 : static_cast<GIntBig>(nNoData),
1609 0 : static_cast<GIntBig>(m_nNodataValueInt64), cdfid, nBand);
1610 : }
1611 : #ifdef NCDF_DEBUG
1612 : else
1613 : {
1614 : CPLDebug("GDAL_netCDF",
1615 : "Setting NoDataValue to " CPL_FRMT_GIB
1616 : " (id #%d, band #%d)",
1617 : static_cast<GIntBig>(nNoData), cdfid, nBand);
1618 : }
1619 : #endif
1620 : // Make sure we are in define mode.
1621 3 : reinterpret_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1622 :
1623 : int status;
1624 3 : if (eDataType == GDT_Int64 &&
1625 3 : reinterpret_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
1626 : {
1627 3 : long long tmp = static_cast<long long>(nNoData);
1628 3 : status = nc_put_att_longlong(cdfid, nZId, _FillValue, nc_datatype,
1629 3 : 1, &tmp);
1630 : }
1631 : else
1632 : {
1633 0 : double dfNoData = static_cast<double>(nNoData);
1634 0 : status = nc_put_att_double(cdfid, nZId, _FillValue, nc_datatype, 1,
1635 : &dfNoData);
1636 : }
1637 :
1638 3 : NCDF_ERR(status);
1639 :
1640 : // Update status if write worked.
1641 3 : if (status == NC_NOERR)
1642 : {
1643 3 : SetNoDataValueNoUpdate(nNoData);
1644 3 : return CE_None;
1645 : }
1646 :
1647 0 : return CE_Failure;
1648 : }
1649 :
1650 0 : SetNoDataValueNoUpdate(nNoData);
1651 0 : return CE_None;
1652 : }
1653 :
1654 : /************************************************************************/
1655 : /* SetNoDataValueNoUpdate() */
1656 : /************************************************************************/
1657 :
1658 13 : void netCDFRasterBand::SetNoDataValueNoUpdate(int64_t nNoData)
1659 : {
1660 13 : m_nNodataValueInt64 = nNoData;
1661 13 : m_bNoDataSet = false;
1662 13 : m_bNoDataSetAsInt64 = true;
1663 13 : m_bNoDataSetAsUInt64 = false;
1664 13 : }
1665 :
1666 : /************************************************************************/
1667 : /* SetNoDataValueAsUInt64() */
1668 : /************************************************************************/
1669 :
1670 3 : CPLErr netCDFRasterBand::SetNoDataValueAsUInt64(uint64_t nNoData)
1671 :
1672 : {
1673 6 : CPLMutexHolderD(&hNCMutex);
1674 :
1675 : // If already set to new value, don't do anything.
1676 3 : if (m_bNoDataSetAsUInt64 && nNoData == m_nNodataValueUInt64)
1677 0 : return CE_None;
1678 :
1679 : // Write value if in update mode.
1680 3 : if (poDS->GetAccess() == GA_Update)
1681 : {
1682 : // netcdf-4 does not allow to set _FillValue after leaving define mode,
1683 : // but it is ok if variable has not been written to, so only print
1684 : // debug. See bug #4484.
1685 3 : if (m_bNoDataSetAsUInt64 &&
1686 0 : !reinterpret_cast<netCDFDataset *>(poDS)->GetDefineMode())
1687 : {
1688 0 : CPLDebug("GDAL_netCDF",
1689 : "Setting NoDataValue to " CPL_FRMT_GUIB
1690 : " (previously set to " CPL_FRMT_GUIB ") "
1691 : "but file is no longer in define mode (id #%d, band #%d)",
1692 : static_cast<GUIntBig>(nNoData),
1693 0 : static_cast<GUIntBig>(m_nNodataValueUInt64), cdfid, nBand);
1694 : }
1695 : #ifdef NCDF_DEBUG
1696 : else
1697 : {
1698 : CPLDebug("GDAL_netCDF",
1699 : "Setting NoDataValue to " CPL_FRMT_GUIB
1700 : " (id #%d, band #%d)",
1701 : static_cast<GUIntBig>(nNoData), cdfid, nBand);
1702 : }
1703 : #endif
1704 : // Make sure we are in define mode.
1705 3 : reinterpret_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1706 :
1707 : int status;
1708 3 : if (eDataType == GDT_UInt64 &&
1709 3 : reinterpret_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
1710 : {
1711 3 : unsigned long long tmp = static_cast<long long>(nNoData);
1712 3 : status = nc_put_att_ulonglong(cdfid, nZId, _FillValue, nc_datatype,
1713 3 : 1, &tmp);
1714 : }
1715 : else
1716 : {
1717 0 : double dfNoData = static_cast<double>(nNoData);
1718 0 : status = nc_put_att_double(cdfid, nZId, _FillValue, nc_datatype, 1,
1719 : &dfNoData);
1720 : }
1721 :
1722 3 : NCDF_ERR(status);
1723 :
1724 : // Update status if write worked.
1725 3 : if (status == NC_NOERR)
1726 : {
1727 3 : SetNoDataValueNoUpdate(nNoData);
1728 3 : return CE_None;
1729 : }
1730 :
1731 0 : return CE_Failure;
1732 : }
1733 :
1734 0 : SetNoDataValueNoUpdate(nNoData);
1735 0 : return CE_None;
1736 : }
1737 :
1738 : /************************************************************************/
1739 : /* SetNoDataValueNoUpdate() */
1740 : /************************************************************************/
1741 :
1742 12 : void netCDFRasterBand::SetNoDataValueNoUpdate(uint64_t nNoData)
1743 : {
1744 12 : m_nNodataValueUInt64 = nNoData;
1745 12 : m_bNoDataSet = false;
1746 12 : m_bNoDataSetAsInt64 = false;
1747 12 : m_bNoDataSetAsUInt64 = true;
1748 12 : }
1749 :
1750 : /************************************************************************/
1751 : /* DeleteNoDataValue() */
1752 : /************************************************************************/
1753 :
1754 : #ifdef notdef
1755 : CPLErr netCDFRasterBand::DeleteNoDataValue()
1756 :
1757 : {
1758 : CPLMutexHolderD(&hNCMutex);
1759 :
1760 : if (!bNoDataSet)
1761 : return CE_None;
1762 :
1763 : // Write value if in update mode.
1764 : if (poDS->GetAccess() == GA_Update)
1765 : {
1766 : // Make sure we are in define mode.
1767 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1768 :
1769 : status = nc_del_att(cdfid, nZId, _FillValue);
1770 :
1771 : NCDF_ERR(status);
1772 :
1773 : // Update status if write worked.
1774 : if (status == NC_NOERR)
1775 : {
1776 : dfNoDataValue = 0.0;
1777 : bNoDataSet = false;
1778 : return CE_None;
1779 : }
1780 :
1781 : return CE_Failure;
1782 : }
1783 :
1784 : dfNoDataValue = 0.0;
1785 : bNoDataSet = false;
1786 : return CE_None;
1787 : }
1788 : #endif
1789 :
1790 : /************************************************************************/
1791 : /* SerializeToXML() */
1792 : /************************************************************************/
1793 :
1794 31 : CPLXMLNode *netCDFRasterBand::SerializeToXML(const char * /* pszUnused */)
1795 : {
1796 : // Overridden from GDALPamDataset to add only band histogram
1797 : // and statistics. See bug #4244.
1798 31 : if (psPam == nullptr)
1799 0 : return nullptr;
1800 :
1801 : // Setup root node and attributes.
1802 : CPLXMLNode *psTree =
1803 31 : CPLCreateXMLNode(nullptr, CXT_Element, "PAMRasterBand");
1804 :
1805 31 : if (GetBand() > 0)
1806 : {
1807 62 : CPLString oFmt;
1808 31 : CPLSetXMLValue(psTree, "#band", oFmt.Printf("%d", GetBand()));
1809 : }
1810 :
1811 : // Histograms.
1812 31 : if (psPam->psSavedHistograms != nullptr)
1813 1 : CPLAddXMLChild(psTree, CPLCloneXMLTree(psPam->psSavedHistograms));
1814 :
1815 : // Metadata (statistics only).
1816 31 : GDALMultiDomainMetadata oMDMDStats;
1817 31 : const char *papszMDStats[] = {"STATISTICS_MINIMUM", "STATISTICS_MAXIMUM",
1818 : "STATISTICS_MEAN", "STATISTICS_STDDEV",
1819 : nullptr};
1820 155 : for (int i = 0; i < CSLCount(papszMDStats); i++)
1821 : {
1822 124 : const char *pszMDI = GetMetadataItem(papszMDStats[i]);
1823 124 : if (pszMDI)
1824 4 : oMDMDStats.SetMetadataItem(papszMDStats[i], pszMDI);
1825 : }
1826 31 : CPLXMLNode *psMD = oMDMDStats.Serialize();
1827 :
1828 31 : if (psMD != nullptr)
1829 : {
1830 1 : if (psMD->psChild == nullptr)
1831 0 : CPLDestroyXMLNode(psMD);
1832 : else
1833 1 : CPLAddXMLChild(psTree, psMD);
1834 : }
1835 :
1836 : // We don't want to return anything if we had no metadata to attach.
1837 31 : if (psTree->psChild == nullptr || psTree->psChild->psNext == nullptr)
1838 : {
1839 29 : CPLDestroyXMLNode(psTree);
1840 29 : psTree = nullptr;
1841 : }
1842 :
1843 31 : return psTree;
1844 : }
1845 :
1846 : /************************************************************************/
1847 : /* Get1DVariableIndexedByDimension() */
1848 : /************************************************************************/
1849 :
1850 70 : static int Get1DVariableIndexedByDimension(int cdfid, int nDimId,
1851 : const char *pszDimName,
1852 : bool bVerboseError, int *pnGroupID)
1853 : {
1854 70 : *pnGroupID = -1;
1855 70 : int nVarID = -1;
1856 : // First try to find a variable whose name is identical to the dimension
1857 : // name, and check that it is indeed indexed by this dimension
1858 70 : if (NCDFResolveVar(cdfid, pszDimName, pnGroupID, &nVarID) == CE_None)
1859 : {
1860 58 : int nDimCountOfVariable = 0;
1861 58 : nc_inq_varndims(*pnGroupID, nVarID, &nDimCountOfVariable);
1862 58 : if (nDimCountOfVariable == 1)
1863 : {
1864 58 : int nDimIdOfVariable = -1;
1865 58 : nc_inq_vardimid(*pnGroupID, nVarID, &nDimIdOfVariable);
1866 58 : if (nDimIdOfVariable == nDimId)
1867 : {
1868 58 : return nVarID;
1869 : }
1870 : }
1871 : }
1872 :
1873 : // Otherwise iterate over the variables to find potential candidates
1874 : // TODO: should be modified to search also in other groups using the same
1875 : // logic than in NCDFResolveVar(), but maybe not needed if it's a
1876 : // very rare case? and I think this is not CF compliant.
1877 12 : int nvars = 0;
1878 12 : CPL_IGNORE_RET_VAL(nc_inq(cdfid, nullptr, &nvars, nullptr, nullptr));
1879 :
1880 12 : int nCountCandidateVars = 0;
1881 12 : int nCandidateVarID = -1;
1882 53 : for (int k = 0; k < nvars; k++)
1883 : {
1884 41 : int nDimCountOfVariable = 0;
1885 41 : nc_inq_varndims(cdfid, k, &nDimCountOfVariable);
1886 41 : if (nDimCountOfVariable == 1)
1887 : {
1888 23 : int nDimIdOfVariable = -1;
1889 23 : nc_inq_vardimid(cdfid, k, &nDimIdOfVariable);
1890 23 : if (nDimIdOfVariable == nDimId)
1891 : {
1892 5 : nCountCandidateVars++;
1893 5 : nCandidateVarID = k;
1894 : }
1895 : }
1896 : }
1897 12 : if (nCountCandidateVars > 1)
1898 : {
1899 1 : if (bVerboseError)
1900 : {
1901 1 : CPLError(CE_Warning, CPLE_AppDefined,
1902 : "Several 1D variables are indexed by dimension %s",
1903 : pszDimName);
1904 : }
1905 1 : *pnGroupID = -1;
1906 1 : return -1;
1907 : }
1908 11 : else if (nCandidateVarID < 0)
1909 : {
1910 8 : if (bVerboseError)
1911 : {
1912 8 : CPLError(CE_Warning, CPLE_AppDefined,
1913 : "No 1D variable is indexed by dimension %s", pszDimName);
1914 : }
1915 : }
1916 11 : *pnGroupID = cdfid;
1917 11 : return nCandidateVarID;
1918 : }
1919 :
1920 : /************************************************************************/
1921 : /* CreateMetadataFromAttributes() */
1922 : /************************************************************************/
1923 :
1924 451 : void netCDFRasterBand::CreateMetadataFromAttributes()
1925 : {
1926 451 : char szVarName[NC_MAX_NAME + 1] = {};
1927 451 : int status = nc_inq_varname(cdfid, nZId, szVarName);
1928 451 : NCDF_ERR(status);
1929 :
1930 451 : GDALPamRasterBand::SetMetadataItem("NETCDF_VARNAME", szVarName);
1931 :
1932 : // Get attribute metadata.
1933 451 : int nAtt = 0;
1934 451 : nc_inq_varnatts(cdfid, nZId, &nAtt);
1935 :
1936 1879 : for (int i = 0; i < nAtt; i++)
1937 : {
1938 1428 : char szMetaName[NC_MAX_NAME + 1] = {};
1939 1428 : status = nc_inq_attname(cdfid, nZId, i, szMetaName);
1940 1428 : if (status != NC_NOERR)
1941 13 : continue;
1942 :
1943 1428 : if (GDALPamRasterBand::GetMetadataItem(szMetaName) != nullptr)
1944 : {
1945 13 : continue;
1946 : }
1947 :
1948 1415 : char *pszMetaValue = nullptr;
1949 1415 : if (NCDFGetAttr(cdfid, nZId, szMetaName, &pszMetaValue) == CE_None)
1950 : {
1951 1415 : GDALPamRasterBand::SetMetadataItem(szMetaName, pszMetaValue);
1952 : }
1953 : else
1954 : {
1955 0 : CPLDebug("GDAL_netCDF", "invalid Band metadata %s", szMetaName);
1956 : }
1957 :
1958 1415 : if (pszMetaValue)
1959 : {
1960 1415 : CPLFree(pszMetaValue);
1961 1415 : pszMetaValue = nullptr;
1962 : }
1963 : }
1964 451 : }
1965 :
1966 : /************************************************************************/
1967 : /* CreateMetadataFromOtherVars() */
1968 : /************************************************************************/
1969 :
1970 48 : void netCDFRasterBand::CreateMetadataFromOtherVars()
1971 :
1972 : {
1973 48 : CPLAssert(!m_bCreateMetadataFromOtherVarsDone);
1974 48 : m_bCreateMetadataFromOtherVarsDone = true;
1975 :
1976 48 : netCDFDataset *l_poDS = reinterpret_cast<netCDFDataset *>(poDS);
1977 :
1978 : // Compute all dimensions from Band number and save in Metadata.
1979 48 : int nd = 0;
1980 48 : nc_inq_varndims(cdfid, nZId, &nd);
1981 : // Compute multidimention band position.
1982 : //
1983 : // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
1984 : // if Data[2,3,4,x,y]
1985 : //
1986 : // BandPos0 = (nBand) / (3*4)
1987 : // BandPos1 = (nBand - BandPos0*(3*4)) / (4)
1988 : // BandPos2 = (nBand - BandPos0*(3*4)) % (4)
1989 :
1990 48 : int Sum = 1;
1991 48 : if (nd == 3)
1992 : {
1993 5 : Sum *= panBandZLev[0];
1994 : }
1995 :
1996 : // Loop over non-spatial dimensions.
1997 48 : int Taken = 0;
1998 :
1999 88 : for (int i = 0; i < nd - 2; i++)
2000 : {
2001 : int result;
2002 40 : if (i != nd - 2 - 1)
2003 : {
2004 18 : Sum = 1;
2005 37 : for (int j = i + 1; j < nd - 2; j++)
2006 : {
2007 19 : Sum *= panBandZLev[j];
2008 : }
2009 18 : result = static_cast<int>((nLevel - Taken) / Sum);
2010 : }
2011 : else
2012 : {
2013 22 : result = static_cast<int>((nLevel - Taken) % Sum);
2014 : }
2015 :
2016 40 : char szName[NC_MAX_NAME + 1] = {};
2017 40 : snprintf(szName, sizeof(szName), "%s",
2018 40 : l_poDS->papszDimName[l_poDS->m_anDimIds[panBandZPos[i]]]);
2019 :
2020 : char szMetaName[NC_MAX_NAME + 1 + 32];
2021 40 : snprintf(szMetaName, sizeof(szMetaName), "NETCDF_DIM_%s", szName);
2022 :
2023 40 : const int nGroupID = l_poDS->m_anExtraDimGroupIds[i];
2024 40 : const int nVarID = l_poDS->m_anExtraDimVarIds[i];
2025 40 : if (nVarID < 0)
2026 : {
2027 2 : GDALPamRasterBand::SetMetadataItem(szMetaName,
2028 : CPLSPrintf("%d", result + 1));
2029 : }
2030 : else
2031 : {
2032 : // TODO: Make sure all the status checks make sense.
2033 :
2034 38 : nc_type nVarType = NC_NAT;
2035 38 : /* status = */ nc_inq_vartype(nGroupID, nVarID, &nVarType);
2036 :
2037 38 : int nDims = 0;
2038 38 : /* status = */ nc_inq_varndims(nGroupID, nVarID, &nDims);
2039 :
2040 38 : char szMetaTemp[256] = {};
2041 38 : if (nDims == 1)
2042 : {
2043 38 : size_t count[1] = {1};
2044 38 : size_t start[1] = {static_cast<size_t>(result)};
2045 :
2046 38 : switch (nVarType)
2047 : {
2048 0 : case NC_BYTE:
2049 : // TODO: Check for signed/unsigned byte.
2050 : signed char cData;
2051 0 : /* status = */ nc_get_vara_schar(nGroupID, nVarID,
2052 : start, count, &cData);
2053 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", cData);
2054 0 : break;
2055 0 : case NC_SHORT:
2056 : short sData;
2057 0 : /* status = */ nc_get_vara_short(nGroupID, nVarID,
2058 : start, count, &sData);
2059 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", sData);
2060 0 : break;
2061 19 : case NC_INT:
2062 : {
2063 : int nData;
2064 19 : /* status = */ nc_get_vara_int(nGroupID, nVarID, start,
2065 : count, &nData);
2066 19 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", nData);
2067 19 : break;
2068 : }
2069 0 : case NC_FLOAT:
2070 : float fData;
2071 0 : /* status = */ nc_get_vara_float(nGroupID, nVarID,
2072 : start, count, &fData);
2073 0 : CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.8g",
2074 : fData);
2075 0 : break;
2076 18 : case NC_DOUBLE:
2077 : double dfData;
2078 18 : /* status = */ nc_get_vara_double(
2079 : nGroupID, nVarID, start, count, &dfData);
2080 18 : CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.16g",
2081 : dfData);
2082 18 : break;
2083 0 : case NC_UBYTE:
2084 : unsigned char ucData;
2085 0 : /* status = */ nc_get_vara_uchar(nGroupID, nVarID,
2086 : start, count, &ucData);
2087 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", ucData);
2088 0 : break;
2089 0 : case NC_USHORT:
2090 : unsigned short usData;
2091 0 : /* status = */ nc_get_vara_ushort(
2092 : nGroupID, nVarID, start, count, &usData);
2093 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", usData);
2094 0 : break;
2095 0 : case NC_UINT:
2096 : {
2097 : unsigned int unData;
2098 0 : /* status = */ nc_get_vara_uint(nGroupID, nVarID, start,
2099 : count, &unData);
2100 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", unData);
2101 0 : break;
2102 : }
2103 1 : case NC_INT64:
2104 : {
2105 : long long nData;
2106 1 : /* status = */ nc_get_vara_longlong(
2107 : nGroupID, nVarID, start, count, &nData);
2108 1 : snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GIB,
2109 : nData);
2110 1 : break;
2111 : }
2112 0 : case NC_UINT64:
2113 : {
2114 : unsigned long long unData;
2115 0 : /* status = */ nc_get_vara_ulonglong(
2116 : nGroupID, nVarID, start, count, &unData);
2117 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GUIB,
2118 : unData);
2119 0 : break;
2120 : }
2121 0 : default:
2122 0 : CPLDebug("GDAL_netCDF", "invalid dim %s, type=%d",
2123 : szMetaTemp, nVarType);
2124 0 : break;
2125 : }
2126 : }
2127 : else
2128 : {
2129 0 : snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", result + 1);
2130 : }
2131 :
2132 : // Save dimension value.
2133 : // NOTE: removed #original_units as not part of CF-1.
2134 :
2135 38 : GDALPamRasterBand::SetMetadataItem(szMetaName, szMetaTemp);
2136 : }
2137 :
2138 : // Avoid int32 overflow. Perhaps something more sensible to do here ?
2139 40 : if (result > 0 && Sum > INT_MAX / result)
2140 0 : break;
2141 40 : if (Taken > INT_MAX - result * Sum)
2142 0 : break;
2143 :
2144 40 : Taken += result * Sum;
2145 : } // End loop non-spatial dimensions.
2146 48 : }
2147 :
2148 : /************************************************************************/
2149 : /* CheckData() */
2150 : /************************************************************************/
2151 : template <class T>
2152 5130 : void netCDFRasterBand::CheckData(void *pImage, void *pImageNC,
2153 : size_t nTmpBlockXSize, size_t nTmpBlockYSize,
2154 : bool bCheckIsNan)
2155 : {
2156 5130 : CPLAssert(pImage != nullptr && pImageNC != nullptr);
2157 :
2158 : // If this block is not a full block (in the x axis), we need to re-arrange
2159 : // the data this is because partial blocks are not arranged the same way in
2160 : // netcdf and gdal.
2161 5130 : if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
2162 : {
2163 6 : T *ptrWrite = static_cast<T *>(pImage);
2164 6 : T *ptrRead = static_cast<T *>(pImageNC);
2165 29 : for (size_t j = 0; j < nTmpBlockYSize;
2166 23 : j++, ptrWrite += nBlockXSize, ptrRead += nTmpBlockXSize)
2167 : {
2168 23 : memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T));
2169 : }
2170 : }
2171 :
2172 : // Is valid data checking needed or requested?
2173 5130 : if (bValidRangeValid || bCheckIsNan)
2174 : {
2175 1265 : T *ptrImage = static_cast<T *>(pImage);
2176 2584 : for (size_t j = 0; j < nTmpBlockYSize; j++)
2177 : {
2178 : // k moves along the gdal block, skipping the out-of-range pixels.
2179 1319 : size_t k = j * nBlockXSize;
2180 96938 : for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
2181 : {
2182 : // Check for nodata and nan.
2183 95619 : if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
2184 6301 : continue;
2185 89318 : if (bCheckIsNan && CPLIsNan((double)ptrImage[k]))
2186 : {
2187 5737 : ptrImage[k] = (T)m_dfNoDataValue;
2188 5737 : continue;
2189 : }
2190 : // Check for valid_range.
2191 83581 : if (bValidRangeValid)
2192 : {
2193 40986 : if (((adfValidRange[0] != m_dfNoDataValue) &&
2194 40986 : (ptrImage[k] < (T)adfValidRange[0])) ||
2195 40983 : ((adfValidRange[1] != m_dfNoDataValue) &&
2196 40983 : (ptrImage[k] > (T)adfValidRange[1])))
2197 : {
2198 4 : ptrImage[k] = (T)m_dfNoDataValue;
2199 : }
2200 : }
2201 : }
2202 : }
2203 : }
2204 :
2205 : // If minimum longitude is > 180, subtract 360 from all.
2206 : // If not, disable checking for further calls (check just once).
2207 : // Only check first and last block elements since lon must be monotonic.
2208 5130 : const bool bIsSigned = std::numeric_limits<T>::is_signed;
2209 5419 : if (bCheckLongitude && bIsSigned &&
2210 9 : !CPLIsEqual((double)((T *)pImage)[0], m_dfNoDataValue) &&
2211 8 : !CPLIsEqual((double)((T *)pImage)[nTmpBlockXSize - 1],
2212 2714 : m_dfNoDataValue) &&
2213 8 : std::min(((T *)pImage)[0], ((T *)pImage)[nTmpBlockXSize - 1]) > 180.0)
2214 : {
2215 0 : T *ptrImage = static_cast<T *>(pImage);
2216 0 : for (size_t j = 0; j < nTmpBlockYSize; j++)
2217 : {
2218 0 : size_t k = j * nBlockXSize;
2219 0 : for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
2220 : {
2221 0 : if (!CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
2222 0 : ptrImage[k] = static_cast<T>(ptrImage[k] - 360);
2223 : }
2224 : }
2225 : }
2226 : else
2227 : {
2228 5130 : bCheckLongitude = false;
2229 : }
2230 5130 : }
2231 :
2232 : /************************************************************************/
2233 : /* CheckDataCpx() */
2234 : /************************************************************************/
2235 : template <class T>
2236 25 : void netCDFRasterBand::CheckDataCpx(void *pImage, void *pImageNC,
2237 : size_t nTmpBlockXSize,
2238 : size_t nTmpBlockYSize, bool bCheckIsNan)
2239 : {
2240 25 : CPLAssert(pImage != nullptr && pImageNC != nullptr);
2241 :
2242 : // If this block is not a full block (in the x axis), we need to re-arrange
2243 : // the data this is because partial blocks are not arranged the same way in
2244 : // netcdf and gdal.
2245 25 : if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
2246 : {
2247 0 : T *ptrWrite = static_cast<T *>(pImage);
2248 0 : T *ptrRead = static_cast<T *>(pImageNC);
2249 0 : for (size_t j = 0; j < nTmpBlockYSize; j++,
2250 0 : ptrWrite += (2 * nBlockXSize),
2251 0 : ptrRead += (2 * nTmpBlockXSize))
2252 : {
2253 0 : memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T) * 2);
2254 : }
2255 : }
2256 :
2257 : // Is valid data checking needed or requested?
2258 25 : if (bValidRangeValid || bCheckIsNan)
2259 : {
2260 0 : T *ptrImage = static_cast<T *>(pImage);
2261 0 : for (size_t j = 0; j < nTmpBlockYSize; j++)
2262 : {
2263 : // k moves along the gdal block, skipping the out-of-range pixels.
2264 0 : size_t k = 2 * j * nBlockXSize;
2265 0 : for (size_t i = 0; i < (2 * nTmpBlockXSize); i++, k++)
2266 : {
2267 : // Check for nodata and nan.
2268 0 : if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
2269 0 : continue;
2270 0 : if (bCheckIsNan && CPLIsNan((double)ptrImage[k]))
2271 : {
2272 0 : ptrImage[k] = (T)m_dfNoDataValue;
2273 0 : continue;
2274 : }
2275 : // Check for valid_range.
2276 0 : if (bValidRangeValid)
2277 : {
2278 0 : if (((adfValidRange[0] != m_dfNoDataValue) &&
2279 0 : (ptrImage[k] < (T)adfValidRange[0])) ||
2280 0 : ((adfValidRange[1] != m_dfNoDataValue) &&
2281 0 : (ptrImage[k] > (T)adfValidRange[1])))
2282 : {
2283 0 : ptrImage[k] = (T)m_dfNoDataValue;
2284 : }
2285 : }
2286 : }
2287 : }
2288 : }
2289 25 : }
2290 :
2291 : /************************************************************************/
2292 : /* FetchNetcdfChunk() */
2293 : /************************************************************************/
2294 :
2295 5155 : bool netCDFRasterBand::FetchNetcdfChunk(size_t xstart, size_t ystart,
2296 : void *pImage)
2297 : {
2298 5155 : size_t start[MAX_NC_DIMS] = {};
2299 5155 : size_t edge[MAX_NC_DIMS] = {};
2300 :
2301 5155 : start[nBandXPos] = xstart;
2302 5155 : edge[nBandXPos] = nBlockXSize;
2303 5155 : if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
2304 6 : edge[nBandXPos] = nRasterXSize - start[nBandXPos];
2305 5155 : if (nBandYPos >= 0)
2306 : {
2307 5151 : start[nBandYPos] = ystart;
2308 5151 : edge[nBandYPos] = nBlockYSize;
2309 5151 : if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
2310 4 : edge[nBandYPos] = nRasterYSize - start[nBandYPos];
2311 : }
2312 5155 : const size_t nYChunkSize = nBandYPos < 0 ? 1 : edge[nBandYPos];
2313 :
2314 : #ifdef NCDF_DEBUG
2315 : CPLDebug("GDAL_netCDF", "start={%ld,%ld} edge={%ld,%ld} bBottomUp=%d",
2316 : start[nBandXPos], nBandYPos < 0 ? 0 : start[nBandYPos],
2317 : edge[nBandXPos], nYChunkSize, ((netCDFDataset *)poDS)->bBottomUp);
2318 : #endif
2319 :
2320 5155 : int nd = 0;
2321 5155 : nc_inq_varndims(cdfid, nZId, &nd);
2322 5155 : if (nd == 3)
2323 : {
2324 478 : start[panBandZPos[0]] = nLevel; // z
2325 478 : edge[panBandZPos[0]] = 1;
2326 : }
2327 :
2328 : // Compute multidimention band position.
2329 : //
2330 : // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
2331 : // if Data[2,3,4,x,y]
2332 : //
2333 : // BandPos0 = (nBand) / (3*4)
2334 : // BandPos1 = (nBand - (3*4)) / (4)
2335 : // BandPos2 = (nBand - (3*4)) % (4)
2336 5155 : if (nd > 3)
2337 : {
2338 160 : int Sum = -1;
2339 160 : int Taken = 0;
2340 480 : for (int i = 0; i < nd - 2; i++)
2341 : {
2342 320 : if (i != nd - 2 - 1)
2343 : {
2344 160 : Sum = 1;
2345 320 : for (int j = i + 1; j < nd - 2; j++)
2346 : {
2347 160 : Sum *= panBandZLev[j];
2348 : }
2349 160 : start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
2350 160 : edge[panBandZPos[i]] = 1;
2351 : }
2352 : else
2353 : {
2354 160 : start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
2355 160 : edge[panBandZPos[i]] = 1;
2356 : }
2357 320 : Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
2358 : }
2359 : }
2360 :
2361 : // Make sure we are in data mode.
2362 5155 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
2363 :
2364 : // If this block is not a full block in the x axis, we need to
2365 : // re-arrange the data because partial blocks are not arranged the
2366 : // same way in netcdf and gdal, so we first we read the netcdf data at
2367 : // the end of the gdal block buffer then re-arrange rows in CheckData().
2368 5155 : void *pImageNC = pImage;
2369 5155 : if (edge[nBandXPos] != static_cast<size_t>(nBlockXSize))
2370 : {
2371 6 : pImageNC = static_cast<GByte *>(pImage) +
2372 6 : ((static_cast<size_t>(nBlockXSize) * nBlockYSize -
2373 12 : edge[nBandXPos] * nYChunkSize) *
2374 6 : (GDALGetDataTypeSize(eDataType) / 8));
2375 : }
2376 :
2377 : // Read data according to type.
2378 : int status;
2379 5155 : if (eDataType == GDT_Byte)
2380 : {
2381 2403 : if (bSignedData)
2382 : {
2383 0 : status = nc_get_vara_schar(cdfid, nZId, start, edge,
2384 : static_cast<signed char *>(pImageNC));
2385 0 : if (status == NC_NOERR)
2386 0 : CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
2387 : nYChunkSize, false);
2388 : }
2389 : else
2390 : {
2391 2403 : status = nc_get_vara_uchar(cdfid, nZId, start, edge,
2392 : static_cast<unsigned char *>(pImageNC));
2393 2403 : if (status == NC_NOERR)
2394 2403 : CheckData<unsigned char>(pImage, pImageNC, edge[nBandXPos],
2395 : nYChunkSize, false);
2396 : }
2397 : }
2398 2752 : else if (eDataType == GDT_Int8)
2399 : {
2400 60 : status = nc_get_vara_schar(cdfid, nZId, start, edge,
2401 : static_cast<signed char *>(pImageNC));
2402 60 : if (status == NC_NOERR)
2403 60 : CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
2404 : nYChunkSize, false);
2405 : }
2406 2692 : else if (nc_datatype == NC_SHORT)
2407 : {
2408 465 : status = nc_get_vara_short(cdfid, nZId, start, edge,
2409 : static_cast<short *>(pImageNC));
2410 465 : if (status == NC_NOERR)
2411 : {
2412 465 : if (eDataType == GDT_Int16)
2413 : {
2414 462 : CheckData<GInt16>(pImage, pImageNC, edge[nBandXPos],
2415 : nYChunkSize, false);
2416 : }
2417 : else
2418 : {
2419 3 : CheckData<GUInt16>(pImage, pImageNC, edge[nBandXPos],
2420 : nYChunkSize, false);
2421 : }
2422 : }
2423 : }
2424 2227 : else if (eDataType == GDT_Int32)
2425 : {
2426 : #if SIZEOF_UNSIGNED_LONG == 4
2427 : status = nc_get_vara_long(cdfid, nZId, start, edge,
2428 : static_cast<long *>(pImageNC));
2429 : if (status == NC_NOERR)
2430 : CheckData<long>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2431 : false);
2432 : #else
2433 912 : status = nc_get_vara_int(cdfid, nZId, start, edge,
2434 : static_cast<int *>(pImageNC));
2435 912 : if (status == NC_NOERR)
2436 912 : CheckData<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2437 : false);
2438 : #endif
2439 : }
2440 1315 : else if (eDataType == GDT_Float32)
2441 : {
2442 1178 : status = nc_get_vara_float(cdfid, nZId, start, edge,
2443 : static_cast<float *>(pImageNC));
2444 1178 : if (status == NC_NOERR)
2445 1178 : CheckData<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2446 : true);
2447 : }
2448 137 : else if (eDataType == GDT_Float64)
2449 : {
2450 86 : status = nc_get_vara_double(cdfid, nZId, start, edge,
2451 : static_cast<double *>(pImageNC));
2452 86 : if (status == NC_NOERR)
2453 86 : CheckData<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2454 : true);
2455 : }
2456 51 : else if (eDataType == GDT_UInt16)
2457 : {
2458 6 : status = nc_get_vara_ushort(cdfid, nZId, start, edge,
2459 : static_cast<unsigned short *>(pImageNC));
2460 6 : if (status == NC_NOERR)
2461 6 : CheckData<unsigned short>(pImage, pImageNC, edge[nBandXPos],
2462 : nYChunkSize, false);
2463 : }
2464 45 : else if (eDataType == GDT_UInt32)
2465 : {
2466 6 : status = nc_get_vara_uint(cdfid, nZId, start, edge,
2467 : static_cast<unsigned int *>(pImageNC));
2468 6 : if (status == NC_NOERR)
2469 6 : CheckData<unsigned int>(pImage, pImageNC, edge[nBandXPos],
2470 : nYChunkSize, false);
2471 : }
2472 39 : else if (eDataType == GDT_Int64)
2473 : {
2474 7 : status = nc_get_vara_longlong(cdfid, nZId, start, edge,
2475 : static_cast<long long *>(pImageNC));
2476 7 : if (status == NC_NOERR)
2477 7 : CheckData<std::int64_t>(pImage, pImageNC, edge[nBandXPos],
2478 : nYChunkSize, false);
2479 : }
2480 32 : else if (eDataType == GDT_UInt64)
2481 : {
2482 : status =
2483 7 : nc_get_vara_ulonglong(cdfid, nZId, start, edge,
2484 : static_cast<unsigned long long *>(pImageNC));
2485 7 : if (status == NC_NOERR)
2486 7 : CheckData<std::uint64_t>(pImage, pImageNC, edge[nBandXPos],
2487 : nYChunkSize, false);
2488 : }
2489 25 : else if (eDataType == GDT_CInt16)
2490 : {
2491 0 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2492 0 : if (status == NC_NOERR)
2493 0 : CheckDataCpx<short>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2494 : false);
2495 : }
2496 25 : else if (eDataType == GDT_CInt32)
2497 : {
2498 0 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2499 0 : if (status == NC_NOERR)
2500 0 : CheckDataCpx<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2501 : false);
2502 : }
2503 25 : else if (eDataType == GDT_CFloat32)
2504 : {
2505 20 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2506 20 : if (status == NC_NOERR)
2507 20 : CheckDataCpx<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2508 : false);
2509 : }
2510 5 : else if (eDataType == GDT_CFloat64)
2511 : {
2512 5 : status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2513 5 : if (status == NC_NOERR)
2514 5 : CheckDataCpx<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2515 : false);
2516 : }
2517 :
2518 : else
2519 0 : status = NC_EBADTYPE;
2520 :
2521 5155 : if (status != NC_NOERR)
2522 : {
2523 0 : CPLError(CE_Failure, CPLE_AppDefined,
2524 : "netCDF chunk fetch failed: #%d (%s)", status,
2525 : nc_strerror(status));
2526 0 : return false;
2527 : }
2528 5155 : return true;
2529 : }
2530 :
2531 : /************************************************************************/
2532 : /* IReadBlock() */
2533 : /************************************************************************/
2534 :
2535 5155 : CPLErr netCDFRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
2536 : void *pImage)
2537 :
2538 : {
2539 10310 : CPLMutexHolderD(&hNCMutex);
2540 :
2541 : // Locate X, Y and Z position in the array.
2542 :
2543 5155 : size_t xstart = static_cast<size_t>(nBlockXOff) * nBlockXSize;
2544 5155 : size_t ystart = 0;
2545 :
2546 : // Check y order.
2547 5155 : if (nBandYPos >= 0)
2548 : {
2549 5151 : auto poGDS = static_cast<netCDFDataset *>(poDS);
2550 5151 : if (poGDS->bBottomUp)
2551 : {
2552 4236 : if (nBlockYSize == 1)
2553 : {
2554 4223 : ystart = nRasterYSize - 1 - nBlockYOff;
2555 : }
2556 : else
2557 : {
2558 : // in GDAL space
2559 13 : ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
2560 : const size_t yend =
2561 26 : std::min(ystart + nBlockYSize - 1,
2562 13 : static_cast<size_t>(nRasterYSize - 1));
2563 : // in netCDF space
2564 13 : const size_t nFirstChunkLine = nRasterYSize - 1 - yend;
2565 13 : const size_t nLastChunkLine = nRasterYSize - 1 - ystart;
2566 13 : const size_t nFirstChunkBlock = nFirstChunkLine / nBlockYSize;
2567 13 : const size_t nLastChunkBlock = nLastChunkLine / nBlockYSize;
2568 :
2569 : const auto firstKey = netCDFDataset::ChunkKey(
2570 13 : nBlockXOff, nFirstChunkBlock, nBand);
2571 : const auto secondKey =
2572 13 : netCDFDataset::ChunkKey(nBlockXOff, nLastChunkBlock, nBand);
2573 :
2574 : // Retrieve data from the one or 2 needed netCDF chunks
2575 13 : std::shared_ptr<std::vector<GByte>> firstChunk;
2576 13 : std::shared_ptr<std::vector<GByte>> secondChunk;
2577 13 : if (poGDS->poChunkCache)
2578 : {
2579 13 : poGDS->poChunkCache->tryGet(firstKey, firstChunk);
2580 13 : if (firstKey != secondKey)
2581 6 : poGDS->poChunkCache->tryGet(secondKey, secondChunk);
2582 : }
2583 : const size_t nChunkLineSize =
2584 13 : static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
2585 13 : nBlockXSize;
2586 13 : const size_t nChunkSize = nChunkLineSize * nBlockYSize;
2587 13 : if (!firstChunk)
2588 : {
2589 11 : firstChunk.reset(new std::vector<GByte>(nChunkSize));
2590 11 : if (!FetchNetcdfChunk(xstart,
2591 11 : nFirstChunkBlock * nBlockYSize,
2592 11 : firstChunk.get()->data()))
2593 0 : return CE_Failure;
2594 11 : if (poGDS->poChunkCache)
2595 11 : poGDS->poChunkCache->insert(firstKey, firstChunk);
2596 : }
2597 13 : if (!secondChunk && firstKey != secondKey)
2598 : {
2599 2 : secondChunk.reset(new std::vector<GByte>(nChunkSize));
2600 2 : if (!FetchNetcdfChunk(xstart, nLastChunkBlock * nBlockYSize,
2601 2 : secondChunk.get()->data()))
2602 0 : return CE_Failure;
2603 2 : if (poGDS->poChunkCache)
2604 2 : poGDS->poChunkCache->insert(secondKey, secondChunk);
2605 : }
2606 :
2607 : // Assemble netCDF chunks into GDAL block
2608 13 : GByte *pabyImage = static_cast<GByte *>(pImage);
2609 13 : const size_t nFirstChunkBlockLine =
2610 13 : nFirstChunkBlock * nBlockYSize;
2611 13 : const size_t nLastChunkBlockLine =
2612 13 : nLastChunkBlock * nBlockYSize;
2613 146 : for (size_t iLine = ystart; iLine <= yend; iLine++)
2614 : {
2615 133 : const size_t nLineFromBottom = nRasterYSize - 1 - iLine;
2616 133 : const size_t nChunkY = nLineFromBottom / nBlockYSize;
2617 133 : if (nChunkY == nFirstChunkBlock)
2618 : {
2619 121 : memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
2620 121 : firstChunk.get()->data() +
2621 121 : (nLineFromBottom - nFirstChunkBlockLine) *
2622 : nChunkLineSize,
2623 : nChunkLineSize);
2624 : }
2625 : else
2626 : {
2627 12 : CPLAssert(nChunkY == nLastChunkBlock);
2628 12 : assert(secondChunk);
2629 12 : memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
2630 12 : secondChunk.get()->data() +
2631 12 : (nLineFromBottom - nLastChunkBlockLine) *
2632 : nChunkLineSize,
2633 : nChunkLineSize);
2634 : }
2635 : }
2636 13 : return CE_None;
2637 : }
2638 : }
2639 : else
2640 : {
2641 915 : ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
2642 : }
2643 : }
2644 :
2645 5142 : return FetchNetcdfChunk(xstart, ystart, pImage) ? CE_None : CE_Failure;
2646 : }
2647 :
2648 : /************************************************************************/
2649 : /* IWriteBlock() */
2650 : /************************************************************************/
2651 :
2652 2010 : CPLErr netCDFRasterBand::IWriteBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
2653 : void *pImage)
2654 : {
2655 4020 : CPLMutexHolderD(&hNCMutex);
2656 :
2657 : #ifdef NCDF_DEBUG
2658 : if (nBlockYOff == 0 || (nBlockYOff == nRasterYSize - 1))
2659 : CPLDebug("GDAL_netCDF",
2660 : "netCDFRasterBand::IWriteBlock( %d, %d, ...) nBand=%d",
2661 : nBlockXOff, nBlockYOff, nBand);
2662 : #endif
2663 :
2664 2010 : int nd = 0;
2665 2010 : nc_inq_varndims(cdfid, nZId, &nd);
2666 :
2667 : // Locate X, Y and Z position in the array.
2668 :
2669 : size_t start[MAX_NC_DIMS];
2670 2010 : memset(start, 0, sizeof(start));
2671 2010 : start[nBandXPos] = static_cast<size_t>(nBlockXOff) * nBlockXSize;
2672 :
2673 : // check y order.
2674 2010 : if (static_cast<netCDFDataset *>(poDS)->bBottomUp)
2675 : {
2676 1986 : if (nBlockYSize == 1)
2677 : {
2678 1986 : start[nBandYPos] = nRasterYSize - 1 - nBlockYOff;
2679 : }
2680 : else
2681 : {
2682 0 : CPLError(CE_Failure, CPLE_AppDefined,
2683 : "nBlockYSize = %d, only 1 supported when "
2684 : "writing bottom-up dataset",
2685 : nBlockYSize);
2686 0 : return CE_Failure;
2687 : }
2688 : }
2689 : else
2690 : {
2691 24 : start[nBandYPos] = static_cast<size_t>(nBlockYOff) * nBlockYSize; // y
2692 : }
2693 :
2694 2010 : size_t edge[MAX_NC_DIMS] = {};
2695 :
2696 2010 : edge[nBandXPos] = nBlockXSize;
2697 2010 : if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
2698 0 : edge[nBandXPos] = nRasterXSize - start[nBandXPos];
2699 2010 : edge[nBandYPos] = nBlockYSize;
2700 2010 : if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
2701 0 : edge[nBandYPos] = nRasterYSize - start[nBandYPos];
2702 :
2703 2010 : if (nd == 3)
2704 : {
2705 10 : start[panBandZPos[0]] = nLevel; // z
2706 10 : edge[panBandZPos[0]] = 1;
2707 : }
2708 :
2709 : // Compute multidimention band position.
2710 : //
2711 : // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
2712 : // if Data[2,3,4,x,y]
2713 : //
2714 : // BandPos0 = (nBand) / (3*4)
2715 : // BandPos1 = (nBand - (3*4)) / (4)
2716 : // BandPos2 = (nBand - (3*4)) % (4)
2717 2010 : if (nd > 3)
2718 : {
2719 178 : int Sum = -1;
2720 178 : int Taken = 0;
2721 534 : for (int i = 0; i < nd - 2; i++)
2722 : {
2723 356 : if (i != nd - 2 - 1)
2724 : {
2725 178 : Sum = 1;
2726 356 : for (int j = i + 1; j < nd - 2; j++)
2727 : {
2728 178 : Sum *= panBandZLev[j];
2729 : }
2730 178 : start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
2731 178 : edge[panBandZPos[i]] = 1;
2732 : }
2733 : else
2734 : {
2735 178 : start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
2736 178 : edge[panBandZPos[i]] = 1;
2737 : }
2738 356 : Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
2739 : }
2740 : }
2741 :
2742 : // Make sure we are in data mode.
2743 2010 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
2744 :
2745 : // Copy data according to type.
2746 2010 : int status = 0;
2747 2010 : if (eDataType == GDT_Byte)
2748 : {
2749 1451 : if (bSignedData)
2750 0 : status = nc_put_vara_schar(cdfid, nZId, start, edge,
2751 : static_cast<signed char *>(pImage));
2752 : else
2753 1451 : status = nc_put_vara_uchar(cdfid, nZId, start, edge,
2754 : static_cast<unsigned char *>(pImage));
2755 : }
2756 559 : else if (eDataType == GDT_Int8)
2757 : {
2758 40 : status = nc_put_vara_schar(cdfid, nZId, start, edge,
2759 : static_cast<signed char *>(pImage));
2760 : }
2761 519 : else if (nc_datatype == NC_SHORT)
2762 : {
2763 101 : status = nc_put_vara_short(cdfid, nZId, start, edge,
2764 : static_cast<short *>(pImage));
2765 : }
2766 418 : else if (eDataType == GDT_Int32)
2767 : {
2768 210 : status = nc_put_vara_int(cdfid, nZId, start, edge,
2769 : static_cast<int *>(pImage));
2770 : }
2771 208 : else if (eDataType == GDT_Float32)
2772 : {
2773 128 : status = nc_put_vara_float(cdfid, nZId, start, edge,
2774 : static_cast<float *>(pImage));
2775 : }
2776 80 : else if (eDataType == GDT_Float64)
2777 : {
2778 50 : status = nc_put_vara_double(cdfid, nZId, start, edge,
2779 : static_cast<double *>(pImage));
2780 : }
2781 30 : else if (eDataType == GDT_UInt16 &&
2782 12 : static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2783 : {
2784 12 : status = nc_put_vara_ushort(cdfid, nZId, start, edge,
2785 : static_cast<unsigned short *>(pImage));
2786 : }
2787 18 : else if (eDataType == GDT_UInt32 &&
2788 12 : static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2789 : {
2790 12 : status = nc_put_vara_uint(cdfid, nZId, start, edge,
2791 : static_cast<unsigned int *>(pImage));
2792 : }
2793 6 : else if (eDataType == GDT_UInt64 &&
2794 3 : static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2795 : {
2796 3 : status =
2797 3 : nc_put_vara_ulonglong(cdfid, nZId, start, edge,
2798 : static_cast<unsigned long long *>(pImage));
2799 : }
2800 3 : else if (eDataType == GDT_Int64 &&
2801 3 : static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2802 : {
2803 3 : status = nc_put_vara_longlong(cdfid, nZId, start, edge,
2804 : static_cast<long long *>(pImage));
2805 : }
2806 : else
2807 : {
2808 0 : CPLError(CE_Failure, CPLE_NotSupported,
2809 : "The NetCDF driver does not support GDAL data type %d",
2810 0 : eDataType);
2811 0 : status = NC_EBADTYPE;
2812 : }
2813 2010 : NCDF_ERR(status);
2814 :
2815 2010 : if (status != NC_NOERR)
2816 : {
2817 0 : CPLError(CE_Failure, CPLE_AppDefined,
2818 : "netCDF scanline write failed: %s", nc_strerror(status));
2819 0 : return CE_Failure;
2820 : }
2821 :
2822 2010 : return CE_None;
2823 : }
2824 :
2825 : /************************************************************************/
2826 : /* ==================================================================== */
2827 : /* netCDFDataset */
2828 : /* ==================================================================== */
2829 : /************************************************************************/
2830 :
2831 : /************************************************************************/
2832 : /* netCDFDataset() */
2833 : /************************************************************************/
2834 :
2835 961 : netCDFDataset::netCDFDataset()
2836 : :
2837 : // Basic dataset vars.
2838 : #ifdef ENABLE_NCDUMP
2839 : bFileToDestroyAtClosing(false),
2840 : #endif
2841 : cdfid(-1), nSubDatasets(0), papszSubDatasets(nullptr),
2842 : papszMetadata(nullptr), bBottomUp(true), eFormat(NCDF_FORMAT_NONE),
2843 : bIsGdalFile(false), bIsGdalCfFile(false), pszCFProjection(nullptr),
2844 : pszCFCoordinates(nullptr), nCFVersion(1.6), bSGSupport(false),
2845 961 : eMultipleLayerBehavior(SINGLE_LAYER), logCount(0), vcdf(cdfid),
2846 961 : GeometryScribe(vcdf, this->generateLogName()),
2847 961 : FieldScribe(vcdf, this->generateLogName()),
2848 1922 : bufManager(CPLGetUsablePhysicalRAM() / 5),
2849 :
2850 : // projection/GT.
2851 : nXDimID(-1), nYDimID(-1), bIsProjected(false),
2852 : bIsGeographic(false), // Can be not projected, and also not geographic
2853 : // State vars.
2854 : bDefineMode(true), bAddedGridMappingRef(false),
2855 :
2856 : // Create vars.
2857 : papszCreationOptions(nullptr), eCompress(NCDF_COMPRESS_NONE),
2858 : nZLevel(NCDF_DEFLATE_LEVEL), bChunking(false), nCreateMode(NC_CLOBBER),
2859 2883 : bSignedData(true)
2860 : {
2861 961 : m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2862 :
2863 : // Projection/GT.
2864 961 : m_adfGeoTransform[0] = 0.0;
2865 961 : m_adfGeoTransform[1] = 1.0;
2866 961 : m_adfGeoTransform[2] = 0.0;
2867 961 : m_adfGeoTransform[3] = 0.0;
2868 961 : m_adfGeoTransform[4] = 0.0;
2869 961 : m_adfGeoTransform[5] = 1.0;
2870 :
2871 : // Set buffers
2872 961 : bufManager.addBuffer(&(GeometryScribe.getMemBuffer()));
2873 961 : bufManager.addBuffer(&(FieldScribe.getMemBuffer()));
2874 961 : }
2875 :
2876 : /************************************************************************/
2877 : /* ~netCDFDataset() */
2878 : /************************************************************************/
2879 :
2880 1861 : netCDFDataset::~netCDFDataset()
2881 :
2882 : {
2883 961 : netCDFDataset::Close();
2884 1861 : }
2885 :
2886 : /************************************************************************/
2887 : /* Close() */
2888 : /************************************************************************/
2889 :
2890 1687 : CPLErr netCDFDataset::Close()
2891 : {
2892 1687 : CPLErr eErr = CE_None;
2893 1687 : if (nOpenFlags != OPEN_FLAGS_CLOSED)
2894 : {
2895 1922 : CPLMutexHolderD(&hNCMutex);
2896 :
2897 : #ifdef NCDF_DEBUG
2898 : CPLDebug("GDAL_netCDF",
2899 : "netCDFDataset::~netCDFDataset(), cdfid=%d filename=%s", cdfid,
2900 : osFilename.c_str());
2901 : #endif
2902 :
2903 : // Write data related to geotransform
2904 1188 : if (GetAccess() == GA_Update && !m_bAddedProjectionVarsData &&
2905 227 : (m_bHasProjection || m_bHasGeoTransform))
2906 : {
2907 : // Ensure projection is written if GeoTransform OR Projection are
2908 : // missing.
2909 36 : if (!m_bAddedProjectionVarsDefs)
2910 : {
2911 2 : AddProjectionVars(true, nullptr, nullptr);
2912 : }
2913 36 : AddProjectionVars(false, nullptr, nullptr);
2914 : }
2915 :
2916 961 : if (netCDFDataset::FlushCache(true) != CE_None)
2917 0 : eErr = CE_Failure;
2918 :
2919 961 : if (!SGCommitPendingTransaction())
2920 0 : eErr = CE_Failure;
2921 :
2922 963 : for (size_t i = 0; i < apoVectorDatasets.size(); i++)
2923 2 : delete apoVectorDatasets[i];
2924 :
2925 : // Make sure projection variable is written to band variable.
2926 961 : if (GetAccess() == GA_Update && !bAddedGridMappingRef)
2927 : {
2928 240 : if (!AddGridMappingRef())
2929 0 : eErr = CE_Failure;
2930 : }
2931 :
2932 961 : CSLDestroy(papszMetadata);
2933 961 : CSLDestroy(papszSubDatasets);
2934 961 : CSLDestroy(papszCreationOptions);
2935 :
2936 961 : CPLFree(pszCFProjection);
2937 :
2938 961 : if (cdfid > 0)
2939 : {
2940 : #ifdef NCDF_DEBUG
2941 : CPLDebug("GDAL_netCDF", "calling nc_close( %d)", cdfid);
2942 : #endif
2943 641 : int status = GDAL_nc_close(cdfid);
2944 : #ifdef ENABLE_UFFD
2945 641 : NETCDF_UFFD_UNMAP(pCtx);
2946 : #endif
2947 641 : NCDF_ERR(status);
2948 641 : if (status != NC_NOERR)
2949 0 : eErr = CE_Failure;
2950 : }
2951 :
2952 961 : if (fpVSIMEM)
2953 15 : VSIFCloseL(fpVSIMEM);
2954 :
2955 : #ifdef ENABLE_NCDUMP
2956 961 : if (bFileToDestroyAtClosing)
2957 0 : VSIUnlink(osFilename);
2958 : #endif
2959 :
2960 961 : if (GDALPamDataset::Close() != CE_None)
2961 0 : eErr = CE_Failure;
2962 : }
2963 1687 : return eErr;
2964 : }
2965 :
2966 : /************************************************************************/
2967 : /* SetDefineMode() */
2968 : /************************************************************************/
2969 9107 : bool netCDFDataset::SetDefineMode(bool bNewDefineMode)
2970 : {
2971 : // Do nothing if already in new define mode
2972 : // or if dataset is in read-only mode or if dataset is true NC4 dataset.
2973 9571 : if (bDefineMode == bNewDefineMode || GetAccess() == GA_ReadOnly ||
2974 464 : eFormat == NCDF_FORMAT_NC4)
2975 8786 : return true;
2976 :
2977 321 : CPLDebug("GDAL_netCDF", "SetDefineMode(%d) old=%d",
2978 321 : static_cast<int>(bNewDefineMode), static_cast<int>(bDefineMode));
2979 :
2980 321 : bDefineMode = bNewDefineMode;
2981 :
2982 : int status;
2983 321 : if (bDefineMode)
2984 101 : status = nc_redef(cdfid);
2985 : else
2986 220 : status = nc_enddef(cdfid);
2987 :
2988 321 : NCDF_ERR(status);
2989 321 : return status == NC_NOERR;
2990 : }
2991 :
2992 : /************************************************************************/
2993 : /* GetMetadataDomainList() */
2994 : /************************************************************************/
2995 :
2996 27 : char **netCDFDataset::GetMetadataDomainList()
2997 : {
2998 27 : char **papszDomains = BuildMetadataDomainList(
2999 : GDALDataset::GetMetadataDomainList(), TRUE, "SUBDATASETS", nullptr);
3000 28 : for (const auto &kv : m_oMapDomainToJSon)
3001 1 : papszDomains = CSLAddString(papszDomains, ("json:" + kv.first).c_str());
3002 27 : return papszDomains;
3003 : }
3004 :
3005 : /************************************************************************/
3006 : /* GetMetadata() */
3007 : /************************************************************************/
3008 347 : char **netCDFDataset::GetMetadata(const char *pszDomain)
3009 : {
3010 347 : if (pszDomain != nullptr && STARTS_WITH_CI(pszDomain, "SUBDATASETS"))
3011 34 : return papszSubDatasets;
3012 :
3013 313 : if (pszDomain != nullptr && STARTS_WITH(pszDomain, "json:"))
3014 : {
3015 1 : auto iter = m_oMapDomainToJSon.find(pszDomain + strlen("json:"));
3016 1 : if (iter != m_oMapDomainToJSon.end())
3017 1 : return iter->second.List();
3018 : }
3019 :
3020 312 : return GDALDataset::GetMetadata(pszDomain);
3021 : }
3022 :
3023 : /************************************************************************/
3024 : /* SetMetadataItem() */
3025 : /************************************************************************/
3026 :
3027 40 : CPLErr netCDFDataset::SetMetadataItem(const char *pszName, const char *pszValue,
3028 : const char *pszDomain)
3029 : {
3030 80 : if (GetAccess() == GA_Update &&
3031 80 : (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)
3032 : {
3033 40 : std::string osName(pszName);
3034 :
3035 : // Same logic as in CopyMetadata()
3036 40 : if (STARTS_WITH(osName.c_str(), "NC_GLOBAL#"))
3037 8 : osName = osName.substr(strlen("NC_GLOBAL#"));
3038 32 : else if (strchr(osName.c_str(), '#') == nullptr)
3039 3 : osName = "GDAL_" + osName;
3040 :
3041 80 : if (STARTS_WITH(osName.c_str(), "NETCDF_DIM_") ||
3042 40 : strchr(osName.c_str(), '#') != nullptr)
3043 : {
3044 : // do nothing
3045 : }
3046 : else
3047 : {
3048 11 : SetDefineMode(true);
3049 :
3050 11 : if (!NCDFPutAttr(cdfid, NC_GLOBAL, osName.c_str(), pszValue))
3051 11 : return CE_Failure;
3052 : }
3053 : }
3054 :
3055 29 : return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);
3056 : }
3057 :
3058 : /************************************************************************/
3059 : /* SetMetadata() */
3060 : /************************************************************************/
3061 :
3062 6 : CPLErr netCDFDataset::SetMetadata(char **papszMD, const char *pszDomain)
3063 : {
3064 10 : if (GetAccess() == GA_Update &&
3065 4 : (pszDomain == nullptr || pszDomain[0] == '\0'))
3066 : {
3067 : // We don't handle metadata item removal for now
3068 46 : for (const char *const *papszIter = papszMD; papszIter && *papszIter;
3069 : ++papszIter)
3070 : {
3071 40 : char *pszName = nullptr;
3072 40 : const char *pszValue = CPLParseNameValue(*papszIter, &pszName);
3073 40 : if (pszName && pszValue)
3074 40 : SetMetadataItem(pszName, pszValue);
3075 40 : CPLFree(pszName);
3076 : }
3077 : }
3078 6 : return GDALPamDataset::SetMetadata(papszMD, pszDomain);
3079 : }
3080 :
3081 : /************************************************************************/
3082 : /* GetSpatialRef() */
3083 : /************************************************************************/
3084 :
3085 167 : const OGRSpatialReference *netCDFDataset::GetSpatialRef() const
3086 : {
3087 167 : if (m_bHasProjection)
3088 68 : return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
3089 :
3090 99 : return GDALPamDataset::GetSpatialRef();
3091 : }
3092 :
3093 : /************************************************************************/
3094 : /* SerializeToXML() */
3095 : /************************************************************************/
3096 :
3097 15 : CPLXMLNode *netCDFDataset::SerializeToXML(const char *pszUnused)
3098 :
3099 : {
3100 : // Overridden from GDALPamDataset to add only band histogram
3101 : // and statistics. See bug #4244.
3102 :
3103 15 : if (psPam == nullptr)
3104 0 : return nullptr;
3105 :
3106 : // Setup root node and attributes.
3107 15 : CPLXMLNode *psDSTree = CPLCreateXMLNode(nullptr, CXT_Element, "PAMDataset");
3108 :
3109 : // Process bands.
3110 46 : for (int iBand = 0; iBand < GetRasterCount(); iBand++)
3111 : {
3112 : netCDFRasterBand *poBand =
3113 31 : static_cast<netCDFRasterBand *>(GetRasterBand(iBand + 1));
3114 :
3115 31 : if (poBand == nullptr || !(poBand->GetMOFlags() & GMO_PAM_CLASS))
3116 0 : continue;
3117 :
3118 31 : CPLXMLNode *psBandTree = poBand->SerializeToXML(pszUnused);
3119 :
3120 31 : if (psBandTree != nullptr)
3121 2 : CPLAddXMLChild(psDSTree, psBandTree);
3122 : }
3123 :
3124 : // We don't want to return anything if we had no metadata to attach.
3125 15 : if (psDSTree->psChild == nullptr)
3126 : {
3127 13 : CPLDestroyXMLNode(psDSTree);
3128 13 : psDSTree = nullptr;
3129 : }
3130 :
3131 15 : return psDSTree;
3132 : }
3133 :
3134 : /************************************************************************/
3135 : /* FetchCopyParam() */
3136 : /************************************************************************/
3137 :
3138 332 : double netCDFDataset::FetchCopyParam(const char *pszGridMappingValue,
3139 : const char *pszParam, double dfDefault,
3140 : bool *pbFound)
3141 :
3142 : {
3143 : char *pszTemp =
3144 332 : CPLStrdup(CPLSPrintf("%s#%s", pszGridMappingValue, pszParam));
3145 332 : const char *pszValue = CSLFetchNameValue(papszMetadata, pszTemp);
3146 332 : CPLFree(pszTemp);
3147 :
3148 332 : if (pbFound)
3149 : {
3150 332 : *pbFound = (pszValue != nullptr);
3151 : }
3152 :
3153 332 : if (pszValue)
3154 : {
3155 0 : return CPLAtofM(pszValue);
3156 : }
3157 :
3158 332 : return dfDefault;
3159 : }
3160 :
3161 : /************************************************************************/
3162 : /* FetchStandardParallels() */
3163 : /************************************************************************/
3164 :
3165 : std::vector<std::string>
3166 0 : netCDFDataset::FetchStandardParallels(const char *pszGridMappingValue)
3167 : {
3168 : // cf-1.0 tags
3169 0 : const char *pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL);
3170 :
3171 0 : std::vector<std::string> ret;
3172 0 : if (pszValue != nullptr)
3173 : {
3174 0 : CPLStringList aosValues;
3175 0 : if (pszValue[0] != '{' &&
3176 0 : CPLString(pszValue).Trim().find(' ') != std::string::npos)
3177 : {
3178 : // Some files like
3179 : // ftp://data.knmi.nl/download/KNW-NetCDF-3D/1.0/noversion/2013/11/14/KNW-1.0_H37-ERA_NL_20131114.nc
3180 : // do not use standard formatting for arrays, but just space
3181 : // separated syntax
3182 0 : aosValues = CSLTokenizeString2(pszValue, " ", 0);
3183 : }
3184 : else
3185 : {
3186 0 : aosValues = NCDFTokenizeArray(pszValue);
3187 : }
3188 0 : for (int i = 0; i < aosValues.size(); i++)
3189 : {
3190 0 : ret.push_back(aosValues[i]);
3191 : }
3192 : }
3193 : // Try gdal tags.
3194 : else
3195 : {
3196 0 : pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_1);
3197 :
3198 0 : if (pszValue != nullptr)
3199 0 : ret.push_back(pszValue);
3200 :
3201 0 : pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_2);
3202 :
3203 0 : if (pszValue != nullptr)
3204 0 : ret.push_back(pszValue);
3205 : }
3206 :
3207 0 : return ret;
3208 : }
3209 :
3210 : /************************************************************************/
3211 : /* FetchAttr() */
3212 : /************************************************************************/
3213 :
3214 3579 : const char *netCDFDataset::FetchAttr(const char *pszVarFullName,
3215 : const char *pszAttr)
3216 :
3217 : {
3218 3579 : char *pszKey = CPLStrdup(CPLSPrintf("%s#%s", pszVarFullName, pszAttr));
3219 3579 : const char *pszValue = CSLFetchNameValue(papszMetadata, pszKey);
3220 3579 : CPLFree(pszKey);
3221 3579 : return pszValue;
3222 : }
3223 :
3224 2371 : const char *netCDFDataset::FetchAttr(int nGroupId, int nVarId,
3225 : const char *pszAttr)
3226 :
3227 : {
3228 2371 : char *pszVarFullName = nullptr;
3229 2371 : NCDFGetVarFullName(nGroupId, nVarId, &pszVarFullName);
3230 2371 : const char *pszValue = FetchAttr(pszVarFullName, pszAttr);
3231 2371 : CPLFree(pszVarFullName);
3232 2371 : return pszValue;
3233 : }
3234 :
3235 : /************************************************************************/
3236 : /* IsDifferenceBelow() */
3237 : /************************************************************************/
3238 :
3239 1015 : static bool IsDifferenceBelow(double dfA, double dfB, double dfError)
3240 : {
3241 1015 : const double dfAbsDiff = fabs(dfA - dfB);
3242 1015 : return dfAbsDiff <= dfError;
3243 : }
3244 :
3245 : /************************************************************************/
3246 : /* SetProjectionFromVar() */
3247 : /************************************************************************/
3248 495 : void netCDFDataset::SetProjectionFromVar(
3249 : int nGroupId, int nVarId, bool bReadSRSOnly, const char *pszGivenGM,
3250 : std::string *returnProjStr, nccfdriver::SGeometry_Reader *sg,
3251 : std::vector<std::string> *paosRemovedMDItems)
3252 : {
3253 495 : bool bGotGeogCS = false;
3254 495 : bool bGotCfSRS = false;
3255 495 : bool bGotCfWktSRS = false;
3256 495 : bool bGotGdalSRS = false;
3257 495 : bool bGotCfGT = false;
3258 495 : bool bGotGdalGT = false;
3259 :
3260 : // These values from CF metadata.
3261 495 : OGRSpatialReference oSRS;
3262 495 : oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3263 495 : size_t xdim = nRasterXSize;
3264 495 : size_t ydim = nRasterYSize;
3265 :
3266 : // These values from GDAL metadata.
3267 495 : const char *pszWKT = nullptr;
3268 495 : const char *pszGeoTransform = nullptr;
3269 :
3270 495 : netCDFDataset *poDS = this; // Perhaps this should be removed for clarity.
3271 :
3272 495 : CPLDebug("GDAL_netCDF", "\n=====\nSetProjectionFromVar( %d, %d)", nGroupId,
3273 : nVarId);
3274 :
3275 : // Get x/y range information.
3276 :
3277 : // Temp variables to use in SetGeoTransform() and SetProjection().
3278 495 : double adfTempGeoTransform[6] = {0.0, 1.0, 0.0, 0.0, 0.0, 1.0};
3279 :
3280 : // Look for grid_mapping metadata.
3281 495 : const char *pszValue = pszGivenGM;
3282 495 : CPLString osTmpGridMapping; // let is in this outer scope as pszValue may
3283 : // point to it
3284 495 : if (pszValue == nullptr)
3285 : {
3286 447 : pszValue = FetchAttr(nGroupId, nVarId, CF_GRD_MAPPING);
3287 447 : if (pszValue && strchr(pszValue, ':') && strchr(pszValue, ' '))
3288 : {
3289 : // Expanded form of grid_mapping
3290 : // e.g. "crsOSGB: x y crsWGS84: lat lon"
3291 : // Pickup the grid_mapping whose coordinates are dimensions of the
3292 : // variable
3293 6 : CPLStringList aosTokens(CSLTokenizeString2(pszValue, " ", 0));
3294 3 : if ((aosTokens.size() % 3) == 0)
3295 : {
3296 3 : for (int i = 0; i < aosTokens.size() / 3; i++)
3297 : {
3298 3 : if (CSLFindString(poDS->papszDimName,
3299 9 : aosTokens[3 * i + 1]) >= 0 &&
3300 3 : CSLFindString(poDS->papszDimName,
3301 3 : aosTokens[3 * i + 2]) >= 0)
3302 : {
3303 3 : osTmpGridMapping = aosTokens[3 * i];
3304 6 : if (!osTmpGridMapping.empty() &&
3305 3 : osTmpGridMapping.back() == ':')
3306 : {
3307 3 : osTmpGridMapping.resize(osTmpGridMapping.size() -
3308 : 1);
3309 : }
3310 3 : pszValue = osTmpGridMapping.c_str();
3311 3 : break;
3312 : }
3313 : }
3314 : }
3315 : }
3316 : }
3317 495 : char *pszGridMappingValue = CPLStrdup(pszValue ? pszValue : "");
3318 :
3319 495 : if (!EQUAL(pszGridMappingValue, ""))
3320 : {
3321 : // Read grid_mapping metadata.
3322 216 : int nProjGroupID = -1;
3323 216 : int nProjVarID = -1;
3324 216 : if (NCDFResolveVar(nGroupId, pszGridMappingValue, &nProjGroupID,
3325 216 : &nProjVarID) == CE_None)
3326 : {
3327 215 : poDS->ReadAttributes(nProjGroupID, nProjVarID);
3328 :
3329 : // Look for GDAL spatial_ref and GeoTransform within grid_mapping.
3330 215 : CPLFree(pszGridMappingValue);
3331 215 : pszGridMappingValue = nullptr;
3332 215 : NCDFGetVarFullName(nProjGroupID, nProjVarID, &pszGridMappingValue);
3333 215 : if (pszGridMappingValue)
3334 : {
3335 215 : CPLDebug("GDAL_netCDF", "got grid_mapping %s",
3336 : pszGridMappingValue);
3337 215 : pszWKT = FetchAttr(pszGridMappingValue, NCDF_SPATIAL_REF);
3338 215 : if (!pszWKT)
3339 : {
3340 34 : pszWKT = FetchAttr(pszGridMappingValue, NCDF_CRS_WKT);
3341 : }
3342 : else
3343 : {
3344 181 : bGotGdalSRS = true;
3345 181 : CPLDebug("GDAL_netCDF", "setting WKT from GDAL");
3346 : }
3347 215 : if (pszWKT)
3348 : {
3349 185 : if (!bGotGdalSRS)
3350 : {
3351 4 : bGotCfWktSRS = true;
3352 4 : CPLDebug("GDAL_netCDF", "setting WKT from CF");
3353 : }
3354 185 : if (returnProjStr != nullptr)
3355 : {
3356 46 : (*returnProjStr) = std::string(pszWKT);
3357 : }
3358 : else
3359 : {
3360 139 : m_bAddedProjectionVarsDefs = true;
3361 139 : m_bAddedProjectionVarsData = true;
3362 278 : OGRSpatialReference oSRSTmp;
3363 139 : oSRSTmp.SetAxisMappingStrategy(
3364 : OAMS_TRADITIONAL_GIS_ORDER);
3365 139 : oSRSTmp.importFromWkt(pszWKT);
3366 139 : SetSpatialRefNoUpdate(&oSRSTmp);
3367 : }
3368 : pszGeoTransform =
3369 185 : FetchAttr(pszGridMappingValue, NCDF_GEOTRANSFORM);
3370 : }
3371 : }
3372 : else
3373 : {
3374 0 : pszGridMappingValue = CPLStrdup("");
3375 : }
3376 : }
3377 : }
3378 :
3379 : // Get information about the file.
3380 : //
3381 : // Was this file created by the GDAL netcdf driver?
3382 : // Was this file created by the newer (CF-conformant) driver?
3383 : //
3384 : // 1) If GDAL netcdf metadata is set, and version >= 1.9,
3385 : // it was created with the new driver
3386 : // 2) Else, if spatial_ref and GeoTransform are present in the
3387 : // grid_mapping variable, it was created by the old driver
3388 495 : pszValue = FetchAttr("NC_GLOBAL", "GDAL");
3389 :
3390 495 : if (pszValue && NCDFIsGDALVersionGTE(pszValue, 1900))
3391 : {
3392 238 : bIsGdalFile = true;
3393 238 : bIsGdalCfFile = true;
3394 : }
3395 257 : else if (pszWKT != nullptr && pszGeoTransform != nullptr)
3396 : {
3397 14 : bIsGdalFile = true;
3398 14 : bIsGdalCfFile = false;
3399 : }
3400 :
3401 : // Set default bottom-up default value.
3402 : // Y axis dimension and absence of GT can modify this value.
3403 : // Override with Config option GDAL_NETCDF_BOTTOMUP.
3404 :
3405 : // New driver is bottom-up by default.
3406 495 : if ((bIsGdalFile && !bIsGdalCfFile) || bSwitchedXY)
3407 16 : poDS->bBottomUp = false;
3408 : else
3409 479 : poDS->bBottomUp = true;
3410 :
3411 495 : CPLDebug("GDAL_netCDF",
3412 : "bIsGdalFile=%d bIsGdalCfFile=%d bSwitchedXY=%d bBottomUp=%d",
3413 495 : static_cast<int>(bIsGdalFile), static_cast<int>(bIsGdalCfFile),
3414 495 : static_cast<int>(bSwitchedXY), static_cast<int>(bBottomUp));
3415 :
3416 : // Read projection coordinates.
3417 :
3418 495 : int nGroupDimXID = -1;
3419 495 : int nVarDimXID = -1;
3420 495 : int nGroupDimYID = -1;
3421 495 : int nVarDimYID = -1;
3422 495 : if (sg != nullptr)
3423 : {
3424 48 : nGroupDimXID = sg->get_ncID();
3425 48 : nGroupDimYID = sg->get_ncID();
3426 48 : nVarDimXID = sg->getNodeCoordVars()[0];
3427 48 : nVarDimYID = sg->getNodeCoordVars()[1];
3428 : }
3429 :
3430 495 : if (!bReadSRSOnly)
3431 : {
3432 338 : NCDFResolveVar(nGroupId, poDS->papszDimName[nXDimID], &nGroupDimXID,
3433 : &nVarDimXID);
3434 338 : NCDFResolveVar(nGroupId, poDS->papszDimName[nYDimID], &nGroupDimYID,
3435 : &nVarDimYID);
3436 : // TODO: if above resolving fails we should also search for coordinate
3437 : // variables without same name than dimension using the same resolving
3438 : // logic. This should handle for example NASA Ocean Color L2 products.
3439 :
3440 : const bool bIgnoreXYAxisNameChecks =
3441 676 : CPLTestBool(CSLFetchNameValueDef(
3442 338 : papszOpenOptions, "IGNORE_XY_AXIS_NAME_CHECKS",
3443 : CPLGetConfigOption("GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS",
3444 338 : "NO"))) ||
3445 : // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a res
3446 : // and transform attributes
3447 338 : (FetchAttr(nGroupId, nVarId, "res") != nullptr &&
3448 676 : FetchAttr(nGroupId, nVarId, "transform") != nullptr) ||
3449 337 : FetchAttr(nGroupId, NC_GLOBAL, "GMT_version") != nullptr;
3450 :
3451 : // Check that they are 1D or 2D variables
3452 338 : if (nVarDimXID >= 0)
3453 : {
3454 243 : int ndims = -1;
3455 243 : nc_inq_varndims(nGroupId, nVarDimXID, &ndims);
3456 243 : if (ndims == 0 || ndims > 2)
3457 0 : nVarDimXID = -1;
3458 243 : else if (!bIgnoreXYAxisNameChecks)
3459 : {
3460 241 : if (!NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
3461 159 : !NCDFIsVarProjectionX(nGroupId, nVarDimXID, nullptr) &&
3462 : // In case of inversion of X/Y
3463 431 : !NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr) &&
3464 31 : !NCDFIsVarProjectionY(nGroupId, nVarDimXID, nullptr))
3465 : {
3466 : char szVarNameX[NC_MAX_NAME + 1];
3467 31 : CPL_IGNORE_RET_VAL(
3468 31 : nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
3469 31 : if (!(ndims == 1 &&
3470 30 : (EQUAL(szVarNameX, CF_LONGITUDE_STD_NAME) ||
3471 29 : EQUAL(szVarNameX, CF_LONGITUDE_VAR_NAME))))
3472 : {
3473 30 : CPLDebug(
3474 : "netCDF",
3475 : "Georeferencing ignored due to non-specific "
3476 : "enough X axis name. "
3477 : "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
3478 : "as configuration option to bypass this check");
3479 30 : nVarDimXID = -1;
3480 : }
3481 : }
3482 : }
3483 : }
3484 :
3485 338 : if (nVarDimYID >= 0)
3486 : {
3487 245 : int ndims = -1;
3488 245 : nc_inq_varndims(nGroupId, nVarDimYID, &ndims);
3489 245 : if (ndims == 0 || ndims > 2)
3490 1 : nVarDimYID = -1;
3491 244 : else if (!bIgnoreXYAxisNameChecks)
3492 : {
3493 242 : if (!NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr) &&
3494 160 : !NCDFIsVarProjectionY(nGroupId, nVarDimYID, nullptr) &&
3495 : // In case of inversion of X/Y
3496 434 : !NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
3497 32 : !NCDFIsVarProjectionX(nGroupId, nVarDimYID, nullptr))
3498 : {
3499 : char szVarNameY[NC_MAX_NAME + 1];
3500 32 : CPL_IGNORE_RET_VAL(
3501 32 : nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
3502 32 : if (!(ndims == 1 &&
3503 32 : (EQUAL(szVarNameY, CF_LATITUDE_STD_NAME) ||
3504 31 : EQUAL(szVarNameY, CF_LATITUDE_VAR_NAME))))
3505 : {
3506 31 : CPLDebug(
3507 : "netCDF",
3508 : "Georeferencing ignored due to non-specific "
3509 : "enough Y axis name. "
3510 : "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
3511 : "as configuration option to bypass this check");
3512 31 : nVarDimYID = -1;
3513 : }
3514 : }
3515 : }
3516 : }
3517 :
3518 338 : if ((nVarDimXID >= 0 && xdim == 1) || (nVarDimXID >= 0 && ydim == 1))
3519 : {
3520 0 : CPLError(CE_Warning, CPLE_AppDefined,
3521 : "1-pixel width/height files not supported, "
3522 : "xdim: %ld ydim: %ld",
3523 : static_cast<long>(xdim), static_cast<long>(ydim));
3524 0 : nVarDimXID = -1;
3525 0 : nVarDimYID = -1;
3526 : }
3527 : }
3528 :
3529 495 : const char *pszUnits = nullptr;
3530 495 : if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
3531 : {
3532 261 : const char *pszUnitsX = FetchAttr(nGroupDimXID, nVarDimXID, "units");
3533 261 : const char *pszUnitsY = FetchAttr(nGroupDimYID, nVarDimYID, "units");
3534 :
3535 261 : if (pszUnitsX && pszUnitsY)
3536 : {
3537 209 : if (EQUAL(pszUnitsX, pszUnitsY))
3538 135 : pszUnits = pszUnitsX;
3539 74 : else if (!pszWKT && !EQUAL(pszGridMappingValue, ""))
3540 : {
3541 1 : CPLError(CE_Failure, CPLE_AppDefined,
3542 : "X axis unit (%s) is different from Y axis "
3543 : "unit (%s). SRS will ignore axis unit and be "
3544 : "likely wrong.",
3545 : pszUnitsX, pszUnitsY);
3546 : }
3547 : }
3548 52 : else if (pszUnitsX && !pszWKT && !EQUAL(pszGridMappingValue, ""))
3549 : {
3550 0 : CPLError(CE_Failure, CPLE_AppDefined,
3551 : "X axis unit is defined, but not Y one ."
3552 : "SRS will ignore axis unit and be likely wrong.");
3553 : }
3554 52 : else if (pszUnitsY && !pszWKT && !EQUAL(pszGridMappingValue, ""))
3555 : {
3556 0 : CPLError(CE_Failure, CPLE_AppDefined,
3557 : "Y axis unit is defined, but not X one ."
3558 : "SRS will ignore axis unit and be likely wrong.");
3559 : }
3560 : }
3561 :
3562 495 : if (!pszWKT && !EQUAL(pszGridMappingValue, ""))
3563 : {
3564 31 : CPLStringList aosGridMappingKeyValues;
3565 31 : const size_t nLenGridMappingValue = strlen(pszGridMappingValue);
3566 789 : for (const char *const *papszIter = papszMetadata;
3567 789 : papszIter && *papszIter; ++papszIter)
3568 : {
3569 758 : if (STARTS_WITH(*papszIter, pszGridMappingValue) &&
3570 236 : (*papszIter)[nLenGridMappingValue] == '#')
3571 : {
3572 236 : char *pszKey = nullptr;
3573 472 : pszValue = CPLParseNameValue(
3574 236 : *papszIter + nLenGridMappingValue + 1, &pszKey);
3575 236 : if (pszKey && pszValue)
3576 236 : aosGridMappingKeyValues.SetNameValue(pszKey, pszValue);
3577 236 : CPLFree(pszKey);
3578 : }
3579 : }
3580 :
3581 31 : bGotGeogCS = aosGridMappingKeyValues.FetchNameValue(
3582 : CF_PP_SEMI_MAJOR_AXIS) != nullptr;
3583 :
3584 31 : oSRS.importFromCF1(aosGridMappingKeyValues.List(), pszUnits);
3585 31 : bGotCfSRS = oSRS.IsGeographic() || oSRS.IsProjected();
3586 : }
3587 : else
3588 : {
3589 : // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a "crs"
3590 : // attribute hold on the variable of interest that contains a PROJ.4
3591 : // string
3592 464 : pszValue = FetchAttr(nGroupId, nVarId, "crs");
3593 465 : if (pszValue &&
3594 1 : (strstr(pszValue, "+proj=") != nullptr ||
3595 0 : strstr(pszValue, "GEOGCS") != nullptr ||
3596 0 : strstr(pszValue, "PROJCS") != nullptr ||
3597 465 : strstr(pszValue, "EPSG:") != nullptr) &&
3598 1 : oSRS.SetFromUserInput(pszValue) == OGRERR_NONE)
3599 : {
3600 1 : bGotCfSRS = true;
3601 : }
3602 : }
3603 :
3604 : // Set Projection from CF.
3605 495 : double dfLinearUnitsConvFactor = 1.0;
3606 495 : if ((bGotGeogCS || bGotCfSRS))
3607 : {
3608 31 : if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
3609 : {
3610 : // Set SRS Units.
3611 :
3612 : // Check units for x and y.
3613 28 : if (oSRS.IsProjected())
3614 : {
3615 25 : dfLinearUnitsConvFactor = oSRS.GetLinearUnits(nullptr);
3616 :
3617 : // If the user doesn't ask to preserve the axis unit,
3618 : // then normalize to metre
3619 31 : if (dfLinearUnitsConvFactor != 1.0 &&
3620 6 : !CPLFetchBool(GetOpenOptions(), "PRESERVE_AXIS_UNIT_IN_CRS",
3621 : false))
3622 : {
3623 5 : oSRS.SetLinearUnits("metre", 1.0);
3624 5 : oSRS.SetAuthority("PROJCS|UNIT", "EPSG", 9001);
3625 : }
3626 : else
3627 : {
3628 20 : dfLinearUnitsConvFactor = 1.0;
3629 : }
3630 : }
3631 : }
3632 :
3633 : // Set projection.
3634 31 : char *pszTempProjection = nullptr;
3635 31 : oSRS.exportToWkt(&pszTempProjection);
3636 31 : if (pszTempProjection)
3637 : {
3638 31 : CPLDebug("GDAL_netCDF", "setting WKT from CF");
3639 31 : if (returnProjStr != nullptr)
3640 : {
3641 2 : (*returnProjStr) = std::string(pszTempProjection);
3642 : }
3643 : else
3644 : {
3645 29 : m_bAddedProjectionVarsDefs = true;
3646 29 : m_bAddedProjectionVarsData = true;
3647 29 : SetSpatialRefNoUpdate(&oSRS);
3648 : }
3649 : }
3650 31 : CPLFree(pszTempProjection);
3651 : }
3652 :
3653 495 : if (!bReadSRSOnly && (nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 &&
3654 : ydim > 0)
3655 : {
3656 : double *pdfXCoord =
3657 213 : static_cast<double *>(CPLCalloc(xdim, sizeof(double)));
3658 : double *pdfYCoord =
3659 213 : static_cast<double *>(CPLCalloc(ydim, sizeof(double)));
3660 :
3661 213 : size_t start[2] = {0, 0};
3662 213 : size_t edge[2] = {xdim, 0};
3663 213 : int status = nc_get_vara_double(nGroupDimXID, nVarDimXID, start, edge,
3664 : pdfXCoord);
3665 213 : NCDF_ERR(status);
3666 :
3667 213 : edge[0] = ydim;
3668 213 : status = nc_get_vara_double(nGroupDimYID, nVarDimYID, start, edge,
3669 : pdfYCoord);
3670 213 : NCDF_ERR(status);
3671 :
3672 213 : nc_type nc_var_dimx_datatype = NC_NAT;
3673 : status =
3674 213 : nc_inq_vartype(nGroupDimXID, nVarDimXID, &nc_var_dimx_datatype);
3675 213 : NCDF_ERR(status);
3676 :
3677 213 : nc_type nc_var_dimy_datatype = NC_NAT;
3678 : status =
3679 213 : nc_inq_vartype(nGroupDimYID, nVarDimYID, &nc_var_dimy_datatype);
3680 213 : NCDF_ERR(status);
3681 :
3682 213 : if (!poDS->bSwitchedXY)
3683 : {
3684 : // Convert ]180,540] longitude values to ]-180,0].
3685 293 : if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
3686 82 : CPLTestBool(
3687 : CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")))
3688 : {
3689 : // If minimum longitude is > 180, subtract 360 from all.
3690 : // Add a check on the maximum X value too, since
3691 : // NCDFIsVarLongitude() is not very specific by default (see
3692 : // https://github.com/OSGeo/gdal/issues/1440)
3693 89 : if (std::min(pdfXCoord[0], pdfXCoord[xdim - 1]) > 180.0 &&
3694 7 : std::max(pdfXCoord[0], pdfXCoord[xdim - 1]) <= 540)
3695 : {
3696 0 : CPLDebug(
3697 : "GDAL_netCDF",
3698 : "Offsetting longitudes from ]180,540] to ]-180,180]. "
3699 : "Can be disabled with GDAL_NETCDF_CENTERLONG_180=NO");
3700 0 : for (size_t i = 0; i < xdim; i++)
3701 0 : pdfXCoord[i] -= 360;
3702 : }
3703 : }
3704 : }
3705 :
3706 : // Is pixel spacing uniform across the map?
3707 :
3708 : // Check Longitude.
3709 :
3710 213 : bool bLonSpacingOK = false;
3711 213 : if (xdim == 2)
3712 : {
3713 31 : bLonSpacingOK = true;
3714 : }
3715 : else
3716 : {
3717 182 : bool bWestIsLeft = (pdfXCoord[0] < pdfXCoord[xdim - 1]);
3718 :
3719 : // fix longitudes if longitudes should increase from
3720 : // west to east, but west > east
3721 254 : if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
3722 72 : !bWestIsLeft)
3723 : {
3724 2 : size_t ndecreases = 0;
3725 :
3726 : // there is lon wrap if longitudes increase
3727 : // with one single decrease
3728 107 : for (size_t i = 1; i < xdim; i++)
3729 : {
3730 105 : if (pdfXCoord[i] < pdfXCoord[i - 1])
3731 1 : ndecreases++;
3732 : }
3733 :
3734 2 : if (ndecreases == 1)
3735 : {
3736 1 : CPLDebug("GDAL_netCDF", "longitude wrap detected");
3737 4 : for (size_t i = 0; i < xdim; i++)
3738 : {
3739 3 : if (pdfXCoord[i] > pdfXCoord[xdim - 1])
3740 1 : pdfXCoord[i] -= 360;
3741 : }
3742 : }
3743 : }
3744 :
3745 182 : const double dfSpacingBegin = pdfXCoord[1] - pdfXCoord[0];
3746 182 : const double dfSpacingMiddle =
3747 182 : pdfXCoord[xdim / 2 + 1] - pdfXCoord[xdim / 2];
3748 182 : const double dfSpacingLast =
3749 182 : pdfXCoord[xdim - 1] - pdfXCoord[xdim - 2];
3750 :
3751 182 : CPLDebug("GDAL_netCDF",
3752 : "xdim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
3753 : "dfSpacingLast: %f",
3754 : static_cast<long>(xdim), dfSpacingBegin, dfSpacingMiddle,
3755 : dfSpacingLast);
3756 : #ifdef NCDF_DEBUG
3757 : CPLDebug("GDAL_netCDF", "xcoords: %f %f %f %f %f %f", pdfXCoord[0],
3758 : pdfXCoord[1], pdfXCoord[xdim / 2],
3759 : pdfXCoord[(xdim / 2) + 1], pdfXCoord[xdim - 2],
3760 : pdfXCoord[xdim - 1]);
3761 : #endif
3762 :
3763 : // ftp://ftp.cdc.noaa.gov/Datasets/NARR/Dailies/monolevel/vwnd.10m.2015.nc
3764 : // requires a 0.02% tolerance, so let's settle for 0.05%
3765 :
3766 : // For float variables, increase to 0.2% (as seen in
3767 : // https://github.com/OSGeo/gdal/issues/3663)
3768 182 : const double dfEpsRel =
3769 182 : nc_var_dimx_datatype == NC_FLOAT ? 0.002 : 0.0005;
3770 :
3771 : const double dfEps =
3772 : dfEpsRel *
3773 364 : std::max(fabs(dfSpacingBegin),
3774 182 : std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
3775 362 : if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
3776 362 : IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
3777 180 : IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
3778 : {
3779 180 : bLonSpacingOK = true;
3780 : }
3781 2 : else if (CPLTestBool(CPLGetConfigOption(
3782 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
3783 : {
3784 0 : bLonSpacingOK = true;
3785 0 : CPLDebug(
3786 : "GDAL_netCDF",
3787 : "Longitude/X is not equally spaced, but will be considered "
3788 : "as such because of "
3789 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
3790 : }
3791 : }
3792 :
3793 213 : if (bLonSpacingOK == false)
3794 : {
3795 2 : CPLDebug(
3796 : "GDAL_netCDF", "%s",
3797 : "Longitude/X is not equally spaced (with a 0.05% tolerance). "
3798 : "You may set the "
3799 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
3800 : "option to YES to ignore this check");
3801 : }
3802 :
3803 : // Check Latitude.
3804 213 : bool bLatSpacingOK = false;
3805 :
3806 213 : if (ydim == 2)
3807 : {
3808 51 : bLatSpacingOK = true;
3809 : }
3810 : else
3811 : {
3812 162 : const double dfSpacingBegin = pdfYCoord[1] - pdfYCoord[0];
3813 162 : const double dfSpacingMiddle =
3814 162 : pdfYCoord[ydim / 2 + 1] - pdfYCoord[ydim / 2];
3815 :
3816 162 : const double dfSpacingLast =
3817 162 : pdfYCoord[ydim - 1] - pdfYCoord[ydim - 2];
3818 :
3819 162 : CPLDebug("GDAL_netCDF",
3820 : "ydim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
3821 : "dfSpacingLast: %f",
3822 : (long)ydim, dfSpacingBegin, dfSpacingMiddle,
3823 : dfSpacingLast);
3824 : #ifdef NCDF_DEBUG
3825 : CPLDebug("GDAL_netCDF", "ycoords: %f %f %f %f %f %f", pdfYCoord[0],
3826 : pdfYCoord[1], pdfYCoord[ydim / 2],
3827 : pdfYCoord[(ydim / 2) + 1], pdfYCoord[ydim - 2],
3828 : pdfYCoord[ydim - 1]);
3829 : #endif
3830 :
3831 162 : const double dfEpsRel =
3832 162 : nc_var_dimy_datatype == NC_FLOAT ? 0.002 : 0.0005;
3833 :
3834 : const double dfEps =
3835 : dfEpsRel *
3836 324 : std::max(fabs(dfSpacingBegin),
3837 162 : std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
3838 322 : if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
3839 322 : IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
3840 151 : IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
3841 : {
3842 151 : bLatSpacingOK = true;
3843 : }
3844 11 : else if (CPLTestBool(CPLGetConfigOption(
3845 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
3846 : {
3847 0 : bLatSpacingOK = true;
3848 0 : CPLDebug(
3849 : "GDAL_netCDF",
3850 : "Latitude/Y is not equally spaced, but will be considered "
3851 : "as such because of "
3852 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
3853 : }
3854 11 : else if (!oSRS.IsProjected() &&
3855 11 : fabs(dfSpacingBegin - dfSpacingLast) <= 0.1 &&
3856 30 : fabs(dfSpacingBegin - dfSpacingMiddle) <= 0.1 &&
3857 8 : fabs(dfSpacingMiddle - dfSpacingLast) <= 0.1)
3858 : {
3859 8 : bLatSpacingOK = true;
3860 8 : CPLError(CE_Warning, CPLE_AppDefined,
3861 : "Latitude grid not spaced evenly. "
3862 : "Setting projection for grid spacing is "
3863 : "within 0.1 degrees threshold.");
3864 :
3865 8 : CPLDebug("GDAL_netCDF",
3866 : "Latitude grid not spaced evenly, but within 0.1 "
3867 : "degree threshold (probably a Gaussian grid). "
3868 : "Saving original latitude values in Y_VALUES "
3869 : "geolocation metadata");
3870 8 : Set1DGeolocation(nGroupDimYID, nVarDimYID, "Y");
3871 : }
3872 :
3873 162 : if (bLatSpacingOK == false)
3874 : {
3875 3 : CPLDebug(
3876 : "GDAL_netCDF", "%s",
3877 : "Latitude/Y is not equally spaced (with a 0.05% "
3878 : "tolerance). "
3879 : "You may set the "
3880 : "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
3881 : "option to YES to ignore this check");
3882 : }
3883 : }
3884 :
3885 213 : if (bLonSpacingOK && bLatSpacingOK)
3886 : {
3887 : // We have gridded data so we can set the Georeferencing info.
3888 :
3889 : // Enable GeoTransform.
3890 :
3891 : // In the following "actual_range" and "node_offset"
3892 : // are attributes used by netCDF files created by GMT.
3893 : // If we find them we know how to proceed. Else, use
3894 : // the original algorithm.
3895 210 : bGotCfGT = true;
3896 :
3897 210 : int node_offset = 0;
3898 210 : NCDFResolveAttInt(nGroupId, NC_GLOBAL, "node_offset", &node_offset);
3899 :
3900 210 : double adfActualRange[2] = {0.0, 0.0};
3901 210 : double xMinMax[2] = {0.0, 0.0};
3902 210 : double yMinMax[2] = {0.0, 0.0};
3903 :
3904 210 : if (!nc_get_att_double(nGroupDimXID, nVarDimXID, "actual_range",
3905 : adfActualRange))
3906 : {
3907 3 : xMinMax[0] = adfActualRange[0];
3908 3 : xMinMax[1] = adfActualRange[1];
3909 :
3910 : // Present xMinMax[] in the same order as padfXCoord
3911 3 : if ((xMinMax[0] - xMinMax[1]) *
3912 3 : (pdfXCoord[0] - pdfXCoord[xdim - 1]) <
3913 : 0)
3914 : {
3915 0 : std::swap(xMinMax[0], xMinMax[1]);
3916 : }
3917 : }
3918 : else
3919 : {
3920 207 : xMinMax[0] = pdfXCoord[0];
3921 207 : xMinMax[1] = pdfXCoord[xdim - 1];
3922 207 : node_offset = 0;
3923 : }
3924 :
3925 210 : if (!nc_get_att_double(nGroupDimYID, nVarDimYID, "actual_range",
3926 : adfActualRange))
3927 : {
3928 3 : yMinMax[0] = adfActualRange[0];
3929 3 : yMinMax[1] = adfActualRange[1];
3930 :
3931 : // Present yMinMax[] in the same order as pdfYCoord
3932 3 : if ((yMinMax[0] - yMinMax[1]) *
3933 3 : (pdfYCoord[0] - pdfYCoord[ydim - 1]) <
3934 : 0)
3935 : {
3936 1 : std::swap(yMinMax[0], yMinMax[1]);
3937 : }
3938 : }
3939 : else
3940 : {
3941 207 : yMinMax[0] = pdfYCoord[0];
3942 207 : yMinMax[1] = pdfYCoord[ydim - 1];
3943 207 : node_offset = 0;
3944 : }
3945 :
3946 210 : double dfCoordOffset = 0.0;
3947 210 : double dfCoordScale = 1.0;
3948 210 : if (!nc_get_att_double(nGroupId, nVarDimXID, CF_ADD_OFFSET,
3949 214 : &dfCoordOffset) &&
3950 4 : !nc_get_att_double(nGroupId, nVarDimXID, CF_SCALE_FACTOR,
3951 : &dfCoordScale))
3952 : {
3953 4 : xMinMax[0] = dfCoordOffset + xMinMax[0] * dfCoordScale;
3954 4 : xMinMax[1] = dfCoordOffset + xMinMax[1] * dfCoordScale;
3955 : }
3956 :
3957 210 : if (!nc_get_att_double(nGroupId, nVarDimYID, CF_ADD_OFFSET,
3958 214 : &dfCoordOffset) &&
3959 4 : !nc_get_att_double(nGroupId, nVarDimYID, CF_SCALE_FACTOR,
3960 : &dfCoordScale))
3961 : {
3962 4 : yMinMax[0] = dfCoordOffset + yMinMax[0] * dfCoordScale;
3963 4 : yMinMax[1] = dfCoordOffset + yMinMax[1] * dfCoordScale;
3964 : }
3965 :
3966 : // Check for reverse order of y-coordinate.
3967 210 : if (!bSwitchedXY)
3968 : {
3969 208 : poDS->bBottomUp = (yMinMax[0] <= yMinMax[1]);
3970 208 : CPLDebug("GDAL_netCDF", "set bBottomUp = %d from Y axis",
3971 208 : static_cast<int>(poDS->bBottomUp));
3972 208 : if (!poDS->bBottomUp)
3973 : {
3974 32 : std::swap(yMinMax[0], yMinMax[1]);
3975 : }
3976 : }
3977 :
3978 : // Geostationary satellites can specify units in (micro)radians
3979 : // So we check if they do, and if so convert to linear units
3980 : // (meters)
3981 210 : const char *pszProjName = oSRS.GetAttrValue("PROJECTION");
3982 210 : if (pszProjName != nullptr)
3983 : {
3984 24 : if (EQUAL(pszProjName, SRS_PT_GEOSTATIONARY_SATELLITE))
3985 : {
3986 : double satelliteHeight =
3987 3 : oSRS.GetProjParm(SRS_PP_SATELLITE_HEIGHT, 1.0);
3988 3 : size_t nAttlen = 0;
3989 : char szUnits[NC_MAX_NAME + 1];
3990 3 : szUnits[0] = '\0';
3991 3 : nc_type nAttype = NC_NAT;
3992 3 : nc_inq_att(nGroupId, nVarDimXID, "units", &nAttype,
3993 : &nAttlen);
3994 6 : if (nAttlen < sizeof(szUnits) &&
3995 3 : nc_get_att_text(nGroupId, nVarDimXID, "units",
3996 : szUnits) == NC_NOERR)
3997 : {
3998 3 : szUnits[nAttlen] = '\0';
3999 3 : if (EQUAL(szUnits, "microradian"))
4000 : {
4001 1 : xMinMax[0] =
4002 1 : xMinMax[0] * satelliteHeight * 0.000001;
4003 1 : xMinMax[1] =
4004 1 : xMinMax[1] * satelliteHeight * 0.000001;
4005 : }
4006 2 : else if (EQUAL(szUnits, "rad") ||
4007 1 : EQUAL(szUnits, "radian"))
4008 : {
4009 2 : xMinMax[0] = xMinMax[0] * satelliteHeight;
4010 2 : xMinMax[1] = xMinMax[1] * satelliteHeight;
4011 : }
4012 : }
4013 3 : szUnits[0] = '\0';
4014 3 : nc_inq_att(nGroupId, nVarDimYID, "units", &nAttype,
4015 : &nAttlen);
4016 6 : if (nAttlen < sizeof(szUnits) &&
4017 3 : nc_get_att_text(nGroupId, nVarDimYID, "units",
4018 : szUnits) == NC_NOERR)
4019 : {
4020 3 : szUnits[nAttlen] = '\0';
4021 3 : if (EQUAL(szUnits, "microradian"))
4022 : {
4023 1 : yMinMax[0] =
4024 1 : yMinMax[0] * satelliteHeight * 0.000001;
4025 1 : yMinMax[1] =
4026 1 : yMinMax[1] * satelliteHeight * 0.000001;
4027 : }
4028 2 : else if (EQUAL(szUnits, "rad") ||
4029 1 : EQUAL(szUnits, "radian"))
4030 : {
4031 2 : yMinMax[0] = yMinMax[0] * satelliteHeight;
4032 2 : yMinMax[1] = yMinMax[1] * satelliteHeight;
4033 : }
4034 : }
4035 : }
4036 : }
4037 :
4038 210 : adfTempGeoTransform[0] = xMinMax[0];
4039 210 : adfTempGeoTransform[1] = (xMinMax[1] - xMinMax[0]) /
4040 210 : (poDS->nRasterXSize + (node_offset - 1));
4041 210 : adfTempGeoTransform[2] = 0;
4042 210 : if (bSwitchedXY)
4043 : {
4044 2 : adfTempGeoTransform[3] = yMinMax[0];
4045 2 : adfTempGeoTransform[4] = 0;
4046 2 : adfTempGeoTransform[5] =
4047 2 : (yMinMax[1] - yMinMax[0]) /
4048 2 : (poDS->nRasterYSize + (node_offset - 1));
4049 : }
4050 : else
4051 : {
4052 208 : adfTempGeoTransform[3] = yMinMax[1];
4053 208 : adfTempGeoTransform[4] = 0;
4054 208 : adfTempGeoTransform[5] =
4055 208 : (yMinMax[0] - yMinMax[1]) /
4056 208 : (poDS->nRasterYSize + (node_offset - 1));
4057 : }
4058 :
4059 : // Compute the center of the pixel.
4060 210 : if (!node_offset)
4061 : {
4062 : // Otherwise its already the pixel center.
4063 210 : adfTempGeoTransform[0] -= (adfTempGeoTransform[1] / 2);
4064 210 : adfTempGeoTransform[3] -= (adfTempGeoTransform[5] / 2);
4065 : }
4066 : }
4067 :
4068 : const auto AreSRSEqualThroughProj4String =
4069 2 : [](const OGRSpatialReference &oSRS1,
4070 : const OGRSpatialReference &oSRS2)
4071 : {
4072 2 : char *pszProj4Str1 = nullptr;
4073 2 : oSRS1.exportToProj4(&pszProj4Str1);
4074 :
4075 2 : char *pszProj4Str2 = nullptr;
4076 2 : oSRS2.exportToProj4(&pszProj4Str2);
4077 :
4078 : {
4079 2 : char *pszTmp = strstr(pszProj4Str1, "+datum=");
4080 2 : if (pszTmp)
4081 0 : memcpy(pszTmp, "+ellps=", strlen("+ellps="));
4082 : }
4083 :
4084 : {
4085 2 : char *pszTmp = strstr(pszProj4Str2, "+datum=");
4086 2 : if (pszTmp)
4087 2 : memcpy(pszTmp, "+ellps=", strlen("+ellps="));
4088 : }
4089 :
4090 2 : bool bRet = false;
4091 2 : if (pszProj4Str1 && pszProj4Str2 &&
4092 2 : EQUAL(pszProj4Str1, pszProj4Str2))
4093 : {
4094 1 : bRet = true;
4095 : }
4096 :
4097 2 : CPLFree(pszProj4Str1);
4098 2 : CPLFree(pszProj4Str2);
4099 2 : return bRet;
4100 : };
4101 :
4102 213 : if (dfLinearUnitsConvFactor != 1.0)
4103 : {
4104 35 : for (int i = 0; i < 6; ++i)
4105 30 : adfTempGeoTransform[i] *= dfLinearUnitsConvFactor;
4106 :
4107 5 : if (paosRemovedMDItems)
4108 : {
4109 : char szVarNameX[NC_MAX_NAME + 1];
4110 5 : CPL_IGNORE_RET_VAL(
4111 5 : nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
4112 :
4113 : char szVarNameY[NC_MAX_NAME + 1];
4114 5 : CPL_IGNORE_RET_VAL(
4115 5 : nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
4116 :
4117 5 : paosRemovedMDItems->push_back(
4118 : CPLSPrintf("%s#units", szVarNameX));
4119 5 : paosRemovedMDItems->push_back(
4120 : CPLSPrintf("%s#units", szVarNameY));
4121 : }
4122 : }
4123 :
4124 : // If there is a global "geospatial_bounds_crs" attribute, check that it
4125 : // is consistent with the SRS, and if so, use it as the SRS
4126 : const char *pszGBCRS =
4127 213 : FetchAttr(nGroupId, NC_GLOBAL, "geospatial_bounds_crs");
4128 213 : if (pszGBCRS && STARTS_WITH(pszGBCRS, "EPSG:"))
4129 : {
4130 4 : OGRSpatialReference oSRSFromGBCRS;
4131 2 : oSRSFromGBCRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
4132 2 : if (oSRSFromGBCRS.SetFromUserInput(
4133 : pszGBCRS,
4134 : OGRSpatialReference::
4135 4 : SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE &&
4136 2 : AreSRSEqualThroughProj4String(oSRS, oSRSFromGBCRS))
4137 : {
4138 1 : oSRS = std::move(oSRSFromGBCRS);
4139 1 : SetSpatialRefNoUpdate(&oSRS);
4140 : }
4141 : }
4142 :
4143 213 : CPLFree(pdfXCoord);
4144 213 : CPLFree(pdfYCoord);
4145 : } // end if(has dims)
4146 :
4147 : // Process custom GeoTransform GDAL value.
4148 495 : if (!EQUAL(pszGridMappingValue, "") && !bGotCfGT)
4149 : {
4150 : // TODO: Read the GT values and detect for conflict with CF.
4151 : // This could resolve the GT precision loss issue.
4152 :
4153 96 : if (pszGeoTransform != nullptr)
4154 : {
4155 : char **papszGeoTransform =
4156 13 : CSLTokenizeString2(pszGeoTransform, " ", CSLT_HONOURSTRINGS);
4157 13 : if (CSLCount(papszGeoTransform) == 6)
4158 : {
4159 13 : bGotGdalGT = true;
4160 91 : for (int i = 0; i < 6; i++)
4161 78 : adfTempGeoTransform[i] = CPLAtof(papszGeoTransform[i]);
4162 : }
4163 13 : CSLDestroy(papszGeoTransform);
4164 : }
4165 : else
4166 : {
4167 : // Look for corner array values.
4168 : // CPLDebug("GDAL_netCDF",
4169 : // "looking for geotransform corners");
4170 83 : bool bGotNN = false;
4171 83 : double dfNN = FetchCopyParam(pszGridMappingValue,
4172 : "Northernmost_Northing", 0, &bGotNN);
4173 :
4174 83 : bool bGotSN = false;
4175 83 : double dfSN = FetchCopyParam(pszGridMappingValue,
4176 : "Southernmost_Northing", 0, &bGotSN);
4177 :
4178 83 : bool bGotEE = false;
4179 83 : double dfEE = FetchCopyParam(pszGridMappingValue,
4180 : "Easternmost_Easting", 0, &bGotEE);
4181 :
4182 83 : bool bGotWE = false;
4183 83 : double dfWE = FetchCopyParam(pszGridMappingValue,
4184 : "Westernmost_Easting", 0, &bGotWE);
4185 :
4186 : // Only set the GeoTransform if we got all the values.
4187 83 : if (bGotNN && bGotSN && bGotEE && bGotWE)
4188 : {
4189 0 : bGotGdalGT = true;
4190 :
4191 0 : adfTempGeoTransform[0] = dfWE;
4192 0 : adfTempGeoTransform[1] =
4193 0 : (dfEE - dfWE) / (poDS->GetRasterXSize() - 1);
4194 0 : adfTempGeoTransform[2] = 0.0;
4195 0 : adfTempGeoTransform[3] = dfNN;
4196 0 : adfTempGeoTransform[4] = 0.0;
4197 0 : adfTempGeoTransform[5] =
4198 0 : (dfSN - dfNN) / (poDS->GetRasterYSize() - 1);
4199 : // Compute the center of the pixel.
4200 0 : adfTempGeoTransform[0] = dfWE - (adfTempGeoTransform[1] / 2);
4201 0 : adfTempGeoTransform[3] = dfNN - (adfTempGeoTransform[5] / 2);
4202 : }
4203 : } // (pszGeoTransform != NULL)
4204 :
4205 96 : if (bGotGdalSRS && !bGotGdalGT)
4206 77 : CPLDebug("GDAL_netCDF", "Got SRS but no geotransform from GDAL!");
4207 : }
4208 :
4209 495 : if (!pszWKT && !bGotCfSRS)
4210 : {
4211 : // Some netCDF files have a srid attribute (#6613) like
4212 : // urn:ogc:def:crs:EPSG::6931
4213 279 : const char *pszSRID = FetchAttr(pszGridMappingValue, "srid");
4214 279 : if (pszSRID != nullptr)
4215 : {
4216 0 : oSRS.Clear();
4217 0 : if (oSRS.SetFromUserInput(
4218 : pszSRID,
4219 : OGRSpatialReference::
4220 0 : SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE)
4221 : {
4222 0 : char *pszWKTExport = nullptr;
4223 0 : CPLDebug("GDAL_netCDF", "Got SRS from %s", pszSRID);
4224 0 : oSRS.exportToWkt(&pszWKTExport);
4225 0 : if (returnProjStr != nullptr)
4226 : {
4227 0 : (*returnProjStr) = std::string(pszWKTExport);
4228 : }
4229 : else
4230 : {
4231 0 : m_bAddedProjectionVarsDefs = true;
4232 0 : m_bAddedProjectionVarsData = true;
4233 0 : SetSpatialRefNoUpdate(&oSRS);
4234 : }
4235 0 : CPLFree(pszWKTExport);
4236 : }
4237 : }
4238 : }
4239 :
4240 495 : CPLFree(pszGridMappingValue);
4241 :
4242 495 : if (bReadSRSOnly)
4243 157 : return;
4244 :
4245 : // Determines the SRS to be used by the geolocation array, if any
4246 676 : std::string osGeolocWKT = SRS_WKT_WGS84_LAT_LONG;
4247 338 : if (!m_oSRS.IsEmpty())
4248 : {
4249 252 : OGRSpatialReference oGeogCRS;
4250 126 : oGeogCRS.CopyGeogCSFrom(&m_oSRS);
4251 126 : char *pszWKTTmp = nullptr;
4252 126 : const char *const apszOptions[] = {"FORMAT=WKT2_2019", nullptr};
4253 126 : if (oGeogCRS.exportToWkt(&pszWKTTmp, apszOptions) == OGRERR_NONE)
4254 : {
4255 126 : osGeolocWKT = pszWKTTmp;
4256 : }
4257 126 : CPLFree(pszWKTTmp);
4258 : }
4259 :
4260 : // Process geolocation arrays from CF "coordinates" attribute.
4261 676 : std::string osGeolocXName, osGeolocYName;
4262 338 : if (ProcessCFGeolocation(nGroupId, nVarId, osGeolocWKT, osGeolocXName,
4263 338 : osGeolocYName))
4264 : {
4265 51 : bool bCanCancelGT = true;
4266 51 : if ((nVarDimXID != -1) && (nVarDimYID != -1))
4267 : {
4268 : char szVarNameX[NC_MAX_NAME + 1];
4269 40 : CPL_IGNORE_RET_VAL(
4270 40 : nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
4271 : char szVarNameY[NC_MAX_NAME + 1];
4272 40 : CPL_IGNORE_RET_VAL(
4273 40 : nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
4274 40 : bCanCancelGT =
4275 40 : !(osGeolocXName == szVarNameX && osGeolocYName == szVarNameY);
4276 : }
4277 88 : if (bCanCancelGT && !m_oSRS.IsGeographic() && !m_oSRS.IsProjected() &&
4278 37 : !bSwitchedXY)
4279 : {
4280 35 : bGotCfGT = false;
4281 : }
4282 : }
4283 117 : else if (!bGotCfGT && !bReadSRSOnly && (nVarDimXID != -1) &&
4284 407 : (nVarDimYID != -1) && xdim > 0 && ydim > 0 &&
4285 3 : ((!bSwitchedXY &&
4286 3 : NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
4287 1 : NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr)) ||
4288 2 : (bSwitchedXY &&
4289 0 : NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
4290 0 : NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr))))
4291 : {
4292 : // Case of autotest/gdrivers/data/netcdf/GLMELT_4X5.OCN.nc
4293 : // which is indexed by lat, lon variables, but lat has irregular
4294 : // spacing.
4295 1 : const char *pszGeolocXFullName = poDS->papszDimName[poDS->nXDimID];
4296 1 : const char *pszGeolocYFullName = poDS->papszDimName[poDS->nYDimID];
4297 1 : if (bSwitchedXY)
4298 : {
4299 0 : std::swap(pszGeolocXFullName, pszGeolocYFullName);
4300 0 : GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", "GEOLOCATION");
4301 : }
4302 :
4303 1 : CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
4304 : pszGeolocXFullName, pszGeolocYFullName);
4305 :
4306 1 : GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
4307 : "GEOLOCATION");
4308 :
4309 2 : CPLString osTMP;
4310 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4311 1 : pszGeolocXFullName);
4312 :
4313 1 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
4314 1 : GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
4315 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4316 1 : pszGeolocYFullName);
4317 :
4318 1 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
4319 1 : GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
4320 :
4321 1 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
4322 1 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
4323 :
4324 1 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
4325 1 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
4326 :
4327 1 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
4328 : "PIXEL_CENTER", "GEOLOCATION");
4329 : }
4330 :
4331 : // Set GeoTransform if we got a complete one - after projection has been set
4332 338 : if (bGotCfGT || bGotGdalGT)
4333 : {
4334 192 : m_bAddedProjectionVarsDefs = true;
4335 192 : m_bAddedProjectionVarsData = true;
4336 192 : SetGeoTransformNoUpdate(adfTempGeoTransform);
4337 : }
4338 :
4339 : // Debugging reports.
4340 338 : CPLDebug("GDAL_netCDF",
4341 : "bGotGeogCS=%d bGotCfSRS=%d bGotCfGT=%d bGotCfWktSRS=%d "
4342 : "bGotGdalSRS=%d bGotGdalGT=%d",
4343 : static_cast<int>(bGotGeogCS), static_cast<int>(bGotCfSRS),
4344 : static_cast<int>(bGotCfGT), static_cast<int>(bGotCfWktSRS),
4345 : static_cast<int>(bGotGdalSRS), static_cast<int>(bGotGdalGT));
4346 :
4347 338 : if (!bGotCfGT && !bGotGdalGT)
4348 146 : CPLDebug("GDAL_netCDF", "did not get geotransform from CF nor GDAL!");
4349 :
4350 338 : if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfGT && !bGotCfWktSRS)
4351 146 : CPLDebug("GDAL_netCDF", "did not get projection from CF nor GDAL!");
4352 :
4353 : // wish of 6195
4354 : // we don't have CS/SRS, but we do have GT, and we live in -180,360 -90,90
4355 338 : if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfWktSRS)
4356 : {
4357 212 : if (bGotCfGT || bGotGdalGT)
4358 : {
4359 132 : bool bAssumedLongLat = CPLTestBool(CSLFetchNameValueDef(
4360 66 : papszOpenOptions, "ASSUME_LONGLAT",
4361 : CPLGetConfigOption("GDAL_NETCDF_ASSUME_LONGLAT", "NO")));
4362 :
4363 2 : if (bAssumedLongLat && adfTempGeoTransform[0] >= -180 &&
4364 2 : adfTempGeoTransform[0] < 360 &&
4365 4 : (adfTempGeoTransform[0] +
4366 2 : adfTempGeoTransform[1] * poDS->GetRasterXSize()) <= 360 &&
4367 70 : adfTempGeoTransform[3] <= 90 && adfTempGeoTransform[3] > -90 &&
4368 4 : (adfTempGeoTransform[3] +
4369 2 : adfTempGeoTransform[5] * poDS->GetRasterYSize()) >= -90)
4370 : {
4371 :
4372 2 : poDS->bIsGeographic = true;
4373 2 : char *pszTempProjection = nullptr;
4374 : // seems odd to use 4326 so OGC:CRS84
4375 2 : oSRS.SetFromUserInput("OGC:CRS84");
4376 2 : oSRS.exportToWkt(&pszTempProjection);
4377 2 : if (returnProjStr != nullptr)
4378 : {
4379 0 : (*returnProjStr) = std::string(pszTempProjection);
4380 : }
4381 : else
4382 : {
4383 2 : m_bAddedProjectionVarsDefs = true;
4384 2 : m_bAddedProjectionVarsData = true;
4385 2 : SetSpatialRefNoUpdate(&oSRS);
4386 : }
4387 2 : CPLFree(pszTempProjection);
4388 :
4389 2 : CPLDebug("netCDF",
4390 : "Assumed Longitude Latitude CRS 'OGC:CRS84' because "
4391 : "none otherwise available and geotransform within "
4392 : "suitable bounds. "
4393 : "Set GDAL_NETCDF_ASSUME_LONGLAT=NO as configuration "
4394 : "option or "
4395 : " ASSUME_LONGLAT=NO as open option to bypass this "
4396 : "assumption.");
4397 : }
4398 : }
4399 : }
4400 :
4401 : // Search for Well-known GeogCS if got only CF WKT
4402 : // Disabled for now, as a named datum also include control points
4403 : // (see mailing list and bug#4281
4404 : // For example, WGS84 vs. GDA94 (EPSG:3577) - AEA in netcdf_cf.py
4405 :
4406 : // Disabled for now, but could be set in a config option.
4407 : #if 0
4408 : bool bLookForWellKnownGCS = false; // This could be a Config Option.
4409 :
4410 : if( bLookForWellKnownGCS && bGotCfSRS && !bGotGdalSRS )
4411 : {
4412 : // ET - Could use a more exhaustive method by scanning all EPSG codes in
4413 : // data/gcs.csv as proposed by Even in the gdal-dev mailing list "help
4414 : // for comparing two WKT".
4415 : // This code could be contributed to a new function.
4416 : // OGRSpatialReference * OGRSpatialReference::FindMatchingGeogCS(
4417 : // const OGRSpatialReference *poOther) */
4418 : CPLDebug("GDAL_netCDF", "Searching for Well-known GeogCS");
4419 : const char *pszWKGCSList[] = { "WGS84", "WGS72", "NAD27", "NAD83" };
4420 : char *pszWKGCS = NULL;
4421 : oSRS.exportToPrettyWkt(&pszWKGCS);
4422 : for( size_t i = 0; i < sizeof(pszWKGCSList) / 8; i++ )
4423 : {
4424 : pszWKGCS = CPLStrdup(pszWKGCSList[i]);
4425 : OGRSpatialReference oSRSTmp;
4426 : oSRSTmp.SetWellKnownGeogCS(pszWKGCSList[i]);
4427 : // Set datum to unknown, bug #4281.
4428 : if( oSRSTmp.GetAttrNode("DATUM" ) )
4429 : oSRSTmp.GetAttrNode("DATUM")->GetChild(0)->SetValue("unknown");
4430 : // Could use OGRSpatialReference::StripCTParms(), but let's keep
4431 : // TOWGS84.
4432 : oSRSTmp.GetRoot()->StripNodes("AXIS");
4433 : oSRSTmp.GetRoot()->StripNodes("AUTHORITY");
4434 : oSRSTmp.GetRoot()->StripNodes("EXTENSION");
4435 :
4436 : oSRSTmp.exportToPrettyWkt(&pszWKGCS);
4437 : if( oSRS.IsSameGeogCS(&oSRSTmp) )
4438 : {
4439 : oSRS.SetWellKnownGeogCS(pszWKGCSList[i]);
4440 : oSRS.exportToWkt(&(pszTempProjection));
4441 : SetProjection(pszTempProjection);
4442 : CPLFree(pszTempProjection);
4443 : }
4444 : }
4445 : }
4446 : #endif
4447 : }
4448 :
4449 109 : void netCDFDataset::SetProjectionFromVar(int nGroupId, int nVarId,
4450 : bool bReadSRSOnly)
4451 : {
4452 109 : SetProjectionFromVar(nGroupId, nVarId, bReadSRSOnly, nullptr, nullptr,
4453 : nullptr, nullptr);
4454 109 : }
4455 :
4456 296 : bool netCDFDataset::ProcessNASAL2OceanGeoLocation(int nGroupId, int nVarId)
4457 : {
4458 : // Cf https://oceancolor.gsfc.nasa.gov/docs/format/l2nc/
4459 : // and https://github.com/OSGeo/gdal/issues/7605
4460 :
4461 : // Check for a structure like:
4462 : /* netcdf SNPP_VIIRS.20230406T024200.L2.OC.NRT {
4463 : dimensions:
4464 : number_of_lines = 3248 ;
4465 : pixels_per_line = 3200 ;
4466 : [...]
4467 : pixel_control_points = 3200 ;
4468 : [...]
4469 : group: geophysical_data {
4470 : variables:
4471 : short aot_862(number_of_lines, pixels_per_line) ; <-- nVarId
4472 : [...]
4473 : }
4474 : group: navigation_data {
4475 : variables:
4476 : float longitude(number_of_lines, pixel_control_points) ;
4477 : [...]
4478 : float latitude(number_of_lines, pixel_control_points) ;
4479 : [...]
4480 : }
4481 : }
4482 : */
4483 : // Note that the longitude and latitude arrays are not indexed by the
4484 : // same dimensions. Handle only the case where
4485 : // pixel_control_points == pixels_per_line
4486 : // If there was a subsampling of the geolocation arrays, we'd need to
4487 : // add more logic.
4488 :
4489 592 : std::string osGroupName;
4490 296 : osGroupName.resize(NC_MAX_NAME);
4491 296 : NCDF_ERR(nc_inq_grpname(nGroupId, &osGroupName[0]));
4492 296 : osGroupName.resize(strlen(osGroupName.data()));
4493 296 : if (osGroupName != "geophysical_data")
4494 295 : return false;
4495 :
4496 1 : int nVarDims = 0;
4497 1 : NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
4498 1 : if (nVarDims != 2)
4499 0 : return false;
4500 :
4501 1 : int nNavigationDataGrpId = 0;
4502 1 : if (nc_inq_grp_ncid(cdfid, "navigation_data", &nNavigationDataGrpId) !=
4503 : NC_NOERR)
4504 0 : return false;
4505 :
4506 : std::array<int, 2> anVarDimIds;
4507 1 : NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
4508 :
4509 1 : int nLongitudeId = 0;
4510 1 : int nLatitudeId = 0;
4511 1 : if (nc_inq_varid(nNavigationDataGrpId, "longitude", &nLongitudeId) !=
4512 2 : NC_NOERR ||
4513 1 : nc_inq_varid(nNavigationDataGrpId, "latitude", &nLatitudeId) !=
4514 : NC_NOERR)
4515 : {
4516 0 : return false;
4517 : }
4518 :
4519 1 : int nDimsLongitude = 0;
4520 1 : NCDF_ERR(
4521 : nc_inq_varndims(nNavigationDataGrpId, nLongitudeId, &nDimsLongitude));
4522 1 : int nDimsLatitude = 0;
4523 1 : NCDF_ERR(
4524 : nc_inq_varndims(nNavigationDataGrpId, nLatitudeId, &nDimsLatitude));
4525 1 : if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
4526 : {
4527 0 : return false;
4528 : }
4529 :
4530 : std::array<int, 2> anDimLongitudeIds;
4531 1 : NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLongitudeId,
4532 : anDimLongitudeIds.data()));
4533 : std::array<int, 2> anDimLatitudeIds;
4534 1 : NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLatitudeId,
4535 : anDimLatitudeIds.data()));
4536 1 : if (anDimLongitudeIds != anDimLatitudeIds)
4537 : {
4538 0 : return false;
4539 : }
4540 :
4541 : std::array<size_t, 2> anSizeVarDimIds;
4542 : std::array<size_t, 2> anSizeLongLatIds;
4543 2 : if (!(nc_inq_dimlen(cdfid, anVarDimIds[0], &anSizeVarDimIds[0]) ==
4544 1 : NC_NOERR &&
4545 1 : nc_inq_dimlen(cdfid, anVarDimIds[1], &anSizeVarDimIds[1]) ==
4546 1 : NC_NOERR &&
4547 1 : nc_inq_dimlen(cdfid, anDimLongitudeIds[0], &anSizeLongLatIds[0]) ==
4548 1 : NC_NOERR &&
4549 1 : nc_inq_dimlen(cdfid, anDimLongitudeIds[1], &anSizeLongLatIds[1]) ==
4550 : NC_NOERR &&
4551 1 : anSizeVarDimIds == anSizeLongLatIds))
4552 : {
4553 0 : return false;
4554 : }
4555 :
4556 1 : const char *pszGeolocXFullName = "/navigation_data/longitude";
4557 1 : const char *pszGeolocYFullName = "/navigation_data/latitude";
4558 :
4559 1 : if (bSwitchedXY)
4560 : {
4561 0 : std::swap(pszGeolocXFullName, pszGeolocYFullName);
4562 0 : GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", "GEOLOCATION");
4563 : }
4564 :
4565 1 : CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
4566 : pszGeolocXFullName, pszGeolocYFullName);
4567 :
4568 1 : GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
4569 : "GEOLOCATION");
4570 :
4571 1 : CPLString osTMP;
4572 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
4573 :
4574 1 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
4575 1 : GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
4576 1 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
4577 :
4578 1 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
4579 1 : GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
4580 :
4581 1 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
4582 1 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
4583 :
4584 1 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
4585 1 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
4586 :
4587 1 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
4588 : "GEOLOCATION");
4589 1 : return true;
4590 : }
4591 :
4592 295 : bool netCDFDataset::ProcessNASAEMITGeoLocation(int nGroupId, int nVarId)
4593 : {
4594 : // Cf https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/
4595 :
4596 : // Check for a structure like:
4597 : /* netcdf EMIT_L2A_RFL_001_20220903T163129_2224611_012 {
4598 : dimensions:
4599 : downtrack = 1280 ;
4600 : crosstrack = 1242 ;
4601 : bands = 285 ;
4602 : [...]
4603 :
4604 : variables:
4605 : float reflectance(downtrack, crosstrack, bands) ;
4606 :
4607 : group: location {
4608 : variables:
4609 : double lon(downtrack, crosstrack) ;
4610 : lon:_FillValue = -9999. ;
4611 : lon:long_name = "Longitude (WGS-84)" ;
4612 : lon:units = "degrees east" ;
4613 : double lat(downtrack, crosstrack) ;
4614 : lat:_FillValue = -9999. ;
4615 : lat:long_name = "Latitude (WGS-84)" ;
4616 : lat:units = "degrees north" ;
4617 : } // group location
4618 :
4619 : }
4620 : or
4621 : netcdf EMIT_L2B_MIN_001_20231024T055538_2329704_040 {
4622 : dimensions:
4623 : downtrack = 1664 ;
4624 : crosstrack = 1242 ;
4625 : [...]
4626 : variables:
4627 : float group_1_band_depth(downtrack, crosstrack) ;
4628 : group_1_band_depth:_FillValue = -9999.f ;
4629 : group_1_band_depth:long_name = "Group 1 Band Depth" ;
4630 : group_1_band_depth:units = "unitless" ;
4631 : [...]
4632 : group: location {
4633 : variables:
4634 : double lon(downtrack, crosstrack) ;
4635 : lon:_FillValue = -9999. ;
4636 : lon:long_name = "Longitude (WGS-84)" ;
4637 : lon:units = "degrees east" ;
4638 : double lat(downtrack, crosstrack) ;
4639 : lat:_FillValue = -9999. ;
4640 : lat:long_name = "Latitude (WGS-84)" ;
4641 : lat:units = "degrees north" ;
4642 : }
4643 : */
4644 :
4645 295 : int nVarDims = 0;
4646 295 : NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
4647 295 : if (nVarDims != 2 && nVarDims != 3)
4648 14 : return false;
4649 :
4650 281 : int nLocationGrpId = 0;
4651 281 : if (nc_inq_grp_ncid(cdfid, "location", &nLocationGrpId) != NC_NOERR)
4652 62 : return false;
4653 :
4654 : std::array<int, 3> anVarDimIds;
4655 219 : NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
4656 219 : if (nYDimID != anVarDimIds[0] || nXDimID != anVarDimIds[1])
4657 18 : return false;
4658 :
4659 201 : int nLongitudeId = 0;
4660 201 : int nLatitudeId = 0;
4661 252 : if (nc_inq_varid(nLocationGrpId, "lon", &nLongitudeId) != NC_NOERR ||
4662 51 : nc_inq_varid(nLocationGrpId, "lat", &nLatitudeId) != NC_NOERR)
4663 : {
4664 150 : return false;
4665 : }
4666 :
4667 51 : int nDimsLongitude = 0;
4668 51 : NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLongitudeId, &nDimsLongitude));
4669 51 : int nDimsLatitude = 0;
4670 51 : NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLatitudeId, &nDimsLatitude));
4671 51 : if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
4672 : {
4673 31 : return false;
4674 : }
4675 :
4676 : std::array<int, 2> anDimLongitudeIds;
4677 20 : NCDF_ERR(nc_inq_vardimid(nLocationGrpId, nLongitudeId,
4678 : anDimLongitudeIds.data()));
4679 : std::array<int, 2> anDimLatitudeIds;
4680 20 : NCDF_ERR(
4681 : nc_inq_vardimid(nLocationGrpId, nLatitudeId, anDimLatitudeIds.data()));
4682 20 : if (anDimLongitudeIds != anDimLatitudeIds)
4683 : {
4684 0 : return false;
4685 : }
4686 :
4687 40 : if (anDimLongitudeIds[0] != anVarDimIds[0] ||
4688 20 : anDimLongitudeIds[1] != anVarDimIds[1])
4689 : {
4690 0 : return false;
4691 : }
4692 :
4693 20 : const char *pszGeolocXFullName = "/location/lon";
4694 20 : const char *pszGeolocYFullName = "/location/lat";
4695 :
4696 20 : CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
4697 : pszGeolocXFullName, pszGeolocYFullName);
4698 :
4699 20 : GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
4700 : "GEOLOCATION");
4701 :
4702 20 : CPLString osTMP;
4703 20 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
4704 :
4705 20 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
4706 20 : GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
4707 20 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
4708 :
4709 20 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
4710 20 : GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
4711 :
4712 20 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
4713 20 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
4714 :
4715 20 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
4716 20 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
4717 :
4718 20 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
4719 : "GEOLOCATION");
4720 20 : return true;
4721 : }
4722 :
4723 338 : int netCDFDataset::ProcessCFGeolocation(int nGroupId, int nVarId,
4724 : const std::string &osGeolocWKT,
4725 : std::string &osGeolocXNameOut,
4726 : std::string &osGeolocYNameOut)
4727 : {
4728 338 : bool bAddGeoloc = false;
4729 338 : char *pszTemp = nullptr;
4730 :
4731 338 : if (NCDFGetAttr(nGroupId, nVarId, "coordinates", &pszTemp) == CE_None)
4732 : {
4733 : // Get X and Y geolocation names from coordinates attribute.
4734 42 : char **papszTokens = NCDFTokenizeCoordinatesAttribute(pszTemp);
4735 42 : if (CSLCount(papszTokens) >= 2)
4736 : {
4737 : char szGeolocXName[NC_MAX_NAME + 1];
4738 : char szGeolocYName[NC_MAX_NAME + 1];
4739 39 : szGeolocXName[0] = '\0';
4740 39 : szGeolocYName[0] = '\0';
4741 :
4742 : // Test that each variable is longitude/latitude.
4743 126 : for (int i = 0; i < CSLCount(papszTokens); i++)
4744 : {
4745 87 : if (NCDFIsVarLongitude(nGroupId, -1, papszTokens[i]))
4746 : {
4747 32 : int nOtherGroupId = -1;
4748 32 : int nOtherVarId = -1;
4749 : // Check that the variable actually exists
4750 : // Needed on Sentinel-3 products
4751 32 : if (NCDFResolveVar(nGroupId, papszTokens[i], &nOtherGroupId,
4752 32 : &nOtherVarId) == CE_None)
4753 : {
4754 30 : snprintf(szGeolocXName, sizeof(szGeolocXName), "%s",
4755 30 : papszTokens[i]);
4756 : }
4757 : }
4758 55 : else if (NCDFIsVarLatitude(nGroupId, -1, papszTokens[i]))
4759 : {
4760 32 : int nOtherGroupId = -1;
4761 32 : int nOtherVarId = -1;
4762 : // Check that the variable actually exists
4763 : // Needed on Sentinel-3 products
4764 32 : if (NCDFResolveVar(nGroupId, papszTokens[i], &nOtherGroupId,
4765 32 : &nOtherVarId) == CE_None)
4766 : {
4767 30 : snprintf(szGeolocYName, sizeof(szGeolocYName), "%s",
4768 30 : papszTokens[i]);
4769 : }
4770 : }
4771 : }
4772 : // Add GEOLOCATION metadata.
4773 39 : if (!EQUAL(szGeolocXName, "") && !EQUAL(szGeolocYName, ""))
4774 : {
4775 30 : osGeolocXNameOut = szGeolocXName;
4776 30 : osGeolocYNameOut = szGeolocYName;
4777 :
4778 30 : char *pszGeolocXFullName = nullptr;
4779 30 : char *pszGeolocYFullName = nullptr;
4780 30 : if (NCDFResolveVarFullName(nGroupId, szGeolocXName,
4781 60 : &pszGeolocXFullName) == CE_None &&
4782 30 : NCDFResolveVarFullName(nGroupId, szGeolocYName,
4783 : &pszGeolocYFullName) == CE_None)
4784 : {
4785 30 : if (bSwitchedXY)
4786 : {
4787 2 : std::swap(pszGeolocXFullName, pszGeolocYFullName);
4788 2 : GDALPamDataset::SetMetadataItem("SWAP_XY", "YES",
4789 : "GEOLOCATION");
4790 : }
4791 :
4792 30 : bAddGeoloc = true;
4793 30 : CPLDebug("GDAL_netCDF",
4794 : "using variables %s and %s for GEOLOCATION",
4795 : pszGeolocXFullName, pszGeolocYFullName);
4796 :
4797 30 : GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
4798 : "GEOLOCATION");
4799 :
4800 60 : CPLString osTMP;
4801 30 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4802 30 : pszGeolocXFullName);
4803 :
4804 30 : GDALPamDataset::SetMetadataItem("X_DATASET", osTMP,
4805 : "GEOLOCATION");
4806 30 : GDALPamDataset::SetMetadataItem("X_BAND", "1",
4807 : "GEOLOCATION");
4808 30 : osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4809 30 : pszGeolocYFullName);
4810 :
4811 30 : GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP,
4812 : "GEOLOCATION");
4813 30 : GDALPamDataset::SetMetadataItem("Y_BAND", "1",
4814 : "GEOLOCATION");
4815 :
4816 30 : GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0",
4817 : "GEOLOCATION");
4818 30 : GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1",
4819 : "GEOLOCATION");
4820 :
4821 30 : GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0",
4822 : "GEOLOCATION");
4823 30 : GDALPamDataset::SetMetadataItem("LINE_STEP", "1",
4824 : "GEOLOCATION");
4825 :
4826 30 : GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
4827 : "PIXEL_CENTER",
4828 : "GEOLOCATION");
4829 : }
4830 : else
4831 : {
4832 0 : CPLDebug("GDAL_netCDF",
4833 : "cannot resolve location of "
4834 : "lat/lon variables specified by the coordinates "
4835 : "attribute [%s]",
4836 : pszTemp);
4837 : }
4838 30 : CPLFree(pszGeolocXFullName);
4839 30 : CPLFree(pszGeolocYFullName);
4840 : }
4841 : else
4842 : {
4843 9 : CPLDebug("GDAL_netCDF",
4844 : "coordinates attribute [%s] is unsupported", pszTemp);
4845 : }
4846 : }
4847 : else
4848 : {
4849 3 : CPLDebug("GDAL_netCDF",
4850 : "coordinates attribute [%s] with %d element(s) is "
4851 : "unsupported",
4852 : pszTemp, CSLCount(papszTokens));
4853 : }
4854 42 : if (papszTokens)
4855 42 : CSLDestroy(papszTokens);
4856 : }
4857 :
4858 : else
4859 : {
4860 296 : bAddGeoloc = ProcessNASAL2OceanGeoLocation(nGroupId, nVarId);
4861 :
4862 296 : if (!bAddGeoloc)
4863 295 : bAddGeoloc = ProcessNASAEMITGeoLocation(nGroupId, nVarId);
4864 : }
4865 :
4866 338 : CPLFree(pszTemp);
4867 :
4868 338 : return bAddGeoloc;
4869 : }
4870 :
4871 8 : CPLErr netCDFDataset::Set1DGeolocation(int nGroupId, int nVarId,
4872 : const char *szDimName)
4873 : {
4874 : // Get values.
4875 8 : char *pszVarValues = nullptr;
4876 8 : CPLErr eErr = NCDFGet1DVar(nGroupId, nVarId, &pszVarValues);
4877 8 : if (eErr != CE_None)
4878 0 : return eErr;
4879 :
4880 : // Write metadata.
4881 8 : char szTemp[NC_MAX_NAME + 1 + 32] = {};
4882 8 : snprintf(szTemp, sizeof(szTemp), "%s_VALUES", szDimName);
4883 8 : GDALPamDataset::SetMetadataItem(szTemp, pszVarValues, "GEOLOCATION2");
4884 :
4885 8 : CPLFree(pszVarValues);
4886 :
4887 8 : return CE_None;
4888 : }
4889 :
4890 0 : double *netCDFDataset::Get1DGeolocation(CPL_UNUSED const char *szDimName,
4891 : int &nVarLen)
4892 : {
4893 0 : nVarLen = 0;
4894 :
4895 : // Get Y_VALUES as tokens.
4896 : char **papszValues =
4897 0 : NCDFTokenizeArray(GetMetadataItem("Y_VALUES", "GEOLOCATION2"));
4898 0 : if (papszValues == nullptr)
4899 0 : return nullptr;
4900 :
4901 : // Initialize and fill array.
4902 0 : nVarLen = CSLCount(papszValues);
4903 : double *pdfVarValues =
4904 0 : static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
4905 :
4906 0 : for (int i = 0, j = 0; i < nVarLen; i++)
4907 : {
4908 0 : if (!bBottomUp)
4909 0 : j = nVarLen - 1 - i;
4910 : else
4911 0 : j = i; // Invert latitude values.
4912 0 : char *pszTemp = nullptr;
4913 0 : pdfVarValues[j] = CPLStrtod(papszValues[i], &pszTemp);
4914 : }
4915 0 : CSLDestroy(papszValues);
4916 :
4917 0 : return pdfVarValues;
4918 : }
4919 :
4920 : /************************************************************************/
4921 : /* SetSpatialRefNoUpdate() */
4922 : /************************************************************************/
4923 :
4924 242 : void netCDFDataset::SetSpatialRefNoUpdate(const OGRSpatialReference *poSRS)
4925 : {
4926 242 : m_oSRS.Clear();
4927 242 : if (poSRS)
4928 235 : m_oSRS = *poSRS;
4929 242 : m_bHasProjection = true;
4930 242 : }
4931 :
4932 : /************************************************************************/
4933 : /* SetSpatialRef() */
4934 : /************************************************************************/
4935 :
4936 71 : CPLErr netCDFDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
4937 : {
4938 142 : CPLMutexHolderD(&hNCMutex);
4939 :
4940 71 : if (GetAccess() != GA_Update || m_bHasProjection)
4941 : {
4942 0 : CPLError(CE_Failure, CPLE_AppDefined,
4943 : "netCDFDataset::_SetProjection() should only be called once "
4944 : "in update mode!");
4945 0 : return CE_Failure;
4946 : }
4947 :
4948 71 : if (m_bHasGeoTransform)
4949 : {
4950 32 : SetSpatialRefNoUpdate(poSRS);
4951 :
4952 : // For NC4/NC4C, writing both projection variables and data,
4953 : // followed by redefining nodata value, cancels the projection
4954 : // info from the Band variable, so for now only write the
4955 : // variable definitions, and write data at the end.
4956 : // See https://trac.osgeo.org/gdal/ticket/7245
4957 32 : return AddProjectionVars(true, nullptr, nullptr);
4958 : }
4959 :
4960 39 : SetSpatialRefNoUpdate(poSRS);
4961 :
4962 39 : return CE_None;
4963 : }
4964 :
4965 : /************************************************************************/
4966 : /* SetGeoTransformNoUpdate() */
4967 : /************************************************************************/
4968 :
4969 264 : void netCDFDataset::SetGeoTransformNoUpdate(double *padfTransform)
4970 : {
4971 264 : memcpy(m_adfGeoTransform, padfTransform, sizeof(double) * 6);
4972 264 : m_bHasGeoTransform = true;
4973 264 : }
4974 :
4975 : /************************************************************************/
4976 : /* SetGeoTransform() */
4977 : /************************************************************************/
4978 :
4979 72 : CPLErr netCDFDataset::SetGeoTransform(double *padfTransform)
4980 : {
4981 144 : CPLMutexHolderD(&hNCMutex);
4982 :
4983 72 : if (GetAccess() != GA_Update || m_bHasGeoTransform)
4984 : {
4985 0 : CPLError(CE_Failure, CPLE_AppDefined,
4986 : "netCDFDataset::SetGeoTransform() should only be called once "
4987 : "in update mode!");
4988 0 : return CE_Failure;
4989 : }
4990 :
4991 72 : CPLDebug("GDAL_netCDF", "SetGeoTransform(%f,%f,%f,%f,%f,%f)",
4992 72 : padfTransform[0], padfTransform[1], padfTransform[2],
4993 72 : padfTransform[3], padfTransform[4], padfTransform[5]);
4994 :
4995 72 : if (m_bHasProjection)
4996 : {
4997 2 : SetGeoTransformNoUpdate(padfTransform);
4998 :
4999 : // For NC4/NC4C, writing both projection variables and data,
5000 : // followed by redefining nodata value, cancels the projection
5001 : // info from the Band variable, so for now only write the
5002 : // variable definitions, and write data at the end.
5003 : // See https://trac.osgeo.org/gdal/ticket/7245
5004 2 : return AddProjectionVars(true, nullptr, nullptr);
5005 : }
5006 :
5007 70 : SetGeoTransformNoUpdate(padfTransform);
5008 70 : return CE_None;
5009 : }
5010 :
5011 : /************************************************************************/
5012 : /* NCDFWriteSRSVariable() */
5013 : /************************************************************************/
5014 :
5015 123 : int NCDFWriteSRSVariable(int cdfid, const OGRSpatialReference *poSRS,
5016 : char **ppszCFProjection, bool bWriteGDALTags,
5017 : const std::string &srsVarName)
5018 : {
5019 123 : char *pszCFProjection = nullptr;
5020 123 : char **papszKeyValues = nullptr;
5021 123 : poSRS->exportToCF1(&pszCFProjection, &papszKeyValues, nullptr, nullptr);
5022 :
5023 123 : if (bWriteGDALTags)
5024 : {
5025 122 : const char *pszWKT = CSLFetchNameValue(papszKeyValues, NCDF_CRS_WKT);
5026 122 : if (pszWKT)
5027 : {
5028 : // SPATIAL_REF is deprecated. Will be removed in GDAL 4.
5029 122 : papszKeyValues =
5030 122 : CSLSetNameValue(papszKeyValues, NCDF_SPATIAL_REF, pszWKT);
5031 : }
5032 : }
5033 :
5034 123 : const int nValues = CSLCount(papszKeyValues);
5035 :
5036 : int NCDFVarID;
5037 246 : std::string varNameRadix(pszCFProjection);
5038 123 : int nCounter = 2;
5039 : while (true)
5040 : {
5041 125 : NCDFVarID = -1;
5042 125 : nc_inq_varid(cdfid, pszCFProjection, &NCDFVarID);
5043 125 : if (NCDFVarID < 0)
5044 120 : break;
5045 :
5046 5 : int nbAttr = 0;
5047 5 : NCDF_ERR(nc_inq_varnatts(cdfid, NCDFVarID, &nbAttr));
5048 5 : bool bSame = nbAttr == nValues;
5049 41 : for (int i = 0; bSame && (i < nbAttr); i++)
5050 : {
5051 : char szAttrName[NC_MAX_NAME + 1];
5052 38 : szAttrName[0] = 0;
5053 38 : NCDF_ERR(nc_inq_attname(cdfid, NCDFVarID, i, szAttrName));
5054 :
5055 : const char *pszValue =
5056 38 : CSLFetchNameValue(papszKeyValues, szAttrName);
5057 38 : if (!pszValue)
5058 : {
5059 0 : bSame = false;
5060 2 : break;
5061 : }
5062 :
5063 38 : nc_type atttype = NC_NAT;
5064 38 : size_t attlen = 0;
5065 38 : NCDF_ERR(
5066 : nc_inq_att(cdfid, NCDFVarID, szAttrName, &atttype, &attlen));
5067 38 : if (atttype != NC_CHAR && atttype != NC_DOUBLE)
5068 : {
5069 0 : bSame = false;
5070 0 : break;
5071 : }
5072 38 : if (atttype == NC_CHAR)
5073 : {
5074 15 : if (CPLGetValueType(pszValue) != CPL_VALUE_STRING)
5075 : {
5076 0 : bSame = false;
5077 0 : break;
5078 : }
5079 15 : std::string val;
5080 15 : val.resize(attlen);
5081 15 : nc_get_att_text(cdfid, NCDFVarID, szAttrName, &val[0]);
5082 15 : if (val != pszValue)
5083 : {
5084 0 : bSame = false;
5085 0 : break;
5086 : }
5087 : }
5088 : else
5089 : {
5090 : const CPLStringList aosTokens(
5091 23 : CSLTokenizeString2(pszValue, ",", 0));
5092 23 : if (static_cast<size_t>(aosTokens.size()) != attlen)
5093 : {
5094 0 : bSame = false;
5095 0 : break;
5096 : }
5097 : double vals[2];
5098 23 : nc_get_att_double(cdfid, NCDFVarID, szAttrName, vals);
5099 44 : if (vals[0] != CPLAtof(aosTokens[0]) ||
5100 21 : (attlen == 2 && vals[1] != CPLAtof(aosTokens[1])))
5101 : {
5102 2 : bSame = false;
5103 2 : break;
5104 : }
5105 : }
5106 : }
5107 5 : if (bSame)
5108 : {
5109 3 : *ppszCFProjection = pszCFProjection;
5110 3 : CSLDestroy(papszKeyValues);
5111 3 : return NCDFVarID;
5112 : }
5113 2 : CPLFree(pszCFProjection);
5114 2 : pszCFProjection =
5115 2 : CPLStrdup(CPLSPrintf("%s_%d", varNameRadix.c_str(), nCounter));
5116 2 : nCounter++;
5117 2 : }
5118 :
5119 120 : *ppszCFProjection = pszCFProjection;
5120 :
5121 : const char *pszVarName;
5122 :
5123 120 : if (srsVarName != "")
5124 : {
5125 38 : pszVarName = srsVarName.c_str();
5126 : }
5127 : else
5128 : {
5129 82 : pszVarName = pszCFProjection;
5130 : }
5131 :
5132 120 : int status = nc_def_var(cdfid, pszVarName, NC_CHAR, 0, nullptr, &NCDFVarID);
5133 120 : NCDF_ERR(status);
5134 1144 : for (int i = 0; i < nValues; ++i)
5135 : {
5136 1024 : char *pszKey = nullptr;
5137 1024 : const char *pszValue = CPLParseNameValue(papszKeyValues[i], &pszKey);
5138 1024 : if (pszKey && pszValue)
5139 : {
5140 2048 : const CPLStringList aosTokens(CSLTokenizeString2(pszValue, ",", 0));
5141 1024 : double adfValues[2] = {0, 0};
5142 1024 : const int nDoubleCount = std::min(2, aosTokens.size());
5143 1024 : if (!(aosTokens.size() == 2 &&
5144 2047 : CPLGetValueType(aosTokens[0]) != CPL_VALUE_STRING) &&
5145 1023 : CPLGetValueType(pszValue) == CPL_VALUE_STRING)
5146 : {
5147 479 : status = nc_put_att_text(cdfid, NCDFVarID, pszKey,
5148 : strlen(pszValue), pszValue);
5149 : }
5150 : else
5151 : {
5152 1091 : for (int j = 0; j < nDoubleCount; ++j)
5153 546 : adfValues[j] = CPLAtof(aosTokens[j]);
5154 545 : status = nc_put_att_double(cdfid, NCDFVarID, pszKey, NC_DOUBLE,
5155 : nDoubleCount, adfValues);
5156 : }
5157 1024 : NCDF_ERR(status);
5158 : }
5159 1024 : CPLFree(pszKey);
5160 : }
5161 :
5162 120 : CSLDestroy(papszKeyValues);
5163 120 : return NCDFVarID;
5164 : }
5165 :
5166 : /************************************************************************/
5167 : /* NCDFWriteLonLatVarsAttributes() */
5168 : /************************************************************************/
5169 :
5170 99 : void NCDFWriteLonLatVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarLonID,
5171 : int nVarLatID)
5172 : {
5173 :
5174 : try
5175 : {
5176 99 : vcdf.nc_put_vatt_text(nVarLatID, CF_STD_NAME, CF_LATITUDE_STD_NAME);
5177 99 : vcdf.nc_put_vatt_text(nVarLatID, CF_LNG_NAME, CF_LATITUDE_LNG_NAME);
5178 99 : vcdf.nc_put_vatt_text(nVarLatID, CF_UNITS, CF_DEGREES_NORTH);
5179 99 : vcdf.nc_put_vatt_text(nVarLonID, CF_STD_NAME, CF_LONGITUDE_STD_NAME);
5180 99 : vcdf.nc_put_vatt_text(nVarLonID, CF_LNG_NAME, CF_LONGITUDE_LNG_NAME);
5181 99 : vcdf.nc_put_vatt_text(nVarLonID, CF_UNITS, CF_DEGREES_EAST);
5182 : }
5183 0 : catch (nccfdriver::SG_Exception &e)
5184 : {
5185 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5186 : }
5187 99 : }
5188 :
5189 : /************************************************************************/
5190 : /* NCDFWriteRLonRLatVarsAttributes() */
5191 : /************************************************************************/
5192 :
5193 0 : void NCDFWriteRLonRLatVarsAttributes(nccfdriver::netCDFVID &vcdf,
5194 : int nVarRLonID, int nVarRLatID)
5195 : {
5196 : try
5197 : {
5198 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_STD_NAME, "grid_latitude");
5199 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_LNG_NAME,
5200 : "latitude in rotated pole grid");
5201 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_UNITS, "degrees");
5202 0 : vcdf.nc_put_vatt_text(nVarRLatID, CF_AXIS, "Y");
5203 :
5204 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_STD_NAME, "grid_longitude");
5205 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_LNG_NAME,
5206 : "longitude in rotated pole grid");
5207 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_UNITS, "degrees");
5208 0 : vcdf.nc_put_vatt_text(nVarRLonID, CF_AXIS, "X");
5209 : }
5210 0 : catch (nccfdriver::SG_Exception &e)
5211 : {
5212 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5213 : }
5214 0 : }
5215 :
5216 : /************************************************************************/
5217 : /* NCDFGetProjectedCFUnit() */
5218 : /************************************************************************/
5219 :
5220 37 : std::string NCDFGetProjectedCFUnit(const OGRSpatialReference *poSRS)
5221 : {
5222 37 : char *pszUnitsToWrite = nullptr;
5223 37 : poSRS->exportToCF1(nullptr, nullptr, &pszUnitsToWrite, nullptr);
5224 37 : const std::string osRet = pszUnitsToWrite ? pszUnitsToWrite : std::string();
5225 37 : CPLFree(pszUnitsToWrite);
5226 74 : return osRet;
5227 : }
5228 :
5229 : /************************************************************************/
5230 : /* NCDFWriteXYVarsAttributes() */
5231 : /************************************************************************/
5232 :
5233 26 : void NCDFWriteXYVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarXID,
5234 : int nVarYID, const OGRSpatialReference *poSRS)
5235 : {
5236 52 : const std::string osUnitsToWrite = NCDFGetProjectedCFUnit(poSRS);
5237 :
5238 : try
5239 : {
5240 26 : vcdf.nc_put_vatt_text(nVarXID, CF_STD_NAME, CF_PROJ_X_COORD);
5241 26 : vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME, CF_PROJ_X_COORD_LONG_NAME);
5242 26 : if (!osUnitsToWrite.empty())
5243 26 : vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, osUnitsToWrite.c_str());
5244 26 : vcdf.nc_put_vatt_text(nVarYID, CF_STD_NAME, CF_PROJ_Y_COORD);
5245 26 : vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME, CF_PROJ_Y_COORD_LONG_NAME);
5246 26 : if (!osUnitsToWrite.empty())
5247 26 : vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, osUnitsToWrite.c_str());
5248 : }
5249 0 : catch (nccfdriver::SG_Exception &e)
5250 : {
5251 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5252 : }
5253 26 : }
5254 :
5255 : /************************************************************************/
5256 : /* AddProjectionVars() */
5257 : /************************************************************************/
5258 :
5259 154 : CPLErr netCDFDataset::AddProjectionVars(bool bDefsOnly,
5260 : GDALProgressFunc pfnProgress,
5261 : void *pProgressData)
5262 : {
5263 154 : if (nCFVersion >= 1.8)
5264 0 : return CE_None; // do nothing
5265 :
5266 154 : bool bWriteGridMapping = false;
5267 154 : bool bWriteLonLat = false;
5268 154 : bool bHasGeoloc = false;
5269 154 : bool bWriteGDALTags = false;
5270 154 : bool bWriteGeoTransform = false;
5271 :
5272 : // For GEOLOCATION information.
5273 154 : GDALDatasetH hDS_X = nullptr;
5274 154 : GDALRasterBandH hBand_X = nullptr;
5275 154 : GDALDatasetH hDS_Y = nullptr;
5276 154 : GDALRasterBandH hBand_Y = nullptr;
5277 :
5278 308 : OGRSpatialReference oSRS(m_oSRS);
5279 154 : if (!m_oSRS.IsEmpty())
5280 : {
5281 128 : if (oSRS.IsProjected())
5282 44 : bIsProjected = true;
5283 84 : else if (oSRS.IsGeographic())
5284 84 : bIsGeographic = true;
5285 : }
5286 :
5287 154 : if (bDefsOnly)
5288 : {
5289 77 : char *pszProjection = nullptr;
5290 77 : m_oSRS.exportToWkt(&pszProjection);
5291 77 : CPLDebug("GDAL_netCDF",
5292 : "SetProjection, WKT now = [%s]\nprojected: %d geographic: %d",
5293 77 : pszProjection ? pszProjection : "(null)",
5294 77 : static_cast<int>(bIsProjected),
5295 77 : static_cast<int>(bIsGeographic));
5296 77 : CPLFree(pszProjection);
5297 :
5298 77 : if (!m_bHasGeoTransform)
5299 5 : CPLDebug("GDAL_netCDF",
5300 : "netCDFDataset::AddProjectionVars() called, "
5301 : "but GeoTransform has not yet been defined!");
5302 :
5303 77 : if (!m_bHasProjection)
5304 6 : CPLDebug("GDAL_netCDF",
5305 : "netCDFDataset::AddProjectionVars() called, "
5306 : "but Projection has not yet been defined!");
5307 : }
5308 :
5309 : // Check GEOLOCATION information.
5310 154 : char **papszGeolocationInfo = netCDFDataset::GetMetadata("GEOLOCATION");
5311 154 : if (papszGeolocationInfo != nullptr)
5312 : {
5313 : // Look for geolocation datasets.
5314 : const char *pszDSName =
5315 10 : CSLFetchNameValue(papszGeolocationInfo, "X_DATASET");
5316 10 : if (pszDSName != nullptr)
5317 10 : hDS_X = GDALOpenShared(pszDSName, GA_ReadOnly);
5318 10 : pszDSName = CSLFetchNameValue(papszGeolocationInfo, "Y_DATASET");
5319 10 : if (pszDSName != nullptr)
5320 10 : hDS_Y = GDALOpenShared(pszDSName, GA_ReadOnly);
5321 :
5322 10 : if (hDS_X != nullptr && hDS_Y != nullptr)
5323 : {
5324 10 : int nBand = std::max(1, atoi(CSLFetchNameValueDef(
5325 10 : papszGeolocationInfo, "X_BAND", "0")));
5326 10 : hBand_X = GDALGetRasterBand(hDS_X, nBand);
5327 10 : nBand = std::max(1, atoi(CSLFetchNameValueDef(papszGeolocationInfo,
5328 10 : "Y_BAND", "0")));
5329 10 : hBand_Y = GDALGetRasterBand(hDS_Y, nBand);
5330 :
5331 : // If geoloc bands are found, do basic validation based on their
5332 : // dimensions.
5333 10 : if (hBand_X != nullptr && hBand_Y != nullptr)
5334 : {
5335 10 : int nXSize_XBand = GDALGetRasterXSize(hDS_X);
5336 10 : int nYSize_XBand = GDALGetRasterYSize(hDS_X);
5337 10 : int nXSize_YBand = GDALGetRasterXSize(hDS_Y);
5338 10 : int nYSize_YBand = GDALGetRasterYSize(hDS_Y);
5339 :
5340 : // TODO 1D geolocation arrays not implemented.
5341 10 : if (nYSize_XBand == 1 && nYSize_YBand == 1)
5342 : {
5343 0 : bHasGeoloc = false;
5344 0 : CPLDebug("GDAL_netCDF",
5345 : "1D GEOLOCATION arrays not supported yet");
5346 : }
5347 : // 2D bands must have same sizes as the raster bands.
5348 10 : else if (nXSize_XBand != nRasterXSize ||
5349 10 : nYSize_XBand != nRasterYSize ||
5350 10 : nXSize_YBand != nRasterXSize ||
5351 10 : nYSize_YBand != nRasterYSize)
5352 : {
5353 0 : bHasGeoloc = false;
5354 0 : CPLDebug("GDAL_netCDF",
5355 : "GEOLOCATION array sizes (%dx%d %dx%d) differ "
5356 : "from raster (%dx%d), not supported",
5357 : nXSize_XBand, nYSize_XBand, nXSize_YBand,
5358 : nYSize_YBand, nRasterXSize, nRasterYSize);
5359 : }
5360 : else
5361 : {
5362 10 : bHasGeoloc = true;
5363 10 : CPLDebug("GDAL_netCDF",
5364 : "dataset has GEOLOCATION information, will try to "
5365 : "write it");
5366 : }
5367 : }
5368 : }
5369 : }
5370 :
5371 : // Process projection options.
5372 154 : if (bIsProjected)
5373 : {
5374 : bool bIsCfProjection =
5375 44 : oSRS.exportToCF1(nullptr, nullptr, nullptr, nullptr) == OGRERR_NONE;
5376 44 : bWriteGridMapping = true;
5377 44 : bWriteGDALTags = CPL_TO_BOOL(
5378 44 : CSLFetchBoolean(papszCreationOptions, "WRITE_GDAL_TAGS", TRUE));
5379 : // Force WRITE_GDAL_TAGS if is not a CF projection.
5380 44 : if (!bWriteGDALTags && !bIsCfProjection)
5381 0 : bWriteGDALTags = true;
5382 44 : if (bWriteGDALTags)
5383 44 : bWriteGeoTransform = true;
5384 :
5385 : // Write lon/lat: default is NO, except if has geolocation.
5386 : // With IF_NEEDED: write if has geoloc or is not CF projection.
5387 : const char *pszValue =
5388 44 : CSLFetchNameValue(papszCreationOptions, "WRITE_LONLAT");
5389 44 : if (pszValue)
5390 : {
5391 2 : if (EQUAL(pszValue, "IF_NEEDED"))
5392 : {
5393 0 : bWriteLonLat = bHasGeoloc || !bIsCfProjection;
5394 : }
5395 : else
5396 : {
5397 2 : bWriteLonLat = CPLTestBool(pszValue);
5398 : }
5399 : }
5400 : else
5401 : {
5402 42 : bWriteLonLat = bHasGeoloc;
5403 : }
5404 :
5405 : // Save value of pszCFCoordinates for later.
5406 44 : if (bWriteLonLat)
5407 : {
5408 4 : pszCFCoordinates = NCDF_LONLAT;
5409 : }
5410 : }
5411 : else
5412 : {
5413 : // Files without a Datum will not have a grid_mapping variable and
5414 : // geographic information.
5415 110 : bWriteGridMapping = bIsGeographic;
5416 :
5417 110 : if (bHasGeoloc)
5418 : {
5419 8 : bWriteLonLat = true;
5420 : }
5421 : else
5422 : {
5423 102 : bWriteGDALTags = CPL_TO_BOOL(CSLFetchBoolean(
5424 102 : papszCreationOptions, "WRITE_GDAL_TAGS", bWriteGridMapping));
5425 102 : if (bWriteGDALTags)
5426 84 : bWriteGeoTransform = true;
5427 :
5428 102 : const char *pszValue = CSLFetchNameValueDef(papszCreationOptions,
5429 : "WRITE_LONLAT", "YES");
5430 102 : if (EQUAL(pszValue, "IF_NEEDED"))
5431 0 : bWriteLonLat = true;
5432 : else
5433 102 : bWriteLonLat = CPLTestBool(pszValue);
5434 : // Don't write lon/lat if no source geotransform.
5435 102 : if (!m_bHasGeoTransform)
5436 0 : bWriteLonLat = false;
5437 : // If we don't write lon/lat, set dimnames to X/Y and write gdal
5438 : // tags.
5439 102 : if (!bWriteLonLat)
5440 : {
5441 0 : CPLError(CE_Warning, CPLE_AppDefined,
5442 : "creating geographic file without lon/lat values!");
5443 0 : if (m_bHasGeoTransform)
5444 : {
5445 0 : bWriteGDALTags = true; // Not desirable if no geotransform.
5446 0 : bWriteGeoTransform = true;
5447 : }
5448 : }
5449 : }
5450 : }
5451 :
5452 : // Make sure we write grid_mapping if we need to write GDAL tags.
5453 154 : if (bWriteGDALTags)
5454 128 : bWriteGridMapping = true;
5455 :
5456 : // bottom-up value: new driver is bottom-up by default.
5457 : // Override with WRITE_BOTTOMUP.
5458 154 : bBottomUp = CPL_TO_BOOL(
5459 154 : CSLFetchBoolean(papszCreationOptions, "WRITE_BOTTOMUP", TRUE));
5460 :
5461 154 : if (bDefsOnly)
5462 : {
5463 77 : CPLDebug(
5464 : "GDAL_netCDF",
5465 : "bIsProjected=%d bIsGeographic=%d bWriteGridMapping=%d "
5466 : "bWriteGDALTags=%d bWriteLonLat=%d bBottomUp=%d bHasGeoloc=%d",
5467 77 : static_cast<int>(bIsProjected), static_cast<int>(bIsGeographic),
5468 : static_cast<int>(bWriteGridMapping),
5469 : static_cast<int>(bWriteGDALTags), static_cast<int>(bWriteLonLat),
5470 77 : static_cast<int>(bBottomUp), static_cast<int>(bHasGeoloc));
5471 : }
5472 :
5473 : // Exit if nothing to do.
5474 154 : if (!bIsProjected && !bWriteLonLat)
5475 0 : return CE_None;
5476 :
5477 : // Define dimension names.
5478 :
5479 154 : constexpr const char *ROTATED_POLE_VAR_NAME = "rotated_pole";
5480 :
5481 154 : if (bDefsOnly)
5482 : {
5483 77 : int nVarLonID = -1;
5484 77 : int nVarLatID = -1;
5485 77 : int nVarXID = -1;
5486 77 : int nVarYID = -1;
5487 :
5488 77 : m_bAddedProjectionVarsDefs = true;
5489 :
5490 : // Make sure we are in define mode.
5491 77 : SetDefineMode(true);
5492 :
5493 : // Write projection attributes.
5494 77 : if (bWriteGridMapping)
5495 : {
5496 64 : const int NCDFVarID = NCDFWriteSRSVariable(
5497 : cdfid, &oSRS, &pszCFProjection, bWriteGDALTags);
5498 64 : if (NCDFVarID < 0)
5499 0 : return CE_Failure;
5500 :
5501 : // Optional GDAL custom projection tags.
5502 64 : if (bWriteGDALTags)
5503 : {
5504 128 : CPLString osGeoTransform;
5505 448 : for (int i = 0; i < 6; i++)
5506 : {
5507 : osGeoTransform +=
5508 384 : CPLSPrintf("%.16g ", m_adfGeoTransform[i]);
5509 : }
5510 64 : CPLDebug("GDAL_netCDF", "szGeoTransform = %s",
5511 : osGeoTransform.c_str());
5512 :
5513 : // if( strlen(pszProj4Defn) > 0 ) {
5514 : // nc_put_att_text(cdfid, NCDFVarID, "proj4",
5515 : // strlen(pszProj4Defn), pszProj4Defn);
5516 : // }
5517 :
5518 : // For now, write the geotransform for back-compat or else
5519 : // the old (1.8.1) driver overrides the CF geotransform with
5520 : // empty values from dfNN, dfSN, dfEE, dfWE;
5521 :
5522 : // TODO: fix this in 1.8 branch, and then remove this here.
5523 64 : if (bWriteGeoTransform && m_bHasGeoTransform)
5524 : {
5525 : {
5526 63 : const int status = nc_put_att_text(
5527 : cdfid, NCDFVarID, NCDF_GEOTRANSFORM,
5528 : osGeoTransform.size(), osGeoTransform.c_str());
5529 63 : NCDF_ERR(status);
5530 : }
5531 : }
5532 : }
5533 :
5534 : // Write projection variable to band variable.
5535 : // Need to call later if there are no bands.
5536 64 : AddGridMappingRef();
5537 : } // end if( bWriteGridMapping )
5538 :
5539 : // Write CF Projection vars.
5540 :
5541 77 : const bool bIsRotatedPole =
5542 141 : pszCFProjection != nullptr &&
5543 64 : EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
5544 77 : if (bIsRotatedPole)
5545 : {
5546 : // Rename dims to rlat/rlon.
5547 : papszDimName
5548 0 : .Clear(); // If we add other dims one day, this has to change
5549 0 : papszDimName.AddString(NCDF_DIMNAME_RLAT);
5550 0 : papszDimName.AddString(NCDF_DIMNAME_RLON);
5551 :
5552 0 : int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_RLAT);
5553 0 : NCDF_ERR(status);
5554 0 : status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_RLON);
5555 0 : NCDF_ERR(status);
5556 : }
5557 : // Rename dimensions if lon/lat.
5558 77 : else if (!bIsProjected && !bHasGeoloc)
5559 : {
5560 : // Rename dims to lat/lon.
5561 : papszDimName
5562 51 : .Clear(); // If we add other dims one day, this has to change
5563 51 : papszDimName.AddString(NCDF_DIMNAME_LAT);
5564 51 : papszDimName.AddString(NCDF_DIMNAME_LON);
5565 :
5566 51 : int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_LAT);
5567 51 : NCDF_ERR(status);
5568 51 : status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_LON);
5569 51 : NCDF_ERR(status);
5570 : }
5571 :
5572 : // Write X/Y attributes.
5573 : else /* if( bIsProjected || bHasGeoloc ) */
5574 : {
5575 : // X
5576 : int anXDims[1];
5577 26 : anXDims[0] = nXDimID;
5578 26 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
5579 : CF_PROJ_X_VAR_NAME, NC_DOUBLE);
5580 26 : int status = nc_def_var(cdfid, CF_PROJ_X_VAR_NAME, NC_DOUBLE, 1,
5581 : anXDims, &nVarXID);
5582 26 : NCDF_ERR(status);
5583 :
5584 : // Y
5585 : int anYDims[1];
5586 26 : anYDims[0] = nYDimID;
5587 26 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
5588 : CF_PROJ_Y_VAR_NAME, NC_DOUBLE);
5589 26 : status = nc_def_var(cdfid, CF_PROJ_Y_VAR_NAME, NC_DOUBLE, 1,
5590 : anYDims, &nVarYID);
5591 26 : NCDF_ERR(status);
5592 :
5593 26 : if (bIsProjected)
5594 : {
5595 22 : NCDFWriteXYVarsAttributes(this->vcdf, nVarXID, nVarYID, &oSRS);
5596 : }
5597 : else
5598 : {
5599 4 : CPLAssert(bHasGeoloc);
5600 : try
5601 : {
5602 4 : vcdf.nc_put_vatt_text(nVarXID, CF_AXIS, CF_SG_X_AXIS);
5603 4 : vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME,
5604 : "x-coordinate in Cartesian system");
5605 4 : vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, "m");
5606 4 : vcdf.nc_put_vatt_text(nVarYID, CF_AXIS, CF_SG_Y_AXIS);
5607 4 : vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME,
5608 : "y-coordinate in Cartesian system");
5609 4 : vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, "m");
5610 :
5611 4 : pszCFCoordinates = NCDF_LONLAT;
5612 : }
5613 0 : catch (nccfdriver::SG_Exception &e)
5614 : {
5615 0 : CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5616 0 : return CE_Failure;
5617 : }
5618 : }
5619 : }
5620 :
5621 : // Write lat/lon attributes if needed.
5622 77 : if (bWriteLonLat)
5623 : {
5624 57 : int *panLatDims = nullptr;
5625 57 : int *panLonDims = nullptr;
5626 57 : int nLatDims = -1;
5627 57 : int nLonDims = -1;
5628 :
5629 : // Get information.
5630 57 : if (bHasGeoloc)
5631 : {
5632 : // Geoloc
5633 5 : nLatDims = 2;
5634 : panLatDims =
5635 5 : static_cast<int *>(CPLCalloc(nLatDims, sizeof(int)));
5636 5 : panLatDims[0] = nYDimID;
5637 5 : panLatDims[1] = nXDimID;
5638 5 : nLonDims = 2;
5639 : panLonDims =
5640 5 : static_cast<int *>(CPLCalloc(nLonDims, sizeof(int)));
5641 5 : panLonDims[0] = nYDimID;
5642 5 : panLonDims[1] = nXDimID;
5643 : }
5644 52 : else if (bIsProjected)
5645 : {
5646 : // Projected
5647 1 : nLatDims = 2;
5648 : panLatDims =
5649 1 : static_cast<int *>(CPLCalloc(nLatDims, sizeof(int)));
5650 1 : panLatDims[0] = nYDimID;
5651 1 : panLatDims[1] = nXDimID;
5652 1 : nLonDims = 2;
5653 : panLonDims =
5654 1 : static_cast<int *>(CPLCalloc(nLonDims, sizeof(int)));
5655 1 : panLonDims[0] = nYDimID;
5656 1 : panLonDims[1] = nXDimID;
5657 : }
5658 : else
5659 : {
5660 : // Geographic
5661 51 : nLatDims = 1;
5662 : panLatDims =
5663 51 : static_cast<int *>(CPLCalloc(nLatDims, sizeof(int)));
5664 51 : panLatDims[0] = nYDimID;
5665 51 : nLonDims = 1;
5666 : panLonDims =
5667 51 : static_cast<int *>(CPLCalloc(nLonDims, sizeof(int)));
5668 51 : panLonDims[0] = nXDimID;
5669 : }
5670 :
5671 57 : nc_type eLonLatType = NC_NAT;
5672 57 : if (bIsProjected)
5673 : {
5674 2 : eLonLatType = NC_FLOAT;
5675 4 : const char *pszValue = CSLFetchNameValueDef(
5676 2 : papszCreationOptions, "TYPE_LONLAT", "FLOAT");
5677 2 : if (EQUAL(pszValue, "DOUBLE"))
5678 0 : eLonLatType = NC_DOUBLE;
5679 : }
5680 : else
5681 : {
5682 55 : eLonLatType = NC_DOUBLE;
5683 110 : const char *pszValue = CSLFetchNameValueDef(
5684 55 : papszCreationOptions, "TYPE_LONLAT", "DOUBLE");
5685 55 : if (EQUAL(pszValue, "FLOAT"))
5686 0 : eLonLatType = NC_FLOAT;
5687 : }
5688 :
5689 : // Def vars and attributes.
5690 : {
5691 57 : const char *pszVarName =
5692 57 : bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME;
5693 57 : int status = nc_def_var(cdfid, pszVarName, eLonLatType,
5694 : nLatDims, panLatDims, &nVarLatID);
5695 57 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
5696 : cdfid, pszVarName, eLonLatType, nLatDims, nVarLatID);
5697 57 : NCDF_ERR(status);
5698 57 : DefVarDeflate(nVarLatID, false); // Don't set chunking.
5699 : }
5700 :
5701 : {
5702 57 : const char *pszVarName =
5703 57 : bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME;
5704 57 : int status = nc_def_var(cdfid, pszVarName, eLonLatType,
5705 : nLonDims, panLonDims, &nVarLonID);
5706 57 : CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
5707 : cdfid, pszVarName, eLonLatType, nLatDims, nVarLonID);
5708 57 : NCDF_ERR(status);
5709 57 : DefVarDeflate(nVarLonID, false); // Don't set chunking.
5710 : }
5711 :
5712 57 : if (bIsRotatedPole)
5713 0 : NCDFWriteRLonRLatVarsAttributes(this->vcdf, nVarLonID,
5714 : nVarLatID);
5715 : else
5716 57 : NCDFWriteLonLatVarsAttributes(this->vcdf, nVarLonID, nVarLatID);
5717 :
5718 57 : CPLFree(panLatDims);
5719 57 : CPLFree(panLonDims);
5720 : }
5721 : }
5722 :
5723 154 : if (!bDefsOnly)
5724 : {
5725 77 : m_bAddedProjectionVarsData = true;
5726 :
5727 77 : int nVarXID = -1;
5728 77 : int nVarYID = -1;
5729 :
5730 77 : nc_inq_varid(cdfid, CF_PROJ_X_VAR_NAME, &nVarXID);
5731 77 : nc_inq_varid(cdfid, CF_PROJ_Y_VAR_NAME, &nVarYID);
5732 :
5733 77 : int nVarLonID = -1;
5734 77 : int nVarLatID = -1;
5735 :
5736 77 : const bool bIsRotatedPole =
5737 141 : pszCFProjection != nullptr &&
5738 64 : EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
5739 77 : nc_inq_varid(cdfid,
5740 : bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME,
5741 : &nVarLonID);
5742 77 : nc_inq_varid(cdfid,
5743 : bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME,
5744 : &nVarLatID);
5745 :
5746 : // Get projection values.
5747 :
5748 77 : double *padLonVal = nullptr;
5749 77 : double *padLatVal = nullptr;
5750 :
5751 77 : if (bIsProjected)
5752 : {
5753 22 : OGRSpatialReference *poLatLonSRS = nullptr;
5754 22 : OGRCoordinateTransformation *poTransform = nullptr;
5755 :
5756 : size_t startX[1];
5757 : size_t countX[1];
5758 : size_t startY[1];
5759 : size_t countY[1];
5760 :
5761 22 : CPLDebug("GDAL_netCDF", "Getting (X,Y) values");
5762 :
5763 : double *padXVal =
5764 22 : static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
5765 : double *padYVal =
5766 22 : static_cast<double *>(CPLMalloc(nRasterYSize * sizeof(double)));
5767 :
5768 : // Get Y values.
5769 22 : const double dfY0 = (!bBottomUp) ? m_adfGeoTransform[3] :
5770 : // Invert latitude values.
5771 22 : m_adfGeoTransform[3] +
5772 22 : (m_adfGeoTransform[5] * nRasterYSize);
5773 22 : const double dfDY = m_adfGeoTransform[5];
5774 :
5775 1393 : for (int j = 0; j < nRasterYSize; j++)
5776 : {
5777 : // The data point is centered inside the pixel.
5778 1371 : if (!bBottomUp)
5779 0 : padYVal[j] = dfY0 + (j + 0.5) * dfDY;
5780 : else // Invert latitude values.
5781 1371 : padYVal[j] = dfY0 - (j + 0.5) * dfDY;
5782 : }
5783 22 : startX[0] = 0;
5784 22 : countX[0] = nRasterXSize;
5785 :
5786 : // Get X values.
5787 22 : const double dfX0 = m_adfGeoTransform[0];
5788 22 : const double dfDX = m_adfGeoTransform[1];
5789 :
5790 1414 : for (int i = 0; i < nRasterXSize; i++)
5791 : {
5792 : // The data point is centered inside the pixel.
5793 1392 : padXVal[i] = dfX0 + (i + 0.5) * dfDX;
5794 : }
5795 22 : startY[0] = 0;
5796 22 : countY[0] = nRasterYSize;
5797 :
5798 : // Write X/Y values.
5799 :
5800 : // Make sure we are in data mode.
5801 22 : SetDefineMode(false);
5802 :
5803 22 : CPLDebug("GDAL_netCDF", "Writing X values");
5804 : int status =
5805 22 : nc_put_vara_double(cdfid, nVarXID, startX, countX, padXVal);
5806 22 : NCDF_ERR(status);
5807 :
5808 22 : CPLDebug("GDAL_netCDF", "Writing Y values");
5809 : status =
5810 22 : nc_put_vara_double(cdfid, nVarYID, startY, countY, padYVal);
5811 22 : NCDF_ERR(status);
5812 :
5813 22 : if (pfnProgress)
5814 19 : pfnProgress(0.20, nullptr, pProgressData);
5815 :
5816 : // Write lon/lat arrays (CF coordinates) if requested.
5817 :
5818 : // Get OGR transform if GEOLOCATION is not available.
5819 22 : if (bWriteLonLat && !bHasGeoloc)
5820 : {
5821 1 : poLatLonSRS = m_oSRS.CloneGeogCS();
5822 1 : if (poLatLonSRS != nullptr)
5823 : {
5824 1 : poLatLonSRS->SetAxisMappingStrategy(
5825 : OAMS_TRADITIONAL_GIS_ORDER);
5826 : poTransform =
5827 1 : OGRCreateCoordinateTransformation(&m_oSRS, poLatLonSRS);
5828 : }
5829 : // If no OGR transform, then don't write CF lon/lat.
5830 1 : if (poTransform == nullptr)
5831 : {
5832 0 : CPLError(CE_Failure, CPLE_AppDefined,
5833 : "Unable to get Coordinate Transform");
5834 0 : bWriteLonLat = false;
5835 : }
5836 : }
5837 :
5838 22 : if (bWriteLonLat)
5839 : {
5840 2 : if (!bHasGeoloc)
5841 1 : CPLDebug("GDAL_netCDF", "Transforming (X,Y)->(lon,lat)");
5842 : else
5843 1 : CPLDebug("GDAL_netCDF",
5844 : "Writing (lon,lat) from GEOLOCATION arrays");
5845 :
5846 2 : bool bOK = true;
5847 2 : double dfProgress = 0.2;
5848 :
5849 2 : size_t start[] = {0, 0};
5850 2 : size_t count[] = {1, (size_t)nRasterXSize};
5851 : padLatVal = static_cast<double *>(
5852 2 : CPLMalloc(nRasterXSize * sizeof(double)));
5853 : padLonVal = static_cast<double *>(
5854 2 : CPLMalloc(nRasterXSize * sizeof(double)));
5855 :
5856 61 : for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR;
5857 : j++)
5858 : {
5859 59 : start[0] = j;
5860 :
5861 : // Get values from geotransform.
5862 59 : if (!bHasGeoloc)
5863 : {
5864 : // Fill values to transform.
5865 420 : for (int i = 0; i < nRasterXSize; i++)
5866 : {
5867 400 : padLatVal[i] = padYVal[j];
5868 400 : padLonVal[i] = padXVal[i];
5869 : }
5870 :
5871 : // Do the transform.
5872 20 : bOK = CPL_TO_BOOL(poTransform->Transform(
5873 20 : nRasterXSize, padLonVal, padLatVal, nullptr));
5874 20 : if (!bOK)
5875 : {
5876 0 : CPLError(CE_Failure, CPLE_AppDefined,
5877 : "Unable to Transform (X,Y) to (lon,lat).");
5878 : }
5879 : }
5880 : // Get values from geoloc arrays.
5881 : else
5882 : {
5883 39 : CPLErr eErr = GDALRasterIO(
5884 : hBand_Y, GF_Read, 0, j, nRasterXSize, 1, padLatVal,
5885 : nRasterXSize, 1, GDT_Float64, 0, 0);
5886 39 : if (eErr == CE_None)
5887 : {
5888 39 : eErr = GDALRasterIO(
5889 : hBand_X, GF_Read, 0, j, nRasterXSize, 1,
5890 : padLonVal, nRasterXSize, 1, GDT_Float64, 0, 0);
5891 : }
5892 :
5893 39 : if (eErr == CE_None)
5894 : {
5895 39 : bOK = true;
5896 : }
5897 : else
5898 : {
5899 0 : bOK = false;
5900 0 : CPLError(CE_Failure, CPLE_AppDefined,
5901 : "Unable to get scanline %d", j);
5902 : }
5903 : }
5904 :
5905 : // Write data.
5906 59 : if (bOK)
5907 : {
5908 59 : status = nc_put_vara_double(cdfid, nVarLatID, start,
5909 : count, padLatVal);
5910 59 : NCDF_ERR(status);
5911 59 : status = nc_put_vara_double(cdfid, nVarLonID, start,
5912 : count, padLonVal);
5913 59 : NCDF_ERR(status);
5914 : }
5915 :
5916 59 : if (pfnProgress && (nRasterYSize / 10) > 0 &&
5917 59 : (j % (nRasterYSize / 10) == 0))
5918 : {
5919 23 : dfProgress += 0.08;
5920 23 : pfnProgress(dfProgress, nullptr, pProgressData);
5921 : }
5922 : }
5923 : }
5924 :
5925 22 : if (poLatLonSRS != nullptr)
5926 1 : delete poLatLonSRS;
5927 22 : if (poTransform != nullptr)
5928 1 : delete poTransform;
5929 :
5930 22 : CPLFree(padXVal);
5931 22 : CPLFree(padYVal);
5932 : } // Projected
5933 :
5934 : // If not projected/geographic and has geoloc
5935 55 : else if (!bIsGeographic && bHasGeoloc)
5936 : {
5937 : // Use
5938 : // https://cfconventions.org/Data/cf-conventions/cf-conventions-1.9/cf-conventions.html#_two_dimensional_latitude_longitude_coordinate_variables
5939 :
5940 4 : bool bOK = true;
5941 4 : double dfProgress = 0.2;
5942 :
5943 : // Make sure we are in data mode.
5944 4 : SetDefineMode(false);
5945 :
5946 : size_t startX[1];
5947 : size_t countX[1];
5948 : size_t startY[1];
5949 : size_t countY[1];
5950 4 : startX[0] = 0;
5951 4 : countX[0] = nRasterXSize;
5952 :
5953 4 : startY[0] = 0;
5954 4 : countY[0] = nRasterYSize;
5955 :
5956 8 : std::vector<double> adfXVal(nRasterXSize);
5957 16 : for (int i = 0; i < nRasterXSize; i++)
5958 12 : adfXVal[i] = i;
5959 :
5960 8 : std::vector<double> adfYVal(nRasterYSize);
5961 12 : for (int i = 0; i < nRasterYSize; i++)
5962 8 : adfYVal[i] = bBottomUp ? nRasterYSize - 1 - i : i;
5963 :
5964 4 : CPLDebug("GDAL_netCDF", "Writing X values");
5965 4 : int status = nc_put_vara_double(cdfid, nVarXID, startX, countX,
5966 4 : adfXVal.data());
5967 4 : NCDF_ERR(status);
5968 :
5969 4 : CPLDebug("GDAL_netCDF", "Writing Y values");
5970 4 : status = nc_put_vara_double(cdfid, nVarYID, startY, countY,
5971 4 : adfYVal.data());
5972 4 : NCDF_ERR(status);
5973 :
5974 4 : if (pfnProgress)
5975 0 : pfnProgress(0.20, nullptr, pProgressData);
5976 :
5977 4 : size_t start[] = {0, 0};
5978 4 : size_t count[] = {1, (size_t)nRasterXSize};
5979 : padLatVal =
5980 4 : static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
5981 : padLonVal =
5982 4 : static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
5983 :
5984 12 : for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR; j++)
5985 : {
5986 8 : start[0] = j;
5987 :
5988 8 : CPLErr eErr = GDALRasterIO(hBand_Y, GF_Read, 0,
5989 8 : bBottomUp ? nRasterYSize - 1 - j : j,
5990 : nRasterXSize, 1, padLatVal,
5991 : nRasterXSize, 1, GDT_Float64, 0, 0);
5992 8 : if (eErr == CE_None)
5993 : {
5994 8 : eErr = GDALRasterIO(hBand_X, GF_Read, 0,
5995 8 : bBottomUp ? nRasterYSize - 1 - j : j,
5996 : nRasterXSize, 1, padLonVal,
5997 : nRasterXSize, 1, GDT_Float64, 0, 0);
5998 : }
5999 :
6000 8 : if (eErr == CE_None)
6001 : {
6002 8 : bOK = true;
6003 : }
6004 : else
6005 : {
6006 0 : bOK = false;
6007 0 : CPLError(CE_Failure, CPLE_AppDefined,
6008 : "Unable to get scanline %d", j);
6009 : }
6010 :
6011 : // Write data.
6012 8 : if (bOK)
6013 : {
6014 8 : status = nc_put_vara_double(cdfid, nVarLatID, start, count,
6015 : padLatVal);
6016 8 : NCDF_ERR(status);
6017 8 : status = nc_put_vara_double(cdfid, nVarLonID, start, count,
6018 : padLonVal);
6019 8 : NCDF_ERR(status);
6020 : }
6021 :
6022 8 : if (pfnProgress && (nRasterYSize / 10) > 0 &&
6023 0 : (j % (nRasterYSize / 10) == 0))
6024 : {
6025 0 : dfProgress += 0.08;
6026 0 : pfnProgress(dfProgress, nullptr, pProgressData);
6027 : }
6028 4 : }
6029 : }
6030 :
6031 : // If not projected, assume geographic to catch grids without Datum.
6032 51 : else if (bWriteLonLat)
6033 : {
6034 : // Get latitude values.
6035 51 : const double dfY0 = (!bBottomUp) ? m_adfGeoTransform[3] :
6036 : // Invert latitude values.
6037 51 : m_adfGeoTransform[3] +
6038 51 : (m_adfGeoTransform[5] * nRasterYSize);
6039 51 : const double dfDY = m_adfGeoTransform[5];
6040 :
6041 : // Override lat values with the ones in GEOLOCATION/Y_VALUES.
6042 51 : if (netCDFDataset::GetMetadataItem("Y_VALUES", "GEOLOCATION") !=
6043 : nullptr)
6044 : {
6045 0 : int nTemp = 0;
6046 0 : padLatVal = Get1DGeolocation("Y_VALUES", nTemp);
6047 : // Make sure we got the correct amount, if not fallback to GT */
6048 : // could add test fabs(fabs(padLatVal[0]) - fabs(dfY0)) <= 0.1))
6049 0 : if (nTemp == nRasterYSize)
6050 : {
6051 0 : CPLDebug(
6052 : "GDAL_netCDF",
6053 : "Using Y_VALUES geolocation metadata for lat values");
6054 : }
6055 : else
6056 : {
6057 0 : CPLDebug("GDAL_netCDF",
6058 : "Got %d elements from Y_VALUES geolocation "
6059 : "metadata, need %d",
6060 : nTemp, nRasterYSize);
6061 0 : if (padLatVal)
6062 : {
6063 0 : CPLFree(padLatVal);
6064 0 : padLatVal = nullptr;
6065 : }
6066 : }
6067 : }
6068 :
6069 51 : if (padLatVal == nullptr)
6070 : {
6071 : padLatVal = static_cast<double *>(
6072 51 : CPLMalloc(nRasterYSize * sizeof(double)));
6073 3375 : for (int i = 0; i < nRasterYSize; i++)
6074 : {
6075 : // The data point is centered inside the pixel.
6076 3324 : if (!bBottomUp)
6077 0 : padLatVal[i] = dfY0 + (i + 0.5) * dfDY;
6078 : else // Invert latitude values.
6079 3324 : padLatVal[i] = dfY0 - (i + 0.5) * dfDY;
6080 : }
6081 : }
6082 :
6083 51 : size_t startLat[1] = {0};
6084 51 : size_t countLat[1] = {static_cast<size_t>(nRasterYSize)};
6085 :
6086 : // Get longitude values.
6087 51 : const double dfX0 = m_adfGeoTransform[0];
6088 51 : const double dfDX = m_adfGeoTransform[1];
6089 :
6090 : padLonVal =
6091 51 : static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
6092 3427 : for (int i = 0; i < nRasterXSize; i++)
6093 : {
6094 : // The data point is centered inside the pixel.
6095 3376 : padLonVal[i] = dfX0 + (i + 0.5) * dfDX;
6096 : }
6097 :
6098 51 : size_t startLon[1] = {0};
6099 51 : size_t countLon[1] = {static_cast<size_t>(nRasterXSize)};
6100 :
6101 : // Write latitude and longitude values.
6102 :
6103 : // Make sure we are in data mode.
6104 51 : SetDefineMode(false);
6105 :
6106 : // Write values.
6107 51 : CPLDebug("GDAL_netCDF", "Writing lat values");
6108 :
6109 51 : int status = nc_put_vara_double(cdfid, nVarLatID, startLat,
6110 : countLat, padLatVal);
6111 51 : NCDF_ERR(status);
6112 :
6113 51 : CPLDebug("GDAL_netCDF", "Writing lon values");
6114 51 : status = nc_put_vara_double(cdfid, nVarLonID, startLon, countLon,
6115 : padLonVal);
6116 51 : NCDF_ERR(status);
6117 :
6118 : } // Not projected.
6119 :
6120 77 : CPLFree(padLatVal);
6121 77 : CPLFree(padLonVal);
6122 :
6123 77 : if (pfnProgress)
6124 37 : pfnProgress(1.00, nullptr, pProgressData);
6125 : }
6126 :
6127 154 : if (hDS_X != nullptr)
6128 : {
6129 10 : GDALClose(hDS_X);
6130 : }
6131 154 : if (hDS_Y != nullptr)
6132 : {
6133 10 : GDALClose(hDS_Y);
6134 : }
6135 :
6136 154 : return CE_None;
6137 : }
6138 :
6139 : // Write Projection variable to band variable.
6140 : // Moved from AddProjectionVars() for cases when bands are added after
6141 : // projection.
6142 356 : bool netCDFDataset::AddGridMappingRef()
6143 : {
6144 356 : bool bRet = true;
6145 356 : bool bOldDefineMode = bDefineMode;
6146 :
6147 531 : if ((GetAccess() == GA_Update) && (nBands >= 1) && (GetRasterBand(1)) &&
6148 175 : ((pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, "")) ||
6149 169 : (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))))
6150 : {
6151 68 : bAddedGridMappingRef = true;
6152 :
6153 : // Make sure we are in define mode.
6154 68 : SetDefineMode(true);
6155 :
6156 182 : for (int i = 1; i <= nBands; i++)
6157 : {
6158 : const int nVarId =
6159 114 : static_cast<netCDFRasterBand *>(GetRasterBand(i))->nZId;
6160 :
6161 114 : if (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))
6162 : {
6163 : int status =
6164 220 : nc_put_att_text(cdfid, nVarId, CF_GRD_MAPPING,
6165 110 : strlen(pszCFProjection), pszCFProjection);
6166 110 : NCDF_ERR(status);
6167 110 : if (status != NC_NOERR)
6168 0 : bRet = false;
6169 : }
6170 114 : if (pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, ""))
6171 : {
6172 : int status =
6173 6 : nc_put_att_text(cdfid, nVarId, CF_COORDINATES,
6174 : strlen(pszCFCoordinates), pszCFCoordinates);
6175 6 : NCDF_ERR(status);
6176 6 : if (status != NC_NOERR)
6177 0 : bRet = false;
6178 : }
6179 : }
6180 :
6181 : // Go back to previous define mode.
6182 68 : SetDefineMode(bOldDefineMode);
6183 : }
6184 356 : return bRet;
6185 : }
6186 :
6187 : /************************************************************************/
6188 : /* GetGeoTransform() */
6189 : /************************************************************************/
6190 :
6191 107 : CPLErr netCDFDataset::GetGeoTransform(double *padfTransform)
6192 :
6193 : {
6194 107 : memcpy(padfTransform, m_adfGeoTransform, sizeof(double) * 6);
6195 107 : if (m_bHasGeoTransform)
6196 77 : return CE_None;
6197 :
6198 30 : return GDALPamDataset::GetGeoTransform(padfTransform);
6199 : }
6200 :
6201 : /************************************************************************/
6202 : /* rint() */
6203 : /************************************************************************/
6204 :
6205 0 : double netCDFDataset::rint(double dfX)
6206 : {
6207 0 : return std::round(dfX);
6208 : }
6209 :
6210 : /************************************************************************/
6211 : /* NCDFReadIsoMetadata() */
6212 : /************************************************************************/
6213 :
6214 16 : static void NCDFReadMetadataAsJson(int cdfid, CPLJSONObject &obj)
6215 : {
6216 16 : int nbAttr = 0;
6217 16 : NCDF_ERR(nc_inq_varnatts(cdfid, NC_GLOBAL, &nbAttr));
6218 :
6219 32 : std::map<std::string, CPLJSONArray> oMapNameToArray;
6220 40 : for (int l = 0; l < nbAttr; l++)
6221 : {
6222 : char szAttrName[NC_MAX_NAME + 1];
6223 24 : szAttrName[0] = 0;
6224 24 : NCDF_ERR(nc_inq_attname(cdfid, NC_GLOBAL, l, szAttrName));
6225 :
6226 24 : char *pszMetaValue = nullptr;
6227 24 : if (NCDFGetAttr(cdfid, NC_GLOBAL, szAttrName, &pszMetaValue) == CE_None)
6228 : {
6229 24 : nc_type nAttrType = NC_NAT;
6230 24 : size_t nAttrLen = 0;
6231 :
6232 24 : NCDF_ERR(nc_inq_att(cdfid, NC_GLOBAL, szAttrName, &nAttrType,
6233 : &nAttrLen));
6234 :
6235 24 : std::string osAttrName(szAttrName);
6236 24 : const auto sharpPos = osAttrName.find('#');
6237 24 : if (sharpPos == std::string::npos)
6238 : {
6239 16 : if (nAttrType == NC_DOUBLE || nAttrType == NC_FLOAT)
6240 4 : obj.Add(osAttrName, CPLAtof(pszMetaValue));
6241 : else
6242 12 : obj.Add(osAttrName, pszMetaValue);
6243 : }
6244 : else
6245 : {
6246 8 : osAttrName.resize(sharpPos);
6247 8 : auto iter = oMapNameToArray.find(osAttrName);
6248 8 : if (iter == oMapNameToArray.end())
6249 : {
6250 8 : CPLJSONArray array;
6251 4 : obj.Add(osAttrName, array);
6252 4 : oMapNameToArray[osAttrName] = array;
6253 4 : array.Add(pszMetaValue);
6254 : }
6255 : else
6256 : {
6257 4 : iter->second.Add(pszMetaValue);
6258 : }
6259 : }
6260 24 : CPLFree(pszMetaValue);
6261 24 : pszMetaValue = nullptr;
6262 : }
6263 : }
6264 :
6265 16 : int nSubGroups = 0;
6266 16 : int *panSubGroupIds = nullptr;
6267 16 : NCDFGetSubGroups(cdfid, &nSubGroups, &panSubGroupIds);
6268 16 : oMapNameToArray.clear();
6269 28 : for (int i = 0; i < nSubGroups; i++)
6270 : {
6271 24 : CPLJSONObject subObj;
6272 12 : NCDFReadMetadataAsJson(panSubGroupIds[i], subObj);
6273 :
6274 24 : std::string osGroupName;
6275 12 : osGroupName.resize(NC_MAX_NAME);
6276 12 : NCDF_ERR(nc_inq_grpname(panSubGroupIds[i], &osGroupName[0]));
6277 12 : osGroupName.resize(strlen(osGroupName.data()));
6278 12 : const auto sharpPos = osGroupName.find('#');
6279 12 : if (sharpPos == std::string::npos)
6280 : {
6281 4 : obj.Add(osGroupName, subObj);
6282 : }
6283 : else
6284 : {
6285 8 : osGroupName.resize(sharpPos);
6286 8 : auto iter = oMapNameToArray.find(osGroupName);
6287 8 : if (iter == oMapNameToArray.end())
6288 : {
6289 8 : CPLJSONArray array;
6290 4 : obj.Add(osGroupName, array);
6291 4 : oMapNameToArray[osGroupName] = array;
6292 4 : array.Add(subObj);
6293 : }
6294 : else
6295 : {
6296 4 : iter->second.Add(subObj);
6297 : }
6298 : }
6299 : }
6300 16 : CPLFree(panSubGroupIds);
6301 16 : }
6302 :
6303 4 : std::string NCDFReadMetadataAsJson(int cdfid)
6304 : {
6305 8 : CPLJSONDocument oDoc;
6306 8 : CPLJSONObject oRoot = oDoc.GetRoot();
6307 4 : NCDFReadMetadataAsJson(cdfid, oRoot);
6308 8 : return oDoc.SaveAsString();
6309 : }
6310 :
6311 : /************************************************************************/
6312 : /* ReadAttributes() */
6313 : /************************************************************************/
6314 1723 : CPLErr netCDFDataset::ReadAttributes(int cdfidIn, int var)
6315 :
6316 : {
6317 1723 : char *pszVarFullName = nullptr;
6318 1723 : ERR_RET(NCDFGetVarFullName(cdfidIn, var, &pszVarFullName));
6319 :
6320 : // For metadata in Sentinel 5
6321 1723 : if (STARTS_WITH(pszVarFullName, "/METADATA/"))
6322 : {
6323 6 : for (const char *key :
6324 : {"ISO_METADATA", "ESA_METADATA", "EOP_METADATA", "QA_STATISTICS",
6325 8 : "GRANULE_DESCRIPTION", "ALGORITHM_SETTINGS"})
6326 : {
6327 14 : if (var == NC_GLOBAL &&
6328 7 : strcmp(pszVarFullName,
6329 : CPLSPrintf("/METADATA/%s/NC_GLOBAL", key)) == 0)
6330 : {
6331 1 : CPLFree(pszVarFullName);
6332 1 : CPLStringList aosList;
6333 2 : aosList.AddString(CPLString(NCDFReadMetadataAsJson(cdfidIn))
6334 1 : .replaceAll("\\/", '/'));
6335 1 : m_oMapDomainToJSon[key] = std::move(aosList);
6336 1 : return CE_None;
6337 : }
6338 : }
6339 : }
6340 1722 : if (STARTS_WITH(pszVarFullName, "/PRODUCT/SUPPORT_DATA/"))
6341 : {
6342 0 : CPLFree(pszVarFullName);
6343 0 : CPLStringList aosList;
6344 : aosList.AddString(
6345 0 : CPLString(NCDFReadMetadataAsJson(cdfidIn)).replaceAll("\\/", '/'));
6346 0 : m_oMapDomainToJSon["SUPPORT_DATA"] = std::move(aosList);
6347 0 : return CE_None;
6348 : }
6349 :
6350 1722 : size_t nMetaNameSize =
6351 1722 : sizeof(char) * (strlen(pszVarFullName) + 1 + NC_MAX_NAME + 1);
6352 1722 : char *pszMetaName = static_cast<char *>(CPLMalloc(nMetaNameSize));
6353 :
6354 1722 : int nbAttr = 0;
6355 1722 : NCDF_ERR(nc_inq_varnatts(cdfidIn, var, &nbAttr));
6356 :
6357 8648 : for (int l = 0; l < nbAttr; l++)
6358 : {
6359 : char szAttrName[NC_MAX_NAME + 1];
6360 6926 : szAttrName[0] = 0;
6361 6926 : NCDF_ERR(nc_inq_attname(cdfidIn, var, l, szAttrName));
6362 6926 : snprintf(pszMetaName, nMetaNameSize, "%s#%s", pszVarFullName,
6363 : szAttrName);
6364 :
6365 6926 : char *pszMetaTemp = nullptr;
6366 6926 : if (NCDFGetAttr(cdfidIn, var, szAttrName, &pszMetaTemp) == CE_None)
6367 : {
6368 6925 : papszMetadata =
6369 6925 : CSLSetNameValue(papszMetadata, pszMetaName, pszMetaTemp);
6370 6925 : CPLFree(pszMetaTemp);
6371 6925 : pszMetaTemp = nullptr;
6372 : }
6373 : else
6374 : {
6375 1 : CPLDebug("GDAL_netCDF", "invalid metadata %s", pszMetaName);
6376 : }
6377 : }
6378 :
6379 1722 : CPLFree(pszVarFullName);
6380 1722 : CPLFree(pszMetaName);
6381 :
6382 1722 : if (var == NC_GLOBAL)
6383 : {
6384 : // Recurse on sub-groups.
6385 505 : int nSubGroups = 0;
6386 505 : int *panSubGroupIds = nullptr;
6387 505 : NCDFGetSubGroups(cdfidIn, &nSubGroups, &panSubGroupIds);
6388 528 : for (int i = 0; i < nSubGroups; i++)
6389 : {
6390 23 : ReadAttributes(panSubGroupIds[i], var);
6391 : }
6392 505 : CPLFree(panSubGroupIds);
6393 : }
6394 :
6395 1722 : return CE_None;
6396 : }
6397 :
6398 : /************************************************************************/
6399 : /* netCDFDataset::CreateSubDatasetList() */
6400 : /************************************************************************/
6401 45 : void netCDFDataset::CreateSubDatasetList(int nGroupId)
6402 : {
6403 : char szVarStdName[NC_MAX_NAME + 1];
6404 45 : int *ponDimIds = nullptr;
6405 : nc_type nAttype;
6406 : size_t nAttlen;
6407 :
6408 45 : netCDFDataset *poDS = this;
6409 :
6410 : int nVarCount;
6411 45 : nc_inq_nvars(nGroupId, &nVarCount);
6412 :
6413 315 : for (int nVar = 0; nVar < nVarCount; nVar++)
6414 : {
6415 :
6416 : int nDims;
6417 270 : nc_inq_varndims(nGroupId, nVar, &nDims);
6418 :
6419 270 : if (nDims >= 2)
6420 : {
6421 151 : ponDimIds = static_cast<int *>(CPLCalloc(nDims, sizeof(int)));
6422 151 : nc_inq_vardimid(nGroupId, nVar, ponDimIds);
6423 :
6424 : // Create Sub dataset list.
6425 151 : CPLString osDim;
6426 468 : for (int i = 0; i < nDims; i++)
6427 : {
6428 : size_t nDimLen;
6429 317 : nc_inq_dimlen(nGroupId, ponDimIds[i], &nDimLen);
6430 317 : osDim += CPLSPrintf("%dx", (int)nDimLen);
6431 : }
6432 151 : CPLFree(ponDimIds);
6433 :
6434 : nc_type nVarType;
6435 151 : nc_inq_vartype(nGroupId, nVar, &nVarType);
6436 : // Get rid of the last "x" character.
6437 151 : osDim.resize(osDim.size() - 1);
6438 151 : const char *pszType = "";
6439 151 : switch (nVarType)
6440 : {
6441 35 : case NC_BYTE:
6442 35 : pszType = "8-bit integer";
6443 35 : break;
6444 2 : case NC_CHAR:
6445 2 : pszType = "8-bit character";
6446 2 : break;
6447 6 : case NC_SHORT:
6448 6 : pszType = "16-bit integer";
6449 6 : break;
6450 8 : case NC_INT:
6451 8 : pszType = "32-bit integer";
6452 8 : break;
6453 51 : case NC_FLOAT:
6454 51 : pszType = "32-bit floating-point";
6455 51 : break;
6456 31 : case NC_DOUBLE:
6457 31 : pszType = "64-bit floating-point";
6458 31 : break;
6459 4 : case NC_UBYTE:
6460 4 : pszType = "8-bit unsigned integer";
6461 4 : break;
6462 1 : case NC_USHORT:
6463 1 : pszType = "16-bit unsigned integer";
6464 1 : break;
6465 1 : case NC_UINT:
6466 1 : pszType = "32-bit unsigned integer";
6467 1 : break;
6468 1 : case NC_INT64:
6469 1 : pszType = "64-bit integer";
6470 1 : break;
6471 1 : case NC_UINT64:
6472 1 : pszType = "64-bit unsigned integer";
6473 1 : break;
6474 10 : default:
6475 10 : break;
6476 : }
6477 :
6478 151 : char *pszName = nullptr;
6479 151 : if (NCDFGetVarFullName(nGroupId, nVar, &pszName) != CE_None)
6480 0 : continue;
6481 :
6482 151 : nSubDatasets++;
6483 :
6484 151 : nAttlen = 0;
6485 151 : nc_inq_att(nGroupId, nVar, CF_STD_NAME, &nAttype, &nAttlen);
6486 302 : if (nAttlen < sizeof(szVarStdName) &&
6487 151 : nc_get_att_text(nGroupId, nVar, CF_STD_NAME, szVarStdName) ==
6488 : NC_NOERR)
6489 : {
6490 51 : szVarStdName[nAttlen] = '\0';
6491 : }
6492 : else
6493 : {
6494 100 : snprintf(szVarStdName, sizeof(szVarStdName), "%s", pszName);
6495 : }
6496 :
6497 : char szTemp[NC_MAX_NAME + 1];
6498 151 : snprintf(szTemp, sizeof(szTemp), "SUBDATASET_%d_NAME",
6499 : nSubDatasets);
6500 :
6501 151 : if (strchr(pszName, ' ') || strchr(pszName, ':'))
6502 : {
6503 1 : poDS->papszSubDatasets = CSLSetNameValue(
6504 : poDS->papszSubDatasets, szTemp,
6505 : CPLSPrintf("NETCDF:\"%s\":\"%s\"", poDS->osFilename.c_str(),
6506 : pszName));
6507 : }
6508 : else
6509 : {
6510 150 : poDS->papszSubDatasets = CSLSetNameValue(
6511 : poDS->papszSubDatasets, szTemp,
6512 : CPLSPrintf("NETCDF:\"%s\":%s", poDS->osFilename.c_str(),
6513 : pszName));
6514 : }
6515 :
6516 151 : CPLFree(pszName);
6517 :
6518 151 : snprintf(szTemp, sizeof(szTemp), "SUBDATASET_%d_DESC",
6519 : nSubDatasets);
6520 :
6521 151 : poDS->papszSubDatasets =
6522 151 : CSLSetNameValue(poDS->papszSubDatasets, szTemp,
6523 : CPLSPrintf("[%s] %s (%s)", osDim.c_str(),
6524 : szVarStdName, pszType));
6525 : }
6526 : }
6527 :
6528 : // Recurse on sub groups.
6529 45 : int nSubGroups = 0;
6530 45 : int *panSubGroupIds = nullptr;
6531 45 : NCDFGetSubGroups(nGroupId, &nSubGroups, &panSubGroupIds);
6532 47 : for (int i = 0; i < nSubGroups; i++)
6533 : {
6534 2 : CreateSubDatasetList(panSubGroupIds[i]);
6535 : }
6536 45 : CPLFree(panSubGroupIds);
6537 45 : }
6538 :
6539 : /************************************************************************/
6540 : /* TestCapability() */
6541 : /************************************************************************/
6542 :
6543 248 : int netCDFDataset::TestCapability(const char *pszCap)
6544 : {
6545 248 : if (EQUAL(pszCap, ODsCCreateLayer))
6546 : {
6547 223 : return eAccess == GA_Update && nBands == 0 &&
6548 218 : (eMultipleLayerBehavior != SINGLE_LAYER ||
6549 229 : this->GetLayerCount() == 0 || bSGSupport);
6550 : }
6551 136 : else if (EQUAL(pszCap, ODsCZGeometries))
6552 2 : return true;
6553 :
6554 134 : return false;
6555 : }
6556 :
6557 : /************************************************************************/
6558 : /* GetLayer() */
6559 : /************************************************************************/
6560 :
6561 389 : OGRLayer *netCDFDataset::GetLayer(int nIdx)
6562 : {
6563 389 : if (nIdx < 0 || nIdx >= this->GetLayerCount())
6564 2 : return nullptr;
6565 387 : return papoLayers[nIdx].get();
6566 : }
6567 :
6568 : /************************************************************************/
6569 : /* ICreateLayer() */
6570 : /************************************************************************/
6571 :
6572 59 : OGRLayer *netCDFDataset::ICreateLayer(const char *pszName,
6573 : const OGRGeomFieldDefn *poGeomFieldDefn,
6574 : CSLConstList papszOptions)
6575 : {
6576 59 : int nLayerCDFId = cdfid;
6577 59 : if (!TestCapability(ODsCCreateLayer))
6578 0 : return nullptr;
6579 :
6580 59 : const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
6581 : const auto poSpatialRef =
6582 59 : poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
6583 :
6584 118 : CPLString osNetCDFLayerName(pszName);
6585 59 : const netCDFWriterConfigLayer *poLayerConfig = nullptr;
6586 59 : if (oWriterConfig.m_bIsValid)
6587 : {
6588 : std::map<CPLString, netCDFWriterConfigLayer>::const_iterator
6589 2 : oLayerIter = oWriterConfig.m_oLayers.find(pszName);
6590 2 : if (oLayerIter != oWriterConfig.m_oLayers.end())
6591 : {
6592 1 : poLayerConfig = &(oLayerIter->second);
6593 1 : osNetCDFLayerName = poLayerConfig->m_osNetCDFName;
6594 : }
6595 : }
6596 :
6597 59 : netCDFDataset *poLayerDataset = nullptr;
6598 59 : if (eMultipleLayerBehavior == SEPARATE_FILES)
6599 : {
6600 2 : char **papszDatasetOptions = nullptr;
6601 2 : papszDatasetOptions = CSLSetNameValue(
6602 : papszDatasetOptions, "CONFIG_FILE",
6603 2 : CSLFetchNameValue(papszCreationOptions, "CONFIG_FILE"));
6604 : papszDatasetOptions =
6605 2 : CSLSetNameValue(papszDatasetOptions, "FORMAT",
6606 2 : CSLFetchNameValue(papszCreationOptions, "FORMAT"));
6607 2 : papszDatasetOptions = CSLSetNameValue(
6608 : papszDatasetOptions, "WRITE_GDAL_TAGS",
6609 2 : CSLFetchNameValue(papszCreationOptions, "WRITE_GDAL_TAGS"));
6610 : CPLString osLayerFilename(
6611 2 : CPLFormFilename(osFilename, osNetCDFLayerName, "nc"));
6612 2 : CPLAcquireMutex(hNCMutex, 1000.0);
6613 2 : poLayerDataset =
6614 2 : CreateLL(osLayerFilename, 0, 0, 0, papszDatasetOptions);
6615 2 : CPLReleaseMutex(hNCMutex);
6616 2 : CSLDestroy(papszDatasetOptions);
6617 2 : if (poLayerDataset == nullptr)
6618 0 : return nullptr;
6619 :
6620 2 : nLayerCDFId = poLayerDataset->cdfid;
6621 2 : NCDFAddGDALHistory(nLayerCDFId, osLayerFilename, bWriteGDALVersion,
6622 2 : bWriteGDALHistory, "", "Create",
6623 : NCDF_CONVENTIONS_CF_V1_6);
6624 : }
6625 57 : else if (eMultipleLayerBehavior == SEPARATE_GROUPS)
6626 : {
6627 2 : SetDefineMode(true);
6628 :
6629 2 : nLayerCDFId = -1;
6630 2 : int status = nc_def_grp(cdfid, osNetCDFLayerName, &nLayerCDFId);
6631 2 : NCDF_ERR(status);
6632 2 : if (status != NC_NOERR)
6633 0 : return nullptr;
6634 :
6635 2 : NCDFAddGDALHistory(nLayerCDFId, osFilename, bWriteGDALVersion,
6636 2 : bWriteGDALHistory, "", "Create",
6637 : NCDF_CONVENTIONS_CF_V1_6);
6638 : }
6639 :
6640 : // Make a clone to workaround a bug in released MapServer versions
6641 : // that destroys the passed SRS instead of releasing it .
6642 59 : OGRSpatialReference *poSRS = nullptr;
6643 59 : if (poSpatialRef)
6644 : {
6645 43 : poSRS = poSpatialRef->Clone();
6646 43 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
6647 : }
6648 : std::shared_ptr<netCDFLayer> poLayer(
6649 59 : new netCDFLayer(poLayerDataset ? poLayerDataset : this, nLayerCDFId,
6650 118 : osNetCDFLayerName, eGType, poSRS));
6651 59 : if (poSRS != nullptr)
6652 43 : poSRS->Release();
6653 :
6654 : // Fetch layer creation options coming from config file
6655 59 : char **papszNewOptions = CSLDuplicate(papszOptions);
6656 59 : if (oWriterConfig.m_bIsValid)
6657 : {
6658 2 : std::map<CPLString, CPLString>::const_iterator oIter;
6659 3 : for (oIter = oWriterConfig.m_oLayerCreationOptions.begin();
6660 3 : oIter != oWriterConfig.m_oLayerCreationOptions.end(); ++oIter)
6661 : {
6662 : papszNewOptions =
6663 1 : CSLSetNameValue(papszNewOptions, oIter->first, oIter->second);
6664 : }
6665 2 : if (poLayerConfig != nullptr)
6666 : {
6667 3 : for (oIter = poLayerConfig->m_oLayerCreationOptions.begin();
6668 3 : oIter != poLayerConfig->m_oLayerCreationOptions.end(); ++oIter)
6669 : {
6670 2 : papszNewOptions = CSLSetNameValue(papszNewOptions, oIter->first,
6671 2 : oIter->second);
6672 : }
6673 : }
6674 : }
6675 :
6676 59 : const bool bRet = poLayer->Create(papszNewOptions, poLayerConfig);
6677 59 : CSLDestroy(papszNewOptions);
6678 :
6679 59 : if (!bRet)
6680 : {
6681 0 : return nullptr;
6682 : }
6683 :
6684 59 : if (poLayerDataset != nullptr)
6685 2 : apoVectorDatasets.push_back(poLayerDataset);
6686 :
6687 59 : papoLayers.push_back(poLayer);
6688 59 : return poLayer.get();
6689 : }
6690 :
6691 : /************************************************************************/
6692 : /* CloneAttributes() */
6693 : /************************************************************************/
6694 :
6695 137 : bool netCDFDataset::CloneAttributes(int old_cdfid, int new_cdfid, int nSrcVarId,
6696 : int nDstVarId)
6697 : {
6698 137 : int nAttCount = -1;
6699 137 : int status = nc_inq_varnatts(old_cdfid, nSrcVarId, &nAttCount);
6700 137 : NCDF_ERR(status);
6701 :
6702 693 : for (int i = 0; i < nAttCount; i++)
6703 : {
6704 : char szName[NC_MAX_NAME + 1];
6705 556 : szName[0] = 0;
6706 556 : status = nc_inq_attname(old_cdfid, nSrcVarId, i, szName);
6707 556 : NCDF_ERR(status);
6708 :
6709 : status =
6710 556 : nc_copy_att(old_cdfid, nSrcVarId, szName, new_cdfid, nDstVarId);
6711 556 : NCDF_ERR(status);
6712 556 : if (status != NC_NOERR)
6713 0 : return false;
6714 : }
6715 :
6716 137 : return true;
6717 : }
6718 :
6719 : /************************************************************************/
6720 : /* CloneVariableContent() */
6721 : /************************************************************************/
6722 :
6723 121 : bool netCDFDataset::CloneVariableContent(int old_cdfid, int new_cdfid,
6724 : int nSrcVarId, int nDstVarId)
6725 : {
6726 121 : int nVarDimCount = -1;
6727 121 : int status = nc_inq_varndims(old_cdfid, nSrcVarId, &nVarDimCount);
6728 121 : NCDF_ERR(status);
6729 121 : int anDimIds[] = {-1, 1};
6730 121 : status = nc_inq_vardimid(old_cdfid, nSrcVarId, anDimIds);
6731 121 : NCDF_ERR(status);
6732 121 : nc_type nc_datatype = NC_NAT;
6733 121 : status = nc_inq_vartype(old_cdfid, nSrcVarId, &nc_datatype);
6734 121 : NCDF_ERR(status);
6735 121 : size_t nTypeSize = 0;
6736 121 : switch (nc_datatype)
6737 : {
6738 35 : case NC_BYTE:
6739 : case NC_CHAR:
6740 35 : nTypeSize = 1;
6741 35 : break;
6742 4 : case NC_SHORT:
6743 4 : nTypeSize = 2;
6744 4 : break;
6745 24 : case NC_INT:
6746 24 : nTypeSize = 4;
6747 24 : break;
6748 4 : case NC_FLOAT:
6749 4 : nTypeSize = 4;
6750 4 : break;
6751 43 : case NC_DOUBLE:
6752 43 : nTypeSize = 8;
6753 43 : break;
6754 2 : case NC_UBYTE:
6755 2 : nTypeSize = 1;
6756 2 : break;
6757 2 : case NC_USHORT:
6758 2 : nTypeSize = 2;
6759 2 : break;
6760 2 : case NC_UINT:
6761 2 : nTypeSize = 4;
6762 2 : break;
6763 4 : case NC_INT64:
6764 : case NC_UINT64:
6765 4 : nTypeSize = 8;
6766 4 : break;
6767 1 : case NC_STRING:
6768 1 : nTypeSize = sizeof(char *);
6769 1 : break;
6770 0 : default:
6771 : {
6772 0 : CPLError(CE_Failure, CPLE_NotSupported, "Unsupported data type: %d",
6773 : nc_datatype);
6774 0 : return false;
6775 : }
6776 : }
6777 :
6778 121 : size_t nElems = 1;
6779 : size_t anStart[NC_MAX_DIMS];
6780 : size_t anCount[NC_MAX_DIMS];
6781 121 : size_t nRecords = 1;
6782 261 : for (int i = 0; i < nVarDimCount; i++)
6783 : {
6784 140 : anStart[i] = 0;
6785 140 : if (i == 0)
6786 : {
6787 116 : anCount[i] = 1;
6788 116 : status = nc_inq_dimlen(old_cdfid, anDimIds[i], &nRecords);
6789 116 : NCDF_ERR(status);
6790 : }
6791 : else
6792 : {
6793 24 : anCount[i] = 0;
6794 24 : status = nc_inq_dimlen(old_cdfid, anDimIds[i], &anCount[i]);
6795 24 : NCDF_ERR(status);
6796 24 : nElems *= anCount[i];
6797 : }
6798 : }
6799 :
6800 : /* Workaround in some cases a netCDF bug:
6801 : * https://github.com/Unidata/netcdf-c/pull/1442 */
6802 121 : if (nRecords > 0 && nRecords < 10 * 1000 * 1000 / (nElems * nTypeSize))
6803 : {
6804 119 : nElems *= nRecords;
6805 119 : anCount[0] = nRecords;
6806 119 : nRecords = 1;
6807 : }
6808 :
6809 121 : void *pBuffer = VSI_MALLOC2_VERBOSE(nElems, nTypeSize);
6810 121 : if (pBuffer == nullptr)
6811 0 : return false;
6812 :
6813 240 : for (size_t iRecord = 0; iRecord < nRecords; iRecord++)
6814 : {
6815 119 : anStart[0] = iRecord;
6816 :
6817 119 : switch (nc_datatype)
6818 : {
6819 5 : case NC_BYTE:
6820 : status =
6821 5 : nc_get_vara_schar(old_cdfid, nSrcVarId, anStart, anCount,
6822 : static_cast<signed char *>(pBuffer));
6823 5 : if (!status)
6824 5 : status = nc_put_vara_schar(
6825 : new_cdfid, nDstVarId, anStart, anCount,
6826 : static_cast<signed char *>(pBuffer));
6827 5 : break;
6828 28 : case NC_CHAR:
6829 : status =
6830 28 : nc_get_vara_text(old_cdfid, nSrcVarId, anStart, anCount,
6831 : static_cast<char *>(pBuffer));
6832 28 : if (!status)
6833 : status =
6834 28 : nc_put_vara_text(new_cdfid, nDstVarId, anStart, anCount,
6835 : static_cast<char *>(pBuffer));
6836 28 : break;
6837 4 : case NC_SHORT:
6838 : status =
6839 4 : nc_get_vara_short(old_cdfid, nSrcVarId, anStart, anCount,
6840 : static_cast<short *>(pBuffer));
6841 4 : if (!status)
6842 4 : status = nc_put_vara_short(new_cdfid, nDstVarId, anStart,
6843 : anCount,
6844 : static_cast<short *>(pBuffer));
6845 4 : break;
6846 24 : case NC_INT:
6847 24 : status = nc_get_vara_int(old_cdfid, nSrcVarId, anStart, anCount,
6848 : static_cast<int *>(pBuffer));
6849 24 : if (!status)
6850 : status =
6851 24 : nc_put_vara_int(new_cdfid, nDstVarId, anStart, anCount,
6852 : static_cast<int *>(pBuffer));
6853 24 : break;
6854 4 : case NC_FLOAT:
6855 : status =
6856 4 : nc_get_vara_float(old_cdfid, nSrcVarId, anStart, anCount,
6857 : static_cast<float *>(pBuffer));
6858 4 : if (!status)
6859 4 : status = nc_put_vara_float(new_cdfid, nDstVarId, anStart,
6860 : anCount,
6861 : static_cast<float *>(pBuffer));
6862 4 : break;
6863 43 : case NC_DOUBLE:
6864 : status =
6865 43 : nc_get_vara_double(old_cdfid, nSrcVarId, anStart, anCount,
6866 : static_cast<double *>(pBuffer));
6867 43 : if (!status)
6868 43 : status = nc_put_vara_double(new_cdfid, nDstVarId, anStart,
6869 : anCount,
6870 : static_cast<double *>(pBuffer));
6871 43 : break;
6872 1 : case NC_STRING:
6873 : status =
6874 1 : nc_get_vara_string(old_cdfid, nSrcVarId, anStart, anCount,
6875 : static_cast<char **>(pBuffer));
6876 1 : if (!status)
6877 : {
6878 1 : status = nc_put_vara_string(
6879 : new_cdfid, nDstVarId, anStart, anCount,
6880 : static_cast<const char **>(pBuffer));
6881 1 : nc_free_string(nElems, static_cast<char **>(pBuffer));
6882 : }
6883 1 : break;
6884 :
6885 2 : case NC_UBYTE:
6886 : status =
6887 2 : nc_get_vara_uchar(old_cdfid, nSrcVarId, anStart, anCount,
6888 : static_cast<unsigned char *>(pBuffer));
6889 2 : if (!status)
6890 2 : status = nc_put_vara_uchar(
6891 : new_cdfid, nDstVarId, anStart, anCount,
6892 : static_cast<unsigned char *>(pBuffer));
6893 2 : break;
6894 2 : case NC_USHORT:
6895 : status =
6896 2 : nc_get_vara_ushort(old_cdfid, nSrcVarId, anStart, anCount,
6897 : static_cast<unsigned short *>(pBuffer));
6898 2 : if (!status)
6899 2 : status = nc_put_vara_ushort(
6900 : new_cdfid, nDstVarId, anStart, anCount,
6901 : static_cast<unsigned short *>(pBuffer));
6902 2 : break;
6903 2 : case NC_UINT:
6904 : status =
6905 2 : nc_get_vara_uint(old_cdfid, nSrcVarId, anStart, anCount,
6906 : static_cast<unsigned int *>(pBuffer));
6907 2 : if (!status)
6908 : status =
6909 2 : nc_put_vara_uint(new_cdfid, nDstVarId, anStart, anCount,
6910 : static_cast<unsigned int *>(pBuffer));
6911 2 : break;
6912 2 : case NC_INT64:
6913 : status =
6914 2 : nc_get_vara_longlong(old_cdfid, nSrcVarId, anStart, anCount,
6915 : static_cast<long long *>(pBuffer));
6916 2 : if (!status)
6917 2 : status = nc_put_vara_longlong(
6918 : new_cdfid, nDstVarId, anStart, anCount,
6919 : static_cast<long long *>(pBuffer));
6920 2 : break;
6921 2 : case NC_UINT64:
6922 2 : status = nc_get_vara_ulonglong(
6923 : old_cdfid, nSrcVarId, anStart, anCount,
6924 : static_cast<unsigned long long *>(pBuffer));
6925 2 : if (!status)
6926 2 : status = nc_put_vara_ulonglong(
6927 : new_cdfid, nDstVarId, anStart, anCount,
6928 : static_cast<unsigned long long *>(pBuffer));
6929 2 : break;
6930 0 : default:
6931 0 : status = NC_EBADTYPE;
6932 : }
6933 :
6934 119 : NCDF_ERR(status);
6935 119 : if (status != NC_NOERR)
6936 : {
6937 0 : VSIFree(pBuffer);
6938 0 : return false;
6939 : }
6940 : }
6941 :
6942 121 : VSIFree(pBuffer);
6943 121 : return true;
6944 : }
6945 :
6946 : /************************************************************************/
6947 : /* NCDFIsUnlimitedDim() */
6948 : /************************************************************************/
6949 :
6950 57 : bool NCDFIsUnlimitedDim(bool bIsNC4, int cdfid, int nDimId)
6951 : {
6952 57 : if (bIsNC4)
6953 : {
6954 16 : int nUnlimitedDims = 0;
6955 16 : nc_inq_unlimdims(cdfid, &nUnlimitedDims, nullptr);
6956 16 : bool bFound = false;
6957 16 : if (nUnlimitedDims)
6958 : {
6959 : int *panUnlimitedDimIds =
6960 16 : static_cast<int *>(CPLMalloc(sizeof(int) * nUnlimitedDims));
6961 16 : nc_inq_unlimdims(cdfid, nullptr, panUnlimitedDimIds);
6962 30 : for (int i = 0; i < nUnlimitedDims; i++)
6963 : {
6964 22 : if (panUnlimitedDimIds[i] == nDimId)
6965 : {
6966 8 : bFound = true;
6967 8 : break;
6968 : }
6969 : }
6970 16 : CPLFree(panUnlimitedDimIds);
6971 : }
6972 16 : return bFound;
6973 : }
6974 : else
6975 : {
6976 41 : int nUnlimitedDimId = -1;
6977 41 : nc_inq(cdfid, nullptr, nullptr, nullptr, &nUnlimitedDimId);
6978 41 : return nDimId == nUnlimitedDimId;
6979 : }
6980 : }
6981 :
6982 : /************************************************************************/
6983 : /* CloneGrp() */
6984 : /************************************************************************/
6985 :
6986 16 : bool netCDFDataset::CloneGrp(int nOldGrpId, int nNewGrpId, bool bIsNC4,
6987 : int nLayerId, int nDimIdToGrow, size_t nNewSize)
6988 : {
6989 : // Clone dimensions
6990 16 : int nDimCount = -1;
6991 16 : int status = nc_inq_ndims(nOldGrpId, &nDimCount);
6992 16 : NCDF_ERR(status);
6993 16 : int *panDimIds = static_cast<int *>(CPLMalloc(sizeof(int) * nDimCount));
6994 16 : int nUnlimiDimID = -1;
6995 16 : status = nc_inq_unlimdim(nOldGrpId, &nUnlimiDimID);
6996 16 : NCDF_ERR(status);
6997 16 : if (bIsNC4)
6998 : {
6999 : // In NC4, the dimension ids of a group are not necessarily in
7000 : // [0,nDimCount-1] range
7001 8 : int nDimCount2 = -1;
7002 8 : status = nc_inq_dimids(nOldGrpId, &nDimCount2, panDimIds, FALSE);
7003 8 : NCDF_ERR(status);
7004 8 : CPLAssert(nDimCount == nDimCount2);
7005 : }
7006 : else
7007 : {
7008 36 : for (int i = 0; i < nDimCount; i++)
7009 28 : panDimIds[i] = i;
7010 : }
7011 60 : for (int i = 0; i < nDimCount; i++)
7012 : {
7013 : char szDimName[NC_MAX_NAME + 1];
7014 44 : szDimName[0] = 0;
7015 44 : size_t nLen = 0;
7016 44 : const int nDimId = panDimIds[i];
7017 44 : status = nc_inq_dim(nOldGrpId, nDimId, szDimName, &nLen);
7018 44 : NCDF_ERR(status);
7019 44 : if (NCDFIsUnlimitedDim(bIsNC4, nOldGrpId, nDimId))
7020 16 : nLen = NC_UNLIMITED;
7021 28 : else if (nDimId == nDimIdToGrow && nOldGrpId == nLayerId)
7022 13 : nLen = nNewSize;
7023 44 : int nNewDimId = -1;
7024 44 : status = nc_def_dim(nNewGrpId, szDimName, nLen, &nNewDimId);
7025 44 : NCDF_ERR(status);
7026 44 : CPLAssert(nDimId == nNewDimId);
7027 44 : if (status != NC_NOERR)
7028 : {
7029 0 : CPLFree(panDimIds);
7030 0 : return false;
7031 : }
7032 : }
7033 16 : CPLFree(panDimIds);
7034 :
7035 : // Clone main attributes
7036 16 : if (!CloneAttributes(nOldGrpId, nNewGrpId, NC_GLOBAL, NC_GLOBAL))
7037 : {
7038 0 : return false;
7039 : }
7040 :
7041 : // Clone variable definitions
7042 16 : int nVarCount = -1;
7043 16 : status = nc_inq_nvars(nOldGrpId, &nVarCount);
7044 16 : NCDF_ERR(status);
7045 :
7046 137 : for (int i = 0; i < nVarCount; i++)
7047 : {
7048 : char szVarName[NC_MAX_NAME + 1];
7049 121 : szVarName[0] = 0;
7050 121 : status = nc_inq_varname(nOldGrpId, i, szVarName);
7051 121 : NCDF_ERR(status);
7052 121 : nc_type nc_datatype = NC_NAT;
7053 121 : status = nc_inq_vartype(nOldGrpId, i, &nc_datatype);
7054 121 : NCDF_ERR(status);
7055 121 : int nVarDimCount = -1;
7056 121 : status = nc_inq_varndims(nOldGrpId, i, &nVarDimCount);
7057 121 : NCDF_ERR(status);
7058 : int anDimIds[NC_MAX_DIMS];
7059 121 : status = nc_inq_vardimid(nOldGrpId, i, anDimIds);
7060 121 : NCDF_ERR(status);
7061 121 : int nNewVarId = -1;
7062 121 : status = nc_def_var(nNewGrpId, szVarName, nc_datatype, nVarDimCount,
7063 : anDimIds, &nNewVarId);
7064 121 : NCDF_ERR(status);
7065 121 : CPLAssert(i == nNewVarId);
7066 121 : if (status != NC_NOERR)
7067 : {
7068 0 : return false;
7069 : }
7070 :
7071 121 : if (!CloneAttributes(nOldGrpId, nNewGrpId, i, i))
7072 : {
7073 0 : return false;
7074 : }
7075 : }
7076 :
7077 16 : status = nc_enddef(nNewGrpId);
7078 16 : NCDF_ERR(status);
7079 16 : if (status != NC_NOERR)
7080 : {
7081 0 : return false;
7082 : }
7083 :
7084 : // Clone variable content
7085 137 : for (int i = 0; i < nVarCount; i++)
7086 : {
7087 121 : if (!CloneVariableContent(nOldGrpId, nNewGrpId, i, i))
7088 : {
7089 0 : return false;
7090 : }
7091 : }
7092 :
7093 16 : return true;
7094 : }
7095 :
7096 : /************************************************************************/
7097 : /* GrowDim() */
7098 : /************************************************************************/
7099 :
7100 13 : bool netCDFDataset::GrowDim(int nLayerId, int nDimIdToGrow, size_t nNewSize)
7101 : {
7102 : int nCreationMode;
7103 : // Set nCreationMode based on eFormat.
7104 13 : switch (eFormat)
7105 : {
7106 : #ifdef NETCDF_HAS_NC2
7107 0 : case NCDF_FORMAT_NC2:
7108 0 : nCreationMode = NC_CLOBBER | NC_64BIT_OFFSET;
7109 0 : break;
7110 : #endif
7111 5 : case NCDF_FORMAT_NC4:
7112 5 : nCreationMode = NC_CLOBBER | NC_NETCDF4;
7113 5 : break;
7114 0 : case NCDF_FORMAT_NC4C:
7115 0 : nCreationMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
7116 0 : break;
7117 8 : case NCDF_FORMAT_NC:
7118 : default:
7119 8 : nCreationMode = NC_CLOBBER;
7120 8 : break;
7121 : }
7122 :
7123 13 : int new_cdfid = -1;
7124 26 : CPLString osTmpFilename(osFilename + ".tmp");
7125 26 : CPLString osFilenameForNCCreate(osTmpFilename);
7126 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
7127 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
7128 : {
7129 : char *pszTemp =
7130 : CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
7131 : osFilenameForNCCreate = pszTemp;
7132 : CPLFree(pszTemp);
7133 : }
7134 : #endif
7135 13 : int status = nc_create(osFilenameForNCCreate, nCreationMode, &new_cdfid);
7136 13 : NCDF_ERR(status);
7137 13 : if (status != NC_NOERR)
7138 0 : return false;
7139 :
7140 13 : if (!CloneGrp(cdfid, new_cdfid, eFormat == NCDF_FORMAT_NC4, nLayerId,
7141 : nDimIdToGrow, nNewSize))
7142 : {
7143 0 : GDAL_nc_close(new_cdfid);
7144 0 : return false;
7145 : }
7146 :
7147 13 : int nGroupCount = 0;
7148 26 : std::vector<CPLString> oListGrpName;
7149 31 : if (eFormat == NCDF_FORMAT_NC4 &&
7150 18 : nc_inq_grps(cdfid, &nGroupCount, nullptr) == NC_NOERR &&
7151 5 : nGroupCount > 0)
7152 : {
7153 : int *panGroupIds =
7154 2 : static_cast<int *>(CPLMalloc(sizeof(int) * nGroupCount));
7155 2 : status = nc_inq_grps(cdfid, nullptr, panGroupIds);
7156 2 : NCDF_ERR(status);
7157 5 : for (int i = 0; i < nGroupCount; i++)
7158 : {
7159 : char szGroupName[NC_MAX_NAME + 1];
7160 3 : szGroupName[0] = 0;
7161 3 : nc_inq_grpname(panGroupIds[i], szGroupName);
7162 3 : int nNewGrpId = -1;
7163 3 : status = nc_def_grp(new_cdfid, szGroupName, &nNewGrpId);
7164 3 : NCDF_ERR(status);
7165 3 : if (status != NC_NOERR)
7166 : {
7167 0 : CPLFree(panGroupIds);
7168 0 : GDAL_nc_close(new_cdfid);
7169 0 : return false;
7170 : }
7171 3 : if (!CloneGrp(panGroupIds[i], nNewGrpId, /*bIsNC4=*/true, nLayerId,
7172 : nDimIdToGrow, nNewSize))
7173 : {
7174 0 : CPLFree(panGroupIds);
7175 0 : GDAL_nc_close(new_cdfid);
7176 0 : return false;
7177 : }
7178 : }
7179 2 : CPLFree(panGroupIds);
7180 :
7181 5 : for (int i = 0; i < this->GetLayerCount(); i++)
7182 : {
7183 3 : auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
7184 3 : if (poLayer)
7185 : {
7186 : char szGroupName[NC_MAX_NAME + 1];
7187 3 : szGroupName[0] = 0;
7188 3 : status = nc_inq_grpname(poLayer->GetCDFID(), szGroupName);
7189 3 : NCDF_ERR(status);
7190 3 : oListGrpName.push_back(szGroupName);
7191 : }
7192 : }
7193 : }
7194 :
7195 13 : GDAL_nc_close(cdfid);
7196 13 : cdfid = -1;
7197 13 : GDAL_nc_close(new_cdfid);
7198 :
7199 26 : CPLString osOriFilename(osFilename + ".ori");
7200 26 : if (VSIRename(osFilename, osOriFilename) != 0 ||
7201 13 : VSIRename(osTmpFilename, osFilename) != 0)
7202 : {
7203 0 : CPLError(CE_Failure, CPLE_FileIO, "Renaming of files failed");
7204 0 : return false;
7205 : }
7206 13 : VSIUnlink(osOriFilename);
7207 :
7208 26 : CPLString osFilenameForNCOpen(osFilename);
7209 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
7210 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
7211 : {
7212 : char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
7213 : osFilenameForNCOpen = pszTemp;
7214 : CPLFree(pszTemp);
7215 : }
7216 : #endif
7217 13 : status = GDAL_nc_open(osFilenameForNCOpen, NC_WRITE, &cdfid);
7218 13 : NCDF_ERR(status);
7219 13 : if (status != NC_NOERR)
7220 0 : return false;
7221 13 : bDefineMode = false;
7222 :
7223 13 : if (!oListGrpName.empty())
7224 : {
7225 5 : for (int i = 0; i < this->GetLayerCount(); i++)
7226 : {
7227 3 : auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
7228 3 : if (poLayer)
7229 : {
7230 3 : int nNewLayerCDFID = -1;
7231 3 : status = nc_inq_ncid(cdfid, oListGrpName[i].c_str(),
7232 : &nNewLayerCDFID);
7233 3 : NCDF_ERR(status);
7234 3 : poLayer->SetCDFID(nNewLayerCDFID);
7235 : }
7236 : }
7237 : }
7238 : else
7239 : {
7240 22 : for (int i = 0; i < this->GetLayerCount(); i++)
7241 : {
7242 11 : auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
7243 11 : if (poLayer)
7244 11 : poLayer->SetCDFID(cdfid);
7245 : }
7246 : }
7247 :
7248 13 : return true;
7249 : }
7250 :
7251 : #ifdef ENABLE_NCDUMP
7252 :
7253 : /************************************************************************/
7254 : /* netCDFDatasetCreateTempFile() */
7255 : /************************************************************************/
7256 :
7257 : /* Create a netCDF file from a text dump (format of ncdump) */
7258 : /* Mostly to easy fuzzing of the driver, while still generating valid */
7259 : /* netCDF files. */
7260 : /* Note: not all data types are supported ! */
7261 4 : bool netCDFDatasetCreateTempFile(NetCDFFormatEnum eFormat,
7262 : const char *pszTmpFilename, VSILFILE *fpSrc)
7263 : {
7264 4 : CPL_IGNORE_RET_VAL(eFormat);
7265 4 : int nCreateMode = NC_CLOBBER;
7266 4 : if (eFormat == NCDF_FORMAT_NC4)
7267 1 : nCreateMode |= NC_NETCDF4;
7268 3 : else if (eFormat == NCDF_FORMAT_NC4C)
7269 0 : nCreateMode |= NC_NETCDF4 | NC_CLASSIC_MODEL;
7270 4 : int nCdfId = -1;
7271 4 : int status = nc_create(pszTmpFilename, nCreateMode, &nCdfId);
7272 4 : if (status != NC_NOERR)
7273 : {
7274 0 : return false;
7275 : }
7276 4 : VSIFSeekL(fpSrc, 0, SEEK_SET);
7277 : const char *pszLine;
7278 4 : constexpr int SECTION_NONE = 0;
7279 4 : constexpr int SECTION_DIMENSIONS = 1;
7280 4 : constexpr int SECTION_VARIABLES = 2;
7281 4 : constexpr int SECTION_DATA = 3;
7282 4 : int nActiveSection = SECTION_NONE;
7283 8 : std::map<CPLString, int> oMapDimToId;
7284 8 : std::map<int, int> oMapDimIdToDimLen;
7285 8 : std::map<CPLString, int> oMapVarToId;
7286 8 : std::map<int, std::vector<int>> oMapVarIdToVectorOfDimId;
7287 8 : std::map<int, int> oMapVarIdToType;
7288 4 : std::set<CPLString> oSetAttrDefined;
7289 4 : oMapVarToId[""] = -1;
7290 4 : size_t nTotalVarSize = 0;
7291 208 : while ((pszLine = CPLReadLineL(fpSrc)) != nullptr)
7292 : {
7293 204 : if (STARTS_WITH(pszLine, "dimensions:") &&
7294 : nActiveSection == SECTION_NONE)
7295 : {
7296 4 : nActiveSection = SECTION_DIMENSIONS;
7297 : }
7298 200 : else if (STARTS_WITH(pszLine, "variables:") &&
7299 : nActiveSection == SECTION_DIMENSIONS)
7300 : {
7301 4 : nActiveSection = SECTION_VARIABLES;
7302 : }
7303 196 : else if (STARTS_WITH(pszLine, "data:") &&
7304 : nActiveSection == SECTION_VARIABLES)
7305 : {
7306 4 : nActiveSection = SECTION_DATA;
7307 4 : status = nc_enddef(nCdfId);
7308 4 : if (status != NC_NOERR)
7309 : {
7310 0 : CPLDebug("netCDF", "nc_enddef() failed: %s",
7311 : nc_strerror(status));
7312 : }
7313 : }
7314 192 : else if (nActiveSection == SECTION_DIMENSIONS)
7315 : {
7316 9 : char **papszTokens = CSLTokenizeString2(pszLine, " \t=;", 0);
7317 9 : if (CSLCount(papszTokens) == 2)
7318 : {
7319 9 : const char *pszDimName = papszTokens[0];
7320 9 : bool bValidName = true;
7321 9 : if (STARTS_WITH(pszDimName, "_nc4_non_coord_"))
7322 : {
7323 : // This is an internal netcdf prefix. Using it may
7324 : // cause memory leaks.
7325 0 : bValidName = false;
7326 : }
7327 9 : if (!bValidName)
7328 : {
7329 0 : CPLDebug("netCDF",
7330 : "nc_def_dim(%s) failed: invalid name found",
7331 : pszDimName);
7332 0 : CSLDestroy(papszTokens);
7333 0 : continue;
7334 : }
7335 :
7336 : const bool bIsASCII =
7337 9 : CPLIsASCII(pszDimName, static_cast<size_t>(-1));
7338 9 : if (!bIsASCII)
7339 : {
7340 : // Workaround https://github.com/Unidata/netcdf-c/pull/450
7341 0 : CPLDebug("netCDF",
7342 : "nc_def_dim(%s) failed: rejected because "
7343 : "of non-ASCII characters",
7344 : pszDimName);
7345 0 : CSLDestroy(papszTokens);
7346 0 : continue;
7347 : }
7348 9 : int nDimSize = EQUAL(papszTokens[1], "UNLIMITED")
7349 : ? NC_UNLIMITED
7350 9 : : atoi(papszTokens[1]);
7351 9 : if (nDimSize >= 1000)
7352 1 : nDimSize = 1000; // to avoid very long processing
7353 9 : if (nDimSize >= 0)
7354 : {
7355 9 : int nDimId = -1;
7356 9 : status = nc_def_dim(nCdfId, pszDimName, nDimSize, &nDimId);
7357 9 : if (status != NC_NOERR)
7358 : {
7359 0 : CPLDebug("netCDF", "nc_def_dim(%s, %d) failed: %s",
7360 : pszDimName, nDimSize, nc_strerror(status));
7361 : }
7362 : else
7363 : {
7364 : #ifdef DEBUG_VERBOSE
7365 : CPLDebug("netCDF", "nc_def_dim(%s, %d) (%s) succeeded",
7366 : pszDimName, nDimSize, pszLine);
7367 : #endif
7368 9 : oMapDimToId[pszDimName] = nDimId;
7369 9 : oMapDimIdToDimLen[nDimId] = nDimSize;
7370 : }
7371 : }
7372 : }
7373 9 : CSLDestroy(papszTokens);
7374 : }
7375 183 : else if (nActiveSection == SECTION_VARIABLES)
7376 : {
7377 390 : while (*pszLine == ' ' || *pszLine == '\t')
7378 249 : pszLine++;
7379 141 : const char *pszColumn = strchr(pszLine, ':');
7380 141 : const char *pszEqual = strchr(pszLine, '=');
7381 141 : if (pszColumn == nullptr)
7382 : {
7383 21 : char **papszTokens = CSLTokenizeString2(pszLine, " \t=(),;", 0);
7384 21 : if (CSLCount(papszTokens) >= 2)
7385 : {
7386 17 : const char *pszVarName = papszTokens[1];
7387 17 : bool bValidName = true;
7388 17 : if (STARTS_WITH(pszVarName, "_nc4_non_coord_"))
7389 : {
7390 : // This is an internal netcdf prefix. Using it may
7391 : // cause memory leaks.
7392 0 : bValidName = false;
7393 : }
7394 138 : for (int i = 0; pszVarName[i]; i++)
7395 : {
7396 121 : if (!((pszVarName[i] >= 'a' && pszVarName[i] <= 'z') ||
7397 28 : (pszVarName[i] >= 'A' && pszVarName[i] <= 'Z') ||
7398 9 : (pszVarName[i] >= '0' && pszVarName[i] <= '9') ||
7399 6 : pszVarName[i] == '_'))
7400 : {
7401 0 : bValidName = false;
7402 : }
7403 : }
7404 17 : if (!bValidName)
7405 : {
7406 0 : CPLDebug(
7407 : "netCDF",
7408 : "nc_def_var(%s) failed: illegal character found",
7409 : pszVarName);
7410 0 : CSLDestroy(papszTokens);
7411 0 : continue;
7412 : }
7413 17 : if (oMapVarToId.find(pszVarName) != oMapVarToId.end())
7414 : {
7415 0 : CPLDebug("netCDF",
7416 : "nc_def_var(%s) failed: already defined",
7417 : pszVarName);
7418 0 : CSLDestroy(papszTokens);
7419 0 : continue;
7420 : }
7421 17 : const char *pszVarType = papszTokens[0];
7422 17 : int nc_datatype = NC_BYTE;
7423 17 : size_t nDataTypeSize = 1;
7424 17 : if (EQUAL(pszVarType, "char"))
7425 : {
7426 6 : nc_datatype = NC_CHAR;
7427 6 : nDataTypeSize = 1;
7428 : }
7429 11 : else if (EQUAL(pszVarType, "byte"))
7430 : {
7431 3 : nc_datatype = NC_BYTE;
7432 3 : nDataTypeSize = 1;
7433 : }
7434 8 : else if (EQUAL(pszVarType, "short"))
7435 : {
7436 0 : nc_datatype = NC_SHORT;
7437 0 : nDataTypeSize = 2;
7438 : }
7439 8 : else if (EQUAL(pszVarType, "int"))
7440 : {
7441 0 : nc_datatype = NC_INT;
7442 0 : nDataTypeSize = 4;
7443 : }
7444 8 : else if (EQUAL(pszVarType, "float"))
7445 : {
7446 0 : nc_datatype = NC_FLOAT;
7447 0 : nDataTypeSize = 4;
7448 : }
7449 8 : else if (EQUAL(pszVarType, "double"))
7450 : {
7451 8 : nc_datatype = NC_DOUBLE;
7452 8 : nDataTypeSize = 8;
7453 : }
7454 0 : else if (EQUAL(pszVarType, "ubyte"))
7455 : {
7456 0 : nc_datatype = NC_UBYTE;
7457 0 : nDataTypeSize = 1;
7458 : }
7459 0 : else if (EQUAL(pszVarType, "ushort"))
7460 : {
7461 0 : nc_datatype = NC_USHORT;
7462 0 : nDataTypeSize = 2;
7463 : }
7464 0 : else if (EQUAL(pszVarType, "uint"))
7465 : {
7466 0 : nc_datatype = NC_UINT;
7467 0 : nDataTypeSize = 4;
7468 : }
7469 0 : else if (EQUAL(pszVarType, "int64"))
7470 : {
7471 0 : nc_datatype = NC_INT64;
7472 0 : nDataTypeSize = 8;
7473 : }
7474 0 : else if (EQUAL(pszVarType, "uint64"))
7475 : {
7476 0 : nc_datatype = NC_UINT64;
7477 0 : nDataTypeSize = 8;
7478 : }
7479 :
7480 17 : int nDims = CSLCount(papszTokens) - 2;
7481 17 : if (nDims >= 32)
7482 : {
7483 : // The number of dimensions in a netCDFv4 file is
7484 : // limited by #define H5S_MAX_RANK 32
7485 : // but libnetcdf doesn't check that...
7486 0 : CPLDebug("netCDF",
7487 : "nc_def_var(%s) failed: too many dimensions",
7488 : pszVarName);
7489 0 : CSLDestroy(papszTokens);
7490 0 : continue;
7491 : }
7492 17 : std::vector<int> aoDimIds;
7493 17 : bool bFailed = false;
7494 17 : size_t nSize = 1;
7495 35 : for (int i = 0; i < nDims; i++)
7496 : {
7497 18 : const char *pszDimName = papszTokens[2 + i];
7498 18 : if (oMapDimToId.find(pszDimName) == oMapDimToId.end())
7499 : {
7500 0 : bFailed = true;
7501 0 : break;
7502 : }
7503 18 : const int nDimId = oMapDimToId[pszDimName];
7504 18 : aoDimIds.push_back(nDimId);
7505 :
7506 18 : const size_t nDimSize = oMapDimIdToDimLen[nDimId];
7507 18 : if (nDimSize != 0)
7508 : {
7509 18 : if (nSize >
7510 18 : std::numeric_limits<size_t>::max() / nDimSize)
7511 : {
7512 0 : bFailed = true;
7513 0 : break;
7514 : }
7515 : else
7516 : {
7517 18 : nSize *= nDimSize;
7518 : }
7519 : }
7520 : }
7521 17 : if (bFailed)
7522 : {
7523 0 : CPLDebug("netCDF",
7524 : "nc_def_var(%s) failed: unknown dimension(s)",
7525 : pszVarName);
7526 0 : CSLDestroy(papszTokens);
7527 0 : continue;
7528 : }
7529 17 : if (nSize > 100U * 1024 * 1024 / nDataTypeSize)
7530 : {
7531 0 : CPLDebug("netCDF",
7532 : "nc_def_var(%s) failed: too large data",
7533 : pszVarName);
7534 0 : CSLDestroy(papszTokens);
7535 0 : continue;
7536 : }
7537 17 : if (nTotalVarSize >
7538 34 : std::numeric_limits<size_t>::max() - nSize ||
7539 17 : nTotalVarSize + nSize > 100 * 1024 * 1024)
7540 : {
7541 0 : CPLDebug("netCDF",
7542 : "nc_def_var(%s) failed: too large data",
7543 : pszVarName);
7544 0 : CSLDestroy(papszTokens);
7545 0 : continue;
7546 : }
7547 17 : nTotalVarSize += nSize;
7548 :
7549 17 : int nVarId = -1;
7550 : status =
7551 30 : nc_def_var(nCdfId, pszVarName, nc_datatype, nDims,
7552 13 : (nDims) ? &aoDimIds[0] : nullptr, &nVarId);
7553 17 : if (status != NC_NOERR)
7554 : {
7555 0 : CPLDebug("netCDF", "nc_def_var(%s) failed: %s",
7556 : pszVarName, nc_strerror(status));
7557 : }
7558 : else
7559 : {
7560 : #ifdef DEBUG_VERBOSE
7561 : CPLDebug("netCDF", "nc_def_var(%s) (%s) succeeded",
7562 : pszVarName, pszLine);
7563 : #endif
7564 17 : oMapVarToId[pszVarName] = nVarId;
7565 17 : oMapVarIdToType[nVarId] = nc_datatype;
7566 17 : oMapVarIdToVectorOfDimId[nVarId] = std::move(aoDimIds);
7567 : }
7568 : }
7569 21 : CSLDestroy(papszTokens);
7570 : }
7571 120 : else if (pszEqual != nullptr && pszEqual - pszColumn > 0)
7572 : {
7573 116 : CPLString osVarName(pszLine, pszColumn - pszLine);
7574 116 : CPLString osAttrName(pszColumn + 1, pszEqual - pszColumn - 1);
7575 116 : osAttrName.Trim();
7576 116 : if (oMapVarToId.find(osVarName) == oMapVarToId.end())
7577 : {
7578 0 : CPLDebug("netCDF",
7579 : "nc_put_att(%s:%s) failed: "
7580 : "no corresponding variable",
7581 : osVarName.c_str(), osAttrName.c_str());
7582 0 : continue;
7583 : }
7584 116 : bool bValidName = true;
7585 1743 : for (size_t i = 0; i < osAttrName.size(); i++)
7586 : {
7587 1865 : if (!((osAttrName[i] >= 'a' && osAttrName[i] <= 'z') ||
7588 238 : (osAttrName[i] >= 'A' && osAttrName[i] <= 'Z') ||
7589 158 : (osAttrName[i] >= '0' && osAttrName[i] <= '9') ||
7590 158 : osAttrName[i] == '_'))
7591 : {
7592 0 : bValidName = false;
7593 : }
7594 : }
7595 116 : if (!bValidName)
7596 : {
7597 0 : CPLDebug(
7598 : "netCDF",
7599 : "nc_put_att(%s:%s) failed: illegal character found",
7600 : osVarName.c_str(), osAttrName.c_str());
7601 0 : continue;
7602 : }
7603 116 : if (oSetAttrDefined.find(osVarName + ":" + osAttrName) !=
7604 232 : oSetAttrDefined.end())
7605 : {
7606 0 : CPLDebug("netCDF",
7607 : "nc_put_att(%s:%s) failed: already defined",
7608 : osVarName.c_str(), osAttrName.c_str());
7609 0 : continue;
7610 : }
7611 :
7612 116 : const int nVarId = oMapVarToId[osVarName];
7613 116 : const char *pszValue = pszEqual + 1;
7614 232 : while (*pszValue == ' ')
7615 116 : pszValue++;
7616 :
7617 116 : status = NC_EBADTYPE;
7618 116 : if (*pszValue == '"')
7619 : {
7620 : // For _FillValue, the attribute type should match
7621 : // the variable type. Leaks memory with NC4 otherwise
7622 74 : if (osAttrName == "_FillValue")
7623 : {
7624 0 : CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
7625 : osVarName.c_str(), osAttrName.c_str(),
7626 : nc_strerror(status));
7627 0 : continue;
7628 : }
7629 :
7630 : // Unquote and unescape string value
7631 74 : CPLString osVal(pszValue + 1);
7632 222 : while (!osVal.empty())
7633 : {
7634 222 : if (osVal.back() == ';' || osVal.back() == ' ')
7635 : {
7636 148 : osVal.resize(osVal.size() - 1);
7637 : }
7638 74 : else if (osVal.back() == '"')
7639 : {
7640 74 : osVal.resize(osVal.size() - 1);
7641 74 : break;
7642 : }
7643 : else
7644 : {
7645 0 : break;
7646 : }
7647 : }
7648 74 : osVal.replaceAll("\\\"", '"');
7649 74 : status = nc_put_att_text(nCdfId, nVarId, osAttrName,
7650 : osVal.size(), osVal.c_str());
7651 : }
7652 : else
7653 : {
7654 84 : CPLString osVal(pszValue);
7655 126 : while (!osVal.empty())
7656 : {
7657 126 : if (osVal.back() == ';' || osVal.back() == ' ')
7658 : {
7659 84 : osVal.resize(osVal.size() - 1);
7660 : }
7661 : else
7662 : {
7663 42 : break;
7664 : }
7665 : }
7666 42 : int nc_datatype = -1;
7667 42 : if (!osVal.empty() && osVal.back() == 'b')
7668 : {
7669 3 : nc_datatype = NC_BYTE;
7670 3 : osVal.resize(osVal.size() - 1);
7671 : }
7672 39 : else if (!osVal.empty() && osVal.back() == 's')
7673 : {
7674 3 : nc_datatype = NC_SHORT;
7675 3 : osVal.resize(osVal.size() - 1);
7676 : }
7677 42 : if (CPLGetValueType(osVal) == CPL_VALUE_INTEGER)
7678 : {
7679 7 : if (nc_datatype < 0)
7680 4 : nc_datatype = NC_INT;
7681 : }
7682 35 : else if (CPLGetValueType(osVal) == CPL_VALUE_REAL)
7683 : {
7684 32 : nc_datatype = NC_DOUBLE;
7685 : }
7686 : else
7687 : {
7688 3 : nc_datatype = -1;
7689 : }
7690 :
7691 : // For _FillValue, check that the attribute type matches
7692 : // the variable type. Leaks memory with NC4 otherwise
7693 42 : if (osAttrName == "_FillValue")
7694 : {
7695 6 : if (nVarId < 0 ||
7696 3 : nc_datatype != oMapVarIdToType[nVarId])
7697 : {
7698 0 : nc_datatype = -1;
7699 : }
7700 : }
7701 :
7702 42 : if (nc_datatype == NC_BYTE)
7703 : {
7704 : signed char chVal =
7705 3 : static_cast<signed char>(atoi(osVal));
7706 3 : status = nc_put_att_schar(nCdfId, nVarId, osAttrName,
7707 : NC_BYTE, 1, &chVal);
7708 : }
7709 39 : else if (nc_datatype == NC_SHORT)
7710 : {
7711 0 : short nVal = static_cast<short>(atoi(osVal));
7712 0 : status = nc_put_att_short(nCdfId, nVarId, osAttrName,
7713 : NC_SHORT, 1, &nVal);
7714 : }
7715 39 : else if (nc_datatype == NC_INT)
7716 : {
7717 4 : int nVal = static_cast<int>(atoi(osVal));
7718 4 : status = nc_put_att_int(nCdfId, nVarId, osAttrName,
7719 : NC_INT, 1, &nVal);
7720 : }
7721 35 : else if (nc_datatype == NC_DOUBLE)
7722 : {
7723 32 : double dfVal = CPLAtof(osVal);
7724 32 : status = nc_put_att_double(nCdfId, nVarId, osAttrName,
7725 : NC_DOUBLE, 1, &dfVal);
7726 : }
7727 : }
7728 116 : if (status != NC_NOERR)
7729 : {
7730 3 : CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
7731 : osVarName.c_str(), osAttrName.c_str(),
7732 : nc_strerror(status));
7733 : }
7734 : else
7735 : {
7736 113 : oSetAttrDefined.insert(osVarName + ":" + osAttrName);
7737 : #ifdef DEBUG_VERBOSE
7738 : CPLDebug("netCDF", "nc_put_att_(%s:%s) (%s) succeeded",
7739 : osVarName.c_str(), osAttrName.c_str(), pszLine);
7740 : #endif
7741 : }
7742 : }
7743 : }
7744 42 : else if (nActiveSection == SECTION_DATA)
7745 : {
7746 55 : while (*pszLine == ' ' || *pszLine == '\t')
7747 17 : pszLine++;
7748 38 : const char *pszEqual = strchr(pszLine, '=');
7749 38 : if (pszEqual)
7750 : {
7751 17 : CPLString osVarName(pszLine, pszEqual - pszLine);
7752 17 : osVarName.Trim();
7753 17 : if (oMapVarToId.find(osVarName) == oMapVarToId.end())
7754 0 : continue;
7755 17 : const int nVarId = oMapVarToId[osVarName];
7756 17 : CPLString osAccVal(pszEqual + 1);
7757 17 : osAccVal.Trim();
7758 153 : while (osAccVal.empty() || osAccVal.back() != ';')
7759 : {
7760 136 : pszLine = CPLReadLineL(fpSrc);
7761 136 : if (pszLine == nullptr)
7762 0 : break;
7763 272 : CPLString osVal(pszLine);
7764 136 : osVal.Trim();
7765 136 : osAccVal += osVal;
7766 : }
7767 17 : if (pszLine == nullptr)
7768 0 : break;
7769 17 : osAccVal.resize(osAccVal.size() - 1);
7770 :
7771 : const std::vector<int> aoDimIds =
7772 34 : oMapVarIdToVectorOfDimId[nVarId];
7773 17 : size_t nSize = 1;
7774 34 : std::vector<size_t> aoStart, aoEdge;
7775 17 : aoStart.resize(aoDimIds.size());
7776 17 : aoEdge.resize(aoDimIds.size());
7777 35 : for (size_t i = 0; i < aoDimIds.size(); ++i)
7778 : {
7779 18 : const size_t nDimSize = oMapDimIdToDimLen[aoDimIds[i]];
7780 36 : if (nDimSize != 0 &&
7781 18 : nSize > std::numeric_limits<size_t>::max() / nDimSize)
7782 : {
7783 0 : nSize = 0;
7784 : }
7785 : else
7786 : {
7787 18 : nSize *= nDimSize;
7788 : }
7789 18 : aoStart[i] = 0;
7790 18 : aoEdge[i] = nDimSize;
7791 : }
7792 :
7793 17 : status = NC_EBADTYPE;
7794 17 : if (nSize == 0)
7795 : {
7796 : // Might happen with a unlimited dimension
7797 : }
7798 17 : else if (oMapVarIdToType[nVarId] == NC_DOUBLE)
7799 : {
7800 8 : if (!aoStart.empty())
7801 : {
7802 : char **papszTokens =
7803 8 : CSLTokenizeString2(osAccVal, " ,;", 0);
7804 8 : size_t nTokens = CSLCount(papszTokens);
7805 8 : if (nTokens >= nSize)
7806 : {
7807 : double *padfVals = static_cast<double *>(
7808 8 : VSI_CALLOC_VERBOSE(nSize, sizeof(double)));
7809 8 : if (padfVals)
7810 : {
7811 132 : for (size_t i = 0; i < nSize; i++)
7812 : {
7813 124 : padfVals[i] = CPLAtof(papszTokens[i]);
7814 : }
7815 8 : status = nc_put_vara_double(
7816 8 : nCdfId, nVarId, &aoStart[0], &aoEdge[0],
7817 : padfVals);
7818 8 : VSIFree(padfVals);
7819 : }
7820 : }
7821 8 : CSLDestroy(papszTokens);
7822 : }
7823 : }
7824 9 : else if (oMapVarIdToType[nVarId] == NC_BYTE)
7825 : {
7826 3 : if (!aoStart.empty())
7827 : {
7828 : char **papszTokens =
7829 3 : CSLTokenizeString2(osAccVal, " ,;", 0);
7830 3 : size_t nTokens = CSLCount(papszTokens);
7831 3 : if (nTokens >= nSize)
7832 : {
7833 : signed char *panVals = static_cast<signed char *>(
7834 3 : VSI_CALLOC_VERBOSE(nSize, sizeof(signed char)));
7835 3 : if (panVals)
7836 : {
7837 1203 : for (size_t i = 0; i < nSize; i++)
7838 : {
7839 1200 : panVals[i] = static_cast<signed char>(
7840 1200 : atoi(papszTokens[i]));
7841 : }
7842 3 : status = nc_put_vara_schar(nCdfId, nVarId,
7843 3 : &aoStart[0],
7844 3 : &aoEdge[0], panVals);
7845 3 : VSIFree(panVals);
7846 : }
7847 : }
7848 3 : CSLDestroy(papszTokens);
7849 : }
7850 : }
7851 6 : else if (oMapVarIdToType[nVarId] == NC_CHAR)
7852 : {
7853 6 : if (aoStart.size() == 2)
7854 : {
7855 4 : std::vector<CPLString> aoStrings;
7856 2 : bool bInString = false;
7857 4 : CPLString osCurString;
7858 935 : for (size_t i = 0; i < osAccVal.size();)
7859 : {
7860 933 : if (!bInString)
7861 : {
7862 8 : if (osAccVal[i] == '"')
7863 : {
7864 4 : bInString = true;
7865 4 : osCurString.clear();
7866 : }
7867 8 : i++;
7868 : }
7869 926 : else if (osAccVal[i] == '\\' &&
7870 926 : i + 1 < osAccVal.size() &&
7871 1 : osAccVal[i + 1] == '"')
7872 : {
7873 1 : osCurString += '"';
7874 1 : i += 2;
7875 : }
7876 924 : else if (osAccVal[i] == '"')
7877 : {
7878 4 : aoStrings.push_back(osCurString);
7879 4 : osCurString.clear();
7880 4 : bInString = false;
7881 4 : i++;
7882 : }
7883 : else
7884 : {
7885 920 : osCurString += osAccVal[i];
7886 920 : i++;
7887 : }
7888 : }
7889 2 : const size_t nRecords = oMapDimIdToDimLen[aoDimIds[0]];
7890 2 : const size_t nWidth = oMapDimIdToDimLen[aoDimIds[1]];
7891 2 : size_t nIters = aoStrings.size();
7892 2 : if (nIters > nRecords)
7893 0 : nIters = nRecords;
7894 6 : for (size_t i = 0; i < nIters; i++)
7895 : {
7896 : size_t anIndex[2];
7897 4 : anIndex[0] = i;
7898 4 : anIndex[1] = 0;
7899 : size_t anCount[2];
7900 4 : anCount[0] = 1;
7901 4 : anCount[1] = aoStrings[i].size();
7902 4 : if (anCount[1] > nWidth)
7903 0 : anCount[1] = nWidth;
7904 : status =
7905 4 : nc_put_vara_text(nCdfId, nVarId, anIndex,
7906 4 : anCount, aoStrings[i].c_str());
7907 4 : if (status != NC_NOERR)
7908 0 : break;
7909 : }
7910 : }
7911 : }
7912 17 : if (status != NC_NOERR)
7913 : {
7914 4 : CPLDebug("netCDF", "nc_put_var_(%s) failed: %s",
7915 : osVarName.c_str(), nc_strerror(status));
7916 : }
7917 : }
7918 : }
7919 : }
7920 :
7921 4 : GDAL_nc_close(nCdfId);
7922 4 : return true;
7923 : }
7924 :
7925 : #endif // ENABLE_NCDUMP
7926 :
7927 : /************************************************************************/
7928 : /* Open() */
7929 : /************************************************************************/
7930 :
7931 654 : GDALDataset *netCDFDataset::Open(GDALOpenInfo *poOpenInfo)
7932 :
7933 : {
7934 : #ifdef NCDF_DEBUG
7935 : CPLDebug("GDAL_netCDF", "\n=====\nOpen(), filename=[%s]",
7936 : poOpenInfo->pszFilename);
7937 : #endif
7938 :
7939 : // Does this appear to be a netcdf file?
7940 654 : NetCDFFormatEnum eTmpFormat = NCDF_FORMAT_NONE;
7941 654 : if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
7942 : {
7943 597 : eTmpFormat = netCDFIdentifyFormat(poOpenInfo, /* bCheckExt = */ true);
7944 : #ifdef NCDF_DEBUG
7945 : CPLDebug("GDAL_netCDF", "identified format %d", eTmpFormat);
7946 : #endif
7947 : // Note: not calling Identify() directly, because we want the file type.
7948 : // Only support NCDF_FORMAT* formats.
7949 597 : if (!(NCDF_FORMAT_NC == eTmpFormat || NCDF_FORMAT_NC2 == eTmpFormat ||
7950 : NCDF_FORMAT_NC4 == eTmpFormat || NCDF_FORMAT_NC4C == eTmpFormat))
7951 2 : return nullptr;
7952 : }
7953 : else
7954 : {
7955 : #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
7956 : // We don't necessarily want to catch bugs in libnetcdf ...
7957 : if (CPLGetConfigOption("DISABLE_OPEN_REAL_NETCDF_FILES", nullptr))
7958 : {
7959 : return nullptr;
7960 : }
7961 : #endif
7962 : }
7963 :
7964 652 : if (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER)
7965 : {
7966 169 : return OpenMultiDim(poOpenInfo);
7967 : }
7968 :
7969 966 : CPLMutexHolderD(&hNCMutex);
7970 :
7971 483 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
7972 : // GDALDataset own mutex.
7973 483 : netCDFDataset *poDS = new netCDFDataset();
7974 483 : poDS->papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
7975 483 : CPLAcquireMutex(hNCMutex, 1000.0);
7976 :
7977 483 : poDS->SetDescription(poOpenInfo->pszFilename);
7978 :
7979 : // Check if filename start with NETCDF: tag.
7980 483 : bool bTreatAsSubdataset = false;
7981 966 : CPLString osSubdatasetName;
7982 :
7983 : #ifdef ENABLE_NCDUMP
7984 483 : const char *pszHeader =
7985 : reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
7986 483 : if (poOpenInfo->fpL != nullptr && STARTS_WITH(pszHeader, "netcdf ") &&
7987 3 : strstr(pszHeader, "dimensions:") && strstr(pszHeader, "variables:"))
7988 : {
7989 : // By default create a temporary file that will be destroyed,
7990 : // unless NETCDF_TMP_FILE is defined. Can be useful to see which
7991 : // netCDF file has been generated from a potential fuzzed input.
7992 3 : poDS->osFilename = CPLGetConfigOption("NETCDF_TMP_FILE", "");
7993 3 : if (poDS->osFilename.empty())
7994 : {
7995 3 : poDS->bFileToDestroyAtClosing = true;
7996 3 : poDS->osFilename = CPLGenerateTempFilename("netcdf_tmp");
7997 : }
7998 3 : if (!netCDFDatasetCreateTempFile(eTmpFormat, poDS->osFilename,
7999 : poOpenInfo->fpL))
8000 : {
8001 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8002 : // deadlock with GDALDataset own mutex.
8003 0 : delete poDS;
8004 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8005 0 : return nullptr;
8006 : }
8007 3 : bTreatAsSubdataset = false;
8008 3 : poDS->eFormat = eTmpFormat;
8009 : }
8010 : else
8011 : #endif
8012 :
8013 480 : if (STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
8014 : {
8015 : char **papszName =
8016 57 : CSLTokenizeString2(poOpenInfo->pszFilename, ":",
8017 : CSLT_HONOURSTRINGS | CSLT_PRESERVEESCAPES);
8018 :
8019 114 : if (CSLCount(papszName) >= 3 &&
8020 57 : ((strlen(papszName[1]) == 1 && /* D:\\bla */
8021 0 : (papszName[2][0] == '/' || papszName[2][0] == '\\')) ||
8022 57 : EQUAL(papszName[1], "http") || EQUAL(papszName[1], "https") ||
8023 57 : EQUAL(papszName[1], "/vsicurl/http") ||
8024 57 : EQUAL(papszName[1], "/vsicurl/https") ||
8025 57 : EQUAL(papszName[1], "/vsicurl_streaming/http") ||
8026 57 : EQUAL(papszName[1], "/vsicurl_streaming/https")))
8027 : {
8028 0 : const int nCountBefore = CSLCount(papszName);
8029 0 : CPLString osTmp = papszName[1];
8030 0 : osTmp += ':';
8031 0 : osTmp += papszName[2];
8032 0 : CPLFree(papszName[1]);
8033 0 : CPLFree(papszName[2]);
8034 0 : papszName[1] = CPLStrdup(osTmp);
8035 0 : memmove(papszName + 2, papszName + 3,
8036 0 : (nCountBefore - 2) * sizeof(char *));
8037 : }
8038 :
8039 57 : if (CSLCount(papszName) == 3)
8040 : {
8041 57 : poDS->osFilename = papszName[1];
8042 57 : osSubdatasetName = papszName[2];
8043 57 : bTreatAsSubdataset = true;
8044 57 : CSLDestroy(papszName);
8045 : }
8046 0 : else if (CSLCount(papszName) == 2)
8047 : {
8048 0 : poDS->osFilename = papszName[1];
8049 0 : osSubdatasetName = "";
8050 0 : bTreatAsSubdataset = false;
8051 0 : CSLDestroy(papszName);
8052 : }
8053 : else
8054 : {
8055 0 : CSLDestroy(papszName);
8056 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8057 : // deadlock with GDALDataset own mutex.
8058 0 : delete poDS;
8059 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8060 0 : CPLError(CE_Failure, CPLE_AppDefined,
8061 : "Failed to parse NETCDF: prefix string into expected 2, 3 "
8062 : "or 4 fields.");
8063 0 : return nullptr;
8064 : }
8065 :
8066 114 : if (!STARTS_WITH(poDS->osFilename, "http://") &&
8067 57 : !STARTS_WITH(poDS->osFilename, "https://"))
8068 : {
8069 : // Identify Format from real file, with bCheckExt=FALSE.
8070 : GDALOpenInfo *poOpenInfo2 =
8071 57 : new GDALOpenInfo(poDS->osFilename.c_str(), GA_ReadOnly);
8072 57 : poDS->eFormat =
8073 57 : netCDFIdentifyFormat(poOpenInfo2, /* bCheckExt = */ false);
8074 57 : delete poOpenInfo2;
8075 57 : if (NCDF_FORMAT_NONE == poDS->eFormat ||
8076 57 : NCDF_FORMAT_UNKNOWN == poDS->eFormat)
8077 : {
8078 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8079 : // deadlock with GDALDataset own mutex.
8080 0 : delete poDS;
8081 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8082 0 : return nullptr;
8083 : }
8084 : }
8085 : }
8086 : else
8087 : {
8088 423 : poDS->osFilename = poOpenInfo->pszFilename;
8089 423 : bTreatAsSubdataset = false;
8090 423 : poDS->eFormat = eTmpFormat;
8091 : }
8092 :
8093 : // Try opening the dataset.
8094 : #if defined(NCDF_DEBUG) && defined(ENABLE_UFFD)
8095 : CPLDebug("GDAL_netCDF", "calling nc_open_mem(%s)",
8096 : poDS->osFilename.c_str());
8097 : #elif defined(NCDF_DEBUG) && !defined(ENABLE_UFFD)
8098 : CPLDebug("GDAL_netCDF", "calling nc_open(%s)", poDS->osFilename.c_str());
8099 : #endif
8100 483 : int cdfid = -1;
8101 483 : const int nMode = ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE) != 0)
8102 : ? NC_WRITE
8103 : : NC_NOWRITE;
8104 966 : CPLString osFilenameForNCOpen(poDS->osFilename);
8105 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
8106 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
8107 : {
8108 : char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
8109 : osFilenameForNCOpen = pszTemp;
8110 : CPLFree(pszTemp);
8111 : }
8112 : #endif
8113 483 : int status2 = -1;
8114 :
8115 : #ifdef ENABLE_UFFD
8116 483 : cpl_uffd_context *pCtx = nullptr;
8117 : #endif
8118 :
8119 498 : if (STARTS_WITH(osFilenameForNCOpen, "/vsimem/") &&
8120 15 : poOpenInfo->eAccess == GA_ReadOnly)
8121 : {
8122 15 : vsi_l_offset nLength = 0;
8123 15 : poDS->fpVSIMEM = VSIFOpenL(osFilenameForNCOpen, "rb");
8124 15 : if (poDS->fpVSIMEM)
8125 : {
8126 : // We assume that the file will not be modified. If it is, then
8127 : // pabyBuffer might become invalid.
8128 : GByte *pabyBuffer =
8129 15 : VSIGetMemFileBuffer(osFilenameForNCOpen, &nLength, false);
8130 15 : if (pabyBuffer)
8131 : {
8132 15 : status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen),
8133 : nMode, static_cast<size_t>(nLength),
8134 : pabyBuffer, &cdfid);
8135 : }
8136 : }
8137 : }
8138 : else
8139 : {
8140 : const bool bVsiFile =
8141 468 : !strncmp(osFilenameForNCOpen, "/vsi", strlen("/vsi"));
8142 : #ifdef ENABLE_UFFD
8143 468 : bool bReadOnly = (poOpenInfo->eAccess == GA_ReadOnly);
8144 468 : void *pVma = nullptr;
8145 468 : uint64_t nVmaSize = 0;
8146 :
8147 468 : if (bVsiFile)
8148 : {
8149 1 : if (bReadOnly)
8150 : {
8151 1 : if (CPLIsUserFaultMappingSupported())
8152 : {
8153 1 : pCtx = CPLCreateUserFaultMapping(osFilenameForNCOpen, &pVma,
8154 : &nVmaSize);
8155 : }
8156 : else
8157 : {
8158 0 : CPLError(CE_Failure, CPLE_AppDefined,
8159 : "Opening a /vsi file with the netCDF driver "
8160 : "requires Linux userfaultfd to be available. "
8161 : "If running from Docker, "
8162 : "--security-opt seccomp=unconfined might be "
8163 : "needed.%s",
8164 0 : ((poDS->eFormat == NCDF_FORMAT_NC4 ||
8165 0 : poDS->eFormat == NCDF_FORMAT_HDF5) &&
8166 0 : GDALGetDriverByName("HDF5"))
8167 : ? " Or you may set the GDAL_SKIP=netCDF "
8168 : "configuration option to force the use of "
8169 : "the HDF5 driver."
8170 : : "");
8171 : }
8172 : }
8173 : else
8174 : {
8175 0 : CPLError(CE_Failure, CPLE_AppDefined,
8176 : "Opening a /vsi file with the netCDF driver is only "
8177 : "supported in read-only mode");
8178 : }
8179 : }
8180 468 : if (pCtx != nullptr && pVma != nullptr && nVmaSize > 0)
8181 : {
8182 : // netCDF code, at least for netCDF 4.7.0, is confused by filenames
8183 : // like /vsicurl/http[s]://example.com/foo.nc, so just pass the
8184 : // final part
8185 1 : status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen), nMode,
8186 : static_cast<size_t>(nVmaSize), pVma, &cdfid);
8187 : }
8188 : else
8189 467 : status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
8190 : #else
8191 : if (bVsiFile)
8192 : {
8193 : CPLError(
8194 : CE_Failure, CPLE_AppDefined,
8195 : "Opening a /vsi file with the netCDF driver requires Linux "
8196 : "userfaultfd to be available.%s",
8197 : ((poDS->eFormat == NCDF_FORMAT_NC4 ||
8198 : poDS->eFormat == NCDF_FORMAT_HDF5) &&
8199 : GDALGetDriverByName("HDF5"))
8200 : ? " Or you may set the GDAL_SKIP=netCDF "
8201 : "configuration option to force the use of the HDF5 "
8202 : "driver."
8203 : : "");
8204 : status2 = NC_EIO;
8205 : }
8206 : else
8207 : {
8208 : status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
8209 : }
8210 : #endif
8211 : }
8212 483 : if (status2 != NC_NOERR)
8213 : {
8214 : #ifdef NCDF_DEBUG
8215 : CPLDebug("GDAL_netCDF", "error opening");
8216 : #endif
8217 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8218 : // with GDALDataset own mutex.
8219 0 : delete poDS;
8220 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8221 0 : return nullptr;
8222 : }
8223 : #ifdef NCDF_DEBUG
8224 : CPLDebug("GDAL_netCDF", "got cdfid=%d", cdfid);
8225 : #endif
8226 :
8227 : #if defined(ENABLE_NCDUMP) && !defined(_WIN32)
8228 : // Try to destroy the temporary file right now on Unix
8229 483 : if (poDS->bFileToDestroyAtClosing)
8230 : {
8231 3 : if (VSIUnlink(poDS->osFilename) == 0)
8232 : {
8233 3 : poDS->bFileToDestroyAtClosing = false;
8234 : }
8235 : }
8236 : #endif
8237 :
8238 : // Is this a real netCDF file?
8239 : int ndims;
8240 : int ngatts;
8241 : int nvars;
8242 : int unlimdimid;
8243 483 : int status = nc_inq(cdfid, &ndims, &nvars, &ngatts, &unlimdimid);
8244 483 : if (status != NC_NOERR)
8245 : {
8246 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8247 : // with GDALDataset own mutex.
8248 0 : delete poDS;
8249 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8250 0 : return nullptr;
8251 : }
8252 :
8253 : // Get file type from netcdf.
8254 483 : int nTmpFormat = NCDF_FORMAT_NONE;
8255 483 : status = nc_inq_format(cdfid, &nTmpFormat);
8256 483 : if (status != NC_NOERR)
8257 : {
8258 0 : NCDF_ERR(status);
8259 : }
8260 : else
8261 : {
8262 483 : CPLDebug("GDAL_netCDF",
8263 : "driver detected file type=%d, libnetcdf detected type=%d",
8264 483 : poDS->eFormat, nTmpFormat);
8265 483 : if (static_cast<NetCDFFormatEnum>(nTmpFormat) != poDS->eFormat)
8266 : {
8267 : // Warn if file detection conflicts with that from libnetcdf
8268 : // except for NC4C, which we have no way of detecting initially.
8269 24 : if (nTmpFormat != NCDF_FORMAT_NC4C &&
8270 12 : !STARTS_WITH(poDS->osFilename, "http://") &&
8271 0 : !STARTS_WITH(poDS->osFilename, "https://"))
8272 : {
8273 0 : CPLError(CE_Warning, CPLE_AppDefined,
8274 : "NetCDF driver detected file type=%d, but libnetcdf "
8275 : "detected type=%d",
8276 0 : poDS->eFormat, nTmpFormat);
8277 : }
8278 12 : CPLDebug("GDAL_netCDF", "setting file type to %d, was %d",
8279 12 : nTmpFormat, poDS->eFormat);
8280 12 : poDS->eFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
8281 : }
8282 : }
8283 :
8284 : // Does the request variable exist?
8285 483 : if (bTreatAsSubdataset)
8286 : {
8287 : int dummy;
8288 57 : if (NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &dummy,
8289 57 : &dummy) != CE_None)
8290 : {
8291 0 : CPLError(CE_Warning, CPLE_AppDefined,
8292 : "%s is a netCDF file, but %s is not a variable.",
8293 : poOpenInfo->pszFilename, osSubdatasetName.c_str());
8294 :
8295 0 : GDAL_nc_close(cdfid);
8296 : #ifdef ENABLE_UFFD
8297 0 : NETCDF_UFFD_UNMAP(pCtx);
8298 : #endif
8299 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8300 : // deadlock with GDALDataset own mutex.
8301 0 : delete poDS;
8302 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8303 0 : return nullptr;
8304 : }
8305 : }
8306 :
8307 : // Figure out whether or not the listed dataset has support for simple
8308 : // geometries (CF-1.8)
8309 483 : poDS->nCFVersion = nccfdriver::getCFVersion(cdfid);
8310 483 : bool bHasSimpleGeometries = false; // but not necessarily valid
8311 483 : if (poDS->nCFVersion >= 1.8)
8312 : {
8313 72 : poDS->bSGSupport = true;
8314 72 : bHasSimpleGeometries = poDS->DetectAndFillSGLayers(cdfid);
8315 72 : poDS->vcdf.enableFullVirtualMode();
8316 : }
8317 : else
8318 : {
8319 411 : poDS->bSGSupport = false;
8320 : }
8321 :
8322 : char szConventions[NC_MAX_NAME + 1];
8323 483 : szConventions[0] = '\0';
8324 483 : nc_type nAttype = NC_NAT;
8325 483 : size_t nAttlen = 0;
8326 483 : nc_inq_att(cdfid, NC_GLOBAL, "Conventions", &nAttype, &nAttlen);
8327 966 : if (nAttlen >= sizeof(szConventions) ||
8328 483 : nc_get_att_text(cdfid, NC_GLOBAL, "Conventions", szConventions) !=
8329 : NC_NOERR)
8330 : {
8331 55 : CPLDebug("GDAL_netCDF", "No UNIDATA NC_GLOBAL:Conventions attribute");
8332 : // Note that 'Conventions' is always capital 'C' in CF spec.
8333 : }
8334 : else
8335 : {
8336 428 : szConventions[nAttlen] = '\0';
8337 : }
8338 :
8339 : // Create band information objects.
8340 483 : CPLDebug("GDAL_netCDF", "var_count = %d", nvars);
8341 :
8342 : // Create a corresponding GDALDataset.
8343 : // Create Netcdf Subdataset if filename as NETCDF tag.
8344 483 : poDS->cdfid = cdfid;
8345 : #ifdef ENABLE_UFFD
8346 483 : poDS->pCtx = pCtx;
8347 : #endif
8348 483 : poDS->eAccess = poOpenInfo->eAccess;
8349 483 : poDS->bDefineMode = false;
8350 :
8351 483 : poDS->ReadAttributes(cdfid, NC_GLOBAL);
8352 :
8353 : // Identify coordinate and boundary variables that we should
8354 : // ignore as Raster Bands.
8355 483 : char **papszIgnoreVars = nullptr;
8356 483 : NCDFGetCoordAndBoundVarFullNames(cdfid, &papszIgnoreVars);
8357 : // Filter variables to keep only valid 2+D raster bands and vector fields.
8358 483 : int nRasterVars = 0;
8359 483 : int nIgnoredVars = 0;
8360 483 : int nGroupID = -1;
8361 483 : int nVarID = -1;
8362 :
8363 : std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>
8364 966 : oMap2DDimsToGroupAndVar;
8365 1125 : if ((poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
8366 159 : STARTS_WITH(CSLFetchNameValueDef(poDS->papszMetadata,
8367 : "NC_GLOBAL#mission_name", ""),
8368 1 : "Sentinel 3") &&
8369 1 : EQUAL(CSLFetchNameValueDef(poDS->papszMetadata,
8370 : "NC_GLOBAL#altimeter_sensor_name", ""),
8371 642 : "SRAL") &&
8372 1 : EQUAL(CSLFetchNameValueDef(poDS->papszMetadata,
8373 : "NC_GLOBAL#radiometer_sensor_name", ""),
8374 : "MWR"))
8375 : {
8376 1 : if (poDS->eAccess == GA_Update)
8377 : {
8378 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8379 : // deadlock with GDALDataset own mutex.
8380 0 : delete poDS;
8381 0 : return nullptr;
8382 : }
8383 1 : poDS->ProcessSentinel3_SRAL_MWR();
8384 : }
8385 : else
8386 : {
8387 482 : poDS->FilterVars(cdfid, (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0,
8388 640 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
8389 158 : !bHasSimpleGeometries,
8390 : papszIgnoreVars, &nRasterVars, &nGroupID, &nVarID,
8391 : &nIgnoredVars, oMap2DDimsToGroupAndVar);
8392 : }
8393 483 : CSLDestroy(papszIgnoreVars);
8394 :
8395 : // Case where there is no raster variable
8396 483 : if (nRasterVars == 0 && !bTreatAsSubdataset)
8397 : {
8398 122 : poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
8399 122 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8400 : // with GDALDataset own mutex.
8401 122 : poDS->TryLoadXML();
8402 : // If the dataset has been opened in raster mode only, exit
8403 122 : if ((poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0 &&
8404 12 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) == 0)
8405 : {
8406 4 : delete poDS;
8407 4 : poDS = nullptr;
8408 : }
8409 : // Otherwise if the dataset is opened in vector mode, that there is
8410 : // no vector layer and we are in read-only, exit too.
8411 118 : else if (poDS->GetLayerCount() == 0 &&
8412 126 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
8413 8 : poOpenInfo->eAccess == GA_ReadOnly)
8414 : {
8415 8 : delete poDS;
8416 8 : poDS = nullptr;
8417 : }
8418 122 : CPLAcquireMutex(hNCMutex, 1000.0);
8419 122 : return poDS;
8420 : }
8421 :
8422 : // We have more than one variable with 2 dimensions in the
8423 : // file, then treat this as a subdataset container dataset.
8424 361 : bool bSeveralVariablesAsBands = false;
8425 361 : if ((nRasterVars > 1) && !bTreatAsSubdataset)
8426 : {
8427 24 : if (CPLFetchBool(poOpenInfo->papszOpenOptions, "VARIABLES_AS_BANDS",
8428 29 : false) &&
8429 5 : oMap2DDimsToGroupAndVar.size() == 1)
8430 : {
8431 5 : std::tie(nGroupID, nVarID) =
8432 10 : oMap2DDimsToGroupAndVar.begin()->second.front();
8433 5 : bSeveralVariablesAsBands = true;
8434 : }
8435 : else
8436 : {
8437 19 : poDS->CreateSubDatasetList(cdfid);
8438 19 : poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
8439 19 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8440 : // deadlock with GDALDataset own mutex.
8441 19 : poDS->TryLoadXML();
8442 19 : CPLAcquireMutex(hNCMutex, 1000.0);
8443 19 : return poDS;
8444 : }
8445 : }
8446 :
8447 : // If we are not treating things as a subdataset, then capture
8448 : // the name of the single available variable as the subdataset.
8449 342 : if (!bTreatAsSubdataset)
8450 : {
8451 285 : char *pszVarName = nullptr;
8452 285 : NCDF_ERR(NCDFGetVarFullName(nGroupID, nVarID, &pszVarName));
8453 285 : osSubdatasetName = (pszVarName != nullptr ? pszVarName : "");
8454 285 : CPLFree(pszVarName);
8455 : }
8456 :
8457 : // We have ignored at least one variable, so we should report them
8458 : // as subdatasets for reference.
8459 342 : if (nIgnoredVars > 0 && !bTreatAsSubdataset)
8460 : {
8461 24 : CPLDebug("GDAL_netCDF",
8462 : "As %d variables were ignored, creating subdataset list "
8463 : "for reference. Variable #%d [%s] is the main variable",
8464 : nIgnoredVars, nVarID, osSubdatasetName.c_str());
8465 24 : poDS->CreateSubDatasetList(cdfid);
8466 : }
8467 :
8468 : // Open the NETCDF subdataset NETCDF:"filename":subdataset.
8469 342 : int var = -1;
8470 342 : NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &nGroupID, &var);
8471 : // Now we can forget the root cdfid and only use the selected group.
8472 342 : cdfid = nGroupID;
8473 342 : int nd = 0;
8474 342 : nc_inq_varndims(cdfid, var, &nd);
8475 :
8476 342 : poDS->m_anDimIds.resize(nd);
8477 :
8478 : // X, Y, Z position in array
8479 684 : std::vector<int> anBandDimPos(nd);
8480 :
8481 342 : nc_inq_vardimid(cdfid, var, poDS->m_anDimIds.data());
8482 :
8483 : // Check if somebody tried to pass a variable with less than 1D.
8484 342 : if (nd < 1)
8485 : {
8486 0 : CPLError(CE_Warning, CPLE_AppDefined,
8487 : "Variable has %d dimension(s) - not supported.", nd);
8488 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8489 : // with GDALDataset own mutex.
8490 0 : delete poDS;
8491 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8492 0 : return nullptr;
8493 : }
8494 :
8495 : // CF-1 Convention
8496 : //
8497 : // Dimensions to appear in the relative order T, then Z, then Y,
8498 : // then X to the file. All other dimensions should, whenever
8499 : // possible, be placed to the left of the spatiotemporal
8500 : // dimensions.
8501 :
8502 : // Verify that dimensions are in the {T,Z,Y,X} or {T,Z,Y,X} order
8503 : // Ideally we should detect for other ordering and act accordingly
8504 : // Only done if file has Conventions=CF-* and only prints warning
8505 : // To disable set GDAL_NETCDF_VERIFY_DIMS=NO and to use only
8506 : // attributes (not varnames) set GDAL_NETCDF_VERIFY_DIMS=STRICT
8507 : const bool bCheckDims =
8508 684 : CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES")) &&
8509 342 : STARTS_WITH_CI(szConventions, "CF");
8510 :
8511 342 : if (nd >= 2 && bCheckDims)
8512 : {
8513 258 : char szDimName1[NC_MAX_NAME + 1] = {};
8514 258 : char szDimName2[NC_MAX_NAME + 1] = {};
8515 258 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 1], szDimName1);
8516 258 : NCDF_ERR(status);
8517 258 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 2], szDimName2);
8518 258 : NCDF_ERR(status);
8519 415 : if (NCDFIsVarLongitude(cdfid, -1, szDimName1) == false &&
8520 157 : NCDFIsVarProjectionX(cdfid, -1, szDimName1) == false)
8521 : {
8522 4 : CPLError(CE_Warning, CPLE_AppDefined,
8523 : "dimension #%d (%s) is not a Longitude/X dimension.",
8524 : nd - 1, szDimName1);
8525 : }
8526 415 : if (NCDFIsVarLatitude(cdfid, -1, szDimName2) == false &&
8527 157 : NCDFIsVarProjectionY(cdfid, -1, szDimName2) == false)
8528 : {
8529 4 : CPLError(CE_Warning, CPLE_AppDefined,
8530 : "dimension #%d (%s) is not a Latitude/Y dimension.",
8531 : nd - 2, szDimName2);
8532 : }
8533 258 : if ((NCDFIsVarLongitude(cdfid, -1, szDimName2) ||
8534 260 : NCDFIsVarProjectionX(cdfid, -1, szDimName2)) &&
8535 2 : (NCDFIsVarLatitude(cdfid, -1, szDimName1) ||
8536 0 : NCDFIsVarProjectionY(cdfid, -1, szDimName1)))
8537 : {
8538 2 : poDS->bSwitchedXY = true;
8539 : }
8540 258 : if (nd >= 3)
8541 : {
8542 45 : char szDimName3[NC_MAX_NAME + 1] = {};
8543 : status =
8544 45 : nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 3], szDimName3);
8545 45 : NCDF_ERR(status);
8546 45 : if (nd >= 4)
8547 : {
8548 13 : char szDimName4[NC_MAX_NAME + 1] = {};
8549 : status =
8550 13 : nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 4], szDimName4);
8551 13 : NCDF_ERR(status);
8552 13 : if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false)
8553 : {
8554 0 : CPLError(CE_Warning, CPLE_AppDefined,
8555 : "dimension #%d (%s) is not a Time dimension.",
8556 : nd - 3, szDimName3);
8557 : }
8558 13 : if (NCDFIsVarTimeCoord(cdfid, -1, szDimName4) == false)
8559 : {
8560 0 : CPLError(CE_Warning, CPLE_AppDefined,
8561 : "dimension #%d (%s) is not a Time dimension.",
8562 : nd - 4, szDimName4);
8563 : }
8564 : }
8565 : else
8566 : {
8567 63 : if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false &&
8568 31 : NCDFIsVarTimeCoord(cdfid, -1, szDimName3) == false)
8569 : {
8570 0 : CPLError(CE_Warning, CPLE_AppDefined,
8571 : "dimension #%d (%s) is not a "
8572 : "Time or Vertical dimension.",
8573 : nd - 3, szDimName3);
8574 : }
8575 : }
8576 : }
8577 : }
8578 :
8579 : // For example for EMIT data (https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/),
8580 : // dimension order is downtrack, crosstrack, bands
8581 342 : bool bYXBandOrder = false;
8582 342 : if (nd == 3)
8583 : {
8584 37 : char szDimName[NC_MAX_NAME + 1] = {};
8585 37 : status = nc_inq_dimname(cdfid, poDS->m_anDimIds[2], szDimName);
8586 37 : NCDF_ERR(status);
8587 37 : bYXBandOrder =
8588 37 : strcmp(szDimName, "bands") == 0 || strcmp(szDimName, "band") == 0;
8589 : }
8590 :
8591 : // Get X dimensions information.
8592 : size_t xdim;
8593 342 : poDS->nXDimID = poDS->m_anDimIds[bYXBandOrder ? 1 : nd - 1];
8594 342 : nc_inq_dimlen(cdfid, poDS->nXDimID, &xdim);
8595 :
8596 : // Get Y dimension information.
8597 : size_t ydim;
8598 342 : if (nd >= 2)
8599 : {
8600 338 : poDS->nYDimID = poDS->m_anDimIds[bYXBandOrder ? 0 : nd - 2];
8601 338 : nc_inq_dimlen(cdfid, poDS->nYDimID, &ydim);
8602 : }
8603 : else
8604 : {
8605 4 : poDS->nYDimID = -1;
8606 4 : ydim = 1;
8607 : }
8608 :
8609 342 : if (xdim > INT_MAX || ydim > INT_MAX)
8610 : {
8611 0 : CPLError(CE_Failure, CPLE_AppDefined,
8612 : "Invalid raster dimensions: " CPL_FRMT_GUIB "x" CPL_FRMT_GUIB,
8613 : static_cast<GUIntBig>(xdim), static_cast<GUIntBig>(ydim));
8614 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8615 : // with GDALDataset own mutex.
8616 0 : delete poDS;
8617 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8618 0 : return nullptr;
8619 : }
8620 :
8621 342 : poDS->nRasterXSize = static_cast<int>(xdim);
8622 342 : poDS->nRasterYSize = static_cast<int>(ydim);
8623 :
8624 342 : unsigned int k = 0;
8625 1092 : for (int j = 0; j < nd; j++)
8626 : {
8627 750 : if (poDS->m_anDimIds[j] == poDS->nXDimID)
8628 : {
8629 342 : anBandDimPos[0] = j; // Save Position of XDim
8630 342 : k++;
8631 : }
8632 750 : if (poDS->m_anDimIds[j] == poDS->nYDimID)
8633 : {
8634 338 : anBandDimPos[1] = j; // Save Position of YDim
8635 338 : k++;
8636 : }
8637 : }
8638 : // X and Y Dimension Ids were not found!
8639 342 : if ((nd >= 2 && k != 2) || (nd == 1 && k != 1))
8640 : {
8641 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
8642 : // with GDALDataset own mutex.
8643 0 : delete poDS;
8644 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8645 0 : return nullptr;
8646 : }
8647 :
8648 : // Read Metadata for this variable.
8649 :
8650 : // Should disable as is also done at band level, except driver needs the
8651 : // variables as metadata (e.g. projection).
8652 342 : poDS->ReadAttributes(cdfid, var);
8653 :
8654 : // Read Metadata for each dimension.
8655 342 : int *panDimIds = nullptr;
8656 342 : NCDFGetVisibleDims(cdfid, &ndims, &panDimIds);
8657 : // With NetCDF-4 groups panDimIds is not always [0..dim_count-1] like
8658 : // in NetCDF-3 because we see only the dimensions of the selected group
8659 : // and its parents.
8660 : // poDS->papszDimName is indexed by dim IDs, so it must contains all IDs
8661 : // [0..max(panDimIds)], but they are not all useful so we fill names
8662 : // of useless dims with empty string.
8663 342 : if (panDimIds)
8664 : {
8665 342 : const int nMaxDimId = *std::max_element(panDimIds, panDimIds + ndims);
8666 342 : std::set<int> oSetExistingDimIds;
8667 1128 : for (int i = 0; i < ndims; i++)
8668 : {
8669 786 : oSetExistingDimIds.insert(panDimIds[i]);
8670 : }
8671 342 : std::set<int> oSetDimIdsUsedByVar;
8672 1092 : for (int i = 0; i < nd; i++)
8673 : {
8674 750 : oSetDimIdsUsedByVar.insert(poDS->m_anDimIds[i]);
8675 : }
8676 1128 : for (int j = 0; j <= nMaxDimId; j++)
8677 : {
8678 : // Is j dim used?
8679 786 : if (oSetExistingDimIds.find(j) != oSetExistingDimIds.end())
8680 : {
8681 : // Useful dim.
8682 786 : char szTemp[NC_MAX_NAME + 1] = {};
8683 786 : status = nc_inq_dimname(cdfid, j, szTemp);
8684 786 : if (status != NC_NOERR)
8685 : {
8686 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8687 : // deadlock with GDALDataset own
8688 : // mutex.
8689 0 : delete poDS;
8690 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8691 0 : return nullptr;
8692 : }
8693 786 : poDS->papszDimName.AddString(szTemp);
8694 :
8695 786 : if (oSetDimIdsUsedByVar.find(j) != oSetDimIdsUsedByVar.end())
8696 : {
8697 750 : int nDimGroupId = -1;
8698 750 : int nDimVarId = -1;
8699 750 : if (NCDFResolveVar(cdfid, poDS->papszDimName[j],
8700 750 : &nDimGroupId, &nDimVarId) == CE_None)
8701 : {
8702 550 : poDS->ReadAttributes(nDimGroupId, nDimVarId);
8703 : }
8704 : }
8705 : }
8706 : else
8707 : {
8708 : // Useless dim.
8709 0 : poDS->papszDimName.AddString("");
8710 : }
8711 : }
8712 342 : CPLFree(panDimIds);
8713 : }
8714 :
8715 : // Set projection info.
8716 684 : std::vector<std::string> aosRemovedMDItems;
8717 342 : if (nd > 1)
8718 : {
8719 338 : poDS->SetProjectionFromVar(cdfid, var,
8720 : /*bReadSRSOnly=*/false,
8721 : /* pszGivenGM = */ nullptr,
8722 : /* returnProjStr = */ nullptr,
8723 : /* sg = */ nullptr, &aosRemovedMDItems);
8724 : }
8725 :
8726 : // Override bottom-up with GDAL_NETCDF_BOTTOMUP config option.
8727 342 : const char *pszValue = CPLGetConfigOption("GDAL_NETCDF_BOTTOMUP", nullptr);
8728 342 : if (pszValue)
8729 : {
8730 24 : poDS->bBottomUp = CPLTestBool(pszValue);
8731 24 : CPLDebug("GDAL_netCDF",
8732 : "set bBottomUp=%d because GDAL_NETCDF_BOTTOMUP=%s",
8733 24 : static_cast<int>(poDS->bBottomUp), pszValue);
8734 : }
8735 :
8736 : // Save non-spatial dimension info.
8737 :
8738 342 : int *panBandZLev = nullptr;
8739 342 : int nDim = (nd >= 2) ? 2 : 1;
8740 : size_t lev_count;
8741 342 : size_t nTotLevCount = 1;
8742 342 : nc_type nType = NC_NAT;
8743 :
8744 684 : CPLString osExtraDimNames;
8745 :
8746 342 : if (nd > 2)
8747 : {
8748 52 : nDim = 2;
8749 52 : panBandZLev = static_cast<int *>(CPLCalloc(nd - 2, sizeof(int)));
8750 :
8751 52 : osExtraDimNames = "{";
8752 :
8753 52 : char szDimName[NC_MAX_NAME + 1] = {};
8754 :
8755 226 : for (int j = 0; j < nd; j++)
8756 : {
8757 296 : if ((poDS->m_anDimIds[j] != poDS->nXDimID) &&
8758 122 : (poDS->m_anDimIds[j] != poDS->nYDimID))
8759 : {
8760 70 : nc_inq_dimlen(cdfid, poDS->m_anDimIds[j], &lev_count);
8761 70 : nTotLevCount *= lev_count;
8762 70 : panBandZLev[nDim - 2] = static_cast<int>(lev_count);
8763 70 : anBandDimPos[nDim] = j; // Save Position of ZDim
8764 : // Save non-spatial dimension names.
8765 70 : if (nc_inq_dimname(cdfid, poDS->m_anDimIds[j], szDimName) ==
8766 : NC_NOERR)
8767 : {
8768 70 : osExtraDimNames += szDimName;
8769 70 : if (j < nd - 3)
8770 : {
8771 18 : osExtraDimNames += ",";
8772 : }
8773 :
8774 70 : int nIdxGroupID = -1;
8775 70 : int nIdxVarID = Get1DVariableIndexedByDimension(
8776 70 : cdfid, poDS->m_anDimIds[j], szDimName, true,
8777 70 : &nIdxGroupID);
8778 70 : poDS->m_anExtraDimGroupIds.push_back(nIdxGroupID);
8779 70 : poDS->m_anExtraDimVarIds.push_back(nIdxVarID);
8780 :
8781 70 : if (nIdxVarID >= 0)
8782 : {
8783 61 : nc_inq_vartype(nIdxGroupID, nIdxVarID, &nType);
8784 : char szExtraDimDef[NC_MAX_NAME + 1];
8785 61 : snprintf(szExtraDimDef, sizeof(szExtraDimDef),
8786 : "{%ld,%d}", (long)lev_count, nType);
8787 : char szTemp[NC_MAX_NAME + 32 + 1];
8788 61 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
8789 : szDimName);
8790 61 : poDS->papszMetadata = CSLSetNameValue(
8791 : poDS->papszMetadata, szTemp, szExtraDimDef);
8792 :
8793 : // Retrieving data for unlimited dimensions might be
8794 : // costly on network storage, so don't do it.
8795 : // Each band will capture the value along the extra
8796 : // dimension in its NETCDF_DIM_xxxx band metadata item
8797 : // Addresses use case of
8798 : // https://lists.osgeo.org/pipermail/gdal-dev/2023-May/057209.html
8799 61 : if (VSIIsLocal(osFilenameForNCOpen.c_str()) ||
8800 0 : !NCDFIsUnlimitedDim(poDS->eFormat ==
8801 : NCDF_FORMAT_NC4,
8802 0 : cdfid, poDS->m_anDimIds[j]))
8803 : {
8804 61 : char *pszTemp = nullptr;
8805 61 : if (NCDFGet1DVar(nIdxGroupID, nIdxVarID,
8806 61 : &pszTemp) == CE_None)
8807 : {
8808 61 : snprintf(szTemp, sizeof(szTemp),
8809 : "NETCDF_DIM_%s_VALUES", szDimName);
8810 61 : poDS->papszMetadata = CSLSetNameValue(
8811 : poDS->papszMetadata, szTemp, pszTemp);
8812 61 : CPLFree(pszTemp);
8813 : }
8814 : }
8815 : }
8816 : }
8817 : else
8818 : {
8819 0 : poDS->m_anExtraDimGroupIds.push_back(-1);
8820 0 : poDS->m_anExtraDimVarIds.push_back(-1);
8821 : }
8822 :
8823 70 : nDim++;
8824 : }
8825 : }
8826 52 : osExtraDimNames += "}";
8827 52 : poDS->papszMetadata = CSLSetNameValue(
8828 : poDS->papszMetadata, "NETCDF_DIM_EXTRA", osExtraDimNames);
8829 : }
8830 :
8831 : // Store Metadata.
8832 352 : for (const auto &osStr : aosRemovedMDItems)
8833 10 : poDS->papszMetadata =
8834 10 : CSLSetNameValue(poDS->papszMetadata, osStr.c_str(), nullptr);
8835 :
8836 342 : poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
8837 :
8838 : // Create bands.
8839 :
8840 : // Arbitrary threshold.
8841 : int nMaxBandCount =
8842 342 : atoi(CPLGetConfigOption("GDAL_MAX_BAND_COUNT", "32768"));
8843 342 : if (nMaxBandCount <= 0)
8844 0 : nMaxBandCount = 32768;
8845 342 : if (nTotLevCount > static_cast<unsigned int>(nMaxBandCount))
8846 : {
8847 0 : CPLError(CE_Warning, CPLE_AppDefined,
8848 : "Limiting number of bands to %d instead of %u", nMaxBandCount,
8849 : static_cast<unsigned int>(nTotLevCount));
8850 0 : nTotLevCount = static_cast<unsigned int>(nMaxBandCount);
8851 : }
8852 342 : if (poDS->nRasterXSize == 0 || poDS->nRasterYSize == 0)
8853 : {
8854 0 : poDS->nRasterXSize = 0;
8855 0 : poDS->nRasterYSize = 0;
8856 0 : nTotLevCount = 0;
8857 0 : if (poDS->GetLayerCount() == 0)
8858 : {
8859 0 : CPLFree(panBandZLev);
8860 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
8861 : // deadlock with GDALDataset own mutex.
8862 0 : delete poDS;
8863 0 : CPLAcquireMutex(hNCMutex, 1000.0);
8864 0 : return nullptr;
8865 : }
8866 : }
8867 342 : if (bSeveralVariablesAsBands)
8868 : {
8869 5 : const auto &listVariables = oMap2DDimsToGroupAndVar.begin()->second;
8870 21 : for (int iBand = 0; iBand < static_cast<int>(listVariables.size());
8871 : ++iBand)
8872 : {
8873 16 : int bandVarGroupId = listVariables[iBand].first;
8874 16 : int bandVarId = listVariables[iBand].second;
8875 : netCDFRasterBand *poBand = new netCDFRasterBand(
8876 0 : netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, bandVarGroupId,
8877 16 : bandVarId, nDim, 0, nullptr, anBandDimPos.data(), iBand + 1);
8878 16 : poDS->SetBand(iBand + 1, poBand);
8879 : }
8880 : }
8881 : else
8882 : {
8883 772 : for (unsigned int lev = 0; lev < nTotLevCount; lev++)
8884 : {
8885 : netCDFRasterBand *poBand = new netCDFRasterBand(
8886 0 : netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, cdfid, var, nDim,
8887 435 : lev, panBandZLev, anBandDimPos.data(), lev + 1);
8888 435 : poDS->SetBand(lev + 1, poBand);
8889 : }
8890 : }
8891 :
8892 342 : if (panBandZLev)
8893 52 : CPLFree(panBandZLev);
8894 : // Handle angular geographic coordinates here
8895 :
8896 : // Initialize any PAM information.
8897 342 : if (bTreatAsSubdataset)
8898 : {
8899 57 : poDS->SetPhysicalFilename(poDS->osFilename);
8900 57 : poDS->SetSubdatasetName(osSubdatasetName);
8901 : }
8902 :
8903 342 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
8904 : // GDALDataset own mutex.
8905 342 : poDS->TryLoadXML();
8906 :
8907 342 : if (bTreatAsSubdataset)
8908 57 : poDS->oOvManager.Initialize(poDS, ":::VIRTUAL:::");
8909 : else
8910 285 : poDS->oOvManager.Initialize(poDS, poDS->osFilename);
8911 :
8912 342 : CPLAcquireMutex(hNCMutex, 1000.0);
8913 :
8914 342 : return poDS;
8915 : }
8916 :
8917 : /************************************************************************/
8918 : /* CopyMetadata() */
8919 : /* */
8920 : /* Create a copy of metadata for NC_GLOBAL or a variable */
8921 : /************************************************************************/
8922 :
8923 140 : static void CopyMetadata(GDALDataset *poSrcDS, GDALRasterBand *poSrcBand,
8924 : GDALRasterBand *poDstBand, int nCdfId, int CDFVarID,
8925 : const char *pszPrefix)
8926 : {
8927 : // Remove the following band meta but set them later from band data.
8928 140 : const char *const papszIgnoreBand[] = {
8929 : CF_ADD_OFFSET, CF_SCALE_FACTOR, "valid_range", "_Unsigned",
8930 : _FillValue, "coordinates", nullptr};
8931 140 : const char *const papszIgnoreGlobal[] = {"NETCDF_DIM_EXTRA", nullptr};
8932 :
8933 140 : CSLConstList papszMetadata = nullptr;
8934 140 : if (poSrcDS)
8935 : {
8936 59 : papszMetadata = poSrcDS->GetMetadata();
8937 : }
8938 81 : else if (poSrcBand)
8939 : {
8940 81 : papszMetadata = poSrcBand->GetMetadata();
8941 : }
8942 :
8943 610 : for (const auto &[pszKey, pszValue] : cpl::IterateNameValue(papszMetadata))
8944 : {
8945 : #ifdef NCDF_DEBUG
8946 : CPLDebug("GDAL_netCDF", "copy metadata [%s]=[%s]", pszKey, pszValue);
8947 : #endif
8948 :
8949 470 : CPLString osMetaName(pszKey);
8950 :
8951 : // Check for items that match pszPrefix if applicable.
8952 470 : if (pszPrefix && !EQUAL(pszPrefix, ""))
8953 : {
8954 : // Remove prefix.
8955 111 : if (STARTS_WITH(osMetaName.c_str(), pszPrefix))
8956 : {
8957 16 : osMetaName = osMetaName.substr(strlen(pszPrefix));
8958 : }
8959 : // Only copy items that match prefix.
8960 : else
8961 : {
8962 95 : continue;
8963 : }
8964 : }
8965 :
8966 : // Fix various issues with metadata translation.
8967 375 : if (CDFVarID == NC_GLOBAL)
8968 : {
8969 : // Do not copy items in papszIgnoreGlobal and NETCDF_DIM_*.
8970 470 : if ((CSLFindString(papszIgnoreGlobal, osMetaName) != -1) ||
8971 233 : (STARTS_WITH(osMetaName, "NETCDF_DIM_")))
8972 18 : continue;
8973 : // Remove NC_GLOBAL prefix for netcdf global Metadata.
8974 219 : else if (STARTS_WITH(osMetaName, "NC_GLOBAL#"))
8975 : {
8976 33 : osMetaName = osMetaName.substr(strlen("NC_GLOBAL#"));
8977 : }
8978 : // GDAL Metadata renamed as GDAL-[meta].
8979 186 : else if (strstr(osMetaName, "#") == nullptr)
8980 : {
8981 14 : osMetaName = "GDAL_" + osMetaName;
8982 : }
8983 : // Keep time, lev and depth information for safe-keeping.
8984 : // Time and vertical coordinate handling need improvements.
8985 : /*
8986 : else if( STARTS_WITH(szMetaName, "time#") )
8987 : {
8988 : szMetaName[4] = '-';
8989 : }
8990 : else if( STARTS_WITH(szMetaName, "lev#") )
8991 : {
8992 : szMetaName[3] = '-';
8993 : }
8994 : else if( STARTS_WITH(szMetaName, "depth#") )
8995 : {
8996 : szMetaName[5] = '-';
8997 : }
8998 : */
8999 : // Only copy data without # (previously all data was copied).
9000 219 : if (strstr(osMetaName, "#") != nullptr)
9001 172 : continue;
9002 : // netCDF attributes do not like the '#' character.
9003 : // for( unsigned int h=0; h < strlen(szMetaName) -1 ; h++ ) {
9004 : // if( szMetaName[h] == '#') szMetaName[h] = '-';
9005 : // }
9006 : }
9007 : else
9008 : {
9009 : // Do not copy varname, stats, NETCDF_DIM_*, nodata
9010 : // and items in papszIgnoreBand.
9011 138 : if (STARTS_WITH(osMetaName, "NETCDF_VARNAME") ||
9012 106 : STARTS_WITH(osMetaName, "STATISTICS_") ||
9013 106 : STARTS_WITH(osMetaName, "NETCDF_DIM_") ||
9014 73 : STARTS_WITH(osMetaName, "missing_value") ||
9015 290 : STARTS_WITH(osMetaName, "_FillValue") ||
9016 46 : CSLFindString(papszIgnoreBand, osMetaName) != -1)
9017 97 : continue;
9018 : }
9019 :
9020 : #ifdef NCDF_DEBUG
9021 : CPLDebug("GDAL_netCDF", "copy name=[%s] value=[%s]", osMetaName.c_str(),
9022 : pszValue);
9023 : #endif
9024 88 : if (NCDFPutAttr(nCdfId, CDFVarID, osMetaName, pszValue) != CE_None)
9025 : {
9026 0 : CPLDebug("GDAL_netCDF", "NCDFPutAttr(%d, %d, %s, %s) failed",
9027 : nCdfId, CDFVarID, osMetaName.c_str(), pszValue);
9028 : }
9029 : }
9030 :
9031 : // Set add_offset and scale_factor here if present.
9032 140 : if (poSrcBand && poDstBand)
9033 : {
9034 :
9035 81 : int bGotAddOffset = FALSE;
9036 81 : const double dfAddOffset = poSrcBand->GetOffset(&bGotAddOffset);
9037 81 : int bGotScale = FALSE;
9038 81 : const double dfScale = poSrcBand->GetScale(&bGotScale);
9039 :
9040 81 : if (bGotAddOffset && dfAddOffset != 0.0)
9041 1 : poDstBand->SetOffset(dfAddOffset);
9042 81 : if (bGotScale && dfScale != 1.0)
9043 1 : poDstBand->SetScale(dfScale);
9044 : }
9045 140 : }
9046 :
9047 : /************************************************************************/
9048 : /* CreateLL() */
9049 : /* */
9050 : /* Shared functionality between netCDFDataset::Create() and */
9051 : /* netCDF::CreateCopy() for creating netcdf file based on a set of */
9052 : /* options and a configuration. */
9053 : /************************************************************************/
9054 :
9055 192 : netCDFDataset *netCDFDataset::CreateLL(const char *pszFilename, int nXSize,
9056 : int nYSize, int nBandsIn,
9057 : char **papszOptions)
9058 : {
9059 192 : if (!((nXSize == 0 && nYSize == 0 && nBandsIn == 0) ||
9060 119 : (nXSize > 0 && nYSize > 0 && nBandsIn > 0)))
9061 : {
9062 1 : return nullptr;
9063 : }
9064 :
9065 191 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
9066 : // GDALDataset own mutex.
9067 191 : netCDFDataset *poDS = new netCDFDataset();
9068 191 : CPLAcquireMutex(hNCMutex, 1000.0);
9069 :
9070 191 : poDS->nRasterXSize = nXSize;
9071 191 : poDS->nRasterYSize = nYSize;
9072 191 : poDS->eAccess = GA_Update;
9073 191 : poDS->osFilename = pszFilename;
9074 :
9075 : // From gtiff driver, is this ok?
9076 : /*
9077 : poDS->nBlockXSize = nXSize;
9078 : poDS->nBlockYSize = 1;
9079 : poDS->nBlocksPerBand =
9080 : ((nYSize + poDS->nBlockYSize - 1) / poDS->nBlockYSize)
9081 : * ((nXSize + poDS->nBlockXSize - 1) / poDS->nBlockXSize);
9082 : */
9083 :
9084 : // process options.
9085 191 : poDS->papszCreationOptions = CSLDuplicate(papszOptions);
9086 191 : poDS->ProcessCreationOptions();
9087 :
9088 191 : if (poDS->eMultipleLayerBehavior == SEPARATE_FILES)
9089 : {
9090 : VSIStatBuf sStat;
9091 3 : if (VSIStat(pszFilename, &sStat) == 0)
9092 : {
9093 0 : if (!VSI_ISDIR(sStat.st_mode))
9094 : {
9095 0 : CPLError(CE_Failure, CPLE_FileIO,
9096 : "%s is an existing file, but not a directory",
9097 : pszFilename);
9098 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9099 : // deadlock with GDALDataset own
9100 : // mutex.
9101 0 : delete poDS;
9102 0 : CPLAcquireMutex(hNCMutex, 1000.0);
9103 0 : return nullptr;
9104 : }
9105 : }
9106 3 : else if (VSIMkdir(pszFilename, 0755) != 0)
9107 : {
9108 2 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s directory",
9109 : pszFilename);
9110 2 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9111 : // deadlock with GDALDataset own mutex.
9112 2 : delete poDS;
9113 2 : CPLAcquireMutex(hNCMutex, 1000.0);
9114 2 : return nullptr;
9115 : }
9116 :
9117 1 : return poDS;
9118 : }
9119 : // Create the dataset.
9120 376 : CPLString osFilenameForNCCreate(pszFilename);
9121 : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
9122 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
9123 : {
9124 : char *pszTemp =
9125 : CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
9126 : osFilenameForNCCreate = pszTemp;
9127 : CPLFree(pszTemp);
9128 : }
9129 : #endif
9130 :
9131 : #if defined(_WIN32)
9132 : {
9133 : // Works around bug of msys2 netCDF 4.9.0 package where nc_create()
9134 : // crashes
9135 : VSIStatBuf sStat;
9136 : const char *pszDir = CPLGetDirname(osFilenameForNCCreate.c_str());
9137 : if (VSIStat(pszDir, &sStat) != 0)
9138 : {
9139 : CPLError(CE_Failure, CPLE_OpenFailed,
9140 : "Unable to create netCDF file %s: non existing output "
9141 : "directory",
9142 : pszFilename);
9143 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
9144 : // deadlock with GDALDataset own mutex.
9145 : delete poDS;
9146 : CPLAcquireMutex(hNCMutex, 1000.0);
9147 : return nullptr;
9148 : }
9149 : }
9150 : #endif
9151 :
9152 : int status =
9153 188 : nc_create(osFilenameForNCCreate, poDS->nCreateMode, &(poDS->cdfid));
9154 :
9155 : // Put into define mode.
9156 188 : poDS->SetDefineMode(true);
9157 :
9158 188 : if (status != NC_NOERR)
9159 : {
9160 30 : CPLError(CE_Failure, CPLE_OpenFailed,
9161 : "Unable to create netCDF file %s (Error code %d): %s .",
9162 : pszFilename, status, nc_strerror(status));
9163 30 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
9164 : // with GDALDataset own mutex.
9165 30 : delete poDS;
9166 30 : CPLAcquireMutex(hNCMutex, 1000.0);
9167 30 : return nullptr;
9168 : }
9169 :
9170 : // Define dimensions.
9171 158 : if (nXSize > 0 && nYSize > 0)
9172 : {
9173 105 : poDS->papszDimName.AddString(NCDF_DIMNAME_X);
9174 : status =
9175 105 : nc_def_dim(poDS->cdfid, NCDF_DIMNAME_X, nXSize, &(poDS->nXDimID));
9176 105 : NCDF_ERR(status);
9177 105 : CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
9178 : poDS->cdfid, NCDF_DIMNAME_X, nXSize, poDS->nXDimID);
9179 :
9180 105 : poDS->papszDimName.AddString(NCDF_DIMNAME_Y);
9181 : status =
9182 105 : nc_def_dim(poDS->cdfid, NCDF_DIMNAME_Y, nYSize, &(poDS->nYDimID));
9183 105 : NCDF_ERR(status);
9184 105 : CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
9185 : poDS->cdfid, NCDF_DIMNAME_Y, nYSize, poDS->nYDimID);
9186 : }
9187 :
9188 158 : return poDS;
9189 : }
9190 :
9191 : /************************************************************************/
9192 : /* Create() */
9193 : /************************************************************************/
9194 :
9195 126 : GDALDataset *netCDFDataset::Create(const char *pszFilename, int nXSize,
9196 : int nYSize, int nBandsIn, GDALDataType eType,
9197 : char **papszOptions)
9198 : {
9199 126 : CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::Create(%s, ...)",
9200 : pszFilename);
9201 :
9202 : const char *legacyCreationOp =
9203 126 : CSLFetchNameValueDef(papszOptions, "GEOMETRY_ENCODING", "CF_1.8");
9204 252 : std::string legacyCreationOp_s = std::string(legacyCreationOp);
9205 :
9206 : // Check legacy creation op FIRST
9207 :
9208 126 : bool legacyCreateMode = false;
9209 :
9210 126 : if (nXSize != 0 || nYSize != 0 || nBandsIn != 0)
9211 : {
9212 55 : legacyCreateMode = true;
9213 : }
9214 71 : else if (legacyCreationOp_s == "CF_1.8")
9215 : {
9216 54 : legacyCreateMode = false;
9217 : }
9218 :
9219 17 : else if (legacyCreationOp_s == "WKT")
9220 : {
9221 17 : legacyCreateMode = true;
9222 : }
9223 :
9224 : else
9225 : {
9226 0 : CPLError(
9227 : CE_Failure, CPLE_NotSupported,
9228 : "Dataset creation option GEOMETRY_ENCODING=%s is not supported.",
9229 : legacyCreationOp_s.c_str());
9230 0 : return nullptr;
9231 : }
9232 :
9233 252 : CPLStringList aosOptions(CSLDuplicate(papszOptions));
9234 239 : if (aosOptions.FetchNameValue("FORMAT") == nullptr &&
9235 113 : (eType == GDT_UInt16 || eType == GDT_UInt32 || eType == GDT_UInt64 ||
9236 : eType == GDT_Int64))
9237 : {
9238 10 : CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
9239 10 : aosOptions.SetNameValue("FORMAT", "NC4");
9240 : }
9241 :
9242 252 : CPLStringList aosBandNames;
9243 126 : if (const char *pszBandNames = aosOptions.FetchNameValue("BAND_NAMES"))
9244 : {
9245 : aosBandNames =
9246 2 : CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);
9247 :
9248 2 : if (aosBandNames.Count() != nBandsIn)
9249 : {
9250 1 : CPLError(CE_Failure, CPLE_OpenFailed,
9251 : "Attempted to create netCDF with %d bands but %d names "
9252 : "provided in BAND_NAMES.",
9253 : nBandsIn, aosBandNames.Count());
9254 :
9255 1 : return nullptr;
9256 : }
9257 : }
9258 :
9259 250 : CPLMutexHolderD(&hNCMutex);
9260 :
9261 125 : auto poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize, nBandsIn,
9262 : aosOptions.List());
9263 :
9264 125 : if (!poDS)
9265 20 : return nullptr;
9266 :
9267 105 : if (!legacyCreateMode)
9268 : {
9269 37 : poDS->bSGSupport = true;
9270 37 : poDS->vcdf.enableFullVirtualMode();
9271 : }
9272 :
9273 : else
9274 : {
9275 68 : poDS->bSGSupport = false;
9276 : }
9277 :
9278 : // Should we write signed or unsigned byte?
9279 : // TODO should this only be done in Create()
9280 105 : poDS->bSignedData = true;
9281 105 : const char *pszValue = CSLFetchNameValueDef(papszOptions, "PIXELTYPE", "");
9282 105 : if (eType == GDT_Byte && !EQUAL(pszValue, "SIGNEDBYTE"))
9283 14 : poDS->bSignedData = false;
9284 :
9285 : // Add Conventions, GDAL info and history.
9286 105 : if (poDS->cdfid >= 0)
9287 : {
9288 104 : const char *CF_Vector_Conv = poDS->bSGSupport
9289 : ? NCDF_CONVENTIONS_CF_V1_8
9290 : : NCDF_CONVENTIONS_CF_V1_6;
9291 104 : poDS->bWriteGDALVersion = CPLTestBool(
9292 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
9293 104 : poDS->bWriteGDALHistory = CPLTestBool(
9294 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
9295 104 : NCDFAddGDALHistory(poDS->cdfid, pszFilename, poDS->bWriteGDALVersion,
9296 104 : poDS->bWriteGDALHistory, "", "Create",
9297 : (nBandsIn == 0) ? CF_Vector_Conv
9298 : : GDAL_DEFAULT_NCDF_CONVENTIONS);
9299 : }
9300 :
9301 : // Define bands.
9302 195 : for (int iBand = 1; iBand <= nBandsIn; iBand++)
9303 : {
9304 : const char *pszBandName =
9305 90 : aosBandNames.empty() ? nullptr : aosBandNames[iBand - 1];
9306 :
9307 90 : poDS->SetBand(iBand, new netCDFRasterBand(
9308 90 : netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS,
9309 90 : eType, iBand, poDS->bSignedData, pszBandName));
9310 : }
9311 :
9312 105 : CPLDebug("GDAL_netCDF", "netCDFDataset::Create(%s, ...) done", pszFilename);
9313 : // Return same dataset.
9314 105 : return poDS;
9315 : }
9316 :
9317 : template <class T>
9318 81 : static CPLErr NCDFCopyBand(GDALRasterBand *poSrcBand, GDALRasterBand *poDstBand,
9319 : int nXSize, int nYSize, GDALProgressFunc pfnProgress,
9320 : void *pProgressData)
9321 : {
9322 81 : GDALDataType eDT = poSrcBand->GetRasterDataType();
9323 81 : CPLErr eErr = CE_None;
9324 81 : T *patScanline = static_cast<T *>(CPLMalloc(nXSize * sizeof(T)));
9325 :
9326 1928 : for (int iLine = 0; iLine < nYSize && eErr == CE_None; iLine++)
9327 : {
9328 1847 : eErr = poSrcBand->RasterIO(GF_Read, 0, iLine, nXSize, 1, patScanline,
9329 : nXSize, 1, eDT, 0, 0, nullptr);
9330 1847 : if (eErr != CE_None)
9331 : {
9332 0 : CPLDebug(
9333 : "GDAL_netCDF",
9334 : "NCDFCopyBand(), poSrcBand->RasterIO() returned error code %d",
9335 : eErr);
9336 : }
9337 : else
9338 : {
9339 1847 : eErr =
9340 : poDstBand->RasterIO(GF_Write, 0, iLine, nXSize, 1, patScanline,
9341 : nXSize, 1, eDT, 0, 0, nullptr);
9342 1847 : if (eErr != CE_None)
9343 0 : CPLDebug("GDAL_netCDF",
9344 : "NCDFCopyBand(), poDstBand->RasterIO() returned error "
9345 : "code %d",
9346 : eErr);
9347 : }
9348 :
9349 1847 : if (nYSize > 10 && (iLine % (nYSize / 10) == 1))
9350 : {
9351 196 : if (!pfnProgress(1.0 * iLine / nYSize, nullptr, pProgressData))
9352 : {
9353 0 : eErr = CE_Failure;
9354 0 : CPLError(CE_Failure, CPLE_UserInterrupt,
9355 : "User terminated CreateCopy()");
9356 : }
9357 : }
9358 : }
9359 :
9360 81 : CPLFree(patScanline);
9361 :
9362 81 : pfnProgress(1.0, nullptr, pProgressData);
9363 :
9364 81 : return eErr;
9365 : }
9366 :
9367 : /************************************************************************/
9368 : /* CreateCopy() */
9369 : /************************************************************************/
9370 :
9371 : GDALDataset *
9372 75 : netCDFDataset::CreateCopy(const char *pszFilename, GDALDataset *poSrcDS,
9373 : CPL_UNUSED int bStrict, char **papszOptions,
9374 : GDALProgressFunc pfnProgress, void *pProgressData)
9375 : {
9376 150 : CPLMutexHolderD(&hNCMutex);
9377 :
9378 75 : CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::CreateCopy(%s, ...)",
9379 : pszFilename);
9380 :
9381 75 : if (poSrcDS->GetRootGroup())
9382 : {
9383 5 : auto poDrv = GDALDriver::FromHandle(GDALGetDriverByName("netCDF"));
9384 5 : if (poDrv)
9385 : {
9386 5 : return poDrv->DefaultCreateCopy(pszFilename, poSrcDS, bStrict,
9387 : papszOptions, pfnProgress,
9388 5 : pProgressData);
9389 : }
9390 : }
9391 :
9392 70 : const int nBands = poSrcDS->GetRasterCount();
9393 70 : const int nXSize = poSrcDS->GetRasterXSize();
9394 70 : const int nYSize = poSrcDS->GetRasterYSize();
9395 70 : const char *pszWKT = poSrcDS->GetProjectionRef();
9396 :
9397 : // Check input bands for errors.
9398 70 : if (nBands == 0)
9399 : {
9400 1 : CPLError(CE_Failure, CPLE_NotSupported,
9401 : "NetCDF driver does not support "
9402 : "source dataset with zero band.");
9403 1 : return nullptr;
9404 : }
9405 :
9406 69 : GDALDataType eDT = GDT_Unknown;
9407 69 : GDALRasterBand *poSrcBand = nullptr;
9408 163 : for (int iBand = 1; iBand <= nBands; iBand++)
9409 : {
9410 98 : poSrcBand = poSrcDS->GetRasterBand(iBand);
9411 98 : eDT = poSrcBand->GetRasterDataType();
9412 98 : if (eDT == GDT_Unknown || GDALDataTypeIsComplex(eDT))
9413 : {
9414 4 : CPLError(CE_Failure, CPLE_NotSupported,
9415 : "NetCDF driver does not support source dataset with band "
9416 : "of complex type.");
9417 4 : return nullptr;
9418 : }
9419 : }
9420 :
9421 65 : if (!pfnProgress(0.0, nullptr, pProgressData))
9422 0 : return nullptr;
9423 :
9424 : // Same as in Create().
9425 130 : CPLStringList aosOptions(CSLDuplicate(papszOptions));
9426 122 : if (aosOptions.FetchNameValue("FORMAT") == nullptr &&
9427 57 : (eDT == GDT_UInt16 || eDT == GDT_UInt32 || eDT == GDT_UInt64 ||
9428 : eDT == GDT_Int64))
9429 : {
9430 6 : CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
9431 6 : aosOptions.SetNameValue("FORMAT", "NC4");
9432 : }
9433 65 : netCDFDataset *poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize,
9434 : nBands, aosOptions.List());
9435 65 : if (!poDS)
9436 13 : return nullptr;
9437 :
9438 : // Copy global metadata.
9439 : // Add Conventions, GDAL info and history.
9440 52 : CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid, NC_GLOBAL, nullptr);
9441 52 : const bool bWriteGDALVersion = CPLTestBool(
9442 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
9443 52 : const bool bWriteGDALHistory = CPLTestBool(
9444 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
9445 52 : NCDFAddGDALHistory(
9446 : poDS->cdfid, pszFilename, bWriteGDALVersion, bWriteGDALHistory,
9447 52 : poSrcDS->GetMetadataItem("NC_GLOBAL#history"), "CreateCopy",
9448 52 : poSrcDS->GetMetadataItem("NC_GLOBAL#Conventions"));
9449 :
9450 52 : pfnProgress(0.1, nullptr, pProgressData);
9451 :
9452 : // Check for extra dimensions.
9453 52 : int nDim = 2;
9454 : char **papszExtraDimNames =
9455 52 : NCDFTokenizeArray(poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
9456 52 : char **papszExtraDimValues = nullptr;
9457 :
9458 52 : if (papszExtraDimNames != nullptr && CSLCount(papszExtraDimNames) > 0)
9459 : {
9460 4 : size_t nDimSizeTot = 1;
9461 : // first make sure dimensions lengths compatible with band count
9462 : // for( int i=0; i<CSLCount(papszExtraDimNames ); i++ ) {
9463 11 : for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--)
9464 : {
9465 : char szTemp[NC_MAX_NAME + 32 + 1];
9466 7 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
9467 7 : papszExtraDimNames[i]);
9468 : papszExtraDimValues =
9469 7 : NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
9470 7 : const size_t nDimSize = atol(papszExtraDimValues[0]);
9471 7 : CSLDestroy(papszExtraDimValues);
9472 7 : nDimSizeTot *= nDimSize;
9473 : }
9474 4 : if (nDimSizeTot == (size_t)nBands)
9475 : {
9476 4 : nDim = 2 + CSLCount(papszExtraDimNames);
9477 : }
9478 : else
9479 : {
9480 : // if nBands != #bands computed raise a warning
9481 : // just issue a debug message, because it was probably intentional
9482 0 : CPLDebug("GDAL_netCDF",
9483 : "Warning: Number of bands (%d) is not compatible with "
9484 : "dimensions "
9485 : "(total=%ld names=%s)",
9486 : nBands, (long)nDimSizeTot,
9487 0 : poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
9488 0 : CSLDestroy(papszExtraDimNames);
9489 0 : papszExtraDimNames = nullptr;
9490 : }
9491 : }
9492 :
9493 52 : int *panDimIds = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
9494 52 : int *panBandDimPos = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
9495 :
9496 : nc_type nVarType;
9497 52 : int *panBandZLev = nullptr;
9498 52 : int *panDimVarIds = nullptr;
9499 :
9500 52 : if (nDim > 2)
9501 : {
9502 4 : panBandZLev = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
9503 4 : panDimVarIds = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
9504 :
9505 : // Define all dims.
9506 11 : for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--)
9507 : {
9508 7 : poDS->papszDimName.AddString(papszExtraDimNames[i]);
9509 : char szTemp[NC_MAX_NAME + 32 + 1];
9510 7 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
9511 7 : papszExtraDimNames[i]);
9512 : papszExtraDimValues =
9513 7 : NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
9514 7 : const int nDimSize = papszExtraDimValues && papszExtraDimValues[0]
9515 14 : ? atoi(papszExtraDimValues[0])
9516 : : 0;
9517 : // nc_type is an enum in netcdf-3, needs casting.
9518 7 : nVarType = static_cast<nc_type>(papszExtraDimValues &&
9519 7 : papszExtraDimValues[0] &&
9520 7 : papszExtraDimValues[1]
9521 7 : ? atol(papszExtraDimValues[1])
9522 : : 0);
9523 7 : CSLDestroy(papszExtraDimValues);
9524 7 : panBandZLev[i] = nDimSize;
9525 7 : panBandDimPos[i + 2] = i; // Save Position of ZDim.
9526 :
9527 : // Define dim.
9528 14 : int status = nc_def_dim(poDS->cdfid, papszExtraDimNames[i],
9529 7 : nDimSize, &(panDimIds[i]));
9530 7 : NCDF_ERR(status);
9531 :
9532 : // Define dim var.
9533 7 : int anDim[1] = {panDimIds[i]};
9534 14 : status = nc_def_var(poDS->cdfid, papszExtraDimNames[i], nVarType, 1,
9535 7 : anDim, &(panDimVarIds[i]));
9536 7 : NCDF_ERR(status);
9537 :
9538 : // Add dim metadata, using global var# items.
9539 7 : snprintf(szTemp, sizeof(szTemp), "%s#", papszExtraDimNames[i]);
9540 7 : CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid,
9541 7 : panDimVarIds[i], szTemp);
9542 : }
9543 : }
9544 :
9545 : // Copy GeoTransform and Projection.
9546 :
9547 : // Copy geolocation info.
9548 52 : char **papszGeolocationInfo = poSrcDS->GetMetadata("GEOLOCATION");
9549 52 : if (papszGeolocationInfo != nullptr)
9550 5 : poDS->GDALPamDataset::SetMetadata(papszGeolocationInfo, "GEOLOCATION");
9551 :
9552 : // Copy geotransform.
9553 52 : bool bGotGeoTransform = false;
9554 : double adfGeoTransform[6];
9555 52 : CPLErr eErr = poSrcDS->GetGeoTransform(adfGeoTransform);
9556 52 : if (eErr == CE_None)
9557 : {
9558 36 : poDS->SetGeoTransform(adfGeoTransform);
9559 : // Disable AddProjectionVars() from being called.
9560 36 : bGotGeoTransform = true;
9561 36 : poDS->m_bHasGeoTransform = false;
9562 : }
9563 :
9564 : // Copy projection.
9565 52 : void *pScaledProgress = nullptr;
9566 52 : if (bGotGeoTransform || (pszWKT && pszWKT[0] != 0))
9567 : {
9568 37 : poDS->SetProjection(pszWKT ? pszWKT : "");
9569 :
9570 : // Now we can call AddProjectionVars() directly.
9571 37 : poDS->m_bHasGeoTransform = bGotGeoTransform;
9572 37 : poDS->AddProjectionVars(true, nullptr, nullptr);
9573 : pScaledProgress =
9574 37 : GDALCreateScaledProgress(0.1, 0.25, pfnProgress, pProgressData);
9575 37 : poDS->AddProjectionVars(false, GDALScaledProgress, pScaledProgress);
9576 : // Save X,Y dim positions.
9577 37 : panDimIds[nDim - 1] = poDS->nXDimID;
9578 37 : panBandDimPos[0] = nDim - 1;
9579 37 : panDimIds[nDim - 2] = poDS->nYDimID;
9580 37 : panBandDimPos[1] = nDim - 2;
9581 37 : GDALDestroyScaledProgress(pScaledProgress);
9582 : }
9583 : else
9584 : {
9585 15 : poDS->bBottomUp =
9586 15 : CPL_TO_BOOL(CSLFetchBoolean(papszOptions, "WRITE_BOTTOMUP", TRUE));
9587 15 : if (papszGeolocationInfo)
9588 : {
9589 4 : poDS->AddProjectionVars(true, nullptr, nullptr);
9590 4 : poDS->AddProjectionVars(false, nullptr, nullptr);
9591 : }
9592 : }
9593 :
9594 : // Write extra dim values - after projection for optimization.
9595 52 : if (nDim > 2)
9596 : {
9597 : // Make sure we are in data mode.
9598 4 : static_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
9599 11 : for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--)
9600 : {
9601 : char szTemp[NC_MAX_NAME + 32 + 1];
9602 7 : snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_VALUES",
9603 7 : papszExtraDimNames[i]);
9604 7 : if (poSrcDS->GetMetadataItem(szTemp) != nullptr)
9605 : {
9606 7 : NCDFPut1DVar(poDS->cdfid, panDimVarIds[i],
9607 7 : poSrcDS->GetMetadataItem(szTemp));
9608 : }
9609 : }
9610 : }
9611 :
9612 52 : pfnProgress(0.25, nullptr, pProgressData);
9613 :
9614 : // Define Bands.
9615 52 : netCDFRasterBand *poBand = nullptr;
9616 52 : int nBandID = -1;
9617 :
9618 133 : for (int iBand = 1; iBand <= nBands; iBand++)
9619 : {
9620 81 : CPLDebug("GDAL_netCDF", "creating band # %d/%d nDim = %d", iBand,
9621 : nBands, nDim);
9622 :
9623 81 : poSrcBand = poSrcDS->GetRasterBand(iBand);
9624 81 : eDT = poSrcBand->GetRasterDataType();
9625 :
9626 : // Get var name from NETCDF_VARNAME.
9627 : const char *pszNETCDF_VARNAME =
9628 81 : poSrcBand->GetMetadataItem("NETCDF_VARNAME");
9629 : char szBandName[NC_MAX_NAME + 1];
9630 81 : if (pszNETCDF_VARNAME)
9631 : {
9632 32 : if (nBands > 1 && papszExtraDimNames == nullptr)
9633 0 : snprintf(szBandName, sizeof(szBandName), "%s%d",
9634 : pszNETCDF_VARNAME, iBand);
9635 : else
9636 32 : snprintf(szBandName, sizeof(szBandName), "%s",
9637 : pszNETCDF_VARNAME);
9638 : }
9639 : else
9640 : {
9641 49 : szBandName[0] = '\0';
9642 : }
9643 :
9644 : // Get long_name from <var>#long_name.
9645 81 : const char *pszLongName = "";
9646 81 : if (pszNETCDF_VARNAME)
9647 : {
9648 : pszLongName =
9649 64 : poSrcDS->GetMetadataItem(std::string(pszNETCDF_VARNAME)
9650 32 : .append("#")
9651 32 : .append(CF_LNG_NAME)
9652 32 : .c_str());
9653 32 : if (!pszLongName)
9654 25 : pszLongName = "";
9655 : }
9656 :
9657 81 : constexpr bool bSignedData = false;
9658 :
9659 81 : if (nDim > 2)
9660 23 : poBand = new netCDFRasterBand(
9661 23 : netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
9662 : bSignedData, szBandName, pszLongName, nBandID, nDim, iBand - 1,
9663 23 : panBandZLev, panBandDimPos, panDimIds);
9664 : else
9665 58 : poBand = new netCDFRasterBand(
9666 58 : netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
9667 58 : bSignedData, szBandName, pszLongName);
9668 :
9669 81 : poDS->SetBand(iBand, poBand);
9670 :
9671 : // Set nodata value, if any.
9672 81 : GDALCopyNoDataValue(poBand, poSrcBand);
9673 :
9674 : // Copy Metadata for band.
9675 81 : CopyMetadata(nullptr, poSrcDS->GetRasterBand(iBand), poBand,
9676 : poDS->cdfid, poBand->nZId);
9677 :
9678 : // If more than 2D pass the first band's netcdf var ID to subsequent
9679 : // bands.
9680 81 : if (nDim > 2)
9681 23 : nBandID = poBand->nZId;
9682 : }
9683 :
9684 : // Write projection variable to band variable.
9685 52 : poDS->AddGridMappingRef();
9686 :
9687 52 : pfnProgress(0.5, nullptr, pProgressData);
9688 :
9689 : // Write bands.
9690 :
9691 : // Make sure we are in data mode.
9692 52 : poDS->SetDefineMode(false);
9693 :
9694 52 : double dfTemp = 0.5;
9695 :
9696 52 : eErr = CE_None;
9697 :
9698 133 : for (int iBand = 1; iBand <= nBands && eErr == CE_None; iBand++)
9699 : {
9700 81 : const double dfTemp2 = dfTemp + 0.4 / nBands;
9701 81 : pScaledProgress = GDALCreateScaledProgress(dfTemp, dfTemp2, pfnProgress,
9702 : pProgressData);
9703 81 : dfTemp = dfTemp2;
9704 :
9705 81 : CPLDebug("GDAL_netCDF", "copying band data # %d/%d ", iBand, nBands);
9706 :
9707 81 : poSrcBand = poSrcDS->GetRasterBand(iBand);
9708 81 : eDT = poSrcBand->GetRasterDataType();
9709 :
9710 81 : GDALRasterBand *poDstBand = poDS->GetRasterBand(iBand);
9711 :
9712 : // Copy band data.
9713 81 : if (eDT == GDT_Byte)
9714 : {
9715 41 : CPLDebug("GDAL_netCDF", "GByte Band#%d", iBand);
9716 41 : eErr = NCDFCopyBand<GByte>(poSrcBand, poDstBand, nXSize, nYSize,
9717 : GDALScaledProgress, pScaledProgress);
9718 : }
9719 40 : else if (eDT == GDT_Int8)
9720 : {
9721 1 : CPLDebug("GDAL_netCDF", "GInt8 Band#%d", iBand);
9722 1 : eErr = NCDFCopyBand<GInt8>(poSrcBand, poDstBand, nXSize, nYSize,
9723 : GDALScaledProgress, pScaledProgress);
9724 : }
9725 39 : else if (eDT == GDT_UInt16)
9726 : {
9727 2 : CPLDebug("GDAL_netCDF", "GUInt16 Band#%d", iBand);
9728 2 : eErr = NCDFCopyBand<GInt16>(poSrcBand, poDstBand, nXSize, nYSize,
9729 : GDALScaledProgress, pScaledProgress);
9730 : }
9731 37 : else if (eDT == GDT_Int16)
9732 : {
9733 5 : CPLDebug("GDAL_netCDF", "GInt16 Band#%d", iBand);
9734 5 : eErr = NCDFCopyBand<GUInt16>(poSrcBand, poDstBand, nXSize, nYSize,
9735 : GDALScaledProgress, pScaledProgress);
9736 : }
9737 32 : else if (eDT == GDT_UInt32)
9738 : {
9739 2 : CPLDebug("GDAL_netCDF", "GUInt32 Band#%d", iBand);
9740 2 : eErr = NCDFCopyBand<GUInt32>(poSrcBand, poDstBand, nXSize, nYSize,
9741 : GDALScaledProgress, pScaledProgress);
9742 : }
9743 30 : else if (eDT == GDT_Int32)
9744 : {
9745 18 : CPLDebug("GDAL_netCDF", "GInt32 Band#%d", iBand);
9746 18 : eErr = NCDFCopyBand<GInt32>(poSrcBand, poDstBand, nXSize, nYSize,
9747 : GDALScaledProgress, pScaledProgress);
9748 : }
9749 12 : else if (eDT == GDT_UInt64)
9750 : {
9751 2 : CPLDebug("GDAL_netCDF", "GUInt64 Band#%d", iBand);
9752 2 : eErr = NCDFCopyBand<std::uint64_t>(poSrcBand, poDstBand, nXSize,
9753 : nYSize, GDALScaledProgress,
9754 : pScaledProgress);
9755 : }
9756 10 : else if (eDT == GDT_Int64)
9757 : {
9758 2 : CPLDebug("GDAL_netCDF", "GInt64 Band#%d", iBand);
9759 : eErr =
9760 2 : NCDFCopyBand<std::int64_t>(poSrcBand, poDstBand, nXSize, nYSize,
9761 : GDALScaledProgress, pScaledProgress);
9762 : }
9763 8 : else if (eDT == GDT_Float32)
9764 : {
9765 6 : CPLDebug("GDAL_netCDF", "float Band#%d", iBand);
9766 6 : eErr = NCDFCopyBand<float>(poSrcBand, poDstBand, nXSize, nYSize,
9767 : GDALScaledProgress, pScaledProgress);
9768 : }
9769 2 : else if (eDT == GDT_Float64)
9770 : {
9771 2 : CPLDebug("GDAL_netCDF", "double Band#%d", iBand);
9772 2 : eErr = NCDFCopyBand<double>(poSrcBand, poDstBand, nXSize, nYSize,
9773 : GDALScaledProgress, pScaledProgress);
9774 : }
9775 : else
9776 : {
9777 0 : CPLError(CE_Failure, CPLE_NotSupported,
9778 : "The NetCDF driver does not support GDAL data type %d",
9779 : eDT);
9780 : }
9781 :
9782 81 : GDALDestroyScaledProgress(pScaledProgress);
9783 : }
9784 :
9785 52 : delete (poDS);
9786 :
9787 52 : CPLFree(panDimIds);
9788 52 : CPLFree(panBandDimPos);
9789 52 : CPLFree(panBandZLev);
9790 52 : CPLFree(panDimVarIds);
9791 52 : if (papszExtraDimNames)
9792 4 : CSLDestroy(papszExtraDimNames);
9793 :
9794 52 : if (eErr != CE_None)
9795 0 : return nullptr;
9796 :
9797 52 : pfnProgress(0.95, nullptr, pProgressData);
9798 :
9799 : // Re-open dataset so we can return it.
9800 104 : CPLStringList aosOpenOptions;
9801 52 : aosOpenOptions.AddString("VARIABLES_AS_BANDS=YES");
9802 52 : GDALOpenInfo oOpenInfo(pszFilename, GA_Update);
9803 52 : oOpenInfo.nOpenFlags = GDAL_OF_RASTER | GDAL_OF_UPDATE;
9804 52 : oOpenInfo.papszOpenOptions = aosOpenOptions.List();
9805 52 : auto poRetDS = Open(&oOpenInfo);
9806 :
9807 : // PAM cloning is disabled. See bug #4244.
9808 : // if( poDS )
9809 : // poDS->CloneInfo(poSrcDS, GCIF_PAM_DEFAULT);
9810 :
9811 52 : pfnProgress(1.0, nullptr, pProgressData);
9812 :
9813 52 : return poRetDS;
9814 : }
9815 :
9816 : // Note: some logic depends on bIsProjected and bIsGeoGraphic.
9817 : // May not be known when Create() is called, see AddProjectionVars().
9818 248 : void netCDFDataset::ProcessCreationOptions()
9819 : {
9820 : const char *pszConfig =
9821 248 : CSLFetchNameValue(papszCreationOptions, "CONFIG_FILE");
9822 248 : if (pszConfig != nullptr)
9823 : {
9824 4 : if (oWriterConfig.Parse(pszConfig))
9825 : {
9826 : // Override dataset creation options from the config file
9827 2 : std::map<CPLString, CPLString>::iterator oIter;
9828 3 : for (oIter = oWriterConfig.m_oDatasetCreationOptions.begin();
9829 3 : oIter != oWriterConfig.m_oDatasetCreationOptions.end();
9830 1 : ++oIter)
9831 : {
9832 2 : papszCreationOptions = CSLSetNameValue(
9833 2 : papszCreationOptions, oIter->first, oIter->second);
9834 : }
9835 : }
9836 : }
9837 :
9838 : // File format.
9839 248 : eFormat = NCDF_FORMAT_NC;
9840 248 : const char *pszValue = CSLFetchNameValue(papszCreationOptions, "FORMAT");
9841 248 : if (pszValue != nullptr)
9842 : {
9843 91 : if (EQUAL(pszValue, "NC"))
9844 : {
9845 2 : eFormat = NCDF_FORMAT_NC;
9846 : }
9847 : #ifdef NETCDF_HAS_NC2
9848 89 : else if (EQUAL(pszValue, "NC2"))
9849 : {
9850 0 : eFormat = NCDF_FORMAT_NC2;
9851 : }
9852 : #endif
9853 89 : else if (EQUAL(pszValue, "NC4"))
9854 : {
9855 85 : eFormat = NCDF_FORMAT_NC4;
9856 : }
9857 4 : else if (EQUAL(pszValue, "NC4C"))
9858 : {
9859 4 : eFormat = NCDF_FORMAT_NC4C;
9860 : }
9861 : else
9862 : {
9863 0 : CPLError(CE_Failure, CPLE_NotSupported,
9864 : "FORMAT=%s in not supported, using the default NC format.",
9865 : pszValue);
9866 : }
9867 : }
9868 :
9869 : // COMPRESS option.
9870 248 : pszValue = CSLFetchNameValue(papszCreationOptions, "COMPRESS");
9871 248 : if (pszValue != nullptr)
9872 : {
9873 2 : if (EQUAL(pszValue, "NONE"))
9874 : {
9875 1 : eCompress = NCDF_COMPRESS_NONE;
9876 : }
9877 1 : else if (EQUAL(pszValue, "DEFLATE"))
9878 : {
9879 1 : eCompress = NCDF_COMPRESS_DEFLATE;
9880 1 : if (!((eFormat == NCDF_FORMAT_NC4) ||
9881 1 : (eFormat == NCDF_FORMAT_NC4C)))
9882 : {
9883 0 : CPLError(CE_Warning, CPLE_IllegalArg,
9884 : "NOTICE: Format set to NC4C because compression is "
9885 : "set to DEFLATE.");
9886 0 : eFormat = NCDF_FORMAT_NC4C;
9887 : }
9888 : }
9889 : else
9890 : {
9891 0 : CPLError(CE_Failure, CPLE_NotSupported,
9892 : "COMPRESS=%s is not supported.", pszValue);
9893 : }
9894 : }
9895 :
9896 : // ZLEVEL option.
9897 248 : pszValue = CSLFetchNameValue(papszCreationOptions, "ZLEVEL");
9898 248 : if (pszValue != nullptr)
9899 : {
9900 1 : nZLevel = atoi(pszValue);
9901 1 : if (!(nZLevel >= 1 && nZLevel <= 9))
9902 : {
9903 0 : CPLError(CE_Warning, CPLE_IllegalArg,
9904 : "ZLEVEL=%s value not recognised, ignoring.", pszValue);
9905 0 : nZLevel = NCDF_DEFLATE_LEVEL;
9906 : }
9907 : }
9908 :
9909 : // CHUNKING option.
9910 248 : bChunking =
9911 248 : CPL_TO_BOOL(CSLFetchBoolean(papszCreationOptions, "CHUNKING", TRUE));
9912 :
9913 : // MULTIPLE_LAYERS option.
9914 : const char *pszMultipleLayerBehavior =
9915 248 : CSLFetchNameValueDef(papszCreationOptions, "MULTIPLE_LAYERS", "NO");
9916 496 : const char *pszGeometryEnc = CSLFetchNameValueDef(
9917 248 : papszCreationOptions, "GEOMETRY_ENCODING", "CF_1.8");
9918 248 : if (EQUAL(pszMultipleLayerBehavior, "NO") ||
9919 4 : EQUAL(pszGeometryEnc, "CF_1.8"))
9920 : {
9921 244 : eMultipleLayerBehavior = SINGLE_LAYER;
9922 : }
9923 4 : else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_FILES"))
9924 : {
9925 3 : eMultipleLayerBehavior = SEPARATE_FILES;
9926 : }
9927 1 : else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_GROUPS"))
9928 : {
9929 1 : if (eFormat == NCDF_FORMAT_NC4)
9930 : {
9931 1 : eMultipleLayerBehavior = SEPARATE_GROUPS;
9932 : }
9933 : else
9934 : {
9935 0 : CPLError(CE_Warning, CPLE_IllegalArg,
9936 : "MULTIPLE_LAYERS=%s is recognised only with FORMAT=NC4",
9937 : pszMultipleLayerBehavior);
9938 : }
9939 : }
9940 : else
9941 : {
9942 0 : CPLError(CE_Warning, CPLE_IllegalArg,
9943 : "MULTIPLE_LAYERS=%s not recognised", pszMultipleLayerBehavior);
9944 : }
9945 :
9946 : // Set nCreateMode based on eFormat.
9947 248 : switch (eFormat)
9948 : {
9949 : #ifdef NETCDF_HAS_NC2
9950 0 : case NCDF_FORMAT_NC2:
9951 0 : nCreateMode = NC_CLOBBER | NC_64BIT_OFFSET;
9952 0 : break;
9953 : #endif
9954 85 : case NCDF_FORMAT_NC4:
9955 85 : nCreateMode = NC_CLOBBER | NC_NETCDF4;
9956 85 : break;
9957 4 : case NCDF_FORMAT_NC4C:
9958 4 : nCreateMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
9959 4 : break;
9960 159 : case NCDF_FORMAT_NC:
9961 : default:
9962 159 : nCreateMode = NC_CLOBBER;
9963 159 : break;
9964 : }
9965 :
9966 248 : CPLDebug("GDAL_netCDF", "file options: format=%d compress=%d zlevel=%d",
9967 248 : eFormat, eCompress, nZLevel);
9968 248 : }
9969 :
9970 266 : int netCDFDataset::DefVarDeflate(int nVarId, bool bChunkingArg)
9971 : {
9972 266 : if (eCompress == NCDF_COMPRESS_DEFLATE)
9973 : {
9974 : // Must set chunk size to avoid huge performance hit (set
9975 : // bChunkingArg=TRUE)
9976 : // perhaps another solution it to change the chunk cache?
9977 : // http://www.unidata.ucar.edu/software/netcdf/docs/netcdf.html#Chunk-Cache
9978 : // TODO: make sure this is okay.
9979 1 : CPLDebug("GDAL_netCDF", "DefVarDeflate(%d, %d) nZlevel=%d", nVarId,
9980 : static_cast<int>(bChunkingArg), nZLevel);
9981 :
9982 1 : int status = nc_def_var_deflate(cdfid, nVarId, 1, 1, nZLevel);
9983 1 : NCDF_ERR(status);
9984 :
9985 1 : if (status == NC_NOERR && bChunkingArg && bChunking)
9986 : {
9987 : // set chunking to be 1 for all dims, except X dim
9988 : // size_t chunksize[] = { 1, (size_t)nRasterXSize };
9989 : size_t chunksize[MAX_NC_DIMS];
9990 : int nd;
9991 1 : nc_inq_varndims(cdfid, nVarId, &nd);
9992 1 : chunksize[0] = (size_t)1;
9993 1 : chunksize[1] = (size_t)1;
9994 1 : for (int i = 2; i < nd; i++)
9995 0 : chunksize[i] = (size_t)1;
9996 1 : chunksize[nd - 1] = (size_t)nRasterXSize;
9997 :
9998 : // Config options just for testing purposes
9999 : const char *pszBlockXSize =
10000 1 : CPLGetConfigOption("BLOCKXSIZE", nullptr);
10001 1 : if (pszBlockXSize)
10002 0 : chunksize[nd - 1] = (size_t)atoi(pszBlockXSize);
10003 :
10004 : const char *pszBlockYSize =
10005 1 : CPLGetConfigOption("BLOCKYSIZE", nullptr);
10006 1 : if (nd >= 2 && pszBlockYSize)
10007 0 : chunksize[nd - 2] = (size_t)atoi(pszBlockYSize);
10008 :
10009 1 : CPLDebug("GDAL_netCDF",
10010 : "DefVarDeflate() chunksize={%ld, %ld} chunkX=%ld nd=%d",
10011 1 : (long)chunksize[0], (long)chunksize[1],
10012 1 : (long)chunksize[nd - 1], nd);
10013 : #ifdef NCDF_DEBUG
10014 : for (int i = 0; i < nd; i++)
10015 : CPLDebug("GDAL_netCDF", "DefVarDeflate() chunk[%d]=%ld", i,
10016 : chunksize[i]);
10017 : #endif
10018 :
10019 1 : status = nc_def_var_chunking(cdfid, nVarId, NC_CHUNKED, chunksize);
10020 1 : NCDF_ERR(status);
10021 : }
10022 : else
10023 : {
10024 0 : CPLDebug("GDAL_netCDF", "chunksize not set");
10025 : }
10026 1 : return status;
10027 : }
10028 265 : return NC_NOERR;
10029 : }
10030 :
10031 : /************************************************************************/
10032 : /* NCDFUnloadDriver() */
10033 : /************************************************************************/
10034 :
10035 7 : static void NCDFUnloadDriver(CPL_UNUSED GDALDriver *poDriver)
10036 : {
10037 7 : if (hNCMutex != nullptr)
10038 4 : CPLDestroyMutex(hNCMutex);
10039 7 : hNCMutex = nullptr;
10040 7 : }
10041 :
10042 : /************************************************************************/
10043 : /* GDALRegister_netCDF() */
10044 : /************************************************************************/
10045 :
10046 : class GDALnetCDFDriver final : public GDALDriver
10047 : {
10048 : public:
10049 12 : GDALnetCDFDriver() = default;
10050 :
10051 964 : const char *GetMetadataItem(const char *pszName,
10052 : const char *pszDomain) override
10053 : {
10054 964 : if (EQUAL(pszName, GDAL_DCAP_VIRTUALIO))
10055 : {
10056 13 : InitializeDCAPVirtualIO();
10057 : }
10058 964 : return GDALDriver::GetMetadataItem(pszName, pszDomain);
10059 : }
10060 :
10061 77 : char **GetMetadata(const char *pszDomain) override
10062 : {
10063 77 : InitializeDCAPVirtualIO();
10064 77 : return GDALDriver::GetMetadata(pszDomain);
10065 : }
10066 :
10067 : private:
10068 : bool m_bInitialized = false;
10069 :
10070 90 : void InitializeDCAPVirtualIO()
10071 : {
10072 90 : if (!m_bInitialized)
10073 : {
10074 9 : m_bInitialized = true;
10075 :
10076 : #ifdef ENABLE_UFFD
10077 9 : if (CPLIsUserFaultMappingSupported())
10078 : {
10079 9 : SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
10080 : }
10081 : #endif
10082 : }
10083 90 : }
10084 : };
10085 :
10086 12 : void GDALRegister_netCDF()
10087 :
10088 : {
10089 12 : if (!GDAL_CHECK_VERSION("netCDF driver"))
10090 0 : return;
10091 :
10092 12 : if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
10093 0 : return;
10094 :
10095 12 : GDALDriver *poDriver = new GDALnetCDFDriver();
10096 12 : netCDFDriverSetCommonMetadata(poDriver);
10097 :
10098 12 : poDriver->SetMetadataItem("NETCDF_CONVENTIONS",
10099 12 : GDAL_DEFAULT_NCDF_CONVENTIONS);
10100 12 : poDriver->SetMetadataItem("NETCDF_VERSION", nc_inq_libvers());
10101 :
10102 : // Set pfns and register driver.
10103 12 : poDriver->pfnOpen = netCDFDataset::Open;
10104 12 : poDriver->pfnCreateCopy = netCDFDataset::CreateCopy;
10105 12 : poDriver->pfnCreate = netCDFDataset::Create;
10106 12 : poDriver->pfnCreateMultiDimensional = netCDFDataset::CreateMultiDimensional;
10107 12 : poDriver->pfnUnloadDriver = NCDFUnloadDriver;
10108 :
10109 12 : GetGDALDriverManager()->RegisterDriver(poDriver);
10110 : }
10111 :
10112 : /************************************************************************/
10113 : /* New functions */
10114 : /************************************************************************/
10115 :
10116 : /* Test for GDAL version string >= target */
10117 238 : static bool NCDFIsGDALVersionGTE(const char *pszVersion, int nTarget)
10118 : {
10119 :
10120 : // Valid strings are "GDAL 1.9dev, released 2011/01/18" and "GDAL 1.8.1 ".
10121 238 : if (pszVersion == nullptr || EQUAL(pszVersion, ""))
10122 0 : return false;
10123 238 : else if (!STARTS_WITH_CI(pszVersion, "GDAL "))
10124 0 : return false;
10125 : // 2.0dev of 2011/12/29 has been later renamed as 1.10dev.
10126 238 : else if (EQUAL("GDAL 2.0dev, released 2011/12/29", pszVersion))
10127 0 : return nTarget <= GDAL_COMPUTE_VERSION(1, 10, 0);
10128 238 : else if (STARTS_WITH_CI(pszVersion, "GDAL 1.9dev"))
10129 2 : return nTarget <= 1900;
10130 236 : else if (STARTS_WITH_CI(pszVersion, "GDAL 1.8dev"))
10131 0 : return nTarget <= 1800;
10132 :
10133 236 : char **papszTokens = CSLTokenizeString2(pszVersion + 5, ".", 0);
10134 :
10135 236 : int nVersions[] = {0, 0, 0, 0};
10136 944 : for (int iToken = 0; papszTokens && iToken < 4 && papszTokens[iToken];
10137 : iToken++)
10138 : {
10139 708 : nVersions[iToken] = atoi(papszTokens[iToken]);
10140 708 : if (nVersions[iToken] < 0)
10141 0 : nVersions[iToken] = 0;
10142 708 : else if (nVersions[iToken] > 99)
10143 0 : nVersions[iToken] = 99;
10144 : }
10145 :
10146 236 : int nVersion = 0;
10147 236 : if (nVersions[0] > 1 || nVersions[1] >= 10)
10148 236 : nVersion =
10149 236 : GDAL_COMPUTE_VERSION(nVersions[0], nVersions[1], nVersions[2]);
10150 : else
10151 0 : nVersion = nVersions[0] * 1000 + nVersions[1] * 100 +
10152 0 : nVersions[2] * 10 + nVersions[3];
10153 :
10154 236 : CSLDestroy(papszTokens);
10155 236 : return nTarget <= nVersion;
10156 : }
10157 :
10158 : // Add Conventions, GDAL version and history.
10159 160 : static void NCDFAddGDALHistory(int fpImage, const char *pszFilename,
10160 : bool bWriteGDALVersion, bool bWriteGDALHistory,
10161 : const char *pszOldHist,
10162 : const char *pszFunctionName,
10163 : const char *pszCFVersion)
10164 : {
10165 160 : if (pszCFVersion == nullptr)
10166 : {
10167 36 : pszCFVersion = GDAL_DEFAULT_NCDF_CONVENTIONS;
10168 : }
10169 160 : int status = nc_put_att_text(fpImage, NC_GLOBAL, "Conventions",
10170 : strlen(pszCFVersion), pszCFVersion);
10171 160 : NCDF_ERR(status);
10172 :
10173 160 : if (bWriteGDALVersion)
10174 : {
10175 159 : const char *pszNCDF_GDAL = GDALVersionInfo("--version");
10176 159 : status = nc_put_att_text(fpImage, NC_GLOBAL, "GDAL",
10177 : strlen(pszNCDF_GDAL), pszNCDF_GDAL);
10178 159 : NCDF_ERR(status);
10179 : }
10180 :
10181 160 : if (bWriteGDALHistory)
10182 : {
10183 : // Add history.
10184 318 : CPLString osTmp;
10185 : #ifdef GDAL_SET_CMD_LINE_DEFINED_TMP
10186 : if (!EQUAL(GDALGetCmdLine(), ""))
10187 : osTmp = GDALGetCmdLine();
10188 : else
10189 : osTmp =
10190 : CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
10191 : #else
10192 159 : osTmp = CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
10193 : #endif
10194 :
10195 159 : NCDFAddHistory(fpImage, osTmp.c_str(), pszOldHist);
10196 : }
10197 1 : else if (pszOldHist != nullptr)
10198 : {
10199 0 : status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
10200 : strlen(pszOldHist), pszOldHist);
10201 0 : NCDF_ERR(status);
10202 : }
10203 160 : }
10204 :
10205 : // Code taken from cdo and libcdi, used for writing the history attribute.
10206 :
10207 : // void cdoDefHistory(int fileID, char *histstring)
10208 159 : static void NCDFAddHistory(int fpImage, const char *pszAddHist,
10209 : const char *pszOldHist)
10210 : {
10211 : // Check pszOldHist - as if there was no previous history, it will be
10212 : // a null pointer - if so set as empty.
10213 159 : if (nullptr == pszOldHist)
10214 : {
10215 48 : pszOldHist = "";
10216 : }
10217 :
10218 : char strtime[32];
10219 159 : strtime[0] = '\0';
10220 :
10221 159 : time_t tp = time(nullptr);
10222 159 : if (tp != -1)
10223 : {
10224 : struct tm ltime;
10225 159 : VSILocalTime(&tp, <ime);
10226 159 : (void)strftime(strtime, sizeof(strtime),
10227 : "%a %b %d %H:%M:%S %Y: ", <ime);
10228 : }
10229 :
10230 : // status = nc_get_att_text(fpImage, NC_GLOBAL,
10231 : // "history", pszOldHist);
10232 : // printf("status: %d pszOldHist: [%s]\n",status,pszOldHist);
10233 :
10234 159 : size_t nNewHistSize =
10235 159 : strlen(pszOldHist) + strlen(strtime) + strlen(pszAddHist) + 1 + 1;
10236 : char *pszNewHist =
10237 159 : static_cast<char *>(CPLMalloc(nNewHistSize * sizeof(char)));
10238 :
10239 159 : strcpy(pszNewHist, strtime);
10240 159 : strcat(pszNewHist, pszAddHist);
10241 :
10242 : // int disableHistory = FALSE;
10243 : // if( !disableHistory )
10244 : {
10245 159 : if (!EQUAL(pszOldHist, ""))
10246 3 : strcat(pszNewHist, "\n");
10247 159 : strcat(pszNewHist, pszOldHist);
10248 : }
10249 :
10250 159 : const int status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
10251 : strlen(pszNewHist), pszNewHist);
10252 159 : NCDF_ERR(status);
10253 :
10254 159 : CPLFree(pszNewHist);
10255 159 : }
10256 :
10257 5814 : static CPLErr NCDFSafeStrcat(char **ppszDest, const char *pszSrc,
10258 : size_t *nDestSize)
10259 : {
10260 : /* Reallocate the data string until the content fits */
10261 5814 : while (*nDestSize < (strlen(*ppszDest) + strlen(pszSrc) + 1))
10262 : {
10263 405 : (*nDestSize) *= 2;
10264 405 : *ppszDest = static_cast<char *>(
10265 405 : CPLRealloc(reinterpret_cast<void *>(*ppszDest), *nDestSize));
10266 : #ifdef NCDF_DEBUG
10267 : CPLDebug("GDAL_netCDF", "NCDFSafeStrcat() resized str from %ld to %ld",
10268 : (*nDestSize) / 2, *nDestSize);
10269 : #endif
10270 : }
10271 5409 : strcat(*ppszDest, pszSrc);
10272 :
10273 5409 : return CE_None;
10274 : }
10275 :
10276 : /* helper function for NCDFGetAttr() */
10277 : /* if pdfValue != nullptr, sets *pdfValue to first value returned */
10278 : /* if ppszValue != nullptr, sets *ppszValue with all attribute values */
10279 : /* *ppszValue is the responsibility of the caller and must be freed */
10280 61527 : static CPLErr NCDFGetAttr1(int nCdfId, int nVarId, const char *pszAttrName,
10281 : double *pdfValue, char **ppszValue)
10282 : {
10283 61527 : nc_type nAttrType = NC_NAT;
10284 61527 : size_t nAttrLen = 0;
10285 :
10286 61527 : if (ppszValue)
10287 60389 : *ppszValue = nullptr;
10288 :
10289 61527 : int status = nc_inq_att(nCdfId, nVarId, pszAttrName, &nAttrType, &nAttrLen);
10290 61527 : if (status != NC_NOERR)
10291 33461 : return CE_Failure;
10292 :
10293 : #ifdef NCDF_DEBUG
10294 : CPLDebug("GDAL_netCDF", "NCDFGetAttr1(%s) len=%ld type=%d", pszAttrName,
10295 : nAttrLen, nAttrType);
10296 : #endif
10297 28066 : if (nAttrLen == 0 && nAttrType != NC_CHAR)
10298 1 : return CE_Failure;
10299 :
10300 : /* Allocate guaranteed minimum size (use 10 or 20 if not a string) */
10301 28065 : size_t nAttrValueSize = nAttrLen + 1;
10302 28065 : if (nAttrType != NC_CHAR && nAttrValueSize < 10)
10303 3081 : nAttrValueSize = 10;
10304 28065 : if (nAttrType == NC_DOUBLE && nAttrValueSize < 20)
10305 1528 : nAttrValueSize = 20;
10306 28065 : if (nAttrType == NC_INT64 && nAttrValueSize < 20)
10307 24 : nAttrValueSize = 22;
10308 : char *pszAttrValue =
10309 28065 : static_cast<char *>(CPLCalloc(nAttrValueSize, sizeof(char)));
10310 28065 : *pszAttrValue = '\0';
10311 :
10312 28065 : if (nAttrLen > 1 && nAttrType != NC_CHAR)
10313 530 : NCDFSafeStrcat(&pszAttrValue, "{", &nAttrValueSize);
10314 :
10315 28065 : double dfValue = 0.0;
10316 : size_t m;
10317 : char szTemp[256];
10318 28065 : bool bSetDoubleFromStr = false;
10319 :
10320 28065 : switch (nAttrType)
10321 : {
10322 24982 : case NC_CHAR:
10323 24982 : CPL_IGNORE_RET_VAL(
10324 24982 : nc_get_att_text(nCdfId, nVarId, pszAttrName, pszAttrValue));
10325 24982 : pszAttrValue[nAttrLen] = '\0';
10326 24982 : bSetDoubleFromStr = true;
10327 24982 : dfValue = 0.0;
10328 24982 : break;
10329 88 : case NC_BYTE:
10330 : {
10331 : signed char *pscTemp = static_cast<signed char *>(
10332 88 : CPLCalloc(nAttrLen, sizeof(signed char)));
10333 88 : nc_get_att_schar(nCdfId, nVarId, pszAttrName, pscTemp);
10334 88 : dfValue = static_cast<double>(pscTemp[0]);
10335 99 : for (m = 0; m < nAttrLen - 1; m++)
10336 : {
10337 11 : snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
10338 11 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10339 : }
10340 88 : snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
10341 88 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10342 88 : CPLFree(pscTemp);
10343 88 : break;
10344 : }
10345 435 : case NC_SHORT:
10346 : {
10347 : short *psTemp =
10348 435 : static_cast<short *>(CPLCalloc(nAttrLen, sizeof(short)));
10349 435 : nc_get_att_short(nCdfId, nVarId, pszAttrName, psTemp);
10350 435 : dfValue = static_cast<double>(psTemp[0]);
10351 767 : for (m = 0; m < nAttrLen - 1; m++)
10352 : {
10353 332 : snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
10354 332 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10355 : }
10356 435 : snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
10357 435 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10358 435 : CPLFree(psTemp);
10359 435 : break;
10360 : }
10361 530 : case NC_INT:
10362 : {
10363 530 : int *pnTemp = static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
10364 530 : nc_get_att_int(nCdfId, nVarId, pszAttrName, pnTemp);
10365 530 : dfValue = static_cast<double>(pnTemp[0]);
10366 667 : for (m = 0; m < nAttrLen - 1; m++)
10367 : {
10368 137 : snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
10369 137 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10370 : }
10371 530 : snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
10372 530 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10373 530 : CPLFree(pnTemp);
10374 530 : break;
10375 : }
10376 371 : case NC_FLOAT:
10377 : {
10378 : float *pfTemp =
10379 371 : static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
10380 371 : nc_get_att_float(nCdfId, nVarId, pszAttrName, pfTemp);
10381 371 : dfValue = static_cast<double>(pfTemp[0]);
10382 399 : for (m = 0; m < nAttrLen - 1; m++)
10383 : {
10384 28 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
10385 28 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10386 : }
10387 371 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
10388 371 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10389 371 : CPLFree(pfTemp);
10390 371 : break;
10391 : }
10392 1528 : case NC_DOUBLE:
10393 : {
10394 : double *pdfTemp =
10395 1528 : static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
10396 1528 : nc_get_att_double(nCdfId, nVarId, pszAttrName, pdfTemp);
10397 1528 : dfValue = pdfTemp[0];
10398 1616 : for (m = 0; m < nAttrLen - 1; m++)
10399 : {
10400 88 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
10401 88 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10402 : }
10403 1528 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
10404 1528 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10405 1528 : CPLFree(pdfTemp);
10406 1528 : break;
10407 : }
10408 8 : case NC_STRING:
10409 : {
10410 : char **ppszTemp =
10411 8 : static_cast<char **>(CPLCalloc(nAttrLen, sizeof(char *)));
10412 8 : nc_get_att_string(nCdfId, nVarId, pszAttrName, ppszTemp);
10413 8 : bSetDoubleFromStr = true;
10414 8 : dfValue = 0.0;
10415 18 : for (m = 0; m < nAttrLen - 1; m++)
10416 : {
10417 10 : NCDFSafeStrcat(&pszAttrValue,
10418 10 : ppszTemp[m] ? ppszTemp[m] : "{NULL}",
10419 : &nAttrValueSize);
10420 10 : NCDFSafeStrcat(&pszAttrValue, ",", &nAttrValueSize);
10421 : }
10422 8 : NCDFSafeStrcat(&pszAttrValue, ppszTemp[m] ? ppszTemp[m] : "{NULL}",
10423 : &nAttrValueSize);
10424 8 : nc_free_string(nAttrLen, ppszTemp);
10425 8 : CPLFree(ppszTemp);
10426 8 : break;
10427 : }
10428 26 : case NC_UBYTE:
10429 : {
10430 : unsigned char *pucTemp = static_cast<unsigned char *>(
10431 26 : CPLCalloc(nAttrLen, sizeof(unsigned char)));
10432 26 : nc_get_att_uchar(nCdfId, nVarId, pszAttrName, pucTemp);
10433 26 : dfValue = static_cast<double>(pucTemp[0]);
10434 26 : for (m = 0; m < nAttrLen - 1; m++)
10435 : {
10436 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
10437 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10438 : }
10439 26 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
10440 26 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10441 26 : CPLFree(pucTemp);
10442 26 : break;
10443 : }
10444 30 : case NC_USHORT:
10445 : {
10446 : unsigned short *pusTemp;
10447 : pusTemp = static_cast<unsigned short *>(
10448 30 : CPLCalloc(nAttrLen, sizeof(unsigned short)));
10449 30 : nc_get_att_ushort(nCdfId, nVarId, pszAttrName, pusTemp);
10450 30 : dfValue = static_cast<double>(pusTemp[0]);
10451 35 : for (m = 0; m < nAttrLen - 1; m++)
10452 : {
10453 5 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
10454 5 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10455 : }
10456 30 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
10457 30 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10458 30 : CPLFree(pusTemp);
10459 30 : break;
10460 : }
10461 19 : case NC_UINT:
10462 : {
10463 : unsigned int *punTemp =
10464 19 : static_cast<unsigned int *>(CPLCalloc(nAttrLen, sizeof(int)));
10465 19 : nc_get_att_uint(nCdfId, nVarId, pszAttrName, punTemp);
10466 19 : dfValue = static_cast<double>(punTemp[0]);
10467 19 : for (m = 0; m < nAttrLen - 1; m++)
10468 : {
10469 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
10470 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10471 : }
10472 19 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
10473 19 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10474 19 : CPLFree(punTemp);
10475 19 : break;
10476 : }
10477 24 : case NC_INT64:
10478 : {
10479 : GIntBig *panTemp =
10480 24 : static_cast<GIntBig *>(CPLCalloc(nAttrLen, sizeof(GIntBig)));
10481 24 : nc_get_att_longlong(nCdfId, nVarId, pszAttrName, panTemp);
10482 24 : dfValue = static_cast<double>(panTemp[0]);
10483 24 : for (m = 0; m < nAttrLen - 1; m++)
10484 : {
10485 0 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",",
10486 0 : panTemp[m]);
10487 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10488 : }
10489 24 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, panTemp[m]);
10490 24 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10491 24 : CPLFree(panTemp);
10492 24 : break;
10493 : }
10494 24 : case NC_UINT64:
10495 : {
10496 : GUIntBig *panTemp =
10497 24 : static_cast<GUIntBig *>(CPLCalloc(nAttrLen, sizeof(GUIntBig)));
10498 24 : nc_get_att_ulonglong(nCdfId, nVarId, pszAttrName, panTemp);
10499 24 : dfValue = static_cast<double>(panTemp[0]);
10500 24 : for (m = 0; m < nAttrLen - 1; m++)
10501 : {
10502 0 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",",
10503 0 : panTemp[m]);
10504 0 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10505 : }
10506 24 : CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, panTemp[m]);
10507 24 : NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10508 24 : CPLFree(panTemp);
10509 24 : break;
10510 : }
10511 0 : default:
10512 0 : CPLDebug("GDAL_netCDF",
10513 : "NCDFGetAttr unsupported type %d for attribute %s",
10514 : nAttrType, pszAttrName);
10515 0 : break;
10516 : }
10517 :
10518 28065 : if (nAttrLen > 1 && nAttrType != NC_CHAR)
10519 530 : NCDFSafeStrcat(&pszAttrValue, "}", &nAttrValueSize);
10520 :
10521 28065 : if (bSetDoubleFromStr)
10522 : {
10523 24990 : if (CPLGetValueType(pszAttrValue) == CPL_VALUE_STRING)
10524 : {
10525 24808 : if (ppszValue == nullptr && pdfValue != nullptr)
10526 : {
10527 1 : CPLFree(pszAttrValue);
10528 1 : return CE_Failure;
10529 : }
10530 : }
10531 24989 : dfValue = CPLAtof(pszAttrValue);
10532 : }
10533 :
10534 : /* set return values */
10535 28064 : if (ppszValue)
10536 27757 : *ppszValue = pszAttrValue;
10537 : else
10538 307 : CPLFree(pszAttrValue);
10539 :
10540 28064 : if (pdfValue)
10541 307 : *pdfValue = dfValue;
10542 :
10543 28064 : return CE_None;
10544 : }
10545 :
10546 : /* sets pdfValue to first value found */
10547 1138 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
10548 : double *pdfValue)
10549 : {
10550 1138 : return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, pdfValue, nullptr);
10551 : }
10552 :
10553 : /* pszValue is the responsibility of the caller and must be freed */
10554 60389 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
10555 : char **pszValue)
10556 : {
10557 60389 : return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, nullptr, pszValue);
10558 : }
10559 :
10560 : /* By default write NC_CHAR, but detect for int/float/double and */
10561 : /* NC4 string arrays */
10562 100 : static CPLErr NCDFPutAttr(int nCdfId, int nVarId, const char *pszAttrName,
10563 : const char *pszValue)
10564 : {
10565 100 : int status = 0;
10566 100 : char *pszTemp = nullptr;
10567 :
10568 : /* get the attribute values as tokens */
10569 100 : char **papszValues = NCDFTokenizeArray(pszValue);
10570 100 : if (papszValues == nullptr)
10571 0 : return CE_Failure;
10572 :
10573 100 : size_t nAttrLen = CSLCount(papszValues);
10574 :
10575 : /* first detect type */
10576 100 : nc_type nAttrType = NC_CHAR;
10577 100 : nc_type nTmpAttrType = NC_CHAR;
10578 213 : for (size_t i = 0; i < nAttrLen; i++)
10579 : {
10580 113 : nTmpAttrType = NC_CHAR;
10581 113 : bool bFoundType = false;
10582 113 : errno = 0;
10583 113 : int nValue = static_cast<int>(strtol(papszValues[i], &pszTemp, 10));
10584 : /* test for int */
10585 : /* TODO test for Byte and short - can this be done safely? */
10586 113 : if (errno == 0 && papszValues[i] != pszTemp && *pszTemp == 0)
10587 : {
10588 : char szTemp[256];
10589 19 : CPLsnprintf(szTemp, sizeof(szTemp), "%d", nValue);
10590 19 : if (EQUAL(szTemp, papszValues[i]))
10591 : {
10592 19 : bFoundType = true;
10593 19 : nTmpAttrType = NC_INT;
10594 : }
10595 : else
10596 : {
10597 : unsigned int unValue = static_cast<unsigned int>(
10598 0 : strtoul(papszValues[i], &pszTemp, 10));
10599 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", unValue);
10600 0 : if (EQUAL(szTemp, papszValues[i]))
10601 : {
10602 0 : bFoundType = true;
10603 0 : nTmpAttrType = NC_UINT;
10604 : }
10605 : }
10606 : }
10607 113 : if (!bFoundType)
10608 : {
10609 : /* test for double */
10610 94 : errno = 0;
10611 94 : double dfValue = CPLStrtod(papszValues[i], &pszTemp);
10612 94 : if ((errno == 0) && (papszValues[i] != pszTemp) && (*pszTemp == 0))
10613 : {
10614 : // Test for float instead of double.
10615 : // strtof() is C89, which is not available in MSVC.
10616 : // See if we loose precision if we cast to float and write to
10617 : // char*.
10618 14 : float fValue = float(dfValue);
10619 : char szTemp[256];
10620 14 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", fValue);
10621 14 : if (EQUAL(szTemp, papszValues[i]))
10622 8 : nTmpAttrType = NC_FLOAT;
10623 : else
10624 6 : nTmpAttrType = NC_DOUBLE;
10625 : }
10626 : }
10627 113 : if ((nTmpAttrType <= NC_DOUBLE && nAttrType <= NC_DOUBLE &&
10628 93 : nTmpAttrType > nAttrType) ||
10629 93 : (nTmpAttrType == NC_UINT && nAttrType < NC_FLOAT) ||
10630 5 : (nTmpAttrType >= NC_FLOAT && nAttrType == NC_UINT))
10631 20 : nAttrType = nTmpAttrType;
10632 : }
10633 :
10634 : #ifdef DEBUG
10635 100 : if (EQUAL(pszAttrName, "DEBUG_EMPTY_DOUBLE_ATTR"))
10636 : {
10637 0 : nAttrType = NC_DOUBLE;
10638 0 : nAttrLen = 0;
10639 : }
10640 : #endif
10641 :
10642 : /* now write the data */
10643 100 : if (nAttrType == NC_CHAR)
10644 : {
10645 80 : int nTmpFormat = 0;
10646 80 : if (nAttrLen > 1)
10647 : {
10648 0 : status = nc_inq_format(nCdfId, &nTmpFormat);
10649 0 : NCDF_ERR(status);
10650 : }
10651 80 : if (nAttrLen > 1 && nTmpFormat == NCDF_FORMAT_NC4)
10652 0 : status = nc_put_att_string(nCdfId, nVarId, pszAttrName, nAttrLen,
10653 : const_cast<const char **>(papszValues));
10654 : else
10655 80 : status = nc_put_att_text(nCdfId, nVarId, pszAttrName,
10656 : strlen(pszValue), pszValue);
10657 80 : NCDF_ERR(status);
10658 : }
10659 : else
10660 : {
10661 20 : switch (nAttrType)
10662 : {
10663 11 : case NC_INT:
10664 : {
10665 : int *pnTemp =
10666 11 : static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
10667 30 : for (size_t i = 0; i < nAttrLen; i++)
10668 : {
10669 19 : pnTemp[i] =
10670 19 : static_cast<int>(strtol(papszValues[i], &pszTemp, 10));
10671 : }
10672 11 : status = nc_put_att_int(nCdfId, nVarId, pszAttrName, NC_INT,
10673 : nAttrLen, pnTemp);
10674 11 : NCDF_ERR(status);
10675 11 : CPLFree(pnTemp);
10676 11 : break;
10677 : }
10678 0 : case NC_UINT:
10679 : {
10680 : unsigned int *punTemp = static_cast<unsigned int *>(
10681 0 : CPLCalloc(nAttrLen, sizeof(unsigned int)));
10682 0 : for (size_t i = 0; i < nAttrLen; i++)
10683 : {
10684 0 : punTemp[i] = static_cast<unsigned int>(
10685 0 : strtol(papszValues[i], &pszTemp, 10));
10686 : }
10687 0 : status = nc_put_att_uint(nCdfId, nVarId, pszAttrName, NC_UINT,
10688 : nAttrLen, punTemp);
10689 0 : NCDF_ERR(status);
10690 0 : CPLFree(punTemp);
10691 0 : break;
10692 : }
10693 6 : case NC_FLOAT:
10694 : {
10695 : float *pfTemp =
10696 6 : static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
10697 14 : for (size_t i = 0; i < nAttrLen; i++)
10698 : {
10699 8 : pfTemp[i] =
10700 8 : static_cast<float>(CPLStrtod(papszValues[i], &pszTemp));
10701 : }
10702 6 : status = nc_put_att_float(nCdfId, nVarId, pszAttrName, NC_FLOAT,
10703 : nAttrLen, pfTemp);
10704 6 : NCDF_ERR(status);
10705 6 : CPLFree(pfTemp);
10706 6 : break;
10707 : }
10708 3 : case NC_DOUBLE:
10709 : {
10710 : double *pdfTemp =
10711 3 : static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
10712 9 : for (size_t i = 0; i < nAttrLen; i++)
10713 : {
10714 6 : pdfTemp[i] = CPLStrtod(papszValues[i], &pszTemp);
10715 : }
10716 3 : status = nc_put_att_double(nCdfId, nVarId, pszAttrName,
10717 : NC_DOUBLE, nAttrLen, pdfTemp);
10718 3 : NCDF_ERR(status);
10719 3 : CPLFree(pdfTemp);
10720 3 : break;
10721 : }
10722 0 : default:
10723 0 : if (papszValues)
10724 0 : CSLDestroy(papszValues);
10725 0 : return CE_Failure;
10726 : break;
10727 : }
10728 : }
10729 :
10730 100 : if (papszValues)
10731 100 : CSLDestroy(papszValues);
10732 :
10733 100 : return CE_None;
10734 : }
10735 :
10736 69 : static CPLErr NCDFGet1DVar(int nCdfId, int nVarId, char **pszValue)
10737 : {
10738 : /* get var information */
10739 69 : int nVarDimId = -1;
10740 69 : int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
10741 69 : if (status != NC_NOERR || nVarDimId != 1)
10742 0 : return CE_Failure;
10743 :
10744 69 : status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
10745 69 : if (status != NC_NOERR)
10746 0 : return CE_Failure;
10747 :
10748 69 : nc_type nVarType = NC_NAT;
10749 69 : status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
10750 69 : if (status != NC_NOERR)
10751 0 : return CE_Failure;
10752 :
10753 69 : size_t nVarLen = 0;
10754 69 : status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
10755 69 : if (status != NC_NOERR)
10756 0 : return CE_Failure;
10757 :
10758 69 : size_t start[1] = {0};
10759 69 : size_t count[1] = {nVarLen};
10760 :
10761 : /* Allocate guaranteed minimum size */
10762 69 : size_t nVarValueSize = NCDF_MAX_STR_LEN;
10763 : char *pszVarValue =
10764 69 : static_cast<char *>(CPLCalloc(nVarValueSize, sizeof(char)));
10765 69 : *pszVarValue = '\0';
10766 :
10767 69 : if (nVarLen == 0)
10768 : {
10769 : /* set return values */
10770 1 : *pszValue = pszVarValue;
10771 :
10772 1 : return CE_None;
10773 : }
10774 :
10775 68 : if (nVarLen > 1 && nVarType != NC_CHAR)
10776 36 : NCDFSafeStrcat(&pszVarValue, "{", &nVarValueSize);
10777 :
10778 68 : switch (nVarType)
10779 : {
10780 0 : case NC_CHAR:
10781 0 : nc_get_vara_text(nCdfId, nVarId, start, count, pszVarValue);
10782 0 : pszVarValue[nVarLen] = '\0';
10783 0 : break;
10784 0 : case NC_BYTE:
10785 : {
10786 : signed char *pscTemp = static_cast<signed char *>(
10787 0 : CPLCalloc(nVarLen, sizeof(signed char)));
10788 0 : nc_get_vara_schar(nCdfId, nVarId, start, count, pscTemp);
10789 : char szTemp[256];
10790 0 : size_t m = 0;
10791 0 : for (; m < nVarLen - 1; m++)
10792 : {
10793 0 : snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
10794 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10795 : }
10796 0 : snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
10797 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10798 0 : CPLFree(pscTemp);
10799 0 : break;
10800 : }
10801 0 : case NC_SHORT:
10802 : {
10803 : short *psTemp =
10804 0 : static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
10805 0 : nc_get_vara_short(nCdfId, nVarId, start, count, psTemp);
10806 : char szTemp[256];
10807 0 : size_t m = 0;
10808 0 : for (; m < nVarLen - 1; m++)
10809 : {
10810 0 : snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
10811 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10812 : }
10813 0 : snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
10814 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10815 0 : CPLFree(psTemp);
10816 0 : break;
10817 : }
10818 17 : case NC_INT:
10819 : {
10820 17 : int *pnTemp = static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
10821 17 : nc_get_vara_int(nCdfId, nVarId, start, count, pnTemp);
10822 : char szTemp[256];
10823 17 : size_t m = 0;
10824 28 : for (; m < nVarLen - 1; m++)
10825 : {
10826 11 : snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
10827 11 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10828 : }
10829 17 : snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
10830 17 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10831 17 : CPLFree(pnTemp);
10832 17 : break;
10833 : }
10834 7 : case NC_FLOAT:
10835 : {
10836 : float *pfTemp =
10837 7 : static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
10838 7 : nc_get_vara_float(nCdfId, nVarId, start, count, pfTemp);
10839 : char szTemp[256];
10840 7 : size_t m = 0;
10841 324 : for (; m < nVarLen - 1; m++)
10842 : {
10843 317 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
10844 317 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10845 : }
10846 7 : CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
10847 7 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10848 7 : CPLFree(pfTemp);
10849 7 : break;
10850 : }
10851 43 : case NC_DOUBLE:
10852 : {
10853 : double *pdfTemp =
10854 43 : static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
10855 43 : nc_get_vara_double(nCdfId, nVarId, start, count, pdfTemp);
10856 : char szTemp[256];
10857 43 : size_t m = 0;
10858 219 : for (; m < nVarLen - 1; m++)
10859 : {
10860 176 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
10861 176 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10862 : }
10863 43 : CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
10864 43 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10865 43 : CPLFree(pdfTemp);
10866 43 : break;
10867 : }
10868 0 : case NC_STRING:
10869 : {
10870 : char **ppszTemp =
10871 0 : static_cast<char **>(CPLCalloc(nVarLen, sizeof(char *)));
10872 0 : nc_get_vara_string(nCdfId, nVarId, start, count, ppszTemp);
10873 0 : size_t m = 0;
10874 0 : for (; m < nVarLen - 1; m++)
10875 : {
10876 0 : NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
10877 0 : NCDFSafeStrcat(&pszVarValue, ",", &nVarValueSize);
10878 : }
10879 0 : NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
10880 0 : nc_free_string(nVarLen, ppszTemp);
10881 0 : CPLFree(ppszTemp);
10882 0 : break;
10883 : }
10884 0 : case NC_UBYTE:
10885 : {
10886 : unsigned char *pucTemp;
10887 : pucTemp = static_cast<unsigned char *>(
10888 0 : CPLCalloc(nVarLen, sizeof(unsigned char)));
10889 0 : nc_get_vara_uchar(nCdfId, nVarId, start, count, pucTemp);
10890 : char szTemp[256];
10891 0 : size_t m = 0;
10892 0 : for (; m < nVarLen - 1; m++)
10893 : {
10894 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
10895 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10896 : }
10897 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
10898 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10899 0 : CPLFree(pucTemp);
10900 0 : break;
10901 : }
10902 0 : case NC_USHORT:
10903 : {
10904 : unsigned short *pusTemp;
10905 : pusTemp = static_cast<unsigned short *>(
10906 0 : CPLCalloc(nVarLen, sizeof(unsigned short)));
10907 0 : nc_get_vara_ushort(nCdfId, nVarId, start, count, pusTemp);
10908 : char szTemp[256];
10909 0 : size_t m = 0;
10910 0 : for (; m < nVarLen - 1; m++)
10911 : {
10912 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
10913 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10914 : }
10915 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
10916 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10917 0 : CPLFree(pusTemp);
10918 0 : break;
10919 : }
10920 0 : case NC_UINT:
10921 : {
10922 : unsigned int *punTemp;
10923 : punTemp = static_cast<unsigned int *>(
10924 0 : CPLCalloc(nVarLen, sizeof(unsigned int)));
10925 0 : nc_get_vara_uint(nCdfId, nVarId, start, count, punTemp);
10926 : char szTemp[256];
10927 0 : size_t m = 0;
10928 0 : for (; m < nVarLen - 1; m++)
10929 : {
10930 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
10931 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10932 : }
10933 0 : CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
10934 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10935 0 : CPLFree(punTemp);
10936 0 : break;
10937 : }
10938 1 : case NC_INT64:
10939 : {
10940 : long long *pnTemp =
10941 1 : static_cast<long long *>(CPLCalloc(nVarLen, sizeof(long long)));
10942 1 : nc_get_vara_longlong(nCdfId, nVarId, start, count, pnTemp);
10943 : char szTemp[256];
10944 1 : size_t m = 0;
10945 2 : for (; m < nVarLen - 1; m++)
10946 : {
10947 1 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",", pnTemp[m]);
10948 1 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10949 : }
10950 1 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, pnTemp[m]);
10951 1 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10952 1 : CPLFree(pnTemp);
10953 1 : break;
10954 : }
10955 0 : case NC_UINT64:
10956 : {
10957 : unsigned long long *pnTemp = static_cast<unsigned long long *>(
10958 0 : CPLCalloc(nVarLen, sizeof(unsigned long long)));
10959 0 : nc_get_vara_ulonglong(nCdfId, nVarId, start, count, pnTemp);
10960 : char szTemp[256];
10961 0 : size_t m = 0;
10962 0 : for (; m < nVarLen - 1; m++)
10963 : {
10964 0 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",", pnTemp[m]);
10965 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10966 : }
10967 0 : snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, pnTemp[m]);
10968 0 : NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10969 0 : CPLFree(pnTemp);
10970 0 : break;
10971 : }
10972 0 : default:
10973 0 : CPLDebug("GDAL_netCDF", "NCDFGetVar1D unsupported type %d",
10974 : nVarType);
10975 0 : CPLFree(pszVarValue);
10976 0 : pszVarValue = nullptr;
10977 0 : break;
10978 : }
10979 :
10980 68 : if (pszVarValue != nullptr && nVarLen > 1 && nVarType != NC_CHAR)
10981 36 : NCDFSafeStrcat(&pszVarValue, "}", &nVarValueSize);
10982 :
10983 : /* set return values */
10984 68 : *pszValue = pszVarValue;
10985 :
10986 68 : return CE_None;
10987 : }
10988 :
10989 7 : static CPLErr NCDFPut1DVar(int nCdfId, int nVarId, const char *pszValue)
10990 : {
10991 7 : if (EQUAL(pszValue, ""))
10992 0 : return CE_Failure;
10993 :
10994 : /* get var information */
10995 7 : int nVarDimId = -1;
10996 7 : int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
10997 7 : if (status != NC_NOERR || nVarDimId != 1)
10998 0 : return CE_Failure;
10999 :
11000 7 : status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
11001 7 : if (status != NC_NOERR)
11002 0 : return CE_Failure;
11003 :
11004 7 : nc_type nVarType = NC_CHAR;
11005 7 : status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
11006 7 : if (status != NC_NOERR)
11007 0 : return CE_Failure;
11008 :
11009 7 : size_t nVarLen = 0;
11010 7 : status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
11011 7 : if (status != NC_NOERR)
11012 0 : return CE_Failure;
11013 :
11014 7 : size_t start[1] = {0};
11015 7 : size_t count[1] = {nVarLen};
11016 :
11017 : /* get the values as tokens */
11018 7 : char **papszValues = NCDFTokenizeArray(pszValue);
11019 7 : if (papszValues == nullptr)
11020 0 : return CE_Failure;
11021 :
11022 7 : nVarLen = CSLCount(papszValues);
11023 :
11024 : /* now write the data */
11025 7 : if (nVarType == NC_CHAR)
11026 : {
11027 0 : status = nc_put_vara_text(nCdfId, nVarId, start, count, pszValue);
11028 0 : NCDF_ERR(status);
11029 : }
11030 : else
11031 : {
11032 7 : switch (nVarType)
11033 : {
11034 0 : case NC_BYTE:
11035 : {
11036 : signed char *pscTemp = static_cast<signed char *>(
11037 0 : CPLCalloc(nVarLen, sizeof(signed char)));
11038 0 : for (size_t i = 0; i < nVarLen; i++)
11039 : {
11040 0 : char *pszTemp = nullptr;
11041 0 : pscTemp[i] = static_cast<signed char>(
11042 0 : strtol(papszValues[i], &pszTemp, 10));
11043 : }
11044 : status =
11045 0 : nc_put_vara_schar(nCdfId, nVarId, start, count, pscTemp);
11046 0 : NCDF_ERR(status);
11047 0 : CPLFree(pscTemp);
11048 0 : break;
11049 : }
11050 0 : case NC_SHORT:
11051 : {
11052 : short *psTemp =
11053 0 : static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
11054 0 : for (size_t i = 0; i < nVarLen; i++)
11055 : {
11056 0 : char *pszTemp = nullptr;
11057 0 : psTemp[i] = static_cast<short>(
11058 0 : strtol(papszValues[i], &pszTemp, 10));
11059 : }
11060 : status =
11061 0 : nc_put_vara_short(nCdfId, nVarId, start, count, psTemp);
11062 0 : NCDF_ERR(status);
11063 0 : CPLFree(psTemp);
11064 0 : break;
11065 : }
11066 2 : case NC_INT:
11067 : {
11068 : int *pnTemp =
11069 2 : static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
11070 6 : for (size_t i = 0; i < nVarLen; i++)
11071 : {
11072 4 : char *pszTemp = nullptr;
11073 4 : pnTemp[i] =
11074 4 : static_cast<int>(strtol(papszValues[i], &pszTemp, 10));
11075 : }
11076 2 : status = nc_put_vara_int(nCdfId, nVarId, start, count, pnTemp);
11077 2 : NCDF_ERR(status);
11078 2 : CPLFree(pnTemp);
11079 2 : break;
11080 : }
11081 0 : case NC_FLOAT:
11082 : {
11083 : float *pfTemp =
11084 0 : static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
11085 0 : for (size_t i = 0; i < nVarLen; i++)
11086 : {
11087 0 : char *pszTemp = nullptr;
11088 0 : pfTemp[i] =
11089 0 : static_cast<float>(CPLStrtod(papszValues[i], &pszTemp));
11090 : }
11091 : status =
11092 0 : nc_put_vara_float(nCdfId, nVarId, start, count, pfTemp);
11093 0 : NCDF_ERR(status);
11094 0 : CPLFree(pfTemp);
11095 0 : break;
11096 : }
11097 5 : case NC_DOUBLE:
11098 : {
11099 : double *pdfTemp =
11100 5 : static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
11101 19 : for (size_t i = 0; i < nVarLen; i++)
11102 : {
11103 14 : char *pszTemp = nullptr;
11104 14 : pdfTemp[i] = CPLStrtod(papszValues[i], &pszTemp);
11105 : }
11106 : status =
11107 5 : nc_put_vara_double(nCdfId, nVarId, start, count, pdfTemp);
11108 5 : NCDF_ERR(status);
11109 5 : CPLFree(pdfTemp);
11110 5 : break;
11111 : }
11112 0 : default:
11113 : {
11114 0 : int nTmpFormat = 0;
11115 0 : status = nc_inq_format(nCdfId, &nTmpFormat);
11116 0 : NCDF_ERR(status);
11117 0 : if (nTmpFormat == NCDF_FORMAT_NC4)
11118 : {
11119 0 : switch (nVarType)
11120 : {
11121 0 : case NC_STRING:
11122 : {
11123 : status =
11124 0 : nc_put_vara_string(nCdfId, nVarId, start, count,
11125 : (const char **)papszValues);
11126 0 : NCDF_ERR(status);
11127 0 : break;
11128 : }
11129 0 : case NC_UBYTE:
11130 : {
11131 : unsigned char *pucTemp =
11132 : static_cast<unsigned char *>(
11133 0 : CPLCalloc(nVarLen, sizeof(unsigned char)));
11134 0 : for (size_t i = 0; i < nVarLen; i++)
11135 : {
11136 0 : char *pszTemp = nullptr;
11137 0 : pucTemp[i] = static_cast<unsigned char>(
11138 0 : strtoul(papszValues[i], &pszTemp, 10));
11139 : }
11140 0 : status = nc_put_vara_uchar(nCdfId, nVarId, start,
11141 : count, pucTemp);
11142 0 : NCDF_ERR(status);
11143 0 : CPLFree(pucTemp);
11144 0 : break;
11145 : }
11146 0 : case NC_USHORT:
11147 : {
11148 : unsigned short *pusTemp =
11149 : static_cast<unsigned short *>(
11150 0 : CPLCalloc(nVarLen, sizeof(unsigned short)));
11151 0 : for (size_t i = 0; i < nVarLen; i++)
11152 : {
11153 0 : char *pszTemp = nullptr;
11154 0 : pusTemp[i] = static_cast<unsigned short>(
11155 0 : strtoul(papszValues[i], &pszTemp, 10));
11156 : }
11157 0 : status = nc_put_vara_ushort(nCdfId, nVarId, start,
11158 : count, pusTemp);
11159 0 : NCDF_ERR(status);
11160 0 : CPLFree(pusTemp);
11161 0 : break;
11162 : }
11163 0 : case NC_UINT:
11164 : {
11165 : unsigned int *punTemp = static_cast<unsigned int *>(
11166 0 : CPLCalloc(nVarLen, sizeof(unsigned int)));
11167 0 : for (size_t i = 0; i < nVarLen; i++)
11168 : {
11169 0 : char *pszTemp = nullptr;
11170 0 : punTemp[i] = static_cast<unsigned int>(
11171 0 : strtoul(papszValues[i], &pszTemp, 10));
11172 : }
11173 0 : status = nc_put_vara_uint(nCdfId, nVarId, start,
11174 : count, punTemp);
11175 0 : NCDF_ERR(status);
11176 0 : CPLFree(punTemp);
11177 0 : break;
11178 : }
11179 0 : default:
11180 0 : if (papszValues)
11181 0 : CSLDestroy(papszValues);
11182 0 : return CE_Failure;
11183 : break;
11184 : }
11185 : }
11186 0 : break;
11187 : }
11188 : }
11189 : }
11190 :
11191 7 : if (papszValues)
11192 7 : CSLDestroy(papszValues);
11193 :
11194 7 : return CE_None;
11195 : }
11196 :
11197 : /************************************************************************/
11198 : /* GetDefaultNoDataValue() */
11199 : /************************************************************************/
11200 :
11201 186 : double NCDFGetDefaultNoDataValue(int nCdfId, int nVarId, int nVarType,
11202 : bool &bGotNoData)
11203 :
11204 : {
11205 186 : int nNoFill = 0;
11206 186 : double dfNoData = 0.0;
11207 :
11208 186 : switch (nVarType)
11209 : {
11210 0 : case NC_CHAR:
11211 : case NC_BYTE:
11212 : case NC_UBYTE:
11213 : // Don't do default fill-values for bytes, too risky.
11214 : // This function should not be called in those cases.
11215 0 : CPLAssert(false);
11216 : break;
11217 24 : case NC_SHORT:
11218 : {
11219 24 : short nFillVal = 0;
11220 24 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11221 : NC_NOERR)
11222 : {
11223 24 : if (!nNoFill)
11224 : {
11225 23 : bGotNoData = true;
11226 23 : dfNoData = nFillVal;
11227 : }
11228 : }
11229 : else
11230 0 : dfNoData = NC_FILL_SHORT;
11231 24 : break;
11232 : }
11233 25 : case NC_INT:
11234 : {
11235 25 : int nFillVal = 0;
11236 25 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11237 : NC_NOERR)
11238 : {
11239 25 : if (!nNoFill)
11240 : {
11241 25 : bGotNoData = true;
11242 25 : dfNoData = nFillVal;
11243 : }
11244 : }
11245 : else
11246 0 : dfNoData = NC_FILL_INT;
11247 25 : break;
11248 : }
11249 70 : case NC_FLOAT:
11250 : {
11251 70 : float fFillVal = 0;
11252 70 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &fFillVal) ==
11253 : NC_NOERR)
11254 : {
11255 70 : if (!nNoFill)
11256 : {
11257 66 : bGotNoData = true;
11258 66 : dfNoData = fFillVal;
11259 : }
11260 : }
11261 : else
11262 0 : dfNoData = NC_FILL_FLOAT;
11263 70 : break;
11264 : }
11265 34 : case NC_DOUBLE:
11266 : {
11267 34 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &dfNoData) ==
11268 : NC_NOERR)
11269 : {
11270 34 : if (!nNoFill)
11271 : {
11272 34 : bGotNoData = true;
11273 : }
11274 : }
11275 : else
11276 0 : dfNoData = NC_FILL_DOUBLE;
11277 34 : break;
11278 : }
11279 7 : case NC_USHORT:
11280 : {
11281 7 : unsigned short nFillVal = 0;
11282 7 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11283 : NC_NOERR)
11284 : {
11285 7 : if (!nNoFill)
11286 : {
11287 7 : bGotNoData = true;
11288 7 : dfNoData = nFillVal;
11289 : }
11290 : }
11291 : else
11292 0 : dfNoData = NC_FILL_USHORT;
11293 7 : break;
11294 : }
11295 7 : case NC_UINT:
11296 : {
11297 7 : unsigned int nFillVal = 0;
11298 7 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11299 : NC_NOERR)
11300 : {
11301 7 : if (!nNoFill)
11302 : {
11303 7 : bGotNoData = true;
11304 7 : dfNoData = nFillVal;
11305 : }
11306 : }
11307 : else
11308 0 : dfNoData = NC_FILL_UINT;
11309 7 : break;
11310 : }
11311 19 : default:
11312 19 : dfNoData = 0.0;
11313 19 : break;
11314 : }
11315 :
11316 186 : return dfNoData;
11317 : }
11318 :
11319 : /************************************************************************/
11320 : /* NCDFGetDefaultNoDataValueAsInt64() */
11321 : /************************************************************************/
11322 :
11323 2 : int64_t NCDFGetDefaultNoDataValueAsInt64(int nCdfId, int nVarId,
11324 : bool &bGotNoData)
11325 :
11326 : {
11327 2 : int nNoFill = 0;
11328 2 : long long nFillVal = 0;
11329 2 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
11330 : {
11331 2 : if (!nNoFill)
11332 : {
11333 2 : bGotNoData = true;
11334 2 : return static_cast<int64_t>(nFillVal);
11335 : }
11336 : }
11337 : else
11338 0 : return static_cast<int64_t>(NC_FILL_INT64);
11339 0 : return 0;
11340 : }
11341 :
11342 : /************************************************************************/
11343 : /* NCDFGetDefaultNoDataValueAsUInt64() */
11344 : /************************************************************************/
11345 :
11346 1 : uint64_t NCDFGetDefaultNoDataValueAsUInt64(int nCdfId, int nVarId,
11347 : bool &bGotNoData)
11348 :
11349 : {
11350 1 : int nNoFill = 0;
11351 1 : unsigned long long nFillVal = 0;
11352 1 : if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
11353 : {
11354 1 : if (!nNoFill)
11355 : {
11356 1 : bGotNoData = true;
11357 1 : return static_cast<uint64_t>(nFillVal);
11358 : }
11359 : }
11360 : else
11361 0 : return static_cast<uint64_t>(NC_FILL_UINT64);
11362 0 : return 0;
11363 : }
11364 :
11365 10801 : static int NCDFDoesVarContainAttribVal(int nCdfId,
11366 : const char *const *papszAttribNames,
11367 : const char *const *papszAttribValues,
11368 : int nVarId, const char *pszVarName,
11369 : bool bStrict = true)
11370 : {
11371 10801 : if (nVarId == -1 && pszVarName != nullptr)
11372 7942 : NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
11373 :
11374 10801 : if (nVarId == -1)
11375 898 : return -1;
11376 :
11377 9903 : bool bFound = false;
11378 46309 : for (int i = 0; !bFound && papszAttribNames != nullptr &&
11379 44170 : papszAttribNames[i] != nullptr;
11380 : i++)
11381 : {
11382 36406 : char *pszTemp = nullptr;
11383 36406 : if (NCDFGetAttr(nCdfId, nVarId, papszAttribNames[i], &pszTemp) ==
11384 52057 : CE_None &&
11385 15651 : pszTemp != nullptr)
11386 : {
11387 15651 : if (bStrict)
11388 : {
11389 15651 : if (EQUAL(pszTemp, papszAttribValues[i]))
11390 2139 : bFound = true;
11391 : }
11392 : else
11393 : {
11394 0 : if (EQUALN(pszTemp, papszAttribValues[i],
11395 : strlen(papszAttribValues[i])))
11396 0 : bFound = true;
11397 : }
11398 15651 : CPLFree(pszTemp);
11399 : }
11400 : }
11401 9903 : return bFound;
11402 : }
11403 :
11404 1926 : static int NCDFDoesVarContainAttribVal2(int nCdfId, const char *papszAttribName,
11405 : const char *const *papszAttribValues,
11406 : int nVarId, const char *pszVarName,
11407 : int bStrict = true)
11408 : {
11409 1926 : if (nVarId == -1 && pszVarName != nullptr)
11410 1552 : NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
11411 :
11412 1926 : if (nVarId == -1)
11413 0 : return -1;
11414 :
11415 1926 : bool bFound = false;
11416 1926 : char *pszTemp = nullptr;
11417 2283 : if (NCDFGetAttr(nCdfId, nVarId, papszAttribName, &pszTemp) != CE_None ||
11418 357 : pszTemp == nullptr)
11419 1569 : return FALSE;
11420 :
11421 7239 : for (int i = 0; !bFound && i < CSLCount(papszAttribValues); i++)
11422 : {
11423 6882 : if (bStrict)
11424 : {
11425 6854 : if (EQUAL(pszTemp, papszAttribValues[i]))
11426 30 : bFound = true;
11427 : }
11428 : else
11429 : {
11430 28 : if (EQUALN(pszTemp, papszAttribValues[i],
11431 : strlen(papszAttribValues[i])))
11432 0 : bFound = true;
11433 : }
11434 : }
11435 :
11436 357 : CPLFree(pszTemp);
11437 :
11438 357 : return bFound;
11439 : }
11440 :
11441 896 : static bool NCDFEqual(const char *papszName, const char *const *papszValues)
11442 : {
11443 896 : if (papszName == nullptr || EQUAL(papszName, ""))
11444 0 : return false;
11445 :
11446 2448 : for (int i = 0; papszValues && papszValues[i]; ++i)
11447 : {
11448 1674 : if (EQUAL(papszName, papszValues[i]))
11449 122 : return true;
11450 : }
11451 :
11452 774 : return false;
11453 : }
11454 :
11455 : // Test that a variable is longitude/latitude coordinate,
11456 : // following CF 4.1 and 4.2.
11457 3639 : bool NCDFIsVarLongitude(int nCdfId, int nVarId, const char *pszVarName)
11458 : {
11459 : // Check for matching attributes.
11460 3639 : int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLongitudeAttribNames,
11461 : papszCFLongitudeAttribValues, nVarId,
11462 : pszVarName);
11463 : // If not found using attributes then check using var name
11464 : // unless GDAL_NETCDF_VERIFY_DIMS=STRICT.
11465 3639 : if (bVal == -1)
11466 : {
11467 288 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11468 : "STRICT"))
11469 288 : bVal = NCDFEqual(pszVarName, papszCFLongitudeVarNames);
11470 : else
11471 0 : bVal = FALSE;
11472 : }
11473 3351 : else if (bVal)
11474 : {
11475 : // Check that the units is not 'm' or '1'. See #6759
11476 737 : char *pszTemp = nullptr;
11477 1093 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11478 356 : pszTemp != nullptr)
11479 : {
11480 356 : if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
11481 97 : bVal = false;
11482 356 : CPLFree(pszTemp);
11483 : }
11484 : }
11485 :
11486 3639 : return CPL_TO_BOOL(bVal);
11487 : }
11488 :
11489 2103 : bool NCDFIsVarLatitude(int nCdfId, int nVarId, const char *pszVarName)
11490 : {
11491 2103 : int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLatitudeAttribNames,
11492 : papszCFLatitudeAttribValues, nVarId,
11493 : pszVarName);
11494 2103 : if (bVal == -1)
11495 : {
11496 165 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11497 : "STRICT"))
11498 165 : bVal = NCDFEqual(pszVarName, papszCFLatitudeVarNames);
11499 : else
11500 0 : bVal = FALSE;
11501 : }
11502 1938 : else if (bVal)
11503 : {
11504 : // Check that the units is not 'm' or '1'. See #6759
11505 507 : char *pszTemp = nullptr;
11506 641 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11507 134 : pszTemp != nullptr)
11508 : {
11509 134 : if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
11510 36 : bVal = false;
11511 134 : CPLFree(pszTemp);
11512 : }
11513 : }
11514 :
11515 2103 : return CPL_TO_BOOL(bVal);
11516 : }
11517 :
11518 2259 : bool NCDFIsVarProjectionX(int nCdfId, int nVarId, const char *pszVarName)
11519 : {
11520 2259 : int bVal = NCDFDoesVarContainAttribVal(
11521 : nCdfId, papszCFProjectionXAttribNames, papszCFProjectionXAttribValues,
11522 : nVarId, pszVarName);
11523 2259 : if (bVal == -1)
11524 : {
11525 282 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11526 : "STRICT"))
11527 282 : bVal = NCDFEqual(pszVarName, papszCFProjectionXVarNames);
11528 : else
11529 0 : bVal = FALSE;
11530 : }
11531 1977 : else if (bVal)
11532 : {
11533 : // Check that the units is not '1'
11534 364 : char *pszTemp = nullptr;
11535 518 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11536 154 : pszTemp != nullptr)
11537 : {
11538 154 : if (EQUAL(pszTemp, "1"))
11539 5 : bVal = false;
11540 154 : CPLFree(pszTemp);
11541 : }
11542 : }
11543 :
11544 2259 : return CPL_TO_BOOL(bVal);
11545 : }
11546 :
11547 1607 : bool NCDFIsVarProjectionY(int nCdfId, int nVarId, const char *pszVarName)
11548 : {
11549 1607 : int bVal = NCDFDoesVarContainAttribVal(
11550 : nCdfId, papszCFProjectionYAttribNames, papszCFProjectionYAttribValues,
11551 : nVarId, pszVarName);
11552 1607 : if (bVal == -1)
11553 : {
11554 161 : if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11555 : "STRICT"))
11556 161 : bVal = NCDFEqual(pszVarName, papszCFProjectionYVarNames);
11557 : else
11558 0 : bVal = FALSE;
11559 : }
11560 1446 : else if (bVal)
11561 : {
11562 : // Check that the units is not '1'
11563 363 : char *pszTemp = nullptr;
11564 516 : if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11565 153 : pszTemp != nullptr)
11566 : {
11567 153 : if (EQUAL(pszTemp, "1"))
11568 5 : bVal = false;
11569 153 : CPLFree(pszTemp);
11570 : }
11571 : }
11572 :
11573 1607 : return CPL_TO_BOOL(bVal);
11574 : }
11575 :
11576 : /* test that a variable is a vertical coordinate, following CF 4.3 */
11577 1003 : bool NCDFIsVarVerticalCoord(int nCdfId, int nVarId, const char *pszVarName)
11578 : {
11579 : /* check for matching attributes */
11580 1003 : if (NCDFDoesVarContainAttribVal(nCdfId, papszCFVerticalAttribNames,
11581 : papszCFVerticalAttribValues, nVarId,
11582 1003 : pszVarName))
11583 70 : return true;
11584 : /* check for matching units */
11585 933 : else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
11586 : papszCFVerticalUnitsValues, nVarId,
11587 933 : pszVarName))
11588 30 : return true;
11589 : /* check for matching standard name */
11590 903 : else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_STD_NAME,
11591 : papszCFVerticalStandardNameValues,
11592 903 : nVarId, pszVarName))
11593 0 : return true;
11594 : else
11595 903 : return false;
11596 : }
11597 :
11598 : /* test that a variable is a time coordinate, following CF 4.4 */
11599 190 : bool NCDFIsVarTimeCoord(int nCdfId, int nVarId, const char *pszVarName)
11600 : {
11601 : /* check for matching attributes */
11602 190 : if (NCDFDoesVarContainAttribVal(nCdfId, papszCFTimeAttribNames,
11603 : papszCFTimeAttribValues, nVarId,
11604 190 : pszVarName))
11605 100 : return true;
11606 : /* check for matching units */
11607 90 : else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
11608 : papszCFTimeUnitsValues, nVarId,
11609 90 : pszVarName, false))
11610 0 : return true;
11611 : else
11612 90 : return false;
11613 : }
11614 :
11615 : // Parse a string, and return as a string list.
11616 : // If it an array of the form {a,b}, then tokenize it.
11617 : // Otherwise, return a copy.
11618 173 : static char **NCDFTokenizeArray(const char *pszValue)
11619 : {
11620 173 : if (pszValue == nullptr || EQUAL(pszValue, ""))
11621 48 : return nullptr;
11622 :
11623 125 : char **papszValues = nullptr;
11624 125 : const int nLen = static_cast<int>(strlen(pszValue));
11625 :
11626 125 : if (pszValue[0] == '{' && nLen > 2 && pszValue[nLen - 1] == '}')
11627 : {
11628 37 : char *pszTemp = static_cast<char *>(CPLMalloc((nLen - 2) + 1));
11629 37 : strncpy(pszTemp, pszValue + 1, nLen - 2);
11630 37 : pszTemp[nLen - 2] = '\0';
11631 37 : papszValues = CSLTokenizeString2(pszTemp, ",", CSLT_ALLOWEMPTYTOKENS);
11632 37 : CPLFree(pszTemp);
11633 : }
11634 : else
11635 : {
11636 88 : papszValues = reinterpret_cast<char **>(CPLCalloc(2, sizeof(char *)));
11637 88 : papszValues[0] = CPLStrdup(pszValue);
11638 88 : papszValues[1] = nullptr;
11639 : }
11640 :
11641 125 : return papszValues;
11642 : }
11643 :
11644 : // Open a NetCDF subdataset from full path /group1/group2/.../groupn/var.
11645 : // Leading slash is optional.
11646 399 : static CPLErr NCDFOpenSubDataset(int nCdfId, const char *pszSubdatasetName,
11647 : int *pnGroupId, int *pnVarId)
11648 : {
11649 399 : *pnGroupId = -1;
11650 399 : *pnVarId = -1;
11651 :
11652 : // Open group.
11653 399 : char *pszGroupFullName = CPLStrdup(CPLGetPath(pszSubdatasetName));
11654 : // Add a leading slash if needed.
11655 399 : if (pszGroupFullName[0] != '/')
11656 : {
11657 385 : char *old = pszGroupFullName;
11658 385 : pszGroupFullName = CPLStrdup(CPLSPrintf("/%s", pszGroupFullName));
11659 385 : CPLFree(old);
11660 : }
11661 : // Detect root group.
11662 399 : if (EQUAL(pszGroupFullName, "/"))
11663 : {
11664 385 : *pnGroupId = nCdfId;
11665 385 : CPLFree(pszGroupFullName);
11666 : }
11667 : else
11668 : {
11669 14 : int status = nc_inq_grp_full_ncid(nCdfId, pszGroupFullName, pnGroupId);
11670 14 : CPLFree(pszGroupFullName);
11671 14 : NCDF_ERR_RET(status);
11672 : }
11673 :
11674 : // Open var.
11675 399 : const char *pszVarName = CPLGetFilename(pszSubdatasetName);
11676 399 : NCDF_ERR_RET(nc_inq_varid(*pnGroupId, pszVarName, pnVarId));
11677 :
11678 399 : return CE_None;
11679 : }
11680 :
11681 : // Get all dimensions visible from a given NetCDF (or group) ID and any of
11682 : // its parents.
11683 342 : static CPLErr NCDFGetVisibleDims(int nGroupId, int *pnDims, int **ppanDimIds)
11684 : {
11685 342 : int nDims = 0;
11686 342 : int *panDimIds = nullptr;
11687 342 : NCDF_ERR_RET(nc_inq_dimids(nGroupId, &nDims, nullptr, true));
11688 :
11689 342 : panDimIds = static_cast<int *>(CPLMalloc(nDims * sizeof(int)));
11690 :
11691 342 : int status = nc_inq_dimids(nGroupId, nullptr, panDimIds, true);
11692 342 : if (status != NC_NOERR)
11693 0 : CPLFree(panDimIds);
11694 342 : NCDF_ERR_RET(status);
11695 :
11696 342 : *pnDims = nDims;
11697 342 : *ppanDimIds = panDimIds;
11698 :
11699 342 : return CE_None;
11700 : }
11701 :
11702 : // Get direct sub-groups IDs of a given NetCDF (or group) ID.
11703 : // Consider only direct children, does not get children of children.
11704 2994 : static CPLErr NCDFGetSubGroups(int nGroupId, int *pnSubGroups,
11705 : int **ppanSubGroupIds)
11706 : {
11707 2994 : *pnSubGroups = 0;
11708 2994 : *ppanSubGroupIds = nullptr;
11709 :
11710 : int nSubGroups;
11711 2994 : NCDF_ERR_RET(nc_inq_grps(nGroupId, &nSubGroups, nullptr));
11712 : int *panSubGroupIds =
11713 2994 : static_cast<int *>(CPLMalloc(nSubGroups * sizeof(int)));
11714 2994 : NCDF_ERR_RET(nc_inq_grps(nGroupId, nullptr, panSubGroupIds));
11715 2994 : *pnSubGroups = nSubGroups;
11716 2994 : *ppanSubGroupIds = panSubGroupIds;
11717 :
11718 2994 : return CE_None;
11719 : }
11720 :
11721 : // Get the full name of a given NetCDF (or group) ID
11722 : // (e.g. /group1/group2/.../groupn).
11723 : // bNC3Compat remove the leading slash for top-level variables for
11724 : // backward compatibility (top-level variables are the ones in the root group).
11725 14788 : static CPLErr NCDFGetGroupFullName(int nGroupId, char **ppszFullName,
11726 : bool bNC3Compat)
11727 : {
11728 14788 : *ppszFullName = nullptr;
11729 :
11730 : size_t nFullNameLen;
11731 14788 : NCDF_ERR_RET(nc_inq_grpname_len(nGroupId, &nFullNameLen));
11732 14788 : *ppszFullName =
11733 14788 : static_cast<char *>(CPLMalloc((nFullNameLen + 1) * sizeof(char)));
11734 14788 : int status = nc_inq_grpname_full(nGroupId, &nFullNameLen, *ppszFullName);
11735 14788 : if (status != NC_NOERR)
11736 : {
11737 0 : CPLFree(*ppszFullName);
11738 0 : *ppszFullName = nullptr;
11739 0 : NCDF_ERR_RET(status);
11740 : }
11741 :
11742 14788 : if (bNC3Compat && EQUAL(*ppszFullName, "/"))
11743 7721 : (*ppszFullName)[0] = '\0';
11744 :
11745 14788 : return CE_None;
11746 : }
11747 :
11748 6896 : CPLString NCDFGetGroupFullName(int nGroupId)
11749 : {
11750 6896 : char *pszFullname = nullptr;
11751 6896 : NCDFGetGroupFullName(nGroupId, &pszFullname, false);
11752 6896 : CPLString osRet(pszFullname ? pszFullname : "");
11753 6896 : CPLFree(pszFullname);
11754 13792 : return osRet;
11755 : }
11756 :
11757 : // Get the full name of a given NetCDF variable ID
11758 : // (e.g. /group1/group2/.../groupn/var).
11759 : // Handle also NC_GLOBAL as nVarId.
11760 : // bNC3Compat remove the leading slash for top-level variables for
11761 : // backward compatibility (top-level variables are the ones in the root group).
11762 7843 : static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId, char **ppszFullName,
11763 : bool bNC3Compat)
11764 : {
11765 7843 : *ppszFullName = nullptr;
11766 7843 : char *pszGroupFullName = nullptr;
11767 7843 : ERR_RET(NCDFGetGroupFullName(nGroupId, &pszGroupFullName, bNC3Compat));
11768 : char szVarName[NC_MAX_NAME + 1];
11769 7843 : if (nVarId == NC_GLOBAL)
11770 : {
11771 1056 : strcpy(szVarName, "NC_GLOBAL");
11772 : }
11773 : else
11774 : {
11775 6787 : int status = nc_inq_varname(nGroupId, nVarId, szVarName);
11776 6787 : if (status != NC_NOERR)
11777 : {
11778 0 : CPLFree(pszGroupFullName);
11779 0 : NCDF_ERR_RET(status);
11780 : }
11781 : }
11782 7843 : const char *pszSep = "/";
11783 7843 : if (EQUAL(pszGroupFullName, "/") || EQUAL(pszGroupFullName, ""))
11784 7674 : pszSep = "";
11785 7843 : *ppszFullName =
11786 7843 : CPLStrdup(CPLSPrintf("%s%s%s", pszGroupFullName, pszSep, szVarName));
11787 7843 : CPLFree(pszGroupFullName);
11788 7843 : return CE_None;
11789 : }
11790 :
11791 : // Get the NetCDF root group ID of a given group ID.
11792 0 : static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId)
11793 : {
11794 0 : *pnRootGroupId = -1;
11795 : // Recurse on parent group.
11796 : int nParentGroupId;
11797 0 : int status = nc_inq_grp_parent(nStartGroupId, &nParentGroupId);
11798 0 : if (status == NC_NOERR)
11799 0 : return NCDFGetRootGroup(nParentGroupId, pnRootGroupId);
11800 0 : else if (status != NC_ENOGRP)
11801 0 : NCDF_ERR_RET(status);
11802 : else // No more parent group.
11803 : {
11804 0 : *pnRootGroupId = nStartGroupId;
11805 : }
11806 :
11807 0 : return CE_None;
11808 : }
11809 :
11810 : // Implementation of NCDFResolveVar/Att.
11811 12830 : static CPLErr NCDFResolveElem(int nStartGroupId, const char *pszVar,
11812 : const char *pszAtt, int *pnGroupId, int *pnId,
11813 : bool bMandatory)
11814 : {
11815 12830 : if (!pszVar && !pszAtt)
11816 : {
11817 0 : CPLError(CE_Failure, CPLE_IllegalArg,
11818 : "pszVar and pszAtt NCDFResolveElem() args are both null.");
11819 0 : return CE_Failure;
11820 : }
11821 :
11822 : enum
11823 : {
11824 : NCRM_PARENT,
11825 : NCRM_WIDTH_WISE
11826 12830 : } eNCResolveMode = NCRM_PARENT;
11827 :
11828 25660 : std::queue<int> aoQueueGroupIdsToVisit;
11829 12830 : aoQueueGroupIdsToVisit.push(nStartGroupId);
11830 :
11831 14489 : while (!aoQueueGroupIdsToVisit.empty())
11832 : {
11833 : // Get the first group of the FIFO queue.
11834 12961 : *pnGroupId = aoQueueGroupIdsToVisit.front();
11835 12961 : aoQueueGroupIdsToVisit.pop();
11836 :
11837 : // Look if this group contains the searched element.
11838 : int status;
11839 12961 : if (pszVar)
11840 12748 : status = nc_inq_varid(*pnGroupId, pszVar, pnId);
11841 : else // pszAtt != nullptr.
11842 213 : status = nc_inq_attid(*pnGroupId, NC_GLOBAL, pszAtt, pnId);
11843 :
11844 12961 : if (status == NC_NOERR)
11845 : {
11846 11302 : return CE_None;
11847 : }
11848 1659 : else if ((pszVar && status != NC_ENOTVAR) ||
11849 210 : (pszAtt && status != NC_ENOTATT))
11850 : {
11851 0 : NCDF_ERR(status);
11852 : }
11853 : // Element not found, in NC4 case we must search in other groups
11854 : // following the CF logic.
11855 :
11856 : // The first resolve mode consists to search on parent groups.
11857 1659 : if (eNCResolveMode == NCRM_PARENT)
11858 : {
11859 1571 : int nParentGroupId = -1;
11860 1571 : int status2 = nc_inq_grp_parent(*pnGroupId, &nParentGroupId);
11861 1571 : if (status2 == NC_NOERR)
11862 41 : aoQueueGroupIdsToVisit.push(nParentGroupId);
11863 1530 : else if (status2 != NC_ENOGRP)
11864 0 : NCDF_ERR(status2);
11865 1530 : else if (pszVar)
11866 : // When resolving a variable, if there is no more
11867 : // parent group then we switch to width-wise search mode
11868 : // starting from the latest found parent group.
11869 1323 : eNCResolveMode = NCRM_WIDTH_WISE;
11870 : }
11871 :
11872 : // The second resolve mode is a width-wise search.
11873 1659 : if (eNCResolveMode == NCRM_WIDTH_WISE)
11874 : {
11875 : // Enqueue all direct sub-groups.
11876 1411 : int nSubGroups = 0;
11877 1411 : int *panSubGroupIds = nullptr;
11878 1411 : NCDFGetSubGroups(*pnGroupId, &nSubGroups, &panSubGroupIds);
11879 1501 : for (int i = 0; i < nSubGroups; i++)
11880 90 : aoQueueGroupIdsToVisit.push(panSubGroupIds[i]);
11881 1411 : CPLFree(panSubGroupIds);
11882 : }
11883 : }
11884 :
11885 1528 : if (bMandatory)
11886 : {
11887 0 : char *pszStartGroupFullName = nullptr;
11888 0 : NCDFGetGroupFullName(nStartGroupId, &pszStartGroupFullName);
11889 0 : CPLError(CE_Failure, CPLE_AppDefined,
11890 : "Cannot resolve mandatory %s %s from group %s",
11891 : (pszVar ? pszVar : pszAtt),
11892 : (pszVar ? "variable" : "attribute"),
11893 0 : (pszStartGroupFullName ? pszStartGroupFullName : ""));
11894 0 : CPLFree(pszStartGroupFullName);
11895 : }
11896 :
11897 1528 : *pnGroupId = -1;
11898 1528 : *pnId = -1;
11899 1528 : return CE_Failure;
11900 : }
11901 :
11902 : // Resolve a variable name from a given starting group following the CF logic:
11903 : // - if var name is an absolute path then directly open it
11904 : // - first search in the starting group and its parent groups
11905 : // - then if there is no more parent group we switch to a width-wise search
11906 : // mode starting from the latest found parent group.
11907 : // The full CF logic is described here:
11908 : // https://github.com/diwg/cf2/blob/master/group/cf2-group.adoc#scope
11909 : // If bMandatory then print an error if resolving fails.
11910 : // TODO: implement support of relative paths.
11911 : // TODO: to follow strictly the CF logic, when searching for a coordinate
11912 : // variable, we must stop the parent search mode once the corresponding
11913 : // dimension is found and start the width-wise search from this group.
11914 : // TODO: to follow strictly the CF logic, when searching in width-wise mode
11915 : // we should skip every groups already visited during the parent
11916 : // search mode (but revisiting them should have no impact so we could
11917 : // let as it is if it is simpler...)
11918 : // TODO: CF specifies that the width-wise search order is "left-to-right" so
11919 : // maybe we must sort sibling groups alphabetically? but maybe not
11920 : // necessary if nc_inq_grps() already sort them?
11921 12620 : CPLErr NCDFResolveVar(int nStartGroupId, const char *pszVar, int *pnGroupId,
11922 : int *pnVarId, bool bMandatory)
11923 : {
11924 12620 : *pnGroupId = -1;
11925 12620 : *pnVarId = -1;
11926 12620 : int nGroupId = nStartGroupId, nVarId;
11927 12620 : if (pszVar[0] == '/')
11928 : {
11929 : // This is an absolute path: we can open the var directly.
11930 : int nRootGroupId;
11931 0 : ERR_RET(NCDFGetRootGroup(nStartGroupId, &nRootGroupId));
11932 0 : ERR_RET(NCDFOpenSubDataset(nRootGroupId, pszVar, &nGroupId, &nVarId));
11933 : }
11934 : else
11935 : {
11936 : // We have to search the variable following the CF logic.
11937 12620 : ERR_RET(NCDFResolveElem(nStartGroupId, pszVar, nullptr, &nGroupId,
11938 : &nVarId, bMandatory));
11939 : }
11940 11299 : *pnGroupId = nGroupId;
11941 11299 : *pnVarId = nVarId;
11942 11299 : return CE_None;
11943 : }
11944 :
11945 : // Like NCDFResolveVar but returns directly the var full name.
11946 1326 : static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,
11947 : char **ppszFullName, bool bMandatory)
11948 : {
11949 1326 : *ppszFullName = nullptr;
11950 : int nGroupId, nVarId;
11951 1326 : ERR_RET(
11952 : NCDFResolveVar(nStartGroupId, pszVar, &nGroupId, &nVarId, bMandatory));
11953 1308 : return NCDFGetVarFullName(nGroupId, nVarId, ppszFullName);
11954 : }
11955 :
11956 : // Like NCDFResolveVar but resolves an attribute instead a variable and
11957 : // returns its integer value.
11958 : // Only GLOBAL attributes are supported for the moment.
11959 210 : static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,
11960 : const char *pszAtt, int *pnAtt, bool bMandatory)
11961 : {
11962 210 : int nGroupId = nStartGroupId, nAttId = nStartVarId;
11963 210 : ERR_RET(NCDFResolveElem(nStartGroupId, nullptr, pszAtt, &nGroupId, &nAttId,
11964 : bMandatory));
11965 3 : NCDF_ERR_RET(nc_get_att_int(nGroupId, NC_GLOBAL, pszAtt, pnAtt));
11966 3 : return CE_None;
11967 : }
11968 :
11969 : // Filter variables to keep only valid 2+D raster bands and vector fields in
11970 : // a given a NetCDF (or group) ID and its sub-groups.
11971 : // Coordinate or boundary variables are ignored.
11972 : // It also creates corresponding vector layers.
11973 508 : CPLErr netCDFDataset::FilterVars(
11974 : int nCdfId, bool bKeepRasters, bool bKeepVectors, char **papszIgnoreVars,
11975 : int *pnRasterVars, int *pnGroupId, int *pnVarId, int *pnIgnoredVars,
11976 : std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>
11977 : &oMap2DDimsToGroupAndVar)
11978 : {
11979 508 : int nVars = 0;
11980 508 : int nRasterVars = 0;
11981 508 : NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
11982 :
11983 1016 : std::vector<int> anPotentialVectorVarID;
11984 : // oMapDimIdToCount[x] = number of times dim x is the first dimension of
11985 : // potential vector variables
11986 1016 : std::map<int, int> oMapDimIdToCount;
11987 508 : int nVarXId = -1;
11988 508 : int nVarYId = -1;
11989 508 : int nVarZId = -1;
11990 508 : int nVarTimeId = -1;
11991 508 : int nVarTimeDimId = -1;
11992 508 : bool bIsVectorOnly = true;
11993 508 : int nProfileDimId = -1;
11994 508 : int nParentIndexVarID = -1;
11995 :
11996 3154 : for (int v = 0; v < nVars; v++)
11997 : {
11998 : int nVarDims;
11999 2646 : NCDF_ERR_RET(nc_inq_varndims(nCdfId, v, &nVarDims));
12000 : // Should we ignore this variable?
12001 : char szTemp[NC_MAX_NAME + 1];
12002 2646 : szTemp[0] = '\0';
12003 2646 : NCDF_ERR_RET(nc_inq_varname(nCdfId, v, szTemp));
12004 :
12005 2646 : if (strstr(szTemp, "_node_coordinates") ||
12006 2646 : strstr(szTemp, "_node_count"))
12007 : {
12008 : // Ignore CF-1.8 Simple Geometries helper variables
12009 75 : continue;
12010 : }
12011 :
12012 3849 : if (nVarDims == 1 && (NCDFIsVarLongitude(nCdfId, -1, szTemp) ||
12013 1278 : NCDFIsVarProjectionX(nCdfId, -1, szTemp)))
12014 : {
12015 352 : nVarXId = v;
12016 : }
12017 3145 : else if (nVarDims == 1 && (NCDFIsVarLatitude(nCdfId, -1, szTemp) ||
12018 926 : NCDFIsVarProjectionY(nCdfId, -1, szTemp)))
12019 : {
12020 352 : nVarYId = v;
12021 : }
12022 1867 : else if (nVarDims == 1 && NCDFIsVarVerticalCoord(nCdfId, -1, szTemp))
12023 : {
12024 77 : nVarZId = v;
12025 : }
12026 : else
12027 : {
12028 1790 : char *pszVarFullName = nullptr;
12029 1790 : CPLErr eErr = NCDFGetVarFullName(nCdfId, v, &pszVarFullName);
12030 1790 : if (eErr != CE_None)
12031 : {
12032 0 : CPLFree(pszVarFullName);
12033 0 : continue;
12034 : }
12035 : bool bIgnoreVar =
12036 1790 : (CSLFindString(papszIgnoreVars, pszVarFullName) != -1);
12037 1790 : CPLFree(pszVarFullName);
12038 1790 : if (bIgnoreVar)
12039 : {
12040 98 : if (nVarDims == 1 && NCDFIsVarTimeCoord(nCdfId, -1, szTemp))
12041 : {
12042 7 : nVarTimeId = v;
12043 7 : nc_inq_vardimid(nCdfId, v, &nVarTimeDimId);
12044 : }
12045 91 : else if (nVarDims > 1)
12046 : {
12047 87 : (*pnIgnoredVars)++;
12048 87 : CPLDebug("GDAL_netCDF", "variable #%d [%s] was ignored", v,
12049 : szTemp);
12050 : }
12051 : }
12052 : // Only accept 2+D vars.
12053 1692 : else if (nVarDims >= 2)
12054 : {
12055 668 : bool bRasterCandidate = true;
12056 : // Identify variables that might be vector variables
12057 668 : if (nVarDims == 2)
12058 : {
12059 605 : int anDimIds[2] = {-1, -1};
12060 605 : nc_inq_vardimid(nCdfId, v, anDimIds);
12061 :
12062 605 : nc_type vartype = NC_NAT;
12063 605 : nc_inq_vartype(nCdfId, v, &vartype);
12064 :
12065 : char szDimNameFirst[NC_MAX_NAME + 1];
12066 : char szDimNameSecond[NC_MAX_NAME + 1];
12067 605 : szDimNameFirst[0] = '\0';
12068 605 : szDimNameSecond[0] = '\0';
12069 1372 : if (vartype == NC_CHAR &&
12070 162 : nc_inq_dimname(nCdfId, anDimIds[0], szDimNameFirst) ==
12071 162 : NC_NOERR &&
12072 162 : nc_inq_dimname(nCdfId, anDimIds[1], szDimNameSecond) ==
12073 162 : NC_NOERR &&
12074 162 : !NCDFIsVarLongitude(nCdfId, -1, szDimNameSecond) &&
12075 162 : !NCDFIsVarProjectionX(nCdfId, -1, szDimNameSecond) &&
12076 929 : !NCDFIsVarLatitude(nCdfId, -1, szDimNameFirst) &&
12077 162 : !NCDFIsVarProjectionY(nCdfId, -1, szDimNameFirst))
12078 : {
12079 162 : anPotentialVectorVarID.push_back(v);
12080 162 : oMapDimIdToCount[anDimIds[0]]++;
12081 162 : if (strstr(szDimNameSecond, "_max_width"))
12082 : {
12083 133 : bRasterCandidate = false;
12084 : }
12085 : else
12086 : {
12087 29 : std::array<int, 3> oKey{anDimIds[0], anDimIds[1],
12088 29 : vartype};
12089 29 : oMap2DDimsToGroupAndVar[oKey].emplace_back(
12090 29 : std::pair(nCdfId, v));
12091 : }
12092 : }
12093 : else
12094 : {
12095 443 : std::array<int, 3> oKey{anDimIds[0], anDimIds[1],
12096 443 : vartype};
12097 443 : oMap2DDimsToGroupAndVar[oKey].emplace_back(
12098 443 : std::pair(nCdfId, v));
12099 443 : bIsVectorOnly = false;
12100 : }
12101 : }
12102 : else
12103 : {
12104 63 : bIsVectorOnly = false;
12105 : }
12106 668 : if (bKeepRasters && bRasterCandidate)
12107 : {
12108 506 : *pnGroupId = nCdfId;
12109 506 : *pnVarId = v;
12110 506 : nRasterVars++;
12111 : }
12112 : }
12113 1024 : else if (nVarDims == 1)
12114 : {
12115 726 : nc_type atttype = NC_NAT;
12116 726 : size_t attlen = 0;
12117 726 : if (nc_inq_att(nCdfId, v, "instance_dimension", &atttype,
12118 14 : &attlen) == NC_NOERR &&
12119 726 : atttype == NC_CHAR && attlen < NC_MAX_NAME)
12120 : {
12121 : char szInstanceDimension[NC_MAX_NAME + 1];
12122 14 : if (nc_get_att_text(nCdfId, v, "instance_dimension",
12123 14 : szInstanceDimension) == NC_NOERR)
12124 : {
12125 14 : szInstanceDimension[attlen] = 0;
12126 14 : int status = nc_inq_dimid(nCdfId, szInstanceDimension,
12127 : &nProfileDimId);
12128 14 : if (status == NC_NOERR)
12129 14 : nParentIndexVarID = v;
12130 : else
12131 0 : nProfileDimId = -1;
12132 14 : if (status == NC_EBADDIM)
12133 0 : CPLError(CE_Warning, CPLE_AppDefined,
12134 : "Attribute instance_dimension='%s' refers "
12135 : "to a non existing dimension",
12136 : szInstanceDimension);
12137 : else
12138 14 : NCDF_ERR(status);
12139 : }
12140 : }
12141 726 : if (v != nParentIndexVarID)
12142 : {
12143 712 : anPotentialVectorVarID.push_back(v);
12144 712 : int nDimId = -1;
12145 712 : nc_inq_vardimid(nCdfId, v, &nDimId);
12146 712 : oMapDimIdToCount[nDimId]++;
12147 : }
12148 : }
12149 : }
12150 : }
12151 :
12152 : // If we are opened in raster-only mode and that there are only 1D or 2D
12153 : // variables and that the 2D variables have no X/Y dim, and all
12154 : // variables refer to the same main dimension (or 2 dimensions for
12155 : // featureType=profile), then it is a pure vector dataset
12156 : CPLString osFeatureType(
12157 508 : CSLFetchNameValueDef(papszMetadata, "NC_GLOBAL#featureType", ""));
12158 397 : if (bKeepRasters && !bKeepVectors && bIsVectorOnly && nRasterVars > 0 &&
12159 905 : !anPotentialVectorVarID.empty() &&
12160 0 : (oMapDimIdToCount.size() == 1 ||
12161 0 : (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2 &&
12162 0 : nProfileDimId >= 0)))
12163 : {
12164 0 : anPotentialVectorVarID.resize(0);
12165 : }
12166 : else
12167 : {
12168 508 : *pnRasterVars += nRasterVars;
12169 : }
12170 :
12171 508 : if (!anPotentialVectorVarID.empty() && bKeepVectors)
12172 : {
12173 : // Take the dimension that is referenced the most times.
12174 64 : if (!(oMapDimIdToCount.size() == 1 ||
12175 27 : (EQUAL(osFeatureType, "profile") &&
12176 26 : oMapDimIdToCount.size() == 2 && nProfileDimId >= 0)))
12177 : {
12178 1 : CPLError(CE_Warning, CPLE_AppDefined,
12179 : "The dataset has several variables that could be "
12180 : "identified as vector fields, but not all share the same "
12181 : "primary dimension. Consequently they will be ignored.");
12182 : }
12183 : else
12184 : {
12185 50 : if (nVarTimeId >= 0 &&
12186 50 : oMapDimIdToCount.find(nVarTimeDimId) != oMapDimIdToCount.end())
12187 : {
12188 1 : anPotentialVectorVarID.push_back(nVarTimeId);
12189 : }
12190 49 : CreateGrpVectorLayers(nCdfId, osFeatureType, anPotentialVectorVarID,
12191 : oMapDimIdToCount, nVarXId, nVarYId, nVarZId,
12192 : nProfileDimId, nParentIndexVarID,
12193 : bKeepRasters);
12194 : }
12195 : }
12196 :
12197 : // Recurse on sub-groups.
12198 508 : int nSubGroups = 0;
12199 508 : int *panSubGroupIds = nullptr;
12200 508 : NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
12201 534 : for (int i = 0; i < nSubGroups; i++)
12202 : {
12203 26 : FilterVars(panSubGroupIds[i], bKeepRasters, bKeepVectors,
12204 : papszIgnoreVars, pnRasterVars, pnGroupId, pnVarId,
12205 : pnIgnoredVars, oMap2DDimsToGroupAndVar);
12206 : }
12207 508 : CPLFree(panSubGroupIds);
12208 :
12209 508 : return CE_None;
12210 : }
12211 :
12212 : // Create vector layers from given potentially identified vector variables
12213 : // resulting from the scanning of a NetCDF (or group) ID.
12214 49 : CPLErr netCDFDataset::CreateGrpVectorLayers(
12215 : int nCdfId, const CPLString &osFeatureType,
12216 : const std::vector<int> &anPotentialVectorVarID,
12217 : const std::map<int, int> &oMapDimIdToCount, int nVarXId, int nVarYId,
12218 : int nVarZId, int nProfileDimId, int nParentIndexVarID, bool bKeepRasters)
12219 : {
12220 49 : char *pszGroupName = nullptr;
12221 49 : NCDFGetGroupFullName(nCdfId, &pszGroupName);
12222 49 : if (pszGroupName == nullptr || pszGroupName[0] == '\0')
12223 : {
12224 47 : CPLFree(pszGroupName);
12225 47 : pszGroupName = CPLStrdup(CPLGetBasename(osFilename));
12226 : }
12227 49 : OGRwkbGeometryType eGType = wkbUnknown;
12228 : CPLString osLayerName = CSLFetchNameValueDef(
12229 98 : papszMetadata, "NC_GLOBAL#ogr_layer_name", pszGroupName);
12230 49 : CPLFree(pszGroupName);
12231 49 : papszMetadata =
12232 49 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_name", nullptr);
12233 :
12234 49 : if (EQUAL(osFeatureType, "point") || EQUAL(osFeatureType, "profile"))
12235 : {
12236 33 : papszMetadata =
12237 33 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#featureType", nullptr);
12238 33 : eGType = wkbPoint;
12239 : }
12240 :
12241 : const char *pszLayerType =
12242 49 : CSLFetchNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_type");
12243 49 : if (pszLayerType != nullptr)
12244 : {
12245 9 : eGType = OGRFromOGCGeomType(pszLayerType);
12246 9 : papszMetadata =
12247 9 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_type", nullptr);
12248 : }
12249 :
12250 : CPLString osGeometryField =
12251 98 : CSLFetchNameValueDef(papszMetadata, "NC_GLOBAL#ogr_geometry_field", "");
12252 49 : papszMetadata =
12253 49 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_geometry_field", nullptr);
12254 :
12255 49 : int nFirstVarId = -1;
12256 49 : int nVectorDim = oMapDimIdToCount.rbegin()->first;
12257 49 : if (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2)
12258 : {
12259 13 : if (nVectorDim == nProfileDimId)
12260 0 : nVectorDim = oMapDimIdToCount.begin()->first;
12261 : }
12262 : else
12263 : {
12264 36 : nProfileDimId = -1;
12265 : }
12266 62 : for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)
12267 : {
12268 62 : int anDimIds[2] = {-1, -1};
12269 62 : nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);
12270 62 : if (nVectorDim == anDimIds[0])
12271 : {
12272 49 : nFirstVarId = anPotentialVectorVarID[j];
12273 49 : break;
12274 : }
12275 : }
12276 :
12277 : // In case where coordinates are explicitly specified for one of the
12278 : // field/variable, use them in priority over the ones that might have been
12279 : // identified above.
12280 49 : char *pszCoordinates = nullptr;
12281 49 : if (NCDFGetAttr(nCdfId, nFirstVarId, "coordinates", &pszCoordinates) ==
12282 : CE_None)
12283 : {
12284 34 : char **papszTokens = NCDFTokenizeCoordinatesAttribute(pszCFCoordinates);
12285 34 : for (int i = 0; papszTokens != nullptr && papszTokens[i] != nullptr;
12286 : i++)
12287 : {
12288 0 : if (NCDFIsVarLongitude(nCdfId, -1, papszTokens[i]) ||
12289 0 : NCDFIsVarProjectionX(nCdfId, -1, papszTokens[i]))
12290 : {
12291 0 : nVarXId = -1;
12292 0 : CPL_IGNORE_RET_VAL(
12293 0 : nc_inq_varid(nCdfId, papszTokens[i], &nVarXId));
12294 : }
12295 0 : else if (NCDFIsVarLatitude(nCdfId, -1, papszTokens[i]) ||
12296 0 : NCDFIsVarProjectionY(nCdfId, -1, papszTokens[i]))
12297 : {
12298 0 : nVarYId = -1;
12299 0 : CPL_IGNORE_RET_VAL(
12300 0 : nc_inq_varid(nCdfId, papszTokens[i], &nVarYId));
12301 : }
12302 0 : else if (NCDFIsVarVerticalCoord(nCdfId, -1, papszTokens[i]))
12303 : {
12304 0 : nVarZId = -1;
12305 0 : CPL_IGNORE_RET_VAL(
12306 0 : nc_inq_varid(nCdfId, papszTokens[i], &nVarZId));
12307 : }
12308 : }
12309 34 : CSLDestroy(papszTokens);
12310 : }
12311 49 : CPLFree(pszCoordinates);
12312 :
12313 : // Check that the X,Y,Z vars share 1D and share the same dimension as
12314 : // attribute variables.
12315 49 : if (nVarXId >= 0 && nVarYId >= 0)
12316 : {
12317 38 : int nVarDimCount = -1;
12318 38 : int nVarDimId = -1;
12319 38 : if (nc_inq_varndims(nCdfId, nVarXId, &nVarDimCount) != NC_NOERR ||
12320 38 : nVarDimCount != 1 ||
12321 38 : nc_inq_vardimid(nCdfId, nVarXId, &nVarDimId) != NC_NOERR ||
12322 38 : nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim) ||
12323 35 : nc_inq_varndims(nCdfId, nVarYId, &nVarDimCount) != NC_NOERR ||
12324 35 : nVarDimCount != 1 ||
12325 111 : nc_inq_vardimid(nCdfId, nVarYId, &nVarDimId) != NC_NOERR ||
12326 35 : nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim))
12327 : {
12328 3 : nVarXId = nVarYId = -1;
12329 : }
12330 69 : else if (nVarZId >= 0 &&
12331 34 : (nc_inq_varndims(nCdfId, nVarZId, &nVarDimCount) != NC_NOERR ||
12332 34 : nVarDimCount != 1 ||
12333 34 : nc_inq_vardimid(nCdfId, nVarZId, &nVarDimId) != NC_NOERR ||
12334 34 : nVarDimId != nVectorDim))
12335 : {
12336 0 : nVarZId = -1;
12337 : }
12338 : }
12339 :
12340 49 : if (eGType == wkbUnknown && nVarXId >= 0 && nVarYId >= 0)
12341 : {
12342 2 : eGType = wkbPoint;
12343 : }
12344 49 : if (eGType == wkbPoint && nVarXId >= 0 && nVarYId >= 0 && nVarZId >= 0)
12345 : {
12346 34 : eGType = wkbPoint25D;
12347 : }
12348 49 : if (eGType == wkbUnknown && osGeometryField.empty())
12349 : {
12350 5 : eGType = wkbNone;
12351 : }
12352 :
12353 : // Read projection info
12354 49 : char **papszMetadataBackup = CSLDuplicate(papszMetadata);
12355 49 : ReadAttributes(nCdfId, nFirstVarId);
12356 49 : if (!this->bSGSupport)
12357 48 : SetProjectionFromVar(nCdfId, nFirstVarId, true);
12358 49 : const char *pszValue = FetchAttr(nCdfId, nFirstVarId, CF_GRD_MAPPING);
12359 49 : char *pszGridMapping = (pszValue ? CPLStrdup(pszValue) : nullptr);
12360 49 : CSLDestroy(papszMetadata);
12361 49 : papszMetadata = papszMetadataBackup;
12362 :
12363 49 : OGRSpatialReference *poSRS = nullptr;
12364 49 : if (!m_oSRS.IsEmpty())
12365 : {
12366 21 : poSRS = m_oSRS.Clone();
12367 : }
12368 : // Reset if there's a 2D raster
12369 49 : m_bHasProjection = false;
12370 49 : m_bHasGeoTransform = false;
12371 :
12372 49 : if (!bKeepRasters)
12373 : {
12374 : // Strip out uninteresting metadata.
12375 45 : papszMetadata =
12376 45 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#Conventions", nullptr);
12377 45 : papszMetadata =
12378 45 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#GDAL", nullptr);
12379 45 : papszMetadata =
12380 45 : CSLSetNameValue(papszMetadata, "NC_GLOBAL#history", nullptr);
12381 : }
12382 :
12383 : std::shared_ptr<netCDFLayer> poLayer(
12384 49 : new netCDFLayer(this, nCdfId, osLayerName, eGType, poSRS));
12385 49 : if (poSRS != nullptr)
12386 21 : poSRS->Release();
12387 49 : poLayer->SetRecordDimID(nVectorDim);
12388 49 : if (wkbFlatten(eGType) == wkbPoint && nVarXId >= 0 && nVarYId >= 0)
12389 : {
12390 35 : poLayer->SetXYZVars(nVarXId, nVarYId, nVarZId);
12391 : }
12392 14 : else if (!osGeometryField.empty())
12393 : {
12394 9 : poLayer->SetWKTGeometryField(osGeometryField);
12395 : }
12396 49 : if (pszGridMapping != nullptr)
12397 : {
12398 21 : poLayer->SetGridMapping(pszGridMapping);
12399 21 : CPLFree(pszGridMapping);
12400 : }
12401 49 : poLayer->SetProfile(nProfileDimId, nParentIndexVarID);
12402 :
12403 574 : for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)
12404 : {
12405 525 : int anDimIds[2] = {-1, -1};
12406 525 : nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);
12407 525 : if (anDimIds[0] == nVectorDim ||
12408 24 : (nProfileDimId >= 0 && anDimIds[0] == nProfileDimId))
12409 : {
12410 : #ifdef NCDF_DEBUG
12411 : char szTemp2[NC_MAX_NAME + 1] = {};
12412 : CPL_IGNORE_RET_VAL(
12413 : nc_inq_varname(nCdfId, anPotentialVectorVarID[j], szTemp2));
12414 : CPLDebug("GDAL_netCDF", "Variable %s is a vector field", szTemp2);
12415 : #endif
12416 525 : poLayer->AddField(anPotentialVectorVarID[j]);
12417 : }
12418 : }
12419 :
12420 49 : if (poLayer->GetLayerDefn()->GetFieldCount() != 0 ||
12421 0 : poLayer->GetGeomType() != wkbNone)
12422 : {
12423 49 : papoLayers.push_back(poLayer);
12424 : }
12425 :
12426 98 : return CE_None;
12427 : }
12428 :
12429 : // Get all coordinate and boundary variables full names referenced in
12430 : // a given a NetCDF (or group) ID and its sub-groups.
12431 : // These variables are identified in other variable's
12432 : // "coordinates" and "bounds" attribute.
12433 : // Searching coordinate and boundary variables may need to explore
12434 : // parents groups (or other groups in case of reference given in form of an
12435 : // absolute path).
12436 : // See CF sections 5.2, 5.6 and 7.1
12437 509 : static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId, char ***ppapszVars)
12438 : {
12439 509 : int nVars = 0;
12440 509 : NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
12441 :
12442 3169 : for (int v = 0; v < nVars; v++)
12443 : {
12444 2660 : char *pszTemp = nullptr;
12445 2660 : char **papszTokens = nullptr;
12446 2660 : if (NCDFGetAttr(nCdfId, v, "coordinates", &pszTemp) == CE_None)
12447 441 : papszTokens = NCDFTokenizeCoordinatesAttribute(pszTemp);
12448 2660 : CPLFree(pszTemp);
12449 2660 : pszTemp = nullptr;
12450 2660 : if (NCDFGetAttr(nCdfId, v, "bounds", &pszTemp) == CE_None &&
12451 2660 : pszTemp != nullptr && !EQUAL(pszTemp, ""))
12452 9 : papszTokens = CSLAddString(papszTokens, pszTemp);
12453 2660 : CPLFree(pszTemp);
12454 3926 : for (int i = 0; papszTokens != nullptr && papszTokens[i] != nullptr;
12455 : i++)
12456 : {
12457 1266 : char *pszVarFullName = nullptr;
12458 1266 : if (NCDFResolveVarFullName(nCdfId, papszTokens[i],
12459 1266 : &pszVarFullName) == CE_None)
12460 1248 : *ppapszVars = CSLAddString(*ppapszVars, pszVarFullName);
12461 1266 : CPLFree(pszVarFullName);
12462 : }
12463 2660 : CSLDestroy(papszTokens);
12464 : }
12465 :
12466 : // Recurse on sub-groups.
12467 : int nSubGroups;
12468 509 : int *panSubGroupIds = nullptr;
12469 509 : NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
12470 535 : for (int i = 0; i < nSubGroups; i++)
12471 : {
12472 26 : NCDFGetCoordAndBoundVarFullNames(panSubGroupIds[i], ppapszVars);
12473 : }
12474 509 : CPLFree(panSubGroupIds);
12475 :
12476 509 : return CE_None;
12477 : }
12478 :
12479 : // Check if give type is user defined
12480 862 : bool NCDFIsUserDefinedType(int /*ncid*/, int type)
12481 : {
12482 862 : return type >= NC_FIRSTUSERTYPEID;
12483 : }
12484 :
12485 533 : char **NCDFTokenizeCoordinatesAttribute(const char *pszCoordinates)
12486 : {
12487 : // CF conventions use space as the separator for variable names in the
12488 : // coordinates attribute, but some products such as
12489 : // https://sentinel.esa.int/web/sentinel/technical-guides/sentinel-3-synergy/products-algorithms/level-2-aod-algorithms-and-products/level-2-aod-products-description
12490 : // use comma.
12491 533 : return CSLTokenizeString2(pszCoordinates, ", ", 0);
12492 : }
|