fjordgard

A desktop clock application
Log | Files | Refs | README | LICENSE

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 }