1 /******************************************************************************
2 *
3 * Project: MSG Driver
4 * Purpose: GDALDataset driver for MSG translator for read support.
5 * Author: Bas Retsios, retsios@itc.nl
6 *
7 ******************************************************************************
8 * Copyright (c) 2004, ITC
9 * Copyright (c) 2009, Even Rouault <even dot rouault at spatialys.com>
10 *
11 * Permission is hereby granted, free of charge, to any person obtaining a
12 * copy of this software and associated documentation files (the "Software"),
13 * to deal in the Software without restriction, including without limitation
14 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15 * and/or sell copies of the Software, and to permit persons to whom the
16 * Software is furnished to do so, subject to the following conditions:
17 *
18 * The above copyright notice and this permission notice shall be included
19 * in all copies or substantial portions of the Software.
20 *
21 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27 * DEALINGS IN THE SOFTWARE.
28 ******************************************************************************/
29 #include "cpl_port.h" // Must be first.
30
31 #include "gdal_frmts.h"
32 #include "msgdataset.h"
33 #include "prologue.h"
34 #include "xritheaderparser.h"
35 #include "reflectancecalculator.h"
36
37 #include "PublicDecompWT_headers.h"
38
39 #include <memory>
40 #include <vector>
41
42 #if _MSC_VER > 1000
43 #include <io.h>
44 #else
45 #include <stdio.h>
46 #endif
47
48 CPL_CVSID("$Id: msgdataset.cpp 6ff924dfc704776cbdeff1e0e23da6452cf06933 2021-03-03 17:22:05 +0100 Even Rouault $")
49
50 const double MSGDataset::rCentralWvl[12] = {0.635, 0.810, 1.640, 3.900, 6.250, 7.350, 8.701, 9.660, 10.800, 12.000, 13.400, 0.750};
51 const double MSGDataset::rVc[12] = {-1, -1, -1, 2569.094, 1598.566, 1362.142, 1149.083, 1034.345, 930.659, 839.661, 752.381, -1};
52 const double MSGDataset::rA[12] = {-1, -1, -1, 0.9959, 0.9963, 0.9991, 0.9996, 0.9999, 0.9983, 0.9988, 0.9981, -1};
53 const double MSGDataset::rB[12] = {-1, -1, -1, 3.471, 2.219, 0.485, 0.181, 0.060, 0.627, 0.397, 0.576, -1};
54 const int MSGDataset::iCentralPixelVIS_IR = 1856; // center pixel VIS and IR
55 const int MSGDataset::iCentralPixelHRV = 5566; // center pixel HRV
56 int MSGDataset::iCurrentSatellite = 1; // satellite number 1,2,3,4 for MSG1, MSG2, MSG3 and MSG4
57 const char *MSGDataset::metadataDomain = "msg"; // the metadata domain
58
59 #define MAX_SATELLITES 4
60
61 /************************************************************************/
62 /* MSGDataset() */
63 /************************************************************************/
64
MSGDataset()65 MSGDataset::MSGDataset()
66
67 {
68 poTransform = nullptr;
69 pszProjection = CPLStrdup("");
70 adfGeoTransform[0] = 0.0;
71 adfGeoTransform[1] = 1.0;
72 adfGeoTransform[2] = 0.0;
73 adfGeoTransform[3] = 0.0;
74 adfGeoTransform[4] = 0.0;
75 adfGeoTransform[5] = 1.0;
76 }
77
78 /************************************************************************/
79 /* ~MSGDataset() */
80 /************************************************************************/
81
~MSGDataset()82 MSGDataset::~MSGDataset()
83
84 {
85 if( poTransform != nullptr )
86 delete poTransform;
87
88 CPLFree( pszProjection );
89 }
90
91 /************************************************************************/
92 /* GetProjectionRef() */
93 /************************************************************************/
94
_GetProjectionRef()95 const char *MSGDataset::_GetProjectionRef()
96
97 {
98 return pszProjection;
99 }
100
101 /************************************************************************/
102 /* SetProjection() */
103 /************************************************************************/
104
_SetProjection(const char * pszNewProjection)105 CPLErr MSGDataset::_SetProjection( const char * pszNewProjection )
106 {
107 CPLFree( pszProjection );
108 pszProjection = CPLStrdup( pszNewProjection );
109
110 return CE_None;
111 }
112
113 /************************************************************************/
114 /* GetGeoTransform() */
115 /************************************************************************/
116
GetGeoTransform(double * padfTransform)117 CPLErr MSGDataset::GetGeoTransform( double * padfTransform )
118
119 {
120 memcpy( padfTransform, adfGeoTransform, sizeof(double) * 6 );
121 return CE_None;
122 }
123
124 /************************************************************************/
125 /* Open() */
126 /************************************************************************/
127
Open(GDALOpenInfo * poOpenInfo)128 GDALDataset *MSGDataset::Open( GDALOpenInfo * poOpenInfo )
129
130 {
131 /* -------------------------------------------------------------------- */
132 /* Does this look like a MSG file */
133 /* -------------------------------------------------------------------- */
134 //if( poOpenInfo->fp == nullptr)
135 // return nullptr;
136 // Do not touch the fp .. it will close by itself if not null after we return (whether it is recognized as HRIT or not)
137
138 std::string command_line (poOpenInfo->pszFilename);
139
140 MSGCommand command;
141 std::string sErr = command.parse(command_line);
142 if (sErr.length() > 0)
143 {
144 if (sErr.compare("-") != 0) // this driver does not recognize this format .. be silent and return false so that another driver can try
145 CPLError( CE_Failure, CPLE_AppDefined, "%s", (sErr+"\n").c_str() );
146 return nullptr;
147 }
148
149 /* -------------------------------------------------------------------- */
150 /* Read the prologue. */
151 /* -------------------------------------------------------------------- */
152 Prologue pp;
153
154 std::string sPrologueFileName = command.sPrologueFileName(iCurrentSatellite, 1);
155 bool fPrologueExists = (access(sPrologueFileName.c_str(), 0) == 0);
156
157 // Make sure we're testing for MSG1,2,3 or 4 exactly once, start with the most recently used, and remember it in the static member for the next round.
158 if (!fPrologueExists)
159 {
160 iCurrentSatellite = 1 + iCurrentSatellite % MAX_SATELLITES;
161 sPrologueFileName = command.sPrologueFileName(iCurrentSatellite, 1);
162 fPrologueExists = (access(sPrologueFileName.c_str(), 0) == 0);
163 int iTries = 2;
164 while (!fPrologueExists && (iTries < MAX_SATELLITES))
165 {
166 iCurrentSatellite = 1 + iCurrentSatellite % MAX_SATELLITES;
167 sPrologueFileName = command.sPrologueFileName(iCurrentSatellite, 1);
168 fPrologueExists = (access(sPrologueFileName.c_str(), 0) == 0);
169 ++iTries;
170 }
171 if (!fPrologueExists) // assume missing prologue file, keep original satellite number
172 {
173 iCurrentSatellite = 1 + iCurrentSatellite % MAX_SATELLITES;
174 sPrologueFileName = command.sPrologueFileName(iCurrentSatellite, 1);
175 }
176 }
177
178 if (fPrologueExists)
179 {
180 std::ifstream p_file(sPrologueFileName.c_str(), std::ios::in|std::ios::binary);
181 XRITHeaderParser xhp (p_file);
182 if (xhp.isValid() && xhp.isPrologue())
183 pp.read(p_file);
184 p_file.close();
185 }
186 else
187 {
188 std::string l_sErr = "The prologue of the data set could not be found at the location specified:\n" + sPrologueFileName + "\n";
189 CPLError( CE_Failure, CPLE_AppDefined, "%s",
190 l_sErr.c_str() );
191 return nullptr;
192 }
193
194 // We're confident the string is formatted as an MSG command_line
195
196 /* -------------------------------------------------------------------- */
197 /* Create a corresponding GDALDataset. */
198 /* -------------------------------------------------------------------- */
199
200 MSGDataset *poDS = new MSGDataset();
201 poDS->command = command; // copy it
202
203 /* -------------------------------------------------------------------- */
204 /* Capture raster size from MSG prologue and submit it to GDAL */
205 /* -------------------------------------------------------------------- */
206
207 if (command.channel[11] != 0) // the HRV band
208 {
209 poDS->nRasterXSize = pp.idr()->ReferenceGridHRV->NumberOfColumns;
210 poDS->nRasterYSize = abs(pp.idr()->PlannedCoverageHRV->UpperNorthLinePlanned - pp.idr()->PlannedCoverageHRV->LowerSouthLinePlanned) + 1;
211 }
212 else
213 {
214 poDS->nRasterXSize = abs(pp.idr()->PlannedCoverageVIS_IR->WesternColumnPlanned - pp.idr()->PlannedCoverageVIS_IR->EasternColumnPlanned) + 1;
215 poDS->nRasterYSize = abs(pp.idr()->PlannedCoverageVIS_IR->NorthernLinePlanned - pp.idr()->PlannedCoverageVIS_IR->SouthernLinePlanned) + 1;
216 }
217
218 /* -------------------------------------------------------------------- */
219 /* Set Georeference Information */
220 /* -------------------------------------------------------------------- */
221
222 double rPixelSizeX;
223 double rPixelSizeY;
224 double rMinX;
225 double rMaxY;
226
227 if (command.channel[11] != 0)
228 {
229 rPixelSizeX = 1000 * pp.idr()->ReferenceGridHRV->ColumnDirGridStep;
230 rPixelSizeY = 1000 * pp.idr()->ReferenceGridHRV->LineDirGridStep;
231 // The MSG Level 1.5 Image Data Format Description (page 22f) defines the center of pixel (5566,5566) from SE as sub satellite point for the HRV channel (0°,0° for operational MSG).
232 rMinX = -rPixelSizeX * (pp.idr()->ReferenceGridHRV->NumberOfColumns - iCentralPixelHRV + 0.5);
233 rMaxY = rPixelSizeY * (pp.idr()->ReferenceGridHRV->NumberOfLines - iCentralPixelHRV + 0.5); //scan direction south -> north
234 }
235 else
236 {
237 rPixelSizeX = 1000 * pp.idr()->ReferenceGridVIS_IR->ColumnDirGridStep;
238 rPixelSizeY = 1000 * pp.idr()->ReferenceGridVIS_IR->LineDirGridStep;
239 // The MSG Level 1.5 Image Data Format Description (page 22f) defines the center of pixel (1856,1856) from SE as sub satellite point for the VIS_IR channels (0°,0° for operational MSG).
240 rMinX = -rPixelSizeX * (pp.idr()->ReferenceGridVIS_IR->NumberOfColumns - iCentralPixelVIS_IR + 0.5);
241 rMaxY = rPixelSizeY * (pp.idr()->ReferenceGridVIS_IR->NumberOfLines - iCentralPixelVIS_IR + 0.5); // The y scan direction is always south -> north
242 }
243 poDS->adfGeoTransform[0] = rMinX;
244 poDS->adfGeoTransform[3] = rMaxY;
245 poDS->adfGeoTransform[1] = rPixelSizeX;
246 poDS->adfGeoTransform[5] = -rPixelSizeY;
247 poDS->adfGeoTransform[2] = 0.0;
248 poDS->adfGeoTransform[4] = 0.0;
249
250 /* -------------------------------------------------------------------- */
251 /* Set Projection Information */
252 /* -------------------------------------------------------------------- */
253
254 poDS->oSRS.SetGEOS( 0, 35785831, 0, 0 );
255 poDS->oSRS.SetWellKnownGeogCS( "WGS84" ); // Temporary line to satisfy ERDAS (otherwise the ellips is "unnamed"). Eventually this should become the custom a and b ellips (CGMS).
256 CPLFree( poDS->pszProjection );
257 poDS->oSRS.exportToWkt( &(poDS->pszProjection) );
258
259 // The following are 3 different try-outs for also setting the ellips a and b parameters.
260 // We leave them out for now however because this does not work. In gdalwarp, when choosing some
261 // specific target SRS, the result is an error message:
262 //
263 // ERROR 1: geocentric transformation missing z or ellps
264 // ERROR 1: GDALWarperOperation::ComputeSourceWindow() failed because
265 // the pfnTransformer failed.
266 //
267 // I can't explain the reason for the message at this time (could be a problem in the way the SRS is set here,
268 // but also a bug in Proj.4 or GDAL.
269 /*
270 oSRS.SetGeogCS( NULL, NULL, NULL, 6378169, 295.488065897, NULL, 0, NULL, 0 );
271
272 oSRS.SetGeogCS( "unnamed ellipse", "unknown", "unnamed", 6378169, 295.488065897, "Greenwich", 0.0);
273
274 if( oSRS.importFromProj4("+proj=geos +h=35785831 +a=6378169 +b=6356583.8") == OGRERR_NONE )
275 {
276 oSRS.exportToWkt( &(poDS->pszProjection) );
277 }
278 */
279
280 /* -------------------------------------------------------------------- */
281 /* Create a transformer to LatLon (only for Reflectance calculation) */
282 /* -------------------------------------------------------------------- */
283
284 OGRSpatialReference* poSRSLongLat = poDS->oSRS.CloneGeogCS();
285 if( poSRSLongLat )
286 {
287 poSRSLongLat->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
288 poDS->poTransform = OGRCreateCoordinateTransformation( &(poDS->oSRS),poSRSLongLat );
289 delete poSRSLongLat;
290 }
291 /* -------------------------------------------------------------------- */
292 /* Set the radiometric calibration parameters. */
293 /* -------------------------------------------------------------------- */
294
295 memcpy( poDS->rCalibrationOffset, pp.rpr()->Cal_Offset, sizeof(double) * 12 );
296 memcpy( poDS->rCalibrationSlope, pp.rpr()->Cal_Slope, sizeof(double) * 12 );
297
298 /* -------------------------------------------------------------------- */
299 /* Create band information objects. */
300 /* -------------------------------------------------------------------- */
301 poDS->nBands = command.iNrChannels()*command.iNrCycles;
302 for( int iBand = 0; iBand < poDS->nBands; iBand++ )
303 {
304 poDS->SetBand( iBand+1, new MSGRasterBand( poDS, iBand+1 ) );
305 }
306
307 /* -------------------------------------------------------------------- */
308 /* Confirm the requested access is supported. */
309 /* -------------------------------------------------------------------- */
310 if( poOpenInfo->eAccess == GA_Update )
311 {
312 delete poDS;
313 CPLError( CE_Failure, CPLE_NotSupported,
314 "The MSG driver does not support update access to existing"
315 " datasets.\n" );
316 return nullptr;
317 }
318
319 /* -------------------------------------------------------------------- */
320 /* Set DataSet metadata information */
321 /* -------------------------------------------------------------------- */
322 CPLString metadataValue;
323 metadataValue.Printf("%d", poDS->iCurrentSatellite);
324 poDS->SetMetadataItem("satellite_number", metadataValue.c_str(), metadataDomain);
325
326 return poDS;
327 }
328
329 /************************************************************************/
330 /* MSGRasterBand() */
331 /************************************************************************/
332
333 const double MSGRasterBand::rRTOA[12] = {20.76, 23.24, 19.85, -1, -1, -1, -1, -1, -1, -1, -1, 25.11};
334
MSGRasterBand(MSGDataset * poDSIn,int nBandIn)335 MSGRasterBand::MSGRasterBand( MSGDataset *poDSIn, int nBandIn )
336 : fScanNorth(false)
337 , iLowerShift(0)
338 , iSplitLine(0)
339 , iLowerWestColumnPlanned(0)
340
341 {
342 this->poDS = poDSIn;
343 this->nBand = nBandIn;
344
345 // Find if we're dealing with MSG1, MSG2, MSG3 or MSG4
346 // Doing this per band is the only way to guarantee time-series when the satellite is changed
347
348 std::string sPrologueFileName = poDSIn->command.sPrologueFileName(poDSIn->iCurrentSatellite, nBand);
349 bool fPrologueExists = (access(sPrologueFileName.c_str(), 0) == 0);
350
351 // Make sure we're testing for MSG1,2,3 or 4 exactly once, start with the most recently used, and remember it in the static member for the next round.
352 if (!fPrologueExists)
353 {
354 poDSIn->iCurrentSatellite = 1 + poDSIn->iCurrentSatellite % MAX_SATELLITES;
355 sPrologueFileName = poDSIn->command.sPrologueFileName(poDSIn->iCurrentSatellite, nBand);
356 fPrologueExists = (access(sPrologueFileName.c_str(), 0) == 0);
357 int iTries = 2;
358 while (!fPrologueExists && (iTries < MAX_SATELLITES))
359 {
360 poDSIn->iCurrentSatellite = 1 + poDSIn->iCurrentSatellite % MAX_SATELLITES;
361 sPrologueFileName = poDSIn->command.sPrologueFileName(poDSIn->iCurrentSatellite, nBand);
362 fPrologueExists = (access(sPrologueFileName.c_str(), 0) == 0);
363 ++iTries;
364 }
365 if (!fPrologueExists) // assume missing prologue file, keep original satellite number
366 {
367 poDSIn->iCurrentSatellite = 1 + poDSIn->iCurrentSatellite % MAX_SATELLITES;
368 sPrologueFileName = poDSIn->command.sPrologueFileName(poDSIn->iCurrentSatellite, nBand);
369 }
370 }
371
372 iSatellite = poDSIn->iCurrentSatellite; // From here on, the satellite that corresponds to this band is settled to the current satellite
373
374 nBlockXSize = poDSIn->GetRasterXSize();
375 nBlockYSize = poDSIn->GetRasterYSize();
376
377 /* -------------------------------------------------------------------- */
378 /* Open an input file and capture the header for the nr. of bits. */
379 /* -------------------------------------------------------------------- */
380 int iStrip = 1;
381 int iChannel = poDSIn->command.iChannel(1 + ((nBand - 1) % poDSIn->command.iNrChannels()));
382 std::string input_file = poDSIn->command.sFileName(iSatellite, nBand, iStrip);
383 while ((access(input_file.c_str(), 0) != 0) && (iStrip <= poDSIn->command.iNrStrips(iChannel))) // compensate for missing strips
384 input_file = poDSIn->command.sFileName(iSatellite, nBand, ++iStrip);
385
386 if (iStrip <= poDSIn->command.iNrStrips(iChannel))
387 {
388 std::ifstream i_file (input_file.c_str(), std::ios::in|std::ios::binary);
389
390 if (i_file.good())
391 {
392 XRITHeaderParser xhp (i_file);
393
394 if (xhp.isValid())
395 {
396 // Data type is either 8 or 16 bits .. we tell this to GDAL here
397 eDataType = GDT_Byte; // default .. always works
398 if (xhp.nrBitsPerPixel() > 8)
399 {
400 if (poDSIn->command.cDataConversion == 'N')
401 eDataType = GDT_UInt16; // normal case: MSG 10 bits data
402 else if (poDSIn->command.cDataConversion == 'B')
403 eDataType = GDT_Byte; // output data type Byte
404 else
405 eDataType = GDT_Float32; // Radiometric calibration
406 }
407
408 // make IReadBlock be called once per file
409 nBlockYSize = xhp.nrRows();
410
411 // remember the scan direction
412
413 fScanNorth = xhp.isScannedNorth();
414 }
415 }
416
417 i_file.close();
418 }
419 else if (nBand > 1)
420 {
421 // missing entire band .. take data from first band
422 MSGRasterBand* pFirstRasterBand = (MSGRasterBand*)poDSIn->GetRasterBand(1);
423 eDataType = pFirstRasterBand->eDataType;
424 nBlockYSize = pFirstRasterBand->nBlockYSize;
425 fScanNorth = pFirstRasterBand->fScanNorth;
426 }
427 else // also first band is missing .. do something for fail-safety
428 {
429 if (poDSIn->command.cDataConversion == 'N')
430 eDataType = GDT_UInt16; // normal case: MSG 10 bits data
431 else if (poDSIn->command.cDataConversion == 'B')
432 eDataType = GDT_Byte; // output data type Byte
433 else
434 eDataType = GDT_Float32; // Radiometric calibration
435
436 // nBlockYSize : default
437 // fScanNorth : default
438 }
439 /* -------------------------------------------------------------------- */
440 /* For the HRV band, read the prologue for shift and splitline. */
441 /* -------------------------------------------------------------------- */
442
443 if (iChannel == 12)
444 {
445 if (fPrologueExists)
446 {
447 std::ifstream p_file(sPrologueFileName.c_str(), std::ios::in|std::ios::binary);
448 XRITHeaderParser xhp(p_file);
449 Prologue pp;
450 if (xhp.isValid() && xhp.isPrologue())
451 pp.read(p_file);
452 p_file.close();
453
454 iLowerShift = pp.idr()->PlannedCoverageHRV->UpperWestColumnPlanned - pp.idr()->PlannedCoverageHRV->LowerWestColumnPlanned;
455 iSplitLine = abs(pp.idr()->PlannedCoverageHRV->UpperNorthLinePlanned - pp.idr()->PlannedCoverageHRV->LowerNorthLinePlanned) + 1; // without the "+ 1" the image of 1-Jan-2005 splits incorrectly
456 iLowerWestColumnPlanned = pp.idr()->PlannedCoverageHRV->LowerWestColumnPlanned;
457 }
458 }
459
460 /* -------------------------------------------------------------------- */
461 /* Initialize the ReflectanceCalculator with the band-dependent info. */
462 /* -------------------------------------------------------------------- */
463
464 int iCycle = 1 + (nBand - 1) / poDSIn->command.iNrChannels();
465 std::string sTimeStamp = poDSIn->command.sCycle(iCycle);
466
467 m_rc = new ReflectanceCalculator(sTimeStamp, rRTOA[iChannel-1]);
468
469 /* -------------------------------------------------------------------- */
470 /* Set DataSet metadata information */
471 /* -------------------------------------------------------------------- */
472 CPLString metadataValue;
473 metadataValue.Printf("%.10f", poDSIn->rCalibrationOffset[iChannel - 1]);
474 SetMetadataItem("calibration_offset", metadataValue.c_str(), poDSIn->metadataDomain);
475 metadataValue.Printf("%.10f", poDSIn->rCalibrationSlope[iChannel - 1]);
476 SetMetadataItem("calibration_slope", metadataValue.c_str(), poDSIn->metadataDomain);
477 metadataValue.Printf("%d", iChannel);
478 SetMetadataItem("channel_number", metadataValue.c_str(), poDSIn->metadataDomain);
479 }
480
481 /************************************************************************/
482 /* ~MSGRasterBand() */
483 /************************************************************************/
~MSGRasterBand()484 MSGRasterBand::~MSGRasterBand()
485 {
486 delete m_rc;
487 }
488
489 /************************************************************************/
490 /* IReadBlock() */
491 /************************************************************************/
IReadBlock(int,int nBlockYOff,void * pImage)492 CPLErr MSGRasterBand::IReadBlock( int /*nBlockXOff*/, int nBlockYOff,
493 void * pImage )
494
495 {
496
497 MSGDataset *poGDS = (MSGDataset *) poDS;
498
499 int iBytesPerPixel = 1;
500 if (eDataType == GDT_UInt16)
501 iBytesPerPixel = 2;
502 else if (eDataType == GDT_Float32)
503 iBytesPerPixel = 4;
504 /* -------------------------------------------------------------------- */
505 /* Calculate the correct input file name based on nBlockYOff */
506 /* -------------------------------------------------------------------- */
507
508 int strip_number;
509 int iChannel = poGDS->command.iChannel(1 + ((nBand - 1) % poGDS->command.iNrChannels()));
510
511 if (fScanNorth)
512 strip_number = nBlockYOff + 1;
513 else
514 strip_number = poGDS->command.iNrStrips(iChannel) - nBlockYOff;
515
516 std::string strip_input_file = poGDS->command.sFileName(iSatellite, nBand, strip_number);
517
518 /* -------------------------------------------------------------------- */
519 /* Open the input file */
520 /* -------------------------------------------------------------------- */
521 if (access(strip_input_file.c_str(), 0) == 0) // does it exist?
522 {
523 std::ifstream i_file (strip_input_file.c_str(), std::ios::in|std::ios::binary);
524
525 if (i_file.good())
526 {
527 XRITHeaderParser xhp (i_file);
528
529 if (xhp.isValid())
530 {
531 std::vector <short> QualityInfo;
532 unsigned short chunk_height = xhp.nrRows();
533 unsigned short chunk_bpp = xhp.nrBitsPerPixel();
534 unsigned short chunk_width = xhp.nrColumns();
535 unsigned __int8 NR = (unsigned __int8)chunk_bpp;
536 unsigned int nb_ibytes = static_cast<unsigned int>(xhp.dataSize());
537 int iShift = 0;
538 bool fSplitStrip = false; // in the split strip the "shift" only happens before the split "row"
539 int iSplitRow = 0;
540 if (iChannel == 12)
541 {
542 iSplitRow = iSplitLine % xhp.nrRows();
543 int iSplitBlock = iSplitLine / xhp.nrRows();
544 fSplitStrip = (nBlockYOff == iSplitBlock); // in the split strip the "shift" only happens before the split "row"
545
546 // When iLowerShift > 0, the lower HRV image is shifted to the right
547 // When iLowerShift < 0, the lower HRV image is shifted to the left
548 // The available raster may be wider than needed, so that time series don't fall outside the raster.
549
550 if (nBlockYOff <= iSplitBlock)
551 iShift = -iLowerShift;
552 // iShift < 0 means upper image moves to the left
553 // iShift > 0 means upper image moves to the right
554 }
555
556 unsigned char* ibuf = new (std::nothrow) unsigned char[nb_ibytes];
557 if (ibuf == nullptr )
558 {
559 CPLError( CE_Failure, CPLE_AppDefined,
560 "Not enough memory to perform wavelet decompression\n");
561 return CE_Failure;
562 }
563
564 i_file.read( (char *)ibuf, nb_ibytes);
565
566 Util::CDataFieldCompressedImage img_compressed(ibuf,
567 nb_ibytes*8,
568 (unsigned char)chunk_bpp,
569 chunk_width,
570 chunk_height );
571
572 Util::CDataFieldUncompressedImage img_uncompressed;
573
574 //****************************************************
575 //*** Here comes the wavelets decompression routine
576 COMP::DecompressWT(img_compressed, NR, img_uncompressed, QualityInfo);
577 //****************************************************
578
579 // convert:
580 // Depth:
581 // 8 bits -> 8 bits
582 // 10 bits -> 16 bits (img_uncompressed contains the 10 bits data in packed form)
583 // Geometry:
584 // chunk_width x chunk_height to nBlockXSize x nBlockYSize
585
586 // cases:
587 // combination of the following:
588 // - scan direction can be north or south
589 // - eDataType can be GDT_Byte, GDT_UInt16 or GDT_Float32
590 // - nBlockXSize == chunk_width or nBlockXSize > chunk_width
591 // - when nBlockXSize > chunk_width, fSplitStrip can be true or false
592 // we won't distinguish the following cases:
593 // - NR can be == 8 or != 8
594 // - when nBlockXSize > chunk_width, iShift iMinCOff-iMaxCOff <= iShift <= 0
595
596 int nBlockSize = nBlockXSize * nBlockYSize;
597 int y = chunk_width * chunk_height;
598 int iStep = -1;
599 if (fScanNorth) // image is the other way around
600 {
601 y = -1; // See how y is used below: += happens first, the result is used in the []
602 iStep = 1;
603 }
604
605 COMP::CImage cimg (img_uncompressed); // unpack
606 if (eDataType == GDT_Byte)
607 {
608 if (nBlockXSize == chunk_width) // optimized version
609 {
610 if (poGDS->command.cDataConversion == 'B')
611 {
612 for( int i = 0; i < nBlockSize; ++i )
613 ((GByte *)pImage)[i] = cimg.Get()[y+=iStep] / 4;
614 }
615 else
616 {
617 for( int i = 0; i < nBlockSize; ++i )
618 ((GByte *)pImage)[i] = cimg.Get()[y+=iStep];
619 }
620 }
621 else
622 {
623 // initialize to 0's (so that it does not have to be done in an 'else' statement <performance>)
624 memset(pImage, 0, nBlockXSize * nBlockYSize * iBytesPerPixel);
625 if (poGDS->command.cDataConversion == 'B')
626 {
627 for( int j = 0; j < chunk_height; ++j ) // assumption: nBlockYSize == chunk_height
628 {
629 int iXOffset = j * nBlockXSize + iShift;
630 iXOffset += nBlockXSize - iLowerWestColumnPlanned - 1; // Position the HRV part in the frame; -1 to compensate the pre-increment in the for-loop
631 if (fSplitStrip && (j >= iSplitRow)) // In splitstrip, below splitline, thus do not shift!!
632 iXOffset -= iShift;
633 for (int i = 0; i < chunk_width; ++i)
634 ((GByte *)pImage)[++iXOffset] = cimg.Get()[y+=iStep] / 4;
635 }
636 }
637 else
638 {
639 for( int j = 0; j < chunk_height; ++j ) // assumption: nBlockYSize == chunk_height
640 {
641 int iXOffset = j * nBlockXSize + iShift;
642 iXOffset += nBlockXSize - iLowerWestColumnPlanned - 1; // Position the HRV part in the frame; -1 to compensate the pre-increment in the for-loop
643 if (fSplitStrip && (j >= iSplitRow)) // In splitstrip, below splitline, thus do not shift!!
644 iXOffset -= iShift;
645 for (int i = 0; i < chunk_width; ++i)
646 ((GByte *)pImage)[++iXOffset] = cimg.Get()[y+=iStep];
647 }
648 }
649 }
650 }
651 else if (eDataType == GDT_UInt16) // this is our "normal case" if scan direction is South: 10 bit MSG data became 2 bytes per pixel
652 {
653 if (nBlockXSize == chunk_width) // optimized version
654 {
655 for( int i = 0; i < nBlockSize; ++i )
656 ((GUInt16 *)pImage)[i] = cimg.Get()[y+=iStep];
657 }
658 else
659 {
660 // initialize to 0's (so that it does not have to be done in an 'else' statement <performance>)
661 memset(pImage, 0, nBlockXSize * nBlockYSize * iBytesPerPixel);
662 for( int j = 0; j < chunk_height; ++j ) // assumption: nBlockYSize == chunk_height
663 {
664 int iXOffset = j * nBlockXSize + iShift;
665 iXOffset += nBlockXSize - iLowerWestColumnPlanned - 1; // Position the HRV part in the frame; -1 to compensate the pre-increment in the for-loop
666 if (fSplitStrip && (j >= iSplitRow)) // In splitstrip, below splitline, thus do not shift!!
667 iXOffset -= iShift;
668 for (int i = 0; i < chunk_width; ++i)
669 ((GUInt16 *)pImage)[++iXOffset] = cimg.Get()[y+=iStep];
670 }
671 }
672 }
673 else if (eDataType == GDT_Float32) // radiometric calibration is requested
674 {
675 if (nBlockXSize == chunk_width) // optimized version
676 {
677 for( int i = 0; i < nBlockSize; ++i )
678 ((float *)pImage)[i] = (float)rRadiometricCorrection(cimg.Get()[y+=iStep], iChannel, nBlockYOff * nBlockYSize + i / nBlockXSize, i % nBlockXSize, poGDS);
679 }
680 else
681 {
682 // initialize to 0's (so that it does not have to be done in an 'else' statement <performance>)
683 memset(pImage, 0, nBlockXSize * nBlockYSize * iBytesPerPixel);
684 for( int j = 0; j < chunk_height; ++j ) // assumption: nBlockYSize == chunk_height
685 {
686 int iXOffset = j * nBlockXSize + iShift;
687 iXOffset += nBlockXSize - iLowerWestColumnPlanned - 1; // Position the HRV part in the frame; -1 to compensate the pre-increment in the for-loop
688 if (fSplitStrip && (j >= iSplitRow)) // In splitstrip, below splitline, thus do not shift!!
689 iXOffset -= iShift;
690 int iXFrom = nBlockXSize - iLowerWestColumnPlanned + iShift; // i is used as the iCol parameter in rRadiometricCorrection
691 int iXTo = nBlockXSize - iLowerWestColumnPlanned + chunk_width + iShift;
692 for (int i = iXFrom; i < iXTo; ++i) // range always equal to chunk_width .. this is to utilize i to get iCol
693 ((float *)pImage)[++iXOffset] = (float)rRadiometricCorrection(cimg.Get()[y+=iStep], iChannel, nBlockYOff * nBlockYSize + j, (fSplitStrip && (j >= iSplitRow))?(i - iShift):i, poGDS);
694 }
695 }
696 }
697 }
698 else // header could not be opened .. make sure block contains 0's
699 memset(pImage, 0, nBlockXSize * nBlockYSize * iBytesPerPixel);
700 }
701 else // file could not be opened .. make sure block contains 0's
702 memset(pImage, 0, nBlockXSize * nBlockYSize * iBytesPerPixel);
703
704 i_file.close();
705 }
706 else // file does not exist .. make sure block contains 0's
707 memset(pImage, 0, nBlockXSize * nBlockYSize * iBytesPerPixel);
708
709 return CE_None;
710 }
711
rRadiometricCorrection(unsigned int iDN,int iChannel,int iRow,int iCol,MSGDataset * poGDS) const712 double MSGRasterBand::rRadiometricCorrection(unsigned int iDN, int iChannel, int iRow, int iCol, MSGDataset* poGDS) const
713 {
714 int iIndex = iChannel - 1; // just for speed optimization
715
716 double rSlope = poGDS->rCalibrationSlope[iIndex];
717 double rOffset = poGDS->rCalibrationOffset[iIndex];
718
719 // Reflectance for visual bands, temperature for IR bands.
720 if (poGDS->command.cDataConversion == 'T')
721 {
722 double rRadiance = rOffset + (iDN * rSlope);
723
724 if (iChannel >= 4 && iChannel <= 11) // Channels 4 to 11 (infrared): Temperature
725 {
726 const double rC1 = 1.19104e-5;
727 const double rC2 = 1.43877e+0;
728
729 double cc2 = rC2 * poGDS->rVc[iIndex];
730 double cc1 = rC1 * pow(poGDS->rVc[iIndex], 3) / rRadiance;
731 // cppcheck suggests using log1p() but not sure how portable this would be
732 // cppcheck-suppress unpreciseMathCall
733 double rTemperature = ((cc2 / log(cc1 + 1)) - poGDS->rB[iIndex]) / poGDS->rA[iIndex];
734 return rTemperature;
735 }
736 else // Channels 1,2,3 and 12 (visual): Reflectance
737 {
738 double rLon = poGDS->adfGeoTransform[0] + iCol * poGDS->adfGeoTransform[1]; // X, in "geos" meters
739 double rLat = poGDS->adfGeoTransform[3] + iRow * poGDS->adfGeoTransform[5]; // Y, in "geos" meters
740 if ((poGDS->poTransform != nullptr) && poGDS->poTransform->Transform( 1, &rLon, &rLat )) // transform it to latlon
741 return m_rc->rGetReflectance(rRadiance, rLat, rLon);
742 else
743 return 0;
744 }
745 }
746 else // radiometric
747 {
748 if (poGDS->command.cDataConversion == 'R')
749 return rOffset + (iDN * rSlope);
750 else
751 {
752 double rFactor = 10 / pow(poGDS->rCentralWvl[iIndex], 2);
753 return rFactor * (rOffset + (iDN * rSlope));
754 }
755 }
756 }
757
758 /************************************************************************/
759 /* GDALRegister_MSG() */
760 /************************************************************************/
761
GDALRegister_MSG()762 void GDALRegister_MSG()
763
764 {
765 if( GDALGetDriverByName( "MSG" ) != nullptr )
766 return;
767
768 GDALDriver *poDriver = new GDALDriver();
769
770 poDriver->SetDescription( "MSG" );
771 poDriver->SetMetadataItem( GDAL_DCAP_RASTER, "YES" );
772 poDriver->SetMetadataItem( GDAL_DMD_LONGNAME, "MSG HRIT Data" );
773
774 poDriver->pfnOpen = MSGDataset::Open;
775
776 GetGDALDriverManager()->RegisterDriver( poDriver );
777 }
778