Add a next-8-hours strip to the Radar panel's forecast
Same Open-Meteo call and cache as the daily outlook, just with an hourly field added — a rolling window from now rather than "rest of today", so it doesn't shrink to nothing right before midnight.
This commit is contained in:
1 parent
254e9b4c61
commit
6911e67c81
2 files changed
+55
-2
No files matched your search
@@ -218,6 +218,29 @@ html, body {
|
|||||||
border-top: 1px solid #eee;
|
border-top: 1px solid #eee;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.radar-control__forecast-hours {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding-bottom: 6px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radar-control__forecast-hour {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radar-control__forecast-hour span:first-child {
|
||||||
|
color: #888;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
.radar-control__forecast-day {
|
.radar-control__forecast-day {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
+32
-2
@@ -12,6 +12,7 @@ const RAINVIEWER_API = "https://api.rainviewer.com/public/weather-maps.json";
|
|||||||
// radar animation above can't.
|
// radar animation above can't.
|
||||||
const FORECAST_API = "https://api.open-meteo.com/v1/forecast";
|
const FORECAST_API = "https://api.open-meteo.com/v1/forecast";
|
||||||
const FORECAST_DAYS = 3;
|
const FORECAST_DAYS = 3;
|
||||||
|
const HOURLY_FORECAST_COUNT = 8;
|
||||||
const FORECAST_CACHE_KEY = "maptool:forecast";
|
const FORECAST_CACHE_KEY = "maptool:forecast";
|
||||||
|
|
||||||
// A small subset of WMO weather codes (open-meteo's `weather_code`) mapped
|
// A small subset of WMO weather codes (open-meteo's `weather_code`) mapped
|
||||||
@@ -30,6 +31,21 @@ function weatherEmoji(code) {
|
|||||||
return WEATHER_EMOJI.find(([codes]) => codes.includes(code))?.[1] ?? "🌡️";
|
return WEATHER_EMOJI.find(([codes]) => codes.includes(code))?.[1] ?? "🌡️";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A rolling window from "now" rather than "rest of today" — the latter
|
||||||
|
// shrinks to nothing right before midnight and balloons to ~24 entries just
|
||||||
|
// after, for no good reason.
|
||||||
|
function nextHours(hourly, count) {
|
||||||
|
const now = new Date();
|
||||||
|
return hourly.time
|
||||||
|
.map((time, i) => ({
|
||||||
|
time,
|
||||||
|
code: hourly.weather_code[i],
|
||||||
|
temp: Math.round(hourly.temperature_2m[i]),
|
||||||
|
}))
|
||||||
|
.filter((hour) => new Date(hour.time) >= now)
|
||||||
|
.slice(0, count);
|
||||||
|
}
|
||||||
|
|
||||||
// Matches the default window used by github.com/jpettitt/weather-radar-card
|
// Matches the default window used by github.com/jpettitt/weather-radar-card
|
||||||
// (60 minutes of history at RainViewer's native 10-minute cadence = 7
|
// (60 minutes of history at RainViewer's native 10-minute cadence = 7
|
||||||
// frames, the last one being "now").
|
// frames, the last one being "now").
|
||||||
@@ -180,7 +196,17 @@ export class RadarControl {
|
|||||||
precip: data.daily.precipitation_probability_max[i],
|
precip: data.daily.precipitation_probability_max[i],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const rows = days
|
const hourRows = nextHours(data.hourly, HOURLY_FORECAST_COUNT)
|
||||||
|
.map(
|
||||||
|
(hour) => `
|
||||||
|
<div class="radar-control__forecast-hour">
|
||||||
|
<span>${new Date(hour.time).toLocaleTimeString([], { hour: "2-digit" })}</span>
|
||||||
|
<span>${weatherEmoji(hour.code)}</span>
|
||||||
|
<span>${hour.temp}°</span>
|
||||||
|
</div>`,
|
||||||
|
)
|
||||||
|
.join("");
|
||||||
|
const dayRows = days
|
||||||
.map(
|
.map(
|
||||||
(day) => `
|
(day) => `
|
||||||
<div class="radar-control__forecast-day">
|
<div class="radar-control__forecast-day">
|
||||||
@@ -194,7 +220,7 @@ export class RadarControl {
|
|||||||
const staleNote = cachedAt
|
const staleNote = cachedAt
|
||||||
? `<p class="radar-control__forecast-stale">Cached from ${new Date(cachedAt).toLocaleString([], { dateStyle: "short", timeStyle: "short" })}</p>`
|
? `<p class="radar-control__forecast-stale">Cached from ${new Date(cachedAt).toLocaleString([], { dateStyle: "short", timeStyle: "short" })}</p>`
|
||||||
: "";
|
: "";
|
||||||
this._forecastEl.innerHTML = rows + staleNote;
|
this._forecastEl.innerHTML = `<div class="radar-control__forecast-hours">${hourRows}</div>` + dayRows + staleNote;
|
||||||
}
|
}
|
||||||
|
|
||||||
_addLayers() {
|
_addLayers() {
|
||||||
@@ -299,6 +325,10 @@ async function fetchForecast(lat, lon) {
|
|||||||
url.searchParams.set("latitude", lat);
|
url.searchParams.set("latitude", lat);
|
||||||
url.searchParams.set("longitude", lon);
|
url.searchParams.set("longitude", lon);
|
||||||
url.searchParams.set("daily", "weather_code,temperature_2m_max,temperature_2m_min,precipitation_probability_max");
|
url.searchParams.set("daily", "weather_code,temperature_2m_max,temperature_2m_min,precipitation_probability_max");
|
||||||
|
// Same call, same cached JSON (writeCachedForecast below) — the next-few-
|
||||||
|
// hours strip alongside the multi-day outlook rather than a second
|
||||||
|
// request/cache entry.
|
||||||
|
url.searchParams.set("hourly", "weather_code,temperature_2m");
|
||||||
url.searchParams.set("timezone", "auto");
|
url.searchParams.set("timezone", "auto");
|
||||||
url.searchParams.set("forecast_days", String(FORECAST_DAYS));
|
url.searchParams.set("forecast_days", String(FORECAST_DAYS));
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
|
|||||||
Reference in new issue
Block a user