Add the Buienradar provider

This commit is contained in:
Paul van Tilburg 2022-02-13 15:38:17 +01:00
parent d432bb4cd6
commit 6279d379ab
Signed by: paul
GPG Key ID: C6DE073EDA9EEC4D
4 changed files with 185 additions and 0 deletions

77
Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -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;

106
src/providers/buienradar.rs Normal file
View File

@ -0,0 +1,106 @@
//! The Buienradar data provider.
//!
//! For more information about Buienradar, see: <https://www.buienradar.nl/overbuienradar/contact>
//! and <https://www.buienradar.nl/overbuienradar/gratis-weerdata>
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<Utc>,
/// 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<Item> {
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<DateTime<Utc>> {
// 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: <https://www.buienradar.nl/overbuienradar/gratis-weerdata>.
///
/// Returns [`None`] if the value cannot be parsed.
fn parse_value(v: &str) -> Option<f32> {
let value = v.parse::<f32>().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<Vec<Item>> {
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()
}