Pythonの正規表現を介したGPS受信機出力の解析
質問
航空宇宙工学の修士号を取得している友人がいます。彼の最終プロジェクトでは、彼は気象観測気球、ロケット、衛星を追跡するためのプログラムを作成する小さなチームに所属しています。このプログラムは、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
データの詳細な説明:
" 5つのものが必要なようです すべての行から。そして心に留めて これらのエリアのいずれかが 空の。意味は2つだけになります すぐ隣にコンマがあります。そのような ',,,'として2つのフィールドがあります。 いつでもいっぱいです。それらのいくつかのみ 彼らがする2つまたは3つのオプションがあります かもしれないが、私はそうすべきではないと思う それを頼りに。"
2日前、私の友人は最近の気球の打ち上げを追跡するために使用されるGPS受信機から完全なログを取得することができました。データは非常に長いため、すべてをこのペーストビンに入れます。
私はまだ正規表現に慣れていないので、いくつかの支援を探しています。
解決
分割はトリックを行う必要があります。データを抽出するための良い方法もあります:
>>> 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)
他のヒント
正規表現よりも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 ']
>>>
これらはコンマ区切りの値であるため、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
いくつかの値をフロートとしてキャストする必要があります。したがって、たとえば、サンプルデータの最初の行の3番目の値は次のとおりです。
>> data = '**4250.5589'
>> print float(data.strip('*'))
4250.5589
最初にデータのチェックサムも確認する必要があります。これは、$と*(それらを含まない)の間の文字のXOR演算と、最後の16進値との比較によって計算されます。
ペーストビンには、破損した行が含まれているようです。以下に簡単なチェックを示します。行が$で始まり、末尾に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
NMEAログの解析には、 pynmea2 などのライブラリを使用できます。
>>> 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の著者です
GPSデータストリームのより広範な分析を行う必要がある場合は、データを名前付きデータフィールドに分割するpyparsingソリューションがあります。 pastebin'nedデータをファイル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 = "<*>quot;
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
ペーストビンの$ GPRMCレコードは、投稿に含めたものと完全には一致していないようですが、必要に応じてこの例を調整できます。
前世紀のデータを解析するために使用された場合、日付は将来(たとえば1994年ではなく2094年)のように見えるため、コードを少し修正することをお勧めします。
修正は完全に正確ではありませんが、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])
これは、年の2つのyy桁を調べ、過去70年が前世紀の文を扱っていると仮定します。 今日の日付と比較して、将来何らかのデータを扱うたびに、実際には過去の世紀のものであると仮定することで、より適切に行うことができます
上で提供されたすべてのコードに感謝します...私はこれを少し楽しみました。