sounding_analysis/
indexes.rs

1//! Indexes that are specific to a sounding, but not a particular parcel analysis of that sounding.
2
3use 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/// Precipitable water (mm)
12#[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        // Remove levels with missing data
19        .filter(|(p, dp)| p.is_some() && dp.is_some())
20        // Unpack from the Optioned type
21        .map(|(p, dp)| (p.unpack(), dp.unpack()))
22        // Converte dew point to mixing ratio, removing failed levels.
23        .filter_map(|(p, dp)| mixing_ratio(dp, p).map(|mw| (p, mw)))
24        // View them as pairs for integration with the trapezoid method
25        .tuple_windows::<(_, _)>()
26        // Do the sum for integrating
27        .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/// The Haines index for fire weather.
38#[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/// The low level version of the Haines index for fire weather.
56#[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/// The mid level version of the Haines index for fire weather.
89#[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/// The high level version of the Haines index for fire weather.
122#[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}