LCOV - code coverage report
Current view: top level - frmts/iris - irisdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 359 562 63.9 %
Date: 2025-07-01 22:47:05 Functions: 16 17 94.1 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  IRIS Reader
       4             :  * Purpose:  All code for IRIS format Reader
       5             :  * Author:   Roger Veciana, rveciana@gmail.com
       6             :  *           Portions are adapted from code copyright (C) 2005-2012
       7             :  *           Chris Veness under a CC-BY 3.0 licence
       8             :  *
       9             :  ******************************************************************************
      10             :  * Copyright (c) 2012, Roger Veciana <rveciana@gmail.com>
      11             :  * Copyright (c) 2012-2013, Even Rouault <even dot rouault at spatialys.com>
      12             :  *
      13             :  * SPDX-License-Identifier: MIT
      14             :  ****************************************************************************/
      15             : 
      16             : #include "cpl_port.h"
      17             : #include "gdal_frmts.h"
      18             : #include "gdal_pam.h"
      19             : #include "ogr_spatialref.h"
      20             : 
      21             : #include <algorithm>
      22             : #include <cassert>
      23             : #include <sstream>
      24             : 
      25             : static double DEG2RAD = M_PI / 180.0;
      26             : static double RAD2DEG = 180.0 / M_PI;
      27             : 
      28             : /************************************************************************/
      29             : /* ==================================================================== */
      30             : /*                                  IRISDataset                         */
      31             : /* ==================================================================== */
      32             : /************************************************************************/
      33             : 
      34             : class IRISRasterBand;
      35             : 
      36             : class IRISDataset final : public GDALPamDataset
      37             : {
      38             :     friend class IRISRasterBand;
      39             : 
      40             :     VSILFILE *fp;
      41             :     GByte abyHeader[640];
      42             :     bool bNoDataSet;
      43             :     double dfNoDataValue;
      44             :     static const char *const aszProductNames[];
      45             :     static const char *const aszDataTypeCodes[];
      46             :     static const char *const aszDataTypes[];
      47             :     static const char *const aszProjections[];
      48             :     unsigned short nProductCode;
      49             :     unsigned short nDataTypeCode;
      50             :     unsigned char nProjectionCode;
      51             :     float fNyquistVelocity;
      52             :     mutable OGRSpatialReference m_oSRS{};
      53             :     mutable GDALGeoTransform m_gt{};
      54             :     mutable bool bHasLoadedProjection;
      55             :     void LoadProjection() const;
      56             :     static bool GeodesicCalculation(double fLat, double fLon, double fAngle,
      57             :                                     double fDist, double fEquatorialRadius,
      58             :                                     double fPolarRadius, double fFlattening,
      59             :                                     std::pair<double, double> &oOutPair);
      60             : 
      61             :   public:
      62             :     IRISDataset();
      63             :     virtual ~IRISDataset();
      64             : 
      65             :     static GDALDataset *Open(GDALOpenInfo *);
      66             :     static int Identify(GDALOpenInfo *);
      67             : 
      68             :     CPLErr GetGeoTransform(GDALGeoTransform &gt) const override;
      69             :     const OGRSpatialReference *GetSpatialRef() const override;
      70             : };
      71             : 
      72             : const char *const IRISDataset::aszProductNames[] = {
      73             :     "",      "PPI",   "RHI",  "CAPPI", "CROSS", "TOPS",  "TRACK",
      74             :     "RAIN1", "RAINN", "VVP",  "VIL",   "SHEAR", "WARN",  "CATCH",
      75             :     "RTI",   "RAW",   "MAX",  "USER",  "USERV", "OTHER", "STATUS",
      76             :     "SLINE", "WIND",  "BEAM", "TEXT",  "FCAST", "NDOP",  "IMAGE",
      77             :     "COMP",  "TDWR",  "GAGE", "DWELL", "SRI",   "BASE",  "HMAX"};
      78             : 
      79             : const char *const IRISDataset::aszDataTypeCodes[] = {
      80             :     "XHDR",     "DBT",       "dBZ",      "VEL",    "WIDTH",   "ZDR",
      81             :     "ORAIN",    "dBZC",      "DBT2",     "dBZ2",   "VEL2",    "WIDTH2",
      82             :     "ZDR2",     "RAINRATE2", "KDP",      "KDP2",   "PHIDP",   "VELC",
      83             :     "SQI",      "RHOHV",     "RHOHV2",   "dBZC2",  "VELC2",   "SQI2",
      84             :     "PHIDP2",   "LDRH",      "LDRH2",    "LDRV",   "LDRV2",   "FLAGS",
      85             :     "FLAGS2",   "FLOAT32",   "HEIGHT",   "VIL2",   "NULL",    "SHEAR",
      86             :     "DIVERGE2", "FLIQUID2",  "USER",     "OTHER",  "DEFORM2", "VVEL2",
      87             :     "HVEL2",    "HDIR2",     "AXDIL2",   "TIME2",  "RHOH",    "RHOH2",
      88             :     "RHOV",     "RHOV2",     "PHIH",     "PHIH2",  "PHIV",    "PHIV2",
      89             :     "USER2",    "HCLASS",    "HCLASS2",  "ZDRC",   "ZDRC2",   "TEMPERATURE16",
      90             :     "VIR16",    "DBTV8",     "DBTV16",   "DBZV8",  "DBZV16",  "SNR8",
      91             :     "SNR16",    "ALBEDO8",   "ALBEDO16", "VILD16", "TURB16"};
      92             : 
      93             : const char *const IRISDataset::aszDataTypes[] = {
      94             :     "Extended Headers",
      95             :     "Total H power (1 byte)",
      96             :     "Clutter Corrected H reflectivity (1 byte)",
      97             :     "Velocity (1 byte)",
      98             :     "Width (1 byte)",
      99             :     "Differential reflectivity (1 byte)",
     100             :     "Old Rainfall rate (stored as dBZ)",
     101             :     "Fully corrected reflectivity (1 byte)",
     102             :     "Uncorrected reflectivity (2 byte)",
     103             :     "Corrected reflectivity (2 byte)",
     104             :     "Velocity (2 byte)",
     105             :     "Width (2 byte)",
     106             :     "Differential reflectivity (2 byte)",
     107             :     "Rainfall rate (2 byte)",
     108             :     "Kdp (specific differential phase)(1 byte)",
     109             :     "Kdp (specific differential phase)(2 byte)",
     110             :     "PHIdp (differential phase)(1 byte)",
     111             :     "Corrected Velocity (1 byte)",
     112             :     "SQI (1 byte)",
     113             :     "RhoHV(0) (1 byte)",
     114             :     "RhoHV(0) (2 byte)",
     115             :     "Fully corrected reflectivity (2 byte)",
     116             :     "Corrected Velocity (2 byte)",
     117             :     "SQI (2 byte)",
     118             :     "PHIdp (differential phase)(2 byte)",
     119             :     "LDR H to V (1 byte)",
     120             :     "LDR H to V (2 byte)",
     121             :     "LDR V to H (1 byte)",
     122             :     "LDR V to H (2 byte)",
     123             :     "Individual flag bits for each bin",
     124             :     "",
     125             :     "Test of floating format",
     126             :     "Height (1/10 km) (1 byte)",
     127             :     "Linear liquid (.001mm) (2 byte)",
     128             :     "Data type is not applicable",
     129             :     "Wind Shear (1 byte)",
     130             :     "Divergence (.001 10**-4) (2-byte)",
     131             :     "Floated liquid (2 byte)",
     132             :     "User type, unspecified data (1 byte)",
     133             :     "Unspecified data, no color legend",
     134             :     "Deformation (.001 10**-4) (2-byte)",
     135             :     "Vertical velocity (.01 m/s) (2-byte)",
     136             :     "Horizontal velocity (.01 m/s) (2-byte)",
     137             :     "Horizontal wind direction (.1 degree) (2-byte)",
     138             :     "Axis of Dillitation (.1 degree) (2-byte)",
     139             :     "Time of data (seconds) (2-byte)",
     140             :     "Rho H to V (1 byte)",
     141             :     "Rho H to V (2 byte)",
     142             :     "Rho V to H (1 byte)",
     143             :     "Rho V to H (2 byte)",
     144             :     "Phi H to V (1 byte)",
     145             :     "Phi H to V (2 byte)",
     146             :     "Phi V to H (1 byte)",
     147             :     "Phi V to H (2 byte)",
     148             :     "User type, unspecified data (2 byte)",
     149             :     "Hydrometeor class (1 byte)",
     150             :     "Hydrometeor class (2 byte)",
     151             :     "Corrected Differential reflectivity (1 byte)",
     152             :     "Corrected Differential reflectivity (2 byte)",
     153             :     "Temperature (2 byte)",
     154             :     "Vertically Integrated Reflectivity (2 byte)",
     155             :     "Total V Power (1 byte)",
     156             :     "Total V Power (2 byte)",
     157             :     "Clutter Corrected V Reflectivity (1 byte)",
     158             :     "Clutter Corrected V Reflectivity (2 byte)",
     159             :     "Signal to Noise ratio (1 byte)",
     160             :     "Signal to Noise ratio (2 byte)",
     161             :     "Albedo (1 byte)",
     162             :     "Albedo (2 byte)",
     163             :     "VIL Density (2 byte)",
     164             :     "Turbulence (2 byte)"};
     165             : 
     166             : const char *const IRISDataset::aszProjections[] = {
     167             :     "Azimutal equidistant", "Mercator", "Polar Stereographic", "UTM",
     168             :     // FIXME: is it a typo here or in IRIS itself: Perspective or Prespective ?
     169             :     "Perspective from geosync", "Equidistant cylindrical", "Gnomonic",
     170             :     "Gauss conformal", "Lambert conformal conic"};
     171             : 
     172             : /************************************************************************/
     173             : /* ==================================================================== */
     174             : /*                            IRISRasterBand                            */
     175             : /* ==================================================================== */
     176             : /************************************************************************/
     177             : 
     178             : class IRISRasterBand final : public GDALPamRasterBand
     179             : {
     180             :     friend class IRISDataset;
     181             : 
     182             :     unsigned char *pszRecord;
     183             :     bool bBufferAllocFailed;
     184             : 
     185             :   public:
     186             :     IRISRasterBand(IRISDataset *, int);
     187             :     virtual ~IRISRasterBand();
     188             : 
     189             :     virtual CPLErr IReadBlock(int, int, void *) override;
     190             : 
     191             :     virtual double GetNoDataValue(int *) override;
     192             :     virtual CPLErr SetNoDataValue(double) override;
     193             : };
     194             : 
     195             : /************************************************************************/
     196             : /*                           IRISRasterBand()                           */
     197             : /************************************************************************/
     198             : 
     199           3 : IRISRasterBand::IRISRasterBand(IRISDataset *poDSIn, int nBandIn)
     200           3 :     : pszRecord(nullptr), bBufferAllocFailed(false)
     201             : {
     202           3 :     poDS = poDSIn;
     203           3 :     nBand = nBandIn;
     204           3 :     eDataType = GDT_Float32;
     205           3 :     nBlockXSize = poDS->GetRasterXSize();
     206           3 :     nBlockYSize = 1;
     207           3 : }
     208             : 
     209           6 : IRISRasterBand::~IRISRasterBand()
     210             : {
     211           3 :     VSIFree(pszRecord);
     212           6 : }
     213             : 
     214             : /************************************************************************/
     215             : /*                             IReadBlock()                             */
     216             : /************************************************************************/
     217             : 
     218         263 : CPLErr IRISRasterBand::IReadBlock(int /* nBlockXOff */, int nBlockYOff,
     219             :                                   void *pImage)
     220             : 
     221             : {
     222         263 :     IRISDataset *poGDS = static_cast<IRISDataset *>(poDS);
     223             : 
     224             :     // Every product type has its own size. TODO: Move it like dataType.
     225         263 :     int nDataLength = 1;
     226         263 :     if (poGDS->nDataTypeCode == 2)
     227         263 :         nDataLength = 1;
     228           0 :     else if (poGDS->nDataTypeCode == 8)
     229           0 :         nDataLength = 2;
     230           0 :     else if (poGDS->nDataTypeCode == 9)
     231           0 :         nDataLength = 2;
     232           0 :     else if (poGDS->nDataTypeCode == 37)
     233           0 :         nDataLength = 2;
     234           0 :     else if (poGDS->nDataTypeCode == 33)
     235           0 :         nDataLength = 2;
     236           0 :     else if (poGDS->nDataTypeCode == 32)
     237           0 :         nDataLength = 1;
     238             : 
     239             :     // We allocate space for storing a record:
     240         263 :     if (pszRecord == nullptr)
     241             :     {
     242           2 :         if (bBufferAllocFailed)
     243           0 :             return CE_Failure;
     244             : 
     245           2 :         pszRecord = static_cast<unsigned char *>(
     246           2 :             VSI_MALLOC_VERBOSE(static_cast<size_t>(nBlockXSize) * nDataLength));
     247             : 
     248           2 :         if (pszRecord == nullptr)
     249             :         {
     250           0 :             bBufferAllocFailed = true;
     251           0 :             return CE_Failure;
     252             :         }
     253             :     }
     254             : 
     255             :     // Prepare to read (640 is the header size in bytes) and read (the
     256             :     // y axis in the IRIS files in the inverse direction).  The
     257             :     // previous bands are also added as an offset
     258             : 
     259         263 :     VSIFSeekL(poGDS->fp,
     260             :               640 +
     261         526 :                   static_cast<vsi_l_offset>(nDataLength) *
     262         263 :                       poGDS->GetRasterXSize() * poGDS->GetRasterYSize() *
     263         526 :                       (this->nBand - 1) +
     264         526 :                   static_cast<vsi_l_offset>(nBlockXSize) * nDataLength *
     265         263 :                       (poGDS->GetRasterYSize() - 1 - nBlockYOff),
     266             :               SEEK_SET);
     267             : 
     268         263 :     if (static_cast<int>(
     269         263 :             VSIFReadL(pszRecord, static_cast<size_t>(nBlockXSize) * nDataLength,
     270         263 :                       1, poGDS->fp)) != 1)
     271           0 :         return CE_Failure;
     272             : 
     273             :     // If datatype is dbZ or dBT:
     274             :     // See point 3.3.3 at page 3.33 of the manual.
     275         263 :     if (poGDS->nDataTypeCode == 2 || poGDS->nDataTypeCode == 1)
     276             :     {
     277       68384 :         for (int i = 0; i < nBlockXSize; i++)
     278             :         {
     279       68121 :             float fVal = ((*(pszRecord + i * nDataLength)) - 64.0f) / 2.0f;
     280       68121 :             if (fVal == 95.5f)
     281           0 :                 fVal = -9999.0f;
     282       68121 :             ((float *)pImage)[i] = fVal;
     283         263 :         }
     284             :         // If datatype is dbZ2 or dBT2:
     285             :         // See point 3.3.4 at page 3.33 of the manual.
     286             :     }
     287           0 :     else if (poGDS->nDataTypeCode == 8 || poGDS->nDataTypeCode == 9)
     288             :     {
     289           0 :         for (int i = 0; i < nBlockXSize; i++)
     290             :         {
     291           0 :             float fVal =
     292           0 :                 (CPL_LSBUINT16PTR(pszRecord + i * nDataLength) - 32768.0f) /
     293             :                 100.0f;
     294           0 :             if (fVal == 327.67f)
     295           0 :                 fVal = -9999.0f;
     296           0 :             ((float *)pImage)[i] = fVal;
     297           0 :         }
     298             :         // Fliquid2 (Rain1 & Rainn products)
     299             :         // See point 3.3.11 at page 3.43 of the manual.
     300             :     }
     301           0 :     else if (poGDS->nDataTypeCode == 37)
     302             :     {
     303           0 :         for (int i = 0; i < nBlockXSize; i++)
     304             :         {
     305           0 :             const unsigned short nVal =
     306           0 :                 CPL_LSBUINT16PTR(pszRecord + i * nDataLength);
     307           0 :             const unsigned short nExp = nVal >> 12;
     308           0 :             const unsigned short nMantissa = nVal - (nExp << 12);
     309           0 :             float fVal2 = 0.0f;
     310           0 :             if (nVal == 65535)
     311           0 :                 fVal2 = -9999.0f;
     312           0 :             else if (nExp == 0)
     313           0 :                 fVal2 = nMantissa / 1000.0f;
     314             :             else
     315           0 :                 fVal2 = ((nMantissa + 4096) << (nExp - 1)) / 1000.0f;
     316           0 :             ((float *)pImage)[i] = fVal2;
     317             :         }
     318             :         // VIL2 (VIL products)
     319             :         // See point 3.3.41 at page 3.54 of the manual.
     320             :     }
     321           0 :     else if (poGDS->nDataTypeCode == 33)
     322             :     {
     323           0 :         for (int i = 0; i < nBlockXSize; i++)
     324             :         {
     325           0 :             float fVal = static_cast<float>(
     326           0 :                 CPL_LSBUINT16PTR(pszRecord + i * nDataLength));
     327           0 :             if (fVal == 65535.0f)
     328           0 :                 ((float *)pImage)[i] = -9999.0f;
     329           0 :             else if (fVal == 0.0f)
     330           0 :                 ((float *)pImage)[i] = -1.0f;
     331             :             else
     332           0 :                 ((float *)pImage)[i] = (fVal - 1) / 1000.0f;
     333             :         }
     334             :         // HEIGHT (TOPS products)
     335             :         // See point 3.3.14 at page 3.46 of the manual.
     336             :     }
     337           0 :     else if (poGDS->nDataTypeCode == 32)
     338             :     {
     339           0 :         for (int i = 0; i < nBlockXSize; i++)
     340             :         {
     341           0 :             unsigned char nVal = *(pszRecord + i * nDataLength);
     342           0 :             if (nVal == 255)
     343           0 :                 ((float *)pImage)[i] = -9999.0f;
     344           0 :             else if (nVal == 0)
     345           0 :                 ((float *)pImage)[i] = -1.0f;
     346             :             else
     347           0 :                 ((float *)pImage)[i] = (nVal - 1.0f) / 10.0f;
     348             :         }
     349             :         // VEL (Velocity 1-Byte in PPI & others)
     350             :         // See point 3.3.37 at page 3.53 of the manual.
     351             :     }
     352           0 :     else if (poGDS->nDataTypeCode == 3)
     353             :     {
     354           0 :         for (int i = 0; i < nBlockXSize; i++)
     355             :         {
     356           0 :             float fVal = static_cast<float>(*(pszRecord + i * nDataLength));
     357           0 :             if (fVal == 0.0f)
     358           0 :                 fVal = -9997.0f;
     359           0 :             else if (fVal == 1.0f)
     360           0 :                 fVal = -9998.0f;
     361           0 :             else if (fVal == 255.0f)
     362           0 :                 fVal = -9999.0f;
     363             :             else
     364           0 :                 fVal = poGDS->fNyquistVelocity * (fVal - 128.0f) / 127.0f;
     365           0 :             ((float *)pImage)[i] = fVal;
     366             :         }
     367             :         // SHEAR (1-Byte Shear)
     368             :         // See point 3.3.23 at page 3.39 of the manual.
     369             :     }
     370           0 :     else if (poGDS->nDataTypeCode == 35)
     371             :     {
     372           0 :         for (int i = 0; i < nBlockXSize; i++)
     373             :         {
     374           0 :             float fVal = static_cast<float>(*(pszRecord + i * nDataLength));
     375           0 :             if (fVal == 0.0f)
     376           0 :                 fVal = -9998.0f;
     377           0 :             else if (fVal == 255.0f)
     378           0 :                 fVal = -9999.0f;
     379             :             else
     380           0 :                 fVal = (fVal - 128.0f) * 0.2f;
     381           0 :             ((float *)pImage)[i] = fVal;
     382             :         }
     383             :     }
     384             : 
     385         263 :     return CE_None;
     386             : }
     387             : 
     388             : /************************************************************************/
     389             : /*                           SetNoDataValue()                           */
     390             : /************************************************************************/
     391             : 
     392           3 : CPLErr IRISRasterBand::SetNoDataValue(double dfNoData)
     393             : 
     394             : {
     395           3 :     IRISDataset *poGDS = static_cast<IRISDataset *>(poDS);
     396             : 
     397           3 :     poGDS->bNoDataSet = true;
     398           3 :     poGDS->dfNoDataValue = dfNoData;
     399             : 
     400           3 :     return CE_None;
     401             : }
     402             : 
     403             : /************************************************************************/
     404             : /*                           GetNoDataValue()                           */
     405             : /************************************************************************/
     406             : 
     407           0 : double IRISRasterBand::GetNoDataValue(int *pbSuccess)
     408             : 
     409             : {
     410           0 :     IRISDataset *poGDS = static_cast<IRISDataset *>(poDS);
     411             : 
     412           0 :     if (poGDS->bNoDataSet)
     413             :     {
     414           0 :         if (pbSuccess)
     415           0 :             *pbSuccess = TRUE;
     416             : 
     417           0 :         return poGDS->dfNoDataValue;
     418             :     }
     419             : 
     420           0 :     return GDALPamRasterBand::GetNoDataValue(pbSuccess);
     421             : }
     422             : 
     423             : /************************************************************************/
     424             : /* ==================================================================== */
     425             : /*                              IRISDataset                             */
     426             : /* ==================================================================== */
     427             : /************************************************************************/
     428             : 
     429             : /************************************************************************/
     430             : /*                            IRISDataset()                             */
     431             : /************************************************************************/
     432             : 
     433           3 : IRISDataset::IRISDataset()
     434             :     : fp(nullptr), bNoDataSet(false), dfNoDataValue(0.0), nProductCode(0),
     435             :       nDataTypeCode(0), nProjectionCode(0), fNyquistVelocity(0.0),
     436           3 :       bHasLoadedProjection(false)
     437             : {
     438           3 :     m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     439           3 :     std::fill_n(abyHeader, CPL_ARRAYSIZE(abyHeader), static_cast<GByte>(0));
     440           3 : }
     441             : 
     442             : /************************************************************************/
     443             : /*                           ~IRISDataset()                             */
     444             : /************************************************************************/
     445             : 
     446           6 : IRISDataset::~IRISDataset()
     447             : 
     448             : {
     449           3 :     FlushCache(true);
     450           3 :     if (fp != nullptr)
     451           3 :         VSIFCloseL(fp);
     452           6 : }
     453             : 
     454             : /************************************************************************/
     455             : /*           Calculates the projection and Geotransform                 */
     456             : /************************************************************************/
     457           1 : void IRISDataset::LoadProjection() const
     458             : {
     459           1 :     bHasLoadedProjection = true;
     460             :     // They give the radius in cm.
     461           1 :     double dfEquatorialRadius =
     462           1 :         CPL_LSBUINT32PTR(abyHeader + 220 + 320 + 12) / 100.0;
     463             :     // Point 3.2.27 pag 3-15.
     464           1 :     double dfInvFlattening =
     465           1 :         CPL_LSBUINT32PTR(abyHeader + 224 + 320 + 12) / 1000000.0;
     466           1 :     double dfFlattening = 0.0;
     467           1 :     double dfPolarRadius = 0.0;
     468             : 
     469           1 :     if (dfEquatorialRadius == 0.0)
     470             :     {
     471             :         // If Radius is 0, change to 6371000 Point 3.2.27 pag 3-15 (old IRIS
     472             :         // versions).
     473           0 :         dfEquatorialRadius = 6371000.0;
     474           0 :         dfPolarRadius = dfEquatorialRadius;
     475           0 :         dfInvFlattening = 0.0;
     476           0 :         dfFlattening = 0.0;
     477             :     }
     478             :     else
     479             :     {
     480           1 :         if (dfInvFlattening == 0.0)
     481             :         {
     482             :             // When inverse flattening is infinite, they use 0.
     483           1 :             dfFlattening = 0.0;
     484           1 :             dfPolarRadius = dfEquatorialRadius;
     485             :         }
     486             :         else
     487             :         {
     488           0 :             dfFlattening = 1.0 / dfInvFlattening;
     489           0 :             dfPolarRadius = dfEquatorialRadius * (1.0 - dfFlattening);
     490             :         }
     491             :     }
     492             : 
     493           1 :     constexpr GUInt32 knUINT32_MAX = 0xFFFFFFFFU;
     494           1 :     const double dfCenterLon =
     495           1 :         CPL_LSBUINT32PTR(abyHeader + 112 + 320 + 12) * 360.0 / knUINT32_MAX;
     496           1 :     const double dfCenterLat =
     497           1 :         CPL_LSBUINT32PTR(abyHeader + 108 + 320 + 12) * 360.0 / knUINT32_MAX;
     498             : 
     499           1 :     const double dfProjRefLon =
     500           1 :         CPL_LSBUINT32PTR(abyHeader + 244 + 320 + 12) * 360.0 / knUINT32_MAX;
     501           1 :     const double dfProjRefLat =
     502           1 :         CPL_LSBUINT32PTR(abyHeader + 240 + 320 + 12) * 360.0 / knUINT32_MAX;
     503             : 
     504           1 :     const double dfRadarLocX = CPL_LSBSINT32PTR(abyHeader + 112 + 12) / 1000.0;
     505           1 :     const double dfRadarLocY = CPL_LSBSINT32PTR(abyHeader + 116 + 12) / 1000.0;
     506             : 
     507           1 :     const double dfScaleX = CPL_LSBSINT32PTR(abyHeader + 88 + 12) / 100.0;
     508           1 :     const double dfScaleY = CPL_LSBSINT32PTR(abyHeader + 92 + 12) / 100.0;
     509           1 :     if (dfScaleX <= 0.0 || dfScaleY <= 0.0 || dfScaleX >= dfPolarRadius ||
     510             :         dfScaleY >= dfPolarRadius)
     511           0 :         return;
     512             : 
     513             :     // Mercator projection.
     514           1 :     if (EQUAL(aszProjections[nProjectionCode], "Mercator"))
     515             :     {
     516           1 :         std::pair<double, double> oPositionX2;
     517           1 :         if (!GeodesicCalculation(dfCenterLat, dfCenterLon, 90.0, dfScaleX,
     518             :                                  dfEquatorialRadius, dfPolarRadius,
     519             :                                  dfFlattening, oPositionX2))
     520           0 :             return;
     521           1 :         std::pair<double, double> oPositionY2;
     522           1 :         if (!GeodesicCalculation(dfCenterLat, dfCenterLon, 0.0, dfScaleY,
     523             :                                  dfEquatorialRadius, dfPolarRadius,
     524             :                                  dfFlattening, oPositionY2))
     525           0 :             return;
     526             : 
     527           1 :         m_oSRS.SetGeogCS("unnamed ellipse", "unknown", "unnamed",
     528             :                          dfEquatorialRadius, dfInvFlattening, "Greenwich", 0.0,
     529             :                          "degree", 0.0174532925199433);
     530             : 
     531           1 :         m_oSRS.SetMercator(dfProjRefLat, dfProjRefLon, 1.0, 0.0, 0.0);
     532           1 :         m_oSRS.SetLinearUnits("Metre", 1.0);
     533             : 
     534             :         // The center coordinates are given in LatLon on the defined
     535             :         // ellipsoid. Necessary to calculate geotransform.
     536             : 
     537           2 :         OGRSpatialReference oSRSLatLon;
     538           1 :         oSRSLatLon.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     539           1 :         oSRSLatLon.SetGeogCS("unnamed ellipse", "unknown", "unnamed",
     540             :                              dfEquatorialRadius, dfInvFlattening, "Greenwich",
     541             :                              0.0, "degree", 0.0174532925199433);
     542             : 
     543             :         OGRCoordinateTransformation *poTransform =
     544           1 :             OGRCreateCoordinateTransformation(&oSRSLatLon, &m_oSRS);
     545             : 
     546           1 :         const double dfLon2 = oPositionX2.first;
     547           1 :         const double dfLat2 = oPositionY2.second;
     548             : 
     549           1 :         double dfX = dfCenterLon;
     550           1 :         double dfY = dfCenterLat;
     551           1 :         if (poTransform == nullptr || !poTransform->Transform(1, &dfX, &dfY))
     552           0 :             CPLError(CE_Failure, CPLE_None, "Transformation Failed");
     553             : 
     554           1 :         double dfX2 = dfLon2;
     555           1 :         double dfY2 = dfLat2;
     556           1 :         if (poTransform == nullptr || !poTransform->Transform(1, &dfX2, &dfY2))
     557           0 :             CPLError(CE_Failure, CPLE_None, "Transformation Failed");
     558             : 
     559           1 :         m_gt[0] = dfX - (dfRadarLocX * (dfX2 - dfX));
     560           1 :         m_gt[1] = dfX2 - dfX;
     561           1 :         m_gt[2] = 0.0;
     562           1 :         m_gt[3] = dfY + (dfRadarLocY * (dfY2 - dfY));
     563           1 :         m_gt[4] = 0.0;
     564           1 :         m_gt[5] = -1 * (dfY2 - dfY);
     565             : 
     566           1 :         delete poTransform;
     567             :     }
     568           0 :     else if (EQUAL(aszProjections[nProjectionCode], "Azimutal equidistant"))
     569             :     {
     570           0 :         m_oSRS.SetGeogCS("unnamed ellipse", "unknown", "unnamed",
     571             :                          dfEquatorialRadius, dfInvFlattening, "Greenwich", 0.0,
     572             :                          "degree", 0.0174532925199433);
     573           0 :         m_oSRS.SetAE(dfProjRefLat, dfProjRefLon, 0.0, 0.0);
     574             : 
     575           0 :         m_gt[0] = -1 * (dfRadarLocX * dfScaleX);
     576           0 :         m_gt[1] = dfScaleX;
     577           0 :         m_gt[2] = 0.0;
     578           0 :         m_gt[3] = dfRadarLocY * dfScaleY;
     579           0 :         m_gt[4] = 0.0;
     580           0 :         m_gt[5] = -1 * dfScaleY;
     581             :         // When the projection is different from Mercator or Azimutal
     582             :         // equidistant, we set a standard geotransform.
     583             :     }
     584             :     else
     585             :     {
     586           0 :         m_gt[0] = -1 * (dfRadarLocX * dfScaleX);
     587           0 :         m_gt[1] = dfScaleX;
     588           0 :         m_gt[2] = 0.0;
     589           0 :         m_gt[3] = dfRadarLocY * dfScaleY;
     590           0 :         m_gt[4] = 0.0;
     591           0 :         m_gt[5] = -1 * dfScaleY;
     592             :     }
     593             : }
     594             : 
     595             : /******************************************************************************/
     596             : /* The geotransform in Mercator projection must be calculated transforming    */
     597             : /* distance to degrees over the ellipsoid, using Vincenty's formula.          */
     598             : /* The following method is ported from a version for Javascript by Chris      */
     599             : /* Veness distributed under a CC-BY 3.0 licence, whose conditions is that the */
     600             : /* following copyright notice is retained as well as the link to :            */
     601             : /* http://www.movable-type.co.uk/scripts/latlong-vincenty-direct.html         */
     602             : /******************************************************************************/
     603             : 
     604             : /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
     605             :  * - - - - - - - -  */
     606             : /* Vincenty Direct Solution of Geodesics on the Ellipsoid (c) Chris Veness
     607             :  * 2005-2012              */
     608             : /*                                                                                                */
     609             : /* from: Vincenty direct formula - T Vincenty, "Direct and Inverse Solutions of
     610             :  * Geodesics on the  */
     611             : /*       Ellipsoid with application of nested equations", Survey Review, vol
     612             :  * XXII no 176, 1975    */
     613             : /*       http://www.ngs.noaa.gov/PUBS_LIB/inverse.pdf */
     614             : /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
     615             :  * - - - - - - - -  */
     616             : 
     617           2 : bool IRISDataset::GeodesicCalculation(double fLat, double fLon, double fAngle,
     618             :                                       double fDist, double fEquatorialRadius,
     619             :                                       double fPolarRadius, double fFlattening,
     620             :                                       std::pair<double, double> &oOutPair)
     621             : {
     622           2 :     const double dfAlpha1 = DEG2RAD * fAngle;
     623           2 :     const double dfSinAlpha1 = sin(dfAlpha1);
     624           2 :     const double dfCosAlpha1 = cos(dfAlpha1);
     625             : 
     626           2 :     const double dfTanU1 = (1 - fFlattening) * tan(fLat * DEG2RAD);
     627           2 :     const double dfCosU1 = 1 / sqrt((1 + dfTanU1 * dfTanU1));
     628           2 :     const double dfSinU1 = dfTanU1 * dfCosU1;
     629             : 
     630           2 :     const double dfSigma1 = atan2(dfTanU1, dfCosAlpha1);
     631           2 :     const double dfSinAlpha = dfCosU1 * dfSinAlpha1;
     632           2 :     const double dfCosSqAlpha = 1 - dfSinAlpha * dfSinAlpha;
     633           2 :     const double dfUSq =
     634           2 :         dfCosSqAlpha *
     635           2 :         (fEquatorialRadius * fEquatorialRadius - fPolarRadius * fPolarRadius) /
     636           2 :         (fPolarRadius * fPolarRadius);
     637           2 :     const double dfA =
     638             :         1 +
     639           2 :         dfUSq / 16384 * (4096 + dfUSq * (-768 + dfUSq * (320 - 175 * dfUSq)));
     640           2 :     const double dfB =
     641           2 :         dfUSq / 1024 * (256 + dfUSq * (-128 + dfUSq * (74 - 47 * dfUSq)));
     642             : 
     643           2 :     double dfSigma = fDist / (fPolarRadius * dfA);
     644           2 :     double dfSigmaP = 2 * M_PI;
     645             : 
     646           2 :     double dfSinSigma = 0.0;
     647           2 :     double dfCosSigma = 0.0;
     648           2 :     double dfCos2SigmaM = 0.0;
     649             : 
     650           2 :     int nIter = 0;
     651           4 :     while (fabs(dfSigma - dfSigmaP) > 1e-12)
     652             :     {
     653           2 :         dfCos2SigmaM = cos(2 * dfSigma1 + dfSigma);
     654           2 :         dfSinSigma = sin(dfSigma);
     655           2 :         dfCosSigma = cos(dfSigma);
     656           2 :         const double dfDeltaSigma =
     657           2 :             dfB * dfSinSigma *
     658           2 :             (dfCos2SigmaM +
     659           2 :              dfB / 4 *
     660           2 :                  (dfCosSigma * (-1 + 2 * dfCos2SigmaM * dfCos2SigmaM) -
     661           2 :                   dfB / 6 * dfCos2SigmaM * (-3 + 4 * dfSinSigma * dfSinSigma) *
     662           2 :                       (-3 + 4 * dfCos2SigmaM * dfCos2SigmaM)));
     663           2 :         dfSigmaP = dfSigma;
     664           2 :         dfSigma = fDist / (fPolarRadius * dfA) + dfDeltaSigma;
     665           2 :         nIter++;
     666           2 :         if (nIter == 100)
     667           0 :             return false;
     668             :     }
     669             : 
     670           2 :     const double dfTmp =
     671           2 :         dfSinU1 * dfSinSigma - dfCosU1 * dfCosSigma * dfCosAlpha1;
     672           2 :     const double dfLat2 = atan2(
     673           2 :         dfSinU1 * dfCosSigma + dfCosU1 * dfSinSigma * dfCosAlpha1,
     674           2 :         (1 - fFlattening) * sqrt(dfSinAlpha * dfSinAlpha + dfTmp * dfTmp));
     675             :     const double dfLambda =
     676           2 :         atan2(dfSinSigma * dfSinAlpha1,
     677           2 :               dfCosU1 * dfCosSigma - dfSinU1 * dfSinSigma * dfCosAlpha1);
     678           2 :     const double dfC = fFlattening / 16 * dfCosSqAlpha *
     679           2 :                        (4 + fFlattening * (4 - 3 * dfCosSqAlpha));
     680           2 :     const double dfL =
     681             :         dfLambda -
     682           2 :         (1 - dfC) * fFlattening * dfSinAlpha *
     683           2 :             (dfSigma +
     684           2 :              dfC * dfSinSigma *
     685           2 :                  (dfCos2SigmaM +
     686           2 :                   dfC * dfCosSigma * (-1 + 2 * dfCos2SigmaM * dfCos2SigmaM)));
     687           2 :     double dfLon2 = fLon * DEG2RAD + dfL;
     688             : 
     689           2 :     if (dfLon2 > M_PI)
     690           0 :         dfLon2 = dfLon2 - 2 * M_PI;
     691           2 :     if (dfLon2 < -1 * M_PI)
     692           0 :         dfLon2 = dfLon2 + 2 * M_PI;
     693             : 
     694           2 :     oOutPair = std::pair<double, double>(dfLon2 * RAD2DEG, dfLat2 * RAD2DEG);
     695             : 
     696           2 :     return true;
     697             : }
     698             : 
     699             : /************************************************************************/
     700             : /*                          GetGeoTransform()                           */
     701             : /************************************************************************/
     702             : 
     703           1 : CPLErr IRISDataset::GetGeoTransform(GDALGeoTransform &gt) const
     704             : 
     705             : {
     706           1 :     if (!bHasLoadedProjection)
     707           0 :         LoadProjection();
     708           1 :     gt = m_gt;
     709           1 :     return CE_None;
     710             : }
     711             : 
     712             : /************************************************************************/
     713             : /*                          GetSpatialRef()                             */
     714             : /************************************************************************/
     715             : 
     716           1 : const OGRSpatialReference *IRISDataset::GetSpatialRef() const
     717             : {
     718           1 :     if (!bHasLoadedProjection)
     719           1 :         LoadProjection();
     720           1 :     return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
     721             : }
     722             : 
     723             : /************************************************************************/
     724             : /*                              Identify()                              */
     725             : /************************************************************************/
     726             : 
     727       56416 : int IRISDataset::Identify(GDALOpenInfo *poOpenInfo)
     728             : 
     729             : {
     730             :     /* -------------------------------------------------------------------- */
     731             :     /*      Confirm that the file is an IRIS file                           */
     732             :     /* -------------------------------------------------------------------- */
     733       56416 :     if (poOpenInfo->nHeaderBytes < 640)
     734       54412 :         return FALSE;
     735             : 
     736        2004 :     const short nId1 = CPL_LSBSINT16PTR(poOpenInfo->pabyHeader);
     737        2004 :     const short nId2 = CPL_LSBSINT16PTR(poOpenInfo->pabyHeader + 12);
     738        2004 :     unsigned short nProductCode =
     739        2004 :         CPL_LSBUINT16PTR(poOpenInfo->pabyHeader + 12 + 12);
     740        2004 :     const short nYear = CPL_LSBSINT16PTR(poOpenInfo->pabyHeader + 26 + 12);
     741        2004 :     const short nMonth = CPL_LSBSINT16PTR(poOpenInfo->pabyHeader + 28 + 12);
     742        2004 :     const short nDay = CPL_LSBSINT16PTR(poOpenInfo->pabyHeader + 30 + 12);
     743             : 
     744             :     // Check if the two headers are 27 (product hdr) & 26 (product
     745             :     // configuration), and other metadata
     746        2010 :     if (!(nId1 == 27 && nId2 == 26 && nProductCode > 0 &&
     747           6 :           nProductCode < CPL_ARRAYSIZE(aszProductNames) && nYear >= 1900 &&
     748           6 :           nYear < 2100 && nMonth >= 1 && nMonth <= 12 && nDay >= 1 &&
     749             :           nDay <= 31))
     750        1998 :         return FALSE;
     751             : 
     752           6 :     return TRUE;
     753             : }
     754             : 
     755             : /************************************************************************/
     756             : /*                             FillString()                             */
     757             : /************************************************************************/
     758             : 
     759          21 : static void FillString(char *szBuffer, size_t nBufferSize, void *pSrcBuffer)
     760             : {
     761         285 :     for (size_t i = 0; i < nBufferSize - 1; i++)
     762         264 :         szBuffer[i] = static_cast<char *>(pSrcBuffer)[i];
     763          21 :     szBuffer[nBufferSize - 1] = '\0';
     764          21 : }
     765             : 
     766             : /************************************************************************/
     767             : /*                                Open()                                */
     768             : /************************************************************************/
     769             : 
     770           3 : GDALDataset *IRISDataset::Open(GDALOpenInfo *poOpenInfo)
     771             : 
     772             : {
     773           3 :     if (!Identify(poOpenInfo) || poOpenInfo->fpL == nullptr)
     774           0 :         return nullptr;
     775             :     /* -------------------------------------------------------------------- */
     776             :     /*      Confirm the requested access is supported.                      */
     777             :     /* -------------------------------------------------------------------- */
     778           3 :     if (poOpenInfo->eAccess == GA_Update)
     779             :     {
     780           0 :         ReportUpdateNotSupportedByDriver("IRIS");
     781           0 :         return nullptr;
     782             :     }
     783             : 
     784             :     /* -------------------------------------------------------------------- */
     785             :     /*      Create a corresponding GDALDataset.                             */
     786             :     /* -------------------------------------------------------------------- */
     787           3 :     IRISDataset *poDS = new IRISDataset();
     788           3 :     poDS->fp = poOpenInfo->fpL;
     789           3 :     poOpenInfo->fpL = nullptr;
     790             : 
     791             :     /* -------------------------------------------------------------------- */
     792             :     /*      Read the header.                                                */
     793             :     /* -------------------------------------------------------------------- */
     794           3 :     VSIFReadL(poDS->abyHeader, 1, 640, poDS->fp);
     795           3 :     const int nXSize = CPL_LSBSINT32PTR(poDS->abyHeader + 100 + 12);
     796           3 :     const int nYSize = CPL_LSBSINT32PTR(poDS->abyHeader + 104 + 12);
     797           3 :     const int nNumBands = CPL_LSBSINT32PTR(poDS->abyHeader + 108 + 12);
     798             : 
     799           3 :     poDS->nRasterXSize = nXSize;
     800             : 
     801           3 :     poDS->nRasterYSize = nYSize;
     802           3 :     if (poDS->nRasterXSize <= 0 || poDS->nRasterYSize <= 0)
     803             :     {
     804           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid dimensions : %d x %d",
     805             :                  poDS->nRasterXSize, poDS->nRasterYSize);
     806           0 :         delete poDS;
     807           0 :         return nullptr;
     808             :     }
     809             : 
     810           3 :     if (!GDALCheckBandCount(nNumBands, TRUE))
     811             :     {
     812           0 :         delete poDS;
     813           0 :         return nullptr;
     814             :     }
     815             : 
     816             :     /* -------------------------------------------------------------------- */
     817             :     /*      Setting the Metadata                                            */
     818             :     /* -------------------------------------------------------------------- */
     819             :     // See point 3.2.26 at page 3.12 of the manual.
     820           3 :     poDS->nProductCode = CPL_LSBUINT16PTR(poDS->abyHeader + 12 + 12);
     821           3 :     poDS->SetMetadataItem("PRODUCT_ID",
     822           6 :                           CPLString().Printf("%d", poDS->nProductCode));
     823           3 :     if (poDS->nProductCode >= CPL_ARRAYSIZE(aszProductNames))
     824             :     {
     825           0 :         delete poDS;
     826           0 :         return nullptr;
     827             :     }
     828             : 
     829           3 :     poDS->SetMetadataItem("PRODUCT", aszProductNames[poDS->nProductCode]);
     830             : 
     831           3 :     poDS->nDataTypeCode = CPL_LSBUINT16PTR(poDS->abyHeader + 130 + 12);
     832           3 :     if (poDS->nDataTypeCode >= CPL_ARRAYSIZE(aszDataTypeCodes))
     833             :     {
     834           0 :         delete poDS;
     835           0 :         return nullptr;
     836             :     }
     837           3 :     poDS->SetMetadataItem("DATA_TYPE_CODE",
     838           3 :                           aszDataTypeCodes[poDS->nDataTypeCode]);
     839             : 
     840           3 :     if (poDS->nDataTypeCode >= CPL_ARRAYSIZE(aszDataTypes))
     841             :     {
     842           0 :         delete poDS;
     843           0 :         return nullptr;
     844             :     }
     845           3 :     poDS->SetMetadataItem("DATA_TYPE", aszDataTypes[poDS->nDataTypeCode]);
     846             : 
     847           3 :     const unsigned short nDataTypeInputCode =
     848           3 :         CPL_LSBUINT16PTR(poDS->abyHeader + 144 + 12);
     849           3 :     if (nDataTypeInputCode >= CPL_ARRAYSIZE(aszDataTypeCodes))
     850             :     {
     851           0 :         delete poDS;
     852           0 :         return nullptr;
     853             :     }
     854           3 :     poDS->SetMetadataItem("DATA_TYPE_INPUT_CODE",
     855           3 :                           aszDataTypeCodes[nDataTypeInputCode]);
     856             : 
     857           3 :     const unsigned short nDataTypeInput =
     858           3 :         CPL_LSBUINT16PTR(poDS->abyHeader + 144 + 12);
     859           3 :     if (nDataTypeInput >= CPL_ARRAYSIZE(aszDataTypes))
     860             :     {
     861           0 :         delete poDS;
     862           0 :         return nullptr;
     863             :     }
     864           3 :     poDS->SetMetadataItem("DATA_TYPE_INPUT", aszDataTypes[nDataTypeInput]);
     865             : 
     866           3 :     poDS->nProjectionCode =
     867           3 :         *static_cast<unsigned char *>(poDS->abyHeader + 146 + 12);
     868           3 :     if (poDS->nProjectionCode >= CPL_ARRAYSIZE(aszProjections))
     869             :     {
     870           0 :         delete poDS;
     871           0 :         return nullptr;
     872             :     }
     873             : 
     874             :     // Times.
     875             :     {
     876           3 :         const int nSeconds = CPL_LSBSINT32PTR(poDS->abyHeader + 20 + 12);
     877             : 
     878           3 :         const int nHour = (nSeconds - (nSeconds % 3600)) / 3600;
     879           3 :         const int nMinute =
     880           3 :             ((nSeconds - nHour * 3600) - (nSeconds - nHour * 3600) % 60) / 60;
     881           3 :         const int nSecond = nSeconds - nHour * 3600 - nMinute * 60;
     882             : 
     883           3 :         const short nYear = CPL_LSBSINT16PTR(poDS->abyHeader + 26 + 12);
     884           3 :         const short nMonth = CPL_LSBSINT16PTR(poDS->abyHeader + 28 + 12);
     885           3 :         const short nDay = CPL_LSBSINT16PTR(poDS->abyHeader + 30 + 12);
     886             : 
     887           3 :         poDS->SetMetadataItem("TIME_PRODUCT_GENERATED",
     888           3 :                               CPLString().Printf("%d-%02d-%02d %02d:%02d:%02d",
     889             :                                                  nYear, nMonth, nDay, nHour,
     890           3 :                                                  nMinute, nSecond));
     891             :     }
     892             : 
     893             :     {
     894           3 :         const int nSeconds = CPL_LSBSINT32PTR(poDS->abyHeader + 32 + 12);
     895             : 
     896           3 :         const int nHour = (nSeconds - (nSeconds % 3600)) / 3600;
     897           3 :         const int nMinute =
     898           3 :             ((nSeconds - nHour * 3600) - (nSeconds - nHour * 3600) % 60) / 60;
     899           3 :         const int nSecond = nSeconds - nHour * 3600 - nMinute * 60;
     900             : 
     901           3 :         const short nYear = CPL_LSBSINT16PTR(poDS->abyHeader + 26 + 12);
     902           3 :         const short nMonth = CPL_LSBSINT16PTR(poDS->abyHeader + 28 + 12);
     903           3 :         const short nDay = CPL_LSBSINT16PTR(poDS->abyHeader + 30 + 12);
     904             : 
     905           3 :         poDS->SetMetadataItem("TIME_INPUT_INGEST_SWEEP",
     906           3 :                               CPLString().Printf("%d-%02d-%02d %02d:%02d:%02d",
     907             :                                                  nYear, nMonth, nDay, nHour,
     908           3 :                                                  nMinute, nSecond));
     909             :     }
     910             : 
     911             :     // Site and task information.
     912             : 
     913           3 :     char szSiteName[17] = {};  // Must have one extra char for string end.
     914           3 :     char szVersionName[9] = {};
     915             : 
     916           3 :     FillString(szSiteName, sizeof(szSiteName), poDS->abyHeader + 320 + 12);
     917           3 :     FillString(szVersionName, sizeof(szVersionName),
     918           3 :                poDS->abyHeader + 16 + 320 + 12);
     919           3 :     poDS->SetMetadataItem("PRODUCT_SITE_NAME", szSiteName);
     920           3 :     poDS->SetMetadataItem("PRODUCT_SITE_IRIS_VERSION", szVersionName);
     921             : 
     922           3 :     FillString(szSiteName, sizeof(szSiteName), poDS->abyHeader + 90 + 320 + 12);
     923           3 :     FillString(szVersionName, sizeof(szVersionName),
     924           3 :                poDS->abyHeader + 24 + 320 + 12);
     925           3 :     poDS->SetMetadataItem("INGEST_SITE_NAME", szSiteName);
     926           3 :     poDS->SetMetadataItem("INGEST_SITE_IRIS_VERSION", szVersionName);
     927             : 
     928           3 :     FillString(szSiteName, sizeof(szSiteName), poDS->abyHeader + 74 + 320 + 12);
     929           3 :     poDS->SetMetadataItem("INGEST_HARDWARE_NAME", szSiteName);
     930             : 
     931           3 :     char szConfigFile[13] = {};
     932           3 :     FillString(szConfigFile, sizeof(szConfigFile), poDS->abyHeader + 62 + 12);
     933           3 :     poDS->SetMetadataItem("PRODUCT_CONFIGURATION_NAME", szConfigFile);
     934             : 
     935           3 :     char szTaskName[13] = {};
     936           3 :     FillString(szTaskName, sizeof(szTaskName), poDS->abyHeader + 74 + 12);
     937           3 :     poDS->SetMetadataItem("TASK_NAME", szTaskName);
     938             : 
     939           3 :     const short nRadarHeight =
     940           3 :         CPL_LSBSINT16PTR(poDS->abyHeader + 284 + 320 + 12);
     941           3 :     poDS->SetMetadataItem("RADAR_HEIGHT",
     942           6 :                           CPLString().Printf("%d m", nRadarHeight));
     943             :     // Ground height over the sea level.
     944           3 :     const short nGroundHeight =
     945           3 :         CPL_LSBSINT16PTR(poDS->abyHeader + 118 + 320 + 12);
     946           3 :     poDS->SetMetadataItem(
     947             :         "GROUND_HEIGHT",
     948           6 :         CPLString().Printf("%d m", nRadarHeight - nGroundHeight));
     949             : 
     950           3 :     unsigned short nFlags = CPL_LSBUINT16PTR(poDS->abyHeader + 86 + 12);
     951             :     // Get eleventh bit.
     952           3 :     nFlags = nFlags << 4;
     953           3 :     nFlags = nFlags >> 15;
     954           3 :     if (nFlags == 1)
     955             :     {
     956           1 :         poDS->SetMetadataItem("COMPOSITED_PRODUCT", "YES");
     957           1 :         const unsigned int compositedMask =
     958           1 :             CPL_LSBUINT32PTR(poDS->abyHeader + 232 + 320 + 12);
     959           1 :         poDS->SetMetadataItem("COMPOSITED_PRODUCT_MASK",
     960           2 :                               CPLString().Printf("0x%08x", compositedMask));
     961             :     }
     962             :     else
     963             :     {
     964           2 :         poDS->SetMetadataItem("COMPOSITED_PRODUCT", "NO");
     965             :     }
     966             : 
     967             :     // Wave values.
     968           3 :     poDS->SetMetadataItem(
     969           3 :         "PRF", CPLString().Printf("%d Hz", CPL_LSBSINT32PTR(poDS->abyHeader +
     970           3 :                                                             120 + 320 + 12)));
     971           3 :     poDS->SetMetadataItem(
     972             :         "WAVELENGTH",
     973           3 :         CPLString().Printf("%4.2f cm",
     974           3 :                            CPL_LSBSINT32PTR(poDS->abyHeader + 148 + 320 + 12) /
     975           3 :                                100.0f));
     976           3 :     const unsigned short nPolarizationType =
     977           3 :         CPL_LSBUINT16PTR(poDS->abyHeader + 172 + 320 + 12);
     978             : 
     979             :     // See section 3.3.37 & 3.2.54.
     980           3 :     float fNyquist = (CPL_LSBSINT32PTR(poDS->abyHeader + 120 + 320 + 12)) *
     981           3 :                      (static_cast<float>(
     982           3 :                           CPL_LSBSINT32PTR(poDS->abyHeader + 148 + 320 + 12)) /
     983             :                       10000.0f) /
     984             :                      4.0f;
     985           3 :     if (nPolarizationType == 1)
     986           0 :         fNyquist = fNyquist * 2.0f;
     987           3 :     else if (nPolarizationType == 2)
     988           0 :         fNyquist = fNyquist * 3.0f;
     989           3 :     else if (nPolarizationType == 3)
     990           0 :         fNyquist = fNyquist * 4.0f;
     991           3 :     poDS->fNyquistVelocity = fNyquist;
     992           3 :     poDS->SetMetadataItem("NYQUIST_VELOCITY",
     993           6 :                           CPLString().Printf("%.2f m/s", fNyquist));
     994             : 
     995             :     // Product dependent metadata (stored in 80 bytes from 162 bytes
     996             :     // at the product header) See point 3.2.30 at page 3.19 of the
     997             :     // manual.
     998             :     // See point 3.2.25 at page 3.12 of the manual.
     999           3 :     if (EQUAL(aszProductNames[poDS->nProductCode], "PPI"))
    1000             :     {
    1001             :         // Degrees = 360 * (Binary Angle)*2^N
    1002           2 :         const float fElevation =
    1003           2 :             CPL_LSBSINT16PTR(poDS->abyHeader + 164 + 12) * 360.0f / 65536.0f;
    1004             : 
    1005           2 :         poDS->SetMetadataItem("PPI_ELEVATION_ANGLE",
    1006           4 :                               CPLString().Printf("%f", fElevation));
    1007           2 :         if (EQUAL(aszDataTypeCodes[poDS->nDataTypeCode], "dBZ"))
    1008           2 :             poDS->SetMetadataItem("DATA_TYPE_UNITS", "dBZ");
    1009             :         else
    1010           0 :             poDS->SetMetadataItem("DATA_TYPE_UNITS", "m/s");
    1011             :         // See point 3.2.2 at page 3.2 of the manual.
    1012             :     }
    1013           1 :     else if (EQUAL(aszProductNames[poDS->nProductCode], "CAPPI"))
    1014             :     {
    1015           1 :         const float fElevation =
    1016           1 :             CPL_LSBSINT32PTR(poDS->abyHeader + 4 + 164 + 12) / 100.0f;
    1017           1 :         poDS->SetMetadataItem("CAPPI_BOTTOM_HEIGHT",
    1018           2 :                               CPLString().Printf("%.1f m", fElevation));
    1019           1 :         const float fAzimuthSmoothingForShear =
    1020           1 :             CPL_LSBUINT16PTR(poDS->abyHeader + 10 + 164 + 12) * 360.0f /
    1021             :             65536.0f;
    1022           1 :         poDS->SetMetadataItem(
    1023             :             "AZIMUTH_SMOOTHING_FOR_SHEAR",
    1024           2 :             CPLString().Printf("%.1f", fAzimuthSmoothingForShear));
    1025           1 :         const unsigned int nMaxAgeVVPCorrection =
    1026           1 :             CPL_LSBUINT32PTR(poDS->abyHeader + 24 + 164 + 12);
    1027           1 :         poDS->SetMetadataItem("MAX_AGE_FOR_SHEAR_VVP_CORRECTION",
    1028           2 :                               CPLString().Printf("%d s", nMaxAgeVVPCorrection));
    1029           1 :         if (EQUAL(aszDataTypeCodes[poDS->nDataTypeCode], "dBZ"))
    1030           1 :             poDS->SetMetadataItem("DATA_TYPE_UNITS", "dBZ");
    1031             :         else
    1032           0 :             poDS->SetMetadataItem("DATA_TYPE_UNITS", "m/s");
    1033             :         // See point 3.2.32 at page 3.19 of the manual.
    1034             :     }
    1035           0 :     else if (EQUAL(aszProductNames[poDS->nProductCode], "RAIN1") ||
    1036           0 :              EQUAL(aszProductNames[poDS->nProductCode], "RAINN"))
    1037             :     {
    1038           0 :         const short nNumProducts =
    1039           0 :             CPL_LSBSINT16PTR(poDS->abyHeader + 170 + 320 + 12);
    1040           0 :         poDS->SetMetadataItem("NUM_FILES_USED",
    1041           0 :                               CPLString().Printf("%d", nNumProducts));
    1042             : 
    1043           0 :         const float fMinZAcum =
    1044           0 :             (CPL_LSBUINT32PTR(poDS->abyHeader + 164 + 12) - 32768.0f) /
    1045             :             10000.0f;
    1046           0 :         poDS->SetMetadataItem("MINIMUM_Z_TO_ACCUMULATE",
    1047           0 :                               CPLString().Printf("%f", fMinZAcum));
    1048             : 
    1049           0 :         const unsigned short nSecondsOfAccumulation =
    1050           0 :             CPL_LSBUINT16PTR(poDS->abyHeader + 6 + 164 + 12);
    1051           0 :         poDS->SetMetadataItem(
    1052             :             "SECONDS_OF_ACCUMULATION",
    1053           0 :             CPLString().Printf("%d s", nSecondsOfAccumulation));
    1054             : 
    1055           0 :         const unsigned int nSpanInputFiles =
    1056           0 :             CPL_LSBUINT32PTR(poDS->abyHeader + 24 + 164 + 12);
    1057           0 :         poDS->SetMetadataItem("SPAN_OF_INPUT_FILES",
    1058           0 :                               CPLString().Printf("%d s", nSpanInputFiles));
    1059           0 :         poDS->SetMetadataItem("DATA_TYPE_UNITS", "mm");
    1060             : 
    1061           0 :         char szInputProductName[13] = "";
    1062           0 :         for (int k = 0; k < 12; k++)
    1063           0 :             szInputProductName[k] =
    1064           0 :                 *reinterpret_cast<char *>(poDS->abyHeader + k + 12 + 164 + 12);
    1065             : 
    1066           0 :         poDS->SetMetadataItem("INPUT_PRODUCT_NAME",
    1067           0 :                               CPLString().Printf("%s", szInputProductName));
    1068             : 
    1069           0 :         if (EQUAL(aszProductNames[poDS->nProductCode], "RAINN"))
    1070           0 :             poDS->SetMetadataItem(
    1071             :                 "NUM_HOURS_ACCUMULATE",
    1072           0 :                 CPLString().Printf(
    1073           0 :                     "%d", CPL_LSBUINT16PTR(poDS->abyHeader + 10 + 164 + 12)));
    1074             : 
    1075             :         // See point 3.2.73 at page 3.36 of the manual.
    1076             :     }
    1077           0 :     else if (EQUAL(aszProductNames[poDS->nProductCode], "VIL"))
    1078             :     {
    1079           0 :         const float fBottomHeightInterval =
    1080           0 :             CPL_LSBSINT32PTR(poDS->abyHeader + 4 + 164 + 12) / 100.0f;
    1081             :         // TYPO in metadata key: FIXME ?
    1082           0 :         poDS->SetMetadataItem(
    1083             :             "BOTTOM_OF_HEIGTH_INTERVAL",
    1084           0 :             CPLString().Printf("%.1f m", fBottomHeightInterval));
    1085           0 :         const float fTopHeightInterval =
    1086           0 :             CPL_LSBSINT32PTR(poDS->abyHeader + 8 + 164 + 12) / 100.0f;
    1087             :         // TYPO in metadata key: FIXME ?
    1088           0 :         poDS->SetMetadataItem("TOP_OF_HEIGTH_INTERVAL",
    1089           0 :                               CPLString().Printf("%.1f m", fTopHeightInterval));
    1090           0 :         poDS->SetMetadataItem("VIL_DENSITY_NOT_AVAILABLE_VALUE", "-1");
    1091           0 :         poDS->SetMetadataItem("DATA_TYPE_UNITS", "mm");
    1092             :         // See point 3.2.68 at page 3.36 of the manual
    1093             :     }
    1094           0 :     else if (EQUAL(aszProductNames[poDS->nProductCode], "TOPS"))
    1095             :     {
    1096           0 :         const float fZThreshold =
    1097           0 :             CPL_LSBSINT16PTR(poDS->abyHeader + 4 + 164 + 12) / 16.0f;
    1098           0 :         poDS->SetMetadataItem("Z_THRESHOLD",
    1099           0 :                               CPLString().Printf("%.1f dBZ", fZThreshold));
    1100           0 :         poDS->SetMetadataItem("ECHO_TOPS_NOT_AVAILABLE_VALUE", "-1");
    1101           0 :         poDS->SetMetadataItem("DATA_TYPE_UNITS", "km");
    1102             :         // See point 3.2.20 at page 3.10 of the manual.
    1103             :     }
    1104           0 :     else if (EQUAL(aszProductNames[poDS->nProductCode], "MAX"))
    1105             :     {
    1106           0 :         const float fBottomInterval =
    1107           0 :             CPL_LSBSINT32PTR(poDS->abyHeader + 4 + 164 + 12) / 100.0f;
    1108           0 :         poDS->SetMetadataItem("BOTTOM_OF_INTERVAL",
    1109           0 :                               CPLString().Printf("%.1f m", fBottomInterval));
    1110           0 :         const float fTopInterval =
    1111           0 :             CPL_LSBSINT32PTR(poDS->abyHeader + 8 + 164 + 12) / 100.0f;
    1112           0 :         poDS->SetMetadataItem("TOP_OF_INTERVAL",
    1113           0 :                               CPLString().Printf("%.1f m", fTopInterval));
    1114           0 :         const int nNumPixelsSidePanels =
    1115           0 :             CPL_LSBSINT32PTR(poDS->abyHeader + 12 + 164 + 12);
    1116           0 :         poDS->SetMetadataItem("NUM_PIXELS_SIDE_PANELS",
    1117           0 :                               CPLString().Printf("%d", nNumPixelsSidePanels));
    1118           0 :         const short nHorizontalSmootherSidePanels =
    1119           0 :             CPL_LSBSINT16PTR(poDS->abyHeader + 16 + 164 + 12);
    1120           0 :         poDS->SetMetadataItem(
    1121             :             "HORIZONTAL_SMOOTHER_SIDE_PANELS",
    1122           0 :             CPLString().Printf("%d", nHorizontalSmootherSidePanels));
    1123           0 :         const short nVerticalSmootherSidePanels =
    1124           0 :             CPL_LSBSINT16PTR(poDS->abyHeader + 18 + 164 + 12);
    1125           0 :         poDS->SetMetadataItem(
    1126             :             "VERTICAL_SMOOTHER_SIDE_PANELS",
    1127           0 :             CPLString().Printf("%d", nVerticalSmootherSidePanels));
    1128             :     }
    1129             : 
    1130             :     /* -------------------------------------------------------------------- */
    1131             :     /*      Create band information objects.                                */
    1132             :     /* -------------------------------------------------------------------- */
    1133           3 :     assert(nNumBands <= INT_MAX - 1);
    1134           6 :     for (int iBandNum = 1; iBandNum <= nNumBands; iBandNum++)
    1135             :     {
    1136           3 :         poDS->SetBand(iBandNum, new IRISRasterBand(poDS, iBandNum));
    1137             : 
    1138           3 :         poDS->GetRasterBand(iBandNum)->SetNoDataValue(-9999);
    1139             :         // Calculating the band height to include it in the band metadata.  Only
    1140             :         // for the CAPPI product.
    1141           3 :         if (EQUAL(aszProductNames[poDS->nProductCode], "CAPPI"))
    1142             :         {
    1143           1 :             const float fScaleZ =
    1144           1 :                 CPL_LSBSINT32PTR(poDS->abyHeader + 96 + 12) / 100.0f;
    1145           1 :             const float fOffset =
    1146           1 :                 CPL_LSBSINT32PTR(poDS->abyHeader + 4 + 164 + 12) / 100.0f;
    1147             : 
    1148           2 :             poDS->GetRasterBand(iBandNum)->SetMetadataItem(
    1149           1 :                 "height", CPLString().Printf(
    1150           1 :                               "%.0f m", fOffset + fScaleZ * (iBandNum - 1)));
    1151             :         }
    1152             :     }
    1153             :     /* -------------------------------------------------------------------- */
    1154             :     /*      Initialize any PAM information.                                 */
    1155             :     /* -------------------------------------------------------------------- */
    1156           3 :     poDS->SetDescription(poOpenInfo->pszFilename);
    1157           3 :     poDS->TryLoadXML();
    1158             : 
    1159             :     /* -------------------------------------------------------------------- */
    1160             :     /*      Check for overviews.                                            */
    1161             :     /* -------------------------------------------------------------------- */
    1162           3 :     poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename);
    1163             : 
    1164           3 :     return poDS;
    1165             : }
    1166             : 
    1167             : /************************************************************************/
    1168             : /*                          GDALRegister_IRIS()                         */
    1169             : /************************************************************************/
    1170             : 
    1171        1911 : void GDALRegister_IRIS()
    1172             : 
    1173             : {
    1174        1911 :     if (GDALGetDriverByName("IRIS") != nullptr)
    1175         282 :         return;
    1176             : 
    1177        1629 :     GDALDriver *poDriver = new GDALDriver();
    1178             : 
    1179        1629 :     poDriver->SetDescription("IRIS");
    1180        1629 :     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
    1181        1629 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
    1182        1629 :                               "IRIS data (.PPI, .CAPPi etc)");
    1183        1629 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/iris.html");
    1184        1629 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "ppi");
    1185        1629 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
    1186             : 
    1187        1629 :     poDriver->pfnOpen = IRISDataset::Open;
    1188        1629 :     poDriver->pfnIdentify = IRISDataset::Identify;
    1189             : 
    1190        1629 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    1191             : }

Generated by: LCOV version 1.14