Coverage Report

Created: 2025-06-09 07:42

/src/gdal/frmts/netcdf/netcdfdataset.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  netCDF read/write Driver
4
 * Purpose:  GDAL bindings over netCDF library.
5
 * Author:   Frank Warmerdam, warmerdam@pobox.com
6
 *           Even Rouault <even.rouault at spatialys.com>
7
 *
8
 ******************************************************************************
9
 * Copyright (c) 2004, Frank Warmerdam
10
 * Copyright (c) 2007-2016, Even Rouault <even.rouault at spatialys.com>
11
 * Copyright (c) 2010, Kyle Shannon <kyle at pobox dot com>
12
 * Copyright (c) 2021, CLS
13
 *
14
 * SPDX-License-Identifier: MIT
15
 ****************************************************************************/
16
17
#include "cpl_port.h"
18
19
#include <array>
20
#include <cassert>
21
#include <cctype>
22
#include <cerrno>
23
#include <climits>
24
#include <cmath>
25
#include <cstdio>
26
#include <cstdlib>
27
#include <cstring>
28
#include <ctime>
29
#include <algorithm>
30
#include <limits>
31
#include <map>
32
#include <mutex>
33
#include <set>
34
#include <queue>
35
#include <string>
36
#include <tuple>
37
#include <utility>
38
#include <vector>
39
40
// Must be included after standard includes, otherwise VS2015 fails when
41
// including <ctime>
42
#include "netcdfdataset.h"
43
#include "netcdfdrivercore.h"
44
#include "netcdfsg.h"
45
#include "netcdfuffd.h"
46
47
#include "netcdf_mem.h"
48
49
#include "cpl_conv.h"
50
#include "cpl_error.h"
51
#include "cpl_float.h"
52
#include "cpl_json.h"
53
#include "cpl_minixml.h"
54
#include "cpl_multiproc.h"
55
#include "cpl_progress.h"
56
#include "cpl_time.h"
57
#include "gdal.h"
58
#include "gdal_frmts.h"
59
#include "gdal_priv_templates.hpp"
60
#include "ogr_core.h"
61
#include "ogr_srs_api.h"
62
63
// netCDF 4.8 switched to expecting filenames in UTF-8 on Windows
64
// But with netCDF 4.9 and https://github.com/Unidata/netcdf-c/pull/2277/,
65
// this is apparently back to expecting filenames in current codepage...
66
// Detect netCDF 4.8 with NC_ENCZARR
67
// Detect netCDF 4.9 with NC_NOATTCREORD
68
#if defined(NC_ENCZARR) && !defined(NC_NOATTCREORD)
69
#define NETCDF_USES_UTF8
70
#endif
71
72
// Internal function declarations.
73
74
static bool NCDFIsGDALVersionGTE(const char *pszVersion, int nTarget);
75
76
static void
77
NCDFAddGDALHistory(int fpImage, const char *pszFilename, bool bWriteGDALVersion,
78
                   bool bWriteGDALHistory, const char *pszOldHist,
79
                   const char *pszFunctionName,
80
                   const char *pszCFVersion = GDAL_DEFAULT_NCDF_CONVENTIONS);
81
82
static void NCDFAddHistory(int fpImage, const char *pszAddHist,
83
                           const char *pszOldHist);
84
85
static CPLErr NCDFSafeStrcat(char **ppszDest, const char *pszSrc,
86
                             size_t *nDestSize);
87
88
// Var / attribute helper functions.
89
static CPLErr NCDFPutAttr(int nCdfId, int nVarId, const char *pszAttrName,
90
                          const char *pszValue);
91
92
// Replace this where used.
93
static CPLErr NCDFGet1DVar(int nCdfId, int nVarId, char **pszValue);
94
static CPLErr NCDFPut1DVar(int nCdfId, int nVarId, const char *pszValue);
95
96
// Replace this where used.
97
static char **NCDFTokenizeArray(const char *pszValue);
98
static void CopyMetadata(GDALDataset *poSrcDS, GDALRasterBand *poSrcBand,
99
                         GDALRasterBand *poDstBand, int fpImage, int CDFVarID,
100
                         const char *pszMatchPrefix = nullptr);
101
102
// NetCDF-4 groups helper functions.
103
// They all work also for NetCDF-3 files which are considered as
104
// NetCDF-4 file with only one group.
105
static CPLErr NCDFOpenSubDataset(int nCdfId, const char *pszSubdatasetName,
106
                                 int *pnGroupId, int *pnVarId);
107
static CPLErr NCDFGetVisibleDims(int nGroupId, int *pnDims, int **ppanDimIds);
108
static CPLErr NCDFGetSubGroups(int nGroupId, int *pnSubGroups,
109
                               int **ppanSubGroupIds);
110
static CPLErr NCDFGetGroupFullName(int nGroupId, char **ppszFullName,
111
                                   bool bNC3Compat = true);
112
static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId, char **ppszFullName,
113
                                 bool bNC3Compat = true);
114
static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId);
115
116
static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,
117
                                     char **ppszFullName,
118
                                     bool bMandatory = false);
119
static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,
120
                                const char *pszAtt, int *pnAtt,
121
                                bool bMandatory = false);
122
static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId, char ***ppapszVars);
123
124
// Uncomment this for more debug output.
125
// #define NCDF_DEBUG 1
126
127
CPLMutex *hNCMutex = nullptr;
128
129
// Workaround https://github.com/OSGeo/gdal/issues/6253
130
// Having 2 netCDF handles on the same file doesn't work in a multi-threaded
131
// way. Apparently having the same handle works better (this is OK since
132
// we have a global mutex on the netCDF library)
133
static std::map<std::string, int> goMapNameToNetCDFId;
134
static std::map<int, std::pair<std::string, int>> goMapNetCDFIdToKeyAndCount;
135
136
int GDAL_nc_open(const char *pszFilename, int nMode, int *pID)
137
399
{
138
399
    std::string osKey(pszFilename);
139
399
    osKey += "#####";
140
399
    osKey += std::to_string(nMode);
141
399
    auto oIter = goMapNameToNetCDFId.find(osKey);
142
399
    if (oIter == goMapNameToNetCDFId.end())
143
399
    {
144
399
        int ret = nc_open(pszFilename, nMode, pID);
145
399
        if (ret != NC_NOERR)
146
0
            return ret;
147
399
        goMapNameToNetCDFId[osKey] = *pID;
148
399
        goMapNetCDFIdToKeyAndCount[*pID] =
149
399
            std::pair<std::string, int>(osKey, 1);
150
399
        return ret;
151
399
    }
152
0
    else
153
0
    {
154
0
        *pID = oIter->second;
155
0
        goMapNetCDFIdToKeyAndCount[oIter->second].second++;
156
0
        return NC_NOERR;
157
0
    }
158
399
}
159
160
int GDAL_nc_close(int cdfid)
161
798
{
162
798
    int ret = NC_NOERR;
163
798
    auto oIter = goMapNetCDFIdToKeyAndCount.find(cdfid);
164
798
    if (oIter != goMapNetCDFIdToKeyAndCount.end())
165
399
    {
166
399
        if (--oIter->second.second == 0)
167
399
        {
168
399
            ret = nc_close(cdfid);
169
399
            goMapNameToNetCDFId.erase(oIter->second.first);
170
399
            goMapNetCDFIdToKeyAndCount.erase(oIter);
171
399
        }
172
399
    }
173
399
    else
174
399
    {
175
        // we can go here if file opened with nc_open_mem() or nc_create()
176
399
        ret = nc_close(cdfid);
177
399
    }
178
798
    return ret;
179
798
}
180
181
/************************************************************************/
182
/* ==================================================================== */
183
/*                         netCDFRasterBand                             */
184
/* ==================================================================== */
185
/************************************************************************/
186
187
class netCDFRasterBand final : public GDALPamRasterBand
188
{
189
    friend class netCDFDataset;
190
191
    nc_type nc_datatype;
192
    int cdfid;
193
    int nZId;
194
    int nZDim;
195
    int nLevel;
196
    int nBandXPos;
197
    int nBandYPos;
198
    int *panBandZPos;
199
    int *panBandZLev;
200
    bool m_bNoDataSet = false;
201
    double m_dfNoDataValue = 0;
202
    bool m_bNoDataSetAsInt64 = false;
203
    int64_t m_nNodataValueInt64 = 0;
204
    bool m_bNoDataSetAsUInt64 = false;
205
    uint64_t m_nNodataValueUInt64 = 0;
206
    bool bValidRangeValid = false;
207
    double adfValidRange[2]{0, 0};
208
    bool m_bHaveScale = false;
209
    bool m_bHaveOffset = false;
210
    double m_dfScale = 1;
211
    double m_dfOffset = 0;
212
    CPLString m_osUnitType{};
213
    bool bSignedData;
214
    bool bCheckLongitude;
215
    bool m_bCreateMetadataFromOtherVarsDone = false;
216
217
    void CreateMetadataFromAttributes();
218
    void CreateMetadataFromOtherVars();
219
220
    template <class T>
221
    void CheckData(void *pImage, void *pImageNC, size_t nTmpBlockXSize,
222
                   size_t nTmpBlockYSize, bool bCheckIsNan = false);
223
    template <class T>
224
    void CheckDataCpx(void *pImage, void *pImageNC, size_t nTmpBlockXSize,
225
                      size_t nTmpBlockYSize, bool bCheckIsNan = false);
226
    void SetBlockSize();
227
228
    bool FetchNetcdfChunk(size_t xstart, size_t ystart, void *pImage);
229
230
    void SetNoDataValueNoUpdate(double dfNoData);
231
    void SetNoDataValueNoUpdate(int64_t nNoData);
232
    void SetNoDataValueNoUpdate(uint64_t nNoData);
233
234
    void SetOffsetNoUpdate(double dfVal);
235
    void SetScaleNoUpdate(double dfVal);
236
    void SetUnitTypeNoUpdate(const char *pszNewValue);
237
238
  protected:
239
    CPLXMLNode *SerializeToXML(const char *pszUnused) override;
240
241
  public:
242
    struct CONSTRUCTOR_OPEN
243
    {
244
    };
245
246
    struct CONSTRUCTOR_CREATE
247
    {
248
    };
249
250
    netCDFRasterBand(const CONSTRUCTOR_OPEN &, netCDFDataset *poDS,
251
                     int nGroupId, int nZId, int nZDim, int nLevel,
252
                     const int *panBandZLen, const int *panBandPos, int nBand);
253
    netCDFRasterBand(const CONSTRUCTOR_CREATE &, netCDFDataset *poDS,
254
                     GDALDataType eType, int nBand, bool bSigned = true,
255
                     const char *pszBandName = nullptr,
256
                     const char *pszLongName = nullptr, int nZId = -1,
257
                     int nZDim = 2, int nLevel = 0,
258
                     const int *panBandZLev = nullptr,
259
                     const int *panBandZPos = nullptr,
260
                     const int *paDimIds = nullptr);
261
    virtual ~netCDFRasterBand();
262
263
    virtual double GetNoDataValue(int *) override;
264
    virtual int64_t GetNoDataValueAsInt64(int *pbSuccess = nullptr) override;
265
    virtual uint64_t GetNoDataValueAsUInt64(int *pbSuccess = nullptr) override;
266
    virtual CPLErr SetNoDataValue(double) override;
267
    virtual CPLErr SetNoDataValueAsInt64(int64_t nNoData) override;
268
    virtual CPLErr SetNoDataValueAsUInt64(uint64_t nNoData) override;
269
    // virtual CPLErr DeleteNoDataValue();
270
    virtual double GetOffset(int *) override;
271
    virtual CPLErr SetOffset(double) override;
272
    virtual double GetScale(int *) override;
273
    virtual CPLErr SetScale(double) override;
274
    virtual const char *GetUnitType() override;
275
    virtual CPLErr SetUnitType(const char *) override;
276
    virtual CPLErr IReadBlock(int, int, void *) override;
277
    virtual CPLErr IWriteBlock(int, int, void *) override;
278
279
    char **GetMetadata(const char *pszDomain = "") override;
280
    const char *GetMetadataItem(const char *pszName,
281
                                const char *pszDomain = "") override;
282
283
    virtual CPLErr SetMetadataItem(const char *pszName, const char *pszValue,
284
                                   const char *pszDomain = "") override;
285
    virtual CPLErr SetMetadata(char **papszMD,
286
                               const char *pszDomain = "") override;
287
};
288
289
/************************************************************************/
290
/*                          netCDFRasterBand()                          */
291
/************************************************************************/
292
293
netCDFRasterBand::netCDFRasterBand(const netCDFRasterBand::CONSTRUCTOR_OPEN &,
294
                                   netCDFDataset *poNCDFDS, int nGroupId,
295
                                   int nZIdIn, int nZDimIn, int nLevelIn,
296
                                   const int *panBandZLevIn,
297
                                   const int *panBandZPosIn, int nBandIn)
298
0
    : nc_datatype(NC_NAT), cdfid(nGroupId), nZId(nZIdIn), nZDim(nZDimIn),
299
0
      nLevel(nLevelIn), nBandXPos(panBandZPosIn[0]),
300
0
      nBandYPos(nZDim == 1 ? -1 : panBandZPosIn[1]), panBandZPos(nullptr),
301
0
      panBandZLev(nullptr),
302
0
      bSignedData(true),  // Default signed, except for Byte.
303
0
      bCheckLongitude(false)
304
0
{
305
0
    poDS = poNCDFDS;
306
0
    nBand = nBandIn;
307
308
    // Take care of all other dimensions.
309
0
    if (nZDim > 2)
310
0
    {
311
0
        panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
312
0
        panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
313
314
0
        for (int i = 0; i < nZDim - 2; i++)
315
0
        {
316
0
            panBandZPos[i] = panBandZPosIn[i + 2];
317
0
            panBandZLev[i] = panBandZLevIn[i];
318
0
        }
319
0
    }
320
321
0
    nRasterXSize = poDS->GetRasterXSize();
322
0
    nRasterYSize = poDS->GetRasterYSize();
323
0
    nBlockXSize = poDS->GetRasterXSize();
324
0
    nBlockYSize = 1;
325
326
    // Get the type of the "z" variable, our target raster array.
327
0
    if (nc_inq_var(cdfid, nZId, nullptr, &nc_datatype, nullptr, nullptr,
328
0
                   nullptr) != NC_NOERR)
329
0
    {
330
0
        CPLError(CE_Failure, CPLE_AppDefined, "Error in nc_var_inq() on 'z'.");
331
0
        return;
332
0
    }
333
334
0
    if (NCDFIsUserDefinedType(cdfid, nc_datatype))
335
0
    {
336
        // First enquire and check that the number of fields is 2
337
0
        size_t nfields, compoundsize;
338
0
        if (nc_inq_compound(cdfid, nc_datatype, nullptr, &compoundsize,
339
0
                            &nfields) != NC_NOERR)
340
0
        {
341
0
            CPLError(CE_Failure, CPLE_AppDefined,
342
0
                     "Error in nc_inq_compound() on 'z'.");
343
0
            return;
344
0
        }
345
346
0
        if (nfields != 2)
347
0
        {
348
0
            CPLError(CE_Failure, CPLE_AppDefined,
349
0
                     "Unsupported data type encountered in nc_inq_compound() "
350
0
                     "on 'z'.");
351
0
            return;
352
0
        }
353
354
        // Now check that that two types are the same in the struct.
355
0
        nc_type field_type1, field_type2;
356
0
        int field_dims1, field_dims2;
357
0
        if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
358
0
                                  &field_type1, &field_dims1,
359
0
                                  nullptr) != NC_NOERR)
360
0
        {
361
0
            CPLError(
362
0
                CE_Failure, CPLE_AppDefined,
363
0
                "Error in querying Field 1 in nc_inq_compound_field() on 'z'.");
364
0
            return;
365
0
        }
366
367
0
        if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
368
0
                                  &field_type2, &field_dims2,
369
0
                                  nullptr) != NC_NOERR)
370
0
        {
371
0
            CPLError(
372
0
                CE_Failure, CPLE_AppDefined,
373
0
                "Error in querying Field 2 in nc_inq_compound_field() on 'z'.");
374
0
            return;
375
0
        }
376
377
0
        if ((field_type1 != field_type2) || (field_dims1 != field_dims2) ||
378
0
            (field_dims1 != 0))
379
0
        {
380
0
            CPLError(CE_Failure, CPLE_AppDefined,
381
0
                     "Error in interpreting compound data type on 'z'.");
382
0
            return;
383
0
        }
384
385
0
        if (field_type1 == NC_SHORT)
386
0
            eDataType = GDT_CInt16;
387
0
        else if (field_type1 == NC_INT)
388
0
            eDataType = GDT_CInt32;
389
0
        else if (field_type1 == NC_FLOAT)
390
0
            eDataType = GDT_CFloat32;
391
0
        else if (field_type1 == NC_DOUBLE)
392
0
            eDataType = GDT_CFloat64;
393
0
        else
394
0
        {
395
0
            CPLError(CE_Failure, CPLE_AppDefined,
396
0
                     "Unsupported netCDF compound data type encountered.");
397
0
            return;
398
0
        }
399
0
    }
400
0
    else
401
0
    {
402
0
        if (nc_datatype == NC_BYTE)
403
0
            eDataType = GDT_Byte;
404
0
        else if (nc_datatype == NC_CHAR)
405
0
            eDataType = GDT_Byte;
406
0
        else if (nc_datatype == NC_SHORT)
407
0
            eDataType = GDT_Int16;
408
0
        else if (nc_datatype == NC_INT)
409
0
            eDataType = GDT_Int32;
410
0
        else if (nc_datatype == NC_FLOAT)
411
0
            eDataType = GDT_Float32;
412
0
        else if (nc_datatype == NC_DOUBLE)
413
0
            eDataType = GDT_Float64;
414
0
        else if (nc_datatype == NC_UBYTE)
415
0
            eDataType = GDT_Byte;
416
0
        else if (nc_datatype == NC_USHORT)
417
0
            eDataType = GDT_UInt16;
418
0
        else if (nc_datatype == NC_UINT)
419
0
            eDataType = GDT_UInt32;
420
0
        else if (nc_datatype == NC_INT64)
421
0
            eDataType = GDT_Int64;
422
0
        else if (nc_datatype == NC_UINT64)
423
0
            eDataType = GDT_UInt64;
424
0
        else
425
0
        {
426
0
            if (nBand == 1)
427
0
                CPLError(CE_Warning, CPLE_AppDefined,
428
0
                         "Unsupported netCDF datatype (%d), treat as Float32.",
429
0
                         static_cast<int>(nc_datatype));
430
0
            eDataType = GDT_Float32;
431
0
            nc_datatype = NC_FLOAT;
432
0
        }
433
0
    }
434
435
    // Find and set No Data for this variable.
436
0
    nc_type atttype = NC_NAT;
437
0
    size_t attlen = 0;
438
0
    const char *pszNoValueName = nullptr;
439
440
    // Find attribute name, either _FillValue or missing_value.
441
0
    int status = nc_inq_att(cdfid, nZId, NCDF_FillValue, &atttype, &attlen);
442
0
    if (status == NC_NOERR)
443
0
    {
444
0
        pszNoValueName = NCDF_FillValue;
445
0
    }
446
0
    else
447
0
    {
448
0
        status = nc_inq_att(cdfid, nZId, "missing_value", &atttype, &attlen);
449
0
        if (status == NC_NOERR)
450
0
        {
451
0
            pszNoValueName = "missing_value";
452
0
        }
453
0
    }
454
455
    // Fetch missing value.
456
0
    double dfNoData = 0.0;
457
0
    bool bGotNoData = false;
458
0
    int64_t nNoDataAsInt64 = 0;
459
0
    bool bGotNoDataAsInt64 = false;
460
0
    uint64_t nNoDataAsUInt64 = 0;
461
0
    bool bGotNoDataAsUInt64 = false;
462
0
    if (status == NC_NOERR)
463
0
    {
464
0
        nc_type nAttrType = NC_NAT;
465
0
        size_t nAttrLen = 0;
466
0
        status = nc_inq_att(cdfid, nZId, pszNoValueName, &nAttrType, &nAttrLen);
467
0
        if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_INT64)
468
0
        {
469
0
            long long v;
470
0
            nc_get_att_longlong(cdfid, nZId, pszNoValueName, &v);
471
0
            bGotNoData = true;
472
0
            bGotNoDataAsInt64 = true;
473
0
            nNoDataAsInt64 = static_cast<int64_t>(v);
474
0
        }
475
0
        else if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_UINT64)
476
0
        {
477
0
            unsigned long long v;
478
0
            nc_get_att_ulonglong(cdfid, nZId, pszNoValueName, &v);
479
0
            bGotNoData = true;
480
0
            bGotNoDataAsUInt64 = true;
481
0
            nNoDataAsUInt64 = static_cast<uint64_t>(v);
482
0
        }
483
0
        else if (NCDFGetAttr(cdfid, nZId, pszNoValueName, &dfNoData) == CE_None)
484
0
        {
485
0
            bGotNoData = true;
486
0
        }
487
0
    }
488
489
    // If NoData was not found, use the default value, but for non-Byte types
490
    // as it is not recommended:
491
    // https://www.unidata.ucar.edu/software/netcdf/docs/attribute_conventions.html
492
0
    nc_type vartype = NC_NAT;
493
0
    if (!bGotNoData)
494
0
    {
495
0
        nc_inq_vartype(cdfid, nZId, &vartype);
496
0
        if (vartype == NC_INT64)
497
0
        {
498
0
            nNoDataAsInt64 =
499
0
                NCDFGetDefaultNoDataValueAsInt64(cdfid, nZId, bGotNoData);
500
0
            bGotNoDataAsInt64 = bGotNoData;
501
0
        }
502
0
        else if (vartype == NC_UINT64)
503
0
        {
504
0
            nNoDataAsUInt64 =
505
0
                NCDFGetDefaultNoDataValueAsUInt64(cdfid, nZId, bGotNoData);
506
0
            bGotNoDataAsUInt64 = bGotNoData;
507
0
        }
508
0
        else if (vartype != NC_CHAR && vartype != NC_BYTE &&
509
0
                 vartype != NC_UBYTE)
510
0
        {
511
0
            dfNoData =
512
0
                NCDFGetDefaultNoDataValue(cdfid, nZId, vartype, bGotNoData);
513
0
            if (bGotNoData)
514
0
            {
515
0
                CPLDebug("GDAL_netCDF",
516
0
                         "did not get nodata value for variable #%d, using "
517
0
                         "default %f",
518
0
                         nZId, dfNoData);
519
0
            }
520
0
        }
521
0
    }
522
523
0
    bool bHasUnderscoreUnsignedAttr = false;
524
0
    bool bUnderscoreUnsignedAttrVal = false;
525
0
    {
526
0
        char *pszTemp = nullptr;
527
0
        if (NCDFGetAttr(cdfid, nZId, "_Unsigned", &pszTemp) == CE_None)
528
0
        {
529
0
            if (EQUAL(pszTemp, "true"))
530
0
            {
531
0
                bHasUnderscoreUnsignedAttr = true;
532
0
                bUnderscoreUnsignedAttrVal = true;
533
0
            }
534
0
            else if (EQUAL(pszTemp, "false"))
535
0
            {
536
0
                bHasUnderscoreUnsignedAttr = true;
537
0
                bUnderscoreUnsignedAttrVal = false;
538
0
            }
539
0
            CPLFree(pszTemp);
540
0
        }
541
0
    }
542
543
    // Look for valid_range or valid_min/valid_max.
544
545
    // First look for valid_range.
546
0
    if (CPLFetchBool(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE", true))
547
0
    {
548
0
        char *pszValidRange = nullptr;
549
0
        if (NCDFGetAttr(cdfid, nZId, "valid_range", &pszValidRange) ==
550
0
                CE_None &&
551
0
            pszValidRange[0] == '{' &&
552
0
            pszValidRange[strlen(pszValidRange) - 1] == '}')
553
0
        {
554
0
            const std::string osValidRange =
555
0
                std::string(pszValidRange).substr(1, strlen(pszValidRange) - 2);
556
0
            const CPLStringList aosValidRange(
557
0
                CSLTokenizeString2(osValidRange.c_str(), ",", 0));
558
0
            if (aosValidRange.size() == 2 &&
559
0
                CPLGetValueType(aosValidRange[0]) != CPL_VALUE_STRING &&
560
0
                CPLGetValueType(aosValidRange[1]) != CPL_VALUE_STRING)
561
0
            {
562
0
                bValidRangeValid = true;
563
0
                adfValidRange[0] = CPLAtof(aosValidRange[0]);
564
0
                adfValidRange[1] = CPLAtof(aosValidRange[1]);
565
0
            }
566
0
        }
567
0
        CPLFree(pszValidRange);
568
569
        // If not found look for valid_min and valid_max.
570
0
        if (!bValidRangeValid)
571
0
        {
572
0
            double dfMin = 0;
573
0
            double dfMax = 0;
574
0
            if (NCDFGetAttr(cdfid, nZId, "valid_min", &dfMin) == CE_None &&
575
0
                NCDFGetAttr(cdfid, nZId, "valid_max", &dfMax) == CE_None)
576
0
            {
577
0
                adfValidRange[0] = dfMin;
578
0
                adfValidRange[1] = dfMax;
579
0
                bValidRangeValid = true;
580
0
            }
581
0
        }
582
583
0
        if (bValidRangeValid &&
584
0
            (adfValidRange[0] < 0 || adfValidRange[1] < 0) &&
585
0
            nc_datatype == NC_SHORT && bHasUnderscoreUnsignedAttr &&
586
0
            bUnderscoreUnsignedAttrVal)
587
0
        {
588
0
            if (adfValidRange[0] < 0)
589
0
                adfValidRange[0] += 65536;
590
0
            if (adfValidRange[1] < 0)
591
0
                adfValidRange[1] += 65536;
592
0
            if (adfValidRange[0] <= adfValidRange[1])
593
0
            {
594
                // Updating metadata item
595
0
                GDALPamRasterBand::SetMetadataItem(
596
0
                    "valid_range",
597
0
                    CPLSPrintf("{%d,%d}", static_cast<int>(adfValidRange[0]),
598
0
                               static_cast<int>(adfValidRange[1])));
599
0
            }
600
0
        }
601
602
0
        if (bValidRangeValid && adfValidRange[0] > adfValidRange[1])
603
0
        {
604
0
            CPLError(CE_Warning, CPLE_AppDefined,
605
0
                     "netCDFDataset::valid_range: min > max:\n"
606
0
                     "  min: %lf\n  max: %lf\n",
607
0
                     adfValidRange[0], adfValidRange[1]);
608
0
            bValidRangeValid = false;
609
0
            adfValidRange[0] = 0.0;
610
0
            adfValidRange[1] = 0.0;
611
0
        }
612
0
    }
613
614
    // Special For Byte Bands: check for signed/unsigned byte.
615
0
    if (nc_datatype == NC_BYTE)
616
0
    {
617
        // netcdf uses signed byte by default, but GDAL uses unsigned by default
618
        // This may cause unexpected results, but is needed for back-compat.
619
0
        if (poNCDFDS->bIsGdalFile)
620
0
            bSignedData = false;
621
0
        else
622
0
            bSignedData = true;
623
624
        // For NC4 format NC_BYTE is (normally) signed, NC_UBYTE is unsigned.
625
        // But in case a NC3 file was converted automatically and has hints
626
        // that it is unsigned, take them into account
627
0
        if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
628
0
        {
629
0
            bSignedData = true;
630
0
        }
631
632
        // If we got valid_range, test for signed/unsigned range.
633
        // https://docs.unidata.ucar.edu/netcdf-c/current/attribute_conventions.html
634
0
        if (bValidRangeValid)
635
0
        {
636
            // If we got valid_range={0,255}, treat as unsigned.
637
0
            if (adfValidRange[0] == 0 && adfValidRange[1] == 255)
638
0
            {
639
0
                bSignedData = false;
640
                // Reset valid_range.
641
0
                bValidRangeValid = false;
642
0
            }
643
            // If we got valid_range={-128,127}, treat as signed.
644
0
            else if (adfValidRange[0] == -128 && adfValidRange[1] == 127)
645
0
            {
646
0
                bSignedData = true;
647
                // Reset valid_range.
648
0
                bValidRangeValid = false;
649
0
            }
650
0
        }
651
        // Else test for _Unsigned.
652
        // https://docs.unidata.ucar.edu/nug/current/best_practices.html
653
0
        else
654
0
        {
655
0
            if (bHasUnderscoreUnsignedAttr)
656
0
                bSignedData = !bUnderscoreUnsignedAttrVal;
657
0
        }
658
659
0
        if (bSignedData)
660
0
        {
661
0
            eDataType = GDT_Int8;
662
0
        }
663
0
        else if (dfNoData < 0)
664
0
        {
665
            // Fix nodata value as it was stored signed.
666
0
            dfNoData += 256;
667
0
            if (pszNoValueName)
668
0
            {
669
                // Updating metadata item
670
0
                GDALPamRasterBand::SetMetadataItem(
671
0
                    pszNoValueName,
672
0
                    CPLSPrintf("%d", static_cast<int>(dfNoData)));
673
0
            }
674
0
        }
675
0
    }
676
0
    else if (nc_datatype == NC_SHORT)
677
0
    {
678
0
        if (bHasUnderscoreUnsignedAttr)
679
0
        {
680
0
            bSignedData = !bUnderscoreUnsignedAttrVal;
681
0
            if (!bSignedData)
682
0
                eDataType = GDT_UInt16;
683
0
        }
684
685
        // Fix nodata value as it was stored signed.
686
0
        if (!bSignedData && dfNoData < 0)
687
0
        {
688
0
            dfNoData += 65536;
689
0
            if (pszNoValueName)
690
0
            {
691
                // Updating metadata item
692
0
                GDALPamRasterBand::SetMetadataItem(
693
0
                    pszNoValueName,
694
0
                    CPLSPrintf("%d", static_cast<int>(dfNoData)));
695
0
            }
696
0
        }
697
0
    }
698
699
0
    else if (nc_datatype == NC_UBYTE || nc_datatype == NC_USHORT ||
700
0
             nc_datatype == NC_UINT || nc_datatype == NC_UINT64)
701
0
    {
702
0
        bSignedData = false;
703
0
    }
704
705
0
    CPLDebug("GDAL_netCDF", "netcdf type=%d gdal type=%d signedByte=%d",
706
0
             nc_datatype, eDataType, static_cast<int>(bSignedData));
707
708
0
    if (bGotNoData)
709
0
    {
710
        // Set nodata value.
711
0
        if (bGotNoDataAsInt64)
712
0
        {
713
0
            if (eDataType == GDT_Int64)
714
0
            {
715
0
                SetNoDataValueNoUpdate(nNoDataAsInt64);
716
0
            }
717
0
            else if (eDataType == GDT_UInt64 && nNoDataAsInt64 >= 0)
718
0
            {
719
0
                SetNoDataValueNoUpdate(static_cast<uint64_t>(nNoDataAsInt64));
720
0
            }
721
0
            else
722
0
            {
723
0
                SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsInt64));
724
0
            }
725
0
        }
726
0
        else if (bGotNoDataAsUInt64)
727
0
        {
728
0
            if (eDataType == GDT_UInt64)
729
0
            {
730
0
                SetNoDataValueNoUpdate(nNoDataAsUInt64);
731
0
            }
732
0
            else if (eDataType == GDT_Int64 &&
733
0
                     nNoDataAsUInt64 <=
734
0
                         static_cast<uint64_t>(
735
0
                             std::numeric_limits<int64_t>::max()))
736
0
            {
737
0
                SetNoDataValueNoUpdate(static_cast<int64_t>(nNoDataAsUInt64));
738
0
            }
739
0
            else
740
0
            {
741
0
                SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsUInt64));
742
0
            }
743
0
        }
744
0
        else
745
0
        {
746
#ifdef NCDF_DEBUG
747
            CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) read", dfNoData);
748
#endif
749
0
            if (eDataType == GDT_Int64 && GDALIsValueExactAs<int64_t>(dfNoData))
750
0
            {
751
0
                SetNoDataValueNoUpdate(static_cast<int64_t>(dfNoData));
752
0
            }
753
0
            else if (eDataType == GDT_UInt64 &&
754
0
                     GDALIsValueExactAs<uint64_t>(dfNoData))
755
0
            {
756
0
                SetNoDataValueNoUpdate(static_cast<uint64_t>(dfNoData));
757
0
            }
758
0
            else
759
0
            {
760
0
                SetNoDataValueNoUpdate(dfNoData);
761
0
            }
762
0
        }
763
0
    }
764
765
0
    CreateMetadataFromAttributes();
766
767
    // Attempt to fetch the scale_factor and add_offset attributes for the
768
    // variable and set them.  If these values are not available, set
769
    // offset to 0 and scale to 1.
770
0
    if (nc_inq_attid(cdfid, nZId, CF_ADD_OFFSET, nullptr) == NC_NOERR)
771
0
    {
772
0
        double dfOffset = 0;
773
0
        status = nc_get_att_double(cdfid, nZId, CF_ADD_OFFSET, &dfOffset);
774
0
        CPLDebug("GDAL_netCDF", "got add_offset=%.16g, status=%d", dfOffset,
775
0
                 status);
776
0
        SetOffsetNoUpdate(dfOffset);
777
0
    }
778
779
0
    bool bHasScale = false;
780
0
    if (nc_inq_attid(cdfid, nZId, CF_SCALE_FACTOR, nullptr) == NC_NOERR)
781
0
    {
782
0
        bHasScale = true;
783
0
        double dfScale = 1;
784
0
        status = nc_get_att_double(cdfid, nZId, CF_SCALE_FACTOR, &dfScale);
785
0
        CPLDebug("GDAL_netCDF", "got scale_factor=%.16g, status=%d", dfScale,
786
0
                 status);
787
0
        SetScaleNoUpdate(dfScale);
788
0
    }
789
790
0
    if (bValidRangeValid && GDALDataTypeIsInteger(eDataType) &&
791
0
        eDataType != GDT_Int64 && eDataType != GDT_UInt64 &&
792
0
        (std::fabs(std::round(adfValidRange[0]) - adfValidRange[0]) > 1e-5 ||
793
0
         std::fabs(std::round(adfValidRange[1]) - adfValidRange[1]) > 1e-5) &&
794
0
        CSLFetchNameValue(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE") ==
795
0
            nullptr)
796
0
    {
797
0
        CPLError(CE_Warning, CPLE_AppDefined,
798
0
                 "validity range = %f, %f contains floating-point values, "
799
0
                 "whereas data type is integer. valid_range is thus likely "
800
0
                 "wrong%s. Ignoring it.",
801
0
                 adfValidRange[0], adfValidRange[1],
802
0
                 bHasScale ? " (likely scaled using scale_factor/add_factor "
803
0
                             "whereas it should be using the packed data type)"
804
0
                           : "");
805
0
        bValidRangeValid = false;
806
0
        adfValidRange[0] = 0.0;
807
0
        adfValidRange[1] = 0.0;
808
0
    }
809
810
    // Should we check for longitude values > 360?
811
0
    bCheckLongitude =
812
0
        CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")) &&
813
0
        NCDFIsVarLongitude(cdfid, nZId, nullptr);
814
815
    // Attempt to fetch the units attribute for the variable and set it.
816
0
    SetUnitTypeNoUpdate(netCDFRasterBand::GetMetadataItem(CF_UNITS));
817
818
0
    SetBlockSize();
819
0
}
820
821
void netCDFRasterBand::SetBlockSize()
822
0
{
823
    // Check for variable chunking (netcdf-4 only).
824
    // GDAL block size should be set to hdf5 chunk size.
825
0
    int nTmpFormat = 0;
826
0
    int status = nc_inq_format(cdfid, &nTmpFormat);
827
0
    NetCDFFormatEnum eTmpFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
828
0
    if ((status == NC_NOERR) &&
829
0
        (eTmpFormat == NCDF_FORMAT_NC4 || eTmpFormat == NCDF_FORMAT_NC4C))
830
0
    {
831
0
        size_t chunksize[MAX_NC_DIMS] = {};
832
        // Check for chunksize and set it as the blocksize (optimizes read).
833
0
        status = nc_inq_var_chunking(cdfid, nZId, &nTmpFormat, chunksize);
834
0
        if ((status == NC_NOERR) && (nTmpFormat == NC_CHUNKED))
835
0
        {
836
0
            nBlockXSize = (int)chunksize[nZDim - 1];
837
0
            if (nZDim >= 2)
838
0
                nBlockYSize = (int)chunksize[nZDim - 2];
839
0
            else
840
0
                nBlockYSize = 1;
841
0
        }
842
0
    }
843
844
    // Deal with bottom-up datasets and nBlockYSize != 1.
845
0
    auto poGDS = static_cast<netCDFDataset *>(poDS);
846
0
    if (poGDS->bBottomUp && nBlockYSize != 1 && poGDS->poChunkCache == nullptr)
847
0
    {
848
0
        if (poGDS->eAccess == GA_ReadOnly)
849
0
        {
850
            // Try to cache 1 or 2 'rows' of netCDF chunks along the whole
851
            // width of the raster
852
0
            size_t nChunks =
853
0
                static_cast<size_t>(DIV_ROUND_UP(nRasterXSize, nBlockXSize));
854
0
            if ((nRasterYSize % nBlockYSize) != 0)
855
0
                nChunks *= 2;
856
0
            const size_t nChunkSize =
857
0
                static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
858
0
                nBlockXSize * nBlockYSize;
859
0
            constexpr size_t MAX_CACHE_SIZE = 100 * 1024 * 1024;
860
0
            nChunks = std::min(nChunks, MAX_CACHE_SIZE / nChunkSize);
861
0
            if (nChunks)
862
0
            {
863
0
                poGDS->poChunkCache.reset(
864
0
                    new netCDFDataset::ChunkCacheType(nChunks));
865
0
            }
866
0
        }
867
0
        else
868
0
        {
869
0
            nBlockYSize = 1;
870
0
        }
871
0
    }
872
0
}
873
874
// Constructor in create mode.
875
// If nZId and following variables are not passed, the band will have 2
876
// dimensions.
877
// TODO: Get metadata, missing val from band #1 if nZDim > 2.
878
netCDFRasterBand::netCDFRasterBand(
879
    const netCDFRasterBand::CONSTRUCTOR_CREATE &, netCDFDataset *poNCDFDS,
880
    const GDALDataType eTypeIn, int nBandIn, bool bSigned,
881
    const char *pszBandName, const char *pszLongName, int nZIdIn, int nZDimIn,
882
    int nLevelIn, const int *panBandZLevIn, const int *panBandZPosIn,
883
    const int *paDimIds)
884
0
    : nc_datatype(NC_NAT), cdfid(poNCDFDS->GetCDFID()), nZId(nZIdIn),
885
0
      nZDim(nZDimIn), nLevel(nLevelIn), nBandXPos(1), nBandYPos(0),
886
0
      panBandZPos(nullptr), panBandZLev(nullptr), bSignedData(bSigned),
887
0
      bCheckLongitude(false), m_bCreateMetadataFromOtherVarsDone(true)
888
0
{
889
0
    poDS = poNCDFDS;
890
0
    nBand = nBandIn;
891
892
0
    nRasterXSize = poDS->GetRasterXSize();
893
0
    nRasterYSize = poDS->GetRasterYSize();
894
0
    nBlockXSize = poDS->GetRasterXSize();
895
0
    nBlockYSize = 1;
896
897
0
    if (poDS->GetAccess() != GA_Update)
898
0
    {
899
0
        CPLError(CE_Failure, CPLE_NotSupported,
900
0
                 "Dataset is not in update mode, "
901
0
                 "wrong netCDFRasterBand constructor");
902
0
        return;
903
0
    }
904
905
    // Take care of all other dimensions.
906
0
    if (nZDim > 2 && paDimIds != nullptr)
907
0
    {
908
0
        nBandXPos = panBandZPosIn[0];
909
0
        nBandYPos = panBandZPosIn[1];
910
911
0
        panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
912
0
        panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
913
914
0
        for (int i = 0; i < nZDim - 2; i++)
915
0
        {
916
0
            panBandZPos[i] = panBandZPosIn[i + 2];
917
0
            panBandZLev[i] = panBandZLevIn[i];
918
0
        }
919
0
    }
920
921
    // Get the type of the "z" variable, our target raster array.
922
0
    eDataType = eTypeIn;
923
924
0
    switch (eDataType)
925
0
    {
926
0
        case GDT_Byte:
927
0
            nc_datatype = NC_BYTE;
928
            // NC_UBYTE (unsigned byte) is only available for NC4.
929
0
            if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
930
0
                nc_datatype = NC_UBYTE;
931
0
            break;
932
0
        case GDT_Int8:
933
0
            nc_datatype = NC_BYTE;
934
0
            break;
935
0
        case GDT_Int16:
936
0
            nc_datatype = NC_SHORT;
937
0
            break;
938
0
        case GDT_Int32:
939
0
            nc_datatype = NC_INT;
940
0
            break;
941
0
        case GDT_Float32:
942
0
            nc_datatype = NC_FLOAT;
943
0
            break;
944
0
        case GDT_Float64:
945
0
            nc_datatype = NC_DOUBLE;
946
0
            break;
947
0
        case GDT_Int64:
948
0
            if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
949
0
            {
950
0
                nc_datatype = NC_INT64;
951
0
            }
952
0
            else
953
0
            {
954
0
                if (nBand == 1)
955
0
                    CPLError(
956
0
                        CE_Warning, CPLE_AppDefined,
957
0
                        "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
958
0
                        "Int64");
959
0
                nc_datatype = NC_DOUBLE;
960
0
                eDataType = GDT_Float64;
961
0
            }
962
0
            break;
963
0
        case GDT_UInt64:
964
0
            if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
965
0
            {
966
0
                nc_datatype = NC_UINT64;
967
0
            }
968
0
            else
969
0
            {
970
0
                if (nBand == 1)
971
0
                    CPLError(
972
0
                        CE_Warning, CPLE_AppDefined,
973
0
                        "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
974
0
                        "UInt64");
975
0
                nc_datatype = NC_DOUBLE;
976
0
                eDataType = GDT_Float64;
977
0
            }
978
0
            break;
979
0
        case GDT_UInt16:
980
0
            if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
981
0
            {
982
0
                nc_datatype = NC_USHORT;
983
0
                break;
984
0
            }
985
0
            [[fallthrough]];
986
0
        case GDT_UInt32:
987
0
            if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
988
0
            {
989
0
                nc_datatype = NC_UINT;
990
0
                break;
991
0
            }
992
0
            [[fallthrough]];
993
0
        default:
994
0
            if (nBand == 1)
995
0
                CPLError(CE_Warning, CPLE_AppDefined,
996
0
                         "Unsupported GDAL datatype (%d), treat as NC_FLOAT.",
997
0
                         static_cast<int>(eDataType));
998
0
            nc_datatype = NC_FLOAT;
999
0
            eDataType = GDT_Float32;
1000
0
            break;
1001
0
    }
1002
1003
    // Define the variable if necessary (if nZId == -1).
1004
0
    bool bDefineVar = false;
1005
1006
0
    if (nZId == -1)
1007
0
    {
1008
0
        bDefineVar = true;
1009
1010
        // Make sure we are in define mode.
1011
0
        static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1012
1013
0
        char szTempPrivate[256 + 1];
1014
0
        const char *pszTemp = nullptr;
1015
0
        if (!pszBandName || EQUAL(pszBandName, ""))
1016
0
        {
1017
0
            snprintf(szTempPrivate, sizeof(szTempPrivate), "Band%d", nBand);
1018
0
            pszTemp = szTempPrivate;
1019
0
        }
1020
0
        else
1021
0
        {
1022
0
            pszTemp = pszBandName;
1023
0
        }
1024
1025
0
        int status;
1026
0
        if (nZDim > 2 && paDimIds != nullptr)
1027
0
        {
1028
0
            status =
1029
0
                nc_def_var(cdfid, pszTemp, nc_datatype, nZDim, paDimIds, &nZId);
1030
0
        }
1031
0
        else
1032
0
        {
1033
0
            int anBandDims[2] = {poNCDFDS->nYDimID, poNCDFDS->nXDimID};
1034
0
            status =
1035
0
                nc_def_var(cdfid, pszTemp, nc_datatype, 2, anBandDims, &nZId);
1036
0
        }
1037
0
        NCDF_ERR(status);
1038
0
        CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d) id=%d", cdfid, pszTemp,
1039
0
                 nc_datatype, nZId);
1040
1041
0
        if (!pszLongName || EQUAL(pszLongName, ""))
1042
0
        {
1043
0
            snprintf(szTempPrivate, sizeof(szTempPrivate),
1044
0
                     "GDAL Band Number %d", nBand);
1045
0
            pszTemp = szTempPrivate;
1046
0
        }
1047
0
        else
1048
0
        {
1049
0
            pszTemp = pszLongName;
1050
0
        }
1051
0
        status =
1052
0
            nc_put_att_text(cdfid, nZId, CF_LNG_NAME, strlen(pszTemp), pszTemp);
1053
0
        NCDF_ERR(status);
1054
1055
0
        poNCDFDS->DefVarDeflate(nZId, true);
1056
0
    }
1057
1058
    // For Byte data add signed/unsigned info.
1059
0
    if (eDataType == GDT_Byte || eDataType == GDT_Int8)
1060
0
    {
1061
0
        if (bDefineVar)
1062
0
        {
1063
            // Only add attributes if creating variable.
1064
            // For unsigned NC_BYTE (except NC4 format),
1065
            // add valid_range and _Unsigned ( defined in CF-1 and NUG ).
1066
0
            if (nc_datatype == NC_BYTE && poNCDFDS->eFormat != NCDF_FORMAT_NC4)
1067
0
            {
1068
0
                CPLDebug("GDAL_netCDF",
1069
0
                         "adding valid_range attributes for Byte Band");
1070
0
                short l_adfValidRange[2] = {0, 0};
1071
0
                int status;
1072
0
                if (bSignedData || eDataType == GDT_Int8)
1073
0
                {
1074
0
                    l_adfValidRange[0] = -128;
1075
0
                    l_adfValidRange[1] = 127;
1076
0
                    status =
1077
0
                        nc_put_att_text(cdfid, nZId, "_Unsigned", 5, "false");
1078
0
                }
1079
0
                else
1080
0
                {
1081
0
                    l_adfValidRange[0] = 0;
1082
0
                    l_adfValidRange[1] = 255;
1083
0
                    status =
1084
0
                        nc_put_att_text(cdfid, nZId, "_Unsigned", 4, "true");
1085
0
                }
1086
0
                NCDF_ERR(status);
1087
0
                status = nc_put_att_short(cdfid, nZId, "valid_range", NC_SHORT,
1088
0
                                          2, l_adfValidRange);
1089
0
                NCDF_ERR(status);
1090
0
            }
1091
0
        }
1092
0
    }
1093
1094
0
    if (nc_datatype != NC_BYTE && nc_datatype != NC_CHAR &&
1095
0
        nc_datatype != NC_UBYTE)
1096
0
    {
1097
        // Set default nodata.
1098
0
        bool bIgnored = false;
1099
0
        double dfNoData =
1100
0
            NCDFGetDefaultNoDataValue(cdfid, nZId, nc_datatype, bIgnored);
1101
#ifdef NCDF_DEBUG
1102
        CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) default", dfNoData);
1103
#endif
1104
0
        netCDFRasterBand::SetNoDataValue(dfNoData);
1105
0
    }
1106
1107
0
    SetBlockSize();
1108
0
}
1109
1110
/************************************************************************/
1111
/*                         ~netCDFRasterBand()                          */
1112
/************************************************************************/
1113
1114
netCDFRasterBand::~netCDFRasterBand()
1115
0
{
1116
0
    netCDFRasterBand::FlushCache(true);
1117
0
    CPLFree(panBandZPos);
1118
0
    CPLFree(panBandZLev);
1119
0
}
1120
1121
/************************************************************************/
1122
/*                          GetMetadata()                               */
1123
/************************************************************************/
1124
1125
char **netCDFRasterBand::GetMetadata(const char *pszDomain)
1126
0
{
1127
0
    if (!m_bCreateMetadataFromOtherVarsDone)
1128
0
        CreateMetadataFromOtherVars();
1129
0
    return GDALPamRasterBand::GetMetadata(pszDomain);
1130
0
}
1131
1132
/************************************************************************/
1133
/*                        GetMetadataItem()                             */
1134
/************************************************************************/
1135
1136
const char *netCDFRasterBand::GetMetadataItem(const char *pszName,
1137
                                              const char *pszDomain)
1138
0
{
1139
0
    if (!m_bCreateMetadataFromOtherVarsDone &&
1140
0
        STARTS_WITH(pszName, "NETCDF_DIM_") &&
1141
0
        (!pszDomain || pszDomain[0] == 0))
1142
0
        CreateMetadataFromOtherVars();
1143
0
    return GDALPamRasterBand::GetMetadataItem(pszName, pszDomain);
1144
0
}
1145
1146
/************************************************************************/
1147
/*                        SetMetadataItem()                             */
1148
/************************************************************************/
1149
1150
CPLErr netCDFRasterBand::SetMetadataItem(const char *pszName,
1151
                                         const char *pszValue,
1152
                                         const char *pszDomain)
1153
0
{
1154
0
    if (GetAccess() == GA_Update &&
1155
0
        (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)
1156
0
    {
1157
        // Same logic as in CopyMetadata()
1158
1159
0
        const char *const papszIgnoreBand[] = {
1160
0
            CF_ADD_OFFSET,  CF_SCALE_FACTOR, "valid_range", "_Unsigned",
1161
0
            NCDF_FillValue, "coordinates",   nullptr};
1162
        // Do not copy varname, stats, NETCDF_DIM_*, nodata
1163
        // and items in papszIgnoreBand.
1164
0
        if (STARTS_WITH(pszName, "NETCDF_VARNAME") ||
1165
0
            STARTS_WITH(pszName, "STATISTICS_") ||
1166
0
            STARTS_WITH(pszName, "NETCDF_DIM_") ||
1167
0
            STARTS_WITH(pszName, "missing_value") ||
1168
0
            STARTS_WITH(pszName, "_FillValue") ||
1169
0
            CSLFindString(papszIgnoreBand, pszName) != -1)
1170
0
        {
1171
            // do nothing
1172
0
        }
1173
0
        else
1174
0
        {
1175
0
            cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1176
1177
0
            if (!NCDFPutAttr(cdfid, nZId, pszName, pszValue))
1178
0
                return CE_Failure;
1179
0
        }
1180
0
    }
1181
1182
0
    return GDALPamRasterBand::SetMetadataItem(pszName, pszValue, pszDomain);
1183
0
}
1184
1185
/************************************************************************/
1186
/*                          SetMetadata()                               */
1187
/************************************************************************/
1188
1189
CPLErr netCDFRasterBand::SetMetadata(char **papszMD, const char *pszDomain)
1190
0
{
1191
0
    if (GetAccess() == GA_Update &&
1192
0
        (pszDomain == nullptr || pszDomain[0] == '\0'))
1193
0
    {
1194
        // We don't handle metadata item removal for now
1195
0
        for (const char *const *papszIter = papszMD; papszIter && *papszIter;
1196
0
             ++papszIter)
1197
0
        {
1198
0
            char *pszName = nullptr;
1199
0
            const char *pszValue = CPLParseNameValue(*papszIter, &pszName);
1200
0
            if (pszName && pszValue)
1201
0
                SetMetadataItem(pszName, pszValue);
1202
0
            CPLFree(pszName);
1203
0
        }
1204
0
    }
1205
0
    return GDALPamRasterBand::SetMetadata(papszMD, pszDomain);
1206
0
}
1207
1208
/************************************************************************/
1209
/*                             GetOffset()                              */
1210
/************************************************************************/
1211
double netCDFRasterBand::GetOffset(int *pbSuccess)
1212
0
{
1213
0
    if (pbSuccess != nullptr)
1214
0
        *pbSuccess = static_cast<int>(m_bHaveOffset);
1215
1216
0
    return m_dfOffset;
1217
0
}
1218
1219
/************************************************************************/
1220
/*                             SetOffset()                              */
1221
/************************************************************************/
1222
CPLErr netCDFRasterBand::SetOffset(double dfNewOffset)
1223
0
{
1224
0
    CPLMutexHolderD(&hNCMutex);
1225
1226
    // Write value if in update mode.
1227
0
    if (poDS->GetAccess() == GA_Update)
1228
0
    {
1229
        // Make sure we are in define mode.
1230
0
        static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1231
1232
0
        const int status = nc_put_att_double(cdfid, nZId, CF_ADD_OFFSET,
1233
0
                                             NC_DOUBLE, 1, &dfNewOffset);
1234
1235
0
        NCDF_ERR(status);
1236
0
        if (status == NC_NOERR)
1237
0
        {
1238
0
            SetOffsetNoUpdate(dfNewOffset);
1239
0
            return CE_None;
1240
0
        }
1241
1242
0
        return CE_Failure;
1243
0
    }
1244
1245
0
    SetOffsetNoUpdate(dfNewOffset);
1246
0
    return CE_None;
1247
0
}
1248
1249
/************************************************************************/
1250
/*                         SetOffsetNoUpdate()                          */
1251
/************************************************************************/
1252
void netCDFRasterBand::SetOffsetNoUpdate(double dfVal)
1253
0
{
1254
0
    m_dfOffset = dfVal;
1255
0
    m_bHaveOffset = true;
1256
0
}
1257
1258
/************************************************************************/
1259
/*                              GetScale()                              */
1260
/************************************************************************/
1261
double netCDFRasterBand::GetScale(int *pbSuccess)
1262
0
{
1263
0
    if (pbSuccess != nullptr)
1264
0
        *pbSuccess = static_cast<int>(m_bHaveScale);
1265
1266
0
    return m_dfScale;
1267
0
}
1268
1269
/************************************************************************/
1270
/*                              SetScale()                              */
1271
/************************************************************************/
1272
CPLErr netCDFRasterBand::SetScale(double dfNewScale)
1273
0
{
1274
0
    CPLMutexHolderD(&hNCMutex);
1275
1276
    // Write value if in update mode.
1277
0
    if (poDS->GetAccess() == GA_Update)
1278
0
    {
1279
        // Make sure we are in define mode.
1280
0
        static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1281
1282
0
        const int status = nc_put_att_double(cdfid, nZId, CF_SCALE_FACTOR,
1283
0
                                             NC_DOUBLE, 1, &dfNewScale);
1284
1285
0
        NCDF_ERR(status);
1286
0
        if (status == NC_NOERR)
1287
0
        {
1288
0
            SetScaleNoUpdate(dfNewScale);
1289
0
            return CE_None;
1290
0
        }
1291
1292
0
        return CE_Failure;
1293
0
    }
1294
1295
0
    SetScaleNoUpdate(dfNewScale);
1296
0
    return CE_None;
1297
0
}
1298
1299
/************************************************************************/
1300
/*                         SetScaleNoUpdate()                           */
1301
/************************************************************************/
1302
void netCDFRasterBand::SetScaleNoUpdate(double dfVal)
1303
0
{
1304
0
    m_dfScale = dfVal;
1305
0
    m_bHaveScale = true;
1306
0
}
1307
1308
/************************************************************************/
1309
/*                            GetUnitType()                             */
1310
/************************************************************************/
1311
1312
const char *netCDFRasterBand::GetUnitType()
1313
1314
0
{
1315
0
    if (!m_osUnitType.empty())
1316
0
        return m_osUnitType;
1317
1318
0
    return GDALRasterBand::GetUnitType();
1319
0
}
1320
1321
/************************************************************************/
1322
/*                           SetUnitType()                              */
1323
/************************************************************************/
1324
1325
CPLErr netCDFRasterBand::SetUnitType(const char *pszNewValue)
1326
1327
0
{
1328
0
    CPLMutexHolderD(&hNCMutex);
1329
1330
0
    const std::string osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
1331
1332
0
    if (!osUnitType.empty())
1333
0
    {
1334
        // Write value if in update mode.
1335
0
        if (poDS->GetAccess() == GA_Update)
1336
0
        {
1337
            // Make sure we are in define mode.
1338
0
            static_cast<netCDFDataset *>(poDS)->SetDefineMode(TRUE);
1339
1340
0
            const int status = nc_put_att_text(
1341
0
                cdfid, nZId, CF_UNITS, osUnitType.size(), osUnitType.c_str());
1342
1343
0
            NCDF_ERR(status);
1344
0
            if (status == NC_NOERR)
1345
0
            {
1346
0
                SetUnitTypeNoUpdate(pszNewValue);
1347
0
                return CE_None;
1348
0
            }
1349
1350
0
            return CE_Failure;
1351
0
        }
1352
0
    }
1353
1354
0
    SetUnitTypeNoUpdate(pszNewValue);
1355
1356
0
    return CE_None;
1357
0
}
1358
1359
/************************************************************************/
1360
/*                       SetUnitTypeNoUpdate()                          */
1361
/************************************************************************/
1362
1363
void netCDFRasterBand::SetUnitTypeNoUpdate(const char *pszNewValue)
1364
0
{
1365
0
    m_osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
1366
0
}
1367
1368
/************************************************************************/
1369
/*                           GetNoDataValue()                           */
1370
/************************************************************************/
1371
1372
double netCDFRasterBand::GetNoDataValue(int *pbSuccess)
1373
1374
0
{
1375
0
    if (m_bNoDataSetAsInt64)
1376
0
    {
1377
0
        if (pbSuccess)
1378
0
            *pbSuccess = TRUE;
1379
0
        return GDALGetNoDataValueCastToDouble(m_nNodataValueInt64);
1380
0
    }
1381
1382
0
    if (m_bNoDataSetAsUInt64)
1383
0
    {
1384
0
        if (pbSuccess)
1385
0
            *pbSuccess = TRUE;
1386
0
        return GDALGetNoDataValueCastToDouble(m_nNodataValueUInt64);
1387
0
    }
1388
1389
0
    if (m_bNoDataSet)
1390
0
    {
1391
0
        if (pbSuccess)
1392
0
            *pbSuccess = TRUE;
1393
0
        return m_dfNoDataValue;
1394
0
    }
1395
1396
0
    return GDALPamRasterBand::GetNoDataValue(pbSuccess);
1397
0
}
1398
1399
/************************************************************************/
1400
/*                        GetNoDataValueAsInt64()                       */
1401
/************************************************************************/
1402
1403
int64_t netCDFRasterBand::GetNoDataValueAsInt64(int *pbSuccess)
1404
1405
0
{
1406
0
    if (m_bNoDataSetAsInt64)
1407
0
    {
1408
0
        if (pbSuccess)
1409
0
            *pbSuccess = TRUE;
1410
1411
0
        return m_nNodataValueInt64;
1412
0
    }
1413
1414
0
    return GDALPamRasterBand::GetNoDataValueAsInt64(pbSuccess);
1415
0
}
1416
1417
/************************************************************************/
1418
/*                        GetNoDataValueAsUInt64()                      */
1419
/************************************************************************/
1420
1421
uint64_t netCDFRasterBand::GetNoDataValueAsUInt64(int *pbSuccess)
1422
1423
0
{
1424
0
    if (m_bNoDataSetAsUInt64)
1425
0
    {
1426
0
        if (pbSuccess)
1427
0
            *pbSuccess = TRUE;
1428
1429
0
        return m_nNodataValueUInt64;
1430
0
    }
1431
1432
0
    return GDALPamRasterBand::GetNoDataValueAsUInt64(pbSuccess);
1433
0
}
1434
1435
/************************************************************************/
1436
/*                           SetNoDataValue()                           */
1437
/************************************************************************/
1438
1439
CPLErr netCDFRasterBand::SetNoDataValue(double dfNoData)
1440
1441
0
{
1442
0
    CPLMutexHolderD(&hNCMutex);
1443
1444
    // If already set to new value, don't do anything.
1445
0
    if (m_bNoDataSet && CPLIsEqual(dfNoData, m_dfNoDataValue))
1446
0
        return CE_None;
1447
1448
    // Write value if in update mode.
1449
0
    if (poDS->GetAccess() == GA_Update)
1450
0
    {
1451
        // netcdf-4 does not allow to set _FillValue after leaving define mode,
1452
        // but it is ok if variable has not been written to, so only print
1453
        // debug. See bug #4484.
1454
0
        if (m_bNoDataSet &&
1455
0
            !reinterpret_cast<netCDFDataset *>(poDS)->GetDefineMode())
1456
0
        {
1457
0
            CPLDebug("GDAL_netCDF",
1458
0
                     "Setting NoDataValue to %.17g (previously set to %.17g) "
1459
0
                     "but file is no longer in define mode (id #%d, band #%d)",
1460
0
                     dfNoData, m_dfNoDataValue, cdfid, nBand);
1461
0
        }
1462
#ifdef NCDF_DEBUG
1463
        else
1464
        {
1465
            CPLDebug("GDAL_netCDF",
1466
                     "Setting NoDataValue to %.17g (id #%d, band #%d)",
1467
                     dfNoData, cdfid, nBand);
1468
        }
1469
#endif
1470
        // Make sure we are in define mode.
1471
0
        reinterpret_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1472
1473
0
        int status;
1474
0
        if (eDataType == GDT_Byte)
1475
0
        {
1476
0
            if (bSignedData)
1477
0
            {
1478
0
                signed char cNoDataValue = static_cast<signed char>(dfNoData);
1479
0
                status = nc_put_att_schar(cdfid, nZId, NCDF_FillValue,
1480
0
                                          nc_datatype, 1, &cNoDataValue);
1481
0
            }
1482
0
            else
1483
0
            {
1484
0
                const unsigned char ucNoDataValue =
1485
0
                    static_cast<unsigned char>(dfNoData);
1486
0
                status = nc_put_att_uchar(cdfid, nZId, NCDF_FillValue,
1487
0
                                          nc_datatype, 1, &ucNoDataValue);
1488
0
            }
1489
0
        }
1490
0
        else if (eDataType == GDT_Int16)
1491
0
        {
1492
0
            short nsNoDataValue = static_cast<short>(dfNoData);
1493
0
            status = nc_put_att_short(cdfid, nZId, NCDF_FillValue, nc_datatype,
1494
0
                                      1, &nsNoDataValue);
1495
0
        }
1496
0
        else if (eDataType == GDT_Int32)
1497
0
        {
1498
0
            int nNoDataValue = static_cast<int>(dfNoData);
1499
0
            status = nc_put_att_int(cdfid, nZId, NCDF_FillValue, nc_datatype, 1,
1500
0
                                    &nNoDataValue);
1501
0
        }
1502
0
        else if (eDataType == GDT_Float32)
1503
0
        {
1504
0
            float fNoDataValue = static_cast<float>(dfNoData);
1505
0
            status = nc_put_att_float(cdfid, nZId, NCDF_FillValue, nc_datatype,
1506
0
                                      1, &fNoDataValue);
1507
0
        }
1508
0
        else if (eDataType == GDT_UInt16 &&
1509
0
                 reinterpret_cast<netCDFDataset *>(poDS)->eFormat ==
1510
0
                     NCDF_FORMAT_NC4)
1511
0
        {
1512
0
            unsigned short usNoDataValue =
1513
0
                static_cast<unsigned short>(dfNoData);
1514
0
            status = nc_put_att_ushort(cdfid, nZId, NCDF_FillValue, nc_datatype,
1515
0
                                       1, &usNoDataValue);
1516
0
        }
1517
0
        else if (eDataType == GDT_UInt32 &&
1518
0
                 reinterpret_cast<netCDFDataset *>(poDS)->eFormat ==
1519
0
                     NCDF_FORMAT_NC4)
1520
0
        {
1521
0
            unsigned int unNoDataValue = static_cast<unsigned int>(dfNoData);
1522
0
            status = nc_put_att_uint(cdfid, nZId, NCDF_FillValue, nc_datatype,
1523
0
                                     1, &unNoDataValue);
1524
0
        }
1525
0
        else
1526
0
        {
1527
0
            status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
1528
0
                                       1, &dfNoData);
1529
0
        }
1530
1531
0
        NCDF_ERR(status);
1532
1533
        // Update status if write worked.
1534
0
        if (status == NC_NOERR)
1535
0
        {
1536
0
            SetNoDataValueNoUpdate(dfNoData);
1537
0
            return CE_None;
1538
0
        }
1539
1540
0
        return CE_Failure;
1541
0
    }
1542
1543
0
    SetNoDataValueNoUpdate(dfNoData);
1544
0
    return CE_None;
1545
0
}
1546
1547
/************************************************************************/
1548
/*                       SetNoDataValueNoUpdate()                       */
1549
/************************************************************************/
1550
1551
void netCDFRasterBand::SetNoDataValueNoUpdate(double dfNoData)
1552
0
{
1553
0
    m_dfNoDataValue = dfNoData;
1554
0
    m_bNoDataSet = true;
1555
0
    m_bNoDataSetAsInt64 = false;
1556
0
    m_bNoDataSetAsUInt64 = false;
1557
0
}
1558
1559
/************************************************************************/
1560
/*                        SetNoDataValueAsInt64()                       */
1561
/************************************************************************/
1562
1563
CPLErr netCDFRasterBand::SetNoDataValueAsInt64(int64_t nNoData)
1564
1565
0
{
1566
0
    CPLMutexHolderD(&hNCMutex);
1567
1568
    // If already set to new value, don't do anything.
1569
0
    if (m_bNoDataSetAsInt64 && nNoData == m_nNodataValueInt64)
1570
0
        return CE_None;
1571
1572
    // Write value if in update mode.
1573
0
    if (poDS->GetAccess() == GA_Update)
1574
0
    {
1575
        // netcdf-4 does not allow to set NCDF_FillValue after leaving define mode,
1576
        // but it is ok if variable has not been written to, so only print
1577
        // debug. See bug #4484.
1578
0
        if (m_bNoDataSetAsInt64 &&
1579
0
            !reinterpret_cast<netCDFDataset *>(poDS)->GetDefineMode())
1580
0
        {
1581
0
            CPLDebug("GDAL_netCDF",
1582
0
                     "Setting NoDataValue to " CPL_FRMT_GIB
1583
0
                     " (previously set to " CPL_FRMT_GIB ") "
1584
0
                     "but file is no longer in define mode (id #%d, band #%d)",
1585
0
                     static_cast<GIntBig>(nNoData),
1586
0
                     static_cast<GIntBig>(m_nNodataValueInt64), cdfid, nBand);
1587
0
        }
1588
#ifdef NCDF_DEBUG
1589
        else
1590
        {
1591
            CPLDebug("GDAL_netCDF",
1592
                     "Setting NoDataValue to " CPL_FRMT_GIB
1593
                     " (id #%d, band #%d)",
1594
                     static_cast<GIntBig>(nNoData), cdfid, nBand);
1595
        }
1596
#endif
1597
        // Make sure we are in define mode.
1598
0
        reinterpret_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1599
1600
0
        int status;
1601
0
        if (eDataType == GDT_Int64 &&
1602
0
            reinterpret_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
1603
0
        {
1604
0
            long long tmp = static_cast<long long>(nNoData);
1605
0
            status = nc_put_att_longlong(cdfid, nZId, NCDF_FillValue,
1606
0
                                         nc_datatype, 1, &tmp);
1607
0
        }
1608
0
        else
1609
0
        {
1610
0
            double dfNoData = static_cast<double>(nNoData);
1611
0
            status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
1612
0
                                       1, &dfNoData);
1613
0
        }
1614
1615
0
        NCDF_ERR(status);
1616
1617
        // Update status if write worked.
1618
0
        if (status == NC_NOERR)
1619
0
        {
1620
0
            SetNoDataValueNoUpdate(nNoData);
1621
0
            return CE_None;
1622
0
        }
1623
1624
0
        return CE_Failure;
1625
0
    }
1626
1627
0
    SetNoDataValueNoUpdate(nNoData);
1628
0
    return CE_None;
1629
0
}
1630
1631
/************************************************************************/
1632
/*                       SetNoDataValueNoUpdate()                       */
1633
/************************************************************************/
1634
1635
void netCDFRasterBand::SetNoDataValueNoUpdate(int64_t nNoData)
1636
0
{
1637
0
    m_nNodataValueInt64 = nNoData;
1638
0
    m_bNoDataSet = false;
1639
0
    m_bNoDataSetAsInt64 = true;
1640
0
    m_bNoDataSetAsUInt64 = false;
1641
0
}
1642
1643
/************************************************************************/
1644
/*                        SetNoDataValueAsUInt64()                      */
1645
/************************************************************************/
1646
1647
CPLErr netCDFRasterBand::SetNoDataValueAsUInt64(uint64_t nNoData)
1648
1649
0
{
1650
0
    CPLMutexHolderD(&hNCMutex);
1651
1652
    // If already set to new value, don't do anything.
1653
0
    if (m_bNoDataSetAsUInt64 && nNoData == m_nNodataValueUInt64)
1654
0
        return CE_None;
1655
1656
    // Write value if in update mode.
1657
0
    if (poDS->GetAccess() == GA_Update)
1658
0
    {
1659
        // netcdf-4 does not allow to set _FillValue after leaving define mode,
1660
        // but it is ok if variable has not been written to, so only print
1661
        // debug. See bug #4484.
1662
0
        if (m_bNoDataSetAsUInt64 &&
1663
0
            !reinterpret_cast<netCDFDataset *>(poDS)->GetDefineMode())
1664
0
        {
1665
0
            CPLDebug("GDAL_netCDF",
1666
0
                     "Setting NoDataValue to " CPL_FRMT_GUIB
1667
0
                     " (previously set to " CPL_FRMT_GUIB ") "
1668
0
                     "but file is no longer in define mode (id #%d, band #%d)",
1669
0
                     static_cast<GUIntBig>(nNoData),
1670
0
                     static_cast<GUIntBig>(m_nNodataValueUInt64), cdfid, nBand);
1671
0
        }
1672
#ifdef NCDF_DEBUG
1673
        else
1674
        {
1675
            CPLDebug("GDAL_netCDF",
1676
                     "Setting NoDataValue to " CPL_FRMT_GUIB
1677
                     " (id #%d, band #%d)",
1678
                     static_cast<GUIntBig>(nNoData), cdfid, nBand);
1679
        }
1680
#endif
1681
        // Make sure we are in define mode.
1682
0
        reinterpret_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1683
1684
0
        int status;
1685
0
        if (eDataType == GDT_UInt64 &&
1686
0
            reinterpret_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
1687
0
        {
1688
0
            unsigned long long tmp = static_cast<long long>(nNoData);
1689
0
            status = nc_put_att_ulonglong(cdfid, nZId, NCDF_FillValue,
1690
0
                                          nc_datatype, 1, &tmp);
1691
0
        }
1692
0
        else
1693
0
        {
1694
0
            double dfNoData = static_cast<double>(nNoData);
1695
0
            status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
1696
0
                                       1, &dfNoData);
1697
0
        }
1698
1699
0
        NCDF_ERR(status);
1700
1701
        // Update status if write worked.
1702
0
        if (status == NC_NOERR)
1703
0
        {
1704
0
            SetNoDataValueNoUpdate(nNoData);
1705
0
            return CE_None;
1706
0
        }
1707
1708
0
        return CE_Failure;
1709
0
    }
1710
1711
0
    SetNoDataValueNoUpdate(nNoData);
1712
0
    return CE_None;
1713
0
}
1714
1715
/************************************************************************/
1716
/*                       SetNoDataValueNoUpdate()                       */
1717
/************************************************************************/
1718
1719
void netCDFRasterBand::SetNoDataValueNoUpdate(uint64_t nNoData)
1720
0
{
1721
0
    m_nNodataValueUInt64 = nNoData;
1722
0
    m_bNoDataSet = false;
1723
0
    m_bNoDataSetAsInt64 = false;
1724
0
    m_bNoDataSetAsUInt64 = true;
1725
0
}
1726
1727
/************************************************************************/
1728
/*                        DeleteNoDataValue()                           */
1729
/************************************************************************/
1730
1731
#ifdef notdef
1732
CPLErr netCDFRasterBand::DeleteNoDataValue()
1733
1734
{
1735
    CPLMutexHolderD(&hNCMutex);
1736
1737
    if (!bNoDataSet)
1738
        return CE_None;
1739
1740
    // Write value if in update mode.
1741
    if (poDS->GetAccess() == GA_Update)
1742
    {
1743
        // Make sure we are in define mode.
1744
        static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
1745
1746
        status = nc_del_att(cdfid, nZId, NCDF_FillValue);
1747
1748
        NCDF_ERR(status);
1749
1750
        // Update status if write worked.
1751
        if (status == NC_NOERR)
1752
        {
1753
            dfNoDataValue = 0.0;
1754
            bNoDataSet = false;
1755
            return CE_None;
1756
        }
1757
1758
        return CE_Failure;
1759
    }
1760
1761
    dfNoDataValue = 0.0;
1762
    bNoDataSet = false;
1763
    return CE_None;
1764
}
1765
#endif
1766
1767
/************************************************************************/
1768
/*                           SerializeToXML()                           */
1769
/************************************************************************/
1770
1771
CPLXMLNode *netCDFRasterBand::SerializeToXML(const char * /* pszUnused */)
1772
0
{
1773
    // Overridden from GDALPamDataset to add only band histogram
1774
    // and statistics. See bug #4244.
1775
0
    if (psPam == nullptr)
1776
0
        return nullptr;
1777
1778
    // Setup root node and attributes.
1779
0
    CPLXMLNode *psTree =
1780
0
        CPLCreateXMLNode(nullptr, CXT_Element, "PAMRasterBand");
1781
1782
0
    if (GetBand() > 0)
1783
0
    {
1784
0
        CPLString oFmt;
1785
0
        CPLSetXMLValue(psTree, "#band", oFmt.Printf("%d", GetBand()));
1786
0
    }
1787
1788
    // Histograms.
1789
0
    if (psPam->psSavedHistograms != nullptr)
1790
0
        CPLAddXMLChild(psTree, CPLCloneXMLTree(psPam->psSavedHistograms));
1791
1792
    // Metadata (statistics only).
1793
0
    GDALMultiDomainMetadata oMDMDStats;
1794
0
    const char *papszMDStats[] = {"STATISTICS_MINIMUM", "STATISTICS_MAXIMUM",
1795
0
                                  "STATISTICS_MEAN", "STATISTICS_STDDEV",
1796
0
                                  nullptr};
1797
0
    for (int i = 0; i < CSLCount(papszMDStats); i++)
1798
0
    {
1799
0
        const char *pszMDI = GetMetadataItem(papszMDStats[i]);
1800
0
        if (pszMDI)
1801
0
            oMDMDStats.SetMetadataItem(papszMDStats[i], pszMDI);
1802
0
    }
1803
0
    CPLXMLNode *psMD = oMDMDStats.Serialize();
1804
1805
0
    if (psMD != nullptr)
1806
0
    {
1807
0
        if (psMD->psChild == nullptr)
1808
0
            CPLDestroyXMLNode(psMD);
1809
0
        else
1810
0
            CPLAddXMLChild(psTree, psMD);
1811
0
    }
1812
1813
    // We don't want to return anything if we had no metadata to attach.
1814
0
    if (psTree->psChild == nullptr || psTree->psChild->psNext == nullptr)
1815
0
    {
1816
0
        CPLDestroyXMLNode(psTree);
1817
0
        psTree = nullptr;
1818
0
    }
1819
1820
0
    return psTree;
1821
0
}
1822
1823
/************************************************************************/
1824
/*               Get1DVariableIndexedByDimension()                      */
1825
/************************************************************************/
1826
1827
static int Get1DVariableIndexedByDimension(int cdfid, int nDimId,
1828
                                           const char *pszDimName,
1829
                                           bool bVerboseError, int *pnGroupID)
1830
0
{
1831
0
    *pnGroupID = -1;
1832
0
    int nVarID = -1;
1833
    // First try to find a variable whose name is identical to the dimension
1834
    // name, and check that it is indeed indexed by this dimension
1835
0
    if (NCDFResolveVar(cdfid, pszDimName, pnGroupID, &nVarID) == CE_None)
1836
0
    {
1837
0
        int nDimCountOfVariable = 0;
1838
0
        nc_inq_varndims(*pnGroupID, nVarID, &nDimCountOfVariable);
1839
0
        if (nDimCountOfVariable == 1)
1840
0
        {
1841
0
            int nDimIdOfVariable = -1;
1842
0
            nc_inq_vardimid(*pnGroupID, nVarID, &nDimIdOfVariable);
1843
0
            if (nDimIdOfVariable == nDimId)
1844
0
            {
1845
0
                return nVarID;
1846
0
            }
1847
0
        }
1848
0
    }
1849
1850
    // Otherwise iterate over the variables to find potential candidates
1851
    // TODO: should be modified to search also in other groups using the same
1852
    //       logic than in NCDFResolveVar(), but maybe not needed if it's a
1853
    //       very rare case? and I think this is not CF compliant.
1854
0
    int nvars = 0;
1855
0
    CPL_IGNORE_RET_VAL(nc_inq(cdfid, nullptr, &nvars, nullptr, nullptr));
1856
1857
0
    int nCountCandidateVars = 0;
1858
0
    int nCandidateVarID = -1;
1859
0
    for (int k = 0; k < nvars; k++)
1860
0
    {
1861
0
        int nDimCountOfVariable = 0;
1862
0
        nc_inq_varndims(cdfid, k, &nDimCountOfVariable);
1863
0
        if (nDimCountOfVariable == 1)
1864
0
        {
1865
0
            int nDimIdOfVariable = -1;
1866
0
            nc_inq_vardimid(cdfid, k, &nDimIdOfVariable);
1867
0
            if (nDimIdOfVariable == nDimId)
1868
0
            {
1869
0
                nCountCandidateVars++;
1870
0
                nCandidateVarID = k;
1871
0
            }
1872
0
        }
1873
0
    }
1874
0
    if (nCountCandidateVars > 1)
1875
0
    {
1876
0
        if (bVerboseError)
1877
0
        {
1878
0
            CPLError(CE_Warning, CPLE_AppDefined,
1879
0
                     "Several 1D variables are indexed by dimension %s",
1880
0
                     pszDimName);
1881
0
        }
1882
0
        *pnGroupID = -1;
1883
0
        return -1;
1884
0
    }
1885
0
    else if (nCandidateVarID < 0)
1886
0
    {
1887
0
        if (bVerboseError)
1888
0
        {
1889
0
            CPLError(CE_Warning, CPLE_AppDefined,
1890
0
                     "No 1D variable is indexed by dimension %s", pszDimName);
1891
0
        }
1892
0
    }
1893
0
    *pnGroupID = cdfid;
1894
0
    return nCandidateVarID;
1895
0
}
1896
1897
/************************************************************************/
1898
/*                      CreateMetadataFromAttributes()                  */
1899
/************************************************************************/
1900
1901
void netCDFRasterBand::CreateMetadataFromAttributes()
1902
0
{
1903
0
    char szVarName[NC_MAX_NAME + 1] = {};
1904
0
    int status = nc_inq_varname(cdfid, nZId, szVarName);
1905
0
    NCDF_ERR(status);
1906
1907
0
    GDALPamRasterBand::SetMetadataItem("NETCDF_VARNAME", szVarName);
1908
1909
    // Get attribute metadata.
1910
0
    int nAtt = 0;
1911
0
    NCDF_ERR(nc_inq_varnatts(cdfid, nZId, &nAtt));
1912
1913
0
    for (int i = 0; i < nAtt; i++)
1914
0
    {
1915
0
        char szMetaName[NC_MAX_NAME + 1] = {};
1916
0
        status = nc_inq_attname(cdfid, nZId, i, szMetaName);
1917
0
        if (status != NC_NOERR)
1918
0
            continue;
1919
1920
0
        if (GDALPamRasterBand::GetMetadataItem(szMetaName) != nullptr)
1921
0
        {
1922
0
            continue;
1923
0
        }
1924
1925
0
        char *pszMetaValue = nullptr;
1926
0
        if (NCDFGetAttr(cdfid, nZId, szMetaName, &pszMetaValue) == CE_None)
1927
0
        {
1928
0
            GDALPamRasterBand::SetMetadataItem(szMetaName, pszMetaValue);
1929
0
        }
1930
0
        else
1931
0
        {
1932
0
            CPLDebug("GDAL_netCDF", "invalid Band metadata %s", szMetaName);
1933
0
        }
1934
1935
0
        if (pszMetaValue)
1936
0
        {
1937
0
            CPLFree(pszMetaValue);
1938
0
            pszMetaValue = nullptr;
1939
0
        }
1940
0
    }
1941
0
}
1942
1943
/************************************************************************/
1944
/*                      CreateMetadataFromOtherVars()                   */
1945
/************************************************************************/
1946
1947
void netCDFRasterBand::CreateMetadataFromOtherVars()
1948
1949
0
{
1950
0
    CPLAssert(!m_bCreateMetadataFromOtherVarsDone);
1951
0
    m_bCreateMetadataFromOtherVarsDone = true;
1952
1953
0
    netCDFDataset *l_poDS = reinterpret_cast<netCDFDataset *>(poDS);
1954
0
    const int nPamFlagsBackup = l_poDS->nPamFlags;
1955
1956
    // Compute all dimensions from Band number and save in Metadata.
1957
0
    int nd = 0;
1958
0
    nc_inq_varndims(cdfid, nZId, &nd);
1959
    // Compute multidimention band position.
1960
    //
1961
    // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
1962
    // if Data[2,3,4,x,y]
1963
    //
1964
    //  BandPos0 = (nBand) / (3*4)
1965
    //  BandPos1 = (nBand - BandPos0*(3*4)) / (4)
1966
    //  BandPos2 = (nBand - BandPos0*(3*4)) % (4)
1967
1968
0
    int Sum = 1;
1969
0
    if (nd == 3)
1970
0
    {
1971
0
        Sum *= panBandZLev[0];
1972
0
    }
1973
1974
    // Loop over non-spatial dimensions.
1975
0
    int Taken = 0;
1976
1977
0
    for (int i = 0; i < nd - 2; i++)
1978
0
    {
1979
0
        int result;
1980
0
        if (i != nd - 2 - 1)
1981
0
        {
1982
0
            Sum = 1;
1983
0
            for (int j = i + 1; j < nd - 2; j++)
1984
0
            {
1985
0
                Sum *= panBandZLev[j];
1986
0
            }
1987
0
            result = static_cast<int>((nLevel - Taken) / Sum);
1988
0
        }
1989
0
        else
1990
0
        {
1991
0
            result = static_cast<int>((nLevel - Taken) % Sum);
1992
0
        }
1993
1994
0
        char szName[NC_MAX_NAME + 1] = {};
1995
0
        snprintf(szName, sizeof(szName), "%s",
1996
0
                 l_poDS->papszDimName[l_poDS->m_anDimIds[panBandZPos[i]]]);
1997
1998
0
        char szMetaName[NC_MAX_NAME + 1 + 32];
1999
0
        snprintf(szMetaName, sizeof(szMetaName), "NETCDF_DIM_%s", szName);
2000
2001
0
        const int nGroupID = l_poDS->m_anExtraDimGroupIds[i];
2002
0
        const int nVarID = l_poDS->m_anExtraDimVarIds[i];
2003
0
        if (nVarID < 0)
2004
0
        {
2005
0
            GDALPamRasterBand::SetMetadataItem(szMetaName,
2006
0
                                               CPLSPrintf("%d", result + 1));
2007
0
        }
2008
0
        else
2009
0
        {
2010
            // TODO: Make sure all the status checks make sense.
2011
2012
0
            nc_type nVarType = NC_NAT;
2013
0
            /* status = */ nc_inq_vartype(nGroupID, nVarID, &nVarType);
2014
2015
0
            int nDims = 0;
2016
0
            /* status = */ nc_inq_varndims(nGroupID, nVarID, &nDims);
2017
2018
0
            char szMetaTemp[256] = {};
2019
0
            if (nDims == 1)
2020
0
            {
2021
0
                size_t count[1] = {1};
2022
0
                size_t start[1] = {static_cast<size_t>(result)};
2023
2024
0
                switch (nVarType)
2025
0
                {
2026
0
                    case NC_BYTE:
2027
                        // TODO: Check for signed/unsigned byte.
2028
0
                        signed char cData;
2029
0
                        /* status = */ nc_get_vara_schar(nGroupID, nVarID,
2030
0
                                                         start, count, &cData);
2031
0
                        snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", cData);
2032
0
                        break;
2033
0
                    case NC_SHORT:
2034
0
                        short sData;
2035
0
                        /* status = */ nc_get_vara_short(nGroupID, nVarID,
2036
0
                                                         start, count, &sData);
2037
0
                        snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", sData);
2038
0
                        break;
2039
0
                    case NC_INT:
2040
0
                    {
2041
0
                        int nData;
2042
0
                        /* status = */ nc_get_vara_int(nGroupID, nVarID, start,
2043
0
                                                       count, &nData);
2044
0
                        snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", nData);
2045
0
                        break;
2046
0
                    }
2047
0
                    case NC_FLOAT:
2048
0
                        float fData;
2049
0
                        /* status = */ nc_get_vara_float(nGroupID, nVarID,
2050
0
                                                         start, count, &fData);
2051
0
                        CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.8g",
2052
0
                                    fData);
2053
0
                        break;
2054
0
                    case NC_DOUBLE:
2055
0
                        double dfData;
2056
0
                        /* status = */ nc_get_vara_double(
2057
0
                            nGroupID, nVarID, start, count, &dfData);
2058
0
                        CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.16g",
2059
0
                                    dfData);
2060
0
                        break;
2061
0
                    case NC_UBYTE:
2062
0
                        unsigned char ucData;
2063
0
                        /* status = */ nc_get_vara_uchar(nGroupID, nVarID,
2064
0
                                                         start, count, &ucData);
2065
0
                        snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", ucData);
2066
0
                        break;
2067
0
                    case NC_USHORT:
2068
0
                        unsigned short usData;
2069
0
                        /* status = */ nc_get_vara_ushort(
2070
0
                            nGroupID, nVarID, start, count, &usData);
2071
0
                        snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", usData);
2072
0
                        break;
2073
0
                    case NC_UINT:
2074
0
                    {
2075
0
                        unsigned int unData;
2076
0
                        /* status = */ nc_get_vara_uint(nGroupID, nVarID, start,
2077
0
                                                        count, &unData);
2078
0
                        snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", unData);
2079
0
                        break;
2080
0
                    }
2081
0
                    case NC_INT64:
2082
0
                    {
2083
0
                        long long nData;
2084
0
                        /* status = */ nc_get_vara_longlong(
2085
0
                            nGroupID, nVarID, start, count, &nData);
2086
0
                        snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GIB,
2087
0
                                 nData);
2088
0
                        break;
2089
0
                    }
2090
0
                    case NC_UINT64:
2091
0
                    {
2092
0
                        unsigned long long unData;
2093
0
                        /* status = */ nc_get_vara_ulonglong(
2094
0
                            nGroupID, nVarID, start, count, &unData);
2095
0
                        snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GUIB,
2096
0
                                 unData);
2097
0
                        break;
2098
0
                    }
2099
0
                    default:
2100
0
                        CPLDebug("GDAL_netCDF", "invalid dim %s, type=%d",
2101
0
                                 szMetaTemp, nVarType);
2102
0
                        break;
2103
0
                }
2104
0
            }
2105
0
            else
2106
0
            {
2107
0
                snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", result + 1);
2108
0
            }
2109
2110
            // Save dimension value.
2111
            // NOTE: removed #original_units as not part of CF-1.
2112
2113
0
            GDALPamRasterBand::SetMetadataItem(szMetaName, szMetaTemp);
2114
0
        }
2115
2116
        // Avoid int32 overflow. Perhaps something more sensible to do here ?
2117
0
        if (result > 0 && Sum > INT_MAX / result)
2118
0
            break;
2119
0
        if (Taken > INT_MAX - result * Sum)
2120
0
            break;
2121
2122
0
        Taken += result * Sum;
2123
0
    }  // End loop non-spatial dimensions.
2124
2125
0
    l_poDS->nPamFlags = nPamFlagsBackup;
2126
0
}
2127
2128
/************************************************************************/
2129
/*                             CheckData()                              */
2130
/************************************************************************/
2131
template <class T>
2132
void netCDFRasterBand::CheckData(void *pImage, void *pImageNC,
2133
                                 size_t nTmpBlockXSize, size_t nTmpBlockYSize,
2134
                                 bool bCheckIsNan)
2135
0
{
2136
0
    CPLAssert(pImage != nullptr && pImageNC != nullptr);
2137
2138
    // If this block is not a full block (in the x axis), we need to re-arrange
2139
    // the data this is because partial blocks are not arranged the same way in
2140
    // netcdf and gdal.
2141
0
    if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
2142
0
    {
2143
0
        T *ptrWrite = static_cast<T *>(pImage);
2144
0
        T *ptrRead = static_cast<T *>(pImageNC);
2145
0
        for (size_t j = 0; j < nTmpBlockYSize;
2146
0
             j++, ptrWrite += nBlockXSize, ptrRead += nTmpBlockXSize)
2147
0
        {
2148
0
            memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T));
2149
0
        }
2150
0
    }
2151
2152
    // Is valid data checking needed or requested?
2153
0
    if (bValidRangeValid || bCheckIsNan)
2154
0
    {
2155
0
        T *ptrImage = static_cast<T *>(pImage);
2156
0
        for (size_t j = 0; j < nTmpBlockYSize; j++)
2157
0
        {
2158
            // k moves along the gdal block, skipping the out-of-range pixels.
2159
0
            size_t k = j * nBlockXSize;
2160
0
            for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
2161
0
            {
2162
                // Check for nodata and nan.
2163
0
                if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
2164
0
                    continue;
2165
0
                if (bCheckIsNan && std::isnan((double)ptrImage[k]))
2166
0
                {
2167
0
                    ptrImage[k] = (T)m_dfNoDataValue;
2168
0
                    continue;
2169
0
                }
2170
                // Check for valid_range.
2171
0
                if (bValidRangeValid)
2172
0
                {
2173
0
                    if (((adfValidRange[0] != m_dfNoDataValue) &&
2174
0
                         (ptrImage[k] < (T)adfValidRange[0])) ||
2175
0
                        ((adfValidRange[1] != m_dfNoDataValue) &&
2176
0
                         (ptrImage[k] > (T)adfValidRange[1])))
2177
0
                    {
2178
0
                        ptrImage[k] = (T)m_dfNoDataValue;
2179
0
                    }
2180
0
                }
2181
0
            }
2182
0
        }
2183
0
    }
2184
2185
    // If minimum longitude is > 180, subtract 360 from all.
2186
    // If not, disable checking for further calls (check just once).
2187
    // Only check first and last block elements since lon must be monotonic.
2188
0
    const bool bIsSigned = std::numeric_limits<T>::is_signed;
2189
0
    if (bCheckLongitude && bIsSigned &&
2190
0
        !CPLIsEqual((double)((T *)pImage)[0], m_dfNoDataValue) &&
2191
0
        !CPLIsEqual((double)((T *)pImage)[nTmpBlockXSize - 1],
2192
0
                    m_dfNoDataValue) &&
2193
0
        std::min(((T *)pImage)[0], ((T *)pImage)[nTmpBlockXSize - 1]) > 180.0)
2194
0
    {
2195
0
        T *ptrImage = static_cast<T *>(pImage);
2196
0
        for (size_t j = 0; j < nTmpBlockYSize; j++)
2197
0
        {
2198
0
            size_t k = j * nBlockXSize;
2199
0
            for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
2200
0
            {
2201
0
                if (!CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
2202
0
                    ptrImage[k] = static_cast<T>(ptrImage[k] - 360);
2203
0
            }
2204
0
        }
2205
0
    }
2206
0
    else
2207
0
    {
2208
0
        bCheckLongitude = false;
2209
0
    }
2210
0
}
Unexecuted instantiation: void netCDFRasterBand::CheckData<signed char>(void*, void*, unsigned long, unsigned long, bool)
Unexecuted instantiation: void netCDFRasterBand::CheckData<unsigned char>(void*, void*, unsigned long, unsigned long, bool)
Unexecuted instantiation: void netCDFRasterBand::CheckData<short>(void*, void*, unsigned long, unsigned long, bool)
Unexecuted instantiation: void netCDFRasterBand::CheckData<unsigned short>(void*, void*, unsigned long, unsigned long, bool)
Unexecuted instantiation: void netCDFRasterBand::CheckData<int>(void*, void*, unsigned long, unsigned long, bool)
Unexecuted instantiation: void netCDFRasterBand::CheckData<float>(void*, void*, unsigned long, unsigned long, bool)
Unexecuted instantiation: void netCDFRasterBand::CheckData<double>(void*, void*, unsigned long, unsigned long, bool)
Unexecuted instantiation: void netCDFRasterBand::CheckData<unsigned int>(void*, void*, unsigned long, unsigned long, bool)
Unexecuted instantiation: void netCDFRasterBand::CheckData<long>(void*, void*, unsigned long, unsigned long, bool)
Unexecuted instantiation: void netCDFRasterBand::CheckData<unsigned long>(void*, void*, unsigned long, unsigned long, bool)
2211
2212
/************************************************************************/
2213
/*                             CheckDataCpx()                              */
2214
/************************************************************************/
2215
template <class T>
2216
void netCDFRasterBand::CheckDataCpx(void *pImage, void *pImageNC,
2217
                                    size_t nTmpBlockXSize,
2218
                                    size_t nTmpBlockYSize, bool bCheckIsNan)
2219
0
{
2220
0
    CPLAssert(pImage != nullptr && pImageNC != nullptr);
2221
2222
    // If this block is not a full block (in the x axis), we need to re-arrange
2223
    // the data this is because partial blocks are not arranged the same way in
2224
    // netcdf and gdal.
2225
0
    if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
2226
0
    {
2227
0
        T *ptrWrite = static_cast<T *>(pImage);
2228
0
        T *ptrRead = static_cast<T *>(pImageNC);
2229
0
        for (size_t j = 0; j < nTmpBlockYSize; j++,
2230
0
                    ptrWrite += (2 * nBlockXSize),
2231
0
                    ptrRead += (2 * nTmpBlockXSize))
2232
0
        {
2233
0
            memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T) * 2);
2234
0
        }
2235
0
    }
2236
2237
    // Is valid data checking needed or requested?
2238
0
    if (bValidRangeValid || bCheckIsNan)
2239
0
    {
2240
0
        T *ptrImage = static_cast<T *>(pImage);
2241
0
        for (size_t j = 0; j < nTmpBlockYSize; j++)
2242
0
        {
2243
            // k moves along the gdal block, skipping the out-of-range pixels.
2244
0
            size_t k = 2 * j * nBlockXSize;
2245
0
            for (size_t i = 0; i < (2 * nTmpBlockXSize); i++, k++)
2246
0
            {
2247
                // Check for nodata and nan.
2248
0
                if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
2249
0
                    continue;
2250
0
                if (bCheckIsNan && std::isnan((double)ptrImage[k]))
2251
0
                {
2252
0
                    ptrImage[k] = (T)m_dfNoDataValue;
2253
0
                    continue;
2254
0
                }
2255
                // Check for valid_range.
2256
0
                if (bValidRangeValid)
2257
0
                {
2258
0
                    if (((adfValidRange[0] != m_dfNoDataValue) &&
2259
0
                         (ptrImage[k] < (T)adfValidRange[0])) ||
2260
0
                        ((adfValidRange[1] != m_dfNoDataValue) &&
2261
0
                         (ptrImage[k] > (T)adfValidRange[1])))
2262
0
                    {
2263
0
                        ptrImage[k] = (T)m_dfNoDataValue;
2264
0
                    }
2265
0
                }
2266
0
            }
2267
0
        }
2268
0
    }
2269
0
}
Unexecuted instantiation: void netCDFRasterBand::CheckDataCpx<short>(void*, void*, unsigned long, unsigned long, bool)
Unexecuted instantiation: void netCDFRasterBand::CheckDataCpx<int>(void*, void*, unsigned long, unsigned long, bool)
Unexecuted instantiation: void netCDFRasterBand::CheckDataCpx<float>(void*, void*, unsigned long, unsigned long, bool)
Unexecuted instantiation: void netCDFRasterBand::CheckDataCpx<double>(void*, void*, unsigned long, unsigned long, bool)
2270
2271
/************************************************************************/
2272
/*                         FetchNetcdfChunk()                           */
2273
/************************************************************************/
2274
2275
bool netCDFRasterBand::FetchNetcdfChunk(size_t xstart, size_t ystart,
2276
                                        void *pImage)
2277
0
{
2278
0
    size_t start[MAX_NC_DIMS] = {};
2279
0
    size_t edge[MAX_NC_DIMS] = {};
2280
2281
0
    start[nBandXPos] = xstart;
2282
0
    edge[nBandXPos] = nBlockXSize;
2283
0
    if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
2284
0
        edge[nBandXPos] = nRasterXSize - start[nBandXPos];
2285
0
    if (nBandYPos >= 0)
2286
0
    {
2287
0
        start[nBandYPos] = ystart;
2288
0
        edge[nBandYPos] = nBlockYSize;
2289
0
        if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
2290
0
            edge[nBandYPos] = nRasterYSize - start[nBandYPos];
2291
0
    }
2292
0
    const size_t nYChunkSize = nBandYPos < 0 ? 1 : edge[nBandYPos];
2293
2294
#ifdef NCDF_DEBUG
2295
    CPLDebug("GDAL_netCDF", "start={%ld,%ld} edge={%ld,%ld} bBottomUp=%d",
2296
             start[nBandXPos], nBandYPos < 0 ? 0 : start[nBandYPos],
2297
             edge[nBandXPos], nYChunkSize, ((netCDFDataset *)poDS)->bBottomUp);
2298
#endif
2299
2300
0
    int nd = 0;
2301
0
    nc_inq_varndims(cdfid, nZId, &nd);
2302
0
    if (nd == 3)
2303
0
    {
2304
0
        start[panBandZPos[0]] = nLevel;  // z
2305
0
        edge[panBandZPos[0]] = 1;
2306
0
    }
2307
2308
    // Compute multidimention band position.
2309
    //
2310
    // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
2311
    // if Data[2,3,4,x,y]
2312
    //
2313
    //  BandPos0 = (nBand) / (3*4)
2314
    //  BandPos1 = (nBand - (3*4)) / (4)
2315
    //  BandPos2 = (nBand - (3*4)) % (4)
2316
0
    if (nd > 3)
2317
0
    {
2318
0
        int Sum = -1;
2319
0
        int Taken = 0;
2320
0
        for (int i = 0; i < nd - 2; i++)
2321
0
        {
2322
0
            if (i != nd - 2 - 1)
2323
0
            {
2324
0
                Sum = 1;
2325
0
                for (int j = i + 1; j < nd - 2; j++)
2326
0
                {
2327
0
                    Sum *= panBandZLev[j];
2328
0
                }
2329
0
                start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
2330
0
                edge[panBandZPos[i]] = 1;
2331
0
            }
2332
0
            else
2333
0
            {
2334
0
                start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
2335
0
                edge[panBandZPos[i]] = 1;
2336
0
            }
2337
0
            Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
2338
0
        }
2339
0
    }
2340
2341
    // Make sure we are in data mode.
2342
0
    static_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
2343
2344
    // If this block is not a full block in the x axis, we need to
2345
    // re-arrange the data because partial blocks are not arranged the
2346
    // same way in netcdf and gdal, so we first we read the netcdf data at
2347
    // the end of the gdal block buffer then re-arrange rows in CheckData().
2348
0
    void *pImageNC = pImage;
2349
0
    if (edge[nBandXPos] != static_cast<size_t>(nBlockXSize))
2350
0
    {
2351
0
        pImageNC = static_cast<GByte *>(pImage) +
2352
0
                   ((static_cast<size_t>(nBlockXSize) * nBlockYSize -
2353
0
                     edge[nBandXPos] * nYChunkSize) *
2354
0
                    (GDALGetDataTypeSize(eDataType) / 8));
2355
0
    }
2356
2357
    // Read data according to type.
2358
0
    int status;
2359
0
    if (eDataType == GDT_Byte)
2360
0
    {
2361
0
        if (bSignedData)
2362
0
        {
2363
0
            status = nc_get_vara_schar(cdfid, nZId, start, edge,
2364
0
                                       static_cast<signed char *>(pImageNC));
2365
0
            if (status == NC_NOERR)
2366
0
                CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
2367
0
                                       nYChunkSize, false);
2368
0
        }
2369
0
        else
2370
0
        {
2371
0
            status = nc_get_vara_uchar(cdfid, nZId, start, edge,
2372
0
                                       static_cast<unsigned char *>(pImageNC));
2373
0
            if (status == NC_NOERR)
2374
0
                CheckData<unsigned char>(pImage, pImageNC, edge[nBandXPos],
2375
0
                                         nYChunkSize, false);
2376
0
        }
2377
0
    }
2378
0
    else if (eDataType == GDT_Int8)
2379
0
    {
2380
0
        status = nc_get_vara_schar(cdfid, nZId, start, edge,
2381
0
                                   static_cast<signed char *>(pImageNC));
2382
0
        if (status == NC_NOERR)
2383
0
            CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
2384
0
                                   nYChunkSize, false);
2385
0
    }
2386
0
    else if (nc_datatype == NC_SHORT)
2387
0
    {
2388
0
        status = nc_get_vara_short(cdfid, nZId, start, edge,
2389
0
                                   static_cast<short *>(pImageNC));
2390
0
        if (status == NC_NOERR)
2391
0
        {
2392
0
            if (eDataType == GDT_Int16)
2393
0
            {
2394
0
                CheckData<GInt16>(pImage, pImageNC, edge[nBandXPos],
2395
0
                                  nYChunkSize, false);
2396
0
            }
2397
0
            else
2398
0
            {
2399
0
                CheckData<GUInt16>(pImage, pImageNC, edge[nBandXPos],
2400
0
                                   nYChunkSize, false);
2401
0
            }
2402
0
        }
2403
0
    }
2404
0
    else if (eDataType == GDT_Int32)
2405
0
    {
2406
#if SIZEOF_UNSIGNED_LONG == 4
2407
        status = nc_get_vara_long(cdfid, nZId, start, edge,
2408
                                  static_cast<long *>(pImageNC));
2409
        if (status == NC_NOERR)
2410
            CheckData<long>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2411
                            false);
2412
#else
2413
0
        status = nc_get_vara_int(cdfid, nZId, start, edge,
2414
0
                                 static_cast<int *>(pImageNC));
2415
0
        if (status == NC_NOERR)
2416
0
            CheckData<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2417
0
                           false);
2418
0
#endif
2419
0
    }
2420
0
    else if (eDataType == GDT_Float32)
2421
0
    {
2422
0
        status = nc_get_vara_float(cdfid, nZId, start, edge,
2423
0
                                   static_cast<float *>(pImageNC));
2424
0
        if (status == NC_NOERR)
2425
0
            CheckData<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2426
0
                             true);
2427
0
    }
2428
0
    else if (eDataType == GDT_Float64)
2429
0
    {
2430
0
        status = nc_get_vara_double(cdfid, nZId, start, edge,
2431
0
                                    static_cast<double *>(pImageNC));
2432
0
        if (status == NC_NOERR)
2433
0
            CheckData<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2434
0
                              true);
2435
0
    }
2436
0
    else if (eDataType == GDT_UInt16)
2437
0
    {
2438
0
        status = nc_get_vara_ushort(cdfid, nZId, start, edge,
2439
0
                                    static_cast<unsigned short *>(pImageNC));
2440
0
        if (status == NC_NOERR)
2441
0
            CheckData<unsigned short>(pImage, pImageNC, edge[nBandXPos],
2442
0
                                      nYChunkSize, false);
2443
0
    }
2444
0
    else if (eDataType == GDT_UInt32)
2445
0
    {
2446
0
        status = nc_get_vara_uint(cdfid, nZId, start, edge,
2447
0
                                  static_cast<unsigned int *>(pImageNC));
2448
0
        if (status == NC_NOERR)
2449
0
            CheckData<unsigned int>(pImage, pImageNC, edge[nBandXPos],
2450
0
                                    nYChunkSize, false);
2451
0
    }
2452
0
    else if (eDataType == GDT_Int64)
2453
0
    {
2454
0
        status = nc_get_vara_longlong(cdfid, nZId, start, edge,
2455
0
                                      static_cast<long long *>(pImageNC));
2456
0
        if (status == NC_NOERR)
2457
0
            CheckData<std::int64_t>(pImage, pImageNC, edge[nBandXPos],
2458
0
                                    nYChunkSize, false);
2459
0
    }
2460
0
    else if (eDataType == GDT_UInt64)
2461
0
    {
2462
0
        status =
2463
0
            nc_get_vara_ulonglong(cdfid, nZId, start, edge,
2464
0
                                  static_cast<unsigned long long *>(pImageNC));
2465
0
        if (status == NC_NOERR)
2466
0
            CheckData<std::uint64_t>(pImage, pImageNC, edge[nBandXPos],
2467
0
                                     nYChunkSize, false);
2468
0
    }
2469
0
    else if (eDataType == GDT_CInt16)
2470
0
    {
2471
0
        status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2472
0
        if (status == NC_NOERR)
2473
0
            CheckDataCpx<short>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2474
0
                                false);
2475
0
    }
2476
0
    else if (eDataType == GDT_CInt32)
2477
0
    {
2478
0
        status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2479
0
        if (status == NC_NOERR)
2480
0
            CheckDataCpx<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2481
0
                              false);
2482
0
    }
2483
0
    else if (eDataType == GDT_CFloat32)
2484
0
    {
2485
0
        status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2486
0
        if (status == NC_NOERR)
2487
0
            CheckDataCpx<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2488
0
                                false);
2489
0
    }
2490
0
    else if (eDataType == GDT_CFloat64)
2491
0
    {
2492
0
        status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
2493
0
        if (status == NC_NOERR)
2494
0
            CheckDataCpx<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
2495
0
                                 false);
2496
0
    }
2497
2498
0
    else
2499
0
        status = NC_EBADTYPE;
2500
2501
0
    if (status != NC_NOERR)
2502
0
    {
2503
0
        CPLError(CE_Failure, CPLE_AppDefined,
2504
0
                 "netCDF chunk fetch failed: #%d (%s)", status,
2505
0
                 nc_strerror(status));
2506
0
        return false;
2507
0
    }
2508
0
    return true;
2509
0
}
2510
2511
/************************************************************************/
2512
/*                             IReadBlock()                             */
2513
/************************************************************************/
2514
2515
CPLErr netCDFRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
2516
                                    void *pImage)
2517
2518
0
{
2519
0
    CPLMutexHolderD(&hNCMutex);
2520
2521
    // Locate X, Y and Z position in the array.
2522
2523
0
    size_t xstart = static_cast<size_t>(nBlockXOff) * nBlockXSize;
2524
0
    size_t ystart = 0;
2525
2526
    // Check y order.
2527
0
    if (nBandYPos >= 0)
2528
0
    {
2529
0
        auto poGDS = static_cast<netCDFDataset *>(poDS);
2530
0
        if (poGDS->bBottomUp)
2531
0
        {
2532
0
            if (nBlockYSize == 1)
2533
0
            {
2534
0
                ystart = nRasterYSize - 1 - nBlockYOff;
2535
0
            }
2536
0
            else
2537
0
            {
2538
                // in GDAL space
2539
0
                ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
2540
0
                const size_t yend =
2541
0
                    std::min(ystart + nBlockYSize - 1,
2542
0
                             static_cast<size_t>(nRasterYSize - 1));
2543
                // in netCDF space
2544
0
                const size_t nFirstChunkLine = nRasterYSize - 1 - yend;
2545
0
                const size_t nLastChunkLine = nRasterYSize - 1 - ystart;
2546
0
                const size_t nFirstChunkBlock = nFirstChunkLine / nBlockYSize;
2547
0
                const size_t nLastChunkBlock = nLastChunkLine / nBlockYSize;
2548
2549
0
                const auto firstKey = netCDFDataset::ChunkKey(
2550
0
                    nBlockXOff, nFirstChunkBlock, nBand);
2551
0
                const auto secondKey =
2552
0
                    netCDFDataset::ChunkKey(nBlockXOff, nLastChunkBlock, nBand);
2553
2554
                // Retrieve data from the one or 2 needed netCDF chunks
2555
0
                std::shared_ptr<std::vector<GByte>> firstChunk;
2556
0
                std::shared_ptr<std::vector<GByte>> secondChunk;
2557
0
                if (poGDS->poChunkCache)
2558
0
                {
2559
0
                    poGDS->poChunkCache->tryGet(firstKey, firstChunk);
2560
0
                    if (firstKey != secondKey)
2561
0
                        poGDS->poChunkCache->tryGet(secondKey, secondChunk);
2562
0
                }
2563
0
                const size_t nChunkLineSize =
2564
0
                    static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
2565
0
                    nBlockXSize;
2566
0
                const size_t nChunkSize = nChunkLineSize * nBlockYSize;
2567
0
                if (!firstChunk)
2568
0
                {
2569
0
                    firstChunk.reset(new std::vector<GByte>(nChunkSize));
2570
0
                    if (!FetchNetcdfChunk(xstart,
2571
0
                                          nFirstChunkBlock * nBlockYSize,
2572
0
                                          firstChunk.get()->data()))
2573
0
                        return CE_Failure;
2574
0
                    if (poGDS->poChunkCache)
2575
0
                        poGDS->poChunkCache->insert(firstKey, firstChunk);
2576
0
                }
2577
0
                if (!secondChunk && firstKey != secondKey)
2578
0
                {
2579
0
                    secondChunk.reset(new std::vector<GByte>(nChunkSize));
2580
0
                    if (!FetchNetcdfChunk(xstart, nLastChunkBlock * nBlockYSize,
2581
0
                                          secondChunk.get()->data()))
2582
0
                        return CE_Failure;
2583
0
                    if (poGDS->poChunkCache)
2584
0
                        poGDS->poChunkCache->insert(secondKey, secondChunk);
2585
0
                }
2586
2587
                // Assemble netCDF chunks into GDAL block
2588
0
                GByte *pabyImage = static_cast<GByte *>(pImage);
2589
0
                const size_t nFirstChunkBlockLine =
2590
0
                    nFirstChunkBlock * nBlockYSize;
2591
0
                const size_t nLastChunkBlockLine =
2592
0
                    nLastChunkBlock * nBlockYSize;
2593
0
                for (size_t iLine = ystart; iLine <= yend; iLine++)
2594
0
                {
2595
0
                    const size_t nLineFromBottom = nRasterYSize - 1 - iLine;
2596
0
                    const size_t nChunkY = nLineFromBottom / nBlockYSize;
2597
0
                    if (nChunkY == nFirstChunkBlock)
2598
0
                    {
2599
0
                        memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
2600
0
                               firstChunk.get()->data() +
2601
0
                                   (nLineFromBottom - nFirstChunkBlockLine) *
2602
0
                                       nChunkLineSize,
2603
0
                               nChunkLineSize);
2604
0
                    }
2605
0
                    else
2606
0
                    {
2607
0
                        CPLAssert(nChunkY == nLastChunkBlock);
2608
0
                        assert(secondChunk);
2609
0
                        memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
2610
0
                               secondChunk.get()->data() +
2611
0
                                   (nLineFromBottom - nLastChunkBlockLine) *
2612
0
                                       nChunkLineSize,
2613
0
                               nChunkLineSize);
2614
0
                    }
2615
0
                }
2616
0
                return CE_None;
2617
0
            }
2618
0
        }
2619
0
        else
2620
0
        {
2621
0
            ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
2622
0
        }
2623
0
    }
2624
2625
0
    return FetchNetcdfChunk(xstart, ystart, pImage) ? CE_None : CE_Failure;
2626
0
}
2627
2628
/************************************************************************/
2629
/*                             IWriteBlock()                            */
2630
/************************************************************************/
2631
2632
CPLErr netCDFRasterBand::IWriteBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
2633
                                     void *pImage)
2634
0
{
2635
0
    CPLMutexHolderD(&hNCMutex);
2636
2637
#ifdef NCDF_DEBUG
2638
    if (nBlockYOff == 0 || (nBlockYOff == nRasterYSize - 1))
2639
        CPLDebug("GDAL_netCDF",
2640
                 "netCDFRasterBand::IWriteBlock( %d, %d, ...) nBand=%d",
2641
                 nBlockXOff, nBlockYOff, nBand);
2642
#endif
2643
2644
0
    int nd = 0;
2645
0
    nc_inq_varndims(cdfid, nZId, &nd);
2646
2647
    // Locate X, Y and Z position in the array.
2648
2649
0
    size_t start[MAX_NC_DIMS];
2650
0
    memset(start, 0, sizeof(start));
2651
0
    start[nBandXPos] = static_cast<size_t>(nBlockXOff) * nBlockXSize;
2652
2653
    // check y order.
2654
0
    if (static_cast<netCDFDataset *>(poDS)->bBottomUp)
2655
0
    {
2656
0
        if (nBlockYSize == 1)
2657
0
        {
2658
0
            start[nBandYPos] = nRasterYSize - 1 - nBlockYOff;
2659
0
        }
2660
0
        else
2661
0
        {
2662
0
            CPLError(CE_Failure, CPLE_AppDefined,
2663
0
                     "nBlockYSize = %d, only 1 supported when "
2664
0
                     "writing bottom-up dataset",
2665
0
                     nBlockYSize);
2666
0
            return CE_Failure;
2667
0
        }
2668
0
    }
2669
0
    else
2670
0
    {
2671
0
        start[nBandYPos] = static_cast<size_t>(nBlockYOff) * nBlockYSize;  // y
2672
0
    }
2673
2674
0
    size_t edge[MAX_NC_DIMS] = {};
2675
2676
0
    edge[nBandXPos] = nBlockXSize;
2677
0
    if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
2678
0
        edge[nBandXPos] = nRasterXSize - start[nBandXPos];
2679
0
    edge[nBandYPos] = nBlockYSize;
2680
0
    if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
2681
0
        edge[nBandYPos] = nRasterYSize - start[nBandYPos];
2682
2683
0
    if (nd == 3)
2684
0
    {
2685
0
        start[panBandZPos[0]] = nLevel;  // z
2686
0
        edge[panBandZPos[0]] = 1;
2687
0
    }
2688
2689
    // Compute multidimention band position.
2690
    //
2691
    // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
2692
    // if Data[2,3,4,x,y]
2693
    //
2694
    //  BandPos0 = (nBand) / (3*4)
2695
    //  BandPos1 = (nBand - (3*4)) / (4)
2696
    //  BandPos2 = (nBand - (3*4)) % (4)
2697
0
    if (nd > 3)
2698
0
    {
2699
0
        int Sum = -1;
2700
0
        int Taken = 0;
2701
0
        for (int i = 0; i < nd - 2; i++)
2702
0
        {
2703
0
            if (i != nd - 2 - 1)
2704
0
            {
2705
0
                Sum = 1;
2706
0
                for (int j = i + 1; j < nd - 2; j++)
2707
0
                {
2708
0
                    Sum *= panBandZLev[j];
2709
0
                }
2710
0
                start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
2711
0
                edge[panBandZPos[i]] = 1;
2712
0
            }
2713
0
            else
2714
0
            {
2715
0
                start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
2716
0
                edge[panBandZPos[i]] = 1;
2717
0
            }
2718
0
            Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
2719
0
        }
2720
0
    }
2721
2722
    // Make sure we are in data mode.
2723
0
    static_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
2724
2725
    // Copy data according to type.
2726
0
    int status = 0;
2727
0
    if (eDataType == GDT_Byte)
2728
0
    {
2729
0
        if (bSignedData)
2730
0
            status = nc_put_vara_schar(cdfid, nZId, start, edge,
2731
0
                                       static_cast<signed char *>(pImage));
2732
0
        else
2733
0
            status = nc_put_vara_uchar(cdfid, nZId, start, edge,
2734
0
                                       static_cast<unsigned char *>(pImage));
2735
0
    }
2736
0
    else if (eDataType == GDT_Int8)
2737
0
    {
2738
0
        status = nc_put_vara_schar(cdfid, nZId, start, edge,
2739
0
                                   static_cast<signed char *>(pImage));
2740
0
    }
2741
0
    else if (nc_datatype == NC_SHORT)
2742
0
    {
2743
0
        status = nc_put_vara_short(cdfid, nZId, start, edge,
2744
0
                                   static_cast<short *>(pImage));
2745
0
    }
2746
0
    else if (eDataType == GDT_Int32)
2747
0
    {
2748
0
        status = nc_put_vara_int(cdfid, nZId, start, edge,
2749
0
                                 static_cast<int *>(pImage));
2750
0
    }
2751
0
    else if (eDataType == GDT_Float32)
2752
0
    {
2753
0
        status = nc_put_vara_float(cdfid, nZId, start, edge,
2754
0
                                   static_cast<float *>(pImage));
2755
0
    }
2756
0
    else if (eDataType == GDT_Float64)
2757
0
    {
2758
0
        status = nc_put_vara_double(cdfid, nZId, start, edge,
2759
0
                                    static_cast<double *>(pImage));
2760
0
    }
2761
0
    else if (eDataType == GDT_UInt16 &&
2762
0
             static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2763
0
    {
2764
0
        status = nc_put_vara_ushort(cdfid, nZId, start, edge,
2765
0
                                    static_cast<unsigned short *>(pImage));
2766
0
    }
2767
0
    else if (eDataType == GDT_UInt32 &&
2768
0
             static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2769
0
    {
2770
0
        status = nc_put_vara_uint(cdfid, nZId, start, edge,
2771
0
                                  static_cast<unsigned int *>(pImage));
2772
0
    }
2773
0
    else if (eDataType == GDT_UInt64 &&
2774
0
             static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2775
0
    {
2776
0
        status =
2777
0
            nc_put_vara_ulonglong(cdfid, nZId, start, edge,
2778
0
                                  static_cast<unsigned long long *>(pImage));
2779
0
    }
2780
0
    else if (eDataType == GDT_Int64 &&
2781
0
             static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
2782
0
    {
2783
0
        status = nc_put_vara_longlong(cdfid, nZId, start, edge,
2784
0
                                      static_cast<long long *>(pImage));
2785
0
    }
2786
0
    else
2787
0
    {
2788
0
        CPLError(CE_Failure, CPLE_NotSupported,
2789
0
                 "The NetCDF driver does not support GDAL data type %d",
2790
0
                 eDataType);
2791
0
        status = NC_EBADTYPE;
2792
0
    }
2793
0
    NCDF_ERR(status);
2794
2795
0
    if (status != NC_NOERR)
2796
0
    {
2797
0
        CPLError(CE_Failure, CPLE_AppDefined,
2798
0
                 "netCDF scanline write failed: %s", nc_strerror(status));
2799
0
        return CE_Failure;
2800
0
    }
2801
2802
0
    return CE_None;
2803
0
}
2804
2805
/************************************************************************/
2806
/* ==================================================================== */
2807
/*                              netCDFDataset                           */
2808
/* ==================================================================== */
2809
/************************************************************************/
2810
2811
/************************************************************************/
2812
/*                           netCDFDataset()                            */
2813
/************************************************************************/
2814
2815
netCDFDataset::netCDFDataset()
2816
    :
2817
// Basic dataset vars.
2818
#ifdef ENABLE_NCDUMP
2819
399
      bFileToDestroyAtClosing(false),
2820
#endif
2821
399
      cdfid(-1), nSubDatasets(0), papszSubDatasets(nullptr),
2822
399
      papszMetadata(nullptr), bBottomUp(true), eFormat(NCDF_FORMAT_NONE),
2823
399
      bIsGdalFile(false), bIsGdalCfFile(false), pszCFProjection(nullptr),
2824
399
      pszCFCoordinates(nullptr), nCFVersion(1.6), bSGSupport(false),
2825
399
      eMultipleLayerBehavior(SINGLE_LAYER), logCount(0), vcdf(this, cdfid),
2826
399
      GeometryScribe(vcdf, this->generateLogName()),
2827
399
      FieldScribe(vcdf, this->generateLogName()),
2828
399
      bufManager(CPLGetUsablePhysicalRAM() / 5),
2829
2830
      // projection/GT.
2831
399
      nXDimID(-1), nYDimID(-1), bIsProjected(false),
2832
399
      bIsGeographic(false),  // Can be not projected, and also not geographic
2833
      // State vars.
2834
399
      bDefineMode(true), bAddedGridMappingRef(false),
2835
2836
      // Create vars.
2837
399
      papszCreationOptions(nullptr), eCompress(NCDF_COMPRESS_NONE),
2838
399
      nZLevel(NCDF_DEFLATE_LEVEL), bChunking(false), nCreateMode(NC_CLOBBER),
2839
399
      bSignedData(true)
2840
399
{
2841
399
    m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2842
2843
    // Projection/GT.
2844
399
    m_adfGeoTransform[0] = 0.0;
2845
399
    m_adfGeoTransform[1] = 1.0;
2846
399
    m_adfGeoTransform[2] = 0.0;
2847
399
    m_adfGeoTransform[3] = 0.0;
2848
399
    m_adfGeoTransform[4] = 0.0;
2849
399
    m_adfGeoTransform[5] = 1.0;
2850
2851
    // Set buffers
2852
399
    bufManager.addBuffer(&(GeometryScribe.getMemBuffer()));
2853
399
    bufManager.addBuffer(&(FieldScribe.getMemBuffer()));
2854
399
}
2855
2856
/************************************************************************/
2857
/*                           ~netCDFDataset()                           */
2858
/************************************************************************/
2859
2860
netCDFDataset::~netCDFDataset()
2861
2862
399
{
2863
399
    netCDFDataset::Close();
2864
399
}
2865
2866
/************************************************************************/
2867
/*                              Close()                                 */
2868
/************************************************************************/
2869
2870
CPLErr netCDFDataset::Close()
2871
422
{
2872
422
    CPLErr eErr = CE_None;
2873
422
    if (nOpenFlags != OPEN_FLAGS_CLOSED)
2874
399
    {
2875
399
        CPLMutexHolderD(&hNCMutex);
2876
2877
#ifdef NCDF_DEBUG
2878
        CPLDebug("GDAL_netCDF",
2879
                 "netCDFDataset::~netCDFDataset(), cdfid=%d filename=%s", cdfid,
2880
                 osFilename.c_str());
2881
#endif
2882
2883
        // Write data related to geotransform
2884
399
        if (GetAccess() == GA_Update && !m_bAddedProjectionVarsData &&
2885
399
            (m_bHasProjection || m_bHasGeoTransform))
2886
0
        {
2887
            // Ensure projection is written if GeoTransform OR Projection are
2888
            // missing.
2889
0
            if (!m_bAddedProjectionVarsDefs)
2890
0
            {
2891
0
                AddProjectionVars(true, nullptr, nullptr);
2892
0
            }
2893
0
            AddProjectionVars(false, nullptr, nullptr);
2894
0
        }
2895
2896
399
        if (netCDFDataset::FlushCache(true) != CE_None)
2897
0
            eErr = CE_Failure;
2898
2899
399
        if (GetAccess() == GA_Update && !SGCommitPendingTransaction())
2900
0
            eErr = CE_Failure;
2901
2902
399
        for (size_t i = 0; i < apoVectorDatasets.size(); i++)
2903
0
            delete apoVectorDatasets[i];
2904
2905
        // Make sure projection variable is written to band variable.
2906
399
        if (GetAccess() == GA_Update && !bAddedGridMappingRef)
2907
0
        {
2908
0
            if (!AddGridMappingRef())
2909
0
                eErr = CE_Failure;
2910
0
        }
2911
2912
399
        CSLDestroy(papszMetadata);
2913
399
        CSLDestroy(papszSubDatasets);
2914
399
        CSLDestroy(papszCreationOptions);
2915
2916
399
        CPLFree(pszCFProjection);
2917
2918
399
        if (cdfid > 0)
2919
399
        {
2920
#ifdef NCDF_DEBUG
2921
            CPLDebug("GDAL_netCDF", "calling nc_close( %d)", cdfid);
2922
#endif
2923
399
            int status = GDAL_nc_close(cdfid);
2924
399
#ifdef ENABLE_UFFD
2925
399
            NETCDF_UFFD_UNMAP(pCtx);
2926
399
#endif
2927
399
            NCDF_ERR(status);
2928
399
            if (status != NC_NOERR)
2929
0
                eErr = CE_Failure;
2930
399
        }
2931
2932
399
        if (fpVSIMEM)
2933
0
            VSIFCloseL(fpVSIMEM);
2934
2935
399
#ifdef ENABLE_NCDUMP
2936
399
        if (bFileToDestroyAtClosing)
2937
0
            VSIUnlink(osFilename);
2938
399
#endif
2939
2940
399
        if (GDALPamDataset::Close() != CE_None)
2941
0
            eErr = CE_Failure;
2942
399
    }
2943
422
    return eErr;
2944
422
}
2945
2946
/************************************************************************/
2947
/*                            SetDefineMode()                           */
2948
/************************************************************************/
2949
bool netCDFDataset::SetDefineMode(bool bNewDefineMode)
2950
5.16k
{
2951
    // Do nothing if already in new define mode
2952
    // or if dataset is in read-only mode or if dataset is true NC4 dataset.
2953
5.16k
    if (bDefineMode == bNewDefineMode || GetAccess() == GA_ReadOnly ||
2954
5.16k
        eFormat == NCDF_FORMAT_NC4)
2955
5.16k
        return true;
2956
2957
0
    CPLDebug("GDAL_netCDF", "SetDefineMode(%d) old=%d",
2958
0
             static_cast<int>(bNewDefineMode), static_cast<int>(bDefineMode));
2959
2960
0
    bDefineMode = bNewDefineMode;
2961
2962
0
    int status;
2963
0
    if (bDefineMode)
2964
0
        status = nc_redef(cdfid);
2965
0
    else
2966
0
        status = nc_enddef(cdfid);
2967
2968
0
    NCDF_ERR(status);
2969
0
    return status == NC_NOERR;
2970
5.16k
}
2971
2972
/************************************************************************/
2973
/*                      GetMetadataDomainList()                         */
2974
/************************************************************************/
2975
2976
char **netCDFDataset::GetMetadataDomainList()
2977
0
{
2978
0
    char **papszDomains = BuildMetadataDomainList(
2979
0
        GDALDataset::GetMetadataDomainList(), TRUE, "SUBDATASETS", nullptr);
2980
0
    for (const auto &kv : m_oMapDomainToJSon)
2981
0
        papszDomains = CSLAddString(papszDomains, ("json:" + kv.first).c_str());
2982
0
    return papszDomains;
2983
0
}
2984
2985
/************************************************************************/
2986
/*                            GetMetadata()                             */
2987
/************************************************************************/
2988
char **netCDFDataset::GetMetadata(const char *pszDomain)
2989
0
{
2990
0
    if (pszDomain != nullptr && STARTS_WITH_CI(pszDomain, "SUBDATASETS"))
2991
0
        return papszSubDatasets;
2992
2993
0
    if (pszDomain != nullptr && STARTS_WITH(pszDomain, "json:"))
2994
0
    {
2995
0
        auto iter = m_oMapDomainToJSon.find(pszDomain + strlen("json:"));
2996
0
        if (iter != m_oMapDomainToJSon.end())
2997
0
            return iter->second.List();
2998
0
    }
2999
3000
0
    return GDALDataset::GetMetadata(pszDomain);
3001
0
}
3002
3003
/************************************************************************/
3004
/*                        SetMetadataItem()                             */
3005
/************************************************************************/
3006
3007
CPLErr netCDFDataset::SetMetadataItem(const char *pszName, const char *pszValue,
3008
                                      const char *pszDomain)
3009
0
{
3010
0
    if (GetAccess() == GA_Update &&
3011
0
        (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)
3012
0
    {
3013
0
        std::string osName(pszName);
3014
3015
        // Same logic as in CopyMetadata()
3016
0
        if (STARTS_WITH(osName.c_str(), "NC_GLOBAL#"))
3017
0
            osName = osName.substr(strlen("NC_GLOBAL#"));
3018
0
        else if (strchr(osName.c_str(), '#') == nullptr)
3019
0
            osName = "GDAL_" + osName;
3020
3021
0
        if (STARTS_WITH(osName.c_str(), "NETCDF_DIM_") ||
3022
0
            strchr(osName.c_str(), '#') != nullptr)
3023
0
        {
3024
            // do nothing
3025
0
            return CE_None;
3026
0
        }
3027
0
        else
3028
0
        {
3029
0
            SetDefineMode(true);
3030
3031
0
            if (!NCDFPutAttr(cdfid, NC_GLOBAL, osName.c_str(), pszValue))
3032
0
                return CE_Failure;
3033
0
        }
3034
0
    }
3035
3036
0
    return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);
3037
0
}
3038
3039
/************************************************************************/
3040
/*                          SetMetadata()                               */
3041
/************************************************************************/
3042
3043
CPLErr netCDFDataset::SetMetadata(char **papszMD, const char *pszDomain)
3044
0
{
3045
0
    if (GetAccess() == GA_Update &&
3046
0
        (pszDomain == nullptr || pszDomain[0] == '\0'))
3047
0
    {
3048
        // We don't handle metadata item removal for now
3049
0
        for (const char *const *papszIter = papszMD; papszIter && *papszIter;
3050
0
             ++papszIter)
3051
0
        {
3052
0
            char *pszName = nullptr;
3053
0
            const char *pszValue = CPLParseNameValue(*papszIter, &pszName);
3054
0
            if (pszName && pszValue)
3055
0
                SetMetadataItem(pszName, pszValue);
3056
0
            CPLFree(pszName);
3057
0
        }
3058
0
        return CE_None;
3059
0
    }
3060
0
    return GDALPamDataset::SetMetadata(papszMD, pszDomain);
3061
0
}
3062
3063
/************************************************************************/
3064
/*                          GetSpatialRef()                             */
3065
/************************************************************************/
3066
3067
const OGRSpatialReference *netCDFDataset::GetSpatialRef() const
3068
0
{
3069
0
    if (m_bHasProjection)
3070
0
        return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
3071
3072
0
    return GDALPamDataset::GetSpatialRef();
3073
0
}
3074
3075
/************************************************************************/
3076
/*                           FetchCopyParam()                            */
3077
/************************************************************************/
3078
3079
double netCDFDataset::FetchCopyParam(const char *pszGridMappingValue,
3080
                                     const char *pszParam, double dfDefault,
3081
                                     bool *pbFound)
3082
3083
0
{
3084
0
    char *pszTemp =
3085
0
        CPLStrdup(CPLSPrintf("%s#%s", pszGridMappingValue, pszParam));
3086
0
    const char *pszValue = CSLFetchNameValue(papszMetadata, pszTemp);
3087
0
    CPLFree(pszTemp);
3088
3089
0
    if (pbFound)
3090
0
    {
3091
0
        *pbFound = (pszValue != nullptr);
3092
0
    }
3093
3094
0
    if (pszValue)
3095
0
    {
3096
0
        return CPLAtofM(pszValue);
3097
0
    }
3098
3099
0
    return dfDefault;
3100
0
}
3101
3102
/************************************************************************/
3103
/*                           FetchStandardParallels()                   */
3104
/************************************************************************/
3105
3106
std::vector<std::string>
3107
netCDFDataset::FetchStandardParallels(const char *pszGridMappingValue)
3108
0
{
3109
    // cf-1.0 tags
3110
0
    const char *pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL);
3111
3112
0
    std::vector<std::string> ret;
3113
0
    if (pszValue != nullptr)
3114
0
    {
3115
0
        CPLStringList aosValues;
3116
0
        if (pszValue[0] != '{' &&
3117
0
            CPLString(pszValue).Trim().find(' ') != std::string::npos)
3118
0
        {
3119
            // Some files like
3120
            // ftp://data.knmi.nl/download/KNW-NetCDF-3D/1.0/noversion/2013/11/14/KNW-1.0_H37-ERA_NL_20131114.nc
3121
            // do not use standard formatting for arrays, but just space
3122
            // separated syntax
3123
0
            aosValues = CSLTokenizeString2(pszValue, " ", 0);
3124
0
        }
3125
0
        else
3126
0
        {
3127
0
            aosValues = NCDFTokenizeArray(pszValue);
3128
0
        }
3129
0
        for (int i = 0; i < aosValues.size(); i++)
3130
0
        {
3131
0
            ret.push_back(aosValues[i]);
3132
0
        }
3133
0
    }
3134
    // Try gdal tags.
3135
0
    else
3136
0
    {
3137
0
        pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_1);
3138
3139
0
        if (pszValue != nullptr)
3140
0
            ret.push_back(pszValue);
3141
3142
0
        pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_2);
3143
3144
0
        if (pszValue != nullptr)
3145
0
            ret.push_back(pszValue);
3146
0
    }
3147
3148
0
    return ret;
3149
0
}
3150
3151
/************************************************************************/
3152
/*                           FetchAttr()                                */
3153
/************************************************************************/
3154
3155
const char *netCDFDataset::FetchAttr(const char *pszVarFullName,
3156
                                     const char *pszAttr)
3157
3158
115
{
3159
115
    char *pszKey = CPLStrdup(CPLSPrintf("%s#%s", pszVarFullName, pszAttr));
3160
115
    const char *pszValue = CSLFetchNameValue(papszMetadata, pszKey);
3161
115
    CPLFree(pszKey);
3162
115
    return pszValue;
3163
115
}
3164
3165
const char *netCDFDataset::FetchAttr(int nGroupId, int nVarId,
3166
                                     const char *pszAttr)
3167
3168
69
{
3169
69
    char *pszVarFullName = nullptr;
3170
69
    NCDFGetVarFullName(nGroupId, nVarId, &pszVarFullName);
3171
69
    const char *pszValue = FetchAttr(pszVarFullName, pszAttr);
3172
69
    CPLFree(pszVarFullName);
3173
69
    return pszValue;
3174
69
}
3175
3176
/************************************************************************/
3177
/*                       IsDifferenceBelow()                            */
3178
/************************************************************************/
3179
3180
static bool IsDifferenceBelow(double dfA, double dfB, double dfError)
3181
0
{
3182
0
    const double dfAbsDiff = fabs(dfA - dfB);
3183
0
    return dfAbsDiff <= dfError;
3184
0
}
3185
3186
/************************************************************************/
3187
/*                      SetProjectionFromVar()                          */
3188
/************************************************************************/
3189
void netCDFDataset::SetProjectionFromVar(
3190
    int nGroupId, int nVarId, bool bReadSRSOnly, const char *pszGivenGM,
3191
    std::string *returnProjStr, nccfdriver::SGeometry_Reader *sg,
3192
    std::vector<std::string> *paosRemovedMDItems)
3193
23
{
3194
23
    bool bGotGeogCS = false;
3195
23
    bool bGotCfSRS = false;
3196
23
    bool bGotCfWktSRS = false;
3197
23
    bool bGotGdalSRS = false;
3198
23
    bool bGotCfGT = false;
3199
23
    bool bGotGdalGT = false;
3200
3201
    // These values from CF metadata.
3202
23
    OGRSpatialReference oSRS;
3203
23
    oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3204
23
    size_t xdim = nRasterXSize;
3205
23
    size_t ydim = nRasterYSize;
3206
3207
    // These values from GDAL metadata.
3208
23
    const char *pszWKT = nullptr;
3209
23
    const char *pszGeoTransform = nullptr;
3210
3211
23
    netCDFDataset *poDS = this;  // Perhaps this should be removed for clarity.
3212
3213
23
    CPLDebug("GDAL_netCDF", "\n=====\nSetProjectionFromVar( %d, %d)", nGroupId,
3214
23
             nVarId);
3215
3216
    // Get x/y range information.
3217
3218
    // Temp variables to use in SetGeoTransform() and SetProjection().
3219
23
    double adfTempGeoTransform[6] = {0.0, 1.0, 0.0, 0.0, 0.0, 1.0};
3220
3221
    // Look for grid_mapping metadata.
3222
23
    const char *pszValue = pszGivenGM;
3223
23
    CPLString osTmpGridMapping;  // let is in this outer scope as pszValue may
3224
        // point to it
3225
23
    if (pszValue == nullptr)
3226
23
    {
3227
23
        pszValue = FetchAttr(nGroupId, nVarId, CF_GRD_MAPPING);
3228
23
        if (pszValue && strchr(pszValue, ':') && strchr(pszValue, ' '))
3229
0
        {
3230
            // Expanded form of grid_mapping
3231
            // e.g. "crsOSGB: x y crsWGS84: lat lon"
3232
            // Pickup the grid_mapping whose coordinates are dimensions of the
3233
            // variable
3234
0
            CPLStringList aosTokens(CSLTokenizeString2(pszValue, " ", 0));
3235
0
            if ((aosTokens.size() % 3) == 0)
3236
0
            {
3237
0
                for (int i = 0; i < aosTokens.size() / 3; i++)
3238
0
                {
3239
0
                    if (CSLFindString(poDS->papszDimName,
3240
0
                                      aosTokens[3 * i + 1]) >= 0 &&
3241
0
                        CSLFindString(poDS->papszDimName,
3242
0
                                      aosTokens[3 * i + 2]) >= 0)
3243
0
                    {
3244
0
                        osTmpGridMapping = aosTokens[3 * i];
3245
0
                        if (!osTmpGridMapping.empty() &&
3246
0
                            osTmpGridMapping.back() == ':')
3247
0
                        {
3248
0
                            osTmpGridMapping.resize(osTmpGridMapping.size() -
3249
0
                                                    1);
3250
0
                        }
3251
0
                        pszValue = osTmpGridMapping.c_str();
3252
0
                        break;
3253
0
                    }
3254
0
                }
3255
0
            }
3256
0
        }
3257
23
    }
3258
23
    char *pszGridMappingValue = CPLStrdup(pszValue ? pszValue : "");
3259
3260
23
    if (!EQUAL(pszGridMappingValue, ""))
3261
0
    {
3262
        // Read grid_mapping metadata.
3263
0
        int nProjGroupID = -1;
3264
0
        int nProjVarID = -1;
3265
0
        if (NCDFResolveVar(nGroupId, pszGridMappingValue, &nProjGroupID,
3266
0
                           &nProjVarID) == CE_None)
3267
0
        {
3268
0
            poDS->ReadAttributes(nProjGroupID, nProjVarID);
3269
3270
            // Look for GDAL spatial_ref and GeoTransform within grid_mapping.
3271
0
            CPLFree(pszGridMappingValue);
3272
0
            pszGridMappingValue = nullptr;
3273
0
            NCDFGetVarFullName(nProjGroupID, nProjVarID, &pszGridMappingValue);
3274
0
            if (pszGridMappingValue)
3275
0
            {
3276
0
                CPLDebug("GDAL_netCDF", "got grid_mapping %s",
3277
0
                         pszGridMappingValue);
3278
0
                pszWKT = FetchAttr(pszGridMappingValue, NCDF_SPATIAL_REF);
3279
0
                if (!pszWKT)
3280
0
                {
3281
0
                    pszWKT = FetchAttr(pszGridMappingValue, NCDF_CRS_WKT);
3282
0
                }
3283
0
                else
3284
0
                {
3285
0
                    bGotGdalSRS = true;
3286
0
                    CPLDebug("GDAL_netCDF", "setting WKT from GDAL");
3287
0
                }
3288
0
                if (pszWKT)
3289
0
                {
3290
0
                    if (!bGotGdalSRS)
3291
0
                    {
3292
0
                        bGotCfWktSRS = true;
3293
0
                        CPLDebug("GDAL_netCDF", "setting WKT from CF");
3294
0
                    }
3295
0
                    if (returnProjStr != nullptr)
3296
0
                    {
3297
0
                        (*returnProjStr) = std::string(pszWKT);
3298
0
                    }
3299
0
                    else
3300
0
                    {
3301
0
                        m_bAddedProjectionVarsDefs = true;
3302
0
                        m_bAddedProjectionVarsData = true;
3303
0
                        OGRSpatialReference oSRSTmp;
3304
0
                        oSRSTmp.SetAxisMappingStrategy(
3305
0
                            OAMS_TRADITIONAL_GIS_ORDER);
3306
0
                        oSRSTmp.importFromWkt(pszWKT);
3307
0
                        SetSpatialRefNoUpdate(&oSRSTmp);
3308
0
                    }
3309
0
                    pszGeoTransform =
3310
0
                        FetchAttr(pszGridMappingValue, NCDF_GEOTRANSFORM);
3311
0
                }
3312
0
            }
3313
0
            else
3314
0
            {
3315
0
                pszGridMappingValue = CPLStrdup("");
3316
0
            }
3317
0
        }
3318
0
    }
3319
3320
    // Get information about the file.
3321
    //
3322
    // Was this file created by the GDAL netcdf driver?
3323
    // Was this file created by the newer (CF-conformant) driver?
3324
    //
3325
    // 1) If GDAL netcdf metadata is set, and version >= 1.9,
3326
    //    it was created with the new driver
3327
    // 2) Else, if spatial_ref and GeoTransform are present in the
3328
    //    grid_mapping variable, it was created by the old driver
3329
23
    pszValue = FetchAttr("NC_GLOBAL", "GDAL");
3330
3331
23
    if (pszValue && NCDFIsGDALVersionGTE(pszValue, 1900))
3332
0
    {
3333
0
        bIsGdalFile = true;
3334
0
        bIsGdalCfFile = true;
3335
0
    }
3336
23
    else if (pszWKT != nullptr && pszGeoTransform != nullptr)
3337
0
    {
3338
0
        bIsGdalFile = true;
3339
0
        bIsGdalCfFile = false;
3340
0
    }
3341
3342
    // Set default bottom-up default value.
3343
    // Y axis dimension and absence of GT can modify this value.
3344
    // Override with Config option GDAL_NETCDF_BOTTOMUP.
3345
3346
    // New driver is bottom-up by default.
3347
23
    if ((bIsGdalFile && !bIsGdalCfFile) || bSwitchedXY)
3348
0
        poDS->bBottomUp = false;
3349
23
    else
3350
23
        poDS->bBottomUp = true;
3351
3352
23
    CPLDebug("GDAL_netCDF",
3353
23
             "bIsGdalFile=%d bIsGdalCfFile=%d bSwitchedXY=%d bBottomUp=%d",
3354
23
             static_cast<int>(bIsGdalFile), static_cast<int>(bIsGdalCfFile),
3355
23
             static_cast<int>(bSwitchedXY), static_cast<int>(bBottomUp));
3356
3357
    // Read projection coordinates.
3358
3359
23
    int nGroupDimXID = -1;
3360
23
    int nVarDimXID = -1;
3361
23
    int nGroupDimYID = -1;
3362
23
    int nVarDimYID = -1;
3363
23
    if (sg != nullptr)
3364
0
    {
3365
0
        nGroupDimXID = sg->get_ncID();
3366
0
        nGroupDimYID = sg->get_ncID();
3367
0
        nVarDimXID = sg->getNodeCoordVars()[0];
3368
0
        nVarDimYID = sg->getNodeCoordVars()[1];
3369
0
    }
3370
3371
23
    if (!bReadSRSOnly)
3372
0
    {
3373
0
        NCDFResolveVar(nGroupId, poDS->papszDimName[nXDimID], &nGroupDimXID,
3374
0
                       &nVarDimXID);
3375
0
        NCDFResolveVar(nGroupId, poDS->papszDimName[nYDimID], &nGroupDimYID,
3376
0
                       &nVarDimYID);
3377
        // TODO: if above resolving fails we should also search for coordinate
3378
        // variables without same name than dimension using the same resolving
3379
        // logic. This should handle for example NASA Ocean Color L2 products.
3380
3381
0
        const bool bIgnoreXYAxisNameChecks =
3382
0
            CPLTestBool(CSLFetchNameValueDef(
3383
0
                papszOpenOptions, "IGNORE_XY_AXIS_NAME_CHECKS",
3384
0
                CPLGetConfigOption("GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS",
3385
0
                                   "NO"))) ||
3386
            // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a res
3387
            // and transform attributes
3388
0
            (FetchAttr(nGroupId, nVarId, "res") != nullptr &&
3389
0
             FetchAttr(nGroupId, nVarId, "transform") != nullptr) ||
3390
0
            FetchAttr(nGroupId, NC_GLOBAL, "GMT_version") != nullptr;
3391
3392
        // Check that they are 1D or 2D variables
3393
0
        if (nVarDimXID >= 0)
3394
0
        {
3395
0
            int ndims = -1;
3396
0
            nc_inq_varndims(nGroupId, nVarDimXID, &ndims);
3397
0
            if (ndims == 0 || ndims > 2)
3398
0
                nVarDimXID = -1;
3399
0
            else if (!bIgnoreXYAxisNameChecks)
3400
0
            {
3401
0
                if (!NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
3402
0
                    !NCDFIsVarProjectionX(nGroupId, nVarDimXID, nullptr) &&
3403
                    // In case of inversion of X/Y
3404
0
                    !NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr) &&
3405
0
                    !NCDFIsVarProjectionY(nGroupId, nVarDimXID, nullptr))
3406
0
                {
3407
0
                    char szVarNameX[NC_MAX_NAME + 1];
3408
0
                    CPL_IGNORE_RET_VAL(
3409
0
                        nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
3410
0
                    if (!(ndims == 1 &&
3411
0
                          (EQUAL(szVarNameX, CF_LONGITUDE_STD_NAME) ||
3412
0
                           EQUAL(szVarNameX, CF_LONGITUDE_VAR_NAME))))
3413
0
                    {
3414
0
                        CPLDebug(
3415
0
                            "netCDF",
3416
0
                            "Georeferencing ignored due to non-specific "
3417
0
                            "enough X axis name. "
3418
0
                            "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
3419
0
                            "as configuration option to bypass this check");
3420
0
                        nVarDimXID = -1;
3421
0
                    }
3422
0
                }
3423
0
            }
3424
0
        }
3425
3426
0
        if (nVarDimYID >= 0)
3427
0
        {
3428
0
            int ndims = -1;
3429
0
            nc_inq_varndims(nGroupId, nVarDimYID, &ndims);
3430
0
            if (ndims == 0 || ndims > 2)
3431
0
                nVarDimYID = -1;
3432
0
            else if (!bIgnoreXYAxisNameChecks)
3433
0
            {
3434
0
                if (!NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr) &&
3435
0
                    !NCDFIsVarProjectionY(nGroupId, nVarDimYID, nullptr) &&
3436
                    // In case of inversion of X/Y
3437
0
                    !NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
3438
0
                    !NCDFIsVarProjectionX(nGroupId, nVarDimYID, nullptr))
3439
0
                {
3440
0
                    char szVarNameY[NC_MAX_NAME + 1];
3441
0
                    CPL_IGNORE_RET_VAL(
3442
0
                        nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
3443
0
                    if (!(ndims == 1 &&
3444
0
                          (EQUAL(szVarNameY, CF_LATITUDE_STD_NAME) ||
3445
0
                           EQUAL(szVarNameY, CF_LATITUDE_VAR_NAME))))
3446
0
                    {
3447
0
                        CPLDebug(
3448
0
                            "netCDF",
3449
0
                            "Georeferencing ignored due to non-specific "
3450
0
                            "enough Y axis name. "
3451
0
                            "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
3452
0
                            "as configuration option to bypass this check");
3453
0
                        nVarDimYID = -1;
3454
0
                    }
3455
0
                }
3456
0
            }
3457
0
        }
3458
3459
0
        if ((nVarDimXID >= 0 && xdim == 1) || (nVarDimXID >= 0 && ydim == 1))
3460
0
        {
3461
0
            CPLError(CE_Warning, CPLE_AppDefined,
3462
0
                     "1-pixel width/height files not supported, "
3463
0
                     "xdim: %ld ydim: %ld",
3464
0
                     static_cast<long>(xdim), static_cast<long>(ydim));
3465
0
            nVarDimXID = -1;
3466
0
            nVarDimYID = -1;
3467
0
        }
3468
0
    }
3469
3470
23
    const char *pszUnits = nullptr;
3471
23
    if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
3472
0
    {
3473
0
        const char *pszUnitsX = FetchAttr(nGroupDimXID, nVarDimXID, "units");
3474
0
        const char *pszUnitsY = FetchAttr(nGroupDimYID, nVarDimYID, "units");
3475
        // Normalize degrees_east/degrees_north to degrees
3476
        // Cf https://github.com/OSGeo/gdal/issues/11009
3477
0
        if (pszUnitsX && EQUAL(pszUnitsX, "degrees_east"))
3478
0
            pszUnitsX = "degrees";
3479
0
        if (pszUnitsY && EQUAL(pszUnitsY, "degrees_north"))
3480
0
            pszUnitsY = "degrees";
3481
3482
0
        if (pszUnitsX && pszUnitsY)
3483
0
        {
3484
0
            if (EQUAL(pszUnitsX, pszUnitsY))
3485
0
                pszUnits = pszUnitsX;
3486
0
            else if (!pszWKT && !EQUAL(pszGridMappingValue, ""))
3487
0
            {
3488
0
                CPLError(CE_Failure, CPLE_AppDefined,
3489
0
                         "X axis unit (%s) is different from Y axis "
3490
0
                         "unit (%s). SRS will ignore axis unit and be "
3491
0
                         "likely wrong.",
3492
0
                         pszUnitsX, pszUnitsY);
3493
0
            }
3494
0
        }
3495
0
        else if (pszUnitsX && !pszWKT && !EQUAL(pszGridMappingValue, ""))
3496
0
        {
3497
0
            CPLError(CE_Failure, CPLE_AppDefined,
3498
0
                     "X axis unit is defined, but not Y one ."
3499
0
                     "SRS will ignore axis unit and be likely wrong.");
3500
0
        }
3501
0
        else if (pszUnitsY && !pszWKT && !EQUAL(pszGridMappingValue, ""))
3502
0
        {
3503
0
            CPLError(CE_Failure, CPLE_AppDefined,
3504
0
                     "Y axis unit is defined, but not X one ."
3505
0
                     "SRS will ignore axis unit and be likely wrong.");
3506
0
        }
3507
0
    }
3508
3509
23
    if (!pszWKT && !EQUAL(pszGridMappingValue, ""))
3510
0
    {
3511
0
        CPLStringList aosGridMappingKeyValues;
3512
0
        const size_t nLenGridMappingValue = strlen(pszGridMappingValue);
3513
0
        for (const char *const *papszIter = papszMetadata;
3514
0
             papszIter && *papszIter; ++papszIter)
3515
0
        {
3516
0
            if (STARTS_WITH(*papszIter, pszGridMappingValue) &&
3517
0
                (*papszIter)[nLenGridMappingValue] == '#')
3518
0
            {
3519
0
                char *pszKey = nullptr;
3520
0
                pszValue = CPLParseNameValue(
3521
0
                    *papszIter + nLenGridMappingValue + 1, &pszKey);
3522
0
                if (pszKey && pszValue)
3523
0
                    aosGridMappingKeyValues.SetNameValue(pszKey, pszValue);
3524
0
                CPLFree(pszKey);
3525
0
            }
3526
0
        }
3527
3528
0
        bGotGeogCS = aosGridMappingKeyValues.FetchNameValue(
3529
0
                         CF_PP_SEMI_MAJOR_AXIS) != nullptr;
3530
3531
0
        oSRS.importFromCF1(aosGridMappingKeyValues.List(), pszUnits);
3532
0
        bGotCfSRS = oSRS.IsGeographic() || oSRS.IsProjected();
3533
0
    }
3534
23
    else
3535
23
    {
3536
        // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a "crs"
3537
        // attribute hold on the variable of interest that contains a PROJ.4
3538
        // string
3539
23
        pszValue = FetchAttr(nGroupId, nVarId, "crs");
3540
23
        if (pszValue &&
3541
23
            (strstr(pszValue, "+proj=") != nullptr ||
3542
0
             strstr(pszValue, "GEOGCS") != nullptr ||
3543
0
             strstr(pszValue, "PROJCS") != nullptr ||
3544
0
             strstr(pszValue, "EPSG:") != nullptr) &&
3545
23
            oSRS.SetFromUserInput(pszValue) == OGRERR_NONE)
3546
0
        {
3547
0
            bGotCfSRS = true;
3548
0
        }
3549
23
    }
3550
3551
    // Set Projection from CF.
3552
23
    double dfLinearUnitsConvFactor = 1.0;
3553
23
    if ((bGotGeogCS || bGotCfSRS))
3554
0
    {
3555
0
        if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
3556
0
        {
3557
            // Set SRS Units.
3558
3559
            // Check units for x and y.
3560
0
            if (oSRS.IsProjected())
3561
0
            {
3562
0
                dfLinearUnitsConvFactor = oSRS.GetLinearUnits(nullptr);
3563
3564
                // If the user doesn't ask to preserve the axis unit,
3565
                // then normalize to metre
3566
0
                if (dfLinearUnitsConvFactor != 1.0 &&
3567
0
                    !CPLFetchBool(GetOpenOptions(), "PRESERVE_AXIS_UNIT_IN_CRS",
3568
0
                                  false))
3569
0
                {
3570
0
                    oSRS.SetLinearUnits("metre", 1.0);
3571
0
                    oSRS.SetAuthority("PROJCS|UNIT", "EPSG", 9001);
3572
0
                }
3573
0
                else
3574
0
                {
3575
0
                    dfLinearUnitsConvFactor = 1.0;
3576
0
                }
3577
0
            }
3578
0
        }
3579
3580
        // Set projection.
3581
0
        char *pszTempProjection = nullptr;
3582
0
        oSRS.exportToWkt(&pszTempProjection);
3583
0
        if (pszTempProjection)
3584
0
        {
3585
0
            CPLDebug("GDAL_netCDF", "setting WKT from CF");
3586
0
            if (returnProjStr != nullptr)
3587
0
            {
3588
0
                (*returnProjStr) = std::string(pszTempProjection);
3589
0
            }
3590
0
            else
3591
0
            {
3592
0
                m_bAddedProjectionVarsDefs = true;
3593
0
                m_bAddedProjectionVarsData = true;
3594
0
                SetSpatialRefNoUpdate(&oSRS);
3595
0
            }
3596
0
        }
3597
0
        CPLFree(pszTempProjection);
3598
0
    }
3599
3600
23
    if (!bReadSRSOnly && (nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 &&
3601
23
        ydim > 0)
3602
0
    {
3603
0
        double *pdfXCoord =
3604
0
            static_cast<double *>(CPLCalloc(xdim, sizeof(double)));
3605
0
        double *pdfYCoord =
3606
0
            static_cast<double *>(CPLCalloc(ydim, sizeof(double)));
3607
3608
0
        size_t start[2] = {0, 0};
3609
0
        size_t edge[2] = {xdim, 0};
3610
0
        int status = nc_get_vara_double(nGroupDimXID, nVarDimXID, start, edge,
3611
0
                                        pdfXCoord);
3612
0
        NCDF_ERR(status);
3613
3614
0
        edge[0] = ydim;
3615
0
        status = nc_get_vara_double(nGroupDimYID, nVarDimYID, start, edge,
3616
0
                                    pdfYCoord);
3617
0
        NCDF_ERR(status);
3618
3619
0
        nc_type nc_var_dimx_datatype = NC_NAT;
3620
0
        status =
3621
0
            nc_inq_vartype(nGroupDimXID, nVarDimXID, &nc_var_dimx_datatype);
3622
0
        NCDF_ERR(status);
3623
3624
0
        nc_type nc_var_dimy_datatype = NC_NAT;
3625
0
        status =
3626
0
            nc_inq_vartype(nGroupDimYID, nVarDimYID, &nc_var_dimy_datatype);
3627
0
        NCDF_ERR(status);
3628
3629
0
        if (!poDS->bSwitchedXY)
3630
0
        {
3631
            // Convert ]180,540] longitude values to ]-180,0].
3632
0
            if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
3633
0
                CPLTestBool(
3634
0
                    CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")))
3635
0
            {
3636
                // If minimum longitude is > 180, subtract 360 from all.
3637
                // Add a check on the maximum X value too, since
3638
                // NCDFIsVarLongitude() is not very specific by default (see
3639
                // https://github.com/OSGeo/gdal/issues/1440)
3640
0
                if (std::min(pdfXCoord[0], pdfXCoord[xdim - 1]) > 180.0 &&
3641
0
                    std::max(pdfXCoord[0], pdfXCoord[xdim - 1]) <= 540)
3642
0
                {
3643
0
                    CPLDebug(
3644
0
                        "GDAL_netCDF",
3645
0
                        "Offsetting longitudes from ]180,540] to ]-180,180]. "
3646
0
                        "Can be disabled with GDAL_NETCDF_CENTERLONG_180=NO");
3647
0
                    for (size_t i = 0; i < xdim; i++)
3648
0
                        pdfXCoord[i] -= 360;
3649
0
                }
3650
0
            }
3651
0
        }
3652
3653
        // Is pixel spacing uniform across the map?
3654
3655
        // Check Longitude.
3656
3657
0
        bool bLonSpacingOK = false;
3658
0
        if (xdim == 2)
3659
0
        {
3660
0
            bLonSpacingOK = true;
3661
0
        }
3662
0
        else
3663
0
        {
3664
0
            bool bWestIsLeft = (pdfXCoord[0] < pdfXCoord[xdim - 1]);
3665
3666
            // fix longitudes if longitudes should increase from
3667
            // west to east, but west > east
3668
0
            if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
3669
0
                !bWestIsLeft)
3670
0
            {
3671
0
                size_t ndecreases = 0;
3672
3673
                // there is lon wrap if longitudes increase
3674
                // with one single decrease
3675
0
                for (size_t i = 1; i < xdim; i++)
3676
0
                {
3677
0
                    if (pdfXCoord[i] < pdfXCoord[i - 1])
3678
0
                        ndecreases++;
3679
0
                }
3680
3681
0
                if (ndecreases == 1)
3682
0
                {
3683
0
                    CPLDebug("GDAL_netCDF", "longitude wrap detected");
3684
0
                    for (size_t i = 0; i < xdim; i++)
3685
0
                    {
3686
0
                        if (pdfXCoord[i] > pdfXCoord[xdim - 1])
3687
0
                            pdfXCoord[i] -= 360;
3688
0
                    }
3689
0
                }
3690
0
            }
3691
3692
0
            const double dfSpacingBegin = pdfXCoord[1] - pdfXCoord[0];
3693
0
            const double dfSpacingMiddle =
3694
0
                pdfXCoord[xdim / 2 + 1] - pdfXCoord[xdim / 2];
3695
0
            const double dfSpacingLast =
3696
0
                pdfXCoord[xdim - 1] - pdfXCoord[xdim - 2];
3697
3698
0
            CPLDebug("GDAL_netCDF",
3699
0
                     "xdim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
3700
0
                     "dfSpacingLast: %f",
3701
0
                     static_cast<long>(xdim), dfSpacingBegin, dfSpacingMiddle,
3702
0
                     dfSpacingLast);
3703
#ifdef NCDF_DEBUG
3704
            CPLDebug("GDAL_netCDF", "xcoords: %f %f %f %f %f %f", pdfXCoord[0],
3705
                     pdfXCoord[1], pdfXCoord[xdim / 2],
3706
                     pdfXCoord[(xdim / 2) + 1], pdfXCoord[xdim - 2],
3707
                     pdfXCoord[xdim - 1]);
3708
#endif
3709
3710
            // ftp://ftp.cdc.noaa.gov/Datasets/NARR/Dailies/monolevel/vwnd.10m.2015.nc
3711
            // requires a 0.02% tolerance, so let's settle for 0.05%
3712
3713
            // For float variables, increase to 0.2% (as seen in
3714
            // https://github.com/OSGeo/gdal/issues/3663)
3715
0
            const double dfEpsRel =
3716
0
                nc_var_dimx_datatype == NC_FLOAT ? 0.002 : 0.0005;
3717
3718
0
            const double dfEps =
3719
0
                dfEpsRel *
3720
0
                std::max(fabs(dfSpacingBegin),
3721
0
                         std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
3722
0
            if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
3723
0
                IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
3724
0
                IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
3725
0
            {
3726
0
                bLonSpacingOK = true;
3727
0
            }
3728
0
            else if (CPLTestBool(CPLGetConfigOption(
3729
0
                         "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
3730
0
            {
3731
0
                bLonSpacingOK = true;
3732
0
                CPLDebug(
3733
0
                    "GDAL_netCDF",
3734
0
                    "Longitude/X is not equally spaced, but will be considered "
3735
0
                    "as such because of "
3736
0
                    "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
3737
0
            }
3738
0
        }
3739
3740
0
        if (bLonSpacingOK == false)
3741
0
        {
3742
0
            CPLDebug(
3743
0
                "GDAL_netCDF", "%s",
3744
0
                "Longitude/X is not equally spaced (with a 0.05% tolerance). "
3745
0
                "You may set the "
3746
0
                "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
3747
0
                "option to YES to ignore this check");
3748
0
        }
3749
3750
        // Check Latitude.
3751
0
        bool bLatSpacingOK = false;
3752
3753
0
        if (ydim == 2)
3754
0
        {
3755
0
            bLatSpacingOK = true;
3756
0
        }
3757
0
        else
3758
0
        {
3759
0
            const double dfSpacingBegin = pdfYCoord[1] - pdfYCoord[0];
3760
0
            const double dfSpacingMiddle =
3761
0
                pdfYCoord[ydim / 2 + 1] - pdfYCoord[ydim / 2];
3762
3763
0
            const double dfSpacingLast =
3764
0
                pdfYCoord[ydim - 1] - pdfYCoord[ydim - 2];
3765
3766
0
            CPLDebug("GDAL_netCDF",
3767
0
                     "ydim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
3768
0
                     "dfSpacingLast: %f",
3769
0
                     (long)ydim, dfSpacingBegin, dfSpacingMiddle,
3770
0
                     dfSpacingLast);
3771
#ifdef NCDF_DEBUG
3772
            CPLDebug("GDAL_netCDF", "ycoords: %f %f %f %f %f %f", pdfYCoord[0],
3773
                     pdfYCoord[1], pdfYCoord[ydim / 2],
3774
                     pdfYCoord[(ydim / 2) + 1], pdfYCoord[ydim - 2],
3775
                     pdfYCoord[ydim - 1]);
3776
#endif
3777
3778
0
            const double dfEpsRel =
3779
0
                nc_var_dimy_datatype == NC_FLOAT ? 0.002 : 0.0005;
3780
3781
0
            const double dfEps =
3782
0
                dfEpsRel *
3783
0
                std::max(fabs(dfSpacingBegin),
3784
0
                         std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
3785
0
            if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
3786
0
                IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
3787
0
                IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
3788
0
            {
3789
0
                bLatSpacingOK = true;
3790
0
            }
3791
0
            else if (CPLTestBool(CPLGetConfigOption(
3792
0
                         "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
3793
0
            {
3794
0
                bLatSpacingOK = true;
3795
0
                CPLDebug(
3796
0
                    "GDAL_netCDF",
3797
0
                    "Latitude/Y is not equally spaced, but will be considered "
3798
0
                    "as such because of "
3799
0
                    "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
3800
0
            }
3801
0
            else if (!oSRS.IsProjected() &&
3802
0
                     fabs(dfSpacingBegin - dfSpacingLast) <= 0.1 &&
3803
0
                     fabs(dfSpacingBegin - dfSpacingMiddle) <= 0.1 &&
3804
0
                     fabs(dfSpacingMiddle - dfSpacingLast) <= 0.1)
3805
0
            {
3806
0
                bLatSpacingOK = true;
3807
0
                CPLError(CE_Warning, CPLE_AppDefined,
3808
0
                         "Latitude grid not spaced evenly.  "
3809
0
                         "Setting projection for grid spacing is "
3810
0
                         "within 0.1 degrees threshold.");
3811
3812
0
                CPLDebug("GDAL_netCDF",
3813
0
                         "Latitude grid not spaced evenly, but within 0.1 "
3814
0
                         "degree threshold (probably a Gaussian grid).  "
3815
0
                         "Saving original latitude values in Y_VALUES "
3816
0
                         "geolocation metadata");
3817
0
                Set1DGeolocation(nGroupDimYID, nVarDimYID, "Y");
3818
0
            }
3819
3820
0
            if (bLatSpacingOK == false)
3821
0
            {
3822
0
                CPLDebug(
3823
0
                    "GDAL_netCDF", "%s",
3824
0
                    "Latitude/Y is not equally spaced (with a 0.05% "
3825
0
                    "tolerance). "
3826
0
                    "You may set the "
3827
0
                    "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
3828
0
                    "option to YES to ignore this check");
3829
0
            }
3830
0
        }
3831
3832
0
        if (bLonSpacingOK && bLatSpacingOK)
3833
0
        {
3834
            // We have gridded data so we can set the Georeferencing info.
3835
3836
            // Enable GeoTransform.
3837
3838
            // In the following "actual_range" and "node_offset"
3839
            // are attributes used by netCDF files created by GMT.
3840
            // If we find them we know how to proceed. Else, use
3841
            // the original algorithm.
3842
0
            bGotCfGT = true;
3843
3844
0
            int node_offset = 0;
3845
0
            NCDFResolveAttInt(nGroupId, NC_GLOBAL, "node_offset", &node_offset);
3846
3847
0
            double adfActualRange[2] = {0.0, 0.0};
3848
0
            double xMinMax[2] = {0.0, 0.0};
3849
0
            double yMinMax[2] = {0.0, 0.0};
3850
3851
0
            const auto RoundMinMaxForFloatVals =
3852
0
                [](double &dfMin, double &dfMax, int nIntervals)
3853
0
            {
3854
                // Helps for a case where longitudes range from
3855
                // -179.99 to 180.0 with a 0.01 degree spacing.
3856
                // However as this is encoded in a float array,
3857
                // -179.99 is actually read as -179.99000549316406 as
3858
                // a double. Try to detect that and correct the rounding
3859
3860
0
                const auto IsAlmostInteger = [](double dfVal)
3861
0
                {
3862
0
                    constexpr double THRESHOLD_INTEGER = 1e-3;
3863
0
                    return std::fabs(dfVal - std::round(dfVal)) <=
3864
0
                           THRESHOLD_INTEGER;
3865
0
                };
3866
3867
0
                const double dfSpacing = (dfMax - dfMin) / nIntervals;
3868
0
                if (dfSpacing > 0)
3869
0
                {
3870
0
                    const double dfInvSpacing = 1.0 / dfSpacing;
3871
0
                    if (IsAlmostInteger(dfInvSpacing))
3872
0
                    {
3873
0
                        const double dfRoundedSpacing =
3874
0
                            1.0 / std::round(dfInvSpacing);
3875
0
                        const double dfMinDivRoundedSpacing =
3876
0
                            dfMin / dfRoundedSpacing;
3877
0
                        const double dfMaxDivRoundedSpacing =
3878
0
                            dfMax / dfRoundedSpacing;
3879
0
                        if (IsAlmostInteger(dfMinDivRoundedSpacing) &&
3880
0
                            IsAlmostInteger(dfMaxDivRoundedSpacing))
3881
0
                        {
3882
0
                            const double dfRoundedMin =
3883
0
                                std::round(dfMinDivRoundedSpacing) *
3884
0
                                dfRoundedSpacing;
3885
0
                            const double dfRoundedMax =
3886
0
                                std::round(dfMaxDivRoundedSpacing) *
3887
0
                                dfRoundedSpacing;
3888
0
                            if (static_cast<float>(dfMin) ==
3889
0
                                    static_cast<float>(dfRoundedMin) &&
3890
0
                                static_cast<float>(dfMax) ==
3891
0
                                    static_cast<float>(dfRoundedMax))
3892
0
                            {
3893
0
                                dfMin = dfRoundedMin;
3894
0
                                dfMax = dfRoundedMax;
3895
0
                            }
3896
0
                        }
3897
0
                    }
3898
0
                }
3899
0
            };
3900
3901
0
            if (!nc_get_att_double(nGroupDimXID, nVarDimXID, "actual_range",
3902
0
                                   adfActualRange))
3903
0
            {
3904
0
                xMinMax[0] = adfActualRange[0];
3905
0
                xMinMax[1] = adfActualRange[1];
3906
3907
                // Present xMinMax[] in the same order as padfXCoord
3908
0
                if ((xMinMax[0] - xMinMax[1]) *
3909
0
                        (pdfXCoord[0] - pdfXCoord[xdim - 1]) <
3910
0
                    0)
3911
0
                {
3912
0
                    std::swap(xMinMax[0], xMinMax[1]);
3913
0
                }
3914
0
            }
3915
0
            else
3916
0
            {
3917
0
                xMinMax[0] = pdfXCoord[0];
3918
0
                xMinMax[1] = pdfXCoord[xdim - 1];
3919
0
                node_offset = 0;
3920
3921
0
                if (nc_var_dimx_datatype == NC_FLOAT)
3922
0
                {
3923
0
                    RoundMinMaxForFloatVals(xMinMax[0], xMinMax[1],
3924
0
                                            poDS->nRasterXSize - 1);
3925
0
                }
3926
0
            }
3927
3928
0
            if (!nc_get_att_double(nGroupDimYID, nVarDimYID, "actual_range",
3929
0
                                   adfActualRange))
3930
0
            {
3931
0
                yMinMax[0] = adfActualRange[0];
3932
0
                yMinMax[1] = adfActualRange[1];
3933
3934
                // Present yMinMax[] in the same order as pdfYCoord
3935
0
                if ((yMinMax[0] - yMinMax[1]) *
3936
0
                        (pdfYCoord[0] - pdfYCoord[ydim - 1]) <
3937
0
                    0)
3938
0
                {
3939
0
                    std::swap(yMinMax[0], yMinMax[1]);
3940
0
                }
3941
0
            }
3942
0
            else
3943
0
            {
3944
0
                yMinMax[0] = pdfYCoord[0];
3945
0
                yMinMax[1] = pdfYCoord[ydim - 1];
3946
0
                node_offset = 0;
3947
3948
0
                if (nc_var_dimy_datatype == NC_FLOAT)
3949
0
                {
3950
0
                    RoundMinMaxForFloatVals(yMinMax[0], yMinMax[1],
3951
0
                                            poDS->nRasterYSize - 1);
3952
0
                }
3953
0
            }
3954
3955
0
            double dfCoordOffset = 0.0;
3956
0
            double dfCoordScale = 1.0;
3957
0
            if (!nc_get_att_double(nGroupId, nVarDimXID, CF_ADD_OFFSET,
3958
0
                                   &dfCoordOffset) &&
3959
0
                !nc_get_att_double(nGroupId, nVarDimXID, CF_SCALE_FACTOR,
3960
0
                                   &dfCoordScale))
3961
0
            {
3962
0
                xMinMax[0] = dfCoordOffset + xMinMax[0] * dfCoordScale;
3963
0
                xMinMax[1] = dfCoordOffset + xMinMax[1] * dfCoordScale;
3964
0
            }
3965
3966
0
            if (!nc_get_att_double(nGroupId, nVarDimYID, CF_ADD_OFFSET,
3967
0
                                   &dfCoordOffset) &&
3968
0
                !nc_get_att_double(nGroupId, nVarDimYID, CF_SCALE_FACTOR,
3969
0
                                   &dfCoordScale))
3970
0
            {
3971
0
                yMinMax[0] = dfCoordOffset + yMinMax[0] * dfCoordScale;
3972
0
                yMinMax[1] = dfCoordOffset + yMinMax[1] * dfCoordScale;
3973
0
            }
3974
3975
            // Check for reverse order of y-coordinate.
3976
0
            if (!bSwitchedXY)
3977
0
            {
3978
0
                poDS->bBottomUp = (yMinMax[0] <= yMinMax[1]);
3979
0
                CPLDebug("GDAL_netCDF", "set bBottomUp = %d from Y axis",
3980
0
                         static_cast<int>(poDS->bBottomUp));
3981
0
                if (!poDS->bBottomUp)
3982
0
                {
3983
0
                    std::swap(yMinMax[0], yMinMax[1]);
3984
0
                }
3985
0
            }
3986
3987
            // Geostationary satellites can specify units in (micro)radians
3988
            // So we check if they do, and if so convert to linear units
3989
            // (meters)
3990
0
            const char *pszProjName = oSRS.GetAttrValue("PROJECTION");
3991
0
            if (pszProjName != nullptr)
3992
0
            {
3993
0
                if (EQUAL(pszProjName, SRS_PT_GEOSTATIONARY_SATELLITE))
3994
0
                {
3995
0
                    double satelliteHeight =
3996
0
                        oSRS.GetProjParm(SRS_PP_SATELLITE_HEIGHT, 1.0);
3997
0
                    size_t nAttlen = 0;
3998
0
                    char szUnits[NC_MAX_NAME + 1];
3999
0
                    szUnits[0] = '\0';
4000
0
                    nc_type nAttype = NC_NAT;
4001
0
                    nc_inq_att(nGroupId, nVarDimXID, "units", &nAttype,
4002
0
                               &nAttlen);
4003
0
                    if (nAttlen < sizeof(szUnits) &&
4004
0
                        nc_get_att_text(nGroupId, nVarDimXID, "units",
4005
0
                                        szUnits) == NC_NOERR)
4006
0
                    {
4007
0
                        szUnits[nAttlen] = '\0';
4008
0
                        if (EQUAL(szUnits, "microradian"))
4009
0
                        {
4010
0
                            xMinMax[0] =
4011
0
                                xMinMax[0] * satelliteHeight * 0.000001;
4012
0
                            xMinMax[1] =
4013
0
                                xMinMax[1] * satelliteHeight * 0.000001;
4014
0
                        }
4015
0
                        else if (EQUAL(szUnits, "rad") ||
4016
0
                                 EQUAL(szUnits, "radian"))
4017
0
                        {
4018
0
                            xMinMax[0] = xMinMax[0] * satelliteHeight;
4019
0
                            xMinMax[1] = xMinMax[1] * satelliteHeight;
4020
0
                        }
4021
0
                    }
4022
0
                    szUnits[0] = '\0';
4023
0
                    nc_inq_att(nGroupId, nVarDimYID, "units", &nAttype,
4024
0
                               &nAttlen);
4025
0
                    if (nAttlen < sizeof(szUnits) &&
4026
0
                        nc_get_att_text(nGroupId, nVarDimYID, "units",
4027
0
                                        szUnits) == NC_NOERR)
4028
0
                    {
4029
0
                        szUnits[nAttlen] = '\0';
4030
0
                        if (EQUAL(szUnits, "microradian"))
4031
0
                        {
4032
0
                            yMinMax[0] =
4033
0
                                yMinMax[0] * satelliteHeight * 0.000001;
4034
0
                            yMinMax[1] =
4035
0
                                yMinMax[1] * satelliteHeight * 0.000001;
4036
0
                        }
4037
0
                        else if (EQUAL(szUnits, "rad") ||
4038
0
                                 EQUAL(szUnits, "radian"))
4039
0
                        {
4040
0
                            yMinMax[0] = yMinMax[0] * satelliteHeight;
4041
0
                            yMinMax[1] = yMinMax[1] * satelliteHeight;
4042
0
                        }
4043
0
                    }
4044
0
                }
4045
0
            }
4046
4047
0
            adfTempGeoTransform[0] = xMinMax[0];
4048
0
            adfTempGeoTransform[1] = (xMinMax[1] - xMinMax[0]) /
4049
0
                                     (poDS->nRasterXSize + (node_offset - 1));
4050
0
            adfTempGeoTransform[2] = 0;
4051
0
            if (bSwitchedXY)
4052
0
            {
4053
0
                adfTempGeoTransform[3] = yMinMax[0];
4054
0
                adfTempGeoTransform[4] = 0;
4055
0
                adfTempGeoTransform[5] =
4056
0
                    (yMinMax[1] - yMinMax[0]) /
4057
0
                    (poDS->nRasterYSize + (node_offset - 1));
4058
0
            }
4059
0
            else
4060
0
            {
4061
0
                adfTempGeoTransform[3] = yMinMax[1];
4062
0
                adfTempGeoTransform[4] = 0;
4063
0
                adfTempGeoTransform[5] =
4064
0
                    (yMinMax[0] - yMinMax[1]) /
4065
0
                    (poDS->nRasterYSize + (node_offset - 1));
4066
0
            }
4067
4068
            // Compute the center of the pixel.
4069
0
            if (!node_offset)
4070
0
            {
4071
                // Otherwise its already the pixel center.
4072
0
                adfTempGeoTransform[0] -= (adfTempGeoTransform[1] / 2);
4073
0
                adfTempGeoTransform[3] -= (adfTempGeoTransform[5] / 2);
4074
0
            }
4075
0
        }
4076
4077
0
        const auto AreSRSEqualThroughProj4String =
4078
0
            [](const OGRSpatialReference &oSRS1,
4079
0
               const OGRSpatialReference &oSRS2)
4080
0
        {
4081
0
            char *pszProj4Str1 = nullptr;
4082
0
            oSRS1.exportToProj4(&pszProj4Str1);
4083
4084
0
            char *pszProj4Str2 = nullptr;
4085
0
            oSRS2.exportToProj4(&pszProj4Str2);
4086
4087
0
            {
4088
0
                char *pszTmp = strstr(pszProj4Str1, "+datum=");
4089
0
                if (pszTmp)
4090
0
                    memcpy(pszTmp, "+ellps=", strlen("+ellps="));
4091
0
            }
4092
4093
0
            {
4094
0
                char *pszTmp = strstr(pszProj4Str2, "+datum=");
4095
0
                if (pszTmp)
4096
0
                    memcpy(pszTmp, "+ellps=", strlen("+ellps="));
4097
0
            }
4098
4099
0
            bool bRet = false;
4100
0
            if (pszProj4Str1 && pszProj4Str2 &&
4101
0
                EQUAL(pszProj4Str1, pszProj4Str2))
4102
0
            {
4103
0
                bRet = true;
4104
0
            }
4105
4106
0
            CPLFree(pszProj4Str1);
4107
0
            CPLFree(pszProj4Str2);
4108
0
            return bRet;
4109
0
        };
4110
4111
0
        if (dfLinearUnitsConvFactor != 1.0)
4112
0
        {
4113
0
            for (int i = 0; i < 6; ++i)
4114
0
                adfTempGeoTransform[i] *= dfLinearUnitsConvFactor;
4115
4116
0
            if (paosRemovedMDItems)
4117
0
            {
4118
0
                char szVarNameX[NC_MAX_NAME + 1];
4119
0
                CPL_IGNORE_RET_VAL(
4120
0
                    nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
4121
4122
0
                char szVarNameY[NC_MAX_NAME + 1];
4123
0
                CPL_IGNORE_RET_VAL(
4124
0
                    nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
4125
4126
0
                paosRemovedMDItems->push_back(
4127
0
                    CPLSPrintf("%s#units", szVarNameX));
4128
0
                paosRemovedMDItems->push_back(
4129
0
                    CPLSPrintf("%s#units", szVarNameY));
4130
0
            }
4131
0
        }
4132
4133
        // If there is a global "geospatial_bounds_crs" attribute, check that it
4134
        // is consistent with the SRS, and if so, use it as the SRS
4135
0
        const char *pszGBCRS =
4136
0
            FetchAttr(nGroupId, NC_GLOBAL, "geospatial_bounds_crs");
4137
0
        if (pszGBCRS && STARTS_WITH(pszGBCRS, "EPSG:"))
4138
0
        {
4139
0
            OGRSpatialReference oSRSFromGBCRS;
4140
0
            oSRSFromGBCRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
4141
0
            if (oSRSFromGBCRS.SetFromUserInput(
4142
0
                    pszGBCRS,
4143
0
                    OGRSpatialReference::
4144
0
                        SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE &&
4145
0
                AreSRSEqualThroughProj4String(oSRS, oSRSFromGBCRS))
4146
0
            {
4147
0
                oSRS = std::move(oSRSFromGBCRS);
4148
0
                SetSpatialRefNoUpdate(&oSRS);
4149
0
            }
4150
0
        }
4151
4152
0
        CPLFree(pdfXCoord);
4153
0
        CPLFree(pdfYCoord);
4154
0
    }  // end if(has dims)
4155
4156
    // Process custom GeoTransform GDAL value.
4157
23
    if (!EQUAL(pszGridMappingValue, ""))
4158
0
    {
4159
0
        if (pszGeoTransform != nullptr)
4160
0
        {
4161
0
            CPLStringList aosGeoTransform(
4162
0
                CSLTokenizeString2(pszGeoTransform, " ", CSLT_HONOURSTRINGS));
4163
0
            if (aosGeoTransform.size() == 6)
4164
0
            {
4165
0
                std::array<double, 6> adfGeoTransformFromAttribute;
4166
0
                for (int i = 0; i < 6; i++)
4167
0
                {
4168
0
                    adfGeoTransformFromAttribute[i] =
4169
0
                        CPLAtof(aosGeoTransform[i]);
4170
0
                }
4171
4172
0
                if (bGotCfGT)
4173
0
                {
4174
0
                    constexpr double GT_RELERROR_WARN_THRESHOLD = 1e-6;
4175
0
                    double dfMaxAbsoluteError = 0.0;
4176
0
                    for (int i = 0; i < 6; i++)
4177
0
                    {
4178
0
                        double dfAbsoluteError =
4179
0
                            std::abs(adfTempGeoTransform[i] -
4180
0
                                     adfGeoTransformFromAttribute[i]);
4181
0
                        if (dfAbsoluteError >
4182
0
                            std::abs(adfGeoTransformFromAttribute[i] *
4183
0
                                     GT_RELERROR_WARN_THRESHOLD))
4184
0
                        {
4185
0
                            dfMaxAbsoluteError =
4186
0
                                std::max(dfMaxAbsoluteError, dfAbsoluteError);
4187
0
                        }
4188
0
                    }
4189
4190
0
                    if (dfMaxAbsoluteError > 0)
4191
0
                    {
4192
0
                        CPLError(CE_Warning, CPLE_AppDefined,
4193
0
                                 "GeoTransform read from attribute of %s "
4194
0
                                 "variable differs from value calculated from "
4195
0
                                 "dimension variables (max diff = %g). Using "
4196
0
                                 "value from attribute.",
4197
0
                                 pszGridMappingValue, dfMaxAbsoluteError);
4198
0
                    }
4199
0
                }
4200
4201
0
                std::copy(adfGeoTransformFromAttribute.begin(),
4202
0
                          adfGeoTransformFromAttribute.end(),
4203
0
                          adfTempGeoTransform);
4204
0
                bGotGdalGT = true;
4205
0
            }
4206
0
        }
4207
0
        else
4208
0
        {
4209
            // Look for corner array values.
4210
            // CPLDebug("GDAL_netCDF",
4211
            //           "looking for geotransform corners");
4212
0
            bool bGotNN = false;
4213
0
            double dfNN = FetchCopyParam(pszGridMappingValue,
4214
0
                                         "Northernmost_Northing", 0, &bGotNN);
4215
4216
0
            bool bGotSN = false;
4217
0
            double dfSN = FetchCopyParam(pszGridMappingValue,
4218
0
                                         "Southernmost_Northing", 0, &bGotSN);
4219
4220
0
            bool bGotEE = false;
4221
0
            double dfEE = FetchCopyParam(pszGridMappingValue,
4222
0
                                         "Easternmost_Easting", 0, &bGotEE);
4223
4224
0
            bool bGotWE = false;
4225
0
            double dfWE = FetchCopyParam(pszGridMappingValue,
4226
0
                                         "Westernmost_Easting", 0, &bGotWE);
4227
4228
            // Only set the GeoTransform if we got all the values.
4229
0
            if (bGotNN && bGotSN && bGotEE && bGotWE)
4230
0
            {
4231
0
                bGotGdalGT = true;
4232
4233
0
                adfTempGeoTransform[0] = dfWE;
4234
0
                adfTempGeoTransform[1] =
4235
0
                    (dfEE - dfWE) / (poDS->GetRasterXSize() - 1);
4236
0
                adfTempGeoTransform[2] = 0.0;
4237
0
                adfTempGeoTransform[3] = dfNN;
4238
0
                adfTempGeoTransform[4] = 0.0;
4239
0
                adfTempGeoTransform[5] =
4240
0
                    (dfSN - dfNN) / (poDS->GetRasterYSize() - 1);
4241
                // Compute the center of the pixel.
4242
0
                adfTempGeoTransform[0] = dfWE - (adfTempGeoTransform[1] / 2);
4243
0
                adfTempGeoTransform[3] = dfNN - (adfTempGeoTransform[5] / 2);
4244
0
            }
4245
0
        }  // (pszGeoTransform != NULL)
4246
4247
0
        if (bGotGdalSRS && !bGotGdalGT)
4248
0
            CPLDebug("GDAL_netCDF", "Got SRS but no geotransform from GDAL!");
4249
0
    }
4250
4251
23
    if (!pszWKT && !bGotCfSRS)
4252
23
    {
4253
        // Some netCDF files have a srid attribute (#6613) like
4254
        // urn:ogc:def:crs:EPSG::6931
4255
23
        const char *pszSRID = FetchAttr(pszGridMappingValue, "srid");
4256
23
        if (pszSRID != nullptr)
4257
0
        {
4258
0
            oSRS.Clear();
4259
0
            if (oSRS.SetFromUserInput(
4260
0
                    pszSRID,
4261
0
                    OGRSpatialReference::
4262
0
                        SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE)
4263
0
            {
4264
0
                char *pszWKTExport = nullptr;
4265
0
                CPLDebug("GDAL_netCDF", "Got SRS from %s", pszSRID);
4266
0
                oSRS.exportToWkt(&pszWKTExport);
4267
0
                if (returnProjStr != nullptr)
4268
0
                {
4269
0
                    (*returnProjStr) = std::string(pszWKTExport);
4270
0
                }
4271
0
                else
4272
0
                {
4273
0
                    m_bAddedProjectionVarsDefs = true;
4274
0
                    m_bAddedProjectionVarsData = true;
4275
0
                    SetSpatialRefNoUpdate(&oSRS);
4276
0
                }
4277
0
                CPLFree(pszWKTExport);
4278
0
            }
4279
0
        }
4280
23
    }
4281
4282
23
    CPLFree(pszGridMappingValue);
4283
4284
23
    if (bReadSRSOnly)
4285
23
        return;
4286
4287
    // Determines the SRS to be used by the geolocation array, if any
4288
0
    std::string osGeolocWKT = SRS_WKT_WGS84_LAT_LONG;
4289
0
    if (!m_oSRS.IsEmpty())
4290
0
    {
4291
0
        OGRSpatialReference oGeogCRS;
4292
0
        oGeogCRS.CopyGeogCSFrom(&m_oSRS);
4293
0
        char *pszWKTTmp = nullptr;
4294
0
        const char *const apszOptions[] = {"FORMAT=WKT2_2019", nullptr};
4295
0
        if (oGeogCRS.exportToWkt(&pszWKTTmp, apszOptions) == OGRERR_NONE)
4296
0
        {
4297
0
            osGeolocWKT = pszWKTTmp;
4298
0
        }
4299
0
        CPLFree(pszWKTTmp);
4300
0
    }
4301
4302
    // Process geolocation arrays from CF "coordinates" attribute.
4303
0
    std::string osGeolocXName, osGeolocYName;
4304
0
    if (ProcessCFGeolocation(nGroupId, nVarId, osGeolocWKT, osGeolocXName,
4305
0
                             osGeolocYName))
4306
0
    {
4307
0
        bool bCanCancelGT = true;
4308
0
        if ((nVarDimXID != -1) && (nVarDimYID != -1))
4309
0
        {
4310
0
            char szVarNameX[NC_MAX_NAME + 1];
4311
0
            CPL_IGNORE_RET_VAL(
4312
0
                nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
4313
0
            char szVarNameY[NC_MAX_NAME + 1];
4314
0
            CPL_IGNORE_RET_VAL(
4315
0
                nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
4316
0
            bCanCancelGT =
4317
0
                !(osGeolocXName == szVarNameX && osGeolocYName == szVarNameY);
4318
0
        }
4319
0
        if (bCanCancelGT && !m_oSRS.IsGeographic() && !m_oSRS.IsProjected() &&
4320
0
            !bSwitchedXY)
4321
0
        {
4322
0
            bGotCfGT = false;
4323
0
        }
4324
0
    }
4325
0
    else if (!bGotCfGT && !bReadSRSOnly && (nVarDimXID != -1) &&
4326
0
             (nVarDimYID != -1) && xdim > 0 && ydim > 0 &&
4327
0
             ((!bSwitchedXY &&
4328
0
               NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
4329
0
               NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr)) ||
4330
0
              (bSwitchedXY &&
4331
0
               NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
4332
0
               NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr))))
4333
0
    {
4334
        // Case of autotest/gdrivers/data/netcdf/GLMELT_4X5.OCN.nc
4335
        // which is indexed by lat, lon variables, but lat has irregular
4336
        // spacing.
4337
0
        const char *pszGeolocXFullName = poDS->papszDimName[poDS->nXDimID];
4338
0
        const char *pszGeolocYFullName = poDS->papszDimName[poDS->nYDimID];
4339
0
        if (bSwitchedXY)
4340
0
        {
4341
0
            std::swap(pszGeolocXFullName, pszGeolocYFullName);
4342
0
            GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", "GEOLOCATION");
4343
0
        }
4344
4345
0
        CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
4346
0
                 pszGeolocXFullName, pszGeolocYFullName);
4347
4348
0
        GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
4349
0
                                        "GEOLOCATION");
4350
4351
0
        CPLString osTMP;
4352
0
        osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4353
0
                     pszGeolocXFullName);
4354
4355
0
        GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
4356
0
        GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
4357
0
        osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4358
0
                     pszGeolocYFullName);
4359
4360
0
        GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
4361
0
        GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
4362
4363
0
        GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
4364
0
        GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
4365
4366
0
        GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
4367
0
        GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
4368
4369
0
        GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
4370
0
                                        "PIXEL_CENTER", "GEOLOCATION");
4371
0
    }
4372
4373
    // Set GeoTransform if we got a complete one - after projection has been set
4374
0
    if (bGotCfGT || bGotGdalGT)
4375
0
    {
4376
0
        m_bAddedProjectionVarsDefs = true;
4377
0
        m_bAddedProjectionVarsData = true;
4378
0
        SetGeoTransformNoUpdate(adfTempGeoTransform);
4379
0
    }
4380
4381
    // Debugging reports.
4382
0
    CPLDebug("GDAL_netCDF",
4383
0
             "bGotGeogCS=%d bGotCfSRS=%d bGotCfGT=%d bGotCfWktSRS=%d "
4384
0
             "bGotGdalSRS=%d bGotGdalGT=%d",
4385
0
             static_cast<int>(bGotGeogCS), static_cast<int>(bGotCfSRS),
4386
0
             static_cast<int>(bGotCfGT), static_cast<int>(bGotCfWktSRS),
4387
0
             static_cast<int>(bGotGdalSRS), static_cast<int>(bGotGdalGT));
4388
4389
0
    if (!bGotCfGT && !bGotGdalGT)
4390
0
        CPLDebug("GDAL_netCDF", "did not get geotransform from CF nor GDAL!");
4391
4392
0
    if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfGT && !bGotCfWktSRS)
4393
0
        CPLDebug("GDAL_netCDF", "did not get projection from CF nor GDAL!");
4394
4395
    // wish of 6195
4396
    // we don't have CS/SRS, but we do have GT, and we live in -180,360 -90,90
4397
0
    if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfWktSRS)
4398
0
    {
4399
0
        if (bGotCfGT || bGotGdalGT)
4400
0
        {
4401
0
            bool bAssumedLongLat = CPLTestBool(CSLFetchNameValueDef(
4402
0
                papszOpenOptions, "ASSUME_LONGLAT",
4403
0
                CPLGetConfigOption("GDAL_NETCDF_ASSUME_LONGLAT", "NO")));
4404
4405
0
            if (bAssumedLongLat && adfTempGeoTransform[0] >= -180 &&
4406
0
                adfTempGeoTransform[0] < 360 &&
4407
0
                (adfTempGeoTransform[0] +
4408
0
                 adfTempGeoTransform[1] * poDS->GetRasterXSize()) <= 360 &&
4409
0
                adfTempGeoTransform[3] <= 90 && adfTempGeoTransform[3] > -90 &&
4410
0
                (adfTempGeoTransform[3] +
4411
0
                 adfTempGeoTransform[5] * poDS->GetRasterYSize()) >= -90)
4412
0
            {
4413
4414
0
                poDS->bIsGeographic = true;
4415
0
                char *pszTempProjection = nullptr;
4416
                // seems odd to use 4326 so OGC:CRS84
4417
0
                oSRS.SetFromUserInput("OGC:CRS84");
4418
0
                oSRS.exportToWkt(&pszTempProjection);
4419
0
                if (returnProjStr != nullptr)
4420
0
                {
4421
0
                    (*returnProjStr) = std::string(pszTempProjection);
4422
0
                }
4423
0
                else
4424
0
                {
4425
0
                    m_bAddedProjectionVarsDefs = true;
4426
0
                    m_bAddedProjectionVarsData = true;
4427
0
                    SetSpatialRefNoUpdate(&oSRS);
4428
0
                }
4429
0
                CPLFree(pszTempProjection);
4430
4431
0
                CPLDebug("netCDF",
4432
0
                         "Assumed Longitude Latitude CRS 'OGC:CRS84' because "
4433
0
                         "none otherwise available and geotransform within "
4434
0
                         "suitable bounds. "
4435
0
                         "Set GDAL_NETCDF_ASSUME_LONGLAT=NO as configuration "
4436
0
                         "option or "
4437
0
                         "    ASSUME_LONGLAT=NO as open option to bypass this "
4438
0
                         "assumption.");
4439
0
            }
4440
0
        }
4441
0
    }
4442
4443
// Search for Well-known GeogCS if got only CF WKT
4444
// Disabled for now, as a named datum also include control points
4445
// (see mailing list and bug#4281
4446
// For example, WGS84 vs. GDA94 (EPSG:3577) - AEA in netcdf_cf.py
4447
4448
// Disabled for now, but could be set in a config option.
4449
#if 0
4450
    bool bLookForWellKnownGCS = false;  // This could be a Config Option.
4451
4452
    if( bLookForWellKnownGCS && bGotCfSRS && !bGotGdalSRS )
4453
    {
4454
        // ET - Could use a more exhaustive method by scanning all EPSG codes in
4455
        // data/gcs.csv as proposed by Even in the gdal-dev mailing list "help
4456
        // for comparing two WKT".
4457
        // This code could be contributed to a new function.
4458
        // OGRSpatialReference * OGRSpatialReference::FindMatchingGeogCS(
4459
        //     const OGRSpatialReference *poOther) */
4460
        CPLDebug("GDAL_netCDF", "Searching for Well-known GeogCS");
4461
        const char *pszWKGCSList[] = { "WGS84", "WGS72", "NAD27", "NAD83" };
4462
        char *pszWKGCS = NULL;
4463
        oSRS.exportToPrettyWkt(&pszWKGCS);
4464
        for( size_t i = 0; i < sizeof(pszWKGCSList) / 8; i++ )
4465
        {
4466
            pszWKGCS = CPLStrdup(pszWKGCSList[i]);
4467
            OGRSpatialReference oSRSTmp;
4468
            oSRSTmp.SetWellKnownGeogCS(pszWKGCSList[i]);
4469
            // Set datum to unknown, bug #4281.
4470
            if( oSRSTmp.GetAttrNode("DATUM" ) )
4471
                oSRSTmp.GetAttrNode("DATUM")->GetChild(0)->SetValue("unknown");
4472
            // Could use OGRSpatialReference::StripCTParms(), but let's keep
4473
            // TOWGS84.
4474
            oSRSTmp.GetRoot()->StripNodes("AXIS");
4475
            oSRSTmp.GetRoot()->StripNodes("AUTHORITY");
4476
            oSRSTmp.GetRoot()->StripNodes("EXTENSION");
4477
4478
            oSRSTmp.exportToPrettyWkt(&pszWKGCS);
4479
            if( oSRS.IsSameGeogCS(&oSRSTmp) )
4480
            {
4481
                oSRS.SetWellKnownGeogCS(pszWKGCSList[i]);
4482
                oSRS.exportToWkt(&(pszTempProjection));
4483
                SetProjection(pszTempProjection);
4484
                CPLFree(pszTempProjection);
4485
            }
4486
        }
4487
    }
4488
#endif
4489
0
}
4490
4491
void netCDFDataset::SetProjectionFromVar(int nGroupId, int nVarId,
4492
                                         bool bReadSRSOnly)
4493
23
{
4494
23
    SetProjectionFromVar(nGroupId, nVarId, bReadSRSOnly, nullptr, nullptr,
4495
23
                         nullptr, nullptr);
4496
23
}
4497
4498
bool netCDFDataset::ProcessNASAL2OceanGeoLocation(int nGroupId, int nVarId)
4499
0
{
4500
    // Cf https://oceancolor.gsfc.nasa.gov/docs/format/l2nc/
4501
    // and https://github.com/OSGeo/gdal/issues/7605
4502
4503
    // Check for a structure like:
4504
    /* netcdf SNPP_VIIRS.20230406T024200.L2.OC.NRT {
4505
        dimensions:
4506
            number_of_lines = 3248 ;
4507
            pixels_per_line = 3200 ;
4508
            [...]
4509
            pixel_control_points = 3200 ;
4510
        [...]
4511
        group: geophysical_data {
4512
          variables:
4513
            short aot_862(number_of_lines, pixels_per_line) ;  <-- nVarId
4514
                [...]
4515
        }
4516
        group: navigation_data {
4517
          variables:
4518
            float longitude(number_of_lines, pixel_control_points) ;
4519
                [...]
4520
            float latitude(number_of_lines, pixel_control_points) ;
4521
                [...]
4522
        }
4523
    }
4524
    */
4525
    // Note that the longitude and latitude arrays are not indexed by the
4526
    // same dimensions. Handle only the case where
4527
    // pixel_control_points == pixels_per_line
4528
    // If there was a subsampling of the geolocation arrays, we'd need to
4529
    // add more logic.
4530
4531
0
    std::string osGroupName;
4532
0
    osGroupName.resize(NC_MAX_NAME);
4533
0
    NCDF_ERR(nc_inq_grpname(nGroupId, &osGroupName[0]));
4534
0
    osGroupName.resize(strlen(osGroupName.data()));
4535
0
    if (osGroupName != "geophysical_data")
4536
0
        return false;
4537
4538
0
    int nVarDims = 0;
4539
0
    NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
4540
0
    if (nVarDims != 2)
4541
0
        return false;
4542
4543
0
    int nNavigationDataGrpId = 0;
4544
0
    if (nc_inq_grp_ncid(cdfid, "navigation_data", &nNavigationDataGrpId) !=
4545
0
        NC_NOERR)
4546
0
        return false;
4547
4548
0
    std::array<int, 2> anVarDimIds;
4549
0
    NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
4550
4551
0
    int nLongitudeId = 0;
4552
0
    int nLatitudeId = 0;
4553
0
    if (nc_inq_varid(nNavigationDataGrpId, "longitude", &nLongitudeId) !=
4554
0
            NC_NOERR ||
4555
0
        nc_inq_varid(nNavigationDataGrpId, "latitude", &nLatitudeId) !=
4556
0
            NC_NOERR)
4557
0
    {
4558
0
        return false;
4559
0
    }
4560
4561
0
    int nDimsLongitude = 0;
4562
0
    NCDF_ERR(
4563
0
        nc_inq_varndims(nNavigationDataGrpId, nLongitudeId, &nDimsLongitude));
4564
0
    int nDimsLatitude = 0;
4565
0
    NCDF_ERR(
4566
0
        nc_inq_varndims(nNavigationDataGrpId, nLatitudeId, &nDimsLatitude));
4567
0
    if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
4568
0
    {
4569
0
        return false;
4570
0
    }
4571
4572
0
    std::array<int, 2> anDimLongitudeIds;
4573
0
    NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLongitudeId,
4574
0
                             anDimLongitudeIds.data()));
4575
0
    std::array<int, 2> anDimLatitudeIds;
4576
0
    NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLatitudeId,
4577
0
                             anDimLatitudeIds.data()));
4578
0
    if (anDimLongitudeIds != anDimLatitudeIds)
4579
0
    {
4580
0
        return false;
4581
0
    }
4582
4583
0
    std::array<size_t, 2> anSizeVarDimIds;
4584
0
    std::array<size_t, 2> anSizeLongLatIds;
4585
0
    if (!(nc_inq_dimlen(cdfid, anVarDimIds[0], &anSizeVarDimIds[0]) ==
4586
0
              NC_NOERR &&
4587
0
          nc_inq_dimlen(cdfid, anVarDimIds[1], &anSizeVarDimIds[1]) ==
4588
0
              NC_NOERR &&
4589
0
          nc_inq_dimlen(cdfid, anDimLongitudeIds[0], &anSizeLongLatIds[0]) ==
4590
0
              NC_NOERR &&
4591
0
          nc_inq_dimlen(cdfid, anDimLongitudeIds[1], &anSizeLongLatIds[1]) ==
4592
0
              NC_NOERR &&
4593
0
          anSizeVarDimIds == anSizeLongLatIds))
4594
0
    {
4595
0
        return false;
4596
0
    }
4597
4598
0
    const char *pszGeolocXFullName = "/navigation_data/longitude";
4599
0
    const char *pszGeolocYFullName = "/navigation_data/latitude";
4600
4601
0
    if (bSwitchedXY)
4602
0
    {
4603
0
        std::swap(pszGeolocXFullName, pszGeolocYFullName);
4604
0
        GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", "GEOLOCATION");
4605
0
    }
4606
4607
0
    CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
4608
0
             pszGeolocXFullName, pszGeolocYFullName);
4609
4610
0
    GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
4611
0
                                    "GEOLOCATION");
4612
4613
0
    CPLString osTMP;
4614
0
    osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
4615
4616
0
    GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
4617
0
    GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
4618
0
    osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
4619
4620
0
    GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
4621
0
    GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
4622
4623
0
    GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
4624
0
    GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
4625
4626
0
    GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
4627
0
    GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
4628
4629
0
    GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
4630
0
                                    "GEOLOCATION");
4631
0
    return true;
4632
0
}
4633
4634
bool netCDFDataset::ProcessNASAEMITGeoLocation(int nGroupId, int nVarId)
4635
0
{
4636
    // Cf https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/
4637
4638
    // Check for a structure like:
4639
    /* netcdf EMIT_L2A_RFL_001_20220903T163129_2224611_012 {
4640
        dimensions:
4641
            downtrack = 1280 ;
4642
            crosstrack = 1242 ;
4643
            bands = 285 ;
4644
            [...]
4645
4646
        variables:
4647
            float reflectance(downtrack, crosstrack, bands) ;
4648
4649
        group: location {
4650
          variables:
4651
                double lon(downtrack, crosstrack) ;
4652
                        lon:_FillValue = -9999. ;
4653
                        lon:long_name = "Longitude (WGS-84)" ;
4654
                        lon:units = "degrees east" ;
4655
                double lat(downtrack, crosstrack) ;
4656
                        lat:_FillValue = -9999. ;
4657
                        lat:long_name = "Latitude (WGS-84)" ;
4658
                        lat:units = "degrees north" ;
4659
          } // group location
4660
4661
    }
4662
    or
4663
    netcdf EMIT_L2B_MIN_001_20231024T055538_2329704_040 {
4664
        dimensions:
4665
                downtrack = 1664 ;
4666
                crosstrack = 1242 ;
4667
                [...]
4668
        variables:
4669
                float group_1_band_depth(downtrack, crosstrack) ;
4670
                        group_1_band_depth:_FillValue = -9999.f ;
4671
                        group_1_band_depth:long_name = "Group 1 Band Depth" ;
4672
                        group_1_band_depth:units = "unitless" ;
4673
                [...]
4674
        group: location {
4675
          variables:
4676
                double lon(downtrack, crosstrack) ;
4677
                        lon:_FillValue = -9999. ;
4678
                        lon:long_name = "Longitude (WGS-84)" ;
4679
                        lon:units = "degrees east" ;
4680
                double lat(downtrack, crosstrack) ;
4681
                        lat:_FillValue = -9999. ;
4682
                        lat:long_name = "Latitude (WGS-84)" ;
4683
                        lat:units = "degrees north" ;
4684
        }
4685
    */
4686
4687
0
    int nVarDims = 0;
4688
0
    NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
4689
0
    if (nVarDims != 2 && nVarDims != 3)
4690
0
        return false;
4691
4692
0
    int nLocationGrpId = 0;
4693
0
    if (nc_inq_grp_ncid(cdfid, "location", &nLocationGrpId) != NC_NOERR)
4694
0
        return false;
4695
4696
0
    std::array<int, 3> anVarDimIds;
4697
0
    NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
4698
0
    if (nYDimID != anVarDimIds[0] || nXDimID != anVarDimIds[1])
4699
0
        return false;
4700
4701
0
    int nLongitudeId = 0;
4702
0
    int nLatitudeId = 0;
4703
0
    if (nc_inq_varid(nLocationGrpId, "lon", &nLongitudeId) != NC_NOERR ||
4704
0
        nc_inq_varid(nLocationGrpId, "lat", &nLatitudeId) != NC_NOERR)
4705
0
    {
4706
0
        return false;
4707
0
    }
4708
4709
0
    int nDimsLongitude = 0;
4710
0
    NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLongitudeId, &nDimsLongitude));
4711
0
    int nDimsLatitude = 0;
4712
0
    NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLatitudeId, &nDimsLatitude));
4713
0
    if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
4714
0
    {
4715
0
        return false;
4716
0
    }
4717
4718
0
    std::array<int, 2> anDimLongitudeIds;
4719
0
    NCDF_ERR(nc_inq_vardimid(nLocationGrpId, nLongitudeId,
4720
0
                             anDimLongitudeIds.data()));
4721
0
    std::array<int, 2> anDimLatitudeIds;
4722
0
    NCDF_ERR(
4723
0
        nc_inq_vardimid(nLocationGrpId, nLatitudeId, anDimLatitudeIds.data()));
4724
0
    if (anDimLongitudeIds != anDimLatitudeIds)
4725
0
    {
4726
0
        return false;
4727
0
    }
4728
4729
0
    if (anDimLongitudeIds[0] != anVarDimIds[0] ||
4730
0
        anDimLongitudeIds[1] != anVarDimIds[1])
4731
0
    {
4732
0
        return false;
4733
0
    }
4734
4735
0
    const char *pszGeolocXFullName = "/location/lon";
4736
0
    const char *pszGeolocYFullName = "/location/lat";
4737
4738
0
    CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
4739
0
             pszGeolocXFullName, pszGeolocYFullName);
4740
4741
0
    GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
4742
0
                                    "GEOLOCATION");
4743
4744
0
    CPLString osTMP;
4745
0
    osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
4746
4747
0
    GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
4748
0
    GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
4749
0
    osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
4750
4751
0
    GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
4752
0
    GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
4753
4754
0
    GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
4755
0
    GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
4756
4757
0
    GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
4758
0
    GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
4759
4760
0
    GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
4761
0
                                    "GEOLOCATION");
4762
0
    return true;
4763
0
}
4764
4765
int netCDFDataset::ProcessCFGeolocation(int nGroupId, int nVarId,
4766
                                        const std::string &osGeolocWKT,
4767
                                        std::string &osGeolocXNameOut,
4768
                                        std::string &osGeolocYNameOut)
4769
0
{
4770
0
    bool bAddGeoloc = false;
4771
0
    char *pszCoordinates = nullptr;
4772
4773
    // If there is no explicit "coordinates" attribute, check if there are
4774
    // "lon" and "lat" 2D variables whose dimensions are the last
4775
    // 2 ones of the variable of interest.
4776
0
    if (NCDFGetAttr(nGroupId, nVarId, "coordinates", &pszCoordinates) !=
4777
0
        CE_None)
4778
0
    {
4779
0
        CPLFree(pszCoordinates);
4780
0
        pszCoordinates = nullptr;
4781
4782
0
        int nVarDims = 0;
4783
0
        NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
4784
0
        if (nVarDims >= 2)
4785
0
        {
4786
0
            std::vector<int> anVarDimIds(nVarDims);
4787
0
            NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
4788
4789
0
            int nLongitudeId = 0;
4790
0
            int nLatitudeId = 0;
4791
0
            if (nc_inq_varid(nGroupId, "lon", &nLongitudeId) == NC_NOERR &&
4792
0
                nc_inq_varid(nGroupId, "lat", &nLatitudeId) == NC_NOERR)
4793
0
            {
4794
0
                int nDimsLongitude = 0;
4795
0
                NCDF_ERR(
4796
0
                    nc_inq_varndims(nGroupId, nLongitudeId, &nDimsLongitude));
4797
0
                int nDimsLatitude = 0;
4798
0
                NCDF_ERR(
4799
0
                    nc_inq_varndims(nGroupId, nLatitudeId, &nDimsLatitude));
4800
0
                if (nDimsLongitude == 2 && nDimsLatitude == 2)
4801
0
                {
4802
0
                    std::vector<int> anDimLongitudeIds(2);
4803
0
                    NCDF_ERR(nc_inq_vardimid(nGroupId, nLongitudeId,
4804
0
                                             anDimLongitudeIds.data()));
4805
0
                    std::vector<int> anDimLatitudeIds(2);
4806
0
                    NCDF_ERR(nc_inq_vardimid(nGroupId, nLatitudeId,
4807
0
                                             anDimLatitudeIds.data()));
4808
0
                    if (anDimLongitudeIds == anDimLatitudeIds &&
4809
0
                        anVarDimIds[anVarDimIds.size() - 2] ==
4810
0
                            anDimLongitudeIds[0] &&
4811
0
                        anVarDimIds[anVarDimIds.size() - 1] ==
4812
0
                            anDimLongitudeIds[1])
4813
0
                    {
4814
0
                        pszCoordinates = CPLStrdup("lon lat");
4815
0
                    }
4816
0
                }
4817
0
            }
4818
0
        }
4819
0
    }
4820
4821
0
    if (pszCoordinates)
4822
0
    {
4823
        // Get X and Y geolocation names from coordinates attribute.
4824
0
        const CPLStringList aosCoordinates(
4825
0
            NCDFTokenizeCoordinatesAttribute(pszCoordinates));
4826
0
        if (aosCoordinates.size() >= 2)
4827
0
        {
4828
0
            char szGeolocXName[NC_MAX_NAME + 1];
4829
0
            char szGeolocYName[NC_MAX_NAME + 1];
4830
0
            szGeolocXName[0] = '\0';
4831
0
            szGeolocYName[0] = '\0';
4832
4833
            // Test that each variable is longitude/latitude.
4834
0
            for (int i = 0; i < aosCoordinates.size(); i++)
4835
0
            {
4836
0
                if (NCDFIsVarLongitude(nGroupId, -1, aosCoordinates[i]))
4837
0
                {
4838
0
                    int nOtherGroupId = -1;
4839
0
                    int nOtherVarId = -1;
4840
                    // Check that the variable actually exists
4841
                    // Needed on Sentinel-3 products
4842
0
                    if (NCDFResolveVar(nGroupId, aosCoordinates[i],
4843
0
                                       &nOtherGroupId, &nOtherVarId) == CE_None)
4844
0
                    {
4845
0
                        snprintf(szGeolocXName, sizeof(szGeolocXName), "%s",
4846
0
                                 aosCoordinates[i]);
4847
0
                    }
4848
0
                }
4849
0
                else if (NCDFIsVarLatitude(nGroupId, -1, aosCoordinates[i]))
4850
0
                {
4851
0
                    int nOtherGroupId = -1;
4852
0
                    int nOtherVarId = -1;
4853
                    // Check that the variable actually exists
4854
                    // Needed on Sentinel-3 products
4855
0
                    if (NCDFResolveVar(nGroupId, aosCoordinates[i],
4856
0
                                       &nOtherGroupId, &nOtherVarId) == CE_None)
4857
0
                    {
4858
0
                        snprintf(szGeolocYName, sizeof(szGeolocYName), "%s",
4859
0
                                 aosCoordinates[i]);
4860
0
                    }
4861
0
                }
4862
0
            }
4863
            // Add GEOLOCATION metadata.
4864
0
            if (!EQUAL(szGeolocXName, "") && !EQUAL(szGeolocYName, ""))
4865
0
            {
4866
0
                osGeolocXNameOut = szGeolocXName;
4867
0
                osGeolocYNameOut = szGeolocYName;
4868
4869
0
                char *pszGeolocXFullName = nullptr;
4870
0
                char *pszGeolocYFullName = nullptr;
4871
0
                if (NCDFResolveVarFullName(nGroupId, szGeolocXName,
4872
0
                                           &pszGeolocXFullName) == CE_None &&
4873
0
                    NCDFResolveVarFullName(nGroupId, szGeolocYName,
4874
0
                                           &pszGeolocYFullName) == CE_None)
4875
0
                {
4876
0
                    if (bSwitchedXY)
4877
0
                    {
4878
0
                        std::swap(pszGeolocXFullName, pszGeolocYFullName);
4879
0
                        GDALPamDataset::SetMetadataItem("SWAP_XY", "YES",
4880
0
                                                        "GEOLOCATION");
4881
0
                    }
4882
4883
0
                    bAddGeoloc = true;
4884
0
                    CPLDebug("GDAL_netCDF",
4885
0
                             "using variables %s and %s for GEOLOCATION",
4886
0
                             pszGeolocXFullName, pszGeolocYFullName);
4887
4888
0
                    GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
4889
0
                                                    "GEOLOCATION");
4890
4891
0
                    CPLString osTMP;
4892
0
                    osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4893
0
                                 pszGeolocXFullName);
4894
4895
0
                    GDALPamDataset::SetMetadataItem("X_DATASET", osTMP,
4896
0
                                                    "GEOLOCATION");
4897
0
                    GDALPamDataset::SetMetadataItem("X_BAND", "1",
4898
0
                                                    "GEOLOCATION");
4899
0
                    osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
4900
0
                                 pszGeolocYFullName);
4901
4902
0
                    GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP,
4903
0
                                                    "GEOLOCATION");
4904
0
                    GDALPamDataset::SetMetadataItem("Y_BAND", "1",
4905
0
                                                    "GEOLOCATION");
4906
4907
0
                    GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0",
4908
0
                                                    "GEOLOCATION");
4909
0
                    GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1",
4910
0
                                                    "GEOLOCATION");
4911
4912
0
                    GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0",
4913
0
                                                    "GEOLOCATION");
4914
0
                    GDALPamDataset::SetMetadataItem("LINE_STEP", "1",
4915
0
                                                    "GEOLOCATION");
4916
4917
0
                    GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
4918
0
                                                    "PIXEL_CENTER",
4919
0
                                                    "GEOLOCATION");
4920
0
                }
4921
0
                else
4922
0
                {
4923
0
                    CPLDebug("GDAL_netCDF",
4924
0
                             "cannot resolve location of "
4925
0
                             "lat/lon variables specified by the coordinates "
4926
0
                             "attribute [%s]",
4927
0
                             pszCoordinates);
4928
0
                }
4929
0
                CPLFree(pszGeolocXFullName);
4930
0
                CPLFree(pszGeolocYFullName);
4931
0
            }
4932
0
            else
4933
0
            {
4934
0
                CPLDebug("GDAL_netCDF",
4935
0
                         "coordinates attribute [%s] is unsupported",
4936
0
                         pszCoordinates);
4937
0
            }
4938
0
        }
4939
0
        else
4940
0
        {
4941
0
            CPLDebug("GDAL_netCDF",
4942
0
                     "coordinates attribute [%s] with %d element(s) is "
4943
0
                     "unsupported",
4944
0
                     pszCoordinates, aosCoordinates.size());
4945
0
        }
4946
0
    }
4947
4948
0
    else
4949
0
    {
4950
0
        bAddGeoloc = ProcessNASAL2OceanGeoLocation(nGroupId, nVarId);
4951
4952
0
        if (!bAddGeoloc)
4953
0
            bAddGeoloc = ProcessNASAEMITGeoLocation(nGroupId, nVarId);
4954
0
    }
4955
4956
0
    CPLFree(pszCoordinates);
4957
4958
0
    return bAddGeoloc;
4959
0
}
4960
4961
CPLErr netCDFDataset::Set1DGeolocation(int nGroupId, int nVarId,
4962
                                       const char *szDimName)
4963
0
{
4964
    // Get values.
4965
0
    char *pszVarValues = nullptr;
4966
0
    CPLErr eErr = NCDFGet1DVar(nGroupId, nVarId, &pszVarValues);
4967
0
    if (eErr != CE_None)
4968
0
        return eErr;
4969
4970
    // Write metadata.
4971
0
    char szTemp[NC_MAX_NAME + 1 + 32] = {};
4972
0
    snprintf(szTemp, sizeof(szTemp), "%s_VALUES", szDimName);
4973
0
    GDALPamDataset::SetMetadataItem(szTemp, pszVarValues, "GEOLOCATION2");
4974
4975
0
    CPLFree(pszVarValues);
4976
4977
0
    return CE_None;
4978
0
}
4979
4980
double *netCDFDataset::Get1DGeolocation(CPL_UNUSED const char *szDimName,
4981
                                        int &nVarLen)
4982
0
{
4983
0
    nVarLen = 0;
4984
4985
    // Get Y_VALUES as tokens.
4986
0
    char **papszValues =
4987
0
        NCDFTokenizeArray(GetMetadataItem("Y_VALUES", "GEOLOCATION2"));
4988
0
    if (papszValues == nullptr)
4989
0
        return nullptr;
4990
4991
    // Initialize and fill array.
4992
0
    nVarLen = CSLCount(papszValues);
4993
0
    double *pdfVarValues =
4994
0
        static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
4995
4996
0
    for (int i = 0, j = 0; i < nVarLen; i++)
4997
0
    {
4998
0
        if (!bBottomUp)
4999
0
            j = nVarLen - 1 - i;
5000
0
        else
5001
0
            j = i;  // Invert latitude values.
5002
0
        char *pszTemp = nullptr;
5003
0
        pdfVarValues[j] = CPLStrtod(papszValues[i], &pszTemp);
5004
0
    }
5005
0
    CSLDestroy(papszValues);
5006
5007
0
    return pdfVarValues;
5008
0
}
5009
5010
/************************************************************************/
5011
/*                        SetSpatialRefNoUpdate()                       */
5012
/************************************************************************/
5013
5014
void netCDFDataset::SetSpatialRefNoUpdate(const OGRSpatialReference *poSRS)
5015
0
{
5016
0
    m_oSRS.Clear();
5017
0
    if (poSRS)
5018
0
        m_oSRS = *poSRS;
5019
0
    m_bHasProjection = true;
5020
0
}
5021
5022
/************************************************************************/
5023
/*                          SetSpatialRef()                             */
5024
/************************************************************************/
5025
5026
CPLErr netCDFDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
5027
0
{
5028
0
    CPLMutexHolderD(&hNCMutex);
5029
5030
0
    if (GetAccess() != GA_Update || m_bHasProjection)
5031
0
    {
5032
0
        CPLError(CE_Failure, CPLE_AppDefined,
5033
0
                 "netCDFDataset::_SetProjection() should only be called once "
5034
0
                 "in update mode!");
5035
0
        return CE_Failure;
5036
0
    }
5037
5038
0
    if (m_bHasGeoTransform)
5039
0
    {
5040
0
        SetSpatialRefNoUpdate(poSRS);
5041
5042
        // For NC4/NC4C, writing both projection variables and data,
5043
        // followed by redefining nodata value, cancels the projection
5044
        // info from the Band variable, so for now only write the
5045
        // variable definitions, and write data at the end.
5046
        // See https://trac.osgeo.org/gdal/ticket/7245
5047
0
        return AddProjectionVars(true, nullptr, nullptr);
5048
0
    }
5049
5050
0
    SetSpatialRefNoUpdate(poSRS);
5051
5052
0
    return CE_None;
5053
0
}
5054
5055
/************************************************************************/
5056
/*                     SetGeoTransformNoUpdate()                        */
5057
/************************************************************************/
5058
5059
void netCDFDataset::SetGeoTransformNoUpdate(double *padfTransform)
5060
0
{
5061
0
    memcpy(m_adfGeoTransform, padfTransform, sizeof(double) * 6);
5062
0
    m_bHasGeoTransform = true;
5063
0
}
5064
5065
/************************************************************************/
5066
/*                          SetGeoTransform()                           */
5067
/************************************************************************/
5068
5069
CPLErr netCDFDataset::SetGeoTransform(double *padfTransform)
5070
0
{
5071
0
    CPLMutexHolderD(&hNCMutex);
5072
5073
0
    if (GetAccess() != GA_Update || m_bHasGeoTransform)
5074
0
    {
5075
0
        CPLError(CE_Failure, CPLE_AppDefined,
5076
0
                 "netCDFDataset::SetGeoTransform() should only be called once "
5077
0
                 "in update mode!");
5078
0
        return CE_Failure;
5079
0
    }
5080
5081
0
    CPLDebug("GDAL_netCDF", "SetGeoTransform(%f,%f,%f,%f,%f,%f)",
5082
0
             padfTransform[0], padfTransform[1], padfTransform[2],
5083
0
             padfTransform[3], padfTransform[4], padfTransform[5]);
5084
5085
0
    if (m_bHasProjection)
5086
0
    {
5087
0
        SetGeoTransformNoUpdate(padfTransform);
5088
5089
        // For NC4/NC4C, writing both projection variables and data,
5090
        // followed by redefining nodata value, cancels the projection
5091
        // info from the Band variable, so for now only write the
5092
        // variable definitions, and write data at the end.
5093
        // See https://trac.osgeo.org/gdal/ticket/7245
5094
0
        return AddProjectionVars(true, nullptr, nullptr);
5095
0
    }
5096
5097
0
    SetGeoTransformNoUpdate(padfTransform);
5098
0
    return CE_None;
5099
0
}
5100
5101
/************************************************************************/
5102
/*                         NCDFWriteSRSVariable()                       */
5103
/************************************************************************/
5104
5105
int NCDFWriteSRSVariable(int cdfid, const OGRSpatialReference *poSRS,
5106
                         char **ppszCFProjection, bool bWriteGDALTags,
5107
                         const std::string &srsVarName)
5108
0
{
5109
0
    char *pszCFProjection = nullptr;
5110
0
    char **papszKeyValues = nullptr;
5111
0
    poSRS->exportToCF1(&pszCFProjection, &papszKeyValues, nullptr, nullptr);
5112
5113
0
    if (bWriteGDALTags)
5114
0
    {
5115
0
        const char *pszWKT = CSLFetchNameValue(papszKeyValues, NCDF_CRS_WKT);
5116
0
        if (pszWKT)
5117
0
        {
5118
            // SPATIAL_REF is deprecated. Will be removed in GDAL 4.
5119
0
            papszKeyValues =
5120
0
                CSLSetNameValue(papszKeyValues, NCDF_SPATIAL_REF, pszWKT);
5121
0
        }
5122
0
    }
5123
5124
0
    const int nValues = CSLCount(papszKeyValues);
5125
5126
0
    int NCDFVarID;
5127
0
    std::string varNameRadix(pszCFProjection);
5128
0
    int nCounter = 2;
5129
0
    while (true)
5130
0
    {
5131
0
        NCDFVarID = -1;
5132
0
        nc_inq_varid(cdfid, pszCFProjection, &NCDFVarID);
5133
0
        if (NCDFVarID < 0)
5134
0
            break;
5135
5136
0
        int nbAttr = 0;
5137
0
        NCDF_ERR(nc_inq_varnatts(cdfid, NCDFVarID, &nbAttr));
5138
0
        bool bSame = nbAttr == nValues;
5139
0
        for (int i = 0; bSame && (i < nbAttr); i++)
5140
0
        {
5141
0
            char szAttrName[NC_MAX_NAME + 1];
5142
0
            szAttrName[0] = 0;
5143
0
            NCDF_ERR(nc_inq_attname(cdfid, NCDFVarID, i, szAttrName));
5144
5145
0
            const char *pszValue =
5146
0
                CSLFetchNameValue(papszKeyValues, szAttrName);
5147
0
            if (!pszValue)
5148
0
            {
5149
0
                bSame = false;
5150
0
                break;
5151
0
            }
5152
5153
0
            nc_type atttype = NC_NAT;
5154
0
            size_t attlen = 0;
5155
0
            NCDF_ERR(
5156
0
                nc_inq_att(cdfid, NCDFVarID, szAttrName, &atttype, &attlen));
5157
0
            if (atttype != NC_CHAR && atttype != NC_DOUBLE)
5158
0
            {
5159
0
                bSame = false;
5160
0
                break;
5161
0
            }
5162
0
            if (atttype == NC_CHAR)
5163
0
            {
5164
0
                if (CPLGetValueType(pszValue) != CPL_VALUE_STRING)
5165
0
                {
5166
0
                    bSame = false;
5167
0
                    break;
5168
0
                }
5169
0
                std::string val;
5170
0
                val.resize(attlen);
5171
0
                nc_get_att_text(cdfid, NCDFVarID, szAttrName, &val[0]);
5172
0
                if (val != pszValue)
5173
0
                {
5174
0
                    bSame = false;
5175
0
                    break;
5176
0
                }
5177
0
            }
5178
0
            else
5179
0
            {
5180
0
                const CPLStringList aosTokens(
5181
0
                    CSLTokenizeString2(pszValue, ",", 0));
5182
0
                if (static_cast<size_t>(aosTokens.size()) != attlen)
5183
0
                {
5184
0
                    bSame = false;
5185
0
                    break;
5186
0
                }
5187
0
                double vals[2];
5188
0
                nc_get_att_double(cdfid, NCDFVarID, szAttrName, vals);
5189
0
                if (vals[0] != CPLAtof(aosTokens[0]) ||
5190
0
                    (attlen == 2 && vals[1] != CPLAtof(aosTokens[1])))
5191
0
                {
5192
0
                    bSame = false;
5193
0
                    break;
5194
0
                }
5195
0
            }
5196
0
        }
5197
0
        if (bSame)
5198
0
        {
5199
0
            *ppszCFProjection = pszCFProjection;
5200
0
            CSLDestroy(papszKeyValues);
5201
0
            return NCDFVarID;
5202
0
        }
5203
0
        CPLFree(pszCFProjection);
5204
0
        pszCFProjection =
5205
0
            CPLStrdup(CPLSPrintf("%s_%d", varNameRadix.c_str(), nCounter));
5206
0
        nCounter++;
5207
0
    }
5208
5209
0
    *ppszCFProjection = pszCFProjection;
5210
5211
0
    const char *pszVarName;
5212
5213
0
    if (srsVarName != "")
5214
0
    {
5215
0
        pszVarName = srsVarName.c_str();
5216
0
    }
5217
0
    else
5218
0
    {
5219
0
        pszVarName = pszCFProjection;
5220
0
    }
5221
5222
0
    int status = nc_def_var(cdfid, pszVarName, NC_CHAR, 0, nullptr, &NCDFVarID);
5223
0
    NCDF_ERR(status);
5224
0
    for (int i = 0; i < nValues; ++i)
5225
0
    {
5226
0
        char *pszKey = nullptr;
5227
0
        const char *pszValue = CPLParseNameValue(papszKeyValues[i], &pszKey);
5228
0
        if (pszKey && pszValue)
5229
0
        {
5230
0
            const CPLStringList aosTokens(CSLTokenizeString2(pszValue, ",", 0));
5231
0
            double adfValues[2] = {0, 0};
5232
0
            const int nDoubleCount = std::min(2, aosTokens.size());
5233
0
            if (!(aosTokens.size() == 2 &&
5234
0
                  CPLGetValueType(aosTokens[0]) != CPL_VALUE_STRING) &&
5235
0
                CPLGetValueType(pszValue) == CPL_VALUE_STRING)
5236
0
            {
5237
0
                status = nc_put_att_text(cdfid, NCDFVarID, pszKey,
5238
0
                                         strlen(pszValue), pszValue);
5239
0
            }
5240
0
            else
5241
0
            {
5242
0
                for (int j = 0; j < nDoubleCount; ++j)
5243
0
                    adfValues[j] = CPLAtof(aosTokens[j]);
5244
0
                status = nc_put_att_double(cdfid, NCDFVarID, pszKey, NC_DOUBLE,
5245
0
                                           nDoubleCount, adfValues);
5246
0
            }
5247
0
            NCDF_ERR(status);
5248
0
        }
5249
0
        CPLFree(pszKey);
5250
0
    }
5251
5252
0
    CSLDestroy(papszKeyValues);
5253
0
    return NCDFVarID;
5254
0
}
5255
5256
/************************************************************************/
5257
/*                   NCDFWriteLonLatVarsAttributes()                    */
5258
/************************************************************************/
5259
5260
void NCDFWriteLonLatVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarLonID,
5261
                                   int nVarLatID)
5262
0
{
5263
5264
0
    try
5265
0
    {
5266
0
        vcdf.nc_put_vatt_text(nVarLatID, CF_STD_NAME, CF_LATITUDE_STD_NAME);
5267
0
        vcdf.nc_put_vatt_text(nVarLatID, CF_LNG_NAME, CF_LATITUDE_LNG_NAME);
5268
0
        vcdf.nc_put_vatt_text(nVarLatID, CF_UNITS, CF_DEGREES_NORTH);
5269
0
        vcdf.nc_put_vatt_text(nVarLonID, CF_STD_NAME, CF_LONGITUDE_STD_NAME);
5270
0
        vcdf.nc_put_vatt_text(nVarLonID, CF_LNG_NAME, CF_LONGITUDE_LNG_NAME);
5271
0
        vcdf.nc_put_vatt_text(nVarLonID, CF_UNITS, CF_DEGREES_EAST);
5272
0
    }
5273
0
    catch (nccfdriver::SG_Exception &e)
5274
0
    {
5275
0
        CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5276
0
    }
5277
0
}
5278
5279
/************************************************************************/
5280
/*                   NCDFWriteRLonRLatVarsAttributes()                    */
5281
/************************************************************************/
5282
5283
void NCDFWriteRLonRLatVarsAttributes(nccfdriver::netCDFVID &vcdf,
5284
                                     int nVarRLonID, int nVarRLatID)
5285
0
{
5286
0
    try
5287
0
    {
5288
0
        vcdf.nc_put_vatt_text(nVarRLatID, CF_STD_NAME, "grid_latitude");
5289
0
        vcdf.nc_put_vatt_text(nVarRLatID, CF_LNG_NAME,
5290
0
                              "latitude in rotated pole grid");
5291
0
        vcdf.nc_put_vatt_text(nVarRLatID, CF_UNITS, "degrees");
5292
0
        vcdf.nc_put_vatt_text(nVarRLatID, CF_AXIS, "Y");
5293
5294
0
        vcdf.nc_put_vatt_text(nVarRLonID, CF_STD_NAME, "grid_longitude");
5295
0
        vcdf.nc_put_vatt_text(nVarRLonID, CF_LNG_NAME,
5296
0
                              "longitude in rotated pole grid");
5297
0
        vcdf.nc_put_vatt_text(nVarRLonID, CF_UNITS, "degrees");
5298
0
        vcdf.nc_put_vatt_text(nVarRLonID, CF_AXIS, "X");
5299
0
    }
5300
0
    catch (nccfdriver::SG_Exception &e)
5301
0
    {
5302
0
        CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5303
0
    }
5304
0
}
5305
5306
/************************************************************************/
5307
/*                        NCDFGetProjectedCFUnit()                      */
5308
/************************************************************************/
5309
5310
std::string NCDFGetProjectedCFUnit(const OGRSpatialReference *poSRS)
5311
0
{
5312
0
    char *pszUnitsToWrite = nullptr;
5313
0
    poSRS->exportToCF1(nullptr, nullptr, &pszUnitsToWrite, nullptr);
5314
0
    std::string osRet = pszUnitsToWrite ? pszUnitsToWrite : std::string();
5315
0
    CPLFree(pszUnitsToWrite);
5316
0
    return osRet;
5317
0
}
5318
5319
/************************************************************************/
5320
/*                     NCDFWriteXYVarsAttributes()                      */
5321
/************************************************************************/
5322
5323
void NCDFWriteXYVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarXID,
5324
                               int nVarYID, const OGRSpatialReference *poSRS)
5325
0
{
5326
0
    const std::string osUnitsToWrite = NCDFGetProjectedCFUnit(poSRS);
5327
5328
0
    try
5329
0
    {
5330
0
        vcdf.nc_put_vatt_text(nVarXID, CF_STD_NAME, CF_PROJ_X_COORD);
5331
0
        vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME, CF_PROJ_X_COORD_LONG_NAME);
5332
0
        if (!osUnitsToWrite.empty())
5333
0
            vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, osUnitsToWrite.c_str());
5334
0
        vcdf.nc_put_vatt_text(nVarYID, CF_STD_NAME, CF_PROJ_Y_COORD);
5335
0
        vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME, CF_PROJ_Y_COORD_LONG_NAME);
5336
0
        if (!osUnitsToWrite.empty())
5337
0
            vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, osUnitsToWrite.c_str());
5338
0
    }
5339
0
    catch (nccfdriver::SG_Exception &e)
5340
0
    {
5341
0
        CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5342
0
    }
5343
0
}
5344
5345
/************************************************************************/
5346
/*                          AddProjectionVars()                         */
5347
/************************************************************************/
5348
5349
CPLErr netCDFDataset::AddProjectionVars(bool bDefsOnly,
5350
                                        GDALProgressFunc pfnProgress,
5351
                                        void *pProgressData)
5352
0
{
5353
0
    if (nCFVersion >= 1.8)
5354
0
        return CE_None;  // do nothing
5355
5356
0
    bool bWriteGridMapping = false;
5357
0
    bool bWriteLonLat = false;
5358
0
    bool bHasGeoloc = false;
5359
0
    bool bWriteGDALTags = false;
5360
0
    bool bWriteGeoTransform = false;
5361
5362
    // For GEOLOCATION information.
5363
0
    GDALDatasetH hDS_X = nullptr;
5364
0
    GDALRasterBandH hBand_X = nullptr;
5365
0
    GDALDatasetH hDS_Y = nullptr;
5366
0
    GDALRasterBandH hBand_Y = nullptr;
5367
5368
0
    OGRSpatialReference oSRS(m_oSRS);
5369
0
    if (!m_oSRS.IsEmpty())
5370
0
    {
5371
0
        if (oSRS.IsProjected())
5372
0
            bIsProjected = true;
5373
0
        else if (oSRS.IsGeographic())
5374
0
            bIsGeographic = true;
5375
0
    }
5376
5377
0
    if (bDefsOnly)
5378
0
    {
5379
0
        char *pszProjection = nullptr;
5380
0
        m_oSRS.exportToWkt(&pszProjection);
5381
0
        CPLDebug("GDAL_netCDF",
5382
0
                 "SetProjection, WKT now = [%s]\nprojected: %d geographic: %d",
5383
0
                 pszProjection ? pszProjection : "(null)",
5384
0
                 static_cast<int>(bIsProjected),
5385
0
                 static_cast<int>(bIsGeographic));
5386
0
        CPLFree(pszProjection);
5387
5388
0
        if (!m_bHasGeoTransform)
5389
0
            CPLDebug("GDAL_netCDF",
5390
0
                     "netCDFDataset::AddProjectionVars() called, "
5391
0
                     "but GeoTransform has not yet been defined!");
5392
5393
0
        if (!m_bHasProjection)
5394
0
            CPLDebug("GDAL_netCDF",
5395
0
                     "netCDFDataset::AddProjectionVars() called, "
5396
0
                     "but Projection has not yet been defined!");
5397
0
    }
5398
5399
    // Check GEOLOCATION information.
5400
0
    char **papszGeolocationInfo = netCDFDataset::GetMetadata("GEOLOCATION");
5401
0
    if (papszGeolocationInfo != nullptr)
5402
0
    {
5403
        // Look for geolocation datasets.
5404
0
        const char *pszDSName =
5405
0
            CSLFetchNameValue(papszGeolocationInfo, "X_DATASET");
5406
0
        if (pszDSName != nullptr)
5407
0
            hDS_X = GDALOpenShared(pszDSName, GA_ReadOnly);
5408
0
        pszDSName = CSLFetchNameValue(papszGeolocationInfo, "Y_DATASET");
5409
0
        if (pszDSName != nullptr)
5410
0
            hDS_Y = GDALOpenShared(pszDSName, GA_ReadOnly);
5411
5412
0
        if (hDS_X != nullptr && hDS_Y != nullptr)
5413
0
        {
5414
0
            int nBand = std::max(1, atoi(CSLFetchNameValueDef(
5415
0
                                        papszGeolocationInfo, "X_BAND", "0")));
5416
0
            hBand_X = GDALGetRasterBand(hDS_X, nBand);
5417
0
            nBand = std::max(1, atoi(CSLFetchNameValueDef(papszGeolocationInfo,
5418
0
                                                          "Y_BAND", "0")));
5419
0
            hBand_Y = GDALGetRasterBand(hDS_Y, nBand);
5420
5421
            // If geoloc bands are found, do basic validation based on their
5422
            // dimensions.
5423
0
            if (hBand_X != nullptr && hBand_Y != nullptr)
5424
0
            {
5425
0
                int nXSize_XBand = GDALGetRasterXSize(hDS_X);
5426
0
                int nYSize_XBand = GDALGetRasterYSize(hDS_X);
5427
0
                int nXSize_YBand = GDALGetRasterXSize(hDS_Y);
5428
0
                int nYSize_YBand = GDALGetRasterYSize(hDS_Y);
5429
5430
                // TODO 1D geolocation arrays not implemented.
5431
0
                if (nYSize_XBand == 1 && nYSize_YBand == 1)
5432
0
                {
5433
0
                    bHasGeoloc = false;
5434
0
                    CPLDebug("GDAL_netCDF",
5435
0
                             "1D GEOLOCATION arrays not supported yet");
5436
0
                }
5437
                // 2D bands must have same sizes as the raster bands.
5438
0
                else if (nXSize_XBand != nRasterXSize ||
5439
0
                         nYSize_XBand != nRasterYSize ||
5440
0
                         nXSize_YBand != nRasterXSize ||
5441
0
                         nYSize_YBand != nRasterYSize)
5442
0
                {
5443
0
                    bHasGeoloc = false;
5444
0
                    CPLDebug("GDAL_netCDF",
5445
0
                             "GEOLOCATION array sizes (%dx%d %dx%d) differ "
5446
0
                             "from raster (%dx%d), not supported",
5447
0
                             nXSize_XBand, nYSize_XBand, nXSize_YBand,
5448
0
                             nYSize_YBand, nRasterXSize, nRasterYSize);
5449
0
                }
5450
0
                else
5451
0
                {
5452
0
                    bHasGeoloc = true;
5453
0
                    CPLDebug("GDAL_netCDF",
5454
0
                             "dataset has GEOLOCATION information, will try to "
5455
0
                             "write it");
5456
0
                }
5457
0
            }
5458
0
        }
5459
0
    }
5460
5461
    // Process projection options.
5462
0
    if (bIsProjected)
5463
0
    {
5464
0
        bool bIsCfProjection =
5465
0
            oSRS.exportToCF1(nullptr, nullptr, nullptr, nullptr) == OGRERR_NONE;
5466
0
        bWriteGridMapping = true;
5467
0
        bWriteGDALTags = CPL_TO_BOOL(
5468
0
            CSLFetchBoolean(papszCreationOptions, "WRITE_GDAL_TAGS", TRUE));
5469
        // Force WRITE_GDAL_TAGS if is not a CF projection.
5470
0
        if (!bWriteGDALTags && !bIsCfProjection)
5471
0
            bWriteGDALTags = true;
5472
0
        if (bWriteGDALTags)
5473
0
            bWriteGeoTransform = true;
5474
5475
        // Write lon/lat: default is NO, except if has geolocation.
5476
        // With IF_NEEDED: write if has geoloc or is not CF projection.
5477
0
        const char *pszValue =
5478
0
            CSLFetchNameValue(papszCreationOptions, "WRITE_LONLAT");
5479
0
        if (pszValue)
5480
0
        {
5481
0
            if (EQUAL(pszValue, "IF_NEEDED"))
5482
0
            {
5483
0
                bWriteLonLat = bHasGeoloc || !bIsCfProjection;
5484
0
            }
5485
0
            else
5486
0
            {
5487
0
                bWriteLonLat = CPLTestBool(pszValue);
5488
0
            }
5489
0
        }
5490
0
        else
5491
0
        {
5492
0
            bWriteLonLat = bHasGeoloc;
5493
0
        }
5494
5495
        // Save value of pszCFCoordinates for later.
5496
0
        if (bWriteLonLat)
5497
0
        {
5498
0
            pszCFCoordinates = NCDF_LONLAT;
5499
0
        }
5500
0
    }
5501
0
    else
5502
0
    {
5503
        // Files without a Datum will not have a grid_mapping variable and
5504
        // geographic information.
5505
0
        bWriteGridMapping = bIsGeographic;
5506
5507
0
        if (bHasGeoloc)
5508
0
        {
5509
0
            bWriteLonLat = true;
5510
0
        }
5511
0
        else
5512
0
        {
5513
0
            bWriteGDALTags = CPL_TO_BOOL(CSLFetchBoolean(
5514
0
                papszCreationOptions, "WRITE_GDAL_TAGS", bWriteGridMapping));
5515
0
            if (bWriteGDALTags)
5516
0
                bWriteGeoTransform = true;
5517
5518
0
            const char *pszValue = CSLFetchNameValueDef(papszCreationOptions,
5519
0
                                                        "WRITE_LONLAT", "YES");
5520
0
            if (EQUAL(pszValue, "IF_NEEDED"))
5521
0
                bWriteLonLat = true;
5522
0
            else
5523
0
                bWriteLonLat = CPLTestBool(pszValue);
5524
            //  Don't write lon/lat if no source geotransform.
5525
0
            if (!m_bHasGeoTransform)
5526
0
                bWriteLonLat = false;
5527
            // If we don't write lon/lat, set dimnames to X/Y and write gdal
5528
            // tags.
5529
0
            if (!bWriteLonLat)
5530
0
            {
5531
0
                CPLError(CE_Warning, CPLE_AppDefined,
5532
0
                         "creating geographic file without lon/lat values!");
5533
0
                if (m_bHasGeoTransform)
5534
0
                {
5535
0
                    bWriteGDALTags = true;  // Not desirable if no geotransform.
5536
0
                    bWriteGeoTransform = true;
5537
0
                }
5538
0
            }
5539
0
        }
5540
0
    }
5541
5542
    // Make sure we write grid_mapping if we need to write GDAL tags.
5543
0
    if (bWriteGDALTags)
5544
0
        bWriteGridMapping = true;
5545
5546
    // bottom-up value: new driver is bottom-up by default.
5547
    // Override with WRITE_BOTTOMUP.
5548
0
    bBottomUp = CPL_TO_BOOL(
5549
0
        CSLFetchBoolean(papszCreationOptions, "WRITE_BOTTOMUP", TRUE));
5550
5551
0
    if (bDefsOnly)
5552
0
    {
5553
0
        CPLDebug(
5554
0
            "GDAL_netCDF",
5555
0
            "bIsProjected=%d bIsGeographic=%d bWriteGridMapping=%d "
5556
0
            "bWriteGDALTags=%d bWriteLonLat=%d bBottomUp=%d bHasGeoloc=%d",
5557
0
            static_cast<int>(bIsProjected), static_cast<int>(bIsGeographic),
5558
0
            static_cast<int>(bWriteGridMapping),
5559
0
            static_cast<int>(bWriteGDALTags), static_cast<int>(bWriteLonLat),
5560
0
            static_cast<int>(bBottomUp), static_cast<int>(bHasGeoloc));
5561
0
    }
5562
5563
    // Exit if nothing to do.
5564
0
    if (!bIsProjected && !bWriteLonLat)
5565
0
        return CE_None;
5566
5567
    // Define dimension names.
5568
5569
0
    constexpr const char *ROTATED_POLE_VAR_NAME = "rotated_pole";
5570
5571
0
    if (bDefsOnly)
5572
0
    {
5573
0
        int nVarLonID = -1;
5574
0
        int nVarLatID = -1;
5575
0
        int nVarXID = -1;
5576
0
        int nVarYID = -1;
5577
5578
0
        m_bAddedProjectionVarsDefs = true;
5579
5580
        // Make sure we are in define mode.
5581
0
        SetDefineMode(true);
5582
5583
        // Write projection attributes.
5584
0
        if (bWriteGridMapping)
5585
0
        {
5586
0
            const int NCDFVarID = NCDFWriteSRSVariable(
5587
0
                cdfid, &oSRS, &pszCFProjection, bWriteGDALTags);
5588
0
            if (NCDFVarID < 0)
5589
0
                return CE_Failure;
5590
5591
            // Optional GDAL custom projection tags.
5592
0
            if (bWriteGDALTags)
5593
0
            {
5594
0
                CPLString osGeoTransform;
5595
0
                for (int i = 0; i < 6; i++)
5596
0
                {
5597
0
                    osGeoTransform +=
5598
0
                        CPLSPrintf("%.17g ", m_adfGeoTransform[i]);
5599
0
                }
5600
0
                CPLDebug("GDAL_netCDF", "szGeoTransform = %s",
5601
0
                         osGeoTransform.c_str());
5602
5603
                // if( strlen(pszProj4Defn) > 0 ) {
5604
                //     nc_put_att_text(cdfid, NCDFVarID, "proj4",
5605
                //                      strlen(pszProj4Defn), pszProj4Defn);
5606
                // }
5607
5608
                // For now, write the geotransform for back-compat or else
5609
                // the old (1.8.1) driver overrides the CF geotransform with
5610
                // empty values from dfNN, dfSN, dfEE, dfWE;
5611
5612
                // TODO: fix this in 1.8 branch, and then remove this here.
5613
0
                if (bWriteGeoTransform && m_bHasGeoTransform)
5614
0
                {
5615
0
                    {
5616
0
                        const int status = nc_put_att_text(
5617
0
                            cdfid, NCDFVarID, NCDF_GEOTRANSFORM,
5618
0
                            osGeoTransform.size(), osGeoTransform.c_str());
5619
0
                        NCDF_ERR(status);
5620
0
                    }
5621
0
                }
5622
0
            }
5623
5624
            // Write projection variable to band variable.
5625
            // Need to call later if there are no bands.
5626
0
            AddGridMappingRef();
5627
0
        }  // end if( bWriteGridMapping )
5628
5629
        // Write CF Projection vars.
5630
5631
0
        const bool bIsRotatedPole =
5632
0
            pszCFProjection != nullptr &&
5633
0
            EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
5634
0
        if (bIsRotatedPole)
5635
0
        {
5636
            // Rename dims to rlat/rlon.
5637
0
            papszDimName
5638
0
                .Clear();  // If we add other dims one day, this has to change
5639
0
            papszDimName.AddString(NCDF_DIMNAME_RLAT);
5640
0
            papszDimName.AddString(NCDF_DIMNAME_RLON);
5641
5642
0
            int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_RLAT);
5643
0
            NCDF_ERR(status);
5644
0
            status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_RLON);
5645
0
            NCDF_ERR(status);
5646
0
        }
5647
        // Rename dimensions if lon/lat.
5648
0
        else if (!bIsProjected && !bHasGeoloc)
5649
0
        {
5650
            // Rename dims to lat/lon.
5651
0
            papszDimName
5652
0
                .Clear();  // If we add other dims one day, this has to change
5653
0
            papszDimName.AddString(NCDF_DIMNAME_LAT);
5654
0
            papszDimName.AddString(NCDF_DIMNAME_LON);
5655
5656
0
            int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_LAT);
5657
0
            NCDF_ERR(status);
5658
0
            status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_LON);
5659
0
            NCDF_ERR(status);
5660
0
        }
5661
5662
        // Write X/Y attributes.
5663
0
        else /* if( bIsProjected || bHasGeoloc ) */
5664
0
        {
5665
            // X
5666
0
            int anXDims[1];
5667
0
            anXDims[0] = nXDimID;
5668
0
            CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
5669
0
                     CF_PROJ_X_VAR_NAME, NC_DOUBLE);
5670
0
            int status = nc_def_var(cdfid, CF_PROJ_X_VAR_NAME, NC_DOUBLE, 1,
5671
0
                                    anXDims, &nVarXID);
5672
0
            NCDF_ERR(status);
5673
5674
            // Y
5675
0
            int anYDims[1];
5676
0
            anYDims[0] = nYDimID;
5677
0
            CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
5678
0
                     CF_PROJ_Y_VAR_NAME, NC_DOUBLE);
5679
0
            status = nc_def_var(cdfid, CF_PROJ_Y_VAR_NAME, NC_DOUBLE, 1,
5680
0
                                anYDims, &nVarYID);
5681
0
            NCDF_ERR(status);
5682
5683
0
            if (bIsProjected)
5684
0
            {
5685
0
                NCDFWriteXYVarsAttributes(this->vcdf, nVarXID, nVarYID, &oSRS);
5686
0
            }
5687
0
            else
5688
0
            {
5689
0
                CPLAssert(bHasGeoloc);
5690
0
                try
5691
0
                {
5692
0
                    vcdf.nc_put_vatt_text(nVarXID, CF_AXIS, CF_SG_X_AXIS);
5693
0
                    vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME,
5694
0
                                          "x-coordinate in Cartesian system");
5695
0
                    vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, "m");
5696
0
                    vcdf.nc_put_vatt_text(nVarYID, CF_AXIS, CF_SG_Y_AXIS);
5697
0
                    vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME,
5698
0
                                          "y-coordinate in Cartesian system");
5699
0
                    vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, "m");
5700
5701
0
                    pszCFCoordinates = NCDF_LONLAT;
5702
0
                }
5703
0
                catch (nccfdriver::SG_Exception &e)
5704
0
                {
5705
0
                    CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
5706
0
                    return CE_Failure;
5707
0
                }
5708
0
            }
5709
0
        }
5710
5711
        // Write lat/lon attributes if needed.
5712
0
        if (bWriteLonLat)
5713
0
        {
5714
0
            int *panLatDims = nullptr;
5715
0
            int *panLonDims = nullptr;
5716
0
            int nLatDims = -1;
5717
0
            int nLonDims = -1;
5718
5719
            // Get information.
5720
0
            if (bHasGeoloc)
5721
0
            {
5722
                // Geoloc
5723
0
                nLatDims = 2;
5724
0
                panLatDims =
5725
0
                    static_cast<int *>(CPLCalloc(nLatDims, sizeof(int)));
5726
0
                panLatDims[0] = nYDimID;
5727
0
                panLatDims[1] = nXDimID;
5728
0
                nLonDims = 2;
5729
0
                panLonDims =
5730
0
                    static_cast<int *>(CPLCalloc(nLonDims, sizeof(int)));
5731
0
                panLonDims[0] = nYDimID;
5732
0
                panLonDims[1] = nXDimID;
5733
0
            }
5734
0
            else if (bIsProjected)
5735
0
            {
5736
                // Projected
5737
0
                nLatDims = 2;
5738
0
                panLatDims =
5739
0
                    static_cast<int *>(CPLCalloc(nLatDims, sizeof(int)));
5740
0
                panLatDims[0] = nYDimID;
5741
0
                panLatDims[1] = nXDimID;
5742
0
                nLonDims = 2;
5743
0
                panLonDims =
5744
0
                    static_cast<int *>(CPLCalloc(nLonDims, sizeof(int)));
5745
0
                panLonDims[0] = nYDimID;
5746
0
                panLonDims[1] = nXDimID;
5747
0
            }
5748
0
            else
5749
0
            {
5750
                // Geographic
5751
0
                nLatDims = 1;
5752
0
                panLatDims =
5753
0
                    static_cast<int *>(CPLCalloc(nLatDims, sizeof(int)));
5754
0
                panLatDims[0] = nYDimID;
5755
0
                nLonDims = 1;
5756
0
                panLonDims =
5757
0
                    static_cast<int *>(CPLCalloc(nLonDims, sizeof(int)));
5758
0
                panLonDims[0] = nXDimID;
5759
0
            }
5760
5761
0
            nc_type eLonLatType = NC_NAT;
5762
0
            if (bIsProjected)
5763
0
            {
5764
0
                eLonLatType = NC_FLOAT;
5765
0
                const char *pszValue = CSLFetchNameValueDef(
5766
0
                    papszCreationOptions, "TYPE_LONLAT", "FLOAT");
5767
0
                if (EQUAL(pszValue, "DOUBLE"))
5768
0
                    eLonLatType = NC_DOUBLE;
5769
0
            }
5770
0
            else
5771
0
            {
5772
0
                eLonLatType = NC_DOUBLE;
5773
0
                const char *pszValue = CSLFetchNameValueDef(
5774
0
                    papszCreationOptions, "TYPE_LONLAT", "DOUBLE");
5775
0
                if (EQUAL(pszValue, "FLOAT"))
5776
0
                    eLonLatType = NC_FLOAT;
5777
0
            }
5778
5779
            // Def vars and attributes.
5780
0
            {
5781
0
                const char *pszVarName =
5782
0
                    bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME;
5783
0
                int status = nc_def_var(cdfid, pszVarName, eLonLatType,
5784
0
                                        nLatDims, panLatDims, &nVarLatID);
5785
0
                CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
5786
0
                         cdfid, pszVarName, eLonLatType, nLatDims, nVarLatID);
5787
0
                NCDF_ERR(status);
5788
0
                DefVarDeflate(nVarLatID, false);  // Don't set chunking.
5789
0
            }
5790
5791
0
            {
5792
0
                const char *pszVarName =
5793
0
                    bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME;
5794
0
                int status = nc_def_var(cdfid, pszVarName, eLonLatType,
5795
0
                                        nLonDims, panLonDims, &nVarLonID);
5796
0
                CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
5797
0
                         cdfid, pszVarName, eLonLatType, nLatDims, nVarLonID);
5798
0
                NCDF_ERR(status);
5799
0
                DefVarDeflate(nVarLonID, false);  // Don't set chunking.
5800
0
            }
5801
5802
0
            if (bIsRotatedPole)
5803
0
                NCDFWriteRLonRLatVarsAttributes(this->vcdf, nVarLonID,
5804
0
                                                nVarLatID);
5805
0
            else
5806
0
                NCDFWriteLonLatVarsAttributes(this->vcdf, nVarLonID, nVarLatID);
5807
5808
0
            CPLFree(panLatDims);
5809
0
            CPLFree(panLonDims);
5810
0
        }
5811
0
    }
5812
5813
0
    if (!bDefsOnly)
5814
0
    {
5815
0
        m_bAddedProjectionVarsData = true;
5816
5817
0
        int nVarXID = -1;
5818
0
        int nVarYID = -1;
5819
5820
0
        nc_inq_varid(cdfid, CF_PROJ_X_VAR_NAME, &nVarXID);
5821
0
        nc_inq_varid(cdfid, CF_PROJ_Y_VAR_NAME, &nVarYID);
5822
5823
0
        int nVarLonID = -1;
5824
0
        int nVarLatID = -1;
5825
5826
0
        const bool bIsRotatedPole =
5827
0
            pszCFProjection != nullptr &&
5828
0
            EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
5829
0
        nc_inq_varid(cdfid,
5830
0
                     bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME,
5831
0
                     &nVarLonID);
5832
0
        nc_inq_varid(cdfid,
5833
0
                     bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME,
5834
0
                     &nVarLatID);
5835
5836
        // Get projection values.
5837
5838
0
        double *padLonVal = nullptr;
5839
0
        double *padLatVal = nullptr;
5840
5841
0
        if (bIsProjected)
5842
0
        {
5843
0
            OGRSpatialReference *poLatLonSRS = nullptr;
5844
0
            OGRCoordinateTransformation *poTransform = nullptr;
5845
5846
0
            size_t startX[1];
5847
0
            size_t countX[1];
5848
0
            size_t startY[1];
5849
0
            size_t countY[1];
5850
5851
0
            CPLDebug("GDAL_netCDF", "Getting (X,Y) values");
5852
5853
0
            double *padXVal =
5854
0
                static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
5855
0
            double *padYVal =
5856
0
                static_cast<double *>(CPLMalloc(nRasterYSize * sizeof(double)));
5857
5858
            // Get Y values.
5859
0
            const double dfY0 = (!bBottomUp) ? m_adfGeoTransform[3] :
5860
                                             // Invert latitude values.
5861
0
                                    m_adfGeoTransform[3] +
5862
0
                                        (m_adfGeoTransform[5] * nRasterYSize);
5863
0
            const double dfDY = m_adfGeoTransform[5];
5864
5865
0
            for (int j = 0; j < nRasterYSize; j++)
5866
0
            {
5867
                // The data point is centered inside the pixel.
5868
0
                if (!bBottomUp)
5869
0
                    padYVal[j] = dfY0 + (j + 0.5) * dfDY;
5870
0
                else  // Invert latitude values.
5871
0
                    padYVal[j] = dfY0 - (j + 0.5) * dfDY;
5872
0
            }
5873
0
            startX[0] = 0;
5874
0
            countX[0] = nRasterXSize;
5875
5876
            // Get X values.
5877
0
            const double dfX0 = m_adfGeoTransform[0];
5878
0
            const double dfDX = m_adfGeoTransform[1];
5879
5880
0
            for (int i = 0; i < nRasterXSize; i++)
5881
0
            {
5882
                // The data point is centered inside the pixel.
5883
0
                padXVal[i] = dfX0 + (i + 0.5) * dfDX;
5884
0
            }
5885
0
            startY[0] = 0;
5886
0
            countY[0] = nRasterYSize;
5887
5888
            // Write X/Y values.
5889
5890
            // Make sure we are in data mode.
5891
0
            SetDefineMode(false);
5892
5893
0
            CPLDebug("GDAL_netCDF", "Writing X values");
5894
0
            int status =
5895
0
                nc_put_vara_double(cdfid, nVarXID, startX, countX, padXVal);
5896
0
            NCDF_ERR(status);
5897
5898
0
            CPLDebug("GDAL_netCDF", "Writing Y values");
5899
0
            status =
5900
0
                nc_put_vara_double(cdfid, nVarYID, startY, countY, padYVal);
5901
0
            NCDF_ERR(status);
5902
5903
0
            if (pfnProgress)
5904
0
                pfnProgress(0.20, nullptr, pProgressData);
5905
5906
            // Write lon/lat arrays (CF coordinates) if requested.
5907
5908
            // Get OGR transform if GEOLOCATION is not available.
5909
0
            if (bWriteLonLat && !bHasGeoloc)
5910
0
            {
5911
0
                poLatLonSRS = m_oSRS.CloneGeogCS();
5912
0
                if (poLatLonSRS != nullptr)
5913
0
                {
5914
0
                    poLatLonSRS->SetAxisMappingStrategy(
5915
0
                        OAMS_TRADITIONAL_GIS_ORDER);
5916
0
                    poTransform =
5917
0
                        OGRCreateCoordinateTransformation(&m_oSRS, poLatLonSRS);
5918
0
                }
5919
                // If no OGR transform, then don't write CF lon/lat.
5920
0
                if (poTransform == nullptr)
5921
0
                {
5922
0
                    CPLError(CE_Failure, CPLE_AppDefined,
5923
0
                             "Unable to get Coordinate Transform");
5924
0
                    bWriteLonLat = false;
5925
0
                }
5926
0
            }
5927
5928
0
            if (bWriteLonLat)
5929
0
            {
5930
0
                if (!bHasGeoloc)
5931
0
                    CPLDebug("GDAL_netCDF", "Transforming (X,Y)->(lon,lat)");
5932
0
                else
5933
0
                    CPLDebug("GDAL_netCDF",
5934
0
                             "Writing (lon,lat) from GEOLOCATION arrays");
5935
5936
0
                bool bOK = true;
5937
0
                double dfProgress = 0.2;
5938
5939
0
                size_t start[] = {0, 0};
5940
0
                size_t count[] = {1, (size_t)nRasterXSize};
5941
0
                padLatVal = static_cast<double *>(
5942
0
                    CPLMalloc(nRasterXSize * sizeof(double)));
5943
0
                padLonVal = static_cast<double *>(
5944
0
                    CPLMalloc(nRasterXSize * sizeof(double)));
5945
5946
0
                for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR;
5947
0
                     j++)
5948
0
                {
5949
0
                    start[0] = j;
5950
5951
                    // Get values from geotransform.
5952
0
                    if (!bHasGeoloc)
5953
0
                    {
5954
                        // Fill values to transform.
5955
0
                        for (int i = 0; i < nRasterXSize; i++)
5956
0
                        {
5957
0
                            padLatVal[i] = padYVal[j];
5958
0
                            padLonVal[i] = padXVal[i];
5959
0
                        }
5960
5961
                        // Do the transform.
5962
0
                        bOK = CPL_TO_BOOL(poTransform->Transform(
5963
0
                            nRasterXSize, padLonVal, padLatVal, nullptr));
5964
0
                        if (!bOK)
5965
0
                        {
5966
0
                            CPLError(CE_Failure, CPLE_AppDefined,
5967
0
                                     "Unable to Transform (X,Y) to (lon,lat).");
5968
0
                        }
5969
0
                    }
5970
                    // Get values from geoloc arrays.
5971
0
                    else
5972
0
                    {
5973
0
                        CPLErr eErr = GDALRasterIO(
5974
0
                            hBand_Y, GF_Read, 0, j, nRasterXSize, 1, padLatVal,
5975
0
                            nRasterXSize, 1, GDT_Float64, 0, 0);
5976
0
                        if (eErr == CE_None)
5977
0
                        {
5978
0
                            eErr = GDALRasterIO(
5979
0
                                hBand_X, GF_Read, 0, j, nRasterXSize, 1,
5980
0
                                padLonVal, nRasterXSize, 1, GDT_Float64, 0, 0);
5981
0
                        }
5982
5983
0
                        if (eErr == CE_None)
5984
0
                        {
5985
0
                            bOK = true;
5986
0
                        }
5987
0
                        else
5988
0
                        {
5989
0
                            bOK = false;
5990
0
                            CPLError(CE_Failure, CPLE_AppDefined,
5991
0
                                     "Unable to get scanline %d", j);
5992
0
                        }
5993
0
                    }
5994
5995
                    // Write data.
5996
0
                    if (bOK)
5997
0
                    {
5998
0
                        status = nc_put_vara_double(cdfid, nVarLatID, start,
5999
0
                                                    count, padLatVal);
6000
0
                        NCDF_ERR(status);
6001
0
                        status = nc_put_vara_double(cdfid, nVarLonID, start,
6002
0
                                                    count, padLonVal);
6003
0
                        NCDF_ERR(status);
6004
0
                    }
6005
6006
0
                    if (pfnProgress && (nRasterYSize / 10) > 0 &&
6007
0
                        (j % (nRasterYSize / 10) == 0))
6008
0
                    {
6009
0
                        dfProgress += 0.08;
6010
0
                        pfnProgress(dfProgress, nullptr, pProgressData);
6011
0
                    }
6012
0
                }
6013
0
            }
6014
6015
0
            if (poLatLonSRS != nullptr)
6016
0
                delete poLatLonSRS;
6017
0
            if (poTransform != nullptr)
6018
0
                delete poTransform;
6019
6020
0
            CPLFree(padXVal);
6021
0
            CPLFree(padYVal);
6022
0
        }  // Projected
6023
6024
        // If not projected/geographic and has geoloc
6025
0
        else if (!bIsGeographic && bHasGeoloc)
6026
0
        {
6027
            // Use
6028
            // https://cfconventions.org/Data/cf-conventions/cf-conventions-1.9/cf-conventions.html#_two_dimensional_latitude_longitude_coordinate_variables
6029
6030
0
            bool bOK = true;
6031
0
            double dfProgress = 0.2;
6032
6033
            // Make sure we are in data mode.
6034
0
            SetDefineMode(false);
6035
6036
0
            size_t startX[1];
6037
0
            size_t countX[1];
6038
0
            size_t startY[1];
6039
0
            size_t countY[1];
6040
0
            startX[0] = 0;
6041
0
            countX[0] = nRasterXSize;
6042
6043
0
            startY[0] = 0;
6044
0
            countY[0] = nRasterYSize;
6045
6046
0
            std::vector<double> adfXVal(nRasterXSize);
6047
0
            for (int i = 0; i < nRasterXSize; i++)
6048
0
                adfXVal[i] = i;
6049
6050
0
            std::vector<double> adfYVal(nRasterYSize);
6051
0
            for (int i = 0; i < nRasterYSize; i++)
6052
0
                adfYVal[i] = bBottomUp ? nRasterYSize - 1 - i : i;
6053
6054
0
            CPLDebug("GDAL_netCDF", "Writing X values");
6055
0
            int status = nc_put_vara_double(cdfid, nVarXID, startX, countX,
6056
0
                                            adfXVal.data());
6057
0
            NCDF_ERR(status);
6058
6059
0
            CPLDebug("GDAL_netCDF", "Writing Y values");
6060
0
            status = nc_put_vara_double(cdfid, nVarYID, startY, countY,
6061
0
                                        adfYVal.data());
6062
0
            NCDF_ERR(status);
6063
6064
0
            if (pfnProgress)
6065
0
                pfnProgress(0.20, nullptr, pProgressData);
6066
6067
0
            size_t start[] = {0, 0};
6068
0
            size_t count[] = {1, (size_t)nRasterXSize};
6069
0
            padLatVal =
6070
0
                static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
6071
0
            padLonVal =
6072
0
                static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
6073
6074
0
            for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR; j++)
6075
0
            {
6076
0
                start[0] = j;
6077
6078
0
                CPLErr eErr = GDALRasterIO(hBand_Y, GF_Read, 0,
6079
0
                                           bBottomUp ? nRasterYSize - 1 - j : j,
6080
0
                                           nRasterXSize, 1, padLatVal,
6081
0
                                           nRasterXSize, 1, GDT_Float64, 0, 0);
6082
0
                if (eErr == CE_None)
6083
0
                {
6084
0
                    eErr = GDALRasterIO(hBand_X, GF_Read, 0,
6085
0
                                        bBottomUp ? nRasterYSize - 1 - j : j,
6086
0
                                        nRasterXSize, 1, padLonVal,
6087
0
                                        nRasterXSize, 1, GDT_Float64, 0, 0);
6088
0
                }
6089
6090
0
                if (eErr == CE_None)
6091
0
                {
6092
0
                    bOK = true;
6093
0
                }
6094
0
                else
6095
0
                {
6096
0
                    bOK = false;
6097
0
                    CPLError(CE_Failure, CPLE_AppDefined,
6098
0
                             "Unable to get scanline %d", j);
6099
0
                }
6100
6101
                // Write data.
6102
0
                if (bOK)
6103
0
                {
6104
0
                    status = nc_put_vara_double(cdfid, nVarLatID, start, count,
6105
0
                                                padLatVal);
6106
0
                    NCDF_ERR(status);
6107
0
                    status = nc_put_vara_double(cdfid, nVarLonID, start, count,
6108
0
                                                padLonVal);
6109
0
                    NCDF_ERR(status);
6110
0
                }
6111
6112
0
                if (pfnProgress && (nRasterYSize / 10) > 0 &&
6113
0
                    (j % (nRasterYSize / 10) == 0))
6114
0
                {
6115
0
                    dfProgress += 0.08;
6116
0
                    pfnProgress(dfProgress, nullptr, pProgressData);
6117
0
                }
6118
0
            }
6119
0
        }
6120
6121
        // If not projected, assume geographic to catch grids without Datum.
6122
0
        else if (bWriteLonLat)
6123
0
        {
6124
            // Get latitude values.
6125
0
            const double dfY0 = (!bBottomUp) ? m_adfGeoTransform[3] :
6126
                                             // Invert latitude values.
6127
0
                                    m_adfGeoTransform[3] +
6128
0
                                        (m_adfGeoTransform[5] * nRasterYSize);
6129
0
            const double dfDY = m_adfGeoTransform[5];
6130
6131
            // Override lat values with the ones in GEOLOCATION/Y_VALUES.
6132
0
            if (netCDFDataset::GetMetadataItem("Y_VALUES", "GEOLOCATION") !=
6133
0
                nullptr)
6134
0
            {
6135
0
                int nTemp = 0;
6136
0
                padLatVal = Get1DGeolocation("Y_VALUES", nTemp);
6137
                // Make sure we got the correct amount, if not fallback to GT */
6138
                // could add test fabs(fabs(padLatVal[0]) - fabs(dfY0)) <= 0.1))
6139
0
                if (nTemp == nRasterYSize)
6140
0
                {
6141
0
                    CPLDebug(
6142
0
                        "GDAL_netCDF",
6143
0
                        "Using Y_VALUES geolocation metadata for lat values");
6144
0
                }
6145
0
                else
6146
0
                {
6147
0
                    CPLDebug("GDAL_netCDF",
6148
0
                             "Got %d elements from Y_VALUES geolocation "
6149
0
                             "metadata, need %d",
6150
0
                             nTemp, nRasterYSize);
6151
0
                    if (padLatVal)
6152
0
                    {
6153
0
                        CPLFree(padLatVal);
6154
0
                        padLatVal = nullptr;
6155
0
                    }
6156
0
                }
6157
0
            }
6158
6159
0
            if (padLatVal == nullptr)
6160
0
            {
6161
0
                padLatVal = static_cast<double *>(
6162
0
                    CPLMalloc(nRasterYSize * sizeof(double)));
6163
0
                for (int i = 0; i < nRasterYSize; i++)
6164
0
                {
6165
                    // The data point is centered inside the pixel.
6166
0
                    if (!bBottomUp)
6167
0
                        padLatVal[i] = dfY0 + (i + 0.5) * dfDY;
6168
0
                    else  // Invert latitude values.
6169
0
                        padLatVal[i] = dfY0 - (i + 0.5) * dfDY;
6170
0
                }
6171
0
            }
6172
6173
0
            size_t startLat[1] = {0};
6174
0
            size_t countLat[1] = {static_cast<size_t>(nRasterYSize)};
6175
6176
            // Get longitude values.
6177
0
            const double dfX0 = m_adfGeoTransform[0];
6178
0
            const double dfDX = m_adfGeoTransform[1];
6179
6180
0
            padLonVal =
6181
0
                static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
6182
0
            for (int i = 0; i < nRasterXSize; i++)
6183
0
            {
6184
                // The data point is centered inside the pixel.
6185
0
                padLonVal[i] = dfX0 + (i + 0.5) * dfDX;
6186
0
            }
6187
6188
0
            size_t startLon[1] = {0};
6189
0
            size_t countLon[1] = {static_cast<size_t>(nRasterXSize)};
6190
6191
            // Write latitude and longitude values.
6192
6193
            // Make sure we are in data mode.
6194
0
            SetDefineMode(false);
6195
6196
            // Write values.
6197
0
            CPLDebug("GDAL_netCDF", "Writing lat values");
6198
6199
0
            int status = nc_put_vara_double(cdfid, nVarLatID, startLat,
6200
0
                                            countLat, padLatVal);
6201
0
            NCDF_ERR(status);
6202
6203
0
            CPLDebug("GDAL_netCDF", "Writing lon values");
6204
0
            status = nc_put_vara_double(cdfid, nVarLonID, startLon, countLon,
6205
0
                                        padLonVal);
6206
0
            NCDF_ERR(status);
6207
6208
0
        }  // Not projected.
6209
6210
0
        CPLFree(padLatVal);
6211
0
        CPLFree(padLonVal);
6212
6213
0
        if (pfnProgress)
6214
0
            pfnProgress(1.00, nullptr, pProgressData);
6215
0
    }
6216
6217
0
    if (hDS_X != nullptr)
6218
0
    {
6219
0
        GDALClose(hDS_X);
6220
0
    }
6221
0
    if (hDS_Y != nullptr)
6222
0
    {
6223
0
        GDALClose(hDS_Y);
6224
0
    }
6225
6226
0
    return CE_None;
6227
0
}
6228
6229
// Write Projection variable to band variable.
6230
// Moved from AddProjectionVars() for cases when bands are added after
6231
// projection.
6232
bool netCDFDataset::AddGridMappingRef()
6233
0
{
6234
0
    bool bRet = true;
6235
0
    bool bOldDefineMode = bDefineMode;
6236
6237
0
    if ((GetAccess() == GA_Update) && (nBands >= 1) && (GetRasterBand(1)) &&
6238
0
        ((pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, "")) ||
6239
0
         (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))))
6240
0
    {
6241
0
        bAddedGridMappingRef = true;
6242
6243
        // Make sure we are in define mode.
6244
0
        SetDefineMode(true);
6245
6246
0
        for (int i = 1; i <= nBands; i++)
6247
0
        {
6248
0
            const int nVarId =
6249
0
                static_cast<netCDFRasterBand *>(GetRasterBand(i))->nZId;
6250
6251
0
            if (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))
6252
0
            {
6253
0
                int status =
6254
0
                    nc_put_att_text(cdfid, nVarId, CF_GRD_MAPPING,
6255
0
                                    strlen(pszCFProjection), pszCFProjection);
6256
0
                NCDF_ERR(status);
6257
0
                if (status != NC_NOERR)
6258
0
                    bRet = false;
6259
0
            }
6260
0
            if (pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, ""))
6261
0
            {
6262
0
                int status =
6263
0
                    nc_put_att_text(cdfid, nVarId, CF_COORDINATES,
6264
0
                                    strlen(pszCFCoordinates), pszCFCoordinates);
6265
0
                NCDF_ERR(status);
6266
0
                if (status != NC_NOERR)
6267
0
                    bRet = false;
6268
0
            }
6269
0
        }
6270
6271
        // Go back to previous define mode.
6272
0
        SetDefineMode(bOldDefineMode);
6273
0
    }
6274
0
    return bRet;
6275
0
}
6276
6277
/************************************************************************/
6278
/*                          GetGeoTransform()                           */
6279
/************************************************************************/
6280
6281
CPLErr netCDFDataset::GetGeoTransform(double *padfTransform)
6282
6283
0
{
6284
0
    memcpy(padfTransform, m_adfGeoTransform, sizeof(double) * 6);
6285
0
    if (m_bHasGeoTransform)
6286
0
        return CE_None;
6287
6288
0
    return GDALPamDataset::GetGeoTransform(padfTransform);
6289
0
}
6290
6291
/************************************************************************/
6292
/*                                rint()                                */
6293
/************************************************************************/
6294
6295
double netCDFDataset::rint(double dfX)
6296
0
{
6297
0
    return std::round(dfX);
6298
0
}
6299
6300
/************************************************************************/
6301
/*                          NCDFReadIsoMetadata()                       */
6302
/************************************************************************/
6303
6304
static void NCDFReadMetadataAsJson(int cdfid, CPLJSONObject &obj)
6305
0
{
6306
0
    int nbAttr = 0;
6307
0
    NCDF_ERR(nc_inq_varnatts(cdfid, NC_GLOBAL, &nbAttr));
6308
6309
0
    std::map<std::string, CPLJSONArray> oMapNameToArray;
6310
0
    for (int l = 0; l < nbAttr; l++)
6311
0
    {
6312
0
        char szAttrName[NC_MAX_NAME + 1];
6313
0
        szAttrName[0] = 0;
6314
0
        NCDF_ERR(nc_inq_attname(cdfid, NC_GLOBAL, l, szAttrName));
6315
6316
0
        char *pszMetaValue = nullptr;
6317
0
        if (NCDFGetAttr(cdfid, NC_GLOBAL, szAttrName, &pszMetaValue) == CE_None)
6318
0
        {
6319
0
            nc_type nAttrType = NC_NAT;
6320
0
            size_t nAttrLen = 0;
6321
6322
0
            NCDF_ERR(nc_inq_att(cdfid, NC_GLOBAL, szAttrName, &nAttrType,
6323
0
                                &nAttrLen));
6324
6325
0
            std::string osAttrName(szAttrName);
6326
0
            const auto sharpPos = osAttrName.find('#');
6327
0
            if (sharpPos == std::string::npos)
6328
0
            {
6329
0
                if (nAttrType == NC_DOUBLE || nAttrType == NC_FLOAT)
6330
0
                    obj.Add(osAttrName, CPLAtof(pszMetaValue));
6331
0
                else
6332
0
                    obj.Add(osAttrName, pszMetaValue);
6333
0
            }
6334
0
            else
6335
0
            {
6336
0
                osAttrName.resize(sharpPos);
6337
0
                auto iter = oMapNameToArray.find(osAttrName);
6338
0
                if (iter == oMapNameToArray.end())
6339
0
                {
6340
0
                    CPLJSONArray array;
6341
0
                    obj.Add(osAttrName, array);
6342
0
                    oMapNameToArray[osAttrName] = array;
6343
0
                    array.Add(pszMetaValue);
6344
0
                }
6345
0
                else
6346
0
                {
6347
0
                    iter->second.Add(pszMetaValue);
6348
0
                }
6349
0
            }
6350
0
            CPLFree(pszMetaValue);
6351
0
            pszMetaValue = nullptr;
6352
0
        }
6353
0
    }
6354
6355
0
    int nSubGroups = 0;
6356
0
    int *panSubGroupIds = nullptr;
6357
0
    NCDFGetSubGroups(cdfid, &nSubGroups, &panSubGroupIds);
6358
0
    oMapNameToArray.clear();
6359
0
    for (int i = 0; i < nSubGroups; i++)
6360
0
    {
6361
0
        CPLJSONObject subObj;
6362
0
        NCDFReadMetadataAsJson(panSubGroupIds[i], subObj);
6363
6364
0
        std::string osGroupName;
6365
0
        osGroupName.resize(NC_MAX_NAME);
6366
0
        NCDF_ERR(nc_inq_grpname(panSubGroupIds[i], &osGroupName[0]));
6367
0
        osGroupName.resize(strlen(osGroupName.data()));
6368
0
        const auto sharpPos = osGroupName.find('#');
6369
0
        if (sharpPos == std::string::npos)
6370
0
        {
6371
0
            obj.Add(osGroupName, subObj);
6372
0
        }
6373
0
        else
6374
0
        {
6375
0
            osGroupName.resize(sharpPos);
6376
0
            auto iter = oMapNameToArray.find(osGroupName);
6377
0
            if (iter == oMapNameToArray.end())
6378
0
            {
6379
0
                CPLJSONArray array;
6380
0
                obj.Add(osGroupName, array);
6381
0
                oMapNameToArray[osGroupName] = array;
6382
0
                array.Add(subObj);
6383
0
            }
6384
0
            else
6385
0
            {
6386
0
                iter->second.Add(subObj);
6387
0
            }
6388
0
        }
6389
0
    }
6390
0
    CPLFree(panSubGroupIds);
6391
0
}
6392
6393
std::string NCDFReadMetadataAsJson(int cdfid)
6394
0
{
6395
0
    CPLJSONDocument oDoc;
6396
0
    CPLJSONObject oRoot = oDoc.GetRoot();
6397
0
    NCDFReadMetadataAsJson(cdfid, oRoot);
6398
0
    return oDoc.SaveAsString();
6399
0
}
6400
6401
/************************************************************************/
6402
/*                        ReadAttributes()                              */
6403
/************************************************************************/
6404
CPLErr netCDFDataset::ReadAttributes(int cdfidIn, int var)
6405
6406
422
{
6407
422
    char *pszVarFullName = nullptr;
6408
422
    ERR_RET(NCDFGetVarFullName(cdfidIn, var, &pszVarFullName));
6409
6410
    // For metadata in Sentinel 5
6411
422
    if (STARTS_WITH(pszVarFullName, "/METADATA/"))
6412
0
    {
6413
0
        for (const char *key :
6414
0
             {"ISO_METADATA", "ESA_METADATA", "EOP_METADATA", "QA_STATISTICS",
6415
0
              "GRANULE_DESCRIPTION", "ALGORITHM_SETTINGS"})
6416
0
        {
6417
0
            if (var == NC_GLOBAL &&
6418
0
                strcmp(pszVarFullName,
6419
0
                       CPLSPrintf("/METADATA/%s/NC_GLOBAL", key)) == 0)
6420
0
            {
6421
0
                CPLFree(pszVarFullName);
6422
0
                CPLStringList aosList;
6423
0
                aosList.AddString(CPLString(NCDFReadMetadataAsJson(cdfidIn))
6424
0
                                      .replaceAll("\\/", '/'));
6425
0
                m_oMapDomainToJSon[key] = std::move(aosList);
6426
0
                return CE_None;
6427
0
            }
6428
0
        }
6429
0
    }
6430
422
    if (STARTS_WITH(pszVarFullName, "/PRODUCT/SUPPORT_DATA/"))
6431
0
    {
6432
0
        CPLFree(pszVarFullName);
6433
0
        CPLStringList aosList;
6434
0
        aosList.AddString(
6435
0
            CPLString(NCDFReadMetadataAsJson(cdfidIn)).replaceAll("\\/", '/'));
6436
0
        m_oMapDomainToJSon["SUPPORT_DATA"] = std::move(aosList);
6437
0
        return CE_None;
6438
0
    }
6439
6440
422
    size_t nMetaNameSize =
6441
422
        sizeof(char) * (strlen(pszVarFullName) + 1 + NC_MAX_NAME + 1);
6442
422
    char *pszMetaName = static_cast<char *>(CPLMalloc(nMetaNameSize));
6443
6444
422
    int nbAttr = 0;
6445
422
    NCDF_ERR(nc_inq_varnatts(cdfidIn, var, &nbAttr));
6446
6447
2.61k
    for (int l = 0; l < nbAttr; l++)
6448
2.19k
    {
6449
2.19k
        char szAttrName[NC_MAX_NAME + 1];
6450
2.19k
        szAttrName[0] = 0;
6451
2.19k
        NCDF_ERR(nc_inq_attname(cdfidIn, var, l, szAttrName));
6452
2.19k
        snprintf(pszMetaName, nMetaNameSize, "%s#%s", pszVarFullName,
6453
2.19k
                 szAttrName);
6454
6455
2.19k
        char *pszMetaTemp = nullptr;
6456
2.19k
        if (NCDFGetAttr(cdfidIn, var, szAttrName, &pszMetaTemp) == CE_None)
6457
2.19k
        {
6458
2.19k
            papszMetadata =
6459
2.19k
                CSLSetNameValue(papszMetadata, pszMetaName, pszMetaTemp);
6460
2.19k
            CPLFree(pszMetaTemp);
6461
2.19k
            pszMetaTemp = nullptr;
6462
2.19k
        }
6463
0
        else
6464
0
        {
6465
0
            CPLDebug("GDAL_netCDF", "invalid metadata %s", pszMetaName);
6466
0
        }
6467
2.19k
    }
6468
6469
422
    CPLFree(pszVarFullName);
6470
422
    CPLFree(pszMetaName);
6471
6472
422
    if (var == NC_GLOBAL)
6473
399
    {
6474
        // Recurse on sub-groups.
6475
399
        int nSubGroups = 0;
6476
399
        int *panSubGroupIds = nullptr;
6477
399
        NCDFGetSubGroups(cdfidIn, &nSubGroups, &panSubGroupIds);
6478
399
        for (int i = 0; i < nSubGroups; i++)
6479
0
        {
6480
0
            ReadAttributes(panSubGroupIds[i], var);
6481
0
        }
6482
399
        CPLFree(panSubGroupIds);
6483
399
    }
6484
6485
422
    return CE_None;
6486
422
}
6487
6488
/************************************************************************/
6489
/*                netCDFDataset::CreateSubDatasetList()                 */
6490
/************************************************************************/
6491
void netCDFDataset::CreateSubDatasetList(int nGroupId)
6492
0
{
6493
0
    char szVarStdName[NC_MAX_NAME + 1];
6494
0
    int *ponDimIds = nullptr;
6495
0
    nc_type nAttype;
6496
0
    size_t nAttlen;
6497
6498
0
    netCDFDataset *poDS = this;
6499
6500
0
    int nVarCount;
6501
0
    nc_inq_nvars(nGroupId, &nVarCount);
6502
6503
0
    const bool bListAllArrays = CPLTestBool(
6504
0
        CSLFetchNameValueDef(papszOpenOptions, "LIST_ALL_ARRAYS", "NO"));
6505
6506
0
    for (int nVar = 0; nVar < nVarCount; nVar++)
6507
0
    {
6508
6509
0
        int nDims;
6510
0
        nc_inq_varndims(nGroupId, nVar, &nDims);
6511
6512
0
        if ((bListAllArrays && nDims > 0) || nDims >= 2)
6513
0
        {
6514
0
            ponDimIds = static_cast<int *>(CPLCalloc(nDims, sizeof(int)));
6515
0
            nc_inq_vardimid(nGroupId, nVar, ponDimIds);
6516
6517
            // Create Sub dataset list.
6518
0
            CPLString osDim;
6519
0
            for (int i = 0; i < nDims; i++)
6520
0
            {
6521
0
                size_t nDimLen;
6522
0
                nc_inq_dimlen(nGroupId, ponDimIds[i], &nDimLen);
6523
0
                if (!osDim.empty())
6524
0
                    osDim += 'x';
6525
0
                osDim += CPLSPrintf("%d", (int)nDimLen);
6526
0
            }
6527
0
            CPLFree(ponDimIds);
6528
6529
0
            nc_type nVarType;
6530
0
            nc_inq_vartype(nGroupId, nVar, &nVarType);
6531
0
            const char *pszType = "";
6532
0
            switch (nVarType)
6533
0
            {
6534
0
                case NC_BYTE:
6535
0
                    pszType = "8-bit integer";
6536
0
                    break;
6537
0
                case NC_CHAR:
6538
0
                    pszType = "8-bit character";
6539
0
                    break;
6540
0
                case NC_SHORT:
6541
0
                    pszType = "16-bit integer";
6542
0
                    break;
6543
0
                case NC_INT:
6544
0
                    pszType = "32-bit integer";
6545
0
                    break;
6546
0
                case NC_FLOAT:
6547
0
                    pszType = "32-bit floating-point";
6548
0
                    break;
6549
0
                case NC_DOUBLE:
6550
0
                    pszType = "64-bit floating-point";
6551
0
                    break;
6552
0
                case NC_UBYTE:
6553
0
                    pszType = "8-bit unsigned integer";
6554
0
                    break;
6555
0
                case NC_USHORT:
6556
0
                    pszType = "16-bit unsigned integer";
6557
0
                    break;
6558
0
                case NC_UINT:
6559
0
                    pszType = "32-bit unsigned integer";
6560
0
                    break;
6561
0
                case NC_INT64:
6562
0
                    pszType = "64-bit integer";
6563
0
                    break;
6564
0
                case NC_UINT64:
6565
0
                    pszType = "64-bit unsigned integer";
6566
0
                    break;
6567
0
                default:
6568
0
                    break;
6569
0
            }
6570
6571
0
            char *pszName = nullptr;
6572
0
            if (NCDFGetVarFullName(nGroupId, nVar, &pszName) != CE_None)
6573
0
                continue;
6574
6575
0
            nSubDatasets++;
6576
6577
0
            nAttlen = 0;
6578
0
            nc_inq_att(nGroupId, nVar, CF_STD_NAME, &nAttype, &nAttlen);
6579
0
            if (nAttlen < sizeof(szVarStdName) &&
6580
0
                nc_get_att_text(nGroupId, nVar, CF_STD_NAME, szVarStdName) ==
6581
0
                    NC_NOERR)
6582
0
            {
6583
0
                szVarStdName[nAttlen] = '\0';
6584
0
            }
6585
0
            else
6586
0
            {
6587
0
                snprintf(szVarStdName, sizeof(szVarStdName), "%s", pszName);
6588
0
            }
6589
6590
0
            char szTemp[NC_MAX_NAME + 1];
6591
0
            snprintf(szTemp, sizeof(szTemp), "SUBDATASET_%d_NAME",
6592
0
                     nSubDatasets);
6593
6594
0
            if (strchr(pszName, ' ') || strchr(pszName, ':'))
6595
0
            {
6596
0
                poDS->papszSubDatasets = CSLSetNameValue(
6597
0
                    poDS->papszSubDatasets, szTemp,
6598
0
                    CPLSPrintf("NETCDF:\"%s\":\"%s\"", poDS->osFilename.c_str(),
6599
0
                               pszName));
6600
0
            }
6601
0
            else
6602
0
            {
6603
0
                poDS->papszSubDatasets = CSLSetNameValue(
6604
0
                    poDS->papszSubDatasets, szTemp,
6605
0
                    CPLSPrintf("NETCDF:\"%s\":%s", poDS->osFilename.c_str(),
6606
0
                               pszName));
6607
0
            }
6608
6609
0
            CPLFree(pszName);
6610
6611
0
            snprintf(szTemp, sizeof(szTemp), "SUBDATASET_%d_DESC",
6612
0
                     nSubDatasets);
6613
6614
0
            poDS->papszSubDatasets =
6615
0
                CSLSetNameValue(poDS->papszSubDatasets, szTemp,
6616
0
                                CPLSPrintf("[%s] %s (%s)", osDim.c_str(),
6617
0
                                           szVarStdName, pszType));
6618
0
        }
6619
0
    }
6620
6621
    // Recurse on sub groups.
6622
0
    int nSubGroups = 0;
6623
0
    int *panSubGroupIds = nullptr;
6624
0
    NCDFGetSubGroups(nGroupId, &nSubGroups, &panSubGroupIds);
6625
0
    for (int i = 0; i < nSubGroups; i++)
6626
0
    {
6627
0
        CreateSubDatasetList(panSubGroupIds[i]);
6628
0
    }
6629
0
    CPLFree(panSubGroupIds);
6630
0
}
6631
6632
/************************************************************************/
6633
/*                            TestCapability()                          */
6634
/************************************************************************/
6635
6636
int netCDFDataset::TestCapability(const char *pszCap)
6637
0
{
6638
0
    if (EQUAL(pszCap, ODsCCreateLayer))
6639
0
    {
6640
0
        return eAccess == GA_Update && nBands == 0 &&
6641
0
               (eMultipleLayerBehavior != SINGLE_LAYER ||
6642
0
                this->GetLayerCount() == 0 || bSGSupport);
6643
0
    }
6644
0
    else if (EQUAL(pszCap, ODsCZGeometries))
6645
0
        return true;
6646
6647
0
    return false;
6648
0
}
6649
6650
/************************************************************************/
6651
/*                            GetLayer()                                */
6652
/************************************************************************/
6653
6654
OGRLayer *netCDFDataset::GetLayer(int nIdx)
6655
46
{
6656
46
    if (nIdx < 0 || nIdx >= this->GetLayerCount())
6657
0
        return nullptr;
6658
46
    return papoLayers[nIdx].get();
6659
46
}
6660
6661
/************************************************************************/
6662
/*                            ICreateLayer()                            */
6663
/************************************************************************/
6664
6665
OGRLayer *netCDFDataset::ICreateLayer(const char *pszName,
6666
                                      const OGRGeomFieldDefn *poGeomFieldDefn,
6667
                                      CSLConstList papszOptions)
6668
0
{
6669
0
    int nLayerCDFId = cdfid;
6670
0
    if (!TestCapability(ODsCCreateLayer))
6671
0
        return nullptr;
6672
6673
0
    const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
6674
0
    const auto poSpatialRef =
6675
0
        poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
6676
6677
0
    CPLString osNetCDFLayerName(pszName);
6678
0
    const netCDFWriterConfigLayer *poLayerConfig = nullptr;
6679
0
    if (oWriterConfig.m_bIsValid)
6680
0
    {
6681
0
        std::map<CPLString, netCDFWriterConfigLayer>::const_iterator
6682
0
            oLayerIter = oWriterConfig.m_oLayers.find(pszName);
6683
0
        if (oLayerIter != oWriterConfig.m_oLayers.end())
6684
0
        {
6685
0
            poLayerConfig = &(oLayerIter->second);
6686
0
            osNetCDFLayerName = poLayerConfig->m_osNetCDFName;
6687
0
        }
6688
0
    }
6689
6690
0
    netCDFDataset *poLayerDataset = nullptr;
6691
0
    if (eMultipleLayerBehavior == SEPARATE_FILES)
6692
0
    {
6693
0
        char **papszDatasetOptions = nullptr;
6694
0
        papszDatasetOptions = CSLSetNameValue(
6695
0
            papszDatasetOptions, "CONFIG_FILE",
6696
0
            CSLFetchNameValue(papszCreationOptions, "CONFIG_FILE"));
6697
0
        papszDatasetOptions =
6698
0
            CSLSetNameValue(papszDatasetOptions, "FORMAT",
6699
0
                            CSLFetchNameValue(papszCreationOptions, "FORMAT"));
6700
0
        papszDatasetOptions = CSLSetNameValue(
6701
0
            papszDatasetOptions, "WRITE_GDAL_TAGS",
6702
0
            CSLFetchNameValue(papszCreationOptions, "WRITE_GDAL_TAGS"));
6703
0
        const CPLString osLayerFilename(
6704
0
            CPLFormFilenameSafe(osFilename, osNetCDFLayerName, "nc"));
6705
0
        CPLAcquireMutex(hNCMutex, 1000.0);
6706
0
        poLayerDataset =
6707
0
            CreateLL(osLayerFilename, 0, 0, 0, papszDatasetOptions);
6708
0
        CPLReleaseMutex(hNCMutex);
6709
0
        CSLDestroy(papszDatasetOptions);
6710
0
        if (poLayerDataset == nullptr)
6711
0
            return nullptr;
6712
6713
0
        nLayerCDFId = poLayerDataset->cdfid;
6714
0
        NCDFAddGDALHistory(nLayerCDFId, osLayerFilename, bWriteGDALVersion,
6715
0
                           bWriteGDALHistory, "", "Create",
6716
0
                           NCDF_CONVENTIONS_CF_V1_6);
6717
0
    }
6718
0
    else if (eMultipleLayerBehavior == SEPARATE_GROUPS)
6719
0
    {
6720
0
        SetDefineMode(true);
6721
6722
0
        nLayerCDFId = -1;
6723
0
        int status = nc_def_grp(cdfid, osNetCDFLayerName, &nLayerCDFId);
6724
0
        NCDF_ERR(status);
6725
0
        if (status != NC_NOERR)
6726
0
            return nullptr;
6727
6728
0
        NCDFAddGDALHistory(nLayerCDFId, osFilename, bWriteGDALVersion,
6729
0
                           bWriteGDALHistory, "", "Create",
6730
0
                           NCDF_CONVENTIONS_CF_V1_6);
6731
0
    }
6732
6733
    // Make a clone to workaround a bug in released MapServer versions
6734
    // that destroys the passed SRS instead of releasing it .
6735
0
    OGRSpatialReference *poSRS = nullptr;
6736
0
    if (poSpatialRef)
6737
0
    {
6738
0
        poSRS = poSpatialRef->Clone();
6739
0
        poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
6740
0
    }
6741
0
    std::shared_ptr<netCDFLayer> poLayer(
6742
0
        new netCDFLayer(poLayerDataset ? poLayerDataset : this, nLayerCDFId,
6743
0
                        osNetCDFLayerName, eGType, poSRS));
6744
0
    if (poSRS != nullptr)
6745
0
        poSRS->Release();
6746
6747
    // Fetch layer creation options coming from config file
6748
0
    char **papszNewOptions = CSLDuplicate(papszOptions);
6749
0
    if (oWriterConfig.m_bIsValid)
6750
0
    {
6751
0
        std::map<CPLString, CPLString>::const_iterator oIter;
6752
0
        for (oIter = oWriterConfig.m_oLayerCreationOptions.begin();
6753
0
             oIter != oWriterConfig.m_oLayerCreationOptions.end(); ++oIter)
6754
0
        {
6755
0
            papszNewOptions =
6756
0
                CSLSetNameValue(papszNewOptions, oIter->first, oIter->second);
6757
0
        }
6758
0
        if (poLayerConfig != nullptr)
6759
0
        {
6760
0
            for (oIter = poLayerConfig->m_oLayerCreationOptions.begin();
6761
0
                 oIter != poLayerConfig->m_oLayerCreationOptions.end(); ++oIter)
6762
0
            {
6763
0
                papszNewOptions = CSLSetNameValue(papszNewOptions, oIter->first,
6764
0
                                                  oIter->second);
6765
0
            }
6766
0
        }
6767
0
    }
6768
6769
0
    const bool bRet = poLayer->Create(papszNewOptions, poLayerConfig);
6770
0
    CSLDestroy(papszNewOptions);
6771
6772
0
    if (!bRet)
6773
0
    {
6774
0
        return nullptr;
6775
0
    }
6776
6777
0
    if (poLayerDataset != nullptr)
6778
0
        apoVectorDatasets.push_back(poLayerDataset);
6779
6780
0
    papoLayers.push_back(poLayer);
6781
0
    return poLayer.get();
6782
0
}
6783
6784
/************************************************************************/
6785
/*                           CloneAttributes()                          */
6786
/************************************************************************/
6787
6788
bool netCDFDataset::CloneAttributes(int old_cdfid, int new_cdfid, int nSrcVarId,
6789
                                    int nDstVarId)
6790
0
{
6791
0
    int nAttCount = -1;
6792
0
    int status = nc_inq_varnatts(old_cdfid, nSrcVarId, &nAttCount);
6793
0
    NCDF_ERR(status);
6794
6795
0
    for (int i = 0; i < nAttCount; i++)
6796
0
    {
6797
0
        char szName[NC_MAX_NAME + 1];
6798
0
        szName[0] = 0;
6799
0
        status = nc_inq_attname(old_cdfid, nSrcVarId, i, szName);
6800
0
        NCDF_ERR(status);
6801
6802
0
        status =
6803
0
            nc_copy_att(old_cdfid, nSrcVarId, szName, new_cdfid, nDstVarId);
6804
0
        NCDF_ERR(status);
6805
0
        if (status != NC_NOERR)
6806
0
            return false;
6807
0
    }
6808
6809
0
    return true;
6810
0
}
6811
6812
/************************************************************************/
6813
/*                          CloneVariableContent()                      */
6814
/************************************************************************/
6815
6816
bool netCDFDataset::CloneVariableContent(int old_cdfid, int new_cdfid,
6817
                                         int nSrcVarId, int nDstVarId)
6818
0
{
6819
0
    int nVarDimCount = -1;
6820
0
    int status = nc_inq_varndims(old_cdfid, nSrcVarId, &nVarDimCount);
6821
0
    NCDF_ERR(status);
6822
0
    int anDimIds[] = {-1, 1};
6823
0
    status = nc_inq_vardimid(old_cdfid, nSrcVarId, anDimIds);
6824
0
    NCDF_ERR(status);
6825
0
    nc_type nc_datatype = NC_NAT;
6826
0
    status = nc_inq_vartype(old_cdfid, nSrcVarId, &nc_datatype);
6827
0
    NCDF_ERR(status);
6828
0
    size_t nTypeSize = 0;
6829
0
    switch (nc_datatype)
6830
0
    {
6831
0
        case NC_BYTE:
6832
0
        case NC_CHAR:
6833
0
            nTypeSize = 1;
6834
0
            break;
6835
0
        case NC_SHORT:
6836
0
            nTypeSize = 2;
6837
0
            break;
6838
0
        case NC_INT:
6839
0
            nTypeSize = 4;
6840
0
            break;
6841
0
        case NC_FLOAT:
6842
0
            nTypeSize = 4;
6843
0
            break;
6844
0
        case NC_DOUBLE:
6845
0
            nTypeSize = 8;
6846
0
            break;
6847
0
        case NC_UBYTE:
6848
0
            nTypeSize = 1;
6849
0
            break;
6850
0
        case NC_USHORT:
6851
0
            nTypeSize = 2;
6852
0
            break;
6853
0
        case NC_UINT:
6854
0
            nTypeSize = 4;
6855
0
            break;
6856
0
        case NC_INT64:
6857
0
        case NC_UINT64:
6858
0
            nTypeSize = 8;
6859
0
            break;
6860
0
        case NC_STRING:
6861
0
            nTypeSize = sizeof(char *);
6862
0
            break;
6863
0
        default:
6864
0
        {
6865
0
            CPLError(CE_Failure, CPLE_NotSupported, "Unsupported data type: %d",
6866
0
                     nc_datatype);
6867
0
            return false;
6868
0
        }
6869
0
    }
6870
6871
0
    size_t nElems = 1;
6872
0
    size_t anStart[NC_MAX_DIMS];
6873
0
    size_t anCount[NC_MAX_DIMS];
6874
0
    size_t nRecords = 1;
6875
0
    for (int i = 0; i < nVarDimCount; i++)
6876
0
    {
6877
0
        anStart[i] = 0;
6878
0
        if (i == 0)
6879
0
        {
6880
0
            anCount[i] = 1;
6881
0
            status = nc_inq_dimlen(old_cdfid, anDimIds[i], &nRecords);
6882
0
            NCDF_ERR(status);
6883
0
        }
6884
0
        else
6885
0
        {
6886
0
            anCount[i] = 0;
6887
0
            status = nc_inq_dimlen(old_cdfid, anDimIds[i], &anCount[i]);
6888
0
            NCDF_ERR(status);
6889
0
            nElems *= anCount[i];
6890
0
        }
6891
0
    }
6892
6893
    /* Workaround in some cases a netCDF bug:
6894
     * https://github.com/Unidata/netcdf-c/pull/1442 */
6895
0
    if (nRecords > 0 && nRecords < 10 * 1000 * 1000 / (nElems * nTypeSize))
6896
0
    {
6897
0
        nElems *= nRecords;
6898
0
        anCount[0] = nRecords;
6899
0
        nRecords = 1;
6900
0
    }
6901
6902
0
    void *pBuffer = VSI_MALLOC2_VERBOSE(nElems, nTypeSize);
6903
0
    if (pBuffer == nullptr)
6904
0
        return false;
6905
6906
0
    for (size_t iRecord = 0; iRecord < nRecords; iRecord++)
6907
0
    {
6908
0
        anStart[0] = iRecord;
6909
6910
0
        switch (nc_datatype)
6911
0
        {
6912
0
            case NC_BYTE:
6913
0
                status =
6914
0
                    nc_get_vara_schar(old_cdfid, nSrcVarId, anStart, anCount,
6915
0
                                      static_cast<signed char *>(pBuffer));
6916
0
                if (!status)
6917
0
                    status = nc_put_vara_schar(
6918
0
                        new_cdfid, nDstVarId, anStart, anCount,
6919
0
                        static_cast<signed char *>(pBuffer));
6920
0
                break;
6921
0
            case NC_CHAR:
6922
0
                status =
6923
0
                    nc_get_vara_text(old_cdfid, nSrcVarId, anStart, anCount,
6924
0
                                     static_cast<char *>(pBuffer));
6925
0
                if (!status)
6926
0
                    status =
6927
0
                        nc_put_vara_text(new_cdfid, nDstVarId, anStart, anCount,
6928
0
                                         static_cast<char *>(pBuffer));
6929
0
                break;
6930
0
            case NC_SHORT:
6931
0
                status =
6932
0
                    nc_get_vara_short(old_cdfid, nSrcVarId, anStart, anCount,
6933
0
                                      static_cast<short *>(pBuffer));
6934
0
                if (!status)
6935
0
                    status = nc_put_vara_short(new_cdfid, nDstVarId, anStart,
6936
0
                                               anCount,
6937
0
                                               static_cast<short *>(pBuffer));
6938
0
                break;
6939
0
            case NC_INT:
6940
0
                status = nc_get_vara_int(old_cdfid, nSrcVarId, anStart, anCount,
6941
0
                                         static_cast<int *>(pBuffer));
6942
0
                if (!status)
6943
0
                    status =
6944
0
                        nc_put_vara_int(new_cdfid, nDstVarId, anStart, anCount,
6945
0
                                        static_cast<int *>(pBuffer));
6946
0
                break;
6947
0
            case NC_FLOAT:
6948
0
                status =
6949
0
                    nc_get_vara_float(old_cdfid, nSrcVarId, anStart, anCount,
6950
0
                                      static_cast<float *>(pBuffer));
6951
0
                if (!status)
6952
0
                    status = nc_put_vara_float(new_cdfid, nDstVarId, anStart,
6953
0
                                               anCount,
6954
0
                                               static_cast<float *>(pBuffer));
6955
0
                break;
6956
0
            case NC_DOUBLE:
6957
0
                status =
6958
0
                    nc_get_vara_double(old_cdfid, nSrcVarId, anStart, anCount,
6959
0
                                       static_cast<double *>(pBuffer));
6960
0
                if (!status)
6961
0
                    status = nc_put_vara_double(new_cdfid, nDstVarId, anStart,
6962
0
                                                anCount,
6963
0
                                                static_cast<double *>(pBuffer));
6964
0
                break;
6965
0
            case NC_STRING:
6966
0
                status =
6967
0
                    nc_get_vara_string(old_cdfid, nSrcVarId, anStart, anCount,
6968
0
                                       static_cast<char **>(pBuffer));
6969
0
                if (!status)
6970
0
                {
6971
0
                    status = nc_put_vara_string(
6972
0
                        new_cdfid, nDstVarId, anStart, anCount,
6973
0
                        static_cast<const char **>(pBuffer));
6974
0
                    nc_free_string(nElems, static_cast<char **>(pBuffer));
6975
0
                }
6976
0
                break;
6977
6978
0
            case NC_UBYTE:
6979
0
                status =
6980
0
                    nc_get_vara_uchar(old_cdfid, nSrcVarId, anStart, anCount,
6981
0
                                      static_cast<unsigned char *>(pBuffer));
6982
0
                if (!status)
6983
0
                    status = nc_put_vara_uchar(
6984
0
                        new_cdfid, nDstVarId, anStart, anCount,
6985
0
                        static_cast<unsigned char *>(pBuffer));
6986
0
                break;
6987
0
            case NC_USHORT:
6988
0
                status =
6989
0
                    nc_get_vara_ushort(old_cdfid, nSrcVarId, anStart, anCount,
6990
0
                                       static_cast<unsigned short *>(pBuffer));
6991
0
                if (!status)
6992
0
                    status = nc_put_vara_ushort(
6993
0
                        new_cdfid, nDstVarId, anStart, anCount,
6994
0
                        static_cast<unsigned short *>(pBuffer));
6995
0
                break;
6996
0
            case NC_UINT:
6997
0
                status =
6998
0
                    nc_get_vara_uint(old_cdfid, nSrcVarId, anStart, anCount,
6999
0
                                     static_cast<unsigned int *>(pBuffer));
7000
0
                if (!status)
7001
0
                    status =
7002
0
                        nc_put_vara_uint(new_cdfid, nDstVarId, anStart, anCount,
7003
0
                                         static_cast<unsigned int *>(pBuffer));
7004
0
                break;
7005
0
            case NC_INT64:
7006
0
                status =
7007
0
                    nc_get_vara_longlong(old_cdfid, nSrcVarId, anStart, anCount,
7008
0
                                         static_cast<long long *>(pBuffer));
7009
0
                if (!status)
7010
0
                    status = nc_put_vara_longlong(
7011
0
                        new_cdfid, nDstVarId, anStart, anCount,
7012
0
                        static_cast<long long *>(pBuffer));
7013
0
                break;
7014
0
            case NC_UINT64:
7015
0
                status = nc_get_vara_ulonglong(
7016
0
                    old_cdfid, nSrcVarId, anStart, anCount,
7017
0
                    static_cast<unsigned long long *>(pBuffer));
7018
0
                if (!status)
7019
0
                    status = nc_put_vara_ulonglong(
7020
0
                        new_cdfid, nDstVarId, anStart, anCount,
7021
0
                        static_cast<unsigned long long *>(pBuffer));
7022
0
                break;
7023
0
            default:
7024
0
                status = NC_EBADTYPE;
7025
0
        }
7026
7027
0
        NCDF_ERR(status);
7028
0
        if (status != NC_NOERR)
7029
0
        {
7030
0
            VSIFree(pBuffer);
7031
0
            return false;
7032
0
        }
7033
0
    }
7034
7035
0
    VSIFree(pBuffer);
7036
0
    return true;
7037
0
}
7038
7039
/************************************************************************/
7040
/*                         NCDFIsUnlimitedDim()                         */
7041
/************************************************************************/
7042
7043
bool NCDFIsUnlimitedDim(bool bIsNC4, int cdfid, int nDimId)
7044
0
{
7045
0
    if (bIsNC4)
7046
0
    {
7047
0
        int nUnlimitedDims = 0;
7048
0
        nc_inq_unlimdims(cdfid, &nUnlimitedDims, nullptr);
7049
0
        bool bFound = false;
7050
0
        if (nUnlimitedDims)
7051
0
        {
7052
0
            int *panUnlimitedDimIds =
7053
0
                static_cast<int *>(CPLMalloc(sizeof(int) * nUnlimitedDims));
7054
0
            nc_inq_unlimdims(cdfid, nullptr, panUnlimitedDimIds);
7055
0
            for (int i = 0; i < nUnlimitedDims; i++)
7056
0
            {
7057
0
                if (panUnlimitedDimIds[i] == nDimId)
7058
0
                {
7059
0
                    bFound = true;
7060
0
                    break;
7061
0
                }
7062
0
            }
7063
0
            CPLFree(panUnlimitedDimIds);
7064
0
        }
7065
0
        return bFound;
7066
0
    }
7067
0
    else
7068
0
    {
7069
0
        int nUnlimitedDimId = -1;
7070
0
        nc_inq(cdfid, nullptr, nullptr, nullptr, &nUnlimitedDimId);
7071
0
        return nDimId == nUnlimitedDimId;
7072
0
    }
7073
0
}
7074
7075
/************************************************************************/
7076
/*                              CloneGrp()                              */
7077
/************************************************************************/
7078
7079
bool netCDFDataset::CloneGrp(int nOldGrpId, int nNewGrpId, bool bIsNC4,
7080
                             int nLayerId, int nDimIdToGrow, size_t nNewSize)
7081
0
{
7082
    // Clone dimensions
7083
0
    int nDimCount = -1;
7084
0
    int status = nc_inq_ndims(nOldGrpId, &nDimCount);
7085
0
    NCDF_ERR(status);
7086
0
    if (nDimCount < 0 || nDimCount > NC_MAX_DIMS)
7087
0
        return false;
7088
0
    int anDimIds[NC_MAX_DIMS];
7089
0
    int nUnlimiDimID = -1;
7090
0
    status = nc_inq_unlimdim(nOldGrpId, &nUnlimiDimID);
7091
0
    NCDF_ERR(status);
7092
0
    if (bIsNC4)
7093
0
    {
7094
        // In NC4, the dimension ids of a group are not necessarily in
7095
        // [0,nDimCount-1] range
7096
0
        int nDimCount2 = -1;
7097
0
        status = nc_inq_dimids(nOldGrpId, &nDimCount2, anDimIds, FALSE);
7098
0
        NCDF_ERR(status);
7099
0
        CPLAssert(nDimCount == nDimCount2);
7100
0
    }
7101
0
    else
7102
0
    {
7103
0
        for (int i = 0; i < nDimCount; i++)
7104
0
            anDimIds[i] = i;
7105
0
    }
7106
0
    for (int i = 0; i < nDimCount; i++)
7107
0
    {
7108
0
        char szDimName[NC_MAX_NAME + 1];
7109
0
        szDimName[0] = 0;
7110
0
        size_t nLen = 0;
7111
0
        const int nDimId = anDimIds[i];
7112
0
        status = nc_inq_dim(nOldGrpId, nDimId, szDimName, &nLen);
7113
0
        NCDF_ERR(status);
7114
0
        if (NCDFIsUnlimitedDim(bIsNC4, nOldGrpId, nDimId))
7115
0
            nLen = NC_UNLIMITED;
7116
0
        else if (nDimId == nDimIdToGrow && nOldGrpId == nLayerId)
7117
0
            nLen = nNewSize;
7118
0
        int nNewDimId = -1;
7119
0
        status = nc_def_dim(nNewGrpId, szDimName, nLen, &nNewDimId);
7120
0
        NCDF_ERR(status);
7121
0
        CPLAssert(nDimId == nNewDimId);
7122
0
        if (status != NC_NOERR)
7123
0
        {
7124
0
            return false;
7125
0
        }
7126
0
    }
7127
7128
    // Clone main attributes
7129
0
    if (!CloneAttributes(nOldGrpId, nNewGrpId, NC_GLOBAL, NC_GLOBAL))
7130
0
    {
7131
0
        return false;
7132
0
    }
7133
7134
    // Clone variable definitions
7135
0
    int nVarCount = -1;
7136
0
    status = nc_inq_nvars(nOldGrpId, &nVarCount);
7137
0
    NCDF_ERR(status);
7138
7139
0
    for (int i = 0; i < nVarCount; i++)
7140
0
    {
7141
0
        char szVarName[NC_MAX_NAME + 1];
7142
0
        szVarName[0] = 0;
7143
0
        status = nc_inq_varname(nOldGrpId, i, szVarName);
7144
0
        NCDF_ERR(status);
7145
0
        nc_type nc_datatype = NC_NAT;
7146
0
        status = nc_inq_vartype(nOldGrpId, i, &nc_datatype);
7147
0
        NCDF_ERR(status);
7148
0
        int nVarDimCount = -1;
7149
0
        status = nc_inq_varndims(nOldGrpId, i, &nVarDimCount);
7150
0
        NCDF_ERR(status);
7151
0
        status = nc_inq_vardimid(nOldGrpId, i, anDimIds);
7152
0
        NCDF_ERR(status);
7153
0
        int nNewVarId = -1;
7154
0
        status = nc_def_var(nNewGrpId, szVarName, nc_datatype, nVarDimCount,
7155
0
                            anDimIds, &nNewVarId);
7156
0
        NCDF_ERR(status);
7157
0
        CPLAssert(i == nNewVarId);
7158
0
        if (status != NC_NOERR)
7159
0
        {
7160
0
            return false;
7161
0
        }
7162
7163
0
        if (!CloneAttributes(nOldGrpId, nNewGrpId, i, i))
7164
0
        {
7165
0
            return false;
7166
0
        }
7167
0
    }
7168
7169
0
    status = nc_enddef(nNewGrpId);
7170
0
    NCDF_ERR(status);
7171
0
    if (status != NC_NOERR)
7172
0
    {
7173
0
        return false;
7174
0
    }
7175
7176
    // Clone variable content
7177
0
    for (int i = 0; i < nVarCount; i++)
7178
0
    {
7179
0
        if (!CloneVariableContent(nOldGrpId, nNewGrpId, i, i))
7180
0
        {
7181
0
            return false;
7182
0
        }
7183
0
    }
7184
7185
0
    return true;
7186
0
}
7187
7188
/************************************************************************/
7189
/*                              GrowDim()                               */
7190
/************************************************************************/
7191
7192
bool netCDFDataset::GrowDim(int nLayerId, int nDimIdToGrow, size_t nNewSize)
7193
0
{
7194
0
    int nCreationMode;
7195
    // Set nCreationMode based on eFormat.
7196
0
    switch (eFormat)
7197
0
    {
7198
0
#ifdef NETCDF_HAS_NC2
7199
0
        case NCDF_FORMAT_NC2:
7200
0
            nCreationMode = NC_CLOBBER | NC_64BIT_OFFSET;
7201
0
            break;
7202
0
#endif
7203
0
        case NCDF_FORMAT_NC4:
7204
0
            nCreationMode = NC_CLOBBER | NC_NETCDF4;
7205
0
            break;
7206
0
        case NCDF_FORMAT_NC4C:
7207
0
            nCreationMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
7208
0
            break;
7209
0
        case NCDF_FORMAT_NC:
7210
0
        default:
7211
0
            nCreationMode = NC_CLOBBER;
7212
0
            break;
7213
0
    }
7214
7215
0
    int new_cdfid = -1;
7216
0
    CPLString osTmpFilename(osFilename + ".tmp");
7217
0
    CPLString osFilenameForNCCreate(osTmpFilename);
7218
#if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
7219
    if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
7220
    {
7221
        char *pszTemp =
7222
            CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
7223
        osFilenameForNCCreate = pszTemp;
7224
        CPLFree(pszTemp);
7225
    }
7226
#endif
7227
0
    int status = nc_create(osFilenameForNCCreate, nCreationMode, &new_cdfid);
7228
0
    NCDF_ERR(status);
7229
0
    if (status != NC_NOERR)
7230
0
        return false;
7231
7232
0
    if (!CloneGrp(cdfid, new_cdfid, eFormat == NCDF_FORMAT_NC4, nLayerId,
7233
0
                  nDimIdToGrow, nNewSize))
7234
0
    {
7235
0
        GDAL_nc_close(new_cdfid);
7236
0
        return false;
7237
0
    }
7238
7239
0
    int nGroupCount = 0;
7240
0
    std::vector<CPLString> oListGrpName;
7241
0
    if (eFormat == NCDF_FORMAT_NC4 &&
7242
0
        nc_inq_grps(cdfid, &nGroupCount, nullptr) == NC_NOERR &&
7243
0
        nGroupCount > 0)
7244
0
    {
7245
0
        int *panGroupIds =
7246
0
            static_cast<int *>(CPLMalloc(sizeof(int) * nGroupCount));
7247
0
        status = nc_inq_grps(cdfid, nullptr, panGroupIds);
7248
0
        NCDF_ERR(status);
7249
0
        for (int i = 0; i < nGroupCount; i++)
7250
0
        {
7251
0
            char szGroupName[NC_MAX_NAME + 1];
7252
0
            szGroupName[0] = 0;
7253
0
            NCDF_ERR(nc_inq_grpname(panGroupIds[i], szGroupName));
7254
0
            int nNewGrpId = -1;
7255
0
            status = nc_def_grp(new_cdfid, szGroupName, &nNewGrpId);
7256
0
            NCDF_ERR(status);
7257
0
            if (status != NC_NOERR)
7258
0
            {
7259
0
                CPLFree(panGroupIds);
7260
0
                GDAL_nc_close(new_cdfid);
7261
0
                return false;
7262
0
            }
7263
0
            if (!CloneGrp(panGroupIds[i], nNewGrpId, /*bIsNC4=*/true, nLayerId,
7264
0
                          nDimIdToGrow, nNewSize))
7265
0
            {
7266
0
                CPLFree(panGroupIds);
7267
0
                GDAL_nc_close(new_cdfid);
7268
0
                return false;
7269
0
            }
7270
0
        }
7271
0
        CPLFree(panGroupIds);
7272
7273
0
        for (int i = 0; i < this->GetLayerCount(); i++)
7274
0
        {
7275
0
            auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
7276
0
            if (poLayer)
7277
0
            {
7278
0
                char szGroupName[NC_MAX_NAME + 1];
7279
0
                szGroupName[0] = 0;
7280
0
                status = nc_inq_grpname(poLayer->GetCDFID(), szGroupName);
7281
0
                NCDF_ERR(status);
7282
0
                oListGrpName.push_back(szGroupName);
7283
0
            }
7284
0
        }
7285
0
    }
7286
7287
0
    GDAL_nc_close(cdfid);
7288
0
    cdfid = -1;
7289
0
    GDAL_nc_close(new_cdfid);
7290
7291
0
    CPLString osOriFilename(osFilename + ".ori");
7292
0
    if (VSIRename(osFilename, osOriFilename) != 0 ||
7293
0
        VSIRename(osTmpFilename, osFilename) != 0)
7294
0
    {
7295
0
        CPLError(CE_Failure, CPLE_FileIO, "Renaming of files failed");
7296
0
        return false;
7297
0
    }
7298
0
    VSIUnlink(osOriFilename);
7299
7300
0
    CPLString osFilenameForNCOpen(osFilename);
7301
#if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
7302
    if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
7303
    {
7304
        char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
7305
        osFilenameForNCOpen = pszTemp;
7306
        CPLFree(pszTemp);
7307
    }
7308
#endif
7309
0
    status = GDAL_nc_open(osFilenameForNCOpen, NC_WRITE, &cdfid);
7310
0
    NCDF_ERR(status);
7311
0
    if (status != NC_NOERR)
7312
0
        return false;
7313
0
    bDefineMode = false;
7314
7315
0
    if (!oListGrpName.empty())
7316
0
    {
7317
0
        for (int i = 0; i < this->GetLayerCount(); i++)
7318
0
        {
7319
0
            auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
7320
0
            if (poLayer)
7321
0
            {
7322
0
                int nNewLayerCDFID = -1;
7323
0
                status = nc_inq_ncid(cdfid, oListGrpName[i].c_str(),
7324
0
                                     &nNewLayerCDFID);
7325
0
                NCDF_ERR(status);
7326
0
                poLayer->SetCDFID(nNewLayerCDFID);
7327
0
            }
7328
0
        }
7329
0
    }
7330
0
    else
7331
0
    {
7332
0
        for (int i = 0; i < this->GetLayerCount(); i++)
7333
0
        {
7334
0
            auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
7335
0
            if (poLayer)
7336
0
                poLayer->SetCDFID(cdfid);
7337
0
        }
7338
0
    }
7339
7340
0
    return true;
7341
0
}
7342
7343
#ifdef ENABLE_NCDUMP
7344
7345
/************************************************************************/
7346
/*                      netCDFDatasetCreateTempFile()                   */
7347
/************************************************************************/
7348
7349
/* Create a netCDF file from a text dump (format of ncdump) */
7350
/* Mostly to easy fuzzing of the driver, while still generating valid */
7351
/* netCDF files. */
7352
/* Note: not all data types are supported ! */
7353
bool netCDFDatasetCreateTempFile(NetCDFFormatEnum eFormat,
7354
                                 const char *pszTmpFilename, VSILFILE *fpSrc)
7355
399
{
7356
399
    CPL_IGNORE_RET_VAL(eFormat);
7357
399
    int nCreateMode = NC_CLOBBER;
7358
399
    if (eFormat == NCDF_FORMAT_NC4)
7359
8
        nCreateMode |= NC_NETCDF4;
7360
391
    else if (eFormat == NCDF_FORMAT_NC4C)
7361
141
        nCreateMode |= NC_NETCDF4 | NC_CLASSIC_MODEL;
7362
399
    int nCdfId = -1;
7363
399
    int status = nc_create(pszTmpFilename, nCreateMode, &nCdfId);
7364
399
    if (status != NC_NOERR)
7365
0
    {
7366
0
        return false;
7367
0
    }
7368
399
    VSIFSeekL(fpSrc, 0, SEEK_SET);
7369
399
    const char *pszLine;
7370
399
    constexpr int SECTION_NONE = 0;
7371
399
    constexpr int SECTION_DIMENSIONS = 1;
7372
399
    constexpr int SECTION_VARIABLES = 2;
7373
399
    constexpr int SECTION_DATA = 3;
7374
399
    int nActiveSection = SECTION_NONE;
7375
399
    std::map<CPLString, int> oMapDimToId;
7376
399
    std::map<int, int> oMapDimIdToDimLen;
7377
399
    std::map<CPLString, int> oMapVarToId;
7378
399
    std::map<int, std::vector<int>> oMapVarIdToVectorOfDimId;
7379
399
    std::map<int, int> oMapVarIdToType;
7380
399
    std::set<CPLString> oSetAttrDefined;
7381
399
    oMapVarToId[""] = -1;
7382
399
    size_t nTotalVarSize = 0;
7383
664k
    while ((pszLine = CPLReadLineL(fpSrc)) != nullptr)
7384
664k
    {
7385
664k
        if (STARTS_WITH(pszLine, "dimensions:") &&
7386
664k
            nActiveSection == SECTION_NONE)
7387
399
        {
7388
399
            nActiveSection = SECTION_DIMENSIONS;
7389
399
        }
7390
663k
        else if (STARTS_WITH(pszLine, "variables:") &&
7391
663k
                 nActiveSection == SECTION_DIMENSIONS)
7392
365
        {
7393
365
            nActiveSection = SECTION_VARIABLES;
7394
365
        }
7395
663k
        else if (STARTS_WITH(pszLine, "data:") &&
7396
663k
                 nActiveSection == SECTION_VARIABLES)
7397
219
        {
7398
219
            nActiveSection = SECTION_DATA;
7399
219
            status = nc_enddef(nCdfId);
7400
219
            if (status != NC_NOERR)
7401
0
            {
7402
0
                CPLDebug("netCDF", "nc_enddef() failed: %s",
7403
0
                         nc_strerror(status));
7404
0
            }
7405
219
        }
7406
663k
        else if (nActiveSection == SECTION_DIMENSIONS)
7407
103k
        {
7408
103k
            char **papszTokens = CSLTokenizeString2(pszLine, " \t=;", 0);
7409
103k
            if (CSLCount(papszTokens) == 2)
7410
23.6k
            {
7411
23.6k
                const char *pszDimName = papszTokens[0];
7412
23.6k
                bool bValidName = true;
7413
23.6k
                if (STARTS_WITH(pszDimName, "_nc4_non_coord_"))
7414
0
                {
7415
                    // This is an internal netcdf prefix. Using it may
7416
                    // cause memory leaks.
7417
0
                    bValidName = false;
7418
0
                }
7419
23.6k
                if (!bValidName)
7420
0
                {
7421
0
                    CPLDebug("netCDF",
7422
0
                             "nc_def_dim(%s) failed: invalid name found",
7423
0
                             pszDimName);
7424
0
                    CSLDestroy(papszTokens);
7425
0
                    continue;
7426
0
                }
7427
7428
23.6k
                const bool bIsASCII =
7429
23.6k
                    CPLIsASCII(pszDimName, static_cast<size_t>(-1));
7430
23.6k
                if (!bIsASCII)
7431
3.09k
                {
7432
                    // Workaround https://github.com/Unidata/netcdf-c/pull/450
7433
3.09k
                    CPLDebug("netCDF",
7434
3.09k
                             "nc_def_dim(%s) failed: rejected because "
7435
3.09k
                             "of non-ASCII characters",
7436
3.09k
                             pszDimName);
7437
3.09k
                    CSLDestroy(papszTokens);
7438
3.09k
                    continue;
7439
3.09k
                }
7440
20.5k
                int nDimSize = EQUAL(papszTokens[1], "UNLIMITED")
7441
20.5k
                                   ? NC_UNLIMITED
7442
20.5k
                                   : atoi(papszTokens[1]);
7443
20.5k
                if (nDimSize >= 1000)
7444
83
                    nDimSize = 1000;  // to avoid very long processing
7445
20.5k
                if (nDimSize >= 0)
7446
14.1k
                {
7447
14.1k
                    int nDimId = -1;
7448
14.1k
                    status = nc_def_dim(nCdfId, pszDimName, nDimSize, &nDimId);
7449
14.1k
                    if (status != NC_NOERR)
7450
13.0k
                    {
7451
13.0k
                        CPLDebug("netCDF", "nc_def_dim(%s, %d) failed: %s",
7452
13.0k
                                 pszDimName, nDimSize, nc_strerror(status));
7453
13.0k
                    }
7454
1.09k
                    else
7455
1.09k
                    {
7456
#ifdef DEBUG_VERBOSE
7457
                        CPLDebug("netCDF", "nc_def_dim(%s, %d) (%s) succeeded",
7458
                                 pszDimName, nDimSize, pszLine);
7459
#endif
7460
1.09k
                        oMapDimToId[pszDimName] = nDimId;
7461
1.09k
                        oMapDimIdToDimLen[nDimId] = nDimSize;
7462
1.09k
                    }
7463
14.1k
                }
7464
20.5k
            }
7465
100k
            CSLDestroy(papszTokens);
7466
100k
        }
7467
559k
        else if (nActiveSection == SECTION_VARIABLES)
7468
377k
        {
7469
527k
            while (*pszLine == ' ' || *pszLine == '\t')
7470
150k
                pszLine++;
7471
377k
            const char *pszColumn = strchr(pszLine, ':');
7472
377k
            const char *pszEqual = strchr(pszLine, '=');
7473
377k
            if (pszColumn == nullptr)
7474
315k
            {
7475
315k
                char **papszTokens = CSLTokenizeString2(pszLine, " \t=(),;", 0);
7476
315k
                if (CSLCount(papszTokens) >= 2)
7477
192k
                {
7478
192k
                    const char *pszVarName = papszTokens[1];
7479
192k
                    bool bValidName = true;
7480
192k
                    if (STARTS_WITH(pszVarName, "_nc4_non_coord_"))
7481
0
                    {
7482
                        // This is an internal netcdf prefix. Using it may
7483
                        // cause memory leaks.
7484
0
                        bValidName = false;
7485
0
                    }
7486
1.64M
                    for (int i = 0; pszVarName[i]; i++)
7487
1.45M
                    {
7488
1.45M
                        if (!((pszVarName[i] >= 'a' && pszVarName[i] <= 'z') ||
7489
1.45M
                              (pszVarName[i] >= 'A' && pszVarName[i] <= 'Z') ||
7490
1.45M
                              (pszVarName[i] >= '0' && pszVarName[i] <= '9') ||
7491
1.45M
                              pszVarName[i] == '_'))
7492
372k
                        {
7493
372k
                            bValidName = false;
7494
372k
                        }
7495
1.45M
                    }
7496
192k
                    if (!bValidName)
7497
82.2k
                    {
7498
82.2k
                        CPLDebug(
7499
82.2k
                            "netCDF",
7500
82.2k
                            "nc_def_var(%s) failed: illegal character found",
7501
82.2k
                            pszVarName);
7502
82.2k
                        CSLDestroy(papszTokens);
7503
82.2k
                        continue;
7504
82.2k
                    }
7505
110k
                    if (oMapVarToId.find(pszVarName) != oMapVarToId.end())
7506
41.2k
                    {
7507
41.2k
                        CPLDebug("netCDF",
7508
41.2k
                                 "nc_def_var(%s) failed: already defined",
7509
41.2k
                                 pszVarName);
7510
41.2k
                        CSLDestroy(papszTokens);
7511
41.2k
                        continue;
7512
41.2k
                    }
7513
69.3k
                    const char *pszVarType = papszTokens[0];
7514
69.3k
                    int nc_datatype = NC_BYTE;
7515
69.3k
                    size_t nDataTypeSize = 1;
7516
69.3k
                    if (EQUAL(pszVarType, "char"))
7517
1.31k
                    {
7518
1.31k
                        nc_datatype = NC_CHAR;
7519
1.31k
                        nDataTypeSize = 1;
7520
1.31k
                    }
7521
68.0k
                    else if (EQUAL(pszVarType, "byte"))
7522
0
                    {
7523
0
                        nc_datatype = NC_BYTE;
7524
0
                        nDataTypeSize = 1;
7525
0
                    }
7526
68.0k
                    else if (EQUAL(pszVarType, "short"))
7527
0
                    {
7528
0
                        nc_datatype = NC_SHORT;
7529
0
                        nDataTypeSize = 2;
7530
0
                    }
7531
68.0k
                    else if (EQUAL(pszVarType, "int"))
7532
35
                    {
7533
35
                        nc_datatype = NC_INT;
7534
35
                        nDataTypeSize = 4;
7535
35
                    }
7536
68.0k
                    else if (EQUAL(pszVarType, "float"))
7537
0
                    {
7538
0
                        nc_datatype = NC_FLOAT;
7539
0
                        nDataTypeSize = 4;
7540
0
                    }
7541
68.0k
                    else if (EQUAL(pszVarType, "double"))
7542
43
                    {
7543
43
                        nc_datatype = NC_DOUBLE;
7544
43
                        nDataTypeSize = 8;
7545
43
                    }
7546
67.9k
                    else if (EQUAL(pszVarType, "ubyte"))
7547
0
                    {
7548
0
                        nc_datatype = NC_UBYTE;
7549
0
                        nDataTypeSize = 1;
7550
0
                    }
7551
67.9k
                    else if (EQUAL(pszVarType, "ushort"))
7552
0
                    {
7553
0
                        nc_datatype = NC_USHORT;
7554
0
                        nDataTypeSize = 2;
7555
0
                    }
7556
67.9k
                    else if (EQUAL(pszVarType, "uint"))
7557
18
                    {
7558
18
                        nc_datatype = NC_UINT;
7559
18
                        nDataTypeSize = 4;
7560
18
                    }
7561
67.9k
                    else if (EQUAL(pszVarType, "int64"))
7562
0
                    {
7563
0
                        nc_datatype = NC_INT64;
7564
0
                        nDataTypeSize = 8;
7565
0
                    }
7566
67.9k
                    else if (EQUAL(pszVarType, "uint64"))
7567
0
                    {
7568
0
                        nc_datatype = NC_UINT64;
7569
0
                        nDataTypeSize = 8;
7570
0
                    }
7571
7572
69.3k
                    int nDims = CSLCount(papszTokens) - 2;
7573
69.3k
                    if (nDims >= 32)
7574
88
                    {
7575
                        // The number of dimensions in a netCDFv4 file is
7576
                        // limited by #define H5S_MAX_RANK    32
7577
                        // but libnetcdf doesn't check that...
7578
88
                        CPLDebug("netCDF",
7579
88
                                 "nc_def_var(%s) failed: too many dimensions",
7580
88
                                 pszVarName);
7581
88
                        CSLDestroy(papszTokens);
7582
88
                        continue;
7583
88
                    }
7584
69.2k
                    std::vector<int> aoDimIds;
7585
69.2k
                    bool bFailed = false;
7586
69.2k
                    size_t nSize = 1;
7587
83.0k
                    for (int i = 0; i < nDims; i++)
7588
76.9k
                    {
7589
76.9k
                        const char *pszDimName = papszTokens[2 + i];
7590
76.9k
                        if (oMapDimToId.find(pszDimName) == oMapDimToId.end())
7591
63.2k
                        {
7592
63.2k
                            bFailed = true;
7593
63.2k
                            break;
7594
63.2k
                        }
7595
13.7k
                        const int nDimId = oMapDimToId[pszDimName];
7596
13.7k
                        aoDimIds.push_back(nDimId);
7597
7598
13.7k
                        const size_t nDimSize = oMapDimIdToDimLen[nDimId];
7599
13.7k
                        if (nDimSize != 0)
7600
5.51k
                        {
7601
5.51k
                            if (nSize >
7602
5.51k
                                std::numeric_limits<size_t>::max() / nDimSize)
7603
0
                            {
7604
0
                                bFailed = true;
7605
0
                                break;
7606
0
                            }
7607
5.51k
                            else
7608
5.51k
                            {
7609
5.51k
                                nSize *= nDimSize;
7610
5.51k
                            }
7611
5.51k
                        }
7612
13.7k
                    }
7613
69.2k
                    if (bFailed)
7614
63.2k
                    {
7615
63.2k
                        CPLDebug("netCDF",
7616
63.2k
                                 "nc_def_var(%s) failed: unknown dimension(s)",
7617
63.2k
                                 pszVarName);
7618
63.2k
                        CSLDestroy(papszTokens);
7619
63.2k
                        continue;
7620
63.2k
                    }
7621
6.05k
                    if (nSize > 100U * 1024 * 1024 / nDataTypeSize)
7622
0
                    {
7623
0
                        CPLDebug("netCDF",
7624
0
                                 "nc_def_var(%s) failed: too large data",
7625
0
                                 pszVarName);
7626
0
                        CSLDestroy(papszTokens);
7627
0
                        continue;
7628
0
                    }
7629
6.05k
                    if (nTotalVarSize >
7630
6.05k
                            std::numeric_limits<size_t>::max() - nSize ||
7631
6.05k
                        nTotalVarSize + nSize > 100 * 1024 * 1024)
7632
0
                    {
7633
0
                        CPLDebug("netCDF",
7634
0
                                 "nc_def_var(%s) failed: too large data",
7635
0
                                 pszVarName);
7636
0
                        CSLDestroy(papszTokens);
7637
0
                        continue;
7638
0
                    }
7639
6.05k
                    nTotalVarSize += nSize;
7640
7641
6.05k
                    int nVarId = -1;
7642
6.05k
                    status =
7643
6.05k
                        nc_def_var(nCdfId, pszVarName, nc_datatype, nDims,
7644
6.05k
                                   (nDims) ? &aoDimIds[0] : nullptr, &nVarId);
7645
6.05k
                    if (status != NC_NOERR)
7646
255
                    {
7647
255
                        CPLDebug("netCDF", "nc_def_var(%s) failed: %s",
7648
255
                                 pszVarName, nc_strerror(status));
7649
255
                    }
7650
5.79k
                    else
7651
5.79k
                    {
7652
#ifdef DEBUG_VERBOSE
7653
                        CPLDebug("netCDF", "nc_def_var(%s) (%s) succeeded",
7654
                                 pszVarName, pszLine);
7655
#endif
7656
5.79k
                        oMapVarToId[pszVarName] = nVarId;
7657
5.79k
                        oMapVarIdToType[nVarId] = nc_datatype;
7658
5.79k
                        oMapVarIdToVectorOfDimId[nVarId] = std::move(aoDimIds);
7659
5.79k
                    }
7660
6.05k
                }
7661
128k
                CSLDestroy(papszTokens);
7662
128k
            }
7663
62.2k
            else if (pszEqual != nullptr && pszEqual - pszColumn > 0)
7664
20.3k
            {
7665
20.3k
                CPLString osVarName(pszLine, pszColumn - pszLine);
7666
20.3k
                CPLString osAttrName(pszColumn + 1, pszEqual - pszColumn - 1);
7667
20.3k
                osAttrName.Trim();
7668
20.3k
                if (oMapVarToId.find(osVarName) == oMapVarToId.end())
7669
5.97k
                {
7670
5.97k
                    CPLDebug("netCDF",
7671
5.97k
                             "nc_put_att(%s:%s) failed: "
7672
5.97k
                             "no corresponding variable",
7673
5.97k
                             osVarName.c_str(), osAttrName.c_str());
7674
5.97k
                    continue;
7675
5.97k
                }
7676
14.3k
                bool bValidName = true;
7677
239k
                for (size_t i = 0; i < osAttrName.size(); i++)
7678
225k
                {
7679
225k
                    if (!((osAttrName[i] >= 'a' && osAttrName[i] <= 'z') ||
7680
225k
                          (osAttrName[i] >= 'A' && osAttrName[i] <= 'Z') ||
7681
225k
                          (osAttrName[i] >= '0' && osAttrName[i] <= '9') ||
7682
225k
                          osAttrName[i] == '_'))
7683
84.0k
                    {
7684
84.0k
                        bValidName = false;
7685
84.0k
                    }
7686
225k
                }
7687
14.3k
                if (!bValidName)
7688
2.36k
                {
7689
2.36k
                    CPLDebug(
7690
2.36k
                        "netCDF",
7691
2.36k
                        "nc_put_att(%s:%s) failed: illegal character found",
7692
2.36k
                        osVarName.c_str(), osAttrName.c_str());
7693
2.36k
                    continue;
7694
2.36k
                }
7695
11.9k
                if (oSetAttrDefined.find(osVarName + ":" + osAttrName) !=
7696
11.9k
                    oSetAttrDefined.end())
7697
7.59k
                {
7698
7.59k
                    CPLDebug("netCDF",
7699
7.59k
                             "nc_put_att(%s:%s) failed: already defined",
7700
7.59k
                             osVarName.c_str(), osAttrName.c_str());
7701
7.59k
                    continue;
7702
7.59k
                }
7703
7704
4.39k
                const int nVarId = oMapVarToId[osVarName];
7705
4.39k
                const char *pszValue = pszEqual + 1;
7706
6.73k
                while (*pszValue == ' ')
7707
2.33k
                    pszValue++;
7708
7709
4.39k
                status = NC_EBADTYPE;
7710
4.39k
                if (*pszValue == '"')
7711
493
                {
7712
                    // For _FillValue, the attribute type should match
7713
                    // the variable type. Leaks memory with NC4 otherwise
7714
493
                    if (osAttrName == "_FillValue")
7715
0
                    {
7716
0
                        CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
7717
0
                                 osVarName.c_str(), osAttrName.c_str(),
7718
0
                                 nc_strerror(status));
7719
0
                        continue;
7720
0
                    }
7721
7722
                    // Unquote and unescape string value
7723
493
                    CPLString osVal(pszValue + 1);
7724
5.24k
                    while (!osVal.empty())
7725
5.00k
                    {
7726
5.00k
                        if (osVal.back() == ';' || osVal.back() == ' ')
7727
4.75k
                        {
7728
4.75k
                            osVal.pop_back();
7729
4.75k
                        }
7730
258
                        else if (osVal.back() == '"')
7731
19
                        {
7732
19
                            osVal.pop_back();
7733
19
                            break;
7734
19
                        }
7735
239
                        else
7736
239
                        {
7737
239
                            break;
7738
239
                        }
7739
5.00k
                    }
7740
493
                    osVal.replaceAll("\\\"", '"');
7741
493
                    status = nc_put_att_text(nCdfId, nVarId, osAttrName,
7742
493
                                             osVal.size(), osVal.c_str());
7743
493
                }
7744
3.90k
                else
7745
3.90k
                {
7746
3.90k
                    CPLString osVal(pszValue);
7747
3.94k
                    while (!osVal.empty())
7748
3.85k
                    {
7749
3.85k
                        if (osVal.back() == ';' || osVal.back() == ' ')
7750
38
                        {
7751
38
                            osVal.pop_back();
7752
38
                        }
7753
3.81k
                        else
7754
3.81k
                        {
7755
3.81k
                            break;
7756
3.81k
                        }
7757
3.85k
                    }
7758
3.90k
                    int nc_datatype = -1;
7759
3.90k
                    if (!osVal.empty() && osVal.back() == 'b')
7760
2
                    {
7761
2
                        nc_datatype = NC_BYTE;
7762
2
                        osVal.pop_back();
7763
2
                    }
7764
3.90k
                    else if (!osVal.empty() && osVal.back() == 's')
7765
0
                    {
7766
0
                        nc_datatype = NC_SHORT;
7767
0
                        osVal.pop_back();
7768
0
                    }
7769
3.90k
                    if (CPLGetValueType(osVal) == CPL_VALUE_INTEGER)
7770
1.72k
                    {
7771
1.72k
                        if (nc_datatype < 0)
7772
1.72k
                            nc_datatype = NC_INT;
7773
1.72k
                    }
7774
2.17k
                    else if (CPLGetValueType(osVal) == CPL_VALUE_REAL)
7775
141
                    {
7776
141
                        nc_datatype = NC_DOUBLE;
7777
141
                    }
7778
2.03k
                    else
7779
2.03k
                    {
7780
2.03k
                        nc_datatype = -1;
7781
2.03k
                    }
7782
7783
                    // For _FillValue, check that the attribute type matches
7784
                    // the variable type. Leaks memory with NC4 otherwise
7785
3.90k
                    if (osAttrName == "_FillValue")
7786
0
                    {
7787
0
                        if (nVarId < 0 ||
7788
0
                            nc_datatype != oMapVarIdToType[nVarId])
7789
0
                        {
7790
0
                            nc_datatype = -1;
7791
0
                        }
7792
0
                    }
7793
7794
3.90k
                    if (nc_datatype == NC_BYTE)
7795
1
                    {
7796
1
                        signed char chVal =
7797
1
                            static_cast<signed char>(atoi(osVal));
7798
1
                        status = nc_put_att_schar(nCdfId, nVarId, osAttrName,
7799
1
                                                  NC_BYTE, 1, &chVal);
7800
1
                    }
7801
3.90k
                    else if (nc_datatype == NC_SHORT)
7802
0
                    {
7803
0
                        short nVal = static_cast<short>(atoi(osVal));
7804
0
                        status = nc_put_att_short(nCdfId, nVarId, osAttrName,
7805
0
                                                  NC_SHORT, 1, &nVal);
7806
0
                    }
7807
3.90k
                    else if (nc_datatype == NC_INT)
7808
1.72k
                    {
7809
1.72k
                        int nVal = static_cast<int>(atoi(osVal));
7810
1.72k
                        status = nc_put_att_int(nCdfId, nVarId, osAttrName,
7811
1.72k
                                                NC_INT, 1, &nVal);
7812
1.72k
                    }
7813
2.17k
                    else if (nc_datatype == NC_DOUBLE)
7814
141
                    {
7815
141
                        double dfVal = CPLAtof(osVal);
7816
141
                        status = nc_put_att_double(nCdfId, nVarId, osAttrName,
7817
141
                                                   NC_DOUBLE, 1, &dfVal);
7818
141
                    }
7819
3.90k
                }
7820
4.39k
                if (status != NC_NOERR)
7821
2.10k
                {
7822
2.10k
                    CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
7823
2.10k
                             osVarName.c_str(), osAttrName.c_str(),
7824
2.10k
                             nc_strerror(status));
7825
2.10k
                }
7826
2.29k
                else
7827
2.29k
                {
7828
2.29k
                    oSetAttrDefined.insert(osVarName + ":" + osAttrName);
7829
#ifdef DEBUG_VERBOSE
7830
                    CPLDebug("netCDF", "nc_put_att_(%s:%s) (%s) succeeded",
7831
                             osVarName.c_str(), osAttrName.c_str(), pszLine);
7832
#endif
7833
2.29k
                }
7834
4.39k
            }
7835
377k
        }
7836
182k
        else if (nActiveSection == SECTION_DATA)
7837
181k
        {
7838
301k
            while (*pszLine == ' ' || *pszLine == '\t')
7839
120k
                pszLine++;
7840
181k
            const char *pszEqual = strchr(pszLine, '=');
7841
181k
            if (pszEqual)
7842
41.6k
            {
7843
41.6k
                CPLString osVarName(pszLine, pszEqual - pszLine);
7844
41.6k
                osVarName.Trim();
7845
41.6k
                if (oMapVarToId.find(osVarName) == oMapVarToId.end())
7846
39.8k
                    continue;
7847
1.73k
                const int nVarId = oMapVarToId[osVarName];
7848
1.73k
                CPLString osAccVal(pszEqual + 1);
7849
1.73k
                osAccVal.Trim();
7850
26.4k
                while (osAccVal.empty() || osAccVal.back() != ';')
7851
24.8k
                {
7852
24.8k
                    pszLine = CPLReadLineL(fpSrc);
7853
24.8k
                    if (pszLine == nullptr)
7854
143
                        break;
7855
24.7k
                    CPLString osVal(pszLine);
7856
24.7k
                    osVal.Trim();
7857
24.7k
                    osAccVal += osVal;
7858
24.7k
                }
7859
1.73k
                if (pszLine == nullptr)
7860
143
                    break;
7861
1.58k
                osAccVal.pop_back();
7862
7863
1.58k
                const std::vector<int> aoDimIds =
7864
1.58k
                    oMapVarIdToVectorOfDimId[nVarId];
7865
1.58k
                size_t nSize = 1;
7866
1.58k
                std::vector<size_t> aoStart, aoEdge;
7867
1.58k
                aoStart.resize(aoDimIds.size());
7868
1.58k
                aoEdge.resize(aoDimIds.size());
7869
4.26k
                for (size_t i = 0; i < aoDimIds.size(); ++i)
7870
2.67k
                {
7871
2.67k
                    const size_t nDimSize = oMapDimIdToDimLen[aoDimIds[i]];
7872
2.67k
                    if (nDimSize != 0 &&
7873
2.67k
                        nSize > std::numeric_limits<size_t>::max() / nDimSize)
7874
0
                    {
7875
0
                        nSize = 0;
7876
0
                    }
7877
2.67k
                    else
7878
2.67k
                    {
7879
2.67k
                        nSize *= nDimSize;
7880
2.67k
                    }
7881
2.67k
                    aoStart[i] = 0;
7882
2.67k
                    aoEdge[i] = nDimSize;
7883
2.67k
                }
7884
7885
1.58k
                status = NC_EBADTYPE;
7886
1.58k
                if (nSize == 0)
7887
0
                {
7888
                    // Might happen with a unlimited dimension
7889
0
                }
7890
1.58k
                else if (oMapVarIdToType[nVarId] == NC_DOUBLE)
7891
0
                {
7892
0
                    if (!aoStart.empty())
7893
0
                    {
7894
0
                        char **papszTokens =
7895
0
                            CSLTokenizeString2(osAccVal, " ,;", 0);
7896
0
                        size_t nTokens = CSLCount(papszTokens);
7897
0
                        if (nTokens >= nSize)
7898
0
                        {
7899
0
                            double *padfVals = static_cast<double *>(
7900
0
                                VSI_CALLOC_VERBOSE(nSize, sizeof(double)));
7901
0
                            if (padfVals)
7902
0
                            {
7903
0
                                for (size_t i = 0; i < nSize; i++)
7904
0
                                {
7905
0
                                    padfVals[i] = CPLAtof(papszTokens[i]);
7906
0
                                }
7907
0
                                status = nc_put_vara_double(
7908
0
                                    nCdfId, nVarId, &aoStart[0], &aoEdge[0],
7909
0
                                    padfVals);
7910
0
                                VSIFree(padfVals);
7911
0
                            }
7912
0
                        }
7913
0
                        CSLDestroy(papszTokens);
7914
0
                    }
7915
0
                }
7916
1.58k
                else if (oMapVarIdToType[nVarId] == NC_BYTE)
7917
304
                {
7918
304
                    if (!aoStart.empty())
7919
301
                    {
7920
301
                        char **papszTokens =
7921
301
                            CSLTokenizeString2(osAccVal, " ,;", 0);
7922
301
                        size_t nTokens = CSLCount(papszTokens);
7923
301
                        if (nTokens >= nSize)
7924
1
                        {
7925
1
                            signed char *panVals = static_cast<signed char *>(
7926
1
                                VSI_CALLOC_VERBOSE(nSize, sizeof(signed char)));
7927
1
                            if (panVals)
7928
1
                            {
7929
1.00k
                                for (size_t i = 0; i < nSize; i++)
7930
1.00k
                                {
7931
1.00k
                                    panVals[i] = static_cast<signed char>(
7932
1.00k
                                        atoi(papszTokens[i]));
7933
1.00k
                                }
7934
1
                                status = nc_put_vara_schar(nCdfId, nVarId,
7935
1
                                                           &aoStart[0],
7936
1
                                                           &aoEdge[0], panVals);
7937
1
                                VSIFree(panVals);
7938
1
                            }
7939
1
                        }
7940
301
                        CSLDestroy(papszTokens);
7941
301
                    }
7942
304
                }
7943
1.28k
                else if (oMapVarIdToType[nVarId] == NC_CHAR)
7944
1.06k
                {
7945
1.06k
                    if (aoStart.size() == 2)
7946
1.06k
                    {
7947
1.06k
                        std::vector<CPLString> aoStrings;
7948
1.06k
                        bool bInString = false;
7949
1.06k
                        CPLString osCurString;
7950
217k
                        for (size_t i = 0; i < osAccVal.size();)
7951
216k
                        {
7952
216k
                            if (!bInString)
7953
78.3k
                            {
7954
78.3k
                                if (osAccVal[i] == '"')
7955
12.7k
                                {
7956
12.7k
                                    bInString = true;
7957
12.7k
                                    osCurString.clear();
7958
12.7k
                                }
7959
78.3k
                                i++;
7960
78.3k
                            }
7961
138k
                            else if (osAccVal[i] == '\\' &&
7962
138k
                                     i + 1 < osAccVal.size() &&
7963
138k
                                     osAccVal[i + 1] == '"')
7964
1.07k
                            {
7965
1.07k
                                osCurString += '"';
7966
1.07k
                                i += 2;
7967
1.07k
                            }
7968
137k
                            else if (osAccVal[i] == '"')
7969
12.0k
                            {
7970
12.0k
                                aoStrings.push_back(osCurString);
7971
12.0k
                                osCurString.clear();
7972
12.0k
                                bInString = false;
7973
12.0k
                                i++;
7974
12.0k
                            }
7975
125k
                            else
7976
125k
                            {
7977
125k
                                osCurString += osAccVal[i];
7978
125k
                                i++;
7979
125k
                            }
7980
216k
                        }
7981
1.06k
                        const size_t nRecords = oMapDimIdToDimLen[aoDimIds[0]];
7982
1.06k
                        const size_t nWidth = oMapDimIdToDimLen[aoDimIds[1]];
7983
1.06k
                        size_t nIters = aoStrings.size();
7984
1.06k
                        if (nIters > nRecords)
7985
58
                            nIters = nRecords;
7986
11.9k
                        for (size_t i = 0; i < nIters; i++)
7987
10.9k
                        {
7988
10.9k
                            size_t anIndex[2];
7989
10.9k
                            anIndex[0] = i;
7990
10.9k
                            anIndex[1] = 0;
7991
10.9k
                            size_t anCount[2];
7992
10.9k
                            anCount[0] = 1;
7993
10.9k
                            anCount[1] = aoStrings[i].size();
7994
10.9k
                            if (anCount[1] > nWidth)
7995
21
                                anCount[1] = nWidth;
7996
10.9k
                            status =
7997
10.9k
                                nc_put_vara_text(nCdfId, nVarId, anIndex,
7998
10.9k
                                                 anCount, aoStrings[i].c_str());
7999
10.9k
                            if (status != NC_NOERR)
8000
0
                                break;
8001
10.9k
                        }
8002
1.06k
                    }
8003
1.06k
                }
8004
1.58k
                if (status != NC_NOERR)
8005
527
                {
8006
527
                    CPLDebug("netCDF", "nc_put_var_(%s) failed: %s",
8007
527
                             osVarName.c_str(), nc_strerror(status));
8008
527
                }
8009
1.58k
            }
8010
181k
        }
8011
664k
    }
8012
8013
399
    GDAL_nc_close(nCdfId);
8014
399
    return true;
8015
399
}
8016
8017
#endif  // ENABLE_NCDUMP
8018
8019
/************************************************************************/
8020
/*                                Open()                                */
8021
/************************************************************************/
8022
8023
GDALDataset *netCDFDataset::Open(GDALOpenInfo *poOpenInfo)
8024
8025
399
{
8026
#ifdef NCDF_DEBUG
8027
    CPLDebug("GDAL_netCDF", "\n=====\nOpen(), filename=[%s]",
8028
             poOpenInfo->pszFilename);
8029
#endif
8030
8031
    // Does this appear to be a netcdf file?
8032
399
    NetCDFFormatEnum eTmpFormat = NCDF_FORMAT_NONE;
8033
399
    if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
8034
399
    {
8035
399
        eTmpFormat = netCDFIdentifyFormat(poOpenInfo, /* bCheckExt = */ true);
8036
#ifdef NCDF_DEBUG
8037
        CPLDebug("GDAL_netCDF", "identified format %d", eTmpFormat);
8038
#endif
8039
        // Note: not calling Identify() directly, because we want the file type.
8040
        // Only support NCDF_FORMAT* formats.
8041
399
        if (NCDF_FORMAT_NC == eTmpFormat || NCDF_FORMAT_NC2 == eTmpFormat ||
8042
399
            NCDF_FORMAT_NC4 == eTmpFormat || NCDF_FORMAT_NC4C == eTmpFormat)
8043
399
        {
8044
            // ok
8045
399
        }
8046
0
        else if (eTmpFormat == NCDF_FORMAT_HDF4 &&
8047
0
                 poOpenInfo->IsSingleAllowedDriver("netCDF"))
8048
0
        {
8049
            // ok
8050
0
        }
8051
0
        else
8052
0
        {
8053
0
            return nullptr;
8054
0
        }
8055
399
    }
8056
0
    else
8057
0
    {
8058
0
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
8059
        // We don't necessarily want to catch bugs in libnetcdf ...
8060
0
        if (CPLGetConfigOption("DISABLE_OPEN_REAL_NETCDF_FILES", nullptr))
8061
0
        {
8062
0
            return nullptr;
8063
0
        }
8064
0
#endif
8065
0
    }
8066
8067
399
    if (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER)
8068
0
    {
8069
0
        return OpenMultiDim(poOpenInfo);
8070
0
    }
8071
8072
399
    CPLMutexHolderD(&hNCMutex);
8073
8074
399
    CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock with
8075
        // GDALDataset own mutex.
8076
399
    netCDFDataset *poDS = new netCDFDataset();
8077
399
    poDS->papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
8078
399
    CPLAcquireMutex(hNCMutex, 1000.0);
8079
8080
399
    poDS->SetDescription(poOpenInfo->pszFilename);
8081
8082
    // Check if filename start with NETCDF: tag.
8083
399
    bool bTreatAsSubdataset = false;
8084
399
    CPLString osSubdatasetName;
8085
8086
399
#ifdef ENABLE_NCDUMP
8087
399
    const char *pszHeader =
8088
399
        reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
8089
399
    if (poOpenInfo->fpL != nullptr && STARTS_WITH(pszHeader, "netcdf ") &&
8090
399
        strstr(pszHeader, "dimensions:") && strstr(pszHeader, "variables:"))
8091
399
    {
8092
        // By default create a temporary file that will be destroyed,
8093
        // unless NETCDF_TMP_FILE is defined. Can be useful to see which
8094
        // netCDF file has been generated from a potential fuzzed input.
8095
399
        poDS->osFilename = CPLGetConfigOption("NETCDF_TMP_FILE", "");
8096
399
        if (poDS->osFilename.empty())
8097
399
        {
8098
399
            poDS->bFileToDestroyAtClosing = true;
8099
399
            poDS->osFilename = CPLGenerateTempFilenameSafe("netcdf_tmp");
8100
399
        }
8101
399
        if (!netCDFDatasetCreateTempFile(eTmpFormat, poDS->osFilename,
8102
399
                                         poOpenInfo->fpL))
8103
0
        {
8104
0
            CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
8105
                // deadlock with GDALDataset own mutex.
8106
0
            delete poDS;
8107
0
            CPLAcquireMutex(hNCMutex, 1000.0);
8108
0
            return nullptr;
8109
0
        }
8110
399
        bTreatAsSubdataset = false;
8111
399
        poDS->eFormat = eTmpFormat;
8112
399
    }
8113
0
    else
8114
0
#endif
8115
8116
0
        if (STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
8117
0
    {
8118
0
        char **papszName =
8119
0
            CSLTokenizeString2(poOpenInfo->pszFilename, ":",
8120
0
                               CSLT_HONOURSTRINGS | CSLT_PRESERVEESCAPES);
8121
8122
0
        if (CSLCount(papszName) >= 3 &&
8123
0
            ((strlen(papszName[1]) == 1 && /* D:\\bla */
8124
0
              (papszName[2][0] == '/' || papszName[2][0] == '\\')) ||
8125
0
             EQUAL(papszName[1], "http") || EQUAL(papszName[1], "https") ||
8126
0
             EQUAL(papszName[1], "/vsicurl/http") ||
8127
0
             EQUAL(papszName[1], "/vsicurl/https") ||
8128
0
             EQUAL(papszName[1], "/vsicurl_streaming/http") ||
8129
0
             EQUAL(papszName[1], "/vsicurl_streaming/https")))
8130
0
        {
8131
0
            const int nCountBefore = CSLCount(papszName);
8132
0
            CPLString osTmp = papszName[1];
8133
0
            osTmp += ':';
8134
0
            osTmp += papszName[2];
8135
0
            CPLFree(papszName[1]);
8136
0
            CPLFree(papszName[2]);
8137
0
            papszName[1] = CPLStrdup(osTmp);
8138
0
            memmove(papszName + 2, papszName + 3,
8139
0
                    (nCountBefore - 2) * sizeof(char *));
8140
0
        }
8141
8142
0
        if (CSLCount(papszName) == 3)
8143
0
        {
8144
0
            poDS->osFilename = papszName[1];
8145
0
            osSubdatasetName = papszName[2];
8146
0
            bTreatAsSubdataset = true;
8147
0
            CSLDestroy(papszName);
8148
0
        }
8149
0
        else if (CSLCount(papszName) == 2)
8150
0
        {
8151
0
            poDS->osFilename = papszName[1];
8152
0
            osSubdatasetName = "";
8153
0
            bTreatAsSubdataset = false;
8154
0
            CSLDestroy(papszName);
8155
0
        }
8156
0
        else
8157
0
        {
8158
0
            CSLDestroy(papszName);
8159
0
            CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
8160
                // deadlock with GDALDataset own mutex.
8161
0
            delete poDS;
8162
0
            CPLAcquireMutex(hNCMutex, 1000.0);
8163
0
            CPLError(CE_Failure, CPLE_AppDefined,
8164
0
                     "Failed to parse NETCDF: prefix string into expected 2, 3 "
8165
0
                     "or 4 fields.");
8166
0
            return nullptr;
8167
0
        }
8168
8169
0
        if (!STARTS_WITH(poDS->osFilename, "http://") &&
8170
0
            !STARTS_WITH(poDS->osFilename, "https://"))
8171
0
        {
8172
            // Identify Format from real file, with bCheckExt=FALSE.
8173
0
            GDALOpenInfo *poOpenInfo2 =
8174
0
                new GDALOpenInfo(poDS->osFilename.c_str(), GA_ReadOnly);
8175
0
            poDS->eFormat =
8176
0
                netCDFIdentifyFormat(poOpenInfo2, /* bCheckExt = */ false);
8177
0
            delete poOpenInfo2;
8178
0
            if (NCDF_FORMAT_NONE == poDS->eFormat ||
8179
0
                NCDF_FORMAT_UNKNOWN == poDS->eFormat)
8180
0
            {
8181
0
                CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
8182
                    // deadlock with GDALDataset own mutex.
8183
0
                delete poDS;
8184
0
                CPLAcquireMutex(hNCMutex, 1000.0);
8185
0
                return nullptr;
8186
0
            }
8187
0
        }
8188
0
    }
8189
0
    else
8190
0
    {
8191
0
        poDS->osFilename = poOpenInfo->pszFilename;
8192
0
        bTreatAsSubdataset = false;
8193
0
        poDS->eFormat = eTmpFormat;
8194
0
    }
8195
8196
// Try opening the dataset.
8197
#if defined(NCDF_DEBUG) && defined(ENABLE_UFFD)
8198
    CPLDebug("GDAL_netCDF", "calling nc_open_mem(%s)",
8199
             poDS->osFilename.c_str());
8200
#elif defined(NCDF_DEBUG) && !defined(ENABLE_UFFD)
8201
    CPLDebug("GDAL_netCDF", "calling nc_open(%s)", poDS->osFilename.c_str());
8202
#endif
8203
399
    int cdfid = -1;
8204
399
    const int nMode = ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE) != 0)
8205
399
                          ? NC_WRITE
8206
399
                          : NC_NOWRITE;
8207
399
    CPLString osFilenameForNCOpen(poDS->osFilename);
8208
#if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
8209
    if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
8210
    {
8211
        char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
8212
        osFilenameForNCOpen = pszTemp;
8213
        CPLFree(pszTemp);
8214
    }
8215
#endif
8216
399
    int status2 = -1;
8217
8218
399
#ifdef ENABLE_UFFD
8219
399
    cpl_uffd_context *pCtx = nullptr;
8220
399
#endif
8221
8222
399
    if (STARTS_WITH(osFilenameForNCOpen, "/vsimem/") &&
8223
399
        poOpenInfo->eAccess == GA_ReadOnly)
8224
0
    {
8225
0
        vsi_l_offset nLength = 0;
8226
0
        poDS->fpVSIMEM = VSIFOpenL(osFilenameForNCOpen, "rb");
8227
0
        if (poDS->fpVSIMEM)
8228
0
        {
8229
            // We assume that the file will not be modified. If it is, then
8230
            // pabyBuffer might become invalid.
8231
0
            GByte *pabyBuffer =
8232
0
                VSIGetMemFileBuffer(osFilenameForNCOpen, &nLength, false);
8233
0
            if (pabyBuffer)
8234
0
            {
8235
0
                status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen),
8236
0
                                      nMode, static_cast<size_t>(nLength),
8237
0
                                      pabyBuffer, &cdfid);
8238
0
            }
8239
0
        }
8240
0
    }
8241
399
    else
8242
399
    {
8243
399
        const bool bVsiFile =
8244
399
            !strncmp(osFilenameForNCOpen, "/vsi", strlen("/vsi"));
8245
399
#ifdef ENABLE_UFFD
8246
399
        bool bReadOnly = (poOpenInfo->eAccess == GA_ReadOnly);
8247
399
        void *pVma = nullptr;
8248
399
        uint64_t nVmaSize = 0;
8249
8250
399
        if (bVsiFile)
8251
0
        {
8252
0
            if (bReadOnly)
8253
0
            {
8254
0
                if (CPLIsUserFaultMappingSupported())
8255
0
                {
8256
0
                    pCtx = CPLCreateUserFaultMapping(osFilenameForNCOpen, &pVma,
8257
0
                                                     &nVmaSize);
8258
0
                }
8259
0
                else
8260
0
                {
8261
0
                    CPLError(CE_Failure, CPLE_AppDefined,
8262
0
                             "Opening a /vsi file with the netCDF driver "
8263
0
                             "requires Linux userfaultfd to be available. "
8264
0
                             "If running from Docker, "
8265
0
                             "--security-opt seccomp=unconfined might be "
8266
0
                             "needed.%s",
8267
0
                             ((poDS->eFormat == NCDF_FORMAT_NC4 ||
8268
0
                               poDS->eFormat == NCDF_FORMAT_HDF5) &&
8269
0
                              GDALGetDriverByName("HDF5"))
8270
0
                                 ? " Or you may set the GDAL_SKIP=netCDF "
8271
0
                                   "configuration option to force the use of "
8272
0
                                   "the HDF5 driver."
8273
0
                                 : "");
8274
0
                }
8275
0
            }
8276
0
            else
8277
0
            {
8278
0
                CPLError(CE_Failure, CPLE_AppDefined,
8279
0
                         "Opening a /vsi file with the netCDF driver is only "
8280
0
                         "supported in read-only mode");
8281
0
            }
8282
0
        }
8283
399
        if (pCtx != nullptr && pVma != nullptr && nVmaSize > 0)
8284
0
        {
8285
            // netCDF code, at least for netCDF 4.7.0, is confused by filenames
8286
            // like /vsicurl/http[s]://example.com/foo.nc, so just pass the
8287
            // final part
8288
0
            status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen), nMode,
8289
0
                                  static_cast<size_t>(nVmaSize), pVma, &cdfid);
8290
0
        }
8291
399
        else
8292
399
            status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
8293
#else
8294
        if (bVsiFile)
8295
        {
8296
            CPLError(
8297
                CE_Failure, CPLE_AppDefined,
8298
                "Opening a /vsi file with the netCDF driver requires Linux "
8299
                "userfaultfd to be available.%s",
8300
                ((poDS->eFormat == NCDF_FORMAT_NC4 ||
8301
                  poDS->eFormat == NCDF_FORMAT_HDF5) &&
8302
                 GDALGetDriverByName("HDF5"))
8303
                    ? " Or you may set the GDAL_SKIP=netCDF "
8304
                      "configuration option to force the use of the HDF5 "
8305
                      "driver."
8306
                    : "");
8307
            status2 = NC_EIO;
8308
        }
8309
        else
8310
        {
8311
            status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
8312
        }
8313
#endif
8314
399
    }
8315
399
    if (status2 != NC_NOERR)
8316
0
    {
8317
#ifdef NCDF_DEBUG
8318
        CPLDebug("GDAL_netCDF", "error opening");
8319
#endif
8320
0
        CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
8321
            // with GDALDataset own mutex.
8322
0
        delete poDS;
8323
0
        CPLAcquireMutex(hNCMutex, 1000.0);
8324
0
        return nullptr;
8325
0
    }
8326
#ifdef NCDF_DEBUG
8327
    CPLDebug("GDAL_netCDF", "got cdfid=%d", cdfid);
8328
#endif
8329
8330
399
#if defined(ENABLE_NCDUMP) && !defined(_WIN32)
8331
    // Try to destroy the temporary file right now on Unix
8332
399
    if (poDS->bFileToDestroyAtClosing)
8333
399
    {
8334
399
        if (VSIUnlink(poDS->osFilename) == 0)
8335
399
        {
8336
399
            poDS->bFileToDestroyAtClosing = false;
8337
399
        }
8338
399
    }
8339
399
#endif
8340
8341
    // Is this a real netCDF file?
8342
399
    int ndims;
8343
399
    int ngatts;
8344
399
    int nvars;
8345
399
    int unlimdimid;
8346
399
    int status = nc_inq(cdfid, &ndims, &nvars, &ngatts, &unlimdimid);
8347
399
    if (status != NC_NOERR)
8348
0
    {
8349
0
        CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
8350
            // with GDALDataset own mutex.
8351
0
        delete poDS;
8352
0
        CPLAcquireMutex(hNCMutex, 1000.0);
8353
0
        return nullptr;
8354
0
    }
8355
8356
    // Get file type from netcdf.
8357
399
    int nTmpFormat = NCDF_FORMAT_NONE;
8358
399
    status = nc_inq_format(cdfid, &nTmpFormat);
8359
399
    if (status != NC_NOERR)
8360
0
    {
8361
0
        NCDF_ERR(status);
8362
0
    }
8363
399
    else
8364
399
    {
8365
399
        CPLDebug("GDAL_netCDF",
8366
399
                 "driver detected file type=%d, libnetcdf detected type=%d",
8367
399
                 poDS->eFormat, nTmpFormat);
8368
399
        if (static_cast<NetCDFFormatEnum>(nTmpFormat) != poDS->eFormat)
8369
0
        {
8370
            // Warn if file detection conflicts with that from libnetcdf
8371
            // except for NC4C, which we have no way of detecting initially.
8372
0
            if (nTmpFormat != NCDF_FORMAT_NC4C &&
8373
0
                !STARTS_WITH(poDS->osFilename, "http://") &&
8374
0
                !STARTS_WITH(poDS->osFilename, "https://"))
8375
0
            {
8376
0
                CPLError(CE_Warning, CPLE_AppDefined,
8377
0
                         "NetCDF driver detected file type=%d, but libnetcdf "
8378
0
                         "detected type=%d",
8379
0
                         poDS->eFormat, nTmpFormat);
8380
0
            }
8381
0
            CPLDebug("GDAL_netCDF", "setting file type to %d, was %d",
8382
0
                     nTmpFormat, poDS->eFormat);
8383
0
            poDS->eFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
8384
0
        }
8385
399
    }
8386
8387
    // Does the request variable exist?
8388
399
    if (bTreatAsSubdataset)
8389
0
    {
8390
0
        int dummy;
8391
0
        if (NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &dummy,
8392
0
                               &dummy) != CE_None)
8393
0
        {
8394
0
            CPLError(CE_Warning, CPLE_AppDefined,
8395
0
                     "%s is a netCDF file, but %s is not a variable.",
8396
0
                     poOpenInfo->pszFilename, osSubdatasetName.c_str());
8397
8398
0
            GDAL_nc_close(cdfid);
8399
0
#ifdef ENABLE_UFFD
8400
0
            NETCDF_UFFD_UNMAP(pCtx);
8401
0
#endif
8402
0
            CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
8403
                // deadlock with GDALDataset own mutex.
8404
0
            delete poDS;
8405
0
            CPLAcquireMutex(hNCMutex, 1000.0);
8406
0
            return nullptr;
8407
0
        }
8408
0
    }
8409
8410
    // Figure out whether or not the listed dataset has support for simple
8411
    // geometries (CF-1.8)
8412
399
    poDS->nCFVersion = nccfdriver::getCFVersion(cdfid);
8413
399
    bool bHasSimpleGeometries = false;  // but not necessarily valid
8414
399
    if (poDS->nCFVersion >= 1.8)
8415
0
    {
8416
0
        bHasSimpleGeometries = poDS->DetectAndFillSGLayers(cdfid);
8417
0
        if (bHasSimpleGeometries)
8418
0
        {
8419
0
            poDS->bSGSupport = true;
8420
0
            poDS->vcdf.enableFullVirtualMode();
8421
0
        }
8422
0
    }
8423
8424
399
    char szConventions[NC_MAX_NAME + 1];
8425
399
    szConventions[0] = '\0';
8426
399
    nc_type nAttype = NC_NAT;
8427
399
    size_t nAttlen = 0;
8428
399
    nc_inq_att(cdfid, NC_GLOBAL, "Conventions", &nAttype, &nAttlen);
8429
399
    if (nAttlen >= sizeof(szConventions) ||
8430
399
        nc_get_att_text(cdfid, NC_GLOBAL, "Conventions", szConventions) !=
8431
399
            NC_NOERR)
8432
364
    {
8433
364
        CPLDebug("GDAL_netCDF", "No UNIDATA NC_GLOBAL:Conventions attribute");
8434
        // Note that 'Conventions' is always capital 'C' in CF spec.
8435
364
    }
8436
35
    else
8437
35
    {
8438
35
        szConventions[nAttlen] = '\0';
8439
35
    }
8440
8441
    // Create band information objects.
8442
399
    CPLDebug("GDAL_netCDF", "var_count = %d", nvars);
8443
8444
    // Create a corresponding GDALDataset.
8445
    // Create Netcdf Subdataset if filename as NETCDF tag.
8446
399
    poDS->cdfid = cdfid;
8447
399
#ifdef ENABLE_UFFD
8448
399
    poDS->pCtx = pCtx;
8449
399
#endif
8450
399
    poDS->eAccess = poOpenInfo->eAccess;
8451
399
    poDS->bDefineMode = false;
8452
8453
399
    poDS->ReadAttributes(cdfid, NC_GLOBAL);
8454
8455
    // Identify coordinate and boundary variables that we should
8456
    // ignore as Raster Bands.
8457
399
    char **papszIgnoreVars = nullptr;
8458
399
    NCDFGetCoordAndBoundVarFullNames(cdfid, &papszIgnoreVars);
8459
    // Filter variables to keep only valid 2+D raster bands and vector fields.
8460
399
    int nRasterVars = 0;
8461
399
    int nIgnoredVars = 0;
8462
399
    int nGroupID = -1;
8463
399
    int nVarID = -1;
8464
8465
399
    std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>
8466
399
        oMap2DDimsToGroupAndVar;
8467
399
    if ((poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
8468
399
        STARTS_WITH(CSLFetchNameValueDef(poDS->papszMetadata,
8469
399
                                         "NC_GLOBAL#mission_name", ""),
8470
399
                    "Sentinel 3") &&
8471
399
        EQUAL(CSLFetchNameValueDef(poDS->papszMetadata,
8472
399
                                   "NC_GLOBAL#altimeter_sensor_name", ""),
8473
399
              "SRAL") &&
8474
399
        EQUAL(CSLFetchNameValueDef(poDS->papszMetadata,
8475
399
                                   "NC_GLOBAL#radiometer_sensor_name", ""),
8476
399
              "MWR"))
8477
0
    {
8478
0
        if (poDS->eAccess == GA_Update)
8479
0
        {
8480
0
            CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
8481
                // deadlock with GDALDataset own mutex.
8482
0
            delete poDS;
8483
0
            return nullptr;
8484
0
        }
8485
0
        poDS->ProcessSentinel3_SRAL_MWR();
8486
0
    }
8487
399
    else
8488
399
    {
8489
399
        poDS->FilterVars(cdfid, (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0,
8490
399
                         (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
8491
399
                             !bHasSimpleGeometries,
8492
399
                         papszIgnoreVars, &nRasterVars, &nGroupID, &nVarID,
8493
399
                         &nIgnoredVars, oMap2DDimsToGroupAndVar);
8494
399
    }
8495
399
    CSLDestroy(papszIgnoreVars);
8496
8497
    // Case where there is no raster variable
8498
399
    if (nRasterVars == 0 && !bTreatAsSubdataset)
8499
399
    {
8500
399
        poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
8501
399
        CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
8502
            // with GDALDataset own mutex.
8503
399
        poDS->TryLoadXML();
8504
        // If the dataset has been opened in raster mode only, exit
8505
399
        if ((poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0 &&
8506
399
            (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) == 0)
8507
0
        {
8508
0
            delete poDS;
8509
0
            poDS = nullptr;
8510
0
        }
8511
        // Otherwise if the dataset is opened in vector mode, that there is
8512
        // no vector layer and we are in read-only, exit too.
8513
399
        else if (poDS->GetLayerCount() == 0 &&
8514
399
                 (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
8515
399
                 poOpenInfo->eAccess == GA_ReadOnly)
8516
376
        {
8517
376
            delete poDS;
8518
376
            poDS = nullptr;
8519
376
        }
8520
399
        CPLAcquireMutex(hNCMutex, 1000.0);
8521
399
        return poDS;
8522
399
    }
8523
8524
    // We have more than one variable with 2 dimensions in the
8525
    // file, then treat this as a subdataset container dataset.
8526
0
    bool bSeveralVariablesAsBands = false;
8527
0
    const bool bListAllArrays = CPLTestBool(
8528
0
        CSLFetchNameValueDef(poDS->papszOpenOptions, "LIST_ALL_ARRAYS", "NO"));
8529
0
    if (bListAllArrays || ((nRasterVars > 1) && !bTreatAsSubdataset))
8530
0
    {
8531
0
        if (CPLFetchBool(poOpenInfo->papszOpenOptions, "VARIABLES_AS_BANDS",
8532
0
                         false) &&
8533
0
            oMap2DDimsToGroupAndVar.size() == 1)
8534
0
        {
8535
0
            std::tie(nGroupID, nVarID) =
8536
0
                oMap2DDimsToGroupAndVar.begin()->second.front();
8537
0
            bSeveralVariablesAsBands = true;
8538
0
        }
8539
0
        else
8540
0
        {
8541
0
            poDS->CreateSubDatasetList(cdfid);
8542
0
            poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
8543
0
            CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
8544
                // deadlock with GDALDataset own mutex.
8545
0
            poDS->TryLoadXML();
8546
0
            CPLAcquireMutex(hNCMutex, 1000.0);
8547
0
            return poDS;
8548
0
        }
8549
0
    }
8550
8551
    // If we are not treating things as a subdataset, then capture
8552
    // the name of the single available variable as the subdataset.
8553
0
    if (!bTreatAsSubdataset)
8554
0
    {
8555
0
        char *pszVarName = nullptr;
8556
0
        NCDF_ERR(NCDFGetVarFullName(nGroupID, nVarID, &pszVarName));
8557
0
        osSubdatasetName = (pszVarName != nullptr ? pszVarName : "");
8558
0
        CPLFree(pszVarName);
8559
0
    }
8560
8561
    // We have ignored at least one variable, so we should report them
8562
    // as subdatasets for reference.
8563
0
    if (nIgnoredVars > 0 && !bTreatAsSubdataset)
8564
0
    {
8565
0
        CPLDebug("GDAL_netCDF",
8566
0
                 "As %d variables were ignored, creating subdataset list "
8567
0
                 "for reference. Variable #%d [%s] is the main variable",
8568
0
                 nIgnoredVars, nVarID, osSubdatasetName.c_str());
8569
0
        poDS->CreateSubDatasetList(cdfid);
8570
0
    }
8571
8572
    // Open the NETCDF subdataset NETCDF:"filename":subdataset.
8573
0
    int var = -1;
8574
0
    NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &nGroupID, &var);
8575
    // Now we can forget the root cdfid and only use the selected group.
8576
0
    cdfid = nGroupID;
8577
0
    int nd = 0;
8578
0
    nc_inq_varndims(cdfid, var, &nd);
8579
8580
0
    poDS->m_anDimIds.resize(nd);
8581
8582
    // X, Y, Z position in array
8583
0
    std::vector<int> anBandDimPos(nd);
8584
8585
0
    nc_inq_vardimid(cdfid, var, poDS->m_anDimIds.data());
8586
8587
    // Check if somebody tried to pass a variable with less than 1D.
8588
0
    if (nd < 1)
8589
0
    {
8590
0
        CPLError(CE_Warning, CPLE_AppDefined,
8591
0
                 "Variable has %d dimension(s) - not supported.", nd);
8592
0
        CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
8593
            // with GDALDataset own mutex.
8594
0
        delete poDS;
8595
0
        CPLAcquireMutex(hNCMutex, 1000.0);
8596
0
        return nullptr;
8597
0
    }
8598
8599
    // CF-1 Convention
8600
    //
8601
    // Dimensions to appear in the relative order T, then Z, then Y,
8602
    // then X  to the file. All other dimensions should, whenever
8603
    // possible, be placed to the left of the spatiotemporal
8604
    // dimensions.
8605
8606
    // Verify that dimensions are in the {T,Z,Y,X} or {T,Z,Y,X} order
8607
    // Ideally we should detect for other ordering and act accordingly
8608
    // Only done if file has Conventions=CF-* and only prints warning
8609
    // To disable set GDAL_NETCDF_VERIFY_DIMS=NO and to use only
8610
    // attributes (not varnames) set GDAL_NETCDF_VERIFY_DIMS=STRICT
8611
0
    const bool bCheckDims =
8612
0
        CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES")) &&
8613
0
        STARTS_WITH_CI(szConventions, "CF");
8614
8615
0
    if (nd >= 2 && bCheckDims)
8616
0
    {
8617
0
        char szDimName1[NC_MAX_NAME + 1] = {};
8618
0
        char szDimName2[NC_MAX_NAME + 1] = {};
8619
0
        status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 1], szDimName1);
8620
0
        NCDF_ERR(status);
8621
0
        status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 2], szDimName2);
8622
0
        NCDF_ERR(status);
8623
0
        if (NCDFIsVarLongitude(cdfid, -1, szDimName1) == false &&
8624
0
            NCDFIsVarProjectionX(cdfid, -1, szDimName1) == false)
8625
0
        {
8626
0
            CPLError(CE_Warning, CPLE_AppDefined,
8627
0
                     "dimension #%d (%s) is not a Longitude/X dimension.",
8628
0
                     nd - 1, szDimName1);
8629
0
        }
8630
0
        if (NCDFIsVarLatitude(cdfid, -1, szDimName2) == false &&
8631
0
            NCDFIsVarProjectionY(cdfid, -1, szDimName2) == false)
8632
0
        {
8633
0
            CPLError(CE_Warning, CPLE_AppDefined,
8634
0
                     "dimension #%d (%s) is not a Latitude/Y dimension.",
8635
0
                     nd - 2, szDimName2);
8636
0
        }
8637
0
        if ((NCDFIsVarLongitude(cdfid, -1, szDimName2) ||
8638
0
             NCDFIsVarProjectionX(cdfid, -1, szDimName2)) &&
8639
0
            (NCDFIsVarLatitude(cdfid, -1, szDimName1) ||
8640
0
             NCDFIsVarProjectionY(cdfid, -1, szDimName1)))
8641
0
        {
8642
0
            poDS->bSwitchedXY = true;
8643
0
        }
8644
0
        if (nd >= 3)
8645
0
        {
8646
0
            char szDimName3[NC_MAX_NAME + 1] = {};
8647
0
            status =
8648
0
                nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 3], szDimName3);
8649
0
            NCDF_ERR(status);
8650
0
            if (nd >= 4)
8651
0
            {
8652
0
                char szDimName4[NC_MAX_NAME + 1] = {};
8653
0
                status =
8654
0
                    nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 4], szDimName4);
8655
0
                NCDF_ERR(status);
8656
0
                if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false)
8657
0
                {
8658
0
                    CPLError(CE_Warning, CPLE_AppDefined,
8659
0
                             "dimension #%d (%s) is not a Vertical dimension.",
8660
0
                             nd - 3, szDimName3);
8661
0
                }
8662
0
                if (NCDFIsVarTimeCoord(cdfid, -1, szDimName4) == false)
8663
0
                {
8664
0
                    CPLError(CE_Warning, CPLE_AppDefined,
8665
0
                             "dimension #%d (%s) is not a Time dimension.",
8666
0
                             nd - 4, szDimName4);
8667
0
                }
8668
0
            }
8669
0
            else
8670
0
            {
8671
0
                if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false &&
8672
0
                    NCDFIsVarTimeCoord(cdfid, -1, szDimName3) == false)
8673
0
                {
8674
0
                    CPLError(CE_Warning, CPLE_AppDefined,
8675
0
                             "dimension #%d (%s) is not a "
8676
0
                             "Time or Vertical dimension.",
8677
0
                             nd - 3, szDimName3);
8678
0
                }
8679
0
            }
8680
0
        }
8681
0
    }
8682
8683
    // For example for EMIT data (https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/),
8684
    // dimension order is downtrack, crosstrack, bands
8685
0
    bool bYXBandOrder = false;
8686
0
    if (nd == 3)
8687
0
    {
8688
0
        char szDimName[NC_MAX_NAME + 1] = {};
8689
0
        status = nc_inq_dimname(cdfid, poDS->m_anDimIds[2], szDimName);
8690
0
        NCDF_ERR(status);
8691
0
        bYXBandOrder =
8692
0
            strcmp(szDimName, "bands") == 0 || strcmp(szDimName, "band") == 0;
8693
0
    }
8694
8695
    // Get X dimensions information.
8696
0
    size_t xdim;
8697
0
    poDS->nXDimID = poDS->m_anDimIds[bYXBandOrder ? 1 : nd - 1];
8698
0
    nc_inq_dimlen(cdfid, poDS->nXDimID, &xdim);
8699
8700
    // Get Y dimension information.
8701
0
    size_t ydim;
8702
0
    if (nd >= 2)
8703
0
    {
8704
0
        poDS->nYDimID = poDS->m_anDimIds[bYXBandOrder ? 0 : nd - 2];
8705
0
        nc_inq_dimlen(cdfid, poDS->nYDimID, &ydim);
8706
0
    }
8707
0
    else
8708
0
    {
8709
0
        poDS->nYDimID = -1;
8710
0
        ydim = 1;
8711
0
    }
8712
8713
0
    if (xdim > INT_MAX || ydim > INT_MAX)
8714
0
    {
8715
0
        CPLError(CE_Failure, CPLE_AppDefined,
8716
0
                 "Invalid raster dimensions: " CPL_FRMT_GUIB "x" CPL_FRMT_GUIB,
8717
0
                 static_cast<GUIntBig>(xdim), static_cast<GUIntBig>(ydim));
8718
0
        CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
8719
            // with GDALDataset own mutex.
8720
0
        delete poDS;
8721
0
        CPLAcquireMutex(hNCMutex, 1000.0);
8722
0
        return nullptr;
8723
0
    }
8724
8725
0
    poDS->nRasterXSize = static_cast<int>(xdim);
8726
0
    poDS->nRasterYSize = static_cast<int>(ydim);
8727
8728
0
    unsigned int k = 0;
8729
0
    for (int j = 0; j < nd; j++)
8730
0
    {
8731
0
        if (poDS->m_anDimIds[j] == poDS->nXDimID)
8732
0
        {
8733
0
            anBandDimPos[0] = j;  // Save Position of XDim
8734
0
            k++;
8735
0
        }
8736
0
        if (poDS->m_anDimIds[j] == poDS->nYDimID)
8737
0
        {
8738
0
            anBandDimPos[1] = j;  // Save Position of YDim
8739
0
            k++;
8740
0
        }
8741
0
    }
8742
    // X and Y Dimension Ids were not found!
8743
0
    if ((nd >= 2 && k != 2) || (nd == 1 && k != 1))
8744
0
    {
8745
0
        CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
8746
            // with GDALDataset own mutex.
8747
0
        delete poDS;
8748
0
        CPLAcquireMutex(hNCMutex, 1000.0);
8749
0
        return nullptr;
8750
0
    }
8751
8752
    // Read Metadata for this variable.
8753
8754
    // Should disable as is also done at band level, except driver needs the
8755
    // variables as metadata (e.g. projection).
8756
0
    poDS->ReadAttributes(cdfid, var);
8757
8758
    // Read Metadata for each dimension.
8759
0
    int *panDimIds = nullptr;
8760
0
    NCDFGetVisibleDims(cdfid, &ndims, &panDimIds);
8761
    // With NetCDF-4 groups panDimIds is not always [0..dim_count-1] like
8762
    // in NetCDF-3 because we see only the dimensions of the selected group
8763
    // and its parents.
8764
    // poDS->papszDimName is indexed by dim IDs, so it must contains all IDs
8765
    // [0..max(panDimIds)], but they are not all useful so we fill names
8766
    // of useless dims with empty string.
8767
0
    if (panDimIds)
8768
0
    {
8769
0
        const int nMaxDimId = *std::max_element(panDimIds, panDimIds + ndims);
8770
0
        std::set<int> oSetExistingDimIds;
8771
0
        for (int i = 0; i < ndims; i++)
8772
0
        {
8773
0
            oSetExistingDimIds.insert(panDimIds[i]);
8774
0
        }
8775
0
        std::set<int> oSetDimIdsUsedByVar;
8776
0
        for (int i = 0; i < nd; i++)
8777
0
        {
8778
0
            oSetDimIdsUsedByVar.insert(poDS->m_anDimIds[i]);
8779
0
        }
8780
0
        for (int j = 0; j <= nMaxDimId; j++)
8781
0
        {
8782
            // Is j dim used?
8783
0
            if (oSetExistingDimIds.find(j) != oSetExistingDimIds.end())
8784
0
            {
8785
                // Useful dim.
8786
0
                char szTemp[NC_MAX_NAME + 1] = {};
8787
0
                status = nc_inq_dimname(cdfid, j, szTemp);
8788
0
                if (status != NC_NOERR)
8789
0
                {
8790
0
                    CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
8791
                        // deadlock with GDALDataset own
8792
                        // mutex.
8793
0
                    delete poDS;
8794
0
                    CPLAcquireMutex(hNCMutex, 1000.0);
8795
0
                    return nullptr;
8796
0
                }
8797
0
                poDS->papszDimName.AddString(szTemp);
8798
8799
0
                if (oSetDimIdsUsedByVar.find(j) != oSetDimIdsUsedByVar.end())
8800
0
                {
8801
0
                    int nDimGroupId = -1;
8802
0
                    int nDimVarId = -1;
8803
0
                    if (NCDFResolveVar(cdfid, poDS->papszDimName[j],
8804
0
                                       &nDimGroupId, &nDimVarId) == CE_None)
8805
0
                    {
8806
0
                        poDS->ReadAttributes(nDimGroupId, nDimVarId);
8807
0
                    }
8808
0
                }
8809
0
            }
8810
0
            else
8811
0
            {
8812
                // Useless dim.
8813
0
                poDS->papszDimName.AddString("");
8814
0
            }
8815
0
        }
8816
0
        CPLFree(panDimIds);
8817
0
    }
8818
8819
    // Set projection info.
8820
0
    std::vector<std::string> aosRemovedMDItems;
8821
0
    if (nd > 1)
8822
0
    {
8823
0
        poDS->SetProjectionFromVar(cdfid, var,
8824
0
                                   /*bReadSRSOnly=*/false,
8825
0
                                   /* pszGivenGM = */ nullptr,
8826
0
                                   /* returnProjStr = */ nullptr,
8827
0
                                   /* sg = */ nullptr, &aosRemovedMDItems);
8828
0
    }
8829
8830
    // Override bottom-up with GDAL_NETCDF_BOTTOMUP config option.
8831
0
    const char *pszValue = CPLGetConfigOption("GDAL_NETCDF_BOTTOMUP", nullptr);
8832
0
    if (pszValue)
8833
0
    {
8834
0
        poDS->bBottomUp = CPLTestBool(pszValue);
8835
0
        CPLDebug("GDAL_netCDF",
8836
0
                 "set bBottomUp=%d because GDAL_NETCDF_BOTTOMUP=%s",
8837
0
                 static_cast<int>(poDS->bBottomUp), pszValue);
8838
0
    }
8839
8840
    // Save non-spatial dimension info.
8841
8842
0
    int *panBandZLev = nullptr;
8843
0
    int nDim = (nd >= 2) ? 2 : 1;
8844
0
    size_t lev_count;
8845
0
    size_t nTotLevCount = 1;
8846
0
    nc_type nType = NC_NAT;
8847
8848
0
    CPLString osExtraDimNames;
8849
8850
0
    if (nd > 2)
8851
0
    {
8852
0
        nDim = 2;
8853
0
        panBandZLev = static_cast<int *>(CPLCalloc(nd - 2, sizeof(int)));
8854
8855
0
        osExtraDimNames = "{";
8856
8857
0
        char szDimName[NC_MAX_NAME + 1] = {};
8858
8859
0
        bool bREPORT_EXTRA_DIM_VALUESWarningEmitted = false;
8860
0
        for (int j = 0; j < nd; j++)
8861
0
        {
8862
0
            if ((poDS->m_anDimIds[j] != poDS->nXDimID) &&
8863
0
                (poDS->m_anDimIds[j] != poDS->nYDimID))
8864
0
            {
8865
0
                nc_inq_dimlen(cdfid, poDS->m_anDimIds[j], &lev_count);
8866
0
                nTotLevCount *= lev_count;
8867
0
                panBandZLev[nDim - 2] = static_cast<int>(lev_count);
8868
0
                anBandDimPos[nDim] = j;  // Save Position of ZDim
8869
                // Save non-spatial dimension names.
8870
0
                if (nc_inq_dimname(cdfid, poDS->m_anDimIds[j], szDimName) ==
8871
0
                    NC_NOERR)
8872
0
                {
8873
0
                    osExtraDimNames += szDimName;
8874
0
                    if (j < nd - 3)
8875
0
                    {
8876
0
                        osExtraDimNames += ",";
8877
0
                    }
8878
8879
0
                    int nIdxGroupID = -1;
8880
0
                    int nIdxVarID = Get1DVariableIndexedByDimension(
8881
0
                        cdfid, poDS->m_anDimIds[j], szDimName, true,
8882
0
                        &nIdxGroupID);
8883
0
                    poDS->m_anExtraDimGroupIds.push_back(nIdxGroupID);
8884
0
                    poDS->m_anExtraDimVarIds.push_back(nIdxVarID);
8885
8886
0
                    if (nIdxVarID >= 0)
8887
0
                    {
8888
0
                        nc_inq_vartype(nIdxGroupID, nIdxVarID, &nType);
8889
0
                        char szExtraDimDef[NC_MAX_NAME + 1];
8890
0
                        snprintf(szExtraDimDef, sizeof(szExtraDimDef),
8891
0
                                 "{%ld,%d}", (long)lev_count, nType);
8892
0
                        char szTemp[NC_MAX_NAME + 32 + 1];
8893
0
                        snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
8894
0
                                 szDimName);
8895
0
                        poDS->papszMetadata = CSLSetNameValue(
8896
0
                            poDS->papszMetadata, szTemp, szExtraDimDef);
8897
8898
                        // Retrieving data for unlimited dimensions might be
8899
                        // costly on network storage, so don't do it.
8900
                        // Each band will capture the value along the extra
8901
                        // dimension in its NETCDF_DIM_xxxx band metadata item
8902
                        // Addresses use case of
8903
                        // https://lists.osgeo.org/pipermail/gdal-dev/2023-May/057209.html
8904
0
                        const bool bIsLocal =
8905
0
                            VSIIsLocal(osFilenameForNCOpen.c_str());
8906
0
                        bool bListDimValues =
8907
0
                            bIsLocal || lev_count == 1 ||
8908
0
                            !NCDFIsUnlimitedDim(poDS->eFormat ==
8909
0
                                                    NCDF_FORMAT_NC4,
8910
0
                                                cdfid, poDS->m_anDimIds[j]);
8911
0
                        const char *pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES =
8912
0
                            CPLGetConfigOption(
8913
0
                                "GDAL_NETCDF_REPORT_EXTRA_DIM_VALUES", nullptr);
8914
0
                        if (pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES)
8915
0
                        {
8916
0
                            bListDimValues = CPLTestBool(
8917
0
                                pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES);
8918
0
                        }
8919
0
                        else if (!bListDimValues && !bIsLocal &&
8920
0
                                 !bREPORT_EXTRA_DIM_VALUESWarningEmitted)
8921
0
                        {
8922
0
                            bREPORT_EXTRA_DIM_VALUESWarningEmitted = true;
8923
0
                            CPLDebug(
8924
0
                                "GDAL_netCDF",
8925
0
                                "Listing extra dimension values is skipped "
8926
0
                                "because this dataset is hosted on a network "
8927
0
                                "file system, and such an operation could be "
8928
0
                                "slow. If you still want to proceed, set the "
8929
0
                                "GDAL_NETCDF_REPORT_EXTRA_DIM_VALUES "
8930
0
                                "configuration option to YES");
8931
0
                        }
8932
0
                        if (bListDimValues)
8933
0
                        {
8934
0
                            char *pszTemp = nullptr;
8935
0
                            if (NCDFGet1DVar(nIdxGroupID, nIdxVarID,
8936
0
                                             &pszTemp) == CE_None)
8937
0
                            {
8938
0
                                snprintf(szTemp, sizeof(szTemp),
8939
0
                                         "NETCDF_DIM_%s_VALUES", szDimName);
8940
0
                                poDS->papszMetadata = CSLSetNameValue(
8941
0
                                    poDS->papszMetadata, szTemp, pszTemp);
8942
0
                                CPLFree(pszTemp);
8943
0
                            }
8944
0
                        }
8945
0
                    }
8946
0
                }
8947
0
                else
8948
0
                {
8949
0
                    poDS->m_anExtraDimGroupIds.push_back(-1);
8950
0
                    poDS->m_anExtraDimVarIds.push_back(-1);
8951
0
                }
8952
8953
0
                nDim++;
8954
0
            }
8955
0
        }
8956
0
        osExtraDimNames += "}";
8957
0
        poDS->papszMetadata = CSLSetNameValue(
8958
0
            poDS->papszMetadata, "NETCDF_DIM_EXTRA", osExtraDimNames);
8959
0
    }
8960
8961
    // Store Metadata.
8962
0
    for (const auto &osStr : aosRemovedMDItems)
8963
0
        poDS->papszMetadata =
8964
0
            CSLSetNameValue(poDS->papszMetadata, osStr.c_str(), nullptr);
8965
8966
0
    poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
8967
8968
    // Create bands.
8969
8970
    // Arbitrary threshold.
8971
0
    int nMaxBandCount =
8972
0
        atoi(CPLGetConfigOption("GDAL_MAX_BAND_COUNT", "32768"));
8973
0
    if (nMaxBandCount <= 0)
8974
0
        nMaxBandCount = 32768;
8975
0
    if (nTotLevCount > static_cast<unsigned int>(nMaxBandCount))
8976
0
    {
8977
0
        CPLError(CE_Warning, CPLE_AppDefined,
8978
0
                 "Limiting number of bands to %d instead of %u", nMaxBandCount,
8979
0
                 static_cast<unsigned int>(nTotLevCount));
8980
0
        nTotLevCount = static_cast<unsigned int>(nMaxBandCount);
8981
0
    }
8982
0
    if (poDS->nRasterXSize == 0 || poDS->nRasterYSize == 0)
8983
0
    {
8984
0
        poDS->nRasterXSize = 0;
8985
0
        poDS->nRasterYSize = 0;
8986
0
        nTotLevCount = 0;
8987
0
        if (poDS->GetLayerCount() == 0)
8988
0
        {
8989
0
            CPLFree(panBandZLev);
8990
0
            CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
8991
                // deadlock with GDALDataset own mutex.
8992
0
            delete poDS;
8993
0
            CPLAcquireMutex(hNCMutex, 1000.0);
8994
0
            return nullptr;
8995
0
        }
8996
0
    }
8997
0
    if (bSeveralVariablesAsBands)
8998
0
    {
8999
0
        const auto &listVariables = oMap2DDimsToGroupAndVar.begin()->second;
9000
0
        for (int iBand = 0; iBand < static_cast<int>(listVariables.size());
9001
0
             ++iBand)
9002
0
        {
9003
0
            int bandVarGroupId = listVariables[iBand].first;
9004
0
            int bandVarId = listVariables[iBand].second;
9005
0
            netCDFRasterBand *poBand = new netCDFRasterBand(
9006
0
                netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, bandVarGroupId,
9007
0
                bandVarId, nDim, 0, nullptr, anBandDimPos.data(), iBand + 1);
9008
0
            poDS->SetBand(iBand + 1, poBand);
9009
0
        }
9010
0
    }
9011
0
    else
9012
0
    {
9013
0
        for (unsigned int lev = 0; lev < nTotLevCount; lev++)
9014
0
        {
9015
0
            netCDFRasterBand *poBand = new netCDFRasterBand(
9016
0
                netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, cdfid, var, nDim,
9017
0
                lev, panBandZLev, anBandDimPos.data(), lev + 1);
9018
0
            poDS->SetBand(lev + 1, poBand);
9019
0
        }
9020
0
    }
9021
9022
0
    if (panBandZLev)
9023
0
        CPLFree(panBandZLev);
9024
    // Handle angular geographic coordinates here
9025
9026
    // Initialize any PAM information.
9027
0
    if (bTreatAsSubdataset)
9028
0
    {
9029
0
        poDS->SetPhysicalFilename(poDS->osFilename);
9030
0
        poDS->SetSubdatasetName(osSubdatasetName);
9031
0
    }
9032
9033
0
    CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock with
9034
        // GDALDataset own mutex.
9035
0
    poDS->TryLoadXML();
9036
9037
0
    if (bTreatAsSubdataset)
9038
0
        poDS->oOvManager.Initialize(poDS, ":::VIRTUAL:::");
9039
0
    else
9040
0
        poDS->oOvManager.Initialize(poDS, poDS->osFilename);
9041
9042
0
    CPLAcquireMutex(hNCMutex, 1000.0);
9043
9044
0
    return poDS;
9045
0
}
9046
9047
/************************************************************************/
9048
/*                            CopyMetadata()                            */
9049
/*                                                                      */
9050
/*      Create a copy of metadata for NC_GLOBAL or a variable           */
9051
/************************************************************************/
9052
9053
static void CopyMetadata(GDALDataset *poSrcDS, GDALRasterBand *poSrcBand,
9054
                         GDALRasterBand *poDstBand, int nCdfId, int CDFVarID,
9055
                         const char *pszPrefix)
9056
0
{
9057
    // Remove the following band meta but set them later from band data.
9058
0
    const char *const papszIgnoreBand[] = {
9059
0
        CF_ADD_OFFSET,  CF_SCALE_FACTOR, "valid_range", "_Unsigned",
9060
0
        NCDF_FillValue, "coordinates",   nullptr};
9061
0
    const char *const papszIgnoreGlobal[] = {"NETCDF_DIM_EXTRA", nullptr};
9062
9063
0
    CSLConstList papszMetadata = nullptr;
9064
0
    if (poSrcDS)
9065
0
    {
9066
0
        papszMetadata = poSrcDS->GetMetadata();
9067
0
    }
9068
0
    else if (poSrcBand)
9069
0
    {
9070
0
        papszMetadata = poSrcBand->GetMetadata();
9071
0
    }
9072
9073
0
    for (const auto &[pszKey, pszValue] : cpl::IterateNameValue(papszMetadata))
9074
0
    {
9075
#ifdef NCDF_DEBUG
9076
        CPLDebug("GDAL_netCDF", "copy metadata [%s]=[%s]", pszKey, pszValue);
9077
#endif
9078
9079
0
        CPLString osMetaName(pszKey);
9080
9081
        // Check for items that match pszPrefix if applicable.
9082
0
        if (pszPrefix && !EQUAL(pszPrefix, ""))
9083
0
        {
9084
            // Remove prefix.
9085
0
            if (STARTS_WITH(osMetaName.c_str(), pszPrefix))
9086
0
            {
9087
0
                osMetaName = osMetaName.substr(strlen(pszPrefix));
9088
0
            }
9089
            // Only copy items that match prefix.
9090
0
            else
9091
0
            {
9092
0
                continue;
9093
0
            }
9094
0
        }
9095
9096
        // Fix various issues with metadata translation.
9097
0
        if (CDFVarID == NC_GLOBAL)
9098
0
        {
9099
            // Do not copy items in papszIgnoreGlobal and NETCDF_DIM_*.
9100
0
            if ((CSLFindString(papszIgnoreGlobal, osMetaName) != -1) ||
9101
0
                (STARTS_WITH(osMetaName, "NETCDF_DIM_")))
9102
0
                continue;
9103
            // Remove NC_GLOBAL prefix for netcdf global Metadata.
9104
0
            else if (STARTS_WITH(osMetaName, "NC_GLOBAL#"))
9105
0
            {
9106
0
                osMetaName = osMetaName.substr(strlen("NC_GLOBAL#"));
9107
0
            }
9108
            // GDAL Metadata renamed as GDAL-[meta].
9109
0
            else if (strstr(osMetaName, "#") == nullptr)
9110
0
            {
9111
0
                osMetaName = "GDAL_" + osMetaName;
9112
0
            }
9113
            // Keep time, lev and depth information for safe-keeping.
9114
            // Time and vertical coordinate handling need improvements.
9115
            /*
9116
            else if( STARTS_WITH(szMetaName, "time#") )
9117
            {
9118
                szMetaName[4] = '-';
9119
            }
9120
            else if( STARTS_WITH(szMetaName, "lev#") )
9121
            {
9122
                szMetaName[3] = '-';
9123
            }
9124
            else if( STARTS_WITH(szMetaName, "depth#") )
9125
            {
9126
                szMetaName[5] = '-';
9127
            }
9128
            */
9129
            // Only copy data without # (previously all data was copied).
9130
0
            if (strstr(osMetaName, "#") != nullptr)
9131
0
                continue;
9132
            // netCDF attributes do not like the '#' character.
9133
            // for( unsigned int h=0; h < strlen(szMetaName) -1 ; h++ ) {
9134
            //     if( szMetaName[h] == '#') szMetaName[h] = '-';
9135
            // }
9136
0
        }
9137
0
        else
9138
0
        {
9139
            // Do not copy varname, stats, NETCDF_DIM_*, nodata
9140
            // and items in papszIgnoreBand.
9141
0
            if (STARTS_WITH(osMetaName, "NETCDF_VARNAME") ||
9142
0
                STARTS_WITH(osMetaName, "STATISTICS_") ||
9143
0
                STARTS_WITH(osMetaName, "NETCDF_DIM_") ||
9144
0
                STARTS_WITH(osMetaName, "missing_value") ||
9145
0
                STARTS_WITH(osMetaName, "_FillValue") ||
9146
0
                CSLFindString(papszIgnoreBand, osMetaName) != -1)
9147
0
                continue;
9148
0
        }
9149
9150
#ifdef NCDF_DEBUG
9151
        CPLDebug("GDAL_netCDF", "copy name=[%s] value=[%s]", osMetaName.c_str(),
9152
                 pszValue);
9153
#endif
9154
0
        if (NCDFPutAttr(nCdfId, CDFVarID, osMetaName, pszValue) != CE_None)
9155
0
        {
9156
0
            CPLDebug("GDAL_netCDF", "NCDFPutAttr(%d, %d, %s, %s) failed",
9157
0
                     nCdfId, CDFVarID, osMetaName.c_str(), pszValue);
9158
0
        }
9159
0
    }
9160
9161
    // Set add_offset and scale_factor here if present.
9162
0
    if (poSrcBand && poDstBand)
9163
0
    {
9164
9165
0
        int bGotAddOffset = FALSE;
9166
0
        const double dfAddOffset = poSrcBand->GetOffset(&bGotAddOffset);
9167
0
        int bGotScale = FALSE;
9168
0
        const double dfScale = poSrcBand->GetScale(&bGotScale);
9169
9170
0
        if (bGotAddOffset && dfAddOffset != 0.0)
9171
0
            poDstBand->SetOffset(dfAddOffset);
9172
0
        if (bGotScale && dfScale != 1.0)
9173
0
            poDstBand->SetScale(dfScale);
9174
0
    }
9175
0
}
9176
9177
/************************************************************************/
9178
/*                            CreateLL()                                */
9179
/*                                                                      */
9180
/*      Shared functionality between netCDFDataset::Create() and        */
9181
/*      netCDF::CreateCopy() for creating netcdf file based on a set of */
9182
/*      options and a configuration.                                    */
9183
/************************************************************************/
9184
9185
netCDFDataset *netCDFDataset::CreateLL(const char *pszFilename, int nXSize,
9186
                                       int nYSize, int nBandsIn,
9187
                                       char **papszOptions)
9188
0
{
9189
0
    if (!((nXSize == 0 && nYSize == 0 && nBandsIn == 0) ||
9190
0
          (nXSize > 0 && nYSize > 0 && nBandsIn > 0)))
9191
0
    {
9192
0
        return nullptr;
9193
0
    }
9194
9195
0
    CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock with
9196
        // GDALDataset own mutex.
9197
0
    netCDFDataset *poDS = new netCDFDataset();
9198
0
    CPLAcquireMutex(hNCMutex, 1000.0);
9199
9200
0
    poDS->nRasterXSize = nXSize;
9201
0
    poDS->nRasterYSize = nYSize;
9202
0
    poDS->eAccess = GA_Update;
9203
0
    poDS->osFilename = pszFilename;
9204
9205
    // From gtiff driver, is this ok?
9206
    /*
9207
    poDS->nBlockXSize = nXSize;
9208
    poDS->nBlockYSize = 1;
9209
    poDS->nBlocksPerBand =
9210
        DIV_ROUND_UP((nYSize, poDS->nBlockYSize))
9211
        * DIV_ROUND_UP((nXSize, poDS->nBlockXSize));
9212
        */
9213
9214
    // process options.
9215
0
    poDS->papszCreationOptions = CSLDuplicate(papszOptions);
9216
0
    poDS->ProcessCreationOptions();
9217
9218
0
    if (poDS->eMultipleLayerBehavior == SEPARATE_FILES)
9219
0
    {
9220
0
        VSIStatBuf sStat;
9221
0
        if (VSIStat(pszFilename, &sStat) == 0)
9222
0
        {
9223
0
            if (!VSI_ISDIR(sStat.st_mode))
9224
0
            {
9225
0
                CPLError(CE_Failure, CPLE_FileIO,
9226
0
                         "%s is an existing file, but not a directory",
9227
0
                         pszFilename);
9228
0
                CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
9229
                    // deadlock with GDALDataset own
9230
                    // mutex.
9231
0
                delete poDS;
9232
0
                CPLAcquireMutex(hNCMutex, 1000.0);
9233
0
                return nullptr;
9234
0
            }
9235
0
        }
9236
0
        else if (VSIMkdir(pszFilename, 0755) != 0)
9237
0
        {
9238
0
            CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s directory",
9239
0
                     pszFilename);
9240
0
            CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
9241
                // deadlock with GDALDataset own mutex.
9242
0
            delete poDS;
9243
0
            CPLAcquireMutex(hNCMutex, 1000.0);
9244
0
            return nullptr;
9245
0
        }
9246
9247
0
        return poDS;
9248
0
    }
9249
    // Create the dataset.
9250
0
    CPLString osFilenameForNCCreate(pszFilename);
9251
#if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
9252
    if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
9253
    {
9254
        char *pszTemp =
9255
            CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
9256
        osFilenameForNCCreate = pszTemp;
9257
        CPLFree(pszTemp);
9258
    }
9259
#endif
9260
9261
#if defined(_WIN32)
9262
    {
9263
        // Works around bug of msys2 netCDF 4.9.0 package where nc_create()
9264
        // crashes
9265
        VSIStatBuf sStat;
9266
        const std::string osDirname =
9267
            CPLGetDirnameSafe(osFilenameForNCCreate.c_str());
9268
        if (VSIStat(osDirname.c_str(), &sStat) != 0)
9269
        {
9270
            CPLError(CE_Failure, CPLE_OpenFailed,
9271
                     "Unable to create netCDF file %s: non existing output "
9272
                     "directory",
9273
                     pszFilename);
9274
            CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
9275
                // deadlock with GDALDataset own mutex.
9276
            delete poDS;
9277
            CPLAcquireMutex(hNCMutex, 1000.0);
9278
            return nullptr;
9279
        }
9280
    }
9281
#endif
9282
9283
0
    int status =
9284
0
        nc_create(osFilenameForNCCreate, poDS->nCreateMode, &(poDS->cdfid));
9285
9286
    // Put into define mode.
9287
0
    poDS->SetDefineMode(true);
9288
9289
0
    if (status != NC_NOERR)
9290
0
    {
9291
0
        CPLError(CE_Failure, CPLE_OpenFailed,
9292
0
                 "Unable to create netCDF file %s (Error code %d): %s .",
9293
0
                 pszFilename, status, nc_strerror(status));
9294
0
        CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
9295
            // with GDALDataset own mutex.
9296
0
        delete poDS;
9297
0
        CPLAcquireMutex(hNCMutex, 1000.0);
9298
0
        return nullptr;
9299
0
    }
9300
9301
    // Define dimensions.
9302
0
    if (nXSize > 0 && nYSize > 0)
9303
0
    {
9304
0
        poDS->papszDimName.AddString(NCDF_DIMNAME_X);
9305
0
        status =
9306
0
            nc_def_dim(poDS->cdfid, NCDF_DIMNAME_X, nXSize, &(poDS->nXDimID));
9307
0
        NCDF_ERR(status);
9308
0
        CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
9309
0
                 poDS->cdfid, NCDF_DIMNAME_X, nXSize, poDS->nXDimID);
9310
9311
0
        poDS->papszDimName.AddString(NCDF_DIMNAME_Y);
9312
0
        status =
9313
0
            nc_def_dim(poDS->cdfid, NCDF_DIMNAME_Y, nYSize, &(poDS->nYDimID));
9314
0
        NCDF_ERR(status);
9315
0
        CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
9316
0
                 poDS->cdfid, NCDF_DIMNAME_Y, nYSize, poDS->nYDimID);
9317
0
    }
9318
9319
0
    return poDS;
9320
0
}
9321
9322
/************************************************************************/
9323
/*                            Create()                                  */
9324
/************************************************************************/
9325
9326
GDALDataset *netCDFDataset::Create(const char *pszFilename, int nXSize,
9327
                                   int nYSize, int nBandsIn, GDALDataType eType,
9328
                                   char **papszOptions)
9329
0
{
9330
0
    CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::Create(%s, ...)",
9331
0
             pszFilename);
9332
9333
0
    const char *legacyCreationOp =
9334
0
        CSLFetchNameValueDef(papszOptions, "GEOMETRY_ENCODING", "CF_1.8");
9335
0
    std::string legacyCreationOp_s = std::string(legacyCreationOp);
9336
9337
    // Check legacy creation op FIRST
9338
9339
0
    bool legacyCreateMode = false;
9340
9341
0
    if (nXSize != 0 || nYSize != 0 || nBandsIn != 0)
9342
0
    {
9343
0
        legacyCreateMode = true;
9344
0
    }
9345
0
    else if (legacyCreationOp_s == "CF_1.8")
9346
0
    {
9347
0
        legacyCreateMode = false;
9348
0
    }
9349
9350
0
    else if (legacyCreationOp_s == "WKT")
9351
0
    {
9352
0
        legacyCreateMode = true;
9353
0
    }
9354
9355
0
    else
9356
0
    {
9357
0
        CPLError(
9358
0
            CE_Failure, CPLE_NotSupported,
9359
0
            "Dataset creation option GEOMETRY_ENCODING=%s is not supported.",
9360
0
            legacyCreationOp_s.c_str());
9361
0
        return nullptr;
9362
0
    }
9363
9364
0
    CPLStringList aosOptions(CSLDuplicate(papszOptions));
9365
0
    if (aosOptions.FetchNameValue("FORMAT") == nullptr &&
9366
0
        (eType == GDT_UInt16 || eType == GDT_UInt32 || eType == GDT_UInt64 ||
9367
0
         eType == GDT_Int64))
9368
0
    {
9369
0
        CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
9370
0
        aosOptions.SetNameValue("FORMAT", "NC4");
9371
0
    }
9372
9373
0
    CPLStringList aosBandNames;
9374
0
    if (const char *pszBandNames = aosOptions.FetchNameValue("BAND_NAMES"))
9375
0
    {
9376
0
        aosBandNames =
9377
0
            CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);
9378
9379
0
        if (aosBandNames.Count() != nBandsIn)
9380
0
        {
9381
0
            CPLError(CE_Failure, CPLE_OpenFailed,
9382
0
                     "Attempted to create netCDF with %d bands but %d names "
9383
0
                     "provided in BAND_NAMES.",
9384
0
                     nBandsIn, aosBandNames.Count());
9385
9386
0
            return nullptr;
9387
0
        }
9388
0
    }
9389
9390
0
    CPLMutexHolderD(&hNCMutex);
9391
9392
0
    auto poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize, nBandsIn,
9393
0
                                        aosOptions.List());
9394
9395
0
    if (!poDS)
9396
0
        return nullptr;
9397
9398
0
    if (!legacyCreateMode)
9399
0
    {
9400
0
        poDS->bSGSupport = true;
9401
0
        poDS->vcdf.enableFullVirtualMode();
9402
0
    }
9403
9404
0
    else
9405
0
    {
9406
0
        poDS->bSGSupport = false;
9407
0
    }
9408
9409
    // Should we write signed or unsigned byte?
9410
    // TODO should this only be done in Create()
9411
0
    poDS->bSignedData = true;
9412
0
    const char *pszValue = CSLFetchNameValueDef(papszOptions, "PIXELTYPE", "");
9413
0
    if (eType == GDT_Byte && !EQUAL(pszValue, "SIGNEDBYTE"))
9414
0
        poDS->bSignedData = false;
9415
9416
    // Add Conventions, GDAL info and history.
9417
0
    if (poDS->cdfid >= 0)
9418
0
    {
9419
0
        const char *CF_Vector_Conv =
9420
0
            poDS->bSGSupport ||
9421
                    // Use of variable length strings require CF-1.8
9422
0
                    EQUAL(aosOptions.FetchNameValueDef("FORMAT", ""), "NC4")
9423
0
                ? NCDF_CONVENTIONS_CF_V1_8
9424
0
                : NCDF_CONVENTIONS_CF_V1_6;
9425
0
        poDS->bWriteGDALVersion = CPLTestBool(
9426
0
            CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
9427
0
        poDS->bWriteGDALHistory = CPLTestBool(
9428
0
            CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
9429
0
        NCDFAddGDALHistory(poDS->cdfid, pszFilename, poDS->bWriteGDALVersion,
9430
0
                           poDS->bWriteGDALHistory, "", "Create",
9431
0
                           (nBandsIn == 0) ? CF_Vector_Conv
9432
0
                                           : GDAL_DEFAULT_NCDF_CONVENTIONS);
9433
0
    }
9434
9435
    // Define bands.
9436
0
    for (int iBand = 1; iBand <= nBandsIn; iBand++)
9437
0
    {
9438
0
        const char *pszBandName =
9439
0
            aosBandNames.empty() ? nullptr : aosBandNames[iBand - 1];
9440
9441
0
        poDS->SetBand(iBand, new netCDFRasterBand(
9442
0
                                 netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS,
9443
0
                                 eType, iBand, poDS->bSignedData, pszBandName));
9444
0
    }
9445
9446
0
    CPLDebug("GDAL_netCDF", "netCDFDataset::Create(%s, ...) done", pszFilename);
9447
    // Return same dataset.
9448
0
    return poDS;
9449
0
}
9450
9451
template <class T>
9452
static CPLErr NCDFCopyBand(GDALRasterBand *poSrcBand, GDALRasterBand *poDstBand,
9453
                           int nXSize, int nYSize, GDALProgressFunc pfnProgress,
9454
                           void *pProgressData)
9455
0
{
9456
0
    GDALDataType eDT = poSrcBand->GetRasterDataType();
9457
0
    CPLErr eErr = CE_None;
9458
0
    T *patScanline = static_cast<T *>(CPLMalloc(nXSize * sizeof(T)));
9459
9460
0
    for (int iLine = 0; iLine < nYSize && eErr == CE_None; iLine++)
9461
0
    {
9462
0
        eErr = poSrcBand->RasterIO(GF_Read, 0, iLine, nXSize, 1, patScanline,
9463
0
                                   nXSize, 1, eDT, 0, 0, nullptr);
9464
0
        if (eErr != CE_None)
9465
0
        {
9466
0
            CPLDebug(
9467
0
                "GDAL_netCDF",
9468
0
                "NCDFCopyBand(), poSrcBand->RasterIO() returned error code %d",
9469
0
                eErr);
9470
0
        }
9471
0
        else
9472
0
        {
9473
0
            eErr =
9474
0
                poDstBand->RasterIO(GF_Write, 0, iLine, nXSize, 1, patScanline,
9475
0
                                    nXSize, 1, eDT, 0, 0, nullptr);
9476
0
            if (eErr != CE_None)
9477
0
                CPLDebug("GDAL_netCDF",
9478
0
                         "NCDFCopyBand(), poDstBand->RasterIO() returned error "
9479
0
                         "code %d",
9480
0
                         eErr);
9481
0
        }
9482
9483
0
        if (nYSize > 10 && (iLine % (nYSize / 10) == 1))
9484
0
        {
9485
0
            if (!pfnProgress(1.0 * iLine / nYSize, nullptr, pProgressData))
9486
0
            {
9487
0
                eErr = CE_Failure;
9488
0
                CPLError(CE_Failure, CPLE_UserInterrupt,
9489
0
                         "User terminated CreateCopy()");
9490
0
            }
9491
0
        }
9492
0
    }
9493
9494
0
    CPLFree(patScanline);
9495
9496
0
    pfnProgress(1.0, nullptr, pProgressData);
9497
9498
0
    return eErr;
9499
0
}
Unexecuted instantiation: netcdfdataset.cpp:CPLErr NCDFCopyBand<unsigned char>(GDALRasterBand*, GDALRasterBand*, int, int, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: netcdfdataset.cpp:CPLErr NCDFCopyBand<signed char>(GDALRasterBand*, GDALRasterBand*, int, int, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: netcdfdataset.cpp:CPLErr NCDFCopyBand<short>(GDALRasterBand*, GDALRasterBand*, int, int, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: netcdfdataset.cpp:CPLErr NCDFCopyBand<unsigned short>(GDALRasterBand*, GDALRasterBand*, int, int, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: netcdfdataset.cpp:CPLErr NCDFCopyBand<unsigned int>(GDALRasterBand*, GDALRasterBand*, int, int, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: netcdfdataset.cpp:CPLErr NCDFCopyBand<int>(GDALRasterBand*, GDALRasterBand*, int, int, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: netcdfdataset.cpp:CPLErr NCDFCopyBand<unsigned long>(GDALRasterBand*, GDALRasterBand*, int, int, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: netcdfdataset.cpp:CPLErr NCDFCopyBand<long>(GDALRasterBand*, GDALRasterBand*, int, int, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: netcdfdataset.cpp:CPLErr NCDFCopyBand<float>(GDALRasterBand*, GDALRasterBand*, int, int, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: netcdfdataset.cpp:CPLErr NCDFCopyBand<double>(GDALRasterBand*, GDALRasterBand*, int, int, int (*)(double, char const*, void*), void*)
9500
9501
/************************************************************************/
9502
/*                            CreateCopy()                              */
9503
/************************************************************************/
9504
9505
GDALDataset *
9506
netCDFDataset::CreateCopy(const char *pszFilename, GDALDataset *poSrcDS,
9507
                          CPL_UNUSED int bStrict, char **papszOptions,
9508
                          GDALProgressFunc pfnProgress, void *pProgressData)
9509
0
{
9510
0
    CPLMutexHolderD(&hNCMutex);
9511
9512
0
    CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::CreateCopy(%s, ...)",
9513
0
             pszFilename);
9514
9515
0
    if (poSrcDS->GetRootGroup())
9516
0
    {
9517
0
        auto poDrv = GDALDriver::FromHandle(GDALGetDriverByName("netCDF"));
9518
0
        if (poDrv)
9519
0
        {
9520
0
            return poDrv->DefaultCreateCopy(pszFilename, poSrcDS, bStrict,
9521
0
                                            papszOptions, pfnProgress,
9522
0
                                            pProgressData);
9523
0
        }
9524
0
    }
9525
9526
0
    const int nBands = poSrcDS->GetRasterCount();
9527
0
    const int nXSize = poSrcDS->GetRasterXSize();
9528
0
    const int nYSize = poSrcDS->GetRasterYSize();
9529
0
    const char *pszWKT = poSrcDS->GetProjectionRef();
9530
9531
    // Check input bands for errors.
9532
0
    if (nBands == 0)
9533
0
    {
9534
0
        CPLError(CE_Failure, CPLE_NotSupported,
9535
0
                 "NetCDF driver does not support "
9536
0
                 "source dataset with zero band.");
9537
0
        return nullptr;
9538
0
    }
9539
9540
0
    GDALDataType eDT = GDT_Unknown;
9541
0
    GDALRasterBand *poSrcBand = nullptr;
9542
0
    for (int iBand = 1; iBand <= nBands; iBand++)
9543
0
    {
9544
0
        poSrcBand = poSrcDS->GetRasterBand(iBand);
9545
0
        eDT = poSrcBand->GetRasterDataType();
9546
0
        if (eDT == GDT_Unknown || GDALDataTypeIsComplex(eDT))
9547
0
        {
9548
0
            CPLError(CE_Failure, CPLE_NotSupported,
9549
0
                     "NetCDF driver does not support source dataset with band "
9550
0
                     "of complex type.");
9551
0
            return nullptr;
9552
0
        }
9553
0
    }
9554
9555
0
    CPLStringList aosBandNames;
9556
0
    if (const char *pszBandNames =
9557
0
            CSLFetchNameValue(papszOptions, "BAND_NAMES"))
9558
0
    {
9559
0
        aosBandNames =
9560
0
            CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);
9561
9562
0
        if (aosBandNames.Count() != nBands)
9563
0
        {
9564
0
            CPLError(CE_Failure, CPLE_OpenFailed,
9565
0
                     "Attempted to create netCDF with %d bands but %d names "
9566
0
                     "provided in BAND_NAMES.",
9567
0
                     nBands, aosBandNames.Count());
9568
9569
0
            return nullptr;
9570
0
        }
9571
0
    }
9572
9573
0
    if (!pfnProgress(0.0, nullptr, pProgressData))
9574
0
        return nullptr;
9575
9576
    // Same as in Create().
9577
0
    CPLStringList aosOptions(CSLDuplicate(papszOptions));
9578
0
    if (aosOptions.FetchNameValue("FORMAT") == nullptr &&
9579
0
        (eDT == GDT_UInt16 || eDT == GDT_UInt32 || eDT == GDT_UInt64 ||
9580
0
         eDT == GDT_Int64))
9581
0
    {
9582
0
        CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
9583
0
        aosOptions.SetNameValue("FORMAT", "NC4");
9584
0
    }
9585
0
    netCDFDataset *poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize,
9586
0
                                                  nBands, aosOptions.List());
9587
0
    if (!poDS)
9588
0
        return nullptr;
9589
9590
    // Copy global metadata.
9591
    // Add Conventions, GDAL info and history.
9592
0
    CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid, NC_GLOBAL, nullptr);
9593
0
    const bool bWriteGDALVersion = CPLTestBool(
9594
0
        CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
9595
0
    const bool bWriteGDALHistory = CPLTestBool(
9596
0
        CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
9597
0
    NCDFAddGDALHistory(
9598
0
        poDS->cdfid, pszFilename, bWriteGDALVersion, bWriteGDALHistory,
9599
0
        poSrcDS->GetMetadataItem("NC_GLOBAL#history"), "CreateCopy",
9600
0
        poSrcDS->GetMetadataItem("NC_GLOBAL#Conventions"));
9601
9602
0
    pfnProgress(0.1, nullptr, pProgressData);
9603
9604
    // Check for extra dimensions.
9605
0
    int nDim = 2;
9606
0
    char **papszExtraDimNames =
9607
0
        NCDFTokenizeArray(poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
9608
0
    char **papszExtraDimValues = nullptr;
9609
9610
0
    if (papszExtraDimNames != nullptr && CSLCount(papszExtraDimNames) > 0)
9611
0
    {
9612
0
        size_t nDimSizeTot = 1;
9613
        // first make sure dimensions lengths compatible with band count
9614
        // for( int i=0; i<CSLCount(papszExtraDimNames ); i++ ) {
9615
0
        for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--)
9616
0
        {
9617
0
            char szTemp[NC_MAX_NAME + 32 + 1];
9618
0
            snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
9619
0
                     papszExtraDimNames[i]);
9620
0
            papszExtraDimValues =
9621
0
                NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
9622
0
            const size_t nDimSize = atol(papszExtraDimValues[0]);
9623
0
            CSLDestroy(papszExtraDimValues);
9624
0
            nDimSizeTot *= nDimSize;
9625
0
        }
9626
0
        if (nDimSizeTot == (size_t)nBands)
9627
0
        {
9628
0
            nDim = 2 + CSLCount(papszExtraDimNames);
9629
0
        }
9630
0
        else
9631
0
        {
9632
            // if nBands != #bands computed raise a warning
9633
            // just issue a debug message, because it was probably intentional
9634
0
            CPLDebug("GDAL_netCDF",
9635
0
                     "Warning: Number of bands (%d) is not compatible with "
9636
0
                     "dimensions "
9637
0
                     "(total=%ld names=%s)",
9638
0
                     nBands, (long)nDimSizeTot,
9639
0
                     poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
9640
0
            CSLDestroy(papszExtraDimNames);
9641
0
            papszExtraDimNames = nullptr;
9642
0
        }
9643
0
    }
9644
9645
0
    int *panDimIds = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
9646
0
    int *panBandDimPos = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
9647
9648
0
    nc_type nVarType;
9649
0
    int *panBandZLev = nullptr;
9650
0
    int *panDimVarIds = nullptr;
9651
9652
0
    if (nDim > 2)
9653
0
    {
9654
0
        panBandZLev = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
9655
0
        panDimVarIds = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
9656
9657
        // Define all dims.
9658
0
        for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--)
9659
0
        {
9660
0
            poDS->papszDimName.AddString(papszExtraDimNames[i]);
9661
0
            char szTemp[NC_MAX_NAME + 32 + 1];
9662
0
            snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
9663
0
                     papszExtraDimNames[i]);
9664
0
            papszExtraDimValues =
9665
0
                NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
9666
0
            const int nDimSize = papszExtraDimValues && papszExtraDimValues[0]
9667
0
                                     ? atoi(papszExtraDimValues[0])
9668
0
                                     : 0;
9669
            // nc_type is an enum in netcdf-3, needs casting.
9670
0
            nVarType = static_cast<nc_type>(papszExtraDimValues &&
9671
0
                                                    papszExtraDimValues[0] &&
9672
0
                                                    papszExtraDimValues[1]
9673
0
                                                ? atol(papszExtraDimValues[1])
9674
0
                                                : 0);
9675
0
            CSLDestroy(papszExtraDimValues);
9676
0
            panBandZLev[i] = nDimSize;
9677
0
            panBandDimPos[i + 2] = i;  // Save Position of ZDim.
9678
9679
            // Define dim.
9680
0
            int status = nc_def_dim(poDS->cdfid, papszExtraDimNames[i],
9681
0
                                    nDimSize, &(panDimIds[i]));
9682
0
            NCDF_ERR(status);
9683
9684
            // Define dim var.
9685
0
            int anDim[1] = {panDimIds[i]};
9686
0
            status = nc_def_var(poDS->cdfid, papszExtraDimNames[i], nVarType, 1,
9687
0
                                anDim, &(panDimVarIds[i]));
9688
0
            NCDF_ERR(status);
9689
9690
            // Add dim metadata, using global var# items.
9691
0
            snprintf(szTemp, sizeof(szTemp), "%s#", papszExtraDimNames[i]);
9692
0
            CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid,
9693
0
                         panDimVarIds[i], szTemp);
9694
0
        }
9695
0
    }
9696
9697
    // Copy GeoTransform and Projection.
9698
9699
    // Copy geolocation info.
9700
0
    char **papszGeolocationInfo = poSrcDS->GetMetadata("GEOLOCATION");
9701
0
    if (papszGeolocationInfo != nullptr)
9702
0
        poDS->GDALPamDataset::SetMetadata(papszGeolocationInfo, "GEOLOCATION");
9703
9704
    // Copy geotransform.
9705
0
    bool bGotGeoTransform = false;
9706
0
    double adfGeoTransform[6];
9707
0
    CPLErr eErr = poSrcDS->GetGeoTransform(adfGeoTransform);
9708
0
    if (eErr == CE_None)
9709
0
    {
9710
0
        poDS->SetGeoTransform(adfGeoTransform);
9711
        // Disable AddProjectionVars() from being called.
9712
0
        bGotGeoTransform = true;
9713
0
        poDS->m_bHasGeoTransform = false;
9714
0
    }
9715
9716
    // Copy projection.
9717
0
    void *pScaledProgress = nullptr;
9718
0
    if (bGotGeoTransform || (pszWKT && pszWKT[0] != 0))
9719
0
    {
9720
0
        poDS->SetProjection(pszWKT ? pszWKT : "");
9721
9722
        // Now we can call AddProjectionVars() directly.
9723
0
        poDS->m_bHasGeoTransform = bGotGeoTransform;
9724
0
        poDS->AddProjectionVars(true, nullptr, nullptr);
9725
0
        pScaledProgress =
9726
0
            GDALCreateScaledProgress(0.1, 0.25, pfnProgress, pProgressData);
9727
0
        poDS->AddProjectionVars(false, GDALScaledProgress, pScaledProgress);
9728
0
        GDALDestroyScaledProgress(pScaledProgress);
9729
0
    }
9730
0
    else
9731
0
    {
9732
0
        poDS->bBottomUp =
9733
0
            CPL_TO_BOOL(CSLFetchBoolean(papszOptions, "WRITE_BOTTOMUP", TRUE));
9734
0
        if (papszGeolocationInfo)
9735
0
        {
9736
0
            poDS->AddProjectionVars(true, nullptr, nullptr);
9737
0
            poDS->AddProjectionVars(false, nullptr, nullptr);
9738
0
        }
9739
0
    }
9740
9741
    // Save X,Y dim positions.
9742
0
    panDimIds[nDim - 1] = poDS->nXDimID;
9743
0
    panBandDimPos[0] = nDim - 1;
9744
0
    panDimIds[nDim - 2] = poDS->nYDimID;
9745
0
    panBandDimPos[1] = nDim - 2;
9746
9747
    // Write extra dim values - after projection for optimization.
9748
0
    if (nDim > 2)
9749
0
    {
9750
        // Make sure we are in data mode.
9751
0
        static_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
9752
0
        for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--)
9753
0
        {
9754
0
            char szTemp[NC_MAX_NAME + 32 + 1];
9755
0
            snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_VALUES",
9756
0
                     papszExtraDimNames[i]);
9757
0
            if (poSrcDS->GetMetadataItem(szTemp) != nullptr)
9758
0
            {
9759
0
                NCDFPut1DVar(poDS->cdfid, panDimVarIds[i],
9760
0
                             poSrcDS->GetMetadataItem(szTemp));
9761
0
            }
9762
0
        }
9763
0
    }
9764
9765
0
    pfnProgress(0.25, nullptr, pProgressData);
9766
9767
    // Define Bands.
9768
0
    netCDFRasterBand *poBand = nullptr;
9769
0
    int nBandID = -1;
9770
9771
0
    for (int iBand = 1; iBand <= nBands; iBand++)
9772
0
    {
9773
0
        CPLDebug("GDAL_netCDF", "creating band # %d/%d nDim = %d", iBand,
9774
0
                 nBands, nDim);
9775
9776
0
        poSrcBand = poSrcDS->GetRasterBand(iBand);
9777
0
        eDT = poSrcBand->GetRasterDataType();
9778
9779
        // Get var name from NETCDF_VARNAME.
9780
0
        const char *pszNETCDF_VARNAME =
9781
0
            poSrcBand->GetMetadataItem("NETCDF_VARNAME");
9782
0
        char szBandName[NC_MAX_NAME + 1];
9783
0
        if (!aosBandNames.empty())
9784
0
        {
9785
0
            snprintf(szBandName, sizeof(szBandName), "%s",
9786
0
                     aosBandNames[iBand - 1]);
9787
0
        }
9788
0
        else if (pszNETCDF_VARNAME)
9789
0
        {
9790
0
            if (nBands > 1 && papszExtraDimNames == nullptr)
9791
0
                snprintf(szBandName, sizeof(szBandName), "%s%d",
9792
0
                         pszNETCDF_VARNAME, iBand);
9793
0
            else
9794
0
                snprintf(szBandName, sizeof(szBandName), "%s",
9795
0
                         pszNETCDF_VARNAME);
9796
0
        }
9797
0
        else
9798
0
        {
9799
0
            szBandName[0] = '\0';
9800
0
        }
9801
9802
        // Get long_name from <var>#long_name.
9803
0
        const char *pszLongName = "";
9804
0
        if (pszNETCDF_VARNAME)
9805
0
        {
9806
0
            pszLongName =
9807
0
                poSrcDS->GetMetadataItem(std::string(pszNETCDF_VARNAME)
9808
0
                                             .append("#")
9809
0
                                             .append(CF_LNG_NAME)
9810
0
                                             .c_str());
9811
0
            if (!pszLongName)
9812
0
                pszLongName = "";
9813
0
        }
9814
9815
0
        constexpr bool bSignedData = false;
9816
9817
0
        if (nDim > 2)
9818
0
            poBand = new netCDFRasterBand(
9819
0
                netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
9820
0
                bSignedData, szBandName, pszLongName, nBandID, nDim, iBand - 1,
9821
0
                panBandZLev, panBandDimPos, panDimIds);
9822
0
        else
9823
0
            poBand = new netCDFRasterBand(
9824
0
                netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
9825
0
                bSignedData, szBandName, pszLongName);
9826
9827
0
        poDS->SetBand(iBand, poBand);
9828
9829
        // Set nodata value, if any.
9830
0
        GDALCopyNoDataValue(poBand, poSrcBand);
9831
9832
        // Copy Metadata for band.
9833
0
        CopyMetadata(nullptr, poSrcDS->GetRasterBand(iBand), poBand,
9834
0
                     poDS->cdfid, poBand->nZId);
9835
9836
        // If more than 2D pass the first band's netcdf var ID to subsequent
9837
        // bands.
9838
0
        if (nDim > 2)
9839
0
            nBandID = poBand->nZId;
9840
0
    }
9841
9842
    // Write projection variable to band variable.
9843
0
    poDS->AddGridMappingRef();
9844
9845
0
    pfnProgress(0.5, nullptr, pProgressData);
9846
9847
    // Write bands.
9848
9849
    // Make sure we are in data mode.
9850
0
    poDS->SetDefineMode(false);
9851
9852
0
    double dfTemp = 0.5;
9853
9854
0
    eErr = CE_None;
9855
9856
0
    for (int iBand = 1; iBand <= nBands && eErr == CE_None; iBand++)
9857
0
    {
9858
0
        const double dfTemp2 = dfTemp + 0.4 / nBands;
9859
0
        pScaledProgress = GDALCreateScaledProgress(dfTemp, dfTemp2, pfnProgress,
9860
0
                                                   pProgressData);
9861
0
        dfTemp = dfTemp2;
9862
9863
0
        CPLDebug("GDAL_netCDF", "copying band data # %d/%d ", iBand, nBands);
9864
9865
0
        poSrcBand = poSrcDS->GetRasterBand(iBand);
9866
0
        eDT = poSrcBand->GetRasterDataType();
9867
9868
0
        GDALRasterBand *poDstBand = poDS->GetRasterBand(iBand);
9869
9870
        // Copy band data.
9871
0
        if (eDT == GDT_Byte)
9872
0
        {
9873
0
            CPLDebug("GDAL_netCDF", "GByte Band#%d", iBand);
9874
0
            eErr = NCDFCopyBand<GByte>(poSrcBand, poDstBand, nXSize, nYSize,
9875
0
                                       GDALScaledProgress, pScaledProgress);
9876
0
        }
9877
0
        else if (eDT == GDT_Int8)
9878
0
        {
9879
0
            CPLDebug("GDAL_netCDF", "GInt8 Band#%d", iBand);
9880
0
            eErr = NCDFCopyBand<GInt8>(poSrcBand, poDstBand, nXSize, nYSize,
9881
0
                                       GDALScaledProgress, pScaledProgress);
9882
0
        }
9883
0
        else if (eDT == GDT_UInt16)
9884
0
        {
9885
0
            CPLDebug("GDAL_netCDF", "GUInt16 Band#%d", iBand);
9886
0
            eErr = NCDFCopyBand<GInt16>(poSrcBand, poDstBand, nXSize, nYSize,
9887
0
                                        GDALScaledProgress, pScaledProgress);
9888
0
        }
9889
0
        else if (eDT == GDT_Int16)
9890
0
        {
9891
0
            CPLDebug("GDAL_netCDF", "GInt16 Band#%d", iBand);
9892
0
            eErr = NCDFCopyBand<GUInt16>(poSrcBand, poDstBand, nXSize, nYSize,
9893
0
                                         GDALScaledProgress, pScaledProgress);
9894
0
        }
9895
0
        else if (eDT == GDT_UInt32)
9896
0
        {
9897
0
            CPLDebug("GDAL_netCDF", "GUInt32 Band#%d", iBand);
9898
0
            eErr = NCDFCopyBand<GUInt32>(poSrcBand, poDstBand, nXSize, nYSize,
9899
0
                                         GDALScaledProgress, pScaledProgress);
9900
0
        }
9901
0
        else if (eDT == GDT_Int32)
9902
0
        {
9903
0
            CPLDebug("GDAL_netCDF", "GInt32 Band#%d", iBand);
9904
0
            eErr = NCDFCopyBand<GInt32>(poSrcBand, poDstBand, nXSize, nYSize,
9905
0
                                        GDALScaledProgress, pScaledProgress);
9906
0
        }
9907
0
        else if (eDT == GDT_UInt64)
9908
0
        {
9909
0
            CPLDebug("GDAL_netCDF", "GUInt64 Band#%d", iBand);
9910
0
            eErr = NCDFCopyBand<std::uint64_t>(poSrcBand, poDstBand, nXSize,
9911
0
                                               nYSize, GDALScaledProgress,
9912
0
                                               pScaledProgress);
9913
0
        }
9914
0
        else if (eDT == GDT_Int64)
9915
0
        {
9916
0
            CPLDebug("GDAL_netCDF", "GInt64 Band#%d", iBand);
9917
0
            eErr =
9918
0
                NCDFCopyBand<std::int64_t>(poSrcBand, poDstBand, nXSize, nYSize,
9919
0
                                           GDALScaledProgress, pScaledProgress);
9920
0
        }
9921
0
        else if (eDT == GDT_Float32)
9922
0
        {
9923
0
            CPLDebug("GDAL_netCDF", "float Band#%d", iBand);
9924
0
            eErr = NCDFCopyBand<float>(poSrcBand, poDstBand, nXSize, nYSize,
9925
0
                                       GDALScaledProgress, pScaledProgress);
9926
0
        }
9927
0
        else if (eDT == GDT_Float64)
9928
0
        {
9929
0
            CPLDebug("GDAL_netCDF", "double Band#%d", iBand);
9930
0
            eErr = NCDFCopyBand<double>(poSrcBand, poDstBand, nXSize, nYSize,
9931
0
                                        GDALScaledProgress, pScaledProgress);
9932
0
        }
9933
0
        else
9934
0
        {
9935
0
            CPLError(CE_Failure, CPLE_NotSupported,
9936
0
                     "The NetCDF driver does not support GDAL data type %d",
9937
0
                     eDT);
9938
0
        }
9939
9940
0
        GDALDestroyScaledProgress(pScaledProgress);
9941
0
    }
9942
9943
0
    delete (poDS);
9944
9945
0
    CPLFree(panDimIds);
9946
0
    CPLFree(panBandDimPos);
9947
0
    CPLFree(panBandZLev);
9948
0
    CPLFree(panDimVarIds);
9949
0
    if (papszExtraDimNames)
9950
0
        CSLDestroy(papszExtraDimNames);
9951
9952
0
    if (eErr != CE_None)
9953
0
        return nullptr;
9954
9955
0
    pfnProgress(0.95, nullptr, pProgressData);
9956
9957
    // Re-open dataset so we can return it.
9958
0
    CPLStringList aosOpenOptions;
9959
0
    aosOpenOptions.AddString("VARIABLES_AS_BANDS=YES");
9960
0
    GDALOpenInfo oOpenInfo(pszFilename, GA_Update);
9961
0
    oOpenInfo.nOpenFlags = GDAL_OF_RASTER | GDAL_OF_UPDATE;
9962
0
    oOpenInfo.papszOpenOptions = aosOpenOptions.List();
9963
0
    auto poRetDS = Open(&oOpenInfo);
9964
9965
    // PAM cloning is disabled. See bug #4244.
9966
    // if( poDS )
9967
    //     poDS->CloneInfo(poSrcDS, GCIF_PAM_DEFAULT);
9968
9969
0
    pfnProgress(1.0, nullptr, pProgressData);
9970
9971
0
    return poRetDS;
9972
0
}
9973
9974
// Note: some logic depends on bIsProjected and bIsGeoGraphic.
9975
// May not be known when Create() is called, see AddProjectionVars().
9976
void netCDFDataset::ProcessCreationOptions()
9977
0
{
9978
0
    const char *pszConfig =
9979
0
        CSLFetchNameValue(papszCreationOptions, "CONFIG_FILE");
9980
0
    if (pszConfig != nullptr)
9981
0
    {
9982
0
        if (oWriterConfig.Parse(pszConfig))
9983
0
        {
9984
            // Override dataset creation options from the config file
9985
0
            std::map<CPLString, CPLString>::iterator oIter;
9986
0
            for (oIter = oWriterConfig.m_oDatasetCreationOptions.begin();
9987
0
                 oIter != oWriterConfig.m_oDatasetCreationOptions.end();
9988
0
                 ++oIter)
9989
0
            {
9990
0
                papszCreationOptions = CSLSetNameValue(
9991
0
                    papszCreationOptions, oIter->first, oIter->second);
9992
0
            }
9993
0
        }
9994
0
    }
9995
9996
    // File format.
9997
0
    eFormat = NCDF_FORMAT_NC;
9998
0
    const char *pszValue = CSLFetchNameValue(papszCreationOptions, "FORMAT");
9999
0
    if (pszValue != nullptr)
10000
0
    {
10001
0
        if (EQUAL(pszValue, "NC"))
10002
0
        {
10003
0
            eFormat = NCDF_FORMAT_NC;
10004
0
        }
10005
0
#ifdef NETCDF_HAS_NC2
10006
0
        else if (EQUAL(pszValue, "NC2"))
10007
0
        {
10008
0
            eFormat = NCDF_FORMAT_NC2;
10009
0
        }
10010
0
#endif
10011
0
        else if (EQUAL(pszValue, "NC4"))
10012
0
        {
10013
0
            eFormat = NCDF_FORMAT_NC4;
10014
0
        }
10015
0
        else if (EQUAL(pszValue, "NC4C"))
10016
0
        {
10017
0
            eFormat = NCDF_FORMAT_NC4C;
10018
0
        }
10019
0
        else
10020
0
        {
10021
0
            CPLError(CE_Failure, CPLE_NotSupported,
10022
0
                     "FORMAT=%s in not supported, using the default NC format.",
10023
0
                     pszValue);
10024
0
        }
10025
0
    }
10026
10027
    // COMPRESS option.
10028
0
    pszValue = CSLFetchNameValue(papszCreationOptions, "COMPRESS");
10029
0
    if (pszValue != nullptr)
10030
0
    {
10031
0
        if (EQUAL(pszValue, "NONE"))
10032
0
        {
10033
0
            eCompress = NCDF_COMPRESS_NONE;
10034
0
        }
10035
0
        else if (EQUAL(pszValue, "DEFLATE"))
10036
0
        {
10037
0
            eCompress = NCDF_COMPRESS_DEFLATE;
10038
0
            if (!((eFormat == NCDF_FORMAT_NC4) ||
10039
0
                  (eFormat == NCDF_FORMAT_NC4C)))
10040
0
            {
10041
0
                CPLError(CE_Warning, CPLE_IllegalArg,
10042
0
                         "NOTICE: Format set to NC4C because compression is "
10043
0
                         "set to DEFLATE.");
10044
0
                eFormat = NCDF_FORMAT_NC4C;
10045
0
            }
10046
0
        }
10047
0
        else
10048
0
        {
10049
0
            CPLError(CE_Failure, CPLE_NotSupported,
10050
0
                     "COMPRESS=%s is not supported.", pszValue);
10051
0
        }
10052
0
    }
10053
10054
    // ZLEVEL option.
10055
0
    pszValue = CSLFetchNameValue(papszCreationOptions, "ZLEVEL");
10056
0
    if (pszValue != nullptr)
10057
0
    {
10058
0
        nZLevel = atoi(pszValue);
10059
0
        if (!(nZLevel >= 1 && nZLevel <= 9))
10060
0
        {
10061
0
            CPLError(CE_Warning, CPLE_IllegalArg,
10062
0
                     "ZLEVEL=%s value not recognised, ignoring.", pszValue);
10063
0
            nZLevel = NCDF_DEFLATE_LEVEL;
10064
0
        }
10065
0
    }
10066
10067
    // CHUNKING option.
10068
0
    bChunking =
10069
0
        CPL_TO_BOOL(CSLFetchBoolean(papszCreationOptions, "CHUNKING", TRUE));
10070
10071
    // MULTIPLE_LAYERS option.
10072
0
    const char *pszMultipleLayerBehavior =
10073
0
        CSLFetchNameValueDef(papszCreationOptions, "MULTIPLE_LAYERS", "NO");
10074
0
    const char *pszGeometryEnc = CSLFetchNameValueDef(
10075
0
        papszCreationOptions, "GEOMETRY_ENCODING", "CF_1.8");
10076
0
    if (EQUAL(pszMultipleLayerBehavior, "NO") ||
10077
0
        EQUAL(pszGeometryEnc, "CF_1.8"))
10078
0
    {
10079
0
        eMultipleLayerBehavior = SINGLE_LAYER;
10080
0
    }
10081
0
    else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_FILES"))
10082
0
    {
10083
0
        eMultipleLayerBehavior = SEPARATE_FILES;
10084
0
    }
10085
0
    else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_GROUPS"))
10086
0
    {
10087
0
        if (eFormat == NCDF_FORMAT_NC4)
10088
0
        {
10089
0
            eMultipleLayerBehavior = SEPARATE_GROUPS;
10090
0
        }
10091
0
        else
10092
0
        {
10093
0
            CPLError(CE_Warning, CPLE_IllegalArg,
10094
0
                     "MULTIPLE_LAYERS=%s is recognised only with FORMAT=NC4",
10095
0
                     pszMultipleLayerBehavior);
10096
0
        }
10097
0
    }
10098
0
    else
10099
0
    {
10100
0
        CPLError(CE_Warning, CPLE_IllegalArg,
10101
0
                 "MULTIPLE_LAYERS=%s not recognised", pszMultipleLayerBehavior);
10102
0
    }
10103
10104
    // Set nCreateMode based on eFormat.
10105
0
    switch (eFormat)
10106
0
    {
10107
0
#ifdef NETCDF_HAS_NC2
10108
0
        case NCDF_FORMAT_NC2:
10109
0
            nCreateMode = NC_CLOBBER | NC_64BIT_OFFSET;
10110
0
            break;
10111
0
#endif
10112
0
        case NCDF_FORMAT_NC4:
10113
0
            nCreateMode = NC_CLOBBER | NC_NETCDF4;
10114
0
            break;
10115
0
        case NCDF_FORMAT_NC4C:
10116
0
            nCreateMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
10117
0
            break;
10118
0
        case NCDF_FORMAT_NC:
10119
0
        default:
10120
0
            nCreateMode = NC_CLOBBER;
10121
0
            break;
10122
0
    }
10123
10124
0
    CPLDebug("GDAL_netCDF", "file options: format=%d compress=%d zlevel=%d",
10125
0
             eFormat, eCompress, nZLevel);
10126
0
}
10127
10128
int netCDFDataset::DefVarDeflate(int nVarId, bool bChunkingArg)
10129
0
{
10130
0
    if (eCompress == NCDF_COMPRESS_DEFLATE)
10131
0
    {
10132
        // Must set chunk size to avoid huge performance hit (set
10133
        // bChunkingArg=TRUE)
10134
        // perhaps another solution it to change the chunk cache?
10135
        // http://www.unidata.ucar.edu/software/netcdf/docs/netcdf.html#Chunk-Cache
10136
        // TODO: make sure this is okay.
10137
0
        CPLDebug("GDAL_netCDF", "DefVarDeflate(%d, %d) nZlevel=%d", nVarId,
10138
0
                 static_cast<int>(bChunkingArg), nZLevel);
10139
10140
0
        int status = nc_def_var_deflate(cdfid, nVarId, 1, 1, nZLevel);
10141
0
        NCDF_ERR(status);
10142
10143
0
        if (status == NC_NOERR && bChunkingArg && bChunking)
10144
0
        {
10145
            // set chunking to be 1 for all dims, except X dim
10146
            // size_t chunksize[] = { 1, (size_t)nRasterXSize };
10147
0
            size_t chunksize[MAX_NC_DIMS];
10148
0
            int nd;
10149
0
            nc_inq_varndims(cdfid, nVarId, &nd);
10150
0
            chunksize[0] = (size_t)1;
10151
0
            chunksize[1] = (size_t)1;
10152
0
            for (int i = 2; i < nd; i++)
10153
0
                chunksize[i] = (size_t)1;
10154
0
            chunksize[nd - 1] = (size_t)nRasterXSize;
10155
10156
            // Config options just for testing purposes
10157
0
            const char *pszBlockXSize =
10158
0
                CPLGetConfigOption("BLOCKXSIZE", nullptr);
10159
0
            if (pszBlockXSize)
10160
0
                chunksize[nd - 1] = (size_t)atoi(pszBlockXSize);
10161
10162
0
            const char *pszBlockYSize =
10163
0
                CPLGetConfigOption("BLOCKYSIZE", nullptr);
10164
0
            if (nd >= 2 && pszBlockYSize)
10165
0
                chunksize[nd - 2] = (size_t)atoi(pszBlockYSize);
10166
10167
0
            CPLDebug("GDAL_netCDF",
10168
0
                     "DefVarDeflate() chunksize={%ld, %ld} chunkX=%ld nd=%d",
10169
0
                     (long)chunksize[0], (long)chunksize[1],
10170
0
                     (long)chunksize[nd - 1], nd);
10171
#ifdef NCDF_DEBUG
10172
            for (int i = 0; i < nd; i++)
10173
                CPLDebug("GDAL_netCDF", "DefVarDeflate() chunk[%d]=%ld", i,
10174
                         chunksize[i]);
10175
#endif
10176
10177
0
            status = nc_def_var_chunking(cdfid, nVarId, NC_CHUNKED, chunksize);
10178
0
            NCDF_ERR(status);
10179
0
        }
10180
0
        else
10181
0
        {
10182
0
            CPLDebug("GDAL_netCDF", "chunksize not set");
10183
0
        }
10184
0
        return status;
10185
0
    }
10186
0
    return NC_NOERR;
10187
0
}
10188
10189
/************************************************************************/
10190
/*                           NCDFUnloadDriver()                         */
10191
/************************************************************************/
10192
10193
static void NCDFUnloadDriver(CPL_UNUSED GDALDriver *poDriver)
10194
0
{
10195
0
    if (hNCMutex != nullptr)
10196
0
        CPLDestroyMutex(hNCMutex);
10197
0
    hNCMutex = nullptr;
10198
0
}
10199
10200
/************************************************************************/
10201
/*                          GDALRegister_netCDF()                       */
10202
/************************************************************************/
10203
10204
class GDALnetCDFDriver final : public GDALDriver
10205
{
10206
  public:
10207
2
    GDALnetCDFDriver() = default;
10208
10209
    const char *GetMetadataItem(const char *pszName,
10210
                                const char *pszDomain) override;
10211
10212
    char **GetMetadata(const char *pszDomain) override
10213
0
    {
10214
0
        std::lock_guard oLock(m_oMutex);
10215
0
        InitializeDCAPVirtualIO();
10216
0
        return GDALDriver::GetMetadata(pszDomain);
10217
0
    }
10218
10219
  private:
10220
    std::mutex m_oMutex{};
10221
    bool m_bInitialized = false;
10222
10223
    void InitializeDCAPVirtualIO()
10224
0
    {
10225
0
        if (!m_bInitialized)
10226
0
        {
10227
0
            m_bInitialized = true;
10228
10229
0
#ifdef ENABLE_UFFD
10230
0
            if (CPLIsUserFaultMappingSupported())
10231
0
            {
10232
0
                SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
10233
0
            }
10234
0
#endif
10235
0
        }
10236
0
    }
10237
};
10238
10239
const char *GDALnetCDFDriver::GetMetadataItem(const char *pszName,
10240
                                              const char *pszDomain)
10241
989k
{
10242
989k
    std::lock_guard oLock(m_oMutex);
10243
989k
    if (EQUAL(pszName, GDAL_DCAP_VIRTUALIO))
10244
0
    {
10245
0
        InitializeDCAPVirtualIO();
10246
0
    }
10247
989k
    return GDALDriver::GetMetadataItem(pszName, pszDomain);
10248
989k
}
10249
10250
void GDALRegister_netCDF()
10251
10252
2
{
10253
2
    if (!GDAL_CHECK_VERSION("netCDF driver"))
10254
0
        return;
10255
10256
2
    if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
10257
0
        return;
10258
10259
2
    GDALDriver *poDriver = new GDALnetCDFDriver();
10260
2
    netCDFDriverSetCommonMetadata(poDriver);
10261
10262
2
    poDriver->SetMetadataItem("NETCDF_CONVENTIONS",
10263
2
                              GDAL_DEFAULT_NCDF_CONVENTIONS);
10264
2
    poDriver->SetMetadataItem("NETCDF_VERSION", nc_inq_libvers());
10265
10266
    // Set pfns and register driver.
10267
2
    poDriver->pfnOpen = netCDFDataset::Open;
10268
2
    poDriver->pfnCreateCopy = netCDFDataset::CreateCopy;
10269
2
    poDriver->pfnCreate = netCDFDataset::Create;
10270
2
    poDriver->pfnCreateMultiDimensional = netCDFDataset::CreateMultiDimensional;
10271
2
    poDriver->pfnUnloadDriver = NCDFUnloadDriver;
10272
10273
2
    GetGDALDriverManager()->RegisterDriver(poDriver);
10274
2
}
10275
10276
/************************************************************************/
10277
/*                          New functions                               */
10278
/************************************************************************/
10279
10280
/* Test for GDAL version string >= target */
10281
static bool NCDFIsGDALVersionGTE(const char *pszVersion, int nTarget)
10282
0
{
10283
10284
    // Valid strings are "GDAL 1.9dev, released 2011/01/18" and "GDAL 1.8.1 ".
10285
0
    if (pszVersion == nullptr || EQUAL(pszVersion, ""))
10286
0
        return false;
10287
0
    else if (!STARTS_WITH_CI(pszVersion, "GDAL "))
10288
0
        return false;
10289
    // 2.0dev of 2011/12/29 has been later renamed as 1.10dev.
10290
0
    else if (EQUAL("GDAL 2.0dev, released 2011/12/29", pszVersion))
10291
0
        return nTarget <= GDAL_COMPUTE_VERSION(1, 10, 0);
10292
0
    else if (STARTS_WITH_CI(pszVersion, "GDAL 1.9dev"))
10293
0
        return nTarget <= 1900;
10294
0
    else if (STARTS_WITH_CI(pszVersion, "GDAL 1.8dev"))
10295
0
        return nTarget <= 1800;
10296
10297
0
    char **papszTokens = CSLTokenizeString2(pszVersion + 5, ".", 0);
10298
10299
0
    int nVersions[] = {0, 0, 0, 0};
10300
0
    for (int iToken = 0; papszTokens && iToken < 4 && papszTokens[iToken];
10301
0
         iToken++)
10302
0
    {
10303
0
        nVersions[iToken] = atoi(papszTokens[iToken]);
10304
0
        if (nVersions[iToken] < 0)
10305
0
            nVersions[iToken] = 0;
10306
0
        else if (nVersions[iToken] > 99)
10307
0
            nVersions[iToken] = 99;
10308
0
    }
10309
10310
0
    int nVersion = 0;
10311
0
    if (nVersions[0] > 1 || nVersions[1] >= 10)
10312
0
        nVersion =
10313
0
            GDAL_COMPUTE_VERSION(nVersions[0], nVersions[1], nVersions[2]);
10314
0
    else
10315
0
        nVersion = nVersions[0] * 1000 + nVersions[1] * 100 +
10316
0
                   nVersions[2] * 10 + nVersions[3];
10317
10318
0
    CSLDestroy(papszTokens);
10319
0
    return nTarget <= nVersion;
10320
0
}
10321
10322
// Add Conventions, GDAL version and history.
10323
static void NCDFAddGDALHistory(int fpImage, const char *pszFilename,
10324
                               bool bWriteGDALVersion, bool bWriteGDALHistory,
10325
                               const char *pszOldHist,
10326
                               const char *pszFunctionName,
10327
                               const char *pszCFVersion)
10328
0
{
10329
0
    if (pszCFVersion == nullptr)
10330
0
    {
10331
0
        pszCFVersion = GDAL_DEFAULT_NCDF_CONVENTIONS;
10332
0
    }
10333
0
    int status = nc_put_att_text(fpImage, NC_GLOBAL, "Conventions",
10334
0
                                 strlen(pszCFVersion), pszCFVersion);
10335
0
    NCDF_ERR(status);
10336
10337
0
    if (bWriteGDALVersion)
10338
0
    {
10339
0
        const char *pszNCDF_GDAL = GDALVersionInfo("--version");
10340
0
        status = nc_put_att_text(fpImage, NC_GLOBAL, "GDAL",
10341
0
                                 strlen(pszNCDF_GDAL), pszNCDF_GDAL);
10342
0
        NCDF_ERR(status);
10343
0
    }
10344
10345
0
    if (bWriteGDALHistory)
10346
0
    {
10347
        // Add history.
10348
0
        CPLString osTmp;
10349
#ifdef GDAL_SET_CMD_LINE_DEFINED_TMP
10350
        if (!EQUAL(GDALGetCmdLine(), ""))
10351
            osTmp = GDALGetCmdLine();
10352
        else
10353
            osTmp =
10354
                CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
10355
#else
10356
0
        osTmp = CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
10357
0
#endif
10358
10359
0
        NCDFAddHistory(fpImage, osTmp.c_str(), pszOldHist);
10360
0
    }
10361
0
    else if (pszOldHist != nullptr)
10362
0
    {
10363
0
        status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
10364
0
                                 strlen(pszOldHist), pszOldHist);
10365
0
        NCDF_ERR(status);
10366
0
    }
10367
0
}
10368
10369
// Code taken from cdo and libcdi, used for writing the history attribute.
10370
10371
// void cdoDefHistory(int fileID, char *histstring)
10372
static void NCDFAddHistory(int fpImage, const char *pszAddHist,
10373
                           const char *pszOldHist)
10374
0
{
10375
    // Check pszOldHist - as if there was no previous history, it will be
10376
    // a null pointer - if so set as empty.
10377
0
    if (nullptr == pszOldHist)
10378
0
    {
10379
0
        pszOldHist = "";
10380
0
    }
10381
10382
0
    char strtime[32];
10383
0
    strtime[0] = '\0';
10384
10385
0
    time_t tp = time(nullptr);
10386
0
    if (tp != -1)
10387
0
    {
10388
0
        struct tm ltime;
10389
0
        VSILocalTime(&tp, &ltime);
10390
0
        (void)strftime(strtime, sizeof(strtime),
10391
0
                       "%a %b %d %H:%M:%S %Y: ", &ltime);
10392
0
    }
10393
10394
    // status = nc_get_att_text(fpImage, NC_GLOBAL,
10395
    //                           "history", pszOldHist);
10396
    // printf("status: %d pszOldHist: [%s]\n",status,pszOldHist);
10397
10398
0
    size_t nNewHistSize =
10399
0
        strlen(pszOldHist) + strlen(strtime) + strlen(pszAddHist) + 1 + 1;
10400
0
    char *pszNewHist =
10401
0
        static_cast<char *>(CPLMalloc(nNewHistSize * sizeof(char)));
10402
10403
0
    strcpy(pszNewHist, strtime);
10404
0
    strcat(pszNewHist, pszAddHist);
10405
10406
    // int disableHistory = FALSE;
10407
    // if( !disableHistory )
10408
0
    {
10409
0
        if (!EQUAL(pszOldHist, ""))
10410
0
            strcat(pszNewHist, "\n");
10411
0
        strcat(pszNewHist, pszOldHist);
10412
0
    }
10413
10414
0
    const int status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
10415
0
                                       strlen(pszNewHist), pszNewHist);
10416
0
    NCDF_ERR(status);
10417
10418
0
    CPLFree(pszNewHist);
10419
0
}
10420
10421
static CPLErr NCDFSafeStrcat(char **ppszDest, const char *pszSrc,
10422
                             size_t *nDestSize)
10423
1.79k
{
10424
    /* Reallocate the data string until the content fits */
10425
1.79k
    while (*nDestSize < (strlen(*ppszDest) + strlen(pszSrc) + 1))
10426
0
    {
10427
0
        (*nDestSize) *= 2;
10428
0
        *ppszDest = static_cast<char *>(
10429
0
            CPLRealloc(reinterpret_cast<void *>(*ppszDest), *nDestSize));
10430
#ifdef NCDF_DEBUG
10431
        CPLDebug("GDAL_netCDF", "NCDFSafeStrcat() resized str from %ld to %ld",
10432
                 (*nDestSize) / 2, *nDestSize);
10433
#endif
10434
0
    }
10435
1.79k
    strcat(*ppszDest, pszSrc);
10436
10437
1.79k
    return CE_None;
10438
1.79k
}
10439
10440
/* helper function for NCDFGetAttr() */
10441
/* if pdfValue != nullptr, sets *pdfValue to first value returned */
10442
/* if ppszValue != nullptr, sets *ppszValue with all attribute values */
10443
/* *ppszValue is the responsibility of the caller and must be freed */
10444
static CPLErr NCDFGetAttr1(int nCdfId, int nVarId, const char *pszAttrName,
10445
                           double *pdfValue, char **ppszValue)
10446
17.1k
{
10447
17.1k
    nc_type nAttrType = NC_NAT;
10448
17.1k
    size_t nAttrLen = 0;
10449
10450
17.1k
    if (ppszValue)
10451
17.1k
        *ppszValue = nullptr;
10452
10453
17.1k
    int status = nc_inq_att(nCdfId, nVarId, pszAttrName, &nAttrType, &nAttrLen);
10454
17.1k
    if (status != NC_NOERR)
10455
14.8k
        return CE_Failure;
10456
10457
#ifdef NCDF_DEBUG
10458
    CPLDebug("GDAL_netCDF", "NCDFGetAttr1(%s) len=%ld type=%d", pszAttrName,
10459
             nAttrLen, nAttrType);
10460
#endif
10461
2.30k
    if (nAttrLen == 0 && nAttrType != NC_CHAR)
10462
0
        return CE_Failure;
10463
10464
    /* Allocate guaranteed minimum size (use 10 or 20 if not a string) */
10465
2.30k
    size_t nAttrValueSize = nAttrLen + 1;
10466
2.30k
    if (nAttrType != NC_CHAR && nAttrValueSize < 10)
10467
1.79k
        nAttrValueSize = 10;
10468
2.30k
    if (nAttrType == NC_DOUBLE && nAttrValueSize < 20)
10469
138
        nAttrValueSize = 20;
10470
2.30k
    if (nAttrType == NC_INT64 && nAttrValueSize < 20)
10471
0
        nAttrValueSize = 22;
10472
2.30k
    char *pszAttrValue =
10473
2.30k
        static_cast<char *>(CPLCalloc(nAttrValueSize, sizeof(char)));
10474
2.30k
    *pszAttrValue = '\0';
10475
10476
2.30k
    if (nAttrLen > 1 && nAttrType != NC_CHAR)
10477
0
        NCDFSafeStrcat(&pszAttrValue, "{", &nAttrValueSize);
10478
10479
2.30k
    double dfValue = 0.0;
10480
2.30k
    size_t m = 0;
10481
2.30k
    char szTemp[256];
10482
2.30k
    bool bSetDoubleFromStr = false;
10483
10484
2.30k
    switch (nAttrType)
10485
2.30k
    {
10486
509
        case NC_CHAR:
10487
509
            CPL_IGNORE_RET_VAL(
10488
509
                nc_get_att_text(nCdfId, nVarId, pszAttrName, pszAttrValue));
10489
509
            pszAttrValue[nAttrLen] = '\0';
10490
509
            bSetDoubleFromStr = true;
10491
509
            dfValue = 0.0;
10492
509
            break;
10493
1
        case NC_BYTE:
10494
1
        {
10495
1
            signed char *pscTemp = static_cast<signed char *>(
10496
1
                CPLCalloc(nAttrLen, sizeof(signed char)));
10497
1
            nc_get_att_schar(nCdfId, nVarId, pszAttrName, pscTemp);
10498
1
            dfValue = static_cast<double>(pscTemp[0]);
10499
1
            if (nAttrLen > 1)
10500
0
            {
10501
0
                for (m = 0; m < nAttrLen - 1; m++)
10502
0
                {
10503
0
                    snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
10504
0
                    NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10505
0
                }
10506
0
            }
10507
1
            snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
10508
1
            NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10509
1
            CPLFree(pscTemp);
10510
1
            break;
10511
0
        }
10512
0
        case NC_SHORT:
10513
0
        {
10514
0
            short *psTemp =
10515
0
                static_cast<short *>(CPLCalloc(nAttrLen, sizeof(short)));
10516
0
            nc_get_att_short(nCdfId, nVarId, pszAttrName, psTemp);
10517
0
            dfValue = static_cast<double>(psTemp[0]);
10518
0
            if (nAttrLen > 1)
10519
0
            {
10520
0
                for (m = 0; m < nAttrLen - 1; m++)
10521
0
                {
10522
0
                    snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
10523
0
                    NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10524
0
                }
10525
0
            }
10526
0
            snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
10527
0
            NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10528
0
            CPLFree(psTemp);
10529
0
            break;
10530
0
        }
10531
1.65k
        case NC_INT:
10532
1.65k
        {
10533
1.65k
            int *pnTemp = static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
10534
1.65k
            nc_get_att_int(nCdfId, nVarId, pszAttrName, pnTemp);
10535
1.65k
            dfValue = static_cast<double>(pnTemp[0]);
10536
1.65k
            if (nAttrLen > 1)
10537
0
            {
10538
0
                for (m = 0; m < nAttrLen - 1; m++)
10539
0
                {
10540
0
                    snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
10541
0
                    NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10542
0
                }
10543
0
            }
10544
1.65k
            snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
10545
1.65k
            NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10546
1.65k
            CPLFree(pnTemp);
10547
1.65k
            break;
10548
0
        }
10549
0
        case NC_FLOAT:
10550
0
        {
10551
0
            float *pfTemp =
10552
0
                static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
10553
0
            nc_get_att_float(nCdfId, nVarId, pszAttrName, pfTemp);
10554
0
            dfValue = static_cast<double>(pfTemp[0]);
10555
0
            if (nAttrLen > 1)
10556
0
            {
10557
0
                for (m = 0; m < nAttrLen - 1; m++)
10558
0
                {
10559
0
                    CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
10560
0
                    NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10561
0
                }
10562
0
            }
10563
0
            CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
10564
0
            NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10565
0
            CPLFree(pfTemp);
10566
0
            break;
10567
0
        }
10568
138
        case NC_DOUBLE:
10569
138
        {
10570
138
            double *pdfTemp =
10571
138
                static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
10572
138
            nc_get_att_double(nCdfId, nVarId, pszAttrName, pdfTemp);
10573
138
            dfValue = pdfTemp[0];
10574
138
            if (nAttrLen > 1)
10575
0
            {
10576
0
                for (m = 0; m < nAttrLen - 1; m++)
10577
0
                {
10578
0
                    CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
10579
0
                    NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10580
0
                }
10581
0
            }
10582
138
            CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
10583
138
            NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10584
138
            CPLFree(pdfTemp);
10585
138
            break;
10586
0
        }
10587
0
        case NC_STRING:
10588
0
        {
10589
0
            char **ppszTemp =
10590
0
                static_cast<char **>(CPLCalloc(nAttrLen, sizeof(char *)));
10591
0
            nc_get_att_string(nCdfId, nVarId, pszAttrName, ppszTemp);
10592
0
            bSetDoubleFromStr = true;
10593
0
            dfValue = 0.0;
10594
0
            if (nAttrLen > 1)
10595
0
            {
10596
0
                for (m = 0; m < nAttrLen - 1; m++)
10597
0
                {
10598
0
                    NCDFSafeStrcat(&pszAttrValue,
10599
0
                                   ppszTemp[m] ? ppszTemp[m] : "{NULL}",
10600
0
                                   &nAttrValueSize);
10601
0
                    NCDFSafeStrcat(&pszAttrValue, ",", &nAttrValueSize);
10602
0
                }
10603
0
            }
10604
0
            NCDFSafeStrcat(&pszAttrValue, ppszTemp[m] ? ppszTemp[m] : "{NULL}",
10605
0
                           &nAttrValueSize);
10606
0
            nc_free_string(nAttrLen, ppszTemp);
10607
0
            CPLFree(ppszTemp);
10608
0
            break;
10609
0
        }
10610
0
        case NC_UBYTE:
10611
0
        {
10612
0
            unsigned char *pucTemp = static_cast<unsigned char *>(
10613
0
                CPLCalloc(nAttrLen, sizeof(unsigned char)));
10614
0
            nc_get_att_uchar(nCdfId, nVarId, pszAttrName, pucTemp);
10615
0
            dfValue = static_cast<double>(pucTemp[0]);
10616
0
            if (nAttrLen > 1)
10617
0
            {
10618
0
                for (m = 0; m < nAttrLen - 1; m++)
10619
0
                {
10620
0
                    CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
10621
0
                    NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10622
0
                }
10623
0
            }
10624
0
            CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
10625
0
            NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10626
0
            CPLFree(pucTemp);
10627
0
            break;
10628
0
        }
10629
0
        case NC_USHORT:
10630
0
        {
10631
0
            unsigned short *pusTemp;
10632
0
            pusTemp = static_cast<unsigned short *>(
10633
0
                CPLCalloc(nAttrLen, sizeof(unsigned short)));
10634
0
            nc_get_att_ushort(nCdfId, nVarId, pszAttrName, pusTemp);
10635
0
            dfValue = static_cast<double>(pusTemp[0]);
10636
0
            if (nAttrLen > 1)
10637
0
            {
10638
0
                for (m = 0; m < nAttrLen - 1; m++)
10639
0
                {
10640
0
                    CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
10641
0
                    NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10642
0
                }
10643
0
            }
10644
0
            CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
10645
0
            NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10646
0
            CPLFree(pusTemp);
10647
0
            break;
10648
0
        }
10649
0
        case NC_UINT:
10650
0
        {
10651
0
            unsigned int *punTemp =
10652
0
                static_cast<unsigned int *>(CPLCalloc(nAttrLen, sizeof(int)));
10653
0
            nc_get_att_uint(nCdfId, nVarId, pszAttrName, punTemp);
10654
0
            dfValue = static_cast<double>(punTemp[0]);
10655
0
            if (nAttrLen > 1)
10656
0
            {
10657
0
                for (m = 0; m < nAttrLen - 1; m++)
10658
0
                {
10659
0
                    CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
10660
0
                    NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10661
0
                }
10662
0
            }
10663
0
            CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
10664
0
            NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10665
0
            CPLFree(punTemp);
10666
0
            break;
10667
0
        }
10668
0
        case NC_INT64:
10669
0
        {
10670
0
            GIntBig *panTemp =
10671
0
                static_cast<GIntBig *>(CPLCalloc(nAttrLen, sizeof(GIntBig)));
10672
0
            nc_get_att_longlong(nCdfId, nVarId, pszAttrName, panTemp);
10673
0
            dfValue = static_cast<double>(panTemp[0]);
10674
0
            if (nAttrLen > 1)
10675
0
            {
10676
0
                for (m = 0; m < nAttrLen - 1; m++)
10677
0
                {
10678
0
                    CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",",
10679
0
                                panTemp[m]);
10680
0
                    NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10681
0
                }
10682
0
            }
10683
0
            CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, panTemp[m]);
10684
0
            NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10685
0
            CPLFree(panTemp);
10686
0
            break;
10687
0
        }
10688
0
        case NC_UINT64:
10689
0
        {
10690
0
            GUIntBig *panTemp =
10691
0
                static_cast<GUIntBig *>(CPLCalloc(nAttrLen, sizeof(GUIntBig)));
10692
0
            nc_get_att_ulonglong(nCdfId, nVarId, pszAttrName, panTemp);
10693
0
            dfValue = static_cast<double>(panTemp[0]);
10694
0
            if (nAttrLen > 1)
10695
0
            {
10696
0
                for (m = 0; m < nAttrLen - 1; m++)
10697
0
                {
10698
0
                    CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",",
10699
0
                                panTemp[m]);
10700
0
                    NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10701
0
                }
10702
0
            }
10703
0
            CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, panTemp[m]);
10704
0
            NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
10705
0
            CPLFree(panTemp);
10706
0
            break;
10707
0
        }
10708
0
        default:
10709
0
            CPLDebug("GDAL_netCDF",
10710
0
                     "NCDFGetAttr unsupported type %d for attribute %s",
10711
0
                     nAttrType, pszAttrName);
10712
0
            break;
10713
2.30k
    }
10714
10715
2.30k
    if (nAttrLen > 1 && nAttrType != NC_CHAR)
10716
0
        NCDFSafeStrcat(&pszAttrValue, "}", &nAttrValueSize);
10717
10718
2.30k
    if (bSetDoubleFromStr)
10719
509
    {
10720
509
        if (CPLGetValueType(pszAttrValue) == CPL_VALUE_STRING)
10721
509
        {
10722
509
            if (ppszValue == nullptr && pdfValue != nullptr)
10723
0
            {
10724
0
                CPLFree(pszAttrValue);
10725
0
                return CE_Failure;
10726
0
            }
10727
509
        }
10728
509
        dfValue = CPLAtof(pszAttrValue);
10729
509
    }
10730
10731
    /* set return values */
10732
2.30k
    if (ppszValue)
10733
2.30k
        *ppszValue = pszAttrValue;
10734
0
    else
10735
0
        CPLFree(pszAttrValue);
10736
10737
2.30k
    if (pdfValue)
10738
0
        *pdfValue = dfValue;
10739
10740
2.30k
    return CE_None;
10741
2.30k
}
10742
10743
/* sets pdfValue to first value found */
10744
CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
10745
                   double *pdfValue)
10746
4
{
10747
4
    return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, pdfValue, nullptr);
10748
4
}
10749
10750
/* pszValue is the responsibility of the caller and must be freed */
10751
CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
10752
                   char **pszValue)
10753
17.1k
{
10754
17.1k
    return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, nullptr, pszValue);
10755
17.1k
}
10756
10757
/* By default write NC_CHAR, but detect for int/float/double and */
10758
/* NC4 string arrays */
10759
static CPLErr NCDFPutAttr(int nCdfId, int nVarId, const char *pszAttrName,
10760
                          const char *pszValue)
10761
0
{
10762
0
    int status = 0;
10763
0
    char *pszTemp = nullptr;
10764
10765
    /* get the attribute values as tokens */
10766
0
    char **papszValues = NCDFTokenizeArray(pszValue);
10767
0
    if (papszValues == nullptr)
10768
0
        return CE_Failure;
10769
10770
0
    size_t nAttrLen = CSLCount(papszValues);
10771
10772
    /* first detect type */
10773
0
    nc_type nAttrType = NC_CHAR;
10774
0
    nc_type nTmpAttrType = NC_CHAR;
10775
0
    for (size_t i = 0; i < nAttrLen; i++)
10776
0
    {
10777
0
        nTmpAttrType = NC_CHAR;
10778
0
        bool bFoundType = false;
10779
0
        errno = 0;
10780
0
        int nValue = static_cast<int>(strtol(papszValues[i], &pszTemp, 10));
10781
        /* test for int */
10782
        /* TODO test for Byte and short - can this be done safely? */
10783
0
        if (errno == 0 && papszValues[i] != pszTemp && *pszTemp == 0)
10784
0
        {
10785
0
            char szTemp[256];
10786
0
            CPLsnprintf(szTemp, sizeof(szTemp), "%d", nValue);
10787
0
            if (EQUAL(szTemp, papszValues[i]))
10788
0
            {
10789
0
                bFoundType = true;
10790
0
                nTmpAttrType = NC_INT;
10791
0
            }
10792
0
            else
10793
0
            {
10794
0
                unsigned int unValue = static_cast<unsigned int>(
10795
0
                    strtoul(papszValues[i], &pszTemp, 10));
10796
0
                CPLsnprintf(szTemp, sizeof(szTemp), "%u", unValue);
10797
0
                if (EQUAL(szTemp, papszValues[i]))
10798
0
                {
10799
0
                    bFoundType = true;
10800
0
                    nTmpAttrType = NC_UINT;
10801
0
                }
10802
0
            }
10803
0
        }
10804
0
        if (!bFoundType)
10805
0
        {
10806
            /* test for double */
10807
0
            errno = 0;
10808
0
            double dfValue = CPLStrtod(papszValues[i], &pszTemp);
10809
0
            if ((errno == 0) && (papszValues[i] != pszTemp) && (*pszTemp == 0))
10810
0
            {
10811
                // Test for float instead of double.
10812
                // strtof() is C89, which is not available in MSVC.
10813
                // See if we loose precision if we cast to float and write to
10814
                // char*.
10815
0
                float fValue = float(dfValue);
10816
0
                char szTemp[256];
10817
0
                CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", fValue);
10818
0
                if (EQUAL(szTemp, papszValues[i]))
10819
0
                    nTmpAttrType = NC_FLOAT;
10820
0
                else
10821
0
                    nTmpAttrType = NC_DOUBLE;
10822
0
            }
10823
0
        }
10824
0
        if ((nTmpAttrType <= NC_DOUBLE && nAttrType <= NC_DOUBLE &&
10825
0
             nTmpAttrType > nAttrType) ||
10826
0
            (nTmpAttrType == NC_UINT && nAttrType < NC_FLOAT) ||
10827
0
            (nTmpAttrType >= NC_FLOAT && nAttrType == NC_UINT))
10828
0
            nAttrType = nTmpAttrType;
10829
0
    }
10830
10831
#ifdef DEBUG
10832
    if (EQUAL(pszAttrName, "DEBUG_EMPTY_DOUBLE_ATTR"))
10833
    {
10834
        nAttrType = NC_DOUBLE;
10835
        nAttrLen = 0;
10836
    }
10837
#endif
10838
10839
    /* now write the data */
10840
0
    if (nAttrType == NC_CHAR)
10841
0
    {
10842
0
        int nTmpFormat = 0;
10843
0
        if (nAttrLen > 1)
10844
0
        {
10845
0
            status = nc_inq_format(nCdfId, &nTmpFormat);
10846
0
            NCDF_ERR(status);
10847
0
        }
10848
0
        if (nAttrLen > 1 && nTmpFormat == NCDF_FORMAT_NC4)
10849
0
            status = nc_put_att_string(nCdfId, nVarId, pszAttrName, nAttrLen,
10850
0
                                       const_cast<const char **>(papszValues));
10851
0
        else
10852
0
            status = nc_put_att_text(nCdfId, nVarId, pszAttrName,
10853
0
                                     strlen(pszValue), pszValue);
10854
0
        NCDF_ERR(status);
10855
0
    }
10856
0
    else
10857
0
    {
10858
0
        switch (nAttrType)
10859
0
        {
10860
0
            case NC_INT:
10861
0
            {
10862
0
                int *pnTemp =
10863
0
                    static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
10864
0
                for (size_t i = 0; i < nAttrLen; i++)
10865
0
                {
10866
0
                    pnTemp[i] =
10867
0
                        static_cast<int>(strtol(papszValues[i], &pszTemp, 10));
10868
0
                }
10869
0
                status = nc_put_att_int(nCdfId, nVarId, pszAttrName, NC_INT,
10870
0
                                        nAttrLen, pnTemp);
10871
0
                NCDF_ERR(status);
10872
0
                CPLFree(pnTemp);
10873
0
                break;
10874
0
            }
10875
0
            case NC_UINT:
10876
0
            {
10877
0
                unsigned int *punTemp = static_cast<unsigned int *>(
10878
0
                    CPLCalloc(nAttrLen, sizeof(unsigned int)));
10879
0
                for (size_t i = 0; i < nAttrLen; i++)
10880
0
                {
10881
0
                    punTemp[i] = static_cast<unsigned int>(
10882
0
                        strtol(papszValues[i], &pszTemp, 10));
10883
0
                }
10884
0
                status = nc_put_att_uint(nCdfId, nVarId, pszAttrName, NC_UINT,
10885
0
                                         nAttrLen, punTemp);
10886
0
                NCDF_ERR(status);
10887
0
                CPLFree(punTemp);
10888
0
                break;
10889
0
            }
10890
0
            case NC_FLOAT:
10891
0
            {
10892
0
                float *pfTemp =
10893
0
                    static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
10894
0
                for (size_t i = 0; i < nAttrLen; i++)
10895
0
                {
10896
0
                    pfTemp[i] =
10897
0
                        static_cast<float>(CPLStrtod(papszValues[i], &pszTemp));
10898
0
                }
10899
0
                status = nc_put_att_float(nCdfId, nVarId, pszAttrName, NC_FLOAT,
10900
0
                                          nAttrLen, pfTemp);
10901
0
                NCDF_ERR(status);
10902
0
                CPLFree(pfTemp);
10903
0
                break;
10904
0
            }
10905
0
            case NC_DOUBLE:
10906
0
            {
10907
0
                double *pdfTemp =
10908
0
                    static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
10909
0
                for (size_t i = 0; i < nAttrLen; i++)
10910
0
                {
10911
0
                    pdfTemp[i] = CPLStrtod(papszValues[i], &pszTemp);
10912
0
                }
10913
0
                status = nc_put_att_double(nCdfId, nVarId, pszAttrName,
10914
0
                                           NC_DOUBLE, nAttrLen, pdfTemp);
10915
0
                NCDF_ERR(status);
10916
0
                CPLFree(pdfTemp);
10917
0
                break;
10918
0
            }
10919
0
            default:
10920
0
                if (papszValues)
10921
0
                    CSLDestroy(papszValues);
10922
0
                return CE_Failure;
10923
0
                break;
10924
0
        }
10925
0
    }
10926
10927
0
    if (papszValues)
10928
0
        CSLDestroy(papszValues);
10929
10930
0
    return CE_None;
10931
0
}
10932
10933
static CPLErr NCDFGet1DVar(int nCdfId, int nVarId, char **pszValue)
10934
0
{
10935
    /* get var information */
10936
0
    int nVarDimId = -1;
10937
0
    int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
10938
0
    if (status != NC_NOERR || nVarDimId != 1)
10939
0
        return CE_Failure;
10940
10941
0
    status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
10942
0
    if (status != NC_NOERR)
10943
0
        return CE_Failure;
10944
10945
0
    nc_type nVarType = NC_NAT;
10946
0
    status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
10947
0
    if (status != NC_NOERR)
10948
0
        return CE_Failure;
10949
10950
0
    size_t nVarLen = 0;
10951
0
    status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
10952
0
    if (status != NC_NOERR)
10953
0
        return CE_Failure;
10954
10955
0
    size_t start[1] = {0};
10956
0
    size_t count[1] = {nVarLen};
10957
10958
    /* Allocate guaranteed minimum size */
10959
0
    size_t nVarValueSize = NCDF_MAX_STR_LEN;
10960
0
    char *pszVarValue =
10961
0
        static_cast<char *>(CPLCalloc(nVarValueSize, sizeof(char)));
10962
0
    *pszVarValue = '\0';
10963
10964
0
    if (nVarLen == 0)
10965
0
    {
10966
        /* set return values */
10967
0
        *pszValue = pszVarValue;
10968
10969
0
        return CE_None;
10970
0
    }
10971
10972
0
    if (nVarLen > 1 && nVarType != NC_CHAR)
10973
0
        NCDFSafeStrcat(&pszVarValue, "{", &nVarValueSize);
10974
10975
0
    switch (nVarType)
10976
0
    {
10977
0
        case NC_CHAR:
10978
0
            nc_get_vara_text(nCdfId, nVarId, start, count, pszVarValue);
10979
0
            pszVarValue[nVarLen] = '\0';
10980
0
            break;
10981
0
        case NC_BYTE:
10982
0
        {
10983
0
            signed char *pscTemp = static_cast<signed char *>(
10984
0
                CPLCalloc(nVarLen, sizeof(signed char)));
10985
0
            nc_get_vara_schar(nCdfId, nVarId, start, count, pscTemp);
10986
0
            char szTemp[256];
10987
0
            size_t m = 0;
10988
0
            for (; m < nVarLen - 1; m++)
10989
0
            {
10990
0
                snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
10991
0
                NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10992
0
            }
10993
0
            snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
10994
0
            NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
10995
0
            CPLFree(pscTemp);
10996
0
            break;
10997
0
        }
10998
0
        case NC_SHORT:
10999
0
        {
11000
0
            short *psTemp =
11001
0
                static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
11002
0
            nc_get_vara_short(nCdfId, nVarId, start, count, psTemp);
11003
0
            char szTemp[256];
11004
0
            size_t m = 0;
11005
0
            for (; m < nVarLen - 1; m++)
11006
0
            {
11007
0
                snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
11008
0
                NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11009
0
            }
11010
0
            snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
11011
0
            NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11012
0
            CPLFree(psTemp);
11013
0
            break;
11014
0
        }
11015
0
        case NC_INT:
11016
0
        {
11017
0
            int *pnTemp = static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
11018
0
            nc_get_vara_int(nCdfId, nVarId, start, count, pnTemp);
11019
0
            char szTemp[256];
11020
0
            size_t m = 0;
11021
0
            for (; m < nVarLen - 1; m++)
11022
0
            {
11023
0
                snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
11024
0
                NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11025
0
            }
11026
0
            snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
11027
0
            NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11028
0
            CPLFree(pnTemp);
11029
0
            break;
11030
0
        }
11031
0
        case NC_FLOAT:
11032
0
        {
11033
0
            float *pfTemp =
11034
0
                static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
11035
0
            nc_get_vara_float(nCdfId, nVarId, start, count, pfTemp);
11036
0
            char szTemp[256];
11037
0
            size_t m = 0;
11038
0
            for (; m < nVarLen - 1; m++)
11039
0
            {
11040
0
                CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
11041
0
                NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11042
0
            }
11043
0
            CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
11044
0
            NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11045
0
            CPLFree(pfTemp);
11046
0
            break;
11047
0
        }
11048
0
        case NC_DOUBLE:
11049
0
        {
11050
0
            double *pdfTemp =
11051
0
                static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
11052
0
            nc_get_vara_double(nCdfId, nVarId, start, count, pdfTemp);
11053
0
            char szTemp[256];
11054
0
            size_t m = 0;
11055
0
            for (; m < nVarLen - 1; m++)
11056
0
            {
11057
0
                CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
11058
0
                NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11059
0
            }
11060
0
            CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
11061
0
            NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11062
0
            CPLFree(pdfTemp);
11063
0
            break;
11064
0
        }
11065
0
        case NC_STRING:
11066
0
        {
11067
0
            char **ppszTemp =
11068
0
                static_cast<char **>(CPLCalloc(nVarLen, sizeof(char *)));
11069
0
            nc_get_vara_string(nCdfId, nVarId, start, count, ppszTemp);
11070
0
            size_t m = 0;
11071
0
            for (; m < nVarLen - 1; m++)
11072
0
            {
11073
0
                NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
11074
0
                NCDFSafeStrcat(&pszVarValue, ",", &nVarValueSize);
11075
0
            }
11076
0
            NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
11077
0
            nc_free_string(nVarLen, ppszTemp);
11078
0
            CPLFree(ppszTemp);
11079
0
            break;
11080
0
        }
11081
0
        case NC_UBYTE:
11082
0
        {
11083
0
            unsigned char *pucTemp;
11084
0
            pucTemp = static_cast<unsigned char *>(
11085
0
                CPLCalloc(nVarLen, sizeof(unsigned char)));
11086
0
            nc_get_vara_uchar(nCdfId, nVarId, start, count, pucTemp);
11087
0
            char szTemp[256];
11088
0
            size_t m = 0;
11089
0
            for (; m < nVarLen - 1; m++)
11090
0
            {
11091
0
                CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
11092
0
                NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11093
0
            }
11094
0
            CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
11095
0
            NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11096
0
            CPLFree(pucTemp);
11097
0
            break;
11098
0
        }
11099
0
        case NC_USHORT:
11100
0
        {
11101
0
            unsigned short *pusTemp;
11102
0
            pusTemp = static_cast<unsigned short *>(
11103
0
                CPLCalloc(nVarLen, sizeof(unsigned short)));
11104
0
            nc_get_vara_ushort(nCdfId, nVarId, start, count, pusTemp);
11105
0
            char szTemp[256];
11106
0
            size_t m = 0;
11107
0
            for (; m < nVarLen - 1; m++)
11108
0
            {
11109
0
                CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
11110
0
                NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11111
0
            }
11112
0
            CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
11113
0
            NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11114
0
            CPLFree(pusTemp);
11115
0
            break;
11116
0
        }
11117
0
        case NC_UINT:
11118
0
        {
11119
0
            unsigned int *punTemp;
11120
0
            punTemp = static_cast<unsigned int *>(
11121
0
                CPLCalloc(nVarLen, sizeof(unsigned int)));
11122
0
            nc_get_vara_uint(nCdfId, nVarId, start, count, punTemp);
11123
0
            char szTemp[256];
11124
0
            size_t m = 0;
11125
0
            for (; m < nVarLen - 1; m++)
11126
0
            {
11127
0
                CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
11128
0
                NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11129
0
            }
11130
0
            CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
11131
0
            NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11132
0
            CPLFree(punTemp);
11133
0
            break;
11134
0
        }
11135
0
        case NC_INT64:
11136
0
        {
11137
0
            long long *pnTemp =
11138
0
                static_cast<long long *>(CPLCalloc(nVarLen, sizeof(long long)));
11139
0
            nc_get_vara_longlong(nCdfId, nVarId, start, count, pnTemp);
11140
0
            char szTemp[256];
11141
0
            size_t m = 0;
11142
0
            for (; m < nVarLen - 1; m++)
11143
0
            {
11144
0
                snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",", pnTemp[m]);
11145
0
                NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11146
0
            }
11147
0
            snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, pnTemp[m]);
11148
0
            NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11149
0
            CPLFree(pnTemp);
11150
0
            break;
11151
0
        }
11152
0
        case NC_UINT64:
11153
0
        {
11154
0
            unsigned long long *pnTemp = static_cast<unsigned long long *>(
11155
0
                CPLCalloc(nVarLen, sizeof(unsigned long long)));
11156
0
            nc_get_vara_ulonglong(nCdfId, nVarId, start, count, pnTemp);
11157
0
            char szTemp[256];
11158
0
            size_t m = 0;
11159
0
            for (; m < nVarLen - 1; m++)
11160
0
            {
11161
0
                snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",", pnTemp[m]);
11162
0
                NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11163
0
            }
11164
0
            snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, pnTemp[m]);
11165
0
            NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
11166
0
            CPLFree(pnTemp);
11167
0
            break;
11168
0
        }
11169
0
        default:
11170
0
            CPLDebug("GDAL_netCDF", "NCDFGetVar1D unsupported type %d",
11171
0
                     nVarType);
11172
0
            CPLFree(pszVarValue);
11173
0
            pszVarValue = nullptr;
11174
0
            break;
11175
0
    }
11176
11177
0
    if (pszVarValue != nullptr && nVarLen > 1 && nVarType != NC_CHAR)
11178
0
        NCDFSafeStrcat(&pszVarValue, "}", &nVarValueSize);
11179
11180
    /* set return values */
11181
0
    *pszValue = pszVarValue;
11182
11183
0
    return CE_None;
11184
0
}
11185
11186
static CPLErr NCDFPut1DVar(int nCdfId, int nVarId, const char *pszValue)
11187
0
{
11188
0
    if (EQUAL(pszValue, ""))
11189
0
        return CE_Failure;
11190
11191
    /* get var information */
11192
0
    int nVarDimId = -1;
11193
0
    int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
11194
0
    if (status != NC_NOERR || nVarDimId != 1)
11195
0
        return CE_Failure;
11196
11197
0
    status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
11198
0
    if (status != NC_NOERR)
11199
0
        return CE_Failure;
11200
11201
0
    nc_type nVarType = NC_CHAR;
11202
0
    status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
11203
0
    if (status != NC_NOERR)
11204
0
        return CE_Failure;
11205
11206
0
    size_t nVarLen = 0;
11207
0
    status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
11208
0
    if (status != NC_NOERR)
11209
0
        return CE_Failure;
11210
11211
0
    size_t start[1] = {0};
11212
0
    size_t count[1] = {nVarLen};
11213
11214
    /* get the values as tokens */
11215
0
    char **papszValues = NCDFTokenizeArray(pszValue);
11216
0
    if (papszValues == nullptr)
11217
0
        return CE_Failure;
11218
11219
0
    nVarLen = CSLCount(papszValues);
11220
11221
    /* now write the data */
11222
0
    if (nVarType == NC_CHAR)
11223
0
    {
11224
0
        status = nc_put_vara_text(nCdfId, nVarId, start, count, pszValue);
11225
0
        NCDF_ERR(status);
11226
0
    }
11227
0
    else
11228
0
    {
11229
0
        switch (nVarType)
11230
0
        {
11231
0
            case NC_BYTE:
11232
0
            {
11233
0
                signed char *pscTemp = static_cast<signed char *>(
11234
0
                    CPLCalloc(nVarLen, sizeof(signed char)));
11235
0
                for (size_t i = 0; i < nVarLen; i++)
11236
0
                {
11237
0
                    char *pszTemp = nullptr;
11238
0
                    pscTemp[i] = static_cast<signed char>(
11239
0
                        strtol(papszValues[i], &pszTemp, 10));
11240
0
                }
11241
0
                status =
11242
0
                    nc_put_vara_schar(nCdfId, nVarId, start, count, pscTemp);
11243
0
                NCDF_ERR(status);
11244
0
                CPLFree(pscTemp);
11245
0
                break;
11246
0
            }
11247
0
            case NC_SHORT:
11248
0
            {
11249
0
                short *psTemp =
11250
0
                    static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
11251
0
                for (size_t i = 0; i < nVarLen; i++)
11252
0
                {
11253
0
                    char *pszTemp = nullptr;
11254
0
                    psTemp[i] = static_cast<short>(
11255
0
                        strtol(papszValues[i], &pszTemp, 10));
11256
0
                }
11257
0
                status =
11258
0
                    nc_put_vara_short(nCdfId, nVarId, start, count, psTemp);
11259
0
                NCDF_ERR(status);
11260
0
                CPLFree(psTemp);
11261
0
                break;
11262
0
            }
11263
0
            case NC_INT:
11264
0
            {
11265
0
                int *pnTemp =
11266
0
                    static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
11267
0
                for (size_t i = 0; i < nVarLen; i++)
11268
0
                {
11269
0
                    char *pszTemp = nullptr;
11270
0
                    pnTemp[i] =
11271
0
                        static_cast<int>(strtol(papszValues[i], &pszTemp, 10));
11272
0
                }
11273
0
                status = nc_put_vara_int(nCdfId, nVarId, start, count, pnTemp);
11274
0
                NCDF_ERR(status);
11275
0
                CPLFree(pnTemp);
11276
0
                break;
11277
0
            }
11278
0
            case NC_FLOAT:
11279
0
            {
11280
0
                float *pfTemp =
11281
0
                    static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
11282
0
                for (size_t i = 0; i < nVarLen; i++)
11283
0
                {
11284
0
                    char *pszTemp = nullptr;
11285
0
                    pfTemp[i] =
11286
0
                        static_cast<float>(CPLStrtod(papszValues[i], &pszTemp));
11287
0
                }
11288
0
                status =
11289
0
                    nc_put_vara_float(nCdfId, nVarId, start, count, pfTemp);
11290
0
                NCDF_ERR(status);
11291
0
                CPLFree(pfTemp);
11292
0
                break;
11293
0
            }
11294
0
            case NC_DOUBLE:
11295
0
            {
11296
0
                double *pdfTemp =
11297
0
                    static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
11298
0
                for (size_t i = 0; i < nVarLen; i++)
11299
0
                {
11300
0
                    char *pszTemp = nullptr;
11301
0
                    pdfTemp[i] = CPLStrtod(papszValues[i], &pszTemp);
11302
0
                }
11303
0
                status =
11304
0
                    nc_put_vara_double(nCdfId, nVarId, start, count, pdfTemp);
11305
0
                NCDF_ERR(status);
11306
0
                CPLFree(pdfTemp);
11307
0
                break;
11308
0
            }
11309
0
            default:
11310
0
            {
11311
0
                int nTmpFormat = 0;
11312
0
                status = nc_inq_format(nCdfId, &nTmpFormat);
11313
0
                NCDF_ERR(status);
11314
0
                if (nTmpFormat == NCDF_FORMAT_NC4)
11315
0
                {
11316
0
                    switch (nVarType)
11317
0
                    {
11318
0
                        case NC_STRING:
11319
0
                        {
11320
0
                            status =
11321
0
                                nc_put_vara_string(nCdfId, nVarId, start, count,
11322
0
                                                   (const char **)papszValues);
11323
0
                            NCDF_ERR(status);
11324
0
                            break;
11325
0
                        }
11326
0
                        case NC_UBYTE:
11327
0
                        {
11328
0
                            unsigned char *pucTemp =
11329
0
                                static_cast<unsigned char *>(
11330
0
                                    CPLCalloc(nVarLen, sizeof(unsigned char)));
11331
0
                            for (size_t i = 0; i < nVarLen; i++)
11332
0
                            {
11333
0
                                char *pszTemp = nullptr;
11334
0
                                pucTemp[i] = static_cast<unsigned char>(
11335
0
                                    strtoul(papszValues[i], &pszTemp, 10));
11336
0
                            }
11337
0
                            status = nc_put_vara_uchar(nCdfId, nVarId, start,
11338
0
                                                       count, pucTemp);
11339
0
                            NCDF_ERR(status);
11340
0
                            CPLFree(pucTemp);
11341
0
                            break;
11342
0
                        }
11343
0
                        case NC_USHORT:
11344
0
                        {
11345
0
                            unsigned short *pusTemp =
11346
0
                                static_cast<unsigned short *>(
11347
0
                                    CPLCalloc(nVarLen, sizeof(unsigned short)));
11348
0
                            for (size_t i = 0; i < nVarLen; i++)
11349
0
                            {
11350
0
                                char *pszTemp = nullptr;
11351
0
                                pusTemp[i] = static_cast<unsigned short>(
11352
0
                                    strtoul(papszValues[i], &pszTemp, 10));
11353
0
                            }
11354
0
                            status = nc_put_vara_ushort(nCdfId, nVarId, start,
11355
0
                                                        count, pusTemp);
11356
0
                            NCDF_ERR(status);
11357
0
                            CPLFree(pusTemp);
11358
0
                            break;
11359
0
                        }
11360
0
                        case NC_UINT:
11361
0
                        {
11362
0
                            unsigned int *punTemp = static_cast<unsigned int *>(
11363
0
                                CPLCalloc(nVarLen, sizeof(unsigned int)));
11364
0
                            for (size_t i = 0; i < nVarLen; i++)
11365
0
                            {
11366
0
                                char *pszTemp = nullptr;
11367
0
                                punTemp[i] = static_cast<unsigned int>(
11368
0
                                    strtoul(papszValues[i], &pszTemp, 10));
11369
0
                            }
11370
0
                            status = nc_put_vara_uint(nCdfId, nVarId, start,
11371
0
                                                      count, punTemp);
11372
0
                            NCDF_ERR(status);
11373
0
                            CPLFree(punTemp);
11374
0
                            break;
11375
0
                        }
11376
0
                        default:
11377
0
                            if (papszValues)
11378
0
                                CSLDestroy(papszValues);
11379
0
                            return CE_Failure;
11380
0
                            break;
11381
0
                    }
11382
0
                }
11383
0
                break;
11384
0
            }
11385
0
        }
11386
0
    }
11387
11388
0
    if (papszValues)
11389
0
        CSLDestroy(papszValues);
11390
11391
0
    return CE_None;
11392
0
}
11393
11394
/************************************************************************/
11395
/*                           GetDefaultNoDataValue()                    */
11396
/************************************************************************/
11397
11398
double NCDFGetDefaultNoDataValue(int nCdfId, int nVarId, int nVarType,
11399
                                 bool &bGotNoData)
11400
11401
0
{
11402
0
    int nNoFill = 0;
11403
0
    double dfNoData = 0.0;
11404
11405
0
    switch (nVarType)
11406
0
    {
11407
0
        case NC_CHAR:
11408
0
        case NC_BYTE:
11409
0
        case NC_UBYTE:
11410
            // Don't do default fill-values for bytes, too risky.
11411
            // This function should not be called in those cases.
11412
0
            CPLAssert(false);
11413
0
            break;
11414
0
        case NC_SHORT:
11415
0
        {
11416
0
            short nFillVal = 0;
11417
0
            if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11418
0
                NC_NOERR)
11419
0
            {
11420
0
                if (!nNoFill)
11421
0
                {
11422
0
                    bGotNoData = true;
11423
0
                    dfNoData = nFillVal;
11424
0
                }
11425
0
            }
11426
0
            else
11427
0
                dfNoData = NC_FILL_SHORT;
11428
0
            break;
11429
0
        }
11430
0
        case NC_INT:
11431
0
        {
11432
0
            int nFillVal = 0;
11433
0
            if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11434
0
                NC_NOERR)
11435
0
            {
11436
0
                if (!nNoFill)
11437
0
                {
11438
0
                    bGotNoData = true;
11439
0
                    dfNoData = nFillVal;
11440
0
                }
11441
0
            }
11442
0
            else
11443
0
                dfNoData = NC_FILL_INT;
11444
0
            break;
11445
0
        }
11446
0
        case NC_FLOAT:
11447
0
        {
11448
0
            float fFillVal = 0;
11449
0
            if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &fFillVal) ==
11450
0
                NC_NOERR)
11451
0
            {
11452
0
                if (!nNoFill)
11453
0
                {
11454
0
                    bGotNoData = true;
11455
0
                    dfNoData = fFillVal;
11456
0
                }
11457
0
            }
11458
0
            else
11459
0
                dfNoData = NC_FILL_FLOAT;
11460
0
            break;
11461
0
        }
11462
0
        case NC_DOUBLE:
11463
0
        {
11464
0
            if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &dfNoData) ==
11465
0
                NC_NOERR)
11466
0
            {
11467
0
                if (!nNoFill)
11468
0
                {
11469
0
                    bGotNoData = true;
11470
0
                }
11471
0
            }
11472
0
            else
11473
0
                dfNoData = NC_FILL_DOUBLE;
11474
0
            break;
11475
0
        }
11476
0
        case NC_USHORT:
11477
0
        {
11478
0
            unsigned short nFillVal = 0;
11479
0
            if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11480
0
                NC_NOERR)
11481
0
            {
11482
0
                if (!nNoFill)
11483
0
                {
11484
0
                    bGotNoData = true;
11485
0
                    dfNoData = nFillVal;
11486
0
                }
11487
0
            }
11488
0
            else
11489
0
                dfNoData = NC_FILL_USHORT;
11490
0
            break;
11491
0
        }
11492
0
        case NC_UINT:
11493
0
        {
11494
0
            unsigned int nFillVal = 0;
11495
0
            if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
11496
0
                NC_NOERR)
11497
0
            {
11498
0
                if (!nNoFill)
11499
0
                {
11500
0
                    bGotNoData = true;
11501
0
                    dfNoData = nFillVal;
11502
0
                }
11503
0
            }
11504
0
            else
11505
0
                dfNoData = NC_FILL_UINT;
11506
0
            break;
11507
0
        }
11508
0
        default:
11509
0
            dfNoData = 0.0;
11510
0
            break;
11511
0
    }
11512
11513
0
    return dfNoData;
11514
0
}
11515
11516
/************************************************************************/
11517
/*                      NCDFGetDefaultNoDataValueAsInt64()              */
11518
/************************************************************************/
11519
11520
int64_t NCDFGetDefaultNoDataValueAsInt64(int nCdfId, int nVarId,
11521
                                         bool &bGotNoData)
11522
11523
0
{
11524
0
    int nNoFill = 0;
11525
0
    long long nFillVal = 0;
11526
0
    if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
11527
0
    {
11528
0
        if (!nNoFill)
11529
0
        {
11530
0
            bGotNoData = true;
11531
0
            return static_cast<int64_t>(nFillVal);
11532
0
        }
11533
0
    }
11534
0
    else
11535
0
        return static_cast<int64_t>(NC_FILL_INT64);
11536
0
    return 0;
11537
0
}
11538
11539
/************************************************************************/
11540
/*                     NCDFGetDefaultNoDataValueAsUInt64()              */
11541
/************************************************************************/
11542
11543
uint64_t NCDFGetDefaultNoDataValueAsUInt64(int nCdfId, int nVarId,
11544
                                           bool &bGotNoData)
11545
11546
0
{
11547
0
    int nNoFill = 0;
11548
0
    unsigned long long nFillVal = 0;
11549
0
    if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
11550
0
    {
11551
0
        if (!nNoFill)
11552
0
        {
11553
0
            bGotNoData = true;
11554
0
            return static_cast<uint64_t>(nFillVal);
11555
0
        }
11556
0
    }
11557
0
    else
11558
0
        return static_cast<uint64_t>(NC_FILL_UINT64);
11559
0
    return 0;
11560
0
}
11561
11562
static int NCDFDoesVarContainAttribVal(int nCdfId,
11563
                                       const char *const *papszAttribNames,
11564
                                       const char *const *papszAttribValues,
11565
                                       int nVarId, const char *pszVarName,
11566
                                       bool bStrict = true)
11567
744
{
11568
744
    if (nVarId == -1 && pszVarName != nullptr)
11569
744
        NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
11570
11571
744
    if (nVarId == -1)
11572
26
        return -1;
11573
11574
718
    bool bFound = false;
11575
3.44k
    for (int i = 0; !bFound && papszAttribNames != nullptr &&
11576
3.44k
                    papszAttribNames[i] != nullptr;
11577
2.73k
         i++)
11578
2.73k
    {
11579
2.73k
        char *pszTemp = nullptr;
11580
2.73k
        if (NCDFGetAttr(nCdfId, nVarId, papszAttribNames[i], &pszTemp) ==
11581
2.73k
                CE_None &&
11582
2.73k
            pszTemp != nullptr)
11583
4
        {
11584
4
            if (bStrict)
11585
4
            {
11586
4
                if (EQUAL(pszTemp, papszAttribValues[i]))
11587
0
                    bFound = true;
11588
4
            }
11589
0
            else
11590
0
            {
11591
0
                if (EQUALN(pszTemp, papszAttribValues[i],
11592
0
                           strlen(papszAttribValues[i])))
11593
0
                    bFound = true;
11594
0
            }
11595
4
            CPLFree(pszTemp);
11596
4
        }
11597
2.73k
    }
11598
718
    return bFound;
11599
744
}
11600
11601
static int NCDFDoesVarContainAttribVal2(int nCdfId, const char *papszAttribName,
11602
                                        const char *const *papszAttribValues,
11603
                                        int nVarId, const char *pszVarName,
11604
                                        int bStrict = true)
11605
279
{
11606
279
    if (nVarId == -1 && pszVarName != nullptr)
11607
279
        NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
11608
11609
279
    if (nVarId == -1)
11610
0
        return -1;
11611
11612
279
    bool bFound = false;
11613
279
    char *pszTemp = nullptr;
11614
279
    if (NCDFGetAttr(nCdfId, nVarId, papszAttribName, &pszTemp) != CE_None ||
11615
279
        pszTemp == nullptr)
11616
279
        return FALSE;
11617
11618
0
    for (int i = 0; !bFound && i < CSLCount(papszAttribValues); i++)
11619
0
    {
11620
0
        if (bStrict)
11621
0
        {
11622
0
            if (EQUAL(pszTemp, papszAttribValues[i]))
11623
0
                bFound = true;
11624
0
        }
11625
0
        else
11626
0
        {
11627
0
            if (EQUALN(pszTemp, papszAttribValues[i],
11628
0
                       strlen(papszAttribValues[i])))
11629
0
                bFound = true;
11630
0
        }
11631
0
    }
11632
11633
0
    CPLFree(pszTemp);
11634
11635
0
    return bFound;
11636
279
}
11637
11638
static bool NCDFEqual(const char *papszName, const char *const *papszValues)
11639
26
{
11640
26
    if (papszName == nullptr || EQUAL(papszName, ""))
11641
0
        return false;
11642
11643
78
    for (int i = 0; papszValues && papszValues[i]; ++i)
11644
52
    {
11645
52
        if (EQUAL(papszName, papszValues[i]))
11646
0
            return true;
11647
52
    }
11648
11649
26
    return false;
11650
26
}
11651
11652
// Test that a variable is longitude/latitude coordinate,
11653
// following CF 4.1 and 4.2.
11654
bool NCDFIsVarLongitude(int nCdfId, int nVarId, const char *pszVarName)
11655
151
{
11656
    // Check for matching attributes.
11657
151
    int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLongitudeAttribNames,
11658
151
                                           papszCFLongitudeAttribValues, nVarId,
11659
151
                                           pszVarName);
11660
    // If not found using attributes then check using var name
11661
    // unless GDAL_NETCDF_VERIFY_DIMS=STRICT.
11662
151
    if (bVal == -1)
11663
6
    {
11664
6
        if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11665
6
                   "STRICT"))
11666
6
            bVal = NCDFEqual(pszVarName, papszCFLongitudeVarNames);
11667
0
        else
11668
0
            bVal = FALSE;
11669
6
    }
11670
145
    else if (bVal)
11671
0
    {
11672
        // Check that the units is not 'm' or '1'. See #6759
11673
0
        char *pszTemp = nullptr;
11674
0
        if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11675
0
            pszTemp != nullptr)
11676
0
        {
11677
0
            if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
11678
0
                bVal = false;
11679
0
            CPLFree(pszTemp);
11680
0
        }
11681
0
    }
11682
11683
151
    return CPL_TO_BOOL(bVal);
11684
151
}
11685
11686
bool NCDFIsVarLatitude(int nCdfId, int nVarId, const char *pszVarName)
11687
151
{
11688
151
    int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLatitudeAttribNames,
11689
151
                                           papszCFLatitudeAttribValues, nVarId,
11690
151
                                           pszVarName);
11691
151
    if (bVal == -1)
11692
7
    {
11693
7
        if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11694
7
                   "STRICT"))
11695
7
            bVal = NCDFEqual(pszVarName, papszCFLatitudeVarNames);
11696
0
        else
11697
0
            bVal = FALSE;
11698
7
    }
11699
144
    else if (bVal)
11700
0
    {
11701
        // Check that the units is not 'm' or '1'. See #6759
11702
0
        char *pszTemp = nullptr;
11703
0
        if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11704
0
            pszTemp != nullptr)
11705
0
        {
11706
0
            if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
11707
0
                bVal = false;
11708
0
            CPLFree(pszTemp);
11709
0
        }
11710
0
    }
11711
11712
151
    return CPL_TO_BOOL(bVal);
11713
151
}
11714
11715
bool NCDFIsVarProjectionX(int nCdfId, int nVarId, const char *pszVarName)
11716
151
{
11717
151
    int bVal = NCDFDoesVarContainAttribVal(
11718
151
        nCdfId, papszCFProjectionXAttribNames, papszCFProjectionXAttribValues,
11719
151
        nVarId, pszVarName);
11720
151
    if (bVal == -1)
11721
6
    {
11722
6
        if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11723
6
                   "STRICT"))
11724
6
            bVal = NCDFEqual(pszVarName, papszCFProjectionXVarNames);
11725
0
        else
11726
0
            bVal = FALSE;
11727
6
    }
11728
145
    else if (bVal)
11729
0
    {
11730
        // Check that the units is not '1'
11731
0
        char *pszTemp = nullptr;
11732
0
        if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11733
0
            pszTemp != nullptr)
11734
0
        {
11735
0
            if (EQUAL(pszTemp, "1"))
11736
0
                bVal = false;
11737
0
            CPLFree(pszTemp);
11738
0
        }
11739
0
    }
11740
11741
151
    return CPL_TO_BOOL(bVal);
11742
151
}
11743
11744
bool NCDFIsVarProjectionY(int nCdfId, int nVarId, const char *pszVarName)
11745
151
{
11746
151
    int bVal = NCDFDoesVarContainAttribVal(
11747
151
        nCdfId, papszCFProjectionYAttribNames, papszCFProjectionYAttribValues,
11748
151
        nVarId, pszVarName);
11749
151
    if (bVal == -1)
11750
7
    {
11751
7
        if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
11752
7
                   "STRICT"))
11753
7
            bVal = NCDFEqual(pszVarName, papszCFProjectionYVarNames);
11754
0
        else
11755
0
            bVal = FALSE;
11756
7
    }
11757
144
    else if (bVal)
11758
0
    {
11759
        // Check that the units is not '1'
11760
0
        char *pszTemp = nullptr;
11761
0
        if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
11762
0
            pszTemp != nullptr)
11763
0
        {
11764
0
            if (EQUAL(pszTemp, "1"))
11765
0
                bVal = false;
11766
0
            CPLFree(pszTemp);
11767
0
        }
11768
0
    }
11769
11770
151
    return CPL_TO_BOOL(bVal);
11771
151
}
11772
11773
/* test that a variable is a vertical coordinate, following CF 4.3 */
11774
bool NCDFIsVarVerticalCoord(int nCdfId, int nVarId, const char *pszVarName)
11775
139
{
11776
    /* check for matching attributes */
11777
139
    if (NCDFDoesVarContainAttribVal(nCdfId, papszCFVerticalAttribNames,
11778
139
                                    papszCFVerticalAttribValues, nVarId,
11779
139
                                    pszVarName))
11780
0
        return true;
11781
    /* check for matching units */
11782
139
    else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
11783
139
                                          papszCFVerticalUnitsValues, nVarId,
11784
139
                                          pszVarName))
11785
0
        return true;
11786
    /* check for matching standard name */
11787
139
    else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_STD_NAME,
11788
139
                                          papszCFVerticalStandardNameValues,
11789
139
                                          nVarId, pszVarName))
11790
0
        return true;
11791
139
    else
11792
139
        return false;
11793
139
}
11794
11795
/* test that a variable is a time coordinate, following CF 4.4 */
11796
bool NCDFIsVarTimeCoord(int nCdfId, int nVarId, const char *pszVarName)
11797
1
{
11798
    /* check for matching attributes */
11799
1
    if (NCDFDoesVarContainAttribVal(nCdfId, papszCFTimeAttribNames,
11800
1
                                    papszCFTimeAttribValues, nVarId,
11801
1
                                    pszVarName))
11802
0
        return true;
11803
    /* check for matching units */
11804
1
    else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
11805
1
                                          papszCFTimeUnitsValues, nVarId,
11806
1
                                          pszVarName, false))
11807
0
        return true;
11808
1
    else
11809
1
        return false;
11810
1
}
11811
11812
// Parse a string, and return as a string list.
11813
// If it an array of the form {a,b}, then tokenize it.
11814
// Otherwise, return a copy.
11815
static char **NCDFTokenizeArray(const char *pszValue)
11816
0
{
11817
0
    if (pszValue == nullptr || EQUAL(pszValue, ""))
11818
0
        return nullptr;
11819
11820
0
    char **papszValues = nullptr;
11821
0
    const int nLen = static_cast<int>(strlen(pszValue));
11822
11823
0
    if (pszValue[0] == '{' && nLen > 2 && pszValue[nLen - 1] == '}')
11824
0
    {
11825
0
        char *pszTemp = static_cast<char *>(CPLMalloc((nLen - 2) + 1));
11826
0
        strncpy(pszTemp, pszValue + 1, nLen - 2);
11827
0
        pszTemp[nLen - 2] = '\0';
11828
0
        papszValues = CSLTokenizeString2(pszTemp, ",", CSLT_ALLOWEMPTYTOKENS);
11829
0
        CPLFree(pszTemp);
11830
0
    }
11831
0
    else
11832
0
    {
11833
0
        papszValues = reinterpret_cast<char **>(CPLCalloc(2, sizeof(char *)));
11834
0
        papszValues[0] = CPLStrdup(pszValue);
11835
0
        papszValues[1] = nullptr;
11836
0
    }
11837
11838
0
    return papszValues;
11839
0
}
11840
11841
// Open a NetCDF subdataset from full path /group1/group2/.../groupn/var.
11842
// Leading slash is optional.
11843
static CPLErr NCDFOpenSubDataset(int nCdfId, const char *pszSubdatasetName,
11844
                                 int *pnGroupId, int *pnVarId)
11845
11.1k
{
11846
11.1k
    *pnGroupId = -1;
11847
11.1k
    *pnVarId = -1;
11848
11849
    // Open group.
11850
11.1k
    char *pszGroupFullName =
11851
11.1k
        CPLStrdup(CPLGetPathSafe(pszSubdatasetName).c_str());
11852
    // Add a leading slash if needed.
11853
11.1k
    if (pszGroupFullName[0] != '/')
11854
0
    {
11855
0
        char *old = pszGroupFullName;
11856
0
        pszGroupFullName = CPLStrdup(CPLSPrintf("/%s", pszGroupFullName));
11857
0
        CPLFree(old);
11858
0
    }
11859
    // Detect root group.
11860
11.1k
    if (EQUAL(pszGroupFullName, "/"))
11861
9.21k
    {
11862
9.21k
        *pnGroupId = nCdfId;
11863
9.21k
        CPLFree(pszGroupFullName);
11864
9.21k
    }
11865
1.93k
    else
11866
1.93k
    {
11867
1.93k
        int status = nc_inq_grp_full_ncid(nCdfId, pszGroupFullName, pnGroupId);
11868
1.93k
        CPLFree(pszGroupFullName);
11869
1.93k
        NCDF_ERR_RET(status);
11870
1.93k
    }
11871
11872
    // Open var.
11873
9.21k
    const char *pszVarName = CPLGetFilename(pszSubdatasetName);
11874
9.21k
    NCDF_ERR_RET(nc_inq_varid(*pnGroupId, pszVarName, pnVarId));
11875
11876
0
    return CE_None;
11877
9.21k
}
11878
11879
// Get all dimensions visible from a given NetCDF (or group) ID and any of
11880
// its parents.
11881
static CPLErr NCDFGetVisibleDims(int nGroupId, int *pnDims, int **ppanDimIds)
11882
0
{
11883
0
    int nDims = 0;
11884
0
    int *panDimIds = nullptr;
11885
0
    NCDF_ERR_RET(nc_inq_dimids(nGroupId, &nDims, nullptr, true));
11886
11887
0
    panDimIds = static_cast<int *>(CPLMalloc(nDims * sizeof(int)));
11888
11889
0
    int status = nc_inq_dimids(nGroupId, nullptr, panDimIds, true);
11890
0
    if (status != NC_NOERR)
11891
0
        CPLFree(panDimIds);
11892
0
    NCDF_ERR_RET(status);
11893
11894
0
    *pnDims = nDims;
11895
0
    *ppanDimIds = panDimIds;
11896
11897
0
    return CE_None;
11898
0
}
11899
11900
// Get direct sub-groups IDs of a given NetCDF (or group) ID.
11901
// Consider only direct children, does not get children of children.
11902
static CPLErr NCDFGetSubGroups(int nGroupId, int *pnSubGroups,
11903
                               int **ppanSubGroupIds)
11904
99.0k
{
11905
99.0k
    *pnSubGroups = 0;
11906
99.0k
    *ppanSubGroupIds = nullptr;
11907
11908
99.0k
    int nSubGroups;
11909
99.0k
    NCDF_ERR_RET(nc_inq_grps(nGroupId, &nSubGroups, nullptr));
11910
99.0k
    int *panSubGroupIds =
11911
99.0k
        static_cast<int *>(CPLMalloc(nSubGroups * sizeof(int)));
11912
99.0k
    NCDF_ERR_RET(nc_inq_grps(nGroupId, nullptr, panSubGroupIds));
11913
99.0k
    *pnSubGroups = nSubGroups;
11914
99.0k
    *ppanSubGroupIds = panSubGroupIds;
11915
11916
99.0k
    return CE_None;
11917
99.0k
}
11918
11919
// Get the full name of a given NetCDF (or group) ID
11920
// (e.g. /group1/group2/.../groupn).
11921
// bNC3Compat remove the leading slash for top-level variables for
11922
// backward compatibility (top-level variables are the ones in the root group).
11923
static CPLErr NCDFGetGroupFullName(int nGroupId, char **ppszFullName,
11924
                                   bool bNC3Compat)
11925
7.06k
{
11926
7.06k
    *ppszFullName = nullptr;
11927
11928
7.06k
    size_t nFullNameLen;
11929
7.06k
    NCDF_ERR_RET(nc_inq_grpname_len(nGroupId, &nFullNameLen));
11930
7.06k
    *ppszFullName =
11931
7.06k
        static_cast<char *>(CPLMalloc((nFullNameLen + 1) * sizeof(char)));
11932
7.06k
    int status = nc_inq_grpname_full(nGroupId, &nFullNameLen, *ppszFullName);
11933
7.06k
    if (status != NC_NOERR)
11934
0
    {
11935
0
        CPLFree(*ppszFullName);
11936
0
        *ppszFullName = nullptr;
11937
0
        NCDF_ERR_RET(status);
11938
0
    }
11939
11940
7.06k
    if (bNC3Compat && EQUAL(*ppszFullName, "/"))
11941
7.06k
        (*ppszFullName)[0] = '\0';
11942
11943
7.06k
    return CE_None;
11944
7.06k
}
11945
11946
CPLString NCDFGetGroupFullName(int nGroupId)
11947
0
{
11948
0
    char *pszFullname = nullptr;
11949
0
    NCDFGetGroupFullName(nGroupId, &pszFullname, false);
11950
0
    CPLString osRet(pszFullname ? pszFullname : "");
11951
0
    CPLFree(pszFullname);
11952
0
    return osRet;
11953
0
}
11954
11955
// Get the full name of a given NetCDF variable ID
11956
// (e.g. /group1/group2/.../groupn/var).
11957
// Handle also NC_GLOBAL as nVarId.
11958
// bNC3Compat remove the leading slash for top-level variables for
11959
// backward compatibility (top-level variables are the ones in the root group).
11960
static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId, char **ppszFullName,
11961
                                 bool bNC3Compat)
11962
7.04k
{
11963
7.04k
    *ppszFullName = nullptr;
11964
7.04k
    char *pszGroupFullName = nullptr;
11965
7.04k
    ERR_RET(NCDFGetGroupFullName(nGroupId, &pszGroupFullName, bNC3Compat));
11966
7.04k
    char szVarName[NC_MAX_NAME + 1];
11967
7.04k
    if (nVarId == NC_GLOBAL)
11968
399
    {
11969
399
        strcpy(szVarName, "NC_GLOBAL");
11970
399
    }
11971
6.64k
    else
11972
6.64k
    {
11973
6.64k
        int status = nc_inq_varname(nGroupId, nVarId, szVarName);
11974
6.64k
        if (status != NC_NOERR)
11975
0
        {
11976
0
            CPLFree(pszGroupFullName);
11977
0
            NCDF_ERR_RET(status);
11978
0
        }
11979
6.64k
    }
11980
7.04k
    const char *pszSep = "/";
11981
7.04k
    if (EQUAL(pszGroupFullName, "/") || EQUAL(pszGroupFullName, ""))
11982
7.04k
        pszSep = "";
11983
7.04k
    *ppszFullName =
11984
7.04k
        CPLStrdup(CPLSPrintf("%s%s%s", pszGroupFullName, pszSep, szVarName));
11985
7.04k
    CPLFree(pszGroupFullName);
11986
7.04k
    return CE_None;
11987
7.04k
}
11988
11989
// Get the NetCDF root group ID of a given group ID.
11990
static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId)
11991
11.1k
{
11992
11.1k
    *pnRootGroupId = -1;
11993
    // Recurse on parent group.
11994
11.1k
    int nParentGroupId;
11995
11.1k
    int status = nc_inq_grp_parent(nStartGroupId, &nParentGroupId);
11996
11.1k
    if (status == NC_NOERR)
11997
0
        return NCDFGetRootGroup(nParentGroupId, pnRootGroupId);
11998
11.1k
    else if (status != NC_ENOGRP)
11999
0
        NCDF_ERR_RET(status);
12000
11.1k
    else  // No more parent group.
12001
11.1k
    {
12002
11.1k
        *pnRootGroupId = nStartGroupId;
12003
11.1k
    }
12004
12005
11.1k
    return CE_None;
12006
11.1k
}
12007
12008
// Implementation of NCDFResolveVar/Att.
12009
static CPLErr NCDFResolveElem(int nStartGroupId, const char *pszVar,
12010
                              const char *pszAtt, int *pnGroupId, int *pnId,
12011
                              bool bMandatory)
12012
99.6k
{
12013
99.6k
    if (!pszVar && !pszAtt)
12014
0
    {
12015
0
        CPLError(CE_Failure, CPLE_IllegalArg,
12016
0
                 "pszVar and pszAtt NCDFResolveElem() args are both null.");
12017
0
        return CE_Failure;
12018
0
    }
12019
12020
99.6k
    enum
12021
99.6k
    {
12022
99.6k
        NCRM_PARENT,
12023
99.6k
        NCRM_WIDTH_WISE
12024
99.6k
    } eNCResolveMode = NCRM_PARENT;
12025
12026
99.6k
    std::queue<int> aoQueueGroupIdsToVisit;
12027
99.6k
    aoQueueGroupIdsToVisit.push(nStartGroupId);
12028
12029
197k
    while (!aoQueueGroupIdsToVisit.empty())
12030
99.6k
    {
12031
        // Get the first group of the FIFO queue.
12032
99.6k
        *pnGroupId = aoQueueGroupIdsToVisit.front();
12033
99.6k
        aoQueueGroupIdsToVisit.pop();
12034
12035
        // Look if this group contains the searched element.
12036
99.6k
        int status;
12037
99.6k
        if (pszVar)
12038
99.6k
            status = nc_inq_varid(*pnGroupId, pszVar, pnId);
12039
0
        else  // pszAtt != nullptr.
12040
0
            status = nc_inq_attid(*pnGroupId, NC_GLOBAL, pszAtt, pnId);
12041
12042
99.6k
        if (status == NC_NOERR)
12043
1.75k
        {
12044
1.75k
            return CE_None;
12045
1.75k
        }
12046
97.8k
        else if ((pszVar && status != NC_ENOTVAR) ||
12047
97.8k
                 (pszAtt && status != NC_ENOTATT))
12048
3.82k
        {
12049
3.82k
            NCDF_ERR(status);
12050
3.82k
        }
12051
        // Element not found, in NC4 case we must search in other groups
12052
        // following the CF logic.
12053
12054
        // The first resolve mode consists to search on parent groups.
12055
97.8k
        if (eNCResolveMode == NCRM_PARENT)
12056
97.8k
        {
12057
97.8k
            int nParentGroupId = -1;
12058
97.8k
            int status2 = nc_inq_grp_parent(*pnGroupId, &nParentGroupId);
12059
97.8k
            if (status2 == NC_NOERR)
12060
0
                aoQueueGroupIdsToVisit.push(nParentGroupId);
12061
97.8k
            else if (status2 != NC_ENOGRP)
12062
0
                NCDF_ERR(status2);
12063
97.8k
            else if (pszVar)
12064
                // When resolving a variable, if there is no more
12065
                // parent group then we switch to width-wise search mode
12066
                // starting from the latest found parent group.
12067
97.8k
                eNCResolveMode = NCRM_WIDTH_WISE;
12068
97.8k
        }
12069
12070
        // The second resolve mode is a width-wise search.
12071
97.8k
        if (eNCResolveMode == NCRM_WIDTH_WISE)
12072
97.8k
        {
12073
            // Enqueue all direct sub-groups.
12074
97.8k
            int nSubGroups = 0;
12075
97.8k
            int *panSubGroupIds = nullptr;
12076
97.8k
            NCDFGetSubGroups(*pnGroupId, &nSubGroups, &panSubGroupIds);
12077
97.8k
            for (int i = 0; i < nSubGroups; i++)
12078
0
                aoQueueGroupIdsToVisit.push(panSubGroupIds[i]);
12079
97.8k
            CPLFree(panSubGroupIds);
12080
97.8k
        }
12081
97.8k
    }
12082
12083
97.8k
    if (bMandatory)
12084
0
    {
12085
0
        char *pszStartGroupFullName = nullptr;
12086
0
        NCDFGetGroupFullName(nStartGroupId, &pszStartGroupFullName);
12087
0
        CPLError(CE_Failure, CPLE_AppDefined,
12088
0
                 "Cannot resolve mandatory %s %s from group %s",
12089
0
                 (pszVar ? pszVar : pszAtt),
12090
0
                 (pszVar ? "variable" : "attribute"),
12091
0
                 (pszStartGroupFullName ? pszStartGroupFullName : ""));
12092
0
        CPLFree(pszStartGroupFullName);
12093
0
    }
12094
12095
97.8k
    *pnGroupId = -1;
12096
97.8k
    *pnId = -1;
12097
97.8k
    return CE_Failure;
12098
99.6k
}
12099
12100
// Resolve a variable name from a given starting group following the CF logic:
12101
// - if var name is an absolute path then directly open it
12102
// - first search in the starting group and its parent groups
12103
// - then if there is no more parent group we switch to a width-wise search
12104
//   mode starting from the latest found parent group.
12105
// The full CF logic is described here:
12106
// https://github.com/diwg/cf2/blob/master/group/cf2-group.adoc#scope
12107
// If bMandatory then print an error if resolving fails.
12108
// TODO: implement support of relative paths.
12109
// TODO: to follow strictly the CF logic, when searching for a coordinate
12110
//       variable, we must stop the parent search mode once the corresponding
12111
//       dimension is found and start the width-wise search from this group.
12112
// TODO: to follow strictly the CF logic, when searching in width-wise mode
12113
//       we should skip every groups already visited during the parent
12114
//       search mode (but revisiting them should have no impact so we could
12115
//       let as it is if it is simpler...)
12116
// TODO: CF specifies that the width-wise search order is "left-to-right" so
12117
//       maybe we must sort sibling groups alphabetically? but maybe not
12118
//       necessary if nc_inq_grps() already sort them?
12119
CPLErr NCDFResolveVar(int nStartGroupId, const char *pszVar, int *pnGroupId,
12120
                      int *pnVarId, bool bMandatory)
12121
110k
{
12122
110k
    *pnGroupId = -1;
12123
110k
    *pnVarId = -1;
12124
110k
    int nGroupId = nStartGroupId, nVarId;
12125
110k
    if (pszVar[0] == '/')
12126
11.1k
    {
12127
        // This is an absolute path: we can open the var directly.
12128
11.1k
        int nRootGroupId;
12129
11.1k
        ERR_RET(NCDFGetRootGroup(nStartGroupId, &nRootGroupId));
12130
11.1k
        ERR_RET(NCDFOpenSubDataset(nRootGroupId, pszVar, &nGroupId, &nVarId));
12131
11.1k
    }
12132
99.6k
    else
12133
99.6k
    {
12134
        // We have to search the variable following the CF logic.
12135
99.6k
        ERR_RET(NCDFResolveElem(nStartGroupId, pszVar, nullptr, &nGroupId,
12136
99.6k
                                &nVarId, bMandatory));
12137
99.6k
    }
12138
1.75k
    *pnGroupId = nGroupId;
12139
1.75k
    *pnVarId = nVarId;
12140
1.75k
    return CE_None;
12141
110k
}
12142
12143
// Like NCDFResolveVar but returns directly the var full name.
12144
static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,
12145
                                     char **ppszFullName, bool bMandatory)
12146
109k
{
12147
109k
    *ppszFullName = nullptr;
12148
109k
    int nGroupId, nVarId;
12149
109k
    ERR_RET(
12150
109k
        NCDFResolveVar(nStartGroupId, pszVar, &nGroupId, &nVarId, bMandatory));
12151
753
    return NCDFGetVarFullName(nGroupId, nVarId, ppszFullName);
12152
109k
}
12153
12154
// Like NCDFResolveVar but resolves an attribute instead a variable and
12155
// returns its integer value.
12156
// Only GLOBAL attributes are supported for the moment.
12157
static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,
12158
                                const char *pszAtt, int *pnAtt, bool bMandatory)
12159
0
{
12160
0
    int nGroupId = nStartGroupId, nAttId = nStartVarId;
12161
0
    ERR_RET(NCDFResolveElem(nStartGroupId, nullptr, pszAtt, &nGroupId, &nAttId,
12162
0
                            bMandatory));
12163
0
    NCDF_ERR_RET(nc_get_att_int(nGroupId, NC_GLOBAL, pszAtt, pnAtt));
12164
0
    return CE_None;
12165
0
}
12166
12167
// Filter variables to keep only valid 2+D raster bands and vector fields in
12168
// a given a NetCDF (or group) ID and its sub-groups.
12169
// Coordinate or boundary variables are ignored.
12170
// It also creates corresponding vector layers.
12171
CPLErr netCDFDataset::FilterVars(
12172
    int nCdfId, bool bKeepRasters, bool bKeepVectors, char **papszIgnoreVars,
12173
    int *pnRasterVars, int *pnGroupId, int *pnVarId, int *pnIgnoredVars,
12174
    std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>
12175
        &oMap2DDimsToGroupAndVar)
12176
399
{
12177
399
    int nVars = 0;
12178
399
    int nRasterVars = 0;
12179
399
    NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
12180
12181
399
    std::vector<int> anPotentialVectorVarID;
12182
    // oMapDimIdToCount[x] = number of times dim x is the first dimension of
12183
    // potential vector variables
12184
399
    std::map<int, int> oMapDimIdToCount;
12185
399
    int nVarXId = -1;
12186
399
    int nVarYId = -1;
12187
399
    int nVarZId = -1;
12188
399
    int nVarTimeId = -1;
12189
399
    int nVarTimeDimId = -1;
12190
399
    bool bIsVectorOnly = true;
12191
399
    int nProfileDimId = -1;
12192
399
    int nParentIndexVarID = -1;
12193
12194
6.19k
    for (int v = 0; v < nVars; v++)
12195
5.79k
    {
12196
5.79k
        int nVarDims;
12197
5.79k
        NCDF_ERR_RET(nc_inq_varndims(nCdfId, v, &nVarDims));
12198
        // Should we ignore this variable?
12199
5.79k
        char szTemp[NC_MAX_NAME + 1];
12200
5.79k
        szTemp[0] = '\0';
12201
5.79k
        NCDF_ERR_RET(nc_inq_varname(nCdfId, v, szTemp));
12202
12203
5.79k
        if (strstr(szTemp, "_node_coordinates") ||
12204
5.79k
            strstr(szTemp, "_node_count"))
12205
0
        {
12206
            // Ignore CF-1.8 Simple Geometries helper variables
12207
0
            continue;
12208
0
        }
12209
12210
5.79k
        if (nVarDims == 1 && (NCDFIsVarLongitude(nCdfId, -1, szTemp) ||
12211
139
                              NCDFIsVarProjectionX(nCdfId, -1, szTemp)))
12212
0
        {
12213
0
            nVarXId = v;
12214
0
        }
12215
5.79k
        else if (nVarDims == 1 && (NCDFIsVarLatitude(nCdfId, -1, szTemp) ||
12216
139
                                   NCDFIsVarProjectionY(nCdfId, -1, szTemp)))
12217
0
        {
12218
0
            nVarYId = v;
12219
0
        }
12220
5.79k
        else if (nVarDims == 1 && NCDFIsVarVerticalCoord(nCdfId, -1, szTemp))
12221
0
        {
12222
0
            nVarZId = v;
12223
0
        }
12224
5.79k
        else
12225
5.79k
        {
12226
5.79k
            char *pszVarFullName = nullptr;
12227
5.79k
            CPLErr eErr = NCDFGetVarFullName(nCdfId, v, &pszVarFullName);
12228
5.79k
            if (eErr != CE_None)
12229
0
            {
12230
0
                CPLFree(pszVarFullName);
12231
0
                continue;
12232
0
            }
12233
5.79k
            bool bIgnoreVar =
12234
5.79k
                (CSLFindString(papszIgnoreVars, pszVarFullName) != -1);
12235
5.79k
            CPLFree(pszVarFullName);
12236
5.79k
            if (bIgnoreVar)
12237
74
            {
12238
74
                if (nVarDims == 1 && NCDFIsVarTimeCoord(nCdfId, -1, szTemp))
12239
0
                {
12240
0
                    nVarTimeId = v;
12241
0
                    nc_inq_vardimid(nCdfId, v, &nVarTimeDimId);
12242
0
                }
12243
74
                else if (nVarDims > 1)
12244
0
                {
12245
0
                    (*pnIgnoredVars)++;
12246
0
                    CPLDebug("GDAL_netCDF", "variable #%d [%s] was ignored", v,
12247
0
                             szTemp);
12248
0
                }
12249
74
            }
12250
            // Only accept 2+D vars.
12251
5.72k
            else if (nVarDims >= 2)
12252
474
            {
12253
474
                bool bRasterCandidate = true;
12254
                // Identify variables that might be vector variables
12255
474
                if (nVarDims == 2)
12256
209
                {
12257
209
                    int anDimIds[2] = {-1, -1};
12258
209
                    nc_inq_vardimid(nCdfId, v, anDimIds);
12259
12260
209
                    nc_type vartype = NC_NAT;
12261
209
                    nc_inq_vartype(nCdfId, v, &vartype);
12262
12263
209
                    char szDimNameFirst[NC_MAX_NAME + 1];
12264
209
                    char szDimNameSecond[NC_MAX_NAME + 1];
12265
209
                    szDimNameFirst[0] = '\0';
12266
209
                    szDimNameSecond[0] = '\0';
12267
209
                    if (vartype == NC_CHAR &&
12268
209
                        nc_inq_dimname(nCdfId, anDimIds[0], szDimNameFirst) ==
12269
12
                            NC_NOERR &&
12270
209
                        nc_inq_dimname(nCdfId, anDimIds[1], szDimNameSecond) ==
12271
12
                            NC_NOERR &&
12272
209
                        !NCDFIsVarLongitude(nCdfId, -1, szDimNameSecond) &&
12273
209
                        !NCDFIsVarProjectionX(nCdfId, -1, szDimNameSecond) &&
12274
209
                        !NCDFIsVarLatitude(nCdfId, -1, szDimNameFirst) &&
12275
209
                        !NCDFIsVarProjectionY(nCdfId, -1, szDimNameFirst))
12276
12
                    {
12277
12
                        anPotentialVectorVarID.push_back(v);
12278
12
                        oMapDimIdToCount[anDimIds[0]]++;
12279
12
                        if (strstr(szDimNameSecond, "_max_width"))
12280
12
                        {
12281
12
                            bRasterCandidate = false;
12282
12
                        }
12283
0
                        else
12284
0
                        {
12285
0
                            std::array<int, 3> oKey{anDimIds[0], anDimIds[1],
12286
0
                                                    vartype};
12287
0
                            oMap2DDimsToGroupAndVar[oKey].emplace_back(
12288
0
                                std::pair(nCdfId, v));
12289
0
                        }
12290
12
                    }
12291
197
                    else
12292
197
                    {
12293
197
                        std::array<int, 3> oKey{anDimIds[0], anDimIds[1],
12294
197
                                                vartype};
12295
197
                        oMap2DDimsToGroupAndVar[oKey].emplace_back(
12296
197
                            std::pair(nCdfId, v));
12297
197
                        bIsVectorOnly = false;
12298
197
                    }
12299
209
                }
12300
265
                else
12301
265
                {
12302
265
                    bIsVectorOnly = false;
12303
265
                }
12304
474
                if (bKeepRasters && bRasterCandidate)
12305
0
                {
12306
0
                    *pnGroupId = nCdfId;
12307
0
                    *pnVarId = v;
12308
0
                    nRasterVars++;
12309
0
                }
12310
474
            }
12311
5.25k
            else if (nVarDims == 1)
12312
138
            {
12313
138
                nc_type atttype = NC_NAT;
12314
138
                size_t attlen = 0;
12315
138
                if (nc_inq_att(nCdfId, v, "instance_dimension", &atttype,
12316
138
                               &attlen) == NC_NOERR &&
12317
138
                    atttype == NC_CHAR && attlen < NC_MAX_NAME)
12318
0
                {
12319
0
                    char szInstanceDimension[NC_MAX_NAME + 1];
12320
0
                    if (nc_get_att_text(nCdfId, v, "instance_dimension",
12321
0
                                        szInstanceDimension) == NC_NOERR)
12322
0
                    {
12323
0
                        szInstanceDimension[attlen] = 0;
12324
0
                        int status = nc_inq_dimid(nCdfId, szInstanceDimension,
12325
0
                                                  &nProfileDimId);
12326
0
                        if (status == NC_NOERR)
12327
0
                            nParentIndexVarID = v;
12328
0
                        else
12329
0
                            nProfileDimId = -1;
12330
0
                        if (status == NC_EBADDIM)
12331
0
                            CPLError(CE_Warning, CPLE_AppDefined,
12332
0
                                     "Attribute instance_dimension='%s' refers "
12333
0
                                     "to a non existing dimension",
12334
0
                                     szInstanceDimension);
12335
0
                        else
12336
0
                            NCDF_ERR(status);
12337
0
                    }
12338
0
                }
12339
138
                if (v != nParentIndexVarID)
12340
138
                {
12341
138
                    anPotentialVectorVarID.push_back(v);
12342
138
                    int nDimId = -1;
12343
138
                    nc_inq_vardimid(nCdfId, v, &nDimId);
12344
138
                    oMapDimIdToCount[nDimId]++;
12345
138
                }
12346
138
            }
12347
5.79k
        }
12348
5.79k
    }
12349
12350
    // If we are opened in raster-only mode and that there are only 1D or 2D
12351
    // variables and that the 2D variables have no X/Y dim, and all
12352
    // variables refer to the same main dimension (or 2 dimensions for
12353
    // featureType=profile), then it is a pure vector dataset
12354
399
    CPLString osFeatureType(
12355
399
        CSLFetchNameValueDef(papszMetadata, "NC_GLOBAL#featureType", ""));
12356
399
    if (bKeepRasters && !bKeepVectors && bIsVectorOnly && nRasterVars > 0 &&
12357
399
        !anPotentialVectorVarID.empty() &&
12358
399
        (oMapDimIdToCount.size() == 1 ||
12359
0
         (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2 &&
12360
0
          nProfileDimId >= 0)))
12361
0
    {
12362
0
        anPotentialVectorVarID.resize(0);
12363
0
    }
12364
399
    else
12365
399
    {
12366
399
        *pnRasterVars += nRasterVars;
12367
399
    }
12368
12369
399
    if (!anPotentialVectorVarID.empty() && bKeepVectors)
12370
61
    {
12371
        // Take the dimension that is referenced the most times.
12372
61
        if (!(oMapDimIdToCount.size() == 1 ||
12373
61
              (EQUAL(osFeatureType, "profile") &&
12374
38
               oMapDimIdToCount.size() == 2 && nProfileDimId >= 0)))
12375
38
        {
12376
38
            CPLError(CE_Warning, CPLE_AppDefined,
12377
38
                     "The dataset has several variables that could be "
12378
38
                     "identified as vector fields, but not all share the same "
12379
38
                     "primary dimension. Consequently they will be ignored.");
12380
38
        }
12381
23
        else
12382
23
        {
12383
23
            if (nVarTimeId >= 0 &&
12384
23
                oMapDimIdToCount.find(nVarTimeDimId) != oMapDimIdToCount.end())
12385
0
            {
12386
0
                anPotentialVectorVarID.push_back(nVarTimeId);
12387
0
            }
12388
23
            CreateGrpVectorLayers(nCdfId, osFeatureType, anPotentialVectorVarID,
12389
23
                                  oMapDimIdToCount, nVarXId, nVarYId, nVarZId,
12390
23
                                  nProfileDimId, nParentIndexVarID,
12391
23
                                  bKeepRasters);
12392
23
        }
12393
61
    }
12394
12395
    // Recurse on sub-groups.
12396
399
    int nSubGroups = 0;
12397
399
    int *panSubGroupIds = nullptr;
12398
399
    NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
12399
399
    for (int i = 0; i < nSubGroups; i++)
12400
0
    {
12401
0
        FilterVars(panSubGroupIds[i], bKeepRasters, bKeepVectors,
12402
0
                   papszIgnoreVars, pnRasterVars, pnGroupId, pnVarId,
12403
0
                   pnIgnoredVars, oMap2DDimsToGroupAndVar);
12404
0
    }
12405
399
    CPLFree(panSubGroupIds);
12406
12407
399
    return CE_None;
12408
399
}
12409
12410
// Create vector layers from given potentially identified vector variables
12411
// resulting from the scanning of a NetCDF (or group) ID.
12412
CPLErr netCDFDataset::CreateGrpVectorLayers(
12413
    int nCdfId, const CPLString &osFeatureType,
12414
    const std::vector<int> &anPotentialVectorVarID,
12415
    const std::map<int, int> &oMapDimIdToCount, int nVarXId, int nVarYId,
12416
    int nVarZId, int nProfileDimId, int nParentIndexVarID, bool bKeepRasters)
12417
23
{
12418
23
    char *pszGroupName = nullptr;
12419
23
    NCDFGetGroupFullName(nCdfId, &pszGroupName);
12420
23
    if (pszGroupName == nullptr || pszGroupName[0] == '\0')
12421
23
    {
12422
23
        CPLFree(pszGroupName);
12423
23
        pszGroupName = CPLStrdup(CPLGetBasenameSafe(osFilename).c_str());
12424
23
    }
12425
23
    OGRwkbGeometryType eGType = wkbUnknown;
12426
23
    CPLString osLayerName = CSLFetchNameValueDef(
12427
23
        papszMetadata, "NC_GLOBAL#ogr_layer_name", pszGroupName);
12428
23
    CPLFree(pszGroupName);
12429
23
    papszMetadata =
12430
23
        CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_name", nullptr);
12431
12432
23
    if (EQUAL(osFeatureType, "point") || EQUAL(osFeatureType, "profile"))
12433
0
    {
12434
0
        papszMetadata =
12435
0
            CSLSetNameValue(papszMetadata, "NC_GLOBAL#featureType", nullptr);
12436
0
        eGType = wkbPoint;
12437
0
    }
12438
12439
23
    const char *pszLayerType =
12440
23
        CSLFetchNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_type");
12441
23
    if (pszLayerType != nullptr)
12442
0
    {
12443
0
        eGType = OGRFromOGCGeomType(pszLayerType);
12444
0
        papszMetadata =
12445
0
            CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_type", nullptr);
12446
0
    }
12447
12448
23
    CPLString osGeometryField =
12449
23
        CSLFetchNameValueDef(papszMetadata, "NC_GLOBAL#ogr_geometry_field", "");
12450
23
    papszMetadata =
12451
23
        CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_geometry_field", nullptr);
12452
12453
23
    int nFirstVarId = -1;
12454
23
    int nVectorDim = oMapDimIdToCount.rbegin()->first;
12455
23
    if (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2)
12456
0
    {
12457
0
        if (nVectorDim == nProfileDimId)
12458
0
            nVectorDim = oMapDimIdToCount.begin()->first;
12459
0
    }
12460
23
    else
12461
23
    {
12462
23
        nProfileDimId = -1;
12463
23
    }
12464
23
    for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)
12465
23
    {
12466
23
        int anDimIds[2] = {-1, -1};
12467
23
        nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);
12468
23
        if (nVectorDim == anDimIds[0])
12469
23
        {
12470
23
            nFirstVarId = anPotentialVectorVarID[j];
12471
23
            break;
12472
23
        }
12473
23
    }
12474
12475
    // In case where coordinates are explicitly specified for one of the
12476
    // field/variable, use them in priority over the ones that might have been
12477
    // identified above.
12478
23
    char *pszCoordinates = nullptr;
12479
23
    if (NCDFGetAttr(nCdfId, nFirstVarId, "coordinates", &pszCoordinates) ==
12480
23
        CE_None)
12481
9
    {
12482
9
        char **papszTokens = NCDFTokenizeCoordinatesAttribute(pszCFCoordinates);
12483
9
        for (int i = 0; papszTokens != nullptr && papszTokens[i] != nullptr;
12484
9
             i++)
12485
0
        {
12486
0
            if (NCDFIsVarLongitude(nCdfId, -1, papszTokens[i]) ||
12487
0
                NCDFIsVarProjectionX(nCdfId, -1, papszTokens[i]))
12488
0
            {
12489
0
                nVarXId = -1;
12490
0
                CPL_IGNORE_RET_VAL(
12491
0
                    nc_inq_varid(nCdfId, papszTokens[i], &nVarXId));
12492
0
            }
12493
0
            else if (NCDFIsVarLatitude(nCdfId, -1, papszTokens[i]) ||
12494
0
                     NCDFIsVarProjectionY(nCdfId, -1, papszTokens[i]))
12495
0
            {
12496
0
                nVarYId = -1;
12497
0
                CPL_IGNORE_RET_VAL(
12498
0
                    nc_inq_varid(nCdfId, papszTokens[i], &nVarYId));
12499
0
            }
12500
0
            else if (NCDFIsVarVerticalCoord(nCdfId, -1, papszTokens[i]))
12501
0
            {
12502
0
                nVarZId = -1;
12503
0
                CPL_IGNORE_RET_VAL(
12504
0
                    nc_inq_varid(nCdfId, papszTokens[i], &nVarZId));
12505
0
            }
12506
0
        }
12507
9
        CSLDestroy(papszTokens);
12508
9
    }
12509
23
    CPLFree(pszCoordinates);
12510
12511
    // Check that the X,Y,Z vars share 1D and share the same dimension as
12512
    // attribute variables.
12513
23
    if (nVarXId >= 0 && nVarYId >= 0)
12514
0
    {
12515
0
        int nVarDimCount = -1;
12516
0
        int nVarDimId = -1;
12517
0
        if (nc_inq_varndims(nCdfId, nVarXId, &nVarDimCount) != NC_NOERR ||
12518
0
            nVarDimCount != 1 ||
12519
0
            nc_inq_vardimid(nCdfId, nVarXId, &nVarDimId) != NC_NOERR ||
12520
0
            nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim) ||
12521
0
            nc_inq_varndims(nCdfId, nVarYId, &nVarDimCount) != NC_NOERR ||
12522
0
            nVarDimCount != 1 ||
12523
0
            nc_inq_vardimid(nCdfId, nVarYId, &nVarDimId) != NC_NOERR ||
12524
0
            nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim))
12525
0
        {
12526
0
            nVarXId = nVarYId = -1;
12527
0
        }
12528
0
        else if (nVarZId >= 0 &&
12529
0
                 (nc_inq_varndims(nCdfId, nVarZId, &nVarDimCount) != NC_NOERR ||
12530
0
                  nVarDimCount != 1 ||
12531
0
                  nc_inq_vardimid(nCdfId, nVarZId, &nVarDimId) != NC_NOERR ||
12532
0
                  nVarDimId != nVectorDim))
12533
0
        {
12534
0
            nVarZId = -1;
12535
0
        }
12536
0
    }
12537
12538
23
    if (eGType == wkbUnknown && nVarXId >= 0 && nVarYId >= 0)
12539
0
    {
12540
0
        eGType = wkbPoint;
12541
0
    }
12542
23
    if (eGType == wkbPoint && nVarXId >= 0 && nVarYId >= 0 && nVarZId >= 0)
12543
0
    {
12544
0
        eGType = wkbPoint25D;
12545
0
    }
12546
23
    if (eGType == wkbUnknown && osGeometryField.empty())
12547
23
    {
12548
23
        eGType = wkbNone;
12549
23
    }
12550
12551
    // Read projection info
12552
23
    char **papszMetadataBackup = CSLDuplicate(papszMetadata);
12553
23
    ReadAttributes(nCdfId, nFirstVarId);
12554
23
    if (!this->bSGSupport)
12555
23
        SetProjectionFromVar(nCdfId, nFirstVarId, true);
12556
23
    const char *pszValue = FetchAttr(nCdfId, nFirstVarId, CF_GRD_MAPPING);
12557
23
    char *pszGridMapping = (pszValue ? CPLStrdup(pszValue) : nullptr);
12558
23
    CSLDestroy(papszMetadata);
12559
23
    papszMetadata = papszMetadataBackup;
12560
12561
23
    OGRSpatialReference *poSRS = nullptr;
12562
23
    if (!m_oSRS.IsEmpty())
12563
0
    {
12564
0
        poSRS = m_oSRS.Clone();
12565
0
    }
12566
    // Reset if there's a 2D raster
12567
23
    m_bHasProjection = false;
12568
23
    m_bHasGeoTransform = false;
12569
12570
23
    if (!bKeepRasters)
12571
23
    {
12572
        // Strip out uninteresting metadata.
12573
23
        papszMetadata =
12574
23
            CSLSetNameValue(papszMetadata, "NC_GLOBAL#Conventions", nullptr);
12575
23
        papszMetadata =
12576
23
            CSLSetNameValue(papszMetadata, "NC_GLOBAL#GDAL", nullptr);
12577
23
        papszMetadata =
12578
23
            CSLSetNameValue(papszMetadata, "NC_GLOBAL#history", nullptr);
12579
23
    }
12580
12581
23
    std::shared_ptr<netCDFLayer> poLayer(
12582
23
        new netCDFLayer(this, nCdfId, osLayerName, eGType, poSRS));
12583
23
    if (poSRS != nullptr)
12584
0
        poSRS->Release();
12585
23
    poLayer->SetRecordDimID(nVectorDim);
12586
23
    if (wkbFlatten(eGType) == wkbPoint && nVarXId >= 0 && nVarYId >= 0)
12587
0
    {
12588
0
        poLayer->SetXYZVars(nVarXId, nVarYId, nVarZId);
12589
0
    }
12590
23
    else if (!osGeometryField.empty())
12591
0
    {
12592
0
        poLayer->SetWKTGeometryField(osGeometryField);
12593
0
    }
12594
23
    if (pszGridMapping != nullptr)
12595
0
    {
12596
0
        poLayer->SetGridMapping(pszGridMapping);
12597
0
        CPLFree(pszGridMapping);
12598
0
    }
12599
23
    poLayer->SetProfile(nProfileDimId, nParentIndexVarID);
12600
12601
64
    for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)
12602
41
    {
12603
41
        int anDimIds[2] = {-1, -1};
12604
41
        nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);
12605
41
        if (anDimIds[0] == nVectorDim ||
12606
41
            (nProfileDimId >= 0 && anDimIds[0] == nProfileDimId))
12607
41
        {
12608
#ifdef NCDF_DEBUG
12609
            char szTemp2[NC_MAX_NAME + 1] = {};
12610
            CPL_IGNORE_RET_VAL(
12611
                nc_inq_varname(nCdfId, anPotentialVectorVarID[j], szTemp2));
12612
            CPLDebug("GDAL_netCDF", "Variable %s is a vector field", szTemp2);
12613
#endif
12614
41
            poLayer->AddField(anPotentialVectorVarID[j]);
12615
41
        }
12616
41
    }
12617
12618
23
    if (poLayer->GetLayerDefn()->GetFieldCount() != 0 ||
12619
23
        poLayer->GetGeomType() != wkbNone)
12620
23
    {
12621
23
        papoLayers.push_back(poLayer);
12622
23
    }
12623
12624
23
    return CE_None;
12625
23
}
12626
12627
// Get all coordinate and boundary variables full names referenced in
12628
// a given a NetCDF (or group) ID and its sub-groups.
12629
// These variables are identified in other variable's
12630
// "coordinates" and "bounds" attribute.
12631
// Searching coordinate and boundary variables may need to explore
12632
// parents groups (or other groups in case of reference given in form of an
12633
// absolute path).
12634
// See CF sections 5.2, 5.6 and 7.1
12635
static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId, char ***ppapszVars)
12636
399
{
12637
399
    int nVars = 0;
12638
399
    NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
12639
12640
6.19k
    for (int v = 0; v < nVars; v++)
12641
5.79k
    {
12642
5.79k
        char *pszTemp = nullptr;
12643
5.79k
        char **papszTokens = nullptr;
12644
5.79k
        if (NCDFGetAttr(nCdfId, v, "coordinates", &pszTemp) == CE_None)
12645
92
            papszTokens = NCDFTokenizeCoordinatesAttribute(pszTemp);
12646
5.79k
        CPLFree(pszTemp);
12647
5.79k
        pszTemp = nullptr;
12648
5.79k
        if (NCDFGetAttr(nCdfId, v, "bounds", &pszTemp) == CE_None &&
12649
5.79k
            pszTemp != nullptr && !EQUAL(pszTemp, ""))
12650
0
            papszTokens = CSLAddString(papszTokens, pszTemp);
12651
5.79k
        CPLFree(pszTemp);
12652
115k
        for (int i = 0; papszTokens != nullptr && papszTokens[i] != nullptr;
12653
109k
             i++)
12654
109k
        {
12655
109k
            char *pszVarFullName = nullptr;
12656
109k
            if (NCDFResolveVarFullName(nCdfId, papszTokens[i],
12657
109k
                                       &pszVarFullName) == CE_None)
12658
753
                *ppapszVars = CSLAddString(*ppapszVars, pszVarFullName);
12659
109k
            CPLFree(pszVarFullName);
12660
109k
        }
12661
5.79k
        CSLDestroy(papszTokens);
12662
5.79k
    }
12663
12664
    // Recurse on sub-groups.
12665
399
    int nSubGroups;
12666
399
    int *panSubGroupIds = nullptr;
12667
399
    NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
12668
399
    for (int i = 0; i < nSubGroups; i++)
12669
0
    {
12670
0
        NCDFGetCoordAndBoundVarFullNames(panSubGroupIds[i], ppapszVars);
12671
0
    }
12672
399
    CPLFree(panSubGroupIds);
12673
12674
399
    return CE_None;
12675
399
}
12676
12677
// Check if give type is user defined
12678
bool NCDFIsUserDefinedType(int /*ncid*/, int type)
12679
0
{
12680
0
    return type >= NC_FIRSTUSERTYPEID;
12681
0
}
12682
12683
char **NCDFTokenizeCoordinatesAttribute(const char *pszCoordinates)
12684
101
{
12685
    // CF conventions use space as the separator for variable names in the
12686
    // coordinates attribute, but some products such as
12687
    // https://sentinel.esa.int/web/sentinel/technical-guides/sentinel-3-synergy/products-algorithms/level-2-aod-algorithms-and-products/level-2-aod-products-description
12688
    // use comma.
12689
101
    return CSLTokenizeString2(pszCoordinates, ", ", 0);
12690
101
}