diff --git a/backend/src/bin/weatherscrape.rs b/backend/src/bin/weatherscrape.rs index fcf3799..26a1f3d 100644 --- a/backend/src/bin/weatherscrape.rs +++ b/backend/src/bin/weatherscrape.rs @@ -32,9 +32,10 @@ fn main() -> Result<()> { let fin = DecodeReaderBytesBuilder::new() .encoding(Some(WINDOWS_1252)) .build(resp.into_reader()); - let data: web::canada_weather::SiteData = from_reader(fin)?; + let data: web::canada_weather::types::SiteData = from_reader(fin)?; + let data: web::canada_weather::Report = data.into(); - println!("{:#?}", data); + println!("{}", serde_json::to_string_pretty(&data)?); Ok(()) } diff --git a/backend/src/web/canada_weather/mod.rs b/backend/src/web/canada_weather/mod.rs index 47ceb81..22022d8 100644 --- a/backend/src/web/canada_weather/mod.rs +++ b/backend/src/web/canada_weather/mod.rs @@ -1,5 +1,7 @@ +use chrono::NaiveDateTime; +use serde::{Deserialize, Serialize}; + pub mod types; -pub use types::SiteData; /// The credit string for this data. /// @@ -8,3 +10,122 @@ pub use types::SiteData; /// This must be manually included in each response to remain within the scope /// of the license. pub const DATA_SOURCE: &'static str = "Data Source: Environment and Climate Change Canada"; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Report { + pub data_source: String, + pub location: Location, + pub conditions: Conditions, + pub forecast: Vec, +} + +impl From for Report { + fn from(sd: types::SiteData) -> Self { + let forecast: Vec = sd + .forecast_group + .forecast + .into_iter() + .map(Into::into) + .collect(); + Report { + data_source: DATA_SOURCE.to_string(), + location: sd.location.into(), + conditions: sd.current_conditions.into(), + forecast: forecast, + } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Location { + pub lat: String, + pub lon: String, + pub code: String, + pub name: String, + pub region: String, +} + +impl From for Location { + fn from(l: types::Location) -> Self { + Location { + lat: l.name.lat.unwrap(), + lon: l.name.lon.unwrap(), + code: l.name.code, + name: l.name.name, + region: l.region, + } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Conditions { + pub as_of_utc: NaiveDateTime, + pub as_of_local: NaiveDateTime, + pub condition: String, + pub temperature: f64, + pub dewpoint: f64, + pub windchill: Option, + /// This is in kilo-Pascals + pub pressure: f64, + pub humidity: f64, + pub icon_url: String, +} + +impl From for Conditions { + fn from(cc: types::CurrentConditions) -> Self { + let dt: Vec = cc.date_time.clone().into_iter().map(Into::into).collect(); + Conditions { + as_of_utc: dt[0], + as_of_local: dt[1], + condition: cc.condition, + temperature: cc.temperature.value.unwrap(), + dewpoint: cc.dewpoint.value.unwrap(), + windchill: match cc.wind_chill { + None => None, + Some(wc) => Some(wc.value.unwrap()), + }, + pressure: cc.pressure.value.unwrap(), + humidity: cc.relative_humidity.value.unwrap(), + icon_url: format!( + "https://weather.gc.ca/weathericons/{}.{}", + cc.icon_code.value, cc.icon_code.format + ), + } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Forecast { + pub period: String, + pub summary: String, + pub icon_url: String, + pub temp_summary: String, + pub high: Option, + pub low: Option, + pub humidity: f64, +} + +impl From for Forecast { + fn from(f: types::Forecast) -> Self { + Self { + period: f.period.value, + summary: f.text_summary, + icon_url: format!( + "https://weather.gc.ca/weathericons/{}.{}", + f.abbreviated_forecast.icon_code.value, f.abbreviated_forecast.icon_code.format + ), + temp_summary: f.temperatures.text_summary, + high: if f.temperatures.temperature.class.as_ref().unwrap() == "high" { + f.temperatures.temperature.value + } else { + None + }, + low: if f.temperatures.temperature.class.as_ref().unwrap() == "low" { + f.temperatures.temperature.value + } else { + None + }, + humidity: f.relative_humidity.value.unwrap(), + } + } +} diff --git a/backend/src/web/canada_weather/types.rs b/backend/src/web/canada_weather/types.rs index c7721e9..d2aec36 100644 --- a/backend/src/web/canada_weather/types.rs +++ b/backend/src/web/canada_weather/types.rs @@ -1,3 +1,4 @@ +use chrono::{FixedOffset, NaiveDate, NaiveDateTime, TimeZone}; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug, Clone)] @@ -19,7 +20,7 @@ pub struct SiteData { pub struct Name { pub name: String, #[serde(rename = "$value")] - pub value: u8, + pub value: u32, } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -28,16 +29,35 @@ pub struct DateTime { pub name: String, pub zone: String, #[serde(rename = "UTCOffset")] - pub utc_offset: String, - pub year: u16, + pub utc_offset: i32, + pub year: i32, pub month: Name, pub day: Name, - pub hour: u8, - pub minute: u8, + pub hour: u32, + pub minute: u32, pub time_stamp: String, pub text_summary: String, } +impl Into for DateTime { + fn into(self) -> NaiveDateTime { + NaiveDate::from_ymd(self.year, self.month.value, self.day.value).and_hms( + self.hour, + self.minute, + 0, + ) + } +} + +impl Into> for DateTime { + fn into(self) -> chrono::DateTime { + let hour = 3600; + FixedOffset::east(self.utc_offset * hour) + .ymd(self.year, self.month.value, self.day.value) + .and_hms(self.hour, self.minute, 0) + } +} + #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] pub struct Location { @@ -73,6 +93,7 @@ pub struct MetricWithUnits { pub unit_type: Option, pub change: Option, pub tendency: Option, + pub class: Option, #[serde(rename = "$value")] pub value: Option, } @@ -152,7 +173,7 @@ pub struct CloudPrecip { pub struct Forecast { pub period: Period, pub text_summary: String, - pub cloud_precip: CloudPrecip, + pub cloud_precip: Option, pub abbreviated_forecast: AbbreviatedForecast, pub temperatures: Temperatures, pub winds: Winds,