Coverage Report

Created: 2025-06-09 07:43

/src/gdal/frmts/raw/snodasdataset.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  SNODAS driver
4
 * Purpose:  Implementation of SNODASDataset
5
 * Author:   Even Rouault, <even dot rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2011, Even Rouault <even dot rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "cpl_string.h"
14
#include "gdal_frmts.h"
15
#include "ogr_srs_api.h"
16
#include "rawdataset.h"
17
18
/************************************************************************/
19
/* ==================================================================== */
20
/*                            SNODASDataset                             */
21
/* ==================================================================== */
22
/************************************************************************/
23
24
class SNODASRasterBand;
25
26
class SNODASDataset final : public RawDataset
27
{
28
    CPLString osDataFilename{};
29
    bool bGotTransform;
30
    double adfGeoTransform[6];
31
    bool bHasNoData;
32
    double dfNoData;
33
    bool bHasMin;
34
    double dfMin;
35
    int bHasMax;
36
    double dfMax;
37
    OGRSpatialReference m_oSRS{};
38
39
    friend class SNODASRasterBand;
40
41
    CPL_DISALLOW_COPY_ASSIGN(SNODASDataset)
42
43
    CPLErr Close() override;
44
45
  public:
46
    SNODASDataset();
47
    ~SNODASDataset() override;
48
49
    CPLErr GetGeoTransform(double *padfTransform) override;
50
51
    const OGRSpatialReference *GetSpatialRef() const override
52
0
    {
53
0
        return &m_oSRS;
54
0
    }
55
56
    char **GetFileList() override;
57
58
    static GDALDataset *Open(GDALOpenInfo *);
59
    static int Identify(GDALOpenInfo *);
60
};
61
62
/************************************************************************/
63
/* ==================================================================== */
64
/*                            SNODASRasterBand                          */
65
/* ==================================================================== */
66
/************************************************************************/
67
68
class SNODASRasterBand final : public RawRasterBand
69
{
70
    CPL_DISALLOW_COPY_ASSIGN(SNODASRasterBand)
71
72
  public:
73
    SNODASRasterBand(VSILFILE *fpRaw, int nXSize, int nYSize);
74
75
    ~SNODASRasterBand() override
76
0
    {
77
0
    }
78
79
    double GetNoDataValue(int *pbSuccess = nullptr) override;
80
    double GetMinimum(int *pbSuccess = nullptr) override;
81
    double GetMaximum(int *pbSuccess = nullptr) override;
82
};
83
84
/************************************************************************/
85
/*                         SNODASRasterBand()                           */
86
/************************************************************************/
87
88
SNODASRasterBand::SNODASRasterBand(VSILFILE *fpRawIn, int nXSize, int nYSize)
89
0
    : RawRasterBand(fpRawIn, 0, 2, nXSize * 2, GDT_Int16, !CPL_IS_LSB, nXSize,
90
0
                    nYSize, RawRasterBand::OwnFP::YES)
91
0
{
92
0
}
93
94
/************************************************************************/
95
/*                          GetNoDataValue()                            */
96
/************************************************************************/
97
98
double SNODASRasterBand::GetNoDataValue(int *pbSuccess)
99
0
{
100
0
    SNODASDataset *poGDS = reinterpret_cast<SNODASDataset *>(poDS);
101
0
    if (pbSuccess)
102
0
        *pbSuccess = poGDS->bHasNoData;
103
104
0
    if (poGDS->bHasNoData)
105
0
        return poGDS->dfNoData;
106
107
0
    return RawRasterBand::GetNoDataValue(pbSuccess);
108
0
}
109
110
/************************************************************************/
111
/*                            GetMinimum()                              */
112
/************************************************************************/
113
114
double SNODASRasterBand::GetMinimum(int *pbSuccess)
115
0
{
116
0
    SNODASDataset *poGDS = reinterpret_cast<SNODASDataset *>(poDS);
117
0
    if (pbSuccess)
118
0
        *pbSuccess = poGDS->bHasMin;
119
120
0
    if (poGDS->bHasMin)
121
0
        return poGDS->dfMin;
122
123
0
    return RawRasterBand::GetMinimum(pbSuccess);
124
0
}
125
126
/************************************************************************/
127
/*                            GetMaximum()                             */
128
/************************************************************************/
129
130
double SNODASRasterBand::GetMaximum(int *pbSuccess)
131
0
{
132
0
    SNODASDataset *poGDS = reinterpret_cast<SNODASDataset *>(poDS);
133
0
    if (pbSuccess)
134
0
        *pbSuccess = poGDS->bHasMax;
135
136
0
    if (poGDS->bHasMax)
137
0
        return poGDS->dfMax;
138
139
0
    return RawRasterBand::GetMaximum(pbSuccess);
140
0
}
141
142
/************************************************************************/
143
/* ==================================================================== */
144
/*                            SNODASDataset                             */
145
/* ==================================================================== */
146
/************************************************************************/
147
148
/************************************************************************/
149
/*                           SNODASDataset()                            */
150
/************************************************************************/
151
152
SNODASDataset::SNODASDataset()
153
0
    : bGotTransform(false), bHasNoData(false), dfNoData(0.0), bHasMin(false),
154
0
      dfMin(0.0), bHasMax(false), dfMax(0.0)
155
0
{
156
0
    m_oSRS.SetFromUserInput(SRS_WKT_WGS84_LAT_LONG);
157
0
    m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
158
159
0
    adfGeoTransform[0] = 0.0;
160
0
    adfGeoTransform[1] = 1.0;
161
0
    adfGeoTransform[2] = 0.0;
162
0
    adfGeoTransform[3] = 0.0;
163
0
    adfGeoTransform[4] = 0.0;
164
0
    adfGeoTransform[5] = 1.0;
165
0
}
166
167
/************************************************************************/
168
/*                           ~SNODASDataset()                           */
169
/************************************************************************/
170
171
SNODASDataset::~SNODASDataset()
172
173
0
{
174
0
    SNODASDataset::Close();
175
0
}
176
177
/************************************************************************/
178
/*                              Close()                                 */
179
/************************************************************************/
180
181
CPLErr SNODASDataset::Close()
182
0
{
183
0
    CPLErr eErr = CE_None;
184
0
    if (nOpenFlags != OPEN_FLAGS_CLOSED)
185
0
    {
186
0
        if (SNODASDataset::FlushCache(true) != CE_None)
187
0
            eErr = CE_Failure;
188
189
0
        if (GDALPamDataset::Close() != CE_None)
190
0
            eErr = CE_Failure;
191
0
    }
192
0
    return eErr;
193
0
}
194
195
/************************************************************************/
196
/*                          GetGeoTransform()                           */
197
/************************************************************************/
198
199
CPLErr SNODASDataset::GetGeoTransform(double *padfTransform)
200
201
0
{
202
0
    if (bGotTransform)
203
0
    {
204
0
        memcpy(padfTransform, adfGeoTransform, sizeof(double) * 6);
205
0
        return CE_None;
206
0
    }
207
208
0
    return GDALPamDataset::GetGeoTransform(padfTransform);
209
0
}
210
211
/************************************************************************/
212
/*                            GetFileList()                             */
213
/************************************************************************/
214
215
char **SNODASDataset::GetFileList()
216
217
0
{
218
0
    char **papszFileList = GDALPamDataset::GetFileList();
219
220
0
    papszFileList = CSLAddString(papszFileList, osDataFilename);
221
222
0
    return papszFileList;
223
0
}
224
225
/************************************************************************/
226
/*                            Identify()                                */
227
/************************************************************************/
228
229
int SNODASDataset::Identify(GDALOpenInfo *poOpenInfo)
230
177k
{
231
177k
    if (poOpenInfo->nHeaderBytes == 0)
232
173k
        return FALSE;
233
234
3.57k
    return STARTS_WITH_CI(reinterpret_cast<char *>(poOpenInfo->pabyHeader),
235
177k
                          "Format version: NOHRSC GIS/RS raster file v1.1");
236
177k
}
237
238
/************************************************************************/
239
/*                                Open()                                */
240
/************************************************************************/
241
242
GDALDataset *SNODASDataset::Open(GDALOpenInfo *poOpenInfo)
243
244
80
{
245
80
    if (!Identify(poOpenInfo) || poOpenInfo->fpL == nullptr)
246
0
        return nullptr;
247
248
    /* -------------------------------------------------------------------- */
249
    /*      Confirm the requested access is supported.                      */
250
    /* -------------------------------------------------------------------- */
251
80
    if (poOpenInfo->eAccess == GA_Update)
252
0
    {
253
0
        ReportUpdateNotSupportedByDriver("SNODAS");
254
0
        return nullptr;
255
0
    }
256
257
80
    int nRows = -1;
258
80
    int nCols = -1;
259
80
    CPLString osDataFilename;
260
80
    bool bIsInteger = false;
261
80
    bool bIs2Bytes = false;
262
80
    double dfNoData = 0;
263
80
    bool bHasNoData = false;
264
80
    double dfMin = 0;
265
80
    bool bHasMin = false;
266
80
    double dfMax = 0;
267
80
    bool bHasMax = false;
268
80
    double dfMinX = 0.0;
269
80
    double dfMinY = 0.0;
270
80
    double dfMaxX = 0.0;
271
80
    double dfMaxY = 0.0;
272
80
    bool bHasMinX = false;
273
80
    bool bHasMinY = false;
274
80
    bool bHasMaxX = false;
275
80
    bool bHasMaxY = false;
276
80
    bool bNotProjected = false;
277
80
    bool bIsWGS84 = false;
278
80
    CPLString osDataUnits;
279
80
    CPLString osDescription;
280
80
    int nStartYear = -1;
281
80
    int nStartMonth = -1;
282
80
    int nStartDay = -1;
283
80
    int nStartHour = -1;
284
80
    int nStartMinute = -1;
285
80
    int nStartSecond = -1;
286
80
    int nStopYear = -1;
287
80
    int nStopMonth = -1;
288
80
    int nStopDay = -1;
289
80
    int nStopHour = -1;
290
80
    int nStopMinute = -1;
291
80
    int nStopSecond = -1;
292
293
80
    const char *pszLine = nullptr;
294
49.6k
    while ((pszLine = CPLReadLine2L(poOpenInfo->fpL, 1024, nullptr)) != nullptr)
295
49.5k
    {
296
49.5k
        char **papszTokens =
297
49.5k
            CSLTokenizeStringComplex(pszLine, ":", TRUE, FALSE);
298
49.5k
        if (CSLCount(papszTokens) != 2)
299
11.3k
        {
300
11.3k
            CSLDestroy(papszTokens);
301
11.3k
            continue;
302
11.3k
        }
303
38.1k
        if (papszTokens[1][0] == ' ')
304
36.9k
            memmove(papszTokens[1], papszTokens[1] + 1,
305
36.9k
                    strlen(papszTokens[1] + 1) + 1);
306
307
38.1k
        if (EQUAL(papszTokens[0], "Data file pathname"))
308
844
        {
309
844
            osDataFilename = papszTokens[1];
310
844
        }
311
37.3k
        else if (EQUAL(papszTokens[0], "Description"))
312
714
        {
313
714
            osDescription = papszTokens[1];
314
714
        }
315
36.6k
        else if (EQUAL(papszTokens[0], "Data units"))
316
611
        {
317
611
            osDataUnits = papszTokens[1];
318
611
        }
319
320
35.9k
        else if (EQUAL(papszTokens[0], "Start year"))
321
279
            nStartYear = atoi(papszTokens[1]);
322
35.7k
        else if (EQUAL(papszTokens[0], "Start month"))
323
280
            nStartMonth = atoi(papszTokens[1]);
324
35.4k
        else if (EQUAL(papszTokens[0], "Start day"))
325
398
            nStartDay = atoi(papszTokens[1]);
326
35.0k
        else if (EQUAL(papszTokens[0], "Start hour"))
327
338
            nStartHour = atoi(papszTokens[1]);
328
34.7k
        else if (EQUAL(papszTokens[0], " Start minute"))
329
0
            nStartMinute = atoi(papszTokens[1]);
330
34.7k
        else if (EQUAL(papszTokens[0], "Start second"))
331
161
            nStartSecond = atoi(papszTokens[1]);
332
333
34.5k
        else if (EQUAL(papszTokens[0], "Stop year"))
334
103
            nStopYear = atoi(papszTokens[1]);
335
34.4k
        else if (EQUAL(papszTokens[0], "Stop month"))
336
181
            nStopMonth = atoi(papszTokens[1]);
337
34.2k
        else if (EQUAL(papszTokens[0], "Stop day"))
338
511
            nStopDay = atoi(papszTokens[1]);
339
33.7k
        else if (EQUAL(papszTokens[0], "Stop hour"))
340
355
            nStopHour = atoi(papszTokens[1]);
341
33.3k
        else if (EQUAL(papszTokens[0], "Stop minute"))
342
617
            nStopMinute = atoi(papszTokens[1]);
343
32.7k
        else if (EQUAL(papszTokens[0], "Stop second"))
344
614
            nStopSecond = atoi(papszTokens[1]);
345
346
32.1k
        else if (EQUAL(papszTokens[0], "Number of columns"))
347
762
        {
348
762
            nCols = atoi(papszTokens[1]);
349
762
        }
350
31.4k
        else if (EQUAL(papszTokens[0], "Number of rows"))
351
441
        {
352
441
            nRows = atoi(papszTokens[1]);
353
441
        }
354
30.9k
        else if (EQUAL(papszTokens[0], "Data type"))
355
1.18k
        {
356
1.18k
            bIsInteger = EQUAL(papszTokens[1], "integer");
357
1.18k
        }
358
29.7k
        else if (EQUAL(papszTokens[0], "Data bytes per pixel"))
359
935
        {
360
935
            bIs2Bytes = EQUAL(papszTokens[1], "2");
361
935
        }
362
28.8k
        else if (EQUAL(papszTokens[0], "Projected"))
363
398
        {
364
398
            bNotProjected = EQUAL(papszTokens[1], "no");
365
398
        }
366
28.4k
        else if (EQUAL(papszTokens[0], "Horizontal datum"))
367
393
        {
368
393
            bIsWGS84 = EQUAL(papszTokens[1], "WGS84");
369
393
        }
370
28.0k
        else if (EQUAL(papszTokens[0], "No data value"))
371
748
        {
372
748
            bHasNoData = true;
373
748
            dfNoData = CPLAtofM(papszTokens[1]);
374
748
        }
375
27.2k
        else if (EQUAL(papszTokens[0], "Minimum data value"))
376
716
        {
377
716
            bHasMin = true;
378
716
            dfMin = CPLAtofM(papszTokens[1]);
379
716
        }
380
26.5k
        else if (EQUAL(papszTokens[0], "Maximum data value"))
381
726
        {
382
726
            bHasMax = true;
383
726
            dfMax = CPLAtofM(papszTokens[1]);
384
726
        }
385
25.8k
        else if (EQUAL(papszTokens[0], "Minimum x-axis coordinate"))
386
120
        {
387
120
            bHasMinX = true;
388
120
            dfMinX = CPLAtofM(papszTokens[1]);
389
120
        }
390
25.7k
        else if (EQUAL(papszTokens[0], "Minimum y-axis coordinate"))
391
236
        {
392
236
            bHasMinY = true;
393
236
            dfMinY = CPLAtofM(papszTokens[1]);
394
236
        }
395
25.4k
        else if (EQUAL(papszTokens[0], "Maximum x-axis coordinate"))
396
134
        {
397
134
            bHasMaxX = true;
398
134
            dfMaxX = CPLAtofM(papszTokens[1]);
399
134
        }
400
25.3k
        else if (EQUAL(papszTokens[0], "Maximum y-axis coordinate"))
401
123
        {
402
123
            bHasMaxY = true;
403
123
            dfMaxY = CPLAtofM(papszTokens[1]);
404
123
        }
405
406
38.1k
        CSLDestroy(papszTokens);
407
38.1k
    }
408
409
80
    CPL_IGNORE_RET_VAL(VSIFCloseL(poOpenInfo->fpL));
410
80
    poOpenInfo->fpL = nullptr;
411
412
    /* -------------------------------------------------------------------- */
413
    /*      Did we get the required keywords?  If not we return with        */
414
    /*      this never having been considered to be a match. This isn't     */
415
    /*      an error!                                                       */
416
    /* -------------------------------------------------------------------- */
417
80
    if (nRows == -1 || nCols == -1 || !bIsInteger || !bIs2Bytes)
418
51
        return nullptr;
419
420
29
    if (!bNotProjected || !bIsWGS84)
421
14
        return nullptr;
422
423
15
    if (osDataFilename.empty())
424
0
        return nullptr;
425
426
15
    if (!GDALCheckDatasetDimensions(nCols, nRows))
427
0
        return nullptr;
428
429
    /* -------------------------------------------------------------------- */
430
    /*      Open target binary file.                                        */
431
    /* -------------------------------------------------------------------- */
432
15
    const std::string osPath = CPLGetPathSafe(poOpenInfo->pszFilename);
433
15
    osDataFilename =
434
15
        CPLFormFilenameSafe(osPath.c_str(), osDataFilename, nullptr);
435
436
15
    VSILFILE *fpRaw = VSIFOpenL(osDataFilename, "rb");
437
438
15
    if (fpRaw == nullptr)
439
15
        return nullptr;
440
441
    /* -------------------------------------------------------------------- */
442
    /*      Create a corresponding GDALDataset.                             */
443
    /* -------------------------------------------------------------------- */
444
0
    auto poDS = std::make_unique<SNODASDataset>();
445
446
0
    poDS->nRasterXSize = nCols;
447
0
    poDS->nRasterYSize = nRows;
448
0
    poDS->osDataFilename = std::move(osDataFilename);
449
0
    poDS->bHasNoData = bHasNoData;
450
0
    poDS->dfNoData = dfNoData;
451
0
    poDS->bHasMin = bHasMin;
452
0
    poDS->dfMin = dfMin;
453
0
    poDS->bHasMax = bHasMax;
454
0
    poDS->dfMax = dfMax;
455
0
    if (bHasMinX && bHasMinY && bHasMaxX && bHasMaxY)
456
0
    {
457
0
        poDS->bGotTransform = true;
458
0
        poDS->adfGeoTransform[0] = dfMinX;
459
0
        poDS->adfGeoTransform[1] = (dfMaxX - dfMinX) / nCols;
460
0
        poDS->adfGeoTransform[2] = 0.0;
461
0
        poDS->adfGeoTransform[3] = dfMaxY;
462
0
        poDS->adfGeoTransform[4] = 0.0;
463
0
        poDS->adfGeoTransform[5] = -(dfMaxY - dfMinY) / nRows;
464
0
    }
465
466
0
    if (!osDescription.empty())
467
0
        poDS->SetMetadataItem("Description", osDescription);
468
0
    if (!osDataUnits.empty())
469
0
        poDS->SetMetadataItem("Data_Units", osDataUnits);
470
0
    if (nStartYear != -1 && nStartMonth != -1 && nStartDay != -1 &&
471
0
        nStartHour != -1 && nStartMinute != -1 && nStartSecond != -1)
472
0
        poDS->SetMetadataItem(
473
0
            "Start_Date",
474
0
            CPLSPrintf("%04d/%02d/%02d %02d:%02d:%02d", nStartYear, nStartMonth,
475
0
                       nStartDay, nStartHour, nStartMinute, nStartSecond));
476
0
    if (nStopYear != -1 && nStopMonth != -1 && nStopDay != -1 &&
477
0
        nStopHour != -1 && nStopMinute != -1 && nStopSecond != -1)
478
0
        poDS->SetMetadataItem("Stop_Date",
479
0
                              CPLSPrintf("%04d/%02d/%02d %02d:%02d:%02d",
480
0
                                         nStopYear, nStopMonth, nStopDay,
481
0
                                         nStopHour, nStopMinute, nStopSecond));
482
483
    /* -------------------------------------------------------------------- */
484
    /*      Create band information objects.                                */
485
    /* -------------------------------------------------------------------- */
486
0
    auto poBand = std::make_unique<SNODASRasterBand>(fpRaw, nCols, nRows);
487
0
    if (!poBand->IsValid())
488
0
        return nullptr;
489
0
    poDS->SetBand(1, std::move(poBand));
490
491
    /* -------------------------------------------------------------------- */
492
    /*      Initialize any PAM information.                                 */
493
    /* -------------------------------------------------------------------- */
494
0
    poDS->SetDescription(poOpenInfo->pszFilename);
495
0
    poDS->TryLoadXML();
496
497
    /* -------------------------------------------------------------------- */
498
    /*      Check for overviews.                                            */
499
    /* -------------------------------------------------------------------- */
500
0
    poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename);
501
502
0
    return poDS.release();
503
0
}
504
505
/************************************************************************/
506
/*                       GDALRegister_SNODAS()                          */
507
/************************************************************************/
508
509
void GDALRegister_SNODAS()
510
511
2
{
512
2
    if (GDALGetDriverByName("SNODAS") != nullptr)
513
0
        return;
514
515
2
    GDALDriver *poDriver = new GDALDriver();
516
517
2
    poDriver->SetDescription("SNODAS");
518
2
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
519
2
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
520
2
                              "Snow Data Assimilation System");
521
2
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/snodas.html");
522
2
    poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "hdr");
523
2
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
524
525
2
    poDriver->pfnOpen = SNODASDataset::Open;
526
2
    poDriver->pfnIdentify = SNODASDataset::Identify;
527
528
2
    GetGDALDriverManager()->RegisterDriver(poDriver);
529
2
}