• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OSGeo / gdal / 15885686134

25 Jun 2025 07:44PM UTC coverage: 71.084%. Remained the same
15885686134

push

github

rouault
gdal_priv.h: fix C++11 compatibility

573814 of 807237 relevant lines covered (71.08%)

250621.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.39
/frmts/grib/gribdataset.cpp
1
/******************************************************************************
2
 *
3
 * Project:  GRIB Driver
4
 * Purpose:  GDALDataset driver for GRIB translator for read support
5
 * Author:   Bas Retsios, retsios@itc.nl
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2007, ITC
9
 * Copyright (c) 2008-2017, Even Rouault <even dot rouault at spatialys dot com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ******************************************************************************
13
 *
14
 */
15

16
#include "cpl_port.h"
17
#include "gribdataset.h"
18
#include "gribdrivercore.h"
19

20
#include <cassert>
21
#include <cerrno>
22
#include <cmath>
23
#include <cstddef>
24
#include <cstdio>
25
#include <cstdlib>
26
#include <cstring>
27
#if HAVE_FCNTL_H
28
#include <fcntl.h>
29
#endif
30

31
#include <algorithm>
32
#include <mutex>
33
#include <set>
34
#include <string>
35
#include <vector>
36

37
#include "cpl_conv.h"
38
#include "cpl_error.h"
39
#include "cpl_multiproc.h"
40
#include "cpl_string.h"
41
#include "cpl_vsi.h"
42
#include "cpl_time.h"
43
#include "degrib/degrib/degrib2.h"
44
#include "degrib/degrib/inventory.h"
45
#include "degrib/degrib/meta.h"
46
#include "degrib/degrib/metaname.h"
47
#include "degrib/degrib/myerror.h"
48
#include "degrib/degrib/type.h"
49
CPL_C_START
50
#include "degrib/g2clib/grib2.h"
51
#include "degrib/g2clib/pdstemplates.h"
52
CPL_C_END
53
#include "gdal.h"
54
#include "gdal_frmts.h"
55
#include "gdal_pam.h"
56
#include "gdal_priv.h"
57
#include "ogr_spatialref.h"
58
#include "memdataset.h"
59

60
static CPLMutex *hGRIBMutex = nullptr;
61

62
/************************************************************************/
63
/*                         ConvertUnitInText()                          */
64
/************************************************************************/
65

66
static CPLString ConvertUnitInText(bool bMetricUnits, const char *pszTxt)
922✔
67
{
68
    if (pszTxt == nullptr)
922✔
69
        return CPLString();
×
70
    if (!bMetricUnits)
922✔
71
        return pszTxt;
4✔
72

73
    CPLString osRes(pszTxt);
1,836✔
74
    size_t iPos = osRes.find("[K]");
918✔
75
    if (iPos != std::string::npos)
918✔
76
        osRes = osRes.substr(0, iPos) + "[C]" + osRes.substr(iPos + 3);
96✔
77
    return osRes;
918✔
78
}
79

80
/************************************************************************/
81
/*                         Lon360to180()                               */
82
/************************************************************************/
83

84
static inline double Lon360to180(double lon)
442✔
85
{
86
    if (lon == 180)
442✔
87
        return 180;
×
88
    return fmod(lon + 180, 360) - 180;
442✔
89
}
90

91
/************************************************************************/
92
/*                           GRIBRasterBand()                            */
93
/************************************************************************/
94

95
GRIBRasterBand::GRIBRasterBand(GRIBDataset *poDSIn, int nBandIn,
444✔
96
                               inventoryType *psInv)
444✔
97
    : start(psInv->start), subgNum(psInv->subgNum),
444✔
98
      longFstLevel(CPLStrdup(psInv->longFstLevel)), m_Grib_Data(nullptr),
888✔
99
      m_Grib_MetaData(nullptr), nGribDataXSize(poDSIn->nRasterXSize),
444✔
100
      nGribDataYSize(poDSIn->nRasterYSize), m_nGribVersion(psInv->GribVersion),
444✔
101
      m_bHasLookedForNoData(false), m_dfNoData(0.0), m_bHasNoData(false)
444✔
102

103
{
104
    poDS = poDSIn;
444✔
105
    nBand = nBandIn;
444✔
106

107
    // Let user do -ot Float32 if needed for saving space, GRIB contains
108
    // Float64 (though not fully utilized most of the time).
109
    eDataType = GDT_Float64;
444✔
110

111
    nBlockXSize = poDSIn->nRasterXSize;
444✔
112
    nBlockYSize = 1;
444✔
113

114
    if (psInv->unitName != nullptr && psInv->comment != nullptr &&
444✔
115
        psInv->element != nullptr)
412✔
116
    {
117
        bLoadedMetadata = true;
412✔
118
        const char *pszGribNormalizeUnits =
119
            CPLGetConfigOption("GRIB_NORMALIZE_UNITS", "YES");
412✔
120
        bool bMetricUnits = CPLTestBool(pszGribNormalizeUnits);
412✔
121

122
        SetMetadataItem("GRIB_UNIT",
412✔
123
                        ConvertUnitInText(bMetricUnits, psInv->unitName));
824✔
124
        SetMetadataItem("GRIB_COMMENT",
412✔
125
                        ConvertUnitInText(bMetricUnits, psInv->comment));
824✔
126
        SetMetadataItem("GRIB_ELEMENT", psInv->element);
412✔
127
        SetMetadataItem("GRIB_SHORT_NAME", psInv->shortFstLevel);
412✔
128
        SetMetadataItem("GRIB_REF_TIME",
412✔
129
                        CPLString().Printf("%.0f", psInv->refTime));
824✔
130
        SetMetadataItem("GRIB_VALID_TIME",
412✔
131
                        CPLString().Printf("%.0f", psInv->validTime));
824✔
132
        SetMetadataItem("GRIB_FORECAST_SECONDS",
412✔
133
                        CPLString().Printf("%.0f", psInv->foreSec));
824✔
134
    }
135
}
444✔
136

137
/************************************************************************/
138
/*                           FindMetaData()                             */
139
/************************************************************************/
140

141
void GRIBRasterBand::FindMetaData()
775✔
142
{
143
    if (bLoadedMetadata)
775✔
144
        return;
749✔
145
    if (m_Grib_MetaData == nullptr)
26✔
146
    {
147
        grib_MetaData *metaData;
148
        GRIBDataset *poGDS = static_cast<GRIBDataset *>(poDS);
5✔
149
        GRIBRasterBand::ReadGribData(poGDS->fp, start, subgNum, nullptr,
5✔
150
                                     &metaData);
151
        if (metaData == nullptr)
5✔
152
            return;
×
153
        m_Grib_MetaData = metaData;
5✔
154
    }
155
    bLoadedMetadata = true;
26✔
156
    m_nGribVersion = m_Grib_MetaData->GribVersion;
26✔
157

158
    const char *pszGribNormalizeUnits =
159
        CPLGetConfigOption("GRIB_NORMALIZE_UNITS", "YES");
26✔
160
    bool bMetricUnits = CPLTestBool(pszGribNormalizeUnits);
26✔
161

162
    GDALRasterBand::SetMetadataItem(
26✔
163
        "GRIB_UNIT",
164
        ConvertUnitInText(bMetricUnits, m_Grib_MetaData->unitName));
52✔
165
    GDALRasterBand::SetMetadataItem(
26✔
166
        "GRIB_COMMENT",
167
        ConvertUnitInText(bMetricUnits, m_Grib_MetaData->comment));
52✔
168

169
    GDALRasterBand::SetMetadataItem("GRIB_ELEMENT", m_Grib_MetaData->element);
26✔
170
    GDALRasterBand::SetMetadataItem("GRIB_SHORT_NAME",
26✔
171
                                    m_Grib_MetaData->shortFstLevel);
26✔
172

173
    if (m_nGribVersion == 2)
26✔
174
    {
175
        GDALRasterBand::SetMetadataItem(
14✔
176
            "GRIB_REF_TIME",
177
            CPLString().Printf("%.0f", m_Grib_MetaData->pds2.refTime));
28✔
178
        GDALRasterBand::SetMetadataItem(
14✔
179
            "GRIB_VALID_TIME",
180
            CPLString().Printf("%.0f", m_Grib_MetaData->pds2.sect4.validTime));
28✔
181
    }
182
    else if (m_nGribVersion == 1)
12✔
183
    {
184
        GDALRasterBand::SetMetadataItem(
12✔
185
            "GRIB_REF_TIME",
186
            CPLString().Printf("%.0f", m_Grib_MetaData->pds1.refTime));
24✔
187
        GDALRasterBand::SetMetadataItem(
12✔
188
            "GRIB_VALID_TIME",
189
            CPLString().Printf("%.0f", m_Grib_MetaData->pds1.validTime));
24✔
190
    }
191

192
    GDALRasterBand::SetMetadataItem(
26✔
193
        "GRIB_FORECAST_SECONDS",
194
        CPLString().Printf("%d", m_Grib_MetaData->deltTime));
52✔
195
}
196

197
/************************************************************************/
198
/*                          FindTrueStart()                             */
199
/*                                                                      */
200
/*      Scan after the official start of the message to find its        */
201
/*      true starting offset.                                           */
202
/************************************************************************/
203
vsi_l_offset GRIBRasterBand::FindTrueStart(VSILFILE *fp, vsi_l_offset start)
866✔
204
{
205
    // GRIB messages can be preceded by "garbage". GRIB2Inventory()
206
    // does not return the offset to the real start of the message
207
    char szHeader[1024 + 1];
208
    VSIFSeekL(fp, start, SEEK_SET);
866✔
209
    const int nRead =
210
        static_cast<int>(VSIFReadL(szHeader, 1, sizeof(szHeader) - 1, fp));
866✔
211
    szHeader[nRead] = 0;
866✔
212
    // Find the real offset of the fist message
213
    int nOffsetFirstMessage = 0;
866✔
214
    for (int j = 0; j + 3 < nRead; j++)
2,548✔
215
    {
216
        if (STARTS_WITH_CI(szHeader + j, "GRIB")
2,548✔
217
#ifdef ENABLE_TDLP
218
            || STARTS_WITH_CI(szHeader + j, "TDLP")
219
#endif
220
        )
221
        {
222
            nOffsetFirstMessage = j;
866✔
223
            break;
866✔
224
        }
225
    }
226
    return start + nOffsetFirstMessage;
866✔
227
}
228

229
/************************************************************************/
230
/*                      FindPDSTemplateGRIB2()                          */
231
/*                                                                      */
232
/*      Scan the file for the PDS template info and represent it as     */
233
/*      metadata.                                                       */
234
/************************************************************************/
235

236
void GRIBRasterBand::FindPDSTemplateGRIB2()
886✔
237

238
{
239
    CPLAssert(m_nGribVersion == 2);
886✔
240

241
    if (bLoadedPDS)
886✔
242
        return;
558✔
243
    bLoadedPDS = true;
328✔
244

245
    GRIBDataset *poGDS = static_cast<GRIBDataset *>(poDS);
328✔
246
    start = FindTrueStart(poGDS->fp, start);
328✔
247

248
    // Read section 0
249
    GByte abySection0[16];
250
    if (VSIFSeekL(poGDS->fp, start, SEEK_SET) != 0 ||
656✔
251
        VSIFReadL(abySection0, 16, 1, poGDS->fp) != 1)
328✔
252
    {
253
        CPLDebug("GRIB", "Cannot read leading bytes of section 0");
×
254
        return;
×
255
    }
256
    GByte nDiscipline = abySection0[7 - 1];
328✔
257
    CPLString osDiscipline;
328✔
258
    osDiscipline = CPLString().Printf("%d", nDiscipline);
328✔
259
    static const char *const table00[] = {"Meteorological",
260
                                          "Hydrological",
261
                                          "Land Surface",
262
                                          "Space products",
263
                                          "Space products",
264
                                          "Reserved",
265
                                          "Reserved",
266
                                          "Reserved",
267
                                          "Reserved",
268
                                          "Reserved",
269
                                          "Oceanographic Products"};
270
    m_nDisciplineCode = nDiscipline;
328✔
271
    if (nDiscipline < CPL_ARRAYSIZE(table00))
328✔
272
    {
273
        m_osDisciplineName = table00[nDiscipline];
327✔
274
        osDiscipline += CPLString("(") +
654✔
275
                        CPLString(table00[nDiscipline]).replaceAll(' ', '_') +
1,308✔
276
                        ")";
327✔
277
    }
278

279
    GDALRasterBand::SetMetadataItem("GRIB_DISCIPLINE", osDiscipline.c_str());
328✔
280

281
    GByte abyHead[5] = {0};
328✔
282

283
    if (VSIFReadL(abyHead, 5, 1, poGDS->fp) != 1)
328✔
284
    {
285
        CPLDebug("GRIB", "Cannot read 5 leading bytes past section 0");
×
286
        return;
×
287
    }
288

289
    GUInt32 nSectSize = 0;
328✔
290
    if (abyHead[4] == 1)
328✔
291
    {
292
        memcpy(&nSectSize, abyHead, 4);
328✔
293
        CPL_MSBPTR32(&nSectSize);
328✔
294
        if (nSectSize >= 21 && nSectSize <= 100000 /* arbitrary upper limit */)
328✔
295
        {
296
            GByte *pabyBody = static_cast<GByte *>(CPLMalloc(nSectSize));
328✔
297
            memcpy(pabyBody, abyHead, 5);
328✔
298
            VSIFReadL(pabyBody + 5, 1, nSectSize - 5, poGDS->fp);
328✔
299

300
            CPLString osIDS;
656✔
301
            unsigned short nCenter = static_cast<unsigned short>(
328✔
302
                pabyBody[6 - 1] * 256 + pabyBody[7 - 1]);
328✔
303
            if (nCenter != GRIB2MISSING_u1 && nCenter != GRIB2MISSING_u2)
328✔
304
            {
305
                osIDS += "CENTER=";
93✔
306
                m_nCenter = nCenter;
93✔
307
                osIDS += CPLSPrintf("%d", nCenter);
93✔
308
                const char *pszCenter = centerLookup(nCenter);
93✔
309
                if (pszCenter)
93✔
310
                {
311
                    m_osCenterName = pszCenter;
93✔
312
                    osIDS += CPLString("(") + pszCenter + ")";
93✔
313
                }
314
            }
315

316
            unsigned short nSubCenter = static_cast<unsigned short>(
328✔
317
                pabyBody[8 - 1] * 256 + pabyBody[9 - 1]);
328✔
318
            if (nSubCenter != GRIB2MISSING_u2)
328✔
319
            {
320
                if (!osIDS.empty())
119✔
321
                    osIDS += " ";
78✔
322
                osIDS += "SUBCENTER=";
119✔
323
                osIDS += CPLSPrintf("%d", nSubCenter);
119✔
324
                m_nSubCenter = nSubCenter;
119✔
325
                const char *pszSubCenter = subCenterLookup(nCenter, nSubCenter);
119✔
326
                if (pszSubCenter)
119✔
327
                {
328
                    m_osSubCenterName = pszSubCenter;
3✔
329
                    osIDS += CPLString("(") + pszSubCenter + ")";
3✔
330
                }
331
            }
332

333
            if (!osIDS.empty())
328✔
334
                osIDS += " ";
134✔
335
            osIDS += "MASTER_TABLE=";
328✔
336
            osIDS += CPLSPrintf("%d", pabyBody[10 - 1]);
328✔
337
            osIDS += " ";
328✔
338
            osIDS += "LOCAL_TABLE=";
328✔
339
            osIDS += CPLSPrintf("%d", pabyBody[11 - 1]);
328✔
340
            osIDS += " ";
328✔
341
            osIDS += "SIGNF_REF_TIME=";
328✔
342
            unsigned nSignRefTime = pabyBody[12 - 1];
328✔
343
            osIDS += CPLSPrintf("%d", nSignRefTime);
328✔
344
            static const char *const table12[] = {
345
                "Analysis", "Start of Forecast", "Verifying time of forecast",
346
                "Observation time"};
347
            if (nSignRefTime < CPL_ARRAYSIZE(table12))
328✔
348
            {
349
                m_osSignRefTimeName = table12[nSignRefTime];
328✔
350
                osIDS += CPLString("(") +
656✔
351
                         CPLString(table12[nSignRefTime]).replaceAll(' ', '_') +
1,312✔
352
                         ")";
328✔
353
            }
354
            osIDS += " ";
328✔
355
            osIDS += "REF_TIME=";
328✔
356
            m_osRefTime =
357
                CPLSPrintf("%04d-%02d-%02dT%02d:%02d:%02dZ",
358
                           pabyBody[13 - 1] * 256 + pabyBody[14 - 1],
328✔
359
                           pabyBody[15 - 1], pabyBody[16 - 1], pabyBody[17 - 1],
328✔
360
                           pabyBody[18 - 1], pabyBody[19 - 1]);
328✔
361
            osIDS += m_osRefTime;
328✔
362
            osIDS += " ";
328✔
363
            osIDS += "PROD_STATUS=";
328✔
364
            unsigned nProdStatus = pabyBody[20 - 1];
328✔
365
            osIDS += CPLSPrintf("%d", nProdStatus);
328✔
366
            static const char *const table13[] = {
367
                "Operational",     "Operational test",
368
                "Research",        "Re-analysis",
369
                "TIGGE",           "TIGGE test",
370
                "S2S operational", "S2S test",
371
                "UERRA",           "UERRA test"};
372
            if (nProdStatus < CPL_ARRAYSIZE(table13))
328✔
373
            {
374
                m_osProductionStatus = table13[nProdStatus];
133✔
375
                osIDS += CPLString("(") +
266✔
376
                         CPLString(table13[nProdStatus]).replaceAll(' ', '_') +
532✔
377
                         ")";
133✔
378
            }
379
            osIDS += " ";
328✔
380
            osIDS += "TYPE=";
328✔
381
            unsigned nType = pabyBody[21 - 1];
328✔
382
            osIDS += CPLSPrintf("%d", nType);
328✔
383
            static const char *const table14[] = {
384
                "Analysis",
385
                "Forecast",
386
                "Analysis and forecast",
387
                "Control forecast",
388
                "Perturbed forecast",
389
                "Control and perturbed forecast",
390
                "Processed satellite observations",
391
                "Processed radar observations",
392
                "Event Probability"};
393
            if (nType < CPL_ARRAYSIZE(table14))
328✔
394
            {
395
                m_osType = table14[nType];
133✔
396
                osIDS += CPLString("(") +
266✔
397
                         CPLString(table14[nType]).replaceAll(' ', '_') + ")";
399✔
398
            }
399

400
            GDALRasterBand::SetMetadataItem("GRIB_IDS", osIDS);
328✔
401

402
            CPLFree(pabyBody);
328✔
403
        }
404

405
        if (VSIFReadL(abyHead, 5, 1, poGDS->fp) != 1)
328✔
406
        {
407
            CPLDebug("GRIB", "Cannot read 5 leading bytes past section 1");
×
408
            return;
×
409
        }
410
    }
411

412
    if (subgNum > 0)
328✔
413
    {
414
        // If we are a subgrid, then iterate over all preceding subgrids
415
        for (int iSubMessage = 0; iSubMessage < subgNum;)
14✔
416
        {
417
            memcpy(&nSectSize, abyHead, 4);
12✔
418
            CPL_MSBPTR32(&nSectSize);
12✔
419
            if (nSectSize < 5)
12✔
420
            {
421
                CPLDebug("GRIB", "Invalid section size for iSubMessage = %d",
×
422
                         iSubMessage);
423
                return;
×
424
            }
425
            if (VSIFSeekL(poGDS->fp, nSectSize - 5, SEEK_CUR) != 0)
12✔
426
            {
427
                CPLDebug("GRIB",
×
428
                         "Cannot read past section for iSubMessage = %d",
429
                         iSubMessage);
430
                return;
×
431
            }
432
            if (abyHead[4] < 2 || abyHead[4] > 7)
12✔
433
            {
434
                CPLDebug("GRIB", "Invalid section number for iSubMessage = %d",
×
435
                         iSubMessage);
436
                return;
×
437
            }
438
            if (abyHead[4] == 7)
12✔
439
            {
440
                ++iSubMessage;
2✔
441
            }
442
            if (VSIFReadL(abyHead, 5, 1, poGDS->fp) != 1)
12✔
443
            {
444
                CPLDebug("GRIB",
×
445
                         "Cannot read 5 leading bytes for iSubMessage = %d",
446
                         iSubMessage);
447
                return;
×
448
            }
449
        }
450
    }
451

452
    // Skip to section 4
453
    while (abyHead[4] != 4)
957✔
454
    {
455
        memcpy(&nSectSize, abyHead, 4);
629✔
456
        CPL_MSBPTR32(&nSectSize);
629✔
457

458
        const int nCurSection = abyHead[4];
629✔
459
        if (nSectSize < 5)
629✔
460
        {
461
            CPLDebug("GRIB", "Invalid section size for section %d",
×
462
                     nCurSection);
463
            return;
×
464
        }
465
        if (VSIFSeekL(poGDS->fp, nSectSize - 5, SEEK_CUR) != 0 ||
1,258✔
466
            VSIFReadL(abyHead, 5, 1, poGDS->fp) != 1)
629✔
467
        {
468
            CPLDebug("GRIB", "Cannot read section %d", nCurSection);
×
469
            return;
×
470
        }
471
    }
472

473
    // Collect section 4 octet information.  We read the file
474
    // ourselves since the GRIB API does not appear to preserve all
475
    // this for us.
476
    // if( abyHead[4] == 4 )
477
    {
478
        memcpy(&nSectSize, abyHead, 4);
328✔
479
        CPL_MSBPTR32(&nSectSize);
328✔
480
        if (nSectSize >= 9 && nSectSize <= 100000 /* arbitrary upper limit */)
328✔
481
        {
482
            GByte *pabyBody = static_cast<GByte *>(CPLMalloc(nSectSize));
328✔
483
            memcpy(pabyBody, abyHead, 5);
328✔
484
            if (VSIFReadL(pabyBody + 5, 1, nSectSize - 5, poGDS->fp) !=
328✔
485
                nSectSize - 5)
328✔
486
            {
487
                CPLDebug("GRIB", "Cannot read section 4");
×
488
                CPLFree(pabyBody);
×
489
                return;
×
490
            }
491

492
            GUInt16 nCoordCount = 0;
328✔
493
            memcpy(&nCoordCount, pabyBody + 6 - 1, 2);
328✔
494
            CPL_MSBPTR16(&nCoordCount);
328✔
495

496
            GUInt16 nPDTN = 0;
328✔
497
            memcpy(&nPDTN, pabyBody + 8 - 1, 2);
328✔
498
            CPL_MSBPTR16(&nPDTN);
328✔
499

500
            GDALRasterBand::SetMetadataItem("GRIB_PDS_PDTN",
328✔
501
                                            CPLString().Printf("%d", nPDTN));
656✔
502
            m_nPDTN = nPDTN;
328✔
503

504
            CPLString osOctet;
656✔
505
            const int nTemplateFoundByteCount =
328✔
506
                nSectSize - 9U >= nCoordCount * 4U
328✔
507
                    ? static_cast<int>(nSectSize - 9 - nCoordCount * 4)
328✔
508
                    : 0;
509
            for (int i = 0; i < nTemplateFoundByteCount; i++)
9,274✔
510
            {
511
                char szByte[10] = {'\0'};
8,946✔
512

513
                if (i == 0)
8,946✔
514
                    snprintf(szByte, sizeof(szByte), "%d", pabyBody[i + 9]);
328✔
515
                else
516
                    snprintf(szByte, sizeof(szByte), " %d", pabyBody[i + 9]);
8,618✔
517
                osOctet += szByte;
8,946✔
518
            }
519

520
            GDALRasterBand::SetMetadataItem("GRIB_PDS_TEMPLATE_NUMBERS",
328✔
521
                                            osOctet);
522

523
            g2int iofst = 0;
328✔
524
            g2int pdsnum = 0;
328✔
525
            g2int *pdstempl = nullptr;
328✔
526
            g2int mappdslen = 0;
328✔
527
            g2float *coordlist = nullptr;
328✔
528
            g2int numcoord = 0;
328✔
529
            if (getpdsindex(nPDTN) < 0)
328✔
530
            {
531
                CPLError(CE_Warning, CPLE_NotSupported,
3✔
532
                         "Template 4.%d is not recognized currently", nPDTN);
533
            }
534
            else if (g2_unpack4(pabyBody, nSectSize, &iofst, &pdsnum, &pdstempl,
325✔
535
                                &mappdslen, &coordlist, &numcoord) == 0)
325✔
536
            {
537
                gtemplate *mappds = extpdstemplate(pdsnum, pdstempl);
325✔
538
                if (mappds)
325✔
539
                {
540
                    int nTemplateByteCount = 0;
325✔
541
                    for (int i = 0; i < mappds->maplen; i++)
5,592✔
542
                        nTemplateByteCount += abs(mappds->map[i]);
5,267✔
543
                    for (int i = 0; i < mappds->extlen; i++)
390✔
544
                        nTemplateByteCount += abs(mappds->ext[i]);
65✔
545
                    if (nTemplateByteCount == nTemplateFoundByteCount)
325✔
546
                    {
547
                        CPLString osValues;
646✔
548
                        for (g2int i = 0; i < mappds->maplen + mappds->extlen;
5,623✔
549
                             i++)
550
                        {
551
                            if (i > 0)
5,300✔
552
                                osValues += " ";
4,977✔
553
                            const int nEltSize =
5,300✔
554
                                (i < mappds->maplen)
5,300✔
555
                                    ? mappds->map[i]
5,300✔
556
                                    : mappds->ext[i - mappds->maplen];
65✔
557
                            if (nEltSize == 4)
5,300✔
558
                            {
559
                                m_anPDSTemplateAssembledValues.push_back(
88✔
560
                                    static_cast<GUInt32>(pdstempl[i]));
88✔
561
                                osValues += CPLSPrintf(
562
                                    "%u", static_cast<GUInt32>(pdstempl[i]));
88✔
563
                            }
564
                            else
565
                            {
566
                                m_anPDSTemplateAssembledValues.push_back(
5,212✔
567
                                    pdstempl[i]);
5,212✔
568
                                osValues += CPLSPrintf("%d", pdstempl[i]);
5,212✔
569
                            }
570
                        }
571
                        GDALRasterBand::SetMetadataItem(
323✔
572
                            "GRIB_PDS_TEMPLATE_ASSEMBLED_VALUES", osValues);
573
                    }
574
                    else
575
                    {
576
                        CPLDebug(
2✔
577
                            "GRIB",
578
                            "Cannot expose GRIB_PDS_TEMPLATE_ASSEMBLED_VALUES "
579
                            "as we would expect %d bytes from the "
580
                            "tables, but %d are available",
581
                            nTemplateByteCount, nTemplateFoundByteCount);
582
                    }
583

584
                    free(mappds->ext);
325✔
585
                    free(mappds);
325✔
586
                }
587
            }
588
            free(pdstempl);
328✔
589
            free(coordlist);
328✔
590

591
            CPLFree(pabyBody);
328✔
592

593
            FindNoDataGrib2(false);
656✔
594
        }
595
        else
596
        {
597
            CPLDebug("GRIB", "Invalid section size for section %d", 4);
×
598
        }
599
    }
600
}
601

602
/************************************************************************/
603
/*                        FindNoDataGrib2()                             */
604
/************************************************************************/
605

606
void GRIBRasterBand::FindNoDataGrib2(bool bSeekToStart)
328✔
607
{
608
    // There is no easy way in the degrib API to retrieve the nodata value
609
    // without decompressing the data point section (which is slow), so
610
    // retrieve nodata value by parsing section 5 (Data Representation Section)
611
    // We also check section 6 to see if there is a bitmap
612
    GRIBDataset *poGDS = static_cast<GRIBDataset *>(poDS);
328✔
613
    CPLAssert(m_nGribVersion == 2);
328✔
614

615
    if (m_bHasLookedForNoData)
328✔
616
        return;
×
617
    m_bHasLookedForNoData = true;
328✔
618

619
    if (bSeekToStart)
328✔
620
    {
621
        // Skip over section 0
622
        VSIFSeekL(poGDS->fp, start + 16, SEEK_SET);
×
623
    }
624

625
    GByte abyHead[5] = {0};
328✔
626
    VSIFReadL(abyHead, 5, 1, poGDS->fp);
328✔
627

628
    // Skip to section 5
629
    GUInt32 nSectSize = 0;
328✔
630
    while (abyHead[4] != 5)
328✔
631
    {
632
        memcpy(&nSectSize, abyHead, 4);
×
633
        CPL_MSBPTR32(&nSectSize);
×
634

635
        if (nSectSize < 5 ||
×
636
            VSIFSeekL(poGDS->fp, nSectSize - 5, SEEK_CUR) != 0 ||
×
637
            VSIFReadL(abyHead, 5, 1, poGDS->fp) != 1)
×
638
            break;
×
639
    }
640

641
    // See http://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_sect5.shtml
642
    if (abyHead[4] == 5)
328✔
643
    {
644
        memcpy(&nSectSize, abyHead, 4);
328✔
645
        CPL_MSBPTR32(&nSectSize);
328✔
646
        if (nSectSize >= 11 && nSectSize <= 100000 /* arbitrary upper limit */)
328✔
647
        {
648
            GByte *pabyBody = static_cast<GByte *>(CPLMalloc(nSectSize));
328✔
649
            memcpy(pabyBody, abyHead, 5);
328✔
650
            VSIFReadL(pabyBody + 5, 1, nSectSize - 5, poGDS->fp);
328✔
651

652
            GUInt16 nDRTN = 0;
328✔
653
            memcpy(&nDRTN, pabyBody + 10 - 1, 2);
328✔
654
            CPL_MSBPTR16(&nDRTN);
328✔
655

656
            GDALRasterBand::SetMetadataItem("DRS_DRTN", CPLSPrintf("%d", nDRTN),
328✔
657
                                            "GRIB");
658
            if ((nDRTN == GS5_SIMPLE || nDRTN == GS5_CMPLX ||
328✔
659
                 nDRTN == GS5_CMPLXSEC || nDRTN == GS5_JPEG2000 ||
182✔
660
                 nDRTN == GS5_PNG) &&
126✔
661
                nSectSize >= 20)
232✔
662
            {
663
                float fRef;
664
                memcpy(&fRef, pabyBody + 12 - 1, 4);
232✔
665
                CPL_MSBPTR32(&fRef);
232✔
666
                GDALRasterBand::SetMetadataItem(
232✔
667
                    "DRS_REF_VALUE", CPLSPrintf("%.10f", fRef), "GRIB");
668

669
                GUInt16 nBinaryScaleFactorUnsigned;
670
                memcpy(&nBinaryScaleFactorUnsigned, pabyBody + 16 - 1, 2);
232✔
671
                CPL_MSBPTR16(&nBinaryScaleFactorUnsigned);
232✔
672
                const int nBSF =
232✔
673
                    (nBinaryScaleFactorUnsigned & 0x8000)
674
                        ? -static_cast<int>(nBinaryScaleFactorUnsigned & 0x7FFF)
25✔
675
                        : static_cast<int>(nBinaryScaleFactorUnsigned);
232✔
676
                GDALRasterBand::SetMetadataItem("DRS_BINARY_SCALE_FACTOR",
232✔
677
                                                CPLSPrintf("%d", nBSF), "GRIB");
678

679
                GUInt16 nDecimalScaleFactorUnsigned;
680
                memcpy(&nDecimalScaleFactorUnsigned, pabyBody + 18 - 1, 2);
232✔
681
                CPL_MSBPTR16(&nDecimalScaleFactorUnsigned);
232✔
682
                const int nDSF =
232✔
683
                    (nDecimalScaleFactorUnsigned & 0x8000)
684
                        ? -static_cast<int>(nDecimalScaleFactorUnsigned &
19✔
685
                                            0x7FFF)
686
                        : static_cast<int>(nDecimalScaleFactorUnsigned);
232✔
687
                GDALRasterBand::SetMetadataItem("DRS_DECIMAL_SCALE_FACTOR",
232✔
688
                                                CPLSPrintf("%d", nDSF), "GRIB");
689

690
                const int nBits = pabyBody[20 - 1];
232✔
691
                GDALRasterBand::SetMetadataItem(
232✔
692
                    "DRS_NBITS", CPLSPrintf("%d", nBits), "GRIB");
693
            }
694

695
            // 2 = Grid Point Data - Complex Packing
696
            // 3 = Grid Point Data - Complex Packing and Spatial Differencing
697
            if ((nDRTN == GS5_CMPLX || nDRTN == GS5_CMPLXSEC) &&
328✔
698
                nSectSize >= 31)
54✔
699
            {
700
                const int nMiss = pabyBody[23 - 1];
54✔
701
                if (nMiss == 1 || nMiss == 2)
54✔
702
                {
703
                    const int original_field_type = pabyBody[21 - 1];
35✔
704
                    if (original_field_type == 0)  // Floating Point
35✔
705
                    {
706
                        float fTemp;
707
                        memcpy(&fTemp, &pabyBody[24 - 1], 4);
27✔
708
                        CPL_MSBPTR32(&fTemp);
27✔
709
                        m_dfNoData = fTemp;
27✔
710
                        m_bHasNoData = true;
27✔
711
                        if (nMiss == 2)
27✔
712
                        {
713
                            memcpy(&fTemp, &pabyBody[28 - 1], 4);
×
714
                            CPL_MSBPTR32(&fTemp);
×
715
                            CPLDebug("GRIB",
×
716
                                     "Secondary missing value also set for "
717
                                     "band %d : %f",
718
                                     nBand, fTemp);
719
                        }
720
                    }
721
                    else if (original_field_type == 1)  // Integer
8✔
722
                    {
723
                        int iTemp;
724
                        memcpy(&iTemp, &pabyBody[24 - 1], 4);
8✔
725
                        CPL_MSBPTR32(&iTemp);
8✔
726
                        m_dfNoData = iTemp;
8✔
727
                        m_bHasNoData = true;
8✔
728
                        if (nMiss == 2)
8✔
729
                        {
730
                            memcpy(&iTemp, &pabyBody[28 - 1], 4);
×
731
                            CPL_MSBPTR32(&iTemp);
×
732
                            CPLDebug("GRIB",
×
733
                                     "Secondary missing value also set for "
734
                                     "band %d : %d",
735
                                     nBand, iTemp);
736
                        }
737
                    }
738
                    else
739
                    {
740
                        // FIXME What to do? Blindly convert to float?
741
                        CPLDebug("GRIB",
×
742
                                 "Complex Packing - Type of Original Field "
743
                                 "Values for band %d:  %u",
744
                                 nBand, original_field_type);
745
                    }
746
                }
747
            }
748

749
            if (nDRTN == GS5_CMPLXSEC && nSectSize >= 48)
328✔
750
            {
751
                const int nOrder = pabyBody[48 - 1];
21✔
752
                GDALRasterBand::SetMetadataItem(
21✔
753
                    "DRS_SPATIAL_DIFFERENCING_ORDER", CPLSPrintf("%d", nOrder),
754
                    "GRIB");
755
            }
756

757
            CPLFree(pabyBody);
328✔
758
        }
759
        else if (nSectSize > 5)
×
760
        {
761
            VSIFSeekL(poGDS->fp, nSectSize - 5, SEEK_CUR);
×
762
        }
763
    }
764

765
    if (!m_bHasNoData)
328✔
766
    {
767
        // Check bitmap section
768
        GByte abySection6[6] = {0};
293✔
769
        VSIFReadL(abySection6, 6, 1, poGDS->fp);
293✔
770
        // Is there a bitmap ?
771
        if (abySection6[4] == 6 && abySection6[5] == 0)
293✔
772
        {
773
            m_dfNoData = 9999.0;  // Same value as in metaparse.cpp:ParseGrid()
6✔
774
            m_bHasNoData = true;
6✔
775
        }
776
    }
777
}
778

779
/************************************************************************/
780
/*                         GetDescription()                             */
781
/************************************************************************/
782

783
const char *GRIBRasterBand::GetDescription() const
29✔
784
{
785
    if (longFstLevel == nullptr)
29✔
786
        return GDALPamRasterBand::GetDescription();
×
787

788
    return longFstLevel;
29✔
789
}
790

791
/************************************************************************/
792
/*                             LoadData()                               */
793
/************************************************************************/
794

795
CPLErr GRIBRasterBand::LoadData()
9,152✔
796

797
{
798
    if (!m_Grib_Data)
9,152✔
799
    {
800
        GRIBDataset *poGDS = static_cast<GRIBDataset *>(poDS);
184✔
801

802
        if (poGDS->bCacheOnlyOneBand)
184✔
803
        {
804
            // In "one-band-at-a-time" strategy, if the last recently used
805
            // band is not that one, uncache it. We could use a smarter strategy
806
            // based on a LRU, but that's a bit overkill for now.
807
            poGDS->poLastUsedBand->UncacheData();
×
808
            poGDS->nCachedBytes = 0;
×
809
        }
810
        else
811
        {
812
            // Once we have cached more than nCachedBytesThreshold bytes, we
813
            // will switch to "one-band-at-a-time" strategy, instead of caching
814
            // all bands that have been accessed.
815
            if (poGDS->nCachedBytes > poGDS->nCachedBytesThreshold)
184✔
816
            {
817
                GUIntBig nMinCacheSize =
818
                    1 + static_cast<GUIntBig>(poGDS->nRasterXSize) *
×
819
                            poGDS->nRasterYSize * poGDS->nBands *
×
820
                            GDALGetDataTypeSizeBytes(eDataType) / 1024 / 1024;
×
821
                CPLDebug("GRIB",
×
822
                         "Maximum band cache size reached for this dataset. "
823
                         "Caching only one band at a time from now, which can "
824
                         "negatively affect performance. Consider "
825
                         "increasing GRIB_CACHEMAX to a higher value (in MB), "
826
                         "at least " CPL_FRMT_GUIB " in that instance",
827
                         nMinCacheSize);
828
                for (int i = 0; i < poGDS->nBands; i++)
×
829
                {
830
                    reinterpret_cast<GRIBRasterBand *>(
831
                        poGDS->GetRasterBand(i + 1))
×
832
                        ->UncacheData();
×
833
                }
834
                poGDS->nCachedBytes = 0;
×
835
                poGDS->bCacheOnlyOneBand = TRUE;
×
836
            }
837
        }
838

839
        // we don't seem to have any way to detect errors in this!
840
        if (m_Grib_MetaData != nullptr)
184✔
841
        {
842
            MetaFree(m_Grib_MetaData);
137✔
843
            delete m_Grib_MetaData;
137✔
844
            m_Grib_MetaData = nullptr;
137✔
845
        }
846
        ReadGribData(poGDS->fp, start, subgNum, &m_Grib_Data, &m_Grib_MetaData);
184✔
847
        if (!m_Grib_Data)
184✔
848
        {
849
            CPLError(CE_Failure, CPLE_AppDefined, "Out of memory.");
×
850
            if (m_Grib_MetaData != nullptr)
×
851
            {
852
                MetaFree(m_Grib_MetaData);
×
853
                delete m_Grib_MetaData;
×
854
                m_Grib_MetaData = nullptr;
×
855
            }
856
            return CE_Failure;
×
857
        }
858

859
        // Check the band matches the dataset as a whole, size wise. (#3246)
860
        nGribDataXSize = m_Grib_MetaData->gds.Nx;
184✔
861
        nGribDataYSize = m_Grib_MetaData->gds.Ny;
184✔
862
        if (nGribDataXSize <= 0 || nGribDataYSize <= 0)
184✔
863
        {
864
            CPLError(CE_Failure, CPLE_AppDefined,
×
865
                     "Band %d of GRIB dataset is %dx%d.", nBand, nGribDataXSize,
866
                     nGribDataYSize);
867
            MetaFree(m_Grib_MetaData);
×
868
            delete m_Grib_MetaData;
×
869
            m_Grib_MetaData = nullptr;
×
870
            return CE_Failure;
×
871
        }
872

873
        poGDS->nCachedBytes += static_cast<GIntBig>(nGribDataXSize) *
184✔
874
                               nGribDataYSize * sizeof(double);
184✔
875
        poGDS->poLastUsedBand = this;
184✔
876

877
        if (nGribDataXSize != nRasterXSize || nGribDataYSize != nRasterYSize)
184✔
878
        {
879
            CPLError(CE_Warning, CPLE_AppDefined,
24✔
880
                     "Band %d of GRIB dataset is %dx%d, while the first band "
881
                     "and dataset is %dx%d.  Georeferencing of band %d may "
882
                     "be incorrect, and data access may be incomplete.",
883
                     nBand, nGribDataXSize, nGribDataYSize, nRasterXSize,
884
                     nRasterYSize, nBand);
885
        }
886
    }
887

888
    return CE_None;
9,152✔
889
}
890

891
/************************************************************************/
892
/*                       IsGdalinfoInteractive()                        */
893
/************************************************************************/
894

895
#ifdef BUILD_APPS
896
static bool IsGdalinfoInteractive()
×
897
{
898
    static const bool bIsGdalinfoInteractive = []()
×
899
    {
900
        if (CPLIsInteractive(stdout))
×
901
        {
902
            std::string osPath;
×
903
            osPath.resize(1024);
×
904
            if (CPLGetExecPath(&osPath[0], static_cast<int>(osPath.size())))
×
905
            {
906
                osPath = CPLGetBasenameSafe(osPath.c_str());
×
907
            }
908
            return osPath == "gdalinfo";
×
909
        }
910
        return false;
×
911
    }();
×
912
    return bIsGdalinfoInteractive;
×
913
}
914
#endif
915

916
/************************************************************************/
917
/*                             GetMetaData()                            */
918
/************************************************************************/
919
char **GRIBRasterBand::GetMetadata(const char *pszDomain)
87✔
920
{
921
    FindMetaData();
87✔
922
    if ((pszDomain == nullptr || pszDomain[0] == 0) && m_nGribVersion == 2 &&
138✔
923
        CPLTestBool(CPLGetConfigOption("GRIB_PDS_ALL_BANDS", "ON")))
51✔
924
    {
925
#ifdef BUILD_APPS
926
        // Detect slow execution of e.g.
927
        // "gdalinfo /vsis3/noaa-hrrr-bdp-pds/hrrr.20220804/conus/hrrr.t00z.wrfsfcf01.grib2"
928
        GRIBDataset *poGDS = static_cast<GRIBDataset *>(poDS);
51✔
929
        if (poGDS->m_bSideCarIdxUsed && !poGDS->m_bWarnedGdalinfoNomd &&
11✔
930
            poGDS->GetRasterCount() > 10 &&
11✔
931
            !VSIIsLocal(poGDS->GetDescription()) && IsGdalinfoInteractive())
62✔
932
        {
933
            if (poGDS->m_nFirstMetadataQueriedTimeStamp)
×
934
            {
935
                if (time(nullptr) - poGDS->m_nFirstMetadataQueriedTimeStamp > 2)
×
936
                {
937
                    poGDS->m_bWarnedGdalinfoNomd = true;
×
938

939
                    CPLError(
×
940
                        CE_Warning, CPLE_AppDefined,
941
                        "If metadata does not matter, faster result could be "
942
                        "obtained by adding the -nomd switch to gdalinfo");
943
                }
944
            }
945
            else
946
            {
947
                poGDS->m_nFirstMetadataQueriedTimeStamp = time(nullptr);
×
948
            }
949
        }
950
#endif
951

952
        FindPDSTemplateGRIB2();
51✔
953
    }
954
    return GDALPamRasterBand::GetMetadata(pszDomain);
87✔
955
}
956

957
/************************************************************************/
958
/*                             GetMetaDataItem()                        */
959
/************************************************************************/
960
const char *GRIBRasterBand::GetMetadataItem(const char *pszName,
693✔
961
                                            const char *pszDomain)
962
{
963
    if (!((!pszDomain || pszDomain[0] == 0) &&
693✔
964
          (EQUAL(pszName, "STATISTICS_MINIMUM") ||
681✔
965
           EQUAL(pszName, "STATISTICS_MAXIMUM"))))
677✔
966
    {
967
        FindMetaData();
688✔
968
        if (m_nGribVersion == 2 &&
1,221✔
969
            CPLTestBool(CPLGetConfigOption("GRIB_PDS_ALL_BANDS", "ON")))
533✔
970
        {
971
            FindPDSTemplateGRIB2();
532✔
972
        }
973
    }
974
    return GDALPamRasterBand::GetMetadataItem(pszName, pszDomain);
693✔
975
}
976

977
/************************************************************************/
978
/*                             IReadBlock()                             */
979
/************************************************************************/
980

981
CPLErr GRIBRasterBand::IReadBlock(int /* nBlockXOff */, int nBlockYOff,
9,152✔
982
                                  void *pImage)
983

984
{
985
    CPLErr eErr = LoadData();
9,152✔
986
    if (eErr != CE_None)
9,152✔
987
        return eErr;
×
988

989
    GRIBDataset *poGDS = static_cast<GRIBDataset *>(poDS);
9,152✔
990

991
    // The image as read is always upside down to our normal
992
    // orientation so we need to effectively flip it at this
993
    // point.  We also need to deal with bands that are a different
994
    // size than the dataset as a whole.
995

996
    if (nGribDataXSize == nRasterXSize && nGribDataYSize == nRasterYSize &&
9,152✔
997
        poGDS->nSplitAndSwapColumn == 0)
8,660✔
998
    {
999
        // Simple 1:1 case.
1000
        memcpy(pImage,
7,001✔
1001
               m_Grib_Data + static_cast<size_t>(nRasterXSize) *
7,001✔
1002
                                 (nRasterYSize - nBlockYOff - 1),
7,001✔
1003
               nRasterXSize * sizeof(double));
7,001✔
1004

1005
        return CE_None;
7,001✔
1006
    }
1007

1008
    memset(pImage, 0, sizeof(double) * nRasterXSize);
2,151✔
1009

1010
    if (nBlockYOff >= nGribDataYSize)  // Off image?
2,151✔
1011
        return CE_None;
57✔
1012

1013
    int nSplitAndSwapColumn = poGDS->nSplitAndSwapColumn;
2,094✔
1014
    if (nRasterXSize != nGribDataXSize)
2,094✔
1015
        nSplitAndSwapColumn = 0;
435✔
1016

1017
    const int nCopyWords = std::min(nRasterXSize, nGribDataXSize);
2,094✔
1018

1019
    memcpy(pImage,
2,094✔
1020
           m_Grib_Data +
2,094✔
1021
               static_cast<size_t>(nGribDataXSize) *
2,094✔
1022
                   (nGribDataYSize - nBlockYOff - 1) +
2,094✔
1023
               nSplitAndSwapColumn,
1024
           (nCopyWords - nSplitAndSwapColumn) * sizeof(double));
2,094✔
1025

1026
    if (nSplitAndSwapColumn > 0)
2,094✔
1027
        memcpy(reinterpret_cast<void *>(reinterpret_cast<double *>(pImage) +
1,659✔
1028
                                        nCopyWords - nSplitAndSwapColumn),
1,659✔
1029
               m_Grib_Data + static_cast<size_t>(nGribDataXSize) *
1,659✔
1030
                                 (nGribDataYSize - nBlockYOff - 1),
1,659✔
1031
               nSplitAndSwapColumn * sizeof(double));
1,659✔
1032

1033
    return CE_None;
2,094✔
1034
}
1035

1036
/************************************************************************/
1037
/*                           GetNoDataValue()                           */
1038
/************************************************************************/
1039

1040
double GRIBRasterBand::GetNoDataValue(int *pbSuccess)
125✔
1041
{
1042
    if (m_bHasLookedForNoData)
125✔
1043
    {
1044
        if (pbSuccess)
105✔
1045
            *pbSuccess = m_bHasNoData;
105✔
1046
        return m_dfNoData;
105✔
1047
    }
1048

1049
    m_bHasLookedForNoData = true;
20✔
1050
    if (m_Grib_MetaData == nullptr)
20✔
1051
    {
1052
        GRIBDataset *poGDS = static_cast<GRIBDataset *>(poDS);
18✔
1053

1054
#ifdef BUILD_APPS
1055
        // Detect slow execution of e.g.
1056
        // "gdalinfo /vsis3/noaa-hrrr-bdp-pds/hrrr.20220804/conus/hrrr.t00z.wrfsfcf01.grib2"
1057

1058
        if (poGDS->m_bSideCarIdxUsed && !poGDS->m_bWarnedGdalinfoNonodata &&
×
1059
            poGDS->GetRasterCount() > 10 &&
×
1060
            !VSIIsLocal(poGDS->GetDescription()) && IsGdalinfoInteractive())
18✔
1061
        {
1062
            if (poGDS->m_nFirstNodataQueriedTimeStamp)
×
1063
            {
1064
                if (time(nullptr) - poGDS->m_nFirstNodataQueriedTimeStamp > 2)
×
1065
                {
1066
                    poGDS->m_bWarnedGdalinfoNonodata = true;
×
1067

1068
                    CPLError(CE_Warning, CPLE_AppDefined,
×
1069
                             "If nodata value does not matter, faster result "
1070
                             "could be obtained by adding the -nonodata switch "
1071
                             "to gdalinfo");
1072
                }
1073
            }
1074
            else
1075
            {
1076
                poGDS->m_nFirstNodataQueriedTimeStamp = time(nullptr);
×
1077
            }
1078
        }
1079
#endif
1080

1081
        ReadGribData(poGDS->fp, start, subgNum, nullptr, &m_Grib_MetaData);
18✔
1082
        if (m_Grib_MetaData == nullptr)
18✔
1083
        {
1084
            m_bHasNoData = false;
×
1085
            m_dfNoData = 0;
×
1086
            if (pbSuccess)
×
1087
                *pbSuccess = m_bHasNoData;
×
1088
            return m_dfNoData;
×
1089
        }
1090
    }
1091

1092
    if (m_Grib_MetaData->gridAttrib.f_miss == 0)
20✔
1093
    {
1094
        m_bHasNoData = false;
4✔
1095
        m_dfNoData = 0;
4✔
1096
        if (pbSuccess)
4✔
1097
            *pbSuccess = m_bHasNoData;
4✔
1098
        return m_dfNoData;
4✔
1099
    }
1100

1101
    if (m_Grib_MetaData->gridAttrib.f_miss == 2)
16✔
1102
    {
1103
        // What TODO?
1104
        CPLDebug("GRIB", "Secondary missing value also set for band %d : %f",
×
1105
                 nBand, m_Grib_MetaData->gridAttrib.missSec);
×
1106
    }
1107

1108
    m_bHasNoData = true;
16✔
1109
    m_dfNoData = m_Grib_MetaData->gridAttrib.missPri;
16✔
1110
    if (pbSuccess)
16✔
1111
        *pbSuccess = m_bHasNoData;
16✔
1112
    return m_dfNoData;
16✔
1113
}
1114

1115
/************************************************************************/
1116
/*                            ReadGribData()                            */
1117
/************************************************************************/
1118

1119
void GRIBRasterBand::ReadGribData(VSILFILE *fp, vsi_l_offset start, int subgNum,
538✔
1120
                                  double **data, grib_MetaData **metaData)
1121
{
1122
    // Initialization, for calling the ReadGrib2Record function.
1123
    sInt4 f_endMsg = 1;  // 1 if we read the last grid in a GRIB message, or we
538✔
1124
                         // haven't read any messages.
1125
    // int subgNum = 0; // The subgrid in the message that we are interested in.
1126
    sChar f_unit = 2;       // None = 0, English = 1, Metric = 2
538✔
1127
    double majEarth = 0.0;  // -radEarth if < 6000 ignore, otherwise use this
538✔
1128
                            // to override the radEarth in the GRIB1 or GRIB2
1129
                            // message.  Needed because NCEP uses 6371.2 but
1130
                            // GRIB1 could only state 6367.47.
1131
    double minEarth = 0.0;  // -minEarth if < 6000 ignore, otherwise use this
538✔
1132
                            // to override the minEarth in the GRIB1 or GRIB2
1133
                            // message.
1134
    sChar f_SimpleVer = 4;  // Which version of the simple NDFD Weather table
538✔
1135
                            // to use. (1 is 6/2003) (2 is 1/2004) (3 is
1136
                            // 2/2004) (4 is 11/2004) (default 4)
1137
    LatLon lwlf;            // Lower left corner (cookie slicing) -lwlf
1138
    LatLon uprt;            // Upper right corner (cookie slicing) -uprt
1139
    IS_dataType is;  // Un-parsed meta data for this GRIB2 message. As well
1140
                     // as some memory used by the unpacker.
1141

1142
    lwlf.lat = -100;  // lat == -100 instructs the GRIB decoder that we don't
538✔
1143
                      // want a subgrid
1144

1145
    IS_Init(&is);
538✔
1146

1147
    const char *pszGribNormalizeUnits =
1148
        CPLGetConfigOption("GRIB_NORMALIZE_UNITS", "YES");
538✔
1149
    if (!CPLTestBool(pszGribNormalizeUnits))
538✔
1150
        f_unit = 0;  // Do not normalize units to metric.
2✔
1151

1152
    start = FindTrueStart(fp, start);
538✔
1153
    // Read GRIB message from file position "start".
1154
    VSIFSeekL(fp, start, SEEK_SET);
538✔
1155
    uInt4 grib_DataLen = 0;  // Size of Grib_Data.
538✔
1156
    *metaData = new grib_MetaData();
538✔
1157
    MetaInit(*metaData);
538✔
1158
    const int simpWWA = 0;  // seem to be unused in degrib
538✔
1159
    ReadGrib2Record(fp, f_unit, data, &grib_DataLen, *metaData, &is, subgNum,
538✔
1160
                    majEarth, minEarth, f_SimpleVer, simpWWA, &f_endMsg, &lwlf,
1161
                    &uprt);
1162

1163
    // No intention to show errors, just swallow it and free the memory.
1164
    char *errMsg = errSprintf(nullptr);
538✔
1165
    if (errMsg != nullptr)
538✔
1166
        CPLDebug("GRIB", "%s", errMsg);
341✔
1167
    free(errMsg);
538✔
1168
    IS_Free(&is);
538✔
1169
}
538✔
1170

1171
/************************************************************************/
1172
/*                            UncacheData()                             */
1173
/************************************************************************/
1174

1175
void GRIBRasterBand::UncacheData()
444✔
1176
{
1177
    if (m_Grib_Data)
444✔
1178
        free(m_Grib_Data);
184✔
1179
    m_Grib_Data = nullptr;
444✔
1180
    if (m_Grib_MetaData)
444✔
1181
    {
1182
        MetaFree(m_Grib_MetaData);
373✔
1183
        delete m_Grib_MetaData;
373✔
1184
    }
1185
    m_Grib_MetaData = nullptr;
444✔
1186
}
444✔
1187

1188
/************************************************************************/
1189
/*                           ~GRIBRasterBand()                          */
1190
/************************************************************************/
1191

1192
GRIBRasterBand::~GRIBRasterBand()
865✔
1193
{
1194
    if (longFstLevel != nullptr)
444✔
1195
        CPLFree(longFstLevel);
444✔
1196
    UncacheData();
444✔
1197
}
865✔
1198

1199
gdal::grib::InventoryWrapper::~InventoryWrapper() = default;
1200

1201
/************************************************************************/
1202
/*                           InventoryWrapperGrib                       */
1203
/************************************************************************/
1204
class InventoryWrapperGrib : public gdal::grib::InventoryWrapper
1205
{
1206
  public:
1207
    explicit InventoryWrapperGrib(VSILFILE *fp) : gdal::grib::InventoryWrapper()
310✔
1208
    {
1209
        result_ = GRIB2Inventory(fp, &inv_, &inv_len_, 0 /* all messages */,
310✔
1210
                                 &num_messages_);
1211
    }
310✔
1212

1213
    ~InventoryWrapperGrib() override;
1214
};
1215

1216
InventoryWrapperGrib::~InventoryWrapperGrib()
620✔
1217
{
1218
    if (inv_ == nullptr)
310✔
1219
        return;
×
1220
    for (uInt4 i = 0; i < inv_len_; i++)
732✔
1221
    {
1222
        GRIB2InventoryFree(inv_ + i);
422✔
1223
    }
1224
    free(inv_);
310✔
1225
}
620✔
1226

1227
/************************************************************************/
1228
/*                           InventoryWrapperSidecar                    */
1229
/************************************************************************/
1230

1231
class InventoryWrapperSidecar : public gdal::grib::InventoryWrapper
1232
{
1233
  public:
1234
    explicit InventoryWrapperSidecar(VSILFILE *fp, uint64_t nStartOffset,
6✔
1235
                                     int64_t nSize)
1236
        : gdal::grib::InventoryWrapper()
6✔
1237
    {
1238
        result_ = -1;
6✔
1239
        VSIFSeekL(fp, 0, SEEK_END);
6✔
1240
        size_t length = static_cast<size_t>(VSIFTellL(fp));
6✔
1241
        if (length > 4 * 1024 * 1024)
6✔
1242
            return;
×
1243
        std::string osSidecar;
6✔
1244
        osSidecar.resize(length);
6✔
1245
        VSIFSeekL(fp, 0, SEEK_SET);
6✔
1246
        if (VSIFReadL(&osSidecar[0], length, 1, fp) != 1)
6✔
1247
            return;
×
1248

1249
        const CPLStringList aosMsgs(
1250
            CSLTokenizeString2(osSidecar.c_str(), "\n",
1251
                               CSLT_PRESERVEQUOTES | CSLT_STRIPLEADSPACES));
6✔
1252
        inv_ = static_cast<inventoryType *>(
6✔
1253
            CPLCalloc(aosMsgs.size(), sizeof(inventoryType)));
6✔
1254

1255
        for (const char *pszMsg : aosMsgs)
42✔
1256
        {
1257
            // We are parsing
1258
            // "msgNum[.subgNum]:start:dontcare:name1:name2:name3" For NOMADS:
1259
            // "msgNum[.subgNum]:start:reftime:var:level:time"
1260
            const CPLStringList aosTokens(CSLTokenizeString2(
1261
                pszMsg, ":", CSLT_PRESERVEQUOTES | CSLT_ALLOWEMPTYTOKENS));
38✔
1262
            CPLStringList aosNum;
38✔
1263

1264
            if (aosTokens.size() < 6)
38✔
1265
                goto err_sidecar;
×
1266

1267
            aosNum = CPLStringList(CSLTokenizeString2(aosTokens[0], ".", 0));
38✔
1268
            if (aosNum.size() < 1)
38✔
1269
                goto err_sidecar;
×
1270

1271
            // FindMetaData will retrieve the correct version number
1272
            char *endptr;
1273
            strtol(aosNum[0], &endptr, 10);
38✔
1274
            if (*endptr != 0)
38✔
1275
                goto err_sidecar;
×
1276

1277
            if (aosNum.size() < 2)
38✔
1278
                inv_[inv_len_].subgNum = 0;
36✔
1279
            else
1280
            {
1281
                auto subgNum = strtol(aosNum[1], &endptr, 10);
2✔
1282
                if (*endptr != 0)
2✔
1283
                    goto err_sidecar;
×
1284
                if (subgNum <= 0 || subgNum > 65536)
2✔
1285
                    goto err_sidecar;
×
1286
                // .idx file use a 1-based indexing, whereas DEGRIB uses a
1287
                // 0-based one
1288
                subgNum--;
2✔
1289
                inv_[inv_len_].subgNum = static_cast<unsigned short>(subgNum);
2✔
1290
            }
1291

1292
            inv_[inv_len_].start = strtoll(aosTokens[1], &endptr, 10);
38✔
1293
            if (*endptr != 0)
38✔
1294
                goto err_sidecar;
×
1295

1296
            if (inv_[inv_len_].start < nStartOffset)
38✔
1297
                continue;
4✔
1298
            if (nSize > 0 && inv_[inv_len_].start >= nStartOffset + nSize)
34✔
1299
                break;
2✔
1300

1301
            inv_[inv_len_].start -= nStartOffset;
32✔
1302

1303
            inv_[inv_len_].unitName = nullptr;
32✔
1304
            inv_[inv_len_].comment = nullptr;
32✔
1305
            inv_[inv_len_].element = nullptr;
32✔
1306
            inv_[inv_len_].shortFstLevel = nullptr;
32✔
1307
            // This is going into the description field ->
1308
            // the only one available before loading the metadata
1309
            inv_[inv_len_].longFstLevel = VSIStrdup(CPLSPrintf(
32✔
1310
                "%s:%s:%s", aosTokens[3], aosTokens[4], aosTokens[5]));
1311
            ++inv_len_;
32✔
1312

1313
            continue;
32✔
1314

1315
        err_sidecar:
×
1316
            CPLDebug("GRIB",
×
1317
                     "Failed parsing sidecar entry '%s', "
1318
                     "falling back to constructing an inventory",
1319
                     pszMsg);
1320
            return;
×
1321
        }
1322

1323
        result_ = inv_len_;
6✔
1324
    }
1325

1326
    ~InventoryWrapperSidecar() override;
1327
};
1328

1329
InventoryWrapperSidecar::~InventoryWrapperSidecar()
12✔
1330
{
1331
    if (inv_ == nullptr)
6✔
1332
        return;
×
1333

1334
    for (unsigned i = 0; i < inv_len_; i++)
38✔
1335
        VSIFree(inv_[i].longFstLevel);
32✔
1336

1337
    VSIFree(inv_);
6✔
1338
}
12✔
1339

1340
/************************************************************************/
1341
/* ==================================================================== */
1342
/*                              GRIBDataset                             */
1343
/* ==================================================================== */
1344
/************************************************************************/
1345

1346
GRIBDataset::GRIBDataset()
316✔
1347
    : fp(nullptr), nCachedBytes(0),
1348
      // Switch caching strategy once 100 MB threshold is reached.
1349
      // Why 100 MB? --> Why not.
1350
      nCachedBytesThreshold(static_cast<GIntBig>(atoi(
632✔
1351
                                CPLGetConfigOption("GRIB_CACHEMAX", "100"))) *
1352
                            1024 * 1024),
316✔
1353
      bCacheOnlyOneBand(FALSE), nSplitAndSwapColumn(0), poLastUsedBand(nullptr)
316✔
1354
{
1355
}
316✔
1356

1357
/************************************************************************/
1358
/*                            ~GRIBDataset()                             */
1359
/************************************************************************/
1360

1361
GRIBDataset::~GRIBDataset()
632✔
1362

1363
{
1364
    FlushCache(true);
316✔
1365
    if (fp != nullptr)
316✔
1366
        VSIFCloseL(fp);
312✔
1367
}
632✔
1368

1369
/************************************************************************/
1370
/*                          GetGeoTransform()                           */
1371
/************************************************************************/
1372

1373
CPLErr GRIBDataset::GetGeoTransform(GDALGeoTransform &gt) const
135✔
1374

1375
{
1376
    gt = m_gt;
135✔
1377
    return CE_None;
135✔
1378
}
1379

1380
/************************************************************************/
1381
/*                                Inventory()                           */
1382
/************************************************************************/
1383

1384
std::unique_ptr<gdal::grib::InventoryWrapper>
1385
GRIBDataset::Inventory(GDALOpenInfo *poOpenInfo)
312✔
1386
{
1387
    std::unique_ptr<gdal::grib::InventoryWrapper> pInventories;
312✔
1388

1389
    VSIFSeekL(fp, 0, SEEK_SET);
312✔
1390
    std::string osSideCarFilename(poOpenInfo->pszFilename);
624✔
1391
    uint64_t nStartOffset = 0;
312✔
1392
    int64_t nSize = -1;
312✔
1393
    if (STARTS_WITH(poOpenInfo->pszFilename, "/vsisubfile/"))
312✔
1394
    {
1395
        const char *pszPtr = poOpenInfo->pszFilename + strlen("/vsisubfile/");
5✔
1396
        const char *pszComma = strchr(pszPtr, ',');
5✔
1397
        if (pszComma)
5✔
1398
        {
1399
            const CPLStringList aosTokens(CSLTokenizeString2(
1400
                std::string(pszPtr, pszComma - pszPtr).c_str(), "_", 0));
15✔
1401
            if (aosTokens.size() == 2)
5✔
1402
            {
1403
                nStartOffset = std::strtoull(aosTokens[0], nullptr, 10);
5✔
1404
                nSize = std::strtoll(aosTokens[1], nullptr, 10);
5✔
1405
                osSideCarFilename = pszComma + 1;
5✔
1406
            }
1407
        }
1408
    }
1409
    osSideCarFilename += ".idx";
312✔
1410
    VSILFILE *fpSideCar = nullptr;
312✔
1411
    if (CPLTestBool(CSLFetchNameValueDef(poOpenInfo->papszOpenOptions,
312✔
1412
                                         "USE_IDX", "YES")) &&
619✔
1413
        ((fpSideCar = VSIFOpenL(osSideCarFilename.c_str(), "rb")) != nullptr))
307✔
1414
    {
1415
        CPLDebug("GRIB", "Reading inventories from sidecar file %s",
6✔
1416
                 osSideCarFilename.c_str());
1417
        // Contains an GRIB2 message inventory of the file.
1418
        pInventories = std::make_unique<InventoryWrapperSidecar>(
12✔
1419
            fpSideCar, nStartOffset, nSize);
6✔
1420
        if (pInventories->result() <= 0 || pInventories->length() == 0)
6✔
1421
            pInventories = nullptr;
×
1422
        VSIFCloseL(fpSideCar);
6✔
1423
#ifdef BUILD_APPS
1424
        m_bSideCarIdxUsed = true;
6✔
1425
#endif
1426
    }
1427
    else
1428
        CPLDebug("GRIB", "Failed opening sidecar %s",
306✔
1429
                 osSideCarFilename.c_str());
1430

1431
    if (pInventories == nullptr)
312✔
1432
    {
1433
        CPLDebug("GRIB", "Reading inventories from GRIB file %s",
306✔
1434
                 poOpenInfo->pszFilename);
1435
        // Contains an GRIB2 message inventory of the file.
1436
        pInventories = std::make_unique<InventoryWrapperGrib>(fp);
306✔
1437
    }
1438

1439
    return pInventories;
624✔
1440
}
1441

1442
/************************************************************************/
1443
/*                                Open()                                */
1444
/************************************************************************/
1445

1446
GDALDataset *GRIBDataset::Open(GDALOpenInfo *poOpenInfo)
318✔
1447

1448
{
1449
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1450
    // During fuzzing, do not use Identify to reject crazy content.
1451
    if (!GRIBDriverIdentify(poOpenInfo))
318✔
1452
        return nullptr;
2✔
1453
#endif
1454
    if (poOpenInfo->fpL == nullptr)
316✔
1455
        return nullptr;
×
1456

1457
    // A fast "probe" on the header that is partially read in memory.
1458
    char *buff = nullptr;
316✔
1459
    uInt4 buffLen = 0;
316✔
1460
    sInt4 sect0[SECT0LEN_WORD] = {0};
316✔
1461
    uInt4 gribLen = 0;
316✔
1462
    int version = 0;
316✔
1463

1464
    // grib is not thread safe, make sure not to cause problems
1465
    // for other thread safe formats
1466
    CPLMutexHolderD(&hGRIBMutex);
632✔
1467

1468
    VSILFILE *memfp = VSIFileFromMemBuffer(nullptr, poOpenInfo->pabyHeader,
632✔
1469
                                           poOpenInfo->nHeaderBytes, FALSE);
316✔
1470
    if (memfp == nullptr ||
632✔
1471
        ReadSECT0(memfp, &buff, &buffLen, -1, sect0, &gribLen, &version) < 0)
316✔
1472
    {
1473
        if (memfp != nullptr)
×
1474
        {
1475
            VSIFCloseL(memfp);
×
1476
        }
1477
        free(buff);
×
1478
        char *errMsg = errSprintf(nullptr);
×
1479
        if (errMsg != nullptr && strstr(errMsg, "Ran out of file") == nullptr)
×
1480
            CPLDebug("GRIB", "%s", errMsg);
×
1481
        free(errMsg);
×
1482
        return nullptr;
×
1483
    }
1484
    VSIFCloseL(memfp);
316✔
1485
    free(buff);
316✔
1486

1487
    // Confirm the requested access is supported.
1488
    if (poOpenInfo->eAccess == GA_Update)
316✔
1489
    {
1490
        ReportUpdateNotSupportedByDriver("GRIB");
×
1491
        return nullptr;
×
1492
    }
1493

1494
    if (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER)
316✔
1495
    {
1496
        return OpenMultiDim(poOpenInfo);
4✔
1497
    }
1498

1499
    // Create a corresponding GDALDataset.
1500
    GRIBDataset *poDS = new GRIBDataset();
312✔
1501

1502
    poDS->fp = poOpenInfo->fpL;
312✔
1503
    poOpenInfo->fpL = nullptr;
312✔
1504

1505
    // Make an inventory of the GRIB file.
1506
    // The inventory does not contain all the information needed for
1507
    // creating the RasterBands (especially the x and y size), therefore
1508
    // the first GRIB band is also read for some additional metadata.
1509
    // The band-data that is read is stored into the first RasterBand,
1510
    // simply so that the same portion of the file is not read twice.
1511

1512
    auto pInventories = poDS->Inventory(poOpenInfo);
624✔
1513
    if (pInventories->result() <= 0)
312✔
1514
    {
1515
        char *errMsg = errSprintf(nullptr);
9✔
1516
        if (errMsg != nullptr)
9✔
1517
            CPLDebug("GRIB", "%s", errMsg);
8✔
1518
        free(errMsg);
9✔
1519

1520
        CPLError(CE_Failure, CPLE_OpenFailed,
9✔
1521
                 "%s is a grib file, "
1522
                 "but no raster dataset was successfully identified.",
1523
                 poOpenInfo->pszFilename);
1524
        // Release hGRIBMutex otherwise we'll deadlock with GDALDataset own
1525
        // hGRIBMutex.
1526
        CPLReleaseMutex(hGRIBMutex);
9✔
1527
        delete poDS;
9✔
1528
        CPLAcquireMutex(hGRIBMutex, 1000.0);
9✔
1529
        return nullptr;
9✔
1530
    }
1531

1532
    // Create band objects.
1533
    const uInt4 nCount = std::min(pInventories->length(), 65536U);
303✔
1534
    for (uInt4 i = 0; i < nCount; ++i)
724✔
1535
    {
1536
        inventoryType *psInv = pInventories->get(i);
421✔
1537
        GRIBRasterBand *gribBand = nullptr;
421✔
1538
        const uInt4 bandNr = i + 1;
421✔
1539
        assert(bandNr <= 65536);
421✔
1540

1541
        if (bandNr == 1)
421✔
1542
        {
1543
            // Important: set DataSet extents before creating first RasterBand
1544
            // in it.
1545
            grib_MetaData *metaData = nullptr;
303✔
1546
            GRIBRasterBand::ReadGribData(poDS->fp, 0, psInv->subgNum, nullptr,
303✔
1547
                                         &metaData);
1548
            if (metaData == nullptr || metaData->gds.Nx < 1 ||
303✔
1549
                metaData->gds.Ny < 1)
303✔
1550
            {
1551
                CPLError(CE_Failure, CPLE_OpenFailed,
×
1552
                         "%s is a grib file, "
1553
                         "but no raster dataset was successfully identified.",
1554
                         poOpenInfo->pszFilename);
1555
                // Release hGRIBMutex otherwise we'll deadlock with GDALDataset
1556
                // own hGRIBMutex.
1557
                CPLReleaseMutex(hGRIBMutex);
×
1558
                delete poDS;
×
1559
                CPLAcquireMutex(hGRIBMutex, 1000.0);
×
1560
                if (metaData != nullptr)
×
1561
                {
1562
                    MetaFree(metaData);
×
1563
                    delete metaData;
×
1564
                }
1565
                return nullptr;
×
1566
            }
1567
            psInv->GribVersion = metaData->GribVersion;
303✔
1568

1569
            // Set the DataSet's x,y size, georeference and projection from
1570
            // the first GRIB band.
1571
            poDS->SetGribMetaData(metaData);
303✔
1572
            gribBand = new GRIBRasterBand(poDS, bandNr, psInv);
303✔
1573

1574
            if (psInv->GribVersion == 2)
303✔
1575
                gribBand->FindPDSTemplateGRIB2();
296✔
1576

1577
            gribBand->m_Grib_MetaData = metaData;
303✔
1578
        }
1579
        else
1580
        {
1581
            gribBand = new GRIBRasterBand(poDS, bandNr, psInv);
118✔
1582
        }
1583
        poDS->SetBand(bandNr, gribBand);
421✔
1584
    }
1585

1586
    // Initialize any PAM information.
1587
    poDS->SetDescription(poOpenInfo->pszFilename);
303✔
1588

1589
    // Release hGRIBMutex otherwise we'll deadlock with GDALDataset own
1590
    // hGRIBMutex.
1591
    CPLReleaseMutex(hGRIBMutex);
303✔
1592
    poDS->TryLoadXML(poOpenInfo->GetSiblingFiles());
303✔
1593

1594
    // Check for external overviews.
1595
    poDS->oOvManager.Initialize(poDS, poOpenInfo);
303✔
1596
    CPLAcquireMutex(hGRIBMutex, 1000.0);
303✔
1597

1598
    return poDS;
303✔
1599
}
1600

1601
/************************************************************************/
1602
/*                          GRIBSharedResource                          */
1603
/************************************************************************/
1604

1605
struct GRIBSharedResource
1606
{
1607
    VSILFILE *m_fp = nullptr;
1608
    vsi_l_offset m_nOffsetCurData = static_cast<vsi_l_offset>(-1);
1609
    std::vector<double> m_adfCurData{};
1610
    std::string m_osFilename;
1611
    std::shared_ptr<GDALPamMultiDim> m_poPAM{};
1612

1613
    GRIBSharedResource(const std::string &osFilename, VSILFILE *fp);
1614
    ~GRIBSharedResource();
1615

1616
    const std::vector<double> &LoadData(vsi_l_offset nOffset, int subgNum);
1617

1618
    const std::shared_ptr<GDALPamMultiDim> &GetPAM()
23✔
1619
    {
1620
        return m_poPAM;
23✔
1621
    }
1622
};
1623

1624
GRIBSharedResource::GRIBSharedResource(const std::string &osFilename,
4✔
1625
                                       VSILFILE *fp)
4✔
1626
    : m_fp(fp), m_osFilename(osFilename),
1627
      m_poPAM(std::make_shared<GDALPamMultiDim>(osFilename))
4✔
1628
{
1629
}
4✔
1630

1631
GRIBSharedResource::~GRIBSharedResource()
4✔
1632
{
1633
    if (m_fp)
4✔
1634
        VSIFCloseL(m_fp);
4✔
1635
}
4✔
1636

1637
/************************************************************************/
1638
/*                                GRIBGroup                             */
1639
/************************************************************************/
1640

1641
class GRIBArray;
1642

1643
class GRIBGroup final : public GDALGroup
1644
{
1645
    friend class GRIBArray;
1646
    std::shared_ptr<GRIBSharedResource> m_poShared{};
1647
    std::vector<std::shared_ptr<GDALMDArray>> m_poArrays{};
1648
    std::vector<std::shared_ptr<GDALDimension>> m_dims{};
1649
    std::map<std::string, std::shared_ptr<GDALDimension>> m_oMapDims{};
1650
    int m_nHorizDimCounter = 0;
1651
    std::shared_ptr<GDALGroup> m_memRootGroup{};
1652

1653
  public:
1654
    explicit GRIBGroup(const std::shared_ptr<GRIBSharedResource> &poShared)
4✔
1655
        : GDALGroup(std::string(), "/"), m_poShared(poShared)
4✔
1656
    {
1657
        std::unique_ptr<GDALDataset> poTmpDS(
1658
            MEMDataset::CreateMultiDimensional("", nullptr, nullptr));
4✔
1659
        m_memRootGroup = poTmpDS->GetRootGroup();
4✔
1660
    }
4✔
1661

1662
    void AddArray(const std::shared_ptr<GDALMDArray> &array)
36✔
1663
    {
1664
        m_poArrays.emplace_back(array);
36✔
1665
    }
36✔
1666

1667
    std::vector<std::string>
1668
    GetMDArrayNames(CSLConstList papszOptions) const override;
1669
    std::shared_ptr<GDALMDArray>
1670
    OpenMDArray(const std::string &osName,
1671
                CSLConstList papszOptions) const override;
1672

1673
    std::vector<std::shared_ptr<GDALDimension>>
1674
    GetDimensions(CSLConstList) const override
3✔
1675
    {
1676
        return m_dims;
3✔
1677
    }
1678
};
1679

1680
/************************************************************************/
1681
/*                                GRIBArray                             */
1682
/************************************************************************/
1683

1684
class GRIBArray final : public GDALPamMDArray
1685
{
1686
    std::shared_ptr<GRIBSharedResource> m_poShared;
1687
    std::vector<std::shared_ptr<GDALDimension>> m_dims{};
1688
    GDALExtendedDataType m_dt = GDALExtendedDataType::Create(GDT_Float64);
1689
    std::shared_ptr<OGRSpatialReference> m_poSRS{};
1690
    std::vector<vsi_l_offset> m_anOffsets{};
1691
    std::vector<int> m_anSubgNums{};
1692
    std::vector<double> m_adfTimes{};
1693
    std::vector<std::shared_ptr<GDALAttribute>> m_attributes{};
1694
    std::string m_osUnit{};
1695
    std::vector<GByte> m_abyNoData{};
1696

1697
    GRIBArray(const std::string &osName,
1698
              const std::shared_ptr<GRIBSharedResource> &poShared);
1699

1700
  protected:
1701
    bool IRead(const GUInt64 *arrayStartIdx, const size_t *count,
1702
               const GInt64 *arrayStep, const GPtrDiff_t *bufferStride,
1703
               const GDALExtendedDataType &bufferDataType,
1704
               void *pDstBuffer) const override;
1705

1706
  public:
1707
    static std::shared_ptr<GRIBArray>
1708
    Create(const std::string &osName,
23✔
1709
           const std::shared_ptr<GRIBSharedResource> &poShared)
1710
    {
1711
        auto ar(std::shared_ptr<GRIBArray>(new GRIBArray(osName, poShared)));
23✔
1712
        ar->SetSelf(ar);
23✔
1713
        return ar;
23✔
1714
    }
1715

1716
    void Init(GRIBGroup *poGroup, GRIBDataset *poDS, GRIBRasterBand *poBand,
1717
              inventoryType *psInv);
1718
    void ExtendTimeDim(vsi_l_offset nOffset, int subgNum, double dfValidTime);
1719
    void Finalize(GRIBGroup *poGroup, inventoryType *psInv);
1720

1721
    bool IsWritable() const override
×
1722
    {
1723
        return false;
×
1724
    }
1725

1726
    const std::string &GetFilename() const override
4✔
1727
    {
1728
        return m_poShared->m_osFilename;
4✔
1729
    }
1730

1731
    const std::vector<std::shared_ptr<GDALDimension>> &
1732
    GetDimensions() const override
30✔
1733
    {
1734
        return m_dims;
30✔
1735
    }
1736

1737
    const GDALExtendedDataType &GetDataType() const override
11✔
1738
    {
1739
        return m_dt;
11✔
1740
    }
1741

1742
    std::shared_ptr<OGRSpatialReference> GetSpatialRef() const override
1✔
1743
    {
1744
        return m_poSRS;
1✔
1745
    }
1746

1747
    std::vector<std::shared_ptr<GDALAttribute>>
1748
    GetAttributes(CSLConstList) const override
14✔
1749
    {
1750
        return m_attributes;
14✔
1751
    }
1752

1753
    const std::string &GetUnit() const override
1✔
1754
    {
1755
        return m_osUnit;
1✔
1756
    }
1757

1758
    const void *GetRawNoDataValue() const override
1✔
1759
    {
1760
        return m_abyNoData.empty() ? nullptr : m_abyNoData.data();
1✔
1761
    }
1762
};
1763

1764
/************************************************************************/
1765
/*                          GetMDArrayNames()                           */
1766
/************************************************************************/
1767

1768
std::vector<std::string> GRIBGroup::GetMDArrayNames(CSLConstList) const
3✔
1769
{
1770
    std::vector<std::string> ret;
3✔
1771
    for (const auto &array : m_poArrays)
31✔
1772
    {
1773
        ret.push_back(array->GetName());
28✔
1774
    }
1775
    return ret;
3✔
1776
}
1777

1778
/************************************************************************/
1779
/*                            OpenMDArray()                             */
1780
/************************************************************************/
1781

1782
std::shared_ptr<GDALMDArray> GRIBGroup::OpenMDArray(const std::string &osName,
3✔
1783
                                                    CSLConstList) const
1784
{
1785
    for (const auto &array : m_poArrays)
12✔
1786
    {
1787
        if (array->GetName() == osName)
11✔
1788
            return array;
2✔
1789
    }
1790
    return nullptr;
1✔
1791
}
1792

1793
/************************************************************************/
1794
/*                             GRIBArray()                              */
1795
/************************************************************************/
1796

1797
GRIBArray::GRIBArray(const std::string &osName,
23✔
1798
                     const std::shared_ptr<GRIBSharedResource> &poShared)
23✔
1799
    : GDALAbstractMDArray("/", osName),
1800
      GDALPamMDArray("/", osName, poShared->GetPAM()), m_poShared(poShared)
23✔
1801
{
1802
}
23✔
1803

1804
/************************************************************************/
1805
/*                                Init()                                */
1806
/************************************************************************/
1807

1808
void GRIBArray::Init(GRIBGroup *poGroup, GRIBDataset *poDS,
23✔
1809
                     GRIBRasterBand *poBand, inventoryType *psInv)
1810
{
1811
    std::shared_ptr<GDALDimension> poDimX;
23✔
1812
    std::shared_ptr<GDALDimension> poDimY;
23✔
1813

1814
    GDALGeoTransform gt;
23✔
1815
    poDS->GetGeoTransform(gt);
23✔
1816

1817
    for (int i = 1; i <= poGroup->m_nHorizDimCounter; i++)
40✔
1818
    {
1819
        std::string osXLookup("X");
34✔
1820
        std::string osYLookup("Y");
34✔
1821
        if (i > 1)
34✔
1822
        {
1823
            osXLookup += CPLSPrintf("%d", i);
15✔
1824
            osYLookup += CPLSPrintf("%d", i);
15✔
1825
        }
1826
        auto oIterX = poGroup->m_oMapDims.find(osXLookup);
34✔
1827
        auto oIterY = poGroup->m_oMapDims.find(osYLookup);
34✔
1828
        CPLAssert(oIterX != poGroup->m_oMapDims.end());
34✔
1829
        CPLAssert(oIterY != poGroup->m_oMapDims.end());
34✔
1830
        if (oIterX->second->GetSize() ==
34✔
1831
                static_cast<size_t>(poDS->GetRasterXSize()) &&
51✔
1832
            oIterY->second->GetSize() ==
17✔
1833
                static_cast<size_t>(poDS->GetRasterYSize()))
17✔
1834
        {
1835
            bool bOK = true;
17✔
1836
            auto poVar = oIterX->second->GetIndexingVariable();
17✔
1837
            constexpr double EPSILON = 1e-10;
17✔
1838
            if (poVar)
17✔
1839
            {
1840
                GUInt64 nStart = 0;
17✔
1841
                size_t nCount = 1;
17✔
1842
                double dfVal = 0;
17✔
1843
                poVar->Read(&nStart, &nCount, nullptr, nullptr, m_dt, &dfVal);
17✔
1844
                if (std::fabs(dfVal - (gt[0] + 0.5 * gt[1])) >
17✔
1845
                    EPSILON * std::fabs(dfVal))
17✔
1846
                {
1847
                    bOK = false;
×
1848
                }
1849
            }
1850
            if (bOK)
17✔
1851
            {
1852
                poVar = oIterY->second->GetIndexingVariable();
17✔
1853
                if (poVar)
17✔
1854
                {
1855
                    GUInt64 nStart = 0;
17✔
1856
                    size_t nCount = 1;
17✔
1857
                    double dfVal = 0;
17✔
1858
                    poVar->Read(&nStart, &nCount, nullptr, nullptr, m_dt,
17✔
1859
                                &dfVal);
1860
                    if (std::fabs(dfVal - (gt[3] + poDS->nRasterYSize * gt[5] -
17✔
1861
                                           0.5 * gt[5])) >
17✔
1862
                        EPSILON * std::fabs(dfVal))
17✔
1863
                    {
1864
                        bOK = false;
×
1865
                    }
1866
                }
1867
            }
1868
            if (bOK)
17✔
1869
            {
1870
                poDimX = oIterX->second;
17✔
1871
                poDimY = oIterY->second;
17✔
1872
                break;
17✔
1873
            }
1874
        }
1875
    }
1876

1877
    if (!poDimX || !poDimY)
23✔
1878
    {
1879
        poGroup->m_nHorizDimCounter++;
6✔
1880
        {
1881
            std::string osName("Y");
12✔
1882
            if (poGroup->m_nHorizDimCounter >= 2)
6✔
1883
            {
1884
                osName = CPLSPrintf("Y%d", poGroup->m_nHorizDimCounter);
2✔
1885
            }
1886

1887
            poDimY = std::make_shared<GDALDimensionWeakIndexingVar>(
12✔
1888
                poGroup->GetFullName(), osName, GDAL_DIM_TYPE_HORIZONTAL_Y,
6✔
1889
                std::string(), poDS->GetRasterYSize());
18✔
1890
            poGroup->m_oMapDims[osName] = poDimY;
6✔
1891
            poGroup->m_dims.emplace_back(poDimY);
6✔
1892

1893
            auto var = GDALMDArrayRegularlySpaced::Create(
1894
                "/", poDimY->GetName(), poDimY,
1895
                gt[3] + poDS->GetRasterYSize() * gt[5], -gt[5], 0.5);
12✔
1896
            poDimY->SetIndexingVariable(var);
6✔
1897
            poGroup->AddArray(var);
6✔
1898
        }
1899

1900
        {
1901
            std::string osName("X");
12✔
1902
            if (poGroup->m_nHorizDimCounter >= 2)
6✔
1903
            {
1904
                osName = CPLSPrintf("X%d", poGroup->m_nHorizDimCounter);
2✔
1905
            }
1906

1907
            poDimX = std::make_shared<GDALDimensionWeakIndexingVar>(
12✔
1908
                poGroup->GetFullName(), osName, GDAL_DIM_TYPE_HORIZONTAL_X,
6✔
1909
                std::string(), poDS->GetRasterXSize());
18✔
1910
            poGroup->m_oMapDims[osName] = poDimX;
6✔
1911
            poGroup->m_dims.emplace_back(poDimX);
6✔
1912

1913
            auto var = GDALMDArrayRegularlySpaced::Create(
1914
                "/", poDimX->GetName(), poDimX, gt[0], gt[1], 0.5);
12✔
1915
            poDimX->SetIndexingVariable(var);
6✔
1916
            poGroup->AddArray(var);
6✔
1917
        }
1918
    }
1919

1920
    m_dims.emplace_back(poDimY);
23✔
1921
    m_dims.emplace_back(poDimX);
23✔
1922
    if (poDS->m_poSRS.get())
23✔
1923
    {
1924
        m_poSRS.reset(poDS->m_poSRS->Clone());
23✔
1925
        if (poDS->m_poSRS->GetDataAxisToSRSAxisMapping() ==
23✔
1926
            std::vector<int>{2, 1})
46✔
1927
            m_poSRS->SetDataAxisToSRSAxisMapping({1, 2});
22✔
1928
        else
1929
            m_poSRS->SetDataAxisToSRSAxisMapping({2, 1});
1✔
1930
    }
1931

1932
    const char *pszGribNormalizeUnits =
1933
        CPLGetConfigOption("GRIB_NORMALIZE_UNITS", "YES");
23✔
1934
    const bool bMetricUnits = CPLTestBool(pszGribNormalizeUnits);
23✔
1935

1936
    m_attributes.emplace_back(std::make_shared<GDALAttributeString>(
46✔
1937
        GetFullName(), "name", psInv->element));
46✔
1938
    m_attributes.emplace_back(std::make_shared<GDALAttributeString>(
46✔
1939
        GetFullName(), "long_name",
23✔
1940
        ConvertUnitInText(bMetricUnits, psInv->comment)));
69✔
1941
    m_osUnit = ConvertUnitInText(bMetricUnits, psInv->unitName);
23✔
1942
    if (!m_osUnit.empty() && m_osUnit[0] == '[' && m_osUnit.back() == ']')
23✔
1943
    {
1944
        m_osUnit = m_osUnit.substr(1, m_osUnit.size() - 2);
23✔
1945
    }
1946
    m_attributes.emplace_back(std::make_shared<GDALAttributeString>(
46✔
1947
        GetFullName(), "first_level", psInv->shortFstLevel));
46✔
1948

1949
    if (poBand->m_nDisciplineCode >= 0)
23✔
1950
    {
1951
        m_attributes.emplace_back(std::make_shared<GDALAttributeNumeric>(
14✔
1952
            GetFullName(), "discipline_code", poBand->m_nDisciplineCode));
14✔
1953
    }
1954
    if (!poBand->m_osDisciplineName.empty())
23✔
1955
    {
1956
        m_attributes.emplace_back(std::make_shared<GDALAttributeString>(
14✔
1957
            GetFullName(), "discipline_name", poBand->m_osDisciplineName));
14✔
1958
    }
1959
    if (poBand->m_nCenter >= 0)
23✔
1960
    {
1961
        m_attributes.emplace_back(std::make_shared<GDALAttributeNumeric>(
14✔
1962
            GetFullName(), "center_code", poBand->m_nCenter));
14✔
1963
    }
1964
    if (!poBand->m_osCenterName.empty())
23✔
1965
    {
1966
        m_attributes.emplace_back(std::make_shared<GDALAttributeString>(
14✔
1967
            GetFullName(), "center_name", poBand->m_osCenterName));
14✔
1968
    }
1969
    if (poBand->m_nSubCenter >= 0)
23✔
1970
    {
1971
        m_attributes.emplace_back(std::make_shared<GDALAttributeNumeric>(
12✔
1972
            GetFullName(), "subcenter_code", poBand->m_nSubCenter));
12✔
1973
    }
1974
    if (!poBand->m_osSubCenterName.empty())
23✔
1975
    {
1976
        m_attributes.emplace_back(std::make_shared<GDALAttributeString>(
×
1977
            GetFullName(), "subcenter_name", poBand->m_osSubCenterName));
×
1978
    }
1979
    if (!poBand->m_osSignRefTimeName.empty())
23✔
1980
    {
1981
        m_attributes.emplace_back(std::make_shared<GDALAttributeString>(
14✔
1982
            GetFullName(), "signification_of_ref_time",
7✔
1983
            poBand->m_osSignRefTimeName));
14✔
1984
    }
1985
    if (!poBand->m_osRefTime.empty())
23✔
1986
    {
1987
        m_attributes.emplace_back(std::make_shared<GDALAttributeString>(
14✔
1988
            GetFullName(), "reference_time_iso8601", poBand->m_osRefTime));
14✔
1989
    }
1990
    if (!poBand->m_osProductionStatus.empty())
23✔
1991
    {
1992
        m_attributes.emplace_back(std::make_shared<GDALAttributeString>(
14✔
1993
            GetFullName(), "production_status", poBand->m_osProductionStatus));
14✔
1994
    }
1995
    if (!poBand->m_osType.empty())
23✔
1996
    {
1997
        m_attributes.emplace_back(std::make_shared<GDALAttributeString>(
14✔
1998
            GetFullName(), "type", poBand->m_osType));
14✔
1999
    }
2000
    if (poBand->m_nPDTN >= 0)
23✔
2001
    {
2002
        m_attributes.emplace_back(std::make_shared<GDALAttributeNumeric>(
14✔
2003
            GetFullName(), "product_definition_template_number",
7✔
2004
            poBand->m_nPDTN));
14✔
2005
    }
2006
    if (!poBand->m_anPDSTemplateAssembledValues.empty())
23✔
2007
    {
2008
        m_attributes.emplace_back(std::make_shared<GDALAttributeNumeric>(
14✔
2009
            GetFullName(), "product_definition_numbers",
7✔
2010
            poBand->m_anPDSTemplateAssembledValues));
14✔
2011
    }
2012

2013
    int bHasNoData = FALSE;
23✔
2014
    double dfNoData = poBand->GetNoDataValue(&bHasNoData);
23✔
2015
    if (bHasNoData)
23✔
2016
    {
2017
        m_abyNoData.resize(sizeof(double));
14✔
2018
        memcpy(&m_abyNoData[0], &dfNoData, sizeof(double));
14✔
2019
    }
2020
}
23✔
2021

2022
/************************************************************************/
2023
/*                         ExtendTimeDim()                              */
2024
/************************************************************************/
2025

2026
void GRIBArray::ExtendTimeDim(vsi_l_offset nOffset, int subgNum,
24✔
2027
                              double dfValidTime)
2028
{
2029
    m_anOffsets.push_back(nOffset);
24✔
2030
    m_anSubgNums.push_back(subgNum);
24✔
2031
    m_adfTimes.push_back(dfValidTime);
24✔
2032
}
24✔
2033

2034
/************************************************************************/
2035
/*                           Finalize()                                 */
2036
/************************************************************************/
2037

2038
void GRIBArray::Finalize(GRIBGroup *poGroup, inventoryType *psInv)
23✔
2039
{
2040
    CPLAssert(!m_adfTimes.empty());
23✔
2041
    CPLAssert(m_anOffsets.size() == m_adfTimes.size());
23✔
2042

2043
    if (m_adfTimes.size() == 1)
23✔
2044
    {
2045
        m_attributes.emplace_back(std::make_shared<GDALAttributeNumeric>(
44✔
2046
            GetFullName(), "forecast_time", psInv->foreSec));
44✔
2047
        m_attributes.emplace_back(std::make_shared<GDALAttributeString>(
44✔
2048
            GetFullName(), "forecast_time_unit", "sec"));
44✔
2049
        m_attributes.emplace_back(std::make_shared<GDALAttributeNumeric>(
44✔
2050
            GetFullName(), "reference_time", psInv->refTime));
44✔
2051
        m_attributes.emplace_back(std::make_shared<GDALAttributeString>(
44✔
2052
            GetFullName(), "reference_time_unit", "sec UTC"));
44✔
2053
        m_attributes.emplace_back(std::make_shared<GDALAttributeNumeric>(
44✔
2054
            GetFullName(), "validity_time", m_adfTimes[0]));
44✔
2055
        m_attributes.emplace_back(std::make_shared<GDALAttributeString>(
44✔
2056
            GetFullName(), "validity_time_unit", "sec UTC"));
44✔
2057
        return;
22✔
2058
    }
2059

2060
    std::shared_ptr<GDALDimension> poDimTime;
1✔
2061

2062
    for (const auto &poDim : poGroup->m_dims)
3✔
2063
    {
2064
        if (STARTS_WITH(poDim->GetName().c_str(), "TIME") &&
2✔
2065
            poDim->GetSize() == m_adfTimes.size())
×
2066
        {
2067
            auto poVar = poDim->GetIndexingVariable();
×
2068
            if (poVar)
×
2069
            {
2070
                GUInt64 nStart = 0;
×
2071
                size_t nCount = 1;
×
2072
                double dfStartTime = 0;
×
2073
                poVar->Read(&nStart, &nCount, nullptr, nullptr, m_dt,
×
2074
                            &dfStartTime);
2075
                if (dfStartTime == m_adfTimes[0])
×
2076
                {
2077
                    poDimTime = poDim;
×
2078
                    break;
×
2079
                }
2080
            }
2081
        }
2082
    }
2083

2084
    if (!poDimTime)
1✔
2085
    {
2086
        std::string osName("TIME");
2✔
2087
        int counter = 2;
1✔
2088
        while (poGroup->m_oMapDims.find(osName) != poGroup->m_oMapDims.end())
1✔
2089
        {
2090
            osName = CPLSPrintf("TIME%d", counter);
×
2091
            counter++;
×
2092
        }
2093

2094
        poDimTime = std::make_shared<GDALDimensionWeakIndexingVar>(
2✔
2095
            poGroup->GetFullName(), osName, GDAL_DIM_TYPE_TEMPORAL,
1✔
2096
            std::string(), m_adfTimes.size());
3✔
2097
        poGroup->m_oMapDims[osName] = poDimTime;
1✔
2098
        poGroup->m_dims.emplace_back(poDimTime);
1✔
2099

2100
        auto var = poGroup->m_memRootGroup->CreateMDArray(
1✔
2101
            poDimTime->GetName(),
2102
            std::vector<std::shared_ptr<GDALDimension>>{poDimTime},
4✔
2103
            GDALExtendedDataType::Create(GDT_Float64), nullptr);
3✔
2104
        poDimTime->SetIndexingVariable(var);
1✔
2105
        poGroup->AddArray(var);
1✔
2106

2107
        GUInt64 nStart = 0;
1✔
2108
        size_t nCount = m_adfTimes.size();
1✔
2109
        var->SetUnit("sec UTC");
1✔
2110
        const GUInt64 anStart[] = {nStart};
1✔
2111
        const size_t anCount[] = {nCount};
1✔
2112
        var->Write(anStart, anCount, nullptr, nullptr, var->GetDataType(),
2✔
2113
                   &m_adfTimes[0]);
1✔
2114
        auto attr = var->CreateAttribute("long_name", {},
1✔
2115
                                         GDALExtendedDataType::CreateString());
3✔
2116
        attr->Write("validity_time");
1✔
2117
    }
2118

2119
#if defined(__GNUC__)
2120
#pragma GCC diagnostic push
2121
#pragma GCC diagnostic ignored "-Wnull-dereference"
2122
#endif
2123
    m_dims.insert(m_dims.begin(), poDimTime);
1✔
2124
#if defined(__GNUC__)
2125
#pragma GCC diagnostic pop
2126
#endif
2127
    if (m_poSRS)
1✔
2128
    {
2129
        auto mapping = m_poSRS->GetDataAxisToSRSAxisMapping();
2✔
2130
        for (auto &v : mapping)
3✔
2131
            v += 1;
2✔
2132
        m_poSRS->SetDataAxisToSRSAxisMapping(mapping);
1✔
2133
    }
2134
}
2135

2136
/************************************************************************/
2137
/*                              LoadData()                              */
2138
/************************************************************************/
2139

2140
const std::vector<double> &GRIBSharedResource::LoadData(vsi_l_offset nOffset,
6✔
2141
                                                        int subgNum)
2142
{
2143
    if (m_nOffsetCurData == nOffset)
6✔
2144
        return m_adfCurData;
1✔
2145

2146
    grib_MetaData *metadata = nullptr;
5✔
2147
    double *data = nullptr;
5✔
2148
    GRIBRasterBand::ReadGribData(m_fp, nOffset, subgNum, &data, &metadata);
5✔
2149
    if (data == nullptr || metadata == nullptr)
5✔
2150
    {
2151
        if (metadata != nullptr)
×
2152
        {
2153
            MetaFree(metadata);
×
2154
            delete metadata;
×
2155
        }
2156
        free(data);
×
2157
        m_adfCurData.clear();
×
2158
        return m_adfCurData;
×
2159
    }
2160
    const int nx = metadata->gds.Nx;
5✔
2161
    const int ny = metadata->gds.Ny;
5✔
2162
    MetaFree(metadata);
5✔
2163
    delete metadata;
5✔
2164
    if (nx <= 0 || ny <= 0)
5✔
2165
    {
2166
        free(data);
×
2167
        m_adfCurData.clear();
×
2168
        return m_adfCurData;
×
2169
    }
2170
    const size_t nPointCount = static_cast<size_t>(nx) * ny;
5✔
2171
    const size_t nByteCount = nPointCount * sizeof(double);
5✔
2172
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
2173
    if (nByteCount > static_cast<size_t>(INT_MAX))
2174
    {
2175
        CPLError(CE_Failure, CPLE_OutOfMemory,
2176
                 "Too large memory allocation attempt");
2177
        free(data);
2178
        m_adfCurData.clear();
2179
        return m_adfCurData;
2180
    }
2181
#endif
2182
    try
2183
    {
2184
        m_adfCurData.resize(nPointCount);
5✔
2185
    }
2186
    catch (const std::exception &e)
×
2187
    {
2188
        CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
×
2189
        free(data);
×
2190
        m_adfCurData.clear();
×
2191
        return m_adfCurData;
×
2192
    }
2193
    m_nOffsetCurData = nOffset;
5✔
2194
    memcpy(&m_adfCurData[0], data, nByteCount);
5✔
2195
    free(data);
5✔
2196
    return m_adfCurData;
5✔
2197
}
2198

2199
/************************************************************************/
2200
/*                             IRead()                                  */
2201
/************************************************************************/
2202

2203
bool GRIBArray::IRead(const GUInt64 *arrayStartIdx, const size_t *count,
4✔
2204
                      const GInt64 *arrayStep, const GPtrDiff_t *bufferStride,
2205
                      const GDALExtendedDataType &bufferDataType,
2206
                      void *pDstBuffer) const
2207
{
2208
    const size_t nBufferDTSize(bufferDataType.GetSize());
4✔
2209
    if (m_dims.size() == 2)
4✔
2210
    {
2211
        auto &vals = m_poShared->LoadData(m_anOffsets[0], m_anSubgNums[0]);
2✔
2212
        constexpr int Y_IDX = 0;
2✔
2213
        constexpr int X_IDX = 1;
2✔
2214
        if (vals.empty() ||
4✔
2215
            vals.size() != m_dims[Y_IDX]->GetSize() * m_dims[X_IDX]->GetSize())
2✔
2216
            return false;
×
2217
        const size_t nWidth(static_cast<size_t>(m_dims[X_IDX]->GetSize()));
2✔
2218
        const bool bDirectCopy = m_dt == bufferDataType &&
2✔
2219
                                 arrayStep[X_IDX] == 1 &&
3✔
2220
                                 bufferStride[X_IDX] == 1;
1✔
2221
        for (size_t j = 0; j < count[Y_IDX]; j++)
150✔
2222
        {
2223
            const size_t y = static_cast<size_t>(arrayStartIdx[Y_IDX] +
148✔
2224
                                                 j * arrayStep[Y_IDX]);
148✔
2225
            GByte *pabyDstPtr = static_cast<GByte *>(pDstBuffer) +
148✔
2226
                                j * bufferStride[Y_IDX] * nBufferDTSize;
148✔
2227
            const size_t x = static_cast<size_t>(arrayStartIdx[X_IDX]);
148✔
2228
            const double *srcPtr = &vals[y * nWidth + x];
148✔
2229
            if (bDirectCopy)
148✔
2230
            {
2231
                memcpy(pabyDstPtr, srcPtr, count[X_IDX] * sizeof(double));
74✔
2232
            }
2233
            else
2234
            {
2235
                const auto dstPtrInc = bufferStride[X_IDX] * nBufferDTSize;
74✔
2236
                for (size_t i = 0; i < count[X_IDX]; i++)
4,958✔
2237
                {
2238
                    GDALExtendedDataType::CopyValue(srcPtr, m_dt, pabyDstPtr,
4,884✔
2239
                                                    bufferDataType);
2240
                    srcPtr += static_cast<std::ptrdiff_t>(arrayStep[X_IDX]);
4,884✔
2241
                    pabyDstPtr += dstPtrInc;
4,884✔
2242
                }
2243
            }
2244
        }
2245
        return true;
2✔
2246
    }
2247

2248
    constexpr int T_IDX = 0;
2✔
2249
    constexpr int Y_IDX = 1;
2✔
2250
    constexpr int X_IDX = 2;
2✔
2251
    const size_t nWidth(static_cast<size_t>(m_dims[X_IDX]->GetSize()));
2✔
2252
    const bool bDirectCopy = m_dt == bufferDataType && arrayStep[X_IDX] == 1 &&
3✔
2253
                             bufferStride[X_IDX] == 1;
1✔
2254
    for (size_t k = 0; k < count[T_IDX]; k++)
6✔
2255
    {
2256
        const size_t tIdx =
4✔
2257
            static_cast<size_t>(arrayStartIdx[T_IDX] + k * arrayStep[T_IDX]);
4✔
2258
        CPLAssert(tIdx < m_anOffsets.size());
4✔
2259
        auto &vals =
2260
            m_poShared->LoadData(m_anOffsets[tIdx], m_anSubgNums[tIdx]);
4✔
2261
        if (vals.empty() ||
8✔
2262
            vals.size() != m_dims[Y_IDX]->GetSize() * m_dims[X_IDX]->GetSize())
4✔
2263
            return false;
×
2264
        for (size_t j = 0; j < count[Y_IDX]; j++)
520✔
2265
        {
2266
            const size_t y = static_cast<size_t>(arrayStartIdx[Y_IDX] +
516✔
2267
                                                 j * arrayStep[Y_IDX]);
516✔
2268
            GByte *pabyDstPtr =
516✔
2269
                static_cast<GByte *>(pDstBuffer) +
2270
                (k * bufferStride[T_IDX] + j * bufferStride[Y_IDX]) *
516✔
2271
                    nBufferDTSize;
2272
            const size_t x = static_cast<size_t>(arrayStartIdx[X_IDX]);
516✔
2273
            const double *srcPtr = &vals[y * nWidth + x];
516✔
2274
            if (bDirectCopy)
516✔
2275
            {
2276
                memcpy(pabyDstPtr, srcPtr, count[X_IDX] * sizeof(double));
258✔
2277
            }
2278
            else
2279
            {
2280
                const auto dstPtrInc = bufferStride[X_IDX] * nBufferDTSize;
258✔
2281
                for (size_t i = 0; i < count[X_IDX]; i++)
45,924✔
2282
                {
2283
                    GDALExtendedDataType::CopyValue(srcPtr, m_dt, pabyDstPtr,
45,666✔
2284
                                                    bufferDataType);
2285
                    srcPtr += static_cast<std::ptrdiff_t>(arrayStep[X_IDX]);
45,666✔
2286
                    pabyDstPtr += dstPtrInc;
45,666✔
2287
                }
2288
            }
2289
        }
2290
    }
2291

2292
    return true;
2✔
2293
}
2294

2295
/************************************************************************/
2296
/*                          OpenMultiDim()                              */
2297
/************************************************************************/
2298

2299
GDALDataset *GRIBDataset::OpenMultiDim(GDALOpenInfo *poOpenInfo)
4✔
2300

2301
{
2302
    auto poShared = std::make_shared<GRIBSharedResource>(
2303
        poOpenInfo->pszFilename, poOpenInfo->fpL);
8✔
2304
    auto poRootGroup = std::make_shared<GRIBGroup>(poShared);
8✔
2305
    poOpenInfo->fpL = nullptr;
4✔
2306

2307
    VSIFSeekL(poShared->m_fp, 0, SEEK_SET);
4✔
2308

2309
    // Contains an GRIB2 message inventory of the file.
2310
    // We can't use the potential .idx file
2311
    auto pInventories = std::make_unique<InventoryWrapperGrib>(poShared->m_fp);
8✔
2312

2313
    if (pInventories->result() <= 0)
4✔
2314
    {
2315
        char *errMsg = errSprintf(nullptr);
×
2316
        if (errMsg != nullptr)
×
2317
            CPLDebug("GRIB", "%s", errMsg);
×
2318
        free(errMsg);
×
2319

2320
        CPLError(CE_Failure, CPLE_OpenFailed,
×
2321
                 "%s is a grib file, "
2322
                 "but no raster dataset was successfully identified.",
2323
                 poOpenInfo->pszFilename);
2324
        return nullptr;
×
2325
    }
2326

2327
    // Create band objects.
2328
    GRIBDataset *poDS = new GRIBDataset();
4✔
2329
    poDS->fp = poShared->m_fp;
4✔
2330

2331
    std::shared_ptr<GRIBArray> poArray;
4✔
2332
    std::set<std::string> oSetArrayNames;
8✔
2333
    std::string osElement;
8✔
2334
    std::string osShortFstLevel;
8✔
2335
    double dfRefTime = 0;
4✔
2336
    double dfForecastTime = 0;
4✔
2337
    for (uInt4 i = 0; i < pInventories->length(); ++i)
28✔
2338
    {
2339
        inventoryType *psInv = pInventories->get(i);
24✔
2340
        uInt4 bandNr = i + 1;
24✔
2341
        assert(bandNr <= 65536);
24✔
2342

2343
        // GRIB messages can be preceded by "garbage". GRIB2Inventory()
2344
        // does not return the offset to the real start of the message
2345
        GByte abyHeader[1024 + 1];
2346
        VSIFSeekL(poShared->m_fp, psInv->start, SEEK_SET);
24✔
2347
        size_t nRead =
2348
            VSIFReadL(abyHeader, 1, sizeof(abyHeader) - 1, poShared->m_fp);
24✔
2349
        abyHeader[nRead] = 0;
24✔
2350
        // Find the real offset of the fist message
2351
        const char *pasHeader = reinterpret_cast<char *>(abyHeader);
24✔
2352
        int nOffsetFirstMessage = 0;
24✔
2353
        for (int j = 0; j < poOpenInfo->nHeaderBytes - 3; j++)
144✔
2354
        {
2355
            if (STARTS_WITH_CI(pasHeader + j, "GRIB")
144✔
2356
#ifdef ENABLE_TDLP
2357
                || STARTS_WITH_CI(pasHeader + j, "TDLP")
2358
#endif
2359
            )
2360
            {
2361
                nOffsetFirstMessage = j;
24✔
2362
                break;
24✔
2363
            }
2364
        }
2365
        psInv->start += nOffsetFirstMessage;
24✔
2366

2367
        if (poArray == nullptr || osElement != psInv->element ||
46✔
2368
            osShortFstLevel != psInv->shortFstLevel ||
46✔
2369
            !((dfRefTime == psInv->refTime &&
1✔
2370
               dfForecastTime != psInv->foreSec) ||
1✔
2371
              (dfRefTime != psInv->refTime &&
×
2372
               dfForecastTime == psInv->foreSec)))
×
2373
        {
2374
            if (poArray)
23✔
2375
            {
2376
                poArray->Finalize(poRootGroup.get(), pInventories->get(i - 1));
19✔
2377
                poRootGroup->AddArray(poArray);
19✔
2378
            }
2379

2380
            grib_MetaData *metaData = nullptr;
23✔
2381
            GRIBRasterBand::ReadGribData(poShared->m_fp, psInv->start,
23✔
2382
                                         psInv->subgNum, nullptr, &metaData);
23✔
2383
            if (metaData == nullptr || metaData->gds.Nx < 1 ||
23✔
2384
                metaData->gds.Ny < 1)
23✔
2385
            {
2386
                CPLError(CE_Failure, CPLE_OpenFailed,
×
2387
                         "%s is a grib file, "
2388
                         "but no raster dataset was successfully identified.",
2389
                         poOpenInfo->pszFilename);
2390

2391
                if (metaData != nullptr)
×
2392
                {
2393
                    MetaFree(metaData);
×
2394
                    delete metaData;
×
2395
                }
2396
                poDS->fp = nullptr;
×
2397
                CPLReleaseMutex(hGRIBMutex);
×
2398
                delete poDS;
×
2399
                CPLAcquireMutex(hGRIBMutex, 1000.0);
×
2400
                return nullptr;
×
2401
            }
2402
            psInv->GribVersion = metaData->GribVersion;
23✔
2403

2404
            // Set the DataSet's x,y size, georeference and projection from
2405
            // the first GRIB band.
2406
            poDS->SetGribMetaData(metaData);
23✔
2407

2408
            GRIBRasterBand gribBand(poDS, bandNr, psInv);
46✔
2409
            if (psInv->GribVersion == 2)
23✔
2410
                gribBand.FindPDSTemplateGRIB2();
7✔
2411
            osElement = psInv->element;
23✔
2412
            osShortFstLevel = psInv->shortFstLevel;
23✔
2413
            dfRefTime = psInv->refTime;
23✔
2414
            dfForecastTime = psInv->foreSec;
23✔
2415

2416
            std::string osRadix(osElement + '_' + osShortFstLevel);
46✔
2417
            std::string osName(osRadix);
46✔
2418
            int nCounter = 2;
23✔
2419
            while (oSetArrayNames.find(osName) != oSetArrayNames.end())
23✔
2420
            {
2421
                osName = osRadix + CPLSPrintf("_%d", nCounter);
×
2422
                nCounter++;
×
2423
            }
2424
            oSetArrayNames.insert(osName);
23✔
2425
            poArray = GRIBArray::Create(osName, poShared);
23✔
2426
            poArray->Init(poRootGroup.get(), poDS, &gribBand, psInv);
23✔
2427
            poArray->ExtendTimeDim(psInv->start, psInv->subgNum,
23✔
2428
                                   psInv->validTime);
2429

2430
            MetaFree(metaData);
23✔
2431
            delete metaData;
23✔
2432
        }
2433
        else
2434
        {
2435
            poArray->ExtendTimeDim(psInv->start, psInv->subgNum,
1✔
2436
                                   psInv->validTime);
2437
        }
2438
    }
2439

2440
    if (poArray)
4✔
2441
    {
2442
        poArray->Finalize(poRootGroup.get(),
8✔
2443
                          pInventories->get(pInventories->length() - 1));
4✔
2444
        poRootGroup->AddArray(poArray);
4✔
2445
    }
2446

2447
    poDS->fp = nullptr;
4✔
2448
    poDS->m_poRootGroup = poRootGroup;
4✔
2449

2450
    poDS->SetDescription(poOpenInfo->pszFilename);
4✔
2451

2452
    // Release hGRIBMutex otherwise we'll deadlock with GDALDataset own
2453
    // hGRIBMutex.
2454
    CPLReleaseMutex(hGRIBMutex);
4✔
2455
    poDS->TryLoadXML();
4✔
2456
    CPLAcquireMutex(hGRIBMutex, 1000.0);
4✔
2457

2458
    return poDS;
4✔
2459
}
2460

2461
/************************************************************************/
2462
/*                            SetMetadata()                             */
2463
/************************************************************************/
2464

2465
void GRIBDataset::SetGribMetaData(grib_MetaData *meta)
326✔
2466
{
2467
    nRasterXSize = meta->gds.Nx;
326✔
2468
    nRasterYSize = meta->gds.Ny;
326✔
2469

2470
    // Image projection.
2471
    OGRSpatialReference oSRS;
326✔
2472
    oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
326✔
2473

2474
    switch (meta->gds.projType)
326✔
2475
    {
2476
        case GS3_LATLON:
134✔
2477
        case GS3_GAUSSIAN_LATLON:
2478
            // No projection, only latlon system (geographic).
2479
            break;
134✔
2480
        case GS3_ROTATED_LATLON:
2✔
2481
            // will apply pole rotation afterwards
2482
            break;
2✔
2483
        case GS3_MERCATOR:
26✔
2484
            if (meta->gds.orientLon == 0.0)
26✔
2485
            {
2486
                if (meta->gds.meshLat == 0.0)
26✔
2487
                    oSRS.SetMercator(0.0, 0.0, 1.0, 0.0, 0.0);
5✔
2488
                else
2489
                    oSRS.SetMercator2SP(meta->gds.meshLat, 0.0, 0.0, 0.0, 0.0);
21✔
2490
            }
2491
            else
2492
            {
2493
                CPLError(CE_Warning, CPLE_NotSupported,
×
2494
                         "Orientation of the grid != 0 not supported");
2495
                return;
×
2496
            }
2497
            break;
26✔
2498
        case GS3_TRANSVERSE_MERCATOR:
135✔
2499
            oSRS.SetTM(meta->gds.latitude_of_origin,
135✔
2500
                       Lon360to180(meta->gds.central_meridian),
2501
                       std::abs(meta->gds.scaleLat1 - 0.9996) < 1e-8
135✔
2502
                           ? 0.9996
2503
                           : meta->gds.scaleLat1,
2504
                       meta->gds.x0, meta->gds.y0);
2505
            break;
135✔
2506
        case GS3_POLAR:
9✔
2507
            oSRS.SetPS(meta->gds.meshLat, meta->gds.orientLon, 1.0, 0.0, 0.0);
9✔
2508
            break;
9✔
2509
        case GS3_LAMBERT:
9✔
2510
            oSRS.SetLCC(meta->gds.scaleLat1, meta->gds.scaleLat2,
9✔
2511
                        meta->gds.meshLat, Lon360to180(meta->gds.orientLon),
2512
                        0.0, 0.0);
2513
            break;
9✔
2514
        case GS3_ALBERS_EQUAL_AREA:
5✔
2515
            oSRS.SetACEA(meta->gds.scaleLat1, meta->gds.scaleLat2,
5✔
2516
                         meta->gds.meshLat, Lon360to180(meta->gds.orientLon),
2517
                         0.0, 0.0);
2518
            break;
5✔
2519

2520
        case GS3_ORTHOGRAPHIC:
×
2521

2522
            // oSRS.SetOrthographic( 0.0, meta->gds.orientLon,
2523
            //                       meta->gds.lon2, meta->gds.lat2);
2524

2525
            // oSRS.SetGEOS( meta->gds.orientLon, meta->gds.stretchFactor,
2526
            //               meta->gds.lon2, meta->gds.lat2);
2527

2528
            // TODO: Hardcoded for now. How to parse the meta->gds section?
2529
            oSRS.SetGEOS(0, 35785831, 0, 0);
×
2530
            break;
×
2531
        case GS3_LAMBERT_AZIMUTHAL:
6✔
2532
            oSRS.SetLAEA(meta->gds.meshLat, Lon360to180(meta->gds.orientLon),
6✔
2533
                         0.0, 0.0);
2534
            break;
6✔
2535

2536
        case GS3_EQUATOR_EQUIDIST:
×
2537
            break;
×
2538
        case GS3_AZIMUTH_RANGE:
×
2539
            break;
×
2540
    }
2541

2542
    if (oSRS.IsProjected())
326✔
2543
    {
2544
        oSRS.SetLinearUnits("Metre", 1.0);
190✔
2545
    }
2546

2547
    const bool bHaveEarthModel =
326✔
2548
        meta->gds.majEarth > 0.0 && meta->gds.minEarth > 0.0;
326✔
2549
    // In meters.
2550
    const double a = bHaveEarthModel
2551
                         ? meta->gds.majEarth * 1.0e3
326✔
2552
                         : CPLAtof(CPLGetConfigOption("GRIB_DEFAULT_SEMI_MAJOR",
2✔
2553
                                                      "6377563.396"));
326✔
2554
    const double b =
2555
        bHaveEarthModel
2556
            ? meta->gds.minEarth * 1.0e3
328✔
2557
            : (meta->gds.f_sphere
2✔
2558
                   ? a
2✔
2559
                   : CPLAtof(CPLGetConfigOption("GRIB_DEFAULT_SEMI_MINOR",
×
2560
                                                "6356256.910")));
326✔
2561
    if (meta->gds.majEarth == 0 || meta->gds.minEarth == 0)
326✔
2562
    {
2563
        CPLDebug("GRIB", "No earth model. Assuming a=%f and b=%f", a, b);
×
2564
    }
2565
    else if (meta->gds.majEarth < 0 || meta->gds.minEarth < 0)
326✔
2566
    {
2567
        const char *pszUseDefaultSpheroid =
2568
            CPLGetConfigOption("GRIB_USE_DEFAULT_SPHEROID", nullptr);
2✔
2569
        if (!pszUseDefaultSpheroid)
2✔
2570
        {
2571
            CPLError(CE_Failure, CPLE_AppDefined,
1✔
2572
                     "The GRIB file contains invalid values for the spheroid. "
2573
                     "You may set the GRIB_USE_DEFAULT_SPHEROID configuration "
2574
                     "option to YES to use a default spheroid with "
2575
                     "a=%f and b=%f",
2576
                     a, b);
2577
            return;
1✔
2578
        }
2579
        else if (!CPLTestBool(pszUseDefaultSpheroid))
1✔
2580
        {
2581
            return;
×
2582
        }
2583
        CPLDebug("GRIB", "Invalid earth model. Assuming a=%f and b=%f", a, b);
1✔
2584
    }
2585

2586
    if (meta->gds.f_sphere || (a == b))
325✔
2587
    {
2588
        oSRS.SetGeogCS("Coordinate System imported from GRIB file", nullptr,
88✔
2589
                       "Sphere", a, 0.0);
2590
    }
2591
    else
2592
    {
2593
        const double fInv = a / (a - b);
237✔
2594
        if (std::abs(a - 6378137.0) < 0.01 &&
339✔
2595
            std::abs(fInv - 298.257223563) < 1e-9)  // WGS84
102✔
2596
        {
2597
            if (meta->gds.projType == GS3_LATLON)
100✔
2598
                oSRS.SetFromUserInput(SRS_WKT_WGS84_LAT_LONG);
68✔
2599
            else
2600
            {
2601
                oSRS.SetGeogCS("Coordinate System imported from GRIB file",
32✔
2602
                               "WGS_1984", "WGS 84", 6378137., 298.257223563);
2603
            }
2604
        }
2605
        else if (std::abs(a - 6378137.0) < 0.01 &&
139✔
2606
                 std::abs(fInv - 298.257222101) < 1e-9)  // GRS80
2✔
2607
        {
2608
            oSRS.SetGeogCS("Coordinate System imported from GRIB file", nullptr,
2✔
2609
                           "GRS80", 6378137., 298.257222101);
2610
        }
2611
        else
2612
        {
2613
            oSRS.SetGeogCS("Coordinate System imported from GRIB file", nullptr,
135✔
2614
                           "Spheroid imported from GRIB file", a, fInv);
2615
        }
2616
    }
2617

2618
    if (meta->gds.projType == GS3_ROTATED_LATLON)
325✔
2619
    {
2620
        oSRS.SetDerivedGeogCRSWithPoleRotationGRIBConvention(
2✔
2621
            oSRS.GetName(), meta->gds.southLat, Lon360to180(meta->gds.southLon),
2622
            meta->gds.angleRotate);
2623
    }
2624

2625
    OGRSpatialReference oLL;  // Construct the "geographic" part of oSRS.
325✔
2626
    oLL.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
325✔
2627
    oLL.CopyGeogCSFrom(&oSRS);
325✔
2628

2629
    double rMinX = 0.0;
325✔
2630
    double rMaxY = 0.0;
325✔
2631
    double rPixelSizeX = 0.0;
325✔
2632
    double rPixelSizeY = 0.0;
325✔
2633
    bool bError = false;
325✔
2634
    if (meta->gds.projType == GS3_ORTHOGRAPHIC)
325✔
2635
    {
2636
        // This is what should work, but it doesn't. Dx seems to have an
2637
        // inverse relation with pixel size.
2638
        // rMinX = -meta->gds.Dx * (meta->gds.Nx / 2);
2639
        // rMaxY = meta->gds.Dy * (meta->gds.Ny / 2);
2640
        // Hardcoded for now, assumption: GEOS projection, full disc (like MSG).
2641
        const double geosExtentInMeters = 11137496.552;
×
2642
        rMinX = -(geosExtentInMeters / 2);
×
2643
        rMaxY = geosExtentInMeters / 2;
×
2644
        rPixelSizeX = geosExtentInMeters / meta->gds.Nx;
×
2645
        rPixelSizeY = geosExtentInMeters / meta->gds.Ny;
×
2646
    }
2647
    else if (meta->gds.projType == GS3_TRANSVERSE_MERCATOR)
325✔
2648
    {
2649
        rMinX = meta->gds.x1;
134✔
2650
        rMaxY = meta->gds.y2;
134✔
2651
        rPixelSizeX = meta->gds.Dx;
134✔
2652
        rPixelSizeY = meta->gds.Dy;
134✔
2653
    }
2654
    else if (oSRS.IsProjected() && meta->gds.projType != GS3_ROTATED_LATLON)
191✔
2655
    {
2656
        // Longitude in degrees, to be transformed to meters (or degrees in
2657
        // case of latlon).
2658
        rMinX = Lon360to180(meta->gds.lon1);
55✔
2659
        // Latitude in degrees, to be transformed to meters.
2660
        double dfGridOriY = meta->gds.lat1;
55✔
2661

2662
        if (m_poSRS == nullptr || m_poLL == nullptr ||
55✔
2663
            !m_poSRS->IsSame(&oSRS) || !m_poLL->IsSame(&oLL))
55✔
2664
        {
2665
            m_poCT = std::unique_ptr<OGRCoordinateTransformation>(
110✔
2666
                OGRCreateCoordinateTransformation(&(oLL), &(oSRS)));
55✔
2667
        }
2668

2669
        // Transform it to meters.
2670
        if ((m_poCT != nullptr) && m_poCT->Transform(1, &rMinX, &dfGridOriY))
55✔
2671
        {
2672
            if (meta->gds.scan == GRIB2BIT_2)  // Y is minY, GDAL wants maxY.
55✔
2673
            {
2674
                const char *pszConfigOpt = CPLGetConfigOption(
55✔
2675
                    "GRIB_LATITUDE_OF_FIRST_GRID_POINT_IS_SOUTHERN_MOST",
2676
                    nullptr);
2677
                bool bLatOfFirstPointIsSouthernMost =
2678
                    !pszConfigOpt || CPLTestBool(pszConfigOpt);
55✔
2679

2680
                // Hack for a file called MANAL_2023030103.grb2 that
2681
                // uses LCC and has Latitude of false origin = 30
2682
                // Longitude of false origin = 140
2683
                // Latitude of 1st standard parallel = 60
2684
                // Latitude of 2nd standard parallel = 30
2685
                // but whose (meta->gds.lon1, meta->gds.lat1) qualifies the
2686
                // northern-most point of the grid and not the bottom-most one
2687
                // as it should given the scan == GRIB2BIT_2
2688
                if (!pszConfigOpt && meta->gds.projType == GS3_LAMBERT &&
55✔
2689
                    std::fabs(meta->gds.scaleLat1 - 60) <= 1e-8 &&
9✔
2690
                    std::fabs(meta->gds.scaleLat2 - 30) <= 1e-8 &&
1✔
2691
                    std::fabs(meta->gds.meshLat - 30) <= 1e-8 &&
111✔
2692
                    std::fabs(Lon360to180(meta->gds.orientLon) - 140) <= 1e-8)
1✔
2693
                {
2694
                    double dfXCenterProj = Lon360to180(meta->gds.orientLon);
1✔
2695
                    double dfYCenterProj = meta->gds.meshLat;
1✔
2696
                    if (m_poCT->Transform(1, &dfXCenterProj, &dfYCenterProj))
1✔
2697
                    {
2698
                        double dfXCenterGridNominal =
1✔
2699
                            rMinX + nRasterXSize * meta->gds.Dx / 2;
1✔
2700
                        double dfYCenterGridNominal =
1✔
2701
                            dfGridOriY + nRasterYSize * meta->gds.Dy / 2;
1✔
2702
                        double dfXCenterGridBuggy = dfXCenterGridNominal;
1✔
2703
                        double dfYCenterGridBuggy =
1✔
2704
                            dfGridOriY - nRasterYSize * meta->gds.Dy / 2;
1✔
2705
                        const auto SQR = [](double x) { return x * x; };
5✔
2706
                        if (SQR(dfXCenterGridBuggy - dfXCenterProj) +
1✔
2707
                                SQR(dfYCenterGridBuggy - dfYCenterProj) <
1✔
2708
                            SQR(10) *
1✔
2709
                                (SQR(dfXCenterGridNominal - dfXCenterProj) +
2✔
2710
                                 SQR(dfYCenterGridNominal - dfYCenterProj)))
1✔
2711
                        {
2712
                            CPLError(
1✔
2713
                                CE_Warning, CPLE_AppDefined,
2714
                                "Likely buggy grid registration for GRIB2 "
2715
                                "product: heuristics shows that the "
2716
                                "latitudeOfFirstGridPoint is likely to qualify "
2717
                                "the latitude of the northern-most grid point "
2718
                                "instead of the southern-most grid point as "
2719
                                "expected. Please report to data producer. "
2720
                                "This heuristics can be disabled by setting "
2721
                                "the "
2722
                                "GRIB_LATITUDE_OF_FIRST_GRID_POINT_IS_SOUTHERN_"
2723
                                "MOST configuration option to YES.");
2724
                            bLatOfFirstPointIsSouthernMost = false;
1✔
2725
                        }
2726
                    }
2727
                }
2728
                if (bLatOfFirstPointIsSouthernMost)
55✔
2729
                {
2730
                    // -1 because we GDAL needs the coordinates of the centre of
2731
                    // the pixel.
2732
                    rMaxY = dfGridOriY + (meta->gds.Ny - 1) * meta->gds.Dy;
54✔
2733
                }
2734
                else
2735
                {
2736
                    rMaxY = dfGridOriY;
1✔
2737
                }
2738
            }
2739
            else
2740
            {
2741
                rMaxY = dfGridOriY;
×
2742
            }
2743
            rPixelSizeX = meta->gds.Dx;
55✔
2744
            rPixelSizeY = meta->gds.Dy;
55✔
2745
        }
2746
        else
2747
        {
2748
            rMinX = 0.0;
×
2749
            // rMaxY = 0.0;
2750

2751
            rPixelSizeX = 1.0;
×
2752
            rPixelSizeY = -1.0;
×
2753

2754
            bError = true;
×
2755
            CPLError(CE_Warning, CPLE_AppDefined,
×
2756
                     "Unable to perform coordinate transformations, so the "
2757
                     "correct projected geotransform could not be deduced "
2758
                     "from the lat/long control points.  "
2759
                     "Defaulting to ungeoreferenced.");
2760
        }
2761
    }
2762
    else
2763
    {
2764
        // Longitude in degrees, to be transformed to meters (or degrees in
2765
        // case of latlon).
2766
        rMinX = meta->gds.lon1;
136✔
2767
        // Latitude in degrees, to be transformed to meters.
2768
        rMaxY = meta->gds.lat1;
136✔
2769

2770
        double rMinY = meta->gds.lat2;
136✔
2771
        double rMaxX = meta->gds.lon2;
136✔
2772
        if (meta->gds.lat2 > rMaxY)
136✔
2773
        {
2774
            rMaxY = meta->gds.lat2;
105✔
2775
            rMinY = meta->gds.lat1;
105✔
2776
        }
2777

2778
        if (meta->gds.Nx == 1)
136✔
2779
            rPixelSizeX = meta->gds.Dx;
16✔
2780
        else if (meta->gds.lon1 > meta->gds.lon2)
120✔
2781
            rPixelSizeX = (360.0 - (meta->gds.lon1 - meta->gds.lon2)) /
13✔
2782
                          (meta->gds.Nx - 1);
13✔
2783
        else
2784
            rPixelSizeX =
107✔
2785
                (meta->gds.lon2 - meta->gds.lon1) / (meta->gds.Nx - 1);
107✔
2786

2787
        if (meta->gds.Ny == 1)
136✔
2788
            rPixelSizeY = meta->gds.Dy;
17✔
2789
        else
2790
            rPixelSizeY = (rMaxY - rMinY) / (meta->gds.Ny - 1);
119✔
2791

2792
        // Do some sanity checks for cases that can't be handled by the above
2793
        // pixel size corrections. GRIB1 has a minimum precision of 0.001
2794
        // for latitudes and longitudes, so we'll allow a bit higher than that.
2795
        if (rPixelSizeX < 0 || fabs(rPixelSizeX - meta->gds.Dx) > 0.002)
136✔
2796
            rPixelSizeX = meta->gds.Dx;
×
2797

2798
        if (rPixelSizeY < 0 || fabs(rPixelSizeY - meta->gds.Dy) > 0.002)
136✔
2799
            rPixelSizeY = meta->gds.Dy;
×
2800

2801
        // GRIB2 files have longitudes in the [0-360] range
2802
        // Shift them to the traditional [-180,180] longitude range
2803
        // See https://github.com/OSGeo/gdal/issues/4524
2804
        if ((rMinX + rPixelSizeX >= 180 || rMaxX - rPixelSizeX >= 180) &&
194✔
2805
            CPLTestBool(
58✔
2806
                CPLGetConfigOption("GRIB_ADJUST_LONGITUDE_RANGE", "YES")))
2807
        {
2808
            if (rPixelSizeX * nRasterXSize > 360 + rPixelSizeX / 4)
56✔
2809
                CPLDebug("GRIB", "Cannot properly handle GRIB2 files with "
×
2810
                                 "overlaps and 0-360 longitudes");
2811
            else if (rMinX == 180)
56✔
2812
            {
2813
                // Case of https://github.com/OSGeo/gdal/issues/10655
2814
                CPLDebug("GRIB", "Shifting longitudes from %lf:%lf to %lf:%lf",
2✔
2815
                         rMinX, rMaxX, -180.0, Lon360to180(rMaxX));
2816
                rMinX = -180;
2✔
2817
            }
2818
            else if (fabs(360 - rPixelSizeX * nRasterXSize) < rPixelSizeX / 4 &&
54✔
2819
                     rMinX <= 180 && meta->gds.projType == GS3_LATLON)
23✔
2820
            {
2821
                // Find the first row number east of the antimeridian
2822
                const int nSplitAndSwapColumnCandidate =
8✔
2823
                    static_cast<int>(ceil((180 - rMinX) / rPixelSizeX));
8✔
2824
                if (nSplitAndSwapColumnCandidate < nRasterXSize)
8✔
2825
                {
2826
                    nSplitAndSwapColumn = nSplitAndSwapColumnCandidate;
8✔
2827
                    CPLDebug("GRIB",
8✔
2828
                             "Rewrapping around the antimeridian at column %d",
2829
                             nSplitAndSwapColumn);
2830
                    rMinX = -180;
8✔
2831
                }
8✔
2832
            }
2833
            else if (Lon360to180(rMinX) > Lon360to180(rMaxX))
46✔
2834
            {
2835
                CPLDebug("GRIB", "GRIB with 0-360 longitudes spanning across "
2✔
2836
                                 "the antimeridian");
2837
                rMinX = Lon360to180(rMinX);
2✔
2838
            }
2839
            else
2840
            {
2841
                CPLDebug("GRIB", "Shifting longitudes from %lf:%lf to %lf:%lf",
44✔
2842
                         rMinX, rMaxX, Lon360to180(rMinX), Lon360to180(rMaxX));
2843
                rMinX = Lon360to180(rMinX);
44✔
2844
            }
2845
        }
2846
    }
2847

2848
    // https://gdal.org/user/raster_data_model.html :
2849
    //   we need the top left corner of the top left pixel.
2850
    //   At the moment we have the center of the pixel.
2851
    rMinX -= rPixelSizeX / 2;
325✔
2852
    rMaxY += rPixelSizeY / 2;
325✔
2853

2854
    m_gt[0] = rMinX;
325✔
2855
    m_gt[3] = rMaxY;
325✔
2856
    m_gt[1] = rPixelSizeX;
325✔
2857
    m_gt[5] = -rPixelSizeY;
325✔
2858

2859
    if (bError)
325✔
2860
        m_poSRS.reset();
×
2861
    else
2862
        m_poSRS.reset(oSRS.Clone());
325✔
2863
    m_poLL = std::unique_ptr<OGRSpatialReference>(oLL.Clone());
325✔
2864
}
2865

2866
/************************************************************************/
2867
/*                       GDALDeregister_GRIB()                          */
2868
/************************************************************************/
2869

2870
static void GDALDeregister_GRIB(GDALDriver *)
1,113✔
2871
{
2872
    if (hGRIBMutex != nullptr)
1,113✔
2873
    {
2874
        MetanameCleanup();
1✔
2875
        CPLDestroyMutex(hGRIBMutex);
1✔
2876
        hGRIBMutex = nullptr;
1✔
2877
    }
2878
}
1,113✔
2879

2880
/************************************************************************/
2881
/*                          GDALGRIBDriver                              */
2882
/************************************************************************/
2883

2884
class GDALGRIBDriver : public GDALDriver
2885
{
2886
    std::mutex m_oMutex{};
2887
    bool m_bHasFullInitMetadata = false;
2888
    void InitializeMetadata();
2889

2890
  public:
2891
    GDALGRIBDriver() = default;
1,629✔
2892

2893
    char **GetMetadata(const char *pszDomain = "") override;
2894
    const char *GetMetadataItem(const char *pszName,
2895
                                const char *pszDomain) override;
2896
};
2897

2898
/************************************************************************/
2899
/*                         InitializeMetadata()                         */
2900
/************************************************************************/
2901

2902
void GDALGRIBDriver::InitializeMetadata()
884✔
2903
{
2904
    {
2905
        // Defer until necessary the setting of the CreationOptionList
2906
        // to let a chance to JPEG2000 drivers to have been loaded.
2907
        if (!m_bHasFullInitMetadata)
884✔
2908
        {
2909
            m_bHasFullInitMetadata = true;
210✔
2910

2911
            std::vector<CPLString> aosJ2KDrivers;
420✔
2912
            for (size_t i = 0; i < CPL_ARRAYSIZE(apszJ2KDrivers); i++)
1,050✔
2913
            {
2914
                if (GDALGetDriverByName(apszJ2KDrivers[i]) != nullptr)
840✔
2915
                {
2916
                    aosJ2KDrivers.push_back(apszJ2KDrivers[i]);
420✔
2917
                }
2918
            }
2919

2920
            CPLString osCreationOptionList(
2921
                "<CreationOptionList>"
2922
                "   <Option name='DATA_ENCODING' type='string-select' "
2923
                "default='AUTO' "
2924
                "description='How data is encoded internally'>"
2925
                "       <Value>AUTO</Value>"
2926
                "       <Value>SIMPLE_PACKING</Value>"
2927
                "       <Value>COMPLEX_PACKING</Value>"
2928
                "       <Value>IEEE_FLOATING_POINT</Value>");
420✔
2929
            if (GDALGetDriverByName("PNG") != nullptr)
210✔
2930
                osCreationOptionList += "       <Value>PNG</Value>";
210✔
2931
            if (!aosJ2KDrivers.empty())
210✔
2932
                osCreationOptionList += "       <Value>JPEG2000</Value>";
210✔
2933
            osCreationOptionList +=
2934
                "   </Option>"
2935
                "   <Option name='NBITS' type='int' default='0' "
2936
                "description='Number of bits per value'/>"
2937
                "   <Option name='DECIMAL_SCALE_FACTOR' type='int' default='0' "
2938
                "description='Value such that raw values are multiplied by "
2939
                "10^DECIMAL_SCALE_FACTOR before integer encoding'/>"
2940
                "   <Option name='SPATIAL_DIFFERENCING_ORDER' type='int' "
2941
                "default='0' "
2942
                "description='Order of spatial difference' min='0' max='2'/>";
210✔
2943
            if (!aosJ2KDrivers.empty())
210✔
2944
            {
2945
                osCreationOptionList +=
2946
                    "   <Option name='COMPRESSION_RATIO' type='int' "
2947
                    "default='1' min='1' max='100' "
2948
                    "description='N:1 target compression ratio for JPEG2000'/>"
2949
                    "   <Option name='JPEG2000_DRIVER' type='string-select' "
2950
                    "description='Explicitly select a JPEG2000 driver'>";
210✔
2951
                for (size_t i = 0; i < aosJ2KDrivers.size(); i++)
630✔
2952
                {
2953
                    osCreationOptionList +=
2954
                        "       <Value>" + aosJ2KDrivers[i] + "</Value>";
420✔
2955
                }
2956
                osCreationOptionList += "   </Option>";
210✔
2957
            }
2958
            osCreationOptionList +=
2959
                "   <Option name='DISCIPLINE' type='int' "
2960
                "description='Discipline of the processed data'/>"
2961
                "   <Option name='IDS' type='string' "
2962
                "description='String equivalent to the GRIB_IDS metadata "
2963
                "item'/>"
2964
                "   <Option name='IDS_CENTER' type='int' "
2965
                "description='Originating/generating center'/>"
2966
                "   <Option name='IDS_SUBCENTER' type='int' "
2967
                "description='Originating/generating subcenter'/>"
2968
                "   <Option name='IDS_MASTER_TABLE' type='int' "
2969
                "description='GRIB master tables version number'/>"
2970
                "   <Option name='IDS_SIGNF_REF_TIME' type='int' "
2971
                "description='Significance of Reference Time'/>"
2972
                "   <Option name='IDS_REF_TIME' type='string' "
2973
                "description='Reference time as YYYY-MM-DDTHH:MM:SSZ'/>"
2974
                "   <Option name='IDS_PROD_STATUS' type='int' "
2975
                "description='Production Status of Processed data'/>"
2976
                "   <Option name='IDS_TYPE' type='int' "
2977
                "description='Type of processed data'/>"
2978
                "   <Option name='PDS_PDTN' type='int' "
2979
                "description='Product Definition Template Number'/>"
2980
                "   <Option name='PDS_TEMPLATE_NUMBERS' type='string' "
2981
                "description='Product definition template raw numbers'/>"
2982
                "   <Option name='PDS_TEMPLATE_ASSEMBLED_VALUES' type='string' "
2983
                "description='Product definition template assembled values'/>"
2984
                "   <Option name='INPUT_UNIT' type='string' "
2985
                "description='Unit of input values. Only for temperatures. C "
2986
                "or K'/>"
2987
                "   <Option name='BAND_*' type='string' "
2988
                "description='Override options at band level'/>"
2989
                "</CreationOptionList>";
210✔
2990

2991
            SetMetadataItem(GDAL_DMD_CREATIONOPTIONLIST, osCreationOptionList);
210✔
2992
        }
2993
    }
2994
}
884✔
2995

2996
/************************************************************************/
2997
/*                            GetMetadata()                             */
2998
/************************************************************************/
2999

3000
char **GDALGRIBDriver::GetMetadata(const char *pszDomain)
520✔
3001
{
3002
    std::lock_guard oLock(m_oMutex);
1,040✔
3003
    if (pszDomain == nullptr || EQUAL(pszDomain, ""))
520✔
3004
    {
3005
        InitializeMetadata();
520✔
3006
    }
3007
    return GDALDriver::GetMetadata(pszDomain);
1,040✔
3008
}
3009

3010
/************************************************************************/
3011
/*                          GetMetadataItem()                           */
3012
/************************************************************************/
3013

3014
const char *GDALGRIBDriver::GetMetadataItem(const char *pszName,
59,915✔
3015
                                            const char *pszDomain)
3016
{
3017
    std::lock_guard oLock(m_oMutex);
119,830✔
3018
    if (pszDomain == nullptr || EQUAL(pszDomain, ""))
59,915✔
3019
    {
3020
        // Defer until necessary the setting of the CreationOptionList
3021
        // to let a chance to JPEG2000 drivers to have been loaded.
3022
        if (EQUAL(pszName, GDAL_DMD_CREATIONOPTIONLIST))
59,915✔
3023
            InitializeMetadata();
364✔
3024
    }
3025
    return GDALDriver::GetMetadataItem(pszName, pszDomain);
119,830✔
3026
}
3027

3028
/************************************************************************/
3029
/*                         GDALRegister_GRIB()                          */
3030
/************************************************************************/
3031

3032
void GDALRegister_GRIB()
1,911✔
3033

3034
{
3035
    if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
1,911✔
3036
        return;
282✔
3037

3038
    GDALDriver *poDriver = new GDALGRIBDriver();
1,629✔
3039
    GRIBDriverSetCommonMetadata(poDriver);
1,629✔
3040

3041
    poDriver->pfnOpen = GRIBDataset::Open;
1,629✔
3042
    poDriver->pfnCreateCopy = GRIBDataset::CreateCopy;
1,629✔
3043
    poDriver->pfnUnloadDriver = GDALDeregister_GRIB;
1,629✔
3044

3045
#ifdef USE_AEC
3046
    poDriver->SetMetadataItem("HAVE_AEC", "YES");
1,629✔
3047
#endif
3048

3049
    GetGDALDriverManager()->RegisterDriver(poDriver);
1,629✔
3050
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc