LCOV - code coverage report
Current view: top level - frmts/msgn - msgndataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 24 367 6.5 %
Date: 2024-04-27 17:22:41 Functions: 2 13 15.4 %

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

Generated by: LCOV version 1.14