modified: app.py

modified:   requirements.txt
	modified:   static/css/style.css
	modified:   templates/base.html
	modified:   templates/weather.html
This commit is contained in:
simon
2026-04-27 10:16:45 +02:00
parent 31d58800f0
commit e793804cea
5 changed files with 75 additions and 17 deletions

56
app.py
View File

@@ -84,6 +84,27 @@ def _get_berlin():
import pytz
return pytz.timezone("Europe/Berlin")
_TF = None # TimezoneFinder singleton
def _get_location_tz(lat, lon):
"""Return (tzinfo, tz_name) for the given coordinates using timezonefinder."""
global _TF
try:
if _TF is None:
from timezonefinder import TimezoneFinder
_TF = TimezoneFinder()
tz_name = _TF.timezone_at(lat=lat, lng=lon)
if tz_name:
try:
import zoneinfo
return zoneinfo.ZoneInfo(tz_name), tz_name
except ImportError:
import pytz
return pytz.timezone(tz_name), tz_name
except Exception:
pass
return _get_berlin(), "Europe/Berlin"
def _normalize_text(value):
if not value:
return ""
@@ -510,26 +531,27 @@ def feels_like(temp_c, wind_kmh, cloud_pct):
def get_sun_times(lat, lon, date=None):
try:
loc = LocationInfo(latitude=lat, longitude=lon, timezone="Europe/Berlin")
loc_tz, tz_name = _get_location_tz(lat, lon)
loc = LocationInfo(latitude=lat, longitude=lon, timezone=tz_name)
d = date or _dt.date.today()
s = astral_sun(loc.observer, date=d, tzinfo=_get_berlin())
s = astral_sun(loc.observer, date=d, tzinfo=loc_tz)
return (s["sunrise"].strftime("%H:%M"), s["sunset"].strftime("%H:%M"),
s["dawn"].strftime("%H:%M"), s["dusk"].strftime("%H:%M"))
except Exception:
return None, None, None, None
def _sunrise_sunset(lat, lon, d):
"""Return (naive_sunrise, naive_sunset) in Berlin local time for date d.
"""Return (naive_sunrise, naive_sunset) in location local time for date d.
Results are cached in _sun_cache to avoid recomputing for every forecast hour."""
key = (round(lat, 1), round(lon, 1), d)
if key in _sun_cache:
return _sun_cache[key]
try:
berlin = _get_berlin()
loc = LocationInfo(latitude=lat, longitude=lon, timezone="Europe/Berlin")
s = astral_sun(loc.observer, date=d, tzinfo=berlin)
sr = pd.Timestamp(s["sunrise"]).tz_convert(berlin).tz_localize(None)
ss = pd.Timestamp(s["sunset"]).tz_convert(berlin).tz_localize(None)
loc_tz, tz_name = _get_location_tz(lat, lon)
loc = LocationInfo(latitude=lat, longitude=lon, timezone=tz_name)
s = astral_sun(loc.observer, date=d, tzinfo=loc_tz)
sr = pd.Timestamp(s["sunrise"]).tz_convert(loc_tz).tz_localize(None)
ss = pd.Timestamp(s["sunset"]).tz_convert(loc_tz).tz_localize(None)
result = (sr, ss)
except Exception:
result = (None, None)
@@ -616,12 +638,13 @@ def wind_direction_name(degrees):
idx = round(float(degrees)/22.5) % 16
return dirs[idx]
def get_mosmix_forecast(lat, lon, hours=72):
def get_mosmix_forecast(lat, lon, hours=72, loc_tz=None):
cache_key = (round(lat,2), round(lon,2), hours)
if cache_key in _forecast_cache:
return _forecast_cache[cache_key]
try:
berlin = _get_berlin()
if loc_tz is None:
loc_tz, _ = _get_location_tz(lat, lon)
req = DwdMosmixRequest(parameters=MOSMIX_PARAMS)
nearest = req.filter_by_rank(latlon=(lat, lon), rank=1)
result = nearest.values.all()
@@ -671,7 +694,7 @@ def get_mosmix_forecast(lat, lon, hours=72):
ww_raw = p.get("weather_significant")
weather_code = int(float(ww_raw)) if not _isnan(ww_raw) else None
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(loc_tz).tz_localize(None)
# Determine day/night for icon selection
_sr, _ss = _sunrise_sunset(lat, lon, dt_local.date())
is_night = bool(
@@ -821,7 +844,8 @@ def wetter():
lat, lon, display_name, state_hint, location_names = geocode_location(ort)
if lat is None:
return render_template("index.html", error=f'Ort "{ort}" konnte nicht gefunden werden.')
forecast, mosmix_station = get_mosmix_forecast(lat, lon, hours=240)
loc_tz, location_tz = _get_location_tz(lat, lon)
forecast, mosmix_station = get_mosmix_forecast(lat, lon, hours=240, loc_tz=loc_tz)
if not forecast:
return render_template("index.html", error="Keine Wetterdaten verfügbar. Bitte später erneut versuchen.")
station_name = mosmix_station.get("name", ort)
@@ -829,15 +853,14 @@ def wetter():
station_lat = float(mosmix_station.get("latitude", lat))
station_lon = float(mosmix_station.get("longitude", lon))
station_dist = round(haversine(lat, lon, station_lat, station_lon), 1)
berlin = _get_berlin()
now_local_dt = _dt.datetime.now(berlin)
now_local_dt = _dt.datetime.now(loc_tz)
now_local = now_local_dt.strftime("%H:%M")
now_berlin_naive = now_local_dt.replace(minute=0, second=0, microsecond=0, tzinfo=None)
now_loc_naive = now_local_dt.replace(minute=0, second=0, microsecond=0, tzinfo=None)
current_idx = 0
for i, h in enumerate(forecast):
dt = h["datetime"]
dt_naive = dt.replace(tzinfo=None) if hasattr(dt,"tzinfo") and dt.tzinfo is not None else dt
if dt_naive >= now_berlin_naive:
if dt_naive >= now_loc_naive:
current_idx = i
break
current = forecast[current_idx]
@@ -889,6 +912,7 @@ def wetter():
ort=ort, display_name=display_name, lat=lat, lon=lon,
station_name=station_name, station_id=station_id, station_dist=station_dist,
current=current, now_local=now_local,
location_tz=location_tz,
pressure_delta=pressure_delta, pressure_trend=pressure_trend,
temp_delta_6h=temp_delta_6h, temp_trend_6h=temp_trend_6h,
best_window=best_window,