From 59c177d50825202c93bd5f1a0a8dc1540dd5ec80 Mon Sep 17 00:00:00 2001 From: Paul van Tilburg Date: Sun, 13 Feb 2022 12:45:27 +0100 Subject: [PATCH] Add the Luchtmeetnet provider Also introduce the providers module. --- Cargo.toml | 2 +- src/main.rs | 4 ++- src/providers.rs | 5 +++ src/providers/luchtmeetnet.rs | 64 +++++++++++++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 src/providers.rs create mode 100644 src/providers/luchtmeetnet.rs diff --git a/Cargo.toml b/Cargo.toml index e012442..6f75bea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,5 +8,5 @@ chrono = "0.4.19" color-eyre = "0.5.6" geocoding = "0.3.1" image = "0.24.0" -reqwest = "0.11.9" +reqwest = { version = "0.11.9", features = ["json"] } rocket = { version = "0.5.0-rc.1", features = ["json"] } diff --git a/src/main.rs b/src/main.rs index 8d582cd..a6f54dd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,8 +21,10 @@ use rocket::tokio::{self, select}; use rocket::{get, routes, FromFormField, State}; use self::maps::{Maps, MapsHandle}; +use self::providers::luchtmeetnet::Item as LuchtmeetnetItem; -mod maps; +pub(crate) mod maps; +pub(crate) mod providers; /// The current for a specific location. /// diff --git a/src/providers.rs b/src/providers.rs new file mode 100644 index 0000000..6676d8d --- /dev/null +++ b/src/providers.rs @@ -0,0 +1,5 @@ +//! All supported metric data providers. +//! +//! Data is either provided via a direct (JSON) API or via looking up values on maps. + +pub(crate) mod luchtmeetnet; diff --git a/src/providers/luchtmeetnet.rs b/src/providers/luchtmeetnet.rs new file mode 100644 index 0000000..018c1cc --- /dev/null +++ b/src/providers/luchtmeetnet.rs @@ -0,0 +1,64 @@ +//! The Luchtmeetnet open data provider. +//! +//! For more information about Luchtmeetnet, see: . + +use chrono::serde::ts_seconds; +use chrono::{DateTime, Utc}; +use rocket::serde::{Deserialize, Serialize}; + +use crate::Metric; + +/// The base URL for the Luchtmeetnet API. +const LUCHTMEETNET_BASE_URL: &str = "https://api.luchtmeetnet.nl/open_api/concentrations"; + +/// The Luchtmeetnet API data container. +/// +/// This is only used temporarily during deserialization. +#[derive(Debug, Deserialize)] +#[serde(crate = "rocket::serde")] +struct Container { + data: Vec, +} + +/// The Luchtmeetnet API data item. +#[derive(Debug, Deserialize, Serialize)] +#[serde(crate = "rocket::serde")] +pub(crate) struct Item { + /// The time for when the value is forecast. + #[serde( + rename(deserialize = "timestamp_measured"), + serialize_with = "ts_seconds::serialize" + )] + time: DateTime, + /// The forecasted value. + /// + /// The unit depends on the selected [metric](Metric). + value: f32, +} + +/// Retrieves the Luchtmeetnet forecasted items for the provided position and metric. +/// +/// 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> { + let formula = match metric { + Metric::AQI => "lki", + Metric::NO2 => "no2", + Metric::O3 => "o3", + Metric::PM10 => "pm10", + _ => return None, // Unsupported metric + }; + let url = format!( + "{LUCHTMEETNET_BASE_URL}?formula={formula}&latitude={:.05}&longitude={:.05}", + lat, lon + ); + + println!("▶️ Retrieving Luchtmeetnet data from {url}"); + let response = reqwest::get(&url).await.ok()?; + let root: Container = match response.error_for_status() { + Ok(res) => res.json().await.ok()?, + Err(_err) => return None, + }; + + Some(root.data) +}