Skip to main content

openmeteo_rs/
weather_code.rs

1//! WMO weather-code helpers.
2
3/// Open-Meteo subset of WMO present weather codes.
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize)]
5#[repr(u8)]
6#[non_exhaustive]
7pub enum WmoCode {
8    /// Clear sky.
9    ClearSky = 0,
10    /// Mainly clear.
11    MainlyClear = 1,
12    /// Partly cloudy.
13    PartlyCloudy = 2,
14    /// Overcast.
15    Overcast = 3,
16    /// Fog.
17    Fog = 45,
18    /// Depositing rime fog.
19    DepositingRimeFog = 48,
20    /// Light drizzle.
21    DrizzleLight = 51,
22    /// Moderate drizzle.
23    DrizzleModerate = 53,
24    /// Dense drizzle.
25    DrizzleDense = 55,
26    /// Light freezing drizzle.
27    FreezingDrizzleLight = 56,
28    /// Dense freezing drizzle.
29    FreezingDrizzleDense = 57,
30    /// Slight rain.
31    RainSlight = 61,
32    /// Moderate rain.
33    RainModerate = 63,
34    /// Heavy rain.
35    RainHeavy = 65,
36    /// Light freezing rain.
37    FreezingRainLight = 66,
38    /// Heavy freezing rain.
39    FreezingRainHeavy = 67,
40    /// Slight snow fall.
41    SnowFallSlight = 71,
42    /// Moderate snow fall.
43    SnowFallModerate = 73,
44    /// Heavy snow fall.
45    SnowFallHeavy = 75,
46    /// Snow grains.
47    SnowGrains = 77,
48    /// Slight rain showers.
49    RainShowersSlight = 80,
50    /// Moderate rain showers.
51    RainShowersModerate = 81,
52    /// Violent rain showers.
53    RainShowersViolent = 82,
54    /// Slight snow showers.
55    SnowShowersSlight = 85,
56    /// Heavy snow showers.
57    SnowShowersHeavy = 86,
58    /// Slight or moderate thunderstorm.
59    ThunderstormSlightOrModerate = 95,
60    /// Thunderstorm with slight hail.
61    ThunderstormWithSlightHail = 96,
62    /// Thunderstorm with heavy hail.
63    ThunderstormWithHeavyHail = 99,
64}
65
66impl WmoCode {
67    /// Converts the numeric API code into a known enum value.
68    pub fn from_code(code: u8) -> Option<Self> {
69        Some(match code {
70            0 => Self::ClearSky,
71            1 => Self::MainlyClear,
72            2 => Self::PartlyCloudy,
73            3 => Self::Overcast,
74            45 => Self::Fog,
75            48 => Self::DepositingRimeFog,
76            51 => Self::DrizzleLight,
77            53 => Self::DrizzleModerate,
78            55 => Self::DrizzleDense,
79            56 => Self::FreezingDrizzleLight,
80            57 => Self::FreezingDrizzleDense,
81            61 => Self::RainSlight,
82            63 => Self::RainModerate,
83            65 => Self::RainHeavy,
84            66 => Self::FreezingRainLight,
85            67 => Self::FreezingRainHeavy,
86            71 => Self::SnowFallSlight,
87            73 => Self::SnowFallModerate,
88            75 => Self::SnowFallHeavy,
89            77 => Self::SnowGrains,
90            80 => Self::RainShowersSlight,
91            81 => Self::RainShowersModerate,
92            82 => Self::RainShowersViolent,
93            85 => Self::SnowShowersSlight,
94            86 => Self::SnowShowersHeavy,
95            95 => Self::ThunderstormSlightOrModerate,
96            96 => Self::ThunderstormWithSlightHail,
97            99 => Self::ThunderstormWithHeavyHail,
98            _ => return None,
99        })
100    }
101
102    /// Human-readable Open-Meteo weather-code description.
103    pub fn description(self) -> &'static str {
104        match self {
105            Self::ClearSky => "clear sky",
106            Self::MainlyClear => "mainly clear",
107            Self::PartlyCloudy => "partly cloudy",
108            Self::Overcast => "overcast",
109            Self::Fog => "fog",
110            Self::DepositingRimeFog => "depositing rime fog",
111            Self::DrizzleLight => "light drizzle",
112            Self::DrizzleModerate => "moderate drizzle",
113            Self::DrizzleDense => "dense drizzle",
114            Self::FreezingDrizzleLight => "light freezing drizzle",
115            Self::FreezingDrizzleDense => "dense freezing drizzle",
116            Self::RainSlight => "slight rain",
117            Self::RainModerate => "moderate rain",
118            Self::RainHeavy => "heavy rain",
119            Self::FreezingRainLight => "light freezing rain",
120            Self::FreezingRainHeavy => "heavy freezing rain",
121            Self::SnowFallSlight => "slight snow fall",
122            Self::SnowFallModerate => "moderate snow fall",
123            Self::SnowFallHeavy => "heavy snow fall",
124            Self::SnowGrains => "snow grains",
125            Self::RainShowersSlight => "slight rain showers",
126            Self::RainShowersModerate => "moderate rain showers",
127            Self::RainShowersViolent => "violent rain showers",
128            Self::SnowShowersSlight => "slight snow showers",
129            Self::SnowShowersHeavy => "heavy snow showers",
130            Self::ThunderstormSlightOrModerate => "slight or moderate thunderstorm",
131            Self::ThunderstormWithSlightHail => "thunderstorm with slight hail",
132            Self::ThunderstormWithHeavyHail => "thunderstorm with heavy hail",
133        }
134    }
135
136    /// Returns whether this weather code implies precipitation.
137    pub fn is_precipitating(self) -> bool {
138        matches!(
139            self,
140            Self::DrizzleLight
141                | Self::DrizzleModerate
142                | Self::DrizzleDense
143                | Self::FreezingDrizzleLight
144                | Self::FreezingDrizzleDense
145                | Self::RainSlight
146                | Self::RainModerate
147                | Self::RainHeavy
148                | Self::FreezingRainLight
149                | Self::FreezingRainHeavy
150                | Self::SnowFallSlight
151                | Self::SnowFallModerate
152                | Self::SnowFallHeavy
153                | Self::SnowGrains
154                | Self::RainShowersSlight
155                | Self::RainShowersModerate
156                | Self::RainShowersViolent
157                | Self::SnowShowersSlight
158                | Self::SnowShowersHeavy
159                | Self::ThunderstormSlightOrModerate
160                | Self::ThunderstormWithSlightHail
161                | Self::ThunderstormWithHeavyHail
162        )
163    }
164
165    /// Returns whether this weather code is a thunderstorm.
166    pub fn is_thunderstorm(self) -> bool {
167        matches!(
168            self,
169            Self::ThunderstormSlightOrModerate
170                | Self::ThunderstormWithSlightHail
171                | Self::ThunderstormWithHeavyHail
172        )
173    }
174
175    /// Coarse severity classification.
176    pub fn severity(self) -> Severity {
177        match self {
178            Self::ClearSky | Self::MainlyClear | Self::PartlyCloudy => Severity::Clear,
179            Self::DrizzleLight
180            | Self::FreezingDrizzleLight
181            | Self::RainSlight
182            | Self::FreezingRainLight
183            | Self::SnowFallSlight
184            | Self::RainShowersSlight
185            | Self::SnowShowersSlight => Severity::Light,
186            Self::Overcast
187            | Self::Fog
188            | Self::DepositingRimeFog
189            | Self::DrizzleModerate
190            | Self::RainModerate
191            | Self::SnowFallModerate
192            | Self::SnowGrains
193            | Self::RainShowersModerate
194            | Self::ThunderstormSlightOrModerate => Severity::Moderate,
195            Self::DrizzleDense
196            | Self::FreezingDrizzleDense
197            | Self::RainHeavy
198            | Self::FreezingRainHeavy
199            | Self::SnowFallHeavy
200            | Self::RainShowersViolent
201            | Self::SnowShowersHeavy => Severity::Heavy,
202            Self::ThunderstormWithSlightHail | Self::ThunderstormWithHeavyHail => Severity::Severe,
203        }
204    }
205}
206
207#[cfg(test)]
208mod tests {
209    use super::{Severity, WmoCode};
210
211    #[test]
212    fn all_documented_open_meteo_codes_are_supported() {
213        for (code, expected) in [
214            (0, WmoCode::ClearSky),
215            (1, WmoCode::MainlyClear),
216            (2, WmoCode::PartlyCloudy),
217            (3, WmoCode::Overcast),
218            (45, WmoCode::Fog),
219            (48, WmoCode::DepositingRimeFog),
220            (51, WmoCode::DrizzleLight),
221            (53, WmoCode::DrizzleModerate),
222            (55, WmoCode::DrizzleDense),
223            (56, WmoCode::FreezingDrizzleLight),
224            (57, WmoCode::FreezingDrizzleDense),
225            (61, WmoCode::RainSlight),
226            (63, WmoCode::RainModerate),
227            (65, WmoCode::RainHeavy),
228            (66, WmoCode::FreezingRainLight),
229            (67, WmoCode::FreezingRainHeavy),
230            (71, WmoCode::SnowFallSlight),
231            (73, WmoCode::SnowFallModerate),
232            (75, WmoCode::SnowFallHeavy),
233            (77, WmoCode::SnowGrains),
234            (80, WmoCode::RainShowersSlight),
235            (81, WmoCode::RainShowersModerate),
236            (82, WmoCode::RainShowersViolent),
237            (85, WmoCode::SnowShowersSlight),
238            (86, WmoCode::SnowShowersHeavy),
239            (95, WmoCode::ThunderstormSlightOrModerate),
240            (96, WmoCode::ThunderstormWithSlightHail),
241            (99, WmoCode::ThunderstormWithHeavyHail),
242        ] {
243            assert_eq!(WmoCode::from_code(code), Some(expected));
244            assert!(!expected.description().is_empty());
245        }
246
247        assert_eq!(WmoCode::from_code(4), None);
248    }
249
250    #[test]
251    fn weather_code_helpers_classify_precipitation_and_storms() {
252        assert!(!WmoCode::ClearSky.is_precipitating());
253        assert!(WmoCode::FreezingRainLight.is_precipitating());
254        assert!(WmoCode::RainShowersViolent.is_precipitating());
255        assert!(WmoCode::ThunderstormWithSlightHail.is_thunderstorm());
256        assert_eq!(
257            WmoCode::ThunderstormWithHeavyHail.severity(),
258            Severity::Severe
259        );
260    }
261}
262
263/// Coarse weather-code severity.
264#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize)]
265#[non_exhaustive]
266pub enum Severity {
267    /// Clear or fair conditions.
268    Clear,
269    /// Light weather impact.
270    Light,
271    /// Moderate weather impact.
272    Moderate,
273    /// Heavy weather impact.
274    Heavy,
275    /// Severe weather impact.
276    Severe,
277}