modified: app.py
modified: static/css/style.css
This commit is contained in:
35
app.py
35
app.py
@@ -72,6 +72,7 @@ MOSMIX_PARAMS = [
|
|||||||
"hourly/large/sunshine_duration",
|
"hourly/large/sunshine_duration",
|
||||||
"hourly/large/probability_precipitation_height_gt_0_1mm_last_1h",
|
"hourly/large/probability_precipitation_height_gt_0_1mm_last_1h",
|
||||||
"hourly/large/uv_index",
|
"hourly/large/uv_index",
|
||||||
|
"hourly/large/visibility",
|
||||||
]
|
]
|
||||||
|
|
||||||
def _get_berlin():
|
def _get_berlin():
|
||||||
@@ -335,14 +336,24 @@ def _parse_warning_datetime(value):
|
|||||||
# regen heavy rain
|
# regen heavy rain
|
||||||
# schnee snow
|
# schnee snow
|
||||||
# blitz thunderstorm (needs additional MOSMIX parameter)
|
# blitz thunderstorm (needs additional MOSMIX parameter)
|
||||||
# wolkig_nebel_sonne fog / mist
|
# nebel dense fog (visibility < 1000 m)
|
||||||
|
# nebel_wolkig foggy + overcast (1000-5000 m, cloud > 60 %)
|
||||||
|
# wolkig_nebel_sonne patchy fog / haze (1000-5000 m, less cloud)
|
||||||
# nacht clear night (moon)
|
# nacht clear night (moon)
|
||||||
# nacht(1) moon behind cloud
|
# nacht(1) moon behind cloud
|
||||||
# nacht(2) moon+cloud+rain
|
# nacht(2) moon+cloud+rain
|
||||||
# nacht(3) moon+cloud+snow
|
# nacht(3) moon+cloud+snow
|
||||||
|
|
||||||
def weather_icon(cloud_pct, precip_mm, rain_prob, temp_c, is_night=False):
|
def weather_icon(cloud_pct, precip_mm, rain_prob, temp_c, is_night=False, visibility_m=None):
|
||||||
"""Return the icon key for static/icons/{key}.png."""
|
"""Return the icon key for static/icons/{key}.png."""
|
||||||
|
# Fog (takes priority; fog unlikely when precipitating heavily)
|
||||||
|
if visibility_m is not None and visibility_m < 5000 and not (precip_mm and precip_mm > 0.5):
|
||||||
|
if visibility_m < 1000:
|
||||||
|
return "nebel"
|
||||||
|
if cloud_pct is not None and cloud_pct > 60:
|
||||||
|
return "nebel_wolkig"
|
||||||
|
return "wolkig_nebel_sonne"
|
||||||
|
|
||||||
# Snow / sleet (day and night)
|
# Snow / sleet (day and night)
|
||||||
if temp_c is not None and temp_c <= 2 and (
|
if temp_c is not None and temp_c <= 2 and (
|
||||||
(precip_mm and precip_mm > 0.1) or (rain_prob and rain_prob >= 40)
|
(precip_mm and precip_mm > 0.1) or (rain_prob and rain_prob >= 40)
|
||||||
@@ -375,6 +386,21 @@ def pick_daily_icon(hours):
|
|||||||
"""Choose the most representative icon key for a whole day."""
|
"""Choose the most representative icon key for a whole day."""
|
||||||
if not hours:
|
if not hours:
|
||||||
return "sonne"
|
return "sonne"
|
||||||
|
# Fog: majority of hours with reduced visibility and no heavy precipitation
|
||||||
|
fog_hours = [
|
||||||
|
h for h in hours
|
||||||
|
if h.get("visibility_m") is not None
|
||||||
|
and h["visibility_m"] < 5000
|
||||||
|
and (h.get("precip_mm") or 0) <= 0.5
|
||||||
|
]
|
||||||
|
if len(fog_hours) >= len(hours) // 2 and fog_hours:
|
||||||
|
avg_vis = sum(h["visibility_m"] for h in fog_hours) / len(fog_hours)
|
||||||
|
avg_cloud_fog = sum(h.get("cloud_pct") or 0 for h in fog_hours) / len(fog_hours)
|
||||||
|
if avg_vis < 1000:
|
||||||
|
return "nebel"
|
||||||
|
if avg_cloud_fog > 60:
|
||||||
|
return "nebel_wolkig"
|
||||||
|
return "wolkig_nebel_sonne"
|
||||||
# Snow / sleet
|
# Snow / sleet
|
||||||
if any(
|
if any(
|
||||||
h.get("temp_c") is not None and h["temp_c"] <= 2
|
h.get("temp_c") is not None and h["temp_c"] <= 2
|
||||||
@@ -590,6 +616,8 @@ def get_mosmix_forecast(lat, lon, hours=72):
|
|||||||
sun_min = round(float(sun)/60) if not _isnan(sun) else 0
|
sun_min = round(float(sun)/60) if not _isnan(sun) else 0
|
||||||
wd = p.get("wind_direction")
|
wd = p.get("wind_direction")
|
||||||
wind_dir = float(wd) if not _isnan(wd) else None
|
wind_dir = float(wd) if not _isnan(wd) else None
|
||||||
|
vis_raw = p.get("visibility")
|
||||||
|
visibility_m = round(float(vis_raw)) if not _isnan(vis_raw) else None
|
||||||
uv_raw = p.get("uv_index")
|
uv_raw = p.get("uv_index")
|
||||||
dt_local = pd.Timestamp(date_val).tz_convert(berlin).tz_localize(None)
|
dt_local = pd.Timestamp(date_val).tz_convert(berlin).tz_localize(None)
|
||||||
# Determine day/night for icon selection
|
# Determine day/night for icon selection
|
||||||
@@ -624,7 +652,8 @@ def get_mosmix_forecast(lat, lon, hours=72):
|
|||||||
"confidence": confidence_score,
|
"confidence": confidence_score,
|
||||||
"confidence_label": confidence_label,
|
"confidence_label": confidence_label,
|
||||||
"activity_score": a_score,
|
"activity_score": a_score,
|
||||||
"icon": weather_icon(clouds, precip, rain_prob, temp_c, is_night=is_night),
|
"visibility_m": visibility_m,
|
||||||
|
"icon": weather_icon(clouds, precip, rain_prob, temp_c, is_night=is_night, visibility_m=visibility_m),
|
||||||
})
|
})
|
||||||
result_data = (forecast, station_info)
|
result_data = (forecast, station_info)
|
||||||
_forecast_cache[cache_key] = result_data
|
_forecast_cache[cache_key] = result_data
|
||||||
|
|||||||
@@ -441,6 +441,8 @@ main { flex: 1; }
|
|||||||
.wx-icon-wrap[data-icon="schnee"] { background: linear-gradient(135deg, #3070a8 0%, #70b8e0 100%); }
|
.wx-icon-wrap[data-icon="schnee"] { background: linear-gradient(135deg, #3070a8 0%, #70b8e0 100%); }
|
||||||
.wx-icon-wrap[data-icon="blitz"] { background: linear-gradient(135deg, #1e1040 0%, #5030a0 100%); }
|
.wx-icon-wrap[data-icon="blitz"] { background: linear-gradient(135deg, #1e1040 0%, #5030a0 100%); }
|
||||||
.wx-icon-wrap[data-icon="wolkig_nebel_sonne"] { background: linear-gradient(135deg, #706858 0%, #a09880 100%); }
|
.wx-icon-wrap[data-icon="wolkig_nebel_sonne"] { background: linear-gradient(135deg, #706858 0%, #a09880 100%); }
|
||||||
|
.wx-icon-wrap[data-icon="nebel"] { background: linear-gradient(135deg, #7a8a96 0%, #b0c0cc 100%); }
|
||||||
|
.wx-icon-wrap[data-icon="nebel_wolkig"] { background: linear-gradient(135deg, #505a64 0%, #8898a8 100%); }
|
||||||
/* Night icons */
|
/* Night icons */
|
||||||
.wx-icon-wrap[data-icon="nacht"] { background: linear-gradient(135deg, #080c20 0%, #141c40 100%); }
|
.wx-icon-wrap[data-icon="nacht"] { background: linear-gradient(135deg, #080c20 0%, #141c40 100%); }
|
||||||
.wx-icon-wrap[data-icon="nacht(1)"] { background: linear-gradient(135deg, #0c1228 0%, #1e2848 100%); }
|
.wx-icon-wrap[data-icon="nacht(1)"] { background: linear-gradient(135deg, #0c1228 0%, #1e2848 100%); }
|
||||||
|
|||||||
Reference in New Issue
Block a user