Skip to main content

nexrad_model/data/
sweep.rs

1use crate::data::Radial;
2use crate::result::{Error, Result};
3use std::fmt::{Debug, Display};
4
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7
8/// A single radar sweep composed of a series of radials. This represents a full rotation of the
9/// radar at some elevation angle and contains the Level II data (reflectivity, velocity, and
10/// spectrum width) for each azimuth angle in that sweep. The resolution of the sweep dictates the
11/// azimuthal distance between rays and thus and number of rays in the sweep. Multiple sweeps are
12/// taken at different elevation angles to create a volume scan.
13#[derive(Clone, PartialEq)]
14#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
15pub struct Sweep {
16    elevation_number: u8,
17    radials: Vec<Radial>,
18}
19
20impl Sweep {
21    /// Create a new radar sweep with the given elevation number and radials.
22    pub fn new(elevation_number: u8, radials: Vec<Radial>) -> Self {
23        Self {
24            elevation_number,
25            radials,
26        }
27    }
28
29    /// Create a new radar sweep from a list of radials by splitting them by elevation.
30    pub fn from_radials(radials: Vec<Radial>) -> Vec<Self> {
31        let mut sweeps = Vec::new();
32
33        let mut sweep_elevation_number = None;
34        let mut sweep_radials = Vec::new();
35
36        for radial in radials {
37            if let Some(elevation_number) = sweep_elevation_number {
38                if elevation_number != radial.elevation_number() {
39                    sweeps.push(Sweep::new(elevation_number, sweep_radials));
40                    sweep_radials = Vec::new();
41                }
42            }
43
44            sweep_elevation_number = Some(radial.elevation_number());
45            sweep_radials.push(radial);
46        }
47
48        sweeps
49    }
50
51    /// The index number for this radial's elevation in the volume scan. The precise elevation angle
52    /// varies and can be found in individual radials.
53    pub fn elevation_number(&self) -> u8 {
54        self.elevation_number
55    }
56
57    /// The radials comprising this sweep.
58    pub fn radials(&self) -> &Vec<Radial> {
59        self.radials.as_ref()
60    }
61
62    /// Merges this sweep with another sweep, combining their radials into a single sweep. The
63    /// sweeps must be at the same elevation, and they should not have duplicate azimuth radials.
64    pub fn merge(self, other: Self) -> Result<Self> {
65        if self.elevation_number != other.elevation_number {
66            return Err(Error::ElevationMismatchError);
67        }
68
69        let mut radials = self.radials;
70        radials.extend(other.radials);
71        radials.sort_by_key(|radial| radial.azimuth_number());
72
73        Ok(Self {
74            elevation_number: self.elevation_number,
75            radials,
76        })
77    }
78}
79
80impl Display for Sweep {
81    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82        if let (Some(first), Some(last)) = (self.radials.first(), self.radials.last()) {
83            write!(
84                f,
85                "Sweep ({:.1}-{:.1} deg, {} radials, {} deg spacing)",
86                first.azimuth_angle_degrees(),
87                last.azimuth_angle_degrees(),
88                self.radials.len(),
89                first.azimuth_spacing_degrees()
90            )
91        } else {
92            write!(f, "Sweep (no radials)")
93        }
94    }
95}
96
97impl Debug for Sweep {
98    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99        f.debug_struct("Sweep")
100            .field("elevation_number", &self.elevation_number())
101            .field("radials", &self.radials())
102            .finish()
103    }
104}