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