AA_logbook_ginasanders_123rf_18055187.jpg

© Gina Sanders, 123RF

Mit Python und OpenLayers eigene Reisen visualisieren

Aufgezeichnet

Welche Strecken haben Sie im Laufe des Jahres zurückgelegt? Ein paar Python-Bibliotheken sowie OpenLayers verhelfen Ihnen zu einer eigenen OpenStreetMap-Karte.

Der Autor zählt sich zur Klasse der Vielreisenden und digitalen Nomaden [1] beziehungsweise Location Independents [2]. Nachdem bereits 2016 die 100 000-Kilometer-Marke mit Trips durch Europa und Afrika geknackt war, zeichnet sich für 2017 ein ähnliches Bild ab, speziell angesichts der im August 2017 stattfindenden Entwicklerkonferenz DebConf [3] im kanadischen Montreal.

Die Reisen erfolgen entweder bedingt durch Aufträge oder persönliche Interessen. Dabei spielt stets die langjährige Erfahrung als Selbstständiger eine Rolle, um an einem Ort mit einer besonderen Atmosphäre dessen Potenzial voll auszuschöpfen und produktiv tätig zu sein.

Nicht zu vergessen ist die Freude am Kennenlernen und Erleben einer Region. Über das letzte Jahrzehnt hinweg haben sich als bislang bevorzugte Ballungsräume neben der Großstadt Berlin, der Region Bodensee, dem Französisch-Schweizer Jura beziehungsweise der Romandie (Franche Comté/Jura/Vaud) mit den Oberzentren Besançon, Neuchâtel, Genf und Lausanne sowie das Western Cape mit dem Schmelztiegel Cape Town (Kapstadt) herauskristallisiert.

Als Verkehrsmittel kamen in jeweils unterschiedlicher Kombination und Verfügbarkeit Fern-, Stadt- und Nachtbus, Straßenbahn, U- und S-Bahn, Zug, Flugzeug und Schiff beziehungsweise Fähre zum Einsatz. Je nach konkreter Route und Anlass, Motivation, zu überbrückender Distanz, Erfordernis zur Termintreue, Mitreisenden auf der gleichen Strecke, Region, Jahreszeit und Wetterlage variierte diese, gegebenenfalls erfolgte die Reise alternativ mit dem Auto oder zu Fuß [4]. Zweiräder blieben aus persönlicher Abneigung eher ungenutzt.

Meist zum Ende eines Jahres wächst dann die Neugierde, welche Strecken (siehe Kasten "Streckenmessung") im Laufe der vergangenen zwölf Monate anfielen. Die aufgezeichneten Daten ermöglichen dabei ein genaues Auswerten, so etwa die gesamten Kilometer pro Zeitraum (Jahr, Quartal, Monat, Woche), sowie die längste und kürzeste Strecke oder aber Frequentierung der Orte. Das geschah in der Vergangenheit schon mit einfachen Mitteln (siehe Kasten "Verwandte Beiträge").

Mit einer überschaubaren Anzahl von Programmzeilen gelang es, die erfassten Strecken statistisch auszuwerten. Zum Einsatz kam dabei die Programmiersprache Python und die auf das Auswerten von Daten spezialisierten Bibliotheken NumPy [5] und Pandas [6]. Mittels Matplotlib [7] war es möglich, die Daten grafisch aufzubereiten. Eine Ansicht auf einer Karte von OpenStreetMap gelang mithilfe der in Javascript entwickelten OpenLayers-Bibliothek [8].

Streckenmessung

Um die direkten Entfernungen zwischen zwei Orten zu erhalten, hilft unter anderem Luftlinie.org [25] weiter (Abbildung 1): Mithilfe der Webseite berechnen Sie neben der Luftlinie zwischen zwei Orten eine mögliche Route über Land unter Einsatz der Maps-API von Google.

Abbildung 1: Die Webseite Luftlinie.org ermöglicht es, die Strecke zwischen zwei Orten auf verschiedenen Wegen zu berechnen, hier im Beispiel zwischen Cape Town und Genf.

Verwandte Beiträge

Dieser Beitrag schließt inhaltlich an "Sieben Brücken: Fahrtenbuch mit Bordmitteln auswerten" aus LU 05/2016 [26] an. Auf diesen hin gab es bereits vielfältige Rückmeldungen, die von Aha-Effekt bis hin zu Tipps für Verbesserungen reichten. An dieser Stelle vielen Dank für die Anregungen. Sie inspirierten zu weiteren Lösungen sowie zum Vortragsduett mit Harald König [27] auf dem Linux-Day Dornbirn (AT) im November 2016 [28].

Der Beitrag "Punktgenau: Python-Bibliotheken zur Erstellung von Grafiken" aus LU 11/2016 [29] legte die Grundlagen für grafische Auswertungen. Das darin vermittelte Wissen nutzen wir hier für eine Anwendung in der Praxis.

Grundlagen und Datenstrukturen

Die erfassten Strecken bedürfen zunächst einer Struktur. Analog zur bisherigen Herangehensweise kommt dabei der Einfachheit halber eine Tabelle zum Einsatz, die in einer Textdatei liegt. Darin trennen Tabulatoren die einzelnen Felder voneinander (Listing 1). Möglich wären statt der TSV-Variante auch eine CSV-Datei beziehungsweise eine SQL- oder XML-Datenbank.

Als Felder für vielfältige Auswertungen speichert die Tabelle die Abfahrt und Ankunft (als Zeitstempel/Zeitwert), die Strecke, die Distanz in Kilometern, die Kosten für die Fahrt und das gewählte Verkehrsmittel. Weitere Felder wären bei diesem Format problemlos möglich.

Listing 1

Abfahrt    Ankunft   Von        Bis     Distanz Verkehrsmittel  Kosten
2017-01-01 10:00:00     2017-01-01 10:30:00     Berlin  Strausberg      30      S-Bahn  5.00
2017-01-01 10:55:00     2017-01-01 11:25:00     Strausberg      Friedrichsfelde 25      S-Bahn  4.00
2017-02-01 10:00:00     2017-02-01 11:25:00     Brandenburg (Havel)     Berlin  85      DB      5.20

Das Format der Zeitangaben setzt sich aus dem Datum und der Uhrzeit zusammen [9]. Das hier genutzte Schema besteht aus dem Jahr (vier Ziffern), gefolgt von Monat und Tag in Ziffern mit gegebenenfalls führender Null. Als Trennzeichen fungiert ein Bindestrich. Die Zeitangabe besteht aus Stunde, Minute und Sekunde, jeweils durch einen Doppelpunkt voneinander getrennt.

Das entspricht dem Formatstring %Y-%m-%d %H:%M:%S und vereinfacht später das Auslesen und Verarbeiten mit den Datums- und Zeitfunktionen von Python [10], Perl [11] oder dem Unix-Kommando Date [12]. Eine Auswahl weiterer Formatstrings entnehmen Sie der Tabelle "Mit Format".

Die Reihenfolge der erfassten Strecken in den Datensätzen spielt keine Rolle: Auch ohne vorherige Sortierung nach dem Zeitpunkt liefert die Software später das korrekte Ergebnis. Den Aufwand beim Sortieren übernehmen die Methoden aus den Bibliotheken.

Mit Format

Komponente Bedeutung Format
%A Wochentag Name in lokaler Übersetzung
%d Tag zwei Ziffern, führende Null
%H Stunde (0-23) zwei Ziffern, führende Null
%I Stunde (0-12) zwei Ziffern, führende Null
%m Monat zwei Ziffern, führende Null
%M Minute zwei Ziffern, führende Null
%S Sekunde zwei Ziffern, führende Null
%W Kalenderwoche zwei Ziffern, führende Null
%Y Jahr vier Ziffern

Auswertung und Statistik

Das Python-Skript zum Auswerten umfasst rund 80 Zeilen. Die Logik kommt dabei aus den Python-Modulen fileinput [13], re [14], datetime [10] und numpy [5]. Diese lädt das Skript, nach dem der Interpreter feststeht (Listing 2 Zeile 1), in den Zeilen 3 bis 6.

Das Modul fileinput wertet die Eingabeparameter aus, re dient zum Auswerten von regulären Ausdrücken, und datetime offeriert Funktionen für Daten und Zeiten. Das Modul numpy liefert dagegen die Methoden zum effektiven Auswerten von Datenstrukturen und multidimensionalen Arrays.

Listing 2

#!/usr/bin/python
import fileinput
import re
from datetime import datetime
import numpy as np
init = True
# preparing the data
for line in fileinput.input():
  line = re.sub('\n', '', line)
  columns = re.split('\t+', line)
  if init:
    data = np.array([columns])
    init = False
  else:
    columns[0] = datetime.strptime(columns[0], '%Y-%m-%d %H:%M:%S')
    columns[1] = datetime.strptime(columns[1], '%Y-%m-%d %H:%M:%S')
    data2 = np.array([columns])
    data = np.vstack((data, data2))
monthlyRanges = [
  ['Ganzes Jahr', '2017-01-01 00:00:00', '2017-12-31 23:59:59'],
  ['Januar',      '2017-01-01 00:00:00', '2017-01-31 23:59:59'],
  ['Februar',     '2017-02-01 00:00:00', '2017-02-28 23:59:59'],
  ['März',        '2017-03-01 00:00:00', '2017-03-31 23:59:59'],
  ['April',       '2017-04-01 00:00:00', '2017-04-30 23:59:59'],
  ['Mai',         '2017-05-01 00:00:00', '2017-05-31 23:59:59'],
  ['Juni',        '2017-06-01 00:00:00', '2017-06-30 23:59:59'],
  ['Juli',        '2017-07-01 00:00:00', '2017-07-31 23:59:59'],
  ['August',      '2017-08-01 00:00:00', '2017-08-31 23:59:59'],
  ['September',   '2017-09-01 00:00:00', '2017-09-30 23:59:59'],
  ['Oktober',     '2017-10-01 00:00:00', '2017-10-31 23:59:59'],
  ['November',    '2017-11-01 00:00:00', '2017-11-30 23:59:59'],
  ['Dezember',    '2017-12-01 00:00:00', '2017-12-31 23:59:59']
]
for currentMonth in monthlyRanges:
  month, dateFrom, dateTo = currentMonth
  dateFromI = datetime.strptime(dateFrom, '%Y-%m-%d %H:%M:%S')
  dateToI = datetime.strptime(dateTo, '%Y-%m-%d %H:%M:%S')
  print (" ")
  print ("----------------------------------------------------")
  print (month, dateFromI, dateToI)
  print ("----------------------------------------------------")
  fromColumn = np.array(data[1:,0:1])
  toColumn = np.array(data[1:,1:2])
  mask = (fromColumn >= dateFromI) & (toColumn < dateToI)
  if mask.any():
    # convert mask into list
    newFilter = mask.ravel()
    stripe = np.compress(newFilter, data[1:], axis=0)
    # calculate the total travelling distance
    # - select the 5th column except the 1st row
    # - convert strings into 32bit integer
    distanceColumn = np.array(stripe[:,4], dtype=np.int32)
    # count the distances
    total = np.sum(distanceColumn)
    print ("Gesamt          : %i km" % (total))
    # count number of travels
    number = np.size(distanceColumn)
    print ("Anzahl Fahrten  : %i" % (number))
    # find shortest travel
    shortest = np.argmin(distanceColumn)
    shortestFrom = stripe[shortest][2]
    shortestTo = stripe[shortest][3]
    shortestDistance = stripe[shortest][4]
    print ("Kürzeste Strecke: %s nach %s mit %s km" % (shortestFrom, shortestTo, shortestDistance))
    # find longest travel
    longest = np.argmax(distanceColumn)
    longestFrom = stripe[longest][2]
    longestTo = stripe[longest][3]
    longestDistance = stripe[longest][4]
    print ("Längste Strecke : %s nach %s mit %s km" % (longestFrom, longestTo, longestDistance))

In den Zeilen 11 bis 21 liest das Skript die Eingabe zeilenweise ein und überführt sie in die interne Datenstruktur. Die Methode fileinput.input() verarbeitet dabei zunächst die Parameter. Rufen Sie das Python-Skript ohne Datei als Parameter auf, liest es stattdessen von der Standardeingabe.

Mithilfe der Methode sub() aus dem Modul re entfernt es danach in der Zeile 15 das Zeilenendezeichen \n – genauer: es ersetzt dieses durch nichts. Mittels split() trennt es anschließend die gelesene Zeile in einzelne Spalten auf. Als Trennzeichen fungiert der Tabulator, der über den regulären Ausdruck '\t+' [15] festgelegt ist und daher einmal oder mehrfach auftreten darf. Die Variable spalten enthält das Ergebnis und besteht aus einer Liste mit den jeweiligen Inhalten.

Die Zeilen 14 bis 21 befüllen die Datenstruktur. Beim Einlesen der ersten Zeile der Eingabe – der Header-Zeile aus der zuvor gelesenen Datei – legt das Skript die Datenstruktur data an. Als Indikator dafür dient die Variable init, die initial auf True steht. Nach dem Verarbeiten der Kopfzeile ändert das Skript das zu False. Zum Befüllen von data kommt die Methode array() aus dem NumPy-Modul zum Einsatz.

Anschließend wandelt das Skript jeweils die erste und zweite Spalte der aktuellen Zeile in ein Datetime-Objekt um. Dabei hilft die Methode strptime(), die den Zeitstempel gemäß des Formatstrings in einzelne Werte zerlegt ("string parse time"). Danach fügt es die Spalten zum bestehenden Feld data. Die Methode vstack() steht hier für das vertikale Anfügen der neuen Struktur an das untere Ende von data.

In den Zeilen 23 bis 37 sind Zeitabschnitte als Liste hinterlegt. Das umfasst das ganze Jahr sowie die einzelnen Monate, Letztere der Verständlichkeit halber fest integriert. Sie ließen sich aber auch ad hoc berechnen. Bei den Einträgen kommt dasselbe Format zum Einsatz wie in der Datei.

Die in Zeile 39 beginnende For-Schleife iteriert danach über jeden Eintrag aus der Liste der Zeitabschnitte, um die Daten für die jeweiligen Monate auszuwerten. Zeile 40 enthält eine Kurzschreibweise und weist jeder der drei Variablen den entsprechenden Wert aus der entsprechenden Spalte des Zeitabschnitts zu.

In den Zeilen 41 bis 46 erfolgt zunächst eine Interpretation der Zeichenkette als Zeitstempel. Danach schließt sich eine Ausgabe einer Zwischenüberschrift mit den Angaben für den Zeitabschnitt an.

In den Zeilen 47 und 48 entnimmt das Skript Teilmengen aus data. Die Schreibweise data[1:,0:1] extrahiert die erste Spalte (mit dem Datum für die Abfahrt) ab der ersten Zeile. Mit data[1:,1:2] fischt es dann die zweite Spalte mit der Ankunftszeit heraus.

Zeile 49 erzeugt eine Liste von Wahrheitswerten aus True und False. True heißt, dass der überprüfte Zeitstempel der Spalte im gerade betrachteten Zeitintervall liegt; False besagt, er liegt außerhalb. Die Länge der Liste entspricht der Anzahl von Daten. Weil die Datentypen der Variablen in den extrahierten Spalten mit den Grenzen des Intervalls identisch sind, führt die verwendete Schreibweise des Vergleichs zum Erfolg.

In Zeile 51 wertet die Methode any() aus, ob es in der Liste mit den Wahrheitswerten überhaupt Einträge mit True gibt. Falls nicht, gibt es nichts zu tun. Dann überspringt das Skript die restlichen Schritte und untersucht das nächste Intervall. Gab es jedoch einen Treffer, ermittelt es nachfolgend die kürzeste beziehungsweise längste Strecke im aktuellen Intervall.

In Zeile 53 wandelt die Methode ravel() die Maske in eine Liste um, die sich dazu eignet, sie mit der Methode compress() zu verarbeiten. Mit deren Hilfe reduziert das Skript in Zeile 54 das Datenfeld data auf jene Elemente, die dem Zeitfenster entsprechen, sprich: in Zeile 49 zu True geführt haben. Als Filter fungiert hier die Liste der vorher generierten Wahrheitswerte. Die Schreibweise data[1:] betrachtet dabei alle Zeiträume außer dem ersten Eintrag, da an dieser Stelle das gesamte Jahr keine Rolle spielt.

Zeile 59 extrahiert die Spalte, die den Wert für die Distanz enthält. Die Schreibweise stripe[:,4] entnimmt aus allen Zeilen die fünfte Spalte (da 0 die erste Spalte repräsentiert). Die Angabe des Datentyps wandelt im gleichen Schritt alle Werte in Integer um. Das ermöglicht das Aufsummieren der Spalte in Zeile 62 mittels sum() und somit das Berechnen der Gesamtsumme in einem einzigen Schritt.

In Zeile 66 hilft die Methode size(), die Anzahl der Fahrten zu ermitteln. size() zählt die Länge einer Liste und entspricht hier der Anzahl der zurückgelegten Fahrten.

Um die kürzeste Strecke zu ermitteln, kommt in Zeile 70 die NumPy-Methode argmin() zum Einsatz. Auf die Spalte mit den Distanzen angewendet, liefert sie den Index des Elements mit dem kleinsten Wert und somit die kürzeste Strecke. Analog fungiert argmax() in Zeile 77 für die längste Strecke. Am Ende erfolgt die Ausgabe der kürzesten und längsten Strecke im Intervall. Listing 3 zeigt die Auswertung für den Juni 2016.

Listing 3

$ cat fahrten-2016-detailliert.txt | python3 distanz.py
...
----------------------------------------------------
Juni 2016-06-01 00:00:00 2016-06-30 23:59:59
----------------------------------------------------
Gesamt          : 1150 km
Anzahl Fahrten  : 11
Kürzeste Strecke: Berlin nach Potsdam mit 30 km
Längste Strecke : Zürich nach Berlin mit 850 km
[...]

Diesen Artikel als PDF kaufen

Express-Kauf als PDF

Umfang: 9 Heftseiten

Preis € 0,99
(inkl. 19% MwSt.)

LinuxCommunity kaufen

Einzelne Ausgabe
 
Abonnements
 
TABLET & SMARTPHONE APPS
Bald erhältlich
Get it on Google Play

Deutschland

Ähnliche Artikel

Kommentare

Infos zur Publikation

LU 05/2018: GEODATEN

Digitale Ausgabe: Preis € 5,95
(inkl. 19% MwSt.)

LinuxUser erscheint monatlich und kostet 5,95 Euro (mit DVD 8,50 Euro). Weitere Infos zum Heft finden Sie auf der Homepage.

Das Jahresabo kostet ab 86,70 Euro. Details dazu finden Sie im Computec-Shop. Im Probeabo erhalten Sie zudem drei Ausgaben zum reduzierten Preis.

Bei Google Play finden Sie digitale Ausgaben für Tablet & Smartphone.

HINWEIS ZU PAYPAL: Die Zahlung ist ohne eigenes Paypal-Konto ganz einfach per Kreditkarte oder Lastschrift möglich!

Stellenmarkt

Aktuelle Fragen

added to access control list
Ingrid Kroll, 27.03.2018 07:59, 10 Antworten
Hallo allerseits, bin einfache Nutzerin und absolut Linux-unwissend............ Beim ganz norm...
Passwortsicherheit
Joe Cole, 15.03.2018 15:15, 2 Antworten
Ich bin derzeit selbständig und meine Existenz hängt am meinem Unternehmen. Wahrscheinlich verfol...
Brother drucker einrichten.
Achim Zerrer, 13.03.2018 11:26, 1 Antworten
Da mein Rechner abgestürzt war, musste ich das Betriebssystem neu einrichten. Jetzt hänge ich wi...
Internet abschalten
Karl-Heinz Hauser, 20.02.2018 20:10, 2 Antworten
In der Symbolleiste kann man das Kabelnetzwerk ein und ausschalten. Wie sicher ist die Abschaltu...
JQuery-Script läuft nicht mit Linux-Browsern
Stefan Jahn, 16.02.2018 12:49, 2 Antworten
Hallo zusammen, ...folgender goldener Code (ein jQuery-Script als Ergebnis verschiedener Exper...