Add weatherapi collector
This commit is contained in:
151
backend/src/collector/weatherapi.rs
Normal file
151
backend/src/collector/weatherapi.rs
Normal file
@ -0,0 +1,151 @@
|
||||
use crate::collector::Collector;
|
||||
use chrono::{serde::ts_seconds, DateTime, Utc};
|
||||
use reqwest::get;
|
||||
use serde::Deserialize;
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Response {
|
||||
current: CurrentBody,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[allow(dead_code)]
|
||||
struct Condition {
|
||||
text: String,
|
||||
icon: String,
|
||||
code: i64,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[allow(dead_code)]
|
||||
struct CurrentBody {
|
||||
#[serde(with = "ts_seconds")]
|
||||
last_updated_epoch: DateTime<Utc>,
|
||||
last_updated: String,
|
||||
temp_c: f64,
|
||||
is_day: i8,
|
||||
condition: Condition,
|
||||
wind_kph: f64,
|
||||
wind_degree: f64,
|
||||
wind_dir: String,
|
||||
pressure_mb: f64,
|
||||
precip_mm: f64,
|
||||
humidity: f64,
|
||||
cloud: f64,
|
||||
feelslike_c: f64,
|
||||
vis_km: f64,
|
||||
uv: f64,
|
||||
gust_kph: f64,
|
||||
}
|
||||
|
||||
pub struct WeatherApi {
|
||||
pub api_key: String,
|
||||
pub location: String,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl Collector for WeatherApi {
|
||||
async fn collect(&mut self) -> anyhow::Result<String> {
|
||||
let path = "https://api.weatherapi.com/v1/current.json";
|
||||
let api_key = &self.api_key;
|
||||
let location = &self.location;
|
||||
let url = format!("{path}?key={api_key}&q={location}&aqi=no");
|
||||
let Response {
|
||||
current:
|
||||
CurrentBody {
|
||||
last_updated_epoch,
|
||||
temp_c,
|
||||
feelslike_c,
|
||||
wind_kph,
|
||||
precip_mm,
|
||||
humidity,
|
||||
cloud,
|
||||
..
|
||||
},
|
||||
..
|
||||
} = get(&url).await?.json().await?;
|
||||
|
||||
let wind_mps = (wind_kph / 3.6).round();
|
||||
let wind = wind_speed_to_beaufort(wind_mps);
|
||||
|
||||
let temp = if feelslike_c != temp_c {
|
||||
format!("**{temp_c}°** (känns som {feelslike_c}°)")
|
||||
} else {
|
||||
format!("**{temp_c}°**")
|
||||
};
|
||||
|
||||
let time_of_day = last_updated_epoch.naive_local().format("%H:%M");
|
||||
|
||||
let markdown = format!(
|
||||
r#"
|
||||
# Väder
|
||||
|
||||
## {location} kl {time_of_day}
|
||||
|
||||
{temp}. **{wind}** ({wind_mps} m/s).
|
||||
|
||||
{cloud}% molntäcke, {precip_mm} mm regn, {humidity}% luftfuktighet.
|
||||
|
||||
"#
|
||||
);
|
||||
|
||||
let html = markdown::to_html(markdown.trim());
|
||||
|
||||
Ok(html)
|
||||
}
|
||||
}
|
||||
|
||||
/// Wind speed expressed on the beaufort scale
|
||||
struct BeaufortScale(u8);
|
||||
|
||||
impl Display for BeaufortScale {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let name = match self {
|
||||
BeaufortScale(0) => "Lugnt",
|
||||
BeaufortScale(1 | 2) => "Svag vind",
|
||||
BeaufortScale(3 | 4) => "Måttlig vind",
|
||||
BeaufortScale(5 | 6) => "Frisk vind",
|
||||
BeaufortScale(7 | 8) => "Hård vind",
|
||||
BeaufortScale(9) => "Mycket hård vind",
|
||||
BeaufortScale(10) => "Storm",
|
||||
BeaufortScale(11) => "Svår storm",
|
||||
BeaufortScale(12..) => "Orkan",
|
||||
};
|
||||
|
||||
write!(f, "{name}")
|
||||
}
|
||||
}
|
||||
|
||||
fn wind_speed_to_beaufort(mps: f64) -> BeaufortScale {
|
||||
let beaufort_wind_speeds = [
|
||||
0.0..0.3,
|
||||
0.3..1.6,
|
||||
1.6..3.4,
|
||||
3.4..5.5,
|
||||
5.5..8.8,
|
||||
8.0..10.8,
|
||||
10.8..13.9,
|
||||
13.9..17.2,
|
||||
17.2..20.8,
|
||||
20.8..24.5,
|
||||
24.5..28.5,
|
||||
28.5..32.7,
|
||||
32.7..37.0,
|
||||
37.0..41.5,
|
||||
41.5..46.2,
|
||||
46.2..51.0,
|
||||
51.0..56.1,
|
||||
56.1..61.3,
|
||||
];
|
||||
|
||||
let mps = mps.max(0.0);
|
||||
|
||||
let index = beaufort_wind_speeds
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(|(i, range)| range.contains(&mps).then(|| i))
|
||||
.unwrap_or(beaufort_wind_speeds.len());
|
||||
|
||||
BeaufortScale(index as u8)
|
||||
}
|
||||
Reference in New Issue
Block a user