nadenau.de

- Website im Umbau -

20*C+B+M+25

GPX-Tracks lesen und konvertieren mit Perl

1. Das gpx-Dateiformat

Das gpx-Dateiformat ist eine Ausprägung von xml, die dazu gemacht ist, Ortsmarkierungen und "Tracks", die ein gps-Logger liefert, zu speichern. Als gps-Logger kann natürlich auch ein Smartphone mit einer entsprechenden App dienen. Zu jedem Ortspunkt eines Tracks werden in der gpx-Datei Längen- und Breitengrad, Höhe über Normalnull und Zeit gespeichert. In der Praxis sieht ein solcher Punkt dann so aus:

<trkpt lat="50.633218288" lon="6.476848125">
  <ele>200.091675</ele>
  <time>2011-10-30T13:21:55Z</time>
</trkpt>

2. Das Problem

Um eine als gpx-Datei (eine fiktive Musterdatei steht hier zum Download bereit - bitte als Muster.gpx abspeichern) gespeicherte Bewegung analysieren zu können, soll sie in ein Format gebracht werden, dass mit jeder beliebigen Tabellenkalkulation eingelesen werden kann; das csv-Format ist hier besonders geeignet. Für die Umwandlung soll ein Perl-Skript sorgen, das leicht an die besonderen Anforderungen des Nutzers angepasst werden kann.

3. Zugriff auf eine gpx-Datei mit XML::Twig1)

Es gibt eine Reihe von Perl-Modulen, die Perl-Skripte in die Lage versetzen, einfach mit xml-, also insbesondere auch mit gpx-Daten umzugehen. Hier ist - letztlich mehr oder minder zufällig - die Entscheidung zugunsten von XML::Twig gefallen. Ein erstes Beispiel zeigt den Zugriff auf eine gpx-Datei mit XML::Twig:

#!/usr/bin/perl -w
# gpx2con.pl
# Aufruf: perl gpx2con.pl Muster.gpx
use strict;
use XML::Twig;
use Date::Parse;
#
my $datei = $ARGV[0];
my $twig_handlers = {'trkpt' => \&behandlung};
my $twig = new XML::Twig(TwigRoots => {trkpt => 1},
                    TwigHandlers => $twig_handlers);
$twig->parsefile($datei);
#
sub behandlung{
  my ($twig, $wert) = @_;
  my $lat = $wert->att('lat');
  print $lat . ";";
  my $lon = $wert->att('lon');
  print $lon . ";";
  my $ele = $wert->first_child('ele')->text();
  print $ele . ";";
  my $isotime = $wert->first_child('time')->text();
  my $time = str2time($isotime);
  print $time . "\n";
}

Das Skript (hier zum Download) gibt Längengrad, Breitengrad, Höhe und Zeit (in Sekunden seit ...) im Terminal (Standardausgabe) aus. Auf den ersten Blick fällt auf, dass Längen- und Breitengrad prinzipiell anders behandelt werden als Höhe und Zeit: Breiten- und Längengrad werden als Attribute (att) des jeweiligen TRKPTs (Trackpoints) behandelt, Höhe und Zeit sind Kinder (first_child) des jeweiligen TRKPTs bzw. GPS-Punktes.

4. Die Rundum-sorglos-Lösung

Das eben dargestellte Skript tut eigentlich schon alles, was notwendig ist, um eine csv-Datei und damit eine Tabellenkalkulationsdatei zu erzeugen. Speichert man die (Standard-)Ausgabe per Ausgabeumleitung oder mittels Copy&Paste als Datei ab, ist man schon am Ziel. Nun ist aber Perl dazu gemacht, Textdateien - und csv-Dateien sind letztlich Texte - komfortabel zu bearbeiten; so liegt es also nahe gleich beim Übertragen der Daten das zu erledigen, was eine Tabellenkalkulation auch nicht besser kann:

  1. Dezimalpunkte durch Dezimalkommas ersetzen, falls mit deutschen Sprachkonventionen gearbeitet werden soll,
  2. weitere Werte berechnen, etwa die Entfernung zwischen zwei aufeinander folgenden Wegpunkten,
  3. eine "Kopfzeile" einfügen, die beschreibt, was in der jeweiligen Spalte steht.

All das leistet das nachfolgende Perl-Skript, das hier zum Download bereit steht:

#!/usr/bin/perl -w
# gpx2csv.pl, Stand 06.01.2013
# ---------------------------------
# Aufruf: perl gpx2csv Muster.gpx #
# ---------------------------------
use strict;
use XML::Twig;
use Date::Parse;
use Math::BigFloat;
use Math::Trig;
#
our $count=0;
our $lat; our $latrad; our $lat0rad;
our $lon; our $lonrad; our $lon0rad;
our $ele;
our $time; our $time0;
our $zeit=0; our $zeit0=0;
our $isotime;
our $vzeit=0;
our $weg=0; 
our $dweg=0;
our $v;
#
open ZIEL, ">", $ARGV[0] . ".csv";
print ZIEL "lat;lon;ele;time;t[s];s[m];t_v[s];v[m/s]" . "\n";
my $datei = $ARGV[0];
#
my $twig_handlers = {'trkpt' => \&behandlung};
my $twig = new XML::Twig(TwigRoots => {trkpt => 1},
                    TwigHandlers => $twig_handlers);
$twig->parsefile($datei);
print $datei . " in csv-Datei konvertiert\n";
#---#
sub behandlung{
  my ($twig, $wert) = @_;
  $lat = $wert->att('lat');
  $lon = $wert->att('lon');
  if ($count>0){
    $lat0rad=$latrad;
    $lon0rad=$lonrad;
    $zeit0=$zeit;
  };
  $latrad=pi*($lat/180);
  $lonrad=pi*($lon/180);
  if ($count>0){
    $dweg=6378388*acos(sin($lat0rad)*sin($latrad)
          +cos($lat0rad)*cos($latrad)*cos($lonrad-$lon0rad));
    $weg=$weg+$dweg
  }  
  $_ = $lat;   s/\./,/g;
  print ".";   
  print ZIEL $_ . ";";
  $_ = $lon;   s/\./,/g;
  print "."; 
  print ZIEL $_ . ";";
  $ele = $wert->first_child('ele')->text();
  $_ = $ele;   s/\./,/g;
  print ".";   
  print ZIEL $_ . ";";
  $isotime = $wert->first_child('time')->text();
  $time = str2time($isotime);
  if ($count==0){$time0 = $time};
  $zeit=$time-$time0;
  $_ = $time;   s/\./,/g;
  print ".";
  print ZIEL $_ . ";";
  $_ = $zeit;   s/\./,/g;
  print ".";
  print ZIEL $_ . ";";
  $_ = $weg;   s/\./,/g;
  print ".";
  print ZIEL $_ . ";";
  if (($count>0) && ($zeit != $zeit0)){
    $vzeit=($zeit+$zeit0)/2;
    $_ = $vzeit; s/\./,/g;
    print ZIEL $_ . ";";
    $v=$dweg/($zeit-$zeit0);
    $_ = $v;   s/\./,/g;
    print "..\n";
    print ZIEL $_ . "\n"
    }
  else {
    print "..\n";
    print ZIEL ";\n"
    };
  if ($count==0){$count++};
}

Das Skript (hier zum Download) liefert folgende Werte:

  1. den Breitengrad (lat),
  2. den Längengrad (lon),
  3. die Höhe über Normalnull (ele),
  4. die Zeit (time) in Sekunden seit dem 1. Januar 1970 (Unix-Zeit),
  5. die seit dem ersten Trackpoint vergangene Zeit (t[s]),
  6. der seit dem ersten Trackpoint (=Ausgangspunkt) zurückgelegte Weg (s[m]) als Summe der einzelnen Wegstücke (und nicht als Entfernung vom Ausgangspunkt),
  7. die seit dem ersten Trackpoint vergangene Zeit (t_v[s]) - und zwar für den Zeitpunkt, dem die nachfolgend aufgeführte Geschwindigkeit v "in etwa" zuzuordnen ist,
  8. die Durchschnittsgeschwindigkeit (v[m/s]) für das Teilstück zwischen dem vorigen und dem aktuellen Trackpont.

Das Ergebnis der Umwandlung kann problemlos mit einer Tabellenkalkulation, etwa mit dem Calc-Modul von LibreOffice oder OpenOffice, weiterbearbeitet werden. (Das Ergebnis kann z.B. so aussehen, wie es die hier hinterlegte pdf-Datei zeigt. Dass das erstellte Diagramm sich so sauber darstellt, liegt daran, dass die gpx-Datei fiktiv, also am Schreibtisch zusammengebastelt ist.)

5. Besondere Anmerkungen

Alle Angaben erfolgen ohne Gewähr.

Die vorliegende Darstellung fasst im Wesentlichen nur zusammen, was in verschiedenen Internetquellen (siehe Literaturliste) zum Thema zu finden ist; den Autoren verdanke ich die Vorschläge zur Lösung meines Problems - mögliche Fehler und Ungenauigkeiten habe ich verursacht.

6. Literaturhinweise

Kompg, Martin: Entfernungsberechnung, http://www.kompf.de/gps/distcalc.html (Stand 24.10.2012).
Rodriguez, Michel: Processing XML efficiently with Perl and XML::Twig, http://www.xmltwig.org/xmltwig/tutorial/ (Stand 24.10.2012). Leider scheint dieser Link inzwischen nicht mehr aktiv zu sein.
Schilli, Michael: Datenfischer. XML-Parser für Perl im Vergleich. Erschienen im Linux-Magazin 2005/08. Online abrufbar unter: http://www.linux-magazin.de/Heft-Abo/Ausgaben/2005/08/Datenfischer2 (Stand 24.10.2012). Leider scheint auch dieser Link inzwischen nicht mehr aktiv zu sein.
Schilli, Michael: Hinterm Horizont. GPS-Daten mit Perl auswerten und online in Karten einblenden. Erschienen im Linux-Magazin 2006/07. Online abrufbar unter: http://www.linux-magazin.de/Heft-Abo/Ausgaben/2006/07/Hinterm-Horizont (Stand 24.10.2012).