changelog shortlog graph tags branches changeset files revisions annotate raw help

Mercurial > core / rust/lib/tenex/models/nws/lib.rs

changeset 698: 96958d3eb5b0
parent: 3d78bed56188
author: Richard Westhaver <ellis@rwest.io>
date: Fri, 04 Oct 2024 22:04:59 -0400
permissions: -rw-r--r--
description: fixes
1 use chrono::{DateTime, Local};
2 use log::debug;
3 use reqwest::{Client, Error};
4 use serde::{Deserialize, Serialize};
5 use serde_json::Value;
6 
7 /// Geo-coordinate Point object type
8 #[derive(Serialize, Deserialize, Debug, Default, PartialEq)]
9 pub struct Point {
10  pub lat: f32,
11  pub lng: f32,
12 }
13 
14 impl Point {
15  /// Create a new Point from (f32, f32)
16  pub fn new(lat: f32, lng: f32) -> Self {
17  Point { lat, lng }
18  }
19 }
20 /// City object
21 ///
22 /// Used to parse City metadata from datasets acquired on the internet
23 #[derive(Deserialize, Serialize, Debug)]
24 pub struct City {
25  pub city: String,
26  pub state_id: String,
27  pub lat: f32,
28  pub lng: f32,
29 }
30 
31 impl City {
32  /// Convert a City to Point.
33  ///
34  /// Returns Ok(Point) on success. Note that only f32 values are
35  /// accepted (0. 1. -- not 0 1).
36  pub fn into_point(&self) -> Point {
37  Point {
38  lat: self.lat,
39  lng: self.lng,
40  }
41  }
42 }
43 
44 /// Result of a GET /point request
45 #[derive(Serialize, Deserialize, Debug)]
46 pub struct PointInfo {
47  id: String,
48  pub properties: PointProps,
49 }
50 
51 /// Inner properties object of PointInfo
52 #[derive(Serialize, Deserialize, Debug)]
53 pub struct PointProps {
54  #[serde(rename(deserialize = "forecastOffice"))]
55  pub forecast_office: String,
56  pub forecast: String,
57  #[serde(rename(deserialize = "forecastHourly"))]
58  pub forecast_hourly: String,
59  #[serde(rename(deserialize = "forecastGridData"))]
60  pub forecast_grid_data: String,
61  #[serde(rename(deserialize = "observationStations"))]
62  pub observation_stations: String,
63  #[serde(rename(deserialize = "relativeLocation"))]
64  pub relative_location: RelativeLocation,
65  #[serde(rename(deserialize = "forecastZone"))]
66  pub forecast_zone: String,
67  pub county: String,
68  #[serde(rename(deserialize = "fireWeatherZone"))]
69  pub fire_weather_zone: String,
70  #[serde(rename(deserialize = "timeZone"))]
71  pub time_zone: String,
72  #[serde(rename(deserialize = "radarStation"))]
73  pub radar_station: String,
74 }
75 
76 /// inner relative_location object of PointProps
77 #[derive(Debug, Serialize, Deserialize)]
78 pub struct RelativeLocation {
79  pub geometry: Value,
80  pub properties: RelativeProps,
81 }
82 
83 /// inner properties object of RelativeLocation
84 #[derive(Debug, Serialize, Deserialize)]
85 pub struct RelativeProps {
86  pub city: String,
87  pub state: String,
88  pub distance: Value,
89  pub bearing: Value,
90 }
91 
92 /// Result of GET /forecast
93 #[derive(Debug, Serialize, Deserialize)]
94 pub struct Forecast {
95  pub properties: ForecastProps,
96 }
97 
98 /// Inner properties object of Forecast
99 #[derive(Debug, Serialize, Deserialize)]
100 pub struct ForecastProps {
101  pub updated: DateTime<Local>,
102  pub units: String,
103  #[serde(rename(deserialize = "generatedAt"))]
104  pub generated_at: DateTime<Local>,
105  pub elevation: Value,
106  pub periods: Vec<ForecastPeriod>,
107 }
108 
109 /// Single instance of item in periods object of ForecastProps
110 #[derive(Debug, Serialize, Deserialize)]
111 pub struct ForecastPeriod {
112  pub number: u16,
113  pub name: String,
114  #[serde(rename(deserialize = "startTime"))]
115  pub start_time: DateTime<Local>,
116  #[serde(rename(deserialize = "endTime"))]
117  pub end_time: DateTime<Local>,
118  #[serde(rename(deserialize = "isDaytime"))]
119  pub is_day_time: bool,
120  pub temperature: i8,
121  #[serde(rename(deserialize = "temperatureUnit"))]
122  pub temperature_unit: String,
123  #[serde(rename(deserialize = "windSpeed"))]
124  pub wind_speed: Option<String>,
125  #[serde(rename(deserialize = "windDirection"))]
126  pub wind_direction: Option<String>,
127  pub icon: String,
128  #[serde(rename(deserialize = "shortForecast"))]
129  pub short_forecast: String,
130  #[serde(rename(deserialize = "detailedForecast"))]
131  pub detailed_forecast: String,
132 }
133 
134 /// Forecast output representation
135 #[derive(Debug, Serialize, Deserialize)]
136 pub struct ForecastBundle {
137  pub start: DateTime<Local>,
138  pub end: DateTime<Local>,
139  pub temperature: i8,
140  pub wind_speed: String, // TODO parse from string to int "30 mph" -> 30
141  pub wind_direction: String,
142  pub short_forecast: String,
143 }
144 
145 /// WeatherForecast output representation tied to a specific City.
146 ///
147 /// This struct is passed directly into an embedded Database
148 #[derive(Debug, Serialize, Deserialize)]
149 pub struct WeatherBundle {
150  pub location: City,
151  pub forecast: Vec<ForecastBundle>,
152  pub updated: DateTime<Local>,
153 }
154 
155 impl WeatherBundle {
156  /// Create a new WeatherBundle from a City and Forecast
157  pub fn new(loc: City, fcb: Forecast) -> Self {
158  let mut vec = Vec::new();
159  for i in fcb.properties.periods.iter() {
160  let i = ForecastBundle {
161  start: i.start_time,
162  end: i.end_time,
163  temperature: i.temperature,
164  wind_speed: i.wind_speed.as_ref().unwrap().to_string(),
165  wind_direction: i.wind_direction.as_ref().unwrap().to_string(),
166  short_forecast: i.short_forecast.to_string(),
167  };
168  vec.push(i);
169  }
170  WeatherBundle {
171  location: loc,
172  forecast: vec,
173  updated: fcb.properties.updated,
174  }
175  }
176 }
177 
178 pub async fn get_point(
179  pnt: &Point,
180  client: &Client,
181 ) -> Result<PointInfo, Error> {
182  let mut url: String = String::from("http://api.weather.gov/");
183  for i in &["points/", &pnt.lat.to_string(), ",", &pnt.lng.to_string()] {
184  url.push_str(i);
185  }
186  let response = client.get(&url).send().await?;
187  let body = response.text().await?;
188  debug!("{}", body);
189  let res: PointInfo = serde_json::from_str(&body).unwrap();
190  Ok(res)
191 }
192 
193 pub async fn get_forecast(
194  pnt: &PointInfo,
195  client: &Client,
196 ) -> Result<Forecast, Error> {
197  let response = client.get(&pnt.properties.forecast).send().await?;
198  let body = response.text().await?;
199  debug!("{}", body);
200  let res: Forecast = serde_json::from_str(&body).unwrap();
201  Ok(res)
202 }
203 
204 pub async fn get_forecast_hourly(
205  pnt: &PointInfo,
206  client: &Client,
207 ) -> Result<Forecast, Error> {
208  let response = client.get(&pnt.properties.forecast_hourly).send().await?;
209  let body = response.text().await?;
210  let res: Forecast = serde_json::from_str(&body).unwrap();
211  Ok(res)
212 }
213 
214 /// TODO [2021-08-21] - get_alerts
215 pub async fn get_alerts(_state: &str) -> Result<(), Error> {
216  Ok(())
217 }
218 
219 pub async fn weather_report(lat: f32, lng: f32) -> Result<(), Error> {
220  let client = Client::builder().user_agent("thunderman").build()?;
221 
222  let point = Point { lat, lng };
223 
224  let res = get_point(&point, &client).await?;
225  let resf = get_forecast_hourly(&res, &client).await?;
226  for i in resf.properties.periods[0..10].iter() {
227  println!(
228  "{:#?}-{:#?} = {:#?}°F :: {:#?}",
229  &i.start_time.time(),
230  &i.end_time.time(),
231  &i.temperature,
232  &i.short_forecast
233  );
234  }
235  Ok(())
236 }