2008-11-22 25 views
12

Tôi có một người bạn đang hoàn thành bằng thạc sĩ về kỹ thuật hàng không vũ trụ. Đối với dự án cuối cùng của mình, ông là một nhóm nhỏ có nhiệm vụ viết một chương trình để theo dõi các bong bóng thời tiết, tên lửa và vệ tinh. Chương trình nhận đầu vào từ thiết bị GPS, thực hiện các phép tính với dữ liệu và sử dụng kết quả của các phép tính đó để điều khiển một loạt các động cơ được thiết kế để định hướng anten truyền dẫn hướng, do đó quả bóng, tên lửa hoặc vệ tinh luôn được chú trọng.Phân tích đầu ra bộ thu GPS qua regex bằng Python

Mặc dù phần nào của một người mới bắt đầu (vĩnh cửu) bản thân mình, tôi có nhiều kinh nghiệm lập trình hơn bạn tôi. Vì vậy, khi anh ấy hỏi tôi để được tư vấn, tôi đã thuyết phục anh ấy viết chương trình bằng Python, ngôn ngữ tôi chọn.

Tại thời điểm này trong dự án, chúng tôi đang làm việc trên mã phân tích cú pháp đầu vào từ thiết bị GPS. Dưới đây là một số ví dụ đầu vào, với các dữ liệu chúng ta cần phải giải nén in đậm:

$ GPRMC, 092.204,999, 4250,5589, S, 14.718,5084, E, 1,12,24.4, 89,6, M ,,, 0000 * 1F $ GPRMC, 093.345,679, 4234,7899, N, 11.344,2567, W, 3,02,24.5, 1000,23, M ,,, 0000 * 1F $ GPRMC, 044.584,936, 1276,5539, N, 88.734,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, M ,,, * 00 $ GPRMC, 066.487,954, 4572,0089, S, 45.572,3345, W, 3,09,15.0, 35000.00, M ,,, * 1F

Dưới đây là một số thêm giải thích về dữ liệu:

"Có vẻ như tôi cần năm thứ trên mỗi dòng. Và ghi nhớ rằng bất kỳ khu vực nào trong số này có thể là trống. Có nghĩa là sẽ chỉ có hai dấu phẩy ngay cạnh nhau. Như vậy là ',,,' Có hai trường có thể đầy đủ bất cứ lúc nào. Một số trong số họ chỉ có hai hoặc ba tùy chọn mà họ có thể nhưng tôi không nghĩ rằng tôi nên đếm trên đó ".

Hai ngày trước bạn bè của tôi đã có thể có được đầy đủ các bản ghi từ GPS dùng để theo dõi một phóng thời tiết khí cầu gần đây. Các dữ liệu là khá dài, vì vậy tôi đặt nó tất cả trong this pastebin.

tôi vẫn còn khá mới mẻ với biểu thức thông thường bản thân mình, vì vậy tôi đang tìm kiếm sự giúp đỡ.

+0

Bằng cách này, $ GPRMC dòng của bạn dường như không phù hợp với tiêu chuẩn. http://home.mira.net/~gnb/gps/nmea.html#gprmc Tôi có thiếu gì đó không? –

+0

Cảm ơn bạn đã chỉ ra rằng Federico. Tôi chắc chắn sẽ xem xét điều đó. – crashsystems

+0

Dường như có nhiều dòng $ GPGGA. –

Trả lời

15

chia nhỏ nên thực hiện thủ thuật. Dưới đây là một cách hay để trích xuất dữ liệu:

>>> 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

Nó đơn giản hơn để sử dụng tách hơn một 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 '] 
>>> 
+0

Hah, tôi sẽ chọn giải pháp phức tạp hơn! – crashsystems

4

Trước tiên, bạn cũng nên kiểm tra tổng kiểm của dữ liệu. Nó được tính bằng cách XORing các ký tự giữa $ và * (không bao gồm chúng) và so sánh nó với giá trị hex ở cuối.

Con nhộng của bạn trông giống như nó có một số dòng bị hỏng trong đó. Đây là một kiểm tra đơn giản, nó giả định rằng dòng bắt đầu bằng $ và không có CR/LF ở cuối. Để xây dựng một trình phân tích cú pháp mạnh mẽ hơn, bạn cần phải tìm kiếm '$' và làm việc thông qua chuỗi cho đến khi nhấn '*'.

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

Cảm ơn ví dụ. Tôi chắc chắn sẽ làm một cái gì đó tương tự như thế này khi viết chức năng kiểm tra đầu vào. – crashsystems

5

Đó là các giá trị được phân tách bằng dấu phẩy, do đó, sử dụng thư viện csv là giải pháp dễ nhất.

Tôi ném rằng dữ liệu mẫu mà bạn có vào/var/tmp/SampleData, sau đó tôi đã làm điều này:

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

Sau đó bạn có thể xử lý dữ liệu tuy nhiên bạn muốn. Có vẻ hơi kỳ quặc với '**' lúc bắt đầu và kết thúc của một số giá trị, bạn có thể muốn tước thứ mà tắt, bạn có thể làm:

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

Bạn sẽ phải bỏ một số giá trị như nổi. Vì vậy, ví dụ, giá trị thứ 3 trên dòng đầu tiên của dữ liệu mẫu là:

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

Nó chỉ ra rằng có một số mã hóa thêm đang xảy ra ở đây. Ví dụ: "4250.5589, S" thực sự là vĩ độ 42 ° 50.5589'S – PaulMcG

2

Nếu bạn cần phải làm một số phân tích rộng rãi hơn của dòng dữ liệu GPS của bạn, đây là một giải pháp pyparsing mà phá vỡ lưu dữ liệu của bạn vào tên trường dữ liệu. Tôi trích xuất dữ liệu pastebin'ned của bạn vào một tập tin gpsstream.txt, và phân tích nó như sau:

""" 
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 

hồ sơ Các $ GPRMC trong pastebin của bạn dường như không hoàn toàn phù hợp với những người bạn đã đưa vào trong bài viết của bạn , nhưng bạn sẽ có thể điều chỉnh ví dụ này nếu cần.

1

Tôi đề nghị một sửa chữa nhỏ trong mã của bạn vì nếu được sử dụng để phân tích dữ liệu từ thế kỷ trước ngày trông giống như đôi khi trong tương lai (ví dụ năm 2094 thay vì 1994)

sửa chữa của tôi là không hoàn toàn chính xác, nhưng Tôi lấy chỗ đứng trước những năm 70 không có dữ liệu GPS nào tồn tại.

Trong chức năng def phân tích cú pháp cho câu RMC chỉ thay thế dòng định dạng theo:

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]) 

này sẽ xem xét hai chữ số yy của năm và giả định rằng quá khứ năm 70 chúng ta đang đối phó với câu từ thế kỷ trước. Có thể thực hiện tốt hơn bằng cách so sánh với ngày hôm nay và giả sử rằng mỗi khi bạn xử lý một số dữ liệu trong tương lai, thực tế là từ thế kỷ trước

Cảm ơn tất cả các mã bạn đã cung cấp ở trên ... Tôi đã có một số niềm vui với điều này.

3

Bạn có thể sử dụng thư viện như pynmea2 để phân tích nhật ký 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) 

Disclaimer: Tôi là tác giả của pynmea2

+0

Cảm ơn bạn vì điều này! :) –

Các vấn đề liên quan