rjw_metoffice/
parse.rs

1use alloc::str::FromStr;
2use alloc::string::String;
3use alloc::vec::Vec;
4use serde::Deserialize;
5
6use crate::units::Coordinates;
7use crate::{Daily, Error, Hourly, ThreeHourly, TimePeriod};
8
9pub(crate) trait RawTimePeriod: Sized {
10    type Output: TimePeriod + TryFrom<Self, Error = Error>;
11}
12
13impl RawTimePeriod for RawHourlyForecast {
14    type Output = Hourly;
15}
16
17impl RawTimePeriod for RawThreeHourlyForecast {
18    type Output = ThreeHourly;
19}
20
21impl RawTimePeriod for RawDailyForecast {
22    type Output = Daily;
23}
24
25#[derive(Debug, Deserialize)]
26pub(crate) struct RawForecast<T>
27where
28    T: RawTimePeriod,
29{
30    pub features: Vec<RawFeature<T>>,
31}
32
33#[derive(Debug, Deserialize)]
34pub(crate) struct RawFeature<T> {
35    pub geometry: Geometry,
36    pub properties: Properties<T>,
37}
38
39#[derive(Debug, Deserialize)]
40pub(crate) struct Geometry {
41    pub coordinates: Coordinates,
42}
43
44#[derive(Debug, Deserialize)]
45#[serde(rename_all = "camelCase")]
46pub(crate) struct Properties<T> {
47    pub location: Location,
48    pub request_point_distance: f32,
49    #[serde(deserialize_with = "utc_minutes")]
50    pub model_run_date: jiff::Zoned,
51    pub time_series: Vec<T>,
52}
53
54#[derive(Debug, Deserialize)]
55pub(crate) struct Location {
56    pub name: String,
57}
58
59fn utc_minutes<'de, D>(d: D) -> Result<jiff::Zoned, D::Error>
60where
61    D: serde::Deserializer<'de>,
62{
63    let s = String::deserialize(d)?;
64    jiff::Timestamp::from_str(&s)
65        .map(|ts| ts.to_zoned(jiff::tz::TimeZone::UTC))
66        .map_err(|_| serde::de::Error::custom("Failed to parse datetime."))
67}
68
69#[derive(Debug, Deserialize)]
70#[serde(rename_all = "camelCase")]
71pub(crate) struct RawHourlyForecast {
72    /// Time at which this forecast is valid.
73    #[serde(deserialize_with = "utc_minutes")]
74    pub time: jiff::Zoned,
75    /// Temperature at screen level.
76    ///
77    /// Stevenson screen height is approximately 1.5m above ground level.
78    pub screen_temperature: f32,
79    /// Maximum air temperature at screen level.
80    ///
81    /// Appears to be missing after 48 hours.
82    pub max_screen_air_temp: Option<f32>,
83    /// Minimum air temperature at screen level.
84    ///
85    /// Appears to be missing after 48 hours.
86    pub min_screen_air_temp: Option<f32>,
87    /// Dew point temperature at screen level.
88    ///
89    /// Stevenson screen height is approximately 1.5m above ground level.
90    pub screen_dew_point_temperature: f32,
91    /// The temperature it feels like, taking into account humidity and wind chill but
92    /// not radiation.
93    pub feels_like_temperature: f32,
94    /// Surface wind speed in metres per second.
95    ///
96    /// Mean wind speed is equivalent to the mean speed observed over the 10 minutes preceding the
97    /// validity time. Measured at 10 metres above ground, this is considered surface wind speed.
98    pub wind_speed_10m: f32,
99    /// Direction from which the wind is blowing in degrees.
100    ///
101    /// Mean wind direction is equivalent to the mean direction observed over the 10 minutes
102    /// preceding the validity time. Measured at 10 metres above ground, this is considered surface
103    /// wind direction.
104    pub wind_direction_from_10m: f32,
105    /// Maximum 3-second mean wind speed observed over the 10 minutes preciding the validity time.
106    pub wind_gust_speed_10m: f32,
107    /// Maximum 3-second mean wind speed observed over the hour preciding the validity time.
108    ///
109    /// Appears to be missing after 48 hours.
110    pub max_10m_wind_gust: Option<f32>,
111    /// Distance in metres at which a known object can be seen horizontally from screen level (1.5m.)
112    pub visibility: f32,
113    /// Percent relative humidity at screen level (1.5m).
114    pub screen_relative_humidity: f32,
115    /// Air pressure at mean sea level in Pascals.
116    pub mslp: u32,
117    /// Maxmium UV value over the hour preceding the validity time. Usually a value 0 to 13 but
118    /// higher values are possible in extreme situations.
119    pub uv_index: u8,
120    /// The most significant weather conditions at this time, taking into account both
121    /// instantaneous and preceding conditions.
122    ///
123    /// This number is really an enum discriminant.
124    pub significant_weather_code: i8,
125    /// Rate at which liquid water is being deposited on the surface, in mm per hour.
126    pub precipitation_rate: f32,
127    /// Implied depth of the layer of liquid water which has been deposited on the
128    /// surface since the previous hour.
129    ///
130    /// Appears to be missing after 48 hours.
131    pub total_precip_amount: Option<f32>,
132    /// Amount of snow that has fallen out of the sky in the last hour.
133    ///
134    /// This does not reflect snow lying on the ground. Falling snow may not settle at all and may
135    /// be accompanied by rain (ie is sleet). Falling snow is stated as liquid water equivalent in
136    /// mm, which can be considered approximately the same as cm of fresh snow or as a kilogram per
137    /// square metre.
138    ///
139    /// Appears to be missing after 48 hours.
140    pub total_snow_amount: Option<f32>,
141    /// Probability of precipitation over the hour centered at the validity time.
142    pub prob_of_precipitation: f32,
143}
144
145#[derive(Debug, Deserialize)]
146#[serde(rename_all = "camelCase")]
147pub(crate) struct RawThreeHourlyForecast {
148    /// Time at which this forecast is valid.
149    #[serde(deserialize_with = "utc_minutes")]
150    pub time: jiff::Zoned,
151    /// Maximum air temperature at screen level.
152    pub max_screen_air_temp: f32,
153    /// Minimum air temperature at screen level.
154    pub min_screen_air_temp: f32,
155    /// The temperature it feels like, taking into account humidity and wind chill but
156    /// not radiation.
157    pub feels_like_temp: f32,
158    /// Surface wind speed in metres per second.
159    pub wind_speed_10m: f32,
160    /// Direction from which the wind is blowing in degrees.
161    pub wind_direction_from_10m: f32,
162    /// Maximum 3-second mean wind speed.
163    pub wind_gust_speed_10m: f32,
164    /// Maximum 3-second mean wind speed.
165    pub max_10m_wind_gust: f32,
166    /// Distance in metres at which a known object can be seen horizontally from screen level (1.5m.)
167    pub visibility: f32,
168    /// Percent relative humidity at screen level (1.5m).
169    pub screen_relative_humidity: f32,
170    /// Air pressure at mean sea level in Pascals.
171    pub mslp: u32,
172    /// Maxmium UV value over the hour preceding the validity time. Usually a value 0 to 13 but
173    /// higher values are possible in extreme situations.
174    pub uv_index: u8,
175    /// The most significant weather conditions at this time, taking into account both
176    /// instantaneous and preceding conditions.
177    ///
178    /// This number is really an enum discriminant.
179    pub significant_weather_code: i8,
180    /// Implied depth of the layer of liquid water which has been deposited on the
181    /// surface since the previous hour.
182    pub total_precip_amount: f32,
183    /// Amount of snow that has fallen out of the sky in the last hour.
184    ///
185    /// This does not reflect snow lying on the ground. Falling snow may not settle at all and may
186    /// be accompanied by rain (ie is sleet). Falling snow is stated as liquid water equivalent in
187    /// mm, which can be considered approximately the same as cm of fresh snow or as a kilogram per
188    /// square metre.
189    pub total_snow_amount: f32,
190    /// Probability of precipitation over the hour centered at the validity time.
191    pub prob_of_precipitation: f32,
192    pub prob_of_snow: f32,
193    pub prob_of_heavy_snow: f32,
194    pub prob_of_rain: f32,
195    pub prob_of_heavy_rain: f32,
196    pub prob_of_hail: f32,
197    pub prob_of_sferics: f32,
198}
199
200#[derive(Debug, Deserialize)]
201#[serde(rename_all = "camelCase")]
202pub(crate) struct RawDailyForecast {
203    /// Time at which this forecast is valid.
204    #[serde(deserialize_with = "utc_minutes")]
205    pub time: jiff::Zoned,
206    pub day_significant_weather_code: Option<i8>,
207    pub day_max_screen_temperature: f32,
208    pub day_upper_bound_max_temp: f32,
209    pub day_lower_bound_max_temp: f32,
210    pub day_max_feels_like_temp: Option<f32>,
211    pub day_upper_bound_max_feels_like_temp: f32,
212    pub day_lower_bound_max_feels_like_temp: f32,
213    pub day_probability_of_precipitation: Option<f32>,
214    pub day_probability_of_rain: Option<f32>,
215    pub day_probability_of_heavy_rain: Option<f32>,
216    pub day_probability_of_snow: Option<f32>,
217    pub day_probability_of_heavy_snow: Option<f32>,
218    pub day_probability_of_hail: Option<f32>,
219    pub day_probability_of_sferics: Option<f32>,
220    pub max_uv_index: Option<u8>,
221    #[serde(rename = "midday10MWindSpeed")]
222    pub midday_10m_wind_speed: f32,
223    #[serde(rename = "midday10MWindDirection")]
224    pub midday_10m_wind_direction: f32,
225    #[serde(rename = "midday10MWindGust")]
226    pub midday_10m_wind_gust: f32,
227    pub midday_mslp: u32,
228    pub midday_relative_humidity: f32,
229    pub midday_visibility: f32,
230    pub night_significant_weather_code: i8,
231    pub night_min_screen_temperature: f32,
232    pub night_upper_bound_min_temp: f32,
233    pub night_lower_bound_min_temp: f32,
234    pub night_min_feels_like_temp: f32,
235    pub night_upper_bound_min_feels_like_temp: f32,
236    pub night_lower_bound_min_feels_like_temp: f32,
237    pub night_probability_of_precipitation: f32,
238    pub night_probability_of_rain: f32,
239    pub night_probability_of_heavy_rain: f32,
240    pub night_probability_of_snow: f32,
241    pub night_probability_of_heavy_snow: f32,
242    pub night_probability_of_hail: f32,
243    pub night_probability_of_sferics: f32,
244    #[serde(rename = "midnight10MWindSpeed")]
245    pub midnight_10m_wind_speed: f32,
246    #[serde(rename = "midnight10MWindDirection")]
247    pub midnight_10m_wind_direction: f32,
248    #[serde(rename = "midnight10MWindGust")]
249    pub midnight_10m_wind_gust: f32,
250    pub midnight_mslp: u32,
251    pub midnight_relative_humidity: f32,
252    pub midnight_visibility: f32,
253}