Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: netCDF read/write Driver
4 : * Author: Even Rouault <even.rouault at spatialys.com>
5 : *
6 : ******************************************************************************
7 : * Copyright (c) 2019, Even Rouault <even.rouault at spatialys.com>
8 : *
9 : * SPDX-License-Identifier: MIT
10 : ****************************************************************************/
11 :
12 : #include <algorithm>
13 : #include <cinttypes>
14 : #include <limits>
15 : #include <map>
16 :
17 : #include "cpl_multiproc.h"
18 : #include "gdal_pam_multidim.h"
19 : #include "gdal_rat.h"
20 : #include "memdataset.h"
21 :
22 : #include "netcdfdataset.h"
23 : #include "netcdfdrivercore.h"
24 :
25 : #include "netcdf_mem.h"
26 :
27 : static bool BuildDataType(int gid, int varid, int nVarType,
28 : std::unique_ptr<GDALExtendedDataType> &dt,
29 : bool &bPerfectDataTypeMatch);
30 :
31 : /************************************************************************/
32 : /* netCDFSharedResources */
33 : /************************************************************************/
34 :
35 : class netCDFSharedResources
36 : {
37 : friend class netCDFDataset;
38 :
39 : bool m_bImappIsInElements = true;
40 : bool m_bReadOnly = true;
41 : bool m_bIsNC4 = false;
42 : int m_cdfid = 0;
43 : #ifdef ENABLE_NCDUMP
44 : bool m_bFileToDestroyAtClosing = false;
45 : #endif
46 : CPLString m_osFilename{};
47 : #ifdef ENABLE_UFFD
48 : cpl_uffd_context *m_pUffdCtx = nullptr;
49 : #endif
50 : VSILFILE *m_fpVSIMEM = nullptr;
51 : bool m_bDefineMode = false;
52 : std::map<int, int> m_oMapDimIdToGroupId{};
53 : bool m_bIsInIndexingVariable = false;
54 : std::shared_ptr<GDALPamMultiDim> m_poPAM{};
55 : std::map<int, std::weak_ptr<GDALDimension>> m_oCachedDimensions{};
56 :
57 : protected:
58 : friend class netCDFVariable;
59 :
60 : std::unique_ptr<GDALDataset> m_poHDF5DS{};
61 : std::shared_ptr<GDALMDArray> m_poHDF5CurArray{};
62 :
63 : public:
64 : explicit netCDFSharedResources(const std::string &osFilename);
65 : ~netCDFSharedResources();
66 :
67 741 : inline int GetCDFId() const
68 : {
69 741 : return m_cdfid;
70 : }
71 :
72 2576 : inline bool IsReadOnly() const
73 : {
74 2576 : return m_bReadOnly;
75 : }
76 :
77 265 : inline bool IsNC4() const
78 : {
79 265 : return m_bIsNC4;
80 : }
81 :
82 : bool SetDefineMode(bool bNewDefineMode);
83 : int GetBelongingGroupOfDim(int startgid, int dimid);
84 :
85 7 : inline bool GetImappIsInElements() const
86 : {
87 7 : return m_bImappIsInElements;
88 : }
89 :
90 1790 : void SetIsInGetIndexingVariable(bool b)
91 : {
92 1790 : m_bIsInIndexingVariable = b;
93 1790 : }
94 :
95 2073 : bool GetIsInIndexingVariable() const
96 : {
97 2073 : return m_bIsInIndexingVariable;
98 : }
99 :
100 891 : const std::string &GetFilename() const
101 : {
102 891 : return m_osFilename;
103 : }
104 :
105 2249 : const std::shared_ptr<GDALPamMultiDim> &GetPAM()
106 : {
107 2249 : return m_poPAM;
108 : }
109 :
110 1820 : void CacheDimension(int dimid, const std::shared_ptr<GDALDimension> &poDim)
111 : {
112 1820 : m_oCachedDimensions[dimid] = poDim;
113 1820 : }
114 :
115 2710 : std::shared_ptr<GDALDimension> GetCachedDimension(int dimid) const
116 : {
117 2710 : auto oIter = m_oCachedDimensions.find(dimid);
118 2710 : if (oIter == m_oCachedDimensions.end())
119 522 : return nullptr;
120 2188 : return oIter->second.lock();
121 : }
122 : };
123 :
124 : /************************************************************************/
125 : /* netCDFSharedResources() */
126 : /************************************************************************/
127 :
128 367 : netCDFSharedResources::netCDFSharedResources(const std::string &osFilename)
129 : : m_bImappIsInElements(false), m_osFilename(osFilename),
130 367 : m_poPAM(std::make_shared<GDALPamMultiDim>(osFilename))
131 : {
132 : // netcdf >= 4.4 uses imapp argument of nc_get/put_varm as a stride in
133 : // elements, whereas earlier versions use bytes.
134 : CPLStringList aosVersionNumbers(
135 734 : CSLTokenizeString2(nc_inq_libvers(), ".", 0));
136 367 : m_bImappIsInElements = false;
137 367 : if (aosVersionNumbers.size() >= 3)
138 : {
139 367 : m_bImappIsInElements =
140 367 : (atoi(aosVersionNumbers[0]) > 4 || atoi(aosVersionNumbers[1]) >= 4);
141 : }
142 367 : }
143 :
144 : /************************************************************************/
145 : /* GetBelongingGroupOfDim() */
146 : /************************************************************************/
147 :
148 1723 : int netCDFSharedResources::GetBelongingGroupOfDim(int startgid, int dimid)
149 : {
150 : // Am I missing a netCDF API to do this directly ?
151 1723 : auto oIter = m_oMapDimIdToGroupId.find(dimid);
152 1723 : if (oIter != m_oMapDimIdToGroupId.end())
153 1313 : return oIter->second;
154 :
155 410 : int gid = startgid;
156 : while (true)
157 : {
158 425 : int nbDims = 0;
159 425 : NCDF_ERR(nc_inq_ndims(gid, &nbDims));
160 425 : if (nbDims > 0)
161 : {
162 410 : std::vector<int> dimids(nbDims);
163 410 : NCDF_ERR(nc_inq_dimids(gid, &nbDims, &dimids[0], FALSE));
164 791 : for (int i = 0; i < nbDims; i++)
165 : {
166 791 : m_oMapDimIdToGroupId[dimid] = gid;
167 791 : if (dimids[i] == dimid)
168 410 : return gid;
169 : }
170 : }
171 15 : int nParentGID = 0;
172 15 : if (nc_inq_grp_parent(gid, &nParentGID) != NC_NOERR)
173 0 : return startgid;
174 15 : gid = nParentGID;
175 15 : }
176 : }
177 :
178 : /************************************************************************/
179 : /* SetDefineMode() */
180 : /************************************************************************/
181 :
182 1539 : bool netCDFSharedResources::SetDefineMode(bool bNewDefineMode)
183 : {
184 : // Do nothing if already in new define mode
185 : // or if dataset is in read-only mode or if dataset is NC4 format.
186 1539 : if (m_bDefineMode == bNewDefineMode || m_bReadOnly || m_bIsNC4)
187 1525 : return true;
188 :
189 14 : CPLDebug("GDAL_netCDF", "SetDefineMode(%d) new=%d, old=%d", m_cdfid,
190 14 : static_cast<int>(bNewDefineMode), static_cast<int>(m_bDefineMode));
191 :
192 14 : m_bDefineMode = bNewDefineMode;
193 :
194 : int status;
195 14 : if (m_bDefineMode)
196 8 : status = nc_redef(m_cdfid);
197 : else
198 6 : status = nc_enddef(m_cdfid);
199 :
200 14 : NCDF_ERR(status);
201 14 : return status == NC_NOERR;
202 : }
203 :
204 : /************************************************************************/
205 : /* netCDFAttributeHolder */
206 : /************************************************************************/
207 :
208 : class netCDFAttributeHolder CPL_NON_FINAL
209 : {
210 : protected:
211 : std::map<std::string, GDALAttribute *> m_oMapAttributes{};
212 :
213 : public:
214 1102 : void RegisterAttribute(GDALAttribute *poAttr)
215 : {
216 1102 : m_oMapAttributes[poAttr->GetName()] = poAttr;
217 1102 : }
218 :
219 981 : void UnRegisterAttribute(GDALAttribute *poAttr)
220 : {
221 981 : m_oMapAttributes.erase(poAttr->GetName());
222 981 : }
223 : };
224 :
225 : /************************************************************************/
226 : /* netCDFGroup */
227 : /************************************************************************/
228 :
229 : class netCDFGroup final : public GDALGroup, public netCDFAttributeHolder
230 : {
231 : std::shared_ptr<netCDFSharedResources> m_poShared;
232 : int m_gid = 0;
233 : CPLStringList m_aosStructuralInfo{};
234 : std::weak_ptr<netCDFGroup> m_poParent{};
235 : std::set<GDALGroup *> m_oSetGroups{};
236 : std::set<GDALDimension *> m_oSetDimensions{};
237 : std::set<GDALMDArray *> m_oSetArrays{};
238 :
239 741 : static std::string retrieveName(int gid)
240 : {
241 741 : CPLMutexHolderD(&hNCMutex);
242 741 : char szName[NC_MAX_NAME + 1] = {};
243 741 : NCDF_ERR(nc_inq_grpname(gid, szName));
244 1482 : return szName;
245 : }
246 :
247 48 : void RegisterSubGroup(GDALGroup *poSubGroup)
248 : {
249 48 : m_oSetGroups.insert(poSubGroup);
250 48 : }
251 :
252 32 : void UnRegisterSubGroup(GDALGroup *poSubGroup)
253 : {
254 32 : m_oSetGroups.erase(poSubGroup);
255 32 : }
256 :
257 : protected:
258 : friend class netCDFDimension;
259 :
260 807 : void RegisterDimension(GDALDimension *poDim)
261 : {
262 807 : m_oSetDimensions.insert(poDim);
263 807 : }
264 :
265 443 : void UnRegisterDimension(GDALDimension *poDim)
266 : {
267 443 : m_oSetDimensions.erase(poDim);
268 443 : }
269 :
270 : friend class netCDFVariable;
271 :
272 896 : void RegisterArray(GDALMDArray *poArray)
273 : {
274 896 : m_oSetArrays.insert(poArray);
275 896 : }
276 :
277 562 : void UnRegisterArray(GDALMDArray *poArray)
278 : {
279 562 : m_oSetArrays.erase(poArray);
280 562 : }
281 :
282 : void NotifyChildrenOfRenaming() override;
283 :
284 : netCDFGroup(const std::shared_ptr<netCDFSharedResources> &poShared,
285 : int gid);
286 :
287 : public:
288 : ~netCDFGroup() override;
289 :
290 : static std::shared_ptr<netCDFGroup>
291 : Create(const std::shared_ptr<netCDFSharedResources> &poShared, int cdfid);
292 :
293 : static std::shared_ptr<netCDFGroup>
294 : Create(const std::shared_ptr<netCDFSharedResources> &poShared,
295 : const std::shared_ptr<netCDFGroup> &poParent, int nSubGroupId);
296 :
297 : std::vector<std::string>
298 : GetGroupNames(CSLConstList papszOptions) const override;
299 : std::shared_ptr<GDALGroup>
300 : OpenGroup(const std::string &osName,
301 : CSLConstList papszOptions = nullptr) const override;
302 :
303 : std::vector<std::string>
304 : GetMDArrayNames(CSLConstList papszOptions) const override;
305 : std::shared_ptr<GDALMDArray>
306 : OpenMDArray(const std::string &osName,
307 : CSLConstList papszOptions) const override;
308 :
309 : std::vector<std::shared_ptr<GDALDimension>>
310 : GetDimensions(CSLConstList papszOptions) const override;
311 :
312 : std::shared_ptr<GDALAttribute>
313 : GetAttribute(const std::string &osName) const override;
314 :
315 : std::vector<std::shared_ptr<GDALAttribute>>
316 : GetAttributes(CSLConstList papszOptions) const override;
317 :
318 : std::shared_ptr<GDALGroup> CreateGroup(const std::string &osName,
319 : CSLConstList papszOptions) override;
320 :
321 : std::shared_ptr<GDALDimension>
322 : CreateDimension(const std::string &osName, const std::string &osType,
323 : const std::string &osDirection, GUInt64 nSize,
324 : CSLConstList papszOptions) override;
325 :
326 : std::shared_ptr<GDALMDArray> CreateMDArray(
327 : const std::string &osName,
328 : const std::vector<std::shared_ptr<GDALDimension>> &aoDimensions,
329 : const GDALExtendedDataType &oDataType,
330 : CSLConstList papszOptions) override;
331 :
332 : std::shared_ptr<GDALAttribute>
333 : CreateAttribute(const std::string &osName,
334 : const std::vector<GUInt64> &anDimensions,
335 : const GDALExtendedDataType &oDataType,
336 : CSLConstList papszOptions) override;
337 :
338 : bool DeleteAttribute(const std::string &osName,
339 : CSLConstList papszOptions) override;
340 :
341 : CSLConstList GetStructuralInfo() const override;
342 :
343 : void ClearStatistics() override;
344 :
345 : bool Rename(const std::string &osNewName) override;
346 : };
347 :
348 : /************************************************************************/
349 : /* netCDFVirtualGroupBySameDimension */
350 : /************************************************************************/
351 :
352 : class netCDFVirtualGroupBySameDimension final : public GDALGroup
353 : {
354 : // the real group to which we derived this virtual group from
355 : std::shared_ptr<netCDFGroup> m_poGroup;
356 : std::string m_osDimName{};
357 :
358 : protected:
359 : netCDFVirtualGroupBySameDimension(
360 : const std::shared_ptr<netCDFGroup> &poGroup,
361 : const std::string &osDimName);
362 :
363 : public:
364 : static std::shared_ptr<netCDFVirtualGroupBySameDimension>
365 : Create(const std::shared_ptr<netCDFGroup> &poGroup,
366 : const std::string &osDimName);
367 :
368 : std::vector<std::string>
369 : GetMDArrayNames(CSLConstList papszOptions) const override;
370 : std::shared_ptr<GDALMDArray>
371 : OpenMDArray(const std::string &osName,
372 : CSLConstList papszOptions) const override;
373 : };
374 :
375 : /************************************************************************/
376 : /* netCDFDimension */
377 : /************************************************************************/
378 :
379 : class netCDFDimension final : public GDALDimension
380 : {
381 : std::shared_ptr<netCDFSharedResources> m_poShared;
382 : int m_gid = 0;
383 : int m_dimid = 0;
384 : std::weak_ptr<netCDFGroup> m_poParent{};
385 :
386 2016 : static std::string retrieveName(int cfid, int dimid)
387 : {
388 2016 : CPLMutexHolderD(&hNCMutex);
389 2016 : char szName[NC_MAX_NAME + 1] = {};
390 2016 : NCDF_ERR(nc_inq_dimname(cfid, dimid, szName));
391 4032 : return szName;
392 : }
393 :
394 2407 : static GUInt64 retrieveSize(int cfid, int dimid)
395 : {
396 2407 : CPLMutexHolderD(&hNCMutex);
397 2407 : size_t nDimLen = 0;
398 2407 : NCDF_ERR(nc_inq_dimlen(cfid, dimid, &nDimLen));
399 4814 : return nDimLen;
400 : }
401 :
402 : public:
403 : netCDFDimension(const std::shared_ptr<netCDFSharedResources> &poShared,
404 : int cfid, int dimid, size_t nForcedSize,
405 : const std::string &osType);
406 :
407 : ~netCDFDimension() override;
408 :
409 : static std::shared_ptr<netCDFDimension>
410 : Create(const std::shared_ptr<netCDFSharedResources> &poShared,
411 : const std::shared_ptr<netCDFGroup> &poParent, int cfid, int dimid,
412 : size_t nForcedSize, const std::string &osType);
413 :
414 : std::shared_ptr<GDALMDArray> GetIndexingVariable() const override;
415 :
416 320 : int GetId() const
417 : {
418 320 : return m_dimid;
419 : }
420 :
421 583 : GUInt64 GetActualSize() const
422 : {
423 583 : return retrieveSize(m_gid, m_dimid);
424 : }
425 :
426 3 : void SetSize(GUInt64 nNewSize)
427 : {
428 3 : m_nSize = nNewSize;
429 3 : }
430 :
431 : bool Rename(const std::string &osNewName) override;
432 : };
433 :
434 : /************************************************************************/
435 : /* netCDFAttribute */
436 : /************************************************************************/
437 :
438 : class netCDFVariable;
439 :
440 : class netCDFAttribute final : public GDALAttribute
441 : {
442 : std::shared_ptr<netCDFSharedResources> m_poShared;
443 : std::weak_ptr<netCDFAttributeHolder> m_poParent;
444 : int m_gid = 0;
445 : int m_varid = 0;
446 : size_t m_nTextLength = 0;
447 : std::vector<std::shared_ptr<GDALDimension>> m_dims{};
448 : nc_type m_nAttType = NC_NAT;
449 : mutable std::unique_ptr<GDALExtendedDataType> m_dt;
450 : mutable bool m_bPerfectDataTypeMatch = false;
451 :
452 : protected:
453 : netCDFAttribute(const std::shared_ptr<netCDFSharedResources> &poShared,
454 : int gid, int varid, const std::string &name);
455 :
456 : netCDFAttribute(const std::shared_ptr<netCDFSharedResources> &poShared,
457 : int gid, int varid, const std::string &osName,
458 : const std::vector<GUInt64> &anDimensions,
459 : const GDALExtendedDataType &oDataType,
460 : CSLConstList papszOptions);
461 :
462 : bool
463 : IRead(const GUInt64 *arrayStartIdx, // array of size GetDimensionCount()
464 : const size_t *count, // array of size GetDimensionCount()
465 : const GInt64 *arrayStep, // step in elements
466 : const GPtrDiff_t *bufferStride, // stride in elements
467 : const GDALExtendedDataType &bufferDataType,
468 : void *pDstBuffer) const override;
469 :
470 : bool
471 : IWrite(const GUInt64 *arrayStartIdx, // array of size GetDimensionCount()
472 : const size_t *count, // array of size GetDimensionCount()
473 : const GInt64 *arrayStep, // step in elements
474 : const GPtrDiff_t *bufferStride, // stride in elements
475 : const GDALExtendedDataType &bufferDataType,
476 : const void *pSrcBuffer) override;
477 :
478 : public:
479 : ~netCDFAttribute() override;
480 :
481 : static std::shared_ptr<netCDFAttribute>
482 : Create(const std::shared_ptr<netCDFSharedResources> &poShared,
483 : const std::shared_ptr<netCDFAttributeHolder> &poParent, int gid,
484 : int varid, const std::string &name);
485 :
486 : static std::shared_ptr<netCDFAttribute>
487 : Create(const std::shared_ptr<netCDFSharedResources> &poShared,
488 : const std::shared_ptr<netCDFAttributeHolder> &poParent, int gid,
489 : int varid, const std::string &osName,
490 : const std::vector<GUInt64> &anDimensions,
491 : const GDALExtendedDataType &oDataType, CSLConstList papszOptions);
492 :
493 : const std::vector<std::shared_ptr<GDALDimension>> &
494 3947 : GetDimensions() const override
495 : {
496 3947 : return m_dims;
497 : }
498 :
499 : const GDALExtendedDataType &GetDataType() const override;
500 :
501 : bool Rename(const std::string &osNewName) override;
502 : };
503 :
504 : /************************************************************************/
505 : /* netCDFVariable */
506 : /************************************************************************/
507 :
508 : class netCDFVariable final : public GDALPamMDArray, public netCDFAttributeHolder
509 : {
510 : std::shared_ptr<netCDFSharedResources> m_poShared;
511 : std::weak_ptr<netCDFGroup> m_poParent{};
512 : int m_gid = 0;
513 : int m_varid = 0;
514 : int m_nDims = 0;
515 : mutable std::vector<std::shared_ptr<GDALDimension>> m_dims{};
516 : mutable nc_type m_nVarType = NC_NAT;
517 : mutable std::unique_ptr<GDALExtendedDataType> m_dt;
518 : mutable bool m_bPerfectDataTypeMatch = false;
519 : mutable std::vector<GByte> m_abyNoData{};
520 : mutable bool m_bGetRawNoDataValueHasRun = false;
521 : bool m_bHasWrittenData = true;
522 : bool m_bUseDefaultFillAsNoData = false;
523 : std::string m_osUnit{};
524 : CPLStringList m_aosStructuralInfo{};
525 : mutable bool m_bSRSRead = false;
526 : mutable std::shared_ptr<OGRSpatialReference> m_poSRS{};
527 : bool m_bWriteGDALTags = true;
528 : size_t m_nTextLength = 0;
529 : mutable std::vector<GUInt64> m_cachedArrayStartIdx{};
530 : mutable std::vector<size_t> m_cachedCount{};
531 : mutable std::shared_ptr<GDALMDArray> m_poCachedArray{};
532 :
533 : void ConvertNCToGDAL(GByte *) const;
534 : void ConvertGDALToNC(GByte *) const;
535 :
536 : bool ReadOneElement(const GDALExtendedDataType &src_datatype,
537 : const GDALExtendedDataType &bufferDataType,
538 : const size_t *array_idx, void *pDstBuffer) const;
539 :
540 : bool WriteOneElement(const GDALExtendedDataType &dst_datatype,
541 : const GDALExtendedDataType &bufferDataType,
542 : const size_t *array_idx, const void *pSrcBuffer) const;
543 :
544 : template <typename BufferType, typename NCGetPutVar1FuncType,
545 : typename ReadOrWriteOneElementType>
546 : bool
547 : IReadWriteGeneric(const size_t *arrayStartIdx, const size_t *count,
548 : const GInt64 *arrayStep, const GPtrDiff_t *bufferStride,
549 : const GDALExtendedDataType &bufferDataType,
550 : BufferType buffer, NCGetPutVar1FuncType NCGetPutVar1Func,
551 : ReadOrWriteOneElementType ReadOrWriteOneElement) const;
552 :
553 : template <typename BufferType, typename NCGetPutVar1FuncType,
554 : typename NCGetPutVaraFuncType, typename NCGetPutVarmFuncType,
555 : typename ReadOrWriteOneElementType>
556 : bool IReadWrite(const bool bIsRead, const GUInt64 *arrayStartIdx,
557 : const size_t *count, const GInt64 *arrayStep,
558 : const GPtrDiff_t *bufferStride,
559 : const GDALExtendedDataType &bufferDataType,
560 : BufferType buffer, NCGetPutVar1FuncType NCGetPutVar1Func,
561 : NCGetPutVaraFuncType NCGetPutVaraFunc,
562 : NCGetPutVarmFuncType NCGetPutVarmFunc,
563 : ReadOrWriteOneElementType ReadOrWriteOneElement) const;
564 :
565 : protected:
566 : netCDFVariable(const std::shared_ptr<netCDFSharedResources> &poShared,
567 : int gid, int varid,
568 : const std::vector<std::shared_ptr<GDALDimension>> &dims,
569 : CSLConstList papszOptions);
570 :
571 : bool
572 : IRead(const GUInt64 *arrayStartIdx, // array of size GetDimensionCount()
573 : const size_t *count, // array of size GetDimensionCount()
574 : const GInt64 *arrayStep, // step in elements
575 : const GPtrDiff_t *bufferStride, // stride in elements
576 : const GDALExtendedDataType &bufferDataType,
577 : void *pDstBuffer) const override;
578 :
579 : bool
580 : IWrite(const GUInt64 *arrayStartIdx, // array of size GetDimensionCount()
581 : const size_t *count, // array of size GetDimensionCount()
582 : const GInt64 *arrayStep, // step in elements
583 : const GPtrDiff_t *bufferStride, // stride in elements
584 : const GDALExtendedDataType &bufferDataType,
585 : const void *pSrcBuffer) override;
586 :
587 : bool IAdviseRead(const GUInt64 *arrayStartIdx, const size_t *count,
588 : CSLConstList papszOptions) const override;
589 :
590 : void NotifyChildrenOfRenaming() override;
591 :
592 : bool SetStatistics(bool bApproxStats, double dfMin, double dfMax,
593 : double dfMean, double dfStdDev, GUInt64 nValidCount,
594 : CSLConstList papszOptions) override;
595 :
596 : public:
597 : static std::shared_ptr<netCDFVariable>
598 2248 : Create(const std::shared_ptr<netCDFSharedResources> &poShared,
599 : const std::shared_ptr<netCDFGroup> &poParent, int gid, int varid,
600 : const std::vector<std::shared_ptr<GDALDimension>> &dims,
601 : CSLConstList papszOptions, bool bCreate)
602 : {
603 : auto var(std::shared_ptr<netCDFVariable>(
604 2248 : new netCDFVariable(poShared, gid, varid, dims, papszOptions)));
605 2248 : var->SetSelf(var);
606 2248 : var->m_poParent = poParent;
607 2248 : if (poParent)
608 896 : poParent->RegisterArray(var.get());
609 2248 : var->m_bHasWrittenData = !bCreate;
610 2248 : return var;
611 : }
612 :
613 : ~netCDFVariable() override;
614 :
615 1415 : void SetUseDefaultFillAsNoData(bool b)
616 : {
617 1415 : m_bUseDefaultFillAsNoData = b;
618 1415 : }
619 :
620 36 : bool IsWritable() const override
621 : {
622 36 : return !m_poShared->IsReadOnly();
623 : }
624 :
625 891 : const std::string &GetFilename() const override
626 : {
627 891 : return m_poShared->GetFilename();
628 : }
629 :
630 : const std::vector<std::shared_ptr<GDALDimension>> &
631 : GetDimensions() const override;
632 :
633 : const GDALExtendedDataType &GetDataType() const override;
634 :
635 : std::shared_ptr<GDALAttribute>
636 : GetAttribute(const std::string &osName) const override;
637 :
638 : std::vector<std::shared_ptr<GDALAttribute>>
639 : GetAttributes(CSLConstList papszOptions) const override;
640 :
641 : std::shared_ptr<GDALAttribute>
642 : CreateAttribute(const std::string &osName,
643 : const std::vector<GUInt64> &anDimensions,
644 : const GDALExtendedDataType &oDataType,
645 : CSLConstList papszOptions) override;
646 :
647 : bool DeleteAttribute(const std::string &osName,
648 : CSLConstList papszOptions) override;
649 :
650 : const void *GetRawNoDataValue() const override;
651 :
652 : bool SetRawNoDataValue(const void *) override;
653 :
654 : std::vector<GUInt64> GetBlockSize() const override;
655 :
656 : CSLConstList GetStructuralInfo() const override;
657 :
658 114 : const std::string &GetUnit() const override
659 : {
660 114 : return m_osUnit;
661 : }
662 :
663 : bool SetUnit(const std::string &osUnit) override;
664 :
665 : std::shared_ptr<OGRSpatialReference> GetSpatialRef() const override;
666 :
667 : bool SetSpatialRef(const OGRSpatialReference *poSRS) override;
668 :
669 : double GetOffset(bool *pbHasOffset,
670 : GDALDataType *peStorageType) const override;
671 :
672 : double GetScale(bool *pbHasScale,
673 : GDALDataType *peStorageType) const override;
674 :
675 : bool SetOffset(double dfOffset, GDALDataType eStorageType) override;
676 :
677 : bool SetScale(double dfScale, GDALDataType eStorageType) override;
678 :
679 : std::vector<std::shared_ptr<GDALMDArray>>
680 : GetCoordinateVariables() const override;
681 :
682 : bool Resize(const std::vector<GUInt64> &anNewDimSizes,
683 : CSLConstList) override;
684 :
685 477 : int GetGroupId() const
686 : {
687 477 : return m_gid;
688 : }
689 :
690 453 : int GetVarId() const
691 : {
692 453 : return m_varid;
693 : }
694 :
695 7270 : static std::string retrieveName(int gid, int varid)
696 : {
697 7270 : CPLMutexHolderD(&hNCMutex);
698 7270 : char szName[NC_MAX_NAME + 1] = {};
699 7270 : NCDF_ERR(nc_inq_varname(gid, varid, szName));
700 14540 : return szName;
701 : }
702 :
703 : bool Rename(const std::string &osNewName) override;
704 :
705 8 : std::shared_ptr<GDALGroup> GetRootGroup() const override
706 : {
707 8 : return netCDFGroup::Create(m_poShared, nullptr, m_gid);
708 : }
709 :
710 : bool GetRawBlockInfo(const uint64_t *panBlockCoordinates,
711 : GDALMDArrayRawBlockInfo &info) const override;
712 : };
713 :
714 : /************************************************************************/
715 : /* ~netCDFSharedResources() */
716 : /************************************************************************/
717 :
718 367 : netCDFSharedResources::~netCDFSharedResources()
719 : {
720 734 : CPLMutexHolderD(&hNCMutex);
721 :
722 367 : if (m_cdfid > 0)
723 : {
724 : #ifdef NCDF_DEBUG
725 : CPLDebug("GDAL_netCDF", "calling nc_close( %d)", m_cdfid);
726 : #endif
727 364 : int status = GDAL_nc_close(m_cdfid);
728 364 : NCDF_ERR(status);
729 : }
730 :
731 : #ifdef ENABLE_UFFD
732 367 : if (m_pUffdCtx)
733 : {
734 1 : NETCDF_UFFD_UNMAP(m_pUffdCtx);
735 : }
736 : #endif
737 :
738 367 : if (m_fpVSIMEM)
739 4 : VSIFCloseL(m_fpVSIMEM);
740 :
741 : #ifdef ENABLE_NCDUMP
742 367 : if (m_bFileToDestroyAtClosing)
743 0 : VSIUnlink(m_osFilename);
744 : #endif
745 367 : }
746 :
747 : /************************************************************************/
748 : /* NCDFGetParentGroupName() */
749 : /************************************************************************/
750 :
751 741 : static CPLString NCDFGetParentGroupName(int gid)
752 : {
753 741 : int nParentGID = 0;
754 741 : if (nc_inq_grp_parent(gid, &nParentGID) != NC_NOERR)
755 1382 : return std::string();
756 50 : return NCDFGetGroupFullName(nParentGID);
757 : }
758 :
759 : /************************************************************************/
760 : /* netCDFGroup() */
761 : /************************************************************************/
762 :
763 741 : netCDFGroup::netCDFGroup(const std::shared_ptr<netCDFSharedResources> &poShared,
764 741 : int gid)
765 1482 : : GDALGroup(NCDFGetParentGroupName(gid), retrieveName(gid)),
766 2223 : m_poShared(poShared), m_gid(gid)
767 : {
768 1482 : CPLMutexHolderD(&hNCMutex);
769 :
770 741 : if (m_gid == m_poShared->GetCDFId())
771 : {
772 691 : int nFormat = 0;
773 691 : NCDF_ERR(nc_inq_format(m_gid, &nFormat));
774 691 : if (nFormat == NC_FORMAT_CLASSIC)
775 : {
776 170 : m_aosStructuralInfo.SetNameValue("NC_FORMAT", "CLASSIC");
777 : }
778 : #ifdef NC_FORMAT_64BIT_OFFSET
779 521 : else if (nFormat == NC_FORMAT_64BIT_OFFSET)
780 : {
781 0 : m_aosStructuralInfo.SetNameValue("NC_FORMAT", "64BIT_OFFSET");
782 : }
783 : #endif
784 : #ifdef NC_FORMAT_CDF5
785 521 : else if (nFormat == NC_FORMAT_CDF5)
786 : {
787 0 : m_aosStructuralInfo.SetNameValue("NC_FORMAT", "CDF5");
788 : }
789 : #endif
790 521 : else if (nFormat == NC_FORMAT_NETCDF4)
791 : {
792 519 : m_aosStructuralInfo.SetNameValue("NC_FORMAT", "NETCDF4");
793 : }
794 2 : else if (nFormat == NC_FORMAT_NETCDF4_CLASSIC)
795 : {
796 2 : m_aosStructuralInfo.SetNameValue("NC_FORMAT", "NETCDF4_CLASSIC");
797 : }
798 : }
799 :
800 : // Get enuerations associated with the group
801 741 : int nCustomTypeCount = 0;
802 741 : NCDF_ERR(nc_inq_typeids(m_gid, &nCustomTypeCount, nullptr));
803 741 : if (nCustomTypeCount > 0)
804 : {
805 80 : std::vector<int> anCustomTypeIDs(nCustomTypeCount);
806 40 : NCDF_ERR(
807 : nc_inq_typeids(m_gid, &nCustomTypeCount, anCustomTypeIDs.data()));
808 :
809 80 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
810 :
811 353 : for (int i = 0; i < nCustomTypeCount; ++i)
812 : {
813 313 : std::unique_ptr<GDALExtendedDataType> dt;
814 313 : bool bPerfectDataTypeMatch = false;
815 313 : if (BuildDataType(m_gid, /* varId = */ -1, anCustomTypeIDs[i], dt,
816 313 : bPerfectDataTypeMatch) &&
817 626 : dt && dt->GetRAT())
818 : {
819 61 : m_apoTypes.push_back(
820 122 : std::shared_ptr<GDALExtendedDataType>(dt.release()));
821 : }
822 : }
823 : }
824 741 : }
825 :
826 : /************************************************************************/
827 : /* ~netCDFGroup() */
828 : /************************************************************************/
829 :
830 1168 : netCDFGroup::~netCDFGroup()
831 : {
832 1482 : auto poParent = m_poParent.lock();
833 741 : if (poParent)
834 32 : poParent->UnRegisterSubGroup(this);
835 1168 : }
836 :
837 : /************************************************************************/
838 : /* Create() */
839 : /************************************************************************/
840 :
841 : /* static */
842 : std::shared_ptr<netCDFGroup>
843 427 : netCDFGroup::Create(const std::shared_ptr<netCDFSharedResources> &poShared,
844 : int cdfid)
845 : {
846 : auto poGroup =
847 427 : std::shared_ptr<netCDFGroup>(new netCDFGroup(poShared, cdfid));
848 427 : poGroup->SetSelf(poGroup);
849 427 : return poGroup;
850 : }
851 :
852 : /************************************************************************/
853 : /* Create() */
854 : /************************************************************************/
855 :
856 : /* static */
857 : std::shared_ptr<netCDFGroup>
858 167 : netCDFGroup::Create(const std::shared_ptr<netCDFSharedResources> &poShared,
859 : const std::shared_ptr<netCDFGroup> &poParent,
860 : int nSubGroupId)
861 : {
862 167 : auto poSubGroup = netCDFGroup::Create(poShared, nSubGroupId);
863 167 : poSubGroup->m_poParent = poParent;
864 167 : if (poParent)
865 48 : poParent->RegisterSubGroup(poSubGroup.get());
866 167 : return poSubGroup;
867 : }
868 :
869 : /************************************************************************/
870 : /* CreateGroup() */
871 : /************************************************************************/
872 :
873 : std::shared_ptr<GDALGroup>
874 16 : netCDFGroup::CreateGroup(const std::string &osName,
875 : CSLConstList /*papszOptions*/)
876 : {
877 16 : if (osName.empty())
878 : {
879 1 : CPLError(CE_Failure, CPLE_NotSupported,
880 : "Empty group name not supported");
881 1 : return nullptr;
882 : }
883 30 : CPLMutexHolderD(&hNCMutex);
884 15 : m_poShared->SetDefineMode(true);
885 15 : int nSubGroupId = -1;
886 15 : int ret = nc_def_grp(m_gid, osName.c_str(), &nSubGroupId);
887 15 : NCDF_ERR(ret);
888 15 : if (ret != NC_NOERR)
889 3 : return nullptr;
890 12 : return netCDFGroup::Create(
891 24 : m_poShared, std::dynamic_pointer_cast<netCDFGroup>(m_pSelf.lock()),
892 12 : nSubGroupId);
893 : }
894 :
895 : /************************************************************************/
896 : /* CreateDimension() */
897 : /************************************************************************/
898 :
899 : std::shared_ptr<GDALDimension>
900 197 : netCDFGroup::CreateDimension(const std::string &osName,
901 : const std::string &osType, const std::string &,
902 : GUInt64 nSize, CSLConstList papszOptions)
903 : {
904 : const bool bUnlimited =
905 197 : CPLTestBool(CSLFetchNameValueDef(papszOptions, "UNLIMITED", "FALSE"));
906 : if (static_cast<size_t>(nSize) != nSize)
907 : {
908 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid size");
909 : return nullptr;
910 : }
911 394 : CPLMutexHolderD(&hNCMutex);
912 197 : m_poShared->SetDefineMode(true);
913 197 : int nDimId = -1;
914 197 : NCDF_ERR(nc_def_dim(m_gid, osName.c_str(),
915 : static_cast<size_t>(bUnlimited ? 0 : nSize), &nDimId));
916 197 : if (nDimId < 0)
917 3 : return nullptr;
918 194 : return netCDFDimension::Create(
919 388 : m_poShared, std::dynamic_pointer_cast<netCDFGroup>(m_pSelf.lock()),
920 194 : m_gid, nDimId, static_cast<size_t>(nSize), osType);
921 : }
922 :
923 : /************************************************************************/
924 : /* CreateOrGetComplexDataType() */
925 : /************************************************************************/
926 :
927 7 : static int CreateOrGetComplexDataType(int gid, GDALDataType eDT)
928 : {
929 7 : const char *pszName = "";
930 7 : CPL_IGNORE_RET_VAL(pszName); // Make CSA happy
931 7 : int nSubTypeId = NC_NAT;
932 7 : switch (eDT)
933 : {
934 1 : case GDT_CInt16:
935 1 : pszName = "ComplexInt16";
936 1 : nSubTypeId = NC_SHORT;
937 1 : break;
938 1 : case GDT_CInt32:
939 1 : pszName = "ComplexInt32";
940 1 : nSubTypeId = NC_INT;
941 1 : break;
942 2 : case GDT_CFloat32:
943 2 : pszName = "ComplexFloat32";
944 2 : nSubTypeId = NC_FLOAT;
945 2 : break;
946 3 : case GDT_CFloat64:
947 3 : pszName = "ComplexFloat64";
948 3 : nSubTypeId = NC_DOUBLE;
949 3 : break;
950 0 : default:
951 0 : CPLAssert(false);
952 : break;
953 : }
954 7 : int nTypeId = NC_NAT;
955 7 : if (nc_inq_typeid(gid, pszName, &nTypeId) == NC_NOERR)
956 : {
957 : // We could check that the type definition is really the one we want
958 1 : return nTypeId;
959 : }
960 6 : const int nDTSize = GDALGetDataTypeSizeBytes(eDT);
961 6 : NCDF_ERR(nc_def_compound(gid, nDTSize, pszName, &nTypeId));
962 6 : if (nTypeId != NC_NAT)
963 : {
964 6 : NCDF_ERR(nc_insert_compound(gid, nTypeId, "real", 0, nSubTypeId));
965 6 : NCDF_ERR(
966 : nc_insert_compound(gid, nTypeId, "imag", nDTSize / 2, nSubTypeId));
967 : }
968 6 : return nTypeId;
969 : }
970 :
971 : /************************************************************************/
972 : /* CreateOrGetCompoundDataType() */
973 : /************************************************************************/
974 :
975 : static int CreateOrGetType(int gid, const GDALExtendedDataType &oType);
976 :
977 3 : static int CreateOrGetCompoundDataType(int gid,
978 : const GDALExtendedDataType &oType)
979 : {
980 3 : int nTypeId = NC_NAT;
981 3 : if (nc_inq_typeid(gid, oType.GetName().c_str(), &nTypeId) == NC_NOERR)
982 : {
983 : // We could check that the type definition is really the one we want
984 2 : return nTypeId;
985 : }
986 1 : NCDF_ERR(nc_def_compound(gid, oType.GetSize(), oType.GetName().c_str(),
987 : &nTypeId));
988 1 : if (nTypeId != NC_NAT)
989 : {
990 3 : for (const auto &comp : oType.GetComponents())
991 : {
992 2 : int nSubTypeId = CreateOrGetType(gid, comp->GetType());
993 2 : if (nSubTypeId == NC_NAT)
994 0 : return NC_NAT;
995 2 : NCDF_ERR(nc_insert_compound(gid, nTypeId, comp->GetName().c_str(),
996 : comp->GetOffset(), nSubTypeId));
997 : }
998 : }
999 1 : return nTypeId;
1000 : }
1001 :
1002 : /************************************************************************/
1003 : /* CreateOrGetType() */
1004 : /************************************************************************/
1005 :
1006 504 : static int CreateOrGetType(int gid, const GDALExtendedDataType &oType)
1007 : {
1008 504 : int nTypeId = NC_NAT;
1009 504 : const auto typeClass = oType.GetClass();
1010 504 : if (typeClass == GEDTC_NUMERIC)
1011 : {
1012 258 : switch (oType.GetNumericDataType())
1013 : {
1014 86 : case GDT_UInt8:
1015 86 : nTypeId = NC_UBYTE;
1016 86 : break;
1017 1 : case GDT_Int8:
1018 1 : nTypeId = NC_BYTE;
1019 1 : break;
1020 11 : case GDT_UInt16:
1021 11 : nTypeId = NC_USHORT;
1022 11 : break;
1023 10 : case GDT_Int16:
1024 10 : nTypeId = NC_SHORT;
1025 10 : break;
1026 5 : case GDT_UInt32:
1027 5 : nTypeId = NC_UINT;
1028 5 : break;
1029 3 : case GDT_Int32:
1030 3 : nTypeId = NC_INT;
1031 3 : break;
1032 1 : case GDT_UInt64:
1033 1 : nTypeId = NC_UINT64;
1034 1 : break;
1035 1 : case GDT_Int64:
1036 1 : nTypeId = NC_INT64;
1037 1 : break;
1038 4 : case GDT_Float32:
1039 4 : nTypeId = NC_FLOAT;
1040 4 : break;
1041 129 : case GDT_Float64:
1042 129 : nTypeId = NC_DOUBLE;
1043 129 : break;
1044 7 : case GDT_CInt16:
1045 : case GDT_CInt32:
1046 : case GDT_CFloat32:
1047 : case GDT_CFloat64:
1048 : nTypeId =
1049 7 : CreateOrGetComplexDataType(gid, oType.GetNumericDataType());
1050 7 : break;
1051 0 : default:
1052 0 : break;
1053 : }
1054 : }
1055 246 : else if (typeClass == GEDTC_STRING)
1056 : {
1057 243 : nTypeId = NC_STRING;
1058 : }
1059 3 : else if (typeClass == GEDTC_COMPOUND)
1060 : {
1061 3 : nTypeId = CreateOrGetCompoundDataType(gid, oType);
1062 : }
1063 504 : return nTypeId;
1064 : }
1065 :
1066 : /************************************************************************/
1067 : /* CreateMDArray() */
1068 : /************************************************************************/
1069 :
1070 237 : std::shared_ptr<GDALMDArray> netCDFGroup::CreateMDArray(
1071 : const std::string &osName,
1072 : const std::vector<std::shared_ptr<GDALDimension>> &aoDimensions,
1073 : const GDALExtendedDataType &oType, CSLConstList papszOptions)
1074 : {
1075 237 : if (osName.empty())
1076 : {
1077 0 : CPLError(CE_Failure, CPLE_NotSupported,
1078 : "Empty array name not supported");
1079 0 : return nullptr;
1080 : }
1081 474 : CPLMutexHolderD(&hNCMutex);
1082 237 : m_poShared->SetDefineMode(true);
1083 237 : int nVarId = -1;
1084 474 : std::vector<int> anDimIds;
1085 474 : std::vector<std::shared_ptr<GDALDimension>> dims;
1086 552 : for (const auto &dim : aoDimensions)
1087 : {
1088 315 : int nDimId = -1;
1089 315 : auto netCDFDim = std::dynamic_pointer_cast<netCDFDimension>(dim);
1090 315 : if (netCDFDim)
1091 : {
1092 312 : nDimId = netCDFDim->GetId();
1093 : }
1094 : else
1095 : {
1096 3 : if (nc_inq_dimid(m_gid, dim->GetName().c_str(), &nDimId) ==
1097 : NC_NOERR)
1098 : {
1099 4 : netCDFDim = netCDFDimension::Create(
1100 2 : m_poShared,
1101 4 : std::dynamic_pointer_cast<netCDFGroup>(m_pSelf.lock()),
1102 2 : m_gid, nDimId, 0, dim->GetType());
1103 2 : if (netCDFDim->GetSize() != dim->GetSize())
1104 : {
1105 1 : CPLError(CE_Warning, CPLE_AppDefined,
1106 : "Dimension %s already exists, "
1107 : "but with a size of " CPL_FRMT_GUIB,
1108 1 : dim->GetName().c_str(),
1109 1 : static_cast<GUIntBig>(netCDFDim->GetSize()));
1110 : }
1111 : }
1112 : else
1113 : {
1114 : netCDFDim =
1115 2 : std::dynamic_pointer_cast<netCDFDimension>(CreateDimension(
1116 : dim->GetName(), dim->GetType(), dim->GetDirection(),
1117 1 : dim->GetSize(), nullptr));
1118 1 : if (!netCDFDim)
1119 0 : return nullptr;
1120 1 : nDimId = netCDFDim->GetId();
1121 : }
1122 : }
1123 315 : anDimIds.push_back(nDimId);
1124 315 : dims.emplace_back(netCDFDim);
1125 : }
1126 237 : int nTypeId = CreateOrGetType(m_gid, oType);
1127 237 : if (nTypeId == NC_NAT)
1128 : {
1129 0 : CPLError(CE_Failure, CPLE_NotSupported, "Unhandled data type");
1130 0 : return nullptr;
1131 : }
1132 237 : const char *pszType = CSLFetchNameValueDef(papszOptions, "NC_TYPE", "");
1133 391 : if ((EQUAL(pszType, "") || EQUAL(pszType, "NC_CHAR")) && dims.size() == 1 &&
1134 628 : oType.GetClass() == GEDTC_STRING && oType.GetMaxStringLength() > 0)
1135 : {
1136 2 : nTypeId = NC_CHAR;
1137 : auto dimLength =
1138 4 : std::dynamic_pointer_cast<netCDFDimension>(CreateDimension(
1139 4 : aoDimensions[0]->GetName() + "_length", std::string(),
1140 4 : std::string(), oType.GetMaxStringLength(), nullptr));
1141 2 : if (!dimLength)
1142 0 : return nullptr;
1143 2 : anDimIds.push_back(dimLength->GetId());
1144 : }
1145 235 : else if (EQUAL(pszType, "NC_BYTE"))
1146 1 : nTypeId = NC_BYTE;
1147 234 : else if (EQUAL(pszType, "NC_INT64"))
1148 1 : nTypeId = NC_INT64;
1149 233 : else if (EQUAL(pszType, "NC_UINT64"))
1150 1 : nTypeId = NC_UINT64;
1151 237 : NCDF_ERR(nc_def_var(m_gid, osName.c_str(), nTypeId,
1152 : static_cast<int>(anDimIds.size()),
1153 : anDimIds.empty() ? nullptr : anDimIds.data(), &nVarId));
1154 237 : if (nVarId < 0)
1155 3 : return nullptr;
1156 :
1157 234 : const char *pszBlockSize = CSLFetchNameValue(papszOptions, "BLOCKSIZE");
1158 249 : if (pszBlockSize &&
1159 : /* ignore for now BLOCKSIZE for 1-dim string variables created as 2-dim
1160 : */
1161 15 : anDimIds.size() == aoDimensions.size())
1162 : {
1163 15 : auto aszTokens(CPLStringList(CSLTokenizeString2(pszBlockSize, ",", 0)));
1164 15 : if (static_cast<size_t>(aszTokens.size()) != aoDimensions.size())
1165 : {
1166 0 : CPLError(CE_Failure, CPLE_AppDefined,
1167 : "Invalid number of values in BLOCKSIZE");
1168 0 : return nullptr;
1169 : }
1170 15 : if (!aoDimensions.empty())
1171 : {
1172 14 : std::vector<size_t> anChunkSize(aoDimensions.size());
1173 41 : for (size_t i = 0; i < anChunkSize.size(); ++i)
1174 : {
1175 27 : anChunkSize[i] =
1176 27 : static_cast<size_t>(CPLAtoGIntBig(aszTokens[i]));
1177 : }
1178 : int ret =
1179 14 : nc_def_var_chunking(m_gid, nVarId, NC_CHUNKED, &anChunkSize[0]);
1180 14 : NCDF_ERR(ret);
1181 14 : if (ret != NC_NOERR)
1182 0 : return nullptr;
1183 : }
1184 : }
1185 :
1186 234 : const char *pszCompress = CSLFetchNameValue(papszOptions, "COMPRESS");
1187 234 : if (pszCompress && EQUAL(pszCompress, "DEFLATE"))
1188 : {
1189 30 : int nZLevel = NCDF_DEFLATE_LEVEL;
1190 30 : const char *pszZLevel = CSLFetchNameValue(papszOptions, "ZLEVEL");
1191 30 : if (pszZLevel != nullptr)
1192 : {
1193 2 : nZLevel = atoi(pszZLevel);
1194 2 : if (!(nZLevel >= 1 && nZLevel <= 9))
1195 : {
1196 0 : CPLError(CE_Warning, CPLE_IllegalArg,
1197 : "ZLEVEL=%s value not recognised, ignoring.",
1198 : pszZLevel);
1199 0 : nZLevel = NCDF_DEFLATE_LEVEL;
1200 : }
1201 : }
1202 30 : int ret = nc_def_var_deflate(m_gid, nVarId, TRUE /* shuffle */,
1203 : TRUE /* deflate on */, nZLevel);
1204 30 : NCDF_ERR(ret);
1205 30 : if (ret != NC_NOERR)
1206 0 : return nullptr;
1207 : }
1208 :
1209 234 : const char *pszFilter = CSLFetchNameValue(papszOptions, "FILTER");
1210 234 : if (pszFilter)
1211 : {
1212 : #ifdef NC_EFILTER
1213 : const auto aosTokens(
1214 0 : CPLStringList(CSLTokenizeString2(pszFilter, ",", 0)));
1215 0 : if (!aosTokens.empty())
1216 : {
1217 : const unsigned nFilterId =
1218 0 : static_cast<unsigned>(CPLAtoGIntBig(aosTokens[0]));
1219 0 : std::vector<unsigned> anParams;
1220 0 : for (int i = 1; i < aosTokens.size(); ++i)
1221 : {
1222 0 : anParams.push_back(
1223 0 : static_cast<unsigned>(CPLAtoGIntBig(aosTokens[i])));
1224 : }
1225 0 : int ret = nc_def_var_filter(m_gid, nVarId, nFilterId,
1226 0 : anParams.size(), anParams.data());
1227 0 : NCDF_ERR(ret);
1228 0 : if (ret != NC_NOERR)
1229 0 : return nullptr;
1230 : }
1231 : #else
1232 : CPLError(CE_Failure, CPLE_NotSupported,
1233 : "netCDF 4.6 or later needed for FILTER option");
1234 : return nullptr;
1235 : #endif
1236 : }
1237 :
1238 : const bool bChecksum =
1239 234 : CPLTestBool(CSLFetchNameValueDef(papszOptions, "CHECKSUM", "FALSE"));
1240 234 : if (bChecksum)
1241 : {
1242 1 : int ret = nc_def_var_fletcher32(m_gid, nVarId, TRUE);
1243 1 : NCDF_ERR(ret);
1244 1 : if (ret != NC_NOERR)
1245 0 : return nullptr;
1246 : }
1247 :
1248 234 : return netCDFVariable::Create(
1249 468 : m_poShared, std::dynamic_pointer_cast<netCDFGroup>(m_pSelf.lock()),
1250 234 : m_gid, nVarId, dims, papszOptions, true);
1251 : }
1252 :
1253 : /************************************************************************/
1254 : /* CreateAttribute() */
1255 : /************************************************************************/
1256 :
1257 133 : std::shared_ptr<GDALAttribute> netCDFGroup::CreateAttribute(
1258 : const std::string &osName, const std::vector<GUInt64> &anDimensions,
1259 : const GDALExtendedDataType &oDataType, CSLConstList papszOptions)
1260 : {
1261 266 : return netCDFAttribute::Create(
1262 399 : m_poShared, std::dynamic_pointer_cast<netCDFGroup>(m_pSelf.lock()),
1263 266 : m_gid, NC_GLOBAL, osName, anDimensions, oDataType, papszOptions);
1264 : }
1265 :
1266 : /************************************************************************/
1267 : /* DeleteAttribute() */
1268 : /************************************************************************/
1269 :
1270 2 : bool netCDFGroup::DeleteAttribute(const std::string &osName,
1271 : CSLConstList /*papszOptions*/)
1272 : {
1273 4 : CPLMutexHolderD(&hNCMutex);
1274 2 : m_poShared->SetDefineMode(true);
1275 :
1276 2 : int ret = nc_del_att(m_gid, NC_GLOBAL, osName.c_str());
1277 2 : NCDF_ERR(ret);
1278 2 : if (ret != NC_NOERR)
1279 1 : return false;
1280 :
1281 1 : auto it = m_oMapAttributes.find(osName);
1282 1 : if (it != m_oMapAttributes.end())
1283 : {
1284 1 : it->second->Deleted();
1285 1 : m_oMapAttributes.erase(it);
1286 : }
1287 :
1288 1 : return true;
1289 : }
1290 :
1291 : /************************************************************************/
1292 : /* GetGroupNames() */
1293 : /************************************************************************/
1294 :
1295 : std::vector<std::string>
1296 39 : netCDFGroup::GetGroupNames(CSLConstList papszOptions) const
1297 : {
1298 78 : CPLMutexHolderD(&hNCMutex);
1299 39 : int nSubGroups = 0;
1300 39 : NCDF_ERR(nc_inq_grps(m_gid, &nSubGroups, nullptr));
1301 39 : if (nSubGroups == 0)
1302 : {
1303 33 : if (EQUAL(CSLFetchNameValueDef(papszOptions, "GROUP_BY", ""),
1304 : "SAME_DIMENSION"))
1305 : {
1306 4 : std::vector<std::string> names;
1307 4 : std::set<std::string> oSetDimNames;
1308 30 : for (const auto &osArrayName : GetMDArrayNames(nullptr))
1309 : {
1310 56 : const auto poArray = OpenMDArray(osArrayName, nullptr);
1311 28 : const auto &apoDims = poArray->GetDimensions();
1312 28 : if (apoDims.size() == 1)
1313 : {
1314 28 : const auto &osDimName = apoDims[0]->GetName();
1315 28 : if (oSetDimNames.find(osDimName) == oSetDimNames.end())
1316 : {
1317 6 : oSetDimNames.insert(osDimName);
1318 6 : names.emplace_back(osDimName);
1319 : }
1320 : }
1321 : }
1322 2 : return names;
1323 : }
1324 31 : return {};
1325 : }
1326 12 : std::vector<int> anSubGroupdsIds(nSubGroups);
1327 6 : NCDF_ERR(nc_inq_grps(m_gid, nullptr, &anSubGroupdsIds[0]));
1328 12 : std::vector<std::string> names;
1329 6 : names.reserve(nSubGroups);
1330 15 : for (const auto &subgid : anSubGroupdsIds)
1331 : {
1332 9 : char szName[NC_MAX_NAME + 1] = {};
1333 9 : NCDF_ERR(nc_inq_grpname(subgid, szName));
1334 9 : if (GetFullName() == "/" && EQUAL(szName, "METADATA"))
1335 : {
1336 4 : const auto poMetadata = OpenGroup(szName);
1337 2 : if (poMetadata && poMetadata->OpenGroup("ISO_METADATA"))
1338 2 : continue;
1339 : }
1340 7 : names.emplace_back(szName);
1341 : }
1342 6 : return names;
1343 : }
1344 :
1345 : /************************************************************************/
1346 : /* OpenGroup() */
1347 : /************************************************************************/
1348 :
1349 : std::shared_ptr<GDALGroup>
1350 94 : netCDFGroup::OpenGroup(const std::string &osName,
1351 : CSLConstList papszOptions) const
1352 : {
1353 188 : CPLMutexHolderD(&hNCMutex);
1354 94 : int nSubGroups = 0;
1355 : // This is weird but nc_inq_grp_ncid() succeeds on a single group file.
1356 94 : NCDF_ERR(nc_inq_grps(m_gid, &nSubGroups, nullptr));
1357 94 : if (nSubGroups == 0)
1358 : {
1359 29 : if (EQUAL(CSLFetchNameValueDef(papszOptions, "GROUP_BY", ""),
1360 : "SAME_DIMENSION"))
1361 : {
1362 1 : const auto oCandidateGroupNames = GetGroupNames(papszOptions);
1363 1 : for (const auto &osCandidateGroupName : oCandidateGroupNames)
1364 : {
1365 1 : if (osCandidateGroupName == osName)
1366 : {
1367 1 : auto poThisGroup = netCDFGroup::Create(m_poShared, m_gid);
1368 2 : return netCDFVirtualGroupBySameDimension::Create(
1369 1 : poThisGroup, osName);
1370 : }
1371 : }
1372 : }
1373 28 : return nullptr;
1374 : }
1375 65 : int nSubGroupId = 0;
1376 101 : if (nc_inq_grp_ncid(m_gid, osName.c_str(), &nSubGroupId) != NC_NOERR ||
1377 36 : nSubGroupId <= 0)
1378 29 : return nullptr;
1379 36 : return netCDFGroup::Create(
1380 72 : m_poShared, std::dynamic_pointer_cast<netCDFGroup>(m_pSelf.lock()),
1381 36 : nSubGroupId);
1382 : }
1383 :
1384 : /************************************************************************/
1385 : /* GetMDArrayNames() */
1386 : /************************************************************************/
1387 :
1388 : std::vector<std::string>
1389 369 : netCDFGroup::GetMDArrayNames(CSLConstList papszOptions) const
1390 : {
1391 738 : CPLMutexHolderD(&hNCMutex);
1392 369 : int nVars = 0;
1393 369 : NCDF_ERR(nc_inq_nvars(m_gid, &nVars));
1394 369 : if (nVars == 0)
1395 21 : return {};
1396 696 : std::vector<int> anVarIds(nVars);
1397 348 : NCDF_ERR(nc_inq_varids(m_gid, nullptr, &anVarIds[0]));
1398 696 : std::vector<std::string> names;
1399 348 : names.reserve(nVars);
1400 : const bool bAll =
1401 348 : CPLTestBool(CSLFetchNameValueDef(papszOptions, "SHOW_ALL", "NO"));
1402 : const bool bZeroDim =
1403 695 : bAll ||
1404 347 : CPLTestBool(CSLFetchNameValueDef(papszOptions, "SHOW_ZERO_DIM", "NO"));
1405 : const bool bCoordinates =
1406 348 : bAll || CPLTestBool(CSLFetchNameValueDef(papszOptions,
1407 348 : "SHOW_COORDINATES", "YES"));
1408 : const bool bBounds =
1409 695 : bAll ||
1410 347 : CPLTestBool(CSLFetchNameValueDef(papszOptions, "SHOW_BOUNDS", "YES"));
1411 : const bool bIndexing =
1412 695 : bAll ||
1413 347 : CPLTestBool(CSLFetchNameValueDef(papszOptions, "SHOW_INDEXING", "YES"));
1414 : const bool bTime =
1415 695 : bAll ||
1416 347 : CPLTestBool(CSLFetchNameValueDef(papszOptions, "SHOW_TIME", "YES"));
1417 696 : std::set<std::string> ignoreList;
1418 348 : if (!bCoordinates || !bBounds)
1419 : {
1420 12 : for (const auto &varid : anVarIds)
1421 : {
1422 9 : char **papszTokens = nullptr;
1423 9 : if (!bCoordinates)
1424 : {
1425 5 : char *pszTemp = nullptr;
1426 5 : if (NCDFGetAttr(m_gid, varid, "coordinates", &pszTemp) ==
1427 : CE_None)
1428 1 : papszTokens = NCDFTokenizeCoordinatesAttribute(pszTemp);
1429 5 : CPLFree(pszTemp);
1430 : }
1431 9 : if (!bBounds)
1432 : {
1433 4 : char *pszTemp = nullptr;
1434 4 : if (NCDFGetAttr(m_gid, varid, "bounds", &pszTemp) == CE_None &&
1435 4 : pszTemp != nullptr && !EQUAL(pszTemp, ""))
1436 2 : papszTokens = CSLAddString(papszTokens, pszTemp);
1437 4 : CPLFree(pszTemp);
1438 : }
1439 15 : for (char **iter = papszTokens; iter && iter[0]; ++iter)
1440 6 : ignoreList.insert(*iter);
1441 9 : CSLDestroy(papszTokens);
1442 : }
1443 : }
1444 :
1445 348 : const bool bGroupBySameDimension = EQUAL(
1446 : CSLFetchNameValueDef(papszOptions, "GROUP_BY", ""), "SAME_DIMENSION");
1447 1688 : for (const auto &varid : anVarIds)
1448 : {
1449 1340 : int nVarDims = 0;
1450 1340 : NCDF_ERR(nc_inq_varndims(m_gid, varid, &nVarDims));
1451 1340 : if (nVarDims == 0 && !bZeroDim)
1452 : {
1453 163 : continue;
1454 : }
1455 1195 : if (nVarDims == 1 && bGroupBySameDimension)
1456 : {
1457 14 : continue;
1458 : }
1459 :
1460 1181 : char szName[NC_MAX_NAME + 1] = {};
1461 1181 : NCDF_ERR(nc_inq_varname(m_gid, varid, szName));
1462 1181 : if (!bIndexing && nVarDims == 1)
1463 : {
1464 1 : int nDimId = 0;
1465 1 : NCDF_ERR(nc_inq_vardimid(m_gid, varid, &nDimId));
1466 1 : char szDimName[NC_MAX_NAME + 1] = {};
1467 1 : NCDF_ERR(nc_inq_dimname(m_gid, nDimId, szDimName));
1468 1 : if (strcmp(szDimName, szName) == 0)
1469 : {
1470 1 : continue;
1471 : }
1472 : }
1473 :
1474 1180 : if (!bTime)
1475 : {
1476 14 : char *pszTemp = nullptr;
1477 14 : bool bSkip = false;
1478 14 : if (NCDFGetAttr(m_gid, varid, "standard_name", &pszTemp) == CE_None)
1479 : {
1480 10 : bSkip = pszTemp && strcmp(pszTemp, "time") == 0;
1481 : }
1482 14 : CPLFree(pszTemp);
1483 14 : if (bSkip)
1484 : {
1485 3 : continue;
1486 : }
1487 : }
1488 :
1489 1177 : if (ignoreList.find(szName) == ignoreList.end())
1490 : {
1491 1171 : names.emplace_back(szName);
1492 : }
1493 : }
1494 348 : return names;
1495 : }
1496 :
1497 : /************************************************************************/
1498 : /* Rename() */
1499 : /************************************************************************/
1500 :
1501 5 : bool netCDFGroup::Rename(const std::string &osNewName)
1502 : {
1503 5 : if (m_poShared->IsReadOnly())
1504 : {
1505 1 : CPLError(CE_Failure, CPLE_AppDefined,
1506 : "Rename() not supported on read-only file");
1507 1 : return false;
1508 : }
1509 4 : if (osNewName.empty())
1510 : {
1511 1 : CPLError(CE_Failure, CPLE_NotSupported, "Empty name not supported");
1512 1 : return false;
1513 : }
1514 3 : if (m_osName == "/")
1515 : {
1516 1 : CPLError(CE_Failure, CPLE_NotSupported, "Cannot rename root group");
1517 1 : return false;
1518 : }
1519 :
1520 4 : CPLMutexHolderD(&hNCMutex);
1521 2 : m_poShared->SetDefineMode(true);
1522 :
1523 2 : int ret = nc_rename_grp(m_gid, osNewName.c_str());
1524 2 : NCDF_ERR(ret);
1525 2 : if (ret != NC_NOERR)
1526 1 : return false;
1527 :
1528 1 : BaseRename(osNewName);
1529 :
1530 1 : return true;
1531 : }
1532 :
1533 : /************************************************************************/
1534 : /* NotifyChildrenOfRenaming() */
1535 : /************************************************************************/
1536 :
1537 2 : void netCDFGroup::NotifyChildrenOfRenaming()
1538 : {
1539 3 : for (const auto poSubGroup : m_oSetGroups)
1540 1 : poSubGroup->ParentRenamed(m_osFullName);
1541 :
1542 3 : for (const auto poDim : m_oSetDimensions)
1543 1 : poDim->ParentRenamed(m_osFullName);
1544 :
1545 4 : for (const auto poArray : m_oSetArrays)
1546 2 : poArray->ParentRenamed(m_osFullName);
1547 :
1548 4 : for (const auto &iter : m_oMapAttributes)
1549 2 : iter.second->ParentRenamed(m_osFullName);
1550 2 : }
1551 :
1552 : /************************************************************************/
1553 : /* OpenMDArray() */
1554 : /************************************************************************/
1555 :
1556 : std::shared_ptr<GDALMDArray>
1557 1450 : netCDFGroup::OpenMDArray(const std::string &osName,
1558 : CSLConstList papszOptions) const
1559 : {
1560 2900 : CPLMutexHolderD(&hNCMutex);
1561 1450 : int nVarId = 0;
1562 1450 : if (nc_inq_varid(m_gid, osName.c_str(), &nVarId) != NC_NOERR)
1563 35 : return nullptr;
1564 :
1565 : auto poVar = netCDFVariable::Create(
1566 2830 : m_poShared, std::dynamic_pointer_cast<netCDFGroup>(m_pSelf.lock()),
1567 1415 : m_gid, nVarId, std::vector<std::shared_ptr<GDALDimension>>(),
1568 4245 : papszOptions, false);
1569 1415 : if (poVar)
1570 : {
1571 1415 : poVar->SetUseDefaultFillAsNoData(CPLTestBool(CSLFetchNameValueDef(
1572 : papszOptions, "USE_DEFAULT_FILL_AS_NODATA", "NO")));
1573 : }
1574 1415 : return poVar;
1575 : }
1576 :
1577 : /************************************************************************/
1578 : /* GetDimensions() */
1579 : /************************************************************************/
1580 :
1581 : std::vector<std::shared_ptr<GDALDimension>>
1582 55 : netCDFGroup::GetDimensions(CSLConstList) const
1583 : {
1584 110 : CPLMutexHolderD(&hNCMutex);
1585 55 : int nbDims = 0;
1586 55 : NCDF_ERR(nc_inq_ndims(m_gid, &nbDims));
1587 55 : if (nbDims == 0)
1588 2 : return {};
1589 106 : std::vector<int> dimids(nbDims);
1590 53 : NCDF_ERR(nc_inq_dimids(m_gid, &nbDims, &dimids[0], FALSE));
1591 106 : std::vector<std::shared_ptr<GDALDimension>> res;
1592 161 : for (int i = 0; i < nbDims; i++)
1593 : {
1594 216 : auto poCachedDim = m_poShared->GetCachedDimension(dimids[i]);
1595 108 : if (poCachedDim == nullptr)
1596 : {
1597 97 : poCachedDim = netCDFDimension::Create(
1598 97 : m_poShared,
1599 194 : std::dynamic_pointer_cast<netCDFGroup>(m_pSelf.lock()), m_gid,
1600 291 : dimids[i], 0, std::string());
1601 97 : m_poShared->CacheDimension(dimids[i], poCachedDim);
1602 : }
1603 108 : res.emplace_back(poCachedDim);
1604 : }
1605 53 : return res;
1606 : }
1607 :
1608 : /************************************************************************/
1609 : /* GetAttribute() */
1610 : /************************************************************************/
1611 :
1612 : static const char *const apszJSONMDKeys[] = {
1613 : "ISO_METADATA", "ESA_METADATA", "EOP_METADATA",
1614 : "QA_STATISTICS", "GRANULE_DESCRIPTION", "ALGORITHM_SETTINGS"};
1615 :
1616 : std::shared_ptr<GDALAttribute>
1617 14 : netCDFGroup::GetAttribute(const std::string &osName) const
1618 : {
1619 28 : CPLMutexHolderD(&hNCMutex);
1620 14 : int nAttId = -1;
1621 14 : if (nc_inq_attid(m_gid, NC_GLOBAL, osName.c_str(), &nAttId) != NC_NOERR)
1622 : {
1623 2 : if (GetFullName() == "/")
1624 : {
1625 8 : for (const char *key : apszJSONMDKeys)
1626 : {
1627 7 : if (osName == key)
1628 : {
1629 2 : auto poMetadata = OpenGroup("METADATA");
1630 1 : if (poMetadata)
1631 : {
1632 : auto poSubMetadata =
1633 : std::dynamic_pointer_cast<netCDFGroup>(
1634 2 : poMetadata->OpenGroup(key));
1635 1 : if (poSubMetadata)
1636 : {
1637 : const auto osJson =
1638 1 : NCDFReadMetadataAsJson(poSubMetadata->m_gid);
1639 2 : return std::make_shared<GDALAttributeString>(
1640 3 : GetFullName(), key, osJson, GEDTST_JSON);
1641 : }
1642 : }
1643 0 : break;
1644 : }
1645 : }
1646 : }
1647 1 : return nullptr;
1648 : }
1649 24 : return netCDFAttribute::Create(
1650 36 : m_poShared, std::dynamic_pointer_cast<netCDFGroup>(m_pSelf.lock()),
1651 24 : m_gid, NC_GLOBAL, osName);
1652 : }
1653 :
1654 : /************************************************************************/
1655 : /* GetAttributes() */
1656 : /************************************************************************/
1657 :
1658 : std::vector<std::shared_ptr<GDALAttribute>>
1659 46 : netCDFGroup::GetAttributes(CSLConstList) const
1660 : {
1661 92 : CPLMutexHolderD(&hNCMutex);
1662 46 : std::vector<std::shared_ptr<GDALAttribute>> res;
1663 46 : int nbAttr = 0;
1664 46 : NCDF_ERR(nc_inq_varnatts(m_gid, NC_GLOBAL, &nbAttr));
1665 46 : res.reserve(nbAttr);
1666 188 : for (int i = 0; i < nbAttr; i++)
1667 : {
1668 : char szAttrName[NC_MAX_NAME + 1];
1669 142 : szAttrName[0] = 0;
1670 142 : NCDF_ERR(nc_inq_attname(m_gid, NC_GLOBAL, i, szAttrName));
1671 142 : if (!EQUAL(szAttrName, "_NCProperties"))
1672 : {
1673 568 : res.emplace_back(netCDFAttribute::Create(
1674 142 : m_poShared,
1675 284 : std::dynamic_pointer_cast<netCDFGroup>(m_pSelf.lock()), m_gid,
1676 142 : NC_GLOBAL, szAttrName));
1677 : }
1678 : }
1679 :
1680 46 : if (GetFullName() == "/")
1681 : {
1682 123 : auto poMetadata = OpenGroup("METADATA");
1683 41 : if (poMetadata)
1684 : {
1685 14 : for (const char *key : apszJSONMDKeys)
1686 : {
1687 : auto poSubMetadata = std::dynamic_pointer_cast<netCDFGroup>(
1688 36 : poMetadata->OpenGroup(key));
1689 12 : if (poSubMetadata)
1690 : {
1691 : const auto osJson =
1692 2 : NCDFReadMetadataAsJson(poSubMetadata->m_gid);
1693 4 : res.emplace_back(std::make_shared<GDALAttributeString>(
1694 6 : GetFullName(), key, osJson, GEDTST_JSON));
1695 : }
1696 : }
1697 : }
1698 : }
1699 :
1700 92 : return res;
1701 : }
1702 :
1703 : /************************************************************************/
1704 : /* GetStructuralInfo() */
1705 : /************************************************************************/
1706 :
1707 13 : CSLConstList netCDFGroup::GetStructuralInfo() const
1708 : {
1709 13 : return m_aosStructuralInfo.List();
1710 : }
1711 :
1712 : /************************************************************************/
1713 : /* ClearStatistics() */
1714 : /************************************************************************/
1715 :
1716 1 : void netCDFGroup::ClearStatistics()
1717 : {
1718 1 : m_poShared->GetPAM()->ClearStatistics();
1719 1 : }
1720 :
1721 : /************************************************************************/
1722 : /* netCDFVirtualGroupBySameDimension() */
1723 : /************************************************************************/
1724 :
1725 1 : netCDFVirtualGroupBySameDimension::netCDFVirtualGroupBySameDimension(
1726 1 : const std::shared_ptr<netCDFGroup> &poGroup, const std::string &osDimName)
1727 1 : : GDALGroup(poGroup->GetName(), osDimName), m_poGroup(poGroup),
1728 2 : m_osDimName(osDimName)
1729 : {
1730 1 : }
1731 :
1732 : /************************************************************************/
1733 : /* Create() */
1734 : /************************************************************************/
1735 :
1736 : /* static */ std::shared_ptr<netCDFVirtualGroupBySameDimension>
1737 1 : netCDFVirtualGroupBySameDimension::Create(
1738 : const std::shared_ptr<netCDFGroup> &poGroup, const std::string &osDimName)
1739 : {
1740 : auto poNewGroup = std::shared_ptr<netCDFVirtualGroupBySameDimension>(
1741 1 : new netCDFVirtualGroupBySameDimension(poGroup, osDimName));
1742 1 : poNewGroup->SetSelf(poNewGroup);
1743 1 : return poNewGroup;
1744 : }
1745 :
1746 : /************************************************************************/
1747 : /* GetMDArrayNames() */
1748 : /************************************************************************/
1749 :
1750 : std::vector<std::string>
1751 1 : netCDFVirtualGroupBySameDimension::GetMDArrayNames(CSLConstList) const
1752 : {
1753 2 : const auto srcNames = m_poGroup->GetMDArrayNames(nullptr);
1754 1 : std::vector<std::string> names;
1755 15 : for (const auto &srcName : srcNames)
1756 : {
1757 28 : auto poArray = m_poGroup->OpenMDArray(srcName, nullptr);
1758 14 : if (poArray)
1759 : {
1760 14 : const auto &apoArrayDims = poArray->GetDimensions();
1761 28 : if (apoArrayDims.size() == 1 &&
1762 14 : apoArrayDims[0]->GetName() == m_osDimName)
1763 : {
1764 7 : names.emplace_back(srcName);
1765 : }
1766 : }
1767 : }
1768 2 : return names;
1769 : }
1770 :
1771 : /************************************************************************/
1772 : /* OpenMDArray() */
1773 : /************************************************************************/
1774 :
1775 : std::shared_ptr<GDALMDArray>
1776 7 : netCDFVirtualGroupBySameDimension::OpenMDArray(const std::string &osName,
1777 : CSLConstList papszOptions) const
1778 : {
1779 7 : return m_poGroup->OpenMDArray(osName, papszOptions);
1780 : }
1781 :
1782 : /************************************************************************/
1783 : /* netCDFDimension() */
1784 : /************************************************************************/
1785 :
1786 2016 : netCDFDimension::netCDFDimension(
1787 : const std::shared_ptr<netCDFSharedResources> &poShared, int cfid, int dimid,
1788 2016 : size_t nForcedSize, const std::string &osType)
1789 4032 : : GDALDimension(NCDFGetGroupFullName(cfid), retrieveName(cfid, dimid),
1790 : osType, // type
1791 4032 : std::string(), // direction
1792 1824 : nForcedSize ? nForcedSize : retrieveSize(cfid, dimid)),
1793 6048 : m_poShared(poShared), m_gid(cfid), m_dimid(dimid)
1794 : {
1795 2016 : if (m_osType.empty() && nForcedSize == 0)
1796 : {
1797 : auto var =
1798 3648 : std::dynamic_pointer_cast<netCDFVariable>(GetIndexingVariable());
1799 1824 : if (var)
1800 : {
1801 453 : const auto gid = var->GetGroupId();
1802 453 : const auto varid = var->GetVarId();
1803 453 : const auto varname = var->GetName().c_str();
1804 893 : if (NCDFIsVarLongitude(gid, varid, varname) ||
1805 440 : NCDFIsVarProjectionX(gid, varid, varname))
1806 : {
1807 105 : m_osType = GDAL_DIM_TYPE_HORIZONTAL_X;
1808 315 : auto attrPositive = var->GetAttribute(CF_UNITS);
1809 105 : if (attrPositive)
1810 : {
1811 100 : const auto val = attrPositive->ReadAsString();
1812 100 : if (val)
1813 : {
1814 100 : if (EQUAL(val, CF_DEGREES_EAST))
1815 : {
1816 10 : m_osDirection = "EAST";
1817 : }
1818 : }
1819 : }
1820 : }
1821 681 : else if (NCDFIsVarLatitude(gid, varid, varname) ||
1822 333 : NCDFIsVarProjectionY(gid, varid, varname))
1823 : {
1824 102 : m_osType = GDAL_DIM_TYPE_HORIZONTAL_Y;
1825 306 : auto attrPositive = var->GetAttribute(CF_UNITS);
1826 102 : if (attrPositive)
1827 : {
1828 97 : const auto val = attrPositive->ReadAsString();
1829 97 : if (val)
1830 : {
1831 97 : if (EQUAL(val, CF_DEGREES_NORTH))
1832 : {
1833 12 : m_osDirection = "NORTH";
1834 : }
1835 : }
1836 : }
1837 : }
1838 246 : else if (NCDFIsVarVerticalCoord(gid, varid, varname))
1839 : {
1840 48 : m_osType = GDAL_DIM_TYPE_VERTICAL;
1841 144 : auto attrPositive = var->GetAttribute("positive");
1842 48 : if (attrPositive)
1843 : {
1844 39 : const auto val = attrPositive->ReadAsString();
1845 39 : if (val)
1846 : {
1847 39 : if (EQUAL(val, "up"))
1848 : {
1849 39 : m_osDirection = "UP";
1850 : }
1851 0 : else if (EQUAL(val, "down"))
1852 : {
1853 0 : m_osDirection = "DOWN";
1854 : }
1855 : }
1856 : }
1857 : }
1858 198 : else if (NCDFIsVarTimeCoord(gid, varid, varname))
1859 : {
1860 49 : m_osType = GDAL_DIM_TYPE_TEMPORAL;
1861 : }
1862 : }
1863 : }
1864 2016 : }
1865 :
1866 : /************************************************************************/
1867 : /* ~netCDFDimension() */
1868 : /************************************************************************/
1869 :
1870 2016 : netCDFDimension::~netCDFDimension()
1871 : {
1872 4032 : auto poParent = m_poParent.lock();
1873 2016 : if (poParent)
1874 443 : poParent->UnRegisterDimension(this);
1875 2016 : }
1876 :
1877 : /************************************************************************/
1878 : /* Create() */
1879 : /************************************************************************/
1880 :
1881 : /* static */
1882 : std::shared_ptr<netCDFDimension>
1883 2016 : netCDFDimension::Create(const std::shared_ptr<netCDFSharedResources> &poShared,
1884 : const std::shared_ptr<netCDFGroup> &poParent, int cfid,
1885 : int dimid, size_t nForcedSize,
1886 : const std::string &osType)
1887 : {
1888 : auto poDim(std::make_shared<netCDFDimension>(poShared, cfid, dimid,
1889 2016 : nForcedSize, osType));
1890 2016 : poDim->m_poParent = poParent;
1891 2016 : if (poParent)
1892 807 : poParent->RegisterDimension(poDim.get());
1893 2016 : return poDim;
1894 : }
1895 :
1896 : /************************************************************************/
1897 : /* GetIndexingVariable() */
1898 : /************************************************************************/
1899 :
1900 : namespace
1901 : {
1902 : struct SetIsInGetIndexingVariable
1903 : {
1904 : netCDFSharedResources *m_poShared;
1905 :
1906 895 : explicit SetIsInGetIndexingVariable(
1907 : netCDFSharedResources *poSharedResources)
1908 895 : : m_poShared(poSharedResources)
1909 : {
1910 895 : m_poShared->SetIsInGetIndexingVariable(true);
1911 895 : }
1912 :
1913 895 : ~SetIsInGetIndexingVariable()
1914 895 : {
1915 895 : m_poShared->SetIsInGetIndexingVariable(false);
1916 895 : }
1917 : };
1918 : } // namespace
1919 :
1920 2073 : std::shared_ptr<GDALMDArray> netCDFDimension::GetIndexingVariable() const
1921 : {
1922 2073 : if (m_poShared->GetIsInIndexingVariable())
1923 1178 : return nullptr;
1924 :
1925 1790 : SetIsInGetIndexingVariable setterIsInGetIndexingVariable(m_poShared.get());
1926 :
1927 1790 : CPLMutexHolderD(&hNCMutex);
1928 :
1929 : // First try to find a variable in this group with the same name as the
1930 : // dimension
1931 895 : int nVarId = 0;
1932 895 : if (nc_inq_varid(m_gid, GetName().c_str(), &nVarId) == NC_NOERR)
1933 : {
1934 581 : int nDims = 0;
1935 581 : NCDF_ERR(nc_inq_varndims(m_gid, nVarId, &nDims));
1936 581 : int nVarType = NC_NAT;
1937 581 : NCDF_ERR(nc_inq_vartype(m_gid, nVarId, &nVarType));
1938 581 : if (nDims == 1 || (nDims == 2 && nVarType == NC_CHAR))
1939 : {
1940 581 : int anDimIds[2] = {};
1941 581 : NCDF_ERR(nc_inq_vardimid(m_gid, nVarId, anDimIds));
1942 581 : if (anDimIds[0] == m_dimid)
1943 : {
1944 581 : if (nDims == 2)
1945 : {
1946 : // Check that there is no variable with the same of the
1947 : // second dimension.
1948 2 : char szExtraDim[NC_MAX_NAME + 1] = {};
1949 2 : NCDF_ERR(nc_inq_dimname(m_gid, anDimIds[1], szExtraDim));
1950 : int nUnused;
1951 2 : if (nc_inq_varid(m_gid, szExtraDim, &nUnused) == NC_NOERR)
1952 0 : return nullptr;
1953 : }
1954 :
1955 581 : return netCDFVariable::Create(
1956 1162 : m_poShared, m_poParent.lock(), m_gid, nVarId,
1957 1162 : std::vector<std::shared_ptr<GDALDimension>>(), nullptr,
1958 581 : false);
1959 : }
1960 : }
1961 : }
1962 :
1963 : // Otherwise explore the variables in this group to find one that has a
1964 : // "coordinates" attribute that references this dimension. If so, let's
1965 : // return the variable pointed by the value of "coordinates" as the indexing
1966 : // variable. This assumes that there is no other variable that would use
1967 : // another variable for the matching dimension of its "coordinates".
1968 628 : netCDFGroup oGroup(m_poShared, m_gid);
1969 628 : const auto arrayNames = oGroup.GetMDArrayNames(nullptr);
1970 314 : std::shared_ptr<GDALMDArray> candidateIndexingVariable;
1971 1217 : for (const auto &arrayName : arrayNames)
1972 : {
1973 923 : auto poArray = oGroup.OpenMDArray(arrayName, nullptr);
1974 : const auto poArrayNC =
1975 923 : std::dynamic_pointer_cast<netCDFVariable>(poArray);
1976 923 : if (!poArrayNC)
1977 0 : continue;
1978 :
1979 923 : const auto &apoArrayDims = poArray->GetDimensions();
1980 923 : if (apoArrayDims.size() == 1)
1981 : {
1982 180 : const auto &poArrayDim = apoArrayDims[0];
1983 : const auto poArrayDimNC =
1984 180 : std::dynamic_pointer_cast<netCDFDimension>(poArrayDim);
1985 360 : if (poArrayDimNC && poArrayDimNC->m_gid == m_gid &&
1986 180 : poArrayDimNC->m_dimid == m_dimid)
1987 : {
1988 : // If the array doesn't have a coordinates variable, but is a 1D
1989 : // array indexed by our dimension, then use it as the indexing
1990 : // variable, provided it is the only such variable.
1991 59 : if (!candidateIndexingVariable)
1992 : {
1993 51 : candidateIndexingVariable = std::move(poArray);
1994 : }
1995 : else
1996 : {
1997 8 : return nullptr;
1998 : }
1999 51 : continue;
2000 : }
2001 : }
2002 :
2003 1728 : const auto poCoordinates = poArray->GetAttribute("coordinates");
2004 864 : if (!(poCoordinates &&
2005 864 : poCoordinates->GetDataType().GetClass() == GEDTC_STRING))
2006 : {
2007 852 : continue;
2008 : }
2009 :
2010 : // Check that the arrays has as many dimensions as its coordinates
2011 : // attribute
2012 : const CPLStringList aosCoordinates(
2013 12 : NCDFTokenizeCoordinatesAttribute(poCoordinates->ReadAsString()));
2014 12 : if (apoArrayDims.size() != static_cast<size_t>(aosCoordinates.size()))
2015 0 : continue;
2016 :
2017 26 : for (size_t i = 0; i < apoArrayDims.size(); ++i)
2018 : {
2019 26 : const auto &poArrayDim = apoArrayDims[i];
2020 : const auto poArrayDimNC =
2021 26 : std::dynamic_pointer_cast<netCDFDimension>(poArrayDim);
2022 :
2023 : // Check if the array is indexed by the current dimension
2024 52 : if (!(poArrayDimNC && poArrayDimNC->m_gid == m_gid &&
2025 26 : poArrayDimNC->m_dimid == m_dimid))
2026 : {
2027 14 : continue;
2028 : }
2029 :
2030 : // Caution: some datasets have their coordinates variables in the
2031 : // same order than dimensions (i.e. from slowest varying to
2032 : // fastest varying), while others have the coordinates variables
2033 : // in the opposite order.
2034 : // Assume same order by default, but if we find the first variable
2035 : // to be of longitude/X type, then assume the opposite order.
2036 12 : bool coordinatesInSameOrderThanDimensions = true;
2037 12 : if (aosCoordinates.size() > 1)
2038 : {
2039 12 : int bFirstGroupId = -1;
2040 12 : int nFirstVarId = -1;
2041 12 : if (NCDFResolveVar(poArrayNC->GetGroupId(), aosCoordinates[0],
2042 24 : &bFirstGroupId, &nVarId, false) == CE_None &&
2043 12 : (NCDFIsVarLongitude(bFirstGroupId, nFirstVarId,
2044 4 : aosCoordinates[0]) ||
2045 4 : NCDFIsVarProjectionX(bFirstGroupId, nFirstVarId,
2046 : aosCoordinates[0])))
2047 : {
2048 8 : coordinatesInSameOrderThanDimensions = false;
2049 : }
2050 : }
2051 :
2052 12 : int nIndexingVarGroupId = -1;
2053 12 : int nIndexingVarId = -1;
2054 : const size_t nIdxCoordinate = coordinatesInSameOrderThanDimensions
2055 12 : ? i
2056 8 : : aosCoordinates.size() - 1 - i;
2057 12 : if (NCDFResolveVar(
2058 : poArrayNC->GetGroupId(), aosCoordinates[nIdxCoordinate],
2059 12 : &nIndexingVarGroupId, &nIndexingVarId, false) == CE_None)
2060 : {
2061 12 : return netCDFVariable::Create(
2062 24 : m_poShared, m_poParent.lock(), nIndexingVarGroupId,
2063 : nIndexingVarId,
2064 24 : std::vector<std::shared_ptr<GDALDimension>>(), nullptr,
2065 12 : false);
2066 : }
2067 : }
2068 : }
2069 :
2070 294 : return candidateIndexingVariable;
2071 : }
2072 :
2073 : /************************************************************************/
2074 : /* Rename() */
2075 : /************************************************************************/
2076 :
2077 4 : bool netCDFDimension::Rename(const std::string &osNewName)
2078 : {
2079 4 : if (m_poShared->IsReadOnly())
2080 : {
2081 1 : CPLError(CE_Failure, CPLE_AppDefined,
2082 : "Rename() not supported on read-only file");
2083 1 : return false;
2084 : }
2085 3 : if (osNewName.empty())
2086 : {
2087 1 : CPLError(CE_Failure, CPLE_NotSupported, "Empty name not supported");
2088 1 : return false;
2089 : }
2090 4 : CPLMutexHolderD(&hNCMutex);
2091 2 : m_poShared->SetDefineMode(true);
2092 :
2093 2 : int ret = nc_rename_dim(m_gid, m_dimid, osNewName.c_str());
2094 2 : NCDF_ERR(ret);
2095 2 : if (ret != NC_NOERR)
2096 1 : return false;
2097 :
2098 1 : BaseRename(osNewName);
2099 :
2100 1 : return true;
2101 : }
2102 :
2103 : /************************************************************************/
2104 : /* netCDFVariable() */
2105 : /************************************************************************/
2106 :
2107 2248 : netCDFVariable::netCDFVariable(
2108 : const std::shared_ptr<netCDFSharedResources> &poShared, int gid, int varid,
2109 : const std::vector<std::shared_ptr<GDALDimension>> &dims,
2110 2248 : CSLConstList papszOptions)
2111 4496 : : GDALAbstractMDArray(NCDFGetGroupFullName(gid), retrieveName(gid, varid)),
2112 4496 : GDALPamMDArray(NCDFGetGroupFullName(gid), retrieveName(gid, varid),
2113 : poShared->GetPAM()),
2114 11240 : m_poShared(poShared), m_gid(gid), m_varid(varid), m_dims(dims)
2115 : {
2116 : {
2117 : // Cf https://docs.unidata.ucar.edu/netcdf-c/current/group__variables.html#gae6b59e92d1140b5fec56481b0f41b610
2118 2248 : size_t nRawDataChunkCacheSize = 0;
2119 2248 : size_t nChunkSlots = 0;
2120 2248 : float fPreemption = 0.0f;
2121 : int ret =
2122 2248 : nc_get_var_chunk_cache(m_gid, m_varid, &nRawDataChunkCacheSize,
2123 : &nChunkSlots, &fPreemption);
2124 2248 : if (ret == NC_NOERR)
2125 : {
2126 1736 : if (const char *pszVar = CSLFetchNameValue(
2127 : papszOptions, "RAW_DATA_CHUNK_CACHE_SIZE"))
2128 : {
2129 1 : nRawDataChunkCacheSize =
2130 1 : static_cast<size_t>(std::min<unsigned long long>(
2131 3 : std::strtoull(pszVar, nullptr, 10),
2132 1 : std::numeric_limits<size_t>::max()));
2133 : }
2134 1736 : if (const char *pszVar =
2135 1736 : CSLFetchNameValue(papszOptions, "CHUNK_SLOTS"))
2136 : {
2137 1 : nChunkSlots = static_cast<size_t>(std::min<unsigned long long>(
2138 3 : std::strtoull(pszVar, nullptr, 10),
2139 1 : std::numeric_limits<size_t>::max()));
2140 : }
2141 1736 : if (const char *pszVar =
2142 1736 : CSLFetchNameValue(papszOptions, "PREEMPTION"))
2143 : {
2144 1 : fPreemption = std::max(
2145 1 : 0.0f, std::min(1.0f, static_cast<float>(CPLAtof(pszVar))));
2146 : }
2147 1736 : NCDF_ERR(nc_set_var_chunk_cache(m_gid, m_varid,
2148 : nRawDataChunkCacheSize, nChunkSlots,
2149 : fPreemption));
2150 : }
2151 512 : else if (ret != NC_ENOTNC4)
2152 : {
2153 0 : CPLError(CE_Warning, CPLE_AppDefined,
2154 : "netcdf error #%d : %s .\nat (%s,%s,%d)\n", ret,
2155 : nc_strerror(ret), __FILE__, __FUNCTION__, __LINE__);
2156 : }
2157 : }
2158 :
2159 2248 : NCDF_ERR(nc_inq_varndims(m_gid, m_varid, &m_nDims));
2160 2248 : NCDF_ERR(nc_inq_vartype(m_gid, m_varid, &m_nVarType));
2161 2248 : if (m_nDims == 2 && m_nVarType == NC_CHAR)
2162 : {
2163 35 : int anDimIds[2] = {};
2164 35 : NCDF_ERR(nc_inq_vardimid(m_gid, m_varid, &anDimIds[0]));
2165 :
2166 : // Check that there is no variable with the same of the
2167 : // second dimension.
2168 35 : char szExtraDim[NC_MAX_NAME + 1] = {};
2169 35 : NCDF_ERR(nc_inq_dimname(m_gid, anDimIds[1], szExtraDim));
2170 : int nUnused;
2171 35 : if (nc_inq_varid(m_gid, szExtraDim, &nUnused) != NC_NOERR)
2172 : {
2173 13 : NCDF_ERR(nc_inq_dimlen(m_gid, anDimIds[1], &m_nTextLength));
2174 : }
2175 : }
2176 :
2177 2248 : int nShuffle = 0;
2178 2248 : int nDeflate = 0;
2179 2248 : int nDeflateLevel = 0;
2180 2248 : if (nc_inq_var_deflate(m_gid, m_varid, &nShuffle, &nDeflate,
2181 2248 : &nDeflateLevel) == NC_NOERR)
2182 : {
2183 2248 : if (nDeflate)
2184 : {
2185 112 : m_aosStructuralInfo.SetNameValue("COMPRESS", "DEFLATE");
2186 : }
2187 : }
2188 6744 : auto unit = netCDFVariable::GetAttribute(CF_UNITS);
2189 2248 : if (unit && unit->GetDataType().GetClass() == GEDTC_STRING)
2190 : {
2191 570 : const char *pszVal = unit->ReadAsString();
2192 570 : if (pszVal)
2193 570 : m_osUnit = pszVal;
2194 : }
2195 2248 : m_bWriteGDALTags = CPLTestBool(
2196 : CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_TAGS", "YES"));
2197 :
2198 : // Non-documented option. Only used for test purposes.
2199 2248 : if (CPLTestBool(CSLFetchNameValueDef(
2200 : papszOptions, "INCLUDE_CHUNK_CACHE_PARAMETERS_IN_STRUCTURAL_INFO",
2201 : "NO")))
2202 : {
2203 1 : size_t nRawDataChunkCacheSize = 0;
2204 1 : size_t nChunkSlots = 0;
2205 1 : float fPreemption = 0.0f;
2206 1 : NCDF_ERR(nc_get_var_chunk_cache(m_gid, m_varid, &nRawDataChunkCacheSize,
2207 : &nChunkSlots, &fPreemption));
2208 : m_aosStructuralInfo.SetNameValue(
2209 : "RAW_DATA_CHUNK_CACHE_SIZE",
2210 : CPLSPrintf("%" PRIu64,
2211 1 : static_cast<uint64_t>(nRawDataChunkCacheSize)));
2212 : m_aosStructuralInfo.SetNameValue(
2213 : "CHUNK_SLOTS",
2214 1 : CPLSPrintf("%" PRIu64, static_cast<uint64_t>(nChunkSlots)));
2215 : m_aosStructuralInfo.SetNameValue("PREEMPTION",
2216 1 : CPLSPrintf("%f", fPreemption));
2217 : }
2218 2248 : }
2219 :
2220 : /************************************************************************/
2221 : /* ~netCDFVariable() */
2222 : /************************************************************************/
2223 :
2224 4496 : netCDFVariable::~netCDFVariable()
2225 : {
2226 4496 : auto poParent = m_poParent.lock();
2227 2248 : if (poParent)
2228 562 : poParent->UnRegisterArray(this);
2229 :
2230 2248 : if (!m_poShared->IsReadOnly() && !m_dims.empty())
2231 : {
2232 370 : bool bNeedToWriteDummy = false;
2233 950 : for (auto &poDim : m_dims)
2234 : {
2235 583 : auto netCDFDim = std::dynamic_pointer_cast<netCDFDimension>(poDim);
2236 583 : CPLAssert(netCDFDim);
2237 583 : if (netCDFDim->GetSize() > netCDFDim->GetActualSize())
2238 : {
2239 3 : bNeedToWriteDummy = true;
2240 3 : break;
2241 : }
2242 : }
2243 370 : if (bNeedToWriteDummy)
2244 : {
2245 3 : CPLDebug("netCDF", "Extending array %s to new dimension sizes",
2246 3 : GetName().c_str());
2247 3 : m_bGetRawNoDataValueHasRun = false;
2248 3 : m_bUseDefaultFillAsNoData = true;
2249 3 : const void *pNoData = GetRawNoDataValue();
2250 6 : std::vector<GByte> abyDummy(GetDataType().GetSize());
2251 3 : if (pNoData == nullptr)
2252 0 : pNoData = abyDummy.data();
2253 3 : const auto nDimCount = m_dims.size();
2254 6 : std::vector<GUInt64> arrayStartIdx(nDimCount);
2255 6 : std::vector<size_t> count(nDimCount, 1);
2256 6 : std::vector<GInt64> arrayStep(nDimCount, 0);
2257 6 : std::vector<GPtrDiff_t> bufferStride(nDimCount, 0);
2258 9 : for (size_t i = 0; i < nDimCount; ++i)
2259 : {
2260 6 : arrayStartIdx[i] = m_dims[i]->GetSize() - 1;
2261 : }
2262 6 : Write(arrayStartIdx.data(), count.data(), arrayStep.data(),
2263 3 : bufferStride.data(), GetDataType(), pNoData);
2264 : }
2265 : }
2266 4496 : }
2267 :
2268 : /************************************************************************/
2269 : /* GetDimensions() */
2270 : /************************************************************************/
2271 :
2272 : const std::vector<std::shared_ptr<GDALDimension>> &
2273 5793 : netCDFVariable::GetDimensions() const
2274 : {
2275 5793 : if (m_nDims == 0 || !m_dims.empty())
2276 4346 : return m_dims;
2277 2894 : CPLMutexHolderD(&hNCMutex);
2278 1447 : std::vector<int> anDimIds(m_nDims);
2279 1447 : NCDF_ERR(nc_inq_vardimid(m_gid, m_varid, &anDimIds[0]));
2280 1447 : if (m_nDims == 2 && m_nVarType == NC_CHAR && m_nTextLength > 0)
2281 9 : anDimIds.resize(1);
2282 1447 : m_dims.reserve(m_nDims);
2283 4049 : for (const auto &dimid : anDimIds)
2284 : {
2285 5204 : auto poCachedDim = m_poShared->GetCachedDimension(dimid);
2286 2602 : if (poCachedDim == nullptr)
2287 : {
2288 : const int groupDim =
2289 1723 : m_poShared->GetBelongingGroupOfDim(m_gid, dimid);
2290 : poCachedDim =
2291 3446 : netCDFDimension::Create(m_poShared, m_poParent.lock(), groupDim,
2292 5169 : dimid, 0, std::string());
2293 1723 : m_poShared->CacheDimension(dimid, poCachedDim);
2294 : }
2295 2602 : m_dims.emplace_back(poCachedDim);
2296 : }
2297 1447 : return m_dims;
2298 : }
2299 :
2300 : /************************************************************************/
2301 : /* GetStructuralInfo() */
2302 : /************************************************************************/
2303 :
2304 33 : CSLConstList netCDFVariable::GetStructuralInfo() const
2305 : {
2306 33 : return m_aosStructuralInfo.List();
2307 : }
2308 :
2309 : /************************************************************************/
2310 : /* GetComplexDataType() */
2311 : /************************************************************************/
2312 :
2313 305 : static GDALDataType GetComplexDataType(int gid, int nVarType)
2314 : {
2315 : // First enquire and check that the number of fields is 2
2316 305 : size_t nfields = 0;
2317 305 : size_t compoundsize = 0;
2318 305 : char szName[NC_MAX_NAME + 1] = {};
2319 305 : if (nc_inq_compound(gid, nVarType, szName, &compoundsize, &nfields) !=
2320 : NC_NOERR)
2321 : {
2322 0 : return GDT_Unknown;
2323 : }
2324 :
2325 305 : if (nfields != 2 || !STARTS_WITH_CI(szName, "complex"))
2326 : {
2327 136 : return GDT_Unknown;
2328 : }
2329 :
2330 : // Now check that that two types are the same in the struct.
2331 : nc_type field_type1, field_type2;
2332 : int field_dims1, field_dims2;
2333 169 : if (nc_inq_compound_field(gid, nVarType, 0, nullptr, nullptr, &field_type1,
2334 169 : &field_dims1, nullptr) != NC_NOERR)
2335 : {
2336 0 : return GDT_Unknown;
2337 : }
2338 :
2339 169 : if (nc_inq_compound_field(gid, nVarType, 0, nullptr, nullptr, &field_type2,
2340 169 : &field_dims2, nullptr) != NC_NOERR)
2341 : {
2342 0 : return GDT_Unknown;
2343 : }
2344 :
2345 169 : if ((field_type1 != field_type2) || (field_dims1 != field_dims2) ||
2346 169 : (field_dims1 != 0))
2347 : {
2348 0 : return GDT_Unknown;
2349 : }
2350 :
2351 169 : if (field_type1 == NC_SHORT)
2352 : {
2353 41 : return GDT_CInt16;
2354 : }
2355 128 : else if (field_type1 == NC_INT)
2356 : {
2357 40 : return GDT_CInt32;
2358 : }
2359 88 : else if (field_type1 == NC_FLOAT)
2360 : {
2361 44 : return GDT_CFloat32;
2362 : }
2363 44 : else if (field_type1 == NC_DOUBLE)
2364 : {
2365 44 : return GDT_CFloat64;
2366 : }
2367 :
2368 0 : return GDT_Unknown;
2369 : }
2370 :
2371 : /************************************************************************/
2372 : /* GetCompoundDataType() */
2373 : /************************************************************************/
2374 :
2375 136 : static bool GetCompoundDataType(int gid, int nVarType,
2376 : std::unique_ptr<GDALExtendedDataType> &dt,
2377 : bool &bPerfectDataTypeMatch)
2378 : {
2379 136 : size_t nfields = 0;
2380 136 : size_t compoundsize = 0;
2381 136 : char szName[NC_MAX_NAME + 1] = {};
2382 136 : if (nc_inq_compound(gid, nVarType, szName, &compoundsize, &nfields) !=
2383 : NC_NOERR)
2384 : {
2385 0 : return false;
2386 : }
2387 136 : bPerfectDataTypeMatch = true;
2388 272 : std::vector<std::unique_ptr<GDALEDTComponent>> comps;
2389 439 : for (size_t i = 0; i < nfields; i++)
2390 : {
2391 303 : nc_type field_type = 0;
2392 303 : int field_dims = 0;
2393 303 : size_t field_offset = 0;
2394 303 : char field_name[NC_MAX_NAME + 1] = {};
2395 303 : if (nc_inq_compound_field(gid, nVarType, static_cast<int>(i),
2396 : field_name, &field_offset, &field_type,
2397 303 : &field_dims, nullptr) != NC_NOERR)
2398 : {
2399 0 : return false;
2400 : }
2401 303 : if (field_dims != 0)
2402 : {
2403 : // We don't support that
2404 0 : return false;
2405 : }
2406 0 : std::unique_ptr<GDALExtendedDataType> subDt;
2407 303 : bool bSubPerfectDataTypeMatch = false;
2408 303 : if (!BuildDataType(gid, -1, field_type, subDt,
2409 : bSubPerfectDataTypeMatch))
2410 : {
2411 0 : return false;
2412 : }
2413 303 : if (!bSubPerfectDataTypeMatch)
2414 : {
2415 0 : CPLError(
2416 : CE_Failure, CPLE_NotSupported,
2417 : "Non native GDAL type found in a component of a compound type");
2418 0 : return false;
2419 : }
2420 606 : comps.emplace_back(std::make_unique<GDALEDTComponent>(
2421 909 : std::string(field_name), field_offset, *subDt));
2422 : }
2423 272 : dt.reset(new GDALExtendedDataType(
2424 136 : GDALExtendedDataType::Create(szName, compoundsize, std::move(comps))));
2425 :
2426 136 : return dt->GetClass() == GEDTC_COMPOUND;
2427 : }
2428 :
2429 : /************************************************************************/
2430 : /* BuildDataType() */
2431 : /************************************************************************/
2432 :
2433 1416 : static bool BuildDataType(int gid, int varid, const int nVarTypeIn,
2434 : std::unique_ptr<GDALExtendedDataType> &dt,
2435 : bool &bPerfectDataTypeMatch)
2436 : {
2437 1416 : int nVarType = nVarTypeIn;
2438 1416 : GDALDataType eDataType = GDT_Unknown;
2439 1416 : bPerfectDataTypeMatch = false;
2440 1416 : int eClass = 0;
2441 1416 : if (NCDFIsUserDefinedType(gid, nVarType))
2442 : {
2443 368 : nc_type nBaseType = NC_NAT;
2444 368 : nc_inq_user_type(gid, nVarType, nullptr, nullptr, &nBaseType, nullptr,
2445 : &eClass);
2446 368 : if (eClass == NC_COMPOUND)
2447 : {
2448 305 : eDataType = GetComplexDataType(gid, nVarType);
2449 305 : if (eDataType != GDT_Unknown)
2450 : {
2451 169 : bPerfectDataTypeMatch = true;
2452 169 : dt.reset(new GDALExtendedDataType(
2453 169 : GDALExtendedDataType::Create(eDataType)));
2454 305 : return true;
2455 : }
2456 136 : else if (GetCompoundDataType(gid, nVarType, dt,
2457 : bPerfectDataTypeMatch))
2458 : {
2459 136 : return true;
2460 : }
2461 : else
2462 : {
2463 0 : CPLError(CE_Failure, CPLE_NotSupported,
2464 : "Unsupported netCDF compound data type encountered.");
2465 0 : return false;
2466 : }
2467 : }
2468 63 : else if (eClass == NC_ENUM)
2469 : {
2470 63 : nVarType = nBaseType;
2471 : }
2472 0 : else if (eClass == NC_VLEN)
2473 : {
2474 0 : CPLError(CE_Failure, CPLE_NotSupported,
2475 : "VLen data type not supported");
2476 0 : return false;
2477 : }
2478 0 : else if (eClass == NC_OPAQUE)
2479 : {
2480 0 : CPLError(CE_Failure, CPLE_NotSupported,
2481 : "Opaque data type not supported");
2482 0 : return false;
2483 : }
2484 : else
2485 : {
2486 0 : CPLError(CE_Failure, CPLE_NotSupported,
2487 : "Unsupported netCDF data type encountered.");
2488 0 : return false;
2489 : }
2490 : }
2491 :
2492 1111 : if (nVarType == NC_STRING)
2493 : {
2494 88 : bPerfectDataTypeMatch = true;
2495 88 : dt.reset(
2496 88 : new GDALExtendedDataType(GDALExtendedDataType::CreateString()));
2497 88 : return true;
2498 : }
2499 : else
2500 : {
2501 1023 : if (nVarType == NC_BYTE)
2502 : {
2503 107 : char *pszTemp = nullptr;
2504 107 : bool bSignedData = true;
2505 212 : if (varid >= 0 &&
2506 105 : NCDFGetAttr(gid, varid, "_Unsigned", &pszTemp) == CE_None)
2507 : {
2508 95 : if (EQUAL(pszTemp, "true"))
2509 94 : bSignedData = false;
2510 1 : else if (EQUAL(pszTemp, "false"))
2511 1 : bSignedData = true;
2512 95 : CPLFree(pszTemp);
2513 : }
2514 107 : if (!bSignedData)
2515 : {
2516 94 : eDataType = GDT_UInt8;
2517 94 : bPerfectDataTypeMatch = true;
2518 : }
2519 : else
2520 : {
2521 13 : eDataType = GDT_Int8;
2522 13 : bPerfectDataTypeMatch = true;
2523 : }
2524 : }
2525 916 : else if (nVarType == NC_CHAR)
2526 : {
2527 : // Not sure of this
2528 1 : bPerfectDataTypeMatch = true;
2529 1 : eDataType = GDT_UInt8;
2530 : }
2531 915 : else if (nVarType == NC_SHORT)
2532 : {
2533 156 : bPerfectDataTypeMatch = true;
2534 156 : char *pszTemp = nullptr;
2535 156 : bool bSignedData = true;
2536 207 : if (varid >= 0 &&
2537 51 : NCDFGetAttr(gid, varid, "_Unsigned", &pszTemp) == CE_None)
2538 : {
2539 38 : if (EQUAL(pszTemp, "true"))
2540 38 : bSignedData = false;
2541 0 : else if (EQUAL(pszTemp, "false"))
2542 0 : bSignedData = true;
2543 38 : CPLFree(pszTemp);
2544 : }
2545 156 : if (!bSignedData)
2546 : {
2547 38 : eDataType = GDT_UInt16;
2548 : }
2549 : else
2550 : {
2551 118 : eDataType = GDT_Int16;
2552 : }
2553 : }
2554 759 : else if (nVarType == NC_INT)
2555 : {
2556 188 : bPerfectDataTypeMatch = true;
2557 188 : eDataType = GDT_Int32;
2558 : }
2559 571 : else if (nVarType == NC_FLOAT)
2560 : {
2561 78 : bPerfectDataTypeMatch = true;
2562 78 : eDataType = GDT_Float32;
2563 : }
2564 493 : else if (nVarType == NC_DOUBLE)
2565 : {
2566 248 : bPerfectDataTypeMatch = true;
2567 248 : eDataType = GDT_Float64;
2568 : }
2569 245 : else if (nVarType == NC_UBYTE)
2570 : {
2571 215 : bPerfectDataTypeMatch = true;
2572 215 : eDataType = GDT_UInt8;
2573 : }
2574 30 : else if (nVarType == NC_USHORT)
2575 : {
2576 9 : bPerfectDataTypeMatch = true;
2577 9 : eDataType = GDT_UInt16;
2578 : }
2579 21 : else if (nVarType == NC_UINT)
2580 : {
2581 7 : bPerfectDataTypeMatch = true;
2582 7 : eDataType = GDT_UInt32;
2583 : }
2584 14 : else if (nVarType == NC_INT64)
2585 : {
2586 8 : bPerfectDataTypeMatch = true;
2587 8 : eDataType = GDT_Int64;
2588 : }
2589 6 : else if (nVarType == NC_UINT64)
2590 : {
2591 6 : bPerfectDataTypeMatch = true;
2592 6 : eDataType = GDT_UInt64;
2593 : }
2594 : else
2595 : {
2596 0 : CPLError(CE_Failure, CPLE_NotSupported,
2597 : "Unsupported netCDF data type encountered.");
2598 0 : return false;
2599 : }
2600 : }
2601 :
2602 1086 : if (eClass == NC_ENUM && GDALDataTypeIsInteger(eDataType) &&
2603 63 : !GDALDataTypeIsComplex(eDataType))
2604 : {
2605 63 : char szEnumName[NC_MAX_NAME + 1] = {};
2606 63 : size_t nMemberCount = 0;
2607 63 : NCDF_ERR(nc_inq_enum(gid, nVarTypeIn, szEnumName, nullptr, nullptr,
2608 : &nMemberCount));
2609 126 : auto poRAT = std::make_unique<GDALDefaultRasterAttributeTable>();
2610 63 : poRAT->CreateColumn("value", GFT_Integer, GFU_MinMax);
2611 63 : poRAT->CreateColumn("name", GFT_String, GFU_Name);
2612 63 : std::vector<GByte> abyValue(GDALGetDataTypeSizeBytes(eDataType));
2613 63 : char szName[NC_MAX_NAME + 1] = {};
2614 190 : for (int i = 0;
2615 190 : i < static_cast<int>(std::min<size_t>(nMemberCount, INT_MAX)); ++i)
2616 : {
2617 127 : szName[0] = 0;
2618 127 : NCDF_ERR(nc_inq_enum_member(gid, nVarTypeIn, i, szName,
2619 : abyValue.data()));
2620 127 : int nValue = 0;
2621 127 : GDALCopyWords(abyValue.data(), eDataType, 0, &nValue, GDT_Int32, 0,
2622 : 1);
2623 127 : poRAT->SetValue(i, 0, nValue);
2624 127 : poRAT->SetValue(i, 1, szName);
2625 : }
2626 189 : dt.reset(new GDALExtendedDataType(GDALExtendedDataType::Create(
2627 126 : szEnumName, eDataType, std::move(poRAT))));
2628 : }
2629 : else
2630 : {
2631 960 : dt.reset(
2632 960 : new GDALExtendedDataType(GDALExtendedDataType::Create(eDataType)));
2633 : }
2634 1023 : return true;
2635 : }
2636 :
2637 : /************************************************************************/
2638 : /* GetDataType() */
2639 : /************************************************************************/
2640 :
2641 3329 : const GDALExtendedDataType &netCDFVariable::GetDataType() const
2642 : {
2643 3329 : if (m_dt)
2644 2704 : return *m_dt;
2645 1250 : CPLMutexHolderD(&hNCMutex);
2646 :
2647 625 : if (m_nDims == 2 && m_nVarType == NC_CHAR && m_nTextLength > 0)
2648 : {
2649 7 : m_bPerfectDataTypeMatch = true;
2650 7 : m_dt.reset(new GDALExtendedDataType(
2651 7 : GDALExtendedDataType::CreateString(m_nTextLength)));
2652 : }
2653 : else
2654 : {
2655 618 : m_dt.reset(new GDALExtendedDataType(
2656 618 : GDALExtendedDataType::Create(GDT_Unknown)));
2657 :
2658 618 : BuildDataType(m_gid, m_varid, m_nVarType, m_dt,
2659 618 : m_bPerfectDataTypeMatch);
2660 : }
2661 625 : return *m_dt;
2662 : }
2663 :
2664 : /************************************************************************/
2665 : /* SetUnit() */
2666 : /************************************************************************/
2667 :
2668 4 : bool netCDFVariable::SetUnit(const std::string &osUnit)
2669 : {
2670 4 : if (osUnit.empty())
2671 : {
2672 0 : nc_del_att(m_gid, m_varid, CF_UNITS);
2673 0 : return true;
2674 : }
2675 12 : auto poUnits(GetAttribute(CF_UNITS));
2676 4 : if (!poUnits)
2677 : {
2678 8 : poUnits = CreateAttribute(
2679 12 : CF_UNITS, {}, GDALExtendedDataType::CreateString(), nullptr);
2680 4 : if (!poUnits)
2681 0 : return false;
2682 : }
2683 4 : return poUnits->Write(osUnit.c_str());
2684 : }
2685 :
2686 : /************************************************************************/
2687 : /* GetSpatialRef() */
2688 : /************************************************************************/
2689 :
2690 148 : std::shared_ptr<OGRSpatialReference> netCDFVariable::GetSpatialRef() const
2691 : {
2692 148 : if (m_bSRSRead)
2693 50 : return m_poSRS;
2694 :
2695 98 : m_bSRSRead = true;
2696 196 : netCDFDataset poDS;
2697 98 : poDS.ReadAttributes(m_gid, m_varid);
2698 98 : int iDimX = 0;
2699 98 : int iDimY = 0;
2700 98 : int iCount = 1;
2701 268 : for (const auto &poDim : GetDimensions())
2702 : {
2703 170 : if (poDim->GetType() == GDAL_DIM_TYPE_HORIZONTAL_X)
2704 39 : iDimX = iCount;
2705 131 : else if (poDim->GetType() == GDAL_DIM_TYPE_HORIZONTAL_Y)
2706 46 : iDimY = iCount;
2707 170 : poDS.papszDimName.AddString(poDim->GetName().c_str());
2708 170 : iCount++;
2709 : }
2710 98 : if ((iDimX == 0 || iDimY == 0) && GetDimensionCount() >= 2)
2711 : {
2712 32 : iDimX = static_cast<int>(GetDimensionCount());
2713 32 : iDimY = iDimX - 1;
2714 : }
2715 98 : poDS.SetProjectionFromVar(m_gid, m_varid, true);
2716 98 : auto poSRS = poDS.GetSpatialRef();
2717 98 : if (poSRS)
2718 : {
2719 30 : m_poSRS.reset(poSRS->Clone());
2720 30 : if (iDimX > 0 && iDimY > 0)
2721 : {
2722 30 : const auto &oMapping = m_poSRS->GetDataAxisToSRSAxisMapping();
2723 85 : if (oMapping == std::vector<int>{2, 1} ||
2724 55 : oMapping == std::vector<int>{2, 1, 3})
2725 5 : m_poSRS->SetDataAxisToSRSAxisMapping({iDimY, iDimX});
2726 : else
2727 25 : m_poSRS->SetDataAxisToSRSAxisMapping({iDimX, iDimY});
2728 : }
2729 : }
2730 :
2731 98 : return m_poSRS;
2732 : }
2733 :
2734 : /************************************************************************/
2735 : /* SetSpatialRef() */
2736 : /************************************************************************/
2737 :
2738 84 : static void WriteDimAttr(std::shared_ptr<GDALMDArray> &poVar,
2739 : const char *pszAttrName, const char *pszAttrValue)
2740 : {
2741 252 : auto poAttr = poVar->GetAttribute(pszAttrName);
2742 84 : if (poAttr)
2743 : {
2744 34 : const char *pszVal = poAttr->ReadAsString();
2745 34 : if (pszVal && !EQUAL(pszVal, pszAttrValue))
2746 : {
2747 0 : CPLError(CE_Warning, CPLE_AppDefined,
2748 : "Variable %s has a %s which is %s and not %s",
2749 0 : poVar->GetName().c_str(), pszAttrName, pszVal,
2750 : pszAttrValue);
2751 : }
2752 : }
2753 : else
2754 : {
2755 150 : poAttr = poVar->CreateAttribute(
2756 150 : pszAttrName, {}, GDALExtendedDataType::CreateString(), nullptr);
2757 50 : if (poAttr)
2758 50 : poAttr->Write(pszAttrValue);
2759 : }
2760 84 : }
2761 :
2762 32 : static void WriteDimAttrs(const std::shared_ptr<GDALDimension> &dim,
2763 : const char *pszStandardName, const char *pszLongName,
2764 : const char *pszUnits)
2765 : {
2766 64 : auto poVar = dim->GetIndexingVariable();
2767 32 : if (poVar)
2768 : {
2769 28 : WriteDimAttr(poVar, CF_STD_NAME, pszStandardName);
2770 28 : WriteDimAttr(poVar, CF_LNG_NAME, pszLongName);
2771 28 : WriteDimAttr(poVar, CF_UNITS, pszUnits);
2772 : }
2773 : else
2774 : {
2775 4 : CPLError(CE_Warning, CPLE_AppDefined,
2776 : "Dimension %s lacks a indexing variable",
2777 4 : dim->GetName().c_str());
2778 : }
2779 32 : }
2780 :
2781 20 : bool netCDFVariable::SetSpatialRef(const OGRSpatialReference *poSRS)
2782 : {
2783 20 : m_bSRSRead = false;
2784 20 : m_poSRS.reset();
2785 :
2786 40 : CPLMutexHolderD(&hNCMutex);
2787 20 : m_poShared->SetDefineMode(true);
2788 :
2789 20 : if (poSRS == nullptr)
2790 : {
2791 2 : nc_del_att(m_gid, m_varid, CF_GRD_MAPPING);
2792 2 : return true;
2793 : }
2794 :
2795 18 : char *pszCFProjection = nullptr;
2796 : int nSRSVarId =
2797 18 : NCDFWriteSRSVariable(m_gid, poSRS, &pszCFProjection, m_bWriteGDALTags);
2798 18 : if (nSRSVarId < 0 || pszCFProjection == nullptr)
2799 0 : return false;
2800 :
2801 18 : NCDF_ERR(nc_put_att_text(m_gid, m_varid, CF_GRD_MAPPING,
2802 : strlen(pszCFProjection), pszCFProjection));
2803 18 : CPLFree(pszCFProjection);
2804 :
2805 18 : auto apoDims = GetDimensions();
2806 18 : if (poSRS->IsProjected())
2807 : {
2808 13 : bool bWriteX = false;
2809 13 : bool bWriteY = false;
2810 26 : const std::string osUnits = NCDFGetProjectedCFUnit(poSRS);
2811 39 : for (const auto &poDim : apoDims)
2812 : {
2813 26 : const char *pszStandardName = nullptr;
2814 26 : const char *pszLongName = nullptr;
2815 45 : if (poDim->GetType() == GDAL_DIM_TYPE_HORIZONTAL_X ||
2816 19 : EQUAL(poDim->GetName().c_str(), CF_PROJ_X_VAR_NAME))
2817 : {
2818 10 : pszStandardName = CF_PROJ_X_COORD;
2819 10 : pszLongName = CF_PROJ_X_COORD_LONG_NAME;
2820 10 : bWriteX = true;
2821 : }
2822 25 : else if (poDim->GetType() == GDAL_DIM_TYPE_HORIZONTAL_Y ||
2823 9 : EQUAL(poDim->GetName().c_str(), CF_PROJ_Y_VAR_NAME))
2824 : {
2825 10 : pszStandardName = CF_PROJ_Y_COORD;
2826 10 : pszLongName = CF_PROJ_Y_COORD_LONG_NAME;
2827 10 : bWriteY = true;
2828 : }
2829 26 : if (pszStandardName && pszLongName)
2830 : {
2831 20 : WriteDimAttrs(poDim, pszStandardName, pszLongName,
2832 : osUnits.c_str());
2833 : }
2834 : }
2835 6 : if (!bWriteX && !bWriteY && apoDims.size() >= 2 &&
2836 6 : apoDims[apoDims.size() - 2]->GetType().empty() &&
2837 6 : apoDims[apoDims.size() - 1]->GetType().empty() &&
2838 32 : apoDims[apoDims.size() - 2]->GetIndexingVariable() &&
2839 14 : apoDims[apoDims.size() - 1]->GetIndexingVariable())
2840 : {
2841 1 : CPLError(CE_Warning, CPLE_AppDefined,
2842 : "Dimensions of variable %s have no type declared. "
2843 : "Assuming the last one is X, and the preceding one Y",
2844 1 : GetName().c_str());
2845 1 : WriteDimAttrs(apoDims[apoDims.size() - 1], CF_PROJ_X_COORD,
2846 : CF_PROJ_X_COORD_LONG_NAME, osUnits.c_str());
2847 1 : WriteDimAttrs(apoDims[apoDims.size() - 2], CF_PROJ_Y_COORD,
2848 : CF_PROJ_Y_COORD_LONG_NAME, osUnits.c_str());
2849 : }
2850 : }
2851 5 : else if (poSRS->IsGeographic())
2852 : {
2853 5 : bool bWriteX = false;
2854 5 : bool bWriteY = false;
2855 15 : for (const auto &poDim : apoDims)
2856 : {
2857 10 : const char *pszStandardName = nullptr;
2858 10 : const char *pszLongName = nullptr;
2859 10 : const char *pszUnits = "";
2860 16 : if (poDim->GetType() == GDAL_DIM_TYPE_HORIZONTAL_X ||
2861 6 : EQUAL(poDim->GetName().c_str(), CF_LONGITUDE_VAR_NAME))
2862 : {
2863 4 : pszStandardName = CF_LONGITUDE_STD_NAME;
2864 4 : pszLongName = CF_LONGITUDE_LNG_NAME;
2865 4 : pszUnits = CF_DEGREES_EAST;
2866 4 : bWriteX = true;
2867 : }
2868 8 : else if (poDim->GetType() == GDAL_DIM_TYPE_HORIZONTAL_Y ||
2869 2 : EQUAL(poDim->GetName().c_str(), CF_LATITUDE_VAR_NAME))
2870 : {
2871 4 : pszStandardName = CF_LATITUDE_STD_NAME;
2872 4 : pszLongName = CF_LATITUDE_LNG_NAME;
2873 4 : pszUnits = CF_DEGREES_NORTH;
2874 4 : bWriteY = true;
2875 : }
2876 10 : if (pszStandardName && pszLongName)
2877 : {
2878 8 : WriteDimAttrs(poDim, pszStandardName, pszLongName, pszUnits);
2879 : }
2880 : }
2881 2 : if (!bWriteX && !bWriteY && apoDims.size() >= 2 &&
2882 2 : apoDims[apoDims.size() - 2]->GetType().empty() &&
2883 2 : apoDims[apoDims.size() - 1]->GetType().empty() &&
2884 12 : apoDims[apoDims.size() - 2]->GetIndexingVariable() &&
2885 6 : apoDims[apoDims.size() - 1]->GetIndexingVariable())
2886 : {
2887 1 : CPLError(CE_Warning, CPLE_AppDefined,
2888 : "Dimensions of variable %s have no type declared. "
2889 : "Assuming the last one is longitude, "
2890 : "and the preceding one latitude",
2891 1 : GetName().c_str());
2892 1 : WriteDimAttrs(apoDims[apoDims.size() - 1], CF_LONGITUDE_STD_NAME,
2893 : CF_LONGITUDE_LNG_NAME, CF_DEGREES_EAST);
2894 1 : WriteDimAttrs(apoDims[apoDims.size() - 2], CF_LATITUDE_STD_NAME,
2895 : CF_LATITUDE_LNG_NAME, CF_DEGREES_NORTH);
2896 : }
2897 : }
2898 :
2899 18 : return true;
2900 : }
2901 :
2902 : /************************************************************************/
2903 : /* SetStatistics() */
2904 : /************************************************************************/
2905 :
2906 5 : bool netCDFVariable::SetStatistics(bool bApproxStats, double dfMin,
2907 : double dfMax, double dfMean, double dfStdDev,
2908 : GUInt64 nValidCount,
2909 : CSLConstList papszOptions)
2910 : {
2911 6 : if (!bApproxStats && !m_poShared->IsReadOnly() &&
2912 1 : CPLTestBool(
2913 : CSLFetchNameValueDef(papszOptions, "UPDATE_METADATA", "NO")))
2914 : {
2915 3 : auto poAttr = GetAttribute("actual_range");
2916 1 : if (!poAttr)
2917 : {
2918 : poAttr =
2919 1 : CreateAttribute("actual_range", {2}, GetDataType(), nullptr);
2920 : }
2921 1 : if (poAttr)
2922 : {
2923 2 : std::vector<GUInt64> startIdx = {0};
2924 2 : std::vector<size_t> count = {2};
2925 1 : std::vector<double> values = {dfMin, dfMax};
2926 2 : poAttr->Write(startIdx.data(), count.data(), nullptr, nullptr,
2927 2 : GDALExtendedDataType::Create(GDT_Float64),
2928 1 : values.data(), nullptr, 0);
2929 : }
2930 : }
2931 5 : return GDALPamMDArray::SetStatistics(bApproxStats, dfMin, dfMax, dfMean,
2932 5 : dfStdDev, nValidCount, papszOptions);
2933 : }
2934 :
2935 : /************************************************************************/
2936 : /* GetNCTypeSize() */
2937 : /************************************************************************/
2938 :
2939 659 : static size_t GetNCTypeSize(const GDALExtendedDataType &dt,
2940 : bool bPerfectDataTypeMatch, int nAttType)
2941 : {
2942 659 : auto nElementSize = dt.GetSize();
2943 659 : if (!bPerfectDataTypeMatch)
2944 : {
2945 10 : if (nAttType == NC_BYTE)
2946 : {
2947 4 : CPLAssert(dt.GetNumericDataType() == GDT_Int16);
2948 4 : nElementSize = sizeof(signed char);
2949 : }
2950 6 : else if (nAttType == NC_INT64)
2951 : {
2952 4 : CPLAssert(dt.GetNumericDataType() == GDT_Float64);
2953 4 : nElementSize = sizeof(GInt64);
2954 : }
2955 2 : else if (nAttType == NC_UINT64)
2956 : {
2957 2 : CPLAssert(dt.GetNumericDataType() == GDT_Float64);
2958 2 : nElementSize = sizeof(GUInt64);
2959 : }
2960 : else
2961 : {
2962 0 : CPLAssert(false);
2963 : }
2964 : }
2965 659 : return nElementSize;
2966 : }
2967 :
2968 : /************************************************************************/
2969 : /* ConvertNCStringsToCPLStrings() */
2970 : /************************************************************************/
2971 :
2972 81 : static void ConvertNCStringsToCPLStrings(GByte *pBuffer,
2973 : const GDALExtendedDataType &dt)
2974 : {
2975 81 : switch (dt.GetClass())
2976 : {
2977 0 : case GEDTC_STRING:
2978 : {
2979 : char *pszStr;
2980 : // cppcheck-suppress pointerSize
2981 0 : memcpy(&pszStr, pBuffer, sizeof(char *));
2982 0 : if (pszStr)
2983 : {
2984 0 : char *pszNewStr = VSIStrdup(pszStr);
2985 0 : nc_free_string(1, &pszStr);
2986 : // cppcheck-suppress pointerSize
2987 0 : memcpy(pBuffer, &pszNewStr, sizeof(char *));
2988 : }
2989 0 : break;
2990 : }
2991 :
2992 77 : case GEDTC_NUMERIC:
2993 : {
2994 77 : break;
2995 : }
2996 :
2997 4 : case GEDTC_COMPOUND:
2998 : {
2999 4 : const auto &comps = dt.GetComponents();
3000 12 : for (const auto &comp : comps)
3001 : {
3002 8 : ConvertNCStringsToCPLStrings(pBuffer + comp->GetOffset(),
3003 : comp->GetType());
3004 : }
3005 4 : break;
3006 : }
3007 : }
3008 81 : }
3009 :
3010 : /************************************************************************/
3011 : /* FreeNCStrings() */
3012 : /************************************************************************/
3013 :
3014 150 : static void FreeNCStrings(GByte *pBuffer, const GDALExtendedDataType &dt)
3015 : {
3016 150 : switch (dt.GetClass())
3017 : {
3018 0 : case GEDTC_STRING:
3019 : {
3020 : char *pszStr;
3021 : // cppcheck-suppress pointerSize
3022 0 : memcpy(&pszStr, pBuffer, sizeof(char *));
3023 0 : if (pszStr)
3024 : {
3025 0 : nc_free_string(1, &pszStr);
3026 : }
3027 0 : break;
3028 : }
3029 :
3030 150 : case GEDTC_NUMERIC:
3031 : {
3032 150 : break;
3033 : }
3034 :
3035 0 : case GEDTC_COMPOUND:
3036 : {
3037 0 : const auto &comps = dt.GetComponents();
3038 0 : for (const auto &comp : comps)
3039 : {
3040 0 : FreeNCStrings(pBuffer + comp->GetOffset(), comp->GetType());
3041 : }
3042 0 : break;
3043 : }
3044 : }
3045 150 : }
3046 :
3047 : /************************************************************************/
3048 : /* IReadWriteGeneric() */
3049 : /************************************************************************/
3050 :
3051 : namespace
3052 : {
3053 : template <typename T> struct GetGByteType
3054 : {
3055 : };
3056 :
3057 : template <> struct GetGByteType<void *>
3058 : {
3059 : typedef GByte *type;
3060 : };
3061 :
3062 : template <> struct GetGByteType<const void *>
3063 : {
3064 : typedef const GByte *type;
3065 : };
3066 : } // namespace
3067 :
3068 : template <typename BufferType, typename NCGetPutVar1FuncType,
3069 : typename ReadOrWriteOneElementType>
3070 60 : bool netCDFVariable::IReadWriteGeneric(
3071 : const size_t *arrayStartIdx, const size_t *count, const GInt64 *arrayStep,
3072 : const GPtrDiff_t *bufferStride, const GDALExtendedDataType &bufferDataType,
3073 : BufferType buffer, NCGetPutVar1FuncType NCGetPutVar1Func,
3074 : ReadOrWriteOneElementType ReadOrWriteOneElement) const
3075 : {
3076 60 : CPLAssert(m_nDims > 0);
3077 120 : std::vector<size_t> array_idx(m_nDims);
3078 120 : std::vector<size_t> stack_count_iters(m_nDims - 1);
3079 : typedef typename GetGByteType<BufferType>::type GBytePtrType;
3080 120 : std::vector<GBytePtrType> stack_ptr(m_nDims);
3081 120 : std::vector<GPtrDiff_t> ptr_inc;
3082 60 : ptr_inc.reserve(m_nDims);
3083 60 : const auto &eArrayEDT = GetDataType();
3084 60 : const bool bSameDT = m_bPerfectDataTypeMatch && eArrayEDT == bufferDataType;
3085 60 : const auto nBufferDTSize = bufferDataType.GetSize();
3086 132 : for (int i = 0; i < m_nDims; i++)
3087 : {
3088 72 : ptr_inc.push_back(bufferStride[i] * nBufferDTSize);
3089 : }
3090 60 : const auto nDimsMinus1 = m_nDims - 1;
3091 60 : stack_ptr[0] = static_cast<GBytePtrType>(buffer);
3092 :
3093 5265 : auto lambdaLastDim = [&](GBytePtrType ptr)
3094 : {
3095 105 : array_idx[nDimsMinus1] = arrayStartIdx[nDimsMinus1];
3096 105 : size_t nIters = count[nDimsMinus1];
3097 561 : while (true)
3098 : {
3099 666 : if (bSameDT)
3100 : {
3101 : int ret =
3102 9 : NCGetPutVar1Func(m_gid, m_varid, array_idx.data(), ptr);
3103 9 : NCDF_ERR(ret);
3104 9 : if (ret != NC_NOERR)
3105 0 : return false;
3106 : }
3107 : else
3108 : {
3109 1314 : if (!(this->*ReadOrWriteOneElement)(eArrayEDT, bufferDataType,
3110 657 : array_idx.data(), ptr))
3111 0 : return false;
3112 : }
3113 666 : if ((--nIters) == 0)
3114 105 : break;
3115 561 : ptr += ptr_inc[nDimsMinus1];
3116 : // CPLUnsanitizedAdd needed as arrayStep[] might be negative, and
3117 : // thus automatic conversion from negative to big unsigned might
3118 : // occur
3119 561 : array_idx[nDimsMinus1] = CPLUnsanitizedAdd<size_t>(
3120 : array_idx[nDimsMinus1],
3121 561 : static_cast<GPtrDiff_t>(arrayStep[nDimsMinus1]));
3122 : }
3123 105 : return true;
3124 : };
3125 :
3126 60 : if (m_nDims == 1)
3127 : {
3128 51 : return lambdaLastDim(stack_ptr[0]);
3129 : }
3130 9 : else if (m_nDims == 2)
3131 : {
3132 7 : auto nIters = count[0];
3133 7 : array_idx[0] = arrayStartIdx[0];
3134 : while (true)
3135 : {
3136 36 : if (!lambdaLastDim(stack_ptr[0]))
3137 0 : return false;
3138 36 : if ((--nIters) == 0)
3139 7 : break;
3140 29 : stack_ptr[0] += ptr_inc[0];
3141 : // CPLUnsanitizedAdd needed as arrayStep[] might be negative, and
3142 : // thus automatic conversion from negative to big unsigned might
3143 : // occur
3144 29 : array_idx[0] = CPLUnsanitizedAdd<size_t>(
3145 : array_idx[0], static_cast<GPtrDiff_t>(arrayStep[0]));
3146 : }
3147 : }
3148 2 : else if (m_nDims == 3)
3149 : {
3150 1 : stack_count_iters[0] = count[0];
3151 1 : array_idx[0] = arrayStartIdx[0];
3152 2 : while (true)
3153 : {
3154 3 : auto nIters = count[1];
3155 3 : array_idx[1] = arrayStartIdx[1];
3156 3 : stack_ptr[1] = stack_ptr[0];
3157 : while (true)
3158 : {
3159 6 : if (!lambdaLastDim(stack_ptr[1]))
3160 0 : return false;
3161 6 : if ((--nIters) == 0)
3162 3 : break;
3163 3 : stack_ptr[1] += ptr_inc[1];
3164 : // CPLUnsanitizedAdd needed as arrayStep[] might be negative,
3165 : // and thus automatic conversion from negative to big unsigned
3166 : // might occur
3167 3 : array_idx[1] = CPLUnsanitizedAdd<size_t>(
3168 3 : array_idx[1], static_cast<GPtrDiff_t>(arrayStep[1]));
3169 : }
3170 3 : if ((--stack_count_iters[0]) == 0)
3171 1 : break;
3172 2 : stack_ptr[0] += ptr_inc[0];
3173 : // CPLUnsanitizedAdd needed as arrayStep[] might be negative, and
3174 : // thus automatic conversion from negative to big unsigned might
3175 : // occur
3176 2 : array_idx[0] = CPLUnsanitizedAdd<size_t>(
3177 : array_idx[0], static_cast<GPtrDiff_t>(arrayStep[0]));
3178 : }
3179 : }
3180 : else
3181 : {
3182 : // Implementation valid for nDims >= 3
3183 :
3184 1 : int dimIdx = 0;
3185 : // Non-recursive implementation. Hence the gotos
3186 : // It might be possible to rewrite this without gotos, but I find they
3187 : // make it clearer to understand the recursive nature of the code
3188 9 : lbl_start:
3189 9 : if (dimIdx == nDimsMinus1 - 1)
3190 : {
3191 6 : array_idx[dimIdx] = arrayStartIdx[dimIdx];
3192 6 : auto nIters = count[dimIdx];
3193 : while (true)
3194 : {
3195 12 : if (!(lambdaLastDim(stack_ptr[dimIdx])))
3196 0 : return false;
3197 12 : if ((--nIters) == 0)
3198 6 : break;
3199 6 : stack_ptr[dimIdx] += ptr_inc[dimIdx];
3200 : // CPLUnsanitizedAdd needed as arrayStep[] might be negative,
3201 : // and thus automatic conversion from negative to big unsigned
3202 : // might occur
3203 6 : array_idx[dimIdx] = CPLUnsanitizedAdd<size_t>(
3204 : array_idx[dimIdx],
3205 6 : static_cast<GPtrDiff_t>(arrayStep[dimIdx]));
3206 : }
3207 : // If there was a test if( dimIdx > 0 ), that would be valid for
3208 : // nDims == 2
3209 6 : goto lbl_return_to_caller;
3210 : }
3211 : else
3212 : {
3213 3 : array_idx[dimIdx] = arrayStartIdx[dimIdx];
3214 3 : stack_count_iters[dimIdx] = count[dimIdx];
3215 : while (true)
3216 : {
3217 : // Simulate a recursive call to the next dimension
3218 : // Implicitly save back count and ptr
3219 8 : dimIdx++;
3220 8 : stack_ptr[dimIdx] = stack_ptr[dimIdx - 1];
3221 8 : goto lbl_start;
3222 8 : lbl_return_to_caller:
3223 8 : dimIdx--;
3224 8 : if ((--stack_count_iters[dimIdx]) == 0)
3225 3 : break;
3226 5 : stack_ptr[dimIdx] += ptr_inc[dimIdx];
3227 : // CPLUnsanitizedAdd needed as arrayStep[] might be negative,
3228 : // and thus automatic conversion from negative to big unsigned
3229 : // might occur
3230 5 : array_idx[dimIdx] = CPLUnsanitizedAdd<size_t>(
3231 : array_idx[dimIdx],
3232 5 : static_cast<GPtrDiff_t>(arrayStep[dimIdx]));
3233 : }
3234 3 : if (dimIdx > 0)
3235 2 : goto lbl_return_to_caller;
3236 : }
3237 : }
3238 :
3239 9 : return true;
3240 : }
3241 :
3242 : /************************************************************************/
3243 : /* CheckNumericDataType() */
3244 : /************************************************************************/
3245 :
3246 1525 : static bool CheckNumericDataType(const GDALExtendedDataType &dt)
3247 : {
3248 1525 : const auto klass = dt.GetClass();
3249 1525 : if (klass == GEDTC_NUMERIC)
3250 1515 : return dt.GetNumericDataType() != GDT_Unknown;
3251 10 : if (klass == GEDTC_STRING)
3252 0 : return false;
3253 10 : CPLAssert(klass == GEDTC_COMPOUND);
3254 10 : const auto &comps = dt.GetComponents();
3255 31 : for (const auto &comp : comps)
3256 : {
3257 21 : if (!CheckNumericDataType(comp->GetType()))
3258 0 : return false;
3259 : }
3260 10 : return true;
3261 : }
3262 :
3263 : /************************************************************************/
3264 : /* IReadWrite() */
3265 : /************************************************************************/
3266 :
3267 : template <typename BufferType, typename NCGetPutVar1FuncType,
3268 : typename NCGetPutVaraFuncType, typename NCGetPutVarmFuncType,
3269 : typename ReadOrWriteOneElementType>
3270 758 : bool netCDFVariable::IReadWrite(
3271 : const bool bIsRead, const GUInt64 *arrayStartIdx, const size_t *count,
3272 : const GInt64 *arrayStep, const GPtrDiff_t *bufferStride,
3273 : const GDALExtendedDataType &bufferDataType, BufferType buffer,
3274 : NCGetPutVar1FuncType NCGetPutVar1Func,
3275 : NCGetPutVaraFuncType NCGetPutVaraFunc,
3276 : NCGetPutVarmFuncType NCGetPutVarmFunc,
3277 : ReadOrWriteOneElementType ReadOrWriteOneElement) const
3278 : {
3279 1516 : CPLMutexHolderD(&hNCMutex);
3280 758 : m_poShared->SetDefineMode(false);
3281 :
3282 758 : const auto &eDT = GetDataType();
3283 1516 : std::vector<size_t> startp;
3284 758 : startp.reserve(m_nDims);
3285 758 : bool bUseSlowPath =
3286 758 : !m_bPerfectDataTypeMatch &&
3287 0 : !(bIsRead && bufferDataType.GetClass() == GEDTC_NUMERIC &&
3288 0 : eDT.GetClass() == GEDTC_NUMERIC &&
3289 0 : bufferDataType.GetSize() >= eDT.GetSize());
3290 2017 : for (int i = 0; i < m_nDims; i++)
3291 : {
3292 : #if SIZEOF_VOIDP == 4
3293 : if (arrayStartIdx[i] > std::numeric_limits<size_t>::max())
3294 : return false;
3295 : #endif
3296 1259 : startp.push_back(static_cast<size_t>(arrayStartIdx[i]));
3297 :
3298 : #if SIZEOF_VOIDP == 4
3299 : if (arrayStep[i] < std::numeric_limits<ptrdiff_t>::min() ||
3300 : arrayStep[i] > std::numeric_limits<ptrdiff_t>::max())
3301 : {
3302 : return false;
3303 : }
3304 : #endif
3305 :
3306 1259 : if (count[i] != 1 && arrayStep[i] <= 0)
3307 3 : bUseSlowPath = true; // netCDF rejects negative or NULL strides
3308 :
3309 1259 : if (bufferStride[i] < 0)
3310 0 : bUseSlowPath =
3311 : true; // and it seems to silently cast to size_t imapp
3312 : }
3313 :
3314 764 : if (eDT.GetClass() == GEDTC_STRING &&
3315 764 : bufferDataType.GetClass() == GEDTC_STRING && m_nVarType == NC_STRING)
3316 : {
3317 6 : if (m_nDims == 0)
3318 : {
3319 2 : return (this->*ReadOrWriteOneElement)(eDT, bufferDataType, nullptr,
3320 2 : buffer);
3321 : }
3322 :
3323 4 : return IReadWriteGeneric(startp.data(), count, arrayStep, bufferStride,
3324 : bufferDataType, buffer, NCGetPutVar1Func,
3325 4 : ReadOrWriteOneElement);
3326 : }
3327 :
3328 752 : if (!CheckNumericDataType(eDT))
3329 0 : return false;
3330 752 : if (!CheckNumericDataType(bufferDataType))
3331 0 : return false;
3332 :
3333 752 : if (m_nDims == 0)
3334 : {
3335 15 : return (this->*ReadOrWriteOneElement)(eDT, bufferDataType, nullptr,
3336 15 : buffer);
3337 : }
3338 :
3339 1471 : if (!bUseSlowPath &&
3340 1458 : ((GDALDataTypeIsComplex(bufferDataType.GetNumericDataType()) ||
3341 724 : bufferDataType.GetClass() == GEDTC_COMPOUND) &&
3342 15 : bufferDataType == eDT))
3343 : {
3344 : // nc_get_varm() not supported for non-atomic types.
3345 14 : ptrdiff_t nExpectedBufferStride = 1;
3346 40 : for (int i = m_nDims; i != 0;)
3347 : {
3348 26 : --i;
3349 26 : if (count[i] != 1 &&
3350 17 : (arrayStep[i] != 1 || bufferStride[i] != nExpectedBufferStride))
3351 : {
3352 0 : bUseSlowPath = true;
3353 0 : break;
3354 : }
3355 26 : nExpectedBufferStride *= count[i];
3356 : }
3357 14 : if (!bUseSlowPath)
3358 : {
3359 : int ret =
3360 14 : NCGetPutVaraFunc(m_gid, m_varid, startp.data(), count, buffer);
3361 14 : NCDF_ERR(ret);
3362 14 : return ret == NC_NOERR;
3363 : }
3364 : }
3365 :
3366 1439 : if (bUseSlowPath || bufferDataType.GetClass() == GEDTC_COMPOUND ||
3367 719 : eDT.GetClass() == GEDTC_COMPOUND ||
3368 719 : (!bIsRead &&
3369 2003 : bufferDataType.GetNumericDataType() != eDT.GetNumericDataType()) ||
3370 560 : (bIsRead && bufferDataType.GetSize() < eDT.GetSize()))
3371 : {
3372 52 : return IReadWriteGeneric(startp.data(), count, arrayStep, bufferStride,
3373 : bufferDataType, buffer, NCGetPutVar1Func,
3374 52 : ReadOrWriteOneElement);
3375 : }
3376 :
3377 671 : bUseSlowPath = false;
3378 671 : ptrdiff_t nExpectedBufferStride = 1;
3379 1822 : for (int i = m_nDims; i != 0;)
3380 : {
3381 1162 : --i;
3382 1162 : if (count[i] != 1 &&
3383 695 : (arrayStep[i] != 1 || bufferStride[i] != nExpectedBufferStride))
3384 : {
3385 11 : bUseSlowPath = true;
3386 11 : break;
3387 : }
3388 1151 : nExpectedBufferStride *= count[i];
3389 : }
3390 671 : if (!bUseSlowPath)
3391 : {
3392 : // nc_get_varm() is terribly inefficient, so use nc_get_vara()
3393 : // when possible.
3394 : int ret =
3395 660 : NCGetPutVaraFunc(m_gid, m_varid, startp.data(), count, buffer);
3396 660 : if (ret != NC_NOERR)
3397 : {
3398 1 : NCDF_ERR(ret);
3399 1 : return false;
3400 : }
3401 1205 : if (bIsRead &&
3402 546 : (!m_bPerfectDataTypeMatch ||
3403 546 : bufferDataType.GetNumericDataType() != eDT.GetNumericDataType()))
3404 : {
3405 : // If the buffer data type is "larger" or of the same size as the
3406 : // native data type, we can do a in-place conversion
3407 56 : GByte *pabyBuffer =
3408 : static_cast<GByte *>(const_cast<void *>(buffer));
3409 56 : CPLAssert(bufferDataType.GetSize() >= eDT.GetSize());
3410 56 : const auto nDTSize = eDT.GetSize();
3411 56 : const auto nBufferDTSize = bufferDataType.GetSize();
3412 56 : if (!m_bPerfectDataTypeMatch &&
3413 0 : (m_nVarType == NC_CHAR || m_nVarType == NC_BYTE))
3414 : {
3415 : // native NC type translates into GDAL data type of larger size
3416 0 : for (ptrdiff_t i = nExpectedBufferStride - 1; i >= 0; --i)
3417 : {
3418 : GByte abySrc[sizeof(
3419 : double)]; // 2 is enough here, but sizeof(double) make
3420 : // MSVC happy
3421 0 : abySrc[0] = *(pabyBuffer + i);
3422 0 : ConvertNCToGDAL(&abySrc[0]);
3423 0 : GDALExtendedDataType::CopyValue(
3424 0 : &abySrc[0], eDT, pabyBuffer + i * nBufferDTSize,
3425 : bufferDataType);
3426 0 : }
3427 : }
3428 56 : else if (!m_bPerfectDataTypeMatch)
3429 : {
3430 : // native NC type translates into GDAL data type of same size
3431 0 : CPLAssert(m_nVarType == NC_INT64 || m_nVarType == NC_UINT64);
3432 0 : for (ptrdiff_t i = nExpectedBufferStride - 1; i >= 0; --i)
3433 : {
3434 0 : ConvertNCToGDAL(pabyBuffer + i * nDTSize);
3435 0 : GDALExtendedDataType::CopyValue(
3436 0 : pabyBuffer + i * nDTSize, eDT,
3437 0 : pabyBuffer + i * nBufferDTSize, bufferDataType);
3438 : }
3439 : }
3440 : else
3441 : {
3442 3036 : for (ptrdiff_t i = nExpectedBufferStride - 1; i >= 0; --i)
3443 : {
3444 2980 : GDALExtendedDataType::CopyValue(
3445 2980 : pabyBuffer + i * nDTSize, eDT,
3446 2980 : pabyBuffer + i * nBufferDTSize, bufferDataType);
3447 : }
3448 : }
3449 : }
3450 659 : return true;
3451 : }
3452 : else
3453 : {
3454 11 : if (bufferDataType.GetNumericDataType() != eDT.GetNumericDataType())
3455 : {
3456 4 : return IReadWriteGeneric(startp.data(), count, arrayStep,
3457 : bufferStride, bufferDataType, buffer,
3458 4 : NCGetPutVar1Func, ReadOrWriteOneElement);
3459 : }
3460 14 : std::vector<ptrdiff_t> stridep;
3461 7 : stridep.reserve(m_nDims);
3462 7 : std::vector<ptrdiff_t> imapp;
3463 7 : imapp.reserve(m_nDims);
3464 25 : for (int i = 0; i < m_nDims; i++)
3465 : {
3466 18 : stridep.push_back(
3467 18 : static_cast<ptrdiff_t>(count[i] == 1 ? 1 : arrayStep[i]));
3468 18 : imapp.push_back(static_cast<ptrdiff_t>(bufferStride[i]));
3469 : }
3470 :
3471 7 : if (!m_poShared->GetImappIsInElements())
3472 : {
3473 : const size_t nMul =
3474 0 : GetNCTypeSize(eDT, m_bPerfectDataTypeMatch, m_nVarType);
3475 0 : for (int i = 0; i < m_nDims; ++i)
3476 : {
3477 0 : imapp[i] = static_cast<ptrdiff_t>(imapp[i] * nMul);
3478 : }
3479 : }
3480 7 : int ret = NCGetPutVarmFunc(m_gid, m_varid, startp.data(), count,
3481 7 : stridep.data(), imapp.data(), buffer);
3482 7 : NCDF_ERR(ret);
3483 7 : return ret == NC_NOERR;
3484 : }
3485 : }
3486 :
3487 : /************************************************************************/
3488 : /* ConvertNCToGDAL() */
3489 : /************************************************************************/
3490 :
3491 579 : void netCDFVariable::ConvertNCToGDAL(GByte *buffer) const
3492 : {
3493 579 : if (!m_bPerfectDataTypeMatch)
3494 : {
3495 0 : if (m_nVarType == NC_CHAR || m_nVarType == NC_BYTE)
3496 : {
3497 0 : short s = reinterpret_cast<signed char *>(buffer)[0];
3498 0 : memcpy(buffer, &s, sizeof(s));
3499 : }
3500 0 : else if (m_nVarType == NC_INT64)
3501 : {
3502 0 : double v =
3503 0 : static_cast<double>(reinterpret_cast<GInt64 *>(buffer)[0]);
3504 0 : memcpy(buffer, &v, sizeof(v));
3505 : }
3506 0 : else if (m_nVarType == NC_UINT64)
3507 : {
3508 0 : double v =
3509 0 : static_cast<double>(reinterpret_cast<GUInt64 *>(buffer)[0]);
3510 0 : memcpy(buffer, &v, sizeof(v));
3511 : }
3512 : }
3513 579 : }
3514 :
3515 : /************************************************************************/
3516 : /* ReadOneElement() */
3517 : /************************************************************************/
3518 :
3519 580 : bool netCDFVariable::ReadOneElement(const GDALExtendedDataType &src_datatype,
3520 : const GDALExtendedDataType &bufferDataType,
3521 : const size_t *array_idx,
3522 : void *pDstBuffer) const
3523 : {
3524 580 : if (src_datatype.GetClass() == GEDTC_STRING)
3525 : {
3526 1 : char *pszStr = nullptr;
3527 1 : int ret = nc_get_var1_string(m_gid, m_varid, array_idx, &pszStr);
3528 1 : NCDF_ERR(ret);
3529 1 : if (ret != NC_NOERR)
3530 0 : return false;
3531 1 : GDALExtendedDataType::CopyValue(&pszStr, src_datatype, pDstBuffer,
3532 : bufferDataType);
3533 1 : nc_free_string(1, &pszStr);
3534 1 : return true;
3535 : }
3536 :
3537 : std::vector<GByte> abySrc(std::max(
3538 0 : src_datatype.GetSize(),
3539 1158 : GetNCTypeSize(src_datatype, m_bPerfectDataTypeMatch, m_nVarType)));
3540 :
3541 579 : int ret = nc_get_var1(m_gid, m_varid, array_idx, &abySrc[0]);
3542 579 : NCDF_ERR(ret);
3543 579 : if (ret != NC_NOERR)
3544 0 : return false;
3545 :
3546 579 : ConvertNCToGDAL(&abySrc[0]);
3547 :
3548 579 : GDALExtendedDataType::CopyValue(&abySrc[0], src_datatype, pDstBuffer,
3549 : bufferDataType);
3550 579 : return true;
3551 : }
3552 :
3553 : /************************************************************************/
3554 : /* IRead() */
3555 : /************************************************************************/
3556 :
3557 678 : bool netCDFVariable::IRead(const GUInt64 *arrayStartIdx, const size_t *count,
3558 : const GInt64 *arrayStep,
3559 : const GPtrDiff_t *bufferStride,
3560 : const GDALExtendedDataType &bufferDataType,
3561 : void *pDstBuffer) const
3562 : {
3563 678 : if (m_nDims == 2 && m_nVarType == NC_CHAR && GetDimensions().size() == 1)
3564 : {
3565 8 : CPLMutexHolderD(&hNCMutex);
3566 4 : m_poShared->SetDefineMode(false);
3567 :
3568 4 : if (bufferDataType.GetClass() != GEDTC_STRING)
3569 0 : return false;
3570 4 : char **ppszDstBuffer = static_cast<char **>(pDstBuffer);
3571 4 : size_t array_idx[2] = {static_cast<size_t>(arrayStartIdx[0]), 0};
3572 4 : size_t array_count[2] = {1, m_nTextLength};
3573 4 : std::string osTmp(m_nTextLength, 0);
3574 4 : char *pszTmp = &osTmp[0];
3575 4 : bool ret = true;
3576 12 : for (size_t i = 0; ret && i < count[0]; i++)
3577 : {
3578 : int ncErr =
3579 8 : nc_get_vara(m_gid, m_varid, array_idx, array_count, pszTmp);
3580 8 : NCDF_ERR(ncErr);
3581 8 : ret = ncErr == NC_NOERR;
3582 8 : if (ret)
3583 : {
3584 8 : *ppszDstBuffer = CPLStrdup(pszTmp);
3585 8 : array_idx[0] = static_cast<size_t>(array_idx[0] + arrayStep[0]);
3586 8 : ppszDstBuffer += bufferStride[0];
3587 : }
3588 : }
3589 4 : return ret;
3590 : }
3591 :
3592 674 : if (m_poCachedArray)
3593 : {
3594 5 : const auto nDims = GetDimensionCount();
3595 5 : std::vector<GUInt64> modifiedArrayStartIdx(nDims);
3596 5 : bool canUseCache = true;
3597 13 : for (size_t i = 0; i < nDims; i++)
3598 : {
3599 17 : if (arrayStartIdx[i] >= m_cachedArrayStartIdx[i] &&
3600 8 : arrayStartIdx[i] + (count[i] - 1) * arrayStep[i] <=
3601 8 : m_cachedArrayStartIdx[i] + m_cachedCount[i] - 1)
3602 : {
3603 8 : modifiedArrayStartIdx[i] =
3604 8 : arrayStartIdx[i] - m_cachedArrayStartIdx[i];
3605 : }
3606 : else
3607 : {
3608 1 : canUseCache = false;
3609 1 : break;
3610 : }
3611 : }
3612 5 : if (canUseCache)
3613 : {
3614 4 : return m_poCachedArray->Read(modifiedArrayStartIdx.data(), count,
3615 : arrayStep, bufferStride,
3616 4 : bufferDataType, pDstBuffer);
3617 : }
3618 : }
3619 :
3620 670 : if (IsTransposedRequest(count, bufferStride))
3621 : {
3622 84 : return ReadForTransposedRequest(arrayStartIdx, count, arrayStep,
3623 : bufferStride, bufferDataType,
3624 84 : pDstBuffer);
3625 : }
3626 :
3627 586 : return IReadWrite(true, arrayStartIdx, count, arrayStep, bufferStride,
3628 : bufferDataType, pDstBuffer, nc_get_var1, nc_get_vara,
3629 586 : nc_get_varm, &netCDFVariable::ReadOneElement);
3630 : }
3631 :
3632 : /************************************************************************/
3633 : /* IAdviseRead() */
3634 : /************************************************************************/
3635 :
3636 4 : bool netCDFVariable::IAdviseRead(const GUInt64 *arrayStartIdx,
3637 : const size_t *count,
3638 : CSLConstList /* papszOptions */) const
3639 : {
3640 4 : const auto nDims = GetDimensionCount();
3641 4 : if (nDims == 0)
3642 0 : return true;
3643 4 : const auto &eDT = GetDataType();
3644 4 : if (eDT.GetClass() != GEDTC_NUMERIC)
3645 0 : return false;
3646 :
3647 4 : m_poCachedArray.reset();
3648 :
3649 4 : size_t nElts = 1;
3650 12 : for (size_t i = 0; i < nDims; i++)
3651 8 : nElts *= count[i];
3652 :
3653 4 : void *pData = VSI_MALLOC2_VERBOSE(nElts, eDT.GetSize());
3654 4 : if (pData == nullptr)
3655 0 : return false;
3656 :
3657 4 : if (!Read(arrayStartIdx, count, nullptr, nullptr, eDT, pData))
3658 : {
3659 0 : VSIFree(pData);
3660 0 : return false;
3661 : }
3662 :
3663 : auto poDS = std::unique_ptr<GDALDataset>(
3664 8 : MEMDataset::CreateMultiDimensional("", nullptr, nullptr));
3665 8 : auto poGroup = poDS->GetRootGroup();
3666 :
3667 4 : std::vector<std::shared_ptr<GDALDimension>> apoMemDims;
3668 4 : const auto &poDims = GetDimensions();
3669 12 : for (size_t i = 0; i < nDims; i++)
3670 : {
3671 : apoMemDims.emplace_back(
3672 24 : poGroup->CreateDimension(poDims[i]->GetName(), std::string(),
3673 24 : std::string(), count[i], nullptr));
3674 : }
3675 : m_poCachedArray =
3676 4 : poGroup->CreateMDArray(GetName(), apoMemDims, eDT, nullptr);
3677 4 : m_poCachedArray->Write(std::vector<GUInt64>(nDims).data(), count, nullptr,
3678 : nullptr, eDT, pData);
3679 4 : m_cachedArrayStartIdx.resize(nDims);
3680 4 : memcpy(&m_cachedArrayStartIdx[0], arrayStartIdx, nDims * sizeof(GUInt64));
3681 4 : m_cachedCount.resize(nDims);
3682 4 : memcpy(&m_cachedCount[0], count, nDims * sizeof(size_t));
3683 4 : VSIFree(pData);
3684 4 : return true;
3685 : }
3686 :
3687 : /************************************************************************/
3688 : /* ConvertGDALToNC() */
3689 : /************************************************************************/
3690 :
3691 111 : void netCDFVariable::ConvertGDALToNC(GByte *buffer) const
3692 : {
3693 111 : if (!m_bPerfectDataTypeMatch)
3694 : {
3695 0 : if (m_nVarType == NC_CHAR || m_nVarType == NC_BYTE)
3696 : {
3697 0 : const auto c =
3698 0 : static_cast<signed char>(reinterpret_cast<short *>(buffer)[0]);
3699 0 : memcpy(buffer, &c, sizeof(c));
3700 : }
3701 0 : else if (m_nVarType == NC_INT64)
3702 : {
3703 0 : const auto v =
3704 0 : static_cast<GInt64>(reinterpret_cast<double *>(buffer)[0]);
3705 0 : memcpy(buffer, &v, sizeof(v));
3706 : }
3707 0 : else if (m_nVarType == NC_UINT64)
3708 : {
3709 0 : const auto v =
3710 0 : static_cast<GUInt64>(reinterpret_cast<double *>(buffer)[0]);
3711 0 : memcpy(buffer, &v, sizeof(v));
3712 : }
3713 : }
3714 111 : }
3715 :
3716 : /************************************************************************/
3717 : /* WriteOneElement() */
3718 : /************************************************************************/
3719 :
3720 94 : bool netCDFVariable::WriteOneElement(const GDALExtendedDataType &dst_datatype,
3721 : const GDALExtendedDataType &bufferDataType,
3722 : const size_t *array_idx,
3723 : const void *pSrcBuffer) const
3724 : {
3725 94 : if (dst_datatype.GetClass() == GEDTC_STRING)
3726 : {
3727 1 : const char *pszStr = (static_cast<const char *const *>(pSrcBuffer))[0];
3728 1 : int ret = nc_put_var1_string(m_gid, m_varid, array_idx, &pszStr);
3729 1 : NCDF_ERR(ret);
3730 1 : return ret == NC_NOERR;
3731 : }
3732 :
3733 93 : std::vector<GByte> abyTmp(dst_datatype.GetSize());
3734 93 : GDALExtendedDataType::CopyValue(pSrcBuffer, bufferDataType, &abyTmp[0],
3735 : dst_datatype);
3736 :
3737 93 : ConvertGDALToNC(&abyTmp[0]);
3738 :
3739 93 : int ret = nc_put_var1(m_gid, m_varid, array_idx, &abyTmp[0]);
3740 93 : NCDF_ERR(ret);
3741 93 : return ret == NC_NOERR;
3742 : }
3743 :
3744 : /************************************************************************/
3745 : /* IWrite() */
3746 : /************************************************************************/
3747 :
3748 175 : bool netCDFVariable::IWrite(const GUInt64 *arrayStartIdx, const size_t *count,
3749 : const GInt64 *arrayStep,
3750 : const GPtrDiff_t *bufferStride,
3751 : const GDALExtendedDataType &bufferDataType,
3752 : const void *pSrcBuffer)
3753 : {
3754 175 : m_bHasWrittenData = true;
3755 :
3756 175 : m_poCachedArray.reset();
3757 :
3758 175 : if (m_nDims == 2 && m_nVarType == NC_CHAR && GetDimensions().size() == 1)
3759 : {
3760 6 : CPLMutexHolderD(&hNCMutex);
3761 3 : m_poShared->SetDefineMode(false);
3762 :
3763 3 : if (bufferDataType.GetClass() != GEDTC_STRING)
3764 0 : return false;
3765 3 : const char *const *ppszSrcBuffer =
3766 : static_cast<const char *const *>(pSrcBuffer);
3767 3 : size_t array_idx[2] = {static_cast<size_t>(arrayStartIdx[0]), 0};
3768 3 : size_t array_count[2] = {1, m_nTextLength};
3769 6 : std::string osTmp(m_nTextLength, 0);
3770 8 : for (size_t i = 0; i < count[0]; i++)
3771 : {
3772 5 : const char *pszStr = *ppszSrcBuffer;
3773 5 : memset(&osTmp[0], 0, m_nTextLength);
3774 5 : if (pszStr)
3775 : {
3776 5 : size_t nLen = strlen(pszStr);
3777 5 : memcpy(&osTmp[0], pszStr, std::min(m_nTextLength, nLen));
3778 : }
3779 : int ret =
3780 5 : nc_put_vara(m_gid, m_varid, array_idx, array_count, &osTmp[0]);
3781 5 : NCDF_ERR(ret);
3782 5 : if (ret != NC_NOERR)
3783 0 : return false;
3784 5 : array_idx[0] = static_cast<size_t>(array_idx[0] + arrayStep[0]);
3785 5 : ppszSrcBuffer += bufferStride[0];
3786 : }
3787 3 : return true;
3788 : }
3789 :
3790 172 : return IReadWrite(false, arrayStartIdx, count, arrayStep, bufferStride,
3791 : bufferDataType, pSrcBuffer, nc_put_var1, nc_put_vara,
3792 172 : nc_put_varm, &netCDFVariable::WriteOneElement);
3793 : }
3794 :
3795 : /************************************************************************/
3796 : /* GetRawNoDataValue() */
3797 : /************************************************************************/
3798 :
3799 273 : const void *netCDFVariable::GetRawNoDataValue() const
3800 : {
3801 273 : const auto &dt = GetDataType();
3802 273 : if (dt.GetClass() != GEDTC_NUMERIC)
3803 2 : return nullptr;
3804 :
3805 271 : if (m_bGetRawNoDataValueHasRun)
3806 : {
3807 111 : return m_abyNoData.empty() ? nullptr : m_abyNoData.data();
3808 : }
3809 :
3810 160 : m_bGetRawNoDataValueHasRun = true;
3811 :
3812 160 : const char *pszAttrName = NCDF_FillValue;
3813 480 : auto poAttr = GetAttribute(pszAttrName);
3814 160 : if (!poAttr)
3815 : {
3816 126 : pszAttrName = "missing_value";
3817 126 : poAttr = GetAttribute(pszAttrName);
3818 : }
3819 160 : if (poAttr && poAttr->GetDataType().GetClass() == GEDTC_NUMERIC)
3820 : {
3821 37 : auto oRawResult = poAttr->ReadAsRaw();
3822 37 : if (oRawResult.data())
3823 : {
3824 : // Round-trip attribute value to target data type and back
3825 : // to attribute data type to ensure there is no loss
3826 : // Normally _FillValue data type should be the same
3827 : // as the array one, but this is not always the case.
3828 : // For example NASA GEDI L2B products have Float64
3829 : // _FillValue for Float32 variables.
3830 37 : m_abyNoData.resize(dt.GetSize());
3831 37 : GDALExtendedDataType::CopyValue(oRawResult.data(),
3832 37 : poAttr->GetDataType(),
3833 37 : m_abyNoData.data(), dt);
3834 74 : std::vector<GByte> abyTmp(poAttr->GetDataType().GetSize());
3835 37 : GDALExtendedDataType::CopyValue(
3836 37 : m_abyNoData.data(), dt, abyTmp.data(), poAttr->GetDataType());
3837 74 : std::vector<GByte> abyOri;
3838 37 : abyOri.assign(oRawResult.data(),
3839 37 : oRawResult.data() + oRawResult.size());
3840 37 : if (abyOri == abyTmp)
3841 36 : return m_abyNoData.data();
3842 1 : m_abyNoData.clear();
3843 1 : char *pszVal = nullptr;
3844 1 : GDALExtendedDataType::CopyValue(
3845 1 : oRawResult.data(), poAttr->GetDataType(), &pszVal,
3846 2 : GDALExtendedDataType::CreateString());
3847 1 : CPLError(CE_Warning, CPLE_AppDefined,
3848 : "%s attribute value (%s) is not in the range of the "
3849 : "variable data type",
3850 1 : pszAttrName, pszVal ? pszVal : "(null)");
3851 1 : CPLFree(pszVal);
3852 1 : return nullptr;
3853 : }
3854 : }
3855 123 : else if (poAttr && poAttr->GetDataType().GetClass() == GEDTC_STRING)
3856 : {
3857 3 : const char *pszVal = poAttr->ReadAsString();
3858 3 : if (pszVal)
3859 : {
3860 : // Round-trip attribute value to target data type and back
3861 : // to attribute data type to ensure there is no loss
3862 3 : m_abyNoData.resize(dt.GetSize());
3863 3 : GDALExtendedDataType::CopyValue(&pszVal, poAttr->GetDataType(),
3864 3 : m_abyNoData.data(), dt);
3865 3 : char *pszTmpVal = nullptr;
3866 3 : GDALExtendedDataType::CopyValue(m_abyNoData.data(), dt, &pszTmpVal,
3867 3 : poAttr->GetDataType());
3868 3 : if (pszTmpVal)
3869 : {
3870 3 : const bool bSame = strcmp(pszVal, pszTmpVal) == 0;
3871 3 : CPLFree(pszTmpVal);
3872 3 : if (bSame)
3873 3 : return m_abyNoData.data();
3874 2 : CPLError(CE_Warning, CPLE_AppDefined,
3875 : "%s attribute value ('%s') is not in the range of the "
3876 : "variable data type",
3877 : pszAttrName, pszVal);
3878 2 : m_abyNoData.clear();
3879 2 : return nullptr;
3880 : }
3881 : }
3882 : }
3883 :
3884 132 : if (m_bUseDefaultFillAsNoData && m_abyNoData.empty() &&
3885 12 : (m_nVarType == NC_SHORT || m_nVarType == NC_USHORT ||
3886 8 : m_nVarType == NC_INT || m_nVarType == NC_UINT ||
3887 6 : m_nVarType == NC_FLOAT || m_nVarType == NC_DOUBLE))
3888 : {
3889 9 : bool bGotNoData = false;
3890 : double dfNoData =
3891 9 : NCDFGetDefaultNoDataValue(m_gid, m_varid, m_nVarType, bGotNoData);
3892 9 : m_abyNoData.resize(dt.GetSize());
3893 9 : GDALCopyWords(&dfNoData, GDT_Float64, 0, &m_abyNoData[0],
3894 : dt.GetNumericDataType(), 0, 1);
3895 : }
3896 114 : else if (m_bUseDefaultFillAsNoData && m_abyNoData.empty() &&
3897 3 : m_nVarType == NC_INT64)
3898 : {
3899 1 : bool bGotNoData = false;
3900 : const auto nNoData =
3901 1 : NCDFGetDefaultNoDataValueAsInt64(m_gid, m_varid, bGotNoData);
3902 1 : m_abyNoData.resize(dt.GetSize());
3903 1 : memcpy(&m_abyNoData[0], &nNoData, sizeof(nNoData));
3904 : }
3905 112 : else if (m_bUseDefaultFillAsNoData && m_abyNoData.empty() &&
3906 2 : m_nVarType == NC_UINT64)
3907 : {
3908 1 : bool bGotNoData = false;
3909 : const auto nNoData =
3910 1 : NCDFGetDefaultNoDataValueAsUInt64(m_gid, m_varid, bGotNoData);
3911 1 : m_abyNoData.resize(dt.GetSize());
3912 1 : memcpy(&m_abyNoData[0], &nNoData, sizeof(nNoData));
3913 : }
3914 :
3915 120 : return m_abyNoData.empty() ? nullptr : m_abyNoData.data();
3916 : }
3917 :
3918 : /************************************************************************/
3919 : /* SetRawNoDataValue() */
3920 : /************************************************************************/
3921 :
3922 21 : bool netCDFVariable::SetRawNoDataValue(const void *pNoData)
3923 : {
3924 21 : GetDataType();
3925 21 : if (m_nVarType == NC_STRING)
3926 0 : return false;
3927 :
3928 21 : m_bGetRawNoDataValueHasRun = false;
3929 42 : CPLMutexHolderD(&hNCMutex);
3930 21 : m_poShared->SetDefineMode(true);
3931 : int ret;
3932 21 : if (pNoData == nullptr)
3933 : {
3934 3 : m_abyNoData.clear();
3935 3 : nc_type atttype = NC_NAT;
3936 3 : size_t attlen = 0;
3937 3 : if (nc_inq_att(m_gid, m_varid, NCDF_FillValue, &atttype, &attlen) ==
3938 : NC_NOERR)
3939 2 : ret = nc_del_att(m_gid, m_varid, NCDF_FillValue);
3940 : else
3941 1 : ret = NC_NOERR;
3942 3 : if (nc_inq_att(m_gid, m_varid, "missing_value", &atttype, &attlen) ==
3943 : NC_NOERR)
3944 : {
3945 0 : int ret2 = nc_del_att(m_gid, m_varid, "missing_value");
3946 0 : if (ret2 != NC_NOERR)
3947 0 : ret = ret2;
3948 : }
3949 : }
3950 : else
3951 : {
3952 18 : const auto nSize = GetDataType().GetSize();
3953 18 : m_abyNoData.resize(nSize);
3954 18 : memcpy(&m_abyNoData[0], pNoData, nSize);
3955 :
3956 18 : std::vector<GByte> abyTmp(nSize);
3957 18 : memcpy(&abyTmp[0], pNoData, nSize);
3958 18 : ConvertGDALToNC(&abyTmp[0]);
3959 :
3960 18 : if (!m_bHasWrittenData)
3961 : {
3962 16 : ret = nc_def_var_fill(m_gid, m_varid, NC_FILL, &abyTmp[0]);
3963 16 : NCDF_ERR(ret);
3964 : }
3965 :
3966 18 : nc_type atttype = NC_NAT;
3967 18 : size_t attlen = 0;
3968 18 : if (nc_inq_att(m_gid, m_varid, "missing_value", &atttype, &attlen) ==
3969 : NC_NOERR)
3970 : {
3971 2 : if (nc_inq_att(m_gid, m_varid, NCDF_FillValue, &atttype, &attlen) ==
3972 : NC_NOERR)
3973 : {
3974 1 : CPLError(CE_Failure, CPLE_NotSupported,
3975 : "Cannot change nodata when missing_value and "
3976 : "_FillValue both exist");
3977 1 : return false;
3978 : }
3979 1 : ret = nc_put_att(m_gid, m_varid, "missing_value", m_nVarType, 1,
3980 1 : &abyTmp[0]);
3981 : }
3982 : else
3983 : {
3984 16 : ret = nc_put_att(m_gid, m_varid, NCDF_FillValue, m_nVarType, 1,
3985 16 : &abyTmp[0]);
3986 : }
3987 : }
3988 20 : NCDF_ERR(ret);
3989 20 : if (ret == NC_NOERR)
3990 20 : m_bGetRawNoDataValueHasRun = true;
3991 20 : return ret == NC_NOERR;
3992 : }
3993 :
3994 : /************************************************************************/
3995 : /* SetScale() */
3996 : /************************************************************************/
3997 :
3998 3 : bool netCDFVariable::SetScale(double dfScale, GDALDataType eStorageType)
3999 : {
4000 9 : auto poAttr = GetAttribute(CF_SCALE_FACTOR);
4001 3 : if (!poAttr)
4002 : {
4003 4 : poAttr = CreateAttribute(
4004 : CF_SCALE_FACTOR, {},
4005 4 : GDALExtendedDataType::Create(
4006 : eStorageType == GDT_Unknown ? GDT_Float64 : eStorageType),
4007 2 : nullptr);
4008 : }
4009 3 : if (!poAttr)
4010 0 : return false;
4011 3 : return poAttr->Write(dfScale);
4012 : }
4013 :
4014 : /************************************************************************/
4015 : /* SetOffset() */
4016 : /************************************************************************/
4017 :
4018 3 : bool netCDFVariable::SetOffset(double dfOffset, GDALDataType eStorageType)
4019 : {
4020 9 : auto poAttr = GetAttribute(CF_ADD_OFFSET);
4021 3 : if (!poAttr)
4022 : {
4023 4 : poAttr = CreateAttribute(
4024 : CF_ADD_OFFSET, {},
4025 4 : GDALExtendedDataType::Create(
4026 : eStorageType == GDT_Unknown ? GDT_Float64 : eStorageType),
4027 2 : nullptr);
4028 : }
4029 3 : if (!poAttr)
4030 0 : return false;
4031 3 : return poAttr->Write(dfOffset);
4032 : }
4033 :
4034 : /************************************************************************/
4035 : /* GetScale() */
4036 : /************************************************************************/
4037 :
4038 96 : double netCDFVariable::GetScale(bool *pbHasScale,
4039 : GDALDataType *peStorageType) const
4040 : {
4041 288 : auto poAttr = GetAttribute(CF_SCALE_FACTOR);
4042 96 : if (!poAttr || poAttr->GetDataType().GetClass() != GEDTC_NUMERIC)
4043 : {
4044 91 : if (pbHasScale)
4045 90 : *pbHasScale = false;
4046 91 : return 1.0;
4047 : }
4048 5 : if (pbHasScale)
4049 5 : *pbHasScale = true;
4050 5 : if (peStorageType)
4051 2 : *peStorageType = poAttr->GetDataType().GetNumericDataType();
4052 5 : return poAttr->ReadAsDouble();
4053 : }
4054 :
4055 : /************************************************************************/
4056 : /* GetOffset() */
4057 : /************************************************************************/
4058 :
4059 96 : double netCDFVariable::GetOffset(bool *pbHasOffset,
4060 : GDALDataType *peStorageType) const
4061 : {
4062 288 : auto poAttr = GetAttribute(CF_ADD_OFFSET);
4063 96 : if (!poAttr || poAttr->GetDataType().GetClass() != GEDTC_NUMERIC)
4064 : {
4065 91 : if (pbHasOffset)
4066 90 : *pbHasOffset = false;
4067 91 : return 0.0;
4068 : }
4069 5 : if (pbHasOffset)
4070 5 : *pbHasOffset = true;
4071 5 : if (peStorageType)
4072 2 : *peStorageType = poAttr->GetDataType().GetNumericDataType();
4073 5 : return poAttr->ReadAsDouble();
4074 : }
4075 :
4076 : /************************************************************************/
4077 : /* GetBlockSize() */
4078 : /************************************************************************/
4079 :
4080 254 : std::vector<GUInt64> netCDFVariable::GetBlockSize() const
4081 : {
4082 254 : const auto nDimCount = GetDimensionCount();
4083 254 : std::vector<GUInt64> res(nDimCount);
4084 254 : if (res.empty())
4085 0 : return res;
4086 254 : int nStorageType = 0;
4087 : // We add 1 to the dimension count, for 2D char variables that we
4088 : // expose as a 1D variable.
4089 508 : std::vector<size_t> anTemp(1 + nDimCount);
4090 508 : CPLMutexHolderD(&hNCMutex);
4091 254 : nc_inq_var_chunking(m_gid, m_varid, &nStorageType, &anTemp[0]);
4092 254 : if (nStorageType == NC_CHUNKED)
4093 : {
4094 59 : for (size_t i = 0; i < res.size(); ++i)
4095 38 : res[i] = anTemp[i];
4096 : }
4097 254 : return res;
4098 : }
4099 :
4100 : /************************************************************************/
4101 : /* GetAttribute() */
4102 : /************************************************************************/
4103 :
4104 : std::shared_ptr<GDALAttribute>
4105 4094 : netCDFVariable::GetAttribute(const std::string &osName) const
4106 : {
4107 8188 : CPLMutexHolderD(&hNCMutex);
4108 4094 : int nAttId = -1;
4109 4094 : if (nc_inq_attid(m_gid, m_varid, osName.c_str(), &nAttId) != NC_NOERR)
4110 3125 : return nullptr;
4111 1938 : return netCDFAttribute::Create(
4112 2907 : m_poShared, std::dynamic_pointer_cast<netCDFVariable>(m_pSelf.lock()),
4113 1938 : m_gid, m_varid, osName);
4114 : }
4115 :
4116 : /************************************************************************/
4117 : /* GetAttributes() */
4118 : /************************************************************************/
4119 :
4120 : std::vector<std::shared_ptr<GDALAttribute>>
4121 196 : netCDFVariable::GetAttributes(CSLConstList papszOptions) const
4122 : {
4123 392 : CPLMutexHolderD(&hNCMutex);
4124 196 : std::vector<std::shared_ptr<GDALAttribute>> res;
4125 196 : int nbAttr = 0;
4126 196 : NCDF_ERR(nc_inq_varnatts(m_gid, m_varid, &nbAttr));
4127 196 : res.reserve(nbAttr);
4128 : const bool bShowAll =
4129 196 : CPLTestBool(CSLFetchNameValueDef(papszOptions, "SHOW_ALL", "NO"));
4130 657 : for (int i = 0; i < nbAttr; i++)
4131 : {
4132 : char szAttrName[NC_MAX_NAME + 1];
4133 461 : szAttrName[0] = 0;
4134 461 : NCDF_ERR(nc_inq_attname(m_gid, m_varid, i, szAttrName));
4135 461 : if (bShowAll || (!EQUAL(szAttrName, NCDF_FillValue) &&
4136 419 : !EQUAL(szAttrName, "missing_value") &&
4137 419 : !EQUAL(szAttrName, CF_UNITS) &&
4138 351 : !EQUAL(szAttrName, CF_SCALE_FACTOR) &&
4139 351 : !EQUAL(szAttrName, CF_ADD_OFFSET) &&
4140 351 : !EQUAL(szAttrName, CF_GRD_MAPPING) &&
4141 310 : !(EQUAL(szAttrName, "_Unsigned") &&
4142 36 : (m_nVarType == NC_BYTE || m_nVarType == NC_SHORT))))
4143 : {
4144 1136 : res.emplace_back(netCDFAttribute::Create(
4145 284 : m_poShared,
4146 568 : std::dynamic_pointer_cast<netCDFVariable>(m_pSelf.lock()),
4147 568 : m_gid, m_varid, szAttrName));
4148 : }
4149 : }
4150 392 : return res;
4151 : }
4152 :
4153 : /************************************************************************/
4154 : /* CreateAttribute() */
4155 : /************************************************************************/
4156 :
4157 135 : std::shared_ptr<GDALAttribute> netCDFVariable::CreateAttribute(
4158 : const std::string &osName, const std::vector<GUInt64> &anDimensions,
4159 : const GDALExtendedDataType &oDataType, CSLConstList papszOptions)
4160 : {
4161 270 : return netCDFAttribute::Create(
4162 405 : m_poShared, std::dynamic_pointer_cast<netCDFVariable>(m_pSelf.lock()),
4163 270 : m_gid, m_varid, osName, anDimensions, oDataType, papszOptions);
4164 : }
4165 :
4166 : /************************************************************************/
4167 : /* DeleteAttribute() */
4168 : /************************************************************************/
4169 :
4170 2 : bool netCDFVariable::DeleteAttribute(const std::string &osName,
4171 : CSLConstList /*papszOptions*/)
4172 : {
4173 4 : CPLMutexHolderD(&hNCMutex);
4174 2 : m_poShared->SetDefineMode(true);
4175 :
4176 2 : int ret = nc_del_att(m_gid, m_varid, osName.c_str());
4177 2 : NCDF_ERR(ret);
4178 2 : if (ret != NC_NOERR)
4179 1 : return false;
4180 :
4181 1 : auto it = m_oMapAttributes.find(osName);
4182 1 : if (it != m_oMapAttributes.end())
4183 : {
4184 1 : it->second->Deleted();
4185 1 : m_oMapAttributes.erase(it);
4186 : }
4187 :
4188 1 : return true;
4189 : }
4190 :
4191 : /************************************************************************/
4192 : /* GetCoordinateVariables() */
4193 : /************************************************************************/
4194 :
4195 : std::vector<std::shared_ptr<GDALMDArray>>
4196 12 : netCDFVariable::GetCoordinateVariables() const
4197 : {
4198 24 : std::vector<std::shared_ptr<GDALMDArray>> ret;
4199 :
4200 36 : const auto poCoordinates = GetAttribute("coordinates");
4201 3 : if (poCoordinates &&
4202 15 : poCoordinates->GetDataType().GetClass() == GEDTC_STRING &&
4203 3 : poCoordinates->GetDimensionCount() == 0)
4204 : {
4205 3 : const char *pszCoordinates = poCoordinates->ReadAsString();
4206 3 : if (pszCoordinates)
4207 : {
4208 : const CPLStringList aosNames(
4209 6 : NCDFTokenizeCoordinatesAttribute(pszCoordinates));
4210 6 : CPLMutexHolderD(&hNCMutex);
4211 9 : for (int i = 0; i < aosNames.size(); i++)
4212 : {
4213 6 : int nVarId = 0;
4214 6 : if (nc_inq_varid(m_gid, aosNames[i], &nVarId) == NC_NOERR)
4215 : {
4216 6 : ret.emplace_back(netCDFVariable::Create(
4217 12 : m_poShared, m_poParent.lock(), m_gid, nVarId,
4218 12 : std::vector<std::shared_ptr<GDALDimension>>(), nullptr,
4219 6 : false));
4220 : }
4221 : else
4222 : {
4223 0 : CPLError(
4224 : CE_Warning, CPLE_AppDefined,
4225 : "Cannot find variable corresponding to coordinate %s",
4226 : aosNames[i]);
4227 : }
4228 : }
4229 : }
4230 : }
4231 :
4232 : // Special case for NASA EMIT datasets
4233 24 : auto apoDims = GetDimensions();
4234 21 : if ((apoDims.size() == 3 && apoDims[0]->GetName() == "downtrack" &&
4235 6 : apoDims[1]->GetName() == "crosstrack" &&
4236 33 : apoDims[2]->GetName() == "bands") ||
4237 18 : (apoDims.size() == 2 && apoDims[0]->GetName() == "downtrack" &&
4238 3 : apoDims[1]->GetName() == "crosstrack"))
4239 : {
4240 6 : auto poRootGroup = netCDFGroup::Create(m_poShared, nullptr, m_gid);
4241 6 : if (poRootGroup)
4242 : {
4243 12 : auto poLocationGroup = poRootGroup->OpenGroup("location");
4244 6 : if (poLocationGroup)
4245 : {
4246 12 : auto poLon = poLocationGroup->OpenMDArray("lon");
4247 12 : auto poLat = poLocationGroup->OpenMDArray("lat");
4248 6 : if (poLon && poLat)
4249 : {
4250 18 : return {std::move(poLon), std::move(poLat)};
4251 : }
4252 : }
4253 : }
4254 : }
4255 :
4256 6 : return ret;
4257 : }
4258 :
4259 : /************************************************************************/
4260 : /* Resize() */
4261 : /************************************************************************/
4262 :
4263 9 : bool netCDFVariable::Resize(const std::vector<GUInt64> &anNewDimSizes,
4264 : CSLConstList /* papszOptions */)
4265 : {
4266 9 : if (!IsWritable())
4267 : {
4268 1 : CPLError(CE_Failure, CPLE_AppDefined,
4269 : "Resize() not supported on read-only file");
4270 1 : return false;
4271 : }
4272 :
4273 8 : const auto nDimCount = GetDimensionCount();
4274 8 : if (anNewDimSizes.size() != nDimCount)
4275 : {
4276 0 : CPLError(CE_Failure, CPLE_IllegalArg,
4277 : "Not expected number of values in anNewDimSizes.");
4278 0 : return false;
4279 : }
4280 :
4281 8 : auto &dims = GetDimensions();
4282 16 : std::vector<size_t> anGrownDimIdx;
4283 16 : std::map<GDALDimension *, GUInt64> oMapDimToSize;
4284 18 : for (size_t i = 0; i < nDimCount; ++i)
4285 : {
4286 14 : auto oIter = oMapDimToSize.find(dims[i].get());
4287 14 : if (oIter != oMapDimToSize.end() && oIter->second != anNewDimSizes[i])
4288 : {
4289 2 : CPLError(CE_Failure, CPLE_AppDefined,
4290 : "Cannot resize a dimension referenced several times "
4291 : "to different sizes");
4292 4 : return false;
4293 : }
4294 12 : if (anNewDimSizes[i] != dims[i]->GetSize())
4295 : {
4296 9 : if (anNewDimSizes[i] < dims[i]->GetSize())
4297 : {
4298 2 : CPLError(CE_Failure, CPLE_NotSupported,
4299 : "Resize() does not support shrinking the array.");
4300 2 : return false;
4301 : }
4302 :
4303 7 : oMapDimToSize[dims[i].get()] = anNewDimSizes[i];
4304 7 : anGrownDimIdx.push_back(i);
4305 : }
4306 : else
4307 : {
4308 3 : oMapDimToSize[dims[i].get()] = dims[i]->GetSize();
4309 : }
4310 : }
4311 :
4312 4 : if (!anGrownDimIdx.empty())
4313 : {
4314 4 : CPLMutexHolderD(&hNCMutex);
4315 : // Query which netCDF dimensions have unlimited size
4316 4 : int nUnlimitedDimIds = 0;
4317 4 : nc_inq_unlimdims(m_gid, &nUnlimitedDimIds, nullptr);
4318 4 : std::vector<int> anUnlimitedDimIds(nUnlimitedDimIds);
4319 4 : nc_inq_unlimdims(m_gid, &nUnlimitedDimIds, anUnlimitedDimIds.data());
4320 4 : std::set<int> oSetUnlimitedDimId;
4321 8 : for (int idx : anUnlimitedDimIds)
4322 4 : oSetUnlimitedDimId.insert(idx);
4323 :
4324 : // Check that dimensions that need to grow are of unlimited size
4325 8 : for (size_t dimIdx : anGrownDimIdx)
4326 : {
4327 : auto netCDFDim =
4328 5 : std::dynamic_pointer_cast<netCDFDimension>(dims[dimIdx]);
4329 5 : if (!netCDFDim)
4330 : {
4331 0 : CPLAssert(false);
4332 : }
4333 5 : else if (oSetUnlimitedDimId.find(netCDFDim->GetId()) ==
4334 10 : oSetUnlimitedDimId.end())
4335 : {
4336 1 : CPLError(CE_Failure, CPLE_NotSupported,
4337 : "Resize() cannot grow dimension %d (%s) "
4338 : "as it is not created as UNLIMITED.",
4339 : static_cast<int>(dimIdx),
4340 1 : netCDFDim->GetName().c_str());
4341 1 : return false;
4342 : }
4343 : }
4344 9 : for (size_t i = 0; i < nDimCount; ++i)
4345 : {
4346 6 : if (anNewDimSizes[i] > dims[i]->GetSize())
4347 : {
4348 : auto netCDFDim =
4349 6 : std::dynamic_pointer_cast<netCDFDimension>(dims[i]);
4350 3 : if (!netCDFDim)
4351 : {
4352 0 : CPLAssert(false);
4353 : }
4354 : else
4355 : {
4356 3 : netCDFDim->SetSize(anNewDimSizes[i]);
4357 : }
4358 : }
4359 : }
4360 : }
4361 3 : return true;
4362 : }
4363 :
4364 : /************************************************************************/
4365 : /* Rename() */
4366 : /************************************************************************/
4367 :
4368 4 : bool netCDFVariable::Rename(const std::string &osNewName)
4369 : {
4370 4 : if (m_poShared->IsReadOnly())
4371 : {
4372 1 : CPLError(CE_Failure, CPLE_AppDefined,
4373 : "Rename() not supported on read-only file");
4374 1 : return false;
4375 : }
4376 3 : if (osNewName.empty())
4377 : {
4378 1 : CPLError(CE_Failure, CPLE_NotSupported, "Empty name not supported");
4379 1 : return false;
4380 : }
4381 :
4382 4 : CPLMutexHolderD(&hNCMutex);
4383 2 : m_poShared->SetDefineMode(true);
4384 :
4385 2 : int ret = nc_rename_var(m_gid, m_varid, osNewName.c_str());
4386 2 : NCDF_ERR(ret);
4387 2 : if (ret != NC_NOERR)
4388 1 : return false;
4389 :
4390 1 : BaseRename(osNewName);
4391 :
4392 1 : return true;
4393 : }
4394 :
4395 : /************************************************************************/
4396 : /* NotifyChildrenOfRenaming() */
4397 : /************************************************************************/
4398 :
4399 3 : void netCDFVariable::NotifyChildrenOfRenaming()
4400 : {
4401 6 : for (const auto &iter : m_oMapAttributes)
4402 3 : iter.second->ParentRenamed(m_osFullName);
4403 3 : }
4404 :
4405 : /************************************************************************/
4406 : /* netCDFVariable::GetRawBlockInfo() */
4407 : /************************************************************************/
4408 :
4409 0 : bool netCDFVariable::GetRawBlockInfo(const uint64_t *panBlockCoordinates,
4410 : GDALMDArrayRawBlockInfo &info) const
4411 : {
4412 0 : info.clear();
4413 :
4414 0 : int nFormat = 0;
4415 0 : if (m_poShared->m_poHDF5DS)
4416 : {
4417 0 : nFormat = NC_FORMAT_NETCDF4;
4418 : }
4419 : else
4420 : {
4421 0 : CPLMutexHolderD(&hNCMutex);
4422 0 : NCDF_ERR(nc_inq_format(m_poShared->GetCDFId(), &nFormat));
4423 : }
4424 0 : if (nFormat == NC_FORMAT_NETCDF4 || nFormat == NC_FORMAT_NETCDF4_CLASSIC)
4425 : {
4426 0 : if (!m_poShared->m_poHDF5DS)
4427 : {
4428 0 : const char *const apszAllowedDrivers[] = {"HDF5", nullptr};
4429 0 : m_poShared->m_poHDF5DS.reset(
4430 0 : GDALDataset::Open(m_poShared->GetFilename().c_str(),
4431 : GDAL_OF_MULTIDIM_RASTER, apszAllowedDrivers));
4432 : }
4433 0 : if (m_poShared->m_poHDF5DS)
4434 : {
4435 0 : if (!m_poShared->m_poHDF5CurArray ||
4436 0 : m_poShared->m_poHDF5CurArray->GetFullName() != GetFullName())
4437 : {
4438 0 : m_poShared->m_poHDF5CurArray =
4439 0 : m_poShared->m_poHDF5DS->GetRootGroup()
4440 0 : ->OpenMDArrayFromFullname(GetFullName());
4441 : }
4442 0 : if (m_poShared->m_poHDF5CurArray)
4443 : {
4444 0 : return m_poShared->m_poHDF5CurArray->GetRawBlockInfo(
4445 0 : panBlockCoordinates, info);
4446 : }
4447 : }
4448 0 : return false;
4449 : }
4450 :
4451 : // We should implement https://docs.unidata.ucar.edu/nug/2.0-draft/nc3_file_formats.html ...
4452 0 : CPLDebug("netCDF", "GetRawBlockInfo() not implemented for netCDF classic");
4453 :
4454 0 : return false;
4455 : }
4456 :
4457 : /************************************************************************/
4458 : /* retrieveAttributeParentName() */
4459 : /************************************************************************/
4460 :
4461 3344 : static CPLString retrieveAttributeParentName(int gid, int varid)
4462 : {
4463 6688 : auto groupName(NCDFGetGroupFullName(gid));
4464 3344 : if (varid == NC_GLOBAL)
4465 : {
4466 570 : if (groupName == "/")
4467 544 : return "/_GLOBAL_";
4468 52 : return groupName + "/_GLOBAL_";
4469 : }
4470 :
4471 5548 : return groupName + "/" + netCDFVariable::retrieveName(gid, varid);
4472 : }
4473 :
4474 : /************************************************************************/
4475 : /* netCDFAttribute() */
4476 : /************************************************************************/
4477 :
4478 1407 : netCDFAttribute::netCDFAttribute(
4479 : const std::shared_ptr<netCDFSharedResources> &poShared, int gid, int varid,
4480 1407 : const std::string &name)
4481 1407 : : GDALAbstractMDArray(retrieveAttributeParentName(gid, varid), name),
4482 1407 : GDALAttribute(retrieveAttributeParentName(gid, varid), name),
4483 4221 : m_poShared(poShared), m_gid(gid), m_varid(varid)
4484 : {
4485 2814 : CPLMutexHolderD(&hNCMutex);
4486 1407 : size_t nLen = 0;
4487 1407 : NCDF_ERR(nc_inq_atttype(m_gid, m_varid, GetName().c_str(), &m_nAttType));
4488 1407 : NCDF_ERR(nc_inq_attlen(m_gid, m_varid, GetName().c_str(), &nLen));
4489 1407 : if (m_nAttType == NC_CHAR)
4490 : {
4491 1129 : m_nTextLength = nLen;
4492 : }
4493 278 : else if (nLen > 1)
4494 : {
4495 186 : m_dims.emplace_back(std::make_shared<GDALDimension>(
4496 279 : std::string(), "length", std::string(), std::string(), nLen));
4497 : }
4498 1407 : }
4499 :
4500 : /************************************************************************/
4501 : /* netCDFAttribute() */
4502 : /************************************************************************/
4503 :
4504 265 : netCDFAttribute::netCDFAttribute(
4505 : const std::shared_ptr<netCDFSharedResources> &poShared, int gid, int varid,
4506 : const std::string &osName, const std::vector<GUInt64> &anDimensions,
4507 265 : const GDALExtendedDataType &oDataType, CSLConstList papszOptions)
4508 265 : : GDALAbstractMDArray(retrieveAttributeParentName(gid, varid), osName),
4509 265 : GDALAttribute(retrieveAttributeParentName(gid, varid), osName),
4510 795 : m_poShared(poShared), m_gid(gid), m_varid(varid)
4511 : {
4512 530 : CPLMutexHolderD(&hNCMutex);
4513 265 : m_bPerfectDataTypeMatch = true;
4514 265 : m_nAttType = CreateOrGetType(gid, oDataType);
4515 265 : m_dt.reset(new GDALExtendedDataType(oDataType));
4516 265 : if (!anDimensions.empty())
4517 : {
4518 72 : m_dims.emplace_back(std::make_shared<GDALDimension>(
4519 72 : std::string(), "length", std::string(), std::string(),
4520 72 : anDimensions[0]));
4521 : }
4522 :
4523 265 : const char *pszType = CSLFetchNameValueDef(papszOptions, "NC_TYPE", "");
4524 475 : if (oDataType.GetClass() == GEDTC_STRING && anDimensions.empty() &&
4525 210 : (EQUAL(pszType, "") || EQUAL(pszType, "NC_CHAR")))
4526 : {
4527 209 : m_nAttType = NC_CHAR;
4528 : }
4529 66 : else if (oDataType.GetNumericDataType() == GDT_UInt8 &&
4530 10 : EQUAL(CSLFetchNameValueDef(papszOptions, "NC_TYPE", ""),
4531 : "NC_BYTE"))
4532 : {
4533 1 : m_nAttType = NC_BYTE;
4534 : }
4535 57 : else if (oDataType.GetNumericDataType() == GDT_Int16 &&
4536 2 : EQUAL(CSLFetchNameValueDef(papszOptions, "NC_TYPE", ""),
4537 : "NC_BYTE"))
4538 : {
4539 2 : m_bPerfectDataTypeMatch = false;
4540 2 : m_nAttType = NC_BYTE;
4541 : }
4542 53 : else if (oDataType.GetNumericDataType() == GDT_Float64)
4543 : {
4544 9 : if (EQUAL(pszType, "NC_INT64"))
4545 : {
4546 2 : m_bPerfectDataTypeMatch = false;
4547 2 : m_nAttType = NC_INT64;
4548 : }
4549 7 : else if (EQUAL(pszType, "NC_UINT64"))
4550 : {
4551 1 : m_bPerfectDataTypeMatch = false;
4552 1 : m_nAttType = NC_UINT64;
4553 : }
4554 : }
4555 265 : }
4556 :
4557 : /************************************************************************/
4558 : /* ~netCDFAttribute() */
4559 : /************************************************************************/
4560 :
4561 3344 : netCDFAttribute::~netCDFAttribute()
4562 : {
4563 1672 : if (m_bValid)
4564 : {
4565 2651 : if (auto poParent = m_poParent.lock())
4566 981 : poParent->UnRegisterAttribute(this);
4567 : }
4568 3344 : }
4569 :
4570 : /************************************************************************/
4571 : /* Create() */
4572 : /************************************************************************/
4573 :
4574 : std::shared_ptr<netCDFAttribute>
4575 1407 : netCDFAttribute::Create(const std::shared_ptr<netCDFSharedResources> &poShared,
4576 : const std::shared_ptr<netCDFAttributeHolder> &poParent,
4577 : int gid, int varid, const std::string &name)
4578 : {
4579 : auto attr(std::shared_ptr<netCDFAttribute>(
4580 1407 : new netCDFAttribute(poShared, gid, varid, name)));
4581 1407 : attr->SetSelf(attr);
4582 1407 : attr->m_poParent = poParent;
4583 1407 : if (poParent)
4584 837 : poParent->RegisterAttribute(attr.get());
4585 1407 : return attr;
4586 : }
4587 :
4588 268 : std::shared_ptr<netCDFAttribute> netCDFAttribute::Create(
4589 : const std::shared_ptr<netCDFSharedResources> &poShared,
4590 : const std::shared_ptr<netCDFAttributeHolder> &poParent, int gid, int varid,
4591 : const std::string &osName, const std::vector<GUInt64> &anDimensions,
4592 : const GDALExtendedDataType &oDataType, CSLConstList papszOptions)
4593 : {
4594 268 : if (poShared->IsReadOnly())
4595 : {
4596 2 : CPLError(CE_Failure, CPLE_AppDefined,
4597 : "CreateAttribute() not supported on read-only file");
4598 2 : return nullptr;
4599 : }
4600 266 : if (anDimensions.size() > 1)
4601 : {
4602 1 : CPLError(CE_Failure, CPLE_NotSupported,
4603 : "Only 0 or 1-dimensional attribute are supported");
4604 1 : return nullptr;
4605 : }
4606 :
4607 265 : const char *apszOptions[2] = {nullptr, nullptr};
4608 280 : if (!poShared->IsNC4() && oDataType.GetClass() == GEDTC_NUMERIC &&
4609 280 : oDataType.GetNumericDataType() == GDT_UInt8 && !papszOptions)
4610 : {
4611 : // GDT_UInt8 would map to a NC_UBYTE datatype, which is not available in
4612 : // NC3 datasets
4613 1 : apszOptions[0] = "NC_TYPE=NC_BYTE";
4614 1 : papszOptions = apszOptions;
4615 : }
4616 :
4617 : auto attr(std::shared_ptr<netCDFAttribute>(new netCDFAttribute(
4618 530 : poShared, gid, varid, osName, anDimensions, oDataType, papszOptions)));
4619 265 : if (attr->m_nAttType == NC_NAT)
4620 0 : return nullptr;
4621 265 : attr->SetSelf(attr);
4622 265 : attr->m_poParent = poParent;
4623 265 : if (poParent)
4624 265 : poParent->RegisterAttribute(attr.get());
4625 265 : return attr;
4626 : }
4627 :
4628 : /************************************************************************/
4629 : /* GetDataType() */
4630 : /************************************************************************/
4631 :
4632 5376 : const GDALExtendedDataType &netCDFAttribute::GetDataType() const
4633 : {
4634 5376 : if (m_dt)
4635 4163 : return *m_dt;
4636 2426 : CPLMutexHolderD(&hNCMutex);
4637 :
4638 1213 : if (m_nAttType == NC_CHAR)
4639 : {
4640 1031 : m_dt.reset(
4641 1031 : new GDALExtendedDataType(GDALExtendedDataType::CreateString()));
4642 : }
4643 : else
4644 : {
4645 182 : m_dt.reset(new GDALExtendedDataType(
4646 182 : GDALExtendedDataType::Create(GDT_Unknown)));
4647 182 : BuildDataType(m_gid, m_varid, m_nAttType, m_dt,
4648 182 : m_bPerfectDataTypeMatch);
4649 : }
4650 :
4651 1213 : return *m_dt;
4652 : }
4653 :
4654 : /************************************************************************/
4655 : /* IRead() */
4656 : /************************************************************************/
4657 :
4658 1231 : bool netCDFAttribute::IRead(const GUInt64 *arrayStartIdx, const size_t *count,
4659 : const GInt64 *arrayStep,
4660 : const GPtrDiff_t *bufferStride,
4661 : const GDALExtendedDataType &bufferDataType,
4662 : void *pDstBuffer) const
4663 : {
4664 1231 : if (!CheckValidAndErrorOutIfNot())
4665 0 : return false;
4666 2462 : CPLMutexHolderD(&hNCMutex);
4667 :
4668 1231 : if (m_nAttType == NC_STRING)
4669 : {
4670 55 : CPLAssert(GetDataType().GetClass() == GEDTC_STRING);
4671 : std::vector<char *> apszStrings(
4672 110 : static_cast<size_t>(GetTotalElementsCount()));
4673 55 : int ret = nc_get_att_string(m_gid, m_varid, GetName().c_str(),
4674 55 : &apszStrings[0]);
4675 55 : NCDF_ERR(ret);
4676 55 : if (ret != NC_NOERR)
4677 0 : return false;
4678 55 : if (m_dims.empty())
4679 : {
4680 50 : const char *pszStr = apszStrings[0];
4681 50 : GDALExtendedDataType::CopyValue(&pszStr, GetDataType(), pDstBuffer,
4682 : bufferDataType);
4683 : }
4684 : else
4685 : {
4686 5 : GByte *pabyDstBuffer = static_cast<GByte *>(pDstBuffer);
4687 14 : for (size_t i = 0; i < count[0]; i++)
4688 : {
4689 9 : auto srcIdx =
4690 9 : static_cast<size_t>(arrayStartIdx[0] + arrayStep[0] * i);
4691 9 : const char *pszStr = apszStrings[srcIdx];
4692 9 : GDALExtendedDataType::CopyValue(&pszStr, GetDataType(),
4693 : pabyDstBuffer, bufferDataType);
4694 9 : pabyDstBuffer += sizeof(char *) * bufferStride[0];
4695 : }
4696 : }
4697 55 : nc_free_string(apszStrings.size(), &apszStrings[0]);
4698 55 : return true;
4699 : }
4700 :
4701 1176 : if (m_nAttType == NC_CHAR)
4702 : {
4703 1037 : CPLAssert(GetDataType().GetClass() == GEDTC_STRING);
4704 1037 : CPLAssert(m_dims.empty());
4705 1037 : if (bufferDataType != GetDataType())
4706 : {
4707 0 : std::string osStr;
4708 0 : osStr.resize(m_nTextLength);
4709 : int ret =
4710 0 : nc_get_att_text(m_gid, m_varid, GetName().c_str(), &osStr[0]);
4711 0 : NCDF_ERR(ret);
4712 0 : if (ret != NC_NOERR)
4713 0 : return false;
4714 0 : const char *pszStr = osStr.c_str();
4715 0 : GDALExtendedDataType::CopyValue(&pszStr, GetDataType(), pDstBuffer,
4716 : bufferDataType);
4717 : }
4718 : else
4719 : {
4720 1037 : char *pszStr = static_cast<char *>(CPLCalloc(1, m_nTextLength + 1));
4721 : int ret =
4722 1037 : nc_get_att_text(m_gid, m_varid, GetName().c_str(), pszStr);
4723 1037 : NCDF_ERR(ret);
4724 1037 : if (ret != NC_NOERR)
4725 : {
4726 1 : CPLFree(pszStr);
4727 1 : return false;
4728 : }
4729 1036 : *static_cast<char **>(pDstBuffer) = pszStr;
4730 : }
4731 1036 : return true;
4732 : }
4733 :
4734 139 : const auto &dt(GetDataType());
4735 274 : if (dt.GetClass() == GEDTC_NUMERIC &&
4736 135 : dt.GetNumericDataType() == GDT_Unknown)
4737 : {
4738 0 : return false;
4739 : }
4740 :
4741 139 : CPLAssert(dt.GetClass() != GEDTC_STRING);
4742 259 : const bool bFastPath = ((m_dims.size() == 1 && arrayStartIdx[0] == 0 &&
4743 60 : count[0] == m_dims[0]->GetSize() &&
4744 139 : arrayStep[0] == 1 && bufferStride[0] == 1) ||
4745 80 : m_dims.empty()) &&
4746 351 : m_bPerfectDataTypeMatch && bufferDataType == dt &&
4747 73 : dt.GetSize() > 0;
4748 139 : if (bFastPath)
4749 : {
4750 73 : int ret = nc_get_att(m_gid, m_varid, GetName().c_str(), pDstBuffer);
4751 73 : NCDF_ERR(ret);
4752 73 : if (ret == NC_NOERR)
4753 : {
4754 73 : ConvertNCStringsToCPLStrings(static_cast<GByte *>(pDstBuffer), dt);
4755 : }
4756 73 : return ret == NC_NOERR;
4757 : }
4758 :
4759 : const auto nElementSize =
4760 66 : GetNCTypeSize(dt, m_bPerfectDataTypeMatch, m_nAttType);
4761 66 : if (nElementSize == 0)
4762 0 : return false;
4763 66 : const auto nOutputDTSize = bufferDataType.GetSize();
4764 66 : std::vector<GByte> abyBuffer(static_cast<size_t>(GetTotalElementsCount()) *
4765 198 : nElementSize);
4766 66 : int ret = nc_get_att(m_gid, m_varid, GetName().c_str(), &abyBuffer[0]);
4767 66 : NCDF_ERR(ret);
4768 66 : if (ret != NC_NOERR)
4769 0 : return false;
4770 :
4771 : GByte *pabySrcBuffer =
4772 66 : m_dims.empty()
4773 66 : ? abyBuffer.data()
4774 41 : : abyBuffer.data() +
4775 41 : static_cast<size_t>(arrayStartIdx[0]) * nElementSize;
4776 66 : GByte *pabyDstBuffer = static_cast<GByte *>(pDstBuffer);
4777 216 : for (size_t i = 0; i < (m_dims.empty() ? 1 : count[0]); i++)
4778 : {
4779 : GByte abyTmpBuffer[sizeof(double)];
4780 150 : const GByte *pabySrcElement = pabySrcBuffer;
4781 150 : if (!m_bPerfectDataTypeMatch)
4782 : {
4783 7 : if (m_nAttType == NC_BYTE)
4784 : {
4785 3 : short s =
4786 3 : reinterpret_cast<const signed char *>(pabySrcBuffer)[0];
4787 3 : memcpy(abyTmpBuffer, &s, sizeof(s));
4788 3 : pabySrcElement = abyTmpBuffer;
4789 : }
4790 4 : else if (m_nAttType == NC_INT64)
4791 : {
4792 3 : double v = static_cast<double>(
4793 3 : reinterpret_cast<const GInt64 *>(pabySrcBuffer)[0]);
4794 3 : memcpy(abyTmpBuffer, &v, sizeof(v));
4795 3 : pabySrcElement = abyTmpBuffer;
4796 : }
4797 1 : else if (m_nAttType == NC_UINT64)
4798 : {
4799 1 : double v = static_cast<double>(
4800 1 : reinterpret_cast<const GUInt64 *>(pabySrcBuffer)[0]);
4801 1 : memcpy(abyTmpBuffer, &v, sizeof(v));
4802 1 : pabySrcElement = abyTmpBuffer;
4803 : }
4804 : else
4805 : {
4806 0 : CPLAssert(false);
4807 : }
4808 : }
4809 150 : GDALExtendedDataType::CopyValue(pabySrcElement, dt, pabyDstBuffer,
4810 : bufferDataType);
4811 150 : FreeNCStrings(pabySrcBuffer, dt);
4812 150 : if (!m_dims.empty())
4813 : {
4814 125 : pabySrcBuffer +=
4815 125 : static_cast<std::ptrdiff_t>(arrayStep[0] * nElementSize);
4816 125 : pabyDstBuffer += nOutputDTSize * bufferStride[0];
4817 : }
4818 : }
4819 :
4820 66 : return true;
4821 : }
4822 :
4823 : /************************************************************************/
4824 : /* IWrite() */
4825 : /************************************************************************/
4826 :
4827 270 : bool netCDFAttribute::IWrite(const GUInt64 *arrayStartIdx, const size_t *count,
4828 : const GInt64 *arrayStep,
4829 : const GPtrDiff_t *bufferStride,
4830 : const GDALExtendedDataType &bufferDataType,
4831 : const void *pSrcBuffer)
4832 : {
4833 270 : if (!CheckValidAndErrorOutIfNot())
4834 0 : return false;
4835 540 : CPLMutexHolderD(&hNCMutex);
4836 :
4837 307 : if (m_dims.size() == 1 &&
4838 37 : (arrayStartIdx[0] != 0 || count[0] != m_dims[0]->GetSize() ||
4839 37 : arrayStep[0] != 1))
4840 : {
4841 0 : CPLError(CE_Failure, CPLE_NotSupported,
4842 : "Only contiguous writing of attribute values supported");
4843 0 : return false;
4844 : }
4845 :
4846 270 : m_poShared->SetDefineMode(true);
4847 :
4848 270 : const auto &dt(GetDataType());
4849 270 : if (m_nAttType == NC_STRING)
4850 : {
4851 30 : CPLAssert(dt.GetClass() == GEDTC_STRING);
4852 30 : if (m_dims.empty())
4853 : {
4854 2 : char *pszStr = nullptr;
4855 : const char *pszStrConst;
4856 2 : if (bufferDataType != dt)
4857 : {
4858 1 : GDALExtendedDataType::CopyValue(pSrcBuffer, bufferDataType,
4859 : &pszStr, dt);
4860 1 : pszStrConst = pszStr;
4861 : }
4862 : else
4863 : {
4864 1 : memcpy(&pszStrConst, pSrcBuffer, sizeof(const char *));
4865 : }
4866 2 : int ret = nc_put_att_string(m_gid, m_varid, GetName().c_str(), 1,
4867 : &pszStrConst);
4868 2 : CPLFree(pszStr);
4869 2 : NCDF_ERR(ret);
4870 2 : if (ret != NC_NOERR)
4871 0 : return false;
4872 2 : return true;
4873 : }
4874 :
4875 : int ret;
4876 28 : if (bufferDataType != dt)
4877 : {
4878 2 : std::vector<char *> apszStrings(count[0]);
4879 1 : const auto nInputDTSize = bufferDataType.GetSize();
4880 1 : const GByte *pabySrcBuffer = static_cast<const GByte *>(pSrcBuffer);
4881 3 : for (size_t i = 0; i < count[0]; i++)
4882 : {
4883 2 : GDALExtendedDataType::CopyValue(pabySrcBuffer, bufferDataType,
4884 2 : &apszStrings[i], dt);
4885 2 : pabySrcBuffer += nInputDTSize * bufferStride[0];
4886 : }
4887 1 : ret = nc_put_att_string(m_gid, m_varid, GetName().c_str(), count[0],
4888 1 : const_cast<const char **>(&apszStrings[0]));
4889 3 : for (size_t i = 0; i < count[0]; i++)
4890 : {
4891 2 : CPLFree(apszStrings[i]);
4892 : }
4893 : }
4894 : else
4895 : {
4896 : const char **ppszStr;
4897 27 : memcpy(&ppszStr, &pSrcBuffer, sizeof(const char **));
4898 27 : ret = nc_put_att_string(m_gid, m_varid, GetName().c_str(), count[0],
4899 : ppszStr);
4900 : }
4901 28 : NCDF_ERR(ret);
4902 28 : if (ret != NC_NOERR)
4903 0 : return false;
4904 28 : return true;
4905 : }
4906 :
4907 240 : if (m_nAttType == NC_CHAR)
4908 : {
4909 215 : CPLAssert(dt.GetClass() == GEDTC_STRING);
4910 215 : CPLAssert(m_dims.empty());
4911 215 : char *pszStr = nullptr;
4912 : const char *pszStrConst;
4913 215 : if (bufferDataType != dt)
4914 : {
4915 1 : GDALExtendedDataType::CopyValue(pSrcBuffer, bufferDataType, &pszStr,
4916 : dt);
4917 1 : pszStrConst = pszStr;
4918 : }
4919 : else
4920 : {
4921 214 : memcpy(&pszStrConst, pSrcBuffer, sizeof(const char *));
4922 : }
4923 215 : m_nTextLength = pszStrConst ? strlen(pszStrConst) : 0;
4924 215 : int ret = nc_put_att_text(m_gid, m_varid, GetName().c_str(),
4925 : m_nTextLength, pszStrConst);
4926 215 : CPLFree(pszStr);
4927 215 : NCDF_ERR(ret);
4928 215 : if (ret != NC_NOERR)
4929 1 : return false;
4930 214 : return true;
4931 : }
4932 :
4933 49 : if (dt.GetClass() == GEDTC_NUMERIC &&
4934 24 : dt.GetNumericDataType() == GDT_Unknown)
4935 : {
4936 0 : return false;
4937 : }
4938 :
4939 25 : CPLAssert(dt.GetClass() != GEDTC_STRING);
4940 : const bool bFastPath =
4941 50 : ((m_dims.size() == 1 && bufferStride[0] == 1) || m_dims.empty()) &&
4942 50 : m_bPerfectDataTypeMatch && bufferDataType == dt && dt.GetSize() > 0;
4943 25 : if (bFastPath)
4944 : {
4945 11 : int ret = nc_put_att(m_gid, m_varid, GetName().c_str(), m_nAttType,
4946 11 : m_dims.empty() ? 1 : count[0], pSrcBuffer);
4947 11 : NCDF_ERR(ret);
4948 11 : return ret == NC_NOERR;
4949 : }
4950 :
4951 : const auto nElementSize =
4952 14 : GetNCTypeSize(dt, m_bPerfectDataTypeMatch, m_nAttType);
4953 14 : if (nElementSize == 0)
4954 0 : return false;
4955 14 : const auto nInputDTSize = bufferDataType.GetSize();
4956 14 : std::vector<GByte> abyBuffer(static_cast<size_t>(GetTotalElementsCount()) *
4957 28 : nElementSize);
4958 :
4959 14 : const GByte *pabySrcBuffer = static_cast<const GByte *>(pSrcBuffer);
4960 14 : auto pabyDstBuffer = &abyBuffer[0];
4961 31 : for (size_t i = 0; i < (m_dims.empty() ? 1 : count[0]); i++)
4962 : {
4963 17 : if (!m_bPerfectDataTypeMatch)
4964 : {
4965 7 : if (m_nAttType == NC_BYTE)
4966 : {
4967 : short s;
4968 3 : GDALExtendedDataType::CopyValue(pabySrcBuffer, bufferDataType,
4969 : &s, dt);
4970 3 : signed char c = static_cast<signed char>(s);
4971 3 : memcpy(pabyDstBuffer, &c, sizeof(c));
4972 : }
4973 4 : else if (m_nAttType == NC_INT64)
4974 : {
4975 : double d;
4976 3 : GDALExtendedDataType::CopyValue(pabySrcBuffer, bufferDataType,
4977 : &d, dt);
4978 3 : GInt64 v = static_cast<GInt64>(d);
4979 3 : memcpy(pabyDstBuffer, &v, sizeof(v));
4980 : }
4981 1 : else if (m_nAttType == NC_UINT64)
4982 : {
4983 : double d;
4984 1 : GDALExtendedDataType::CopyValue(pabySrcBuffer, bufferDataType,
4985 : &d, dt);
4986 1 : GUInt64 v = static_cast<GUInt64>(d);
4987 1 : memcpy(pabyDstBuffer, &v, sizeof(v));
4988 : }
4989 : else
4990 : {
4991 0 : CPLAssert(false);
4992 : }
4993 : }
4994 : else
4995 : {
4996 10 : GDALExtendedDataType::CopyValue(pabySrcBuffer, bufferDataType,
4997 : pabyDstBuffer, dt);
4998 : }
4999 :
5000 17 : if (!m_dims.empty())
5001 : {
5002 7 : pabySrcBuffer += nInputDTSize * bufferStride[0];
5003 7 : pabyDstBuffer += nElementSize;
5004 : }
5005 : }
5006 :
5007 14 : int ret = nc_put_att(m_gid, m_varid, GetName().c_str(), m_nAttType,
5008 14 : m_dims.empty() ? 1 : count[0], &abyBuffer[0]);
5009 14 : NCDF_ERR(ret);
5010 14 : return ret == NC_NOERR;
5011 : }
5012 :
5013 : /************************************************************************/
5014 : /* Rename() */
5015 : /************************************************************************/
5016 :
5017 8 : bool netCDFAttribute::Rename(const std::string &osNewName)
5018 : {
5019 8 : if (!CheckValidAndErrorOutIfNot())
5020 2 : return false;
5021 6 : if (m_poShared->IsReadOnly())
5022 : {
5023 1 : CPLError(CE_Failure, CPLE_AppDefined,
5024 : "Rename() not supported on read-only file");
5025 1 : return false;
5026 : }
5027 5 : if (osNewName.empty())
5028 : {
5029 1 : CPLError(CE_Failure, CPLE_NotSupported, "Empty name not supported");
5030 1 : return false;
5031 : }
5032 8 : CPLMutexHolderD(&hNCMutex);
5033 4 : m_poShared->SetDefineMode(true);
5034 :
5035 : int ret =
5036 4 : nc_rename_att(m_gid, m_varid, m_osName.c_str(), osNewName.c_str());
5037 4 : NCDF_ERR(ret);
5038 4 : if (ret != NC_NOERR)
5039 2 : return false;
5040 :
5041 2 : BaseRename(osNewName);
5042 :
5043 2 : return true;
5044 : }
5045 :
5046 : /************************************************************************/
5047 : /* OpenMultiDim() */
5048 : /************************************************************************/
5049 :
5050 262 : GDALDataset *netCDFDataset::OpenMultiDim(GDALOpenInfo *poOpenInfo)
5051 : {
5052 :
5053 524 : CPLMutexHolderD(&hNCMutex);
5054 :
5055 262 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
5056 : // GDALDataset own mutex.
5057 262 : netCDFDataset *poDS = new netCDFDataset();
5058 262 : CPLAcquireMutex(hNCMutex, 1000.0);
5059 :
5060 524 : std::string osFilename;
5061 :
5062 : // For example to open DAP datasets
5063 262 : if (STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
5064 : {
5065 0 : osFilename = poOpenInfo->pszFilename + strlen("NETCDF:");
5066 0 : if (!osFilename.empty() && osFilename[0] == '"' &&
5067 0 : osFilename.back() == '"')
5068 : {
5069 0 : osFilename = osFilename.substr(1, osFilename.size() - 2);
5070 : }
5071 : }
5072 : else
5073 : {
5074 262 : osFilename = poOpenInfo->pszFilename;
5075 262 : poDS->eFormat =
5076 262 : netCDFIdentifyFormat(poOpenInfo, /* bCheckExt = */ true);
5077 : }
5078 :
5079 262 : poDS->SetDescription(poOpenInfo->pszFilename);
5080 262 : poDS->papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
5081 :
5082 : #ifdef ENABLE_NCDUMP
5083 262 : bool bFileToDestroyAtClosing = false;
5084 262 : const char *pszHeader =
5085 : reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
5086 262 : if (poOpenInfo->fpL != nullptr && STARTS_WITH(pszHeader, "netcdf ") &&
5087 1 : strstr(pszHeader, "dimensions:") && strstr(pszHeader, "variables:"))
5088 : {
5089 : // By default create a temporary file that will be destroyed,
5090 : // unless NETCDF_TMP_FILE is defined. Can be useful to see which
5091 : // netCDF file has been generated from a potential fuzzed input.
5092 1 : osFilename = CPLGetConfigOption("NETCDF_TMP_FILE", "");
5093 1 : if (osFilename.empty())
5094 : {
5095 1 : bFileToDestroyAtClosing = true;
5096 1 : osFilename = CPLGenerateTempFilenameSafe("netcdf_tmp");
5097 : }
5098 1 : if (!netCDFDatasetCreateTempFile(NCDF_FORMAT_NC4, osFilename.c_str(),
5099 : poOpenInfo->fpL))
5100 : {
5101 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
5102 : // deadlock with GDALDataset own mutex.
5103 0 : delete poDS;
5104 0 : CPLAcquireMutex(hNCMutex, 1000.0);
5105 0 : return nullptr;
5106 : }
5107 1 : poDS->eFormat = NCDF_FORMAT_NC4;
5108 : }
5109 : #endif
5110 :
5111 : // Try opening the dataset.
5112 : #if defined(NCDF_DEBUG) && defined(ENABLE_UFFD)
5113 : CPLDebug("GDAL_netCDF", "calling nc_open_mem(%s)", osFilename.c_str());
5114 : #elif defined(NCDF_DEBUG) && !defined(ENABLE_UFFD)
5115 : CPLDebug("GDAL_netCDF", "calling nc_open(%s)", osFilename.c_str());
5116 : #endif
5117 262 : int cdfid = -1;
5118 262 : const int nMode =
5119 262 : (poOpenInfo->nOpenFlags & GDAL_OF_UPDATE) != 0 ? NC_WRITE : NC_NOWRITE;
5120 524 : CPLString osFilenameForNCOpen(osFilename);
5121 : #ifdef _WIN32
5122 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
5123 : {
5124 : char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
5125 : osFilenameForNCOpen = pszTemp;
5126 : CPLFree(pszTemp);
5127 : }
5128 : #endif
5129 262 : int status2 = -1;
5130 :
5131 524 : auto poSharedResources(std::make_shared<netCDFSharedResources>(osFilename));
5132 : #ifdef ENABLE_NCDUMP
5133 262 : poSharedResources->m_bFileToDestroyAtClosing = bFileToDestroyAtClosing;
5134 : #endif
5135 :
5136 266 : if (STARTS_WITH(osFilenameForNCOpen, "/vsimem/") &&
5137 4 : poOpenInfo->eAccess == GA_ReadOnly)
5138 : {
5139 4 : vsi_l_offset nLength = 0;
5140 4 : poDS->fpVSIMEM = VSIFOpenL(osFilenameForNCOpen, "rb");
5141 4 : if (poDS->fpVSIMEM)
5142 : {
5143 : // We assume that the file will not be modified. If it is, then
5144 : // pabyBuffer might become invalid.
5145 : GByte *pabyBuffer =
5146 4 : VSIGetMemFileBuffer(osFilenameForNCOpen, &nLength, false);
5147 4 : if (pabyBuffer)
5148 : {
5149 4 : status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen),
5150 : nMode, static_cast<size_t>(nLength),
5151 : pabyBuffer, &cdfid);
5152 : }
5153 : }
5154 : }
5155 : else
5156 : {
5157 : #ifdef ENABLE_UFFD
5158 258 : bool bVsiFile = !strncmp(osFilenameForNCOpen, "/vsi", strlen("/vsi"));
5159 258 : bool bReadOnly = (poOpenInfo->eAccess == GA_ReadOnly);
5160 258 : void *pVma = nullptr;
5161 258 : uint64_t nVmaSize = 0;
5162 258 : cpl_uffd_context *pCtx = nullptr;
5163 :
5164 258 : if (bVsiFile && bReadOnly && CPLIsUserFaultMappingSupported())
5165 1 : pCtx = CPLCreateUserFaultMapping(osFilenameForNCOpen, &pVma,
5166 : &nVmaSize);
5167 258 : if (pCtx != nullptr && pVma != nullptr && nVmaSize > 0)
5168 : {
5169 : // netCDF code, at least for netCDF 4.7.0, is confused by filenames
5170 : // like /vsicurl/http[s]://example.com/foo.nc, so just pass the
5171 : // final part
5172 1 : status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen), nMode,
5173 : static_cast<size_t>(nVmaSize), pVma, &cdfid);
5174 : }
5175 : else
5176 257 : status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
5177 258 : poSharedResources->m_pUffdCtx = pCtx;
5178 : #else
5179 : status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
5180 : #endif
5181 : }
5182 262 : if (status2 != NC_NOERR)
5183 : {
5184 : #ifdef NCDF_DEBUG
5185 : CPLDebug("GDAL_netCDF", "error opening");
5186 : #endif
5187 3 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
5188 : // with GDALDataset own mutex.
5189 3 : delete poDS;
5190 3 : CPLAcquireMutex(hNCMutex, 1000.0);
5191 3 : return nullptr;
5192 : }
5193 : #ifdef NCDF_DEBUG
5194 : CPLDebug("GDAL_netCDF", "got cdfid=%d", cdfid);
5195 : #endif
5196 :
5197 : #if defined(ENABLE_NCDUMP) && !defined(_WIN32)
5198 : // Try to destroy the temporary file right now on Unix
5199 259 : if (poSharedResources->m_bFileToDestroyAtClosing)
5200 : {
5201 1 : if (VSIUnlink(poSharedResources->m_osFilename) == 0)
5202 : {
5203 1 : poSharedResources->m_bFileToDestroyAtClosing = false;
5204 : }
5205 : }
5206 : #endif
5207 259 : poSharedResources->m_bReadOnly = nMode == NC_NOWRITE;
5208 259 : poSharedResources->m_bIsNC4 =
5209 259 : poDS->eFormat == NCDF_FORMAT_NC4 || poDS->eFormat == NCDF_FORMAT_NC4C;
5210 259 : poSharedResources->m_cdfid = cdfid;
5211 259 : poSharedResources->m_fpVSIMEM = poDS->fpVSIMEM;
5212 259 : poDS->fpVSIMEM = nullptr;
5213 :
5214 : // Is this a real netCDF file?
5215 : int ndims;
5216 : int ngatts;
5217 : int nvars;
5218 : int unlimdimid;
5219 259 : int status = nc_inq(cdfid, &ndims, &nvars, &ngatts, &unlimdimid);
5220 259 : if (status != NC_NOERR)
5221 : {
5222 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
5223 : // with GDALDataset own mutex.
5224 0 : delete poDS;
5225 0 : CPLAcquireMutex(hNCMutex, 1000.0);
5226 0 : return nullptr;
5227 : }
5228 :
5229 259 : poDS->m_poRootGroup = netCDFGroup::Create(poSharedResources, cdfid);
5230 :
5231 259 : poDS->TryLoadXML();
5232 :
5233 259 : return poDS;
5234 : }
5235 :
5236 : /************************************************************************/
5237 : /* GetRootGroup() */
5238 : /************************************************************************/
5239 :
5240 479 : std::shared_ptr<GDALGroup> netCDFDataset::GetRootGroup() const
5241 : {
5242 479 : return m_poRootGroup;
5243 : }
5244 :
5245 : /************************************************************************/
5246 : /* CreateMultiDimensional() */
5247 : /************************************************************************/
5248 :
5249 : GDALDataset *
5250 107 : netCDFDataset::CreateMultiDimensional(const char *pszFilename,
5251 : CSLConstList /* papszRootGroupOptions */,
5252 : CSLConstList papszOptions)
5253 : {
5254 214 : CPLMutexHolderD(&hNCMutex);
5255 :
5256 107 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
5257 : // GDALDataset own mutex.
5258 107 : netCDFDataset *poDS = new netCDFDataset();
5259 107 : CPLAcquireMutex(hNCMutex, 1000.0);
5260 107 : poDS->eAccess = GA_Update;
5261 107 : poDS->osFilename = pszFilename;
5262 :
5263 : // process options.
5264 107 : poDS->papszCreationOptions = CSLDuplicate(papszOptions);
5265 107 : if (CSLFetchNameValue(papszOptions, "FORMAT") == nullptr)
5266 : {
5267 106 : poDS->papszCreationOptions =
5268 106 : CSLSetNameValue(poDS->papszCreationOptions, "FORMAT", "NC4");
5269 : }
5270 107 : poDS->ProcessCreationOptions();
5271 :
5272 : // Create the dataset.
5273 214 : CPLString osFilenameForNCCreate(pszFilename);
5274 : #ifdef _WIN32
5275 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
5276 : {
5277 : char *pszTemp =
5278 : CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
5279 : osFilenameForNCCreate = pszTemp;
5280 : CPLFree(pszTemp);
5281 : }
5282 : #endif
5283 107 : int cdfid = 0;
5284 107 : int status = nc_create(osFilenameForNCCreate, poDS->nCreateMode, &cdfid);
5285 107 : if (status != NC_NOERR)
5286 : {
5287 2 : CPLError(CE_Failure, CPLE_OpenFailed,
5288 : "Unable to create netCDF file %s (Error code %d): %s .",
5289 : pszFilename, status, nc_strerror(status));
5290 2 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
5291 : // with GDALDataset own mutex.
5292 2 : delete poDS;
5293 2 : CPLAcquireMutex(hNCMutex, 1000.0);
5294 2 : return nullptr;
5295 : }
5296 :
5297 : auto poSharedResources(
5298 105 : std::make_shared<netCDFSharedResources>(pszFilename));
5299 105 : poSharedResources->m_cdfid = cdfid;
5300 105 : poSharedResources->m_bReadOnly = false;
5301 105 : poSharedResources->m_bDefineMode = true;
5302 105 : poSharedResources->m_bIsNC4 =
5303 105 : poDS->eFormat == NCDF_FORMAT_NC4 || poDS->eFormat == NCDF_FORMAT_NC4C;
5304 : poDS->m_poRootGroup =
5305 105 : netCDFGroup::Create(poSharedResources, nullptr, cdfid);
5306 105 : const char *pszConventions = CSLFetchNameValueDef(
5307 : papszOptions, "CONVENTIONS", NCDF_CONVENTIONS_CF_V1_6);
5308 105 : if (!EQUAL(pszConventions, ""))
5309 : {
5310 105 : auto poAttr = poDS->m_poRootGroup->CreateAttribute(
5311 315 : NCDF_CONVENTIONS, {}, GDALExtendedDataType::CreateString());
5312 105 : if (poAttr)
5313 105 : poAttr->Write(pszConventions);
5314 : }
5315 :
5316 105 : return poDS;
5317 : }
|