/src/gdal/frmts/msgn/msgndataset.cpp
Line | Count | Source |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: MSG Native Reader |
4 | | * Purpose: All code for EUMETSAT Archive format reader |
5 | | * Author: Frans van den Bergh, fvdbergh@csir.co.za |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 2005, Frans van den Bergh <fvdbergh@csir.co.za> |
9 | | * Copyright (c) 2008-2009, Even Rouault <even dot rouault at spatialys.com> |
10 | | * |
11 | | * SPDX-License-Identifier: MIT |
12 | | ****************************************************************************/ |
13 | | #include "cpl_port.h" |
14 | | #include "cpl_error.h" |
15 | | |
16 | | #include "gdal_frmts.h" |
17 | | #include "gdal_priv.h" |
18 | | #include "ogr_spatialref.h" |
19 | | |
20 | | #include "msg_reader_core.h" |
21 | | |
22 | | #include <algorithm> |
23 | | |
24 | | using namespace msg_native_format; |
25 | | |
26 | | typedef enum |
27 | | { |
28 | | MODE_VISIR, // Visible and Infrared bands (1 through 11) in 10-bit raw mode |
29 | | MODE_HRV, // Pan band (band 11) only, in 10-bit raw mode |
30 | | MODE_RAD // Black-body temperature (K) for thermal bands only (4-10), |
31 | | // 64-bit float |
32 | | } open_mode_type; |
33 | | |
34 | | typedef enum |
35 | | { |
36 | | WHOLE_DISK, |
37 | | RSS, // letterbox of N 1/3 of earth |
38 | | SPLIT_HRV // the half-width HRV, may be sheared into two block to follow |
39 | | // the sun W later in the day |
40 | | } image_shape_type; |
41 | | |
42 | | class MSGNRasterBand; |
43 | | |
44 | | /************************************************************************/ |
45 | | /* ==================================================================== */ |
46 | | /* MSGNDataset */ |
47 | | /* ==================================================================== */ |
48 | | /************************************************************************/ |
49 | | |
50 | | class MSGNDataset final : public GDALDataset |
51 | | { |
52 | | friend class MSGNRasterBand; |
53 | | |
54 | | VSILFILE *fp; |
55 | | |
56 | | Msg_reader_core *msg_reader_core; |
57 | | open_mode_type m_open_mode = MODE_VISIR; |
58 | | image_shape_type m_Shape = WHOLE_DISK; |
59 | | int m_nHRVSplitLine = 0; |
60 | | int m_nHRVLowerShiftX = 0; |
61 | | int m_nHRVUpperShiftX = 0; |
62 | | GDALGeoTransform m_gt{}; |
63 | | OGRSpatialReference m_oSRS{}; |
64 | | |
65 | | public: |
66 | | MSGNDataset(); |
67 | | ~MSGNDataset() override; |
68 | | |
69 | | static GDALDataset *Open(GDALOpenInfo *); |
70 | | |
71 | | CPLErr GetGeoTransform(GDALGeoTransform >) const override; |
72 | | const OGRSpatialReference *GetSpatialRef() const override; |
73 | | }; |
74 | | |
75 | | /************************************************************************/ |
76 | | /* ==================================================================== */ |
77 | | /* MSGNRasterBand */ |
78 | | /* ==================================================================== */ |
79 | | /************************************************************************/ |
80 | | |
81 | | class MSGNRasterBand final : public GDALRasterBand |
82 | | { |
83 | | friend class MSGNDataset; |
84 | | |
85 | | unsigned int packet_size; |
86 | | unsigned int bytes_per_line; |
87 | | unsigned int interline_spacing; |
88 | | unsigned int orig_band_no; // The name of the band |
89 | | unsigned int band_in_file; // The effective index of the band in the file |
90 | | open_mode_type open_mode; |
91 | | |
92 | | double GetNoDataValue(int *pbSuccess = nullptr) override |
93 | 0 | { |
94 | 0 | if (pbSuccess) |
95 | 0 | { |
96 | 0 | *pbSuccess = 1; |
97 | 0 | } |
98 | 0 | return MSGN_NODATA_VALUE; |
99 | 0 | } |
100 | | |
101 | | double MSGN_NODATA_VALUE; |
102 | | |
103 | | char band_description[30]; |
104 | | |
105 | | public: |
106 | | MSGNRasterBand(MSGNDataset *, int, open_mode_type mode, int orig_band_no, |
107 | | int band_in_file); |
108 | | |
109 | | CPLErr IReadBlock(int, int, void *) override; |
110 | | double GetMinimum(int *pbSuccess = nullptr) override; |
111 | | double GetMaximum(int *pbSuccess = nullptr) override; |
112 | | |
113 | | const char *GetDescription() const override |
114 | 0 | { |
115 | 0 | return band_description; |
116 | 0 | } |
117 | | }; |
118 | | |
119 | | /************************************************************************/ |
120 | | /* MSGNRasterBand() */ |
121 | | /************************************************************************/ |
122 | | |
123 | | MSGNRasterBand::MSGNRasterBand(MSGNDataset *poDSIn, int nBandIn, |
124 | | open_mode_type mode, int orig_band_noIn, |
125 | | int band_in_fileIn) |
126 | 0 | : packet_size(0), bytes_per_line(0), |
127 | 0 | interline_spacing(poDSIn->msg_reader_core->get_interline_spacing()), |
128 | 0 | orig_band_no(orig_band_noIn), band_in_file(band_in_fileIn), |
129 | 0 | open_mode(mode) |
130 | 0 | { |
131 | 0 | poDS = poDSIn; |
132 | 0 | nBand = nBandIn; // GDAL's band number, i.e. always starts at 1. |
133 | |
|
134 | 0 | snprintf(band_description, sizeof(band_description), "band %02u", |
135 | 0 | orig_band_no); |
136 | |
|
137 | 0 | if (mode != MODE_RAD) |
138 | 0 | { |
139 | 0 | eDataType = GDT_UInt16; |
140 | 0 | MSGN_NODATA_VALUE = 0; |
141 | 0 | } |
142 | 0 | else |
143 | 0 | { |
144 | 0 | eDataType = GDT_Float64; |
145 | 0 | MSGN_NODATA_VALUE = -1000; |
146 | 0 | } |
147 | |
|
148 | 0 | nBlockXSize = poDS->GetRasterXSize(); |
149 | 0 | nBlockYSize = 1; |
150 | |
|
151 | 0 | if (mode != MODE_HRV) |
152 | 0 | { |
153 | 0 | packet_size = poDSIn->msg_reader_core->get_visir_packet_size(); |
154 | 0 | bytes_per_line = poDSIn->msg_reader_core->get_visir_bytes_per_line(); |
155 | 0 | } |
156 | 0 | else |
157 | 0 | { |
158 | 0 | packet_size = poDSIn->msg_reader_core->get_hrv_packet_size(); |
159 | 0 | bytes_per_line = poDSIn->msg_reader_core->get_hrv_bytes_per_line(); |
160 | 0 | } |
161 | 0 | } |
162 | | |
163 | | /************************************************************************/ |
164 | | /* IReadBlock() */ |
165 | | /************************************************************************/ |
166 | | |
167 | | CPLErr MSGNRasterBand::IReadBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff, |
168 | | void *pImage) |
169 | | |
170 | 0 | { |
171 | 0 | MSGNDataset *poGDS = cpl::down_cast<MSGNDataset *>(poDS); |
172 | | |
173 | | // invert y position |
174 | 0 | const int i_nBlockYOff = poDS->GetRasterYSize() - 1 - nBlockYOff; |
175 | |
|
176 | 0 | const int nSamples = static_cast<int>((bytes_per_line * 8) / 10); |
177 | 0 | if (poGDS->m_Shape == WHOLE_DISK && nRasterXSize != nSamples) |
178 | 0 | { |
179 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "nRasterXSize %d != nSamples %d", |
180 | 0 | nRasterXSize, nSamples); |
181 | 0 | return CE_Failure; |
182 | 0 | } |
183 | | |
184 | 0 | unsigned int data_length = |
185 | 0 | bytes_per_line + (unsigned int)sizeof(SUB_VISIRLINE); |
186 | 0 | vsi_l_offset data_offset = 0; |
187 | |
|
188 | 0 | if (open_mode != MODE_HRV) |
189 | 0 | { |
190 | 0 | data_offset = |
191 | 0 | poGDS->msg_reader_core->get_f_data_offset() + |
192 | 0 | static_cast<vsi_l_offset>(interline_spacing) * i_nBlockYOff + |
193 | 0 | static_cast<vsi_l_offset>(band_in_file - 1) * packet_size + |
194 | 0 | (packet_size - data_length); |
195 | 0 | } |
196 | 0 | else |
197 | 0 | { |
198 | 0 | data_offset = |
199 | 0 | poGDS->msg_reader_core->get_f_data_offset() + |
200 | 0 | static_cast<vsi_l_offset>(interline_spacing) * |
201 | 0 | (int(i_nBlockYOff / 3) + 1) - |
202 | 0 | static_cast<vsi_l_offset>(packet_size) * (3 - (i_nBlockYOff % 3)) + |
203 | 0 | (packet_size - data_length); |
204 | 0 | } |
205 | |
|
206 | 0 | if (VSIFSeekL(poGDS->fp, data_offset, SEEK_SET) != 0) |
207 | 0 | return CE_Failure; |
208 | | |
209 | 0 | char *pszRecord = (char *)CPLMalloc(data_length); |
210 | 0 | size_t nread = VSIFReadL(pszRecord, 1, data_length, poGDS->fp); |
211 | |
|
212 | 0 | SUB_VISIRLINE *p = reinterpret_cast<SUB_VISIRLINE *>(pszRecord); |
213 | 0 | to_native(*p); |
214 | |
|
215 | 0 | if (p->lineValidity != 1 || poGDS->m_Shape != WHOLE_DISK) |
216 | 0 | { // Split lines are not full width, so NODATA all first |
217 | 0 | for (int c = 0; c < nBlockXSize; c++) |
218 | 0 | { |
219 | 0 | if (open_mode != MODE_RAD) |
220 | 0 | { |
221 | 0 | ((GUInt16 *)pImage)[c] = (GUInt16)MSGN_NODATA_VALUE; |
222 | 0 | } |
223 | 0 | else |
224 | 0 | { |
225 | 0 | ((double *)pImage)[c] = MSGN_NODATA_VALUE; |
226 | 0 | } |
227 | 0 | } |
228 | 0 | } |
229 | |
|
230 | 0 | if (nread != data_length || |
231 | 0 | (p->lineNumberInVisirGrid - |
232 | 0 | ((open_mode == MODE_HRV && poGDS->m_Shape == RSS) |
233 | 0 | ? (3 * poGDS->msg_reader_core->get_line_start()) - 2 |
234 | 0 | : poGDS->msg_reader_core->get_line_start())) != |
235 | 0 | (unsigned int)i_nBlockYOff) |
236 | 0 | { |
237 | 0 | CPLDebug("MSGN", "Shape %s", |
238 | 0 | poGDS->m_Shape == RSS |
239 | 0 | ? "RSS" |
240 | 0 | : (poGDS->m_Shape == WHOLE_DISK ? "whole" : "split HRV")); |
241 | |
|
242 | 0 | CPLDebug( |
243 | 0 | "MSGN", "nread = %lu, data_len %d, linenum %d, start %d, offset %d", |
244 | 0 | (long unsigned int)nread, // Mingw_w64 otherwise wants %llu - MSG |
245 | | // read will never exceed 32 bits |
246 | 0 | data_length, (p->lineNumberInVisirGrid), |
247 | 0 | poGDS->msg_reader_core->get_line_start(), i_nBlockYOff); |
248 | |
|
249 | 0 | CPLFree(pszRecord); |
250 | |
|
251 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "MSGN Scanline corrupt."); |
252 | |
|
253 | 0 | return CE_Failure; |
254 | 0 | } |
255 | | |
256 | | // unpack the 10-bit values into 16-bit unsigned short ints |
257 | 0 | unsigned char *cptr = |
258 | 0 | (unsigned char *)pszRecord + (data_length - bytes_per_line); |
259 | 0 | int bitsLeft = 8; |
260 | |
|
261 | 0 | if (open_mode != MODE_RAD) |
262 | 0 | { |
263 | 0 | int shift = 0; |
264 | 0 | if (poGDS->m_Shape == SPLIT_HRV) |
265 | 0 | shift = i_nBlockYOff < poGDS->m_nHRVSplitLine |
266 | 0 | ? poGDS->m_nHRVLowerShiftX |
267 | 0 | : poGDS->m_nHRVUpperShiftX; |
268 | 0 | for (int c = 0; c < nSamples; c++) |
269 | 0 | { |
270 | 0 | unsigned short value = 0; |
271 | 0 | for (int bit = 0; bit < 10; bit++) |
272 | 0 | { |
273 | 0 | value <<= 1; |
274 | 0 | if (*cptr & 128) |
275 | 0 | { |
276 | 0 | value |= 1; |
277 | 0 | } |
278 | 0 | *cptr <<= 1; |
279 | 0 | bitsLeft--; |
280 | 0 | if (bitsLeft == 0) |
281 | 0 | { |
282 | 0 | cptr++; |
283 | 0 | bitsLeft = 8; |
284 | 0 | } |
285 | 0 | } |
286 | 0 | ((GUInt16 *)pImage)[nBlockXSize - 1 - c - shift] = value; |
287 | 0 | } |
288 | 0 | } |
289 | 0 | else |
290 | 0 | { |
291 | | // radiance mode |
292 | 0 | for (int c = 0; c < nSamples; c++) |
293 | 0 | { |
294 | 0 | unsigned short value = 0; |
295 | 0 | for (int bit = 0; bit < 10; bit++) |
296 | 0 | { |
297 | 0 | value <<= 1; |
298 | 0 | if (*cptr & 128) |
299 | 0 | { |
300 | 0 | value |= 1; |
301 | 0 | } |
302 | 0 | *cptr <<= 1; |
303 | 0 | bitsLeft--; |
304 | 0 | if (bitsLeft == 0) |
305 | 0 | { |
306 | 0 | cptr++; |
307 | 0 | bitsLeft = 8; |
308 | 0 | } |
309 | 0 | } |
310 | 0 | double dvalue = double(value); |
311 | 0 | double bbvalue = |
312 | 0 | dvalue * poGDS->msg_reader_core |
313 | 0 | ->get_calibration_parameters()[orig_band_no - 1] |
314 | 0 | .cal_slope + |
315 | 0 | poGDS->msg_reader_core |
316 | 0 | ->get_calibration_parameters()[orig_band_no - 1] |
317 | 0 | .cal_offset; |
318 | |
|
319 | 0 | ((double *)pImage)[nBlockXSize - 1 - c] = bbvalue; |
320 | 0 | } |
321 | 0 | } |
322 | 0 | CPLFree(pszRecord); |
323 | 0 | return CE_None; |
324 | 0 | } |
325 | | |
326 | | /************************************************************************/ |
327 | | /* GetMinimum() */ |
328 | | /************************************************************************/ |
329 | | double MSGNRasterBand::GetMinimum(int *pbSuccess) |
330 | 0 | { |
331 | 0 | if (pbSuccess) |
332 | 0 | { |
333 | 0 | *pbSuccess = 1; |
334 | 0 | } |
335 | 0 | return open_mode != MODE_RAD ? 1 : GDALRasterBand::GetMinimum(pbSuccess); |
336 | 0 | } |
337 | | |
338 | | /************************************************************************/ |
339 | | /* GetMaximum() */ |
340 | | /************************************************************************/ |
341 | | double MSGNRasterBand::GetMaximum(int *pbSuccess) |
342 | 0 | { |
343 | 0 | if (pbSuccess) |
344 | 0 | { |
345 | 0 | *pbSuccess = 1; |
346 | 0 | } |
347 | 0 | return open_mode != MODE_RAD ? 1023 : GDALRasterBand::GetMaximum(pbSuccess); |
348 | 0 | } |
349 | | |
350 | | /************************************************************************/ |
351 | | /* ==================================================================== */ |
352 | | /* MSGNDataset */ |
353 | | /* ==================================================================== */ |
354 | | /************************************************************************/ |
355 | | |
356 | 3 | MSGNDataset::MSGNDataset() : fp(nullptr), msg_reader_core(nullptr) |
357 | 3 | { |
358 | 3 | m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
359 | 3 | } |
360 | | |
361 | | /************************************************************************/ |
362 | | /* ~MSGNDataset() */ |
363 | | /************************************************************************/ |
364 | | |
365 | | MSGNDataset::~MSGNDataset() |
366 | | |
367 | 3 | { |
368 | 3 | if (fp != nullptr) |
369 | 3 | VSIFCloseL(fp); |
370 | | |
371 | 3 | if (msg_reader_core) |
372 | 3 | { |
373 | 3 | delete msg_reader_core; |
374 | 3 | } |
375 | 3 | } |
376 | | |
377 | | /************************************************************************/ |
378 | | /* GetGeoTransform() */ |
379 | | /************************************************************************/ |
380 | | |
381 | | CPLErr MSGNDataset::GetGeoTransform(GDALGeoTransform >) const |
382 | | |
383 | 0 | { |
384 | 0 | gt = m_gt; |
385 | |
|
386 | 0 | return CE_None; |
387 | 0 | } |
388 | | |
389 | | /************************************************************************/ |
390 | | /* GetSpatialRef() */ |
391 | | /************************************************************************/ |
392 | | |
393 | | const OGRSpatialReference *MSGNDataset::GetSpatialRef() const |
394 | | |
395 | 0 | { |
396 | 0 | return m_oSRS.IsEmpty() ? nullptr : &m_oSRS; |
397 | 0 | } |
398 | | |
399 | | /************************************************************************/ |
400 | | /* Open() */ |
401 | | /************************************************************************/ |
402 | | |
403 | | GDALDataset *MSGNDataset::Open(GDALOpenInfo *poOpenInfo) |
404 | | |
405 | 502k | { |
406 | 502k | open_mode_type open_mode = MODE_VISIR; |
407 | 502k | GDALOpenInfo *open_info = poOpenInfo; |
408 | 502k | std::unique_ptr<GDALOpenInfo> poOpenInfoToFree; |
409 | | |
410 | 502k | if (!poOpenInfo->bStatOK) |
411 | 393k | { |
412 | 393k | if (STARTS_WITH_CI(poOpenInfo->pszFilename, "HRV:")) |
413 | 0 | { |
414 | 0 | poOpenInfoToFree = std::make_unique<GDALOpenInfo>( |
415 | 0 | &poOpenInfo->pszFilename[4], poOpenInfo->eAccess); |
416 | 0 | open_info = poOpenInfoToFree.get(); |
417 | 0 | open_mode = MODE_HRV; |
418 | 0 | } |
419 | 393k | else if (STARTS_WITH_CI(poOpenInfo->pszFilename, "RAD:")) |
420 | 0 | { |
421 | 0 | poOpenInfoToFree = std::make_unique<GDALOpenInfo>( |
422 | 0 | &poOpenInfo->pszFilename[4], poOpenInfo->eAccess); |
423 | 0 | open_info = poOpenInfoToFree.get(); |
424 | 0 | open_mode = MODE_RAD; |
425 | 0 | } |
426 | 393k | } |
427 | | |
428 | | /* -------------------------------------------------------------------- */ |
429 | | /* Before trying MSGNOpen() we first verify that there is at */ |
430 | | /* least one "\n#keyword" type signature in the first chunk of */ |
431 | | /* the file. */ |
432 | | /* -------------------------------------------------------------------- */ |
433 | 502k | if (open_info->fpL == nullptr || open_info->nHeaderBytes < 50) |
434 | 402k | { |
435 | 402k | return nullptr; |
436 | 402k | } |
437 | | |
438 | | /* check if this is a "NATIVE" MSG format image */ |
439 | 100k | if (!STARTS_WITH_CI((char *)open_info->pabyHeader, |
440 | 100k | "FormatName : NATIVE")) |
441 | 100k | { |
442 | 100k | return nullptr; |
443 | 100k | } |
444 | | |
445 | | /* -------------------------------------------------------------------- */ |
446 | | /* Confirm the requested access is supported. */ |
447 | | /* -------------------------------------------------------------------- */ |
448 | 3 | if (poOpenInfo->eAccess == GA_Update) |
449 | 0 | { |
450 | 0 | ReportUpdateNotSupportedByDriver("MSGN"); |
451 | 0 | return nullptr; |
452 | 0 | } |
453 | | |
454 | | /* -------------------------------------------------------------------- */ |
455 | | /* Create a corresponding GDALDataset. */ |
456 | | /* -------------------------------------------------------------------- */ |
457 | 3 | VSILFILE *fp = VSIFOpenL(open_info->pszFilename, "rb"); |
458 | 3 | if (fp == nullptr) |
459 | 0 | { |
460 | 0 | return nullptr; |
461 | 0 | } |
462 | | |
463 | 3 | auto poDS = std::make_unique<MSGNDataset>(); |
464 | | |
465 | 3 | poDS->m_open_mode = open_mode; |
466 | 3 | poDS->fp = fp; |
467 | | |
468 | | /* -------------------------------------------------------------------- */ |
469 | | /* Read the header. */ |
470 | | /* -------------------------------------------------------------------- */ |
471 | | // first reset the file pointer, then hand over to the msg_reader_core |
472 | 3 | CPL_IGNORE_RET_VAL(VSIFSeekL(poDS->fp, 0, SEEK_SET)); |
473 | | |
474 | 3 | poDS->msg_reader_core = new Msg_reader_core(poDS->fp); |
475 | | |
476 | 3 | if (!poDS->msg_reader_core->get_open_success()) |
477 | 3 | { |
478 | 3 | return nullptr; |
479 | 3 | } |
480 | | |
481 | 0 | poDS->nRasterXSize = poDS->msg_reader_core->get_columns(); |
482 | 0 | poDS->nRasterYSize = poDS->msg_reader_core->get_lines(); |
483 | |
|
484 | 0 | if (open_mode == MODE_HRV) |
485 | 0 | { |
486 | 0 | const int nRawHRVColumns = |
487 | 0 | (poDS->msg_reader_core->get_hrv_bytes_per_line() * 8) / 10; |
488 | 0 | poDS->nRasterYSize *= 3; |
489 | 0 | const auto &idr = poDS->msg_reader_core->get_image_description_record(); |
490 | | // Check if the split layout of the HRV channel meets our expectations |
491 | | // to re-assemble it in a consistent way |
492 | 0 | CPLDebug("MSGN", "HRV raw col %d raster X %d raster Y %d", |
493 | 0 | nRawHRVColumns, poDS->nRasterXSize, poDS->nRasterYSize); |
494 | |
|
495 | 0 | if (idr.plannedCoverage_hrv.lowerSouthLinePlanned == 1 && |
496 | 0 | idr.plannedCoverage_hrv.lowerNorthLinePlanned > 1 && |
497 | 0 | idr.plannedCoverage_hrv.lowerNorthLinePlanned < |
498 | 0 | poDS->nRasterYSize && |
499 | 0 | idr.plannedCoverage_hrv.upperSouthLinePlanned == |
500 | 0 | idr.plannedCoverage_hrv.lowerNorthLinePlanned + 1 && |
501 | 0 | idr.plannedCoverage_hrv.upperNorthLinePlanned == |
502 | 0 | poDS->nRasterYSize && |
503 | 0 | idr.plannedCoverage_hrv.lowerEastColumnPlanned >= 1 && |
504 | 0 | idr.plannedCoverage_hrv.lowerWestColumnPlanned == |
505 | 0 | idr.plannedCoverage_hrv.lowerEastColumnPlanned + |
506 | 0 | nRawHRVColumns - 1 && |
507 | 0 | idr.plannedCoverage_hrv.lowerWestColumnPlanned <= |
508 | 0 | poDS->nRasterXSize * 3 && |
509 | 0 | idr.plannedCoverage_hrv.upperEastColumnPlanned >= 1 && |
510 | 0 | idr.plannedCoverage_hrv.upperWestColumnPlanned == |
511 | 0 | idr.plannedCoverage_hrv.upperEastColumnPlanned + |
512 | 0 | nRawHRVColumns - 1 && |
513 | 0 | idr.plannedCoverage_hrv.upperWestColumnPlanned <= |
514 | 0 | poDS->nRasterXSize * 3) |
515 | 0 | { |
516 | 0 | poDS->nRasterXSize *= 3; |
517 | 0 | poDS->m_Shape = SPLIT_HRV; |
518 | 0 | poDS->m_nHRVSplitLine = |
519 | 0 | idr.plannedCoverage_hrv.upperSouthLinePlanned; |
520 | 0 | poDS->m_nHRVLowerShiftX = |
521 | 0 | idr.plannedCoverage_hrv.lowerEastColumnPlanned - 1; |
522 | 0 | poDS->m_nHRVUpperShiftX = |
523 | 0 | idr.plannedCoverage_hrv.upperEastColumnPlanned - 1; |
524 | 0 | } |
525 | 0 | else if (idr.plannedCoverage_hrv.upperNorthLinePlanned == 0 && |
526 | 0 | idr.plannedCoverage_hrv.upperSouthLinePlanned == 0 && |
527 | 0 | idr.plannedCoverage_hrv.upperWestColumnPlanned == 0 && |
528 | 0 | idr.plannedCoverage_hrv.upperEastColumnPlanned == |
529 | 0 | 0 && // RSS only uses the lower section |
530 | 0 | idr.plannedCoverage_hrv.lowerNorthLinePlanned == |
531 | 0 | idr.referencegrid_hrv.numberOfLines && // start at max N |
532 | | // full expected width |
533 | 0 | idr.plannedCoverage_hrv.lowerWestColumnPlanned == |
534 | 0 | idr.plannedCoverage_hrv.lowerEastColumnPlanned + |
535 | 0 | nRawHRVColumns - 1 && |
536 | 0 | idr.plannedCoverage_hrv.lowerSouthLinePlanned > 1 && |
537 | 0 | idr.plannedCoverage_hrv.lowerSouthLinePlanned < |
538 | 0 | idr.referencegrid_hrv.numberOfLines && |
539 | 0 | idr.plannedCoverage_hrv.lowerEastColumnPlanned >= 1 && |
540 | 0 | idr.plannedCoverage_hrv.lowerWestColumnPlanned <= |
541 | 0 | poDS->nRasterXSize * 3 && |
542 | | // full height |
543 | 0 | idr.plannedCoverage_hrv.lowerNorthLinePlanned == |
544 | 0 | idr.plannedCoverage_hrv.lowerSouthLinePlanned + |
545 | 0 | poDS->nRasterYSize - 1) |
546 | 0 | { |
547 | 0 | poDS->nRasterXSize *= 3; |
548 | 0 | poDS->m_Shape = RSS; |
549 | 0 | } |
550 | 0 | else |
551 | 0 | { |
552 | 0 | CPLError( |
553 | 0 | CE_Failure, CPLE_AppDefined, |
554 | 0 | "HRV neither Whole Disk nor RSS - don't know how to handle"); |
555 | 0 | return nullptr; |
556 | 0 | } |
557 | 0 | } |
558 | 0 | else |
559 | 0 | { |
560 | 0 | const int nRawVisIRColumns = |
561 | 0 | (poDS->msg_reader_core->get_visir_bytes_per_line() * 8) / 10; |
562 | |
|
563 | 0 | const auto &idr = poDS->msg_reader_core->get_image_description_record(); |
564 | | // Check if the VisIR channel is RSS or not, and if it meets our |
565 | | // expectations to re-assemble it in a consistent way |
566 | 0 | CPLDebug("MSGN", "raw col %d raster X %d raster Y %d", nRawVisIRColumns, |
567 | 0 | poDS->nRasterXSize, poDS->nRasterYSize); |
568 | |
|
569 | 0 | if (idr.plannedCoverage_visir.southernLinePlanned == 1 && |
570 | 0 | idr.plannedCoverage_visir.northernLinePlanned == |
571 | 0 | poDS->nRasterYSize && |
572 | 0 | idr.plannedCoverage_visir.easternColumnPlanned >= 1 && |
573 | 0 | idr.plannedCoverage_visir.westernColumnPlanned == |
574 | 0 | idr.plannedCoverage_visir.easternColumnPlanned + |
575 | 0 | nRawVisIRColumns - 1 && |
576 | 0 | idr.plannedCoverage_visir.westernColumnPlanned <= |
577 | 0 | poDS->nRasterXSize) |
578 | 0 | { |
579 | 0 | poDS->m_Shape = WHOLE_DISK; |
580 | 0 | } |
581 | 0 | else if (idr.plannedCoverage_visir.northernLinePlanned == |
582 | 0 | idr.referencegrid_visir.numberOfLines && // start at max N |
583 | | // full expected width |
584 | 0 | idr.plannedCoverage_visir.westernColumnPlanned == |
585 | 0 | idr.plannedCoverage_visir.easternColumnPlanned + |
586 | 0 | nRawVisIRColumns - 1 && |
587 | 0 | idr.plannedCoverage_visir.southernLinePlanned > 1 && |
588 | 0 | idr.plannedCoverage_visir.easternColumnPlanned >= 1 && |
589 | 0 | idr.plannedCoverage_visir.westernColumnPlanned <= |
590 | 0 | poDS->nRasterXSize && |
591 | | // full height |
592 | 0 | idr.plannedCoverage_visir.northernLinePlanned == |
593 | 0 | idr.plannedCoverage_visir.southernLinePlanned + |
594 | 0 | poDS->nRasterYSize - 1) |
595 | 0 | { |
596 | 0 | poDS->m_Shape = RSS; |
597 | 0 | } |
598 | 0 | else |
599 | 0 | { |
600 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
601 | 0 | "Neither Whole Disk nor RSS - don't know how to handle"); |
602 | 0 | return nullptr; |
603 | 0 | } |
604 | 0 | } |
605 | | |
606 | 0 | CPLDebug("MSGN", "Shape %s", |
607 | 0 | poDS->m_Shape == RSS |
608 | 0 | ? "RSS" |
609 | 0 | : (poDS->m_Shape == WHOLE_DISK ? "whole" : "split HRV")); |
610 | | |
611 | | /* -------------------------------------------------------------------- */ |
612 | | /* Create band information objects. */ |
613 | | /* -------------------------------------------------------------------- */ |
614 | 0 | unsigned int i; |
615 | 0 | unsigned int band_count = 1; |
616 | 0 | unsigned int missing_band_count = 0; |
617 | 0 | const unsigned char *bands = poDS->msg_reader_core->get_band_map(); |
618 | 0 | unsigned char band_map[MSG_NUM_CHANNELS + 1] = { |
619 | 0 | 0}; // map GDAL band numbers to MSG channels |
620 | 0 | for (i = 0; i < MSG_NUM_CHANNELS; i++) |
621 | 0 | { |
622 | 0 | if (bands[i]) |
623 | 0 | { |
624 | 0 | bool ok_to_add = false; |
625 | 0 | switch (open_mode) |
626 | 0 | { |
627 | 0 | case MODE_VISIR: |
628 | 0 | ok_to_add = i < MSG_NUM_CHANNELS - 1; |
629 | 0 | break; |
630 | 0 | case MODE_RAD: |
631 | 0 | ok_to_add = (i <= 2) || |
632 | 0 | (Msg_reader_core::Blackbody_LUT[i + 1].B != 0); |
633 | 0 | break; |
634 | 0 | case MODE_HRV: |
635 | 0 | ok_to_add = i == MSG_NUM_CHANNELS - 1; |
636 | 0 | break; |
637 | 0 | } |
638 | 0 | if (ok_to_add) |
639 | 0 | { |
640 | 0 | poDS->SetBand(band_count, |
641 | 0 | new MSGNRasterBand(poDS.get(), band_count, |
642 | 0 | open_mode, i + 1, |
643 | 0 | i + 1 - missing_band_count)); |
644 | 0 | band_map[band_count] = (unsigned char)(i + 1); |
645 | 0 | band_count++; |
646 | 0 | } |
647 | 0 | } |
648 | 0 | else |
649 | 0 | { |
650 | 0 | missing_band_count++; |
651 | 0 | } |
652 | 0 | } |
653 | | |
654 | 0 | double pixel_gsd_x; |
655 | 0 | double pixel_gsd_y; |
656 | 0 | double origin_x; |
657 | 0 | double origin_y; |
658 | |
|
659 | 0 | { |
660 | 0 | const auto &idr = poDS->msg_reader_core->get_image_description_record(); |
661 | | /* there are a number of 'magic' constants below |
662 | | I trimmed them to get registration for MSG4, MSG3, MSG2 with country |
663 | | outlines from |
664 | | http://ec.europa.eu/eurostat/web/gisco/geodata/reference-data/administrative-units-statistical-units |
665 | | |
666 | | Adjust in two phases P1, P2. |
667 | | I describe direction as outline being NSEW of coast shape when number |
668 | | is changed |
669 | | */ |
670 | |
|
671 | 0 | if (open_mode != MODE_HRV) |
672 | 0 | { |
673 | 0 | pixel_gsd_x = |
674 | 0 | 1000.0 * poDS->msg_reader_core |
675 | 0 | ->get_col_dir_step(); // convert from km to m |
676 | 0 | pixel_gsd_y = |
677 | 0 | 1000.0 * poDS->msg_reader_core |
678 | 0 | ->get_line_dir_step(); // convert from km to m |
679 | 0 | origin_x = -pixel_gsd_x * (-(Conversions::nlines / 2.0) + |
680 | 0 | poDS->msg_reader_core->get_col_start() - |
681 | 0 | 1); // all vis/NIR E-W -ve E |
682 | 0 | origin_y = -pixel_gsd_y * ((Conversions::nlines / 2.0) - |
683 | 0 | poDS->msg_reader_core->get_line_start() + |
684 | 0 | 1.5); // set with 4 N-S +ve S |
685 | 0 | } |
686 | 0 | else |
687 | 0 | { |
688 | 0 | pixel_gsd_x = |
689 | 0 | 1000.0 * poDS->msg_reader_core |
690 | 0 | ->get_hrv_col_dir_step(); // convert from km to m |
691 | 0 | pixel_gsd_y = |
692 | 0 | 1000.0 * poDS->msg_reader_core |
693 | 0 | ->get_hrv_line_dir_step(); // convert from km to m |
694 | 0 | if (poDS->m_Shape == RSS) |
695 | 0 | { |
696 | 0 | origin_x = -pixel_gsd_x * |
697 | 0 | (-(3 * Conversions::nlines / 2.0) - |
698 | 0 | idr.plannedCoverage_hrv.lowerEastColumnPlanned - |
699 | 0 | 1); // MSG3 HRV E-W -ve E |
700 | 0 | origin_y = -pixel_gsd_y * |
701 | 0 | ((3 * Conversions::nlines / 2.0) - |
702 | 0 | idr.plannedCoverage_hrv.lowerSouthLinePlanned + |
703 | 0 | 2); // N-S -ve S |
704 | 0 | } |
705 | 0 | else |
706 | 0 | { |
707 | 0 | origin_x = |
708 | 0 | -pixel_gsd_x * (-(3 * Conversions::nlines / 2.0) + |
709 | 0 | 1 * poDS->msg_reader_core->get_col_start() - |
710 | 0 | 3); // MSG4, MSG2 HRV E-W -ve E |
711 | 0 | origin_y = -pixel_gsd_y * |
712 | 0 | ((3 * Conversions::nlines / 2.0) - |
713 | 0 | 1 * poDS->msg_reader_core->get_line_start() + |
714 | 0 | 4); // N-S +ve S |
715 | 0 | } |
716 | 0 | } |
717 | | |
718 | | /* the conversion to lat/long is in two parts: |
719 | | pixels to m (around imaginary circle r=sta height) in the geo |
720 | | projection (affine transformation) geo to lat/long via the GEOS |
721 | | projection (in WKT) and the ellipsoid |
722 | | |
723 | | CGMS/DOC/12/0017 section 4.4.2 |
724 | | */ |
725 | |
|
726 | 0 | poDS->m_gt.xorig = -origin_x; |
727 | 0 | poDS->m_gt.xscale = pixel_gsd_x; |
728 | 0 | poDS->m_gt.xrot = 0.0; |
729 | |
|
730 | 0 | poDS->m_gt.yorig = -origin_y; |
731 | 0 | poDS->m_gt.yrot = 0.0; |
732 | 0 | poDS->m_gt.yscale = -pixel_gsd_y; |
733 | |
|
734 | 0 | poDS->m_oSRS.SetProjCS("Geostationary projection (MSG)"); |
735 | |
|
736 | 0 | poDS->m_oSRS.SetGeogCS( |
737 | 0 | "MSG Ellipsoid", "MSG_DATUM", "MSG_SPHEROID", |
738 | 0 | Conversions::req * |
739 | 0 | 1000.0, // SetGeogCS doesn't specify length units, so all must |
740 | | // be the same - here m. |
741 | 0 | 1.0 / Conversions::oblate // 1 / ( 1 - |
742 | | // Conversions::rpol/Conversions::req) |
743 | 0 | ); |
744 | |
|
745 | 0 | poDS->m_oSRS.SetGEOS( |
746 | 0 | idr.longitudeOfSSP, |
747 | 0 | (Conversions::altitude - Conversions::req) * |
748 | 0 | 1000.0, // we're using meters as length unit |
749 | 0 | 0.0, |
750 | | // false northing to handle the fact RSS is only 1/3 disk |
751 | 0 | pixel_gsd_y * |
752 | 0 | ((poDS->m_Shape == RSS) |
753 | 0 | ? ((open_mode != MODE_HRV) |
754 | 0 | ? -(idr.plannedCoverage_visir.southernLinePlanned - |
755 | 0 | 1) |
756 | 0 | : // MSG-3 vis/NIR N-S P2 |
757 | 0 | -(idr.plannedCoverage_hrv.lowerSouthLinePlanned + |
758 | 0 | 1)) |
759 | 0 | : // MSG-3 HRV N-S P2 -ve N |
760 | 0 | 0.0)); |
761 | 0 | } |
762 | |
|
763 | 0 | const CALIBRATION *cal = |
764 | 0 | poDS->msg_reader_core->get_calibration_parameters(); |
765 | 0 | char tagname[30]; |
766 | 0 | char field[300]; |
767 | |
|
768 | 0 | poDS->SetMetadataItem("Radiometric parameters format", "offset slope"); |
769 | 0 | for (i = 1; i < band_count; i++) |
770 | 0 | { |
771 | 0 | snprintf(tagname, sizeof(tagname), "ch%02u_cal", band_map[i]); |
772 | 0 | CPLsnprintf(field, sizeof(field), "%.12e %.12e", |
773 | 0 | cal[band_map[i] - 1].cal_offset, |
774 | 0 | cal[band_map[i] - 1].cal_slope); |
775 | 0 | poDS->SetMetadataItem(tagname, field); |
776 | 0 | } |
777 | |
|
778 | 0 | snprintf( |
779 | 0 | field, sizeof(field), "%04u%02u%02u/%02u:%02u", |
780 | 0 | poDS->msg_reader_core->get_year(), poDS->msg_reader_core->get_month(), |
781 | 0 | poDS->msg_reader_core->get_day(), poDS->msg_reader_core->get_hour(), |
782 | 0 | poDS->msg_reader_core->get_minute()); |
783 | 0 | poDS->SetMetadataItem("Date/Time", field); |
784 | |
|
785 | 0 | snprintf(field, sizeof(field), "%u %u", |
786 | 0 | poDS->msg_reader_core->get_line_start(), |
787 | 0 | poDS->msg_reader_core->get_col_start()); |
788 | 0 | poDS->SetMetadataItem("Origin", field); |
789 | |
|
790 | 0 | return poDS.release(); |
791 | 0 | } |
792 | | |
793 | | /************************************************************************/ |
794 | | /* GDALRegister_MSGN() */ |
795 | | /************************************************************************/ |
796 | | |
797 | | void GDALRegister_MSGN() |
798 | | |
799 | 24 | { |
800 | 24 | if (GDALGetDriverByName("MSGN") != nullptr) |
801 | 0 | return; |
802 | | |
803 | 24 | GDALDriver *poDriver = new GDALDriver(); |
804 | | |
805 | 24 | poDriver->SetDescription("MSGN"); |
806 | 24 | poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES"); |
807 | 24 | poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, |
808 | 24 | "EUMETSAT Archive native (.nat)"); |
809 | 24 | poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/msgn.html"); |
810 | 24 | poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "nat"); |
811 | 24 | poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES"); |
812 | | |
813 | 24 | poDriver->pfnOpen = MSGNDataset::Open; |
814 | | |
815 | 24 | GetGDALDriverManager()->RegisterDriver(poDriver); |
816 | 24 | } |