在 Python 中通过正则表达式解析 GPS 接收器输出
Posted
技术标签:
【中文标题】在 Python 中通过正则表达式解析 GPS 接收器输出【英文标题】:Parsing GPS receiver output via regex in Python 【发布时间】:2010-09-23 15:06:39 【问题描述】:我有一个朋友正在完成他的航空航天工程硕士学位。对于他的最后一个项目,他在一个小团队中负责编写一个跟踪气象气球、火箭和卫星的程序。该程序接收来自 GPS 设备的输入,对数据进行计算,并使用这些计算的结果来控制一系列电机,这些电机旨在为定向通信天线定向,因此气球、火箭或卫星始终保持焦点。
虽然我自己有点(永恒的)初学者,但我比我的朋友有更多的编程经验。因此,当他向我寻求建议时,我说服他用我选择的语言 Python 编写程序。
在项目的这一点上,我们正在处理解析来自 GPS 设备的输入的代码。这是一些示例输入,我们需要提取的数据以粗体显示:
$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
以下是对数据的进一步解释:
“我看起来需要五样东西 从每一行。记住 这些区域中的任何一个都可能是 空的。意味着只有两个 逗号紧挨着。这样的 如 ',,,' 有两个字段可以 随时吃饱。仅其中一些 他们有两个或三个选项 可能是,但我认为我不应该是 指望这一点。”
两天前,我的朋友能够从用于跟踪最近气象气球发射的 GPS 接收器获取完整日志。数据比较长,我都放在this pastebin里了。
我自己对正则表达式还是很陌生,所以我正在寻求一些帮助。
【问题讨论】:
顺便说一句,您的 $GPRMC 行似乎不符合标准。 home.mira.net/~gnb/gps/nmea.html#gprmc我错过了什么吗? 感谢您指出 Federico。我一定会调查的。 这似乎更像是一条 $GPGGA 行。 老实说,我个人对使用的设备并不熟悉。但是,我认为我链接到 (pastebin.com/f5f5cf9ab) 的 pastebin 可能会提供一些说明。 【参考方案1】:拆分应该可以解决问题。这也是一种提取数据的好方法:
>>> 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)
【讨论】:
【参考方案2】:使用 split 比使用正则表达式更简单。
>>> 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 ']
>>>
【讨论】:
哈,我会选择更复杂的解决方案!【参考方案3】:您可以使用像 pynmea2 这样的库来解析 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)
免责声明:我是pynmea2的作者
【讨论】:
【参考方案4】:这些是逗号分隔值,因此使用 csv 库是最简单的解决方案。
我将你拥有的样本数据放入 /var/tmp/sampledata,然后我这样做了:
>>> 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']
然后,您可以根据需要处理数据。某些值的开头和结尾处的 '**' 看起来有点奇怪,你可能想去掉这些东西,你可以这样做:
>> eastwest = 'E**'
>> eastwest = eastwest.strip('*')
>> print eastwest
E
您必须将一些值转换为浮点数。例如,样本数据第一行的第三个值是:
>> data = '**4250.5589'
>> print float(data.strip('*'))
4250.5589
【讨论】:
原来这里有一些额外的编码。例如,“4250.5589,S”实际上是纬度 42°50.5589'S【参考方案5】:您还应该首先检查数据的校验和。它是通过对 $ 和 * 之间的字符(不包括它们)进行异或运算并将其与末尾的十六进制值进行比较来计算的。
您的 pastebin 中似乎有一些损坏的行。这是一个简单的检查,它假设该行以 $ 开头并且末尾没有 CR/LF。要构建更强大的解析器,您需要搜索“$”并遍历字符串,直到找到“*”。
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
【讨论】:
感谢您的示例。在编写输入检查函数时,我一定会做类似的事情。 def check_nmea(s): return s and \s[0] == '$' and \s[-3] == '*' and \reduce(lambda x, y: x^ ord(y), s[1:-3], 0) == int(s[-2:],16)【参考方案6】:如果您需要对 GPS 数据流进行更广泛的分析,这里有一个 pyparsing 解决方案,可将您的数据分解为命名数据字段。我将您粘贴的数据提取到文件 gpsstream.txt 中,并使用以下内容对其进行解析:
"""
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"\d2\d2\d2\.\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>\d2)(?P<min>\d2\.\d+),(?P<ns>[NS])").setParseAction(lonlatConversion)
lon = Regex(r"(?P<deg>\d3)(?P<min>\d2\.\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
您的 pastebin 中的 $GPRMC 记录似乎与您在帖子中包含的记录不太匹配,但您应该能够根据需要调整此示例。
【讨论】:
【参考方案7】:我建议在您的代码中进行一个小修复,因为如果用于解析上个世纪的数据,日期看起来像是在未来的某个时间(例如 2094 年而不是 1994 年)
我的修正并不完全准确,但我的立场是在 70 年代之前不存在 GPS 数据。
在 RMC 语句的 def 解析函数中,只需将格式行替换为:
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])
这将查看年份的两位 yy 数字,并假设过去 70 年我们正在处理上个世纪的句子。 与今天的日期相比,并假设将来每次处理一些数据时,它们实际上都是上个世纪的,这可能会做得更好
感谢您在上面提供的所有代码...我玩得很开心。
【讨论】:
以上是关于在 Python 中通过正则表达式解析 GPS 接收器输出的主要内容,如果未能解决你的问题,请参考以下文章