1use crate::sounding::Sounding;
13use itertools::izip;
14use metfor::{Celsius, Mm, StatuteMiles};
15use std::{convert::From, fmt::Display};
16use strum_macros::EnumIter;
17
18mod bourgouin;
19pub use bourgouin::bourgouin_precip_type;
20mod nssl;
21pub use nssl::nssl_precip_type;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumIter, Hash, PartialOrd, Ord)]
26#[repr(u8)]
27#[allow(missing_docs)]
28pub enum PrecipType {
29 None = 0,
31
32 Drizzle = 50,
34 LightDrizzle = 51,
35 ModerateDrizzle = 52,
36 HeavyDrizzle = 53,
37 LightFreezingDrizzle = 54,
38 ModerateFreezingDrizzle = 55,
39 HeavyFreezingDrizzle = 56,
40 LightDrizzleAndRain = 57,
41 ModerateDrizzleAndRain = 58,
42 Rain = 60,
46 LightRain = 61,
47 ModerateRain = 62,
48 HeavyRain = 63,
49 LightFreezingRain = 64,
50 ModerateFreezingRain = 65,
51 HeavyFreezingRain = 66,
52 LightRainAndSnow = 67,
53 ModerateRainAndSnow = 68,
54 Snow = 70,
58 LightSnow = 71,
59 ModerateSnow = 72,
60 HeavySnow = 73,
61 LightIcePellets = 74,
62 ModerateIcePellets = 75,
63 HeavyIcePellets = 76,
64 SnowGrains = 77,
65 IceCrystals = 78,
66 Showers = 80,
70 LightRainShowers = 81,
71 ModerateRainShowers = 82,
72 HeavyRainShowers = 83,
73 ViolentRainShowers = 84,
74 LightSnowShowers = 85,
75 ModerateSnowShowers = 86,
76 HeavySnowShowers = 87,
77 Hail = 89,
79
80 Unknown = 100,
84}
85
86impl From<u8> for PrecipType {
87 fn from(val: u8) -> Self {
88 use PrecipType::*;
89
90 match val {
91 0 => None,
92
93 50 => Drizzle,
95 51 => LightDrizzle,
96 52 => ModerateDrizzle,
97 53 => HeavyDrizzle,
98 54 => LightFreezingDrizzle,
99 55 => ModerateFreezingDrizzle,
100 56 => HeavyFreezingDrizzle,
101 57 => LightDrizzleAndRain,
102 58 => ModerateDrizzleAndRain,
103
104 60 => Rain,
106 61 => LightRain,
107 62 => ModerateRain,
108 63 => HeavyRain,
109 64 => LightFreezingRain,
110 65 => ModerateFreezingRain,
111 66 => HeavyFreezingRain,
112 67 => LightRainAndSnow,
113 68 => ModerateRainAndSnow,
114
115 70 => Snow,
117 71 => LightSnow,
118 72 => ModerateSnow,
119 73 => HeavySnow,
120 74 => LightIcePellets,
121 75 => ModerateIcePellets,
122 76 => HeavyIcePellets,
123 77 => SnowGrains,
124 78 => IceCrystals,
125
126 80 => Showers,
128 81 => LightRainShowers,
129 82 => ModerateRainShowers,
130 83 => HeavyRainShowers,
131 84 => ViolentRainShowers,
132 85 => LightSnowShowers,
133 86 => ModerateSnowShowers,
134 87 => HeavySnowShowers,
135
136 89 => Hail,
137
138 _ => Unknown,
139 }
140 }
141}
142
143impl Display for PrecipType {
144 fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
145 write!(formatter, "{} => {:?}", *self as u8, self)
146 }
147}
148
149fn is_drizzler(snd: &Sounding) -> bool {
150 let ts = snd.temperature_profile();
151 let tds = snd.dew_point_profile();
152
153 !izip!(ts, tds)
154 .filter(|(t, td)| t.is_some() && td.is_some())
156 .map(|(t, td)| (t.unpack(), td.unpack()))
158 .filter(|&(t, _)| t <= Celsius(-12.0) && t >= Celsius(-18.0))
160 .filter_map(|(t, td)| metfor::rh(t, td))
162 .any(|rh_val| rh_val >= 0.80)
164}
165
166pub fn check_precip_type_intensity<S1, S2, V>(
181 precip_type: PrecipType,
182 hourly_precipitation: Option<S1>,
183 convective_precipitation: Option<S2>,
184 visibility: Option<V>,
185) -> PrecipType
186where
187 S1: Into<Mm>,
188 S2: Into<Mm>,
189 V: Into<StatuteMiles>,
190{
191 use PrecipType::*;
192
193 let hourly_precipitation = hourly_precipitation.map(Into::<Mm>::into);
195 let convective_precipitation = convective_precipitation.map(Into::<Mm>::into);
196 let visibility = visibility.map(Into::<StatuteMiles>::into);
197
198 match check_preconditions_for_wx_type_adjustment(precip_type, hourly_precipitation, visibility)
200 {
201 PreconditionsCheckResult::Pass => {}
202 PreconditionsCheckResult::Fail => return precip_type,
203 PreconditionsCheckResult::NoQPF => return PrecipType::None,
204 }
205
206 let mode = get_precip_type_mode(hourly_precipitation, convective_precipitation);
207
208 let intensity = get_precip_type_intensity(precip_type, hourly_precipitation, visibility);
209
210 match precip_type {
211 LightDrizzle => match intensity {
212 Intensity::Light => LightDrizzle,
213 Intensity::Moderate => ModerateDrizzle,
214 Intensity::Heavy => HeavyDrizzle,
215 },
216 LightFreezingDrizzle => match intensity {
217 Intensity::Light => LightFreezingDrizzle,
218 Intensity::Moderate => ModerateFreezingDrizzle,
219 Intensity::Heavy => HeavyFreezingDrizzle,
220 },
221 LightDrizzleAndRain => match intensity {
222 Intensity::Light => LightDrizzleAndRain,
223 Intensity::Moderate | Intensity::Heavy => ModerateDrizzleAndRain,
224 },
225 LightRain => match intensity {
226 Intensity::Light => match mode {
227 Mode::Convective => LightRainShowers,
228 Mode::Stratiform => LightRain,
229 },
230 Intensity::Moderate => match mode {
231 Mode::Convective => ModerateRainShowers,
232 Mode::Stratiform => ModerateRain,
233 },
234 Intensity::Heavy => match mode {
235 Mode::Convective => HeavyRainShowers,
236 Mode::Stratiform => HeavyRain,
237 },
238 },
239 LightFreezingRain => match intensity {
240 Intensity::Light => LightFreezingRain,
241 Intensity::Moderate => ModerateFreezingRain,
242 Intensity::Heavy => HeavyFreezingRain,
243 },
244 LightRainAndSnow => match intensity {
245 Intensity::Light => LightRainAndSnow,
246 Intensity::Moderate | Intensity::Heavy => ModerateRainAndSnow,
247 },
248 LightSnow => match intensity {
249 Intensity::Light => match mode {
250 Mode::Convective => LightSnowShowers,
251 Mode::Stratiform => LightSnow,
252 },
253 Intensity::Moderate => match mode {
254 Mode::Convective => ModerateSnowShowers,
255 Mode::Stratiform => ModerateSnow,
256 },
257 Intensity::Heavy => match mode {
258 Mode::Convective => HeavySnowShowers,
259 Mode::Stratiform => HeavySnow,
260 },
261 },
262 LightIcePellets => match intensity {
263 Intensity::Light => LightIcePellets,
264 Intensity::Moderate => ModerateIcePellets,
265 Intensity::Heavy => HeavyIcePellets,
266 },
267 _ => unreachable!(),
269 }
270}
271
272enum Mode {
273 Stratiform,
274 Convective,
275}
276
277enum Intensity {
278 Light,
279 Moderate,
280 Heavy,
281}
282
283enum PreconditionsCheckResult {
284 Pass,
285 Fail,
286 NoQPF,
287}
288
289fn check_preconditions_for_wx_type_adjustment(
291 precip_type: PrecipType,
292 hourly_qpf: Option<Mm>,
293 visibility: Option<StatuteMiles>,
294) -> PreconditionsCheckResult {
295 use PrecipType::*;
296 use PreconditionsCheckResult::*;
297
298 match precip_type {
300 LightDrizzle | LightFreezingDrizzle | LightDrizzleAndRain | LightRain
301 | LightFreezingRain | LightRainAndSnow | LightSnow | LightIcePellets => {}
302 _ => return Fail,
303 }
304
305 match hourly_qpf {
307 Some(pcp) => {
308 if pcp <= Mm(0.0) {
309 return NoQPF;
310 }
311 }
312 std::option::Option::None => return Fail,
313 }
314
315 match precip_type {
317 LightDrizzle | LightFreezingDrizzle | LightDrizzleAndRain | LightRain
318 | LightFreezingRain | LightRainAndSnow | LightIcePellets => {
319 if hourly_qpf.is_none() {
320 return Fail;
321 }
322 }
323 LightSnow => {
324 if hourly_qpf.is_none() && visibility.is_none() {
325 return Fail;
326 }
327 }
328 _ => unreachable!(),
329 }
330 Pass
331}
332
333fn get_precip_type_mode(hourly_qpf: Option<Mm>, convective_qpf: Option<Mm>) -> Mode {
334 hourly_qpf
335 .and_then(|total_pcp| {
336 convective_qpf.map(|conv_pcp| {
337 if conv_pcp > total_pcp * 0.5 {
338 Mode::Convective
339 } else {
340 Mode::Stratiform
341 }
342 })
343 })
344 .unwrap_or(Mode::Stratiform)
345}
346
347fn get_precip_type_intensity(
348 precip_type: PrecipType,
349 hourly_qpf: Option<Mm>,
350 visibility: Option<StatuteMiles>,
351) -> Intensity {
352 use PrecipType::*;
353
354 match precip_type {
355 LightDrizzleAndRain | LightRain | LightFreezingRain | LightRainAndSnow
356 | LightIcePellets => {
357 if let Some(pcp) = hourly_qpf {
358 qpf_to_intensity_for_rain(pcp)
359 } else {
360 Intensity::Light
361 }
362 }
363 LightDrizzle | LightFreezingDrizzle => {
364 if let Some(pcp) = hourly_qpf {
365 qpf_to_intensity_for_drizzle(pcp)
366 } else {
367 Intensity::Light
368 }
369 }
370 LightSnow => {
371 if let Some(vsby) = visibility {
372 visibility_to_intensity(vsby)
373 } else if let Some(pcp) = hourly_qpf {
374 qpf_to_intensity_for_rain(pcp)
375 } else {
376 Intensity::Light
377 }
378 }
379 _ => unreachable!(),
381 }
382}
383
384fn qpf_to_intensity_for_rain(qpf: Mm) -> Intensity {
386 if qpf <= Mm(2.5) {
387 Intensity::Light
388 } else if qpf <= Mm(7.6) {
389 Intensity::Moderate
390 } else {
391 Intensity::Heavy
392 }
393}
394
395fn qpf_to_intensity_for_drizzle(qpf: Mm) -> Intensity {
397 if qpf <= Mm(0.3) {
398 Intensity::Light
399 } else if qpf <= Mm(0.5) {
400 Intensity::Moderate
401 } else {
402 Intensity::Heavy
403 }
404}
405
406fn visibility_to_intensity(vsby: StatuteMiles) -> Intensity {
408 if vsby < StatuteMiles(5.0 / 16.0) {
409 Intensity::Heavy
410 } else if vsby < StatuteMiles(5.0 / 8.0) {
411 Intensity::Moderate
412 } else {
413 Intensity::Light
414 }
415}
416
417#[cfg(test)]
418mod test {
419 use super::PrecipType::*;
420 use super::*;
421 use strum::IntoEnumIterator;
422
423 #[test]
424 #[rustfmt::skip]
425 fn test_adjust_check_precip_type_intensity() {
426
427 assert_eq!(check_precip_type_intensity(LightDrizzle, Some(Mm(0.01)), Some(Mm(0.0)), Some(StatuteMiles(10.0))), LightDrizzle);
428 assert_eq!(check_precip_type_intensity(LightDrizzle, Some(Mm(0.4)), Some(Mm(0.0)), Some(StatuteMiles(10.0))), ModerateDrizzle);
429 assert_eq!(check_precip_type_intensity(LightDrizzle, Some(Mm(0.6)), Some(Mm(0.0)), Some(StatuteMiles(10.0))), HeavyDrizzle);
430
431 assert_eq!(check_precip_type_intensity(LightFreezingDrizzle, Some(Mm(0.1)), Some(Mm(0.0)), Some(StatuteMiles(10.0))), LightFreezingDrizzle);
432 assert_eq!(check_precip_type_intensity(LightFreezingDrizzle, Some(Mm(0.4)), Some(Mm(0.0)), Some(StatuteMiles(10.0))), ModerateFreezingDrizzle);
433 assert_eq!(check_precip_type_intensity(LightFreezingDrizzle, Some(Mm(0.6)), Some(Mm(0.0)), Some(StatuteMiles(10.0))), HeavyFreezingDrizzle);
434
435 assert_eq!(check_precip_type_intensity(LightRain, Some(Mm(1.0)), Some(Mm(0.0)), Some(StatuteMiles(10.0))), LightRain);
436 assert_eq!(check_precip_type_intensity(LightRain, Some(Mm(5.0)), Some(Mm(0.0)), Some(StatuteMiles(10.0))), ModerateRain);
437 assert_eq!(check_precip_type_intensity(LightRain, Some(Mm(8.0)), Some(Mm(0.0)), Some(StatuteMiles(10.0))), HeavyRain);
438
439 assert_eq!(check_precip_type_intensity(LightFreezingRain, Some(Mm(1.0)), Some(Mm(0.0)), Some(StatuteMiles(10.0))), LightFreezingRain);
440 assert_eq!(check_precip_type_intensity(LightFreezingRain, Some(Mm(5.0)), Some(Mm(0.0)), Some(StatuteMiles(10.0))), ModerateFreezingRain);
441 assert_eq!(check_precip_type_intensity(LightFreezingRain, Some(Mm(8.0)), Some(Mm(0.0)), Some(StatuteMiles(10.0))), HeavyFreezingRain);
442
443 assert_eq!(check_precip_type_intensity(LightSnow, Some(Mm(1.0)), Some(Mm(0.0)), std::option::Option::<StatuteMiles>::None), LightSnow);
444 assert_eq!(check_precip_type_intensity(LightSnow, Some(Mm(5.0)), Some(Mm(0.0)), std::option::Option::<StatuteMiles>::None), ModerateSnow);
445 assert_eq!(check_precip_type_intensity(LightSnow, Some(Mm(8.0)), Some(Mm(0.0)), std::option::Option::<StatuteMiles>::None), HeavySnow);
446
447 for variant in PrecipType::iter() {
448 match variant {
450 LightIcePellets | LightRainAndSnow | LightDrizzleAndRain | LightDrizzle |
451 LightFreezingDrizzle | LightRain | LightSnow | LightFreezingRain => {
452 continue
453 }
454 _ => {}
455 }
456
457 for &qpf in &[Mm(1.0), Mm(5.0), Mm(8.0)] {
458 assert_eq!(check_precip_type_intensity(variant, Some(qpf), Some(Mm(0.0)), Some(StatuteMiles(10.0))), variant);
459 }
460 }
461 }
462
463 #[test]
464 fn test_from_u8_and_back() {
465 for variant in PrecipType::iter() {
466 assert_eq!(variant, PrecipType::from(variant as u8));
467 }
468 }
469}