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

Generated by: LCOV version 1.14