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_Byte:
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 : auto comp = std::unique_ptr<GDALEDTComponent>(new GDALEDTComponent(
2421 909 : std::string(field_name), field_offset, *subDt));
2422 303 : comps.emplace_back(std::move(comp));
2423 : }
2424 272 : dt.reset(new GDALExtendedDataType(
2425 136 : GDALExtendedDataType::Create(szName, compoundsize, std::move(comps))));
2426 :
2427 136 : return dt->GetClass() == GEDTC_COMPOUND;
2428 : }
2429 :
2430 : /************************************************************************/
2431 : /* BuildDataType() */
2432 : /************************************************************************/
2433 :
2434 1416 : static bool BuildDataType(int gid, int varid, const int nVarTypeIn,
2435 : std::unique_ptr<GDALExtendedDataType> &dt,
2436 : bool &bPerfectDataTypeMatch)
2437 : {
2438 1416 : int nVarType = nVarTypeIn;
2439 1416 : GDALDataType eDataType = GDT_Unknown;
2440 1416 : bPerfectDataTypeMatch = false;
2441 1416 : int eClass = 0;
2442 1416 : if (NCDFIsUserDefinedType(gid, nVarType))
2443 : {
2444 368 : nc_type nBaseType = NC_NAT;
2445 368 : nc_inq_user_type(gid, nVarType, nullptr, nullptr, &nBaseType, nullptr,
2446 : &eClass);
2447 368 : if (eClass == NC_COMPOUND)
2448 : {
2449 305 : eDataType = GetComplexDataType(gid, nVarType);
2450 305 : if (eDataType != GDT_Unknown)
2451 : {
2452 169 : bPerfectDataTypeMatch = true;
2453 169 : dt.reset(new GDALExtendedDataType(
2454 169 : GDALExtendedDataType::Create(eDataType)));
2455 305 : return true;
2456 : }
2457 136 : else if (GetCompoundDataType(gid, nVarType, dt,
2458 : bPerfectDataTypeMatch))
2459 : {
2460 136 : return true;
2461 : }
2462 : else
2463 : {
2464 0 : CPLError(CE_Failure, CPLE_NotSupported,
2465 : "Unsupported netCDF compound data type encountered.");
2466 0 : return false;
2467 : }
2468 : }
2469 63 : else if (eClass == NC_ENUM)
2470 : {
2471 63 : nVarType = nBaseType;
2472 : }
2473 0 : else if (eClass == NC_VLEN)
2474 : {
2475 0 : CPLError(CE_Failure, CPLE_NotSupported,
2476 : "VLen data type not supported");
2477 0 : return false;
2478 : }
2479 0 : else if (eClass == NC_OPAQUE)
2480 : {
2481 0 : CPLError(CE_Failure, CPLE_NotSupported,
2482 : "Opaque data type not supported");
2483 0 : return false;
2484 : }
2485 : else
2486 : {
2487 0 : CPLError(CE_Failure, CPLE_NotSupported,
2488 : "Unsupported netCDF data type encountered.");
2489 0 : return false;
2490 : }
2491 : }
2492 :
2493 1111 : if (nVarType == NC_STRING)
2494 : {
2495 88 : bPerfectDataTypeMatch = true;
2496 88 : dt.reset(
2497 88 : new GDALExtendedDataType(GDALExtendedDataType::CreateString()));
2498 88 : return true;
2499 : }
2500 : else
2501 : {
2502 1023 : if (nVarType == NC_BYTE)
2503 : {
2504 107 : char *pszTemp = nullptr;
2505 107 : bool bSignedData = true;
2506 212 : if (varid >= 0 &&
2507 105 : NCDFGetAttr(gid, varid, "_Unsigned", &pszTemp) == CE_None)
2508 : {
2509 95 : if (EQUAL(pszTemp, "true"))
2510 94 : bSignedData = false;
2511 1 : else if (EQUAL(pszTemp, "false"))
2512 1 : bSignedData = true;
2513 95 : CPLFree(pszTemp);
2514 : }
2515 107 : if (!bSignedData)
2516 : {
2517 94 : eDataType = GDT_Byte;
2518 94 : bPerfectDataTypeMatch = true;
2519 : }
2520 : else
2521 : {
2522 13 : eDataType = GDT_Int8;
2523 13 : bPerfectDataTypeMatch = true;
2524 : }
2525 : }
2526 916 : else if (nVarType == NC_CHAR)
2527 : {
2528 : // Not sure of this
2529 1 : bPerfectDataTypeMatch = true;
2530 1 : eDataType = GDT_Byte;
2531 : }
2532 915 : else if (nVarType == NC_SHORT)
2533 : {
2534 156 : bPerfectDataTypeMatch = true;
2535 156 : char *pszTemp = nullptr;
2536 156 : bool bSignedData = true;
2537 207 : if (varid >= 0 &&
2538 51 : NCDFGetAttr(gid, varid, "_Unsigned", &pszTemp) == CE_None)
2539 : {
2540 38 : if (EQUAL(pszTemp, "true"))
2541 38 : bSignedData = false;
2542 0 : else if (EQUAL(pszTemp, "false"))
2543 0 : bSignedData = true;
2544 38 : CPLFree(pszTemp);
2545 : }
2546 156 : if (!bSignedData)
2547 : {
2548 38 : eDataType = GDT_UInt16;
2549 : }
2550 : else
2551 : {
2552 118 : eDataType = GDT_Int16;
2553 : }
2554 : }
2555 759 : else if (nVarType == NC_INT)
2556 : {
2557 188 : bPerfectDataTypeMatch = true;
2558 188 : eDataType = GDT_Int32;
2559 : }
2560 571 : else if (nVarType == NC_FLOAT)
2561 : {
2562 78 : bPerfectDataTypeMatch = true;
2563 78 : eDataType = GDT_Float32;
2564 : }
2565 493 : else if (nVarType == NC_DOUBLE)
2566 : {
2567 248 : bPerfectDataTypeMatch = true;
2568 248 : eDataType = GDT_Float64;
2569 : }
2570 245 : else if (nVarType == NC_UBYTE)
2571 : {
2572 215 : bPerfectDataTypeMatch = true;
2573 215 : eDataType = GDT_Byte;
2574 : }
2575 30 : else if (nVarType == NC_USHORT)
2576 : {
2577 9 : bPerfectDataTypeMatch = true;
2578 9 : eDataType = GDT_UInt16;
2579 : }
2580 21 : else if (nVarType == NC_UINT)
2581 : {
2582 7 : bPerfectDataTypeMatch = true;
2583 7 : eDataType = GDT_UInt32;
2584 : }
2585 14 : else if (nVarType == NC_INT64)
2586 : {
2587 8 : bPerfectDataTypeMatch = true;
2588 8 : eDataType = GDT_Int64;
2589 : }
2590 6 : else if (nVarType == NC_UINT64)
2591 : {
2592 6 : bPerfectDataTypeMatch = true;
2593 6 : eDataType = GDT_UInt64;
2594 : }
2595 : else
2596 : {
2597 0 : CPLError(CE_Failure, CPLE_NotSupported,
2598 : "Unsupported netCDF data type encountered.");
2599 0 : return false;
2600 : }
2601 : }
2602 :
2603 1086 : if (eClass == NC_ENUM && GDALDataTypeIsInteger(eDataType) &&
2604 63 : !GDALDataTypeIsComplex(eDataType))
2605 : {
2606 63 : char szEnumName[NC_MAX_NAME + 1] = {};
2607 63 : size_t nMemberCount = 0;
2608 63 : NCDF_ERR(nc_inq_enum(gid, nVarTypeIn, szEnumName, nullptr, nullptr,
2609 : &nMemberCount));
2610 126 : auto poRAT = std::make_unique<GDALDefaultRasterAttributeTable>();
2611 63 : poRAT->CreateColumn("value", GFT_Integer, GFU_MinMax);
2612 63 : poRAT->CreateColumn("name", GFT_String, GFU_Name);
2613 63 : std::vector<GByte> abyValue(GDALGetDataTypeSizeBytes(eDataType));
2614 63 : char szName[NC_MAX_NAME + 1] = {};
2615 190 : for (int i = 0;
2616 190 : i < static_cast<int>(std::min<size_t>(nMemberCount, INT_MAX)); ++i)
2617 : {
2618 127 : szName[0] = 0;
2619 127 : NCDF_ERR(nc_inq_enum_member(gid, nVarTypeIn, i, szName,
2620 : abyValue.data()));
2621 127 : int nValue = 0;
2622 127 : GDALCopyWords(abyValue.data(), eDataType, 0, &nValue, GDT_Int32, 0,
2623 : 1);
2624 127 : poRAT->SetValue(i, 0, nValue);
2625 127 : poRAT->SetValue(i, 1, szName);
2626 : }
2627 189 : dt.reset(new GDALExtendedDataType(GDALExtendedDataType::Create(
2628 126 : szEnumName, eDataType, std::move(poRAT))));
2629 : }
2630 : else
2631 : {
2632 960 : dt.reset(
2633 960 : new GDALExtendedDataType(GDALExtendedDataType::Create(eDataType)));
2634 : }
2635 1023 : return true;
2636 : }
2637 :
2638 : /************************************************************************/
2639 : /* GetDataType() */
2640 : /************************************************************************/
2641 :
2642 3329 : const GDALExtendedDataType &netCDFVariable::GetDataType() const
2643 : {
2644 3329 : if (m_dt)
2645 2704 : return *m_dt;
2646 1250 : CPLMutexHolderD(&hNCMutex);
2647 :
2648 625 : if (m_nDims == 2 && m_nVarType == NC_CHAR && m_nTextLength > 0)
2649 : {
2650 7 : m_bPerfectDataTypeMatch = true;
2651 7 : m_dt.reset(new GDALExtendedDataType(
2652 7 : GDALExtendedDataType::CreateString(m_nTextLength)));
2653 : }
2654 : else
2655 : {
2656 618 : m_dt.reset(new GDALExtendedDataType(
2657 618 : GDALExtendedDataType::Create(GDT_Unknown)));
2658 :
2659 618 : BuildDataType(m_gid, m_varid, m_nVarType, m_dt,
2660 618 : m_bPerfectDataTypeMatch);
2661 : }
2662 625 : return *m_dt;
2663 : }
2664 :
2665 : /************************************************************************/
2666 : /* SetUnit() */
2667 : /************************************************************************/
2668 :
2669 4 : bool netCDFVariable::SetUnit(const std::string &osUnit)
2670 : {
2671 4 : if (osUnit.empty())
2672 : {
2673 0 : nc_del_att(m_gid, m_varid, CF_UNITS);
2674 0 : return true;
2675 : }
2676 12 : auto poUnits(GetAttribute(CF_UNITS));
2677 4 : if (!poUnits)
2678 : {
2679 8 : poUnits = CreateAttribute(
2680 12 : CF_UNITS, {}, GDALExtendedDataType::CreateString(), nullptr);
2681 4 : if (!poUnits)
2682 0 : return false;
2683 : }
2684 4 : return poUnits->Write(osUnit.c_str());
2685 : }
2686 :
2687 : /************************************************************************/
2688 : /* GetSpatialRef() */
2689 : /************************************************************************/
2690 :
2691 148 : std::shared_ptr<OGRSpatialReference> netCDFVariable::GetSpatialRef() const
2692 : {
2693 148 : if (m_bSRSRead)
2694 50 : return m_poSRS;
2695 :
2696 98 : m_bSRSRead = true;
2697 196 : netCDFDataset poDS;
2698 98 : poDS.ReadAttributes(m_gid, m_varid);
2699 98 : int iDimX = 0;
2700 98 : int iDimY = 0;
2701 98 : int iCount = 1;
2702 268 : for (const auto &poDim : GetDimensions())
2703 : {
2704 170 : if (poDim->GetType() == GDAL_DIM_TYPE_HORIZONTAL_X)
2705 39 : iDimX = iCount;
2706 131 : else if (poDim->GetType() == GDAL_DIM_TYPE_HORIZONTAL_Y)
2707 46 : iDimY = iCount;
2708 170 : poDS.papszDimName.AddString(poDim->GetName().c_str());
2709 170 : iCount++;
2710 : }
2711 98 : if ((iDimX == 0 || iDimY == 0) && GetDimensionCount() >= 2)
2712 : {
2713 32 : iDimX = static_cast<int>(GetDimensionCount());
2714 32 : iDimY = iDimX - 1;
2715 : }
2716 98 : poDS.SetProjectionFromVar(m_gid, m_varid, true);
2717 98 : auto poSRS = poDS.GetSpatialRef();
2718 98 : if (poSRS)
2719 : {
2720 30 : m_poSRS.reset(poSRS->Clone());
2721 30 : if (iDimX > 0 && iDimY > 0)
2722 : {
2723 30 : const auto &oMapping = m_poSRS->GetDataAxisToSRSAxisMapping();
2724 85 : if (oMapping == std::vector<int>{2, 1} ||
2725 55 : oMapping == std::vector<int>{2, 1, 3})
2726 5 : m_poSRS->SetDataAxisToSRSAxisMapping({iDimY, iDimX});
2727 : else
2728 25 : m_poSRS->SetDataAxisToSRSAxisMapping({iDimX, iDimY});
2729 : }
2730 : }
2731 :
2732 98 : return m_poSRS;
2733 : }
2734 :
2735 : /************************************************************************/
2736 : /* SetSpatialRef() */
2737 : /************************************************************************/
2738 :
2739 84 : static void WriteDimAttr(std::shared_ptr<GDALMDArray> &poVar,
2740 : const char *pszAttrName, const char *pszAttrValue)
2741 : {
2742 252 : auto poAttr = poVar->GetAttribute(pszAttrName);
2743 84 : if (poAttr)
2744 : {
2745 34 : const char *pszVal = poAttr->ReadAsString();
2746 34 : if (pszVal && !EQUAL(pszVal, pszAttrValue))
2747 : {
2748 0 : CPLError(CE_Warning, CPLE_AppDefined,
2749 : "Variable %s has a %s which is %s and not %s",
2750 0 : poVar->GetName().c_str(), pszAttrName, pszVal,
2751 : pszAttrValue);
2752 : }
2753 : }
2754 : else
2755 : {
2756 150 : poAttr = poVar->CreateAttribute(
2757 150 : pszAttrName, {}, GDALExtendedDataType::CreateString(), nullptr);
2758 50 : if (poAttr)
2759 50 : poAttr->Write(pszAttrValue);
2760 : }
2761 84 : }
2762 :
2763 32 : static void WriteDimAttrs(const std::shared_ptr<GDALDimension> &dim,
2764 : const char *pszStandardName, const char *pszLongName,
2765 : const char *pszUnits)
2766 : {
2767 64 : auto poVar = dim->GetIndexingVariable();
2768 32 : if (poVar)
2769 : {
2770 28 : WriteDimAttr(poVar, CF_STD_NAME, pszStandardName);
2771 28 : WriteDimAttr(poVar, CF_LNG_NAME, pszLongName);
2772 28 : WriteDimAttr(poVar, CF_UNITS, pszUnits);
2773 : }
2774 : else
2775 : {
2776 4 : CPLError(CE_Warning, CPLE_AppDefined,
2777 : "Dimension %s lacks a indexing variable",
2778 4 : dim->GetName().c_str());
2779 : }
2780 32 : }
2781 :
2782 20 : bool netCDFVariable::SetSpatialRef(const OGRSpatialReference *poSRS)
2783 : {
2784 20 : m_bSRSRead = false;
2785 20 : m_poSRS.reset();
2786 :
2787 40 : CPLMutexHolderD(&hNCMutex);
2788 20 : m_poShared->SetDefineMode(true);
2789 :
2790 20 : if (poSRS == nullptr)
2791 : {
2792 2 : nc_del_att(m_gid, m_varid, CF_GRD_MAPPING);
2793 2 : return true;
2794 : }
2795 :
2796 18 : char *pszCFProjection = nullptr;
2797 : int nSRSVarId =
2798 18 : NCDFWriteSRSVariable(m_gid, poSRS, &pszCFProjection, m_bWriteGDALTags);
2799 18 : if (nSRSVarId < 0 || pszCFProjection == nullptr)
2800 0 : return false;
2801 :
2802 18 : NCDF_ERR(nc_put_att_text(m_gid, m_varid, CF_GRD_MAPPING,
2803 : strlen(pszCFProjection), pszCFProjection));
2804 18 : CPLFree(pszCFProjection);
2805 :
2806 18 : auto apoDims = GetDimensions();
2807 18 : if (poSRS->IsProjected())
2808 : {
2809 13 : bool bWriteX = false;
2810 13 : bool bWriteY = false;
2811 26 : const std::string osUnits = NCDFGetProjectedCFUnit(poSRS);
2812 39 : for (const auto &poDim : apoDims)
2813 : {
2814 26 : const char *pszStandardName = nullptr;
2815 26 : const char *pszLongName = nullptr;
2816 45 : if (poDim->GetType() == GDAL_DIM_TYPE_HORIZONTAL_X ||
2817 19 : EQUAL(poDim->GetName().c_str(), CF_PROJ_X_VAR_NAME))
2818 : {
2819 10 : pszStandardName = CF_PROJ_X_COORD;
2820 10 : pszLongName = CF_PROJ_X_COORD_LONG_NAME;
2821 10 : bWriteX = true;
2822 : }
2823 25 : else if (poDim->GetType() == GDAL_DIM_TYPE_HORIZONTAL_Y ||
2824 9 : EQUAL(poDim->GetName().c_str(), CF_PROJ_Y_VAR_NAME))
2825 : {
2826 10 : pszStandardName = CF_PROJ_Y_COORD;
2827 10 : pszLongName = CF_PROJ_Y_COORD_LONG_NAME;
2828 10 : bWriteY = true;
2829 : }
2830 26 : if (pszStandardName && pszLongName)
2831 : {
2832 20 : WriteDimAttrs(poDim, pszStandardName, pszLongName,
2833 : osUnits.c_str());
2834 : }
2835 : }
2836 6 : if (!bWriteX && !bWriteY && apoDims.size() >= 2 &&
2837 6 : apoDims[apoDims.size() - 2]->GetType().empty() &&
2838 6 : apoDims[apoDims.size() - 1]->GetType().empty() &&
2839 32 : apoDims[apoDims.size() - 2]->GetIndexingVariable() &&
2840 14 : apoDims[apoDims.size() - 1]->GetIndexingVariable())
2841 : {
2842 1 : CPLError(CE_Warning, CPLE_AppDefined,
2843 : "Dimensions of variable %s have no type declared. "
2844 : "Assuming the last one is X, and the preceding one Y",
2845 1 : GetName().c_str());
2846 1 : WriteDimAttrs(apoDims[apoDims.size() - 1], CF_PROJ_X_COORD,
2847 : CF_PROJ_X_COORD_LONG_NAME, osUnits.c_str());
2848 1 : WriteDimAttrs(apoDims[apoDims.size() - 2], CF_PROJ_Y_COORD,
2849 : CF_PROJ_Y_COORD_LONG_NAME, osUnits.c_str());
2850 : }
2851 : }
2852 5 : else if (poSRS->IsGeographic())
2853 : {
2854 5 : bool bWriteX = false;
2855 5 : bool bWriteY = false;
2856 15 : for (const auto &poDim : apoDims)
2857 : {
2858 10 : const char *pszStandardName = nullptr;
2859 10 : const char *pszLongName = nullptr;
2860 10 : const char *pszUnits = "";
2861 16 : if (poDim->GetType() == GDAL_DIM_TYPE_HORIZONTAL_X ||
2862 6 : EQUAL(poDim->GetName().c_str(), CF_LONGITUDE_VAR_NAME))
2863 : {
2864 4 : pszStandardName = CF_LONGITUDE_STD_NAME;
2865 4 : pszLongName = CF_LONGITUDE_LNG_NAME;
2866 4 : pszUnits = CF_DEGREES_EAST;
2867 4 : bWriteX = true;
2868 : }
2869 8 : else if (poDim->GetType() == GDAL_DIM_TYPE_HORIZONTAL_Y ||
2870 2 : EQUAL(poDim->GetName().c_str(), CF_LATITUDE_VAR_NAME))
2871 : {
2872 4 : pszStandardName = CF_LATITUDE_STD_NAME;
2873 4 : pszLongName = CF_LATITUDE_LNG_NAME;
2874 4 : pszUnits = CF_DEGREES_NORTH;
2875 4 : bWriteY = true;
2876 : }
2877 10 : if (pszStandardName && pszLongName)
2878 : {
2879 8 : WriteDimAttrs(poDim, pszStandardName, pszLongName, pszUnits);
2880 : }
2881 : }
2882 2 : if (!bWriteX && !bWriteY && apoDims.size() >= 2 &&
2883 2 : apoDims[apoDims.size() - 2]->GetType().empty() &&
2884 2 : apoDims[apoDims.size() - 1]->GetType().empty() &&
2885 12 : apoDims[apoDims.size() - 2]->GetIndexingVariable() &&
2886 6 : apoDims[apoDims.size() - 1]->GetIndexingVariable())
2887 : {
2888 1 : CPLError(CE_Warning, CPLE_AppDefined,
2889 : "Dimensions of variable %s have no type declared. "
2890 : "Assuming the last one is longitude, "
2891 : "and the preceding one latitude",
2892 1 : GetName().c_str());
2893 1 : WriteDimAttrs(apoDims[apoDims.size() - 1], CF_LONGITUDE_STD_NAME,
2894 : CF_LONGITUDE_LNG_NAME, CF_DEGREES_EAST);
2895 1 : WriteDimAttrs(apoDims[apoDims.size() - 2], CF_LATITUDE_STD_NAME,
2896 : CF_LATITUDE_LNG_NAME, CF_DEGREES_NORTH);
2897 : }
2898 : }
2899 :
2900 18 : return true;
2901 : }
2902 :
2903 : /************************************************************************/
2904 : /* SetStatistics() */
2905 : /************************************************************************/
2906 :
2907 5 : bool netCDFVariable::SetStatistics(bool bApproxStats, double dfMin,
2908 : double dfMax, double dfMean, double dfStdDev,
2909 : GUInt64 nValidCount,
2910 : CSLConstList papszOptions)
2911 : {
2912 6 : if (!bApproxStats && !m_poShared->IsReadOnly() &&
2913 1 : CPLTestBool(
2914 : CSLFetchNameValueDef(papszOptions, "UPDATE_METADATA", "NO")))
2915 : {
2916 3 : auto poAttr = GetAttribute("actual_range");
2917 1 : if (!poAttr)
2918 : {
2919 : poAttr =
2920 1 : CreateAttribute("actual_range", {2}, GetDataType(), nullptr);
2921 : }
2922 1 : if (poAttr)
2923 : {
2924 2 : std::vector<GUInt64> startIdx = {0};
2925 2 : std::vector<size_t> count = {2};
2926 1 : std::vector<double> values = {dfMin, dfMax};
2927 2 : poAttr->Write(startIdx.data(), count.data(), nullptr, nullptr,
2928 2 : GDALExtendedDataType::Create(GDT_Float64),
2929 1 : values.data(), nullptr, 0);
2930 : }
2931 : }
2932 5 : return GDALPamMDArray::SetStatistics(bApproxStats, dfMin, dfMax, dfMean,
2933 5 : dfStdDev, nValidCount, papszOptions);
2934 : }
2935 :
2936 : /************************************************************************/
2937 : /* GetNCTypeSize() */
2938 : /************************************************************************/
2939 :
2940 659 : static size_t GetNCTypeSize(const GDALExtendedDataType &dt,
2941 : bool bPerfectDataTypeMatch, int nAttType)
2942 : {
2943 659 : auto nElementSize = dt.GetSize();
2944 659 : if (!bPerfectDataTypeMatch)
2945 : {
2946 10 : if (nAttType == NC_BYTE)
2947 : {
2948 4 : CPLAssert(dt.GetNumericDataType() == GDT_Int16);
2949 4 : nElementSize = sizeof(signed char);
2950 : }
2951 6 : else if (nAttType == NC_INT64)
2952 : {
2953 4 : CPLAssert(dt.GetNumericDataType() == GDT_Float64);
2954 4 : nElementSize = sizeof(GInt64);
2955 : }
2956 2 : else if (nAttType == NC_UINT64)
2957 : {
2958 2 : CPLAssert(dt.GetNumericDataType() == GDT_Float64);
2959 2 : nElementSize = sizeof(GUInt64);
2960 : }
2961 : else
2962 : {
2963 0 : CPLAssert(false);
2964 : }
2965 : }
2966 659 : return nElementSize;
2967 : }
2968 :
2969 : /************************************************************************/
2970 : /* ConvertNCStringsToCPLStrings() */
2971 : /************************************************************************/
2972 :
2973 81 : static void ConvertNCStringsToCPLStrings(GByte *pBuffer,
2974 : const GDALExtendedDataType &dt)
2975 : {
2976 81 : switch (dt.GetClass())
2977 : {
2978 0 : case GEDTC_STRING:
2979 : {
2980 : char *pszStr;
2981 : // cppcheck-suppress pointerSize
2982 0 : memcpy(&pszStr, pBuffer, sizeof(char *));
2983 0 : if (pszStr)
2984 : {
2985 0 : char *pszNewStr = VSIStrdup(pszStr);
2986 0 : nc_free_string(1, &pszStr);
2987 : // cppcheck-suppress pointerSize
2988 0 : memcpy(pBuffer, &pszNewStr, sizeof(char *));
2989 : }
2990 0 : break;
2991 : }
2992 :
2993 77 : case GEDTC_NUMERIC:
2994 : {
2995 77 : break;
2996 : }
2997 :
2998 4 : case GEDTC_COMPOUND:
2999 : {
3000 4 : const auto &comps = dt.GetComponents();
3001 12 : for (const auto &comp : comps)
3002 : {
3003 8 : ConvertNCStringsToCPLStrings(pBuffer + comp->GetOffset(),
3004 : comp->GetType());
3005 : }
3006 4 : break;
3007 : }
3008 : }
3009 81 : }
3010 :
3011 : /************************************************************************/
3012 : /* FreeNCStrings() */
3013 : /************************************************************************/
3014 :
3015 150 : static void FreeNCStrings(GByte *pBuffer, const GDALExtendedDataType &dt)
3016 : {
3017 150 : switch (dt.GetClass())
3018 : {
3019 0 : case GEDTC_STRING:
3020 : {
3021 : char *pszStr;
3022 : // cppcheck-suppress pointerSize
3023 0 : memcpy(&pszStr, pBuffer, sizeof(char *));
3024 0 : if (pszStr)
3025 : {
3026 0 : nc_free_string(1, &pszStr);
3027 : }
3028 0 : break;
3029 : }
3030 :
3031 150 : case GEDTC_NUMERIC:
3032 : {
3033 150 : break;
3034 : }
3035 :
3036 0 : case GEDTC_COMPOUND:
3037 : {
3038 0 : const auto &comps = dt.GetComponents();
3039 0 : for (const auto &comp : comps)
3040 : {
3041 0 : FreeNCStrings(pBuffer + comp->GetOffset(), comp->GetType());
3042 : }
3043 0 : break;
3044 : }
3045 : }
3046 150 : }
3047 :
3048 : /************************************************************************/
3049 : /* IReadWriteGeneric() */
3050 : /************************************************************************/
3051 :
3052 : namespace
3053 : {
3054 : template <typename T> struct GetGByteType
3055 : {
3056 : };
3057 :
3058 : template <> struct GetGByteType<void *>
3059 : {
3060 : typedef GByte *type;
3061 : };
3062 :
3063 : template <> struct GetGByteType<const void *>
3064 : {
3065 : typedef const GByte *type;
3066 : };
3067 : } // namespace
3068 :
3069 : template <typename BufferType, typename NCGetPutVar1FuncType,
3070 : typename ReadOrWriteOneElementType>
3071 60 : bool netCDFVariable::IReadWriteGeneric(
3072 : const size_t *arrayStartIdx, const size_t *count, const GInt64 *arrayStep,
3073 : const GPtrDiff_t *bufferStride, const GDALExtendedDataType &bufferDataType,
3074 : BufferType buffer, NCGetPutVar1FuncType NCGetPutVar1Func,
3075 : ReadOrWriteOneElementType ReadOrWriteOneElement) const
3076 : {
3077 60 : CPLAssert(m_nDims > 0);
3078 120 : std::vector<size_t> array_idx(m_nDims);
3079 120 : std::vector<size_t> stack_count_iters(m_nDims - 1);
3080 : typedef typename GetGByteType<BufferType>::type GBytePtrType;
3081 120 : std::vector<GBytePtrType> stack_ptr(m_nDims);
3082 120 : std::vector<GPtrDiff_t> ptr_inc;
3083 60 : ptr_inc.reserve(m_nDims);
3084 60 : const auto &eArrayEDT = GetDataType();
3085 60 : const bool bSameDT = m_bPerfectDataTypeMatch && eArrayEDT == bufferDataType;
3086 60 : const auto nBufferDTSize = bufferDataType.GetSize();
3087 132 : for (int i = 0; i < m_nDims; i++)
3088 : {
3089 72 : ptr_inc.push_back(bufferStride[i] * nBufferDTSize);
3090 : }
3091 60 : const auto nDimsMinus1 = m_nDims - 1;
3092 60 : stack_ptr[0] = static_cast<GBytePtrType>(buffer);
3093 :
3094 5265 : auto lambdaLastDim = [&](GBytePtrType ptr)
3095 : {
3096 105 : array_idx[nDimsMinus1] = arrayStartIdx[nDimsMinus1];
3097 105 : size_t nIters = count[nDimsMinus1];
3098 561 : while (true)
3099 : {
3100 666 : if (bSameDT)
3101 : {
3102 : int ret =
3103 9 : NCGetPutVar1Func(m_gid, m_varid, array_idx.data(), ptr);
3104 9 : NCDF_ERR(ret);
3105 9 : if (ret != NC_NOERR)
3106 0 : return false;
3107 : }
3108 : else
3109 : {
3110 1314 : if (!(this->*ReadOrWriteOneElement)(eArrayEDT, bufferDataType,
3111 657 : array_idx.data(), ptr))
3112 0 : return false;
3113 : }
3114 666 : if ((--nIters) == 0)
3115 105 : break;
3116 561 : ptr += ptr_inc[nDimsMinus1];
3117 : // CPLUnsanitizedAdd needed as arrayStep[] might be negative, and
3118 : // thus automatic conversion from negative to big unsigned might
3119 : // occur
3120 561 : array_idx[nDimsMinus1] = CPLUnsanitizedAdd<size_t>(
3121 : array_idx[nDimsMinus1],
3122 561 : static_cast<GPtrDiff_t>(arrayStep[nDimsMinus1]));
3123 : }
3124 105 : return true;
3125 : };
3126 :
3127 60 : if (m_nDims == 1)
3128 : {
3129 51 : return lambdaLastDim(stack_ptr[0]);
3130 : }
3131 9 : else if (m_nDims == 2)
3132 : {
3133 7 : auto nIters = count[0];
3134 7 : array_idx[0] = arrayStartIdx[0];
3135 : while (true)
3136 : {
3137 36 : if (!lambdaLastDim(stack_ptr[0]))
3138 0 : return false;
3139 36 : if ((--nIters) == 0)
3140 7 : break;
3141 29 : stack_ptr[0] += ptr_inc[0];
3142 : // CPLUnsanitizedAdd needed as arrayStep[] might be negative, and
3143 : // thus automatic conversion from negative to big unsigned might
3144 : // occur
3145 29 : array_idx[0] = CPLUnsanitizedAdd<size_t>(
3146 : array_idx[0], static_cast<GPtrDiff_t>(arrayStep[0]));
3147 : }
3148 : }
3149 2 : else if (m_nDims == 3)
3150 : {
3151 1 : stack_count_iters[0] = count[0];
3152 1 : array_idx[0] = arrayStartIdx[0];
3153 2 : while (true)
3154 : {
3155 3 : auto nIters = count[1];
3156 3 : array_idx[1] = arrayStartIdx[1];
3157 3 : stack_ptr[1] = stack_ptr[0];
3158 : while (true)
3159 : {
3160 6 : if (!lambdaLastDim(stack_ptr[1]))
3161 0 : return false;
3162 6 : if ((--nIters) == 0)
3163 3 : break;
3164 3 : stack_ptr[1] += ptr_inc[1];
3165 : // CPLUnsanitizedAdd needed as arrayStep[] might be negative,
3166 : // and thus automatic conversion from negative to big unsigned
3167 : // might occur
3168 3 : array_idx[1] = CPLUnsanitizedAdd<size_t>(
3169 3 : array_idx[1], static_cast<GPtrDiff_t>(arrayStep[1]));
3170 : }
3171 3 : if ((--stack_count_iters[0]) == 0)
3172 1 : break;
3173 2 : stack_ptr[0] += ptr_inc[0];
3174 : // CPLUnsanitizedAdd needed as arrayStep[] might be negative, and
3175 : // thus automatic conversion from negative to big unsigned might
3176 : // occur
3177 2 : array_idx[0] = CPLUnsanitizedAdd<size_t>(
3178 : array_idx[0], static_cast<GPtrDiff_t>(arrayStep[0]));
3179 : }
3180 : }
3181 : else
3182 : {
3183 : // Implementation valid for nDims >= 3
3184 :
3185 1 : int dimIdx = 0;
3186 : // Non-recursive implementation. Hence the gotos
3187 : // It might be possible to rewrite this without gotos, but I find they
3188 : // make it clearer to understand the recursive nature of the code
3189 9 : lbl_start:
3190 9 : if (dimIdx == nDimsMinus1 - 1)
3191 : {
3192 6 : array_idx[dimIdx] = arrayStartIdx[dimIdx];
3193 6 : auto nIters = count[dimIdx];
3194 : while (true)
3195 : {
3196 12 : if (!(lambdaLastDim(stack_ptr[dimIdx])))
3197 0 : return false;
3198 12 : if ((--nIters) == 0)
3199 6 : break;
3200 6 : stack_ptr[dimIdx] += ptr_inc[dimIdx];
3201 : // CPLUnsanitizedAdd needed as arrayStep[] might be negative,
3202 : // and thus automatic conversion from negative to big unsigned
3203 : // might occur
3204 6 : array_idx[dimIdx] = CPLUnsanitizedAdd<size_t>(
3205 : array_idx[dimIdx],
3206 6 : static_cast<GPtrDiff_t>(arrayStep[dimIdx]));
3207 : }
3208 : // If there was a test if( dimIdx > 0 ), that would be valid for
3209 : // nDims == 2
3210 6 : goto lbl_return_to_caller;
3211 : }
3212 : else
3213 : {
3214 3 : array_idx[dimIdx] = arrayStartIdx[dimIdx];
3215 3 : stack_count_iters[dimIdx] = count[dimIdx];
3216 : while (true)
3217 : {
3218 : // Simulate a recursive call to the next dimension
3219 : // Implicitly save back count and ptr
3220 8 : dimIdx++;
3221 8 : stack_ptr[dimIdx] = stack_ptr[dimIdx - 1];
3222 8 : goto lbl_start;
3223 8 : lbl_return_to_caller:
3224 8 : dimIdx--;
3225 8 : if ((--stack_count_iters[dimIdx]) == 0)
3226 3 : break;
3227 5 : stack_ptr[dimIdx] += ptr_inc[dimIdx];
3228 : // CPLUnsanitizedAdd needed as arrayStep[] might be negative,
3229 : // and thus automatic conversion from negative to big unsigned
3230 : // might occur
3231 5 : array_idx[dimIdx] = CPLUnsanitizedAdd<size_t>(
3232 : array_idx[dimIdx],
3233 5 : static_cast<GPtrDiff_t>(arrayStep[dimIdx]));
3234 : }
3235 3 : if (dimIdx > 0)
3236 2 : goto lbl_return_to_caller;
3237 : }
3238 : }
3239 :
3240 9 : return true;
3241 : }
3242 :
3243 : /************************************************************************/
3244 : /* CheckNumericDataType() */
3245 : /************************************************************************/
3246 :
3247 1525 : static bool CheckNumericDataType(const GDALExtendedDataType &dt)
3248 : {
3249 1525 : const auto klass = dt.GetClass();
3250 1525 : if (klass == GEDTC_NUMERIC)
3251 1515 : return dt.GetNumericDataType() != GDT_Unknown;
3252 10 : if (klass == GEDTC_STRING)
3253 0 : return false;
3254 10 : CPLAssert(klass == GEDTC_COMPOUND);
3255 10 : const auto &comps = dt.GetComponents();
3256 31 : for (const auto &comp : comps)
3257 : {
3258 21 : if (!CheckNumericDataType(comp->GetType()))
3259 0 : return false;
3260 : }
3261 10 : return true;
3262 : }
3263 :
3264 : /************************************************************************/
3265 : /* IReadWrite() */
3266 : /************************************************************************/
3267 :
3268 : template <typename BufferType, typename NCGetPutVar1FuncType,
3269 : typename NCGetPutVaraFuncType, typename NCGetPutVarmFuncType,
3270 : typename ReadOrWriteOneElementType>
3271 758 : bool netCDFVariable::IReadWrite(
3272 : const bool bIsRead, const GUInt64 *arrayStartIdx, const size_t *count,
3273 : const GInt64 *arrayStep, const GPtrDiff_t *bufferStride,
3274 : const GDALExtendedDataType &bufferDataType, BufferType buffer,
3275 : NCGetPutVar1FuncType NCGetPutVar1Func,
3276 : NCGetPutVaraFuncType NCGetPutVaraFunc,
3277 : NCGetPutVarmFuncType NCGetPutVarmFunc,
3278 : ReadOrWriteOneElementType ReadOrWriteOneElement) const
3279 : {
3280 1516 : CPLMutexHolderD(&hNCMutex);
3281 758 : m_poShared->SetDefineMode(false);
3282 :
3283 758 : const auto &eDT = GetDataType();
3284 1516 : std::vector<size_t> startp;
3285 758 : startp.reserve(m_nDims);
3286 758 : bool bUseSlowPath =
3287 758 : !m_bPerfectDataTypeMatch &&
3288 0 : !(bIsRead && bufferDataType.GetClass() == GEDTC_NUMERIC &&
3289 0 : eDT.GetClass() == GEDTC_NUMERIC &&
3290 0 : bufferDataType.GetSize() >= eDT.GetSize());
3291 2017 : for (int i = 0; i < m_nDims; i++)
3292 : {
3293 : #if SIZEOF_VOIDP == 4
3294 : if (arrayStartIdx[i] > std::numeric_limits<size_t>::max())
3295 : return false;
3296 : #endif
3297 1259 : startp.push_back(static_cast<size_t>(arrayStartIdx[i]));
3298 :
3299 : #if SIZEOF_VOIDP == 4
3300 : if (arrayStep[i] < std::numeric_limits<ptrdiff_t>::min() ||
3301 : arrayStep[i] > std::numeric_limits<ptrdiff_t>::max())
3302 : {
3303 : return false;
3304 : }
3305 : #endif
3306 :
3307 1259 : if (count[i] != 1 && arrayStep[i] <= 0)
3308 3 : bUseSlowPath = true; // netCDF rejects negative or NULL strides
3309 :
3310 1259 : if (bufferStride[i] < 0)
3311 0 : bUseSlowPath =
3312 : true; // and it seems to silently cast to size_t imapp
3313 : }
3314 :
3315 764 : if (eDT.GetClass() == GEDTC_STRING &&
3316 764 : bufferDataType.GetClass() == GEDTC_STRING && m_nVarType == NC_STRING)
3317 : {
3318 6 : if (m_nDims == 0)
3319 : {
3320 2 : return (this->*ReadOrWriteOneElement)(eDT, bufferDataType, nullptr,
3321 2 : buffer);
3322 : }
3323 :
3324 4 : return IReadWriteGeneric(startp.data(), count, arrayStep, bufferStride,
3325 : bufferDataType, buffer, NCGetPutVar1Func,
3326 4 : ReadOrWriteOneElement);
3327 : }
3328 :
3329 752 : if (!CheckNumericDataType(eDT))
3330 0 : return false;
3331 752 : if (!CheckNumericDataType(bufferDataType))
3332 0 : return false;
3333 :
3334 752 : if (m_nDims == 0)
3335 : {
3336 15 : return (this->*ReadOrWriteOneElement)(eDT, bufferDataType, nullptr,
3337 15 : buffer);
3338 : }
3339 :
3340 1471 : if (!bUseSlowPath &&
3341 1458 : ((GDALDataTypeIsComplex(bufferDataType.GetNumericDataType()) ||
3342 724 : bufferDataType.GetClass() == GEDTC_COMPOUND) &&
3343 15 : bufferDataType == eDT))
3344 : {
3345 : // nc_get_varm() not supported for non-atomic types.
3346 14 : ptrdiff_t nExpectedBufferStride = 1;
3347 40 : for (int i = m_nDims; i != 0;)
3348 : {
3349 26 : --i;
3350 26 : if (count[i] != 1 &&
3351 17 : (arrayStep[i] != 1 || bufferStride[i] != nExpectedBufferStride))
3352 : {
3353 0 : bUseSlowPath = true;
3354 0 : break;
3355 : }
3356 26 : nExpectedBufferStride *= count[i];
3357 : }
3358 14 : if (!bUseSlowPath)
3359 : {
3360 : int ret =
3361 14 : NCGetPutVaraFunc(m_gid, m_varid, startp.data(), count, buffer);
3362 14 : NCDF_ERR(ret);
3363 14 : return ret == NC_NOERR;
3364 : }
3365 : }
3366 :
3367 1439 : if (bUseSlowPath || bufferDataType.GetClass() == GEDTC_COMPOUND ||
3368 719 : eDT.GetClass() == GEDTC_COMPOUND ||
3369 719 : (!bIsRead &&
3370 2003 : bufferDataType.GetNumericDataType() != eDT.GetNumericDataType()) ||
3371 560 : (bIsRead && bufferDataType.GetSize() < eDT.GetSize()))
3372 : {
3373 52 : return IReadWriteGeneric(startp.data(), count, arrayStep, bufferStride,
3374 : bufferDataType, buffer, NCGetPutVar1Func,
3375 52 : ReadOrWriteOneElement);
3376 : }
3377 :
3378 671 : bUseSlowPath = false;
3379 671 : ptrdiff_t nExpectedBufferStride = 1;
3380 1822 : for (int i = m_nDims; i != 0;)
3381 : {
3382 1162 : --i;
3383 1162 : if (count[i] != 1 &&
3384 695 : (arrayStep[i] != 1 || bufferStride[i] != nExpectedBufferStride))
3385 : {
3386 11 : bUseSlowPath = true;
3387 11 : break;
3388 : }
3389 1151 : nExpectedBufferStride *= count[i];
3390 : }
3391 671 : if (!bUseSlowPath)
3392 : {
3393 : // nc_get_varm() is terribly inefficient, so use nc_get_vara()
3394 : // when possible.
3395 : int ret =
3396 660 : NCGetPutVaraFunc(m_gid, m_varid, startp.data(), count, buffer);
3397 660 : if (ret != NC_NOERR)
3398 : {
3399 1 : NCDF_ERR(ret);
3400 1 : return false;
3401 : }
3402 1205 : if (bIsRead &&
3403 546 : (!m_bPerfectDataTypeMatch ||
3404 546 : bufferDataType.GetNumericDataType() != eDT.GetNumericDataType()))
3405 : {
3406 : // If the buffer data type is "larger" or of the same size as the
3407 : // native data type, we can do a in-place conversion
3408 56 : GByte *pabyBuffer =
3409 : static_cast<GByte *>(const_cast<void *>(buffer));
3410 56 : CPLAssert(bufferDataType.GetSize() >= eDT.GetSize());
3411 56 : const auto nDTSize = eDT.GetSize();
3412 56 : const auto nBufferDTSize = bufferDataType.GetSize();
3413 56 : if (!m_bPerfectDataTypeMatch &&
3414 0 : (m_nVarType == NC_CHAR || m_nVarType == NC_BYTE))
3415 : {
3416 : // native NC type translates into GDAL data type of larger size
3417 0 : for (ptrdiff_t i = nExpectedBufferStride - 1; i >= 0; --i)
3418 : {
3419 : GByte abySrc[sizeof(
3420 : double)]; // 2 is enough here, but sizeof(double) make
3421 : // MSVC happy
3422 0 : abySrc[0] = *(pabyBuffer + i);
3423 0 : ConvertNCToGDAL(&abySrc[0]);
3424 0 : GDALExtendedDataType::CopyValue(
3425 0 : &abySrc[0], eDT, pabyBuffer + i * nBufferDTSize,
3426 : bufferDataType);
3427 0 : }
3428 : }
3429 56 : else if (!m_bPerfectDataTypeMatch)
3430 : {
3431 : // native NC type translates into GDAL data type of same size
3432 0 : CPLAssert(m_nVarType == NC_INT64 || m_nVarType == NC_UINT64);
3433 0 : for (ptrdiff_t i = nExpectedBufferStride - 1; i >= 0; --i)
3434 : {
3435 0 : ConvertNCToGDAL(pabyBuffer + i * nDTSize);
3436 0 : GDALExtendedDataType::CopyValue(
3437 0 : pabyBuffer + i * nDTSize, eDT,
3438 0 : pabyBuffer + i * nBufferDTSize, bufferDataType);
3439 : }
3440 : }
3441 : else
3442 : {
3443 3036 : for (ptrdiff_t i = nExpectedBufferStride - 1; i >= 0; --i)
3444 : {
3445 2980 : GDALExtendedDataType::CopyValue(
3446 2980 : pabyBuffer + i * nDTSize, eDT,
3447 2980 : pabyBuffer + i * nBufferDTSize, bufferDataType);
3448 : }
3449 : }
3450 : }
3451 659 : return true;
3452 : }
3453 : else
3454 : {
3455 11 : if (bufferDataType.GetNumericDataType() != eDT.GetNumericDataType())
3456 : {
3457 4 : return IReadWriteGeneric(startp.data(), count, arrayStep,
3458 : bufferStride, bufferDataType, buffer,
3459 4 : NCGetPutVar1Func, ReadOrWriteOneElement);
3460 : }
3461 14 : std::vector<ptrdiff_t> stridep;
3462 7 : stridep.reserve(m_nDims);
3463 7 : std::vector<ptrdiff_t> imapp;
3464 7 : imapp.reserve(m_nDims);
3465 25 : for (int i = 0; i < m_nDims; i++)
3466 : {
3467 18 : stridep.push_back(
3468 18 : static_cast<ptrdiff_t>(count[i] == 1 ? 1 : arrayStep[i]));
3469 18 : imapp.push_back(static_cast<ptrdiff_t>(bufferStride[i]));
3470 : }
3471 :
3472 7 : if (!m_poShared->GetImappIsInElements())
3473 : {
3474 : const size_t nMul =
3475 0 : GetNCTypeSize(eDT, m_bPerfectDataTypeMatch, m_nVarType);
3476 0 : for (int i = 0; i < m_nDims; ++i)
3477 : {
3478 0 : imapp[i] = static_cast<ptrdiff_t>(imapp[i] * nMul);
3479 : }
3480 : }
3481 7 : int ret = NCGetPutVarmFunc(m_gid, m_varid, startp.data(), count,
3482 7 : stridep.data(), imapp.data(), buffer);
3483 7 : NCDF_ERR(ret);
3484 7 : return ret == NC_NOERR;
3485 : }
3486 : }
3487 :
3488 : /************************************************************************/
3489 : /* ConvertNCToGDAL() */
3490 : /************************************************************************/
3491 :
3492 579 : void netCDFVariable::ConvertNCToGDAL(GByte *buffer) const
3493 : {
3494 579 : if (!m_bPerfectDataTypeMatch)
3495 : {
3496 0 : if (m_nVarType == NC_CHAR || m_nVarType == NC_BYTE)
3497 : {
3498 0 : short s = reinterpret_cast<signed char *>(buffer)[0];
3499 0 : memcpy(buffer, &s, sizeof(s));
3500 : }
3501 0 : else if (m_nVarType == NC_INT64)
3502 : {
3503 0 : double v =
3504 0 : static_cast<double>(reinterpret_cast<GInt64 *>(buffer)[0]);
3505 0 : memcpy(buffer, &v, sizeof(v));
3506 : }
3507 0 : else if (m_nVarType == NC_UINT64)
3508 : {
3509 0 : double v =
3510 0 : static_cast<double>(reinterpret_cast<GUInt64 *>(buffer)[0]);
3511 0 : memcpy(buffer, &v, sizeof(v));
3512 : }
3513 : }
3514 579 : }
3515 :
3516 : /************************************************************************/
3517 : /* ReadOneElement() */
3518 : /************************************************************************/
3519 :
3520 580 : bool netCDFVariable::ReadOneElement(const GDALExtendedDataType &src_datatype,
3521 : const GDALExtendedDataType &bufferDataType,
3522 : const size_t *array_idx,
3523 : void *pDstBuffer) const
3524 : {
3525 580 : if (src_datatype.GetClass() == GEDTC_STRING)
3526 : {
3527 1 : char *pszStr = nullptr;
3528 1 : int ret = nc_get_var1_string(m_gid, m_varid, array_idx, &pszStr);
3529 1 : NCDF_ERR(ret);
3530 1 : if (ret != NC_NOERR)
3531 0 : return false;
3532 1 : GDALExtendedDataType::CopyValue(&pszStr, src_datatype, pDstBuffer,
3533 : bufferDataType);
3534 1 : nc_free_string(1, &pszStr);
3535 1 : return true;
3536 : }
3537 :
3538 : std::vector<GByte> abySrc(std::max(
3539 0 : src_datatype.GetSize(),
3540 1158 : GetNCTypeSize(src_datatype, m_bPerfectDataTypeMatch, m_nVarType)));
3541 :
3542 579 : int ret = nc_get_var1(m_gid, m_varid, array_idx, &abySrc[0]);
3543 579 : NCDF_ERR(ret);
3544 579 : if (ret != NC_NOERR)
3545 0 : return false;
3546 :
3547 579 : ConvertNCToGDAL(&abySrc[0]);
3548 :
3549 579 : GDALExtendedDataType::CopyValue(&abySrc[0], src_datatype, pDstBuffer,
3550 : bufferDataType);
3551 579 : return true;
3552 : }
3553 :
3554 : /************************************************************************/
3555 : /* IRead() */
3556 : /************************************************************************/
3557 :
3558 678 : bool netCDFVariable::IRead(const GUInt64 *arrayStartIdx, const size_t *count,
3559 : const GInt64 *arrayStep,
3560 : const GPtrDiff_t *bufferStride,
3561 : const GDALExtendedDataType &bufferDataType,
3562 : void *pDstBuffer) const
3563 : {
3564 678 : if (m_nDims == 2 && m_nVarType == NC_CHAR && GetDimensions().size() == 1)
3565 : {
3566 8 : CPLMutexHolderD(&hNCMutex);
3567 4 : m_poShared->SetDefineMode(false);
3568 :
3569 4 : if (bufferDataType.GetClass() != GEDTC_STRING)
3570 0 : return false;
3571 4 : char **ppszDstBuffer = static_cast<char **>(pDstBuffer);
3572 4 : size_t array_idx[2] = {static_cast<size_t>(arrayStartIdx[0]), 0};
3573 4 : size_t array_count[2] = {1, m_nTextLength};
3574 4 : std::string osTmp(m_nTextLength, 0);
3575 4 : char *pszTmp = &osTmp[0];
3576 4 : bool ret = true;
3577 12 : for (size_t i = 0; ret && i < count[0]; i++)
3578 : {
3579 : int ncErr =
3580 8 : nc_get_vara(m_gid, m_varid, array_idx, array_count, pszTmp);
3581 8 : NCDF_ERR(ncErr);
3582 8 : ret = ncErr == NC_NOERR;
3583 8 : if (ret)
3584 : {
3585 8 : *ppszDstBuffer = CPLStrdup(pszTmp);
3586 8 : array_idx[0] = static_cast<size_t>(array_idx[0] + arrayStep[0]);
3587 8 : ppszDstBuffer += bufferStride[0];
3588 : }
3589 : }
3590 4 : return ret;
3591 : }
3592 :
3593 674 : if (m_poCachedArray)
3594 : {
3595 5 : const auto nDims = GetDimensionCount();
3596 5 : std::vector<GUInt64> modifiedArrayStartIdx(nDims);
3597 5 : bool canUseCache = true;
3598 13 : for (size_t i = 0; i < nDims; i++)
3599 : {
3600 17 : if (arrayStartIdx[i] >= m_cachedArrayStartIdx[i] &&
3601 8 : arrayStartIdx[i] + (count[i] - 1) * arrayStep[i] <=
3602 8 : m_cachedArrayStartIdx[i] + m_cachedCount[i] - 1)
3603 : {
3604 8 : modifiedArrayStartIdx[i] =
3605 8 : arrayStartIdx[i] - m_cachedArrayStartIdx[i];
3606 : }
3607 : else
3608 : {
3609 1 : canUseCache = false;
3610 1 : break;
3611 : }
3612 : }
3613 5 : if (canUseCache)
3614 : {
3615 4 : return m_poCachedArray->Read(modifiedArrayStartIdx.data(), count,
3616 : arrayStep, bufferStride,
3617 4 : bufferDataType, pDstBuffer);
3618 : }
3619 : }
3620 :
3621 670 : if (IsTransposedRequest(count, bufferStride))
3622 : {
3623 84 : return ReadForTransposedRequest(arrayStartIdx, count, arrayStep,
3624 : bufferStride, bufferDataType,
3625 84 : pDstBuffer);
3626 : }
3627 :
3628 586 : return IReadWrite(true, arrayStartIdx, count, arrayStep, bufferStride,
3629 : bufferDataType, pDstBuffer, nc_get_var1, nc_get_vara,
3630 586 : nc_get_varm, &netCDFVariable::ReadOneElement);
3631 : }
3632 :
3633 : /************************************************************************/
3634 : /* IAdviseRead() */
3635 : /************************************************************************/
3636 :
3637 4 : bool netCDFVariable::IAdviseRead(const GUInt64 *arrayStartIdx,
3638 : const size_t *count,
3639 : CSLConstList /* papszOptions */) const
3640 : {
3641 4 : const auto nDims = GetDimensionCount();
3642 4 : if (nDims == 0)
3643 0 : return true;
3644 4 : const auto &eDT = GetDataType();
3645 4 : if (eDT.GetClass() != GEDTC_NUMERIC)
3646 0 : return false;
3647 :
3648 4 : m_poCachedArray.reset();
3649 :
3650 4 : size_t nElts = 1;
3651 12 : for (size_t i = 0; i < nDims; i++)
3652 8 : nElts *= count[i];
3653 :
3654 4 : void *pData = VSI_MALLOC2_VERBOSE(nElts, eDT.GetSize());
3655 4 : if (pData == nullptr)
3656 0 : return false;
3657 :
3658 4 : if (!Read(arrayStartIdx, count, nullptr, nullptr, eDT, pData))
3659 : {
3660 0 : VSIFree(pData);
3661 0 : return false;
3662 : }
3663 :
3664 : auto poDS = std::unique_ptr<GDALDataset>(
3665 8 : MEMDataset::CreateMultiDimensional("", nullptr, nullptr));
3666 8 : auto poGroup = poDS->GetRootGroup();
3667 :
3668 4 : std::vector<std::shared_ptr<GDALDimension>> apoMemDims;
3669 4 : const auto &poDims = GetDimensions();
3670 12 : for (size_t i = 0; i < nDims; i++)
3671 : {
3672 : apoMemDims.emplace_back(
3673 24 : poGroup->CreateDimension(poDims[i]->GetName(), std::string(),
3674 24 : std::string(), count[i], nullptr));
3675 : }
3676 : m_poCachedArray =
3677 4 : poGroup->CreateMDArray(GetName(), apoMemDims, eDT, nullptr);
3678 4 : m_poCachedArray->Write(std::vector<GUInt64>(nDims).data(), count, nullptr,
3679 : nullptr, eDT, pData);
3680 4 : m_cachedArrayStartIdx.resize(nDims);
3681 4 : memcpy(&m_cachedArrayStartIdx[0], arrayStartIdx, nDims * sizeof(GUInt64));
3682 4 : m_cachedCount.resize(nDims);
3683 4 : memcpy(&m_cachedCount[0], count, nDims * sizeof(size_t));
3684 4 : VSIFree(pData);
3685 4 : return true;
3686 : }
3687 :
3688 : /************************************************************************/
3689 : /* ConvertGDALToNC() */
3690 : /************************************************************************/
3691 :
3692 111 : void netCDFVariable::ConvertGDALToNC(GByte *buffer) const
3693 : {
3694 111 : if (!m_bPerfectDataTypeMatch)
3695 : {
3696 0 : if (m_nVarType == NC_CHAR || m_nVarType == NC_BYTE)
3697 : {
3698 0 : const auto c =
3699 0 : static_cast<signed char>(reinterpret_cast<short *>(buffer)[0]);
3700 0 : memcpy(buffer, &c, sizeof(c));
3701 : }
3702 0 : else if (m_nVarType == NC_INT64)
3703 : {
3704 0 : const auto v =
3705 0 : static_cast<GInt64>(reinterpret_cast<double *>(buffer)[0]);
3706 0 : memcpy(buffer, &v, sizeof(v));
3707 : }
3708 0 : else if (m_nVarType == NC_UINT64)
3709 : {
3710 0 : const auto v =
3711 0 : static_cast<GUInt64>(reinterpret_cast<double *>(buffer)[0]);
3712 0 : memcpy(buffer, &v, sizeof(v));
3713 : }
3714 : }
3715 111 : }
3716 :
3717 : /************************************************************************/
3718 : /* WriteOneElement() */
3719 : /************************************************************************/
3720 :
3721 94 : bool netCDFVariable::WriteOneElement(const GDALExtendedDataType &dst_datatype,
3722 : const GDALExtendedDataType &bufferDataType,
3723 : const size_t *array_idx,
3724 : const void *pSrcBuffer) const
3725 : {
3726 94 : if (dst_datatype.GetClass() == GEDTC_STRING)
3727 : {
3728 1 : const char *pszStr = (static_cast<const char *const *>(pSrcBuffer))[0];
3729 1 : int ret = nc_put_var1_string(m_gid, m_varid, array_idx, &pszStr);
3730 1 : NCDF_ERR(ret);
3731 1 : return ret == NC_NOERR;
3732 : }
3733 :
3734 93 : std::vector<GByte> abyTmp(dst_datatype.GetSize());
3735 93 : GDALExtendedDataType::CopyValue(pSrcBuffer, bufferDataType, &abyTmp[0],
3736 : dst_datatype);
3737 :
3738 93 : ConvertGDALToNC(&abyTmp[0]);
3739 :
3740 93 : int ret = nc_put_var1(m_gid, m_varid, array_idx, &abyTmp[0]);
3741 93 : NCDF_ERR(ret);
3742 93 : return ret == NC_NOERR;
3743 : }
3744 :
3745 : /************************************************************************/
3746 : /* IWrite() */
3747 : /************************************************************************/
3748 :
3749 175 : bool netCDFVariable::IWrite(const GUInt64 *arrayStartIdx, const size_t *count,
3750 : const GInt64 *arrayStep,
3751 : const GPtrDiff_t *bufferStride,
3752 : const GDALExtendedDataType &bufferDataType,
3753 : const void *pSrcBuffer)
3754 : {
3755 175 : m_bHasWrittenData = true;
3756 :
3757 175 : m_poCachedArray.reset();
3758 :
3759 175 : if (m_nDims == 2 && m_nVarType == NC_CHAR && GetDimensions().size() == 1)
3760 : {
3761 6 : CPLMutexHolderD(&hNCMutex);
3762 3 : m_poShared->SetDefineMode(false);
3763 :
3764 3 : if (bufferDataType.GetClass() != GEDTC_STRING)
3765 0 : return false;
3766 3 : const char *const *ppszSrcBuffer =
3767 : static_cast<const char *const *>(pSrcBuffer);
3768 3 : size_t array_idx[2] = {static_cast<size_t>(arrayStartIdx[0]), 0};
3769 3 : size_t array_count[2] = {1, m_nTextLength};
3770 6 : std::string osTmp(m_nTextLength, 0);
3771 8 : for (size_t i = 0; i < count[0]; i++)
3772 : {
3773 5 : const char *pszStr = *ppszSrcBuffer;
3774 5 : memset(&osTmp[0], 0, m_nTextLength);
3775 5 : if (pszStr)
3776 : {
3777 5 : size_t nLen = strlen(pszStr);
3778 5 : memcpy(&osTmp[0], pszStr, std::min(m_nTextLength, nLen));
3779 : }
3780 : int ret =
3781 5 : nc_put_vara(m_gid, m_varid, array_idx, array_count, &osTmp[0]);
3782 5 : NCDF_ERR(ret);
3783 5 : if (ret != NC_NOERR)
3784 0 : return false;
3785 5 : array_idx[0] = static_cast<size_t>(array_idx[0] + arrayStep[0]);
3786 5 : ppszSrcBuffer += bufferStride[0];
3787 : }
3788 3 : return true;
3789 : }
3790 :
3791 172 : return IReadWrite(false, arrayStartIdx, count, arrayStep, bufferStride,
3792 : bufferDataType, pSrcBuffer, nc_put_var1, nc_put_vara,
3793 172 : nc_put_varm, &netCDFVariable::WriteOneElement);
3794 : }
3795 :
3796 : /************************************************************************/
3797 : /* GetRawNoDataValue() */
3798 : /************************************************************************/
3799 :
3800 273 : const void *netCDFVariable::GetRawNoDataValue() const
3801 : {
3802 273 : const auto &dt = GetDataType();
3803 273 : if (dt.GetClass() != GEDTC_NUMERIC)
3804 2 : return nullptr;
3805 :
3806 271 : if (m_bGetRawNoDataValueHasRun)
3807 : {
3808 111 : return m_abyNoData.empty() ? nullptr : m_abyNoData.data();
3809 : }
3810 :
3811 160 : m_bGetRawNoDataValueHasRun = true;
3812 :
3813 160 : const char *pszAttrName = NCDF_FillValue;
3814 480 : auto poAttr = GetAttribute(pszAttrName);
3815 160 : if (!poAttr)
3816 : {
3817 126 : pszAttrName = "missing_value";
3818 126 : poAttr = GetAttribute(pszAttrName);
3819 : }
3820 160 : if (poAttr && poAttr->GetDataType().GetClass() == GEDTC_NUMERIC)
3821 : {
3822 37 : auto oRawResult = poAttr->ReadAsRaw();
3823 37 : if (oRawResult.data())
3824 : {
3825 : // Round-trip attribute value to target data type and back
3826 : // to attribute data type to ensure there is no loss
3827 : // Normally _FillValue data type should be the same
3828 : // as the array one, but this is not always the case.
3829 : // For example NASA GEDI L2B products have Float64
3830 : // _FillValue for Float32 variables.
3831 37 : m_abyNoData.resize(dt.GetSize());
3832 37 : GDALExtendedDataType::CopyValue(oRawResult.data(),
3833 37 : poAttr->GetDataType(),
3834 37 : m_abyNoData.data(), dt);
3835 74 : std::vector<GByte> abyTmp(poAttr->GetDataType().GetSize());
3836 37 : GDALExtendedDataType::CopyValue(
3837 37 : m_abyNoData.data(), dt, abyTmp.data(), poAttr->GetDataType());
3838 74 : std::vector<GByte> abyOri;
3839 37 : abyOri.assign(oRawResult.data(),
3840 37 : oRawResult.data() + oRawResult.size());
3841 37 : if (abyOri == abyTmp)
3842 36 : return m_abyNoData.data();
3843 1 : m_abyNoData.clear();
3844 1 : char *pszVal = nullptr;
3845 1 : GDALExtendedDataType::CopyValue(
3846 1 : oRawResult.data(), poAttr->GetDataType(), &pszVal,
3847 2 : GDALExtendedDataType::CreateString());
3848 1 : CPLError(CE_Warning, CPLE_AppDefined,
3849 : "%s attribute value (%s) is not in the range of the "
3850 : "variable data type",
3851 1 : pszAttrName, pszVal ? pszVal : "(null)");
3852 1 : CPLFree(pszVal);
3853 1 : return nullptr;
3854 : }
3855 : }
3856 123 : else if (poAttr && poAttr->GetDataType().GetClass() == GEDTC_STRING)
3857 : {
3858 3 : const char *pszVal = poAttr->ReadAsString();
3859 3 : if (pszVal)
3860 : {
3861 : // Round-trip attribute value to target data type and back
3862 : // to attribute data type to ensure there is no loss
3863 3 : m_abyNoData.resize(dt.GetSize());
3864 3 : GDALExtendedDataType::CopyValue(&pszVal, poAttr->GetDataType(),
3865 3 : m_abyNoData.data(), dt);
3866 3 : char *pszTmpVal = nullptr;
3867 3 : GDALExtendedDataType::CopyValue(m_abyNoData.data(), dt, &pszTmpVal,
3868 3 : poAttr->GetDataType());
3869 3 : if (pszTmpVal)
3870 : {
3871 3 : const bool bSame = strcmp(pszVal, pszTmpVal) == 0;
3872 3 : CPLFree(pszTmpVal);
3873 3 : if (bSame)
3874 3 : return m_abyNoData.data();
3875 2 : CPLError(CE_Warning, CPLE_AppDefined,
3876 : "%s attribute value ('%s') is not in the range of the "
3877 : "variable data type",
3878 : pszAttrName, pszVal);
3879 2 : m_abyNoData.clear();
3880 2 : return nullptr;
3881 : }
3882 : }
3883 : }
3884 :
3885 132 : if (m_bUseDefaultFillAsNoData && m_abyNoData.empty() &&
3886 12 : (m_nVarType == NC_SHORT || m_nVarType == NC_USHORT ||
3887 8 : m_nVarType == NC_INT || m_nVarType == NC_UINT ||
3888 6 : m_nVarType == NC_FLOAT || m_nVarType == NC_DOUBLE))
3889 : {
3890 9 : bool bGotNoData = false;
3891 : double dfNoData =
3892 9 : NCDFGetDefaultNoDataValue(m_gid, m_varid, m_nVarType, bGotNoData);
3893 9 : m_abyNoData.resize(dt.GetSize());
3894 9 : GDALCopyWords(&dfNoData, GDT_Float64, 0, &m_abyNoData[0],
3895 : dt.GetNumericDataType(), 0, 1);
3896 : }
3897 114 : else if (m_bUseDefaultFillAsNoData && m_abyNoData.empty() &&
3898 3 : m_nVarType == NC_INT64)
3899 : {
3900 1 : bool bGotNoData = false;
3901 : const auto nNoData =
3902 1 : NCDFGetDefaultNoDataValueAsInt64(m_gid, m_varid, bGotNoData);
3903 1 : m_abyNoData.resize(dt.GetSize());
3904 1 : memcpy(&m_abyNoData[0], &nNoData, sizeof(nNoData));
3905 : }
3906 112 : else if (m_bUseDefaultFillAsNoData && m_abyNoData.empty() &&
3907 2 : m_nVarType == NC_UINT64)
3908 : {
3909 1 : bool bGotNoData = false;
3910 : const auto nNoData =
3911 1 : NCDFGetDefaultNoDataValueAsUInt64(m_gid, m_varid, bGotNoData);
3912 1 : m_abyNoData.resize(dt.GetSize());
3913 1 : memcpy(&m_abyNoData[0], &nNoData, sizeof(nNoData));
3914 : }
3915 :
3916 120 : return m_abyNoData.empty() ? nullptr : m_abyNoData.data();
3917 : }
3918 :
3919 : /************************************************************************/
3920 : /* SetRawNoDataValue() */
3921 : /************************************************************************/
3922 :
3923 21 : bool netCDFVariable::SetRawNoDataValue(const void *pNoData)
3924 : {
3925 21 : GetDataType();
3926 21 : if (m_nVarType == NC_STRING)
3927 0 : return false;
3928 :
3929 21 : m_bGetRawNoDataValueHasRun = false;
3930 42 : CPLMutexHolderD(&hNCMutex);
3931 21 : m_poShared->SetDefineMode(true);
3932 : int ret;
3933 21 : if (pNoData == nullptr)
3934 : {
3935 3 : m_abyNoData.clear();
3936 3 : nc_type atttype = NC_NAT;
3937 3 : size_t attlen = 0;
3938 3 : if (nc_inq_att(m_gid, m_varid, NCDF_FillValue, &atttype, &attlen) ==
3939 : NC_NOERR)
3940 2 : ret = nc_del_att(m_gid, m_varid, NCDF_FillValue);
3941 : else
3942 1 : ret = NC_NOERR;
3943 3 : if (nc_inq_att(m_gid, m_varid, "missing_value", &atttype, &attlen) ==
3944 : NC_NOERR)
3945 : {
3946 0 : int ret2 = nc_del_att(m_gid, m_varid, "missing_value");
3947 0 : if (ret2 != NC_NOERR)
3948 0 : ret = ret2;
3949 : }
3950 : }
3951 : else
3952 : {
3953 18 : const auto nSize = GetDataType().GetSize();
3954 18 : m_abyNoData.resize(nSize);
3955 18 : memcpy(&m_abyNoData[0], pNoData, nSize);
3956 :
3957 18 : std::vector<GByte> abyTmp(nSize);
3958 18 : memcpy(&abyTmp[0], pNoData, nSize);
3959 18 : ConvertGDALToNC(&abyTmp[0]);
3960 :
3961 18 : if (!m_bHasWrittenData)
3962 : {
3963 16 : ret = nc_def_var_fill(m_gid, m_varid, NC_FILL, &abyTmp[0]);
3964 16 : NCDF_ERR(ret);
3965 : }
3966 :
3967 18 : nc_type atttype = NC_NAT;
3968 18 : size_t attlen = 0;
3969 18 : if (nc_inq_att(m_gid, m_varid, "missing_value", &atttype, &attlen) ==
3970 : NC_NOERR)
3971 : {
3972 2 : if (nc_inq_att(m_gid, m_varid, NCDF_FillValue, &atttype, &attlen) ==
3973 : NC_NOERR)
3974 : {
3975 1 : CPLError(CE_Failure, CPLE_NotSupported,
3976 : "Cannot change nodata when missing_value and "
3977 : "_FillValue both exist");
3978 1 : return false;
3979 : }
3980 1 : ret = nc_put_att(m_gid, m_varid, "missing_value", m_nVarType, 1,
3981 1 : &abyTmp[0]);
3982 : }
3983 : else
3984 : {
3985 16 : ret = nc_put_att(m_gid, m_varid, NCDF_FillValue, m_nVarType, 1,
3986 16 : &abyTmp[0]);
3987 : }
3988 : }
3989 20 : NCDF_ERR(ret);
3990 20 : if (ret == NC_NOERR)
3991 20 : m_bGetRawNoDataValueHasRun = true;
3992 20 : return ret == NC_NOERR;
3993 : }
3994 :
3995 : /************************************************************************/
3996 : /* SetScale() */
3997 : /************************************************************************/
3998 :
3999 3 : bool netCDFVariable::SetScale(double dfScale, GDALDataType eStorageType)
4000 : {
4001 9 : auto poAttr = GetAttribute(CF_SCALE_FACTOR);
4002 3 : if (!poAttr)
4003 : {
4004 4 : poAttr = CreateAttribute(
4005 : CF_SCALE_FACTOR, {},
4006 4 : GDALExtendedDataType::Create(
4007 : eStorageType == GDT_Unknown ? GDT_Float64 : eStorageType),
4008 2 : nullptr);
4009 : }
4010 3 : if (!poAttr)
4011 0 : return false;
4012 3 : return poAttr->Write(dfScale);
4013 : }
4014 :
4015 : /************************************************************************/
4016 : /* SetOffset() */
4017 : /************************************************************************/
4018 :
4019 3 : bool netCDFVariable::SetOffset(double dfOffset, GDALDataType eStorageType)
4020 : {
4021 9 : auto poAttr = GetAttribute(CF_ADD_OFFSET);
4022 3 : if (!poAttr)
4023 : {
4024 4 : poAttr = CreateAttribute(
4025 : CF_ADD_OFFSET, {},
4026 4 : GDALExtendedDataType::Create(
4027 : eStorageType == GDT_Unknown ? GDT_Float64 : eStorageType),
4028 2 : nullptr);
4029 : }
4030 3 : if (!poAttr)
4031 0 : return false;
4032 3 : return poAttr->Write(dfOffset);
4033 : }
4034 :
4035 : /************************************************************************/
4036 : /* GetScale() */
4037 : /************************************************************************/
4038 :
4039 96 : double netCDFVariable::GetScale(bool *pbHasScale,
4040 : GDALDataType *peStorageType) const
4041 : {
4042 288 : auto poAttr = GetAttribute(CF_SCALE_FACTOR);
4043 96 : if (!poAttr || poAttr->GetDataType().GetClass() != GEDTC_NUMERIC)
4044 : {
4045 91 : if (pbHasScale)
4046 90 : *pbHasScale = false;
4047 91 : return 1.0;
4048 : }
4049 5 : if (pbHasScale)
4050 5 : *pbHasScale = true;
4051 5 : if (peStorageType)
4052 2 : *peStorageType = poAttr->GetDataType().GetNumericDataType();
4053 5 : return poAttr->ReadAsDouble();
4054 : }
4055 :
4056 : /************************************************************************/
4057 : /* GetOffset() */
4058 : /************************************************************************/
4059 :
4060 96 : double netCDFVariable::GetOffset(bool *pbHasOffset,
4061 : GDALDataType *peStorageType) const
4062 : {
4063 288 : auto poAttr = GetAttribute(CF_ADD_OFFSET);
4064 96 : if (!poAttr || poAttr->GetDataType().GetClass() != GEDTC_NUMERIC)
4065 : {
4066 91 : if (pbHasOffset)
4067 90 : *pbHasOffset = false;
4068 91 : return 0.0;
4069 : }
4070 5 : if (pbHasOffset)
4071 5 : *pbHasOffset = true;
4072 5 : if (peStorageType)
4073 2 : *peStorageType = poAttr->GetDataType().GetNumericDataType();
4074 5 : return poAttr->ReadAsDouble();
4075 : }
4076 :
4077 : /************************************************************************/
4078 : /* GetBlockSize() */
4079 : /************************************************************************/
4080 :
4081 254 : std::vector<GUInt64> netCDFVariable::GetBlockSize() const
4082 : {
4083 254 : const auto nDimCount = GetDimensionCount();
4084 254 : std::vector<GUInt64> res(nDimCount);
4085 254 : if (res.empty())
4086 0 : return res;
4087 254 : int nStorageType = 0;
4088 : // We add 1 to the dimension count, for 2D char variables that we
4089 : // expose as a 1D variable.
4090 508 : std::vector<size_t> anTemp(1 + nDimCount);
4091 508 : CPLMutexHolderD(&hNCMutex);
4092 254 : nc_inq_var_chunking(m_gid, m_varid, &nStorageType, &anTemp[0]);
4093 254 : if (nStorageType == NC_CHUNKED)
4094 : {
4095 59 : for (size_t i = 0; i < res.size(); ++i)
4096 38 : res[i] = anTemp[i];
4097 : }
4098 254 : return res;
4099 : }
4100 :
4101 : /************************************************************************/
4102 : /* GetAttribute() */
4103 : /************************************************************************/
4104 :
4105 : std::shared_ptr<GDALAttribute>
4106 4094 : netCDFVariable::GetAttribute(const std::string &osName) const
4107 : {
4108 8188 : CPLMutexHolderD(&hNCMutex);
4109 4094 : int nAttId = -1;
4110 4094 : if (nc_inq_attid(m_gid, m_varid, osName.c_str(), &nAttId) != NC_NOERR)
4111 3125 : return nullptr;
4112 1938 : return netCDFAttribute::Create(
4113 2907 : m_poShared, std::dynamic_pointer_cast<netCDFVariable>(m_pSelf.lock()),
4114 1938 : m_gid, m_varid, osName);
4115 : }
4116 :
4117 : /************************************************************************/
4118 : /* GetAttributes() */
4119 : /************************************************************************/
4120 :
4121 : std::vector<std::shared_ptr<GDALAttribute>>
4122 196 : netCDFVariable::GetAttributes(CSLConstList papszOptions) const
4123 : {
4124 392 : CPLMutexHolderD(&hNCMutex);
4125 196 : std::vector<std::shared_ptr<GDALAttribute>> res;
4126 196 : int nbAttr = 0;
4127 196 : NCDF_ERR(nc_inq_varnatts(m_gid, m_varid, &nbAttr));
4128 196 : res.reserve(nbAttr);
4129 : const bool bShowAll =
4130 196 : CPLTestBool(CSLFetchNameValueDef(papszOptions, "SHOW_ALL", "NO"));
4131 657 : for (int i = 0; i < nbAttr; i++)
4132 : {
4133 : char szAttrName[NC_MAX_NAME + 1];
4134 461 : szAttrName[0] = 0;
4135 461 : NCDF_ERR(nc_inq_attname(m_gid, m_varid, i, szAttrName));
4136 461 : if (bShowAll || (!EQUAL(szAttrName, NCDF_FillValue) &&
4137 419 : !EQUAL(szAttrName, "missing_value") &&
4138 419 : !EQUAL(szAttrName, CF_UNITS) &&
4139 351 : !EQUAL(szAttrName, CF_SCALE_FACTOR) &&
4140 351 : !EQUAL(szAttrName, CF_ADD_OFFSET) &&
4141 351 : !EQUAL(szAttrName, CF_GRD_MAPPING) &&
4142 310 : !(EQUAL(szAttrName, "_Unsigned") &&
4143 36 : (m_nVarType == NC_BYTE || m_nVarType == NC_SHORT))))
4144 : {
4145 1136 : res.emplace_back(netCDFAttribute::Create(
4146 284 : m_poShared,
4147 568 : std::dynamic_pointer_cast<netCDFVariable>(m_pSelf.lock()),
4148 568 : m_gid, m_varid, szAttrName));
4149 : }
4150 : }
4151 392 : return res;
4152 : }
4153 :
4154 : /************************************************************************/
4155 : /* CreateAttribute() */
4156 : /************************************************************************/
4157 :
4158 135 : std::shared_ptr<GDALAttribute> netCDFVariable::CreateAttribute(
4159 : const std::string &osName, const std::vector<GUInt64> &anDimensions,
4160 : const GDALExtendedDataType &oDataType, CSLConstList papszOptions)
4161 : {
4162 270 : return netCDFAttribute::Create(
4163 405 : m_poShared, std::dynamic_pointer_cast<netCDFVariable>(m_pSelf.lock()),
4164 270 : m_gid, m_varid, osName, anDimensions, oDataType, papszOptions);
4165 : }
4166 :
4167 : /************************************************************************/
4168 : /* DeleteAttribute() */
4169 : /************************************************************************/
4170 :
4171 2 : bool netCDFVariable::DeleteAttribute(const std::string &osName,
4172 : CSLConstList /*papszOptions*/)
4173 : {
4174 4 : CPLMutexHolderD(&hNCMutex);
4175 2 : m_poShared->SetDefineMode(true);
4176 :
4177 2 : int ret = nc_del_att(m_gid, m_varid, osName.c_str());
4178 2 : NCDF_ERR(ret);
4179 2 : if (ret != NC_NOERR)
4180 1 : return false;
4181 :
4182 1 : auto it = m_oMapAttributes.find(osName);
4183 1 : if (it != m_oMapAttributes.end())
4184 : {
4185 1 : it->second->Deleted();
4186 1 : m_oMapAttributes.erase(it);
4187 : }
4188 :
4189 1 : return true;
4190 : }
4191 :
4192 : /************************************************************************/
4193 : /* GetCoordinateVariables() */
4194 : /************************************************************************/
4195 :
4196 : std::vector<std::shared_ptr<GDALMDArray>>
4197 12 : netCDFVariable::GetCoordinateVariables() const
4198 : {
4199 24 : std::vector<std::shared_ptr<GDALMDArray>> ret;
4200 :
4201 36 : const auto poCoordinates = GetAttribute("coordinates");
4202 3 : if (poCoordinates &&
4203 15 : poCoordinates->GetDataType().GetClass() == GEDTC_STRING &&
4204 3 : poCoordinates->GetDimensionCount() == 0)
4205 : {
4206 3 : const char *pszCoordinates = poCoordinates->ReadAsString();
4207 3 : if (pszCoordinates)
4208 : {
4209 : const CPLStringList aosNames(
4210 6 : NCDFTokenizeCoordinatesAttribute(pszCoordinates));
4211 6 : CPLMutexHolderD(&hNCMutex);
4212 9 : for (int i = 0; i < aosNames.size(); i++)
4213 : {
4214 6 : int nVarId = 0;
4215 6 : if (nc_inq_varid(m_gid, aosNames[i], &nVarId) == NC_NOERR)
4216 : {
4217 6 : ret.emplace_back(netCDFVariable::Create(
4218 12 : m_poShared, m_poParent.lock(), m_gid, nVarId,
4219 12 : std::vector<std::shared_ptr<GDALDimension>>(), nullptr,
4220 6 : false));
4221 : }
4222 : else
4223 : {
4224 0 : CPLError(
4225 : CE_Warning, CPLE_AppDefined,
4226 : "Cannot find variable corresponding to coordinate %s",
4227 : aosNames[i]);
4228 : }
4229 : }
4230 : }
4231 : }
4232 :
4233 : // Special case for NASA EMIT datasets
4234 24 : auto apoDims = GetDimensions();
4235 21 : if ((apoDims.size() == 3 && apoDims[0]->GetName() == "downtrack" &&
4236 6 : apoDims[1]->GetName() == "crosstrack" &&
4237 33 : apoDims[2]->GetName() == "bands") ||
4238 18 : (apoDims.size() == 2 && apoDims[0]->GetName() == "downtrack" &&
4239 3 : apoDims[1]->GetName() == "crosstrack"))
4240 : {
4241 6 : auto poRootGroup = netCDFGroup::Create(m_poShared, nullptr, m_gid);
4242 6 : if (poRootGroup)
4243 : {
4244 12 : auto poLocationGroup = poRootGroup->OpenGroup("location");
4245 6 : if (poLocationGroup)
4246 : {
4247 12 : auto poLon = poLocationGroup->OpenMDArray("lon");
4248 12 : auto poLat = poLocationGroup->OpenMDArray("lat");
4249 6 : if (poLon && poLat)
4250 : {
4251 18 : return {std::move(poLon), std::move(poLat)};
4252 : }
4253 : }
4254 : }
4255 : }
4256 :
4257 6 : return ret;
4258 : }
4259 :
4260 : /************************************************************************/
4261 : /* Resize() */
4262 : /************************************************************************/
4263 :
4264 9 : bool netCDFVariable::Resize(const std::vector<GUInt64> &anNewDimSizes,
4265 : CSLConstList /* papszOptions */)
4266 : {
4267 9 : if (!IsWritable())
4268 : {
4269 1 : CPLError(CE_Failure, CPLE_AppDefined,
4270 : "Resize() not supported on read-only file");
4271 1 : return false;
4272 : }
4273 :
4274 8 : const auto nDimCount = GetDimensionCount();
4275 8 : if (anNewDimSizes.size() != nDimCount)
4276 : {
4277 0 : CPLError(CE_Failure, CPLE_IllegalArg,
4278 : "Not expected number of values in anNewDimSizes.");
4279 0 : return false;
4280 : }
4281 :
4282 8 : auto &dims = GetDimensions();
4283 16 : std::vector<size_t> anGrownDimIdx;
4284 16 : std::map<GDALDimension *, GUInt64> oMapDimToSize;
4285 18 : for (size_t i = 0; i < nDimCount; ++i)
4286 : {
4287 14 : auto oIter = oMapDimToSize.find(dims[i].get());
4288 14 : if (oIter != oMapDimToSize.end() && oIter->second != anNewDimSizes[i])
4289 : {
4290 2 : CPLError(CE_Failure, CPLE_AppDefined,
4291 : "Cannot resize a dimension referenced several times "
4292 : "to different sizes");
4293 4 : return false;
4294 : }
4295 12 : if (anNewDimSizes[i] != dims[i]->GetSize())
4296 : {
4297 9 : if (anNewDimSizes[i] < dims[i]->GetSize())
4298 : {
4299 2 : CPLError(CE_Failure, CPLE_NotSupported,
4300 : "Resize() does not support shrinking the array.");
4301 2 : return false;
4302 : }
4303 :
4304 7 : oMapDimToSize[dims[i].get()] = anNewDimSizes[i];
4305 7 : anGrownDimIdx.push_back(i);
4306 : }
4307 : else
4308 : {
4309 3 : oMapDimToSize[dims[i].get()] = dims[i]->GetSize();
4310 : }
4311 : }
4312 :
4313 4 : if (!anGrownDimIdx.empty())
4314 : {
4315 4 : CPLMutexHolderD(&hNCMutex);
4316 : // Query which netCDF dimensions have unlimited size
4317 4 : int nUnlimitedDimIds = 0;
4318 4 : nc_inq_unlimdims(m_gid, &nUnlimitedDimIds, nullptr);
4319 4 : std::vector<int> anUnlimitedDimIds(nUnlimitedDimIds);
4320 4 : nc_inq_unlimdims(m_gid, &nUnlimitedDimIds, anUnlimitedDimIds.data());
4321 4 : std::set<int> oSetUnlimitedDimId;
4322 8 : for (int idx : anUnlimitedDimIds)
4323 4 : oSetUnlimitedDimId.insert(idx);
4324 :
4325 : // Check that dimensions that need to grow are of unlimited size
4326 8 : for (size_t dimIdx : anGrownDimIdx)
4327 : {
4328 : auto netCDFDim =
4329 5 : std::dynamic_pointer_cast<netCDFDimension>(dims[dimIdx]);
4330 5 : if (!netCDFDim)
4331 : {
4332 0 : CPLAssert(false);
4333 : }
4334 5 : else if (oSetUnlimitedDimId.find(netCDFDim->GetId()) ==
4335 10 : oSetUnlimitedDimId.end())
4336 : {
4337 1 : CPLError(CE_Failure, CPLE_NotSupported,
4338 : "Resize() cannot grow dimension %d (%s) "
4339 : "as it is not created as UNLIMITED.",
4340 : static_cast<int>(dimIdx),
4341 1 : netCDFDim->GetName().c_str());
4342 1 : return false;
4343 : }
4344 : }
4345 9 : for (size_t i = 0; i < nDimCount; ++i)
4346 : {
4347 6 : if (anNewDimSizes[i] > dims[i]->GetSize())
4348 : {
4349 : auto netCDFDim =
4350 6 : std::dynamic_pointer_cast<netCDFDimension>(dims[i]);
4351 3 : if (!netCDFDim)
4352 : {
4353 0 : CPLAssert(false);
4354 : }
4355 : else
4356 : {
4357 3 : netCDFDim->SetSize(anNewDimSizes[i]);
4358 : }
4359 : }
4360 : }
4361 : }
4362 3 : return true;
4363 : }
4364 :
4365 : /************************************************************************/
4366 : /* Rename() */
4367 : /************************************************************************/
4368 :
4369 4 : bool netCDFVariable::Rename(const std::string &osNewName)
4370 : {
4371 4 : if (m_poShared->IsReadOnly())
4372 : {
4373 1 : CPLError(CE_Failure, CPLE_AppDefined,
4374 : "Rename() not supported on read-only file");
4375 1 : return false;
4376 : }
4377 3 : if (osNewName.empty())
4378 : {
4379 1 : CPLError(CE_Failure, CPLE_NotSupported, "Empty name not supported");
4380 1 : return false;
4381 : }
4382 :
4383 4 : CPLMutexHolderD(&hNCMutex);
4384 2 : m_poShared->SetDefineMode(true);
4385 :
4386 2 : int ret = nc_rename_var(m_gid, m_varid, osNewName.c_str());
4387 2 : NCDF_ERR(ret);
4388 2 : if (ret != NC_NOERR)
4389 1 : return false;
4390 :
4391 1 : BaseRename(osNewName);
4392 :
4393 1 : return true;
4394 : }
4395 :
4396 : /************************************************************************/
4397 : /* NotifyChildrenOfRenaming() */
4398 : /************************************************************************/
4399 :
4400 3 : void netCDFVariable::NotifyChildrenOfRenaming()
4401 : {
4402 6 : for (const auto &iter : m_oMapAttributes)
4403 3 : iter.second->ParentRenamed(m_osFullName);
4404 3 : }
4405 :
4406 : /************************************************************************/
4407 : /* netCDFVariable::GetRawBlockInfo() */
4408 : /************************************************************************/
4409 :
4410 0 : bool netCDFVariable::GetRawBlockInfo(const uint64_t *panBlockCoordinates,
4411 : GDALMDArrayRawBlockInfo &info) const
4412 : {
4413 0 : info.clear();
4414 :
4415 0 : int nFormat = 0;
4416 0 : if (m_poShared->m_poHDF5DS)
4417 : {
4418 0 : nFormat = NC_FORMAT_NETCDF4;
4419 : }
4420 : else
4421 : {
4422 0 : CPLMutexHolderD(&hNCMutex);
4423 0 : NCDF_ERR(nc_inq_format(m_poShared->GetCDFId(), &nFormat));
4424 : }
4425 0 : if (nFormat == NC_FORMAT_NETCDF4 || nFormat == NC_FORMAT_NETCDF4_CLASSIC)
4426 : {
4427 0 : if (!m_poShared->m_poHDF5DS)
4428 : {
4429 0 : const char *const apszAllowedDrivers[] = {"HDF5", nullptr};
4430 0 : m_poShared->m_poHDF5DS.reset(
4431 0 : GDALDataset::Open(m_poShared->GetFilename().c_str(),
4432 : GDAL_OF_MULTIDIM_RASTER, apszAllowedDrivers));
4433 : }
4434 0 : if (m_poShared->m_poHDF5DS)
4435 : {
4436 0 : if (!m_poShared->m_poHDF5CurArray ||
4437 0 : m_poShared->m_poHDF5CurArray->GetFullName() != GetFullName())
4438 : {
4439 0 : m_poShared->m_poHDF5CurArray =
4440 0 : m_poShared->m_poHDF5DS->GetRootGroup()
4441 0 : ->OpenMDArrayFromFullname(GetFullName());
4442 : }
4443 0 : if (m_poShared->m_poHDF5CurArray)
4444 : {
4445 0 : return m_poShared->m_poHDF5CurArray->GetRawBlockInfo(
4446 0 : panBlockCoordinates, info);
4447 : }
4448 : }
4449 0 : return false;
4450 : }
4451 :
4452 : // We should implement https://docs.unidata.ucar.edu/nug/2.0-draft/nc3_file_formats.html ...
4453 0 : CPLDebug("netCDF", "GetRawBlockInfo() not implemented for netCDF classic");
4454 :
4455 0 : return false;
4456 : }
4457 :
4458 : /************************************************************************/
4459 : /* retrieveAttributeParentName() */
4460 : /************************************************************************/
4461 :
4462 3344 : static CPLString retrieveAttributeParentName(int gid, int varid)
4463 : {
4464 6688 : auto groupName(NCDFGetGroupFullName(gid));
4465 3344 : if (varid == NC_GLOBAL)
4466 : {
4467 570 : if (groupName == "/")
4468 544 : return "/_GLOBAL_";
4469 52 : return groupName + "/_GLOBAL_";
4470 : }
4471 :
4472 5548 : return groupName + "/" + netCDFVariable::retrieveName(gid, varid);
4473 : }
4474 :
4475 : /************************************************************************/
4476 : /* netCDFAttribute() */
4477 : /************************************************************************/
4478 :
4479 1407 : netCDFAttribute::netCDFAttribute(
4480 : const std::shared_ptr<netCDFSharedResources> &poShared, int gid, int varid,
4481 1407 : const std::string &name)
4482 1407 : : GDALAbstractMDArray(retrieveAttributeParentName(gid, varid), name),
4483 1407 : GDALAttribute(retrieveAttributeParentName(gid, varid), name),
4484 4221 : m_poShared(poShared), m_gid(gid), m_varid(varid)
4485 : {
4486 2814 : CPLMutexHolderD(&hNCMutex);
4487 1407 : size_t nLen = 0;
4488 1407 : NCDF_ERR(nc_inq_atttype(m_gid, m_varid, GetName().c_str(), &m_nAttType));
4489 1407 : NCDF_ERR(nc_inq_attlen(m_gid, m_varid, GetName().c_str(), &nLen));
4490 1407 : if (m_nAttType == NC_CHAR)
4491 : {
4492 1129 : m_nTextLength = nLen;
4493 : }
4494 278 : else if (nLen > 1)
4495 : {
4496 186 : m_dims.emplace_back(std::make_shared<GDALDimension>(
4497 279 : std::string(), "length", std::string(), std::string(), nLen));
4498 : }
4499 1407 : }
4500 :
4501 : /************************************************************************/
4502 : /* netCDFAttribute() */
4503 : /************************************************************************/
4504 :
4505 265 : netCDFAttribute::netCDFAttribute(
4506 : const std::shared_ptr<netCDFSharedResources> &poShared, int gid, int varid,
4507 : const std::string &osName, const std::vector<GUInt64> &anDimensions,
4508 265 : const GDALExtendedDataType &oDataType, CSLConstList papszOptions)
4509 265 : : GDALAbstractMDArray(retrieveAttributeParentName(gid, varid), osName),
4510 265 : GDALAttribute(retrieveAttributeParentName(gid, varid), osName),
4511 795 : m_poShared(poShared), m_gid(gid), m_varid(varid)
4512 : {
4513 530 : CPLMutexHolderD(&hNCMutex);
4514 265 : m_bPerfectDataTypeMatch = true;
4515 265 : m_nAttType = CreateOrGetType(gid, oDataType);
4516 265 : m_dt.reset(new GDALExtendedDataType(oDataType));
4517 265 : if (!anDimensions.empty())
4518 : {
4519 72 : m_dims.emplace_back(std::make_shared<GDALDimension>(
4520 72 : std::string(), "length", std::string(), std::string(),
4521 72 : anDimensions[0]));
4522 : }
4523 :
4524 265 : const char *pszType = CSLFetchNameValueDef(papszOptions, "NC_TYPE", "");
4525 475 : if (oDataType.GetClass() == GEDTC_STRING && anDimensions.empty() &&
4526 210 : (EQUAL(pszType, "") || EQUAL(pszType, "NC_CHAR")))
4527 : {
4528 209 : m_nAttType = NC_CHAR;
4529 : }
4530 66 : else if (oDataType.GetNumericDataType() == GDT_Byte &&
4531 10 : EQUAL(CSLFetchNameValueDef(papszOptions, "NC_TYPE", ""),
4532 : "NC_BYTE"))
4533 : {
4534 1 : m_nAttType = NC_BYTE;
4535 : }
4536 57 : else if (oDataType.GetNumericDataType() == GDT_Int16 &&
4537 2 : EQUAL(CSLFetchNameValueDef(papszOptions, "NC_TYPE", ""),
4538 : "NC_BYTE"))
4539 : {
4540 2 : m_bPerfectDataTypeMatch = false;
4541 2 : m_nAttType = NC_BYTE;
4542 : }
4543 53 : else if (oDataType.GetNumericDataType() == GDT_Float64)
4544 : {
4545 9 : if (EQUAL(pszType, "NC_INT64"))
4546 : {
4547 2 : m_bPerfectDataTypeMatch = false;
4548 2 : m_nAttType = NC_INT64;
4549 : }
4550 7 : else if (EQUAL(pszType, "NC_UINT64"))
4551 : {
4552 1 : m_bPerfectDataTypeMatch = false;
4553 1 : m_nAttType = NC_UINT64;
4554 : }
4555 : }
4556 265 : }
4557 :
4558 : /************************************************************************/
4559 : /* ~netCDFAttribute() */
4560 : /************************************************************************/
4561 :
4562 3344 : netCDFAttribute::~netCDFAttribute()
4563 : {
4564 1672 : if (m_bValid)
4565 : {
4566 2651 : if (auto poParent = m_poParent.lock())
4567 981 : poParent->UnRegisterAttribute(this);
4568 : }
4569 3344 : }
4570 :
4571 : /************************************************************************/
4572 : /* Create() */
4573 : /************************************************************************/
4574 :
4575 : std::shared_ptr<netCDFAttribute>
4576 1407 : netCDFAttribute::Create(const std::shared_ptr<netCDFSharedResources> &poShared,
4577 : const std::shared_ptr<netCDFAttributeHolder> &poParent,
4578 : int gid, int varid, const std::string &name)
4579 : {
4580 : auto attr(std::shared_ptr<netCDFAttribute>(
4581 1407 : new netCDFAttribute(poShared, gid, varid, name)));
4582 1407 : attr->SetSelf(attr);
4583 1407 : attr->m_poParent = poParent;
4584 1407 : if (poParent)
4585 837 : poParent->RegisterAttribute(attr.get());
4586 1407 : return attr;
4587 : }
4588 :
4589 268 : std::shared_ptr<netCDFAttribute> netCDFAttribute::Create(
4590 : const std::shared_ptr<netCDFSharedResources> &poShared,
4591 : const std::shared_ptr<netCDFAttributeHolder> &poParent, int gid, int varid,
4592 : const std::string &osName, const std::vector<GUInt64> &anDimensions,
4593 : const GDALExtendedDataType &oDataType, CSLConstList papszOptions)
4594 : {
4595 268 : if (poShared->IsReadOnly())
4596 : {
4597 2 : CPLError(CE_Failure, CPLE_AppDefined,
4598 : "CreateAttribute() not supported on read-only file");
4599 2 : return nullptr;
4600 : }
4601 266 : if (anDimensions.size() > 1)
4602 : {
4603 1 : CPLError(CE_Failure, CPLE_NotSupported,
4604 : "Only 0 or 1-dimensional attribute are supported");
4605 1 : return nullptr;
4606 : }
4607 :
4608 265 : const char *apszOptions[2] = {nullptr, nullptr};
4609 280 : if (!poShared->IsNC4() && oDataType.GetClass() == GEDTC_NUMERIC &&
4610 280 : oDataType.GetNumericDataType() == GDT_Byte && !papszOptions)
4611 : {
4612 : // GDT_Byte would map to a NC_UBYTE datatype, which is not available in
4613 : // NC3 datasets
4614 1 : apszOptions[0] = "NC_TYPE=NC_BYTE";
4615 1 : papszOptions = apszOptions;
4616 : }
4617 :
4618 : auto attr(std::shared_ptr<netCDFAttribute>(new netCDFAttribute(
4619 530 : poShared, gid, varid, osName, anDimensions, oDataType, papszOptions)));
4620 265 : if (attr->m_nAttType == NC_NAT)
4621 0 : return nullptr;
4622 265 : attr->SetSelf(attr);
4623 265 : attr->m_poParent = poParent;
4624 265 : if (poParent)
4625 265 : poParent->RegisterAttribute(attr.get());
4626 265 : return attr;
4627 : }
4628 :
4629 : /************************************************************************/
4630 : /* GetDataType() */
4631 : /************************************************************************/
4632 :
4633 5376 : const GDALExtendedDataType &netCDFAttribute::GetDataType() const
4634 : {
4635 5376 : if (m_dt)
4636 4163 : return *m_dt;
4637 2426 : CPLMutexHolderD(&hNCMutex);
4638 :
4639 1213 : if (m_nAttType == NC_CHAR)
4640 : {
4641 1031 : m_dt.reset(
4642 1031 : new GDALExtendedDataType(GDALExtendedDataType::CreateString()));
4643 : }
4644 : else
4645 : {
4646 182 : m_dt.reset(new GDALExtendedDataType(
4647 182 : GDALExtendedDataType::Create(GDT_Unknown)));
4648 182 : BuildDataType(m_gid, m_varid, m_nAttType, m_dt,
4649 182 : m_bPerfectDataTypeMatch);
4650 : }
4651 :
4652 1213 : return *m_dt;
4653 : }
4654 :
4655 : /************************************************************************/
4656 : /* IRead() */
4657 : /************************************************************************/
4658 :
4659 1231 : bool netCDFAttribute::IRead(const GUInt64 *arrayStartIdx, const size_t *count,
4660 : const GInt64 *arrayStep,
4661 : const GPtrDiff_t *bufferStride,
4662 : const GDALExtendedDataType &bufferDataType,
4663 : void *pDstBuffer) const
4664 : {
4665 1231 : if (!CheckValidAndErrorOutIfNot())
4666 0 : return false;
4667 2462 : CPLMutexHolderD(&hNCMutex);
4668 :
4669 1231 : if (m_nAttType == NC_STRING)
4670 : {
4671 55 : CPLAssert(GetDataType().GetClass() == GEDTC_STRING);
4672 : std::vector<char *> apszStrings(
4673 110 : static_cast<size_t>(GetTotalElementsCount()));
4674 55 : int ret = nc_get_att_string(m_gid, m_varid, GetName().c_str(),
4675 55 : &apszStrings[0]);
4676 55 : NCDF_ERR(ret);
4677 55 : if (ret != NC_NOERR)
4678 0 : return false;
4679 55 : if (m_dims.empty())
4680 : {
4681 50 : const char *pszStr = apszStrings[0];
4682 50 : GDALExtendedDataType::CopyValue(&pszStr, GetDataType(), pDstBuffer,
4683 : bufferDataType);
4684 : }
4685 : else
4686 : {
4687 5 : GByte *pabyDstBuffer = static_cast<GByte *>(pDstBuffer);
4688 14 : for (size_t i = 0; i < count[0]; i++)
4689 : {
4690 9 : auto srcIdx =
4691 9 : static_cast<size_t>(arrayStartIdx[0] + arrayStep[0] * i);
4692 9 : const char *pszStr = apszStrings[srcIdx];
4693 9 : GDALExtendedDataType::CopyValue(&pszStr, GetDataType(),
4694 : pabyDstBuffer, bufferDataType);
4695 9 : pabyDstBuffer += sizeof(char *) * bufferStride[0];
4696 : }
4697 : }
4698 55 : nc_free_string(apszStrings.size(), &apszStrings[0]);
4699 55 : return true;
4700 : }
4701 :
4702 1176 : if (m_nAttType == NC_CHAR)
4703 : {
4704 1037 : CPLAssert(GetDataType().GetClass() == GEDTC_STRING);
4705 1037 : CPLAssert(m_dims.empty());
4706 1037 : if (bufferDataType != GetDataType())
4707 : {
4708 0 : std::string osStr;
4709 0 : osStr.resize(m_nTextLength);
4710 : int ret =
4711 0 : nc_get_att_text(m_gid, m_varid, GetName().c_str(), &osStr[0]);
4712 0 : NCDF_ERR(ret);
4713 0 : if (ret != NC_NOERR)
4714 0 : return false;
4715 0 : const char *pszStr = osStr.c_str();
4716 0 : GDALExtendedDataType::CopyValue(&pszStr, GetDataType(), pDstBuffer,
4717 : bufferDataType);
4718 : }
4719 : else
4720 : {
4721 1037 : char *pszStr = static_cast<char *>(CPLCalloc(1, m_nTextLength + 1));
4722 : int ret =
4723 1037 : nc_get_att_text(m_gid, m_varid, GetName().c_str(), pszStr);
4724 1037 : NCDF_ERR(ret);
4725 1037 : if (ret != NC_NOERR)
4726 : {
4727 1 : CPLFree(pszStr);
4728 1 : return false;
4729 : }
4730 1036 : *static_cast<char **>(pDstBuffer) = pszStr;
4731 : }
4732 1036 : return true;
4733 : }
4734 :
4735 139 : const auto &dt(GetDataType());
4736 274 : if (dt.GetClass() == GEDTC_NUMERIC &&
4737 135 : dt.GetNumericDataType() == GDT_Unknown)
4738 : {
4739 0 : return false;
4740 : }
4741 :
4742 139 : CPLAssert(dt.GetClass() != GEDTC_STRING);
4743 259 : const bool bFastPath = ((m_dims.size() == 1 && arrayStartIdx[0] == 0 &&
4744 60 : count[0] == m_dims[0]->GetSize() &&
4745 139 : arrayStep[0] == 1 && bufferStride[0] == 1) ||
4746 80 : m_dims.empty()) &&
4747 351 : m_bPerfectDataTypeMatch && bufferDataType == dt &&
4748 73 : dt.GetSize() > 0;
4749 139 : if (bFastPath)
4750 : {
4751 73 : int ret = nc_get_att(m_gid, m_varid, GetName().c_str(), pDstBuffer);
4752 73 : NCDF_ERR(ret);
4753 73 : if (ret == NC_NOERR)
4754 : {
4755 73 : ConvertNCStringsToCPLStrings(static_cast<GByte *>(pDstBuffer), dt);
4756 : }
4757 73 : return ret == NC_NOERR;
4758 : }
4759 :
4760 : const auto nElementSize =
4761 66 : GetNCTypeSize(dt, m_bPerfectDataTypeMatch, m_nAttType);
4762 66 : if (nElementSize == 0)
4763 0 : return false;
4764 66 : const auto nOutputDTSize = bufferDataType.GetSize();
4765 66 : std::vector<GByte> abyBuffer(static_cast<size_t>(GetTotalElementsCount()) *
4766 198 : nElementSize);
4767 66 : int ret = nc_get_att(m_gid, m_varid, GetName().c_str(), &abyBuffer[0]);
4768 66 : NCDF_ERR(ret);
4769 66 : if (ret != NC_NOERR)
4770 0 : return false;
4771 :
4772 : GByte *pabySrcBuffer =
4773 66 : m_dims.empty()
4774 66 : ? abyBuffer.data()
4775 41 : : abyBuffer.data() +
4776 41 : static_cast<size_t>(arrayStartIdx[0]) * nElementSize;
4777 66 : GByte *pabyDstBuffer = static_cast<GByte *>(pDstBuffer);
4778 216 : for (size_t i = 0; i < (m_dims.empty() ? 1 : count[0]); i++)
4779 : {
4780 : GByte abyTmpBuffer[sizeof(double)];
4781 150 : const GByte *pabySrcElement = pabySrcBuffer;
4782 150 : if (!m_bPerfectDataTypeMatch)
4783 : {
4784 7 : if (m_nAttType == NC_BYTE)
4785 : {
4786 3 : short s =
4787 3 : reinterpret_cast<const signed char *>(pabySrcBuffer)[0];
4788 3 : memcpy(abyTmpBuffer, &s, sizeof(s));
4789 3 : pabySrcElement = abyTmpBuffer;
4790 : }
4791 4 : else if (m_nAttType == NC_INT64)
4792 : {
4793 3 : double v = static_cast<double>(
4794 3 : reinterpret_cast<const GInt64 *>(pabySrcBuffer)[0]);
4795 3 : memcpy(abyTmpBuffer, &v, sizeof(v));
4796 3 : pabySrcElement = abyTmpBuffer;
4797 : }
4798 1 : else if (m_nAttType == NC_UINT64)
4799 : {
4800 1 : double v = static_cast<double>(
4801 1 : reinterpret_cast<const GUInt64 *>(pabySrcBuffer)[0]);
4802 1 : memcpy(abyTmpBuffer, &v, sizeof(v));
4803 1 : pabySrcElement = abyTmpBuffer;
4804 : }
4805 : else
4806 : {
4807 0 : CPLAssert(false);
4808 : }
4809 : }
4810 150 : GDALExtendedDataType::CopyValue(pabySrcElement, dt, pabyDstBuffer,
4811 : bufferDataType);
4812 150 : FreeNCStrings(pabySrcBuffer, dt);
4813 150 : if (!m_dims.empty())
4814 : {
4815 125 : pabySrcBuffer +=
4816 125 : static_cast<std::ptrdiff_t>(arrayStep[0] * nElementSize);
4817 125 : pabyDstBuffer += nOutputDTSize * bufferStride[0];
4818 : }
4819 : }
4820 :
4821 66 : return true;
4822 : }
4823 :
4824 : /************************************************************************/
4825 : /* IWrite() */
4826 : /************************************************************************/
4827 :
4828 270 : bool netCDFAttribute::IWrite(const GUInt64 *arrayStartIdx, const size_t *count,
4829 : const GInt64 *arrayStep,
4830 : const GPtrDiff_t *bufferStride,
4831 : const GDALExtendedDataType &bufferDataType,
4832 : const void *pSrcBuffer)
4833 : {
4834 270 : if (!CheckValidAndErrorOutIfNot())
4835 0 : return false;
4836 540 : CPLMutexHolderD(&hNCMutex);
4837 :
4838 307 : if (m_dims.size() == 1 &&
4839 37 : (arrayStartIdx[0] != 0 || count[0] != m_dims[0]->GetSize() ||
4840 37 : arrayStep[0] != 1))
4841 : {
4842 0 : CPLError(CE_Failure, CPLE_NotSupported,
4843 : "Only contiguous writing of attribute values supported");
4844 0 : return false;
4845 : }
4846 :
4847 270 : m_poShared->SetDefineMode(true);
4848 :
4849 270 : const auto &dt(GetDataType());
4850 270 : if (m_nAttType == NC_STRING)
4851 : {
4852 30 : CPLAssert(dt.GetClass() == GEDTC_STRING);
4853 30 : if (m_dims.empty())
4854 : {
4855 2 : char *pszStr = nullptr;
4856 : const char *pszStrConst;
4857 2 : if (bufferDataType != dt)
4858 : {
4859 1 : GDALExtendedDataType::CopyValue(pSrcBuffer, bufferDataType,
4860 : &pszStr, dt);
4861 1 : pszStrConst = pszStr;
4862 : }
4863 : else
4864 : {
4865 1 : memcpy(&pszStrConst, pSrcBuffer, sizeof(const char *));
4866 : }
4867 2 : int ret = nc_put_att_string(m_gid, m_varid, GetName().c_str(), 1,
4868 : &pszStrConst);
4869 2 : CPLFree(pszStr);
4870 2 : NCDF_ERR(ret);
4871 2 : if (ret != NC_NOERR)
4872 0 : return false;
4873 2 : return true;
4874 : }
4875 :
4876 : int ret;
4877 28 : if (bufferDataType != dt)
4878 : {
4879 2 : std::vector<char *> apszStrings(count[0]);
4880 1 : const auto nInputDTSize = bufferDataType.GetSize();
4881 1 : const GByte *pabySrcBuffer = static_cast<const GByte *>(pSrcBuffer);
4882 3 : for (size_t i = 0; i < count[0]; i++)
4883 : {
4884 2 : GDALExtendedDataType::CopyValue(pabySrcBuffer, bufferDataType,
4885 2 : &apszStrings[i], dt);
4886 2 : pabySrcBuffer += nInputDTSize * bufferStride[0];
4887 : }
4888 1 : ret = nc_put_att_string(m_gid, m_varid, GetName().c_str(), count[0],
4889 1 : const_cast<const char **>(&apszStrings[0]));
4890 3 : for (size_t i = 0; i < count[0]; i++)
4891 : {
4892 2 : CPLFree(apszStrings[i]);
4893 : }
4894 : }
4895 : else
4896 : {
4897 : const char **ppszStr;
4898 27 : memcpy(&ppszStr, &pSrcBuffer, sizeof(const char **));
4899 27 : ret = nc_put_att_string(m_gid, m_varid, GetName().c_str(), count[0],
4900 : ppszStr);
4901 : }
4902 28 : NCDF_ERR(ret);
4903 28 : if (ret != NC_NOERR)
4904 0 : return false;
4905 28 : return true;
4906 : }
4907 :
4908 240 : if (m_nAttType == NC_CHAR)
4909 : {
4910 215 : CPLAssert(dt.GetClass() == GEDTC_STRING);
4911 215 : CPLAssert(m_dims.empty());
4912 215 : char *pszStr = nullptr;
4913 : const char *pszStrConst;
4914 215 : if (bufferDataType != dt)
4915 : {
4916 1 : GDALExtendedDataType::CopyValue(pSrcBuffer, bufferDataType, &pszStr,
4917 : dt);
4918 1 : pszStrConst = pszStr;
4919 : }
4920 : else
4921 : {
4922 214 : memcpy(&pszStrConst, pSrcBuffer, sizeof(const char *));
4923 : }
4924 215 : m_nTextLength = pszStrConst ? strlen(pszStrConst) : 0;
4925 215 : int ret = nc_put_att_text(m_gid, m_varid, GetName().c_str(),
4926 : m_nTextLength, pszStrConst);
4927 215 : CPLFree(pszStr);
4928 215 : NCDF_ERR(ret);
4929 215 : if (ret != NC_NOERR)
4930 1 : return false;
4931 214 : return true;
4932 : }
4933 :
4934 49 : if (dt.GetClass() == GEDTC_NUMERIC &&
4935 24 : dt.GetNumericDataType() == GDT_Unknown)
4936 : {
4937 0 : return false;
4938 : }
4939 :
4940 25 : CPLAssert(dt.GetClass() != GEDTC_STRING);
4941 : const bool bFastPath =
4942 50 : ((m_dims.size() == 1 && bufferStride[0] == 1) || m_dims.empty()) &&
4943 50 : m_bPerfectDataTypeMatch && bufferDataType == dt && dt.GetSize() > 0;
4944 25 : if (bFastPath)
4945 : {
4946 11 : int ret = nc_put_att(m_gid, m_varid, GetName().c_str(), m_nAttType,
4947 11 : m_dims.empty() ? 1 : count[0], pSrcBuffer);
4948 11 : NCDF_ERR(ret);
4949 11 : return ret == NC_NOERR;
4950 : }
4951 :
4952 : const auto nElementSize =
4953 14 : GetNCTypeSize(dt, m_bPerfectDataTypeMatch, m_nAttType);
4954 14 : if (nElementSize == 0)
4955 0 : return false;
4956 14 : const auto nInputDTSize = bufferDataType.GetSize();
4957 14 : std::vector<GByte> abyBuffer(static_cast<size_t>(GetTotalElementsCount()) *
4958 28 : nElementSize);
4959 :
4960 14 : const GByte *pabySrcBuffer = static_cast<const GByte *>(pSrcBuffer);
4961 14 : auto pabyDstBuffer = &abyBuffer[0];
4962 31 : for (size_t i = 0; i < (m_dims.empty() ? 1 : count[0]); i++)
4963 : {
4964 17 : if (!m_bPerfectDataTypeMatch)
4965 : {
4966 7 : if (m_nAttType == NC_BYTE)
4967 : {
4968 : short s;
4969 3 : GDALExtendedDataType::CopyValue(pabySrcBuffer, bufferDataType,
4970 : &s, dt);
4971 3 : signed char c = static_cast<signed char>(s);
4972 3 : memcpy(pabyDstBuffer, &c, sizeof(c));
4973 : }
4974 4 : else if (m_nAttType == NC_INT64)
4975 : {
4976 : double d;
4977 3 : GDALExtendedDataType::CopyValue(pabySrcBuffer, bufferDataType,
4978 : &d, dt);
4979 3 : GInt64 v = static_cast<GInt64>(d);
4980 3 : memcpy(pabyDstBuffer, &v, sizeof(v));
4981 : }
4982 1 : else if (m_nAttType == NC_UINT64)
4983 : {
4984 : double d;
4985 1 : GDALExtendedDataType::CopyValue(pabySrcBuffer, bufferDataType,
4986 : &d, dt);
4987 1 : GUInt64 v = static_cast<GUInt64>(d);
4988 1 : memcpy(pabyDstBuffer, &v, sizeof(v));
4989 : }
4990 : else
4991 : {
4992 0 : CPLAssert(false);
4993 : }
4994 : }
4995 : else
4996 : {
4997 10 : GDALExtendedDataType::CopyValue(pabySrcBuffer, bufferDataType,
4998 : pabyDstBuffer, dt);
4999 : }
5000 :
5001 17 : if (!m_dims.empty())
5002 : {
5003 7 : pabySrcBuffer += nInputDTSize * bufferStride[0];
5004 7 : pabyDstBuffer += nElementSize;
5005 : }
5006 : }
5007 :
5008 14 : int ret = nc_put_att(m_gid, m_varid, GetName().c_str(), m_nAttType,
5009 14 : m_dims.empty() ? 1 : count[0], &abyBuffer[0]);
5010 14 : NCDF_ERR(ret);
5011 14 : return ret == NC_NOERR;
5012 : }
5013 :
5014 : /************************************************************************/
5015 : /* Rename() */
5016 : /************************************************************************/
5017 :
5018 8 : bool netCDFAttribute::Rename(const std::string &osNewName)
5019 : {
5020 8 : if (!CheckValidAndErrorOutIfNot())
5021 2 : return false;
5022 6 : if (m_poShared->IsReadOnly())
5023 : {
5024 1 : CPLError(CE_Failure, CPLE_AppDefined,
5025 : "Rename() not supported on read-only file");
5026 1 : return false;
5027 : }
5028 5 : if (osNewName.empty())
5029 : {
5030 1 : CPLError(CE_Failure, CPLE_NotSupported, "Empty name not supported");
5031 1 : return false;
5032 : }
5033 8 : CPLMutexHolderD(&hNCMutex);
5034 4 : m_poShared->SetDefineMode(true);
5035 :
5036 : int ret =
5037 4 : nc_rename_att(m_gid, m_varid, m_osName.c_str(), osNewName.c_str());
5038 4 : NCDF_ERR(ret);
5039 4 : if (ret != NC_NOERR)
5040 2 : return false;
5041 :
5042 2 : BaseRename(osNewName);
5043 :
5044 2 : return true;
5045 : }
5046 :
5047 : /************************************************************************/
5048 : /* OpenMultiDim() */
5049 : /************************************************************************/
5050 :
5051 262 : GDALDataset *netCDFDataset::OpenMultiDim(GDALOpenInfo *poOpenInfo)
5052 : {
5053 :
5054 524 : CPLMutexHolderD(&hNCMutex);
5055 :
5056 262 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
5057 : // GDALDataset own mutex.
5058 262 : netCDFDataset *poDS = new netCDFDataset();
5059 262 : CPLAcquireMutex(hNCMutex, 1000.0);
5060 :
5061 524 : std::string osFilename;
5062 :
5063 : // For example to open DAP datasets
5064 262 : if (STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
5065 : {
5066 0 : osFilename = poOpenInfo->pszFilename + strlen("NETCDF:");
5067 0 : if (!osFilename.empty() && osFilename[0] == '"' &&
5068 0 : osFilename.back() == '"')
5069 : {
5070 0 : osFilename = osFilename.substr(1, osFilename.size() - 2);
5071 : }
5072 : }
5073 : else
5074 : {
5075 262 : osFilename = poOpenInfo->pszFilename;
5076 262 : poDS->eFormat =
5077 262 : netCDFIdentifyFormat(poOpenInfo, /* bCheckExt = */ true);
5078 : }
5079 :
5080 262 : poDS->SetDescription(poOpenInfo->pszFilename);
5081 262 : poDS->papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
5082 :
5083 : #ifdef ENABLE_NCDUMP
5084 262 : bool bFileToDestroyAtClosing = false;
5085 262 : const char *pszHeader =
5086 : reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
5087 262 : if (poOpenInfo->fpL != nullptr && STARTS_WITH(pszHeader, "netcdf ") &&
5088 1 : strstr(pszHeader, "dimensions:") && strstr(pszHeader, "variables:"))
5089 : {
5090 : // By default create a temporary file that will be destroyed,
5091 : // unless NETCDF_TMP_FILE is defined. Can be useful to see which
5092 : // netCDF file has been generated from a potential fuzzed input.
5093 1 : osFilename = CPLGetConfigOption("NETCDF_TMP_FILE", "");
5094 1 : if (osFilename.empty())
5095 : {
5096 1 : bFileToDestroyAtClosing = true;
5097 1 : osFilename = CPLGenerateTempFilenameSafe("netcdf_tmp");
5098 : }
5099 1 : if (!netCDFDatasetCreateTempFile(NCDF_FORMAT_NC4, osFilename.c_str(),
5100 : poOpenInfo->fpL))
5101 : {
5102 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll
5103 : // deadlock with GDALDataset own mutex.
5104 0 : delete poDS;
5105 0 : CPLAcquireMutex(hNCMutex, 1000.0);
5106 0 : return nullptr;
5107 : }
5108 1 : poDS->eFormat = NCDF_FORMAT_NC4;
5109 : }
5110 : #endif
5111 :
5112 : // Try opening the dataset.
5113 : #if defined(NCDF_DEBUG) && defined(ENABLE_UFFD)
5114 : CPLDebug("GDAL_netCDF", "calling nc_open_mem(%s)", osFilename.c_str());
5115 : #elif defined(NCDF_DEBUG) && !defined(ENABLE_UFFD)
5116 : CPLDebug("GDAL_netCDF", "calling nc_open(%s)", osFilename.c_str());
5117 : #endif
5118 262 : int cdfid = -1;
5119 262 : const int nMode =
5120 262 : (poOpenInfo->nOpenFlags & GDAL_OF_UPDATE) != 0 ? NC_WRITE : NC_NOWRITE;
5121 524 : CPLString osFilenameForNCOpen(osFilename);
5122 : #ifdef _WIN32
5123 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
5124 : {
5125 : char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
5126 : osFilenameForNCOpen = pszTemp;
5127 : CPLFree(pszTemp);
5128 : }
5129 : #endif
5130 262 : int status2 = -1;
5131 :
5132 524 : auto poSharedResources(std::make_shared<netCDFSharedResources>(osFilename));
5133 : #ifdef ENABLE_NCDUMP
5134 262 : poSharedResources->m_bFileToDestroyAtClosing = bFileToDestroyAtClosing;
5135 : #endif
5136 :
5137 266 : if (STARTS_WITH(osFilenameForNCOpen, "/vsimem/") &&
5138 4 : poOpenInfo->eAccess == GA_ReadOnly)
5139 : {
5140 4 : vsi_l_offset nLength = 0;
5141 4 : poDS->fpVSIMEM = VSIFOpenL(osFilenameForNCOpen, "rb");
5142 4 : if (poDS->fpVSIMEM)
5143 : {
5144 : // We assume that the file will not be modified. If it is, then
5145 : // pabyBuffer might become invalid.
5146 : GByte *pabyBuffer =
5147 4 : VSIGetMemFileBuffer(osFilenameForNCOpen, &nLength, false);
5148 4 : if (pabyBuffer)
5149 : {
5150 4 : status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen),
5151 : nMode, static_cast<size_t>(nLength),
5152 : pabyBuffer, &cdfid);
5153 : }
5154 : }
5155 : }
5156 : else
5157 : {
5158 : #ifdef ENABLE_UFFD
5159 258 : bool bVsiFile = !strncmp(osFilenameForNCOpen, "/vsi", strlen("/vsi"));
5160 258 : bool bReadOnly = (poOpenInfo->eAccess == GA_ReadOnly);
5161 258 : void *pVma = nullptr;
5162 258 : uint64_t nVmaSize = 0;
5163 258 : cpl_uffd_context *pCtx = nullptr;
5164 :
5165 258 : if (bVsiFile && bReadOnly && CPLIsUserFaultMappingSupported())
5166 1 : pCtx = CPLCreateUserFaultMapping(osFilenameForNCOpen, &pVma,
5167 : &nVmaSize);
5168 258 : if (pCtx != nullptr && pVma != nullptr && nVmaSize > 0)
5169 : {
5170 : // netCDF code, at least for netCDF 4.7.0, is confused by filenames
5171 : // like /vsicurl/http[s]://example.com/foo.nc, so just pass the
5172 : // final part
5173 1 : status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen), nMode,
5174 : static_cast<size_t>(nVmaSize), pVma, &cdfid);
5175 : }
5176 : else
5177 257 : status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
5178 258 : poSharedResources->m_pUffdCtx = pCtx;
5179 : #else
5180 : status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
5181 : #endif
5182 : }
5183 262 : if (status2 != NC_NOERR)
5184 : {
5185 : #ifdef NCDF_DEBUG
5186 : CPLDebug("GDAL_netCDF", "error opening");
5187 : #endif
5188 3 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
5189 : // with GDALDataset own mutex.
5190 3 : delete poDS;
5191 3 : CPLAcquireMutex(hNCMutex, 1000.0);
5192 3 : return nullptr;
5193 : }
5194 : #ifdef NCDF_DEBUG
5195 : CPLDebug("GDAL_netCDF", "got cdfid=%d", cdfid);
5196 : #endif
5197 :
5198 : #if defined(ENABLE_NCDUMP) && !defined(_WIN32)
5199 : // Try to destroy the temporary file right now on Unix
5200 259 : if (poSharedResources->m_bFileToDestroyAtClosing)
5201 : {
5202 1 : if (VSIUnlink(poSharedResources->m_osFilename) == 0)
5203 : {
5204 1 : poSharedResources->m_bFileToDestroyAtClosing = false;
5205 : }
5206 : }
5207 : #endif
5208 259 : poSharedResources->m_bReadOnly = nMode == NC_NOWRITE;
5209 259 : poSharedResources->m_bIsNC4 =
5210 259 : poDS->eFormat == NCDF_FORMAT_NC4 || poDS->eFormat == NCDF_FORMAT_NC4C;
5211 259 : poSharedResources->m_cdfid = cdfid;
5212 259 : poSharedResources->m_fpVSIMEM = poDS->fpVSIMEM;
5213 259 : poDS->fpVSIMEM = nullptr;
5214 :
5215 : // Is this a real netCDF file?
5216 : int ndims;
5217 : int ngatts;
5218 : int nvars;
5219 : int unlimdimid;
5220 259 : int status = nc_inq(cdfid, &ndims, &nvars, &ngatts, &unlimdimid);
5221 259 : if (status != NC_NOERR)
5222 : {
5223 0 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
5224 : // with GDALDataset own mutex.
5225 0 : delete poDS;
5226 0 : CPLAcquireMutex(hNCMutex, 1000.0);
5227 0 : return nullptr;
5228 : }
5229 :
5230 259 : poDS->m_poRootGroup = netCDFGroup::Create(poSharedResources, cdfid);
5231 :
5232 259 : poDS->TryLoadXML();
5233 :
5234 259 : return poDS;
5235 : }
5236 :
5237 : /************************************************************************/
5238 : /* GetRootGroup() */
5239 : /************************************************************************/
5240 :
5241 479 : std::shared_ptr<GDALGroup> netCDFDataset::GetRootGroup() const
5242 : {
5243 479 : return m_poRootGroup;
5244 : }
5245 :
5246 : /************************************************************************/
5247 : /* CreateMultiDimensional() */
5248 : /************************************************************************/
5249 :
5250 : GDALDataset *
5251 107 : netCDFDataset::CreateMultiDimensional(const char *pszFilename,
5252 : CSLConstList /* papszRootGroupOptions */,
5253 : CSLConstList papszOptions)
5254 : {
5255 214 : CPLMutexHolderD(&hNCMutex);
5256 :
5257 107 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with
5258 : // GDALDataset own mutex.
5259 107 : netCDFDataset *poDS = new netCDFDataset();
5260 107 : CPLAcquireMutex(hNCMutex, 1000.0);
5261 107 : poDS->eAccess = GA_Update;
5262 107 : poDS->osFilename = pszFilename;
5263 :
5264 : // process options.
5265 107 : poDS->papszCreationOptions = CSLDuplicate(papszOptions);
5266 107 : if (CSLFetchNameValue(papszOptions, "FORMAT") == nullptr)
5267 : {
5268 106 : poDS->papszCreationOptions =
5269 106 : CSLSetNameValue(poDS->papszCreationOptions, "FORMAT", "NC4");
5270 : }
5271 107 : poDS->ProcessCreationOptions();
5272 :
5273 : // Create the dataset.
5274 214 : CPLString osFilenameForNCCreate(pszFilename);
5275 : #ifdef _WIN32
5276 : if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
5277 : {
5278 : char *pszTemp =
5279 : CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
5280 : osFilenameForNCCreate = pszTemp;
5281 : CPLFree(pszTemp);
5282 : }
5283 : #endif
5284 107 : int cdfid = 0;
5285 107 : int status = nc_create(osFilenameForNCCreate, poDS->nCreateMode, &cdfid);
5286 107 : if (status != NC_NOERR)
5287 : {
5288 2 : CPLError(CE_Failure, CPLE_OpenFailed,
5289 : "Unable to create netCDF file %s (Error code %d): %s .",
5290 : pszFilename, status, nc_strerror(status));
5291 2 : CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock
5292 : // with GDALDataset own mutex.
5293 2 : delete poDS;
5294 2 : CPLAcquireMutex(hNCMutex, 1000.0);
5295 2 : return nullptr;
5296 : }
5297 :
5298 : auto poSharedResources(
5299 105 : std::make_shared<netCDFSharedResources>(pszFilename));
5300 105 : poSharedResources->m_cdfid = cdfid;
5301 105 : poSharedResources->m_bReadOnly = false;
5302 105 : poSharedResources->m_bDefineMode = true;
5303 105 : poSharedResources->m_bIsNC4 =
5304 105 : poDS->eFormat == NCDF_FORMAT_NC4 || poDS->eFormat == NCDF_FORMAT_NC4C;
5305 : poDS->m_poRootGroup =
5306 105 : netCDFGroup::Create(poSharedResources, nullptr, cdfid);
5307 105 : const char *pszConventions = CSLFetchNameValueDef(
5308 : papszOptions, "CONVENTIONS", NCDF_CONVENTIONS_CF_V1_6);
5309 105 : if (!EQUAL(pszConventions, ""))
5310 : {
5311 105 : auto poAttr = poDS->m_poRootGroup->CreateAttribute(
5312 315 : NCDF_CONVENTIONS, {}, GDALExtendedDataType::CreateString());
5313 105 : if (poAttr)
5314 105 : poAttr->Write(pszConventions);
5315 : }
5316 :
5317 105 : return poDS;
5318 : }
|