Dienste wie Google Maps oder Openrouteservice spucken die Entfernung zwischen zwei Punkten auf der Landkarte aus. Mit diesen Python-Tools docken Sie an Online-Routern an und entwickeln einen eigenen Entfernungsrechner.
Wer früher die auf einer kurvigen Landstraße zurückzulegende Distanz mithilfe einer Karte ermitteln wollte, benötigt ein Kurvimeter oder Opisometer: Mit dem Rädchen an der Spitze des stiftartigen Geräts fuhr man die gewünschte Strecke auf der Karte ab, ein Zeiger auf einer Skala gab dann die entsprechende Distanz in mehreren Maßstäben an. In der digitalen Ära braucht man solche Werkzeuge nicht mehr: Google Maps oder OpenStreetMap [1] liefern auf Knopfdruck exakte Zahlen.
Es gibt aber auch noch andere Wege mit den Kartendaten zu arbeiten. Als Alternative zu kommerziellen Kartendiensten bietet es sich an, den eigenen Computer rechnen zu lassen. Im Folgenden zeigen wir Ihnen, wie Sie das mithilfe der Programmiersprache Python und der Open-Source-Bibliothek Geopy [2] bewerkstelligen. Dazu genügen eine Internet-Verbindung und wenige Programmzeilen Python-Code.
Ähnliche Beiträge aus LinuxUser
Bereits in früheren Ausgaben von LinuxUser haben wir uns mit dem Auswerten und Verarbeiten von Geodaten beschäftigt. So zeigten wir etwa, wie Sie ein Fahrtenbuch mit Bordmitteln auswerten [26], mit Python und OpenLayers eigene Reisen visualisieren [27] oder Routino als freien Offline-Routenplaner einsetzen [28].
Mit Geodaten rechnen
Im Python-Universum finden sich eine ganze Reihe von Bibliotheken zum Umgang mit Geodaten, die an öffentlich verfügbare APIs andocken.
So dient das bereits erwähnte Geopy zum Umrechnen zwischen Geokoordinaten und Ortsangaben. Geotiler [3] und Cartopy [4] stellen passende, in Kacheln (engl. “tiles”) unterteilte Kartenausschnitte bereit.
GeoPandas [5] basiert auf Pandas [6] und stellt länderspezifische, statistische Daten bereit, beispielsweise die einzelnen Länder der Erde samt deren geografischer Grenzen und Zugehörigkeit zu einem Kontinent, geschätzter Bevölkerungszahl, ISO-Code und Bruttoinlandsprodukt (engl. GDP).
GeoAlchemy [7], eine Erweiterung zu SQLAlchemy [8] und PostGIS [9], ergänzt das Datenbankmanagementsystem PostgreSQL um Funktionen zum Verarbeiten von Geodaten. Geoviews [10] ermöglicht das Erforschen und Darstellen geografischer, meteorologischer und ozeanografischer Daten, wie sie beispielsweise in der Klimaforschung benötigt werden.
Welche Bibliothek optimal zu dem von Ihrem angepeilten Anwendungsfall passt, hängt davon ab, in welchem Format und welcher Form Ihre Daten bislang vorliegen, was Sie ausrechnen möchten und wie Sie die Ausgabe gestalten wollen – als Daten in einer CSV-Datei oder Datenbank, als statische Datei in Form einer Grafik oder interaktiv für eine Webseite. Als Entscheidungshilfe legen wir Ihnen das PyViz-Tutorial [11] ans Herz, das einen Überblick über die verschiedenen Bibliotheken gibt und sie anhand von Beispielen erläutert.
Luftlinie per Geopy
Geopy dient als Python-Client für verschiedene populäre geobasierte Webdienste (Abbildung 1), wie etwa OpenStreetMap Nominatim [12], Google Geocoding API (V3), Base Adresse Nationale France (BAN France [13]), Pelias [14] oder auch AzureMaps [15] (Microsoft/TomTom).
Im Folgenden zeigen wir, wie Sie mithilfe von Geopy die direkte Gesamtstrecke einer Reiseroute anhand von Ortsnamen ermitteln. Zu den Ortsangaben berechnet das Tool den jeweiligen Breiten- und Längengrad und danach die Distanz zwischen diesen geografischen Punkten. Je detaillierter die Route mit den jeweiligen Ortsangaben vorliegt, desto exakter kalkuliert Geopy die Gesamtstrecke. Dabei ermittelt es die direkte geografische Distanz zwischen den Orten, lässt unterschiedliche Fortbewegungsarten, die verschiedenen Verkehrswege sowie Höhenunterschiede bei der Berechnung jedoch außer Acht.
Praktischerweise bezieht Geopy bei der Berechnung der Distanz die Erdkrümmung mit ein. Es sieht die Erde als Ellipsoid [16] und nicht als Kugel, wobei es derzeit sechs verschiedene Berechnungsmethoden für Abstände zwischen einzelnen geografischen Punkten unterstützt (siehe Tabelle “Berechnungsmethoden”). Als Standardverfahren nutzt Geopy WGS-84 [17], dass auch die Basis des Global Positioning Systems (GPS) darstellt. Bitte beachten Sie, dass die Genauigkeit der errechneten Distanz zwischen zwei Punkten schwankt: Sie hängt von der gewählten Methode und den Orten ab, zwischen denen Sie die Entfernung berechnen.
|
Verfahren |
Beschreibung |
|---|---|
|
Airy (1830) |
Erdmodell nach G. B. Airy (1801-1892) |
|
Clarke (1880) |
Erdmodell nach A. R. Clarke (1828-1914) |
|
GRS-67 |
Geodätisches Referenzsystem 1967 |
|
GRS-80 |
Geodätisches Referenzsystem 1980 |
|
Intl 1924 |
Internationales Modell (1924) |
|
WGS-84 |
World Geodetic System 1984 |
Distanz zwischen Punkten
Nach der Installation von Geopy entweder über die Paketverwaltung der verwendeten Distribution oder via Pip steht Geopy zum Einsatz bereit. Listing 1 zeigt den ersten Schritt, das Ermitteln der Geokoordinaten zu einer Ortsangabe. Nominatim übernimmt dabei die Auswertung.
Listing 1
Geokoordinaten ermitteln
from geopy.geocoders import Nominatim
geolocator = Nominatim(user_agent="step1")
# Koordinaten für Breisach am Rhein
location1 = geolocator.geocode("Breisach am Rhein")
laengengradB = location1.longitude # 7.5806123
breitengradB = location1.latitude # 48.0291415
# Koordinaten für Ihringen am Kaiserstuhl
location2 = geolocator.geocode("Ihringen")
laengengradI = location2.longitude # 7.6483815
breitengradI = location2.latitude # 48.0439812
In Zeile 1 importieren Sie daher aus dem Geopy-Modul die gleichnamige Klasse. Anschließend erzeugen Sie ein passendes Objekt dieser Klasse (Zeile 2), um mit Nominatim zu kommunizieren (siehe auch Kasten “Nominatim und der User Agent”). Dabei müssen Sie zwingend einen (wahlfreien) User-Agent-String angeben, um eine Fehlermeldung zu vermeiden. In den Zeilen 4 und 8 senden Sie eine Anfrage an den Webservice von Nominatim, um zu dem als Parameter angegebenen Ort die entsprechenden Geokoordinaten zu bestimmen.
Nominatim und der User Agent
Nominatim dient in allererster Linie als Engine hinter OpenStreetMap, stellt seine Daten jedoch freundlicherweise über eine API auch der Allgemeinheit zur Verfügung. Dafür erwartet das Projekt jedoch im Gegenzug, dass Benutzer sich an gewisse Konventionen halten, die die Nominatim-Webseite klar aufführt. Vor allem setzt das Projekt voraus, dass Anwendungen sich auf maximal eine Anfrage pro Sekunde beschränken, um Überlasten zu vermeiden. Darüber hinaus muss der Aufruf einen validen HTTP Referer [29] oder User-Agent-String [30] liefern, der die aufrufende Anwendung eindeutig identifiziert. Standard-User-Agents, wie sie etwa HTTP-Bibliotheken setzen, genügen nicht. Für kleine Experimente wie in den hier gezeigten Listings tut es ein wahlfreier String, um eine Fehlermeldung und einen Abbruch des Skripts zu vermeiden. Schreiben Sie jedoch tatsächlich eine Anwendung, die die API nutzt, sollten Sie darin auch einen korrekten User-Agent-String beziehungsweise HTTP Referer verwenden. (jlu)
Anhand der Ortsangabe ermittelt Nominatim in seiner Datenbank die geografischen Daten als Gleitkommazahlen und liefert sie als Attribute zum Rückgabewert des Methodenaufrufs geocode() zurück. Im Attribut longitude finden Sie den Längengrad, in latitude den Breitengrad. Kann Nominatim die Ortsangabe nicht auflösen, weil es den Ort nicht kennt oder Sie ihn zu ungenau angegeben haben, bleibt das Ergebnis leer.
Im zweiten Schritt rechnen Sie die Distanz zwischen zwei geografischen Punkten aus. Hier kommt die Methode distance() zum Einsatz, die zwei Punkte als Aufzählung aus Breitengrad und Längengrad erwartet. Als Rückgabewert liefert sie ein Objekt mit den zwei Attributen km und miles zur Ausgabe in Kilometern und Meilen. Listing 2 zeigt den Aufruf und die Ausgabe der Entfernung in Kilometern zwischen beiden Orten.
Listing 2
Distanz berechnen
# [... Schritt 1 ...]
import geopy.distance
# Breisach am Rhein
location1 = (breitengradB, laengengradB)
# Ihringen am Kaiserstuhl
location2 = (breitengradI, laengengradI)
distanz = geopy.distance.distance(location1, location2)
# Ausgabe: 5.316275267707444
print("Distanz: %f km" % distanz.km)
Reiseroute berechnen
Das Wissen aus diesen beiden Schritten kombinieren Sie jetzt, indem Sie eine ganze Liste von Ortsangaben verarbeiten (Listing 3). Unsere Entdeckertour um den Kaiserstuhl erfolgt im umgekehrten Uhrzeigersinn und umfasst die Orte Breisach, Ihringen, Bötzingen, Eichstetten, Bahlingen, Riegel und Endingen (ab Zeile 4). Sofern erforderlich, haben wir die Ortsangabe noch um den Text “am Kaiserstuhl” ergänzt, um sie für Nominatim eindeutig zu machen.
Listing 3
Reiseroute berechnen (td.py)
from geopy.geocoders import Nominatim
import geopy.distance
geolocator = Nominatim(user_agent="td")
places = [
"Breisach am Rhein",
"Ihringen am Kaiserstuhl",
"Bötzingen",
"Eichstetten am Kaiserstuhl",
"Bahlingen am Kaiserstuhl",
"Riegel am Kaiserstuhl",
"Endingen am Kaiserstuhl"
]
coordinatesStore = [] # Liste mit Koordinaten
for location in places:
coordinates = geolocator.geocode(location)
latitude, longitude = coordinates.latitude, coordinates.longitude
coordinatesStore.append([location, latitude, longitude])
totalDistance = 0
start = 0
end = 1
while end < len(coordinatesStore):
location1 = coordinatesStore[start]
location2 = coordinatesStore[end]
interDistance = geopy.distance.distance(
(location1[1], location1[2]),
(location2[1], location2[2])
)
totalDistance += interDistance.km # Distanz in Kilometern
# totalDistance += interDistance.miles # Distanz in Meilen
start += 1
end += 1
startingPlace = coordinatesStore[0][0]
endingPlace = coordinatesStore[end - 1][0]
print ("Die Distanz zwischen %s und %s beträgt %.2f Kilometer." % (startingPlace, endingPlace, totalDistance))
# print ("Die Distanz zwischen %s und %s beträgt %.2f Meilen." % (startingPlace, endingPlace, totalDistance))
Nun rufen Sie das Python-Skript im Terminal auf. Die Berechnung dauert einen kleinen Moment, die Rückgabe erfolgt dann am Stück. In Zeile 34 begrenzt die Angabe %.2f in der print()-Anweisung die Anzahl der Nachkommastellen auf zwei. Das errechnete Ergebnis sieht wie in Listing 4 gezeigt aus und deckt sich mit dem Wert von Seiten wie etwa Luftlinie.org (Abbildung 2).
Listing 4
td.py ausführen
$ python3 td.py
Die Distanz zwischen Breisach am Rhein und Endingen am Kaiserstuhl beträgt 25.15 Kilometer.
Möchten Sie in Ihren Berechnungen ein anderes Verfahren als WGS-84 benutzen, dann parametrisieren Sie Geopy entsprechend (siehe Listing 5). Das gewünschte Verfahren definieren Sie über den Parameter ellipsoid in der Funktion distance.distance(). Als Wert übergeben Sie den Namen des Verfahrens aus der Tabelle “Berechnungsmethoden”, inklusive Leerzeichen und Klammern.
Listing 5
Berechnung mit GRS-80
interDistance = geopy.distance.distance(location1, location2, ellipsoid='GRS-80'))
Genauigkeit und Optimierungen
Für eine Berechnung mit alltagstauglichen Werten passt das bislang beschriebene Vorgehen; es spielt in der Regel keine Rolle, ob der Weg 20 Meter kürzer oder länger als in der Realität ist. Möchten Sie jedoch das Ergebnis präzisieren, also die Distanz zwischen zwei Ortsangaben noch genauer bestimmen, bedarf es etwas Feinarbeit. Eine Option besteht darin, mehr Datenpunkte zu benutzen. Auf diese Weise erhalten Sie einen genaueren Verlauf der Strecke, auch wenn die Berechnung dann mehr Zeit in Anspruch nimmt. Mehr Daten erzeugen Sie zum Beispiel über einen GPS-Tracker oder ein Smartphone mit einer Tracking-App, die Ihre Position genauer bestimmt und aufzeichnet.
Eine weitere Möglichkeit, die Präzision der Berechnung zu optimieren, liegt darin, die Berechnung nicht auf die Luftlinie zwischen zwei Orten zu beschränken, sondern auf real existierende Wege zu basieren. Hier kommen Online-Router ins Spiel, über deren API Sie auf deren Service zugreifen. Aus dem Überblick zu offenen Online-Routern [18] picken wir als Beispiel Openrouteservice (ORS [19]) heraus. Dieser Dienst erscheint uns am vollständigsten und berücksichtigt in der Berechnung möglicher Routen Fußgänger, Radfahrer, Rollstuhlfahrer sowie Pkw und Lkw.
ORS wird am Heidelberg Institute for Geoinformation Technology (HeiGIT [20]) an der Universität Heidelberg entwickelt. Zu ORS bestehen eine Reihe von APIs, die sich mit Python, R, Javascript oder mit einem Plugin für das grafische Informationssystem QGIS [21] ansprechen lassen. Den Quellcode für die Python-Bibliothek zum Ansprechen der ORS-Routing-APIs finden Sie auf Github [22] und auf PyPI [23], die Dokumentation auf Readthedocs [24]. Ein passendes Jupyter-Notebook finden Sie unter [25], Sie benötigen für den Zugriff jedoch einen API-Key vom HeiGIT.
Listing 6 zeigt den Quellcode, um die bereits besprochene Tour um den Kaiserstuhl von ORS für die Benutzung eines Pkw berechnen zu lassen. Bitte beachten Sie, dass die Angaben aus Längen- und Breitengraden bestehen, die Parameter müssen im Vergleich zu Geopy also in umgekehrter Reihenfolge vorliegen. Für den Aufruf in Zeile 6 benötigen Sie einen API-Key für ORS, den Sie vorher anfordern müssen. Als Ergebnis erhalten Sie eine Tabelle mit den berechneten Entfernungen. ORS errechnet 30,9 Kilometer als Distanz und eine Fahrtdauer von knapp 55 Minuten für die gesamte Strecke. Beide Werte erscheinen realitätsnah. Wie die Strecke um den Kaiserstuhl verläuft, sehen Sie in Abbildung 3.
Listing 6
Distanz mit ORS berechnen
import openrouteservice locations = [] for position in coordinatesStore: place,latitude,longitude = position locations.append((longitude, latitude)) client = openrouteservice.Client(key='geheim') # API-Key distances = openrouteservice.distance_matrix.distance_matrix( client, # Client-Kennung locations, # Streckenpunkte profile='driving-car' # Profil (Pkw) ) print(distances)
Fazit
Wie Sie aus den hier gezeigten Beispielen ersehen, ist das Errechnen einer eigenen Fahrtroute kein Hexenwerk. Mit wenigen Zeilen Python-Code erhalten Sie eine verlässliche Distanz und Fahrtbeschreibung. Damit steht dem Entwickeln einer eigenen Anwendung nichts mehr im Weg. (cla/jlu)
Der Autor
Frank Hofmann arbeitet zumeist von unterwegs, bevorzugt aus Berlin, Genf und Kapstadt, als Entwickler, Trainer und Autor. Er gehört zu den Verfassern des Debian-Paketmanagement-Buchs.
Infos
-
OpenStreetMap: https://openstreetmap.org
-
Geotiler: https://pypi.org/project/geotiler
-
GeoPandas: https://geopandas.org
-
Pandas: https://pandas.pydata.org
-
GeoAlchemy: https://geoalchemy-2.readthedocs.io/en/latest
-
SQLAlchemy: https://www.sqlalchemy.org
-
PostGIS: http://postgis.net
-
Geoviews: https://geoviews.org
-
PyViz-Tutorial: https://pyviz-tutorial.readthedocs.io/de/latest/index.html
-
Nominatim: https://nominatim.org
-
Geoportal Frankreich: https://geo.api.gouv.fr/adresse
-
Pelias: https://github.com/pelias
-
AzureMaps: https://docs.microsoft.com/en-us/azure/azure-maps/
-
“Earth Ellipsoid”: https://en.wikipedia.org/wiki/Earth_ellipsoid
-
Übersicht Online-Router: https://wiki.openstreetmap.org/wiki/Routing/online_routers
-
Openrouteservice: https://maps.openrouteservice.org/
-
Heidelberg Institute for Geoinformation Technology: https://heigit.org
-
QGIS: https://qgis.org
-
Python-API zu Openrouteservice (Github): https://github.com/GIScience/openrouteservice-py
-
Python-API zu Openrouteservice (PyPI): https://pypi.org/project/openrouteservice
-
Python-API zu Openrouteservice (Beschreibung): https://openrouteservice-py.readthedocs.io/en/latest
-
Jupyter-Notebook zu Openrouteservice: https://mybinder.org/v2/gh/GIScience/openrouteservice-py/master?filepath=examples%2Fbasic_example.ipynb
-
Skripting: Frank Hofmann, “Sieben Brücken”, LU 05/2016, S. 62, https://www.linux-community.de/36800
-
Fahrtenbuch: Frank Hofmann, “Aufgezeichnet”, LU 05/2017, S. 34, https://www.linux-community.de/38817
-
Routino: Frank Hofmann, “Pfadfinder”, LU 05/2017, S. 30, https://www.linux-community.de/39057
-
HTTP Referer: https://en.wikipedia.org/wiki/HTTP_referer
-
User Agent: https://de.wikipedia.org/wiki/User_Agent








