GPS und Java: NMEA-Daten auswerten (1)

Bild: GPS Recorder von andyp_uk, cc-by-nc

Irgendwann kommt die Zeit im Leben jedes Menschen, an der er GPS-Daten automatisiert verarbeiten will. Vertraut mir, was das angeht. Das passiert jedem.

Unter C++ ist sowas sicher viel simpler und toller. Ich hatte aber die fixe Idee, das unter Java zu machen, weil das ja gewohntes und damit vermeintlich sicheres Terrain fuer mich sei. Das lassen wir jetzt mal so dahingestellt.

Schritt 1: Daten holen

Die Idee, hier java.io verwenden zu wollen, kann man sich ziemlich in die Haare schmieren. Gleich gnu.io.rxtx zu verwenden, spart Aerger. Weil wir NMEA-Daten ueber die serielle Schnittstelle haben wollen, setzen wir die Parameter entsprechend: setSerialPortParams(4800, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);

Hier kommt jetzt alles moegliche NMEA-Zeug heraus, das man theoretisch direkt parsen koennte. Alternativ kann man mit der Java NMEA API gezielt nach den interessanten NMEA-Saetzen lauschen. Kurz deren Doku ueberfliegen lohnt sich auf jeden Fall, da sich auch ein Beispiel findet, wie man ein GPS-Geraet ueber die serielle Schnittstelle einliest.

PS: Viele GPS-Maeuse kommen heute mit einem integrierten Serial-to-USB-Konverter daher, damit man sie per USB anschliessen kann. Sollte RxTx unter Linux zwar einen USB-Port (a la /dev/ttyUSB0) erkennen, darauf aber nicht zugreifen koennen („No such port“), helfen unter Umstaenden chown unsinn! chmod 666 /dev/ttyUSB0 und chgrp tty /dev/ttyUSB0 weiter.

Schritt 2: Was brauchen wir da eigentlich?

Wenn man sich mal direkt ansieht, was da ueber die Konsole rauscht, dann ist das ziemlich viel. Beispiel:

$GPRMC,101836.000,A,4825.1856,N,00956.8032,E,0.23,226.38,230710,,*07
$GPVTG,226.38,T,,M,0.23,N,0.4,K*68
$GPGGA,101837.000,4825.1857,N,00956.8023,E,1,04,1.4,567.4,M,48.0,M,,0000*5E
$GPGSA,A,3,17,05,08,18,,,,,,,,,8.6,1.4,8.5*36
$GPRMC,101837.000,A,4825.1857,N,00956.8023,E,0.57,292.18,230710,,*09

Das sieht auf den ersten Blick wenig einladend aus, im Endeffekt aber ziemlich simpel. „$GP“ bedeutet, dass es sich um GPS-Daten handelt. „RMC“, „VTG“, „GGA“, „GSA“ und Co. sind verschiedene Satztypen, die hierzu gehoeren. RMC muss jeder GPS-Empfaenger koennen, idealerweise nehmen wir uns aber die GGA-Saetze her, weil die in einem Satz alle fuer uns wichtigen Informationen bereitstellen.

Durch Kommas getrennt finden sich hier:

  • Uhrzeit in UTC
  • Breite und Bezeichner N oder S fuer die Breite
  • Laenge und Bezeichner W oder E fuer die Laenge
  • Qualität der Messung (0 == ungueltig, 1 == GPS, 2 == DGPS)
  • Anzahl der gemessenen Satelliten
  • Horizontal Dilution Of Precision (mehr dazu im naechsten Post)
  • Hoehe ueber Meer mit Einheit
  • Hoehe ueber Geoid minus Hoehe ueber Ellipsoid, mit Einheit

…gefolgt zum Schluss von einer Pruefsumme, die ich aber nie irgendwie beruecksichtigt habe. Bis auf das aktuelle Datum ist hier also alles vorhanden, was man braucht, es muss nur noch die Zeichenkette aufgetrennt und die WGS84-Koordinate in ein Format umgerechnet werden, mit dem man weiterarbeiten kann.

Schritt 3: Koordinaten umrechnen

Umrechnen? Ja. In den NMEA-Saetzen steht naemlich folgendes:

4825.1857,N,00956.8023,E

Das sind also 48° 25.1857′ noerdlicher Breite und 9° 56.8023′ oestlicher Laenge. In Minuten wollen wir aber nicht rechnen, wir wollen zwei glatte Dezimalbrueche — in diesem Fall etwa 48.4197616 und 9.946705. Dazu muessen wir aber erst einmal unsere NMEA-Saetze in ihre Bestandteile zerlegen.

In einigen Tutorials, die ich gefunden hatte, war hier immer vom StringTokenizer die Rede, um die Zeichenkette zu zerlegen. Das ist aber nicht immer eine gute Idee, da der NMEA-Output direkt nach dem GPS-Start oder beim Fix-Verlust (z.B. in einem Tunnel) so aussehen kann:

$GPGGA,103927.819,,,,,0,00,,,M,0.0,M,,0000*58

Schoener geht das alles mit der split()-Methode, um die Zeichenkette an den Kommas zu trennen:

String sentence_parts[] = e.getContent().split(",");

Nun ist klar definiert, an welcher Stelle was zu erwarten ist. Sollten wir keinen Fix haben, koennen wir das ganz einfach herausfinden:

if (sentence_parts[6].equals("0")) {
    System.err.println("No fix!");
    // TODO: Handle this further, if needed
 }

Gehen wir aber mal davon aus, dass wir einen Fix haben. Dann hilft uns der Algorithmus aus dem oben genannten Tutorial weiter, mit dem wir die Minutenangabe der NMEA-Koordinate in den Dezimalbruch nach dem Komma der Gradangabe umwandeln koennen:

float lat_val = convertLat(sentence_parts[2], sentence_parts[3]);
float lon_val = convertLon(sentence_parts[4], sentence_parts[5]);
public float convertLat (String raw_latitude, String lat_direction) {

  String lat_deg = raw_latitude.substring(0, 2);
  String lat_min1 = raw_latitude.substring(2, 4);
  String lat_min2 = raw_latitude.substring(5);
  String lat_min3 = "0." + lat_min1 + lat_min2;
  float lat_dec = Float.parseFloat(lat_min3)/.6f;
  float lat_val = Float.parseFloat(lat_deg) + lat_dec;

 // Direction of latitude. North is positive, south negative
  if (lat_direction.equals("N")) {
    // no correction needed
  } else {
    lat_val = lat_val * -1;
  }
 return lat_val;
 }
public float convertLon(String raw_longitude, String lon_direction) {
 // Conversion of longitude to floating point values

  String lon_deg = raw_longitude.substring(0, 3);
  String lon_min1 = raw_longitude.substring(3, 5);
  String lon_min2 = raw_longitude.substring(6);
  String lon_min3 = "0." + lon_min1 + lon_min2;
  float lon_dec = Float.parseFloat(lon_min3)/.6f;
  float lon_val = Float.parseFloat(lon_deg) + lon_dec;

  //direction of longitude, east is positive
  if (lon_direction.equals("E")) {
    // No correction needed
  } else {
    lon_val = lon_val * -1;
  }
 return lon_val;
 }

Jetzt haben wir unseren Standort. Naja, eigentlich nicht, sondern nur eine Annaeherung unserer Position. Hier kommt noch eine bestimmte Abweichung ins Spiel, die in Metern auszudruecken gar nicht so leicht ist. Wie man damit umgehen kann und wie das generell mit Genauigkeit, Praezision und Verfahren wie DGPS und SBAS geht, schreibe ich dann demnaechst mal.

Nachtrag: Waehrend ich das hier schreibe, faellt mir auf, dass ich bei den Vorzeichen automatisch das vom UTM-Koordinatensystem her bekannte Nordwert-Ostwert-Paradigma verwendet habe — bin mir aber aktuell nicht sicher, ob bei WGS84-Koordinaten nicht doch der Westwert ein positives Vorzeichen hat.

Nach-Nachtrag: Gpsvisualizer hat meine Annahme bestaetigt. Puh 🙂

8 Gedanken zu „GPS und Java: NMEA-Daten auswerten (1)

  1. Pingback: Tweets that mention GPS und Java: NMEA-Daten auswerten (1) | stk -- Topsy.com

  2. Peggy

    Hey,

    sage mal, würdest du mir den gesamten Code von deiner Variante zur Verfügung stellen?! Ich studiere Telematik und mache momentan ein Praktikum bei der Polizei, da soll ich auch NMEA-Daten einer GPS-Maus auswerten.

    Viele Grüße!

    Antworten
  3. Klaus Pollmann

    Hallo,

    an dem Code wäre ich innerhalb meines Projektes ebenfalls sehr interessiert. Meine Email-Adresse ist hinterlegt.

    Beste Grüße

    Antworten
  4. stk Beitragsautor

    Weil sich die Anfragen wiederholen: Im Wesentlichen steht in der Doku zur Java NMEA API alles, was man braucht, um an die Daten zu kommen und sie weiterzuverarbeiten. Ich hatte noch mit einigen Ideen herumgespielt die man aber allesamt nicht braucht (und fuer’s Verstaendnis vermutlich auch nicht so arg foerderlich sind) — die Codebeispiele aus der NMEA API sollten deshalb erster Anlaufpunkt sein.

    Antworten
  5. Manuel

    Echt eine klasse Beschreibung.
    Was mir nur noch unklar ist, ist bei der Verarbeitung der Positionsdaten.
    Hier teilst du beispielsweiße die Variable „lat_min3“ durch 0.6.
    Welchen Sinn hat diese Division?

    Antworten
    1. stk Beitragsautor

      Es gibt drei Moeglichkeiten, Laenge und Breite darzustellen:
      * Grad, Minute, Sekunde (dd° mm‘ ss“), 60 Sekunden sind eine Minute, 60 Minuten sind eine Sekunde
      * Grad, Dezimalminute (dd° mm.mmm‘), 60 Sekunden sind eine Minute, Fraktionen einer Minute werden als Dezimalbruch an die Minute gehaengt
      * Dezimalgrad (dd.dddddd°), Fraktionen eines Grads werden als Dezimalbruch angehaengt.

      Wir bekommen aus dem NMEA-Satz Grad und Dezimalminuten, fuer die Berechnung wollen wir aber intern nur Dezimalgrad verwenden. Das muss man nicht, bietet sich aber an, wenn man nur eine Variable verwenden und das sauber haben mag. Wir muessen also die Winkelminute in eine Dezimalfraktion eines Grads umrechnen. Und das schaffen wir mit der Division durch 0.6.

      Hope this helps 😉

      Antworten

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert