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