2008-11-22 7 views
12

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.Analizando la salida del receptor GPS a través de expresiones regulares en Python

Aunque soy un principiante (eterno), tengo más experiencia en programación que mi amigo. Entonces, cuando me pidió consejo, lo convencí para que escribiera 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 alguna entrada de ejemplo, con los datos que necesitamos para extraer en negrita:

$ GPRMC, 092.204,999, 4250,5589, S, 14718.5084, E, 1,12,24.4, 89,6, H ,,, 0000 * 1F $ GPRMC, 093.345,679, 4234,7899, N, 11344.2567, W, 3,02,24.5, 1.000,23, H ,,, 0000 * 1F $ GPRMC, 044.584,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, H ,,, * 00 $ GPRMC, 066.487,954, 4572,0089, S, 45572.3345, W, 3,09,15.0, 35000.00, H ,,, * 1F

Aquí está algo más explicación 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. Tal como ',,,' Hay dos campos que pueden llenos en cualquier momento. Algunos de ellos sólo tienen dos o tres opciones que puede ser, pero no creo que yo debería ser contando con que ".

hace

Dos días mi amigo fue capaz de adquirir el registro completo de la receptor GPS utilizado para hacer un seguimiento de una reciente lanzamiento de un globo meteorológico. Los datos son bastante largo, por lo que poner todo en this pastebin.

estoy todavía bastante nuevo con expresiones regulares a mí mismo, por lo que estoy buscando algo de ayuda.

+0

Por cierto, no parece que su línea de GPRMC $ para ajustarse a la norma. http://home.mira.net/~gnb/gps/nmea.html#gprmc ¿Me estoy perdiendo algo? –

+0

Gracias por señalar eso Federico. Me aseguraré de investigar eso. – crashsystems

+0

Parece más una línea $ GPGGA. –

Respuesta

15

splitting 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) 
8

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 '] 
>>> 
+0

¡Hah, sería como elegir la solución más complicada! – crashsystems

4

También debe comprobar 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 
+0

Gracias por el ejemplo. Me aseguraré de hacer algo similar a esto cuando escriba la función de verificación de entrada. – crashsystems

5

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

me arrojó que los datos de la muestra que tiene 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. Se ve un poco rara, con la '**' al principio y al final de algunos de los valores, es posible que desee quitar esas cosas fuera, que puede hacer:

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

que tendrá que emitir algunos valores como flota. Así, por ejemplo, la tercera valor en la primera línea de los datos de la muestra es:

>> data = '**4250.5589' 
>> print float(data.strip('*')) 
4250.5589 
+0

Resulta que hay algo de codificación adicional pasando aquí. Por ejemplo, "4250.5589, S" es en realidad la latitud 42 ° 50.5589'S – PaulMcG

2

Si necesita hacer algún análisis más extenso de sus flujos de datos GPS, aquí es una solución pyparsing que rompe sus datos en el nombre Campos de información. Extraje sus datos pastebin'ned a un gpsstream.txt archivo y Analizada 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 
no parecen

registros $ El GPRMC en su Pastebin para que coincida bastante con los que incluyó en su puesto , pero debería poder ajustar este ejemplo según sea necesario.

1

que sugieren un pequeño arreglo en su código, porque si se utiliza para analizar los datos desde el siglo anterior a la fecha parece que en algún momento en el futuro (por ejemplo, 2094 en lugar de 1994)

Mi solución no es totalmente exacta, pero Considero que antes de los años 70 no existían datos de GPS.

En la función DEF análisis sintáctico de oraciones RMC basta con sustituir 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 se verá en los dos dígitos AA del año y asumir que el año pasado 70 estamos tratando con frases de la siglo anterior. Podría ser mejor comparándolo con la fecha de hoy y suponiendo que cada vez que maneje datos en el futuro, en realidad son del siglo pasado

Gracias por todos los códigos provistos anteriormente ... Me divertí un poco con esto.

3

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) 

responsabilidad: yo soy el autor de pynmea2

+0

¡Gracias por esto! :) –

Cuestiones relacionadas