Weiter geht es mit der geografischen Darstellung von Analysendaten. Nachdem es in Teil 1 darum ging mit “cartopy” eine OpenStreetMap (OSM) Karte als Hintergrundbild für einen Matplotlib-Plot zu setzen und dann Datenpunkte mit ihren GPS Koordinaten in diese Karte zu zeichnen, soll es in Teil 2 darum gehen, einen Gewässerverlauf in eine OSM Karte einzufügen.
Zwar werden Gewässer auf OpenStreetMap Karten durchaus dargestellt, allerdings kann es zur Verdeutlichung sehr nützlich sein, ein Gewässer mit einer dicken Linie deutlich darzustellen.
Für dieses Beispiel hier möchte ich die Geodaten des Flusses “Nette” im Kreis Viersen abgreifen und im Plot darstellen. Die Daten können über die Seite “Overpass Turbo” aus OpenStreetMap in einem verwendbaren Format exportiert werden (https://overpass-turbo.eu/index.html). “Overpass Turbo” eine Abfrageschnittstelle zu OpenStreetMap und ist sehr nützlich, um solche Geodaten aus OSM zu exportieren.
Bevor wir dies tun können, benötigen wir die OpenStreetMap-ID des Flüsschens “Nette”, die man als sogenannte “Relation ID” auf der OSM-Webseite finden kann. Eine Suche für die “Nette” liefert uns die ID “4727359”:
https://www.openstreetmap.org/relation/4727359
Mit dieser RelationID ist es nun möglich, mittels Overpass die Geodaten für den Gewässerverlauf abzufragen und im Format “GeoJSON” zu speichern. Die passende Abfrage für das linke Eingabefenster der Overpass-Schnittstelle sieht wie folgt aus:
Ein Klick auf “Ausführen” startet die Abfrage und die Daten werden im rechten Fenster visuell dargestellt. Über “Export -> GeoJSON” lassen sich die Daten nun im Format “GeoJSON” auf unserem PC lokal speichern. Im Jupyter-Notebook starten wir nun mit den notwendigen Imports:
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
import geopandas as gpd
Zu den bekannten Imports aus Teil 1 kommt nun noch das Paket “geopandas” hinzu, welches zum Verarbeiten des exportierten GeoJSON-Files benötigt wird. Bevor es ans Laden und Plotten der Gewässerdaten geht, muss noch der Kartenausschnitt festgelegt werden. Genau wie in Teil 1 entspricht die Koordinate lat1/lon1 der linken unteren Ecke der Karte und lat2/lon2 der rechten oberen Ecke.
# Kartenausschnitt festglegen
lat1 = 51.247
lon1 = 6.189
lat2 = 51.426
lon2 = 6.409
Mittels “geopandas” kann nun das vorher über Overpass exportierte Datenfile aus OpenStreetMap mit einem Einzeiler geladen werden.
geo_df = gpd.read_file("export.geojson")
geo_df
id | @id | destination | destination:ref | name | ref:fgkz | type | waterway | wikidata | wikipedia | geometry | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | relation/4727359 | relation/4727359 | Niers | 286 | Nette | 2862 | waterway | stream | Q315322 | de:Nette (Niers) | LINESTRING (6.32156 51.25316, 6.31938 51.25311… |
Das DataFrame sieht zuerst recht komisch aus. Insbesondere die Spalte “geometry”. Hierbei handelt es sich um eine sogenannte GeoSeries:
type(geo_df.geometry)
geopandas.geoseries.GeoSeries
Die Spalte “geometry” enthält den GPS-Verlauf des Flüsschens “Nette”. Ähnlich wie eine pandas-Series eine Serie von skalaren Zahlen enthält, so einhält die von “geopandas” bereitgestellte “GeoSeries” eine Serie mit Geodaten. Diese Serie lässt sich glücklicherweise direkt über die “cartopy”-Funktion “add_geometries” in das OpenStreetMap Image plotten. Hierzu erweitern wir den Plot aus Teil 1 lediglich um den “ax.add_geometries”-Teil und übergeben hier das DataFrame mit den Geodaten:
plt.figure(figsize=(8, 8))
osm_tiles = OSM()
ax = plt.axes(projection=osm_tiles.crs)
ax.set_extent([lon1, lon2, lat1, lat2])
ax.add_image(osm_tiles, 12, alpha=0.5)
ax.add_geometries(geo_df["geometry"], ccrs.PlateCarree(), edgecolor="#008bd0", facecolor='none', linewidth=3, alpha=1)
<cartopy.mpl.feature_artist.FeatureArtist at 0x27697dd6750>
![No description has been provided for this image](/jupyter-img/0006/img1.png)
Durch die Reduktion der Deckkraft für das OSM Image (alpha=0.5) und der vollen Deckkraft für die Gewässer-Geometrie ist das Flüsschen wesentlich deutlicher dargestellt als im originalen OSM Bild. Analog zum ersten Teil ist es nun kein Problem mehr, mit z.B. mit einem Scatterplot über GPS Koordinaten Datenpunkte auf den Gewässerverlauf zu plotten. Für zwei fiktive Datenpunkte zu einer Stoffkonzentration in zwei der Seen sähe das so aus:
daten = {"Ort": ["See 1", "See 2"],
"Konzentration": [10, 24],
"lat": [51.287, 51.345],
"lon": [6.272, 6.251]}
df = pd.DataFrame(daten)
df
Ort | Konzentration | lat | lon | |
---|---|---|---|---|
0 | See 1 | 10 | 51.287 | 6.272 |
1 | See 2 | 24 | 51.345 | 6.251 |
Eine Erweiterung des Plot um einen Scatterplot liefert dann eine Grafik mit den Gewässerverlauf und den zwei Datenpunkten auf dem Gewässerverlauf an den Positionen der Seen.
plt.figure(figsize=(8, 8))
osm_tiles = OSM()
ax = plt.axes(projection=osm_tiles.crs)
ax.set_extent([lon1, lon2, lat1, lat2])
ax.add_image(osm_tiles, 12, alpha=0.5)
ax.add_geometries(geo_df["geometry"], ccrs.PlateCarree(), edgecolor="#008bd0", facecolor='none', linewidth=3, alpha=0.7)
ax = sns.scatterplot(data=df, x="lon", y="lat", hue="Konzentration", size="Konzentration", sizes=(400, 800),
transform=ccrs.PlateCarree(), palette="Paired")
plt.legend(markerscale=0.3, bbox_to_anchor=(1.02, 1.02), loc='upper left')
<matplotlib.legend.Legend at 0x27697da3410>
![No description has been provided for this image](/jupyter-img/0006/img2.png)
Diese geopandas Funktionalität zur direkten Verwendung von geoJSON Dateien ist in Verbindung mit cartopy ein klasse Tool um unkompliziert mit Geodaten aus Datenbanken wie OpenStreetMap zu arbeiten. Mittels solcher geoJSON Pfade lassen sich dann leicht Landes- oder Städtegrenzen, Flüsse oder Routen in OSM-Maps plotten.