Coverage Report

Created: 2025-06-09 07:07

/src/gdal/frmts/grib/gribdataset.cpp
Line
Count
Source (jump to first uncovered line)
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)
67
27.9k
{
68
27.9k
    if (pszTxt == nullptr)
69
0
        return CPLString();
70
27.9k
    if (!bMetricUnits)
71
0
        return pszTxt;
72
73
27.9k
    CPLString osRes(pszTxt);
74
27.9k
    size_t iPos = osRes.find("[K]");
75
27.9k
    if (iPos != std::string::npos)
76
5.13k
        osRes = osRes.substr(0, iPos) + "[C]" + osRes.substr(iPos + 3);
77
27.9k
    return osRes;
78
27.9k
}
79
80
/************************************************************************/
81
/*                         Lon360to180()                               */
82
/************************************************************************/
83
84
static inline double Lon360to180(double lon)
85
345
{
86
345
    if (lon == 180)
87
0
        return 180;
88
345
    return fmod(lon + 180, 360) - 180;
89
345
}
90
91
/************************************************************************/
92
/*                           GRIBRasterBand()                            */
93
/************************************************************************/
94
95
GRIBRasterBand::GRIBRasterBand(GRIBDataset *poDSIn, int nBandIn,
96
                               inventoryType *psInv)
97
13.9k
    : start(psInv->start), subgNum(psInv->subgNum),
98
13.9k
      longFstLevel(CPLStrdup(psInv->longFstLevel)), m_Grib_Data(nullptr),
99
13.9k
      m_Grib_MetaData(nullptr), nGribDataXSize(poDSIn->nRasterXSize),
100
13.9k
      nGribDataYSize(poDSIn->nRasterYSize), m_nGribVersion(psInv->GribVersion),
101
13.9k
      m_bHasLookedForNoData(false), m_dfNoData(0.0), m_bHasNoData(false)
102
103
13.9k
{
104
13.9k
    poDS = poDSIn;
105
13.9k
    nBand = nBandIn;
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
13.9k
    eDataType = GDT_Float64;
110
111
13.9k
    nBlockXSize = poDSIn->nRasterXSize;
112
13.9k
    nBlockYSize = 1;
113
114
13.9k
    if (psInv->unitName != nullptr && psInv->comment != nullptr &&
115
13.9k
        psInv->element != nullptr)
116
13.9k
    {
117
13.9k
        bLoadedMetadata = true;
118
13.9k
        const char *pszGribNormalizeUnits =
119
13.9k
            CPLGetConfigOption("GRIB_NORMALIZE_UNITS", "YES");
120
13.9k
        bool bMetricUnits = CPLTestBool(pszGribNormalizeUnits);
121
122
13.9k
        SetMetadataItem("GRIB_UNIT",
123
13.9k
                        ConvertUnitInText(bMetricUnits, psInv->unitName));
124
13.9k
        SetMetadataItem("GRIB_COMMENT",
125
13.9k
                        ConvertUnitInText(bMetricUnits, psInv->comment));
126
13.9k
        SetMetadataItem("GRIB_ELEMENT", psInv->element);
127
13.9k
        SetMetadataItem("GRIB_SHORT_NAME", psInv->shortFstLevel);
128
13.9k
        SetMetadataItem("GRIB_REF_TIME",
129
13.9k
                        CPLString().Printf("%.0f", psInv->refTime));
130
13.9k
        SetMetadataItem("GRIB_VALID_TIME",
131
13.9k
                        CPLString().Printf("%.0f", psInv->validTime));
132
13.9k
        SetMetadataItem("GRIB_FORECAST_SECONDS",
133
13.9k
                        CPLString().Printf("%.0f", psInv->foreSec));
134
13.9k
    }
135
13.9k
}
136
137
/************************************************************************/
138
/*                           FindMetaData()                             */
139
/************************************************************************/
140
141
void GRIBRasterBand::FindMetaData()
142
0
{
143
0
    if (bLoadedMetadata)
144
0
        return;
145
0
    if (m_Grib_MetaData == nullptr)
146
0
    {
147
0
        grib_MetaData *metaData;
148
0
        GRIBDataset *poGDS = static_cast<GRIBDataset *>(poDS);
149
0
        GRIBRasterBand::ReadGribData(poGDS->fp, start, subgNum, nullptr,
150
0
                                     &metaData);
151
0
        if (metaData == nullptr)
152
0
            return;
153
0
        m_Grib_MetaData = metaData;
154
0
    }
155
0
    bLoadedMetadata = true;
156
0
    m_nGribVersion = m_Grib_MetaData->GribVersion;
157
158
0
    const char *pszGribNormalizeUnits =
159
0
        CPLGetConfigOption("GRIB_NORMALIZE_UNITS", "YES");
160
0
    bool bMetricUnits = CPLTestBool(pszGribNormalizeUnits);
161
162
0
    GDALRasterBand::SetMetadataItem(
163
0
        "GRIB_UNIT",
164
0
        ConvertUnitInText(bMetricUnits, m_Grib_MetaData->unitName));
165
0
    GDALRasterBand::SetMetadataItem(
166
0
        "GRIB_COMMENT",
167
0
        ConvertUnitInText(bMetricUnits, m_Grib_MetaData->comment));
168
169
0
    GDALRasterBand::SetMetadataItem("GRIB_ELEMENT", m_Grib_MetaData->element);
170
0
    GDALRasterBand::SetMetadataItem("GRIB_SHORT_NAME",
171
0
                                    m_Grib_MetaData->shortFstLevel);
172
173
0
    if (m_nGribVersion == 2)
174
0
    {
175
0
        GDALRasterBand::SetMetadataItem(
176
0
            "GRIB_REF_TIME",
177
0
            CPLString().Printf("%.0f", m_Grib_MetaData->pds2.refTime));
178
0
        GDALRasterBand::SetMetadataItem(
179
0
            "GRIB_VALID_TIME",
180
0
            CPLString().Printf("%.0f", m_Grib_MetaData->pds2.sect4.validTime));
181
0
    }
182
0
    else if (m_nGribVersion == 1)
183
0
    {
184
0
        GDALRasterBand::SetMetadataItem(
185
0
            "GRIB_REF_TIME",
186
0
            CPLString().Printf("%.0f", m_Grib_MetaData->pds1.refTime));
187
0
        GDALRasterBand::SetMetadataItem(
188
0
            "GRIB_VALID_TIME",
189
0
            CPLString().Printf("%.0f", m_Grib_MetaData->pds1.validTime));
190
0
    }
191
192
0
    GDALRasterBand::SetMetadataItem(
193
0
        "GRIB_FORECAST_SECONDS",
194
0
        CPLString().Printf("%d", m_Grib_MetaData->deltTime));
195
0
}
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)
204
819
{
205
    // GRIB messages can be preceded by "garbage". GRIB2Inventory()
206
    // does not return the offset to the real start of the message
207
819
    char szHeader[1024 + 1];
208
819
    VSIFSeekL(fp, start, SEEK_SET);
209
819
    const int nRead =
210
819
        static_cast<int>(VSIFReadL(szHeader, 1, sizeof(szHeader) - 1, fp));
211
819
    szHeader[nRead] = 0;
212
    // Find the real offset of the fist message
213
819
    int nOffsetFirstMessage = 0;
214
130k
    for (int j = 0; j + 3 < nRead; j++)
215
130k
    {
216
130k
        if (STARTS_WITH_CI(szHeader + j, "GRIB")
217
#ifdef ENABLE_TDLP
218
            || STARTS_WITH_CI(szHeader + j, "TDLP")
219
#endif
220
130k
        )
221
819
        {
222
819
            nOffsetFirstMessage = j;
223
819
            break;
224
819
        }
225
130k
    }
226
819
    return start + nOffsetFirstMessage;
227
819
}
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()
237
238
0
{
239
0
    CPLAssert(m_nGribVersion == 2);
240
241
0
    if (bLoadedPDS)
242
0
        return;
243
0
    bLoadedPDS = true;
244
245
0
    GRIBDataset *poGDS = static_cast<GRIBDataset *>(poDS);
246
0
    start = FindTrueStart(poGDS->fp, start);
247
248
    // Read section 0
249
0
    GByte abySection0[16];
250
0
    if (VSIFSeekL(poGDS->fp, start, SEEK_SET) != 0 ||
251
0
        VSIFReadL(abySection0, 16, 1, poGDS->fp) != 1)
252
0
    {
253
0
        CPLDebug("GRIB", "Cannot read leading bytes of section 0");
254
0
        return;
255
0
    }
256
0
    GByte nDiscipline = abySection0[7 - 1];
257
0
    CPLString osDiscipline;
258
0
    osDiscipline = CPLString().Printf("%d", nDiscipline);
259
0
    static const char *const table00[] = {"Meteorological",
260
0
                                          "Hydrological",
261
0
                                          "Land Surface",
262
0
                                          "Space products",
263
0
                                          "Space products",
264
0
                                          "Reserved",
265
0
                                          "Reserved",
266
0
                                          "Reserved",
267
0
                                          "Reserved",
268
0
                                          "Reserved",
269
0
                                          "Oceanographic Products"};
270
0
    m_nDisciplineCode = nDiscipline;
271
0
    if (nDiscipline < CPL_ARRAYSIZE(table00))
272
0
    {
273
0
        m_osDisciplineName = table00[nDiscipline];
274
0
        osDiscipline += CPLString("(") +
275
0
                        CPLString(table00[nDiscipline]).replaceAll(' ', '_') +
276
0
                        ")";
277
0
    }
278
279
0
    GDALRasterBand::SetMetadataItem("GRIB_DISCIPLINE", osDiscipline.c_str());
280
281
0
    GByte abyHead[5] = {0};
282
283
0
    if (VSIFReadL(abyHead, 5, 1, poGDS->fp) != 1)
284
0
    {
285
0
        CPLDebug("GRIB", "Cannot read 5 leading bytes past section 0");
286
0
        return;
287
0
    }
288
289
0
    GUInt32 nSectSize = 0;
290
0
    if (abyHead[4] == 1)
291
0
    {
292
0
        memcpy(&nSectSize, abyHead, 4);
293
0
        CPL_MSBPTR32(&nSectSize);
294
0
        if (nSectSize >= 21 && nSectSize <= 100000 /* arbitrary upper limit */)
295
0
        {
296
0
            GByte *pabyBody = static_cast<GByte *>(CPLMalloc(nSectSize));
297
0
            memcpy(pabyBody, abyHead, 5);
298
0
            VSIFReadL(pabyBody + 5, 1, nSectSize - 5, poGDS->fp);
299
300
0
            CPLString osIDS;
301
0
            unsigned short nCenter = static_cast<unsigned short>(
302
0
                pabyBody[6 - 1] * 256 + pabyBody[7 - 1]);
303
0
            if (nCenter != GRIB2MISSING_u1 && nCenter != GRIB2MISSING_u2)
304
0
            {
305
0
                osIDS += "CENTER=";
306
0
                m_nCenter = nCenter;
307
0
                osIDS += CPLSPrintf("%d", nCenter);
308
0
                const char *pszCenter = centerLookup(nCenter);
309
0
                if (pszCenter)
310
0
                {
311
0
                    m_osCenterName = pszCenter;
312
0
                    osIDS += CPLString("(") + pszCenter + ")";
313
0
                }
314
0
            }
315
316
0
            unsigned short nSubCenter = static_cast<unsigned short>(
317
0
                pabyBody[8 - 1] * 256 + pabyBody[9 - 1]);
318
0
            if (nSubCenter != GRIB2MISSING_u2)
319
0
            {
320
0
                if (!osIDS.empty())
321
0
                    osIDS += " ";
322
0
                osIDS += "SUBCENTER=";
323
0
                osIDS += CPLSPrintf("%d", nSubCenter);
324
0
                m_nSubCenter = nSubCenter;
325
0
                const char *pszSubCenter = subCenterLookup(nCenter, nSubCenter);
326
0
                if (pszSubCenter)
327
0
                {
328
0
                    m_osSubCenterName = pszSubCenter;
329
0
                    osIDS += CPLString("(") + pszSubCenter + ")";
330
0
                }
331
0
            }
332
333
0
            if (!osIDS.empty())
334
0
                osIDS += " ";
335
0
            osIDS += "MASTER_TABLE=";
336
0
            osIDS += CPLSPrintf("%d", pabyBody[10 - 1]);
337
0
            osIDS += " ";
338
0
            osIDS += "LOCAL_TABLE=";
339
0
            osIDS += CPLSPrintf("%d", pabyBody[11 - 1]);
340
0
            osIDS += " ";
341
0
            osIDS += "SIGNF_REF_TIME=";
342
0
            unsigned nSignRefTime = pabyBody[12 - 1];
343
0
            osIDS += CPLSPrintf("%d", nSignRefTime);
344
0
            static const char *const table12[] = {
345
0
                "Analysis", "Start of Forecast", "Verifying time of forecast",
346
0
                "Observation time"};
347
0
            if (nSignRefTime < CPL_ARRAYSIZE(table12))
348
0
            {
349
0
                m_osSignRefTimeName = table12[nSignRefTime];
350
0
                osIDS += CPLString("(") +
351
0
                         CPLString(table12[nSignRefTime]).replaceAll(' ', '_') +
352
0
                         ")";
353
0
            }
354
0
            osIDS += " ";
355
0
            osIDS += "REF_TIME=";
356
0
            m_osRefTime =
357
0
                CPLSPrintf("%04d-%02d-%02dT%02d:%02d:%02dZ",
358
0
                           pabyBody[13 - 1] * 256 + pabyBody[14 - 1],
359
0
                           pabyBody[15 - 1], pabyBody[16 - 1], pabyBody[17 - 1],
360
0
                           pabyBody[18 - 1], pabyBody[19 - 1]);
361
0
            osIDS += m_osRefTime;
362
0
            osIDS += " ";
363
0
            osIDS += "PROD_STATUS=";
364
0
            unsigned nProdStatus = pabyBody[20 - 1];
365
0
            osIDS += CPLSPrintf("%d", nProdStatus);
366
0
            static const char *const table13[] = {
367
0
                "Operational",     "Operational test",
368
0
                "Research",        "Re-analysis",
369
0
                "TIGGE",           "TIGGE test",
370
0
                "S2S operational", "S2S test",
371
0
                "UERRA",           "UERRA test"};
372
0
            if (nProdStatus < CPL_ARRAYSIZE(table13))
373
0
            {
374
0
                m_osProductionStatus = table13[nProdStatus];
375
0
                osIDS += CPLString("(") +
376
0
                         CPLString(table13[nProdStatus]).replaceAll(' ', '_') +
377
0
                         ")";
378
0
            }
379
0
            osIDS += " ";
380
0
            osIDS += "TYPE=";
381
0
            unsigned nType = pabyBody[21 - 1];
382
0
            osIDS += CPLSPrintf("%d", nType);
383
0
            static const char *const table14[] = {
384
0
                "Analysis",
385
0
                "Forecast",
386
0
                "Analysis and forecast",
387
0
                "Control forecast",
388
0
                "Perturbed forecast",
389
0
                "Control and perturbed forecast",
390
0
                "Processed satellite observations",
391
0
                "Processed radar observations",
392
0
                "Event Probability"};
393
0
            if (nType < CPL_ARRAYSIZE(table14))
394
0
            {
395
0
                m_osType = table14[nType];
396
0
                osIDS += CPLString("(") +
397
0
                         CPLString(table14[nType]).replaceAll(' ', '_') + ")";
398
0
            }
399
400
0
            GDALRasterBand::SetMetadataItem("GRIB_IDS", osIDS);
401
402
0
            CPLFree(pabyBody);
403
0
        }
404
405
0
        if (VSIFReadL(abyHead, 5, 1, poGDS->fp) != 1)
406
0
        {
407
0
            CPLDebug("GRIB", "Cannot read 5 leading bytes past section 1");
408
0
            return;
409
0
        }
410
0
    }
411
412
0
    if (subgNum > 0)
413
0
    {
414
        // If we are a subgrid, then iterate over all preceding subgrids
415
0
        for (int iSubMessage = 0; iSubMessage < subgNum;)
416
0
        {
417
0
            memcpy(&nSectSize, abyHead, 4);
418
0
            CPL_MSBPTR32(&nSectSize);
419
0
            if (nSectSize < 5)
420
0
            {
421
0
                CPLDebug("GRIB", "Invalid section size for iSubMessage = %d",
422
0
                         iSubMessage);
423
0
                return;
424
0
            }
425
0
            if (VSIFSeekL(poGDS->fp, nSectSize - 5, SEEK_CUR) != 0)
426
0
            {
427
0
                CPLDebug("GRIB",
428
0
                         "Cannot read past section for iSubMessage = %d",
429
0
                         iSubMessage);
430
0
                return;
431
0
            }
432
0
            if (abyHead[4] < 2 || abyHead[4] > 7)
433
0
            {
434
0
                CPLDebug("GRIB", "Invalid section number for iSubMessage = %d",
435
0
                         iSubMessage);
436
0
                return;
437
0
            }
438
0
            if (abyHead[4] == 7)
439
0
            {
440
0
                ++iSubMessage;
441
0
            }
442
0
            if (VSIFReadL(abyHead, 5, 1, poGDS->fp) != 1)
443
0
            {
444
0
                CPLDebug("GRIB",
445
0
                         "Cannot read 5 leading bytes for iSubMessage = %d",
446
0
                         iSubMessage);
447
0
                return;
448
0
            }
449
0
        }
450
0
    }
451
452
    // Skip to section 4
453
0
    while (abyHead[4] != 4)
454
0
    {
455
0
        memcpy(&nSectSize, abyHead, 4);
456
0
        CPL_MSBPTR32(&nSectSize);
457
458
0
        const int nCurSection = abyHead[4];
459
0
        if (nSectSize < 5)
460
0
        {
461
0
            CPLDebug("GRIB", "Invalid section size for section %d",
462
0
                     nCurSection);
463
0
            return;
464
0
        }
465
0
        if (VSIFSeekL(poGDS->fp, nSectSize - 5, SEEK_CUR) != 0 ||
466
0
            VSIFReadL(abyHead, 5, 1, poGDS->fp) != 1)
467
0
        {
468
0
            CPLDebug("GRIB", "Cannot read section %d", nCurSection);
469
0
            return;
470
0
        }
471
0
    }
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
0
    {
478
0
        memcpy(&nSectSize, abyHead, 4);
479
0
        CPL_MSBPTR32(&nSectSize);
480
0
        if (nSectSize >= 9 && nSectSize <= 100000 /* arbitrary upper limit */)
481
0
        {
482
0
            GByte *pabyBody = static_cast<GByte *>(CPLMalloc(nSectSize));
483
0
            memcpy(pabyBody, abyHead, 5);
484
0
            if (VSIFReadL(pabyBody + 5, 1, nSectSize - 5, poGDS->fp) !=
485
0
                nSectSize - 5)
486
0
            {
487
0
                CPLDebug("GRIB", "Cannot read section 4");
488
0
                CPLFree(pabyBody);
489
0
                return;
490
0
            }
491
492
0
            GUInt16 nCoordCount = 0;
493
0
            memcpy(&nCoordCount, pabyBody + 6 - 1, 2);
494
0
            CPL_MSBPTR16(&nCoordCount);
495
496
0
            GUInt16 nPDTN = 0;
497
0
            memcpy(&nPDTN, pabyBody + 8 - 1, 2);
498
0
            CPL_MSBPTR16(&nPDTN);
499
500
0
            GDALRasterBand::SetMetadataItem("GRIB_PDS_PDTN",
501
0
                                            CPLString().Printf("%d", nPDTN));
502
0
            m_nPDTN = nPDTN;
503
504
0
            CPLString osOctet;
505
0
            const int nTemplateFoundByteCount =
506
0
                nSectSize - 9U >= nCoordCount * 4U
507
0
                    ? static_cast<int>(nSectSize - 9 - nCoordCount * 4)
508
0
                    : 0;
509
0
            for (int i = 0; i < nTemplateFoundByteCount; i++)
510
0
            {
511
0
                char szByte[10] = {'\0'};
512
513
0
                if (i == 0)
514
0
                    snprintf(szByte, sizeof(szByte), "%d", pabyBody[i + 9]);
515
0
                else
516
0
                    snprintf(szByte, sizeof(szByte), " %d", pabyBody[i + 9]);
517
0
                osOctet += szByte;
518
0
            }
519
520
0
            GDALRasterBand::SetMetadataItem("GRIB_PDS_TEMPLATE_NUMBERS",
521
0
                                            osOctet);
522
523
0
            g2int iofst = 0;
524
0
            g2int pdsnum = 0;
525
0
            g2int *pdstempl = nullptr;
526
0
            g2int mappdslen = 0;
527
0
            g2float *coordlist = nullptr;
528
0
            g2int numcoord = 0;
529
0
            if (getpdsindex(nPDTN) < 0)
530
0
            {
531
0
                CPLError(CE_Warning, CPLE_NotSupported,
532
0
                         "Template 4.%d is not recognized currently", nPDTN);
533
0
            }
534
0
            else if (g2_unpack4(pabyBody, nSectSize, &iofst, &pdsnum, &pdstempl,
535
0
                                &mappdslen, &coordlist, &numcoord) == 0)
536
0
            {
537
0
                gtemplate *mappds = extpdstemplate(pdsnum, pdstempl);
538
0
                if (mappds)
539
0
                {
540
0
                    int nTemplateByteCount = 0;
541
0
                    for (int i = 0; i < mappds->maplen; i++)
542
0
                        nTemplateByteCount += abs(mappds->map[i]);
543
0
                    for (int i = 0; i < mappds->extlen; i++)
544
0
                        nTemplateByteCount += abs(mappds->ext[i]);
545
0
                    if (nTemplateByteCount == nTemplateFoundByteCount)
546
0
                    {
547
0
                        CPLString osValues;
548
0
                        for (g2int i = 0; i < mappds->maplen + mappds->extlen;
549
0
                             i++)
550
0
                        {
551
0
                            if (i > 0)
552
0
                                osValues += " ";
553
0
                            const int nEltSize =
554
0
                                (i < mappds->maplen)
555
0
                                    ? mappds->map[i]
556
0
                                    : mappds->ext[i - mappds->maplen];
557
0
                            if (nEltSize == 4)
558
0
                            {
559
0
                                m_anPDSTemplateAssembledValues.push_back(
560
0
                                    static_cast<GUInt32>(pdstempl[i]));
561
0
                                osValues += CPLSPrintf(
562
0
                                    "%u", static_cast<GUInt32>(pdstempl[i]));
563
0
                            }
564
0
                            else
565
0
                            {
566
0
                                m_anPDSTemplateAssembledValues.push_back(
567
0
                                    pdstempl[i]);
568
0
                                osValues += CPLSPrintf("%d", pdstempl[i]);
569
0
                            }
570
0
                        }
571
0
                        GDALRasterBand::SetMetadataItem(
572
0
                            "GRIB_PDS_TEMPLATE_ASSEMBLED_VALUES", osValues);
573
0
                    }
574
0
                    else
575
0
                    {
576
0
                        CPLDebug(
577
0
                            "GRIB",
578
0
                            "Cannot expose GRIB_PDS_TEMPLATE_ASSEMBLED_VALUES "
579
0
                            "as we would expect %d bytes from the "
580
0
                            "tables, but %d are available",
581
0
                            nTemplateByteCount, nTemplateFoundByteCount);
582
0
                    }
583
584
0
                    free(mappds->ext);
585
0
                    free(mappds);
586
0
                }
587
0
            }
588
0
            free(pdstempl);
589
0
            free(coordlist);
590
591
0
            CPLFree(pabyBody);
592
593
0
            FindNoDataGrib2(false);
594
0
        }
595
0
        else
596
0
        {
597
0
            CPLDebug("GRIB", "Invalid section size for section %d", 4);
598
0
        }
599
0
    }
600
0
}
601
602
/************************************************************************/
603
/*                        FindNoDataGrib2()                             */
604
/************************************************************************/
605
606
void GRIBRasterBand::FindNoDataGrib2(bool bSeekToStart)
607
0
{
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
0
    GRIBDataset *poGDS = static_cast<GRIBDataset *>(poDS);
613
0
    CPLAssert(m_nGribVersion == 2);
614
615
0
    if (m_bHasLookedForNoData)
616
0
        return;
617
0
    m_bHasLookedForNoData = true;
618
619
0
    if (bSeekToStart)
620
0
    {
621
        // Skip over section 0
622
0
        VSIFSeekL(poGDS->fp, start + 16, SEEK_SET);
623
0
    }
624
625
0
    GByte abyHead[5] = {0};
626
0
    VSIFReadL(abyHead, 5, 1, poGDS->fp);
627
628
    // Skip to section 5
629
0
    GUInt32 nSectSize = 0;
630
0
    while (abyHead[4] != 5)
631
0
    {
632
0
        memcpy(&nSectSize, abyHead, 4);
633
0
        CPL_MSBPTR32(&nSectSize);
634
635
0
        if (nSectSize < 5 ||
636
0
            VSIFSeekL(poGDS->fp, nSectSize - 5, SEEK_CUR) != 0 ||
637
0
            VSIFReadL(abyHead, 5, 1, poGDS->fp) != 1)
638
0
            break;
639
0
    }
640
641
    // See http://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_sect5.shtml
642
0
    if (abyHead[4] == 5)
643
0
    {
644
0
        memcpy(&nSectSize, abyHead, 4);
645
0
        CPL_MSBPTR32(&nSectSize);
646
0
        if (nSectSize >= 11 && nSectSize <= 100000 /* arbitrary upper limit */)
647
0
        {
648
0
            GByte *pabyBody = static_cast<GByte *>(CPLMalloc(nSectSize));
649
0
            memcpy(pabyBody, abyHead, 5);
650
0
            VSIFReadL(pabyBody + 5, 1, nSectSize - 5, poGDS->fp);
651
652
0
            GUInt16 nDRTN = 0;
653
0
            memcpy(&nDRTN, pabyBody + 10 - 1, 2);
654
0
            CPL_MSBPTR16(&nDRTN);
655
656
0
            GDALRasterBand::SetMetadataItem("DRS_DRTN", CPLSPrintf("%d", nDRTN),
657
0
                                            "GRIB");
658
0
            if ((nDRTN == GS5_SIMPLE || nDRTN == GS5_CMPLX ||
659
0
                 nDRTN == GS5_CMPLXSEC || nDRTN == GS5_JPEG2000 ||
660
0
                 nDRTN == GS5_PNG) &&
661
0
                nSectSize >= 20)
662
0
            {
663
0
                float fRef;
664
0
                memcpy(&fRef, pabyBody + 12 - 1, 4);
665
0
                CPL_MSBPTR32(&fRef);
666
0
                GDALRasterBand::SetMetadataItem(
667
0
                    "DRS_REF_VALUE", CPLSPrintf("%.10f", fRef), "GRIB");
668
669
0
                GUInt16 nBinaryScaleFactorUnsigned;
670
0
                memcpy(&nBinaryScaleFactorUnsigned, pabyBody + 16 - 1, 2);
671
0
                CPL_MSBPTR16(&nBinaryScaleFactorUnsigned);
672
0
                const int nBSF =
673
0
                    (nBinaryScaleFactorUnsigned & 0x8000)
674
0
                        ? -static_cast<int>(nBinaryScaleFactorUnsigned & 0x7FFF)
675
0
                        : static_cast<int>(nBinaryScaleFactorUnsigned);
676
0
                GDALRasterBand::SetMetadataItem("DRS_BINARY_SCALE_FACTOR",
677
0
                                                CPLSPrintf("%d", nBSF), "GRIB");
678
679
0
                GUInt16 nDecimalScaleFactorUnsigned;
680
0
                memcpy(&nDecimalScaleFactorUnsigned, pabyBody + 18 - 1, 2);
681
0
                CPL_MSBPTR16(&nDecimalScaleFactorUnsigned);
682
0
                const int nDSF =
683
0
                    (nDecimalScaleFactorUnsigned & 0x8000)
684
0
                        ? -static_cast<int>(nDecimalScaleFactorUnsigned &
685
0
                                            0x7FFF)
686
0
                        : static_cast<int>(nDecimalScaleFactorUnsigned);
687
0
                GDALRasterBand::SetMetadataItem("DRS_DECIMAL_SCALE_FACTOR",
688
0
                                                CPLSPrintf("%d", nDSF), "GRIB");
689
690
0
                const int nBits = pabyBody[20 - 1];
691
0
                GDALRasterBand::SetMetadataItem(
692
0
                    "DRS_NBITS", CPLSPrintf("%d", nBits), "GRIB");
693
0
            }
694
695
            // 2 = Grid Point Data - Complex Packing
696
            // 3 = Grid Point Data - Complex Packing and Spatial Differencing
697
0
            if ((nDRTN == GS5_CMPLX || nDRTN == GS5_CMPLXSEC) &&
698
0
                nSectSize >= 31)
699
0
            {
700
0
                const int nMiss = pabyBody[23 - 1];
701
0
                if (nMiss == 1 || nMiss == 2)
702
0
                {
703
0
                    const int original_field_type = pabyBody[21 - 1];
704
0
                    if (original_field_type == 0)  // Floating Point
705
0
                    {
706
0
                        float fTemp;
707
0
                        memcpy(&fTemp, &pabyBody[24 - 1], 4);
708
0
                        CPL_MSBPTR32(&fTemp);
709
0
                        m_dfNoData = fTemp;
710
0
                        m_bHasNoData = true;
711
0
                        if (nMiss == 2)
712
0
                        {
713
0
                            memcpy(&fTemp, &pabyBody[28 - 1], 4);
714
0
                            CPL_MSBPTR32(&fTemp);
715
0
                            CPLDebug("GRIB",
716
0
                                     "Secondary missing value also set for "
717
0
                                     "band %d : %f",
718
0
                                     nBand, fTemp);
719
0
                        }
720
0
                    }
721
0
                    else if (original_field_type == 1)  // Integer
722
0
                    {
723
0
                        int iTemp;
724
0
                        memcpy(&iTemp, &pabyBody[24 - 1], 4);
725
0
                        CPL_MSBPTR32(&iTemp);
726
0
                        m_dfNoData = iTemp;
727
0
                        m_bHasNoData = true;
728
0
                        if (nMiss == 2)
729
0
                        {
730
0
                            memcpy(&iTemp, &pabyBody[28 - 1], 4);
731
0
                            CPL_MSBPTR32(&iTemp);
732
0
                            CPLDebug("GRIB",
733
0
                                     "Secondary missing value also set for "
734
0
                                     "band %d : %d",
735
0
                                     nBand, iTemp);
736
0
                        }
737
0
                    }
738
0
                    else
739
0
                    {
740
                        // FIXME What to do? Blindly convert to float?
741
0
                        CPLDebug("GRIB",
742
0
                                 "Complex Packing - Type of Original Field "
743
0
                                 "Values for band %d:  %u",
744
0
                                 nBand, original_field_type);
745
0
                    }
746
0
                }
747
0
            }
748
749
0
            if (nDRTN == GS5_CMPLXSEC && nSectSize >= 48)
750
0
            {
751
0
                const int nOrder = pabyBody[48 - 1];
752
0
                GDALRasterBand::SetMetadataItem(
753
0
                    "DRS_SPATIAL_DIFFERENCING_ORDER", CPLSPrintf("%d", nOrder),
754
0
                    "GRIB");
755
0
            }
756
757
0
            CPLFree(pabyBody);
758
0
        }
759
0
        else if (nSectSize > 5)
760
0
        {
761
0
            VSIFSeekL(poGDS->fp, nSectSize - 5, SEEK_CUR);
762
0
        }
763
0
    }
764
765
0
    if (!m_bHasNoData)
766
0
    {
767
        // Check bitmap section
768
0
        GByte abySection6[6] = {0};
769
0
        VSIFReadL(abySection6, 6, 1, poGDS->fp);
770
        // Is there a bitmap ?
771
0
        if (abySection6[4] == 6 && abySection6[5] == 0)
772
0
        {
773
0
            m_dfNoData = 9999.0;  // Same value as in metaparse.cpp:ParseGrid()
774
0
            m_bHasNoData = true;
775
0
        }
776
0
    }
777
0
}
778
779
/************************************************************************/
780
/*                         GetDescription()                             */
781
/************************************************************************/
782
783
const char *GRIBRasterBand::GetDescription() const
784
0
{
785
0
    if (longFstLevel == nullptr)
786
0
        return GDALPamRasterBand::GetDescription();
787
788
0
    return longFstLevel;
789
0
}
790
791
/************************************************************************/
792
/*                             LoadData()                               */
793
/************************************************************************/
794
795
CPLErr GRIBRasterBand::LoadData()
796
797
0
{
798
0
    if (!m_Grib_Data)
799
0
    {
800
0
        GRIBDataset *poGDS = static_cast<GRIBDataset *>(poDS);
801
802
0
        if (poGDS->bCacheOnlyOneBand)
803
0
        {
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
0
            poGDS->poLastUsedBand->UncacheData();
808
0
            poGDS->nCachedBytes = 0;
809
0
        }
810
0
        else
811
0
        {
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
0
            if (poGDS->nCachedBytes > poGDS->nCachedBytesThreshold)
816
0
            {
817
0
                GUIntBig nMinCacheSize =
818
0
                    1 + static_cast<GUIntBig>(poGDS->nRasterXSize) *
819
0
                            poGDS->nRasterYSize * poGDS->nBands *
820
0
                            GDALGetDataTypeSizeBytes(eDataType) / 1024 / 1024;
821
0
                CPLDebug("GRIB",
822
0
                         "Maximum band cache size reached for this dataset. "
823
0
                         "Caching only one band at a time from now, which can "
824
0
                         "negatively affect performance. Consider "
825
0
                         "increasing GRIB_CACHEMAX to a higher value (in MB), "
826
0
                         "at least " CPL_FRMT_GUIB " in that instance",
827
0
                         nMinCacheSize);
828
0
                for (int i = 0; i < poGDS->nBands; i++)
829
0
                {
830
0
                    reinterpret_cast<GRIBRasterBand *>(
831
0
                        poGDS->GetRasterBand(i + 1))
832
0
                        ->UncacheData();
833
0
                }
834
0
                poGDS->nCachedBytes = 0;
835
0
                poGDS->bCacheOnlyOneBand = TRUE;
836
0
            }
837
0
        }
838
839
        // we don't seem to have any way to detect errors in this!
840
0
        if (m_Grib_MetaData != nullptr)
841
0
        {
842
0
            MetaFree(m_Grib_MetaData);
843
0
            delete m_Grib_MetaData;
844
0
            m_Grib_MetaData = nullptr;
845
0
        }
846
0
        ReadGribData(poGDS->fp, start, subgNum, &m_Grib_Data, &m_Grib_MetaData);
847
0
        if (!m_Grib_Data)
848
0
        {
849
0
            CPLError(CE_Failure, CPLE_AppDefined, "Out of memory.");
850
0
            if (m_Grib_MetaData != nullptr)
851
0
            {
852
0
                MetaFree(m_Grib_MetaData);
853
0
                delete m_Grib_MetaData;
854
0
                m_Grib_MetaData = nullptr;
855
0
            }
856
0
            return CE_Failure;
857
0
        }
858
859
        // Check the band matches the dataset as a whole, size wise. (#3246)
860
0
        nGribDataXSize = m_Grib_MetaData->gds.Nx;
861
0
        nGribDataYSize = m_Grib_MetaData->gds.Ny;
862
0
        if (nGribDataXSize <= 0 || nGribDataYSize <= 0)
863
0
        {
864
0
            CPLError(CE_Failure, CPLE_AppDefined,
865
0
                     "Band %d of GRIB dataset is %dx%d.", nBand, nGribDataXSize,
866
0
                     nGribDataYSize);
867
0
            MetaFree(m_Grib_MetaData);
868
0
            delete m_Grib_MetaData;
869
0
            m_Grib_MetaData = nullptr;
870
0
            return CE_Failure;
871
0
        }
872
873
0
        poGDS->nCachedBytes += static_cast<GIntBig>(nGribDataXSize) *
874
0
                               nGribDataYSize * sizeof(double);
875
0
        poGDS->poLastUsedBand = this;
876
877
0
        if (nGribDataXSize != nRasterXSize || nGribDataYSize != nRasterYSize)
878
0
        {
879
0
            CPLError(CE_Warning, CPLE_AppDefined,
880
0
                     "Band %d of GRIB dataset is %dx%d, while the first band "
881
0
                     "and dataset is %dx%d.  Georeferencing of band %d may "
882
0
                     "be incorrect, and data access may be incomplete.",
883
0
                     nBand, nGribDataXSize, nGribDataYSize, nRasterXSize,
884
0
                     nRasterYSize, nBand);
885
0
        }
886
0
    }
887
888
0
    return CE_None;
889
0
}
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)
920
0
{
921
0
    FindMetaData();
922
0
    if ((pszDomain == nullptr || pszDomain[0] == 0) && m_nGribVersion == 2 &&
923
0
        CPLTestBool(CPLGetConfigOption("GRIB_PDS_ALL_BANDS", "ON")))
924
0
    {
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);
929
        if (poGDS->m_bSideCarIdxUsed && !poGDS->m_bWarnedGdalinfoNomd &&
930
            poGDS->GetRasterCount() > 10 &&
931
            !VSIIsLocal(poGDS->GetDescription()) && IsGdalinfoInteractive())
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
0
        FindPDSTemplateGRIB2();
953
0
    }
954
0
    return GDALPamRasterBand::GetMetadata(pszDomain);
955
0
}
956
957
/************************************************************************/
958
/*                             GetMetaDataItem()                        */
959
/************************************************************************/
960
const char *GRIBRasterBand::GetMetadataItem(const char *pszName,
961
                                            const char *pszDomain)
962
0
{
963
0
    if (!((!pszDomain || pszDomain[0] == 0) &&
964
0
          (EQUAL(pszName, "STATISTICS_MINIMUM") ||
965
0
           EQUAL(pszName, "STATISTICS_MAXIMUM"))))
966
0
    {
967
0
        FindMetaData();
968
0
        if (m_nGribVersion == 2 &&
969
0
            CPLTestBool(CPLGetConfigOption("GRIB_PDS_ALL_BANDS", "ON")))
970
0
        {
971
0
            FindPDSTemplateGRIB2();
972
0
        }
973
0
    }
974
0
    return GDALPamRasterBand::GetMetadataItem(pszName, pszDomain);
975
0
}
976
977
/************************************************************************/
978
/*                             IReadBlock()                             */
979
/************************************************************************/
980
981
CPLErr GRIBRasterBand::IReadBlock(int /* nBlockXOff */, int nBlockYOff,
982
                                  void *pImage)
983
984
0
{
985
0
    CPLErr eErr = LoadData();
986
0
    if (eErr != CE_None)
987
0
        return eErr;
988
989
0
    GRIBDataset *poGDS = static_cast<GRIBDataset *>(poDS);
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
0
    if (nGribDataXSize == nRasterXSize && nGribDataYSize == nRasterYSize &&
997
0
        poGDS->nSplitAndSwapColumn == 0)
998
0
    {
999
        // Simple 1:1 case.
1000
0
        memcpy(pImage,
1001
0
               m_Grib_Data + static_cast<size_t>(nRasterXSize) *
1002
0
                                 (nRasterYSize - nBlockYOff - 1),
1003
0
               nRasterXSize * sizeof(double));
1004
1005
0
        return CE_None;
1006
0
    }
1007
1008
0
    memset(pImage, 0, sizeof(double) * nRasterXSize);
1009
1010
0
    if (nBlockYOff >= nGribDataYSize)  // Off image?
1011
0
        return CE_None;
1012
1013
0
    int nSplitAndSwapColumn = poGDS->nSplitAndSwapColumn;
1014
0
    if (nRasterXSize != nGribDataXSize)
1015
0
        nSplitAndSwapColumn = 0;
1016
1017
0
    const int nCopyWords = std::min(nRasterXSize, nGribDataXSize);
1018
1019
0
    memcpy(pImage,
1020
0
           m_Grib_Data +
1021
0
               static_cast<size_t>(nGribDataXSize) *
1022
0
                   (nGribDataYSize - nBlockYOff - 1) +
1023
0
               nSplitAndSwapColumn,
1024
0
           (nCopyWords - nSplitAndSwapColumn) * sizeof(double));
1025
1026
0
    if (nSplitAndSwapColumn > 0)
1027
0
        memcpy(reinterpret_cast<void *>(reinterpret_cast<double *>(pImage) +
1028
0
                                        nCopyWords - nSplitAndSwapColumn),
1029
0
               m_Grib_Data + static_cast<size_t>(nGribDataXSize) *
1030
0
                                 (nGribDataYSize - nBlockYOff - 1),
1031
0
               nSplitAndSwapColumn * sizeof(double));
1032
1033
0
    return CE_None;
1034
0
}
1035
1036
/************************************************************************/
1037
/*                           GetNoDataValue()                           */
1038
/************************************************************************/
1039
1040
double GRIBRasterBand::GetNoDataValue(int *pbSuccess)
1041
0
{
1042
0
    if (m_bHasLookedForNoData)
1043
0
    {
1044
0
        if (pbSuccess)
1045
0
            *pbSuccess = m_bHasNoData;
1046
0
        return m_dfNoData;
1047
0
    }
1048
1049
0
    m_bHasLookedForNoData = true;
1050
0
    if (m_Grib_MetaData == nullptr)
1051
0
    {
1052
0
        GRIBDataset *poGDS = static_cast<GRIBDataset *>(poDS);
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())
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
0
        ReadGribData(poGDS->fp, start, subgNum, nullptr, &m_Grib_MetaData);
1082
0
        if (m_Grib_MetaData == nullptr)
1083
0
        {
1084
0
            m_bHasNoData = false;
1085
0
            m_dfNoData = 0;
1086
0
            if (pbSuccess)
1087
0
                *pbSuccess = m_bHasNoData;
1088
0
            return m_dfNoData;
1089
0
        }
1090
0
    }
1091
1092
0
    if (m_Grib_MetaData->gridAttrib.f_miss == 0)
1093
0
    {
1094
0
        m_bHasNoData = false;
1095
0
        m_dfNoData = 0;
1096
0
        if (pbSuccess)
1097
0
            *pbSuccess = m_bHasNoData;
1098
0
        return m_dfNoData;
1099
0
    }
1100
1101
0
    if (m_Grib_MetaData->gridAttrib.f_miss == 2)
1102
0
    {
1103
        // What TODO?
1104
0
        CPLDebug("GRIB", "Secondary missing value also set for band %d : %f",
1105
0
                 nBand, m_Grib_MetaData->gridAttrib.missSec);
1106
0
    }
1107
1108
0
    m_bHasNoData = true;
1109
0
    m_dfNoData = m_Grib_MetaData->gridAttrib.missPri;
1110
0
    if (pbSuccess)
1111
0
        *pbSuccess = m_bHasNoData;
1112
0
    return m_dfNoData;
1113
0
}
1114
1115
/************************************************************************/
1116
/*                            ReadGribData()                            */
1117
/************************************************************************/
1118
1119
void GRIBRasterBand::ReadGribData(VSILFILE *fp, vsi_l_offset start, int subgNum,
1120
                                  double **data, grib_MetaData **metaData)
1121
819
{
1122
    // Initialization, for calling the ReadGrib2Record function.
1123
819
    sInt4 f_endMsg = 1;  // 1 if we read the last grid in a GRIB message, or we
1124
                         // haven't read any messages.
1125
    // int subgNum = 0; // The subgrid in the message that we are interested in.
1126
819
    sChar f_unit = 2;       // None = 0, English = 1, Metric = 2
1127
819
    double majEarth = 0.0;  // -radEarth if < 6000 ignore, otherwise use this
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
819
    double minEarth = 0.0;  // -minEarth if < 6000 ignore, otherwise use this
1132
                            // to override the minEarth in the GRIB1 or GRIB2
1133
                            // message.
1134
819
    sChar f_SimpleVer = 4;  // Which version of the simple NDFD Weather table
1135
                            // to use. (1 is 6/2003) (2 is 1/2004) (3 is
1136
                            // 2/2004) (4 is 11/2004) (default 4)
1137
819
    LatLon lwlf;            // Lower left corner (cookie slicing) -lwlf
1138
819
    LatLon uprt;            // Upper right corner (cookie slicing) -uprt
1139
819
    IS_dataType is;  // Un-parsed meta data for this GRIB2 message. As well
1140
                     // as some memory used by the unpacker.
1141
1142
819
    lwlf.lat = -100;  // lat == -100 instructs the GRIB decoder that we don't
1143
                      // want a subgrid
1144
1145
819
    IS_Init(&is);
1146
1147
819
    const char *pszGribNormalizeUnits =
1148
819
        CPLGetConfigOption("GRIB_NORMALIZE_UNITS", "YES");
1149
819
    if (!CPLTestBool(pszGribNormalizeUnits))
1150
0
        f_unit = 0;  // Do not normalize units to metric.
1151
1152
819
    start = FindTrueStart(fp, start);
1153
    // Read GRIB message from file position "start".
1154
819
    VSIFSeekL(fp, start, SEEK_SET);
1155
819
    uInt4 grib_DataLen = 0;  // Size of Grib_Data.
1156
819
    *metaData = new grib_MetaData();
1157
819
    MetaInit(*metaData);
1158
819
    const int simpWWA = 0;  // seem to be unused in degrib
1159
819
    ReadGrib2Record(fp, f_unit, data, &grib_DataLen, *metaData, &is, subgNum,
1160
819
                    majEarth, minEarth, f_SimpleVer, simpWWA, &f_endMsg, &lwlf,
1161
819
                    &uprt);
1162
1163
    // No intention to show errors, just swallow it and free the memory.
1164
819
    char *errMsg = errSprintf(nullptr);
1165
819
    if (errMsg != nullptr)
1166
819
        CPLDebug("GRIB", "%s", errMsg);
1167
819
    free(errMsg);
1168
819
    IS_Free(&is);
1169
819
}
1170
1171
/************************************************************************/
1172
/*                            UncacheData()                             */
1173
/************************************************************************/
1174
1175
void GRIBRasterBand::UncacheData()
1176
13.9k
{
1177
13.9k
    if (m_Grib_Data)
1178
0
        free(m_Grib_Data);
1179
13.9k
    m_Grib_Data = nullptr;
1180
13.9k
    if (m_Grib_MetaData)
1181
217
    {
1182
217
        MetaFree(m_Grib_MetaData);
1183
217
        delete m_Grib_MetaData;
1184
217
    }
1185
13.9k
    m_Grib_MetaData = nullptr;
1186
13.9k
}
1187
1188
/************************************************************************/
1189
/*                           ~GRIBRasterBand()                          */
1190
/************************************************************************/
1191
1192
GRIBRasterBand::~GRIBRasterBand()
1193
13.9k
{
1194
13.9k
    if (longFstLevel != nullptr)
1195
13.9k
        CPLFree(longFstLevel);
1196
13.9k
    UncacheData();
1197
13.9k
}
1198
1199
1.71k
gdal::grib::InventoryWrapper::~InventoryWrapper() = default;
1200
1201
/************************************************************************/
1202
/*                           InventoryWrapperGrib                       */
1203
/************************************************************************/
1204
class InventoryWrapperGrib : public gdal::grib::InventoryWrapper
1205
{
1206
  public:
1207
1.71k
    explicit InventoryWrapperGrib(VSILFILE *fp) : gdal::grib::InventoryWrapper()
1208
1.71k
    {
1209
1.71k
        result_ = GRIB2Inventory(fp, &inv_, &inv_len_, 0 /* all messages */,
1210
1.71k
                                 &num_messages_);
1211
1.71k
    }
1212
1213
    ~InventoryWrapperGrib() override;
1214
};
1215
1216
InventoryWrapperGrib::~InventoryWrapperGrib()
1217
1.71k
{
1218
1.71k
    if (inv_ == nullptr)
1219
459
        return;
1220
51.9k
    for (uInt4 i = 0; i < inv_len_; i++)
1221
50.7k
    {
1222
50.7k
        GRIB2InventoryFree(inv_ + i);
1223
50.7k
    }
1224
1.25k
    free(inv_);
1225
1.25k
}
1226
1227
/************************************************************************/
1228
/*                           InventoryWrapperSidecar                    */
1229
/************************************************************************/
1230
1231
class InventoryWrapperSidecar : public gdal::grib::InventoryWrapper
1232
{
1233
  public:
1234
    explicit InventoryWrapperSidecar(VSILFILE *fp, uint64_t nStartOffset,
1235
                                     int64_t nSize)
1236
0
        : gdal::grib::InventoryWrapper()
1237
0
    {
1238
0
        result_ = -1;
1239
0
        VSIFSeekL(fp, 0, SEEK_END);
1240
0
        size_t length = static_cast<size_t>(VSIFTellL(fp));
1241
0
        if (length > 4 * 1024 * 1024)
1242
0
            return;
1243
0
        std::string osSidecar;
1244
0
        osSidecar.resize(length);
1245
0
        VSIFSeekL(fp, 0, SEEK_SET);
1246
0
        if (VSIFReadL(&osSidecar[0], length, 1, fp) != 1)
1247
0
            return;
1248
1249
0
        const CPLStringList aosMsgs(
1250
0
            CSLTokenizeString2(osSidecar.c_str(), "\n",
1251
0
                               CSLT_PRESERVEQUOTES | CSLT_STRIPLEADSPACES));
1252
0
        inv_ = static_cast<inventoryType *>(
1253
0
            CPLCalloc(aosMsgs.size(), sizeof(inventoryType)));
1254
1255
0
        for (const char *pszMsg : aosMsgs)
1256
0
        {
1257
            // We are parsing
1258
            // "msgNum[.subgNum]:start:dontcare:name1:name2:name3" For NOMADS:
1259
            // "msgNum[.subgNum]:start:reftime:var:level:time"
1260
0
            const CPLStringList aosTokens(CSLTokenizeString2(
1261
0
                pszMsg, ":", CSLT_PRESERVEQUOTES | CSLT_ALLOWEMPTYTOKENS));
1262
0
            CPLStringList aosNum;
1263
1264
0
            if (aosTokens.size() < 6)
1265
0
                goto err_sidecar;
1266
1267
0
            aosNum = CPLStringList(CSLTokenizeString2(aosTokens[0], ".", 0));
1268
0
            if (aosNum.size() < 1)
1269
0
                goto err_sidecar;
1270
1271
            // FindMetaData will retrieve the correct version number
1272
0
            char *endptr;
1273
0
            strtol(aosNum[0], &endptr, 10);
1274
0
            if (*endptr != 0)
1275
0
                goto err_sidecar;
1276
1277
0
            if (aosNum.size() < 2)
1278
0
                inv_[inv_len_].subgNum = 0;
1279
0
            else
1280
0
            {
1281
0
                auto subgNum = strtol(aosNum[1], &endptr, 10);
1282
0
                if (*endptr != 0)
1283
0
                    goto err_sidecar;
1284
0
                if (subgNum <= 0 || subgNum > 65536)
1285
0
                    goto err_sidecar;
1286
                // .idx file use a 1-based indexing, whereas DEGRIB uses a
1287
                // 0-based one
1288
0
                subgNum--;
1289
0
                inv_[inv_len_].subgNum = static_cast<unsigned short>(subgNum);
1290
0
            }
1291
1292
0
            inv_[inv_len_].start = strtoll(aosTokens[1], &endptr, 10);
1293
0
            if (*endptr != 0)
1294
0
                goto err_sidecar;
1295
1296
0
            if (inv_[inv_len_].start < nStartOffset)
1297
0
                continue;
1298
0
            if (nSize > 0 && inv_[inv_len_].start >= nStartOffset + nSize)
1299
0
                break;
1300
1301
0
            inv_[inv_len_].start -= nStartOffset;
1302
1303
0
            inv_[inv_len_].unitName = nullptr;
1304
0
            inv_[inv_len_].comment = nullptr;
1305
0
            inv_[inv_len_].element = nullptr;
1306
0
            inv_[inv_len_].shortFstLevel = nullptr;
1307
            // This is going into the description field ->
1308
            // the only one available before loading the metadata
1309
0
            inv_[inv_len_].longFstLevel = VSIStrdup(CPLSPrintf(
1310
0
                "%s:%s:%s", aosTokens[3], aosTokens[4], aosTokens[5]));
1311
0
            ++inv_len_;
1312
1313
0
            continue;
1314
1315
0
        err_sidecar:
1316
0
            CPLDebug("GRIB",
1317
0
                     "Failed parsing sidecar entry '%s', "
1318
0
                     "falling back to constructing an inventory",
1319
0
                     pszMsg);
1320
0
            return;
1321
0
        }
1322
1323
0
        result_ = inv_len_;
1324
0
    }
1325
1326
    ~InventoryWrapperSidecar() override;
1327
};
1328
1329
InventoryWrapperSidecar::~InventoryWrapperSidecar()
1330
0
{
1331
0
    if (inv_ == nullptr)
1332
0
        return;
1333
1334
0
    for (unsigned i = 0; i < inv_len_; i++)
1335
0
        VSIFree(inv_[i].longFstLevel);
1336
1337
0
    VSIFree(inv_);
1338
0
}
1339
1340
/************************************************************************/
1341
/* ==================================================================== */
1342
/*                              GRIBDataset                             */
1343
/* ==================================================================== */
1344
/************************************************************************/
1345
1346
GRIBDataset::GRIBDataset()
1347
1.71k
    : fp(nullptr), nCachedBytes(0),
1348
      // Switch caching strategy once 100 MB threshold is reached.
1349
      // Why 100 MB? --> Why not.
1350
1.71k
      nCachedBytesThreshold(static_cast<GIntBig>(atoi(
1351
1.71k
                                CPLGetConfigOption("GRIB_CACHEMAX", "100"))) *
1352
1.71k
                            1024 * 1024),
1353
1.71k
      bCacheOnlyOneBand(FALSE), nSplitAndSwapColumn(0), poLastUsedBand(nullptr)
1354
1.71k
{
1355
1.71k
    adfGeoTransform[0] = 0.0;
1356
1.71k
    adfGeoTransform[1] = 1.0;
1357
1.71k
    adfGeoTransform[2] = 0.0;
1358
1.71k
    adfGeoTransform[3] = 0.0;
1359
1.71k
    adfGeoTransform[4] = 0.0;
1360
1.71k
    adfGeoTransform[5] = 1.0;
1361
1.71k
}
1362
1363
/************************************************************************/
1364
/*                            ~GRIBDataset()                             */
1365
/************************************************************************/
1366
1367
GRIBDataset::~GRIBDataset()
1368
1369
1.71k
{
1370
1.71k
    FlushCache(true);
1371
1.71k
    if (fp != nullptr)
1372
1.71k
        VSIFCloseL(fp);
1373
1.71k
}
1374
1375
/************************************************************************/
1376
/*                          GetGeoTransform()                           */
1377
/************************************************************************/
1378
1379
CPLErr GRIBDataset::GetGeoTransform(double *padfTransform)
1380
1381
0
{
1382
0
    memcpy(padfTransform, adfGeoTransform, sizeof(double) * 6);
1383
0
    return CE_None;
1384
0
}
1385
1386
/************************************************************************/
1387
/*                                Inventory()                           */
1388
/************************************************************************/
1389
1390
std::unique_ptr<gdal::grib::InventoryWrapper>
1391
GRIBDataset::Inventory(GDALOpenInfo *poOpenInfo)
1392
1.71k
{
1393
1.71k
    std::unique_ptr<gdal::grib::InventoryWrapper> pInventories;
1394
1395
1.71k
    VSIFSeekL(fp, 0, SEEK_SET);
1396
1.71k
    std::string osSideCarFilename(poOpenInfo->pszFilename);
1397
1.71k
    uint64_t nStartOffset = 0;
1398
1.71k
    int64_t nSize = -1;
1399
1.71k
    if (STARTS_WITH(poOpenInfo->pszFilename, "/vsisubfile/"))
1400
0
    {
1401
0
        const char *pszPtr = poOpenInfo->pszFilename + strlen("/vsisubfile/");
1402
0
        const char *pszComma = strchr(pszPtr, ',');
1403
0
        if (pszComma)
1404
0
        {
1405
0
            const CPLStringList aosTokens(CSLTokenizeString2(
1406
0
                std::string(pszPtr, pszComma - pszPtr).c_str(), "_", 0));
1407
0
            if (aosTokens.size() == 2)
1408
0
            {
1409
0
                nStartOffset = std::strtoull(aosTokens[0], nullptr, 10);
1410
0
                nSize = std::strtoll(aosTokens[1], nullptr, 10);
1411
0
                osSideCarFilename = pszComma + 1;
1412
0
            }
1413
0
        }
1414
0
    }
1415
1.71k
    osSideCarFilename += ".idx";
1416
1.71k
    VSILFILE *fpSideCar = nullptr;
1417
1.71k
    if (CPLTestBool(CSLFetchNameValueDef(poOpenInfo->papszOpenOptions,
1418
1.71k
                                         "USE_IDX", "YES")) &&
1419
1.71k
        ((fpSideCar = VSIFOpenL(osSideCarFilename.c_str(), "rb")) != nullptr))
1420
0
    {
1421
0
        CPLDebug("GRIB", "Reading inventories from sidecar file %s",
1422
0
                 osSideCarFilename.c_str());
1423
        // Contains an GRIB2 message inventory of the file.
1424
0
        pInventories = std::make_unique<InventoryWrapperSidecar>(
1425
0
            fpSideCar, nStartOffset, nSize);
1426
0
        if (pInventories->result() <= 0 || pInventories->length() == 0)
1427
0
            pInventories = nullptr;
1428
0
        VSIFCloseL(fpSideCar);
1429
#ifdef BUILD_APPS
1430
        m_bSideCarIdxUsed = true;
1431
#endif
1432
0
    }
1433
1.71k
    else
1434
1.71k
        CPLDebug("GRIB", "Failed opening sidecar %s",
1435
1.71k
                 osSideCarFilename.c_str());
1436
1437
1.71k
    if (pInventories == nullptr)
1438
1.71k
    {
1439
1.71k
        CPLDebug("GRIB", "Reading inventories from GRIB file %s",
1440
1.71k
                 poOpenInfo->pszFilename);
1441
        // Contains an GRIB2 message inventory of the file.
1442
1.71k
        pInventories = std::make_unique<InventoryWrapperGrib>(fp);
1443
1.71k
    }
1444
1445
1.71k
    return pInventories;
1446
1.71k
}
1447
1448
/************************************************************************/
1449
/*                                Open()                                */
1450
/************************************************************************/
1451
1452
GDALDataset *GRIBDataset::Open(GDALOpenInfo *poOpenInfo)
1453
1454
1.83k
{
1455
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1456
    // During fuzzing, do not use Identify to reject crazy content.
1457
    if (!GRIBDriverIdentify(poOpenInfo))
1458
        return nullptr;
1459
#endif
1460
1.83k
    if (poOpenInfo->fpL == nullptr)
1461
0
        return nullptr;
1462
1463
    // A fast "probe" on the header that is partially read in memory.
1464
1.83k
    char *buff = nullptr;
1465
1.83k
    uInt4 buffLen = 0;
1466
1.83k
    sInt4 sect0[SECT0LEN_WORD] = {0};
1467
1.83k
    uInt4 gribLen = 0;
1468
1.83k
    int version = 0;
1469
1470
    // grib is not thread safe, make sure not to cause problems
1471
    // for other thread safe formats
1472
1.83k
    CPLMutexHolderD(&hGRIBMutex);
1473
1474
1.83k
    VSILFILE *memfp = VSIFileFromMemBuffer(nullptr, poOpenInfo->pabyHeader,
1475
1.83k
                                           poOpenInfo->nHeaderBytes, FALSE);
1476
1.83k
    if (memfp == nullptr ||
1477
1.83k
        ReadSECT0(memfp, &buff, &buffLen, -1, sect0, &gribLen, &version) < 0)
1478
127
    {
1479
127
        if (memfp != nullptr)
1480
127
        {
1481
127
            VSIFCloseL(memfp);
1482
127
        }
1483
127
        free(buff);
1484
127
        char *errMsg = errSprintf(nullptr);
1485
127
        if (errMsg != nullptr && strstr(errMsg, "Ran out of file") == nullptr)
1486
4
            CPLDebug("GRIB", "%s", errMsg);
1487
127
        free(errMsg);
1488
127
        return nullptr;
1489
127
    }
1490
1.71k
    VSIFCloseL(memfp);
1491
1.71k
    free(buff);
1492
1493
    // Confirm the requested access is supported.
1494
1.71k
    if (poOpenInfo->eAccess == GA_Update)
1495
0
    {
1496
0
        ReportUpdateNotSupportedByDriver("GRIB");
1497
0
        return nullptr;
1498
0
    }
1499
1500
1.71k
    if (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER)
1501
0
    {
1502
0
        return OpenMultiDim(poOpenInfo);
1503
0
    }
1504
1505
    // Create a corresponding GDALDataset.
1506
1.71k
    GRIBDataset *poDS = new GRIBDataset();
1507
1508
1.71k
    poDS->fp = poOpenInfo->fpL;
1509
1.71k
    poOpenInfo->fpL = nullptr;
1510
1511
    // Make an inventory of the GRIB file.
1512
    // The inventory does not contain all the information needed for
1513
    // creating the RasterBands (especially the x and y size), therefore
1514
    // the first GRIB band is also read for some additional metadata.
1515
    // The band-data that is read is stored into the first RasterBand,
1516
    // simply so that the same portion of the file is not read twice.
1517
1518
1.71k
    auto pInventories = poDS->Inventory(poOpenInfo);
1519
1.71k
    if (pInventories->result() <= 0)
1520
893
    {
1521
893
        char *errMsg = errSprintf(nullptr);
1522
893
        if (errMsg != nullptr)
1523
893
            CPLDebug("GRIB", "%s", errMsg);
1524
893
        free(errMsg);
1525
1526
893
        CPLError(CE_Failure, CPLE_OpenFailed,
1527
893
                 "%s is a grib file, "
1528
893
                 "but no raster dataset was successfully identified.",
1529
893
                 poOpenInfo->pszFilename);
1530
        // Release hGRIBMutex otherwise we'll deadlock with GDALDataset own
1531
        // hGRIBMutex.
1532
893
        CPLReleaseMutex(hGRIBMutex);
1533
893
        delete poDS;
1534
893
        CPLAcquireMutex(hGRIBMutex, 1000.0);
1535
893
        return nullptr;
1536
893
    }
1537
1538
    // Create band objects.
1539
819
    const uInt4 nCount = std::min(pInventories->length(), 65536U);
1540
14.7k
    for (uInt4 i = 0; i < nCount; ++i)
1541
14.5k
    {
1542
14.5k
        inventoryType *psInv = pInventories->get(i);
1543
14.5k
        GRIBRasterBand *gribBand = nullptr;
1544
14.5k
        const uInt4 bandNr = i + 1;
1545
14.5k
        assert(bandNr <= 65536);
1546
1547
14.5k
        if (bandNr == 1)
1548
819
        {
1549
            // Important: set DataSet extents before creating first RasterBand
1550
            // in it.
1551
819
            grib_MetaData *metaData = nullptr;
1552
819
            GRIBRasterBand::ReadGribData(poDS->fp, 0, psInv->subgNum, nullptr,
1553
819
                                         &metaData);
1554
819
            if (metaData == nullptr || metaData->gds.Nx < 1 ||
1555
819
                metaData->gds.Ny < 1)
1556
602
            {
1557
602
                CPLError(CE_Failure, CPLE_OpenFailed,
1558
602
                         "%s is a grib file, "
1559
602
                         "but no raster dataset was successfully identified.",
1560
602
                         poOpenInfo->pszFilename);
1561
                // Release hGRIBMutex otherwise we'll deadlock with GDALDataset
1562
                // own hGRIBMutex.
1563
602
                CPLReleaseMutex(hGRIBMutex);
1564
602
                delete poDS;
1565
602
                CPLAcquireMutex(hGRIBMutex, 1000.0);
1566
602
                if (metaData != nullptr)
1567
602
                {
1568
602
                    MetaFree(metaData);
1569
602
                    delete metaData;
1570
602
                }
1571
602
                return nullptr;
1572
602
            }
1573
217
            psInv->GribVersion = metaData->GribVersion;
1574
1575
            // Set the DataSet's x,y size, georeference and projection from
1576
            // the first GRIB band.
1577
217
            poDS->SetGribMetaData(metaData);
1578
217
            gribBand = new GRIBRasterBand(poDS, bandNr, psInv);
1579
1580
217
            if (psInv->GribVersion == 2)
1581
0
                gribBand->FindPDSTemplateGRIB2();
1582
1583
217
            gribBand->m_Grib_MetaData = metaData;
1584
217
        }
1585
13.7k
        else
1586
13.7k
        {
1587
13.7k
            gribBand = new GRIBRasterBand(poDS, bandNr, psInv);
1588
13.7k
        }
1589
13.9k
        poDS->SetBand(bandNr, gribBand);
1590
13.9k
    }
1591
1592
    // Initialize any PAM information.
1593
217
    poDS->SetDescription(poOpenInfo->pszFilename);
1594
1595
    // Release hGRIBMutex otherwise we'll deadlock with GDALDataset own
1596
    // hGRIBMutex.
1597
217
    CPLReleaseMutex(hGRIBMutex);
1598
217
    poDS->TryLoadXML(poOpenInfo->GetSiblingFiles());
1599
1600
    // Check for external overviews.
1601
217
    poDS->oOvManager.Initialize(poDS, poOpenInfo);
1602
217
    CPLAcquireMutex(hGRIBMutex, 1000.0);
1603
1604
217
    return poDS;
1605
819
}
1606
1607
/************************************************************************/
1608
/*                          GRIBSharedResource                          */
1609
/************************************************************************/
1610
1611
struct GRIBSharedResource
1612
{
1613
    VSILFILE *m_fp = nullptr;
1614
    vsi_l_offset m_nOffsetCurData = static_cast<vsi_l_offset>(-1);
1615
    std::vector<double> m_adfCurData{};
1616
    std::string m_osFilename;
1617
    std::shared_ptr<GDALPamMultiDim> m_poPAM{};
1618
1619
    GRIBSharedResource(const std::string &osFilename, VSILFILE *fp);
1620
    ~GRIBSharedResource();
1621
1622
    const std::vector<double> &LoadData(vsi_l_offset nOffset, int subgNum);
1623
1624
    const std::shared_ptr<GDALPamMultiDim> &GetPAM()
1625
0
    {
1626
0
        return m_poPAM;
1627
0
    }
1628
};
1629
1630
GRIBSharedResource::GRIBSharedResource(const std::string &osFilename,
1631
                                       VSILFILE *fp)
1632
0
    : m_fp(fp), m_osFilename(osFilename),
1633
0
      m_poPAM(std::make_shared<GDALPamMultiDim>(osFilename))
1634
0
{
1635
0
}
1636
1637
GRIBSharedResource::~GRIBSharedResource()
1638
0
{
1639
0
    if (m_fp)
1640
0
        VSIFCloseL(m_fp);
1641
0
}
1642
1643
/************************************************************************/
1644
/*                                GRIBGroup                             */
1645
/************************************************************************/
1646
1647
class GRIBArray;
1648
1649
class GRIBGroup final : public GDALGroup
1650
{
1651
    friend class GRIBArray;
1652
    std::shared_ptr<GRIBSharedResource> m_poShared{};
1653
    std::vector<std::shared_ptr<GDALMDArray>> m_poArrays{};
1654
    std::vector<std::shared_ptr<GDALDimension>> m_dims{};
1655
    std::map<std::string, std::shared_ptr<GDALDimension>> m_oMapDims{};
1656
    int m_nHorizDimCounter = 0;
1657
    std::shared_ptr<GDALGroup> m_memRootGroup{};
1658
1659
  public:
1660
    explicit GRIBGroup(const std::shared_ptr<GRIBSharedResource> &poShared)
1661
0
        : GDALGroup(std::string(), "/"), m_poShared(poShared)
1662
0
    {
1663
0
        std::unique_ptr<GDALDataset> poTmpDS(
1664
0
            MEMDataset::CreateMultiDimensional("", nullptr, nullptr));
1665
0
        m_memRootGroup = poTmpDS->GetRootGroup();
1666
0
    }
1667
1668
    void AddArray(const std::shared_ptr<GDALMDArray> &array)
1669
0
    {
1670
0
        m_poArrays.emplace_back(array);
1671
0
    }
1672
1673
    std::vector<std::string>
1674
    GetMDArrayNames(CSLConstList papszOptions) const override;
1675
    std::shared_ptr<GDALMDArray>
1676
    OpenMDArray(const std::string &osName,
1677
                CSLConstList papszOptions) const override;
1678
1679
    std::vector<std::shared_ptr<GDALDimension>>
1680
    GetDimensions(CSLConstList) const override
1681
0
    {
1682
0
        return m_dims;
1683
0
    }
1684
};
1685
1686
/************************************************************************/
1687
/*                                GRIBArray                             */
1688
/************************************************************************/
1689
1690
class GRIBArray final : public GDALPamMDArray
1691
{
1692
    std::shared_ptr<GRIBSharedResource> m_poShared;
1693
    std::vector<std::shared_ptr<GDALDimension>> m_dims{};
1694
    GDALExtendedDataType m_dt = GDALExtendedDataType::Create(GDT_Float64);
1695
    std::shared_ptr<OGRSpatialReference> m_poSRS{};
1696
    std::vector<vsi_l_offset> m_anOffsets{};
1697
    std::vector<int> m_anSubgNums{};
1698
    std::vector<double> m_adfTimes{};
1699
    std::vector<std::shared_ptr<GDALAttribute>> m_attributes{};
1700
    std::string m_osUnit{};
1701
    std::vector<GByte> m_abyNoData{};
1702
1703
    GRIBArray(const std::string &osName,
1704
              const std::shared_ptr<GRIBSharedResource> &poShared);
1705
1706
  protected:
1707
    bool IRead(const GUInt64 *arrayStartIdx, const size_t *count,
1708
               const GInt64 *arrayStep, const GPtrDiff_t *bufferStride,
1709
               const GDALExtendedDataType &bufferDataType,
1710
               void *pDstBuffer) const override;
1711
1712
  public:
1713
    static std::shared_ptr<GRIBArray>
1714
    Create(const std::string &osName,
1715
           const std::shared_ptr<GRIBSharedResource> &poShared)
1716
0
    {
1717
0
        auto ar(std::shared_ptr<GRIBArray>(new GRIBArray(osName, poShared)));
1718
0
        ar->SetSelf(ar);
1719
0
        return ar;
1720
0
    }
1721
1722
    void Init(GRIBGroup *poGroup, GRIBDataset *poDS, GRIBRasterBand *poBand,
1723
              inventoryType *psInv);
1724
    void ExtendTimeDim(vsi_l_offset nOffset, int subgNum, double dfValidTime);
1725
    void Finalize(GRIBGroup *poGroup, inventoryType *psInv);
1726
1727
    bool IsWritable() const override
1728
0
    {
1729
0
        return false;
1730
0
    }
1731
1732
    const std::string &GetFilename() const override
1733
0
    {
1734
0
        return m_poShared->m_osFilename;
1735
0
    }
1736
1737
    const std::vector<std::shared_ptr<GDALDimension>> &
1738
    GetDimensions() const override
1739
0
    {
1740
0
        return m_dims;
1741
0
    }
1742
1743
    const GDALExtendedDataType &GetDataType() const override
1744
0
    {
1745
0
        return m_dt;
1746
0
    }
1747
1748
    std::shared_ptr<OGRSpatialReference> GetSpatialRef() const override
1749
0
    {
1750
0
        return m_poSRS;
1751
0
    }
1752
1753
    std::vector<std::shared_ptr<GDALAttribute>>
1754
    GetAttributes(CSLConstList) const override
1755
0
    {
1756
0
        return m_attributes;
1757
0
    }
1758
1759
    const std::string &GetUnit() const override
1760
0
    {
1761
0
        return m_osUnit;
1762
0
    }
1763
1764
    const void *GetRawNoDataValue() const override
1765
0
    {
1766
0
        return m_abyNoData.empty() ? nullptr : m_abyNoData.data();
1767
0
    }
1768
};
1769
1770
/************************************************************************/
1771
/*                          GetMDArrayNames()                           */
1772
/************************************************************************/
1773
1774
std::vector<std::string> GRIBGroup::GetMDArrayNames(CSLConstList) const
1775
0
{
1776
0
    std::vector<std::string> ret;
1777
0
    for (const auto &array : m_poArrays)
1778
0
    {
1779
0
        ret.push_back(array->GetName());
1780
0
    }
1781
0
    return ret;
1782
0
}
1783
1784
/************************************************************************/
1785
/*                            OpenMDArray()                             */
1786
/************************************************************************/
1787
1788
std::shared_ptr<GDALMDArray> GRIBGroup::OpenMDArray(const std::string &osName,
1789
                                                    CSLConstList) const
1790
0
{
1791
0
    for (const auto &array : m_poArrays)
1792
0
    {
1793
0
        if (array->GetName() == osName)
1794
0
            return array;
1795
0
    }
1796
0
    return nullptr;
1797
0
}
1798
1799
/************************************************************************/
1800
/*                             GRIBArray()                              */
1801
/************************************************************************/
1802
1803
GRIBArray::GRIBArray(const std::string &osName,
1804
                     const std::shared_ptr<GRIBSharedResource> &poShared)
1805
0
    : GDALAbstractMDArray("/", osName),
1806
0
      GDALPamMDArray("/", osName, poShared->GetPAM()), m_poShared(poShared)
1807
0
{
1808
0
}
Unexecuted instantiation: GRIBArray::GRIBArray(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::shared_ptr<GRIBSharedResource> const&)
Unexecuted instantiation: GRIBArray::GRIBArray(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::shared_ptr<GRIBSharedResource> const&)
1809
1810
/************************************************************************/
1811
/*                                Init()                                */
1812
/************************************************************************/
1813
1814
void GRIBArray::Init(GRIBGroup *poGroup, GRIBDataset *poDS,
1815
                     GRIBRasterBand *poBand, inventoryType *psInv)
1816
0
{
1817
0
    std::shared_ptr<GDALDimension> poDimX;
1818
0
    std::shared_ptr<GDALDimension> poDimY;
1819
1820
0
    double adfGT[6];
1821
0
    poDS->GetGeoTransform(adfGT);
1822
1823
0
    for (int i = 1; i <= poGroup->m_nHorizDimCounter; i++)
1824
0
    {
1825
0
        std::string osXLookup("X");
1826
0
        std::string osYLookup("Y");
1827
0
        if (i > 1)
1828
0
        {
1829
0
            osXLookup += CPLSPrintf("%d", i);
1830
0
            osYLookup += CPLSPrintf("%d", i);
1831
0
        }
1832
0
        auto oIterX = poGroup->m_oMapDims.find(osXLookup);
1833
0
        auto oIterY = poGroup->m_oMapDims.find(osYLookup);
1834
0
        CPLAssert(oIterX != poGroup->m_oMapDims.end());
1835
0
        CPLAssert(oIterY != poGroup->m_oMapDims.end());
1836
0
        if (oIterX->second->GetSize() ==
1837
0
                static_cast<size_t>(poDS->GetRasterXSize()) &&
1838
0
            oIterY->second->GetSize() ==
1839
0
                static_cast<size_t>(poDS->GetRasterYSize()))
1840
0
        {
1841
0
            bool bOK = true;
1842
0
            auto poVar = oIterX->second->GetIndexingVariable();
1843
0
            constexpr double EPSILON = 1e-10;
1844
0
            if (poVar)
1845
0
            {
1846
0
                GUInt64 nStart = 0;
1847
0
                size_t nCount = 1;
1848
0
                double dfVal = 0;
1849
0
                poVar->Read(&nStart, &nCount, nullptr, nullptr, m_dt, &dfVal);
1850
0
                if (std::fabs(dfVal - (adfGT[0] + 0.5 * adfGT[1])) >
1851
0
                    EPSILON * std::fabs(dfVal))
1852
0
                {
1853
0
                    bOK = false;
1854
0
                }
1855
0
            }
1856
0
            if (bOK)
1857
0
            {
1858
0
                poVar = oIterY->second->GetIndexingVariable();
1859
0
                if (poVar)
1860
0
                {
1861
0
                    GUInt64 nStart = 0;
1862
0
                    size_t nCount = 1;
1863
0
                    double dfVal = 0;
1864
0
                    poVar->Read(&nStart, &nCount, nullptr, nullptr, m_dt,
1865
0
                                &dfVal);
1866
0
                    if (std::fabs(dfVal -
1867
0
                                  (adfGT[3] + poDS->nRasterYSize * adfGT[5] -
1868
0
                                   0.5 * adfGT[5])) >
1869
0
                        EPSILON * std::fabs(dfVal))
1870
0
                    {
1871
0
                        bOK = false;
1872
0
                    }
1873
0
                }
1874
0
            }
1875
0
            if (bOK)
1876
0
            {
1877
0
                poDimX = oIterX->second;
1878
0
                poDimY = oIterY->second;
1879
0
                break;
1880
0
            }
1881
0
        }
1882
0
    }
1883
1884
0
    if (!poDimX || !poDimY)
1885
0
    {
1886
0
        poGroup->m_nHorizDimCounter++;
1887
0
        {
1888
0
            std::string osName("Y");
1889
0
            if (poGroup->m_nHorizDimCounter >= 2)
1890
0
            {
1891
0
                osName = CPLSPrintf("Y%d", poGroup->m_nHorizDimCounter);
1892
0
            }
1893
1894
0
            poDimY = std::make_shared<GDALDimensionWeakIndexingVar>(
1895
0
                poGroup->GetFullName(), osName, GDAL_DIM_TYPE_HORIZONTAL_Y,
1896
0
                std::string(), poDS->GetRasterYSize());
1897
0
            poGroup->m_oMapDims[osName] = poDimY;
1898
0
            poGroup->m_dims.emplace_back(poDimY);
1899
1900
0
            auto var = GDALMDArrayRegularlySpaced::Create(
1901
0
                "/", poDimY->GetName(), poDimY,
1902
0
                adfGT[3] + poDS->GetRasterYSize() * adfGT[5], -adfGT[5], 0.5);
1903
0
            poDimY->SetIndexingVariable(var);
1904
0
            poGroup->AddArray(var);
1905
0
        }
1906
1907
0
        {
1908
0
            std::string osName("X");
1909
0
            if (poGroup->m_nHorizDimCounter >= 2)
1910
0
            {
1911
0
                osName = CPLSPrintf("X%d", poGroup->m_nHorizDimCounter);
1912
0
            }
1913
1914
0
            poDimX = std::make_shared<GDALDimensionWeakIndexingVar>(
1915
0
                poGroup->GetFullName(), osName, GDAL_DIM_TYPE_HORIZONTAL_X,
1916
0
                std::string(), poDS->GetRasterXSize());
1917
0
            poGroup->m_oMapDims[osName] = poDimX;
1918
0
            poGroup->m_dims.emplace_back(poDimX);
1919
1920
0
            auto var = GDALMDArrayRegularlySpaced::Create(
1921
0
                "/", poDimX->GetName(), poDimX, adfGT[0], adfGT[1], 0.5);
1922
0
            poDimX->SetIndexingVariable(var);
1923
0
            poGroup->AddArray(var);
1924
0
        }
1925
0
    }
1926
1927
0
    m_dims.emplace_back(poDimY);
1928
0
    m_dims.emplace_back(poDimX);
1929
0
    if (poDS->m_poSRS.get())
1930
0
    {
1931
0
        m_poSRS.reset(poDS->m_poSRS->Clone());
1932
0
        if (poDS->m_poSRS->GetDataAxisToSRSAxisMapping() ==
1933
0
            std::vector<int>{2, 1})
1934
0
            m_poSRS->SetDataAxisToSRSAxisMapping({1, 2});
1935
0
        else
1936
0
            m_poSRS->SetDataAxisToSRSAxisMapping({2, 1});
1937
0
    }
1938
1939
0
    const char *pszGribNormalizeUnits =
1940
0
        CPLGetConfigOption("GRIB_NORMALIZE_UNITS", "YES");
1941
0
    const bool bMetricUnits = CPLTestBool(pszGribNormalizeUnits);
1942
1943
0
    m_attributes.emplace_back(std::make_shared<GDALAttributeString>(
1944
0
        GetFullName(), "name", psInv->element));
1945
0
    m_attributes.emplace_back(std::make_shared<GDALAttributeString>(
1946
0
        GetFullName(), "long_name",
1947
0
        ConvertUnitInText(bMetricUnits, psInv->comment)));
1948
0
    m_osUnit = ConvertUnitInText(bMetricUnits, psInv->unitName);
1949
0
    if (!m_osUnit.empty() && m_osUnit[0] == '[' && m_osUnit.back() == ']')
1950
0
    {
1951
0
        m_osUnit = m_osUnit.substr(1, m_osUnit.size() - 2);
1952
0
    }
1953
0
    m_attributes.emplace_back(std::make_shared<GDALAttributeString>(
1954
0
        GetFullName(), "first_level", psInv->shortFstLevel));
1955
1956
0
    if (poBand->m_nDisciplineCode >= 0)
1957
0
    {
1958
0
        m_attributes.emplace_back(std::make_shared<GDALAttributeNumeric>(
1959
0
            GetFullName(), "discipline_code", poBand->m_nDisciplineCode));
1960
0
    }
1961
0
    if (!poBand->m_osDisciplineName.empty())
1962
0
    {
1963
0
        m_attributes.emplace_back(std::make_shared<GDALAttributeString>(
1964
0
            GetFullName(), "discipline_name", poBand->m_osDisciplineName));
1965
0
    }
1966
0
    if (poBand->m_nCenter >= 0)
1967
0
    {
1968
0
        m_attributes.emplace_back(std::make_shared<GDALAttributeNumeric>(
1969
0
            GetFullName(), "center_code", poBand->m_nCenter));
1970
0
    }
1971
0
    if (!poBand->m_osCenterName.empty())
1972
0
    {
1973
0
        m_attributes.emplace_back(std::make_shared<GDALAttributeString>(
1974
0
            GetFullName(), "center_name", poBand->m_osCenterName));
1975
0
    }
1976
0
    if (poBand->m_nSubCenter >= 0)
1977
0
    {
1978
0
        m_attributes.emplace_back(std::make_shared<GDALAttributeNumeric>(
1979
0
            GetFullName(), "subcenter_code", poBand->m_nSubCenter));
1980
0
    }
1981
0
    if (!poBand->m_osSubCenterName.empty())
1982
0
    {
1983
0
        m_attributes.emplace_back(std::make_shared<GDALAttributeString>(
1984
0
            GetFullName(), "subcenter_name", poBand->m_osSubCenterName));
1985
0
    }
1986
0
    if (!poBand->m_osSignRefTimeName.empty())
1987
0
    {
1988
0
        m_attributes.emplace_back(std::make_shared<GDALAttributeString>(
1989
0
            GetFullName(), "signification_of_ref_time",
1990
0
            poBand->m_osSignRefTimeName));
1991
0
    }
1992
0
    if (!poBand->m_osRefTime.empty())
1993
0
    {
1994
0
        m_attributes.emplace_back(std::make_shared<GDALAttributeString>(
1995
0
            GetFullName(), "reference_time_iso8601", poBand->m_osRefTime));
1996
0
    }
1997
0
    if (!poBand->m_osProductionStatus.empty())
1998
0
    {
1999
0
        m_attributes.emplace_back(std::make_shared<GDALAttributeString>(
2000
0
            GetFullName(), "production_status", poBand->m_osProductionStatus));
2001
0
    }
2002
0
    if (!poBand->m_osType.empty())
2003
0
    {
2004
0
        m_attributes.emplace_back(std::make_shared<GDALAttributeString>(
2005
0
            GetFullName(), "type", poBand->m_osType));
2006
0
    }
2007
0
    if (poBand->m_nPDTN >= 0)
2008
0
    {
2009
0
        m_attributes.emplace_back(std::make_shared<GDALAttributeNumeric>(
2010
0
            GetFullName(), "product_definition_template_number",
2011
0
            poBand->m_nPDTN));
2012
0
    }
2013
0
    if (!poBand->m_anPDSTemplateAssembledValues.empty())
2014
0
    {
2015
0
        m_attributes.emplace_back(std::make_shared<GDALAttributeNumeric>(
2016
0
            GetFullName(), "product_definition_numbers",
2017
0
            poBand->m_anPDSTemplateAssembledValues));
2018
0
    }
2019
2020
0
    int bHasNoData = FALSE;
2021
0
    double dfNoData = poBand->GetNoDataValue(&bHasNoData);
2022
0
    if (bHasNoData)
2023
0
    {
2024
0
        m_abyNoData.resize(sizeof(double));
2025
0
        memcpy(&m_abyNoData[0], &dfNoData, sizeof(double));
2026
0
    }
2027
0
}
2028
2029
/************************************************************************/
2030
/*                         ExtendTimeDim()                              */
2031
/************************************************************************/
2032
2033
void GRIBArray::ExtendTimeDim(vsi_l_offset nOffset, int subgNum,
2034
                              double dfValidTime)
2035
0
{
2036
0
    m_anOffsets.push_back(nOffset);
2037
0
    m_anSubgNums.push_back(subgNum);
2038
0
    m_adfTimes.push_back(dfValidTime);
2039
0
}
2040
2041
/************************************************************************/
2042
/*                           Finalize()                                 */
2043
/************************************************************************/
2044
2045
void GRIBArray::Finalize(GRIBGroup *poGroup, inventoryType *psInv)
2046
0
{
2047
0
    CPLAssert(!m_adfTimes.empty());
2048
0
    CPLAssert(m_anOffsets.size() == m_adfTimes.size());
2049
2050
0
    if (m_adfTimes.size() == 1)
2051
0
    {
2052
0
        m_attributes.emplace_back(std::make_shared<GDALAttributeNumeric>(
2053
0
            GetFullName(), "forecast_time", psInv->foreSec));
2054
0
        m_attributes.emplace_back(std::make_shared<GDALAttributeString>(
2055
0
            GetFullName(), "forecast_time_unit", "sec"));
2056
0
        m_attributes.emplace_back(std::make_shared<GDALAttributeNumeric>(
2057
0
            GetFullName(), "reference_time", psInv->refTime));
2058
0
        m_attributes.emplace_back(std::make_shared<GDALAttributeString>(
2059
0
            GetFullName(), "reference_time_unit", "sec UTC"));
2060
0
        m_attributes.emplace_back(std::make_shared<GDALAttributeNumeric>(
2061
0
            GetFullName(), "validity_time", m_adfTimes[0]));
2062
0
        m_attributes.emplace_back(std::make_shared<GDALAttributeString>(
2063
0
            GetFullName(), "validity_time_unit", "sec UTC"));
2064
0
        return;
2065
0
    }
2066
2067
0
    std::shared_ptr<GDALDimension> poDimTime;
2068
2069
0
    for (const auto &poDim : poGroup->m_dims)
2070
0
    {
2071
0
        if (STARTS_WITH(poDim->GetName().c_str(), "TIME") &&
2072
0
            poDim->GetSize() == m_adfTimes.size())
2073
0
        {
2074
0
            auto poVar = poDim->GetIndexingVariable();
2075
0
            if (poVar)
2076
0
            {
2077
0
                GUInt64 nStart = 0;
2078
0
                size_t nCount = 1;
2079
0
                double dfStartTime = 0;
2080
0
                poVar->Read(&nStart, &nCount, nullptr, nullptr, m_dt,
2081
0
                            &dfStartTime);
2082
0
                if (dfStartTime == m_adfTimes[0])
2083
0
                {
2084
0
                    poDimTime = poDim;
2085
0
                    break;
2086
0
                }
2087
0
            }
2088
0
        }
2089
0
    }
2090
2091
0
    if (!poDimTime)
2092
0
    {
2093
0
        std::string osName("TIME");
2094
0
        int counter = 2;
2095
0
        while (poGroup->m_oMapDims.find(osName) != poGroup->m_oMapDims.end())
2096
0
        {
2097
0
            osName = CPLSPrintf("TIME%d", counter);
2098
0
            counter++;
2099
0
        }
2100
2101
0
        poDimTime = std::make_shared<GDALDimensionWeakIndexingVar>(
2102
0
            poGroup->GetFullName(), osName, GDAL_DIM_TYPE_TEMPORAL,
2103
0
            std::string(), m_adfTimes.size());
2104
0
        poGroup->m_oMapDims[osName] = poDimTime;
2105
0
        poGroup->m_dims.emplace_back(poDimTime);
2106
2107
0
        auto var = poGroup->m_memRootGroup->CreateMDArray(
2108
0
            poDimTime->GetName(),
2109
0
            std::vector<std::shared_ptr<GDALDimension>>{poDimTime},
2110
0
            GDALExtendedDataType::Create(GDT_Float64), nullptr);
2111
0
        poDimTime->SetIndexingVariable(var);
2112
0
        poGroup->AddArray(var);
2113
2114
0
        GUInt64 nStart = 0;
2115
0
        size_t nCount = m_adfTimes.size();
2116
0
        var->SetUnit("sec UTC");
2117
0
        const GUInt64 anStart[] = {nStart};
2118
0
        const size_t anCount[] = {nCount};
2119
0
        var->Write(anStart, anCount, nullptr, nullptr, var->GetDataType(),
2120
0
                   &m_adfTimes[0]);
2121
0
        auto attr = var->CreateAttribute("long_name", {},
2122
0
                                         GDALExtendedDataType::CreateString());
2123
0
        attr->Write("validity_time");
2124
0
    }
2125
2126
0
#if defined(__GNUC__)
2127
0
#pragma GCC diagnostic push
2128
0
#pragma GCC diagnostic ignored "-Wnull-dereference"
2129
0
#endif
2130
0
    m_dims.insert(m_dims.begin(), poDimTime);
2131
0
#if defined(__GNUC__)
2132
0
#pragma GCC diagnostic pop
2133
0
#endif
2134
0
    if (m_poSRS)
2135
0
    {
2136
0
        auto mapping = m_poSRS->GetDataAxisToSRSAxisMapping();
2137
0
        for (auto &v : mapping)
2138
0
            v += 1;
2139
0
        m_poSRS->SetDataAxisToSRSAxisMapping(mapping);
2140
0
    }
2141
0
}
2142
2143
/************************************************************************/
2144
/*                              LoadData()                              */
2145
/************************************************************************/
2146
2147
const std::vector<double> &GRIBSharedResource::LoadData(vsi_l_offset nOffset,
2148
                                                        int subgNum)
2149
0
{
2150
0
    if (m_nOffsetCurData == nOffset)
2151
0
        return m_adfCurData;
2152
2153
0
    grib_MetaData *metadata = nullptr;
2154
0
    double *data = nullptr;
2155
0
    GRIBRasterBand::ReadGribData(m_fp, nOffset, subgNum, &data, &metadata);
2156
0
    if (data == nullptr || metadata == nullptr)
2157
0
    {
2158
0
        if (metadata != nullptr)
2159
0
        {
2160
0
            MetaFree(metadata);
2161
0
            delete metadata;
2162
0
        }
2163
0
        free(data);
2164
0
        m_adfCurData.clear();
2165
0
        return m_adfCurData;
2166
0
    }
2167
0
    const int nx = metadata->gds.Nx;
2168
0
    const int ny = metadata->gds.Ny;
2169
0
    MetaFree(metadata);
2170
0
    delete metadata;
2171
0
    if (nx <= 0 || ny <= 0)
2172
0
    {
2173
0
        free(data);
2174
0
        m_adfCurData.clear();
2175
0
        return m_adfCurData;
2176
0
    }
2177
0
    const size_t nPointCount = static_cast<size_t>(nx) * ny;
2178
0
    const size_t nByteCount = nPointCount * sizeof(double);
2179
0
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
2180
0
    if (nByteCount > static_cast<size_t>(INT_MAX))
2181
0
    {
2182
0
        CPLError(CE_Failure, CPLE_OutOfMemory,
2183
0
                 "Too large memory allocation attempt");
2184
0
        free(data);
2185
0
        m_adfCurData.clear();
2186
0
        return m_adfCurData;
2187
0
    }
2188
0
#endif
2189
0
    try
2190
0
    {
2191
0
        m_adfCurData.resize(nPointCount);
2192
0
    }
2193
0
    catch (const std::exception &e)
2194
0
    {
2195
0
        CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
2196
0
        free(data);
2197
0
        m_adfCurData.clear();
2198
0
        return m_adfCurData;
2199
0
    }
2200
0
    m_nOffsetCurData = nOffset;
2201
0
    memcpy(&m_adfCurData[0], data, nByteCount);
2202
0
    free(data);
2203
0
    return m_adfCurData;
2204
0
}
2205
2206
/************************************************************************/
2207
/*                             IRead()                                  */
2208
/************************************************************************/
2209
2210
bool GRIBArray::IRead(const GUInt64 *arrayStartIdx, const size_t *count,
2211
                      const GInt64 *arrayStep, const GPtrDiff_t *bufferStride,
2212
                      const GDALExtendedDataType &bufferDataType,
2213
                      void *pDstBuffer) const
2214
0
{
2215
0
    const size_t nBufferDTSize(bufferDataType.GetSize());
2216
0
    if (m_dims.size() == 2)
2217
0
    {
2218
0
        auto &vals = m_poShared->LoadData(m_anOffsets[0], m_anSubgNums[0]);
2219
0
        constexpr int Y_IDX = 0;
2220
0
        constexpr int X_IDX = 1;
2221
0
        if (vals.empty() ||
2222
0
            vals.size() != m_dims[Y_IDX]->GetSize() * m_dims[X_IDX]->GetSize())
2223
0
            return false;
2224
0
        const size_t nWidth(static_cast<size_t>(m_dims[X_IDX]->GetSize()));
2225
0
        const bool bDirectCopy = m_dt == bufferDataType &&
2226
0
                                 arrayStep[X_IDX] == 1 &&
2227
0
                                 bufferStride[X_IDX] == 1;
2228
0
        for (size_t j = 0; j < count[Y_IDX]; j++)
2229
0
        {
2230
0
            const size_t y = static_cast<size_t>(arrayStartIdx[Y_IDX] +
2231
0
                                                 j * arrayStep[Y_IDX]);
2232
0
            GByte *pabyDstPtr = static_cast<GByte *>(pDstBuffer) +
2233
0
                                j * bufferStride[Y_IDX] * nBufferDTSize;
2234
0
            const size_t x = static_cast<size_t>(arrayStartIdx[X_IDX]);
2235
0
            const double *srcPtr = &vals[y * nWidth + x];
2236
0
            if (bDirectCopy)
2237
0
            {
2238
0
                memcpy(pabyDstPtr, srcPtr, count[X_IDX] * sizeof(double));
2239
0
            }
2240
0
            else
2241
0
            {
2242
0
                const auto dstPtrInc = bufferStride[X_IDX] * nBufferDTSize;
2243
0
                for (size_t i = 0; i < count[X_IDX]; i++)
2244
0
                {
2245
0
                    GDALExtendedDataType::CopyValue(srcPtr, m_dt, pabyDstPtr,
2246
0
                                                    bufferDataType);
2247
0
                    srcPtr += static_cast<std::ptrdiff_t>(arrayStep[X_IDX]);
2248
0
                    pabyDstPtr += dstPtrInc;
2249
0
                }
2250
0
            }
2251
0
        }
2252
0
        return true;
2253
0
    }
2254
2255
0
    constexpr int T_IDX = 0;
2256
0
    constexpr int Y_IDX = 1;
2257
0
    constexpr int X_IDX = 2;
2258
0
    const size_t nWidth(static_cast<size_t>(m_dims[X_IDX]->GetSize()));
2259
0
    const bool bDirectCopy = m_dt == bufferDataType && arrayStep[X_IDX] == 1 &&
2260
0
                             bufferStride[X_IDX] == 1;
2261
0
    for (size_t k = 0; k < count[T_IDX]; k++)
2262
0
    {
2263
0
        const size_t tIdx =
2264
0
            static_cast<size_t>(arrayStartIdx[T_IDX] + k * arrayStep[T_IDX]);
2265
0
        CPLAssert(tIdx < m_anOffsets.size());
2266
0
        auto &vals =
2267
0
            m_poShared->LoadData(m_anOffsets[tIdx], m_anSubgNums[tIdx]);
2268
0
        if (vals.empty() ||
2269
0
            vals.size() != m_dims[Y_IDX]->GetSize() * m_dims[X_IDX]->GetSize())
2270
0
            return false;
2271
0
        for (size_t j = 0; j < count[Y_IDX]; j++)
2272
0
        {
2273
0
            const size_t y = static_cast<size_t>(arrayStartIdx[Y_IDX] +
2274
0
                                                 j * arrayStep[Y_IDX]);
2275
0
            GByte *pabyDstPtr =
2276
0
                static_cast<GByte *>(pDstBuffer) +
2277
0
                (k * bufferStride[T_IDX] + j * bufferStride[Y_IDX]) *
2278
0
                    nBufferDTSize;
2279
0
            const size_t x = static_cast<size_t>(arrayStartIdx[X_IDX]);
2280
0
            const double *srcPtr = &vals[y * nWidth + x];
2281
0
            if (bDirectCopy)
2282
0
            {
2283
0
                memcpy(pabyDstPtr, srcPtr, count[X_IDX] * sizeof(double));
2284
0
            }
2285
0
            else
2286
0
            {
2287
0
                const auto dstPtrInc = bufferStride[X_IDX] * nBufferDTSize;
2288
0
                for (size_t i = 0; i < count[X_IDX]; i++)
2289
0
                {
2290
0
                    GDALExtendedDataType::CopyValue(srcPtr, m_dt, pabyDstPtr,
2291
0
                                                    bufferDataType);
2292
0
                    srcPtr += static_cast<std::ptrdiff_t>(arrayStep[X_IDX]);
2293
0
                    pabyDstPtr += dstPtrInc;
2294
0
                }
2295
0
            }
2296
0
        }
2297
0
    }
2298
2299
0
    return true;
2300
0
}
2301
2302
/************************************************************************/
2303
/*                          OpenMultiDim()                              */
2304
/************************************************************************/
2305
2306
GDALDataset *GRIBDataset::OpenMultiDim(GDALOpenInfo *poOpenInfo)
2307
2308
0
{
2309
0
    auto poShared = std::make_shared<GRIBSharedResource>(
2310
0
        poOpenInfo->pszFilename, poOpenInfo->fpL);
2311
0
    auto poRootGroup = std::make_shared<GRIBGroup>(poShared);
2312
0
    poOpenInfo->fpL = nullptr;
2313
2314
0
    VSIFSeekL(poShared->m_fp, 0, SEEK_SET);
2315
2316
    // Contains an GRIB2 message inventory of the file.
2317
    // We can't use the potential .idx file
2318
0
    auto pInventories = std::make_unique<InventoryWrapperGrib>(poShared->m_fp);
2319
2320
0
    if (pInventories->result() <= 0)
2321
0
    {
2322
0
        char *errMsg = errSprintf(nullptr);
2323
0
        if (errMsg != nullptr)
2324
0
            CPLDebug("GRIB", "%s", errMsg);
2325
0
        free(errMsg);
2326
2327
0
        CPLError(CE_Failure, CPLE_OpenFailed,
2328
0
                 "%s is a grib file, "
2329
0
                 "but no raster dataset was successfully identified.",
2330
0
                 poOpenInfo->pszFilename);
2331
0
        return nullptr;
2332
0
    }
2333
2334
    // Create band objects.
2335
0
    GRIBDataset *poDS = new GRIBDataset();
2336
0
    poDS->fp = poShared->m_fp;
2337
2338
0
    std::shared_ptr<GRIBArray> poArray;
2339
0
    std::set<std::string> oSetArrayNames;
2340
0
    std::string osElement;
2341
0
    std::string osShortFstLevel;
2342
0
    double dfRefTime = 0;
2343
0
    double dfForecastTime = 0;
2344
0
    for (uInt4 i = 0; i < pInventories->length(); ++i)
2345
0
    {
2346
0
        inventoryType *psInv = pInventories->get(i);
2347
0
        uInt4 bandNr = i + 1;
2348
2349
        // GRIB messages can be preceded by "garbage". GRIB2Inventory()
2350
        // does not return the offset to the real start of the message
2351
0
        GByte abyHeader[1024 + 1];
2352
0
        VSIFSeekL(poShared->m_fp, psInv->start, SEEK_SET);
2353
0
        size_t nRead =
2354
0
            VSIFReadL(abyHeader, 1, sizeof(abyHeader) - 1, poShared->m_fp);
2355
0
        abyHeader[nRead] = 0;
2356
        // Find the real offset of the fist message
2357
0
        const char *pasHeader = reinterpret_cast<char *>(abyHeader);
2358
0
        int nOffsetFirstMessage = 0;
2359
0
        for (int j = 0; j < poOpenInfo->nHeaderBytes - 3; j++)
2360
0
        {
2361
0
            if (STARTS_WITH_CI(pasHeader + j, "GRIB")
2362
#ifdef ENABLE_TDLP
2363
                || STARTS_WITH_CI(pasHeader + j, "TDLP")
2364
#endif
2365
0
            )
2366
0
            {
2367
0
                nOffsetFirstMessage = j;
2368
0
                break;
2369
0
            }
2370
0
        }
2371
0
        psInv->start += nOffsetFirstMessage;
2372
2373
0
        if (poArray == nullptr || osElement != psInv->element ||
2374
0
            osShortFstLevel != psInv->shortFstLevel ||
2375
0
            !((dfRefTime == psInv->refTime &&
2376
0
               dfForecastTime != psInv->foreSec) ||
2377
0
              (dfRefTime != psInv->refTime &&
2378
0
               dfForecastTime == psInv->foreSec)))
2379
0
        {
2380
0
            if (poArray)
2381
0
            {
2382
0
                poArray->Finalize(poRootGroup.get(), pInventories->get(i - 1));
2383
0
                poRootGroup->AddArray(poArray);
2384
0
            }
2385
2386
0
            grib_MetaData *metaData = nullptr;
2387
0
            GRIBRasterBand::ReadGribData(poShared->m_fp, psInv->start,
2388
0
                                         psInv->subgNum, nullptr, &metaData);
2389
0
            if (metaData == nullptr || metaData->gds.Nx < 1 ||
2390
0
                metaData->gds.Ny < 1)
2391
0
            {
2392
0
                CPLError(CE_Failure, CPLE_OpenFailed,
2393
0
                         "%s is a grib file, "
2394
0
                         "but no raster dataset was successfully identified.",
2395
0
                         poOpenInfo->pszFilename);
2396
2397
0
                if (metaData != nullptr)
2398
0
                {
2399
0
                    MetaFree(metaData);
2400
0
                    delete metaData;
2401
0
                }
2402
0
                poDS->fp = nullptr;
2403
0
                CPLReleaseMutex(hGRIBMutex);
2404
0
                delete poDS;
2405
0
                CPLAcquireMutex(hGRIBMutex, 1000.0);
2406
0
                return nullptr;
2407
0
            }
2408
0
            psInv->GribVersion = metaData->GribVersion;
2409
2410
            // Set the DataSet's x,y size, georeference and projection from
2411
            // the first GRIB band.
2412
0
            poDS->SetGribMetaData(metaData);
2413
2414
            // coverity[tainted_data]
2415
0
            GRIBRasterBand gribBand(poDS, bandNr, psInv);
2416
0
            if (psInv->GribVersion == 2)
2417
0
                gribBand.FindPDSTemplateGRIB2();
2418
0
            osElement = psInv->element;
2419
0
            osShortFstLevel = psInv->shortFstLevel;
2420
0
            dfRefTime = psInv->refTime;
2421
0
            dfForecastTime = psInv->foreSec;
2422
2423
0
            std::string osRadix(osElement + '_' + osShortFstLevel);
2424
0
            std::string osName(osRadix);
2425
0
            int nCounter = 2;
2426
0
            while (oSetArrayNames.find(osName) != oSetArrayNames.end())
2427
0
            {
2428
0
                osName = osRadix + CPLSPrintf("_%d", nCounter);
2429
0
                nCounter++;
2430
0
            }
2431
0
            oSetArrayNames.insert(osName);
2432
0
            poArray = GRIBArray::Create(osName, poShared);
2433
0
            poArray->Init(poRootGroup.get(), poDS, &gribBand, psInv);
2434
0
            poArray->ExtendTimeDim(psInv->start, psInv->subgNum,
2435
0
                                   psInv->validTime);
2436
2437
0
            MetaFree(metaData);
2438
0
            delete metaData;
2439
0
        }
2440
0
        else
2441
0
        {
2442
0
            poArray->ExtendTimeDim(psInv->start, psInv->subgNum,
2443
0
                                   psInv->validTime);
2444
0
        }
2445
0
    }
2446
2447
0
    if (poArray)
2448
0
    {
2449
0
        poArray->Finalize(poRootGroup.get(),
2450
0
                          pInventories->get(pInventories->length() - 1));
2451
0
        poRootGroup->AddArray(poArray);
2452
0
    }
2453
2454
0
    poDS->fp = nullptr;
2455
0
    poDS->m_poRootGroup = poRootGroup;
2456
2457
0
    poDS->SetDescription(poOpenInfo->pszFilename);
2458
2459
    // Release hGRIBMutex otherwise we'll deadlock with GDALDataset own
2460
    // hGRIBMutex.
2461
0
    CPLReleaseMutex(hGRIBMutex);
2462
0
    poDS->TryLoadXML();
2463
0
    CPLAcquireMutex(hGRIBMutex, 1000.0);
2464
2465
0
    return poDS;
2466
0
}
2467
2468
/************************************************************************/
2469
/*                            SetMetadata()                             */
2470
/************************************************************************/
2471
2472
void GRIBDataset::SetGribMetaData(grib_MetaData *meta)
2473
217
{
2474
217
    nRasterXSize = meta->gds.Nx;
2475
217
    nRasterYSize = meta->gds.Ny;
2476
2477
    // Image projection.
2478
217
    OGRSpatialReference oSRS;
2479
217
    oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2480
2481
217
    switch (meta->gds.projType)
2482
217
    {
2483
79
        case GS3_LATLON:
2484
79
        case GS3_GAUSSIAN_LATLON:
2485
            // No projection, only latlon system (geographic).
2486
79
            break;
2487
67
        case GS3_ROTATED_LATLON:
2488
            // will apply pole rotation afterwards
2489
67
            break;
2490
12
        case GS3_MERCATOR:
2491
12
            if (meta->gds.orientLon == 0.0)
2492
12
            {
2493
12
                if (meta->gds.meshLat == 0.0)
2494
1
                    oSRS.SetMercator(0.0, 0.0, 1.0, 0.0, 0.0);
2495
11
                else
2496
11
                    oSRS.SetMercator2SP(meta->gds.meshLat, 0.0, 0.0, 0.0, 0.0);
2497
12
            }
2498
0
            else
2499
0
            {
2500
0
                CPLError(CE_Warning, CPLE_NotSupported,
2501
0
                         "Orientation of the grid != 0 not supported");
2502
0
                return;
2503
0
            }
2504
12
            break;
2505
12
        case GS3_TRANSVERSE_MERCATOR:
2506
0
            oSRS.SetTM(meta->gds.latitude_of_origin,
2507
0
                       Lon360to180(meta->gds.central_meridian),
2508
0
                       std::abs(meta->gds.scaleLat1 - 0.9996) < 1e-8
2509
0
                           ? 0.9996
2510
0
                           : meta->gds.scaleLat1,
2511
0
                       meta->gds.x0, meta->gds.y0);
2512
0
            break;
2513
2
        case GS3_POLAR:
2514
2
            oSRS.SetPS(meta->gds.meshLat, meta->gds.orientLon, 1.0, 0.0, 0.0);
2515
2
            break;
2516
57
        case GS3_LAMBERT:
2517
57
            oSRS.SetLCC(meta->gds.scaleLat1, meta->gds.scaleLat2,
2518
57
                        meta->gds.meshLat, Lon360to180(meta->gds.orientLon),
2519
57
                        0.0, 0.0);
2520
57
            break;
2521
0
        case GS3_ALBERS_EQUAL_AREA:
2522
0
            oSRS.SetACEA(meta->gds.scaleLat1, meta->gds.scaleLat2,
2523
0
                         meta->gds.meshLat, Lon360to180(meta->gds.orientLon),
2524
0
                         0.0, 0.0);
2525
0
            break;
2526
2527
0
        case GS3_ORTHOGRAPHIC:
2528
2529
            // oSRS.SetOrthographic( 0.0, meta->gds.orientLon,
2530
            //                       meta->gds.lon2, meta->gds.lat2);
2531
2532
            // oSRS.SetGEOS( meta->gds.orientLon, meta->gds.stretchFactor,
2533
            //               meta->gds.lon2, meta->gds.lat2);
2534
2535
            // TODO: Hardcoded for now. How to parse the meta->gds section?
2536
0
            oSRS.SetGEOS(0, 35785831, 0, 0);
2537
0
            break;
2538
0
        case GS3_LAMBERT_AZIMUTHAL:
2539
0
            oSRS.SetLAEA(meta->gds.meshLat, Lon360to180(meta->gds.orientLon),
2540
0
                         0.0, 0.0);
2541
0
            break;
2542
2543
0
        case GS3_EQUATOR_EQUIDIST:
2544
0
            break;
2545
0
        case GS3_AZIMUTH_RANGE:
2546
0
            break;
2547
217
    }
2548
2549
217
    if (oSRS.IsProjected())
2550
71
    {
2551
71
        oSRS.SetLinearUnits("Metre", 1.0);
2552
71
    }
2553
2554
217
    const bool bHaveEarthModel =
2555
217
        meta->gds.majEarth > 0.0 && meta->gds.minEarth > 0.0;
2556
    // In meters.
2557
217
    const double a = bHaveEarthModel
2558
217
                         ? meta->gds.majEarth * 1.0e3
2559
217
                         : CPLAtof(CPLGetConfigOption("GRIB_DEFAULT_SEMI_MAJOR",
2560
0
                                                      "6377563.396"));
2561
217
    const double b =
2562
217
        bHaveEarthModel
2563
217
            ? meta->gds.minEarth * 1.0e3
2564
217
            : (meta->gds.f_sphere
2565
0
                   ? a
2566
0
                   : CPLAtof(CPLGetConfigOption("GRIB_DEFAULT_SEMI_MINOR",
2567
0
                                                "6356256.910")));
2568
217
    if (meta->gds.majEarth == 0 || meta->gds.minEarth == 0)
2569
0
    {
2570
0
        CPLDebug("GRIB", "No earth model. Assuming a=%f and b=%f", a, b);
2571
0
    }
2572
217
    else if (meta->gds.majEarth < 0 || meta->gds.minEarth < 0)
2573
0
    {
2574
0
        const char *pszUseDefaultSpheroid =
2575
0
            CPLGetConfigOption("GRIB_USE_DEFAULT_SPHEROID", nullptr);
2576
0
        if (!pszUseDefaultSpheroid)
2577
0
        {
2578
0
            CPLError(CE_Failure, CPLE_AppDefined,
2579
0
                     "The GRIB file contains invalid values for the spheroid. "
2580
0
                     "You may set the GRIB_USE_DEFAULT_SPHEROID configuration "
2581
0
                     "option to YES to use a default spheroid with "
2582
0
                     "a=%f and b=%f",
2583
0
                     a, b);
2584
0
            return;
2585
0
        }
2586
0
        else if (!CPLTestBool(pszUseDefaultSpheroid))
2587
0
        {
2588
0
            return;
2589
0
        }
2590
0
        CPLDebug("GRIB", "Invalid earth model. Assuming a=%f and b=%f", a, b);
2591
0
    }
2592
2593
217
    if (meta->gds.f_sphere || (a == b))
2594
154
    {
2595
154
        oSRS.SetGeogCS("Coordinate System imported from GRIB file", nullptr,
2596
154
                       "Sphere", a, 0.0);
2597
154
    }
2598
63
    else
2599
63
    {
2600
63
        const double fInv = a / (a - b);
2601
63
        if (std::abs(a - 6378137.0) < 0.01 &&
2602
63
            std::abs(fInv - 298.257223563) < 1e-9)  // WGS84
2603
0
        {
2604
0
            if (meta->gds.projType == GS3_LATLON)
2605
0
                oSRS.SetFromUserInput(SRS_WKT_WGS84_LAT_LONG);
2606
0
            else
2607
0
            {
2608
0
                oSRS.SetGeogCS("Coordinate System imported from GRIB file",
2609
0
                               "WGS_1984", "WGS 84", 6378137., 298.257223563);
2610
0
            }
2611
0
        }
2612
63
        else if (std::abs(a - 6378137.0) < 0.01 &&
2613
63
                 std::abs(fInv - 298.257222101) < 1e-9)  // GRS80
2614
0
        {
2615
0
            oSRS.SetGeogCS("Coordinate System imported from GRIB file", nullptr,
2616
0
                           "GRS80", 6378137., 298.257222101);
2617
0
        }
2618
63
        else
2619
63
        {
2620
63
            oSRS.SetGeogCS("Coordinate System imported from GRIB file", nullptr,
2621
63
                           "Spheroid imported from GRIB file", a, fInv);
2622
63
        }
2623
63
    }
2624
2625
217
    if (meta->gds.projType == GS3_ROTATED_LATLON)
2626
67
    {
2627
67
        oSRS.SetDerivedGeogCRSWithPoleRotationGRIBConvention(
2628
67
            oSRS.GetName(), meta->gds.southLat, Lon360to180(meta->gds.southLon),
2629
67
            meta->gds.angleRotate);
2630
67
    }
2631
2632
217
    OGRSpatialReference oLL;  // Construct the "geographic" part of oSRS.
2633
217
    oLL.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2634
217
    oLL.CopyGeogCSFrom(&oSRS);
2635
2636
217
    double rMinX = 0.0;
2637
217
    double rMaxY = 0.0;
2638
217
    double rPixelSizeX = 0.0;
2639
217
    double rPixelSizeY = 0.0;
2640
217
    bool bError = false;
2641
217
    if (meta->gds.projType == GS3_ORTHOGRAPHIC)
2642
0
    {
2643
        // This is what should work, but it doesn't. Dx seems to have an
2644
        // inverse relation with pixel size.
2645
        // rMinX = -meta->gds.Dx * (meta->gds.Nx / 2);
2646
        // rMaxY = meta->gds.Dy * (meta->gds.Ny / 2);
2647
        // Hardcoded for now, assumption: GEOS projection, full disc (like MSG).
2648
0
        const double geosExtentInMeters = 11137496.552;
2649
0
        rMinX = -(geosExtentInMeters / 2);
2650
0
        rMaxY = geosExtentInMeters / 2;
2651
0
        rPixelSizeX = geosExtentInMeters / meta->gds.Nx;
2652
0
        rPixelSizeY = geosExtentInMeters / meta->gds.Ny;
2653
0
    }
2654
217
    else if (meta->gds.projType == GS3_TRANSVERSE_MERCATOR)
2655
0
    {
2656
0
        rMinX = meta->gds.x1;
2657
0
        rMaxY = meta->gds.y2;
2658
0
        rPixelSizeX = meta->gds.Dx;
2659
0
        rPixelSizeY = meta->gds.Dy;
2660
0
    }
2661
217
    else if (oSRS.IsProjected() && meta->gds.projType != GS3_ROTATED_LATLON)
2662
71
    {
2663
        // Longitude in degrees, to be transformed to meters (or degrees in
2664
        // case of latlon).
2665
71
        rMinX = Lon360to180(meta->gds.lon1);
2666
        // Latitude in degrees, to be transformed to meters.
2667
71
        double dfGridOriY = meta->gds.lat1;
2668
2669
71
        if (m_poSRS == nullptr || m_poLL == nullptr ||
2670
71
            !m_poSRS->IsSame(&oSRS) || !m_poLL->IsSame(&oLL))
2671
71
        {
2672
71
            m_poCT = std::unique_ptr<OGRCoordinateTransformation>(
2673
71
                OGRCreateCoordinateTransformation(&(oLL), &(oSRS)));
2674
71
        }
2675
2676
        // Transform it to meters.
2677
71
        if ((m_poCT != nullptr) && m_poCT->Transform(1, &rMinX, &dfGridOriY))
2678
4
        {
2679
4
            if (meta->gds.scan == GRIB2BIT_2)  // Y is minY, GDAL wants maxY.
2680
0
            {
2681
0
                const char *pszConfigOpt = CPLGetConfigOption(
2682
0
                    "GRIB_LATITUDE_OF_FIRST_GRID_POINT_IS_SOUTHERN_MOST",
2683
0
                    nullptr);
2684
0
                bool bLatOfFirstPointIsSouthernMost =
2685
0
                    !pszConfigOpt || CPLTestBool(pszConfigOpt);
2686
2687
                // Hack for a file called MANAL_2023030103.grb2 that
2688
                // uses LCC and has Latitude of false origin = 30
2689
                // Longitude of false origin = 140
2690
                // Latitude of 1st standard parallel = 60
2691
                // Latitude of 2nd standard parallel = 30
2692
                // but whose (meta->gds.lon1, meta->gds.lat1) qualifies the
2693
                // northern-most point of the grid and not the bottom-most one
2694
                // as it should given the scan == GRIB2BIT_2
2695
0
                if (!pszConfigOpt && meta->gds.projType == GS3_LAMBERT &&
2696
0
                    std::fabs(meta->gds.scaleLat1 - 60) <= 1e-8 &&
2697
0
                    std::fabs(meta->gds.scaleLat2 - 30) <= 1e-8 &&
2698
0
                    std::fabs(meta->gds.meshLat - 30) <= 1e-8 &&
2699
0
                    std::fabs(Lon360to180(meta->gds.orientLon) - 140) <= 1e-8)
2700
0
                {
2701
0
                    double dfXCenterProj = Lon360to180(meta->gds.orientLon);
2702
0
                    double dfYCenterProj = meta->gds.meshLat;
2703
0
                    if (m_poCT->Transform(1, &dfXCenterProj, &dfYCenterProj))
2704
0
                    {
2705
0
                        double dfXCenterGridNominal =
2706
0
                            rMinX + nRasterXSize * meta->gds.Dx / 2;
2707
0
                        double dfYCenterGridNominal =
2708
0
                            dfGridOriY + nRasterYSize * meta->gds.Dy / 2;
2709
0
                        double dfXCenterGridBuggy = dfXCenterGridNominal;
2710
0
                        double dfYCenterGridBuggy =
2711
0
                            dfGridOriY - nRasterYSize * meta->gds.Dy / 2;
2712
0
                        const auto SQR = [](double x) { return x * x; };
2713
0
                        if (SQR(dfXCenterGridBuggy - dfXCenterProj) +
2714
0
                                SQR(dfYCenterGridBuggy - dfYCenterProj) <
2715
0
                            SQR(10) *
2716
0
                                (SQR(dfXCenterGridNominal - dfXCenterProj) +
2717
0
                                 SQR(dfYCenterGridNominal - dfYCenterProj)))
2718
0
                        {
2719
0
                            CPLError(
2720
0
                                CE_Warning, CPLE_AppDefined,
2721
0
                                "Likely buggy grid registration for GRIB2 "
2722
0
                                "product: heuristics shows that the "
2723
0
                                "latitudeOfFirstGridPoint is likely to qualify "
2724
0
                                "the latitude of the northern-most grid point "
2725
0
                                "instead of the southern-most grid point as "
2726
0
                                "expected. Please report to data producer. "
2727
0
                                "This heuristics can be disabled by setting "
2728
0
                                "the "
2729
0
                                "GRIB_LATITUDE_OF_FIRST_GRID_POINT_IS_SOUTHERN_"
2730
0
                                "MOST configuration option to YES.");
2731
0
                            bLatOfFirstPointIsSouthernMost = false;
2732
0
                        }
2733
0
                    }
2734
0
                }
2735
0
                if (bLatOfFirstPointIsSouthernMost)
2736
0
                {
2737
                    // -1 because we GDAL needs the coordinates of the centre of
2738
                    // the pixel.
2739
0
                    rMaxY = dfGridOriY + (meta->gds.Ny - 1) * meta->gds.Dy;
2740
0
                }
2741
0
                else
2742
0
                {
2743
0
                    rMaxY = dfGridOriY;
2744
0
                }
2745
0
            }
2746
4
            else
2747
4
            {
2748
4
                rMaxY = dfGridOriY;
2749
4
            }
2750
4
            rPixelSizeX = meta->gds.Dx;
2751
4
            rPixelSizeY = meta->gds.Dy;
2752
4
        }
2753
67
        else
2754
67
        {
2755
67
            rMinX = 0.0;
2756
            // rMaxY = 0.0;
2757
2758
67
            rPixelSizeX = 1.0;
2759
67
            rPixelSizeY = -1.0;
2760
2761
67
            bError = true;
2762
67
            CPLError(CE_Warning, CPLE_AppDefined,
2763
67
                     "Unable to perform coordinate transformations, so the "
2764
67
                     "correct projected geotransform could not be deduced "
2765
67
                     "from the lat/long control points.  "
2766
67
                     "Defaulting to ungeoreferenced.");
2767
67
        }
2768
71
    }
2769
146
    else
2770
146
    {
2771
        // Longitude in degrees, to be transformed to meters (or degrees in
2772
        // case of latlon).
2773
146
        rMinX = meta->gds.lon1;
2774
        // Latitude in degrees, to be transformed to meters.
2775
146
        rMaxY = meta->gds.lat1;
2776
2777
146
        double rMinY = meta->gds.lat2;
2778
146
        double rMaxX = meta->gds.lon2;
2779
146
        if (meta->gds.lat2 > rMaxY)
2780
59
        {
2781
59
            rMaxY = meta->gds.lat2;
2782
59
            rMinY = meta->gds.lat1;
2783
59
        }
2784
2785
146
        if (meta->gds.Nx == 1)
2786
0
            rPixelSizeX = meta->gds.Dx;
2787
146
        else if (meta->gds.lon1 > meta->gds.lon2)
2788
58
            rPixelSizeX = (360.0 - (meta->gds.lon1 - meta->gds.lon2)) /
2789
58
                          (meta->gds.Nx - 1);
2790
88
        else
2791
88
            rPixelSizeX =
2792
88
                (meta->gds.lon2 - meta->gds.lon1) / (meta->gds.Nx - 1);
2793
2794
146
        if (meta->gds.Ny == 1)
2795
19
            rPixelSizeY = meta->gds.Dy;
2796
127
        else
2797
127
            rPixelSizeY = (rMaxY - rMinY) / (meta->gds.Ny - 1);
2798
2799
        // Do some sanity checks for cases that can't be handled by the above
2800
        // pixel size corrections. GRIB1 has a minimum precision of 0.001
2801
        // for latitudes and longitudes, so we'll allow a bit higher than that.
2802
146
        if (rPixelSizeX < 0 || fabs(rPixelSizeX - meta->gds.Dx) > 0.002)
2803
122
            rPixelSizeX = meta->gds.Dx;
2804
2805
146
        if (rPixelSizeY < 0 || fabs(rPixelSizeY - meta->gds.Dy) > 0.002)
2806
80
            rPixelSizeY = meta->gds.Dy;
2807
2808
        // GRIB2 files have longitudes in the [0-360] range
2809
        // Shift them to the traditional [-180,180] longitude range
2810
        // See https://github.com/OSGeo/gdal/issues/4524
2811
146
        if ((rMinX + rPixelSizeX >= 180 || rMaxX - rPixelSizeX >= 180) &&
2812
146
            CPLTestBool(
2813
100
                CPLGetConfigOption("GRIB_ADJUST_LONGITUDE_RANGE", "YES")))
2814
100
        {
2815
100
            if (rPixelSizeX * nRasterXSize > 360 + rPixelSizeX / 4)
2816
62
                CPLDebug("GRIB", "Cannot properly handle GRIB2 files with "
2817
62
                                 "overlaps and 0-360 longitudes");
2818
38
            else if (rMinX == 180)
2819
0
            {
2820
                // Case of https://github.com/OSGeo/gdal/issues/10655
2821
0
                CPLDebug("GRIB", "Shifting longitudes from %lf:%lf to %lf:%lf",
2822
0
                         rMinX, rMaxX, -180.0, Lon360to180(rMaxX));
2823
0
                rMinX = -180;
2824
0
            }
2825
38
            else if (fabs(360 - rPixelSizeX * nRasterXSize) < rPixelSizeX / 4 &&
2826
38
                     rMinX <= 180 && meta->gds.projType == GS3_LATLON)
2827
0
            {
2828
                // Find the first row number east of the antimeridian
2829
0
                const int nSplitAndSwapColumnCandidate =
2830
0
                    static_cast<int>(ceil((180 - rMinX) / rPixelSizeX));
2831
0
                if (nSplitAndSwapColumnCandidate < nRasterXSize)
2832
0
                {
2833
0
                    nSplitAndSwapColumn = nSplitAndSwapColumnCandidate;
2834
0
                    CPLDebug("GRIB",
2835
0
                             "Rewrapping around the antimeridian at column %d",
2836
0
                             nSplitAndSwapColumn);
2837
0
                    rMinX = -180;
2838
0
                }
2839
0
            }
2840
38
            else if (Lon360to180(rMinX) > Lon360to180(rMaxX))
2841
20
            {
2842
20
                CPLDebug("GRIB", "GRIB with 0-360 longitudes spanning across "
2843
20
                                 "the antimeridian");
2844
20
                rMinX = Lon360to180(rMinX);
2845
20
            }
2846
18
            else
2847
18
            {
2848
18
                CPLDebug("GRIB", "Shifting longitudes from %lf:%lf to %lf:%lf",
2849
18
                         rMinX, rMaxX, Lon360to180(rMinX), Lon360to180(rMaxX));
2850
18
                rMinX = Lon360to180(rMinX);
2851
18
            }
2852
100
        }
2853
146
    }
2854
2855
    // https://gdal.org/user/raster_data_model.html :
2856
    //   we need the top left corner of the top left pixel.
2857
    //   At the moment we have the center of the pixel.
2858
217
    rMinX -= rPixelSizeX / 2;
2859
217
    rMaxY += rPixelSizeY / 2;
2860
2861
217
    adfGeoTransform[0] = rMinX;
2862
217
    adfGeoTransform[3] = rMaxY;
2863
217
    adfGeoTransform[1] = rPixelSizeX;
2864
217
    adfGeoTransform[5] = -rPixelSizeY;
2865
2866
217
    if (bError)
2867
67
        m_poSRS.reset();
2868
150
    else
2869
150
        m_poSRS.reset(oSRS.Clone());
2870
217
    m_poLL = std::unique_ptr<OGRSpatialReference>(oLL.Clone());
2871
217
}
2872
2873
/************************************************************************/
2874
/*                       GDALDeregister_GRIB()                          */
2875
/************************************************************************/
2876
2877
static void GDALDeregister_GRIB(GDALDriver *)
2878
0
{
2879
0
    if (hGRIBMutex != nullptr)
2880
0
    {
2881
0
        MetanameCleanup();
2882
0
        CPLDestroyMutex(hGRIBMutex);
2883
0
        hGRIBMutex = nullptr;
2884
0
    }
2885
0
}
2886
2887
/************************************************************************/
2888
/*                          GDALGRIBDriver                              */
2889
/************************************************************************/
2890
2891
class GDALGRIBDriver : public GDALDriver
2892
{
2893
    std::mutex m_oMutex{};
2894
    bool m_bHasFullInitMetadata = false;
2895
    void InitializeMetadata();
2896
2897
  public:
2898
2
    GDALGRIBDriver() = default;
2899
2900
    char **GetMetadata(const char *pszDomain = "") override;
2901
    const char *GetMetadataItem(const char *pszName,
2902
                                const char *pszDomain) override;
2903
};
2904
2905
/************************************************************************/
2906
/*                         InitializeMetadata()                         */
2907
/************************************************************************/
2908
2909
void GDALGRIBDriver::InitializeMetadata()
2910
0
{
2911
0
    {
2912
        // Defer until necessary the setting of the CreationOptionList
2913
        // to let a chance to JPEG2000 drivers to have been loaded.
2914
0
        if (!m_bHasFullInitMetadata)
2915
0
        {
2916
0
            m_bHasFullInitMetadata = true;
2917
2918
0
            std::vector<CPLString> aosJ2KDrivers;
2919
0
            for (size_t i = 0; i < CPL_ARRAYSIZE(apszJ2KDrivers); i++)
2920
0
            {
2921
0
                if (GDALGetDriverByName(apszJ2KDrivers[i]) != nullptr)
2922
0
                {
2923
0
                    aosJ2KDrivers.push_back(apszJ2KDrivers[i]);
2924
0
                }
2925
0
            }
2926
2927
0
            CPLString osCreationOptionList(
2928
0
                "<CreationOptionList>"
2929
0
                "   <Option name='DATA_ENCODING' type='string-select' "
2930
0
                "default='AUTO' "
2931
0
                "description='How data is encoded internally'>"
2932
0
                "       <Value>AUTO</Value>"
2933
0
                "       <Value>SIMPLE_PACKING</Value>"
2934
0
                "       <Value>COMPLEX_PACKING</Value>"
2935
0
                "       <Value>IEEE_FLOATING_POINT</Value>");
2936
0
            if (GDALGetDriverByName("PNG") != nullptr)
2937
0
                osCreationOptionList += "       <Value>PNG</Value>";
2938
0
            if (!aosJ2KDrivers.empty())
2939
0
                osCreationOptionList += "       <Value>JPEG2000</Value>";
2940
0
            osCreationOptionList +=
2941
0
                "   </Option>"
2942
0
                "   <Option name='NBITS' type='int' default='0' "
2943
0
                "description='Number of bits per value'/>"
2944
0
                "   <Option name='DECIMAL_SCALE_FACTOR' type='int' default='0' "
2945
0
                "description='Value such that raw values are multiplied by "
2946
0
                "10^DECIMAL_SCALE_FACTOR before integer encoding'/>"
2947
0
                "   <Option name='SPATIAL_DIFFERENCING_ORDER' type='int' "
2948
0
                "default='0' "
2949
0
                "description='Order of spatial difference' min='0' max='2'/>";
2950
0
            if (!aosJ2KDrivers.empty())
2951
0
            {
2952
0
                osCreationOptionList +=
2953
0
                    "   <Option name='COMPRESSION_RATIO' type='int' "
2954
0
                    "default='1' min='1' max='100' "
2955
0
                    "description='N:1 target compression ratio for JPEG2000'/>"
2956
0
                    "   <Option name='JPEG2000_DRIVER' type='string-select' "
2957
0
                    "description='Explicitly select a JPEG2000 driver'>";
2958
0
                for (size_t i = 0; i < aosJ2KDrivers.size(); i++)
2959
0
                {
2960
0
                    osCreationOptionList +=
2961
0
                        "       <Value>" + aosJ2KDrivers[i] + "</Value>";
2962
0
                }
2963
0
                osCreationOptionList += "   </Option>";
2964
0
            }
2965
0
            osCreationOptionList +=
2966
0
                "   <Option name='DISCIPLINE' type='int' "
2967
0
                "description='Discipline of the processed data'/>"
2968
0
                "   <Option name='IDS' type='string' "
2969
0
                "description='String equivalent to the GRIB_IDS metadata "
2970
0
                "item'/>"
2971
0
                "   <Option name='IDS_CENTER' type='int' "
2972
0
                "description='Originating/generating center'/>"
2973
0
                "   <Option name='IDS_SUBCENTER' type='int' "
2974
0
                "description='Originating/generating subcenter'/>"
2975
0
                "   <Option name='IDS_MASTER_TABLE' type='int' "
2976
0
                "description='GRIB master tables version number'/>"
2977
0
                "   <Option name='IDS_SIGNF_REF_TIME' type='int' "
2978
0
                "description='Significance of Reference Time'/>"
2979
0
                "   <Option name='IDS_REF_TIME' type='string' "
2980
0
                "description='Reference time as YYYY-MM-DDTHH:MM:SSZ'/>"
2981
0
                "   <Option name='IDS_PROD_STATUS' type='int' "
2982
0
                "description='Production Status of Processed data'/>"
2983
0
                "   <Option name='IDS_TYPE' type='int' "
2984
0
                "description='Type of processed data'/>"
2985
0
                "   <Option name='PDS_PDTN' type='int' "
2986
0
                "description='Product Definition Template Number'/>"
2987
0
                "   <Option name='PDS_TEMPLATE_NUMBERS' type='string' "
2988
0
                "description='Product definition template raw numbers'/>"
2989
0
                "   <Option name='PDS_TEMPLATE_ASSEMBLED_VALUES' type='string' "
2990
0
                "description='Product definition template assembled values'/>"
2991
0
                "   <Option name='INPUT_UNIT' type='string' "
2992
0
                "description='Unit of input values. Only for temperatures. C "
2993
0
                "or K'/>"
2994
0
                "   <Option name='BAND_*' type='string' "
2995
0
                "description='Override options at band level'/>"
2996
0
                "</CreationOptionList>";
2997
2998
0
            SetMetadataItem(GDAL_DMD_CREATIONOPTIONLIST, osCreationOptionList);
2999
0
        }
3000
0
    }
3001
0
}
3002
3003
/************************************************************************/
3004
/*                            GetMetadata()                             */
3005
/************************************************************************/
3006
3007
char **GDALGRIBDriver::GetMetadata(const char *pszDomain)
3008
0
{
3009
0
    std::lock_guard oLock(m_oMutex);
3010
0
    if (pszDomain == nullptr || EQUAL(pszDomain, ""))
3011
0
    {
3012
0
        InitializeMetadata();
3013
0
    }
3014
0
    return GDALDriver::GetMetadata(pszDomain);
3015
0
}
3016
3017
/************************************************************************/
3018
/*                          GetMetadataItem()                           */
3019
/************************************************************************/
3020
3021
const char *GDALGRIBDriver::GetMetadataItem(const char *pszName,
3022
                                            const char *pszDomain)
3023
23.8k
{
3024
23.8k
    std::lock_guard oLock(m_oMutex);
3025
23.8k
    if (pszDomain == nullptr || EQUAL(pszDomain, ""))
3026
23.8k
    {
3027
        // Defer until necessary the setting of the CreationOptionList
3028
        // to let a chance to JPEG2000 drivers to have been loaded.
3029
23.8k
        if (EQUAL(pszName, GDAL_DMD_CREATIONOPTIONLIST))
3030
0
            InitializeMetadata();
3031
23.8k
    }
3032
23.8k
    return GDALDriver::GetMetadataItem(pszName, pszDomain);
3033
23.8k
}
3034
3035
/************************************************************************/
3036
/*                         GDALRegister_GRIB()                          */
3037
/************************************************************************/
3038
3039
void GDALRegister_GRIB()
3040
3041
2
{
3042
2
    if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
3043
0
        return;
3044
3045
2
    GDALDriver *poDriver = new GDALGRIBDriver();
3046
2
    GRIBDriverSetCommonMetadata(poDriver);
3047
3048
2
    poDriver->pfnOpen = GRIBDataset::Open;
3049
2
    poDriver->pfnCreateCopy = GRIBDataset::CreateCopy;
3050
2
    poDriver->pfnUnloadDriver = GDALDeregister_GRIB;
3051
3052
2
#ifdef USE_AEC
3053
2
    poDriver->SetMetadataItem("HAVE_AEC", "YES");
3054
2
#endif
3055
3056
2
    GetGDALDriverManager()->RegisterDriver(poDriver);
3057
2
}