model.rs (14102B)
1 use std::{collections::HashMap, fmt::Display, hash::Hash, str::FromStr}; 2 3 use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Visitor}; 4 use serde_with::DeserializeFromStr; 5 use strum::{Display, EnumString}; 6 7 use crate::Error; 8 9 #[derive(Deserialize, Debug)] 10 #[serde(untagged)] 11 pub(crate) enum MeteoResponse { 12 Success(serde_json::Value), 13 Error { reason: String }, 14 } 15 16 #[serde_with::skip_serializing_none] 17 #[derive(Serialize, Default)] 18 pub struct GeocodeOptions { 19 pub count: Option<usize>, 20 pub language: Option<String>, 21 #[serde(rename = "countryCode")] 22 pub country_code: Option<String>, 23 } 24 25 #[derive(Deserialize, Debug, Clone)] 26 pub struct Location { 27 pub id: usize, 28 pub name: String, 29 pub latitude: f64, 30 pub longitude: f64, 31 pub elevation: f64, 32 pub timezone: String, 33 pub feature_code: String, 34 pub country_code: String, 35 pub country: String, 36 pub country_id: usize, 37 #[serde(default)] 38 pub population: Option<usize>, 39 #[serde(default)] 40 pub postcodes: Vec<String>, 41 #[serde(default)] 42 pub admin1: Option<String>, 43 #[serde(default)] 44 pub admin2: Option<String>, 45 #[serde(default)] 46 pub admin3: Option<String>, 47 #[serde(default)] 48 pub admin4: Option<String>, 49 #[serde(default)] 50 pub admin1_id: Option<usize>, 51 #[serde(default)] 52 pub admin2_id: Option<usize>, 53 #[serde(default)] 54 pub admin3_id: Option<usize>, 55 #[serde(default)] 56 pub admin4_id: Option<usize>, 57 } 58 59 #[derive(Deserialize, Debug, Clone)] 60 pub(crate) struct GeocodeResponse { 61 #[serde(default)] 62 pub(crate) results: Vec<Location>, 63 } 64 65 #[derive(Display, EnumString, Clone, Copy, Debug, Hash, PartialEq, Eq)] 66 #[strum(serialize_all = "snake_case")] 67 pub enum HourlyVariable { 68 #[strum(to_string = "temperature_2m")] 69 Temperature2m, 70 #[strum(to_string = "temperature_{0}hPa")] 71 TemperaturePressureLevel(usize), 72 #[strum(to_string = "relative_humidity_2m")] 73 RelativeHumidity2m, 74 #[strum(to_string = "relative_humidity_{0}hPa")] 75 RelativeHumidityPressureLevel(usize), 76 #[strum(to_string = "dew_point_2m")] 77 DewPoint2m, 78 #[strum(to_string = "dew_point_{0}hPa")] 79 DewPointPressureLevel(usize), 80 ApparentTemperature, 81 PressureMsl, 82 SurfacePressure, 83 CloudCover, 84 CloudCoverLow, 85 CloudCoverMid, 86 CloudCoverHigh, 87 #[strum(to_string = "cloud_cover_{0}hPa")] 88 CloudCoverPressureLevel(usize), 89 #[strum(to_string = "wind_speed_10m")] 90 WindSpeed10m, 91 #[strum(to_string = "wind_speed_80m")] 92 WindSpeed80m, 93 #[strum(to_string = "wind_speed_120m")] 94 WindSpeed120m, 95 #[strum(to_string = "wind_speed_180m")] 96 WindSpeed180m, 97 #[strum(to_string = "wind_speed_{0}hPa")] 98 WindSpeedPressureLevel(usize), 99 #[strum(to_string = "wind_direction_10m")] 100 WindDirection10m, 101 #[strum(to_string = "wind_direction_80m")] 102 WindDirection80m, 103 #[strum(to_string = "wind_direction_120m")] 104 WindDirection120m, 105 #[strum(to_string = "wind_direction_180m")] 106 WindDIrection180m, 107 #[strum(to_string = "wind_direction_{0}hPa")] 108 WindDirectionPressureLevel(usize), 109 #[strum(to_string = "wind_gusts_10m")] 110 WindGusts10m, 111 ShortwaveRadiation, 112 DirectRadiation, 113 DirectNormalIrradiance, 114 DiffuseRadiation, 115 GlobalTiltedIrradiance, 116 VapourPressureDeficit, 117 Cape, 118 Evapotranspiration, 119 Et0FaoEvapotranspiration, 120 Precipitation, 121 Snowfall, 122 PrecipitationProbability, 123 Rain, 124 Showers, 125 WeatherCode, 126 SnowDepth, 127 FreezingLevelHeight, 128 Visibility, 129 #[strum(to_string = "soil_temperature_0cm")] 130 SoilTemperature0cm, 131 #[strum(to_string = "soil_temperature_6cm")] 132 SoilTemperature6cm, 133 #[strum(to_string = "soil_temperature_18cm")] 134 SoilTemperature18cm, 135 #[strum(to_string = "soil_temperature_54cm")] 136 SoilTemperature54cm, 137 #[strum(to_string = "soil_moisture_0_to_1cm")] 138 SoilMoisture0To1cm, 139 #[strum(to_string = "soil_moisture_1_to_3cm")] 140 SoilMoisture1To3cm, 141 #[strum(to_string = "soil_moisture_3_to_9cm")] 142 SoilMoisture3To9cm, 143 #[strum(to_string = "soil_moisture_9_to_27cm")] 144 SoilMoisture9To27cm, 145 #[strum(to_string = "soil_moisture_27_to_81cm")] 146 SoilMoisture28To81cm, 147 IsDay, 148 #[strum(to_string = "geopotential_height_{0}hPa")] 149 GeopotentialHeightPressureLevel(usize), 150 /// NOTE: Not a valid variable, only found within `.hourly_units` 151 Time, 152 } 153 154 impl<'de> Deserialize<'de> for HourlyVariable { 155 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { 156 struct HourlyVariableVisitor; 157 158 impl<'de> Visitor<'de> for HourlyVariableVisitor { 159 type Value = HourlyVariable; 160 161 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 162 formatter.write_str("a valid hourly variable") 163 } 164 165 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 166 where 167 E: serde::de::Error, 168 { 169 match Self::Value::from_str(v) { 170 Ok(v) => Ok(v), 171 Err(e) => { 172 // temperature_{0}hPa 173 // relative_humidity_{0}hPa 174 // dew_point_{0}hPa 175 // cloud_cover_{0}hPa 176 // wind_speed_{0}hPa 177 // wind_direction_{0}hPa 178 // geopotential_height_{0}hPa 179 180 if !v.ends_with("hPa") { 181 return Err(serde::de::Error::custom(e)); 182 } 183 184 let stripped = v.strip_suffix("hPa").unwrap(); 185 186 let pos = stripped 187 .find(|c: char| c.is_ascii_digit()) 188 .ok_or(serde::de::Error::custom(Error::InvalidPressureLevel))?; 189 190 let var = &stripped[..pos]; 191 let num = stripped[pos..] 192 .parse::<usize>() 193 .map_err(serde::de::Error::custom)?; 194 195 let res = match var { 196 "temperature_" => HourlyVariable::TemperaturePressureLevel(num), 197 "relative_humidity_" => { 198 HourlyVariable::RelativeHumidityPressureLevel(num) 199 } 200 "dew_point_" => HourlyVariable::DewPointPressureLevel(num), 201 "cloud_cover_" => HourlyVariable::CloudCoverPressureLevel(num), 202 "wind_speed_" => HourlyVariable::WindSpeedPressureLevel(num), 203 "wind_direction_" => HourlyVariable::WindDirectionPressureLevel(num), 204 "geopotential_height_" => { 205 HourlyVariable::GeopotentialHeightPressureLevel(num) 206 } 207 _ => return Err(serde::de::Error::custom(Error::InvalidPressureLevel)), 208 }; 209 210 Ok(res) 211 } 212 } 213 } 214 } 215 216 deserializer.deserialize_str(HourlyVariableVisitor) 217 } 218 } 219 220 #[derive(Display, EnumString, Clone, Copy, Debug, Hash, PartialEq, Eq, DeserializeFromStr)] 221 #[strum(serialize_all = "snake_case")] 222 pub enum DailyVariable { 223 #[strum(to_string = "temperature_2m_max")] 224 Temperature2mMax, 225 #[strum(to_string = "temperature_2m_mean")] 226 Temperature2mMean, 227 #[strum(to_string = "temperature_2m_min")] 228 Temperature2mMin, 229 ApparentTemperatureMax, 230 ApparentTemperatureMean, 231 ApparentTemperatureMin, 232 PrecipitationSum, 233 RainSum, 234 ShowersSum, 235 SnowfallSum, 236 PrecipitationHours, 237 PrecipitationProbabilityMax, 238 PrecipitationProbabilityMean, 239 PrecipitationProbabilityMin, 240 WeatherCode, 241 Sunrise, 242 Sunset, 243 SunshineDuration, 244 DaylightDuration, 245 #[strum(to_string = "wind_speed_10m_max")] 246 WindSpeed10mMax, 247 #[strum(to_string = "wind_gusts_10m_max")] 248 WindGusts10mMax, 249 #[strum(to_string = "wind_direction_10m_dominant")] 250 WindDirection10mDominant, 251 ShortwaveRadiationSum, 252 Et0FaoEvapotranspiration, 253 UvIndexMax, 254 UvIndexClearSkyMax, 255 /// NOTE: Not a valid variable, only found within `.daily_units` 256 Time, 257 } 258 259 #[derive(Display, EnumString, Clone, Copy, Debug, Hash, PartialEq, Eq, DeserializeFromStr)] 260 #[strum(serialize_all = "snake_case")] 261 pub enum CurrentVariable { 262 #[strum(to_string = "temperature_2m")] 263 Temperature2m, 264 #[strum(to_string = "relative_humidity_2m")] 265 RelativeHumidity2m, 266 #[strum(to_string = "dew_point_2m")] 267 DewPoint2m, 268 ApparentTemperature, 269 ShortwaveRadiation, 270 DirectRadiation, 271 DirectNormalIrradiance, 272 GlobalTiltedIrradiance, 273 GlobalTiltedIrradianceInstant, 274 DiffuseRadiation, 275 SunshineDuration, 276 LightningPotential, 277 Precipitation, 278 Snowfall, 279 Rain, 280 Showers, 281 SnowfallHeight, 282 FreezingLevelHeight, 283 Cape, 284 #[strum(to_string = "wind_speed_10m")] 285 WindSpeed10m, 286 #[strum(to_string = "wind_speed_80m")] 287 WindSpeed80m, 288 #[strum(to_string = "wind_direction_10m")] 289 WindDirection10m, 290 #[strum(to_string = "wind_direction_80m")] 291 WindDirection80m, 292 #[strum(to_string = "wind_gusts_10m")] 293 WindGusts10m, 294 Visibility, 295 WeatherCode, 296 /// NOTE: Not a valid variable, only found within `.current_units` 297 Time, 298 /// NOTE: Not a valid variable, only found within `.current_units` 299 Interval, 300 301 // Duplicates of Hourly (interpolated) 302 PressureMsl, 303 SurfacePressure, 304 CloudCover, 305 CloudCoverLow, 306 CloudCoverMid, 307 CloudCoverHigh, 308 #[strum(to_string = "wind_speed_120m")] 309 WindSpeed120m, 310 #[strum(to_string = "wind_speed_180m")] 311 WindSpeed180m, 312 #[strum(to_string = "wind_direction_120m")] 313 WindDirection120m, 314 #[strum(to_string = "wind_direction_180m")] 315 WindDIrection180m, 316 VapourPressureDeficit, 317 Evapotranspiration, 318 Et0FaoEvapotranspiration, 319 PrecipitationProbability, 320 SnowDepth, 321 #[strum(to_string = "soil_temperature_0cm")] 322 SoilTemperature0cm, 323 #[strum(to_string = "soil_temperature_6cm")] 324 SoilTemperature6cm, 325 #[strum(to_string = "soil_temperature_18cm")] 326 SoilTemperature18cm, 327 #[strum(to_string = "soil_temperature_54cm")] 328 SoilTemperature54cm, 329 #[strum(to_string = "soil_moisture_0_to_1cm")] 330 SoilMoisture0To1cm, 331 #[strum(to_string = "soil_moisture_1_to_3cm")] 332 SoilMoisture1To3cm, 333 #[strum(to_string = "soil_moisture_3_to_9cm")] 334 SoilMoisture3To9cm, 335 #[strum(to_string = "soil_moisture_9_to_27cm")] 336 SoilMoisture9To27cm, 337 #[strum(to_string = "soil_moisture_27_to_81cm")] 338 SoilMoisture28To81cm, 339 IsDay, 340 } 341 342 #[derive(Serialize)] 343 #[serde(rename_all = "snake_case")] 344 pub enum TemperatureUnit { 345 Celsius, 346 Fahrenheit, 347 } 348 349 #[derive(Serialize)] 350 pub enum SpeedUnit { 351 #[serde(rename = "kmh")] 352 KilometersPerHour, 353 #[serde(rename = "ms")] 354 MetersPerSecond, 355 #[serde(rename = "mph")] 356 MilesPerHour, 357 #[serde(rename = "kn")] 358 Knots, 359 } 360 361 #[derive(Serialize)] 362 pub enum PrecipitationUnit { 363 #[serde(rename = "mm")] 364 Millimeter, 365 #[serde(rename = "inch")] 366 Inch, 367 } 368 369 #[derive(Serialize)] 370 #[serde(rename = "lowercase")] 371 pub enum TimeFormat { 372 Iso8601, 373 UnixTime, 374 } 375 376 #[derive(Serialize)] 377 #[serde(rename = "lowercase")] 378 pub enum CellSelection { 379 Land, 380 Sea, 381 Nearest, 382 } 383 384 #[serde_with::skip_serializing_none] 385 #[derive(Serialize, Default)] 386 pub struct ForecastOptions { 387 pub elevation: Option<f64>, 388 #[serde(serialize_with = "csv")] 389 pub hourly: Option<Vec<HourlyVariable>>, 390 #[serde(serialize_with = "csv")] 391 pub daily: Option<Vec<DailyVariable>>, 392 #[serde(serialize_with = "csv")] 393 pub current: Option<Vec<CurrentVariable>>, 394 pub temperature_unit: Option<TemperatureUnit>, 395 pub wind_speed_unit: Option<SpeedUnit>, 396 pub precipitation_unit: Option<PrecipitationUnit>, 397 pub time_format: Option<TimeFormat>, 398 pub timezone: Option<String>, 399 pub past_days: Option<usize>, 400 pub past_hours: Option<usize>, 401 pub past_minutely_15: Option<usize>, 402 pub forecast_days: Option<usize>, 403 pub forecast_hours: Option<usize>, 404 pub forecast_minutely_15: Option<usize>, 405 pub start_date: Option<String>, 406 pub end_date: Option<String>, 407 pub start_hour: Option<String>, 408 pub end_hour: Option<String>, 409 pub start_minutely_15: Option<String>, 410 pub end_minutely_15: Option<String>, 411 #[serde(serialize_with = "csv")] 412 pub models: Option<Vec<String>>, 413 pub cell_selection: Option<CellSelection>, 414 } 415 416 #[derive(Deserialize, Debug, Clone)] 417 pub struct HourlyData { 418 pub time: Vec<String>, 419 #[serde(flatten)] 420 pub data: HashMap<HourlyVariable, Vec<f64>>, 421 } 422 423 #[derive(Deserialize, Debug, Clone)] 424 pub struct DailyData { 425 pub time: Vec<String>, 426 #[serde(flatten)] 427 pub data: HashMap<DailyVariable, Vec<f64>>, 428 } 429 430 #[derive(Deserialize, Debug, Clone)] 431 pub struct CurrentData { 432 pub time: String, 433 pub interval: usize, 434 #[serde(flatten)] 435 pub data: HashMap<CurrentVariable, f64>, 436 } 437 438 #[derive(Deserialize, Debug, Clone)] 439 pub struct Forecast { 440 pub latitude: f64, 441 pub longitude: f64, 442 pub elevation: f64, 443 pub utc_offset_seconds: isize, 444 pub timezone: String, 445 pub timezone_abbreviation: String, 446 pub hourly: Option<HourlyData>, 447 pub hourly_units: Option<HashMap<HourlyVariable, String>>, 448 pub daily: Option<DailyData>, 449 pub daily_units: Option<HashMap<DailyVariable, String>>, 450 pub current: Option<CurrentData>, 451 pub current_units: Option<HashMap<CurrentVariable, String>>, 452 } 453 454 fn csv<S: Serializer, T: Display>(list: &Option<Vec<T>>, serializer: S) -> Result<S::Ok, S::Error> { 455 if let Some(list) = list { 456 let s: String = list 457 .iter() 458 .map(|v| v.to_string()) 459 .collect::<Vec<String>>() 460 .join(","); 461 462 serializer.serialize_str(&s) 463 } else { 464 serializer.serialize_none() 465 } 466 }