regex 在Python中通过正则表达式解析GPS接收机输出

ig9co6j1  于 2023-02-05  发布在  Python
关注(0)|答案(8)|浏览(99)

我有一个朋友即将完成他的航空航天工程硕士学位。在他的最后一个项目中,他在一个小团队中,任务是编写一个跟踪气象气球、火箭和卫星的程序。该程序接收来自GPS设备的输入,对数据进行计算,并使用这些计算的结果来控制一系列电机,这些电机旨在确定定向通信天线的方向,因此气球,火箭或卫星总是保持在焦点上。
虽然我自己是个(永远的)初学者,但我比我的朋友有更多的编程经验,所以当他向我征求建议时,我说服他用Python(我选择的语言)编写程序。
在项目的这一点上,我们正在编写解析来自GPS设备的输入的代码。下面是一些示例输入,我们需要提取的数据用粗体显示:
美国通用PRMC公司,092204.999,4250.5589,S,14718.5084,E,1,12,24.4,89.6,M,,,0000* 一法美国通用PRMC公司,093345.679,4234.7899,N,11344.2567,W,3,02,24.5,1000.23,M,,,0000* 一法美国通用PRMC公司,044584.936,1276.5539,北,88734.1543,英,2,04,33.5,600.323,中,,,*00通用无线电通信公司,199304.973,3248.7780,北,11355.7832,西,1,06,02.2,25722.5,中,,,00通用无线电通信公司,066487.954,4572.0089,中,45572.3345,西,3,09,15.0,35000.00,中,,,, 第1页,女
以下是对数据的进一步解释:
“我看起来需要每行有五个内容。请记住,这些区域中的任何一个都可能是空的。这意味着只有两个逗号紧挨着。例如”,,,“有两个字段随时可能是满的。其中一些字段只有两个或三个选项,但我认为我不应该指望这一点。”
两天前,我的朋友能够从GPS接收器中获得完整的日志,GPS接收器用于跟踪最近的一次气象气球发射。数据相当长,所以我把它们都放在this pastebin中。
我自己对正则表达式还是相当陌生的,所以我正在寻找一些帮助。

ff29svar

ff29svar1#

拆分应该可以解决这个问题。这里还有一个提取数据的好方法:

>>> 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)
6kkfgxo0

6kkfgxo02#

您可以使用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的作者

z18hc3ub

z18hc3ub3#

使用split比使用regex简单。

>>> 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 ']
>>>
q0qdq0h2

q0qdq0h24#

这些都是逗号分隔的值,所以使用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
yruzcnhs

yruzcnhs5#

你也应该首先检查数据的校验和。它是通过异或$和 * 之间的字符(不包括它们)并将其与末尾的十六进制值进行比较来计算的。
你的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
lhcgjxsq

lhcgjxsq6#

这是GPRMC字符串。拆分字符串后,需要解析纬度和经度值。

line = "$GPRMC,199304.973,3248.7780,N,11355.7832,W,1,06,02.2,25722.5,M,,,*00"
line = line.split(",")

经纬度部分([..., '3248.7780', 'N', '11355.7832, 'W', ...]):

  • 第一个数字不是一个纯粹的数字,它是一个像字符串一样连接起来的数字。我的意思是,3248.778032度,48.7780分(纬度)
  • 第二个数字(11355.7832)表示113度,55.7832分(经度)

它们不能照原样用在公式中,必须转换成十进制度数。

def toDD(s):
    d = float(s[:-7])
    m = float(s[-7:]) / 60
    return d + m

lat_lon = (toDD(line[2]), line[3], toDD(line[4]), line[5])
print(lat_lon)

# (32.81296666666667, 'N', 113.92972, 'W')
jobtbby3

jobtbby37#

如果你需要对你的GPS数据流做一些更广泛的分析,这里有一个pyparse解决方案,它把你的数据分解成命名的数据字段。我把你的pastebin数据提取到一个文件gpssream.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"\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

你的pastebin中的$GPRMC记录看起来和你的帖子中的不太匹配,但是你应该能够根据需要调整这个例子。

dohp0rv5

dohp0rv58#

我建议在代码中做一个小的修正,因为如果用来解析上个世纪的数据,日期看起来像是未来的某个时候(例如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是上个世纪的句子。通过与今天的日期进行比较,并假设将来每次处理某些数据时,它们实际上都是上个世纪的,这样做可能会更好
感谢您提供的所有代码片段以上...我有一些乐趣与此。

相关问题