Coverage Report

Created: 2026-06-30 08:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/iris/irisdataset.cpp
Line
Count
Source
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
IRISRasterBand::IRISRasterBand(IRISDataset *poDSIn, int nBandIn)
204
3.24k
    : pszRecord(nullptr), bBufferAllocFailed(false)
205
3.24k
{
206
3.24k
    poDS = poDSIn;
207
3.24k
    nBand = nBandIn;
208
3.24k
    eDataType = GDT_Float32;
209
3.24k
    nBlockXSize = poDS->GetRasterXSize();
210
3.24k
    nBlockYSize = 1;
211
3.24k
}
212
213
IRISRasterBand::~IRISRasterBand()
214
3.24k
{
215
3.24k
    VSIFree(pszRecord);
216
3.24k
}
217
218
/************************************************************************/
219
/*                             IReadBlock()                             */
220
/************************************************************************/
221
222
CPLErr IRISRasterBand::IReadBlock(int /* nBlockXOff */, int nBlockYOff,
223
                                  void *pImage)
224
225
15.6k
{
226
15.6k
    IRISDataset *poGDS = static_cast<IRISDataset *>(poDS);
227
228
    // Every product type has its own size. TODO: Move it like dataType.
229
15.6k
    int nDataLength = 1;
230
15.6k
    if (poGDS->nDataTypeCode == 2)
231
1.83k
        nDataLength = 1;
232
13.8k
    else if (poGDS->nDataTypeCode == 8)
233
0
        nDataLength = 2;
234
13.8k
    else if (poGDS->nDataTypeCode == 9)
235
2.08k
        nDataLength = 2;
236
11.7k
    else if (poGDS->nDataTypeCode == 37)
237
30
        nDataLength = 2;
238
11.6k
    else if (poGDS->nDataTypeCode == 33)
239
20
        nDataLength = 2;
240
11.6k
    else if (poGDS->nDataTypeCode == 32)
241
20
        nDataLength = 1;
242
243
    // We allocate space for storing a record:
244
15.6k
    if (pszRecord == nullptr)
245
138
    {
246
138
        if (bBufferAllocFailed)
247
0
            return CE_Failure;
248
249
138
        pszRecord = static_cast<unsigned char *>(
250
138
            VSI_MALLOC_VERBOSE(static_cast<size_t>(nBlockXSize) * nDataLength));
251
252
138
        if (pszRecord == nullptr)
253
0
        {
254
0
            bBufferAllocFailed = true;
255
0
            return CE_Failure;
256
0
        }
257
138
    }
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
15.6k
    VSIFSeekL(poGDS->fp,
264
15.6k
              640 +
265
15.6k
                  static_cast<vsi_l_offset>(nDataLength) *
266
15.6k
                      poGDS->GetRasterXSize() * poGDS->GetRasterYSize() *
267
15.6k
                      (this->nBand - 1) +
268
15.6k
                  static_cast<vsi_l_offset>(nBlockXSize) * nDataLength *
269
15.6k
                      (poGDS->GetRasterYSize() - 1 - nBlockYOff),
270
15.6k
              SEEK_SET);
271
272
15.6k
    if (static_cast<int>(
273
15.6k
            VSIFReadL(pszRecord, static_cast<size_t>(nBlockXSize) * nDataLength,
274
15.6k
                      1, poGDS->fp)) != 1)
275
43
        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
15.5k
    if (poGDS->nDataTypeCode == 2 || poGDS->nDataTypeCode == 1)
280
2.87k
    {
281
480k
        for (int i = 0; i < nBlockXSize; i++)
282
477k
        {
283
477k
            float fVal = ((*(pszRecord + i * nDataLength)) - 64.0f) / 2.0f;
284
477k
            if (fVal == 95.5f)
285
3.58k
                fVal = -9999.0f;
286
477k
            ((float *)pImage)[i] = fVal;
287
477k
        }
288
        // If datatype is dbZ2 or dBT2:
289
        // See point 3.3.4 at page 3.33 of the manual.
290
2.87k
    }
291
12.7k
    else if (poGDS->nDataTypeCode == 8 || poGDS->nDataTypeCode == 9)
292
2.08k
    {
293
4.16k
        for (int i = 0; i < nBlockXSize; i++)
294
2.08k
        {
295
2.08k
            float fVal =
296
2.08k
                (CPL_LSBUINT16PTR(pszRecord + i * nDataLength) - 32768.0f) /
297
2.08k
                100.0f;
298
2.08k
            if (fVal == 327.67f)
299
13
                fVal = -9999.0f;
300
2.08k
            ((float *)pImage)[i] = fVal;
301
2.08k
        }
302
        // Fliquid2 (Rain1 & Rainn products)
303
        // See point 3.3.11 at page 3.43 of the manual.
304
2.08k
    }
305
10.6k
    else if (poGDS->nDataTypeCode == 37)
306
14
    {
307
3.59k
        for (int i = 0; i < nBlockXSize; i++)
308
3.58k
        {
309
3.58k
            const unsigned short nVal =
310
3.58k
                CPL_LSBUINT16PTR(pszRecord + i * nDataLength);
311
3.58k
            const unsigned short nExp = nVal >> 12;
312
3.58k
            const unsigned short nMantissa = nVal - (nExp << 12);
313
3.58k
            float fVal2 = 0.0f;
314
3.58k
            if (nVal == 65535)
315
89
                fVal2 = -9999.0f;
316
3.49k
            else if (nExp == 0)
317
835
                fVal2 = nMantissa / 1000.0f;
318
2.66k
            else
319
2.66k
                fVal2 = ((nMantissa + 4096) << (nExp - 1)) / 1000.0f;
320
3.58k
            ((float *)pImage)[i] = fVal2;
321
3.58k
        }
322
        // VIL2 (VIL products)
323
        // See point 3.3.41 at page 3.54 of the manual.
324
14
    }
325
10.6k
    else if (poGDS->nDataTypeCode == 33)
326
9
    {
327
2.31k
        for (int i = 0; i < nBlockXSize; i++)
328
2.30k
        {
329
2.30k
            float fVal = static_cast<float>(
330
2.30k
                CPL_LSBUINT16PTR(pszRecord + i * nDataLength));
331
2.30k
            if (fVal == 65535.0f)
332
22
                ((float *)pImage)[i] = -9999.0f;
333
2.28k
            else if (fVal == 0.0f)
334
543
                ((float *)pImage)[i] = -1.0f;
335
1.73k
            else
336
1.73k
                ((float *)pImage)[i] = (fVal - 1) / 1000.0f;
337
2.30k
        }
338
        // HEIGHT (TOPS products)
339
        // See point 3.3.14 at page 3.46 of the manual.
340
9
    }
341
10.6k
    else if (poGDS->nDataTypeCode == 32)
342
20
    {
343
380
        for (int i = 0; i < nBlockXSize; i++)
344
360
        {
345
360
            unsigned char nVal = *(pszRecord + i * nDataLength);
346
360
            if (nVal == 255)
347
7
                ((float *)pImage)[i] = -9999.0f;
348
353
            else if (nVal == 0)
349
37
                ((float *)pImage)[i] = -1.0f;
350
316
            else
351
316
                ((float *)pImage)[i] = (nVal - 1.0f) / 10.0f;
352
360
        }
353
        // VEL (Velocity 1-Byte in PPI & others)
354
        // See point 3.3.37 at page 3.53 of the manual.
355
20
    }
356
10.5k
    else if (poGDS->nDataTypeCode == 3)
357
7.73k
    {
358
15.4k
        for (int i = 0; i < nBlockXSize; i++)
359
7.73k
        {
360
7.73k
            float fVal = static_cast<float>(*(pszRecord + i * nDataLength));
361
7.73k
            if (fVal == 0.0f)
362
2.18k
                fVal = -9997.0f;
363
5.55k
            else if (fVal == 1.0f)
364
23
                fVal = -9998.0f;
365
5.53k
            else if (fVal == 255.0f)
366
41
                fVal = -9999.0f;
367
5.48k
            else
368
5.48k
                fVal = poGDS->fNyquistVelocity * (fVal - 128.0f) / 127.0f;
369
7.73k
            ((float *)pImage)[i] = fVal;
370
7.73k
        }
371
        // SHEAR (1-Byte Shear)
372
        // See point 3.3.23 at page 3.39 of the manual.
373
7.73k
    }
374
2.86k
    else if (poGDS->nDataTypeCode == 35)
375
0
    {
376
0
        for (int i = 0; i < nBlockXSize; i++)
377
0
        {
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
0
            else
384
0
                fVal = (fVal - 128.0f) * 0.2f;
385
0
            ((float *)pImage)[i] = fVal;
386
0
        }
387
0
    }
388
389
15.5k
    return CE_None;
390
15.6k
}
391
392
/************************************************************************/
393
/*                           SetNoDataValue()                           */
394
/************************************************************************/
395
396
CPLErr IRISRasterBand::SetNoDataValue(double dfNoData)
397
398
3.24k
{
399
3.24k
    IRISDataset *poGDS = static_cast<IRISDataset *>(poDS);
400
401
3.24k
    poGDS->bNoDataSet = true;
402
3.24k
    poGDS->dfNoDataValue = dfNoData;
403
404
3.24k
    return CE_None;
405
3.24k
}
406
407
/************************************************************************/
408
/*                           GetNoDataValue()                           */
409
/************************************************************************/
410
411
double IRISRasterBand::GetNoDataValue(int *pbSuccess)
412
413
84
{
414
84
    IRISDataset *poGDS = static_cast<IRISDataset *>(poDS);
415
416
84
    if (poGDS->bNoDataSet)
417
84
    {
418
84
        if (pbSuccess)
419
63
            *pbSuccess = TRUE;
420
421
84
        return poGDS->dfNoDataValue;
422
84
    }
423
424
0
    return GDALPamRasterBand::GetNoDataValue(pbSuccess);
425
84
}
426
427
/************************************************************************/
428
/* ==================================================================== */
429
/*                              IRISDataset                             */
430
/* ==================================================================== */
431
/************************************************************************/
432
433
/************************************************************************/
434
/*                            IRISDataset()                             */
435
/************************************************************************/
436
437
IRISDataset::IRISDataset()
438
21
    : fp(nullptr), bNoDataSet(false), dfNoDataValue(0.0), nProductCode(0),
439
21
      nDataTypeCode(0), nProjectionCode(0), fNyquistVelocity(0.0),
440
21
      bHasLoadedProjection(false)
441
21
{
442
21
    m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
443
21
    std::fill_n(abyHeader, CPL_ARRAYSIZE(abyHeader), static_cast<GByte>(0));
444
21
}
445
446
/************************************************************************/
447
/*                            ~IRISDataset()                            */
448
/************************************************************************/
449
450
IRISDataset::~IRISDataset()
451
452
21
{
453
21
    FlushCache(true);
454
21
    if (fp != nullptr)
455
21
        VSIFCloseL(fp);
456
21
}
457
458
/************************************************************************/
459
/*              Calculates the projection and Geotransform              */
460
/************************************************************************/
461
void IRISDataset::LoadProjection() const
462
21
{
463
21
    bHasLoadedProjection = true;
464
    // They give the radius in cm.
465
21
    double dfEquatorialRadius =
466
21
        CPL_LSBUINT32PTR(abyHeader + 220 + 320 + 12) / 100.0;
467
    // Point 3.2.27 pag 3-15.
468
21
    double dfInvFlattening =
469
21
        CPL_LSBUINT32PTR(abyHeader + 224 + 320 + 12) / 1000000.0;
470
21
    double dfFlattening = 0.0;
471
21
    double dfPolarRadius = 0.0;
472
473
21
    if (dfEquatorialRadius == 0.0)
474
3
    {
475
        // If Radius is 0, change to 6371000 Point 3.2.27 pag 3-15 (old IRIS
476
        // versions).
477
3
        dfEquatorialRadius = 6371000.0;
478
3
        dfPolarRadius = dfEquatorialRadius;
479
3
        dfInvFlattening = 0.0;
480
3
        dfFlattening = 0.0;
481
3
    }
482
18
    else
483
18
    {
484
18
        if (dfInvFlattening == 0.0)
485
4
        {
486
            // When inverse flattening is infinite, they use 0.
487
4
            dfFlattening = 0.0;
488
4
            dfPolarRadius = dfEquatorialRadius;
489
4
        }
490
14
        else
491
14
        {
492
14
            dfFlattening = 1.0 / dfInvFlattening;
493
14
            dfPolarRadius = dfEquatorialRadius * (1.0 - dfFlattening);
494
14
        }
495
18
    }
496
497
21
    constexpr GUInt32 knUINT32_MAX = 0xFFFFFFFFU;
498
21
    const double dfCenterLon =
499
21
        CPL_LSBUINT32PTR(abyHeader + 112 + 320 + 12) * 360.0 / knUINT32_MAX;
500
21
    const double dfCenterLat =
501
21
        CPL_LSBUINT32PTR(abyHeader + 108 + 320 + 12) * 360.0 / knUINT32_MAX;
502
503
21
    const double dfProjRefLon =
504
21
        CPL_LSBUINT32PTR(abyHeader + 244 + 320 + 12) * 360.0 / knUINT32_MAX;
505
21
    const double dfProjRefLat =
506
21
        CPL_LSBUINT32PTR(abyHeader + 240 + 320 + 12) * 360.0 / knUINT32_MAX;
507
508
21
    const double dfRadarLocX = CPL_LSBSINT32PTR(abyHeader + 112 + 12) / 1000.0;
509
21
    const double dfRadarLocY = CPL_LSBSINT32PTR(abyHeader + 116 + 12) / 1000.0;
510
511
21
    const double dfScaleX = CPL_LSBSINT32PTR(abyHeader + 88 + 12) / 100.0;
512
21
    const double dfScaleY = CPL_LSBSINT32PTR(abyHeader + 92 + 12) / 100.0;
513
21
    if (dfScaleX <= 0.0 || dfScaleY <= 0.0 || dfScaleX >= dfPolarRadius ||
514
15
        dfScaleY >= dfPolarRadius)
515
6
        return;
516
517
    // Mercator projection.
518
15
    if (EQUAL(aszProjections[nProjectionCode], "Mercator"))
519
8
    {
520
8
        std::pair<double, double> oPositionX2;
521
8
        if (!GeodesicCalculation(dfCenterLat, dfCenterLon, 90.0, dfScaleX,
522
8
                                 dfEquatorialRadius, dfPolarRadius,
523
8
                                 dfFlattening, oPositionX2))
524
0
            return;
525
8
        std::pair<double, double> oPositionY2;
526
8
        if (!GeodesicCalculation(dfCenterLat, dfCenterLon, 0.0, dfScaleY,
527
8
                                 dfEquatorialRadius, dfPolarRadius,
528
8
                                 dfFlattening, oPositionY2))
529
0
            return;
530
531
8
        m_oSRS.SetGeogCS("unnamed ellipse", "unknown", "unnamed",
532
8
                         dfEquatorialRadius, dfInvFlattening, "Greenwich", 0.0,
533
8
                         "degree", 0.0174532925199433);
534
535
8
        m_oSRS.SetMercator(dfProjRefLat, dfProjRefLon, 1.0, 0.0, 0.0);
536
8
        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
8
        OGRSpatialReference oSRSLatLon;
542
8
        oSRSLatLon.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
543
8
        oSRSLatLon.SetGeogCS("unnamed ellipse", "unknown", "unnamed",
544
8
                             dfEquatorialRadius, dfInvFlattening, "Greenwich",
545
8
                             0.0, "degree", 0.0174532925199433);
546
547
8
        OGRCoordinateTransformation *poTransform =
548
8
            OGRCreateCoordinateTransformation(&oSRSLatLon, &m_oSRS);
549
550
8
        const double dfLon2 = oPositionX2.first;
551
8
        const double dfLat2 = oPositionY2.second;
552
553
8
        double dfX = dfCenterLon;
554
8
        double dfY = dfCenterLat;
555
8
        if (poTransform == nullptr || !poTransform->Transform(1, &dfX, &dfY))
556
1
            CPLError(CE_Failure, CPLE_None, "Transformation Failed");
557
558
8
        double dfX2 = dfLon2;
559
8
        double dfY2 = dfLat2;
560
8
        if (poTransform == nullptr || !poTransform->Transform(1, &dfX2, &dfY2))
561
1
            CPLError(CE_Failure, CPLE_None, "Transformation Failed");
562
563
8
        m_gt.xorig = dfX - (dfRadarLocX * (dfX2 - dfX));
564
8
        m_gt.xscale = dfX2 - dfX;
565
8
        m_gt.xrot = 0.0;
566
8
        m_gt.yorig = dfY + (dfRadarLocY * (dfY2 - dfY));
567
8
        m_gt.yrot = 0.0;
568
8
        m_gt.yscale = -1 * (dfY2 - dfY);
569
570
8
        delete poTransform;
571
8
    }
572
7
    else if (EQUAL(aszProjections[nProjectionCode], "Azimutal equidistant"))
573
7
    {
574
7
        m_oSRS.SetGeogCS("unnamed ellipse", "unknown", "unnamed",
575
7
                         dfEquatorialRadius, dfInvFlattening, "Greenwich", 0.0,
576
7
                         "degree", 0.0174532925199433);
577
7
        m_oSRS.SetAE(dfProjRefLat, dfProjRefLon, 0.0, 0.0);
578
579
7
        m_gt.xorig = -1 * (dfRadarLocX * dfScaleX);
580
7
        m_gt.xscale = dfScaleX;
581
7
        m_gt.xrot = 0.0;
582
7
        m_gt.yorig = dfRadarLocY * dfScaleY;
583
7
        m_gt.yrot = 0.0;
584
7
        m_gt.yscale = -1 * dfScaleY;
585
        // When the projection is different from Mercator or Azimutal
586
        // equidistant, we set a standard geotransform.
587
7
    }
588
0
    else
589
0
    {
590
0
        m_gt.xorig = -1 * (dfRadarLocX * dfScaleX);
591
0
        m_gt.xscale = dfScaleX;
592
0
        m_gt.xrot = 0.0;
593
0
        m_gt.yorig = dfRadarLocY * dfScaleY;
594
0
        m_gt.yrot = 0.0;
595
0
        m_gt.yscale = -1 * dfScaleY;
596
0
    }
597
15
}
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
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
16
{
626
16
    const double dfAlpha1 = DEG2RAD * fAngle;
627
16
    const double dfSinAlpha1 = sin(dfAlpha1);
628
16
    const double dfCosAlpha1 = cos(dfAlpha1);
629
630
16
    const double dfTanU1 = (1 - fFlattening) * tan(fLat * DEG2RAD);
631
16
    const double dfCosU1 = 1 / sqrt((1 + dfTanU1 * dfTanU1));
632
16
    const double dfSinU1 = dfTanU1 * dfCosU1;
633
634
16
    const double dfSigma1 = atan2(dfTanU1, dfCosAlpha1);
635
16
    const double dfSinAlpha = dfCosU1 * dfSinAlpha1;
636
16
    const double dfCosSqAlpha = 1 - dfSinAlpha * dfSinAlpha;
637
16
    const double dfUSq =
638
16
        dfCosSqAlpha *
639
16
        (fEquatorialRadius * fEquatorialRadius - fPolarRadius * fPolarRadius) /
640
16
        (fPolarRadius * fPolarRadius);
641
16
    const double dfA =
642
16
        1 +
643
16
        dfUSq / 16384 * (4096 + dfUSq * (-768 + dfUSq * (320 - 175 * dfUSq)));
644
16
    const double dfB =
645
16
        dfUSq / 1024 * (256 + dfUSq * (-128 + dfUSq * (74 - 47 * dfUSq)));
646
647
16
    double dfSigma = fDist / (fPolarRadius * dfA);
648
16
    double dfSigmaP = 2 * M_PI;
649
650
16
    double dfSinSigma = 0.0;
651
16
    double dfCosSigma = 0.0;
652
16
    double dfCos2SigmaM = 0.0;
653
654
16
    int nIter = 0;
655
45
    while (fabs(dfSigma - dfSigmaP) > 1e-12)
656
29
    {
657
29
        dfCos2SigmaM = cos(2 * dfSigma1 + dfSigma);
658
29
        dfSinSigma = sin(dfSigma);
659
29
        dfCosSigma = cos(dfSigma);
660
29
        const double dfDeltaSigma =
661
29
            dfB * dfSinSigma *
662
29
            (dfCos2SigmaM +
663
29
             dfB / 4 *
664
29
                 (dfCosSigma * (-1 + 2 * dfCos2SigmaM * dfCos2SigmaM) -
665
29
                  dfB / 6 * dfCos2SigmaM * (-3 + 4 * dfSinSigma * dfSinSigma) *
666
29
                      (-3 + 4 * dfCos2SigmaM * dfCos2SigmaM)));
667
29
        dfSigmaP = dfSigma;
668
29
        dfSigma = fDist / (fPolarRadius * dfA) + dfDeltaSigma;
669
29
        nIter++;
670
29
        if (nIter == 100)
671
0
            return false;
672
29
    }
673
674
16
    const double dfTmp =
675
16
        dfSinU1 * dfSinSigma - dfCosU1 * dfCosSigma * dfCosAlpha1;
676
16
    const double dfLat2 = atan2(
677
16
        dfSinU1 * dfCosSigma + dfCosU1 * dfSinSigma * dfCosAlpha1,
678
16
        (1 - fFlattening) * sqrt(dfSinAlpha * dfSinAlpha + dfTmp * dfTmp));
679
16
    const double dfLambda =
680
16
        atan2(dfSinSigma * dfSinAlpha1,
681
16
              dfCosU1 * dfCosSigma - dfSinU1 * dfSinSigma * dfCosAlpha1);
682
16
    const double dfC = fFlattening / 16 * dfCosSqAlpha *
683
16
                       (4 + fFlattening * (4 - 3 * dfCosSqAlpha));
684
16
    const double dfL =
685
16
        dfLambda -
686
16
        (1 - dfC) * fFlattening * dfSinAlpha *
687
16
            (dfSigma +
688
16
             dfC * dfSinSigma *
689
16
                 (dfCos2SigmaM +
690
16
                  dfC * dfCosSigma * (-1 + 2 * dfCos2SigmaM * dfCos2SigmaM)));
691
16
    double dfLon2 = fLon * DEG2RAD + dfL;
692
693
16
    if (dfLon2 > M_PI)
694
2
        dfLon2 = dfLon2 - 2 * M_PI;
695
16
    if (dfLon2 < -1 * M_PI)
696
0
        dfLon2 = dfLon2 + 2 * M_PI;
697
698
16
    oOutPair = std::pair<double, double>(dfLon2 * RAD2DEG, dfLat2 * RAD2DEG);
699
700
16
    return true;
701
16
}
702
703
/************************************************************************/
704
/*                          GetGeoTransform()                           */
705
/************************************************************************/
706
707
CPLErr IRISDataset::GetGeoTransform(GDALGeoTransform &gt) const
708
709
21
{
710
21
    if (!bHasLoadedProjection)
711
0
        LoadProjection();
712
21
    gt = m_gt;
713
21
    return CE_None;
714
21
}
715
716
/************************************************************************/
717
/*                           GetSpatialRef()                            */
718
/************************************************************************/
719
720
const OGRSpatialReference *IRISDataset::GetSpatialRef() const
721
21
{
722
21
    if (!bHasLoadedProjection)
723
21
        LoadProjection();
724
21
    return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
725
21
}
726
727
/************************************************************************/
728
/*                              Identify()                              */
729
/************************************************************************/
730
731
int IRISDataset::Identify(GDALOpenInfo *poOpenInfo)
732
733
459k
{
734
    /* -------------------------------------------------------------------- */
735
    /*      Confirm that the file is an IRIS file                           */
736
    /* -------------------------------------------------------------------- */
737
459k
    if (poOpenInfo->nHeaderBytes < 640)
738
399k
        return FALSE;
739
740
60.2k
    const short nId1 = CPL_LSBSINT16PTR(poOpenInfo->pabyHeader);
741
60.2k
    const short nId2 = CPL_LSBSINT16PTR(poOpenInfo->pabyHeader + 12);
742
60.2k
    unsigned short nProductCode =
743
60.2k
        CPL_LSBUINT16PTR(poOpenInfo->pabyHeader + 12 + 12);
744
60.2k
    const short nYear = CPL_LSBSINT16PTR(poOpenInfo->pabyHeader + 26 + 12);
745
60.2k
    const short nMonth = CPL_LSBSINT16PTR(poOpenInfo->pabyHeader + 28 + 12);
746
60.2k
    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
60.2k
    if (!(nId1 == 27 && nId2 == 26 && nProductCode > 0 &&
751
42
          nProductCode < CPL_ARRAYSIZE(aszProductNames) && nYear >= 1900 &&
752
42
          nYear < 2100 && nMonth >= 1 && nMonth <= 12 && nDay >= 1 &&
753
42
          nDay <= 31))
754
60.2k
        return FALSE;
755
756
42
    return TRUE;
757
60.2k
}
758
759
/************************************************************************/
760
/*                             FillString()                             */
761
/************************************************************************/
762
763
static void FillString(char *szBuffer, size_t nBufferSize, void *pSrcBuffer)
764
147
{
765
1.99k
    for (size_t i = 0; i < nBufferSize - 1; i++)
766
1.84k
        szBuffer[i] = static_cast<char *>(pSrcBuffer)[i];
767
147
    szBuffer[nBufferSize - 1] = '\0';
768
147
}
769
770
/************************************************************************/
771
/*                                Open()                                */
772
/************************************************************************/
773
774
GDALDataset *IRISDataset::Open(GDALOpenInfo *poOpenInfo)
775
776
21
{
777
21
    if (!Identify(poOpenInfo) || poOpenInfo->fpL == nullptr)
778
0
        return nullptr;
779
    /* -------------------------------------------------------------------- */
780
    /*      Confirm the requested access is supported.                      */
781
    /* -------------------------------------------------------------------- */
782
21
    if (poOpenInfo->eAccess == GA_Update)
783
0
    {
784
0
        ReportUpdateNotSupportedByDriver("IRIS");
785
0
        return nullptr;
786
0
    }
787
788
    /* -------------------------------------------------------------------- */
789
    /*      Create a corresponding GDALDataset.                             */
790
    /* -------------------------------------------------------------------- */
791
21
    IRISDataset *poDS = new IRISDataset();
792
21
    poDS->fp = poOpenInfo->fpL;
793
21
    poOpenInfo->fpL = nullptr;
794
795
    /* -------------------------------------------------------------------- */
796
    /*      Read the header.                                                */
797
    /* -------------------------------------------------------------------- */
798
21
    VSIFReadL(poDS->abyHeader, 1, 640, poDS->fp);
799
21
    const int nXSize = CPL_LSBSINT32PTR(poDS->abyHeader + 100 + 12);
800
21
    const int nYSize = CPL_LSBSINT32PTR(poDS->abyHeader + 104 + 12);
801
21
    const int nNumBands = CPL_LSBSINT32PTR(poDS->abyHeader + 108 + 12);
802
803
21
    poDS->nRasterXSize = nXSize;
804
805
21
    poDS->nRasterYSize = nYSize;
806
21
    if (poDS->nRasterXSize <= 0 || poDS->nRasterYSize <= 0)
807
0
    {
808
0
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid dimensions : %d x %d",
809
0
                 poDS->nRasterXSize, poDS->nRasterYSize);
810
0
        delete poDS;
811
0
        return nullptr;
812
0
    }
813
814
21
    if (!GDALCheckBandCount(nNumBands, TRUE))
815
0
    {
816
0
        delete poDS;
817
0
        return nullptr;
818
0
    }
819
820
    /* -------------------------------------------------------------------- */
821
    /*      Setting the Metadata                                            */
822
    /* -------------------------------------------------------------------- */
823
    // See point 3.2.26 at page 3.12 of the manual.
824
21
    poDS->nProductCode = CPL_LSBUINT16PTR(poDS->abyHeader + 12 + 12);
825
21
    poDS->SetMetadataItem("PRODUCT_ID",
826
21
                          CPLString().Printf("%d", poDS->nProductCode));
827
21
    if (poDS->nProductCode >= CPL_ARRAYSIZE(aszProductNames))
828
0
    {
829
0
        delete poDS;
830
0
        return nullptr;
831
0
    }
832
833
21
    poDS->SetMetadataItem("PRODUCT", aszProductNames[poDS->nProductCode]);
834
835
21
    poDS->nDataTypeCode = CPL_LSBUINT16PTR(poDS->abyHeader + 130 + 12);
836
21
    if (poDS->nDataTypeCode >= CPL_ARRAYSIZE(aszDataTypeCodes))
837
0
    {
838
0
        delete poDS;
839
0
        return nullptr;
840
0
    }
841
21
    poDS->SetMetadataItem("DATA_TYPE_CODE",
842
21
                          aszDataTypeCodes[poDS->nDataTypeCode]);
843
844
21
    if (poDS->nDataTypeCode >= CPL_ARRAYSIZE(aszDataTypes))
845
0
    {
846
0
        delete poDS;
847
0
        return nullptr;
848
0
    }
849
21
    poDS->SetMetadataItem("DATA_TYPE", aszDataTypes[poDS->nDataTypeCode]);
850
851
21
    const unsigned short nDataTypeInputCode =
852
21
        CPL_LSBUINT16PTR(poDS->abyHeader + 144 + 12);
853
21
    if (nDataTypeInputCode >= CPL_ARRAYSIZE(aszDataTypeCodes))
854
0
    {
855
0
        delete poDS;
856
0
        return nullptr;
857
0
    }
858
21
    poDS->SetMetadataItem("DATA_TYPE_INPUT_CODE",
859
21
                          aszDataTypeCodes[nDataTypeInputCode]);
860
861
21
    const unsigned short nDataTypeInput =
862
21
        CPL_LSBUINT16PTR(poDS->abyHeader + 144 + 12);
863
21
    if (nDataTypeInput >= CPL_ARRAYSIZE(aszDataTypes))
864
0
    {
865
0
        delete poDS;
866
0
        return nullptr;
867
0
    }
868
21
    poDS->SetMetadataItem("DATA_TYPE_INPUT", aszDataTypes[nDataTypeInput]);
869
870
21
    poDS->nProjectionCode =
871
21
        *static_cast<unsigned char *>(poDS->abyHeader + 146 + 12);
872
21
    if (poDS->nProjectionCode >= CPL_ARRAYSIZE(aszProjections))
873
0
    {
874
0
        delete poDS;
875
0
        return nullptr;
876
0
    }
877
878
    // Times.
879
21
    {
880
21
        const int nSeconds = CPL_LSBSINT32PTR(poDS->abyHeader + 20 + 12);
881
882
21
        const int nHour = (nSeconds - (nSeconds % 3600)) / 3600;
883
21
        const int nMinute =
884
21
            ((nSeconds - nHour * 3600) - (nSeconds - nHour * 3600) % 60) / 60;
885
21
        const int nSecond = nSeconds - nHour * 3600 - nMinute * 60;
886
887
21
        const short nYear = CPL_LSBSINT16PTR(poDS->abyHeader + 26 + 12);
888
21
        const short nMonth = CPL_LSBSINT16PTR(poDS->abyHeader + 28 + 12);
889
21
        const short nDay = CPL_LSBSINT16PTR(poDS->abyHeader + 30 + 12);
890
891
21
        poDS->SetMetadataItem("TIME_PRODUCT_GENERATED",
892
21
                              CPLString().Printf("%d-%02d-%02d %02d:%02d:%02d",
893
21
                                                 nYear, nMonth, nDay, nHour,
894
21
                                                 nMinute, nSecond));
895
21
    }
896
897
21
    {
898
21
        const int nSeconds = CPL_LSBSINT32PTR(poDS->abyHeader + 32 + 12);
899
900
21
        const int nHour = (nSeconds - (nSeconds % 3600)) / 3600;
901
21
        const int nMinute =
902
21
            ((nSeconds - nHour * 3600) - (nSeconds - nHour * 3600) % 60) / 60;
903
21
        const int nSecond = nSeconds - nHour * 3600 - nMinute * 60;
904
905
21
        const short nYear = CPL_LSBSINT16PTR(poDS->abyHeader + 26 + 12);
906
21
        const short nMonth = CPL_LSBSINT16PTR(poDS->abyHeader + 28 + 12);
907
21
        const short nDay = CPL_LSBSINT16PTR(poDS->abyHeader + 30 + 12);
908
909
21
        poDS->SetMetadataItem("TIME_INPUT_INGEST_SWEEP",
910
21
                              CPLString().Printf("%d-%02d-%02d %02d:%02d:%02d",
911
21
                                                 nYear, nMonth, nDay, nHour,
912
21
                                                 nMinute, nSecond));
913
21
    }
914
915
    // Site and task information.
916
917
21
    char szSiteName[17] = {};  // Must have one extra char for string end.
918
21
    char szVersionName[9] = {};
919
920
21
    FillString(szSiteName, sizeof(szSiteName), poDS->abyHeader + 320 + 12);
921
21
    FillString(szVersionName, sizeof(szVersionName),
922
21
               poDS->abyHeader + 16 + 320 + 12);
923
21
    poDS->SetMetadataItem("PRODUCT_SITE_NAME", szSiteName);
924
21
    poDS->SetMetadataItem("PRODUCT_SITE_IRIS_VERSION", szVersionName);
925
926
21
    FillString(szSiteName, sizeof(szSiteName), poDS->abyHeader + 90 + 320 + 12);
927
21
    FillString(szVersionName, sizeof(szVersionName),
928
21
               poDS->abyHeader + 24 + 320 + 12);
929
21
    poDS->SetMetadataItem("INGEST_SITE_NAME", szSiteName);
930
21
    poDS->SetMetadataItem("INGEST_SITE_IRIS_VERSION", szVersionName);
931
932
21
    FillString(szSiteName, sizeof(szSiteName), poDS->abyHeader + 74 + 320 + 12);
933
21
    poDS->SetMetadataItem("INGEST_HARDWARE_NAME", szSiteName);
934
935
21
    char szConfigFile[13] = {};
936
21
    FillString(szConfigFile, sizeof(szConfigFile), poDS->abyHeader + 62 + 12);
937
21
    poDS->SetMetadataItem("PRODUCT_CONFIGURATION_NAME", szConfigFile);
938
939
21
    char szTaskName[13] = {};
940
21
    FillString(szTaskName, sizeof(szTaskName), poDS->abyHeader + 74 + 12);
941
21
    poDS->SetMetadataItem("TASK_NAME", szTaskName);
942
943
21
    const short nRadarHeight =
944
21
        CPL_LSBSINT16PTR(poDS->abyHeader + 284 + 320 + 12);
945
21
    poDS->SetMetadataItem("RADAR_HEIGHT",
946
21
                          CPLString().Printf("%d m", nRadarHeight));
947
    // Ground height over the sea level.
948
21
    const short nGroundHeight =
949
21
        CPL_LSBSINT16PTR(poDS->abyHeader + 118 + 320 + 12);
950
21
    poDS->SetMetadataItem(
951
21
        "GROUND_HEIGHT",
952
21
        CPLString().Printf("%d m", nRadarHeight - nGroundHeight));
953
954
21
    unsigned short nFlags = CPL_LSBUINT16PTR(poDS->abyHeader + 86 + 12);
955
    // Get eleventh bit.
956
21
    nFlags = nFlags << 4;
957
21
    nFlags = nFlags >> 15;
958
21
    if (nFlags == 1)
959
14
    {
960
14
        poDS->SetMetadataItem("COMPOSITED_PRODUCT", "YES");
961
14
        const unsigned int compositedMask =
962
14
            CPL_LSBUINT32PTR(poDS->abyHeader + 232 + 320 + 12);
963
14
        poDS->SetMetadataItem("COMPOSITED_PRODUCT_MASK",
964
14
                              CPLString().Printf("0x%08x", compositedMask));
965
14
    }
966
7
    else
967
7
    {
968
7
        poDS->SetMetadataItem("COMPOSITED_PRODUCT", "NO");
969
7
    }
970
971
    // Wave values.
972
21
    poDS->SetMetadataItem(
973
21
        "PRF", CPLString().Printf("%d Hz", CPL_LSBSINT32PTR(poDS->abyHeader +
974
21
                                                            120 + 320 + 12)));
975
21
    poDS->SetMetadataItem(
976
21
        "WAVELENGTH",
977
21
        CPLString().Printf("%4.2f cm",
978
21
                           CPL_LSBSINT32PTR(poDS->abyHeader + 148 + 320 + 12) /
979
21
                               100.0f));
980
21
    const unsigned short nPolarizationType =
981
21
        CPL_LSBUINT16PTR(poDS->abyHeader + 172 + 320 + 12);
982
983
    // See section 3.3.37 & 3.2.54.
984
21
    float fNyquist = (CPL_LSBSINT32PTR(poDS->abyHeader + 120 + 320 + 12)) *
985
21
                     (static_cast<float>(
986
21
                          CPL_LSBSINT32PTR(poDS->abyHeader + 148 + 320 + 12)) /
987
21
                      10000.0f) /
988
21
                     4.0f;
989
21
    if (nPolarizationType == 1)
990
0
        fNyquist = fNyquist * 2.0f;
991
21
    else if (nPolarizationType == 2)
992
0
        fNyquist = fNyquist * 3.0f;
993
21
    else if (nPolarizationType == 3)
994
0
        fNyquist = fNyquist * 4.0f;
995
21
    poDS->fNyquistVelocity = fNyquist;
996
21
    poDS->SetMetadataItem("NYQUIST_VELOCITY",
997
21
                          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
21
    if (EQUAL(aszProductNames[poDS->nProductCode], "PPI"))
1004
2
    {
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
2
                              CPLString().Printf("%f", fElevation));
1011
2
        if (EQUAL(aszDataTypeCodes[poDS->nDataTypeCode], "dBZ"))
1012
0
            poDS->SetMetadataItem("DATA_TYPE_UNITS", "dBZ");
1013
2
        else
1014
2
            poDS->SetMetadataItem("DATA_TYPE_UNITS", "m/s");
1015
        // See point 3.2.2 at page 3.2 of the manual.
1016
2
    }
1017
19
    else if (EQUAL(aszProductNames[poDS->nProductCode], "CAPPI"))
1018
13
    {
1019
13
        const float fElevation =
1020
13
            CPL_LSBSINT32PTR(poDS->abyHeader + 4 + 164 + 12) / 100.0f;
1021
13
        poDS->SetMetadataItem("CAPPI_BOTTOM_HEIGHT",
1022
13
                              CPLString().Printf("%.1f m", fElevation));
1023
13
        const float fAzimuthSmoothingForShear =
1024
13
            CPL_LSBUINT16PTR(poDS->abyHeader + 10 + 164 + 12) * 360.0f /
1025
13
            65536.0f;
1026
13
        poDS->SetMetadataItem(
1027
13
            "AZIMUTH_SMOOTHING_FOR_SHEAR",
1028
13
            CPLString().Printf("%.1f", fAzimuthSmoothingForShear));
1029
13
        const unsigned int nMaxAgeVVPCorrection =
1030
13
            CPL_LSBUINT32PTR(poDS->abyHeader + 24 + 164 + 12);
1031
13
        poDS->SetMetadataItem("MAX_AGE_FOR_SHEAR_VVP_CORRECTION",
1032
13
                              CPLString().Printf("%d s", nMaxAgeVVPCorrection));
1033
13
        if (EQUAL(aszDataTypeCodes[poDS->nDataTypeCode], "dBZ"))
1034
7
            poDS->SetMetadataItem("DATA_TYPE_UNITS", "dBZ");
1035
6
        else
1036
6
            poDS->SetMetadataItem("DATA_TYPE_UNITS", "m/s");
1037
        // See point 3.2.32 at page 3.19 of the manual.
1038
13
    }
1039
6
    else if (EQUAL(aszProductNames[poDS->nProductCode], "RAIN1") ||
1040
6
             EQUAL(aszProductNames[poDS->nProductCode], "RAINN"))
1041
6
    {
1042
6
        const short nNumProducts =
1043
6
            CPL_LSBSINT16PTR(poDS->abyHeader + 170 + 320 + 12);
1044
6
        poDS->SetMetadataItem("NUM_FILES_USED",
1045
6
                              CPLString().Printf("%d", nNumProducts));
1046
1047
6
        const float fMinZAcum =
1048
6
            (CPL_LSBUINT32PTR(poDS->abyHeader + 164 + 12) - 32768.0f) /
1049
6
            10000.0f;
1050
6
        poDS->SetMetadataItem("MINIMUM_Z_TO_ACCUMULATE",
1051
6
                              CPLString().Printf("%f", fMinZAcum));
1052
1053
6
        const unsigned short nSecondsOfAccumulation =
1054
6
            CPL_LSBUINT16PTR(poDS->abyHeader + 6 + 164 + 12);
1055
6
        poDS->SetMetadataItem(
1056
6
            "SECONDS_OF_ACCUMULATION",
1057
6
            CPLString().Printf("%d s", nSecondsOfAccumulation));
1058
1059
6
        const unsigned int nSpanInputFiles =
1060
6
            CPL_LSBUINT32PTR(poDS->abyHeader + 24 + 164 + 12);
1061
6
        poDS->SetMetadataItem("SPAN_OF_INPUT_FILES",
1062
6
                              CPLString().Printf("%d s", nSpanInputFiles));
1063
6
        poDS->SetMetadataItem("DATA_TYPE_UNITS", "mm");
1064
1065
6
        char szInputProductName[13] = "";
1066
78
        for (int k = 0; k < 12; k++)
1067
72
            szInputProductName[k] =
1068
72
                *reinterpret_cast<char *>(poDS->abyHeader + k + 12 + 164 + 12);
1069
1070
6
        poDS->SetMetadataItem("INPUT_PRODUCT_NAME",
1071
6
                              CPLString().Printf("%s", szInputProductName));
1072
1073
6
        if (EQUAL(aszProductNames[poDS->nProductCode], "RAINN"))
1074
6
            poDS->SetMetadataItem(
1075
6
                "NUM_HOURS_ACCUMULATE",
1076
6
                CPLString().Printf(
1077
6
                    "%d", CPL_LSBUINT16PTR(poDS->abyHeader + 10 + 164 + 12)));
1078
1079
        // See point 3.2.73 at page 3.36 of the manual.
1080
6
    }
1081
0
    else if (EQUAL(aszProductNames[poDS->nProductCode], "VIL"))
1082
0
    {
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
0
            "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
0
    }
1098
0
    else if (EQUAL(aszProductNames[poDS->nProductCode], "TOPS"))
1099
0
    {
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
0
    }
1108
0
    else if (EQUAL(aszProductNames[poDS->nProductCode], "MAX"))
1109
0
    {
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
0
            "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
0
            "VERTICAL_SMOOTHER_SIDE_PANELS",
1131
0
            CPLString().Printf("%d", nVerticalSmootherSidePanels));
1132
0
    }
1133
1134
    /* -------------------------------------------------------------------- */
1135
    /*      Create band information objects.                                */
1136
    /* -------------------------------------------------------------------- */
1137
21
    assert(nNumBands <= INT_MAX - 1);
1138
3.26k
    for (int iBandNum = 1; iBandNum <= nNumBands; iBandNum++)
1139
3.24k
    {
1140
3.24k
        poDS->SetBand(iBandNum, new IRISRasterBand(poDS, iBandNum));
1141
1142
3.24k
        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.24k
        if (EQUAL(aszProductNames[poDS->nProductCode], "CAPPI"))
1146
1.31k
        {
1147
1.31k
            const float fScaleZ =
1148
1.31k
                CPL_LSBSINT32PTR(poDS->abyHeader + 96 + 12) / 100.0f;
1149
1.31k
            const float fOffset =
1150
1.31k
                CPL_LSBSINT32PTR(poDS->abyHeader + 4 + 164 + 12) / 100.0f;
1151
1152
1.31k
            poDS->GetRasterBand(iBandNum)->SetMetadataItem(
1153
1.31k
                "height", CPLString().Printf(
1154
1.31k
                              "%.0f m", fOffset + fScaleZ * (iBandNum - 1)));
1155
1.31k
        }
1156
3.24k
    }
1157
    /* -------------------------------------------------------------------- */
1158
    /*      Initialize any PAM information.                                 */
1159
    /* -------------------------------------------------------------------- */
1160
21
    poDS->SetDescription(poOpenInfo->pszFilename);
1161
21
    poDS->TryLoadXML();
1162
1163
    /* -------------------------------------------------------------------- */
1164
    /*      Check for overviews.                                            */
1165
    /* -------------------------------------------------------------------- */
1166
21
    poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename);
1167
1168
21
    return poDS;
1169
21
}
1170
1171
/************************************************************************/
1172
/*                         GDALRegister_IRIS()                          */
1173
/************************************************************************/
1174
1175
void GDALRegister_IRIS()
1176
1177
22
{
1178
22
    if (GDALGetDriverByName("IRIS") != nullptr)
1179
0
        return;
1180
1181
22
    GDALDriver *poDriver = new GDALDriver();
1182
1183
22
    poDriver->SetDescription("IRIS");
1184
22
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
1185
22
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
1186
22
                              "IRIS data (.PPI, .CAPPi etc)");
1187
22
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/iris.html");
1188
22
    poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "ppi");
1189
22
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
1190
1191
22
    poDriver->pfnOpen = IRISDataset::Open;
1192
22
    poDriver->pfnIdentify = IRISDataset::Identify;
1193
1194
22
    GetGDALDriverManager()->RegisterDriver(poDriver);
1195
22
}