Análisis de la salida del receptor GPS a través de expresiones regulares en Python

Tengo un amigo que está terminando su maestría en ingeniería aeroespacial. Para su proyecto final, está en un pequeño equipo encargado de escribir un progtwig para rastrear globos meteorológicos, cohetes y satélites. El progtwig 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 siempre permanezcan enfocados.

Aunque soy un principiante (eterno), tengo más experiencia en progtwigción que mi amigo. Entonces, cuando me pidió consejo, lo convencí de que escribiera el progtwig en Python, mi idioma de elección.

En este punto del proyecto, estamos trabajando en el código que analiza la entrada del dispositivo GPS. Aquí hay una entrada de ejemplo, 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 una explicación más detallada 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. Lo que significa que solo habrá dos comas una al lado de la otra. Como ‘,,,’ There Hay 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 globo meteorológico. Los datos son bastante largos, así que los puse todos en este pastebin .

Todavía soy bastante nuevo con expresiones regulares, así que estoy buscando ayuda.

La división debe 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) 

Es más fácil de usar división 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 '] >>> 

Esos son valores separados por comas, por lo que usar una biblioteca csv es la solución más fácil.

Tiré los datos de muestra que tienes en / 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 desee. Parece un poco extraño con el ‘**’ al principio y al final de algunos de los valores, es posible que desees despojarte de eso, puedes hacerlo:

 >> eastwest = 'E**' >> eastwest = eastwest.strip('*') >> print eastwest E 

Tendrás que lanzar algunos valores como flotadores. 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 

También debe comprobar primero la sum de comprobación de los datos. Se calcula al XORAR los caracteres entre $ y * (sin incluirlos) y comparándolos con el valor hexadecimal al final.

Parece que tu pastebin tiene algunas líneas corruptas. Aquí hay una comprobació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 llegar al ‘*’.

 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 

Podría 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

Si necesita hacer un análisis más extenso de sus flujos de datos GPS, aquí hay una solución de análisis de datos que divide sus datos en campos de datos con nombre. Extraje sus datos pastebin’ned a 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\d{2})(?P\d{2}\.\d+),(?P[NS])").setParseAction(lonlatConversion) lon = Regex(r"(?P\d{3})(?P\d{2}\.\d+),(?P[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 solución en su código porque, si se utiliza para analizar datos del siglo anterior, la fecha parece en algún momento en el futuro (por ejemplo, 2094 en lugar de 1994)

Mi solución no es totalmente precisa, pero creo que antes de los 70 no existían datos de GPS.

En la función def parse para las oraciones 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 mirará los dos dígitos yy del año y asumirá que en el año 70 anterior estamos tratando con oraciones del siglo anterior. Se podría hacer mejor comparando con la fecha de hoy y asumiendo que cada vez que trata con algunos datos en el futuro, de hecho, son del siglo pasado.

Gracias por todas las piezas de código que proporcionó anteriormente … Me divertí mucho con esto.