2010-03-02 11 views
2

En utilisant Python (3.1 ou 2.6), j'essaie de lire des données à partir de fichiers de données binaires produits par un récepteur GPS. Les données pour chaque heure sont stockées dans un fichier séparé, dont chacune est d'environ 18 MiB. Les fichiers de données ont plusieurs enregistrements de longueur variable, mais pour le moment je dois extraire des données d'un seul enregistrement.En utilisant Python, comment lire et extraire des données d'un fichier de données binaires avec plusieurs enregistrements de longueur variable?

Je dois aller jusqu'à décoder, en quelque sorte, l'en-tête. Je dis un peu parce que certains chiffres n'ont pas de sens, mais la plupart le font. Après avoir passé quelques jours sur ce sujet (j'ai commencé à apprendre à programmer en utilisant Python), je ne progresse pas, il est donc temps de demander de l'aide.

Le guide de référence me donne la structure d'en-tête de message et la structure d'enregistrement. Les en-têtes peuvent être de longueur variable mais sont généralement de 28 octets.

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. 

Voici mon code pour essayer de comprendre l'en-tête:

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) 

Il produit:

b'\xaa' b'D' b'\x12' 28 274 b'\x02' 32 1524 0 78 b'\xa0' 1573 126060000 10485760 3545 35358 

Les 3 champs de synchronisation doivent retourner Xaa x44 x12. (D est l'équivalent ascii de x44 - je suppose.)

L'ID d'enregistrement pour lequel je regarde est 274 - cela semble correct.

La semaine GPS est retournée sous 1573 - cela semble correct.

Millisecondes est retourné comme 126060000 - je me attendais 126015000.

Comment puis-je faire pour trouver les dossiers identifiés comme 274 et les extraire? (J'apprends Python, et la programmation, alors gardez en tête la réponse que vous donnez un codeur expérimenté peut être sur ma tête.)

+0

Simplifiez votre vie. 'print (hex (x), hex (y), hex (z)' pour voir les valeurs hexadécimales.Vous n'avez donc pas besoin de deviner si 'b'D'' est' b '\ x44'' –

Répondre

2

18 MB devrait aller confortablement dans la mémoire, donc je voudrais juste avaler le tout dans une grande chaîne d'octets avec un seul with open(thefile, 'rb') as f: data = f.read() et ensuite effectuer tous les "analyse" sur les tranches pour avancer l'enregistrement par enregistrement. C'est plus pratique, et peut être plus rapide que de faire beaucoup de petites lectures d'ici et là dans le fichier (bien que cela n'affecte pas la logique ci-dessous, car dans les deux cas le "point d'intérêt actuel" est toujours en mouvement. [toujours en avant, comme cela arrive]] par des montants calculés sur la struct-unpacking de quelques octets à la fois, pour trouver la longueur des en-têtes et des enregistrements). Étant donné le décalage "début d'un enregistrement", vous pouvez déterminer la longueur de l'en-tête en regardant un seul octet ("champ quatre", décalage 3 du début de l'en-tête qui est le même que le début de l'enregistrement) ID (champ suivant, 2 octets) pour voir si c'est l'enregistrement qui vous intéresse (donc un struct unpack de seulement 3 octets devrait suffire pour ça).

Qu'il s'agisse de l'enregistrement que vous souhaitez ou non, vous devez ensuite calculer la longueur de l'enregistrement (soit pour l'ignorer, soit pour l'obtenir entièrement); pour cela, vous calculez le début des données d'enregistrement (début d'enregistrement plus longueur d'en-tête plus le champ suivant de l'enregistrement (les 4 octets juste après l'en-tête) fois la longueur d'une observation (32 octets si je vous lis bien De cette façon, vous isolez la sous-chaîne à donner à struct.unpack (lorsque vous avez finalement atteint l'enregistrement que vous voulez), ou ajoutez simplement la longueur totale de l'en-tête + enregistrement au décalage de "début d'enregistrement", pour obtenir le décalage pour le début de l'enregistrement suivant.

+0

Le système d'exploitation fait une mise en mémoire tampon, le surcoût en utilisant la lecture n'est pas si élevé – ondra

+1

@ondra, les systèmes d'exploitation font des tampons différents (surtout si vous cherchez dans le fichier), et dans mon expérience c'est souvent plus rapide (et généralement plus pratique) , pour des fichiers de quelques mégaoctets, pour les traiter tous en même temps, puis travailler sur les données en mémoire.Si la performance est un goulot d'étranglement crucial, bien sûr, il vaut la peine de comparer les deux possibilités! –

+0

Merci Alex. d'une observation est 148 octets.J'ai essayé d'énumérer quelques champs pour donner une idée des données avec lesquelles je travaille - édité ma question pour montrer la longueur correcte de l'observation. – ljt

5

Vous devez lire en morceaux. Pas à cause de contraintes de mémoire, mais à cause des exigences d'analyse. 18 MoB s'inscrit dans la mémoire facilement. Sur une machine 4Gb, il est 200 fois en mémoire.

Voici le motif de conception habituel.

  1. Lire les 4 premiers octets uniquement. Utilisez struct pour décompresser uniquement ces octets. Confirmer les octets de synchronisation et obtenir la longueur de l'en-tête.

    Si vous voulez le reste de l'en-tête, vous connaissez la longueur, lire les autres octets.

    Si vous ne voulez pas l'en-tête, utilisez seek pour l'ignorer.

  2. Lire les quatre premiers octets d'un enregistrement pour obtenir le nombre d'observations SV. Utilisez struct pour le décompresser. Faites le calcul et lisez le nombre d'octets indiqué pour obtenir toutes les observations SV dans l'enregistrement.

    Déballez-les et faites ce que vous faites.

    Je suggère fortement de construire des objets namedtuple à partir des données avant de faire quoi que ce soit d'autre.

Si vous voulez toutes les données, vous devez réellement lire toutes les données.

"et sans lire un fichier de 18 Mio un octet à la fois)?" Je ne comprends pas cette contrainte. Vous devez lire tous les octets pour obtenir tous les octets.

Vous pouvez utiliser les informations de longueur pour lire les octets en segments significatifs. Mais vous ne pouvez pas éviter de lire tous les octets.

De plus, beaucoup de lectures (et de recherches) sont souvent assez rapides. Vos tampons d'OS pour vous, alors ne vous inquiétez pas d'essayer de micro-optimiser le nombre de lectures. Il suffit de suivre le modèle "lire la longueur - lire les données".

+0

Merci. Par un octet à la fois, je voulais dire que je ne suis pas limité par la mémoire ou le processeur. – ljt

+0

@ljt: "Contraint"? 18MiB n'est guère dans le voisinage d'une «contrainte». Si vous êtes aussi vieux que moi, vous vous souviendrez peut-être quand 18K était un fichier énorme. Mon ordinateur portable a 4G de RAM; un fichier 18M s'y loge 200 fois. Vous n'avez pas besoin de vous inquiéter de "contrainte" jusqu'à ce que vos fichiers atteignent la taille de 1 Go ou plus. –

1

En plus d'écrire un analyseur qui lit correctement le fichier, peut essayer une approche un peu brutale ... lire les données en mémoire et les scinder à l'aide de la sentinelle 'Sync'. Attention - vous pourriez avoir des faux positifs. Mais ...

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

Mais il est plutôt un hack très méchant ...

+0

Merci. Bien que je ne sois pas convaincu de savoir ce que je fais, je suis peut-être un peu moins confus que je ne l'étais hier. – ljt