feat: add hour lines and sun/moon rise/set times
This commit is contained in:
19
src/dwd.rs
19
src/dwd.rs
@@ -3,14 +3,14 @@ use std::collections::{BTreeMap, HashMap};
|
|||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::data::Data;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct DwdCache {}
|
pub struct DwdCache {}
|
||||||
|
|
||||||
pub type Forecast = HashMap<String, ForecastDatas>;
|
pub type Forecast = HashMap<String, ForecastDatas>;
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct ForecastDatas {
|
pub struct ForecastDatas {
|
||||||
|
#[serde(default)]
|
||||||
|
pub days: Vec<ForecastDay>,
|
||||||
forecast0: Option<ForecastData>,
|
forecast0: Option<ForecastData>,
|
||||||
forecast1: Option<ForecastData>,
|
forecast1: Option<ForecastData>,
|
||||||
forecast2: Option<ForecastData>,
|
forecast2: Option<ForecastData>,
|
||||||
@@ -22,6 +22,21 @@ pub struct ForecastDatas {
|
|||||||
forecast8: Option<ForecastData>,
|
forecast8: Option<ForecastData>,
|
||||||
forecast9: Option<ForecastData>,
|
forecast9: Option<ForecastData>,
|
||||||
}
|
}
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct ForecastDay {
|
||||||
|
#[serde(rename = "dayDate")]
|
||||||
|
pub date: String,
|
||||||
|
// #[serde(rename = "moonPhase")]
|
||||||
|
// pub moon_phase: Option<i64>,
|
||||||
|
// #[serde(rename = "moonriseOnThisDay")]
|
||||||
|
pub moonrise: Option<f64>,
|
||||||
|
// #[serde(rename = "moonsetOnThisDay")]
|
||||||
|
pub moonset: Option<f64>,
|
||||||
|
// #[serde(rename = "sunriseOnThisDay")]
|
||||||
|
pub sunrise: Option<f64>,
|
||||||
|
// #[serde(rename = "sunsetOnThisDay")]
|
||||||
|
pub sunset: Option<f64>,
|
||||||
|
}
|
||||||
impl ForecastDatas {
|
impl ForecastDatas {
|
||||||
pub fn forecasts(&self) -> impl Iterator<Item = &ForecastData> {
|
pub fn forecasts(&self) -> impl Iterator<Item = &ForecastData> {
|
||||||
[
|
[
|
||||||
|
|||||||
75
src/main.rs
75
src/main.rs
@@ -247,6 +247,22 @@ async fn weather_overview(q: &str, data: &State, accept: &Accept) -> Page {
|
|||||||
i += 1;
|
i += 1;
|
||||||
i
|
i
|
||||||
};
|
};
|
||||||
|
let additional = |date| {
|
||||||
|
forecast
|
||||||
|
.values()
|
||||||
|
.flat_map(|forecast| forecast.days.iter())
|
||||||
|
.find(|day| day.date == date)
|
||||||
|
.map(|day| {
|
||||||
|
(
|
||||||
|
day.sunrise
|
||||||
|
.and_then(|rise| day.sunset.map(|set| (rise, set)))
|
||||||
|
.map(|(rise, set)| (rise / 1000.0, set / 1000.0)),
|
||||||
|
day.moonrise
|
||||||
|
.and_then(|rise| day.moonset.map(|set| (rise, set)))
|
||||||
|
.map(|(rise, set)| (rise / 1000.0, set / 1000.0)),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
};
|
||||||
diagram_head(&mut body);
|
diagram_head(&mut body);
|
||||||
diagram(
|
diagram(
|
||||||
("Temperature", "°C", 5.0),
|
("Temperature", "°C", 5.0),
|
||||||
@@ -261,6 +277,7 @@ async fn weather_overview(q: &str, data: &State, accept: &Accept) -> Page {
|
|||||||
avg(forecasts(&forecast)
|
avg(forecasts(&forecast)
|
||||||
.flat_map(move |forecast| forecast.temperature(time)))
|
.flat_map(move |forecast| forecast.temperature(time)))
|
||||||
},
|
},
|
||||||
|
additional,
|
||||||
timer.clone(),
|
timer.clone(),
|
||||||
&mut body,
|
&mut body,
|
||||||
);
|
);
|
||||||
@@ -272,6 +289,7 @@ async fn weather_overview(q: &str, data: &State, accept: &Accept) -> Page {
|
|||||||
avg(forecasts(&forecast)
|
avg(forecasts(&forecast)
|
||||||
.flat_map(move |forecast| forecast.precipitation(time)))
|
.flat_map(move |forecast| forecast.precipitation(time)))
|
||||||
},
|
},
|
||||||
|
additional,
|
||||||
timer.clone(),
|
timer.clone(),
|
||||||
&mut body,
|
&mut body,
|
||||||
);
|
);
|
||||||
@@ -283,6 +301,7 @@ async fn weather_overview(q: &str, data: &State, accept: &Accept) -> Page {
|
|||||||
avg(forecasts(&forecast)
|
avg(forecasts(&forecast)
|
||||||
.flat_map(move |forecast| forecast.sunshine(time)))
|
.flat_map(move |forecast| forecast.sunshine(time)))
|
||||||
},
|
},
|
||||||
|
additional,
|
||||||
timer.clone(),
|
timer.clone(),
|
||||||
&mut body,
|
&mut body,
|
||||||
);
|
);
|
||||||
@@ -294,6 +313,7 @@ async fn weather_overview(q: &str, data: &State, accept: &Accept) -> Page {
|
|||||||
avg(forecasts(&forecast)
|
avg(forecasts(&forecast)
|
||||||
.flat_map(move |forecast| forecast.humidity(time)))
|
.flat_map(move |forecast| forecast.humidity(time)))
|
||||||
},
|
},
|
||||||
|
additional,
|
||||||
timer.clone(),
|
timer.clone(),
|
||||||
&mut body,
|
&mut body,
|
||||||
);
|
);
|
||||||
@@ -329,7 +349,7 @@ a {
|
|||||||
overflow-x: scroll;
|
overflow-x: scroll;
|
||||||
}
|
}
|
||||||
.diagram {
|
.diagram {
|
||||||
height: 10em;
|
height: 12em;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
border: 0px;
|
border: 0px;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
@@ -347,6 +367,7 @@ fn diagram(
|
|||||||
(dindex, fmin, fmax): (usize, Option<f64>, Option<f64>),
|
(dindex, fmin, fmax): (usize, Option<f64>, Option<f64>),
|
||||||
gradient: &[(f64, &str)],
|
gradient: &[(f64, &str)],
|
||||||
f: impl Fn(f64) -> Option<f64>,
|
f: impl Fn(f64) -> Option<f64>,
|
||||||
|
additional: impl Fn(String) -> Option<(Option<(f64, f64)>, Option<(f64, f64)>)>,
|
||||||
mut timer: TimeStepper,
|
mut timer: TimeStepper,
|
||||||
body: &mut String,
|
body: &mut String,
|
||||||
) {
|
) {
|
||||||
@@ -394,7 +415,7 @@ fn diagram(
|
|||||||
let scale_value = |v: f64| (max - v) * 1000.0 / (max - min);
|
let scale_value = |v: f64| (max - v) * 1000.0 / (max - min);
|
||||||
let scaled_end = scale_time(end);
|
let scaled_end = scale_time(end);
|
||||||
*body += &format!(
|
*body += &format!(
|
||||||
r##"<svg class="diagram" xmlns="http://www.w3.org/2000/svg" viewBox="-500 -50 {} 1200">"##,
|
r##"<svg class="diagram" xmlns="http://www.w3.org/2000/svg" viewBox="-500 -150 {} 1300">"##,
|
||||||
scaled_end + 500.0,
|
scaled_end + 500.0,
|
||||||
);
|
);
|
||||||
let shape_head = format!(r##"<path stroke="url(#gra{dindex})" fill="transparent" d=""##);
|
let shape_head = format!(r##"<path stroke="url(#gra{dindex})" fill="transparent" d=""##);
|
||||||
@@ -411,23 +432,53 @@ fn diagram(
|
|||||||
.with_timezone(&Local)
|
.with_timezone(&Local)
|
||||||
.date_naive();
|
.date_naive();
|
||||||
loop {
|
loop {
|
||||||
let day_start = date
|
fn unix_day_start(date: NaiveDate) -> f64 {
|
||||||
.and_time(NaiveTime::from_hms_opt(0, 0, 0).unwrap())
|
date.and_time(NaiveTime::from_hms_opt(0, 0, 0).unwrap())
|
||||||
.and_local_timezone(Local)
|
.and_local_timezone(Local)
|
||||||
.earliest()
|
.earliest()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.signed_duration_since(DateTime::UNIX_EPOCH)
|
.signed_duration_since(DateTime::UNIX_EPOCH)
|
||||||
.as_seconds_f64();
|
.as_seconds_f64()
|
||||||
if let Some(succ) = date.succ_opt() {
|
}
|
||||||
date = succ;
|
let day_start = unix_day_start(date);
|
||||||
|
let (date, day_end) = if let Some(succ) = date.succ_opt() {
|
||||||
|
(std::mem::replace(&mut date, succ), unix_day_start(succ))
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
};
|
||||||
if day_start > end {
|
if day_start > end {
|
||||||
break;
|
break;
|
||||||
} else if day_start < start {
|
} else if day_start < start {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if let Some((sun, moon)) = additional(date.to_string()) {
|
||||||
|
for (risenset, color1, color2, y) in
|
||||||
|
[(sun, "#FF08", "#FB08", 150), (moon, "#88F8", "#00F6", 130)]
|
||||||
|
{
|
||||||
|
if let Some((rise, set)) = risenset {
|
||||||
|
for (t, color) in [(rise, color1), (set, color2)] {
|
||||||
|
let x = scale_time(t);
|
||||||
|
*body += &format!(
|
||||||
|
r#"<line x1="{x}" x2="{x}" y1="-{y}" y2="-100" stroke="{color}"/>"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (i, x) in (0..)
|
||||||
|
.map(|i| (i, day_end + (i as f64 - 24.0) * 3600.0))
|
||||||
|
.skip_while(|(_, t)| *t <= day_start)
|
||||||
|
.take_while(|(_, t)| *t < day_end && *t <= end)
|
||||||
|
.map(|(i, t)| (i, scale_time(t)))
|
||||||
|
{
|
||||||
|
*body += &format!(
|
||||||
|
r##"<line x1="{x}" x2="{x}" y1="-100" y2="-{}" stroke="#FFF8"/>"##,
|
||||||
|
match i {
|
||||||
|
6 | 12 | 18 => "50",
|
||||||
|
_ => "80",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
let x = scale_time(day_start);
|
let x = scale_time(day_start);
|
||||||
*body += &format!(r#"<line x1="{x}" x2="{x}" y1="1040" y2="1120" stroke="white"/>"#);
|
*body += &format!(r#"<line x1="{x}" x2="{x}" y1="1040" y2="1120" stroke="white"/>"#);
|
||||||
let x = scale_time(day_start + 3.0 * 3600.0);
|
let x = scale_time(day_start + 3.0 * 3600.0);
|
||||||
|
|||||||
Reference in New Issue
Block a user