sirve - Analizar la salida del receptor GPS a través de regex en Python
regex python (7)
Tengo un amigo que está terminando su maestría en ingeniería aeroespacial. Para su proyecto final, forma parte de un pequeño equipo encargado de escribir un programa para rastrear globos meteorológicos, cohetes y satélites. El programa recibe información de un dispositivo GPS, realiza cálculos con los datos y utiliza los resultados de esos cálculos para controlar una serie de motores diseñados para orientar una antena de comunicación direccional, de modo que el globo, el cohete o el satélite permanezcan siempre enfocados.
Aunque soy un principiante (eterno), tengo más experiencia en programación que mi amigo. Entonces, cuando me pidió consejo, lo convencí de escribir el programa en Python, mi idioma de elección.
En este punto del proyecto, estamos trabajando en el código que analiza la entrada desde el dispositivo GPS. Aquí hay algunos ejemplos de entrada, con los datos que necesitamos extraer en negrita:
$ 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
Aquí hay alguna explicación adicional de los datos:
"Parece que necesitaré cinco cosas de cada línea. Y tenga en cuenta que cualquiera de estas áreas puede estar vacía. Es decir, habrá solo dos comas una al lado de la otra, como '',,,'' Hay son dos campos que pueden estar llenos en cualquier momento. Algunos de ellos solo tienen dos o tres opciones, pero no creo que deba contar con eso ".
Hace dos días, mi amigo pudo adquirir el registro completo del receptor GPS utilizado para rastrear un lanzamiento reciente de un globo meteorológico. Los datos son bastante largos, así que lo puse todo en este pastebin .
Todavía soy bastante nuevo con expresiones regulares, así que estoy buscando ayuda.
Es más simple usar split que una expresión regular.
>>> 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 '']
>>>
También debe verificar primero la suma de comprobación de los datos. Se calcula haciendo un XOR de los caracteres entre $ y * (sin incluirlos) y comparándolo con el valor hexadecimal al final.
Su pastebin parece que tiene algunas líneas corruptas. Aquí hay una verificación simple, se supone que la línea comienza con $ y no tiene CR / LF al final. Para construir un analizador más robusto, debes buscar el ''$'' y trabajar a través de la cadena hasta que toques el ''*''.
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
Esos son valores separados por comas, por lo que usar una biblioteca csv es la solución más fácil.
Lancé los datos de muestra que tienes a / var / tmp / sampledata, luego hice esto:
>>> 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'']
A continuación, puede procesar los datos como lo desee. Parece un poco extraño con ''**'' al principio y al final de algunos de los valores, es posible que desee quitar esas cosas, puede hacer:
>> eastwest = ''E**''
>> eastwest = eastwest.strip(''*'')
>> print eastwest
E
Deberás lanzar algunos valores como flotantes. Entonces, por ejemplo, el tercer valor en la primera línea de datos de muestra es:
>> data = ''**4250.5589''
>> print float(data.strip(''*''))
4250.5589
Si necesita hacer un análisis más extenso de sus flujos de datos GPS, aquí hay una solución de pyparsing que divide sus datos en campos de datos con nombre. Extraje tus datos pegados en un archivo gpsstream.txt y los analicé con lo siguiente:
"""
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
Los registros de $ GPRMC en su pastebin no parecen coincidir con los que incluyó en su publicación, pero debería poder ajustar este ejemplo según sea necesario.
Sugiero una pequeña corrección en su código porque si se utiliza para analizar datos del siglo pasado, la fecha parece en algún momento en el futuro (por ejemplo 2094 en lugar de 1994)
Mi solución no es del todo precisa, pero considero que antes de los años 70 no existían datos de GPS.
En la función de definición de definiciones para frases RMC, simplemente reemplace la línea de formato por:
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])
Esto examinará los dos yy dígitos del año y supondrá que el año pasado 70 estamos lidiando con oraciones del siglo anterior. Podría hacerse mejor comparando con la fecha de hoy y suponiendo que cada vez que manejes datos en el futuro, en realidad son del siglo pasado
Gracias por todos los códigos provistos anteriormente ... Me divertí un poco con esto.
la división debería hacer el truco. Aquí hay una buena manera de extraer los datos, también:
>>> 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)
Puede usar una biblioteca como pynmea2 para analizar el registro NMEA.
>>> 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)
Descargo de responsabilidad: soy el autor de pynmea2