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