From 08a8bb2e1307c7fa533da1b06bb3c4dd4c42f650 Mon Sep 17 00:00:00 2001 From: simon Date: Fri, 24 Apr 2026 09:43:36 +0200 Subject: [PATCH] modified: app.py modified: static/css/style.css modified: templates/weather.html --- app.py | 69 ++++++++++++++++++++++++++---------------- static/css/style.css | 11 +++++-- templates/weather.html | 12 ++++++-- 3 files changed, 60 insertions(+), 32 deletions(-) diff --git a/app.py b/app.py index 191cb82..e0bca6a 100644 --- a/app.py +++ b/app.py @@ -73,6 +73,20 @@ MOSMIX_PARAMS = [ "hourly/large/uv_index", ] +# ── Wetter-Icons (OpenWeatherMap CDN) ──────────────────────────────────────── +_OWM = "https://openweathermap.org/img/wn/" +ICON_URLS = { + "☀️": _OWM + "01d@2x.png", # klar + "⛅": _OWM + "02d@2x.png", # leicht bewölkt + "☁️": _OWM + "04d@2x.png", # bedeckt + "🌧️": _OWM + "10d@2x.png", # Regen + "🌦️": _OWM + "09d@2x.png", # Regenschauer + "❄️": _OWM + "13d@2x.png", # Schnee +} + +def _icon_url(emoji): + return ICON_URLS.get(str(emoji), _OWM + "03d@2x.png") + def _get_berlin(): try: import zoneinfo @@ -341,41 +355,43 @@ def pick_daily_icon(hours): return "☀️" def feels_like(temp_c, wind_kmh, cloud_pct): - """Apparent temperature. + """Apparent / perceived temperature. - * Below 10 °C with wind > 4.8 km/h: Windchill (JAG/TI formula) - * Above 27 °C: Heat index (Rothfusz, assumed RH 60 %) - * Otherwise: actual temp ± sunshine/wind adjustments - - Wind cools by up to ~3 °C even in the mild range - - Sunshine (low clouds, no wind) warms by up to +4 °C + Uses the JAG/TI wind-chill formula blended smoothly across the full + temperature range so there is no abrupt jump at the old 10 °C threshold. + Above 27 °C the Rothfusz heat index (assumed RH 60 %) is applied. + + The previous cloud-based sunshine bonus has been removed: cloud cover + fluctuates strongly hour to hour, which caused the felt temperature to + jump by several degrees without any real change in conditions. """ if temp_c is None: return None - # ── Cold range: wind chill ──────────────────────────────────────── - if temp_c <= 10 and wind_kmh is not None and wind_kmh > 4.8: - v = wind_kmh - wc = 13.12 + 0.6215*temp_c - 11.37*(v**0.16) + 0.3965*temp_c*(v**0.16) - return round(wc, 1) - # ── Hot range: heat index ───────────────────────────────────────── + + # ── Hot range: heat index (Rothfusz, RH 60 %) ──────────────────── if temp_c >= 27: rh = 60 - hi = (-8.78469475556 + 1.61139411*temp_c + 2.33854883889*rh - - 0.14611605*temp_c*rh - 0.012308094*temp_c**2 - - 0.016424828*rh**2 + 0.002211732*temp_c**2*rh - + 0.00072546*temp_c*rh**2 - 0.000003582*temp_c**2*rh**2) + hi = (-8.78469475556 + 1.61139411 * temp_c + 2.33854883889 * rh + - 0.14611605 * temp_c * rh - 0.012308094 * temp_c ** 2 + - 0.016424828 * rh ** 2 + 0.002211732 * temp_c ** 2 * rh + + 0.00072546 * temp_c * rh ** 2 + - 0.000003582 * temp_c ** 2 * rh ** 2) return round(hi, 1) - # ── Mild range: sunshine bonus + light wind cooling ─────────────── + + # ── Wind-chill (JAG/TI), blended into mild range ────────────────── + # Full wind-chill effect at ≤ 5 °C, linearly faded to zero at ≥ 20 °C. + # This avoids the hard jump that the old ≤ 10 °C threshold caused. + wind = wind_kmh or 0 adjusted = float(temp_c) - # Sunshine bonus: clear sky (cloud_pct < 30) adds up to +4 °C - if cloud_pct is not None and cloud_pct < 30: - sunshine_bonus = (30 - cloud_pct) / 30 * 4.0 - adjusted += sunshine_bonus - # Moderate wind cooling: >10 km/h removes up to 3 °C - if wind_kmh is not None and wind_kmh > 10: - wind_penalty = _clamp((wind_kmh - 10) / 40 * 3.0, 0, 3.0) - adjusted -= wind_penalty + if wind > 4.8: + wc = (13.12 + + 0.6215 * temp_c + - 11.37 * (wind ** 0.16) + + 0.3965 * temp_c * (wind ** 0.16)) + blend = _clamp((20.0 - temp_c) / 15.0, 0.0, 1.0) + adjusted = temp_c * (1.0 - blend) + wc * blend + result = round(adjusted, 1) - # Only return a value if it meaningfully differs from actual temp return result if abs(result - temp_c) >= 0.5 else temp_c def get_sun_times(lat, lon, date=None): @@ -747,6 +763,7 @@ def wetter(): chart_precip=chart_precip, chart_rain_prob=chart_rain_prob, wind_dir_name=wind_direction_name, uv_risk_info=uv_risk_info, + icon_url=_icon_url, ) @app.route("/api/suggest") diff --git a/static/css/style.css b/static/css/style.css index b5a88ca..7cbf869 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -299,7 +299,12 @@ main { flex: 1; } display: flex; flex-direction: column; justify-content: flex-end; padding-bottom: 0.6rem; } -.hero-icon-big { font-size: 3rem; line-height: 1; margin-bottom: 0.4rem; } +.hero-icon-big { line-height: 1; margin-bottom: 0.4rem; } + +/* Weather icon images (OWM CDN) */ +.wx-icon { display: block; width: 40px; height: 40px; object-fit: contain; } +.wx-icon--hero { width: 80px; height: 80px; } +.wx-icon--sm { width: 32px; height: 32px; } .hero-stats-mini { display: flex; flex-direction: column; gap: 0.2rem; } .hero-stats-mini span { font-size: 0.85rem; color: var(--muted2); } @@ -410,7 +415,7 @@ main { flex: 1; } .hcard-time { font-size: 0.85rem; font-weight: 600; color: var(--text); } .hcard-date { font-size: 0.68rem; color: var(--muted); margin-top: -0.15rem; } -.hcard-icon { font-size: 1.4rem; line-height: 1; margin: 0.2rem 0; } +.hcard-icon { line-height: 1; margin: 0.2rem 0; display: flex; justify-content: center; } .hcard-temp { font-size: 1rem; font-weight: 700; } .hcard-precip { font-size: 0.7rem; color: var(--blue); font-weight: 500; } .hcard-precip--none { color: var(--muted); font-weight: 400; } @@ -480,7 +485,7 @@ main { flex: 1; } .drow:hover { background: rgba(255,255,255,0.05); } .drow-left { display: flex; align-items: center; gap: 0.6rem; } -.drow-icon { font-size: 1.4rem; flex-shrink: 0; } +.drow-icon { font-size: 1.4rem; flex-shrink: 0; display: flex; align-items: center; } .drow-dow { font-size: 0.875rem; font-weight: 600; color: var(--text); } .drow-bar-wrap { position: relative; } diff --git a/templates/weather.html b/templates/weather.html index 125fa2d..186bae4 100644 --- a/templates/weather.html +++ b/templates/weather.html @@ -27,7 +27,9 @@
{{ current.temp_c if current.temp_c is not none else "–" }}°
-
{{ current.icon or "☁️" }}
+
+ {{ current.icon }} +
{% if current.wind_kmh is not none %} {{ current.wind_kmh }} km/h {{ wind_dir_name(current.wind_dir) }} @@ -168,7 +170,9 @@ {% if h.datetime is string %}{{ h.datetime[8:10] }}.{{ h.datetime[5:7] }}. {% else %}{{ h.datetime.strftime('%d.%m.') }}{% endif %}
-
{{ h.icon }}
+
+ {{ h.icon }} +
{% if h.temp_c is not none %}{{ h.temp_c }}°{% else %}–{% endif %}
@@ -226,7 +230,9 @@ {% for d in daily %}
- {{ d.icon }} + + {{ d.icon }} +
{% if d.date is string %}