Coverage Report

Created: 2026-02-14 09:00

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
182k
#define L1B_DATASET_NAME_SIZE 42
132
86.1k
#define L1B_NOAA9_HEADER_SIZE 122   // Terabit memory (TBM) header length
133
64.9k
#define L1B_NOAA9_HDR_NAME_OFF 30   // Dataset name offset
134
#define L1B_NOAA9_HDR_SRC_OFF 70    // Receiving station name offset
135
57.9k
#define L1B_NOAA9_HDR_CHAN_OFF 97   // Selected channels map offset
136
30.9k
#define L1B_NOAA9_HDR_CHAN_SIZE 20  // Length of selected channels map
137
718
#define L1B_NOAA9_HDR_WORD_OFF 117  // Sensor data word size offset
138
139
// Archive Retrieval System (ARS) header
140
151k
#define L1B_NOAA15_HEADER_SIZE 512
141
11.5k
#define L1B_NOAA15_HDR_CHAN_OFF 97   // Selected channels map offset
142
6.32k
#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
2.07k
#define L1B_NOAA9_HDR_REC_SIZE 146
147
1.93k
#define L1B_NOAA9_HDR_REC_ID_OFF 0      // Spacecraft ID offset
148
1.03k
#define L1B_NOAA9_HDR_REC_PROD_OFF 1    // Data type offset
149
1.00k
#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
642
#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
284
    4  // NOAA Level 1b Format Version Number
158
#define L1B_NOAA15_HDR_REC_FORMAT_VERSION_YEAR_OFF                             \
159
813
    6  // Level 1b Format Version Year (e.g., 1999)
160
#define L1B_NOAA15_HDR_REC_FORMAT_VERSION_DAY_OFF                              \
161
813
    8  // Level 1b Format Version Day of Year (e.g., 365)
162
#define L1B_NOAA15_HDR_REC_LOGICAL_REC_LENGTH_OFF                              \
163
284
    10  // Logical Record Length of source Level 1b data set prior to processing
164
#define L1B_NOAA15_HDR_REC_BLOCK_SIZE_OFF                                      \
165
284
    12  // Block Size of source Level 1b data set prior to processing
166
#define L1B_NOAA15_HDR_REC_HDR_REC_COUNT_OFF                                   \
167
813
    14  // Count of Header Records in this Data Set
168
284
#define L1B_NOAA15_HDR_REC_NAME_OFF 22   // Dataset name
169
284
#define L1B_NOAA15_HDR_REC_ID_OFF 72     // Spacecraft ID offset
170
139
#define L1B_NOAA15_HDR_REC_PROD_OFF 76   // Data type offset
171
138
#define L1B_NOAA15_HDR_REC_STAT_OFF 116  // Instrument status offset
172
284
#define L1B_NOAA15_HDR_REC_DATA_RECORD_COUNT_OFF 128
173
284
#define L1B_NOAA15_HDR_REC_CALIBRATED_SCANLINE_COUNT_OFF 130
174
284
#define L1B_NOAA15_HDR_REC_MISSING_SCANLINE_COUNT_OFF 132
175
138
#define L1B_NOAA15_HDR_REC_SRC_OFF 154  // Receiving station name offset
176
284
#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
706k
#define L1B_NOAA9_GCP_SCALE 128.0
185
217k
#define L1B_NOAA15_GCP_SCALE 10000.0
186
187
/************************************************************************/
188
/* ==================================================================== */
189
/*                      TimeCode (helper class)                         */
190
/* ==================================================================== */
191
/************************************************************************/
192
193
1.21k
#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
4.18k
    TimeCode() : lYear(0), lDay(0), lMillisecond(0)
204
4.18k
    {
205
4.18k
        memset(szString, 0, sizeof(szString));
206
4.18k
    }
207
208
    void SetYear(long year)
209
1.21k
    {
210
1.21k
        lYear = year;
211
1.21k
    }
212
213
    void SetDay(long day)
214
1.21k
    {
215
1.21k
        lDay = day;
216
1.21k
    }
217
218
    void SetMillisecond(long millisecond)
219
1.21k
    {
220
1.21k
        lMillisecond = millisecond;
221
1.21k
    }
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.21k
    {
240
1.21k
        snprintf(szString, L1B_TIMECODE_LENGTH,
241
1.21k
                 "year: %ld, day: %ld, millisecond: %ld", lYear, lDay,
242
1.21k
                 lMillisecond);
243
1.21k
        return szString;
244
1.21k
    }
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
130
{
394
130
    CPLAssert(poDSIn->eL1BFormat == L1B_NOAA15 ||
395
130
              poDSIn->eL1BFormat == L1B_NOAA15_NOHDR);
396
397
130
    poDS = poDSIn;
398
130
    eDataType = GDT_UInt8;
399
400
130
    nRasterXSize = poDS->GetRasterXSize();
401
130
    nRasterYSize = poDS->GetRasterYSize();
402
130
    nBlockXSize = poDS->GetRasterXSize();
403
130
    nBlockYSize = 1;
404
130
}
405
406
/************************************************************************/
407
/*                             IReadBlock()                             */
408
/************************************************************************/
409
410
CPLErr L1BMaskBand::IReadBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
411
                               void *pImage)
412
2.13k
{
413
2.13k
    L1BDataset *poGDS = cpl::down_cast<L1BDataset *>(poDS);
414
415
2.13k
    CPL_IGNORE_RET_VAL(
416
2.13k
        VSIFSeekL(poGDS->fp, poGDS->GetLineOffset(nBlockYOff) + 24, SEEK_SET));
417
418
2.13k
    GByte abyData[4];
419
2.13k
    CPL_IGNORE_RET_VAL(VSIFReadL(abyData, 1, 4, poGDS->fp));
420
2.13k
    GUInt32 n32 = poGDS->GetUInt32(abyData);
421
422
2.13k
    if ((n32 >> 31) != 0) /* fatal flag */
423
1.04k
        memset(pImage, 0, nBlockXSize);
424
1.08k
    else
425
1.08k
        memset(pImage, 255, nBlockXSize);
426
427
2.13k
    return CE_None;
428
2.13k
}
429
430
/************************************************************************/
431
/*                           L1BRasterBand()                            */
432
/************************************************************************/
433
434
L1BRasterBand::L1BRasterBand(L1BDataset *poDSIn, int nBandIn)
435
436
1.23k
{
437
1.23k
    poDS = poDSIn;
438
1.23k
    nBand = nBandIn;
439
1.23k
    eDataType = GDT_UInt16;
440
441
1.23k
    nBlockXSize = poDS->GetRasterXSize();
442
1.23k
    nBlockYSize = 1;
443
1.23k
}
444
445
/************************************************************************/
446
/*                            GetMaskBand()                             */
447
/************************************************************************/
448
449
GDALRasterBand *L1BRasterBand::GetMaskBand()
450
6.79k
{
451
6.79k
    L1BDataset *poGDS = cpl::down_cast<L1BDataset *>(poDS);
452
6.79k
    if (poGDS->poMaskBand)
453
6.35k
        return poGDS->poMaskBand;
454
435
    return GDALPamRasterBand::GetMaskBand();
455
6.79k
}
456
457
/************************************************************************/
458
/*                            GetMaskFlags()                            */
459
/************************************************************************/
460
461
int L1BRasterBand::GetMaskFlags()
462
7.17k
{
463
7.17k
    L1BDataset *poGDS = cpl::down_cast<L1BDataset *>(poDS);
464
7.17k
    if (poGDS->poMaskBand)
465
6.69k
        return GMF_PER_DATASET;
466
480
    return GDALPamRasterBand::GetMaskFlags();
467
7.17k
}
468
469
/************************************************************************/
470
/*                             IReadBlock()                             */
471
/************************************************************************/
472
473
CPLErr L1BRasterBand::IReadBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
474
                                 void *pImage)
475
15.5k
{
476
15.5k
    L1BDataset *poGDS = cpl::down_cast<L1BDataset *>(poDS);
477
478
    /* -------------------------------------------------------------------- */
479
    /*      Seek to data.                                                   */
480
    /* -------------------------------------------------------------------- */
481
15.5k
    CPL_IGNORE_RET_VAL(
482
15.5k
        VSIFSeekL(poGDS->fp, poGDS->GetLineOffset(nBlockYOff), SEEK_SET));
483
484
    /* -------------------------------------------------------------------- */
485
    /*      Read data into the buffer.                                      */
486
    /* -------------------------------------------------------------------- */
487
15.5k
    GUInt16 *iScan = nullptr;  // Unpacked 16-bit scanline buffer
488
15.5k
    int i, j;
489
490
15.5k
    switch (poGDS->iDataFormat)
491
15.5k
    {
492
8.21k
        case PACKED10BIT:
493
8.21k
        {
494
            // Read packed scanline
495
8.21k
            GUInt32 *iRawScan = (GUInt32 *)CPLMalloc(poGDS->nRecordSize);
496
8.21k
            CPL_IGNORE_RET_VAL(
497
8.21k
                VSIFReadL(iRawScan, 1, poGDS->nRecordSize, poGDS->fp));
498
499
8.21k
            iScan = (GUInt16 *)CPLMalloc(poGDS->nBufferSize);
500
8.21k
            j = 0;
501
8.21k
            for (i = poGDS->nRecordDataStart / (int)sizeof(iRawScan[0]);
502
5.79M
                 i < poGDS->nRecordDataEnd / (int)sizeof(iRawScan[0]); i++)
503
5.78M
            {
504
5.78M
                GUInt32 iWord1 = poGDS->GetUInt32(&iRawScan[i]);
505
5.78M
                GUInt32 iWord2 = iWord1 & 0x3FF00000;
506
507
5.78M
                iScan[j++] = (GUInt16)(iWord2 >> 20);
508
5.78M
                iWord2 = iWord1 & 0x000FFC00;
509
5.78M
                iScan[j++] = (GUInt16)(iWord2 >> 10);
510
5.78M
                iScan[j++] = (GUInt16)(iWord1 & 0x000003FF);
511
5.78M
            }
512
8.21k
            CPLFree(iRawScan);
513
8.21k
        }
514
8.21k
        break;
515
1.33k
        case UNPACKED16BIT:
516
1.33k
        {
517
            // Read unpacked scanline
518
1.33k
            GUInt16 *iRawScan = (GUInt16 *)CPLMalloc(poGDS->nRecordSize);
519
1.33k
            CPL_IGNORE_RET_VAL(
520
1.33k
                VSIFReadL(iRawScan, 1, poGDS->nRecordSize, poGDS->fp));
521
522
1.33k
            iScan = (GUInt16 *)CPLMalloc(
523
1.33k
                sizeof(GUInt16) * poGDS->GetRasterXSize() * poGDS->nBands);
524
548k
            for (i = 0; i < poGDS->GetRasterXSize() * poGDS->nBands; i++)
525
546k
            {
526
546k
                iScan[i] =
527
546k
                    poGDS->GetUInt16(&iRawScan[poGDS->nRecordDataStart /
528
546k
                                                   (int)sizeof(iRawScan[0]) +
529
546k
                                               i]);
530
546k
            }
531
1.33k
            CPLFree(iRawScan);
532
1.33k
        }
533
1.33k
        break;
534
6.01k
        case UNPACKED8BIT:
535
6.01k
        {
536
            // Read 8-bit unpacked scanline
537
6.01k
            GByte *byRawScan = (GByte *)CPLMalloc(poGDS->nRecordSize);
538
6.01k
            CPL_IGNORE_RET_VAL(
539
6.01k
                VSIFReadL(byRawScan, 1, poGDS->nRecordSize, poGDS->fp));
540
541
6.01k
            iScan = (GUInt16 *)CPLMalloc(
542
6.01k
                sizeof(GUInt16) * poGDS->GetRasterXSize() * poGDS->nBands);
543
9.31M
            for (i = 0; i < poGDS->GetRasterXSize() * poGDS->nBands; i++)
544
9.31M
                iScan[i] = byRawScan[poGDS->nRecordDataStart /
545
9.31M
                                         (int)sizeof(byRawScan[0]) +
546
9.31M
                                     i];
547
6.01k
            CPLFree(byRawScan);
548
6.01k
        }
549
6.01k
        break;
550
0
        default:  // NOTREACHED
551
0
            break;
552
15.5k
    }
553
554
15.5k
    int nBlockSize = nBlockXSize * nBlockYSize;
555
15.5k
    if (poGDS->eLocationIndicator == DESCEND)
556
7.36k
    {
557
3.12M
        for (i = 0, j = 0; i < nBlockSize; i++)
558
3.12M
        {
559
3.12M
            ((GUInt16 *)pImage)[i] = iScan[j + nBand - 1];
560
3.12M
            j += poGDS->nBands;
561
3.12M
        }
562
7.36k
    }
563
8.21k
    else
564
8.21k
    {
565
4.44M
        for (i = nBlockSize - 1, j = 0; i >= 0; i--)
566
4.43M
        {
567
4.43M
            ((GUInt16 *)pImage)[i] = iScan[j + nBand - 1];
568
4.43M
            j += poGDS->nBands;
569
4.43M
        }
570
8.21k
    }
571
572
15.5k
    CPLFree(iScan);
573
15.5k
    return CE_None;
574
15.5k
}
575
576
/************************************************************************/
577
/*                             L1BDataset()                             */
578
/************************************************************************/
579
580
L1BDataset::L1BDataset(L1BFileFormat eL1BFormatIn)
581
2.09k
    : eSource(UNKNOWN_STATION), eProcCenter(UNKNOWN_CENTER),
582
      // sStartTime
583
      // sStopTime
584
      bHighGCPDensityStrategy(
585
2.09k
          CPLTestBool(CPLGetConfigOption("L1B_HIGH_GCP_DENSITY", "TRUE"))),
586
2.09k
      pasGCPList(nullptr), nGCPCount(0), iGCPOffset(0), iGCPCodeOffset(0),
587
2.09k
      iCLAVRStart(0), nGCPsPerLine(0),
588
2.09k
      eLocationIndicator(DESCEND),  // XXX: should be initialized
589
2.09k
      iGCPStart(0), iGCPStep(0), eL1BFormat(eL1BFormatIn), nBufferSize(0),
590
2.09k
      eSpacecraftID(TIROSN), eProductType(HRPT), iDataFormat(PACKED10BIT),
591
2.09k
      nRecordDataStart(0), nRecordDataEnd(0), nDataStartOffset(0),
592
2.09k
      nRecordSize(0), nRecordSizeFromHeader(0), iInstrumentStatus(0),
593
2.09k
      iChannelsMask(0), fp(nullptr), bGuessDataFormat(FALSE),
594
      // L1B is normally big-endian ordered, so byte-swap on little-endian CPU.
595
2.09k
      bByteSwap(CPL_IS_LSB), bExposeMaskBand(FALSE), poMaskBand(nullptr)
596
2.09k
{
597
2.09k
    m_oGCPSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
598
2.09k
    m_oGCPSRS.importFromWkt(
599
2.09k
        "GEOGCS[\"WGS 72\",DATUM[\"WGS_1972\","
600
2.09k
        "SPHEROID[\"WGS 72\",6378135,298.26,AUTHORITY[\"EPSG\",7043]],"
601
2.09k
        "TOWGS84[0,0,4.5,0,0,0.554,0.2263],AUTHORITY[\"EPSG\",6322]],"
602
2.09k
        "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",8901]],"
603
2.09k
        "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",9108]],"
604
2.09k
        "AUTHORITY[\"EPSG\",4322]]");
605
2.09k
}
606
607
/************************************************************************/
608
/*                            ~L1BDataset()                             */
609
/************************************************************************/
610
611
L1BDataset::~L1BDataset()
612
613
2.09k
{
614
2.09k
    FlushCache(true);
615
616
2.09k
    if (nGCPCount > 0)
617
572
    {
618
572
        GDALDeinitGCPs(nGCPCount, pasGCPList);
619
572
        CPLFree(pasGCPList);
620
572
    }
621
2.09k
    if (fp != nullptr)
622
2.09k
        CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
623
2.09k
    delete poMaskBand;
624
2.09k
}
625
626
/************************************************************************/
627
/*                           GetLineOffset()                            */
628
/************************************************************************/
629
630
vsi_l_offset L1BDataset::GetLineOffset(int nBlockYOff) const
631
17.7k
{
632
17.7k
    return (eLocationIndicator == DESCEND)
633
17.7k
               ? nDataStartOffset + (vsi_l_offset)nBlockYOff * nRecordSize
634
17.7k
               : nDataStartOffset +
635
9.16k
                     (vsi_l_offset)(nRasterYSize - nBlockYOff - 1) *
636
9.16k
                         nRecordSize;
637
17.7k
}
638
639
/************************************************************************/
640
/*                            GetGCPCount()                             */
641
/************************************************************************/
642
643
int L1BDataset::GetGCPCount()
644
645
1.84k
{
646
1.84k
    return nGCPCount;
647
1.84k
}
648
649
/************************************************************************/
650
/*                          GetGCPSpatialRef()                          */
651
/************************************************************************/
652
653
const OGRSpatialReference *L1BDataset::GetGCPSpatialRef() const
654
655
275
{
656
275
    if (nGCPCount > 0 && !m_oGCPSRS.IsEmpty())
657
248
        return &m_oGCPSRS;
658
27
    else
659
27
        return nullptr;
660
275
}
661
662
/************************************************************************/
663
/*                              GetGCPs()                               */
664
/************************************************************************/
665
666
const GDAL_GCP *L1BDataset::GetGCPs()
667
275
{
668
275
    return pasGCPList;
669
275
}
670
671
/************************************************************************/
672
/*                        Byte swapping helpers                         */
673
/************************************************************************/
674
675
GUInt16 L1BDataset::GetUInt16(const void *pabyData) const
676
552k
{
677
552k
    GUInt16 iTemp;
678
552k
    memcpy(&iTemp, pabyData, 2);
679
552k
    if (bByteSwap)
680
551k
        return CPL_SWAP16(iTemp);
681
1.28k
    return iTemp;
682
552k
}
683
684
GInt16 L1BDataset::GetInt16(const void *pabyData) const
685
706k
{
686
706k
    GInt16 iTemp;
687
706k
    memcpy(&iTemp, pabyData, 2);
688
706k
    if (bByteSwap)
689
706k
        return CPL_SWAP16(iTemp);
690
0
    return iTemp;
691
706k
}
692
693
GUInt32 L1BDataset::GetUInt32(const void *pabyData) const
694
5.79M
{
695
5.79M
    GUInt32 lTemp;
696
5.79M
    memcpy(&lTemp, pabyData, 4);
697
5.79M
    if (bByteSwap)
698
3.18M
        return CPL_SWAP32(lTemp);
699
2.60M
    return lTemp;
700
5.79M
}
701
702
GInt32 L1BDataset::GetInt32(const void *pabyData) const
703
217k
{
704
217k
    GInt32 lTemp;
705
217k
    memcpy(&lTemp, pabyData, 4);
706
217k
    if (bByteSwap)
707
150k
        return CPL_SWAP32(lTemp);
708
67.1k
    return lTemp;
709
217k
}
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
970
{
719
970
    GUInt32 lTemp;
720
721
970
    lTemp = ((piRecordHeader[2] >> 1) & 0x7F);
722
970
    psTime->SetYear((lTemp > 77)
723
970
                        ? (lTemp + 1900)
724
970
                        : (lTemp + 2000));  // Avoid `Year 2000' problem
725
970
    psTime->SetDay((GUInt32)(piRecordHeader[2] & 0x01) << 8 |
726
970
                   (GUInt32)piRecordHeader[3]);
727
970
    psTime->SetMillisecond(((GUInt32)(piRecordHeader[4] & 0x07) << 24) |
728
970
                           ((GUInt32)piRecordHeader[5] << 16) |
729
970
                           ((GUInt32)piRecordHeader[6] << 8) |
730
970
                           (GUInt32)piRecordHeader[7]);
731
970
    if (peLocationIndicator)
732
485
    {
733
485
        *peLocationIndicator =
734
485
            ((piRecordHeader[8] & 0x02) == 0) ? ASCEND : DESCEND;
735
485
    }
736
970
}
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
240
{
746
240
    psTime->SetYear(GetUInt16(pabyRecordHeader + 2));
747
240
    psTime->SetDay(GetUInt16(pabyRecordHeader + 4));
748
240
    psTime->SetMillisecond(GetUInt32(pabyRecordHeader + 8));
749
240
    if (peLocationIndicator)
750
120
    {
751
        // FIXME: hemisphere
752
120
        *peLocationIndicator =
753
120
            ((GetUInt16(pabyRecordHeader + 12) & 0x8000) == 0) ? ASCEND
754
120
                                                               : DESCEND;
755
120
    }
756
240
}
757
758
/************************************************************************/
759
/*                           FetchTimeCode()                            */
760
/************************************************************************/
761
762
void L1BDataset::FetchTimeCode(TimeCode *psTime, const void *pRecordHeader,
763
                               int *peLocationIndicator) const
764
1.21k
{
765
1.21k
    if (eSpacecraftID <= NOAA14)
766
970
    {
767
970
        FetchNOAA9TimeCode(psTime, (const GByte *)pRecordHeader,
768
970
                           peLocationIndicator);
769
970
    }
770
240
    else
771
240
    {
772
240
        FetchNOAA15TimeCode(psTime, (const GByte *)pRecordHeader,
773
240
                            peLocationIndicator);
774
240
    }
775
1.21k
}
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
13.5k
{
784
    // LAC and HRPT GCPs are tied to the center of pixel,
785
    // GAC ones are slightly displaced.
786
13.5k
    double dfDelta = (eProductType == GAC) ? 0.9 : 0.5;
787
13.5k
    double dfPixel = (eLocationIndicator == DESCEND)
788
13.5k
                         ? iGCPStart + dfDelta
789
13.5k
                         : (nRasterXSize - (iGCPStart + dfDelta));
790
791
13.5k
    int nGCPs;
792
13.5k
    if (eSpacecraftID <= NOAA14)
793
11.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
11.4k
        nGCPs = (*(pabyRecordHeader + iGCPCodeOffset) < nGCPsPerLine)
797
11.4k
                    ? *(pabyRecordHeader + iGCPCodeOffset)
798
11.4k
                    : nGCPsPerLine;
799
#ifdef DEBUG_VERBOSE
800
        CPLDebug("L1B", "iGCPCodeOffset=%d, nGCPsPerLine=%d, nGoodGCPs=%d",
801
                 iGCPCodeOffset, nGCPsPerLine, nGCPs);
802
#endif
803
11.4k
    }
804
2.13k
    else
805
2.13k
        nGCPs = nGCPsPerLine;
806
807
13.5k
    pabyRecordHeader += iGCPOffset;
808
809
13.5k
    int nGCPCountRow = 0;
810
475k
    while (nGCPs--)
811
462k
    {
812
462k
        if (eSpacecraftID <= NOAA14)
813
353k
        {
814
353k
            GInt16 nRawY = GetInt16(pabyRecordHeader);
815
353k
            pabyRecordHeader += sizeof(GInt16);
816
353k
            GInt16 nRawX = GetInt16(pabyRecordHeader);
817
353k
            pabyRecordHeader += sizeof(GInt16);
818
819
353k
            pasGCPListRow[nGCPCountRow].dfGCPY = nRawY / L1B_NOAA9_GCP_SCALE;
820
353k
            pasGCPListRow[nGCPCountRow].dfGCPX = nRawX / L1B_NOAA9_GCP_SCALE;
821
353k
        }
822
108k
        else
823
108k
        {
824
108k
            GInt32 nRawY = GetInt32(pabyRecordHeader);
825
108k
            pabyRecordHeader += sizeof(GInt32);
826
108k
            GInt32 nRawX = GetInt32(pabyRecordHeader);
827
108k
            pabyRecordHeader += sizeof(GInt32);
828
829
108k
            pasGCPListRow[nGCPCountRow].dfGCPY = nRawY / L1B_NOAA15_GCP_SCALE;
830
108k
            pasGCPListRow[nGCPCountRow].dfGCPX = nRawX / L1B_NOAA15_GCP_SCALE;
831
108k
        }
832
833
462k
        if (pasGCPListRow[nGCPCountRow].dfGCPX < -180 ||
834
381k
            pasGCPListRow[nGCPCountRow].dfGCPX > 180 ||
835
276k
            pasGCPListRow[nGCPCountRow].dfGCPY < -90 ||
836
249k
            pasGCPListRow[nGCPCountRow].dfGCPY > 90)
837
314k
            continue;
838
839
148k
        pasGCPListRow[nGCPCountRow].dfGCPZ = 0.0;
840
148k
        pasGCPListRow[nGCPCountRow].dfGCPPixel = dfPixel;
841
148k
        dfPixel += (eLocationIndicator == DESCEND) ? iGCPStep : -iGCPStep;
842
148k
        pasGCPListRow[nGCPCountRow].dfGCPLine =
843
148k
            (double)((eLocationIndicator == DESCEND)
844
148k
                         ? iLine
845
148k
                         : nRasterYSize - iLine - 1) +
846
148k
            0.5;
847
148k
        nGCPCountRow++;
848
148k
    }
849
13.5k
    return nGCPCountRow;
850
13.5k
}
851
852
/************************************************************************/
853
/*                        ProcessRecordHeaders()                        */
854
/************************************************************************/
855
856
void L1BDataset::ProcessRecordHeaders()
857
668
{
858
668
    if (nRasterYSize == 0)
859
63
        return;
860
605
    void *pRecordHeader = CPLCalloc(1, nRecordDataStart);
861
862
605
    CPL_IGNORE_RET_VAL(
863
605
        VSIFSeekL(fp, static_cast<vsi_l_offset>(nDataStartOffset), SEEK_SET));
864
605
    CPL_IGNORE_RET_VAL(VSIFReadL(pRecordHeader, 1, nRecordDataStart, fp));
865
866
605
    FetchTimeCode(&sStartTime, pRecordHeader, &eLocationIndicator);
867
868
605
    CPL_IGNORE_RET_VAL(
869
605
        VSIFSeekL(fp,
870
605
                  nDataStartOffset +
871
605
                      static_cast<vsi_l_offset>(nRasterYSize - 1) * nRecordSize,
872
605
                  SEEK_SET));
873
605
    CPL_IGNORE_RET_VAL(VSIFReadL(pRecordHeader, 1, nRecordDataStart, fp));
874
875
605
    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
605
    int nTargetLines;
883
605
    double dfLineStep = 0.0;
884
885
605
    if (bHighGCPDensityStrategy)
886
605
    {
887
605
        if (nRasterYSize < nGCPsPerLine)
888
493
        {
889
493
            nTargetLines = nRasterYSize;
890
493
        }
891
112
        else
892
112
        {
893
112
            int nColStep;
894
112
            nColStep = nRasterXSize / nGCPsPerLine;
895
112
            if (nRasterYSize >= nRasterXSize)
896
7
            {
897
7
                dfLineStep = nColStep;
898
7
            }
899
105
            else
900
105
            {
901
105
                dfLineStep = nRasterYSize / nGCPsPerLine;
902
105
            }
903
112
            nTargetLines = static_cast<int>(nRasterYSize / dfLineStep);
904
112
        }
905
605
    }
906
0
    else
907
0
    {
908
0
        nTargetLines = std::min(DESIRED_LINES_OF_GCPS, nRasterYSize);
909
0
    }
910
605
    if (nTargetLines > 1)
911
519
        dfLineStep = 1.0 * (nRasterYSize - 1) / (nTargetLines - 1);
912
913
    /* -------------------------------------------------------------------- */
914
    /*      Initialize the GCP list.                                        */
915
    /* -------------------------------------------------------------------- */
916
605
    const int nExpectedGCPs = nTargetLines * nGCPsPerLine;
917
605
    if (nExpectedGCPs > 0)
918
605
    {
919
605
        pasGCPList =
920
605
            (GDAL_GCP *)VSI_CALLOC_VERBOSE(nExpectedGCPs, sizeof(GDAL_GCP));
921
605
        if (pasGCPList == nullptr)
922
0
        {
923
0
            CPLFree(pRecordHeader);
924
0
            return;
925
0
        }
926
605
        GDALInitGCPs(nExpectedGCPs, pasGCPList);
927
605
    }
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
605
    int iStep;
935
605
    int iPrevLine = -1;
936
937
14.1k
    for (iStep = 0; iStep < nTargetLines; iStep++)
938
13.5k
    {
939
13.5k
        int iLine;
940
941
13.5k
        if (iStep == nTargetLines - 1)
942
605
            iLine = nRasterYSize - 1;
943
12.9k
        else
944
12.9k
            iLine = (int)(dfLineStep * iStep);
945
13.5k
        if (iLine == iPrevLine)
946
0
            continue;
947
13.5k
        iPrevLine = iLine;
948
949
13.5k
        CPL_IGNORE_RET_VAL(
950
13.5k
            VSIFSeekL(fp, nDataStartOffset + iLine * nRecordSize, SEEK_SET));
951
13.5k
        CPL_IGNORE_RET_VAL(VSIFReadL(pRecordHeader, 1, nRecordDataStart, fp));
952
953
13.5k
        int nGCPsOnThisLine =
954
13.5k
            FetchGCPs(pasGCPList + nGCPCount, (GByte *)pRecordHeader, iLine);
955
956
13.5k
        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
13.5k
        else
995
13.5k
        {
996
13.5k
            nGCPCount += nGCPsOnThisLine;
997
13.5k
        }
998
13.5k
    }
999
1000
605
    if (nGCPCount < nExpectedGCPs)
1001
603
    {
1002
603
        GDALDeinitGCPs(nExpectedGCPs - nGCPCount, pasGCPList + nGCPCount);
1003
603
        if (nGCPCount == 0)
1004
33
        {
1005
33
            CPLFree(pasGCPList);
1006
33
            pasGCPList = nullptr;
1007
33
        }
1008
603
    }
1009
1010
605
    CPLFree(pRecordHeader);
1011
1012
    /* -------------------------------------------------------------------- */
1013
    /*      Set fetched information as metadata records                     */
1014
    /* -------------------------------------------------------------------- */
1015
    // Time of first scanline
1016
605
    SetMetadataItem("START", sStartTime.PrintTime());
1017
    // Time of last scanline
1018
605
    SetMetadataItem("STOP", sStopTime.PrintTime());
1019
    // AVHRR Earth location indication
1020
1021
605
    switch (eLocationIndicator)
1022
605
    {
1023
315
        case ASCEND:
1024
315
            SetMetadataItem("LOCATION", "Ascending");
1025
315
            break;
1026
290
        case DESCEND:
1027
290
        default:
1028
290
            SetMetadataItem("LOCATION", "Descending");
1029
290
            break;
1030
605
    }
1031
605
}
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
2.09k
{
1406
2.09k
    char szDatasetName[L1B_DATASET_NAME_SIZE + 1];
1407
1408
2.09k
    if (eL1BFormat == L1B_NOAA9)
1409
1.47k
    {
1410
1.47k
        GByte abyTBMHeader[L1B_NOAA9_HEADER_SIZE];
1411
1412
1.47k
        if (VSIFSeekL(fp, 0, SEEK_SET) < 0 ||
1413
1.47k
            VSIFReadL(abyTBMHeader, 1, L1B_NOAA9_HEADER_SIZE, fp) <
1414
1.47k
                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
1.47k
        if (*(abyTBMHeader + 8 + 25) == 'K' &&
1422
756
            *(abyTBMHeader + 8 + 30) == 'K' &&
1423
756
            *(abyTBMHeader + 8 + 33) == 'K' &&
1424
756
            *(abyTBMHeader + 8 + 40) == 'K' &&
1425
756
            *(abyTBMHeader + 8 + 46) == 'K' &&
1426
756
            *(abyTBMHeader + 8 + 52) == 'K' && *(abyTBMHeader + 8 + 61) == 'K')
1427
756
        {
1428
32.5k
            for (int i = 0; i < L1B_DATASET_NAME_SIZE; i++)
1429
31.7k
                abyTBMHeader[L1B_NOAA9_HDR_NAME_OFF + i] =
1430
31.7k
                    EBCDICToASCII[abyTBMHeader[L1B_NOAA9_HDR_NAME_OFF + i]];
1431
756
        }
1432
1433
        // Fetch dataset name. NOAA-9/14 datasets contain the names in TBM
1434
        // header only, so read it there.
1435
1.47k
        memcpy(szDatasetName, abyTBMHeader + L1B_NOAA9_HDR_NAME_OFF,
1436
1.47k
               L1B_DATASET_NAME_SIZE);
1437
1.47k
        szDatasetName[L1B_DATASET_NAME_SIZE] = '\0';
1438
1439
        // Deal with a few NOAA <= 9 datasets with no dataset name in TBM header
1440
1.47k
        if (memcmp(szDatasetName,
1441
1.47k
                   "\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
1.47k
                   "\0\0\0\0\0\0\0\0\0\0\0\0\0",
1443
1.47k
                   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
1.47k
        if (STARTS_WITH_CI(szDatasetName, "CMS"))
1451
0
            eProcCenter = CMS;
1452
1.47k
        else if (STARTS_WITH_CI(szDatasetName, "DSS"))
1453
0
            eProcCenter = DSS;
1454
1.47k
        else if (STARTS_WITH_CI(szDatasetName, "NSS"))
1455
50
            eProcCenter = NSS;
1456
1.42k
        else if (STARTS_WITH_CI(szDatasetName, "UKM"))
1457
0
            eProcCenter = UKM;
1458
1.42k
        else
1459
1.42k
            eProcCenter = UNKNOWN_CENTER;
1460
1461
        // Determine number of bands
1462
1.47k
        int i;
1463
30.9k
        for (i = 0; i < L1B_NOAA9_HDR_CHAN_SIZE; i++)
1464
29.4k
        {
1465
29.4k
            if (abyTBMHeader[L1B_NOAA9_HDR_CHAN_OFF + i] == 1 ||
1466
28.4k
                abyTBMHeader[L1B_NOAA9_HDR_CHAN_OFF + i] == 'Y')
1467
1.01k
            {
1468
1.01k
                nBands++;
1469
1.01k
                iChannelsMask |= (1 << i);
1470
1.01k
            }
1471
29.4k
        }
1472
1.47k
        if (nBands == 0 || nBands > 5)
1473
714
        {
1474
714
            nBands = 5;
1475
714
            iChannelsMask = 0x1F;
1476
714
        }
1477
1478
        // Determine data format (10-bit packed or 8/16-bit unpacked)
1479
1.47k
        if (STARTS_WITH_CI((const char *)abyTBMHeader + L1B_NOAA9_HDR_WORD_OFF,
1480
1.47k
                           "10"))
1481
137
            iDataFormat = PACKED10BIT;
1482
1.33k
        else if (STARTS_WITH_CI(
1483
1.33k
                     (const char *)abyTBMHeader + L1B_NOAA9_HDR_WORD_OFF, "16"))
1484
79
            iDataFormat = UNPACKED16BIT;
1485
1.25k
        else if (STARTS_WITH_CI(
1486
1.25k
                     (const char *)abyTBMHeader + L1B_NOAA9_HDR_WORD_OFF, "08"))
1487
436
            iDataFormat = UNPACKED8BIT;
1488
821
        else if (STARTS_WITH_CI((const char *)abyTBMHeader +
1489
718
                                    L1B_NOAA9_HDR_WORD_OFF,
1490
718
                                "  ") ||
1491
718
                 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
386
            bGuessDataFormat = TRUE;
1503
435
        else
1504
435
        {
1505
#ifdef DEBUG
1506
            CPLDebug("L1B", "Unknown data format \"%.2s\".",
1507
                     abyTBMHeader + L1B_NOAA9_HDR_WORD_OFF);
1508
#endif
1509
435
            return CE_Failure;
1510
435
        }
1511
1512
        // Now read the dataset header record
1513
1.03k
        GByte abyRecHeader[L1B_NOAA9_HDR_REC_SIZE];
1514
1.03k
        if (VSIFSeekL(fp, L1B_NOAA9_HEADER_SIZE, SEEK_SET) < 0 ||
1515
1.03k
            VSIFReadL(abyRecHeader, 1, L1B_NOAA9_HDR_REC_SIZE, fp) <
1516
1.03k
                L1B_NOAA9_HDR_REC_SIZE)
1517
7
        {
1518
7
            CPLDebug("L1B", "Can't read NOAA-9/14 record header.");
1519
7
            return CE_Failure;
1520
7
        }
1521
1522
        // Determine the spacecraft name
1523
1.03k
        switch (abyRecHeader[L1B_NOAA9_HDR_REC_ID_OFF])
1524
1.03k
        {
1525
2
            case 4:
1526
2
                eSpacecraftID = NOAA7;
1527
2
                break;
1528
10
            case 6:
1529
10
                eSpacecraftID = NOAA8;
1530
10
                break;
1531
3
            case 7:
1532
3
                eSpacecraftID = NOAA9;
1533
3
                break;
1534
15
            case 8:
1535
15
                eSpacecraftID = NOAA10;
1536
15
                break;
1537
55
            case 1:
1538
55
            {
1539
                /* We could also use the time code to determine TIROS-N */
1540
55
                if (strlen(pszFilename) == L1B_DATASET_NAME_SIZE &&
1541
1
                    STARTS_WITH(pszFilename + 8, ".TN."))
1542
0
                    eSpacecraftID = TIROSN;
1543
55
                else
1544
55
                    eSpacecraftID = NOAA11;
1545
55
                break;
1546
0
            }
1547
46
            case 5:
1548
46
                eSpacecraftID = NOAA12;
1549
46
                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
1
            case 3:
1561
1
                eSpacecraftID = NOAA14;
1562
1
                break;
1563
899
            default:
1564
899
                CPLError(CE_Warning, CPLE_AppDefined,
1565
899
                         "Unknown spacecraft ID \"%d\".",
1566
899
                         abyRecHeader[L1B_NOAA9_HDR_REC_ID_OFF]);
1567
1568
899
                eSpacecraftID = NOAA9_UNKNOWN;
1569
899
                break;
1570
1.03k
        }
1571
1572
        // Determine the product data type
1573
1.03k
        int iWord = abyRecHeader[L1B_NOAA9_HDR_REC_PROD_OFF] >> 4;
1574
1.03k
        switch (iWord)
1575
1.03k
        {
1576
10
            case 1:
1577
10
                eProductType = LAC;
1578
10
                break;
1579
720
            case 2:
1580
720
                eProductType = GAC;
1581
720
                break;
1582
274
            case 3:
1583
274
                eProductType = HRPT;
1584
274
                break;
1585
27
            default:
1586
#ifdef DEBUG
1587
                CPLDebug("L1B", "Unknown product type \"%d\".", iWord);
1588
#endif
1589
27
                return CE_Failure;
1590
1.03k
        }
1591
1592
        // Determine receiving station name
1593
1.00k
        iWord = (abyRecHeader[L1B_NOAA9_HDR_REC_DSTAT_OFF] & 0x60) >> 5;
1594
1.00k
        switch (iWord)
1595
1.00k
        {
1596
209
            case 1:
1597
209
                eSource = GC;
1598
209
                break;
1599
250
            case 2:
1600
250
                eSource = WI;
1601
250
                break;
1602
282
            case 3:
1603
282
                eSource = SO;
1604
282
                break;
1605
263
            default:
1606
263
                eSource = UNKNOWN_STATION;
1607
263
                break;
1608
1.00k
        }
1609
1.00k
    }
1610
1611
619
    else if (eL1BFormat == L1B_NOAA15 || eL1BFormat == L1B_NOAA15_NOHDR)
1612
619
    {
1613
619
        if (eL1BFormat == L1B_NOAA15)
1614
301
        {
1615
301
            GByte abyARSHeader[L1B_NOAA15_HEADER_SIZE];
1616
1617
301
            if (VSIFSeekL(fp, 0, SEEK_SET) < 0 ||
1618
301
                VSIFReadL(abyARSHeader, 1, L1B_NOAA15_HEADER_SIZE, fp) <
1619
301
                    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
301
            int i;
1627
6.32k
            for (i = 0; i < L1B_NOAA15_HDR_CHAN_SIZE; i++)
1628
6.02k
            {
1629
6.02k
                if (abyARSHeader[L1B_NOAA15_HDR_CHAN_OFF + i] == 1 ||
1630
5.55k
                    abyARSHeader[L1B_NOAA15_HDR_CHAN_OFF + i] == 'Y')
1631
505
                {
1632
505
                    nBands++;
1633
505
                    iChannelsMask |= (1 << i);
1634
505
                }
1635
6.02k
            }
1636
301
            if (nBands == 0 || nBands > 5)
1637
47
            {
1638
47
                nBands = 5;
1639
47
                iChannelsMask = 0x1F;
1640
47
            }
1641
1642
            // Determine data format (10-bit packed or 8/16-bit unpacked)
1643
301
            if (STARTS_WITH_CI(
1644
301
                    (const char *)abyARSHeader + L1B_NOAA15_HDR_WORD_OFF, "10"))
1645
1
                iDataFormat = PACKED10BIT;
1646
300
            else if (STARTS_WITH_CI((const char *)abyARSHeader +
1647
300
                                        L1B_NOAA15_HDR_WORD_OFF,
1648
300
                                    "16"))
1649
0
                iDataFormat = UNPACKED16BIT;
1650
300
            else if (STARTS_WITH_CI((const char *)abyARSHeader +
1651
300
                                        L1B_NOAA15_HDR_WORD_OFF,
1652
300
                                    "08"))
1653
2
                iDataFormat = UNPACKED8BIT;
1654
298
            else
1655
298
            {
1656
#ifdef DEBUG
1657
                CPLDebug("L1B", "Unknown data format \"%.2s\".",
1658
                         abyARSHeader + L1B_NOAA9_HDR_WORD_OFF);
1659
#endif
1660
298
                return CE_Failure;
1661
298
            }
1662
301
        }
1663
318
        else
1664
318
        {
1665
318
            nBands = 5;
1666
318
            iChannelsMask = 0x1F;
1667
318
            iDataFormat = PACKED10BIT;
1668
318
        }
1669
1670
        // Now read the dataset header record
1671
321
        GByte abyRecHeader[L1B_NOAA15_HDR_REC_SIZE];
1672
321
        if (VSIFSeekL(fp,
1673
321
                      (eL1BFormat == L1B_NOAA15) ? L1B_NOAA15_HEADER_SIZE : 0,
1674
321
                      SEEK_SET) < 0 ||
1675
321
            VSIFReadL(abyRecHeader, 1, L1B_NOAA15_HDR_REC_SIZE, fp) <
1676
321
                L1B_NOAA15_HDR_REC_SIZE)
1677
37
        {
1678
37
            CPLDebug("L1B", "Can't read NOAA-9/14 record header.");
1679
37
            return CE_Failure;
1680
37
        }
1681
1682
        // Fetch dataset name
1683
284
        memcpy(szDatasetName, abyRecHeader + L1B_NOAA15_HDR_REC_NAME_OFF,
1684
284
               L1B_DATASET_NAME_SIZE);
1685
284
        szDatasetName[L1B_DATASET_NAME_SIZE] = '\0';
1686
1687
        // Determine processing center where the dataset was created
1688
284
        if (STARTS_WITH_CI((const char *)abyRecHeader +
1689
284
                               L1B_NOAA15_HDR_REC_SITE_OFF,
1690
284
                           "CMS"))
1691
0
            eProcCenter = CMS;
1692
284
        else if (STARTS_WITH_CI((const char *)abyRecHeader +
1693
284
                                    L1B_NOAA15_HDR_REC_SITE_OFF,
1694
284
                                "DSS"))
1695
0
            eProcCenter = DSS;
1696
284
        else if (STARTS_WITH_CI((const char *)abyRecHeader +
1697
284
                                    L1B_NOAA15_HDR_REC_SITE_OFF,
1698
284
                                "NSS"))
1699
0
            eProcCenter = NSS;
1700
284
        else if (STARTS_WITH_CI((const char *)abyRecHeader +
1701
284
                                    L1B_NOAA15_HDR_REC_SITE_OFF,
1702
284
                                "UKM"))
1703
0
            eProcCenter = UKM;
1704
284
        else
1705
284
            eProcCenter = UNKNOWN_CENTER;
1706
1707
284
        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
284
        int i = 0;
1714
284
        do
1715
813
        {
1716
813
            nFormatVersionYear = GetUInt16(
1717
813
                abyRecHeader + L1B_NOAA15_HDR_REC_FORMAT_VERSION_YEAR_OFF);
1718
813
            nFormatVersionDayOfYear = GetUInt16(
1719
813
                abyRecHeader + L1B_NOAA15_HDR_REC_FORMAT_VERSION_DAY_OFF);
1720
813
            nHeaderRecCount =
1721
813
                GetUInt16(abyRecHeader + L1B_NOAA15_HDR_REC_HDR_REC_COUNT_OFF);
1722
813
            if (i == 2)
1723
247
                break;
1724
566
            if (!(nFormatVersionYear >= 1980 && nFormatVersionYear <= 2100) &&
1725
566
                !(nFormatVersionDayOfYear <= 366) && !(nHeaderRecCount == 1))
1726
529
            {
1727
529
                if (i == 0)
1728
282
                    CPLDebug("L1B", "Trying little-endian ordering");
1729
247
                else
1730
247
                    CPLDebug("L1B", "Not completely convincing... Returning to "
1731
247
                                    "big-endian order");
1732
529
                bByteSwap = !bByteSwap;
1733
529
            }
1734
37
            else
1735
37
                break;
1736
529
            i++;
1737
529
        } while (i <= 2);
1738
284
        nRecordSizeFromHeader =
1739
284
            GetUInt16(abyRecHeader + L1B_NOAA15_HDR_REC_LOGICAL_REC_LENGTH_OFF);
1740
284
        int nFormatVersion =
1741
284
            GetUInt16(abyRecHeader + L1B_NOAA15_HDR_REC_FORMAT_VERSION_OFF);
1742
284
        CPLDebug("L1B", "NOAA Level 1b Format Version Number = %d",
1743
284
                 nFormatVersion);
1744
284
        CPLDebug("L1B", "Level 1b Format Version Year = %d",
1745
284
                 nFormatVersionYear);
1746
284
        CPLDebug("L1B", "Level 1b Format Version Day of Year = %d",
1747
284
                 nFormatVersionDayOfYear);
1748
284
        CPLDebug("L1B",
1749
284
                 "Logical Record Length of source Level 1b data set prior to "
1750
284
                 "processing = %d",
1751
284
                 nRecordSizeFromHeader);
1752
284
        int nBlockSize =
1753
284
            GetUInt16(abyRecHeader + L1B_NOAA15_HDR_REC_BLOCK_SIZE_OFF);
1754
284
        CPLDebug(
1755
284
            "L1B",
1756
284
            "Block Size of source Level 1b data set prior to processing = %d",
1757
284
            nBlockSize);
1758
284
        CPLDebug("L1B", "Count of Header Records in this Data Set = %d",
1759
284
                 nHeaderRecCount);
1760
1761
284
        int nDataRecordCount =
1762
284
            GetUInt16(abyRecHeader + L1B_NOAA15_HDR_REC_DATA_RECORD_COUNT_OFF);
1763
284
        CPLDebug("L1B", "Count of Data Records = %d", nDataRecordCount);
1764
1765
284
        int nCalibratedScanlineCount = GetUInt16(
1766
284
            abyRecHeader + L1B_NOAA15_HDR_REC_CALIBRATED_SCANLINE_COUNT_OFF);
1767
284
        CPLDebug("L1B", "Count of Calibrated, Earth Located Scan Lines = %d",
1768
284
                 nCalibratedScanlineCount);
1769
1770
284
        int nMissingScanlineCount = GetUInt16(
1771
284
            abyRecHeader + L1B_NOAA15_HDR_REC_MISSING_SCANLINE_COUNT_OFF);
1772
284
        CPLDebug("L1B", "Count of Missing Scan Lines = %d",
1773
284
                 nMissingScanlineCount);
1774
284
        if (nMissingScanlineCount != 0)
1775
269
            bExposeMaskBand = TRUE;
1776
1777
284
        char szEllipsoid[8 + 1];
1778
284
        memcpy(szEllipsoid, abyRecHeader + L1B_NOAA15_HDR_REC_ELLIPSOID_OFF, 8);
1779
284
        szEllipsoid[8] = '\0';
1780
284
        CPLDebug("L1B", "Reference Ellipsoid Model ID = '%s'", szEllipsoid);
1781
284
        if (EQUAL(szEllipsoid, "WGS-84  "))
1782
0
        {
1783
0
            m_oGCPSRS.importFromWkt(SRS_WKT_WGS84_LAT_LONG);
1784
0
        }
1785
284
        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
284
        int iWord = GetUInt16(abyRecHeader + L1B_NOAA15_HDR_REC_ID_OFF);
1798
284
        switch (iWord)
1799
284
        {
1800
21
            case 2:
1801
21
                eSpacecraftID = NOAA16;
1802
21
                break;
1803
101
            case 4:
1804
101
                eSpacecraftID = NOAA15;
1805
101
                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
6
            case 11:
1816
6
                eSpacecraftID = METOP1;
1817
6
                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
145
            default:
1829
#ifdef DEBUG
1830
                CPLDebug("L1B", "Unknown spacecraft ID \"%d\".", iWord);
1831
#endif
1832
145
                return CE_Failure;
1833
284
        }
1834
1835
        // Determine the product data type
1836
139
        iWord = GetUInt16(abyRecHeader + L1B_NOAA15_HDR_REC_PROD_OFF);
1837
139
        switch (iWord)
1838
139
        {
1839
6
            case 1:
1840
6
                eProductType = LAC;
1841
6
                break;
1842
131
            case 2:
1843
131
                eProductType = GAC;
1844
131
                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
1
            default:
1853
#ifdef DEBUG
1854
                CPLDebug("L1B", "Unknown product type \"%d\".", iWord);
1855
#endif
1856
1
                return CE_Failure;
1857
139
        }
1858
1859
        // Fetch instrument status. Helps to determine whether we have
1860
        // 3A or 3B channel in the dataset.
1861
138
        iInstrumentStatus =
1862
138
            GetUInt32(abyRecHeader + L1B_NOAA15_HDR_REC_STAT_OFF);
1863
1864
        // Determine receiving station name
1865
138
        iWord = GetUInt16(abyRecHeader + L1B_NOAA15_HDR_REC_SRC_OFF);
1866
138
        switch (iWord)
1867
138
        {
1868
6
            case 1:
1869
6
                eSource = GC;
1870
6
                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
131
            default:
1884
131
                eSource = UNKNOWN_STATION;
1885
131
                break;
1886
138
        }
1887
138
    }
1888
0
    else
1889
0
        return CE_Failure;
1890
1891
    /* -------------------------------------------------------------------- */
1892
    /*      Set fetched information as metadata records                     */
1893
    /* -------------------------------------------------------------------- */
1894
1895
1.14k
    SetMetadataItem("DATASET_NAME", szDatasetName);
1896
1897
1.14k
    const char *pszText = nullptr;
1898
1.14k
    switch (eSpacecraftID)
1899
1.14k
    {
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
1
        case NOAA7:
1910
1
            pszText = "NOAA-7(C)";
1911
1
            break;
1912
10
        case NOAA8:
1913
10
            pszText = "NOAA-8(E)";
1914
10
            break;
1915
874
        case NOAA9_UNKNOWN:
1916
874
            pszText = "UNKNOWN";
1917
874
            break;
1918
3
        case NOAA9:
1919
3
            pszText = "NOAA-9(F)";
1920
3
            break;
1921
15
        case NOAA10:
1922
15
            pszText = "NOAA-10(G)";
1923
15
            break;
1924
54
        case NOAA11:
1925
54
            pszText = "NOAA-11(H)";
1926
54
            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
100
        case NOAA15:
1937
100
            pszText = "NOAA-15(K)";
1938
100
            break;
1939
21
        case NOAA16:
1940
21
            pszText = "NOAA-16(L)";
1941
21
            break;
1942
11
        case NOAA17:
1943
11
            pszText = "NOAA-17(M)";
1944
11
            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
6
        case METOP1:
1955
6
            pszText = "METOP-B(1)";
1956
6
            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
1.14k
    }
1964
1.14k
    SetMetadataItem("SATELLITE", pszText);
1965
1966
1.14k
    switch (eProductType)
1967
1.14k
    {
1968
16
        case LAC:
1969
16
            pszText = "AVHRR LAC";
1970
16
            break;
1971
274
        case HRPT:
1972
274
            pszText = "AVHRR HRPT";
1973
274
            break;
1974
851
        case GAC:
1975
851
            pszText = "AVHRR GAC";
1976
851
            break;
1977
1
        case FRAC:
1978
1
            pszText = "AVHRR FRAC";
1979
1
            break;
1980
0
        default:
1981
0
            pszText = "Unknown";
1982
0
            break;
1983
1.14k
    }
1984
1.14k
    SetMetadataItem("DATA_TYPE", pszText);
1985
1986
    // Get revolution number as string, we don't need this value for processing
1987
1.14k
    char szRevolution[6];
1988
1.14k
    memcpy(szRevolution, szDatasetName + 32, 5);
1989
1.14k
    szRevolution[5] = '\0';
1990
1.14k
    SetMetadataItem("REVOLUTION", szRevolution);
1991
1992
1.14k
    switch (eSource)
1993
1.14k
    {
1994
0
        case DU:
1995
0
            pszText = "Dundee, Scotland, UK";
1996
0
            break;
1997
215
        case GC:
1998
215
            pszText = "Fairbanks, Alaska, USA (formerly Gilmore Creek)";
1999
215
            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
282
        case SO:
2010
282
            pszText = "SOCC (Satellite Operations Control Center), Suitland, "
2011
282
                      "Maryland, USA";
2012
282
            break;
2013
250
        case WI:
2014
250
            pszText = "Wallops Island, Virginia, USA";
2015
250
            break;
2016
395
        default:
2017
395
            pszText = "Unknown receiving station";
2018
395
            break;
2019
1.14k
    }
2020
1.14k
    SetMetadataItem("SOURCE", pszText);
2021
2022
1.14k
    switch (eProcCenter)
2023
1.14k
    {
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
1.09k
        default:
2039
1.09k
            pszText = "Unknown processing center";
2040
1.09k
            break;
2041
1.14k
    }
2042
1.14k
    SetMetadataItem("PROCESSING_CENTER", pszText);
2043
2044
1.14k
    return CE_None;
2045
1.14k
}
2046
2047
/************************************************************************/
2048
/*                         ComputeFileOffsets()                         */
2049
/************************************************************************/
2050
2051
int L1BDataset::ComputeFileOffsets()
2052
1.85k
{
2053
1.85k
    CPLDebug("L1B", "Data format = %s",
2054
1.85k
             (iDataFormat == PACKED10BIT)     ? "Packed 10 bit"
2055
1.85k
             : (iDataFormat == UNPACKED16BIT) ? "Unpacked 16 bit"
2056
1.22k
                                              : "Unpacked 8 bit");
2057
2058
1.85k
    switch (eProductType)
2059
1.85k
    {
2060
790
        case HRPT:
2061
824
        case LAC:
2062
825
        case FRAC:
2063
825
            nRasterXSize = 2048;
2064
825
            nBufferSize = 20484;
2065
825
            iGCPStart =
2066
825
                25 -
2067
825
                1; /* http://www.ncdc.noaa.gov/oa/pod-guide/ncdc/docs/klm/html/c2/sec2-4.htm
2068
                    */
2069
825
            iGCPStep = 40;
2070
825
            nGCPsPerLine = 51;
2071
825
            if (eL1BFormat == L1B_NOAA9)
2072
818
            {
2073
818
                if (iDataFormat == PACKED10BIT)
2074
270
                {
2075
270
                    nRecordSize = 14800;
2076
270
                    nRecordDataEnd = 14104;
2077
270
                }
2078
548
                else if (iDataFormat == UNPACKED16BIT)
2079
267
                {
2080
267
                    switch (nBands)
2081
267
                    {
2082
10
                        case 1:
2083
10
                            nRecordSize = 4544;
2084
10
                            nRecordDataEnd = 4544;
2085
10
                            break;
2086
4
                        case 2:
2087
4
                            nRecordSize = 8640;
2088
4
                            nRecordDataEnd = 8640;
2089
4
                            break;
2090
0
                        case 3:
2091
0
                            nRecordSize = 12736;
2092
0
                            nRecordDataEnd = 12736;
2093
0
                            break;
2094
1
                        case 4:
2095
1
                            nRecordSize = 16832;
2096
1
                            nRecordDataEnd = 16832;
2097
1
                            break;
2098
252
                        case 5:
2099
252
                            nRecordSize = 20928;
2100
252
                            nRecordDataEnd = 20928;
2101
252
                            break;
2102
267
                    }
2103
267
                }
2104
281
                else  // UNPACKED8BIT
2105
281
                {
2106
281
                    switch (nBands)
2107
281
                    {
2108
12
                        case 1:
2109
12
                            nRecordSize = 2496;
2110
12
                            nRecordDataEnd = 2496;
2111
12
                            break;
2112
4
                        case 2:
2113
4
                            nRecordSize = 4544;
2114
4
                            nRecordDataEnd = 4544;
2115
4
                            break;
2116
0
                        case 3:
2117
0
                            nRecordSize = 6592;
2118
0
                            nRecordDataEnd = 6592;
2119
0
                            break;
2120
1
                        case 4:
2121
1
                            nRecordSize = 8640;
2122
1
                            nRecordDataEnd = 8640;
2123
1
                            break;
2124
264
                        case 5:
2125
264
                            nRecordSize = 10688;
2126
264
                            nRecordDataEnd = 10688;
2127
264
                            break;
2128
281
                    }
2129
281
                }
2130
818
                nDataStartOffset = nRecordSize + L1B_NOAA9_HEADER_SIZE;
2131
818
                nRecordDataStart = 448;
2132
818
                iGCPCodeOffset = 52;
2133
818
                iGCPOffset = 104;
2134
818
            }
2135
2136
7
            else if (eL1BFormat == L1B_NOAA15 || eL1BFormat == L1B_NOAA15_NOHDR)
2137
7
            {
2138
7
                if (iDataFormat == PACKED10BIT)
2139
7
                {
2140
7
                    nRecordSize = 15872;
2141
7
                    nRecordDataEnd = 14920;
2142
7
                    iCLAVRStart = 14984;
2143
7
                }
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
7
                nDataStartOffset = (eL1BFormat == L1B_NOAA15_NOHDR)
2216
7
                                       ? nRecordDataEnd
2217
7
                                       : nRecordSize + L1B_NOAA15_HEADER_SIZE;
2218
7
                nRecordDataStart = 1264;
2219
7
                iGCPCodeOffset = 0;  // XXX: not exist for NOAA15?
2220
7
                iGCPOffset = 640;
2221
7
            }
2222
0
            else
2223
0
                return 0;
2224
825
            break;
2225
2226
1.02k
        case GAC:
2227
1.02k
            nRasterXSize = 409;
2228
1.02k
            nBufferSize = 4092;
2229
1.02k
            iGCPStart = 5 - 1;  // FIXME: depends of scan direction
2230
1.02k
            iGCPStep = 8;
2231
1.02k
            nGCPsPerLine = 51;
2232
1.02k
            if (eL1BFormat == L1B_NOAA9)
2233
896
            {
2234
896
                if (iDataFormat == PACKED10BIT)
2235
222
                {
2236
222
                    nRecordSize = 3220;
2237
222
                    nRecordDataEnd = 3176;
2238
222
                }
2239
674
                else if (iDataFormat == UNPACKED16BIT)
2240
166
                    switch (nBands)
2241
166
                    {
2242
100
                        case 1:
2243
100
                            nRecordSize = 1268;
2244
100
                            nRecordDataEnd = 1266;
2245
100
                            break;
2246
7
                        case 2:
2247
7
                            nRecordSize = 2084;
2248
7
                            nRecordDataEnd = 2084;
2249
7
                            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
59
                        case 5:
2259
59
                            nRecordSize = 4540;
2260
59
                            nRecordDataEnd = 4538;
2261
59
                            break;
2262
166
                    }
2263
508
                else  // UNPACKED8BIT
2264
508
                {
2265
508
                    switch (nBands)
2266
508
                    {
2267
427
                        case 1:
2268
427
                            nRecordSize = 860;
2269
427
                            nRecordDataEnd = 858;
2270
427
                            break;
2271
16
                        case 2:
2272
16
                            nRecordSize = 1268;
2273
16
                            nRecordDataEnd = 1266;
2274
16
                            break;
2275
3
                        case 3:
2276
3
                            nRecordSize = 1676;
2277
3
                            nRecordDataEnd = 1676;
2278
3
                            break;
2279
0
                        case 4:
2280
0
                            nRecordSize = 2084;
2281
0
                            nRecordDataEnd = 2084;
2282
0
                            break;
2283
62
                        case 5:
2284
62
                            nRecordSize = 2496;
2285
62
                            nRecordDataEnd = 2494;
2286
62
                            break;
2287
508
                    }
2288
508
                }
2289
896
                nDataStartOffset = nRecordSize * 2 + L1B_NOAA9_HEADER_SIZE;
2290
896
                nRecordDataStart = 448;
2291
896
                iGCPCodeOffset = 52;
2292
896
                iGCPOffset = 104;
2293
896
            }
2294
2295
131
            else if (eL1BFormat == L1B_NOAA15 || eL1BFormat == L1B_NOAA15_NOHDR)
2296
131
            {
2297
131
                if (iDataFormat == PACKED10BIT)
2298
131
                {
2299
131
                    nRecordSize = 4608;
2300
131
                    nRecordDataEnd = 3992;
2301
131
                    iCLAVRStart = 4056;
2302
131
                }
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
131
                nDataStartOffset = (eL1BFormat == L1B_NOAA15_NOHDR)
2376
131
                                       ? nRecordDataEnd
2377
131
                                       : nRecordSize + L1B_NOAA15_HEADER_SIZE;
2378
131
                nRecordDataStart = 1264;
2379
131
                iGCPCodeOffset = 0;  // XXX: not exist for NOAA15?
2380
131
                iGCPOffset = 640;
2381
131
            }
2382
0
            else
2383
0
                return 0;
2384
1.02k
            break;
2385
1.02k
        default:
2386
0
            return 0;
2387
1.85k
    }
2388
2389
1.85k
    return 1;
2390
1.85k
}
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
359k
{
3174
359k
    if (pabyHeader == nullptr || nHeaderBytes < L1B_NOAA9_HEADER_SIZE)
3175
283k
        return L1B_NONE;
3176
3177
    // try NOAA-18 formats
3178
76.5k
    if (*(pabyHeader + 0) == '\0' && *(pabyHeader + 1) == '\0' &&
3179
215
        *(pabyHeader + 2) == '\0' && *(pabyHeader + 3) == '\0' &&
3180
76
        *(pabyHeader + 4) == '\0' && *(pabyHeader + 5) == '\0' &&
3181
37
        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
76.5k
    if (nHeaderBytes > L1B_NOAA15_HEADER_SIZE + 61 &&
3186
69.5k
        *(pabyHeader + L1B_NOAA15_HEADER_SIZE + 25) == '.' &&
3187
1.78k
        *(pabyHeader + L1B_NOAA15_HEADER_SIZE + 30) == '.' &&
3188
748
        *(pabyHeader + L1B_NOAA15_HEADER_SIZE + 33) == '.' &&
3189
731
        *(pabyHeader + L1B_NOAA15_HEADER_SIZE + 40) == '.' &&
3190
656
        *(pabyHeader + L1B_NOAA15_HEADER_SIZE + 46) == '.' &&
3191
638
        *(pabyHeader + L1B_NOAA15_HEADER_SIZE + 52) == '.' &&
3192
615
        *(pabyHeader + L1B_NOAA15_HEADER_SIZE + 61) == '.')
3193
602
        return L1B_NOAA15;
3194
3195
    // Next try the NOAA-9/14 formats
3196
75.9k
    if (*(pabyHeader + 8 + 25) == '.' && *(pabyHeader + 8 + 30) == '.' &&
3197
2.42k
        *(pabyHeader + 8 + 33) == '.' && *(pabyHeader + 8 + 40) == '.' &&
3198
2.32k
        *(pabyHeader + 8 + 46) == '.' && *(pabyHeader + 8 + 52) == '.' &&
3199
2.00k
        *(pabyHeader + 8 + 61) == '.')
3200
1.43k
        return L1B_NOAA9;
3201
3202
    // Next try the NOAA-9/14 formats with dataset name in EBCDIC
3203
74.4k
    if (*(pabyHeader + 8 + 25) == 'K' && *(pabyHeader + 8 + 30) == 'K' &&
3204
1.85k
        *(pabyHeader + 8 + 33) == 'K' && *(pabyHeader + 8 + 40) == 'K' &&
3205
1.66k
        *(pabyHeader + 8 + 46) == 'K' && *(pabyHeader + 8 + 52) == 'K' &&
3206
1.52k
        *(pabyHeader + 8 + 61) == 'K')
3207
1.51k
        return L1B_NOAA9;
3208
3209
    // Finally try the AAPP formats
3210
72.9k
    if (*(pabyHeader + 25) == '.' && *(pabyHeader + 30) == '.' &&
3211
1.16k
        *(pabyHeader + 33) == '.' && *(pabyHeader + 40) == '.' &&
3212
799
        *(pabyHeader + 46) == '.' && *(pabyHeader + 52) == '.' &&
3213
740
        *(pabyHeader + 61) == '.')
3214
636
        return L1B_NOAA15_NOHDR;
3215
3216
    // A few NOAA <= 9 datasets with no dataset name in TBM header
3217
72.3k
    if (strlen(pszFilename) == L1B_DATASET_NAME_SIZE && pszFilename[3] == '.' &&
3218
0
        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
72.3k
    return L1B_NONE;
3232
72.3k
}
3233
3234
/************************************************************************/
3235
/*                              Identify()                              */
3236
/************************************************************************/
3237
3238
int L1BDataset::Identify(GDALOpenInfo *poOpenInfo)
3239
3240
357k
{
3241
357k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "L1BGCPS:"))
3242
4
        return TRUE;
3243
357k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "L1BGCPS_INTERPOL:"))
3244
0
        return TRUE;
3245
357k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "L1B_SOLAR_ZENITH_ANGLES:"))
3246
0
        return TRUE;
3247
357k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "L1B_ANGLES:"))
3248
0
        return TRUE;
3249
357k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "L1B_CLOUDS:"))
3250
0
        return TRUE;
3251
3252
357k
    if (DetectFormat(CPLGetFilename(poOpenInfo->pszFilename),
3253
357k
                     poOpenInfo->pabyHeader,
3254
357k
                     poOpenInfo->nHeaderBytes) == L1B_NONE)
3255
355k
        return FALSE;
3256
3257
2.09k
    return TRUE;
3258
357k
}
3259
3260
/************************************************************************/
3261
/*                                Open()                                */
3262
/************************************************************************/
3263
3264
GDALDataset *L1BDataset::Open(GDALOpenInfo *poOpenInfo)
3265
3266
2.09k
{
3267
2.09k
    GDALDataset *poOutDS = nullptr;
3268
2.09k
    VSILFILE *fp = nullptr;
3269
2.09k
    CPLString osFilename = poOpenInfo->pszFilename;
3270
2.09k
    int bAskGeolocationDS = FALSE;
3271
2.09k
    int bInterpolGeolocationDS = FALSE;
3272
2.09k
    int bAskSolarZenithAnglesDS = FALSE;
3273
2.09k
    int bAskAnglesDS = FALSE;
3274
2.09k
    int bAskCloudsDS = FALSE;
3275
2.09k
    L1BFileFormat eL1BFormat;
3276
3277
2.09k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "L1BGCPS:") ||
3278
2.09k
        STARTS_WITH_CI(poOpenInfo->pszFilename, "L1BGCPS_INTERPOL:") ||
3279
2.09k
        STARTS_WITH_CI(poOpenInfo->pszFilename, "L1B_SOLAR_ZENITH_ANGLES:") ||
3280
2.09k
        STARTS_WITH_CI(poOpenInfo->pszFilename, "L1B_ANGLES:") ||
3281
2.09k
        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
2.09k
    else
3331
2.09k
        eL1BFormat =
3332
2.09k
            DetectFormat(CPLGetFilename(osFilename), poOpenInfo->pabyHeader,
3333
2.09k
                         poOpenInfo->nHeaderBytes);
3334
3335
2.09k
    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
2.09k
    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
2.09k
    L1BDataset *poDS;
3357
2.09k
    VSIStatBufL sStat;
3358
3359
2.09k
    poDS = new L1BDataset(eL1BFormat);
3360
3361
2.09k
    if (fp == nullptr)
3362
2.09k
        fp = VSIFOpenL(osFilename, "rb");
3363
2.09k
    poDS->fp = fp;
3364
2.09k
    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
2.09k
    if (poDS->ProcessDatasetHeader(CPLGetFilename(osFilename)) != CE_None)
3374
950
    {
3375
950
        CPLDebug("L1B", "Error reading L1B record header.");
3376
950
        goto bad;
3377
950
    }
3378
3379
1.14k
    if (poDS->eL1BFormat == L1B_NOAA15_NOHDR &&
3380
138
        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
1.14k
    else if (poDS->bGuessDataFormat)
3390
355
    {
3391
355
        int nTempYSize;
3392
355
        GUInt16 nScanlineNumber;
3393
355
        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
1.42k
        for (j = 0; j < 3; j++)
3402
1.06k
        {
3403
1.06k
            poDS->iDataFormat = (L1BDataFormat)(PACKED10BIT + j);
3404
1.06k
            if (!poDS->ComputeFileOffsets())
3405
0
                goto bad;
3406
3407
1.06k
            nTempYSize = static_cast<int>(
3408
1.06k
                (sStat.st_size - poDS->nDataStartOffset) / poDS->nRecordSize);
3409
1.06k
            if (nTempYSize < 5)
3410
932
                continue;
3411
3412
133
            int nLastScanlineNumber = 0;
3413
133
            int nDiffLine = 0;
3414
133
            int i;
3415
370
            for (i = 0; i < 5; i++)
3416
370
            {
3417
370
                nScanlineNumber = 0;
3418
3419
370
                CPL_IGNORE_RET_VAL(VSIFSeekL(
3420
370
                    poDS->fp, poDS->nDataStartOffset + i * poDS->nRecordSize,
3421
370
                    SEEK_SET));
3422
370
                CPL_IGNORE_RET_VAL(VSIFReadL(&nScanlineNumber, 1, 2, poDS->fp));
3423
370
                nScanlineNumber = poDS->GetUInt16(&nScanlineNumber);
3424
3425
370
                if (i == 1)
3426
133
                {
3427
133
                    nDiffLine = nScanlineNumber - nLastScanlineNumber;
3428
133
                    if (nDiffLine == 0)
3429
29
                        break;
3430
133
                }
3431
237
                else if (i > 1)
3432
104
                {
3433
104
                    if (nDiffLine != nScanlineNumber - nLastScanlineNumber)
3434
104
                        break;
3435
104
                }
3436
3437
237
                nLastScanlineNumber = nScanlineNumber;
3438
237
            }
3439
3440
133
            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
133
        }
3449
3450
355
        if (j == 3)
3451
355
        {
3452
355
            CPLError(CE_Failure, CPLE_AppDefined,
3453
355
                     "Could not guess data format of L1B product");
3454
355
            goto bad;
3455
355
        }
3456
355
    }
3457
787
    else
3458
787
    {
3459
787
        if (!poDS->ComputeFileOffsets())
3460
0
            goto bad;
3461
787
    }
3462
3463
787
    CPLDebug("L1B", "nRecordDataStart = %d", poDS->nRecordDataStart);
3464
787
    CPLDebug("L1B", "nRecordDataEnd = %d", poDS->nRecordDataEnd);
3465
787
    CPLDebug("L1B", "nDataStartOffset = %d", poDS->nDataStartOffset);
3466
787
    CPLDebug("L1B", "iCLAVRStart = %d", poDS->iCLAVRStart);
3467
787
    CPLDebug("L1B", "nRecordSize = %d", poDS->nRecordSize);
3468
3469
    // Compute number of lines dynamically, so we can read partially
3470
    // downloaded files.
3471
787
    if (poDS->nDataStartOffset > sStat.st_size)
3472
119
        goto bad;
3473
668
    poDS->nRasterYSize = static_cast<int>(
3474
668
        (sStat.st_size - poDS->nDataStartOffset) / poDS->nRecordSize);
3475
3476
    /* -------------------------------------------------------------------- */
3477
    /*      Deal with GCPs                                                  */
3478
    /* -------------------------------------------------------------------- */
3479
668
    poDS->ProcessRecordHeaders();
3480
3481
668
    if (bAskGeolocationDS)
3482
0
    {
3483
0
        return L1BGeolocDataset::CreateGeolocationDS(poDS,
3484
0
                                                     bInterpolGeolocationDS);
3485
0
    }
3486
668
    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
668
    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
668
    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
668
    else
3517
668
    {
3518
668
        poOutDS = poDS;
3519
668
    }
3520
3521
668
    {
3522
668
        CPLString osTMP;
3523
668
        int bInterpol =
3524
668
            CPLTestBool(CPLGetConfigOption("L1B_INTERPOL_GCPS", "TRUE"));
3525
3526
668
        char *pszWKT = nullptr;
3527
668
        poDS->m_oGCPSRS.exportToWkt(&pszWKT);
3528
668
        poOutDS->SetMetadataItem("SRS", pszWKT,
3529
668
                                 "GEOLOCATION"); /* unused by gdalgeoloc.cpp */
3530
668
        CPLFree(pszWKT);
3531
3532
668
        if (bInterpol)
3533
668
            osTMP.Printf("L1BGCPS_INTERPOL:\"%s\"", osFilename.c_str());
3534
0
        else
3535
0
            osTMP.Printf("L1BGCPS:\"%s\"", osFilename.c_str());
3536
668
        poOutDS->SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
3537
668
        poOutDS->SetMetadataItem("X_BAND", "1", "GEOLOCATION");
3538
668
        poOutDS->SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
3539
668
        poOutDS->SetMetadataItem("Y_BAND", "2", "GEOLOCATION");
3540
3541
668
        if (bInterpol)
3542
668
        {
3543
668
            poOutDS->SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
3544
668
            poOutDS->SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
3545
668
        }
3546
0
        else
3547
0
        {
3548
0
            osTMP.Printf("%d", poDS->iGCPStart);
3549
0
            poOutDS->SetMetadataItem("PIXEL_OFFSET", osTMP, "GEOLOCATION");
3550
0
            osTMP.Printf("%d", poDS->iGCPStep);
3551
0
            poOutDS->SetMetadataItem("PIXEL_STEP", osTMP, "GEOLOCATION");
3552
0
        }
3553
3554
668
        poOutDS->SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
3555
668
        poOutDS->SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
3556
668
    }
3557
3558
668
    if (poOutDS != poDS)
3559
0
        return poOutDS;
3560
3561
668
    if (eL1BFormat == L1B_NOAA9)
3562
538
    {
3563
538
        char **papszSubdatasets = nullptr;
3564
538
        papszSubdatasets = CSLSetNameValue(
3565
538
            papszSubdatasets, "SUBDATASET_1_NAME",
3566
538
            CPLSPrintf("L1B_SOLAR_ZENITH_ANGLES:\"%s\"", osFilename.c_str()));
3567
538
        papszSubdatasets = CSLSetNameValue(
3568
538
            papszSubdatasets, "SUBDATASET_1_DESC", "Solar zenith angles");
3569
538
        poDS->SetMetadata(papszSubdatasets, "SUBDATASETS");
3570
538
        CSLDestroy(papszSubdatasets);
3571
538
    }
3572
130
    else
3573
130
    {
3574
130
        char **papszSubdatasets = nullptr;
3575
130
        papszSubdatasets = CSLSetNameValue(
3576
130
            papszSubdatasets, "SUBDATASET_1_NAME",
3577
130
            CPLSPrintf("L1B_ANGLES:\"%s\"", osFilename.c_str()));
3578
130
        papszSubdatasets =
3579
130
            CSLSetNameValue(papszSubdatasets, "SUBDATASET_1_DESC",
3580
130
                            "Solar zenith angles, satellite zenith angles and "
3581
130
                            "relative azimuth angles");
3582
3583
130
        if (poDS->iCLAVRStart > 0)
3584
130
        {
3585
130
            papszSubdatasets = CSLSetNameValue(
3586
130
                papszSubdatasets, "SUBDATASET_2_NAME",
3587
130
                CPLSPrintf("L1B_CLOUDS:\"%s\"", osFilename.c_str()));
3588
130
            papszSubdatasets =
3589
130
                CSLSetNameValue(papszSubdatasets, "SUBDATASET_2_DESC",
3590
130
                                "Clouds from AVHRR (CLAVR)");
3591
130
        }
3592
3593
130
        poDS->SetMetadata(papszSubdatasets, "SUBDATASETS");
3594
130
        CSLDestroy(papszSubdatasets);
3595
130
    }
3596
3597
    /* -------------------------------------------------------------------- */
3598
    /*      Create band information objects.                                */
3599
    /* -------------------------------------------------------------------- */
3600
668
    int iBand, i;
3601
3602
1.90k
    for (iBand = 1, i = 0; iBand <= poDS->nBands; iBand++)
3603
1.23k
    {
3604
1.23k
        poDS->SetBand(iBand, new L1BRasterBand(poDS, iBand));
3605
3606
        // Channels descriptions
3607
1.23k
        if (poDS->eSpacecraftID >= NOAA6 && poDS->eSpacecraftID <= METOP3)
3608
1.23k
        {
3609
1.23k
            if (!(i & 0x01) && poDS->iChannelsMask & 0x01)
3610
311
            {
3611
311
                poDS->GetRasterBand(iBand)->SetDescription(apszBandDesc[0]);
3612
311
                i |= 0x01;
3613
311
                continue;
3614
311
            }
3615
928
            if (!(i & 0x02) && poDS->iChannelsMask & 0x02)
3616
142
            {
3617
142
                poDS->GetRasterBand(iBand)->SetDescription(apszBandDesc[1]);
3618
142
                i |= 0x02;
3619
142
                continue;
3620
142
            }
3621
786
            if (!(i & 0x04) && poDS->iChannelsMask & 0x04)
3622
146
            {
3623
146
                if (poDS->eSpacecraftID >= NOAA15 &&
3624
130
                    poDS->eSpacecraftID <= METOP3)
3625
130
                    if (poDS->iInstrumentStatus & 0x0400)
3626
20
                        poDS->GetRasterBand(iBand)->SetDescription(
3627
20
                            apszBandDesc[7]);
3628
110
                    else
3629
110
                        poDS->GetRasterBand(iBand)->SetDescription(
3630
110
                            apszBandDesc[6]);
3631
16
                else
3632
16
                    poDS->GetRasterBand(iBand)->SetDescription(apszBandDesc[2]);
3633
146
                i |= 0x04;
3634
146
                continue;
3635
146
            }
3636
640
            if (!(i & 0x08) && poDS->iChannelsMask & 0x08)
3637
198
            {
3638
198
                poDS->GetRasterBand(iBand)->SetDescription(apszBandDesc[3]);
3639
198
                i |= 0x08;
3640
198
                continue;
3641
198
            }
3642
442
            if (!(i & 0x10) && poDS->iChannelsMask & 0x10)
3643
142
            {
3644
142
                if (poDS->eSpacecraftID == NOAA13)  // 5 NOAA-13
3645
0
                    poDS->GetRasterBand(iBand)->SetDescription(apszBandDesc[5]);
3646
142
                else if (poDS->eSpacecraftID == NOAA6 ||
3647
142
                         poDS->eSpacecraftID == NOAA8 ||
3648
142
                         poDS->eSpacecraftID == NOAA10)  // 4 NOAA-6,-8,-10
3649
0
                    poDS->GetRasterBand(iBand)->SetDescription(apszBandDesc[3]);
3650
142
                else
3651
142
                    poDS->GetRasterBand(iBand)->SetDescription(apszBandDesc[4]);
3652
142
                i |= 0x10;
3653
142
                continue;
3654
142
            }
3655
442
        }
3656
1.23k
    }
3657
3658
668
    if (poDS->bExposeMaskBand)
3659
130
        poDS->poMaskBand = new L1BMaskBand(poDS);
3660
3661
    /* -------------------------------------------------------------------- */
3662
    /*      Initialize any PAM information.                                 */
3663
    /* -------------------------------------------------------------------- */
3664
668
    poDS->SetDescription(poOpenInfo->pszFilename);
3665
668
    poDS->TryLoadXML();
3666
3667
    /* -------------------------------------------------------------------- */
3668
    /*      Check for external overviews.                                   */
3669
    /* -------------------------------------------------------------------- */
3670
668
    poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename,
3671
668
                                poOpenInfo->GetSiblingFiles());
3672
3673
    /* -------------------------------------------------------------------- */
3674
    /*      Fetch metadata in CSV file                                      */
3675
    /* -------------------------------------------------------------------- */
3676
668
    if (CPLTestBool(CPLGetConfigOption("L1B_FETCH_METADATA", "NO")))
3677
0
    {
3678
0
        poDS->FetchMetadata();
3679
0
    }
3680
3681
668
    return poDS;
3682
3683
1.42k
bad:
3684
1.42k
    delete poDS;
3685
1.42k
    return nullptr;
3686
668
}
3687
3688
/************************************************************************/
3689
/*                          GDALRegister_L1B()                          */
3690
/************************************************************************/
3691
3692
void GDALRegister_L1B()
3693
3694
22
{
3695
22
    if (GDALGetDriverByName("L1B") != nullptr)
3696
0
        return;
3697
3698
22
    GDALDriver *poDriver = new GDALDriver();
3699
3700
22
    poDriver->SetDescription("L1B");
3701
22
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
3702
22
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
3703
22
                              "NOAA Polar Orbiter Level 1b Data Set");
3704
22
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/l1b.html");
3705
22
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
3706
22
    poDriver->SetMetadataItem(GDAL_DMD_SUBDATASETS, "YES");
3707
3708
22
    poDriver->pfnOpen = L1BDataset::Open;
3709
22
    poDriver->pfnIdentify = L1BDataset::Identify;
3710
3711
22
    GetGDALDriverManager()->RegisterDriver(poDriver);
3712
22
}