import requests
import pandas as pd
import geopandas as gpd
import matplotlib.pyplot as plt # for plotting
import folium
import branca
import math
# Auswahl an Zeitraum (z.B. eine Hitzewelle, ein Monat, oder der ganze Sommer)
# Daten (UTC) als String in folgendem Format: "YYYY-MM-DDThh:mm:ssZ"
time_from = "2024-10-01T00:00:00Z"
time_to = "2024-10-11T23:59:59Z"
# Auswahl an Stationen
# Wenn alle Stationen berücksichtigt werden sollen, dann einfach die Liste leer lassen
# Hier ist eine Liste aller Stationen mit Name und ID: https://smart-urban-heat-map.ch/api/v2/latest
# station_ids = ["11001", "11002"] # Beispiel für nur eine Auswahl von zwei Stationen
station_ids = [] # Beispiel für alle Stationen
def get_stations() -> gpd.GeoDataFrame:
response = requests.get(
url=f"https://smart-urban-heat-map.ch/api/v2/latest"
)
stations = gpd.read_file(response.text)
return stations
def get_station_analysis_mean_temp(time_from: str, time_to: str, station_ids: [str]) -> pd.DataFrame:
stations = get_stations()
if station_ids:
stations = stations[stations["stationId"].isin(station_ids)]
time_from_dt = pd.to_datetime(time_from)
time_to_dt = pd.to_datetime(time_to)
time_difference = time_to_dt - time_from_dt
min_expected_values = round(time_difference.days * 24 * 2) # minimum two values per hour
stations["mean_temperature"] = None
stations["max_temperature"] = None
stations["date_of_max_temperature"] = None
stations["min_temperature"] = None
stations["date_of_min_temperature"] = None
for idx, station in stations.iterrows():
station_id = station.stationId
response = requests.get(url=f"https://smart-urban-heat-map.ch/api/v2/timeseries?stationId={station_id}&timeFrom={time_from}&timeTo={time_to}")
payload = response.json()
if payload["values"] is None or not len(payload["values"]):
stations = stations.drop(idx)
continue
if len(payload["values"]) < min_expected_values:
stations = stations.drop(idx)
continue
df = pd.DataFrame(payload["values"])
df["dateObserved"] = pd.to_datetime(df["dateObserved"])
df["dateObserved"] = df["dateObserved"].dt.tz_convert("Europe/Zurich")
# hier wird der mittelwert berechnet
mean_temperature = mean_temperatures(df)
# hier werden die min und max temperaturen berechnet
# und das jeweilige datum herausgeschrieben
min_temperature = min_temperatures(df)[0]
date_of_min_temperature = min_temperatures(df)[1]
max_temperature = max_temperatures(df)[0]
date_of_max_temperature = max_temperatures(df)[1]
# hier wird der mittelwert dem 'stations' dataframe hinzugefügt
stations.at[idx, "mean_temperature"] = mean_temperature
# hier werden der min und max temperatur sowie deren messdatum dem 'stations' dataframe hinzugefügt
stations.at[idx, "min_temperature"] = min_temperature
stations.at[idx, "date_of_min_temperature"] = date_of_min_temperature
stations.at[idx, "max_temperature"] = max_temperature
stations.at[idx, "date_of_max_temperature"] = date_of_max_temperature
return stations
def mean_temperatures(df: pd.DataFrame) -> float:
mean_temp = df.mean()["temperature"]
return mean_temp
def min_temperatures(df: pd.DataFrame) -> tuple[float,str]:
min_temp = df.min()["temperature"]
dateof_min_temp = df.loc[df['temperature'].idxmin()]['dateObserved']
return min_temp, dateof_min_temp
def max_temperatures(df: pd.DataFrame) -> tuple[float,str]:
max_temp = df.max()["temperature"]
dateof_max_temp = df.loc[df['temperature'].idxmax()]['dateObserved']
return max_temp, dateof_max_temp
# Analyse der Temperatur laufen lassen
# Dies kann einige Sekunden / Minuten dauern, je nach ausgewähltem Zeitintervall
station_analysis = get_station_analysis_mean_temp(time_from, time_to, station_ids)
name | stationId | geometry | mean_temperature | max_temperature | date_of_max_temperature | min_temperature | date_of_min_temperature | |
---|---|---|---|---|---|---|---|---|
0 | Ausserholligen 2, ewb | 11001 | POINT (7.40642 46.94542) | 23.117102 | 33.069733 | 2024-08-11 16:27:12+02:00 | 16.003662 | 2024-08-09 05:57:11+02:00 |
1 | Bundesplatz | 11002 | POINT (7.44353 46.94692) | 23.867881 | 32.96292 | 2024-08-11 16:09:44+02:00 | 16.791409 | 2024-08-09 06:49:41+02:00 |
2 | Breitenrain, Waffenweg | 11003 | POINT (7.45192 46.96173) | 23.429834 | 33.21126 | 2024-08-11 17:23:41+02:00 | 16.324102 | 2024-08-09 06:23:40+02:00 |
3 | Schosshaldenfriedhof 2 | 11004 | POINT (7.47186 46.95339) | 22.075432 | 31.966888 | 2024-08-11 16:25:48+02:00 | 14.804685 | 2024-08-09 06:25:46+02:00 |
4 | Monbijou-Park | 11005 | POINT (7.43462 46.94187) | 22.962302 | 31.67048 | 2024-08-11 17:31:49+02:00 | 16.006332 | 2024-08-09 06:11:46+02:00 |
... | ... | ... | ... | ... | ... | ... | ... | ... |
134 | Monopoliplatz Lyss | 12006 | POINT (7.30588 47.07661) | 24.016614 | 34.450294 | 2024-08-11 17:29:36+02:00 | 16.193256 | 2024-08-06 06:19:34+02:00 |
135 | Spielplatz Stiglimatt | 12007 | POINT (7.2992 47.07154) | 23.146633 | 33.4329 | 2024-08-11 17:14:31+02:00 | 15.349432 | 2024-08-06 06:14:27+02:00 |
136 | Sportanlage Grien | 12008 | POINT (7.29574 47.07508) | 23.142006 | 33.37415 | 2024-08-11 18:35:40+02:00 | 14.970245 | 2024-08-09 06:25:38+02:00 |
138 | Reitplatz Grünau | 12010 | POINT (7.30167 47.07714) | 23.309495 | 33.742657 | 2024-08-11 16:38:38+02:00 | 14.553674 | 2024-08-06 06:38:35+02:00 |
139 | Sigriswil | 13001 | POINT (7.71244 46.71345) | 22.144175 | 33.128483 | 2024-08-11 17:41:43+02:00 | 15.947585 | 2024-08-05 06:41:37+02:00 |
118 rows × 8 columns
# anzeigen aller stationen
station_analysis
name | stationId | geometry | mean_temperature | max_temperature | date_of_max_temperature | min_temperature | date_of_min_temperature | |
---|---|---|---|---|---|---|---|---|
0 | Ausserholligen 2, ewb | 11001 | POINT (7.40642 46.94542) | 23.117102 | 33.069733 | 2024-08-11 16:27:12+02:00 | 16.003662 | 2024-08-09 05:57:11+02:00 |
1 | Bundesplatz | 11002 | POINT (7.44353 46.94692) | 23.867881 | 32.96292 | 2024-08-11 16:09:44+02:00 | 16.791409 | 2024-08-09 06:49:41+02:00 |
2 | Breitenrain, Waffenweg | 11003 | POINT (7.45192 46.96173) | 23.429834 | 33.21126 | 2024-08-11 17:23:41+02:00 | 16.324102 | 2024-08-09 06:23:40+02:00 |
3 | Schosshaldenfriedhof 2 | 11004 | POINT (7.47186 46.95339) | 22.075432 | 31.966888 | 2024-08-11 16:25:48+02:00 | 14.804685 | 2024-08-09 06:25:46+02:00 |
4 | Monbijou-Park | 11005 | POINT (7.43462 46.94187) | 22.962302 | 31.67048 | 2024-08-11 17:31:49+02:00 | 16.006332 | 2024-08-09 06:11:46+02:00 |
... | ... | ... | ... | ... | ... | ... | ... | ... |
134 | Monopoliplatz Lyss | 12006 | POINT (7.30588 47.07661) | 24.016614 | 34.450294 | 2024-08-11 17:29:36+02:00 | 16.193256 | 2024-08-06 06:19:34+02:00 |
135 | Spielplatz Stiglimatt | 12007 | POINT (7.2992 47.07154) | 23.146633 | 33.4329 | 2024-08-11 17:14:31+02:00 | 15.349432 | 2024-08-06 06:14:27+02:00 |
136 | Sportanlage Grien | 12008 | POINT (7.29574 47.07508) | 23.142006 | 33.37415 | 2024-08-11 18:35:40+02:00 | 14.970245 | 2024-08-09 06:25:38+02:00 |
138 | Reitplatz Grünau | 12010 | POINT (7.30167 47.07714) | 23.309495 | 33.742657 | 2024-08-11 16:38:38+02:00 | 14.553674 | 2024-08-06 06:38:35+02:00 |
139 | Sigriswil | 13001 | POINT (7.71244 46.71345) | 22.144175 | 33.128483 | 2024-08-11 17:41:43+02:00 | 15.947585 | 2024-08-05 06:41:37+02:00 |
118 rows × 8 columns
Note: Die Zeitstempel sind bereits von UTC zu UTC+2 (Central European Summer Time) konvertiert. Dies ist durch den Suffix "+02:00" nach dem Zeitwert angegeben.
Definition: Standort mit höchster mittlerer Temperatur über den oben definierten Zeitraum.
Hier muss für n_warmest eine Zahl eingegeben werden.
# n wärmste Stationen auswählen
n_warmest = 3 # z.B. Wert 3 eingeben, um die wärmsten 3 Stationen anzuzeigen
# Sortieren um die heissesten Stationen anzuzeigen
df_warm = station_analysis.sort_values(by='mean_temperature', ascending=False)[:n_warmest]
df_warm
name | stationId | geometry | mean_temperature | max_temperature | date_of_max_temperature | min_temperature | date_of_min_temperature | |
---|---|---|---|---|---|---|---|---|
59 | Inselspital | 11062 | POINT (7.42571 46.94719) | 24.770183 | 34.874874 | 2024-08-11 14:00:29+02:00 | 17.317465 | 2024-08-09 06:20:26+02:00 |
33 | Laupenstrasse | 11036 | POINT (7.43639 46.94734) | 24.562022 | 34.874874 | 2024-08-11 17:57:00+02:00 | 17.672617 | 2024-08-07 10:36:57+02:00 |
35 | Postgasse | 11038 | POINT (7.45381 46.94878) | 24.392376 | 33.945602 | 2024-08-11 16:18:31+02:00 | 17.726025 | 2024-08-09 06:28:27+02:00 |
Definition: Standort mit tiefster mittlerer Temperatur über den oben definierten Zeitraum.
Hier muss für c_coldest eine Zahl eingegeben werden.
# n kälteste Stationen auswählen
n_coldest = 3 # z.B. Wert 3 eingeben, um die kältesten 3 Stationen anzuzeigen
# Sortieren um die kältesten Stationen anzuzeigen
df_cold = station_analysis.sort_values(by='mean_temperature', ascending=True)[:n_coldest]
df_cold
name | stationId | geometry | mean_temperature | max_temperature | date_of_max_temperature | min_temperature | date_of_min_temperature | |
---|---|---|---|---|---|---|---|---|
47 | Bremgartenwald | 11050 | POINT (7.42128 46.96512) | 20.431999 | 29.563593 | 2024-08-11 16:46:06+02:00 | 14.465552 | 2024-08-09 06:26:05+02:00 |
88 | Köniz Schliern Laterne | 11091 | POINT (7.41517 46.90915) | 20.45244 | 32.69589 | 2024-08-11 15:59:28+02:00 | 14.804685 | 2024-08-05 06:39:24+02:00 |
57 | Liebefeld Turnierstrasse Laterne | 11060 | POINT (7.41337 46.93826) | 21.056738 | 30.436789 | 2024-08-11 14:04:57+02:00 | 14.569695 | 2024-08-09 06:54:55+02:00 |
# mean_temperature Map
m = folium.Map(location=[station_analysis.geometry.y.mean(), station_analysis.geometry.x.mean()], zoom_start=13, tiles="CartoDB positron")
# Add a fixed title to the map
title_html = f'''
<div style="position: fixed;
top: 20px; left: 100px; width: 25%; height: 45px;
background-color: #F0F0F0; border: 1px solid black; z-index: 9999; font-size: 14px; font-weight: bold;">
Mittlere Temperatur <br> vom {pd.to_datetime(time_from).strftime('%d.%m.%Y')} bis {pd.to_datetime(time_to).strftime('%d.%m.%Y')}
</div>
'''
m.get_root().html.add_child(folium.Element(title_html))
colormap = branca.colormap.linear.YlOrRd_09
mean_temperature = station_analysis.mean_temperature.values
# Define colourmap range depending on values of 'mean_temperature
vmin = math.floor(mean_temperature.min())
vmax = math.ceil(mean_temperature.max())
# Define the colormap with the specified range
colormap = branca.colormap.linear.YlOrRd_09.scale(vmin, vmax)
# Convert to step colormap with a specified number of steps
n_steps = int((vmax - vmin) / 0.5) # Define the number of steps
colormap = colormap.to_step(n_steps)
# colormap = colormap.scale(0, mean_temperature).to_step(mean_temperature)
colormap.caption = "Mittlere Temperatur"
colormap.add_to(m)
# plot each station temperature
for idx, station in station_analysis.iterrows():
color = colormap(station.mean_temperature)
# text with temperature value
folium.Marker(
location=(station.geometry.y, station.geometry.x),
icon=folium.DivIcon(
html=f'<div style="font-size: 10pt; color: {color}; text-shadow: -1px -1px 0 #D3D3D3, 1px -1px 0 #D3D3D3, -1px 1px 0 #D3D3D3, 1px 1px 0 #D3D3D3;">{station.mean_temperature:.1f}°C</div>'
),
tooltip=f"{station['name']}: Mittlere Temperatur {station.mean_temperature:.1f} °C",
).add_to(m)
# show map
m