Skip to main content

nexrad_decode/messages/volume_coverage_pattern/
elevation_data_block.rs

1use serde::Deserialize;
2use std::fmt::Debug;
3
4use crate::messages::primitive_aliases::{Code1, Code2, Integer1, Integer2, ScaledSInteger2};
5use crate::messages::volume_coverage_pattern::definitions::{ChannelConfiguration, WaveformType};
6
7#[cfg(feature = "uom")]
8use uom::si::{
9    angle::degree,
10    angular_velocity::degree_per_second,
11    f64::{Angle, AngularVelocity},
12};
13
14/// A data block for a single elevation cut.
15#[derive(Clone, PartialEq, Deserialize)]
16pub struct ElevationDataBlock {
17    /// The elevation angle for this cut
18    pub elevation_angle: Code2,
19
20    /// The channel configuration for this cut
21    /// 0 => Constant Phase
22    /// 1 => Random Phase
23    /// 2 => SZ2 Phase
24    pub channel_configuration: Code1,
25
26    /// The waveform type for this cut
27    /// 1 => Contiguous Surveillance
28    /// 2 => Contiguous Doppler w/ Ambiguity Resolution
29    /// 3 => Contiguous Doppler w/o Ambiguity Resolution
30    /// 4 => Batch
31    /// 5 => Staggered Pulse Pair
32    pub waveform_type: Code1,
33
34    /// Super resolution control values for this cut
35    /// Bit 0: 0.5 degree azimuth
36    /// Bit 1: 1/4 km reflectivity
37    /// Bit 2: Doppler to 300 km
38    /// Bit 3: Dual polarization to 300 km
39    pub super_resolution_control: Code1,
40
41    /// The pulse repetition frequency number for surveillance cuts
42    pub surveillance_prf_number: Integer1,
43
44    /// The pulse count per radial for surveillance cuts
45    pub surveillance_prf_pulse_count_radial: Integer2,
46
47    /// The azimuth rate of the cut
48    pub azimuth_rate: Code2,
49
50    /// Signal to noise ratio (SNR) threshold for reflectivity
51    pub reflectivity_threshold: ScaledSInteger2,
52
53    /// Signal to noise ratio (SNR) threshold for velocity
54    pub velocity_threshold: ScaledSInteger2,
55
56    /// Signal to noise ratio (SNR) threshold for spectrum width
57    pub spectrum_width_threshold: ScaledSInteger2,
58
59    /// Signal to noise ratio (SNR) threshold for differential reflectivity
60    pub differential_reflectivity_threshold: ScaledSInteger2,
61
62    /// Signal to noise ratio (SNR) threshold for differential phase
63    pub differential_phase_threshold: ScaledSInteger2,
64
65    /// Signal to noise ratio (SNR) threshold for correlation coefficitn
66    pub correlation_coefficient_threshold: ScaledSInteger2,
67
68    /// Sector 1 Azimuth Clockwise Edge Angle (denotes start angle)
69    pub sector_1_edge_angle: Code2,
70
71    /// Sector 1 Doppler PRF Number
72    pub sector_1_doppler_prf_number: Integer2,
73
74    /// Sector 1 Doppler Pulse Count/Radial
75    pub sector_1_doppler_prf_pulse_count_radial: Integer2,
76
77    /// Supplemental Data
78    /// Bit 0:    SAILS Cut
79    /// Bits 1-3: SAILS Sequence Number
80    /// Bit 4:    MRLE Cut
81    /// Bits 5-7: MRLE Sequence Number
82    /// Bit 8:    Spare
83    /// Bit 9:    MPDA Cut
84    /// Bit 10:   BASE TILT Cut
85    pub supplemental_data: Code2,
86
87    /// Sector 2 Azimuth Clockwise Edge Angle (denotes start angle)
88    pub sector_2_edge_angle: Code2,
89
90    /// Sector 2 Doppler PRF Number
91    pub sector_2_doppler_prf_number: Integer2,
92
93    /// Sector 2 Doppler Pulse Count/Radial
94    pub sector_2_doppler_prf_pulse_count_radial: Integer2,
95
96    /// The correction added to the elevation angle for this cut
97    pub ebc_angle: Code2,
98
99    /// Sector 3 Azimuth Clockwise Edge Angle (denotes start angle)
100    pub sector_3_edge_angle: Code2,
101
102    /// Sector 3 Doppler PRF Number
103    pub sector_3_doppler_prf_number: Integer2,
104
105    /// Sector 3 Doppler Pulse Count/Radial
106    pub sector_3_doppler_prf_pulse_count_radial: Integer2,
107
108    /// Reserved
109    pub reserved: Integer2,
110}
111
112/// Decodes an angle as defined in table III-A of ICD 2620002W
113fn decode_angle(raw: Code2) -> f64 {
114    let mut angle: f64 = 0.0;
115    for i in 3..16 {
116        if ((raw >> i) & 1) == 1 {
117            angle += 180.0 * f64::powf(2.0, (i - 15) as f64);
118        }
119    }
120
121    return angle;
122}
123
124/// Decodes an angular velocity as defined in table XI-D of ICD 2620002W
125fn decode_angular_velocity(raw: Code2) -> f64 {
126    let mut angular_velocity: f64 = 0.0;
127
128    for i in 3..15 {
129        if ((raw >> i) & 1) == 1 {
130            angular_velocity += 22.5 * f64::powf(2.0, (i - 14) as f64);
131        }
132    }
133
134    if ((raw >> 15) & 1) == 1 {
135        angular_velocity = -angular_velocity
136    }
137
138    return angular_velocity;
139}
140
141impl ElevationDataBlock {
142    /// The elevation angle for this cut
143    #[cfg(feature = "uom")]
144    pub fn elevation_angle(&self) -> Angle {
145        Angle::new::<degree>(decode_angle(self.elevation_angle))
146    }
147
148    /// The elevation angle for this cut, in degrees
149    pub fn elevation_angle_degrees(&self) -> f64 {
150        decode_angle(self.elevation_angle)
151    }
152
153    /// The channel configuration for this cut
154    pub fn channel_configuration(&self) -> ChannelConfiguration {
155        match self.channel_configuration {
156            0 => ChannelConfiguration::ConstantPhase,
157            1 => ChannelConfiguration::RandomPhase,
158            2 => ChannelConfiguration::SZ2Phase,
159            _ => ChannelConfiguration::UnknownPhase,
160        }
161    }
162
163    /// The waveform type for this cut
164    pub fn waveform_type(&self) -> WaveformType {
165        match self.waveform_type {
166            1 => WaveformType::CS,
167            2 => WaveformType::CDW,
168            3 => WaveformType::CDWO,
169            4 => WaveformType::B,
170            5 => WaveformType::SPP,
171            _ => WaveformType::Unknown,
172        }
173    }
174
175    /// Whether this cut uses super resolution 0.5 degree azimuth
176    pub fn super_resolution_control_half_degree_azimuth(&self) -> bool {
177        (self.super_resolution_control & 0x1) == 1
178    }
179
180    /// Whether this cut uses super resolution 0.25 km reflectivity
181    pub fn super_resolution_control_quarter_km_reflectivity(&self) -> bool {
182        ((self.super_resolution_control >> 1) & 0x1) == 1
183    }
184
185    /// Whether this cut uses super resolution doppler to 300 km
186    pub fn super_resolution_control_doppler_to_300km(&self) -> bool {
187        ((self.super_resolution_control >> 2) & 0x1) == 1
188    }
189
190    /// Whether this cut uses super resolution dual polarization to 300km
191    pub fn super_resolution_control_dual_polarization_to_300km(&self) -> bool {
192        ((self.super_resolution_control >> 3) & 0x1) == 1
193    }
194
195    /// The azimuth rate used for this cut
196    #[cfg(feature = "uom")]
197    pub fn azimuth_rate(&self) -> AngularVelocity {
198        AngularVelocity::new::<degree_per_second>(decode_angular_velocity(self.azimuth_rate))
199    }
200
201    /// The azimuth rate used for this cut, in degrees per second
202    pub fn azimuth_rate_degrees_per_second(&self) -> f64 {
203        decode_angular_velocity(self.azimuth_rate)
204    }
205
206    /// The reflectivity threshold for this cut
207    pub fn reflectivity_threshold(&self) -> f64 {
208        self.reflectivity_threshold as f64 * 0.125
209    }
210
211    /// The velocity threshold for this cut
212    pub fn velocity_threshold(&self) -> f64 {
213        self.velocity_threshold as f64 * 0.125
214    }
215
216    /// The spectrum width threshold for this cut
217    pub fn spectrum_width_threshold(&self) -> f64 {
218        self.spectrum_width_threshold as f64 * 0.125
219    }
220
221    /// The differential reflectivity threshold for this cut
222    pub fn differential_reflectivity_threshold(&self) -> f64 {
223        self.differential_reflectivity_threshold as f64 * 0.125
224    }
225
226    /// The differential phase threshold for this cut
227    pub fn differential_phase_threshold(&self) -> f64 {
228        self.differential_phase_threshold as f64 * 0.125
229    }
230
231    /// The correlation coefficient threshold for this cut
232    pub fn correlation_coefficient_threshold(&self) -> f64 {
233        self.correlation_coefficient_threshold as f64 * 0.125
234    }
235
236    /// Sector 1 Azimuth Clockwise Edge Angle (denotes start angle)
237    #[cfg(feature = "uom")]
238    pub fn sector_1_edge_angle(&self) -> Angle {
239        Angle::new::<degree>(decode_angle(self.sector_1_edge_angle))
240    }
241
242    /// Sector 1 Azimuth Clockwise Edge Angle (denotes start angle), in degrees
243    pub fn sector_1_edge_angle_degrees(&self) -> f64 {
244        decode_angle(self.sector_1_edge_angle)
245    }
246
247    /// Sector 2 Azimuth Clockwise Edge Angle (denotes start angle)
248    #[cfg(feature = "uom")]
249    pub fn sector_2_edge_angle(&self) -> Angle {
250        Angle::new::<degree>(decode_angle(self.sector_2_edge_angle))
251    }
252
253    /// Sector 2 Azimuth Clockwise Edge Angle (denotes start angle), in degrees
254    pub fn sector_2_edge_angle_degrees(&self) -> f64 {
255        decode_angle(self.sector_2_edge_angle)
256    }
257
258    /// Sector 3 Azimuth Clockwise Edge Angle (denotes start angle)
259    #[cfg(feature = "uom")]
260    pub fn sector_3_edge_angle(&self) -> Angle {
261        Angle::new::<degree>(decode_angle(self.sector_3_edge_angle))
262    }
263
264    /// Sector 3 Azimuth Clockwise Edge Angle (denotes start angle), in degrees
265    pub fn sector_3_edge_angle_degrees(&self) -> f64 {
266        decode_angle(self.sector_3_edge_angle)
267    }
268
269    /// The correction added to the elevation angle for this cut
270    #[cfg(feature = "uom")]
271    pub fn ebc_angle(&self) -> Angle {
272        Angle::new::<degree>(decode_angle(self.ebc_angle))
273    }
274
275    /// The correction added to the elevation angle for this cut, in degrees
276    pub fn ebc_angle_degrees(&self) -> f64 {
277        decode_angle(self.ebc_angle)
278    }
279
280    /// Whether this cut is a SAILS cut
281    pub fn supplemental_data_sails_cut(&self) -> bool {
282        (self.supplemental_data & 0x0001) == 1
283    }
284
285    /// The SAILS sequence number of this cut
286    pub fn supplemental_data_sails_sequence_number(&self) -> u8 {
287        ((self.supplemental_data & 0x000E) >> 1) as u8
288    }
289
290    /// Whether this cut is an MRLE cut
291    pub fn supplemental_data_mrle_cut(&self) -> bool {
292        ((self.supplemental_data & 0x0010) >> 4) == 1
293    }
294
295    /// The MRLE sequence number of this cut
296    pub fn supplemental_data_mrle_sequence_number(&self) -> u8 {
297        ((self.supplemental_data & 0x00E0) >> 5) as u8
298    }
299
300    /// Whether this cut is an MPDA cut
301    pub fn supplemental_data_mpda_cut(&self) -> bool {
302        ((self.supplemental_data & 0x0200) >> 9) == 1
303    }
304
305    /// Whether this cut is a BASE TILT cut
306    pub fn supplemental_data_base_tilt_cut(&self) -> bool {
307        ((self.supplemental_data & 0x0400) >> 10) == 1
308    }
309}
310
311impl Debug for ElevationDataBlock {
312    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
313        let mut debug = f.debug_struct("ElevationDataBlock");
314
315        #[cfg(feature = "uom")]
316        debug.field("elevation_angle", &self.elevation_angle());
317        #[cfg(not(feature = "uom"))]
318        debug.field("elevation_angle", &self.elevation_angle_degrees());
319
320        debug.field("channel_configuration", &self.channel_configuration());
321        debug.field("waveform_type", &self.waveform_type());
322
323        debug.field(
324            "super_resolution_control_raw",
325            &self.super_resolution_control,
326        );
327        debug.field(
328            "super_resolution_control_half_degree_azimuth",
329            &self.super_resolution_control_half_degree_azimuth(),
330        );
331        debug.field(
332            "super_resolution_control_quarter_km_reflectivity",
333            &self.super_resolution_control_quarter_km_reflectivity(),
334        );
335        debug.field(
336            "super_resolution_control_doppler_to_300km",
337            &self.super_resolution_control_doppler_to_300km(),
338        );
339        debug.field(
340            "super_resolution_control_dual_polarization_to_300km",
341            &self.super_resolution_control_dual_polarization_to_300km(),
342        );
343
344        debug.field("surveillance_prf_number", &self.surveillance_prf_number);
345        debug.field(
346            "surveillance_prf_pulse_count_radial",
347            &self.surveillance_prf_pulse_count_radial,
348        );
349
350        #[cfg(feature = "uom")]
351        debug.field("azimuth_rate", &self.azimuth_rate());
352        #[cfg(not(feature = "uom"))]
353        debug.field("azimuth_rate", &self.azimuth_rate_degrees_per_second());
354
355        debug.field("reflectivity_threshold", &self.reflectivity_threshold());
356        debug.field("velocity_threshold", &self.velocity_threshold());
357        debug.field("spectrum_width_threshold", &self.spectrum_width_threshold());
358        debug.field(
359            "differential_reflectivity_threshold",
360            &self.differential_reflectivity_threshold(),
361        );
362        debug.field(
363            "differential_phase_threshold",
364            &self.differential_phase_threshold(),
365        );
366        debug.field(
367            "correlation_coefficient_threshold",
368            &self.correlation_coefficient_threshold(),
369        );
370
371        #[cfg(feature = "uom")]
372        debug.field("sector_1_edge_angle", &self.sector_1_edge_angle());
373        #[cfg(not(feature = "uom"))]
374        debug.field("sector_1_edge_angle", &self.sector_1_edge_angle_degrees());
375
376        debug.field(
377            "sector_1_doppler_prf_number",
378            &self.sector_1_doppler_prf_number,
379        );
380        debug.field(
381            "sector_1_doppler_prf_pulse_count_radial",
382            &self.sector_1_doppler_prf_pulse_count_radial,
383        );
384
385        #[cfg(feature = "uom")]
386        debug.field("sector_2_edge_angle", &self.sector_2_edge_angle());
387        #[cfg(not(feature = "uom"))]
388        debug.field("sector_2_edge_angle", &self.sector_2_edge_angle_degrees());
389
390        debug.field(
391            "sector_2_doppler_prf_number",
392            &self.sector_2_doppler_prf_number,
393        );
394        debug.field(
395            "sector_2_doppler_prf_pulse_count_radial",
396            &self.sector_2_doppler_prf_pulse_count_radial,
397        );
398
399        #[cfg(feature = "uom")]
400        debug.field("sector_3_edge_angle", &self.sector_3_edge_angle());
401        #[cfg(not(feature = "uom"))]
402        debug.field("sector_3_edge_angle", &self.sector_3_edge_angle_degrees());
403
404        debug.field(
405            "sector_3_doppler_prf_number",
406            &self.sector_3_doppler_prf_number,
407        );
408        debug.field(
409            "sector_3_doppler_prf_pulse_count_radial",
410            &self.sector_3_doppler_prf_pulse_count_radial,
411        );
412
413        #[cfg(feature = "uom")]
414        debug.field("ebc_angle", &self.ebc_angle());
415        #[cfg(not(feature = "uom"))]
416        debug.field("ebc_angle", &self.ebc_angle_degrees());
417
418        debug.field("supplemental_data", &self.supplemental_data);
419        debug.field(
420            "supplemental_data_sails_cut",
421            &self.supplemental_data_sails_cut(),
422        );
423        debug.field(
424            "supplemental_data_sails_sequence_number",
425            &self.supplemental_data_sails_sequence_number(),
426        );
427        debug.field(
428            "supplemental_data_mrle_cut",
429            &self.supplemental_data_mrle_cut(),
430        );
431        debug.field(
432            "supplemental_data_mrle_sequence_number",
433            &self.supplemental_data_mrle_sequence_number(),
434        );
435        debug.field(
436            "supplemental_data_mpda_cut",
437            &self.supplemental_data_mpda_cut(),
438        );
439        debug.field(
440            "supplemental_data_base_tilt_cut",
441            &self.supplemental_data_base_tilt_cut(),
442        );
443
444        debug.field("reserved", &self.reserved);
445
446        debug.finish()
447    }
448}