Parsen von GPS-Empfänger-Ausgang über regulären Ausdruck in Python
Frage
Ich habe einen Freund, der seinen Master-Abschluss in Luft- und Raumfahrttechnik ist Finishing. Für sein letztes Projekt ist er auf einem kleinen Team die Aufgabe, ein Programm zu schreiben für die Verfolgung von Wetterballons, Raketen und Satelliten. Das Programm empfängt eine Eingabe von einem GPS-Gerät, tut Berechnungen mit den Daten und verwendet die Ergebnisse dieser Berechnungen eine Reihe von Motoren, die eine Richtungskommunikationsantenne entwickelt, um die Steuerung zu orientieren, so dass der Ballon, Rakete oder Satellit bleibt immer im Mittelpunkt steht.
Obwohl ein wenig von einer (ewigen) Anfängern mir, ich mehr Programmiererfahrung habe, als mein Freund. Also, wenn er mich um Rat gefragt, ich überzeugte ihn das Programm in Python zu schreiben, meine Sprache der Wahl.
An diesem Punkt in dem Projekt arbeiten wir an dem Code, der die Eingabe von dem GPS-Gerät analysiert. Hier ist ein Beispiel-Eingang, mit dem Daten, die wir in fett extrahieren müssen:
$ GPRMC, 092204.999, 4250.5589, S, 14718.5084, E , 1,12,24.4, 89,6 , M ,,, 0000 * 1F $ GPRMC, 093345.679, 4234.7899, N, 11344.2567, W , 3,02,24.5, 1.000,23 , M ,,, 0000 * 1F $ GPRMC, 044584.936, 1276.5539, N, 88734.1543, E , 2,04,33.5, 600,323 , M ,,, * 00 $ GPRMC, 199304.973, 3248.7780, N, 11355.7832, W , 1,06,02.2, 25.722,5 , M ,,, * 00 $ GPRMC, 066487.954, 4572.0089, S, 45572.3345, W , 3,09,15.0, 35.000,00 , M ,,, * 1F
Hier sind einige weitere Erläuterung der Daten:
"Ich sehe aus wie ich fünf Dinge brauche aus jeder Zeile. Und beachten dass eine dieser Gegend kann leeren. Bedeutung wird es nur zwei sein Kommas direkt nebeneinander. Eine solche als ‚,,,‘ Es gibt zwei Felder, die möglicherweise jederzeit voll sein. Einige von ihnen nur haben zwei oder drei Optionen, die sie kann sein, aber ich glaube nicht, dass ich sein Zählen auf dem. "
Vor zwei Tagen mein Freund konnte das volle Protokoll aus dem GPS-Empfänger erwerben verwendete eine aktuelle Wetterballonstart zu verfolgen. Die Daten sind ziemlich lang, so dass ich sie alle in dieser Pastebin .
Ich bin noch recht neu mit regulären Ausdrücken mich, so dass ich für etwas Unterstützung suchen.
Lösung
Splitting sollte es tun. Hier ist ein guter Weg, um die Daten zu extrahieren, auch:
>>> line = "$GPRMC,199304.973,3248.7780,N,11355.7832,W,1,06,02.2,25722.5,M,,,*00"
>>> line = line.split(",")
>>> neededData = (float(line[2]), line[3], float(line[4]), line[5], float(line[9]))
>>> print neededData
(3248.7779999999998, 'N', 11355.7832, 'W', 25722.5)
Andere Tipps
Es ist einfacher Spaltung als ein Regex zu verwenden.
>>> line="$GPRMC,092204.999,4250.5589,S,14718.5084,E,1,12,24.4,89.6,M,,,0000*1F "
>>> line.split(',')
['$GPRMC', '092204.999', '4250.5589', 'S', '14718.5084', 'E', '1', '12', '24.4', '89.6', 'M', '', '', '0000*1F ']
>>>
Die getrennten Werte sind Komma, so eine csv-Bibliothek ist die einfachste Lösung.
warf ich, dass die Probendaten, die Sie haben in / var / tmp / sample, dann habe ich diese:
>>> import csv
>>> for line in csv.reader(open('/var/tmp/sampledata')):
... print line
['$GPRMC', '092204.999', '**4250.5589', 'S', '14718.5084', 'E**', '1', '12', '24.4', '**89.6**', 'M', '', '', '0000\\*1F']
['$GPRMC', '093345.679', '**4234.7899', 'N', '11344.2567', 'W**', '3', '02', '24.5', '**1000.23**', 'M', '', '', '0000\\*1F']
['$GPRMC', '044584.936', '**1276.5539', 'N', '88734.1543', 'E**', '2', '04', '33.5', '**600.323**', 'M', '', '', '\\*00']
['$GPRMC', '199304.973', '**3248.7780', 'N', '11355.7832', 'W**', '1', '06', '02.2', '**25722.5**', 'M', '', '', '\\*00']
['$GPRMC', '066487.954', '**4572.0089', 'S', '45572.3345', 'W**', '3', '09', '15.0', '**35000.00**', 'M', '', '', '\\*1F']
Sie können dann die Daten verarbeiten, wie Sie wollen. Es sieht ein wenig seltsam mit dem ‚**‘ am Anfang und Ende von einigen der Werte, möchten Sie vielleicht das Zeug abzustreifen, können Sie tun:
>> eastwest = 'E**'
>> eastwest = eastwest.strip('*')
>> print eastwest
E
Sie müssen einige Werte als Schwimmer werfen. So zum Beispiel der dritte Wert in der ersten Zeile von Beispieldaten ist:
>> data = '**4250.5589'
>> print float(data.strip('*'))
4250.5589
Sie sollten auch zuerst die Prüfsumme der Daten überprüfen. Es wird berechnet, indem XOR-Verknüpfung der Zeichen zwischen $ und * (ohne sie) und auf den Hex-Wert am Ende verglichen wird.
Ihre Pastebin sieht aus wie es einige korrupte Linien in ihm hat. Hier ist eine einfache Prüfung, es wird davon ausgegangen, dass die Linie mit $ beginnt und hat keine CR / LF am Ende. Zum Aufbau eines robusteren Parser Sie für die ‚$‘ suchen müssen und die Arbeit durch den Strang bis Schlagen der ‚*‘.
def check_nmea0183(s):
"""
Check a string to see if it is a valid NMEA 0183 sentence
"""
if s[0] != '$':
return False
if s[-3] != '*':
return False
checksum = 0
for c in s[1:-3]:
checksum ^= ord(c)
if int(s[-2:],16) != checksum:
return False
return True
Sie können eine Bibliothek verwenden, wie pynmea2 für das NMEA Protokoll Parsen.
>>> import pynmea2
>>> msg = pynmea2.parse('$GPGGA,142927.829,2831.4705,N,08041.0067,W,1,07,1.0,7.9,M,-31.2,M,0.0,0000*4F')
>>> msg.timestamp, msg.latitude, msg.longitude, msg.altitude
(datetime.time(14, 29, 27), 28.524508333333333, -80.683445, 7.9)
Disclaimer: Ich bin der Autor des pynmea2
Wenn Sie etwas umfangreichere Analyse der GPS-Datenströme tun müssen, ist hier eine pyparsing Lösung, die Ihre Daten in benannte Datenfelder aufbricht. Ich extrahiert Ihre pastebin'ned Daten in eine Datei gpsstream.txt und analysiert sie mit folgendem:
"""
Parse NMEA 0183 codes for GPS data
http://en.wikipedia.org/wiki/NMEA_0183
(data formats from http://www.gpsinformation.org/dale/nmea.htm)
"""
from pyparsing import *
lead = "$"
code = Word(alphas.upper(),exact=5)
end = "*"
COMMA = Suppress(',')
cksum = Word(hexnums,exact=2).setParseAction(lambda t:int(t[0],16))
# define basic data value forms, and attach conversion actions
word = Word(alphanums)
N,S,E,W = map(Keyword,"NSEW")
integer = Regex(r"-?\d+").setParseAction(lambda t:int(t[0]))
real = Regex(r"-?\d+\.\d*").setParseAction(lambda t:float(t[0]))
timestamp = Regex(r"\d{2}\d{2}\d{2}\.\d+")
timestamp.setParseAction(lambda t: t[0][:2]+':'+t[0][2:4]+':'+t[0][4:])
def lonlatConversion(t):
t["deg"] = int(t.deg)
t["min"] = float(t.min)
t["value"] = ((t.deg + t.min/60.0)
* {'N':1,'S':-1,'':1}[t.ns]
* {'E':1,'W':-1,'':1}[t.ew])
lat = Regex(r"(?P<deg>\d{2})(?P<min>\d{2}\.\d+),(?P<ns>[NS])").setParseAction(lonlatConversion)
lon = Regex(r"(?P<deg>\d{3})(?P<min>\d{2}\.\d+),(?P<ew>[EW])").setParseAction(lonlatConversion)
# define expression for a complete data record
value = timestamp | Group(lon) | Group(lat) | real | integer | N | S | E | W | word
item = lead + code("code") + COMMA + delimitedList(Optional(value,None))("datafields") + end + cksum("cksum")
def parseGGA(tokens):
keys = "time lat lon qual numsats horiz_dilut alt _ geoid_ht _ last_update_secs stnid".split()
for k,v in zip(keys, tokens.datafields):
if k != '_':
tokens[k] = v
#~ print tokens.dump()
def parseGSA(tokens):
keys = "auto_manual _3dfix prn prn prn prn prn prn prn prn prn prn prn prn pdop hdop vdop".split()
tokens["prn"] = []
for k,v in zip(keys, tokens.datafields):
if k != 'prn':
tokens[k] = v
else:
if v is not None:
tokens[k].append(v)
#~ print tokens.dump()
def parseRMC(tokens):
keys = "time active_void lat lon speed track_angle date mag_var _ signal_integrity".split()
for k,v in zip(keys, tokens.datafields):
if k != '_':
if k == 'date' and v is not None:
v = "%06d" % v
tokens[k] = '20%s/%s/%s' % (v[4:],v[2:4],v[:2])
else:
tokens[k] = v
#~ print tokens.dump()
# process sample data
data = open("gpsstream.txt").read().expandtabs()
count = 0
for i,s,e in item.scanString(data):
# use checksum to validate input
linebody = data[s+1:e-3]
checksum = reduce(lambda a,b:a^b, map(ord, linebody))
if i.cksum != checksum:
continue
count += 1
# parse out specific data fields, depending on code field
fn = {'GPGGA' : parseGGA,
'GPGSA' : parseGSA,
'GPRMC' : parseRMC,}[i.code]
fn(i)
# print out time/position/speed values
if i.code == 'GPRMC':
print "%s %8.3f %8.3f %4d" % (i.time, i.lat.value, i.lon.value, i.speed or 0)
print count
Die $ GPRMC Datensätze in Ihrer Pastebin scheinen nicht ganz mit denen übereinstimmen Sie in Ihrem Beitrag enthalten, aber Sie sollten dieses Beispiel nach Bedarf anzupassen können.
Ich schlage vor, eine kleine Korrektur in Ihrem Code, weil, wenn verwendet, um Daten aus dem vorigen Jahrhundert zu analysieren das Datum wie irgendwann in der Zukunft sieht (zB 2094 statt 1994)
Meine Lösung ist nicht ganz genau, aber ich nehme den Stand, der keine GPS-Daten bestanden zu den 70er Jahren vor.
In der def Parse-Funktion für RMC Sätze ersetzen Sie einfach das Format Zeile:
p = int(v[4:])
print "p = ", p
if p > 70:
tokens[k] = '19%s/%s/%s' % (v[4:],v[2:4],v[:2])
else:
tokens[k] = '20%s/%s/%s' % (v[4:],v[2:4],v[:2])
Dies wird an den beiden yy Ziffern des Jahres schauen und geht davon aus, dass vergangenes Jahr 70 wir mit Sätzen aus dem vorigen Jahrhundert handeln. Es könnte besser durch Vergleich mit dem heutigen Datum erfolgen und unter der Annahme, dass jedes Mal, wenn Sie mit einigen Daten in der Zukunft beschäftigen, sind sie in der Tat aus dem vergangenen Jahrhundert
Vielen Dank für alle Teile des Codes Ihrer oben zur Verfügung gestellt ... Ich hatte etwas Spaß mit diesem.