nexrad-decode 0.1.2

Decoding functions and models for NEXRAD weather radar data.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
use serde::Deserialize;
use std::fmt::Debug;

use crate::messages::primitive_aliases::{Code1, Code2, Integer1, Integer2, ScaledSInteger2};
use crate::messages::volume_coverage_pattern::definitions::{ChannelConfiguration, WaveformType};

#[cfg(feature = "uom")]
use uom::si::{
    angle::degree,
    angular_velocity::degree_per_second,
    f64::{Angle, AngularVelocity},
};

/// A data block for a single elevation cut.
#[derive(Clone, PartialEq, Deserialize)]
pub struct ElevationDataBlock {
    /// The elevation angle for this cut
    pub elevation_angle: Code2,

    /// The channel configuration for this cut
    /// 0 => Constant Phase
    /// 1 => Random Phase
    /// 2 => SZ2 Phase
    pub channel_configuration: Code1,

    /// The waveform type for this cut
    /// 1 => Contiguous Surveillance
    /// 2 => Contiguous Doppler w/ Ambiguity Resolution
    /// 3 => Contiguous Doppler w/o Ambiguity Resolution
    /// 4 => Batch
    /// 5 => Staggered Pulse Pair
    pub waveform_type: Code1,

    /// Super resolution control values for this cut
    /// Bit 0: 0.5 degree azimuth
    /// Bit 1: 1/4 km reflectivity
    /// Bit 2: Doppler to 300 km
    /// Bit 3: Dual polarization to 300 km
    pub super_resolution_control: Code1,

    /// The pulse repetition frequency number for surveillance cuts
    pub surveillance_prf_number: Integer1,

    /// The pulse count per radial for surveillance cuts
    pub surveillance_prf_pulse_count_radial: Integer2,

    /// The azimuth rate of the cut
    pub azimuth_rate: Code2,

    /// Signal to noise ratio (SNR) threshold for reflectivity
    pub reflectivity_threshold: ScaledSInteger2,

    /// Signal to noise ratio (SNR) threshold for velocity
    pub velocity_threshold: ScaledSInteger2,

    /// Signal to noise ratio (SNR) threshold for spectrum width
    pub spectrum_width_threshold: ScaledSInteger2,

    /// Signal to noise ratio (SNR) threshold for differential reflectivity
    pub differential_reflectivity_threshold: ScaledSInteger2,

    /// Signal to noise ratio (SNR) threshold for differential phase
    pub differential_phase_threshold: ScaledSInteger2,

    /// Signal to noise ratio (SNR) threshold for correlation coefficitn
    pub correlation_coefficient_threshold: ScaledSInteger2,

    /// Sector 1 Azimuth Clockwise Edge Angle (denotes start angle)
    pub sector_1_edge_angle: Code2,

    /// Sector 1 Doppler PRF Number
    pub sector_1_doppler_prf_number: Integer2,

    /// Sector 1 Doppler Pulse Count/Radial
    pub sector_1_doppler_prf_pulse_count_radial: Integer2,

    /// Supplemental Data
    /// Bit 0:    SAILS Cut
    /// Bits 1-3: SAILS Sequence Number
    /// Bit 4:    MRLE Cut
    /// Bits 5-7: MRLE Sequence Number
    /// Bit 8:    Spare
    /// Bit 9:    MPDA Cut
    /// Bit 10:   BASE TILT Cut
    pub supplemental_data: Code2,

    /// Sector 2 Azimuth Clockwise Edge Angle (denotes start angle)
    pub sector_2_edge_angle: Code2,

    /// Sector 2 Doppler PRF Number
    pub sector_2_doppler_prf_number: Integer2,

    /// Sector 2 Doppler Pulse Count/Radial
    pub sector_2_doppler_prf_pulse_count_radial: Integer2,

    /// The correction added to the elevation angle for this cut
    pub ebc_angle: Code2,

    /// Sector 3 Azimuth Clockwise Edge Angle (denotes start angle)
    pub sector_3_edge_angle: Code2,

    /// Sector 3 Doppler PRF Number
    pub sector_3_doppler_prf_number: Integer2,

    /// Sector 3 Doppler Pulse Count/Radial
    pub sector_3_doppler_prf_pulse_count_radial: Integer2,

    /// Reserved
    pub reserved: Integer2,
}

/// Decodes an angle as defined in table III-A of ICD 2620002W
fn decode_angle(raw: Code2) -> f64 {
    let mut angle: f64 = 0.0;
    for i in 3..16 {
        if ((raw >> i) & 1) == 1 {
            angle += 180.0 * f64::powf(2.0, (i - 15) as f64);
        }
    }

    return angle;
}

/// Decodes an angular velocity as defined in table XI-D of ICD 2620002W
fn decode_angular_velocity(raw: Code2) -> f64 {
    let mut angular_velocity: f64 = 0.0;

    for i in 3..15 {
        if ((raw >> i) & 1) == 1 {
            angular_velocity += 22.5 * f64::powf(2.0, (i - 14) as f64);
        }
    }

    if ((raw >> 15) & 1) == 1 {
        angular_velocity = -angular_velocity
    }

    return angular_velocity;
}

impl ElevationDataBlock {
    /// The elevation angle for this cut
    #[cfg(feature = "uom")]
    pub fn elevation_angle(&self) -> Angle {
        Angle::new::<degree>(decode_angle(self.elevation_angle))
    }

    /// The elevation angle for this cut, in degrees
    pub fn elevation_angle_degrees(&self) -> f64 {
        decode_angle(self.elevation_angle)
    }

    /// The channel configuration for this cut
    pub fn channel_configuration(&self) -> ChannelConfiguration {
        match self.channel_configuration {
            0 => ChannelConfiguration::ConstantPhase,
            1 => ChannelConfiguration::RandomPhase,
            2 => ChannelConfiguration::SZ2Phase,
            _ => ChannelConfiguration::UnknownPhase,
        }
    }

    /// The waveform type for this cut
    pub fn waveform_type(&self) -> WaveformType {
        match self.waveform_type {
            1 => WaveformType::CS,
            2 => WaveformType::CDW,
            3 => WaveformType::CDWO,
            4 => WaveformType::B,
            5 => WaveformType::SPP,
            _ => WaveformType::Unknown,
        }
    }

    /// Whether this cut uses super resolution 0.5 degree azimuth
    pub fn super_resolution_control_half_degree_azimuth(&self) -> bool {
        (self.super_resolution_control & 0x1) == 1
    }

    /// Whether this cut uses super resolution 0.25 km reflectivity
    pub fn super_resolution_control_quarter_km_reflectivity(&self) -> bool {
        ((self.super_resolution_control >> 1) & 0x1) == 1
    }

    /// Whether this cut uses super resolution doppler to 300 km
    pub fn super_resolution_control_doppler_to_300km(&self) -> bool {
        ((self.super_resolution_control >> 2) & 0x1) == 1
    }

    /// Whether this cut uses super resolution dual polarization to 300km
    pub fn super_resolution_control_dual_polarization_to_300km(&self) -> bool {
        ((self.super_resolution_control >> 3) & 0x1) == 1
    }

    /// The azimuth rate used for this cut
    #[cfg(feature = "uom")]
    pub fn azimuth_rate(&self) -> AngularVelocity {
        AngularVelocity::new::<degree_per_second>(decode_angular_velocity(self.azimuth_rate))
    }

    /// The azimuth rate used for this cut, in degrees per second
    pub fn azimuth_rate_degrees_per_second(&self) -> f64 {
        decode_angular_velocity(self.azimuth_rate)
    }

    /// The reflectivity threshold for this cut
    pub fn reflectivity_threshold(&self) -> f64 {
        self.reflectivity_threshold as f64 * 0.125
    }

    /// The velocity threshold for this cut
    pub fn velocity_threshold(&self) -> f64 {
        self.velocity_threshold as f64 * 0.125
    }

    /// The spectrum width threshold for this cut
    pub fn spectrum_width_threshold(&self) -> f64 {
        self.spectrum_width_threshold as f64 * 0.125
    }

    /// The differential reflectivity threshold for this cut
    pub fn differential_reflectivity_threshold(&self) -> f64 {
        self.differential_reflectivity_threshold as f64 * 0.125
    }

    /// The differential phase threshold for this cut
    pub fn differential_phase_threshold(&self) -> f64 {
        self.differential_phase_threshold as f64 * 0.125
    }

    /// The correlation coefficient threshold for this cut
    pub fn correlation_coefficient_threshold(&self) -> f64 {
        self.correlation_coefficient_threshold as f64 * 0.125
    }

    /// Sector 1 Azimuth Clockwise Edge Angle (denotes start angle)
    #[cfg(feature = "uom")]
    pub fn sector_1_edge_angle(&self) -> Angle {
        Angle::new::<degree>(decode_angle(self.sector_1_edge_angle))
    }

    /// Sector 1 Azimuth Clockwise Edge Angle (denotes start angle), in degrees
    pub fn sector_1_edge_angle_degrees(&self) -> f64 {
        decode_angle(self.sector_1_edge_angle)
    }

    /// Sector 2 Azimuth Clockwise Edge Angle (denotes start angle)
    #[cfg(feature = "uom")]
    pub fn sector_2_edge_angle(&self) -> Angle {
        Angle::new::<degree>(decode_angle(self.sector_2_edge_angle))
    }

    /// Sector 2 Azimuth Clockwise Edge Angle (denotes start angle), in degrees
    pub fn sector_2_edge_angle_degrees(&self) -> f64 {
        decode_angle(self.sector_2_edge_angle)
    }

    /// Sector 3 Azimuth Clockwise Edge Angle (denotes start angle)
    #[cfg(feature = "uom")]
    pub fn sector_3_edge_angle(&self) -> Angle {
        Angle::new::<degree>(decode_angle(self.sector_3_edge_angle))
    }

    /// Sector 3 Azimuth Clockwise Edge Angle (denotes start angle), in degrees
    pub fn sector_3_edge_angle_degrees(&self) -> f64 {
        decode_angle(self.sector_3_edge_angle)
    }

    /// The correction added to the elevation angle for this cut
    #[cfg(feature = "uom")]
    pub fn ebc_angle(&self) -> Angle {
        Angle::new::<degree>(decode_angle(self.ebc_angle))
    }

    /// The correction added to the elevation angle for this cut, in degrees
    pub fn ebc_angle_degrees(&self) -> f64 {
        decode_angle(self.ebc_angle)
    }

    /// Whether this cut is a SAILS cut
    pub fn supplemental_data_sails_cut(&self) -> bool {
        (self.supplemental_data & 0x0001) == 1
    }

    /// The SAILS sequence number of this cut
    pub fn supplemental_data_sails_sequence_number(&self) -> u8 {
        ((self.supplemental_data & 0x000E) >> 1) as u8
    }

    /// Whether this cut is an MRLE cut
    pub fn supplemental_data_mrle_cut(&self) -> bool {
        ((self.supplemental_data & 0x0010) >> 4) == 1
    }

    /// The MRLE sequence number of this cut
    pub fn supplemental_data_mrle_sequence_number(&self) -> u8 {
        ((self.supplemental_data & 0x00E0) >> 5) as u8
    }

    /// Whether this cut is an MPDA cut
    pub fn supplemental_data_mpda_cut(&self) -> bool {
        ((self.supplemental_data & 0x0200) >> 9) == 1
    }

    /// Whether this cut is a BASE TILT cut
    pub fn supplemental_data_base_tilt_cut(&self) -> bool {
        ((self.supplemental_data & 0x0400) >> 10) == 1
    }
}

impl Debug for ElevationDataBlock {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let mut debug = f.debug_struct("ElevationDataBlock");

        #[cfg(feature = "uom")]
        debug.field("elevation_angle", &self.elevation_angle());
        #[cfg(not(feature = "uom"))]
        debug.field("elevation_angle", &self.elevation_angle_degrees());

        debug.field("channel_configuration", &self.channel_configuration());
        debug.field("waveform_type", &self.waveform_type());

        debug.field(
            "super_resolution_control_raw",
            &self.super_resolution_control,
        );
        debug.field(
            "super_resolution_control_half_degree_azimuth",
            &self.super_resolution_control_half_degree_azimuth(),
        );
        debug.field(
            "super_resolution_control_quarter_km_reflectivity",
            &self.super_resolution_control_quarter_km_reflectivity(),
        );
        debug.field(
            "super_resolution_control_doppler_to_300km",
            &self.super_resolution_control_doppler_to_300km(),
        );
        debug.field(
            "super_resolution_control_dual_polarization_to_300km",
            &self.super_resolution_control_dual_polarization_to_300km(),
        );

        debug.field("surveillance_prf_number", &self.surveillance_prf_number);
        debug.field(
            "surveillance_prf_pulse_count_radial",
            &self.surveillance_prf_pulse_count_radial,
        );

        #[cfg(feature = "uom")]
        debug.field("azimuth_rate", &self.azimuth_rate());
        #[cfg(not(feature = "uom"))]
        debug.field("azimuth_rate", &self.azimuth_rate_degrees_per_second());

        debug.field("reflectivity_threshold", &self.reflectivity_threshold());
        debug.field("velocity_threshold", &self.velocity_threshold());
        debug.field("spectrum_width_threshold", &self.spectrum_width_threshold());
        debug.field(
            "differential_reflectivity_threshold",
            &self.differential_reflectivity_threshold(),
        );
        debug.field(
            "differential_phase_threshold",
            &self.differential_phase_threshold(),
        );
        debug.field(
            "correlation_coefficient_threshold",
            &self.correlation_coefficient_threshold(),
        );

        #[cfg(feature = "uom")]
        debug.field("sector_1_edge_angle", &self.sector_1_edge_angle());
        #[cfg(not(feature = "uom"))]
        debug.field("sector_1_edge_angle", &self.sector_1_edge_angle_degrees());

        debug.field(
            "sector_1_doppler_prf_number",
            &self.sector_1_doppler_prf_number,
        );
        debug.field(
            "sector_1_doppler_prf_pulse_count_radial",
            &self.sector_1_doppler_prf_pulse_count_radial,
        );

        #[cfg(feature = "uom")]
        debug.field("sector_2_edge_angle", &self.sector_2_edge_angle());
        #[cfg(not(feature = "uom"))]
        debug.field("sector_2_edge_angle", &self.sector_2_edge_angle_degrees());

        debug.field(
            "sector_2_doppler_prf_number",
            &self.sector_2_doppler_prf_number,
        );
        debug.field(
            "sector_2_doppler_prf_pulse_count_radial",
            &self.sector_2_doppler_prf_pulse_count_radial,
        );

        #[cfg(feature = "uom")]
        debug.field("sector_3_edge_angle", &self.sector_3_edge_angle());
        #[cfg(not(feature = "uom"))]
        debug.field("sector_3_edge_angle", &self.sector_3_edge_angle_degrees());

        debug.field(
            "sector_3_doppler_prf_number",
            &self.sector_3_doppler_prf_number,
        );
        debug.field(
            "sector_3_doppler_prf_pulse_count_radial",
            &self.sector_3_doppler_prf_pulse_count_radial,
        );

        #[cfg(feature = "uom")]
        debug.field("ebc_angle", &self.ebc_angle());
        #[cfg(not(feature = "uom"))]
        debug.field("ebc_angle", &self.ebc_angle_degrees());

        debug.field("supplemental_data", &self.supplemental_data);
        debug.field(
            "supplemental_data_sails_cut",
            &self.supplemental_data_sails_cut(),
        );
        debug.field(
            "supplemental_data_sails_sequence_number",
            &self.supplemental_data_sails_sequence_number(),
        );
        debug.field(
            "supplemental_data_mrle_cut",
            &self.supplemental_data_mrle_cut(),
        );
        debug.field(
            "supplemental_data_mrle_sequence_number",
            &self.supplemental_data_mrle_sequence_number(),
        );
        debug.field(
            "supplemental_data_mpda_cut",
            &self.supplemental_data_mpda_cut(),
        );
        debug.field(
            "supplemental_data_base_tilt_cut",
            &self.supplemental_data_base_tilt_cut(),
        );

        debug.field("reserved", &self.reserved);

        debug.finish()
    }
}