Skip to main content

nexrad_model/data/
radial.rs

1use crate::data::MomentData;
2use std::fmt::Debug;
3
4#[cfg(feature = "chrono")]
5use chrono::{DateTime, Utc};
6
7#[cfg(feature = "uom")]
8use uom::si::{angle::degree, f32::Angle};
9
10#[cfg(feature = "serde")]
11use serde::{Deserialize, Serialize};
12
13/// A single radar ray composed of a series of gates. This represents a single azimuth angle and
14/// elevation angle pair at a point in time and contains the Level II data (reflectivity, velocity,
15/// and spectrum width) for each range gate in that ray. The range of the radar and gate interval
16/// distance determines the resolution of the ray and the number of gates in the ray.
17#[derive(Clone, PartialEq)]
18#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
19pub struct Radial {
20    collection_timestamp: i64,
21
22    azimuth_number: u16,
23    azimuth_angle_degrees: f32,
24    azimuth_spacing_degrees: f32,
25
26    radial_status: RadialStatus,
27
28    elevation_number: u8,
29    elevation_angle_degrees: f32,
30
31    reflectivity: Option<MomentData>,
32    velocity: Option<MomentData>,
33    spectrum_width: Option<MomentData>,
34    differential_reflectivity: Option<MomentData>,
35    differential_phase: Option<MomentData>,
36    correlation_coefficient: Option<MomentData>,
37    specific_differential_phase: Option<MomentData>,
38}
39
40impl Radial {
41    /// Create a new radial with the given properties.
42    pub fn new(
43        collection_timestamp: i64,
44        azimuth_number: u16,
45        azimuth_angle_degrees: f32,
46        azimuth_spacing_degrees: f32,
47        radial_status: RadialStatus,
48        elevation_number: u8,
49        elevation_angle_degrees: f32,
50        reflectivity: Option<MomentData>,
51        velocity: Option<MomentData>,
52        spectrum_width: Option<MomentData>,
53        differential_reflectivity: Option<MomentData>,
54        differential_phase: Option<MomentData>,
55        correlation_coefficient: Option<MomentData>,
56        specific_differential_phase: Option<MomentData>,
57    ) -> Self {
58        Self {
59            collection_timestamp,
60            azimuth_number,
61            azimuth_angle_degrees,
62            azimuth_spacing_degrees,
63            radial_status,
64            elevation_number,
65            elevation_angle_degrees,
66            reflectivity,
67            velocity,
68            spectrum_width,
69            differential_reflectivity,
70            differential_phase,
71            correlation_coefficient,
72            specific_differential_phase,
73        }
74    }
75
76    /// The collection timestamp in milliseconds since midnight Jan 1, 1970 (epoch/UNIX timestamp).
77    pub fn collection_timestamp(&self) -> i64 {
78        self.collection_timestamp
79    }
80
81    /// The collection time for this radial and its data.
82    #[cfg(feature = "chrono")]
83    pub fn collection_time(&self) -> Option<DateTime<Utc>> {
84        DateTime::from_timestamp_millis(self.collection_timestamp)
85    }
86
87    /// The index number for this radial's azimuth in the elevation sweep, ranging up to 720
88    /// depending on the azimuthal resolution.
89    pub fn azimuth_number(&self) -> u16 {
90        self.azimuth_number
91    }
92
93    /// Azimuth angle this radial's data was collected at in degrees.
94    pub fn azimuth_angle_degrees(&self) -> f32 {
95        self.azimuth_angle_degrees
96    }
97
98    /// Azimuth angle this radial's data was collected at.
99    #[cfg(feature = "uom")]
100    pub fn azimuth(&self) -> Angle {
101        Angle::new::<degree>(self.azimuth_angle_degrees)
102    }
103
104    /// Azimuthal distance between radials in the sweep in degrees.
105    pub fn azimuth_spacing_degrees(&self) -> f32 {
106        self.azimuth_spacing_degrees
107    }
108
109    /// Azimuthal distance between radials in the sweep.
110    #[cfg(feature = "uom")]
111    pub fn azimuth_spacing(&self) -> Angle {
112        Angle::new::<degree>(self.azimuth_spacing_degrees)
113    }
114
115    /// The radial's position in the sequence of radials making up a scan.
116    pub fn radial_status(&self) -> RadialStatus {
117        self.radial_status
118    }
119
120    /// The elevation number for this radial in the volume scan.
121    pub fn elevation_number(&self) -> u8 {
122        self.elevation_number
123    }
124
125    /// Elevation angle this radial's data was collected at in degrees.
126    pub fn elevation_angle_degrees(&self) -> f32 {
127        self.elevation_angle_degrees
128    }
129
130    /// Elevation angle this radial's data was collected at.
131    #[cfg(feature = "uom")]
132    pub fn elevation_angle(&self) -> Angle {
133        Angle::new::<degree>(self.elevation_angle_degrees)
134    }
135
136    /// Reflectivity data for this radial if available.
137    pub fn reflectivity(&self) -> Option<&MomentData> {
138        self.reflectivity.as_ref()
139    }
140
141    /// Velocity data for this radial if available.
142    pub fn velocity(&self) -> Option<&MomentData> {
143        self.velocity.as_ref()
144    }
145
146    /// Spectrum width data for this radial if available.
147    pub fn spectrum_width(&self) -> Option<&MomentData> {
148        self.spectrum_width.as_ref()
149    }
150
151    /// Differential reflectivity data for this radial if available.
152    pub fn differential_reflectivity(&self) -> Option<&MomentData> {
153        self.differential_reflectivity.as_ref()
154    }
155
156    /// Differential phase data for this radial if available.
157    pub fn differential_phase(&self) -> Option<&MomentData> {
158        self.differential_phase.as_ref()
159    }
160
161    /// Correlation coefficient data for this radial if available.
162    pub fn correlation_coefficient(&self) -> Option<&MomentData> {
163        self.correlation_coefficient.as_ref()
164    }
165
166    /// Specific differential phase data for this radial if available.
167    pub fn specific_differential_phase(&self) -> Option<&MomentData> {
168        self.specific_differential_phase.as_ref()
169    }
170}
171
172impl Debug for Radial {
173    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
174        let mut debug = f.debug_struct("Radial");
175
176        debug.field("collection_timestamp", &self.collection_timestamp());
177
178        #[cfg(feature = "chrono")]
179        debug.field("collection_time", &self.collection_time());
180
181        debug.field("azimuth_number", &self.azimuth_number());
182
183        debug.field("azimuth_angle_degrees", &self.azimuth_angle_degrees());
184
185        #[cfg(feature = "uom")]
186        debug.field("azimuth_angle", &self.azimuth());
187
188        debug.field("azimuth_spacing_degrees", &self.azimuth_spacing_degrees());
189
190        #[cfg(feature = "uom")]
191        debug.field("azimuth_spacing", &self.azimuth_spacing());
192
193        debug.field("radial_status", &self.radial_status());
194
195        debug.field("elevation_number", &self.elevation_number());
196
197        debug.field("elevation_angle_degrees", &self.elevation_angle_degrees());
198
199        #[cfg(feature = "uom")]
200        debug.field("elevation_angle", &self.elevation_angle());
201
202        debug.field("reflectivity", &self.reflectivity());
203
204        debug.field("velocity", &self.velocity());
205
206        debug.field("spectrum_width", &self.spectrum_width());
207
208        debug.field(
209            "differential_reflectivity",
210            &self.differential_reflectivity(),
211        );
212
213        debug.field("differential_phase", &self.differential_phase());
214
215        debug.field("correlation_coefficient", &self.correlation_coefficient());
216
217        debug.field(
218            "specific_differential_phase",
219            &self.specific_differential_phase(),
220        );
221
222        debug.finish()
223    }
224}
225
226/// Describe a radial's position within the sequence of radials comprising a scan.
227#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
228#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
229pub enum RadialStatus {
230    ElevationStart,
231    IntermediateRadialData,
232    ElevationEnd,
233    VolumeScanStart,
234    VolumeScanEnd,
235    /// Start of new elevation which is the last in the VCP.
236    ElevationStartVCPFinal,
237}