Coverage Report

Created: 2026-11-12 08:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/l1b/l1bdataset.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  NOAA Polar Orbiter Level 1b Dataset Reader (AVHRR)
4
 * Purpose:  Can read NOAA-9(F)-NOAA-17(M) AVHRR datasets
5
 * Author:   Andrey Kiselev, dron@ak4719.spb.edu
6
 *
7
 * Some format info at: http://www.sat.dundee.ac.uk/noaa1b.html
8
 *
9
 ******************************************************************************
10
 * Copyright (c) 2002, Andrey Kiselev <dron@ak4719.spb.edu>
11
 * Copyright (c) 2008-2013, Even Rouault <even dot rouault at spatialys.com>
12
 *
13
 * ----------------------------------------------------------------------------
14
 * Lagrange interpolation suitable for NOAA level 1B file formats.
15
 * Submitted by Andrew Brooks <arb@sat.dundee.ac.uk>
16
 *
17
 * SPDX-License-Identifier: MIT
18
 ****************************************************************************/
19
20
#include "cpl_string.h"
21
#include "gdal_frmts.h"
22
#include "gdal_pam.h"
23
#include "gdal_driver.h"
24
#include "gdal_drivermanager.h"
25
#include "gdal_openinfo.h"
26
#include "gdal_cpp_functions.h"
27
#include "ogr_srs_api.h"
28
29
#include <algorithm>
30
31
typedef enum
32
{                     // File formats
33
    L1B_NONE,         // Not a L1B format
34
    L1B_NOAA9,        // NOAA-9/14
35
    L1B_NOAA15,       // NOAA-15/METOP-2
36
    L1B_NOAA15_NOHDR  // NOAA-15/METOP-2 without ARS header
37
} L1BFileFormat;
38
39
typedef enum
40
{            // Spacecrafts:
41
    TIROSN,  // TIROS-N
42
             // NOAA are given a letter before launch and a number after launch
43
    NOAA6,   // NOAA-6(A)
44
    NOAAB,   // NOAA-B
45
    NOAA7,   // NOAA-7(C)
46
    NOAA8,   // NOAA-8(E)
47
    NOAA9_UNKNOWN,  // Some NOAA-18 and NOAA-19 HRPT are recognized like that...
48
    NOAA9,          // NOAA-9(F)
49
    NOAA10,         // NOAA-10(G)
50
    NOAA11,         // NOAA-11(H)
51
    NOAA12,         // NOAA-12(D)
52
    NOAA13,         // NOAA-13(I)
53
    NOAA14,         // NOAA-14(J)
54
    NOAA15,         // NOAA-15(K)
55
    NOAA16,         // NOAA-16(L)
56
    NOAA17,         // NOAA-17(M)
57
    NOAA18,         // NOAA-18(N)
58
    NOAA19,         // NOAA-19(N')
59
    // MetOp are given a number before launch and a letter after launch
60
    METOP2,  // METOP-A(2)
61
    METOP1,  // METOP-B(1)
62
    METOP3,  // METOP-C(3)
63
} L1BSpaceCraftdID;
64
65
typedef enum
66
{  // Product types
67
    HRPT,
68
    LAC,
69
    GAC,
70
    FRAC
71
} L1BProductType;
72
73
typedef enum
74
{  // Data format
75
    PACKED10BIT,
76
    UNPACKED8BIT,
77
    UNPACKED16BIT
78
} L1BDataFormat;
79
80
typedef enum
81
{        // Receiving stations names:
82
    DU,  // Dundee, Scotland, UK
83
    GC,  // Fairbanks, Alaska, USA (formerly Gilmore Creek)
84
    HO,  // Honolulu, Hawaii, USA
85
    MO,  // Monterey, California, USA
86
    WE,  // Western Europe CDA, Lannion, France
87
    SO,  // SOCC (Satellite Operations Control Center), Suitland, Maryland, USA
88
    WI,  // Wallops Island, Virginia, USA
89
    SV,  // Svalbard, Norway
90
    UNKNOWN_STATION
91
} L1BReceivingStation;
92
93
typedef enum
94
{         // Data processing centers:
95
    CMS,  // Centre de Meteorologie Spatiale - Lannion, France
96
    DSS,  // Dundee Satellite Receiving Station - Dundee, Scotland, UK
97
    NSS,  // NOAA/NESDIS - Suitland, Maryland, USA
98
    UKM,  // United Kingdom Meteorological Office - Bracknell, England, UK
99
    UNKNOWN_CENTER
100
} L1BProcessingCenter;
101
102
typedef enum
103
{  // AVHRR Earth location indication
104
    ASCEND,
105
    DESCEND
106
} L1BAscendOrDescend;
107
108
/************************************************************************/
109
/*                          AVHRR band widths                           */
110
/************************************************************************/
111
112
static const char *const apszBandDesc[] = {
113
    // NOAA-7 -- METOP-2 channels
114
    "AVHRR Channel 1:  0.58  micrometers -- 0.68 micrometers",
115
    "AVHRR Channel 2:  0.725 micrometers -- 1.10 micrometers",
116
    "AVHRR Channel 3:  3.55  micrometers -- 3.93 micrometers",
117
    "AVHRR Channel 4:  10.3  micrometers -- 11.3 micrometers",
118
    "AVHRR Channel 5:  11.5  micrometers -- 12.5 micrometers",  // not in
119
                                                                // NOAA-6,-8,-10
120
                                                                // NOAA-13
121
    "AVHRR Channel 5:  11.4  micrometers -- 12.4 micrometers",
122
    // NOAA-15 -- METOP-2
123
    "AVHRR Channel 3A: 1.58  micrometers -- 1.64 micrometers",
124
    "AVHRR Channel 3B: 3.55  micrometers -- 3.93 micrometers"};
125
126
/************************************************************************/
127
/*                  L1B file format related constants                   */
128
/************************************************************************/
129
130
// Length of the string containing dataset name
131
284k
#define L1B_DATASET_NAME_SIZE 42
132
137k
#define L1B_NOAA9_HEADER_SIZE 122   // Terabit memory (TBM) header length
133
107k
#define L1B_NOAA9_HDR_NAME_OFF 30   // Dataset name offset
134
#define L1B_NOAA9_HDR_SRC_OFF 70    // Receiving station name offset
135
114k
#define L1B_NOAA9_HDR_CHAN_OFF 97   // Selected channels map offset
136
60.9k
#define L1B_NOAA9_HDR_CHAN_SIZE 20  // Length of selected channels map
137
1.66k
#define L1B_NOAA9_HDR_WORD_OFF 117  // Sensor data word size offset
138
139
// Archive Retrieval System (ARS) header
140
240k
#define L1B_NOAA15_HEADER_SIZE 512
141
28.0k
#define L1B_NOAA15_HDR_CHAN_OFF 97   // Selected channels map offset
142
15.1k
#define L1B_NOAA15_HDR_CHAN_SIZE 20  // Length of selected channels map
143
#define L1B_NOAA15_HDR_WORD_OFF 117  // Sensor data word size offset
144
145
// Length of header record filled with the data
146
4.62k
#define L1B_NOAA9_HDR_REC_SIZE 146
147
4.44k
#define L1B_NOAA9_HDR_REC_ID_OFF 0      // Spacecraft ID offset
148
2.28k
#define L1B_NOAA9_HDR_REC_PROD_OFF 1    // Data type offset
149
2.20k
#define L1B_NOAA9_HDR_REC_DSTAT_OFF 34  // DACS status offset
150
151
/* See
152
 * http://www.ncdc.noaa.gov/oa/pod-guide/ncdc/docs/klm/html/c8/sec83132-2.htm */
153
// Length of header record filled with the data
154
1.29k
#define L1B_NOAA15_HDR_REC_SIZE 992
155
#define L1B_NOAA15_HDR_REC_SITE_OFF 0  // Dataset creation site ID offset
156
#define L1B_NOAA15_HDR_REC_FORMAT_VERSION_OFF                                  \
157
555
    4  // NOAA Level 1b Format Version Number
158
#define L1B_NOAA15_HDR_REC_FORMAT_VERSION_YEAR_OFF                             \
159
1.58k
    6  // Level 1b Format Version Year (e.g., 1999)
160
#define L1B_NOAA15_HDR_REC_FORMAT_VERSION_DAY_OFF                              \
161
1.58k
    8  // Level 1b Format Version Day of Year (e.g., 365)
162
#define L1B_NOAA15_HDR_REC_LOGICAL_REC_LENGTH_OFF                              \
163
555
    10  // Logical Record Length of source Level 1b data set prior to processing
164
#define L1B_NOAA15_HDR_REC_BLOCK_SIZE_OFF                                      \
165
555
    12  // Block Size of source Level 1b data set prior to processing
166
#define L1B_NOAA15_HDR_REC_HDR_REC_COUNT_OFF                                   \
167
1.58k
    14  // Count of Header Records in this Data Set
168
555
#define L1B_NOAA15_HDR_REC_NAME_OFF 22   // Dataset name
169
555
#define L1B_NOAA15_HDR_REC_ID_OFF 72     // Spacecraft ID offset
170
244
#define L1B_NOAA15_HDR_REC_PROD_OFF 76   // Data type offset
171
242
#define L1B_NOAA15_HDR_REC_STAT_OFF 116  // Instrument status offset
172
555
#define L1B_NOAA15_HDR_REC_DATA_RECORD_COUNT_OFF 128
173
555
#define L1B_NOAA15_HDR_REC_CALIBRATED_SCANLINE_COUNT_OFF 130
174
555
#define L1B_NOAA15_HDR_REC_MISSING_SCANLINE_COUNT_OFF 132
175
242
#define L1B_NOAA15_HDR_REC_SRC_OFF 154  // Receiving station name offset
176
555
#define L1B_NOAA15_HDR_REC_ELLIPSOID_OFF 328
177
178
/* This only apply if L1B_HIGH_GCP_DENSITY is explicitly set to NO */
179
/* otherwise we will report more GCPs */
180
0
#define DESIRED_GCPS_PER_LINE 11
181
0
#define DESIRED_LINES_OF_GCPS 20
182
183
// Fixed values used to scale GCPs coordinates in AVHRR records
184
1.04M
#define L1B_NOAA9_GCP_SCALE 128.0
185
311k
#define L1B_NOAA15_GCP_SCALE 10000.0
186
187
/************************************************************************/
188
/* ==================================================================== */
189
/*                      TimeCode (helper class)                         */
190
/* ==================================================================== */
191
/************************************************************************/
192
193
1.85k
#define L1B_TIMECODE_LENGTH 100
194
195
class TimeCode
196
{
197
    long lYear;
198
    long lDay;
199
    long lMillisecond;
200
    char szString[L1B_TIMECODE_LENGTH];
201
202
  public:
203
8.52k
    TimeCode() : lYear(0), lDay(0), lMillisecond(0)
204
8.52k
    {
205
8.52k
        memset(szString, 0, sizeof(szString));
206
8.52k
    }
207
208
    void SetYear(long year)
209
1.85k
    {
210
1.85k
        lYear = year;
211
1.85k
    }
212
213
    void SetDay(long day)
214
1.85k
    {
215
1.85k
        lDay = day;
216
1.85k
    }
217
218
    void SetMillisecond(long millisecond)
219
1.85k
    {
220
1.85k
        lMillisecond = millisecond;
221
1.85k
    }
222
223
    long GetYear() const
224
0
    {
225
0
        return lYear;
226
0
    }
227
228
    long GetDay() const
229
0
    {
230
0
        return lDay;
231
0
    }
232
233
    long GetMillisecond() const
234
0
    {
235
0
        return lMillisecond;
236
0
    }
237
238
    char *PrintTime()
239
1.85k
    {
240
1.85k
        snprintf(szString, L1B_TIMECODE_LENGTH,
241
1.85k
                 "year: %ld, day: %ld, millisecond: %ld", lYear, lDay,
242
1.85k
                 lMillisecond);
243
1.85k
        return szString;
244
1.85k
    }
245
};
246
247
#undef L1B_TIMECODE_LENGTH
248
249
/************************************************************************/
250
/* ==================================================================== */
251
/*                              L1BDataset                              */
252
/* ==================================================================== */
253
/************************************************************************/
254
class L1BGeolocDataset;
255
class L1BGeolocRasterBand;
256
class L1BSolarZenithAnglesDataset;
257
class L1BSolarZenithAnglesRasterBand;
258
class L1BNOAA15AnglesDataset;
259
class L1BNOAA15AnglesRasterBand;
260
class L1BCloudsDataset;
261
class L1BCloudsRasterBand;
262
263
class L1BDataset final : public GDALPamDataset
264
{
265
    friend class L1BRasterBand;
266
    friend class L1BMaskBand;
267
    friend class L1BGeolocDataset;
268
    friend class L1BGeolocRasterBand;
269
    friend class L1BSolarZenithAnglesDataset;
270
    friend class L1BSolarZenithAnglesRasterBand;
271
    friend class L1BNOAA15AnglesDataset;
272
    friend class L1BNOAA15AnglesRasterBand;
273
    friend class L1BCloudsDataset;
274
    friend class L1BCloudsRasterBand;
275
276
    // char        pszRevolution[6]; // Five-digit number identifying spacecraft
277
    // revolution
278
    L1BReceivingStation eSource;      // Source of data (receiving station name)
279
    L1BProcessingCenter eProcCenter;  // Data processing center
280
    TimeCode sStartTime;
281
    TimeCode sStopTime;
282
283
    int bHighGCPDensityStrategy;
284
    GDAL_GCP *pasGCPList;
285
    int nGCPCount;
286
    int iGCPOffset;
287
    int iGCPCodeOffset;
288
    int iCLAVRStart;
289
    int nGCPsPerLine;
290
    int eLocationIndicator;
291
    int iGCPStart;
292
    int iGCPStep;
293
294
    L1BFileFormat eL1BFormat;
295
    int nBufferSize;
296
    L1BSpaceCraftdID eSpacecraftID;
297
    L1BProductType eProductType;  // LAC, GAC, HRPT, FRAC
298
    L1BDataFormat iDataFormat;    // 10-bit packed or 16-bit unpacked
299
    int nRecordDataStart;
300
    int nRecordDataEnd;
301
    int nDataStartOffset;
302
    int nRecordSize;
303
    int nRecordSizeFromHeader;
304
    GUInt32 iInstrumentStatus;
305
    GUInt32 iChannelsMask;
306
307
    OGRSpatialReference m_oGCPSRS{};
308
309
    VSILFILE *fp;
310
311
    int bGuessDataFormat;
312
313
    int bByteSwap;
314
315
    int bExposeMaskBand;
316
    GDALRasterBand *poMaskBand;
317
318
    void ProcessRecordHeaders();
319
    int FetchGCPs(GDAL_GCP *, GByte *, int) const;
320
    static void FetchNOAA9TimeCode(TimeCode *, const GByte *, int *);
321
    void FetchNOAA15TimeCode(TimeCode *, const GByte *, int *) const;
322
    void FetchTimeCode(TimeCode *psTime, const void *pRecordHeader,
323
                       int *peLocationIndicator) const;
324
    CPLErr ProcessDatasetHeader(const char *pszFilename);
325
    int ComputeFileOffsets();
326
327
    void FetchMetadata();
328
    void FetchMetadataNOAA15();
329
330
    vsi_l_offset GetLineOffset(int nBlockYOff) const;
331
332
    GUInt16 GetUInt16(const void *pabyData) const;
333
    GInt16 GetInt16(const void *pabyData) const;
334
    GUInt32 GetUInt32(const void *pabyData) const;
335
    GInt32 GetInt32(const void *pabyData) const;
336
337
    static L1BFileFormat DetectFormat(const char *pszFilename,
338
                                      const GByte *pabyHeader,
339
                                      int nHeaderBytes);
340
341
  public:
342
    explicit L1BDataset(L1BFileFormat);
343
    ~L1BDataset() override;
344
345
    int GetGCPCount() override;
346
    const OGRSpatialReference *GetGCPSpatialRef() const override;
347
    const GDAL_GCP *GetGCPs() override;
348
349
    static int Identify(GDALOpenInfo *);
350
    static GDALDataset *Open(GDALOpenInfo *);
351
};
352
353
/************************************************************************/
354
/* ==================================================================== */
355
/*                            L1BRasterBand                             */
356
/* ==================================================================== */
357
/************************************************************************/
358
359
class L1BRasterBand final : public GDALPamRasterBand
360
{
361
    friend class L1BDataset;
362
363
  public:
364
    L1BRasterBand(L1BDataset *, int);
365
366
    //    virtual double GetNoDataValue( int *pbSuccess = NULL );
367
    CPLErr IReadBlock(int, int, void *) override;
368
    GDALRasterBand *GetMaskBand() override;
369
    int GetMaskFlags() override;
370
};
371
372
/************************************************************************/
373
/* ==================================================================== */
374
/*                            L1BMaskBand                               */
375
/* ==================================================================== */
376
/************************************************************************/
377
378
class L1BMaskBand final : public GDALPamRasterBand
379
{
380
    friend class L1BDataset;
381
382
  public:
383
    explicit L1BMaskBand(L1BDataset *);
384
385
    CPLErr IReadBlock(int, int, void *) override;
386
};
387
388
/************************************************************************/
389
/*                            L1BMaskBand()                             */
390
/************************************************************************/
391
392
L1BMaskBand::L1BMaskBand(L1BDataset *poDSIn)
393
189
{
394
189
    CPLAssert(poDSIn->eL1BFormat == L1B_NOAA15 ||
395
189
              poDSIn->eL1BFormat == L1B_NOAA15_NOHDR);
396
397
189
    poDS = poDSIn;
398
189
    eDataType = GDT_UInt8;
399
400
189
    nRasterXSize = poDS->GetRasterXSize();
401
189
    nRasterYSize = poDS->GetRasterYSize();
402
189
    nBlockXSize = poDS->GetRasterXSize();
403
189
    nBlockYSize = 1;
404
189
}
405
406
/************************************************************************/
407
/*                             IReadBlock()                             */
408
/************************************************************************/
409
410
CPLErr L1BMaskBand::IReadBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
411
                               void *pImage)
412
3.11k
{
413
3.11k
    L1BDataset *poGDS = cpl::down_cast<L1BDataset *>(poDS);
414
415
3.11k
    CPL_IGNORE_RET_VAL(
416
3.11k
        VSIFSeekL(poGDS->fp, poGDS->GetLineOffset(nBlockYOff) + 24, SEEK_SET));
417
418
3.11k
    GByte abyData[4];
419
3.11k
    CPL_IGNORE_RET_VAL(VSIFReadL(abyData, 1, 4, poGDS->fp));
420
3.11k
    GUInt32 n32 = poGDS->GetUInt32(abyData);
421
422
3.11k
    if ((n32 >> 31) != 0) /* fatal flag */
423
1.54k
        memset(pImage, 0, nBlockXSize);
424
1.56k
    else
425
1.56k
        memset(pImage, 255, nBlockXSize);
426
427
3.11k
    return CE_None;
428
3.11k
}
429
430
/************************************************************************/
431
/*                           L1BRasterBand()                            */
432
/************************************************************************/
433
434
L1BRasterBand::L1BRasterBand(L1BDataset *poDSIn, int nBandIn)
435
436
1.87k
{
437
1.87k
    poDS = poDSIn;
438
1.87k
    nBand = nBandIn;
439
1.87k
    eDataType = GDT_UInt16;
440
441
1.87k
    nBlockXSize = poDS->GetRasterXSize();
442
1.87k
    nBlockYSize = 1;
443
1.87k
}
444
445
/************************************************************************/
446
/*                            GetMaskBand()                             */
447
/************************************************************************/
448
449
GDALRasterBand *L1BRasterBand::GetMaskBand()
450
11.1k
{
451
11.1k
    L1BDataset *poGDS = cpl::down_cast<L1BDataset *>(poDS);
452
11.1k
    if (poGDS->poMaskBand)
453
10.1k
        return poGDS->poMaskBand;
454
1.03k
    return GDALPamRasterBand::GetMaskBand();
455
11.1k
}
456
457
/************************************************************************/
458
/*                            GetMaskFlags()                            */
459
/************************************************************************/
460
461
int L1BRasterBand::GetMaskFlags()
462
12.6k
{
463
12.6k
    L1BDataset *poGDS = cpl::down_cast<L1BDataset *>(poDS);
464
12.6k
    if (poGDS->poMaskBand)
465
11.6k
        return GMF_PER_DATASET;
466
1.01k
    return GDALPamRasterBand::GetMaskFlags();
467
12.6k
}
468
469
/************************************************************************/
470
/*                             IReadBlock()                             */
471
/************************************************************************/
472
473
CPLErr L1BRasterBand::IReadBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
474
                                 void *pImage)
475
19.4k
{
476
19.4k
    L1BDataset *poGDS = cpl::down_cast<L1BDataset *>(poDS);
477
478
    /* -------------------------------------------------------------------- */
479
    /*      Seek to data.                                                   */
480
    /* -------------------------------------------------------------------- */
481
19.4k
    CPL_IGNORE_RET_VAL(
482
19.4k
        VSIFSeekL(poGDS->fp, poGDS->GetLineOffset(nBlockYOff), SEEK_SET));
483
484
    /* -------------------------------------------------------------------- */
485
    /*      Read data into the buffer.                                      */
486
    /* -------------------------------------------------------------------- */
487
19.4k
    GUInt16 *iScan = nullptr;  // Unpacked 16-bit scanline buffer
488
19.4k
    int i, j;
489
490
19.4k
    switch (poGDS->iDataFormat)
491
19.4k
    {
492
10.2k
        case PACKED10BIT:
493
10.2k
        {
494
            // Read packed scanline
495
10.2k
            GUInt32 *iRawScan = (GUInt32 *)CPLMalloc(poGDS->nRecordSize);
496
10.2k
            CPL_IGNORE_RET_VAL(
497
10.2k
                VSIFReadL(iRawScan, 1, poGDS->nRecordSize, poGDS->fp));
498
499
10.2k
            iScan = (GUInt16 *)CPLMalloc(poGDS->nBufferSize);
500
10.2k
            j = 0;
501
10.2k
            for (i = poGDS->nRecordDataStart / (int)sizeof(iRawScan[0]);
502
7.30M
                 i < poGDS->nRecordDataEnd / (int)sizeof(iRawScan[0]); i++)
503
7.29M
            {
504
7.29M
                GUInt32 iWord1 = poGDS->GetUInt32(&iRawScan[i]);
505
7.29M
                GUInt32 iWord2 = iWord1 & 0x3FF00000;
506
507
7.29M
                iScan[j++] = (GUInt16)(iWord2 >> 20);
508
7.29M
                iWord2 = iWord1 & 0x000FFC00;
509
7.29M
                iScan[j++] = (GUInt16)(iWord2 >> 10);
510
7.29M
                iScan[j++] = (GUInt16)(iWord1 & 0x000003FF);
511
7.29M
            }
512
10.2k
            CPLFree(iRawScan);
513
10.2k
        }
514
10.2k
        break;
515
1.59k
        case UNPACKED16BIT:
516
1.59k
        {
517
            // Read unpacked scanline
518
1.59k
            GUInt16 *iRawScan = (GUInt16 *)CPLMalloc(poGDS->nRecordSize);
519
1.59k
            CPL_IGNORE_RET_VAL(
520
1.59k
                VSIFReadL(iRawScan, 1, poGDS->nRecordSize, poGDS->fp));
521
522
1.59k
            iScan = (GUInt16 *)CPLMalloc(
523
1.59k
                sizeof(GUInt16) * poGDS->GetRasterXSize() * poGDS->nBands);
524
654k
            for (i = 0; i < poGDS->GetRasterXSize() * poGDS->nBands; i++)
525
652k
            {
526
652k
                iScan[i] =
527
652k
                    poGDS->GetUInt16(&iRawScan[poGDS->nRecordDataStart /
528
652k
                                                   (int)sizeof(iRawScan[0]) +
529
652k
                                               i]);
530
652k
            }
531
1.59k
            CPLFree(iRawScan);
532
1.59k
        }
533
1.59k
        break;
534
7.51k
        case UNPACKED8BIT:
535
7.51k
        {
536
            // Read 8-bit unpacked scanline
537
7.51k
            GByte *byRawScan = (GByte *)CPLMalloc(poGDS->nRecordSize);
538
7.51k
            CPL_IGNORE_RET_VAL(
539
7.51k
                VSIFReadL(byRawScan, 1, poGDS->nRecordSize, poGDS->fp));
540
541
7.51k
            iScan = (GUInt16 *)CPLMalloc(
542
7.51k
                sizeof(GUInt16) * poGDS->GetRasterXSize() * poGDS->nBands);
543
8.81M
            for (i = 0; i < poGDS->GetRasterXSize() * poGDS->nBands; i++)
544
8.81M
                iScan[i] = byRawScan[poGDS->nRecordDataStart /
545
8.81M
                                         (int)sizeof(byRawScan[0]) +
546
8.81M
                                     i];
547
7.51k
            CPLFree(byRawScan);
548
7.51k
        }
549
7.51k
        break;
550
0
        default:  // NOTREACHED
551
0
            break;
552
19.4k
    }
553
554
19.4k
    int nBlockSize = nBlockXSize * nBlockYSize;
555
19.4k
    if (poGDS->eLocationIndicator == DESCEND)
556
9.26k
    {
557
3.95M
        for (i = 0, j = 0; i < nBlockSize; i++)
558
3.94M
        {
559
3.94M
            ((GUInt16 *)pImage)[i] = iScan[j + nBand - 1];
560
3.94M
            j += poGDS->nBands;
561
3.94M
        }
562
9.26k
    }
563
10.1k
    else
564
10.1k
    {
565
5.04M
        for (i = nBlockSize - 1, j = 0; i >= 0; i--)
566
5.03M
        {
567
5.03M
            ((GUInt16 *)pImage)[i] = iScan[j + nBand - 1];
568
5.03M
            j += poGDS->nBands;
569
5.03M
        }
570
10.1k
    }
571
572
19.4k
    CPLFree(iScan);
573
19.4k
    return CE_None;
574
19.4k
}
575
576
/************************************************************************/
577
/*                             L1BDataset()                             */
578
/************************************************************************/
579
580
L1BDataset::L1BDataset(L1BFileFormat eL1BFormatIn)
581
4.26k
    : eSource(UNKNOWN_STATION), eProcCenter(UNKNOWN_CENTER),
582
      // sStartTime
583
      // sStopTime
584
      bHighGCPDensityStrategy(
585
4.26k
          CPLTestBool(CPLGetConfigOption("L1B_HIGH_GCP_DENSITY", "TRUE"))),
586
4.26k
      pasGCPList(nullptr), nGCPCount(0), iGCPOffset(0), iGCPCodeOffset(0),
587
4.26k
      iCLAVRStart(0), nGCPsPerLine(0),
588
4.26k
      eLocationIndicator(DESCEND),  // XXX: should be initialized
589
4.26k
      iGCPStart(0), iGCPStep(0), eL1BFormat(eL1BFormatIn), nBufferSize(0),
590
4.26k
      eSpacecraftID(TIROSN), eProductType(HRPT), iDataFormat(PACKED10BIT),
591
4.26k
      nRecordDataStart(0), nRecordDataEnd(0), nDataStartOffset(0),
592
4.26k
      nRecordSize(0), nRecordSizeFromHeader(0), iInstrumentStatus(0),
593
4.26k
      iChannelsMask(0), fp(nullptr), bGuessDataFormat(FALSE),
594
      // L1B is normally big-endian ordered, so byte-swap on little-endian CPU.
595
4.26k
      bByteSwap(CPL_IS_LSB), bExposeMaskBand(FALSE), poMaskBand(nullptr)
596
4.26k
{
597
4.26k
    m_oGCPSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
598
4.26k
    m_oGCPSRS.importFromWkt(
599
4.26k
        "GEOGCS[\"WGS 72\",DATUM[\"WGS_1972\","
600
4.26k
        "SPHEROID[\"WGS 72\",6378135,298.26,AUTHORITY[\"EPSG\",7043]],"
601
4.26k
        "TOWGS84[0,0,4.5,0,0,0.554,0.2263],AUTHORITY[\"EPSG\",6322]],"
602
4.26k
        "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",8901]],"
603
4.26k
        "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",9108]],"
604
4.26k
        "AUTHORITY[\"EPSG\",4322]]");
605
4.26k
}
606
607
/************************************************************************/
608
/*                            ~L1BDataset()                             */
609
/************************************************************************/
610
611
L1BDataset::~L1BDataset()
612
613
4.26k
{
614
4.26k
    FlushCache(true);
615
616
4.26k
    if (nGCPCount > 0)
617
876
    {
618
876
        GDALDeinitGCPs(nGCPCount, pasGCPList);
619
876
        CPLFree(pasGCPList);
620
876
    }
621
4.26k
    if (fp != nullptr)
622
4.26k
        CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
623
4.26k
    delete poMaskBand;
624
4.26k
}
625
626
/************************************************************************/
627
/*                           GetLineOffset()                            */
628
/************************************************************************/
629
630
vsi_l_offset L1BDataset::GetLineOffset(int nBlockYOff) const
631
22.5k
{
632
22.5k
    return (eLocationIndicator == DESCEND)
633
22.5k
               ? nDataStartOffset + (vsi_l_offset)nBlockYOff * nRecordSize
634
22.5k
               : nDataStartOffset +
635
11.6k
                     (vsi_l_offset)(nRasterYSize - nBlockYOff - 1) *
636
11.6k
                         nRecordSize;
637
22.5k
}
638
639
/************************************************************************/
640
/*                            GetGCPCount()                             */
641
/************************************************************************/
642
643
int L1BDataset::GetGCPCount()
644
645
6.02k
{
646
6.02k
    return nGCPCount;
647
6.02k
}
648
649
/************************************************************************/
650
/*                          GetGCPSpatialRef()                          */
651
/************************************************************************/
652
653
const OGRSpatialReference *L1BDataset::GetGCPSpatialRef() const
654
655
376
{
656
376
    if (nGCPCount > 0 && !m_oGCPSRS.IsEmpty())
657
342
        return &m_oGCPSRS;
658
34
    else
659
34
        return nullptr;
660
376
}
661
662
/************************************************************************/
663
/*                              GetGCPs()                               */
664
/************************************************************************/
665
666
const GDAL_GCP *L1BDataset::GetGCPs()
667
375
{
668
375
    return pasGCPList;
669
375
}
670
671
/************************************************************************/
672
/*                        Byte swapping helpers                         */
673
/************************************************************************/
674
675
GUInt16 L1BDataset::GetUInt16(const void *pabyData) const
676
663k
{
677
663k
    GUInt16 iTemp;
678
663k
    memcpy(&iTemp, pabyData, 2);
679
663k
    if (bByteSwap)
680
661k
        return CPL_SWAP16(iTemp);
681
2.45k
    return iTemp;
682
663k
}
683
684
GInt16 L1BDataset::GetInt16(const void *pabyData) const
685
1.04M
{
686
1.04M
    GInt16 iTemp;
687
1.04M
    memcpy(&iTemp, pabyData, 2);
688
1.04M
    if (bByteSwap)
689
1.04M
        return CPL_SWAP16(iTemp);
690
0
    return iTemp;
691
1.04M
}
692
693
GUInt32 L1BDataset::GetUInt32(const void *pabyData) const
694
7.29M
{
695
7.29M
    GUInt32 lTemp;
696
7.29M
    memcpy(&lTemp, pabyData, 4);
697
7.29M
    if (bByteSwap)
698
4.09M
        return CPL_SWAP32(lTemp);
699
3.19M
    return lTemp;
700
7.29M
}
701
702
GInt32 L1BDataset::GetInt32(const void *pabyData) const
703
311k
{
704
311k
    GInt32 lTemp;
705
311k
    memcpy(&lTemp, pabyData, 4);
706
311k
    if (bByteSwap)
707
226k
        return CPL_SWAP32(lTemp);
708
85.0k
    return lTemp;
709
311k
}
710
711
/************************************************************************/
712
/*     Fetch timecode from the record header (NOAA9-NOAA14 version)     */
713
/************************************************************************/
714
715
void L1BDataset::FetchNOAA9TimeCode(TimeCode *psTime,
716
                                    const GByte *piRecordHeader,
717
                                    int *peLocationIndicator)
718
1.49k
{
719
1.49k
    GUInt32 lTemp;
720
721
1.49k
    lTemp = ((piRecordHeader[2] >> 1) & 0x7F);
722
1.49k
    psTime->SetYear((lTemp > 77)
723
1.49k
                        ? (lTemp + 1900)
724
1.49k
                        : (lTemp + 2000));  // Avoid `Year 2000' problem
725
1.49k
    psTime->SetDay((GUInt32)(piRecordHeader[2] & 0x01) << 8 |
726
1.49k
                   (GUInt32)piRecordHeader[3]);
727
1.49k
    psTime->SetMillisecond(((GUInt32)(piRecordHeader[4] & 0x07) << 24) |
728
1.49k
                           ((GUInt32)piRecordHeader[5] << 16) |
729
1.49k
                           ((GUInt32)piRecordHeader[6] << 8) |
730
1.49k
                           (GUInt32)piRecordHeader[7]);
731
1.49k
    if (peLocationIndicator)
732
747
    {
733
747
        *peLocationIndicator =
734
747
            ((piRecordHeader[8] & 0x02) == 0) ? ASCEND : DESCEND;
735
747
    }
736
1.49k
}
737
738
/************************************************************************/
739
/*    Fetch timecode from the record header (NOAA15-METOP2 version)     */
740
/************************************************************************/
741
742
void L1BDataset::FetchNOAA15TimeCode(TimeCode *psTime,
743
                                     const GByte *pabyRecordHeader,
744
                                     int *peLocationIndicator) const
745
360
{
746
360
    psTime->SetYear(GetUInt16(pabyRecordHeader + 2));
747
360
    psTime->SetDay(GetUInt16(pabyRecordHeader + 4));
748
360
    psTime->SetMillisecond(GetUInt32(pabyRecordHeader + 8));
749
360
    if (peLocationIndicator)
750
180
    {
751
        // FIXME: hemisphere
752
180
        *peLocationIndicator =
753
180
            ((GetUInt16(pabyRecordHeader + 12) & 0x8000) == 0) ? ASCEND
754
180
                                                               : DESCEND;
755
180
    }
756
360
}
757
758
/************************************************************************/
759
/*                           FetchTimeCode()                            */
760
/************************************************************************/
761
762
void L1BDataset::FetchTimeCode(TimeCode *psTime, const void *pRecordHeader,
763
                               int *peLocationIndicator) const
764
1.85k
{
765
1.85k
    if (eSpacecraftID <= NOAA14)
766
1.49k
    {
767
1.49k
        FetchNOAA9TimeCode(psTime, (const GByte *)pRecordHeader,
768
1.49k
                           peLocationIndicator);
769
1.49k
    }
770
360
    else
771
360
    {
772
360
        FetchNOAA15TimeCode(psTime, (const GByte *)pRecordHeader,
773
360
                            peLocationIndicator);
774
360
    }
775
1.85k
}
776
777
/************************************************************************/
778
/*               Fetch GCPs from the individual scanlines               */
779
/************************************************************************/
780
781
int L1BDataset::FetchGCPs(GDAL_GCP *pasGCPListRow, GByte *pabyRecordHeader,
782
                          int iLine) const
783
20.4k
{
784
    // LAC and HRPT GCPs are tied to the center of pixel,
785
    // GAC ones are slightly displaced.
786
20.4k
    double dfDelta = (eProductType == GAC) ? 0.9 : 0.5;
787
20.4k
    double dfPixel = (eLocationIndicator == DESCEND)
788
20.4k
                         ? iGCPStart + dfDelta
789
20.4k
                         : (nRasterXSize - (iGCPStart + dfDelta));
790
791
20.4k
    int nGCPs;
792
20.4k
    if (eSpacecraftID <= NOAA14)
793
17.4k
    {
794
        // NOAA9-NOAA14 records have an indicator of number of working GCPs.
795
        // Number of good GCPs may be smaller than the total amount of points.
796
17.4k
        nGCPs = (*(pabyRecordHeader + iGCPCodeOffset) < nGCPsPerLine)
797
17.4k
                    ? *(pabyRecordHeader + iGCPCodeOffset)
798
17.4k
                    : nGCPsPerLine;
799
#ifdef DEBUG_VERBOSE
800
        CPLDebug("L1B", "iGCPCodeOffset=%d, nGCPsPerLine=%d, nGoodGCPs=%d",
801
                 iGCPCodeOffset, nGCPsPerLine, nGCPs);
802
#endif
803
17.4k
    }
804
3.05k
    else
805
3.05k
        nGCPs = nGCPsPerLine;
806
807
20.4k
    pabyRecordHeader += iGCPOffset;
808
809
20.4k
    int nGCPCountRow = 0;
810
700k
    while (nGCPs--)
811
680k
    {
812
680k
        if (eSpacecraftID <= NOAA14)
813
524k
        {
814
524k
            GInt16 nRawY = GetInt16(pabyRecordHeader);
815
524k
            pabyRecordHeader += sizeof(GInt16);
816
524k
            GInt16 nRawX = GetInt16(pabyRecordHeader);
817
524k
            pabyRecordHeader += sizeof(GInt16);
818
819
524k
            pasGCPListRow[nGCPCountRow].dfGCPY = nRawY / L1B_NOAA9_GCP_SCALE;
820
524k
            pasGCPListRow[nGCPCountRow].dfGCPX = nRawX / L1B_NOAA9_GCP_SCALE;
821
524k
        }
822
155k
        else
823
155k
        {
824
155k
            GInt32 nRawY = GetInt32(pabyRecordHeader);
825
155k
            pabyRecordHeader += sizeof(GInt32);
826
155k
            GInt32 nRawX = GetInt32(pabyRecordHeader);
827
155k
            pabyRecordHeader += sizeof(GInt32);
828
829
155k
            pasGCPListRow[nGCPCountRow].dfGCPY = nRawY / L1B_NOAA15_GCP_SCALE;
830
155k
            pasGCPListRow[nGCPCountRow].dfGCPX = nRawX / L1B_NOAA15_GCP_SCALE;
831
155k
        }
832
833
680k
        if (pasGCPListRow[nGCPCountRow].dfGCPX < -180 ||
834
567k
            pasGCPListRow[nGCPCountRow].dfGCPX > 180 ||
835
413k
            pasGCPListRow[nGCPCountRow].dfGCPY < -90 ||
836
374k
            pasGCPListRow[nGCPCountRow].dfGCPY > 90)
837
459k
            continue;
838
839
220k
        pasGCPListRow[nGCPCountRow].dfGCPZ = 0.0;
840
220k
        pasGCPListRow[nGCPCountRow].dfGCPPixel = dfPixel;
841
220k
        dfPixel += (eLocationIndicator == DESCEND) ? iGCPStep : -iGCPStep;
842
220k
        pasGCPListRow[nGCPCountRow].dfGCPLine =
843
220k
            (double)((eLocationIndicator == DESCEND)
844
220k
                         ? iLine
845
220k
                         : nRasterYSize - iLine - 1) +
846
220k
            0.5;
847
220k
        nGCPCountRow++;
848
220k
    }
849
20.4k
    return nGCPCountRow;
850
20.4k
}
851
852
/************************************************************************/
853
/*                        ProcessRecordHeaders()                        */
854
/************************************************************************/
855
856
void L1BDataset::ProcessRecordHeaders()
857
1.01k
{
858
1.01k
    if (nRasterYSize == 0)
859
91
        return;
860
927
    void *pRecordHeader = CPLCalloc(1, nRecordDataStart);
861
862
927
    CPL_IGNORE_RET_VAL(
863
927
        VSIFSeekL(fp, static_cast<vsi_l_offset>(nDataStartOffset), SEEK_SET));
864
927
    CPL_IGNORE_RET_VAL(VSIFReadL(pRecordHeader, 1, nRecordDataStart, fp));
865
866
927
    FetchTimeCode(&sStartTime, pRecordHeader, &eLocationIndicator);
867
868
927
    CPL_IGNORE_RET_VAL(
869
927
        VSIFSeekL(fp,
870
927
                  nDataStartOffset +
871
927
                      static_cast<vsi_l_offset>(nRasterYSize - 1) * nRecordSize,
872
927
                  SEEK_SET));
873
927
    CPL_IGNORE_RET_VAL(VSIFReadL(pRecordHeader, 1, nRecordDataStart, fp));
874
875
927
    FetchTimeCode(&sStopTime, pRecordHeader, nullptr);
876
877
    /* -------------------------------------------------------------------- */
878
    /*      Pick a skip factor so that we will get roughly 20 lines         */
879
    /*      worth of GCPs.  That should give respectable coverage on all    */
880
    /*      but the longest swaths.                                         */
881
    /* -------------------------------------------------------------------- */
882
927
    int nTargetLines;
883
927
    double dfLineStep = 0.0;
884
885
927
    if (bHighGCPDensityStrategy)
886
927
    {
887
927
        if (nRasterYSize < nGCPsPerLine)
888
752
        {
889
752
            nTargetLines = nRasterYSize;
890
752
        }
891
175
        else
892
175
        {
893
175
            int nColStep;
894
175
            nColStep = nRasterXSize / nGCPsPerLine;
895
175
            if (nRasterYSize >= nRasterXSize)
896
17
            {
897
17
                dfLineStep = nColStep;
898
17
            }
899
158
            else
900
158
            {
901
158
                dfLineStep = nRasterYSize / nGCPsPerLine;
902
158
            }
903
175
            nTargetLines = static_cast<int>(nRasterYSize / dfLineStep);
904
175
        }
905
927
    }
906
0
    else
907
0
    {
908
0
        nTargetLines = std::min(DESIRED_LINES_OF_GCPS, nRasterYSize);
909
0
    }
910
927
    if (nTargetLines > 1)
911
784
        dfLineStep = 1.0 * (nRasterYSize - 1) / (nTargetLines - 1);
912
913
    /* -------------------------------------------------------------------- */
914
    /*      Initialize the GCP list.                                        */
915
    /* -------------------------------------------------------------------- */
916
927
    const int nExpectedGCPs = nTargetLines * nGCPsPerLine;
917
927
    if (nExpectedGCPs > 0)
918
927
    {
919
927
        pasGCPList =
920
927
            (GDAL_GCP *)VSI_CALLOC_VERBOSE(nExpectedGCPs, sizeof(GDAL_GCP));
921
927
        if (pasGCPList == nullptr)
922
0
        {
923
0
            CPLFree(pRecordHeader);
924
0
            return;
925
0
        }
926
927
        GDALInitGCPs(nExpectedGCPs, pasGCPList);
927
927
    }
928
929
    /* -------------------------------------------------------------------- */
930
    /*      Fetch the GCPs for each selected line.  We force the last       */
931
    /*      line sampled to be the last line in the dataset even if that    */
932
    /*      leaves a bigger than expected gap.                              */
933
    /* -------------------------------------------------------------------- */
934
927
    int iStep;
935
927
    int iPrevLine = -1;
936
937
21.3k
    for (iStep = 0; iStep < nTargetLines; iStep++)
938
20.4k
    {
939
20.4k
        int iLine;
940
941
20.4k
        if (iStep == nTargetLines - 1)
942
927
            iLine = nRasterYSize - 1;
943
19.5k
        else
944
19.5k
            iLine = (int)(dfLineStep * iStep);
945
20.4k
        if (iLine == iPrevLine)
946
0
            continue;
947
20.4k
        iPrevLine = iLine;
948
949
20.4k
        CPL_IGNORE_RET_VAL(
950
20.4k
            VSIFSeekL(fp, nDataStartOffset + iLine * nRecordSize, SEEK_SET));
951
20.4k
        CPL_IGNORE_RET_VAL(VSIFReadL(pRecordHeader, 1, nRecordDataStart, fp));
952
953
20.4k
        int nGCPsOnThisLine =
954
20.4k
            FetchGCPs(pasGCPList + nGCPCount, (GByte *)pRecordHeader, iLine);
955
956
20.4k
        if (!bHighGCPDensityStrategy)
957
0
        {
958
            /* --------------------------------------------------------------------
959
             */
960
            /*      We don't really want too many GCPs per line.  Downsample to
961
             */
962
            /*      11 per line. */
963
            /* --------------------------------------------------------------------
964
             */
965
966
0
            const int nDesiredGCPsPerLine =
967
0
                std::min(DESIRED_GCPS_PER_LINE, nGCPsOnThisLine);
968
0
            int nGCPStep =
969
0
                (nDesiredGCPsPerLine > 1)
970
0
                    ? (nGCPsOnThisLine - 1) / (nDesiredGCPsPerLine - 1)
971
0
                    : 1;
972
0
            int iSrcGCP = nGCPCount;
973
0
            int iDstGCP = nGCPCount;
974
975
0
            if (nGCPStep == 0)
976
0
                nGCPStep = 1;
977
978
0
            for (int iGCP = 0; iGCP < nDesiredGCPsPerLine; iGCP++)
979
0
            {
980
0
                if (iGCP == nDesiredGCPsPerLine - 1)
981
0
                    iSrcGCP = nGCPCount + nGCPsOnThisLine - 1;
982
0
                else
983
0
                    iSrcGCP += nGCPStep;
984
0
                iDstGCP++;
985
986
0
                pasGCPList[iDstGCP].dfGCPX = pasGCPList[iSrcGCP].dfGCPX;
987
0
                pasGCPList[iDstGCP].dfGCPY = pasGCPList[iSrcGCP].dfGCPY;
988
0
                pasGCPList[iDstGCP].dfGCPPixel = pasGCPList[iSrcGCP].dfGCPPixel;
989
0
                pasGCPList[iDstGCP].dfGCPLine = pasGCPList[iSrcGCP].dfGCPLine;
990
0
            }
991
992
0
            nGCPCount += nDesiredGCPsPerLine;
993
0
        }
994
20.4k
        else
995
20.4k
        {
996
20.4k
            nGCPCount += nGCPsOnThisLine;
997
20.4k
        }
998
20.4k
    }
999
1000
927
    if (nGCPCount < nExpectedGCPs)
1001
916
    {
1002
916
        GDALDeinitGCPs(nExpectedGCPs - nGCPCount, pasGCPList + nGCPCount);
1003
916
        if (nGCPCount == 0)
1004
51
        {
1005
51
            CPLFree(pasGCPList);
1006
51
            pasGCPList = nullptr;
1007
51
        }
1008
916
    }
1009
1010
927
    CPLFree(pRecordHeader);
1011
1012
    /* -------------------------------------------------------------------- */
1013
    /*      Set fetched information as metadata records                     */
1014
    /* -------------------------------------------------------------------- */
1015
    // Time of first scanline
1016
927
    SetMetadataItem("START", sStartTime.PrintTime());
1017
    // Time of last scanline
1018
927
    SetMetadataItem("STOP", sStopTime.PrintTime());
1019
    // AVHRR Earth location indication
1020
1021
927
    switch (eLocationIndicator)
1022
927
    {
1023
509
        case ASCEND:
1024
509
            SetMetadataItem("LOCATION", "Ascending");
1025
509
            break;
1026
418
        case DESCEND:
1027
418
        default:
1028
418
            SetMetadataItem("LOCATION", "Descending");
1029
418
            break;
1030
927
    }
1031
927
}
1032
1033
/************************************************************************/
1034
/*                           FetchMetadata()                            */
1035
/************************************************************************/
1036
1037
void L1BDataset::FetchMetadata()
1038
0
{
1039
0
    if (eL1BFormat != L1B_NOAA9)
1040
0
    {
1041
0
        FetchMetadataNOAA15();
1042
0
        return;
1043
0
    }
1044
1045
0
    std::string osDir = CPLGetConfigOption("L1B_METADATA_DIRECTORY", "");
1046
0
    if (osDir.empty())
1047
0
    {
1048
0
        osDir = CPLGetPathSafe(GetDescription());
1049
0
        if (osDir.empty())
1050
0
            osDir = ".";
1051
0
    }
1052
0
    const std::string osMetadataFile =
1053
0
        std::string(osDir)
1054
0
            .append("/")
1055
0
            .append(CPLGetFilename(GetDescription()))
1056
0
            .append("_metadata.csv");
1057
0
    CPL_IGNORE_RET_VAL(osDir);
1058
0
    VSILFILE *fpCSV = VSIFOpenL(osMetadataFile.c_str(), "wb");
1059
0
    if (fpCSV == nullptr)
1060
0
    {
1061
0
        CPLError(CE_Failure, CPLE_AppDefined,
1062
0
                 "Cannot create metadata file : %s", osMetadataFile.c_str());
1063
0
        return;
1064
0
    }
1065
1066
0
    CPL_IGNORE_RET_VAL(
1067
0
        VSIFPrintfL(fpCSV, "SCANLINE,NBLOCKYOFF,YEAR,DAY,MS_IN_DAY,"));
1068
0
    CPL_IGNORE_RET_VAL(VSIFPrintfL(
1069
0
        fpCSV, "FATAL_FLAG,TIME_ERROR,DATA_GAP,DATA_JITTER,INSUFFICIENT_DATA_"
1070
0
               "FOR_CAL,NO_EARTH_LOCATION,DESCEND,P_N_STATUS,"));
1071
0
    CPL_IGNORE_RET_VAL(VSIFPrintfL(
1072
0
        fpCSV, "BIT_SYNC_STATUS,SYNC_ERROR,FRAME_SYNC_ERROR,FLYWHEELING,BIT_"
1073
0
               "SLIPPAGE,C3_SBBC,C4_SBBC,C5_SBBC,"));
1074
0
    CPL_IGNORE_RET_VAL(
1075
0
        VSIFPrintfL(fpCSV, "TIP_PARITY_FRAME_1,TIP_PARITY_FRAME_2,TIP_PARITY_"
1076
0
                           "FRAME_3,TIP_PARITY_FRAME_4,TIP_PARITY_FRAME_5,"));
1077
0
    CPL_IGNORE_RET_VAL(VSIFPrintfL(fpCSV, "SYNC_ERRORS,"));
1078
0
    CPL_IGNORE_RET_VAL(VSIFPrintfL(
1079
0
        fpCSV, "CAL_SLOPE_C1,CAL_INTERCEPT_C1,CAL_SLOPE_C2,CAL_INTERCEPT_C2,"
1080
0
               "CAL_SLOPE_C3,CAL_INTERCEPT_C3,CAL_SLOPE_C4,CAL_INTERCEPT_C4,"
1081
0
               "CAL_SLOPE_C5,CAL_INTERCEPT_C5,"));
1082
0
    CPL_IGNORE_RET_VAL(VSIFPrintfL(fpCSV, "NUM_SOLZENANGLES_EARTHLOCPNTS"));
1083
0
    CPL_IGNORE_RET_VAL(VSIFPrintfL(fpCSV, "\n"));
1084
1085
0
    GByte *pabyRecordHeader = (GByte *)CPLMalloc(nRecordDataStart);
1086
1087
0
    for (int nBlockYOff = 0; nBlockYOff < nRasterYSize; nBlockYOff++)
1088
0
    {
1089
        /* --------------------------------------------------------------------
1090
         */
1091
        /*      Seek to data. */
1092
        /* --------------------------------------------------------------------
1093
         */
1094
0
        CPL_IGNORE_RET_VAL(VSIFSeekL(fp, GetLineOffset(nBlockYOff), SEEK_SET));
1095
1096
0
        CPL_IGNORE_RET_VAL(
1097
0
            VSIFReadL(pabyRecordHeader, 1, nRecordDataStart, fp));
1098
1099
0
        GUInt16 nScanlineNumber = GetUInt16(pabyRecordHeader);
1100
1101
0
        TimeCode timeCode;
1102
0
        FetchTimeCode(&timeCode, pabyRecordHeader, nullptr);
1103
1104
0
        CPL_IGNORE_RET_VAL(
1105
0
            VSIFPrintfL(fpCSV, "%d,%d,%d,%d,%d,", nScanlineNumber, nBlockYOff,
1106
0
                        (int)timeCode.GetYear(), (int)timeCode.GetDay(),
1107
0
                        (int)timeCode.GetMillisecond()));
1108
0
        CPL_IGNORE_RET_VAL(VSIFPrintfL(
1109
0
            fpCSV, "%d,%d,%d,%d,%d,%d,%d,%d,", (pabyRecordHeader[8] >> 7) & 1,
1110
0
            (pabyRecordHeader[8] >> 6) & 1, (pabyRecordHeader[8] >> 5) & 1,
1111
0
            (pabyRecordHeader[8] >> 4) & 1, (pabyRecordHeader[8] >> 3) & 1,
1112
0
            (pabyRecordHeader[8] >> 2) & 1, (pabyRecordHeader[8] >> 1) & 1,
1113
0
            (pabyRecordHeader[8] >> 0) & 1));
1114
0
        CPL_IGNORE_RET_VAL(VSIFPrintfL(
1115
0
            fpCSV, "%d,%d,%d,%d,%d,%d,%d,%d,", (pabyRecordHeader[9] >> 7) & 1,
1116
0
            (pabyRecordHeader[9] >> 6) & 1, (pabyRecordHeader[9] >> 5) & 1,
1117
0
            (pabyRecordHeader[9] >> 4) & 1, (pabyRecordHeader[9] >> 3) & 1,
1118
0
            (pabyRecordHeader[9] >> 2) & 1, (pabyRecordHeader[9] >> 1) & 1,
1119
0
            (pabyRecordHeader[9] >> 0) & 1));
1120
0
        CPL_IGNORE_RET_VAL(VSIFPrintfL(
1121
0
            fpCSV, "%d,%d,%d,%d,%d,", (pabyRecordHeader[10] >> 7) & 1,
1122
0
            (pabyRecordHeader[10] >> 6) & 1, (pabyRecordHeader[10] >> 5) & 1,
1123
0
            (pabyRecordHeader[10] >> 4) & 1, (pabyRecordHeader[10] >> 3) & 1));
1124
0
        CPL_IGNORE_RET_VAL(
1125
0
            VSIFPrintfL(fpCSV, "%d,", pabyRecordHeader[11] >> 2));
1126
0
        GInt32 i32;
1127
0
        for (int i = 0; i < 10; i++)
1128
0
        {
1129
0
            i32 = GetInt32(pabyRecordHeader + 12 + 4 * i);
1130
            /* Scales :
1131
             * http://www.ncdc.noaa.gov/oa/pod-guide/ncdc/docs/podug/html/c3/sec3-3.htm
1132
             */
1133
0
            if ((i % 2) == 0)
1134
0
                CPL_IGNORE_RET_VAL(
1135
0
                    VSIFPrintfL(fpCSV, "%f,", i32 / pow(2.0, 30.0)));
1136
0
            else
1137
0
                CPL_IGNORE_RET_VAL(
1138
0
                    VSIFPrintfL(fpCSV, "%f,", i32 / pow(2.0, 22.0)));
1139
0
        }
1140
0
        CPL_IGNORE_RET_VAL(VSIFPrintfL(fpCSV, "%d", pabyRecordHeader[52]));
1141
0
        CPL_IGNORE_RET_VAL(VSIFPrintfL(fpCSV, "\n"));
1142
0
    }
1143
1144
0
    CPLFree(pabyRecordHeader);
1145
0
    CPL_IGNORE_RET_VAL(VSIFCloseL(fpCSV));
1146
0
}
1147
1148
/************************************************************************/
1149
/*                        FetchMetadataNOAA15()                         */
1150
/************************************************************************/
1151
1152
void L1BDataset::FetchMetadataNOAA15()
1153
0
{
1154
0
    int i, j;
1155
0
    std::string osDir = CPLGetConfigOption("L1B_METADATA_DIRECTORY", "");
1156
0
    if (osDir.empty())
1157
0
    {
1158
0
        osDir = CPLGetPathSafe(GetDescription());
1159
0
        if (osDir.empty())
1160
0
            osDir = ".";
1161
0
    }
1162
0
    const std::string osMetadataFile =
1163
0
        std::string(osDir)
1164
0
            .append("/")
1165
0
            .append(CPLGetFilename(GetDescription()))
1166
0
            .append("_metadata.csv");
1167
0
    CPL_IGNORE_RET_VAL(osDir);
1168
0
    VSILFILE *fpCSV = VSIFOpenL(osMetadataFile.c_str(), "wb");
1169
0
    if (fpCSV == nullptr)
1170
0
    {
1171
0
        CPLError(CE_Failure, CPLE_AppDefined,
1172
0
                 "Cannot create metadata file : %s", osMetadataFile.c_str());
1173
0
        return;
1174
0
    }
1175
1176
0
    CPL_IGNORE_RET_VAL(VSIFPrintfL(
1177
0
        fpCSV, "SCANLINE,NBLOCKYOFF,YEAR,DAY,MS_IN_DAY,SAT_CLOCK_DRIF_DELTA,"
1178
0
               "SOUTHBOUND,SCANTIME_CORRECTED,C3_SELECT,"));
1179
0
    CPL_IGNORE_RET_VAL(VSIFPrintfL(
1180
0
        fpCSV,
1181
0
        "FATAL_FLAG,TIME_ERROR,DATA_GAP,INSUFFICIENT_DATA_FOR_CAL,"
1182
0
        "NO_EARTH_LOCATION,FIRST_GOOD_TIME_AFTER_CLOCK_UPDATE,"
1183
0
        "INSTRUMENT_STATUS_CHANGED,SYNC_LOCK_DROPPED,"
1184
0
        "FRAME_SYNC_ERROR,FRAME_SYNC_DROPPED_LOCK,FLYWHEELING,"
1185
0
        "BIT_SLIPPAGE,TIP_PARITY_ERROR,REFLECTED_SUNLIGHT_C3B,"
1186
0
        "REFLECTED_SUNLIGHT_C4,REFLECTED_SUNLIGHT_C5,RESYNC,P_N_STATUS,"));
1187
0
    CPL_IGNORE_RET_VAL(VSIFPrintfL(
1188
0
        fpCSV, "BAD_TIME_CAN_BE_INFERRED,BAD_TIME_CANNOT_BE_INFERRED,"
1189
0
               "TIME_DISCONTINUITY,REPEAT_SCAN_TIME,"));
1190
0
    CPL_IGNORE_RET_VAL(
1191
0
        VSIFPrintfL(fpCSV, "UNCALIBRATED_BAD_TIME,CALIBRATED_FEWER_SCANLINES,"
1192
0
                           "UNCALIBRATED_BAD_PRT,CALIBRATED_MARGINAL_PRT,"
1193
0
                           "UNCALIBRATED_CHANNELS,"));
1194
0
    CPL_IGNORE_RET_VAL(VSIFPrintfL(
1195
0
        fpCSV, "NO_EARTH_LOC_BAD_TIME,EARTH_LOC_QUESTIONABLE_TIME,"
1196
0
               "EARTH_LOC_QUESTIONABLE,EARTH_LOC_VERY_QUESTIONABLE,"));
1197
0
    CPL_IGNORE_RET_VAL(VSIFPrintfL(
1198
0
        fpCSV,
1199
0
        "C3B_UNCALIBRATED,C3B_QUESTIONABLE,C3B_ALL_BLACKBODY,"
1200
0
        "C3B_ALL_SPACEVIEW,C3B_MARGINAL_BLACKBODY,C3B_MARGINAL_SPACEVIEW,"));
1201
0
    CPL_IGNORE_RET_VAL(VSIFPrintfL(
1202
0
        fpCSV,
1203
0
        "C4_UNCALIBRATED,C4_QUESTIONABLE,C4_ALL_BLACKBODY,"
1204
0
        "C4_ALL_SPACEVIEW,C4_MARGINAL_BLACKBODY,C4_MARGINAL_SPACEVIEW,"));
1205
0
    CPL_IGNORE_RET_VAL(VSIFPrintfL(
1206
0
        fpCSV,
1207
0
        "C5_UNCALIBRATED,C5_QUESTIONABLE,C5_ALL_BLACKBODY,"
1208
0
        "C5_ALL_SPACEVIEW,C5_MARGINAL_BLACKBODY,C5_MARGINAL_SPACEVIEW,"));
1209
0
    CPL_IGNORE_RET_VAL(VSIFPrintfL(fpCSV, "BIT_ERRORS,"));
1210
0
    for (i = 0; i < 3; i++)
1211
0
    {
1212
0
        const char *pszChannel = (i == 0) ? "C1" : (i == 1) ? "C2" : "C3A";
1213
0
        for (j = 0; j < 3; j++)
1214
0
        {
1215
0
            const char *pszType = (j == 0)   ? "OP"
1216
0
                                  : (j == 1) ? "TEST"
1217
0
                                             : "PRELAUNCH";
1218
0
            CPL_IGNORE_RET_VAL(VSIFPrintfL(fpCSV, "VIS_%s_CAL_%s_SLOPE_1,",
1219
0
                                           pszType, pszChannel));
1220
0
            CPL_IGNORE_RET_VAL(VSIFPrintfL(fpCSV, "VIS_%s_CAL_%s_INTERCEPT_1,",
1221
0
                                           pszType, pszChannel));
1222
0
            CPL_IGNORE_RET_VAL(VSIFPrintfL(fpCSV, "VIS_%s_CAL_%s_SLOPE_2,",
1223
0
                                           pszType, pszChannel));
1224
0
            CPL_IGNORE_RET_VAL(VSIFPrintfL(fpCSV, "VIS_%s_CAL_%s_INTERCEPT_2,",
1225
0
                                           pszType, pszChannel));
1226
0
            CPL_IGNORE_RET_VAL(VSIFPrintfL(fpCSV, "VIS_%s_CAL_%s_INTERSECTION,",
1227
0
                                           pszType, pszChannel));
1228
0
        }
1229
0
    }
1230
0
    for (i = 0; i < 3; i++)
1231
0
    {
1232
0
        const char *pszChannel = (i == 0) ? "C3B" : (i == 1) ? "C4" : "C5";
1233
0
        for (j = 0; j < 2; j++)
1234
0
        {
1235
0
            const char *pszType = (j == 0) ? "OP" : "TEST";
1236
0
            CPL_IGNORE_RET_VAL(VSIFPrintfL(fpCSV, "IR_%s_CAL_%s_COEFF_1,",
1237
0
                                           pszType, pszChannel));
1238
0
            CPL_IGNORE_RET_VAL(VSIFPrintfL(fpCSV, "IR_%s_CAL_%s_COEFF_2,",
1239
0
                                           pszType, pszChannel));
1240
0
            CPL_IGNORE_RET_VAL(VSIFPrintfL(fpCSV, "IR_%s_CAL_%s_COEFF_3,",
1241
0
                                           pszType, pszChannel));
1242
0
        }
1243
0
    }
1244
0
    CPL_IGNORE_RET_VAL(VSIFPrintfL(
1245
0
        fpCSV, "EARTH_LOC_CORR_TIP_EULER,EARTH_LOC_IND,"
1246
0
               "SPACECRAFT_ATT_CTRL,ATT_SMODE,ATT_PASSIVE_WHEEL_TEST,"
1247
0
               "TIME_TIP_EULER,TIP_EULER_ROLL,TIP_EULER_PITCH,TIP_EULER_YAW,"
1248
0
               "SPACECRAFT_ALT"));
1249
0
    CPL_IGNORE_RET_VAL(VSIFPrintfL(fpCSV, "\n"));
1250
1251
0
    GByte *pabyRecordHeader = (GByte *)CPLMalloc(nRecordDataStart);
1252
0
    GInt16 i16;
1253
0
    GUInt16 n16;
1254
0
    GInt32 i32;
1255
0
    GUInt32 n32;
1256
1257
0
    for (int nBlockYOff = 0; nBlockYOff < nRasterYSize; nBlockYOff++)
1258
0
    {
1259
        /* --------------------------------------------------------------------
1260
         */
1261
        /*      Seek to data. */
1262
        /* --------------------------------------------------------------------
1263
         */
1264
0
        CPL_IGNORE_RET_VAL(VSIFSeekL(fp, GetLineOffset(nBlockYOff), SEEK_SET));
1265
1266
0
        CPL_IGNORE_RET_VAL(
1267
0
            VSIFReadL(pabyRecordHeader, 1, nRecordDataStart, fp));
1268
1269
0
        GUInt16 nScanlineNumber = GetUInt16(pabyRecordHeader);
1270
1271
0
        TimeCode timeCode;
1272
0
        FetchTimeCode(&timeCode, pabyRecordHeader, nullptr);
1273
1274
        /* Clock drift delta */
1275
0
        i16 = GetInt16(pabyRecordHeader + 6);
1276
        /* Scanline bit field */
1277
0
        n16 = GetInt16(pabyRecordHeader + 12);
1278
1279
0
        CPL_IGNORE_RET_VAL(
1280
0
            VSIFPrintfL(fpCSV, "%d,%d,%d,%d,%d,%d,%d,%d,%d,", nScanlineNumber,
1281
0
                        nBlockYOff, (int)timeCode.GetYear(),
1282
0
                        (int)timeCode.GetDay(), (int)timeCode.GetMillisecond(),
1283
0
                        i16, (n16 >> 15) & 1, (n16 >> 14) & 1, (n16) & 3));
1284
1285
0
        n32 = GetUInt32(pabyRecordHeader + 24);
1286
0
        CPL_IGNORE_RET_VAL(VSIFPrintfL(
1287
0
            fpCSV, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,",
1288
0
            (n32 >> 31) & 1, (n32 >> 30) & 1, (n32 >> 29) & 1, (n32 >> 28) & 1,
1289
0
            (n32 >> 27) & 1, (n32 >> 26) & 1, (n32 >> 25) & 1, (n32 >> 24) & 1,
1290
0
            (n32 >> 23) & 1, (n32 >> 22) & 1, (n32 >> 21) & 1, (n32 >> 20) & 1,
1291
0
            (n32 >> 8) & 1, (n32 >> 6) & 3, (n32 >> 4) & 3, (n32 >> 2) & 3,
1292
0
            (n32 >> 1) & 1, (n32 >> 0) & 1));
1293
1294
0
        n32 = GetUInt32(pabyRecordHeader + 28);
1295
0
        CPL_IGNORE_RET_VAL(VSIFPrintfL(
1296
0
            fpCSV, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,", (n32 >> 23) & 1,
1297
0
            (n32 >> 22) & 1, (n32 >> 21) & 1, (n32 >> 20) & 1, (n32 >> 15) & 1,
1298
0
            (n32 >> 14) & 1, (n32 >> 13) & 1, (n32 >> 12) & 1, (n32 >> 11) & 1,
1299
0
            (n32 >> 7) & 1, (n32 >> 6) & 1, (n32 >> 5) & 1, (n32 >> 4) & 1));
1300
1301
0
        for (i = 0; i < 3; i++)
1302
0
        {
1303
0
            n16 = GetUInt16(pabyRecordHeader + 32 + 2 * i);
1304
0
            CPL_IGNORE_RET_VAL(VSIFPrintfL(fpCSV, "%d,%d,%d,%d,%d,%d,",
1305
0
                                           (n16 >> 7) & 1, (n16 >> 6) & 1,
1306
0
                                           (n16 >> 5) & 1, (n16 >> 4) & 1,
1307
0
                                           (n16 >> 2) & 1, (n16 >> 1) & 1));
1308
0
        }
1309
1310
        /* Bit errors */
1311
0
        n16 = GetUInt16(pabyRecordHeader + 38);
1312
0
        CPL_IGNORE_RET_VAL(VSIFPrintfL(fpCSV, "%d,", n16));
1313
1314
0
        int nOffset = 48;
1315
0
        for (i = 0; i < 3; i++)
1316
0
        {
1317
0
            for (j = 0; j < 3; j++)
1318
0
            {
1319
0
                i32 = GetInt32(pabyRecordHeader + nOffset);
1320
0
                nOffset += 4;
1321
0
                CPL_IGNORE_RET_VAL(
1322
0
                    VSIFPrintfL(fpCSV, "%f,", i32 / pow(10.0, 7.0)));
1323
0
                i32 = GetInt32(pabyRecordHeader + nOffset);
1324
0
                nOffset += 4;
1325
0
                CPL_IGNORE_RET_VAL(
1326
0
                    VSIFPrintfL(fpCSV, "%f,", i32 / pow(10.0, 6.0)));
1327
0
                i32 = GetInt32(pabyRecordHeader + nOffset);
1328
0
                nOffset += 4;
1329
0
                CPL_IGNORE_RET_VAL(
1330
0
                    VSIFPrintfL(fpCSV, "%f,", i32 / pow(10.0, 7.0)));
1331
0
                i32 = GetInt32(pabyRecordHeader + nOffset);
1332
0
                nOffset += 4;
1333
0
                CPL_IGNORE_RET_VAL(
1334
0
                    VSIFPrintfL(fpCSV, "%f,", i32 / pow(10.0, 6.0)));
1335
0
                i32 = GetInt32(pabyRecordHeader + nOffset);
1336
0
                nOffset += 4;
1337
0
                CPL_IGNORE_RET_VAL(VSIFPrintfL(fpCSV, "%d,", i32));
1338
0
            }
1339
0
        }
1340
0
        for (i = 0; i < 18; i++)
1341
0
        {
1342
0
            i32 = GetInt32(pabyRecordHeader + nOffset);
1343
0
            nOffset += 4;
1344
0
            CPL_IGNORE_RET_VAL(VSIFPrintfL(fpCSV, "%f,", i32 / pow(10.0, 6.0)));
1345
0
        }
1346
1347
0
        n32 = GetUInt32(pabyRecordHeader + 312);
1348
0
        CPL_IGNORE_RET_VAL(VSIFPrintfL(
1349
0
            fpCSV, "%d,%d,%d,%d,%d,", (n32 >> 16) & 1, (n32 >> 12) & 15,
1350
0
            (n32 >> 8) & 15, (n32 >> 4) & 15, (n32 >> 0) & 15));
1351
1352
0
        n32 = GetUInt32(pabyRecordHeader + 316);
1353
0
        CPL_IGNORE_RET_VAL(VSIFPrintfL(fpCSV, "%d,", n32));
1354
1355
0
        for (i = 0; i < 3; i++)
1356
0
        {
1357
0
            i16 = GetUInt16(pabyRecordHeader + 320 + 2 * i);
1358
0
            CPL_IGNORE_RET_VAL(VSIFPrintfL(fpCSV, "%f,", i16 / pow(10.0, 3.0)));
1359
0
        }
1360
1361
0
        n16 = GetUInt16(pabyRecordHeader + 326);
1362
0
        CPL_IGNORE_RET_VAL(VSIFPrintfL(fpCSV, "%f", n16 / pow(10.0, 1.0)));
1363
1364
0
        CPL_IGNORE_RET_VAL(VSIFPrintfL(fpCSV, "\n"));
1365
0
    }
1366
1367
0
    CPLFree(pabyRecordHeader);
1368
0
    CPL_IGNORE_RET_VAL(VSIFCloseL(fpCSV));
1369
0
}
1370
1371
/************************************************************************/
1372
/*                            EBCDICToASCII                             */
1373
/************************************************************************/
1374
1375
constexpr GByte EBCDICToASCII[] = {
1376
    0x00, 0x01, 0x02, 0x03, 0x9C, 0x09, 0x86, 0x7F, 0x97, 0x8D, 0x8E, 0x0B,
1377
    0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x9D, 0x85, 0x08, 0x87,
1378
    0x18, 0x19, 0x92, 0x8F, 0x1C, 0x1D, 0x1E, 0x1F, 0x80, 0x81, 0x82, 0x83,
1379
    0x84, 0x0A, 0x17, 0x1B, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x05, 0x06, 0x07,
1380
    0x90, 0x91, 0x16, 0x93, 0x94, 0x95, 0x96, 0x04, 0x98, 0x99, 0x9A, 0x9B,
1381
    0x14, 0x15, 0x9E, 0x1A, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1382
    0x00, 0x00, 0xA2, 0x2E, 0x3C, 0x28, 0x2B, 0x7C, 0x26, 0x00, 0x00, 0x00,
1383
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x24, 0x2A, 0x29, 0x3B, 0xAC,
1384
    0x2D, 0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA6, 0x2C,
1385
    0x25, 0x5F, 0x3E, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1386
    0x00, 0x60, 0x3A, 0x23, 0x40, 0x27, 0x3D, 0x22, 0x00, 0x61, 0x62, 0x63,
1387
    0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1388
    0x00, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x00, 0x00,
1389
    0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
1390
    0x79, 0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1391
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1392
    0x7B, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x00, 0x00,
1393
    0x00, 0x00, 0x00, 0x00, 0x7D, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50,
1394
    0x51, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5C, 0x00, 0x53, 0x54,
1395
    0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1396
    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x00, 0x00,
1397
    0x00, 0x00, 0x00, 0x9F,
1398
};
1399
1400
/************************************************************************/
1401
/*                        ProcessDatasetHeader()                        */
1402
/************************************************************************/
1403
1404
CPLErr L1BDataset::ProcessDatasetHeader(const char *pszFilename)
1405
4.26k
{
1406
4.26k
    char szDatasetName[L1B_DATASET_NAME_SIZE + 1];
1407
1408
4.26k
    if (eL1BFormat == L1B_NOAA9)
1409
2.90k
    {
1410
2.90k
        GByte abyTBMHeader[L1B_NOAA9_HEADER_SIZE];
1411
1412
2.90k
        if (VSIFSeekL(fp, 0, SEEK_SET) < 0 ||
1413
2.90k
            VSIFReadL(abyTBMHeader, 1, L1B_NOAA9_HEADER_SIZE, fp) <
1414
2.90k
                L1B_NOAA9_HEADER_SIZE)
1415
0
        {
1416
0
            CPLDebug("L1B", "Can't read NOAA-9/14 TBM header.");
1417
0
            return CE_Failure;
1418
0
        }
1419
1420
        // If dataset name in EBCDIC, decode it in ASCII
1421
2.90k
        if (*(abyTBMHeader + 8 + 25) == 'K' &&
1422
1.24k
            *(abyTBMHeader + 8 + 30) == 'K' &&
1423
1.24k
            *(abyTBMHeader + 8 + 33) == 'K' &&
1424
1.24k
            *(abyTBMHeader + 8 + 40) == 'K' &&
1425
1.24k
            *(abyTBMHeader + 8 + 46) == 'K' &&
1426
1.24k
            *(abyTBMHeader + 8 + 52) == 'K' && *(abyTBMHeader + 8 + 61) == 'K')
1427
1.24k
        {
1428
53.4k
            for (int i = 0; i < L1B_DATASET_NAME_SIZE; i++)
1429
52.2k
                abyTBMHeader[L1B_NOAA9_HDR_NAME_OFF + i] =
1430
52.2k
                    EBCDICToASCII[abyTBMHeader[L1B_NOAA9_HDR_NAME_OFF + i]];
1431
1.24k
        }
1432
1433
        // Fetch dataset name. NOAA-9/14 datasets contain the names in TBM
1434
        // header only, so read it there.
1435
2.90k
        memcpy(szDatasetName, abyTBMHeader + L1B_NOAA9_HDR_NAME_OFF,
1436
2.90k
               L1B_DATASET_NAME_SIZE);
1437
2.90k
        szDatasetName[L1B_DATASET_NAME_SIZE] = '\0';
1438
1439
        // Deal with a few NOAA <= 9 datasets with no dataset name in TBM header
1440
2.90k
        if (memcmp(szDatasetName,
1441
2.90k
                   "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
1442
2.90k
                   "\0\0\0\0\0\0\0\0\0\0\0\0\0",
1443
2.90k
                   L1B_DATASET_NAME_SIZE) == 0 &&
1444
0
            strlen(pszFilename) == L1B_DATASET_NAME_SIZE)
1445
0
        {
1446
0
            strcpy(szDatasetName, pszFilename);
1447
0
        }
1448
1449
        // Determine processing center where the dataset was created
1450
2.90k
        if (STARTS_WITH_CI(szDatasetName, "CMS"))
1451
0
            eProcCenter = CMS;
1452
2.90k
        else if (STARTS_WITH_CI(szDatasetName, "DSS"))
1453
0
            eProcCenter = DSS;
1454
2.90k
        else if (STARTS_WITH_CI(szDatasetName, "NSS"))
1455
50
            eProcCenter = NSS;
1456
2.85k
        else if (STARTS_WITH_CI(szDatasetName, "UKM"))
1457
0
            eProcCenter = UKM;
1458
2.85k
        else
1459
2.85k
            eProcCenter = UNKNOWN_CENTER;
1460
1461
        // Determine number of bands
1462
2.90k
        int i;
1463
60.9k
        for (i = 0; i < L1B_NOAA9_HDR_CHAN_SIZE; i++)
1464
58.0k
        {
1465
58.0k
            if (abyTBMHeader[L1B_NOAA9_HDR_CHAN_OFF + i] == 1 ||
1466
56.7k
                abyTBMHeader[L1B_NOAA9_HDR_CHAN_OFF + i] == 'Y')
1467
1.50k
            {
1468
1.50k
                nBands++;
1469
1.50k
                iChannelsMask |= (1 << i);
1470
1.50k
            }
1471
58.0k
        }
1472
2.90k
        if (nBands == 0 || nBands > 5)
1473
1.68k
        {
1474
1.68k
            nBands = 5;
1475
1.68k
            iChannelsMask = 0x1F;
1476
1.68k
        }
1477
1478
        // Determine data format (10-bit packed or 8/16-bit unpacked)
1479
2.90k
        if (STARTS_WITH_CI((const char *)abyTBMHeader + L1B_NOAA9_HDR_WORD_OFF,
1480
2.90k
                           "10"))
1481
323
            iDataFormat = PACKED10BIT;
1482
2.58k
        else if (STARTS_WITH_CI(
1483
2.58k
                     (const char *)abyTBMHeader + L1B_NOAA9_HDR_WORD_OFF, "16"))
1484
152
            iDataFormat = UNPACKED16BIT;
1485
2.42k
        else if (STARTS_WITH_CI(
1486
2.42k
                     (const char *)abyTBMHeader + L1B_NOAA9_HDR_WORD_OFF, "08"))
1487
605
            iDataFormat = UNPACKED8BIT;
1488
1.82k
        else if (STARTS_WITH_CI((const char *)abyTBMHeader +
1489
1.66k
                                    L1B_NOAA9_HDR_WORD_OFF,
1490
1.66k
                                "  ") ||
1491
1.66k
                 abyTBMHeader[L1B_NOAA9_HDR_WORD_OFF] == '\0')
1492
            /* Empty string can be found in the following samples :
1493
                http://www2.ncdc.noaa.gov/docs/podug/data/avhrr/franh.1b (10
1494
               bit) http://www2.ncdc.noaa.gov/docs/podug/data/avhrr/frang.1b (10
1495
               bit) http://www2.ncdc.noaa.gov/docs/podug/data/avhrr/calfilel.1b
1496
               (16 bit)
1497
                http://www2.ncdc.noaa.gov/docs/podug/data/avhrr/rapnzg.1b (16
1498
               bit)
1499
                ftp://ftp.sat.dundee.ac.uk/misc/testdata/noaa12/hrptnoaa1b.dat
1500
               (10 bit)
1501
            */
1502
1.23k
            bGuessDataFormat = TRUE;
1503
590
        else
1504
590
        {
1505
#ifdef DEBUG
1506
            CPLDebug("L1B", "Unknown data format \"%.2s\".",
1507
                     abyTBMHeader + L1B_NOAA9_HDR_WORD_OFF);
1508
#endif
1509
590
            return CE_Failure;
1510
590
        }
1511
1512
        // Now read the dataset header record
1513
2.31k
        GByte abyRecHeader[L1B_NOAA9_HDR_REC_SIZE];
1514
2.31k
        if (VSIFSeekL(fp, L1B_NOAA9_HEADER_SIZE, SEEK_SET) < 0 ||
1515
2.31k
            VSIFReadL(abyRecHeader, 1, L1B_NOAA9_HDR_REC_SIZE, fp) <
1516
2.31k
                L1B_NOAA9_HDR_REC_SIZE)
1517
27
        {
1518
27
            CPLDebug("L1B", "Can't read NOAA-9/14 record header.");
1519
27
            return CE_Failure;
1520
27
        }
1521
1522
        // Determine the spacecraft name
1523
2.28k
        switch (abyRecHeader[L1B_NOAA9_HDR_REC_ID_OFF])
1524
2.28k
        {
1525
12
            case 4:
1526
12
                eSpacecraftID = NOAA7;
1527
12
                break;
1528
0
            case 6:
1529
0
                eSpacecraftID = NOAA8;
1530
0
                break;
1531
7
            case 7:
1532
7
                eSpacecraftID = NOAA9;
1533
7
                break;
1534
8
            case 8:
1535
8
                eSpacecraftID = NOAA10;
1536
8
                break;
1537
45
            case 1:
1538
45
            {
1539
                /* We could also use the time code to determine TIROS-N */
1540
45
                if (strlen(pszFilename) == L1B_DATASET_NAME_SIZE &&
1541
0
                    STARTS_WITH(pszFilename + 8, ".TN."))
1542
0
                    eSpacecraftID = TIROSN;
1543
45
                else
1544
45
                    eSpacecraftID = NOAA11;
1545
45
                break;
1546
0
            }
1547
49
            case 5:
1548
49
                eSpacecraftID = NOAA12;
1549
49
                break;
1550
0
            case 2:
1551
0
            {
1552
                /* We could also use the time code to determine NOAA6 */
1553
0
                if (strlen(pszFilename) == L1B_DATASET_NAME_SIZE &&
1554
0
                    STARTS_WITH(pszFilename + 8, ".NA."))
1555
0
                    eSpacecraftID = NOAA6;
1556
0
                else
1557
0
                    eSpacecraftID = NOAA13;
1558
0
                break;
1559
0
            }
1560
2
            case 3:
1561
2
                eSpacecraftID = NOAA14;
1562
2
                break;
1563
2.16k
            default:
1564
2.16k
                CPLError(CE_Warning, CPLE_AppDefined,
1565
2.16k
                         "Unknown spacecraft ID \"%d\".",
1566
2.16k
                         abyRecHeader[L1B_NOAA9_HDR_REC_ID_OFF]);
1567
1568
2.16k
                eSpacecraftID = NOAA9_UNKNOWN;
1569
2.16k
                break;
1570
2.28k
        }
1571
1572
        // Determine the product data type
1573
2.28k
        int iWord = abyRecHeader[L1B_NOAA9_HDR_REC_PROD_OFF] >> 4;
1574
2.28k
        switch (iWord)
1575
2.28k
        {
1576
8
            case 1:
1577
8
                eProductType = LAC;
1578
8
                break;
1579
1.17k
            case 2:
1580
1.17k
                eProductType = GAC;
1581
1.17k
                break;
1582
1.02k
            case 3:
1583
1.02k
                eProductType = HRPT;
1584
1.02k
                break;
1585
77
            default:
1586
#ifdef DEBUG
1587
                CPLDebug("L1B", "Unknown product type \"%d\".", iWord);
1588
#endif
1589
77
                return CE_Failure;
1590
2.28k
        }
1591
1592
        // Determine receiving station name
1593
2.20k
        iWord = (abyRecHeader[L1B_NOAA9_HDR_REC_DSTAT_OFF] & 0x60) >> 5;
1594
2.20k
        switch (iWord)
1595
2.20k
        {
1596
320
            case 1:
1597
320
                eSource = GC;
1598
320
                break;
1599
446
            case 2:
1600
446
                eSource = WI;
1601
446
                break;
1602
783
            case 3:
1603
783
                eSource = SO;
1604
783
                break;
1605
660
            default:
1606
660
                eSource = UNKNOWN_STATION;
1607
660
                break;
1608
2.20k
        }
1609
2.20k
    }
1610
1611
1.36k
    else if (eL1BFormat == L1B_NOAA15 || eL1BFormat == L1B_NOAA15_NOHDR)
1612
1.36k
    {
1613
1.36k
        if (eL1BFormat == L1B_NOAA15)
1614
720
        {
1615
720
            GByte abyARSHeader[L1B_NOAA15_HEADER_SIZE];
1616
1617
720
            if (VSIFSeekL(fp, 0, SEEK_SET) < 0 ||
1618
720
                VSIFReadL(abyARSHeader, 1, L1B_NOAA15_HEADER_SIZE, fp) <
1619
720
                    L1B_NOAA15_HEADER_SIZE)
1620
0
            {
1621
0
                CPLDebug("L1B", "Can't read NOAA-15 ARS header.");
1622
0
                return CE_Failure;
1623
0
            }
1624
1625
            // Determine number of bands
1626
720
            int i;
1627
15.1k
            for (i = 0; i < L1B_NOAA15_HDR_CHAN_SIZE; i++)
1628
14.4k
            {
1629
14.4k
                if (abyARSHeader[L1B_NOAA15_HDR_CHAN_OFF + i] == 1 ||
1630
13.6k
                    abyARSHeader[L1B_NOAA15_HDR_CHAN_OFF + i] == 'Y')
1631
945
                {
1632
945
                    nBands++;
1633
945
                    iChannelsMask |= (1 << i);
1634
945
                }
1635
14.4k
            }
1636
720
            if (nBands == 0 || nBands > 5)
1637
67
            {
1638
67
                nBands = 5;
1639
67
                iChannelsMask = 0x1F;
1640
67
            }
1641
1642
            // Determine data format (10-bit packed or 8/16-bit unpacked)
1643
720
            if (STARTS_WITH_CI(
1644
720
                    (const char *)abyARSHeader + L1B_NOAA15_HDR_WORD_OFF, "10"))
1645
1
                iDataFormat = PACKED10BIT;
1646
719
            else if (STARTS_WITH_CI((const char *)abyARSHeader +
1647
719
                                        L1B_NOAA15_HDR_WORD_OFF,
1648
719
                                    "16"))
1649
0
                iDataFormat = UNPACKED16BIT;
1650
719
            else if (STARTS_WITH_CI((const char *)abyARSHeader +
1651
719
                                        L1B_NOAA15_HDR_WORD_OFF,
1652
719
                                    "08"))
1653
4
                iDataFormat = UNPACKED8BIT;
1654
715
            else
1655
715
            {
1656
#ifdef DEBUG
1657
                CPLDebug("L1B", "Unknown data format \"%.2s\".",
1658
                         abyARSHeader + L1B_NOAA9_HDR_WORD_OFF);
1659
#endif
1660
715
                return CE_Failure;
1661
715
            }
1662
720
        }
1663
640
        else
1664
640
        {
1665
640
            nBands = 5;
1666
640
            iChannelsMask = 0x1F;
1667
640
            iDataFormat = PACKED10BIT;
1668
640
        }
1669
1670
        // Now read the dataset header record
1671
645
        GByte abyRecHeader[L1B_NOAA15_HDR_REC_SIZE];
1672
645
        if (VSIFSeekL(fp,
1673
645
                      (eL1BFormat == L1B_NOAA15) ? L1B_NOAA15_HEADER_SIZE : 0,
1674
645
                      SEEK_SET) < 0 ||
1675
645
            VSIFReadL(abyRecHeader, 1, L1B_NOAA15_HDR_REC_SIZE, fp) <
1676
645
                L1B_NOAA15_HDR_REC_SIZE)
1677
90
        {
1678
90
            CPLDebug("L1B", "Can't read NOAA-9/14 record header.");
1679
90
            return CE_Failure;
1680
90
        }
1681
1682
        // Fetch dataset name
1683
555
        memcpy(szDatasetName, abyRecHeader + L1B_NOAA15_HDR_REC_NAME_OFF,
1684
555
               L1B_DATASET_NAME_SIZE);
1685
555
        szDatasetName[L1B_DATASET_NAME_SIZE] = '\0';
1686
1687
        // Determine processing center where the dataset was created
1688
555
        if (STARTS_WITH_CI((const char *)abyRecHeader +
1689
555
                               L1B_NOAA15_HDR_REC_SITE_OFF,
1690
555
                           "CMS"))
1691
0
            eProcCenter = CMS;
1692
555
        else if (STARTS_WITH_CI((const char *)abyRecHeader +
1693
555
                                    L1B_NOAA15_HDR_REC_SITE_OFF,
1694
555
                                "DSS"))
1695
0
            eProcCenter = DSS;
1696
555
        else if (STARTS_WITH_CI((const char *)abyRecHeader +
1697
555
                                    L1B_NOAA15_HDR_REC_SITE_OFF,
1698
555
                                "NSS"))
1699
0
            eProcCenter = NSS;
1700
555
        else if (STARTS_WITH_CI((const char *)abyRecHeader +
1701
555
                                    L1B_NOAA15_HDR_REC_SITE_OFF,
1702
555
                                "UKM"))
1703
0
            eProcCenter = UKM;
1704
555
        else
1705
555
            eProcCenter = UNKNOWN_CENTER;
1706
1707
555
        int nFormatVersionYear, nFormatVersionDayOfYear, nHeaderRecCount;
1708
1709
        /* Some products from NOAA-18 and NOAA-19 coming from 'ess' processing
1710
         * station */
1711
        /* have little-endian ordering. Try to detect it with some consistency
1712
         * checks */
1713
555
        int i = 0;
1714
555
        do
1715
1.58k
        {
1716
1.58k
            nFormatVersionYear = GetUInt16(
1717
1.58k
                abyRecHeader + L1B_NOAA15_HDR_REC_FORMAT_VERSION_YEAR_OFF);
1718
1.58k
            nFormatVersionDayOfYear = GetUInt16(
1719
1.58k
                abyRecHeader + L1B_NOAA15_HDR_REC_FORMAT_VERSION_DAY_OFF);
1720
1.58k
            nHeaderRecCount =
1721
1.58k
                GetUInt16(abyRecHeader + L1B_NOAA15_HDR_REC_HDR_REC_COUNT_OFF);
1722
1.58k
            if (i == 2)
1723
484
                break;
1724
1.10k
            if (!(nFormatVersionYear >= 1980 && nFormatVersionYear <= 2100) &&
1725
1.10k
                !(nFormatVersionDayOfYear <= 366) && !(nHeaderRecCount == 1))
1726
1.03k
            {
1727
1.03k
                if (i == 0)
1728
549
                    CPLDebug("L1B", "Trying little-endian ordering");
1729
484
                else
1730
484
                    CPLDebug("L1B", "Not completely convincing... Returning to "
1731
484
                                    "big-endian order");
1732
1.03k
                bByteSwap = !bByteSwap;
1733
1.03k
            }
1734
71
            else
1735
71
                break;
1736
1.03k
            i++;
1737
1.03k
        } while (i <= 2);
1738
555
        nRecordSizeFromHeader =
1739
555
            GetUInt16(abyRecHeader + L1B_NOAA15_HDR_REC_LOGICAL_REC_LENGTH_OFF);
1740
555
        int nFormatVersion =
1741
555
            GetUInt16(abyRecHeader + L1B_NOAA15_HDR_REC_FORMAT_VERSION_OFF);
1742
555
        CPLDebug("L1B", "NOAA Level 1b Format Version Number = %d",
1743
555
                 nFormatVersion);
1744
555
        CPLDebug("L1B", "Level 1b Format Version Year = %d",
1745
555
                 nFormatVersionYear);
1746
555
        CPLDebug("L1B", "Level 1b Format Version Day of Year = %d",
1747
555
                 nFormatVersionDayOfYear);
1748
555
        CPLDebug("L1B",
1749
555
                 "Logical Record Length of source Level 1b data set prior to "
1750
555
                 "processing = %d",
1751
555
                 nRecordSizeFromHeader);
1752
555
        int nBlockSize =
1753
555
            GetUInt16(abyRecHeader + L1B_NOAA15_HDR_REC_BLOCK_SIZE_OFF);
1754
555
        CPLDebug(
1755
555
            "L1B",
1756
555
            "Block Size of source Level 1b data set prior to processing = %d",
1757
555
            nBlockSize);
1758
555
        CPLDebug("L1B", "Count of Header Records in this Data Set = %d",
1759
555
                 nHeaderRecCount);
1760
1761
555
        int nDataRecordCount =
1762
555
            GetUInt16(abyRecHeader + L1B_NOAA15_HDR_REC_DATA_RECORD_COUNT_OFF);
1763
555
        CPLDebug("L1B", "Count of Data Records = %d", nDataRecordCount);
1764
1765
555
        int nCalibratedScanlineCount = GetUInt16(
1766
555
            abyRecHeader + L1B_NOAA15_HDR_REC_CALIBRATED_SCANLINE_COUNT_OFF);
1767
555
        CPLDebug("L1B", "Count of Calibrated, Earth Located Scan Lines = %d",
1768
555
                 nCalibratedScanlineCount);
1769
1770
555
        int nMissingScanlineCount = GetUInt16(
1771
555
            abyRecHeader + L1B_NOAA15_HDR_REC_MISSING_SCANLINE_COUNT_OFF);
1772
555
        CPLDebug("L1B", "Count of Missing Scan Lines = %d",
1773
555
                 nMissingScanlineCount);
1774
555
        if (nMissingScanlineCount != 0)
1775
482
            bExposeMaskBand = TRUE;
1776
1777
555
        char szEllipsoid[8 + 1];
1778
555
        memcpy(szEllipsoid, abyRecHeader + L1B_NOAA15_HDR_REC_ELLIPSOID_OFF, 8);
1779
555
        szEllipsoid[8] = '\0';
1780
555
        CPLDebug("L1B", "Reference Ellipsoid Model ID = '%s'", szEllipsoid);
1781
555
        if (EQUAL(szEllipsoid, "WGS-84  "))
1782
0
        {
1783
0
            m_oGCPSRS.importFromWkt(SRS_WKT_WGS84_LAT_LONG);
1784
0
        }
1785
555
        else if (EQUAL(szEllipsoid, "  GRS 80"))
1786
0
        {
1787
0
            m_oGCPSRS.importFromWkt(
1788
0
                "GEOGCS[\"GRS 1980(IUGG, "
1789
0
                "1980)\",DATUM[\"unknown\",SPHEROID[\"GRS80\",6378137,298."
1790
0
                "257222101],TOWGS84[0,0,0,0,0,0,0]],PRIMEM[\"Greenwich\",0],"
1791
0
                "UNIT[\"degree\",0.0174532925199433]]");
1792
0
        }
1793
1794
        // Determine the spacecraft name
1795
        // See
1796
        // http://www.ncdc.noaa.gov/oa/pod-guide/ncdc/docs/klm/html/c8/sec83132-2.htm
1797
555
        int iWord = GetUInt16(abyRecHeader + L1B_NOAA15_HDR_REC_ID_OFF);
1798
555
        switch (iWord)
1799
555
        {
1800
21
            case 2:
1801
21
                eSpacecraftID = NOAA16;
1802
21
                break;
1803
170
            case 4:
1804
170
                eSpacecraftID = NOAA15;
1805
170
                break;
1806
11
            case 6:
1807
11
                eSpacecraftID = NOAA17;
1808
11
                break;
1809
0
            case 7:
1810
0
                eSpacecraftID = NOAA18;
1811
0
                break;
1812
0
            case 8:
1813
0
                eSpacecraftID = NOAA19;
1814
0
                break;
1815
42
            case 11:
1816
42
                eSpacecraftID = METOP1;
1817
42
                break;
1818
0
            case 12:
1819
0
                eSpacecraftID = METOP2;
1820
0
                break;
1821
            // METOP3 is not documented yet
1822
0
            case 13:
1823
0
                eSpacecraftID = METOP3;
1824
0
                break;
1825
0
            case 14:
1826
0
                eSpacecraftID = METOP3;
1827
0
                break;
1828
311
            default:
1829
#ifdef DEBUG
1830
                CPLDebug("L1B", "Unknown spacecraft ID \"%d\".", iWord);
1831
#endif
1832
311
                return CE_Failure;
1833
555
        }
1834
1835
        // Determine the product data type
1836
244
        iWord = GetUInt16(abyRecHeader + L1B_NOAA15_HDR_REC_PROD_OFF);
1837
244
        switch (iWord)
1838
244
        {
1839
42
            case 1:
1840
42
                eProductType = LAC;
1841
42
                break;
1842
199
            case 2:
1843
199
                eProductType = GAC;
1844
199
                break;
1845
0
            case 3:
1846
0
                eProductType = HRPT;
1847
0
                break;
1848
1
            case 4:   // XXX: documentation specifies the code '4'
1849
1
            case 13:  // for FRAC but real datasets contain '13 here.'
1850
1
                eProductType = FRAC;
1851
1
                break;
1852
2
            default:
1853
#ifdef DEBUG
1854
                CPLDebug("L1B", "Unknown product type \"%d\".", iWord);
1855
#endif
1856
2
                return CE_Failure;
1857
244
        }
1858
1859
        // Fetch instrument status. Helps to determine whether we have
1860
        // 3A or 3B channel in the dataset.
1861
242
        iInstrumentStatus =
1862
242
            GetUInt32(abyRecHeader + L1B_NOAA15_HDR_REC_STAT_OFF);
1863
1864
        // Determine receiving station name
1865
242
        iWord = GetUInt16(abyRecHeader + L1B_NOAA15_HDR_REC_SRC_OFF);
1866
242
        switch (iWord)
1867
242
        {
1868
30
            case 1:
1869
30
                eSource = GC;
1870
30
                break;
1871
0
            case 2:
1872
0
                eSource = WI;
1873
0
                break;
1874
0
            case 3:
1875
0
                eSource = SO;
1876
0
                break;
1877
1
            case 4:
1878
1
                eSource = SV;
1879
1
                break;
1880
0
            case 5:
1881
0
                eSource = MO;
1882
0
                break;
1883
211
            default:
1884
211
                eSource = UNKNOWN_STATION;
1885
211
                break;
1886
242
        }
1887
242
    }
1888
0
    else
1889
0
        return CE_Failure;
1890
1891
    /* -------------------------------------------------------------------- */
1892
    /*      Set fetched information as metadata records                     */
1893
    /* -------------------------------------------------------------------- */
1894
1895
2.45k
    SetMetadataItem("DATASET_NAME", szDatasetName);
1896
1897
2.45k
    const char *pszText = nullptr;
1898
2.45k
    switch (eSpacecraftID)
1899
2.45k
    {
1900
0
        case TIROSN:
1901
0
            pszText = "TIROS-N";
1902
0
            break;
1903
0
        case NOAA6:
1904
0
            pszText = "NOAA-6(A)";
1905
0
            break;
1906
0
        case NOAAB:
1907
0
            pszText = "NOAA-B";
1908
0
            break;
1909
0
        case NOAA7:
1910
0
            pszText = "NOAA-7(C)";
1911
0
            break;
1912
0
        case NOAA8:
1913
0
            pszText = "NOAA-8(E)";
1914
0
            break;
1915
2.10k
        case NOAA9_UNKNOWN:
1916
2.10k
            pszText = "UNKNOWN";
1917
2.10k
            break;
1918
7
        case NOAA9:
1919
7
            pszText = "NOAA-9(F)";
1920
7
            break;
1921
8
        case NOAA10:
1922
8
            pszText = "NOAA-10(G)";
1923
8
            break;
1924
42
        case NOAA11:
1925
42
            pszText = "NOAA-11(H)";
1926
42
            break;
1927
46
        case NOAA12:
1928
46
            pszText = "NOAA-12(D)";
1929
46
            break;
1930
0
        case NOAA13:
1931
0
            pszText = "NOAA-13(I)";
1932
0
            break;
1933
1
        case NOAA14:
1934
1
            pszText = "NOAA-14(J)";
1935
1
            break;
1936
169
        case NOAA15:
1937
169
            pszText = "NOAA-15(K)";
1938
169
            break;
1939
21
        case NOAA16:
1940
21
            pszText = "NOAA-16(L)";
1941
21
            break;
1942
10
        case NOAA17:
1943
10
            pszText = "NOAA-17(M)";
1944
10
            break;
1945
0
        case NOAA18:
1946
0
            pszText = "NOAA-18(N)";
1947
0
            break;
1948
0
        case NOAA19:
1949
0
            pszText = "NOAA-19(N')";
1950
0
            break;
1951
0
        case METOP2:
1952
0
            pszText = "METOP-A(2)";
1953
0
            break;
1954
42
        case METOP1:
1955
42
            pszText = "METOP-B(1)";
1956
42
            break;
1957
0
        case METOP3:
1958
0
            pszText = "METOP-C(3)";
1959
0
            break;
1960
0
        default:
1961
0
            pszText = "Unknown";
1962
0
            break;
1963
2.45k
    }
1964
2.45k
    SetMetadataItem("SATELLITE", pszText);
1965
1966
2.45k
    switch (eProductType)
1967
2.45k
    {
1968
50
        case LAC:
1969
50
            pszText = "AVHRR LAC";
1970
50
            break;
1971
1.02k
        case HRPT:
1972
1.02k
            pszText = "AVHRR HRPT";
1973
1.02k
            break;
1974
1.37k
        case GAC:
1975
1.37k
            pszText = "AVHRR GAC";
1976
1.37k
            break;
1977
1
        case FRAC:
1978
1
            pszText = "AVHRR FRAC";
1979
1
            break;
1980
0
        default:
1981
0
            pszText = "Unknown";
1982
0
            break;
1983
2.45k
    }
1984
2.45k
    SetMetadataItem("DATA_TYPE", pszText);
1985
1986
    // Get revolution number as string, we don't need this value for processing
1987
2.45k
    char szRevolution[6];
1988
2.45k
    memcpy(szRevolution, szDatasetName + 32, 5);
1989
2.45k
    szRevolution[5] = '\0';
1990
2.45k
    SetMetadataItem("REVOLUTION", szRevolution);
1991
1992
2.45k
    switch (eSource)
1993
2.45k
    {
1994
0
        case DU:
1995
0
            pszText = "Dundee, Scotland, UK";
1996
0
            break;
1997
350
        case GC:
1998
350
            pszText = "Fairbanks, Alaska, USA (formerly Gilmore Creek)";
1999
350
            break;
2000
0
        case HO:
2001
0
            pszText = "Honolulu, Hawaii, USA";
2002
0
            break;
2003
0
        case MO:
2004
0
            pszText = "Monterey, California, USA";
2005
0
            break;
2006
0
        case WE:
2007
0
            pszText = "Western Europe CDA, Lannion, France";
2008
0
            break;
2009
783
        case SO:
2010
783
            pszText = "SOCC (Satellite Operations Control Center), Suitland, "
2011
783
                      "Maryland, USA";
2012
783
            break;
2013
446
        case WI:
2014
446
            pszText = "Wallops Island, Virginia, USA";
2015
446
            break;
2016
872
        default:
2017
872
            pszText = "Unknown receiving station";
2018
872
            break;
2019
2.45k
    }
2020
2.45k
    SetMetadataItem("SOURCE", pszText);
2021
2022
2.45k
    switch (eProcCenter)
2023
2.45k
    {
2024
0
        case CMS:
2025
0
            pszText = "Centre de Meteorologie Spatiale - Lannion, France";
2026
0
            break;
2027
0
        case DSS:
2028
0
            pszText =
2029
0
                "Dundee Satellite Receiving Station - Dundee, Scotland, UK";
2030
0
            break;
2031
48
        case NSS:
2032
48
            pszText = "NOAA/NESDIS - Suitland, Maryland, USA";
2033
48
            break;
2034
0
        case UKM:
2035
0
            pszText =
2036
0
                "United Kingdom Meteorological Office - Bracknell, England, UK";
2037
0
            break;
2038
2.40k
        default:
2039
2.40k
            pszText = "Unknown processing center";
2040
2.40k
            break;
2041
2.45k
    }
2042
2.45k
    SetMetadataItem("PROCESSING_CENTER", pszText);
2043
2044
2.45k
    return CE_None;
2045
2.45k
}
2046
2047
/************************************************************************/
2048
/*                         ComputeFileOffsets()                         */
2049
/************************************************************************/
2050
2051
int L1BDataset::ComputeFileOffsets()
2052
4.72k
{
2053
4.72k
    CPLDebug("L1B", "Data format = %s",
2054
4.72k
             (iDataFormat == PACKED10BIT)     ? "Packed 10 bit"
2055
4.72k
             : (iDataFormat == UNPACKED16BIT) ? "Unpacked 16 bit"
2056
3.02k
                                              : "Unpacked 8 bit");
2057
2058
4.72k
    switch (eProductType)
2059
4.72k
    {
2060
3.03k
        case HRPT:
2061
3.09k
        case LAC:
2062
3.09k
        case FRAC:
2063
3.09k
            nRasterXSize = 2048;
2064
3.09k
            nBufferSize = 20484;
2065
3.09k
            iGCPStart =
2066
3.09k
                25 -
2067
3.09k
                1; /* http://www.ncdc.noaa.gov/oa/pod-guide/ncdc/docs/klm/html/c2/sec2-4.htm
2068
                    */
2069
3.09k
            iGCPStep = 40;
2070
3.09k
            nGCPsPerLine = 51;
2071
3.09k
            if (eL1BFormat == L1B_NOAA9)
2072
3.05k
            {
2073
3.05k
                if (iDataFormat == PACKED10BIT)
2074
1.01k
                {
2075
1.01k
                    nRecordSize = 14800;
2076
1.01k
                    nRecordDataEnd = 14104;
2077
1.01k
                }
2078
2.04k
                else if (iDataFormat == UNPACKED16BIT)
2079
1.01k
                {
2080
1.01k
                    switch (nBands)
2081
1.01k
                    {
2082
49
                        case 1:
2083
49
                            nRecordSize = 4544;
2084
49
                            nRecordDataEnd = 4544;
2085
49
                            break;
2086
5
                        case 2:
2087
5
                            nRecordSize = 8640;
2088
5
                            nRecordDataEnd = 8640;
2089
5
                            break;
2090
0
                        case 3:
2091
0
                            nRecordSize = 12736;
2092
0
                            nRecordDataEnd = 12736;
2093
0
                            break;
2094
0
                        case 4:
2095
0
                            nRecordSize = 16832;
2096
0
                            nRecordDataEnd = 16832;
2097
0
                            break;
2098
957
                        case 5:
2099
957
                            nRecordSize = 20928;
2100
957
                            nRecordDataEnd = 20928;
2101
957
                            break;
2102
1.01k
                    }
2103
1.01k
                }
2104
1.03k
                else  // UNPACKED8BIT
2105
1.03k
                {
2106
1.03k
                    switch (nBands)
2107
1.03k
                    {
2108
50
                        case 1:
2109
50
                            nRecordSize = 2496;
2110
50
                            nRecordDataEnd = 2496;
2111
50
                            break;
2112
5
                        case 2:
2113
5
                            nRecordSize = 4544;
2114
5
                            nRecordDataEnd = 4544;
2115
5
                            break;
2116
0
                        case 3:
2117
0
                            nRecordSize = 6592;
2118
0
                            nRecordDataEnd = 6592;
2119
0
                            break;
2120
0
                        case 4:
2121
0
                            nRecordSize = 8640;
2122
0
                            nRecordDataEnd = 8640;
2123
0
                            break;
2124
975
                        case 5:
2125
975
                            nRecordSize = 10688;
2126
975
                            nRecordDataEnd = 10688;
2127
975
                            break;
2128
1.03k
                    }
2129
1.03k
                }
2130
3.05k
                nDataStartOffset = nRecordSize + L1B_NOAA9_HEADER_SIZE;
2131
3.05k
                nRecordDataStart = 448;
2132
3.05k
                iGCPCodeOffset = 52;
2133
3.05k
                iGCPOffset = 104;
2134
3.05k
            }
2135
2136
43
            else if (eL1BFormat == L1B_NOAA15 || eL1BFormat == L1B_NOAA15_NOHDR)
2137
43
            {
2138
43
                if (iDataFormat == PACKED10BIT)
2139
43
                {
2140
43
                    nRecordSize = 15872;
2141
43
                    nRecordDataEnd = 14920;
2142
43
                    iCLAVRStart = 14984;
2143
43
                }
2144
0
                else if (iDataFormat == UNPACKED16BIT)
2145
0
                { /* Table 8.3.1.3.3.1-3 */
2146
0
                    switch (nBands)
2147
0
                    {
2148
0
                        case 1:
2149
0
                            nRecordSize = 6144;
2150
0
                            nRecordDataEnd = 5360;
2151
0
                            iCLAVRStart =
2152
0
                                5368 + 56; /* guessed but not verified */
2153
0
                            break;
2154
0
                        case 2:
2155
0
                            nRecordSize = 10240;
2156
0
                            nRecordDataEnd = 9456;
2157
0
                            iCLAVRStart =
2158
0
                                9464 + 56; /* guessed but not verified */
2159
0
                            break;
2160
0
                        case 3:
2161
0
                            nRecordSize = 14336;
2162
0
                            nRecordDataEnd = 13552;
2163
0
                            iCLAVRStart =
2164
0
                                13560 + 56; /* guessed but not verified */
2165
0
                            break;
2166
0
                        case 4:
2167
0
                            nRecordSize = 18432;
2168
0
                            nRecordDataEnd = 17648;
2169
0
                            iCLAVRStart =
2170
0
                                17656 + 56; /* guessed but not verified */
2171
0
                            break;
2172
0
                        case 5:
2173
0
                            nRecordSize = 22528;
2174
0
                            nRecordDataEnd = 21744;
2175
0
                            iCLAVRStart = 21752 + 56;
2176
0
                            break;
2177
0
                    }
2178
0
                }
2179
0
                else  // UNPACKED8BIT
2180
0
                {     /* Table 8.3.1.3.3.1-2 */
2181
0
                    switch (nBands)
2182
0
                    {
2183
0
                        case 1:
2184
0
                            nRecordSize = 4096;
2185
0
                            nRecordDataEnd = 3312;
2186
0
                            iCLAVRStart =
2187
0
                                3320 + 56; /* guessed but not verified */
2188
0
                            break;
2189
0
                        case 2:
2190
0
                            nRecordSize = 6144;
2191
0
                            nRecordDataEnd = 5360;
2192
0
                            iCLAVRStart =
2193
0
                                5368 + 56; /* guessed but not verified */
2194
0
                            break;
2195
0
                        case 3:
2196
0
                            nRecordSize = 8192;
2197
0
                            nRecordDataEnd = 7408;
2198
0
                            iCLAVRStart =
2199
0
                                7416 + 56; /* guessed but not verified */
2200
0
                            break;
2201
0
                        case 4:
2202
0
                            nRecordSize = 10240;
2203
0
                            nRecordDataEnd = 9456;
2204
0
                            iCLAVRStart =
2205
0
                                9464 + 56; /* guessed but not verified */
2206
0
                            break;
2207
0
                        case 5:
2208
0
                            nRecordSize = 12288;
2209
0
                            nRecordDataEnd = 11504;
2210
0
                            iCLAVRStart =
2211
0
                                11512 + 56; /* guessed but not verified */
2212
0
                            break;
2213
0
                    }
2214
0
                }
2215
43
                nDataStartOffset = (eL1BFormat == L1B_NOAA15_NOHDR)
2216
43
                                       ? nRecordDataEnd
2217
43
                                       : nRecordSize + L1B_NOAA15_HEADER_SIZE;
2218
43
                nRecordDataStart = 1264;
2219
43
                iGCPCodeOffset = 0;  // XXX: not exist for NOAA15?
2220
43
                iGCPOffset = 640;
2221
43
            }
2222
0
            else
2223
0
                return 0;
2224
3.09k
            break;
2225
2226
3.09k
        case GAC:
2227
1.63k
            nRasterXSize = 409;
2228
1.63k
            nBufferSize = 4092;
2229
1.63k
            iGCPStart = 5 - 1;  // FIXME: depends of scan direction
2230
1.63k
            iGCPStep = 8;
2231
1.63k
            nGCPsPerLine = 51;
2232
1.63k
            if (eL1BFormat == L1B_NOAA9)
2233
1.43k
            {
2234
1.43k
                if (iDataFormat == PACKED10BIT)
2235
444
                {
2236
444
                    nRecordSize = 3220;
2237
444
                    nRecordDataEnd = 3176;
2238
444
                }
2239
987
                else if (iDataFormat == UNPACKED16BIT)
2240
277
                    switch (nBands)
2241
277
                    {
2242
170
                        case 1:
2243
170
                            nRecordSize = 1268;
2244
170
                            nRecordDataEnd = 1266;
2245
170
                            break;
2246
41
                        case 2:
2247
41
                            nRecordSize = 2084;
2248
41
                            nRecordDataEnd = 2084;
2249
41
                            break;
2250
0
                        case 3:
2251
0
                            nRecordSize = 2904;
2252
0
                            nRecordDataEnd = 2902;
2253
0
                            break;
2254
0
                        case 4:
2255
0
                            nRecordSize = 3720;
2256
0
                            nRecordDataEnd = 3720;
2257
0
                            break;
2258
66
                        case 5:
2259
66
                            nRecordSize = 4540;
2260
66
                            nRecordDataEnd = 4538;
2261
66
                            break;
2262
277
                    }
2263
710
                else  // UNPACKED8BIT
2264
710
                {
2265
710
                    switch (nBands)
2266
710
                    {
2267
589
                        case 1:
2268
589
                            nRecordSize = 860;
2269
589
                            nRecordDataEnd = 858;
2270
589
                            break;
2271
48
                        case 2:
2272
48
                            nRecordSize = 1268;
2273
48
                            nRecordDataEnd = 1266;
2274
48
                            break;
2275
2
                        case 3:
2276
2
                            nRecordSize = 1676;
2277
2
                            nRecordDataEnd = 1676;
2278
2
                            break;
2279
0
                        case 4:
2280
0
                            nRecordSize = 2084;
2281
0
                            nRecordDataEnd = 2084;
2282
0
                            break;
2283
71
                        case 5:
2284
71
                            nRecordSize = 2496;
2285
71
                            nRecordDataEnd = 2494;
2286
71
                            break;
2287
710
                    }
2288
710
                }
2289
1.43k
                nDataStartOffset = nRecordSize * 2 + L1B_NOAA9_HEADER_SIZE;
2290
1.43k
                nRecordDataStart = 448;
2291
1.43k
                iGCPCodeOffset = 52;
2292
1.43k
                iGCPOffset = 104;
2293
1.43k
            }
2294
2295
199
            else if (eL1BFormat == L1B_NOAA15 || eL1BFormat == L1B_NOAA15_NOHDR)
2296
199
            {
2297
199
                if (iDataFormat == PACKED10BIT)
2298
199
                {
2299
199
                    nRecordSize = 4608;
2300
199
                    nRecordDataEnd = 3992;
2301
199
                    iCLAVRStart = 4056;
2302
199
                }
2303
0
                else if (iDataFormat == UNPACKED16BIT)
2304
0
                { /* Table 8.3.1.4.3.1-3 */
2305
0
                    switch (nBands)
2306
0
                    {
2307
0
                        case 1:
2308
0
                            nRecordSize = 2360;
2309
0
                            nRecordDataEnd = 2082;
2310
0
                            iCLAVRStart =
2311
0
                                2088 + 56; /* guessed but not verified */
2312
0
                            break;
2313
0
                        case 2:
2314
0
                            nRecordSize = 3176;
2315
0
                            nRecordDataEnd = 2900;
2316
0
                            iCLAVRStart =
2317
0
                                2904 + 56; /* guessed but not verified */
2318
0
                            break;
2319
0
                        case 3:
2320
0
                            nRecordSize = 3992;
2321
0
                            nRecordDataEnd = 3718;
2322
0
                            iCLAVRStart =
2323
0
                                3720 + 56; /* guessed but not verified */
2324
0
                            break;
2325
0
                        case 4:
2326
0
                            nRecordSize = 4816;
2327
0
                            nRecordDataEnd = 4536;
2328
0
                            iCLAVRStart =
2329
0
                                4544 + 56; /* guessed but not verified */
2330
0
                            break;
2331
0
                        case 5:
2332
0
                            nRecordSize = 5632;
2333
0
                            nRecordDataEnd = 5354;
2334
0
                            iCLAVRStart = 5360 + 56;
2335
0
                            break;
2336
0
                    }
2337
0
                }
2338
0
                else  // UNPACKED8BIT
2339
0
                { /* Table 8.3.1.4.3.1-2 but record length is wrong in the table
2340
                     ! */
2341
0
                    switch (nBands)
2342
0
                    {
2343
0
                        case 1:
2344
0
                            nRecordSize = 1952;
2345
0
                            nRecordDataEnd = 1673;
2346
0
                            iCLAVRStart =
2347
0
                                1680 + 56; /* guessed but not verified */
2348
0
                            break;
2349
0
                        case 2:
2350
0
                            nRecordSize = 2360;
2351
0
                            nRecordDataEnd = 2082;
2352
0
                            iCLAVRStart =
2353
0
                                2088 + 56; /* guessed but not verified */
2354
0
                            break;
2355
0
                        case 3:
2356
0
                            nRecordSize = 2768;
2357
0
                            nRecordDataEnd = 2491;
2358
0
                            iCLAVRStart =
2359
0
                                2496 + 56; /* guessed but not verified */
2360
0
                            break;
2361
0
                        case 4:
2362
0
                            nRecordSize = 3176;
2363
0
                            nRecordDataEnd = 2900;
2364
0
                            iCLAVRStart =
2365
0
                                2904 + 56; /* guessed but not verified */
2366
0
                            break;
2367
0
                        case 5:
2368
0
                            nRecordSize = 3584;
2369
0
                            nRecordDataEnd = 3309;
2370
0
                            iCLAVRStart =
2371
0
                                3312 + 56; /* guessed but not verified */
2372
0
                            break;
2373
0
                    }
2374
0
                }
2375
199
                nDataStartOffset = (eL1BFormat == L1B_NOAA15_NOHDR)
2376
199
                                       ? nRecordDataEnd
2377
199
                                       : nRecordSize + L1B_NOAA15_HEADER_SIZE;
2378
199
                nRecordDataStart = 1264;
2379
199
                iGCPCodeOffset = 0;  // XXX: not exist for NOAA15?
2380
199
                iGCPOffset = 640;
2381
199
            }
2382
0
            else
2383
0
                return 0;
2384
1.63k
            break;
2385
1.63k
        default:
2386
0
            return 0;
2387
4.72k
    }
2388
2389
4.72k
    return 1;
2390
4.72k
}
2391
2392
/************************************************************************/
2393
/*                           L1BGeolocDataset                           */
2394
/************************************************************************/
2395
2396
class L1BGeolocDataset final : public GDALDataset
2397
{
2398
    friend class L1BGeolocRasterBand;
2399
2400
    L1BDataset *poL1BDS;
2401
    int bInterpolGeolocationDS;
2402
2403
  public:
2404
    L1BGeolocDataset(L1BDataset *poMainDS, int bInterpolGeolocationDS);
2405
    ~L1BGeolocDataset() override;
2406
2407
    static GDALDataset *CreateGeolocationDS(L1BDataset *poL1BDS,
2408
                                            int bInterpolGeolocationDS);
2409
};
2410
2411
/************************************************************************/
2412
/*                         L1BGeolocRasterBand                          */
2413
/************************************************************************/
2414
2415
class L1BGeolocRasterBand final : public GDALRasterBand
2416
{
2417
  public:
2418
    L1BGeolocRasterBand(L1BGeolocDataset *poDS, int nBand);
2419
2420
    CPLErr IReadBlock(int, int, void *) override;
2421
    double GetNoDataValue(int *pbSuccess = nullptr) override;
2422
};
2423
2424
/************************************************************************/
2425
/*                          L1BGeolocDataset()                          */
2426
/************************************************************************/
2427
2428
L1BGeolocDataset::L1BGeolocDataset(L1BDataset *poL1BDSIn,
2429
                                   int bInterpolGeolocationDSIn)
2430
0
    : poL1BDS(poL1BDSIn), bInterpolGeolocationDS(bInterpolGeolocationDSIn)
2431
0
{
2432
0
    if (bInterpolGeolocationDS)
2433
0
        nRasterXSize = poL1BDS->nRasterXSize;
2434
0
    else
2435
0
        nRasterXSize = poL1BDS->nGCPsPerLine;
2436
0
    nRasterYSize = poL1BDS->nRasterYSize;
2437
0
}
2438
2439
/************************************************************************/
2440
/*                         ~L1BGeolocDataset()                          */
2441
/************************************************************************/
2442
2443
L1BGeolocDataset::~L1BGeolocDataset()
2444
0
{
2445
0
    delete poL1BDS;
2446
0
}
2447
2448
/************************************************************************/
2449
/*                        L1BGeolocRasterBand()                         */
2450
/************************************************************************/
2451
2452
L1BGeolocRasterBand::L1BGeolocRasterBand(L1BGeolocDataset *poDSIn, int nBandIn)
2453
0
{
2454
0
    poDS = poDSIn;
2455
0
    nBand = nBandIn;
2456
0
    nRasterXSize = poDSIn->nRasterXSize;
2457
0
    nRasterYSize = poDSIn->nRasterYSize;
2458
0
    eDataType = GDT_Float64;
2459
0
    nBlockXSize = nRasterXSize;
2460
0
    nBlockYSize = 1;
2461
0
    if (nBand == 1)
2462
0
        SetDescription("GEOLOC X");
2463
0
    else
2464
0
        SetDescription("GEOLOC Y");
2465
0
}
2466
2467
/************************************************************************/
2468
/*                          LagrangeInterpol()                          */
2469
/************************************************************************/
2470
2471
/* ----------------------------------------------------------------------------
2472
 * Perform a Lagrangian interpolation through the given x,y coordinates
2473
 * and return the interpolated y value for the given x value.
2474
 * The array size and thus the polynomial order is defined by numpt.
2475
 * Input: x[] and y[] are of size numpt,
2476
 *  x0 is the x value for which we calculate the corresponding y
2477
 * Returns: y value calculated for given x0.
2478
 */
2479
static double LagrangeInterpol(const double x[], const double y[], double x0,
2480
                               int numpt)
2481
0
{
2482
0
    int i, j;
2483
0
    double L;
2484
0
    double y0 = 0;
2485
2486
0
    for (i = 0; i < numpt; i++)
2487
0
    {
2488
0
        L = 1.0;
2489
0
        for (j = 0; j < numpt; j++)
2490
0
        {
2491
0
            if (i == j)
2492
0
                continue;
2493
0
            L = L * (x0 - x[j]) / (x[i] - x[j]);
2494
0
        }
2495
0
        y0 = y0 + L * y[i];
2496
0
    }
2497
0
    return y0;
2498
0
}
2499
2500
/************************************************************************/
2501
/*                            L1BInterpol()                             */
2502
/************************************************************************/
2503
2504
/* ----------------------------------------------------------------------------
2505
 * Interpolate an array of size numPoints where the only values set on input are
2506
 * at knownFirst, and intervals of knownStep thereafter.
2507
 * On return all the rest from 0..numPoints-1 will be filled in.
2508
 * Uses the LagrangeInterpol() function to do the interpolation; 5-point for the
2509
 * beginning and end of the array and 4-point for the rest.
2510
 * To use this function for NOAA level 1B data extract the 51 latitude values
2511
 * into their appropriate places in the vals array then call L1BInterpol to
2512
 * calculate the rest of the values.  Do similarly for longitudes, solar zenith
2513
 * angles, and any others which are present in the file.
2514
 * Reference:
2515
 *  http://www.ncdc.noaa.gov/oa/pod-guide/ncdc/docs/klm/html/c2/sec2-4.htm
2516
 */
2517
2518
0
#define MIDDLE_INTERP_ORDER 4
2519
0
#define END_INTERP_ORDER 5 /* Ensure this is an odd number, 5 is suitable.*/
2520
2521
/* Convert number of known point to its index in full array */
2522
0
#define IDX(N) ((N) * knownStep + knownFirst)
2523
2524
static void L1BInterpol(
2525
    double vals[], int numKnown, /* Number of known points (typically 51) */
2526
    int knownFirst, /* Index in full array of first known point (24) */
2527
    int knownStep,  /* Interval to next and subsequent known points (40) */
2528
    int numPoints /* Number of points in whole array (2048) */)
2529
0
{
2530
0
    int i, j;
2531
0
    double x[END_INTERP_ORDER];
2532
0
    double y[END_INTERP_ORDER];
2533
2534
    /* First extrapolate first 24 points */
2535
0
    for (i = 0; i < END_INTERP_ORDER; i++)
2536
0
    {
2537
0
        x[i] = IDX(i);
2538
0
        y[i] = vals[IDX(i)];
2539
0
    }
2540
0
    for (i = 0; i < knownFirst; i++)
2541
0
    {
2542
0
        vals[i] = LagrangeInterpol(x, y, i, END_INTERP_ORDER);
2543
0
    }
2544
2545
    /* Next extrapolate last 23 points */
2546
0
    for (i = 0; i < END_INTERP_ORDER; i++)
2547
0
    {
2548
0
        x[i] = IDX(numKnown - END_INTERP_ORDER + i);
2549
0
        y[i] = vals[IDX(numKnown - END_INTERP_ORDER + i)];
2550
0
    }
2551
0
    for (i = IDX(numKnown - 1); i < numPoints; i++)
2552
0
    {
2553
0
        vals[i] = LagrangeInterpol(x, y, i, END_INTERP_ORDER);
2554
0
    }
2555
2556
    /* Interpolate all intermediate points using two before and two after */
2557
0
    for (i = knownFirst; i < IDX(numKnown - 1); i++)
2558
0
    {
2559
0
        double x2[MIDDLE_INTERP_ORDER];
2560
0
        double y2[MIDDLE_INTERP_ORDER];
2561
0
        int startpt;
2562
2563
        /* Find a suitable set of two known points before and two after */
2564
0
        startpt = (i / knownStep) - MIDDLE_INTERP_ORDER / 2;
2565
0
        if (startpt < 0)
2566
0
            startpt = 0;
2567
0
        if (startpt + MIDDLE_INTERP_ORDER - 1 >= numKnown)
2568
0
            startpt = numKnown - MIDDLE_INTERP_ORDER;
2569
0
        for (j = 0; j < MIDDLE_INTERP_ORDER; j++)
2570
0
        {
2571
0
            x2[j] = IDX(startpt + j);
2572
0
            y2[j] = vals[IDX(startpt + j)];
2573
0
        }
2574
0
        vals[i] = LagrangeInterpol(x2, y2, i, MIDDLE_INTERP_ORDER);
2575
0
    }
2576
0
}
2577
2578
/************************************************************************/
2579
/*                             IReadBlock()                             */
2580
/************************************************************************/
2581
2582
CPLErr L1BGeolocRasterBand::IReadBlock(CPL_UNUSED int nBlockXOff,
2583
                                       int nBlockYOff, void *pData)
2584
0
{
2585
0
    L1BGeolocDataset *poGDS = cpl::down_cast<L1BGeolocDataset *>(poDS);
2586
0
    L1BDataset *poL1BDS = poGDS->poL1BDS;
2587
0
    GDAL_GCP *pasGCPList =
2588
0
        (GDAL_GCP *)CPLCalloc(poL1BDS->nGCPsPerLine, sizeof(GDAL_GCP));
2589
0
    GDALInitGCPs(poL1BDS->nGCPsPerLine, pasGCPList);
2590
2591
0
    GByte *pabyRecordHeader = (GByte *)CPLMalloc(poL1BDS->nRecordSize);
2592
2593
    /* -------------------------------------------------------------------- */
2594
    /*      Seek to data.                                                   */
2595
    /* -------------------------------------------------------------------- */
2596
0
    CPL_IGNORE_RET_VAL(
2597
0
        VSIFSeekL(poL1BDS->fp, poL1BDS->GetLineOffset(nBlockYOff), SEEK_SET));
2598
2599
0
    CPL_IGNORE_RET_VAL(
2600
0
        VSIFReadL(pabyRecordHeader, 1, poL1BDS->nRecordDataStart, poL1BDS->fp));
2601
2602
    /* Fetch the GCPs for the row */
2603
0
    int nGotGCPs = poL1BDS->FetchGCPs(pasGCPList, pabyRecordHeader, nBlockYOff);
2604
0
    double *padfData = (double *)pData;
2605
0
    int i;
2606
0
    if (poGDS->bInterpolGeolocationDS)
2607
0
    {
2608
        /* Fill the known position */
2609
0
        for (i = 0; i < nGotGCPs; i++)
2610
0
        {
2611
0
            double dfVal =
2612
0
                (nBand == 1) ? pasGCPList[i].dfGCPX : pasGCPList[i].dfGCPY;
2613
0
            padfData[poL1BDS->iGCPStart + i * poL1BDS->iGCPStep] = dfVal;
2614
0
        }
2615
2616
0
        if (nGotGCPs == poL1BDS->nGCPsPerLine)
2617
0
        {
2618
            /* And do Lagangian interpolation to fill the holes */
2619
0
            L1BInterpol(padfData, poL1BDS->nGCPsPerLine, poL1BDS->iGCPStart,
2620
0
                        poL1BDS->iGCPStep, nRasterXSize);
2621
0
        }
2622
0
        else
2623
0
        {
2624
0
            int iFirstNonValid = 0;
2625
0
            if (nGotGCPs > 5)
2626
0
                iFirstNonValid = poL1BDS->iGCPStart +
2627
0
                                 nGotGCPs * poL1BDS->iGCPStep +
2628
0
                                 poL1BDS->iGCPStep / 2;
2629
0
            for (i = iFirstNonValid; i < nRasterXSize; i++)
2630
0
            {
2631
0
                padfData[i] = GetNoDataValue(nullptr);
2632
0
            }
2633
0
            if (iFirstNonValid > 0)
2634
0
            {
2635
0
                L1BInterpol(padfData, poL1BDS->nGCPsPerLine, poL1BDS->iGCPStart,
2636
0
                            poL1BDS->iGCPStep, iFirstNonValid);
2637
0
            }
2638
0
        }
2639
0
    }
2640
0
    else
2641
0
    {
2642
0
        for (i = 0; i < nGotGCPs; i++)
2643
0
        {
2644
0
            padfData[i] =
2645
0
                (nBand == 1) ? pasGCPList[i].dfGCPX : pasGCPList[i].dfGCPY;
2646
0
        }
2647
0
        for (i = nGotGCPs; i < nRasterXSize; i++)
2648
0
            padfData[i] = GetNoDataValue(nullptr);
2649
0
    }
2650
2651
0
    if (poL1BDS->eLocationIndicator == ASCEND)
2652
0
    {
2653
0
        for (i = 0; i < nRasterXSize / 2; i++)
2654
0
        {
2655
0
            double dfTmp = padfData[i];
2656
0
            padfData[i] = padfData[nRasterXSize - 1 - i];
2657
0
            padfData[nRasterXSize - 1 - i] = dfTmp;
2658
0
        }
2659
0
    }
2660
2661
0
    CPLFree(pabyRecordHeader);
2662
0
    GDALDeinitGCPs(poL1BDS->nGCPsPerLine, pasGCPList);
2663
0
    CPLFree(pasGCPList);
2664
2665
0
    return CE_None;
2666
0
}
2667
2668
/************************************************************************/
2669
/*                           GetNoDataValue()                           */
2670
/************************************************************************/
2671
2672
double L1BGeolocRasterBand::GetNoDataValue(int *pbSuccess)
2673
0
{
2674
0
    if (pbSuccess)
2675
0
        *pbSuccess = TRUE;
2676
0
    return -200.0;
2677
0
}
2678
2679
/************************************************************************/
2680
/*                        CreateGeolocationDS()                         */
2681
/************************************************************************/
2682
2683
GDALDataset *L1BGeolocDataset::CreateGeolocationDS(L1BDataset *poL1BDS,
2684
                                                   int bInterpolGeolocationDS)
2685
0
{
2686
0
    L1BGeolocDataset *poGeolocDS =
2687
0
        new L1BGeolocDataset(poL1BDS, bInterpolGeolocationDS);
2688
0
    for (int i = 1; i <= 2; i++)
2689
0
    {
2690
0
        poGeolocDS->SetBand(i, new L1BGeolocRasterBand(poGeolocDS, i));
2691
0
    }
2692
0
    return poGeolocDS;
2693
0
}
2694
2695
/************************************************************************/
2696
/*                     L1BSolarZenithAnglesDataset                      */
2697
/************************************************************************/
2698
2699
class L1BSolarZenithAnglesDataset final : public GDALDataset
2700
{
2701
    friend class L1BSolarZenithAnglesRasterBand;
2702
2703
    L1BDataset *poL1BDS;
2704
2705
  public:
2706
    explicit L1BSolarZenithAnglesDataset(L1BDataset *poMainDS);
2707
    ~L1BSolarZenithAnglesDataset() override;
2708
2709
    static GDALDataset *CreateSolarZenithAnglesDS(L1BDataset *poL1BDS);
2710
};
2711
2712
/************************************************************************/
2713
/*                    L1BSolarZenithAnglesRasterBand                    */
2714
/************************************************************************/
2715
2716
class L1BSolarZenithAnglesRasterBand final : public GDALRasterBand
2717
{
2718
  public:
2719
    L1BSolarZenithAnglesRasterBand(L1BSolarZenithAnglesDataset *poDS,
2720
                                   int nBand);
2721
2722
    CPLErr IReadBlock(int, int, void *) override;
2723
    double GetNoDataValue(int *pbSuccess = nullptr) override;
2724
};
2725
2726
/************************************************************************/
2727
/*                    L1BSolarZenithAnglesDataset()                     */
2728
/************************************************************************/
2729
2730
L1BSolarZenithAnglesDataset::L1BSolarZenithAnglesDataset(L1BDataset *poL1BDSIn)
2731
0
{
2732
0
    poL1BDS = poL1BDSIn;
2733
0
    nRasterXSize = 51;
2734
0
    nRasterYSize = poL1BDSIn->nRasterYSize;
2735
0
}
2736
2737
/************************************************************************/
2738
/*                    ~L1BSolarZenithAnglesDataset()                    */
2739
/************************************************************************/
2740
2741
L1BSolarZenithAnglesDataset::~L1BSolarZenithAnglesDataset()
2742
0
{
2743
0
    delete poL1BDS;
2744
0
}
2745
2746
/************************************************************************/
2747
/*                   L1BSolarZenithAnglesRasterBand()                   */
2748
/************************************************************************/
2749
2750
L1BSolarZenithAnglesRasterBand::L1BSolarZenithAnglesRasterBand(
2751
    L1BSolarZenithAnglesDataset *poDSIn, int nBandIn)
2752
0
{
2753
0
    poDS = poDSIn;
2754
0
    nBand = nBandIn;
2755
0
    nRasterXSize = poDSIn->nRasterXSize;
2756
0
    nRasterYSize = poDSIn->nRasterYSize;
2757
0
    eDataType = GDT_Float32;
2758
0
    nBlockXSize = nRasterXSize;
2759
0
    nBlockYSize = 1;
2760
0
}
2761
2762
/************************************************************************/
2763
/*                             IReadBlock()                             */
2764
/************************************************************************/
2765
2766
CPLErr L1BSolarZenithAnglesRasterBand::IReadBlock(CPL_UNUSED int nBlockXOff,
2767
                                                  int nBlockYOff, void *pData)
2768
0
{
2769
0
    L1BSolarZenithAnglesDataset *poGDS =
2770
0
        cpl::down_cast<L1BSolarZenithAnglesDataset *>(poDS);
2771
0
    L1BDataset *poL1BDS = poGDS->poL1BDS;
2772
0
    int i;
2773
2774
0
    GByte *pabyRecordHeader = (GByte *)CPLMalloc(poL1BDS->nRecordSize);
2775
2776
    /* -------------------------------------------------------------------- */
2777
    /*      Seek to data.                                                   */
2778
    /* -------------------------------------------------------------------- */
2779
0
    CPL_IGNORE_RET_VAL(
2780
0
        VSIFSeekL(poL1BDS->fp, poL1BDS->GetLineOffset(nBlockYOff), SEEK_SET));
2781
2782
0
    CPL_IGNORE_RET_VAL(
2783
0
        VSIFReadL(pabyRecordHeader, 1, poL1BDS->nRecordSize, poL1BDS->fp));
2784
2785
0
    const int nValidValues =
2786
0
        std::min(nRasterXSize,
2787
0
                 static_cast<int>(pabyRecordHeader[poL1BDS->iGCPCodeOffset]));
2788
0
    float *pafData = (float *)pData;
2789
2790
0
    int bHasFractional = (poL1BDS->nRecordDataEnd + 20 <= poL1BDS->nRecordSize);
2791
2792
#ifdef notdef
2793
    if (bHasFractional)
2794
    {
2795
        for (i = 0; i < 20; i++)
2796
        {
2797
            GByte val = pabyRecordHeader[poL1BDS->nRecordDataEnd + i];
2798
            for (int j = 0; j < 8; j++)
2799
                fprintf(stderr, "%c", /*ok*/
2800
                        ((val >> (7 - j)) & 1) ? '1' : '0');
2801
            fprintf(stderr, " "); /*ok*/
2802
        }
2803
        fprintf(stderr, "\n"); /*ok*/
2804
    }
2805
#endif
2806
2807
0
    for (i = 0; i < nValidValues; i++)
2808
0
    {
2809
0
        pafData[i] = pabyRecordHeader[poL1BDS->iGCPCodeOffset + 1 + i] / 2.0f;
2810
2811
0
        if (bHasFractional)
2812
0
        {
2813
            /* Cf
2814
             * http://www.ncdc.noaa.gov/oa/pod-guide/ncdc/docs/podug/html/l/app-l.htm#notl-2
2815
             */
2816
            /* This is not very clear on if bits must be counted from MSB or LSB
2817
             */
2818
            /* but when testing on n12gac10bit.l1b, it appears that the 3 bits
2819
             * for i=0 are the 3 MSB bits */
2820
0
            int nAddBitStart = i * 3;
2821
0
            int nFractional;
2822
2823
0
#if 1
2824
0
            if ((nAddBitStart % 8) + 3 <= 8)
2825
0
            {
2826
0
                nFractional = (pabyRecordHeader[poL1BDS->nRecordDataEnd +
2827
0
                                                (nAddBitStart / 8)] >>
2828
0
                               (8 - ((nAddBitStart % 8) + 3))) &
2829
0
                              0x7;
2830
0
            }
2831
0
            else
2832
0
            {
2833
0
                nFractional = (((pabyRecordHeader[poL1BDS->nRecordDataEnd +
2834
0
                                                  (nAddBitStart / 8)]
2835
0
                                 << 8) |
2836
0
                                pabyRecordHeader[poL1BDS->nRecordDataEnd +
2837
0
                                                 (nAddBitStart / 8) + 1]) >>
2838
0
                               (16 - ((nAddBitStart % 8) + 3))) &
2839
0
                              0x7;
2840
0
            }
2841
#else
2842
            nFractional = (pabyRecordHeader[poL1BDS->nRecordDataEnd +
2843
                                            (nAddBitStart / 8)] >>
2844
                           (nAddBitStart % 8)) &
2845
                          0x7;
2846
            if ((nAddBitStart % 8) + 3 > 8)
2847
                nFractional |= (pabyRecordHeader[poL1BDS->nRecordDataEnd +
2848
                                                 (nAddBitStart / 8) + 1] &
2849
                                ((1 << (((nAddBitStart % 8) + 3 - 8))) - 1))
2850
                               << (3 - ((((nAddBitStart % 8) + 3 - 8))));
2851
            */
2852
#endif
2853
0
            if (nFractional > 4)
2854
0
            {
2855
0
                CPLDebug("L1B",
2856
0
                         "For nBlockYOff=%d, i=%d, wrong fractional value : %d",
2857
0
                         nBlockYOff, i, nFractional);
2858
0
            }
2859
2860
0
            pafData[i] += nFractional / 10.0f;
2861
0
        }
2862
0
    }
2863
2864
0
    for (; i < nRasterXSize; i++)
2865
0
        pafData[i] = static_cast<float>(GetNoDataValue(nullptr));
2866
2867
0
    if (poL1BDS->eLocationIndicator == ASCEND)
2868
0
    {
2869
0
        for (i = 0; i < nRasterXSize / 2; i++)
2870
0
        {
2871
0
            float fTmp = pafData[i];
2872
0
            pafData[i] = pafData[nRasterXSize - 1 - i];
2873
0
            pafData[nRasterXSize - 1 - i] = fTmp;
2874
0
        }
2875
0
    }
2876
2877
0
    CPLFree(pabyRecordHeader);
2878
2879
0
    return CE_None;
2880
0
}
2881
2882
/************************************************************************/
2883
/*                           GetNoDataValue()                           */
2884
/************************************************************************/
2885
2886
double L1BSolarZenithAnglesRasterBand::GetNoDataValue(int *pbSuccess)
2887
0
{
2888
0
    if (pbSuccess)
2889
0
        *pbSuccess = TRUE;
2890
0
    return -200.0;
2891
0
}
2892
2893
/************************************************************************/
2894
/*                     CreateSolarZenithAnglesDS()                      */
2895
/************************************************************************/
2896
2897
GDALDataset *
2898
L1BSolarZenithAnglesDataset::CreateSolarZenithAnglesDS(L1BDataset *poL1BDS)
2899
0
{
2900
0
    L1BSolarZenithAnglesDataset *poGeolocDS =
2901
0
        new L1BSolarZenithAnglesDataset(poL1BDS);
2902
0
    for (int i = 1; i <= 1; i++)
2903
0
    {
2904
0
        poGeolocDS->SetBand(i,
2905
0
                            new L1BSolarZenithAnglesRasterBand(poGeolocDS, i));
2906
0
    }
2907
0
    return poGeolocDS;
2908
0
}
2909
2910
/************************************************************************/
2911
/*                        L1BNOAA15AnglesDataset                        */
2912
/************************************************************************/
2913
2914
class L1BNOAA15AnglesDataset final : public GDALDataset
2915
{
2916
    friend class L1BNOAA15AnglesRasterBand;
2917
2918
    L1BDataset *poL1BDS;
2919
2920
  public:
2921
    explicit L1BNOAA15AnglesDataset(L1BDataset *poMainDS);
2922
    ~L1BNOAA15AnglesDataset() override;
2923
2924
    static GDALDataset *CreateAnglesDS(L1BDataset *poL1BDS);
2925
};
2926
2927
/************************************************************************/
2928
/*                      L1BNOAA15AnglesRasterBand                       */
2929
/************************************************************************/
2930
2931
class L1BNOAA15AnglesRasterBand final : public GDALRasterBand
2932
{
2933
  public:
2934
    L1BNOAA15AnglesRasterBand(L1BNOAA15AnglesDataset *poDS, int nBand);
2935
2936
    CPLErr IReadBlock(int, int, void *) override;
2937
};
2938
2939
/************************************************************************/
2940
/*                       L1BNOAA15AnglesDataset()                       */
2941
/************************************************************************/
2942
2943
L1BNOAA15AnglesDataset::L1BNOAA15AnglesDataset(L1BDataset *poL1BDSIn)
2944
0
    : poL1BDS(poL1BDSIn)
2945
0
{
2946
0
    nRasterXSize = 51;
2947
0
    nRasterYSize = poL1BDS->nRasterYSize;
2948
0
}
2949
2950
/************************************************************************/
2951
/*                      ~L1BNOAA15AnglesDataset()                       */
2952
/************************************************************************/
2953
2954
L1BNOAA15AnglesDataset::~L1BNOAA15AnglesDataset()
2955
0
{
2956
0
    delete poL1BDS;
2957
0
}
2958
2959
/************************************************************************/
2960
/*                     L1BNOAA15AnglesRasterBand()                      */
2961
/************************************************************************/
2962
2963
L1BNOAA15AnglesRasterBand::L1BNOAA15AnglesRasterBand(
2964
    L1BNOAA15AnglesDataset *poDSIn, int nBandIn)
2965
0
{
2966
0
    poDS = poDSIn;
2967
0
    nBand = nBandIn;
2968
0
    nRasterXSize = poDSIn->nRasterXSize;
2969
0
    nRasterYSize = poDSIn->nRasterYSize;
2970
0
    eDataType = GDT_Float32;
2971
0
    nBlockXSize = nRasterXSize;
2972
0
    nBlockYSize = 1;
2973
0
    if (nBand == 1)
2974
0
        SetDescription("Solar zenith angles");
2975
0
    else if (nBand == 2)
2976
0
        SetDescription("Satellite zenith angles");
2977
0
    else
2978
0
        SetDescription("Relative azimuth angles");
2979
0
}
2980
2981
/************************************************************************/
2982
/*                             IReadBlock()                             */
2983
/************************************************************************/
2984
2985
CPLErr L1BNOAA15AnglesRasterBand::IReadBlock(CPL_UNUSED int nBlockXOff,
2986
                                             int nBlockYOff, void *pData)
2987
0
{
2988
0
    L1BNOAA15AnglesDataset *poGDS =
2989
0
        cpl::down_cast<L1BNOAA15AnglesDataset *>(poDS);
2990
0
    L1BDataset *poL1BDS = poGDS->poL1BDS;
2991
0
    int i;
2992
2993
0
    GByte *pabyRecordHeader = (GByte *)CPLMalloc(poL1BDS->nRecordSize);
2994
2995
    /* -------------------------------------------------------------------- */
2996
    /*      Seek to data.                                                   */
2997
    /* -------------------------------------------------------------------- */
2998
0
    CPL_IGNORE_RET_VAL(
2999
0
        VSIFSeekL(poL1BDS->fp, poL1BDS->GetLineOffset(nBlockYOff), SEEK_SET));
3000
3001
0
    CPL_IGNORE_RET_VAL(
3002
0
        VSIFReadL(pabyRecordHeader, 1, poL1BDS->nRecordSize, poL1BDS->fp));
3003
3004
0
    float *pafData = (float *)pData;
3005
3006
0
    for (i = 0; i < nRasterXSize; i++)
3007
0
    {
3008
0
        GInt16 i16 =
3009
0
            poL1BDS->GetInt16(pabyRecordHeader + 328 + 6 * i + 2 * (nBand - 1));
3010
0
        pafData[i] = i16 / 100.0f;
3011
0
    }
3012
3013
0
    if (poL1BDS->eLocationIndicator == ASCEND)
3014
0
    {
3015
0
        for (i = 0; i < nRasterXSize / 2; i++)
3016
0
        {
3017
0
            float fTmp = pafData[i];
3018
0
            pafData[i] = pafData[nRasterXSize - 1 - i];
3019
0
            pafData[nRasterXSize - 1 - i] = fTmp;
3020
0
        }
3021
0
    }
3022
3023
0
    CPLFree(pabyRecordHeader);
3024
3025
0
    return CE_None;
3026
0
}
3027
3028
/************************************************************************/
3029
/*                           CreateAnglesDS()                           */
3030
/************************************************************************/
3031
3032
GDALDataset *L1BNOAA15AnglesDataset::CreateAnglesDS(L1BDataset *poL1BDS)
3033
0
{
3034
0
    L1BNOAA15AnglesDataset *poGeolocDS = new L1BNOAA15AnglesDataset(poL1BDS);
3035
0
    for (int i = 1; i <= 3; i++)
3036
0
    {
3037
0
        poGeolocDS->SetBand(i, new L1BNOAA15AnglesRasterBand(poGeolocDS, i));
3038
0
    }
3039
0
    return poGeolocDS;
3040
0
}
3041
3042
/************************************************************************/
3043
/*                           L1BCloudsDataset                           */
3044
/************************************************************************/
3045
3046
class L1BCloudsDataset final : public GDALDataset
3047
{
3048
    friend class L1BCloudsRasterBand;
3049
3050
    L1BDataset *poL1BDS;
3051
3052
  public:
3053
    explicit L1BCloudsDataset(L1BDataset *poMainDS);
3054
    ~L1BCloudsDataset() override;
3055
3056
    static GDALDataset *CreateCloudsDS(L1BDataset *poL1BDS);
3057
};
3058
3059
/************************************************************************/
3060
/*                         L1BCloudsRasterBand                          */
3061
/************************************************************************/
3062
3063
class L1BCloudsRasterBand final : public GDALRasterBand
3064
{
3065
  public:
3066
    L1BCloudsRasterBand(L1BCloudsDataset *poDS, int nBand);
3067
3068
    CPLErr IReadBlock(int, int, void *) override;
3069
};
3070
3071
/************************************************************************/
3072
/*                          L1BCloudsDataset()                          */
3073
/************************************************************************/
3074
3075
0
L1BCloudsDataset::L1BCloudsDataset(L1BDataset *poL1BDSIn) : poL1BDS(poL1BDSIn)
3076
0
{
3077
0
    nRasterXSize = poL1BDSIn->nRasterXSize;
3078
0
    nRasterYSize = poL1BDSIn->nRasterYSize;
3079
0
}
3080
3081
/************************************************************************/
3082
/*                         ~L1BCloudsDataset()                          */
3083
/************************************************************************/
3084
3085
L1BCloudsDataset::~L1BCloudsDataset()
3086
0
{
3087
0
    delete poL1BDS;
3088
0
}
3089
3090
/************************************************************************/
3091
/*                        L1BCloudsRasterBand()                         */
3092
/************************************************************************/
3093
3094
L1BCloudsRasterBand::L1BCloudsRasterBand(L1BCloudsDataset *poDSIn, int nBandIn)
3095
0
{
3096
0
    poDS = poDSIn;
3097
0
    nBand = nBandIn;
3098
0
    nRasterXSize = poDSIn->nRasterXSize;
3099
0
    nRasterYSize = poDSIn->nRasterYSize;
3100
0
    eDataType = GDT_UInt8;
3101
0
    nBlockXSize = nRasterXSize;
3102
0
    nBlockYSize = 1;
3103
0
}
3104
3105
/************************************************************************/
3106
/*                             IReadBlock()                             */
3107
/************************************************************************/
3108
3109
CPLErr L1BCloudsRasterBand::IReadBlock(CPL_UNUSED int nBlockXOff,
3110
                                       int nBlockYOff, void *pData)
3111
0
{
3112
0
    L1BCloudsDataset *poGDS = cpl::down_cast<L1BCloudsDataset *>(poDS);
3113
0
    L1BDataset *poL1BDS = poGDS->poL1BDS;
3114
0
    int i;
3115
3116
0
    GByte *pabyRecordHeader = (GByte *)CPLMalloc(poL1BDS->nRecordSize);
3117
3118
    /* -------------------------------------------------------------------- */
3119
    /*      Seek to data.                                                   */
3120
    /* -------------------------------------------------------------------- */
3121
0
    CPL_IGNORE_RET_VAL(
3122
0
        VSIFSeekL(poL1BDS->fp, poL1BDS->GetLineOffset(nBlockYOff), SEEK_SET));
3123
3124
0
    CPL_IGNORE_RET_VAL(
3125
0
        VSIFReadL(pabyRecordHeader, 1, poL1BDS->nRecordSize, poL1BDS->fp));
3126
3127
0
    GByte *pabyData = (GByte *)pData;
3128
3129
0
    for (i = 0; i < nRasterXSize; i++)
3130
0
    {
3131
0
        pabyData[i] = ((pabyRecordHeader[poL1BDS->iCLAVRStart + (i / 4)] >>
3132
0
                        (8 - ((i % 4) * 2 + 2))) &
3133
0
                       0x3);
3134
0
    }
3135
3136
0
    if (poL1BDS->eLocationIndicator == ASCEND)
3137
0
    {
3138
0
        for (i = 0; i < nRasterXSize / 2; i++)
3139
0
        {
3140
0
            GByte byTmp = pabyData[i];
3141
0
            pabyData[i] = pabyData[nRasterXSize - 1 - i];
3142
0
            pabyData[nRasterXSize - 1 - i] = byTmp;
3143
0
        }
3144
0
    }
3145
3146
0
    CPLFree(pabyRecordHeader);
3147
3148
0
    return CE_None;
3149
0
}
3150
3151
/************************************************************************/
3152
/*                           CreateCloudsDS()                           */
3153
/************************************************************************/
3154
3155
GDALDataset *L1BCloudsDataset::CreateCloudsDS(L1BDataset *poL1BDS)
3156
0
{
3157
0
    L1BCloudsDataset *poGeolocDS = new L1BCloudsDataset(poL1BDS);
3158
0
    for (int i = 1; i <= 1; i++)
3159
0
    {
3160
0
        poGeolocDS->SetBand(i, new L1BCloudsRasterBand(poGeolocDS, i));
3161
0
    }
3162
0
    return poGeolocDS;
3163
0
}
3164
3165
/************************************************************************/
3166
/*                            DetectFormat()                            */
3167
/************************************************************************/
3168
3169
L1BFileFormat L1BDataset::DetectFormat(const char *pszFilename,
3170
                                       const GByte *pabyHeader,
3171
                                       int nHeaderBytes)
3172
3173
554k
{
3174
554k
    if (pabyHeader == nullptr || nHeaderBytes < L1B_NOAA9_HEADER_SIZE)
3175
435k
        return L1B_NONE;
3176
3177
    // try NOAA-18 formats
3178
119k
    if (*(pabyHeader + 0) == '\0' && *(pabyHeader + 1) == '\0' &&
3179
296
        *(pabyHeader + 2) == '\0' && *(pabyHeader + 3) == '\0' &&
3180
127
        *(pabyHeader + 4) == '\0' && *(pabyHeader + 5) == '\0' &&
3181
49
        EQUALN((const char *)(pabyHeader + 22), "/N1BD/N18/", 10))
3182
0
        return L1B_NOAA15_NOHDR;
3183
3184
    // We will try the NOAA-15 and later formats first
3185
119k
    if (nHeaderBytes > L1B_NOAA15_HEADER_SIZE + 61 &&
3186
108k
        *(pabyHeader + L1B_NOAA15_HEADER_SIZE + 25) == '.' &&
3187
3.46k
        *(pabyHeader + L1B_NOAA15_HEADER_SIZE + 30) == '.' &&
3188
1.69k
        *(pabyHeader + L1B_NOAA15_HEADER_SIZE + 33) == '.' &&
3189
1.66k
        *(pabyHeader + L1B_NOAA15_HEADER_SIZE + 40) == '.' &&
3190
1.58k
        *(pabyHeader + L1B_NOAA15_HEADER_SIZE + 46) == '.' &&
3191
1.54k
        *(pabyHeader + L1B_NOAA15_HEADER_SIZE + 52) == '.' &&
3192
1.52k
        *(pabyHeader + L1B_NOAA15_HEADER_SIZE + 61) == '.')
3193
1.44k
        return L1B_NOAA15;
3194
3195
    // Next try the NOAA-9/14 formats
3196
117k
    if (*(pabyHeader + 8 + 25) == '.' && *(pabyHeader + 8 + 30) == '.' &&
3197
5.34k
        *(pabyHeader + 8 + 33) == '.' && *(pabyHeader + 8 + 40) == '.' &&
3198
5.13k
        *(pabyHeader + 8 + 46) == '.' && *(pabyHeader + 8 + 52) == '.' &&
3199
4.55k
        *(pabyHeader + 8 + 61) == '.')
3200
3.31k
        return L1B_NOAA9;
3201
3202
    // Next try the NOAA-9/14 formats with dataset name in EBCDIC
3203
114k
    if (*(pabyHeader + 8 + 25) == 'K' && *(pabyHeader + 8 + 30) == 'K' &&
3204
2.99k
        *(pabyHeader + 8 + 33) == 'K' && *(pabyHeader + 8 + 40) == 'K' &&
3205
2.78k
        *(pabyHeader + 8 + 46) == 'K' && *(pabyHeader + 8 + 52) == 'K' &&
3206
2.50k
        *(pabyHeader + 8 + 61) == 'K')
3207
2.48k
        return L1B_NOAA9;
3208
3209
    // Finally try the AAPP formats
3210
111k
    if (*(pabyHeader + 25) == '.' && *(pabyHeader + 30) == '.' &&
3211
2.11k
        *(pabyHeader + 33) == '.' && *(pabyHeader + 40) == '.' &&
3212
1.56k
        *(pabyHeader + 46) == '.' && *(pabyHeader + 52) == '.' &&
3213
1.47k
        *(pabyHeader + 61) == '.')
3214
1.28k
        return L1B_NOAA15_NOHDR;
3215
3216
    // A few NOAA <= 9 datasets with no dataset name in TBM header
3217
110k
    if (strlen(pszFilename) == L1B_DATASET_NAME_SIZE && pszFilename[3] == '.' &&
3218
9
        pszFilename[8] == '.' && pszFilename[11] == '.' &&
3219
0
        pszFilename[18] == '.' && pszFilename[24] == '.' &&
3220
0
        pszFilename[30] == '.' && pszFilename[39] == '.' &&
3221
0
        memcmp(pabyHeader + 30,
3222
0
               "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
3223
0
               "\0\0\0\0\0\0\0\0\0\0\0",
3224
0
               L1B_DATASET_NAME_SIZE) == 0 &&
3225
0
        (pabyHeader[75] == '+' || pabyHeader[75] == '-') &&
3226
0
        (pabyHeader[78] == '+' || pabyHeader[78] == '-') &&
3227
0
        (pabyHeader[81] == '+' || pabyHeader[81] == '-') &&
3228
0
        (pabyHeader[85] == '+' || pabyHeader[85] == '-'))
3229
0
        return L1B_NOAA9;
3230
3231
110k
    return L1B_NONE;
3232
110k
}
3233
3234
/************************************************************************/
3235
/*                              Identify()                              */
3236
/************************************************************************/
3237
3238
int L1BDataset::Identify(GDALOpenInfo *poOpenInfo)
3239
3240
550k
{
3241
550k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "L1BGCPS:"))
3242
4
        return TRUE;
3243
550k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "L1BGCPS_INTERPOL:"))
3244
0
        return TRUE;
3245
550k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "L1B_SOLAR_ZENITH_ANGLES:"))
3246
0
        return TRUE;
3247
550k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "L1B_ANGLES:"))
3248
0
        return TRUE;
3249
550k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "L1B_CLOUDS:"))
3250
0
        return TRUE;
3251
3252
550k
    if (DetectFormat(CPLGetFilename(poOpenInfo->pszFilename),
3253
550k
                     poOpenInfo->pabyHeader,
3254
550k
                     poOpenInfo->nHeaderBytes) == L1B_NONE)
3255
546k
        return FALSE;
3256
3257
4.26k
    return TRUE;
3258
550k
}
3259
3260
/************************************************************************/
3261
/*                                Open()                                */
3262
/************************************************************************/
3263
3264
GDALDataset *L1BDataset::Open(GDALOpenInfo *poOpenInfo)
3265
3266
4.26k
{
3267
4.26k
    GDALDataset *poOutDS = nullptr;
3268
4.26k
    VSILFILE *fp = nullptr;
3269
4.26k
    CPLString osFilename = poOpenInfo->pszFilename;
3270
4.26k
    int bAskGeolocationDS = FALSE;
3271
4.26k
    int bInterpolGeolocationDS = FALSE;
3272
4.26k
    int bAskSolarZenithAnglesDS = FALSE;
3273
4.26k
    int bAskAnglesDS = FALSE;
3274
4.26k
    int bAskCloudsDS = FALSE;
3275
4.26k
    L1BFileFormat eL1BFormat;
3276
3277
4.26k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "L1BGCPS:") ||
3278
4.26k
        STARTS_WITH_CI(poOpenInfo->pszFilename, "L1BGCPS_INTERPOL:") ||
3279
4.26k
        STARTS_WITH_CI(poOpenInfo->pszFilename, "L1B_SOLAR_ZENITH_ANGLES:") ||
3280
4.26k
        STARTS_WITH_CI(poOpenInfo->pszFilename, "L1B_ANGLES:") ||
3281
4.26k
        STARTS_WITH_CI(poOpenInfo->pszFilename, "L1B_CLOUDS:"))
3282
4
    {
3283
4
        GByte abyHeader[1024];
3284
4
        const char *pszFilename = nullptr;
3285
4
        if (STARTS_WITH_CI(poOpenInfo->pszFilename, "L1BGCPS_INTERPOL:"))
3286
0
        {
3287
0
            bAskGeolocationDS = TRUE;
3288
0
            bInterpolGeolocationDS = TRUE;
3289
0
            pszFilename = poOpenInfo->pszFilename + strlen("L1BGCPS_INTERPOL:");
3290
0
        }
3291
4
        else if (STARTS_WITH_CI(poOpenInfo->pszFilename, "L1BGCPS:"))
3292
4
        {
3293
4
            bAskGeolocationDS = TRUE;
3294
4
            pszFilename = poOpenInfo->pszFilename + strlen("L1BGCPS:");
3295
4
        }
3296
0
        else if (STARTS_WITH_CI(poOpenInfo->pszFilename,
3297
0
                                "L1B_SOLAR_ZENITH_ANGLES:"))
3298
0
        {
3299
0
            bAskSolarZenithAnglesDS = TRUE;
3300
0
            pszFilename =
3301
0
                poOpenInfo->pszFilename + strlen("L1B_SOLAR_ZENITH_ANGLES:");
3302
0
        }
3303
0
        else if (STARTS_WITH_CI(poOpenInfo->pszFilename, "L1B_ANGLES:"))
3304
0
        {
3305
0
            bAskAnglesDS = TRUE;
3306
0
            pszFilename = poOpenInfo->pszFilename + strlen("L1B_ANGLES:");
3307
0
        }
3308
0
        else
3309
0
        {
3310
0
            bAskCloudsDS = TRUE;
3311
0
            pszFilename = poOpenInfo->pszFilename + strlen("L1B_CLOUDS:");
3312
0
        }
3313
4
        if (pszFilename[0] == '"')
3314
0
            pszFilename++;
3315
4
        osFilename = pszFilename;
3316
4
        if (!osFilename.empty() && osFilename.back() == '"')
3317
0
            osFilename.pop_back();
3318
4
        fp = VSIFOpenL(osFilename, "rb");
3319
4
        if (!fp)
3320
4
        {
3321
4
            CPLError(CE_Failure, CPLE_AppDefined, "Can't open file \"%s\".",
3322
4
                     osFilename.c_str());
3323
4
            return nullptr;
3324
4
        }
3325
0
        CPL_IGNORE_RET_VAL(VSIFReadL(abyHeader, 1, sizeof(abyHeader) - 1, fp));
3326
0
        abyHeader[sizeof(abyHeader) - 1] = '\0';
3327
0
        eL1BFormat = DetectFormat(CPLGetFilename(osFilename), abyHeader,
3328
0
                                  sizeof(abyHeader));
3329
0
    }
3330
4.26k
    else
3331
4.26k
        eL1BFormat =
3332
4.26k
            DetectFormat(CPLGetFilename(osFilename), poOpenInfo->pabyHeader,
3333
4.26k
                         poOpenInfo->nHeaderBytes);
3334
3335
4.26k
    if (eL1BFormat == L1B_NONE)
3336
0
    {
3337
0
        if (fp != nullptr)
3338
0
            CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
3339
0
        return nullptr;
3340
0
    }
3341
3342
    /* -------------------------------------------------------------------- */
3343
    /*      Confirm the requested access is supported.                      */
3344
    /* -------------------------------------------------------------------- */
3345
4.26k
    if (poOpenInfo->eAccess == GA_Update)
3346
0
    {
3347
0
        ReportUpdateNotSupportedByDriver("L1B");
3348
0
        if (fp != nullptr)
3349
0
            CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
3350
0
        return nullptr;
3351
0
    }
3352
3353
    /* -------------------------------------------------------------------- */
3354
    /*      Create a corresponding GDALDataset.                             */
3355
    /* -------------------------------------------------------------------- */
3356
4.26k
    L1BDataset *poDS;
3357
4.26k
    VSIStatBufL sStat;
3358
3359
4.26k
    poDS = new L1BDataset(eL1BFormat);
3360
3361
4.26k
    if (fp == nullptr)
3362
4.26k
        fp = VSIFOpenL(osFilename, "rb");
3363
4.26k
    poDS->fp = fp;
3364
4.26k
    if (!poDS->fp || VSIStatL(osFilename, &sStat) != 0)
3365
0
    {
3366
0
        CPLDebug("L1B", "Can't open file \"%s\".", osFilename.c_str());
3367
0
        goto bad;
3368
0
    }
3369
3370
    /* -------------------------------------------------------------------- */
3371
    /*      Read the header.                                                */
3372
    /* -------------------------------------------------------------------- */
3373
4.26k
    if (poDS->ProcessDatasetHeader(CPLGetFilename(osFilename)) != CE_None)
3374
1.81k
    {
3375
1.81k
        CPLDebug("L1B", "Error reading L1B record header.");
3376
1.81k
        goto bad;
3377
1.81k
    }
3378
3379
2.45k
    if (poDS->eL1BFormat == L1B_NOAA15_NOHDR &&
3380
242
        poDS->nRecordSizeFromHeader == 22016 &&
3381
0
        (sStat.st_size % 22016 /* poDS->nRecordSizeFromHeader*/) == 0)
3382
0
    {
3383
0
        poDS->iDataFormat = UNPACKED16BIT;
3384
0
        poDS->ComputeFileOffsets();
3385
0
        poDS->nDataStartOffset = poDS->nRecordSizeFromHeader;
3386
0
        poDS->nRecordSize = poDS->nRecordSizeFromHeader;
3387
0
        poDS->iCLAVRStart = 0;
3388
0
    }
3389
2.45k
    else if (poDS->bGuessDataFormat)
3390
1.13k
    {
3391
1.13k
        int nTempYSize;
3392
1.13k
        GUInt16 nScanlineNumber;
3393
1.13k
        int j;
3394
3395
        /* If the data format is unspecified, try each one of the 3 known data
3396
         * formats */
3397
        /* It is considered valid when the spacing between the first 5 scanline
3398
         * numbers */
3399
        /* is a constant */
3400
3401
4.54k
        for (j = 0; j < 3; j++)
3402
3.41k
        {
3403
3.41k
            poDS->iDataFormat = (L1BDataFormat)(PACKED10BIT + j);
3404
3.41k
            if (!poDS->ComputeFileOffsets())
3405
0
                goto bad;
3406
3407
3.41k
            nTempYSize = static_cast<int>(
3408
3.41k
                (sStat.st_size - poDS->nDataStartOffset) / poDS->nRecordSize);
3409
3.41k
            if (nTempYSize < 5)
3410
3.09k
                continue;
3411
3412
316
            int nLastScanlineNumber = 0;
3413
316
            int nDiffLine = 0;
3414
316
            int i;
3415
874
            for (i = 0; i < 5; i++)
3416
874
            {
3417
874
                nScanlineNumber = 0;
3418
3419
874
                CPL_IGNORE_RET_VAL(VSIFSeekL(
3420
874
                    poDS->fp, poDS->nDataStartOffset + i * poDS->nRecordSize,
3421
874
                    SEEK_SET));
3422
874
                CPL_IGNORE_RET_VAL(VSIFReadL(&nScanlineNumber, 1, 2, poDS->fp));
3423
874
                nScanlineNumber = poDS->GetUInt16(&nScanlineNumber);
3424
3425
874
                if (i == 1)
3426
316
                {
3427
316
                    nDiffLine = nScanlineNumber - nLastScanlineNumber;
3428
316
                    if (nDiffLine == 0)
3429
75
                        break;
3430
316
                }
3431
558
                else if (i > 1)
3432
242
                {
3433
242
                    if (nDiffLine != nScanlineNumber - nLastScanlineNumber)
3434
241
                        break;
3435
242
                }
3436
3437
558
                nLastScanlineNumber = nScanlineNumber;
3438
558
            }
3439
3440
316
            if (i == 5)
3441
0
            {
3442
0
                CPLDebug("L1B", "Guessed data format : %s",
3443
0
                         (poDS->iDataFormat == PACKED10BIT)    ? "10"
3444
0
                         : (poDS->iDataFormat == UNPACKED8BIT) ? "08"
3445
0
                                                               : "16");
3446
0
                break;
3447
0
            }
3448
316
        }
3449
3450
1.13k
        if (j == 3)
3451
1.13k
        {
3452
1.13k
            CPLError(CE_Failure, CPLE_AppDefined,
3453
1.13k
                     "Could not guess data format of L1B product");
3454
1.13k
            goto bad;
3455
1.13k
        }
3456
1.13k
    }
3457
1.31k
    else
3458
1.31k
    {
3459
1.31k
        if (!poDS->ComputeFileOffsets())
3460
0
            goto bad;
3461
1.31k
    }
3462
3463
1.31k
    CPLDebug("L1B", "nRecordDataStart = %d", poDS->nRecordDataStart);
3464
1.31k
    CPLDebug("L1B", "nRecordDataEnd = %d", poDS->nRecordDataEnd);
3465
1.31k
    CPLDebug("L1B", "nDataStartOffset = %d", poDS->nDataStartOffset);
3466
1.31k
    CPLDebug("L1B", "iCLAVRStart = %d", poDS->iCLAVRStart);
3467
1.31k
    CPLDebug("L1B", "nRecordSize = %d", poDS->nRecordSize);
3468
3469
    // Compute number of lines dynamically, so we can read partially
3470
    // downloaded files.
3471
1.31k
    if (poDS->nDataStartOffset > sStat.st_size)
3472
296
        goto bad;
3473
1.01k
    poDS->nRasterYSize = static_cast<int>(
3474
1.01k
        (sStat.st_size - poDS->nDataStartOffset) / poDS->nRecordSize);
3475
3476
    /* -------------------------------------------------------------------- */
3477
    /*      Deal with GCPs                                                  */
3478
    /* -------------------------------------------------------------------- */
3479
1.01k
    poDS->ProcessRecordHeaders();
3480
3481
1.01k
    if (bAskGeolocationDS)
3482
0
    {
3483
0
        return L1BGeolocDataset::CreateGeolocationDS(poDS,
3484
0
                                                     bInterpolGeolocationDS);
3485
0
    }
3486
1.01k
    else if (bAskSolarZenithAnglesDS)
3487
0
    {
3488
0
        if (eL1BFormat == L1B_NOAA9)
3489
0
            return L1BSolarZenithAnglesDataset::CreateSolarZenithAnglesDS(poDS);
3490
0
        else
3491
0
        {
3492
0
            delete poDS;
3493
0
            return nullptr;
3494
0
        }
3495
0
    }
3496
1.01k
    else if (bAskAnglesDS)
3497
0
    {
3498
0
        if (eL1BFormat != L1B_NOAA9)
3499
0
            return L1BNOAA15AnglesDataset::CreateAnglesDS(poDS);
3500
0
        else
3501
0
        {
3502
0
            delete poDS;
3503
0
            return nullptr;
3504
0
        }
3505
0
    }
3506
1.01k
    else if (bAskCloudsDS)
3507
0
    {
3508
0
        if (poDS->iCLAVRStart > 0)
3509
0
            poOutDS = L1BCloudsDataset::CreateCloudsDS(poDS);
3510
0
        else
3511
0
        {
3512
0
            delete poDS;
3513
0
            return nullptr;
3514
0
        }
3515
0
    }
3516
1.01k
    else
3517
1.01k
    {
3518
1.01k
        poOutDS = poDS;
3519
1.01k
    }
3520
3521
1.01k
    {
3522
1.01k
        CPLString osTMP;
3523
1.01k
        int bInterpol =
3524
1.01k
            CPLTestBool(CPLGetConfigOption("L1B_INTERPOL_GCPS", "TRUE"));
3525
3526
1.01k
        char *pszWKT = nullptr;
3527
1.01k
        poDS->m_oGCPSRS.exportToWkt(&pszWKT);
3528
1.01k
        poOutDS->SetMetadataItem(
3529
1.01k
            "SRS", pszWKT, GDAL_MDD_GEOLOCATION); /* unused by gdalgeoloc.cpp */
3530
1.01k
        CPLFree(pszWKT);
3531
3532
1.01k
        if (bInterpol)
3533
1.01k
            osTMP.Printf("L1BGCPS_INTERPOL:\"%s\"", osFilename.c_str());
3534
0
        else
3535
0
            osTMP.Printf("L1BGCPS:\"%s\"", osFilename.c_str());
3536
1.01k
        poOutDS->SetMetadataItem("X_DATASET", osTMP, GDAL_MDD_GEOLOCATION);
3537
1.01k
        poOutDS->SetMetadataItem("X_BAND", "1", GDAL_MDD_GEOLOCATION);
3538
1.01k
        poOutDS->SetMetadataItem("Y_DATASET", osTMP, GDAL_MDD_GEOLOCATION);
3539
1.01k
        poOutDS->SetMetadataItem("Y_BAND", "2", GDAL_MDD_GEOLOCATION);
3540
3541
1.01k
        if (bInterpol)
3542
1.01k
        {
3543
1.01k
            poOutDS->SetMetadataItem("PIXEL_OFFSET", "0", GDAL_MDD_GEOLOCATION);
3544
1.01k
            poOutDS->SetMetadataItem("PIXEL_STEP", "1", GDAL_MDD_GEOLOCATION);
3545
1.01k
        }
3546
0
        else
3547
0
        {
3548
0
            osTMP.Printf("%d", poDS->iGCPStart);
3549
0
            poOutDS->SetMetadataItem("PIXEL_OFFSET", osTMP,
3550
0
                                     GDAL_MDD_GEOLOCATION);
3551
0
            osTMP.Printf("%d", poDS->iGCPStep);
3552
0
            poOutDS->SetMetadataItem("PIXEL_STEP", osTMP, GDAL_MDD_GEOLOCATION);
3553
0
        }
3554
3555
1.01k
        poOutDS->SetMetadataItem("LINE_OFFSET", "0", GDAL_MDD_GEOLOCATION);
3556
1.01k
        poOutDS->SetMetadataItem("LINE_STEP", "1", GDAL_MDD_GEOLOCATION);
3557
1.01k
    }
3558
3559
1.01k
    if (poOutDS != poDS)
3560
0
        return poOutDS;
3561
3562
1.01k
    if (eL1BFormat == L1B_NOAA9)
3563
829
    {
3564
829
        char **papszSubdatasets = nullptr;
3565
829
        papszSubdatasets = CSLSetNameValue(
3566
829
            papszSubdatasets, "SUBDATASET_1_NAME",
3567
829
            CPLSPrintf("L1B_SOLAR_ZENITH_ANGLES:\"%s\"", osFilename.c_str()));
3568
829
        papszSubdatasets = CSLSetNameValue(
3569
829
            papszSubdatasets, "SUBDATASET_1_DESC", "Solar zenith angles");
3570
829
        poDS->SetMetadata(papszSubdatasets, GDAL_MDD_SUBDATASETS);
3571
829
        CSLDestroy(papszSubdatasets);
3572
829
    }
3573
189
    else
3574
189
    {
3575
189
        char **papszSubdatasets = nullptr;
3576
189
        papszSubdatasets = CSLSetNameValue(
3577
189
            papszSubdatasets, "SUBDATASET_1_NAME",
3578
189
            CPLSPrintf("L1B_ANGLES:\"%s\"", osFilename.c_str()));
3579
189
        papszSubdatasets =
3580
189
            CSLSetNameValue(papszSubdatasets, "SUBDATASET_1_DESC",
3581
189
                            "Solar zenith angles, satellite zenith angles and "
3582
189
                            "relative azimuth angles");
3583
3584
189
        if (poDS->iCLAVRStart > 0)
3585
189
        {
3586
189
            papszSubdatasets = CSLSetNameValue(
3587
189
                papszSubdatasets, "SUBDATASET_2_NAME",
3588
189
                CPLSPrintf("L1B_CLOUDS:\"%s\"", osFilename.c_str()));
3589
189
            papszSubdatasets =
3590
189
                CSLSetNameValue(papszSubdatasets, "SUBDATASET_2_DESC",
3591
189
                                "Clouds from AVHRR (CLAVR)");
3592
189
        }
3593
3594
189
        poDS->SetMetadata(papszSubdatasets, GDAL_MDD_SUBDATASETS);
3595
189
        CSLDestroy(papszSubdatasets);
3596
189
    }
3597
3598
    /* -------------------------------------------------------------------- */
3599
    /*      Create band information objects.                                */
3600
    /* -------------------------------------------------------------------- */
3601
1.01k
    int iBand, i;
3602
3603
2.89k
    for (iBand = 1, i = 0; iBand <= poDS->nBands; iBand++)
3604
1.87k
    {
3605
1.87k
        poDS->SetBand(iBand, new L1BRasterBand(poDS, iBand));
3606
3607
        // Channels descriptions
3608
1.87k
        if (poDS->eSpacecraftID >= NOAA6 && poDS->eSpacecraftID <= METOP3)
3609
1.87k
        {
3610
1.87k
            if (!(i & 0x01) && poDS->iChannelsMask & 0x01)
3611
504
            {
3612
504
                poDS->GetRasterBand(iBand)->SetDescription(apszBandDesc[0]);
3613
504
                i |= 0x01;
3614
504
                continue;
3615
504
            }
3616
1.37k
            if (!(i & 0x02) && poDS->iChannelsMask & 0x02)
3617
214
            {
3618
214
                poDS->GetRasterBand(iBand)->SetDescription(apszBandDesc[1]);
3619
214
                i |= 0x02;
3620
214
                continue;
3621
214
            }
3622
1.16k
            if (!(i & 0x04) && poDS->iChannelsMask & 0x04)
3623
221
            {
3624
221
                if (poDS->eSpacecraftID >= NOAA15 &&
3625
189
                    poDS->eSpacecraftID <= METOP3)
3626
189
                    if (poDS->iInstrumentStatus & 0x0400)
3627
22
                        poDS->GetRasterBand(iBand)->SetDescription(
3628
22
                            apszBandDesc[7]);
3629
167
                    else
3630
167
                        poDS->GetRasterBand(iBand)->SetDescription(
3631
167
                            apszBandDesc[6]);
3632
32
                else
3633
32
                    poDS->GetRasterBand(iBand)->SetDescription(apszBandDesc[2]);
3634
221
                i |= 0x04;
3635
221
                continue;
3636
221
            }
3637
939
            if (!(i & 0x08) && poDS->iChannelsMask & 0x08)
3638
298
            {
3639
298
                poDS->GetRasterBand(iBand)->SetDescription(apszBandDesc[3]);
3640
298
                i |= 0x08;
3641
298
                continue;
3642
298
            }
3643
641
            if (!(i & 0x10) && poDS->iChannelsMask & 0x10)
3644
216
            {
3645
216
                if (poDS->eSpacecraftID == NOAA13)  // 5 NOAA-13
3646
0
                    poDS->GetRasterBand(iBand)->SetDescription(apszBandDesc[5]);
3647
216
                else if (poDS->eSpacecraftID == NOAA6 ||
3648
216
                         poDS->eSpacecraftID == NOAA8 ||
3649
216
                         poDS->eSpacecraftID == NOAA10)  // 4 NOAA-6,-8,-10
3650
0
                    poDS->GetRasterBand(iBand)->SetDescription(apszBandDesc[3]);
3651
216
                else
3652
216
                    poDS->GetRasterBand(iBand)->SetDescription(apszBandDesc[4]);
3653
216
                i |= 0x10;
3654
216
                continue;
3655
216
            }
3656
641
        }
3657
1.87k
    }
3658
3659
1.01k
    if (poDS->bExposeMaskBand)
3660
189
        poDS->poMaskBand = new L1BMaskBand(poDS);
3661
3662
    /* -------------------------------------------------------------------- */
3663
    /*      Initialize any PAM information.                                 */
3664
    /* -------------------------------------------------------------------- */
3665
1.01k
    poDS->SetDescription(poOpenInfo->pszFilename);
3666
1.01k
    poDS->TryLoadXML();
3667
3668
    /* -------------------------------------------------------------------- */
3669
    /*      Check for external overviews.                                   */
3670
    /* -------------------------------------------------------------------- */
3671
1.01k
    poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename,
3672
1.01k
                                poOpenInfo->GetSiblingFiles());
3673
3674
    /* -------------------------------------------------------------------- */
3675
    /*      Fetch metadata in CSV file                                      */
3676
    /* -------------------------------------------------------------------- */
3677
1.01k
    if (CPLTestBool(CPLGetConfigOption("L1B_FETCH_METADATA", "NO")))
3678
0
    {
3679
0
        poDS->FetchMetadata();
3680
0
    }
3681
3682
1.01k
    return poDS;
3683
3684
3.24k
bad:
3685
3.24k
    delete poDS;
3686
3.24k
    return nullptr;
3687
1.01k
}
3688
3689
/************************************************************************/
3690
/*                          GDALRegister_L1B()                          */
3691
/************************************************************************/
3692
3693
void GDALRegister_L1B()
3694
3695
24
{
3696
24
    if (GDALGetDriverByName("L1B") != nullptr)
3697
0
        return;
3698
3699
24
    GDALDriver *poDriver = new GDALDriver();
3700
3701
24
    poDriver->SetDescription("L1B");
3702
24
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
3703
24
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
3704
24
                              "NOAA Polar Orbiter Level 1b Data Set");
3705
24
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/l1b.html");
3706
24
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
3707
24
    poDriver->SetMetadataItem(GDAL_DMD_SUBDATASETS, "YES");
3708
3709
24
    poDriver->pfnOpen = L1BDataset::Open;
3710
24
    poDriver->pfnIdentify = L1BDataset::Identify;
3711
3712
24
    GetGDALDriverManager()->RegisterDriver(poDriver);
3713
24
}