In der Umweltanalytik hat man es sehr oft mit Analysenergebnissen zu tun, die einen geografischen Bezug haben. Hier kann es dann hilfreich sein, solche Daten auch geografisch darzustellen, ob für Berichte, Präsentationen oder auch als Diskussionsgrundlage in Besprechungen. Mit Python gibt es da mehrere Wege die zum Ziel führen, z.B. mit dem Package “cartopy” oder auch mit “plotly”. Heute möchte ich eine Variante mit “cartopy” vorstellen. Dazu wird zuerst einmal wieder alles importiert, was hierzu benötigt wird.
Neben den fast schon obligatorischen Imports von “pandas”, “matplotlib” und “seaborn” benötigen wir aus dem Paket “cartopy” das Koordinatenreferenzsystem (CRS) und “Image tiles” zur Darstellung von Geo-Karten bzw. für den Support von OpenStreetMap Karten.
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import cartopy.crs as ccrs
from cartopy.io.img_tiles import OSM
Als Beispiel sollen die Jahresmittelwerte für den polyzykischen aromatischen Kohlenwasserstoff “Benzo[a]pyren” (BaP) für das Jahr 2022 für einige ausgewählte Städte des Ruhrgebiets in eine Karte geplottet werden. Die Größe des Datenpunkts in der Karte soll die größe des Jahresmittelwerts repräsentieren. Diese Daten gibt es beim LANUV NRW. (Datenquelle: https://www.lanuv.nrw.de/fileadmin/lanuv/luft/immissionen/Disko-Immissionen/Disko-Immissionen-2022-1.0_PMx.pdf) Ein Blick in die Zusammenfassung vom LANUV ergibt:
Ort | BaP [ng/m³] |
Bottrop-Welheim | 0,73 |
Duisburg-Ehingen | 1,42 |
Duisburg-Walsum | 0,26 |
Krefeld (Hafen) | 0,12 |
Mülheim-Styrum | 0,17 |
Da in den LANUV Daten keine Geodaten für die Messstellen angegeben sind, habe ich diese aus dem groben Mittelpunkt des jeweiligen Stadtteils geschätzt. Diese Daten wandern dann zusammen mit den Daten zu BaP in ein DataFrame. Dabei ist “lat” die GPS Breitengradangabe und “lon” die zugehörige GPS Längengradangabe:
daten = {"Ort": ["Bottrop-Welheim", "Duisburg-Ehingen", "Duisburg-Walsum", "Krefeld (Hafen)", "Mülheim-Styrum"],
"BaP": [0.73, 1.42, 0.26, 0.12, 0.17],
"lat": [51.527, 51.366, 51.544, 51.352, 51.451],
"lon": [6.982, 6.697, 6.716, 6.651, 6.856]}
df = pd.DataFrame(daten)
df
Ort | BaP | lat | lon | |
---|---|---|---|---|
0 | Bottrop-Welheim | 0.73 | 51.527 | 6.982 |
1 | Duisburg-Ehingen | 1.42 | 51.366 | 6.697 |
2 | Duisburg-Walsum | 0.26 | 51.544 | 6.716 |
3 | Krefeld (Hafen) | 0.12 | 51.352 | 6.651 |
4 | Mülheim-Styrum | 0.17 | 51.451 | 6.856 |
Als nächstes setzen wir die Eckpunkte für den Kartenausschnitt, wobei “lat1/lon1” dem unteren linken Eckpunkt des Kartenausschnitts entspricht und “lat2/lon2” entsprechend dem oberen rechten Eckpunkt.
# lat und lon für Basis-Map setzen
lat1 = 51.25
lon1 = 6.45
lat2 = 51.59
lon2 = 7.10
Nun geht es auch schon ans Plotten der Karte und der Datenpunkte. Zuerst wird mit “plt.figure” die Größe des Plots definiert, dann wird eine OpenStreetMap Kachel initialisiert und die Projektion den Achsen zugewiesen. Es folgt das Setzen des Kartenausschnitts und das Hinzufügen des OSM-Bildes in den Plot. Mit “alpha=0.8” wird die Deckkraft der Karte ein wenig reduziert. Schließlich kann das DataFrame als Scatterplot mit den Koordinaten zugefügt werden.
plt.figure(figsize=(8, 10))
osm_tiles = OSM()
ax = plt.axes(projection=osm_tiles.crs)
ax.set_extent([lon1, lon2, lat1, lat2])
ax.add_image(osm_tiles, 10, alpha=0.8)
ax2 = sns.scatterplot(data=df, x="lon", y="lat", hue="BaP", size="BaP", sizes=(400, 1500),
transform=ccrs.PlateCarree(), palette="dark:salmon_r")
plt.legend(markerscale=0.4, bbox_to_anchor=(1.02, 1.02), loc='upper left')
ax.set_title("Benza[a]pyren Jahresmittelwerte 2022 [ng/m³]")
Mit dieser Variante ist es also ziemlich einfach, Analysenwerte geografisch darzustellen. In Teil 2 werden wir Gewässerverläufe aus Geo-JSON Dateien mittels “geopandas” landen und die Gewässerverläufe in die Karten plotten, um Analysenwerte an Flüssen und Gewässern zu visualisieren.