feat: Create a web map application with a FastAPI backend and Leaflet frontend for displaying weather data and analysis.

This commit is contained in:
2569718930@qq.com
2026-03-04 03:43:53 +08:00
parent c6b2aca69e
commit 910c28eda0
3 changed files with 73 additions and 6 deletions
+29
View File
@@ -359,6 +359,34 @@ def _analyze(city: str) -> Dict[str, Any]:
} }
# ── 15. Extended Multi-Model Daily ──
multi_model_daily = {}
mm_daily_raw = mm.get("daily_forecasts", {})
for i, d_str in enumerate(dates):
if i == 0:
day_m = current_forecasts.copy()
d_val, d_winfo = deb_val, deb_weights
else:
day_m = mm_daily_raw.get(d_str, {}).copy()
if i < len(maxtemps) and maxtemps[i] is not None:
day_m["Open-Meteo"] = _sf(maxtemps[i])
d_val, d_winfo = None, ""
if day_m:
try:
blended, winfo = calculate_dynamic_weights(city, day_m)
if blended is not None:
d_val = blended
d_winfo = winfo
except Exception:
pass
if day_m:
multi_model_daily[d_str] = {
"models": day_m,
"deb": {"prediction": d_val, "weights_info": d_winfo}
}
# ── Assemble result ── # ── Assemble result ──
result = { result = {
"name": city, "name": city,
@@ -402,6 +430,7 @@ def _analyze(city: str) -> Dict[str, Any]:
"sunshine_hours": sunshine_h, "sunshine_hours": sunshine_h,
}, },
"multi_model": {k: v for k, v in current_forecasts.items() if v is not None}, "multi_model": {k: v for k, v in current_forecasts.items() if v is not None},
"multi_model_daily": multi_model_daily,
"deb": {"prediction": deb_val, "weights_info": deb_weights}, "deb": {"prediction": deb_val, "weights_info": deb_weights},
"ensemble": ens_data, "ensemble": ens_data,
"probabilities": { "probabilities": {
+35 -6
View File
@@ -38,6 +38,7 @@ function saveCache() {
let selectedCity = null; let selectedCity = null;
let tempChart = null; let tempChart = null;
const AUTO_REFRESH_MS = 60 * 60 * 1000; // 1 hour const AUTO_REFRESH_MS = 60 * 60 * 1000; // 1 hour
let selectedForecastDate = null;
// ────────────────────────────────────────────────────────── // ──────────────────────────────────────────────────────────
// Map Setup // Map Setup
@@ -231,6 +232,7 @@ async function fetchCityDetail(cityName) {
// ────────────────────────────────────────────────────────── // ──────────────────────────────────────────────────────────
async function loadCityDetail(cityName) { async function loadCityDetail(cityName) {
selectedCity = cityName; selectedCity = cityName;
selectedForecastDate = null; // Reset selection for new city
setActiveCityItem(cityName); setActiveCityItem(cityName);
setSelectedMarker(cityName); setSelectedMarker(cityName);
@@ -316,9 +318,11 @@ function renderPanel(data) {
renderChart(data); renderChart(data);
// Probabilities // Probabilities
renderProbabilities(data); renderProbabilities(data);
// Multi-model // Multi-model & Forecast synchronization
if (!selectedForecastDate) {
selectedForecastDate = data.local_date;
}
renderModels(data); renderModels(data);
// Forecast
renderForecast(data); renderForecast(data);
// AI // AI
renderAI(data); renderAI(data);
@@ -653,8 +657,18 @@ function renderProbabilities(data) {
function renderModels(data) { function renderModels(data) {
const container = document.getElementById("modelBars"); const container = document.getElementById("modelBars");
const models = data.multi_model || {}; const targetDate = selectedForecastDate || data.local_date;
const deb = data.deb?.prediction;
let models = {};
let deb = null;
if (data.multi_model_daily && data.multi_model_daily[targetDate]) {
models = data.multi_model_daily[targetDate].models || {};
deb = data.multi_model_daily[targetDate].deb?.prediction;
} else {
models = data.multi_model || {};
deb = data.deb?.prediction;
}
if (Object.keys(models).length === 0) { if (Object.keys(models).length === 0) {
container.innerHTML = container.innerHTML =
@@ -717,9 +731,13 @@ function renderForecast(data) {
let html = ""; let html = "";
daily.forEach((d, i) => { daily.forEach((d, i) => {
const isToday = i === 0; const isToday = i === 0;
const dateLabel = isToday ? "今天" : d.date.substring(5); const isSelected = d.date === selectedForecastDate;
const dateLabel = isToday ? "今天" : d.date.substring(5).replace("-", "/");
html += ` html += `
<div class="forecast-day ${isToday ? "today" : ""}"> <div class="forecast-day ${isToday ? "today" : ""} ${isSelected ? "selected" : ""}"
onclick="switchForecastDate('${data.name}', '${d.date}')"
style="cursor: pointer;">
<div class="f-date">${dateLabel}</div> <div class="f-date">${dateLabel}</div>
<div class="f-temp">${d.max_temp}${sym}</div> <div class="f-temp">${d.max_temp}${sym}</div>
</div> </div>
@@ -737,6 +755,17 @@ function renderForecast(data) {
sunEl.innerHTML = parts.map((p) => `<span>${p}</span>`).join(""); sunEl.innerHTML = parts.map((p) => `<span>${p}</span>`).join("");
} }
function switchForecastDate(cityName, dateStr) {
if (selectedCity !== cityName) return;
selectedForecastDate = dateStr;
const data = cityDataCache[cityName];
if (data) {
renderModels(data);
renderForecast(data);
}
}
function renderAI(data) { function renderAI(data) {
const container = document.getElementById("aiAnalysis"); const container = document.getElementById("aiAnalysis");
const text = data.ai_analysis || ""; const text = data.ai_analysis || "";
+9
View File
@@ -768,6 +768,15 @@ body {
color: var(--accent-cyan); color: var(--accent-cyan);
} }
.forecast-day.selected {
border-color: var(--accent-cyan);
background: rgba(34, 211, 238, 0.1);
box-shadow: 0 0 12px rgba(34, 211, 238, 0.2);
}
.forecast-day.selected .f-date {
color: var(--accent-cyan);
}
.sun-info { .sun-info {
margin-top: 10px; margin-top: 10px;
font-size: 12px; font-size: 12px;