使用Python(3.1或2.6),我试图从GPS接收器生成的二进制数据文件中读取数据。每小时的数据存储在一个单独的文件中,每个文件大约18 MiB。数据文件有多个可变长度记录,但是现在我需要从其中一个记录中提取数据。
我已经能够在某种程度上解码标题了。我有点说,因为有些数字没有意义,但大多数都没有。花了几天时间(我开始学习使用Python编程),我没有取得进展,所以是时候寻求帮助了。
参考指南为我提供了邮件头结构和记录结构。标头长度可变,但通常为28个字节。
Header
Field # Field Name Field Type Desc Bytes Offset
1 Sync char Hex 0xAA 1 0
2 Sync char Hex 0x44 1 1
3 Sync char Hex 0x12 1 2
4 Header Lgth uchar Length of header 1 3
5 Message ID ushort Message ID of log 2 4
8 Message Lgth ushort length of message 2 8
11 Time Status enum Quality of GPS time 1 13
12 Week ushort GPS week number 2 14
13 Milliseconds GPSec Time in ms 4 16
Record
Field # Data Bytes Format Units Offset
1 Header 0
2 Number of SV Observations 4 integer n/a H
*For first SV Observation*
3 PRN 4 integer n/a H+4
4 SV Azimuth angle 4 float degrees H+8
5 SV Elevation angle 4 float degrees H+12
6 C/N0 8 double db-Hz H+16
7 Total S4 8 double n/a H+24
...
27 L2 C/N0 8 double db-Hz H+148
28 *For next SV Observation*
SV Observation is satellite - there could be anywhere from 8 to 13
in view.
这是我尝试理解标题的代码:
import struct
filename = "100301_110000.nvd"
f = open(filename, "rb")
s = f.read(28)
x, y, z, lgth, msg_id, mtype, port, mlgth, seq, idletime, timestatus, week, millis, recstatus, reserved, version = struct.unpack("<cccBHcBHHBcHLLHH", s)
print(x, y, z, lgth, msg_id, mtype, port, mlgth, seq, idletime, timestatus, week, millis, recstatus, reserved, version)
输出:
b'\xaa' b'D' b'\x12' 28 274 b'\x02' 32 1524 0 78 b'\xa0' 1573 126060000 10485760 3545 35358
3个同步字段应返回xAA x44 x12。 (D是x44的ascii等价 - 我假设。)
我正在寻找的记录ID是274 - 这似乎是正确的。
GPS周返回为1573 - 这似乎是正确的。
毫秒返回为126060000 - 我期待126015000。
如何查找标识为274的记录并将其解压缩? (我正在学习Python和编程,所以请记住,你给经验丰富的程序员提供的答案可能超出我的想法。)
答案 0 :(得分:6)
你必须阅读。不是因为内存限制,而是因为解析要求。 18MiB很容易适合记忆。在4Gb机器上,它可以在内存中容纳200次。
这是通常的设计模式。
仅读取前4个字节。使用struct
解压缩那些字节。
确认同步字节并获得标题长度。
如果你想要标题的其余部分,你知道长度,读取其余的字节。
如果您不想要标题,请使用seek
跳过它。
读取记录的前四个字节以获取SV观察的数量。使用struct
将其解压缩。
进行数学运算并读取指定的字节数,以获得记录中的所有SV观测值。
打开包装,做你正在做的事。
我强烈建议您在使用数据之前从数据中构建namedtuple
个对象。
如果您想要所有数据,则必须实际读取所有数据。
“并且没有一次读取一个字节的18 MiB文件?”我不明白这个约束。您必须读取所有字节才能获得所有字节。
您可以使用长度信息来读取有意义块中的字节。但你无法避免阅读所有字节。
此外,大量读取(和搜索)通常足够快。您的操作系统缓冲区,所以不要担心尝试微量优化读取次数。
只需按照“读取长度 - 读取数据”模式即可。
答案 1 :(得分:2)
18 MB应该可以很好地适应内存,所以我只需要用一个with open(thefile, 'rb') as f: data = f.read()
将整个字符串放入一个大的字节串中,然后对切片执行所有“解析”以逐个记录。它更方便,并且可能比在文件中从此处和那里进行许多小读取更快(尽管它不会影响下面的逻辑,因为在任何一种情况下,“数据中的当前关注点”始终在移动[ [总是转发,因为它发生]]基于一次几个字节的struct-unpack计算的数量,以找到标题和记录的长度。)
给定“记录开始”偏移量,您可以通过查看一个字节(“字段四”,从头部开头偏移3,与记录开头相同)来确定其标头的长度,并查看消息ID (下一个字段,2个字节),看看它是否是你关心的记录(所以只需要那3个字节的结构解包就足够了。)
无论是否是您想要的记录,您接下来需要计算记录的长度(要么跳过它还是要全部记录);为此,您计算实际记录数据的开始(记录的开始加上标题的长度加上记录的下一个字段(标题后面的4个字节)乘以观察的长度(如果我正确读取,则为32个字节) )。
这样你就可以将子字符串隔离到struct.unpack
(当你最终到达你想要的记录时),或者只是将标题+记录的总长度添加到“记录开始”偏移量,获取 next 记录开头的偏移量。
答案 2 :(得分:2)
除了编写一个正确读取文件的解析器之外,你可能尝试一些蛮力的方法......将数据读入内存并使用“同步”哨兵将其拆分。警告 - 你可能会得到一些误报。但...
f = open('filename')
data = f.read()
messages = data.split('\xaa\x44\x12')
mymessages = [ msg for msg in messages if len(msg) > 5 and msg[4:5] == '\x12\x01' ]
但这是一个非常讨厌的黑客......