如何从具有多个可变长度记录的二进制数据文件中读取和提取数据?

2024-04-23 14:49:58 发布

您现在位置:Python中文网/ 问答频道 /正文

使用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和编程,所以请记住,你给一个有经验的程序员的答案可能是我无法理解的。)


Tags: ofidfieldmessage记录syncgpsheader
3条回答

你必须分头阅读。不是因为内存限制,而是因为解析需求。18MiB易于装入内存。在4Gb的机器上,它可以容纳200倍以上的内存

这是通常的设计模式

  1. 仅读取前4个字节。使用struct仅解压缩这些字节。 确认同步字节并获取标头长度

    如果你想要头的其余部分,你知道长度,读剩余的字节

    如果您不想要标题,请使用seek跳过它

  2. 读取记录的前四个字节以获得SV观测值的数量。使用struct将其解压缩

    计算并读取指定的字节数,以获得记录中的所有SV观测值

    打开它们,做你正在做的任何事情

    我强烈建议在对数据进行任何其他操作之前,先从数据中构建namedtuple对象

如果你想要所有的数据,你必须实际读取所有的数据

“如果不一次读取一个18MIB文件一个字节的话?”“我不理解这个限制。您必须读取所有字节才能获取所有字节

您可以使用长度信息读取有意义的块中的字节。但是您无法避免读取所有字节

此外,大量的读取(和查找)通常足够快。你的操作系统为你缓冲,所以不用担心尝试微优化读取的数量

只需遵循“读取长度读取数据”模式

除了编写正确读取文件的解析器外,您还可以尝试某种蛮力方法……将数据读取到内存中,并使用“Sync”哨兵将其拆分。警告-您可能会得到一些误报。但是

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

但这是一个相当恶劣的黑客行为

18MB应该可以很好地存储在内存中,所以我只需要用一个with open(thefile, 'rb') as f: data = f.read()将整个内容压缩成一个大的字节串,然后在片上执行所有的“解析”,以逐条记录前进。它更方便,并且可能比在文件中到处进行许多小的读取要快(尽管它不会影响下面的逻辑,因为在任何一种情况下,“数据中的当前关注点”总是在移动[[总是向前,就像它发生的那样]]通过基于每次解包几个字节的结构计算出的数量,以查找头和记录的长度)

给定“记录的开始”偏移量,您可以通过只查看一个字节(“字段4”,从报头开始的偏移量3,与记录的开始相同)和查看消息ID(下一个字段,2个字节)来确定其报头的长度,以确定它是否是您关心的记录(因此,仅包含这3个字节的结构解包应该足以满足此要求)

不管是不是你想要的记录,你接下来需要计算记录的长度(要么跳过它,要么全部得到);为此,您计算实际记录数据的开始(记录的开始加上标题的长度加上记录的下一个字段(标题后面的4个字节)乘以观察的长度(如果我读对了,则为32个字节)

通过这种方式,您可以隔离要提供给struct.unpack的子字符串(当您最终到达所需的记录时),或者只需将header+record的总长度添加到“start of record”偏移量,以获取下一个记录的开始偏移量

相关问题 更多 >