feat: add hour lines and sun/moon rise/set times

This commit is contained in:
Mark
2026-06-05 13:44:48 +02:00
parent 0f7d3a2a83
commit 333eae6816
2 changed files with 80 additions and 14 deletions

View File

@@ -3,14 +3,14 @@ use std::collections::{BTreeMap, HashMap};
use reqwest::Url;
use serde::Deserialize;
use crate::data::Data;
#[derive(Default)]
pub struct DwdCache {}
pub type Forecast = HashMap<String, ForecastDatas>;
#[derive(Deserialize)]
pub struct ForecastDatas {
#[serde(default)]
pub days: Vec<ForecastDay>,
forecast0: Option<ForecastData>,
forecast1: Option<ForecastData>,
forecast2: Option<ForecastData>,
@@ -22,6 +22,21 @@ pub struct ForecastDatas {
forecast8: 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 {
pub fn forecasts(&self) -> impl Iterator<Item = &ForecastData> {
[

View File

@@ -247,6 +247,22 @@ async fn weather_overview(q: &str, data: &State, accept: &Accept) -> Page {
i += 1;
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(
("Temperature", "°C", 5.0),
@@ -261,6 +277,7 @@ async fn weather_overview(q: &str, data: &State, accept: &Accept) -> Page {
avg(forecasts(&forecast)
.flat_map(move |forecast| forecast.temperature(time)))
},
additional,
timer.clone(),
&mut body,
);
@@ -272,6 +289,7 @@ async fn weather_overview(q: &str, data: &State, accept: &Accept) -> Page {
avg(forecasts(&forecast)
.flat_map(move |forecast| forecast.precipitation(time)))
},
additional,
timer.clone(),
&mut body,
);
@@ -283,6 +301,7 @@ async fn weather_overview(q: &str, data: &State, accept: &Accept) -> Page {
avg(forecasts(&forecast)
.flat_map(move |forecast| forecast.sunshine(time)))
},
additional,
timer.clone(),
&mut body,
);
@@ -294,6 +313,7 @@ async fn weather_overview(q: &str, data: &State, accept: &Accept) -> Page {
avg(forecasts(&forecast)
.flat_map(move |forecast| forecast.humidity(time)))
},
additional,
timer.clone(),
&mut body,
);
@@ -329,7 +349,7 @@ a {
overflow-x: scroll;
}
.diagram {
height: 10em;
height: 12em;
margin: 0px;
border: 0px;
padding: 0px;
@@ -347,6 +367,7 @@ fn diagram(
(dindex, fmin, fmax): (usize, Option<f64>, Option<f64>),
gradient: &[(f64, &str)],
f: impl Fn(f64) -> Option<f64>,
additional: impl Fn(String) -> Option<(Option<(f64, f64)>, Option<(f64, f64)>)>,
mut timer: TimeStepper,
body: &mut String,
) {
@@ -394,7 +415,7 @@ fn diagram(
let scale_value = |v: f64| (max - v) * 1000.0 / (max - min);
let scaled_end = scale_time(end);
*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,
);
let shape_head = format!(r##"<path stroke="url(#gra{dindex})" fill="transparent" d=""##);
@@ -411,23 +432,53 @@ fn diagram(
.with_timezone(&Local)
.date_naive();
loop {
let day_start = date
.and_time(NaiveTime::from_hms_opt(0, 0, 0).unwrap())
fn unix_day_start(date: NaiveDate) -> f64 {
date.and_time(NaiveTime::from_hms_opt(0, 0, 0).unwrap())
.and_local_timezone(Local)
.earliest()
.unwrap()
.signed_duration_since(DateTime::UNIX_EPOCH)
.as_seconds_f64();
if let Some(succ) = date.succ_opt() {
date = succ;
.as_seconds_f64()
}
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 {
break;
}
};
if day_start > end {
break;
} else if day_start < start {
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 (-2i32..)
.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);
*body += &format!(r#"<line x1="{x}" x2="{x}" y1="1040" y2="1120" stroke="white"/>"#);
let x = scale_time(day_start + 3.0 * 3600.0);