sounding_analysis/
indexes.rs1use crate::{
4 error::{AnalysisError, Result},
5 interpolation::linear_interpolate_sounding,
6 sounding::Sounding,
7};
8use itertools::{izip, Itertools};
9use metfor::{mixing_ratio, Celsius, HectoPascal, Meters, Mm, Quantity};
10
11#[inline]
13pub fn precipitable_water(snd: &Sounding) -> Result<Mm> {
14 let p_profile = snd.pressure_profile();
15 let dp_profile = snd.dew_point_profile();
16
17 let integrated_mw = izip!(p_profile, dp_profile)
18 .filter(|(p, dp)| p.is_some() && dp.is_some())
20 .map(|(p, dp)| (p.unpack(), dp.unpack()))
22 .filter_map(|(p, dp)| mixing_ratio(dp, p).map(|mw| (p, mw)))
24 .tuple_windows::<(_, _)>()
26 .fold(0.0, |mut acc_mw, ((p0, mw0), (p1, mw1))| {
28 let dp = p0 - p1;
29 acc_mw += (mw0 + mw1) * dp.unpack();
30
31 acc_mw
32 });
33
34 Ok(Mm(integrated_mw / 9.81 / 997.0 * 100_000.0 / 2.0))
35}
36
37#[inline]
39pub fn haines(snd: &Sounding) -> Result<u8> {
40 snd.station_info()
41 .elevation()
42 .into_option()
43 .ok_or(AnalysisError::MissingValue)
44 .and_then(|elev| {
45 if elev <= Meters(304.8) {
46 haines_low(snd)
47 } else if elev <= Meters(914.4) {
48 haines_mid(snd)
49 } else {
50 haines_high(snd)
51 }
52 })
53}
54
55#[inline]
57pub fn haines_low(snd: &Sounding) -> Result<u8> {
58 let level1 = linear_interpolate_sounding(snd, HectoPascal(950.0))
59 .map_err(|_| AnalysisError::MissingValue)?;
60 let level2 = linear_interpolate_sounding(snd, HectoPascal(850.0))
61 .map_err(|_| AnalysisError::MissingValue)?;
62
63 let Celsius(t_low) = level1.temperature.ok_or(AnalysisError::MissingValue)?;
64 let Celsius(t_hi) = level2.temperature.ok_or(AnalysisError::MissingValue)?;
65 let Celsius(dp_hi) = level2.dew_point.ok_or(AnalysisError::MissingValue)?;
66
67 let stability_term = (t_low - t_hi).round();
68 let stability_term = if stability_term >= 8.0 {
69 3
70 } else if stability_term > 3.0 {
71 2
72 } else {
73 1
74 };
75
76 let moisture_term = (t_hi - dp_hi).round();
77 let moisture_term = if moisture_term >= 10.0 {
78 3
79 } else if moisture_term > 5.0 {
80 2
81 } else {
82 1
83 };
84
85 Ok(stability_term + moisture_term)
86}
87
88#[inline]
90pub fn haines_mid(snd: &Sounding) -> Result<u8> {
91 let level1 = linear_interpolate_sounding(snd, HectoPascal(850.0))
92 .map_err(|_| AnalysisError::MissingValue)?;
93 let level2 = linear_interpolate_sounding(snd, HectoPascal(700.0))
94 .map_err(|_| AnalysisError::MissingValue)?;
95
96 let Celsius(t_low) = level1.temperature.ok_or(AnalysisError::MissingValue)?;
97 let Celsius(t_hi) = level2.temperature.ok_or(AnalysisError::MissingValue)?;
98 let Celsius(dp_low) = level1.dew_point.ok_or(AnalysisError::MissingValue)?;
99
100 let stability_term = (t_low - t_hi).round();
101 let stability_term = if stability_term >= 11.0 {
102 3
103 } else if stability_term > 5.0 {
104 2
105 } else {
106 1
107 };
108
109 let moisture_term = (t_low - dp_low).round();
110 let moisture_term = if moisture_term >= 13.0 {
111 3
112 } else if moisture_term > 5.0 {
113 2
114 } else {
115 1
116 };
117
118 Ok(stability_term + moisture_term)
119}
120
121#[inline]
123pub fn haines_high(snd: &Sounding) -> Result<u8> {
124 let level1 = linear_interpolate_sounding(snd, HectoPascal(700.0))
125 .map_err(|_| AnalysisError::MissingValue)?;
126 let level2 = linear_interpolate_sounding(snd, HectoPascal(500.0))
127 .map_err(|_| AnalysisError::MissingValue)?;
128
129 let Celsius(t_low) = level1.temperature.ok_or(AnalysisError::MissingValue)?;
130 let Celsius(t_hi) = level2.temperature.ok_or(AnalysisError::MissingValue)?;
131 let Celsius(dp_low) = level1.dew_point.ok_or(AnalysisError::MissingValue)?;
132
133 let stability_term = (t_low - t_hi).round();
134 let stability_term = if stability_term >= 22.0 {
135 3
136 } else if stability_term > 17.0 {
137 2
138 } else {
139 1
140 };
141
142 let moisture_term = (t_low - dp_low).round();
143 let moisture_term = if moisture_term >= 21.0 {
144 3
145 } else if moisture_term > 14.0 {
146 2
147 } else {
148 1
149 };
150
151 Ok(stability_term + moisture_term)
152}