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