From 6279d379ab88a9a4b8e89279eb9edb60f477d2ad Mon Sep 17 00:00:00 2001 From: Paul van Tilburg Date: Sun, 13 Feb 2022 15:38:17 +0100 Subject: [PATCH] Add the Buienradar provider --- Cargo.lock | 77 ++++++++++++++++++++++++++ Cargo.toml | 1 + src/providers.rs | 1 + src/providers/buienradar.rs | 106 ++++++++++++++++++++++++++++++++++++ 4 files changed, 185 insertions(+) create mode 100644 src/providers/buienradar.rs diff --git a/Cargo.lock b/Cargo.lock index 5b4c963..f61e00f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -206,6 +206,28 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "chrono-tz" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58549f1842da3080ce63002102d5bc954c7bc843d4f47818e642abdc36253552" +dependencies = [ + "chrono", + "chrono-tz-build", + "phf", +] + +[[package]] +name = "chrono-tz-build" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db058d493fb2f65f41861bfed7e3fe6335264a9f0f92710cab5bdf01fef09069" +dependencies = [ + "parse-zoneinfo", + "phf", + "phf_codegen", +] + [[package]] name = "color-eyre" version = "0.5.11" @@ -1355,6 +1377,15 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "parse-zoneinfo" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41" +dependencies = [ + "regex", +] + [[package]] name = "pear" version = "0.2.3" @@ -1384,6 +1415,45 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", + "uncased", +] + [[package]] name = "pin-project" version = "1.0.10" @@ -1939,6 +2009,7 @@ name = "sinoptik" version = "0.1.0" dependencies = [ "chrono", + "chrono-tz", "color-eyre", "geocoding", "image", @@ -1946,6 +2017,12 @@ dependencies = [ "rocket", ] +[[package]] +name = "siphasher" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a86232ab60fa71287d7f2ddae4a7073f6b7aac33631c3015abb556f08c6d0a3e" + [[package]] name = "slab" version = "0.4.5" diff --git a/Cargo.toml b/Cargo.toml index 6f75bea..3f6dd27 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] chrono = "0.4.19" +chrono-tz = "0.6.1" color-eyre = "0.5.6" geocoding = "0.3.1" image = "0.24.0" diff --git a/src/providers.rs b/src/providers.rs index 6676d8d..f1bf8cc 100644 --- a/src/providers.rs +++ b/src/providers.rs @@ -2,4 +2,5 @@ //! //! Data is either provided via a direct (JSON) API or via looking up values on maps. +pub(crate) mod buienradar; pub(crate) mod luchtmeetnet; diff --git a/src/providers/buienradar.rs b/src/providers/buienradar.rs new file mode 100644 index 0000000..078e6a7 --- /dev/null +++ b/src/providers/buienradar.rs @@ -0,0 +1,106 @@ +//! The Buienradar data provider. +//! +//! For more information about Buienradar, see: +//! and + +use chrono::offset::TimeZone; +use chrono::serde::ts_seconds; +use chrono::{DateTime, Local, NaiveDate, NaiveTime, Utc}; +use chrono_tz::Europe; +use reqwest::Url; +use rocket::serde::Serialize; + +use crate::Metric; + +/// The base URL for the Buienradar API. +const BUIENRADAR_BASE_URL: &str = "https://gpsgadget.buienradar.nl/data/raintext"; + +/// The Buienradar API precipitation data item. +#[derive(Debug, Serialize)] +#[serde(crate = "rocket::serde")] +pub(crate) struct Item { + /// The time(stamp) of the forecast. + #[serde(serialize_with = "ts_seconds::serialize")] + time: DateTime, + + /// The forecasted value. + /// + /// The unit is FIXME? + value: f32, +} + +/// Parses a line of the Buienradar precipitation text interface into an item. +/// +// Each line has the format: `val|HH:MM`, for example: `362|12:30`. +fn parse_item(line: &str, today: &NaiveDate) -> Option { + line.split_once('|') + .map(|(v, t)| { + let time = parse_time(t, today)?; + let value = parse_value(v)?; + + Some(Item { time, value }) + }) + .flatten() +} + +/// Parses a time string to date/time in the UTC time zone. +/// +/// The provided time has the format `HH:MM` and is considered to be in the Europe/Amsterdam +/// time zone. +/// +/// Returns [`None`] if the time cannot be parsed. +fn parse_time(t: &str, today: &NaiveDate) -> Option> { + // First, get the naive date/time. + let ntime = NaiveTime::parse_from_str(t, "%H:%M").ok()?; + let ndtime = today.and_time(ntime); + // Then, interpret the naive date/time in the Europe/Amsterdam time zone and convert it to the + // UTC time zone. + let ldtime = Europe::Amsterdam.from_local_datetime(&ndtime).unwrap(); + let dtime = ldtime.with_timezone(&Utc); + + Some(dtime) +} + +/// Parses a precipitation value into an intensity value in mm/h. +/// +/// For the conversion formula, see: . +/// +/// Returns [`None`] if the value cannot be parsed. +fn parse_value(v: &str) -> Option { + let value = v.parse::().ok()?; + let base: f32 = 10.0; + let value = base.powf((value - 109.0) / 32.0); + let value = (value * 10.0).round() / 10.0; + + Some(value) +} + +/// Retrieves the Buienradar forecasted precipitation items for the provided position. +/// +/// It only supports the following metric: +/// * [`Metric::Precipitation`] +/// +/// Returns [`None`] if retrieval or deserialization fails, or if the metric is not supported by +/// this provider. +pub(crate) async fn get(lat: f64, lon: f64, metric: Metric) -> Option> { + if metric != Metric::Precipitation { + return None; + } + let mut url = Url::parse(BUIENRADAR_BASE_URL).unwrap(); + url.query_pairs_mut() + .append_pair("lat", &format!("{:.02}", lat)) + .append_pair("lon", &format!("{:.02}", lon)); + + println!("▶️ Retrieving Buienradar data from {url}"); + let response = reqwest::get(url).await.ok()?; + let output = match response.error_for_status() { + Ok(res) => res.text().await.ok()?, + Err(_err) => return None, + }; + let today = Local::today().naive_utc(); + + output + .lines() + .map(|line| parse_item(line, &today)) + .collect() +}