/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 >) 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 >) 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 | } |