Coverage Report

Created: 2026-11-12 08:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/netcdf/netcdfdrivercore.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  netCDF driver
5
 * Author:   Even Rouault, <even.rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2023, Even Rouault, <even.rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "ogrsf_frmts.h"
14
#include "gdal_priv.h"
15
16
#include "gdal_frmts.h"
17
#include "gdalplugindriverproxy.h"
18
#include "gdalsubdatasetinfo.h"
19
20
#include "netcdfdrivercore.h"
21
22
#include <algorithm>
23
#include <cctype>
24
#include <string_view>
25
26
#ifdef HAS_NETCDF_H
27
#include "netcdfdataset.h"
28
#ifdef NETCDF_HAS_NC2
29
#define NETCDF_CORE_HAS_NC2 1
30
#endif
31
#else
32
// We don't have an easy way to guess that without accessing netcdf.h, so
33
// assume it is present
34
#ifndef NETCDF_CORE_HAS_NC2
35
#define NETCDF_CORE_HAS_NC2 1
36
#endif
37
#endif
38
39
/************************************************************************/
40
/*                        netCDFIdentifyFormat()                        */
41
/************************************************************************/
42
43
NetCDFFormatEnum netCDFIdentifyFormat(GDALOpenInfo *poOpenInfo, bool bCheckExt)
44
800k
{
45
    // Does this appear to be a netcdf file? If so, which format?
46
    // http://www.unidata.ucar.edu/software/netcdf/docs/faq.html#fv1_5
47
48
800k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
49
0
        return NCDF_FORMAT_UNKNOWN;
50
800k
    if (poOpenInfo->nHeaderBytes < 4)
51
488k
        return NCDF_FORMAT_NONE;
52
312k
    const char *pszHeader =
53
312k
        reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
54
55
312k
#ifdef ENABLE_NCDUMP
56
312k
    if (poOpenInfo->fpL != nullptr && STARTS_WITH(pszHeader, "netcdf ") &&
57
50.0k
        strstr(pszHeader, "dimensions:") && strstr(pszHeader, "variables:"))
58
48.6k
    {
59
48.6k
        if (strstr(pszHeader, "// NC4C"))
60
6.95k
            return NCDF_FORMAT_NC4C;
61
41.7k
        else if (strstr(pszHeader, "// NC4"))
62
5.14k
            return NCDF_FORMAT_NC4;
63
36.5k
        else
64
36.5k
            return NCDF_FORMAT_NC;
65
48.6k
    }
66
263k
#endif  // ENABLE_NCDUMP
67
68
263k
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
69
    // We don't necessarily want to catch bugs in libnetcdf ...
70
263k
    if (CPLGetConfigOption("DISABLE_OPEN_REAL_NETCDF_FILES", nullptr))
71
263k
    {
72
263k
        return NCDF_FORMAT_NONE;
73
263k
    }
74
0
#endif
75
76
0
    if (STARTS_WITH_CI(pszHeader, "CDF\001"))
77
0
    {
78
0
        return NCDF_FORMAT_NC;
79
0
    }
80
81
0
    if (STARTS_WITH_CI(pszHeader, "CDF\002"))
82
0
    {
83
0
        return NCDF_FORMAT_NC2;
84
0
    }
85
86
0
    constexpr char HDF5_SIG[] = "\211HDF\r\n\032\n";
87
0
    constexpr int HDF5_SIG_LEN = int(std::char_traits<char>::length(HDF5_SIG));
88
0
    static_assert(HDF5_SIG_LEN == 8);
89
    // First non-zero offset at which the HDF5 signature can be found.
90
0
    constexpr int HDF5_SIG_OFFSET = 512;
91
0
    if (STARTS_WITH_CI(pszHeader, HDF5_SIG) ||
92
0
        (poOpenInfo->nHeaderBytes > HDF5_SIG_OFFSET + HDF5_SIG_LEN &&
93
0
         memcmp(pszHeader + HDF5_SIG_OFFSET, HDF5_SIG, HDF5_SIG_LEN) == 0))
94
0
    {
95
        // Requires netCDF-4/HDF5 support in libnetcdf (not just libnetcdf-v4).
96
97
        // If only the netCDF driver is allowed, immediately recognize the file
98
0
        if (poOpenInfo->IsSingleAllowedDriver("netCDF"))
99
0
        {
100
0
            return NCDF_FORMAT_NC4;
101
0
        }
102
103
        // If there is a HDF5 GDAL driver available, delegate to it.
104
        // Otherwise open it with this driver.
105
        // If the user really wants to open with this driver, use NETCDF:file.h5
106
        // format.
107
108
        // Check for HDF5 driver availability.
109
0
        if (bCheckExt)
110
0
        {
111
            // Check by default.
112
0
            const char *pszExtension = poOpenInfo->osExtension.c_str();
113
0
            if (!(EQUAL(pszExtension, "nc") || EQUAL(pszExtension, "cdf") ||
114
0
                  EQUAL(pszExtension, "nc2") || EQUAL(pszExtension, "nc4") ||
115
0
                  EQUAL(pszExtension, "nc3") || EQUAL(pszExtension, "grd") ||
116
0
                  EQUAL(pszExtension, "gmac")))
117
0
            {
118
0
                if (GDALGetDriverByName("HDF5") != nullptr)
119
0
                {
120
0
                    return NCDF_FORMAT_HDF5;
121
0
                }
122
0
            }
123
0
        }
124
125
0
        return NCDF_FORMAT_NC4;
126
0
    }
127
0
    else if (STARTS_WITH_CI(pszHeader, "\016\003\023\001"))
128
0
    {
129
#ifdef NETCDF_HAS_HDF4
130
        // There is HDF4 support in libnetcdf and only the netCDF driver is
131
        // allowed ==> immediately recognize the file.
132
        if (poOpenInfo->IsSingleAllowedDriver("netCDF"))
133
        {
134
            return NCDF_FORMAT_HDF4;
135
        }
136
#endif
137
138
        // If there is a HDF4 GDAL driver available, delegate to it.
139
        // Otherwise open it with this driver.
140
        // If the user really wants to open with this driver, use NETCDF:file.hdf
141
        // format.
142
143
        // Check for HDF4 driver availability.
144
0
        if (bCheckExt && GDALGetDriverByName("HDF4") != nullptr)
145
0
        {
146
            // Check by default.
147
            // Always treat as HDF4 file.
148
0
            return NCDF_FORMAT_HDF4;
149
0
        }
150
151
#ifdef NETCDF_HAS_HDF4
152
        // There is HDF4 support in libnetcdf.
153
        return NCDF_FORMAT_HDF4;
154
#else
155
0
        return NCDF_FORMAT_NONE;
156
0
#endif
157
0
    }
158
159
    // The HDF5 signature of netCDF 4 files can be at offsets 512, 1024, 2048,
160
    // etc.
161
0
    const char *pszExtension = poOpenInfo->osExtension.c_str();
162
0
    if (poOpenInfo->fpL != nullptr &&
163
0
        (!bCheckExt || EQUAL(pszExtension, "nc") ||
164
0
         EQUAL(pszExtension, "cdf") || EQUAL(pszExtension, "nc4") ||
165
0
         poOpenInfo->IsSingleAllowedDriver("netCDF")))
166
0
    {
167
0
        vsi_l_offset nOffset = HDF5_SIG_OFFSET;
168
0
        for (int i = 0; i < 64; i++)
169
0
        {
170
0
            GByte abyBuf[HDF5_SIG_LEN];
171
0
            if (VSIFSeekL(poOpenInfo->fpL, nOffset, SEEK_SET) != 0 ||
172
0
                VSIFReadL(abyBuf, 1, HDF5_SIG_LEN, poOpenInfo->fpL) !=
173
0
                    HDF5_SIG_LEN)
174
0
            {
175
0
                break;
176
0
            }
177
0
            if (memcmp(abyBuf, HDF5_SIG, HDF5_SIG_LEN) == 0)
178
0
            {
179
0
                return NCDF_FORMAT_NC4;
180
0
            }
181
0
            nOffset *= 2;
182
0
        }
183
0
    }
184
185
0
    return NCDF_FORMAT_NONE;
186
0
}
187
188
/************************************************************************/
189
/*                              Identify()                              */
190
/************************************************************************/
191
192
static int netCDFDatasetIdentify(GDALOpenInfo *poOpenInfo)
193
194
774k
{
195
774k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
196
7
    {
197
7
        return TRUE;
198
7
    }
199
774k
    const NetCDFFormatEnum eTmpFormat =
200
774k
        netCDFIdentifyFormat(poOpenInfo,
201
774k
                             /* bCheckExt = */ true);
202
774k
    if (NCDF_FORMAT_NC == eTmpFormat || NCDF_FORMAT_NC2 == eTmpFormat ||
203
757k
        NCDF_FORMAT_NC4 == eTmpFormat || NCDF_FORMAT_NC4C == eTmpFormat)
204
23.0k
        return TRUE;
205
751k
    if (eTmpFormat == NCDF_FORMAT_HDF4 &&
206
0
        poOpenInfo->IsSingleAllowedDriver("netCDF"))
207
0
    {
208
0
        return TRUE;
209
0
    }
210
211
751k
    return FALSE;
212
751k
}
213
214
/************************************************************************/
215
/*                    NCDFDriverGetSubdatasetInfo()                     */
216
/************************************************************************/
217
218
struct NCDFDriverSubdatasetInfo final : public GDALSubdatasetInfo
219
{
220
  public:
221
    explicit NCDFDriverSubdatasetInfo(const std::string &fileName)
222
0
        : GDALSubdatasetInfo(fileName)
223
0
    {
224
0
    }
225
226
    // GDALSubdatasetInfo interface
227
  private:
228
    void parseFileName() override;
229
};
230
231
void NCDFDriverSubdatasetInfo::parseFileName()
232
0
{
233
234
0
    if (!STARTS_WITH_CI(m_fileName.c_str(), "NETCDF:"))
235
0
    {
236
0
        return;
237
0
    }
238
239
0
    CPLStringList aosParts{CSLTokenizeString2(
240
0
        m_fileName.c_str(), ":", CSLT_HONOURSTRINGS | CSLT_PRESERVEQUOTES)};
241
0
    const int iPartsCount{CSLCount(aosParts)};
242
243
0
    m_driverPrefixComponent = aosParts[0];
244
0
    if (iPartsCount == 1)
245
0
    {
246
0
        return;
247
0
    }
248
249
0
    if (iPartsCount == 2)
250
0
    {
251
0
        m_pathComponent = aosParts[1];
252
0
        return;
253
0
    }
254
255
0
    int subdatasetIndex{2};
256
257
0
    std::string part1{aosParts[1]};
258
0
    if (!part1.empty() && part1[0] == '"')
259
0
    {
260
0
        part1 = part1.substr(1);
261
0
    }
262
263
0
    const bool hasDriveLetter{
264
0
        (strlen(aosParts[2]) > 1 &&
265
0
         (aosParts[2][0] == '\\' || aosParts[2][0] == '/')) &&
266
0
        part1.length() == 1 &&
267
0
        std::isalpha(static_cast<unsigned char>(part1.at(0)))};
268
269
0
    const bool hasProtocol{part1 == "/vsicurl/http" ||
270
0
                           part1 == "/vsicurl/https" ||
271
0
                           part1 == "/vsicurl_streaming/http" ||
272
0
                           part1 == "/vsicurl_streaming/https" ||
273
0
                           part1 == "http" || part1 == "https"};
274
275
0
    m_pathComponent = aosParts[1];
276
0
    if (hasDriveLetter || hasProtocol)
277
0
    {
278
0
        m_pathComponent.append(":");
279
0
        m_pathComponent.append(aosParts[2]);
280
0
        subdatasetIndex++;
281
282
        // Check for a port number and add it to the path component.
283
0
        if (hasProtocol && aosParts.size() > 3)
284
0
        {
285
0
            const auto nChars = strlen(aosParts[3]);
286
0
            size_t i = 0;
287
0
            while (i < nChars && std::isdigit(aosParts[3][i]))
288
0
            {
289
0
                i++;
290
0
            }
291
0
            if (i > 0 && aosParts[3][i] == '/')
292
0
            {
293
0
                m_pathComponent.append(":");
294
0
                m_pathComponent.append(aosParts[3]);
295
0
                subdatasetIndex++;
296
0
            }
297
0
        }
298
0
    }
299
300
    // Check for bogus paths
301
0
    if (subdatasetIndex < iPartsCount)
302
0
    {
303
0
        m_subdatasetComponent = aosParts[subdatasetIndex];
304
305
        // Append any remaining part
306
0
        for (int i = subdatasetIndex + 1; i < iPartsCount; ++i)
307
0
        {
308
0
            m_subdatasetComponent.append(":");
309
0
            m_subdatasetComponent.append(aosParts[i]);
310
0
        }
311
0
    }
312
313
    // Remove quotes from subdataset component
314
0
    if (!m_subdatasetComponent.empty() && m_subdatasetComponent[0] == '"')
315
0
    {
316
0
        m_subdatasetComponent = m_subdatasetComponent.substr(1);
317
0
    }
318
0
    if (!m_subdatasetComponent.empty() &&
319
0
        m_subdatasetComponent.rfind('"') == m_subdatasetComponent.length() - 1)
320
0
    {
321
0
        m_subdatasetComponent.pop_back();
322
0
    }
323
0
}
324
325
static GDALSubdatasetInfo *NCDFDriverGetSubdatasetInfo(const char *pszFileName)
326
7.36k
{
327
7.36k
    if (STARTS_WITH_CI(pszFileName, "NETCDF:"))
328
0
    {
329
0
        std::unique_ptr<GDALSubdatasetInfo> info =
330
0
            std::make_unique<NCDFDriverSubdatasetInfo>(pszFileName);
331
        // Subdataset component can be empty, path cannot.
332
0
        if (!info->GetPathComponent().empty())
333
0
        {
334
0
            return info.release();
335
0
        }
336
0
    }
337
7.36k
    return nullptr;
338
7.36k
}
339
340
/************************************************************************/
341
/*                   netCDFDriverSetCommonMetadata()                    */
342
/************************************************************************/
343
344
void netCDFDriverSetCommonMetadata(GDALDriver *poDriver)
345
24
{
346
    // Set the driver details.
347
24
    poDriver->SetDescription(DRIVER_NAME);
348
24
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
349
24
    poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
350
24
    poDriver->SetMetadataItem(GDAL_DCAP_CREATE_LAYER, "YES");
351
24
    poDriver->SetMetadataItem(GDAL_DCAP_CREATE_FIELD, "YES");
352
24
    poDriver->SetMetadataItem(GDAL_DCAP_Z_GEOMETRIES, "YES");
353
24
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Network Common Data Format");
354
24
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/netcdf.html");
355
24
    poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "nc");
356
357
24
    poDriver->SetMetadataItem(
358
24
        GDAL_DMD_OPENOPTIONLIST,
359
24
        "<OpenOptionList>"
360
24
        "   <Option name='LIST_ALL_ARRAYS' type='boolean' "
361
24
        "description='Whether to list all arrays, and not only those whose "
362
24
        "dimension count is 2 or more' default='NO'/>"
363
24
        "   <Option name='HONOUR_VALID_RANGE' type='boolean' scope='raster' "
364
24
        "description='Whether to set to nodata pixel values outside of the "
365
24
        "validity range' default='YES'/>"
366
24
        "   <Option name='IGNORE_XY_AXIS_NAME_CHECKS' type='boolean' "
367
24
        "scope='raster' "
368
24
        "description='Whether X/Y dimensions should be always considered as "
369
24
        "geospatial axis, even if the lack conventional attributes confirming "
370
24
        "it.'"
371
24
        " default='NO'/>"
372
24
        "   <Option name='VARIABLES_AS_BANDS' type='boolean' scope='raster' "
373
24
        "description='Whether 2D variables that share the same indexing "
374
24
        "dimensions "
375
24
        "should be exposed as several bands of a same dataset instead of "
376
24
        "several "
377
24
        "subdatasets.' default='NO'/>"
378
24
        "   <Option name='ASSUME_LONGLAT' type='boolean' scope='raster' "
379
24
        "description='Whether when all else has failed for determining a CRS, "
380
24
        "a "
381
24
        "meaningful geotransform has been found, and is within the  "
382
24
        "bounds -180,360 -90,90, assume OGC:CRS84.' default='NO'/>"
383
24
        "   <Option name='PRESERVE_AXIS_UNIT_IN_CRS' type='boolean' "
384
24
        "scope='raster' description='Whether unusual linear axis unit (km) "
385
24
        "should be kept as such, instead of being normalized to metre' "
386
24
        "default='NO'/>"
387
24
        "</OpenOptionList>");
388
24
    poDriver->SetMetadataItem(
389
24
        GDAL_DMD_CREATIONDATATYPES,
390
24
        "Byte Int8 UInt16 Int16 UInt32 Int32 Int64 UInt64 "
391
24
        "Float32 Float64 "
392
24
        "CInt16 CInt32 CFloat32 CFloat64");
393
24
    poDriver->SetMetadataItem(
394
24
        GDAL_DMD_CREATIONOPTIONLIST,
395
24
        "<CreationOptionList>"
396
24
        "   <Option name='FORMAT' type='string-select' default='NC'>"
397
24
        "     <Value>NC</Value>"
398
24
#if NETCDF_CORE_HAS_NC2
399
24
        "     <Value>NC2</Value>"
400
24
#endif
401
24
        "     <Value>NC4</Value>"
402
24
        "     <Value>NC4C</Value>"
403
24
        "   </Option>"
404
24
        "   <Option name='COMPRESS' type='string-select' scope='raster' "
405
24
        "default='NONE'>"
406
24
        "     <Value>NONE</Value>"
407
24
        "     <Value>DEFLATE</Value>"
408
24
        "   </Option>"
409
24
        "   <Option name='ZLEVEL' type='int' scope='raster' "
410
24
        "description='DEFLATE compression level 1-9' default='1'/>"
411
24
        "   <Option name='WRITE_BOTTOMUP' type='boolean' scope='raster' "
412
24
        "default='YES'>"
413
24
        "   </Option>"
414
24
        "   <Option name='WRITE_GDAL_TAGS' type='boolean' default='YES'>"
415
24
        "   </Option>"
416
24
        "   <Option name='WRITE_LONLAT' type='string-select' scope='raster'>"
417
24
        "     <Value>YES</Value>"
418
24
        "     <Value>NO</Value>"
419
24
        "     <Value>IF_NEEDED</Value>"
420
24
        "   </Option>"
421
24
        "   <Option name='TYPE_LONLAT' type='string-select' scope='raster'>"
422
24
        "     <Value>float</Value>"
423
24
        "     <Value>double</Value>"
424
24
        "   </Option>"
425
24
        "   <Option name='PIXELTYPE' type='string-select' scope='raster' "
426
24
        "description='(deprecated, use Int8 datatype) only used in Create()'>"
427
24
        "       <Value>DEFAULT</Value>"
428
24
        "       <Value>SIGNEDBYTE</Value>"
429
24
        "   </Option>"
430
24
        "   <Option name='CHUNKING' type='boolean' scope='raster' "
431
24
        "default='YES' description='define chunking when creating netcdf4 "
432
24
        "file'/>"
433
24
        "   <Option name='MULTIPLE_LAYERS' type='string-select' scope='vector' "
434
24
        "description='Behaviour regarding multiple vector layer creation' "
435
24
        "default='NO'>"
436
24
        "       <Value>NO</Value>"
437
24
        "       <Value>SEPARATE_FILES</Value>"
438
24
        "       <Value>SEPARATE_GROUPS</Value>"
439
24
        "   </Option>"
440
24
        "   <Option name='GEOMETRY_ENCODING' type='string' scope='vector' "
441
24
        "default='CF_1.8' description='Specifies the type of geometry encoding "
442
24
        "when creating a netCDF dataset'>"
443
24
        "       <Value>WKT</Value>"
444
24
        "       <Value>CF_1.8</Value>"
445
24
        "   </Option>"
446
24
        "   <Option name='CONFIG_FILE' type='string' scope='vector' "
447
24
        "description='Path to a XML configuration file (or content inlined)'/>"
448
24
        "   <Option name='WRITE_GDAL_VERSION' type='boolean' default='YES'/>"
449
24
        "   <Option name='WRITE_GDAL_HISTORY' type='boolean' default='YES'/>"
450
24
        "   <Option name='BAND_NAMES' type='string' scope='raster' />"
451
24
        "</CreationOptionList>");
452
24
    poDriver->SetMetadataItem(GDAL_DMD_SUBDATASETS, "YES");
453
454
24
    poDriver->SetMetadataItem(
455
24
        GDAL_DS_LAYER_CREATIONOPTIONLIST,
456
24
        "<LayerCreationOptionList>"
457
24
        "   <Option name='RECORD_DIM_NAME' type='string' description='Name of "
458
24
        "the unlimited dimension' default='record'/>"
459
24
        "   <Option name='STRING_DEFAULT_WIDTH' type='int' description='"
460
24
        "For non-NC4 format, "
461
24
        "default width of strings. Default is 10 in autogrow mode, 80 "
462
24
        "otherwise.'/>"
463
24
        "   <Option name='WKT_DEFAULT_WIDTH' type='int' description='"
464
24
        "For non-NC4 format, "
465
24
        "default width of WKT strings. Default is 1000 in autogrow mode, 10000 "
466
24
        "otherwise.'/>"
467
24
        "   <Option name='AUTOGROW_STRINGS' type='boolean' "
468
24
        "description='Whether to auto-grow non-bounded string fields of "
469
24
        "bidimensional char variable' default='YES'/>"
470
24
        "   <Option name='USE_STRING_IN_NC4' type='boolean' "
471
24
        "description='Whether to use NetCDF string type for strings in NC4 "
472
24
        "format. If NO, bidimensional char variable are used' default='YES'/>"
473
#if 0
474
"   <Option name='NCDUMP_COMPAT' type='boolean' description='When USE_STRING_IN_NC4=YEs, whether to use empty string instead of null string to avoid crashes with ncdump' default='NO'/>"
475
#endif
476
24
        "   <Option name='FEATURE_TYPE' type='string-select' description='CF "
477
24
        "FeatureType' default='AUTO'>"
478
24
        "       <Value>AUTO</Value>"
479
24
        "       <Value>POINT</Value>"
480
24
        "       <Value>PROFILE</Value>"
481
24
        "   </Option>"
482
24
        "   <Option name='BUFFER_SIZE' type='int' default='' "
483
24
        "description='Specifies the soft limit of buffer translation in bytes. "
484
24
        "Minimum size is 4096. Does not apply to datasets with CF version less "
485
24
        "than 1.8.'/>"
486
24
        "   <Option name='GROUPLESS_WRITE_BACK' type='boolean' default='NO' "
487
24
        "description='Enables or disables array building write back for "
488
24
        "CF-1.8.'/>"
489
24
        "   <Option name='PROFILE_DIM_NAME' type='string' description='Name of "
490
24
        "the profile dimension and variable' default='profile'/>"
491
24
        "   <Option name='PROFILE_DIM_INIT_SIZE' type='string' "
492
24
        "description='Initial size of profile dimension (default 100), or "
493
24
        "UNLIMITED for NC4 files'/>"
494
24
        "   <Option name='PROFILE_VARIABLES' type='string' description='Comma "
495
24
        "separated list of field names that must be indexed by the profile "
496
24
        "dimension'/>"
497
24
        "</LayerCreationOptionList>");
498
499
    // Make driver config and capabilities available.
500
24
#if NETCDF_CORE_HAS_NC2
501
24
    poDriver->SetMetadataItem("NETCDF_HAS_NC2", "YES");
502
24
#endif
503
24
    poDriver->SetMetadataItem("NETCDF_HAS_NC4", "YES");
504
#ifdef NETCDF_HAS_HDF4
505
    poDriver->SetMetadataItem("NETCDF_HAS_HDF4", "YES");
506
#endif
507
508
24
    poDriver->SetMetadataItem("NETCDF_HAS_NETCDF_MEM", "YES");
509
510
24
#ifdef ENABLE_NCDUMP
511
24
    poDriver->SetMetadataItem("ENABLE_NCDUMP", "YES");
512
24
#endif
513
514
24
    poDriver->SetMetadataItem(GDAL_DCAP_MULTIDIM_RASTER, "YES");
515
516
24
    poDriver->SetMetadataItem(
517
24
        GDAL_DMD_MULTIDIM_DATASET_CREATIONOPTIONLIST,
518
24
        "<MultiDimDatasetCreationOptionList>"
519
24
        "   <Option name='FORMAT' type='string-select' default='NC4'>"
520
24
        "     <Value>NC</Value>"
521
24
#if NETCDF_CORE_HAS_NC2
522
24
        "     <Value>NC2</Value>"
523
24
#endif
524
24
        "     <Value>NC4</Value>"
525
24
        "     <Value>NC4C</Value>"
526
24
        "   </Option>"
527
24
        "   <Option name='CONVENTIONS' type='string' default='CF-1.6' "
528
24
        "description='Value of the Conventions attribute'/>"
529
24
        "</MultiDimDatasetCreationOptionList>");
530
531
24
    poDriver->SetMetadataItem(
532
24
        GDAL_DMD_MULTIDIM_DIMENSION_CREATIONOPTIONLIST,
533
24
        "<MultiDimDimensionCreationOptionList>"
534
24
        "   <Option name='UNLIMITED' type='boolean' description='Whether the "
535
24
        "dimension should be unlimited' default='false'/>"
536
24
        "</MultiDimDimensionCreationOptionList>");
537
538
24
    poDriver->SetMetadataItem(
539
24
        GDAL_DMD_MULTIDIM_ARRAY_CREATIONOPTIONLIST,
540
24
        "<MultiDimArrayCreationOptionList>"
541
24
        "   <Option name='BLOCKSIZE' type='int' description='Block size in "
542
24
        "pixels'/>"
543
24
        "   <Option name='COMPRESS' type='string-select' default='NONE'>"
544
24
        "     <Value>NONE</Value>"
545
24
        "     <Value>DEFLATE</Value>"
546
24
        "   </Option>"
547
24
        "   <Option name='ZLEVEL' type='int' description='DEFLATE compression "
548
24
        "level 1-9' default='1'/>"
549
24
        "   <Option name='NC_TYPE' type='string-select' default='netCDF data "
550
24
        "type'>"
551
24
        "     <Value>AUTO</Value>"
552
24
        "     <Value>NC_BYTE</Value>"
553
24
        "     <Value>NC_INT64</Value>"
554
24
        "     <Value>NC_UINT64</Value>"
555
24
        "   </Option>"
556
24
        "</MultiDimArrayCreationOptionList>");
557
558
24
    poDriver->SetMetadataItem(
559
24
        GDAL_DMD_MULTIDIM_ARRAY_OPENOPTIONLIST,
560
24
        "<MultiDimArrayOpenOptionList>"
561
24
        "   <Option name='USE_DEFAULT_FILL_AS_NODATA' type='boolean' "
562
24
        "description='Whether the default fill value should be used as nodata "
563
24
        "when there is no _FillValue or missing_value attribute' default='NO'/>"
564
24
        "   <Option name='RAW_DATA_CHUNK_CACHE_SIZE' type='integer' "
565
24
        "description='The total size of the libnetcdf raw data chunk cache, "
566
24
        "in bytes. Only for netCDF4/HDF5 files'/>"
567
24
        "   <Option name='CHUNK_SLOTS' type='integer' "
568
24
        "description='The number of chunk slots in the libnetcdf raw data "
569
24
        "chunk cache. "
570
24
        "Only for netCDF4/HDF5 files'/>"
571
24
        "   <Option name='PREEMPTION' type='float' min='0' max='1' "
572
24
        "description='Indicates how much chunks from libnetcdf chunk cache "
573
24
        "that have been fully read are favored for preemption. "
574
24
        "Only for netCDF4/HDF5 files'/>"
575
24
        "</MultiDimArrayOpenOptionList>");
576
577
24
    poDriver->SetMetadataItem(GDAL_DMD_MULTIDIM_ATTRIBUTE_CREATIONOPTIONLIST,
578
24
                              "<MultiDimAttributeCreationOptionList>"
579
24
                              "   <Option name='NC_TYPE' type='string-select' "
580
24
                              "default='netCDF data type'>"
581
24
                              "     <Value>AUTO</Value>"
582
24
                              "     <Value>NC_BYTE</Value>"
583
24
                              "     <Value>NC_CHAR</Value>"
584
24
                              "     <Value>NC_INT64</Value>"
585
24
                              "     <Value>NC_UINT64</Value>"
586
24
                              "   </Option>"
587
24
                              "</MultiDimAttributeCreationOptionList>");
588
589
24
    poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATATYPES,
590
24
                              "Integer Integer64 Real String Date DateTime");
591
24
    poDriver->SetMetadataItem(GDAL_DMD_CREATION_FIELD_DEFN_FLAGS,
592
24
                              "Comment AlternativeName");
593
594
24
    poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "OGRSQL SQLITE");
595
596
24
    poDriver->pfnIdentify = netCDFDatasetIdentify;
597
24
    poDriver->pfnGetSubdatasetInfoFunc = NCDFDriverGetSubdatasetInfo;
598
24
    poDriver->SetMetadataItem(GDAL_DCAP_OPEN, "YES");
599
24
    poDriver->SetMetadataItem(GDAL_DCAP_CREATE, "YES");
600
24
    poDriver->SetMetadataItem(GDAL_DCAP_CREATECOPY, "YES");
601
24
    poDriver->SetMetadataItem(GDAL_DCAP_CREATE_MULTIDIMENSIONAL, "YES");
602
603
24
    poDriver->SetMetadataItem(GDAL_DCAP_UPDATE, "YES");
604
24
    poDriver->SetMetadataItem(GDAL_DMD_UPDATE_ITEMS,
605
24
                              "GeoTransform SRS "  // if not already set...
606
24
                              "DatasetMetadata BandMetadata RasterValues");
607
24
}
608
609
/************************************************************************/
610
/*                    DeclareDeferredNetCDFPlugin()                     */
611
/************************************************************************/
612
613
#ifdef PLUGIN_FILENAME
614
void DeclareDeferredNetCDFPlugin()
615
{
616
    if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
617
    {
618
        return;
619
    }
620
    auto poDriver = new GDALPluginDriverProxy(PLUGIN_FILENAME);
621
#ifdef PLUGIN_INSTALLATION_MESSAGE
622
    poDriver->SetMetadataItem(GDAL_DMD_PLUGIN_INSTALLATION_MESSAGE,
623
                              PLUGIN_INSTALLATION_MESSAGE);
624
#endif
625
    netCDFDriverSetCommonMetadata(poDriver);
626
    GetGDALDriverManager()->DeclareDeferredPluginDriver(poDriver);
627
}
628
#endif