From f6251c8a33b639841a87f44d6069356b60aeeddf Mon Sep 17 00:00:00 2001 From: _ <_@_> Date: Sat, 3 May 2025 16:47:28 -0500 Subject: [PATCH] add current_maximum and current_minimum --- data/empire_city_3.xml | 185 +++++++++++++++++++++++++++++++++++++++++ examples/file.rs | 4 +- src/dwml.rs | 121 ++++++++++++++++++--------- 3 files changed, 272 insertions(+), 38 deletions(-) create mode 100644 data/empire_city_3.xml diff --git a/data/empire_city_3.xml b/data/empire_city_3.xml new file mode 100644 index 0000000..0eb91b6 --- /dev/null +++ b/data/empire_city_3.xml @@ -0,0 +1,185 @@ + + + + + 2025-05-03T15:51:08-04:00 + current observations and forecast + + + New York, NY + https://www.weather.gov/okx/ + https://www.nws.noaa.gov/forecasts/xml/ + + + + + + point1 + New York, NY + + New York + 7 + +https://forecast.weather.gov/MapClick.php?lat=40.71&lon=-74.01 + + k-p12h-n15-1 + 2025-05-03T17:00:00-04:00 + 2025-05-03T18:00:00-04:00 + 2025-05-04T06:00:00-04:00 + 2025-05-04T18:00:00-04:00 + 2025-05-05T06:00:00-04:00 + 2025-05-05T18:00:00-04:00 + 2025-05-06T06:00:00-04:00 + 2025-05-06T18:00:00-04:00 + 2025-05-07T06:00:00-04:00 + 2025-05-07T18:00:00-04:00 + 2025-05-08T06:00:00-04:00 + 2025-05-08T18:00:00-04:00 + 2025-05-09T06:00:00-04:00 + 2025-05-09T18:00:00-04:00 + 2025-05-10T06:00:00-04:00 + + + + k-p24h-n8-1 + 2025-05-03T17:00:00-04:00 + 2025-05-04T06:00:00-04:00 + 2025-05-05T06:00:00-04:00 + 2025-05-06T06:00:00-04:00 + 2025-05-07T06:00:00-04:00 + 2025-05-08T06:00:00-04:00 + 2025-05-09T06:00:00-04:00 + 2025-05-10T06:00:00-04:00 + + + + k-p24h-n7-2 + 2025-05-03T18:00:00-04:00 + 2025-05-04T18:00:00-04:00 + 2025-05-05T18:00:00-04:00 + 2025-05-06T18:00:00-04:00 + 2025-05-07T18:00:00-04:00 + 2025-05-08T18:00:00-04:00 + 2025-05-09T18:00:00-04:00 + + + + + + + Daily Maximum Temperature + 78 + 72 + 62 + 64 + 69 + 66 + 59 + 67 + + + + Daily Minimum Temperature + 61 + 57 + 60 + 59 + 59 + 52 + 52 + + + + 12 Hourly Probability of Precipitation + 60 + 80 + 60 + 60 + 90 + 90 + 90 + 50 + + + 40 + 40 + 30 + 30 + + + + + Weather Type, Coverage, Intensity + + + + + + + + + + + + + + + + + + + Conditions Icon https://forecast.weather.gov/newimages/medium/shra60.png + https://forecast.weather.gov/DualImage.php?i=nshra&j=ntsra&ip=80&jp=30 + https://forecast.weather.gov/DualImage.php?i=shra&j=shra&ip=30&jp=60 + https://forecast.weather.gov/newimages/medium/nshra60.png + https://forecast.weather.gov/newimages/medium/shra90.png + https://forecast.weather.gov/newimages/medium/nshra90.png + https://forecast.weather.gov/newimages/medium/shra90.png + https://forecast.weather.gov/DualImage.php?i=nscttsra&j=nshra&ip=50&jp=50 + https://forecast.weather.gov/newimages/medium/sct.png + https://forecast.weather.gov/newimages/medium/nsct.png + https://forecast.weather.gov/DualImage.php?i=bkn&j=shra&jp=40 + https://forecast.weather.gov/newimages/medium/nshra40.png + https://forecast.weather.gov/newimages/medium/shra30.png + https://forecast.weather.gov/newimages/medium/hi_nshwrs30.png + https://forecast.weather.gov/newimages/medium/sct.png + + + + Watches, Warnings, and Advisories + + + https://forecast.weather.gov/showsigwx.php?warnzone=NYZ072&warncounty=NYC061&firewxzone=NYZ212&local_place1=New+York+NY&product1=Hazardous+Weather+Outlook + + + + + + Text Forecast + Showers likely and possibly a thunderstorm. Partly sunny, with a steady temperature around 78. Southwest wind around 13 mph, with gusts as high as 25 mph. Chance of precipitation is 60%. + Showers and possibly a thunderstorm before 11pm, then a chance of showers and thunderstorms between 11pm and 2am, then a slight chance of showers after 2am. Low around 61. South wind around 10 mph. Chance of precipitation is 80%. New rainfall amounts between a tenth and quarter of an inch, except higher amounts possible in thunderstorms. + A chance of showers, then showers likely and possibly a thunderstorm after 2pm. Cloudy, with a high near 72. South wind 9 to 14 mph. Chance of precipitation is 60%. New rainfall amounts of less than a tenth of an inch, except higher amounts possible in thunderstorms. + Showers likely and possibly a thunderstorm. Cloudy, with a low around 57. East wind 9 to 11 mph. Chance of precipitation is 60%. New precipitation amounts between a quarter and half of an inch possible. + Showers, with thunderstorms also possible after 2pm. High near 62. East wind 11 to 14 mph. Chance of precipitation is 90%. New rainfall amounts between a half and three quarters of an inch possible. + Showers, with thunderstorms also possible after 2am. Low around 60. Chance of precipitation is 90%. New rainfall amounts between a half and three quarters of an inch possible. + Showers and possibly a thunderstorm before 8am, then showers between 8am and 2pm, then showers and possibly a thunderstorm after 2pm. High near 64. Chance of precipitation is 90%. + A chance of showers and thunderstorms before 8pm, then a chance of showers between 8pm and 2am. Mostly cloudy, with a low around 59. Chance of precipitation is 50%. + Mostly sunny, with a high near 69. + Partly cloudy, with a low around 59. + A 40 percent chance of showers. Partly sunny, with a high near 66. + A 40 percent chance of showers. Mostly cloudy, with a low around 52. + A 30 percent chance of showers. Partly sunny, with a high near 59. + A 30 percent chance of showers. Partly cloudy, with a low around 52. + Mostly sunny, with a high near 67. + + + + + + + point1 + + New York City, Central Park, NY + 154 + + https://www.nws.noaa.gov/data/obhistory/KNYC.html k-p1h-n1-1 2025-05-03T16:51:00-04:00 84 55 37 Weather Type, Coverage, Intensity 10.00 Conditions Icon https://forecast.weather.gov/newimages/medium/sct.png NA NA NA 29.84 \ No newline at end of file diff --git a/examples/file.rs b/examples/file.rs index aed9d08..09e862b 100644 --- a/examples/file.rs +++ b/examples/file.rs @@ -5,7 +5,9 @@ fn main() -> Result<()> { let mut args = std::env::args(); args.next().unwrap(); - let path = args.next().context("Usage: cargo run --example file -- untracked/data.xml")?; + let path = args + .next() + .context("Usage: cargo run --example file -- untracked/data.xml")?; let s = std::fs::read_to_string(&path)?; let dwml = weather_dot_gov_rs::Dwml::from_str(&s)?; diff --git a/src/dwml.rs b/src/dwml.rs index 301b333..295df25 100644 --- a/src/dwml.rs +++ b/src/dwml.rs @@ -10,6 +10,32 @@ pub struct Dwml { pub data: (Forecast, CurrentObservations), } +impl Dwml { + /// Returns the current "high" temperature. + /// + /// Panics if the data doesn't show a current high + pub fn current_maximum(&self) -> TempWithUnit { + self.forecast() + .parameters + .temp_by_type(TempType::Maximum) + .first() + } + + /// Returns the current "low" temperature. + /// + /// Panics if the data doesn't show a current low + pub fn current_minimum(&self) -> TempWithUnit { + self.forecast() + .parameters + .temp_by_type(TempType::Minimum) + .first() + } + + fn forecast(&self) -> &Forecast { + &self.data.0 + } +} + impl std::str::FromStr for Dwml { type Err = quick_xml::DeError; @@ -51,24 +77,6 @@ pub struct TimeLayout { pub start_valid_time: Vec, } -impl TimeLayout { - /// For a given time, returns the index of the interval that the time falls in. - fn lookup(&self, t: &chrono::DateTime) -> Option { - for i in 0..self.start_valid_time.len() - 1 { - let Some(start) = self.start_valid_time.get(i) else { - break; - }; - let Some(stop) = self.start_valid_time.get(i + 1) else { - break; - }; - if start.value.0 <= *t && *t < stop.value.0 { - return Some(i); - } - } - None - } -} - #[derive(Debug, Deserialize, PartialEq)] pub enum TimeCoordinate { #[serde(rename = "local")] @@ -86,7 +94,7 @@ pub struct StartValidTime { #[derive(Debug, Deserialize, PartialEq)] #[serde(try_from = "String")] -pub struct DateTime (pub chrono::DateTime); +pub struct DateTime(pub chrono::DateTime); impl TryFrom for DateTime { type Error = chrono::ParseError; @@ -101,6 +109,26 @@ impl TryFrom for DateTime { #[derive(Debug, Deserialize, PartialEq)] pub struct CurrentObservations { pub location: Location, + pub parameters: CurrentParameters, +} + +#[derive(Debug, Deserialize, PartialEq)] +pub struct CurrentParameters { + pub temperature: (Temperature, Temperature), + pub humidity: Humidity, +} + +#[derive(Debug, Deserialize, PartialEq)] +pub struct Humidity { + #[serde(rename = "@type")] + pub humidity_type: HumidityType, + pub value: u8, +} + +#[derive(Debug, Deserialize, PartialEq)] +pub enum HumidityType { + #[serde(rename = "relative")] + Relative, } #[derive(Debug, Deserialize, PartialEq)] @@ -126,7 +154,7 @@ pub struct Source { #[derive(Debug, Deserialize, PartialEq)] pub struct Parameters { - pub temperature: (Temperature, Temperature), + pub temperature: [Temperature; 2], #[serde(rename = "probability-of-precipitation")] pub probability_of_precipitation: ProbabilityOfPrecipitation, @@ -134,6 +162,22 @@ pub struct Parameters { pub weather: Weather, } +impl Parameters { + fn temp_by_type(&self, temp_type: TempType) -> &Temperature { + for temp in &self.temperature { + if temp.temp_type == temp_type { + return temp; + } + } + panic!("Couldn't find a Temperature with the given TempType"); + } +} + +pub struct TempWithUnit { + pub units: TempUnits, + pub value: i32, +} + #[derive(Debug, Deserialize, PartialEq)] pub struct Temperature { #[serde(rename = "@type")] @@ -148,6 +192,19 @@ pub struct Temperature { pub value: Vec, } +impl Temperature { + fn first(&self) -> TempWithUnit { + let value = *self + .value + .first() + .expect("temperatures should always have at least one value"); + TempWithUnit { + units: self.units, + value, + } + } +} + #[derive(Debug, Deserialize, PartialEq)] pub struct ProbabilityOfPrecipitation { #[serde(rename = "@time-layout")] @@ -180,7 +237,7 @@ pub struct PrecipitationValue { pub value: Option, } -#[derive(Debug, Deserialize, PartialEq)] +#[derive(Clone, Copy, Debug, Deserialize, PartialEq)] pub enum TempType { #[serde(rename = "apparent")] Apparent, @@ -192,7 +249,7 @@ pub enum TempType { Maximum, } -#[derive(Debug, Deserialize, PartialEq)] +#[derive(Clone, Copy, Debug, Deserialize, PartialEq)] pub enum TempUnits { Celsius, Fahrenheit, @@ -203,24 +260,16 @@ pub struct TimeLayoutKey(String); #[cfg(test)] mod tests { - use anyhow::{Context as _, Result}; use super::*; + use anyhow::{Context as _, Result}; #[test] fn empire_city() { let s = include_str!("../data/empire_city.xml"); let dwml: Dwml = quick_xml::de::from_str(s).unwrap(); - for (input, expected) in [ - ("2025-01-14T18:59:59-05:00", None), - ("2025-01-14T19:00:00-05:00", Some(0)), - ("2025-01-21T05:59:59-05:00", Some(12)), - ("2025-01-21T06:00:00-05:00", None), - ] { - let dt = chrono::DateTime::parse_from_rfc3339(input).unwrap(); - let actual = dwml.data.0.time_layout.0.lookup(&dt); - assert_eq!(actual, expected); - } + assert_eq!(dwml.current_maximum().value, 33); + assert_eq!(dwml.current_minimum().value, 26); } #[test] @@ -237,10 +286,8 @@ mod tests { #[test] fn date() -> Result<()> { - for s in [ - "2025-01-01T23:00:00-00:00" - ] { - let _ = chrono::DateTime::parse_from_rfc3339(&s).context(s).unwrap(); + for s in ["2025-01-01T23:00:00-00:00", "2025-01-01T23:00:00-00:00"] { + let _ = chrono::DateTime::parse_from_rfc3339(s).context(s).unwrap(); } Ok(()) }