sounding_analysis/
layers.rs1use crate::sounding::DataRow;
8use metfor::{CelsiusDiff, CelsiusPKm, HectoPascal, Km, Meters, MetersPSec, WindUV};
9
10#[derive(Debug, Clone, Copy)]
12pub struct Layer {
13 pub bottom: DataRow,
15 pub top: DataRow,
17}
18
19pub type Layers = Vec<Layer>;
21
22impl Layer {
23 pub fn lapse_rate(&self) -> Option<CelsiusPKm> {
25 let top_t = self.top.temperature.into_option()?;
26 let bottom_t = self.bottom.temperature.into_option()?;
27
28 #[allow(clippy::useless_conversion)]
29 let CelsiusDiff(dt) = CelsiusDiff::from(top_t - bottom_t);
30 let Km(dz) = Km::from(self.height_thickness()?);
31
32 Some(CelsiusPKm(dt / dz))
33 }
34
35 pub fn height_thickness(&self) -> Option<Meters> {
37 let top = self.top.height.into_option()?;
38 let bottom = self.bottom.height.into_option()?;
39 if top == bottom {
40 None
41 } else {
42 Some(top - bottom)
43 }
44 }
45
46 pub fn pressure_thickness(&self) -> Option<HectoPascal> {
48 let bottom_p = self.bottom.pressure.into_option()?;
49 let top_p = self.top.pressure.into_option()?;
50 if bottom_p == top_p {
51 None
52 } else {
53 Some(bottom_p - top_p)
54 }
55 }
56
57 pub fn wind_shear(&self) -> Option<WindUV<MetersPSec>> {
59 let top = WindUV::from(self.top.wind.into_option()?);
60 let bottom = WindUV::from(self.bottom.wind.into_option()?);
61
62 Some(top - bottom)
63 }
64}
65
66#[cfg(test)]
67mod layer_tests {
68 use super::*;
69 use crate::sounding::DataRow;
70 use metfor::*;
71 use optional::some;
72
73 fn make_test_layer() -> Layer {
74 let mut bottom = DataRow::default();
75 bottom.pressure = some(HectoPascal(1000.0));
76 bottom.temperature = some(Celsius(20.0));
77 bottom.height = some(Meters(5.0));
78 bottom.wind = some(WindSpdDir::<Knots> {
79 speed: Knots(1.0),
80 direction: 180.0,
81 });
82
83 let mut top = DataRow::default();
84 top.pressure = some(HectoPascal(700.0));
85 top.temperature = some(Celsius(-2.0));
86 top.height = some(Meters(3012.0));
87 top.wind = some(WindSpdDir::<Knots> {
88 speed: Knots(1.0),
89 direction: 90.0,
90 });
91
92 Layer { bottom, top }
93 }
94
95 fn approx_eq(a: f64, b: f64, tol: f64) -> bool {
96 (a - b).abs() <= tol
97 }
98
99 #[test]
100 fn test_height_thickness() {
101 let lyr = make_test_layer();
102 println!("{:#?}", lyr);
103 assert!(lyr
104 .height_thickness()
105 .unwrap()
106 .approx_eq(Meters(3007.0), Meters(std::f64::EPSILON)));
107 }
108
109 #[test]
110 fn test_pressure_thickness() {
111 let lyr = make_test_layer();
112 println!("{:#?}", lyr);
113 assert!(lyr
114 .pressure_thickness()
115 .unwrap()
116 .approx_eq(HectoPascal(300.0), HectoPascal(std::f64::EPSILON)));
117 }
118
119 #[test]
120 fn test_lapse_rate() {
121 let lyr = make_test_layer();
122 println!(
123 "{:#?}\n\n -- \n\n Lapse Rate = {:#?} \n\n --",
124 lyr,
125 lyr.lapse_rate().unwrap()
126 );
127 assert!(lyr
128 .lapse_rate()
129 .unwrap()
130 .approx_eq(CelsiusPKm(-7.31626), CelsiusPKm(1.0e-5)));
131 }
132
133 #[test]
134 fn test_wind_shear() {
135 let lyr = make_test_layer();
136 println!(
137 "{:#?}\n\n -- \n\n {:#?} \n\n --",
138 lyr,
139 lyr.wind_shear().unwrap()
140 );
141 let shear = WindSpdDir::<Knots>::from(lyr.wind_shear().unwrap());
142 let speed_shear = shear.abs();
143 let WindSpdDir {
144 direction: direction_shear,
145 ..
146 } = shear;
147
148 assert!(speed_shear.approx_eq(Knots(::std::f64::consts::SQRT_2), Knots(1.0e-5)));
149 assert!(approx_eq(direction_shear, 45.0, 1.0e-5));
150 }
151}
152
153mod temperature_layers;
154pub use temperature_layers::{
155 cold_surface_temperature_layer, dendritic_snow_zone, hail_growth_zone,
156 melting_freezing_energy_area, warm_surface_temperature_layer, warm_temperature_layer_aloft,
157 warm_wet_bulb_layer_aloft,
158};
159
160mod height_pressure;
161pub use height_pressure::{layer_agl, pressure_layer};
162
163mod inversions;
164pub use inversions::{inversions, sfc_based_inversion};
165
166mod convective;
167pub use convective::effective_inflow_layer;