remove web dependencies
This commit is contained in:
@@ -363,6 +363,10 @@ function sum(values) {
|
||||
return seen ? total : null;
|
||||
}
|
||||
|
||||
function clamp(value, min, max) {
|
||||
return Math.max(min, Math.min(max, value));
|
||||
}
|
||||
|
||||
function updateText(id, text) {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.textContent = text;
|
||||
@@ -430,48 +434,123 @@ function lastNonNull(points, key) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function computeRainProbability(latest, pressureTrend1h) {
|
||||
if (!latest) {
|
||||
function classifyRainProbability(prob) {
|
||||
if (prob >= 0.6) return "High";
|
||||
if (prob >= 0.35) return "Medium";
|
||||
return "Low";
|
||||
}
|
||||
|
||||
function computeDewPointC(tempC, rh) {
|
||||
if (tempC === null || tempC === undefined || rh === null || rh === undefined) {
|
||||
return null;
|
||||
}
|
||||
const safeRh = clamp(rh, 1, 100);
|
||||
const a = 17.625;
|
||||
const b = 243.04;
|
||||
const gamma = Math.log(safeRh / 100) + (a * tempC) / (b + tempC);
|
||||
return (b * gamma) / (a - gamma);
|
||||
}
|
||||
|
||||
function computeRainProbabilityFromInputs(tempC, rh, pressureHpa) {
|
||||
if (tempC === null || tempC === undefined || rh === null || rh === undefined || pressureHpa === null || pressureHpa === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let prob = 0.1;
|
||||
if (pressureTrend1h !== null && pressureTrend1h !== undefined) {
|
||||
if (pressureTrend1h <= -3.0) {
|
||||
prob += 0.5;
|
||||
} else if (pressureTrend1h <= -2.0) {
|
||||
prob += 0.35;
|
||||
} else if (pressureTrend1h <= -1.0) {
|
||||
prob += 0.2;
|
||||
} else if (pressureTrend1h <= -0.5) {
|
||||
prob += 0.1;
|
||||
}
|
||||
const dewPointC = computeDewPointC(tempC, rh);
|
||||
if (dewPointC === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (latest.rh !== null && latest.rh !== undefined) {
|
||||
if (latest.rh >= 95) {
|
||||
prob += 0.2;
|
||||
} else if (latest.rh >= 90) {
|
||||
prob += 0.15;
|
||||
} else if (latest.rh >= 85) {
|
||||
prob += 0.1;
|
||||
}
|
||||
const saturationSpread = Math.max(0, tempC - dewPointC);
|
||||
const humidityFactor = clamp((rh - 55) / 45, 0, 1);
|
||||
const pressureFactor = clamp((1016 - pressureHpa) / 18, 0, 1);
|
||||
const saturationFactor = clamp((6 - saturationSpread) / 6, 0, 1);
|
||||
|
||||
const score = 0.45 * humidityFactor + 0.35 * pressureFactor + 0.2 * saturationFactor;
|
||||
const prob = clamp(0.02 + 0.93 * score, 0.02, 0.98);
|
||||
|
||||
return { prob, label: classifyRainProbability(prob) };
|
||||
}
|
||||
|
||||
function computeRainProbability(latest) {
|
||||
if (!latest) {
|
||||
return null;
|
||||
}
|
||||
return computeRainProbabilityFromInputs(latest.temp_c, latest.rh, latest.pressure_hpa);
|
||||
}
|
||||
|
||||
function buildRainProbabilitySeries(points) {
|
||||
const out = [];
|
||||
for (const p of points) {
|
||||
const t = new Date(p.ts).getTime();
|
||||
if (Number.isNaN(t)) continue;
|
||||
const rp = computeRainProbabilityFromInputs(p.temp_c, p.rh, p.pressure_hpa);
|
||||
out.push({
|
||||
x: t,
|
||||
y: rp ? Math.round(rp.prob * 1000) / 10 : null,
|
||||
});
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function updateWeatherIcons(latest, rainProb) {
|
||||
const sunEl = document.getElementById("live-icon-sun");
|
||||
const cloudEl = document.getElementById("live-icon-cloud");
|
||||
const rainEl = document.getElementById("live-icon-rain");
|
||||
const textEl = document.getElementById("live-weather-text");
|
||||
|
||||
[sunEl, cloudEl, rainEl].forEach((el) => {
|
||||
if (el) el.classList.remove("active");
|
||||
});
|
||||
|
||||
if (!latest) {
|
||||
if (textEl) textEl.textContent = "--";
|
||||
return;
|
||||
}
|
||||
|
||||
if (latest.wind_m_s !== null && latest.wind_m_s !== undefined && latest.wind_m_s >= 6) {
|
||||
prob += 0.05;
|
||||
const prob = rainProb ? rainProb.prob : null;
|
||||
const rh = latest.rh;
|
||||
const pressure = latest.pressure_hpa;
|
||||
const uvi = latest.uvi;
|
||||
|
||||
let sunActive = false;
|
||||
let cloudActive = false;
|
||||
let rainActive = false;
|
||||
let label = "Partly cloudy";
|
||||
|
||||
if (prob !== null && prob >= 0.6) {
|
||||
rainActive = true;
|
||||
cloudActive = true;
|
||||
label = "Rain likely";
|
||||
} else if (
|
||||
(prob !== null && prob >= 0.35) ||
|
||||
(rh !== null && rh !== undefined && rh >= 80) ||
|
||||
(pressure !== null && pressure !== undefined && pressure <= 1008)
|
||||
) {
|
||||
cloudActive = true;
|
||||
label = "Cloudy";
|
||||
} else if (
|
||||
uvi !== null &&
|
||||
uvi !== undefined &&
|
||||
uvi >= 4 &&
|
||||
rh !== null &&
|
||||
rh !== undefined &&
|
||||
rh < 75 &&
|
||||
pressure !== null &&
|
||||
pressure !== undefined &&
|
||||
pressure >= 1012
|
||||
) {
|
||||
sunActive = true;
|
||||
label = "Sunny";
|
||||
} else {
|
||||
sunActive = true;
|
||||
cloudActive = true;
|
||||
}
|
||||
|
||||
prob = Math.max(0.05, Math.min(0.95, prob));
|
||||
|
||||
let label = "Low";
|
||||
if (prob >= 0.6) {
|
||||
label = "High";
|
||||
} else if (prob >= 0.35) {
|
||||
label = "Medium";
|
||||
}
|
||||
|
||||
return { prob, label };
|
||||
if (sunEl) sunEl.classList.toggle("active", sunActive);
|
||||
if (cloudEl) cloudEl.classList.toggle("active", cloudActive);
|
||||
if (rainEl) rainEl.classList.toggle("active", rainActive);
|
||||
if (textEl) textEl.textContent = label;
|
||||
}
|
||||
|
||||
function extendForecastTo(points, endTime) {
|
||||
@@ -607,12 +686,13 @@ function renderDashboard(data) {
|
||||
const forecast = filterRange(forecastAll, rangeStart, rangeEnd);
|
||||
const forecastLine = extendForecastTo(forecast, rangeEnd);
|
||||
const lastPressureTrend = lastNonNull(obsFiltered, "pressure_trend_1h");
|
||||
const rainProb = computeRainProbability(latest, lastPressureTrend);
|
||||
const rainProb = computeRainProbability(latest);
|
||||
if (rainProb) {
|
||||
updateText("live-rain-prob", `${Math.round(rainProb.prob * 100)}% (${rainProb.label})`);
|
||||
} else {
|
||||
updateText("live-rain-prob", "--");
|
||||
}
|
||||
updateWeatherIcons(latest, rainProb);
|
||||
updateText("baro-outlook", describeBarometer(latest ? latest.pressure_hpa : null, lastPressureTrend));
|
||||
|
||||
const obsTemps = obsFiltered.map((p) => p.temp_c);
|
||||
@@ -757,9 +837,12 @@ function renderDashboard(data) {
|
||||
upsertChart("chart-power", powerChart);
|
||||
|
||||
const rainOptions = baseOptions(range);
|
||||
rainOptions.scales.y.ticks.color = colors.rain;
|
||||
rainOptions.scales.y.title = { display: true, text: "Observed Rain (mm)", color: colors.rain };
|
||||
rainOptions.scales.y1 = {
|
||||
position: "right",
|
||||
ticks: { color: "#a4c4c4" },
|
||||
ticks: { color: colors.forecast },
|
||||
title: { display: true, text: "Forecast Rain (mm)", color: colors.forecast },
|
||||
grid: { drawOnChartArea: false },
|
||||
};
|
||||
|
||||
@@ -788,7 +871,7 @@ function renderDashboard(data) {
|
||||
label: "forecast precip (mm)",
|
||||
data: series(forecastRain, "precip_mm"),
|
||||
backgroundColor: colors.forecast,
|
||||
yAxisID: "y",
|
||||
yAxisID: "y1",
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -796,6 +879,31 @@ function renderDashboard(data) {
|
||||
};
|
||||
upsertChart("chart-rain", rainChart);
|
||||
|
||||
const rainProbOptions = baseOptions(range);
|
||||
rainProbOptions.scales.y.min = 0;
|
||||
rainProbOptions.scales.y.max = 100;
|
||||
rainProbOptions.scales.y.ticks.color = colors.rain;
|
||||
rainProbOptions.scales.y.ticks.callback = (value) => `${value}%`;
|
||||
rainProbOptions.scales.y.title = { display: true, text: "Probability (%)", color: colors.rain };
|
||||
|
||||
const rainProbChart = {
|
||||
type: "line",
|
||||
data: {
|
||||
datasets: [
|
||||
{
|
||||
label: "predicted rain probability (%)",
|
||||
data: buildRainProbabilitySeries(obsFiltered),
|
||||
borderColor: colors.rain,
|
||||
backgroundColor: "rgba(78, 168, 222, 0.18)",
|
||||
fill: true,
|
||||
yAxisID: "y",
|
||||
},
|
||||
],
|
||||
},
|
||||
options: rainProbOptions,
|
||||
};
|
||||
upsertChart("chart-rain-prob", rainProbChart);
|
||||
|
||||
|
||||
updateSingleChartMode();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user