Используя Python, как мне прочитать и извлечь данные из двоичного файла данных с несколькими записями переменной длины? - PullRequest
3 голосов
/ 02 марта 2010

Используя Python (3.1 или 2.6), я пытаюсь прочитать данные из двоичных файлов данных, созданных приемником GPS. Данные за каждый час хранятся в отдельном файле, каждый из которых составляет около 18 МиБ. Файлы данных имеют несколько записей переменной длины, но сейчас мне нужно извлечь данные только из одной из записей.

У меня есть возможность немного расшифровать заголовок. Я говорю несколько, потому что некоторые цифры не имеют смысла, но большинство из них. Потратив на это несколько дней (я начал учиться программировать на 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 является эквивалентом Ascii x44 - я полагаю.)

Идентификатор записи, которую я ищу, - 274 - это кажется правильным.

Неделя GPS возвращается как 1573 - кажется правильным.

Миллисекунды возвращаются как 126060000 - я ожидал 126015000.

Как мне найти записи, идентифицированные как 274, и извлечь их? (Я изучаю Python и программирую, поэтому имейте в виду, что ответ, который вы дадите опытному программисту, может быть у меня над головой.)

Ответы [ 3 ]

6 голосов
/ 02 марта 2010

Вы должны читать по частям. Не из-за ограничений памяти, а из-за требований разбора. 18MiB легко помещается в памяти. На 4Gb машине он вмещается в память в 200 раз больше.

Вот обычный шаблон дизайна.

  1. Читать только первые 4 байта. Используйте struct, чтобы распаковать только эти байты. Подтвердите синхронизацию байтов и получите длину заголовка.

    Если вы хотите остаток заголовка, вы знаете длину, прочитайте остаток байтов.

    Если вы не хотите заголовок, используйте seek, чтобы пропустить его.

  2. Считайте первые четыре байта записи, чтобы получить количество наблюдений SV. Используйте struct, чтобы распаковать его.

    Выполните математику и прочитайте указанное количество байтов, чтобы получить все наблюдения SV в записи.

    Распакуйте их и делайте все, что вы делаете.

    Я настоятельно рекомендую создавать namedtuple объекты из данных, прежде чем делать с ними что-либо еще.

Если вы хотите получить все данные, вам нужно прочитать все данные.

"и без чтения файла размером 18 МБ по одному байту за раз)?" Я не понимаю это ограничение. Вы должны прочитать все байты, чтобы получить все байты.

Вы можете использовать информацию о длине для чтения байтов значащими кусками. Но вы не можете избежать чтения всех байтов.

Кроме того, многие операции чтения (и поиска) часто выполняются достаточно быстро. Ваша операционная система буферизуется для вас, так что не беспокойтесь о попытке микрооптимизировать количество операций чтения.

Просто следуйте шаблону «длина чтения - чтение данных».

2 голосов
/ 02 марта 2010

Помимо написания синтаксического анализатора, который правильно читает файл, вы можете попробовать несколько грубый метод ... прочитать данные в память и разделить их, используя страж 'Синхронизация'.Предупреждение - вы можете получить некоторые ложные срабатывания.Но ...

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

Но это довольно неприятный хак ...

2 голосов
/ 02 марта 2010

18 МБ должно удобно помещаться в памяти, поэтому я просто сгребаю все это в одну большую строку байтов с одним with open(thefile, 'rb') as f: data = f.read(), а затем выполняю весь «синтаксический анализ» срезов, чтобы продвигать запись за записью. Это более удобно и может быть быстрее, чем многократное чтение отсюда и там в файле (хотя это не влияет на логику ниже, потому что в любом случае «текущая точка интереса к данным» всегда движется [ [всегда вперед, как это происходит]] суммами, вычисленными на основе распаковки структуры из нескольких байтов за раз, чтобы найти длины заголовков и записей).

Учитывая смещение «начало записи», вы можете определить длину его заголовка, взглянув всего на один байт («поле четыре», смещение 3 от начала заголовка, которое совпадает с началом записи), и посмотреть на идентификатор сообщения. (следующее поле, 2 байта), чтобы увидеть, нужна ли вам запись (поэтому для этого достаточно распаковать структуру только из этих 3 байтов).

Независимо от того, хотите ли вы эту запись или нет, вам необходимо вычислить длину записи (либо пропустить ее, либо получить всю); для этого вы вычисляете начало фактических данных записи (начало записи плюс длина заголовка плюс следующее поле записи (4 байта сразу после заголовка), умноженное на длину наблюдения (32 байта, если я вас правильно прочитал) ).

Таким образом, вы либо изолируете подстроку, которая будет передана в struct.unpack (когда вы наконец достигли нужной записи), либо просто добавляете общую длину заголовка + записи к смещению «начало записи», чтобы получить смещение для начала следующей записи.

...