modified: app.py
modified: requirements.txt modified: static/css/style.css modified: templates/base.html modified: templates/weather.html
This commit is contained in:
56
app.py
56
app.py
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user